import 'dart:io'; import 'package:dio/dio.dart'; import '../../../core/network/api_response.dart'; import '../../../core/utils/device_fingerprint_service.dart'; /// 注册前 `/uc/check-account` 的语义结果(与 Web checkAccountForRegister 一致): /// 业务成功 code=0 表示可申请注册;占用由非 0 与异常文案体现;掩码「用户名或密码错误」放行可继续注册。 enum CheckAccountForRegisterResult { /// 账号已占用(如接口返回「已注册」等文案) alreadyRegistered, /// 可申请注册(成功或掩码「用户名或密码错误」) available, } /// 登录响应数据 class LoginResult { final String token; final String? uid; final String? email; const LoginResult({required this.token, this.uid, this.email}); factory LoginResult.fromJson(Map json) { return LoginResult( token: json['token'] as String? ?? '', uid: json['id']?.toString(), email: json['email'] as String?, ); } } /// 认证 API 服务(无状态,只封装 HTTP 调用) /// /// 所有接口使用 form-urlencoded 格式提交 class AuthService { const AuthService(this._dio); final Dio _dio; /// form-urlencoded 请求选项 static final _formOptions = Options( contentType: 'application/x-www-form-urlencoded', ); /// 设备类型:1=iOS,2=Android,5=PC/其他 static int get _deviceType { if (Platform.isIOS) return 1; if (Platform.isAndroid) return 2; return 5; } /// 与 Web 一致的掩码文案:账号不存在时接口不落真实原因 static bool isRegisterAvailabilityMaskMessage(String message) { return message.contains('用户名或密码错误'); } /// 与 Web `checkAccountForRegister` 一致:接口用业务异常文案表示「已占用」 static bool isAccountAlreadyRegisteredHint(String message) { if (message.contains('已注册')) { return true; } if (message.contains('已存在')) { return true; } if (message.contains('已被使用')) { return true; } return RegExp( r'already\s+(exists|registered)', caseSensitive: false, ).hasMatch(message); } // ── 登录发码前校验密码(与 Web `/uc/check-password` 一致)──────── Future checkLoginPassword({ required String username, required String password, }) async { await _dio.post( 'uc/check-password', data: {'username': username.trim(), 'password': password}, options: _formOptions, ); } // ── 注册发码前校验账号是否已存在(与 Web `/uc/check-account` 一致)── Future checkAccountForRegister( String username, ) async { try { await _dio.post( 'uc/check-account', data: {'username': username.trim()}, options: _formOptions, ); return CheckAccountForRegisterResult.available; } on DioException catch (e) { final err = e.error; if (err is ApiException) { final msg = err.message; if (isAccountAlreadyRegisteredHint(msg)) { return CheckAccountForRegisterResult.alreadyRegistered; } if (isRegisterAvailabilityMaskMessage(msg)) { return CheckAccountForRegisterResult.available; } } rethrow; } } // ── 发送登录验证码 ────────────────────────────────────── Future sendLoginEmailCode(String email) async { await _dio.post( 'uc/login/email/code', data: {'username': email, 'email': email}, options: _formOptions, ); } // ── 密码+验证码登录 ────────────────────────────────────── /// [vtype] 验证类型:2=邮箱验证码,3=谷歌验证码 Future loginWithPassword({ required String email, required String password, required String code, String vtype = '2', }) async { final deviceInfo = await DeviceFingerprintService.getDeviceInfo(); final response = await _dio.post>( 'uc/login', data: { 'username': email, 'password': password, 'country': '', 'vtype': vtype, 'vcode': code, 'deviceType': _deviceType, 'deviceFingerprint': deviceInfo.fingerprint, 'deviceModel': deviceInfo.model, 'osVersion': deviceInfo.osVersion, }, options: _formOptions, ); final data = (response.data?['data'] as Map?) ?? {}; return LoginResult.fromJson(data); } // ── 发送注册验证码 ────────────────────────────────────── Future sendRegisterEmailCode(String email) async { await _dio.post( 'uc/reg/email/code', data: {'username': email, 'email': email}, options: _formOptions, ); } // ── 邮箱注册 ──────────────────────────────────────────── Future registerWithEmail({ required String email, required String password, required String code, String? inviteCode, String country = '不丹', }) async { await _dio.post>( 'uc/register/email', data: { 'email': email, 'username': email, 'password': password, 'code': code, 'country': country, 'promotion': inviteCode ?? '', 'deviceType': _deviceType, }, options: _formOptions, ); } // ── 发送重置密码验证码 ────────────────────────────────── Future sendResetEmailCode(String email) async { await _dio.post( 'uc/reset/email/code', data: {'username': email, 'account': email}, options: _formOptions, ); } // ── 重置密码 ──────────────────────────────────────────── Future resetPassword({ required String email, required String password, required String code, }) async { await _dio.post( 'uc/reset/login/password', data: {'email': email, 'password': password, 'code': code, 'account': email, 'mode': 1}, options: _formOptions, ); } // ── 退出登录 ──────────────────────────────────────────── Future logout() async { await _dio.post('uc/loginout'); } // ── 获取用户信息 ──────────────────────────────────────── Future> getMyInfo() async { final response = await _dio.post>('uc/member/my-info'); return (response.data?['data'] as Map?) ?? {}; } // ── 谷歌验证器绑定流程 ────────────────────────────────── /// 获取谷歌验证器秘钥 /// 返回 { link: "otpauth://...", secret: "P12EB42NCCSE8VTX" } Future> getGoogleSecret() async { final response = await _dio.get>( 'uc/google/sendgoogle', ); return (response.data?['data'] as Map?) ?? {}; } /// 发送谷歌验证器绑定的邮箱验证码 Future sendGoogleAuthEmailCode() async { await _dio.post( 'uc/googleAuth/email/code', options: _formOptions, ); } /// 绑定谷歌验证器 /// [codes] Google Authenticator 6位动态码 /// [vcode] 邮箱收到的6位验证码 /// [secret] 第一步获取的密钥 /// [vtype] 验证方式 "2"=邮箱 Future bindGoogleAuth({ required String codes, required String vcode, required String secret, String vtype = '2', }) async { await _dio.post( 'uc/google/googleAuthWithSms', data: { 'codes': codes, 'vtype': vtype, 'vcode': vcode, 'secret': secret, }, options: _formOptions, ); } // ── 资金密码 ────────────────────────────────────────────── /// 首次设置资金密码 Future setFundPassword(String password) async { await _dio.post( 'uc/approve/transaction/password', data: {'jyPassword': password}, options: _formOptions, ); } /// 发送资金密码重置邮箱验证码 Future sendFundPasswordEmailCode() async { await _dio.post( 'uc/transaction/email/code', options: _formOptions, ); } /// 重置资金密码 /// 发送修改登录密码邮箱验证码 — POST uc/updatePassword/email/code Future sendChangePasswordEmailCode() async { await _dio.post( 'uc/updatePassword/email/code', options: _formOptions, ); } /// 修改登录密码 — POST uc/approve/update/password Future changeLoginPassword({ required String oldPassword, required String newPassword, required String vcode, }) async { await _dio.post( 'uc/approve/update/password', data: { 'oldPassword': oldPassword, 'newPassword': newPassword, 'vtype': '2', 'vcode': vcode, }, options: _formOptions, ); } Future resetFundPassword({ required String newPassword, required String vcode, String vtype = '2', }) async { await _dio.post( 'uc/approve/reset/transaction/password', data: { 'newPassword': newPassword, 'vtype': vtype, 'vcode': vcode, }, options: _formOptions, ); } }