如何在Flutter中实现API响应月级缓存并每月初自动刷新?
实现Flutter API调用的月级缓存机制
针对你的需求,我们可以通过缓存月份标识结合SQFlite(或Hive)实现月级缓存,核心逻辑是:用YYYY-MM格式字符串标记缓存所属月份,每次请求先判断本地缓存是否属于当前月份,是则复用,否则重新请求并更新缓存。
方案一:基于SQFlite的实现
1. 调整数据模型与数据库表结构
给PrayerTimes模型添加缓存月份字段,同时更新SQFlite表结构:
class PrayerTimes { final String date; final String fajr; // 其他原有字段... final String cacheMonth; // 格式:YYYY-MM PrayerTimes({ required this.date, required this.fajr, // 其他字段参数... required this.cacheMonth, }); // 从JSON转换时自动设置当前月份为缓存标识 factory PrayerTimes.fromJson(Map<String, dynamic> json) { return PrayerTimes( date: json['date'], fajr: json['fajr'], // 其他字段赋值... cacheMonth: DateFormat('yyyy-MM').format(DateTime.now()), ); } // 转换为SQFlite存储的Map Map<String, dynamic> toMap() { return { 'date': date, 'fajr': fajr, // 其他字段... 'cache_month': cacheMonth, }; } }
更新SQFlite数据库操作类,增加缓存有效性检查方法:
class LocalDatabase { static Database? _db; static Future<Database> get db async { if (_db != null) return _db!; _db = await _initDB(); return _db!; } static Future<Database> _initDB() async { final path = await getDatabasesPath(); return openDatabase( join(path, 'prayer_times.db'), onCreate: (db, version) { return db.execute(''' CREATE TABLE prayer_times( date TEXT PRIMARY KEY, fajr TEXT, -- 其他字段... cache_month TEXT NOT NULL ) '''); }, version: 1, ); } // 插入或更新缓存(覆盖同日期数据) static Future<void> upsertPrayerTimes(PrayerTimes data) async { final db = await LocalDatabase.db; await db.insert( 'prayer_times', data.toMap(), conflictAlgorithm: ConflictAlgorithm.replace, ); } // 查询当前月份的当日缓存 static Future<PrayerTimes?> getValidCachedData(String currentDate) async { final db = await LocalDatabase.db; final currentMonth = DateFormat('yyyy-MM').format(DateTime.now()); final result = await db.query( 'prayer_times', where: 'date = ? AND cache_month = ?', whereArgs: [currentDate, currentMonth], ); return result.isNotEmpty ? PrayerTimes.fromJson(result.first) : null; } }
2. 修改API调用逻辑,加入缓存判断
重写API方法,优先读取有效缓存,无效则请求并更新:
class PrayerTimesApi { static Future<PrayerTimes> getPrayerTimes() async { final currentDate = DateFormat('EEEE dd MMMM yyyy').format(DateTime.now()); // 第一步:检查有效缓存 final cachedData = await LocalDatabase.getValidCachedData(currentDate); if (cachedData != null) { return cachedData; } // 第二步:缓存无效,请求API const url = 'example.com'; final response = await http.post( Uri.parse(url), headers: {'Content-Type': 'application/json; charset=UTF-8'}, ); if (response.statusCode == 200) { final responseData = jsonDecode(response.body); final prayerTimesList = responseData['prayerTimes'] as List; // 查找当日数据 final currentDayData = prayerTimesList.firstWhere( (item) => item['date'] == currentDate, orElse: () => throw '当前日期$currentDate的祈祷时间不可用', ); final prayerTimes = PrayerTimes.fromJson(currentDayData); await LocalDatabase.upsertPrayerTimes(prayerTimes); return prayerTimes; } else { throw '请求失败,状态码:${response.statusCode}'; } } }
方案二:基于Hive的轻量实现(推荐)
如果不需要复杂SQL查询,Hive作为NoSQL轻量存储更高效,适合直接缓存整月数据:
1. 初始化Hive与注册Adapter
void initHive() async { await Hive.initFlutter(); Hive.registerAdapter(PrayerTimesAdapter()); // 需要通过build_runner生成Adapter await Hive.openBox<List<PrayerTimes>>('prayer_times'); }
2. 实现缓存逻辑
class PrayerTimesApi { static Future<PrayerTimes> getPrayerTimes() async { final currentDate = DateFormat('EEEE dd MMMM yyyy').format(DateTime.now()); final currentMonth = DateFormat('yyyy-MM').format(DateTime.now()); final box = Hive.box<List<PrayerTimes>>('prayer_times'); // 检查当月缓存 final cachedMonthData = box.get(currentMonth); if (cachedMonthData != null) { return cachedMonthData.firstWhere( (pt) => pt.date == currentDate, orElse: () => throw '当前日期$currentDate的祈祷时间不可用', ); } // 请求API并缓存整月数据 const url = 'example.com'; final response = await http.post( Uri.parse(url), headers: {'Content-Type': 'application/json; charset=UTF-8'}, ); if (response.statusCode == 200) { final responseData = jsonDecode(response.body); final monthData = (responseData['prayerTimes'] as List) .map((json) => PrayerTimes.fromJson(json)) .toList(); await box.put(currentMonth, monthData); return monthData.firstWhere( (pt) => pt.date == currentDate, orElse: () => throw '当前日期$currentDate的祈祷时间不可用', ); } else { throw '请求失败,状态码:${response.statusCode}'; } } }
核心逻辑说明
- 自动刷新:每次请求会对比缓存月份与当前月份,月初时缓存月份为上月,自动触发API请求更新缓存。
- 缓存有效性:用
YYYY-MM作为唯一标识,确保当月内所有请求复用同一缓存。 - 异常处理:缓存不存在时才请求API,请求失败直接抛出错误(可根据需求调整为返回旧缓存)。
内容的提问来源于stack exchange,提问作者Auvee
相关产品推荐
相关产品推荐

