104 lines
3.5 KiB
Dart
104 lines
3.5 KiB
Dart
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;
|
||
}
|