import 'package:networks_sdk/networks_sdk.dart'; import 'package:storage_sdk/storage_sdk.dart'; import '../../../core/services/socket_manager.dart'; import '../../../domain/entities/user.dart'; import '../../../domain/repositories/auth_repository.dart'; /// 登录用例 /// /// 封装登录的完整业务流程: /// 格式校验 → 调 Repository 登录 → 初始化 WebSocket → 打开本地数据库 → 返回 User /// /// ## 为什么需要 UseCase? /// /// ViewModel 直接调 Repository 也能跑通,但登录有明确的多步业务规则: /// - 格式校验(不发无效请求,省流量、减少服务端压力) /// - 登录后初始化 WebSocket 连接 /// - 登录后按 user id 打开对应的本地数据库 /// /// 把这些规则封装在 UseCase 里,ViewModel 只需一行调用。 /// /// ## 数据流位置 /// /// ``` /// LoginViewModel.login(email, password) /// → ★ LoginUseCase.execute() ★ ← 你在这里 /// → 格式校验(邮箱 + 密码) /// → AuthRepository.login() /// → AuthRepositoryImpl.login() /// → _client.executeRequest(LoginRequest) /// ← LoginResponse(SDK 已拆包 envelope) /// → _onTokenUpdate(accessToken) ← 回调写入 Token(内存 + 持久化,由 Provider 层组合) /// ← LoginResponse.toEntity() → User /// → SocketManager.connect(token) ← 登录后连接 WebSocket /// → StorageSdkApi.openDatabase(user.id) ← 按用户 id 打开本地库 /// ← User /// ``` class LoginUseCase { final AuthRepository _authRepository; final SocketManager _socketManager; final ApiConfig _apiConfig; final StorageSdkApi _storageApi; StorageSdkLifecycle get _storageLifeCycle => _storageApi as StorageSdkLifecycle; LoginUseCase({ required AuthRepository authRepository, required SocketManager socketManager, required ApiConfig apiConfig, required StorageSdkApi storageApi, }) : _authRepository = authRepository, _socketManager = socketManager, _apiConfig = apiConfig, _storageApi = storageApi; /// 执行登录 /// /// 1. 格式校验 → 不合法直接抛 [FormatException] /// 2. 调 Repository 登录 → 拿到 User(token 写入由 Repository 处理) /// 3. 用已存入 ApiConfig 的 token 连接 WebSocket /// 4. 按 user id 打开本地数据库 /// /// 抛出: /// - [FormatException] — 邮箱或密码格式不合法 /// - [ApiError] — 网络/服务端错误(由 Repository 透传) Future execute({ required String email, required String password, }) async { // ── 1. 格式校验 ── _validateEmail(email); _validatePassword(password); // ── 2. 登录 ── final user = await _authRepository.login(email: email, password: password); // ── 3. 连接 WebSocket ── // token 在 Repository 的 _onTokenUpdate 回调中已写入 ApiConfig, // 此处直接读取,避免改动现有接口。 final token = _apiConfig.token; if (token != null && token.isNotEmpty) { await _socketManager.connect(token: token); } // ── 4. 打开数据库 ── // TODO: 当服务端返回整型 uid 时,换成 user.uid;目前用 hashCode 作为临时标识。 await _storageLifeCycle.openDatabase(user.hashCode); // TODO: 后续扩展点 // - 同步联系人列表 // - 注册推送 token return user; } void _validateEmail(String email) { if (email.trim().isEmpty) { throw const FormatException('邮箱不能为空'); // TODO: 接入国际化 } final emailRegex = RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$'); if (!emailRegex.hasMatch(email.trim())) { throw const FormatException('邮箱格式不正确'); // TODO: 接入国际化 } } void _validatePassword(String password) { if (password.isEmpty) { throw const FormatException('密码不能为空'); // TODO: 接入国际化 } if (password.length < 6) { throw const FormatException('密码长度不能少于 6 位'); // TODO: 接入国际化 } } }