docs: 升级设计方案迭代 — Agent 协作节点 + CTSDB 双实现 + 业务链路图

- 04-规则引擎:新增第十二节 Agent 协作节点设计(ACP 协议调用内部数字员工)
- 04-规则引擎:Provider 清单新增 agent_request / agent_judge 预留标注
- 07-数据存储:重写为基于 aiot-iot-data-storage 已有 CTSDB/TDengine 双实现演进
- 01-整体架构:新增第五节端到端业务链路图(设备→网关→总线→规则引擎→动作)
- 全文档统一:TDengine 硬引用改为"时序库(CTSDB/TDengine)"
- 00-总览:融合策略表新增 Agent 协作行,不引入表新增 TB AI Node 直连 LLM
- 00-总览:修正设计原则表残留的 EntityRelation 引用
- 新增 assets 目录(drawio 图表资源)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lzh
2026-04-12 23:15:15 +08:00
parent 87dc7f7aba
commit f827bffe91
28 changed files with 2153 additions and 191 deletions

View File

@@ -20,49 +20,50 @@
## 二、设计原则
| 原则 | 说明 |
|------|------|
| **南向屏蔽,北向统一** | 协议层允许混乱,业务层必须干净(继承现有铁律) |
| **JetLinks 为主骨架** | 规则引擎、条件引擎、SPI 扩展采用 JetLinks 模式 |
| **ThingsBoard 补关键设计** | 属性三元分类、RPC 状态机、EntityRelation、告警状态机 |
| **保留现有消息总线** | Local/Redis/RocketMQ 三实现不动,消费端接入新引擎 |
| **渐进式迁移** | 新旧规则可并行运行,不要求一次性切换 |
| 原则 | 说明 |
| --------------------- | ----------------------------------- |
| **南向屏蔽,北向统一** | 协议层允许混乱,业务层必须干净(继承现有铁律) |
| **JetLinks 为主骨架** | 规则引擎、条件引擎、SPI 扩展采用 JetLinks 模式 |
| **ThingsBoard 补关键设计** | 属性三元分类、RPC 状态机、告警状态机 |
| **保留现有消息总线** | Local/Redis/RocketMQ 三实现不动,消费端接入新引擎 |
| **渐进式迁移** | 新旧规则可并行运行,不要求一次性切换 |
---
## 三、文档索引
| 编号 | 文档 | 内容 |
|------|------|------|
| 01 | [[01-整体架构设计]] | 模块划分、分层架构、依赖关系、部署拓扑 |
| 02 | [[02-子系统与设备归属模型]] | 租户→项目→子系统→设备层级、设备归属管理 |
| 03 | [[03-物模型规范v2]] | 属性三元分类、派生物模型、数据类型增强 |
| 04 | [[04-规则引擎方案]] | DAG 编排、SPI Provider、脚本节点、抖动抑制 |
| 05 | [[05-告警体系设计]] | 两级存储、状态机、传播机制、通知集成 |
| 06 | [[06-设备影子与RPC]] | Shared 属性同步、持久化 RPC 状态机、离线指令队列 |
| 07 | [[07-数据存储方案]] | 存储策略插件化、写入缓冲、Key 压缩、TDengine 优化 |
| 08 | [[08-协议与编解码扩展]] | 协议包热加载、Codec SPI、透传编解码 |
| 编号 | 文档 | 内容 |
| --- | ----------------- | ------------------------------- |
| 01 | [[01-整体架构设计]] | 模块划分、分层架构、依赖关系、部署拓扑 |
| 02 | [[02-子系统与设备归属模型]] | 租户→项目→子系统→设备层级、设备归属管理 |
| 03 | [[03-物模型规范v2]] | 属性三元分类、派生物模型、数据类型增强 |
| 04 | [[04-规则引擎方案]] | DAG 编排、SPI Provider、脚本节点、抖动抑制 |
| 05 | [[05-告警体系设计]] | 两级存储、状态机、传播机制、通知集成 |
| 06 | [[06-设备影子与RPC]] | Shared 属性同步、持久化 RPC 状态机、离线指令队列 |
| 07 | [[07-数据存储方案]] | 时序库双实现CTSDB/TDengine、写入缓冲、缓存优化 |
| 08 | [[08-协议与编解码扩展]] | 协议包热加载、Codec SPI、透传编解码 |
---
## 四、融合策略速查表
| 设计点 | 取自 | 说明 |
| ------ | -------------------------- | ------------------------------------------ |
| 规则引擎框架 | JetLinks SceneRule DAG | 串行/并行/分支编排 |
| 条件引擎 | JetLinks ReactorQL 思路 | 编译期过滤器,支持指标对比 |
| 脚本节点 | TB TBEL 思路 | Aviator/QLExpress 实现Action Provider 注册 |
| 抖动抑制 | JetLinks ShakeLimit | 7 参数模型,参数化配置 |
| 消息总线 | 保留现有 | Local/Redis/RocketMQ 三实现 |
| 子系统模型 | 简化层级:租户→项目→子系统→设备 | 设备 FK 归属子系统,本次只做设备-子系统关系 |
| 设备影子 | TB 属性三元分类 + RPC | Client/Server/Shared + 持久化状态机 |
| 告警系统 | JetLinks 两级存储 + TB 状态机 | Record + History + Create→Update→Clear→ACK |
| 物模型继承 | JetLinks 派生物模型 | 设备级覆盖产品定义 |
| 存储策略 | JetLinks 策略模式 | 产品级配置存储后端 |
| 协议扩展 | JetLinks 协议热加载 | JAR + ClassLoader 隔离 |
| SPI 扩展 | JetLinks Provider + TB 注解 | 动态注册 + 元数据自描述 |
| 可观测性 | JetLinks + TB Micrometer | 连接数/吞吐量/规则耗时 |
| 写入缓冲 | JetLinks PersistenceBuffer | 内存+文件双层批量写入 |
| 设计点 | 取自 | 说明 |
| -------- | -------------------------- | ------------------------------------------ |
| 规则引擎框架 | JetLinks SceneRule DAG | 串行/并行/分支编排 |
| 条件引擎 | JetLinks ReactorQL 思路 | 编译期过滤器,支持指标对比 |
| 脚本节点 | TB TBEL 思路 | Aviator/QLExpress 实现Action Provider 注册 |
| 抖动抑制 | JetLinks ShakeLimit | 7 参数模型,参数化配置 |
| 消息总线 | 保留现有 | Local/Redis/RocketMQ 三实现 |
| 子系统模型 | 简化层级:租户→项目→子系统→设备 | 设备 FK 归属子系统,本次只做设备-子系统关系 |
| 设备影子 | TB 属性三元分类 + RPC | Client/Server/Shared + 持久化状态机 |
| 告警系统 | JetLinks 两级存储 + TB 状态机 | Record + History + Create→Update→Clear→ACK |
| 物模型继承 | JetLinks 派生物模型 | 设备级覆盖产品定义 |
| 存储策略 | 已有双实现 + JetLinks 策略模式 | CTSDB/TDengine 可切换DAO 接口统一 |
| 协议扩展 | JetLinks 协议热加载 | JAR + ClassLoader 隔离 |
| SPI 扩展 | JetLinks Provider + TB 注解 | 动态注册 + 元数据自描述 |
| 可观测性 | JetLinks + TB Micrometer | 连接数/吞吐量/规则耗时 |
| 写入缓冲 | JetLinks PersistenceBuffer | 内存+文件双层批量写入 |
| Agent 协作 | TB 4.2 AI Node 思路 + ACP 协议 | IoT 内先实现 Agent 调用,后续抽取为平台 ai 模块 |
---
@@ -75,6 +76,7 @@
| Edge 同步 | TB | 无边缘计算需求 |
| 完整 87 节点 | TB | 20-25 个覆盖核心场景 |
| 全栈响应式 | JetLinks | 现有 Spring MVC 生态稳定,不做全量迁移 |
| TB AI Node 直连 LLM | TB 4.2 | 不直接对接模型,改为 ACP 协议调用内部 Agent 服务 |
| ReactorQL 原版 | JetLinks | 依赖 Project Reactor改用表达式引擎替代核心思路 |
| EntityRelation 图关系 | TB | 过度设计,改用简洁的 subsystem_id FK 归属 |

View File

@@ -31,7 +31,7 @@
│ Codec SPIAlink/JT808/Camera3D11/...
├─────────────────────────────────────────────────────┤
│ 存储层Storage
│ MySQL业务实体 │ TDengine(时序数据) │ Redis缓存
│ MySQL业务实体时序库 CTSDB/TDengine │ Redis缓存
└─────────────────────────────────────────────────────┘
```
@@ -81,7 +81,7 @@ viewsh-module-iot/
│ │ └── statistics/ # 统计(增强)
│ ├── dal/ # 数据访问
│ │ ├── mysql/ # 关系库
│ │ ├── tdengine/ # 时序库(策略模式增强
│ │ ├── tsdb/ # 时序库(CTSDB/TDengine 双实现
│ │ └── redis/ # 缓存
│ └── framework/ # 框架层
│ ├── observe/ # 【新增】可观测性Micrometer
@@ -120,21 +120,226 @@ server 额外依赖:
|------|------|------|
| **IotProjectDO** | `iot_project` | 项目(架构预留,本次不开放 API |
| **IotSubsystemDO** | `iot_subsystem` | 子系统(设备归属单元) |
| **IotAlarmHistoryDO** | TDengine `alarm_history` | 告警时序归档 |
| **IotAlarmHistoryDO** | 时序库 `alarm_history` | 告警时序归档CTSDB/TDengine |
| **IotDeviceRpcDO** | `iot_device_rpc` | 持久化 RPC 指令 |
| **IotRuleChainDO** | `iot_rule_chain` | 规则链DAG 图定义) |
| **IotRuleNodeDO** | `iot_rule_node` | 规则节点 |
---
## 五、关键技术选型变更
## 五、端到端业务链路
从设备上报到最终动作的完整链路,覆盖 v2.0 所有设计点:
![[iot-e2e-business-flow.png]]
```
═══════════════════════════════════════════════════════════════════════════════
设备接入层Gateway
═══════════════════════════════════════════════════════════════════════════════
物理设备 网关
┌──────┐ MQTT/HTTP/TCP ┌─────────────────────────────────────────┐
│ 传感器 │ ──── 原始报文 ────→ │ Codec SPI │
│ 控制器 │ │ ├── ALink Codec阿里协议
│ 摄像头 │ │ ├── JT808 Codec定位协议
│ 工牌 │ │ └── Camera3D11 Codec摄像头协议
└──────┘ │ ↓ 解码 │
│ IotDeviceMessage统一消息模型
│ {deviceKey, productKey, type, │
│ identifier, data, timestamp} │
└──────────────┬──────────────────────────┘
═══════════════════════════════════════════════╪═══════════════════════════════
消息总线Message Bus
═══════════════════════════════════════════════╪═══════════════════════════════
┌───────────────────────────────┤
│ 三种实现按部署模式选择 │
│ ├── LocalMessageBus单机
│ ├── RedisStreamMessageBus │
│ └── RocketMQMessageBus │
└───────────────────────────────┤
═══════════════════════════════════════════════════════════════════════════════
核心服务层Core Services
═══════════════════════════════════════════════════════════════════════════════
IotDeviceMessageSubscriber统一消息消费者
├──→ ① 设备状态更新
│ └── 在线/离线状态 → Redis 标记
│ └── 上线时触发:
│ ├── Shared 属性同步pending → 下发) ← 06-设备影子
│ └── Pending RPC 补发(限速 5 条/秒) ← 06-设备影子
├──→ ② 属性三元分类写入 ← 03-物模型v2
│ ├── CLIENT 属性 → Redis iot:device_property:{id}:client
│ ├── SERVER 属性 → Redis iot:device_property:{id}:server规则引擎写
│ └── SHARED 属性 → Redis iot:device_property:{id}:shared + MySQL
├──→ ③ 时序数据持久化 ← 07-数据存储
│ └── PersistenceBuffer内存+文件双层)
│ → 批量写入时序库CTSDB 异步批量 / TDengine PersistenceBuffer
├──→ ④ 派生物模型合并 ← 03-物模型v2
│ └── 设备级覆盖产品定义derive_metadata JSON
└──→ ⑤ 规则引擎触发 ← 04-规则引擎
═══════════════════════════════════════════════════════════════════════════════
规则引擎层Rule Engine
═══════════════════════════════════════════════════════════════════════════════
IotRuleEngineMessageHandler统一入口
│ 按 subsystemId + productId + deviceId 匹配规则链(全量缓存)
│ 同时匹配 subsystemId=NULL 的全局规则链
↓ ← 链级 try-catch 隔离,单链异常不影响其他链(工程评审决议 #3
┌─────────────────────────────────────────────────────────────────┐
│ RuleChain DAG 执行 │
│ │
│ ┌─────────┐ │
│ │ Trigger │ 设备属性上报 / 设备事件 / 设备上下线 / 定时 / 手动 │
│ └────┬────┘ │
│ ↓ │
│ ┌─────────┐ │
│ │ Enrich │ 数据富化(可选) │
│ │ │ ├── 读取设备最新属性Redis
│ │ │ ├── 读取属性历史(时序库) │
│ │ │ └── 读取关联设备/资产信息 │
│ │ │ 结果注入 → RuleContext.metadata │
│ └────┬────┘ │
│ ↓ │
│ ┌──────────┐ │
│ │ Condition │ 条件评估 │
│ │ │ ├── ExpressionAviator 表达式 │
│ │ │ │ temperature > 40 && humidity < 20 │
│ │ │ ├── Script脚本条件复杂逻辑
│ │ │ ├── TimeRange时间范围过滤 │
│ │ │ └── DeviceState在线/离线判断 │
│ └────┬────┘ │
│ ↓ │
│ ┌──────────────┐ │
│ │ ShakeLimit │ 抖动抑制(可选) │
│ │ │ 10 秒窗口内连续 3 次触发才真正执行 │
│ │ │ ├── 固定窗口 / 滚动窗口 │
│ │ │ ├── 取第一条 / 取最后一条 │
│ │ │ └── 连续模式:不满足则重置计数 │
│ └────┬────┘ │
│ ↓ │
│ ┌─────────────┐ │
│ │ Branch │ 分支执行 │
│ │ ├── 分支A │ executeAnyway=false → if/else-if 语义 │
│ │ │ 条件组 │ executeAnyway=true → 重叠触发 │
│ │ │ ↓ │ │
│ │ │ Actions │ │
│ │ ├── 分支B │ │
│ │ │ ... │ │
│ └──┴──┬──┴────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Actions动作层 │ │
│ │ │ │
│ │ ┌── 设备控制 ──────────────────────────────────────────┐ │ │
│ │ │ device_property_set 设置设备属性Shared 属性下发) │ │ │
│ │ │ device_service_invoke 调用设备服务(持久化 RPC │ │ │
│ │ │ ↓ │ │ │
│ │ │ RPC 状态机QUEUED → SENT → SUCCESS │ │ │
│ │ │ 设备离线 → 持久化等待 → 上线补发 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌── 告警处理 ──────────────────────────────────────────┐ │ │
│ │ │ alarm_trigger 触发告警ACTIVE 状态) │ │ │
│ │ │ ├── upsert AlarmRecordMySQL 幂等) │ │ │
│ │ │ ├── append AlarmHistory时序库 │ │ │
│ │ │ └── 告警传播(沿子系统层级向上) │ │ │
│ │ │ alarm_clear 清除告警CLEARED 状态) │ │ │
│ │ │ 竞态保护Redis 缓存 + 分布式锁 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌── 数据转发 ──────────────────────────────────────────┐ │ │
│ │ │ http_push HTTP 推送Webhook │ │ │
│ │ │ mq_push MQ 推送RocketMQ/Kafka/RabbitMQ │ │ │
│ │ │ redis_push Redis 推送 │ │ │
│ │ │ tcp_push TCP 推送 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌── 通知 ──────────────────────────────────────────────┐ │ │
│ │ │ notify 短信 / 邮件 / 站内信 / 企业微信 / 钉钉 │ │ │
│ │ │ 模板变量:${alarm.name} ${device.name} ... │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌── 数据处理 ──────────────────────────────────────────┐ │ │
│ │ │ script Aviator 脚本(数据转换、单位换算、字段映射) │ │ │
│ │ │ 沙箱:超时 3s + 循环上限 1000 + 黑名单 │ │ │
│ │ │ enrich 数据富化(读取属性/历史/关联设备) │ │ │
│ │ │ delay 延迟执行 │ │ │
│ │ │ log 日志记录 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌── Agent 协作(预留)─────────────────────────────────┐ │ │
│ │ │ agent_request ACP 协议调用内部 Agent 服务 │ │ │
│ │ │ ├── 异常检测 Agent │ │ │
│ │ │ ├── 保洁质检 Agent │ │ │
│ │ │ ├── 能耗分析 Agent │ │ │
│ │ │ └── 结果注入 metadata → 后续节点引用 │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
═══════════════════════════════════════════════════════════════════════════════
存储层Storage
═══════════════════════════════════════════════════════════════════════════════
┌─────────────────────────────────────────────────────────────────────┐
│ MySQL │ 时序库(CTSDB/TDengine)│ Redis │
│ ├── iot_subsystem │ ├── device_message │ ├── 属性缓存│
│ ├── iot_device+subsystem_id │ ├── product_property │ │ (三元分类)│
│ ├── iot_rule_chain/node/link │ ├── alarm_history │ ├── 设备在线│
│ ├── iot_alarm_record │ ├── rule_debug_log │ ├── 告警缓存│
│ ├── iot_device_rpc │ └── 通过 DAO 接口 │ ├── 规则缓存│
│ ├── iot_agent_service │ 双实现可切换 │ └── RPC 超时│
│ └── iot_project预留 │ │ │
└─────────────────────────────────────────────────────────────────────┘
```
### 典型场景走读
**场景:温度传感器上报 → 异常检测 → 告警 → 通知**
```
1. 传感器通过 MQTT 上报 {"temperature": 52, "humidity": 18}
2. Gateway ALink Codec 解码 → IotDeviceMessage
3. 消息总线投递 → IotDeviceMessageSubscriber 消费
4. 属性写入 RedisCLIENT scope+ 时序库存储
5. 规则引擎匹配:该设备属于"安防子系统",命中 2 条规则链
6. 规则链 A场景联动
├── Trigger: device_property温度上报
├── Enrich: 读取近 1 小时均值温度 → metadata.avgTemp = 38
├── Condition: temperature > 40 → true
├── ShakeLimit: 30 秒内第 3 次触发 → 通过
├── AgentRequest: anomaly-detector → {"level":"critical","reason":"偏离均值 14°C"}
├── AlarmTrigger: 创建 CRITICAL 告警upsert Record + append History
├── 告警传播: 安防子系统 → 1 楼 → 楼宇 A
└── Notify: 短信通知安防负责人
7. 规则链 B数据转发
├── Trigger: device_property
└── HttpPush: 推送到第三方监控平台
```
---
## 六、关键技术选型变更
| 领域 | v1.0(现有) | v2.0(目标) |
|------|-------------|-------------|
| 规则引擎条件 | SpEL 字符串表达式 | Aviator 表达式引擎(编译缓存+类型安全) |
| 脚本执行 | 无 | Aviator/QLExpress轻量+沙箱) |
| 规则编排 | 平铺动作列表 | DAGRuleModel参考 JetLinks |
| 告警存储 | 单表 MySQL | MySQLAlarmRecord 当前)+ TDengineAlarmHistory 时序 |
| 告警存储 | 单表 MySQL | MySQLAlarmRecord 当前)+ 时序库AlarmHistoryCTSDB/TDengine |
| 设备属性 | Redis Hash无分类 | Redis Hash + AttributeScope 三元分类 |
| RPC | 单次下发,丢失不重试 | 持久化 RPC + 状态机QUEUED→SENT→DELIVERED→SUCCESS |
| 设备组织 | groupIds JSON 数组 | 子系统归属subsystem_id FK+ 分组保留并存 |

View File

@@ -4,6 +4,8 @@
## 一、组织层级架构
![[diagram-subsystem-er.png]]
```
租户Tenant ← 已有TenantBaseDO
└── 项目Project ← 架构预留,本次不实现

View File

