| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- import 'dart:async';
- import 'dart:convert';
- import 'package:flutter/material.dart';
- import 'package:http/http.dart' as http;
- import 'package:puzzleweave/models/api_helper.dart';
- import 'package:puzzleweave/utils/utils.dart';
- import 'package:logging/logging.dart';
- final Logger _log = Logger('cached_request.dart');
- typedef TransformFunction = Future<dynamic> Function(dynamic json);
- class CachedRequest with WidgetsBindingObserver {
- /// ✅ 优化:网络状态跟踪
- bool _hasRecentSuccessfulFetch = false;
- bool get hasRecentSuccessfulFetch => _hasRecentSuccessfulFetch;
- /// ✅ 优化:防抖机制
- Timer? _refreshDebouncer;
- static const _refreshDebounceDelay = Duration(seconds: 2);
- /// ✅ 优化:重试机制
- int _retryCount = 0;
- static const _maxRetries = 3;
- static const _retryDelay = Duration(seconds: 5);
- static final Map<String, CachedRequest> _cache = {};
- final String url;
- TransformFunction? transformFunction;
- final StreamController _streamController = StreamController.broadcast();
- dynamic _transformed;
- dynamic get cachedData => _transformed;
- /// ✅ 优化:请求状态跟踪
- bool _isLoading = false;
- bool get isLoading => _isLoading;
- CachedRequest._internal(this.url, this.transformFunction) {
- _log.info('New cached request: $url');
- WidgetsBinding.instance.addObserver(this);
- _init();
- }
- factory CachedRequest.fromUrl(String url, {TransformFunction? transformFunction}) {
- if (!_cache.containsKey(url)) {
- _cache[url] = CachedRequest._internal(url, transformFunction);
- }
- return _cache[url]!;
- }
- @override
- void didChangeAppLifecycleState(AppLifecycleState state) {
- if (state == AppLifecycleState.resumed) {
- _log.info('App Resumed from background. Scheduling refresh for $url');
- /// ✅ 优化:使用防抖刷新而不是立即刷新
- _debouncedRefresh();
- }
- }
- _init() {
- _cacheLoad();
- _remoteLoad();
- _streamController.onListen = () {
- _log.info('Stream listener added for $url. Current data available? ${_transformed != null}');
- if (_transformed != null) {
- Future.microtask(() => _streamController.add(_transformed));
- }
- };
- }
- /// ✅ 优化:防抖刷新
- void _debouncedRefresh() {
- _refreshDebouncer?.cancel();
- _refreshDebouncer = Timer(_refreshDebounceDelay, () {
- if (!_isLoading) {
- refresh();
- }
- });
- }
- Future<void> refresh() async {
- /// ✅ 优化:防止重复请求
- if (_isLoading) {
- _log.info('Refresh already in progress for $url, skipping');
- return;
- }
-
- _retryCount = 0;
- await _remoteLoad();
- }
- Future<void> reload() async {
- await _cacheLoad();
- }
- /// ✅ 优化:带重试机制的远程加载
- _remoteLoad() async {
- if (_isLoading) return;
-
- _isLoading = true;
-
- try {
- _log.info('Starting remote load for $url (attempt ${_retryCount + 1})');
-
- /// ✅ 优化:添加超时控制
- final response = await http.get(Uri.parse(url)).timeout(
- const Duration(seconds: 30),
- onTimeout: () => throw TimeoutException('Request timeout for $url', const Duration(seconds: 30)),
- );
-
- if (response.statusCode != 200) {
- _hasRecentSuccessfulFetch = false;
- throw Exception('Invalid status code: ${response.statusCode} when fetching: $url');
- }
- _hasRecentSuccessfulFetch = true;
- _retryCount = 0; // 重置重试计数
- _log.info('${response.statusCode}, $url, Network Success: true');
- final data = jsonDecode(response.body);
- await _emit(data);
- await saveString(cachePath, response.body);
-
- } catch (error) {
- _hasRecentSuccessfulFetch = false;
- _log.severe('Remote load failed for $url (attempt ${_retryCount + 1}): $error');
-
- /// ✅ 优化:智能重试机制
- if (_retryCount < _maxRetries && _shouldRetry(error)) {
- _retryCount++;
- _log.info('Scheduling retry ${_retryCount}/$_maxRetries for $url in ${_retryDelay.inSeconds}s');
-
- Timer(_retryDelay, () {
- if (!_isLoading) { // 确保没有其他请求在进行
- _remoteLoad();
- }
- });
- } else {
- _streamController.addError(error);
- }
- } finally {
- _isLoading = false;
- }
- }
- /// ✅ 优化:判断是否应该重试
- bool _shouldRetry(dynamic error) {
- if (error is TimeoutException) return true;
- if (error is Exception) {
- final message = error.toString().toLowerCase();
- // 网络相关错误可以重试
- if (message.contains('timeout') ||
- message.contains('connection') ||
- message.contains('network') ||
- message.contains('socket')) {
- return true;
- }
- }
- return false;
- }
- /// ✅ 优化:异步数据发射
- Future<void> _emit(dynamic data) async {
- _log.info('Emitting data for $url');
- try {
- if (transformFunction != null) {
- data = await transformFunction?.call(data);
- }
- if (_streamController.hasListener) {
- _streamController.add(data);
- }
- _transformed = data;
- } catch (error) {
- _streamController.addError(error);
- _log.severe('Data transformation or emission failed: $error');
- }
- }
- /// ✅ 优化:缓存加载错误处理
- _cacheLoad() async {
- try {
- final file = await localFile(cachePath);
- if (await file.exists()) {
- _log.info('Loading from cache: $cachePath');
- final data = await loadJson(cachePath);
- await _emit(data);
- return;
- }
- /// ✅ 优化:内置资源加载
- if (url == ApiHelper.latestUri) {
- _log.info('Loading builtin latest asset data...');
- final data = await loadJSONFromAsset('assets/builtin/latest.json');
- await _emit(data);
- } else if (url == ApiHelper.collectionUri) {
- _log.info('Loading builtin collection asset data...');
- final data = await loadJSONFromAsset('assets/builtin/collection.json');
- await _emit(data);
- }
- } catch (error) {
- _log.warning('Cache load failed for $url: $error');
- // 缓存加载失败不应该阻止远程加载,只记录警告
- }
- }
- /// ✅ 优化:清理资源
- void dispose() {
- _refreshDebouncer?.cancel();
- WidgetsBinding.instance.removeObserver(this);
- _streamController.close();
- }
- String get hash => md5Hash(url);
- String get cachePath => 'api_cache/$hash';
- Stream get stream => _streamController.stream;
- @override
- String toString() => 'CachedRequest(url=$url, loading=$_isLoading, hasRecentFetch=$_hasRecentSuccessfulFetch)';
- }
|