|
|
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 |
|
|
|
ec3981195d
|
feat(iot): B17 SceneRule → DAG 自动转换工具 + dry-run/execute
- SceneRuleToChainMapper:v1→v2 纯转换逻辑
· trigger type 映射(1→device_state 等 4 种 + timer)
· action type 映射(1→device_property_set / 2→device_service_invoke / 100→alarm_trigger / 101→alarm_clear)
· SpEL→Aviator:#root.x → ${data.x};含 T(/instanceof/new 标记 WARNING 不中断
· 线性 DAG:Trigger → [Condition] → Action×N,临时 key -1/-2/-3...
- SceneRuleMigrator:干运行 + 分批执行(50条/批)+ 幂等(force 覆盖重迁)
- SceneRuleMigrationController:3 端点 dry-run/execute/mapping
- MigrationDryRunResultVO / MigrationExecuteReqVO
- 8 单元测试全绿(含 spel→aviator / unsupported_spel / idempotent / force)
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-24 10:21:52 +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 |
|
|
|
1f87d599c0
|
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>
|
2026-04-24 00:03:57 +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 |
|
|
|
6649e1abb6
|
feat(iot): B10 iot_subsystem 表 + CRUD + Redis 设备计数聚合
- 新增 sql/iot/V2.0.2__iot_subsystem.sql(iot_project + iot_subsystem)
- 新增 server 模块 subsystem/ 下 DO + Mapper + Service + Controller + VO(7 端点)
- 新增 IotSubsystemDeviceCountRedisDAO(HINCRBY + rebuild + ApplicationReadyEvent 触发)
- api 模块 ErrorCodeConstants 新增子系统段(1-050-020-xxx)
- server 模块 RedisKeyConstants 新增 SUBSYSTEM_DEVCOUNT
- 测试:8 个单元用例全绿(mvn test IotSubsystemServiceImplTest)
- Known Pitfalls 落地:
⚠️ 评审 A4:UK(name, tenant_id, project_id, deleted) + 应用层 existsByNameAndProject 兜底 NULL
⚠️ 评审 A6:device-stats 走 Redis Hash,避免 GROUP BY
⚠️ 评审 A7:simple-list 权限码 iot:device:query,返回字段仅 id/name/code
⚠️ 删除校验:Redis 计数 > 0 抛 SUBSYSTEM_HAS_DEVICES
⚠️ Redis 重建:ApplicationReadyEvent + try/catch + log.warn 不阻塞启动
说明:iot_device 当前无 subsystem_id 列(rebuild 逻辑标 TODO B11),
待 B11 加列后启用 DB 重建查询。
Co-Authored-By: Claude Sonnet (B10 subagent) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context, orchestrator) <noreply@anthropic.com>
|
2026-04-23 21:08:00 +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 |
|
|
|
a72c96692e
|
feat(iot-v2): 拆分 AGENTS.md / CLAUDE.md(方案 C)
- AGENTS.md 作为 agents.md 标准通用规范(所有 AI agent 可读)
- .gitignore 放开 AGENTS.md(删除 AGENTS.md 忽略规则)
- CLAUDE.md 保持 gitignored(Claude Code 本地配置)
用于 IoT v2.0 第一期任务的跨 agent 规范:
- 项目背景 / 任务卡系统 / 硬约束 / 技术约定 / 测试命令 → AGENTS.md
- /loop / subagent 并行 / checkpoint / PushNotification → CLAUDE.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-23 17:48:40 +08:00 |
|
|
|
fa8e814af4
|
feat(iot): 抽象时序数据库访问层,支持 TDengine + CTSDB(InfluxDB) 双引擎
将原有 TDengine 强耦合的 Mapper 层重构为统一的 TsDb 抽象接口:
- 新增 IotTsDbDeviceMessageDao / IotTsDbDevicePropertyDao 接口
- 实现 TDengine 和 CTSDB(InfluxDB) 两套适配器
- 通过 viewsh.iot.tsdb.type 配置项切换时序数据库引擎
- Service 层从直接依赖 TDengine Mapper 改为依赖抽象 Dao 接口
- 新增 influxdb-client-java 7.2.0 依赖
- 删除旧的 TDengineTableInitRunner,统一由 TsDbTableInitRunner 管理
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-23 15:57:04 +08:00 |
|
|
|
db31462774
|
refactor(video): JT1078 模块未迁移时的软依赖降级收敛
- JT1078 相关两个 stub 接口 Ijt1078Service / Ijt1078PlayService
目前没有 @Service 实现(等待从 WVP 后续迁移),直接 @Autowired
会导致 video server 启动失败。
- GbChannelPlayServiceImpl:原本只声明了 jt1078PlayService 字段,
从未在任何方法体里引用,属于死代码,直接删掉 import + field。
- MediaServiceImpl:保留引用(checkStreamFromJt 有真实调用),
改成 @Autowired(required=false) 做软依赖;在 "1078" app 分支
入口加 null guard,接口缺失时直接返回 false 拒绝请求;
补注释说明两处处理不对称的原因,并留 TODO 指向 JT1078
迁移完成后的收尾动作(去掉 required=false / 引入
JtModuleProperties.enabled 开关统一管理)。
|
2026-04-23 15:16:50 +08:00 |
|
|
|
10ea5e5eee
|
chore(video): ZLM 凭证挪出默认配置 + 连接池 keep-alive + 日志/队列开关
- application.yaml 里的 media.id/ip/http-port/secret/auto-config 改用
${ZLM_*:占位} 形式,secret 默认值 please-override-in-env,防止
真实凭证写进默认 profile 被全环境继承;开发机实际值搬到
application-local.yaml,继续支持 env var 覆盖。已泄露到 git 历史
的 secret 需运维侧单独旋转。
- application-local.yaml druid 池:min-evictable-idle 从 10min 降到
5min + 打开 keep-alive,配合 eviction 扫描主动 ping,解决远程
MySQL 经 NAT/云防火墙 60~80s 静默断开导致
Communications link failure / last packet received X ms ago。
- application.yaml 补两个 video 模块运维开关:
· viewsh.access-log.exclude-paths 屏蔽 /index/hook/on_server_keepalive
心跳刷屏,出错仍会 WARN;
· video.sip-queue.enabled 管控上一笔 commit 引入的三个
SIP 消息 QueueScheduler,默认开启,关闭后队列会在内存堆积
需要运维兜底(注释已提醒慎用)。
|
2026-04-23 15:14:00 +08:00 |
|
|
|
42d53bb02d
|
fix(video): 收紧 Redis Jackson default typing 白名单,修补反序列化攻击面
- 本模块通过 ZLM hook / GB28181 信令把大量外部输入写入 Redis,
必须保证 default typing 的白名单足够紧。
- 新增 buildRedisObjectMapper:
· 注册 JavaTimeModule 支持 LocalDateTime / LocalDate 等 Java 8
时间类型,避免含 createTime 的对象序列化报错;
· PropertyAccessor 从 ALL/ANY 收窄到 FIELD/ANY,配合 Lombok getter
够用,同时降低经 setter 触发 gadget chain 的面;
· BasicPolymorphicTypeValidator 的 allowIfSubType 原先是 Object.class
(等价 LaissezFaireSubTypeValidator,Jackson 官方警告有 CVE 级
反序列化风险,如 jackson-databind #2367 / #2996),改成按包名
白名单:com.viewsh.* / com.alibaba.fastjson2.JSONObject|JSONArray
/ java.util|time|lang|math / 数组,严格收敛到业务真实存取范围。
- 两个 RedisTemplate Bean 都改用自构建的 ObjectMapper。
- 后续若遇到"新 DTO 反序列化不了",优先放进 com.viewsh 包下,
不要回退到 allowIfSubType(Object.class)。
|
2026-04-23 15:09:04 +08:00 |
|
|
|
a58ab1928e
|
refactor(video): SIP 高频消息队列消费拆独立 Scheduler + 修 Catalog 事务自调用
背景:
- 原先 Alarm / Keepalive / Catalog 三个业务 Handler 自带 @Scheduled,
@Scheduled 会让 Spring 对业务类生成 CGLIB 代理,
与 @EventListener / @Async 等场景叠加时容易出现"找不到方法"/
重复包装等怪异行为。
- CatalogResponseMessageHandler.executeTaskQueue 上标 @Transactional
会导致空队列 50ms 触发一次空事务,浪费连接池;与此同时
this.saveData(...) 是自调用,saveData 上的 @Transactional
又根本不生效,事务语义双重翻车。
本次改动:
- 新增 CatalogResponseMessageQueueScheduler 无接口 @Component,
@Scheduled(fixedDelay=50) 驱动业务 Handler 的 executeTaskQueue。
- Alarm / Keepalive 两个 QueueScheduler 补 @ConditionalOnProperty
(video.sip-queue.enabled, matchIfMissing=true),三个调度器统一
开关;注释对齐,标明关闭后消息会在内存堆积、需运维兜底。
- CatalogResponseMessageHandler:
· 去掉 executeTaskQueue 上的 @Scheduled + @Transactional,
入口处 taskQueue.isEmpty() 直接 return,不再开空事务;
· @Autowired @Lazy 注入自身代理 self,把 this.saveData(...)
改成 self.saveData(...),让 saveData 上的 @Transactional
真正生效(MyBatis 落库 + 区域/分组批写回到同一事务)。
|
2026-04-23 15:07:44 +08:00 |
|
|
|
38681c39c1
|
refactor(video): ISIPProcessorObserver 接口补齐注册方法,processor 改依赖接口
- 原先 addRequestProcessor / addResponseProcessor 只在实现类
SIPProcessorObserver 上,13 个处理器在 afterPropertiesSet 里
@Autowired 实现类才能自注册,耦合到具体实现。
- 把两个 register 方法提到 ISIPProcessorObserver 接口上,
13 个 processor 的注入类型改为 ISIPProcessorObserver,
处理器侧不再感知具体实现,方便后续测试 mock / 多实现。
- 本次只改注入类型与接口签名,消息分发行为不变。
|
2026-04-23 15:05:00 +08:00 |
|
|
|
6b50254a96
|
feat(video): 适配多租户上下文透传 + 定时任务 TTL 装饰 + 生命周期收口
问题背景:
- WVP 原生代码的 SIP / @Scheduled / Hook EventListener 都跑在没有
TenantContextHolder / ProjectContextHolder 的线程上,接入多租户
框架后会漏 tenant_id / project_id 过滤或抛 NPE。
- 框架默认 @EnableAsync 走 JDK 动态代理,但 WVP 代码大量"按具体类
注入 / 按具体类查方法",JDK 代理下会 cast/查方法失败。
本次改动:
- VideoServerApplication 排除框架 ViewshAsyncAutoConfiguration,
由 ThreadPoolTaskConfig 本地 @EnableAsync(proxyTargetClass=true)
+ TtlRunnable 装饰器接管(从框架抄一份 BeanPostProcessor)。
- DynamicTask 的 scheduleAtFixedRate / schedule 入口都用 TtlRunnable
包一层,把调用线程的 TTL 快照透传到调度线程;新增 @PreDestroy
在容器关停时取消 future、shutdown scheduler、记录耗时,
避免关停阶段调度线程继续借 DataSource 触发噪音日志。
- 新增 VideoContextUtils.executeIgnoreTenantAndProject,
统一封装 TenantUtils.executeIgnore(ProjectUtils.executeIgnore(…))
的嵌套模板,覆盖启动初始化 / 定时任务 / Hook 事件 / Redis 消息
四种场景共 8 个调用点。
- MobilePositionServiceImpl 拆出 @Transactional persistPositions,
通过 @Lazy self 代理调用,避免外层方法 this 自调用让事务失效;
外层只做 Redis 拉队列 + 空队列短路,不再开空事务。
- StreamProxyServiceImpl / StreamPushServiceImpl 的 @EventListener
在业务体内包裹 executeIgnoreTenantAndProject,保证 ZLM Hook
到来时仍能跨租户落库。
|
2026-04-23 15:03:26 +08:00 |
|
|
|
0526322fa8
|
refactor(video): Controller 下沉到 controller/admin 包对齐框架 /admin-api 前缀
- 原先散落在 gb28181/controller/、streamProxy/controller/、aiot/controller/、
vmanager/**/、streamPush/controller/ 下的 Controller 统一移到各自
controller/admin/ 子包,对齐框架 WebProperties 的自动前缀规则
(根 CLAUDE.md「Controller 路径规范」:admin 包自动拼 /admin-api)。
- SecurityConfiguration 的 permitAll 路径同步补 /admin-api 前缀,
覆盖 /video/device/query/snap/** 与 /video/sse/** / /video/emit,
避免路径匹配不上导致匿名请求被拦截。
- 本次仅变更 package 声明与安全路径,controller 内部逻辑保持不变。
|
2026-04-23 15:00:57 +08:00 |
|
|
|
54250f2f5a
|
feat(video): video_media_server 补齐 deleted 列对齐 BaseDO
- DO 继承 BaseDO(含 @TableLogic),但建表脚本初版漏了 deleted 列,
MyBatis Plus 自动生成的 UPDATE/SELECT 语句会报
"Unknown column 'deleted' in 'where clause'"。
- 同步更新 video.sql 中的建表脚本与段落注释,增量脚本放到
sql/mysql/migrations/2026-04-22_video_media_server_deleted.sql,
已部署库手动执行即可。
|
2026-04-23 14:59:31 +08:00 |
|
|
|
88cab42a9c
|
feat(system): 用户-项目绑定管理 API + 顶栏项目下拉修正
- 新增 UserProjectService/ServiceImpl/Controller:给用户分配项目、给项目分配成员
幂等覆盖写入(diff 出增删),参考 PermissionServiceImpl.assignUserRole 模式
- 自踢守卫:禁止用户把自己从当前正在访问的项目中移除
- 超管守卫:assignProjectUsers 拒绝移除持有超管角色的用户(用 RoleService.hasAnySuperAdmin 判别,非 userId==1)
- ProjectController.simple-list 改为只返回"当前用户授权且启用"的项目(修 bug:原返回整租户启用项目,会让顶栏下拉看到无权访问的项目)
- 新增 /system/project/all-simple-list:管理员分配场景的全量项目下拉,权限复用 system:project:query
- ProjectService.deleteProject 加 @Transactional,同事务内级联软删 system_user_project
- 新增两条菜单权限种子 SQL,parent_id 子查询动态定位:
* system:user:assign-project
* system:project:assign-user
- 新增错误码 USER_PROJECT_CANNOT_REMOVE_SELF_CURRENT / USER_PROJECT_CANNOT_REMOVE_SUPER_ADMIN
设计文档:docs/design/2026-04-23-user-project-binding.md(在前端仓库)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-23 14:48:57 +08:00 |
|
|
|
b91a366f51
|
Merge branch 'master' into feat/multi-tenant
|
2026-04-22 18:23:50 +08:00 |
|
|
|
d6f625151c
|
Merge pull request 'fix(ops): 修复工牌绑定/手动派单/审计日志三处缺陷' (#2) from fix/badge-online-and-manual-dispatch into master
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
Reviewed-on: #2
|
2026-04-22 18:10:33 +08:00 |
|
|
|
3cfd342318
|
fix(ops): BADGE 绑定/解绑后即时同步工牌缓存
问题:
1. 设备先上线、后绑定为 BADGE 时,"可分配工牌"列表迟迟不出现该设备;
2. 已绑定区域的设备收不到工单(被分派策略过滤掉)。
根因:实时上线路径 BadgeDeviceStatusEventHandler.onMessage 在写
ops:badge:status:{deviceId} Redis 缓存前,先 isBadgeDevice() 校验
ops_area_device_relation 是否存在 BADGE 关系。设备如果在绑定前就上线,
事件被丢弃;建立关系后又没有任何机制回填 Redis,得等 5/30 分钟的
BadgeDeviceStatusSyncJob 对账才会被发现(线上日志可见 deviceId=58 在
绑定后 30 分钟才被对账修正:reason=定时对账修正-上线)。
解绑同样有反向缺口:关系记录删了但 Redis 缓存得等 24h TTL 自然过期,
期间 listAvailableBadges 仍可能返回该设备。
修复:在 ops-biz 引入 AreaDeviceBoundEvent / AreaDeviceUnboundEvent,
bindDevice / unbindDevice 成功后通过 ApplicationEventPublisher 发布;
environment-biz 新增 BadgeAreaBoundEventListener 仅订阅 BADGE 类型,
使用 @TransactionalEventListener(AFTER_COMMIT) + @Async 确保事务提交后
异步执行不阻塞接口:
- 绑定:单次调 IotDeviceQueryApi.getDevice 取齐 state + nickname +
deviceName,根据状态写 Redis(IDLE/OFFLINE);
- 解绑:直接调 BadgeDeviceStatusService.deleteBadgeStatus 清理 Redis。
依赖方向遵循 environment-biz → ops-biz;ops-biz 不反向依赖条线模块,
通过事件解耦,与现有 OrderStateChangedEvent 模式一致。
测试:
- AreaDeviceRelationServiceTest 补 iotDeviceQueryApi mock 让原本因缺
mock 而 RED 的 testBindDevice_TrafficCounter_Success 转绿;
- 新增对 bound / unbound 事件 publishEvent 调用的 verify。
- 全套 10/10 通过。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-22 18:04:47 +08:00 |
|
|
|
6c4153fe23
|
fix(ops): 手动派单提前写执行人字段,修复按键报"没有工单"
问题:管理员手动派单后,工牌按键查询持续返回"没有工单",TTS 循环播报"工单
来啦"但用户无法响应(线上日志可见 deviceId=58 一段时间内连续 8 次按键查询
全部命中 CleanOrderAuditEventHandler.handleQueryEvent 的"没有工单"分支)。
根因:ManualOrderActionFacade.dispatch() 原顺序是
1. transition() —— 事务内同步发布 OrderStateChangedEvent,
BadgeDeviceStatusEventListener.onOrderStateChanged 重新 selectById 拿
assigneeId 决定是否写 Redis 工单关联;
2. update assigneeId / assigneeName。
第 1 步事件触发时 assigneeId 仍是 null,listener 走"工单未关联设备,跳过
处理"分支,Redis ops:badge:status:{deviceId} 的 currentOpsOrderId 永远不
会写入;同时主表 assigneeDeviceId 也始终为 null,
CleanOrderAuditEventHandler.handleQueryEvent 用
"WHERE assignee_device_id=?" 查工单永远落空 → "没有工单"。
修复:把执行人字段更新前置到 transition() 之前,并补 setAssigneeDeviceId
(与 OrderLifecycleManagerImpl.dispatch() 自动派单路径一致)。
事件 listener 此时 selectById 拿得到 deviceId,正常落 Redis;audit
查询也命中,按键路径恢复。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-22 18:04:10 +08:00 |
|
|
|
8c664a479d
|
refactor(ops): 状态转换成功不再镜像写 bus_log
问题:每次 transition() 成功 commit 后,OrderTransitionAuditListener 都会
向 bus_log 写一条"状态转换成功: X -> Y"的镜像记录。该信息已由各条线
EventListener(CleanOrderEventListener 的 ORDER_DISPATCHED 等)和
ops_order_event 表完整覆盖,bus_log 里这条镜像形成噪声且与业务日志重复,
线上一次工单流转能产出 4-5 条同义日志,运维抓异常时被淹没。
改动:onAfterCommit 在 event.isSuccess()=true 时直接 return;
失败 / 派发被拒(DISPATCH_REJECTED)/ 回滚三类异常路径继续写,
保留运维真正关心的"为什么失败"审计闭环。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-22 18:03:51 +08:00 |
|
|
|
b3d76ad00c
|
fix(video): 修复 CommonGBChannel 子类 @TableId 冲突与主键列映射
问题:
1. CommonGBChannel.gbId 的 @TableId 按驼峰转 snake_case 映射到 gb_id 列,
但实际所有相关表的主键列都是 id
2. StreamProxy/StreamPush 子类自己声明 @TableId(id),与父类 gbId 的
@TableId 同时扫描到 MyBatis-Plus 会报 "@TableId can't more than one"
3. DeviceChannel/PlatformChannel 没自己的 @TableId,运行时会用父类 gbId
作为主键查 gb_id 列 → Unknown column 运行时错误
修复:
- CommonGBChannel.gbId 的 @TableId 加 value="id" 显式映射到 id 列
保证 CommonGBChannelMapper 操作 video_common_gb_channel 时主键正确
- 4 个子类(StreamProxy/StreamPush/DeviceChannel/PlatformChannel)均
shadow 父类 gbId 字段:
@TableField(exist = false)
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
private Long gbId;
让 MyBatis-Plus 反射扫描时只看到子类 gbId(标记 exist=false 跳过),
父类 @TableId 不参与扫描;Lombok 禁用子类 getter/setter,所有业务
代码的 setGbId/getGbId 继续走父类访问器,字段存储在父类
- DeviceChannel 和 PlatformChannel 给自己的 id 字段加 @TableId(IdType.AUTO)
显式声明各子表主键
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-22 14:40:13 +08:00 |
|
|
|
d876d0387a
|
refactor(video): @Scheduled 转 xxl-job,对齐 ops 模块定时任务约定
WVP 原生单体采用 @Scheduled 做周期任务,位于 ServiceImpl 上的任务
Spring 用 JDK 代理无法匹配到接口方法,导致启动失败。按 ops 模块
约定(QueueSyncJob 模板),把 6 个 ServiceImpl 的周期任务转为
独立 @XxlJob Job 类;2 个 IMessageHandler 的高频轮询拆为独立
无接口 @Component。
新建 6 个 Job 类(framework/job/):
- InviteStreamCleanupJob (10s) 清理 Redis 错误 Invite 数据
- DeviceSubscribeLostCheckJob (10s) 设备订阅丢失检查
- DeviceStatusLostCheckJob (30s) 设备状态丢失检查
- PlatformStatusLostCheckJob (20s) 平台注册状态检查
- PlatformAutoRegisterJob (2s) 级联平台自动注册监听
- AiEdgeDeviceOfflineCheckJob (90s) AI 边缘设备离线标记
接口变更(让 Job 类通过 JDK 代理正常调用):
- IInviteStreamService 新增 cleanInvalidInviteCache()
- IDeviceService 新增 lostCheckForSubscribe() / lostCheckForStatus()
- IPlatformService 新增 statusLostCheck() / cascadePlatformAutoRegister()
- PlatformServiceImpl.execute() 重命名为 cascadePlatformAutoRegister()
ServiceImpl 调整:
- InviteStream/Device/Platform/AiEdgeDevice ServiceImpl 删除
@Scheduled 注解,方法体保留
- 清理 @Scheduled / TimeUnit 无用 import
新建 2 个高频 Scheduler @Component(保持 100-200ms 毫秒级轮询):
- AlarmNotifyMessageQueueScheduler (200ms)
- KeepaliveNotifyMessageQueueScheduler (100ms)
这两个消息处理器是 SIP 协议栈内部机制,不适合走 xxl-job 中心调度,
拆到独立无接口 Component 后 Spring 自动走 CGLIB,代理正常。
原 MessageHandler 删除 @Scheduled 注解,executeTaskQueue() 保留
为 public 供新 Scheduler 调用。
配置:本地 xxl.job.enabled=false 已配置(与 ops 对齐)
编译通过(mvn compile BUILD SUCCESS)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-22 14:29:51 +08:00 |
|
|
|
4f0c8f7162
|
fix(video): Mapper 移除多数据库方言分支(仅保留 MySQL)
WVP 原支持 MySQL / H2 / KingBase / PostgreSQL,使用 databaseId 属性
区分 SQL 方言,但本项目 MyBatis 未配置 databaseIdProvider,运行时
databaseId 为 null 导致 "Could not find a statement annotation that
correspond a current database" 启动失败。
改动:
- CommonGBChannelMapper.queryListInCircle / queryListInPolygon:
仅保留 queryListInCircleForMysql / queryListInPolygonForMysql
对应的 @SelectProvider,删除 h2/kingbase/postgresql 变体
- GroupMapper.updateParentId / updateParentIdWithBusinessGroup /
fixParentId:仅保留 MySQL JOIN 语法,删除 3 种方言 UPDATE
- RegionMapper.updateParentId:同上
项目固定使用 MySQL,这些方言变体在本仓库永远不会命中,删除后启动
不再需要依赖 databaseIdProvider 配置。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-22 14:00:29 +08:00 |
|
|
|
3a3f7b78d4
|
refactor(video): 预置 AI 算法改为 SQL 种子数据,移除 @PostConstruct 初始化
原 AiAlgorithmServiceImpl.initPresetAlgorithms() 在 @PostConstruct
里插入 4 条预置算法,带来两个问题:
1. 启动时无登录上下文,MP 自动填充 creator 失效 → 启动失败
2. 算法清单硬编码在 Java 代码,迭代要重编译发布
改为 video.sql 种子数据管理:
- video.sql 的预置算法 INSERT 扩展为 4 条(leave_post / intrusion /
illegal_parking / vehicle_congestion),参数 schema 与边缘端对齐
- 使用 ON DUPLICATE KEY UPDATE 保证幂等:新库初始化 + 存量库升级
都走同一条语句,param_schema / description 会被自动校正
- 保留用户侧可修改的字段(is_active / global_params)不被覆盖
代码层:
- 删除 initPresetAlgorithms() 方法与 PRESET_ALGORITHMS 静态 Map
- 删除 SYSTEM_USER 常量
- 删除 @PostConstruct / HashMap 相关 import
- 保留 syncFromEdge() 作为边缘端主动同步的运行时入口
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-22 13:54:39 +08:00 |
|
|
|
1ac72b23c5
|
fix(video): AI 算法预置数据 @PostConstruct 插入时显式设置 creator/updater
启动时 initPresetAlgorithms() 在 @PostConstruct 执行,此时无登录上下文:
- DefaultDBFieldHandler.insertFill 在 getLoginUserId()==null 时不填充
creator/updater
- SQL video_ai_algorithm.creator NOT NULL 约束触发
"Column 'creator' cannot be null" 启动失败
手动设置 creator/updater = "1"(系统用户)作为系统级初始化的占位,
同时 update 分支也显式设置 updater 避免同类问题。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-22 13:48:52 +08:00 |
|
|
|
42cb3d9d57
|
chore(video): 数据源改用统一 aiot-platform 库,与 ops/iot 对齐
原 video 模块 application-local.yaml 默认 MYSQL_DATABASE 是 aiot-video
(设计初衷是 WVP 独立库)。运维按单库部署,26 张 video_* 业务表
已执行到 aiot-platform 库,因此默认值改为 aiot-platform,
避免启动时 "Unknown database 'aiot-video'" 报错。
同步 master / slave 两个数据源默认值。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-22 11:33:49 +08:00 |
|
|
|
8ae7b1a823
|
docs(video): 遗留项防御性加固 — GB2022 扩展字段 exist=false + Mapper 继承警告
基于遗留项调研结论(三项均无当前运行时风险,真正价值是防御未来
误用 BaseMapperX 高层方法),执行最小化防御性修复:
PlatformChannel 的 20 个 GB2022 扩展字段加 @TableField(exist=false):
- customSecurityLevelCode / customPhotoelectricImagingTyp /
customCapturePositionType / customStreamNumberList /
customSsvcRatioSupportList / customMobileDeviceType /
customHorizontalFieldAngle / customVerticalFieldAngle /
customMaxViewDistance / customGrassrootsCode / customPoType /
customPoCommonName / customMac / customFunctionType /
customEncodeType / customInstallTime / customManagementUnit /
customContactInfo / customRecordSaveDays / customIndustrialClassification
这些是 WVP 预占位字段,SQL 尚未建列,加 exist=false 消除
BaseMapperX 反射写入时 "Unknown column" 的潜在风险,
待业务方决定是否推进 GB2022 自定义属性持久化再补 SQL 列。
StreamProxyMapper / StreamPushMapper 加接口级 Javadoc 警告:
- StreamProxy/StreamPush 继承 CommonGBChannel 带入 40+ 个 gb_* 字段,
但 video_stream_proxy / video_stream_push 表不含这些列
(gb 通道信息由 GbChannelService 写入 video_common_gb_channel)
- 警告开发者严禁使用 BaseMapperX 的 insert/updateById/selectByMap
自动映射方法,写入必须走显式 SQL 方法(add/update/addAll)
- 读取 selectById/selectList 可用(SELECT 时 gb_* 字段返回 null 不报错)
未改动(零当前风险 + 改动成本高):
- SIP 协议时间字段(register_time 等 varchar):Mapper 全显式 SQL
用 #{} 绑定 String,与 varchar 列自洽;DateUtil.getNow() 返回
"yyyy-MM-dd HH:mm:ss" 格式字符串,时间比较查询的字符串排序
与 datetime 排序结果一致;改造涉及 72+ 处调用链
- StreamProxy/StreamPush 继承架构:改组合式重构代价大,
通过 Javadoc 警告防御未来误用即可
编译通过(mvn compile BUILD SUCCESS)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-21 13:43:23 +08:00 |
|
|
|
0ca1adf2a4
|
refactor(video): P2 应用层时间字段 varchar → datetime / String → LocalDateTime
SQL 类型优化(应用层生成的业务时间改为标准 datetime):
- video_ai_edge_device: last_heartbeat / updated_at
- video_ai_config_log: updated_at
- video_ai_config_snapshot: created_at
- video_ai_algorithm: sync_time
- video_ai_alert: received_at
- video_media_server: last_keepalive_time
DO 字段 String → LocalDateTime 对齐:AiEdgeDevice / AiConfigLog /
AiConfigSnapshot / AiAlgorithm / AiAlert / MediaServer
调用点适配:
- Service 层 new Date() 字符串格式化赋值统一改为 LocalDateTime.now()
- AiAlertController.edgeReport / MqttService.handleAlert 新增
parseEventTime / parseTimestamp 私有方法,外部字符串防御性解析
保留 varchar(本批次暂不动,SIP 协议原生字符串,改动涉及 72+ 处
Mapper 与 SIP 处理器,单独迭代):
- video_device.register_time / keepalive_time
- video_device_channel.end_time / gps_time
- video_device_mobile_position.time
- video_stream_push.push_time
编译通过(mvn compile BUILD SUCCESS)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-21 12:32:23 +08:00 |
|
|
|
64dcdd8d4c
|
refactor(video): P1 类型规范化 — varchar 数值转 int / Integer 布尔转 Boolean
SQL 类型修正(数值语义列不再用 varchar):
- video_platform: device_port / expires / keep_timeout varchar(50) → int
- video_platform_channel: custom_cert_num / custom_end_time varchar(50) → int
DO 布尔语义字段 Integer → Boolean(SQL 早已是 tinyint(1),DO 对齐语义):
- AiAlgorithm.isActive
- AiRoi.enabled
- AiRoiAlgoBind.enabled
同步修复 ~20 处调用点的布尔判断,setEnabled(1) → setEnabled(true),
getEnabled() == 1 → Boolean.TRUE.equals(getEnabled())
MediaServer.hookAliveInterval Float → Integer(SQL 是 int,Float 精度丢失)
同步修复 MediaConfig、ABL/ZLM 两个 StatusManager 的 10F → 10 字面量
编译通过(mvn compile BUILD SUCCESS)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-21 12:13:56 +08:00 |
|
|
|
1698c71d84
|
fix(video): P0 修复 @TableName 映射错误、字段命名、补缺列与 exist=false
@TableName 修正:
- CommonGBChannel: "video_device_channel" → "video_common_gb_channel"
- DeviceChannel: 显式加 @TableName("video_device_channel")
- PlatformChannel: 补充 @TableName("video_platform_channel")
避免因继承 CommonGBChannel 而写入错误表
字段命名规范化(修复驼峰转 snake_case 错位):
- CommonGBChannel.recordPLan → recordPlanId(对应 record_plan_id)
- MediaServer.httpSSlPort / flvSSLPort / wsFlvSSLPort
→ httpSslPort / flvSslPort / wsFlvSslPort
(原命名 L 大写/双大写导致转 snake 变 http_s_sl_port)
- 同步更新 Mapper SQL 中的 #{xxx} 引用和 ZLM/ABL 节点服务调用点
@TableField(exist=false) 标注(JOIN 显示字段/业务计算值/内存对象):
- DeviceChannel: parentDeviceId、parentName、ptzTypeText
- DeviceAlarm: deviceName、alarmPriorityDescription、alarmMethodDescription、
alarmTypeDescription
- AiAlert: cameraName、roiName
- Device: sipTransactionInfo
- MobilePosition: channelDeviceId
- StreamPush: uniqueKey
补齐 SQL 缺失列(需持久化的业务状态字段):
- video_device: channel_count
- video_platform: channel_count / catalog_subscribe / alarm_subscribe
/ mobile_position_subscribe
- video_media_server: status / last_keepalive_time
- video_cloud_record: reserve
- video_device_mobile_position.channel_id: varchar(50) → bigint
(DO 字段是 Long,作为外键引用 video_device_channel.id)
补齐 DO 缺失字段:
- AiAlgorithm: 新增 globalParams(对应 SQL global_params)
- StreamPush: 新增 status(对应 SQL status)
编译通过(mvn compile BUILD SUCCESS)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-21 12:07:47 +08:00 |
|
|
|
43becf998c
|
refactor(video): projectId 两级隔离适配 + SQL 融合到 video.sql
SQL 融合:
- sql/mysql/video.sql 作为最终主脚本(26 张表),整合用户改造版表前缀 video_
与 cherry-pick 版的框架字段(tenant_id/creator/updater/deleted/datetime)
- 新增 video_ai_camera_snapshot(AI 抓拍)、video_common_gb_channel(国标通道抽象)
- 删除旧的 aiot-video.sql(被 video.sql 替代)
- 17 张业务表 + 6 张 AI 业务表加 project_id 列(项目级隔离)
- 2 张字典表(video_ai_algorithm/video_ai_algo_template)仅租户级
- video_media_server 全局共享,无多租户字段
代码改造:
- 21 个 DO 的 @TableName 从 wvp_* 改为 video_*
- 16 个业务 DO 改继承 ProjectBaseDO(StreamProxy/StreamPush 通过
CommonGBChannel 自动获得),字典 DO 保留 TenantBaseDO,
MediaServer 保留 BaseDO
- 28 个 Mapper/Provider 的 SQL 表名全部更新为 video_*
- application.yaml 新增 tenant.ignore-project-tables 配置,
列出 video_media_server/video_ai_algorithm/video_ai_algo_template
不参与项目隔离
- 编译通过(mvn compile BUILD SUCCESS)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-21 11:10:27 +08:00 |
|
|
|
fe5bbbe8c6
|
refactor(video): 清理 Security/User stub,替换为框架 SecurityFrameworkUtils
- 删除 SecurityUtils/LoginUser/JwtUtils 3 个空壳 stub
- 删除 UserMapper/RoleMapper/UserApiKeyMapper 3 个废弃 Mapper 及 DTO
- 删除 IUserService/IRoleService/IUserApiKeyService 及实现类
- MediaController 改用 SecurityFrameworkUtils.getLoginUserId()
- 21 个 Controller 的 JwtUtils.HEADER 替换为 "Authorization" 字面量
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-21 10:15:20 +08:00 |
|
|
|
3c752aeb89
|
refactor(video): PageHelper → PageResult 分页迁移,删除 shim 兼容类
- 54 个文件从 PageHelper.startPage() + PageInfo 迁移到 MyBatis Plus Page + PageResult
- 复杂 @Select 查询加 IPage 参数实现自动分页
- 删除 PageHelper/PageInfo/StringUtil 3 个 shim 文件
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-21 10:15:20 +08:00 |
|
|
|
f48c1846fb
|
refactor(video): Media/Stream 域 ORM 改造 + 全域硬删除转逻辑删除
- MediaServer 继承 BaseDO(共享表无 tenant_id)
- StreamProxy/StreamPush/CloudRecord/RecordPlan DO 改造
- 5 个 Mapper 继承 BaseMapperX
- 32 处 @Delete 硬删除转为逻辑删除(default 方法或 @Update SET deleted=1)
- Service/Controller/RPC 适配 int→Long 类型变化
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-21 10:15:19 +08:00 |
|
|
|
0b0c264dca
|
refactor(video): GB28181 域 ORM 改造 — DO 继承 TenantBaseDO + Mapper 继承 BaseMapperX
- Device/Platform/Channel/Group/Region/Alarm/MobilePosition 等 DO 改造
- 9 个 Mapper 继承 BaseMapperX,简单 CRUD 改为 default 方法
- 复杂多表 JOIN/动态 SQL 保留 @Select 注解
- Service/Controller 适配 int→Long 类型变化
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-21 10:15:17 +08:00 |
|
|
|
0c061ad74c
|
refactor(video): AI 域 ORM 改造 — DO 继承 TenantBaseDO + Mapper 继承 BaseMapperX
- 8 个 AI DO 加 @TableName、继承 TenantBaseDO、主键改 Long
- 9 个 AI Mapper 继承 BaseMapperX,简单 CRUD 改为 default 方法
- Service/Controller 适配类型变化(Integer→Long、删除手动 setCreateTime)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-21 10:15:15 +08:00 |
|
|
|
a604471e6f
|
feat(video): Phase 5-9 编译修复、Controller 路径、数据库 SQL
Phase 5: 全局编译修复 — Security 引用清理、JT1078 引用移除
PageInfo shim 兼容类(后续迁移到 PageResult)
web/custom 和 web/gb28181 补充迁移
Phase 6: SecurityConfiguration 更新放行 Hook/SSE 路径
Phase 7: 32 个 Controller 路径 /api/ → /video/
Phase 8: aiot-video 数据库 SQL(25 张表,含多租户 tenant_id + 逻辑删除 deleted)
Phase 9: mvn compile BUILD SUCCESS
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-21 10:15:14 +08:00 |
|
|
|
d2c82d52c9
|
feat(video): Phase 0-4 WVP 代码搬迁、依赖、Redis 隔离、多租户配置
Phase 0: 前置检查完成(Java 21 语法无风险)
Phase 1: 574 个 Java 文件搬迁,包名替换 com.genersoft.iot.vmp → com.viewsh.module.video
Phase 2: pom.xml 添加 JAIN SIP/dom4j/okhttp/bouncycastle/fastjson2 等依赖
application.yaml 添加 SIP/Media/UserSettings 配置段
Phase 3: RedisTemplate Bean 隔离(videoRedisTemplate),30+ 文件加 @Qualifier
Phase 4: 多租户配置(ignore-tables: wvp_media_server)
待 Phase 5: ORM 改造 + 编译修复
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-21 10:15:07 +08:00 |
|
|
|
50a826f157
|
docs(video): WVP-Platform 迁移提案 (proposal + tasks + design)
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-21 10:15:02 +08:00 |
|
|
|
948d2c6a41
|
feat(video): 新建 viewsh-module-video 服务模块骨架
新增视频管理模块,用于后续迁移 WVP-Platform(GB28181 视频监控平台)。
- viewsh-module-video-api: 契约层(Feign RPC 接口、枚举、错误码)
- viewsh-module-video-server: 业务层(端口 48093)
- 网关路由: video-admin-api / video-app-api
- SecurityConfiguration: 放行 Swagger/Actuator/Druid/RPC
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
2026-04-21 10:15:02 +08:00 |
|
|
|
65ad3f35e5
|
Merge branch 'master' into feat/multi-tenant
吸收 master 今日 9 个工单链路修复:
- autoDispatchNext/dispatch 空闲兜底 + FOR UPDATE 并发防护
- 状态转换审计闭环(AFTER_COMMIT/AFTER_ROLLBACK)
- 队列楼层权重强优先 + 三级 baseline 兜底 + N+1 优化
- 工牌 nickname 回填
- CleanOrderAutoCancelJob 超时工单自动取消
|
2026-04-20 16:04:46 +08:00 |
|
|
|
c78759fd52
|
feat(ops): 新增保洁工单超时自动取消 Job + 集成测试
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
背景:保洁工单偶尔因设备离线/信标丢失导致卡在非终态(如 PENDING 超 12h 没派,
DISPATCHED 超 12h 没确认),靠人工清理成本高。补一个每小时跑的 XXL-Job 扫描关单。
实现:
- CleanOrderAutoCancelJob.scanAndCancel:
* 查询 update_time 距今超 timeoutHours(默认 12h)的 CLEAN 工单
* 状态白名单 = PENDING/QUEUED/DISPATCHED/CONFIRMED/ARRIVED,**排除 PAUSED**
(PAUSED 是 P0 打断的产物,应由 resumeInterruptedOrder 走状态机恢复,
此处若把它 CANCEL,会破坏 P0 完成后的 resume 链路)
* 调用 orderLifecycleManager.cancelOrder 走完整责任链,事件监听器负责
TTS 停播/设备关联回收/审计日志
* cancel 前再 selectById 做乐观校验:若 update_time 已刷新或状态已变
(COMPLETED/CANCELLED/PAUSED),跳过;避免候选装内存到实际 cancel
之间用户刚触达的工单被误杀
* 单单独立 try/catch 隔离,单条失败不断批
* batchSize 限流(默认 200),事件风暴防护
- application.yaml 补默认配置:viewsh.ops.clean.auto-cancel.{timeout-hours, batch-size}
- CleanOrderAutoCancelJobTest 覆盖 6 条不变量:
无候选零计数、全成功、部分失败不中断、乐观锁跳过 stale、终态跳过、PAUSED 跳过
XXL-Job 配置建议:
- JobHandler: cleanOrderAutoCancelJob
- Cron: 0 17 * * * ? (每小时 :17,避开整点尖峰)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2026-04-20 15:21:33 +08:00 |
|