Initial project

This commit is contained in:
Cody
2026-03-06 14:56:17 +08:00
parent 977b627b15
commit bf9e099747
1180 changed files with 50973 additions and 0 deletions

View File

@@ -0,0 +1,404 @@
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<NetworkMonitor>((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<ApiConfig>((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<NetworksSdkApi>((ref) {
final config = ref.read(apiConfigProvider);
return NetworksSdkApi()..initialize(config);
});
// ── WebSocket 基础设施 ────────────────────────────────────────────────────────
/// SocketConfig Provider全局单例
///
/// 与 apiConfigProvider 对称,通过回调注入 App 层能力,
/// SDK 内部不调用其他 SDK。
final socketConfigProvider = Provider<SocketConfig>((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<NetworksMessagingApi>((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<SocketManager>((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.onStatusChangedtrue / false
// → SocketManager.handleNetworkStatusChanged()
// → 断网: disconnect()
// → 恢复: connect(token: lastToken)
//
// 前后台事件驱动链路:
//
// WidgetsBindingObserverApp 层 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: 定义 Requestdata/remote/login_request.dart
// ─────────────────────────────────────────────────────────────────────────
//
// 一个文件包含两部分Response DTO + Request 类。
//
// @JsonSerializable()
// class LoginData { // Response DTO
// final String token;
// final String userId;
// factory LoginData.fromJson(Map<String, dynamic> 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<LoginData> with _$LoginRequestApi {
// final String email;
// final String password;
// LoginRequest({required this.email, required this.password});
// @override Map<String, dynamic> toJson() => _$LoginRequestToJson(this);
// }
//
// build_runner 生成 _$LoginRequestApi mixin → 自动提供 path / method / fromJson 注册。
//
// ─────────────────────────────────────────────────────────────────────────
// Step 2: Repositorydata/repositories/auth_repository_impl.dart
// ─────────────────────────────────────────────────────────────────────────
//
// class AuthRepositoryImpl implements AuthRepository {
// final ApiClient _client; // ← 直接注入 ApiClient
// final void Function(String?) _onTokenUpdate; // ← 回调,由 Provider 层组合
//
// Future<User> 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));
// });
//
// // --- ViewModelfeatures/auth/presentation/login_view_model.dart ---
//
// // 常规写法ViewModel 直接调 Repository
// @riverpod
// class LoginViewModel extends _$LoginViewModel {
// Future<void> 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)
// → 返回 LoginDataDTO
// → _onTokenUpdate(token) 回调写入 TokenProvider 层组合:内存 + 持久化)
// → LoginData.toEntity() → UserDomain 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<ProfileData> with _$... { }
//
// POST参数走 JSON body
// 见上方 LoginRequest 示例。
//
// DELETE / PUT / PATCH与 POST 相同,只改 method
// @ApiRequest(path: ..., method: HttpMethod.delete, responseType: ...)
//
// POST 无响应数据(如 logout
// class LogoutRequest extends ApiRequestable<void> { ... }
// // → 返回 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 不走标准信封
//