Files
photo-live/apps/mobile/lib/core/device_id.dart
T

92 lines
3.1 KiB
Dart
Raw Normal View History

/// 设备标识(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());