Flutter实现不依赖设备时间的倒计时及用户一周限制功能
实现Flutter应用的防篡改一周访问限制倒计时
核心思路是依赖网络/服务器时间作为基准,彻底规避设备时间修改的影响,同时通过本地持久化存储保存限制起始信息,确保应用关闭后倒计时继续生效。以下是具体实现方案:
关键方案拆解
- 拒绝依赖本地时间:用NTP网络时间或服务器接口返回的UTC时间作为时间基准,完全不受设备系统时间篡改影响。
- 本地持久化存储:将限制起始时间(UTC时间戳)存在本地,应用重启后可读取并继续计算剩余时间。
- 实时计算剩余时长:每次计算时用当前网络时间减去起始时间,得到已流逝时长,再用总限制时长(一周)减去该值,得到剩余时间。
具体代码实现
1. 添加依赖
在pubspec.yaml中引入所需包:
dependencies: flutter: sdk: flutter shared_preferences: ^2.2.2 # 本地持久化存储 ntp: ^2.0.0 # 获取NTP网络时间
2. 封装限制管理工具类
import 'package:shared_preferences/shared_preferences.dart'; import 'package:ntp/ntp.dart'; import 'package:flutter/material.dart'; class AccessRestrictionManager { // 一周总限制时长(秒) static const int _totalRestrictionSecs = 7 * 24 * 3600; static const String _startTimeStorageKey = 'restriction_start_utc'; // 初始化限制:从服务器获取起始UTC时间戳并存储 static Future<void> initUserRestriction(int serverStartTimeUtc) async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt(_startTimeStorageKey, serverStartTimeUtc); } // 获取剩余限制时长(秒),返回null表示未设置或已过期 static Future<int?> getRemainingSeconds() async { final prefs = await SharedPreferences.getInstance(); final startTimeUtc = prefs.getInt(_startTimeStorageKey); if (startTimeUtc == null) return null; // 获取NTP网络UTC时间(替代服务器接口也可) final currentUtcTime = await NTP.now(); final elapsedSecs = currentUtcTime.difference( DateTime.fromMillisecondsSinceEpoch(startTimeUtc) ).inSeconds; // 限制已过期,清除本地存储 if (elapsedSecs >= _totalRestrictionSecs) { await prefs.remove(_startTimeStorageKey); return 0; } return _totalRestrictionSecs - elapsedSecs; } // 将剩余秒数格式化为"X天X小时" static String formatRemainingTime(int seconds) { final days = seconds ~/ (24 * 3600); final remainingSecs = seconds % (24 * 3600); final hours = remainingSecs ~/ 3600; return '$days天 $hours小时'; } }
3. 倒计时UI实现
class RestrictionPage extends StatefulWidget { const RestrictionPage({super.key}); @override State<RestrictionPage> createState() => _RestrictionPageState(); } class _RestrictionPageState extends State<RestrictionPage> { String _displayText = '加载中...'; Timer? _updateTimer; @override void initState() { super.initState(); _refreshRemainingTime(); // 每秒刷新一次显示 _updateTimer = Timer.periodic( const Duration(seconds: 1), (_) => _refreshRemainingTime() ); } Future<void> _refreshRemainingTime() async { final remainingSecs = await AccessRestrictionManager.getRemainingSeconds(); if (remainingSecs == null || remainingSecs <= 0) { setState(() => _displayText = '访问限制已解除'); _updateTimer?.cancel(); return; } setState(() { _displayText = '剩余访问时间:${AccessRestrictionManager.formatRemainingTime(remainingSecs)}'; }); } @override void dispose() { _updateTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('访问限制')), body: Center( child: Text( _displayText, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500), textAlign: TextAlign.center, ), ), ); } }
注意事项
- 初始化时机:当用户触发访问限制时,必须从服务器获取当前UTC时间戳,调用
initUserRestriction方法存储到本地。 - 网络异常处理:如果NTP获取时间失败,可以 fallback 到你的后端接口获取服务器时间,避免断网时无法计算。
- 过期自动清理:当剩余时间为0时,工具类会自动清除本地存储的起始时间,下次打开应用不会再显示限制。
内容的提问来源于stack exchange,提问作者Owais Fazal
相关产品推荐
相关产品推荐

