You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,
        ),
      ),
    );
  }
}

注意事项

  1. 初始化时机:当用户触发访问限制时,必须从服务器获取当前UTC时间戳,调用initUserRestriction方法存储到本地。
  2. 网络异常处理:如果NTP获取时间失败,可以 fallback 到你的后端接口获取服务器时间,避免断网时无法计算。
  3. 过期自动清理:当剩余时间为0时,工具类会自动清除本地存储的起始时间,下次打开应用不会再显示限制。

内容的提问来源于stack exchange,提问作者Owais Fazal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 17:36:30