优化配置,修复 demo bug
1,network 框架完善 2,websocket 机制完善 3,设计文档整理到架构文档 4,脚本,配置完善
This commit is contained in:
@@ -6,6 +6,8 @@ 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';
|
||||
|
||||
@@ -47,6 +49,21 @@ final networkMonitorProvider = Provider<NetworkMonitor>((ref) {
|
||||
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<StreamController<String>>((ref) {
|
||||
final controller = StreamController<String>.broadcast(sync: true);
|
||||
ref.onDispose(controller.close);
|
||||
return controller;
|
||||
});
|
||||
|
||||
// ── HTTP 基础设施 ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// API 配置 Provider(全局单例)
|
||||
@@ -58,15 +75,18 @@ final networkMonitorProvider = Provider<NetworkMonitor>((ref) {
|
||||
/// 请求前先判断网络状态,无网络时直接抛 [ApiError.noNetworkConnection]。
|
||||
final apiConfigProvider = Provider<ApiConfig>((ref) {
|
||||
final networkMonitor = ref.read(networkMonitorProvider);
|
||||
final tokenStream = ref.read(_tokenUpdateStreamProvider);
|
||||
|
||||
return ApiConfig(
|
||||
baseURL: AppConfig.apiBaseUrl,
|
||||
platformHeaders: {
|
||||
'Platform': 'Android', // TODO: 运行时从平台 API 获取
|
||||
'Platform': 'Android', // TODO: 运行时从 platform API 获取
|
||||
'client-version': '1.0.0', // TODO: 运行时从 package_info 获取
|
||||
'Channel': '', // TODO: 从 AppConfig 读取渠道标识
|
||||
'lang': 'zh-CN', // TODO: 从 l10n_sdk 或系统 locale 动态获取
|
||||
},
|
||||
tokenExpiredCodes: {30002, 30003, 30124},
|
||||
forceLogoutCodes: {30125},
|
||||
tokenExpiredCodes: ApiErrorCodes.tokenExpiredCodes,
|
||||
forceLogoutCodes: ApiErrorCodes.forceLogoutCodes,
|
||||
onForceLogout: () {
|
||||
// TODO: 清除登录态,跳转登录页
|
||||
},
|
||||
@@ -74,7 +94,17 @@ final apiConfigProvider = Provider<ApiConfig>((ref) {
|
||||
// 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}) {
|
||||
@@ -94,16 +124,47 @@ final networkSdkApiProvider = Provider<NetworksSdkApi>((ref) {
|
||||
|
||||
// ── WebSocket 基础设施 ────────────────────────────────────────────────────────
|
||||
|
||||
/// SocketConfig Provider(全局单例)
|
||||
/// SocketConfig Provider(内部使用,不对外暴露)
|
||||
///
|
||||
/// 与 apiConfigProvider 对称,通过回调注入 App 层能力,
|
||||
/// SDK 内部不调用其他 SDK。
|
||||
final socketConfigProvider = Provider<SocketConfig>((ref) {
|
||||
final _socketConfigProvider = Provider<SocketConfig>((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');
|
||||
@@ -114,12 +175,11 @@ final socketConfigProvider = Provider<SocketConfig>((ref) {
|
||||
);
|
||||
});
|
||||
|
||||
/// SocketClient Provider(全局单例)
|
||||
/// SocketClient Provider(内部使用,不对外暴露)
|
||||
///
|
||||
/// 与 apiClientProvider 对称。
|
||||
final socketClientProvider = Provider<NetworksMessagingApi>((ref)
|
||||
{
|
||||
final config = ref.read(socketConfigProvider);
|
||||
/// 与 networkSdkApiProvider 对称。
|
||||
final _socketClientProvider = Provider<NetworksMessagingApi>((ref) {
|
||||
final config = ref.read(_socketConfigProvider);
|
||||
return NetworksMessagingApi()..initialize(config);
|
||||
});
|
||||
|
||||
@@ -139,17 +199,43 @@ final socketClientProvider = Provider<NetworksMessagingApi>((ref)
|
||||
/// 网络状态变化由 [networkMonitorProvider](公共服务)驱动,
|
||||
/// 自动触发断连/重连。
|
||||
///
|
||||
/// Token 更新由 [_tokenUpdateStreamProvider] 事件流驱动,
|
||||
/// HTTP 层刷新 token 后自动同步到 WebSocket。
|
||||
///
|
||||
/// onMessageTransform 参考 HTTP 层 onTokenRefresh 的回调模式:
|
||||
/// 后续接入加解密 SDK 时,在此注入解密回调,
|
||||
/// SDK 内部不调用其他 SDK。
|
||||
final socketManagerProvider = Provider<SocketManager>((ref) {
|
||||
final client = ref.read(socketClientProvider);
|
||||
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),
|
||||
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
|
||||
@@ -157,13 +243,19 @@ final socketManagerProvider = Provider<SocketManager>((ref) {
|
||||
},
|
||||
);
|
||||
|
||||
// 监听 token 更新事件 → 同步到 WebSocket
|
||||
final tokenSub = tokenStream.stream.listen((newToken) {
|
||||
manager.updateToken(newToken);
|
||||
});
|
||||
|
||||
// 监听网络状态变化 → 驱动 SocketManager 断连/重连
|
||||
final subscription = networkMonitor.onStatusChanged.listen((isAvailable) {
|
||||
final networkSub = networkMonitor.onStatusChanged.listen((isAvailable) {
|
||||
manager.handleNetworkStatusChanged(isAvailable: isAvailable);
|
||||
});
|
||||
|
||||
ref.onDispose(() {
|
||||
subscription.cancel();
|
||||
tokenSub.cancel();
|
||||
networkSub.cancel();
|
||||
unawaited(manager.dispose());
|
||||
});
|
||||
|
||||
@@ -215,23 +307,55 @@ String _buildWsUrl(String httpBaseUrl) {
|
||||
// Provider 链路:
|
||||
//
|
||||
// networkMonitorProvider(公共服务,HTTP + WS 共用)
|
||||
// ├── apiConfigProvider → apiClientProvider ← HTTP 层
|
||||
// └── socketConfigProvider → socketClientProvider ← WS 层
|
||||
// ├── apiConfigProvider → networkSdkApiProvider ← HTTP 层
|
||||
// └── _socketConfigProvider → _socketClientProvider ← WS 层(内部)
|
||||
// → socketManagerProvider
|
||||
//
|
||||
// _tokenUpdateStreamProvider(打破循环引用的中间层)
|
||||
// ← apiConfigProvider.onTokenUpdated 推送
|
||||
// → socketManagerProvider 监听 → socketManager.updateToken()
|
||||
//
|
||||
// 网络事件驱动链路:
|
||||
//
|
||||
// connectivity_plus(平台网络事件)
|
||||
// → NetworkMonitor.onStatusChanged(true / false)
|
||||
// → SocketManager.handleNetworkStatusChanged()
|
||||
// → 断网: disconnect()
|
||||
// → 恢复: connect(token: lastToken)
|
||||
// → 恢复: onBeforeReconnect → connect(token: lastToken)
|
||||
//
|
||||
// 前后台事件驱动链路:
|
||||
//
|
||||
// WidgetsBindingObserver(App 层 app.dart)
|
||||
// → SocketManager.onEnterBackground() → disconnect
|
||||
// → SocketManager.onEnterForeground() → reconnect
|
||||
// → SocketManager.onEnterBackground()
|
||||
// disconnectInBackground=true → disconnect(默认,移动端省电)
|
||||
// disconnectInBackground=false → 完全保活,不断连不暂停心跳(桌面端)
|
||||
// → 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 能力:
|
||||
//
|
||||
@@ -313,7 +437,7 @@ String _buildWsUrl(String httpBaseUrl) {
|
||||
// final authRepositoryProvider = Provider((ref) {
|
||||
// final apiConfig = ref.read(apiConfigProvider);
|
||||
// return AuthRepositoryImpl(
|
||||
// client: ref.read(apiClientProvider), // 直接注入
|
||||
// client: ref.read(networkSdkApiProvider), // 注入 Facade 接口
|
||||
// onTokenUpdate: (token) {
|
||||
// apiConfig.updateToken(token); // 内存(network_sdk)
|
||||
// // secureStorage.saveToken(token); // 持久化(crypto_sdk)
|
||||
|
||||
Reference in New Issue
Block a user