118 lines
4.1 KiB
Dart
118 lines
4.1 KiB
Dart
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)
|
||
/// ← LoginData(DTO)
|
||
/// → _onTokenUpdate(token) ← 回调写入 Token(内存 + 持久化,由 Provider 层组合)
|
||
/// ← LoginData.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<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: 接入国际化
|
||
}
|
||
}
|
||
}
|