import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:get/get.dart' hide Response; import 'package:flutter/foundation.dart'; import 'dart:io'; import 'package:path/path.dart' as p; import 'auth_interceptor.dart'; import 'nw_method.dart'; class DioManager { static final DioManager _shared = DioManager._internal(); factory DioManager() => _shared; late Dio dio; // 根据地区获取服务器URL String get baseUrl { final countryCode = Get.deviceLocale?.countryCode?.toUpperCase(); final isChinaMainland = countryCode == 'CN'; if (!isChinaMainland) { return dotenv.env['HW_SERVER_URL'] ?? dotenv.env['SERVER_URL'] ?? ''; } else { return dotenv.env['SERVER_URL'] ?? ''; } } DioManager._internal() { BaseOptions options = BaseOptions( baseUrl: baseUrl, contentType: Headers.jsonContentType, responseType: ResponseType.json, connectTimeout: const Duration(seconds: 30), receiveTimeout: const Duration(seconds: 30), ); dio = Dio(options); dio.interceptors.add(AuthInterceptor()); } // 请求,返回参数为 T // method:请求方法,NWMethod.POST等 // path:请求地址 // params:请求参数 Future request( NWMethod method, String path, { Object? params, Map? queryParameters, }) async { Response response = await dio.request( path, data: params, queryParameters: queryParameters, options: Options(method: nwMethodValues[method]), ); return response.data; } /// 下载二进制字节(支持可选忽略证书,仅限移动/桌面端) Future> downloadBytes(String url, {bool allowInsecure = false}) async { // 使用独立 Dio,避免全局适配器被修改 final client = Dio(BaseOptions( responseType: ResponseType.bytes, followRedirects: true, receiveTimeout: const Duration(seconds: 60), connectTimeout: const Duration(seconds: 60), )); client.interceptors.add(AuthInterceptor()); // 仅在非Web端尝试忽略证书 if (allowInsecure && !kIsWeb) { client.httpClientAdapter = IOHttpClientAdapter(createHttpClient: () { // ignore: unnecessary_new final ioClient = new HttpClient(); ioClient.badCertificateCallback = (cert, host, port) => true; return ioClient; }); } final resp = await client.get(url); final status = resp.statusCode ?? 0; if (status >= 200 && status < 300) { // Dio v5 将 bytes 直接作为 List 返回 final data = resp.data; if (data is List) return data; // 某些平台可能返回 Uint8List if (data is List) { return List.from(data); } throw Exception('下载返回格式异常'); } throw Exception('下载失败($status)'); } /// 按 .env 配置决定是否允许忽略证书 Future> downloadBytesWithEnv(String url) async { final allow = (dotenv.env['ALLOW_INSECURE_SSL'] ?? 'false').toLowerCase() == 'true'; return downloadBytes(url, allowInsecure: allow); } /// 使用 Dio 的 download API 下载到临时文件并读出字节 Future> downloadBytesUsingDownload(String url, {bool allowInsecure = false}) async { final client = Dio(BaseOptions( followRedirects: true, receiveTimeout: const Duration(seconds: 120), connectTimeout: const Duration(seconds: 60), )); client.interceptors.add(AuthInterceptor()); if (allowInsecure && !kIsWeb) { client.httpClientAdapter = IOHttpClientAdapter(createHttpClient: () { final ioClient = HttpClient(); ioClient.badCertificateCallback = (cert, host, port) => true; return ioClient; }); } final tmpDir = await Directory.systemTemp.createTemp('voice_pkg_'); final baseName = _fileNameFromUrl(url); final savePath = p.join(tmpDir.path, baseName); await client.download( url, savePath, options: Options(responseType: ResponseType.bytes), ); final file = File(savePath); final bytes = await file.readAsBytes(); // 清理临时文件 try { await file.delete(); await tmpDir.delete(recursive: true); } catch (_) {} return bytes; } /// 读取 .env 决定是否忽略证书,使用 download API Future> downloadBytesUsingDownloadWithEnv(String url) async { final allow = (dotenv.env['ALLOW_INSECURE_SSL'] ?? 'false').toLowerCase() == 'true'; return downloadBytesUsingDownload(url, allowInsecure: allow); } String _fileNameFromUrl(String url) { try { final clean = url.split('?').first; final last = clean.split('/').last; if (last.isEmpty) return 'voice.pkg'; return last; } catch (_) { return 'voice.pkg'; } } }