Files
customer-im-client-dev/apps/im_app/lib/features/login/usecases/login_usecase.dart

116 lines
4.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
/// ← LoginResponseSDK 已拆包 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 登录 → 拿到 Usertoken 写入由 Repository 处理)
/// 3. 用已存入 ApiConfig 的 token 连接 WebSocket
/// 4. 按 user id 打开本地数据库
///
/// 抛出:
/// - [FormatException] — 邮箱或密码格式不合法
/// - [ApiError] — 网络/服务端错误(由 Repository 透传)
Future<User> 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: 接入国际化
}
}
}