封面

一次物联网设备开放 API 的权限模型重构

写作时间:2026-07-12 18:16:00
# Java
# Spring Boot
# OpenAPI
# 物联网
# 权限设计
# 部署

这次改造最初只是为了验证一种新设备能否接入现有平台。

设备可以连接通信服务,后台也能显示在线,但通过开放接口查询、下发和控制时,偶尔会出现“设备不存在”“签名失败”或者设备归属不匹配。

沿着开放接口、数据库、缓存、消息服务和硬件适配服务逐层排查后,我发现真正需要解决的不是某个品牌的协议兼容,而是开放 API 原有的权限模型。

旧模型的问题

旧逻辑把请求中的调用方 ID 直接作为设备归属 ID。

在设备数量少、组织层级简单时,这种做法能够工作。但系统实际存在多层组织关系:

  • 上级组织持有开放 API 身份和密钥;
  • 上级组织下面包含多个真实项目或小区;
  • 设备实际归属于具体项目;
  • 同一项目中可能同时存在内部设备和第三方接入设备;
  • 不同调用方之间必须保持设备控制隔离。

如果继续让调用身份和设备归属使用同一个值,就只能给每个项目配置独立密钥,或者把设备错误地挂在上级组织节点。

前者会制造大量调用主体,后者会破坏原有的项目、住户、门禁和数据权限关系。

把三种身份拆开

最终方案将设备相关身份拆成三层。

调用身份

上级组织的 AppID 和 Secret 只负责:

  • 请求身份认证;
  • 签名验证;
  • 回调地址配置;
  • 确定允许访问的组织范围。

下级项目不需要配置独立密钥,也不会成为新的开放 API 调用方。

实际归属

设备仍然归属于真实项目或小区。

所有内部业务继续依赖真实归属,包括:

  • 后台数据权限;
  • 门禁关系;
  • 用户授权;
  • 设备日志;
  • 访客流程;
  • 业务统计。

开放 API 的改造不能破坏这些原有关系。

开放来源

设备增加独立的“开放来源”标识,用于记录它由哪个调用方创建和管理。

开放接口控制设备时,需要同时满足:

  1. 设备的开放来源与请求调用方一致;
  2. 设备当前项目仍位于调用方的有效下级范围;
  3. 设备序列号和品牌同时匹配;
  4. 设备已经审核通过;
  5. 设备没有过期或删除;
  6. 当前调用方的开放范围仍处于启用状态。

这样,即使多个调用方的设备处于同一个项目,它们之间也不能互相查询或控制。

内部设备没有开放来源,不会被第三方接口意外接管。

新增设备的两条路径

改造后,平台存在两种不同的新增方式。

通过开放 API 新增

通过开放 API 新增时,服务端记录调用方身份,并将设备置为待审核状态。

后台操作人员需要:

  1. 将设备分配到调用方下面的真实项目;
  2. 核对设备序列号、类型和品牌;
  3. 执行审核;
  4. 门禁设备继续完成门禁业务绑定。

审核通过后,系统自动生成初始有效期。

在设备仍停留于上级组织占位节点、被分配到范围外项目或者项目已经停用时,审核必须失败。

通过后台手工新增

后台手工新增的设备默认属于内部设备。

它可以正常参与平台内部管理,但不会自动绑定某个开放 API 调用方,因此不能直接通过第三方 AppID 查询和控制。

这条边界很重要。否则后台新增一台设备后,某个第三方调用方可能因为组织范围重合而自动取得控制权。

数据迁移

为了兼容历史设备,正式数据库进行了有回滚能力的迁移:

  • 增加开放范围配置;
  • 增加设备来源字段;
  • 增加面向调用方、设备序列号、品牌和状态的联合索引;
  • 建立设备归属和业务关系快照;
  • 将历史占位设备迁入真实下级项目;
  • 为已经明确来源的设备补充开放来源;
  • 保持内部设备来源为空。

迁移完成后,继续检查:

  • 设备归属是否位于合法范围;
  • 门禁关系与设备归属是否一致;
  • 是否存在跨调用方设备;
  • 是否出现丢失、重复或无效设备;
  • 迁移前后的数据数量是否一致。

消息服务器、设备主题、访问控制规则、硬件协议和固件均未修改。

生产环境里的两个问题

