优化配置,修复 demo bug

1,network 框架完善
2,websocket 机制完善
3,设计文档整理到架构文档
4,脚本,配置完善
This commit is contained in:
Cody
2026-03-07 14:58:10 +08:00
parent f8a118af73
commit 0ee2c8c63c
82 changed files with 2704 additions and 1045 deletions

View File

@@ -1,92 +1,75 @@
import 'dart:typed_data';
import 'package:networks_sdk/src/domain/entities/socket_connection_state.dart';
import 'package:networks_sdk/src/domain/entities/socket_error.dart';
import 'package:networks_sdk/src/presentation/wiring/networks_sdk_wiring.dart';
import 'package:networks_sdk/src/presentation/wiring/socket_config.dart';
/// Messaging API for real-time communication
/// 实时通信公开接口
///
/// This abstract class provides a technology-agnostic interface for
/// real-time messaging. The actual implementation may use WebSocket
/// or other transport mechanisms.
/// 底层基于 WebSocket支持 JSON / 字符串 / 二进制消息、
/// 自动重连含无限重连、Token 热更新、消息加密/解密钩子。
///
/// ## Usage
/// ## 使用方式
///
/// ```dart
/// final messaging = NetworksMessagingApi();
/// await messaging.initialize(SocketConfig(...));
///
/// // Connect to messaging server
/// await messaging.connect('wss://api.example.com/ws', token: 'xxx');
///
/// // Listen for messages
/// messaging.messageStream.listen((msg) => print(msg));
///
/// // Send messages
/// await messaging.send({'type': 'chat', 'data': {...}});
///
/// // Handle connection state
/// messaging.connectionStateStream.listen((state) => ...);
/// // Token 热更新(不断连)
/// messaging.updateToken('new_token');
///
/// // Handle errors
/// messaging.errorStream.listen((error) => ...);
/// // 发送二进制
/// await messaging.sendBytes(Uint8List.fromList([0x01, 0x02]));
///
/// // Lifecycle management
/// messaging.onEnterForeground();
/// messaging.onEnterBackground();
///
/// // Cleanup
/// await messaging.disconnect();
/// await messaging.dispose();
/// ```
abstract class NetworksMessagingApi
{
abstract class NetworksMessagingApi {
factory NetworksMessagingApi() => NetworksSdkWiring.buildMessagingApi();
/// Initialize the messaging service with configuration
void initialize(SocketConfig config);
/// Connect to the messaging server
///
/// [url] - WebSocket URL (e.g., 'wss://api.example.com/ws')
/// [token] - Optional authentication token
Future<bool> connect(String url, {String? token});
/// Disconnect from the messaging server
///
/// Manual disconnect does not trigger auto-reconnect
Future<void> disconnect();
/// Check if currently connected
bool get isConnected;
/// Current connection state
SocketConnectionState get connectionState;
/// Send a JSON message
/// Token 热更新(不断开连接)
///
/// 仅更新内部 token下次重连自动使用新 token。
void updateToken(String token);
Future<bool> send(Map<String, dynamic> message);
/// Send a raw string message
Future<bool> sendString(String message);
/// Stream of incoming parsed JSON messages
/// 发送二进制数据
Future<bool> sendBytes(List<int> bytes);
Stream<Map<String, dynamic>> get messageStream;
/// Stream of raw string messages (including failed JSON parses)
Stream<String> get rawMessageStream;
/// Stream of connection state changes
/// 二进制消息流
Stream<Uint8List> get binaryMessageStream;
Stream<SocketConnectionState> get connectionStateStream;
/// Stream of errors
Stream<SocketError> get errorStream;
/// Called when app enters foreground
void onEnterForeground();
/// Called when app enters background
void onEnterBackground();
/// Dispose all resources
Future<void> dispose();
}

View File

@@ -1,19 +1,69 @@
import 'package:dio/dio.dart';
import 'package:networks_sdk/src/data/dto/api_requestable.dart';
import 'package:networks_sdk/src/presentation/wiring/api_config.dart';
import 'package:networks_sdk/src/presentation/wiring/network_callbacks.dart';
import 'package:networks_sdk/src/presentation/wiring/networks_sdk_wiring.dart';
/// SDK API
abstract class NetworksSdkApi
{
/// Networks SDK 公开接口
///
/// 提供两种请求入口:
/// - [executeRequest] — 统一请求入口(标准 / Upload / 流式)
/// - [executeDownload] — 带进度的文件下载(支持断点续传)
///
/// 流式请求SSE也走 [executeRequest],由业务 Request 类 override
/// `decodeResponse` 处理 SSE 解析。SDK 根据 `requestType == stream`
/// 自动切换响应类型。
///
/// 使用方式:
/// ```dart
/// final sdk = NetworksSdkApi();
/// sdk.initialize(apiConfig);
///
/// // 标准请求
/// final data = await sdk.executeRequest(LoginRequest(...));
///
/// // 流式请求SSE— 同一入口Request 类 override decodeResponse
/// final result = await sdk.executeRequest(VoiceTranscribeRequest(...));
///
/// // 文件下载
/// await sdk.executeDownload(
/// url: '/files/report.pdf',
/// savePath: '/tmp/report.pdf',
/// onProgress: (received, total) => print('$received / $total'),
/// );
/// ```
abstract class NetworksSdkApi {
factory NetworksSdkApi() => NetworksSdkWiring.build();
Future<String?> platformVersion();
void initialize(ApiConfig aApiConfig);
void initialize(ApiConfig apiConfig);
Future<T?> executeRequest<T>(ApiRequestable<T> request);
/// 执行 API 请求 — 统一入口
///
/// 支持标准请求、登录、上传、流式SSE由 `request.requestType` 控制。
/// 流式请求由业务 Request 类 override `decodeResponse` 处理 SSE 解析。
///
/// [cancelToken] — 可选,用于取消正在进行的请求
Future<T?> executeRequest<T>(
ApiRequestable<T> request, {
CancelToken? cancelToken,
});
/// 下载文件到本地路径
///
/// [url] — 下载 URL完整路径或相对路径
/// [savePath] — 本地保存路径
/// [onProgress] — 下载进度回调
/// [cancelToken] — 取消令牌
/// [resume] — 是否断点续传
/// [headers] — 额外请求头
Future<void> executeDownload({
required String url,
required String savePath,
OnDownloadProgress? onProgress,
CancelToken? cancelToken,
bool resume = false,
Map<String, String>? headers,
});
}

View File

@@ -1,4 +1,3 @@
import 'network_callbacks.dart';
/// API 配置
@@ -13,12 +12,22 @@ class ApiConfig {
/// 平台相关 headersApp 层注入version、platform、channel 等)
Map<String, String> platformHeaders;
// ── 认证回调 ──
/// Token 过期时的刷新回调
final OnTokenRefresh? onTokenRefresh;
/// 需要强制登出时的回调
final OnForceLogout? onForceLogout;
/// Token 更新后的通知回调
///
/// 在 [updateToken] 被调用且新 token 非空时触发。
/// App 层用于同步 token 到 WebSocket 等其他模块。
final void Function(String newToken)? onTokenUpdated;
// ── 基础回调 ──
/// 日志输出回调(不设置则不输出日志)
final OnLog? onLog;
@@ -29,12 +38,39 @@ class ApiConfig {
/// 返回 false 则直接抛 [ApiError.noNetworkConnection],不走网络。
final OnCheckNetworkAvailable? onCheckNetworkAvailable;
// ── 加密回调(预留给 cipher_guard_sdk──
/// 请求体加密回调null 时不加密
final OnEncryptRequest? onEncryptRequest;
/// 响应体解密回调null 时不解密
final OnDecryptResponse? onDecryptResponse;
// ── 业务错误回调 ──
/// 业务错误拦截回调
///
/// 在 token 过期 / 强制登出判断之后执行。
/// 返回 true = App 层已处理SDK 正常传递响应;
/// 返回 false = 未处理SDK 继续正常流程。
final OnBusinessError? onBusinessError;
/// 响应变换回调
///
/// 在 `executeRequest` 解码前调用App 层可以统一解包
/// `{ code, data, message }` 结构。返回 null 表示不变换。
final OnTransformResponse? onTransformResponse;
// ── 错误码集合 ──
/// App 层定义的 Token 过期错误码集合
final Set<int> tokenExpiredCodes;
/// App 层定义的强制登出错误码集合
final Set<int> forceLogoutCodes;
// ── 重试配置 ──
/// 瞬态错误最大重试次数5xx / 超时 / 连接失败)
///
/// 0 = 不重试(默认),设为 3 启用重试。
@@ -46,18 +82,50 @@ class ApiConfig {
/// 实际延迟 = min(baseDelay * 2^attempt, 30s) + jitter
final Duration retryBaseDelay;
// ── Token 刷新配置 ──
/// Token 刷新超时时间,防止 onTokenRefresh 卡住导致请求永远阻塞
final Duration tokenRefreshTimeout;
/// Token 刷新时间窗口:刷新成功后此时间内再次收到过期码直接返回成功,
/// 避免服务端同步延迟导致的误判
final Duration tokenReuseWindow;
// ── 主动刷新配置 ──
/// Token 过期时间解析回调
///
/// App 层解析 JWT `exp` claim用于主动刷新判断。
/// 未注入时不启用主动刷新。
final OnGetTokenExpiry? onGetTokenExpiry;
/// 主动刷新阈值:距过期不足此时间时提前刷新
///
/// 默认 1 小时。WebSocket 重连前、App 回前台时
/// 自动检查并刷新即将过期的 token避免带过期 token 发起请求。
final Duration proactiveRefreshThreshold;
ApiConfig({
required this.baseURL,
this.token,
this.platformHeaders = const {},
this.onTokenRefresh,
this.onForceLogout,
this.onTokenUpdated,
this.onLog,
this.onCheckNetworkAvailable,
this.onEncryptRequest,
this.onDecryptResponse,
this.onBusinessError,
this.onTransformResponse,
this.tokenExpiredCodes = const {},
this.forceLogoutCodes = const {},
this.maxRetries = 0,
this.retryBaseDelay = const Duration(seconds: 1),
this.tokenRefreshTimeout = const Duration(seconds: 10),
this.tokenReuseWindow = const Duration(seconds: 3),
this.onGetTokenExpiry,
this.proactiveRefreshThreshold = const Duration(hours: 1),
});
/// 构建默认 headers
@@ -70,6 +138,8 @@ class ApiConfig {
final headers = <String, String>{
'Content-Type': 'application/json; charset=utf-8',
'Accept': 'application/json',
'Keep-Alive': 'timeout=60',
// Unix 时间戳(秒),整数值,非格式化日期字符串
'Timestamp': '${DateTime.now().millisecondsSinceEpoch ~/ 1000}',
'APP-Request-ID': _generateRequestId(),
};
@@ -91,8 +161,13 @@ class ApiConfig {
}
/// 更新 token
///
/// 同时触发 [onTokenUpdated] 通知其他模块(如 WebSocket同步 token。
void updateToken(String? newToken) {
token = newToken;
if (newToken != null && newToken.isNotEmpty) {
onTokenUpdated?.call(newToken);
}
}
/// 更新 base URL
@@ -105,4 +180,4 @@ class ApiConfig {
final now = DateTime.now().microsecondsSinceEpoch;
return '$now${Object().hashCode}';
}
}
}

View File

@@ -1,13 +1,105 @@
/// 网络层回调类型定义,由 App 层注入 SDK避免 SDK 直接依赖外部实现。
library;
import 'package:networks_sdk/src/domain/entities/encrypted_request.dart';
// ── 认证 ──
/// Token 刷新回调,返回新 token返回 null 表示刷新失败
typedef OnTokenRefresh = Future<String?> Function();
/// 制登出回調
/// 制登出回
typedef OnForceLogout = void Function();
/// 日誌輸出回調
// ── Token 生命周期 ──
/// 获取 token 过期时间
///
/// App 层解析 JWT 的 `exp` claim 返回过期时间。
/// 返回 null 表示无法解析(非 JWT 或格式错误)。
typedef OnGetTokenExpiry = DateTime? Function(String token);
// ── 基础 ──
/// 日志输出回调
typedef OnLog = void Function(String message, {String? tag});
/// 網路可用性查App 注入SDK 在求前調用)
typedef OnCheckNetworkAvailable = Future<bool> Function();
/// 网络可用性查App 注入SDK 在求前用)
typedef OnCheckNetworkAvailable = Future<bool> Function();
// ── 加密(预留给 cipher_guard_sdk──
/// HTTP 请求加密回调
///
/// 接收原始路径、headers、请求体返回 [EncryptedRequest]。
/// 拦截器根据返回值中的非 null 字段覆盖原始请求。
///
/// 参数说明:
/// - [path] — 原始请求路径(如 `/api/v1/auth/login`
/// - [headers] — 当前请求的全部 headers含 token、platform headers 等)
/// - [body] — 原始请求体(可能是 Map、String、null 等)
///
/// App 层实现示例X25519 + AES-256-CBC 模式):
/// - 加密 path → hex 编码 → 替换路径
/// - 加密 body → base64 编码 → 替换请求体
/// - 加密 token → 放入 X-Token header
/// - Ed25519 签名 → 放入 X-Signature header
/// - Content-Type → text/plain
typedef OnEncryptRequest =
Future<EncryptedRequest> Function(
String path,
Map<String, String> headers,
Object? body,
);
/// HTTP 响应解密回调
///
/// 输入是原始响应数据(加密后可能是 String、`List<int>`、或 Map
/// 返回解密后的 Map 供业务层使用。
///
/// [responseData] 的实际类型取决于服务端响应格式:
/// - 加密模式下通常是 base64 字符串
/// - 非加密模式下是 `Map<String, dynamic>`
typedef OnDecryptResponse =
Future<Map<String, dynamic>> Function(Object responseData);
// ── 业务错误 ──
/// 业务错误拦截回调
///
/// App 层统一处理特定错误码,返回 true = 已处理SDK 不再抛错),
/// 返回 false = 未处理SDK 继续正常流程)。
typedef OnBusinessError = bool Function(int code, String message, String path);
/// 响应变换回调
///
/// App 层自定义响应解包逻辑(如统一解包 `{ code, data, message }` 结构)。
/// 返回 null 表示不变换,使用原始响应。
typedef OnTransformResponse =
Map<String, dynamic>? Function(Map<String, dynamic> raw);
// ── 下载 ──
/// 下载进度回调
typedef OnDownloadProgress = void Function(int received, int total);
// ── WebSocket 加密(预留给 cipher_guard_sdk──
/// WebSocket 连接 URL 构建回调
///
/// 建立连接前调用,接收原始 URL 和 token返回最终的连接 URL 字符串。
/// WS 握手本质是 HTTP GET 升级请求,只需控制 URL路径 + 查询参数)。
///
/// App 层可在此(通过调用 cipher_guard_sdk
/// - 加密 URL 路径(如 `/ws` → `/hex(encrypt(ws))`
/// - 加密 token 参数(明文 token 不出现在 URL 中)
/// - 添加加密模式协商参数(如 `cipher=true&type=mode3`
///
/// null 时使用默认行为:在 URL 后追加 `?token=xxx`。
typedef OnBuildConnectUrl = String Function(String url, String? token);
/// WebSocket 发送前加密回调
typedef OnEncryptMessage = Future<String> Function(String plainText);
/// WebSocket 收到后解密回调
typedef OnDecryptMessage = Future<String> Function(String cipherText);

View File

@@ -1,3 +1,5 @@
import 'dart:typed_data';
import 'package:networks_sdk/src/data/repositories/networks_messaging_repository_impl.dart';
import 'package:networks_sdk/src/domain/entities/socket_connection_state.dart';
import 'package:networks_sdk/src/domain/entities/socket_error.dart';
@@ -5,7 +7,7 @@ import 'package:networks_sdk/src/domain/repositories/networks_messaging_reposito
import 'package:networks_sdk/src/presentation/facade/networks_messaging_api.dart';
import 'package:networks_sdk/src/presentation/wiring/socket_config.dart';
/// Implementation of [NetworksMessagingApi] using [NetworksMessagingRepository]
/// [NetworksMessagingApi] 的实现,透传给 [NetworksMessagingRepository]
class NetworksMessagingApiImpl implements NetworksMessagingApi {
NetworksMessagingRepository? _repository;
@@ -47,6 +49,12 @@ class NetworksMessagingApiImpl implements NetworksMessagingApi {
return _repository!.connectionState;
}
@override
void updateToken(String token) {
_checkInitialized();
_repository!.updateToken(token);
}
@override
Future<bool> send(Map<String, dynamic> message) {
_checkInitialized();
@@ -59,6 +67,12 @@ class NetworksMessagingApiImpl implements NetworksMessagingApi {
return _repository!.sendString(message);
}
@override
Future<bool> sendBytes(List<int> bytes) {
_checkInitialized();
return _repository!.sendBytes(bytes);
}
@override
Stream<Map<String, dynamic>> get messageStream {
_checkInitialized();
@@ -71,6 +85,12 @@ class NetworksMessagingApiImpl implements NetworksMessagingApi {
return _repository!.rawMessageStream;
}
@override
Stream<Uint8List> get binaryMessageStream {
_checkInitialized();
return _repository!.binaryMessageStream;
}
@override
Stream<SocketConnectionState> get connectionStateStream {
_checkInitialized();
@@ -103,4 +123,3 @@ class NetworksMessagingApiImpl implements NetworksMessagingApi {
}
}
}

View File

@@ -1,7 +1,7 @@
import '../../../networks_sdk.dart';
import 'networks_sdk_core.dart';
/// SDK API Implementation
/// [NetworksSdkApi] 的实现,透传给 Repository
class NetworksSdkApiImpl implements NetworksSdkApi {
final NetworksSdkCore _core;
@@ -14,6 +14,29 @@ class NetworksSdkApiImpl implements NetworksSdkApi {
void initialize(ApiConfig apiConfig) => _core.repo.initialize(apiConfig);
@override
Future<T?> executeRequest<T>(ApiRequestable<T> request) => _core.repo.executeRequest(request);
Future<T?> executeRequest<T>(
ApiRequestable<T> request, {
CancelToken? cancelToken,
}) {
return _core.repo.executeRequest(request, cancelToken: cancelToken);
}
@override
Future<void> executeDownload({
required String url,
required String savePath,
OnDownloadProgress? onProgress,
CancelToken? cancelToken,
bool resume = false,
Map<String, String>? headers,
}) {
return _core.repo.executeDownload(
url: url,
savePath: savePath,
onProgress: onProgress,
cancelToken: cancelToken,
resume: resume,
headers: headers,
);
}
}

View File

@@ -1,9 +1,13 @@
import 'package:networks_sdk/src/presentation/wiring/network_callbacks.dart';
/// WebSocket 配置
/// 非单例,由 App 层构造并注入到 SocketClient
///
/// 与 [ApiConfig] 设计一致SDK 不依赖 Flutter
/// 网络检测、生命周期等业务逻辑通过回调注入。
class SocketConfig {
// ── 心跳 ──
/// 应用层心跳间隔(定时发送 "ping" 字符串)
final Duration heartbeatInterval;
@@ -13,10 +17,19 @@ class SocketConfig {
/// Pong 超时(超过此时间未收到 pong 则判定连接断开)
final Duration pongTimeout;
// ── 连接 ──
/// 连接超时
final Duration connectTimeout;
/// 是否启用 WebSocket 压缩permessage-deflate
final bool enableCompression;
// ── 重连 ──
/// 最大重连次数0 = 不重连)
///
/// 当 [unlimitedReconnect] 为 true 时此字段无效。
final int maxReconnectAttempts;
/// 最大重连延迟(指数退避上限)
@@ -25,22 +38,65 @@ class SocketConfig {
/// 是否自动重连
final bool autoReconnect;
/// 无限重连模式
///
/// IM 场景建议开启:连接断开后始终尝试重连,不受
/// [maxReconnectAttempts] 限制。退避延迟仍受
/// [maxReconnectDelay] 约束。
final bool unlimitedReconnect;
// ── 回调 ──
/// 日志输出回调(与 ApiConfig.onLog 同签名)
final void Function(String message, {String? tag})? onLog;
final OnLog? onLog;
/// 网络可用性查询App 层注入SDK 在重连前调用)
/// 返回 true 表示网络可用,可以尝试重连
final Future<bool> Function()? onCheckNetworkAvailable;
final OnCheckNetworkAvailable? onCheckNetworkAvailable;
/// 重连前回调
///
/// 每次自动重连前调用(心跳超时、连接断开等触发的内部重连)。
/// App 层用于:
/// - 检查并刷新即将过期的 token通过 [SocketClient.updateToken]
/// - 其他重连前准备工作
///
/// 回调完成后才发起实际连接。如果回调抛出异常,本次重连跳过,
/// 等下一轮退避定时器触发。
final Future<void> Function()? onBeforeReconnect;
// ── 加密回调(预留给 cipher_guard_sdk──
/// 连接 URL 构建回调
///
/// 建立连接前调用,接收原始 URL 和 token返回最终连接 URL 字符串。
/// null 时使用默认行为URL 后追加 `?token=xxx`)。
///
/// App 层注入 cipher_guard_sdk 的加密逻辑:路径/token 加密、
/// 添加 `cipher=true` 参数等。
final OnBuildConnectUrl? onBuildConnectUrl;
/// 发送前加密回调null 时不加密
final OnEncryptMessage? onEncryptMessage;
/// 收到后解密回调null 时不解密
final OnDecryptMessage? onDecryptMessage;
SocketConfig({
this.heartbeatInterval = const Duration(seconds: 10),
this.pingInterval = const Duration(seconds: 5),
this.pongTimeout = const Duration(seconds: 10),
this.connectTimeout = const Duration(seconds: 15),
this.enableCompression = false,
this.maxReconnectAttempts = 5,
this.maxReconnectDelay = const Duration(seconds: 30),
this.autoReconnect = true,
this.unlimitedReconnect = false,
this.onLog,
this.onCheckNetworkAvailable,
this.onBeforeReconnect,
this.onBuildConnectUrl,
this.onEncryptMessage,
this.onDecryptMessage,
});
}