auth_service.dart 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. import 'dart:io';
  2. import 'package:dio/dio.dart';
  3. import '../../../core/network/api_response.dart';
  4. import '../../../core/utils/device_fingerprint_service.dart';
  5. /// 注册前 `/uc/check-account` 的语义结果(与 Web checkAccountForRegister 一致):
  6. /// 业务成功 code=0 表示可申请注册;占用由非 0 与异常文案体现;掩码「用户名或密码错误」放行可继续注册。
  7. enum CheckAccountForRegisterResult {
  8. /// 账号已占用(如接口返回「已注册」等文案)
  9. alreadyRegistered,
  10. /// 可申请注册(成功或掩码「用户名或密码错误」)
  11. available,
  12. }
  13. /// 登录响应数据
  14. class LoginResult {
  15. final String token;
  16. final String? uid;
  17. final String? email;
  18. const LoginResult({required this.token, this.uid, this.email});
  19. factory LoginResult.fromJson(Map<String, dynamic> json) {
  20. return LoginResult(
  21. token: json['token'] as String? ?? '',
  22. uid: json['id']?.toString(),
  23. email: json['email'] as String?,
  24. );
  25. }
  26. }
  27. /// 认证 API 服务(无状态,只封装 HTTP 调用)
  28. ///
  29. /// 所有接口使用 form-urlencoded 格式提交
  30. class AuthService {
  31. const AuthService(this._dio);
  32. final Dio _dio;
  33. /// form-urlencoded 请求选项
  34. static final _formOptions = Options(
  35. contentType: 'application/x-www-form-urlencoded',
  36. );
  37. /// 设备类型:1=iOS,2=Android,5=PC/其他
  38. static int get _deviceType {
  39. if (Platform.isIOS) return 1;
  40. if (Platform.isAndroid) return 2;
  41. return 5;
  42. }
  43. /// 与 Web 一致的掩码文案:账号不存在时接口不落真实原因
  44. static bool isRegisterAvailabilityMaskMessage(String message) {
  45. return message.contains('用户名或密码错误');
  46. }
  47. /// 与 Web `checkAccountForRegister` 一致:接口用业务异常文案表示「已占用」
  48. static bool isAccountAlreadyRegisteredHint(String message) {
  49. if (message.contains('已注册')) {
  50. return true;
  51. }
  52. if (message.contains('已存在')) {
  53. return true;
  54. }
  55. if (message.contains('已被使用')) {
  56. return true;
  57. }
  58. return RegExp(
  59. r'already\s+(exists|registered)',
  60. caseSensitive: false,
  61. ).hasMatch(message);
  62. }
  63. // ── 登录发码前校验密码(与 Web `/uc/check-password` 一致)────────
  64. Future<void> checkLoginPassword({
  65. required String username,
  66. required String password,
  67. }) async {
  68. await _dio.post<dynamic>(
  69. 'uc/check-password',
  70. data: {'username': username.trim(), 'password': password},
  71. options: _formOptions,
  72. );
  73. }
  74. // ── 注册发码前校验账号是否已存在(与 Web `/uc/check-account` 一致)──
  75. Future<CheckAccountForRegisterResult> checkAccountForRegister(
  76. String username,
  77. ) async {
  78. try {
  79. await _dio.post<dynamic>(
  80. 'uc/check-account',
  81. data: {'username': username.trim()},
  82. options: _formOptions,
  83. );
  84. return CheckAccountForRegisterResult.available;
  85. } on DioException catch (e) {
  86. final err = e.error;
  87. if (err is ApiException) {
  88. final msg = err.message;
  89. if (isAccountAlreadyRegisteredHint(msg)) {
  90. return CheckAccountForRegisterResult.alreadyRegistered;
  91. }
  92. if (isRegisterAvailabilityMaskMessage(msg)) {
  93. return CheckAccountForRegisterResult.available;
  94. }
  95. }
  96. rethrow;
  97. }
  98. }
  99. // ── 发送登录验证码 ──────────────────────────────────────
  100. Future<void> sendLoginEmailCode(String email) async {
  101. await _dio.post<dynamic>(
  102. 'uc/login/email/code',
  103. data: {'username': email, 'email': email},
  104. options: _formOptions,
  105. );
  106. }
  107. // ── 密码+验证码登录 ──────────────────────────────────────
  108. /// [vtype] 验证类型:2=邮箱验证码,3=谷歌验证码
  109. Future<LoginResult> loginWithPassword({
  110. required String email,
  111. required String password,
  112. required String code,
  113. String vtype = '2',
  114. }) async {
  115. final deviceInfo = await DeviceFingerprintService.getDeviceInfo();
  116. final response = await _dio.post<Map<String, dynamic>>(
  117. 'uc/login',
  118. data: {
  119. 'username': email,
  120. 'password': password,
  121. 'country': '',
  122. 'vtype': vtype,
  123. 'vcode': code,
  124. 'deviceType': _deviceType,
  125. 'deviceFingerprint': deviceInfo.fingerprint,
  126. 'deviceModel': deviceInfo.model,
  127. 'osVersion': deviceInfo.osVersion,
  128. },
  129. options: _formOptions,
  130. );
  131. final data = (response.data?['data'] as Map<String, dynamic>?) ?? {};
  132. return LoginResult.fromJson(data);
  133. }
  134. // ── 发送注册验证码 ──────────────────────────────────────
  135. Future<void> sendRegisterEmailCode(String email) async {
  136. await _dio.post<dynamic>(
  137. 'uc/reg/email/code',
  138. data: {'username': email, 'email': email},
  139. options: _formOptions,
  140. );
  141. }
  142. // ── 邮箱注册 ────────────────────────────────────────────
  143. Future<void> registerWithEmail({
  144. required String email,
  145. required String password,
  146. required String code,
  147. String? inviteCode,
  148. String country = '不丹',
  149. }) async {
  150. await _dio.post<Map<String, dynamic>>(
  151. 'uc/register/email',
  152. data: {
  153. 'email': email,
  154. 'username': email,
  155. 'password': password,
  156. 'code': code,
  157. 'country': country,
  158. 'promotion': inviteCode ?? '',
  159. 'deviceType': _deviceType,
  160. },
  161. options: _formOptions,
  162. );
  163. }
  164. // ── 发送重置密码验证码 ──────────────────────────────────
  165. Future<void> sendResetEmailCode(String email) async {
  166. await _dio.post<dynamic>(
  167. 'uc/reset/email/code',
  168. data: {'username': email, 'account': email},
  169. options: _formOptions,
  170. );
  171. }
  172. // ── 重置密码 ────────────────────────────────────────────
  173. Future<void> resetPassword({
  174. required String email,
  175. required String password,
  176. required String code,
  177. }) async {
  178. await _dio.post<dynamic>(
  179. 'uc/reset/login/password',
  180. data: {'email': email, 'password': password, 'code': code, 'account': email, 'mode': 1},
  181. options: _formOptions,
  182. );
  183. }
  184. // ── 退出登录 ────────────────────────────────────────────
  185. Future<void> logout() async {
  186. await _dio.post<dynamic>('uc/loginout');
  187. }
  188. // ── 获取用户信息 ────────────────────────────────────────
  189. Future<Map<String, dynamic>> getMyInfo() async {
  190. final response =
  191. await _dio.post<Map<String, dynamic>>('uc/member/my-info');
  192. return (response.data?['data'] as Map<String, dynamic>?) ?? {};
  193. }
  194. // ── 谷歌验证器绑定流程 ──────────────────────────────────
  195. /// 获取谷歌验证器秘钥
  196. /// 返回 { link: "otpauth://...", secret: "P12EB42NCCSE8VTX" }
  197. Future<Map<String, dynamic>> getGoogleSecret() async {
  198. final response = await _dio.get<Map<String, dynamic>>(
  199. 'uc/google/sendgoogle',
  200. );
  201. return (response.data?['data'] as Map<String, dynamic>?) ?? {};
  202. }
  203. /// 发送谷歌验证器绑定的邮箱验证码
  204. Future<void> sendGoogleAuthEmailCode() async {
  205. await _dio.post<dynamic>(
  206. 'uc/googleAuth/email/code',
  207. options: _formOptions,
  208. );
  209. }
  210. /// 绑定谷歌验证器
  211. /// [codes] Google Authenticator 6位动态码
  212. /// [vcode] 邮箱收到的6位验证码
  213. /// [secret] 第一步获取的密钥
  214. /// [vtype] 验证方式 "2"=邮箱
  215. Future<void> bindGoogleAuth({
  216. required String codes,
  217. required String vcode,
  218. required String secret,
  219. String vtype = '2',
  220. }) async {
  221. await _dio.post<dynamic>(
  222. 'uc/google/googleAuthWithSms',
  223. data: {
  224. 'codes': codes,
  225. 'vtype': vtype,
  226. 'vcode': vcode,
  227. 'secret': secret,
  228. },
  229. options: _formOptions,
  230. );
  231. }
  232. // ── 资金密码 ──────────────────────────────────────────────
  233. /// 首次设置资金密码
  234. Future<void> setFundPassword(String password) async {
  235. await _dio.post<dynamic>(
  236. 'uc/approve/transaction/password',
  237. data: {'jyPassword': password},
  238. options: _formOptions,
  239. );
  240. }
  241. /// 发送资金密码重置邮箱验证码
  242. Future<void> sendFundPasswordEmailCode() async {
  243. await _dio.post<dynamic>(
  244. 'uc/transaction/email/code',
  245. options: _formOptions,
  246. );
  247. }
  248. /// 重置资金密码
  249. /// 发送修改登录密码邮箱验证码 — POST uc/updatePassword/email/code
  250. Future<void> sendChangePasswordEmailCode() async {
  251. await _dio.post<dynamic>(
  252. 'uc/updatePassword/email/code',
  253. options: _formOptions,
  254. );
  255. }
  256. /// 修改登录密码 — POST uc/approve/update/password
  257. Future<void> changeLoginPassword({
  258. required String oldPassword,
  259. required String newPassword,
  260. required String vcode,
  261. }) async {
  262. await _dio.post<dynamic>(
  263. 'uc/approve/update/password',
  264. data: {
  265. 'oldPassword': oldPassword,
  266. 'newPassword': newPassword,
  267. 'vtype': '2',
  268. 'vcode': vcode,
  269. },
  270. options: _formOptions,
  271. );
  272. }
  273. Future<void> resetFundPassword({
  274. required String newPassword,
  275. required String vcode,
  276. String vtype = '2',
  277. }) async {
  278. await _dio.post<dynamic>(
  279. 'uc/approve/reset/transaction/password',
  280. data: {
  281. 'newPassword': newPassword,
  282. 'vtype': vtype,
  283. 'vcode': vcode,
  284. },
  285. options: _formOptions,
  286. );
  287. }
  288. }