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

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