alroyso 1 month ago
parent
commit
0ef2bfc4be

+ 4 - 4
lib/app/clash/service/clash_service.dart

@@ -242,9 +242,9 @@ class ClashService extends GetxController {
           'TLS': {
             'ports': [443, 8443],
           },
-          'QUIC': {
-            'ports': [443, 8443],
-          },
+          // 'QUIC': {
+          //   'ports': [443, 8443],
+          // },
         },
         skipDomain: ['www.baidu.com'],
       ),
@@ -316,7 +316,7 @@ class ClashService extends GetxController {
               server: node.host ?? '',
               port: node.port ?? 0,
               udp: node.udp == 1,
-              flow: 'xtls-rprx-vision',
+              flow: '',
               tls: true,
               servername: node.v2Sni,
               realityOpts: {'public-key': node.vlessPulkey},

+ 4 - 4
lib/app/controller/GlobalController.dart

@@ -59,10 +59,10 @@ class GlobalController extends GetxController {
     var PathclashName = path.basename(Files.assetsCCore.path);
     LogHelper().d('clash_name {$clashName}');
     LogHelper().d('Path {$PathclashName}');
-    if(Platform.isMacOS)
-    {
-      await _wlBaseHelpPlugin.killProcess(ClashName.name);
-    }
+    // if(Platform.isMacOS)
+    // {
+    //   await _wlBaseHelpPlugin.killProcess(ClashName.name);
+    // }
     watchExit();
     await controllers.config.initConfig();
     await controllers.cc_service.initConfig();

+ 26 - 9
lib/app/modules/login/controllers/login_controller.dart

@@ -21,21 +21,38 @@ class LoginController extends GetxController {
   var isReg = false.obs;
   Future<void> fetchLogin(String username, String password) async {
     try {
-      if(username.isEmpty || password.isEmpty){
+      if (username.trim().isEmpty || password.trim().isEmpty) {
         controllers.global.handleApiError(Exception("账号密码不能为空"));
         return;
       }
 
       isLoading.value = true;
-      loginModes.value = await ApiService().login(kLogin,data: {"email":username,"password":password});
-
-      if(loginModes.value.accessToken != null){
-        await SharedPreferencesUtil().setString("token", loginModes.value.accessToken.toString());
-        var userModes = LocalUser(email: username,password: password,accessToken: loginModes.value.accessToken.toString());
-        await SharedPreferencesUtil().setString("localUser", JsonMapper.serialize(userModes));
+      loginModes.value = await ApiService().login(
+        kLogin,
+        data: {
+          "email": username.trim(),
+          "password": password,
+        }
+      );
+
+      final token = loginModes.value.accessToken;
+      if (token != null) {
+        await Future.wait([
+          SharedPreferencesUtil().setString("token", token),
+          SharedPreferencesUtil().setString(
+            "localUser",
+            JsonMapper.serialize(
+              LocalUser(
+                email: username.trim(),
+                password: password,
+                accessToken: token,
+              )
+            )
+          ),
+        ]);
       }
-      Get.offNamed(Routes.HOME,arguments: loginModes.value);
-
+      
+      Get.offNamed(Routes.HOME, arguments: loginModes.value);
     } catch (e) {
       controllers.global.handleApiError(e);
     } finally {

+ 106 - 21
lib/app/network/dio_client.dart

@@ -2,12 +2,16 @@ import 'dart:convert';
 import 'dart:io';
 
 import 'package:connectivity_plus/connectivity_plus.dart';
+import 'package:dart_json_mapper/dart_json_mapper.dart';
 import 'package:dio/dio.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:logger/logger.dart';
 import 'package:naiyouwl/app/common/LogHelper.dart';
+import 'package:synchronized/synchronized.dart' as synchronized;
+
 
 import '../common/SharedPreferencesUtil.dart';
+import '../data/model/LocalUser.dart';
 //import 'custom_interceptors.dart';
 
 class DioClient {
@@ -18,7 +22,6 @@ class DioClient {
   final Logger _logger = Logger();
 
   DioClient._internal() {
-
     _dio = Dio(BaseOptions(
       baseUrl: 'https://api.androidrj01.top', // 你的API地址
       connectTimeout: 5000,
@@ -36,21 +39,18 @@ class DioClient {
       ));
       return true;
     }());
-    final customInterceptor = CustomInterceptors(_dio);
-    //token
-    _dio.interceptors.add(TokenInterceptor());
-    // 添加拦截器
-    _dio.interceptors.add(customInterceptor);
 
-    // 添加响应拦截器
+    // 修改拦截器添加顺序
+    // 1. 先添加 Token 拦截器
+    _dio.interceptors.add(TokenInterceptor(_dio));
+    
+    // 2. 再添加响应处理拦截器
     _dio.interceptors.add(InterceptorsWrapper(
       onResponse: (Response<dynamic> response, ResponseInterceptorHandler handler) async {
         final responseData = response.data as Map<String, dynamic>;
-        //LogHelper().d("当前地址:==== ${response.requestOptions.baseUrl} ");
-        LogHelper().d("data:==== ${responseData} ");
+        //LogHelper().d("data:==== ${responseData} ");
         await SharedPreferencesUtil().setString("last_successful_url", response.requestOptions.baseUrl);
         if (responseData['ret'] == 1) {
-          customInterceptor.resetRetryCount();  // 当请求成功时重置重试计数器
           handler.next(
             Response<dynamic>(
               data: responseData['data'],
@@ -70,7 +70,9 @@ class DioClient {
       },
     ));
 
-
+    // 3. 最后添加自定义拦截器(处理备用地址)
+    final customInterceptor = CustomInterceptors(_dio);
+    _dio.interceptors.add(customInterceptor);
   }
   void updateBaseUrl(String newBaseUrl) {
     _dio.options.baseUrl = newBaseUrl;
@@ -79,17 +81,100 @@ class DioClient {
 }
 
 class TokenInterceptor extends Interceptor {
-  // 这里假设您有一个方法来从安全存储中获取Token
-  Future<String?> getToken() async {
-    // 从您存储Token的地方获取Token,例如从SharedPreferences或SecureStorage
-    String? token = await SharedPreferencesUtil().getString("token");
-    return token;
-    //return "Your_Token";
+  final _lock = synchronized.Lock();
+  bool _isRefreshing = false;
+  final Dio _dio;  // 添加 Dio 实例引用
+
+  TokenInterceptor(this._dio);  // 添加构造函数
+
+  Future<String?> _refreshToken() async {
+    try {
+      // 从本地获取用户信息
+      final localUserStr = await SharedPreferencesUtil().getString("localUser");
+      if (localUserStr == null) {
+        return null;
+      }
+
+      // 解析本地用户信息
+      final localUser = JsonMapper.deserialize<LocalUser>(localUserStr);
+      if (localUser == null) {
+        return null;
+      }
+
+      // 使用保存的邮箱和密码重新登录
+      final response = await _dio.post(
+        '/api/client/v3/login',
+        data: {
+          'email': localUser.email?.trim(),
+          'password': localUser.password,
+        },
+      );
+
+      // 直接使用 response.data,因为响应拦截器已经处理过了
+      final accessToken = response.data['access_token'];
+      if (accessToken != null) {
+        // 更新本地用户信息
+        await SharedPreferencesUtil().setString(
+          "localUser",
+          JsonMapper.serialize(
+            LocalUser(
+              email: localUser.email,
+              password: localUser.password,
+              accessToken: accessToken,
+            )
+          )
+        );
+        return accessToken;
+      }
+      return null;
+    } catch (e) {
+      LogHelper().e("刷新token失败: $e");
+      return null;
+    }
+  }
+
+  @override
+  Future<void> onError(DioError err, ErrorInterceptorHandler handler) async {
+    // 只处理401错误,其他错误传递给下一个拦截器
+    if (err.response?.statusCode == 401 && !err.requestOptions.path.contains('/user/login')) {
+      LogHelper().d("Token过期,尝试刷新...");
+      
+      RequestOptions options = err.requestOptions;
+      
+      return await _lock.synchronized(() async {
+        try {
+          if (!_isRefreshing) {
+            _isRefreshing = true;
+            final newToken = await _refreshToken();
+            if (newToken != null) {
+              // 更新token
+              await SharedPreferencesUtil().setString("token", newToken);
+              // 使用新token重试原请求
+              options.headers["Authorization"] = "Bearer $newToken";
+              
+              // 创建新的dio实例来避免拦截器循环
+              final clonedDio = Dio()..options = _dio.options;
+              final response = await clonedDio.fetch(options);
+              
+              _isRefreshing = false;
+              return handler.resolve(response);
+            }
+          }
+        } catch (e) {
+          LogHelper().e("刷新token失败: $e");
+        }
+        _isRefreshing = false;
+        err.error = AppException(message: "登录已过期,请重新登录");
+        return handler.next(err);
+      });
+    }
+    return handler.next(err);
   }
 
+  // 添加请求拦截器来设置token
   @override
   Future<void> onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
-    final token = await getToken();
+    final token = await SharedPreferencesUtil().getString("token");
     if (token != null) {
       options.headers["Authorization"] = "Bearer $token";
     }
@@ -111,7 +196,7 @@ class CustomInterceptors extends Interceptor {
   }
   @override
   Future<void> onError(DioError err, ErrorInterceptorHandler handler) async {
-    LogHelper().d("onError === $_retryCount");
+    //LogHelper().d("onError === $_retryCount");
     // 检查网络连接状态
     bool isConnectNetWork = await isConnected();
 
@@ -120,7 +205,7 @@ class CustomInterceptors extends Interceptor {
       err.error = AppException(message: "当前网络不可用,请检查您的网络");
       return handler.next(err);
     } else if (err.error is SocketException || err.type == DioErrorType.other || err.type == DioErrorType.connectTimeout || err.type == DioErrorType.sendTimeout || err.type == DioErrorType.receiveTimeout) {
-      LogHelper().d("错误类型:==== ${err.type}");
+      //LogHelper().d("错误类型:==== ${err.type}");
       if (_retryCount < _backupUrls.length) {
         // 有网络连接但请求失败,尝试使用备用地址
         err.requestOptions.baseUrl = _backupUrls[_retryCount];
@@ -147,7 +232,7 @@ class CustomInterceptors extends Interceptor {
 
     // 其他错误,统一处理
     AppException appException = AppException.create(err);
-    debugPrint('DioError===: ${appException.toString()}');
+   // debugPrint('DioError===: ${appException.toString()}');
     err.error = appException;
     return handler.next(err);
   }