@@ -6,6 +6,8 @@
## 一、属性三元分类(借鉴 ThingsBoard
![[diagram-attribute-classification.png]]
### 1.1 设计动机
现有系统所有属性不区分来源和可见性,设备上报的状态、平台计算的指标、下发给设备的配置混在一起。引入 **AttributeScope** 三元分类:
@@ -40,7 +42,7 @@ iot:device_property:{deviceId}:server → {identifier: value}
iot:device_property:{deviceId}:shared → {identifier: value}
```
**TDengine 不变**:时序数据仍按 `product_property_{productId}` 超级表存储scope 作为普通列TINYINT标记。
**时序库不变**:时序数据仍按 `product_property_{productId}` 存储CTSDB 为 measurementTDengine 为超级表scope 作为字段标记。
### 1.4 物模型属性定义扩展
@@ -108,9 +110,9 @@ List<ThingModelDO> getEffectiveThingModel(Long deviceId) {
### 3.1 新增 TIMESTAMP 类型
| 物模型类型 | TDengine 类型 | 说明 |
|-----------|-------------|------|
| `timestamp` | TIMESTAMP | 毫秒级时间戳(新增) |
| 物模型类型 | CTSDB (InfluxDB) | TDengine | 说明 |
|-----------|-----------------|----------|------|
| `timestamp` | TIMESTAMP | TIMESTAMP | 毫秒级时间戳(新增) |
### 3.2 struct/array 支持嵌套校验

View File

@@ -12,6 +12,8 @@
## 二、核心架构
![[diagram-rule-action-tree.png]]
```
设备消息IotDeviceMessage
↓ 消息总线消费
@@ -139,6 +141,7 @@ interface ConditionEvaluator {
| `delay` | DelayAction | **新增** | 延迟执行 |
| `enrich` | EnrichAction | **新增** | 数据富化(读取属性/时序) |
| `log` | LogAction | **新增** | 日志记录 |
| `agent_request` | AgentRequestAction | **预留** | 调用外部 Agent 服务ACP 协议) |
**条件评估器Condition**
@@ -148,6 +151,7 @@ interface ConditionEvaluator {
| `script` | ScriptCondition | 脚本条件(复杂逻辑) |
| `time_range` | TimeRangeCondition | 时间范围(当日时间/日期区间) |
| `device_state` | DeviceStateCondition | 设备在线/离线状态 |
| `agent_judge` | AgentConditionEvaluator | **预留** 调用 Agent 做复杂条件判断 |
---
@@ -248,7 +252,7 @@ class ShakeLimitConfig {
| 富化类型 | 配置 | 说明 |
|---------|------|------|
| `device_property` | deviceId + identifiers | 读取设备最新属性(从 Redis |
| `device_history` | deviceId + identifier + timeRange | 读取属性历史(从 TDengine |
| `device_history` | deviceId + identifier + timeRange | 读取属性历史(从时序库 |
| `related_device` | relationType + identifiers | 读取关联设备属性 |
| `asset_info` | assetId 或 relationType | 读取所属资产信息 |
@@ -306,7 +310,7 @@ class ShakeLimitConfig {
规则链 `debug_mode=true` 时:
- 每个节点执行前后记录 `RuleContext` 快照
- 写入 TDengine `rule_debug_log`
- 写入时序库 `rule_debug_log`
- 前端可回放执行路径,查看每步的输入/输出/耗时
- 生产环境建议关闭(性能开销)
@@ -372,3 +376,319 @@ Map<String, List<Long>> ruleChainIndex; // key → ruleChainIds
```
不使用延迟加载(@Cacheable),避免首次消息处理的延迟和 TTL 窗口期数据不一致。
---
## 十二、Agent 协作节点
> 参考ThingsBoard 4.2 AI Request Node 思路 + ACP 智能体通信协议
> 本阶段在 IoT 模块内实现 Agent 调用能力,后续成熟后再抽取为平台级 `viewsh-module-ai` 模块。
### 12.1 设计背景
TB 4.2 新增了 AI Request 规则节点,直接对接 LLM APIOpenAI/Gemini 等)。我们的场景不同:不直接对接模型,而是通过 **ACPAgent Communication Protocol协议** 调用内部已有的 Agent 服务(数字员工),由 Agent 内部决定调用哪个模型、如何处理。
```
TB 方式: 规则节点 ──→ LLM APIOpenAI/Gemini
我们的方式:规则节点 ──→ AgentCallService ──→ AcpClient ──→ Agent 服务(数字员工)
└── 全部在 IoT 模块内,后续抽取为独立 ai 模块
```
### 12.2 模块内部结构
Agent 调用相关代码放在 `viewsh-module-iot-biz``agent` 包下,与规则引擎同模块部署:
```
viewsh-module-iot-biz/
└── src/main/java/.../iot/
├── rule/ ← 规则引擎(已有)
│ └── action/
│ └── AgentRequestAction.java ← 规则节点
└── agent/ ← Agent 调用(新增)
├── dal/
│ ├── IotAgentServiceDO.java ← 实体
│ └── IotAgentServiceMapper.java
├── service/
│ ├── IotAgentServiceService.java ← Agent 注册表 CRUD
│ └── IotAgentCallService.java ← 调用编排(组装请求 + 调 ACP + 解析响应)
├── client/
│ ├── AcpClient.java ← ACP 协议客户端接口
│ └── AcpHttpClient.java ← HTTPS 实现(先做同步,后续加 WSS/SSE
└── controller/
└── IotAgentServiceController.java ← 管理后台 CRUD
```
**后续抽取路径**:当 Ops 等其他模块也需要调 Agent 时,将 `agent/` 包整体迁移到 `viewsh-module-ai`IoT 侧改为 Feign 调用。接口不变,只是调用方式从本地变远程。
### 12.3 调用架构
```
┌───────────────────────────────────────────────────────┐
│ viewsh-module-iot-biz │
│ │
│ 规则引擎 Agent 调用 │
│ ┌─────────────────────┐ ┌──────────────────┐ │
│ │ AgentRequestAction │──调用──→│ IotAgentCallService│ │
│ │ (ActionProvider) │ │ ├── 查询 Agent 配置│ │
│ └─────────────────────┘ │ ├── 组装 ACP 请求 │ │
│ │ └── 解析响应 │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌────────↓─────────┐ │
│ │ AcpClient │ │
│ │ HTTPS 同步调用 │ │
│ └────────┬─────────┘ │
│ │ │
└──────────────────────────────────────────┼─────────────┘
│ ACP 协议
┌─────────↓──────────┐
│ 外部 Agent 服务 │
│ ├── 异常检测 Agent │
│ ├── 保洁质检 Agent │
│ ├── 能耗分析 Agent │
│ └── 工单派发 Agent │
└────────────────────┘
```
### 12.4 数据模型
```sql
CREATE TABLE iot_agent_service (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(128) NOT NULL COMMENT 'Agent 服务名称',
code VARCHAR(64) NOT NULL COMMENT 'Agent 编码(业务标识)',
description TEXT COMMENT 'Agent 能力描述',
-- ACP 连接
acp_endpoint VARCHAR(512) NOT NULL COMMENT 'ACP 接入点地址',
agent_aid VARCHAR(256) NOT NULL COMMENT '目标 Agent AID身份标识',
auth_mode VARCHAR(32) DEFAULT 'free' COMMENT '认证方式free/token/pki',
auth_config JSON COMMENT '认证配置',
-- 能力声明
input_format JSON COMMENT '输入格式说明JSON Schema可选',
output_format JSON COMMENT '输出格式说明JSON Schema可选',
capabilities JSON COMMENT '能力标签 ["anomaly_detection","time_series"]',
-- 调用约束
timeout_seconds INT DEFAULT 60 COMMENT '默认超时(秒)',
max_concurrency INT DEFAULT 10 COMMENT '最大并发调用数',
-- 通用字段
status TINYINT DEFAULT 1,
tenant_id BIGINT NOT NULL,
creator VARCHAR(64),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
updater VARCHAR(64),
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted BIT DEFAULT 0,
UNIQUE KEY uk_code_tenant (code, tenant_id, deleted)
) COMMENT 'Agent 服务注册表(后续迁移至 ai 模块)';
```
### 12.5 核心实现
#### AcpClientACP 协议客户端)
```java
/**
* ACP 协议客户端 —— 封装与 Agent 的通信
* 本阶段只实现 HTTPS 同步模式,后续按需扩展 WSS/SSE
*/
public interface AcpClient {
/**
* 同步调用:建 Session → 发消息 → 等响应 → 关 Session
*/
AcpResponse call(AcpRequest request);
}
@Data @Builder
class AcpRequest {
private String endpoint; // ACP 接入点地址
private String targetAid; // 目标 Agent AID
private String authMode;
private String authConfig;
private JsonNode payload; // 业务数据
private Map<String, String> metadata; // 附加上下文
private int timeoutSeconds;
}
@Data
class AcpResponse {
private boolean success;
private String sessionId;
private JsonNode data; // Agent 返回结果
private String errorMessage;
private long latencyMs;
}
```
#### IotAgentCallService调用编排
```java
@Service
public class IotAgentCallService {
private final IotAgentServiceService agentServiceService;
private final AcpClient acpClient;
/**
* 调用 Agent 服务
* @param agentCode Agent 编码
* @param payload 业务数据
* @param context 调用上下文(来源、设备信息等)
* @param timeoutOverride 超时覆盖null 则使用 Agent 默认超时)
*/
public AgentCallResult call(String agentCode, JsonNode payload,
Map<String, String> context, Integer timeoutOverride) {
// 1. 查询 Agent 配置
IotAgentServiceDO agent = agentServiceService.getByCode(agentCode);
if (agent == null || agent.getStatus() != 1) {
return AgentCallResult.failure("Agent not found or disabled: " + agentCode);
}
// 2. ACP 调用
AcpRequest request = AcpRequest.builder()
.endpoint(agent.getAcpEndpoint())
.targetAid(agent.getAgentAid())
.authMode(agent.getAuthMode())
.authConfig(agent.getAuthConfig())
.payload(payload)
.metadata(context)
.timeoutSeconds(timeoutOverride != null ? timeoutOverride : agent.getTimeoutSeconds())
.build();
AcpResponse response = acpClient.call(request);
// 3. 封装结果
return AgentCallResult.of(response);
}
}
```
#### AgentRequestAction规则引擎 SPI 节点)
```java
@Component
public class AgentRequestAction implements ActionProvider {
private final IotAgentCallService agentCallService;
@Override
public String getType() { return "agent_request"; }
@Override
public ActionResult execute(RuleContext ctx, JsonNode config) {
// 1. 解析配置
String agentCode = config.get("agentCode").asText();
JsonNode payload = resolvePayload(config.get("payload"), ctx);
Integer timeout = config.has("timeoutSeconds")
? config.get("timeoutSeconds").asInt() : null;
// 2. 构建调用上下文
Map<String, String> context = Map.of(
"source", "iot-rule-engine",
"deviceId", String.valueOf(ctx.getDeviceId()),
"productId", String.valueOf(ctx.getProductId()),
"subsystemId", String.valueOf(ctx.getSubsystemId()),
"ruleChainId", String.valueOf(ctx.getRuleChainId())
);
// 3. 调用
AgentCallResult result = agentCallService.call(agentCode, payload, context, timeout);
// 4. 结果注入
if (result.isSuccess()) {
String outputTo = config.path("outputTo").asText("metadata.agentResult");
injectResult(ctx, outputTo, result.getData());
return ActionResult.success();
}
return ActionResult.failure(result.getErrorMessage());
}
}
```
### 12.6 模板变量解析
规则引擎统一提供模板解析能力Agent 节点与脚本节点共用),借鉴 TB 的 `TbNodeUtils.processPattern()`
| 模板语法 | 含义 | 示例 |
|---------|------|------|
| `$[identifier]` | 消息数据字段 | `$[temperature]` → 设备上报的温度值 |
| `$[*]` | 整个消息体 JSON | 传递全部上报数据 |
| `${key}` | 元数据字段 | `${deviceName}` → 设备名称 |
### 12.7 节点配置格式
```json
{
"type": "agent_request",
"config": {
"agentCode": "anomaly-detector",
"payload": {
"taskType": "anomaly_detection",
"deviceName": "${deviceName}",
"subsystem": "${subsystemCode}",
"data": {
"temperature": "$[temperature]",
"humidity": "$[humidity]"
},
"context": {
"recentAvgTemp": "${avgTemp}",
"deviceModel": "${productName}"
}
},
"timeoutSeconds": 60,
"outputTo": "metadata.agentResult"
}
}
```
### 12.8 Agent 结果在规则链中的使用
Agent 返回的结果注入 `metadata.agentResult` 后,后续节点可直接引用:
```
设备上报 → [Enrich: 读取近1小时均值]
→ [AgentRequest: anomaly-detector]
→ [Expression: metadata.agentResult.level == 'critical']
├── true → [AlarmTrigger: 严重告警]
└── false → [Expression: metadata.agentResult.level == 'warning']
├── true → [Notify: 发送预警通知]
└── false → 结束
```
### 12.9 典型业务场景
| 场景 | Agent | 触发方式 | Agent 输入 | Agent 输出 | 后续动作 |
| ------ | ---------------------- | ------- | --------- | ----------------- | --------- |
| 设备异常检测 | anomaly-detector | 属性上报 | 温湿度+历史均值 | level + reason | 告警/通知 |
| 保洁质量验收 | clean-quality-checker | 保洁完成事件 | 区域图片+清洁标准 | pass/fail + score | 标记完成/派返工单 |
| 能耗优化 | energy-advisor | 定时(每小时) | 用电数据+设备列表 | 调度建议 | 设备控制指令 |
| 预测性维护 | predictive-maintenance | 定时(每天) | 30 天运行参数 | 剩余寿命+劣化趋势 | 提前告警 |
### 12.10 与 TB AI Node 的差异
| 维度 | TB 4.2 AI Node | 我们的 Agent 节点 |
|------|---------------|-----------------|
| 对接对象 | LLM APIOpenAI 等 9 个 Provider | 内部 Agent 服务(数字员工) |
| 协议 | 各厂商私有 HTTP API | ACP 统一协议 |
| 抽象层 | LangChain4j SDK | AcpClientIoT 内置) |
| 管理对象 | AI Modelendpoint + apiKey | Agent ServiceACP 地址 + 能力声明) |
| Prompt 管理 | 节点配置中写 SystemPrompt/UserPrompt | 由 Agent 内部管理,规则引擎只传业务 Payload |
### 12.11 后续演进路径
```
阶段 1本次 阶段 2其他模块需要时
┌─────────────────────┐ ┌──────────────────────────────┐
│ iot-biz/agent/ │ │ viewsh-module-ai独立模块
│ ├── dal/ │ 迁移 │ ├── ai-api/Feign 接口) │
│ ├── service/ │ ─────→ │ ├── ai-biz/(实现) │
│ ├── client/ │ │ │ ├── Agent 注册表 │
│ └── controller/ │ │ │ ├── AcpClient │
│ │ │ │ └── 调用日志审计 │
│ iot_agent_service 表│ 改前缀 │ └── ai_agent_service 表 │
└─────────────────────┘ └──────────────────────────────┘
↑ Feign 调用
┌─────┼─────────┐
IoT Ops 其他模块
```
**迁移原则**:接口不变,只改调用方式。`IotAgentCallService` 的方法签名保持一致IoT 侧改为通过 Feign 代理调用即可。

View File

@@ -6,6 +6,8 @@
## 一、告警状态机5 状态)
![[diagram-alarm-state-machine.png]]
```
┌──── 持续触发 ────┐
↓ │
@@ -72,7 +74,7 @@ CREATE TABLE iot_alarm_record (
**幂等策略**(工程评审决议):保持 BIGINT 自增 ID与全局一致通过 `record_key = MD5(deviceId + "-" + alarmConfigId)` 唯一索引实现幂等。写入时使用 `INSERT ... ON DUPLICATE KEY UPDATE`
### 2.2 AlarmHistoryTDengine,时序归档
### 2.2 AlarmHistory时序库CTSDB/TDengine
每次告警状态变化都追加一条记录,用于趋势分析和审计。

View File

@@ -12,6 +12,10 @@
---
![[diagram-rpc-state-machine.png]]
---
## 二、Shared 属性同步机制
### 2.1 流程

View File

@@ -1,187 +1,199 @@
# 07-数据存储方案
> 存储策略插件化 + 写入缓冲 + TDengine 优化
> 基于 aiot-iot-data-storage 已有实现演进时序数据库可切换CTSDB/TDengine + 写入缓冲 + 缓存优化
---
## 一、存储策略插件化(借鉴 JetLinks
## 一、时序数据库现状
### 1.1 设计动机
![[diagram-tsdb-architecture.png]]
现有系统所有产品设备数据统一存 TDengine无法针对不同场景选择最优存储。引入策略模式支持产品级配置。
![[diagram-write-buffer.png]]
### 1.2 策略接口
### 1.1 已有双实现aiot-iot-data-storage
`aiot-iot-data-storage` 项目已实现 **CTSDB 和 TDengine 双后端**,通过配置切换:
```yaml
viewsh.iot.tsdb.type: ctsdb # 或 "tdengine"
```
```
TsDbAutoConfiguration策略选择器
├── tsdb.type = ctsdb → CTSDB DAO 实现InfluxDB 协议)
└── tsdb.type = tdengine → TDengine DAO 实现JDBC
```
### 1.2 核心 DAO 接口(已定义)
```java
interface ThingsDataStorageStrategy {
String getId(); // "tdengine-column" / "tdengine-row"
int getOrder(); // 优先级
void saveProperties(Long deviceId, Map<String, Object> properties, LocalDateTime reportTime);
List<PropertyRecord> queryHistory(Long deviceId, String identifier, TimeRange range, int limit);
Map<String, Object> getLatest(Long deviceId);
void defineTable(Long productId, List<ThingModelDO> thingModels);
void alterTable(Long productId, List<ThingModelDO> oldModels, List<ThingModelDO> newModels);
// 设备属性时序存储
interface IotTsDbDevicePropertyDao {
List<TsDbTableField> getTableFields(Long productId);
void createPropertyTable(Long productId, List<TsDbTableField> fields);
void alterPropertyTable(Long productId, List<TsDbTableField> oldFields, List<TsDbTableField> newFields);
void insert(IotDeviceDO device, Map<String, Object> properties, Long reportTime);
List<Map<String, Object>> selectHistory(IotDevicePropertyHistoryReqVO reqVO, Long productId);
}
// 设备消息日志存储
interface IotTsDbDeviceMessageDao {
void createSchema();
boolean schemaExists();
void insert(IotDeviceMessageDO message);
PageResult<IotDeviceMessageDO> selectPage(PageParam page, IotDeviceMessagePageReqVO reqVO);
Long selectCountByCreateTime(LocalDateTime createTime);
List<IotDeviceMessageDO> selectListByRequestIdsAndReply(Long deviceId, List<String> requestIds, Boolean reply);
List<HourlyMessageCountDTO> selectDeviceMessageCountGroupByDate(LocalDateTime startTime, LocalDateTime endTime);
}
```
### 1.3 内置策略
### 1.3 CTSDB 实现(基于 InfluxDB 协议)
| 策略 ID | 说明 | 适用场景 |
| ----------------- | --------------- | -------------- |
| `tdengine-column` | 列模式(现有方案),每属性一列 | 属性固定、查询频繁 |
| `tdengine-row` | 行模式,每属性一行 | 属性动态变化、物模型频繁变更 |
| 维度 | 说明 |
|------|------|
| SDK | `influxdb-client-java` |
| 协议 | HTTP REST API`http://host:8086` |
| 认证 | Token 方式 |
| 查询语言 | Flux |
| 建表 | Schema-on-write自动无需预建 |
| 写入 | 异步批量1000 条/批 + 1s 自动刷新 + 3 次重试 |
| 防注入 | `FluxQuerySanitizer` 转义特殊字符 |
| 多租户 | Flux 查询中 `tenant_id` 字段过滤 |
### 1.4 列模式 vs 行模式
**列模式(现有,保留为默认)**
```sql
CREATE STABLE product_property_{productId} (
ts TIMESTAMP, report_time TIMESTAMP,
temperature INT, humidity FLOAT, status TINYINT -- 每属性一列
) TAGS (device_id BIGINT);
```
- 优点:查询快,单行包含所有属性
- 缺点:物模型变更需 ALTER TABLETDengine 不支持缩短字段
**行模式(新增)**
```sql
CREATE STABLE product_property_row_{productId} (
ts TIMESTAMP, report_time TIMESTAMP,
identifier NCHAR(64), -- 属性标识符
value_int INT, -- 整型值
value_float FLOAT, -- 浮点值
value_double DOUBLE, -- 双精度值
value_bool TINYINT, -- 布尔值
value_str NCHAR(1024), -- 字符串值
scope TINYINT -- 属性域1=CLIENT, 2=SERVER, 3=SHARED
) TAGS (device_id BIGINT);
```
- 优点:物模型变更无需 DDL属性可任意扩展
- 缺点:查询需按 identifier 过滤,同时查多属性需 PIVOT
### 1.5 产品级配置
```sql
ALTER TABLE iot_product ADD COLUMN store_policy VARCHAR(32) DEFAULT 'tdengine-column';
```yaml
# CTSDB 连接配置
viewsh.iot.tsdb.ctsdb:
url: http://${CTSDB_HOST:localhost}:${CTSDB_PORT:8086}
token: ${CTSDB_TOKEN:}
org: ${CTSDB_ORG:aiot}
bucket: ${CTSDB_BUCKET:aiot_platform}
```
`ThingsDataStorageRouter` 按 productId 路由到对应策略:
```java
ThingsDataStorageStrategy getStrategy(Long productId) {
String policy = productService.getProduct(productId).getStorePolicy();
return strategyMap.get(policy); // 策略注册表
}
### 1.4 TDengine 实现
| 维度 | 说明 |
|------|------|
| SDK | `taos-jdbcdriver` 3.7.9 |
| 协议 | JDBCWebSocket / REST |
| 认证 | 用户名密码 |
| 查询语言 | TDengine SQL |
| 建表 | 需要预建超级表CREATE STABLE |
| 写入 | 同步写入(通过 Mapper XML |
| 多租户 | `@InterceptorIgnore` 绕过 JSqlParser |
### 1.5 数据模型(两套实现共用)
```
measurement/超级表: device_message
├── TAG: device_id
└── FIELDS: id, report_time, tenant_id, server_id, upstream,
reply, identifier, request_id, method, params, data, code, msg
measurement/超级表: product_property_{productId}
├── TAG: device_id
└── FIELDS: report_time + 动态属性列(从物模型生成)
```
### 1.6 物模型类型映射
| 物模型类型 | CTSDB (InfluxDB) | TDengine |
|---------|-----------------|----------|
| INT | INT | INT |
| FLOAT | FLOAT | FLOAT |
| DOUBLE | DOUBLE | DOUBLE |
| ENUM | TINYINT | TINYINT |
| BOOL | BOOL | TINYINT |
| TEXT | STRING | VARCHAR(1024) |
| DATE | TIMESTAMP | TIMESTAMP |
| STRUCT | STRING (JSON) | VARCHAR(1024) |
| ARRAY | STRING (JSON) | VARCHAR(1024) |
---
## 二、写入缓冲(借鉴 JetLinks PersistenceBuffer
## 二、v2.0 存储扩展
### 2.1 设计动机
### 2.1 新增时序表
设备高频上报时(如 1Hz × 1000 台),每条消息直接写 TDengine 会产生大量小批次 INSERT。引入写入缓冲批量合并后写入。
在现有 `device_message``product_property_*` 基础上v2.0 需新增两张时序表,同样遵循 DAO 接口双实现模式:
### 2.2 PersistenceBuffer 设计
| 时序表 | 用途 | TAG | 关键字段 |
|--------|------|-----|---------|
| `alarm_history` | 告警状态变化归档 | device_id | alarm_record_id, severity, state, trigger_data |
| `rule_debug_log` | 规则调试日志 | device_id | rule_chain_id, node_id, input_data, output_data, duration_ms |
需新增对应 DAO 接口:
```java
// 告警历史时序存储
interface IotTsDbAlarmHistoryDao {
void insert(AlarmHistoryDO history);
List<AlarmHistoryDO> selectByRecordId(Long alarmRecordId, TimeRange range);
List<AlarmHistoryDO> selectByDeviceId(Long deviceId, TimeRange range);
}
// 规则调试日志时序存储
interface IotTsDbRuleDebugLogDao {
void insert(RuleDebugLogDO log);
List<RuleDebugLogDO> selectByChainId(Long ruleChainId, TimeRange range);
}
```
### 2.2 写入缓冲
CTSDB 侧已有 InfluxDB WriteApi 的异步批量写入1000 条/批 + 1s 刷新TDengine 侧需补齐 PersistenceBuffer
```java
class PersistenceBuffer<T> {
// 配置
int bufferSize = 200; // 数量触发阈值
Duration bufferTimeout = Duration.ofSeconds(3); // 时间触发阈值
int parallelism = 4; // 并行写出线程数
int bufferSize; // 数量触发阈值
Duration bufferTimeout; // 时间触发阈值
int parallelism; // 并行写出线程数
BlockingQueue<T> queue; // 内存队列
ScheduledExecutorService timer;// 定时刷新
// 内部结构
BlockingQueue<T> queue; // 内存队列
ScheduledExecutorService timer; // 定时刷新
// 写入方法
void write(T item) {
queue.add(item);
if (queue.size() >= bufferSize) {
flush();
}
if (queue.size() >= bufferSize) { flush(); }
}
// 批量刷出
void flush() {
List<T> batch = new ArrayList<>();
queue.drainTo(batch, bufferSize);
if (!batch.isEmpty()) {
batchWriter.accept(batch); // 回调:批量写入 TDengine
}
if (!batch.isEmpty()) { batchWriter.accept(batch); }
}
}
```
### 2.3 应用点
| 缓冲实例 | 写入目标 | bufferSize | timeout | 备注 |
|---------|---------|-----------|---------|------|
| 属性写入缓冲 | `product_property_*` | 200 | 3s | CTSDB 已内置批量TDengine 需要 |
| 消息日志缓冲 | `device_message` | 500 | 5s | 同上 |
| 告警历史缓冲 | `alarm_history` | 100 | 3s | 新增 |
| 规则调试日志缓冲 | `rule_debug_log` | 100 | 5s | 新增 |
| 缓冲实例 | 写入目标 | bufferSize | timeout |
|---------|---------|-----------|---------|
| 属性写入缓冲 | TDengine `product_property_*` | 200 | 3s |
| 消息日志缓冲 | TDengine `device_message` | 500 | 5s |
| 告警历史缓冲 | TDengine `alarm_history` | 100 | 3s |
| 规则调试日志缓冲 | TDengine `rule_debug_log` | 100 | 5s |
### 2.3 数据保留策略
| 时序表 | 建议保留期 | CTSDB 实现 | TDengine 实现 |
|--------|----------|-----------|--------------|
| `device_message` | 90 天 | Bucket Retention Policy | `KEEP 90d` |
| `product_property_*` | 365 天 | Bucket Retention Policy | `KEEP 365d` |
| `alarm_history` | 365 天 | Bucket Retention Policy | `KEEP 365d` |
| `rule_debug_log` | 7 天 | Bucket Retention Policy | `KEEP 7d` |
### 2.4 属性作用域扩展
v2.0 引入属性三元分类CLIENT/SERVER/SHARED时序数据需新增 `scope` 维度:
- **CTSDB**:写入时增加 `scope` 字段INT作为 FIELD查询时按 scope 过滤
- **TDengine**:超级表增加 `scope TINYINT`
两套 DAO 实现的 `insert()` 方法均需接收 `AttributeScope` 参数。
---
## 三、TDengine 优化
## 三、Redis 缓存优化
### 3.1 消息超级表优化
现有 `device_message` 表的 `params``data` 字段为 `NCHAR(2048)`,大消息会截断。
优化方案:
```sql
-- 增大到 4096覆盖绝大多数场景
ALTER STABLE device_message MODIFY COLUMN params NCHAR(4096);
ALTER STABLE device_message MODIFY COLUMN data NCHAR(4096);
```
### 3.2 新增超级表
```sql
-- 告警历史(两级告警的时序部分)
CREATE STABLE IF NOT EXISTS alarm_history (
ts TIMESTAMP,
alarm_record_id NCHAR(64),
alarm_config_id BIGINT,
severity TINYINT,
state NCHAR(16),
trigger_data NCHAR(4096),
details NCHAR(2048),
operator NCHAR(64),
remark NCHAR(256)
) TAGS (device_id BIGINT);
-- 规则调试日志
CREATE STABLE IF NOT EXISTS rule_debug_log (
ts TIMESTAMP,
rule_chain_id BIGINT,
node_id BIGINT,
node_type NCHAR(64),
input_data NCHAR(4096),
output_data NCHAR(4096),
duration_ms INT,
success BOOL,
error_msg NCHAR(512)
) TAGS (device_id BIGINT);
```
### 3.3 数据保留策略
| 超级表 | 建议保留期 | 配置方式 |
|--------|----------|---------|
| `device_message` | 90 天 | TDengine `KEEP 90d` |
| `product_property_*` | 365 天 | TDengine `KEEP 365d` |
| `alarm_history` | 365 天 | TDengine `KEEP 365d` |
| `rule_debug_log` | 7 天 | TDengine `KEEP 7d` |
---
## 四、Redis 缓存优化
### 4.1 修复生产隐患
### 3.1 修复生产隐患
| 问题 | 现状 | 修复 |
|------|------|------|
@@ -189,7 +201,7 @@ CREATE STABLE IF NOT EXISTS rule_debug_log (
| 部分缓存无 TTL | `iot:device_property:*``iot:device_server_id` 常驻 | 设备删除时主动清理 |
| 告警缓存竞态 | 无并发保护 | 分布式锁 + RecordCache |
### 4.2 新增 Redis Key
### 3.2 新增 Redis Key
| Key | 类型 | 用途 | TTL |
|-----|------|------|-----|
@@ -201,9 +213,9 @@ CREATE STABLE IF NOT EXISTS rule_debug_log (
---
## 、可观测性埋点Micrometer
## 、可观测性埋点Micrometer
### 5.1 核心指标
### 4.1 核心指标
| 指标名 | 类型 | 标签 | 说明 |
|--------|------|------|------|
@@ -217,19 +229,17 @@ CREATE STABLE IF NOT EXISTS rule_debug_log (
| `iot.buffer.size` | Gauge | bufferName | 缓冲区当前大小 |
| `iot.rpc.status` | Counter | status | RPC 状态分布 |
### 5.2 集成方式
### 4.2 集成方式
```java
@Component
class IotMetrics {
private final MeterRegistry registry;
// 设备上线
void recordDeviceOnline(String protocol) {
Metrics.gauge("iot.device.online", Tags.of("protocol", protocol), onlineCount);
}
// 规则执行耗时
void recordRuleExecution(Long chainId, String nodeType, Duration duration) {
Timer.builder("iot.rule.execution")
.tag("ruleChainId", String.valueOf(chainId))

View File

@@ -10,6 +10,10 @@
---
![[diagram-protocol-hotload.png]]
---
## 二、Codec SPI 增强
### 2.1 现有接口保留

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

View File

@@ -0,0 +1,94 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 470" width="960" height="470">
<style>
text { font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Microsoft YaHei", sans-serif; }
</style>
<defs>
<marker id="arrow-blue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
</marker>
<marker id="arrow-green" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#16a34a"/>
</marker>
<marker id="arrow-orange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#ea580c"/>
</marker>
<marker id="arrow-purple" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#9333ea"/>
</marker>
<marker id="arrow-red" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#dc2626"/>
</marker>
<marker id="arrow-gray" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#6b7280"/>
</marker>
<marker id="arrow-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#0ea5e9"/>
</marker>
<filter id="shadow" x="-4%" y="-4%" width="108%" height="108%">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#000" flood-opacity="0.08"/>
</filter>
</defs>
<rect width="960" height="470" fill="#ffffff"/>
<text x="480" y="36" text-anchor="middle" fill="#111827" font-size="18" font-weight="700">告警状态机 Alarm State Machine</text>
<text x="480" y="56" text-anchor="middle" fill="#6b7280" font-size="11">05-告警体系设计 | 5 状态 + 竞态保护 + 层级传播</text>
<circle cx="100" cy="160" r="12" fill="#111827"/>
<text x="100" y="140" text-anchor="middle" fill="#6b7280" font-size="10">告警触发</text>
<rect x="210" y="130" width="140" height="60" rx="30" fill="#fee2e2" stroke="#dc2626" stroke-width="2" filter="url(#shadow)"/>
<text x="280" y="156" text-anchor="middle" fill="#dc2626" font-size="13" font-weight="700">ACTIVE</text>
<text x="280" y="174" text-anchor="middle" fill="#dc2626" font-size="10">活跃</text>
<rect x="450" y="70" width="140" height="60" rx="30" fill="#fed7aa" stroke="#ea580c" stroke-width="2" filter="url(#shadow)"/>
<text x="520" y="96" text-anchor="middle" fill="#ea580c" font-size="13" font-weight="700">ACKNOWLEDGED</text>
<text x="520" y="114" text-anchor="middle" fill="#ea580c" font-size="10">已确认</text>
<rect x="450" y="210" width="140" height="60" rx="30" fill="#dcfce7" stroke="#16a34a" stroke-width="2" filter="url(#shadow)"/>
<text x="520" y="236" text-anchor="middle" fill="#16a34a" font-size="13" font-weight="700">CLEARED</text>
<text x="520" y="254" text-anchor="middle" fill="#16a34a" font-size="10">已清除</text>
<rect x="690" y="130" width="140" height="60" rx="30" fill="#f3f4f6" stroke="#6b7280" stroke-width="2" filter="url(#shadow)"/>
<text x="760" y="156" text-anchor="middle" fill="#6b7280" font-size="13" font-weight="700">RESOLVED</text>
<text x="760" y="174" text-anchor="middle" fill="#6b7280" font-size="10">已解决</text>
<circle cx="900" cy="160" r="14" fill="none" stroke="#111827" stroke-width="2"/>
<circle cx="900" cy="160" r="9" fill="#111827"/>
<text x="900" y="140" text-anchor="middle" fill="#6b7280" font-size="10">归档</text>
<line x1="112" y1="160" x2="200" y2="160" stroke="#dc2626" stroke-width="2" marker-end="url(#arrow-red)"/>
<rect x="135" y="148" width="60" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="165" y="160" text-anchor="middle" fill="#dc2626" font-size="9">trigger</text>
<path d="M 350,145 L 440,110" stroke="#ea580c" stroke-width="2" fill="none" marker-end="url(#arrow-orange)"/>
<rect x="365" y="112" width="60" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="395" y="124" text-anchor="middle" fill="#ea580c" font-size="9">用户确认</text>
<path d="M 350,175 L 440,225" stroke="#16a34a" stroke-width="2" fill="none" marker-end="url(#arrow-green)"/>
<rect x="362" y="192" width="65" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="394" y="204" text-anchor="middle" fill="#16a34a" font-size="9">条件恢复</text>
<line x1="520" y1="130" x2="520" y2="200" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
<rect x="525" y="155" width="60" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="555" y="167" text-anchor="middle" fill="#16a34a" font-size="9">条件恢复</text>
<path d="M 590,100 L 680,145" stroke="#6b7280" stroke-width="2" fill="none" marker-end="url(#arrow-gray)"/>
<rect x="610" y="108" width="55" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="637" y="120" text-anchor="middle" fill="#6b7280" font-size="9">手动解决</text>
<path d="M 590,230 L 680,175" stroke="#6b7280" stroke-width="2" fill="none" marker-end="url(#arrow-gray)"/>
<rect x="610" y="198" width="55" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="637" y="210" text-anchor="middle" fill="#6b7280" font-size="9">手动解决</text>
<line x1="830" y1="160" x2="880" y2="160" stroke="#111827" stroke-width="2" marker-end="url(#arrow-gray)"/>
<path d="M 280,130 C 280,80 340,80 340,130" stroke="#dc2626" stroke-width="1.5" fill="none" marker-end="url(#arrow-red)"/>
<text x="310" y="82" text-anchor="middle" fill="#dc2626" font-size="9">重复触发</text>
<text x="310" y="94" text-anchor="middle" fill="#dc2626" font-size="8">(计数+1)</text>
<text x="60" y="320" fill="#111827" font-size="12" font-weight="600">告警级别</text>
<rect x="60" y="335" width="100" height="28" rx="14" fill="#fee2e2" stroke="#dc2626" stroke-width="1.2"/>
<text x="110" y="354" text-anchor="middle" fill="#dc2626" font-size="10" font-weight="600">CRITICAL</text>
<rect x="170" y="335" width="100" height="28" rx="14" fill="#fed7aa" stroke="#ea580c" stroke-width="1.2"/>
<text x="220" y="354" text-anchor="middle" fill="#ea580c" font-size="10" font-weight="600">MAJOR</text>
<rect x="280" y="335" width="100" height="28" rx="14" fill="#fef9c3" stroke="#eab308" stroke-width="1.2"/>
<text x="330" y="354" text-anchor="middle" fill="#eab308" font-size="10" font-weight="600">MINOR</text>
<rect x="390" y="335" width="100" height="28" rx="14" fill="#dbeafe" stroke="#2563eb" stroke-width="1.2"/>
<text x="440" y="354" text-anchor="middle" fill="#2563eb" font-size="10" font-weight="600">WARNING</text>
<rect x="500" y="335" width="100" height="28" rx="14" fill="#f3f4f6" stroke="#6b7280" stroke-width="1.2"/>
<text x="550" y="354" text-anchor="middle" fill="#6b7280" font-size="10" font-weight="600">INFO</text>
<rect x="620" y="310" width="310" height="65" rx="8" fill="#f3f4f6" stroke="#d1d5db" stroke-width="1"/>
<text x="775" y="330" text-anchor="middle" fill="#111827" font-size="11" font-weight="600">竞态保护机制</text>
<text x="775" y="348" text-anchor="middle" fill="#6b7280" font-size="10">Redis 缓存 + 分布式锁 (Redisson)</text>
<text x="775" y="364" text-anchor="middle" fill="#6b7280" font-size="10">AlarmRecord record_key UK 幂等</text>
<rect x="60" y="390" width="400" height="55" rx="8" fill="#dbeafe" stroke="#4479A1" stroke-width="1.2"/>
<text x="260" y="412" text-anchor="middle" fill="#4479A1" font-size="11" font-weight="600">MySQL AlarmRecord (当前状态 Upsert)</text>
<text x="260" y="430" text-anchor="middle" fill="#6b7280" font-size="10">record_key = MD5(deviceId + alarmConfigId)</text>
<rect x="500" y="390" width="430" height="55" rx="8" fill="#ede9fe" stroke="#9333ea" stroke-width="1.2"/>
<text x="715" y="412" text-anchor="middle" fill="#9333ea" font-size="11" font-weight="600">TimeSeries AlarmHistory (审计轨迹 Append)</text>
<text x="715" y="430" text-anchor="middle" fill="#6b7280" font-size="10">每次状态变更追加记录 (ts, state, trigger_data)</text>
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -0,0 +1,73 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 500" width="960" height="500">
<style>
text { font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Microsoft YaHei", sans-serif; }
</style>
<defs>
<marker id="arrow-blue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
</marker>
<marker id="arrow-green" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#16a34a"/>
</marker>
<marker id="arrow-orange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#ea580c"/>
</marker>
<marker id="arrow-purple" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#9333ea"/>
</marker>
<marker id="arrow-red" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#dc2626"/>
</marker>
<marker id="arrow-gray" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#6b7280"/>
</marker>
<marker id="arrow-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#0ea5e9"/>
</marker>
<filter id="shadow" x="-4%" y="-4%" width="108%" height="108%">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#000" flood-opacity="0.08"/>
</filter>
</defs>
<rect width="960" height="500" fill="#ffffff"/>
<text x="480" y="36" text-anchor="middle" fill="#111827" font-size="18" font-weight="700">属性三元分类数据流向</text>
<text x="480" y="56" text-anchor="middle" fill="#6b7280" font-size="11">03-物模型规范 / 06-设备影子 | CLIENT / SERVER / SHARED 读写流向</text>
<rect x="40" y="140" width="140" height="200" rx="10" fill="#fff7ed" stroke="#ea580c" stroke-width="2" filter="url(#shadow)"/>
<text x="110" y="170" text-anchor="middle" fill="#ea580c" font-size="14" font-weight="700">IoT 设备</text>
<text x="110" y="195" text-anchor="middle" fill="#6b7280" font-size="10">传感器数据上报</text>
<text x="110" y="215" text-anchor="middle" fill="#6b7280" font-size="10">状态属性汇报</text>
<text x="110" y="240" text-anchor="middle" fill="#6b7280" font-size="10">接收配置下发</text>
<text x="110" y="265" text-anchor="middle" fill="#6b7280" font-size="10">执行 RPC 指令</text>
<rect x="780" y="140" width="140" height="200" rx="10" fill="#eff6ff" stroke="#2563eb" stroke-width="2" filter="url(#shadow)"/>
<text x="850" y="170" text-anchor="middle" fill="#2563eb" font-size="14" font-weight="700">IoT 平台</text>
<text x="850" y="195" text-anchor="middle" fill="#6b7280" font-size="10">规则引擎计算</text>
<text x="850" y="215" text-anchor="middle" fill="#6b7280" font-size="10">管理后台 UI</text>
<text x="850" y="240" text-anchor="middle" fill="#6b7280" font-size="10">REST API</text>
<text x="850" y="265" text-anchor="middle" fill="#6b7280" font-size="10">Agent 服务</text>
<rect x="280" y="95" width="400" height="65" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5" filter="url(#shadow)"/>
<text x="480" y="118" text-anchor="middle" fill="#16a34a" font-size="14" font-weight="700">CLIENT 属性</text>
<text x="480" y="136" text-anchor="middle" fill="#6b7280" font-size="10">设备写入 | 平台只读 | Redis iot:device_property:{id}:client</text>
<path d="M 180,180 L 270,127" stroke="#16a34a" stroke-width="2" fill="none" marker-end="url(#arrow-green)"/>
<rect x="195" y="138" width="40" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="215" y="150" text-anchor="middle" fill="#16a34a" font-size="9">写入</text>
<path d="M 680,127 L 770,180" stroke="#2563eb" stroke-width="1.5" fill="none" stroke-dasharray="5,3" marker-end="url(#arrow-blue)"/>
<rect x="695" y="140" width="40" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="715" y="152" text-anchor="middle" fill="#2563eb" font-size="9">读取</text>
<rect x="280" y="205" width="400" height="65" rx="8" fill="#dbeafe" stroke="#2563eb" stroke-width="1.5" filter="url(#shadow)"/>
<text x="480" y="228" text-anchor="middle" fill="#2563eb" font-size="14" font-weight="700">SERVER 属性</text>
<text x="480" y="248" text-anchor="middle" fill="#6b7280" font-size="10">平台写入 | 平台只读 | Redis iot:device_property:{id}:server</text>
<path d="M 770,230 L 690,237" stroke="#2563eb" stroke-width="2" fill="none" marker-end="url(#arrow-blue)"/>
<rect x="710" y="218" width="40" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="730" y="230" text-anchor="middle" fill="#2563eb" font-size="9">写入</text>
<rect x="280" y="315" width="400" height="65" rx="8" fill="#ede9fe" stroke="#9333ea" stroke-width="1.5" filter="url(#shadow)"/>
<text x="480" y="338" text-anchor="middle" fill="#9333ea" font-size="14" font-weight="700">SHARED 属性</text>
<text x="480" y="358" text-anchor="middle" fill="#6b7280" font-size="10">平台写入 | 设备+平台读取 | Redis + MySQL 双写</text>
<path d="M 770,280 L 690,337" stroke="#9333ea" stroke-width="2" fill="none" marker-end="url(#arrow-purple)"/>
<rect x="710" y="295" width="40" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="730" y="307" text-anchor="middle" fill="#9333ea" font-size="9">写入</text>
<path d="M 280,347 L 180,290" stroke="#9333ea" stroke-width="2" fill="none" marker-end="url(#arrow-purple)"/>
<rect x="195" y="310" width="60" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="225" y="322" text-anchor="middle" fill="#9333ea" font-size="9">配置下发</text>
<rect x="280" y="420" width="400" height="55" rx="8" fill="#f3f4f6" stroke="#d1d5db" stroke-width="1"/>
<text x="480" y="442" text-anchor="middle" fill="#111827" font-size="12" font-weight="600">存储策略</text>
<text x="480" y="460" text-anchor="middle" fill="#6b7280" font-size="10">CLIENT/SERVER: 仅 Redis | SHARED: Redis + MySQL 双写 (持久化)</text>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View File

@@ -0,0 +1,65 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 480" width="960" height="480">
<style>
text { font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Microsoft YaHei", sans-serif; }
</style>
<defs>
<marker id="arrow-blue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
</marker>
<marker id="arrow-green" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#16a34a"/>
</marker>
<marker id="arrow-orange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#ea580c"/>
</marker>
<marker id="arrow-purple" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#9333ea"/>
</marker>
<marker id="arrow-red" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#dc2626"/>
</marker>
<marker id="arrow-gray" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#6b7280"/>
</marker>
<marker id="arrow-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#0ea5e9"/>
</marker>
<filter id="shadow" x="-4%" y="-4%" width="108%" height="108%">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#000" flood-opacity="0.08"/>
</filter>
</defs>
<rect width="960" height="480" fill="#ffffff"/>
<text x="480" y="36" text-anchor="middle" fill="#111827" font-size="18" font-weight="700">协议包热加载流程</text>
<text x="480" y="56" text-anchor="middle" fill="#6b7280" font-size="11">08-协议与编解码扩展 | JAR 上传 → ClassLoader → SPI 注册</text>
<rect x="340" y="80" width="280" height="40" rx="8" fill="#dbeafe" stroke="#2563eb" stroke-width="1.5" filter="url(#shadow)"/>
<text x="480" y="105" text-anchor="middle" fill="#2563eb" font-size="12" font-weight="600">上传协议包 JAR</text>
<rect x="340" y="155" width="280" height="50" rx="8" fill="#fed7aa" stroke="#ea580c" stroke-width="1.5" filter="url(#shadow)"/>
<text x="480" y="176" text-anchor="middle" fill="#ea580c" font-size="12" font-weight="600">校验 JAR 完整性</text>
<text x="480" y="194" text-anchor="middle" fill="#6b7280" font-size="10">签名验证 + META-INF 检查</text>
<rect x="340" y="235" width="280" height="50" rx="8" fill="#ede9fe" stroke="#9333ea" stroke-width="1.5" filter="url(#shadow)"/>
<text x="480" y="256" text-anchor="middle" fill="#9333ea" font-size="12" font-weight="600">创建隔离 ClassLoader</text>
<text x="480" y="274" text-anchor="middle" fill="#6b7280" font-size="10">独立类加载器避免冲突</text>
<rect x="340" y="315" width="280" height="50" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5" filter="url(#shadow)"/>
<text x="480" y="336" text-anchor="middle" fill="#16a34a" font-size="12" font-weight="600">ServiceLoader 发现 Codec</text>
<text x="480" y="354" text-anchor="middle" fill="#6b7280" font-size="10">扫描 META-INF/services</text>
<rect x="340" y="395" width="280" height="50" rx="8" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5" filter="url(#shadow)"/>
<text x="480" y="416" text-anchor="middle" fill="#dc2626" font-size="12" font-weight="600">注册到 CodecRegistry</text>
<text x="480" y="434" text-anchor="middle" fill="#6b7280" font-size="10">codecId -> Codec 实例映射</text>
<line x1="480" y1="125" x2="480" y2="155" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<line x1="480" y1="205" x2="480" y2="235" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<line x1="480" y1="285" x2="480" y2="315" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<line x1="480" y1="365" x2="480" y2="395" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<rect x="700" y="320" width="200" height="45" rx="8" fill="#fee2e2" stroke="#dc2626" stroke-width="1.2"/>
<text x="800" y="340" text-anchor="middle" fill="#dc2626" font-size="11" font-weight="600">卸载协议包</text>
<text x="800" y="356" text-anchor="middle" fill="#6b7280" font-size="10">关闭 ClassLoader + 移除注册</text>
<path d="M 620,340 L 690,340" stroke="#dc2626" stroke-width="1.5" fill="none" marker-end="url(#arrow-red)"/>
<rect x="60" y="180" width="200" height="120" rx="8" fill="#f3f4f6" stroke="#d1d5db" stroke-width="1"/>
<text x="160" y="200" text-anchor="middle" fill="#111827" font-size="11" font-weight="600">内置 Codec (Spring Bean)</text>
<text x="160" y="220" text-anchor="middle" fill="#6b7280" font-size="10">ALink Codec</text>
<text x="160" y="238" text-anchor="middle" fill="#6b7280" font-size="10">JT808 Codec</text>
<text x="160" y="256" text-anchor="middle" fill="#6b7280" font-size="10">Camera3D11 Codec</text>
<text x="160" y="274" text-anchor="middle" fill="#6b7280" font-size="10">Transparent Codec</text>
<path d="M 260,275 L 330,275" stroke="#6b7280" stroke-width="1.2" fill="none" stroke-dasharray="5,3" marker-end="url(#arrow-gray)"/>
<rect x="268" y="258" width="55" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="296" y="270" text-anchor="middle" fill="#6b7280" font-size="8">共存注册</text>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

View File

@@ -0,0 +1,91 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 500" width="960" height="500">
<style>
text { font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Microsoft YaHei", sans-serif; }
</style>
<defs>
<marker id="arrow-blue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
</marker>
<marker id="arrow-green" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#16a34a"/>
</marker>
<marker id="arrow-orange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#ea580c"/>
</marker>
<marker id="arrow-purple" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#9333ea"/>
</marker>
<marker id="arrow-red" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#dc2626"/>
</marker>
<marker id="arrow-gray" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#6b7280"/>
</marker>
<marker id="arrow-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#0ea5e9"/>
</marker>
<filter id="shadow" x="-4%" y="-4%" width="108%" height="108%">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#000" flood-opacity="0.08"/>
</filter>
</defs>
<rect width="960" height="500" fill="#ffffff"/>
<text x="480" y="36" text-anchor="middle" fill="#111827" font-size="18" font-weight="700">RPC 状态机 (持久化指令投递)</text>
<text x="480" y="56" text-anchor="middle" fill="#6b7280" font-size="11">06-设备影子与RPC | 离线排队 + 上线补发 + 超时重试</text>
<circle cx="80" cy="200" r="12" fill="#111827"/>
<text x="80" y="180" text-anchor="middle" fill="#6b7280" font-size="10">创建指令</text>
<rect x="165" y="172" width="130" height="56" rx="28" fill="#f3f4f6" stroke="#6b7280" stroke-width="2" filter="url(#shadow)"/>
<text x="230" y="196" text-anchor="middle" fill="#6b7280" font-size="12" font-weight="700">QUEUED</text>
<text x="230" y="214" text-anchor="middle" fill="#6b7280" font-size="10">排队中</text>
<rect x="370" y="172" width="120" height="56" rx="28" fill="#dbeafe" stroke="#2563eb" stroke-width="2" filter="url(#shadow)"/>
<text x="430" y="196" text-anchor="middle" fill="#2563eb" font-size="12" font-weight="700">SENT</text>
<text x="430" y="214" text-anchor="middle" fill="#2563eb" font-size="10">已发送</text>
<rect x="565" y="172" width="130" height="56" rx="28" fill="#ede9fe" stroke="#9333ea" stroke-width="2" filter="url(#shadow)"/>
<text x="630" y="196" text-anchor="middle" fill="#9333ea" font-size="12" font-weight="700">DELIVERED</text>
<text x="630" y="214" text-anchor="middle" fill="#9333ea" font-size="10">已送达</text>
<rect x="770" y="92" width="120" height="56" rx="28" fill="#dcfce7" stroke="#16a34a" stroke-width="2" filter="url(#shadow)"/>
<text x="830" y="116" text-anchor="middle" fill="#16a34a" font-size="12" font-weight="700">SUCCESS</text>
<text x="830" y="134" text-anchor="middle" fill="#16a34a" font-size="10">成功</text>
<rect x="770" y="252" width="120" height="56" rx="28" fill="#fee2e2" stroke="#dc2626" stroke-width="2" filter="url(#shadow)"/>
<text x="830" y="276" text-anchor="middle" fill="#dc2626" font-size="12" font-weight="700">FAILURE</text>
<text x="830" y="294" text-anchor="middle" fill="#dc2626" font-size="10">失败</text>
<rect x="370" y="332" width="120" height="56" rx="28" fill="#fed7aa" stroke="#ea580c" stroke-width="2" filter="url(#shadow)"/>
<text x="430" y="356" text-anchor="middle" fill="#ea580c" font-size="12" font-weight="700">TIMEOUT</text>
<text x="430" y="374" text-anchor="middle" fill="#ea580c" font-size="10">超时</text>
<rect x="170" y="332" width="120" height="56" rx="28" fill="#e5e7eb" stroke="#6b7280" stroke-width="2" filter="url(#shadow)"/>
<text x="230" y="356" text-anchor="middle" fill="#6b7280" font-size="12" font-weight="700">EXPIRED</text>
<text x="230" y="374" text-anchor="middle" fill="#6b7280" font-size="10">已过期</text>
<line x1="92" y1="200" x2="155" y2="200" stroke="#6b7280" stroke-width="2" marker-end="url(#arrow-gray)"/>
<line x1="295" y1="200" x2="360" y2="200" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow-blue)"/>
<rect x="305" y="183" width="50" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="330" y="195" text-anchor="middle" fill="#2563eb" font-size="9">设备在线</text>
<line x1="490" y1="200" x2="555" y2="200" stroke="#9333ea" stroke-width="2" marker-end="url(#arrow-purple)"/>
<rect x="498" y="183" width="55" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="525" y="195" text-anchor="middle" fill="#9333ea" font-size="9">设备确认</text>
<path d="M 695,185 L 760,135" stroke="#16a34a" stroke-width="2" fill="none" marker-end="url(#arrow-green)"/>
<rect x="702" y="145" width="55" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="729" y="157" text-anchor="middle" fill="#16a34a" font-size="9">执行成功</text>
<path d="M 695,215 L 760,265" stroke="#dc2626" stroke-width="2" fill="none" marker-end="url(#arrow-red)"/>
<rect x="702" y="237" width="55" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="729" y="249" text-anchor="middle" fill="#dc2626" font-size="9">执行失败</text>
<line x1="430" y1="228" x2="430" y2="322" stroke="#ea580c" stroke-width="2" marker-end="url(#arrow-orange)"/>
<rect x="435" y="268" width="70" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="470" y="280" text-anchor="middle" fill="#ea580c" font-size="9">响应超时</text>
<path d="M 370,360 C 310,360 310,220 365,200" stroke="#2563eb" stroke-width="1.5" fill="none" stroke-dasharray="5,3" marker-end="url(#arrow-blue)"/>
<rect x="298" y="278" width="60" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="328" y="290" text-anchor="middle" fill="#2563eb" font-size="9">重试 (3次)</text>
<line x1="370" y1="360" x2="295" y2="360" stroke="#6b7280" stroke-width="2" marker-end="url(#arrow-gray)"/>
<rect x="305" y="343" width="55" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="332" y="355" text-anchor="middle" fill="#6b7280" font-size="9">超过上限</text>
<rect x="60" y="420" width="280" height="60" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1"/>
<text x="200" y="440" text-anchor="middle" fill="#16a34a" font-size="11" font-weight="600">上线补发机制</text>
<text x="200" y="458" text-anchor="middle" fill="#6b7280" font-size="10">设备上线 -> 拉取 QUEUED -> 限速 5msg/s</text>
<text x="200" y="472" text-anchor="middle" fill="#6b7280" font-size="9">防止设备上线瞬间过载</text>
<rect x="380" y="420" width="280" height="60" rx="8" fill="#dbeafe" stroke="#2563eb" stroke-width="1"/>
<text x="520" y="440" text-anchor="middle" fill="#2563eb" font-size="11" font-weight="600">持久化保证</text>
<text x="520" y="458" text-anchor="middle" fill="#6b7280" font-size="10">所有状态变更持久化到 iot_device_rpc</text>
<text x="520" y="472" text-anchor="middle" fill="#6b7280" font-size="9">支持审计追踪与断点续传</text>
<rect x="700" y="420" width="230" height="60" rx="8" fill="#fed7aa" stroke="#ea580c" stroke-width="1"/>
<text x="815" y="440" text-anchor="middle" fill="#ea580c" font-size="11" font-weight="600">过期策略</text>
<text x="815" y="458" text-anchor="middle" fill="#6b7280" font-size="10">expireTime 到达 -> 标记 EXPIRED</text>
<text x="815" y="472" text-anchor="middle" fill="#6b7280" font-size="9">过期指令不再下发设备</text>
</svg>

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -0,0 +1,67 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 410" width="960" height="410">
<style>
text { font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Microsoft YaHei", sans-serif; }
</style>
<defs>
<marker id="arrow-blue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
</marker>
<marker id="arrow-green" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#16a34a"/>
</marker>
<marker id="arrow-orange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#ea580c"/>
</marker>
<marker id="arrow-purple" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#9333ea"/>
</marker>
<marker id="arrow-red" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#dc2626"/>
</marker>
<marker id="arrow-gray" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#6b7280"/>
</marker>
<marker id="arrow-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#0ea5e9"/>
</marker>
<filter id="shadow" x="-4%" y="-4%" width="108%" height="108%">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#000" flood-opacity="0.08"/>
</filter>
</defs>
<rect width="960" height="410" fill="#ffffff"/>
<text x="480" y="36" text-anchor="middle" fill="#111827" font-size="18" font-weight="700">规则引擎动作分类</text>
<text x="480" y="56" text-anchor="middle" fill="#6b7280" font-size="11">04-规则引擎方案 | 7 大类动作节点 + SPI 扩展</text>
<rect x="370" y="80" width="220" height="45" rx="8" fill="#ede9fe" stroke="#9333ea" stroke-width="1.5" filter="url(#shadow)"/>
<text x="480" y="96" text-anchor="middle" fill="#9333ea" font-size="12" font-weight="600">Actions 动作层</text>
<text x="480" y="112" text-anchor="middle" fill="#6b7280" font-size="10">SPI Provider 注册</text>
<rect x="75" y="190" width="150" height="110" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
<text x="150" y="210" text-anchor="middle" fill="#16a34a" font-size="12" font-weight="600">设备控制</text>
<text x="150" y="230" text-anchor="middle" fill="#6b7280" font-size="10">device_property_set</text>
<text x="150" y="246" text-anchor="middle" fill="#6b7280" font-size="10">device_service_invoke</text>
<path d="M 480,125 L 150,180" stroke="#9333ea" stroke-width="1.2" fill="none" marker-end="url(#arrow-purple)"/>
<rect x="255" y="190" width="150" height="110" rx="8" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5"/>
<text x="330" y="210" text-anchor="middle" fill="#dc2626" font-size="12" font-weight="600">告警处理</text>
<text x="330" y="230" text-anchor="middle" fill="#6b7280" font-size="10">alarm_trigger</text>
<text x="330" y="246" text-anchor="middle" fill="#6b7280" font-size="10">alarm_clear</text>
<path d="M 480,125 L 330,180" stroke="#9333ea" stroke-width="1.2" fill="none" marker-end="url(#arrow-purple)"/>
<rect x="435" y="190" width="150" height="85" rx="8" fill="#dbeafe" stroke="#2563eb" stroke-width="1.5"/>
<text x="510" y="210" text-anchor="middle" fill="#2563eb" font-size="12" font-weight="600">消息通知</text>
<text x="510" y="230" text-anchor="middle" fill="#6b7280" font-size="10">notify (多通道)</text>
<path d="M 480,125 L 510,180" stroke="#9333ea" stroke-width="1.2" fill="none" marker-end="url(#arrow-purple)"/>
<rect x="615" y="190" width="150" height="110" rx="8" fill="#f3f4f6" stroke="#6b7280" stroke-width="1.5"/>
<text x="690" y="210" text-anchor="middle" fill="#6b7280" font-size="12" font-weight="600">数据转发</text>
<text x="690" y="230" text-anchor="middle" fill="#6b7280" font-size="10">http_push</text>
<text x="690" y="246" text-anchor="middle" fill="#6b7280" font-size="10">mq_push</text>
<text x="690" y="262" text-anchor="middle" fill="#6b7280" font-size="10">redis_push</text>
<text x="690" y="278" text-anchor="middle" fill="#6b7280" font-size="10">tcp_push</text>
<path d="M 480,125 L 690,180" stroke="#9333ea" stroke-width="1.2" fill="none" marker-end="url(#arrow-purple)"/>
<rect x="795" y="190" width="140" height="85" rx="8" fill="#ede9fe" stroke="#9333ea" stroke-width="1.5"/>
<text x="865" y="210" text-anchor="middle" fill="#9333ea" font-size="12" font-weight="600">数据处理</text>
<text x="865" y="230" text-anchor="middle" fill="#6b7280" font-size="10">script (Aviator)</text>
<text x="865" y="246" text-anchor="middle" fill="#6b7280" font-size="10">enrich / delay / log</text>
<path d="M 480,125 L 865,180" stroke="#9333ea" stroke-width="1.2" fill="none" marker-end="url(#arrow-purple)"/>
<rect x="400" y="330" width="160" height="60" rx="8" fill="#e0f2fe" stroke="#0ea5e9" stroke-width="1.5" stroke-dasharray="5,3"/>
<text x="480" y="355" text-anchor="middle" fill="#0ea5e9" font-size="12" font-weight="600">Agent 协作 (预留)</text>
<text x="480" y="375" text-anchor="middle" fill="#6b7280" font-size="10">agent_request (ACP)</text>
<line x1="480" y1="125" x2="480" y2="320" stroke="#0ea5e9" stroke-width="1.2" marker-end="url(#arrow-cyan)"/>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

View File

@@ -0,0 +1,94 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 570" width="960" height="570">
<style>
text { font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Microsoft YaHei", sans-serif; }
</style>
<defs>
<marker id="arrow-blue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
</marker>
<marker id="arrow-green" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#16a34a"/>
</marker>
<marker id="arrow-orange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#ea580c"/>
</marker>
<marker id="arrow-purple" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#9333ea"/>
</marker>
<marker id="arrow-red" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#dc2626"/>
</marker>
<marker id="arrow-gray" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#6b7280"/>
</marker>
<marker id="arrow-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#0ea5e9"/>
</marker>
<filter id="shadow" x="-4%" y="-4%" width="108%" height="108%">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#000" flood-opacity="0.08"/>
</filter>
</defs>
<rect width="960" height="570" fill="#ffffff"/>
<text x="480" y="36" text-anchor="middle" fill="#111827" font-size="18" font-weight="700">子系统与设备归属 ER 关系图</text>
<text x="480" y="56" text-anchor="middle" fill="#6b7280" font-size="11">02-子系统与设备归属模型 | 租户 → 项目 → 子系统 → 设备</text>
<rect x="60" y="90" width="180" height="114" rx="6" fill="#ffffff" stroke="#ea580c" stroke-width="1.5" filter="url(#shadow)"/>
<rect x="60" y="90" width="180" height="32" rx="6" fill="#fed7aa" stroke="#ea580c" stroke-width="1.5"/>
<rect x="60" y="116" width="180" height="6" fill="#fed7aa"/>
<text x="150" y="111" text-anchor="middle" fill="#ea580c" font-size="13" font-weight="700">iot_tenant</text>
<text x="70" y="140" text-decoration="underline" fill="#374151" font-size="10">id BIGINT PK</text>
<text x="70" y="158" fill="#374151" font-size="10">name VARCHAR</text>
<text x="70" y="176" fill="#374151" font-size="10">status TINYINT</text>
<text x="70" y="194" fill="#374151" font-size="10">...</text>
<rect x="60" y="280" width="180" height="132" rx="6" fill="#ffffff" stroke="#ea580c" stroke-width="1.5" filter="url(#shadow)"/>
<rect x="60" y="280" width="180" height="32" rx="6" fill="#fed7aa" stroke="#ea580c" stroke-width="1.5"/>
<rect x="60" y="306" width="180" height="6" fill="#fed7aa"/>
<text x="150" y="301" text-anchor="middle" fill="#ea580c" font-size="13" font-weight="700">iot_project</text>
<text x="70" y="330" text-decoration="underline" fill="#374151" font-size="10">id BIGINT PK</text>
<text x="70" y="348" font-style="italic" fill="#374151" font-size="10">tenant_id BIGINT FK</text>
<text x="70" y="366" fill="#374151" font-size="10">name VARCHAR</text>
<text x="70" y="384" fill="#374151" font-size="10">code VARCHAR UK</text>
<text x="70" y="402" fill="#374151" font-size="10">status TINYINT</text>
<rect x="380" y="230" width="200" height="168" rx="6" fill="#ffffff" stroke="#9333ea" stroke-width="1.5" filter="url(#shadow)"/>
<rect x="380" y="230" width="200" height="32" rx="6" fill="#ede9fe" stroke="#9333ea" stroke-width="1.5"/>
<rect x="380" y="256" width="200" height="6" fill="#ede9fe"/>
<text x="480" y="251" text-anchor="middle" fill="#9333ea" font-size="13" font-weight="700">iot_subsystem</text>
<text x="390" y="280" text-decoration="underline" fill="#374151" font-size="10">id BIGINT PK</text>
<text x="390" y="298" font-style="italic" fill="#374151" font-size="10">project_id BIGINT FK</text>
<text x="390" y="316" font-style="italic" fill="#374151" font-size="10">tenant_id BIGINT FK</text>
<text x="390" y="334" fill="#374151" font-size="10">name VARCHAR</text>
<text x="390" y="352" fill="#374151" font-size="10">code VARCHAR UK NOT NULL</text>
<text x="390" y="370" fill="#374151" font-size="10">type TINYINT</text>
<text x="390" y="388" fill="#374151" font-size="10">status TINYINT</text>
<rect x="700" y="160" width="200" height="186" rx="6" fill="#ffffff" stroke="#2563eb" stroke-width="1.5" filter="url(#shadow)"/>
<rect x="700" y="160" width="200" height="32" rx="6" fill="#dbeafe" stroke="#2563eb" stroke-width="1.5"/>
<rect x="700" y="186" width="200" height="6" fill="#dbeafe"/>
<text x="800" y="181" text-anchor="middle" fill="#2563eb" font-size="13" font-weight="700">iot_device</text>
<text x="710" y="210" text-decoration="underline" fill="#374151" font-size="10">id BIGINT PK</text>
<text x="710" y="228" font-style="italic" fill="#374151" font-size="10">product_id BIGINT FK</text>
<text x="710" y="246" font-style="italic" fill="#374151" font-size="10">subsystem_id BIGINT FK</text>
<text x="710" y="264" font-style="italic" fill="#374151" font-size="10">tenant_id BIGINT FK</text>
<text x="710" y="282" fill="#374151" font-size="10">device_key VARCHAR UK</text>
<text x="710" y="300" fill="#374151" font-size="10">device_name VARCHAR</text>
<text x="710" y="318" fill="#374151" font-size="10">status TINYINT</text>
<text x="710" y="336" fill="#374151" font-size="10">last_online_time DATETIME</text>
<rect x="700" y="420" width="200" height="150" rx="6" fill="#ffffff" stroke="#16a34a" stroke-width="1.5" filter="url(#shadow)"/>
<rect x="700" y="420" width="200" height="32" rx="6" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
<rect x="700" y="446" width="200" height="6" fill="#dcfce7"/>
<text x="800" y="441" text-anchor="middle" fill="#16a34a" font-size="13" font-weight="700">iot_product</text>
<text x="710" y="470" text-decoration="underline" fill="#374151" font-size="10">id BIGINT PK</text>
<text x="710" y="488" font-style="italic" fill="#374151" font-size="10">tenant_id BIGINT FK</text>
<text x="710" y="506" fill="#374151" font-size="10">product_key VARCHAR UK</text>
<text x="710" y="524" fill="#374151" font-size="10">name VARCHAR</text>
<text x="710" y="542" fill="#374151" font-size="10">protocol_type TINYINT</text>
<text x="710" y="560" fill="#374151" font-size="10">status TINYINT</text>
<line x1="150" y1="185" x2="150" y2="270" stroke="#ea580c" stroke-width="1.5" marker-end="url(#arrow-orange)"/>
<text x="160" y="230" fill="#ea580c" font-size="9" font-weight="600">1 : N</text>
<line x1="240" y1="320" x2="370" y2="320" stroke="#9333ea" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
<text x="300" y="313" fill="#9333ea" font-size="9" font-weight="600">1 : N</text>
<line x1="580" y1="310" x2="690" y2="310" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<text x="630" y="303" fill="#2563eb" font-size="9" font-weight="600">1 : N</text>
<path d="M 800,420 L 800,380" stroke="#16a34a" stroke-width="1.5" fill="none" marker-end="url(#arrow-green)"/>
<text x="810" y="400" fill="#16a34a" font-size="9" font-weight="600">1 : N</text>
<rect x="60" y="520" width="840" height="30" rx="6" fill="#f3f4f6" stroke="#d1d5db" stroke-width="1"/>
<text x="480" y="540" text-anchor="middle" fill="#6b7280" font-size="10">设备通过 subsystem_id FK 归属子系统 | 子系统决定规则链匹配、告警传播、用户权限范围 | iot_project 本次架构预留</text>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View File

@@ -0,0 +1,72 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 475" width="960" height="475">
<style>
text { font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Microsoft YaHei", sans-serif; }
</style>
<defs>
<marker id="arrow-blue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
</marker>
<marker id="arrow-green" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#16a34a"/>
</marker>
<marker id="arrow-orange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#ea580c"/>
</marker>
<marker id="arrow-purple" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#9333ea"/>
</marker>
<marker id="arrow-red" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#dc2626"/>
</marker>
<marker id="arrow-gray" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#6b7280"/>
</marker>
<marker id="arrow-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#0ea5e9"/>
</marker>
<filter id="shadow" x="-4%" y="-4%" width="108%" height="108%">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#000" flood-opacity="0.08"/>
</filter>
</defs>
<rect width="960" height="475" fill="#ffffff"/>
<text x="480" y="36" text-anchor="middle" fill="#111827" font-size="18" font-weight="700">时序库双实现架构</text>
<text x="480" y="56" text-anchor="middle" fill="#6b7280" font-size="11">07-数据存储方案 | CTSDB / TDengine 可切换 + DAO 接口统一</text>
<rect x="280" y="80" width="400" height="50" rx="8" fill="#f3f4f6" stroke="#111827" stroke-width="1.5" filter="url(#shadow)"/>
<text x="480" y="99" text-anchor="middle" fill="#111827" font-size="12" font-weight="600">IotTimeSeriesDAO (统一接口)</text>
<text x="480" y="115" text-anchor="middle" fill="#6b7280" font-size="10">saveDeviceMessage / queryHistory / createSchema</text>
<line x1="380" y1="130" x2="380" y2="165" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<line x1="580" y1="130" x2="580" y2="165" stroke="#9333ea" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
<rect x="80" y="175" width="380" height="200" rx="8" fill="#dbeafe" stroke="#2563eb" stroke-width="1.5" filter="url(#shadow)"/>
<text x="270" y="198" text-anchor="middle" fill="#2563eb" font-size="14" font-weight="700">CTSDB (InfluxDB 兼容)</text>
<rect x="100" y="210" width="340" height="25" rx="4" fill="#eff6ff"/>
<text x="270" y="228" text-anchor="middle" fill="#2563eb" font-size="10">IotCtsdbTimeSeriesDAOImpl</text>
<text x="270" y="255" text-anchor="middle" fill="#374151" font-size="11" font-weight="600">特点</text>
<text x="270" y="275" text-anchor="middle" fill="#6b7280" font-size="10">异步批量写入 (1000条/批 + 1s 自动刷新)</text>
<text x="270" y="295" text-anchor="middle" fill="#6b7280" font-size="10">HTTP REST API 交互</text>
<text x="270" y="315" text-anchor="middle" fill="#6b7280" font-size="10">自动 schema 创建 (基于物模型)</text>
<text x="270" y="335" text-anchor="middle" fill="#6b7280" font-size="10">保留策略: 90天自动过期</text>
<text x="270" y="360" text-anchor="middle" fill="#2563eb" font-size="10" font-weight="600">spring.profiles: ctsdb</text>
<rect x="500" y="175" width="380" height="200" rx="8" fill="#ede9fe" stroke="#9333ea" stroke-width="1.5" filter="url(#shadow)"/>
<text x="690" y="198" text-anchor="middle" fill="#9333ea" font-size="14" font-weight="700">TDengine (JDBC)</text>
<rect x="520" y="210" width="340" height="25" rx="4" fill="#faf5ff"/>
<text x="690" y="228" text-anchor="middle" fill="#9333ea" font-size="10">IotTdEngineTimeSeriesDAOImpl</text>
<text x="690" y="255" text-anchor="middle" fill="#374151" font-size="11" font-weight="600">特点</text>
<text x="690" y="275" text-anchor="middle" fill="#6b7280" font-size="10">同步 JDBC 写入 + PersistenceBuffer</text>
<text x="690" y="295" text-anchor="middle" fill="#6b7280" font-size="10">超级表 (STable) 自动建表</text>
<text x="690" y="315" text-anchor="middle" fill="#6b7280" font-size="10">子表按设备自动创建</text>
<text x="690" y="335" text-anchor="middle" fill="#6b7280" font-size="10">保留策略: DURATION + KEEP 配置</text>
<text x="690" y="360" text-anchor="middle" fill="#9333ea" font-size="10" font-weight="600">spring.profiles: tdengine</text>
<text x="480" y="405" text-anchor="middle" fill="#111827" font-size="12" font-weight="600">存储表结构</text>
<rect x="120" y="415" width="170" height="40" rx="6" fill="#f3f4f6" stroke="#d1d5db" stroke-width="1"/>
<text x="205" y="432" text-anchor="middle" fill="#374151" font-size="10" font-weight="600">device_message</text>
<text x="205" y="448" text-anchor="middle" fill="#6b7280" font-size="9">设备上行消息</text>
<rect x="320" y="415" width="170" height="40" rx="6" fill="#f3f4f6" stroke="#d1d5db" stroke-width="1"/>
<text x="405" y="432" text-anchor="middle" fill="#374151" font-size="10" font-weight="600">product_property_{id}</text>
<text x="405" y="448" text-anchor="middle" fill="#6b7280" font-size="9">产品属性时序</text>
<rect x="560" y="415" width="170" height="40" rx="6" fill="#f3f4f6" stroke="#d1d5db" stroke-width="1"/>
<text x="645" y="432" text-anchor="middle" fill="#374151" font-size="10" font-weight="600">alarm_history</text>
<text x="645" y="448" text-anchor="middle" fill="#6b7280" font-size="9">告警状态变更</text>
<rect x="780" y="415" width="170" height="40" rx="6" fill="#f3f4f6" stroke="#d1d5db" stroke-width="1"/>
<text x="865" y="432" text-anchor="middle" fill="#374151" font-size="10" font-weight="600">rule_debug_log</text>
<text x="865" y="448" text-anchor="middle" fill="#6b7280" font-size="9">规则调试日志</text>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -0,0 +1,66 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 310" width="960" height="310">
<style>
text { font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Microsoft YaHei", sans-serif; }
</style>
<defs>
<marker id="arrow-blue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
</marker>
<marker id="arrow-green" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#16a34a"/>
</marker>
<marker id="arrow-orange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#ea580c"/>
</marker>
<marker id="arrow-purple" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#9333ea"/>
</marker>
<marker id="arrow-red" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#dc2626"/>
</marker>
<marker id="arrow-gray" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#6b7280"/>
</marker>
<marker id="arrow-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#0ea5e9"/>
</marker>
<filter id="shadow" x="-4%" y="-4%" width="108%" height="108%">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#000" flood-opacity="0.08"/>
</filter>
</defs>
<rect width="960" height="310" fill="#ffffff"/>
<text x="480" y="36" text-anchor="middle" fill="#111827" font-size="18" font-weight="700">写入缓冲管道 PersistenceBuffer</text>
<text x="480" y="56" text-anchor="middle" fill="#6b7280" font-size="11">07-数据存储方案 | 内存队列 + 文件持久化 + 批量写入时序库</text>
<rect x="40" y="100" width="160" height="50" rx="8" fill="#fed7aa" stroke="#ea580c" stroke-width="1.5" filter="url(#shadow)"/>
<text x="120" y="119" text-anchor="middle" fill="#ea580c" font-size="12" font-weight="600">设备消息</text>
<text x="120" y="135" text-anchor="middle" fill="#6b7280" font-size="10">IotDeviceMessage</text>
<line x1="200" y1="125" x2="260" y2="125" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow-blue)"/>
<rect x="270" y="85" width="200" height="80" rx="8" fill="#dbeafe" stroke="#2563eb" stroke-width="1.5" filter="url(#shadow)"/>
<text x="370" y="110" text-anchor="middle" fill="#2563eb" font-size="13" font-weight="700">内存队列 L1</text>
<text x="370" y="128" text-anchor="middle" fill="#6b7280" font-size="10">ConcurrentLinkedQueue</text>
<text x="370" y="145" text-anchor="middle" fill="#6b7280" font-size="10">最大 1000 条 / 1s 自动刷新</text>
<line x1="470" y1="125" x2="530" y2="125" stroke="#2563eb" stroke-width="2" marker-end="url(#arrow-blue)"/>
<rect x="478" y="107" width="50" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="503" y="119" text-anchor="middle" fill="#2563eb" font-size="9">批量刷新</text>
<rect x="540" y="85" width="180" height="80" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5" filter="url(#shadow)"/>
<text x="630" y="110" text-anchor="middle" fill="#16a34a" font-size="13" font-weight="700">批量写入器</text>
<text x="630" y="128" text-anchor="middle" fill="#6b7280" font-size="10">BatchInsert 1000条/批</text>
<text x="630" y="145" text-anchor="middle" fill="#6b7280" font-size="10">异步/同步 (按实现)</text>
<line x1="720" y1="125" x2="780" y2="125" stroke="#16a34a" stroke-width="2" marker-end="url(#arrow-green)"/>
<ellipse cx="850" cy="105" rx="60" ry="12" fill="#ede9fe" stroke="#9333ea" stroke-width="1.5"/>
<rect x="790" y="105" width="120" height="45" fill="#ede9fe" stroke="none"/>
<line x1="790" y1="105" x2="790" y2="150" stroke="#9333ea" stroke-width="1.5"/>
<line x1="910" y1="105" x2="910" y2="150" stroke="#9333ea" stroke-width="1.5"/>
<ellipse cx="850" cy="150" rx="60" ry="12" fill="#e9d5ff" stroke="#9333ea" stroke-width="1.5"/>
<text x="850" y="132" text-anchor="middle" fill="#9333ea" font-size="12" font-weight="700">TimeSeries DB</text>
<line x1="370" y1="165" x2="370" y2="210" stroke="#dc2626" stroke-width="1.5" marker-end="url(#arrow-red)"/>
<rect x="270" y="220" width="200" height="65" rx="8" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5" filter="url(#shadow)"/>
<text x="370" y="242" text-anchor="middle" fill="#dc2626" font-size="13" font-weight="700">文件持久化 L2</text>
<text x="370" y="260" text-anchor="middle" fill="#6b7280" font-size="10">内存溢出 / 进程崩溃时写磁盘</text>
<text x="370" y="275" text-anchor="middle" fill="#6b7280" font-size="10">重启后自动重放</text>
<path d="M 470,252 L 530,145" stroke="#dc2626" stroke-width="1.5" fill="none" stroke-dasharray="5,3" marker-end="url(#arrow-red)"/>
<rect x="485" y="188" width="50" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="510" y="200" text-anchor="middle" fill="#dc2626" font-size="9">重启重放</text>
<rect x="338" y="172" width="65" height="16" rx="3" fill="#fff" fill-opacity="0.9"/>
<text x="370" y="184" text-anchor="middle" fill="#dc2626" font-size="9">溢出降级</text>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

View File

@@ -0,0 +1,308 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1100" width="1200" height="1100">
<style>
text { font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Microsoft YaHei", sans-serif; }
</style>
<defs>
<marker id="arrow-blue" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#2563eb"/>
</marker>
<marker id="arrow-green" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#16a34a"/>
</marker>
<marker id="arrow-orange" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#ea580c"/>
</marker>
<marker id="arrow-purple" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#9333ea"/>
</marker>
<marker id="arrow-red" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#dc2626"/>
</marker>
<marker id="arrow-gray" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#6b7280"/>
</marker>
<marker id="arrow-cyan" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#0ea5e9"/>
</marker>
<filter id="shadow" x="-4%" y="-4%" width="108%" height="108%">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-color="#000" flood-opacity="0.08"/>
</filter>
</defs>
<rect width="1200" height="1100" fill="#ffffff"/>
<text x="600" y="36" text-anchor="middle" fill="#111827" font-size="20" font-weight="700">IoT 平台端到端业务链路</text>
<text x="600" y="56" text-anchor="middle" fill="#6b7280" font-size="12">v2.0 架构 | 设备 → 网关 → 总线 → 核心处理 → 规则引擎 → 动作执行 → 存储</text>
<rect x="40" y="72" width="1120" height="70" rx="6" fill="#fff7ed" fill-opacity="0.5" stroke="#fed7aa" stroke-width="1" stroke-dasharray="6,4"/>
<text x="56" y="88" fill="#ea580c" font-size="10" font-weight="600" letter-spacing="0.06em">物理设备层 DEVICE LAYER</text>
<rect x="105" y="96" width="100" height="38" rx="6" fill="#ffffff" stroke="#d1d5db" stroke-width="1.2"/>
<circle cx="133" cy="115" r="10" fill="#dc2626" fill-opacity="0.15" stroke="#dc2626" stroke-width="1"/>
<circle cx="133" cy="115" r="4" fill="#dc2626"/>
<text x="169" y="119" text-anchor="middle" fill="#374151" font-size="10" font-weight="600">温湿度传感器</text>
<rect x="285" y="96" width="100" height="38" rx="6" fill="#ffffff" stroke="#d1d5db" stroke-width="1.2"/>
<circle cx="313" cy="115" r="10" fill="#2563eb" fill-opacity="0.15" stroke="#2563eb" stroke-width="1"/>
<circle cx="313" cy="115" r="4" fill="#2563eb"/>
<text x="349" y="119" text-anchor="middle" fill="#374151" font-size="10" font-weight="600">3D 摄像头</text>
<rect x="465" y="96" width="100" height="38" rx="6" fill="#ffffff" stroke="#d1d5db" stroke-width="1.2"/>
<circle cx="493" cy="115" r="10" fill="#16a34a" fill-opacity="0.15" stroke="#16a34a" stroke-width="1"/>
<circle cx="493" cy="115" r="4" fill="#16a34a"/>
<text x="529" y="119" text-anchor="middle" fill="#374151" font-size="10" font-weight="600">智能控制器</text>
<rect x="645" y="96" width="100" height="38" rx="6" fill="#ffffff" stroke="#d1d5db" stroke-width="1.2"/>
<circle cx="673" cy="115" r="10" fill="#ea580c" fill-opacity="0.15" stroke="#ea580c" stroke-width="1"/>
<circle cx="673" cy="115" r="4" fill="#ea580c"/>
<text x="709" y="119" text-anchor="middle" fill="#374151" font-size="10" font-weight="600">GPS 定位器</text>
<rect x="825" y="96" width="100" height="38" rx="6" fill="#ffffff" stroke="#d1d5db" stroke-width="1.2"/>
<circle cx="853" cy="115" r="10" fill="#9333ea" fill-opacity="0.15" stroke="#9333ea" stroke-width="1"/>
<circle cx="853" cy="115" r="4" fill="#9333ea"/>
<text x="889" y="119" text-anchor="middle" fill="#374151" font-size="10" font-weight="600">Modbus 设备</text>
<rect x="995" y="96" width="100" height="38" rx="6" fill="#ffffff" stroke="#d1d5db" stroke-width="1.2"/>
<circle cx="1023" cy="115" r="10" fill="#6b7280" fill-opacity="0.15" stroke="#6b7280" stroke-width="1"/>
<circle cx="1023" cy="115" r="4" fill="#6b7280"/>
<text x="1059" y="119" text-anchor="middle" fill="#374151" font-size="10" font-weight="600">自定义设备</text>
<line x1="155" y1="134" x2="155" y2="175" stroke="#2563eb" stroke-width="1.2" marker-end="url(#arrow-blue)"/>
<line x1="335" y1="134" x2="335" y2="175" stroke="#2563eb" stroke-width="1.2" marker-end="url(#arrow-blue)"/>
<line x1="515" y1="134" x2="515" y2="175" stroke="#2563eb" stroke-width="1.2" marker-end="url(#arrow-blue)"/>
<line x1="695" y1="134" x2="695" y2="175" stroke="#2563eb" stroke-width="1.2" marker-end="url(#arrow-blue)"/>
<line x1="875" y1="134" x2="875" y2="175" stroke="#2563eb" stroke-width="1.2" marker-end="url(#arrow-blue)"/>
<line x1="1045" y1="134" x2="1045" y2="175" stroke="#2563eb" stroke-width="1.2" marker-end="url(#arrow-blue)"/>
<rect x="40" y="158" width="1120" height="90" rx="6" fill="#eff6ff" fill-opacity="0.4" stroke="#bfdbfe" stroke-width="1" stroke-dasharray="6,4"/>
<text x="56" y="174" fill="#2563eb" font-size="10" font-weight="600" letter-spacing="0.06em">设备接入层 DEVICE GATEWAY &amp; CODEC</text>
<rect x="105" y="182" width="150" height="55" rx="8" fill="#dbeafe" stroke="#2563eb" stroke-width="1.5" filter="url(#shadow)"/>
<text x="180" y="203" text-anchor="middle" fill="#2563eb" font-size="13" font-weight="600">MQTT Broker</text>
<text x="180" y="220" text-anchor="middle" fill="#6b7280" font-size="10">MQTT 3.1 / 5.0</text>
<rect x="315" y="182" width="150" height="55" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5" filter="url(#shadow)"/>
<text x="390" y="203" text-anchor="middle" fill="#16a34a" font-size="13" font-weight="600">HTTP 网关</text>
<text x="390" y="220" text-anchor="middle" fill="#6b7280" font-size="10">REST / CoAP</text>
<rect x="525" y="182" width="150" height="55" rx="8" fill="#ede9fe" stroke="#9333ea" stroke-width="1.5" filter="url(#shadow)"/>
<text x="600" y="203" text-anchor="middle" fill="#9333ea" font-size="13" font-weight="600">TCP 网关</text>
<text x="600" y="220" text-anchor="middle" fill="#6b7280" font-size="10">自定义协议</text>
<rect x="735" y="182" width="150" height="55" rx="8" fill="#fed7aa" stroke="#ea580c" stroke-width="1.5" filter="url(#shadow)"/>
<text x="810" y="203" text-anchor="middle" fill="#ea580c" font-size="13" font-weight="600">EMQX 桥接</text>
<text x="810" y="220" text-anchor="middle" fill="#6b7280" font-size="10">集群桥接</text>
<rect x="935" y="182" width="210" height="55" rx="8" fill="#ffffff" stroke="#d1d5db" stroke-width="1.5" filter="url(#shadow)"/>
<text x="1040" y="200" text-anchor="middle" fill="#111827" font-size="12" font-weight="600">协议解码 Codec SPI</text>
<text x="1040" y="216" text-anchor="middle" fill="#6b7280" font-size="10">ALink | JT808 | Camera3D</text>
<text x="1040" y="230" text-anchor="middle" fill="#9333ea" font-size="9">热加载 JAR 扩展</text>
<line x1="885" y1="210" x2="925" y2="210" stroke="#9333ea" stroke-width="1.2" fill="none" marker-end="url(#arrow-purple)"/>
<line x1="180" y1="237" x2="180" y2="265" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<line x1="390" y1="237" x2="390" y2="265" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<line x1="600" y1="237" x2="600" y2="265" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<line x1="810" y1="237" x2="810" y2="265" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<path d="M 1040,237 L 1040,255 L 600,255 L 600,265" stroke="#2563eb" stroke-width="2" fill="none" marker-end="url(#arrow-blue)"/>
<rect x="730" y="247" width="200" height="16" rx="4" fill="#ffffff" fill-opacity="0.95" stroke="#2563eb" stroke-width="0.5"/>
<text x="830" y="259" text-anchor="middle" fill="#2563eb" font-size="10" font-weight="600">IotDeviceMessage 统一消息模型</text>
<rect x="140" y="275" width="920" height="48" rx="10" fill="#111827" stroke="#374151" stroke-width="1.5" filter="url(#shadow)"/>
<text x="600" y="296" text-anchor="middle" fill="#ffffff" font-size="14" font-weight="600">消息总线 Message Bus</text>
<text x="600" y="312" text-anchor="middle" fill="#9ca3af" font-size="10">LocalMessageBus | RedisStreamMessageBus | RocketMQMessageBus</text>
<line x1="210" y1="323" x2="210" y2="365" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<line x1="410" y1="323" x2="410" y2="365" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<line x1="600" y1="323" x2="600" y2="365" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<line x1="790" y1="323" x2="790" y2="365" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<line x1="990" y1="323" x2="990" y2="365" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<rect x="40" y="350" width="1120" height="100" rx="6" fill="#f0fdf4" fill-opacity="0.4" stroke="#bbf7d0" stroke-width="1" stroke-dasharray="6,4"/>
<text x="56" y="366" fill="#16a34a" font-size="10" font-weight="600" letter-spacing="0.06em">核心处理层 IotDeviceMessageSubscriber</text>
<rect x="130" y="375" width="160" height="60" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5" filter="url(#shadow)"/>
<text x="210" y="400" text-anchor="middle" fill="#16a34a" font-size="12" font-weight="600">设备上下线</text>
<text x="210" y="418" text-anchor="middle" fill="#6b7280" font-size="10">Redis 状态标记</text>
<rect x="330" y="375" width="160" height="60" rx="8" fill="#dbeafe" stroke="#2563eb" stroke-width="1.5" filter="url(#shadow)"/>
<text x="410" y="400" text-anchor="middle" fill="#2563eb" font-size="12" font-weight="600">属性三分类</text>
<text x="410" y="418" text-anchor="middle" fill="#6b7280" font-size="10">CLIENT|SERVER|SHARED</text>
<rect x="520" y="375" width="160" height="60" rx="8" fill="#ede9fe" stroke="#9333ea" stroke-width="1.5" filter="url(#shadow)"/>
<text x="600" y="400" text-anchor="middle" fill="#9333ea" font-size="12" font-weight="600">时序数据写入</text>
<text x="600" y="418" text-anchor="middle" fill="#6b7280" font-size="10">PersistenceBuffer</text>
<rect x="710" y="375" width="160" height="60" rx="8" fill="#fed7aa" stroke="#ea580c" stroke-width="1.5" filter="url(#shadow)"/>
<text x="790" y="400" text-anchor="middle" fill="#ea580c" font-size="12" font-weight="600">物模型合并</text>
<text x="790" y="418" text-anchor="middle" fill="#6b7280" font-size="10">产品 + 设备扩展</text>
<rect x="910" y="375" width="160" height="60" rx="8" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5" filter="url(#shadow)"/>
<text x="990" y="400" text-anchor="middle" fill="#dc2626" font-size="12" font-weight="600">规则引擎触发</text>
<text x="990" y="418" text-anchor="middle" fill="#6b7280" font-size="10">转发至规则链</text>
<path d="M 210,435 L 210,460 L 80,460 L 80,960 L 125,960" stroke="#16a34a" stroke-width="1.2" stroke-dasharray="5,3" fill="none" marker-end="url(#arrow-green)"/>
<rect x="56" y="452" width="60" height="16" rx="3" fill="#ffffff" fill-opacity="0.95"/>
<text x="86" y="464" text-anchor="middle" fill="#16a34a" font-size="8">写入 Redis</text>
<path d="M 410,435 L 410,460 L 100,460 L 100,990 L 125,990" stroke="#2563eb" stroke-width="1.2" stroke-dasharray="5,3" fill="none" marker-end="url(#arrow-blue)"/>
<rect x="76" y="475" width="60" height="16" rx="3" fill="#ffffff" fill-opacity="0.95"/>
<text x="106" y="487" text-anchor="middle" fill="#2563eb" font-size="8">写入 Redis</text>
<path d="M 600,435 L 600,462 L 1135,462 L 1135,955 L 555,955" stroke="#9333ea" stroke-width="1.2" stroke-dasharray="5,3" fill="none" marker-end="url(#arrow-purple)"/>
<rect x="1105" y="470" width="55" height="16" rx="3" fill="#ffffff" fill-opacity="0.95"/>
<text x="1132" y="482" text-anchor="middle" fill="#9333ea" font-size="8">写入 TSDB</text>
<line x1="990" y1="435" x2="990" y2="480" stroke="#dc2626" stroke-width="2" marker-end="url(#arrow-red)"/>
<rect x="40" y="490" width="1120" height="185" rx="6" fill="#faf5ff" fill-opacity="0.4" stroke="#e9d5ff" stroke-width="1" stroke-dasharray="6,4"/>
<text x="56" y="506" fill="#9333ea" font-size="10" font-weight="600" letter-spacing="0.06em">规则引擎层 RULE ENGINE — DAG 编排</text>
<rect x="60" y="518" width="170" height="65" rx="8" fill="#ede9fe" stroke="#9333ea" stroke-width="1.5" filter="url(#shadow)"/>
<text x="145" y="540" text-anchor="middle" fill="#9333ea" font-size="12" font-weight="600">规则链匹配</text>
<text x="145" y="556" text-anchor="middle" fill="#6b7280" font-size="9">子系统 + 产品 + 设备</text>
<text x="145" y="568" text-anchor="middle" fill="#6b7280" font-size="9">+ 全局规则合并</text>
<rect x="255" y="518" width="130" height="65" rx="8" fill="#ffffff" stroke="#9333ea" stroke-width="1.2"/>
<text x="320" y="540" text-anchor="middle" fill="#9333ea" font-size="12" font-weight="600">触发节点</text>
<text x="320" y="556" text-anchor="middle" fill="#6b7280" font-size="9">设备状态 / 属性</text>
<text x="320" y="568" text-anchor="middle" fill="#6b7280" font-size="9">事件 / 定时 / 手动</text>
<rect x="405" y="518" width="130" height="65" rx="8" fill="#ffffff" stroke="#9333ea" stroke-width="1.2"/>
<text x="470" y="540" text-anchor="middle" fill="#9333ea" font-size="12" font-weight="600">数据丰富</text>
<text x="470" y="556" text-anchor="middle" fill="#6b7280" font-size="9">读取历史均值</text>
<text x="470" y="568" text-anchor="middle" fill="#6b7280" font-size="9">关联设备信息</text>
<rect x="555" y="518" width="130" height="65" rx="8" fill="#ffffff" stroke="#9333ea" stroke-width="1.2"/>
<text x="620" y="540" text-anchor="middle" fill="#9333ea" font-size="12" font-weight="600">条件判断</text>
<text x="620" y="556" text-anchor="middle" fill="#6b7280" font-size="9">表达式 / 脚本</text>
<text x="620" y="568" text-anchor="middle" fill="#6b7280" font-size="9">时间范围 / 状态</text>
<rect x="705" y="518" width="130" height="65" rx="8" fill="#ffffff" stroke="#9333ea" stroke-width="1.2"/>
<text x="770" y="540" text-anchor="middle" fill="#9333ea" font-size="12" font-weight="600">抖动过滤</text>
<text x="770" y="556" text-anchor="middle" fill="#6b7280" font-size="9">窗口期 N 秒</text>
<text x="770" y="568" text-anchor="middle" fill="#6b7280" font-size="9">触发 M 次通过</text>
<rect x="855" y="518" width="130" height="65" rx="8" fill="#ffffff" stroke="#9333ea" stroke-width="1.2"/>
<text x="920" y="540" text-anchor="middle" fill="#9333ea" font-size="12" font-weight="600">分支执行</text>
<text x="920" y="556" text-anchor="middle" fill="#6b7280" font-size="9">条件分流</text>
<text x="920" y="568" text-anchor="middle" fill="#6b7280" font-size="9">并行动作链</text>
<line x1="230" y1="550" x2="245" y2="550" stroke="#9333ea" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
<line x1="385" y1="550" x2="395" y2="550" stroke="#9333ea" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
<line x1="535" y1="550" x2="545" y2="550" stroke="#9333ea" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
<line x1="685" y1="550" x2="695" y2="550" stroke="#9333ea" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
<line x1="835" y1="550" x2="845" y2="550" stroke="#9333ea" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
<text x="56" y="610" fill="#9333ea" font-size="10" font-weight="600">动作执行 ACTION NODES</text>
<rect x="70" y="620" width="120" height="45" rx="6" fill="#fee2e2" stroke="#dc2626" stroke-width="1.2"/>
<text x="130" y="639" text-anchor="middle" fill="#dc2626" font-size="11" font-weight="600">告警触发</text>
<text x="130" y="655" text-anchor="middle" fill="#6b7280" font-size="9">alarm_trigger</text>
<rect x="220" y="620" width="120" height="45" rx="6" fill="#fed7aa" stroke="#ea580c" stroke-width="1.2"/>
<text x="280" y="639" text-anchor="middle" fill="#ea580c" font-size="11" font-weight="600">告警清除</text>
<text x="280" y="655" text-anchor="middle" fill="#6b7280" font-size="9">alarm_clear</text>
<rect x="370" y="620" width="120" height="45" rx="6" fill="#dbeafe" stroke="#2563eb" stroke-width="1.2"/>
<text x="430" y="639" text-anchor="middle" fill="#2563eb" font-size="11" font-weight="600">消息通知</text>
<text x="430" y="655" text-anchor="middle" fill="#6b7280" font-size="9">短信/邮件/钉钉</text>
<rect x="520" y="620" width="120" height="45" rx="6" fill="#dcfce7" stroke="#16a34a" stroke-width="1.2"/>
<text x="580" y="639" text-anchor="middle" fill="#16a34a" font-size="11" font-weight="600">设备控制</text>
<text x="580" y="655" text-anchor="middle" fill="#6b7280" font-size="9">RPC / 属性下发</text>
<rect x="670" y="620" width="120" height="45" rx="6" fill="#f3f4f6" stroke="#6b7280" stroke-width="1.2"/>
<text x="730" y="639" text-anchor="middle" fill="#6b7280" font-size="11" font-weight="600">数据转发</text>
<text x="730" y="655" text-anchor="middle" fill="#6b7280" font-size="9">HTTP/MQ/Redis/TCP</text>
<rect x="820" y="620" width="120" height="45" rx="6" fill="#ede9fe" stroke="#9333ea" stroke-width="1.2"/>
<text x="880" y="639" text-anchor="middle" fill="#9333ea" font-size="11" font-weight="600">脚本处理</text>
<text x="880" y="655" text-anchor="middle" fill="#6b7280" font-size="9">Aviator 沙箱</text>
<rect x="980" y="620" width="120" height="45" rx="6" fill="#e0f2fe" stroke="#0ea5e9" stroke-width="1.2"/>
<text x="1040" y="639" text-anchor="middle" fill="#0ea5e9" font-size="11" font-weight="600">Agent 协作</text>
<text x="1040" y="655" text-anchor="middle" fill="#6b7280" font-size="9">ACP 智能体</text>
<line x1="920" y1="583" x2="920" y2="600" stroke="#9333ea" stroke-width="1.5"/>
<line x1="100" y1="600" x2="1070" y2="600" stroke="#9333ea" stroke-width="1.2"/>
<line x1="130" y1="600" x2="130" y2="610" stroke="#9333ea" stroke-width="1.2" marker-end="url(#arrow-purple)"/>
<line x1="280" y1="600" x2="280" y2="610" stroke="#9333ea" stroke-width="1.2" marker-end="url(#arrow-purple)"/>
<line x1="430" y1="600" x2="430" y2="610" stroke="#9333ea" stroke-width="1.2" marker-end="url(#arrow-purple)"/>
<line x1="580" y1="600" x2="580" y2="610" stroke="#9333ea" stroke-width="1.2" marker-end="url(#arrow-purple)"/>
<line x1="730" y1="600" x2="730" y2="610" stroke="#9333ea" stroke-width="1.2" marker-end="url(#arrow-purple)"/>
<line x1="880" y1="600" x2="880" y2="610" stroke="#9333ea" stroke-width="1.2" marker-end="url(#arrow-purple)"/>
<line x1="1040" y1="600" x2="1040" y2="610" stroke="#9333ea" stroke-width="1.2" marker-end="url(#arrow-purple)"/>
<rect x="40" y="700" width="540" height="125" rx="6" fill="#fef2f2" fill-opacity="0.4" stroke="#fecaca" stroke-width="1" stroke-dasharray="6,4"/>
<text x="56" y="716" fill="#dc2626" font-size="10" font-weight="600" letter-spacing="0.06em">告警状态机 ALARM STATE MACHINE</text>
<rect x="75" y="730" width="110" height="36" rx="18" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5"/>
<text x="130" y="753" text-anchor="middle" fill="#dc2626" font-size="10" font-weight="600">活跃 ACTIVE</text>
<rect x="215" y="730" width="110" height="36" rx="18" fill="#fed7aa" stroke="#ea580c" stroke-width="1.5"/>
<text x="270" y="753" text-anchor="middle" fill="#ea580c" font-size="10" font-weight="600">已确认 ACK</text>
<rect x="355" y="730" width="110" height="36" rx="18" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
<text x="410" y="753" text-anchor="middle" fill="#16a34a" font-size="10" font-weight="600">已清除</text>
<rect x="480" y="730" width="90" height="36" rx="18" fill="#f3f4f6" stroke="#6b7280" stroke-width="1.5"/>
<text x="530" y="753" text-anchor="middle" fill="#6b7280" font-size="10" font-weight="600">已解决</text>
<line x1="185" y1="748" x2="205" y2="748" stroke="#ea580c" stroke-width="1.5" marker-end="url(#arrow-orange)"/>
<line x1="325" y1="748" x2="345" y2="748" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
<line x1="465" y1="748" x2="470" y2="748" stroke="#6b7280" stroke-width="1.5" marker-end="url(#arrow-gray)"/>
<text x="70" y="792" fill="#dc2626" font-size="10" font-weight="600">双层存储:</text>
<rect x="60" y="798" width="240" height="18" rx="4" fill="#fee2e2"/>
<text x="180" y="812" text-anchor="middle" fill="#dc2626" font-size="9">MySQL AlarmRecord 当前状态 (Upsert)</text>
<rect x="310" y="798" width="250" height="18" rx="4" fill="#fee2e2"/>
<text x="435" y="812" text-anchor="middle" fill="#dc2626" font-size="9">TimeSeries AlarmHistory 审计轨迹 (Append)</text>
<line x1="130" y1="665" x2="130" y2="690" stroke="#dc2626" stroke-width="1.5" marker-end="url(#arrow-red)"/>
<line x1="280" y1="665" x2="280" y2="690" stroke="#ea580c" stroke-width="1.5" marker-end="url(#arrow-orange)"/>
<rect x="600" y="700" width="260" height="125" rx="6" fill="#eff6ff" fill-opacity="0.4" stroke="#bfdbfe" stroke-width="1" stroke-dasharray="6,4"/>
<text x="616" y="716" fill="#2563eb" font-size="10" font-weight="600" letter-spacing="0.06em">通知体系 NOTIFICATION</text>
<rect x="620" y="730" width="75" height="20" rx="4" fill="#dbeafe"/>
<text x="657" y="744" text-anchor="middle" fill="#2563eb" font-size="9">短信 SMS</text>
<rect x="700" y="730" width="75" height="20" rx="4" fill="#dbeafe"/>
<text x="737" y="744" text-anchor="middle" fill="#2563eb" font-size="9">邮件 Email</text>
<rect x="620" y="755" width="75" height="20" rx="4" fill="#dbeafe"/>
<text x="657" y="769" text-anchor="middle" fill="#2563eb" font-size="9">钉钉机器人</text>
<rect x="700" y="755" width="75" height="20" rx="4" fill="#dbeafe"/>
<text x="737" y="769" text-anchor="middle" fill="#2563eb" font-size="9">企业微信</text>
<rect x="620" y="780" width="75" height="20" rx="4" fill="#dbeafe"/>
<text x="657" y="794" text-anchor="middle" fill="#2563eb" font-size="9">站内消息</text>
<rect x="700" y="780" width="75" height="20" rx="4" fill="#dbeafe"/>
<text x="737" y="794" text-anchor="middle" fill="#2563eb" font-size="9">模板变量</text>
<path d="M 430,665 L 430,685 L 730,685 L 730,690" stroke="#2563eb" stroke-width="1.5" fill="none" marker-end="url(#arrow-blue)"/>
<rect x="880" y="700" width="280" height="125" rx="6" fill="#f0fdf4" fill-opacity="0.4" stroke="#bbf7d0" stroke-width="1" stroke-dasharray="6,4"/>
<text x="896" y="716" fill="#16a34a" font-size="10" font-weight="600" letter-spacing="0.06em">设备控制 RPC 状态机</text>
<rect x="900" y="732" width="72" height="28" rx="14" fill="#ffffff" stroke="#6b7280" stroke-width="1.2"/>
<text x="936" y="751" text-anchor="middle" fill="#6b7280" font-size="10" font-weight="600">排队</text>
<rect x="1000" y="732" width="60" height="28" rx="14" fill="#ffffff" stroke="#2563eb" stroke-width="1.2"/>
<text x="1030" y="751" text-anchor="middle" fill="#2563eb" font-size="10" font-weight="600">已发送</text>
<rect x="1088" y="732" width="60" height="28" rx="14" fill="#ffffff" stroke="#16a34a" stroke-width="1.2"/>
<text x="1118" y="751" text-anchor="middle" fill="#16a34a" font-size="10" font-weight="600">成功</text>
<line x1="972" y1="746" x2="990" y2="746" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<line x1="1060" y1="746" x2="1078" y2="746" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
<text x="1020" y="780" text-anchor="middle" fill="#6b7280" font-size="9">离线排队 → 上线重发 (5msg/s)</text>
<text x="1020" y="795" text-anchor="middle" fill="#6b7280" font-size="9">超时重试 (3次) → 过期清理</text>
<text x="1020" y="810" text-anchor="middle" fill="#6b7280" font-size="9">Shared 属性同步 + 持久化 RPC</text>
<path d="M 580,665 L 580,685 L 1020,685 L 1020,690" stroke="#16a34a" stroke-width="1.5" fill="none" marker-end="url(#arrow-green)"/>
<rect x="40" y="845" width="540" height="70" rx="6" fill="#fff7ed" fill-opacity="0.5" stroke="#fed7aa" stroke-width="1" stroke-dasharray="6,4"/>
<text x="56" y="861" fill="#ea580c" font-size="10" font-weight="600" letter-spacing="0.06em">组织架构 ORGANIZATION HIERARCHY</text>
<rect x="50" y="872" width="110" height="34" rx="6" fill="#ffffff" stroke="#ea580c" stroke-width="1.2"/>
<text x="105" y="894" text-anchor="middle" fill="#ea580c" font-size="10" font-weight="600">租户 Tenant</text>
<rect x="180" y="872" width="110" height="34" rx="6" fill="#ffffff" stroke="#ea580c" stroke-width="1.2"/>
<text x="235" y="894" text-anchor="middle" fill="#ea580c" font-size="10" font-weight="600">项目 Project</text>
<rect x="310" y="872" width="110" height="34" rx="6" fill="#ffffff" stroke="#ea580c" stroke-width="1.2"/>
<text x="365" y="894" text-anchor="middle" fill="#ea580c" font-size="10" font-weight="600">子系统</text>
<rect x="455" y="872" width="110" height="34" rx="6" fill="#ffffff" stroke="#ea580c" stroke-width="1.2"/>
<text x="510" y="894" text-anchor="middle" fill="#ea580c" font-size="10" font-weight="600">设备 Device</text>
<line x1="160" y1="889" x2="170" y2="889" stroke="#ea580c" stroke-width="1.5" marker-end="url(#arrow-orange)"/>
<line x1="290" y1="889" x2="300" y2="889" stroke="#ea580c" stroke-width="1.5" marker-end="url(#arrow-orange)"/>
<line x1="420" y1="889" x2="445" y2="889" stroke="#ea580c" stroke-width="1.5" marker-end="url(#arrow-orange)"/>
<rect x="600" y="845" width="560" height="70" rx="6" fill="#eff6ff" fill-opacity="0.4" stroke="#bfdbfe" stroke-width="1" stroke-dasharray="6,4"/>
<text x="616" y="861" fill="#2563eb" font-size="10" font-weight="600" letter-spacing="0.06em">应用层 APPLICATION LAYER</text>
<rect x="645" y="872" width="110" height="34" rx="6" fill="#dbeafe" stroke="#2563eb" stroke-width="1.2"/>
<text x="700" y="894" text-anchor="middle" fill="#2563eb" font-size="10" font-weight="600">管理后台 UI</text>
<rect x="785" y="872" width="110" height="34" rx="6" fill="#dcfce7" stroke="#16a34a" stroke-width="1.2"/>
<text x="840" y="894" text-anchor="middle" fill="#16a34a" font-size="10" font-weight="600">REST API</text>
<rect x="925" y="872" width="110" height="34" rx="6" fill="#ede9fe" stroke="#9333ea" stroke-width="1.2"/>
<text x="980" y="894" text-anchor="middle" fill="#9333ea" font-size="10" font-weight="600">WebSocket</text>
<rect x="1045" y="872" width="110" height="34" rx="6" fill="#fed7aa" stroke="#ea580c" stroke-width="1.2"/>
<text x="1100" y="894" text-anchor="middle" fill="#ea580c" font-size="10" font-weight="600">Feign 内部调用</text>
<rect x="40" y="935" width="1120" height="130" rx="6" fill="#f3f4f6" fill-opacity="0.5" stroke="#d1d5db" stroke-width="1" stroke-dasharray="6,4"/>
<text x="56" y="951" fill="#6b7280" font-size="10" font-weight="600" letter-spacing="0.06em">存储层 STORAGE LAYER</text>
<ellipse cx="200" cy="968" rx="70" ry="14" fill="#dbeafe" stroke="#4479A1" stroke-width="1.5"/>
<rect x="130" y="968" width="140" height="60" fill="#dbeafe" stroke="none"/>
<line x1="130" y1="968" x2="130" y2="1028" stroke="#4479A1" stroke-width="1.5"/>
<line x1="270" y1="968" x2="270" y2="1028" stroke="#4479A1" stroke-width="1.5"/>
<ellipse cx="200" cy="1028" rx="70" ry="14" fill="#bfdbfe" stroke="#4479A1" stroke-width="1.5"/>
<text x="200" y="994" text-anchor="middle" fill="#4479A1" font-size="13" font-weight="700">MySQL</text>
<text x="200" y="1008" text-anchor="middle" fill="#6b7280" font-size="9">设备 | 规则 | 告警 | RPC</text>
<text x="200" y="1020" text-anchor="middle" fill="#6b7280" font-size="9">产品 | 子系统 | Agent</text>
<ellipse cx="480" cy="968" rx="70" ry="14" fill="#ede9fe" stroke="#9333ea" stroke-width="1.5"/>
<rect x="410" y="968" width="140" height="60" fill="#ede9fe" stroke="none"/>
<line x1="410" y1="968" x2="410" y2="1028" stroke="#9333ea" stroke-width="1.5"/>
<line x1="550" y1="968" x2="550" y2="1028" stroke="#9333ea" stroke-width="1.5"/>
<ellipse cx="480" cy="1028" rx="70" ry="14" fill="#e9d5ff" stroke="#9333ea" stroke-width="1.5"/>
<text x="480" y="994" text-anchor="middle" fill="#9333ea" font-size="13" font-weight="700">TimeSeries DB</text>
<text x="480" y="1008" text-anchor="middle" fill="#6b7280" font-size="9">CTSDB / TDengine</text>
<text x="480" y="1020" text-anchor="middle" fill="#6b7280" font-size="9">设备消息 | 告警历史 | 调试日志</text>
<ellipse cx="760" cy="968" rx="70" ry="14" fill="#fee2e2" stroke="#DC382D" stroke-width="1.5"/>
<rect x="690" y="968" width="140" height="60" fill="#fee2e2" stroke="none"/>
<line x1="690" y1="968" x2="690" y2="1028" stroke="#DC382D" stroke-width="1.5"/>
<line x1="830" y1="968" x2="830" y2="1028" stroke="#DC382D" stroke-width="1.5"/>
<ellipse cx="760" cy="1028" rx="70" ry="14" fill="#fecaca" stroke="#DC382D" stroke-width="1.5"/>
<text x="760" y="994" text-anchor="middle" fill="#DC382D" font-size="13" font-weight="700">Redis</text>
<text x="760" y="1008" text-anchor="middle" fill="#6b7280" font-size="9">CLIENT | SERVER | SHARED</text>
<text x="760" y="1020" text-anchor="middle" fill="#6b7280" font-size="9">在线状态 | 规则缓存 | RPC 超时</text>
<rect x="890" y="962" width="255" height="88" rx="8" fill="#ffffff" stroke="#d1d5db" stroke-width="1.2"/>
<text x="1018" y="982" text-anchor="middle" fill="#111827" font-size="12" font-weight="600">可观测性 Observability</text>
<text x="1018" y="998" text-anchor="middle" fill="#6b7280" font-size="10">Micrometer 指标采集</text>
<text x="1018" y="1014" text-anchor="middle" fill="#6b7280" font-size="9">连接数 | 消息吞吐 | 规则执行耗时</text>
<text x="1018" y="1030" text-anchor="middle" fill="#6b7280" font-size="9">告警延迟 | RPC 成功率 | 编解码错误</text>
<rect x="40" y="1072" width="1120" height="2" fill="#e5e7eb"/>
<g transform="translate(60, 1078)">
<text x="0" y="10" fill="#111827" font-size="10" font-weight="600">图例:</text>
<line x1="45" y1="6" x2="75" y2="6" stroke="#2563eb" stroke-width="1.5" marker-end="url(#arrow-blue)"/>
<text x="81" y="10" fill="#6b7280" font-size="9">数据上行</text>
<line x1="140" y1="6" x2="170" y2="6" stroke="#16a34a" stroke-width="1.5" marker-end="url(#arrow-green)"/>
<text x="176" y="10" fill="#6b7280" font-size="9">存储写入</text>
<line x1="240" y1="6" x2="270" y2="6" stroke="#dc2626" stroke-width="1.5" marker-end="url(#arrow-red)"/>
<text x="276" y="10" fill="#6b7280" font-size="9">告警流</text>
<line x1="330" y1="6" x2="360" y2="6" stroke="#ea580c" stroke-width="1.5" marker-end="url(#arrow-orange)"/>
<text x="366" y="10" fill="#6b7280" font-size="9">控制/组织</text>
<line x1="430" y1="6" x2="460" y2="6" stroke="#9333ea" stroke-width="1.5" marker-end="url(#arrow-purple)"/>
<text x="466" y="10" fill="#6b7280" font-size="9">规则引擎</text>
<line x1="535" y1="6" x2="565" y2="6" stroke="#6b7280" stroke-width="1.2" stroke-dasharray="5,3"/>
<text x="571" y="10" fill="#6b7280" font-size="9">异步/持久化</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -0,0 +1,481 @@
<mxfile host="embed.diagrams.net">
<diagram id="iyRB7K1gAj4k8ZFcetGC" name="第 1 页">
<mxGraphModel dx="1247" dy="673" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="title" parent="1" style="text;html=1;fontSize=22;fontStyle=1;align=center;verticalAlign=middle;fillColor=none;strokeColor=none;fontColor=#1a1a2e;" value="IoT v2.0 端到端业务链路" vertex="1">
<mxGeometry height="35" width="600" x="100" y="10" as="geometry" />
</mxCell>
<mxCell id="subtitle" parent="1" style="text;html=1;fontSize=11;align=center;verticalAlign=middle;fillColor=none;strokeColor=none;fontColor=#888888;" value="从设备上报到最终动作的完整数据流 | 时序库 CTSDB / TDengine 双实现可切换" vertex="1">
<mxGeometry height="18" width="600" x="100" y="42" as="geometry" />
</mxCell>
<mxCell id="sec1" parent="1" style="text;html=1;fontSize=13;fontStyle=1;align=left;verticalAlign=middle;fillColor=#e8eaf6;strokeColor=none;fontColor=#3949ab;rounded=1;" value="① 设备接入层Gateway" vertex="1">
<mxGeometry height="24" width="760" x="20" y="68" as="geometry" />
</mxCell>
<mxCell id="dev_box" parent="1" style="rounded=1;arcSize=12;fillColor=#f5f5f5;strokeColor=#bdbdbd;dashed=1;" value="" vertex="1">
<mxGeometry height="90" width="130" x="30" y="100" as="geometry" />
</mxCell>
<mxCell id="dev_t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=none;strokeColor=none;fontColor=#616161;" value="物理设备" vertex="1">
<mxGeometry height="16" width="80" x="55" y="103" as="geometry" />
</mxCell>
<mxCell id="d1" parent="1" style="rounded=1;fillColor=#78909c;strokeColor=#546e7a;fontColor=#fff;fontSize=9;" value="传感器" vertex="1">
<mxGeometry height="22" width="54" x="38" y="123" as="geometry" />
</mxCell>
<mxCell id="d2" parent="1" style="rounded=1;fillColor=#78909c;strokeColor=#546e7a;fontColor=#fff;fontSize=9;" value="控制器" vertex="1">
<mxGeometry height="22" width="54" x="98" y="123" as="geometry" />
</mxCell>
<mxCell id="d3" parent="1" style="rounded=1;fillColor=#78909c;strokeColor=#546e7a;fontColor=#fff;fontSize=9;" value="摄像头" vertex="1">
<mxGeometry height="22" width="54" x="38" y="150" as="geometry" />
</mxCell>
<mxCell id="d4" parent="1" style="rounded=1;fillColor=#78909c;strokeColor=#546e7a;fontColor=#fff;fontSize=9;" value="工牌 ..." vertex="1">
<mxGeometry height="22" width="54" x="98" y="150" as="geometry" />
</mxCell>
<mxCell id="e_dev_gw" edge="1" parent="1" source="dev_box" style="endArrow=classic;strokeColor=#5c6bc0;strokeWidth=2;exitX=1;exitY=0.5;entryX=0;entryY=0.35;" target="gw_box" value="">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="proto" parent="1" style="text;html=1;fontSize=8;align=center;fillColor=none;strokeColor=none;fontColor=#5c6bc0;fontStyle=2;" value="MQTT / HTTP / TCP&#xa;原始报文 →" vertex="1">
<mxGeometry height="24" width="100" x="168" y="127" as="geometry" />
</mxCell>
<mxCell id="gw_box" parent="1" style="rounded=1;arcSize=8;fillColor=#e8eaf6;strokeColor=#5c6bc0;strokeWidth=2;" value="" vertex="1">
<mxGeometry height="95" width="500" x="275" y="98" as="geometry" />
</mxCell>
<mxCell id="gw_t" parent="1" style="text;html=1;fontSize=11;fontStyle=1;align=left;fillColor=none;strokeColor=none;fontColor=#3949ab;" value="网关Gateway" vertex="1">
<mxGeometry height="18" width="140" x="285" y="100" as="geometry" />
</mxCell>
<mxCell id="codec" parent="1" style="rounded=1;fillColor=#5c6bc0;strokeColor=#3949ab;fontColor=#fff;fontSize=10;fontStyle=1;" value="Codec SPI" vertex="1">
<mxGeometry height="26" width="85" x="290" y="122" as="geometry" />
</mxCell>
<mxCell id="c1" parent="1" style="rounded=1;fillColor=#7986cb;strokeColor=#5c6bc0;fontColor=#fff;fontSize=9;" value="ALink" vertex="1">
<mxGeometry height="22" width="60" x="385" y="122" as="geometry" />
</mxCell>
<mxCell id="c2" parent="1" style="rounded=1;fillColor=#7986cb;strokeColor=#5c6bc0;fontColor=#fff;fontSize=9;" value="JT808" vertex="1">
<mxGeometry height="22" width="60" x="450" y="122" as="geometry" />
</mxCell>
<mxCell id="c3" parent="1" style="rounded=1;fillColor=#7986cb;strokeColor=#5c6bc0;fontColor=#fff;fontSize=9;" value="Camera3D11" vertex="1">
<mxGeometry height="22" width="80" x="515" y="122" as="geometry" />
</mxCell>
<mxCell id="c4" parent="1" style="rounded=1;fillColor=#7986cb;strokeColor=#5c6bc0;fontColor=#fff;fontSize=9;" value="自定义 ..." vertex="1">
<mxGeometry height="22" width="65" x="600" y="122" as="geometry" />
</mxCell>
<mxCell id="decode_arr" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#5c6bc0;fontStyle=2;" value="解码 ↓" vertex="1">
<mxGeometry height="14" width="50" x="290" y="148" as="geometry" />
</mxCell>
<mxCell id="msg_model" parent="1" style="rounded=1;fillColor=#3949ab;strokeColor=#283593;fontColor=#ffffff;fontSize=9;align=center;whiteSpace=wrap;" value="IotDeviceMessage统一消息模型{ deviceKey, productKey, type, identifier, data, timestamp }" vertex="1">
<mxGeometry height="24" width="475" x="290" y="162" as="geometry" />
</mxCell>
<mxCell id="sec2" parent="1" style="text;html=1;fontSize=13;fontStyle=1;align=left;verticalAlign=middle;fillColor=#fce4ec;strokeColor=none;fontColor=#c62828;rounded=1;" value="② 消息总线Message Bus— 三种实现按部署模式选择" vertex="1">
<mxGeometry height="24" width="760" x="20" y="205" as="geometry" />
</mxCell>
<mxCell id="e_msg_bus" edge="1" parent="1" style="endArrow=classic;strokeColor=#e53935;strokeWidth=2;" value="">
<mxGeometry relative="1" as="geometry">
<mxPoint x="528" y="186" as="sourcePoint" />
<mxPoint x="400" y="236" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="bus_box" parent="1" style="rounded=1;arcSize=12;fillColor=#ffebee;strokeColor=#e53935;strokeWidth=2;" value="" vertex="1">
<mxGeometry height="36" width="640" x="80" y="235" as="geometry" />
</mxCell>
<mxCell id="bus1" parent="1" style="rounded=1;fillColor=#e53935;strokeColor=#c62828;fontColor=#fff;fontSize=9;" value="LocalMessageBus单机" vertex="1">
<mxGeometry height="24" width="175" x="95" y="241" as="geometry" />
</mxCell>
<mxCell id="bus2" parent="1" style="rounded=1;fillColor=#e53935;strokeColor=#c62828;fontColor=#fff;fontSize=9;" value="RedisStreamMessageBus" vertex="1">
<mxGeometry height="24" width="175" x="313" y="241" as="geometry" />
</mxCell>
<mxCell id="bus3" parent="1" style="rounded=1;fillColor=#e53935;strokeColor=#c62828;fontColor=#fff;fontSize=9;" value="RocketMQMessageBus" vertex="1">
<mxGeometry height="24" width="175" x="531" y="241" as="geometry" />
</mxCell>
<mxCell id="sec3" parent="1" style="text;html=1;fontSize=13;fontStyle=1;align=left;verticalAlign=middle;fillColor=#e8f5e9;strokeColor=none;fontColor=#2e7d32;rounded=1;" value="③ 核心服务层Core Services" vertex="1">
<mxGeometry height="24" width="760" x="20" y="283" as="geometry" />
</mxCell>
<mxCell id="e_bus_sub" edge="1" parent="1" style="endArrow=classic;strokeColor=#43a047;strokeWidth=2;" value="">
<mxGeometry relative="1" as="geometry">
<mxPoint x="400" y="271" as="sourcePoint" />
<mxPoint x="400" y="314" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="subscriber" parent="1" style="rounded=1;fillColor=#2e7d32;strokeColor=#1b5e20;fontColor=#ffffff;fontSize=12;fontStyle=1;" value="IotDeviceMessageSubscriber统一消息消费者" vertex="1">
<mxGeometry height="30" width="500" x="150" y="314" as="geometry" />
</mxCell>
<mxCell id="b1" parent="1" style="rounded=1;arcSize=10;fillColor=#e8f5e9;strokeColor=#66bb6a;" value="" vertex="1">
<mxGeometry height="105" width="148" x="20" y="362" as="geometry" />
</mxCell>
<mxCell id="b1t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#43a047;strokeColor=none;fontColor=#fff;rounded=1;" value="① 设备状态更新" vertex="1">
<mxGeometry height="18" width="140" x="24" y="365" as="geometry" />
</mxCell>
<mxCell id="b1a" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="在线/离线 → Redis 标记" vertex="1">
<mxGeometry height="14" width="136" x="28" y="388" as="geometry" />
</mxCell>
<mxCell id="b1b" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#666;fontStyle=2;" value="上线触发:" vertex="1">
<mxGeometry height="14" width="60" x="28" y="404" as="geometry" />
</mxCell>
<mxCell id="b1c" parent="1" style="text;html=1;fontSize=7;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="· Shared 属性同步" vertex="1">
<mxGeometry height="12" width="136" x="28" y="419" as="geometry" />
</mxCell>
<mxCell id="b1d" parent="1" style="text;html=1;fontSize=7;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="· Pending RPC 补发(5条/秒)" vertex="1">
<mxGeometry height="12" width="136" x="28" y="432" as="geometry" />
</mxCell>
<mxCell id="b1ref" parent="1" style="text;html=1;fontSize=7;align=center;fillColor=#fff3e0;strokeColor=none;fontColor=#e65100;rounded=1;" value="← 06-设备影子" vertex="1">
<mxGeometry height="14" width="80" x="44" y="448" as="geometry" />
</mxCell>
<mxCell id="b2" parent="1" style="rounded=1;arcSize=10;fillColor=#e8f5e9;strokeColor=#66bb6a;" value="" vertex="1">
<mxGeometry height="105" width="148" x="178" y="362" as="geometry" />
</mxCell>
<mxCell id="b2t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#43a047;strokeColor=none;fontColor=#fff;rounded=1;" value="② 属性三元分类写入" vertex="1">
<mxGeometry height="18" width="140" x="182" y="365" as="geometry" />
</mxCell>
<mxCell id="b2a" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="CLIENT → Redis :client" vertex="1">
<mxGeometry height="14" width="136" x="186" y="390" as="geometry" />
</mxCell>
<mxCell id="b2b" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="SERVER → Redis :server" vertex="1">
<mxGeometry height="14" width="136" x="186" y="407" as="geometry" />
</mxCell>
<mxCell id="b2c" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="SHARED → Redis + MySQL" vertex="1">
<mxGeometry height="14" width="136" x="186" y="424" as="geometry" />
</mxCell>
<mxCell id="b2ref" parent="1" style="text;html=1;fontSize=7;align=center;fillColor=#fff3e0;strokeColor=none;fontColor=#e65100;rounded=1;" value="← 03-物模型v2" vertex="1">
<mxGeometry height="14" width="80" x="202" y="448" as="geometry" />
</mxCell>
<mxCell id="b3" parent="1" style="rounded=1;arcSize=10;fillColor=#e8f5e9;strokeColor=#66bb6a;" value="" vertex="1">
<mxGeometry height="105" width="148" x="336" y="362" as="geometry" />
</mxCell>
<mxCell id="b3t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#43a047;strokeColor=none;fontColor=#fff;rounded=1;" value="③ 时序数据持久化" vertex="1">
<mxGeometry height="18" width="140" x="340" y="365" as="geometry" />
</mxCell>
<mxCell id="b3a" parent="1" style="text;html=1;fontSize=9;align=center;fillColor=none;strokeColor=none;fontColor=#333;fontStyle=1;" value="PersistenceBuffer" vertex="1">
<mxGeometry height="14" width="128" x="348" y="390" as="geometry" />
</mxCell>
<mxCell id="b3b" parent="1" style="text;html=1;fontSize=8;align=center;fillColor=none;strokeColor=none;fontColor=#666;" value="内存队列 + 定时刷新" vertex="1">
<mxGeometry height="14" width="128" x="348" y="406" as="geometry" />
</mxCell>
<mxCell id="b3c" parent="1" style="text;html=1;fontSize=8;align=center;fillColor=none;strokeColor=none;fontColor=#43a047;fontStyle=2;" value="↓ 批量写入" vertex="1">
<mxGeometry height="14" width="128" x="348" y="422" as="geometry" />
</mxCell>
<mxCell id="b3d" parent="1" style="text;html=1;fontSize=8;align=center;fillColor=none;strokeColor=none;fontColor=#333;" value="CTSDB / TDengine可切换" vertex="1">
<mxGeometry height="14" width="140" x="340" y="437" as="geometry" />
</mxCell>
<mxCell id="b3ref" parent="1" style="text;html=1;fontSize=7;align=center;fillColor=#fff3e0;strokeColor=none;fontColor=#e65100;rounded=1;" value="← 07-数据存储" vertex="1">
<mxGeometry height="12" width="80" x="370" y="453" as="geometry" />
</mxCell>
<mxCell id="b4" parent="1" style="rounded=1;arcSize=10;fillColor=#e8f5e9;strokeColor=#66bb6a;" value="" vertex="1">
<mxGeometry height="105" width="148" x="494" y="362" as="geometry" />
</mxCell>
<mxCell id="b4t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#43a047;strokeColor=none;fontColor=#fff;rounded=1;" value="④ 派生物模型合并" vertex="1">
<mxGeometry height="18" width="140" x="498" y="365" as="geometry" />
</mxCell>
<mxCell id="b4a" parent="1" style="text;html=1;fontSize=8;align=center;fillColor=none;strokeColor=none;fontColor=#333;" value="设备级覆盖产品定义" vertex="1">
<mxGeometry height="14" width="128" x="506" y="392" as="geometry" />
</mxCell>
<mxCell id="b4b" parent="1" style="text;html=1;fontSize=8;align=center;fillColor=none;strokeColor=none;fontColor=#666;fontStyle=2;" value="derive_metadata JSON" vertex="1">
<mxGeometry height="14" width="128" x="506" y="410" as="geometry" />
</mxCell>
<mxCell id="b4ref" parent="1" style="text;html=1;fontSize=7;align=center;fillColor=#fff3e0;strokeColor=none;fontColor=#e65100;rounded=1;" value="← 03-物模型v2" vertex="1">
<mxGeometry height="14" width="80" x="528" y="432" as="geometry" />
</mxCell>
<mxCell id="b5" parent="1" style="rounded=1;arcSize=10;fillColor=#fff3e0;strokeColor=#fb8c00;strokeWidth=2;" value="" vertex="1">
<mxGeometry height="105" width="128" x="652" y="362" as="geometry" />
</mxCell>
<mxCell id="b5t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#fb8c00;strokeColor=none;fontColor=#fff;rounded=1;" value="⑤ 规则引擎触发" vertex="1">
<mxGeometry height="18" width="120" x="656" y="365" as="geometry" />
</mxCell>
<mxCell id="b5a" parent="1" style="text;html=1;fontSize=8;align=center;fillColor=none;strokeColor=none;fontColor=#333;" value="将消息转发到&#xa;规则引擎层处理" vertex="1">
<mxGeometry height="28" width="108" x="664" y="390" as="geometry" />
</mxCell>
<mxCell id="b5arr" parent="1" style="text;html=1;fontSize=18;fontStyle=1;align=center;fillColor=none;strokeColor=none;fontColor=#fb8c00;" value="↓" vertex="1">
<mxGeometry height="24" width="50" x="690" y="420" as="geometry" />
</mxCell>
<mxCell id="b5ref" parent="1" style="text;html=1;fontSize=7;align=center;fillColor=#fff3e0;strokeColor=none;fontColor=#e65100;rounded=1;" value="← 04-规则引擎" vertex="1">
<mxGeometry height="14" width="80" x="676" y="448" as="geometry" />
</mxCell>
<mxCell id="es1" edge="1" parent="1" source="subscriber" style="endArrow=classic;strokeColor=#43a047;strokeWidth=1.5;exitX=0.08;exitY=1;entryX=0.5;entryY=0;" target="b1" value="">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="es2" edge="1" parent="1" source="subscriber" style="endArrow=classic;strokeColor=#43a047;strokeWidth=1.5;exitX=0.24;exitY=1;entryX=0.5;entryY=0;" target="b2" value="">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="es3" edge="1" parent="1" source="subscriber" style="endArrow=classic;strokeColor=#43a047;strokeWidth=1.5;exitX=0.46;exitY=1;entryX=0.5;entryY=0;" target="b3" value="">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="es4" edge="1" parent="1" source="subscriber" style="endArrow=classic;strokeColor=#43a047;strokeWidth=1.5;exitX=0.68;exitY=1;entryX=0.5;entryY=0;" target="b4" value="">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="es5" edge="1" parent="1" source="subscriber" style="endArrow=classic;strokeColor=#fb8c00;strokeWidth=2;exitX=0.92;exitY=1;entryX=0.5;entryY=0;" target="b5" value="">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="sec4" parent="1" style="text;html=1;fontSize=13;fontStyle=1;align=left;verticalAlign=middle;fillColor=#fff3e0;strokeColor=none;fontColor=#e65100;rounded=1;" value="④ 规则引擎层Rule Engine— DAG 编排执行" vertex="1">
<mxGeometry height="24" width="760" x="20" y="480" as="geometry" />
</mxCell>
<mxCell id="rule_handler" parent="1" style="rounded=1;fillColor=#e65100;strokeColor=#bf360c;fontColor=#ffffff;fontSize=11;fontStyle=1;" value="IotRuleEngineMessageHandler统一入口" vertex="1">
<mxGeometry height="28" width="460" x="170" y="510" as="geometry" />
</mxCell>
<mxCell id="rule_note" parent="1" style="text;html=1;fontSize=8;align=center;fillColor=none;strokeColor=none;fontColor=#e65100;fontStyle=2;" value="按 subsystemId + productId + deviceId 匹配规则链(全量缓存)| 链级 try-catch 隔离" vertex="1">
<mxGeometry height="14" width="560" x="120" y="540" as="geometry" />
</mxCell>
<mxCell id="eb5r" edge="1" parent="1" source="b5" style="endArrow=classic;strokeColor=#fb8c00;strokeWidth=2;exitX=0.5;exitY=1;entryX=0.8;entryY=0;" target="rule_handler" value="">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="dag" parent="1" style="rounded=1;arcSize=6;fillColor=#fff8e1;strokeColor=#fb8c00;strokeWidth=2;" value="" vertex="1">
<mxGeometry height="75" width="760" x="20" y="560" as="geometry" />
</mxCell>
<mxCell id="dag_t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=left;fillColor=none;strokeColor=none;fontColor=#e65100;" value="RuleChain DAG 执行管线" vertex="1">
<mxGeometry height="16" width="200" x="30" y="563" as="geometry" />
</mxCell>
<mxCell id="p1" parent="1" style="rounded=1;fillColor=#ff8f00;strokeColor=#e65100;fontColor=#fff;fontSize=10;fontStyle=1;" value="Trigger&#xa;触发器" vertex="1">
<mxGeometry height="40" width="85" x="35" y="585" as="geometry" />
</mxCell>
<mxCell id="pa1" parent="1" style="text;html=1;fontSize=14;fontStyle=1;align=center;fillColor=none;strokeColor=none;fontColor=#e65100;" value="→" vertex="1">
<mxGeometry height="20" width="20" x="120" y="595" as="geometry" />
</mxCell>
<mxCell id="p2" parent="1" style="rounded=1;fillColor=#ff8f00;strokeColor=#e65100;fontColor=#fff;fontSize=10;fontStyle=1;" value="Enrich&#xa;数据富化" vertex="1">
<mxGeometry height="40" width="90" x="140" y="585" as="geometry" />
</mxCell>
<mxCell id="pa2" parent="1" style="text;html=1;fontSize=14;fontStyle=1;align=center;fillColor=none;strokeColor=none;fontColor=#e65100;" value="→" vertex="1">
<mxGeometry height="20" width="20" x="230" y="595" as="geometry" />
</mxCell>
<mxCell id="p3" parent="1" style="rounded=1;fillColor=#ff8f00;strokeColor=#e65100;fontColor=#fff;fontSize=10;fontStyle=1;" value="Condition&#xa;条件评估" vertex="1">
<mxGeometry height="40" width="95" x="250" y="585" as="geometry" />
</mxCell>
<mxCell id="pa3" parent="1" style="text;html=1;fontSize=14;fontStyle=1;align=center;fillColor=none;strokeColor=none;fontColor=#e65100;" value="→" vertex="1">
<mxGeometry height="20" width="20" x="345" y="595" as="geometry" />
</mxCell>
<mxCell id="p4" parent="1" style="rounded=1;fillColor=#ff8f00;strokeColor=#e65100;fontColor=#fff;fontSize=10;fontStyle=1;" value="ShakeLimit&#xa;抖动抑制" vertex="1">
<mxGeometry height="40" width="95" x="365" y="585" as="geometry" />
</mxCell>
<mxCell id="pa4" parent="1" style="text;html=1;fontSize=14;fontStyle=1;align=center;fillColor=none;strokeColor=none;fontColor=#e65100;" value="→" vertex="1">
<mxGeometry height="20" width="20" x="460" y="595" as="geometry" />
</mxCell>
<mxCell id="p5" parent="1" style="rhombus;fillColor=#ff8f00;strokeColor=#e65100;fontColor=#fff;fontSize=10;fontStyle=1;" value="Branch&#xa;分支" vertex="1">
<mxGeometry height="46" width="90" x="480" y="582" as="geometry" />
</mxCell>
<mxCell id="pa5" parent="1" style="text;html=1;fontSize=14;fontStyle=1;align=center;fillColor=none;strokeColor=none;fontColor=#e65100;" value="→" vertex="1">
<mxGeometry height="20" width="20" x="570" y="595" as="geometry" />
</mxCell>
<mxCell id="p6" parent="1" style="rounded=1;fillColor=#e65100;strokeColor=#bf360c;fontColor=#fff;fontSize=11;fontStyle=1;" value="Actions&#xa;动作执行" vertex="1">
<mxGeometry height="40" width="100" x="590" y="585" as="geometry" />
</mxCell>
<mxCell id="sec5" parent="1" style="text;html=1;fontSize=13;fontStyle=1;align=left;verticalAlign=middle;fillColor=#fbe9e7;strokeColor=none;fontColor=#bf360c;rounded=1;" value="⑤ 动作层Actions— 6 大类别" vertex="1">
<mxGeometry height="24" width="760" x="20" y="648" as="geometry" />
</mxCell>
<mxCell id="ea_detail" edge="1" parent="1" style="endArrow=classic;strokeColor=#e65100;strokeWidth=2;" value="">
<mxGeometry relative="1" as="geometry">
<mxPoint x="640" y="625" as="sourcePoint" />
<mxPoint x="400" y="648" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="a1" parent="1" style="rounded=1;arcSize=8;fillColor=#e3f2fd;strokeColor=#1e88e5;strokeWidth=1.5;" value="" vertex="1">
<mxGeometry height="95" width="245" x="20" y="680" as="geometry" />
</mxCell>
<mxCell id="a1t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#1e88e5;strokeColor=none;fontColor=#fff;rounded=1;" value="设备控制" vertex="1">
<mxGeometry height="17" width="235" x="25" y="683" as="geometry" />
</mxCell>
<mxCell id="a1a" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="device_property_set — Shared 属性下发" vertex="1">
<mxGeometry height="14" width="228" x="30" y="705" as="geometry" />
</mxCell>
<mxCell id="a1b" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="device_service_invoke — 持久化 RPC 调用" vertex="1">
<mxGeometry height="14" width="228" x="30" y="721" as="geometry" />
</mxCell>
<mxCell id="a1c" parent="1" style="text;html=1;fontSize=7;align=center;fillColor=#e3f2fd;strokeColor=#90caf9;fontColor=#1565c0;rounded=1;" value="RPC 状态机: QUEUED → SENT → SUCCESS" vertex="1">
<mxGeometry height="14" width="220" x="35" y="739" as="geometry" />
</mxCell>
<mxCell id="a1d" parent="1" style="text;html=1;fontSize=7;align=center;fillColor=none;strokeColor=none;fontColor=#666;fontStyle=2;" value="设备离线 → 持久化等待 → 上线补发" vertex="1">
<mxGeometry height="14" width="220" x="35" y="755" as="geometry" />
</mxCell>
<mxCell id="a2" parent="1" style="rounded=1;arcSize=8;fillColor=#fce4ec;strokeColor=#e53935;strokeWidth=1.5;" value="" vertex="1">
<mxGeometry height="95" width="245" x="275" y="680" as="geometry" />
</mxCell>
<mxCell id="a2t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#e53935;strokeColor=none;fontColor=#fff;rounded=1;" value="告警处理" vertex="1">
<mxGeometry height="17" width="235" x="280" y="683" as="geometry" />
</mxCell>
<mxCell id="a2a" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="alarm_trigger — 触发告警 (ACTIVE)" vertex="1">
<mxGeometry height="14" width="228" x="283" y="705" as="geometry" />
</mxCell>
<mxCell id="a2b" parent="1" style="text;html=1;fontSize=7;align=left;fillColor=none;strokeColor=none;fontColor=#666;" value=" upsert AlarmRecord (MySQL 幂等)" vertex="1">
<mxGeometry height="12" width="228" x="283" y="720" as="geometry" />
</mxCell>
<mxCell id="a2c" parent="1" style="text;html=1;fontSize=7;align=left;fillColor=none;strokeColor=none;fontColor=#666;" value=" append AlarmHistory (时序库)" vertex="1">
<mxGeometry height="12" width="228" x="283" y="733" as="geometry" />
</mxCell>
<mxCell id="a2d" parent="1" style="text;html=1;fontSize=7;align=left;fillColor=none;strokeColor=none;fontColor=#666;" value=" 告警传播(沿子系统层级向上)" vertex="1">
<mxGeometry height="12" width="228" x="283" y="746" as="geometry" />
</mxCell>
<mxCell id="a2e" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="alarm_clear — 清除告警 (CLEARED)" vertex="1">
<mxGeometry height="12" width="228" x="283" y="760" as="geometry" />
</mxCell>
<mxCell id="a3" parent="1" style="rounded=1;arcSize=8;fillColor=#e0f2f1;strokeColor=#00897b;strokeWidth=1.5;" value="" vertex="1">
<mxGeometry height="95" width="250" x="530" y="680" as="geometry" />
</mxCell>
<mxCell id="a3t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#00897b;strokeColor=none;fontColor=#fff;rounded=1;" value="数据转发" vertex="1">
<mxGeometry height="17" width="240" x="535" y="683" as="geometry" />
</mxCell>
<mxCell id="a3a" parent="1" style="rounded=1;fillColor=#26a69a;strokeColor=#00897b;fontColor=#fff;fontSize=8;" value="http_push" vertex="1">
<mxGeometry height="22" width="70" x="540" y="708" as="geometry" />
</mxCell>
<mxCell id="a3b" parent="1" style="rounded=1;fillColor=#26a69a;strokeColor=#00897b;fontColor=#fff;fontSize=8;" value="mq_push" vertex="1">
<mxGeometry height="22" width="70" x="618" y="708" as="geometry" />
</mxCell>
<mxCell id="a3c" parent="1" style="rounded=1;fillColor=#26a69a;strokeColor=#00897b;fontColor=#fff;fontSize=8;" value="redis_push" vertex="1">
<mxGeometry height="22" width="70" x="696" y="708" as="geometry" />
</mxCell>
<mxCell id="a3d" parent="1" style="rounded=1;fillColor=#26a69a;strokeColor=#00897b;fontColor=#fff;fontSize=8;" value="tcp_push" vertex="1">
<mxGeometry height="22" width="70" x="540" y="738" as="geometry" />
</mxCell>
<mxCell id="a3e" parent="1" style="text;html=1;fontSize=7;align=left;fillColor=none;strokeColor=none;fontColor=#666;fontStyle=2;" value="Webhook / RocketMQ&#xa;Kafka / RabbitMQ" vertex="1">
<mxGeometry height="20" width="150" x="618" y="740" as="geometry" />
</mxCell>
<mxCell id="a4" parent="1" style="rounded=1;arcSize=8;fillColor=#f3e5f5;strokeColor=#8e24aa;strokeWidth=1.5;" value="" vertex="1">
<mxGeometry height="68" width="245" x="20" y="785" as="geometry" />
</mxCell>
<mxCell id="a4t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#8e24aa;strokeColor=none;fontColor=#fff;rounded=1;" value="通知" vertex="1">
<mxGeometry height="17" width="235" x="25" y="788" as="geometry" />
</mxCell>
<mxCell id="a4a" parent="1" style="rounded=1;fillColor=#ab47bc;strokeColor=#8e24aa;fontColor=#fff;fontSize=9;" value="短信" vertex="1">
<mxGeometry height="20" width="42" x="30" y="812" as="geometry" />
</mxCell>
<mxCell id="a4b" parent="1" style="rounded=1;fillColor=#ab47bc;strokeColor=#8e24aa;fontColor=#fff;fontSize=9;" value="邮件" vertex="1">
<mxGeometry height="20" width="42" x="78" y="812" as="geometry" />
</mxCell>
<mxCell id="a4c" parent="1" style="rounded=1;fillColor=#ab47bc;strokeColor=#8e24aa;fontColor=#fff;fontSize=9;" value="站内信" vertex="1">
<mxGeometry height="20" width="42" x="126" y="812" as="geometry" />
</mxCell>
<mxCell id="a4d" parent="1" style="rounded=1;fillColor=#ab47bc;strokeColor=#8e24aa;fontColor=#fff;fontSize=9;" value="企微" vertex="1">
<mxGeometry height="20" width="38" x="174" y="812" as="geometry" />
</mxCell>
<mxCell id="a4e" parent="1" style="rounded=1;fillColor=#ab47bc;strokeColor=#8e24aa;fontColor=#fff;fontSize=9;" value="钉钉" vertex="1">
<mxGeometry height="20" width="38" x="218" y="812" as="geometry" />
</mxCell>
<mxCell id="a4f" parent="1" style="text;html=1;fontSize=7;align=center;fillColor=none;strokeColor=none;fontColor=#666;fontStyle=2;" value="模板变量: ${alarm.name} ${device.name}" vertex="1">
<mxGeometry height="12" width="228" x="30" y="836" as="geometry" />
</mxCell>
<mxCell id="a5" parent="1" style="rounded=1;arcSize=8;fillColor=#fff3e0;strokeColor=#fb8c00;strokeWidth=1.5;" value="" vertex="1">
<mxGeometry height="68" width="245" x="275" y="785" as="geometry" />
</mxCell>
<mxCell id="a5t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#fb8c00;strokeColor=none;fontColor=#fff;rounded=1;" value="数据处理" vertex="1">
<mxGeometry height="17" width="235" x="280" y="788" as="geometry" />
</mxCell>
<mxCell id="a5a" parent="1" style="rounded=1;fillColor=#ffa726;strokeColor=#fb8c00;fontColor=#fff;fontSize=9;" value="script" vertex="1">
<mxGeometry height="20" width="52" x="285" y="812" as="geometry" />
</mxCell>
<mxCell id="a5b" parent="1" style="rounded=1;fillColor=#ffa726;strokeColor=#fb8c00;fontColor=#fff;fontSize=9;" value="enrich" vertex="1">
<mxGeometry height="20" width="52" x="343" y="812" as="geometry" />
</mxCell>
<mxCell id="a5c" parent="1" style="rounded=1;fillColor=#ffa726;strokeColor=#fb8c00;fontColor=#fff;fontSize=9;" value="delay" vertex="1">
<mxGeometry height="20" width="52" x="401" y="812" as="geometry" />
</mxCell>
<mxCell id="a5d" parent="1" style="rounded=1;fillColor=#ffa726;strokeColor=#fb8c00;fontColor=#fff;fontSize=9;" value="log" vertex="1">
<mxGeometry height="20" width="52" x="459" y="812" as="geometry" />
</mxCell>
<mxCell id="a5e" parent="1" style="text;html=1;fontSize=7;align=center;fillColor=none;strokeColor=none;fontColor=#666;fontStyle=2;" value="Aviator 沙箱: 超时3s / 循环上限1000 / 黑名单" vertex="1">
<mxGeometry height="12" width="235" x="280" y="836" as="geometry" />
</mxCell>
<mxCell id="a6" parent="1" style="rounded=1;arcSize=8;fillColor=#efebe9;strokeColor=#6d4c41;strokeWidth=1.5;dashed=1;dashPattern=6 3;" value="" vertex="1">
<mxGeometry height="68" width="250" x="530" y="785" as="geometry" />
</mxCell>
<mxCell id="a6t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#6d4c41;strokeColor=none;fontColor=#fff;rounded=1;" value="Agent 协作" vertex="1">
<mxGeometry height="17" width="240" x="535" y="788" as="geometry" />
</mxCell>
<mxCell id="a6a" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="agent_request — ACP 协议" vertex="1">
<mxGeometry height="14" width="230" x="540" y="812" as="geometry" />
</mxCell>
<mxCell id="a6b" parent="1" style="text;html=1;fontSize=7;align=left;fillColor=none;strokeColor=none;fontColor=#666;fontStyle=2;" value="异常检测 | 保洁质检 | 能耗分析 → 注入 metadata" vertex="1">
<mxGeometry height="14" width="230" x="540" y="828" as="geometry" />
</mxCell>
<mxCell id="sec6" parent="1" style="text;html=1;fontSize=13;fontStyle=1;align=left;verticalAlign=middle;fillColor=#efebe9;strokeColor=none;fontColor=#4e342e;rounded=1;" value="⑥ 存储层Storage" vertex="1">
<mxGeometry height="24" width="760" x="20" y="866" as="geometry" />
</mxCell>
<mxCell id="s_mysql" parent="1" style="rounded=1;arcSize=8;fillColor=#e3f2fd;strokeColor=#1e88e5;strokeWidth=1.5;" value="" vertex="1">
<mxGeometry height="130" width="240" x="20" y="898" as="geometry" />
</mxCell>
<mxCell id="s_mysql_t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#1e88e5;strokeColor=none;fontColor=#fff;rounded=1;" value="MySQL业务实体" vertex="1">
<mxGeometry height="18" width="230" x="25" y="901" as="geometry" />
</mxCell>
<mxCell id="sm1" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="iot_subsystem子系统" vertex="1">
<mxGeometry height="14" width="200" x="30" y="924" as="geometry" />
</mxCell>
<mxCell id="sm2" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="iot_device+subsystem_id" vertex="1">
<mxGeometry height="14" width="200" x="30" y="940" as="geometry" />
</mxCell>
<mxCell id="sm3" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="iot_rule_chain / node / link" vertex="1">
<mxGeometry height="14" width="200" x="30" y="956" as="geometry" />
</mxCell>
<mxCell id="sm4" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="iot_alarm_record" vertex="1">
<mxGeometry height="14" width="200" x="30" y="972" as="geometry" />
</mxCell>
<mxCell id="sm5" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="iot_device_rpc / iot_agent_service" vertex="1">
<mxGeometry height="14" width="200" x="30" y="988" as="geometry" />
</mxCell>
<mxCell id="sm6" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#999;fontStyle=2;" value="iot_project预留" vertex="1">
<mxGeometry height="14" width="200" x="30" y="1006" as="geometry" />
</mxCell>
<mxCell id="s_tsdb" parent="1" style="rounded=1;arcSize=8;fillColor=#e8f5e9;strokeColor=#43a047;strokeWidth=1.5;" value="" vertex="1">
<mxGeometry height="130" width="260" x="270" y="898" as="geometry" />
</mxCell>
<mxCell id="s_tsdb_t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#43a047;strokeColor=none;fontColor=#fff;rounded=1;" value="时序库CTSDB / TDengine 双实现)" vertex="1">
<mxGeometry height="18" width="250" x="275" y="901" as="geometry" />
</mxCell>
<mxCell id="st1" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="device_message消息日志·90天" vertex="1">
<mxGeometry height="14" width="240" x="280" y="926" as="geometry" />
</mxCell>
<mxCell id="st2" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="product_property_*属性时序·365天" vertex="1">
<mxGeometry height="14" width="240" x="280" y="943" as="geometry" />
</mxCell>
<mxCell id="st3" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="alarm_history告警归档·365天" vertex="1">
<mxGeometry height="14" width="240" x="280" y="960" as="geometry" />
</mxCell>
<mxCell id="st4" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="rule_debug_log调试日志·7天" vertex="1">
<mxGeometry height="14" width="240" x="280" y="977" as="geometry" />
</mxCell>
<mxCell id="st_dao" parent="1" style="text;html=1;fontSize=8;align=center;fillColor=#c8e6c9;strokeColor=#a5d6a7;fontColor=#2e7d32;rounded=1;fontStyle=1;" value="DAO 接口双实现 · 统一数据模型" vertex="1">
<mxGeometry height="14" width="240" x="285" y="996" as="geometry" />
</mxCell>
<mxCell id="st_switch" parent="1" style="text;html=1;fontSize=7;align=center;fillColor=none;strokeColor=none;fontColor=#2e7d32;fontStyle=2;" value="viewsh.iot.tsdb.type: ctsdb (InfluxDB) / tdengine (JDBC)" vertex="1">
<mxGeometry height="12" width="240" x="285" y="1012" as="geometry" />
</mxCell>
<mxCell id="s_redis" parent="1" style="rounded=1;arcSize=8;fillColor=#fce4ec;strokeColor=#e53935;strokeWidth=1.5;" value="" vertex="1">
<mxGeometry height="130" width="240" x="540" y="898" as="geometry" />
</mxCell>
<mxCell id="s_redis_t" parent="1" style="text;html=1;fontSize=10;fontStyle=1;align=center;fillColor=#e53935;strokeColor=none;fontColor=#fff;rounded=1;" value="Redis缓存 / 状态)" vertex="1">
<mxGeometry height="18" width="230" x="545" y="901" as="geometry" />
</mxCell>
<mxCell id="sr1" parent="1" style="text;html=1;fontSize=7;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="iot:device_property:{did}:client/server/shared" vertex="1">
<mxGeometry height="14" width="225" x="550" y="926" as="geometry" />
</mxCell>
<mxCell id="sr2" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="设备在线状态标记" vertex="1">
<mxGeometry height="14" width="200" x="550" y="943" as="geometry" />
</mxCell>
<mxCell id="sr3" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="iot:alarm:cache / lock:{recordId}" vertex="1">
<mxGeometry height="14" width="225" x="550" y="960" as="geometry" />
</mxCell>
<mxCell id="sr4" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="规则链全量缓存" vertex="1">
<mxGeometry height="14" width="200" x="550" y="977" as="geometry" />
</mxCell>
<mxCell id="sr5" parent="1" style="text;html=1;fontSize=8;align=left;fillColor=none;strokeColor=none;fontColor=#333;" value="iot:rpc:pending:{deviceId}RPC 队列)" vertex="1">
<mxGeometry height="14" width="225" x="550" y="994" as="geometry" />
</mxCell>
<mxCell id="sr6" parent="1" style="text;html=1;fontSize=7;align=left;fillColor=none;strokeColor=none;fontColor=#999;fontStyle=2;" value="SCAN 遍历替代 keys() 全量扫描" vertex="1">
<mxGeometry height="12" width="225" x="550" y="1012" as="geometry" />
</mxCell>
<mxCell id="buf_box" parent="1" style="rounded=1;arcSize=8;fillColor=#f1f8e9;strokeColor=#7cb342;strokeWidth=1;dashed=1;" value="" vertex="1">
<mxGeometry height="48" width="760" x="20" y="1038" as="geometry" />
</mxCell>
<mxCell id="buf_t" parent="1" style="text;html=1;fontSize=8;fontStyle=1;align=left;fillColor=none;strokeColor=none;fontColor=#558b2f;" value="PersistenceBuffer 写入缓冲TDengine 侧需补齐CTSDB 已内置异步批量 1000条/批+1s刷新" vertex="1">
<mxGeometry height="14" width="740" x="30" y="1042" as="geometry" />
</mxCell>
<mxCell id="buf1" parent="1" style="rounded=1;fillColor=#aed581;strokeColor=#7cb342;fontColor=#33691e;fontSize=8;" value="属性写入 200条/3s" vertex="1">
<mxGeometry height="20" width="160" x="40" y="1060" as="geometry" />
</mxCell>
<mxCell id="buf2" parent="1" style="rounded=1;fillColor=#aed581;strokeColor=#7cb342;fontColor=#33691e;fontSize=8;" value="消息日志 500条/5s" vertex="1">
<mxGeometry height="20" width="160" x="215" y="1060" as="geometry" />
</mxCell>
<mxCell id="buf3" parent="1" style="rounded=1;fillColor=#aed581;strokeColor=#7cb342;fontColor=#33691e;fontSize=8;" value="告警历史 100条/3s" vertex="1">
<mxGeometry height="20" width="160" x="390" y="1060" as="geometry" />
</mxCell>
<mxCell id="buf4" parent="1" style="rounded=1;fillColor=#aed581;strokeColor=#7cb342;fontColor=#33691e;fontSize=8;" value="调试日志 100条/5s" vertex="1">
<mxGeometry height="20" width="160" x="565" y="1060" as="geometry" />
</mxCell>
<mxCell id="ver" parent="1" style="text;html=1;fontSize=8;align=right;fillColor=none;strokeColor=none;fontColor=#bdbdbd;" value="IoT Module v2.0 | 时序库 CTSDB/TDengine 双实现可切换" vertex="1">
<mxGeometry height="14" width="320" x="460" y="1092" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>