|
|
7dc00b542d
|
feat(iot): 一期 Controller 补齐 (B2/B4-6/B10/B11/B12/B13)
对照前端 feat/iot-2.0 已固化 API 契约补齐 5 组缺失端点(发现于一期 19/19
宣称完成后前端联调阶段),归属原任务卡 Controller 层返工,不占用二期
B20+ 编号。
- B2 规则链: 补 PUT /disable /deploy /debug + POST /copy?id= +
新增 GET /rule-chain/get?id= 返 GraphVO(保留 /get/{id})
deployRuleChain=enable+主动 Pub/Sub evict(对齐 B8)
- B10 子系统: 新增 GET /device-count 聚合(HGETALL 返空 map 遵循 A6)
+ GET /get?id= query 别名(保留 /get/{id})
- B11 设备: 新增驼峰 PUT /bindSubsystem /batchBindSubsystem
+ 2 ReqVO,保留 kebab 兼容
- B12/B13 告警: 新增 IotAlarmRecordController(整缺)11 端点:
page/get/ack/unack/clear/archive/batch-{ack,clear,archive}/
history/remark;Service 补 6 方法(getPage/batchAck/
batchClear/batchArchive/updateRemark/listHistory)
+ Mapper 2 方法 + 8 VO
- B4/5/6 节点元数据: 新增 GET /iot/rule/provider/metadata 聚合端点;
3 SPI 加 default getMetadata(),4 Manager 加
listAllMetadata(),13 具体 Provider 覆写(中文 label
+ mdi: icon),schema MVP 空骨架 {rule:[]}
测试:
- iot-rule 191/191 全绿(+5 B2 补齐 +9 B4/5/6 补齐)
- iot-server 106 active/161 Skipped v1 遗产 全绿
(+6 B12/B13 补齐 +3 B10 补齐)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-24 15:47:41 +08:00 |
|
|
|
4f89b49e5a
|
fix(iot): B7 BranchNode @Lazy 注入 NodeProviderRegistry 打破循环依赖
启动失败:Spring 检测到环形依赖
nodeProviderRegistry → branchNode → nodeProviderRegistry
根因:BranchNode 既是 NodeProvider(被 Registry 收集),又依赖 Registry
dispatch 子节点 — 典型 "collect vs dispatch" 死结。
修复:构造参数加 @Lazy,Spring 注入代理,首次方法调用才解析 Registry,
构造阶段打破环。运行期行为等价。
rule 模块 177/177 测试全绿。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-24 14:35:30 +08:00 |
|
|
|
ce2e6cc32a
|
fix(iot): 清理 iot-rule spring.factories 旧 EnableAutoConfiguration 条目
Boot 3.x 已弃用,实际加载改由 AutoConfiguration.imports 负责,
spring.factories 中的有效条目移除,仅保留注释说明。
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-24 14:22:47 +08:00 |
|
|
|
04eba61067
|
fix(iot): iot-rule 注册 AutoConfiguration.imports(Spring Boot 3.x)
spring.factories 中的 EnableAutoConfiguration 在 Spring Boot 3.x 已被移除,
须改用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。
缺失此文件导致 IotRuleEngineVersionAutoConfiguration 未加载,
RuleEngineVersionProperties bean 找不到,服务启动失败。
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-24 14:20:25 +08:00 |
|
|
|
171f201384
|
feat(iot): Wave 5 Round 2 — B9/B14/B16 统一消费入口 + 告警分布式锁 + 通知集成
B9 IotRuleEngineMessageHandler(统一消费入口)
- 新消费者 v2 统一入口,@PostConstruct 注册到 IotMessageBus
- versionResolver.shouldUseV2 三态路由(V1/V2/HYBRID),绝不双跑
- device null 时 WARN + skip;RuleEngine 异常 try-catch 吞掉防重试风暴
- v1 三消费者(DataRule/SceneRule/CleanRule)增加前置 v2 bypass 判断
- 6 个单元测试(global-v1/v2/hybrid 白名单命中/未命中/device-null/引擎异常)
B14 告警缓存 + SET NX PX 分布式锁 + 有效性判断
- IotAlarmLockService:SET NX PX + Lua 原子解锁,锁冲突抛 ALARM_LOCK_CONFLICT
- IotAlarmCacheService:Redis Hash iot:alarm:state:{id},TTL 7天,cache miss 从DB重建
- AlarmStateValidator:isEffectiveTrigger/isEffectiveClear 时序校验,防旧消息重置已清除告警
- IotAlarmRecordServiceImpl:trigger/clear/ack/archive 全部在锁内,DB写后立即同步缓存
- 新增 ALARM_LOCK_CONFLICT 错误码;AlarmTriggerRequest 增加 timestamp 字段
- 17 个单元测试(锁 4 + 缓存 5 + 校验 9 + 集成 3)
B16 NotifyAction 4 通道集成 + 模板解析
- NotifyChannel SPI 接口 + Sms/Email/InApp/Webhook 四实现(@Component 注册)
- WebhookNotifyChannel:JDK 17 HttpClient 10s 超时 + SSRF 强制 HTTPS 校验
- NotifyDispatcher:独立 ForkJoinPool(8) 并行分发,30s 整体超时,部分失败不阻塞
- 模板变量统一走 TemplateResolver(评审 C5),缺失变量降级为空串
- NotifyAction 移除 stub,委托 NotifyDispatcher
- viewsh-module-system-api 依赖引入;13 个测试(Dispatcher 7 + Webhook SSRF 6)
测试:iot-rule 177/177 全绿;iot-server 251/251 全绿(含 Skipped 161 旧 v1 测试)
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-24 11:00:38 +08:00 |
|
|
|
8e7631987f
|
feat(iot): Wave 5 Round 1 — B8/B13 规则链缓存 + AlarmHistory 时序 DAO
B8 规则链全量缓存 + Redis Pub/Sub + 版本拉模式兜底:
- CompiledRuleChainFactory:IotRuleChainGraphVO→CompiledRuleChain
- RuleChainCache(@PostConstruct loadAll + evict + reload + B48 钩子)
· TenantUtils.executeIgnore 跨租户全量加载;TenantUtils.execute 逐租户切换
· ConcurrentHashMap.compute 保证 reload 串行(避免并发 DB 查询)
· 超 500 条规则链打 WARN 日志
- RuleChainCacheListener:Redis Pub/Sub 订阅 iot:rule:cache:evict,收到后 evict+reload
- RuleChainVersionChecker:5 分钟拉模式兜底,version drift 时 reload + metric
- RuleChainCacheConfiguration:@EnableScheduling + RedisMessageListenerContainer
- IotRuleChainMapper 新增 selectAllEnabledTenantIds()(跨租户查询)
- IotRuleChainServiceImpl.updateRuleChain 末尾发布 Pub/Sub 驱逐事件
- 5 单元测试全绿(含 version drift 检测 + 容量告警)
B13 AlarmHistory 时序表 DAO 双实现:
- AlarmHistoryDO(时序对象:ts/device/severity/ack/clear/archived/eventType 等)
- IotTsDbAlarmHistoryDao 接口(insert/queryByAlarmRecord/queryLatestByDevice)
- CtsdbAlarmHistoryDaoImpl(CTSDB/InfluxDB 协议,@ConditionalOnProperty)
- TdengineAlarmHistoryDaoImpl(TDengine JDBC,@ConditionalOnProperty)
- IotAlarmHistoryService(协调 TSDB 写;异步 @Async;写失败不影响主流程)
- TsDbAutoConfiguration 注册 IotAlarmHistoryService
- 5 单元测试全绿(含 TSDB 失败降级 + 异步写验证)
测试总计:rule 模块 164/164 ✓,server 模块 B13 5/5 ✓
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-24 10:37:07 +08:00 |
|
|
|
24c486900a
|
feat(iot): Wave 4 Round 2 — B6/B7/B18 ActionProvider + 分支执行 + DataRule迁移
B6 ActionProvider SPI + 5 核心动作(alarm/notify/device-ctrl):
- ActionProvider 接口(extends NodeProvider,默认 bridge execute)
- ActionResult record(SUCCESS/FAILURE/SKIP + output + message)
- ActionProviderManager(Spring 自动收集 + fail-fast 重复 type)
- AlarmTriggerAction(调用 IotAlarmRecordApi.triggerAlarm,模板变量解析)
- AlarmClearAction(alarmId 从 config 或 ctx.metadata 解析,幂等)
- NotifyAction(4 通道并发 + 部分失败不阻塞,第一期 stub)
- DeviceServiceInvokeAction(调用 IotDeviceControlApi.invokeService)
- DevicePropertySetAction(第一期 stub,B27 补全 Redis/MySQL)
- IotAlarmRecordApi + DTO(rule 模块→server 跨模块接口)
- IotAlarmRecordApiImpl(server 端 FeignClient 实现,委托 Service)
- 14 单元测试全绿
B7 分支执行逻辑(executeAnyway if/else-if/else):
- BranchConfiguration POJO(branches[] + executeAnyway + BranchCondition)
- BranchExecutor(核心语义:else/executeAnyway/条件异常短路/action 异常隔离)
- BranchNode NodeProvider(ACTION/"branch",内联执行命中 branch actions)
- DagExecutor 最小扩展(ctx.metadata 传递 CompiledRuleChain 供 BranchNode 使用)
- 9 单元测试全绿(含 validate else 位置校验)
B18 DataRule → DAG 自动转换工具:
- DataRuleToChainMapper(v1→v2 映射,6 种 Sink,合并/拆分多 source)
- DataRuleMigrator(dry-run + execute + 幂等映射表)
- DataRuleMigrationController(3 端点:dry-run/execute/mapping)
- 8 单元测试全绿
测试总计:rule 模块 159/159 ✓,server 模块 8/8(B18)✓
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-24 10:10:04 +08:00 |
|
|
|
42466363c7
|
feat(iot): Wave 4 Round 1 — B12/B4/B5 告警状态机 + 规则引擎 SPI
主会话 Opus:
- B12 iot_alarm_record 正交状态机(ack_state + clear_state + archived)
* V2.0.4__iot_alarm_record.sql:主表 + iot_alarm_propagation 关联表
* 评审 C1 正交三字段(替代线性 4 枚举,表达"已清除未确认")
* 评审 C2 联合 UK (device_id, alarm_config_id, tenant_id, deleted)
* 评审 C3 传播关联表(替代 propagated_to JSON 查询)
* Service 5 方法:triggerAlarm / ackAlarm / unackAlarm / clearAlarm / archiveAlarm
* 幂等 upsert(trigger_count++)+ 归档后禁止修改
* 13 单元测试全绿
* TODO B14 分布式锁 / B15 传播 / B16 通知
Sonnet subagent B4:TriggerProvider SPI + 5 内置触发器
* spi/TriggerProvider + TriggerProviderManager(@Component + getType 索引,fail-fast 重复 type)
* trigger/DeviceState / DeviceProperty / DeviceEvent / DeviceService / Timer(Spring TaskScheduler)
* 评审 A3 落地:禁 ServiceLoader / @SPI
* 44 单元测试全绿
Sonnet subagent B5:ConditionEvaluator SPI + 3 条件 + 统一模板变量
* spi/ConditionEvaluator + condition/Manager
* condition/Expression(Aviator + LRU(256) 编译缓存)
* condition/TimeRange(跨午夜支持)
* condition/DeviceState(Redis 查询,空值按 offline)
* template/TemplateResolver:\${namespace.key},拒绝 \$[...] 旧语法(评审 B5)
* TODO B44 完整 8 层 Aviator 沙箱
* 50 单元测试全绿(TemplateResolver 16 + 条件 3x ≈ 34)
测试汇总:rule 136 全绿 / server 13 新增全绿
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet (subagent) <noreply@anthropic.com>
|
2026-04-24 00:35:14 +08:00 |
|
|
|
ae74b4752a
|
feat(iot): B3 RuleEngine 执行器(DAG + 链级隔离 + 三层匹配去重)
rule 模块 engine/ 新增 14 个核心类 + 4 个测试:
- RuleEngine / DefaultRuleEngine:对外入口,链级 try-catch 隔离(决议 #3)
- DagExecutor:BFS 遍历,按 relation_type 选 outgoing links,RuntimeException 转 FAILURE
- ChainIndex:三层绑定(subsystem/product/device)4 种 key 匹配 + LinkedHashMap 去重 + priority ASC 排序
- RuleChainCompiler:DO → CompiledRuleChain,含单 Trigger + DAG 无环 + 非法枚举兜底
- NodeProvider / NodeProviderRegistry:SPI + Spring @Component 路由(禁用 ServiceLoader/@SPI)
- RuleContext / NodeResult / CompiledRuleChain/Node/Link / RuleEngineResult / RuleChainException
测试覆盖(42/42 全绿):
- DagExecutorTest: 线性链 / 分支(TRUE/FALSE)/ 并行动作 / 异常转 FAILURE / metadata 传递 / SKIP 截断 / 缺 Trigger
- ChainIndexTest: 4 种 wildcard 组合 / 去重 / priority 排序 / 租户隔离 / evict
- RuleChainCompilerTest: 正常编译 / 单 Trigger 兜底 / DAG 无环 / 非法 category & relation_type / 连线 sortOrder
- DefaultRuleEngineTest: 链级异常隔离(chain1+chain3 成功,chain2 失败,counters 各 1 次)
补齐依赖:
- rule/pom.xml 加 io.micrometer:micrometer-core(节点执行 Timer + 失败 Counter)
- RuleNodeCategory 加 of(String) 静态查找方法(配合 RuleLinkRelationType.of 一致风格)
Known Pitfalls 落地:
⚠️ 评审 B1:ShakeLimit 节点 hook 留在 DagExecutor(B48 补)
⚠️ 评审 B3:LinkedHashMap 去重保留顺序(ChainIndex.match)
⚠️ 评审 B4:relation_type 严格封闭 6 值(RuleLinkRelationType + isValid)
⚠️ 评审 A5:chains 顺序 for 循环 + try-catch;不使用 Reactor flatMap 并发
⚠️ 评审 B10:单 Trigger 兜底在 Compiler 层(Service 层 + Compiler 双重保障)
⚠️ Metrics 基数:tag 含 chainId + nodeType + outcome;规则链 ≤ 500 控制
⚠️ DAG 兜底 MAX_NODES_PER_EXECUTION=1000 防脏数据绕过无环校验
未实现(留给后续任务):
- 具体 Provider(B4/B5/B6 Trigger/Condition/Action 实现)
- 全量缓存加载 + Pub/Sub 驱逐(B8)
- JMH Benchmark(任务卡 §6.4 + AC9 p99 < 50ms,第二期补)
- @SpringBootTest 集成测试(B9 Handler 就位后补)
Co-Authored-By: Claude Opus 4.7 (1M context, main session) <noreply@anthropic.com>
|
2026-04-23 23:58:48 +08:00 |
|
|
|
66647e19dd
|
feat(iot): B19 规则引擎 v1/v2/hybrid 版本解析器 + 管理 API
- config/RuleEngineVersion.java(三态枚举 V1/V2/HYBRID)
- config/RuleEngineVersionProperties.java(@ConfigurationProperties, prefix=viewsh.iot.rule.engine)
- config/IotRuleEngineVersionResolver.java(volatile Set 本地缓存 < 1μs + Redis 动态白名单 + 降级)
- config/RuleEngineVersionAdminController.java(5 端点:add/remove/list/version/refresh,@PreAuthorize)
- config/IotRuleEngineVersionAutoConfiguration.java(@AutoConfiguration + @EnableConfigurationProperties)
- spring.factories 注册 AutoConfiguration
- 测试:IotRuleEngineVersionResolverTest 8 用例全绿(含 Redis 降级 + 动态刷新)
- Known Pitfalls 落地:
⚠️ 评审 B11:三态枚举 + switch 全覆盖
⚠️ 性能:volatile + Set.contains,无 I/O
⚠️ Redis 降级:try-catch + log.warn,不抛出
⚠️ subsystemId=null 走 v1(存量未归属设备安全默认)
Co-Authored-By: Claude Sonnet (B19 subagent) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context, orchestrator) <noreply@anthropic.com>
|
2026-04-23 23:40:25 +08:00 |
|
|
|
48e605b3c9
|
fix(iot): 恢复 B2 Controller/VO 的 @PreAuthorize/@Schema/@Valid 注解
Wave 2 三个 subagent 并行时,B19 subagent 看不到主会话后补的
rule/pom.xml web/security 依赖,把 B2 已写的注解全注释掉作为编译兜底。
rule/pom.xml 现已含 web/security/biz-tenant starter,恢复注解。
- IotRuleChainController: 7 @PreAuthorize + @Tag + 7 @Operation + @Parameter + @Valid + @Validated
- IotRuleChainSaveReqVO: @Schema + @NotEmpty/@NotNull/@Valid(含 NodeVO/LinkVO 子 VO)
- IotRuleChainRespVO: @Schema
- IotRuleChainGraphVO: @Schema(含子类)
- IotRuleChainPageReqVO: @Schema
落地 B2 AC9(REST API 所有端点带权限校验)。
测试:mvn test -pl viewsh-module-iot/viewsh-module-iot-rule → 17/17 全绿
Co-Authored-By: Claude Sonnet (annotation-restore subagent) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context, orchestrator) <noreply@anthropic.com>
|
2026-04-23 23:39:26 +08:00 |
|
|
|
962e69290b
|
feat(iot): B2 RuleChain/Node/Link 数据模型 + CRUD(单 Trigger/DAG 无环/乐观锁)
- 新增 sql/iot/V2.0.1__iot_rule_chain.sql(iot_rule_chain/node/link 三表 + idx_binding 索引)
- 新增 rule 模块 dal/(3 个 DO + 4 个封闭枚举 + 3 个 Mapper)
- 新增 rule 模块 service/(CRUD + 单 Trigger 校验 + DAG DFS 无环 + 乐观锁 + 级联软删)
- 新增 rule 模块 controller/admin/(7 REST 端点 + @PreAuthorize + VO)
- 新增 resources/mapper/rule/(3 个 MyBatis XML)
- api 模块 ErrorCodeConstants 新增规则链段(1-050-030-xxx)
- **补 B1 遗漏依赖**:rule/pom.xml 追加 viewsh-spring-boot-starter-{web,security,biz-tenant}
- 测试:8 个单元用例全绿(BaseMockitoUnitTest)
- Known Pitfalls 落地:
⚠️ 评审 B4:relation_type VARCHAR + 应用层 RuleLinkRelationType.isValid 校验
⚠️ 评审 B9:updateWithVersion 乐观锁原子 SQL + idx_update_time 索引支撑 B9 拉模式兜底扫描
⚠️ 评审 B10:单 Trigger 校验在 Service 层(validateSingleTrigger)
⚠️ 评审 A4:name UK(name, tenant_id, deleted)
⚠️ 评审 §十一-B:idx_binding (tenant_id, subsystem_id, product_id, device_id) 最左匹配
Co-Authored-By: Claude Sonnet (B2 subagent) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context, orchestrator) <noreply@anthropic.com>
|
2026-04-23 21:09:54 +08:00 |
|
|
|
4614737d51
|
feat(iot): B1 新建 viewsh-module-iot-rule Maven 模块骨架
- 新增 viewsh-module-iot-rule/pom.xml(仅依赖 iot-api + iot-core + Aviator 5.3.3 + mybatis/redis starter)
- 新增 package-info.java / ModuleSmokeTest.java / spring.factories 占位
- viewsh-module-iot/pom.xml <modules> 新增 rule(core 之后、server 之前)
- viewsh-module-iot-server/pom.xml 新增 rule 依赖(core 之后、业务组件之前)
- Known Pitfalls 落地:
⚠️ 评审 A3:依赖方向 rule → core → api,严禁反向(dependency:tree 验证)
⚠️ 评审 R6:server 对 rule 的依赖按项目一贯顺序插入
⚠️ Aviator 5.3.3 首次引入,rule 模块 pom 内显式声明 version
Acceptance Criteria 全 8 项通过(见任务卡 Notes §8.2026-04-23 执行记录)
Co-Authored-By: Claude Sonnet (B1 subagent) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context, orchestrator) <noreply@anthropic.com>
|
2026-04-23 20:50:31 +08:00 |
|