cached_request.dart 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:flutter/material.dart';
  4. import 'package:http/http.dart' as http;
  5. import 'package:puzzleweave/models/api_helper.dart';
  6. import 'package:puzzleweave/utils/utils.dart';
  7. import 'package:logging/logging.dart';
  8. final Logger _log = Logger('cached_request.dart');
  9. typedef TransformFunction = Future<dynamic> Function(dynamic json);
  10. class CachedRequest with WidgetsBindingObserver {
  11. /// ✅ 优化:网络状态跟踪
  12. bool _hasRecentSuccessfulFetch = false;
  13. bool get hasRecentSuccessfulFetch => _hasRecentSuccessfulFetch;
  14. /// ✅ 优化:防抖机制
  15. Timer? _refreshDebouncer;
  16. static const _refreshDebounceDelay = Duration(seconds: 2);
  17. /// ✅ 优化:重试机制
  18. int _retryCount = 0;
  19. static const _maxRetries = 3;
  20. static const _retryDelay = Duration(seconds: 5);
  21. static final Map<String, CachedRequest> _cache = {};
  22. final String url;
  23. TransformFunction? transformFunction;
  24. final StreamController _streamController = StreamController.broadcast();
  25. dynamic _transformed;
  26. dynamic get cachedData => _transformed;
  27. /// ✅ 优化:请求状态跟踪
  28. bool _isLoading = false;
  29. bool get isLoading => _isLoading;
  30. CachedRequest._internal(this.url, this.transformFunction) {
  31. _log.info('New cached request: $url');
  32. WidgetsBinding.instance.addObserver(this);
  33. _init();
  34. }
  35. factory CachedRequest.fromUrl(String url, {TransformFunction? transformFunction}) {
  36. if (!_cache.containsKey(url)) {
  37. _cache[url] = CachedRequest._internal(url, transformFunction);
  38. }
  39. return _cache[url]!;
  40. }
  41. @override
  42. void didChangeAppLifecycleState(AppLifecycleState state) {
  43. if (state == AppLifecycleState.resumed) {
  44. _log.info('App Resumed from background. Scheduling refresh for $url');
  45. /// ✅ 优化:使用防抖刷新而不是立即刷新
  46. _debouncedRefresh();
  47. }
  48. }
  49. _init() {
  50. _cacheLoad();
  51. _remoteLoad();
  52. _streamController.onListen = () {
  53. _log.info('Stream listener added for $url. Current data available? ${_transformed != null}');
  54. if (_transformed != null) {
  55. Future.microtask(() => _streamController.add(_transformed));
  56. }
  57. };
  58. }
  59. /// ✅ 优化:防抖刷新
  60. void _debouncedRefresh() {
  61. _refreshDebouncer?.cancel();
  62. _refreshDebouncer = Timer(_refreshDebounceDelay, () {
  63. if (!_isLoading) {
  64. refresh();
  65. }
  66. });
  67. }
  68. Future<void> refresh() async {
  69. /// ✅ 优化:防止重复请求
  70. if (_isLoading) {
  71. _log.info('Refresh already in progress for $url, skipping');
  72. return;
  73. }
  74. _retryCount = 0;
  75. await _remoteLoad();
  76. }
  77. Future<void> reload() async {
  78. await _cacheLoad();
  79. }
  80. /// ✅ 优化:带重试机制的远程加载
  81. _remoteLoad() async {
  82. if (_isLoading) return;
  83. _isLoading = true;
  84. try {
  85. _log.info('Starting remote load for $url (attempt ${_retryCount + 1})');
  86. /// ✅ 优化:添加超时控制
  87. final response = await http.get(Uri.parse(url)).timeout(
  88. const Duration(seconds: 30),
  89. onTimeout: () => throw TimeoutException('Request timeout for $url', const Duration(seconds: 30)),
  90. );
  91. if (response.statusCode != 200) {
  92. _hasRecentSuccessfulFetch = false;
  93. throw Exception('Invalid status code: ${response.statusCode} when fetching: $url');
  94. }
  95. _hasRecentSuccessfulFetch = true;
  96. _retryCount = 0; // 重置重试计数
  97. _log.info('${response.statusCode}, $url, Network Success: true');
  98. final data = jsonDecode(response.body);
  99. await _emit(data);
  100. await saveString(cachePath, response.body);
  101. } catch (error) {
  102. _hasRecentSuccessfulFetch = false;
  103. _log.severe('Remote load failed for $url (attempt ${_retryCount + 1}): $error');
  104. /// ✅ 优化:智能重试机制
  105. if (_retryCount < _maxRetries && _shouldRetry(error)) {
  106. _retryCount++;
  107. _log.info('Scheduling retry ${_retryCount}/$_maxRetries for $url in ${_retryDelay.inSeconds}s');
  108. Timer(_retryDelay, () {
  109. if (!_isLoading) { // 确保没有其他请求在进行
  110. _remoteLoad();
  111. }
  112. });
  113. } else {
  114. _streamController.addError(error);
  115. }
  116. } finally {
  117. _isLoading = false;
  118. }
  119. }
  120. /// ✅ 优化:判断是否应该重试
  121. bool _shouldRetry(dynamic error) {
  122. if (error is TimeoutException) return true;
  123. if (error is Exception) {
  124. final message = error.toString().toLowerCase();
  125. // 网络相关错误可以重试
  126. if (message.contains('timeout') ||
  127. message.contains('connection') ||
  128. message.contains('network') ||
  129. message.contains('socket')) {
  130. return true;
  131. }
  132. }
  133. return false;
  134. }
  135. /// ✅ 优化:异步数据发射
  136. Future<void> _emit(dynamic data) async {
  137. _log.info('Emitting data for $url');
  138. try {
  139. if (transformFunction != null) {
  140. data = await transformFunction?.call(data);
  141. }
  142. if (_streamController.hasListener) {
  143. _streamController.add(data);
  144. }
  145. _transformed = data;
  146. } catch (error) {
  147. _streamController.addError(error);
  148. _log.severe('Data transformation or emission failed: $error');
  149. }
  150. }
  151. /// ✅ 优化:缓存加载错误处理
  152. _cacheLoad() async {
  153. try {
  154. final file = await localFile(cachePath);
  155. if (await file.exists()) {
  156. _log.info('Loading from cache: $cachePath');
  157. final data = await loadJson(cachePath);
  158. await _emit(data);
  159. return;
  160. }
  161. /// ✅ 优化:内置资源加载
  162. if (url == ApiHelper.latestUri) {
  163. _log.info('Loading builtin latest asset data...');
  164. final data = await loadJSONFromAsset('assets/builtin/latest.json');
  165. await _emit(data);
  166. } else if (url == ApiHelper.collectionUri) {
  167. _log.info('Loading builtin collection asset data...');
  168. final data = await loadJSONFromAsset('assets/builtin/collection.json');
  169. await _emit(data);
  170. }
  171. } catch (error) {
  172. _log.warning('Cache load failed for $url: $error');
  173. // 缓存加载失败不应该阻止远程加载,只记录警告
  174. }
  175. }
  176. /// ✅ 优化:清理资源
  177. void dispose() {
  178. _refreshDebouncer?.cancel();
  179. WidgetsBinding.instance.removeObserver(this);
  180. _streamController.close();
  181. }
  182. String get hash => md5Hash(url);
  183. String get cachePath => 'api_cache/$hash';
  184. Stream get stream => _streamController.stream;
  185. @override
  186. String toString() => 'CachedRequest(url=$url, loading=$_isLoading, hasRecentFetch=$_hasRecentSuccessfulFetch)';
  187. }