feat(sdk): 绑定时上报设备 log 到 update-metadata 并内聚双层绑定

- bind(userId:) 内聚为硬绑 + getVersion + 软绑 + updateMetadata 一次原子调用,
  软绑失败自动硬解绑回滚 + 清状态;集成方无需感知双层绑定
- fetchLatestFirmware 简化为对账上报 + checkUpgrade 两步,不再重复软绑
- VersionInfo 增加 log: [String: Any]?,走 JSONSerialization 解析(放弃 Codable);
  SDK 暴露只读 versionLog,断连/解绑/软绑回滚时清空
- ShipmentSnDeviceService 新增 updateMetadata(sn:data:),抽出 performPOST
  共享 HTTP 编排,新增 postAnyJSON 支持 [String: Any] body
- 老固件不返回 log → versionLog == nil → 自动跳过 updateMetadata,前向兼容

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
km2023
2026-05-18 16:25:55 +08:00
parent cdcf36a75b
commit 4bc8fdb64d
6 changed files with 163 additions and 67 deletions

View File

@@ -4,24 +4,44 @@ public struct VersionInfo: Equatable {
public let version: String
/// Int ( 7) String ( "0x07")
public let type: String
}
/// BLE `log` JSON / nil
/// SDK `update-metadata`
public let log: [String: Any]?
extension VersionInfo: Codable {
enum CodingKeys: String, CodingKey {
case version, type
public init(version: String, type: String, log: [String: Any]? = nil) {
self.version = version
self.type = type
self.log = log
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
version = try container.decode(String.self, forKey: .version)
public static func == (lhs: VersionInfo, rhs: VersionInfo) -> Bool {
guard lhs.version == rhs.version, lhs.type == rhs.type else { return false }
return NSDictionary(dictionary: lhs.log ?? [:]).isEqual(to: rhs.log ?? [:])
}
}
// type may come as Int or String from different firmware versions
if let str = try? container.decode(String.self, forKey: .type) {
type = str
} else if let num = try? container.decode(Int.self, forKey: .type) {
type = "\(num)"
extension VersionInfo {
/// BLE 0
/// type Int / String log / nil
static func parse(from data: Data) throws -> VersionInfo {
let cleaned = data.filter { $0 != 0 }
let object = try JSONSerialization.jsonObject(with: cleaned)
guard let json = object as? [String: Any] else {
throw DuooomiBleError.transferFailed("VersionInfo: top-level not an object")
}
let version = (json["version"] as? String) ?? ""
let type: String
if let s = json["type"] as? String {
type = s
} else if let n = json["type"] as? NSNumber {
type = "\(n.intValue)"
} else {
type = ""
}
let log = json["log"] as? [String: Any]
return VersionInfo(version: version, type: type, log: log)
}
}