优化配置,修复 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

@@ -883,13 +883,16 @@ flowchart TD
│ ├── data/
│ │ ├── datasources/
│ │ │ ├── http/
│ │ │ │ ├── api_client.dart # Dio REST 客户端
│ │ │ │ ├── api_client.dart # Dio REST 客户端
│ │ │ │ ├── token_refresh_manager.dart # Token 刷新管理(竞态安全 + 超时 + 时间窗口复用)
│ │ │ │ └── interceptor/
│ │ │ │ ├── auth_interceptor.dart # Token + 默认 header 注入
│ │ │ │ ├── retry_interceptor.dart # Token 刷新 + 瞬态错误重试
│ │ │ │ ── logging_interceptor.dart # 请求/响应日志
│ │ │ └── socket/
│ │ │ ── socket_client.dart # WebSocket 长连接(心跳/重连/Stream
│ │ │ │ ├── auth_interceptor.dart # Token + 默认 header 注入
│ │ │ │ ├── encryption_interceptor.dart # 加密拦截器(预留给 cipher_guard_sdk
│ │ │ │ ── retry_interceptor.dart # Token 刷新 + 瞬态错误重试 + 业务错误钩子
│ │ │ │ └── logging_interceptor.dart # 请求/响应日志
│ │ │ ── socket/
│ │ │ │ └── socket_client.dart # WebSocket 长连接(心跳/重连/Stream/加密钩子)
│ │ │ └── networks_sdk_method_channel_datasource.dart # 统一执行入口
│ │ ├── dto/
│ │ │ ├── api_requestable.dart # 请求基类 + fromJson 注册表
│ │ │ └── api_response_wrapper.dart # { code, message/msg, data } 信封解析
@@ -898,11 +901,12 @@ flowchart TD
│ │ └── networks_messaging_repository_impl.dart
│ ├── domain/
│ │ ├── entities/
│ │ │ ├── api_error.dart # @freezed HTTP 错误联合类型
│ │ │ ├── api_error.dart # @freezed HTTP 错误联合类型7 变体)
│ │ │ ├── encrypted_request.dart # 加密请求结果数据类
│ │ │ ├── socket_error.dart # @freezed WebSocket 错误联合类型
│ │ │ ├── socket_connection_state.dart # 连接状态 enum
│ │ │ ├── http_method.dart # GET/POST/PUT/DELETE/PATCH
│ │ │ └── api_request_type.dart # request/login/upload
│ │ │ └── api_request_type.dart # request/login/upload/stream/download
│ │ └── repositories/
│ │ ├── networks_sdk_repository.dart
│ │ └── networks_messaging_repository.dart
@@ -911,9 +915,9 @@ flowchart TD
│ │ ├── networks_sdk_api.dart # HTTP 公开 API 接口
│ │ └── networks_messaging_api.dart # WebSocket 公开 API 接口
│ └── wiring/
│ ├── api_config.dart # HTTP 配置baseURL/Token/回调)
│ ├── socket_config.dart # WebSocket 配置(心跳/重连策略
│ ├── network_callbacks.dart # 回调类型定义
│ ├── api_config.dart # HTTP 配置baseURL/Token/回调/加密/重试
│ ├── socket_config.dart # WebSocket 配置(心跳/重连/加密钩子
│ ├── network_callbacks.dart # 回调类型定义(认证/加密/业务错误/下载/WS
│ ├── networks_sdk_core.dart
│ ├── networks_sdk_api_impl.dart
│ ├── networks_messaging_api_impl.dart
@@ -1670,7 +1674,7 @@ final viewModel = ref.read(chatViewModelProvider.notifier); // viewModel 类型
<pre><code class="language-dart">// Riverpod DevTools 可以看到完整的依赖图
ChatViewModel
├─ chatRepositoryProvider
│ ├─ apiClientProvider
│ ├─ networkSdkApiProvider
│ └─ messageLocalDataSourceProvider
└─ sendMessageUseCaseProvider
└─ chatRepositoryProvider
@@ -2187,8 +2191,8 @@ extension APIRequestableDefaults&lt;T&gt; on APIRequestable&lt;T&gt; {
<pre><code class="language-dart">/// 执行 API 请求 - 唯一的请求入口
Future&lt;T?&gt; executeRequest&lt;T&gt;(Ref ref, APIRequestable&lt;T&gt; request) async {
final dio = ref.read(apiClientProvider);
final config = ref.read(aPIConfigurationProvider);
final client = ref.read(networkSdkApiProvider);
final config = ref.read(apiConfigProvider);
// 1. 检查网络连接
if (!networkManager.isNetworkAvailable) {
@@ -2646,18 +2650,20 @@ final apiConfigProvider = Provider&lt;ApiConfig&gt;((ref) {
);
});
/// 2. API 客户端(内部自动挂载 Auth / Retry / Logging 拦截器
final apiClientProvider = Provider&lt;ApiClient&gt;((ref) {
return ApiClient(config: ref.read(apiConfigProvider));
/// 2. Networks SDK API Provider全局单例Facade 接口
/// 内部自动挂载 AuthInterceptor / EncryptionInterceptor / RetryInterceptor / LoggingInterceptor
final networkSdkApiProvider = Provider&lt;NetworksSdkApi&gt;((ref) {
final config = ref.read(apiConfigProvider);
return NetworksSdkWiring.build(config: config);
});
// ── features/auth/di/auth_providers.dart ── Auth 模块完整 DI 链路)
/// 3. Repository注入 domain 接口类型ViewModel 不感知具体实现)
/// 3. Repository注入 Facade 接口类型ViewModel 不感知具体实现)
final authRepositoryProvider = Provider&lt;AuthRepository&gt;((ref) {
final apiConfig = ref.read(apiConfigProvider);
return AuthRepositoryImpl(
client: ref.read(apiClientProvider), // 直接注入 ApiClient
client: ref.read(networkSdkApiProvider), // 注入 Facade 接口
onTokenUpdate: (token) {
apiConfig.updateToken(token); // 内存networks_sdk
// secureStorage.saveToken(token); // 持久化storage_sdk待接入
@@ -2707,8 +2713,9 @@ class LoginViewModel extends _$LoginViewModel {
→ Repository: _client.executeRequest(LoginRequest(...))
→ ApiClient.executeRequest() ← networks_sdk 内部
→ AuthInterceptor ← 注入 token + headers
→ EncryptionInterceptor ← 加密请求体(预留)
→ Dio.request(baseURL + path, data) ← 实际 HTTP 请求
→ RetryInterceptor ← token 过期自动刷新重试
→ RetryInterceptor ← token 过期自动刷新重试 + 业务错误钩子
→ LoggingInterceptor ← 请求/响应日志
← request.decodeResponse(response) ← 自动解码
← ApiResponseWrapper.fromJson ← 拆 { code, msg, data }
@@ -2987,8 +2994,9 @@ flowchart TD
│ │ └── auth_guard.dart # 登录守卫switch AppRouteName穷举防漏路由
│ │
│ └── di/ # 全局 DI — 手动装配的 Provider
│ ├── network_provider.dart # NetworkMonitor + ApiConfig + ApiClient + SocketConfig + SocketClient + SocketManager
── app_providers.dart # 全局共享状态themeModeProvider + AuthNotifier
│ ├── network_provider.dart # NetworkMonitor + ApiConfig + NetworksSdkApi + SocketConfig + SocketClient + SocketManager
── db_provider.dart # StorageSdkApi注入 AppDatabase factory
│ └── app_providers.dart # AppInitializer + ThemeModeNotifier + AuthNotifier
├── features/ # 功能模块垂直切片Feature 间禁止直接 import
│ │
@@ -3022,6 +3030,7 @@ flowchart TD
│ ├── di/
│ │ └── settings_providers.dart # settingsRepositoryProvider待 storage_sdk 接入)
│ ├── presentation/
│ │ ├── settings_view_model.dart # @riverpod ViewModel设置页导航
│ │ └── theme_view_model.dart # @riverpod ViewModel生成 theme_view_model.g.dart
│ ├── usecases/
│ │ └── set_theme_usecase.dart # 主题切换用例
@@ -3081,6 +3090,8 @@ flowchart TD
└── ui/ # Core UI设计系统 + 可复用组件)
├── base/ # 设计 Token
│ ├── assets.dart # 静态资源路径常量AppAssetslogo / 占位图)
│ ├── icons.dart # 图标常量AppIcons导航 / 操作 / 聊天 / 用户 / 状态)
│ ├── app_theme.dart # ThemeData 组装Light / Dark
│ ├── colors.dart # 颜色体系(品牌色 / 语义色 / 灰阶)
│ ├── context_theme_ext.dart # BuildContext 主题扩展context.theme / context.colors
@@ -3172,7 +3183,7 @@ flowchart LR
direction TB
Step1["① 用户点击发送按钮"]
Step2["② ref.read(chatVM.notifier)<br/>.sendMessage(content)"]
Step3["③ ViewModel 调用 UseCase<br/>→ Repository → ApiClient"]
Step3["③ ViewModel 调用 UseCase<br/>→ Repository → NetworksSdkApi"]
Step4["④ state = state.copyWith(<br/>messages: [..., newMsg])"]
Step5["⑤ ref.watch(chatVM) 检测变化<br/>→ ConsumerWidget 自动 rebuild"]
Step6["⑥ UI 展示最新消息列表"]
@@ -3356,7 +3367,7 @@ abstract class ChatRepository {
// Data 层实现接口
class ChatRepositoryImpl implements ChatRepository {
final ApiClient _client;
final NetworksSdkApi _client;
final MessageLocalDataSource _localDataSource;
@override
@@ -5623,7 +5634,7 @@ flowchart TD
│ ├── file_storage.dart # 文件存储管理
│ └── image_cache.dart # 图片缓存
├── remote/ # Request 文件一个端点一个文件Repository 直接调 ApiClient
├── remote/ # Request 文件一个端点一个文件Repository 直接调 NetworksSdkApi
│ ├── login_request.dart # 登录端点
│ ├── logout_request.dart # 登出端点
│ ├── send_message_request.dart # 发消息端点
@@ -5648,17 +5659,17 @@ flowchart TD
Domain[Domain Layer<br/>domain/repositories/<br/>Repository 接口] -.实现.-> Repo[Data Layer<br/>data/repositories/<br/>Repository 实现]
Repo -->|读取| LocalDS[Local DataSource<br/>data/local/]
Repo -->|请求| ApiClient[ApiClient<br/>networks_sdk]
Repo -->|请求| SdkApi[NetworksSdkApi<br/>networks_sdk]
Repo -->|缓存| Cache[Cache Manager<br/>data/cache/]
LocalDS -->|Drift| DB[(Database)]
ApiClient -->|HTTP/WebSocket| API[API Server]
SdkApi -->|HTTP/WebSocket| API[API Server]
Cache -->|内存| Memory[Memory Cache]
style Domain fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
style Repo fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
style LocalDS fill:#c8e6c9,stroke:#388e3c
style ApiClient fill:#c8e6c9,stroke:#388e3c
style SdkApi fill:#c8e6c9,stroke:#388e3c
style Cache fill:#c8e6c9,stroke:#388e3c
</div>
@@ -5686,14 +5697,14 @@ class MessageLocalDataSource {
<ul>
<li><strong>一个端点 = 一个文件</strong>Response DTO + Request 类放在同一文件中</li>
<li><strong>Repository 直接调 ApiClient</strong>:无需 RemoteDataSource 中间层</li>
<li><strong>Repository 直接调 NetworksSdkApi</strong>:无需 RemoteDataSource 中间层</li>
<li><strong>@ApiRequest 注解 + 代码生成</strong>:自动实现 path / method / fromJson 注册</li>
</ul>
<pre><code>// 示例Repository 直接调用 Request
// data/repositories/message_repository_impl.dart
class MessageRepositoryImpl implements MessageRepository {
final ApiClient _client;
final NetworksSdkApi _client;
Future&lt;SendMessageData?&gt; sendMessage({
required String chatId,
@@ -5758,9 +5769,9 @@ flowchart LR
RepoImpl -->|1. 检查缓存| Cache[Cache]
RepoImpl -->|2. 读取本地| LocalDS[Local DS]
RepoImpl -->|3. 请求远程| ApiClient2[ApiClient]
RepoImpl -->|3. 请求远程| SdkApi2[NetworksSdkApi]
ApiClient2 -->|DTO| RepoImpl
SdkApi2 -->|DTO| RepoImpl
RepoImpl -->|转换| Entity[Entity]
Entity -->|返回| UC
@@ -5884,13 +5895,16 @@ flowchart LR
├── data/
│ ├── datasources/
│ │ ├── http/
│ │ │ ├── api_client.dart # Dio REST 客户端executeRequest&lt;T&gt; 唯一入口)
│ │ │ ├── api_client.dart # Dio REST 客户端
│ │ │ ├── token_refresh_manager.dart # Token 刷新管理(竞态安全 + 超时 + 时间窗口复用 + 主动刷新)
│ │ │ └── interceptor/
│ │ │ ├── auth_interceptor.dart # Token + 默认 header 注入
│ │ │ ├── retry_interceptor.dart # Token 刷新 + 瞬态错误重试
│ │ │ ── logging_interceptor.dart # 请求/响应日志
│ │ └── socket/
│ │ ── socket_client.dart # WebSocket 长连接(心跳/重连/Stream 输出)
│ │ │ ├── auth_interceptor.dart # Token + 默认 header 注入
│ │ │ ├── encryption_interceptor.dart # 请求加密 / 响应解密(预留给 cipher_guard_sdk
│ │ │ ── retry_interceptor.dart # Token 刷新 + 瞬态错误重试 + 业务错误钩子
│ │ │ └── logging_interceptor.dart # 请求/响应日志
│ │ ── socket/
│ │ │ └── socket_client.dart # WebSocket 长连接(心跳/重连/Stream/Token 热更新/加密钩子)
│ │ └── networks_sdk_method_channel_datasource.dart # 统一执行入口executeRequest / executeDownload
│ ├── dto/
│ │ ├── api_requestable.dart # 请求基类 + fromJson 注册表 + 解码扩展
│ │ └── api_response_wrapper.dart # { code, message/msg, data } 信封解析
@@ -5899,22 +5913,23 @@ flowchart LR
│ └── networks_messaging_repository_impl.dart
├── domain/
│ ├── entities/
│ │ ├── api_error.dart # @freezed HTTP 错误联合类型
│ │ ├── api_error.dart # @freezed HTTP 错误联合类型7 变体,含 cancelled
│ │ ├── encrypted_request.dart # 加密请求结果数据类path / headers / body 覆盖)
│ │ ├── socket_error.dart # @freezed WebSocket 错误联合类型
│ │ ├── socket_connection_state.dart # 连接状态 enum
│ │ ├── http_method.dart # GET / POST / PUT / DELETE / PATCH
│ │ └── api_request_type.dart # request / login / upload
│ │ └── api_request_type.dart # request / login / upload / stream / download
│ └── repositories/
│ ├── networks_sdk_repository.dart
│ └── networks_messaging_repository.dart
└── presentation/
├── facade/
│ ├── networks_sdk_api.dart # HTTP 公开 API 接口
│ └── networks_messaging_api.dart # WebSocket 公开 API 接口
│ ├── networks_sdk_api.dart # HTTP 公开 API 接口(含 executeDownload
│ └── networks_messaging_api.dart # WebSocket 公开 API 接口(含 updateToken / sendBytes
└── wiring/
├── api_config.dart # HTTP 配置baseURL / token / 回调)
├── socket_config.dart # WebSocket 配置(心跳 / 重连策略
├── network_callbacks.dart # 回调类型定义(OnTokenRefresh 等
├── api_config.dart # HTTP 配置baseURL / token / 回调 / 加密 / 重试 / 主动刷新
├── socket_config.dart # WebSocket 配置(心跳 / 重连 / 加密钩子 / 压缩
├── network_callbacks.dart # 回调类型定义(认证 / 加密 / 业务错误 / 下载进度 / WS 加密
├── networks_sdk_core.dart
├── networks_sdk_api_impl.dart
├── networks_messaging_api_impl.dart
@@ -5928,7 +5943,7 @@ flowchart LR
<tr><th>职责</th><th>SDK (networks_sdk)</th><th>App 层 (im_app)</th></tr>
</thead>
<tbody>
<tr><td>Dio 管理</td><td>ApiClient 内部创建管理</td><td>构造 ApiClient 传入 config</td></tr>
<tr><td>Dio 管理</td><td>ApiClient 内部创建管理</td><td>通过 NetworksSdkWiring.build(config:) 创建</td></tr>
<tr><td>baseURL</td><td>ApiConfig.baseURL</td><td>AppConfig.apiBaseUrl 提供初始值</td></tr>
<tr><td>Token 存储</td><td>ApiConfig.token内存</td><td>安全存储、持久化</td></tr>
<tr><td>Token 刷新</td><td>检测过期 → 调 onTokenRefresh</td><td>提供回调实现</td></tr>
@@ -5941,7 +5956,7 @@ flowchart LR
<tr><td>WebSocket 重连</td><td>指数退避自动重连1s→2s→4s→8s→16s→30s</td><td>无需关心</td></tr>
<tr><td>WebSocket 生命周期</td><td>提供 onEnterForeground/Background</td><td>App 层调用AppLifecycleListener</td></tr>
<tr><td>WebSocket 消息解析</td><td>JSON.decode → Stream 输出</td><td>App 层按 type 过滤 + DTO 解析</td></tr>
<tr><td>Riverpod</td><td>无依赖</td><td>Provider 包装 ApiClient / SocketClient</td></tr>
<tr><td>Riverpod</td><td>无依赖</td><td>Provider 包装 NetworksSdkApi / SocketClient</td></tr>
</tbody>
</table>
@@ -5968,7 +5983,7 @@ flowchart LR
<li><strong>一个端点 = 一个 Request 文件</strong>Response DTO + Request 类放在同一文件中</li>
<li><strong>Response DTO 必须有 <code>toEntity()</code></strong>:统一 DTO → Domain Entity 的转换入口</li>
<li><strong>持久化 DTO 和 Response DTO 分开</strong>Response DTO<code>XxxData</code>)在 request 文件中,持久化 DTO<code>XxxDto</code>)在 <code>data/models/</code></li>
<li><strong>禁止跳层</strong>ViewModel → Repository→ UseCase 按需)→ ApiClient,每层职责明确</li>
<li><strong>禁止跳层</strong>ViewModel → Repository→ UseCase 按需)→ NetworksSdkApi,每层职责明确</li>
</ul>
<h5>傻瓜式教程:从零开始定义并发送一个接口</h5>
@@ -6112,10 +6127,10 @@ class LoginRequest extends ApiRequestable&lt;LoginData&gt; // ← 固定写法
<!-- ────────── 第 2 步 ────────── -->
<h6>第 2 步:在 Repository 中调用 ApiClient,转为 Domain Entity</h6>
<h6>第 2 步:在 Repository 中调用 NetworksSdkApi,转为 Domain Entity</h6>
<p><strong>在哪写</strong><code>lib/data/repositories/auth_repository_impl.dart</code></p>
<p><strong>做什么</strong>直接调 ApiClient.executeRequest → 拿到 DTO → 回调写 Token → 转为 Domain Entity → 返回。</p>
<p><strong>做什么</strong>调 NetworksSdkApi.executeRequest → 拿到 DTO → 回调写 Token → 转为 Domain Entity → 返回。</p>
<pre><code class="language-dart">import 'package:networks_sdk/networks_sdk.dart';
import '../../domain/entities/user.dart';
@@ -6123,11 +6138,11 @@ import '../../domain/repositories/auth_repository.dart';
import '../remote/login_request.dart';
class AuthRepositoryImpl implements AuthRepository {
final ApiClient _client; // ← 直接注入 ApiClient
final NetworksSdkApi _client; // ← 注入 Facade 接口
final void Function(String?) _onTokenUpdate; // ← 回调,由 Provider 层组合
AuthRepositoryImpl({
required ApiClient client,
required NetworksSdkApi client,
required void Function(String?) onTokenUpdate,
}) : _client = client,
_onTokenUpdate = onTokenUpdate;
@@ -6137,7 +6152,7 @@ class AuthRepositoryImpl implements AuthRepository {
required String email,
required String password,
}) async {
// 1. 直接调 ApiClient,构造请求 → 发 HTTP → 自动解码 → 返回 DTO
// 1. 调 NetworksSdkApi,构造请求 → 发 HTTP → 自动解码 → 返回 DTO
final LoginData? loginData = await _client.executeRequest(
LoginRequest(email: email, password: password),
);
@@ -6162,15 +6177,15 @@ class AuthRepositoryImpl implements AuthRepository {
<p><strong>3.1 注册 ProviderDI 装配)</strong></p>
<p><strong>在哪写</strong><code>lib/features/{模块}/di/{模块}_providers.dart</code></p>
<p><strong>做什么</strong>:在 Feature 目录下创建 Provider 文件,注册该模块的 DI 链路Repository → UseCase 按需)。<code>app/di/</code> 只提供 SDK 基础设施ApiConfig / ApiClient),业务模块的 Provider 内聚在 Feature 目录下。</p>
<p><strong>做什么</strong>:在 Feature 目录下创建 Provider 文件,注册该模块的 DI 链路Repository → UseCase 按需)。<code>app/di/</code> 只提供 SDK 基础设施ApiConfig / NetworksSdkApi),业务模块的 Provider 内聚在 Feature 目录下。</p>
<pre><code class="language-dart">// ── features/auth/di/auth_providers.dart ──
// Repository直接注入 ApiClient + 回调组合多个 SDK 能力)
// Repository注入 Facade 接口 + 回调组合多个 SDK 能力)
final authRepositoryProvider = Provider&lt;AuthRepository&gt;((ref) {
final apiConfig = ref.read(apiConfigProvider);
return AuthRepositoryImpl(
client: ref.read(apiClientProvider), // 直接注入 ApiClient
client: ref.read(networkSdkApiProvider), // 注入 Facade 接口
onTokenUpdate: (token) {
apiConfig.updateToken(token); // 内存networks_sdk
// secureStorage.saveToken(token); // 持久化storage_sdk待接入
@@ -6199,7 +6214,7 @@ import '../../../domain/repositories/message_repository.dart';
// ── Repository ──
final messageRepositoryProvider = Provider&lt;MessageRepository&gt;((ref) {
return MessageRepositoryImpl(
client: ref.read(apiClientProvider), // 直接注入 ApiClient
client: ref.read(networkSdkApiProvider), // 注入 Facade 接口
);
});
@@ -6207,7 +6222,7 @@ final messageRepositoryProvider = Provider&lt;MessageRepository&gt;((ref) {
// 如需 UseCase多步编排、跨模块协调参考 auth_providers.dart 中的 loginUseCaseProvider。
</code></pre>
<p style="margin-bottom: 0;"><strong>原则</strong><code>app/di/</code> 只放 SDK 基础设施ApiConfig / ApiClient),业务模块的 DI 链路Repository → UseCase 按需)内聚在 <code>features/{模块}/di/{模块}_providers.dart</code> 中。</p>
<p style="margin-bottom: 0;"><strong>原则</strong><code>app/di/</code> 只放 SDK 基础设施ApiConfig / NetworksSdkApi),业务模块的 DI 链路Repository → UseCase 按需)内聚在 <code>features/{模块}/di/{模块}_providers.dart</code> 中。</p>
</div>
<p><strong>3.2 编写 ViewModel</strong></p>
@@ -6280,7 +6295,7 @@ class LoginViewModel extends _$LoginViewModel {
→ View: vm.doSomething(...)
→ ViewModel: ref.read(xxxRepositoryProvider).doSomething(...)
→ RepositoryImpl.doSomething() // data/repositories/
→ _client.executeRequest(XxxRequest) // 直接调 ApiClient
→ _client.executeRequest(XxxRequest) // 调 NetworksSdkApi
→ 自动注入 header → HTTP 请求 → 自动解码 → DTO
→ dto.toEntity() → Domain Entity
← state = state.copyWith(...) // 更新状态
@@ -6295,8 +6310,8 @@ class LoginViewModel extends _$LoginViewModel {
→ LoginUseCase: 格式校验(邮箱 + 密码) // features/auth/usecases/
→ LoginUseCase: authRepository.login(...)
→ AuthRepositoryImpl.login() // data/repositories/
→ _client.executeRequest(LoginRequest) // 直接调 ApiClient
→ AuthInterceptor → Dio.request → RetryInterceptor // 自动处理
→ _client.executeRequest(LoginRequest) // 调 NetworksSdkApi
→ Auth → Encryption → Dio.request → Retry → Logging // 拦截器链自动处理
← request.decodeResponse → LoginData.fromJson // 自动解码
← LoginDataDTO
→ onTokenUpdate(token) // 回调:内存写入 + 持久化
@@ -6360,7 +6375,7 @@ class SendMessageRequest extends ApiRequestable&lt;SendMessageData&gt;
}
</code></pre>
<p>保存 → 自动生成 → 然后在 Repository 中直接调 ApiClient 就完了:</p>
<p>保存 → 自动生成 → 然后在 Repository 中调 NetworksSdkApi 就完了:</p>
<pre><code class="language-dart">// 在 MessageRepositoryImpl 中添加
Future&lt;SendMessageData?&gt; sendMessage({
@@ -6568,18 +6583,18 @@ final apiConfigProvider = Provider&lt;ApiConfig&gt;((ref) {
);
});
/// API 客户端 Provider全局单例
/// 内部自动挂载 AuthInterceptor / RetryInterceptor / LoggingInterceptor
final apiClientProvider = Provider&lt;ApiClient&gt;((ref) {
/// Networks SDK API Provider全局单例Facade 接口
/// 内部自动挂载 AuthInterceptor / EncryptionInterceptor / RetryInterceptor / LoggingInterceptor
final networkSdkApiProvider = Provider&lt;NetworksSdkApi&gt;((ref) {
final config = ref.read(apiConfigProvider);
return ApiClient(config: config);
return NetworksSdkWiring.build(config: config);
});
</code></pre>
<h5>DI 装配总览</h5>
<pre><code>app/di/ ← 手动装配SDK 基础设施
└── network_provider.dart → apiConfigProvider + apiClientProvider
└── network_provider.dart → apiConfigProvider + networkSdkApiProvider
features/{模块}/di/ ← 手动装配:业务模块 DI 链路Repository → UseCase 按需)
├── auth/di/auth_providers.dart → authRepositoryProvider
@@ -6593,7 +6608,7 @@ features/{模块}/presentation/ ← @riverpod 自动生成ViewModel
</code></pre>
<p><strong>di/ 目录的定位</strong>:只放<strong>需要手动装配的 Provider</strong>构造注入、回调组合等。ViewModel Provider 由 <code>@riverpod</code> 注解自动生成(写在 <code>presentation/</code> 下),不在 <code>di/</code> 中。</p>
<p><strong>最小化原则</strong><code>app/di/</code> 只提供 SDK 能力ApiConfig / ApiClient),不放业务模块的 Provider。每个业务模块的手动装配 Provider 内聚在 <code>features/{模块}/di/{模块}_providers.dart</code> 中,需要时才创建。</p>
<p><strong>最小化原则</strong><code>app/di/</code> 只提供 SDK 能力ApiConfig / NetworksSdkApi),不放业务模块的 Provider。每个业务模块的手动装配 Provider 内聚在 <code>features/{模块}/di/{模块}_providers.dart</code> 中,需要时才创建。</p>
<h5>SDK 间解耦:回调注入模式</h5>
@@ -6605,7 +6620,7 @@ final authRepositoryProvider = Provider&lt;AuthRepository&gt;((ref) {
// final secureStorage = ref.read(secureStorageProvider); // storage_sdk待接入
return AuthRepositoryImpl(
client: ref.read(apiClientProvider), // 直接注入 ApiClient
client: ref.read(networkSdkApiProvider), // 注入 Facade 接口
onTokenUpdate: (token) {
apiConfig.updateToken(token); // 内存networks_sdk
// secureStorage.saveToken(token); // 持久化storage_sdk待接入
@@ -6635,6 +6650,7 @@ final authRepositoryProvider = Provider&lt;AuthRepository&gt;((ref) {
networkError: (msg) =&gt; showToast('网络错误: $msg'),
decodingError: (msg) =&gt; showToast('数据解析失败'),
apiError: (code, msg) =&gt; showToast('服务端错误[$code]: $msg'),
cancelled: () =&gt; {}, // 用户主动取消,通常不提示
unknown: (msg) =&gt; showToast('未知错误'),
);
}
@@ -7137,9 +7153,9 @@ abstract class ProfileRepository {
<pre><code>// data/repositories/profile_repository_impl.dart
class ProfileRepositoryImpl implements ProfileRepository {
final ApiClient _client;
final NetworksSdkApi _client;
ProfileRepositoryImpl({required ApiClient client})
ProfileRepositoryImpl({required NetworksSdkApi client})
: _client = client;
@override
@@ -7166,7 +7182,7 @@ import '../../../app/di/network_provider.dart';
// ── Repository ──
final profileRepositoryProvider = Provider&lt;ProfileRepository&gt;((ref) {
return ProfileRepositoryImpl(
client: ref.read(apiClientProvider), // 直接注入 ApiClient
client: ref.read(networkSdkApiProvider), // 注入 Facade 接口
);
});
@@ -7174,7 +7190,7 @@ final profileRepositoryProvider = Provider&lt;ProfileRepository&gt;((ref) {
// ViewModel 通过 @riverpod 注解自动生成 Provider无需额外注册。
</code></pre>
<p><strong>说明</strong>Profile 属于简单模板ViewModel 直接调 Repository无需 UseCase 中间层。<code>app/di/</code> 只提供 SDK 基础设施ApiConfig / ApiClient),业务模块的 DI 链路内聚在 Feature 目录下。</p>
<p><strong>说明</strong>Profile 属于简单模板ViewModel 直接调 Repository无需 UseCase 中间层。<code>app/di/</code> 只提供 SDK 基础设施ApiConfig / NetworksSdkApi),业务模块的 DI 链路内聚在 Feature 目录下。</p>
<h4>Feature 结构图示</h4>
@@ -7714,7 +7730,7 @@ sequenceDiagram
participant Repo as domain/repositories/<br/>message_repository.dart
participant RepoImpl as data/repositories/<br/>message_repository_impl.dart
participant LocalDS as data/local/<br/>message_local_ds.dart
participant SDK as networks_sdk/<br/>ApiClient / SocketClient
participant SDK as networks_sdk/<br/>NetworksSdkApi / SocketClient
participant WS as WebSocket Server
UI->>VM: 1. 用户点击发送按钮
@@ -7746,7 +7762,7 @@ sequenceDiagram
<li><strong>Repository 接口</strong>UseCase 通过 <code>domain/repositories/message_repository.dart</code> 接口调用</li>
<li><strong>Repository 实现</strong><code>data/repositories/message_repository_impl.dart</code> 实现具体逻辑</li>
<li><strong>本地优先</strong>:先保存到 <code>data/local/message_local_ds.dart</code></li>
<li><strong>网络发送</strong>Repository 直接调 SDKApiClient / SocketClient发送</li>
<li><strong>网络发送</strong>Repository 调 SDKNetworksSdkApi / SocketClient发送</li>
<li><strong>服务器确认</strong>WebSocket 服务器确认接收</li>
<li><strong>状态更新</strong>:更新本地数据库中的消息状态</li>
<li><strong>数据返回</strong>:层层返回,最终更新 UI</li>
@@ -7830,7 +7846,7 @@ abstract class MessageRepository {
<pre><code>// data/repositories/message_repository_impl.dart
class MessageRepositoryImpl implements MessageRepository {
final MessageLocalDataSource _localDS;
final ApiClient _client; // 直接注入 ApiClient / SocketClient
final NetworksSdkApi _client; // 注入 Facade 接口
MessageRepositoryImpl(this._localDS, this._client);
@@ -7861,7 +7877,7 @@ class MessageRepositoryImpl implements MessageRepository {
MessageRepository messageRepository(MessageRepositoryRef ref) {
return MessageRepositoryImpl(
ref.watch(messageLocalDataSourceProvider),
ref.watch(apiClientProvider), // 直接注入 ApiClient
ref.watch(networkSdkApiProvider), // 注入 Facade 接口
);
}
</code></pre>
@@ -7895,7 +7911,7 @@ sequenceDiagram
participant RepoImpl as data/repositories/<br/>chat_repository_impl.dart
participant Cache as data/cache/<br/>cache_manager.dart
participant LocalDS as data/local/<br/>chat_local_ds.dart
participant SDK as networks_sdk/<br/>ApiClient
participant SDK as networks_sdk/<br/>NetworksSdkApi
UI->>VM: 1. 页面初始化
VM->>UC: 2. 调用 LoadChatListUseCase
@@ -7907,7 +7923,7 @@ sequenceDiagram
else 缓存未命中
RepoImpl->>LocalDS: 6b. 读取本地数据库
LocalDS-->>RepoImpl: 7. 返回本地数据
RepoImpl->>SDK: 8. 直接调 ApiClient 请求远程数据
RepoImpl->>SDK: 8. 调 NetworksSdkApi 请求远程数据
SDK-->>RepoImpl: 9. 返回最新数据
RepoImpl->>LocalDS: 10. 更新本地数据库
RepoImpl->>Cache: 11. 更新缓存
@@ -8583,9 +8599,9 @@ abstract class ChatRepository {
Future&lt;void&gt; sendMessage(Message message);
}
// 2. Repository 实现层(直接注入 ApiClient
// 2. Repository 实现层(注入 Facade 接口
class ChatRepositoryImpl implements ChatRepository {
final ApiClient client;
final NetworksSdkApi client;
final LocalDataSource localDataSource;
final MessageMapper mapper;
@@ -8603,7 +8619,7 @@ class ChatRepositoryImpl implements ChatRepository {
return localDTOs.map(mapper.toEntity).toList();
}
// 直接调 ApiClient 从远程获取
// 调 NetworksSdkApi 从远程获取
final response = await client.executeRequest(
GetMessagesRequest(chatId: chatId),
);
@@ -9770,6 +9786,146 @@ flowchart TD
<p><strong>单一职责</strong>每个模块只做一件事UseCase/ViewModel/Repository 各司其职</p>
</blockquote>
<!-- ═══════════════════════ 第八部分UI 设计规范 ═══════════════════════ -->
<h2 id="part8-ui-design" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 8px;">第八部分UI 设计规范</h2>
<p>本章定义颜色、字体、组件、弹框、图标的命名与使用规则明确设计与研发的协作约定。Figma 按此命名,代码按此封装,两端名称一一对应。</p>
<h3 id="8-0-核心约定">8.0 核心约定</h3>
<div style="background: #e3f2fd; padding: 20px; border-radius: 8px; border-left: 4px solid #1565c0; margin: 20px 0;">
<p style="margin-top: 0; font-weight: 700; color: #1565c0;">全局只有一份</p>
<p>颜色、字体、基础组件、业务弹框、图片、图标——Figma 里每种元素只有一个定义。没有"备用版本",没有"临时副本",不允许两个"差不多一样"的组件并存。</p>
</div>
<ol>
<li><strong>Figma 命名是重中之重</strong>:点中任何元素都必须看到抽象名称。六类无例外:
<ul>
<li><code>颜色</code> — 如 <code>primary</code><code>surface</code></li>
<li><code>字体</code> — 如 <code>Body/Medium</code><code>Label/Small</code></li>
<li><code>基础组件</code> — 如 <code>Button/Primary</code><code>Input/Default</code>,全局只有一个版本</li>
<li><code>业务弹框</code> — 如 <code>Dialog/Confirm</code></li>
<li><code>图片</code> — Figma 统一导出,代码侧 <code>AppAssets</code> 注册,不硬编码路径</li>
<li><code>图标</code> — 如 <code>send</code><code>more_options</code>,代码侧 <code>AppIcons</code> 调用</li>
</ul>
</li>
<li><strong>基础组件定稿后不随意改动</strong>:需改动时必须先告知研发,评估影响范围,双方同步后再执行</li>
<li><strong>UI 团队自主维护 UI 基建体系</strong>:研发照着 Figma 名字封装,名称必须完全一致</li>
<li><strong>所有元素遵循同一套命名规则</strong>:新增任何元素先在 Figma 定名,研发用相同名字注册</li>
</ol>
<div style="background: #FFF8E6; border-left: 3px solid #F2C94C; border-radius: 0 8px 8px 0; padding: 12px 16px; margin: 16px 0; font-size: 14px; color: #5F4B00;">
<strong>图片和组件是重灾区:</strong>没有统一来源时不同研发各自导出同一张图文件名不同、尺寸不同最终项目里堆满重复文件。Figma 统一命名、代码统一注册,才能从源头堵住。
</div>
<h3 id="8-1-颜色体系">8.1 颜色体系</h3>
<p>所有颜色通过抽象名称引用。抽象名在亮色 / 暗色两套主题下对应不同色值,修改主题只需改映射表,不需逐个找组件。</p>
<h4>语义色(随主题变化)</h4>
<table>
<thead>
<tr><th>抽象名</th><th>Figma 名</th><th>亮色</th><th>暗色</th><th>用途</th></tr>
</thead>
<tbody>
<tr><td><code>primary</code></td><td>Primary</td><td>#2F80ED</td><td>#5BA3F5</td><td>主操作、链接、选中态</td></tr>
<tr><td><code>background</code></td><td>Background</td><td>#F8F9FA</td><td>#202124</td><td>页面底色</td></tr>
<tr><td><code>surface</code></td><td>Surface</td><td>#FFFFFF</td><td>#3C4043</td><td>卡片、弹框、输入框</td></tr>
<tr><td><code>onSurface</code></td><td>On Surface</td><td>#202124</td><td>#FFFFFF</td><td>surface 上的文字</td></tr>
<tr><td><code>error</code></td><td>Error</td><td colspan="2">#EB5757</td><td>错误状态</td></tr>
<tr><td><code>success</code></td><td>Success</td><td colspan="2">#27AE60</td><td>成功状态</td></tr>
<tr><td><code>warning</code></td><td>Warning</td><td colspan="2">#F2C94C</td><td>警告状态</td></tr>
</tbody>
</table>
<h4>灰阶(固定值,不随主题变化)</h4>
<table>
<thead><tr><th>名称</th><th>色值</th><th>名称</th><th>色值</th><th>名称</th><th>色值</th></tr></thead>
<tbody>
<tr><td>white</td><td>#FFFFFF</td><td>gray50</td><td>#F8F9FA</td><td>gray100</td><td>#F1F3F4</td></tr>
<tr><td>gray200</td><td>#E8EAED</td><td>gray400</td><td>#BDC1C6</td><td>gray600</td><td>#80868B</td></tr>
<tr><td>gray800</td><td>#3C4043</td><td>gray900</td><td>#202124</td><td>black</td><td>#000000</td></tr>
</tbody>
</table>
<p><strong>使用原则</strong>:需随主题切换 → 用语义色(<code>primary</code><code>surface</code>);亮暗保持不变 → 用灰阶固定值。</p>
<h3 id="8-2-字体体系">8.2 字体体系</h3>
<p>字体按层级分五档Display、Headline、Title、Body、Label每档三个尺寸。Figma 中按 <strong>层级/尺寸</strong> 格式命名(如 <code>Body/Large</code>),开发用同名变量调用。</p>
<table>
<thead><tr><th>Figma 名称</th><th>字号</th><th>字重</th><th>行高</th><th>字距</th><th>典型用途</th></tr></thead>
<tbody>
<tr><td colspan="6" style="font-weight:700;color:#80868B;font-size:12px;">DISPLAY</td></tr>
<tr><td>Display/Large</td><td>57</td><td>400</td><td>64</td><td>-0.25</td><td>启动页大标题、空状态</td></tr>
<tr><td>Display/Medium</td><td>45</td><td>400</td><td>52</td><td></td><td></td></tr>
<tr><td>Display/Small</td><td>36</td><td>400</td><td>44</td><td></td><td></td></tr>
<tr><td colspan="6" style="font-weight:700;color:#80868B;font-size:12px;">HEADLINE</td></tr>
<tr><td>Headline/Large</td><td>32</td><td>400</td><td>40</td><td></td><td>页面主标题、导航栏</td></tr>
<tr><td>Headline/Medium</td><td>28</td><td>400</td><td>36</td><td></td><td></td></tr>
<tr><td>Headline/Small</td><td>24</td><td>400</td><td>32</td><td></td><td></td></tr>
<tr><td colspan="6" style="font-weight:700;color:#80868B;font-size:12px;">TITLE</td></tr>
<tr><td>Title/Large</td><td>22</td><td>500</td><td>28</td><td></td><td>会话列表名称、设置项标题</td></tr>
<tr><td>Title/Medium</td><td>16</td><td>500</td><td>24</td><td>0.15</td><td>卡片标题、列表主行</td></tr>
<tr><td>Title/Small</td><td>14</td><td>500</td><td>20</td><td>0.1</td><td></td></tr>
<tr><td colspan="6" style="font-weight:700;color:#80868B;font-size:12px;">BODY</td></tr>
<tr><td>Body/Large</td><td>16</td><td>400</td><td>24</td><td>0.5</td><td>聊天气泡、表单输入</td></tr>
<tr><td>Body/Medium</td><td>14</td><td>400</td><td>20</td><td>0.25</td><td>正文说明、列表副行</td></tr>
<tr><td>Body/Small</td><td>12</td><td>400</td><td>16</td><td>0.4</td><td>辅助信息、提示文字</td></tr>
<tr><td colspan="6" style="font-weight:700;color:#80868B;font-size:12px;">LABEL</td></tr>
<tr><td>Label/Large</td><td>14</td><td>500</td><td>20</td><td>0.1</td><td>按钮文字、Tab 标签、Badge</td></tr>
<tr><td>Label/Medium</td><td>12</td><td>500</td><td>16</td><td>0.5</td><td>次要标签、徽标文字</td></tr>
<tr><td>Label/Small</td><td>11</td><td>500</td><td>16</td><td>0.5</td><td>最小粒度标签</td></tr>
<tr><td colspan="6" style="font-weight:700;color:#80868B;font-size:12px;">语义样式</td></tr>
<tr><td>Section Label</td><td>13</td><td>600</td><td></td><td>0.5</td><td>列表分组标题、设置分区</td></tr>
<tr><td>Body/Muted</td><td>12</td><td>400</td><td>16</td><td></td><td>说明文字(灰色,低对比度)</td></tr>
<tr><td>Body/Error</td><td>12</td><td>400</td><td>16</td><td></td><td>表单错误提示(红色)</td></tr>
<tr><td>Label/Muted</td><td>12</td><td>500</td><td>16</td><td></td><td>时间戳、元数据(低对比度)</td></tr>
</tbody>
</table>
<h3 id="8-3-组件-button">8.3 组件 — Button</h3>
<p>按钮共四种变体,每种有明确使用场景和 Figma 组件名。每个页面上主操作只用一个 Primary。</p>
<table>
<thead><tr><th>Figma 组件名</th><th>用途</th><th>亮色样式</th><th>暗色样式</th><th>状态</th></tr></thead>
<tbody>
<tr><td><code>Button/Primary</code></td><td>主操作(登录、发送、确认),每屏最多一次</td><td>背景 #2F80ED白字</td><td>背景 #5BA3F5白字</td><td>默认 / Loading / 禁用(#BDC1C6</td></tr>
<tr><td><code>Button/Secondary</code></td><td>次要操作(注册、稍后再说),描边样式</td><td>描边 #2F80ED蓝字</td><td>描边 #5BA3F5蓝字</td><td>默认 / 禁用</td></tr>
<tr><td><code>Button/Text</code></td><td>辅助链接(忘记密码、查看全部、弹框取消)</td><td>无背景,蓝字</td><td>无背景,蓝字</td><td>默认</td></tr>
<tr><td><code>Button/Inverse</code></td><td>反色按钮(深色背景高亮),支持左侧图标</td><td>背景 #202124白字</td><td>背景 #FFFFFF黑字</td><td>默认</td></tr>
</tbody>
</table>
<h3 id="8-4-业务弹框-dialog">8.4 业务弹框 — Dialog</h3>
<p>当前封装一种通用确认弹框 <code>Dialog/Confirm</code>,后续新增先在 Figma 以 <code>Dialog/</code> 前缀命名。</p>
<table>
<thead><tr><th>项目</th><th>说明</th></tr></thead>
<tbody>
<tr><td>结构</td><td>标题(不超 15 字) + 内容 + 操作区(取消 Text 样式 | 确认 Primary 样式)</td></tr>
<tr><td>可配置</td><td>标题文字、内容文字、确认/取消标签、点击背景是否可关闭</td></tr>
<tr><td>返回值</td><td>确认 / 取消 / 关闭(点击背景)</td></tr>
</tbody>
</table>
<h3 id="8-5-图标规范">8.5 图标规范</h3>
<ol>
<li><strong>UI 先命名,开发跟随</strong>Figma 确定名称,开发用完全相同的名称封装到 <code>AppIcons</code></li>
<li><strong>名称有实际语义</strong>:全小写下划线,如 <code>send</code><code>add_contact</code><code>more_options</code>。不用拼音,不缩写</li>
<li><strong>统一用 AppIcons 调用</strong>:不允许直接用裸 icon 库变量,替换时改一处全局生效</li>
<li><strong>同义图标只保留一个</strong>:同功能图标在整个产品内只存在一种</li>
</ol>
<p><strong>新增图标流程</strong>:设计师 Figma 确认名称 → 告知开发 → 开发用相同名称在 AppIcons 注册。两端名称必须完全一致。</p>
</main>
</div>