136 lines
4.2 KiB
Dart
136 lines
4.2 KiB
Dart
import 'package:networks_sdk/networks_sdk.dart';
|
||
import 'package:storage_sdk/storage_sdk.dart';
|
||
|
||
import 'package:im_app/core/services/socket_manager.dart';
|
||
import 'package:im_app/domain/entities/user.dart';
|
||
import 'package:im_app/domain/repositories/auth_repository.dart';
|
||
|
||
/// 登录用例
|
||
///
|
||
/// 封装登录的完整业务流程:
|
||
/// - sendOtp:格式校验 → 发短信
|
||
/// - verifyAndLogin:格式校验 → 校验验证码 → 登录 → 初始化 WebSocket → 打开本地数据库
|
||
///
|
||
/// ## 为什么需要 UseCase?
|
||
///
|
||
/// 登录有明确的多步业务规则,UseCase 把这些规则集中封装,
|
||
/// ViewModel 只需一行调用。
|
||
///
|
||
/// ## 数据流位置
|
||
///
|
||
/// ```
|
||
/// LoginViewModel.sendOtp(countryCode, contact)
|
||
/// → ★ LoginUseCase.sendOtp() ★ ← 你在这里(步骤 1)
|
||
/// → 格式校验(手机号)
|
||
/// → AuthRepository.sendOtp()
|
||
///
|
||
/// LoginViewModel.verifyAndLogin(code)
|
||
/// → ★ LoginUseCase.verifyAndLogin() ★ ← 你在这里(步骤 2+3)
|
||
/// → 格式校验(验证码)
|
||
/// → AuthRepository.verifyOtp() → vcode_token
|
||
/// → AuthRepository.login() → User + token
|
||
/// → SocketManager.connect(token)
|
||
/// → StorageSdkApi.openDatabase(uid)
|
||
/// ← 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] — 手机号格式不合法
|
||
/// - [ApiError] — 网络/服务端错误
|
||
Future<void> sendOtp({
|
||
required String countryCode,
|
||
required String contact,
|
||
}) async {
|
||
_validatePhone(contact);
|
||
await _authRepository.sendOtp(
|
||
countryCode: countryCode,
|
||
contact: contact,
|
||
);
|
||
}
|
||
|
||
/// 步骤 2+3:校验验证码并完成登录,返回 [User]
|
||
///
|
||
/// 内部串行:verifyOtp → login → connectWebSocket → openDatabase
|
||
///
|
||
/// 抛出:
|
||
/// - [FormatException] — 验证码格式不合法
|
||
/// - [ApiError] — 网络/服务端错误
|
||
Future<User> verifyAndLogin({
|
||
required String countryCode,
|
||
required String contact,
|
||
required String code,
|
||
}) async {
|
||
_validateCode(code);
|
||
|
||
// 校验验证码,换取 vcode_token
|
||
final vcodeToken = await _authRepository.verifyOtp(
|
||
countryCode: countryCode,
|
||
contact: contact,
|
||
code: code,
|
||
);
|
||
|
||
// 用 vcode_token 登录(token 写入由 Repository._onTokenUpdate 回调处理)
|
||
final user = await _authRepository.login(
|
||
countryCode: countryCode,
|
||
contact: contact,
|
||
vcodeToken: vcodeToken,
|
||
);
|
||
|
||
// 连接 WebSocket(token 已由 Repository 写入 ApiConfig,直接读取)
|
||
final token = _apiConfig.token;
|
||
if (token != null && token.isNotEmpty) {
|
||
await _socketManager.connect(token: token);
|
||
}
|
||
|
||
// 按用户 uid 打开本地数据库
|
||
await _storageLifeCycle.openDatabase(user.uid);
|
||
|
||
// TODO: 扩展点 — 同步联系人列表、注册推送 token
|
||
|
||
return user;
|
||
}
|
||
|
||
void _validatePhone(String contact) {
|
||
final trimmed = contact.trim();
|
||
if (trimmed.isEmpty) {
|
||
throw const FormatException('手机号不能为空'); // TODO: 接入国际化
|
||
}
|
||
if (trimmed.length < 7 || trimmed.length > 15) {
|
||
throw const FormatException('手机号长度不正确'); // TODO: 接入国际化
|
||
}
|
||
if (!RegExp(r'^\d+$').hasMatch(trimmed)) {
|
||
throw const FormatException('手机号只能包含数字'); // TODO: 接入国际化
|
||
}
|
||
}
|
||
|
||
void _validateCode(String code) {
|
||
final trimmed = code.trim();
|
||
if (trimmed.isEmpty) {
|
||
throw const FormatException('验证码不能为空'); // TODO: 接入国际化
|
||
}
|
||
if (!RegExp(r'^\d+$').hasMatch(trimmed)) {
|
||
throw const FormatException('验证码只能包含数字'); // TODO: l10n
|
||
}
|
||
}
|
||
}
|