80 lines
2.8 KiB
Dart
80 lines
2.8 KiB
Dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
|
|
import '../auth/session_store.dart';
|
|
import 'models.dart';
|
|
import 'order_api.dart';
|
|
|
|
/// 授权管理:本地缓存用于主界面展示到期时间;识别入口强制服务端校验。
|
|
/// 缓存按手机号隔离(键含账号),切换账号不会读到上一账号的到期时间。
|
|
class LicenseService {
|
|
static const _storage = FlutterSecureStorage();
|
|
|
|
final OrderApi orderApi;
|
|
final SessionStore sessionStore;
|
|
|
|
LicenseService({required this.orderApi, required this.sessionStore});
|
|
|
|
/// 主界面展示/启动加载:先读本账号本地缓存,有效则直接用;
|
|
/// 无效或缺失时询问服务端(网络失败按缓存兜底,未登录按过期处理)。
|
|
Future<LicenseStatus> check() async {
|
|
final phone = await sessionStore.readPhone();
|
|
if (phone == null) return const LicenseStatus(active: false);
|
|
|
|
final cached = await _readCache(phone);
|
|
final now = DateTime.now();
|
|
if (cached != null && cached.expiresAt!.isAfter(now)) return cached;
|
|
|
|
if (await sessionStore.readToken() == null) {
|
|
return const LicenseStatus(active: false);
|
|
}
|
|
try {
|
|
final remote = await orderApi.fetchLicense();
|
|
try {
|
|
if (remote.active && remote.expiresAt != null) {
|
|
await _writeCache(phone, remote.expiresAt!);
|
|
} else if (!remote.active) {
|
|
await _clearCache(phone);
|
|
}
|
|
} catch (_) {
|
|
// 本地缓存不可用不影响授权状态展示
|
|
}
|
|
return remote;
|
|
} on OrderApiException {
|
|
return cached ?? const LicenseStatus(active: false);
|
|
}
|
|
}
|
|
|
|
/// 识别入口强制校验:必须走服务端且 active 才放行,失败即抛错(不进相机)
|
|
Future<LicenseStatus> verifyServer() async {
|
|
if (await sessionStore.readToken() == null) {
|
|
throw const SessionExpiredException();
|
|
}
|
|
return orderApi.fetchLicense();
|
|
}
|
|
|
|
/// 支付成功后立即刷新(清缓存强制走服务端,避免旧授权干扰)
|
|
Future<LicenseStatus> refresh() async {
|
|
final phone = await sessionStore.readPhone();
|
|
if (phone != null) await _clearCache(phone);
|
|
return orderApi.fetchLicense();
|
|
}
|
|
|
|
Future<LicenseStatus?> _readCache(String phone) async {
|
|
try {
|
|
final raw = await _storage.read(key: _cacheKey(phone));
|
|
final t = raw == null ? null : DateTime.tryParse(raw);
|
|
if (t == null || !t.isAfter(DateTime.now())) return null;
|
|
return LicenseStatus(active: true, expiresAt: t);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Future<void> _writeCache(String phone, DateTime expiresAt) =>
|
|
_storage.write(key: _cacheKey(phone), value: expiresAt.toIso8601String());
|
|
|
|
Future<void> _clearCache(String phone) => _storage.delete(key: _cacheKey(phone));
|
|
|
|
static String _cacheKey(String phone) => 'license_expires_at:$phone';
|
|
}
|