You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

157 lines
4.8 KiB

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<T>(
NWMethod method,
String path, {
Object? params,
Map<String, dynamic>? queryParameters,
}) async {
Response response = await dio.request(
path,
data: params,
queryParameters: queryParameters,
options: Options(method: nwMethodValues[method]),
);
return response.data;
}
/// 下载二进制字节(支持可选忽略证书,仅限移动/桌面端)
Future<List<int>> 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<int> 返回
final data = resp.data;
if (data is List<int>) return data;
// 某些平台可能返回 Uint8List
if (data is List) {
return List<int>.from(data);
}
throw Exception('下载返回格式异常');
}
throw Exception('下载失败($status)');
}
/// 按 .env 配置决定是否允许忽略证书
Future<List<int>> downloadBytesWithEnv(String url) async {
final allow = (dotenv.env['ALLOW_INSECURE_SSL'] ?? 'false').toLowerCase() == 'true';
return downloadBytes(url, allowInsecure: allow);
}
/// 使用 Dio 的 download API 下载到临时文件并读出字节
Future<List<int>> 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<List<int>> 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';
}
}
}