feat(iot): B11 iot_device.subsystem_id + 设备归属绑定 API(一期允许 NULL)

- 新增 sql/iot/V2.0.3__iot_device_add_subsystem.sql(ALTER + idx_subsystem)
- 新增 sql/iot/V2.1.0__iot_device_subsystem_not_null.sql(二期预留,带 "勿执行" 注释)
- IotDeviceDO 加 subsystemId(一期可 NULL,二期改 NOT NULL)
- IotDeviceService 加 bindDeviceToSubsystem / batchBind / unbind / selectCountBySubsystemId
- IotDeviceServiceImpl.createDevice 强校验 subsystemId + 同租户 + Redis HINCRBY +1
- 绑定变更按 TransactionSynchronizationManager afterCommit 同步 Redis(-1 / +1,避免脏状态)
- IotDeviceMapper 加 selectCountBySubsystemId / updateSubsystemId 等
- IotSubsystemServiceImpl 加 incrementDeviceCount/decrementDeviceCount;deleteSubsystem 改用 DB 计数兜底(更可靠)
- IotDeviceController 加 PUT /bindSubsystem + /batchBindSubsystem(@PreAuthorize iot:device:update)
- IotDevicePageReqVO 加 subsystemId 过滤参数(null 可走 IS NULL 查未归属)
- api ErrorCodeConstants 加 DEVICE_SUBSYSTEM_REQUIRED / DEVICE_SUBSYSTEM_CROSS_TENANT(1_050_003_009/010)
- 测试:IotDeviceServiceImplTest 8/8 + B10 IotSubsystemServiceImplTest 补 mock deviceMapper 后 8/8 全绿
- Known Pitfalls 落地:
  ⚠️ 评审 A2:一期允许 NULL,V2.1.0 预留二期 NOT NULL
  ⚠️ Redis 计数:事务提交后同步(TransactionSynchronizationManager.afterCommit)
  ⚠️ 跨租户:校验 subsystem 属于当前租户,不然抛 DEVICE_SUBSYSTEM_CROSS_TENANT
  ⚠️ 索引 idx_subsystem (tenant_id, subsystem_id, deleted) 最左匹配;IS NULL 查询走全表扫,文档已提示

Co-Authored-By: Claude Sonnet (B11 subagent) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context, orchestrator) <noreply@anthropic.com>
This commit is contained in:
lzh
2026-04-24 00:03:57 +08:00
parent ae74b4752a
commit 1f87d599c0
15 changed files with 622 additions and 16 deletions

View File

@@ -33,6 +33,9 @@ public interface ErrorCodeConstants {
ErrorCode DEVICE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_003_006, "导入设备数据不能为空!");
ErrorCode DEVICE_DOWNSTREAM_FAILED_SERVER_ID_NULL = new ErrorCode(1_050_003_007, "下行设备消息失败,原因:设备未连接网关");
ErrorCode DEVICE_SERIAL_NUMBER_EXISTS = new ErrorCode(1_050_003_008, "设备序列号已存在,序列号必须全局唯一");
// B11子系统绑定相关
ErrorCode DEVICE_SUBSYSTEM_REQUIRED = new ErrorCode(1_050_003_009, "新建设备必须指定所属子系统");
ErrorCode DEVICE_SUBSYSTEM_CROSS_TENANT = new ErrorCode(1_050_003_010, "不允许绑定跨租户的子系统");
// ========== 产品分类 1-050-004-000 ==========
ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_004_000, "产品分类不存在");