feat:合并2.0全部商业版全部代码,保持与演示环境100%一致

This commit is contained in:
Join
2025-10-30 08:43:29 +08:00
parent c63cbd51d2
commit 5f9e1ab472
457 changed files with 23195 additions and 16091 deletions
+2 -1
View File
@@ -120,7 +120,8 @@
- **文档地址
**[https://nexiotplatform.github.io/universal-iot-docs/](https://nexiotplatform.github.io/universal-iot-docs/)
- **在线演示**[http://iot.192886.xyz:81/](http://iot.192886.xyz:81/)
- **调试IDE**[http://iot.192886.xyz:81/magic/debug/index.html](http://iot.192886.xyz:81/magic/debug/index.html)
- **调试IDE
**[http://iot.192886.xyz:81/magic/debug/index.html](http://iot.192886.xyz:81/magic/debug/index.html)
---
+50 -38
View File
@@ -50,6 +50,7 @@
一键导入,无需修改平台代码,无需重新编译部署。设备驱动在线热更新,新增设备类型零停机。稳定运行几年无需重启维护。
### 🎯 核心价值
- **🚀 零代码侵入**:设备驱动完全外置,一键导出源码直接使用,无任何代码侵入
- **🔧 解耦架构**:驱动与平台完全分离,热插拔式设备接入,生态共享
- **🏗️ 全协议支持**TCP、MQTT、Modbus、天翼物联、HTTP等主流协议
@@ -65,7 +66,6 @@
-**全协议支持**Modbus RTU/TCP、MQTT、HTTP等工业协议和物联网协议
-**云平台对接**:天翼物联、移动OneNet等云平台集成
## 🚀 快速开始
### 🎯 演示地址
@@ -82,34 +82,37 @@
### 🛠️ 技术栈
#### 🚀 核心框架(极简轻量)
- **后端框架**`Java 21` `SpringBoot 3.5` `Tk.Mybatis 5.0.1`
- **前端技术**:基于`RuoYi-Antdv`构建,感谢开源社区!
#### 💾 数据存储
- **关系数据库**MySQL 5.8+
- **缓存系统**Redis 6.0+
- **关系数据库**MySQL 5.8+
- **缓存系统**Redis 6.0+
- **时序数据库**ClickHouse / IoTDB(配置开启)
## 📈 正在推进
### 🚀 近期规划
- **🤖 AI能力增强**:智能故障诊断
- **📱 移动端应用**:付费图鸟定小程序,具备指令控制、属性、告警查看,轻量化、多管理员
### 🌟 长期愿景
- **🧠 深度学习**:设备行为学习、预测性维护、智能优化
- **🌐 生态建设**:插件市场、第三方集成、开发者社区
- **🏭 行业方案**:智能制造、智慧城市、智慧农业等垂直解决方案
## 📸 实战案例与视频教程
### 📊 支持设备类型
| | |
|-------------------------------------------------|-----------------------------------------------|
| ![电表设备](/__MACOSX/shot/1018/dian.png) | ![摄像头](/__MACOSX/shot/1018/v22.png) |
| ![网关DTU](/__MACOSX/shot/1018/1760431601441.jpg) | ![水浸设备](/__MACOSX/shot/1018/111.jpg) |
| | |
|-------------------------------------------------|--------------------------------------------|
| ![电表设备](/__MACOSX/shot/1018/dian.png) | ![摄像头](/__MACOSX/shot/1018/v22.png) |
| ![网关DTU](/__MACOSX/shot/1018/1760431601441.jpg) | ![水浸设备](/__MACOSX/shot/1018/111.jpg) |
| ![声光报警器](/__MACOSX/shot/1026/device-sgbjq.png) | ![水浸设备](/__MACOSX/shot/1026/device-sj.jpg) |
### 📡 天翼物联AI接入教程
@@ -117,15 +120,18 @@
#### 🌊 NB水浸传感器
1. **【手把手实战:水浸传感器开箱:初见与硬件解析】**
- 📺 [B站观看](https://www.bilibili.com/video/BV1kLY5z5EMH/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
-
📺 [B站观看](https://www.bilibili.com/video/BV1kLY5z5EMH/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
- 📝 内容:设备开箱、硬件介绍、AI智能识别功能测试
2. **【手把手实战:NexIoT接入Ctwing参数配置】**
- 📺 [B站观看](https://www.bilibili.com/video/BV1jVY7zVEjM/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
-
📺 [B站观看](https://www.bilibili.com/video/BV1jVY7zVEjM/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
- 📝 内容:天翼物联平台参数配置、智能设备注册流程
3. **【手把手实战:NB水浸传感器CTWing接入全流程】**
- 📺 [B站观看](https://www.bilibili.com/video/BV1sJaZzBEfr/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
-
📺 [B站观看](https://www.bilibili.com/video/BV1sJaZzBEfr/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
- 📝 内容:外置驱动的完整设备接入流程,从智能配置到数据上报
### 🔌 TCP直连AI接入教程
@@ -133,15 +139,18 @@
#### ⚡ DL/T645电表
1. **【NexIoT开源】手把手实战:DL/T645电表TCP直连接入】**
- 📺 [B站观看](https://www.bilibili.com/video/BV1x4pBzZEVN/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
-
📺 [B站观看](https://www.bilibili.com/video/BV1x4pBzZEVN/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
- 📝 内容:TCP协议解析、设备直连配置、智能数据解析
2. **【NexIoT开源】手把手实战:产品创建与SNI解读】**
- 📺 [B站观看](https://www.bilibili.com/video/BV1zSWNzoETF/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
-
📺 [B站观看](https://www.bilibili.com/video/BV1zSWNzoETF/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
- 📝 内容:创建流程、智能SNI设备标识解析、AI协议调试
3. **【NexIoT开源】手把手实战:如何写TCP接入的DL/T645电表解析脚本】**
- 📺 [B站观看](https://www.bilibili.com/video/BV1SAWVzwEZE/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
-
📺 [B站观看](https://www.bilibili.com/video/BV1SAWVzwEZE/?share_source=copy_web&vd_source=c9e1500efcc8aa0763f711fadaa68dff)
- 📝 内容:数据流介绍、智能调试IDE的编写和调试、645电表的AI解析
#### ⚡ 接入成果展示
@@ -180,15 +189,14 @@
### 🎯 功能深度展示
| | |
|------------------------------------------|---------------------------------------|
| | |
|-----------------------------------------|--------------------------------------|
| ![产品管理](/__MACOSX/shot/1018/pd.jpg) | ![设备管理](/__MACOSX/shot/1018/sb.jpg) |
| ![视频监控](/__MACOSX/shot/1018/video.jpg) | ![网络管理](/__MACOSX/shot/1018/net.jpg) |
| ![数据管理1](/__MACOSX/shot/1018/d1.jpg) | ![数据管理2](/__MACOSX/shot/1018/d2.jpg) |
| ![数据管理1](/__MACOSX/shot/1018/d1.jpg) | ![数据管理2](/__MACOSX/shot/1018/d2.jpg) |
| ![运维管理](/__MACOSX/shot/1018/yz.jpg) | ![设备状态](/__MACOSX/shot/1018/zsb.jpg) |
| ![设备面板](/__MACOSX/shot/1018/zsb-pb.jpg) | ![系统配置](/__MACOSX/shot/1018/zt.jpg) |
| ![报警管理](/__MACOSX/shot/1018/bjm.jpg) | ![数据采集](/__MACOSX/shot/1018/cj.jpg) |
| ![报警管理](/__MACOSX/shot/1018/bjm.jpg) | ![数据采集](/__MACOSX/shot/1018/cj.jpg) |
## 📝 更新日志
@@ -228,25 +236,27 @@
```
4. **创建 Pull Request**
- 在 Gitee 上创建 Pull Request
- 详细描述您的修改内容和增强功能
- 等待代码审查和功能验证
- 在 Gitee 上创建 Pull Request
- 详细描述您的修改内容和增强功能
- 等待代码审查和功能验证
#### 🌟 社区价值观
- **🤝 相互尊重**:欢迎所有开发者加入,有问题我们第一时间回复
- **⭐ 真诚支持**:喜欢请点star,不喜欢也请尊重,不要恶意行为
- **🚀 共同成长**:分享技术经验,推动物联网智能化发展
- **💡 开放创新**:鼓励功能创新和零代码技术改进
#### 📢 重要声明
- **自媒体转发**:欢迎转发项目信息,请遵循AGPL3.0开源协议
- **企业授权**:商业使用需获得授权,保留企业授权信息
- **法律追责**:如单位误用引起法律后果,保留追责责任
#### 🌐 社区联系方式
| QQ群 | 微信 | B站 | 抖音 |
|-------------------------------|-------------------------------|----------------------------------|----------------------------------|
| QQ群 | 微信 | B站 | 抖音 |
|-------------------------------|-------------------------------|---------------------------------|----------------------------------|
| ![QQ群](/__MACOSX/shot/qq.png) | ![微信群](/__MACOSX/shot/wx.png) | ![B站](/__MACOSX/shot/bzhan.jpg) | ![抖音](/__MACOSX/shot/douyin.jpg) |
## 🛠️ 技术支持与服务
@@ -255,14 +265,14 @@
### 📋 服务类型对比
| 服务类型 | 服务内容 | 价格 | 适用场景 | 联系方式 |
|---------|---------|--------------|----------|----------|
| **🆓 社区技术答疑** | • 基础问题答疑<br>• 使用指导<br>• 社区交流 | **免费** | 学习、使用 | QQ群、微信群 |
| **📚 技术文档** | • 技术文档<br>• 实践指南<br>• 视频教程 | **免费** | 自学、参考 | [文档地址](https://nexiotplatform.github.io/universal-iot-docs/) |
| **☁️ 线上部署服务** | • 客户提供硬件<br>• 完成系统部署<br>• 云服务部署 | **¥169** | 快速上线、云部署 | 联系客服 |
| **🔧 专属技术支持** | • 各类对接支持<br>• 使用指导<br>• 部署方案<br>• 解决方案 | **¥99/天** | 项目对接、技术咨询 | 联系客服 |
| **⚡ 设备接入服务** | • IDE解析源码<br>• 产品配置导出<br>• 定制化接入 | **¥1000/日** | 复杂设备接入、定制开发 | 联系客服 |
| **🏢 企业省心服务** | • 商业版本地部署<br>• 代运维服务<br>• 5×8技术支持 | **¥5,999/年** | 仅剩5名,企业级应用、长期运维 | 联系客服 |
| 服务类型 | 服务内容 | 价格 | 适用场景 | 联系方式 |
|---------------|----------------------------------------|--------------|-----------------|--------------------------------------------------------------|
| **🆓 社区技术答疑** | • 基础问题答疑<br>• 使用指导<br>• 社区交流 | **免费** | 学习、使用 | QQ群、微信群 |
| **📚 技术文档** | • 技术文档<br>• 实践指南<br>• 视频教程 | **免费** | 自学、参考 | [文档地址](https://nexiotplatform.github.io/universal-iot-docs/) |
| **☁️ 线上部署服务** | • 客户提供硬件<br>• 完成系统部署<br>• 云服务部署 | **¥169** | 快速上线、云部署 | 联系客服 |
| **🔧 专属技术支持** | • 各类对接支持<br>• 使用指导<br>• 部署方案<br>• 解决方案 | **¥99/天** | 项目对接、技术咨询 | 联系客服 |
| **⚡ 设备接入服务** | • IDE解析源码<br>• 产品配置导出<br>• 定制化接入 | **¥1000/日** | 复杂设备接入、定制开发 | 联系客服 |
| **🏢 企业省心服务** | • 商业版本地部署<br>• 代运维服务<br>• 5×8技术支持 | **¥5,999/年** | 仅剩5名,企业级应用、长期运维 | 联系客服 |
---
@@ -271,18 +281,20 @@
### 📜 详细授权条款
#### ✅ 授权用户权益
1. **内部使用**:授权用户可无限期在其内部使用
- 企业授权:不含分公司、子公司及第三方公司
- 个人授权:仅限个人使用,不可用于在职公司或第三方
- 企业授权:不含分公司、子公司及第三方公司
- 个人授权:仅限个人使用,不可用于在职公司或第三方
2. **开发权限**:授权用户可通过项目形式进行源码二次开发
- 定制化软件必须加密打包后交付客户
- 如需交付源码,必须为客户购买对应商业授权
- 定制化软件必须加密打包后交付客户
- 如需交付源码,必须为客户购买对应商业授权
#### ❌ 授权限制
1. **禁止转让**:不可向第三方发行或转让授权源码
- 无论有偿或无偿转让
- 不可申请包含本项目的软件著作权
- 无论有偿或无偿转让
- 不可申请包含本项目的软件著作权
### 🔒 开源和商业
+133 -133
View File
@@ -1,143 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cn-universal</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cn-universal</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cn-universal-admin</artifactId>
<dependencies>
<artifactId>cn-universal-admin</artifactId>
<dependencies>
<!--velocity代码生成使用模板 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-persistence</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-rule</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-dm</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-core</artifactId>
<version>${project.version}</version>
</dependency>
<!--velocity代码生成使用模板 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-persistence</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-rule</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-dm</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-oss</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-oss</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
<!--排除这个slf4j-log4j12-->
<groupId>org.springframework.boot</groupId>
<version>${spring-boot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
<!--排除这个slf4j-log4j12-->
<groupId>org.springframework.boot</groupId>
<version>${spring-boot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- Validation依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- OAuth2 Resource Server (JWT support) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
<exclusion>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-mqtt-protocol</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-tcp-protocol</artifactId>
<scope>compile</scope>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-udp-protocol</artifactId>
<scope>compile</scope>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-notice</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-security</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- Validation依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- OAuth2 Resource Server (JWT support) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
<exclusion>
<groupId>com.vaadin.external.google</groupId>
<artifactId>android-json</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-mqtt-protocol</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-tcp-protocol</artifactId>
<scope>compile</scope>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-udp-protocol</artifactId>
<scope>compile</scope>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-notice</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-security</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
@@ -809,7 +809,6 @@ public class NetworkServiceImpl implements INetworkService {
throw new IoTException("TCP模块未注入,无法启动TCP网络组件");
}
String productKey = network.getProductKey();
if (StrUtil.isBlank(productKey)) {
log.error("TCP网络组件缺少productKey: {}", network.getName());
@@ -843,7 +842,6 @@ public class NetworkServiceImpl implements INetworkService {
throw new IoTException("TCP模块未注入,无法启动TCP网络组件");
}
String productKey = network.getProductKey();
if (StrUtil.isBlank(productKey)) {
log.error("TCP网络组件缺少productKey: {}", network.getName());
@@ -876,7 +874,6 @@ public class NetworkServiceImpl implements INetworkService {
throw new IoTException("TCP模块未注入,无法启动TCP网络组件");
}
String productKey = network.getProductKey();
if (StrUtil.isBlank(productKey)) {
log.error("TCP网络组件缺少productKey: {}", network.getName());
@@ -26,58 +26,50 @@ import org.springframework.stereotype.Component;
/**
* 网关轮询定时任务调度器
*
*
* <p>使用固定轮询间隔(30s, 60s, 120s, 300s, 600s),通过分布式锁保证集群环境下只有一个节点执行
*
*
* @author Aleo
* @date 2025-10-26
*/
@Component
@Slf4j
@ConditionalOnProperty(prefix = "gateway.polling", name = "enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(
prefix = "gateway.polling",
name = "enabled",
havingValue = "true",
matchIfMissing = true)
public class GatewayPollingScheduler {
@Autowired
private IGatewayPollingService gatewayPollingService;
@Autowired private IGatewayPollingService gatewayPollingService;
@Autowired
private RedissonClient redissonClient;
@Autowired private RedissonClient redissonClient;
/**
* 30秒轮询任务
*/
/** 30秒轮询任务 */
@Scheduled(cron = "0/30 * * * * ?")
public void polling30s() {
executePollingWithLock(30);
}
/**
* 60秒轮询任务
*/
/** 60秒轮询任务 */
@Scheduled(cron = "0 0/1 * * * ?")
public void polling60s() {
executePollingWithLock(60);
}
/**
* 120秒轮询任务
*/
/** 120秒轮询任务 */
@Scheduled(cron = "0 0/2 * * * ?")
public void polling120s() {
executePollingWithLock(120);
}
/**
* 300秒轮询任务 (5分钟)
*/
/** 300秒轮询任务 (5分钟) */
@Scheduled(cron = "0 0/5 * * * ?")
public void polling300s() {
executePollingWithLock(300);
}
/**
* 600秒轮询任务 (10分钟)
*/
/** 600秒轮询任务 (10分钟) */
@Scheduled(cron = "0 0/10 * * * ?")
public void polling600s() {
executePollingWithLock(600);
@@ -85,7 +77,7 @@ public class GatewayPollingScheduler {
/**
* 使用分布式锁执行轮询任务
*
*
* @param intervalSeconds 轮询间隔(秒)
*/
private void executePollingWithLock(int intervalSeconds) {
@@ -118,14 +110,15 @@ public class GatewayPollingScheduler {
/**
* 执行轮询任务
*
*
* @param intervalSeconds 轮询间隔(秒)
*/
private void executePollingTask(int intervalSeconds) {
long startTime = System.currentTimeMillis();
// 1. 查询待轮询的网关设备
List<GatewayPollingConfig> configs = gatewayPollingService.getDuePollingDevices(intervalSeconds);
List<GatewayPollingConfig> configs =
gatewayPollingService.getDuePollingDevices(intervalSeconds);
if (configs == null || configs.isEmpty()) {
log.debug("本次{}s轮询无待执行设备", intervalSeconds);
@@ -149,7 +142,12 @@ public class GatewayPollingScheduler {
}
long costTime = System.currentTimeMillis() - startTime;
log.info("{}s轮询任务完成: 总数={}, 成功={}, 失败={}, 耗时={}ms",
intervalSeconds, configs.size(), successCount, failCount, costTime);
log.info(
"{}s轮询任务完成: 总数={}, 成功={}, 失败={}, 耗时={}ms",
intervalSeconds,
configs.size(),
successCount,
failCount,
costTime);
}
}
@@ -19,7 +19,7 @@ import java.util.List;
/**
* 网关轮询服务接口
*
*
* @author Aleo
* @date 2025-10-26
*/
@@ -27,7 +27,7 @@ public interface IGatewayPollingService {
/**
* 保存或更新网关轮询配置
*
*
* @param dto 配置DTO
* @return 操作结果
*/
@@ -35,7 +35,7 @@ public interface IGatewayPollingService {
/**
* 获取网关轮询配置
*
*
* @param productKey 产品Key
* @param deviceId 设备ID
* @return 配置信息
@@ -44,7 +44,7 @@ public interface IGatewayPollingService {
/**
* 删除网关轮询配置
*
*
* @param productKey 产品Key
* @param deviceId 设备ID
* @return 操作结果
@@ -53,14 +53,14 @@ public interface IGatewayPollingService {
/**
* 执行单个网关的轮询任务
*
*
* @param config 轮询配置
*/
void pollGatewayDevice(GatewayPollingConfig config);
/**
* 查询待轮询的网关设备列表
*
*
* @param intervalSeconds 轮询间隔
* @return 待轮询设备列表
*/
@@ -68,7 +68,7 @@ public interface IGatewayPollingService {
/**
* 测试轮询 - 立即执行一次轮询
*
*
* @param productKey 产品Key
* @param deviceId 设备ID
* @return 操作结果
@@ -214,7 +214,7 @@ public class ConnectionInfoServiceImpl implements ConnectionInfoService {
.passthroughTopics(passthroughTopics)
.build();
}
/** 构建订阅主题信息 */
private ConnectionInfoDTO.TopicInfo buildSubscribeTopics(JSONObject config, String productKey) {
String subscribe = config.getStr("subscribeTopics");
@@ -15,9 +15,6 @@ package cn.universal.admin.platform.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.universal.admin.platform.service.DeviceCommandDispatcher;
import cn.universal.admin.platform.service.IGatewayPollingService;
import cn.universal.common.constant.IoTConstant.DeviceNode;
@@ -32,8 +29,6 @@ import cn.universal.persistence.mapper.GatewayPollingConfigMapper;
import cn.universal.persistence.mapper.IoTDeviceMapper;
import java.util.Date;
import java.util.List;
import cn.universal.persistence.query.IoTAPIQuery;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -193,11 +188,12 @@ public class GatewayPollingServiceImpl implements IGatewayPollingService {
IoTDevice gateway =
deviceMapper.selectIoTDevice(config.getProductKey(), config.getDeviceId());
// if (gateway == null || !Boolean.TRUE.equals(gateway.getState())) {
// log.warn(
// "网关离线,跳过轮询: productKey={}, deviceId={}", config.getProductKey(), config.getDeviceId());
// return;
// }
// if (gateway == null || !Boolean.TRUE.equals(gateway.getState())) {
// log.warn(
// "网关离线,跳过轮询: productKey={}, deviceId={}", config.getProductKey(),
// config.getDeviceId());
// return;
// }
// 2. 查询轮询指令 (使用 productKey + deviceId)
List<GatewayPollingCommand> commands =
@@ -219,8 +215,9 @@ public class GatewayPollingServiceImpl implements IGatewayPollingService {
boolean allSuccess = true;
int executedCount = 0;
// 获取配置的指令间隔,默认300ms
int commandIntervalMs = config.getCommandIntervalMs() != null ? config.getCommandIntervalMs() : 300;
int commandIntervalMs =
config.getCommandIntervalMs() != null ? config.getCommandIntervalMs() : 300;
for (GatewayPollingCommand command : commands) {
if (Boolean.TRUE.equals(command.getEnabled())) {
// 非第一条指令先等待,给设备响应时间
@@ -228,15 +225,13 @@ public class GatewayPollingServiceImpl implements IGatewayPollingService {
try {
Thread.sleep(commandIntervalMs);
log.debug(
"[指令间隔] 等待{}ms,防止设备缓冲区溢出: deviceId={}",
commandIntervalMs,
config.getDeviceId());
"[指令间隔] 等待{}ms,防止设备缓冲区溢出: deviceId={}", commandIntervalMs, config.getDeviceId());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("指令间隔等待被中断", e);
}
}
boolean success = executeCommand(gateway, command);
if (!success) {
allSuccess = false;
@@ -285,10 +280,7 @@ public class GatewayPollingServiceImpl implements IGatewayPollingService {
IoTDeviceDTO device =
deviceMapper.selectIoTDeviceBO(
BeanUtil.beanToMap(
IoTDevice.builder()
.productKey(productKey)
.deviceId(deviceId)
.build()));
IoTDevice.builder().productKey(productKey).deviceId(deviceId).build()));
if (device == null) {
return R.error("设备不存在");
@@ -306,13 +298,16 @@ public class GatewayPollingServiceImpl implements IGatewayPollingService {
return R.error("未配置轮询指令,请先添加轮询指令");
}
log.info("开始测试轮询: productKey={}, deviceId={}, commands={}",
productKey, deviceId, commands.size());
log.info(
"开始测试轮询: productKey={}, deviceId={}, commands={}", productKey, deviceId, commands.size());
// 4. 立即执行轮询
pollGatewayDevice(config);
return R.ok("测试轮询已执行,共发送 " + commands.stream().filter(cmd -> Boolean.TRUE.equals(cmd.getEnabled())).count() + " 条指令");
return R.ok(
"测试轮询已执行,共发送 "
+ commands.stream().filter(cmd -> Boolean.TRUE.equals(cmd.getEnabled())).count()
+ " 条指令");
} catch (Exception e) {
log.error("测试轮询失败: productKey={}, deviceId={}", productKey, deviceId, e);
@@ -546,7 +546,8 @@ public class IoTDeviceServiceImpl extends BaseServiceImpl implements IIoTDeviceS
IoTProduct ioTProduct = ioTProductMapper.getProductByProductKey(productKey);
// 转换为UnifiedDownlinkCommand(保持所有参数)
UnifiedDownlinkCommand command = UnifiedDownlinkCommand.fromJson(JSONUtil.parseObj(downRequest));
UnifiedDownlinkCommand command =
UnifiedDownlinkCommand.fromJson(JSONUtil.parseObj(downRequest));
R result = IoTDownlFactory.getIDown(ioTProduct.getThirdPlatform()).doAction(command);
return result;
}
@@ -25,7 +25,7 @@ import org.springframework.web.bind.annotation.*;
/**
* 网关轮询配置控制器
*
*
* @author Aleo
* @date 2025-10-26
*/
@@ -34,12 +34,9 @@ import org.springframework.web.bind.annotation.*;
@Tag(name = "网关轮询管理", description = "网关云端轮询配置管理接口")
public class GatewayPollingController extends BaseController {
@Autowired
private IGatewayPollingService gatewayPollingService;
@Autowired private IGatewayPollingService gatewayPollingService;
/**
* 保存网关轮询配置
*/
/** 保存网关轮询配置 */
@Operation(summary = "保存轮询配置", description = "保存或更新网关轮询配置及指令")
@PostMapping("/save")
@PreAuthorize("@ss.hasPermi('platform:device:edit')")
@@ -47,45 +44,33 @@ public class GatewayPollingController extends BaseController {
return gatewayPollingService.savePollingConfig(dto);
}
/**
* 获取网关轮询配置
*/
/** 获取网关轮询配置 */
@Operation(summary = "获取轮询配置", description = "根据产品Key和设备ID获取轮询配置")
@GetMapping("/get/{productKey}/{deviceId}")
@PreAuthorize("@ss.hasPermi('platform:device:query')")
public R getConfig(
@Parameter(description = "产品Key", required = true)
@PathVariable String productKey,
@Parameter(description = "设备ID", required = true)
@PathVariable String deviceId) {
@Parameter(description = "产品Key", required = true) @PathVariable String productKey,
@Parameter(description = "设备ID", required = true) @PathVariable String deviceId) {
return gatewayPollingService.getPollingConfig(productKey, deviceId);
}
/**
* 删除网关轮询配置
*/
/** 删除网关轮询配置 */
@Operation(summary = "删除轮询配置", description = "删除指定设备的轮询配置")
@DeleteMapping("/delete/{productKey}/{deviceId}")
@PreAuthorize("@ss.hasPermi('platform:device:remove')")
public R deleteConfig(
@Parameter(description = "产品Key", required = true)
@PathVariable String productKey,
@Parameter(description = "设备ID", required = true)
@PathVariable String deviceId) {
@Parameter(description = "产品Key", required = true) @PathVariable String productKey,
@Parameter(description = "设备ID", required = true) @PathVariable String deviceId) {
return gatewayPollingService.deletePollingConfig(productKey, deviceId);
}
/**
* 测试轮询指令
*/
/** 测试轮询指令 */
@Operation(summary = "测试轮询指令", description = "立即执行一次轮询测试")
@PostMapping("/test/{productKey}/{deviceId}")
@PreAuthorize("@ss.hasPermi('platform:device:edit')")
public R testPolling(
@Parameter(description = "产品Key", required = true)
@PathVariable String productKey,
@Parameter(description = "设备ID", required = true)
@PathVariable String deviceId) {
@Parameter(description = "产品Key", required = true) @PathVariable String productKey,
@Parameter(description = "设备ID", required = true) @PathVariable String deviceId) {
return gatewayPollingService.testPolling(productKey, deviceId);
}
}
@@ -57,16 +57,11 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/admin/v1/protocol")
public class IoTDeviceProtocolController extends BaseController {
@Autowired
private IoTDeviceProtocolService devProtocolService;
@Resource
private IoTDeviceMessageCodecService ioTDeviceMessageCodecService;
@Resource
private ISysOssService ossService;
@Autowired private IoTDeviceProtocolService devProtocolService;
@Resource private IoTDeviceMessageCodecService ioTDeviceMessageCodecService;
@Resource private ISysOssService ossService;
/**
* 查询设备协议列表
*/
/** 查询设备协议列表 */
@GetMapping("/list")
public TableDataInfo list(IoTDeviceProtocol ioTDeviceProtocol) {
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
@@ -76,9 +71,7 @@ public class IoTDeviceProtocolController extends BaseController {
return getDataTable(list);
}
/**
* 查询设备协议列表
*/
/** 查询设备协议列表 */
@GetMapping("/list/ids")
public AjaxResult<List<String>> idList(IoTDeviceProtocol ioTDeviceProtocol) {
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
@@ -88,9 +81,7 @@ public class IoTDeviceProtocolController extends BaseController {
return AjaxResult.success(list1);
}
/**
* 导出设备协议列表
*/
/** 导出设备协议列表 */
@PostMapping("/export")
@Log(title = "导出设备协议列表", businessType = BusinessType.EXPORT)
public void export(HttpServletResponse response, IoTDeviceProtocol ioTDeviceProtocol) {
@@ -101,9 +92,7 @@ public class IoTDeviceProtocolController extends BaseController {
util.exportExcel(response, list, "设备协议数据");
}
/**
* 获取设备协议详细信息
*/
/** 获取设备协议详细信息 */
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") String id) {
if (StrUtil.isBlank(id)) {
@@ -126,9 +115,7 @@ public class IoTDeviceProtocolController extends BaseController {
return AjaxResult.success(devProductVO);
}
/**
* 新增设备协议
*/
/** 新增设备协议 */
@PostMapping
@Log(title = "新增设备协议", businessType = BusinessType.INSERT)
public AjaxResult add(@RequestBody IoTDeviceProtocolBO ioTDeviceProtocolBO) {
@@ -157,9 +144,7 @@ public class IoTDeviceProtocolController extends BaseController {
return toAjax(devProtocolService.insertDevProtocol(ioTDeviceProtocol));
}
/**
* 修改设备协议
*/
/** 修改设备协议 */
@PutMapping
@Log(title = "修改设备协议", businessType = BusinessType.UPDATE)
public AjaxResult edit(@RequestBody IoTDeviceProtocolBO ioTDeviceProtocolBO) {
@@ -167,9 +152,7 @@ public class IoTDeviceProtocolController extends BaseController {
return toAjax(devProtocolService.updateDevProtocol(ioTDeviceProtocolBO, iotUser));
}
/**
* 删除设备协议
*/
/** 删除设备协议 */
@DeleteMapping("/{ids}")
@Log(title = "删除设备协议", businessType = BusinessType.DELETE)
public AjaxResult remove(@PathVariable String[] ids) {
@@ -182,9 +165,7 @@ public class IoTDeviceProtocolController extends BaseController {
return toAjax(devProtocolService.deleteDevProtocolByIds(ids));
}
/**
* 编解码测试
*/
/** 编解码测试 */
@PostMapping("/codec")
public AjaxResult codec(@RequestBody IoTDeviceProtocolBO ioTDeviceProtocolBO) {
@@ -45,7 +45,6 @@ import cn.universal.persistence.entity.admin.SysDictData;
import cn.universal.persistence.entity.bo.IoTProductBO;
import cn.universal.persistence.entity.bo.IoTProductImportBO;
import cn.universal.persistence.entity.vo.IoTDeviceModelVO;
import cn.universal.persistence.entity.vo.IoTProductExportVO;
import cn.universal.persistence.entity.vo.IoTProductVO;
import cn.universal.persistence.mapper.IoTProductMapper;
import cn.universal.persistence.page.TableDataInfo;
@@ -573,8 +572,9 @@ public class IoTProductController extends BaseController {
/**
* 导出设备产品列表
* @deprecated 请使用 ProductImportExportController.exportProductPackages() 代替
* 该接口仅保留用于向后兼容,建议使用 /admin/v1/product/transfer/export
*
* @deprecated 请使用 ProductImportExportController.exportProductPackages() 代替 该接口仅保留用于向后兼容,建议使用
* /admin/v1/product/transfer/export
*/
@Deprecated
@PostMapping("/export")
@@ -590,17 +590,17 @@ public class IoTProductController extends BaseController {
}
// 使用新的导出包方法
List<cn.universal.persistence.entity.dto.ProductExportPackageDTO> packageList =
List<cn.universal.persistence.entity.dto.ProductExportPackageDTO> packageList =
devProductService.exportProductPackages(query);
// 设置响应头为文件下载格式
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
// 生成文件名:产品导出_时间戳.json
String fileName = String.format("products_export_%d.json", System.currentTimeMillis());
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
// 添加缓存控制头,防止浏览器缓存
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
@@ -629,8 +629,9 @@ public class IoTProductController extends BaseController {
/**
* 批量导入产品
* @deprecated 请使用 ProductImportExportController.importProductPackages() 代替
* 该接口仅保留用于向后兼容,建议使用 /admin/v1/product/transfer/import
*
* @deprecated 请使用 ProductImportExportController.importProductPackages() 代替 该接口仅保留用于向后兼容,建议使用
* /admin/v1/product/transfer/import
*/
@Deprecated
@PostMapping("/import")
@@ -645,7 +646,8 @@ public class IoTProductController extends BaseController {
// 解析JSON数据 - 尝试解析为新格式(完整包)
try {
List<cn.universal.persistence.entity.dto.ProductExportPackageDTO> packages =
JSONUtil.toList(jsonContent, cn.universal.persistence.entity.dto.ProductExportPackageDTO.class);
JSONUtil.toList(
jsonContent, cn.universal.persistence.entity.dto.ProductExportPackageDTO.class);
if (packages != null && !packages.isEmpty()) {
// 使用新的导入方法
@@ -220,9 +220,9 @@ public class IoTUserAppController extends BaseController {
@Log(title = "重置密钥", businessType = BusinessType.UPDATE)
public AjaxResult<Void> resetSecret(@RequestBody IoTUserApplication iotUserApplication) {
IoTUser user = loginIoTUnionUser(SecurityUtils.getUnionId());
// 权限检查
IoTUserApplication oldApplication =
IoTUserApplication oldApplication =
iotUserApplicationService.selectIotUserApplicationById(iotUserApplication.getAppUniqueId());
if (!user.isAdmin()
&& ObjectUtil.isNotEmpty(oldApplication)
@@ -36,9 +36,8 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* 产品导入导出控制器
* 专门处理产品的完整导出导入功能
*
* 产品导入导出控制器 专门处理产品的完整导出导入功能
*
* @author NexIoT
* @since 2025-10-27
*/
@@ -50,11 +49,7 @@ public class ProductImportExportController extends BaseController {
@Autowired private IIoTProductService devProductService;
/**
* 导出产品完整包
* 包含产品基本信息、设备协议、网络配置、物模型等所有配置
* 导出后可直接在其他环境导入使用,无需额外配置
*/
/** 导出产品完整包 包含产品基本信息、设备协议、网络配置、物模型等所有配置 导出后可直接在其他环境导入使用,无需额外配置 */
@PostMapping("/export")
@Operation(summary = "导出产品完整包", description = "导出产品及其关联的协议、网络配置、物模型等完整信息")
@Log(title = "产品完整包导出", businessType = BusinessType.EXPORT)
@@ -62,7 +57,7 @@ public class ProductImportExportController extends BaseController {
try {
// 获取当前用户信息
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
// 权限控制:非管理员只能导出自己创建的产品
query.setSelf(!iotUser.isAdmin());
if (query.isSelf()) {
@@ -78,11 +73,11 @@ public class ProductImportExportController extends BaseController {
// 设置响应头
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
// 生成文件名:产品导出包_时间戳.json
String fileName = String.format("product_packages_%d.json", System.currentTimeMillis());
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
// 缓存控制
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
@@ -100,7 +95,7 @@ public class ProductImportExportController extends BaseController {
out.write(bytes);
out.flush();
}
log.info("产品导出成功: 用户={}, 导出数量={}", iotUser.getUnionId(), packageList.size());
} catch (Exception e) {
log.error("产品导出失败", e);
@@ -108,11 +103,7 @@ public class ProductImportExportController extends BaseController {
}
}
/**
* 导入产品完整包
* 支持导入完整的产品配置,包括协议、网络、物模型等
* 自动处理ProductKey冲突、协议依赖等问题
*/
/** 导入产品完整包 支持导入完整的产品配置,包括协议、网络、物模型等 自动处理ProductKey冲突、协议依赖等问题 */
@PostMapping("/import")
@Operation(summary = "导入产品完整包", description = "导入产品完整配置包,自动处理所有依赖关系")
@Log(title = "产品完整包导入", businessType = BusinessType.IMPORT)
@@ -121,7 +112,7 @@ public class ProductImportExportController extends BaseController {
try {
// 获取当前用户ID
String unionId = loginIoTUnionUser(SecurityUtils.getUnionId()).getUnionId();
// 读取上传的JSON文件
String jsonContent = new String(file.getBytes(), "UTF-8");
@@ -135,7 +126,7 @@ public class ProductImportExportController extends BaseController {
// 执行导入
String result = devProductService.importProductPackages(packages, unionId);
log.info("产品导入完成: 用户={}, 导入数量={}", unionId, packages.size());
return AjaxResult.success(result);
} catch (Exception e) {
@@ -17,56 +17,45 @@ import org.springframework.web.bind.annotation.RestController;
/**
* 系统通知Controller
*
*
* @author universal
* @date 2023-01-01
*/
@RestController
@RequestMapping("/system/notice")
public class NoticeController extends BaseController {
@Autowired
private ISysNoticeService noticeService;
/**
* 查询系统通知列表
*/
@GetMapping("/list")
public TableDataInfo<SysNotice> list(SysNotice notice) {
startPage();
List<SysNotice> list = noticeService.selectNoticeList(notice);
return getDataTable(list);
}
@Autowired private ISysNoticeService noticeService;
/**
* 获取系统通知详细信息
*/
@GetMapping(value = "/{noticeId}")
public SysNotice getInfo(@PathVariable("noticeId") Long noticeId) {
return noticeService.selectNoticeById(noticeId);
}
/** 查询系统通知列表 */
@GetMapping("/list")
public TableDataInfo<SysNotice> list(SysNotice notice) {
startPage();
List<SysNotice> list = noticeService.selectNoticeList(notice);
return getDataTable(list);
}
/**
* 新增系统通知
*/
@PostMapping
public void add(@RequestBody SysNotice notice) {
noticeService.insertNotice(notice);
}
/** 获取系统通知详细信息 */
@GetMapping(value = "/{noticeId}")
public SysNotice getInfo(@PathVariable("noticeId") Long noticeId) {
return noticeService.selectNoticeById(noticeId);
}
/**
* 修改系统通知
*/
@PutMapping
public void edit(@RequestBody SysNotice notice) {
noticeService.updateNotice(notice);
}
/** 新增系统通知 */
@PostMapping
public void add(@RequestBody SysNotice notice) {
noticeService.insertNotice(notice);
}
/**
* 删除系统通知
*/
@DeleteMapping("/{noticeIds}")
public void remove(@PathVariable Long[] noticeIds) {
noticeService.deleteNoticeByIds(noticeIds);
}
/** 修改系统通知 */
@PutMapping
public void edit(@RequestBody SysNotice notice) {
noticeService.updateNotice(notice);
}
/** 删除系统通知 */
@DeleteMapping("/{noticeIds}")
public void remove(@PathVariable Long[] noticeIds) {
noticeService.deleteNoticeByIds(noticeIds);
}
}
@@ -80,7 +80,6 @@ public interface ISysDictDataService {
*/
Map<String, Object> selectDictDataByTypes(String[] dictType);
/**
* 根据label标签,查找白名单
*
@@ -5,56 +5,56 @@ import java.util.List;
/**
* 系统通知Service接口
*
*
* @author universal
* @date 2023-01-01
*/
public interface ISysNoticeService {
/**
* 查询系统通知
*
* @param noticeId 系统通知主键
* @return 系统通知
*/
public SysNotice selectNoticeById(Long noticeId);
/**
* 查询系统通知
*
* @param noticeId 系统通知主键
* @return 系统通知
*/
public SysNotice selectNoticeById(Long noticeId);
/**
* 查询系统通知列表
*
* @param notice 系统通知
* @return 系统通知集合
*/
public List<SysNotice> selectNoticeList(SysNotice notice);
/**
* 查询系统通知列表
*
* @param notice 系统通知
* @return 系统通知集合
*/
public List<SysNotice> selectNoticeList(SysNotice notice);
/**
* 新增系统通知
*
* @param notice 系统通知
* @return 结果
*/
public int insertNotice(SysNotice notice);
/**
* 新增系统通知
*
* @param notice 系统通知
* @return 结果
*/
public int insertNotice(SysNotice notice);
/**
* 修改系统通知
*
* @param notice 系统通知
* @return 结果
*/
public int updateNotice(SysNotice notice);
/**
* 修改系统通知
*
* @param notice 系统通知
* @return 结果
*/
public int updateNotice(SysNotice notice);
/**
* 批量删除系统通知
*
* @param noticeIds 需要删除的系统通知主键集合
* @return 结果
*/
public int deleteNoticeByIds(Long[] noticeIds);
/**
* 批量删除系统通知
*
* @param noticeIds 需要删除的系统通知主键集合
* @return 结果
*/
public int deleteNoticeByIds(Long[] noticeIds);
/**
* 删除系统通知信息
*
* @param noticeId 系统通知主键
* @return 结果
*/
public int deleteNoticeById(Long noticeId);
/**
* 删除系统通知信息
*
* @param noticeId 系统通知主键
* @return 结果
*/
public int deleteNoticeById(Long noticeId);
}
@@ -187,7 +187,6 @@ public class SysDictDataServiceImpl implements ISysDictDataService {
}
/** 新增字典类型(进销人员) */
@Override
@Cacheable(
cacheNames = "selectWhitelist",
@@ -133,7 +133,8 @@ public class RulegoChainController extends BaseController {
@Operation(summary = "获取设计器URL")
@GetMapping("/chain/{id}/designer")
public AjaxResult<String> getDesignerUrl(@PathVariable @Parameter(description = "规则链ID") Long id) {
public AjaxResult<String> getDesignerUrl(
@PathVariable @Parameter(description = "规则链ID") Long id) {
String creatorId = loginIoTUnionUser(SecurityUtils.getUnionId()).getUnionId();
RulegoChainVO rulegoChain = rulegoChainService.queryRulegoChainById(id, creatorId);
String designerUrl = rulegoChainService.getDesignerUrl(rulegoChain.getRulegoId());
@@ -87,7 +87,6 @@ public class SysDictDataController extends BaseController {
return toAjax(dictDataService.insertDictData(dict));
}
/** 修改保存字典类型 */
@PutMapping
public AjaxResult edit(@Validated @RequestBody SysDictData dict) {
@@ -1,161 +1,161 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.universal.admin.generator.mapper.GenTableColumnMapper">
<resultMap type="cn.universal.admin.generator.domain.GenTableColumn" id="GenTableColumnResult">
<id property="columnId" column="column_id"/>
<result property="tableId" column="table_id"/>
<result property="columnName" column="column_name"/>
<result property="columnComment" column="column_comment"/>
<result property="columnType" column="column_type"/>
<result property="javaType" column="java_type"/>
<result property="javaField" column="java_field"/>
<result property="isPk" column="is_pk"/>
<result property="isIncrement" column="is_increment"/>
<result property="isRequired" column="is_required"/>
<result property="isInsert" column="is_insert"/>
<result property="isEdit" column="is_edit"/>
<result property="isList" column="is_list"/>
<result property="isQuery" column="is_query"/>
<result property="queryType" column="query_type"/>
<result property="htmlType" column="html_type"/>
<result property="dictType" column="dict_type"/>
<result property="sort" column="sort"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<resultMap type="cn.universal.admin.generator.domain.GenTableColumn" id="GenTableColumnResult">
<id property="columnId" column="column_id"/>
<result property="tableId" column="table_id"/>
<result property="columnName" column="column_name"/>
<result property="columnComment" column="column_comment"/>
<result property="columnType" column="column_type"/>
<result property="javaType" column="java_type"/>
<result property="javaField" column="java_field"/>
<result property="isPk" column="is_pk"/>
<result property="isIncrement" column="is_increment"/>
<result property="isRequired" column="is_required"/>
<result property="isInsert" column="is_insert"/>
<result property="isEdit" column="is_edit"/>
<result property="isList" column="is_list"/>
<result property="isQuery" column="is_query"/>
<result property="queryType" column="query_type"/>
<result property="htmlType" column="html_type"/>
<result property="dictType" column="dict_type"/>
<result property="sort" column="sort"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="selectGenTableColumnVo">
select column_id,
table_id,
column_name,
column_comment,
column_type,
java_type,
java_field,
is_pk,
is_increment,
is_required,
is_insert,
is_edit,
is_list,
is_query,
query_type,
html_type,
dict_type,
sort,
create_by,
create_time,
update_by,
update_time
from gen_table_column
</sql>
<sql id="selectGenTableColumnVo">
select column_id,
table_id,
column_name,
column_comment,
column_type,
java_type,
java_field,
is_pk,
is_increment,
is_required,
is_insert,
is_edit,
is_list,
is_query,
query_type,
html_type,
dict_type,
sort,
create_by,
create_time,
update_by,
update_time
from gen_table_column
</sql>
<select id="selectGenTableColumnListByTableId" parameterType="Long"
resultMap="GenTableColumnResult">
<include refid="selectGenTableColumnVo"/>
where table_id = #{tableId}
order by sort
</select>
<select id="selectGenTableColumnListByTableId" parameterType="Long"
resultMap="GenTableColumnResult">
<include refid="selectGenTableColumnVo"/>
where table_id = #{tableId}
order by sort
</select>
<select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult">
select column_name,
(case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else null end) as is_required,
(case when column_key = 'PRI' then '1' else '0' end) as is_pk,
ordinal_position as sort,
column_comment,
(case when extra = 'auto_increment' then '1' else '0' end) as is_increment,
column_type
from information_schema.columns
where table_schema = (select database())
and table_name = (#{tableName})
order by ordinal_position
</select>
<select id="selectDbTableColumnsByName" parameterType="String" resultMap="GenTableColumnResult">
select column_name,
(case when (is_nullable = 'no' <![CDATA[ && ]]> column_key != 'PRI') then '1' else null end) as is_required,
(case when column_key = 'PRI' then '1' else '0' end) as is_pk,
ordinal_position as sort,
column_comment,
(case when extra = 'auto_increment' then '1' else '0' end) as is_increment,
column_type
from information_schema.columns
where table_schema = (select database())
and table_name = (#{tableName})
order by ordinal_position
</select>
<insert id="insertGenTableColumn"
parameterType="cn.universal.admin.generator.domain.GenTableColumn" useGeneratedKeys="true"
keyProperty="columnId">
insert into gen_table_column (
<if test="tableId != null and tableId != ''">table_id,</if>
<if test="columnName != null and columnName != ''">column_name,</if>
<if test="columnComment != null and columnComment != ''">column_comment,</if>
<if test="columnType != null and columnType != ''">column_type,</if>
<if test="javaType != null and javaType != ''">java_type,</if>
<if test="javaField != null and javaField != ''">java_field,</if>
<if test="isPk != null and isPk != ''">is_pk,</if>
<if test="isIncrement != null and isIncrement != ''">is_increment,</if>
<if test="isRequired != null and isRequired != ''">is_required,</if>
<if test="isInsert != null and isInsert != ''">is_insert,</if>
<if test="isEdit != null and isEdit != ''">is_edit,</if>
<if test="isList != null and isList != ''">is_list,</if>
<if test="isQuery != null and isQuery != ''">is_query,</if>
<if test="queryType != null and queryType != ''">query_type,</if>
<if test="htmlType != null and htmlType != ''">html_type,</if>
<if test="dictType != null and dictType != ''">dict_type,</if>
<if test="sort != null">sort,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
create_time
)values(
<if test="tableId != null and tableId != ''">#{tableId},</if>
<if test="columnName != null and columnName != ''">#{columnName},</if>
<if test="columnComment != null and columnComment != ''">#{columnComment},</if>
<if test="columnType != null and columnType != ''">#{columnType},</if>
<if test="javaType != null and javaType != ''">#{javaType},</if>
<if test="javaField != null and javaField != ''">#{javaField},</if>
<if test="isPk != null and isPk != ''">#{isPk},</if>
<if test="isIncrement != null and isIncrement != ''">#{isIncrement},</if>
<if test="isRequired != null and isRequired != ''">#{isRequired},</if>
<if test="isInsert != null and isInsert != ''">#{isInsert},</if>
<if test="isEdit != null and isEdit != ''">#{isEdit},</if>
<if test="isList != null and isList != ''">#{isList},</if>
<if test="isQuery != null and isQuery != ''">#{isQuery},</if>
<if test="queryType != null and queryType != ''">#{queryType},</if>
<if test="htmlType != null and htmlType != ''">#{htmlType},</if>
<if test="dictType != null and dictType != ''">#{dictType},</if>
<if test="sort != null">#{sort},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
sysdate()
)
</insert>
<insert id="insertGenTableColumn"
parameterType="cn.universal.admin.generator.domain.GenTableColumn" useGeneratedKeys="true"
keyProperty="columnId">
insert into gen_table_column (
<if test="tableId != null and tableId != ''">table_id,</if>
<if test="columnName != null and columnName != ''">column_name,</if>
<if test="columnComment != null and columnComment != ''">column_comment,</if>
<if test="columnType != null and columnType != ''">column_type,</if>
<if test="javaType != null and javaType != ''">java_type,</if>
<if test="javaField != null and javaField != ''">java_field,</if>
<if test="isPk != null and isPk != ''">is_pk,</if>
<if test="isIncrement != null and isIncrement != ''">is_increment,</if>
<if test="isRequired != null and isRequired != ''">is_required,</if>
<if test="isInsert != null and isInsert != ''">is_insert,</if>
<if test="isEdit != null and isEdit != ''">is_edit,</if>
<if test="isList != null and isList != ''">is_list,</if>
<if test="isQuery != null and isQuery != ''">is_query,</if>
<if test="queryType != null and queryType != ''">query_type,</if>
<if test="htmlType != null and htmlType != ''">html_type,</if>
<if test="dictType != null and dictType != ''">dict_type,</if>
<if test="sort != null">sort,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
create_time
)values(
<if test="tableId != null and tableId != ''">#{tableId},</if>
<if test="columnName != null and columnName != ''">#{columnName},</if>
<if test="columnComment != null and columnComment != ''">#{columnComment},</if>
<if test="columnType != null and columnType != ''">#{columnType},</if>
<if test="javaType != null and javaType != ''">#{javaType},</if>
<if test="javaField != null and javaField != ''">#{javaField},</if>
<if test="isPk != null and isPk != ''">#{isPk},</if>
<if test="isIncrement != null and isIncrement != ''">#{isIncrement},</if>
<if test="isRequired != null and isRequired != ''">#{isRequired},</if>
<if test="isInsert != null and isInsert != ''">#{isInsert},</if>
<if test="isEdit != null and isEdit != ''">#{isEdit},</if>
<if test="isList != null and isList != ''">#{isList},</if>
<if test="isQuery != null and isQuery != ''">#{isQuery},</if>
<if test="queryType != null and queryType != ''">#{queryType},</if>
<if test="htmlType != null and htmlType != ''">#{htmlType},</if>
<if test="dictType != null and dictType != ''">#{dictType},</if>
<if test="sort != null">#{sort},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
sysdate()
)
</insert>
<update id="updateGenTableColumn"
parameterType="cn.universal.admin.generator.domain.GenTableColumn">
update gen_table_column
<set>
column_comment = #{columnComment},
java_type = #{javaType},
java_field = #{javaField},
is_insert = #{isInsert},
is_edit = #{isEdit},
is_list = #{isList},
is_query = #{isQuery},
is_required = #{isRequired},
query_type = #{queryType},
html_type = #{htmlType},
dict_type = #{dictType},
sort = #{sort},
update_by = #{updateBy},
update_time = sysdate()
</set>
where column_id = #{columnId}
</update>
<update id="updateGenTableColumn"
parameterType="cn.universal.admin.generator.domain.GenTableColumn">
update gen_table_column
<set>
column_comment = #{columnComment},
java_type = #{javaType},
java_field = #{javaField},
is_insert = #{isInsert},
is_edit = #{isEdit},
is_list = #{isList},
is_query = #{isQuery},
is_required = #{isRequired},
query_type = #{queryType},
html_type = #{htmlType},
dict_type = #{dictType},
sort = #{sort},
update_by = #{updateBy},
update_time = sysdate()
</set>
where column_id = #{columnId}
</update>
<delete id="deleteGenTableColumnByIds" parameterType="Long">
delete from gen_table_column where table_id in
<foreach collection="array" item="tableId" open="(" separator="," close=")">
#{tableId}
</foreach>
</delete>
<delete id="deleteGenTableColumnByIds" parameterType="Long">
delete from gen_table_column where table_id in
<foreach collection="array" item="tableId" open="(" separator="," close=")">
#{tableId}
</foreach>
</delete>
<delete id="deleteGenTableColumns">
delete from gen_table_column where column_id in
<foreach collection="list" item="item" open="(" separator="," close=")">
#{item.columnId}
</foreach>
</delete>
<delete id="deleteGenTableColumns">
delete from gen_table_column where column_id in
<foreach collection="list" item="item" open="(" separator="," close=")">
#{item.columnId}
</foreach>
</delete>
</mapper>
@@ -1,323 +1,323 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.universal.admin.generator.mapper.GenTableMapper">
<resultMap type="cn.universal.admin.generator.domain.GenTable" id="GenTableResult">
<id property="tableId" column="table_id"/>
<result property="tableName" column="table_name"/>
<result property="tableComment" column="table_comment"/>
<result property="subTableName" column="sub_table_name"/>
<result property="subTableFkName" column="sub_table_fk_name"/>
<result property="className" column="class_name"/>
<result property="tplCategory" column="tpl_category"/>
<result property="packageName" column="package_name"/>
<result property="moduleName" column="module_name"/>
<result property="businessName" column="business_name"/>
<result property="functionName" column="function_name"/>
<result property="functionAuthor" column="function_Author"/>
<result property="genType" column="gen_type"/>
<result property="genPath" column="gen_path"/>
<result property="options" column="options"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
<collection property="columns" javaType="java.util.List" resultMap="GenTableColumnResult"/>
</resultMap>
<resultMap type="cn.universal.admin.generator.domain.GenTable" id="GenTableResult">
<id property="tableId" column="table_id"/>
<result property="tableName" column="table_name"/>
<result property="tableComment" column="table_comment"/>
<result property="subTableName" column="sub_table_name"/>
<result property="subTableFkName" column="sub_table_fk_name"/>
<result property="className" column="class_name"/>
<result property="tplCategory" column="tpl_category"/>
<result property="packageName" column="package_name"/>
<result property="moduleName" column="module_name"/>
<result property="businessName" column="business_name"/>
<result property="functionName" column="function_name"/>
<result property="functionAuthor" column="function_Author"/>
<result property="genType" column="gen_type"/>
<result property="genPath" column="gen_path"/>
<result property="options" column="options"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
<collection property="columns" javaType="java.util.List" resultMap="GenTableColumnResult"/>
</resultMap>
<resultMap type="cn.universal.admin.generator.domain.GenTableColumn" id="GenTableColumnResult">
<id property="columnId" column="column_id"/>
<result property="tableId" column="table_id"/>
<result property="columnName" column="column_name"/>
<result property="columnComment" column="column_comment"/>
<result property="columnType" column="column_type"/>
<result property="javaType" column="java_type"/>
<result property="javaField" column="java_field"/>
<result property="isPk" column="is_pk"/>
<result property="isIncrement" column="is_increment"/>
<result property="isRequired" column="is_required"/>
<result property="isInsert" column="is_insert"/>
<result property="isEdit" column="is_edit"/>
<result property="isList" column="is_list"/>
<result property="isQuery" column="is_query"/>
<result property="queryType" column="query_type"/>
<result property="htmlType" column="html_type"/>
<result property="dictType" column="dict_type"/>
<result property="sort" column="sort"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<resultMap type="cn.universal.admin.generator.domain.GenTableColumn" id="GenTableColumnResult">
<id property="columnId" column="column_id"/>
<result property="tableId" column="table_id"/>
<result property="columnName" column="column_name"/>
<result property="columnComment" column="column_comment"/>
<result property="columnType" column="column_type"/>
<result property="javaType" column="java_type"/>
<result property="javaField" column="java_field"/>
<result property="isPk" column="is_pk"/>
<result property="isIncrement" column="is_increment"/>
<result property="isRequired" column="is_required"/>
<result property="isInsert" column="is_insert"/>
<result property="isEdit" column="is_edit"/>
<result property="isList" column="is_list"/>
<result property="isQuery" column="is_query"/>
<result property="queryType" column="query_type"/>
<result property="htmlType" column="html_type"/>
<result property="dictType" column="dict_type"/>
<result property="sort" column="sort"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="selectGenTableVo">
select table_id,
table_name,
table_comment,
sub_table_name,
sub_table_fk_name,
class_name,
tpl_category,
package_name,
module_name,
business_name,
function_name,
function_Author,
gen_type,
gen_path,
options,
create_by,
create_time,
update_by,
update_time,
remark
from gen_table
</sql>
<sql id="selectGenTableVo">
select table_id,
table_name,
table_comment,
sub_table_name,
sub_table_fk_name,
class_name,
tpl_category,
package_name,
module_name,
business_name,
function_name,
function_Author,
gen_type,
gen_path,
options,
create_by,
create_time,
update_by,
update_time,
remark
from gen_table
</sql>
<select id="selectGenTableList" parameterType="cn.universal.admin.generator.domain.GenTable"
resultMap="GenTableResult">
<include refid="selectGenTableVo"/>
<where>
<if test="tableName != null and tableName != ''">
AND lower(table_name) like lower(concat('%', #{tableName}, '%'))
</if>
<if test="tableComment != null and tableComment != ''">
AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))
</if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
AND date_format(create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
AND date_format(create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
</if>
</where>
</select>
<select id="selectGenTableList" parameterType="cn.universal.admin.generator.domain.GenTable"
resultMap="GenTableResult">
<include refid="selectGenTableVo"/>
<where>
<if test="tableName != null and tableName != ''">
AND lower(table_name) like lower(concat('%', #{tableName}, '%'))
</if>
<if test="tableComment != null and tableComment != ''">
AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))
</if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
AND date_format(create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
AND date_format(create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
</if>
</where>
</select>
<select id="selectDbTableList" parameterType="cn.universal.admin.generator.domain.GenTable"
resultMap="GenTableResult">
select table_name, table_comment, create_time, update_time from information_schema.tables
where table_schema = (select database())
AND table_name NOT LIKE 'qrtz_%' AND table_name NOT LIKE 'gen_%'
AND table_name NOT IN (select table_name from gen_table)
<if test="tableName != null and tableName != ''">
AND lower(table_name) like lower(concat('%', #{tableName}, '%'))
</if>
<if test="tableComment != null and tableComment != ''">
AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))
</if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
AND date_format(create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
AND date_format(create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
</if>
order by create_time desc
</select>
<select id="selectDbTableList" parameterType="cn.universal.admin.generator.domain.GenTable"
resultMap="GenTableResult">
select table_name, table_comment, create_time, update_time from information_schema.tables
where table_schema = (select database())
AND table_name NOT LIKE 'qrtz_%' AND table_name NOT LIKE 'gen_%'
AND table_name NOT IN (select table_name from gen_table)
<if test="tableName != null and tableName != ''">
AND lower(table_name) like lower(concat('%', #{tableName}, '%'))
</if>
<if test="tableComment != null and tableComment != ''">
AND lower(table_comment) like lower(concat('%', #{tableComment}, '%'))
</if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
AND date_format(create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
AND date_format(create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
</if>
order by create_time desc
</select>
<select id="selectDbTableListByNames" resultMap="GenTableResult">
select table_name, table_comment, create_time, update_time from information_schema.tables
where table_name NOT LIKE 'qrtz_%' and table_name NOT LIKE 'gen_%' and table_schema = (select
database())
and table_name in
<foreach collection="array" item="name" open="(" separator="," close=")">
#{name}
</foreach>
</select>
<select id="selectDbTableListByNames" resultMap="GenTableResult">
select table_name, table_comment, create_time, update_time from information_schema.tables
where table_name NOT LIKE 'qrtz_%' and table_name NOT LIKE 'gen_%' and table_schema = (select
database())
and table_name in
<foreach collection="array" item="name" open="(" separator="," close=")">
#{name}
</foreach>
</select>
<select id="selectTableByName" parameterType="String" resultMap="GenTableResult">
select table_name, table_comment, create_time, update_time
from information_schema.tables
where table_comment <![CDATA[ <> ]]> ''
and table_schema = (select database())
and table_name = #{tableName}
</select>
<select id="selectTableByName" parameterType="String" resultMap="GenTableResult">
select table_name, table_comment, create_time, update_time
from information_schema.tables
where table_comment <![CDATA[ <> ]]> ''
and table_schema = (select database())
and table_name = #{tableName}
</select>
<select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">
SELECT t.table_id,
t.table_name,
t.table_comment,
t.sub_table_name,
t.sub_table_fk_name,
t.class_name,
t.tpl_category,
t.package_name,
t.module_name,
t.business_name,
t.function_name,
t.function_Author,
t.gen_type,
t.gen_path,
t.options,
t.remark,
c.column_id,
c.column_name,
c.column_comment,
c.column_type,
c.java_type,
c.java_field,
c.is_pk,
c.is_increment,
c.is_required,
c.is_insert,
c.is_edit,
c.is_list,
c.is_query,
c.query_type,
c.html_type,
c.dict_type,
c.sort
FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id
where t.table_id = #{tableId}
order by c.sort
</select>
<select id="selectGenTableById" parameterType="Long" resultMap="GenTableResult">
SELECT t.table_id,
t.table_name,
t.table_comment,
t.sub_table_name,
t.sub_table_fk_name,
t.class_name,
t.tpl_category,
t.package_name,
t.module_name,
t.business_name,
t.function_name,
t.function_Author,
t.gen_type,
t.gen_path,
t.options,
t.remark,
c.column_id,
c.column_name,
c.column_comment,
c.column_type,
c.java_type,
c.java_field,
c.is_pk,
c.is_increment,
c.is_required,
c.is_insert,
c.is_edit,
c.is_list,
c.is_query,
c.query_type,
c.html_type,
c.dict_type,
c.sort
FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id
where t.table_id = #{tableId}
order by c.sort
</select>
<select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult">
SELECT t.table_id,
t.table_name,
t.table_comment,
t.sub_table_name,
t.sub_table_fk_name,
t.class_name,
t.tpl_category,
t.package_name,
t.module_name,
t.business_name,
t.function_name,
t.function_Author,
t.gen_type,
t.gen_path,
t.options,
t.remark,
c.column_id,
c.column_name,
c.column_comment,
c.column_type,
c.java_type,
c.java_field,
c.is_pk,
c.is_increment,
c.is_required,
c.is_insert,
c.is_edit,
c.is_list,
c.is_query,
c.query_type,
c.html_type,
c.dict_type,
c.sort
FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id
where t.table_name = #{tableName}
order by c.sort
</select>
<select id="selectGenTableByName" parameterType="String" resultMap="GenTableResult">
SELECT t.table_id,
t.table_name,
t.table_comment,
t.sub_table_name,
t.sub_table_fk_name,
t.class_name,
t.tpl_category,
t.package_name,
t.module_name,
t.business_name,
t.function_name,
t.function_Author,
t.gen_type,
t.gen_path,
t.options,
t.remark,
c.column_id,
c.column_name,
c.column_comment,
c.column_type,
c.java_type,
c.java_field,
c.is_pk,
c.is_increment,
c.is_required,
c.is_insert,
c.is_edit,
c.is_list,
c.is_query,
c.query_type,
c.html_type,
c.dict_type,
c.sort
FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id
where t.table_name = #{tableName}
order by c.sort
</select>
<select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult">
SELECT t.table_id,
t.table_name,
t.table_comment,
t.sub_table_name,
t.sub_table_fk_name,
t.class_name,
t.tpl_category,
t.package_name,
t.module_name,
t.business_name,
t.function_name,
t.function_Author,
t.options,
t.remark,
c.column_id,
c.column_name,
c.column_comment,
c.column_type,
c.java_type,
c.java_field,
c.is_pk,
c.is_increment,
c.is_required,
c.is_insert,
c.is_edit,
c.is_list,
c.is_query,
c.query_type,
c.html_type,
c.dict_type,
c.sort
FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id
order by c.sort
</select>
<select id="selectGenTableAll" parameterType="String" resultMap="GenTableResult">
SELECT t.table_id,
t.table_name,
t.table_comment,
t.sub_table_name,
t.sub_table_fk_name,
t.class_name,
t.tpl_category,
t.package_name,
t.module_name,
t.business_name,
t.function_name,
t.function_Author,
t.options,
t.remark,
c.column_id,
c.column_name,
c.column_comment,
c.column_type,
c.java_type,
c.java_field,
c.is_pk,
c.is_increment,
c.is_required,
c.is_insert,
c.is_edit,
c.is_list,
c.is_query,
c.query_type,
c.html_type,
c.dict_type,
c.sort
FROM gen_table t
LEFT JOIN gen_table_column c ON t.table_id = c.table_id
order by c.sort
</select>
<insert id="insertGenTable" parameterType="cn.universal.admin.generator.domain.GenTable"
useGeneratedKeys="true" keyProperty="tableId">
insert into gen_table (
<if test="tableName != null">table_name,</if>
<if test="tableComment != null and tableComment != ''">table_comment,</if>
<if test="className != null and className != ''">class_name,</if>
<if test="tplCategory != null and tplCategory != ''">tpl_category,</if>
<if test="packageName != null and packageName != ''">package_name,</if>
<if test="moduleName != null and moduleName != ''">module_name,</if>
<if test="businessName != null and businessName != ''">business_name,</if>
<if test="functionName != null and functionName != ''">function_name,</if>
<if test="functionAuthor != null and functionAuthor != ''">function_Author,</if>
<if test="genType != null and genType != ''">gen_type,</if>
<if test="genPath != null and genPath != ''">gen_path,</if>
<if test="remark != null and remark != ''">remark,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
create_time
)values(
<if test="tableName != null">#{tableName},</if>
<if test="tableComment != null and tableComment != ''">#{tableComment},</if>
<if test="className != null and className != ''">#{className},</if>
<if test="tplCategory != null and tplCategory != ''">#{tplCategory},</if>
<if test="packageName != null and packageName != ''">#{packageName},</if>
<if test="moduleName != null and moduleName != ''">#{moduleName},</if>
<if test="businessName != null and businessName != ''">#{businessName},</if>
<if test="functionName != null and functionName != ''">#{functionName},</if>
<if test="functionAuthor != null and functionAuthor != ''">#{functionAuthor},</if>
<if test="genType != null and genType != ''">#{genType},</if>
<if test="genPath != null and genPath != ''">#{genPath},</if>
<if test="remark != null and remark != ''">#{remark},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
sysdate()
)
</insert>
<insert id="insertGenTable" parameterType="cn.universal.admin.generator.domain.GenTable"
useGeneratedKeys="true" keyProperty="tableId">
insert into gen_table (
<if test="tableName != null">table_name,</if>
<if test="tableComment != null and tableComment != ''">table_comment,</if>
<if test="className != null and className != ''">class_name,</if>
<if test="tplCategory != null and tplCategory != ''">tpl_category,</if>
<if test="packageName != null and packageName != ''">package_name,</if>
<if test="moduleName != null and moduleName != ''">module_name,</if>
<if test="businessName != null and businessName != ''">business_name,</if>
<if test="functionName != null and functionName != ''">function_name,</if>
<if test="functionAuthor != null and functionAuthor != ''">function_Author,</if>
<if test="genType != null and genType != ''">gen_type,</if>
<if test="genPath != null and genPath != ''">gen_path,</if>
<if test="remark != null and remark != ''">remark,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
create_time
)values(
<if test="tableName != null">#{tableName},</if>
<if test="tableComment != null and tableComment != ''">#{tableComment},</if>
<if test="className != null and className != ''">#{className},</if>
<if test="tplCategory != null and tplCategory != ''">#{tplCategory},</if>
<if test="packageName != null and packageName != ''">#{packageName},</if>
<if test="moduleName != null and moduleName != ''">#{moduleName},</if>
<if test="businessName != null and businessName != ''">#{businessName},</if>
<if test="functionName != null and functionName != ''">#{functionName},</if>
<if test="functionAuthor != null and functionAuthor != ''">#{functionAuthor},</if>
<if test="genType != null and genType != ''">#{genType},</if>
<if test="genPath != null and genPath != ''">#{genPath},</if>
<if test="remark != null and remark != ''">#{remark},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
sysdate()
)
</insert>
<update id="updateGenTable" parameterType="cn.universal.admin.generator.domain.GenTable">
update gen_table
<set>
<if test="tableName != null">table_name = #{tableName},</if>
<if test="tableComment != null and tableComment != ''">table_comment = #{tableComment},</if>
<if test="subTableName != null">sub_table_name = #{subTableName},</if>
<if test="subTableFkName != null">sub_table_fk_name = #{subTableFkName},</if>
<if test="className != null and className != ''">class_name = #{className},</if>
<if test="functionAuthor != null and functionAuthor != ''">function_Author =
#{functionAuthor},
</if>
<if test="genType != null and genType != ''">gen_type = #{genType},</if>
<if test="genPath != null and genPath != ''">gen_path = #{genPath},</if>
<if test="tplCategory != null and tplCategory != ''">tpl_category = #{tplCategory},</if>
<if test="packageName != null and packageName != ''">package_name = #{packageName},</if>
<if test="moduleName != null and moduleName != ''">module_name = #{moduleName},</if>
<if test="businessName != null and businessName != ''">business_name = #{businessName},</if>
<if test="functionName != null and functionName != ''">function_name = #{functionName},</if>
<if test="options != null and options != ''">options = #{options},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
<if test="remark != null">remark = #{remark},</if>
update_time = sysdate()
</set>
where table_id = #{tableId}
</update>
<update id="updateGenTable" parameterType="cn.universal.admin.generator.domain.GenTable">
update gen_table
<set>
<if test="tableName != null">table_name = #{tableName},</if>
<if test="tableComment != null and tableComment != ''">table_comment = #{tableComment},</if>
<if test="subTableName != null">sub_table_name = #{subTableName},</if>
<if test="subTableFkName != null">sub_table_fk_name = #{subTableFkName},</if>
<if test="className != null and className != ''">class_name = #{className},</if>
<if test="functionAuthor != null and functionAuthor != ''">function_Author =
#{functionAuthor},
</if>
<if test="genType != null and genType != ''">gen_type = #{genType},</if>
<if test="genPath != null and genPath != ''">gen_path = #{genPath},</if>
<if test="tplCategory != null and tplCategory != ''">tpl_category = #{tplCategory},</if>
<if test="packageName != null and packageName != ''">package_name = #{packageName},</if>
<if test="moduleName != null and moduleName != ''">module_name = #{moduleName},</if>
<if test="businessName != null and businessName != ''">business_name = #{businessName},</if>
<if test="functionName != null and functionName != ''">function_name = #{functionName},</if>
<if test="options != null and options != ''">options = #{options},</if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
<if test="remark != null">remark = #{remark},</if>
update_time = sysdate()
</set>
where table_id = #{tableId}
</update>
<delete id="deleteGenTableByIds" parameterType="Long">
delete from gen_table where table_id in
<foreach collection="array" item="tableId" open="(" separator="," close=")">
#{tableId}
</foreach>
</delete>
<delete id="deleteGenTableByIds" parameterType="Long">
delete from gen_table where table_id in
<foreach collection="array" item="tableId" open="(" separator="," close=")">
#{tableId}
</foreach>
</delete>
</mapper>
@@ -29,79 +29,79 @@ import io.swagger.annotations.Api;
* @date ${datetime}
*/
@RestController
@Api(value = "${functionName}管理" , tags = {"${functionName}管理" })
@RequestMapping("/admin/${moduleName}/${businessName}" )
@Api(value = "${functionName}管理", tags = {"${functionName}管理"})
@RequestMapping("/admin/${moduleName}/${businessName}")
public class ${ClassName}Controller extends BaseController {
@Autowired
private I${ClassName}Service ${className}Service;
@Autowired
private I${ClassName}Service ${className}Service;
/**
* 查询${functionName}列表
*/
@ApiOperation("查询${functionName}列表" )
@GetMapping("/list" )
@ApiOperation("查询${functionName}列表")
@GetMapping("/list")
#if($table.crud || $table.sub)
public TableDataInfo list(${ClassName} ${className}) {
startPage();
List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
return getDataTable(list);
startPage();
List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
return getDataTable(list);
}
#elseif($table.tree)
public AjaxResult list(${ClassName} ${className}) {
List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
return AjaxResult.success(list);
}
public AjaxResult list(${ClassName} ${className}) {
List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
return AjaxResult.success(list);
}
#end
/**
* 导出${functionName}列表
*/
@ApiOperation("导出${functionName}列表" )
@PostMapping("/export" )
public void export(HttpServletResponse response, ${ClassName} ${className}) {
List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}. class);
util.exportExcel(response, list, "${functionName}数据" );
}
/**
* 导出${functionName}列表
*/
@ApiOperation("导出${functionName}列表")
@PostMapping("/export")
public void export(HttpServletResponse response, ${ClassName} ${className}) {
List<${ClassName}> list = ${className}Service.select${ClassName}List(${className});
ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}. class);
util.exportExcel(response, list, "${functionName}数据");
}
/**
* 获取${functionName}详细信息
*/
@ApiOperation("获取${functionName}详细信息" )
@GetMapping(value = "/{${pkColumn.javaField}}" )
public AjaxResult<${ClassName}> getInfo(
@PathVariable("${pkColumn.javaField}" ) ${pkColumn.javaType} ${pkColumn.javaField}) {
return AjaxResult.success(${className}Service.select${ClassName}ById(${pkColumn.javaField}));
}
/**
* 获取${functionName}详细信息
*/
@ApiOperation("获取${functionName}详细信息")
@GetMapping(value = "/{${pkColumn.javaField}}")
public AjaxResult<${ClassName}> getInfo(
@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) {
return AjaxResult.success(${className}Service.select${ClassName}ById(${pkColumn.javaField}));
}
/**
* 新增${functionName}
*/
@ApiOperation("新增${functionName}" )
@PostMapping
public AjaxResult<Void> add(@RequestBody ${ClassName} ${className}) {
${className}Service.insert${ClassName}(${className});
return AjaxResult.success();
}
/**
* 新增${functionName}
*/
@ApiOperation("新增${functionName}")
@PostMapping
public AjaxResult<Void> add(@RequestBody ${ClassName} ${className}) {
${className}Service.insert${ClassName}(${className});
return AjaxResult.success();
}
/**
* 修改${functionName}
*/
@ApiOperation("修改${functionName}" )
@PutMapping
public AjaxResult<Void> edit(@RequestBody ${ClassName} ${className}) {
${className}Service.update${ClassName}(${className});
return AjaxResult.success();
}
/**
* 修改${functionName}
*/
@ApiOperation("修改${functionName}")
@PutMapping
public AjaxResult<Void> edit(@RequestBody ${ClassName} ${className}) {
${className}Service.update${ClassName}(${className});
return AjaxResult.success();
}
/**
* 删除${functionName}
*/
@ApiOperation("删除${functionName}" )
@DeleteMapping("/{${pkColumn.javaField}s}" )
public AjaxResult<Void> remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) {
${className}Service.delete${ClassName}ByIds(${pkColumn.javaField}s);
return AjaxResult.success();
}
/**
* 删除${functionName}
*/
@ApiOperation("删除${functionName}")
@DeleteMapping("/{${pkColumn.javaField}s}")
public AjaxResult<Void> remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) {
${className}Service.delete${ClassName}ByIds(${pkColumn.javaField}s);
return AjaxResult.success();
}
}
@@ -26,38 +26,38 @@ import ${import};
#elseif($table.tree)
#set($Entity="TreeEntity")
#end
@Table(name = "${tableName}" )
@Data
public class ${ClassName} extends
@Table(name = "${tableName}")
@Data
public class ${ClassName} extends
BaseEntity {
private static final long serialVersionUID = 1L;
BaseEntity {
private static final long serialVersionUID = 1L;
#foreach ($column in $columns)
#if(!$column.superColumn)
/** $column.columnComment */
#if($column.list)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($parentheseIndex != -1)
@Excel(name = "${comment}" , readConverterExp = "$column.readConverterExp()" )
#elseif($column.javaType == 'Date')
@Excel(name = "${comment}" , width = 30, dateFormat = "yyyy-MM-dd" )
#else
@Excel(name = "${comment}" )
#end
#end
#if($column.isPk==1)
@Id
#foreach ($column in $columns)
#if(!$column.superColumn)
/** $column.columnComment */
#if($column.list)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
@Column(name = "${column.columnName}" )
#set($comment=$column.columnComment)
#end
#if($parentheseIndex != -1)
@Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
#elseif($column.javaType == 'Date')
@Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd")
#else
@Excel(name = "${comment}")
#end
private $column.javaType $column.javaField;
#end
#if($column.isPk==1)
@Id
#else
@Column(name = "${column.columnName}")
#end
private $column.javaType $column.javaField;
#end
}
#end
}
@@ -13,51 +13,51 @@ import cn.universal.persistence.common.BaseMapper;
*/
public interface ${ClassName}Mapper extends BaseMapper<${ClassName}> {
/**
* 查询${functionName}
*
* @param ${pkColumn.javaField} ${functionName}ID
* @return ${functionName}
*/
${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField});
/**
* 查询${functionName}
*
* @param ${pkColumn.javaField} ${functionName}ID
* @return ${functionName}
*/
${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField});
/**
* 查询${functionName}列表
*
* @param ${className} ${functionName}
* @return ${functionName}集合
*/
List<${ClassName}> select${ClassName}List(${ClassName} ${className});
/**
* 查询${functionName}列表
*
* @param ${className} ${functionName}
* @return ${functionName}集合
*/
List<${ClassName}> select${ClassName}List(${ClassName} ${className});
/**
* 新增${functionName}
*
* @param ${className} ${functionName}
* @return 结果
*/
int insert${ClassName}(${ClassName} ${className});
/**
* 新增${functionName}
*
* @param ${className} ${functionName}
* @return 结果
*/
int insert${ClassName}(${ClassName} ${className});
/**
* 修改${functionName}
*
* @param ${className} ${functionName}
* @return 结果
*/
int update${ClassName}(${ClassName} ${className});
/**
* 修改${functionName}
*
* @param ${className} ${functionName}
* @return 结果
*/
int update${ClassName}(${ClassName} ${className});
/**
* 删除${functionName}
*
* @param ${pkColumn.javaField} ${functionName}ID
* @return 结果
*/
int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField});
/**
* 删除${functionName}
*
* @param ${pkColumn.javaField} ${functionName}ID
* @return 结果
*/
int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField});
/**
* 批量删除${functionName}
*
* @param ${pkColumn.javaField}s 需要删除的数据ID
* @return 结果
*/
int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s);
/**
* 批量删除${functionName}
*
* @param ${pkColumn.javaField}s 需要删除的数据ID
* @return 结果
*/
int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s);
}
@@ -14,60 +14,60 @@ import com.zebra.common.core.domain.Ztree;
*/
public interface I${ClassName}Service {
/**
* 查询${functionName}
*
* @param ${pkColumn.javaField} ${functionName}ID
* @return ${functionName}
*/
${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField});
/**
* 查询${functionName}
*
* @param ${pkColumn.javaField} ${functionName}ID
* @return ${functionName}
*/
${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField});
/**
* 查询${functionName}列表
*
* @param ${className} ${functionName}
* @return ${functionName}集合
*/
List<${ClassName}> select${ClassName}List(${ClassName} ${className});
/**
* 查询${functionName}列表
*
* @param ${className} ${functionName}
* @return ${functionName}集合
*/
List<${ClassName}> select${ClassName}List(${ClassName} ${className});
/**
* 新增${functionName}
*
* @param ${className} ${functionName}
* @return 结果
*/
int insert${ClassName}(${ClassName} ${className});
/**
* 新增${functionName}
*
* @param ${className} ${functionName}
* @return 结果
*/
int insert${ClassName}(${ClassName} ${className});
/**
* 修改${functionName}
*
* @param ${className} ${functionName}
* @return 结果
*/
int update${ClassName}(${ClassName} ${className});
/**
* 修改${functionName}
*
* @param ${className} ${functionName}
* @return 结果
*/
int update${ClassName}(${ClassName} ${className});
/**
* 批量删除${functionName}
*
* @param ${pkColumn.javaType}[] ${pkColumn.javaField}s 需要删除的数据ID
* @return 结果
*/
int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s);
/**
* 批量删除${functionName}
*
* @param ${pkColumn.javaType}[] ${pkColumn.javaField}s 需要删除的数据ID
* @return 结果
*/
int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s);
/**
* 删除${functionName}信息
*
* @param ${pkColumn.javaField} ${functionName}ID
* @return 结果
*/
int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField});
/**
* 删除${functionName}信息
*
* @param ${pkColumn.javaField} ${functionName}ID
* @return 结果
*/
int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField});
#if($table.tree)
/**
* 查询${functionName}树列表
*
* @return 所有${functionName}信息
*/
List<Ztree> select${ClassName}Tree();
/**
* 查询${functionName}树列表
*
* @return 所有${functionName}信息
*/
List<Ztree> select${ClassName}Tree();
#end
}
@@ -25,118 +25,118 @@ import ${packageName}.service.I${ClassName}Service;
@Service
public class ${ClassName}ServiceImpl implements I${ClassName}Service {
@Resource
private ${ClassName}Mapper ${className}Mapper;
@Resource
private ${ClassName}Mapper ${className}Mapper;
/**
* 查询${functionName}
*
* @param ${pkColumn.javaField} ${functionName}ID
* @return ${functionName}
*/
@Override
public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}) {
return ${className}Mapper.select${ClassName}ById(${pkColumn.javaField});
}
/**
* 查询${functionName}
*
* @param ${pkColumn.javaField} ${functionName}ID
* @return ${functionName}
*/
@Override
public ${ClassName} select${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}) {
return ${className}Mapper.select${ClassName}ById(${pkColumn.javaField});
}
/**
* 查询${functionName}列表
*
* @param ${className} ${functionName}
* @return ${functionName}
*/
@Override
public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) {
return ${className}Mapper.select${ClassName}List(${className});
}
/**
* 查询${functionName}列表
*
* @param ${className} ${functionName}
* @return ${functionName}
*/
@Override
public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) {
return ${className}Mapper.select${ClassName}List(${className});
}
/**
* 新增${functionName}
*
* @param ${className} ${functionName}
* @return 结果
*/
@Override
public int insert${ClassName}(${ClassName} ${className}) {
#foreach ($column in $columns)
#if($column.javaField == 'createTime')
${className}.setCreateTime(new Date());
#end
#if($column.javaField == 'updateTime')
${className}.setUpdateTime(new Date());
#end
#if($column.javaField == 'updateBy')
${className}.setUpdateBy(SecurityUtils.getUnionId());
#end
#end
return ${className}Mapper.insert${ClassName}(${className});
}
/**
* 新增${functionName}
*
* @param ${className} ${functionName}
* @return 结果
*/
@Override
public int insert${ClassName}(${ClassName} ${className}) {
#foreach ($column in $columns)
#if($column.javaField == 'createTime')
${className}.setCreateTime(new Date());
#end
#if($column.javaField == 'updateTime')
${className}.setUpdateTime(new Date());
#end
#if($column.javaField == 'updateBy')
${className}.setUpdateBy(SecurityUtils.getUnionId());
#end
#end
return ${className}Mapper.insert${ClassName}(${className});
}
/**
* 修改${functionName}
*
* @param ${className} ${functionName}
* @return 结果
*/
@Override
public int update${ClassName}(${ClassName} ${className}) {
#foreach ($column in $columns)
#if($column.javaField == 'updateTime')
${className}.setUpdateTime(new Date());
#end
#if($column.javaField == 'updateBy')
${className}.setUpdateBy(SecurityUtils.getUnionId());
#end
#end
return ${className}Mapper.update${ClassName}(${className});
}
/**
* 修改${functionName}
*
* @param ${className} ${functionName}
* @return 结果
*/
@Override
public int update${ClassName}(${ClassName} ${className}) {
#foreach ($column in $columns)
#if($column.javaField == 'updateTime')
${className}.setUpdateTime(new Date());
#end
#if($column.javaField == 'updateBy')
${className}.setUpdateBy(SecurityUtils.getUnionId());
#end
#end
return ${className}Mapper.update${ClassName}(${className});
}
/**
* 删除${functionName}对象
*
* @param ${pkColumn.javaType}[] ${pkColumn.javaField}s 需要删除的数据ID
* @return 结果
*/
@Override
public int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s) {
return ${className}Mapper.delete${ClassName}ByIds(${pkColumn.javaType}[]${pkColumn.javaField}s);
}
/**
* 删除${functionName}对象
*
* @param ${pkColumn.javaType}[] ${pkColumn.javaField}s 需要删除的数据ID
* @return 结果
*/
@Override
public int delete${ClassName}ByIds(${pkColumn.javaType}[] ${pkColumn.javaField}s) {
return ${className}Mapper.delete${ClassName}ByIds(${pkColumn.javaType}[]${pkColumn.javaField}s);
}
/**
* 删除${functionName}信息
*
* @param ${pkColumn.javaField} ${functionName}ID
* @return 结果
*/
@Override
public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}) {
return ${className}Mapper.delete${ClassName}ById(${pkColumn.javaField});
}
/**
* 删除${functionName}信息
*
* @param ${pkColumn.javaField} ${functionName}ID
* @return 结果
*/
@Override
public int delete${ClassName}ById(${pkColumn.javaType} ${pkColumn.javaField}) {
return ${className}Mapper.delete${ClassName}ById(${pkColumn.javaField});
}
#if($table.tree)
/**
* 查询${functionName}树列表
*
* @return 所有${functionName}信息
*/
@Override
public List<Ztree> select${ClassName}Tree() {
List<${ClassName}> ${className}List = ${className}Mapper.select${ClassName}List(
new ${ClassName}());
List<Ztree> ztrees = new ArrayList<Ztree>();
for (${ClassName} ${className} :${className}List)
{
Ztree ztree = new Ztree();
#set($TreeCode=$treeCode.substring(0,1).toUpperCase() + ${treeCode.substring(1)})
#set($TreeParentCode=$treeParentCode.substring(0,1).toUpperCase() + ${treeParentCode.substring(1)})
#set($TreeName=$treeName.substring(0,1).toUpperCase() + ${treeName.substring(1)})
ztree.setId(${className}.get${TreeCode}());
ztree.setpId(${className}.get${TreeParentCode}());
ztree.setName(${className}.get${TreeName}());
ztree.setTitle(${className}.get${TreeName}());
ztrees.add(ztree);
}
return ztrees;
/**
* 查询${functionName}树列表
*
* @return 所有${functionName}信息
*/
@Override
public List<Ztree> select${ClassName}Tree() {
List<${ClassName}> ${className}List = ${className}Mapper.select${ClassName}List(
new ${ClassName}());
List<Ztree> ztrees = new ArrayList<Ztree>();
for (${ClassName} ${className} :${className}List)
{
Ztree ztree = new Ztree();
#set($TreeCode=$treeCode.substring(0,1).toUpperCase() + ${treeCode.substring(1)})
#set($TreeParentCode=$treeParentCode.substring(0,1).toUpperCase() + ${treeParentCode.substring(1)})
#set($TreeName=$treeName.substring(0,1).toUpperCase() + ${treeName.substring(1)})
ztree.setId(${className}.get${TreeCode}());
ztree.setpId(${className}.get${TreeParentCode}());
ztree.setName(${className}.get${TreeName}());
ztree.setTitle(${className}.get${TreeName}());
ztrees.add(ztree);
}
return ztrees;
}
#end
}
@@ -15,31 +15,31 @@ import com.ruoyi.common.annotation.Excel;
@Data
public class ${subClassName} extends
BaseEntity {
private static final long serialVersionUID = 1L;
BaseEntity {
private static final long serialVersionUID = 1L;
#foreach ($column in $subTable.columns)
#if(!$table.isSuperColumn($column.javaField))
/** $column.columnComment */
#if($column.list)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($parentheseIndex != -1)
@Excel(name = "${comment}" , readConverterExp = "$column.readConverterExp()" )
#elseif($column.javaType == 'Date')
@JsonFormat(pattern = "yyyy-MM-dd" )
@Excel(name = "${comment}" , width = 30, dateFormat = "yyyy-MM-dd" )
#else
@Excel(name = "${comment}" )
#end
#foreach ($column in $subTable.columns)
#if(!$table.isSuperColumn($column.javaField))
/** $column.columnComment */
#if($column.list)
#set($parentheseIndex=$column.columnComment.indexOf(""))
#if($parentheseIndex != -1)
#set($comment=$column.columnComment.substring(0, $parentheseIndex))
#else
#set($comment=$column.columnComment)
#end
#if($parentheseIndex != -1)
@Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
#elseif($column.javaType == 'Date')
@JsonFormat(pattern = "yyyy-MM-dd")
@Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd")
#else
@Excel(name = "${comment}")
#end
private $column.javaType $column.javaField;
#end
#end
private $column.javaType $column.javaField;
}
#end
#end
}
@@ -2,43 +2,43 @@ import request from '@/utils/request'
// 查询${functionName}列表
export function list${BusinessName}(query) {
return request({
url: '/admin/${moduleName}/${businessName}/list',
method: 'get',
params: query
})
return request({
url: '/admin/${moduleName}/${businessName}/list',
method: 'get',
params: query
})
}
// 查询${functionName}详细
export function get${BusinessName}(${pkColumn.javaField}) {
return request({
url: '/admin/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'get'
})
return request({
url: '/admin/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'get'
})
}
// 新增${functionName}
export function add${BusinessName}(data) {
return request({
url: '/admin/${moduleName}/${businessName}',
method: 'post',
data: data
})
return request({
url: '/admin/${moduleName}/${businessName}',
method: 'post',
data: data
})
}
// 修改${functionName}
export function update${BusinessName}(data) {
return request({
url: '/admin/${moduleName}/${businessName}',
method: 'put',
data: data
})
return request({
url: '/admin/${moduleName}/${businessName}',
method: 'put',
data: data
})
}
// 删除${functionName}
export function del${BusinessName}(${pkColumn.javaField}) {
return request({
url: '/admin/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'delete'
})
return request({
url: '/admin/${moduleName}/${businessName}/' + ${pkColumn.javaField},
method: 'delete'
})
}
@@ -70,7 +70,8 @@
<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">{{ $t('button.reset') }}</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">{{ $t('button.reset') }}
</el-button>
</el-form-item>
</el-form>
@@ -68,9 +68,11 @@
#end
#end
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">{{ $t('button.search') }}
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">{{ $t
('button.search') }}
</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">{{ $t('button.reset') }}
</el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">{{ $t('button.reset') }}</el-button>
</el-form-item>
</el-form>
@@ -68,8 +68,11 @@
#end
#end
<el-form-item>
<el-button type="primary" icon="Search" size="mini" @click="handleQuery">{{ $t('button.edit') }}</el-button>
<el-button icon="Refresh" size="mini" @click="resetQuery">{{ $t('button.reset') }}</el-button>
<el-button type="primary" icon="Search" size="mini" @click="handleQuery">{{ $t
('button.edit') }}
</el-button>
<el-button icon="Refresh" size="mini" @click="resetQuery">{{ $t('button.reset') }}
</el-button>
</el-form-item>
</el-form>
@@ -68,8 +68,11 @@
#end
#end
<el-form-item>
<el-button type="primary" icon="Search" size="mini" @click="handleQuery">{{ $t('button.search') }}</el-button>
<el-button icon="Refresh" size="mini" @click="resetQuery">{{ $t('button.reset') }}</el-button>
<el-button type="primary" icon="Search" size="mini" @click="handleQuery">{{ $t
('button.search') }}
</el-button>
<el-button icon="Refresh" size="mini" @click="resetQuery">{{ $t('button.reset') }}
</el-button>
</el-form-item>
</el-form>
@@ -1,139 +1,139 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${packageName}.mapper.${ClassName}Mapper">
<resultMap type="${ClassName}" id="${ClassName}Result">
#foreach ($column in $columns)
<result property="${column.javaField}" column="${column.columnName}"/>
#end
#if($table.tree)
<result property="parentName" column="parent_name"/>
#end
</resultMap>
<resultMap type="${ClassName}" id="${ClassName}Result">
#foreach ($column in $columns)
<result property="${column.javaField}" column="${column.columnName}"/>
#end
#if($table.tree)
<result property="parentName" column="parent_name"/>
#end
</resultMap>
<sql id="select${ClassName}Vo">
select#foreach($column in $columns) $column.columnName#if($velocityCount != $columns.size()),#end#end
from ${tableName}
</sql>
<sql id="select${ClassName}Vo">
select#foreach($column in $columns) $column.columnName#if($velocityCount != $columns.size()),#end#end
from ${tableName}
</sql>
<select id="select${ClassName}List" parameterType="${ClassName}" resultMap="${ClassName}Result">
<select id="select${ClassName}List" parameterType="${ClassName}" resultMap="${ClassName}Result">
<include refid="select${ClassName}Vo"/>
<where>
#foreach($column in $columns)
#set($queryType=$column.queryType)
#set($javaField=$column.javaField)
#set($javaType=$column.javaType)
#set($columnName=$column.columnName)
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#if($column.query)
#if($column.queryType == "EQ")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName = #{$javaField}
</if>
#elseif($queryType == "NE")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName != #{$javaField}
</if>
#elseif($queryType == "GT")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName &gt; #{$javaField}
</if>
#elseif($queryType == "GTE")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName &gt;= #{$javaField
}
</if>
#elseif($queryType == "LT")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName &lt; #{$javaField}
</if>
#elseif($queryType == "LTE")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName &lt;= #{$javaField
}
</if>
#elseif($queryType == "LIKE")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName like concat('%',
#{$javaField}, '%')
</if>
#elseif($queryType == "BETWEEN")
<if
test="params.begin$AttrName != null and params.begin$AttrName != '' and params.end$AttrName != null and params.end$AttrName != ''">
and $columnName between #{params.begin$AttrName} and #{params.end$AttrName}
</if>
#end
#end
#end
</where>
#if($table.tree)
order by ${tree_parent_code}
#end
</select>
<select id="select${ClassName}ById" parameterType="${pkColumn.javaType}"
resultMap="${ClassName}Result">
#if($table.crud)
<include refid="select${ClassName}Vo"/>
<where>
#foreach($column in $columns)
#set($queryType=$column.queryType)
#set($javaField=$column.javaField)
#set($javaType=$column.javaType)
#set($columnName=$column.columnName)
#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
#if($column.query)
#if($column.queryType == "EQ")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName = #{$javaField}
</if>
#elseif($queryType == "NE")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName != #{$javaField}
</if>
#elseif($queryType == "GT")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName &gt; #{$javaField}
</if>
#elseif($queryType == "GTE")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName &gt;= #{$javaField
}
</if>
#elseif($queryType == "LT")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName &lt; #{$javaField}
</if>
#elseif($queryType == "LTE")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName &lt;= #{$javaField
}
</if>
#elseif($queryType == "LIKE")
<if test="$javaField != null #if($javaType ==
'String' ) and $javaField.trim() != ''#end">and $columnName like concat('%',
#{$javaField}, '%')
</if>
#elseif($queryType == "BETWEEN")
<if
test="params.begin$AttrName != null and params.begin$AttrName != '' and params.end$AttrName != null and params.end$AttrName != ''">
and $columnName between #{params.begin$AttrName} and #{params.end$AttrName}
</if>
#end
#end
#end
</where>
#if($table.tree)
order by ${tree_parent_code}
#end
</select>
<select id="select${ClassName}ById" parameterType="${pkColumn.javaType}"
resultMap="${ClassName}Result">
#if($table.crud)
<include refid="select${ClassName}Vo"/>
where ${pkColumn.columnName} = #{${pkColumn.javaField}}
#elseif($table.tree)
select#foreach($column in $columns) t.$column.columnName,#end p.${tree_name} as parent_name
from ${tableName} t
left join ${tableName} p on p.${pkColumn.columnName} = t.${tree_parent_code}
where t.${pkColumn.columnName} = #{${pkColumn.javaField}}
#end
</select>
<insert id="insert${ClassName}" parameterType="${ClassName}"#if($pkColumn.increment)
useGeneratedKeys="true" keyProperty="$pkColumn.javaField"#end>
insert into ${tableName}
<trim prefix="(" suffix=")" suffixOverrides=",">
#foreach($column in $columns)
#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment)
<if test="$column.javaField != null #if($column.javaType ==
'String' ) and $column.javaField != ''#end">$column.columnName,
</if>
#end
#end
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
#foreach($column in $columns)
#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment)
<if test="$column.javaField != null #if($column.javaType ==
'String' ) and $column.javaField != ''#end">#{$column.javaField},
</if>
#end
#end
</trim>
</insert>
<update id="update${ClassName}" parameterType="${ClassName}">
update ${tableName}
<trim prefix="SET" suffixOverrides=",">
#foreach($column in $columns)
#if($column.columnName != $pkColumn.columnName)
<if test="$column.javaField != null #if($column.javaType ==
'String' ) and $column.javaField != ''#end">$column.columnName =
#{$column.javaField},
</if>
#end
#end
</trim>
where ${pkColumn.columnName} = #{${pkColumn.javaField}}
</update>
#elseif($table.tree)
select#foreach($column in $columns) t.$column.columnName,#end p.${tree_name} as parent_name
from ${tableName} t
left join ${tableName} p on p.${pkColumn.columnName} = t.${tree_parent_code}
where t.${pkColumn.columnName} = #{${pkColumn.javaField}}
#end
</select>
<delete id="delete${ClassName}ById" parameterType="${pkColumn.javaType}">
delete
from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}}
</delete>
<insert id="insert${ClassName}" parameterType="${ClassName}"#if($pkColumn.increment)
useGeneratedKeys="true" keyProperty="$pkColumn.javaField"#end>
insert into ${tableName}
<trim prefix="(" suffix=")" suffixOverrides=",">
#foreach($column in $columns)
#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment)
<if test="$column.javaField != null #if($column.javaType ==
'String' ) and $column.javaField != ''#end">$column.columnName,
</if>
#end
#end
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
#foreach($column in $columns)
#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment)
<if test="$column.javaField != null #if($column.javaType ==
'String' ) and $column.javaField != ''#end">#{$column.javaField},
</if>
#end
#end
</trim>
</insert>
<delete id="delete${ClassName}ByIds" parameterType="${pkColumn.javaType}">
delete from ${tableName} where ${pkColumn.columnName} in
<foreach item="${pkColumn.javaField}" collection="array" open="(" separator="," close=")">
#{${pkColumn.javaField}}
</foreach>
</delete>
<update id="update${ClassName}" parameterType="${ClassName}">
update ${tableName}
<trim prefix="SET" suffixOverrides=",">
#foreach($column in $columns)
#if($column.columnName != $pkColumn.columnName)
<if test="$column.javaField != null #if($column.javaType ==
'String' ) and $column.javaField != ''#end">$column.columnName =
#{$column.javaField},
</if>
#end
#end
</trim>
where ${pkColumn.columnName} = #{${pkColumn.javaField}}
</update>
<delete id="delete${ClassName}ById" parameterType="${pkColumn.javaType}">
delete
from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}}
</delete>
<delete id="delete${ClassName}ByIds" parameterType="${pkColumn.javaType}">
delete from ${tableName} where ${pkColumn.columnName} in
<foreach item="${pkColumn.javaField}" collection="array" open="(" separator="," close=")">
#{${pkColumn.javaField}}
</foreach>
</delete>
</mapper>
@@ -1,88 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-data-bridge</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<parent>
<artifactId>cn-universal-data-bridge</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<artifactId>cn-universal-data-bridge-core</artifactId>
<packaging>jar</packaging>
<artifactId>cn-universal-data-bridge-core</artifactId>
<packaging>jar</packaging>
<name>cn-universal-data-bridge-core</name>
<description>数据桥接核心模块</description>
<name>cn-universal-data-bridge-core</name>
<description>数据桥接核心模块</description>
<dependencies>
<!-- 父项目依赖 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-persistence</artifactId>
<version>${project.version}</version>
</dependency>
<dependencies>
<!-- 父项目依赖 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-persistence</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- Hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Kafka Client -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</dependency>
<!-- Kafka Client -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</dependency>
<!-- MQTT Client -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
</dependency>
<!-- MQTT Client -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
</dependency>
<!-- IoTDB JDBC Driver -->
<dependency>
<groupId>org.apache.iotdb</groupId>
<artifactId>iotdb-jdbc</artifactId>
</dependency>
<!-- IoTDB JDBC Driver -->
<dependency>
<groupId>org.apache.iotdb</groupId>
<artifactId>iotdb-jdbc</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-cache</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-cache</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
@@ -34,63 +34,61 @@ import lombok.NoArgsConstructor;
@Table(name = "iot_data_input_log")
public class DataInputLog implements Serializable {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/** 配置ID */
@Column(name = "config_id")
private Long configId;
/** 配置ID */
@Column(name = "config_id")
private Long configId;
/** 配置名称 */
@Column(name = "config_name")
private String configName;
/** 配置名称 */
@Column(name = "config_name")
private String configName;
/** 源系统 */
@Column(name = "source_system")
private String sourceSystem;
/** 源系统 */
@Column(name = "source_system")
private String sourceSystem;
/** 消息数量 */
@Column(name = "message_count")
private Integer messageCount;
/** 消息数量 */
@Column(name = "message_count")
private Integer messageCount;
/** 成功数量 */
@Column(name = "success_count")
private Integer successCount;
/** 成功数量 */
@Column(name = "success_count")
private Integer successCount;
/** 失败数量 */
@Column(name = "failed_count")
private Integer failedCount;
/** 失败数量 */
@Column(name = "failed_count")
private Integer failedCount;
/** 错误信息 */
@Column(name = "error_message")
private String errorMessage;
/** 错误信息 */
@Column(name = "error_message")
private String errorMessage;
/** 执行时间(毫秒) */
@Column(name = "execution_time")
private Long executionTime;
/** 执行时间(毫秒) */
@Column(name = "execution_time")
private Long executionTime;
/** 状态 */
@Column(name = "status")
@Enumerated(EnumType.STRING)
private Status status;
/** 状态 */
@Column(name = "status")
@Enumerated(EnumType.STRING)
private Status status;
/** 创建时间 */
@Column(name = "create_time")
private LocalDateTime createTime;
/** 创建时间 */
@Column(name = "create_time")
private LocalDateTime createTime;
/** 创建者 */
@Column(name = "create_by")
private String createBy;
/** 创建者 */
@Column(name = "create_by")
private String createBy;
/**
* 状态枚举
*/
public enum Status {
SUCCESS, // 成功
FAILED, // 失败
RUNNING // 运行中
}
/** 状态枚举 */
public enum Status {
SUCCESS, // 成功
FAILED, // 失败
RUNNING // 运行中
}
}
@@ -34,76 +34,72 @@ import lombok.NoArgsConstructor;
@Table(name = "iot_data_input_task")
public class DataInputTask implements Serializable {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/** 配置ID */
@Column(name = "config_id")
private Long configId;
/** 配置ID */
@Column(name = "config_id")
private Long configId;
/** 任务名称 */
@Column(name = "task_name")
private String taskName;
/** 任务名称 */
@Column(name = "task_name")
private String taskName;
/** 任务类型 */
@Column(name = "task_type")
@Enumerated(EnumType.STRING)
private TaskType taskType;
/** 任务类型 */
@Column(name = "task_type")
@Enumerated(EnumType.STRING)
private TaskType taskType;
/** Cron表达式 */
@Column(name = "cron_expression")
private String cronExpression;
/** Cron表达式 */
@Column(name = "cron_expression")
private String cronExpression;
/** 最后执行时间 */
@Column(name = "last_execution_time")
private LocalDateTime lastExecutionTime;
/** 最后执行时间 */
@Column(name = "last_execution_time")
private LocalDateTime lastExecutionTime;
/** 下次执行时间 */
@Column(name = "next_execution_time")
private LocalDateTime nextExecutionTime;
/** 下次执行时间 */
@Column(name = "next_execution_time")
private LocalDateTime nextExecutionTime;
/** 状态 */
@Column(name = "status")
@Enumerated(EnumType.STRING)
private Status status;
/** 状态 */
@Column(name = "status")
@Enumerated(EnumType.STRING)
private Status status;
/** 错误信息 */
@Column(name = "error_message")
private String errorMessage;
/** 错误信息 */
@Column(name = "error_message")
private String errorMessage;
/** 创建时间 */
@Column(name = "create_time")
private LocalDateTime createTime;
/** 创建时间 */
@Column(name = "create_time")
private LocalDateTime createTime;
/** 更新时间 */
@Column(name = "update_time")
private LocalDateTime updateTime;
/** 更新时间 */
@Column(name = "update_time")
private LocalDateTime updateTime;
/** 创建者 */
@Column(name = "create_by")
private String createBy;
/** 创建者 */
@Column(name = "create_by")
private String createBy;
/** 更新者 */
@Column(name = "update_by")
private String updateBy;
/** 更新者 */
@Column(name = "update_by")
private String updateBy;
/**
* 任务类型枚举
*/
public enum TaskType {
SCHEDULED, // 定时任务
REALTIME // 实时任务
}
/** 任务类型枚举 */
public enum TaskType {
SCHEDULED, // 定时任务
REALTIME // 实时任务
}
/**
* 状态枚举
*/
public enum Status {
RUNNING, // 运行中
STOPPED, // 已停止
ERROR // 错误状态
}
/** 状态枚举 */
public enum Status {
RUNNING, // 运行中
STOPPED, // 已停止
ERROR // 错误状态
}
}
@@ -34,109 +34,112 @@ import lombok.NoArgsConstructor;
@Table(name = "iot_resource_connection")
public class ResourceConnection implements Serializable {
private static final long serialVersionUID = 1L;
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/** 资源名称 */
@Column(name = "name")
private String name;
/** 资源名称 */
@Column(name = "name")
private String name;
/** 资源类型 */
@Column(name = "type")
@Enumerated(EnumType.STRING)
private ResourceType type;
/** 资源类型 */
@Column(name = "type")
@Enumerated(EnumType.STRING)
private ResourceType type;
/** 插件类型 */
@Column(name = "plugin_type")
private String pluginType;
/** 插件类型 */
@Column(name = "plugin_type")
private String pluginType;
/** 主机地址 */
@Column(name = "host")
private String host;
/** 主机地址 */
@Column(name = "host")
private String host;
/** 端口号 */
@Column(name = "port")
private Integer port;
/** 端口号 */
@Column(name = "port")
private Integer port;
/** 用户名 */
@Column(name = "username")
private String username;
/** 用户名 */
@Column(name = "username")
private String username;
/** 密码 */
@Column(name = "password")
private String password;
/** 密码 */
@Column(name = "password")
private String password;
/** 数据库名 */
@Column(name = "database_name")
private String databaseName;
/** 数据库名 */
@Column(name = "database_name")
private String databaseName;
/** 扩展配置JSON */
@Column(name = "extra_config")
private String extraConfig;
/** 扩展配置JSON */
@Column(name = "extra_config")
private String extraConfig;
/** 状态:0禁用,1启用 */
@Column(name = "status")
private Integer status;
/** 状态:0禁用,1启用 */
@Column(name = "status")
private Integer status;
/** 描述 */
@Column(name = "description")
private String description;
/** 描述 */
@Column(name = "description")
private String description;
/** 创建者 */
@Column(name = "create_by")
private String createBy;
/** 创建者 */
@Column(name = "create_by")
private String createBy;
/** 创建时间 */
@Column(name = "create_time")
private LocalDateTime createTime;
/** 创建时间 */
@Column(name = "create_time")
private LocalDateTime createTime;
/** 更新者 */
@Column(name = "update_by")
private String updateBy;
/** 更新者 */
@Column(name = "update_by")
private String updateBy;
/** 更新时间 */
@Column(name = "update_time")
private LocalDateTime updateTime;
/** 更新时间 */
@Column(name = "update_time")
private LocalDateTime updateTime;
/** 方向:IN-输入,OUT-输出,BOTH-双向 */
@Column(name = "direction")
@Enumerated(EnumType.STRING)
private Direction direction;
/** 方向:IN-输入,OUT-输出,BOTH-双向 */
@Column(name = "direction")
@Enumerated(EnumType.STRING)
private Direction direction;
/** 数据流向:INPUT-输入,OUTPUT-输出,BIDIRECTIONAL-双向 */
@Column(name = "data_direction")
@Enumerated(EnumType.STRING)
private DataDirection dataDirection;
/** 数据流向:INPUT-输入,OUTPUT-输出,BIDIRECTIONAL-双向 */
@Column(name = "data_direction")
@Enumerated(EnumType.STRING)
private DataDirection dataDirection;
/** 动态配置JSON - 使用现有的extra_config字段存储 */
@Transient
private String dynamicConfig;
/** 动态配置JSON - 使用现有的extra_config字段存储 */
@Transient private String dynamicConfig;
/**
* 资源类型枚举
*/
public enum ResourceType {
MYSQL, KAFKA, IOTDB, INFLUXDB, MQTT, HTTP, REDIS, ELASTICSEARCH, ALIYUN_IOT, TENCENT_IOT, HUAWEI_IOT
}
/** 资源类型枚举 */
public enum ResourceType {
MYSQL,
KAFKA,
IOTDB,
INFLUXDB,
MQTT,
HTTP,
REDIS,
ELASTICSEARCH,
ALIYUN_IOT,
TENCENT_IOT,
HUAWEI_IOT
}
/**
* 方向枚举
*/
public enum Direction {
IN, // 输入
OUT, // 输出
BOTH // 双向
}
/** 方向枚举 */
public enum Direction {
IN, // 输入
OUT, // 输出
BOTH // 双向
}
/**
* 数据流向枚举
*/
public enum DataDirection {
INPUT, // 数据输入
OUTPUT, // 数据输出
BIDIRECTIONAL // 双向流转
}
/** 数据流向枚举 */
public enum DataDirection {
INPUT, // 数据输入
OUTPUT, // 数据输出
BIDIRECTIONAL // 双向流转
}
}
@@ -44,224 +44,219 @@ import org.springframework.stereotype.Component;
@Slf4j
public class DataInputManager {
@Autowired(required = false)
private Map<String, DataInputPlugin> inputPlugins;
@Autowired(required = false)
private Map<String, DataInputPlugin> inputPlugins;
@Resource
private ResourceConnectionService resourceConnectionService;
@Resource private ResourceConnectionService resourceConnectionService;
@Resource
private DataBridgeConfigService dataBridgeConfigService;
@Resource private DataBridgeConfigService dataBridgeConfigService;
@Resource
private DataInputLogService dataInputLogService;
@Resource private DataInputLogService dataInputLogService;
@Resource
private DataBridgeLogger dataBridgeLogger;
@Resource private DataBridgeLogger dataBridgeLogger;
// 存储正在运行的输入任务
private final Map<Long, Boolean> runningTasks = new ConcurrentHashMap<>();
// 存储正在运行的输入任务
private final Map<Long, Boolean> runningTasks = new ConcurrentHashMap<>();
/**
* 启动数据输入任务
*/
public void startInputTask(Long configId) {
/** 启动数据输入任务 */
public void startInputTask(Long configId) {
try {
DataBridgeConfig config = dataBridgeConfigService.getById(configId);
if (config == null) {
throw new RuntimeException("配置不存在");
}
// TODO: 需要实现新的输入配置判断逻辑,因为Direction枚举已被移除
ResourceConnection connection =
resourceConnectionService.getById(config.getTargetResourceId());
if (connection == null || connection.getStatus() != 1) {
throw new RuntimeException("资源连接不存在或已禁用");
}
DataInputPlugin plugin = inputPlugins.get(config.getBridgeType().name().toUpperCase());
if (plugin == null) {
throw new RuntimeException("未找到对应的输入插件: " + config.getBridgeType());
}
// 标记任务为运行状态
runningTasks.put(configId, true);
// 创建数据消费者回调
Consumer<List<BaseUPRequest>> dataConsumer = data -> processInputData(data, config);
// 启动数据输入
// TODO: 需要重新设计输入插件的启动机制,因为相关方法已被移除
log.info("数据输入任务启动逻辑需要重新实现,配置ID: {}", configId);
} catch (Exception e) {
runningTasks.remove(configId);
log.error("启动数据输入任务失败,配置ID: {}, 错误: {}", configId, e.getMessage(), e);
throw e;
}
}
/** 停止数据输入任务 */
public void stopInputTask(Long configId) {
try {
DataBridgeConfig config = dataBridgeConfigService.getById(configId);
if (config == null) {
return;
}
ResourceConnection connection =
resourceConnectionService.getById(config.getTargetResourceId());
if (connection == null) {
return;
}
DataInputPlugin plugin = inputPlugins.get(config.getBridgeType().name().toUpperCase());
if (plugin != null) {
// TODO: 需要重新设计输入插件的停止机制,因为stopDataInput方法已被移除
log.info("停止数据输入插件逻辑需要重新实现");
}
// 移除运行标记
runningTasks.remove(configId);
log.info("停止数据输入任务成功,配置ID: {}", configId);
} catch (Exception e) {
log.error("停止数据输入任务失败,配置ID: {}, 错误: {}", configId, e.getMessage(), e);
throw e;
}
}
/** 检查任务是否正在运行 */
public Boolean isTaskRunning(Long configId) {
return runningTasks.getOrDefault(configId, false);
}
/** 启动定时拉取任务 */
@Async("dataBridgeExecutor")
public void startScheduledPullTask(
DataBridgeConfig config, ResourceConnection connection, DataInputPlugin plugin) {
Long configId = config.getId();
while (runningTasks.getOrDefault(configId, false)) {
try {
// 拉取数据
// TODO: 需要重新设计数据拉取机制,因为pullInputData方法已被移除
List<BaseUPRequest> data = null; // plugin.pullInputData(config, connection);
if (CollectionUtil.isNotEmpty(data)) {
processInputData(data, config);
}
// 等待下次拉取(可以从配置中读取间隔时间)
Thread.sleep(getScheduleInterval(config));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
log.error("定时拉取数据失败,配置ID: {}, 错误: {}", configId, e.getMessage(), e);
// 记录错误日志
logInputExecution(config, 0, 0, 0, e.getMessage(), 0);
// 等待一段时间后重试
try {
DataBridgeConfig config = dataBridgeConfigService.getById(configId);
if (config == null) {
throw new RuntimeException("配置不存在");
}
// TODO: 需要实现新的输入配置判断逻辑,因为Direction枚举已被移除
ResourceConnection connection = resourceConnectionService.getById(config.getTargetResourceId());
if (connection == null || connection.getStatus() != 1) {
throw new RuntimeException("资源连接不存在或已禁用");
}
DataInputPlugin plugin = inputPlugins.get(config.getBridgeType().name().toUpperCase());
if (plugin == null) {
throw new RuntimeException("未找到对应的输入插件: " + config.getBridgeType());
}
// 标记任务为运行状态
runningTasks.put(configId, true);
// 创建数据消费者回调
Consumer<List<BaseUPRequest>> dataConsumer = data -> processInputData(data, config);
// 启动数据输入
// TODO: 需要重新设计输入插件的启动机制,因为相关方法已被移除
log.info("数据输入任务启动逻辑需要重新实现,配置ID: {}", configId);
} catch (Exception e) {
runningTasks.remove(configId);
log.error("启动数据输入任务失败,配置ID: {}, 错误: {}", configId, e.getMessage(), e);
throw e;
Thread.sleep(30000); // 30秒后重试
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
/** 处理输入数据 */
private void processInputData(List<BaseUPRequest> data, DataBridgeConfig config) {
if (CollectionUtil.isEmpty(data)) {
return;
}
/**
* 停止数据输入任务
*/
public void stopInputTask(Long configId) {
long startTime = System.currentTimeMillis();
int successCount = 0;
int failedCount = 0;
String errorMessage = null;
try {
// TODO: 这里需要将数据推送到IoT平台
// 可以通过消息队列或直接调用IoT平台的API
for (BaseUPRequest request : data) {
try {
DataBridgeConfig config = dataBridgeConfigService.getById(configId);
if (config == null) {
return;
}
ResourceConnection connection = resourceConnectionService.getById(config.getTargetResourceId());
if (connection == null) {
return;
}
DataInputPlugin plugin = inputPlugins.get(config.getBridgeType().name().toUpperCase());
if (plugin != null) {
// TODO: 需要重新设计输入插件的停止机制,因为stopDataInput方法已被移除
log.info("停止数据输入插件逻辑需要重新实现");
}
// 移除运行标记
runningTasks.remove(configId);
log.info("停止数据输入任务成功,配置ID: {}", configId);
// 推送到IoT平台的逻辑
pushToIoTPlatform(request, config);
successCount++;
} catch (Exception e) {
log.error("停止数据输入任务失败,配置ID: {}, 错误: {}", configId, e.getMessage(), e);
throw e;
failedCount++;
log.error("推送数据到IoT平台失败: {}", e.getMessage(), e);
}
}
log.debug(
"处理输入数据完成,配置: {}, 总数: {}, 成功: {}, 失败: {}",
config.getName(),
data.size(),
successCount,
failedCount);
} catch (Exception e) {
failedCount = data.size();
errorMessage = e.getMessage();
log.error("处理输入数据异常,配置: {}, 错误: {}", config.getName(), e.getMessage(), e);
} finally {
long executionTime = System.currentTimeMillis() - startTime;
logInputExecution(
config, data.size(), successCount, failedCount, errorMessage, executionTime);
}
}
/**
* 检查任务是否正在运行
*/
public Boolean isTaskRunning(Long configId) {
return runningTasks.getOrDefault(configId, false);
/** 推送数据到IoT平台 */
private void pushToIoTPlatform(BaseUPRequest request, DataBridgeConfig config) {
// TODO: 实现推送到IoT平台的逻辑
// 这里可以通过消息队列、HTTP API等方式将数据推送到IoT平台
log.debug(
"推送数据到IoT平台: 设备={}, 产品={}",
request.getIoTDeviceDTO() != null ? request.getIoTDeviceDTO().getDeviceId() : "unknown",
config.getTargetResourceId());
}
/** 记录输入执行日志 */
private void logInputExecution(
DataBridgeConfig config,
int messageCount,
int successCount,
int failedCount,
String errorMessage,
long executionTime) {
try {
DataInputLog log =
DataInputLog.builder()
.configId(config.getId())
.configName(config.getName())
.sourceSystem(config.getBridgeType().name())
.messageCount(messageCount)
.successCount(successCount)
.failedCount(failedCount)
.errorMessage(errorMessage)
.executionTime(executionTime)
.status(failedCount > 0 ? DataInputLog.Status.FAILED : DataInputLog.Status.SUCCESS)
.createTime(LocalDateTime.now())
.createBy(config.getCreateBy())
.build();
dataInputLogService.save(log);
} catch (Exception e) {
log.error("记录输入执行日志失败: {}", e.getMessage(), e);
}
}
/**
* 启动定时拉取任务
*/
@Async("dataBridgeExecutor")
public void startScheduledPullTask(DataBridgeConfig config, ResourceConnection connection, DataInputPlugin plugin) {
Long configId = config.getId();
while (runningTasks.getOrDefault(configId, false)) {
try {
// 拉取数据
// TODO: 需要重新设计数据拉取机制,因为pullInputData方法已被移除
List<BaseUPRequest> data = null; // plugin.pullInputData(config, connection);
if (CollectionUtil.isNotEmpty(data)) {
processInputData(data, config);
}
// 等待下次拉取(可以从配置中读取间隔时间)
Thread.sleep(getScheduleInterval(config));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
log.error("定时拉取数据失败,配置ID: {}, 错误: {}", configId, e.getMessage(), e);
// 记录错误日志
logInputExecution(config, 0, 0, 0, e.getMessage(), 0);
// 等待一段时间后重试
try {
Thread.sleep(30000); // 30秒后重试
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
/**
* 处理输入数据
*/
private void processInputData(List<BaseUPRequest> data, DataBridgeConfig config) {
if (CollectionUtil.isEmpty(data)) {
return;
}
long startTime = System.currentTimeMillis();
int successCount = 0;
int failedCount = 0;
String errorMessage = null;
try {
// TODO: 这里需要将数据推送到IoT平台
// 可以通过消息队列或直接调用IoT平台的API
for (BaseUPRequest request : data) {
try {
// 推送到IoT平台的逻辑
pushToIoTPlatform(request, config);
successCount++;
} catch (Exception e) {
failedCount++;
log.error("推送数据到IoT平台失败: {}", e.getMessage(), e);
}
}
log.debug("处理输入数据完成,配置: {}, 总数: {}, 成功: {}, 失败: {}",
config.getName(), data.size(), successCount, failedCount);
} catch (Exception e) {
failedCount = data.size();
errorMessage = e.getMessage();
log.error("处理输入数据异常,配置: {}, 错误: {}", config.getName(), e.getMessage(), e);
} finally {
long executionTime = System.currentTimeMillis() - startTime;
logInputExecution(config, data.size(), successCount, failedCount, errorMessage, executionTime);
}
}
/**
* 推送数据到IoT平台
*/
private void pushToIoTPlatform(BaseUPRequest request, DataBridgeConfig config) {
// TODO: 实现推送到IoT平台的逻辑
// 这里可以通过消息队列、HTTP API等方式将数据推送到IoT平台
log.debug("推送数据到IoT平台: 设备={}, 产品={}",
request.getIoTDeviceDTO() != null ? request.getIoTDeviceDTO().getDeviceId() : "unknown",
config.getTargetResourceId());
}
/**
* 记录输入执行日志
*/
private void logInputExecution(DataBridgeConfig config, int messageCount, int successCount,
int failedCount, String errorMessage, long executionTime) {
try {
DataInputLog log = DataInputLog.builder()
.configId(config.getId())
.configName(config.getName())
.sourceSystem(config.getBridgeType().name())
.messageCount(messageCount)
.successCount(successCount)
.failedCount(failedCount)
.errorMessage(errorMessage)
.executionTime(executionTime)
.status(failedCount > 0 ? DataInputLog.Status.FAILED : DataInputLog.Status.SUCCESS)
.createTime(LocalDateTime.now())
.createBy(config.getCreateBy())
.build();
dataInputLogService.save(log);
} catch (Exception e) {
log.error("记录输入执行日志失败: {}", e.getMessage(), e);
}
}
/**
* 获取调度间隔时间(毫秒)
*/
private long getScheduleInterval(DataBridgeConfig config) {
// 从配置中读取间隔时间,默认60秒
// TODO: 可以从 inputConfig 中解析间隔时间
return 60000; // 60秒
}
/** 获取调度间隔时间(毫秒) */
private long getScheduleInterval(DataBridgeConfig config) {
// 从配置中读取间隔时间,默认60秒
// TODO: 可以从 inputConfig 中解析间隔时间
return 60000; // 60秒
}
}
@@ -26,54 +26,58 @@ import org.apache.ibatis.annotations.Param;
*/
public interface DataBridgeConfigMapper extends BaseMapper<DataBridgeConfig> {
/**
* 根据产品KEY获取活跃配置
*
* @param productKey 产品KEY
* @return 活跃配置列表
*/
List<DataBridgeConfig> selectActiveConfigsByProductKey(@Param("productKey") String productKey);
/**
* 根据产品KEY获取活跃配置
*
* @param productKey 产品KEY
* @return 活跃配置列表
*/
List<DataBridgeConfig> selectActiveConfigsByProductKey(@Param("productKey") String productKey);
/**
* 根据桥接类型获取配置
*
* @param bridgeType 桥接类型
* @return 配置列表
*/
List<DataBridgeConfig> selectConfigsByBridgeType(@Param("bridgeType") String bridgeType);
/**
* 根据桥接类型获取配置
*
* @param bridgeType 桥接类型
* @return 配置列表
*/
List<DataBridgeConfig> selectConfigsByBridgeType(@Param("bridgeType") String bridgeType);
/**
* 根据目标资源ID获取配置
*
* @param targetResourceId 目标资源ID
* @return 配置列表
*/
List<DataBridgeConfig> selectConfigsByTargetResourceId(@Param("targetResourceId") Long targetResourceId);
/**
* 根据目标资源ID获取配置
*
* @param targetResourceId 目标资源ID
* @return 配置列表
*/
List<DataBridgeConfig> selectConfigsByTargetResourceId(
@Param("targetResourceId") Long targetResourceId);
/**
* 批量更新配置状态
*
* @param ids 配置ID列表
* @param status 状态
* @param updateBy 更新者
* @return 更新数量
*/
int batchUpdateStatus(@Param("ids") List<Long> ids, @Param("status") Integer status, @Param("updateBy") String updateBy);
/**
* 批量更新配置状态
*
* @param ids 配置ID列表
* @param status 状态
* @param updateBy 更新者
* @return 更新数量
*/
int batchUpdateStatus(
@Param("ids") List<Long> ids,
@Param("status") Integer status,
@Param("updateBy") String updateBy);
/**
* 根据名称查询配置(用于重名检查)
*
* @param name 配置名称
* @param excludeId 排除的ID(用于更新时检查)
* @return 配置
*/
DataBridgeConfig selectByName(@Param("name") String name, @Param("excludeId") Long excludeId);
/**
* 根据名称查询配置(用于重名检查)
*
* @param name 配置名称
* @param excludeId 排除的ID(用于更新时检查)
* @return 配置
*/
DataBridgeConfig selectByName(@Param("name") String name, @Param("excludeId") Long excludeId);
/**
* 根据创建者查询配置列表
*
* @param createBy 创建者
* @return 配置列表
*/
List<DataBridgeConfig> selectByCreateBy(@Param("createBy") String createBy);
/**
* 根据创建者查询配置列表
*
* @param createBy 创建者
* @return 配置列表
*/
List<DataBridgeConfig> selectByCreateBy(@Param("createBy") String createBy);
}
@@ -26,48 +26,52 @@ import org.apache.ibatis.annotations.Param;
*/
public interface ResourceConnectionMapper extends BaseMapper<ResourceConnection> {
/**
* 根据资源类型获取活跃连接
*
* @param type 资源类型
* @return 活跃连接列表
*/
List<ResourceConnection> selectActiveConnectionsByType(@Param("type") String type);
/**
* 根据资源类型获取活跃连接
*
* @param type 资源类型
* @return 活跃连接列表
*/
List<ResourceConnection> selectActiveConnectionsByType(@Param("type") String type);
/**
* 根据主机和端口查询连接
*
* @param host 主机地址
* @param port 端口号
* @param excludeId 排除的ID(用于更新时检查)
* @return 连接
*/
ResourceConnection selectByHostAndPort(@Param("host") String host, @Param("port") Integer port, @Param("excludeId") Long excludeId);
/**
* 根据主机和端口查询连接
*
* @param host 主机地址
* @param port 端口号
* @param excludeId 排除的ID(用于更新时检查)
* @return 连接
*/
ResourceConnection selectByHostAndPort(
@Param("host") String host, @Param("port") Integer port, @Param("excludeId") Long excludeId);
/**
* 根据名称查询连接(用于重名检查)
*
* @param name 连接名称
* @param excludeId 排除的ID(用于更新时检查)
* @return 连接
*/
ResourceConnection selectByName(@Param("name") String name, @Param("excludeId") Long excludeId);
/**
* 根据名称查询连接(用于重名检查)
*
* @param name 连接名称
* @param excludeId 排除的ID(用于更新时检查)
* @return 连接
*/
ResourceConnection selectByName(@Param("name") String name, @Param("excludeId") Long excludeId);
/**
* 批量更新连接状态
*
* @param ids 连接ID列表
* @param status 状态
* @param updateBy 更新者
* @return 更新数量
*/
int batchUpdateStatus(@Param("ids") List<Long> ids, @Param("status") Integer status, @Param("updateBy") String updateBy);
/**
* 批量更新连接状态
*
* @param ids 连接ID列表
* @param status 状态
* @param updateBy 更新者
* @return 更新数量
*/
int batchUpdateStatus(
@Param("ids") List<Long> ids,
@Param("status") Integer status,
@Param("updateBy") String updateBy);
/**
* 测试连接可用性(通过查询系统表)
*
* @param id 连接ID
* @return 是否可用
*/
int testConnection(@Param("id") Long id);
/**
* 测试连接可用性(通过查询系统表)
*
* @param id 连接ID
* @return 是否可用
*/
int testConnection(@Param("id") Long id);
}
@@ -30,170 +30,162 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ConfigValidator {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final ObjectMapper objectMapper = new ObjectMapper();
// 名称正则表达式:字母、数字、下划线、中划线,长度1-100
private static final Pattern NAME_PATTERN = Pattern.compile("^[\\u4e00-\\u9fa5a-zA-Z0-9_-]{1,100}$");
// 名称正则表达式:字母、数字、下划线、中划线,长度1-100
private static final Pattern NAME_PATTERN =
Pattern.compile("^[\\u4e00-\\u9fa5a-zA-Z0-9_-]{1,100}$");
// 产品KEY正则表达式:字母、数字、下划线,长度1-50
private static final Pattern PRODUCT_KEY_PATTERN = Pattern.compile("^[a-zA-Z0-9_]{1,50}$");
// 产品KEY正则表达式:字母、数字、下划线,长度1-50
private static final Pattern PRODUCT_KEY_PATTERN = Pattern.compile("^[a-zA-Z0-9_]{1,50}$");
// 主机地址正则表达式
private static final Pattern HOST_PATTERN = Pattern.compile("^[a-zA-Z0-9.-]{1,255}$");
// 主机地址正则表达式
private static final Pattern HOST_PATTERN = Pattern.compile("^[a-zA-Z0-9.-]{1,255}$");
// 数据库名正则表达式:字母、数字、下划线,长度1-64
private static final Pattern DATABASE_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]{1,64}$");
// 数据库名正则表达式:字母、数字、下划线,长度1-64
private static final Pattern DATABASE_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]{1,64}$");
/**
* 验证桥接配置
*/
public static void validateDataBridgeConfig(DataBridgeConfig config) {
if (config == null) {
throw new DataBridgeException("CONFIG_NULL", "桥接配置不能为空");
}
// 验证名称
validateName(config.getName(), "桥接配置名称");
// 验证源范围
if (config.getSourceScope() == null) {
throw new DataBridgeException("INVALID_SOURCE_SCOPE", "源范围不能为空");
}
// 验证目标资源ID
if (config.getTargetResourceId() == null || config.getTargetResourceId() <= 0) {
throw new DataBridgeException("INVALID_TARGET_RESOURCE_ID", "目标资源ID无效");
}
// 验证桥接类型
if (config.getBridgeType() == null) {
throw new DataBridgeException("INVALID_BRIDGE_TYPE", "桥接类型不能为空");
}
// 验证模板
if (config.getTemplate() == null || config.getTemplate().trim().isEmpty()) {
throw new DataBridgeException("INVALID_TEMPLATE", "模板内容不能为空");
}
// 验证统一配置JSON格式
if (config.getConfig() != null && !config.getConfig().trim().isEmpty()) {
validateJsonFormat(config.getConfig(), "统一配置");
}
// 验证Magic脚本
if (config.getMagicScript() != null && !config.getMagicScript().trim().isEmpty()) {
// Magic脚本内容验证可以在这里添加
}
// 验证状态
if (config.getStatus() != null && config.getStatus() != 0 && config.getStatus() != 1) {
throw new DataBridgeException("INVALID_STATUS", "状态值无效,只能为0或1");
}
// 验证描述长度
if (config.getDescription() != null && config.getDescription().length() > 500) {
throw new DataBridgeException("INVALID_DESCRIPTION", "描述长度不能超过500个字符");
}
/** 验证桥接配置 */
public static void validateDataBridgeConfig(DataBridgeConfig config) {
if (config == null) {
throw new DataBridgeException("CONFIG_NULL", "桥接配置不能为空");
}
/**
* 验证资源连接
*/
public static void validateResourceConnection(ResourceConnection connection) {
if (connection == null) {
throw new DataBridgeException("CONNECTION_NULL", "资源连接不能为空");
}
// 验证名称
validateName(config.getName(), "桥接配置名称");
// 验证名称
validateName(connection.getName(), "资源连接名称");
// 验证资源类型
if (connection.getType() == null) {
throw new DataBridgeException("INVALID_RESOURCE_TYPE", "资源类型不能为空");
}
// 验证数据流向
if (connection.getDataDirection() == null) {
throw new DataBridgeException("INVALID_DATA_DIRECTION", "数据流向不能为空");
}
// 验证扩展配置JSON格式(包含动态配置)
if (connection.getExtraConfig() != null && !connection.getExtraConfig().trim().isEmpty()) {
validateJsonFormat(connection.getExtraConfig(), "扩展配置");
}
// 验证状态
if (connection.getStatus() != null && connection.getStatus() != 0 && connection.getStatus() != 1) {
throw new DataBridgeException("INVALID_STATUS", "状态值无效,只能为0或1");
}
// 验证描述长度
if (connection.getDescription() != null && connection.getDescription().length() > 500) {
throw new DataBridgeException("INVALID_DESCRIPTION", "描述长度不能超过500个字符");
}
// 验证方向
if (connection.getDirection() == null) {
throw new DataBridgeException("INVALID_DIRECTION", "连接方向不能为空");
}
// 验证源范围
if (config.getSourceScope() == null) {
throw new DataBridgeException("INVALID_SOURCE_SCOPE", "源范围不能为空");
}
/**
* 验证名称
*/
private static void validateName(String name, String fieldName) {
if (name == null || name.trim().isEmpty()) {
throw new DataBridgeException("INVALID_NAME", fieldName + "不能为空");
}
if (!NAME_PATTERN.matcher(name).matches()) {
throw new DataBridgeException("INVALID_NAME_FORMAT", fieldName + "格式无效,只能包含中文、字母、数字、下划线、中划线,长度1-100");
}
// 验证目标资源ID
if (config.getTargetResourceId() == null || config.getTargetResourceId() <= 0) {
throw new DataBridgeException("INVALID_TARGET_RESOURCE_ID", "目标资源ID无效");
}
/**
* 验证产品KEY
*/
private static void validateProductKey(String productKey) {
if (productKey == null || productKey.trim().isEmpty()) {
throw new DataBridgeException("INVALID_PRODUCT_KEY", "产品KEY不能为空");
}
if (!PRODUCT_KEY_PATTERN.matcher(productKey).matches()) {
throw new DataBridgeException("INVALID_PRODUCT_KEY_FORMAT", "产品KEY格式无效,只能包含字母、数字、下划线,长度1-50");
}
// 验证桥接类型
if (config.getBridgeType() == null) {
throw new DataBridgeException("INVALID_BRIDGE_TYPE", "桥接类型不能为空");
}
/**
* 验证主机地址
*/
private static void validateHost(String host) {
if (host == null || host.trim().isEmpty()) {
throw new DataBridgeException("INVALID_HOST", "主机地址不能为空");
}
if (!HOST_PATTERN.matcher(host).matches()) {
throw new DataBridgeException("INVALID_HOST_FORMAT", "主机地址格式无效");
}
// 验证模板
if (config.getTemplate() == null || config.getTemplate().trim().isEmpty()) {
throw new DataBridgeException("INVALID_TEMPLATE", "模板内容不能为空");
}
/**
* 验证数据库名
*/
private static void validateDatabaseName(String databaseName) {
if (databaseName == null || databaseName.trim().isEmpty()) {
throw new DataBridgeException("INVALID_DATABASE_NAME", "数据库名不能为空");
}
if (!DATABASE_NAME_PATTERN.matcher(databaseName).matches()) {
throw new DataBridgeException("INVALID_DATABASE_NAME_FORMAT", "数据库名格式无效,只能包含字母、数字、下划线,长度1-64");
}
// 验证统一配置JSON格式
if (config.getConfig() != null && !config.getConfig().trim().isEmpty()) {
validateJsonFormat(config.getConfig(), "统一配置");
}
/**
* 验证JSON格式
*/
private static void validateJsonFormat(String json, String fieldName) {
try {
objectMapper.readTree(json);
} catch (JsonProcessingException e) {
throw new DataBridgeException("INVALID_JSON_FORMAT", fieldName + "JSON格式无效: " + e.getMessage());
}
// 验证Magic脚本
if (config.getMagicScript() != null && !config.getMagicScript().trim().isEmpty()) {
// Magic脚本内容验证可以在这里添加
}
// 验证状态
if (config.getStatus() != null && config.getStatus() != 0 && config.getStatus() != 1) {
throw new DataBridgeException("INVALID_STATUS", "状态值无效,只能为0或1");
}
// 验证描述长度
if (config.getDescription() != null && config.getDescription().length() > 500) {
throw new DataBridgeException("INVALID_DESCRIPTION", "描述长度不能超过500个字符");
}
}
/** 验证资源连接 */
public static void validateResourceConnection(ResourceConnection connection) {
if (connection == null) {
throw new DataBridgeException("CONNECTION_NULL", "资源连接不能为空");
}
// 验证名称
validateName(connection.getName(), "资源连接名称");
// 验证资源类型
if (connection.getType() == null) {
throw new DataBridgeException("INVALID_RESOURCE_TYPE", "资源类型不能为空");
}
// 验证数据流向
if (connection.getDataDirection() == null) {
throw new DataBridgeException("INVALID_DATA_DIRECTION", "数据流向不能为空");
}
// 验证扩展配置JSON格式(包含动态配置)
if (connection.getExtraConfig() != null && !connection.getExtraConfig().trim().isEmpty()) {
validateJsonFormat(connection.getExtraConfig(), "扩展配置");
}
// 验证状态
if (connection.getStatus() != null
&& connection.getStatus() != 0
&& connection.getStatus() != 1) {
throw new DataBridgeException("INVALID_STATUS", "状态值无效,只能为0或1");
}
// 验证描述长度
if (connection.getDescription() != null && connection.getDescription().length() > 500) {
throw new DataBridgeException("INVALID_DESCRIPTION", "描述长度不能超过500个字符");
}
// 验证方向
if (connection.getDirection() == null) {
throw new DataBridgeException("INVALID_DIRECTION", "连接方向不能为空");
}
}
/** 验证名称 */
private static void validateName(String name, String fieldName) {
if (name == null || name.trim().isEmpty()) {
throw new DataBridgeException("INVALID_NAME", fieldName + "不能为空");
}
if (!NAME_PATTERN.matcher(name).matches()) {
throw new DataBridgeException(
"INVALID_NAME_FORMAT", fieldName + "格式无效,只能包含中文、字母、数字、下划线、中划线,长度1-100");
}
}
/** 验证产品KEY */
private static void validateProductKey(String productKey) {
if (productKey == null || productKey.trim().isEmpty()) {
throw new DataBridgeException("INVALID_PRODUCT_KEY", "产品KEY不能为空");
}
if (!PRODUCT_KEY_PATTERN.matcher(productKey).matches()) {
throw new DataBridgeException("INVALID_PRODUCT_KEY_FORMAT", "产品KEY格式无效,只能包含字母、数字、下划线,长度1-50");
}
}
/** 验证主机地址 */
private static void validateHost(String host) {
if (host == null || host.trim().isEmpty()) {
throw new DataBridgeException("INVALID_HOST", "主机地址不能为空");
}
if (!HOST_PATTERN.matcher(host).matches()) {
throw new DataBridgeException("INVALID_HOST_FORMAT", "主机地址格式无效");
}
}
/** 验证数据库名 */
private static void validateDatabaseName(String databaseName) {
if (databaseName == null || databaseName.trim().isEmpty()) {
throw new DataBridgeException("INVALID_DATABASE_NAME", "数据库名不能为空");
}
if (!DATABASE_NAME_PATTERN.matcher(databaseName).matches()) {
throw new DataBridgeException(
"INVALID_DATABASE_NAME_FORMAT", "数据库名格式无效,只能包含字母、数字、下划线,长度1-64");
}
}
/** 验证JSON格式 */
private static void validateJsonFormat(String json, String fieldName) {
try {
objectMapper.readTree(json);
} catch (JsonProcessingException e) {
throw new DataBridgeException(
"INVALID_JSON_FORMAT", fieldName + "JSON格式无效: " + e.getMessage());
}
}
}
@@ -23,8 +23,7 @@ import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
/**
* 数据桥接连接池管理器
* 独立管理数据桥接的数据库连接池,避免与框架的tk.mybatis冲突
* 数据桥接连接池管理器 独立管理数据桥接的数据库连接池,避免与框架的tk.mybatis冲突
*
* @author gitee.com/NexIoT
* @version 1.0
@@ -33,375 +32,384 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DataBridgeConnectionManager {
// 连接池缓存 - 使用独立的命名空间避免冲突
private final ConcurrentMap<String, DataSource> connectionPoolCache = new ConcurrentHashMap<>();
// 保活调度器
private final ScheduledExecutorService keepAliveScheduler = Executors.newScheduledThreadPool(1);
// 连接池配置 - 针对数据桥接优化
private static final int MAX_POOL_SIZE = 3; // 数据桥接不需要太多连接
private static final int MIN_IDLE = 1;
private static final long CONNECTION_TIMEOUT = 20000L; // 20秒连接超时
private static final long IDLE_TIMEOUT = 300000L; // 5分钟空闲超时
private static final long MAX_LIFETIME = 1800000L; // 30分钟最大生命周期
private static final long VALIDATION_TIMEOUT = 2000L; // 2秒验证超时
// 保活配置
private static final long KEEP_ALIVE_INTERVAL = 300000L; // 5分钟保活间隔
public DataBridgeConnectionManager() {
// 启动保活任务
startKeepAliveTask();
// 连接池缓存 - 使用独立的命名空间避免冲突
private final ConcurrentMap<String, DataSource> connectionPoolCache = new ConcurrentHashMap<>();
// 保活调度器
private final ScheduledExecutorService keepAliveScheduler = Executors.newScheduledThreadPool(1);
// 连接池配置 - 针对数据桥接优化
private static final int MAX_POOL_SIZE = 3; // 数据桥接不需要太多连接
private static final int MIN_IDLE = 1;
private static final long CONNECTION_TIMEOUT = 20000L; // 20秒连接超时
private static final long IDLE_TIMEOUT = 300000L; // 5分钟空闲超时
private static final long MAX_LIFETIME = 1800000L; // 30分钟最大生命周期
private static final long VALIDATION_TIMEOUT = 2000L; // 2秒验证超时
// 保活配置
private static final long KEEP_ALIVE_INTERVAL = 300000L; // 5分钟保活间隔
public DataBridgeConnectionManager() {
// 启动保活任务
startKeepAliveTask();
}
/**
* 获取或创建数据源
*
* @param connection 连接配置
* @return 数据源
*/
public DataSource getOrCreateDataSource(ResourceConnection connection) {
String cacheKey = generateCacheKey(connection);
// 先检查缓存中是否已有连接池
DataSource existingDataSource = connectionPoolCache.get(cacheKey);
if (existingDataSource != null) {
// 检查连接池是否健康
if (isDataSourceHealthy(existingDataSource)) {
log.debug("复用现有数据桥接连接池: {}", cacheKey);
return existingDataSource;
} else {
log.warn("连接池不健康,移除并重新创建: {}", cacheKey);
removeDataSource(connection);
}
}
/**
* 获取或创建数据源
*
* @param connection 连接配置
* @return 数据源
*/
public DataSource getOrCreateDataSource(ResourceConnection connection) {
String cacheKey = generateCacheKey(connection);
// 先检查缓存中是否已有连接池
DataSource existingDataSource = connectionPoolCache.get(cacheKey);
if (existingDataSource != null) {
// 检查连接池是否健康
if (isDataSourceHealthy(existingDataSource)) {
log.debug("复用现有数据桥接连接池: {}", cacheKey);
return existingDataSource;
} else {
log.warn("连接池不健康,移除并重新创建: {}", cacheKey);
removeDataSource(connection);
}
}
// 创建新的连接池
return connectionPoolCache.computeIfAbsent(cacheKey, key -> {
log.info("创建数据桥接连接池: {}", key);
return createDataBridgeDataSource(connection);
// 创建新的连接池
return connectionPoolCache.computeIfAbsent(
cacheKey,
key -> {
log.info("创建数据桥接连接池: {}", key);
return createDataBridgeDataSource(connection);
});
}
/**
* 创建数据桥接专用的数据源
*
* @param connection 连接配置
* @return 数据源
*/
private DataSource createDataBridgeDataSource(ResourceConnection connection) {
try {
log.info("创建数据桥接连接池: {}:{}", connection.getHost(), connection.getPort());
// 测试网络连接
testNetworkConnection(connection);
HikariConfig config = new HikariConfig();
// 基本连接信息
String driverClassName = getDriverClassName(connection.getType().name());
String jdbcUrl = buildConnectionUrl(connection);
config.setDriverClassName(driverClassName);
config.setJdbcUrl(jdbcUrl);
config.setUsername(connection.getUsername());
config.setPassword(connection.getPassword());
// 数据桥接专用连接池配置
config.setMaximumPoolSize(MAX_POOL_SIZE);
config.setMinimumIdle(MIN_IDLE);
config.setConnectionTimeout(CONNECTION_TIMEOUT);
config.setIdleTimeout(IDLE_TIMEOUT);
config.setMaxLifetime(MAX_LIFETIME);
config.setValidationTimeout(VALIDATION_TIMEOUT);
// 连接池名称 - 使用独立前缀避免冲突
config.setPoolName("DataBridge-" + connection.getId());
// 连接测试配置
config.setConnectionTestQuery("SELECT 1");
config.setConnectionInitSql("SET SESSION wait_timeout=28800");
// 数据桥接专用配置
config.setInitializationFailTimeout(15000); // 15秒初始化超时
config.setRegisterMbeans(false); // 关闭JMX监控,避免冲突
config.setLeakDetectionThreshold(30000); // 30秒连接泄漏检测
config.setAutoCommit(true);
config.setKeepaliveTime(30000); // 30秒保活
// MySQL连接属性 - 数据桥接优化
if ("MYSQL".equalsIgnoreCase(connection.getType().name())) {
addMySQLProperties(config);
}
// 创建数据源
HikariDataSource dataSource = new HikariDataSource(config);
// 测试连接
testDataSource(dataSource);
log.info("数据桥接连接池创建成功");
return dataSource;
} catch (Exception e) {
log.error(
"创建数据桥接连接池失败: {}:{} - {}", connection.getHost(), connection.getPort(), e.getMessage());
// 根据错误类型提供解决建议
if (e.getMessage().contains("Access denied")) {
log.error(
"解决建议: 检查MySQL用户权限,确保用户 '{}' 有访问数据库 '{}' 的权限",
connection.getUsername(),
getDatabaseName(connection));
} else if (e.getMessage().contains("Could not create connection")) {
log.error("解决建议: 检查网络连接和MySQL服务,确保端口 {} 可访问", connection.getPort());
}
throw new RuntimeException("数据桥接连接池创建失败: " + e.getMessage(), e);
}
}
/**
* 创建数据桥接专用的数据源
*
* @param connection 连接配置
* @return 数据源
*/
private DataSource createDataBridgeDataSource(ResourceConnection connection) {
try {
log.info("创建数据桥接连接池: {}:{}", connection.getHost(), connection.getPort());
// 测试网络连接
testNetworkConnection(connection);
HikariConfig config = new HikariConfig();
/**
* 添加MySQL特定属性
*
* @param config HikariCP配置
*/
private void addMySQLProperties(HikariConfig config) {
config.addDataSourceProperty("useSSL", "false");
config.addDataSourceProperty("allowPublicKeyRetrieval", "true");
config.addDataSourceProperty("autoReconnect", "true");
config.addDataSourceProperty("failOverReadOnly", "false");
config.addDataSourceProperty("maxReconnects", "2"); // 减少重连次数
config.addDataSourceProperty("initialTimeout", "1");
config.addDataSourceProperty("connectTimeout", "20000");
config.addDataSourceProperty("socketTimeout", "20000");
config.addDataSourceProperty("useLocalSessionState", "true");
config.addDataSourceProperty("rewriteBatchedStatements", "true");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("useServerPrepStmts", "true");
config.addDataSourceProperty("maintainTimeStats", "false");
config.addDataSourceProperty("elideSetAutoCommits", "true");
config.addDataSourceProperty("useLocalTransactionState", "true");
config.addDataSourceProperty("alwaysSendSetIsolation", "false");
config.addDataSourceProperty("cacheCallableStmts", "true");
config.addDataSourceProperty("cacheResultSetMetadata", "true");
config.addDataSourceProperty("cacheServerConfiguration", "true");
}
// 基本连接信息
String driverClassName = getDriverClassName(connection.getType().name());
String jdbcUrl = buildConnectionUrl(connection);
config.setDriverClassName(driverClassName);
config.setJdbcUrl(jdbcUrl);
config.setUsername(connection.getUsername());
config.setPassword(connection.getPassword());
/**
* 测试网络连接
*
* @param connection 连接配置
*/
private void testNetworkConnection(ResourceConnection connection) {
try {
java.net.Socket socket = new java.net.Socket();
socket.connect(
new java.net.InetSocketAddress(connection.getHost(), connection.getPort()), 5000);
socket.close();
} catch (Exception e) {
log.error("网络连接测试失败: {}:{} - {}", connection.getHost(), connection.getPort(), e.getMessage());
throw new RuntimeException("无法连接到数据库服务器: " + e.getMessage(), e);
}
}
// 数据桥接专用连接池配置
config.setMaximumPoolSize(MAX_POOL_SIZE);
config.setMinimumIdle(MIN_IDLE);
config.setConnectionTimeout(CONNECTION_TIMEOUT);
config.setIdleTimeout(IDLE_TIMEOUT);
config.setMaxLifetime(MAX_LIFETIME);
config.setValidationTimeout(VALIDATION_TIMEOUT);
/**
* 测试数据源连接
*
* @param dataSource 数据源
*/
private void testDataSource(DataSource dataSource) {
try (java.sql.Connection conn = dataSource.getConnection()) {
if (!conn.isValid(2)) {
throw new RuntimeException("连接无效");
}
} catch (Exception e) {
log.error("数据桥接连接测试失败: {}", e.getMessage());
throw new RuntimeException("数据桥接连接测试失败: " + e.getMessage(), e);
}
}
// 连接池名称 - 使用独立前缀避免冲突
config.setPoolName("DataBridge-" + connection.getId());
/**
* 检查数据源是否健康
*
* @param dataSource 数据源
* @return 是否健康
*/
private boolean isDataSourceHealthy(DataSource dataSource) {
try {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
// 连接测试配置
config.setConnectionTestQuery("SELECT 1");
config.setConnectionInitSql("SET SESSION wait_timeout=28800");
// 数据桥接专用配置
config.setInitializationFailTimeout(15000); // 15秒初始化超时
config.setRegisterMbeans(false); // 关闭JMX监控,避免冲突
config.setLeakDetectionThreshold(30000); // 30秒连接泄漏检测
config.setAutoCommit(true);
config.setKeepaliveTime(30000); // 30秒保活
// MySQL连接属性 - 数据桥接优化
if ("MYSQL".equalsIgnoreCase(connection.getType().name())) {
addMySQLProperties(config);
}
// 创建数据源
HikariDataSource dataSource = new HikariDataSource(config);
// 测试连接
testDataSource(dataSource);
log.info("数据桥接连接池创建成功");
return dataSource;
} catch (Exception e) {
log.error("创建数据桥接连接池失败: {}:{} - {}",
connection.getHost(), connection.getPort(), e.getMessage());
// 根据错误类型提供解决建议
if (e.getMessage().contains("Access denied")) {
log.error("解决建议: 检查MySQL用户权限,确保用户 '{}' 有访问数据库 '{}' 的权限",
connection.getUsername(), getDatabaseName(connection));
} else if (e.getMessage().contains("Could not create connection")) {
log.error("解决建议: 检查网络连接和MySQL服务,确保端口 {} 可访问", connection.getPort());
}
throw new RuntimeException("数据桥接连接池创建失败: " + e.getMessage(), e);
// 检查连接池是否已关闭
if (hikariDataSource.isClosed()) {
return false;
}
}
/**
* 添加MySQL特定属性
*
* @param config HikariCP配置
*/
private void addMySQLProperties(HikariConfig config) {
config.addDataSourceProperty("useSSL", "false");
config.addDataSourceProperty("allowPublicKeyRetrieval", "true");
config.addDataSourceProperty("autoReconnect", "true");
config.addDataSourceProperty("failOverReadOnly", "false");
config.addDataSourceProperty("maxReconnects", "2"); // 减少重连次数
config.addDataSourceProperty("initialTimeout", "1");
config.addDataSourceProperty("connectTimeout", "20000");
config.addDataSourceProperty("socketTimeout", "20000");
config.addDataSourceProperty("useLocalSessionState", "true");
config.addDataSourceProperty("rewriteBatchedStatements", "true");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("useServerPrepStmts", "true");
config.addDataSourceProperty("maintainTimeStats", "false");
config.addDataSourceProperty("elideSetAutoCommits", "true");
config.addDataSourceProperty("useLocalTransactionState", "true");
config.addDataSourceProperty("alwaysSendSetIsolation", "false");
config.addDataSourceProperty("cacheCallableStmts", "true");
config.addDataSourceProperty("cacheResultSetMetadata", "true");
config.addDataSourceProperty("cacheServerConfiguration", "true");
}
/**
* 测试网络连接
*
* @param connection 连接配置
*/
private void testNetworkConnection(ResourceConnection connection) {
try {
java.net.Socket socket = new java.net.Socket();
socket.connect(new java.net.InetSocketAddress(connection.getHost(), connection.getPort()), 5000);
socket.close();
} catch (Exception e) {
log.error("网络连接测试失败: {}:{} - {}", connection.getHost(), connection.getPort(), e.getMessage());
throw new RuntimeException("无法连接到数据库服务器: " + e.getMessage(), e);
// 检查连接池状态
try (java.sql.Connection conn = hikariDataSource.getConnection()) {
return conn.isValid(2);
}
}
}
/**
* 测试数据源连接
*
* @param dataSource 数据源
*/
private void testDataSource(DataSource dataSource) {
try (java.sql.Connection conn = dataSource.getConnection()) {
if (!conn.isValid(2)) {
throw new RuntimeException("连接无效");
}
} catch (Exception e) {
log.error("数据桥接连接测试失败: {}", e.getMessage());
throw new RuntimeException("数据桥接连接测试失败: " + e.getMessage(), e);
}
// 对于非HikariCP数据源,尝试获取连接
try (java.sql.Connection conn = dataSource.getConnection()) {
return conn.isValid(2);
}
} catch (Exception e) {
return false;
}
}
/**
* 检查数据源是否健康
*
* @param dataSource 数据源
* @return 是否健康
*/
private boolean isDataSourceHealthy(DataSource dataSource) {
try {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
// 检查连接池是否已关闭
if (hikariDataSource.isClosed()) {
return false;
}
// 检查连接池状态
try (java.sql.Connection conn = hikariDataSource.getConnection()) {
return conn.isValid(2);
}
}
// 对于非HikariCP数据源,尝试获取连接
try (java.sql.Connection conn = dataSource.getConnection()) {
return conn.isValid(2);
}
} catch (Exception e) {
return false;
}
/**
* 构建连接URL
*
* @param connection 连接配置
* @return 连接URL
*/
private String buildConnectionUrl(ResourceConnection connection) {
return String.format(
"jdbc:mysql://%s:%d/%s?useSSL=false&allowPublicKeyRetrieval=true&autoReconnect=true&failOverReadOnly=false&maxReconnects=2&initialTimeout=1&connectTimeout=20000&socketTimeout=20000&useLocalSessionState=true&rewriteBatchedStatements=true&cachePrepStmts=true&useServerPrepStmts=true&maintainTimeStats=false&elideSetAutoCommits=true&useLocalTransactionState=true&alwaysSendSetIsolation=false&cacheCallableStmts=true&cacheResultSetMetadata=true&cacheServerConfiguration=true",
connection.getHost(), connection.getPort(), getDatabaseName(connection));
}
/**
* 获取数据库名称
*
* @param connection 连接配置
* @return 数据库名称
*/
private String getDatabaseName(ResourceConnection connection) {
// 使用数据库名称字段,如果为空则使用默认值
String databaseName = connection.getDatabaseName();
return databaseName != null && !databaseName.trim().isEmpty() ? databaseName : "mysql";
}
/**
* 获取驱动类名
*
* @param dbType 数据库类型
* @return 驱动类名
*/
private String getDriverClassName(String dbType) {
return switch (dbType.toUpperCase()) {
case "MYSQL" -> "com.mysql.cj.jdbc.Driver";
case "POSTGRESQL" -> "org.postgresql.Driver";
case "ORACLE" -> "oracle.jdbc.driver.OracleDriver";
case "SQLSERVER" -> "com.microsoft.sqlserver.jdbc.SQLServerDriver";
default -> throw new IllegalArgumentException("不支持的数据库类型: " + dbType);
};
}
/**
* 生成缓存键 - 使用独立前缀避免冲突
*
* @param connection 连接配置
* @return 缓存键
*/
private String generateCacheKey(ResourceConnection connection) {
return String.format(
"DataBridge_%s_%s_%s_%s_%s",
connection.getType().name(),
connection.getHost(),
connection.getPort(),
connection.getUsername(),
getDatabaseName(connection));
}
/**
* 移除指定连接池
*
* @param connection 连接配置
*/
public void removeDataSource(ResourceConnection connection) {
String cacheKey = generateCacheKey(connection);
DataSource dataSource = connectionPoolCache.remove(cacheKey);
if (dataSource instanceof HikariDataSource) {
try {
((HikariDataSource) dataSource).close();
log.info("已关闭数据桥接连接池: {}", cacheKey);
} catch (Exception e) {
log.warn("关闭数据桥接连接池失败: {} - {}", cacheKey, e.getMessage());
}
}
}
/**
* 构建连接URL
*
* @param connection 连接配置
* @return 连接URL
*/
private String buildConnectionUrl(ResourceConnection connection) {
return String.format("jdbc:mysql://%s:%d/%s?useSSL=false&allowPublicKeyRetrieval=true&autoReconnect=true&failOverReadOnly=false&maxReconnects=2&initialTimeout=1&connectTimeout=20000&socketTimeout=20000&useLocalSessionState=true&rewriteBatchedStatements=true&cachePrepStmts=true&useServerPrepStmts=true&maintainTimeStats=false&elideSetAutoCommits=true&useLocalTransactionState=true&alwaysSendSetIsolation=false&cacheCallableStmts=true&cacheResultSetMetadata=true&cacheServerConfiguration=true",
connection.getHost(),
connection.getPort(),
getDatabaseName(connection));
}
/**
* 获取数据库名称
*
* @param connection 连接配置
* @return 数据库名称
*/
private String getDatabaseName(ResourceConnection connection) {
// 使用数据库名称字段,如果为空则使用默认值
String databaseName = connection.getDatabaseName();
return databaseName != null && !databaseName.trim().isEmpty() ? databaseName : "mysql";
}
/**
* 获取驱动类名
*
* @param dbType 数据库类型
* @return 驱动类名
*/
private String getDriverClassName(String dbType) {
return switch (dbType.toUpperCase()) {
case "MYSQL" -> "com.mysql.cj.jdbc.Driver";
case "POSTGRESQL" -> "org.postgresql.Driver";
case "ORACLE" -> "oracle.jdbc.driver.OracleDriver";
case "SQLSERVER" -> "com.microsoft.sqlserver.jdbc.SQLServerDriver";
default -> throw new IllegalArgumentException("不支持的数据库类型: " + dbType);
};
}
/**
* 生成缓存键 - 使用独立前缀避免冲突
*
* @param connection 连接配置
* @return 缓存键
*/
private String generateCacheKey(ResourceConnection connection) {
return String.format("DataBridge_%s_%s_%s_%s_%s",
connection.getType().name(),
connection.getHost(),
connection.getPort(),
connection.getUsername(),
getDatabaseName(connection));
}
/**
* 移除指定连接池
*
* @param connection 连接配置
*/
public void removeDataSource(ResourceConnection connection) {
String cacheKey = generateCacheKey(connection);
DataSource dataSource = connectionPoolCache.remove(cacheKey);
if (dataSource instanceof HikariDataSource) {
try {
((HikariDataSource) dataSource).close();
log.info("已关闭数据桥接连接池: {}", cacheKey);
} catch (Exception e) {
log.warn("关闭数据桥接连接池失败: {} - {}", cacheKey, e.getMessage());
}
}
}
/**
* 启动保活任务
*/
private void startKeepAliveTask() {
keepAliveScheduler.scheduleWithFixedDelay(() -> {
try {
connectionPoolCache.forEach((key, dataSource) -> {
if (!isDataSourceHealthy(dataSource)) {
log.warn("检测到不健康的连接池: {}", key);
}
/** 启动保活任务 */
private void startKeepAliveTask() {
keepAliveScheduler.scheduleWithFixedDelay(
() -> {
try {
connectionPoolCache.forEach(
(key, dataSource) -> {
if (!isDataSourceHealthy(dataSource)) {
log.warn("检测到不健康的连接池: {}", key);
}
});
} catch (Exception e) {
log.error("连接池保活检查失败: {}", e.getMessage());
}
}, KEEP_ALIVE_INTERVAL, KEEP_ALIVE_INTERVAL, TimeUnit.MILLISECONDS);
log.info("数据桥接连接池保活任务已启动,检查间隔: {}秒", KEEP_ALIVE_INTERVAL / 1000);
} catch (Exception e) {
log.error("连接池保活检查失败: {}", e.getMessage());
}
},
KEEP_ALIVE_INTERVAL,
KEEP_ALIVE_INTERVAL,
TimeUnit.MILLISECONDS);
log.info("数据桥接连接池保活任务已启动,检查间隔: {}秒", KEEP_ALIVE_INTERVAL / 1000);
}
/** 关闭所有连接池 */
public void shutdown() {
log.info("开始关闭所有数据桥接连接池...");
// 关闭保活调度器
keepAliveScheduler.shutdown();
try {
if (!keepAliveScheduler.awaitTermination(5, TimeUnit.SECONDS)) {
keepAliveScheduler.shutdownNow();
}
} catch (InterruptedException e) {
keepAliveScheduler.shutdownNow();
Thread.currentThread().interrupt();
}
/**
* 关闭所有连接池
*/
public void shutdown() {
log.info("开始关闭所有数据桥接连接池...");
// 关闭保活调度器
keepAliveScheduler.shutdown();
try {
if (!keepAliveScheduler.awaitTermination(5, TimeUnit.SECONDS)) {
keepAliveScheduler.shutdownNow();
}
} catch (InterruptedException e) {
keepAliveScheduler.shutdownNow();
Thread.currentThread().interrupt();
}
// 关闭所有连接池
connectionPoolCache.forEach((key, dataSource) -> {
try {
if (dataSource instanceof HikariDataSource) {
((HikariDataSource) dataSource).close();
}
} catch (Exception e) {
log.warn("关闭数据桥接连接池失败: {} - {}", key, e.getMessage());
}
});
connectionPoolCache.clear();
log.info("所有数据桥接连接池已关闭");
}
/**
* 获取连接池状态
*
* @return 连接池状态信息
*/
public String getPoolStatus() {
StringBuilder status = new StringBuilder();
status.append("数据桥接连接池状态:\n");
connectionPoolCache.forEach((key, dataSource) -> {
// 关闭所有连接池
connectionPoolCache.forEach(
(key, dataSource) -> {
try {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
status.append(String.format(" %s: 活跃=%d, 空闲=%d, 等待=%d\n",
key,
hikariDataSource.getHikariPoolMXBean().getActiveConnections(),
hikariDataSource.getHikariPoolMXBean().getIdleConnections(),
hikariDataSource.getHikariPoolMXBean().getThreadsAwaitingConnection()));
((HikariDataSource) dataSource).close();
}
} catch (Exception e) {
log.warn("关闭数据桥接连接池失败: {} - {}", key, e.getMessage());
}
});
return status.toString();
}
connectionPoolCache.clear();
log.info("所有数据桥接连接池已关闭");
}
/**
* 获取连接池数量
*
* @return 连接池数量
*/
public int getPoolCount() {
return connectionPoolCache.size();
}
/**
* 获取连接池状态
*
* @return 连接池状态信息
*/
public String getPoolStatus() {
StringBuilder status = new StringBuilder();
status.append("数据桥接连接池状态:\n");
connectionPoolCache.forEach(
(key, dataSource) -> {
if (dataSource instanceof HikariDataSource) {
HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
status.append(
String.format(
" %s: 活跃=%d, 空闲=%d, 等待=%d\n",
key,
hikariDataSource.getHikariPoolMXBean().getActiveConnections(),
hikariDataSource.getHikariPoolMXBean().getIdleConnections(),
hikariDataSource.getHikariPoolMXBean().getThreadsAwaitingConnection()));
}
});
return status.toString();
}
/**
* 获取连接池数量
*
* @return 连接池数量
*/
public int getPoolCount() {
return connectionPoolCache.size();
}
}
@@ -18,8 +18,7 @@ import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
/**
* SQL模板处理器
* 智能处理SQL模板中的变量替换,避免引号冲突
* SQL模板处理器 智能处理SQL模板中的变量替换,避免引号冲突
*
* @author gitee.com/NexIoT
* @version 1.0
@@ -28,142 +27,142 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SqlTemplateProcessor {
/**
* 处理SQL模板,智能处理引号冲突
*
* @param template SQL模板
* @param variables 变量映射
* @return 处理后的SQL
*/
public static String processTemplate(String template, Map<String, Object> variables) {
if (template == null || template.trim().isEmpty()) {
return "";
}
// 使用正则表达式匹配所有 #{...} 占位符
Pattern pattern = Pattern.compile("#\\{([a-zA-Z0-9_.]+)\\}");
Matcher matcher = pattern.matcher(template);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String paramPath = matcher.group(1);
Object value = getNestedValue(variables, paramPath);
String replacement = convertToSqlValue(value, template, matcher.start());
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(result);
return result.toString();
/**
* 处理SQL模板,智能处理引号冲突
*
* @param template SQL模板
* @param variables 变量映射
* @return 处理后的SQL
*/
public static String processTemplate(String template, Map<String, Object> variables) {
if (template == null || template.trim().isEmpty()) {
return "";
}
/**
* 智能转换为SQL值,根据上下文选择合适的引号
*
* @param value 要转换的值
* @param template 完整模板
* @param position 当前位置
* @return SQL安全格式的字符串
*/
private static String convertToSqlValue(Object value, String template, int position) {
if (value == null) {
return "NULL";
}
// 使用正则表达式匹配所有 #{...} 占位符
Pattern pattern = Pattern.compile("#\\{([a-zA-Z0-9_.]+)\\}");
Matcher matcher = pattern.matcher(template);
// 检查上下文,判断是否在JSON字符串中
boolean inJsonContext = isInJsonContext(template, position);
if (inJsonContext) {
// 在JSON上下文中,使用双引号
return convertToJsonValue(value);
} else {
// 在普通SQL上下文中,使用单引号
return SqlValueConverter.convertToSqlValue(value);
}
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String paramPath = matcher.group(1);
Object value = getNestedValue(variables, paramPath);
String replacement = convertToSqlValue(value, template, matcher.start());
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(result);
return result.toString();
}
/**
* 智能转换为SQL值,根据上下文选择合适的引号
*
* @param value 要转换的值
* @param template 完整模板
* @param position 当前位置
* @return SQL安全格式的字符串
*/
private static String convertToSqlValue(Object value, String template, int position) {
if (value == null) {
return "NULL";
}
/**
* 检查当前位置是否在JSON字符串上下文中
*
* @param template 模板
* @param position 位置
* @return 是否在JSON上下文中
*/
private static boolean isInJsonContext(String template, int position) {
// 向前查找最近的引号
int singleQuotePos = template.lastIndexOf("'", position);
int doubleQuotePos = template.lastIndexOf("\"", position);
// 如果最近的是双引号,说明在JSON上下文中
if (doubleQuotePos > singleQuotePos) {
return true;
}
// 检查是否在JSON对象中(包含大括号)
String beforePosition = template.substring(0, position);
int lastOpenBrace = beforePosition.lastIndexOf("{");
int lastCloseBrace = beforePosition.lastIndexOf("}");
// 如果最近的大括号是开括号,说明在JSON对象中
if (lastOpenBrace > lastCloseBrace) {
return true;
}
return false;
// 检查上下文,判断是否在JSON字符串中
boolean inJsonContext = isInJsonContext(template, position);
if (inJsonContext) {
// 在JSON上下文中,使用双引号
return convertToJsonValue(value);
} else {
// 在普通SQL上下文中,使用单引号
return SqlValueConverter.convertToSqlValue(value);
}
}
/**
* 检查当前位置是否在JSON字符串上下文中
*
* @param template 模板
* @param position 位置
* @return 是否在JSON上下文中
*/
private static boolean isInJsonContext(String template, int position) {
// 向前查找最近的引号
int singleQuotePos = template.lastIndexOf("'", position);
int doubleQuotePos = template.lastIndexOf("\"", position);
// 如果最近的是双引号,说明在JSON上下文中
if (doubleQuotePos > singleQuotePos) {
return true;
}
/**
* 转换为JSON值
*
* @param value 要转换的值
* @return JSON格式的字符串
*/
private static String convertToJsonValue(Object value) {
if (value == null) {
return "null";
}
if (value instanceof String) {
// 字符串:转义双引号
String strValue = (String) value;
return "\"" + strValue.replace("\"", "\\\"") + "\"";
} else if (value instanceof Number || value instanceof Boolean) {
// 数字和布尔类型直接转换
return value.toString();
} else {
// 其他类型:转为JSON字符串
return JSONUtil.toJsonStr(value);
}
// 检查是否在JSON对象中(包含大括号)
String beforePosition = template.substring(0, position);
int lastOpenBrace = beforePosition.lastIndexOf("{");
int lastCloseBrace = beforePosition.lastIndexOf("}");
// 如果最近的大括号是开括号,说明在JSON对象中
if (lastOpenBrace > lastCloseBrace) {
return true;
}
/**
* 获取嵌套属性值
*
* @param variables 变量映射
* @param path 属性路径
* @return 属性值
*/
@SuppressWarnings("unchecked")
private static Object getNestedValue(Map<String, Object> variables, String path) {
if (path == null || path.trim().isEmpty()) {
return null;
}
return false;
}
String[] keys = path.split("\\.");
Object current = variables;
for (String key : keys) {
if (current == null) {
return null;
}
if (current instanceof Map) {
current = ((Map<String, Object>) current).get(key);
} else {
return null;
}
}
return current;
/**
* 转换为JSON值
*
* @param value 要转换的值
* @return JSON格式的字符串
*/
private static String convertToJsonValue(Object value) {
if (value == null) {
return "null";
}
if (value instanceof String) {
// 字符串:转义双引号
String strValue = (String) value;
return "\"" + strValue.replace("\"", "\\\"") + "\"";
} else if (value instanceof Number || value instanceof Boolean) {
// 数字和布尔类型直接转换
return value.toString();
} else {
// 其他类型:转为JSON字符串
return JSONUtil.toJsonStr(value);
}
}
/**
* 获取嵌套属性值
*
* @param variables 变量映射
* @param path 属性路径
* @return 属性值
*/
@SuppressWarnings("unchecked")
private static Object getNestedValue(Map<String, Object> variables, String path) {
if (path == null || path.trim().isEmpty()) {
return null;
}
String[] keys = path.split("\\.");
Object current = variables;
for (String key : keys) {
if (current == null) {
return null;
}
if (current instanceof Map) {
current = ((Map<String, Object>) current).get(key);
} else {
return null;
}
}
return current;
}
}
@@ -16,8 +16,7 @@ import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
/**
* SQL值转换工具类
* 提供将Java对象转换为SQL安全格式的通用方法
* SQL值转换工具类 提供将Java对象转换为SQL安全格式的通用方法
*
* @author gitee.com/NexIoT
* @version 1.0
@@ -27,12 +26,8 @@ import lombok.extern.slf4j.Slf4j;
public class SqlValueConverter {
/**
* 将变量值转换为SQL安全格式
* 1. 字符串类型:添加单引号,转义内部单引号
* 2. 数字/布尔类型:直接转换为字符串
* 3. null:转换为SQL的NULL关键字
* 4. 日期类型:转换为SQL标准格式
* 5. 其他类型:转为JSON字符串
* 将变量值转换为SQL安全格式 1. 字符串类型:添加单引号,转义内部单引号 2. 数字/布尔类型:直接转换为字符串 3. null:转换为SQL的NULL关键字 4.
* 日期类型:转换为SQL标准格式 5. 其他类型:转为JSON字符串
*
* @param value 要转换的值
* @return SQL安全格式的字符串
@@ -41,7 +36,7 @@ public class SqlValueConverter {
if (value == null) {
return "NULL";
}
if (value instanceof String) {
// 转义单引号(防止SQL注入和语法错误)
String strValue = (String) value;
@@ -69,7 +64,7 @@ public class SqlValueConverter {
if (values == null) {
return new String[0];
}
String[] result = new String[values.length];
for (int i = 0; i < values.length; i++) {
result[i] = convertToSqlValue(values[i]);
@@ -78,8 +73,7 @@ public class SqlValueConverter {
}
/**
* 将变量值转换为SQL安全格式(JSON字符串专用)
* 使用双引号包围JSON字符串,避免单引号冲突
* 将变量值转换为SQL安全格式(JSON字符串专用) 使用双引号包围JSON字符串,避免单引号冲突
*
* @param value 要转换的值
* @return SQL安全格式的字符串
@@ -88,7 +82,7 @@ public class SqlValueConverter {
if (value == null) {
return "NULL";
}
if (value instanceof String) {
// JSON字符串:使用双引号包围,转义内部双引号
String strValue = (String) value;
@@ -116,12 +110,12 @@ public class SqlValueConverter {
if (value == null) {
return false;
}
if (value instanceof String) {
String strValue = (String) value;
return strValue.contains("'") || strValue.contains("\\") || strValue.contains("\"");
}
return !(value instanceof Number || value instanceof Boolean);
}
}
@@ -1,98 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.universal.databridge.mapper.DataBridgeConfigMapper">
<resultMap id="BaseResultMap" type="cn.universal.databridge.entity.DataBridgeConfig">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="source_scope" jdbcType="VARCHAR" property="sourceScope"/>
<result column="source_product_keys" jdbcType="LONGVARCHAR" property="sourceProductKeys"/>
<result column="source_application_id" jdbcType="BIGINT" property="sourceApplicationId"/>
<result column="target_resource_id" jdbcType="BIGINT" property="targetResourceId"/>
<result column="bridge_type" jdbcType="VARCHAR" property="bridgeType"/>
<result column="template" jdbcType="LONGVARCHAR" property="template"/>
<result column="magic_script" jdbcType="LONGVARCHAR" property="magicScript"/>
<result column="config" jdbcType="LONGVARCHAR" property="config"/>
<result column="status" jdbcType="TINYINT" property="status"/>
<result column="description" jdbcType="VARCHAR" property="description"/>
<result column="create_by" jdbcType="VARCHAR" property="createBy"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="update_by" jdbcType="VARCHAR" property="updateBy"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
</resultMap>
<resultMap id="BaseResultMap" type="cn.universal.databridge.entity.DataBridgeConfig">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="source_scope" jdbcType="VARCHAR" property="sourceScope"/>
<result column="source_product_keys" jdbcType="LONGVARCHAR" property="sourceProductKeys"/>
<result column="source_application_id" jdbcType="BIGINT" property="sourceApplicationId"/>
<result column="target_resource_id" jdbcType="BIGINT" property="targetResourceId"/>
<result column="bridge_type" jdbcType="VARCHAR" property="bridgeType"/>
<result column="template" jdbcType="LONGVARCHAR" property="template"/>
<result column="magic_script" jdbcType="LONGVARCHAR" property="magicScript"/>
<result column="config" jdbcType="LONGVARCHAR" property="config"/>
<result column="status" jdbcType="TINYINT" property="status"/>
<result column="description" jdbcType="VARCHAR" property="description"/>
<result column="create_by" jdbcType="VARCHAR" property="createBy"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="update_by" jdbcType="VARCHAR" property="updateBy"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
</resultMap>
<sql id="Base_Column_List">
id
, name, source_scope, source_product_keys, source_application_id,
<sql id="Base_Column_List">
id
, name, source_scope, source_product_keys, source_application_id,
target_resource_id, bridge_type, template, magic_script, config,
status, description, create_by, create_time, update_by, update_time
</sql>
</sql>
<!-- 根据产品KEY获取活跃配置(兼容新结构:ALL_PRODUCTS 直接命中;SPECIFIC_PRODUCTS 判断JSON列表包含;APPLICATION 由上层按应用过滤) -->
<select id="selectActiveConfigsByProductKey" parameterType="string" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_data_bridge_config
WHERE status = 1
AND (
source_scope = 'ALL_PRODUCTS'
OR (
source_scope = 'SPECIFIC_PRODUCTS'
AND JSON_CONTAINS(CAST(source_product_keys AS JSON), CONCAT('"', #{productKey}, '"'))
)
)
ORDER BY create_time DESC
</select>
<!-- 根据产品KEY获取活跃配置(兼容新结构:ALL_PRODUCTS 直接命中;SPECIFIC_PRODUCTS 判断JSON列表包含;APPLICATION 由上层按应用过滤) -->
<select id="selectActiveConfigsByProductKey" parameterType="string" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_data_bridge_config
WHERE status = 1
AND (
source_scope = 'ALL_PRODUCTS'
OR (
source_scope = 'SPECIFIC_PRODUCTS'
AND JSON_CONTAINS(CAST(source_product_keys AS JSON), CONCAT('"', #{productKey}, '"'))
)
)
ORDER BY create_time DESC
</select>
<!-- 根据桥接类型获取配置 -->
<select id="selectConfigsByBridgeType" parameterType="string" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_data_bridge_config
WHERE bridge_type = #{bridgeType}
ORDER BY create_time DESC
</select>
<!-- 根据桥接类型获取配置 -->
<select id="selectConfigsByBridgeType" parameterType="string" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_data_bridge_config
WHERE bridge_type = #{bridgeType}
ORDER BY create_time DESC
</select>
<!-- 根据目标资源ID获取配置 -->
<select id="selectConfigsByTargetResourceId" parameterType="long" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_data_bridge_config
WHERE target_resource_id = #{targetResourceId}
ORDER BY create_time DESC
</select>
<!-- 根据目标资源ID获取配置 -->
<select id="selectConfigsByTargetResourceId" parameterType="long" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_data_bridge_config
WHERE target_resource_id = #{targetResourceId}
ORDER BY create_time DESC
</select>
<!-- 批量更新配置状态 -->
<update id="batchUpdateStatus">
UPDATE iot_data_bridge_config
SET status = #{status},
update_by = #{updateBy},
update_time = NOW()
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<!-- 批量更新配置状态 -->
<update id="batchUpdateStatus">
UPDATE iot_data_bridge_config
SET status = #{status},
update_by = #{updateBy},
update_time = NOW()
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<!-- 根据名称查询配置(用于重名检查) -->
<select id="selectByName" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_data_bridge_config
WHERE name = #{name}
<if test="excludeId != null">
AND id != #{excludeId}
</if>
LIMIT 1
</select>
<!-- 根据名称查询配置(用于重名检查) -->
<select id="selectByName" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_data_bridge_config
WHERE name = #{name}
<if test="excludeId != null">
AND id != #{excludeId}
</if>
LIMIT 1
</select>
<!-- 根据创建者查询配置列表 -->
<select id="selectByCreateBy" parameterType="string" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_data_bridge_config
WHERE create_by = #{createBy}
ORDER BY create_time DESC
</select>
<!-- 根据创建者查询配置列表 -->
<select id="selectByCreateBy" parameterType="string" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_data_bridge_config
WHERE create_by = #{createBy}
ORDER BY create_time DESC
</select>
</mapper>
@@ -1,38 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.universal.databridge.mapper.DataInputLogMapper">
<!-- 查询最近的日志 -->
<select id="selectRecentLogs" resultType="cn.universal.databridge.entity.DataInputLog">
SELECT *
FROM iot_data_input_log
WHERE config_id = #{configId}
ORDER BY create_time DESC
LIMIT #{limit}
</select>
<!-- 查询最近的日志 -->
<select id="selectRecentLogs" resultType="cn.universal.databridge.entity.DataInputLog">
SELECT *
FROM iot_data_input_log
WHERE config_id = #{configId}
ORDER BY create_time DESC
LIMIT #{limit}
</select>
<!-- 计算成功率 -->
<select id="calculateSuccessRate" resultType="java.lang.Double">
SELECT
CASE
WHEN SUM(message_count) = 0 THEN 0.0
ELSE ROUND(SUM(success_count) * 100.0 / SUM(message_count), 2)
END as success_rate
FROM iot_data_input_log
WHERE config_id = #{configId}
<if test="startTime != null">
AND create_time >= #{startTime}
</if>
<if test="endTime != null">
AND create_time &lt;= #{endTime}
</if>
</select>
<!-- 计算成功率 -->
<select id="calculateSuccessRate" resultType="java.lang.Double">
SELECT
CASE
WHEN SUM(message_count) = 0 THEN 0.0
ELSE ROUND(SUM(success_count) * 100.0 / SUM(message_count), 2)
END as success_rate
FROM iot_data_input_log
WHERE config_id = #{configId}
<if test="startTime != null">
AND create_time >= #{startTime}
</if>
<if test="endTime != null">
AND create_time &lt;= #{endTime}
</if>
</select>
<!-- 删除过期日志 -->
<delete id="deleteExpiredLogs">
DELETE
FROM iot_data_input_log
WHERE create_time &lt; #{expireTime}
</delete>
<!-- 删除过期日志 -->
<delete id="deleteExpiredLogs">
DELETE
FROM iot_data_input_log
WHERE create_time &lt; #{expireTime}
</delete>
</mapper>
@@ -1,84 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.universal.databridge.mapper.ResourceConnectionMapper">
<resultMap id="BaseResultMap" type="cn.universal.databridge.entity.ResourceConnection">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="type" jdbcType="VARCHAR" property="type"/>
<result column="host" jdbcType="VARCHAR" property="host"/>
<result column="port" jdbcType="INTEGER" property="port"/>
<result column="username" jdbcType="VARCHAR" property="username"/>
<result column="password" jdbcType="VARCHAR" property="password"/>
<result column="database_name" jdbcType="VARCHAR" property="databaseName"/>
<result column="extra_config" jdbcType="LONGVARCHAR" property="extraConfig"/>
<result column="status" jdbcType="TINYINT" property="status"/>
<result column="description" jdbcType="VARCHAR" property="description"/>
<result column="create_by" jdbcType="VARCHAR" property="createBy"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="update_by" jdbcType="VARCHAR" property="updateBy"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
</resultMap>
<resultMap id="BaseResultMap" type="cn.universal.databridge.entity.ResourceConnection">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="type" jdbcType="VARCHAR" property="type"/>
<result column="host" jdbcType="VARCHAR" property="host"/>
<result column="port" jdbcType="INTEGER" property="port"/>
<result column="username" jdbcType="VARCHAR" property="username"/>
<result column="password" jdbcType="VARCHAR" property="password"/>
<result column="database_name" jdbcType="VARCHAR" property="databaseName"/>
<result column="extra_config" jdbcType="LONGVARCHAR" property="extraConfig"/>
<result column="status" jdbcType="TINYINT" property="status"/>
<result column="description" jdbcType="VARCHAR" property="description"/>
<result column="create_by" jdbcType="VARCHAR" property="createBy"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="update_by" jdbcType="VARCHAR" property="updateBy"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
</resultMap>
<sql id="Base_Column_List">
id
, name, type, host, port, username, password, database_name, extra_config,
<sql id="Base_Column_List">
id
, name, type, host, port, username, password, database_name, extra_config,
status, description, create_by, create_time, update_by, update_time
</sql>
</sql>
<!-- 根据资源类型获取活跃连接 -->
<select id="selectActiveConnectionsByType" parameterType="string" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_resource_connection
WHERE type = #{type}
AND status = 1
ORDER BY create_time DESC
</select>
<!-- 根据资源类型获取活跃连接 -->
<select id="selectActiveConnectionsByType" parameterType="string" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_resource_connection
WHERE type = #{type}
AND status = 1
ORDER BY create_time DESC
</select>
<!-- 根据主机和端口查询连接 -->
<select id="selectByHostAndPort" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_resource_connection
WHERE host = #{host}
AND port = #{port}
<if test="excludeId != null">
AND id != #{excludeId}
</if>
LIMIT 1
</select>
<!-- 根据主机和端口查询连接 -->
<select id="selectByHostAndPort" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_resource_connection
WHERE host = #{host}
AND port = #{port}
<if test="excludeId != null">
AND id != #{excludeId}
</if>
LIMIT 1
</select>
<!-- 根据名称查询连接(用于重名检查) -->
<select id="selectByName" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_resource_connection
WHERE name = #{name}
<if test="excludeId != null">
AND id != #{excludeId}
</if>
LIMIT 1
</select>
<!-- 根据名称查询连接(用于重名检查) -->
<select id="selectByName" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM iot_resource_connection
WHERE name = #{name}
<if test="excludeId != null">
AND id != #{excludeId}
</if>
LIMIT 1
</select>
<!-- 批量更新连接状态 -->
<update id="batchUpdateStatus">
UPDATE iot_resource_connection
SET status = #{status},
update_by = #{updateBy},
update_time = NOW()
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<!-- 批量更新连接状态 -->
<update id="batchUpdateStatus">
UPDATE iot_resource_connection
SET status = #{status},
update_by = #{updateBy},
update_time = NOW()
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<!-- 测试连接可用性 -->
<select id="testConnection" parameterType="long" resultType="int">
SELECT COUNT(1)
FROM iot_resource_connection
WHERE id = #{id}
AND status = 1
</select>
<!-- 测试连接可用性 -->
<select id="testConnection" parameterType="long" resultType="int">
SELECT COUNT(1)
FROM iot_resource_connection
WHERE id = #{id}
AND status = 1
</select>
</mapper>
@@ -1,36 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<parent>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<artifactId>cn-universal-data-bridge-plugin-http</artifactId>
<packaging>jar</packaging>
<artifactId>cn-universal-data-bridge-plugin-http</artifactId>
<packaging>jar</packaging>
<name>cn-universal-data-bridge-plugin-http</name>
<description>HTTP数据桥接插件</description>
<name>cn-universal-data-bridge-plugin-http</name>
<description>HTTP数据桥接插件</description>
<dependencies>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
@@ -22,181 +22,192 @@ import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Component("http")
@ConditionalOnProperty(prefix = "databridge.plugins.http", name = "enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(
prefix = "databridge.plugins.http",
name = "enabled",
havingValue = "true",
matchIfMissing = true)
@Slf4j
public class HttpDataBridgePlugin extends AbstractDataOutputPlugin {
private final RestTemplate restTemplate = new RestTemplate();
private final RestTemplate restTemplate = new RestTemplate();
@Override
public PluginInfo getPluginInfo() {
return PluginInfo.builder()
.name("HTTP数据桥接插件")
.version("1.0.0")
.description("支持HTTP上报")
.author("gitee.com/NexIoT")
.pluginType("HTTP")
.supportedResourceTypes(List.of("HTTP"))
.dataDirection(PluginInfo.DataDirection.BIDIRECTIONAL)
.category("网络")
.icon("api")
.build();
@Override
public PluginInfo getPluginInfo() {
return PluginInfo.builder()
.name("HTTP数据桥接插件")
.version("1.0.0")
.description("支持HTTP上报")
.author("gitee.com/NexIoT")
.pluginType("HTTP")
.supportedResourceTypes(List.of("HTTP"))
.dataDirection(PluginInfo.DataDirection.BIDIRECTIONAL)
.category("网络")
.icon("api")
.build();
}
@Override
public List<SourceScope> getSupportedSourceScopes() {
return List.of(
SourceScope.ALL_PRODUCTS, SourceScope.SPECIFIC_PRODUCTS, SourceScope.APPLICATION);
}
@Override
protected void processProcessedData(
Object processedData,
BaseUPRequest request,
DataBridgeConfig config,
ResourceConnection connection) {
try {
String url = buildUrl(connection);
HttpHeaders headers = buildHeaders(config);
String payload;
if (processedData instanceof String) {
payload = (String) processedData;
} else {
// 如果Magic脚本返回的不是字符串,使用模板处理
Map<String, Object> variables = buildTemplateVariables(request, config);
payload = processTemplate(config.getTemplate(), variables);
}
HttpEntity<String> entity = new HttpEntity<>(payload, headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, entity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
log.debug("HTTP请求成功: status={}, body={}", response.getStatusCode(), response.getBody());
} else {
log.warn("HTTP请求失败: status={}, body={}", response.getStatusCode(), response.getBody());
}
} catch (Exception e) {
log.error("HTTP数据处理失败: {}", e.getMessage(), e);
throw new RuntimeException(e);
}
}
@Override
public List<SourceScope> getSupportedSourceScopes() {
return List.of(
SourceScope.ALL_PRODUCTS,
SourceScope.SPECIFIC_PRODUCTS,
SourceScope.APPLICATION
);
@Override
protected void processTemplateResult(
String templateResult,
BaseUPRequest request,
DataBridgeConfig config,
ResourceConnection connection) {
try {
String url = buildUrl(connection);
HttpHeaders headers = buildHeaders(config);
HttpEntity<String> entity = new HttpEntity<>(templateResult, headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, entity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
log.debug("HTTP请求成功: status={}, body={}", response.getStatusCode(), response.getBody());
} else {
log.warn("HTTP请求失败: status={}, body={}", response.getStatusCode(), response.getBody());
}
} catch (Exception e) {
log.error("HTTP模板处理失败: {}", e.getMessage(), e);
throw new RuntimeException(e);
}
}
@Override
protected void processProcessedData(Object processedData, BaseUPRequest request, DataBridgeConfig config, ResourceConnection connection) {
try {
String url = buildUrl(connection);
HttpHeaders headers = buildHeaders(config);
String payload;
if (processedData instanceof String) {
payload = (String) processedData;
} else {
// 如果Magic脚本返回的不是字符串,使用模板处理
Map<String, Object> variables = buildTemplateVariables(request, config);
payload = processTemplate(config.getTemplate(), variables);
}
HttpEntity<String> entity = new HttpEntity<>(payload, headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, entity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
log.debug("HTTP请求成功: status={}, body={}", response.getStatusCode(), response.getBody());
} else {
log.warn("HTTP请求失败: status={}, body={}", response.getStatusCode(), response.getBody());
}
} catch (Exception e) {
log.error("HTTP数据处理失败: {}", e.getMessage(), e);
throw new RuntimeException(e);
@Override
public Boolean testConnection(ResourceConnection connection) {
try {
String url = buildUrl(connection);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>("{}", headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, entity, String.class);
return response.getStatusCode().is2xxSuccessful();
} catch (Exception e) {
log.error("HTTP连接测试失败: {}", e.getMessage(), e);
return false;
}
}
@Override
public Boolean validateConfig(DataBridgeConfig config) {
return validateMagicScript(config.getMagicScript());
}
private String buildUrl(ResourceConnection connection) {
// 默认使用http协议,可以通过extraConfig配置SSL
String protocol = "http";
if (StrUtil.isNotBlank(connection.getExtraConfig())) {
try {
JSONObject extraConfig = JSONUtil.parseObj(connection.getExtraConfig());
if (extraConfig.getBool("ssl", false)) {
protocol = "https";
}
} catch (Exception e) {
log.warn("解析SSL配置失败: {}", e.getMessage());
}
}
@Override
protected void processTemplateResult(String templateResult, BaseUPRequest request, DataBridgeConfig config, ResourceConnection connection) {
try {
String url = buildUrl(connection);
HttpHeaders headers = buildHeaders(config);
HttpEntity<String> entity = new HttpEntity<>(templateResult, headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, entity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
log.debug("HTTP请求成功: status={}, body={}", response.getStatusCode(), response.getBody());
} else {
log.warn("HTTP请求失败: status={}, body={}", response.getStatusCode(), response.getBody());
}
} catch (Exception e) {
log.error("HTTP模板处理失败: {}", e.getMessage(), e);
throw new RuntimeException(e);
}
String path = "/";
if (StrUtil.isNotBlank(connection.getExtraConfig())) {
try {
JSONObject extraConfig = JSONUtil.parseObj(connection.getExtraConfig());
path = extraConfig.getStr("path", "/");
} catch (Exception e) {
log.warn("解析路径配置失败: {}", e.getMessage());
}
}
@Override
public Boolean testConnection(ResourceConnection connection) {
try {
String url = buildUrl(connection);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>("{}", headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, entity, String.class);
return response.getStatusCode().is2xxSuccessful();
} catch (Exception e) {
log.error("HTTP连接测试失败: {}", e.getMessage(), e);
return false;
return String.format(
"%s://%s:%d%s", protocol, connection.getHost(), connection.getPort(), path);
}
private HttpHeaders buildHeaders(DataBridgeConfig config) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// 从统一配置中提取headers
if (StrUtil.isNotBlank(config.getConfig())) {
try {
JSONObject configJson = JSONUtil.parseObj(config.getConfig());
if (configJson.containsKey("headers")) {
JSONObject headersJson = configJson.getJSONObject("headers");
for (String key : headersJson.keySet()) {
headers.set(key, headersJson.getStr(key));
}
}
} catch (Exception e) {
log.warn("解析HTTP配置失败: {}", e.getMessage());
}
}
@Override
public Boolean validateConfig(DataBridgeConfig config) {
return validateMagicScript(config.getMagicScript());
return headers;
}
private Map<String, Object> buildTemplateVariables(
BaseUPRequest request, DataBridgeConfig config) {
Map<String, Object> variables = new HashMap<>();
// 设备信息
if (request.getIoTDeviceDTO() != null) {
variables.put("deviceKey", request.getIoTDeviceDTO().getDeviceId());
variables.put("productKey", request.getIoTDeviceDTO().getProductKey());
}
private String buildUrl(ResourceConnection connection) {
// 默认使用http协议,可以通过extraConfig配置SSL
String protocol = "http";
if (StrUtil.isNotBlank(connection.getExtraConfig())) {
try {
JSONObject extraConfig = JSONUtil.parseObj(connection.getExtraConfig());
if (extraConfig.getBool("ssl", false)) {
protocol = "https";
}
} catch (Exception e) {
log.warn("解析SSL配置失败: {}", e.getMessage());
}
}
String path = "/";
if (StrUtil.isNotBlank(connection.getExtraConfig())) {
try {
JSONObject extraConfig = JSONUtil.parseObj(connection.getExtraConfig());
path = extraConfig.getStr("path", "/");
} catch (Exception e) {
log.warn("解析路径配置失败: {}", e.getMessage());
}
}
return String.format("%s://%s:%d%s", protocol, connection.getHost(), connection.getPort(), path);
// 消息信息
variables.put("messageType", request.getMessageType().name());
variables.put("timestamp", System.currentTimeMillis());
variables.put("properties", request.getProperties());
// 配置信息
if (StrUtil.isNotBlank(config.getConfig())) {
try {
variables.put("configJson", JSONUtil.parseObj(config.getConfig()));
} catch (Exception e) {
log.warn("解析配置JSON失败: {}", e.getMessage());
}
}
private HttpHeaders buildHeaders(DataBridgeConfig config) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// 从统一配置中提取headers
if (StrUtil.isNotBlank(config.getConfig())) {
try {
JSONObject configJson = JSONUtil.parseObj(config.getConfig());
if (configJson.containsKey("headers")) {
JSONObject headersJson = configJson.getJSONObject("headers");
for (String key : headersJson.keySet()) {
headers.set(key, headersJson.getStr(key));
}
}
} catch (Exception e) {
log.warn("解析HTTP配置失败: {}", e.getMessage());
}
}
return headers;
}
private Map<String, Object> buildTemplateVariables(BaseUPRequest request, DataBridgeConfig config) {
Map<String, Object> variables = new HashMap<>();
// 设备信息
if (request.getIoTDeviceDTO() != null) {
variables.put("deviceKey", request.getIoTDeviceDTO().getDeviceId());
variables.put("productKey", request.getIoTDeviceDTO().getProductKey());
}
// 消息信息
variables.put("messageType", request.getMessageType().name());
variables.put("timestamp", System.currentTimeMillis());
variables.put("properties", request.getProperties());
// 配置信息
if (StrUtil.isNotBlank(config.getConfig())) {
try {
variables.put("configJson", JSONUtil.parseObj(config.getConfig()));
} catch (Exception e) {
log.warn("解析配置JSON失败: {}", e.getMessage());
}
}
return variables;
}
}
return variables;
}
}
@@ -1,54 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<parent>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<artifactId>cn-universal-data-bridge-plugin-influxdb</artifactId>
<packaging>jar</packaging>
<artifactId>cn-universal-data-bridge-plugin-influxdb</artifactId>
<packaging>jar</packaging>
<name>cn-universal-data-bridge-plugin-influxdb</name>
<description>InfluxDB数据桥接插件</description>
<name>cn-universal-data-bridge-plugin-influxdb</name>
<description>InfluxDB数据桥接插件</description>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring Web (用于HTTP客户端) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Web (用于HTTP客户端) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- InfluxDB客户端 -->
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
<version>6.10.0</version>
</dependency>
<!-- InfluxDB客户端 -->
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
<version>6.10.0</version>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -1,55 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<parent>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<artifactId>cn-universal-data-bridge-plugin-iotdb</artifactId>
<packaging>jar</packaging>
<artifactId>cn-universal-data-bridge-plugin-iotdb</artifactId>
<packaging>jar</packaging>
<name>cn-universal-data-bridge-plugin-iotdb</name>
<description>IoTDB数据桥接插件</description>
<name>cn-universal-data-bridge-plugin-iotdb</name>
<description>IoTDB数据桥接插件</description>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- IoTDB JDBC驱动 -->
<dependency>
<groupId>org.apache.iotdb</groupId>
<artifactId>iotdb-jdbc</artifactId>
<version>1.3.0</version>
</dependency>
<!-- IoTDB JDBC驱动 -->
<dependency>
<groupId>org.apache.iotdb</groupId>
<artifactId>iotdb-jdbc</artifactId>
<version>1.3.0</version>
</dependency>
<!-- IoTDB Session API -->
<dependency>
<groupId>org.apache.iotdb</groupId>
<artifactId>iotdb-session</artifactId>
<version>1.3.0</version>
</dependency>
<!-- IoTDB Session API -->
<dependency>
<groupId>org.apache.iotdb</groupId>
<artifactId>iotdb-session</artifactId>
<version>1.3.0</version>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -1,67 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<parent>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<artifactId>cn-universal-data-bridge-plugin-jdbc</artifactId>
<packaging>jar</packaging>
<artifactId>cn-universal-data-bridge-plugin-jdbc</artifactId>
<packaging>jar</packaging>
<name>cn-universal-data-bridge-plugin-jdbc</name>
<description>JDBC数据桥接插件</description>
<name>cn-universal-data-bridge-plugin-jdbc</name>
<description>JDBC数据桥接插件</description>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- PostgreSQL驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- PostgreSQL驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- H2数据库(用于测试) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<!-- H2数据库(用于测试) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -106,22 +106,21 @@ public class DefaultJdbcOutPlugin extends AbstractDataOutputPlugin {
BaseUPRequest request,
DataBridgeConfig config,
ResourceConnection connection) {
try {
// 获取数据源
DataSource dataSource = connectionManager.getOrCreateDataSource(connection);
// 使用Spring的JdbcTemplate执行SQL
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update(templateResult);
} catch (Exception e) {
log.error("执行SQL失败: {} - {}", templateResult, e.getMessage());
// 清除可能损坏的连接池
connectionManager.removeDataSource(connection);
throw new RuntimeException("SQL执行失败: " + e.getMessage(), e);
}
}
@@ -183,6 +182,4 @@ public class DefaultJdbcOutPlugin extends AbstractDataOutputPlugin {
return result;
}
}
@@ -16,53 +16,59 @@ import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
/**
* MySQL连接诊断工具
* 用于排查连接问题
*/
/** MySQL连接诊断工具 用于排查连接问题 */
public class ConnectionDiagnostic {
public static void main(String[] args) {
// 测试不同的连接配置
testConnection("jdbc:mysql://192.168.31.194:3306/test?useSSL=false&allowPublicKeyRetrieval=true", "root", "password");
testConnection("jdbc:mysql://192.168.31.194:3306/test?useSSL=false&allowPublicKeyRetrieval=true&autoReconnect=true", "root", "password");
testConnection("jdbc:mysql://192.168.31.194:3306/test?useSSL=false&allowPublicKeyRetrieval=true&useLocalSessionState=true", "root", "password");
public static void main(String[] args) {
// 测试不同的连接配置
testConnection(
"jdbc:mysql://192.168.31.194:3306/test?useSSL=false&allowPublicKeyRetrieval=true",
"root",
"password");
testConnection(
"jdbc:mysql://192.168.31.194:3306/test?useSSL=false&allowPublicKeyRetrieval=true&autoReconnect=true",
"root",
"password");
testConnection(
"jdbc:mysql://192.168.31.194:3306/test?useSSL=false&allowPublicKeyRetrieval=true&useLocalSessionState=true",
"root",
"password");
}
private static void testConnection(String url, String username, String password) {
System.out.println("=== 测试连接配置 ===");
System.out.println("URL: " + url);
System.out.println("Username: " + username);
try {
// 设置连接属性
Properties props = new Properties();
props.setProperty("user", username);
props.setProperty("password", password);
props.setProperty("useSSL", "false");
props.setProperty("allowPublicKeyRetrieval", "true");
props.setProperty("autoReconnect", "true");
props.setProperty("failOverReadOnly", "false");
props.setProperty("maxReconnects", "3");
props.setProperty("connectTimeout", "30000");
props.setProperty("socketTimeout", "30000");
Connection conn = DriverManager.getConnection(url, props);
System.out.println("✅ 连接成功!");
// 测试查询
conn.createStatement().executeQuery("SELECT 1");
System.out.println("✅ 查询测试成功!");
conn.close();
System.out.println("✅ 连接关闭成功!");
} catch (SQLException e) {
System.out.println("❌ 连接失败: " + e.getMessage());
System.out.println("错误代码: " + e.getErrorCode());
System.out.println("SQL状态: " + e.getSQLState());
}
private static void testConnection(String url, String username, String password) {
System.out.println("=== 测试连接配置 ===");
System.out.println("URL: " + url);
System.out.println("Username: " + username);
try {
// 设置连接属性
Properties props = new Properties();
props.setProperty("user", username);
props.setProperty("password", password);
props.setProperty("useSSL", "false");
props.setProperty("allowPublicKeyRetrieval", "true");
props.setProperty("autoReconnect", "true");
props.setProperty("failOverReadOnly", "false");
props.setProperty("maxReconnects", "3");
props.setProperty("connectTimeout", "30000");
props.setProperty("socketTimeout", "30000");
Connection conn = DriverManager.getConnection(url, props);
System.out.println("✅ 连接成功!");
// 测试查询
conn.createStatement().executeQuery("SELECT 1");
System.out.println("✅ 查询测试成功!");
conn.close();
System.out.println("✅ 连接关闭成功!");
} catch (SQLException e) {
System.out.println("❌ 连接失败: " + e.getMessage());
System.out.println("错误代码: " + e.getErrorCode());
System.out.println("SQL状态: " + e.getSQLState());
}
System.out.println();
}
System.out.println();
}
}
@@ -34,29 +34,31 @@ public class QuickTemplateTest {
try {
// 测试通用实现
AbstractDataBridgePlugin genericPlugin = new AbstractDataBridgePlugin() {
@Override
public PluginInfo getPluginInfo() {
AbstractDataBridgePlugin genericPlugin =
new AbstractDataBridgePlugin() {
@Override
public PluginInfo getPluginInfo() {
return null;
}
}
@Override
public Boolean testConnection(ResourceConnection connection) {
@Override
public Boolean testConnection(ResourceConnection connection) {
return null;
}
}
@Override
public Boolean validateConfig(DataBridgeConfig config) {
@Override
public Boolean validateConfig(DataBridgeConfig config) {
return null;
}
}
@Override
public List<SourceScope> getSupportedSourceScopes() {
@Override
public List<SourceScope> getSupportedSourceScopes() {
return List.of();
}
};
java.lang.reflect.Method genericMethod =
AbstractDataBridgePlugin.class.getDeclaredMethod("processTemplate", String.class, Map.class);
}
};
java.lang.reflect.Method genericMethod =
AbstractDataBridgePlugin.class.getDeclaredMethod(
"processTemplate", String.class, Map.class);
genericMethod.setAccessible(true);
String genericResult = (String) genericMethod.invoke(genericPlugin, template, variables);
@@ -64,7 +66,7 @@ public class QuickTemplateTest {
// 测试JDBC实现
DefaultJdbcOutPlugin jdbcPlugin = new DefaultJdbcOutPlugin();
java.lang.reflect.Method jdbcMethod =
java.lang.reflect.Method jdbcMethod =
DefaultJdbcOutPlugin.class.getDeclaredMethod("processTemplate", String.class, Map.class);
jdbcMethod.setAccessible(true);
String jdbcResult = (String) jdbcMethod.invoke(jdbcPlugin, template, variables);
@@ -104,31 +106,33 @@ public class QuickTemplateTest {
String template = "Nested: #{root.level2.value}";
AbstractDataBridgePlugin plugin = new AbstractDataBridgePlugin() {
@Override
public PluginInfo getPluginInfo() {
AbstractDataBridgePlugin plugin =
new AbstractDataBridgePlugin() {
@Override
public PluginInfo getPluginInfo() {
return null;
}
}
@Override
public Boolean testConnection(ResourceConnection connection) {
@Override
public Boolean testConnection(ResourceConnection connection) {
return null;
}
}
@Override
public Boolean validateConfig(DataBridgeConfig config) {
@Override
public Boolean validateConfig(DataBridgeConfig config) {
return null;
}
}
@Override
public List<SourceScope> getSupportedSourceScopes() {
@Override
public List<SourceScope> getSupportedSourceScopes() {
return List.of();
}
};
}
};
try {
java.lang.reflect.Method method =
AbstractDataBridgePlugin.class.getDeclaredMethod("processTemplate", String.class, Map.class);
java.lang.reflect.Method method =
AbstractDataBridgePlugin.class.getDeclaredMethod(
"processTemplate", String.class, Map.class);
method.setAccessible(true);
String result = (String) method.invoke(plugin, template, variables);
@@ -29,10 +29,10 @@ public class TemplateDemo {
variables.put("properties", properties);
// 测试模板
String template =
"Device: #{deviceId}, Product: #{productKey}, Name: #{deviceName}, " +
"CSQ: #{properties.csq}, Code: #{properties.code}, " +
"City: #{properties.location.city}, Country: #{properties.location.country}";
String template =
"Device: #{deviceId}, Product: #{productKey}, Name: #{deviceName}, "
+ "CSQ: #{properties.csq}, Code: #{properties.code}, "
+ "City: #{properties.location.city}, Country: #{properties.location.country}";
System.out.println("原始模板:");
System.out.println(template);
@@ -41,29 +41,31 @@ public class TemplateDemo {
try {
// 1. 通用模板处理
System.out.println("1. 通用模板处理 (AbstractDataBridgePlugin):");
AbstractDataBridgePlugin genericPlugin = new AbstractDataBridgePlugin() {
@Override
public PluginInfo getPluginInfo() {
AbstractDataBridgePlugin genericPlugin =
new AbstractDataBridgePlugin() {
@Override
public PluginInfo getPluginInfo() {
return null;
}
}
@Override
public Boolean testConnection(ResourceConnection connection) {
@Override
public Boolean testConnection(ResourceConnection connection) {
return null;
}
}
@Override
public Boolean validateConfig(DataBridgeConfig config) {
@Override
public Boolean validateConfig(DataBridgeConfig config) {
return null;
}
}
@Override
public List<SourceScope> getSupportedSourceScopes() {
@Override
public List<SourceScope> getSupportedSourceScopes() {
return List.of();
}
};
java.lang.reflect.Method genericMethod =
AbstractDataBridgePlugin.class.getDeclaredMethod("processTemplate", String.class, Map.class);
}
};
java.lang.reflect.Method genericMethod =
AbstractDataBridgePlugin.class.getDeclaredMethod(
"processTemplate", String.class, Map.class);
genericMethod.setAccessible(true);
String genericResult = (String) genericMethod.invoke(genericPlugin, template, variables);
System.out.println("结果: " + genericResult);
@@ -73,7 +75,7 @@ public class TemplateDemo {
// 2. JDBC专用模板处理
System.out.println("2. JDBC专用模板处理 (DefaultJdbcOutPlugin):");
DefaultJdbcOutPlugin jdbcPlugin = new DefaultJdbcOutPlugin();
java.lang.reflect.Method jdbcMethod =
java.lang.reflect.Method jdbcMethod =
DefaultJdbcOutPlugin.class.getDeclaredMethod("processTemplate", String.class, Map.class);
jdbcMethod.setAccessible(true);
String jdbcResult = (String) jdbcMethod.invoke(jdbcPlugin, template, variables);
@@ -102,7 +104,8 @@ public class TemplateDemo {
System.out.println("- 文本模板: Device #{deviceId} is online");
System.out.println();
System.out.println("JDBC实现适用于:");
System.out.println("- SQL模板: INSERT INTO devices(id, name) VALUES(#{deviceId}, #{deviceName})");
System.out.println(
"- SQL模板: INSERT INTO devices(id, name) VALUES(#{deviceId}, #{deviceName})");
System.out.println("- 需要SQL注入防护的场景");
System.out.println();
@@ -22,14 +22,14 @@ public class TemplateProcessingTest {
variables.put("productKey", "product001");
variables.put("deviceName", "测试设备");
variables.put("iotId", "iot001");
variables.put("deviceNode", IoTConstant.DeviceNode.DEVICE.toString());
variables.put("deviceNode", IoTConstant.DeviceNode.DEVICE.toString());
variables.put("messageType", "PROPERTIES");
variables.put("properties", properties);
variables.put("data", "raw_data_content");
// 测试模板
String template =
"""
// 测试模板
String template =
"""
{
"deviceId": "#{deviceId}",
"location": {
@@ -91,10 +91,10 @@ public class TemplateProcessingTest {
variables.put("properties", properties);
variables.put("data", "raw_data_content");
// 测试模板
String template =
"INSERT INTO device_data(device_id,product_key,device_name,iot_id,device_node,message_type,csq,code,temperature,properties,raw_data) "
+ "VALUES(#{deviceId},#{productKey},#{deviceName},#{iotId},#{deviceNode},#{messageType},#{properties.csq},#{properties.code},#{properties.temperature},#{properties},#{data});";
// 测试模板
String template =
"INSERT INTO device_data(device_id,product_key,device_name,iot_id,device_node,message_type,csq,code,temperature,properties,raw_data) "
+ "VALUES(#{deviceId},#{productKey},#{deviceName},#{iotId},#{deviceNode},#{messageType},#{properties.csq},#{properties.code},#{properties.temperature},#{properties},#{data});";
// 创建插件实例进行测试
DefaultJdbcOutPlugin plugin = new DefaultJdbcOutPlugin();
@@ -141,8 +141,8 @@ public class TemplateProcessingTest {
variables.put("deviceId", "device001");
variables.put("properties", properties);
String template =
"INSERT INTO test_table(csq,code,temperature) VALUES(#{properties.csq},#{properties.code},#{properties.temperature});";
String template =
"INSERT INTO test_table(csq,code,temperature) VALUES(#{properties.csq},#{properties.code},#{properties.temperature});";
DefaultJdbcOutPlugin plugin = new DefaultJdbcOutPlugin();
@@ -178,8 +178,8 @@ public class TemplateProcessingTest {
variables.put("deviceId", "device001");
variables.put("properties", properties);
String template =
"INSERT INTO test_table(csq,non_existent) VALUES(#{properties.csq},#{properties.nonExistent});";
String template =
"INSERT INTO test_table(csq,non_existent) VALUES(#{properties.csq},#{properties.nonExistent});";
DefaultJdbcOutPlugin plugin = new DefaultJdbcOutPlugin();
@@ -1,53 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<parent>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<artifactId>cn-universal-data-bridge-plugin-kafka</artifactId>
<packaging>jar</packaging>
<artifactId>cn-universal-data-bridge-plugin-kafka</artifactId>
<packaging>jar</packaging>
<name>cn-universal-data-bridge-plugin-kafka</name>
<description>Kafka数据桥接插件</description>
<name>cn-universal-data-bridge-plugin-kafka</name>
<description>Kafka数据桥接插件</description>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!-- Kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -57,17 +57,18 @@ public class KafkaDataBridgePlugin extends AbstractDataOutputPlugin {
@Override
public List<SourceScope> getSupportedSourceScopes() {
return List.of(
SourceScope.ALL_PRODUCTS,
SourceScope.SPECIFIC_PRODUCTS,
SourceScope.APPLICATION
);
SourceScope.ALL_PRODUCTS, SourceScope.SPECIFIC_PRODUCTS, SourceScope.APPLICATION);
}
@Override
protected void processProcessedData(Object processedData, BaseUPRequest request, DataBridgeConfig config, ResourceConnection connection) {
protected void processProcessedData(
Object processedData,
BaseUPRequest request,
DataBridgeConfig config,
ResourceConnection connection) {
try (KafkaProducer<String, String> producer = createKafkaProducer(connection)) {
String topic = extractTopic(config);
String payload;
if (processedData instanceof String) {
payload = (String) processedData;
@@ -76,17 +77,22 @@ public class KafkaDataBridgePlugin extends AbstractDataOutputPlugin {
Map<String, Object> variables = buildTemplateVariables(request, config);
payload = processTemplate(config.getTemplate(), variables);
}
ProducerRecord<String, String> record = new ProducerRecord<>(topic, payload);
producer.send(record, (metadata, exception) -> {
if (exception != null) {
log.error("Kafka消息发送失败: {}", exception.getMessage(), exception);
} else {
log.debug("Kafka消息发送成功: topic={}, partition={}, offset={}",
metadata.topic(), metadata.partition(), metadata.offset());
}
});
producer.send(
record,
(metadata, exception) -> {
if (exception != null) {
log.error("Kafka消息发送失败: {}", exception.getMessage(), exception);
} else {
log.debug(
"Kafka消息发送成功: topic={}, partition={}, offset={}",
metadata.topic(),
metadata.partition(),
metadata.offset());
}
});
} catch (Exception e) {
log.error("Kafka数据处理失败: {}", e.getMessage(), e);
throw new RuntimeException(e);
@@ -94,20 +100,29 @@ public class KafkaDataBridgePlugin extends AbstractDataOutputPlugin {
}
@Override
protected void processTemplateResult(String templateResult, BaseUPRequest request, DataBridgeConfig config, ResourceConnection connection) {
protected void processTemplateResult(
String templateResult,
BaseUPRequest request,
DataBridgeConfig config,
ResourceConnection connection) {
try (KafkaProducer<String, String> producer = createKafkaProducer(connection)) {
String topic = extractTopic(config);
ProducerRecord<String, String> record = new ProducerRecord<>(topic, templateResult);
producer.send(record, (metadata, exception) -> {
if (exception != null) {
log.error("Kafka消息发送失败: {}", exception.getMessage(), exception);
} else {
log.debug("Kafka消息发送成功: topic={}, partition={}, offset={}",
metadata.topic(), metadata.partition(), metadata.offset());
}
});
producer.send(
record,
(metadata, exception) -> {
if (exception != null) {
log.error("Kafka消息发送失败: {}", exception.getMessage(), exception);
} else {
log.debug(
"Kafka消息发送成功: topic={}, partition={}, offset={}",
metadata.topic(),
metadata.partition(),
metadata.offset());
}
});
} catch (Exception e) {
log.error("Kafka模板处理失败: {}", e.getMessage(), e);
throw new RuntimeException(e);
@@ -128,13 +143,13 @@ public class KafkaDataBridgePlugin extends AbstractDataOutputPlugin {
@Override
public Boolean validateConfig(DataBridgeConfig config) {
return StrUtil.isNotBlank(extractTopic(config))
&& validateMagicScript(config.getMagicScript());
return StrUtil.isNotBlank(extractTopic(config)) && validateMagicScript(config.getMagicScript());
}
private KafkaProducer<String, String> createKafkaProducer(ResourceConnection connection) {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
props.put(
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
String.format("%s:%d", connection.getHost(), connection.getPort()));
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
@@ -143,32 +158,37 @@ public class KafkaDataBridgePlugin extends AbstractDataOutputPlugin {
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
props.put(ProducerConfig.LINGER_MS_CONFIG, 1);
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
if (StrUtil.isNotBlank(connection.getUsername())) {
props.put("security.protocol", "SASL_PLAINTEXT");
props.put("sasl.mechanism", "PLAIN");
props.put("sasl.jaas.config",
String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
props.put(
"sasl.jaas.config",
String.format(
"org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
connection.getUsername(), connection.getPassword()));
}
return new KafkaProducer<>(props);
}
private AdminClient createAdminClient(ResourceConnection connection) {
Properties props = new Properties();
props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,
props.put(
AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,
String.format("%s:%d", connection.getHost(), connection.getPort()));
props.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, 5000);
if (StrUtil.isNotBlank(connection.getUsername())) {
props.put("security.protocol", "SASL_PLAINTEXT");
props.put("sasl.mechanism", "PLAIN");
props.put("sasl.jaas.config",
String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
props.put(
"sasl.jaas.config",
String.format(
"org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
connection.getUsername(), connection.getPassword()));
}
return AdminClient.create(props);
}
@@ -177,7 +197,7 @@ public class KafkaDataBridgePlugin extends AbstractDataOutputPlugin {
if (StrUtil.isBlank(config.getConfig())) {
return "iot-device-data"; // 默认topic
}
try {
JSONObject configJson = JSONUtil.parseObj(config.getConfig());
return configJson.getStr("topic", "iot-device-data");
@@ -187,20 +207,21 @@ public class KafkaDataBridgePlugin extends AbstractDataOutputPlugin {
}
}
private Map<String, Object> buildTemplateVariables(BaseUPRequest request, DataBridgeConfig config) {
private Map<String, Object> buildTemplateVariables(
BaseUPRequest request, DataBridgeConfig config) {
Map<String, Object> variables = new HashMap<>();
// 设备信息
if (request.getIoTDeviceDTO() != null) {
variables.put("deviceKey", request.getIoTDeviceDTO().getDeviceId());
variables.put("productKey", request.getIoTDeviceDTO().getProductKey());
}
// 消息信息
variables.put("messageType", request.getMessageType().name());
variables.put("timestamp", System.currentTimeMillis());
variables.put("properties", request.getProperties());
// 配置信息
if (StrUtil.isNotBlank(config.getConfig())) {
try {
@@ -209,7 +230,7 @@ public class KafkaDataBridgePlugin extends AbstractDataOutputPlugin {
log.warn("解析配置JSON失败: {}", e.getMessage());
}
}
return variables;
}
}
}
@@ -18,174 +18,179 @@ import org.apache.kafka.clients.admin.AdminClientConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
/**
* Kafka数据输入插件
* 支持从Kafka消费数据
*/
/** Kafka数据输入插件 支持从Kafka消费数据 */
@Component("kafkaInput")
@ConditionalOnProperty(prefix = "databridge.plugins.kafka-input", name = "enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(
prefix = "databridge.plugins.kafka-input",
name = "enabled",
havingValue = "true",
matchIfMissing = true)
@Slf4j
public class KafkaDataInputPlugin extends AbstractDataInputPlugin {
@Override
public PluginInfo getPluginInfo() {
return PluginInfo.builder()
.name("Kafka数据输入插件")
.version("1.0.0")
.description("支持从Kafka消费数据,实现数据输入功能")
.author("gitee.com/NexIoT")
.pluginType("KAFKA_INPUT")
.supportedResourceTypes(List.of("KAFKA"))
.dataDirection(PluginInfo.DataDirection.INPUT)
.category("消息队列")
.icon("message")
.build();
@Override
public PluginInfo getPluginInfo() {
return PluginInfo.builder()
.name("Kafka数据输入插件")
.version("1.0.0")
.description("支持从Kafka消费数据,实现数据输入功能")
.author("gitee.com/NexIoT")
.pluginType("KAFKA_INPUT")
.supportedResourceTypes(List.of("KAFKA"))
.dataDirection(PluginInfo.DataDirection.INPUT)
.category("消息队列")
.icon("message")
.build();
}
@Override
public List<SourceScope> getSupportedSourceScopes() {
return List.of(
SourceScope.ALL_PRODUCTS, SourceScope.SPECIFIC_PRODUCTS, SourceScope.APPLICATION);
}
@Override
protected void sendToIoTPlatform(Object processedData, DataBridgeConfig config) {
// TODO: 实现推送到IoT平台的逻辑
// 这里可以通过消息队列、HTTP API等方式将数据推送到IoT平台
log.debug("推送数据到IoT平台: 配置ID={}", config.getId());
}
@Override
protected void processExternalDataWithDefaultLogic(
Object externalData, DataBridgeConfig config, ResourceConnection connection) {
try {
// 处理外部数据,转换为IoT平台格式
BaseUPRequest request = transformToIoTData(externalData, config);
if (request != null) {
sendToIoTPlatform(request, config);
}
} catch (Exception e) {
log.error("处理外部数据失败: {}", e.getMessage(), e);
}
}
@Override
public Boolean testConnection(ResourceConnection connection) {
try (AdminClient adminClient = createAdminClient(connection)) {
// 尝试列出topics来测试连接
adminClient.listTopics().names().get();
return true;
} catch (Exception e) {
log.error("Kafka连接测试失败: {}", e.getMessage(), e);
return false;
}
}
@Override
public Boolean validateConfig(DataBridgeConfig config) {
// 验证统一配置
if (StrUtil.isBlank(config.getConfig())) {
return false;
}
@Override
public List<SourceScope> getSupportedSourceScopes() {
return List.of(
SourceScope.ALL_PRODUCTS,
SourceScope.SPECIFIC_PRODUCTS,
SourceScope.APPLICATION
);
try {
JSONObject configJson = JSONUtil.parseObj(config.getConfig());
// 验证必要的配置项
return configJson.containsKey("topic") && StrUtil.isNotBlank(configJson.getStr("topic"));
} catch (Exception e) {
log.error("验证Kafka输入配置失败: {}", e.getMessage(), e);
return false;
}
}
@Override
protected void sendToIoTPlatform(Object processedData, DataBridgeConfig config) {
// TODO: 实现推送到IoT平台的逻辑
// 这里可以通过消息队列、HTTP API等方式将数据推送到IoT平台
log.debug("推送数据到IoT平台: 配置ID={}", config.getId());
}
private BaseUPRequest transformToIoTData(Object externalData, DataBridgeConfig config) {
try {
if (externalData instanceof String) {
String payload = (String) externalData;
JSONObject data = JSONUtil.parseObj(payload);
@Override
protected void processExternalDataWithDefaultLogic(Object externalData, DataBridgeConfig config, ResourceConnection connection) {
try {
// 处理外部数据,转换为IoT平台格式
BaseUPRequest request = transformToIoTData(externalData, config);
if (request != null) {
sendToIoTPlatform(request, config);
}
} catch (Exception e) {
log.error("处理外部数据失败: {}", e.getMessage(), e);
// 构建BaseUPRequest
BaseUPRequest request = new BaseUPRequest();
request.setMessageType(MessageType.PROPERTIES);
request.setProperties(new HashMap<>());
// 设置设备信息
IoTDeviceDTO deviceDTO = new IoTDeviceDTO();
deviceDTO.setDeviceId(data.getStr("deviceId", "unknown"));
deviceDTO.setProductKey(data.getStr("productKey", "unknown"));
request.setIoTDeviceDTO(deviceDTO);
// 设置属性
if (data.containsKey("properties")) {
Object properties = data.get("properties");
if (properties instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> props = (Map<String, Object>) properties;
request.setProperties(props);
}
}
return request;
}
} catch (Exception e) {
log.error("转换外部数据为IoT格式失败: {}", e.getMessage(), e);
}
return null;
}
private AdminClient createAdminClient(ResourceConnection connection) {
Properties props = new Properties();
props.put(
AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,
String.format("%s:%d", connection.getHost(), connection.getPort()));
props.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, 5000);
if (StrUtil.isNotBlank(connection.getUsername())) {
props.put("security.protocol", "SASL_PLAINTEXT");
props.put("sasl.mechanism", "PLAIN");
props.put(
"sasl.jaas.config",
String.format(
"org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
connection.getUsername(), connection.getPassword()));
}
@Override
public Boolean testConnection(ResourceConnection connection) {
try (AdminClient adminClient = createAdminClient(connection)) {
// 尝试列出topics来测试连接
adminClient.listTopics().names().get();
return true;
} catch (Exception e) {
log.error("Kafka连接测试失败: {}", e.getMessage(), e);
return false;
}
}
return AdminClient.create(props);
}
@Override
public Boolean validateConfig(DataBridgeConfig config) {
// 验证统一配置
if (StrUtil.isBlank(config.getConfig())) {
return false;
}
try {
JSONObject configJson = JSONUtil.parseObj(config.getConfig());
// 验证必要的配置项
return configJson.containsKey("topic") &&
StrUtil.isNotBlank(configJson.getStr("topic"));
} catch (Exception e) {
log.error("验证Kafka输入配置失败: {}", e.getMessage(), e);
return false;
}
}
// TODO: Kafka消费者创建方法,用于数据输入
// private KafkaConsumer<String, String> createKafkaConsumer(ResourceConnection connection, String
// topic) {
// Properties props = new Properties();
// props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
// String.format("%s:%d", connection.getHost(), connection.getPort()));
// props.put(ConsumerConfig.GROUP_ID_CONFIG, "databridge-input-group");
// props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
// StringDeserializer.class.getName());
// props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
// StringDeserializer.class.getName());
// props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
// props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
// props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000);
//
// if (StrUtil.isNotBlank(connection.getUsername())) {
// props.put("security.protocol", "SASL_PLAINTEXT");
// props.put("sasl.mechanism", "PLAIN");
// props.put("sasl.jaas.config",
// String.format("org.apache.kafka.common.security.plain.PlainLoginModule required
// username=\"%s\" password=\"%s\";",
// connection.getUsername(), connection.getPassword()));
// }
//
// KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
// consumer.subscribe(Collections.singletonList(topic));
// return consumer;
// }
private BaseUPRequest transformToIoTData(Object externalData, DataBridgeConfig config) {
try {
if (externalData instanceof String) {
String payload = (String) externalData;
JSONObject data = JSONUtil.parseObj(payload);
// 构建BaseUPRequest
BaseUPRequest request = new BaseUPRequest();
request.setMessageType(MessageType.PROPERTIES);
request.setProperties(new HashMap<>());
// 设置设备信息
IoTDeviceDTO deviceDTO = new IoTDeviceDTO();
deviceDTO.setDeviceId(data.getStr("deviceId", "unknown"));
deviceDTO.setProductKey(data.getStr("productKey", "unknown"));
request.setIoTDeviceDTO(deviceDTO);
// 设置属性
if (data.containsKey("properties")) {
Object properties = data.get("properties");
if (properties instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> props = (Map<String, Object>) properties;
request.setProperties(props);
}
}
return request;
}
} catch (Exception e) {
log.error("转换外部数据为IoT格式失败: {}", e.getMessage(), e);
}
return null;
}
private AdminClient createAdminClient(ResourceConnection connection) {
Properties props = new Properties();
props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG,
String.format("%s:%d", connection.getHost(), connection.getPort()));
props.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, 5000);
if (StrUtil.isNotBlank(connection.getUsername())) {
props.put("security.protocol", "SASL_PLAINTEXT");
props.put("sasl.mechanism", "PLAIN");
props.put("sasl.jaas.config",
String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
connection.getUsername(), connection.getPassword()));
}
return AdminClient.create(props);
}
// TODO: Kafka消费者创建方法,用于数据输入
// private KafkaConsumer<String, String> createKafkaConsumer(ResourceConnection connection, String topic) {
// Properties props = new Properties();
// props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
// String.format("%s:%d", connection.getHost(), connection.getPort()));
// props.put(ConsumerConfig.GROUP_ID_CONFIG, "databridge-input-group");
// props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
// props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
// props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000);
//
// if (StrUtil.isNotBlank(connection.getUsername())) {
// props.put("security.protocol", "SASL_PLAINTEXT");
// props.put("sasl.mechanism", "PLAIN");
// props.put("sasl.jaas.config",
// String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
// connection.getUsername(), connection.getPassword()));
// }
//
// KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
// consumer.subscribe(Collections.singletonList(topic));
// return consumer;
// }
// TODO: 提取topic的方法,用于Kafka订阅
// private String extractTopic(DataBridgeConfig config) {
// try {
// JSONObject configJson = JSONUtil.parseObj(config.getConfig());
// return configJson.getStr("topic", "iot-input-data");
// } catch (Exception e) {
// log.warn("解析Kafka输入配置失败,使用默认topic: {}", e.getMessage());
// return "iot-input-data";
// }
// }
}
// TODO: 提取topic的方法,用于Kafka订阅
// private String extractTopic(DataBridgeConfig config) {
// try {
// JSONObject configJson = JSONUtil.parseObj(config.getConfig());
// return configJson.getStr("topic", "iot-input-data");
// } catch (Exception e) {
// log.warn("解析Kafka输入配置失败,使用默认topic: {}", e.getMessage());
// return "iot-input-data";
// }
// }
}
@@ -1,48 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<parent>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<artifactId>cn-universal-data-bridge-plugin-mqtt</artifactId>
<packaging>jar</packaging>
<artifactId>cn-universal-data-bridge-plugin-mqtt</artifactId>
<packaging>jar</packaging>
<name>cn-universal-data-bridge-plugin-mqtt</name>
<description>MQTT数据桥接插件</description>
<name>cn-universal-data-bridge-plugin-mqtt</name>
<description>MQTT数据桥接插件</description>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependencies>
<!-- 核心模块 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- MQTT -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
<!-- MQTT -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -44,17 +44,18 @@ public class MqttDataBridgePlugin extends AbstractDataOutputPlugin {
@Override
public List<SourceScope> getSupportedSourceScopes() {
return List.of(
SourceScope.ALL_PRODUCTS,
SourceScope.SPECIFIC_PRODUCTS,
SourceScope.APPLICATION
);
SourceScope.ALL_PRODUCTS, SourceScope.SPECIFIC_PRODUCTS, SourceScope.APPLICATION);
}
@Override
protected void processProcessedData(Object processedData, BaseUPRequest request, DataBridgeConfig config, ResourceConnection connection) {
protected void processProcessedData(
Object processedData,
BaseUPRequest request,
DataBridgeConfig config,
ResourceConnection connection) {
try (MqttClient client = createMqttClient(connection)) {
client.connect(buildOptions(connection));
String payload;
if (processedData instanceof String) {
payload = (String) processedData;
@@ -63,11 +64,11 @@ public class MqttDataBridgePlugin extends AbstractDataOutputPlugin {
Map<String, Object> variables = buildTemplateVariables(request, config);
payload = processTemplate(config.getTemplate(), variables);
}
String topic = extractTopic(config);
client.publish(topic, new MqttMessage(payload.getBytes()));
client.disconnect();
} catch (Exception e) {
log.error("MQTT数据处理失败: {}", e.getMessage(), e);
throw new RuntimeException(e);
@@ -75,14 +76,18 @@ public class MqttDataBridgePlugin extends AbstractDataOutputPlugin {
}
@Override
protected void processTemplateResult(String templateResult, BaseUPRequest request, DataBridgeConfig config, ResourceConnection connection) {
protected void processTemplateResult(
String templateResult,
BaseUPRequest request,
DataBridgeConfig config,
ResourceConnection connection) {
try (MqttClient client = createMqttClient(connection)) {
client.connect(buildOptions(connection));
String topic = extractTopic(config);
client.publish(topic, new MqttMessage(templateResult.getBytes()));
client.disconnect();
} catch (Exception e) {
log.error("MQTT模板处理失败: {}", e.getMessage(), e);
throw new RuntimeException(e);
@@ -106,8 +111,7 @@ public class MqttDataBridgePlugin extends AbstractDataOutputPlugin {
@Override
public Boolean validateConfig(DataBridgeConfig config) {
return StrUtil.isNotBlank(extractTopic(config))
&& validateMagicScript(config.getMagicScript());
return StrUtil.isNotBlank(extractTopic(config)) && validateMagicScript(config.getMagicScript());
}
private MqttClient createMqttClient(ResourceConnection connection) throws Exception {
@@ -120,14 +124,14 @@ public class MqttDataBridgePlugin extends AbstractDataOutputPlugin {
options.setCleanSession(true);
options.setConnectionTimeout(30);
options.setKeepAliveInterval(60);
if (StrUtil.isNotBlank(connection.getUsername())) {
options.setUserName(connection.getUsername());
}
if (StrUtil.isNotBlank(connection.getPassword())) {
options.setPassword(connection.getPassword().toCharArray());
}
return options;
}
@@ -136,7 +140,7 @@ public class MqttDataBridgePlugin extends AbstractDataOutputPlugin {
if (StrUtil.isBlank(config.getConfig())) {
return "/iot/bridge"; // 默认topic
}
try {
JSONObject configJson = JSONUtil.parseObj(config.getConfig());
return configJson.getStr("topic", "/iot/bridge");
@@ -146,20 +150,21 @@ public class MqttDataBridgePlugin extends AbstractDataOutputPlugin {
}
}
private Map<String, Object> buildTemplateVariables(BaseUPRequest request, DataBridgeConfig config) {
private Map<String, Object> buildTemplateVariables(
BaseUPRequest request, DataBridgeConfig config) {
Map<String, Object> variables = new HashMap<>();
// 设备信息
if (request.getIoTDeviceDTO() != null) {
variables.put("deviceKey", request.getIoTDeviceDTO().getDeviceId());
variables.put("productKey", request.getIoTDeviceDTO().getProductKey());
}
// 消息信息
variables.put("messageType", request.getMessageType().name());
variables.put("timestamp", System.currentTimeMillis());
variables.put("properties", request.getProperties());
// 配置信息
if (StrUtil.isNotBlank(config.getConfig())) {
try {
@@ -168,7 +173,7 @@ public class MqttDataBridgePlugin extends AbstractDataOutputPlugin {
log.warn("解析配置JSON失败: {}", e.getMessage());
}
}
return variables;
}
}
}
@@ -17,17 +17,18 @@ import org.eclipse.paho.client.mqttv3.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
/**
* MQTT数据输入插件
* 支持从MQTT订阅数据
*/
/** MQTT数据输入插件 支持从MQTT订阅数据 */
@Component("mqttInput")
@ConditionalOnProperty(prefix = "databridge.plugins.mqtt-input", name = "enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(
prefix = "databridge.plugins.mqtt-input",
name = "enabled",
havingValue = "true",
matchIfMissing = true)
@Slf4j
public class MqttDataInputPlugin extends AbstractDataInputPlugin {
// TODO: 存储正在运行的MQTT客户端,用于管理连接生命周期
// private final Map<Long, MqttClient> runningClients = new ConcurrentHashMap<>();
// TODO: 存储正在运行的MQTT客户端,用于管理连接生命周期
// private final Map<Long, MqttClient> runningClients = new ConcurrentHashMap<>();
@Override
public PluginInfo getPluginInfo() {
@@ -47,125 +48,122 @@ public class MqttDataInputPlugin extends AbstractDataInputPlugin {
@Override
public List<SourceScope> getSupportedSourceScopes() {
return List.of(
SourceScope.ALL_PRODUCTS,
SourceScope.SPECIFIC_PRODUCTS,
SourceScope.APPLICATION
);
SourceScope.ALL_PRODUCTS, SourceScope.SPECIFIC_PRODUCTS, SourceScope.APPLICATION);
}
@Override
protected void sendToIoTPlatform(Object processedData, DataBridgeConfig config) {
// TODO: 实现推送到IoT平台的逻辑
// 这里可以通过消息队列、HTTP API等方式将数据推送到IoT平台
log.debug("推送数据到IoT平台: 配置ID={}", config.getId());
@Override
protected void sendToIoTPlatform(Object processedData, DataBridgeConfig config) {
// TODO: 实现推送到IoT平台的逻辑
// 这里可以通过消息队列、HTTP API等方式将数据推送到IoT平台
log.debug("推送数据到IoT平台: 配置ID={}", config.getId());
}
@Override
protected void processExternalDataWithDefaultLogic(
Object externalData, DataBridgeConfig config, ResourceConnection connection) {
try {
// 处理外部数据,转换为IoT平台格式
BaseUPRequest request = transformToIoTData(externalData, config);
if (request != null) {
sendToIoTPlatform(request, config);
}
} catch (Exception e) {
log.error("处理外部数据失败: {}", e.getMessage(), e);
}
}
@Override
public Boolean testConnection(ResourceConnection connection) {
try {
String broker = String.format("tcp://%s:%d", connection.getHost(), connection.getPort());
MqttClient client = new MqttClient(broker, MqttClient.generateClientId());
MqttConnectOptions options = buildOptions(connection);
client.connect(options);
client.disconnect();
client.close();
return true;
} catch (Exception e) {
log.error("MQTT连接测试失败: {}", e.getMessage(), e);
return false;
}
}
@Override
public Boolean validateConfig(DataBridgeConfig config) {
// 验证统一配置
if (StrUtil.isBlank(config.getConfig())) {
return false;
}
@Override
protected void processExternalDataWithDefaultLogic(Object externalData, DataBridgeConfig config, ResourceConnection connection) {
try {
// 处理外部数据,转换为IoT平台格式
BaseUPRequest request = transformToIoTData(externalData, config);
if (request != null) {
sendToIoTPlatform(request, config);
}
} catch (Exception e) {
log.error("处理外部数据失败: {}", e.getMessage(), e);
try {
JSONObject configJson = JSONUtil.parseObj(config.getConfig());
// 验证必要的配置项
return configJson.containsKey("topic") && StrUtil.isNotBlank(configJson.getStr("topic"));
} catch (Exception e) {
log.error("验证MQTT输入配置失败: {}", e.getMessage(), e);
return false;
}
}
private BaseUPRequest transformToIoTData(Object externalData, DataBridgeConfig config) {
try {
if (externalData instanceof String) {
String payload = (String) externalData;
JSONObject data = JSONUtil.parseObj(payload);
// 构建BaseUPRequest
BaseUPRequest request = new BaseUPRequest();
request.setMessageType(MessageType.PROPERTIES);
request.setProperties(new HashMap<>());
// 设置设备信息
IoTDeviceDTO deviceDTO = new IoTDeviceDTO();
deviceDTO.setDeviceId(data.getStr("deviceId", "unknown"));
deviceDTO.setProductKey(data.getStr("productKey", "unknown"));
request.setIoTDeviceDTO(deviceDTO);
// 设置属性
if (data.containsKey("properties")) {
Object properties = data.get("properties");
if (properties instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> props = (Map<String, Object>) properties;
request.setProperties(props);
}
}
return request;
}
} catch (Exception e) {
log.error("转换外部数据为IoT格式失败: {}", e.getMessage(), e);
}
return null;
}
private MqttConnectOptions buildOptions(ResourceConnection connection) {
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
options.setConnectionTimeout(30);
options.setKeepAliveInterval(60);
if (StrUtil.isNotBlank(connection.getUsername())) {
options.setUserName(connection.getUsername());
}
if (StrUtil.isNotBlank(connection.getPassword())) {
options.setPassword(connection.getPassword().toCharArray());
}
@Override
public Boolean testConnection(ResourceConnection connection) {
try {
String broker = String.format("tcp://%s:%d", connection.getHost(), connection.getPort());
MqttClient client = new MqttClient(broker, MqttClient.generateClientId());
MqttConnectOptions options = buildOptions(connection);
client.connect(options);
client.disconnect();
client.close();
return true;
} catch (Exception e) {
log.error("MQTT连接测试失败: {}", e.getMessage(), e);
return false;
}
}
return options;
}
@Override
public Boolean validateConfig(DataBridgeConfig config) {
// 验证统一配置
if (StrUtil.isBlank(config.getConfig())) {
return false;
}
try {
JSONObject configJson = JSONUtil.parseObj(config.getConfig());
// 验证必要的配置项
return configJson.containsKey("topic") &&
StrUtil.isNotBlank(configJson.getStr("topic"));
} catch (Exception e) {
log.error("验证MQTT输入配置失败: {}", e.getMessage(), e);
return false;
}
}
private BaseUPRequest transformToIoTData(Object externalData, DataBridgeConfig config) {
try {
if (externalData instanceof String) {
String payload = (String) externalData;
JSONObject data = JSONUtil.parseObj(payload);
// 构建BaseUPRequest
BaseUPRequest request = new BaseUPRequest();
request.setMessageType(MessageType.PROPERTIES);
request.setProperties(new HashMap<>());
// 设置设备信息
IoTDeviceDTO deviceDTO = new IoTDeviceDTO();
deviceDTO.setDeviceId(data.getStr("deviceId", "unknown"));
deviceDTO.setProductKey(data.getStr("productKey", "unknown"));
request.setIoTDeviceDTO(deviceDTO);
// 设置属性
if (data.containsKey("properties")) {
Object properties = data.get("properties");
if (properties instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> props = (Map<String, Object>) properties;
request.setProperties(props);
}
}
return request;
}
} catch (Exception e) {
log.error("转换外部数据为IoT格式失败: {}", e.getMessage(), e);
}
return null;
}
private MqttConnectOptions buildOptions(ResourceConnection connection) {
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(true);
options.setConnectionTimeout(30);
options.setKeepAliveInterval(60);
if (StrUtil.isNotBlank(connection.getUsername())) {
options.setUserName(connection.getUsername());
}
if (StrUtil.isNotBlank(connection.getPassword())) {
options.setPassword(connection.getPassword().toCharArray());
}
return options;
}
// TODO: 提取topic的方法,用于MQTT订阅
// private String extractTopic(DataBridgeConfig config) {
// try {
// JSONObject configJson = JSONUtil.parseObj(config.getConfig());
// return configJson.getStr("topic", "/iot/input");
// } catch (Exception e) {
// log.warn("解析MQTT输入配置失败,使用默认topic: {}", e.getMessage());
// return "/iot/input";
// }
// }
}
// TODO: 提取topic的方法,用于MQTT订阅
// private String extractTopic(DataBridgeConfig config) {
// try {
// JSONObject configJson = JSONUtil.parseObj(config.getConfig());
// return configJson.getStr("topic", "/iot/input");
// } catch (Exception e) {
// log.warn("解析MQTT输入配置失败,使用默认topic: {}", e.getMessage());
// return "/iot/input";
// }
// }
}
@@ -1,28 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-data-bridge</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<parent>
<artifactId>cn-universal-data-bridge</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<packaging>pom</packaging>
<artifactId>cn-universal-data-bridge-plugins</artifactId>
<packaging>pom</packaging>
<name>cn-universal-data-bridge-plugins</name>
<description>数据桥接插件模块</description>
<name>cn-universal-data-bridge-plugins</name>
<description>数据桥接插件模块</description>
<modules>
<module>cn-universal-data-bridge-plugin-jdbc</module>
<module>cn-universal-data-bridge-plugin-kafka</module>
<module>cn-universal-data-bridge-plugin-mqtt</module>
<module>cn-universal-data-bridge-plugin-http</module>
<module>cn-universal-data-bridge-plugin-iotdb</module>
<module>cn-universal-data-bridge-plugin-influxdb</module>
</modules>
<modules>
<module>cn-universal-data-bridge-plugin-jdbc</module>
<module>cn-universal-data-bridge-plugin-kafka</module>
<module>cn-universal-data-bridge-plugin-mqtt</module>
<module>cn-universal-data-bridge-plugin-http</module>
<module>cn-universal-data-bridge-plugin-iotdb</module>
<module>cn-universal-data-bridge-plugin-influxdb</module>
</modules>
</project>
@@ -1,68 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-data-bridge</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<parent>
<artifactId>cn-universal-data-bridge</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<artifactId>cn-universal-data-bridge-starter</artifactId>
<packaging>jar</packaging>
<artifactId>cn-universal-data-bridge-starter</artifactId>
<packaging>jar</packaging>
<name>cn-universal-data-bridge-starter</name>
<description>数据桥接自动配置</description>
<name>cn-universal-data-bridge-starter</name>
<description>数据桥接自动配置</description>
<dependencies>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-jdbc</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-kafka</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-mqtt</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-http</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-iotdb</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-influxdb</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-jdbc</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-kafka</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-mqtt</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-http</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-iotdb</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-influxdb</artifactId>
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
</project>
@@ -11,15 +11,14 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* 数据桥接自动配置类
*
* <p>负责自动装配数据桥接相关的组件和插件</p>
* <p>负责自动装配数据桥接相关的组件和插件
*
* <p><b>注意:</b>@EnableAsync 已在主启动类中全局启用,此处无需重复配置</p>
* <p><b>注意:</b>@EnableAsync 已在主启动类中全局启用,此处无需重复配置
*
* @author NexIoT
* @since 1.0
@@ -1,74 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-data-bridge</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<parent>
<artifactId>cn-universal-data-bridge</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<artifactId>cn-universal-data-bridge-web</artifactId>
<packaging>jar</packaging>
<artifactId>cn-universal-data-bridge-web</artifactId>
<packaging>jar</packaging>
<name>cn-universal-data-bridge-web</name>
<description>数据桥接Web接口(预留)</description>
<name>cn-universal-data-bridge-web</name>
<description>数据桥接Web接口(预留)</description>
<dependencies>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-starter</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 插件依赖 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-jdbc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-http</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-kafka</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-mqtt</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-iotdb</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-influxdb</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-security</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-starter</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 插件依赖 -->
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-jdbc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-http</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-kafka</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-mqtt</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-iotdb</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-data-bridge-plugin-influxdb</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-security</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
@@ -1,29 +0,0 @@
-- 数据桥接字典类型定义
-- 删除现有数据(如果需要重新导入)
-- DELETE FROM sys_dict_data WHERE dict_type LIKE 'databridge_%';
-- DELETE FROM sys_dict_type WHERE dict_type LIKE 'databridge_%';
-- 插入字典类型
INSERT INTO `sys_dict_type` (`dict_id`, `dict_name`, `dict_type`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (192, '数据桥接资源类型', 'databridge_resource_type', '0', 'admin', NOW(), '', NULL, '数据桥接支持的资源类型'),
(193, '资源能力', 'databridge_resource_capability', '0', 'admin', NOW(), '', NULL, '资源支持的数据流向'),
(194, 'MySQL配置字段', 'databridge_mysql_fields', '0', 'admin', NOW(), '', NULL, 'MySQL资源配置字段定义'),
(195, 'Kafka配置字段', 'databridge_kafka_fields', '0', 'admin', NOW(), '', NULL, 'Kafka资源配置字段定义'),
(196, 'MQTT配置字段', 'databridge_mqtt_fields', '0', 'admin', NOW(), '', NULL, 'MQTT资源配置字段定义'),
(197, 'HTTP配置字段', 'databridge_http_fields', '0', 'admin', NOW(), '', NULL, 'HTTP资源配置字段定义'),
(198, 'IoTDB配置字段', 'databridge_iotdb_fields', '0', 'admin', NOW(), '', NULL, 'IoTDB资源配置字段定义'),
(199, 'InfluxDB配置字段', 'databridge_influxdb_fields', '0', 'admin', NOW(), '', NULL,
'InfluxDB资源配置字段定义'),
(200, 'Elasticsearch配置字段', 'databridge_elasticsearch_fields', '0', 'admin', NOW(), '', NULL,
'Elasticsearch资源配置字段定义'),
(201, '云平台类型', 'databridge_cloud_platform', '0', 'admin', NOW(), '', NULL, '支持的云平台类型'),
(202, '阿里云配置字段', 'databridge_aliyun_iot_fields', '0', 'admin', NOW(), '', NULL, '阿里云IoT平台配置字段'),
(203, '腾讯云配置字段', 'databridge_tencent_iot_fields', '0', 'admin', NOW(), '', NULL, '腾讯云IoT平台配置字段'),
(205, 'Redis配置字段', 'databridge_redis_fields', '0', 'admin', NOW(), '', NULL, 'Redis缓存配置字段定义'),
(206, '数据流向', 'databridge_direction', '0', 'admin', NOW(), '', NULL, '数据桥接流向类型定义'),
(207, '桥接状态', 'databridge_status', '0', 'admin', NOW(), '', NULL, '数据桥接配置状态定义'),
(208, '桥接类型', 'databridge_type', '0', 'admin', NOW(), '', NULL, '数据桥接类型定义'),
(209, '消息类型', 'databridge_message_type', '0', 'admin', NOW(), '', NULL, 'IoT消息类型定义'),
(211, '华为云配置字段', 'databridge_huawei_iot_fields', '0', 'admin', NOW(), '', NULL,
'华为云IoT平台配置字段定义');
@@ -1,93 +0,0 @@
-- 基础字典数据
-- 资源类型
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (609, 1, 'MySQL数据库', 'MYSQL', 'databridge_resource_type', '', 'success', 'Y', '0', 'admin', NOW(), '', NULL,
'支持双向数据流转'),
(610, 2, 'Kafka消息队列', 'KAFKA', 'databridge_resource_type', '', 'processing', 'Y', '0', 'admin', NOW(), '',
NULL, '支持双向数据流转'),
(611, 3, 'MQTT消息代理', 'MQTT', 'databridge_resource_type', '', 'processing', 'Y', '0', 'admin', NOW(), '',
NULL, '支持双向数据流转'),
(612, 4, 'HTTP接口', 'HTTP', 'databridge_resource_type', '', 'warning', 'Y', '0', 'admin', NOW(), '', NULL,
'支持双向数据流转'),
(613, 5, 'IoTDB时序数据库', 'IOTDB', 'databridge_resource_type', '', 'default', 'Y', '0', 'admin', NOW(), '',
NULL, '仅支持数据输出'),
(614, 6, 'InfluxDB时序数据库', 'INFLUXDB', 'databridge_resource_type', '', 'default', 'Y', '0', 'admin', NOW(),
'', NULL, '仅支持数据输出'),
(615, 7, 'Elasticsearch搜索引擎', 'ELASTICSEARCH', 'databridge_resource_type', '', 'default', 'Y', '0', 'admin',
NOW(), '', NULL, '仅支持数据输出'),
(750, 8, 'Redis缓存', 'REDIS', 'databridge_resource_type', '', 'danger', 'Y', '0', 'admin', NOW(), '', NULL,
'支持双向数据流转');
-- 资源能力
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (616, 1, '双向流转', 'BIDIRECTIONAL', 'databridge_resource_capability', '', 'success', 'Y', '0', 'admin', NOW(),
'', NULL, '支持输入和输出'),
(617, 2, '仅输出', 'OUTPUT_ONLY', 'databridge_resource_capability', '', 'warning', 'Y', '0', 'admin', NOW(), '',
NULL, '仅支持数据输出');
-- 云平台类型
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (618, 1, '阿里云IoT平台', 'ALIYUN_IOT', 'databridge_cloud_platform', '', 'success', 'Y', '0', 'admin', NOW(), '',
NULL, '阿里云物联网平台'),
(619, 2, '腾讯云IoT平台', 'TENCENT_IOT', 'databridge_cloud_platform', '', 'processing', 'Y', '0', 'admin', NOW(),
'', NULL, '腾讯云物联网平台'),
(620, 3, '华为云IoT平台', 'HUAWEI_IOT', 'databridge_cloud_platform', '', 'warning', 'Y', '0', 'admin', NOW(), '',
NULL, '华为云物联网平台');
-- 数据流向
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (634, 1, '数据输出', 'OUT', 'databridge_direction', '', 'success', 'Y', '0', 'admin', NOW(), '', NULL,
'IoT平台数据输出到外部系统'),
(635, 2, '数据输入', 'IN', 'databridge_direction', '', 'processing', 'Y', '0', 'admin', NOW(), '', NULL,
'外部系统数据输入到IoT平台'),
(636, 3, '双向流转', 'BOTH', 'databridge_direction', '', 'warning', 'Y', '0', 'admin', NOW(), '', NULL,
'支持双向数据流转');
-- 桥接状态
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (720, 1, '已停用', '0', 'databridge_status', '', 'danger', 'Y', '0', 'admin', NOW(), '', NULL, '桥接配置已停用'),
(721, 2, '已启用', '1', 'databridge_status', '', 'success', 'Y', '0', 'admin', NOW(), '', NULL,
'桥接配置已启用'),
(722, 3, '运行中', '2', 'databridge_status', '', 'processing', 'Y', '0', 'admin', NOW(), '', NULL,
'桥接任务运行中'),
(723, 4, '已暂停', '3', 'databridge_status', '', 'warning', 'Y', '0', 'admin', NOW(), '', NULL,
'桥接任务已暂停'),
(724, 5, '错误', '4', 'databridge_status', '', 'danger', 'Y', '0', 'admin', NOW(), '', NULL, '桥接任务出错');
-- 桥接类型
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (730, 1, '实时桥接', 'REALTIME', 'databridge_type', '', 'success', 'Y', '0', 'admin', NOW(), '', NULL,
'实时数据桥接'),
(731, 2, '定时桥接', 'SCHEDULED', 'databridge_type', '', 'processing', 'Y', '0', 'admin', NOW(), '', NULL,
'定时数据桥接'),
(732, 3, '批量桥接', 'BATCH', 'databridge_type', '', 'warning', 'Y', '0', 'admin', NOW(), '', NULL,
'批量数据桥接'),
(733, 4, '事件触发', 'EVENT_DRIVEN', 'databridge_type', '', 'default', 'Y', '0', 'admin', NOW(), '', NULL,
'事件驱动桥接');
-- 消息类型
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (740, 1, '属性上报', 'PROPERTIES', 'databridge_message_type', '', 'success', 'Y', '0', 'admin', NOW(), '', NULL,
'设备属性数据上报'),
(741, 2, '事件上报', 'EVENT', 'databridge_message_type', '', 'processing', 'Y', '0', 'admin', NOW(), '', NULL,
'设备事件数据上报'),
(742, 3, '功能调用', 'FUNCTIONS', 'databridge_message_type', '', 'warning', 'Y', '0', 'admin', NOW(), '', NULL,
'设备功能调用'),
(743, 4, '设备上线', 'DEVICE_ONLINE', 'databridge_message_type', '', 'default', 'Y', '0', 'admin', NOW(), '',
NULL, '设备上线消息'),
(744, 5, '设备离线', 'DEVICE_OFFLINE', 'databridge_message_type', '', 'default', 'Y', '0', 'admin', NOW(), '',
NULL, '设备离线消息');
@@ -1,24 +0,0 @@
-- MySQL配置字段
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (621, 1, '主机地址', 'host', 'databridge_mysql_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入MySQL主机地址"}'),
(622, 2, '端口', 'port', 'databridge_mysql_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":true,"default":3306,"min":1,"max":65535}'),
(623, 3, '用户名', 'username', 'databridge_mysql_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入数据库用户名"}'),
(624, 4, '密码', 'password', 'databridge_mysql_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"password","required":true,"placeholder":"请输入数据库密码"}'),
(625, 5, '数据库名', 'databaseName', 'databridge_mysql_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入数据库名称"}'),
(770, 6, '字符集', 'charset', 'databridge_mysql_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"select","required":false,"options":["utf8","utf8mb4","latin1"],"default":"utf8mb4"}'),
(771, 7, '连接超时', 'connectionTimeout', 'databridge_mysql_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":30000,"min":1000,"max":300000}'),
(772, 8, '最大连接数', 'maxConnections', 'databridge_mysql_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":10,"min":1,"max":100}'),
(773, 9, '表名前缀', 'tablePrefix', 'databridge_mysql_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入表名前缀(可选)"}'),
(774, 10, '批量大小', 'batchSize', 'databridge_mysql_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":1000,"min":1,"max":10000}');
@@ -1,24 +0,0 @@
-- Kafka配置字段
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (626, 1, '主机地址', 'host', 'databridge_kafka_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入Kafka主机地址"}'),
(627, 2, '端口', 'port', 'databridge_kafka_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":true,"default":9092}'),
(628, 3, '安全协议', 'securityProtocol', 'databridge_kafka_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"select","required":false,"options":["PLAINTEXT","SSL","SASL_PLAINTEXT","SASL_SSL"],"default":"PLAINTEXT"}'),
(629, 4, 'SASL机制', 'saslMechanism', 'databridge_kafka_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"select","required":false,"options":["PLAIN","SCRAM-SHA-256","SCRAM-SHA-512"]}'),
(760, 5, '主题名称', 'topic', 'databridge_kafka_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入Kafka主题名称"}'),
(761, 6, '消费者组', 'groupId', 'databridge_kafka_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入消费者组ID(输入模式)"}'),
(762, 7, '分区数', 'partitions', 'databridge_kafka_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":1,"min":1,"max":100}'),
(763, 8, '副本数', 'replicationFactor', 'databridge_kafka_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":1,"min":1,"max":10}'),
(764, 9, '序列化器', 'serializer', 'databridge_kafka_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"select","required":false,"options":["StringSerializer","JsonSerializer","AvroSerializer"],"default":"JsonSerializer"}'),
(765, 10, '压缩类型', 'compressionType', 'databridge_kafka_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"select","required":false,"options":["none","gzip","snappy","lz4","zstd"],"default":"none"}');
@@ -1,24 +0,0 @@
-- MQTT配置字段
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (660, 1, '主机地址', 'host', 'databridge_mqtt_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入MQTT Broker地址"}'),
(661, 2, '端口', 'port', 'databridge_mqtt_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":true,"default":1883,"min":1,"max":65535}'),
(662, 3, '用户名', 'username', 'databridge_mqtt_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入MQTT用户名(可选)"}'),
(663, 4, '密码', 'password', 'databridge_mqtt_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"password","required":false,"placeholder":"请输入MQTT密码(可选)"}'),
(664, 5, '客户端ID', 'clientId', 'databridge_mqtt_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入客户端ID(可选)"}'),
(665, 6, 'QoS等级', 'qos', 'databridge_mqtt_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"select","required":false,"options":["0","1","2"],"default":"1"}'),
(666, 7, '保持连接', 'keepAlive', 'databridge_mqtt_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":60,"min":10,"max":3600}'),
(667, 8, '清除会话', 'cleanSession', 'databridge_mqtt_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"switch","required":false,"default":true}'),
(668, 9, '订阅主题', 'subscribeTopic', 'databridge_mqtt_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入订阅主题(输入模式)"}'),
(669, 10, '发布主题', 'publishTopic', 'databridge_mqtt_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入发布主题(输出模式)"}');
@@ -1,20 +0,0 @@
-- HTTP配置字段
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (670, 1, '基础URL', 'baseUrl', 'databridge_http_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入HTTP基础URL"}'),
(671, 2, '请求方法', 'method', 'databridge_http_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"select","required":true,"options":["GET","POST","PUT","DELETE"],"default":"POST"}'),
(672, 3, '内容类型', 'contentType', 'databridge_http_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"select","required":true,"options":["application/json","application/x-www-form-urlencoded","text/plain"],"default":"application/json"}'),
(673, 4, '认证类型', 'authType', 'databridge_http_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"select","required":false,"options":["none","basic","bearer","apikey"],"default":"none"}'),
(674, 5, '认证令牌', 'authToken', 'databridge_http_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"password","required":false,"placeholder":"请输入认证令牌"}'),
(675, 6, '超时时间', 'timeout', 'databridge_http_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":30000,"min":1000,"max":300000}'),
(676, 7, '重试次数', 'retryTimes', 'databridge_http_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":3,"min":0,"max":10}'),
(677, 8, '自定义头部', 'customHeaders', 'databridge_http_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"textarea","required":false,"placeholder":"请输入自定义HTTP头部(JSON格式)"}');
@@ -1,18 +0,0 @@
-- IoTDB配置字段
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (690, 1, '主机地址', 'host', 'databridge_iotdb_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入IoTDB主机地址"}'),
(691, 2, '端口', 'port', 'databridge_iotdb_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":true,"default":6667,"min":1,"max":65535}'),
(692, 3, '用户名', 'username', 'databridge_iotdb_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"default":"root","placeholder":"请输入IoTDB用户名"}'),
(693, 4, '密码', 'password', 'databridge_iotdb_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"password","required":true,"default":"root","placeholder":"请输入IoTDB密码"}'),
(694, 5, '存储组', 'storageGroup', 'databridge_iotdb_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入存储组名称(可选)"}'),
(695, 6, '时间序列路径', 'timeseriesPath', 'databridge_iotdb_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入时间序列路径模板"}'),
(696, 7, '批量大小', 'batchSize', 'databridge_iotdb_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":1000,"min":1,"max":10000}');
@@ -1,20 +0,0 @@
-- InfluxDB配置字段
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (700, 1, '主机地址', 'host', 'databridge_influxdb_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入InfluxDB主机地址"}'),
(701, 2, '端口', 'port', 'databridge_influxdb_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":true,"default":8086,"min":1,"max":65535}'),
(702, 3, '数据库名', 'database', 'databridge_influxdb_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入InfluxDB数据库名"}'),
(703, 4, '用户名', 'username', 'databridge_influxdb_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入InfluxDB用户名(可选)"}'),
(704, 5, '密码', 'password', 'databridge_influxdb_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"password","required":false,"placeholder":"请输入InfluxDB密码(可选)"}'),
(705, 6, '保留策略', 'retentionPolicy', 'databridge_influxdb_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"default":"autogen","placeholder":"请输入保留策略"}'),
(706, 7, '测量名', 'measurement', 'databridge_influxdb_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入测量名模板"}'),
(707, 8, '批量大小', 'batchSize', 'databridge_influxdb_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":1000,"min":1,"max":10000}');
@@ -1,20 +0,0 @@
-- Elasticsearch配置字段
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (710, 1, '主机地址', 'host', 'databridge_elasticsearch_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入Elasticsearch主机地址"}'),
(711, 2, '端口', 'port', 'databridge_elasticsearch_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":true,"default":9200,"min":1,"max":65535}'),
(712, 3, '索引名', 'indexName', 'databridge_elasticsearch_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入Elasticsearch索引名"}'),
(713, 4, '用户名', 'username', 'databridge_elasticsearch_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入Elasticsearch用户名(可选)"}'),
(714, 5, '密码', 'password', 'databridge_elasticsearch_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"password","required":false,"placeholder":"请输入Elasticsearch密码(可选)"}'),
(715, 6, '文档类型', 'docType', 'databridge_elasticsearch_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"default":"_doc","placeholder":"请输入文档类型"}'),
(716, 7, '批量大小', 'batchSize', 'databridge_elasticsearch_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":1000,"min":1,"max":10000}'),
(717, 8, '刷新策略', 'refreshPolicy', 'databridge_elasticsearch_fields', '', '', 'N', '0', 'admin', NOW(), '',
NULL, '{"type":"select","required":false,"options":["none","wait_for","immediate"],"default":"none"}');
@@ -1,20 +0,0 @@
-- Redis配置字段
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (680, 1, '主机地址', 'host', 'databridge_redis_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入Redis主机地址"}'),
(681, 2, '端口', 'port', 'databridge_redis_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":true,"default":6379,"min":1,"max":65535}'),
(682, 3, '密码', 'password', 'databridge_redis_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"password","required":false,"placeholder":"请输入Redis密码(可选)"}'),
(683, 4, '数据库索引', 'database', 'databridge_redis_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":0,"min":0,"max":15}'),
(684, 5, '连接超时', 'connectionTimeout', 'databridge_redis_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":5000,"min":1000,"max":60000}'),
(685, 6, '读取超时', 'readTimeout', 'databridge_redis_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":3000,"min":1000,"max":60000}'),
(686, 7, '最大连接数', 'maxConnections', 'databridge_redis_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"number","required":false,"default":8,"min":1,"max":100}'),
(687, 8, '键前缀', 'keyPrefix', 'databridge_redis_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入键前缀(可选)"}');
@@ -1,42 +0,0 @@
-- 阿里云IoT配置字段
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (630, 1, 'Region ID', 'regionId', 'databridge_aliyun_iot_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"select","required":true,"options":["cn-shanghai","cn-beijing","cn-shenzhen","ap-southeast-1"],"default":"cn-shanghai"}'),
(631, 2, 'Access Key ID', 'accessKeyId', 'databridge_aliyun_iot_fields', '', '', 'Y', '0', 'admin', NOW(), '',
NULL, '{"type":"input","required":true,"placeholder":"请输入AccessKey ID"}'),
(632, 3, 'Access Key Secret', 'accessKeySecret', 'databridge_aliyun_iot_fields', '', '', 'Y', '0', 'admin',
NOW(), '', NULL, '{"type":"password","required":true,"placeholder":"请输入AccessKey Secret"}'),
(633, 4, '实例ID', 'instanceId', 'databridge_aliyun_iot_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入IoT实例ID"}');
-- 腾讯云IoT配置字段
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (640, 1, 'Region', 'region', 'databridge_tencent_iot_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"select","required":true,"options":["ap-guangzhou","ap-shanghai","ap-beijing","ap-singapore"],"default":"ap-guangzhou","placeholder":"请选择地域"}'),
(641, 2, 'Secret ID', 'secretId', 'databridge_tencent_iot_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入腾讯云Secret ID"}'),
(642, 3, 'Secret Key', 'secretKey', 'databridge_tencent_iot_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"password","required":true,"placeholder":"请输入腾讯云Secret Key"}'),
(643, 4, '产品ID', 'productId', 'databridge_tencent_iot_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入产品ID(可选)"}'),
(644, 5, '设备名称', 'deviceName', 'databridge_tencent_iot_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入设备名称(可选)"}');
-- 华为云IoT配置字段
INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`,
`list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`,
`update_time`, `remark`)
VALUES (650, 1, 'Region', 'region', 'databridge_huawei_iot_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"select","required":true,"options":["cn-north-4","cn-south-1","cn-east-2","ap-southeast-1"],"default":"cn-north-4","placeholder":"请选择地域"}'),
(651, 2, '项目ID', 'projectId', 'databridge_huawei_iot_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入华为云项目ID"}'),
(652, 3, 'App ID', 'appId', 'databridge_huawei_iot_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":true,"placeholder":"请输入应用ID"}'),
(653, 4, 'App Secret', 'appSecret', 'databridge_huawei_iot_fields', '', '', 'Y', '0', 'admin', NOW(), '', NULL,
'{"type":"password","required":true,"placeholder":"请输入应用密钥"}'),
(654, 5, '应用名称', 'appName', 'databridge_huawei_iot_fields', '', '', 'N', '0', 'admin', NOW(), '', NULL,
'{"type":"input","required":false,"placeholder":"请输入应用名称(可选)"}');
+23 -23
View File
@@ -1,32 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<parent>
<artifactId>cn-universal</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<artifactId>cn-universal-data-bridge</artifactId>
<packaging>pom</packaging>
<artifactId>cn-universal-data-bridge</artifactId>
<packaging>pom</packaging>
<name>cn-universal-data-bridge</name>
<description>数据桥接模块</description>
<name>cn-universal-data-bridge</name>
<description>数据桥接模块</description>
<modules>
<module>cn-universal-data-bridge-core</module>
<module>cn-universal-data-bridge-plugins</module>
<module>cn-universal-data-bridge-web</module>
<module>cn-universal-data-bridge-starter</module>
</modules>
<modules>
<module>cn-universal-data-bridge-core</module>
<module>cn-universal-data-bridge-plugins</module>
<module>cn-universal-data-bridge-web</module>
<module>cn-universal-data-bridge-starter</module>
</modules>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
@@ -1,61 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>cn-universal-common</artifactId>
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-framework</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>cn-universal-common</artifactId>
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-framework</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<!-- Redis 相关依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- excel工具 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<!-- 文件上传 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<!-- IP2Region IP地址定位库 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<!-- Redis 相关依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- excel工具 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<!-- 文件上传 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<!-- IP2Region IP地址定位库 -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
</dependencies>
</project>
@@ -19,8 +19,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* 延迟启动工具类
* 提供轻量级的延迟启动功能等待所有Web容器启动完成
* 延迟启动工具类 提供轻量级的延迟启动功能等待所有Web容器启动完成
*
* @author gitee.com/NexIoT
* @version 1.0
@@ -30,85 +29,89 @@ import org.springframework.stereotype.Component;
@Component
public class DelayedStartupUtil {
@Autowired
private ApplicationContext applicationContext;
@Autowired private ApplicationContext applicationContext;
/**
* 延迟执行任务
*
* @param task 要执行的任务
* @param delaySeconds 延迟秒数
* @param executorService 执行器
*/
public void executeWithDelay(Runnable task, long delaySeconds, ExecutorService executorService) {
CompletableFuture.runAsync(() -> {
try {
log.info("延迟启动任务,等待{}秒后执行...", delaySeconds);
Thread.sleep(delaySeconds * 1000);
log.info("开始执行延迟启动任务...");
task.run();
log.info("延迟启动任务执行完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("延迟启动任务被中断", e);
} catch (Exception e) {
log.error("延迟启动任务执行失败", e);
}
}, executorService);
}
/**
* 延迟执行任务
*
* @param task 要执行的任务
* @param delaySeconds 延迟秒数
* @param executorService 执行器
*/
public void executeWithDelay(Runnable task, long delaySeconds, ExecutorService executorService) {
CompletableFuture.runAsync(
() -> {
try {
log.info("延迟启动任务,等待{}秒后执行...", delaySeconds);
Thread.sleep(delaySeconds * 1000);
log.info("开始执行延迟启动任务...");
task.run();
log.info("延迟启动任务执行完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("延迟启动任务被中断", e);
} catch (Exception e) {
log.error("延迟启动任务执行失败", e);
}
},
executorService);
}
/**
* 延迟执行任务使用默认延迟时间
*
* @param task 要执行的任务
* @param executorService 执行器
*/
public void executeWithDelay(Runnable task, ExecutorService executorService) {
executeWithDelay(task, 5, executorService); // 默认延迟5秒
}
/**
* 延迟执行任务使用默认延迟时间
*
* @param task 要执行的任务
* @param executorService 执行器
*/
public void executeWithDelay(Runnable task, ExecutorService executorService) {
executeWithDelay(task, 5, executorService); // 默认延迟5秒
}
/**
* 等待特定Bean初始化完成后再执行任务
*
* @param task 要执行的任务
* @param beanName Bean名称
* @param maxWaitSeconds 最大等待秒数
* @param executorService 执行器
*/
public void executeAfterBeanReady(Runnable task, String beanName, int maxWaitSeconds, ExecutorService executorService) {
CompletableFuture.runAsync(() -> {
try {
log.info("等待Bean {} 初始化完成...", beanName);
// 等待Bean初始化完成
for (int i = 0; i < maxWaitSeconds * 10; i++) { // 每100ms检查一次
try {
if (applicationContext.containsBean(beanName)) {
Object bean = applicationContext.getBean(beanName);
if (bean != null) {
log.info("Bean {} 已初始化完成,开始执行任务", beanName);
task.run();
log.info("任务执行完成");
return;
}
}
} catch (Exception e) {
// Bean还未初始化完成继续等待
}
Thread.sleep(100);
/**
* 等待特定Bean初始化完成后再执行任务
*
* @param task 要执行的任务
* @param beanName Bean名称
* @param maxWaitSeconds 最大等待秒数
* @param executorService 执行器
*/
public void executeAfterBeanReady(
Runnable task, String beanName, int maxWaitSeconds, ExecutorService executorService) {
CompletableFuture.runAsync(
() -> {
try {
log.info("等待Bean {} 初始化完成...", beanName);
// 等待Bean初始化完成
for (int i = 0; i < maxWaitSeconds * 10; i++) { // 每100ms检查一次
try {
if (applicationContext.containsBean(beanName)) {
Object bean = applicationContext.getBean(beanName);
if (bean != null) {
log.info("Bean {} 已初始化完成,开始执行任务", beanName);
task.run();
log.info("任务执行完成");
return;
}
}
log.warn("等待Bean {} 超时,强制执行任务", beanName);
task.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("等待Bean初始化被中断", e);
} catch (Exception e) {
log.error("等待Bean初始化后执行任务失败", e);
} catch (Exception e) {
// Bean还未初始化完成继续等待
}
Thread.sleep(100);
}
}, executorService);
}
log.warn("等待Bean {} 超时,强制执行任务", beanName);
task.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("等待Bean初始化被中断", e);
} catch (Exception e) {
log.error("等待Bean初始化后执行任务失败", e);
}
},
executorService);
}
/**
* 等待Web容器完全启动后再执行任务
@@ -117,122 +120,127 @@ public class DelayedStartupUtil {
* @param executorService 执行器
*/
public void executeAfterWebReady(Runnable task, ExecutorService executorService) {
executeWithDelay(() -> {
try {
// 检查关键Web组件是否已启动
String[] webBeans = {
"dispatcherServlet",
"requestMappingHandlerMapping",
"requestMappingHandlerAdapter",
"webMvcConfigurer"
};
boolean allReady = true;
for (String beanName : webBeans) {
if (!applicationContext.containsBean(beanName)) {
allReady = false;
log.debug("Web组件 {} 尚未就绪", beanName);
break;
}
}
// 额外检查确保所有Controller都已注册
try {
Object handlerMapping = applicationContext.getBean("requestMappingHandlerMapping");
if (handlerMapping != null) {
// 通过反射检查HandlerMapping是否已完全初始化
java.lang.reflect.Method getHandlerMethods = handlerMapping.getClass().getMethod("getHandlerMethods");
Object handlerMethods = getHandlerMethods.invoke(handlerMapping);
if (handlerMethods != null) {
log.info("Web容器已完全启动,所有Controller已注册,开始执行任务");
task.run();
return;
}
}
} catch (Exception e) {
log.debug("检查HandlerMapping状态时出错: {}", e.getMessage());
}
if (allReady) {
log.info("Web容器已完全启动,开始执行任务");
task.run();
} else {
log.warn("Web容器未完全启动,但继续执行任务");
task.run();
}
} catch (Exception e) {
log.error("检查Web容器状态失败,直接执行任务", e);
task.run();
}
}, 5, executorService); // 增加延迟时间到5秒
}
executeWithDelay(
() -> {
try {
// 检查关键Web组件是否已启动
String[] webBeans = {
"dispatcherServlet",
"requestMappingHandlerMapping",
"requestMappingHandlerAdapter",
"webMvcConfigurer"
};
/**
* 等待Web接口完全就绪包括所有Controller和路由注册完成
* 特别适用于需要Webhook回调的场景
*
* @param task 要执行的任务
* @param executorService 执行器
*/
public void executeAfterWebInterfaceReady(Runnable task, ExecutorService executorService) {
CompletableFuture.runAsync(() -> {
try {
log.info("等待Web接口完全就绪...");
// 等待Web容器启动
Thread.sleep(3000);
// 检查关键Web组件
String[] criticalBeans = {
"dispatcherServlet",
"requestMappingHandlerMapping",
"requestMappingHandlerAdapter"
};
for (int attempt = 0; attempt < 10; attempt++) {
boolean allReady = true;
for (String beanName : criticalBeans) {
if (!applicationContext.containsBean(beanName)) {
allReady = false;
break;
boolean allReady = true;
for (String beanName : webBeans) {
if (!applicationContext.containsBean(beanName)) {
allReady = false;
log.debug("Web组件 {} 尚未就绪", beanName);
break;
}
}
}
if (allReady) {
// 额外等待确保所有Controller都已注册
Thread.sleep(2000);
// 检查是否有注册的Handler
// 额外检查确保所有Controller都已注册
try {
Object handlerMapping = applicationContext.getBean("requestMappingHandlerMapping");
if (handlerMapping != null) {
java.lang.reflect.Method getHandlerMethods = handlerMapping.getClass().getMethod("getHandlerMethods");
// 通过反射检查HandlerMapping是否已完全初始化
java.lang.reflect.Method getHandlerMethods =
handlerMapping.getClass().getMethod("getHandlerMethods");
Object handlerMethods = getHandlerMethods.invoke(handlerMapping);
if (handlerMethods != null) {
log.info("Web接口已完全就绪,所有Controller已注册,开始执行任务");
log.info("Web容器已完全启动,所有Controller已注册,开始执行任务");
task.run();
return;
}
}
} catch (Exception e) {
log.debug("检查HandlerMapping时出错: {}", e.getMessage());
log.debug("检查HandlerMapping状态时出错: {}", e.getMessage());
}
if (allReady) {
log.info("Web容器已完全启动,开始执行任务");
task.run();
} else {
log.warn("Web容器未完全启动,但继续执行任务");
task.run();
}
} catch (Exception e) {
log.error("检查Web容器状态失败,直接执行任务", e);
task.run();
}
log.debug("Web接口尚未完全就绪,等待中... (第{}次检查)", attempt + 1);
Thread.sleep(1000);
}
log.warn("等待Web接口就绪超时,强制执行任务");
task.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("等待Web接口就绪被中断", e);
} catch (Exception e) {
log.error("等待Web接口就绪失败", e);
task.run();
}
}, executorService);
},
5,
executorService); // 增加延迟时间到5秒
}
/**
* 等待Web接口完全就绪包括所有Controller和路由注册完成 特别适用于需要Webhook回调的场景
*
* @param task 要执行的任务
* @param executorService 执行器
*/
public void executeAfterWebInterfaceReady(Runnable task, ExecutorService executorService) {
CompletableFuture.runAsync(
() -> {
try {
log.info("等待Web接口完全就绪...");
// 等待Web容器启动
Thread.sleep(3000);
// 检查关键Web组件
String[] criticalBeans = {
"dispatcherServlet", "requestMappingHandlerMapping", "requestMappingHandlerAdapter"
};
for (int attempt = 0; attempt < 10; attempt++) {
boolean allReady = true;
for (String beanName : criticalBeans) {
if (!applicationContext.containsBean(beanName)) {
allReady = false;
break;
}
}
if (allReady) {
// 额外等待确保所有Controller都已注册
Thread.sleep(2000);
// 检查是否有注册的Handler
try {
Object handlerMapping =
applicationContext.getBean("requestMappingHandlerMapping");
if (handlerMapping != null) {
java.lang.reflect.Method getHandlerMethods =
handlerMapping.getClass().getMethod("getHandlerMethods");
Object handlerMethods = getHandlerMethods.invoke(handlerMapping);
if (handlerMethods != null) {
log.info("Web接口已完全就绪,所有Controller已注册,开始执行任务");
task.run();
return;
}
}
} catch (Exception e) {
log.debug("检查HandlerMapping时出错: {}", e.getMessage());
}
}
log.debug("Web接口尚未完全就绪,等待中... (第{}次检查)", attempt + 1);
Thread.sleep(1000);
}
log.warn("等待Web接口就绪超时,强制执行任务");
task.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("等待Web接口就绪被中断", e);
} catch (Exception e) {
log.error("等待Web接口就绪失败", e);
task.run();
}
},
executorService);
}
}
@@ -20,8 +20,7 @@ import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
/**
* Web接口就绪检查器
* 专门用于检查Web接口是否完全就绪特别适用于Webhook场景
* Web接口就绪检查器 专门用于检查Web接口是否完全就绪特别适用于Webhook场景
*
* @author gitee.com/NexIoT
* @version 1.0
@@ -31,148 +30,150 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
@Component
public class WebInterfaceReadyChecker {
@Autowired
private ApplicationContext applicationContext;
@Autowired private ApplicationContext applicationContext;
/**
* 检查Web接口是否完全就绪
*
* @return true如果Web接口已就绪false否则
*/
public boolean isWebInterfaceReady() {
try {
// 1. 检查关键Web组件
String[] criticalBeans = {
"dispatcherServlet",
"requestMappingHandlerMapping",
"requestMappingHandlerAdapter"
};
for (String beanName : criticalBeans) {
if (!applicationContext.containsBean(beanName)) {
log.debug("Web组件 {} 尚未就绪", beanName);
return false;
}
}
// 2. 检查RequestMappingHandlerMapping是否已完全初始化
RequestMappingHandlerMapping handlerMapping =
applicationContext.getBean(RequestMappingHandlerMapping.class);
if (handlerMapping == null) {
log.debug("RequestMappingHandlerMapping 尚未就绪");
return false;
}
// 3. 检查是否有注册的Handler
try {
var handlerMethods = handlerMapping.getHandlerMethods();
if (handlerMethods == null || handlerMethods.isEmpty()) {
log.debug("尚未有Handler注册");
return false;
}
// 4. 检查是否有Webhook相关的Controller
boolean hasWebhookController = handlerMethods.entrySet().stream()
.anyMatch(entry -> {
String path = entry.getKey().toString();
return path.contains("webhook") ||
path.contains("callback") ||
path.contains("hook");
});
if (hasWebhookController) {
log.info("检测到Webhook相关Controller已注册");
} else {
log.debug("未检测到Webhook相关Controller");
}
log.info("Web接口已完全就绪,共注册 {} 个Handler", handlerMethods.size());
return true;
} catch (Exception e) {
log.debug("检查HandlerMapping时出错: {}", e.getMessage());
return false;
}
} catch (Exception e) {
log.debug("检查Web接口状态时出错: {}", e.getMessage());
return false;
}
}
/**
* 检查Web接口是否完全就绪
*
* @return true如果Web接口已就绪false否则
*/
public boolean isWebInterfaceReady() {
try {
// 1. 检查关键Web组件
String[] criticalBeans = {
"dispatcherServlet", "requestMappingHandlerMapping", "requestMappingHandlerAdapter"
};
/**
* 等待Web接口就绪
*
* @param maxWaitSeconds 最大等待秒数
* @param checkIntervalMs 检查间隔毫秒数
* @return true如果Web接口在指定时间内就绪false否则
*/
public boolean waitForWebInterfaceReady(int maxWaitSeconds, long checkIntervalMs) {
long startTime = System.currentTimeMillis();
long maxWaitMs = maxWaitSeconds * 1000L;
while (System.currentTimeMillis() - startTime < maxWaitMs) {
if (isWebInterfaceReady()) {
return true;
}
try {
Thread.sleep(checkIntervalMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("等待Web接口就绪被中断");
return false;
}
for (String beanName : criticalBeans) {
if (!applicationContext.containsBean(beanName)) {
log.debug("Web组件 {} 尚未就绪", beanName);
return false;
}
log.warn("等待Web接口就绪超时 ({}秒)", maxWaitSeconds);
}
// 2. 检查RequestMappingHandlerMapping是否已完全初始化
RequestMappingHandlerMapping handlerMapping =
applicationContext.getBean(RequestMappingHandlerMapping.class);
if (handlerMapping == null) {
log.debug("RequestMappingHandlerMapping 尚未就绪");
return false;
}
// 3. 检查是否有注册的Handler
try {
var handlerMethods = handlerMapping.getHandlerMethods();
if (handlerMethods == null || handlerMethods.isEmpty()) {
log.debug("尚未有Handler注册");
return false;
}
// 4. 检查是否有Webhook相关的Controller
boolean hasWebhookController =
handlerMethods.entrySet().stream()
.anyMatch(
entry -> {
String path = entry.getKey().toString();
return path.contains("webhook")
|| path.contains("callback")
|| path.contains("hook");
});
if (hasWebhookController) {
log.info("检测到Webhook相关Controller已注册");
} else {
log.debug("未检测到Webhook相关Controller");
}
log.info("Web接口已完全就绪,共注册 {} 个Handler", handlerMethods.size());
return true;
} catch (Exception e) {
log.debug("检查HandlerMapping时出错: {}", e.getMessage());
return false;
}
} catch (Exception e) {
log.debug("检查Web接口状态时出错: {}", e.getMessage());
return false;
}
}
/**
* 等待Web接口就绪
*
* @param maxWaitSeconds 最大等待秒数
* @param checkIntervalMs 检查间隔毫秒数
* @return true如果Web接口在指定时间内就绪false否则
*/
public boolean waitForWebInterfaceReady(int maxWaitSeconds, long checkIntervalMs) {
long startTime = System.currentTimeMillis();
long maxWaitMs = maxWaitSeconds * 1000L;
while (System.currentTimeMillis() - startTime < maxWaitMs) {
if (isWebInterfaceReady()) {
return true;
}
try {
Thread.sleep(checkIntervalMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("等待Web接口就绪被中断");
return false;
}
}
/**
* 异步等待Web接口就绪后执行任务
*
* @param task 要执行的任务
* @param executorService 执行器
* @param maxWaitSeconds 最大等待秒数
*/
public void executeAfterWebInterfaceReady(Runnable task, ExecutorService executorService, int maxWaitSeconds) {
CompletableFuture.runAsync(() -> {
try {
log.info("等待Web接口完全就绪...");
// 先等待一段时间让Web容器启动
Thread.sleep(2000);
// 等待Web接口就绪
boolean ready = waitForWebInterfaceReady(maxWaitSeconds, 1000);
if (ready) {
log.info("Web接口已完全就绪,开始执行任务");
task.run();
} else {
log.warn("Web接口就绪超时,强制执行任务");
task.run();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("等待Web接口就绪被中断", e);
} catch (Exception e) {
log.error("等待Web接口就绪失败", e);
task.run();
log.warn("等待Web接口就绪超时 ({}秒)", maxWaitSeconds);
return false;
}
/**
* 异步等待Web接口就绪后执行任务
*
* @param task 要执行的任务
* @param executorService 执行器
* @param maxWaitSeconds 最大等待秒数
*/
public void executeAfterWebInterfaceReady(
Runnable task, ExecutorService executorService, int maxWaitSeconds) {
CompletableFuture.runAsync(
() -> {
try {
log.info("等待Web接口完全就绪...");
// 先等待一段时间让Web容器启动
Thread.sleep(2000);
// 等待Web接口就绪
boolean ready = waitForWebInterfaceReady(maxWaitSeconds, 1000);
if (ready) {
log.info("Web接口已完全就绪,开始执行任务");
task.run();
} else {
log.warn("Web接口就绪超时,强制执行任务");
task.run();
}
}, executorService);
}
/**
* 异步等待Web接口就绪后执行任务使用默认参数
*
* @param task 要执行的任务
* @param executorService 执行器
*/
public void executeAfterWebInterfaceReady(Runnable task, ExecutorService executorService) {
executeAfterWebInterfaceReady(task, executorService, 15); // 默认等待15秒
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("等待Web接口就绪被中断", e);
} catch (Exception e) {
log.error("等待Web接口就绪失败", e);
task.run();
}
},
executorService);
}
/**
* 异步等待Web接口就绪后执行任务使用默认参数
*
* @param task 要执行的任务
* @param executorService 执行器
*/
public void executeAfterWebInterfaceReady(Runnable task, ExecutorService executorService) {
executeAfterWebInterfaceReady(task, executorService, 15); // 默认等待15秒
}
}
@@ -1,24 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>cn-universal-config</artifactId>
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-framework</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>cn-universal-config</artifactId>
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-framework</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>
@@ -1,85 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>cn-universal-core</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
</plugins>
</build>
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-framework</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>cn-universal-core</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
</plugins>
</build>
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-framework</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
<!-- GraalJS - 替代 Nashorn -->
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>23.0.0</version>
</dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js-scriptengine</artifactId>
<version>23.0.0</version>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
<!-- GraalJS - 替代 Nashorn -->
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>23.0.0</version>
</dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js-scriptengine</artifactId>
<version>23.0.0</version>
</dependency>
<!-- Spring Security Crypto - 提供 Base64Utils -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<!-- Spring Security Crypto - 提供 Base64Utils -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -20,8 +20,7 @@ import java.util.Map;
import lombok.Data;
/**
* 下行处理上下文
* 贯穿整个下行处理流程用于拦截器间传递数据
* 下行处理上下文 贯穿整个下行处理流程用于拦截器间传递数据
*
* @version 1.0
* @since 2025/10/24
@@ -13,8 +13,7 @@
package cn.universal.core.downlink;
/**
* 下行拦截器接口
* 所有下行拦截器都需要实现此接口
* 下行拦截器接口 所有下行拦截器都需要实现此接口
*
* @version 1.0
* @since 2025/10/24
@@ -29,19 +28,15 @@ public interface DownlinkInterceptor {
String getName();
/**
* 执行顺序数字越小越先执行
* 建议
* - 0-100: 系统级拦截器鉴权限流等
* - 100-500: 业务级拦截器数据处理编解码等
* - 500-1000: 辅助级拦截器日志监控等
* 执行顺序数字越小越先执行 建议 - 0-100: 系统级拦截器鉴权限流等 - 100-500: 业务级拦截器数据处理编解码等 - 500-1000:
* 辅助级拦截器日志监控等
*
* @return 执行顺序
*/
int getOrder();
/**
* 是否启用此拦截器
* 可通过配置动态控制拦截器的启用状态
* 是否启用此拦截器 可通过配置动态控制拦截器的启用状态
*
* @return true-启用false-禁用
*/
@@ -57,8 +52,7 @@ public interface DownlinkInterceptor {
InterceptorPhase getPhase();
/**
* 是否支持当前上下文
* 可根据协议类型产品类型等条件判断是否需要执行此拦截器
* 是否支持当前上下文 可根据协议类型产品类型等条件判断是否需要执行此拦截器
*
* @param context 下行上下文
* @return true-支持false-不支持
@@ -68,8 +62,7 @@ public interface DownlinkInterceptor {
}
/**
* 前置处理在主流程之前执行
* 此方法在拦截器链中按order顺序执行
* 前置处理在主流程之前执行 此方法在拦截器链中按order顺序执行
*
* @param context 下行上下文
* @return true-继续执行后续拦截器和主流程false-中断执行
@@ -78,9 +71,7 @@ public interface DownlinkInterceptor {
boolean preHandle(DownlinkContext<?> context) throws Exception;
/**
* 后置处理在主流程之后执行
* 此方法在拦截器链中按order顺序执行
* 注意即使preHandle返回falsepostHandle也不会被调用
* 后置处理在主流程之后执行 此方法在拦截器链中按order顺序执行 注意即使preHandle返回falsepostHandle也不会被调用
*
* @param context 下行上下文
* @throws Exception 处理异常
@@ -88,9 +79,7 @@ public interface DownlinkInterceptor {
void postHandle(DownlinkContext<?> context) throws Exception;
/**
* 完成处理无论成功失败都会执行
* 此方法在拦截器链中按order倒序执行类似finally块
* 可用于资源清理异常处理等
* 完成处理无论成功失败都会执行 此方法在拦截器链中按order倒序执行类似finally块 可用于资源清理异常处理等
*
* @param context 下行上下文
* @param ex 执行过程中的异常如果有
@@ -9,11 +9,13 @@
### 步骤1:理解新架构
**改造前的流程**
```
IDown.doAction() → AbstractDownService.convert() → XxxDownProcessorChain.process()
```
**改造后的流程**
```
IDown.doAction()
→ createContext() // 创建上下文
@@ -285,20 +287,24 @@ public class MQTTDownService extends AbstractDownService<MQTTDownRequest> implem
## 🎯 改造要点总结
### ✅ 必须实现的方法
1. `createContext(Object msg)` - 创建下行上下文
2. `doProcess(DownlinkContext<?> context)` - 核心处理逻辑
3. `getInterceptorChain()` - 获取拦截器链
### ✅ 必须添加的依赖
```java
@Resource private DownlinkInterceptorChain downlinkInterceptorChain;
```
### ✅ 必须移除的方法
- `doAction(String msg)` - 使用接口默认实现
- `doAction(JSONObject msg)` - 使用接口默认实现
### ✅ 保持不变的方法
- `code()` - 协议代码
- `name()` - 协议名称
- `convert()` - 消息转换逻辑
@@ -306,11 +312,13 @@ public class MQTTDownService extends AbstractDownService<MQTTDownRequest> implem
## 🔍 逐步迁移策略
### 方案1:一次性迁移(推荐用于新协议)
直接按照上述步骤改造,一次性启用拦截器模式。
### 方案2:渐进式迁移(推荐用于现有协议)
#### 阶段1:兼容模式(支持新旧两种方式)
```java
@Override
public R doAction(String msg) {
@@ -334,6 +342,7 @@ private boolean enableInterceptor() {
```
#### 阶段2:灰度发布
```yaml
# application.yml
downlink:
@@ -345,11 +354,13 @@ downlink:
```
#### 阶段3:全面启用
移除兼容代码,完全使用新模式。
## 🧪 测试清单
### 功能测试
- [ ] 下行消息能正常发送
- [ ] 拦截器按顺序执行
- [ ] 拦截器可以中断执行
@@ -357,11 +368,13 @@ downlink:
- [ ] 异常能正确处理
### 性能测试
- [ ] 拦截器对性能影响在可接受范围内(< 5%)
- [ ] 并发场景下正常工作
- [ ] 无内存泄漏
### 兼容性测试
- [ ] 现有业务功能不受影响
- [ ] 各协议下行正常
- [ ] 第三方平台下行正常
@@ -369,6 +382,7 @@ downlink:
## 📋 其他协议改造示例
### TCP 协议改造
```java
@Service("tcpDownService")
public class TcpDownService extends AbstractDownService<TcpDownRequest> implements IDown {
@@ -392,6 +406,7 @@ public class TcpDownService extends AbstractDownService<TcpDownRequest> implemen
```
### HTTP 协议改造
```java
@Service("httpDownService")
public class HttpDownService extends AbstractDownService<HttpDownRequest> implements IDown {
@@ -417,18 +432,23 @@ public class HttpDownService extends AbstractDownService<HttpDownRequest> implem
## ⚠️ 常见问题
### Q1: 为什么不能直接在 IDown 接口中注入 DownlinkInterceptorChain
A: 接口不能有成员变量,需要由实现类通过依赖注入提供。
### Q2: 拦截器链为 null 怎么办?
A: `doActionWithInterceptors()` 中已经做了 null 检查,即使拦截器链为 null,也能正常工作。
### Q3: 如何禁用某个拦截器?
A: 在拦截器中实现 `isEnabled()` 方法,返回 false。
### Q4: 拦截器执行顺序如何控制?
A: 通过 `getOrder()` 方法和 `@Order` 注解控制,数字越小越先执行。
### Q5: 如何只对特定协议应用拦截器?
A: 在拦截器的 `supports()` 方法中判断协议类型。
## 📚 相关文档
@@ -20,21 +20,12 @@ package cn.universal.core.downlink;
*/
public enum InterceptorPhase {
/**
* 前置阶段在消息转换convert之前执行
* 适用场景鉴权限流参数校验日志记录等
*/
/** 前置阶段:在消息转换(convert)之前执行 适用场景:鉴权、限流、参数校验、日志记录等 */
PRE,
/**
* 中置阶段在消息转换convert之后具体处理器processor之前执行
* 适用场景数据增强编解码影子服务业务规则检查等
*/
/** 中置阶段:在消息转换(convert)之后,具体处理器(processor)之前执行 适用场景:数据增强、编解码、影子服务、业务规则检查等 */
MID,
/**
* 后置阶段在具体处理器processor执行之后
* 适用场景结果处理通知推送审计日志清理资源等
*/
/** 后置阶段:在具体处理器(processor)执行之后 适用场景:结果处理、通知推送、审计日志、清理资源等 */
POST
}
@@ -125,34 +125,38 @@ public class CustomDownlinkInterceptor implements DownlinkInterceptor {
### 2. 在协议实现中使用
拦截器会自动应用到所有实现了 `IDown` 接口的协议服务中。无需额外配置,只要将拦截器注册为 Spring Bean 即可。
拦截器会自动应用到所有实现了 `IDown` 接口的协议服务中。无需额外配置,只要将拦截器注册为 Spring Bean
即可。
## 📋 拦截器执行阶段
### PRE 阶段(前置阶段)
- **执行时机**:在消息转换(convert)之前
- **适用场景**
- ✅ 鉴权认证
- ✅ 参数校验
- ✅ 限流控制
- ✅ 日志记录
- ✅ 监控统计
- ✅ 鉴权认证
- ✅ 参数校验
- ✅ 限流控制
- ✅ 日志记录
- ✅ 监控统计
### MID 阶段(中置阶段)
- **执行时机**:在消息转换(convert)之后,协议处理器(processor)之前
- **适用场景**
- ✅ 数据增强
- ✅ 编解码处理
- ✅ 影子服务集成
- ✅ 业务规则校验
- ✅ 数据增强
- ✅ 编解码处理
- ✅ 影子服务集成
- ✅ 业务规则校验
### POST 阶段(后置阶段)
- **执行时机**:在协议处理器(processor)执行之后
- **适用场景**
- ✅ 结果处理
- ✅ 通知推送
- ✅ 审计日志
- ✅ 数据持久化
- ✅ 结果处理
- ✅ 通知推送
- ✅ 审计日志
- ✅ 数据持久化
## 🔧 上下文对象
@@ -186,16 +190,19 @@ context.removeAttribute(key); // 移除属性
## 🎯 内置拦截器
### 1. DownlinkLoggingInterceptor(日志拦截器)
- **Order**: 1000
- **Phase**: PRE
- **功能**: 记录下行消息的详细日志
### 2. DownlinkMetricsInterceptor(监控拦截器)
- **Order**: 10
- **Phase**: PRE
- **功能**: 收集性能指标(耗时、成功率等)
### 3. DownlinkValidationInterceptor(校验拦截器)
- **Order**: 50
- **Phase**: PRE
- **功能**: 校验必要参数
@@ -203,6 +210,7 @@ context.removeAttribute(key); // 移除属性
## 💡 最佳实践
### 1. 拦截器命名规范
```java
// 建议使用:Downlink + 功能 + Interceptor
DownlinkAuthenticationInterceptor // 鉴权拦截器
@@ -211,6 +219,7 @@ DownlinkCodecInterceptor // 编解码拦截器
```
### 2. Order 值建议
```
0-100: 系统级拦截器(鉴权、限流、监控)
100-500: 业务级拦截器(数据处理、编解码)
@@ -218,6 +227,7 @@ DownlinkCodecInterceptor // 编解码拦截器
```
### 3. 错误处理
```java
@Override
public boolean preHandle(DownlinkContext<?> context) throws Exception {
@@ -233,6 +243,7 @@ public boolean preHandle(DownlinkContext<?> context) throws Exception {
```
### 4. 使用上下文传递数据
```java
// 在前置拦截器中存储数据
@Override
@@ -251,6 +262,7 @@ public void postHandle(DownlinkContext<?> context) {
```
### 5. 条件执行
```java
@Override
public boolean supports(DownlinkContext<?> context) {
@@ -369,6 +381,7 @@ public class DownlinkAuthenticationInterceptor implements DownlinkInterceptor {
## 🐛 调试和测试
### 启用调试日志
```yaml
logging:
level:
@@ -376,6 +389,7 @@ logging:
```
### 查看拦截器链信息
```java
@Resource
private DownlinkInterceptorChain interceptorChain;
@@ -17,18 +17,19 @@ import cn.universal.core.message.DownRequest;
import cn.universal.core.message.UnifiedDownlinkCommand;
/**
* 下行请求转换器接口
* 负责将统一的UnifiedDownlinkCommand转换为协议特定的DownRequest对象
* 下行请求转换器接口 负责将统一的UnifiedDownlinkCommand转换为协议特定的DownRequest对象
*
* <p>设计原则
*
* <ul>
* <li>单一职责每个转换器只负责一种协议的转换</li>
* <li>无状态转换器应该是无状态的可以被多次复用</li>
* <li>向后兼容转换器应该能处理旧格式的数据</li>
* <li>异常处理转换失败应该抛出明确的异常</li>
* <li>单一职责每个转换器只负责一种协议的转换
* <li>无状态转换器应该是无状态的可以被多次复用
* <li>向后兼容转换器应该能处理旧格式的数据
* <li>异常处理转换失败应该抛出明确的异常
* </ul>
*
* <p>实现示例
*
* <pre>{@code
* @Component("mqttConverter")
* public class MQTTDownRequestConverter implements DownRequestConverter<MQTTDownRequest> {
@@ -47,8 +48,8 @@ import cn.universal.core.message.UnifiedDownlinkCommand;
* }
* }</pre>
*
* <p>注意泛型参数T使用{@link DownRequest}作为上界以保持核心模块的独立性
* 实际使用时建议使用{@link cn.universal.persistence.base.BaseDownRequest}的子类
* <p>注意泛型参数T使用{@link DownRequest}作为上界以保持核心模块的独立性 实际使用时建议使用{@link
* cn.universal.persistence.base.BaseDownRequest}的子类
*
* @param <T> 协议特定的DownRequest类型通常是BaseDownRequest的子类
* @version 1.0
@@ -60,12 +61,13 @@ public interface DownRequestConverter<T extends DownRequest> {
* 转换统一命令为协议特定请求
*
* <p>转换过程应该包括
*
* <ol>
* <li>复制通用字段productKeydeviceId等</li>
* <li>设置协议特定字段</li>
* <li>执行编解码如果需要</li>
* <li>加载产品配置</li>
* <li>加载设备信息</li>
* <li>复制通用字段productKeydeviceId等
* <li>设置协议特定字段
* <li>执行编解码如果需要
* <li>加载产品配置
* <li>加载设备信息
* </ol>
*
* @param command 统一下行命令对象
@@ -89,6 +91,7 @@ public interface DownRequestConverter<T extends DownRequest> {
* 获取转换器优先级
*
* <p>当多个转换器支持同一协议时优先级高的会被优先使用
*
* <p>数字越小优先级越高默认为100
*
* @return 优先级0-最高Integer.MAX_VALUE-最低
@@ -101,6 +104,7 @@ public interface DownRequestConverter<T extends DownRequest> {
* 检查是否支持指定的命令
*
* <p>默认实现支持所有命令
*
* <p>子类可以重写此方法以实现更细粒度的控制
*
* @param command 统一下行命令
@@ -114,10 +118,11 @@ public interface DownRequestConverter<T extends DownRequest> {
* 转换前的预处理钩子
*
* <p>在执行convert之前调用可用于
*
* <ul>
* <li>参数验证</li>
* <li>参数标准化</li>
* <li>日志记录</li>
* <li>参数验证
* <li>参数标准化
* <li>日志记录
* </ul>
*
* @param command 统一下行命令
@@ -131,18 +136,18 @@ public interface DownRequestConverter<T extends DownRequest> {
* 转换后的后处理钩子
*
* <p>在执行convert之后调用可用于
*
* <ul>
* <li>结果验证</li>
* <li>数据增强</li>
* <li>日志记录</li>
* <li>结果验证
* <li>数据增强
* <li>日志记录
* </ul>
*
* @param command 统一下行命令
* @param request 转换后的请求对象
* @param context 下行上下文
*/
default void postConvert(
UnifiedDownlinkCommand command, T request, DownlinkContext<?> context) {
default void postConvert(UnifiedDownlinkCommand command, T request, DownlinkContext<?> context) {
// 默认空实现
}
}
@@ -30,19 +30,19 @@ import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 命令转换拦截器
* 负责将UnifiedDownlinkCommand转换为协议特定的DownRequest对象
* 命令转换拦截器 负责将UnifiedDownlinkCommand转换为协议特定的DownRequest对象
*
* <p>执行时机MID阶段在参数验证之后业务处理之前
*
* <p>转换流程
*
* <ol>
* <li>检查上下文中是否存在UnifiedDownlinkCommand</li>
* <li>根据协议代码查找对应的转换器</li>
* <li>执行转换前的预处理preConvert</li>
* <li>执行转换convert</li>
* <li>执行转换后的后处理postConvert</li>
* <li>将转换结果设置到上下文中</li>
* <li>检查上下文中是否存在UnifiedDownlinkCommand
* <li>根据协议代码查找对应的转换器
* <li>执行转换前的预处理preConvert
* <li>执行转换convert
* <li>执行转换后的后处理postConvert
* <li>将转换结果设置到上下文中
* </ol>
*
* @version 1.0
@@ -80,10 +80,7 @@ public class CommandConversionInterceptor implements DownlinkInterceptor {
}
}
log.info(
"命令转换拦截器初始化完成,加载转换器数量: {},支持协议: {}",
converters.size(),
converterCache.keySet());
log.info("命令转换拦截器初始化完成,加载转换器数量: {},支持协议: {}", converters.size(), converterCache.keySet());
} else {
log.warn("未找到任何DownRequestConverter实现,命令转换功能将不可用");
}
@@ -142,7 +139,7 @@ public class CommandConversionInterceptor implements DownlinkInterceptor {
// 6. 执行转换使用类型安全的方式
@SuppressWarnings("unchecked")
DownRequestConverter<DownRequest> typedConverter =
DownRequestConverter<DownRequest> typedConverter =
(DownRequestConverter<DownRequest>) converter;
DownRequest downRequest = typedConverter.convert(command, context);
if (downRequest == null) {
@@ -20,8 +20,7 @@ import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 下行日志拦截器
* 记录下行消息的详细日志信息
* 下行日志拦截器 记录下行消息的详细日志信息
*
* @version 1.0
* @since 2025/10/24
@@ -68,8 +67,7 @@ public class DownlinkLoggingInterceptor implements DownlinkInterceptor {
boolean success = context.getResult() != null && context.getResult().isSuccess();
log.info(
"[下行日志] 下行消息处理完成: protocol={}, productKey={}, deviceId={}, "
+ "success={}, duration={}ms",
"[下行日志] 下行消息处理完成: protocol={}, productKey={}, deviceId={}, " + "success={}, duration={}ms",
context.getProtocolCode(),
context.getProductKey(),
context.getDeviceId(),
@@ -20,16 +20,16 @@ import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 下行子设备拦截器
* 用于处理网关下的子设备相关逻辑
* 下行子设备拦截器 用于处理网关下的子设备相关逻辑
*
* <p>使用场景
*
* <ul>
* <li>子设备路由判断消息是否需要转发到子设备</li>
* <li>子设备鉴权验证子设备是否有权接收指令</li>
* <li>子设备映射将网关设备ID映射到子设备ID</li>
* <li>子设备状态检查检查子设备是否在线</li>
* <li>子设备协议转换处理网关到子设备的协议差异</li>
* <li>子设备路由判断消息是否需要转发到子设备
* <li>子设备鉴权验证子设备是否有权接收指令
* <li>子设备映射将网关设备ID映射到子设备ID
* <li>子设备状态检查检查子设备是否在线
* <li>子设备协议转换处理网关到子设备的协议差异
* </ul>
*
* @version 1.0
@@ -43,7 +43,7 @@ public class DownlinkSubDeviceInterceptor implements DownlinkInterceptor {
// TODO: 注入需要的服务
// @Resource
// private SubDeviceService subDeviceService;
// @Resource
// private GatewayService gatewayService;
@@ -70,37 +70,38 @@ public class DownlinkSubDeviceInterceptor implements DownlinkInterceptor {
// 1. 检查上下文中是否有子设备标识
// 2. 检查产品类型是否支持子设备
// 3. 检查是否有网关设备信息
// 示例代码
// String productKey = context.getProductKey();
// Boolean isSubDevice = context.getAttribute("isSubDevice");
// return Boolean.TRUE.equals(isSubDevice);
// 默认返回 false由你根据业务逻辑实现
return false;
}
@Override
public boolean preHandle(DownlinkContext<?> context) throws Exception {
log.debug("[子设备拦截器][PreHandle] 开始处理: productKey={}, deviceId={}",
context.getProductKey(),
log.debug(
"[子设备拦截器][PreHandle] 开始处理: productKey={}, deviceId={}",
context.getProductKey(),
context.getDeviceId());
// TODO: 实现前置处理逻辑
// 示例场景
// 1. 子设备路由判断
// if (isSubDeviceMessage(context)) {
// context.setAttribute("routeType", "subdevice");
// log.info("[子设备拦截器] 检测到子设备消息");
// }
// 2. 子设备鉴权
// if (!checkSubDevicePermission(context)) {
// log.warn("[子设备拦截器] 子设备鉴权失败");
// return false; // 中断执行
// }
// 3. 子设备状态检查
// if (!isSubDeviceOnline(context)) {
// log.warn("[子设备拦截器] 子设备离线");
@@ -108,14 +109,14 @@ public class DownlinkSubDeviceInterceptor implements DownlinkInterceptor {
// // 可以选择是否中断
// // return false;
// }
// 4. 子设备ID映射
// String subDeviceId = mapToSubDeviceId(context);
// if (subDeviceId != null) {
// context.setAttribute("subDeviceId", subDeviceId);
// log.debug("[子设备拦截器] 映射到子设备ID: {}", subDeviceId);
// }
// 5. 网关信息填充
// fillGatewayInfo(context);
@@ -124,24 +125,25 @@ public class DownlinkSubDeviceInterceptor implements DownlinkInterceptor {
@Override
public void postHandle(DownlinkContext<?> context) throws Exception {
log.debug("[子设备拦截器][PostHandle] 处理完成: productKey={}, deviceId={}",
context.getProductKey(),
log.debug(
"[子设备拦截器][PostHandle] 处理完成: productKey={}, deviceId={}",
context.getProductKey(),
context.getDeviceId());
// TODO: 实现后置处理逻辑
// 示例场景
// 1. 记录子设备下行日志
// if (Boolean.TRUE.equals(context.getAttribute("isSubDevice"))) {
// logSubDeviceDownlink(context);
// }
// 2. 更新子设备状态
// updateSubDeviceStatus(context);
// 3. 统计子设备指令数量
// incrementSubDeviceCommandCount(context);
// 4. 清理临时数据
// cleanupTempData(context);
}
@@ -150,24 +152,25 @@ public class DownlinkSubDeviceInterceptor implements DownlinkInterceptor {
public void afterCompletion(DownlinkContext<?> context, Exception ex) {
// TODO: 实现完成处理逻辑无论成功失败都会执行
// 示例场景
// 1. 异常处理
if (ex != null) {
log.error("[子设备拦截器][AfterCompletion] 处理异常: productKey={}, deviceId={}, error={}",
context.getProductKey(),
context.getDeviceId(),
log.error(
"[子设备拦截器][AfterCompletion] 处理异常: productKey={}, deviceId={}, error={}",
context.getProductKey(),
context.getDeviceId(),
ex.getMessage());
// TODO: 记录子设备错误
// recordSubDeviceError(context, ex);
}
// 2. 资源清理
// cleanupResources(context);
// 3. 释放锁如果有
// releaseLock(context);
// 4. 记录处理耗时
long duration = context.getDuration();
if (duration > 1000) {
@@ -21,8 +21,7 @@ import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 下行数据校验拦截器
* 校验下行消息的必要参数
* 下行数据校验拦截器 校验下行消息的必要参数
*
* @version 1.0
* @since 2025/10/24
@@ -2,35 +2,37 @@
## 📖 概述
`DownlinkSubDeviceInterceptor` 是专门用于处理网关下子设备下行消息的拦截器。它在消息转换之后、业务处理之前执行(MID 阶段),order 值为 400。
`DownlinkSubDeviceInterceptor` 是专门用于处理网关下子设备下行消息的拦截器。它在消息转换之后、业务处理之前执行(MID
阶段),order 值为 400。
## 🎯 适用场景
### 典型使用场景
1. **子设备路由判断**
- 判断消息是否需要转发到子设备
- 区分网关直接处理还是转发给子设备
- 判断消息是否需要转发到子设备
- 区分网关直接处理还是转发给子设备
2. **子设备鉴权验证**
- 验证子设备是否有权接收指令
- 检查子设备是否绑定到对应网关
- 验证子设备是否有权接收指令
- 检查子设备是否绑定到对应网关
3. **子设备状态检查**
- 检查子设备是否在线
- 验证子设备健康状态
- 检查子设备是否在线
- 验证子设备健康状态
4. **子设备ID映射**
- 将网关设备ID映射到子设备ID
- 处理设备层级关系
- 将网关设备ID映射到子设备ID
- 处理设备层级关系
5. **协议转换准备**
- 处理网关到子设备的协议差异
- 准备子设备专用参数
- 处理网关到子设备的协议差异
- 准备子设备专用参数
## 🏗️ 架构设计
### 执行阶段
- **Phase**: `MID`(中置阶段)
- **Order**: `400`
- **时机**: 在消息转换之后,业务处理之前
@@ -370,24 +372,24 @@ public boolean preHandle(DownlinkContext<?> context) throws Exception {
## ⚠️ 注意事项
1. **性能考虑**
- 避免在拦截器中执行耗时操作
- 使用缓存减少数据库查询
- 异步处理非关键逻辑
- 避免在拦截器中执行耗时操作
- 使用缓存减少数据库查询
- 异步处理非关键逻辑
2. **异常处理**
- `preHandle` 中的异常会导致整个流程中断
- 使用 `try-catch` 妥善处理异常
- 在 `afterCompletion` 中记录错误
- `preHandle` 中的异常会导致整个流程中断
- 使用 `try-catch` 妥善处理异常
- 在 `afterCompletion` 中记录错误
3. **上下文传递**
- 使用 `context.setAttribute()` 传递数据
- 注意数据的生命周期
- 在 `afterCompletion` 中清理临时数据
- 使用 `context.setAttribute()` 传递数据
- 注意数据的生命周期
- 在 `afterCompletion` 中清理临时数据
4. **条件判断**
- `supports()` 方法要尽量轻量
- 复杂判断放在 `preHandle()`
- 避免重复查询
- `supports()` 方法要尽量轻量
- 复杂判断放在 `preHandle()`
- 避免重复查询
## 📚 相关文档
@@ -26,15 +26,15 @@ import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 统一下行指令模型
* 作为所有下行指令的标准化入口统一参数格式避免String/JSONObject来回转换
* 统一下行指令模型 作为所有下行指令的标准化入口统一参数格式避免String/JSONObject来回转换
*
* <p>设计目标
*
* <ul>
* <li>类型安全通过强类型字段避免类型转换错误</li>
* <li>参数统一将分散的参数集中管理</li>
* <li>易于扩展通过extensions支持协议特定参数</li>
* <li>向后兼容支持从旧格式String/JSONObject构建</li>
* <li>类型安全通过强类型字段避免类型转换错误
* <li>参数统一将分散的参数集中管理
* <li>易于扩展通过extensions支持协议特定参数
* <li>向后兼容支持从旧格式String/JSONObject构建
* </ul>
*
* @version 1.0
@@ -110,8 +110,7 @@ public class UnifiedDownlinkCommand implements Serializable {
// ===== 扩展参数协议特定 =====
/** 扩展字段(用于协议特定参数,避免污染核心字段) */
@Builder.Default
private Map<String, Object> extensions = new HashMap<>();
@Builder.Default private Map<String, Object> extensions = new HashMap<>();
// ===== 控制标志 =====
@@ -127,12 +126,9 @@ public class UnifiedDownlinkCommand implements Serializable {
// ===== 元数据 =====
/** 指令元数据(用于记录来源、优先级等) */
@Builder.Default
private CommandMetadata metadata = new CommandMetadata();
@Builder.Default private CommandMetadata metadata = new CommandMetadata();
/**
* 指令元数据
*/
/** 指令元数据 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@@ -205,7 +201,7 @@ public class UnifiedDownlinkCommand implements Serializable {
// 控制标志
command.setDeviceReuse(json.getBool("deviceReuse", false));
command.setAdmin(json.getBool("isAdmin", false)); // 使用setAdmin而非setIsAdmin
command.setAdmin(json.getBool("isAdmin", false)); // 使用setAdmin而非setIsAdmin
command.setAllowInsert(json.getBool("allowInsert", false));
// 保存原始数据用于协议特定处理
@@ -250,49 +246,37 @@ public class UnifiedDownlinkCommand implements Serializable {
// ===== 链式调用方法便于构建 =====
/**
* 设置产品Key链式调用
*/
/** 设置产品Key(链式调用) */
public UnifiedDownlinkCommand withProductKey(String productKey) {
this.productKey = productKey;
return this;
}
/**
* 设置设备ID链式调用
*/
/** 设置设备ID(链式调用) */
public UnifiedDownlinkCommand withDeviceId(String deviceId) {
this.deviceId = deviceId;
return this;
}
/**
* 设置应用ID链式调用
*/
/** 设置应用ID(链式调用) */
public UnifiedDownlinkCommand withAppUnionId(String appUnionId) {
this.appUnionId = appUnionId;
return this;
}
/**
* 设置应用实例ID链式调用
*/
/** 设置应用实例ID(链式调用) */
public UnifiedDownlinkCommand withApplicationId(String applicationId) {
this.applicationId = applicationId;
return this;
}
/**
* 设置指令类型链式调用
*/
/** 设置指令类型(链式调用) */
public UnifiedDownlinkCommand withCmd(DownCmd cmd) {
this.cmd = cmd;
return this;
}
/**
* 添加扩展字段链式调用
*/
/** 添加扩展字段(链式调用) */
public UnifiedDownlinkCommand addExtension(String key, Object value) {
if (this.extensions == null) {
this.extensions = new HashMap<>();
@@ -301,9 +285,7 @@ public class UnifiedDownlinkCommand implements Serializable {
return this;
}
/**
* 设置元数据来源链式调用
*/
/** 设置元数据来源(链式调用) */
public UnifiedDownlinkCommand withSource(String source) {
this.metadata.setSource(source);
return this;
@@ -328,12 +310,11 @@ public class UnifiedDownlinkCommand implements Serializable {
// 根据不同指令类型验证特定参数
switch (cmd) {
case DEV_ADD:
case DEV_DEL: // 使用DEV_DEL而非DEV_DELETE
case DEV_DEL: // 使用DEV_DEL而非DEV_DELETE
case DEV_UPDATE:
case DEV_FUNCTION:
if (StrUtil.isBlank(deviceId) && StrUtil.isBlank(iotId)) {
throw new IllegalArgumentException(
cmd.getValue() + "指令必须提供deviceId或iotId");
throw new IllegalArgumentException(cmd.getValue() + "指令必须提供deviceId或iotId");
}
break;
default:
@@ -433,11 +414,18 @@ public class UnifiedDownlinkCommand implements Serializable {
@Override
public String toString() {
return "UnifiedDownlinkCommand{" +
"productKey='" + productKey + '\'' +
", deviceId='" + deviceId + '\'' +
", cmd=" + cmd +
", msgId='" + msgId + '\'' +
'}';
return "UnifiedDownlinkCommand{"
+ "productKey='"
+ productKey
+ '\''
+ ", deviceId='"
+ deviceId
+ '\''
+ ", cmd="
+ cmd
+ ", msgId='"
+ msgId
+ '\''
+ '}';
}
}
@@ -45,7 +45,6 @@ import lombok.extern.slf4j.Slf4j;
* @since 2023/05/19 19:28
*/
@Slf4j
public class ProtocolCodecMagic extends ProtocolCodecSupportWrapper
implements ProtocolCodecLoader, ProtocolCodecSupport, ProtocolCodecWrapper {
@@ -28,8 +28,10 @@ public interface IDown {
/**
* 下行处理统一命令对象
* <p>这是唯一的下行入口方法提供类型安全的参数传递</p>
* <p>此方法会自动应用拦截器链如果已配置</p>
*
* <p>这是唯一的下行入口方法提供类型安全的参数传递
*
* <p>此方法会自动应用拦截器链如果已配置
*
* @param command 统一下行命令对象
* @return 处理结果
@@ -57,8 +59,7 @@ public interface IDown {
}
/**
* 创建下行上下文
* 基于UnifiedDownlinkCommand创建上下文对象
* 创建下行上下文 基于UnifiedDownlinkCommand创建上下文对象
*
* @param command 统一下行命令对象
* @return 下行上下文
@@ -1,4 +1,4 @@
///*
/// *
// *
// * Copyright (c) 2025, NexIoT. All Rights Reserved.
// *
@@ -10,25 +10,25 @@
// *
// */
//
//package cn.universal.core.message;
// package cn.universal.core.message;
//
//import cn.hutool.json.JSONObject;
//import cn.hutool.json.JSONUtil;
//import cn.universal.common.constant.IoTConstant.DownCmd;
//import org.junit.Test;
// import cn.hutool.json.JSONObject;
// import cn.hutool.json.JSONUtil;
// import cn.universal.common.constant.IoTConstant.DownCmd;
// import org.junit.Test;
//
//import java.util.HashMap;
//import java.util.Map;
// import java.util.HashMap;
// import java.util.Map;
//
//import static org.junit.Assert.*;
// import static org.junit.Assert.*;
//
///**
/// **
// * UnifiedDownlinkCommand 单元测试
// *
// * @version 1.0
// * @since 2025/10/25
// */
//public class UnifiedDownlinkCommandTest {
// public class UnifiedDownlinkCommandTest {
//
// @Test
// public void testFromJson() {
@@ -54,7 +54,8 @@
// public void testFromString() {
// // 准备JSON字符串
// String jsonString =
// "{\"productKey\":\"test-product-002\",\"deviceId\":\"test-device-002\",\"cmd\":\"DEV_FUNCTION\",\"function\":{\"messageType\":\"FUNCTIONS\",\"function\":\"turnOn\",\"data\":{\"switch\":true}}}";
//
// "{\"productKey\":\"test-product-002\",\"deviceId\":\"test-device-002\",\"cmd\":\"DEV_FUNCTION\",\"function\":{\"messageType\":\"FUNCTIONS\",\"function\":\"turnOn\",\"data\":{\"switch\":true}}}";
//
// // 执行转换
// UnifiedDownlinkCommand command = UnifiedDownlinkCommand.fromString(jsonString);
@@ -266,7 +267,8 @@
// public void testFromJson_WithExtensions() {
// // 测试包含扩展字段的JSON转换
// String jsonString =
// "{\"productKey\":\"test-product-013\",\"deviceId\":\"test-device-013\",\"cmd\":\"DEV_ADD\",\"data\":{\"imei\":\"123456789\",\"location\":\"Beijing\"}}";
//
// "{\"productKey\":\"test-product-013\",\"deviceId\":\"test-device-013\",\"cmd\":\"DEV_ADD\",\"data\":{\"imei\":\"123456789\",\"location\":\"Beijing\"}}";
//
// UnifiedDownlinkCommand command = UnifiedDownlinkCommand.fromString(jsonString);
//
@@ -347,4 +349,4 @@
// assertTrue(str.contains("DEV_ADD"));
// assertTrue(str.contains("msg-001"));
// }
//}
// }

Some files were not shown because too many files have changed in this diff Show More