account_repository.dart 7.3 KB

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