From 47569bdfc6342f418825c3a99b054971d3808c8c Mon Sep 17 00:00:00 2001 From: wuqifeng <540416539@qq.com> Date: Fri, 17 Jul 2026 00:28:59 +0800 Subject: [PATCH] feat:支持按需启用 Whistle 抓包 --- README.md | 28 ++++++++++++++++++++++++++++ lib/common/request/request_client.dart | 32 ++++++++++++++++++++++++++++---- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c7eb711..f8739ad 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,31 @@ # Wow English * flutter版本号:3.19.2 + +## Android 使用 Whistle 抓包 + +为了让 Dio 的 HTTPS 请求能够经过 Whistle 解密,Android Debug 包默认允许任意服务器证书,不需要在项目中内置或指定 Whistle Root CA。普通 Release 包仍使用系统默认的严格证书校验。 + +### 构建方式 + +Debug 包默认允许 Whistle 抓包: + +```bash +flutter build apk --debug +``` + +构建允许 Whistle 抓包的 Release 包: + +```bash +flutter build apk --release --dart-define=ENABLE_WHISTLE_CA=true +``` + +构建正常生产 Release 包(不允许 Whistle 抓包): + +```bash +flutter build apk --release +``` + +手机通过 Clash Meta 将流量转发到运行 Whistle 的电脑后,即可抓取 Dio 发出的 HTTPS 请求。因为抓包构建不绑定具体 CA,所以更换 Whistle 实例或重新生成 Root CA 后无需修改代码和重新导入证书。 + +> Debug 包以及带 `ENABLE_WHISTLE_CA=true` 的 Release 包会接受任意 HTTPS 证书,无法抵御中间人攻击。此类 APK 只应在内部测试设备和测试人员之间分发,不能作为正式生产包发布。 diff --git a/lib/common/request/request_client.dart b/lib/common/request/request_client.dart index 848c9b3..e3ad890 100644 --- a/lib/common/request/request_client.dart +++ b/lib/common/request/request_client.dart @@ -1,6 +1,8 @@ import 'dart:convert'; +import 'dart:io'; import 'package:dio/dio.dart'; +import 'package:dio/io.dart'; import 'package:flutter/foundation.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; import 'package:wow_english/utils/toast_util.dart'; @@ -17,14 +19,32 @@ part 'apis.dart'; RequestClient requestClient = RequestClient(); class RequestClient { + static const bool _enableWhistleCaptureInRelease = bool.fromEnvironment( + 'ENABLE_WHISTLE_CA', + defaultValue: false, + ); + late Dio _dio; RequestClient() { - _dio = Dio(BaseOptions(baseUrl: RequestConfig.baseUrl, connectTimeout: RequestConfig.connectTimeout)); + _dio = Dio(BaseOptions( + baseUrl: RequestConfig.baseUrl, + connectTimeout: RequestConfig.connectTimeout)); _dio.interceptors.add(TokenInterceptor()); if (kDebugMode) { - _dio.interceptors - .add(PrettyDioLogger(requestHeader: true, requestBody: true, responseHeader: true, maxWidth: 120)); + _dio.interceptors.add(PrettyDioLogger( + requestHeader: true, + requestBody: true, + responseHeader: true, + maxWidth: 120)); + } + + // 仅供内部抓包构建使用;启用后会接受任意 HTTPS 证书。 + if (Platform.isAndroid && (kDebugMode || _enableWhistleCaptureInRelease)) { + _dio.httpClientAdapter = IOHttpClientAdapter( + createHttpClient: () => + HttpClient()..badCertificateCallback = (_, __, ___) => true, + ); } } @@ -79,7 +99,11 @@ class RequestClient { bool Function(ApiException)? onError, }) { return request(url, - method: 'GET', queryParameters: queryParameters, headers: headers, onResponse: onResponse, onError: onError); + method: 'GET', + queryParameters: queryParameters, + headers: headers, + onResponse: onResponse, + onError: onError); } /// post -- libgit2 0.22.2