Files
video/app/lib/services/auth_service.dart
2026-10-16 13:51:00 +08:00

104 lines
3.5 KiB
Dart
Raw Permalink 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 '../core/network/api_client.dart';
import '../core/network/api_exception.dart';
import '../core/network/endpoints.dart';
import '../core/storage/token_store.dart';
import '../models/user_model.dart';
/// 认证服务(《03-接口文档》第 3 章)
class AuthService {
AuthService._();
/// 3.1 发送短信验证码 scene: register/login/reset
/// 返回开发模式验证码(短信通道未配置时服务端下发),生产通道下为 null
static Future<String?> sendSmsCode(String phone, String scene) async {
final data = await ApiClient.post<Map<String, dynamic>>(
Endpoints.smsSend,
data: {'phone': phone, 'scene': scene},
);
return data['dev_code'] as String?;
}
/// 3.2 发送邮箱验证码
static Future<String?> sendEmailCode(String email, String scene) async {
final data = await ApiClient.post<Map<String, dynamic>>(
Endpoints.emailSend,
data: {'email': email, 'scene': scene},
);
return data['dev_code'] as String?;
}
/// 3.3 注册(type: phone/email,可带邀请码),返回 Token 对与用户信息
static Future<LoginResult> register({
required String type,
required String account,
required String code,
required String password,
String? nickname,
String? inviteCode,
}) async {
final data = await ApiClient.post<Map<String, dynamic>>(Endpoints.register, data: {
'type': type,
if (type == 'phone') 'phone': account else 'email': account,
'code': code,
'password': password,
if (nickname != null && nickname.isNotEmpty) 'nickname': nickname,
if (inviteCode != null && inviteCode.isNotEmpty) 'invite_code': inviteCode,
});
return _fromTokenData(data);
}
/// 3.4 登录(type: phone_password/phone_code/email_password)
static Future<LoginResult> login({
required String type,
String? phone,
String? email,
String? password,
String? code,
}) async {
final data = await ApiClient.post<Map<String, dynamic>>(Endpoints.login, data: {
'type': type,
if (phone != null) 'phone': phone,
if (email != null) 'email': email,
if (password != null) 'password': password,
if (code != null) 'code': code,
});
return _fromTokenData(data);
}
/// 3.6 退出登录
static Future<void> logout() async {
try {
await ApiClient.I.post(Endpoints.logout);
} on ApiException catch (e) {
// Token 已失效等情况忽略,本地照常清理
if (!e.isUnauthorized) rethrow;
}
}
/// 统一解析 Token 响应:access/refresh 入安全存储,user 转模型
static Future<LoginResult> _fromTokenData(Map<String, dynamic> data) async {
// 登录/注册响应携带新 token 对,必须先落存储(否则登录态判定与后续请求鉴权全挂)
final access = data['access_token'] as String?;
final refresh = data['refresh_token'] as String?;
if (access != null && access.isNotEmpty && refresh != null && refresh.isNotEmpty) {
await TokenStore.save(access: access, refresh: refresh);
}
final user = UserModel.fromJson((data['user'] as Map<String, dynamic>?) ?? const {});
final trial = data['trial_granted'] as Map<String, dynamic>?;
return LoginResult(
user: user,
trialPlanName: trial?['plan_name'] as String?,
);
}
}
/// 登录/注册结果
class LoginResult {
LoginResult({required this.user, this.trialPlanName});
final UserModel user;
/// 注册赠送的体验会员名称(关闭赠送开关时为 null)
final String? trialPlanName;
}