20260525:dcp平台对接
This commit is contained in:
parent
3dd347a21e
commit
9ae84114d0
BIN
docs/DCP接口文档(轴力计).pdf
Normal file
BIN
docs/DCP接口文档(轴力计).pdf
Normal file
Binary file not shown.
95
docs/knowledge-base/01-architecture.md
Normal file
95
docs/knowledge-base/01-architecture.md
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# 整体架构
|
||||||
|
|
||||||
|
## 项目定位
|
||||||
|
|
||||||
|
FastBee 是基于若依 v3.8.5 改造的物联网平台,覆盖设备接入、产品/物模型管理、数据采集、规则引擎、通知、视频监控、SCADA/GoView 可视化和移动端。
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
| 层 | 技术 |
|
||||||
|
| --- | --- |
|
||||||
|
| 后端框架 | Spring Boot 2.5.14, Spring Security, JWT |
|
||||||
|
| ORM/数据访问 | MyBatis Plus, MyBatis XML Mapper, PageHelper |
|
||||||
|
| 数据源 | MySQL 主库,动态数据源,可选 TDengine |
|
||||||
|
| 缓存/锁 | Redis, Redisson, Lock4j |
|
||||||
|
| 协议接入 | Netty MQTT/TCP/UDP/HTTP/CoAP, SIP GB28181 |
|
||||||
|
| 规则引擎 | LiteFlow, Groovy 脚本 |
|
||||||
|
| 消息通道 | 本地 `LinkedBlockingQueue` 队列族,MQTT/EMQX 或内嵌 Broker |
|
||||||
|
| 前端 | Vue 2 + Element UI, Vue 3 + Vite + Naive UI, UniApp + uView |
|
||||||
|
|
||||||
|
## 后端模块分层
|
||||||
|
|
||||||
|
```text
|
||||||
|
fastbee-admin
|
||||||
|
-> fastbee-framework
|
||||||
|
-> fastbee-open-api
|
||||||
|
-> fastbee-server/boot-strap
|
||||||
|
-> fastbee-service/*
|
||||||
|
-> fastbee-iot-data
|
||||||
|
-> fastbee-mq
|
||||||
|
-> fastbee-notify/*
|
||||||
|
-> fastbee-plugs/*
|
||||||
|
-> fastbee-common
|
||||||
|
```
|
||||||
|
|
||||||
|
| 模块 | 职责 |
|
||||||
|
| --- | --- |
|
||||||
|
| `fastbee-admin` | Spring Boot 主入口,端口默认 8080,聚合 Web/API/协议/插件依赖。 |
|
||||||
|
| `fastbee-framework` | 若依框架层:安全、鉴权、Web 配置、数据源、拦截器、通用基础设施。 |
|
||||||
|
| `fastbee-common` | 公共 DTO、枚举、工具、注解、Redis Key、MQ 消息模型、协议通用模型。 |
|
||||||
|
| `fastbee-open-api` | 对外 REST Controller,覆盖设备、产品、物模型、规则、视频、通知等业务 API。 |
|
||||||
|
| `fastbee-service/fastbee-system-service` | 系统管理服务:用户、角色、菜单、部门、字典、参数、日志。 |
|
||||||
|
| `fastbee-service/fastbee-iot-service` | IoT 核心业务:产品、设备、物模型、场景、规则、告警、Modbus、网关、固件。 |
|
||||||
|
| `fastbee-server` | Netty/SIP 协议服务集合:基础 Server、MQTT Broker、TCP/UDP/HTTP/CoAP/SIP、启动装配。 |
|
||||||
|
| `fastbee-protocol` | 协议抽象和具体编解码实现,如 Modbus、JSON、自定义采集协议、OTA 包。 |
|
||||||
|
| `fastbee-iot-data` | 设备数据处理层:上报处理、下发处理、规则触发、TDengine 日志、OTA、设备消息发布。 |
|
||||||
|
| `fastbee-mq` | 设备消息通道封装,当前核心是 JVM 内本地阻塞队列和消息生产入口。 |
|
||||||
|
| `fastbee-notify` | 通知配置、模板、日志和发送通道实现,包含邮件、短信、微信、钉钉等。 |
|
||||||
|
| `fastbee-plugs` | 插件能力:Quartz、代码生成、OSS、Forest HTTP、OAuth、MQTT Client、LiteFlow 扩展、支付。 |
|
||||||
|
| `fastbee-record` | 独立视频录像/回调服务,入口 `ZlmRecordApplication`,与 ZLMediaKit 配合使用。 |
|
||||||
|
|
||||||
|
## 启动入口与条件装配
|
||||||
|
|
||||||
|
- 主应用入口:`fastbee-admin/src/main/java/com/fastbee/FastBeeApplication.java`。
|
||||||
|
- `@SpringBootApplication` 扫描 `com.fastbee` 包下所有模块 Bean。
|
||||||
|
- `@ForestScan(basePackages = "com.fastbee")` 扫描 Forest HTTP 客户端。
|
||||||
|
- 协议服务由 `fastbee-server/boot-strap` 按配置条件创建 Netty `Server` Bean:
|
||||||
|
- `server.broker.enabled=true`:启动内嵌 MQTT Broker 和 WebSocket MQTT。
|
||||||
|
- `server.tcp.enabled=true`:启动 TCP 服务,默认 8888。
|
||||||
|
- `server.http.enabled=true`:启动 HTTP 设备接入服务,默认 8081。
|
||||||
|
- `server.coap.enabled=true`:启动 CoAP 服务,默认 5683。
|
||||||
|
- `sip.enabled=true`:启动 GB28181 视频接入。
|
||||||
|
|
||||||
|
## 前端组成
|
||||||
|
|
||||||
|
| 应用 | 目录 | 技术 | 角色 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 管理后台 | `yack-web` | Vue 2, Element UI, Vuex | 运维/管理端,覆盖系统、设备、产品、规则、告警等后台功能。 |
|
||||||
|
| 低代码可视化 | `yack-view` | Vue 3, TypeScript, Vite, Naive UI, Pinia | GoView 风格大屏和项目可视化编辑器。 |
|
||||||
|
| 移动端 | `yack-app` | UniApp, uView 2.0, MQTT.js | App/小程序/H5 设备查看、控制和移动操作。 |
|
||||||
|
|
||||||
|
## 配置中心
|
||||||
|
|
||||||
|
| 文件 | 重点 |
|
||||||
|
| --- | --- |
|
||||||
|
| `fastbee-admin/src/main/resources/application.yml` | 端口、协议服务开关、JWT、MyBatis Plus、Redisson、Forest、LiteFlow SQL 规则源。 |
|
||||||
|
| `fastbee-admin/src/main/resources/application-dev.yml` | 本地 MySQL、Redis、MQTT、EMQX、SIP、Swagger 配置。 |
|
||||||
|
| `fastbee-admin/src/main/resources/application-prod.yml` | 生产环境覆盖配置。 |
|
||||||
|
| `fastbee-admin/src/main/resources/application-sql.yml` | SQL 相关配置。 |
|
||||||
|
|
||||||
|
## 数据与依赖边界
|
||||||
|
|
||||||
|
```text
|
||||||
|
Controller(open-api/admin/plugs)
|
||||||
|
-> Service(fastbee-iot-service/system-service/notify/sip/oss)
|
||||||
|
-> Mapper XML
|
||||||
|
-> MySQL / TDengine
|
||||||
|
|
||||||
|
Protocol Server(Netty/SIP/MQTT)
|
||||||
|
-> fastbee-mq queue
|
||||||
|
-> fastbee-iot-data handler
|
||||||
|
-> IoT Service + Rule Engine + Log Service
|
||||||
|
-> MQTT/WebSocket/DB/Notify
|
||||||
|
```
|
||||||
|
|
||||||
|
`fastbee-common` 不应反向依赖业务模块;`fastbee-framework` 提供框架装配;业务 Controller 优先调用 Service 接口,不直接写复杂 SQL。
|
||||||
127
docs/knowledge-base/02-runtime-flows.md
Normal file
127
docs/knowledge-base/02-runtime-flows.md
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# 核心运行链路
|
||||||
|
|
||||||
|
## 应用启动
|
||||||
|
|
||||||
|
1. `FastBeeApplication` 启动 Spring Boot。
|
||||||
|
2. `application.yml` 激活 profile,默认是 `prod`,本地开发通常改为 `dev`。
|
||||||
|
3. 动态数据源初始化 `master`,可选初始化 `taos`。
|
||||||
|
4. MyBatis Plus 扫描 `com.fastbee.**.domain` 和 `classpath*:mapper/**/*Mapper.xml`。
|
||||||
|
5. `boot-strap` 模块按配置启动协议服务。
|
||||||
|
6. LiteFlow 从数据库加载规则链和脚本:
|
||||||
|
- 规则链表:`iot_scene`
|
||||||
|
- 脚本表:`iot_script`
|
||||||
|
- 应用名字段:`application_name`
|
||||||
|
|
||||||
|
## 设备上报链路
|
||||||
|
|
||||||
|
```text
|
||||||
|
设备
|
||||||
|
-> MQTT/TCP/HTTP/CoAP/SIP 协议接入
|
||||||
|
-> 协议解码 / Topic 解析 / Session 管理
|
||||||
|
-> MessageProducer.sendPublishMsg 或其他队列入口
|
||||||
|
-> fastbee-mq 本地队列
|
||||||
|
-> fastbee-iot-data DataHandlerImpl
|
||||||
|
-> 设备物模型值更新
|
||||||
|
-> 设备日志/事件日志/TDengine
|
||||||
|
-> 规则引擎/场景联动/条件触发
|
||||||
|
-> WebSocket/MQTT 推送到前端
|
||||||
|
```
|
||||||
|
|
||||||
|
关键代码位置:
|
||||||
|
|
||||||
|
| 环节 | 位置 |
|
||||||
|
| --- | --- |
|
||||||
|
| MQTT Broker 启动 | `fastbee-server/boot-strap/.../MQTTBootStrap.java` |
|
||||||
|
| TCP 服务启动 | `fastbee-server/boot-strap/.../TCPBootStrap.java` |
|
||||||
|
| 消息生产入口 | `fastbee-mq/.../MessageProducer.java` |
|
||||||
|
| 上报队列 | `fastbee-mq/.../DeviceReportQueue.java` |
|
||||||
|
| 上报处理 | `fastbee-iot-data/.../DataHandlerImpl.java` |
|
||||||
|
| 物模型更新 | `fastbee-service/fastbee-iot-service` 下 `IDeviceService` 实现 |
|
||||||
|
| 规则匹配 | `fastbee-iot-data` 的 `IRuleEngine` 和 `fastbee-iot-service` 的 `RuleProcess` |
|
||||||
|
|
||||||
|
## 服务/指令下发链路
|
||||||
|
|
||||||
|
```text
|
||||||
|
前端/API
|
||||||
|
-> DeviceMessageServiceImpl.messagePost / MqttMessagePublishImpl.funcSend
|
||||||
|
-> 查询设备产品、协议、物模型和传输方式
|
||||||
|
-> MQTT: 发布到设备 Topic
|
||||||
|
-> TCP: 组包后通过 SessionManager/MessageManager 写回连接
|
||||||
|
-> GB28181: 调用 SIP Gateway
|
||||||
|
-> FunctionLog 记录下发结果
|
||||||
|
-> WebSocket/MQTT 推送调试消息到前端
|
||||||
|
```
|
||||||
|
|
||||||
|
下发时的重要分支:
|
||||||
|
|
||||||
|
| 分支 | 处理 |
|
||||||
|
| --- | --- |
|
||||||
|
| MQTT 设备 | 使用 `PubMqttClient.publish` 发布到 `TopicsUtils.buildTopic(...)` 生成的 Topic。 |
|
||||||
|
| TCP 设备 | 将指令转为协议字节,交给 `MessageManager.requestR` 对对应 Session 写出。 |
|
||||||
|
| 子设备 | 先解析网关设备关系,把子设备指令映射到网关产品/设备,再设置 slave 地址。 |
|
||||||
|
| 影子模式 | 不真实下发,直接构造上报数据并走 `dataHandler.reportData` 更新影子值。 |
|
||||||
|
| 离线延迟下发 | `deferIfOffline=true` 时写入 `iot_device_deferred_command`,等待设备上线后补发。 |
|
||||||
|
|
||||||
|
## 规则引擎链路
|
||||||
|
|
||||||
|
规则体系有两层:
|
||||||
|
|
||||||
|
| 类型 | 入口 | 数据表 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 场景联动 | `IRuleEngine.ruleMatch(...)` | `iot_scene`, `iot_scene_device`, `iot_scene_script`, `iot_device_job` |
|
||||||
|
| 数据桥接/脚本 | `RuleProcess.processRuleScript(...)` | `iot_script`, `bridge`, `iot_script_bridge` |
|
||||||
|
|
||||||
|
LiteFlow SQL 规则源配置在 `application.yml`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
chainTableName: iot_scene
|
||||||
|
scriptTableName: iot_script
|
||||||
|
chainEnableField: enable
|
||||||
|
scriptEnableField: enable
|
||||||
|
```
|
||||||
|
|
||||||
|
设备 TCP 属性上报后,如果启用规则引擎,会额外以 `tcp/property/post` 作为桥接 Topic 调用 `RuleProcess`,用于 HTTP/MQTT/数据库桥接。
|
||||||
|
|
||||||
|
## 数据持久化链路
|
||||||
|
|
||||||
|
| 数据 | 主要表 |
|
||||||
|
| --- | --- |
|
||||||
|
| 产品、分类、协议 | `iot_product`, `iot_category`, `iot_protocol` |
|
||||||
|
| 设备与绑定 | `iot_device`, `iot_device_user`, `iot_device_group`, `iot_sub_gateway` |
|
||||||
|
| 物模型 | `iot_things_model`, `iot_things_model_template`, 翻译表 |
|
||||||
|
| 上报日志 | `iot_device_log`, `iot_event_log`, `iot_function_log`, `iot_alert_log` |
|
||||||
|
| 固件升级 | `iot_firmware`, `iot_firmware_task`, `iot_firmware_task_detail` |
|
||||||
|
| 规则/桥接 | `iot_scene`, `iot_script`, `bridge`, `iot_script_bridge` |
|
||||||
|
| 通知 | `notify_channel`, `notify_template`, `notify_log` |
|
||||||
|
| 视频 | `sip_device`, `sip_device_channel`, `media_server`, `iot_sip_relation` |
|
||||||
|
|
||||||
|
TDengine 可选数据源 `taos` 默认禁用,相关日志服务在 `fastbee-iot-service` 的 `tdengine` 包和 `fastbee-iot-data` 中被调用。
|
||||||
|
|
||||||
|
## 视频监控链路
|
||||||
|
|
||||||
|
```text
|
||||||
|
GB28181 设备/NVR
|
||||||
|
-> SIP Server
|
||||||
|
-> sip_device / sip_device_channel
|
||||||
|
-> ZLMediaKit media_server
|
||||||
|
-> 播放、PTZ、录像、回放 API
|
||||||
|
-> fastbee-record 可独立承接录像相关回调/下载
|
||||||
|
```
|
||||||
|
|
||||||
|
视频相关 Controller 分布在 `fastbee-open-api/src/main/java/com/fastbee/controller/media` 和 `fastbee-record`。
|
||||||
|
|
||||||
|
## 通知链路
|
||||||
|
|
||||||
|
```text
|
||||||
|
业务事件/告警/推送配置
|
||||||
|
-> notify_template
|
||||||
|
-> notify_channel
|
||||||
|
-> fastbee-notify-core 具体渠道
|
||||||
|
-> notify_log
|
||||||
|
```
|
||||||
|
|
||||||
|
物模型推送扩展表:
|
||||||
|
|
||||||
|
- `iot_things_model_push_config`:某设备的物模型推送配置。
|
||||||
|
- `iot_push_model_item`:配置下需要推送的物模型项和阈值。
|
||||||
|
- `iot_push_user`:配置下的接收用户。
|
||||||
280
docs/knowledge-base/03-data-model.md
Normal file
280
docs/knowledge-base/03-data-model.md
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
# 数据库与表关系
|
||||||
|
|
||||||
|
## 数据库来源
|
||||||
|
|
||||||
|
主初始化脚本:`sql/yack-20260316.sql`。
|
||||||
|
|
||||||
|
补充脚本:
|
||||||
|
|
||||||
|
- `sql/iot_device_deferred_command.sql`
|
||||||
|
- `sql/iot_things_model_push.sql`
|
||||||
|
- `sql/yack.sql`
|
||||||
|
|
||||||
|
`yack-20260316.sql` 中包含约 100+ 张核心表,并带有大量初始化数据。业务表普遍不使用数据库外键;关系主要由字段命名、联合主键、索引和业务代码保证。Quartz 表保留真实外键。
|
||||||
|
|
||||||
|
## 通用字段约定
|
||||||
|
|
||||||
|
| 字段 | 含义 |
|
||||||
|
| --- | --- |
|
||||||
|
| `tenant_id`, `tenant_name` | 多租户归属,IoT/通知/可视化等表常见。 |
|
||||||
|
| `user_id`, `user_name` | 用户归属或操作者。 |
|
||||||
|
| `create_by`, `create_time`, `update_by`, `update_time` | 若依通用审计字段。 |
|
||||||
|
| `del_flag` | 软删除标记,常见取值 0 正常、2 删除。 |
|
||||||
|
| `status` | 业务状态,不同表含义不同,需结合枚举/字典。 |
|
||||||
|
| `serial_number` | 设备唯一编号,是设备日志、Topic、指令和网关关系中的高频关联键。 |
|
||||||
|
|
||||||
|
## 核心业务 ER 概览
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
SYS_USER ||--o{ IOT_DEVICE_USER : owns
|
||||||
|
SYS_USER ||--o{ IOT_GROUP : creates
|
||||||
|
SYS_USER ||--o{ IOT_DEVICE_SHARE : shared_to
|
||||||
|
IOT_CATEGORY ||--o{ IOT_PRODUCT : classifies
|
||||||
|
IOT_PRODUCT ||--o{ IOT_DEVICE : has
|
||||||
|
IOT_PRODUCT ||--o{ IOT_THINGS_MODEL : defines
|
||||||
|
IOT_DEVICE ||--o{ IOT_THINGS_MODEL : overrides
|
||||||
|
IOT_DEVICE ||--o{ IOT_DEVICE_LOG : reports
|
||||||
|
IOT_DEVICE ||--o{ IOT_EVENT_LOG : emits
|
||||||
|
IOT_DEVICE ||--o{ IOT_FUNCTION_LOG : receives
|
||||||
|
IOT_PRODUCT ||--o{ IOT_FIRMWARE : owns
|
||||||
|
IOT_FIRMWARE ||--o{ IOT_FIRMWARE_TASK : upgrades
|
||||||
|
IOT_FIRMWARE_TASK ||--o{ IOT_FIRMWARE_TASK_DETAIL : tracks
|
||||||
|
IOT_SCENE ||--o{ IOT_SCENE_DEVICE : triggers
|
||||||
|
IOT_SCENE ||--o{ IOT_SCENE_SCRIPT : actions
|
||||||
|
IOT_SCRIPT ||--o{ IOT_SCRIPT_BRIDGE : uses
|
||||||
|
BRIDGE ||--o{ IOT_SCRIPT_BRIDGE : bound
|
||||||
|
```
|
||||||
|
|
||||||
|
上图是业务约定关系,不代表 SQL 外键。
|
||||||
|
|
||||||
|
## 产品、设备、物模型
|
||||||
|
|
||||||
|
| 表 | 主键 | 说明 | 关键关系 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `iot_category` | `category_id` | 产品分类,支持 `parent_id` 层级。 | `iot_product.category_id` 关联分类。 |
|
||||||
|
| `iot_product` | `product_id` | 产品定义,包含分类、租户、协议、设备类型等。 | 一对多关联设备、物模型、固件、Modbus 配置。 |
|
||||||
|
| `iot_protocol` | `id` | 协议定义,`protocol_code` 唯一。 | 产品通过协议编码/配置选择编解码实现。 |
|
||||||
|
| `iot_device` | `device_id` | 设备注册表,`serial_number` 唯一。 | 关联产品、租户、用户、日志、网关、物模型覆盖。 |
|
||||||
|
| `iot_things_model` | `model_id` | 产品级/设备级物模型。 | `product_id` 关联产品;`device_id=0` 通常表示产品级,`device_id>0` 表示设备级覆盖或计算物模型。 |
|
||||||
|
| `iot_things_model_template` | `template_id` | 物模型模板。 | 可用于产品物模型复用。 |
|
||||||
|
| `iot_device_template` | `id` | 产品和采集点模板关系。 | `product_id` + `template_id`。 |
|
||||||
|
| `iot_things_model_translate` | `id` | 物模型翻译。 | 通过 `product_id`、标识符等业务字段关联。 |
|
||||||
|
| `iot_things_model_template_translate` | `id` | 物模型模板翻译。 | 通过模板标识业务关联。 |
|
||||||
|
|
||||||
|
关键约束/索引:
|
||||||
|
|
||||||
|
- `iot_device.serial_number` 是唯一索引。
|
||||||
|
- `iot_product.category_id`、`iot_product.tenant_id` 有普通索引。
|
||||||
|
- `iot_things_model.product_id`、`tenant_id`、`model_order` 有普通索引。
|
||||||
|
- `iot_things_model` 还有 `idx_calculated_serial(is_calculated, serial_number)`,用于计算物模型按设备检索。
|
||||||
|
|
||||||
|
## 设备归属、共享和分组
|
||||||
|
|
||||||
|
| 表 | 主键 | 说明 | 关键关系 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `iot_device_user` | `device_id`, `user_id` | 设备与用户归属关系。 | 关联 `iot_device` 和 `sys_user`。 |
|
||||||
|
| `iot_device_share` | `device_id`, `user_id` | 设备共享关系和权限。 | 关联设备与被共享用户。 |
|
||||||
|
| `iot_device_group` | `device_id`, `group_id` | 设备加入分组。 | 关联 `iot_device` 和 `iot_group`。 |
|
||||||
|
| `iot_group` | `group_id` | 用户创建的设备分组。 | `user_id` 关联 `sys_user`。 |
|
||||||
|
| `iot_device_alert_user` | `device_id`, `user_id` | 设备告警接收用户。 | 关联设备与告警接收用户。 |
|
||||||
|
| `order_control` | `id` | 指令权限控制。 | `user_id` + `device_id` 限制控制权限。 |
|
||||||
|
| `command_preferences` | `id` | 指令偏好模板。 | `product_id` 和 `serial_number` 关联设备/产品。 |
|
||||||
|
|
||||||
|
## 网关、子设备和 Modbus
|
||||||
|
|
||||||
|
| 表 | 主键 | 说明 | 关键关系 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `iot_sub_gateway` | `id` | 网关设备与子设备关系。 | `gw_device_id`、`sub_device_id` 均约定关联 `iot_device.device_id`。 |
|
||||||
|
| `iot_product_sub_gateway` | `id` | 网关产品与子产品关系。 | `gw_product_id`、`sub_product_id` 约定关联 `iot_product.product_id`。 |
|
||||||
|
| `iot_modbus_config` | `id` | 产品物模型的 Modbus 点位配置。 | `product_id` 关联产品。 |
|
||||||
|
| `iot_modbus_params` | `id` | 产品级 Modbus 默认参数。 | `product_id` 关联产品,`slave_id` 表示默认从机。 |
|
||||||
|
| `iot_modbus_job` | `task_id` | 设备轮询任务。 | `sub_device_id`、`job_id` 是业务关联字段。 |
|
||||||
|
| `iot_product_modbus_job` | `task_id` | 产品轮询任务模板。 | `product_id` 关联产品。 |
|
||||||
|
| `iot_var_temp` | `template_id` | 采集变量模板。 | `user_id` 关联模板所属用户。 |
|
||||||
|
| `iot_var_temp_salve` | `id` | 模板下从机/点位配置。 | `device_temp_id` 关联 `iot_var_temp.template_id`。 |
|
||||||
|
|
||||||
|
## 数据日志
|
||||||
|
|
||||||
|
| 表 | 主键 | 说明 | 关键关系 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `iot_device_log` | `log_id` | 设备属性/监测日志。 | `device_id`、`serial_number`、`tenant_id`、`user_id`。 |
|
||||||
|
| `iot_event_log` | `log_id` | 设备事件日志。 | 关联设备、租户和用户。 |
|
||||||
|
| `iot_function_log` | `id` | 服务/指令下发日志。 | `serial_number`、`message_id`、`user_id`。 |
|
||||||
|
| `iot_alert_log` | `alert_log_id` | 告警历史。 | `serial_number`、`product_id`、`user_id`。 |
|
||||||
|
| `iot_simulate_log` | `log_id` | 模拟设备日志。 | `device_id`、`serial_number`。 |
|
||||||
|
| `iot_device_air_log` | `id` | Air780 上报记录。 | `product_id`、`device_id`。 |
|
||||||
|
|
||||||
|
高频查询索引:
|
||||||
|
|
||||||
|
- `iot_device_log(serial_number, create_time)`
|
||||||
|
- `iot_device_log(serial_number, is_monitor, create_time)`
|
||||||
|
- `iot_alert_log(serial_number)`
|
||||||
|
- `iot_alert_log(product_id)`
|
||||||
|
- `iot_device_log(device_id)`
|
||||||
|
|
||||||
|
## 规则、场景、桥接和告警
|
||||||
|
|
||||||
|
| 表 | 主键 | 说明 | 关键关系 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `iot_scene` | `scene_id` | 场景联动/LiteFlow 规则链。 | `user_id`,`recover_id` 可指向恢复场景。 |
|
||||||
|
| `iot_scene_device` | `scene_device_id` | 场景触发设备/产品。 | `scene_id`、`product_id`、`serial_number`、`script_id`。 |
|
||||||
|
| `iot_scene_script` | `script_id` | 场景动作/条件脚本。 | `scene_id`、`product_id`、`job_id`、`parent_id`。 |
|
||||||
|
| `iot_script` | `script_id` | 规则引擎脚本组件。 | `scene_id`、`product_id`、`user_id`。 |
|
||||||
|
| `bridge` | `id` | 数据桥接端点配置。 | HTTP/MQTT/数据库桥接配置主体。 |
|
||||||
|
| `iot_script_bridge` | `script_id`, `bridge_id` | 脚本与桥接端点关系。 | 多对多关联 `iot_script` 和 `bridge`。 |
|
||||||
|
| `iot_alert` | `alert_id` | 告警定义。 | 租户维度定义告警。 |
|
||||||
|
| `iot_alert_scene` | `alert_id`, `scene_id` | 告警与场景关系。 | 多对多关联告警和场景。 |
|
||||||
|
| `iot_alert_notify_template` | `alert_id`, `notify_template_id` | 告警与通知模板关系。 | 多对多关联告警和通知模板。 |
|
||||||
|
| `iot_device_job` | `job_id`, `job_name`, `job_group` | 设备定时任务。 | 可关联设备、产品、场景、告警。 |
|
||||||
|
|
||||||
|
LiteFlow SQL 源直接读取:
|
||||||
|
|
||||||
|
- `iot_scene.chain_name`
|
||||||
|
- `iot_scene.el_data`
|
||||||
|
- `iot_scene.enable`
|
||||||
|
- `iot_script.script_id`
|
||||||
|
- `iot_script.script_data`
|
||||||
|
- `iot_script.script_language`
|
||||||
|
- `iot_script.enable`
|
||||||
|
|
||||||
|
## 场景模型、SCADA 和可视化
|
||||||
|
|
||||||
|
| 表 | 主键 | 说明 | 关键关系 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `scene_model` | `scene_model_id` | 业务场景管理。 | 租户维度。 |
|
||||||
|
| `scene_model_device` | `id` | 场景关联设备/虚拟数据源。 | `scene_model_id`、`cus_device_id`。 |
|
||||||
|
| `scene_model_data` | `id` | 场景数据源绑定。 | `scene_model_id`、`scene_model_device_id`、`datasource_id`。 |
|
||||||
|
| `scene_model_tag` | `id` | 场景录入型变量/计算变量。 | `scene_model_id`。 |
|
||||||
|
| `scene_tag_points` | `id` | 运算型变量点。 | `tag_id` 关联变量,`scene_model_data_id` 关联数据源。 |
|
||||||
|
| `iot_goview_project` | `id` | GoView 项目。 | 租户维度。 |
|
||||||
|
| `iot_goview_project_data` | `id` | GoView 项目数据。 | `project_id` 关联项目。 |
|
||||||
|
| `scada` | `id` | 组态页面。 | 租户维度。 |
|
||||||
|
| `scada_component` | `id` | 组态组件。 | `tenant_id`、`user_id`。 |
|
||||||
|
| `scada_device_bind` | `id` | 组态组件设备绑定。 | `serial_number` 关联设备。 |
|
||||||
|
| `scada_echart` | `id` | 图表管理。 | 租户维度。 |
|
||||||
|
| `scada_gallery` | `id` | 组态图库。 | 租户维度。 |
|
||||||
|
| `scada_model` | `id` | 三维模型配置。 | 租户维度。 |
|
||||||
|
|
||||||
|
## 固件和 OTA
|
||||||
|
|
||||||
|
| 表 | 主键 | 说明 | 关键关系 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `iot_firmware` | `firmware_id` | 产品固件版本。 | `product_id` 关联产品,`tenant_id` 关联租户。 |
|
||||||
|
| `iot_firmware_task` | `id` | 固件升级任务。 | `firmware_id` 关联固件。 |
|
||||||
|
| `iot_firmware_task_detail` | `id` | 任务下设备升级状态。 | `task_id` 关联任务,`serial_number` 关联设备。 |
|
||||||
|
|
||||||
|
## 视频监控和媒体
|
||||||
|
|
||||||
|
| 表 | 主键 | 说明 | 关键关系 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `sip_config` | `id` | SIP 系统配置。 | 可关联 `product_id`。 |
|
||||||
|
| `sip_device` | `device_id` | GB28181 监控设备/NVR。 | `product_id`,`device_sip_id`。 |
|
||||||
|
| `sip_device_channel` | `id`, `device_sip_id` | 摄像头通道。 | `device_sip_id` 关联 SIP 设备编号,`channel_sip_id` 是通道编号。 |
|
||||||
|
| `media_server` | `id` | ZLMediaKit 流媒体服务器配置。 | `server_id` 是媒体服务标识。 |
|
||||||
|
| `iot_sip_relation` | `id` | IoT 设备/场景与 SIP 通道关系。 | `channel_id`、`re_device_id`、`re_scene_model_id`。 |
|
||||||
|
|
||||||
|
## 通知和推送
|
||||||
|
|
||||||
|
| 表 | 主键 | 说明 | 关键关系 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `notify_channel` | `id` | 通知渠道账号。 | 租户维度。 |
|
||||||
|
| `notify_template` | `id` | 通知模板。 | `channel_id` 关联通知渠道。 |
|
||||||
|
| `notify_log` | `id` | 通知发送日志。 | `channel_id`、`notify_template_id`。 |
|
||||||
|
| `iot_things_model_push_config` | `id` | 物模型数据推送配置。 | `device_id`、`template_id`、`user_id`。 |
|
||||||
|
| `iot_push_model_item` | `id` | 推送配置下的物模型项和阈值。 | `config_id` 关联推送配置,`model_id` 关联物模型。 |
|
||||||
|
| `iot_push_user` | `id` | 推送接收用户。 | `config_id` 关联推送配置,`user_id` 关联系统用户。 |
|
||||||
|
|
||||||
|
## 系统 RBAC 和基础表
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
SYS_DEPT ||--o{ SYS_USER : contains
|
||||||
|
SYS_USER ||--o{ SYS_USER_ROLE : has
|
||||||
|
SYS_ROLE ||--o{ SYS_USER_ROLE : assigned
|
||||||
|
SYS_ROLE ||--o{ SYS_ROLE_MENU : grants
|
||||||
|
SYS_MENU ||--o{ SYS_ROLE_MENU : included
|
||||||
|
SYS_ROLE ||--o{ SYS_ROLE_DEPT : scopes
|
||||||
|
SYS_DEPT ||--o{ SYS_ROLE_DEPT : scoped
|
||||||
|
SYS_USER ||--o{ SYS_USER_POST : holds
|
||||||
|
SYS_POST ||--o{ SYS_USER_POST : assigned
|
||||||
|
```
|
||||||
|
|
||||||
|
| 表 | 主键 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `sys_user` | `user_id` | 用户。 |
|
||||||
|
| `sys_dept` | `dept_id` | 部门/机构,`parent_id` 形成树。 |
|
||||||
|
| `sys_role` | `role_id` | 角色。 |
|
||||||
|
| `sys_menu` | `menu_id` | 菜单和权限,`parent_id` 形成树。 |
|
||||||
|
| `sys_post` | `post_id` | 岗位。 |
|
||||||
|
| `sys_user_role` | `user_id`, `role_id` | 用户角色关系。 |
|
||||||
|
| `sys_role_menu` | `role_id`, `menu_id` | 角色菜单权限。 |
|
||||||
|
| `sys_role_dept` | `role_id`, `dept_id` | 角色数据权限。 |
|
||||||
|
| `sys_user_post` | `user_id`, `post_id` | 用户岗位关系。 |
|
||||||
|
| `sys_config` | `config_id` | 系统参数。 |
|
||||||
|
| `sys_dict_type` | `dict_id` | 字典类型,`dict_type` 唯一。 |
|
||||||
|
| `sys_dict_data` | `dict_code` | 字典数据,通过 `dict_type` 关联字典类型。 |
|
||||||
|
| `sys_notice` | `notice_id` | 通知公告。 |
|
||||||
|
| `sys_oper_log` | `oper_id` | 操作日志。 |
|
||||||
|
| `sys_logininfor` | `info_id` | 登录日志。 |
|
||||||
|
| `sys_client` | `id` | 系统授权客户端。 |
|
||||||
|
| `sys_auth_user` | `auth_id` | 第三方授权用户。 |
|
||||||
|
| `sys_user_relation` | `root_id` | 用户关系和层级深度缓存。 |
|
||||||
|
|
||||||
|
翻译表:
|
||||||
|
|
||||||
|
- `sys_menu_translate`
|
||||||
|
- `sys_dict_type_translate`
|
||||||
|
- `sys_dict_data_translate`
|
||||||
|
|
||||||
|
## OAuth、第三方登录和内容
|
||||||
|
|
||||||
|
| 表 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| `oauth_client_details` | OAuth 客户端配置。 |
|
||||||
|
| `oauth_access_token`, `oauth_refresh_token`, `oauth_client_token`, `oauth_code`, `oauth_approvals` | Spring OAuth Token/授权数据。 |
|
||||||
|
| `iot_social_platform` | 第三方登录平台配置。 |
|
||||||
|
| `iot_social_user` | 第三方用户和系统用户绑定。 |
|
||||||
|
| `app_language` | App 语言配置。 |
|
||||||
|
| `app_preferences` | 用户 App 偏好,`user_id + language` 唯一。 |
|
||||||
|
| `news`, `news_category` | 新闻资讯和分类。 |
|
||||||
|
|
||||||
|
## OSS、代码生成和任务
|
||||||
|
|
||||||
|
| 表 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| `oss_config` | 对象存储配置。 |
|
||||||
|
| `oss_detail` | 上传文件记录。 |
|
||||||
|
| `gen_table`, `gen_table_column` | 代码生成器元数据。 |
|
||||||
|
| `sys_job`, `sys_job_log` | 若依定时任务配置和日志。 |
|
||||||
|
|
||||||
|
## Quartz 强约束关系
|
||||||
|
|
||||||
|
这些是 SQL 中真实存在的外键:
|
||||||
|
|
||||||
|
```text
|
||||||
|
qrtz_triggers(sched_name, job_name, job_group)
|
||||||
|
-> qrtz_job_details(sched_name, job_name, job_group)
|
||||||
|
|
||||||
|
qrtz_blob_triggers(sched_name, trigger_name, trigger_group)
|
||||||
|
-> qrtz_triggers(sched_name, trigger_name, trigger_group)
|
||||||
|
|
||||||
|
qrtz_cron_triggers(sched_name, trigger_name, trigger_group)
|
||||||
|
-> qrtz_triggers(sched_name, trigger_name, trigger_group)
|
||||||
|
|
||||||
|
qrtz_simple_triggers(sched_name, trigger_name, trigger_group)
|
||||||
|
-> qrtz_triggers(sched_name, trigger_name, trigger_group)
|
||||||
|
|
||||||
|
qrtz_simprop_triggers(sched_name, trigger_name, trigger_group)
|
||||||
|
-> qrtz_triggers(sched_name, trigger_name, trigger_group)
|
||||||
|
```
|
||||||
|
|
||||||
|
其他 Quartz 表如 `qrtz_calendars`、`qrtz_fired_triggers`、`qrtz_locks`、`qrtz_scheduler_state` 由 Quartz 框架维护。
|
||||||
|
|
||||||
|
## 关系梳理原则
|
||||||
|
|
||||||
|
- 优先看 SQL 主键、联合主键、唯一索引和普通索引。
|
||||||
|
- 再看 Java Domain、Mapper XML、Service 查询条件。
|
||||||
|
- 对无外键的业务表,文档只标记为“业务约定关联”。
|
||||||
|
- `serial_number` 是很多运行时链路的核心关联键,但真正的设备主键仍是 `iot_device.device_id`。
|
||||||
|
- `product_id`、`device_id`、`user_id`、`tenant_id` 等字段在不同表中含义一致,但删除/更新一致性由业务代码维护。
|
||||||
703
docs/knowledge-base/04-er-diagrams.md
Normal file
703
docs/knowledge-base/04-er-diagrams.md
Normal file
@ -0,0 +1,703 @@
|
|||||||
|
# 数据库关系图
|
||||||
|
|
||||||
|
本文件用 Mermaid ER 图描述 FastBee 数据库关系。除 Quartz 表外,大部分业务表没有 SQL 外键,图中的连线表示业务约定关联,主要依据字段命名、联合主键、索引和代码查询关系整理。
|
||||||
|
|
||||||
|
## 总览
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
System["系统 RBAC<br/>sys_*"] --> IoT["IoT 核心<br/>产品/设备/物模型"]
|
||||||
|
IoT --> DataLog["数据日志<br/>上报/事件/下发/告警日志"]
|
||||||
|
IoT --> Gateway["网关与 Modbus<br/>子设备/轮询/点位"]
|
||||||
|
IoT --> Rule["规则与告警<br/>场景/LiteFlow/桥接"]
|
||||||
|
IoT --> Firmware["固件 OTA<br/>任务/明细"]
|
||||||
|
IoT --> Visual["可视化<br/>SceneModel/SCADA/GoView"]
|
||||||
|
IoT --> Video["视频 GB28181<br/>SIP/ZLM"]
|
||||||
|
Rule --> Notify["通知<br/>渠道/模板/日志"]
|
||||||
|
System --> OAuth[OAuth/第三方登录]
|
||||||
|
System --> Gen[代码生成/OSS/Quartz]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 产品、设备、物模型
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
IOT_CATEGORY ||--o{ IOT_CATEGORY : parent
|
||||||
|
IOT_CATEGORY ||--o{ IOT_PRODUCT : classifies
|
||||||
|
IOT_PRODUCT ||--o{ IOT_DEVICE : owns
|
||||||
|
IOT_PRODUCT ||--o{ IOT_THINGS_MODEL : defines
|
||||||
|
IOT_PRODUCT ||--o{ IOT_THINGS_MODEL_TEMPLATE : uses
|
||||||
|
IOT_PRODUCT ||--o{ IOT_DEVICE_TEMPLATE : maps_template
|
||||||
|
IOT_THINGS_MODEL_TEMPLATE ||--o{ IOT_DEVICE_TEMPLATE : assigned
|
||||||
|
IOT_THINGS_MODEL ||--o{ IOT_THINGS_MODEL_TRANSLATE : translated
|
||||||
|
IOT_THINGS_MODEL_TEMPLATE ||--o{ IOT_THINGS_MODEL_TEMPLATE_TRANSLATE : translated
|
||||||
|
IOT_PROTOCOL ||--o{ IOT_PRODUCT : selected_by
|
||||||
|
SYS_USER ||--o{ IOT_PRODUCT : tenant_or_owner
|
||||||
|
SYS_USER ||--o{ IOT_DEVICE : tenant_or_owner
|
||||||
|
|
||||||
|
IOT_CATEGORY {
|
||||||
|
bigint category_id PK
|
||||||
|
bigint parent_id
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
IOT_PRODUCT {
|
||||||
|
bigint product_id PK
|
||||||
|
bigint category_id
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
IOT_DEVICE {
|
||||||
|
bigint device_id PK
|
||||||
|
bigint product_id
|
||||||
|
varchar serial_number UK
|
||||||
|
bigint tenant_id
|
||||||
|
bigint user_id
|
||||||
|
}
|
||||||
|
IOT_THINGS_MODEL {
|
||||||
|
bigint model_id PK
|
||||||
|
bigint product_id
|
||||||
|
bigint device_id
|
||||||
|
varchar serial_number
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
IOT_THINGS_MODEL_TEMPLATE {
|
||||||
|
bigint template_id PK
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
IOT_DEVICE_TEMPLATE {
|
||||||
|
bigint id PK
|
||||||
|
bigint product_id
|
||||||
|
bigint template_id
|
||||||
|
}
|
||||||
|
IOT_PROTOCOL {
|
||||||
|
bigint id PK
|
||||||
|
varchar protocol_code UK
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 设备归属、共享、分组
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
SYS_USER ||--o{ IOT_DEVICE_USER : owns
|
||||||
|
IOT_DEVICE ||--o{ IOT_DEVICE_USER : owned_by
|
||||||
|
SYS_USER ||--o{ IOT_DEVICE_SHARE : shared_to
|
||||||
|
IOT_DEVICE ||--o{ IOT_DEVICE_SHARE : shared
|
||||||
|
SYS_USER ||--o{ IOT_GROUP : creates
|
||||||
|
IOT_GROUP ||--o{ IOT_DEVICE_GROUP : contains
|
||||||
|
IOT_DEVICE ||--o{ IOT_DEVICE_GROUP : grouped
|
||||||
|
SYS_USER ||--o{ IOT_DEVICE_ALERT_USER : receives_alert
|
||||||
|
IOT_DEVICE ||--o{ IOT_DEVICE_ALERT_USER : alert_users
|
||||||
|
SYS_USER ||--o{ ORDER_CONTROL : restricted
|
||||||
|
IOT_DEVICE ||--o{ ORDER_CONTROL : command_acl
|
||||||
|
IOT_PRODUCT ||--o{ COMMAND_PREFERENCES : command_template
|
||||||
|
IOT_DEVICE ||--o{ COMMAND_PREFERENCES : device_command_template
|
||||||
|
|
||||||
|
IOT_DEVICE_USER {
|
||||||
|
bigint device_id PK
|
||||||
|
bigint user_id PK
|
||||||
|
}
|
||||||
|
IOT_DEVICE_SHARE {
|
||||||
|
bigint device_id PK
|
||||||
|
bigint user_id PK
|
||||||
|
}
|
||||||
|
IOT_GROUP {
|
||||||
|
bigint group_id PK
|
||||||
|
bigint user_id
|
||||||
|
}
|
||||||
|
IOT_DEVICE_GROUP {
|
||||||
|
bigint device_id PK
|
||||||
|
bigint group_id PK
|
||||||
|
}
|
||||||
|
IOT_DEVICE_ALERT_USER {
|
||||||
|
bigint device_id PK
|
||||||
|
bigint user_id PK
|
||||||
|
}
|
||||||
|
ORDER_CONTROL {
|
||||||
|
bigint id PK
|
||||||
|
bigint user_id
|
||||||
|
bigint device_id
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
COMMAND_PREFERENCES {
|
||||||
|
bigint id PK
|
||||||
|
bigint product_id
|
||||||
|
varchar serial_number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 网关、子设备、Modbus
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
IOT_DEVICE ||--o{ IOT_SUB_GATEWAY : gateway
|
||||||
|
IOT_DEVICE ||--o{ IOT_SUB_GATEWAY : sub_device
|
||||||
|
IOT_PRODUCT ||--o{ IOT_PRODUCT_SUB_GATEWAY : gateway_product
|
||||||
|
IOT_PRODUCT ||--o{ IOT_PRODUCT_SUB_GATEWAY : sub_product
|
||||||
|
IOT_PRODUCT ||--o{ IOT_MODBUS_CONFIG : point_config
|
||||||
|
IOT_PRODUCT ||--o{ IOT_MODBUS_PARAMS : params
|
||||||
|
IOT_PRODUCT ||--o{ IOT_PRODUCT_MODBUS_JOB : poll_template
|
||||||
|
IOT_DEVICE ||--o{ IOT_MODBUS_JOB : poll_task
|
||||||
|
IOT_VAR_TEMP ||--o{ IOT_VAR_TEMP_SALVE : slave_points
|
||||||
|
SYS_USER ||--o{ IOT_VAR_TEMP : owns
|
||||||
|
|
||||||
|
IOT_SUB_GATEWAY {
|
||||||
|
bigint id PK
|
||||||
|
bigint gw_device_id
|
||||||
|
bigint sub_device_id
|
||||||
|
int slave_id
|
||||||
|
}
|
||||||
|
IOT_PRODUCT_SUB_GATEWAY {
|
||||||
|
bigint id PK
|
||||||
|
bigint gw_product_id
|
||||||
|
bigint sub_product_id
|
||||||
|
int slave_id
|
||||||
|
}
|
||||||
|
IOT_MODBUS_CONFIG {
|
||||||
|
bigint id PK
|
||||||
|
bigint product_id
|
||||||
|
}
|
||||||
|
IOT_MODBUS_PARAMS {
|
||||||
|
bigint id PK
|
||||||
|
bigint product_id
|
||||||
|
int slave_id
|
||||||
|
}
|
||||||
|
IOT_MODBUS_JOB {
|
||||||
|
bigint task_id PK
|
||||||
|
bigint sub_device_id
|
||||||
|
bigint job_id
|
||||||
|
}
|
||||||
|
IOT_PRODUCT_MODBUS_JOB {
|
||||||
|
bigint task_id PK
|
||||||
|
bigint product_id
|
||||||
|
}
|
||||||
|
IOT_VAR_TEMP {
|
||||||
|
bigint template_id PK
|
||||||
|
bigint user_id
|
||||||
|
}
|
||||||
|
IOT_VAR_TEMP_SALVE {
|
||||||
|
bigint id PK
|
||||||
|
bigint device_temp_id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 设备数据日志
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
IOT_PRODUCT ||--o{ IOT_ALERT_LOG : product_alerts
|
||||||
|
IOT_DEVICE ||--o{ IOT_DEVICE_LOG : property_logs
|
||||||
|
IOT_DEVICE ||--o{ IOT_EVENT_LOG : event_logs
|
||||||
|
IOT_DEVICE ||--o{ IOT_FUNCTION_LOG : function_logs
|
||||||
|
IOT_DEVICE ||--o{ IOT_ALERT_LOG : alert_logs
|
||||||
|
IOT_DEVICE ||--o{ IOT_SIMULATE_LOG : simulate_logs
|
||||||
|
IOT_DEVICE ||--o{ IOT_DEVICE_AIR_LOG : air_logs
|
||||||
|
SYS_USER ||--o{ IOT_DEVICE_LOG : user_scope
|
||||||
|
SYS_USER ||--o{ IOT_EVENT_LOG : user_scope
|
||||||
|
SYS_USER ||--o{ IOT_FUNCTION_LOG : operator
|
||||||
|
|
||||||
|
IOT_DEVICE_LOG {
|
||||||
|
bigint log_id PK
|
||||||
|
bigint device_id
|
||||||
|
varchar serial_number
|
||||||
|
bigint user_id
|
||||||
|
bigint tenant_id
|
||||||
|
datetime create_time
|
||||||
|
}
|
||||||
|
IOT_EVENT_LOG {
|
||||||
|
bigint log_id PK
|
||||||
|
bigint device_id
|
||||||
|
varchar serial_number
|
||||||
|
bigint user_id
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
IOT_FUNCTION_LOG {
|
||||||
|
bigint id PK
|
||||||
|
varchar message_id
|
||||||
|
varchar serial_number
|
||||||
|
bigint user_id
|
||||||
|
}
|
||||||
|
IOT_ALERT_LOG {
|
||||||
|
bigint alert_log_id PK
|
||||||
|
varchar serial_number
|
||||||
|
bigint product_id
|
||||||
|
bigint user_id
|
||||||
|
}
|
||||||
|
IOT_SIMULATE_LOG {
|
||||||
|
bigint log_id PK
|
||||||
|
bigint device_id
|
||||||
|
varchar serial_number
|
||||||
|
}
|
||||||
|
IOT_DEVICE_AIR_LOG {
|
||||||
|
bigint id PK
|
||||||
|
bigint product_id
|
||||||
|
bigint device_id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 规则、场景、告警、桥接
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
SYS_USER ||--o{ IOT_SCENE : owns
|
||||||
|
IOT_SCENE ||--o{ IOT_SCENE_DEVICE : has_trigger
|
||||||
|
IOT_SCENE ||--o{ IOT_SCENE_SCRIPT : has_action
|
||||||
|
IOT_PRODUCT ||--o{ IOT_SCENE_DEVICE : trigger_product
|
||||||
|
IOT_PRODUCT ||--o{ IOT_SCENE_SCRIPT : action_product
|
||||||
|
IOT_SCRIPT ||--o{ IOT_SCRIPT_BRIDGE : uses
|
||||||
|
BRIDGE ||--o{ IOT_SCRIPT_BRIDGE : endpoint
|
||||||
|
IOT_SCENE ||--o{ IOT_SCRIPT : liteflow_script
|
||||||
|
IOT_ALERT ||--o{ IOT_ALERT_SCENE : triggers_scene
|
||||||
|
IOT_SCENE ||--o{ IOT_ALERT_SCENE : alert_bound
|
||||||
|
IOT_ALERT ||--o{ IOT_ALERT_NOTIFY_TEMPLATE : notifies
|
||||||
|
NOTIFY_TEMPLATE ||--o{ IOT_ALERT_NOTIFY_TEMPLATE : used_by_alert
|
||||||
|
IOT_DEVICE ||--o{ IOT_DEVICE_JOB : scheduled
|
||||||
|
IOT_PRODUCT ||--o{ IOT_DEVICE_JOB : scheduled
|
||||||
|
IOT_SCENE ||--o{ IOT_DEVICE_JOB : scheduled
|
||||||
|
IOT_ALERT ||--o{ IOT_DEVICE_JOB : scheduled
|
||||||
|
|
||||||
|
IOT_SCENE {
|
||||||
|
bigint scene_id PK
|
||||||
|
bigint user_id
|
||||||
|
varchar chain_name
|
||||||
|
text el_data
|
||||||
|
int enable
|
||||||
|
}
|
||||||
|
IOT_SCENE_DEVICE {
|
||||||
|
bigint scene_device_id PK
|
||||||
|
bigint scene_id
|
||||||
|
bigint product_id
|
||||||
|
varchar serial_number
|
||||||
|
bigint script_id
|
||||||
|
}
|
||||||
|
IOT_SCENE_SCRIPT {
|
||||||
|
bigint script_id PK
|
||||||
|
bigint scene_id
|
||||||
|
bigint product_id
|
||||||
|
bigint job_id
|
||||||
|
bigint parent_id
|
||||||
|
}
|
||||||
|
IOT_SCRIPT {
|
||||||
|
bigint script_id PK
|
||||||
|
bigint scene_id
|
||||||
|
bigint product_id
|
||||||
|
bigint user_id
|
||||||
|
int enable
|
||||||
|
}
|
||||||
|
BRIDGE {
|
||||||
|
bigint id PK
|
||||||
|
}
|
||||||
|
IOT_SCRIPT_BRIDGE {
|
||||||
|
bigint script_id PK
|
||||||
|
bigint bridge_id PK
|
||||||
|
}
|
||||||
|
IOT_ALERT {
|
||||||
|
bigint alert_id PK
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
IOT_ALERT_SCENE {
|
||||||
|
bigint alert_id PK
|
||||||
|
bigint scene_id PK
|
||||||
|
}
|
||||||
|
IOT_ALERT_NOTIFY_TEMPLATE {
|
||||||
|
bigint alert_id PK
|
||||||
|
bigint notify_template_id PK
|
||||||
|
}
|
||||||
|
IOT_DEVICE_JOB {
|
||||||
|
bigint job_id PK
|
||||||
|
bigint device_id
|
||||||
|
bigint product_id
|
||||||
|
bigint scene_id
|
||||||
|
bigint alert_id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 通知与物模型推送
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
NOTIFY_CHANNEL ||--o{ NOTIFY_TEMPLATE : has
|
||||||
|
NOTIFY_CHANNEL ||--o{ NOTIFY_LOG : sends
|
||||||
|
NOTIFY_TEMPLATE ||--o{ NOTIFY_LOG : produces
|
||||||
|
IOT_DEVICE ||--o{ IOT_THINGS_MODEL_PUSH_CONFIG : push_config
|
||||||
|
IOT_PRODUCT ||--o{ IOT_THINGS_MODEL_PUSH_CONFIG : product_scope
|
||||||
|
NOTIFY_TEMPLATE ||--o{ IOT_THINGS_MODEL_PUSH_CONFIG : template
|
||||||
|
SYS_USER ||--o{ IOT_THINGS_MODEL_PUSH_CONFIG : owner
|
||||||
|
IOT_THINGS_MODEL_PUSH_CONFIG ||--o{ IOT_PUSH_MODEL_ITEM : includes
|
||||||
|
IOT_THINGS_MODEL ||--o{ IOT_PUSH_MODEL_ITEM : model_item
|
||||||
|
IOT_THINGS_MODEL_PUSH_CONFIG ||--o{ IOT_PUSH_USER : receivers
|
||||||
|
SYS_USER ||--o{ IOT_PUSH_USER : receiver
|
||||||
|
|
||||||
|
NOTIFY_CHANNEL {
|
||||||
|
bigint id PK
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
NOTIFY_TEMPLATE {
|
||||||
|
bigint id PK
|
||||||
|
bigint channel_id
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
NOTIFY_LOG {
|
||||||
|
bigint id PK
|
||||||
|
bigint channel_id
|
||||||
|
bigint notify_template_id
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
IOT_THINGS_MODEL_PUSH_CONFIG {
|
||||||
|
bigint id PK
|
||||||
|
bigint product_id
|
||||||
|
bigint device_id
|
||||||
|
varchar serial_number
|
||||||
|
bigint template_id
|
||||||
|
bigint user_id
|
||||||
|
}
|
||||||
|
IOT_PUSH_MODEL_ITEM {
|
||||||
|
bigint id PK
|
||||||
|
bigint config_id
|
||||||
|
bigint model_id
|
||||||
|
}
|
||||||
|
IOT_PUSH_USER {
|
||||||
|
bigint id PK
|
||||||
|
bigint config_id
|
||||||
|
bigint user_id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 固件 OTA
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
IOT_PRODUCT ||--o{ IOT_FIRMWARE : owns
|
||||||
|
IOT_FIRMWARE ||--o{ IOT_FIRMWARE_TASK : creates
|
||||||
|
IOT_FIRMWARE_TASK ||--o{ IOT_FIRMWARE_TASK_DETAIL : tracks
|
||||||
|
IOT_DEVICE ||--o{ IOT_FIRMWARE_TASK_DETAIL : target
|
||||||
|
|
||||||
|
IOT_FIRMWARE {
|
||||||
|
bigint firmware_id PK
|
||||||
|
bigint product_id
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
IOT_FIRMWARE_TASK {
|
||||||
|
bigint id PK
|
||||||
|
bigint firmware_id
|
||||||
|
}
|
||||||
|
IOT_FIRMWARE_TASK_DETAIL {
|
||||||
|
bigint id PK
|
||||||
|
bigint task_id
|
||||||
|
varchar serial_number
|
||||||
|
varchar message_id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## SceneModel、SCADA、GoView
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
SCENE_MODEL ||--o{ SCENE_MODEL_DEVICE : has
|
||||||
|
SCENE_MODEL ||--o{ SCENE_MODEL_DATA : has_data
|
||||||
|
SCENE_MODEL_DEVICE ||--o{ SCENE_MODEL_DATA : source
|
||||||
|
SCENE_MODEL ||--o{ SCENE_MODEL_TAG : has_tag
|
||||||
|
SCENE_MODEL_TAG ||--o{ SCENE_TAG_POINTS : calculated_from
|
||||||
|
SCENE_MODEL_DATA ||--o{ SCENE_TAG_POINTS : point_source
|
||||||
|
IOT_GOVIEW_PROJECT ||--o{ IOT_GOVIEW_PROJECT_DATA : has_data
|
||||||
|
SCADA ||--o{ SCADA_DEVICE_BIND : binds_by_guid
|
||||||
|
IOT_DEVICE ||--o{ SCADA_DEVICE_BIND : bound
|
||||||
|
SYS_USER ||--o{ SCADA_COMPONENT : owns
|
||||||
|
|
||||||
|
SCENE_MODEL {
|
||||||
|
bigint scene_model_id PK
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
SCENE_MODEL_DEVICE {
|
||||||
|
bigint id PK
|
||||||
|
bigint scene_model_id
|
||||||
|
bigint cus_device_id
|
||||||
|
}
|
||||||
|
SCENE_MODEL_DATA {
|
||||||
|
bigint id PK
|
||||||
|
bigint scene_model_id
|
||||||
|
bigint scene_model_device_id
|
||||||
|
bigint datasource_id
|
||||||
|
}
|
||||||
|
SCENE_MODEL_TAG {
|
||||||
|
bigint id PK
|
||||||
|
bigint scene_model_id
|
||||||
|
}
|
||||||
|
SCENE_TAG_POINTS {
|
||||||
|
bigint id PK
|
||||||
|
bigint tag_id
|
||||||
|
bigint scene_model_data_id
|
||||||
|
}
|
||||||
|
IOT_GOVIEW_PROJECT {
|
||||||
|
bigint id PK
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
IOT_GOVIEW_PROJECT_DATA {
|
||||||
|
bigint id PK
|
||||||
|
bigint project_id
|
||||||
|
}
|
||||||
|
SCADA {
|
||||||
|
bigint id PK
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
SCADA_COMPONENT {
|
||||||
|
bigint id PK
|
||||||
|
bigint tenant_id
|
||||||
|
bigint user_id
|
||||||
|
}
|
||||||
|
SCADA_DEVICE_BIND {
|
||||||
|
bigint id PK
|
||||||
|
varchar serial_number
|
||||||
|
varchar scada_guid
|
||||||
|
}
|
||||||
|
SCADA_ECHART {
|
||||||
|
bigint id PK
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
SCADA_GALLERY {
|
||||||
|
bigint id PK
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
SCADA_MODEL {
|
||||||
|
bigint id PK
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 视频 GB28181 与媒体
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
IOT_PRODUCT ||--o{ SIP_CONFIG : config_for
|
||||||
|
IOT_PRODUCT ||--o{ SIP_DEVICE : product_device
|
||||||
|
SIP_DEVICE ||--o{ SIP_DEVICE_CHANNEL : has_channel
|
||||||
|
SIP_DEVICE_CHANNEL ||--o{ IOT_SIP_RELATION : bind_channel
|
||||||
|
IOT_DEVICE ||--o{ IOT_SIP_RELATION : bind_iot_device
|
||||||
|
SCENE_MODEL ||--o{ IOT_SIP_RELATION : bind_scene
|
||||||
|
MEDIA_SERVER ||--o{ SIP_DEVICE_CHANNEL : streams
|
||||||
|
|
||||||
|
SIP_CONFIG {
|
||||||
|
bigint id PK
|
||||||
|
bigint product_id
|
||||||
|
}
|
||||||
|
SIP_DEVICE {
|
||||||
|
bigint device_id PK
|
||||||
|
bigint product_id
|
||||||
|
varchar device_sip_id
|
||||||
|
}
|
||||||
|
SIP_DEVICE_CHANNEL {
|
||||||
|
bigint id PK
|
||||||
|
varchar device_sip_id PK
|
||||||
|
varchar channel_sip_id
|
||||||
|
bigint product_id
|
||||||
|
bigint user_id
|
||||||
|
}
|
||||||
|
MEDIA_SERVER {
|
||||||
|
bigint id PK
|
||||||
|
varchar server_id
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
IOT_SIP_RELATION {
|
||||||
|
bigint id PK
|
||||||
|
varchar channel_id
|
||||||
|
bigint re_device_id
|
||||||
|
bigint re_scene_model_id
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 系统 RBAC
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
SYS_DEPT ||--o{ SYS_DEPT : parent
|
||||||
|
SYS_DEPT ||--o{ SYS_USER : contains
|
||||||
|
SYS_USER ||--o{ SYS_USER_ROLE : has
|
||||||
|
SYS_ROLE ||--o{ SYS_USER_ROLE : assigned
|
||||||
|
SYS_ROLE ||--o{ SYS_ROLE_MENU : grants
|
||||||
|
SYS_MENU ||--o{ SYS_ROLE_MENU : granted
|
||||||
|
SYS_MENU ||--o{ SYS_MENU : parent
|
||||||
|
SYS_ROLE ||--o{ SYS_ROLE_DEPT : data_scope
|
||||||
|
SYS_DEPT ||--o{ SYS_ROLE_DEPT : scoped
|
||||||
|
SYS_USER ||--o{ SYS_USER_POST : holds
|
||||||
|
SYS_POST ||--o{ SYS_USER_POST : assigned
|
||||||
|
SYS_USER ||--o{ SYS_USER_RELATION : hierarchy
|
||||||
|
SYS_DICT_TYPE ||--o{ SYS_DICT_DATA : has
|
||||||
|
SYS_DICT_TYPE ||--o{ SYS_DICT_TYPE_TRANSLATE : translated
|
||||||
|
SYS_DICT_DATA ||--o{ SYS_DICT_DATA_TRANSLATE : translated
|
||||||
|
SYS_MENU ||--o{ SYS_MENU_TRANSLATE : translated
|
||||||
|
|
||||||
|
SYS_USER {
|
||||||
|
bigint user_id PK
|
||||||
|
bigint dept_id
|
||||||
|
}
|
||||||
|
SYS_DEPT {
|
||||||
|
bigint dept_id PK
|
||||||
|
bigint parent_id
|
||||||
|
}
|
||||||
|
SYS_ROLE {
|
||||||
|
bigint role_id PK
|
||||||
|
}
|
||||||
|
SYS_MENU {
|
||||||
|
bigint menu_id PK
|
||||||
|
bigint parent_id
|
||||||
|
}
|
||||||
|
SYS_POST {
|
||||||
|
bigint post_id PK
|
||||||
|
}
|
||||||
|
SYS_USER_ROLE {
|
||||||
|
bigint user_id PK
|
||||||
|
bigint role_id PK
|
||||||
|
}
|
||||||
|
SYS_ROLE_MENU {
|
||||||
|
bigint role_id PK
|
||||||
|
bigint menu_id PK
|
||||||
|
}
|
||||||
|
SYS_ROLE_DEPT {
|
||||||
|
bigint role_id PK
|
||||||
|
bigint dept_id PK
|
||||||
|
}
|
||||||
|
SYS_USER_POST {
|
||||||
|
bigint user_id PK
|
||||||
|
bigint post_id PK
|
||||||
|
}
|
||||||
|
SYS_DICT_TYPE {
|
||||||
|
bigint dict_id PK
|
||||||
|
varchar dict_type UK
|
||||||
|
}
|
||||||
|
SYS_DICT_DATA {
|
||||||
|
bigint dict_code PK
|
||||||
|
varchar dict_type
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## OAuth、第三方登录、App 偏好
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
SYS_USER ||--o{ SYS_AUTH_USER : auth_binding
|
||||||
|
SYS_USER ||--o{ IOT_SOCIAL_USER : social_binding
|
||||||
|
IOT_SOCIAL_PLATFORM ||--o{ IOT_SOCIAL_USER : platform
|
||||||
|
OAUTH_CLIENT_DETAILS ||--o{ OAUTH_ACCESS_TOKEN : issues
|
||||||
|
OAUTH_CLIENT_DETAILS ||--o{ OAUTH_REFRESH_TOKEN : refreshes
|
||||||
|
OAUTH_CLIENT_DETAILS ||--o{ OAUTH_CLIENT_TOKEN : client_token
|
||||||
|
SYS_USER ||--o{ OAUTH_ACCESS_TOKEN : token_user
|
||||||
|
SYS_USER ||--o{ APP_PREFERENCES : preferences
|
||||||
|
APP_LANGUAGE ||--o{ APP_PREFERENCES : selected_language
|
||||||
|
|
||||||
|
SYS_AUTH_USER {
|
||||||
|
bigint auth_id PK
|
||||||
|
bigint user_id
|
||||||
|
}
|
||||||
|
IOT_SOCIAL_PLATFORM {
|
||||||
|
bigint social_platform_id PK
|
||||||
|
varchar platform UK
|
||||||
|
}
|
||||||
|
IOT_SOCIAL_USER {
|
||||||
|
bigint social_user_id PK
|
||||||
|
bigint sys_user_id
|
||||||
|
varchar open_id
|
||||||
|
varchar union_id
|
||||||
|
}
|
||||||
|
OAUTH_CLIENT_DETAILS {
|
||||||
|
bigint id PK
|
||||||
|
varchar client_id
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
OAUTH_ACCESS_TOKEN {
|
||||||
|
varchar token_id
|
||||||
|
varchar client_id
|
||||||
|
varchar user_id
|
||||||
|
}
|
||||||
|
APP_LANGUAGE {
|
||||||
|
bigint id PK
|
||||||
|
}
|
||||||
|
APP_PREFERENCES {
|
||||||
|
bigint id PK
|
||||||
|
bigint user_id
|
||||||
|
varchar language
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 内容、OSS、代码生成
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
NEWS_CATEGORY ||--o{ NEWS : contains
|
||||||
|
OSS_CONFIG ||--o{ OSS_DETAIL : stores
|
||||||
|
GEN_TABLE ||--o{ GEN_TABLE_COLUMN : has_column
|
||||||
|
SYS_DICT_TYPE ||--o{ GEN_TABLE_COLUMN : dict
|
||||||
|
|
||||||
|
NEWS_CATEGORY {
|
||||||
|
bigint category_id PK
|
||||||
|
}
|
||||||
|
NEWS {
|
||||||
|
bigint news_id PK
|
||||||
|
bigint category_id
|
||||||
|
}
|
||||||
|
OSS_CONFIG {
|
||||||
|
bigint id PK
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
OSS_DETAIL {
|
||||||
|
bigint id PK
|
||||||
|
bigint tenant_id
|
||||||
|
}
|
||||||
|
GEN_TABLE {
|
||||||
|
bigint table_id PK
|
||||||
|
}
|
||||||
|
GEN_TABLE_COLUMN {
|
||||||
|
bigint column_id PK
|
||||||
|
bigint table_id
|
||||||
|
varchar dict_type
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quartz 真实外键图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
QRTZ_JOB_DETAILS ||--o{ QRTZ_TRIGGERS : has
|
||||||
|
QRTZ_TRIGGERS ||--o| QRTZ_BLOB_TRIGGERS : blob
|
||||||
|
QRTZ_TRIGGERS ||--o| QRTZ_CRON_TRIGGERS : cron
|
||||||
|
QRTZ_TRIGGERS ||--o| QRTZ_SIMPLE_TRIGGERS : simple
|
||||||
|
QRTZ_TRIGGERS ||--o| QRTZ_SIMPROP_TRIGGERS : simprop
|
||||||
|
|
||||||
|
QRTZ_JOB_DETAILS {
|
||||||
|
varchar sched_name PK
|
||||||
|
varchar job_name PK
|
||||||
|
varchar job_group PK
|
||||||
|
}
|
||||||
|
QRTZ_TRIGGERS {
|
||||||
|
varchar sched_name PK
|
||||||
|
varchar trigger_name PK
|
||||||
|
varchar trigger_group PK
|
||||||
|
varchar job_name FK
|
||||||
|
varchar job_group FK
|
||||||
|
}
|
||||||
|
QRTZ_BLOB_TRIGGERS {
|
||||||
|
varchar sched_name PK
|
||||||
|
varchar trigger_name PK
|
||||||
|
varchar trigger_group PK
|
||||||
|
}
|
||||||
|
QRTZ_CRON_TRIGGERS {
|
||||||
|
varchar sched_name PK
|
||||||
|
varchar trigger_name PK
|
||||||
|
varchar trigger_group PK
|
||||||
|
}
|
||||||
|
QRTZ_SIMPLE_TRIGGERS {
|
||||||
|
varchar sched_name PK
|
||||||
|
varchar trigger_name PK
|
||||||
|
varchar trigger_group PK
|
||||||
|
}
|
||||||
|
QRTZ_SIMPROP_TRIGGERS {
|
||||||
|
varchar sched_name PK
|
||||||
|
varchar trigger_name PK
|
||||||
|
varchar trigger_group PK
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 读图说明
|
||||||
|
|
||||||
|
- `||--o{` 表示一对多业务关系。
|
||||||
|
- `||--o|` 表示一对零或一的扩展关系。
|
||||||
|
- 图中 `PK`、`UK`、`FK` 是阅读标注;除 Quartz 图外,`FK` 不代表数据库中存在外键约束。
|
||||||
|
- 对于 `serial_number` 关联,运行时经常直接使用设备编号查找数据,但数据建模上仍应优先理解 `iot_device.device_id` 是设备主键。
|
||||||
663
docs/knowledge-base/05-data-bridge-integration.md
Normal file
663
docs/knowledge-base/05-data-bridge-integration.md
Normal file
@ -0,0 +1,663 @@
|
|||||||
|
# 数据桥接对接文档
|
||||||
|
|
||||||
|
本文面向需要和 岩安测控 对接的第三方系统,覆盖两类能力:
|
||||||
|
|
||||||
|
1. 第三方系统查询设备及其物模型。
|
||||||
|
2. 第三方系统获取设备上报数据,包括 岩安测控 主动推送和第三方主动查询。
|
||||||
|
|
||||||
|
本文基于当前仓库已有接口和数据桥接实现整理。默认服务根地址记为 `{BASE_URL}`,例如 `http://1.116.124.85:8080`。
|
||||||
|
|
||||||
|
## 对接方式总览
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
Device[设备] --> Protocol[MQTT/TCP/HTTP/CoAP 等接入]
|
||||||
|
Protocol --> 岩安测控[岩安测控 数据处理]
|
||||||
|
岩安测控 --> Store[(MySQL/TDengine)]
|
||||||
|
岩安测控 --> Rule[数据流脚本/规则引擎]
|
||||||
|
Rule --> PushHTTP[主动推送 HTTP]
|
||||||
|
Rule --> PushMQTT[主动桥接 MQTT]
|
||||||
|
Rule --> PushDB[写入第三方数据库]
|
||||||
|
Third[第三方系统] --> API[REST API 主动查询]
|
||||||
|
API --> 岩安测控
|
||||||
|
```
|
||||||
|
|
||||||
|
| 场景 | 推荐方式 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 获取设备清单 | REST API 主动查询 | 查询 `iot_device` 设备列表或按设备编号查询详情。 |
|
||||||
|
| 获取设备物模型 | REST API 主动查询 | 查询产品级/设备级物模型,包含属性、事件、服务、计算模型。 |
|
||||||
|
| 获取实时值 | REST API 主动查询 | 查询设备运行时缓存值。 |
|
||||||
|
| 获取历史上报数据 | REST API 主动查询 | 查询数据中心历史接口,支持按设备、时间范围、物模型标识符过滤。 |
|
||||||
|
| 上报数据实时同步给第三方 | 数据桥接主动推送 | 通过数据流脚本触发 HTTP、MQTT 或数据库桥接。 |
|
||||||
|
| 第三方系统周期同步 | REST API 定时拉取 | 第三方按时间窗口分页/分段查询历史数据。 |
|
||||||
|
|
||||||
|
## 认证
|
||||||
|
|
||||||
|
除安全配置中明确放行的匿名接口外,REST API 默认需要登录 Token。
|
||||||
|
|
||||||
|
### 登录获取 Token
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST {BASE_URL}/login
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin123",
|
||||||
|
"code": "验证码",
|
||||||
|
"uuid": "验证码uuid"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
成功响应:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "操作成功",
|
||||||
|
"token": "eyJhbGciOiJIUzUxMiJ9..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
后续请求头:
|
||||||
|
|
||||||
|
```http
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
如果系统关闭验证码或使用内部服务账号,可按部署配置调整登录参数。第三方生产对接建议单独创建服务账号,并只授予以下最小权限:
|
||||||
|
|
||||||
|
| 能力 | 权限标识 |
|
||||||
|
| --- | --- |
|
||||||
|
| 设备查询 | `iot:device:list`, `iot:device:query` |
|
||||||
|
| 物模型查询 | `iot:model:list`, `iot:model:query` |
|
||||||
|
| 历史数据查询 | `dataCenter:history:list` |
|
||||||
|
| 数据分析查询 | `dataCenter:analysis:list` |
|
||||||
|
| 数据桥接配置 | `iot:bridge:list`, `iot:bridge:add`, `iot:bridge:edit`, `iot:bridge:query` |
|
||||||
|
| 数据流脚本配置 | `iot:script:list`, `iot:script:add`, `iot:script:edit`, `iot:script:query` |
|
||||||
|
|
||||||
|
## 通用响应
|
||||||
|
|
||||||
|
普通接口返回 `AjaxResult`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "操作成功",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
分页接口返回 `TableDataInfo`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "查询成功",
|
||||||
|
"total": 100,
|
||||||
|
"rows": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
常见约定:
|
||||||
|
|
||||||
|
- `code=200` 表示成功。
|
||||||
|
- 设备编号 `serialNumber` 在上报和查询中通常会转为大写处理,第三方建议统一传大写。
|
||||||
|
- 时间字段建议使用 `yyyy-MM-dd HH:mm:ss`。
|
||||||
|
|
||||||
|
## 查询设备
|
||||||
|
|
||||||
|
### 设备分页列表
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/device/list?pageNum=1&pageSize=20&deviceName=xxx&serialNumber=ABC001&productId=1
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- Controller:`DeviceController.list`
|
||||||
|
- 权限:`iot:device:list`
|
||||||
|
- 返回:分页设备列表。
|
||||||
|
- 常用过滤字段来自 `Device` 对象,实际可用字段包括 `deviceId`、`deviceName`、`serialNumber`、`productId`、`tenantId`、`deptId`、`status` 等。
|
||||||
|
|
||||||
|
### 设备简短列表
|
||||||
|
|
||||||
|
适合第三方做设备下拉或轻量同步。
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/device/shortList?pageNum=1&pageSize=100&productId=1
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查询所有设备简短列表
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/device/all
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 按设备 ID 查询详情
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/device/{deviceId}
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 按设备编号查询详情
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/device/getDeviceBySerialNumber/{serialNumber}
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/device/getDeviceBySerialNumber/ABC001
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 查询设备物模型
|
||||||
|
|
||||||
|
岩安测控 的物模型主要由 `productId + identifier` 定义。设备级计算物模型或特殊覆盖会结合 `serialNumber` 处理。
|
||||||
|
|
||||||
|
### 按产品 ID 和设备编号查询物模型
|
||||||
|
|
||||||
|
推荐第三方使用该接口,它会从缓存中合并产品级物模型和设备级处理结果。
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/model/listByProductIdAndSerialNumber?productId=1&serialNumber=ABC001
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- Controller:`ThingsModelController.listByProductIdAndSerialNumber`
|
||||||
|
- 权限:`iot:model:list`
|
||||||
|
- 返回:`ThingsModelDTO` 列表。
|
||||||
|
|
||||||
|
典型返回项字段:
|
||||||
|
|
||||||
|
| 字段 | 含义 |
|
||||||
|
| --- | --- |
|
||||||
|
| `modelId` | 物模型 ID。 |
|
||||||
|
| `identifier` | 物模型标识符,历史数据查询和推送数据中使用该字段作为变量名。 |
|
||||||
|
| `modelName` | 物模型名称。 |
|
||||||
|
| `type` | 物模型类型,通常表示属性/功能/事件等。 |
|
||||||
|
| `datatype` | 数据类型定义,例如 number、string、bool、enum 等。 |
|
||||||
|
| `specs` | 枚举、单位、取值范围等扩展规格。 |
|
||||||
|
| `modelOrder` | 排序。 |
|
||||||
|
|
||||||
|
### 查询设备变量概况
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/device/listThingsModel?deviceId=100&pageNum=1&pageSize=50
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
可选过滤:
|
||||||
|
|
||||||
|
- `modelName`:按物模型名称模糊过滤。
|
||||||
|
- `type`:按物模型类型过滤。
|
||||||
|
|
||||||
|
### 查询产品物模型列表
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/model/list?pageNum=1&pageSize=100&productId=1
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取缓存 JSON 物模型
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/model/cache/{productId}
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
适用于需要拿完整 TSL JSON 的场景。
|
||||||
|
|
||||||
|
## 查询实时数据
|
||||||
|
|
||||||
|
### 查询设备运行时状态
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/runtime/running?serialNumber=ABC001
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- Controller:`DeviceRuntimeController.runState`
|
||||||
|
- 返回:物模型当前值列表。
|
||||||
|
- 数据来源:运行时缓存/物模型缓存,不适合替代历史审计。
|
||||||
|
|
||||||
|
典型返回:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "操作成功",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "temperature",
|
||||||
|
"name": "温度",
|
||||||
|
"value": "26.5",
|
||||||
|
"type": 1,
|
||||||
|
"datatype": {
|
||||||
|
"type": "decimal",
|
||||||
|
"unit": "℃"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 第三方主动查询设备上报数据
|
||||||
|
|
||||||
|
### 推荐接口:数据中心历史查询
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST {BASE_URL}/data/center/deviceHistory
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"deviceId": 100,
|
||||||
|
"serialNumber": "ABC001",
|
||||||
|
"beginTime": "2026-05-24 00:00:00",
|
||||||
|
"endTime": "2026-05-24 23:59:59",
|
||||||
|
"identifierList": [
|
||||||
|
{ "identifier": "temperature", "type": 1 },
|
||||||
|
{ "identifier": "humidity", "type": 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- Controller:`DataCenterController.deviceHistory`
|
||||||
|
- 权限:`dataCenter:history:list`
|
||||||
|
- `serialNumber` 必填。
|
||||||
|
- `identifierList` 建议传入,避免一次拉取全部变量导致响应过大。
|
||||||
|
- `type` 与物模型类型保持一致,常见属性数据为 `1`,以实际物模型返回值为准。
|
||||||
|
|
||||||
|
典型返回结构:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "操作成功",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"2026-05-24 10:00:00": [
|
||||||
|
{ "temperature": "26.5" },
|
||||||
|
{ "humidity": "60" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"2026-05-24 10:01:00": [
|
||||||
|
{ "temperature": "26.7" },
|
||||||
|
{ "humidity": "59" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设备日志列表
|
||||||
|
|
||||||
|
适合按原始日志粒度查询。
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/deviceLog/list?pageNum=1&pageSize=50&serialNumber=ABC001&beginTime=2026-05-24 00:00:00&endTime=2026-05-24 23:59:59
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- Controller:`DeviceLogController.list`
|
||||||
|
- 权限:`iot:device:list`
|
||||||
|
- 返回:分页日志行,字段来自 `DeviceLog`。
|
||||||
|
|
||||||
|
### 设备历史数据 GET 查询
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/deviceLog/history?serialNumber=ABC001&beginTime=2026-05-24 00:00:00&endTime=2026-05-24 23:59:59
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
返回按 `identifier` 分组的历史数据,适合较简单的查询场景。
|
||||||
|
|
||||||
|
### 按时间聚合的历史数据
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET {BASE_URL}/iot/deviceLog/historyGroupByCreateTime?pageNum=1&pageSize=100&serialNumber=ABC001&beginTime=2026-05-24 00:00:00&endTime=2026-05-24 23:59:59
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 网关计算物模型历史
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST {BASE_URL}/data/center/deviceHistoryCalculated
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"deviceId": 100,
|
||||||
|
"serialNumber": "GW001",
|
||||||
|
"beginTime": "2026-05-24 00:00:00",
|
||||||
|
"endTime": "2026-05-24 23:59:59",
|
||||||
|
"identifierList": [
|
||||||
|
{ "identifier": "avg_temperature", "type": 1 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 周期拉取建议
|
||||||
|
|
||||||
|
第三方主动查询历史数据时,建议使用滑动时间窗口:
|
||||||
|
|
||||||
|
1. 首次同步:按小时或天分段拉取。
|
||||||
|
2. 增量同步:每 1 至 5 分钟拉取一次 `[lastSyncTime, now - 10s]`。
|
||||||
|
3. 去重键:`serialNumber + identifier + time`。
|
||||||
|
4. 重试:失败窗口不要丢弃,下次继续从 `lastSuccessEndTime` 拉取。
|
||||||
|
5. 大设备量:先同步设备列表,再按设备分批拉取,避免单次请求过大。
|
||||||
|
|
||||||
|
## 岩安测控 主动推送给第三方系统
|
||||||
|
|
||||||
|
岩安测控 当前主动推送走“数据流脚本 + 数据桥接”机制。设备上报后,`RuleProcess.processRuleScript(...)` 会按产品和事件类型找到启用的数据流脚本,然后执行脚本动作:
|
||||||
|
|
||||||
|
| 脚本动作 | `scriptAction` | 桥接组件 | 用途 |
|
||||||
|
| --- | ---: | --- | --- |
|
||||||
|
| HTTP 推送 | 3 | `httpBridge` | 调用第三方 HTTP 接口。 |
|
||||||
|
| MQTT 桥接 | 4 | `mqttBridge` | 发布到第三方 MQTT Broker。 |
|
||||||
|
| 数据库存储 | 5 | `databaseBridge` | 写入第三方数据库。 |
|
||||||
|
|
||||||
|
设备上报事件类型:
|
||||||
|
|
||||||
|
| 事件 | `scriptEvent` | 说明 |
|
||||||
|
| --- | ---: | --- |
|
||||||
|
| 设备上报 | 1 | 设备属性/数据上报后触发。 |
|
||||||
|
| 平台下发 | 2 | 平台服务/指令下发前后触发。 |
|
||||||
|
| 设备上线 | 3 | 设备上线触发。 |
|
||||||
|
| 设备离线 | 4 | 设备离线触发。 |
|
||||||
|
| HTTP 桥接入口数据 | 5 | 第三方调用 `/bridge/get|post|put` 后触发。 |
|
||||||
|
| MQTT 桥接入口数据 | 6 | MQTT 输入桥接触发,按脚本配置处理。 |
|
||||||
|
|
||||||
|
### 主动 HTTP 推送配置
|
||||||
|
|
||||||
|
1. 新增数据桥接。
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST {BASE_URL}/iot/bridge
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "推送到第三方业务系统",
|
||||||
|
"enable": "1",
|
||||||
|
"type": 3,
|
||||||
|
"direction": 2,
|
||||||
|
"route": "https://third.example.com/iot/report",
|
||||||
|
"configJson": "{\"method\":\"POST\",\"hostUrl\":\"https://third.example.com/iot/report\",\"requestHeaders\":\"{\\\"Authorization\\\":\\\"Bearer xxx\\\"}\",\"requestQuerys\":\"{}\",\"requestConfig\":\"\",\"requestBody\":\"\"}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
字段说明:
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| `type=3` | HTTP 推送。 |
|
||||||
|
| `direction=2` | 输出方向。 |
|
||||||
|
| `route` | 展示/路由字段,实际 HTTP 地址以 `configJson.hostUrl` 为准。 |
|
||||||
|
| `configJson.method` | HTTP 方法,如 `POST`。 |
|
||||||
|
| `configJson.hostUrl` | 第三方接收地址。 |
|
||||||
|
| `configJson.requestHeaders` | 请求头 JSON 字符串。 |
|
||||||
|
| `configJson.requestQuerys` | Query 参数 JSON 字符串。 |
|
||||||
|
| `configJson.requestBody` | 自定义请求体模板;为空时默认使用脚本上下文 payload。 |
|
||||||
|
|
||||||
|
2. 新增数据流脚本。
|
||||||
|
|
||||||
|
下面脚本展示的是核心意图:取得 `MsgContext`,设置桥接 ID,必要时重写 `payload/topic`。不同 LiteFlow 脚本语言的上下文变量写法可能不同,落地时以当前环境脚本校验结果为准。
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST {BASE_URL}/iot/script
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"applicationName": "岩安测控",
|
||||||
|
"scriptName": "设备上报转第三方HTTP",
|
||||||
|
"scriptType": "script",
|
||||||
|
"scriptLanguage": "groovy",
|
||||||
|
"scriptPurpose": 1,
|
||||||
|
"scriptEvent": 1,
|
||||||
|
"scriptAction": 3,
|
||||||
|
"productId": 1,
|
||||||
|
"enable": 1,
|
||||||
|
"bridgeId": 10,
|
||||||
|
"scriptData": "def cxt = _meta.getContextBean(com.岩安测控.ruleEngine.context.MsgContext.class); cxt.setPayload(cxt.getPayload()); cxt.setData('httpBridgeID', 10);"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
脚本关键点:
|
||||||
|
|
||||||
|
- `scriptPurpose=1` 表示数据流。
|
||||||
|
- `scriptEvent=1` 表示设备上报触发。
|
||||||
|
- `scriptAction=3` 表示执行 HTTP 推送组件。
|
||||||
|
- `productId` 表示该产品下设备上报时触发。
|
||||||
|
- 脚本必须向上下文写入 `httpBridgeID`,值为桥接配置 `bridge.id`;HTTP 桥接组件会从上下文读取该 ID。
|
||||||
|
- 脚本可改写 `payload`,从而控制推送给第三方的报文。
|
||||||
|
|
||||||
|
### 推荐 HTTP 推送报文
|
||||||
|
|
||||||
|
如果第三方希望使用稳定格式,建议脚本把 payload 改造成如下 JSON:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"event": "property.post",
|
||||||
|
"productId": 1,
|
||||||
|
"serialNumber": "ABC001",
|
||||||
|
"protocolCode": "ModbusRtu",
|
||||||
|
"reportTime": "2026-05-24 10:00:00",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"identifier": "temperature",
|
||||||
|
"value": "26.5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "humidity",
|
||||||
|
"value": "60"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rawPayload": "[{\"id\":\"temperature\",\"value\":\"26.5\"}]"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
当前默认上下文 payload 通常是设备上报解析后的物模型数据 JSON,字段形态可能随协议解析结果变化。生产对接建议在脚本中统一出站格式,避免第三方直接依赖内部 payload。
|
||||||
|
|
||||||
|
### 主动 MQTT 桥接配置
|
||||||
|
|
||||||
|
1. 新增 MQTT 桥接。
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST {BASE_URL}/iot/bridge
|
||||||
|
Authorization: Bearer {token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "第三方MQTT桥接",
|
||||||
|
"enable": "1",
|
||||||
|
"type": 4,
|
||||||
|
"direction": 2,
|
||||||
|
"route": "third/iot/report",
|
||||||
|
"configJson": "{\"name\":\"third-mqtt\",\"hostUrl\":\"broker.example.com:1883\",\"username\":\"user\",\"password\":\"pass\",\"clientId\":\"岩安测控-bridge\"}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 新增脚本。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"applicationName": "岩安测控",
|
||||||
|
"scriptName": "设备上报转第三方MQTT",
|
||||||
|
"scriptType": "script",
|
||||||
|
"scriptLanguage": "groovy",
|
||||||
|
"scriptPurpose": 1,
|
||||||
|
"scriptEvent": 1,
|
||||||
|
"scriptAction": 4,
|
||||||
|
"productId": 1,
|
||||||
|
"enable": 1,
|
||||||
|
"bridgeId": 11,
|
||||||
|
"scriptData": "def cxt = _meta.getContextBean(com.岩安测控.ruleEngine.context.MsgContext.class); cxt.setTopic('third/iot/' + cxt.getSerialNumber() + '/property/post'); cxt.setData('mqttBridgeID', 11);"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- MQTT 桥接组件使用 `cxt.getTopic()` 作为发布主题。
|
||||||
|
- 脚本必须写入 `mqttBridgeID`;MQTT 桥接组件会从上下文读取该 ID。
|
||||||
|
- `payload` 默认透传,建议按第三方格式重写。
|
||||||
|
|
||||||
|
### 写入第三方数据库
|
||||||
|
|
||||||
|
1. 配置数据库桥接数据源。
|
||||||
|
|
||||||
|
数据库桥接组件读取 `MultipleDataSource`,字段包括:
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| `databaseSource` | 数据库类型,支持 MySQL、SQLServer、Oracle、PostgreSQL、H2。 |
|
||||||
|
| `host` | 地址,包含主机和端口。 |
|
||||||
|
| `dataBaseName` | 数据库名。 |
|
||||||
|
| `userName` | 用户名。 |
|
||||||
|
| `password` | 密码。 |
|
||||||
|
| `sql` | 要执行的 SQL,可使用上下文占位符替换。 |
|
||||||
|
|
||||||
|
2. 新增数据流脚本,写入 `databaseBridgeID`。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scriptPurpose": 1,
|
||||||
|
"scriptEvent": 1,
|
||||||
|
"scriptAction": 5,
|
||||||
|
"productId": 1,
|
||||||
|
"enable": 1,
|
||||||
|
"scriptData": "def cxt = _meta.getContextBean(com.岩安测控.ruleEngine.context.MsgContext.class); cxt.setData('databaseBridgeID', 20);"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:数据库桥接当前通过拼接后的 SQL 执行,生产环境必须控制 SQL 模板来源和字段转义,避免把不可信 payload 直接拼入 SQL。
|
||||||
|
|
||||||
|
## 第三方调用 岩安测控 桥接入口
|
||||||
|
|
||||||
|
岩安测控 也提供了桥接入口,第三方可以把数据送入 岩安测控 的数据流脚本:
|
||||||
|
|
||||||
|
| 方法 | 地址 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| GET | `{BASE_URL}/bridge/get` | Query/Header 进入上下文。 |
|
||||||
|
| POST | `{BASE_URL}/bridge/post` | Body 进入上下文 payload。 |
|
||||||
|
| PUT | `{BASE_URL}/bridge/put` | Body 进入上下文 payload。 |
|
||||||
|
|
||||||
|
这些入口会以:
|
||||||
|
|
||||||
|
```text
|
||||||
|
scriptPurpose=1
|
||||||
|
scriptEvent=5
|
||||||
|
route=/bridge/get|/bridge/post|/bridge/put
|
||||||
|
```
|
||||||
|
|
||||||
|
查询并执行数据流脚本。
|
||||||
|
|
||||||
|
安全注意:当前安全配置未默认放行 `/bridge/**`,因此仍需要 Token,除非部署侧自行放开。
|
||||||
|
|
||||||
|
## 推荐对接方案
|
||||||
|
|
||||||
|
### 方案 A:实时同步优先
|
||||||
|
|
||||||
|
适用:第三方需要尽快拿到设备上报数据。
|
||||||
|
|
||||||
|
1. 第三方提供 HTTP 接收地址或 MQTT Broker。
|
||||||
|
2. 岩安测控 配置 `bridge`。
|
||||||
|
3. 岩安测控 为对应产品配置数据流脚本。
|
||||||
|
4. 第三方收到推送后按 `serialNumber + identifier + reportTime` 入库去重。
|
||||||
|
5. 第三方定时调用历史查询接口补偿漏推窗口。
|
||||||
|
|
||||||
|
推荐组合:
|
||||||
|
|
||||||
|
- 实时:HTTP 推送或 MQTT 桥接。
|
||||||
|
- 补偿:`POST /data/center/deviceHistory`。
|
||||||
|
|
||||||
|
### 方案 B:第三方定时拉取
|
||||||
|
|
||||||
|
适用:第三方不开放公网回调地址,或只需要分钟级/小时级同步。
|
||||||
|
|
||||||
|
1. 调用 `/iot/device/list` 或 `/iot/device/shortList` 同步设备。
|
||||||
|
2. 调用 `/iot/model/listByProductIdAndSerialNumber` 同步物模型。
|
||||||
|
3. 按设备调用 `/data/center/deviceHistory` 拉取上报数据。
|
||||||
|
4. 使用滑动窗口和去重键保证幂等。
|
||||||
|
|
||||||
|
### 方案 C:第三方数据库落库
|
||||||
|
|
||||||
|
适用:第三方只提供数据库账号,不提供 API。
|
||||||
|
|
||||||
|
1. 配置数据库连接。
|
||||||
|
2. 为产品配置数据流脚本。
|
||||||
|
3. 数据库桥接执行 SQL 写入第三方库。
|
||||||
|
|
||||||
|
不建议把该方案作为首选,原因是数据库账号权限、SQL 模板安全和网络连通性风险更高。
|
||||||
|
|
||||||
|
## 字段映射建议
|
||||||
|
|
||||||
|
第三方侧建议建立如下最小模型。
|
||||||
|
|
||||||
|
### 设备表
|
||||||
|
|
||||||
|
| 字段 | 来源 |
|
||||||
|
| --- | --- |
|
||||||
|
| `device_id` | `iot_device.deviceId` |
|
||||||
|
| `serial_number` | `iot_device.serialNumber` |
|
||||||
|
| `device_name` | `iot_device.deviceName` |
|
||||||
|
| `product_id` | `iot_device.productId` |
|
||||||
|
| `status` | `iot_device.status` |
|
||||||
|
| `tenant_id` | `iot_device.tenantId` |
|
||||||
|
|
||||||
|
### 物模型表
|
||||||
|
|
||||||
|
| 字段 | 来源 |
|
||||||
|
| --- | --- |
|
||||||
|
| `model_id` | `ThingsModelDTO.modelId` |
|
||||||
|
| `product_id` | 查询入参或返回字段 |
|
||||||
|
| `identifier` | `ThingsModelDTO.identifier` |
|
||||||
|
| `name` | `ThingsModelDTO.modelName` |
|
||||||
|
| `type` | `ThingsModelDTO.type` |
|
||||||
|
| `datatype` | `ThingsModelDTO.datatype` |
|
||||||
|
|
||||||
|
### 设备数据表
|
||||||
|
|
||||||
|
| 字段 | 来源 |
|
||||||
|
| --- | --- |
|
||||||
|
| `serial_number` | 设备编号 |
|
||||||
|
| `product_id` | 设备所属产品 |
|
||||||
|
| `identifier` | 物模型标识符 |
|
||||||
|
| `value` | 上报值 |
|
||||||
|
| `report_time` | 历史接口返回的时间或推送报文时间 |
|
||||||
|
| `source` | `pull` 或 `push` |
|
||||||
|
| `raw_payload` | 原始推送/查询报文 |
|
||||||
|
|
||||||
|
## 幂等、重试和安全
|
||||||
|
|
||||||
|
- 推送接口必须幂等,建议第三方以 `serialNumber + identifier + reportTime` 去重。
|
||||||
|
- 岩安测控 主动推送失败当前主要依赖组件日志排查,建议第三方同时做主动拉取补偿。
|
||||||
|
- 第三方主动查询必须按时间窗口限制范围,避免一次拉取全量历史。
|
||||||
|
- HTTP 推送建议使用 HTTPS、固定 Token 或签名头。
|
||||||
|
- MQTT 桥接建议使用独立账号和最小 Topic 权限。
|
||||||
|
- 数据库桥接建议只授予 INSERT 权限,避免执行 DDL 或高危 SQL。
|
||||||
|
|
||||||
|
## 当前实现注意点
|
||||||
|
|
||||||
|
- `岩安测控-mq` 当前核心是 JVM 内本地阻塞队列,不是外部 RocketMQ 的强持久化链路。
|
||||||
|
- 主动 HTTP 推送的默认 payload 由规则脚本上下文决定,生产对接应在脚本中统一出站报文。
|
||||||
|
- 数据桥接脚本查询按 `productId + scriptPurpose + scriptEvent + enable` 等条件筛选;产品维度必须配置正确。
|
||||||
|
- 业务表关系大多没有数据库外键,第三方不要依赖数据库外键判断关联,应以 API 返回和业务字段为准。
|
||||||
25
docs/knowledge-base/README.md
Normal file
25
docs/knowledge-base/README.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# FastBee 项目知识库
|
||||||
|
|
||||||
|
本知识库用于快速理解 FastBee 当前仓库的整体架构、运行链路、模块职责和数据库关系。内容基于本仓库代码、Maven 配置、`application*.yml` 和 `sql/yack-20260316.sql` 梳理。
|
||||||
|
|
||||||
|
## 阅读路径
|
||||||
|
|
||||||
|
1. [整体架构](./01-architecture.md):项目分层、模块边界、依赖入口、前后端组成。
|
||||||
|
2. [核心运行链路](./02-runtime-flows.md):启动、设备接入、上报、下发、规则引擎、视频和通知链路。
|
||||||
|
3. [数据库与表关系](./03-data-model.md):表分组、核心实体、业务关联、真实外键和约定关联。
|
||||||
|
4. [数据库关系图](./04-er-diagrams.md):按业务域拆分的 Mermaid ER 图。
|
||||||
|
5. [数据桥接对接文档](./05-data-bridge-integration.md):设备/物模型查询、设备上报数据推送和第三方主动查询。
|
||||||
|
|
||||||
|
## 当前结论
|
||||||
|
|
||||||
|
- 后端是 Java 8 + Spring Boot 2.5.14 的 Maven 多模块工程,主入口是 `fastbee-admin`。
|
||||||
|
- 前端包含三个独立应用:`yack-web` 管理后台、`yack-view` 低代码可视化、`yack-app` UniApp 移动端。
|
||||||
|
- IoT 接入由 `fastbee-server` 下的 Netty 协议服务承载,协议编解码在 `fastbee-protocol`,业务处理集中在 `fastbee-iot-data` 和 `fastbee-service/fastbee-iot-service`。
|
||||||
|
- 设备消息队列当前主要是 JVM 内本地 `LinkedBlockingQueue` 队列族,`fastbee-mq` 模块名称虽为 MQ,但核心代码不是外部 RocketMQ Consumer 模式。
|
||||||
|
- 主库初始化脚本为 `sql/yack-20260316.sql`。脚本中大部分业务表没有数据库外键,关系依靠字段命名、联合主键、索引和业务代码约定;Quartz 表保留了真实外键约束。
|
||||||
|
|
||||||
|
## 维护约定
|
||||||
|
|
||||||
|
- 业务逻辑、表关系和配置变化后,同步更新本目录对应文档。
|
||||||
|
- 表关系文档中用“强约束”表示 SQL 外键,用“业务约定”表示代码和字段约定形成的关系。
|
||||||
|
- 不把推断关系写成数据库强约束,除非 SQL 中确实存在 `FOREIGN KEY`。
|
||||||
@ -0,0 +1,196 @@
|
|||||||
|
package com.fastbee.controller.integration;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.fastbee.common.annotation.Anonymous;
|
||||||
|
import com.fastbee.common.core.controller.BaseController;
|
||||||
|
import com.fastbee.common.core.domain.AjaxResult;
|
||||||
|
import com.fastbee.common.core.page.TableDataInfo;
|
||||||
|
import com.fastbee.common.exception.ServiceException;
|
||||||
|
import com.fastbee.common.utils.StringUtils;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationApi;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationDeviceMapping;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationLog;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationPlatform;
|
||||||
|
import com.fastbee.iot.model.integration.IntegrationSyncRequest;
|
||||||
|
import com.fastbee.iot.service.integration.IIntegrationPlatformService;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方平台接入。
|
||||||
|
*/
|
||||||
|
@Api(tags = "第三方平台接入")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/iot/integration")
|
||||||
|
public class IntegrationPlatformController extends BaseController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IIntegrationPlatformService integrationPlatformService;
|
||||||
|
|
||||||
|
@ApiOperation("查询平台接入配置")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
|
||||||
|
@GetMapping("/platform/list")
|
||||||
|
public TableDataInfo list(IntegrationPlatform query) {
|
||||||
|
startPage();
|
||||||
|
List<IntegrationPlatform> list = integrationPlatformService.selectPlatformList(query);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("查询平台接入配置详情")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
|
||||||
|
@GetMapping("/platform/{id}")
|
||||||
|
public AjaxResult getPlatform(@PathVariable Long id) {
|
||||||
|
return AjaxResult.success(integrationPlatformService.selectPlatformById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("新增平台接入配置")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:add')")
|
||||||
|
@PostMapping("/platform")
|
||||||
|
public AjaxResult addPlatform(@RequestBody IntegrationPlatform platform) {
|
||||||
|
return toAjax(integrationPlatformService.insertPlatform(platform));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("修改平台接入配置")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:edit')")
|
||||||
|
@PutMapping("/platform")
|
||||||
|
public AjaxResult editPlatform(@RequestBody IntegrationPlatform platform) {
|
||||||
|
return toAjax(integrationPlatformService.updatePlatform(platform));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("删除平台接入配置")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:remove')")
|
||||||
|
@DeleteMapping("/platform/{ids}")
|
||||||
|
public AjaxResult removePlatform(@PathVariable Long[] ids) {
|
||||||
|
return toAjax(integrationPlatformService.deletePlatformByIds(ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("查询平台接口定义")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
|
||||||
|
@GetMapping("/api/list")
|
||||||
|
public TableDataInfo apiList(IntegrationApi query) {
|
||||||
|
startPage();
|
||||||
|
List<IntegrationApi> list = integrationPlatformService.selectApiList(query);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("查询平台接口定义详情")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
|
||||||
|
@GetMapping("/api/{id}")
|
||||||
|
public AjaxResult getApi(@PathVariable Long id) {
|
||||||
|
return AjaxResult.success(integrationPlatformService.selectApiById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("新增平台接口定义")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:add')")
|
||||||
|
@PostMapping("/api")
|
||||||
|
public AjaxResult addApi(@RequestBody IntegrationApi api) {
|
||||||
|
return toAjax(integrationPlatformService.insertApi(api));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("修改平台接口定义")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:edit')")
|
||||||
|
@PutMapping("/api")
|
||||||
|
public AjaxResult editApi(@RequestBody IntegrationApi api) {
|
||||||
|
return toAjax(integrationPlatformService.updateApi(api));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("删除平台接口定义")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:remove')")
|
||||||
|
@DeleteMapping("/api/{ids}")
|
||||||
|
public AjaxResult removeApi(@PathVariable Long[] ids) {
|
||||||
|
return toAjax(integrationPlatformService.deleteApiByIds(ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("查询平台设备映射")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
|
||||||
|
@GetMapping("/device-mapping/list")
|
||||||
|
public TableDataInfo deviceMappingList(IntegrationDeviceMapping query) {
|
||||||
|
startPage();
|
||||||
|
List<IntegrationDeviceMapping> list = integrationPlatformService.selectDeviceMappingList(query);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("查询平台设备映射详情")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
|
||||||
|
@GetMapping("/device-mapping/{id}")
|
||||||
|
public AjaxResult getDeviceMapping(@PathVariable Long id) {
|
||||||
|
return AjaxResult.success(integrationPlatformService.selectDeviceMappingById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("新增平台设备映射")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:add')")
|
||||||
|
@PostMapping("/device-mapping")
|
||||||
|
public AjaxResult addDeviceMapping(@RequestBody IntegrationDeviceMapping mapping) {
|
||||||
|
return toAjax(integrationPlatformService.insertDeviceMapping(mapping));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("修改平台设备映射")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:edit')")
|
||||||
|
@PutMapping("/device-mapping")
|
||||||
|
public AjaxResult editDeviceMapping(@RequestBody IntegrationDeviceMapping mapping) {
|
||||||
|
return toAjax(integrationPlatformService.updateDeviceMapping(mapping));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("删除平台设备映射")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:remove')")
|
||||||
|
@DeleteMapping("/device-mapping/{ids}")
|
||||||
|
public AjaxResult removeDeviceMapping(@PathVariable Long[] ids) {
|
||||||
|
return toAjax(integrationPlatformService.deleteDeviceMappingByIds(ids));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("查询平台同步日志")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:list')")
|
||||||
|
@GetMapping("/log/list")
|
||||||
|
public TableDataInfo logList(IntegrationLog query) {
|
||||||
|
startPage();
|
||||||
|
List<IntegrationLog> list = integrationPlatformService.selectLogList(query);
|
||||||
|
return getDataTable(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("触发平台数据同步")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:sync')")
|
||||||
|
@PostMapping("/sync")
|
||||||
|
public AjaxResult sync(@RequestBody IntegrationSyncRequest request) {
|
||||||
|
return AjaxResult.success(integrationPlatformService.sync(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Anonymous
|
||||||
|
@ApiOperation("第三方触发平台数据同步")
|
||||||
|
@PostMapping("/open/sync")
|
||||||
|
public AjaxResult openSync(@RequestHeader(value = "X-Integration-Key", required = false) String integrationKey,
|
||||||
|
@RequestBody IntegrationSyncRequest request) {
|
||||||
|
checkOpenKey(request, integrationKey);
|
||||||
|
return AjaxResult.success(integrationPlatformService.sync(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("平台适配器自测")
|
||||||
|
@PreAuthorize("@ss.hasPermi('iot:integration:sync')")
|
||||||
|
@PostMapping("/self-test/{platformCode}")
|
||||||
|
public AjaxResult selfTest(@PathVariable String platformCode) {
|
||||||
|
return AjaxResult.success(integrationPlatformService.selfTest(platformCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkOpenKey(IntegrationSyncRequest request, String integrationKey) {
|
||||||
|
if (request == null || StringUtils.isBlank(request.getPlatformCode())) {
|
||||||
|
throw new ServiceException("平台编码不能为空");
|
||||||
|
}
|
||||||
|
IntegrationPlatform platform = integrationPlatformService.selectPlatformByCode(request.getPlatformCode());
|
||||||
|
if (platform == null) {
|
||||||
|
throw new ServiceException("平台不存在");
|
||||||
|
}
|
||||||
|
JSONObject credential = StringUtils.isBlank(platform.getCredentialJson()) ? new JSONObject() : JSON.parseObject(platform.getCredentialJson());
|
||||||
|
String openApiKey = credential.getString("openApiKey");
|
||||||
|
if (StringUtils.isBlank(openApiKey)) {
|
||||||
|
throw new ServiceException("平台未启用开放调度密钥");
|
||||||
|
}
|
||||||
|
if (!openApiKey.equals(integrationKey)) {
|
||||||
|
throw new ServiceException("开放调度密钥无效");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
package com.fastbee.controller.integration.dcp;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.fastbee.common.annotation.Anonymous;
|
||||||
|
import com.fastbee.iot.service.integration.IDcpOpenApiService;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DCP轴力计兼容开放接口。
|
||||||
|
*
|
||||||
|
* <p>接口路径与《DCP接口文档(轴力计)》保持一致,前缀为 /open/dcp。</p>
|
||||||
|
*/
|
||||||
|
@Anonymous
|
||||||
|
@Api(tags = "DCP轴力计兼容开放接口")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/open/dcp")
|
||||||
|
public class DcpOpenApiController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IDcpOpenApiService dcpOpenApiService;
|
||||||
|
|
||||||
|
@ApiOperation("获取访问凭证")
|
||||||
|
@PostMapping("/connect/token")
|
||||||
|
public JSONObject token(@RequestParam Map<String, String> form) {
|
||||||
|
return dcpOpenApiService.createToken(form == null ? new HashMap<>() : form);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("获取用户信息")
|
||||||
|
@GetMapping("/api/app/user/user-id")
|
||||||
|
public JSONObject userId(@RequestHeader("Authorization") String authorization) {
|
||||||
|
return dcpOpenApiService.getUser(authorization);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("获取项目信息")
|
||||||
|
@GetMapping("/api/app/project/by-user-id/{userId}")
|
||||||
|
public JSONArray projects(@RequestHeader("Authorization") String authorization,
|
||||||
|
@PathVariable String userId) {
|
||||||
|
return dcpOpenApiService.getProjects(authorization, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("获取项目轴力计测点")
|
||||||
|
@PostMapping("/api/app/stress-sensor/by-project-id/{projectId}")
|
||||||
|
public JSONArray stressSensors(@RequestHeader("Authorization") String authorization,
|
||||||
|
@PathVariable String projectId) {
|
||||||
|
return dcpOpenApiService.getStressSensors(authorization, projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation("批量获取轴力计数据")
|
||||||
|
@PostMapping("/api/app/inclino-data/load-dataset-by-device-ids")
|
||||||
|
public JSONObject dataset(@RequestHeader("Authorization") String authorization,
|
||||||
|
@RequestParam String startTime,
|
||||||
|
@RequestParam String endTime,
|
||||||
|
@RequestParam(defaultValue = "true") Boolean isOriginal,
|
||||||
|
@RequestBody List<String> deviceIds) {
|
||||||
|
return dcpOpenApiService.getDataset(authorization, startTime, endTime, isOriginal, deviceIds);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,68 @@
|
|||||||
|
package com.fastbee.iot.domain.integration;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fastbee.common.core.domain.BaseEntity;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方平台接口定义。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("iot_integration_api")
|
||||||
|
@ApiModel(value = "IntegrationApi", description = "第三方平台接口定义")
|
||||||
|
public class IntegrationApi extends BaseEntity {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
@ApiModelProperty("主键")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ApiModelProperty("平台ID")
|
||||||
|
private Long platformId;
|
||||||
|
|
||||||
|
@ApiModelProperty("接口编码")
|
||||||
|
private String apiCode;
|
||||||
|
|
||||||
|
@ApiModelProperty("接口名称")
|
||||||
|
private String apiName;
|
||||||
|
|
||||||
|
@ApiModelProperty("请求方法")
|
||||||
|
private String method;
|
||||||
|
|
||||||
|
@ApiModelProperty("接口 URI")
|
||||||
|
private String uri;
|
||||||
|
|
||||||
|
@ApiModelProperty("Content-Type")
|
||||||
|
private String contentType;
|
||||||
|
|
||||||
|
@ApiModelProperty("请求头模板 JSON")
|
||||||
|
private String headersJson;
|
||||||
|
|
||||||
|
@ApiModelProperty("Query 模板 JSON")
|
||||||
|
private String queryJson;
|
||||||
|
|
||||||
|
@ApiModelProperty("Body 模板 JSON")
|
||||||
|
private String bodyJson;
|
||||||
|
|
||||||
|
@ApiModelProperty("返回数据路径")
|
||||||
|
private String responsePath;
|
||||||
|
|
||||||
|
@ApiModelProperty("依赖接口编码")
|
||||||
|
private String dependsOn;
|
||||||
|
|
||||||
|
@ApiModelProperty("排序")
|
||||||
|
private Integer sortOrder;
|
||||||
|
|
||||||
|
@ApiModelProperty("状态:0=停用 1=启用")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@ApiModelProperty("删除标志:0=存在 2=删除")
|
||||||
|
private String delFlag;
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
package com.fastbee.iot.domain.integration;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fastbee.common.core.domain.BaseEntity;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方设备与本系统设备映射。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("iot_integration_device_mapping")
|
||||||
|
@ApiModel(value = "IntegrationDeviceMapping", description = "第三方设备映射")
|
||||||
|
public class IntegrationDeviceMapping extends BaseEntity {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
@ApiModelProperty("主键")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ApiModelProperty("平台ID")
|
||||||
|
private Long platformId;
|
||||||
|
|
||||||
|
@ApiModelProperty("第三方设备ID")
|
||||||
|
private String externalDeviceId;
|
||||||
|
|
||||||
|
@ApiModelProperty("第三方设备名称")
|
||||||
|
private String externalDeviceName;
|
||||||
|
|
||||||
|
@ApiModelProperty("本系统产品ID")
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
@ApiModelProperty("本系统设备编号")
|
||||||
|
private String serialNumber;
|
||||||
|
|
||||||
|
@ApiModelProperty("指标映射 JSON,sourceField -> identifier")
|
||||||
|
private String identifierMappingJson;
|
||||||
|
|
||||||
|
@ApiModelProperty("状态:0=停用 1=启用")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@ApiModelProperty("删除标志:0=存在 2=删除")
|
||||||
|
private String delFlag;
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
package com.fastbee.iot.domain.integration;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fastbee.common.core.domain.BaseEntity;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方平台接入执行日志。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("iot_integration_log")
|
||||||
|
@ApiModel(value = "IntegrationLog", description = "第三方平台接入执行日志")
|
||||||
|
public class IntegrationLog extends BaseEntity {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
@ApiModelProperty("主键")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ApiModelProperty("平台ID")
|
||||||
|
private Long platformId;
|
||||||
|
|
||||||
|
@ApiModelProperty("平台编码")
|
||||||
|
private String platformCode;
|
||||||
|
|
||||||
|
@ApiModelProperty("流程编码")
|
||||||
|
private String flowCode;
|
||||||
|
|
||||||
|
@ApiModelProperty("请求ID")
|
||||||
|
private String requestId;
|
||||||
|
|
||||||
|
@ApiModelProperty("开始时间")
|
||||||
|
private Date startTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("结束时间")
|
||||||
|
private Date endTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("状态:0=失败 1=成功 2=执行中")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@ApiModelProperty("成功数量")
|
||||||
|
private Integer successCount;
|
||||||
|
|
||||||
|
@ApiModelProperty("失败数量")
|
||||||
|
private Integer failCount;
|
||||||
|
|
||||||
|
@ApiModelProperty("错误信息")
|
||||||
|
private String errorMsg;
|
||||||
|
|
||||||
|
@ApiModelProperty("详情 JSON")
|
||||||
|
private String detailJson;
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package com.fastbee.iot.domain.integration;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fastbee.common.core.domain.BaseEntity;
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方平台接入配置。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("iot_integration_platform")
|
||||||
|
@ApiModel(value = "IntegrationPlatform", description = "第三方平台接入配置")
|
||||||
|
public class IntegrationPlatform extends BaseEntity {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
@ApiModelProperty("主键")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ApiModelProperty("平台编码")
|
||||||
|
private String platformCode;
|
||||||
|
|
||||||
|
@ApiModelProperty("平台名称")
|
||||||
|
private String platformName;
|
||||||
|
|
||||||
|
@ApiModelProperty("基础地址")
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
@ApiModelProperty("认证类型")
|
||||||
|
private String authType;
|
||||||
|
|
||||||
|
@ApiModelProperty("凭证配置 JSON")
|
||||||
|
private String credentialJson;
|
||||||
|
|
||||||
|
@ApiModelProperty("状态:0=停用 1=启用")
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
@ApiModelProperty("删除标志:0=存在 2=删除")
|
||||||
|
private String delFlag;
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.fastbee.iot.mapper.integration;
|
||||||
|
|
||||||
|
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationApi;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface IntegrationApiMapper extends BaseMapperX<IntegrationApi> {
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.fastbee.iot.mapper.integration;
|
||||||
|
|
||||||
|
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationDeviceMapping;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface IntegrationDeviceMappingMapper extends BaseMapperX<IntegrationDeviceMapping> {
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.fastbee.iot.mapper.integration;
|
||||||
|
|
||||||
|
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationLog;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface IntegrationLogMapper extends BaseMapperX<IntegrationLog> {
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.fastbee.iot.mapper.integration;
|
||||||
|
|
||||||
|
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationPlatform;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface IntegrationPlatformMapper extends BaseMapperX<IntegrationPlatform> {
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package com.fastbee.iot.model.integration;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方平台同步请求。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "IntegrationSyncRequest", description = "第三方平台同步请求")
|
||||||
|
public class IntegrationSyncRequest {
|
||||||
|
|
||||||
|
@ApiModelProperty("平台编码")
|
||||||
|
private String platformCode;
|
||||||
|
|
||||||
|
@ApiModelProperty("开始时间,ISO-8601 格式")
|
||||||
|
private String startTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("结束时间,ISO-8601 格式")
|
||||||
|
private String endTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否原始数据")
|
||||||
|
private Boolean original;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否只演练不写入")
|
||||||
|
private Boolean dryRun;
|
||||||
|
|
||||||
|
@ApiModelProperty("第三方项目ID,未传则使用平台流程获取")
|
||||||
|
private String projectId;
|
||||||
|
|
||||||
|
@ApiModelProperty("指定第三方设备ID")
|
||||||
|
private List<String> externalDeviceIds;
|
||||||
|
|
||||||
|
@ApiModelProperty("运行时参数")
|
||||||
|
private Map<String, Object> params;
|
||||||
|
}
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
package com.fastbee.iot.model.integration;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModel;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方平台同步结果。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@ApiModel(value = "IntegrationSyncResult", description = "第三方平台同步结果")
|
||||||
|
public class IntegrationSyncResult {
|
||||||
|
|
||||||
|
@ApiModelProperty("平台编码")
|
||||||
|
private String platformCode;
|
||||||
|
|
||||||
|
@ApiModelProperty("流程编码")
|
||||||
|
private String flowCode;
|
||||||
|
|
||||||
|
@ApiModelProperty("请求ID")
|
||||||
|
private String requestId;
|
||||||
|
|
||||||
|
@ApiModelProperty("是否演练")
|
||||||
|
private boolean dryRun;
|
||||||
|
|
||||||
|
@ApiModelProperty("成功数量")
|
||||||
|
private int successCount;
|
||||||
|
|
||||||
|
@ApiModelProperty("失败数量")
|
||||||
|
private int failCount;
|
||||||
|
|
||||||
|
@ApiModelProperty("设备数量")
|
||||||
|
private int deviceCount;
|
||||||
|
|
||||||
|
@ApiModelProperty("数据数量")
|
||||||
|
private int dataCount;
|
||||||
|
|
||||||
|
@ApiModelProperty("消息")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@ApiModelProperty("详情")
|
||||||
|
private List<String> details = new ArrayList<>();
|
||||||
|
|
||||||
|
public void addSuccess() {
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFail(String detail) {
|
||||||
|
failCount++;
|
||||||
|
if (detail != null) {
|
||||||
|
details.add(detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.fastbee.iot.model.integration;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台接入后的统一遥测数据。
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class UnifiedTelemetry {
|
||||||
|
|
||||||
|
private String platformCode;
|
||||||
|
|
||||||
|
private String externalDeviceId;
|
||||||
|
|
||||||
|
private String externalDeviceName;
|
||||||
|
|
||||||
|
private Date reportTime;
|
||||||
|
|
||||||
|
private Map<String, Object> values = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
private Object raw;
|
||||||
|
}
|
||||||
@ -0,0 +1,434 @@
|
|||||||
|
package com.fastbee.iot.service.impl.integration;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.fastbee.common.exception.ServiceException;
|
||||||
|
import com.fastbee.common.utils.StringUtils;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationApi;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationDeviceMapping;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationLog;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationPlatform;
|
||||||
|
import com.fastbee.iot.mapper.integration.IntegrationApiMapper;
|
||||||
|
import com.fastbee.iot.mapper.integration.IntegrationDeviceMappingMapper;
|
||||||
|
import com.fastbee.iot.mapper.integration.IntegrationLogMapper;
|
||||||
|
import com.fastbee.iot.mapper.integration.IntegrationPlatformMapper;
|
||||||
|
import com.fastbee.iot.model.integration.IntegrationSyncRequest;
|
||||||
|
import com.fastbee.iot.model.integration.IntegrationSyncResult;
|
||||||
|
import com.fastbee.iot.service.integration.IIntegrationPlatformService;
|
||||||
|
import com.fastbee.iot.service.integration.PlatformAdapter;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方平台接入服务。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class IntegrationPlatformServiceImpl implements IIntegrationPlatformService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IntegrationPlatformMapper platformMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IntegrationApiMapper apiMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IntegrationDeviceMappingMapper deviceMappingMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IntegrationLogMapper logMapper;
|
||||||
|
|
||||||
|
private final Map<String, PlatformAdapter> adapterMap = new HashMap<>();
|
||||||
|
|
||||||
|
public IntegrationPlatformServiceImpl(List<PlatformAdapter> adapters) {
|
||||||
|
if (adapters != null) {
|
||||||
|
for (PlatformAdapter adapter : adapters) {
|
||||||
|
adapterMap.put(adapter.platformCode().toUpperCase(), adapter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IntegrationPlatform selectPlatformByCode(String platformCode) {
|
||||||
|
if (StringUtils.isBlank(platformCode)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<IntegrationPlatform> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(IntegrationPlatform::getPlatformCode, platformCode.toUpperCase())
|
||||||
|
.eq(IntegrationPlatform::getDelFlag, "0")
|
||||||
|
.last("limit 1");
|
||||||
|
return platformMapper.selectOne(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IntegrationPlatform selectPlatformById(Long id) {
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<IntegrationPlatform> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(IntegrationPlatform::getId, id)
|
||||||
|
.eq(IntegrationPlatform::getDelFlag, "0")
|
||||||
|
.last("limit 1");
|
||||||
|
return platformMapper.selectOne(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<IntegrationPlatform> selectPlatformList(IntegrationPlatform query) {
|
||||||
|
LambdaQueryWrapper<IntegrationPlatform> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(IntegrationPlatform::getDelFlag, "0");
|
||||||
|
if (query != null) {
|
||||||
|
wrapper.like(StringUtils.isNotBlank(query.getPlatformName()), IntegrationPlatform::getPlatformName, query.getPlatformName());
|
||||||
|
wrapper.eq(StringUtils.isNotBlank(query.getPlatformCode()), IntegrationPlatform::getPlatformCode, query.getPlatformCode());
|
||||||
|
wrapper.eq(query.getStatus() != null, IntegrationPlatform::getStatus, query.getStatus());
|
||||||
|
}
|
||||||
|
wrapper.orderByDesc(IntegrationPlatform::getCreateTime);
|
||||||
|
return platformMapper.selectList(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int insertPlatform(IntegrationPlatform platform) {
|
||||||
|
checkPlatform(platform);
|
||||||
|
String platformCode = platform.getPlatformCode().toUpperCase();
|
||||||
|
if (selectPlatformByCode(platformCode) != null) {
|
||||||
|
throw new ServiceException("平台编码已存在:" + platformCode);
|
||||||
|
}
|
||||||
|
platform.setPlatformCode(platformCode);
|
||||||
|
platform.setCredentialJson(normalizeJson(platform.getCredentialJson(), "凭证配置"));
|
||||||
|
if (platform.getStatus() == null) {
|
||||||
|
platform.setStatus(0);
|
||||||
|
}
|
||||||
|
platform.setDelFlag("0");
|
||||||
|
platform.setCreateTime(new Date());
|
||||||
|
return platformMapper.insert(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int updatePlatform(IntegrationPlatform platform) {
|
||||||
|
if (platform == null || platform.getId() == null) {
|
||||||
|
throw new ServiceException("平台ID不能为空");
|
||||||
|
}
|
||||||
|
checkPlatform(platform);
|
||||||
|
String platformCode = platform.getPlatformCode().toUpperCase();
|
||||||
|
IntegrationPlatform exists = selectPlatformByCode(platformCode);
|
||||||
|
if (exists != null && !exists.getId().equals(platform.getId())) {
|
||||||
|
throw new ServiceException("平台编码已存在:" + platformCode);
|
||||||
|
}
|
||||||
|
platform.setPlatformCode(platformCode);
|
||||||
|
platform.setCredentialJson(normalizeJson(platform.getCredentialJson(), "凭证配置"));
|
||||||
|
platform.setUpdateTime(new Date());
|
||||||
|
return platformMapper.updateById(platform);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int deletePlatformByIds(Long[] ids) {
|
||||||
|
int rows = 0;
|
||||||
|
if (ids == null) {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
for (Long id : ids) {
|
||||||
|
if (id == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
IntegrationPlatform platform = new IntegrationPlatform();
|
||||||
|
platform.setId(id);
|
||||||
|
platform.setDelFlag("2");
|
||||||
|
platform.setUpdateTime(new Date());
|
||||||
|
rows += platformMapper.updateById(platform);
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<IntegrationApi> selectApiList(IntegrationApi query) {
|
||||||
|
LambdaQueryWrapper<IntegrationApi> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(IntegrationApi::getDelFlag, "0");
|
||||||
|
if (query != null) {
|
||||||
|
wrapper.eq(query.getPlatformId() != null, IntegrationApi::getPlatformId, query.getPlatformId());
|
||||||
|
wrapper.like(StringUtils.isNotBlank(query.getApiCode()), IntegrationApi::getApiCode, query.getApiCode());
|
||||||
|
wrapper.like(StringUtils.isNotBlank(query.getApiName()), IntegrationApi::getApiName, query.getApiName());
|
||||||
|
wrapper.eq(query.getStatus() != null, IntegrationApi::getStatus, query.getStatus());
|
||||||
|
}
|
||||||
|
wrapper.orderByAsc(IntegrationApi::getSortOrder).orderByAsc(IntegrationApi::getId);
|
||||||
|
return apiMapper.selectList(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IntegrationApi selectApiById(Long id) {
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<IntegrationApi> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(IntegrationApi::getId, id)
|
||||||
|
.eq(IntegrationApi::getDelFlag, "0")
|
||||||
|
.last("limit 1");
|
||||||
|
return apiMapper.selectOne(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int insertApi(IntegrationApi api) {
|
||||||
|
checkApi(api);
|
||||||
|
if (api.getStatus() == null) {
|
||||||
|
api.setStatus(1);
|
||||||
|
}
|
||||||
|
if (api.getSortOrder() == null) {
|
||||||
|
api.setSortOrder(0);
|
||||||
|
}
|
||||||
|
api.setApiCode(api.getApiCode().toUpperCase());
|
||||||
|
api.setMethod(api.getMethod().toUpperCase());
|
||||||
|
api.setHeadersJson(normalizeJson(api.getHeadersJson(), "请求头模板"));
|
||||||
|
api.setQueryJson(normalizeJson(api.getQueryJson(), "Query模板"));
|
||||||
|
api.setBodyJson(normalizeJson(api.getBodyJson(), "Body模板"));
|
||||||
|
api.setDelFlag("0");
|
||||||
|
api.setCreateTime(new Date());
|
||||||
|
return apiMapper.insert(api);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int updateApi(IntegrationApi api) {
|
||||||
|
if (api == null || api.getId() == null) {
|
||||||
|
throw new ServiceException("接口ID不能为空");
|
||||||
|
}
|
||||||
|
checkApi(api);
|
||||||
|
api.setApiCode(api.getApiCode().toUpperCase());
|
||||||
|
api.setMethod(api.getMethod().toUpperCase());
|
||||||
|
api.setHeadersJson(normalizeJson(api.getHeadersJson(), "请求头模板"));
|
||||||
|
api.setQueryJson(normalizeJson(api.getQueryJson(), "Query模板"));
|
||||||
|
api.setBodyJson(normalizeJson(api.getBodyJson(), "Body模板"));
|
||||||
|
api.setUpdateTime(new Date());
|
||||||
|
return apiMapper.updateById(api);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int deleteApiByIds(Long[] ids) {
|
||||||
|
int rows = 0;
|
||||||
|
if (ids == null) {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
for (Long id : ids) {
|
||||||
|
if (id == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
IntegrationApi api = new IntegrationApi();
|
||||||
|
api.setId(id);
|
||||||
|
api.setDelFlag("2");
|
||||||
|
api.setUpdateTime(new Date());
|
||||||
|
rows += apiMapper.updateById(api);
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<IntegrationDeviceMapping> selectDeviceMappingList(IntegrationDeviceMapping query) {
|
||||||
|
LambdaQueryWrapper<IntegrationDeviceMapping> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(IntegrationDeviceMapping::getDelFlag, "0");
|
||||||
|
if (query != null) {
|
||||||
|
wrapper.eq(query.getPlatformId() != null, IntegrationDeviceMapping::getPlatformId, query.getPlatformId());
|
||||||
|
wrapper.like(StringUtils.isNotBlank(query.getExternalDeviceId()), IntegrationDeviceMapping::getExternalDeviceId, query.getExternalDeviceId());
|
||||||
|
wrapper.like(StringUtils.isNotBlank(query.getExternalDeviceName()), IntegrationDeviceMapping::getExternalDeviceName, query.getExternalDeviceName());
|
||||||
|
wrapper.eq(query.getProductId() != null, IntegrationDeviceMapping::getProductId, query.getProductId());
|
||||||
|
wrapper.like(StringUtils.isNotBlank(query.getSerialNumber()), IntegrationDeviceMapping::getSerialNumber, query.getSerialNumber());
|
||||||
|
wrapper.eq(query.getStatus() != null, IntegrationDeviceMapping::getStatus, query.getStatus());
|
||||||
|
}
|
||||||
|
wrapper.orderByDesc(IntegrationDeviceMapping::getCreateTime);
|
||||||
|
return deviceMappingMapper.selectList(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IntegrationDeviceMapping selectDeviceMappingById(Long id) {
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<IntegrationDeviceMapping> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(IntegrationDeviceMapping::getId, id)
|
||||||
|
.eq(IntegrationDeviceMapping::getDelFlag, "0")
|
||||||
|
.last("limit 1");
|
||||||
|
return deviceMappingMapper.selectOne(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int insertDeviceMapping(IntegrationDeviceMapping mapping) {
|
||||||
|
checkDeviceMapping(mapping);
|
||||||
|
if (mapping.getStatus() == null) {
|
||||||
|
mapping.setStatus(1);
|
||||||
|
}
|
||||||
|
mapping.setIdentifierMappingJson(normalizeJson(mapping.getIdentifierMappingJson(), "指标映射"));
|
||||||
|
mapping.setDelFlag("0");
|
||||||
|
mapping.setCreateTime(new Date());
|
||||||
|
return deviceMappingMapper.insert(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int updateDeviceMapping(IntegrationDeviceMapping mapping) {
|
||||||
|
if (mapping == null || mapping.getId() == null) {
|
||||||
|
throw new ServiceException("映射ID不能为空");
|
||||||
|
}
|
||||||
|
checkDeviceMapping(mapping);
|
||||||
|
mapping.setIdentifierMappingJson(normalizeJson(mapping.getIdentifierMappingJson(), "指标映射"));
|
||||||
|
mapping.setUpdateTime(new Date());
|
||||||
|
return deviceMappingMapper.updateById(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int deleteDeviceMappingByIds(Long[] ids) {
|
||||||
|
int rows = 0;
|
||||||
|
if (ids == null) {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
for (Long id : ids) {
|
||||||
|
if (id == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
IntegrationDeviceMapping mapping = new IntegrationDeviceMapping();
|
||||||
|
mapping.setId(id);
|
||||||
|
mapping.setDelFlag("2");
|
||||||
|
mapping.setUpdateTime(new Date());
|
||||||
|
rows += deviceMappingMapper.updateById(mapping);
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<IntegrationLog> selectLogList(IntegrationLog query) {
|
||||||
|
LambdaQueryWrapper<IntegrationLog> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
if (query != null) {
|
||||||
|
wrapper.eq(query.getPlatformId() != null, IntegrationLog::getPlatformId, query.getPlatformId());
|
||||||
|
wrapper.like(StringUtils.isNotBlank(query.getPlatformCode()), IntegrationLog::getPlatformCode, query.getPlatformCode());
|
||||||
|
wrapper.like(StringUtils.isNotBlank(query.getFlowCode()), IntegrationLog::getFlowCode, query.getFlowCode());
|
||||||
|
wrapper.like(StringUtils.isNotBlank(query.getRequestId()), IntegrationLog::getRequestId, query.getRequestId());
|
||||||
|
wrapper.eq(query.getStatus() != null, IntegrationLog::getStatus, query.getStatus());
|
||||||
|
}
|
||||||
|
wrapper.orderByDesc(IntegrationLog::getStartTime);
|
||||||
|
return logMapper.selectList(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IntegrationSyncResult sync(IntegrationSyncRequest request) {
|
||||||
|
if (request == null || StringUtils.isBlank(request.getPlatformCode())) {
|
||||||
|
throw new ServiceException("平台编码不能为空");
|
||||||
|
}
|
||||||
|
String platformCode = request.getPlatformCode().toUpperCase();
|
||||||
|
IntegrationPlatform platform = selectPlatformByCode(platformCode);
|
||||||
|
if (platform == null || platform.getStatus() == null || platform.getStatus() != 1) {
|
||||||
|
throw new ServiceException("平台不存在或未启用:" + platformCode);
|
||||||
|
}
|
||||||
|
PlatformAdapter adapter = adapterMap.get(platformCode);
|
||||||
|
if (adapter == null) {
|
||||||
|
throw new ServiceException("未找到平台适配器:" + platformCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
String requestId = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
IntegrationLog log = new IntegrationLog();
|
||||||
|
log.setPlatformId(platform.getId());
|
||||||
|
log.setPlatformCode(platformCode);
|
||||||
|
log.setFlowCode(platformCode + "_SYNC");
|
||||||
|
log.setRequestId(requestId);
|
||||||
|
log.setStartTime(new Date());
|
||||||
|
log.setStatus(2);
|
||||||
|
log.setSuccessCount(0);
|
||||||
|
log.setFailCount(0);
|
||||||
|
logMapper.insert(log);
|
||||||
|
|
||||||
|
try {
|
||||||
|
IntegrationSyncResult result = adapter.sync(platform, request);
|
||||||
|
result.setRequestId(requestId);
|
||||||
|
log.setStatus(result.getFailCount() == 0 ? 1 : 0);
|
||||||
|
log.setSuccessCount(result.getSuccessCount());
|
||||||
|
log.setFailCount(result.getFailCount());
|
||||||
|
log.setDetailJson(com.alibaba.fastjson2.JSON.toJSONString(result.getDetails()));
|
||||||
|
log.setEndTime(new Date());
|
||||||
|
logMapper.updateById(log);
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.setStatus(0);
|
||||||
|
log.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 1000));
|
||||||
|
log.setEndTime(new Date());
|
||||||
|
logMapper.updateById(log);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IntegrationSyncResult selfTest(String platformCode) {
|
||||||
|
String code = StringUtils.isBlank(platformCode) ? "DCP" : platformCode.toUpperCase();
|
||||||
|
PlatformAdapter adapter = adapterMap.get(code);
|
||||||
|
if (adapter == null) {
|
||||||
|
throw new ServiceException("未找到平台适配器:" + code);
|
||||||
|
}
|
||||||
|
IntegrationPlatform platform = selectPlatformByCode(code);
|
||||||
|
if (platform == null) {
|
||||||
|
platform = new IntegrationPlatform();
|
||||||
|
platform.setPlatformCode(code);
|
||||||
|
platform.setPlatformName(code + " 自测平台");
|
||||||
|
platform.setBaseUrl("http://example.com");
|
||||||
|
platform.setStatus(1);
|
||||||
|
}
|
||||||
|
IntegrationSyncResult result = adapter.selfTest(platform);
|
||||||
|
result.setRequestId(UUID.randomUUID().toString().replace("-", ""));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPlatform(IntegrationPlatform platform) {
|
||||||
|
if (platform == null) {
|
||||||
|
throw new ServiceException("平台配置不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(platform.getPlatformCode())) {
|
||||||
|
throw new ServiceException("平台编码不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(platform.getPlatformName())) {
|
||||||
|
throw new ServiceException("平台名称不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(platform.getBaseUrl())) {
|
||||||
|
throw new ServiceException("平台基础地址不能为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkApi(IntegrationApi api) {
|
||||||
|
if (api == null) {
|
||||||
|
throw new ServiceException("接口配置不能为空");
|
||||||
|
}
|
||||||
|
if (api.getPlatformId() == null) {
|
||||||
|
throw new ServiceException("平台ID不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(api.getApiCode())) {
|
||||||
|
throw new ServiceException("接口编码不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(api.getMethod())) {
|
||||||
|
throw new ServiceException("请求方法不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(api.getUri())) {
|
||||||
|
throw new ServiceException("接口URI不能为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkDeviceMapping(IntegrationDeviceMapping mapping) {
|
||||||
|
if (mapping == null) {
|
||||||
|
throw new ServiceException("设备映射不能为空");
|
||||||
|
}
|
||||||
|
if (mapping.getPlatformId() == null) {
|
||||||
|
throw new ServiceException("平台ID不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(mapping.getExternalDeviceId())) {
|
||||||
|
throw new ServiceException("第三方设备ID不能为空");
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(mapping.getSerialNumber())) {
|
||||||
|
throw new ServiceException("本系统设备编号不能为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeJson(String json, String label) {
|
||||||
|
if (StringUtils.isBlank(json)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String normalized = json.trim();
|
||||||
|
try {
|
||||||
|
com.alibaba.fastjson2.JSON.parse(normalized);
|
||||||
|
return normalized;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ServiceException(label + "必须是合法JSON");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
package com.fastbee.iot.service.impl.integration.dcp;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.fastbee.common.core.redis.RedisCache;
|
||||||
|
import com.fastbee.common.exception.ServiceException;
|
||||||
|
import com.fastbee.common.utils.StringUtils;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationPlatform;
|
||||||
|
import com.fastbee.iot.service.integration.IDcpOpenApiService;
|
||||||
|
import com.fastbee.iot.service.integration.IIntegrationPlatformService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DCP兼容开放接口服务。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class DcpOpenApiServiceImpl implements IDcpOpenApiService {
|
||||||
|
|
||||||
|
private static final String PLATFORM_CODE = "DCP";
|
||||||
|
private static final String TOKEN_PREFIX = "iot:integration:open:dcp:token:";
|
||||||
|
private static final int TOKEN_SECONDS = 7200;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IIntegrationPlatformService integrationPlatformService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DcpPlatformAdapter dcpPlatformAdapter;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisCache redisCache;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject createToken(Map<String, String> form) {
|
||||||
|
IntegrationPlatform platform = requirePlatform();
|
||||||
|
JSONObject credential = StringUtils.isBlank(platform.getCredentialJson()) ? new JSONObject() : JSON.parseObject(platform.getCredentialJson());
|
||||||
|
String expectedClientId = credential.getString("openClientId");
|
||||||
|
String expectedUserName = credential.getString("openUserName");
|
||||||
|
String expectedPassword = credential.getString("openPassword");
|
||||||
|
if (StringUtils.isAnyBlank(expectedClientId, expectedUserName, expectedPassword)) {
|
||||||
|
throw new ServiceException("DCP开放接口账号未配置");
|
||||||
|
}
|
||||||
|
if (!"password".equals(form.get("grant_type"))) {
|
||||||
|
throw new ServiceException("grant_type不支持");
|
||||||
|
}
|
||||||
|
if (!expectedClientId.equals(form.get("client_id"))
|
||||||
|
|| !expectedUserName.equals(form.get("userName"))
|
||||||
|
|| !expectedPassword.equals(form.get("password"))) {
|
||||||
|
throw new ServiceException("DCP开放接口账号或密码错误");
|
||||||
|
}
|
||||||
|
String token = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
redisCache.setCacheObject(TOKEN_PREFIX + token, platform.getId(), TOKEN_SECONDS, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
JSONObject result = new JSONObject();
|
||||||
|
result.put("access_token", token);
|
||||||
|
result.put("expires_in", TOKEN_SECONDS);
|
||||||
|
result.put("token_type", "Bearer");
|
||||||
|
result.put("scope", StringUtils.defaultIfBlank(form.get("scope"), "DACP"));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject getUser(String authorization) {
|
||||||
|
return dcpPlatformAdapter.fetchUser(requireAuthorizedPlatform(authorization));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONArray getProjects(String authorization, String userId) {
|
||||||
|
if (StringUtils.isBlank(userId)) {
|
||||||
|
throw new ServiceException("userId不能为空");
|
||||||
|
}
|
||||||
|
return dcpPlatformAdapter.fetchProjects(requireAuthorizedPlatform(authorization), userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONArray getStressSensors(String authorization, String projectId) {
|
||||||
|
if (StringUtils.isBlank(projectId)) {
|
||||||
|
throw new ServiceException("projectId不能为空");
|
||||||
|
}
|
||||||
|
return dcpPlatformAdapter.fetchStressSensors(requireAuthorizedPlatform(authorization), projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JSONObject getDataset(String authorization, String startTime, String endTime, Boolean original, List<String> deviceIds) {
|
||||||
|
if (deviceIds == null || deviceIds.isEmpty()) {
|
||||||
|
throw new ServiceException("deviceId数组不能为空");
|
||||||
|
}
|
||||||
|
return dcpPlatformAdapter.fetchDataset(requireAuthorizedPlatform(authorization), startTime, endTime, original, deviceIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntegrationPlatform requireAuthorizedPlatform(String authorization) {
|
||||||
|
if (StringUtils.isBlank(authorization) || !authorization.startsWith("Bearer ")) {
|
||||||
|
throw new ServiceException("Authorization不能为空");
|
||||||
|
}
|
||||||
|
String token = authorization.substring("Bearer ".length());
|
||||||
|
Long platformId = redisCache.getCacheObject(TOKEN_PREFIX + token);
|
||||||
|
IntegrationPlatform platform = requirePlatform();
|
||||||
|
if (platformId == null || !platform.getId().equals(platformId)) {
|
||||||
|
throw new ServiceException("Authorization无效或已过期");
|
||||||
|
}
|
||||||
|
return platform;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntegrationPlatform requirePlatform() {
|
||||||
|
IntegrationPlatform platform = integrationPlatformService.selectPlatformByCode(PLATFORM_CODE);
|
||||||
|
if (platform == null || platform.getStatus() == null || platform.getStatus() != 1) {
|
||||||
|
throw new ServiceException("DCP平台未启用");
|
||||||
|
}
|
||||||
|
return platform;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,547 @@
|
|||||||
|
package com.fastbee.iot.service.impl.integration.dcp;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.fastbee.common.core.redis.RedisCache;
|
||||||
|
import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
|
||||||
|
import com.fastbee.common.core.thingsModel.ThingsModelValuesInput;
|
||||||
|
import com.fastbee.common.exception.ServiceException;
|
||||||
|
import com.fastbee.common.utils.StringUtils;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationApi;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationDeviceMapping;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationPlatform;
|
||||||
|
import com.fastbee.iot.mapper.integration.IntegrationApiMapper;
|
||||||
|
import com.fastbee.iot.mapper.integration.IntegrationDeviceMappingMapper;
|
||||||
|
import com.fastbee.iot.model.integration.IntegrationSyncRequest;
|
||||||
|
import com.fastbee.iot.model.integration.IntegrationSyncResult;
|
||||||
|
import com.fastbee.iot.model.integration.UnifiedTelemetry;
|
||||||
|
import com.fastbee.iot.service.IDeviceService;
|
||||||
|
import com.fastbee.iot.service.integration.PlatformAdapter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DCP 轴力计平台适配器。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class DcpPlatformAdapter implements PlatformAdapter {
|
||||||
|
|
||||||
|
private static final String PLATFORM_CODE = "DCP";
|
||||||
|
private static final String API_TOKEN = "dcp_token";
|
||||||
|
private static final String API_USER = "dcp_user_id";
|
||||||
|
private static final String API_PROJECT = "dcp_project_list";
|
||||||
|
private static final String API_DEVICE = "dcp_stress_sensor_list";
|
||||||
|
private static final String API_DATA = "dcp_stress_sensor_data";
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IntegrationApiMapper apiMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IntegrationDeviceMappingMapper deviceMappingMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private IDeviceService deviceService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisCache redisCache;
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String platformCode() {
|
||||||
|
return PLATFORM_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IntegrationSyncResult sync(IntegrationPlatform platform, IntegrationSyncRequest request) {
|
||||||
|
IntegrationSyncResult result = new IntegrationSyncResult();
|
||||||
|
result.setPlatformCode(PLATFORM_CODE);
|
||||||
|
result.setFlowCode("DCP_STRESS_SENSOR_SYNC");
|
||||||
|
result.setDryRun(Boolean.TRUE.equals(request.getDryRun()));
|
||||||
|
|
||||||
|
JSONObject credential = parseObject(platform.getCredentialJson());
|
||||||
|
String authorization = resolveAuthorization(platform, credential);
|
||||||
|
String userId = getUserId(platform, authorization);
|
||||||
|
String projectId = StringUtils.isNotBlank(request.getProjectId()) ? request.getProjectId() : getFirstProjectId(platform, authorization, userId);
|
||||||
|
if (StringUtils.isBlank(projectId)) {
|
||||||
|
throw new ServiceException("DCP项目ID为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<JSONObject> devices = getDevices(platform, authorization, projectId);
|
||||||
|
List<String> targetDeviceIds = selectTargetDeviceIds(devices, request.getExternalDeviceIds());
|
||||||
|
result.setDeviceCount(targetDeviceIds.size());
|
||||||
|
if (targetDeviceIds.isEmpty()) {
|
||||||
|
result.setMessage("未获取到可同步的DCP轴力计测点");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, List<JSONObject>> dataMap = getDataset(platform, authorization, request, targetDeviceIds);
|
||||||
|
Map<String, IntegrationDeviceMapping> mappings = loadMappings(platform.getId(), targetDeviceIds);
|
||||||
|
|
||||||
|
int dataCount = 0;
|
||||||
|
for (String externalDeviceId : targetDeviceIds) {
|
||||||
|
List<JSONObject> rows = dataMap.get(externalDeviceId);
|
||||||
|
if (rows == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
IntegrationDeviceMapping mapping = mappings.get(externalDeviceId);
|
||||||
|
for (JSONObject row : rows) {
|
||||||
|
dataCount++;
|
||||||
|
if (mapping == null) {
|
||||||
|
result.addFail("未配置设备映射 externalDeviceId=" + externalDeviceId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
UnifiedTelemetry telemetry = convertTelemetry(externalDeviceId, findDeviceName(devices, externalDeviceId), row);
|
||||||
|
if (!result.isDryRun()) {
|
||||||
|
writeThingsModel(mapping, telemetry);
|
||||||
|
}
|
||||||
|
result.addSuccess();
|
||||||
|
} catch (Exception e) {
|
||||||
|
result.addFail("写入失败 externalDeviceId=" + externalDeviceId + ", error=" + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.setDataCount(dataCount);
|
||||||
|
result.setMessage(result.isDryRun() ? "DCP同步演练完成" : "DCP同步完成");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IntegrationSyncResult selfTest(IntegrationPlatform platform) {
|
||||||
|
IntegrationSyncResult result = new IntegrationSyncResult();
|
||||||
|
result.setPlatformCode(PLATFORM_CODE);
|
||||||
|
result.setFlowCode("DCP_STRESS_SENSOR_SELF_TEST");
|
||||||
|
result.setDryRun(true);
|
||||||
|
|
||||||
|
JSONObject sample = JSON.parseObject("{\"isDeleted\":false,\"epoch\":\"2026-04-09T08:05:12+08:00\",\"deviceId\":\"3a1e1d88-c0ed-5f77-56f1-7704351b5a33\",\"e\":-323.26,\"fi\":0,\"ti\":0,\"span\":0,\"isOriginal\":true,\"temperature\":0,\"ct\":0,\"pt\":0}");
|
||||||
|
UnifiedTelemetry telemetry = convertTelemetry(sample.getString("deviceId"), "第二层支护结构DK138+931", sample);
|
||||||
|
if (!"3a1e1d88-c0ed-5f77-56f1-7704351b5a33".equals(telemetry.getExternalDeviceId())) {
|
||||||
|
result.addFail("设备ID解析失败");
|
||||||
|
}
|
||||||
|
if (!telemetry.getValues().containsKey("e") || !telemetry.getValues().containsKey("temperature")) {
|
||||||
|
result.addFail("轴力计数据字段解析失败");
|
||||||
|
}
|
||||||
|
if (telemetry.getReportTime() == null) {
|
||||||
|
result.addFail("数据时间解析失败");
|
||||||
|
}
|
||||||
|
if (result.getFailCount() == 0) {
|
||||||
|
result.addSuccess();
|
||||||
|
result.setDataCount(1);
|
||||||
|
result.setDeviceCount(1);
|
||||||
|
result.setMessage("DCP适配器自测通过");
|
||||||
|
} else {
|
||||||
|
result.setMessage("DCP适配器自测失败");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject fetchUser(IntegrationPlatform platform) {
|
||||||
|
String authorization = resolveAuthorization(platform, parseObject(platform.getCredentialJson()));
|
||||||
|
IntegrationApi api = getApi(platform.getId(), API_USER);
|
||||||
|
return exchangeJson(platform, api, authorization, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONArray fetchProjects(IntegrationPlatform platform, String userId) {
|
||||||
|
String authorization = resolveAuthorization(platform, parseObject(platform.getCredentialJson()));
|
||||||
|
IntegrationApi api = getApi(platform.getId(), API_PROJECT);
|
||||||
|
Map<String, Object> pathVars = new HashMap<>();
|
||||||
|
pathVars.put("userId", userId);
|
||||||
|
return exchangeArray(platform, api, authorization, pathVars, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONArray fetchStressSensors(IntegrationPlatform platform, String projectId) {
|
||||||
|
String authorization = resolveAuthorization(platform, parseObject(platform.getCredentialJson()));
|
||||||
|
IntegrationApi api = getApi(platform.getId(), API_DEVICE);
|
||||||
|
Map<String, Object> pathVars = new HashMap<>();
|
||||||
|
pathVars.put("projectId", projectId);
|
||||||
|
pathVars.put("projcetId", projectId);
|
||||||
|
return exchangeArray(platform, api, authorization, pathVars, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject fetchDataset(IntegrationPlatform platform, String startTime, String endTime, Boolean original, List<String> deviceIds) {
|
||||||
|
String authorization = resolveAuthorization(platform, parseObject(platform.getCredentialJson()));
|
||||||
|
IntegrationApi api = getApi(platform.getId(), API_DATA);
|
||||||
|
Map<String, Object> query = new HashMap<>();
|
||||||
|
query.put("startTime", defaultStartTime(startTime));
|
||||||
|
query.put("endTime", defaultEndTime(endTime));
|
||||||
|
query.put("isOriginal", String.valueOf(original == null || original));
|
||||||
|
return exchangeJson(platform, api, authorization, query, deviceIds == null ? Collections.emptyList() : deviceIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveAuthorization(IntegrationPlatform platform, JSONObject credential) {
|
||||||
|
String cacheKey = "iot:integration:dcp:token:" + platform.getId();
|
||||||
|
try {
|
||||||
|
String cached = redisCache.getCacheObject(cacheKey);
|
||||||
|
if (StringUtils.isNotBlank(cached)) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("读取DCP token缓存失败,将直接请求token:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
IntegrationApi api = getApi(platform.getId(), API_TOKEN);
|
||||||
|
String url = buildUrl(platform.getBaseUrl(), api.getUri());
|
||||||
|
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
||||||
|
body.add("grant_type", value(credential, "grant_type", "grantType", "password"));
|
||||||
|
body.add("scope", value(credential, "scope", null, "DACP"));
|
||||||
|
body.add("client_id", value(credential, "client_id", "clientId", "DACP_App"));
|
||||||
|
body.add("userName", value(credential, "userName", "username", null));
|
||||||
|
body.add("password", value(credential, "password", null, null));
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(body, headers), String.class);
|
||||||
|
JSONObject json = JSON.parseObject(response.getBody());
|
||||||
|
String token = json.getString("access_token");
|
||||||
|
String tokenType = json.getString("token_type");
|
||||||
|
if (StringUtils.isBlank(token)) {
|
||||||
|
throw new ServiceException("DCP token获取失败");
|
||||||
|
}
|
||||||
|
String authorization = (StringUtils.isBlank(tokenType) ? "Bearer" : tokenType) + " " + token;
|
||||||
|
int expires = json.getIntValue("expires_in");
|
||||||
|
try {
|
||||||
|
redisCache.setCacheObject(cacheKey, authorization, Math.max(expires - 60, 60), TimeUnit.SECONDS);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("写入DCP token缓存失败:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
return authorization;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUserId(IntegrationPlatform platform, String authorization) {
|
||||||
|
IntegrationApi api = getApi(platform.getId(), API_USER);
|
||||||
|
JSONObject json = exchangeJson(platform, api, authorization, null, null);
|
||||||
|
String id = json.getString("id");
|
||||||
|
if (StringUtils.isBlank(id)) {
|
||||||
|
throw new ServiceException("DCP用户ID获取失败");
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFirstProjectId(IntegrationPlatform platform, String authorization, String userId) {
|
||||||
|
IntegrationApi api = getApi(platform.getId(), API_PROJECT);
|
||||||
|
Map<String, Object> pathVars = new HashMap<>();
|
||||||
|
pathVars.put("userId", userId);
|
||||||
|
JSONArray array = exchangeArray(platform, api, authorization, pathVars, null);
|
||||||
|
if (array == null || array.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return array.getJSONObject(0).getString("id");
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<JSONObject> getDevices(IntegrationPlatform platform, String authorization, String projectId) {
|
||||||
|
IntegrationApi api = getApi(platform.getId(), API_DEVICE);
|
||||||
|
Map<String, Object> pathVars = new HashMap<>();
|
||||||
|
pathVars.put("projectId", projectId);
|
||||||
|
pathVars.put("projcetId", projectId);
|
||||||
|
JSONArray array = exchangeArray(platform, api, authorization, pathVars, null);
|
||||||
|
return toObjectList(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, List<JSONObject>> getDataset(IntegrationPlatform platform, String authorization, IntegrationSyncRequest request, List<String> deviceIds) {
|
||||||
|
IntegrationApi api = getApi(platform.getId(), API_DATA);
|
||||||
|
Map<String, Object> query = new HashMap<>();
|
||||||
|
query.put("startTime", defaultStartTime(request.getStartTime()));
|
||||||
|
query.put("endTime", defaultEndTime(request.getEndTime()));
|
||||||
|
query.put("isOriginal", String.valueOf(request.getOriginal() == null || request.getOriginal()));
|
||||||
|
JSONObject json = exchangeJson(platform, api, authorization, query, deviceIds);
|
||||||
|
Map<String, List<JSONObject>> result = new LinkedHashMap<>();
|
||||||
|
for (String key : json.keySet()) {
|
||||||
|
result.put(key, toObjectList(json.getJSONArray(key)));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject exchangeJson(IntegrationPlatform platform, IntegrationApi api, String authorization, Map<String, Object> varsOrQuery, Object body) {
|
||||||
|
String response = exchange(platform, api, authorization, varsOrQuery, body);
|
||||||
|
if (StringUtils.isBlank(response)) {
|
||||||
|
return new JSONObject();
|
||||||
|
}
|
||||||
|
return JSON.parseObject(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONArray exchangeArray(IntegrationPlatform platform, IntegrationApi api, String authorization, Map<String, Object> varsOrQuery, Object body) {
|
||||||
|
String response = exchange(platform, api, authorization, varsOrQuery, body);
|
||||||
|
if (StringUtils.isBlank(response)) {
|
||||||
|
return new JSONArray();
|
||||||
|
}
|
||||||
|
return JSON.parseArray(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String exchange(IntegrationPlatform platform, IntegrationApi api, String authorization, Map<String, Object> varsOrQuery, Object body) {
|
||||||
|
String uri = render(api.getUri(), varsOrQuery);
|
||||||
|
String url = buildUrl(platform.getBaseUrl(), uri);
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.set("Authorization", authorization);
|
||||||
|
if ("application/x-www-form-urlencoded".equalsIgnoreCase(api.getContentType())) {
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
} else {
|
||||||
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
}
|
||||||
|
if ("GET".equalsIgnoreCase(api.getMethod())) {
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(headers), String.class);
|
||||||
|
return response.getBody();
|
||||||
|
}
|
||||||
|
if (API_DATA.equals(api.getApiCode()) && varsOrQuery != null && !varsOrQuery.isEmpty()) {
|
||||||
|
url = appendQuery(url, varsOrQuery);
|
||||||
|
}
|
||||||
|
HttpMethod method = HttpMethod.resolve(api.getMethod());
|
||||||
|
if (method == null) {
|
||||||
|
throw new ServiceException("不支持的请求方法:" + api.getMethod());
|
||||||
|
}
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(url, method, new HttpEntity<>(body, headers), String.class);
|
||||||
|
return response.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeThingsModel(IntegrationDeviceMapping mapping, UnifiedTelemetry telemetry) {
|
||||||
|
Map<String, String> identifierMapping = parseStringMap(mapping.getIdentifierMappingJson());
|
||||||
|
if (identifierMapping.isEmpty()) {
|
||||||
|
identifierMapping.put("e", "axialForce");
|
||||||
|
identifierMapping.put("temperature", "temperature");
|
||||||
|
identifierMapping.put("ct", "ct");
|
||||||
|
identifierMapping.put("pt", "pt");
|
||||||
|
}
|
||||||
|
List<ThingsModelSimpleItem> items = new ArrayList<>();
|
||||||
|
for (Map.Entry<String, Object> entry : telemetry.getValues().entrySet()) {
|
||||||
|
String identifier = identifierMapping.get(entry.getKey());
|
||||||
|
if (StringUtils.isBlank(identifier) || entry.getValue() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ThingsModelSimpleItem item = new ThingsModelSimpleItem();
|
||||||
|
item.setId(identifier);
|
||||||
|
item.setValue(String.valueOf(entry.getValue()));
|
||||||
|
item.setTs(telemetry.getReportTime());
|
||||||
|
item.setRemark("integration:" + PLATFORM_CODE);
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ThingsModelValuesInput input = new ThingsModelValuesInput();
|
||||||
|
input.setProductId(mapping.getProductId());
|
||||||
|
input.setDeviceNumber(mapping.getSerialNumber());
|
||||||
|
input.setThingsModelValueRemarkItem(items);
|
||||||
|
deviceService.reportDeviceThingsModelValue(input, 1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UnifiedTelemetry convertTelemetry(String externalDeviceId, String externalDeviceName, JSONObject row) {
|
||||||
|
UnifiedTelemetry telemetry = new UnifiedTelemetry();
|
||||||
|
telemetry.setPlatformCode(PLATFORM_CODE);
|
||||||
|
telemetry.setExternalDeviceId(externalDeviceId);
|
||||||
|
telemetry.setExternalDeviceName(externalDeviceName);
|
||||||
|
telemetry.setRaw(row);
|
||||||
|
telemetry.setReportTime(parseDate(row.getString("epoch")));
|
||||||
|
putIfPresent(telemetry.getValues(), "e", row.get("e"));
|
||||||
|
putIfPresent(telemetry.getValues(), "temperature", row.get("temperature"));
|
||||||
|
putIfPresent(telemetry.getValues(), "ct", row.get("ct"));
|
||||||
|
putIfPresent(telemetry.getValues(), "pt", row.get("pt"));
|
||||||
|
putIfPresent(telemetry.getValues(), "fi", row.get("fi"));
|
||||||
|
putIfPresent(telemetry.getValues(), "ti", row.get("ti"));
|
||||||
|
putIfPresent(telemetry.getValues(), "span", row.get("span"));
|
||||||
|
return telemetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, IntegrationDeviceMapping> loadMappings(Long platformId, List<String> externalDeviceIds) {
|
||||||
|
if (externalDeviceIds.isEmpty()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<IntegrationDeviceMapping> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(IntegrationDeviceMapping::getPlatformId, platformId)
|
||||||
|
.eq(IntegrationDeviceMapping::getStatus, 1)
|
||||||
|
.eq(IntegrationDeviceMapping::getDelFlag, "0")
|
||||||
|
.in(IntegrationDeviceMapping::getExternalDeviceId, externalDeviceIds);
|
||||||
|
List<IntegrationDeviceMapping> list = deviceMappingMapper.selectList(wrapper);
|
||||||
|
Map<String, IntegrationDeviceMapping> result = new HashMap<>();
|
||||||
|
for (IntegrationDeviceMapping item : list) {
|
||||||
|
result.put(item.getExternalDeviceId(), item);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntegrationApi getApi(Long platformId, String apiCode) {
|
||||||
|
LambdaQueryWrapper<IntegrationApi> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(IntegrationApi::getPlatformId, platformId)
|
||||||
|
.eq(IntegrationApi::getApiCode, apiCode)
|
||||||
|
.eq(IntegrationApi::getDelFlag, "0")
|
||||||
|
.last("limit 1");
|
||||||
|
IntegrationApi api = apiMapper.selectOne(wrapper);
|
||||||
|
return api == null ? defaultApi(apiCode) : api;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntegrationApi defaultApi(String apiCode) {
|
||||||
|
IntegrationApi api = new IntegrationApi();
|
||||||
|
api.setApiCode(apiCode);
|
||||||
|
api.setStatus(1);
|
||||||
|
api.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
if (API_TOKEN.equals(apiCode)) {
|
||||||
|
api.setMethod("POST");
|
||||||
|
api.setUri("/connect/token");
|
||||||
|
api.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||||
|
} else if (API_USER.equals(apiCode)) {
|
||||||
|
api.setMethod("GET");
|
||||||
|
api.setUri("/api/app/user/user-id");
|
||||||
|
} else if (API_PROJECT.equals(apiCode)) {
|
||||||
|
api.setMethod("GET");
|
||||||
|
api.setUri("/api/app/project/by-user-id/{userId}");
|
||||||
|
} else if (API_DEVICE.equals(apiCode)) {
|
||||||
|
api.setMethod("POST");
|
||||||
|
api.setUri("/api/app/stress-sensor/by-project-id/{projectId}");
|
||||||
|
} else if (API_DATA.equals(apiCode)) {
|
||||||
|
api.setMethod("POST");
|
||||||
|
api.setUri("/api/app/inclino-data/load-dataset-by-device-ids");
|
||||||
|
} else {
|
||||||
|
throw new ServiceException("未知DCP接口编码:" + apiCode);
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> selectTargetDeviceIds(List<JSONObject> devices, List<String> requested) {
|
||||||
|
if (requested != null && !requested.isEmpty()) {
|
||||||
|
return requested;
|
||||||
|
}
|
||||||
|
List<String> ids = new ArrayList<>();
|
||||||
|
for (JSONObject device : devices) {
|
||||||
|
String id = device.getString("id");
|
||||||
|
if (StringUtils.isNotBlank(id)) {
|
||||||
|
ids.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String findDeviceName(List<JSONObject> devices, String id) {
|
||||||
|
for (JSONObject device : devices) {
|
||||||
|
if (id.equals(device.getString("id"))) {
|
||||||
|
return device.getString("name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<JSONObject> toObjectList(JSONArray array) {
|
||||||
|
List<JSONObject> result = new ArrayList<>();
|
||||||
|
if (array == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < array.size(); i++) {
|
||||||
|
result.add(array.getJSONObject(i));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject parseObject(String json) {
|
||||||
|
return StringUtils.isBlank(json) ? new JSONObject() : JSON.parseObject(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> parseStringMap(String json) {
|
||||||
|
Map<String, String> result = new LinkedHashMap<>();
|
||||||
|
if (StringUtils.isBlank(json)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
JSONObject object = JSON.parseObject(json);
|
||||||
|
for (String key : object.keySet()) {
|
||||||
|
result.put(key, object.getString(key));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String value(JSONObject object, String key, String alias, String defaultValue) {
|
||||||
|
String value = object.getString(key);
|
||||||
|
if (StringUtils.isBlank(value) && StringUtils.isNotBlank(alias)) {
|
||||||
|
value = object.getString(alias);
|
||||||
|
}
|
||||||
|
return StringUtils.isBlank(value) ? defaultValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Date parseDate(String text) {
|
||||||
|
if (StringUtils.isBlank(text)) {
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
return Date.from(OffsetDateTime.parse(text).toInstant());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putIfPresent(Map<String, Object> values, String key, Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value instanceof BigDecimal) {
|
||||||
|
values.put(key, ((BigDecimal) value).stripTrailingZeros().toPlainString());
|
||||||
|
} else {
|
||||||
|
values.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultStartTime(String value) {
|
||||||
|
if (StringUtils.isNotBlank(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return OffsetDateTime.now().minusDays(1).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultEndTime(String value) {
|
||||||
|
if (StringUtils.isNotBlank(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return OffsetDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildUrl(String baseUrl, String uri) {
|
||||||
|
String base = baseUrl == null ? "" : baseUrl;
|
||||||
|
while (base.endsWith("/")) {
|
||||||
|
base = base.substring(0, base.length() - 1);
|
||||||
|
}
|
||||||
|
String path = uri.startsWith("/") ? uri : "/" + uri;
|
||||||
|
return base + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String render(String template, Map<String, Object> vars) {
|
||||||
|
if (template == null || vars == null) {
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
String result = template;
|
||||||
|
for (Map.Entry<String, Object> entry : vars.entrySet()) {
|
||||||
|
String value = String.valueOf(entry.getValue());
|
||||||
|
result = result.replace("{" + entry.getKey() + "}", value);
|
||||||
|
result = result.replace("${" + entry.getKey() + "}", value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String appendQuery(String url, Map<String, Object> query) {
|
||||||
|
StringBuilder builder = new StringBuilder(url);
|
||||||
|
builder.append(url.contains("?") ? "&" : "?");
|
||||||
|
boolean first = true;
|
||||||
|
for (Map.Entry<String, Object> entry : query.entrySet()) {
|
||||||
|
if (!first) {
|
||||||
|
builder.append("&");
|
||||||
|
}
|
||||||
|
builder.append(encode(entry.getKey())).append("=").append(encode(String.valueOf(entry.getValue())));
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encode(String value) {
|
||||||
|
try {
|
||||||
|
return URLEncoder.encode(value, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new ServiceException("URL参数编码失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package com.fastbee.iot.service.integration;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface IDcpOpenApiService {
|
||||||
|
|
||||||
|
JSONObject createToken(Map<String, String> form);
|
||||||
|
|
||||||
|
JSONObject getUser(String authorization);
|
||||||
|
|
||||||
|
JSONArray getProjects(String authorization, String userId);
|
||||||
|
|
||||||
|
JSONArray getStressSensors(String authorization, String projectId);
|
||||||
|
|
||||||
|
JSONObject getDataset(String authorization, String startTime, String endTime, Boolean original, List<String> deviceIds);
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.fastbee.iot.service.integration;
|
||||||
|
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationPlatform;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationApi;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationDeviceMapping;
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationLog;
|
||||||
|
import com.fastbee.iot.model.integration.IntegrationSyncRequest;
|
||||||
|
import com.fastbee.iot.model.integration.IntegrationSyncResult;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IIntegrationPlatformService {
|
||||||
|
|
||||||
|
IntegrationPlatform selectPlatformByCode(String platformCode);
|
||||||
|
|
||||||
|
IntegrationPlatform selectPlatformById(Long id);
|
||||||
|
|
||||||
|
List<IntegrationPlatform> selectPlatformList(IntegrationPlatform query);
|
||||||
|
|
||||||
|
int insertPlatform(IntegrationPlatform platform);
|
||||||
|
|
||||||
|
int updatePlatform(IntegrationPlatform platform);
|
||||||
|
|
||||||
|
int deletePlatformByIds(Long[] ids);
|
||||||
|
|
||||||
|
List<IntegrationApi> selectApiList(IntegrationApi query);
|
||||||
|
|
||||||
|
IntegrationApi selectApiById(Long id);
|
||||||
|
|
||||||
|
int insertApi(IntegrationApi api);
|
||||||
|
|
||||||
|
int updateApi(IntegrationApi api);
|
||||||
|
|
||||||
|
int deleteApiByIds(Long[] ids);
|
||||||
|
|
||||||
|
List<IntegrationDeviceMapping> selectDeviceMappingList(IntegrationDeviceMapping query);
|
||||||
|
|
||||||
|
IntegrationDeviceMapping selectDeviceMappingById(Long id);
|
||||||
|
|
||||||
|
int insertDeviceMapping(IntegrationDeviceMapping mapping);
|
||||||
|
|
||||||
|
int updateDeviceMapping(IntegrationDeviceMapping mapping);
|
||||||
|
|
||||||
|
int deleteDeviceMappingByIds(Long[] ids);
|
||||||
|
|
||||||
|
List<IntegrationLog> selectLogList(IntegrationLog query);
|
||||||
|
|
||||||
|
IntegrationSyncResult sync(IntegrationSyncRequest request);
|
||||||
|
|
||||||
|
IntegrationSyncResult selfTest(String platformCode);
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package com.fastbee.iot.service.integration;
|
||||||
|
|
||||||
|
import com.fastbee.iot.domain.integration.IntegrationPlatform;
|
||||||
|
import com.fastbee.iot.model.integration.IntegrationSyncRequest;
|
||||||
|
import com.fastbee.iot.model.integration.IntegrationSyncResult;
|
||||||
|
|
||||||
|
public interface PlatformAdapter {
|
||||||
|
|
||||||
|
String platformCode();
|
||||||
|
|
||||||
|
IntegrationSyncResult sync(IntegrationPlatform platform, IntegrationSyncRequest request);
|
||||||
|
|
||||||
|
IntegrationSyncResult selfTest(IntegrationPlatform platform);
|
||||||
|
}
|
||||||
170
sql/iot_integration_platform.sql
Normal file
170
sql/iot_integration_platform.sql
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
-- 第三方平台接入框架
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `iot_integration_platform` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
|
`platform_code` varchar(64) NOT NULL COMMENT '平台编码',
|
||||||
|
`platform_name` varchar(128) NOT NULL COMMENT '平台名称',
|
||||||
|
`base_url` varchar(512) NOT NULL COMMENT '基础地址',
|
||||||
|
`auth_type` varchar(64) DEFAULT NULL COMMENT '认证类型',
|
||||||
|
`credential_json` json DEFAULT NULL COMMENT '凭证配置',
|
||||||
|
`status` tinyint DEFAULT 1 COMMENT '状态:0=停用 1=启用',
|
||||||
|
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志',
|
||||||
|
`create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
|
||||||
|
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
|
||||||
|
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_integration_platform_code` (`platform_code`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方平台接入配置';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `iot_integration_api` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
|
`platform_id` bigint NOT NULL COMMENT '平台ID',
|
||||||
|
`api_code` varchar(64) NOT NULL COMMENT '接口编码',
|
||||||
|
`api_name` varchar(128) NOT NULL COMMENT '接口名称',
|
||||||
|
`method` varchar(16) NOT NULL COMMENT '请求方法',
|
||||||
|
`uri` varchar(512) NOT NULL COMMENT '接口URI',
|
||||||
|
`content_type` varchar(128) DEFAULT 'application/json' COMMENT 'Content-Type',
|
||||||
|
`headers_json` json DEFAULT NULL COMMENT '请求头模板',
|
||||||
|
`query_json` json DEFAULT NULL COMMENT 'Query模板',
|
||||||
|
`body_json` json DEFAULT NULL COMMENT 'Body模板',
|
||||||
|
`response_path` varchar(256) DEFAULT NULL COMMENT '返回路径',
|
||||||
|
`depends_on` varchar(64) DEFAULT NULL COMMENT '依赖接口编码',
|
||||||
|
`sort_order` int DEFAULT 0 COMMENT '排序',
|
||||||
|
`status` tinyint DEFAULT 1 COMMENT '状态:0=停用 1=启用',
|
||||||
|
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志',
|
||||||
|
`create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
|
||||||
|
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
|
||||||
|
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_integration_api_code` (`platform_id`, `api_code`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方平台接口定义';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `iot_integration_device_mapping` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
|
`platform_id` bigint NOT NULL COMMENT '平台ID',
|
||||||
|
`external_device_id` varchar(128) NOT NULL COMMENT '第三方设备ID',
|
||||||
|
`external_device_name` varchar(128) DEFAULT NULL COMMENT '第三方设备名称',
|
||||||
|
`product_id` bigint NOT NULL COMMENT '本系统产品ID',
|
||||||
|
`serial_number` varchar(64) NOT NULL COMMENT '本系统设备编号',
|
||||||
|
`identifier_mapping_json` json DEFAULT NULL COMMENT '指标映射 sourceField -> identifier',
|
||||||
|
`status` tinyint DEFAULT 1 COMMENT '状态:0=停用 1=启用',
|
||||||
|
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志',
|
||||||
|
`create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
|
||||||
|
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
|
||||||
|
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_integration_device_mapping` (`platform_id`, `external_device_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方设备映射';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `iot_integration_log` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||||
|
`platform_id` bigint DEFAULT NULL COMMENT '平台ID',
|
||||||
|
`platform_code` varchar(64) NOT NULL COMMENT '平台编码',
|
||||||
|
`flow_code` varchar(64) NOT NULL COMMENT '流程编码',
|
||||||
|
`request_id` varchar(64) NOT NULL COMMENT '请求ID',
|
||||||
|
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
|
||||||
|
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
|
||||||
|
`status` tinyint DEFAULT 2 COMMENT '状态:0=失败 1=成功 2=执行中',
|
||||||
|
`success_count` int DEFAULT 0 COMMENT '成功数量',
|
||||||
|
`fail_count` int DEFAULT 0 COMMENT '失败数量',
|
||||||
|
`error_msg` varchar(1000) DEFAULT NULL COMMENT '错误信息',
|
||||||
|
`detail_json` json DEFAULT NULL COMMENT '详情',
|
||||||
|
`create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
|
||||||
|
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
`update_by` varchar(64) DEFAULT NULL COMMENT '更新者',
|
||||||
|
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_integration_log_request` (`request_id`),
|
||||||
|
KEY `idx_integration_log_platform` (`platform_code`, `start_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方平台接入执行日志';
|
||||||
|
|
||||||
|
INSERT INTO `iot_integration_platform`
|
||||||
|
(`platform_code`, `platform_name`, `base_url`, `auth_type`, `credential_json`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||||
|
VALUES
|
||||||
|
('DCP', 'DCP轴力计平台', 'http://47.108.182.132:8200', 'PASSWORD_TOKEN',
|
||||||
|
JSON_OBJECT('grant_type','password','scope','DACP','client_id','DACP_App','userName','ceshi','password','12345678',
|
||||||
|
'openClientId','DCP_Open_App','openUserName','open_user','openPassword','change-me'),
|
||||||
|
0, '0', 'admin', NOW())
|
||||||
|
ON DUPLICATE KEY UPDATE `platform_name` = VALUES(`platform_name`);
|
||||||
|
|
||||||
|
-- 启用前请按现场账号修改 credential_json 中的 DCP 上游账号和 openClientId/openUserName/openPassword,并将 status 改为 1。
|
||||||
|
-- 若需要第三方应用匿名触发同步,请在 credential_json 中增加 openApiKey:
|
||||||
|
-- UPDATE iot_integration_platform SET credential_json = JSON_SET(credential_json, '$.openApiKey', 'replace-with-random-secret') WHERE platform_code = 'DCP';
|
||||||
|
--
|
||||||
|
-- 设备映射示例,需替换 product_id、serial_number、external_device_id:
|
||||||
|
-- INSERT INTO iot_integration_device_mapping
|
||||||
|
-- (platform_id, external_device_id, external_device_name, product_id, serial_number, identifier_mapping_json, status, del_flag, create_by, create_time)
|
||||||
|
-- SELECT id, 'DCP_DEVICE_ID', 'DCP测点名称', 1, 'FASTBEE_SERIAL_NUMBER',
|
||||||
|
-- JSON_OBJECT('e','axialForce','temperature','temperature','ct','ct','pt','pt'),
|
||||||
|
-- 1, '0', 'admin', NOW()
|
||||||
|
-- FROM iot_integration_platform WHERE platform_code = 'DCP';
|
||||||
|
|
||||||
|
INSERT INTO `iot_integration_api`
|
||||||
|
(`platform_id`, `api_code`, `api_name`, `method`, `uri`, `content_type`, `depends_on`, `sort_order`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||||
|
SELECT p.id, 'dcp_token', '获取访问凭证', 'POST', '/connect/token', 'application/x-www-form-urlencoded', NULL, 1, 1, '0', 'admin', NOW()
|
||||||
|
FROM `iot_integration_platform` p WHERE p.platform_code = 'DCP'
|
||||||
|
ON DUPLICATE KEY UPDATE `uri` = VALUES(`uri`);
|
||||||
|
|
||||||
|
INSERT INTO `iot_integration_api`
|
||||||
|
(`platform_id`, `api_code`, `api_name`, `method`, `uri`, `content_type`, `depends_on`, `sort_order`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||||
|
SELECT p.id, 'dcp_user_id', '获取用户信息', 'GET', '/api/app/user/user-id', 'application/json', 'dcp_token', 2, 1, '0', 'admin', NOW()
|
||||||
|
FROM `iot_integration_platform` p WHERE p.platform_code = 'DCP'
|
||||||
|
ON DUPLICATE KEY UPDATE `uri` = VALUES(`uri`);
|
||||||
|
|
||||||
|
INSERT INTO `iot_integration_api`
|
||||||
|
(`platform_id`, `api_code`, `api_name`, `method`, `uri`, `content_type`, `depends_on`, `sort_order`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||||
|
SELECT p.id, 'dcp_project_list', '获取项目信息', 'GET', '/api/app/project/by-user-id/{userId}', 'application/json', 'dcp_user_id', 3, 1, '0', 'admin', NOW()
|
||||||
|
FROM `iot_integration_platform` p WHERE p.platform_code = 'DCP'
|
||||||
|
ON DUPLICATE KEY UPDATE `uri` = VALUES(`uri`);
|
||||||
|
|
||||||
|
INSERT INTO `iot_integration_api`
|
||||||
|
(`platform_id`, `api_code`, `api_name`, `method`, `uri`, `content_type`, `depends_on`, `sort_order`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||||
|
SELECT p.id, 'dcp_stress_sensor_list', '获取项目轴力计测点', 'POST', '/api/app/stress-sensor/by-project-id/{projectId}', 'application/json', 'dcp_project_list', 4, 1, '0', 'admin', NOW()
|
||||||
|
FROM `iot_integration_platform` p WHERE p.platform_code = 'DCP'
|
||||||
|
ON DUPLICATE KEY UPDATE `uri` = VALUES(`uri`);
|
||||||
|
|
||||||
|
INSERT INTO `iot_integration_api`
|
||||||
|
(`platform_id`, `api_code`, `api_name`, `method`, `uri`, `content_type`, `depends_on`, `sort_order`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||||
|
SELECT p.id, 'dcp_stress_sensor_data', '批量获取轴力计数据', 'POST', '/api/app/inclino-data/load-dataset-by-device-ids', 'application/json', 'dcp_stress_sensor_list', 5, 1, '0', 'admin', NOW()
|
||||||
|
FROM `iot_integration_platform` p WHERE p.platform_code = 'DCP'
|
||||||
|
ON DUPLICATE KEY UPDATE `uri` = VALUES(`uri`);
|
||||||
|
|
||||||
|
-- 菜单权限。若目标库已有相同 menu_id,请按现场菜单ID调整后执行。
|
||||||
|
INSERT INTO `sys_menu`
|
||||||
|
(`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES
|
||||||
|
(3440, '平台接入', 3051, 9, 'integration', 'iot/integration/index', NULL, 1, 0, 'C', '0', '0', 'iot:integration:list', 'api', 'admin', NOW(), '', NULL, '第三方平台接入菜单')
|
||||||
|
ON DUPLICATE KEY UPDATE `menu_name` = VALUES(`menu_name`), `component` = VALUES(`component`), `visible` = VALUES(`visible`), `perms` = VALUES(`perms`);
|
||||||
|
|
||||||
|
INSERT INTO `sys_menu`
|
||||||
|
(`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES
|
||||||
|
(3441, '平台接入查询', 3440, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:integration:list', '#', 'admin', NOW(), '', NULL, '')
|
||||||
|
ON DUPLICATE KEY UPDATE `perms` = VALUES(`perms`);
|
||||||
|
|
||||||
|
INSERT INTO `sys_menu`
|
||||||
|
(`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES
|
||||||
|
(3442, '平台数据同步', 3440, 2, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:integration:sync', '#', 'admin', NOW(), '', NULL, '')
|
||||||
|
ON DUPLICATE KEY UPDATE `perms` = VALUES(`perms`);
|
||||||
|
|
||||||
|
INSERT INTO `sys_menu`
|
||||||
|
(`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES
|
||||||
|
(3443, '平台接入新增', 3440, 3, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:integration:add', '#', 'admin', NOW(), '', NULL, '')
|
||||||
|
ON DUPLICATE KEY UPDATE `perms` = VALUES(`perms`);
|
||||||
|
|
||||||
|
INSERT INTO `sys_menu`
|
||||||
|
(`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES
|
||||||
|
(3444, '平台接入修改', 3440, 4, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:integration:edit', '#', 'admin', NOW(), '', NULL, '')
|
||||||
|
ON DUPLICATE KEY UPDATE `perms` = VALUES(`perms`);
|
||||||
|
|
||||||
|
INSERT INTO `sys_menu`
|
||||||
|
(`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
|
||||||
|
VALUES
|
||||||
|
(3445, '平台接入删除', 3440, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:integration:remove', '#', 'admin', NOW(), '', NULL, '')
|
||||||
|
ON DUPLICATE KEY UPDATE `perms` = VALUES(`perms`);
|
||||||
138
yack-web/src/api/iot/integration.js
Normal file
138
yack-web/src/api/iot/integration.js
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export function listPlatform(query) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/platform/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPlatform(id) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/platform/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addPlatform(data) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/platform',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updatePlatform(data) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/platform',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delPlatform(id) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/platform/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listIntegrationApi(query) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/api/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIntegrationApi(id) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/api/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addIntegrationApi(data) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/api',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateIntegrationApi(data) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/api',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delIntegrationApi(id) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/api/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listDeviceMapping(query) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/device-mapping/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDeviceMapping(id) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/device-mapping/' + id,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addDeviceMapping(data) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/device-mapping',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDeviceMapping(data) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/device-mapping',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function delDeviceMapping(id) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/device-mapping/' + id,
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listIntegrationLog(query) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/log/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function syncIntegration(data) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/sync',
|
||||||
|
method: 'post',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selfTestIntegration(platformCode) {
|
||||||
|
return request({
|
||||||
|
url: '/iot/integration/self-test/' + platformCode,
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
808
yack-web/src/views/iot/integration/index.vue
Normal file
808
yack-web/src/views/iot/integration/index.vue
Normal file
@ -0,0 +1,808 @@
|
|||||||
|
<template>
|
||||||
|
<div class="app-container integration-page">
|
||||||
|
<el-form ref="queryForm" :model="queryParams" :inline="true" label-width="80px">
|
||||||
|
<el-form-item label="平台编码" prop="platformCode">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.platformCode"
|
||||||
|
placeholder="请输入平台编码"
|
||||||
|
clearable
|
||||||
|
size="small"
|
||||||
|
@keyup.enter.native="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="平台名称" prop="platformName">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.platformName"
|
||||||
|
placeholder="请输入平台名称"
|
||||||
|
clearable
|
||||||
|
size="small"
|
||||||
|
@keyup.enter.native="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-select v-model="queryParams.status" placeholder="全部" clearable size="small">
|
||||||
|
<el-option label="启用" :value="1" />
|
||||||
|
<el-option label="停用" :value="0" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-row :gutter="10" class="mb8">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="primary" plain icon="el-icon-plus" size="mini" v-hasPermi="['iot:integration:add']" @click="handleAddPlatform">新增平台</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="single" v-hasPermi="['iot:integration:edit']" @click="handleUpdatePlatform">修改平台</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="multiple" v-hasPermi="['iot:integration:remove']" @click="handleDeletePlatform">删除平台</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="warning" plain icon="el-icon-video-play" size="mini" :disabled="!currentPlatform" v-hasPermi="['iot:integration:sync']" @click="openSyncDialog(false)">同步</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button plain icon="el-icon-check" size="mini" :disabled="!currentPlatform" v-hasPermi="['iot:integration:sync']" @click="handleSelfTest">自测</el-button>
|
||||||
|
</el-col>
|
||||||
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="platformList"
|
||||||
|
highlight-current-row
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
@current-change="handleCurrentPlatformChange"
|
||||||
|
@row-click="selectPlatform"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
|
<el-table-column label="平台编码" align="center" prop="platformCode" width="120" />
|
||||||
|
<el-table-column label="平台名称" align="center" prop="platformName" min-width="160" />
|
||||||
|
<el-table-column label="基础地址" align="center" prop="baseUrl" min-width="240" show-overflow-tooltip />
|
||||||
|
<el-table-column label="认证类型" align="center" prop="authType" width="140" />
|
||||||
|
<el-table-column label="状态" align="center" width="90">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'">{{ scope.row.status === 1 ? '启用' : '停用' }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="创建时间" align="center" prop="createTime" width="160" />
|
||||||
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="210">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button size="mini" type="text" icon="el-icon-edit" v-hasPermi="['iot:integration:edit']" @click.stop="handleUpdatePlatform(scope.row)">修改</el-button>
|
||||||
|
<el-button size="mini" type="text" icon="el-icon-refresh" v-hasPermi="['iot:integration:sync']" @click.stop="openSyncDialog(false, scope.row)">同步</el-button>
|
||||||
|
<el-button size="mini" type="text" icon="el-icon-delete" v-hasPermi="['iot:integration:remove']" @click.stop="handleDeletePlatform(scope.row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<pagination
|
||||||
|
v-show="total > 0"
|
||||||
|
:total="total"
|
||||||
|
:page.sync="queryParams.pageNum"
|
||||||
|
:limit.sync="queryParams.pageSize"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-if="currentPlatform" class="integration-detail">
|
||||||
|
<div class="detail-header">
|
||||||
|
<span class="detail-title">{{ currentPlatform.platformName }}</span>
|
||||||
|
<span class="detail-subtitle">{{ currentPlatform.platformCode }}</span>
|
||||||
|
</div>
|
||||||
|
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||||
|
<el-tab-pane label="接口定义" name="api">
|
||||||
|
<el-row :gutter="10" class="mb8">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="primary" plain icon="el-icon-plus" size="mini" v-hasPermi="['iot:integration:add']" @click="handleAddApi">新增接口</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="apiSingle" v-hasPermi="['iot:integration:edit']" @click="handleUpdateApi">修改接口</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="apiMultiple" v-hasPermi="['iot:integration:remove']" @click="handleDeleteApi">删除接口</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-table v-loading="apiLoading" :data="apiList" @selection-change="handleApiSelectionChange">
|
||||||
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
|
<el-table-column label="编码" align="center" prop="apiCode" min-width="160" />
|
||||||
|
<el-table-column label="名称" align="center" prop="apiName" min-width="160" />
|
||||||
|
<el-table-column label="方法" align="center" prop="method" width="90" />
|
||||||
|
<el-table-column label="URI" align="center" prop="uri" min-width="260" show-overflow-tooltip />
|
||||||
|
<el-table-column label="依赖" align="center" prop="dependsOn" width="150" />
|
||||||
|
<el-table-column label="排序" align="center" prop="sortOrder" width="80" />
|
||||||
|
<el-table-column label="状态" align="center" width="90">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'">{{ scope.row.status === 1 ? '启用' : '停用' }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" width="150">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button size="mini" type="text" icon="el-icon-edit" v-hasPermi="['iot:integration:edit']" @click="handleUpdateApi(scope.row)">修改</el-button>
|
||||||
|
<el-button size="mini" type="text" icon="el-icon-delete" v-hasPermi="['iot:integration:remove']" @click="handleDeleteApi(scope.row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane label="设备映射" name="mapping">
|
||||||
|
<el-row :gutter="10" class="mb8">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="primary" plain icon="el-icon-plus" size="mini" v-hasPermi="['iot:integration:add']" @click="handleAddMapping">新增映射</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="success" plain icon="el-icon-edit" size="mini" :disabled="mappingSingle" v-hasPermi="['iot:integration:edit']" @click="handleUpdateMapping">修改映射</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button type="danger" plain icon="el-icon-delete" size="mini" :disabled="mappingMultiple" v-hasPermi="['iot:integration:remove']" @click="handleDeleteMapping">删除映射</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-table v-loading="mappingLoading" :data="mappingList" @selection-change="handleMappingSelectionChange">
|
||||||
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
|
<el-table-column label="第三方设备ID" align="center" prop="externalDeviceId" min-width="170" show-overflow-tooltip />
|
||||||
|
<el-table-column label="第三方设备名称" align="center" prop="externalDeviceName" min-width="160" show-overflow-tooltip />
|
||||||
|
<el-table-column label="产品ID" align="center" prop="productId" width="100" />
|
||||||
|
<el-table-column label="设备编号" align="center" prop="serialNumber" min-width="160" show-overflow-tooltip />
|
||||||
|
<el-table-column label="指标映射" align="center" prop="identifierMappingJson" min-width="220" show-overflow-tooltip />
|
||||||
|
<el-table-column label="状态" align="center" width="90">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag :type="scope.row.status === 1 ? 'success' : 'info'">{{ scope.row.status === 1 ? '启用' : '停用' }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" width="150">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button size="mini" type="text" icon="el-icon-edit" v-hasPermi="['iot:integration:edit']" @click="handleUpdateMapping(scope.row)">修改</el-button>
|
||||||
|
<el-button size="mini" type="text" icon="el-icon-delete" v-hasPermi="['iot:integration:remove']" @click="handleDeleteMapping(scope.row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane label="同步日志" name="log">
|
||||||
|
<el-table v-loading="logLoading" :data="logList">
|
||||||
|
<el-table-column label="请求ID" align="center" prop="requestId" min-width="180" show-overflow-tooltip />
|
||||||
|
<el-table-column label="流程" align="center" prop="flowCode" width="140" />
|
||||||
|
<el-table-column label="开始时间" align="center" prop="startTime" width="160" />
|
||||||
|
<el-table-column label="结束时间" align="center" prop="endTime" width="160" />
|
||||||
|
<el-table-column label="状态" align="center" width="90">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag :type="logStatusTag(scope.row.status)">{{ logStatusText(scope.row.status) }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="成功" align="center" prop="successCount" width="80" />
|
||||||
|
<el-table-column label="失败" align="center" prop="failCount" width="80" />
|
||||||
|
<el-table-column label="错误信息" align="center" prop="errorMsg" min-width="220" show-overflow-tooltip />
|
||||||
|
</el-table>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-dialog :title="platformTitle" :visible.sync="platformOpen" width="760px" append-to-body>
|
||||||
|
<el-form ref="platformForm" :model="platformForm" :rules="platformRules" label-width="110px">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="平台编码" prop="platformCode">
|
||||||
|
<el-input v-model="platformForm.platformCode" placeholder="如 DCP" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="平台名称" prop="platformName">
|
||||||
|
<el-input v-model="platformForm.platformName" placeholder="请输入平台名称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="基础地址" prop="baseUrl">
|
||||||
|
<el-input v-model="platformForm.baseUrl" placeholder="请输入第三方平台基础地址" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="认证类型" prop="authType">
|
||||||
|
<el-input v-model="platformForm.authType" placeholder="如 PASSWORD_TOKEN" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-switch v-model="platformForm.status" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="停用" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="凭证配置" prop="credentialJson">
|
||||||
|
<el-input v-model="platformForm.credentialJson" type="textarea" :rows="8" placeholder="请输入 JSON 格式凭证配置" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="submitPlatformForm">确 定</el-button>
|
||||||
|
<el-button @click="platformOpen = false">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog :title="apiTitle" :visible.sync="apiOpen" width="820px" append-to-body>
|
||||||
|
<el-form ref="apiForm" :model="apiForm" :rules="apiRules" label-width="110px">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="接口编码" prop="apiCode">
|
||||||
|
<el-input v-model="apiForm.apiCode" placeholder="如 dcp_token" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="接口名称" prop="apiName">
|
||||||
|
<el-input v-model="apiForm.apiName" placeholder="请输入接口名称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="请求方法" prop="method">
|
||||||
|
<el-select v-model="apiForm.method" placeholder="方法">
|
||||||
|
<el-option label="GET" value="GET" />
|
||||||
|
<el-option label="POST" value="POST" />
|
||||||
|
<el-option label="PUT" value="PUT" />
|
||||||
|
<el-option label="DELETE" value="DELETE" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="Content-Type" prop="contentType">
|
||||||
|
<el-input v-model="apiForm.contentType" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="排序" prop="sortOrder">
|
||||||
|
<el-input-number v-model="apiForm.sortOrder" :min="0" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="URI" prop="uri">
|
||||||
|
<el-input v-model="apiForm.uri" placeholder="/api/path/{id}" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="依赖接口">
|
||||||
|
<el-input v-model="apiForm.dependsOn" placeholder="可为空" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-switch v-model="apiForm.status" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="停用" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="返回路径">
|
||||||
|
<el-input v-model="apiForm.responsePath" placeholder="如 data.items" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="请求头模板">
|
||||||
|
<el-input v-model="apiForm.headersJson" type="textarea" :rows="5" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="Query模板">
|
||||||
|
<el-input v-model="apiForm.queryJson" type="textarea" :rows="5" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="Body模板">
|
||||||
|
<el-input v-model="apiForm.bodyJson" type="textarea" :rows="5" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="submitApiForm">确 定</el-button>
|
||||||
|
<el-button @click="apiOpen = false">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog :title="mappingTitle" :visible.sync="mappingOpen" width="760px" append-to-body>
|
||||||
|
<el-form ref="mappingForm" :model="mappingForm" :rules="mappingRules" label-width="120px">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="第三方设备ID" prop="externalDeviceId">
|
||||||
|
<el-input v-model="mappingForm.externalDeviceId" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="第三方设备名">
|
||||||
|
<el-input v-model="mappingForm.externalDeviceName" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="产品ID" prop="productId">
|
||||||
|
<el-input-number v-model="mappingForm.productId" :min="1" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="设备编号" prop="serialNumber">
|
||||||
|
<el-input v-model="mappingForm.serialNumber" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="指标映射">
|
||||||
|
<el-input v-model="mappingForm.identifierMappingJson" type="textarea" :rows="6" placeholder='如 {"e":"axialForce","temperature":"temperature"}' />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-switch v-model="mappingForm.status" :active-value="1" :inactive-value="0" active-text="启用" inactive-text="停用" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="submitMappingForm">确 定</el-button>
|
||||||
|
<el-button @click="mappingOpen = false">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog title="平台数据同步" :visible.sync="syncOpen" width="620px" append-to-body>
|
||||||
|
<el-form ref="syncForm" :model="syncForm" label-width="110px">
|
||||||
|
<el-form-item label="平台编码">
|
||||||
|
<el-input v-model="syncForm.platformCode" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="项目ID">
|
||||||
|
<el-input v-model="syncForm.projectId" placeholder="可选,不填则按平台默认拉取" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="开始时间">
|
||||||
|
<el-date-picker v-model="syncForm.startTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="开始时间" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="结束时间">
|
||||||
|
<el-date-picker v-model="syncForm.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="结束时间" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="第三方设备ID">
|
||||||
|
<el-input v-model="syncForm.externalDeviceIdsText" type="textarea" :rows="5" placeholder="可选,一行一个设备ID" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="原始数据">
|
||||||
|
<el-switch v-model="syncForm.original" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="只预演">
|
||||||
|
<el-switch v-model="syncForm.dryRun" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="submitSync">执 行</el-button>
|
||||||
|
<el-button @click="syncOpen = false">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
addDeviceMapping,
|
||||||
|
addIntegrationApi,
|
||||||
|
addPlatform,
|
||||||
|
delDeviceMapping,
|
||||||
|
delIntegrationApi,
|
||||||
|
delPlatform,
|
||||||
|
getDeviceMapping,
|
||||||
|
getIntegrationApi,
|
||||||
|
getPlatform,
|
||||||
|
listDeviceMapping,
|
||||||
|
listIntegrationApi,
|
||||||
|
listIntegrationLog,
|
||||||
|
listPlatform,
|
||||||
|
selfTestIntegration,
|
||||||
|
syncIntegration,
|
||||||
|
updateDeviceMapping,
|
||||||
|
updateIntegrationApi,
|
||||||
|
updatePlatform
|
||||||
|
} from '@/api/iot/integration'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Integration',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showSearch: true,
|
||||||
|
loading: false,
|
||||||
|
apiLoading: false,
|
||||||
|
mappingLoading: false,
|
||||||
|
logLoading: false,
|
||||||
|
total: 0,
|
||||||
|
platformList: [],
|
||||||
|
apiList: [],
|
||||||
|
mappingList: [],
|
||||||
|
logList: [],
|
||||||
|
ids: [],
|
||||||
|
apiIds: [],
|
||||||
|
mappingIds: [],
|
||||||
|
single: true,
|
||||||
|
multiple: true,
|
||||||
|
apiSingle: true,
|
||||||
|
apiMultiple: true,
|
||||||
|
mappingSingle: true,
|
||||||
|
mappingMultiple: true,
|
||||||
|
currentPlatform: null,
|
||||||
|
activeTab: 'api',
|
||||||
|
queryParams: {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
platformCode: null,
|
||||||
|
platformName: null,
|
||||||
|
status: null
|
||||||
|
},
|
||||||
|
platformOpen: false,
|
||||||
|
platformTitle: '',
|
||||||
|
platformForm: {},
|
||||||
|
platformRules: {
|
||||||
|
platformCode: [{ required: true, message: '平台编码不能为空', trigger: 'blur' }],
|
||||||
|
platformName: [{ required: true, message: '平台名称不能为空', trigger: 'blur' }],
|
||||||
|
baseUrl: [{ required: true, message: '基础地址不能为空', trigger: 'blur' }]
|
||||||
|
},
|
||||||
|
apiOpen: false,
|
||||||
|
apiTitle: '',
|
||||||
|
apiForm: {},
|
||||||
|
apiRules: {
|
||||||
|
apiCode: [{ required: true, message: '接口编码不能为空', trigger: 'blur' }],
|
||||||
|
apiName: [{ required: true, message: '接口名称不能为空', trigger: 'blur' }],
|
||||||
|
method: [{ required: true, message: '请求方法不能为空', trigger: 'change' }],
|
||||||
|
uri: [{ required: true, message: '接口URI不能为空', trigger: 'blur' }]
|
||||||
|
},
|
||||||
|
mappingOpen: false,
|
||||||
|
mappingTitle: '',
|
||||||
|
mappingForm: {},
|
||||||
|
mappingRules: {
|
||||||
|
externalDeviceId: [{ required: true, message: '第三方设备ID不能为空', trigger: 'blur' }],
|
||||||
|
productId: [{ required: true, message: '产品ID不能为空', trigger: 'blur' }],
|
||||||
|
serialNumber: [{ required: true, message: '设备编号不能为空', trigger: 'blur' }]
|
||||||
|
},
|
||||||
|
syncOpen: false,
|
||||||
|
syncForm: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getList()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getList() {
|
||||||
|
this.loading = true
|
||||||
|
listPlatform(this.queryParams).then(response => {
|
||||||
|
this.platformList = response.rows || []
|
||||||
|
this.total = response.total || 0
|
||||||
|
this.loading = false
|
||||||
|
if (!this.currentPlatform && this.platformList.length > 0) {
|
||||||
|
this.selectPlatform(this.platformList[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleQuery() {
|
||||||
|
this.queryParams.pageNum = 1
|
||||||
|
this.currentPlatform = null
|
||||||
|
this.getList()
|
||||||
|
},
|
||||||
|
resetQuery() {
|
||||||
|
this.resetForm('queryForm')
|
||||||
|
this.handleQuery()
|
||||||
|
},
|
||||||
|
handleSelectionChange(selection) {
|
||||||
|
this.ids = selection.map(item => item.id)
|
||||||
|
this.single = selection.length !== 1
|
||||||
|
this.multiple = !selection.length
|
||||||
|
},
|
||||||
|
handleCurrentPlatformChange(row) {
|
||||||
|
if (row) {
|
||||||
|
this.selectPlatform(row)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectPlatform(row) {
|
||||||
|
this.currentPlatform = row
|
||||||
|
this.apiIds = []
|
||||||
|
this.mappingIds = []
|
||||||
|
this.apiSingle = true
|
||||||
|
this.apiMultiple = true
|
||||||
|
this.mappingSingle = true
|
||||||
|
this.mappingMultiple = true
|
||||||
|
this.getApiList()
|
||||||
|
this.getMappingList()
|
||||||
|
this.getLogList()
|
||||||
|
},
|
||||||
|
handleTabClick() {
|
||||||
|
if (!this.currentPlatform) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.activeTab === 'api') {
|
||||||
|
this.getApiList()
|
||||||
|
} else if (this.activeTab === 'mapping') {
|
||||||
|
this.getMappingList()
|
||||||
|
} else {
|
||||||
|
this.getLogList()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetPlatformForm() {
|
||||||
|
this.platformForm = {
|
||||||
|
id: null,
|
||||||
|
platformCode: null,
|
||||||
|
platformName: null,
|
||||||
|
baseUrl: null,
|
||||||
|
authType: 'PASSWORD_TOKEN',
|
||||||
|
credentialJson: '{}',
|
||||||
|
status: 0
|
||||||
|
}
|
||||||
|
this.resetForm('platformForm')
|
||||||
|
},
|
||||||
|
handleAddPlatform() {
|
||||||
|
this.resetPlatformForm()
|
||||||
|
this.platformOpen = true
|
||||||
|
this.platformTitle = '新增接入平台'
|
||||||
|
},
|
||||||
|
handleUpdatePlatform(row) {
|
||||||
|
this.resetPlatformForm()
|
||||||
|
const id = row.id || this.ids
|
||||||
|
getPlatform(id).then(response => {
|
||||||
|
this.platformForm = response.data
|
||||||
|
this.platformOpen = true
|
||||||
|
this.platformTitle = '修改接入平台'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submitPlatformForm() {
|
||||||
|
this.$refs.platformForm.validate(valid => {
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const request = this.platformForm.id != null ? updatePlatform(this.platformForm) : addPlatform(this.platformForm)
|
||||||
|
request.then(() => {
|
||||||
|
this.$modal.msgSuccess(this.platformForm.id != null ? '修改成功' : '新增成功')
|
||||||
|
this.platformOpen = false
|
||||||
|
this.currentPlatform = null
|
||||||
|
this.getList()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleDeletePlatform(row) {
|
||||||
|
const ids = row.id || this.ids
|
||||||
|
this.$modal.confirm('是否确认删除平台编号为 "' + ids + '" 的数据项?').then(function() {
|
||||||
|
return delPlatform(ids)
|
||||||
|
}).then(() => {
|
||||||
|
this.currentPlatform = null
|
||||||
|
this.getList()
|
||||||
|
this.$modal.msgSuccess('删除成功')
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
getApiList() {
|
||||||
|
if (!this.currentPlatform) {
|
||||||
|
this.apiList = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.apiLoading = true
|
||||||
|
listIntegrationApi({ platformId: this.currentPlatform.id, pageNum: 1, pageSize: 100 }).then(response => {
|
||||||
|
this.apiList = response.rows || []
|
||||||
|
this.apiLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleApiSelectionChange(selection) {
|
||||||
|
this.apiIds = selection.map(item => item.id)
|
||||||
|
this.apiSingle = selection.length !== 1
|
||||||
|
this.apiMultiple = !selection.length
|
||||||
|
},
|
||||||
|
resetApiForm() {
|
||||||
|
this.apiForm = {
|
||||||
|
id: null,
|
||||||
|
platformId: this.currentPlatform ? this.currentPlatform.id : null,
|
||||||
|
apiCode: null,
|
||||||
|
apiName: null,
|
||||||
|
method: 'GET',
|
||||||
|
uri: null,
|
||||||
|
contentType: 'application/json',
|
||||||
|
headersJson: '{}',
|
||||||
|
queryJson: '{}',
|
||||||
|
bodyJson: '{}',
|
||||||
|
responsePath: null,
|
||||||
|
dependsOn: null,
|
||||||
|
sortOrder: 0,
|
||||||
|
status: 1
|
||||||
|
}
|
||||||
|
this.resetForm('apiForm')
|
||||||
|
},
|
||||||
|
handleAddApi() {
|
||||||
|
this.resetApiForm()
|
||||||
|
this.apiOpen = true
|
||||||
|
this.apiTitle = '新增接口定义'
|
||||||
|
},
|
||||||
|
handleUpdateApi(row) {
|
||||||
|
this.resetApiForm()
|
||||||
|
const id = row.id || this.apiIds
|
||||||
|
getIntegrationApi(id).then(response => {
|
||||||
|
this.apiForm = response.data
|
||||||
|
this.apiOpen = true
|
||||||
|
this.apiTitle = '修改接口定义'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submitApiForm() {
|
||||||
|
this.$refs.apiForm.validate(valid => {
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const request = this.apiForm.id != null ? updateIntegrationApi(this.apiForm) : addIntegrationApi(this.apiForm)
|
||||||
|
request.then(() => {
|
||||||
|
this.$modal.msgSuccess(this.apiForm.id != null ? '修改成功' : '新增成功')
|
||||||
|
this.apiOpen = false
|
||||||
|
this.getApiList()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleDeleteApi(row) {
|
||||||
|
const ids = row.id || this.apiIds
|
||||||
|
this.$modal.confirm('是否确认删除接口编号为 "' + ids + '" 的数据项?').then(function() {
|
||||||
|
return delIntegrationApi(ids)
|
||||||
|
}).then(() => {
|
||||||
|
this.getApiList()
|
||||||
|
this.$modal.msgSuccess('删除成功')
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
getMappingList() {
|
||||||
|
if (!this.currentPlatform) {
|
||||||
|
this.mappingList = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.mappingLoading = true
|
||||||
|
listDeviceMapping({ platformId: this.currentPlatform.id, pageNum: 1, pageSize: 100 }).then(response => {
|
||||||
|
this.mappingList = response.rows || []
|
||||||
|
this.mappingLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleMappingSelectionChange(selection) {
|
||||||
|
this.mappingIds = selection.map(item => item.id)
|
||||||
|
this.mappingSingle = selection.length !== 1
|
||||||
|
this.mappingMultiple = !selection.length
|
||||||
|
},
|
||||||
|
resetMappingForm() {
|
||||||
|
this.mappingForm = {
|
||||||
|
id: null,
|
||||||
|
platformId: this.currentPlatform ? this.currentPlatform.id : null,
|
||||||
|
externalDeviceId: null,
|
||||||
|
externalDeviceName: null,
|
||||||
|
productId: null,
|
||||||
|
serialNumber: null,
|
||||||
|
identifierMappingJson: '{"e":"axialForce","temperature":"temperature","ct":"ct","pt":"pt"}',
|
||||||
|
status: 1
|
||||||
|
}
|
||||||
|
this.resetForm('mappingForm')
|
||||||
|
},
|
||||||
|
handleAddMapping() {
|
||||||
|
this.resetMappingForm()
|
||||||
|
this.mappingOpen = true
|
||||||
|
this.mappingTitle = '新增设备映射'
|
||||||
|
},
|
||||||
|
handleUpdateMapping(row) {
|
||||||
|
this.resetMappingForm()
|
||||||
|
const id = row.id || this.mappingIds
|
||||||
|
getDeviceMapping(id).then(response => {
|
||||||
|
this.mappingForm = response.data
|
||||||
|
this.mappingOpen = true
|
||||||
|
this.mappingTitle = '修改设备映射'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submitMappingForm() {
|
||||||
|
this.$refs.mappingForm.validate(valid => {
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const request = this.mappingForm.id != null ? updateDeviceMapping(this.mappingForm) : addDeviceMapping(this.mappingForm)
|
||||||
|
request.then(() => {
|
||||||
|
this.$modal.msgSuccess(this.mappingForm.id != null ? '修改成功' : '新增成功')
|
||||||
|
this.mappingOpen = false
|
||||||
|
this.getMappingList()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleDeleteMapping(row) {
|
||||||
|
const ids = row.id || this.mappingIds
|
||||||
|
this.$modal.confirm('是否确认删除映射编号为 "' + ids + '" 的数据项?').then(function() {
|
||||||
|
return delDeviceMapping(ids)
|
||||||
|
}).then(() => {
|
||||||
|
this.getMappingList()
|
||||||
|
this.$modal.msgSuccess('删除成功')
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
getLogList() {
|
||||||
|
if (!this.currentPlatform) {
|
||||||
|
this.logList = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.logLoading = true
|
||||||
|
listIntegrationLog({ platformId: this.currentPlatform.id, pageNum: 1, pageSize: 20 }).then(response => {
|
||||||
|
this.logList = response.rows || []
|
||||||
|
this.logLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
openSyncDialog(dryRun, row) {
|
||||||
|
const platform = row || this.currentPlatform
|
||||||
|
if (!platform) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.syncForm = {
|
||||||
|
platformCode: platform.platformCode,
|
||||||
|
projectId: null,
|
||||||
|
startTime: null,
|
||||||
|
endTime: null,
|
||||||
|
original: false,
|
||||||
|
dryRun: !!dryRun,
|
||||||
|
externalDeviceIdsText: ''
|
||||||
|
}
|
||||||
|
this.syncOpen = true
|
||||||
|
},
|
||||||
|
submitSync() {
|
||||||
|
const externalDeviceIds = (this.syncForm.externalDeviceIdsText || '').split(/\r?\n/).map(item => item.trim()).filter(item => item)
|
||||||
|
const data = {
|
||||||
|
platformCode: this.syncForm.platformCode,
|
||||||
|
projectId: this.syncForm.projectId,
|
||||||
|
startTime: this.syncForm.startTime,
|
||||||
|
endTime: this.syncForm.endTime,
|
||||||
|
original: this.syncForm.original,
|
||||||
|
dryRun: this.syncForm.dryRun,
|
||||||
|
externalDeviceIds: externalDeviceIds
|
||||||
|
}
|
||||||
|
syncIntegration(data).then(response => {
|
||||||
|
this.$modal.msgSuccess('执行完成,成功 ' + response.data.successCount + ' 条,失败 ' + response.data.failCount + ' 条')
|
||||||
|
this.syncOpen = false
|
||||||
|
this.getLogList()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleSelfTest() {
|
||||||
|
if (!this.currentPlatform) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selfTestIntegration(this.currentPlatform.platformCode).then(response => {
|
||||||
|
this.$modal.msgSuccess('自测完成,成功 ' + response.data.successCount + ' 条,失败 ' + response.data.failCount + ' 条')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
logStatusText(status) {
|
||||||
|
if (status === 1) {
|
||||||
|
return '成功'
|
||||||
|
}
|
||||||
|
if (status === 2) {
|
||||||
|
return '执行中'
|
||||||
|
}
|
||||||
|
return '失败'
|
||||||
|
},
|
||||||
|
logStatusTag(status) {
|
||||||
|
if (status === 1) {
|
||||||
|
return 'success'
|
||||||
|
}
|
||||||
|
if (status === 2) {
|
||||||
|
return 'warning'
|
||||||
|
}
|
||||||
|
return 'danger'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.integration-page {
|
||||||
|
.integration-detail {
|
||||||
|
margin-top: 18px;
|
||||||
|
padding-top: 14px;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-subtitle {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue
Block a user