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 _reported = {}; /// 登出时清掉:换账号后同一台设备要重新登记到新账号名下。 static void reset() => _reported.clear(); /// 确保**当前账号**在服务端登记了这台设备。 /// /// 调用方不必 await —— 连接流程不该等这一次网络往返,登记失败也不影响设备可用。 /// 未登录时直接返回:那条路由 [DeviceAuth.bindLoginRetry] 在登录完成后补。 static Future 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) { 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'); } } }