Files
photo-live/apps/mobile/lib/core/device_id.dart
T
2026-09-19 02:50:40 +08:00

92 lines
3.1 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// 设备标识(P3 计划 Task 6,A19 设备来源语义)。
///
/// 每安装一次生成一个随机 32 位十六进制串(16 字节 `Random.secure`),
/// 持久化后跨进程重启稳定;随 `initUpload` 上报为可选 sourceDeviceId,
/// 服务端历史(GET /uploads)据此区分上传来源设备。
///
/// 非机密(不可当作设备指纹做安全决策,服务端仅作展示归属),明文存
/// shared_preferences 足够——不占用 secure storage。卸载重装视为新设备
/// (per-install 语义,符合「客户端首次生成并持久化」的契约描述)。
library;
import 'dart:math';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// 存储抽象(测试注入内存实现;生产=shared_preferences)。
abstract class DeviceIdStore {
Future<String?> read();
Future<void> write(String value);
}
/// shared_preferences 生产实现(单键)。
class SharedPrefsDeviceIdStore implements DeviceIdStore {
const SharedPrefsDeviceIdStore();
static const String kKey = 'device.id';
@override
Future<String?> read() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(kKey);
}
@override
Future<void> write(String value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(kKey, value);
}
}
/// 设备标识:懒初始化 + 进程内缓存(并发调用共享首次生成结果)。
class DeviceId {
DeviceId({DeviceIdStore? store, Random? random})
: _store = store ?? const SharedPrefsDeviceIdStore(),
_random = random ?? Random.secure();
final DeviceIdStore _store;
final Random _random;
Future<String>? _pending;
/// 合法取值形状:32 位小写十六进制。
static final RegExp kPattern = RegExp(r'^[0-9a-f]{32}$');
/// 当前设备标识(首次调用生成并持久化;后续读缓存)。
Future<String> value() => _pending ??= _loadOrCreate();
Future<String> _loadOrCreate() async {
String? stored;
try {
stored = await _store.read();
} on Object {
// 存储读取失败按未生成处理:重新生成并尽力写回。
}
if (stored != null && kPattern.hasMatch(stored)) {
return stored;
}
final generated = _generate();
try {
await _store.write(generated);
} on Object {
// 写回失败不阻断:本次进程内仍可用(下次启动重新生成——
// sourceDeviceId 可选,服务端容忍 null/变更)。
}
return generated;
}
/// 16 随机字节 → 32 位小写十六进制。
String _generate() {
final bytes = List<int>.generate(16, (_) => _random.nextInt(256));
return hexEncode(bytes);
}
/// 字节 → 小写十六进制(独立函数,单测直接覆盖)。
static String hexEncode(List<int> bytes) =>
bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
}
/// 设备标识 Provider(全局单例语义:整个 App 共享同一 per-install 值)。
final deviceIdProvider = Provider<DeviceId>((ref) => DeviceId());