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/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; }); // ── 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); return ApiConfig( baseURL: AppConfig.apiBaseUrl, platformHeaders: { 'Platform': 'Android', // TODO: 运行时从平台 API 获取 'client-version': '1.0.0', // TODO: 运行时从 package_info 获取 }, tokenExpiredCodes: {30002, 30003, 30124}, forceLogoutCodes: {30125}, onForceLogout: () { // TODO: 清除登录态,跳转登录页 }, onTokenRefresh: () async { // TODO: App 层刷新 token 逻辑 return null; }, onCheckNetworkAvailable: () async => networkMonitor.isConnected, 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, onLog: (message, {tag}) { // ignore: avoid_print print('[${tag ?? 'Socket'}] $message'); }, onCheckNetworkAvailable: () async { return networkMonitor.isConnected; }, ); }); /// SocketClient Provider(全局单例) /// /// 与 apiClientProvider 对称。 final socketClientProvider = Provider((ref) { final config = ref.read(socketConfigProvider); return NetworksMessagingApi()..initialize(config); }); /// SocketManager Provider /// /// 封装连接生命周期、网络/前后台事件响应、操作前置检查、消息预处理。 /// 业务模块通过此 Provider 访问 WebSocket 能力。 /// /// ## 前置检查 /// /// connect / send 前先检查网络可用性 + 后台状态, /// 无效操作直接跳过,避免无意义的网络请求。 /// 与 HTTP 层 [ApiClient.executeRequest] 的网络前置检查对称。 /// /// ## 事件驱动 /// /// 网络状态变化由 [networkMonitorProvider](公共服务)驱动, /// 自动触发断连/重连。 /// /// onMessageTransform 参考 HTTP 层 onTokenRefresh 的回调模式: /// 后续接入加解密 SDK 时,在此注入解密回调, /// SDK 内部不调用其他 SDK。 final socketManagerProvider = Provider((ref) { final client = ref.read(socketClientProvider); final networkMonitor = ref.read(networkMonitorProvider); final manager = SocketManager( client: client, wsUrl: _buildWsUrl(AppConfig.apiBaseUrl), onMessageTransform: null, // TODO: 接入加解密 SDK 后注入解密回调 onCheckNetworkAvailable: () async => networkMonitor.isConnected, onLog: (message, {tag}) { // ignore: avoid_print print('[${tag ?? 'SocketManager'}] $message'); }, ); // 监听网络状态变化 → 驱动 SocketManager 断连/重连 final subscription = networkMonitor.onStatusChanged.listen((isAvailable) { manager.handleNetworkStatusChanged(isAvailable: isAvailable); }); ref.onDispose(() { subscription.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 → apiClientProvider ← HTTP 层 // └── socketConfigProvider → socketClientProvider ← WS 层 // → socketManagerProvider // // 网络事件驱动链路: // // connectivity_plus(平台网络事件) // → NetworkMonitor.onStatusChanged(true / false) // → SocketManager.handleNetworkStatusChanged() // → 断网: disconnect() // → 恢复: connect(token: lastToken) // // 前后台事件驱动链路: // // WidgetsBindingObserver(App 层 app.dart) // → SocketManager.onEnterBackground() → disconnect // → SocketManager.onEnterForeground() → reconnect // // 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(apiClientProvider), // 直接注入 // 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 不走标准信封 //