account_repository.dart 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import 'dart:async';
  2. import 'package:get/get.dart';
  3. import 'package:injectable/injectable.dart';
  4. import 'package:location/base/app_base_request.dart';
  5. import 'package:location/data/api/atmob_api.dart';
  6. import 'package:location/data/api/request/login_request.dart';
  7. import 'package:location/data/api/request/send_code_request.dart';
  8. import 'package:location/data/bean/location_info.dart';
  9. import 'package:location/data/bean/member_status_info.dart';
  10. import 'package:location/data/bean/user_info.dart';
  11. import 'package:location/data/consts/constants.dart';
  12. import 'package:location/data/consts/error_code.dart';
  13. import 'package:location/data/repositories/message_repository.dart';
  14. import 'package:location/socket/atmob_location_client.dart';
  15. import 'package:location/data/repositories/friends_repository.dart';
  16. import 'package:location/di/get_it.dart';
  17. import 'package:location/resource/string.gen.dart';
  18. import 'package:location/utils/async_util.dart';
  19. import 'package:location/utils/atmob_log.dart';
  20. import 'package:location/utils/http_handler.dart';
  21. import 'package:location/utils/mmkv_util.dart';
  22. import '../../sdk/map/map_helper.dart';
  23. import '../api/response/login_response.dart';
  24. @lazySingleton
  25. class AccountRepository {
  26. final AtmobApi atmobApi;
  27. final String tag = "AccountRepository";
  28. static final String keyAccountLoginPhoneNum = 'key_account_login_phone_num';
  29. static final String keyAccountLoginToken = 'key_account_login_token';
  30. static final String keyAccountLoginUserId = 'key_account_login_user_id';
  31. RxnString loginPhoneNum = RxnString();
  32. RxBool isLogin = RxBool(false);
  33. Rxn<MemberStatusInfo> memberStatusInfo = Rxn<MemberStatusInfo>();
  34. int? _lastRequestCodeTime;
  35. int _errorCodeTimes = 0;
  36. Timer? refreshMemberHandler;
  37. CancelableFuture? memberStatusFuture;
  38. static String? token = KVUtil.getString(keyAccountLoginToken, null);
  39. late final FriendsRepository friendsRepository;
  40. late final MessageRepository messageRepository;
  41. final Rx<UserInfo> mineUserInfo = Rx<UserInfo>(UserInfo(
  42. id: Constants.mineLocationId,
  43. phoneNumber: StringName.locationMine,
  44. isMine: true));
  45. AccountRepository(this.atmobApi) {
  46. AtmobLog.d(tag, '$tag....init');
  47. friendsRepository = FriendsRepository.getInstance();
  48. messageRepository = MessageRepository.getInstance();
  49. isLogin.bindStream(
  50. loginPhoneNum.map((value) {
  51. return value?.isNotEmpty == true;
  52. }),
  53. );
  54. loginPhoneNum.value = KVUtil.getString(keyAccountLoginPhoneNum, null);
  55. refreshMemberStatus();
  56. MapHelper.addLocationListener((location) {
  57. mineUserInfo.value.lastLocation.value =
  58. LocationInfo.fromMapLocation(location);
  59. });
  60. }
  61. Future<void> loginSendCode(String phoneNum) {
  62. final currentTime = DateTime.now().millisecondsSinceEpoch;
  63. // 检查是否在 60 秒内重复请求
  64. if (currentTime - (_lastRequestCodeTime ?? 0) < 60 * 1000) {
  65. throw RequestCodeTooOftenException();
  66. }
  67. return atmobApi
  68. .loginSendCode(SendCodeRequest(phoneNum))
  69. .then(HttpHandler.handle(true))
  70. .then((value) {
  71. _lastRequestCodeTime = currentTime;
  72. _errorCodeTimes = 0;
  73. });
  74. }
  75. Future<LoginResponse> loginUserLogin(
  76. String phoneNum, String verificationCode) {
  77. if (_errorCodeTimes >= 5) {
  78. throw LoginTooOftenException();
  79. }
  80. return atmobApi
  81. .loginUserLogin(LoginRequest(phoneNum, verificationCode))
  82. .then(HttpHandler.handle(true))
  83. .then((response) {
  84. _errorCodeTimes = 0;
  85. onLoginSuccess(phoneNum, response.authToken);
  86. return response;
  87. }).catchError((error) {
  88. if (error is ServerErrorException &&
  89. error.code == ErrorCode.verificationCodeError) {
  90. _errorCodeTimes++;
  91. }
  92. throw error;
  93. });
  94. }
  95. void onLoginSuccess(String phoneNum, String authToken) {
  96. AccountRepository.token = authToken;
  97. loginPhoneNum.value = phoneNum;
  98. KVUtil.putString(keyAccountLoginPhoneNum, phoneNum);
  99. KVUtil.putString(keyAccountLoginToken, authToken);
  100. AtmobLocationClient.connectWebSocket();
  101. refreshMemberStatus();
  102. friendsRepository.refreshFriends();
  103. messageRepository.refreshFriendRequest();
  104. }
  105. void logout() {
  106. token = null;
  107. refreshMemberHandler?.cancel();
  108. AtmobLocationClient.disConnectWebSocket();
  109. KVUtil.putString(keyAccountLoginPhoneNum, null);
  110. KVUtil.putString(keyAccountLoginToken, null);
  111. KVUtil.putString(keyAccountLoginUserId, null);
  112. loginPhoneNum.value = null;
  113. memberStatusInfo.value = null;
  114. friendsRepository.clearFriends();
  115. messageRepository.clearMessage();
  116. }
  117. void refreshMemberStatus() {
  118. memberStatusFuture?.cancel();
  119. memberStatusFuture = AsyncUtil.retryWithExponentialBackoff(
  120. () => getMemberStatus(), 10, predicate: (error) {
  121. if (error is ServerErrorException) {
  122. return error.code != ErrorCode.noLoginError;
  123. }
  124. return true;
  125. });
  126. memberStatusFuture?.then((data) {
  127. AtmobLog.d(tag, "getMemberStatus success: ${memberStatusInfo.value}");
  128. }).catchError((error) {
  129. AtmobLog.e(tag, "getMemberStatus error: $error");
  130. });
  131. }
  132. Future<MemberStatusInfo?> getMemberStatus() {
  133. return atmobApi
  134. .getMemberStatus(AppBaseRequest())
  135. .then(HttpHandler.handle(true))
  136. .then((response) {
  137. refreshMemberHandler?.cancel();
  138. if (response != null) {
  139. KVUtil.putString(keyAccountLoginUserId, response.userId);
  140. if (!response.permanent && !response.expired) {
  141. refreshMemberHandler = Timer(
  142. Duration(
  143. milliseconds:
  144. response.endTimestamp - response.serverTimestamp),
  145. () => refreshMemberStatus());
  146. }
  147. }
  148. return response;
  149. }).then((response) {
  150. if (response == null) {
  151. return null;
  152. }
  153. MemberStatusInfo memberStatusInfo = MemberStatusInfo(
  154. level: response.level,
  155. endTimestamp: response.endTimestamp,
  156. expired: response.expired,
  157. permanent: response.permanent);
  158. this.memberStatusInfo.value = memberStatusInfo;
  159. return memberStatusInfo;
  160. });
  161. }
  162. static AccountRepository getInstance() {
  163. return getIt.get<AccountRepository>();
  164. }
  165. bool memberIsExpired() {
  166. return memberStatusInfo.value == null ||
  167. memberStatusInfo.value?.expired == true;
  168. }
  169. }
  170. class RequestCodeTooOftenException implements Exception {
  171. final String message;
  172. /// 可选的构造函数,支持自定义错误信息
  173. RequestCodeTooOftenException([this.message = '请求验证码过于频繁']);
  174. @override
  175. String toString() => message;
  176. }
  177. class LoginTooOftenException implements Exception {
  178. final String message;
  179. /// 可选的构造函数,支持自定义错误信息
  180. LoginTooOftenException([this.message = '登录过于频繁']);
  181. @override
  182. String toString() => message;
  183. }