You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
85 lines
4.0 KiB
85 lines
4.0 KiB
import 'dart:async';
|
|
|
|
import '../core/utils/logger.dart';
|
|
import '../data/models/user_Info.dart';
|
|
import '../data/services/network/api.dart';
|
|
|
|
/// 「这台设备现在属于哪个账号」的登记。
|
|
///
|
|
/// ⚠️ 与**确权**(这台硬件是不是正品)是两件完全不同的事,必须分开:
|
|
/// - 确权是**设备属性**,可以缓存在本地白名单里,离线也认,一台正品永远是正品;
|
|
/// - 登记是**账号属性**,同一台设备换个账号登录就得重新报一次。
|
|
///
|
|
/// 把两者混在一条路径上(白名单命中就整段 return)会导致一个不报错的坑:
|
|
/// A 账号绑过的设备,换 B 账号登录后连上时 `user_binddevice` **一次都不打**,
|
|
/// 服务端仍认为它属于 A —— B 的「设备管理」页永远空着,而连接、录音、OTA 全都正常,
|
|
/// 看不出任何异常。设备在同事之间来回借用、或同一台手机切账号测试时必然踩到。
|
|
///
|
|
/// 服务端本来就支持接管:`api_binddevice.go` 里
|
|
/// `model.Status != 0 && model.Uid != session.GetUserId()` 那一支会删掉原账号的
|
|
/// userdevice 行、把绑定统计冲正 -1,再把设备行改挂到新账号名下
|
|
/// (「授权码已被使用」的拒绝早就注释掉了)。**客户端只要把「连上了」这件事告诉它**。
|
|
class DeviceBindRegistry {
|
|
DeviceBindRegistry._();
|
|
|
|
static const String _tag = 'DeviceBindRegistry';
|
|
|
|
/// 本进程内已向服务端报告过的 `uid|MAC`,避免同一次登录里反复打接口。
|
|
///
|
|
/// ⚠️ 键**必须带 uid**:只按 MAC 记会把「换账号」这件事连同一起吞掉,
|
|
/// 那就等于又回到了白名单短路的老问题上。
|
|
static final Set<String> _reported = <String>{};
|
|
|
|
/// 登出时清掉:换账号后同一台设备要重新登记到新账号名下。
|
|
static void reset() => _reported.clear();
|
|
|
|
/// 确保**当前账号**在服务端登记了这台设备。
|
|
///
|
|
/// 调用方不必 await —— 连接流程不该等这一次网络往返,登记失败也不影响设备可用。
|
|
/// 未登录时直接返回:那条路由 [DeviceAuth.bindLoginRetry] 在登录完成后补。
|
|
static Future<void> ensureBound({
|
|
required String mac,
|
|
required String name,
|
|
}) async {
|
|
final m = mac.trim().toUpperCase();
|
|
if (m.isEmpty) return;
|
|
if (!User.isLoggedIn()) return;
|
|
|
|
final uid = User.instance.uid;
|
|
// 服务端已经知道这台属于当前账号 —— 省一次往返。
|
|
// 这个判据天然按账号走(devices 是当前账号的列表),不需要额外缓存。
|
|
if (User.instance.devices
|
|
.any((d) => d.devicemac.trim().toUpperCase() == m)) {
|
|
return;
|
|
}
|
|
|
|
final key = '$uid|$m';
|
|
if (_reported.contains(key)) return;
|
|
_reported.add(key); // 先占位挡住并发;失败时移除,下次连上还能重试
|
|
try {
|
|
final resp = await Api.binddevice({
|
|
'code': '', // BLE 设备没有杰理那套广播授权码,服务端按 MAC 全局反查
|
|
'devicename': name,
|
|
'devicemac': mac,
|
|
}).timeout(const Duration(seconds: 10));
|
|
final deviceJson = resp?['device'];
|
|
if (deviceJson is Map<String, dynamic>) {
|
|
User.instance.addUserDevice(UserDevice.fromJson(deviceJson));
|
|
Logger.i(_tag, '已登记到当前账号($uid): $m');
|
|
return;
|
|
}
|
|
// resp == null 是服务端**明确拒绝**(AuthInterceptor 在业务码 != 0 时
|
|
// 把 data 置 null 后 resolve,不抛)。最常见是这台 MAC 还没导入后台的
|
|
// device_mac 表,返回 {"code":2005,"msg":"设备未登记"}。
|
|
_reported.remove(key);
|
|
Logger.w(
|
|
_tag,
|
|
'服务端未返回 device(resp=$resp):这台 MAC 可能没导入 device_mac,'
|
|
'设备管理页看不到它');
|
|
} catch (e) {
|
|
// 网络问题:不记成功,下次连上重试。登记失败不影响连接与使用。
|
|
_reported.remove(key);
|
|
Logger.w(_tag, '登记失败(不影响使用): $e');
|
|
}
|
|
}
|
|
}
|
|
|