数据库字符序不一致

第一版在生产环境查询组织祖先路径时,遇到了不同字符序无法比较的问题。

测试环境没有暴露这个差异,正式签名查询直接返回失败。

最终在数据访问层显式统一比较双方的字符序,并补充契约测试:先让测试稳定复现失败,再验证修复结果。

这次再次说明,依赖字符串保存组织路径时,不能假设数据库、字段和连接的默认字符序永远一致。

服务停止不代表进程已经退出

正式切换时,服务管理工具已经执行停止操作,但实际 Java 进程仍然存在。

如果这时直接覆盖并重新启动,就可能出现:

  • 端口仍被占用;
  • 两个版本同时运行;
  • 设备服务连接到旧实例;
  • 数据迁移与应用版本不一致。

最终处理方式是同时核对:

  • 服务状态;
  • Java 进程;
  • 进程命令行;
  • 工作目录;
  • HTTP 和 RPC 监听端口。

只有确认目标进程身份后,才完成旧进程退出、制品替换和新服务启动。

权限改造后的验证

发布前执行了定向单元测试、多模块构建和数据库契约检查。

正式环境继续验证了:

  • 正确调用方可以查询自己的设备;
  • 其他调用方查询同一设备会被拒绝;
  • 待审核、过期、删除和范围外设备无法控制;
  • 公网 HTTPS 接口能够正常访问;
  • 用户、照片和卡号下发链路能够执行;
  • 清空用户和远程控制请求能够送达设备服务;
  • 相同签名不能在有效窗口内重复使用;
  • 主应用、数据库、缓存和设备服务连接恢复正常;
  • 消息服务器和在线设备连接未因主后端发布而重启。

API 成功不等于现场完成

设备接口返回成功,只能说明请求通过了平台和设备服务的处理,不能直接证明物理世界中的动作已经完成。

以下操作仍需要现场确认:

  • 远程开门后,真实门体是否动作;
  • 删除照片或卡号后,旧凭证是否确实失效;
  • 人脸、卡号和二维码能否按预期通行;
  • 设备主动上报是否生成内部记录;
  • 第三方回调是否被对方系统正确接收。

第三方回调还存在一个常见细节:HTTP 状态码为 200 不一定代表业务成功。

如果平台约定返回 JSON 成功结构,而接收方只返回纯文本,平台仍可能将本次回调记录为失败。

发布后的“设备消失”

上线后还遇到一个看似与开放 API 有关的问题。

超级管理员能够看到设备,普通合作账号却看到空列表。

设备数据实际上没有丢失。原因是设备已经迁入真实下级项目,而合作账号的数据范围仍然是“仅本部门”。

此时出现了两套不同的权限结果:

  • 开放 API 已经可以动态覆盖全部有效下级项目;
  • 普通后台账号的数据范围仍然只有当前组织节点;
  • 后台设备列表按登录账号的数据范围过滤,因此返回空数据。

处理方式不是迁回设备,也不是修改消息服务,而是将对应角色的数据范围调整为“本部门及以下”,再让账号重新登录刷新缓存。

这说明系统中至少存在三套相互独立的权限:

  1. 开放 API 调用权限;
  2. 后台菜单和按钮权限;
  3. 后台业务数据范围。

只完成其中一套权限改造,不能保证其他入口自动拥有相同行为。

最后的收获

这次真正完成的不是“多兼容一种设备”,而是把调用身份、设备实际归属和开放来源从一个概念中拆开。

最值得保留的结论是:

  • 调用身份不应该同时承担设备物理归属;
  • 上级组织可以授权下级范围,但设备必须保留真实项目;
  • 开放来源必须单独记录,不能只依赖组织树判断;
  • 内部设备和开放设备必须保持明确边界;
  • 接口权限和后台数据权限需要分别验证;
  • API 返回成功不能代替物理设备验收;
  • 数据迁移必须同时准备快照、校验和回滚;
  • 超级管理员可见而普通账号不可见时,应优先排查数据范围;
  • 生产切换必须确认真实进程和端口,而不能只看服务管理状态。

当这些边界稳定以后,后续接入新设备时就不需要再设计一套特殊权限逻辑。

只要设备品牌配置、处理器和硬件服务同时可用,就可以复用同一套开放设备协议。