import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:networks_sdk/networks_sdk.dart'; import '../../core/foundation/api_paths.dart'; import '../../core/foundation/config.dart'; import '../../core/foundation/constants.dart'; import '../../core/foundation/errors.dart'; import '../../core/foundation/utils.dart'; import '../../core/services/network_monitor.dart'; import '../../core/services/socket_manager.dart'; // ── 网络状态监听 ────────────────────────────────────────────────────────────── /// 网络状态监听 Provider(全局单例) /// /// 基于 connectivity_plus 监听平台网络变化, /// 作为公共服务供多个模块使用: /// - SocketManager:网络变化时自动断连/重连 WebSocket /// - HTTP 层:请求前检查网络可用性 /// - UI 层:显示网络状态提示 /// /// ## 使用 /// /// ```dart /// // 查询当前状态 /// final isConnected = ref.read(networkMonitorProvider).isConnected; /// /// // 监听状态变化 /// ref.listen(networkMonitorProvider, (prev, monitor) { /// monitor.onStatusChanged.listen((isAvailable) { /// // 处理网络变化 /// }); /// }); /// ``` final networkMonitorProvider = Provider((ref) { final monitor = NetworkMonitor( onLog: (message, {tag}) { // ignore: avoid_print print('[${tag ?? 'Network'}] $message'); }, ); ref.onDispose(() { monitor.dispose(); }); return monitor; }); // ── Token 更新事件流 ───────────────────────────────────────────────────────── /// Token 更新事件流 /// /// apiConfigProvider.onTokenUpdated → 推送新 token 到此流 /// socketManagerProvider → 监听此流 → 同步 token 到 WebSocket /// onBeforeReconnect 中刷新 token 后调用 apiConfig.updateToken → tokenStream.add, /// 需要同步传播到 socketManager.updateToken → socketClient._currentToken, /// 确保随后的 _doConnect() 使用新 token。异步模式下 _doConnect 会在 stream final _tokenUpdateStreamProvider = Provider>((ref) { final controller = StreamController.broadcast(sync: true); ref.onDispose(controller.close); return controller; }); // ── HTTP 基础设施 ───────────────────────────────────────────────────────────── /// API 配置 Provider(全局单例) /// /// 从 [AppConfig.apiBaseUrl](config.json → --dart-define-from-file)读取 baseURL, /// 注入到 Network SDK 作为所有 HTTP 请求的基础 URL。 /// /// [onCheckNetworkAvailable] 由 [networkMonitorProvider](公共服务)注入, /// 请求前先判断网络状态,无网络时直接抛 [ApiError.noNetworkConnection]。 final apiConfigProvider = Provider((ref) { final networkMonitor = ref.read(networkMonitorProvider); final tokenStream = ref.read(_tokenUpdateStreamProvider); return ApiConfig( baseURL: AppConfig.apiBaseUrl, platformHeaders: { 'Platform': 'Android', // TODO: 运行时从 platform API 获取 'client-version': '1.0.0', // TODO: 运行时从 package_info 获取 'Channel': '', // TODO: 从 AppConfig 读取渠道标识 'lang': 'zh-CN', // TODO: 从 l10n_sdk 或系统 locale 动态获取 }, tokenExpiredCodes: ApiErrorCodes.tokenExpiredCodes, forceLogoutCodes: ApiErrorCodes.forceLogoutCodes, onForceLogout: () { // TODO: 清除登录态,跳转登录页 }, onTokenRefresh: () async { // TODO: App 层刷新 token 逻辑 return null; }, onTokenUpdated: (newToken) { // 通过事件流同步到 WebSocket,避免直接引用 socketManagerProvider 造成循环依赖 tokenStream.add(newToken); }, onCheckNetworkAvailable: () async => networkMonitor.isConnected, onEncryptRequest: null, // TODO: 接入 cipher_guard_sdk 后注入请求加密回调 onDecryptResponse: null, // TODO: 接入 cipher_guard_sdk 后注入响应解密回调 onBusinessError: null, // TODO: 接入业务错误统一处理(弹窗 / Toast / 跳转等) onTransformResponse: null, // TODO: 如后端响应格式非标准,在此归一化为 { code, data, message } onGetTokenExpiry: parseJwtExpiry, maxRetries: AppConstants.maxRetries, retryBaseDelay: AppConstants.retryBaseDelay, onLog: (message, {tag}) { // ignore: avoid_print print('[${tag ?? 'Network'}] $message'); }, ); }); /// API 客户端 Provider(全局单例) /// /// 含拦截器(Auth / Retry / Logging)、超时配置。 final networkSdkApiProvider = Provider((ref) { final config = ref.read(apiConfigProvider); return NetworksSdkApi()..initialize(config); }); // ── WebSocket 基础设施 ──────────────────────────────────────────────────────── /// SocketConfig Provider(内部使用,不对外暴露) /// /// 与 apiConfigProvider 对称,通过回调注入 App 层能力, /// SDK 内部不调用其他 SDK。 final _socketConfigProvider = Provider((ref) { final networkMonitor = ref.read(networkMonitorProvider); return SocketConfig( maxReconnectAttempts: AppConstants.maxRetries, maxReconnectDelay: AppConstants.maxReconnectDelay, unlimitedReconnect: true, // IM 场景始终保持连接 onBuildConnectUrl: null, // TODO: 接入 cipher_guard_sdk 后注入 WS URL 加密(路径/token/cipher 参数) onEncryptMessage: null, // TODO: 接入 cipher_guard_sdk 后注入消息加密回调 onDecryptMessage: null, // TODO: 接入 cipher_guard_sdk 后注入消息解密回调 onBeforeReconnect: () async { // SocketClient 内部重连(心跳超时、stream onDone)前调用。 // 与 SocketManager.onBeforeReconnect 职责相同:检查 token 并按需刷新。 // 刷新后通过 sync stream 同步传播到 SocketClient._currentToken, // 确保随后的 _doConnect() 使用新 token。 final apiConfig = ref.read(apiConfigProvider); final currentToken = apiConfig.token; if (currentToken == null || apiConfig.onGetTokenExpiry == null) return; final expiry = apiConfig.onGetTokenExpiry!(currentToken); if (expiry == null) return; final remaining = expiry.difference(DateTime.now()); if (remaining > apiConfig.proactiveRefreshThreshold) return; // ignore: avoid_print print( '[Socket] Token expiring in ${remaining.inMinutes}min, refreshing before reconnect', ); final newToken = await apiConfig.onTokenRefresh?.call(); if (newToken != null && newToken.isNotEmpty) { // updateToken → onTokenUpdated → sync stream → manager.updateToken // → _client.updateToken → socketClient._currentToken 同步更新 apiConfig.updateToken(newToken); } }, onLog: (message, {tag}) { // ignore: avoid_print print('[${tag ?? 'Socket'}] $message'); }, onCheckNetworkAvailable: () async { return networkMonitor.isConnected; }, ); }); /// SocketClient Provider(内部使用,不对外暴露) /// /// 与 networkSdkApiProvider 对称。 final _socketClientProvider = Provider((ref) { final config = ref.read(_socketConfigProvider); return NetworksMessagingApi()..initialize(config); }); /// SocketManager Provider /// /// 封装连接生命周期、网络/前后台事件响应、操作前置检查、消息预处理。 /// 业务模块通过此 Provider 访问 WebSocket 能力。 /// /// ## 前置检查 /// /// connect / send 前先检查网络可用性 + 后台状态, /// 无效操作直接跳过,避免无意义的网络请求。 /// 与 HTTP 层 [ApiClient.executeRequest] 的网络前置检查对称。 /// /// ## 事件驱动 /// /// 网络状态变化由 [networkMonitorProvider](公共服务)驱动, /// 自动触发断连/重连。 /// /// Token 更新由 [_tokenUpdateStreamProvider] 事件流驱动, /// HTTP 层刷新 token 后自动同步到 WebSocket。 /// /// onMessageTransform 参考 HTTP 层 onTokenRefresh 的回调模式: /// 后续接入加解密 SDK 时,在此注入解密回调, /// SDK 内部不调用其他 SDK。 final socketManagerProvider = Provider((ref) { final client = ref.read(_socketClientProvider); final networkMonitor = ref.read(networkMonitorProvider); final apiConfig = ref.read(apiConfigProvider); final tokenStream = ref.read(_tokenUpdateStreamProvider); final manager = SocketManager( client: client, wsUrl: _buildWsUrl(AppConfig.apiBaseUrl), disconnectInBackground: false, // 所有平台后台保活,心跳不停、连接不断 onMessageTransform: null, // TODO: 接入加解密 SDK 后注入解密回调 onBeforeReconnect: () async { // 重连前检查 token 是否即将过期,是则主动刷新 final currentToken = apiConfig.token; if (currentToken == null || apiConfig.onGetTokenExpiry == null) return; final expiry = apiConfig.onGetTokenExpiry!(currentToken); if (expiry == null) return; final remaining = expiry.difference(DateTime.now()); if (remaining > apiConfig.proactiveRefreshThreshold) return; // ignore: avoid_print print( '[SocketManager] Token expiring in ${remaining.inMinutes}min, refreshing before reconnect', ); final newToken = await apiConfig.onTokenRefresh?.call(); if (newToken != null && newToken.isNotEmpty) { // updateToken 触发 onTokenUpdated → tokenStream → socketManager.updateToken apiConfig.updateToken(newToken); } }, onCheckNetworkAvailable: () async => networkMonitor.isConnected, onLog: (message, {tag}) { // ignore: avoid_print print('[${tag ?? 'SocketManager'}] $message'); }, ); // 监听 token 更新事件 → 同步到 WebSocket final tokenSub = tokenStream.stream.listen((newToken) { manager.updateToken(newToken); }); // 监听网络状态变化 → 驱动 SocketManager 断连/重连 final networkSub = networkMonitor.onStatusChanged.listen((isAvailable) { manager.handleNetworkStatusChanged(isAvailable: isAvailable); }); ref.onDispose(() { tokenSub.cancel(); networkSub.cancel(); unawaited(manager.dispose()); }); return manager; }); // ── 辅助 ────────────────────────────────────────────────────────────────────── /// HTTP baseURL → WebSocket URL 转换 /// /// https://api.example.com → wss://api.example.com/ws /// http://api.example.com → ws://api.example.com/ws String _buildWsUrl(String httpBaseUrl) { String base = httpBaseUrl; if (base.startsWith('https://')) { base = base.replaceFirst('https://', 'wss://'); } else if (base.startsWith('http://')) { base = base.replaceFirst('http://', 'ws://'); } return '$base${ApiPaths.wsConnect}'; } // ══════════════════════════════════════════════════════════════════════════════ // 本文件的职责 // ══════════════════════════════════════════════════════════════════════════════ // // 提供所有网络基础设施 Provider:网络监听 + HTTP + WebSocket。 // 业务模块的 DI 链路(Repository → UseCase 按需) // 内聚在 features/{模块}/di/{模块}_providers.dart 中。 // // di/ 目录只放「需要手动装配的 Provider」(构造注入、回调组合等)。 // ViewModel Provider 由 @riverpod 注解自动生成,不在 di/ 中。 // // ┌──────────────────────────────────────────────────────────────────────────┐ // │ DI 架构 │ // ├──────────────────────────────────────────────────────────────────────────┤ // │ app/di/ ← 手动装配:SDK 基础设施 │ // │ ├── app_providers.dart → 主题 + 启动初始化 │ // │ └── network_provider.dart → 网络监听 + HTTP + WebSocket │ // │ │ // │ features/{模块}/di/ ← 手动装配:业务模块 DI 链路 │ // │ └── auth_providers.dart → Repository → UseCase(按需) │ // │ chat_providers.dart 每个模块 DI 链路内聚在一个文件 │ // │ │ // │ features/{模块}/presentation/ ← @riverpod 自动生成:ViewModel │ // │ └── login_view_model.dart → loginViewModelProvider(代码生成) │ // └──────────────────────────────────────────────────────────────────────────┘ // // Provider 链路: // // networkMonitorProvider(公共服务,HTTP + WS 共用) // ├── apiConfigProvider → networkSdkApiProvider ← HTTP 层 // └── _socketConfigProvider → _socketClientProvider ← WS 层(内部) // → socketManagerProvider // // _tokenUpdateStreamProvider(打破循环引用的中间层) // ← apiConfigProvider.onTokenUpdated 推送 // → socketManagerProvider 监听 → socketManager.updateToken() // // 网络事件驱动链路: // // connectivity_plus(平台网络事件) // → NetworkMonitor.onStatusChanged(true / false) // → SocketManager.handleNetworkStatusChanged() // → 断网: disconnect() // → 恢复: onBeforeReconnect → connect(token: lastToken) // // 前后台事件驱动链路: // // WidgetsBindingObserver(App 层 app.dart) // → SocketManager.onEnterBackground() // disconnectInBackground=false → 完全保活,心跳不停(本项目默认) // disconnectInBackground=true → disconnect + 暂停心跳(省电模式) // → SocketManager.onEnterForeground() // 保活模式 → 检查连接健康,异常则重连 // 断连模式 → onBeforeReconnect → reconnect // // Token 刷新 → WebSocket 同步链路: // // RetryInterceptor 检测 token 过期 // → TokenRefreshManager.refreshIfNeeded() // → apiConfig.updateToken(newToken) // → onTokenUpdated(newToken) // → _tokenUpdateStream.add(newToken) // → socketManager.updateToken(newToken) // 不断连,下次重连自动用新 token // // 主动 token 刷新(重连前,两个层级): // // SocketManager 层(前台恢复 / 网络恢复触发): // SocketManager.onBeforeReconnect() // → 解析 JWT exp → 距过期 < 阈值 // → apiConfig.onTokenRefresh() → 刷新 // → apiConfig.updateToken(newToken) // → sync stream → manager.updateToken → _lastToken 更新 // → _client.connect(token: _lastToken) 使用新 token // // SocketClient 层(心跳超时 / stream onDone 触发): // SocketConfig.onBeforeReconnect() // → 同上逻辑:检查 JWT exp → 刷新 → apiConfig.updateToken // → sync stream → manager.updateToken → _client.updateToken // → socketClient._currentToken 同步更新 // → _doConnect() 使用新 token // // Repository 直接注入 ApiClient,通过回调注入其他 SDK 能力: // // onTokenUpdate: (token) { // apiConfig.updateToken(token); // 内存(network_sdk) // secureStorage.saveToken(token); // 持久化(crypto_sdk) // } // // 这样 network_sdk 和 crypto_sdk 互不依赖,App 层是唯一知道两者的地方。 // // ══════════════════════════════════════════════════════════════════════════════ // 新增接口的完整流程(以登录为例) // ══════════════════════════════════════════════════════════════════════════════ // // 「一个接口 = 一个 Request 文件」,严格按层调用,禁止跳层。 // // ┌──────────────────────────────────────────────────────────────────────────┐ // │ 文件 & 职责总览 │ // ├──────────────────────────────────────────────────────────────────────────┤ // │ login_request.dart Request + Response DTO(一个端点一个文件) │ // │ auth_repository_impl.dart executeRequest → DTO → Entity + 回调写 Token│ // │ login_usecase.dart 格式校验 → 调 Repository(按需,非必须) │ // │ auth_providers.dart DI 装配(Repository → UseCase 按需) │ // │ login_view_model.dart ref.read(authRepositoryProvider).login() │ // │ 或 ref.read(loginUseCaseProvider).execute() │ // │ login_page.dart ref.watch(loginViewModelProvider) │ // └──────────────────────────────────────────────────────────────────────────┘ // // ───────────────────────────────────────────────────────────────────────── // Step 1: 定义 Request(data/remote/login_request.dart) // ───────────────────────────────────────────────────────────────────────── // // 一个文件包含两部分:Response DTO + Request 类。 // // @JsonSerializable() // class LoginData { // Response DTO // final String token; // final String userId; // factory LoginData.fromJson(Map json) => _$LoginDataFromJson(json); // User toEntity() => User(id: userId, ...); // DTO → Domain Entity // } // // @ApiRequest(path: ApiPaths.authLogin, method: HttpMethod.post, // responseType: LoginData, requestType: ApiRequestType.login) // @JsonSerializable() // class LoginRequest extends ApiRequestable with _$LoginRequestApi { // final String email; // final String password; // LoginRequest({required this.email, required this.password}); // @override Map toJson() => _$LoginRequestToJson(this); // } // // build_runner 生成 _$LoginRequestApi mixin → 自动提供 path / method / fromJson 注册。 // // ───────────────────────────────────────────────────────────────────────── // Step 2: Repository(data/repositories/auth_repository_impl.dart) // ───────────────────────────────────────────────────────────────────────── // // class AuthRepositoryImpl implements AuthRepository { // final ApiClient _client; // ← 直接注入 ApiClient // final void Function(String?) _onTokenUpdate; // ← 回调,由 Provider 层组合 // // Future login({required String email, required String password}) async { // final dto = await _client.executeRequest( // LoginRequest(email: email, password: password), // ); // _onTokenUpdate(dto!.token); // 回调写入 Token // return dto.toEntity(); // DTO → Domain Entity // } // } // // ───────────────────────────────────────────────────────────────────────── // Step 3: Provider 装配 + ViewModel // ───────────────────────────────────────────────────────────────────────── // // // --- Provider 装配(features/auth/di/auth_providers.dart) --- // // // Repository(直接注入 ApiClient + 回调组合多个 SDK 能力) // final authRepositoryProvider = Provider((ref) { // final apiConfig = ref.read(apiConfigProvider); // return AuthRepositoryImpl( // client: ref.read(networkSdkApiProvider), // 注入 Facade 接口 // onTokenUpdate: (token) { // apiConfig.updateToken(token); // 内存(network_sdk) // // secureStorage.saveToken(token); // 持久化(crypto_sdk) // }, // ); // }); // // // UseCase(按需 — 登录有多步编排,所以需要) // final loginUseCaseProvider = Provider((ref) { // return LoginUseCase(authRepository: ref.read(authRepositoryProvider)); // }); // // // --- ViewModel(features/auth/presentation/login_view_model.dart) --- // // // 常规写法:ViewModel 直接调 Repository // @riverpod // class LoginViewModel extends _$LoginViewModel { // Future login(String email, String password) async { // state = state.copyWith(isLoading: true); // try { // final user = await ref.read(authRepositoryProvider).login( // email: email, password: password, // ); // state = state.copyWith(user: user, isLoading: false); // } on ApiError catch (e) { // state = state.copyWith(error: e.displayMessage, isLoading: false); // } // } // } // // // 进阶写法:有 UseCase 时(格式校验 + 多步编排) // // final user = await ref.read(loginUseCaseProvider).execute( // // email: email, password: password, // // ); // // ═════════════════════════════════════════════════════════════════════════ // 内部执行链路(点击登录按钮后发生了什么) // ═════════════════════════════════════════════════════════════════════════ // // View: vm.login(email, password) // → ViewModel: ref.read(authRepositoryProvider).login(...) ← 常规路径 // → ViewModel: ref.read(loginUseCaseProvider).execute(...) ← 进阶路径(有 UseCase 时) // → UseCase: 格式校验(邮箱 + 密码) // → UseCase/ViewModel: authRepository.login(...) // → Repository: _client.executeRequest(LoginRequest(...)) // → ApiClient.executeRequest(request) // 1. 拼 URL: baseURL + "/auth/login" // 2. request.parameters 触发 fromJson 自动注册 // 3. AuthInterceptor: 注入 token + platform headers // 4. Dio.request(url, data: {email, password}) // 5. RetryInterceptor: token 过期 → 刷新 → 自动重试 // 6. LoggingInterceptor: 打印请求/响应日志 // → request.decodeResponse(response) // 1. ApiResponseWrapper.fromJson: 拆 { code, message, data } // 2. 检查 code != 0 → 抛 ApiError // 3. fromJsonRegistry[LoginData] → LoginData.fromJson(data) // → 返回 LoginData(DTO) // → _onTokenUpdate(token) 回调写入 Token(Provider 层组合:内存 + 持久化) // → LoginData.toEntity() → User(Domain Entity) // → state.copyWith(user: user) 更新状态 // View: ref.watch → 自动 rebuild UI // // ═════════════════════════════════════════════════════════════════════════ // 各 HTTP 方法速查 — 新增接口时参照 // ═════════════════════════════════════════════════════════════════════════ // // GET(参数走 URL query string) // @ApiRequest(path: ..., method: HttpMethod.get, responseType: ProfileData) // class GetProfileRequest extends ApiRequestable with _$... { } // // POST(参数走 JSON body) // 见上方 LoginRequest 示例。 // // DELETE / PUT / PATCH(与 POST 相同,只改 method) // @ApiRequest(path: ..., method: HttpMethod.delete, responseType: ...) // // POST 无响应数据(如 logout) // class LogoutRequest extends ApiRequestable { ... } // // → 返回 null // // Upload A: FormData 上传到自有后端 // @override Object? get uploadData => FormData.fromMap({ 'file': ... }); // // Upload B: 二进制上传到 S3 presigned URL // @override String get path => presignedURL; // 完整 URL,不拼 baseURL // @override Object? get uploadData => bytes; // Uint8List // @override decodeResponse(response) { ... } // S3 不走标准响应格式 //