mirror of
https://gitee.com/NexIoT/Universal-IoT-Java.git
synced 2026-10-31 00:41:27 +08:00
version(2.1):11月代码合并,保持与企业版和演示环境一致,补充docker镜像和SQL;增加wvp的集成开源内容
This commit is contained in:
+127
-121
@@ -1,131 +1,137 @@
|
||||
<?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>
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-universal-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-universal-oss</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>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>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>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-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-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>
|
||||
@@ -10,7 +10,7 @@ import org.springframework.stereotype.Component;
|
||||
public class ConnectionConfig {
|
||||
|
||||
// TCP配置 - 复用现有配置
|
||||
@Value("${tcp.protocol.sni.host.out:#productKey.tcp.nexiot.xyz}")
|
||||
@Value("${tcp.protocol.sni.host.out:#productKey.tcp.nexiot.cc}")
|
||||
private String tcpSniHostTemplate;
|
||||
|
||||
@Value("${tcp.protocol.sni.port.out:38883}")
|
||||
|
||||
+39
-13
@@ -53,6 +53,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.aop.framework.AopContext;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -291,7 +292,7 @@ public class NetworkServiceImpl implements INetworkService {
|
||||
}
|
||||
// 启用/停用状态
|
||||
vo.setEnableName(Boolean.TRUE.equals(network.getState()) ? "已启用" : "已停用");
|
||||
// 实际运行状态
|
||||
// 实际运行状态和绑定产品
|
||||
String type = network.getType();
|
||||
String productKey = network.getProductKey();
|
||||
String unionId = network.getUnionId();
|
||||
@@ -310,17 +311,29 @@ public class NetworkServiceImpl implements INetworkService {
|
||||
vo.setStateName("未启动");
|
||||
vo.setRunning(Boolean.FALSE);
|
||||
}
|
||||
|
||||
} else if (NetworkType.MQTT_CLIENT.getId().equals(type)
|
||||
|| NetworkType.MQTT_SERVER.getId().equals(type)) {
|
||||
if (mqttServerManager != null && mqttServerManager.isConnected(unionId)) {
|
||||
vo.setStateName("已启动");
|
||||
vo.setRunning(Boolean.TRUE);
|
||||
} else {
|
||||
vo.setStateName("未启动");
|
||||
vo.setRunning(Boolean.FALSE);
|
||||
// TCP类型查询绑定产品
|
||||
if (NetworkType.TCP_SERVER.getId().equals(type)) {
|
||||
IoTProductBO ioTProductBO =
|
||||
ioTProductMapper.selectTcpProductsUseNetwork(productKey);
|
||||
vo.setBindTcpServerProductCount(ioTProductBO == null ? 0 : 1);
|
||||
vo.setBindTcpServerProducts(ioTProductBO);
|
||||
}
|
||||
} else if (NetworkType.UDP.getId().equals(type)) {
|
||||
|
||||
} else if (NetworkType.MQTT_CLIENT.getId().equals(type)
|
||||
|| NetworkType.MQTT_SERVER.getId().equals(type)) {
|
||||
// 查询MQTT绑定产品
|
||||
List<IoTProductBO> ioTProductBOS = ioTProductMapper.selectMqttProductsUseNetwork(unionId);
|
||||
vo.setBindMqttServerProductCount(CollUtil.size(ioTProductBOS));
|
||||
vo.setBindMqttServerProducts(ioTProductBOS);
|
||||
// 查询运行状态
|
||||
if (mqttServerManager != null && mqttServerManager.isConnected(unionId)) {
|
||||
vo.setStateName("已启动");
|
||||
vo.setRunning(Boolean.TRUE);
|
||||
} else {
|
||||
vo.setStateName("未启动");
|
||||
vo.setRunning(Boolean.FALSE);
|
||||
}
|
||||
} else if (NetworkType.UDP.getId().equals(type)) {
|
||||
if (udpClusterService != null && udpClusterService.isProductServerAlive(productKey)) {
|
||||
vo.setStateName("已启动");
|
||||
vo.setRunning(Boolean.TRUE);
|
||||
@@ -480,13 +493,26 @@ public class NetworkServiceImpl implements INetworkService {
|
||||
}
|
||||
}
|
||||
}
|
||||
return networkMapper.deleteNetworkById(id);
|
||||
int ct = networkMapper.deleteNetworkById(id);
|
||||
if (ct <= 0) {
|
||||
throw new RuntimeException("删除失败,请重试");
|
||||
}
|
||||
ioTProductMapper.updateNetworkProductsUseNetwork(network.getUnionId());
|
||||
return ct;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public int deleteNetworkByIds(Integer[] ids) {
|
||||
return networkMapper.deleteNetworkByIds(ids);
|
||||
if (ids == null || ids.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
for (Integer id : ids) {
|
||||
count= ((INetworkService) AopContext.currentProxy()).deleteNetworkById(id);
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -172,12 +172,12 @@ public class NetworkConfigUtil {
|
||||
config.set("readerIdleTime", 360);
|
||||
config.set("writerIdleTime", 0);
|
||||
config.set("decoderType", "STRING");
|
||||
config.set("parserType", "DELIMITED");
|
||||
config.set("parserType", "DELIMITER");
|
||||
config.set(
|
||||
"parserConfiguration",
|
||||
new JSONObject()
|
||||
.set("byteOrderLittle", true)
|
||||
.set("delimited", "]")
|
||||
.set("delimiter", "]")
|
||||
.set("delimitedMaxlength", 1024)
|
||||
.set("failFast", true));
|
||||
return config.toString();
|
||||
|
||||
+85
-32
@@ -13,12 +13,15 @@
|
||||
package cn.universal.admin.platform.scheduler;
|
||||
|
||||
import cn.universal.admin.platform.service.IGatewayPollingService;
|
||||
import cn.universal.common.constant.IoTConstant;
|
||||
import cn.universal.persistence.entity.GatewayPollingConfig;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
@@ -26,70 +29,126 @@ 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);
|
||||
String traceId = generateTraceId("polling-30s");
|
||||
MDC.put(IoTConstant.TRACE_ID, traceId);
|
||||
try {
|
||||
executePollingWithLock(30);
|
||||
} finally {
|
||||
MDC.remove(IoTConstant.TRACE_ID);
|
||||
}
|
||||
}
|
||||
|
||||
/** 60秒轮询任务 */
|
||||
/**
|
||||
* 60秒轮询任务
|
||||
*/
|
||||
@Scheduled(cron = "0 0/1 * * * ?")
|
||||
public void polling60s() {
|
||||
executePollingWithLock(60);
|
||||
String traceId = generateTraceId("polling-60s");
|
||||
MDC.put(IoTConstant.TRACE_ID, traceId);
|
||||
try {
|
||||
executePollingWithLock(60);
|
||||
} finally {
|
||||
MDC.remove(IoTConstant.TRACE_ID);
|
||||
}
|
||||
}
|
||||
|
||||
/** 120秒轮询任务 */
|
||||
/**
|
||||
* 120秒轮询任务
|
||||
*/
|
||||
@Scheduled(cron = "0 0/2 * * * ?")
|
||||
public void polling120s() {
|
||||
executePollingWithLock(120);
|
||||
String traceId = generateTraceId("polling-120s");
|
||||
MDC.put(IoTConstant.TRACE_ID, traceId);
|
||||
try {
|
||||
executePollingWithLock(120);
|
||||
} finally {
|
||||
MDC.remove(IoTConstant.TRACE_ID);
|
||||
}
|
||||
}
|
||||
|
||||
/** 300秒轮询任务 (5分钟) */
|
||||
/**
|
||||
* 300秒轮询任务 (5分钟)
|
||||
*/
|
||||
@Scheduled(cron = "0 0/5 * * * ?")
|
||||
public void polling300s() {
|
||||
executePollingWithLock(300);
|
||||
String traceId = generateTraceId("polling-300s");
|
||||
MDC.put(IoTConstant.TRACE_ID, traceId);
|
||||
try {
|
||||
executePollingWithLock(300);
|
||||
} finally {
|
||||
MDC.remove(IoTConstant.TRACE_ID);
|
||||
}
|
||||
}
|
||||
|
||||
/** 600秒轮询任务 (10分钟) */
|
||||
/**
|
||||
* 600秒轮询任务 (10分钟)
|
||||
*/
|
||||
@Scheduled(cron = "0 0/10 * * * ?")
|
||||
public void polling600s() {
|
||||
executePollingWithLock(600);
|
||||
String traceId = generateTraceId("polling-600s");
|
||||
MDC.put(IoTConstant.TRACE_ID, traceId);
|
||||
try {
|
||||
executePollingWithLock(600);
|
||||
} finally {
|
||||
MDC.remove(IoTConstant.TRACE_ID);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 traceId
|
||||
* 格式: {prefix}-{timestamp}-{random}
|
||||
* 示例: polling-30s-1699344000-a1b2c3
|
||||
*/
|
||||
private String generateTraceId(String prefix) {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
String random = UUID.randomUUID().toString().substring(0, 6);
|
||||
return String.format("%s-%s-%s", prefix, timestamp, random);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用分布式锁执行轮询任务
|
||||
*
|
||||
*
|
||||
* @param intervalSeconds 轮询间隔(秒)
|
||||
*/
|
||||
private void executePollingWithLock(int intervalSeconds) {
|
||||
String lockKey = "gateway:polling:lock:" + intervalSeconds;
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
|
||||
// 打印线程信息用于验证虚拟线程
|
||||
Thread currentThread = Thread.currentThread();
|
||||
log.debug("当前线程: name={}, isVirtual={}, id={}",
|
||||
currentThread.getName(),
|
||||
currentThread.isVirtual(),
|
||||
currentThread.threadId());
|
||||
|
||||
try {
|
||||
// 尝试获取锁,最多等待0秒,锁自动释放时间为任务执行周期的80%
|
||||
boolean acquired = lock.tryLock(0, (long) (intervalSeconds * 0.8), TimeUnit.SECONDS);
|
||||
|
||||
if (acquired) {
|
||||
log.info("✓ 节点获得 {}s 轮询任务锁,开始执行", intervalSeconds);
|
||||
log.debug("✓ 节点获得 {}s 轮询任务锁,开始执行", intervalSeconds);
|
||||
executePollingTask(intervalSeconds);
|
||||
} else {
|
||||
log.debug("✗ 节点未获得 {}s 轮询任务锁,跳过执行", intervalSeconds);
|
||||
@@ -110,22 +169,21 @@ 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);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("本次{}s轮询共 {} 个网关设备", intervalSeconds, configs.size());
|
||||
log.debug("本次{}s轮询共 {} 个网关设备", intervalSeconds, configs.size());
|
||||
|
||||
// 2. 并发执行轮询
|
||||
int successCount = 0;
|
||||
@@ -142,12 +200,7 @@ public class GatewayPollingScheduler {
|
||||
}
|
||||
|
||||
long costTime = System.currentTimeMillis() - startTime;
|
||||
log.info(
|
||||
"{}s轮询任务完成: 总数={}, 成功={}, 失败={}, 耗时={}ms",
|
||||
intervalSeconds,
|
||||
configs.size(),
|
||||
successCount,
|
||||
failCount,
|
||||
costTime);
|
||||
log.debug("{}s轮询任务完成: 总数={}, 成功={}, 失败={}, 耗时={}ms",
|
||||
intervalSeconds, configs.size(), successCount, failCount, costTime);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -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 操作结果
|
||||
|
||||
+286
-30
@@ -1,11 +1,13 @@
|
||||
package cn.universal.admin.platform.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.universal.admin.config.ConnectionConfig;
|
||||
import cn.universal.admin.platform.dto.ConnectionInfoDTO;
|
||||
import cn.universal.admin.platform.service.ConnectionInfoService;
|
||||
import cn.universal.common.constant.IoTConstant;
|
||||
import cn.universal.persistence.entity.IoTProduct;
|
||||
import cn.universal.persistence.entity.Network;
|
||||
import cn.universal.persistence.mapper.IoTProductMapper;
|
||||
@@ -139,7 +141,7 @@ public class ConnectionInfoServiceImpl implements ConnectionInfoService {
|
||||
// 处理MQTT主题信息
|
||||
if ("mqtt".equals(product.getThirdPlatform())) {
|
||||
ConnectionInfoDTO.TopicInfo subscribeTopics =
|
||||
buildSubscribeTopics(config, product.getProductKey());
|
||||
buildThirdMQTTSubscribeTopics(config, product);
|
||||
builder.subscribeTopics(subscribeTopics);
|
||||
}
|
||||
|
||||
@@ -214,45 +216,299 @@ public class ConnectionInfoServiceImpl implements ConnectionInfoService {
|
||||
.passthroughTopics(passthroughTopics)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/** 构建订阅主题信息 */
|
||||
private ConnectionInfoDTO.TopicInfo buildSubscribeTopics(JSONObject config, String productKey) {
|
||||
String subscribe = config.getStr("subscribeTopics");
|
||||
;
|
||||
if (!JSONUtil.isTypeJSON(subscribe)) {
|
||||
private ConnectionInfoDTO.TopicInfo buildThirdMQTTSubscribeTopics(JSONObject config, IoTProduct product) {
|
||||
String productKey = product.getProductKey();
|
||||
Object subscribeTopicsObj = config.get("subscribeTopics");
|
||||
|
||||
if (subscribeTopicsObj == null) {
|
||||
return buildDefaultTopics(productKey);
|
||||
}
|
||||
JSONObject subscribeTopics = config.getJSONObject("subscribeTopics");
|
||||
if (subscribeTopics == null || subscribeTopics.isEmpty()) {
|
||||
// 使用默认主题
|
||||
|
||||
// 检测是否为数组格式(第三方MQTT新格式)
|
||||
if (subscribeTopicsObj instanceof List || subscribeTopicsObj instanceof JSONArray) {
|
||||
return buildThirdPartyTopics(subscribeTopicsObj, product);
|
||||
}
|
||||
|
||||
// 检测是否为对象格式(旧格式)
|
||||
if (subscribeTopicsObj instanceof JSONObject) {
|
||||
JSONObject subscribeTopics = (JSONObject) subscribeTopicsObj;
|
||||
if (subscribeTopics.isEmpty()) {
|
||||
return buildDefaultTopics(productKey);
|
||||
}
|
||||
|
||||
ConnectionInfoDTO.ThingTopics thingTopics = null;
|
||||
ConnectionInfoDTO.PassthroughTopics passthroughTopics = null;
|
||||
|
||||
JSONObject thingTopicsConfig = subscribeTopics.getJSONObject("thingTopics");
|
||||
if (thingTopicsConfig != null) {
|
||||
thingTopics =
|
||||
ConnectionInfoDTO.ThingTopics.builder()
|
||||
.propertyUp(thingTopicsConfig.getStr("propertyUp"))
|
||||
.eventUp(thingTopicsConfig.getStr("eventUp"))
|
||||
.commandDown(thingTopicsConfig.getStr("commandDown"))
|
||||
.build();
|
||||
}
|
||||
|
||||
JSONObject passthroughTopicsConfig = subscribeTopics.getJSONObject("passthroughTopics");
|
||||
if (passthroughTopicsConfig != null) {
|
||||
passthroughTopics =
|
||||
ConnectionInfoDTO.PassthroughTopics.builder()
|
||||
.dataUp(passthroughTopicsConfig.getStr("dataUp"))
|
||||
.commandDown(passthroughTopicsConfig.getStr("commandDown"))
|
||||
.build();
|
||||
}
|
||||
|
||||
return ConnectionInfoDTO.TopicInfo.builder()
|
||||
.thingTopics(thingTopics)
|
||||
.passthroughTopics(passthroughTopics)
|
||||
.build();
|
||||
}
|
||||
|
||||
// 其他情况,回退到默认主题
|
||||
return buildDefaultTopics(productKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建第三方MQTT主题信息(数组格式)
|
||||
*
|
||||
* @param subscribeTopicsObj 订阅主题配置(数组格式)
|
||||
* @param product 产品对象,用于过滤匹配的主题和获取下行主题配置
|
||||
* @return 主题信息
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private ConnectionInfoDTO.TopicInfo buildThirdPartyTopics(Object subscribeTopicsObj, IoTProduct product) {
|
||||
String productKey = product.getProductKey();
|
||||
List<Object> topicList;
|
||||
|
||||
// 转换为List
|
||||
if (subscribeTopicsObj instanceof JSONArray) {
|
||||
JSONArray jsonArray = (JSONArray) subscribeTopicsObj;
|
||||
topicList = jsonArray.toList(Object.class);
|
||||
} else if (subscribeTopicsObj instanceof List) {
|
||||
topicList = (List<Object>) subscribeTopicsObj;
|
||||
} else {
|
||||
return buildDefaultTopics(productKey);
|
||||
}
|
||||
ConnectionInfoDTO.ThingTopics thingTopics = null;
|
||||
ConnectionInfoDTO.PassthroughTopics passthroughTopics = null;
|
||||
|
||||
JSONObject thingTopicsConfig = subscribeTopics.getJSONObject("thingTopics");
|
||||
if (thingTopicsConfig != null) {
|
||||
thingTopics =
|
||||
ConnectionInfoDTO.ThingTopics.builder()
|
||||
.propertyUp(thingTopicsConfig.getStr("propertyUp"))
|
||||
.eventUp(thingTopicsConfig.getStr("eventUp"))
|
||||
.commandDown(thingTopicsConfig.getStr("commandDown"))
|
||||
.build();
|
||||
if (topicList == null || topicList.isEmpty()) {
|
||||
return buildDefaultTopics(productKey);
|
||||
}
|
||||
|
||||
JSONObject passthroughTopicsConfig = subscribeTopics.getJSONObject("passthroughTopics");
|
||||
if (passthroughTopicsConfig != null) {
|
||||
passthroughTopics =
|
||||
ConnectionInfoDTO.PassthroughTopics.builder()
|
||||
.dataUp(passthroughTopicsConfig.getStr("dataUp"))
|
||||
.commandDown(passthroughTopicsConfig.getStr("commandDown"))
|
||||
.build();
|
||||
// 使用List收集相同类型的主题,最后用逗号连接
|
||||
List<String> propertyUpList = new java.util.ArrayList<>();
|
||||
List<String> eventUpList = new java.util.ArrayList<>();
|
||||
List<String> thingCommandDownList = new java.util.ArrayList<>();
|
||||
List<String> passthroughDataUpList = new java.util.ArrayList<>();
|
||||
List<String> passthroughCommandDownList = new java.util.ArrayList<>();
|
||||
|
||||
boolean hasThingTopics = false;
|
||||
boolean hasPassthroughTopics = false;
|
||||
|
||||
// 遍历主题配置,过滤出与当前productKey匹配的主题
|
||||
for (Object item : topicList) {
|
||||
if (!(item instanceof JSONObject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
JSONObject topicConfig = (JSONObject) item;
|
||||
|
||||
// 检查是否启用
|
||||
if (!topicConfig.getBool("enabled", true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查productKey匹配(如果配置的productKey为空,视为通用主题)
|
||||
String configProductKey = topicConfig.getStr("productKey");
|
||||
if (StrUtil.isNotBlank(configProductKey) && !configProductKey.equals(productKey)) {
|
||||
continue; // 不匹配,跳过
|
||||
}
|
||||
|
||||
String topicPattern = topicConfig.getStr("topicPattern");
|
||||
if (StrUtil.isBlank(topicPattern)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 根据topicCategory分类
|
||||
String topicCategory = topicConfig.getStr("topicCategory");
|
||||
|
||||
if ("THING_MODEL".equals(topicCategory)) {
|
||||
// 物模型主题:根据topic pattern判断是propertyUp、eventUp还是commandDown
|
||||
String topicType = inferThingTopicType(topicPattern);
|
||||
switch (topicType) {
|
||||
case "propertyUp":
|
||||
propertyUpList.add(topicPattern);
|
||||
hasThingTopics = true;
|
||||
break;
|
||||
case "eventUp":
|
||||
eventUpList.add(topicPattern);
|
||||
hasThingTopics = true;
|
||||
break;
|
||||
case "commandDown":
|
||||
thingCommandDownList.add(topicPattern);
|
||||
hasThingTopics = true;
|
||||
break;
|
||||
default:
|
||||
// 如果无法判断,默认作为propertyUp
|
||||
propertyUpList.add(topicPattern);
|
||||
hasThingTopics = true;
|
||||
break;
|
||||
}
|
||||
} else if ("PASSTHROUGH".equals(topicCategory)) {
|
||||
// 透传主题:根据topic pattern判断是dataUp还是commandDown
|
||||
String topicType = inferPassthroughTopicType(topicPattern);
|
||||
switch (topicType) {
|
||||
case "dataUp":
|
||||
passthroughDataUpList.add(topicPattern);
|
||||
hasPassthroughTopics = true;
|
||||
break;
|
||||
case "commandDown":
|
||||
passthroughCommandDownList.add(topicPattern);
|
||||
hasPassthroughTopics = true;
|
||||
break;
|
||||
default:
|
||||
// 如果无法判断,默认作为dataUp
|
||||
passthroughDataUpList.add(topicPattern);
|
||||
hasPassthroughTopics = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// 未配置topicCategory,尝试自动判断
|
||||
// 如果包含/down/,可能是下行主题;否则默认为上行主题
|
||||
if (topicPattern.contains("/down/") || topicPattern.contains("/down")) {
|
||||
// 无法确定是物模型还是透传,优先作为物模型的commandDown
|
||||
thingCommandDownList.add(topicPattern);
|
||||
hasThingTopics = true;
|
||||
} else {
|
||||
// 上行主题,无法确定类型,优先作为透传的dataUp
|
||||
passthroughDataUpList.add(topicPattern);
|
||||
hasPassthroughTopics = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ConnectionInfoDTO.TopicInfo.builder()
|
||||
.thingTopics(thingTopics)
|
||||
.passthroughTopics(passthroughTopics)
|
||||
.build();
|
||||
// 构建物模型主题
|
||||
ConnectionInfoDTO.ThingTopics.ThingTopicsBuilder thingTopicsBuilder =
|
||||
ConnectionInfoDTO.ThingTopics.builder();
|
||||
|
||||
if (!propertyUpList.isEmpty()) {
|
||||
thingTopicsBuilder.propertyUp(String.join(",", propertyUpList));
|
||||
}
|
||||
if (!eventUpList.isEmpty()) {
|
||||
thingTopicsBuilder.eventUp(String.join(",", eventUpList));
|
||||
}
|
||||
|
||||
// 处理下行主题:优先从产品配置中读取downTopic
|
||||
String commandDownTopic = getProductDownTopic(product);
|
||||
if (StrUtil.isNotBlank(commandDownTopic)) {
|
||||
// 产品配置中有downTopic,直接使用
|
||||
thingTopicsBuilder.commandDown(commandDownTopic);
|
||||
} else if (!thingCommandDownList.isEmpty()) {
|
||||
// 如果产品配置中没有,使用收集到的下行主题(用逗号连接)
|
||||
thingTopicsBuilder.commandDown(String.join(",", thingCommandDownList));
|
||||
} else {
|
||||
// 如果都没有,使用默认的物模型下行主题格式(与BaseReplyProcessor中的规则一致)
|
||||
// 格式:$thing/down/${productKey}/${deviceId}
|
||||
String defaultDownTopic = "$thing/down/${productKey}/${deviceId}";
|
||||
thingTopicsBuilder.commandDown(defaultDownTopic);
|
||||
}
|
||||
|
||||
// 构建透传主题
|
||||
ConnectionInfoDTO.PassthroughTopics.PassthroughTopicsBuilder passthroughTopicsBuilder =
|
||||
ConnectionInfoDTO.PassthroughTopics.builder();
|
||||
|
||||
if (!passthroughDataUpList.isEmpty()) {
|
||||
passthroughTopicsBuilder.dataUp(String.join(",", passthroughDataUpList));
|
||||
}
|
||||
if (!passthroughCommandDownList.isEmpty()) {
|
||||
passthroughTopicsBuilder.commandDown(String.join(",", passthroughCommandDownList));
|
||||
}
|
||||
|
||||
ConnectionInfoDTO.TopicInfo.TopicInfoBuilder builder = ConnectionInfoDTO.TopicInfo.builder();
|
||||
|
||||
if (hasThingTopics) {
|
||||
builder.thingTopics(thingTopicsBuilder.build());
|
||||
}
|
||||
|
||||
if (hasPassthroughTopics) {
|
||||
builder.passthroughTopics(passthroughTopicsBuilder.build());
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从产品配置中获取下行主题
|
||||
* 如果产品配置中没有downTopic,返回null,使用默认规则
|
||||
*
|
||||
* @param product 产品对象
|
||||
* @return 下行主题,如果不存在返回null
|
||||
*/
|
||||
private String getProductDownTopic(IoTProduct product) {
|
||||
if (product == null || StrUtil.isBlank(product.getConfiguration())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject config = JSONUtil.parseObj(product.getConfiguration());
|
||||
String downTopic = config.getStr("downTopic");
|
||||
return StrUtil.isNotBlank(downTopic) ? downTopic : null;
|
||||
} catch (Exception e) {
|
||||
log.warn("解析产品配置失败,productKey={}", product.getProductKey(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推断物模型主题类型(propertyUp、eventUp、commandDown)
|
||||
*/
|
||||
private String inferThingTopicType(String topicPattern) {
|
||||
if (StrUtil.isBlank(topicPattern)) {
|
||||
return "propertyUp";
|
||||
}
|
||||
|
||||
String lowerPattern = topicPattern.toLowerCase();
|
||||
|
||||
// 判断是否为下行主题
|
||||
if (lowerPattern.contains("/down/") || lowerPattern.contains("/down")
|
||||
|| lowerPattern.contains("/set/") || lowerPattern.contains("/set")) {
|
||||
return "commandDown";
|
||||
}
|
||||
|
||||
// 判断是否为事件主题
|
||||
if (lowerPattern.contains("/event/") || lowerPattern.contains("/event")) {
|
||||
return "eventUp";
|
||||
}
|
||||
|
||||
// 判断是否为属性主题
|
||||
if (lowerPattern.contains("/property/") || lowerPattern.contains("/property")) {
|
||||
return "propertyUp";
|
||||
}
|
||||
|
||||
// 默认作为属性上报
|
||||
return "propertyUp";
|
||||
}
|
||||
|
||||
/**
|
||||
* 推断透传主题类型(dataUp、commandDown)
|
||||
*/
|
||||
private String inferPassthroughTopicType(String topicPattern) {
|
||||
if (StrUtil.isBlank(topicPattern)) {
|
||||
return "dataUp";
|
||||
}
|
||||
|
||||
String lowerPattern = topicPattern.toLowerCase();
|
||||
|
||||
// 判断是否为下行主题
|
||||
if (lowerPattern.contains("/down/") || lowerPattern.contains("/down")
|
||||
|| lowerPattern.contains("/set/") || lowerPattern.contains("/set")
|
||||
|| lowerPattern.contains("/command/") || lowerPattern.contains("/command")) {
|
||||
return "commandDown";
|
||||
}
|
||||
|
||||
// 默认作为数据上报
|
||||
return "dataUp";
|
||||
}
|
||||
|
||||
/** 替换主题中的产品Key占位符 */
|
||||
|
||||
+23
-18
@@ -15,6 +15,9 @@ 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;
|
||||
@@ -29,6 +32,8 @@ 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;
|
||||
@@ -188,12 +193,11 @@ 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 =
|
||||
@@ -215,9 +219,8 @@ 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())) {
|
||||
// 非第一条指令先等待,给设备响应时间
|
||||
@@ -225,13 +228,15 @@ 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;
|
||||
@@ -280,7 +285,10 @@ 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("设备不存在");
|
||||
@@ -298,16 +306,13 @@ 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);
|
||||
|
||||
+1
-2
@@ -546,8 +546,7 @@ 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;
|
||||
}
|
||||
|
||||
+18
-67
@@ -487,7 +487,9 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc
|
||||
"supportMQTTNetwork",
|
||||
"selectDevProductV4ListNoPage",
|
||||
"countDevNumberByProductKey",
|
||||
"iot_product_device"
|
||||
"iot_product_device",
|
||||
"getProductEncoderType",
|
||||
"getProductDecoderType"
|
||||
},
|
||||
allEntries = true)
|
||||
public int updateDevProduct(IoTProduct ioTProduct) {
|
||||
@@ -529,7 +531,11 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc
|
||||
},
|
||||
allEntries = true)
|
||||
public int deleteDevProductByIds(String[] ids) {
|
||||
String appUnionId = queryIotUser(SecurityUtils.getUnionId()).getUnionId();
|
||||
IoTUser iotUser = queryIotUser(SecurityUtils.getUnionId());
|
||||
if (iotUser == null) {
|
||||
throw new IoTException("获取用户信息异常", HttpStatus.HTTP_UNAUTHORIZED);
|
||||
}
|
||||
String appUnionId = iotUser.getUnionId();
|
||||
int count = 0;
|
||||
for (String id : ids) {
|
||||
IoTProduct ioTProduct = ioTProductMapper.selectDevProductById(id);
|
||||
@@ -595,17 +601,22 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc
|
||||
"selectDevCount",
|
||||
"supportMQTTNetwork",
|
||||
"iot_dev_product_list",
|
||||
"getProductConfiguration"
|
||||
"getProductConfiguration",
|
||||
"getProductEncoderType",
|
||||
"getProductDecoderType"
|
||||
},
|
||||
allEntries = true)
|
||||
public int updateDevProductConfig(IoTProductVO devProduct) {
|
||||
String appUnionId = queryIotUser(SecurityUtils.getUnionId()).getUnionId();
|
||||
IoTUser iotUser = queryIotUser(SecurityUtils.getUnionId());
|
||||
if (iotUser == null || iotUser.getUnionId() == null) {
|
||||
throw new IoTException("获取用户信息异常", HttpStatus.HTTP_UNAUTHORIZED);
|
||||
}
|
||||
// 根据id获取产品信息
|
||||
IoTProduct product = ioTProductMapper.selectDevProductById(String.valueOf(devProduct.getId()));
|
||||
if (devProduct == null) {
|
||||
throw new IoTException("产品不存在");
|
||||
}
|
||||
if (!appUnionId.equals(product.getCreatorId())) {
|
||||
if (!iotUser.getUnionId().equals(product.getCreatorId()) && !iotUser.isAdmin()) {
|
||||
throw new IoTException("无产品权限");
|
||||
}
|
||||
// 获取新的产品配置信息
|
||||
@@ -627,67 +638,6 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc
|
||||
throw new IoTException(port + "端口" + "已被占用");
|
||||
}
|
||||
}
|
||||
Network network =
|
||||
networkMapper.selectOne(Network.builder().productKey(product.getProductKey()).build());
|
||||
int updateCount;
|
||||
try {
|
||||
// 获取配置
|
||||
JSONObject tcpConfig = new JSONObject();
|
||||
JSONObject parserConfiguration = new JSONObject();
|
||||
parserConfiguration.set("cert", newConfig.getStr("cert"));
|
||||
parserConfiguration.set("key", newConfig.getStr("key"));
|
||||
parserConfiguration.set("rootCa", newConfig.getStr("rootCa"));
|
||||
parserConfiguration.set("delimited", newConfig.getStr("delimited"));
|
||||
parserConfiguration.set("lineMaxLength", newConfig.getInt("lineMaxLength"));
|
||||
parserConfiguration.set("delimitedMaxlength", newConfig.getInt("delimitedMaxlength"));
|
||||
parserConfiguration.set("fixedLength", newConfig.getInt("fixedLength"));
|
||||
parserConfiguration.set("byteOrderLittle", newConfig.getBool("byteOrderLittle", false));
|
||||
parserConfiguration.set("maxFrameLength", newConfig.getInt("maxFrameLength"));
|
||||
parserConfiguration.set("lengthFieldOffset", newConfig.getInt("lengthFieldOffset"));
|
||||
parserConfiguration.set("lengthFieldLength", newConfig.getInt("lengthFieldLength"));
|
||||
parserConfiguration.set("lengthAdjustment", newConfig.getInt("lengthAdjustment"));
|
||||
parserConfiguration.set("initialBytesToStrip", newConfig.getInt("initialBytesToStrip"));
|
||||
parserConfiguration.set("failFast", newConfig.getBool("failFast", false));
|
||||
tcpConfig.set("parserConfiguration", parserConfiguration);
|
||||
tcpConfig.set("ssl", newConfig.getBool("ssl", false));
|
||||
tcpConfig.set("port", port);
|
||||
tcpConfig.set("onlyCache", newConfig.getBool("onlyCache", false));
|
||||
tcpConfig.set("productKey", product.getProductKey());
|
||||
tcpConfig.set("host", newConfig.getStr("host"));
|
||||
tcpConfig.set(IoTConstant.ALLOW_INSERT, newConfig.getBool(IoTConstant.ALLOW_INSERT, false));
|
||||
tcpConfig.set("preStore", newConfig.getBool("preStore", false));
|
||||
tcpConfig.set("alwaysPreDecode", newConfig.getBool("alwaysPreDecode", false));
|
||||
tcpConfig.set("decoderType", newConfig.getStr("decoderType"));
|
||||
tcpConfig.set("parserType", newConfig.getStr("parserType"));
|
||||
tcpConfig.set("allIdleTime", newConfig.getInt("allIdleTime"));
|
||||
tcpConfig.set("readerIdleTime", newConfig.getInt("readerIdleTime"));
|
||||
tcpConfig.set("writerIdleTime", newConfig.getInt("writerIdleTime"));
|
||||
tcpConfig.set("readTimeout", newConfig.getInt("readTimeout"));
|
||||
tcpConfig.set("sendTimeout", newConfig.getInt("sendTimeout"));
|
||||
tcpConfig.set("idleInterval", newConfig.getInt("idleInterval"));
|
||||
// if (Objects.isNull(network)) {
|
||||
// // 新增
|
||||
// updateCount = networkMapper.insert(
|
||||
// Network.builder().type("TCP_SERVER").productKey(product.getProductKey())
|
||||
// .description("tcp_server").createDate(new Date()).createUser(appUnionId)
|
||||
//
|
||||
// .state(newConfig.getBool("enabled")).configuration(tcpConfig.toString()).build());
|
||||
// } else {
|
||||
// // 更新
|
||||
// network.setConfiguration(tcpConfig.toString());
|
||||
// network.setState(newConfig.getBool("enabled"));
|
||||
// updateCount = networkMapper.updateByPrimaryKey(network);
|
||||
// }
|
||||
// 更改配置后重启tcp监听
|
||||
// if (updateCount > 0) {
|
||||
// 暂时不重启
|
||||
// flushNettyServer(JSONUtil.toJsonStr(tcpConfig), product.getProductKey(),
|
||||
// TcpFlushType.reload);
|
||||
// }
|
||||
} catch (Exception e) {
|
||||
log.error("tcp网络组件配置失败", e);
|
||||
throw new IoTException("添加失败");
|
||||
}
|
||||
}
|
||||
// 获取旧的产品配置信息
|
||||
JSONObject oldConfig = JSONUtil.parseObj(product.getConfiguration());
|
||||
@@ -703,7 +653,7 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc
|
||||
"更新产品={},更新状态={},更新人={}",
|
||||
devProduct.getProductKey() + devProduct.getName(),
|
||||
updateCount,
|
||||
appUnionId);
|
||||
iotUser.getUnionId());
|
||||
}
|
||||
|
||||
return updateCount;
|
||||
@@ -1139,6 +1089,7 @@ public class IoTProductServiceImpl extends BaseServiceImpl implements IIoTProduc
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(cacheNames = {"iot_product_log_store_policy"},allEntries = true)
|
||||
public int updateDevProductStoreConfig(IoTProductVO storeConfig) {
|
||||
// 获取产品信息
|
||||
IoTProduct product = ioTProductMapper.selectDevProductById(String.valueOf(storeConfig.getId()));
|
||||
|
||||
+27
-12
@@ -25,7 +25,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 网关轮询配置控制器
|
||||
*
|
||||
*
|
||||
* @author Aleo
|
||||
* @date 2025-10-26
|
||||
*/
|
||||
@@ -34,9 +34,12 @@ 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')")
|
||||
@@ -44,33 +47,45 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
+51
-11
@@ -26,6 +26,7 @@ import cn.universal.common.exception.CodecException;
|
||||
import cn.universal.common.exception.IoTException;
|
||||
import cn.universal.ossm.service.ISysOssService;
|
||||
import cn.universal.persistence.entity.IoTDeviceProtocol;
|
||||
import cn.universal.persistence.entity.IoTProduct;
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.persistence.entity.bo.IoTDeviceProtocolBO;
|
||||
import cn.universal.persistence.entity.vo.IoTProtocolVO;
|
||||
@@ -46,6 +47,7 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
@@ -57,11 +59,16 @@ 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());
|
||||
@@ -71,7 +78,9 @@ public class IoTDeviceProtocolController extends BaseController {
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/** 查询设备协议列表 */
|
||||
/**
|
||||
* 查询设备协议列表
|
||||
*/
|
||||
@GetMapping("/list/ids")
|
||||
public AjaxResult<List<String>> idList(IoTDeviceProtocol ioTDeviceProtocol) {
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
@@ -81,7 +90,9 @@ public class IoTDeviceProtocolController extends BaseController {
|
||||
return AjaxResult.success(list1);
|
||||
}
|
||||
|
||||
/** 导出设备协议列表 */
|
||||
/**
|
||||
* 导出设备协议列表
|
||||
*/
|
||||
@PostMapping("/export")
|
||||
@Log(title = "导出设备协议列表", businessType = BusinessType.EXPORT)
|
||||
public void export(HttpServletResponse response, IoTDeviceProtocol ioTDeviceProtocol) {
|
||||
@@ -92,7 +103,9 @@ public class IoTDeviceProtocolController extends BaseController {
|
||||
util.exportExcel(response, list, "设备协议数据");
|
||||
}
|
||||
|
||||
/** 获取设备协议详细信息 */
|
||||
/**
|
||||
* 获取设备协议详细信息
|
||||
*/
|
||||
@GetMapping(value = "/{id}")
|
||||
public AjaxResult getInfo(@PathVariable("id") String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
@@ -115,7 +128,9 @@ public class IoTDeviceProtocolController extends BaseController {
|
||||
return AjaxResult.success(devProductVO);
|
||||
}
|
||||
|
||||
/** 新增设备协议 */
|
||||
/**
|
||||
* 新增设备协议
|
||||
*/
|
||||
@PostMapping
|
||||
@Log(title = "新增设备协议", businessType = BusinessType.INSERT)
|
||||
public AjaxResult add(@RequestBody IoTDeviceProtocolBO ioTDeviceProtocolBO) {
|
||||
@@ -144,7 +159,9 @@ public class IoTDeviceProtocolController extends BaseController {
|
||||
return toAjax(devProtocolService.insertDevProtocol(ioTDeviceProtocol));
|
||||
}
|
||||
|
||||
/** 修改设备协议 */
|
||||
/**
|
||||
* 修改设备协议
|
||||
*/
|
||||
@PutMapping
|
||||
@Log(title = "修改设备协议", businessType = BusinessType.UPDATE)
|
||||
public AjaxResult edit(@RequestBody IoTDeviceProtocolBO ioTDeviceProtocolBO) {
|
||||
@@ -152,7 +169,9 @@ public class IoTDeviceProtocolController extends BaseController {
|
||||
return toAjax(devProtocolService.updateDevProtocol(ioTDeviceProtocolBO, iotUser));
|
||||
}
|
||||
|
||||
/** 删除设备协议 */
|
||||
/**
|
||||
* 删除设备协议
|
||||
*/
|
||||
@DeleteMapping("/{ids}")
|
||||
@Log(title = "删除设备协议", businessType = BusinessType.DELETE)
|
||||
public AjaxResult remove(@PathVariable String[] ids) {
|
||||
@@ -165,7 +184,9 @@ public class IoTDeviceProtocolController extends BaseController {
|
||||
return toAjax(devProtocolService.deleteDevProtocolByIds(ids));
|
||||
}
|
||||
|
||||
/** 编解码测试 */
|
||||
/**
|
||||
* 编解码测试
|
||||
*/
|
||||
@PostMapping("/codec")
|
||||
public AjaxResult codec(@RequestBody IoTDeviceProtocolBO ioTDeviceProtocolBO) {
|
||||
|
||||
@@ -183,4 +204,23 @@ public class IoTDeviceProtocolController extends BaseController {
|
||||
return AjaxResult.success(e.getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询未创建协议的产品列表(归属人为当前用户)
|
||||
*
|
||||
* @param searchKey 搜索关键词(产品名称或productKey,可选)
|
||||
* @return 产品列表
|
||||
*/
|
||||
@GetMapping("/products/without-protocol")
|
||||
public AjaxResult<List<IoTProduct>> getProductsWithoutProtocol(
|
||||
@RequestParam(value = "searchKey", required = false) String searchKey) {
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
// 获取当前用户的unionId,如果是管理员则使用parentUnionId
|
||||
String unionId = iotUser.isAdmin() ? iotUser.getParentUnionId() : iotUser.getUnionId();
|
||||
if (unionId == null) {
|
||||
unionId = iotUser.getUnionId();
|
||||
}
|
||||
List<IoTProduct> list = devProtocolService.selectProductsWithoutProtocol(unionId, searchKey);
|
||||
return AjaxResult.success(list);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-11
@@ -45,6 +45,7 @@ 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;
|
||||
@@ -572,9 +573,8 @@ 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,9 +629,8 @@ public class IoTProductController extends BaseController {
|
||||
|
||||
/**
|
||||
* 批量导入产品
|
||||
*
|
||||
* @deprecated 请使用 ProductImportExportController.importProductPackages() 代替 该接口仅保留用于向后兼容,建议使用
|
||||
* /admin/v1/product/transfer/import
|
||||
* @deprecated 请使用 ProductImportExportController.importProductPackages() 代替
|
||||
* 该接口仅保留用于向后兼容,建议使用 /admin/v1/product/transfer/import
|
||||
*/
|
||||
@Deprecated
|
||||
@PostMapping("/import")
|
||||
@@ -646,8 +645,7 @@ 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()) {
|
||||
// 使用新的导入方法
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
+1
-5
@@ -168,12 +168,8 @@ public class IoTUserController extends BaseController {
|
||||
userbo.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
|
||||
// userbo.setPassword(user.getPassword());
|
||||
userbo.setUnionId(userbo.getUsername());
|
||||
userbo.setRegisterFrom("admin");
|
||||
userbo.setDeleted(0);
|
||||
if (parentUser.isAdmin()) {
|
||||
userbo.setLicense(5000);
|
||||
userbo.setLicenseTotalAmount(5000);
|
||||
}
|
||||
|
||||
return toAjax(ioTUserService.insertUser(userbo));
|
||||
}
|
||||
|
||||
|
||||
+19
-10
@@ -36,8 +36,9 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 产品导入导出控制器 专门处理产品的完整导出导入功能
|
||||
*
|
||||
* 产品导入导出控制器
|
||||
* 专门处理产品的完整导出导入功能
|
||||
*
|
||||
* @author NexIoT
|
||||
* @since 2025-10-27
|
||||
*/
|
||||
@@ -49,7 +50,11 @@ public class ProductImportExportController extends BaseController {
|
||||
|
||||
@Autowired private IIoTProductService devProductService;
|
||||
|
||||
/** 导出产品完整包 包含产品基本信息、设备协议、网络配置、物模型等所有配置 导出后可直接在其他环境导入使用,无需额外配置 */
|
||||
/**
|
||||
* 导出产品完整包
|
||||
* 包含产品基本信息、设备协议、网络配置、物模型等所有配置
|
||||
* 导出后可直接在其他环境导入使用,无需额外配置
|
||||
*/
|
||||
@PostMapping("/export")
|
||||
@Operation(summary = "导出产品完整包", description = "导出产品及其关联的协议、网络配置、物模型等完整信息")
|
||||
@Log(title = "产品完整包导出", businessType = BusinessType.EXPORT)
|
||||
@@ -57,7 +62,7 @@ public class ProductImportExportController extends BaseController {
|
||||
try {
|
||||
// 获取当前用户信息
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
|
||||
// 权限控制:非管理员只能导出自己创建的产品
|
||||
query.setSelf(!iotUser.isAdmin());
|
||||
if (query.isSelf()) {
|
||||
@@ -73,11 +78,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");
|
||||
@@ -95,7 +100,7 @@ public class ProductImportExportController extends BaseController {
|
||||
out.write(bytes);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
|
||||
log.info("产品导出成功: 用户={}, 导出数量={}", iotUser.getUnionId(), packageList.size());
|
||||
} catch (Exception e) {
|
||||
log.error("产品导出失败", e);
|
||||
@@ -103,7 +108,11 @@ public class ProductImportExportController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
/** 导入产品完整包 支持导入完整的产品配置,包括协议、网络、物模型等 自动处理ProductKey冲突、协议依赖等问题 */
|
||||
/**
|
||||
* 导入产品完整包
|
||||
* 支持导入完整的产品配置,包括协议、网络、物模型等
|
||||
* 自动处理ProductKey冲突、协议依赖等问题
|
||||
*/
|
||||
@PostMapping("/import")
|
||||
@Operation(summary = "导入产品完整包", description = "导入产品完整配置包,自动处理所有依赖关系")
|
||||
@Log(title = "产品完整包导入", businessType = BusinessType.IMPORT)
|
||||
@@ -112,7 +121,7 @@ public class ProductImportExportController extends BaseController {
|
||||
try {
|
||||
// 获取当前用户ID
|
||||
String unionId = loginIoTUnionUser(SecurityUtils.getUnionId()).getUnionId();
|
||||
|
||||
|
||||
// 读取上传的JSON文件
|
||||
String jsonContent = new String(file.getBytes(), "UTF-8");
|
||||
|
||||
@@ -126,7 +135,7 @@ public class ProductImportExportController extends BaseController {
|
||||
|
||||
// 执行导入
|
||||
String result = devProductService.importProductPackages(packages, unionId);
|
||||
|
||||
|
||||
log.info("产品导入完成: 用户={}, 导入数量={}", unionId, packages.size());
|
||||
return AjaxResult.success(result);
|
||||
} catch (Exception e) {
|
||||
|
||||
+59
-2
@@ -6,12 +6,14 @@ import cn.universal.manager.notice.dto.NoticeChannelDTO;
|
||||
import cn.universal.manager.notice.model.NoticeChannel;
|
||||
import cn.universal.manager.notice.service.NoticeChannelService;
|
||||
import cn.universal.manager.notice.util.JsonDesensitizationUtil;
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.persistence.page.TableDataInfo;
|
||||
import cn.universal.security.BaseController;
|
||||
import cn.universal.security.utils.SecurityUtils;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -40,8 +42,18 @@ public class NoticeChannelController extends BaseController {
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) String name,
|
||||
@RequestParam(required = false) String channelType) {
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
|
||||
startPage();
|
||||
List<NoticeChannel> list = noticeChannelService.search(name, channelType, null);
|
||||
|
||||
// 如果不是管理员,只显示该用户创建的渠道
|
||||
if (!iotUser.isAdmin()) {
|
||||
list = list.stream()
|
||||
.filter(channel -> currentUser.equals(channel.getCreator()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<NoticeChannelDTO> dtoList =
|
||||
list.stream()
|
||||
@@ -82,6 +94,15 @@ public class NoticeChannelController extends BaseController {
|
||||
if (channel == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 如果不是管理员且不是创建者,无权限访问
|
||||
if (!iotUser.isAdmin() && !currentUser.equals(channel.getCreator())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
NoticeChannelDTO dto = new NoticeChannelDTO();
|
||||
dto.setId(channel.getId());
|
||||
dto.setName(channel.getName());
|
||||
@@ -107,14 +128,37 @@ public class NoticeChannelController extends BaseController {
|
||||
@PostMapping("/save")
|
||||
public Map<String, Object> save(@RequestBody NoticeChannelDTO channelDTO) {
|
||||
try {
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
NoticeChannel existingChannel = null;
|
||||
// 如果是修改操作,检查权限
|
||||
if (channelDTO.getId() != null) {
|
||||
existingChannel = noticeChannelService.getById(channelDTO.getId());
|
||||
if (existingChannel == null) {
|
||||
return Map.of("code", 1, "msg", "渠道不存在");
|
||||
}
|
||||
// 如果不是管理员且不是创建者,无权限修改
|
||||
if (!iotUser.isAdmin() && !currentUser.equals(existingChannel.getCreator())) {
|
||||
return Map.of("code", 1, "msg", "无权限修改该渠道");
|
||||
}
|
||||
}
|
||||
|
||||
NoticeChannel channel = new NoticeChannel();
|
||||
channel.setId(channelDTO.getId());
|
||||
channel.setName(channelDTO.getName());
|
||||
channel.setChannelType(channelDTO.getChannelType());
|
||||
channel.setStatus(channelDTO.getStatus());
|
||||
channel.setRemark(channelDTO.getRemark());
|
||||
channel.setCreator(SecurityUtils.getUnionId());
|
||||
channel.setCreateTime(channelDTO.getCreateTime());
|
||||
|
||||
// 如果是新增,设置创建者;如果是修改,保持原有创建者
|
||||
if (channelDTO.getId() == null) {
|
||||
channel.setCreator(currentUser);
|
||||
} else if (existingChannel != null) {
|
||||
channel.setCreator(existingChannel.getCreator());
|
||||
channel.setCreateTime(existingChannel.getCreateTime());
|
||||
}
|
||||
|
||||
channel.setUpdateTime(channelDTO.getUpdateTime());
|
||||
// 将config对象转换为JSON字符串
|
||||
if (channelDTO.getConfig() != null) {
|
||||
@@ -135,6 +179,19 @@ public class NoticeChannelController extends BaseController {
|
||||
public Map<String, Object> delete(@RequestBody List<Long> ids) {
|
||||
try {
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 如果不是管理员,检查是否有权限删除
|
||||
if (!iotUser.isAdmin()) {
|
||||
for (Long id : ids) {
|
||||
NoticeChannel channel = noticeChannelService.getById(id);
|
||||
if (channel != null && !currentUser.equals(channel.getCreator())) {
|
||||
return Map.of("code", 1, "msg", "无权限删除该渠道");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
noticeChannelService.deleteBatch(ids);
|
||||
return Map.of("code", 0, "msg", "删除成功");
|
||||
} else {
|
||||
|
||||
+95
-30
@@ -1,9 +1,11 @@
|
||||
package cn.universal.admin.system.notice;
|
||||
|
||||
import cn.universal.admin.system.service.ISysNoticeService;
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.persistence.entity.admin.SysNotice;
|
||||
import cn.universal.persistence.page.TableDataInfo;
|
||||
import cn.universal.security.BaseController;
|
||||
import cn.universal.security.utils.SecurityUtils;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
@@ -17,45 +19,108 @@ 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;
|
||||
|
||||
@Autowired private ISysNoticeService noticeService;
|
||||
/**
|
||||
* 查询系统通知列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<SysNotice> list(SysNotice notice) {
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
|
||||
// 如果不是管理员,只显示该用户创建的通知
|
||||
if (!iotUser.isAdmin()) {
|
||||
notice.setCreateBy(currentUser);
|
||||
}
|
||||
|
||||
startPage();
|
||||
List<SysNotice> list = noticeService.selectNoticeList(notice);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/** 查询系统通知列表 */
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo<SysNotice> list(SysNotice notice) {
|
||||
startPage();
|
||||
List<SysNotice> list = noticeService.selectNoticeList(notice);
|
||||
return getDataTable(list);
|
||||
}
|
||||
/**
|
||||
* 获取系统通知详细信息
|
||||
*/
|
||||
@GetMapping(value = "/{noticeId}")
|
||||
public SysNotice getInfo(@PathVariable("noticeId") Long noticeId) {
|
||||
SysNotice notice = noticeService.selectNoticeById(noticeId);
|
||||
if (notice == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 如果不是管理员且不是创建者,无权限访问
|
||||
if (!iotUser.isAdmin() && !currentUser.equals(notice.getCreateBy())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return notice;
|
||||
}
|
||||
|
||||
/** 获取系统通知详细信息 */
|
||||
@GetMapping(value = "/{noticeId}")
|
||||
public SysNotice getInfo(@PathVariable("noticeId") Long noticeId) {
|
||||
return noticeService.selectNoticeById(noticeId);
|
||||
}
|
||||
/**
|
||||
* 新增系统通知
|
||||
*/
|
||||
@PostMapping
|
||||
public void add(@RequestBody SysNotice notice) {
|
||||
// 设置创建者
|
||||
notice.setCreateBy(SecurityUtils.getUnionId());
|
||||
noticeService.insertNotice(notice);
|
||||
}
|
||||
|
||||
/** 新增系统通知 */
|
||||
@PostMapping
|
||||
public void add(@RequestBody SysNotice notice) {
|
||||
noticeService.insertNotice(notice);
|
||||
}
|
||||
/**
|
||||
* 修改系统通知
|
||||
*/
|
||||
@PutMapping
|
||||
public void edit(@RequestBody SysNotice notice) {
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 检查权限
|
||||
SysNotice existingNotice = noticeService.selectNoticeById(notice.getNoticeId());
|
||||
if (existingNotice == null) {
|
||||
throw new RuntimeException("通知不存在");
|
||||
}
|
||||
|
||||
// 如果不是管理员且不是创建者,无权限修改
|
||||
if (!iotUser.isAdmin() && !currentUser.equals(existingNotice.getCreateBy())) {
|
||||
throw new RuntimeException("无权限修改该通知");
|
||||
}
|
||||
|
||||
// 设置更新者
|
||||
notice.setUpdateBy(currentUser);
|
||||
noticeService.updateNotice(notice);
|
||||
}
|
||||
|
||||
/** 修改系统通知 */
|
||||
@PutMapping
|
||||
public void edit(@RequestBody SysNotice notice) {
|
||||
noticeService.updateNotice(notice);
|
||||
}
|
||||
|
||||
/** 删除系统通知 */
|
||||
@DeleteMapping("/{noticeIds}")
|
||||
public void remove(@PathVariable Long[] noticeIds) {
|
||||
noticeService.deleteNoticeByIds(noticeIds);
|
||||
}
|
||||
/**
|
||||
* 删除系统通知
|
||||
*/
|
||||
@DeleteMapping("/{noticeIds}")
|
||||
public void remove(@PathVariable Long[] noticeIds) {
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 如果不是管理员,检查是否有权限删除
|
||||
if (!iotUser.isAdmin()) {
|
||||
for (Long noticeId : noticeIds) {
|
||||
SysNotice notice = noticeService.selectNoticeById(noticeId);
|
||||
if (notice != null && !currentUser.equals(notice.getCreateBy())) {
|
||||
throw new RuntimeException("无权限删除该通知");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
noticeService.deleteNoticeByIds(noticeIds);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -2,8 +2,10 @@ package cn.universal.admin.system.notice;
|
||||
|
||||
import cn.universal.manager.notice.model.NoticeSendRecord;
|
||||
import cn.universal.manager.notice.service.NoticeRecordService;
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.persistence.page.TableDataInfo;
|
||||
import cn.universal.security.BaseController;
|
||||
import cn.universal.security.utils.SecurityUtils;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -28,8 +30,15 @@ public class NoticeRecordController extends BaseController {
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String type,
|
||||
@RequestParam(required = false) String status) {
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
|
||||
// 如果不是管理员,只显示该用户创建的记录
|
||||
String creator = iotUser.isAdmin() ? null : currentUser;
|
||||
|
||||
startPage();
|
||||
List<NoticeSendRecord> list = recordService.search(keyword, type, status);
|
||||
List<NoticeSendRecord> list = recordService.search(keyword, type, status, creator);
|
||||
|
||||
return getDataTable(list);
|
||||
}
|
||||
}
|
||||
|
||||
+25
-2
@@ -1,7 +1,12 @@
|
||||
package cn.universal.admin.system.notice;
|
||||
|
||||
import cn.universal.manager.notice.model.NoticeSendRequest;
|
||||
import cn.universal.manager.notice.model.NoticeTemplate;
|
||||
import cn.universal.manager.notice.service.NoticeService;
|
||||
import cn.universal.manager.notice.service.NoticeTemplateService;
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.security.BaseController;
|
||||
import cn.universal.security.utils.SecurityUtils;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -12,13 +17,16 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
/** 通知发送 */
|
||||
@RestController
|
||||
@RequestMapping("/admin/v1/notice")
|
||||
public class NoticeSendController {
|
||||
public class NoticeSendController extends BaseController {
|
||||
|
||||
private final NoticeService noticeService;
|
||||
private final NoticeTemplateService templateService;
|
||||
|
||||
@Autowired
|
||||
public NoticeSendController(NoticeService noticeService) {
|
||||
public NoticeSendController(
|
||||
NoticeService noticeService, NoticeTemplateService templateService) {
|
||||
this.noticeService = noticeService;
|
||||
this.templateService = templateService;
|
||||
}
|
||||
|
||||
@PostMapping("/send")
|
||||
@@ -31,9 +39,24 @@ public class NoticeSendController {
|
||||
throw new IllegalArgumentException("模板ID不能为空");
|
||||
}
|
||||
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 检查模板是否存在及权限
|
||||
NoticeTemplate template = templateService.getById(templateId);
|
||||
if (template == null) {
|
||||
throw new IllegalArgumentException("模板不存在");
|
||||
}
|
||||
|
||||
// 如果不是管理员且不是创建者,无权限发送
|
||||
if (!iotUser.isAdmin() && !currentUser.equals(template.getCreator())) {
|
||||
throw new IllegalArgumentException("无权限使用该模板发送通知");
|
||||
}
|
||||
|
||||
NoticeSendRequest req = new NoticeSendRequest();
|
||||
req.setTemplateId(templateId);
|
||||
req.setReceivers(receivers);
|
||||
req.setCreator(currentUser); // 设置创建者为当前用户
|
||||
if (params instanceof Map) {
|
||||
req.setParams((Map<String, Object>) params);
|
||||
}
|
||||
|
||||
+102
-8
@@ -3,12 +3,14 @@ package cn.universal.admin.system.notice;
|
||||
import cn.universal.manager.notice.dto.NoticeTemplateDTO;
|
||||
import cn.universal.manager.notice.model.NoticeTemplate;
|
||||
import cn.universal.manager.notice.service.NoticeTemplateService;
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.persistence.page.TableDataInfo;
|
||||
import cn.universal.persistence.query.AjaxResult;
|
||||
import cn.universal.security.BaseController;
|
||||
import cn.universal.security.utils.SecurityUtils;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -34,8 +36,18 @@ public class NoticeTemplateController extends BaseController {
|
||||
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||
@RequestParam(required = false) String name,
|
||||
@RequestParam(required = false) String channelType) {
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
|
||||
startPage();
|
||||
List<NoticeTemplate> list = noticeTemplateService.search(name, channelType, null);
|
||||
|
||||
// 如果不是管理员,只显示该用户创建的模板
|
||||
if (!iotUser.isAdmin()) {
|
||||
list = list.stream()
|
||||
.filter(template -> currentUser.equals(template.getCreator()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<NoticeTemplateDTO> dtoList =
|
||||
list.stream()
|
||||
@@ -63,7 +75,22 @@ public class NoticeTemplateController extends BaseController {
|
||||
@PostMapping("/save")
|
||||
public AjaxResult save(@RequestBody NoticeTemplateDTO templateDTO) {
|
||||
try {
|
||||
noticeTemplateService.saveTemplate(templateDTO, SecurityUtils.getUnionId());
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 如果是修改操作,检查权限
|
||||
if (templateDTO.getId() != null) {
|
||||
NoticeTemplate existingTemplate = noticeTemplateService.getById(templateDTO.getId());
|
||||
if (existingTemplate == null) {
|
||||
return AjaxResult.error("模板不存在");
|
||||
}
|
||||
// 如果不是管理员且不是创建者,无权限修改
|
||||
if (!iotUser.isAdmin() && !currentUser.equals(existingTemplate.getCreator())) {
|
||||
return AjaxResult.error("无权限修改该模板");
|
||||
}
|
||||
}
|
||||
|
||||
noticeTemplateService.saveTemplate(templateDTO, currentUser);
|
||||
return AjaxResult.success("操作成功");
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("操作失败: " + e.getMessage());
|
||||
@@ -74,6 +101,19 @@ public class NoticeTemplateController extends BaseController {
|
||||
public AjaxResult delete(@RequestBody List<Long> ids) {
|
||||
try {
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 如果不是管理员,检查是否有权限删除
|
||||
if (!iotUser.isAdmin()) {
|
||||
for (Long id : ids) {
|
||||
NoticeTemplate template = noticeTemplateService.getById(id);
|
||||
if (template != null && !currentUser.equals(template.getCreator())) {
|
||||
return AjaxResult.error("无权限删除该模板");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
noticeTemplateService.deleteBatch(ids);
|
||||
return AjaxResult.success("删除成功");
|
||||
} else {
|
||||
@@ -89,6 +129,19 @@ public class NoticeTemplateController extends BaseController {
|
||||
public AjaxResult enable(@RequestBody List<Long> ids) {
|
||||
try {
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 如果不是管理员,检查是否有权限启用
|
||||
if (!iotUser.isAdmin()) {
|
||||
for (Long id : ids) {
|
||||
NoticeTemplate template = noticeTemplateService.getById(id);
|
||||
if (template != null && !currentUser.equals(template.getCreator())) {
|
||||
return AjaxResult.error("无权限启用该模板");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
noticeTemplateService.enableBatch(ids);
|
||||
return AjaxResult.success("启用成功");
|
||||
} else {
|
||||
@@ -104,6 +157,19 @@ public class NoticeTemplateController extends BaseController {
|
||||
public AjaxResult disable(@RequestBody List<Long> ids) {
|
||||
try {
|
||||
if (ids != null && !ids.isEmpty()) {
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 如果不是管理员,检查是否有权限停用
|
||||
if (!iotUser.isAdmin()) {
|
||||
for (Long id : ids) {
|
||||
NoticeTemplate template = noticeTemplateService.getById(id);
|
||||
if (template != null && !currentUser.equals(template.getCreator())) {
|
||||
return AjaxResult.error("无权限停用该模板");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
noticeTemplateService.disableBatch(ids);
|
||||
return AjaxResult.success("停用成功");
|
||||
} else {
|
||||
@@ -119,8 +185,17 @@ public class NoticeTemplateController extends BaseController {
|
||||
try {
|
||||
NoticeTemplate template = noticeTemplateService.getById(id);
|
||||
if (template == null) {
|
||||
return null;
|
||||
return AjaxResult.error("模板不存在");
|
||||
}
|
||||
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 如果不是管理员且不是创建者,无权限访问
|
||||
if (!iotUser.isAdmin() && !currentUser.equals(template.getCreator())) {
|
||||
return AjaxResult.error("无权限访问该模板");
|
||||
}
|
||||
|
||||
NoticeTemplateDTO dto = new NoticeTemplateDTO();
|
||||
dto.setId(template.getId());
|
||||
dto.setName(template.getName());
|
||||
@@ -135,16 +210,35 @@ public class NoticeTemplateController extends BaseController {
|
||||
dto.setUpdateTime(template.getUpdateTime());
|
||||
return AjaxResult.success(dto);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
return AjaxResult.error("查询失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/test")
|
||||
public AjaxResult test(@RequestBody Map<String, Object> request) {
|
||||
Long templateId = Long.valueOf(request.get("templateId").toString());
|
||||
String receivers = (String) request.get("receivers");
|
||||
Object params = request.get("params");
|
||||
noticeTemplateService.testTemplate(templateId, receivers, params);
|
||||
return AjaxResult.success("成功");
|
||||
try {
|
||||
Long templateId = Long.valueOf(request.get("templateId").toString());
|
||||
String receivers = (String) request.get("receivers");
|
||||
Object params = request.get("params");
|
||||
|
||||
String currentUser = SecurityUtils.getUnionId();
|
||||
IoTUser iotUser = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
|
||||
// 检查模板是否存在及权限
|
||||
NoticeTemplate template = noticeTemplateService.getById(templateId);
|
||||
if (template == null) {
|
||||
return AjaxResult.error("模板不存在");
|
||||
}
|
||||
|
||||
// 如果不是管理员且不是创建者,无权限测试
|
||||
if (!iotUser.isAdmin() && !currentUser.equals(template.getCreator())) {
|
||||
return AjaxResult.error("无权限测试该模板");
|
||||
}
|
||||
|
||||
noticeTemplateService.testTemplate(templateId, receivers, params);
|
||||
return AjaxResult.success("成功");
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("测试失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -80,6 +80,7 @@ public interface ISysDictDataService {
|
||||
*/
|
||||
Map<String, Object> selectDictDataByTypes(String[] dictType);
|
||||
|
||||
|
||||
/**
|
||||
* 根据label标签,查找白名单
|
||||
*
|
||||
|
||||
+43
-43
@@ -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);
|
||||
}
|
||||
|
||||
+10
@@ -14,6 +14,7 @@ package cn.universal.admin.system.service;
|
||||
|
||||
import cn.universal.admin.common.service.BaseService;
|
||||
import cn.universal.persistence.entity.IoTDeviceProtocol;
|
||||
import cn.universal.persistence.entity.IoTProduct;
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.persistence.entity.bo.IoTDeviceProtocolBO;
|
||||
import java.util.List;
|
||||
@@ -92,4 +93,13 @@ public interface IoTDeviceProtocolService extends BaseService {
|
||||
* @return 结果
|
||||
*/
|
||||
public int deleteDevProtocolById(String id);
|
||||
|
||||
/**
|
||||
* 查询未创建协议的产品列表(归属人为当前用户)
|
||||
*
|
||||
* @param unionId 用户unionId
|
||||
* @param searchKey 搜索关键词(产品名称或productKey)
|
||||
* @return 产品列表
|
||||
*/
|
||||
List<IoTProduct> selectProductsWithoutProtocol(String unionId, String searchKey);
|
||||
}
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ public class IoTDeviceMessageCodecService {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Object result = codecService.preDecode(productKey, JSONUtil.toJsonStr(payload));
|
||||
Object result = codecService.preDecode(productKey, JSONUtil.toJsonStr(payload), null);
|
||||
log.info("产品编号={} 原始报文={} , preDecode解码={}", productKey, payload, JSONUtil.toJsonStr(result));
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
|
||||
+13
@@ -32,6 +32,7 @@ import cn.universal.core.protocol.magic.ProtocolCodecMagic;
|
||||
import cn.universal.dm.device.service.impl.IoTCacheRemoveService;
|
||||
import cn.universal.ossm.service.ISysOssService;
|
||||
import cn.universal.persistence.entity.IoTDeviceProtocol;
|
||||
import cn.universal.persistence.entity.IoTProduct;
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.persistence.entity.bo.IoTDeviceProtocolBO;
|
||||
import cn.universal.persistence.mapper.IoTDeviceProtocolMapper;
|
||||
@@ -280,4 +281,16 @@ public class IoTDeviceProtocolServiceImpl extends BaseServiceImpl
|
||||
public int deleteDevProtocolById(String id) {
|
||||
return ioTDeviceProtocolMapper.deleteDevProtocolById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询未创建协议的产品列表(归属人为当前用户)
|
||||
*
|
||||
* @param unionId 用户unionId
|
||||
* @param searchKey 搜索关键词(产品名称或productKey)
|
||||
* @return 产品列表
|
||||
*/
|
||||
@Override
|
||||
public List<IoTProduct> selectProductsWithoutProtocol(String unionId, String searchKey) {
|
||||
return ioTDeviceProtocolMapper.selectProductsWithoutProtocol(unionId, searchKey);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -270,9 +270,11 @@ public class IoTUserApplicationServiceImpl extends BaseServiceImpl
|
||||
if (isEnable) {
|
||||
// HTTP启用:设置enable和support为true
|
||||
JSONObject httpConfig = pushCfg.getJSONObject("http");
|
||||
httpConfig.set("enable", true);
|
||||
httpConfig.set("support", true);
|
||||
pushCfg.set("http", httpConfig);
|
||||
if(httpConfig!=null){
|
||||
httpConfig.set("enable", true);
|
||||
httpConfig.set("support", true);
|
||||
pushCfg.set("http", httpConfig);
|
||||
}
|
||||
} else {
|
||||
// HTTP禁用:设置enable为false
|
||||
if (pushCfg.containsKey("http")) {
|
||||
|
||||
+6
-3
@@ -105,9 +105,10 @@ public class SysDictDataServiceImpl implements ISysDictDataService {
|
||||
"selectDictDataById",
|
||||
"selectDictLabel",
|
||||
"selectDictDataByTypes",
|
||||
"selectDictDataByType",
|
||||
"selectWhitelist",
|
||||
"selectDictTypeByType"
|
||||
})
|
||||
},allEntries = true)
|
||||
public void deleteDictDataByIds(Long[] dictCodes) {
|
||||
for (Long dictCode : dictCodes) {
|
||||
SysDictData data = selectDictDataById(dictCode);
|
||||
@@ -130,7 +131,7 @@ public class SysDictDataServiceImpl implements ISysDictDataService {
|
||||
"selectDictDataByTypes",
|
||||
"selectWhitelist",
|
||||
"selectDictTypeByType"
|
||||
})
|
||||
},allEntries = true)
|
||||
public int insertDictData(SysDictData data) {
|
||||
int row = sysDictDataMapper.insert(data);
|
||||
return row;
|
||||
@@ -148,9 +149,11 @@ public class SysDictDataServiceImpl implements ISysDictDataService {
|
||||
"selectDictDataById",
|
||||
"selectDictLabel",
|
||||
"selectDictDataByTypes",
|
||||
"selectDictDataByType",
|
||||
"selectWhitelist",
|
||||
"selectDictTypeByType"
|
||||
})
|
||||
},
|
||||
allEntries = true)
|
||||
public int updateDictData(SysDictData data) {
|
||||
int row = sysDictDataMapper.updateByPrimaryKey(data);
|
||||
return row;
|
||||
|
||||
+5
-5
@@ -142,7 +142,7 @@ public class SysDictTypeServiceImpl
|
||||
"selectDictTypeById",
|
||||
"selectDictTypeByType",
|
||||
"selectPageDictTypeList"
|
||||
})
|
||||
},allEntries = true)
|
||||
public void deleteDictTypeByIds(Long[] dictIds) {
|
||||
for (Long dictId : dictIds) {
|
||||
SysDictType dictType = selectDictTypeById(dictId);
|
||||
@@ -177,7 +177,7 @@ public class SysDictTypeServiceImpl
|
||||
"selectDictTypeById",
|
||||
"selectDictTypeByType",
|
||||
"selectPageDictTypeList"
|
||||
})
|
||||
},allEntries = true)
|
||||
public void clearDictCache() {}
|
||||
|
||||
/** 重置字典缓存数据 */
|
||||
@@ -190,7 +190,7 @@ public class SysDictTypeServiceImpl
|
||||
"selectDictTypeById",
|
||||
"selectDictTypeByType",
|
||||
"selectPageDictTypeList"
|
||||
})
|
||||
},allEntries = true)
|
||||
public void resetDictCache() {}
|
||||
|
||||
/**
|
||||
@@ -208,7 +208,7 @@ public class SysDictTypeServiceImpl
|
||||
"selectDictTypeById",
|
||||
"selectDictTypeByType",
|
||||
"selectPageDictTypeList"
|
||||
})
|
||||
},allEntries = true)
|
||||
public int insertDictType(SysDictType dict) {
|
||||
int row = dictTypeMapper.insert(dict);
|
||||
return row;
|
||||
@@ -230,7 +230,7 @@ public class SysDictTypeServiceImpl
|
||||
"selectDictTypeById",
|
||||
"selectDictTypeByType",
|
||||
"selectPageDictTypeList"
|
||||
})
|
||||
},allEntries = true)
|
||||
public int updateDictType(SysDictType dict) {
|
||||
SysDictType oldDict = dictTypeMapper.selectByPrimaryKey(dict.getDictId());
|
||||
Example ex = new Example(SysDictData.class);
|
||||
|
||||
+1
-2
@@ -133,8 +133,7 @@ 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());
|
||||
|
||||
+1
@@ -87,6 +87,7 @@ public class SysDictDataController extends BaseController {
|
||||
return toAjax(dictDataService.insertDictData(dict));
|
||||
}
|
||||
|
||||
|
||||
/** 修改保存字典类型 */
|
||||
@PutMapping
|
||||
public AjaxResult edit(@Validated @RequestBody SysDictData dict) {
|
||||
|
||||
+3
@@ -84,6 +84,9 @@ public class SystemProtocolController extends BaseController {
|
||||
details.put("category", protocol.getCategory().name());
|
||||
details.put("available", true); // 运行时注册表中的都是可用的
|
||||
details.put("status", "已启用");
|
||||
// 添加是否支持手动创建产品的信息
|
||||
details.put("isManualCreatable", protocol.isManualCreatable());
|
||||
details.put("notCreatableReason", protocol.getNotCreatableReason());
|
||||
runtimeProtocols.put(protocol.getCode(), details);
|
||||
});
|
||||
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 视频设备导入服务
|
||||
* @Author: gitee.com/NexIoT
|
||||
*
|
||||
*/
|
||||
package cn.universal.admin.video.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 视频设备导入服务
|
||||
* 从三方平台拉取设备后,勾选使用时落库到IoTDevice
|
||||
*/
|
||||
public interface VideoDeviceImportService {
|
||||
|
||||
/**
|
||||
* 批量导入设备(勾选使用流程)
|
||||
* @param instanceKey 平台实例Key
|
||||
* @param devices 设备列表(从适配器返回的原始设备数据)
|
||||
* @param productKey 指定产品Key(可选,为空则自动创建/选择平台产品)
|
||||
* @return 导入结果 {success: 成功数, failed: 失败数, exists: 已存在数}
|
||||
*/
|
||||
Map<String, Integer> importDevices(
|
||||
String instanceKey, List<Map<String, Object>> devices, String productKey);
|
||||
|
||||
/**
|
||||
* 单个设备导入
|
||||
* @param instanceKey 平台实例Key
|
||||
* @param device 设备数据
|
||||
* @param productKey 指定产品Key
|
||||
* @return 导入成功的设备ID
|
||||
*/
|
||||
String importDevice(String instanceKey, Map<String, Object> device, String productKey);
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 视频平台实例服务接口
|
||||
* @Author: gitee.com/NexIoT
|
||||
*
|
||||
*/
|
||||
package cn.universal.admin.video.service;
|
||||
|
||||
import cn.universal.persistence.entity.VideoPlatformInstance;
|
||||
import cn.universal.persistence.entity.VideoPlatformDevice;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface VideoPlatformInstanceService {
|
||||
|
||||
/**
|
||||
* 查询所有平台实例
|
||||
*/
|
||||
List<VideoPlatformInstance> listAll();
|
||||
|
||||
/**
|
||||
* 根据平台类型查询
|
||||
*/
|
||||
List<VideoPlatformInstance> listByType(String platformType);
|
||||
|
||||
/**
|
||||
* 根据instanceKey查询
|
||||
*/
|
||||
VideoPlatformInstance getByInstanceKey(String instanceKey);
|
||||
|
||||
/** 根据ID查询 */
|
||||
VideoPlatformInstance getById(Long id);
|
||||
|
||||
/**
|
||||
* 创建平台实例
|
||||
*/
|
||||
VideoPlatformInstance create(VideoPlatformInstance instance);
|
||||
|
||||
/**
|
||||
* 更新平台实例
|
||||
*/
|
||||
VideoPlatformInstance update(VideoPlatformInstance instance);
|
||||
|
||||
/**
|
||||
* 删除平台实例
|
||||
*/
|
||||
void delete(Long id);
|
||||
void delete(Long id, boolean force);
|
||||
|
||||
/**
|
||||
* 测试连接(调用适配器真实测试)
|
||||
*/
|
||||
Map<String, Object> testConnection(String instanceKey);
|
||||
|
||||
/**
|
||||
* 实时拉取设备目录(调用适配器)
|
||||
*/
|
||||
List<Map<String, Object>> listDevices(String instanceKey, Map<String, Object> filters);
|
||||
|
||||
/**
|
||||
* 获取组织树(调用适配器,部分平台支持)
|
||||
*/
|
||||
List<Map<String, Object>> listOrganizations(String instanceKey, Map<String, Object> filters);
|
||||
|
||||
/**
|
||||
* 应用订阅(根据实例配置)
|
||||
*/
|
||||
void applySubscription(String instanceKey);
|
||||
|
||||
/**
|
||||
* 取消订阅(根据实例配置)
|
||||
*/
|
||||
void cancelSubscription(String instanceKey);
|
||||
|
||||
/**
|
||||
* 获取设备通道列表(从中间表)
|
||||
*/
|
||||
List<Map<String, Object>> listDeviceChannels(String instanceKey, String deviceId);
|
||||
|
||||
/**
|
||||
* 查询通道列表(从通道表)
|
||||
* @param instanceKey 平台实例Key
|
||||
* @param deviceId 设备ID(可选)
|
||||
* @param filters 筛选条件: keyword(通道名称), status(通道状态), ptzType(PTZ类型)
|
||||
*/
|
||||
List<Map<String, Object>> listChannels(String instanceKey, String deviceId, Map<String, Object> filters);
|
||||
|
||||
/**
|
||||
* 获取通道的流地址(预览流)
|
||||
*/
|
||||
Map<String, String> getStreamUrl(String instanceKey, String deviceId, String channelId, String streamType);
|
||||
|
||||
/**
|
||||
* 获取通道的回放流地址
|
||||
*/
|
||||
Map<String, Object> getPlaybackUrl(String instanceKey, String deviceId, String channelId, long startTime, long endTime);
|
||||
|
||||
/**
|
||||
* PTZ控制
|
||||
*/
|
||||
void controlPTZ(String instanceKey, String deviceId, String channelId, String command, int speed);
|
||||
|
||||
/**
|
||||
* 查询录像记录
|
||||
*/
|
||||
List<Map<String, Object>> queryRecords(String instanceKey, String deviceId, String channelId, long startTime, long endTime);
|
||||
|
||||
// ==================== 国标录像下载 ====================
|
||||
|
||||
/**
|
||||
* 开始国标录像下载
|
||||
*/
|
||||
cn.universal.common.domain.R<Map<String, Object>> startGBRecordDownload(String instanceKey, String deviceId, String channelId, Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* 停止国标录像下载
|
||||
*/
|
||||
cn.universal.common.domain.R<Void> stopGBRecordDownload(String instanceKey, String deviceId, String channelId, Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* 获取国标录像下载进度
|
||||
*/
|
||||
cn.universal.common.domain.R<Map<String, Object>> getGBRecordDownloadProgress(String instanceKey, String deviceId, String channelId, String stream);
|
||||
|
||||
// ==================== 云端录像 ====================
|
||||
|
||||
/**
|
||||
* 查询云端录像日期列表
|
||||
*/
|
||||
cn.universal.common.domain.R<List<String>> queryCloudRecordDates(String instanceKey, String deviceId, String channelId, Map<String, String> params);
|
||||
|
||||
/**
|
||||
* 查询云端录像列表
|
||||
*/
|
||||
cn.universal.common.domain.R<Map<String, Object>> queryCloudRecords(String instanceKey, String deviceId, String channelId, Map<String, String> params);
|
||||
|
||||
/**
|
||||
* 加载云端录像文件
|
||||
*/
|
||||
cn.universal.common.domain.R<Map<String, Object>> loadCloudRecord(String instanceKey, String deviceId, String channelId, Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* 云端录像定位
|
||||
*/
|
||||
cn.universal.common.domain.R<Void> seekCloudRecord(String instanceKey, String deviceId, String channelId, Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* 设置云端录像倍速
|
||||
*/
|
||||
cn.universal.common.domain.R<Void> setCloudRecordSpeed(String instanceKey, String deviceId, String channelId, Map<String, Object> data);
|
||||
|
||||
/**
|
||||
* 获取云端录像下载地址
|
||||
*/
|
||||
cn.universal.common.domain.R<Map<String, Object>> getCloudRecordPlayPath(String instanceKey, Integer recordId);
|
||||
|
||||
/**
|
||||
* 获取设备完整信息(跨主表和扩展表查询)
|
||||
* @param instanceKey 平台实例Key
|
||||
* @param deviceId 设备ID
|
||||
* @return 设备完整信息(主表+扩展表)
|
||||
*/
|
||||
Map<String, Object> getDeviceDetail(String instanceKey, String deviceId);
|
||||
|
||||
/**
|
||||
* 更新设备基本信息(remark/gps/org/enabled等)
|
||||
*/
|
||||
Map<String, Object> updateDevice(String instanceKey, String deviceId, Map<String, Object> updates);
|
||||
|
||||
/** 删除设备(级联删除扩展与通道) */
|
||||
void deleteDevice(String instanceKey, String deviceId);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 视频平台产品自动创建服务
|
||||
* @Author: gitee.com/NexIoT
|
||||
*
|
||||
*/
|
||||
package cn.universal.admin.video.service;
|
||||
|
||||
import cn.universal.persistence.entity.IoTProduct;
|
||||
import cn.universal.persistence.entity.VideoPlatformInstance;
|
||||
|
||||
/**
|
||||
* 视频平台产品创建策略服务
|
||||
*/
|
||||
public interface VideoPlatformProductService {
|
||||
|
||||
/**
|
||||
* 为WVP平台自动创建GB/级联两个产品(如果autoCreateProducts=1且尚未创建)
|
||||
* @param instance 平台实例
|
||||
*/
|
||||
void autoCreateWvpProducts(VideoPlatformInstance instance);
|
||||
|
||||
/**
|
||||
* 懒创建平台产品(HIK/ICC/WVP通用)
|
||||
* @param instance 平台实例
|
||||
* @param deviceNode 设备节点类型:VIDEO_GATEWAY/VIDEO_DEVICE等
|
||||
* @return 平台产品(用于后续设备落库时填充gwProductKey)
|
||||
*/
|
||||
IoTProduct ensurePlatformProduct(VideoPlatformInstance instance, String deviceNode);
|
||||
|
||||
/**
|
||||
* 根据instanceKey获取或创建平台产品
|
||||
* @param instanceKey 平台实例Key
|
||||
* @param deviceNode 设备节点类型
|
||||
* @return 平台产品
|
||||
*/
|
||||
IoTProduct getOrCreatePlatformProduct(String instanceKey, String deviceNode);
|
||||
}
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 视频设备导入服务实现
|
||||
* @Author: gitee.com/NexIoT
|
||||
*
|
||||
*/
|
||||
package cn.universal.admin.video.service.impl;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.universal.admin.video.service.VideoDeviceImportService;
|
||||
import cn.universal.admin.video.service.VideoPlatformProductService;
|
||||
import cn.universal.common.constant.IoTConstant;
|
||||
import cn.universal.common.exception.BaseException;
|
||||
import cn.universal.common.utils.StringUtils;
|
||||
import cn.universal.persistence.entity.IoTDevice;
|
||||
import cn.universal.persistence.entity.IoTProduct;
|
||||
import cn.universal.persistence.entity.VideoPlatformInstance;
|
||||
import cn.universal.persistence.mapper.IoTDeviceMapper;
|
||||
import cn.universal.persistence.mapper.IoTProductMapper;
|
||||
import cn.universal.persistence.mapper.VideoPlatformInstanceMapper;
|
||||
import cn.universal.security.utils.SecurityUtils;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import tk.mybatis.mapper.entity.Example;
|
||||
|
||||
/**
|
||||
* 视频设备导入服务
|
||||
* 负责将三方平台设备落库到IoTDevice
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class VideoDeviceImportServiceImpl implements VideoDeviceImportService {
|
||||
|
||||
private final VideoPlatformInstanceMapper instanceMapper;
|
||||
private final VideoPlatformProductService productService;
|
||||
private final IoTDeviceMapper deviceMapper;
|
||||
private final IoTProductMapper productMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Map<String, Integer> importDevices(
|
||||
String instanceKey, List<Map<String, Object>> devices, String productKey) {
|
||||
Map<String, Integer> result = new HashMap<>();
|
||||
int success = 0, failed = 0, exists = 0;
|
||||
|
||||
for (Map<String, Object> device : devices) {
|
||||
try {
|
||||
String deviceId = importDevice(instanceKey, device, productKey);
|
||||
if (deviceId != null) {
|
||||
if (deviceId.startsWith("EXISTS:")) {
|
||||
exists++;
|
||||
} else {
|
||||
success++;
|
||||
}
|
||||
} else {
|
||||
failed++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("导入设备失败:{}", e.getMessage(), e);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
result.put("success", success);
|
||||
result.put("failed", failed);
|
||||
result.put("exists", exists);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public String importDevice(String instanceKey, Map<String, Object> device, String productKey) {
|
||||
// 1. 获取平台实例
|
||||
Example example = new Example(VideoPlatformInstance.class);
|
||||
example.createCriteria().andEqualTo("instanceKey", instanceKey);
|
||||
VideoPlatformInstance instance =
|
||||
instanceMapper.selectByExample(example).stream().findFirst().orElse(null);
|
||||
if (instance == null) {
|
||||
throw new BaseException("平台实例不存在:" + instanceKey);
|
||||
}
|
||||
|
||||
// 2. 获取或创建平台产品
|
||||
String deviceId = (String) device.get("deviceId");
|
||||
if (StringUtils.isEmpty(deviceId)) {
|
||||
throw new BaseException("设备DeviceId不能为空");
|
||||
}
|
||||
|
||||
// 判断设备节点类型(优先从设备数据获取,否则默认VIDEO_DEVICE)
|
||||
String deviceNode = (String) device.getOrDefault("deviceNode", "VIDEO_DEVICE");
|
||||
|
||||
IoTProduct product;
|
||||
if (StringUtils.isEmpty(productKey)) {
|
||||
// 自动创建/选择平台产品
|
||||
product = productService.ensurePlatformProduct(instance, deviceNode);
|
||||
} else {
|
||||
// 使用指定产品
|
||||
product = productMapper.getProductByProductKey(productKey);
|
||||
if (product == null) {
|
||||
throw new BaseException("指定产品不存在:" + productKey);
|
||||
}
|
||||
}
|
||||
String deviceName = (String) device.getOrDefault("deviceName", "");
|
||||
String deviceIp = (String) device.getOrDefault("deviceIp", "");
|
||||
|
||||
// 3. 检查设备是否已存在(幂等性校验:productKey + deviceId)
|
||||
Example deviceExample = new Example(IoTDevice.class);
|
||||
deviceExample
|
||||
.createCriteria()
|
||||
.andEqualTo("productKey", product.getProductKey())
|
||||
.andEqualTo("deviceId", deviceId);
|
||||
IoTDevice existingDevice = deviceMapper.selectByExample(deviceExample).stream().findFirst().orElse(null);
|
||||
if (existingDevice != null) {
|
||||
log.info("设备已存在,跳过导入:{}/{}", product.getProductKey(), deviceId);
|
||||
return "EXISTS:" + existingDevice.getIotId();
|
||||
}
|
||||
|
||||
// 4. 创建IoTDevice
|
||||
IoTDevice iotDevice = new IoTDevice();
|
||||
iotDevice.setIotId(product.getProductKey()+deviceId);
|
||||
iotDevice.setProductKey(product.getProductKey());
|
||||
iotDevice.setDeviceId(deviceId);
|
||||
iotDevice.setDeviceName(StrUtil.isBlank(deviceName)?deviceIp:deviceName);
|
||||
iotDevice.setProductName(product.getName());
|
||||
iotDevice.setState(parseDeviceStatus(device));
|
||||
iotDevice.setCreatorId(SecurityUtils.getUnionId());
|
||||
iotDevice.setCreateTime(System.currentTimeMillis() / 1000);
|
||||
|
||||
// 填充网关产品Key(平台实例作为网关)
|
||||
iotDevice.setGwProductKey(product.getProductKey());
|
||||
|
||||
// 填充配置字段(包含通道列表等)
|
||||
iotDevice.setConfiguration(buildConfiguration(device));
|
||||
|
||||
// 填充派生元数据(媒体能力、通道信息等)
|
||||
iotDevice.setDeriveMetadata(buildDeriveMetadata(device));
|
||||
|
||||
// 填充事件元数据到ext1(遵循规范)
|
||||
iotDevice.setExt1(buildEventMetadata(device));
|
||||
|
||||
deviceMapper.insertSelective(iotDevice);
|
||||
log.info(
|
||||
"设备导入成功:{}/{} -> {}", product.getProductKey(), deviceId, iotDevice.getIotId());
|
||||
return iotDevice.getIotId();
|
||||
}
|
||||
/**
|
||||
* 解析设备状态
|
||||
*/
|
||||
private Boolean parseDeviceStatus(Map<String, Object> device) {
|
||||
Object status = device.get("status");
|
||||
if (status == null) return false;
|
||||
if (status instanceof Boolean) return (Boolean) status;
|
||||
String statusStr = status.toString().toLowerCase();
|
||||
return "online".equals(statusStr) || "1".equals(statusStr) || "true".equals(statusStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建配置字段(保存轻量配置,如通道列表)
|
||||
*/
|
||||
private String buildConfiguration(Map<String, Object> device) {
|
||||
try {
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
// 保存厂商、型号等基础信息
|
||||
if (device.containsKey("manufacturer")) {
|
||||
config.put("manufacturer", device.get("manufacturer"));
|
||||
}
|
||||
if (device.containsKey("model")) {
|
||||
config.put("model", device.get("model"));
|
||||
}
|
||||
// 保存通道列表(如果有)
|
||||
if (device.containsKey("channels")) {
|
||||
config.put("channelList", device.get("channels"));
|
||||
}
|
||||
if (device.containsKey("channelCount")) {
|
||||
config.put("channelCount", device.get("channelCount"));
|
||||
}
|
||||
return objectMapper.writeValueAsString(config);
|
||||
} catch (Exception e) {
|
||||
log.warn("构建配置失败:{}", e.getMessage());
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建派生元数据(媒体能力、编解码等)
|
||||
*/
|
||||
private String buildDeriveMetadata(Map<String, Object> device) {
|
||||
try {
|
||||
Map<String, Object> metadata = new HashMap<>();
|
||||
// 媒体能力(预览URL、回放能力等)
|
||||
if (device.containsKey("capabilities")) {
|
||||
metadata.put("capabilities", device.get("capabilities"));
|
||||
}
|
||||
// PTZ能力
|
||||
if (device.containsKey("ptzSupport")) {
|
||||
metadata.put("ptzSupport", device.get("ptzSupport"));
|
||||
}
|
||||
// 编解码信息
|
||||
if (device.containsKey("videoCodec")) {
|
||||
metadata.put("videoCodec", device.get("videoCodec"));
|
||||
}
|
||||
if (device.containsKey("audioCodec")) {
|
||||
metadata.put("audioCodec", device.get("audioCodec"));
|
||||
}
|
||||
return objectMapper.writeValueAsString(metadata);
|
||||
} catch (Exception e) {
|
||||
log.warn("构建派生元数据失败:{}", e.getMessage());
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建事件元数据(仅用ext1,遵循规范)
|
||||
*/
|
||||
private String buildEventMetadata(Map<String, Object> device) {
|
||||
try {
|
||||
Map<String, Object> eventMeta = new HashMap<>();
|
||||
// 保存原始三方设备ID
|
||||
eventMeta.put("thirdPartyDeviceId", device.get("extDeviceId"));
|
||||
// 设备类型(IPC/NVR等)
|
||||
if (device.containsKey("deviceType")) {
|
||||
eventMeta.put("deviceType", device.get("deviceType"));
|
||||
}
|
||||
return objectMapper.writeValueAsString(eventMeta);
|
||||
} catch (Exception e) {
|
||||
log.warn("构建事件元数据失败:{}", e.getMessage());
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
}
|
||||
+1236
File diff suppressed because it is too large
Load Diff
+150
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
*
|
||||
* Copyright (c) 2025, NexIoT. All Rights Reserved.
|
||||
*
|
||||
* @Description: 视频平台产品自动创建服务实现
|
||||
* @Author: gitee.com/NexIoT
|
||||
*
|
||||
*/
|
||||
package cn.universal.admin.video.service.impl;
|
||||
|
||||
import cn.hutool.core.lang.Snowflake;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.universal.admin.video.service.VideoPlatformProductService;
|
||||
import cn.universal.common.constant.IoTConstant;
|
||||
import cn.universal.common.exception.BaseException;
|
||||
import cn.universal.persistence.entity.IoTProduct;
|
||||
import cn.universal.persistence.entity.VideoPlatformInstance;
|
||||
import cn.universal.persistence.mapper.IoTProductMapper;
|
||||
import cn.universal.persistence.mapper.VideoPlatformInstanceMapper;
|
||||
import cn.universal.security.utils.SecurityUtils;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import tk.mybatis.mapper.entity.Example;
|
||||
|
||||
/** 视频平台产品创建策略 WVP:自动创建GB/级联两个产品(autoCreateProducts=1) HIK/ICC:懒创建,首次勾选使用设备时创建平台产品 */
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class VideoPlatformProductServiceImpl implements VideoPlatformProductService {
|
||||
private String metadata =
|
||||
"""
|
||||
{"tags":[],"events":[{"id":"online","name":"设备上线","valueType":{"type":"string"}},{"id":"offline","name":"设备下线","valueType":{"type":"string"}}],"functions":[{"id":"cameraLiveStream","name":"实时预览","config":false,"inputs":[{"id":"deviceId","name":"设备ID","valueType":{"type":"string"}},{"id":"channelId","name":"通道ID","valueType":{"type":"string"}},{"id":"streamType","name":"码流类型","valueType":{"type":"enum","elements":[{"text":"主码流","value":"main"},{"text":"子码流","value":"sub"}]}}],"output":{}},{"id":"cameraTurn","name":"云台控制","config":false,"inputs":[{"id":"deviceId","name":"设备ID","valueType":{"type":"string"}},{"id":"channelId","name":"通道ID","valueType":{"type":"string"}},{"id":"command","name":"控制命令","valueType":{"type":"string"}},{"id":"speed","name":"速度","valueType":{"type":"int"}}],"output":{}},{"id":"cameraPlayback","name":"录像回放","config":false,"inputs":[{"id":"deviceId","name":"设备ID","valueType":{"type":"string"}},{"id":"channelId","name":"通道ID","valueType":{"type":"string"}},{"id":"startTime","name":"开始时间","valueType":{"type":"long"}},{"id":"endTime","name":"结束时间","valueType":{"type":"long"}}],"output":{}},{"id":"cameraSnapshot","name":"抓拍","config":false,"inputs":[{"id":"deviceId","name":"设备ID","valueType":{"type":"string"}},{"id":"channelId","name":"通道ID","valueType":{"type":"string"}}],"output":{}},{"id":"queryRecords","name":"查询录像","config":false,"inputs":[{"id":"deviceId","name":"设备ID","valueType":{"type":"string"}},{"id":"channelId","name":"通道ID","valueType":{"type":"string"}},{"id":"startTime","name":"开始时间","valueType":{"type":"string"}},{"id":"endTime","name":"结束时间","valueType":{"type":"string"}}],"output":{}},{"id":"queryCloudRecords","name":"查询云端录像","config":false,"inputs":[{"id":"app","name":"应用名","valueType":{"type":"string"}},{"id":"stream","name":"流ID","valueType":{"type":"string"}},{"id":"page","name":"页码","valueType":{"type":"int"}},{"id":"count","name":"每页数量","valueType":{"type":"int"}}],"output":{}},{"id":"loadCloudRecord","name":"加载云端录像","config":false,"inputs":[{"id":"app","name":"应用名","valueType":{"type":"string"}},{"id":"stream","name":"流ID","valueType":{"type":"string"}},{"id":"cloudRecordId","name":"录像ID","valueType":{"type":"int"}}],"output":{}}],"properties":[{"id":"deviceStatus","mode":"r","name":"设备状态","config":false,"source":"device","valueType":{"type":"enum","elements":[{"text":"在线","value":"online"},{"text":"离线","value":"offline"}]}},{"id":"manufacturer","mode":"r","name":"厂商","config":false,"source":"device","valueType":{"type":"string"}},{"id":"model","mode":"r","name":"型号","config":false,"source":"device","valueType":{"type":"string"}}]}""";
|
||||
private final IoTProductMapper productMapper;
|
||||
private final VideoPlatformInstanceMapper instanceMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void autoCreateWvpProducts(VideoPlatformInstance instance) {
|
||||
if (!"wvp".equals(instance.getPlatformType())) {
|
||||
log.warn("仅WVP平台支持自动创建产品,当前平台类型:{}", instance.getPlatformType());
|
||||
return;
|
||||
}
|
||||
if (instance.getAutoCreateProducts() != 1) {
|
||||
log.info("WVP平台实例未开启自动创建产品,跳过");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否已创建GB产品
|
||||
String gbProductKey = instance.getInstanceKey() + "_GB";
|
||||
IoTProduct gbProduct = productMapper.getProductByProductKey(gbProductKey);
|
||||
if (gbProduct == null) {
|
||||
createPlatformProduct(instance, "VIDEO_DEVICE", gbProductKey, instance.getName() + "-GB设备");
|
||||
log.info("WVP平台自动创建GB产品:{}", gbProductKey);
|
||||
}
|
||||
|
||||
// 检查是否已创建级联产品
|
||||
// String cascadeProductKey = instance.getInstanceKey() + "_CASCADE";
|
||||
// IoTProduct cascadeProduct = productMapper.getProductByProductKey(cascadeProductKey);
|
||||
// if (cascadeProduct == null) {
|
||||
// createPlatformProduct(
|
||||
// instance, "VIDEO_GATEWAY", cascadeProductKey, instance.getName() + "-级联平台");
|
||||
// log.info("WVP平台自动创建级联产品:{}", cascadeProductKey);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public IoTProduct ensurePlatformProduct(VideoPlatformInstance instance, String deviceNode) {
|
||||
String productKey = buildPlatformProductKey(instance, deviceNode);
|
||||
IoTProduct product = productMapper.getProductByProductKey(productKey);
|
||||
if (product == null) {
|
||||
product = createPlatformProduct(instance, deviceNode, productKey, instance.getName());
|
||||
log.info("懒创建平台产品:{} [{}]", productKey, deviceNode);
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IoTProduct getOrCreatePlatformProduct(String instanceKey, String deviceNode) {
|
||||
Example example = new Example(VideoPlatformInstance.class);
|
||||
example.createCriteria().andEqualTo("instanceKey", instanceKey);
|
||||
VideoPlatformInstance instance =
|
||||
instanceMapper.selectByExample(example).stream().findFirst().orElse(null);
|
||||
if (instance == null) {
|
||||
throw new BaseException("平台实例不存在:" + instanceKey);
|
||||
}
|
||||
return ensurePlatformProduct(instance, deviceNode);
|
||||
}
|
||||
|
||||
/** 创建产品*/
|
||||
private IoTProduct createPlatformProduct(
|
||||
VideoPlatformInstance instance, String deviceNode, String productKey, String productName) {
|
||||
try {
|
||||
IoTProduct product = new IoTProduct();
|
||||
product.setProductKey(productKey);
|
||||
product.setProductId(productKey); // 与productKey一致
|
||||
product.setName(productName);
|
||||
product.setThirdPlatform(instance.getPlatformType());
|
||||
product.setDeviceNode(deviceNode);
|
||||
product.setState((IoTConstant.NORMAL.byteValue())); // 启用
|
||||
product.setCreatorId(SecurityUtils.getUnionId());
|
||||
product.setCreateTime(new Date());
|
||||
product.setUpdateTime(new Date());
|
||||
product.setStorePolicy("mysql");
|
||||
product.setTransportProtocol("以太网");
|
||||
product.setIsDeleted(0);
|
||||
// 将平台实例配置写入thirdConfiguration
|
||||
Map<String, Object> thirdConfig = new HashMap<>();
|
||||
thirdConfig.put("instanceKey", instance.getInstanceKey());
|
||||
thirdConfig.put("endpoint", instance.getEndpoint());
|
||||
thirdConfig.put("auth", instance.getAuth());
|
||||
thirdConfig.put("version", instance.getVersion());
|
||||
thirdConfig.put("options", instance.getOptions());
|
||||
thirdConfig.put("customField", new ArrayList<String>());
|
||||
product.setThirdConfiguration(objectMapper.writeValueAsString(thirdConfig));
|
||||
|
||||
// 默认物模型(仅包含online/offline事件)
|
||||
product.setMetadata(metadata);
|
||||
|
||||
productMapper.insertSelective(product);
|
||||
return product;
|
||||
} catch (Exception e) {
|
||||
log.error("创建平台产品失败:{}", e.getMessage(), e);
|
||||
throw new BaseException("创建平台产品失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 根据平台类型与设备节点构建产品Key */
|
||||
private String buildPlatformProductKey(VideoPlatformInstance instance, String deviceNode) {
|
||||
// // WVP有特殊规则(已在autoCreateWvpProducts中处理),这里是通用规则
|
||||
// if ("wvp".equals(instance.getPlatformType())) {
|
||||
// if ("VIDEO_DEVICE".equals(deviceNode)) {
|
||||
// return instance.getInstanceKey() + "_GB";
|
||||
// } else if ("VIDEO_GATEWAY".equals(deviceNode)) {
|
||||
// return instance.getInstanceKey() + "_CASCADE";
|
||||
// }
|
||||
// }
|
||||
// HIK/ICC/其他平台:instanceKey_deviceNode
|
||||
return instance.getInstanceKey();
|
||||
}
|
||||
}
|
||||
+484
@@ -0,0 +1,484 @@
|
||||
package cn.universal.admin.video.web;
|
||||
|
||||
import cn.universal.admin.video.service.VideoDeviceImportService;
|
||||
import cn.universal.admin.video.service.VideoPlatformInstanceService;
|
||||
import cn.universal.common.annotation.Log;
|
||||
import cn.universal.common.domain.R;
|
||||
import cn.universal.common.enums.BusinessType;
|
||||
import cn.universal.common.exception.BaseException;
|
||||
import cn.universal.persistence.entity.VideoPlatformInstance;
|
||||
import cn.universal.persistence.entity.VideoPlatformDevice;
|
||||
import cn.universal.persistence.entity.IoTUser;
|
||||
import cn.universal.security.BaseController;
|
||||
import cn.universal.security.utils.SecurityUtils;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/** 视频平台实例与设备接口(先行联调,逐步接入真实适配器) */
|
||||
@RestController
|
||||
@RequestMapping("/api/video/platforms")
|
||||
@RequiredArgsConstructor
|
||||
public class VideoPlatformController extends BaseController {
|
||||
|
||||
private final VideoPlatformInstanceService instanceService;
|
||||
private final VideoDeviceImportService deviceImportService;
|
||||
|
||||
// 同步限频缓存:记录每个实例最后一次强制同步时间戳
|
||||
private static final java.util.concurrent.ConcurrentHashMap<String, Long> SYNC_RATE_LIMIT =
|
||||
new java.util.concurrent.ConcurrentHashMap<>();
|
||||
|
||||
/** 平台实例列表 */
|
||||
@GetMapping("")
|
||||
public R<List<VideoPlatformInstance>> listPlatforms() {
|
||||
IoTUser user = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
List<VideoPlatformInstance> all = instanceService.listAll();
|
||||
if (user != null && !user.isAdmin()) {
|
||||
String uid = user.getUnionId();
|
||||
all = all.stream().filter(i -> uid != null && uid.equals(i.getCreatorId()))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
return R.ok(all);
|
||||
}
|
||||
|
||||
/** 根据instanceKey查询实例 */
|
||||
@GetMapping("/{instanceKey}")
|
||||
public R<VideoPlatformInstance> getInstance(@PathVariable("instanceKey") String instanceKey) {
|
||||
assertInstancePerm(instanceKey);
|
||||
return R.ok(instanceService.getByInstanceKey(instanceKey));
|
||||
}
|
||||
|
||||
/** 创建/更新平台实例(若id为空则创建,否则更新) */
|
||||
@PostMapping("")
|
||||
@Log(title = "创建/更新视频平台实例", businessType = BusinessType.INSERT)
|
||||
public R<VideoPlatformInstance> savePlatform(@RequestBody VideoPlatformInstance body) {
|
||||
IoTUser user = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
VideoPlatformInstance saved;
|
||||
if (body.getId() == null) {
|
||||
if (user != null) {
|
||||
body.setCreatorId(user.getUnionId());
|
||||
}
|
||||
saved = instanceService.create(body);
|
||||
} else {
|
||||
VideoPlatformInstance existing = instanceService.getByInstanceKey(body.getInstanceKey());
|
||||
if (existing == null) throw new BaseException("平台实例不存在");
|
||||
if (user != null && !user.isAdmin()) {
|
||||
if (existing.getCreatorId() == null || !existing.getCreatorId().equals(user.getUnionId())) {
|
||||
throw new BaseException("无权更新该平台实例");
|
||||
}
|
||||
}
|
||||
saved = instanceService.update(body);
|
||||
}
|
||||
// 尝试应用订阅配置(若启用)
|
||||
try {
|
||||
instanceService.applySubscription(saved.getInstanceKey());
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
return R.ok(saved);
|
||||
}
|
||||
|
||||
/** 删除平台实例 */
|
||||
@DeleteMapping("/{id}")
|
||||
@Log(title = "删除视频平台实例", businessType = BusinessType.DELETE)
|
||||
public R<Void> deletePlatform(@PathVariable("id") Long id, @RequestParam(value = "force", required = false, defaultValue = "false") boolean force) {
|
||||
IoTUser user = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
VideoPlatformInstance inst = instanceService.getById(id);
|
||||
if (inst == null) throw new BaseException("平台实例不存在");
|
||||
if (user != null && !user.isAdmin()) {
|
||||
if (inst.getCreatorId() == null || !inst.getCreatorId().equals(user.getUnionId())) {
|
||||
throw new BaseException("无权删除该平台实例");
|
||||
}
|
||||
}
|
||||
instanceService.delete(id, force);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/** 测试连接(占位,后续接入适配器) */
|
||||
@PostMapping("/test")
|
||||
public R<Map<String, Object>> testConnection(@RequestBody Map<String, Object> body) {
|
||||
String instanceKey = (String) body.get("instanceKey");
|
||||
assertInstancePerm(instanceKey);
|
||||
return R.ok(instanceService.testConnection(instanceKey));
|
||||
}
|
||||
|
||||
/** 获取组织树(部分平台支持) */
|
||||
@GetMapping("/{instanceKey}/orgs")
|
||||
public R<List<Map<String, Object>>> listOrganizations(
|
||||
@PathVariable("instanceKey") String instanceKey, @RequestParam Map<String, String> params) {
|
||||
Map<String, Object> filters = new java.util.HashMap<>(params);
|
||||
assertInstancePerm(instanceKey);
|
||||
return R.ok(instanceService.listOrganizations(instanceKey, filters));
|
||||
}
|
||||
|
||||
/** 实时拉取设备目录(占位),用于前端页面测试 */
|
||||
@GetMapping("/{instanceKey}/devices")
|
||||
public R<List<Map<String, Object>>> listDevices(
|
||||
@PathVariable("instanceKey") String instanceKey, @RequestParam Map<String, String> params) {
|
||||
Map<String, Object> filters = new java.util.HashMap<>(params);
|
||||
assertInstancePerm(instanceKey);
|
||||
return R.ok(instanceService.listDevices(instanceKey, filters));
|
||||
}
|
||||
|
||||
@PostMapping("/{instanceKey}/subscribe")
|
||||
public R<Void> subscribe(@PathVariable("instanceKey") String instanceKey) {
|
||||
assertInstancePerm(instanceKey);
|
||||
instanceService.applySubscription(instanceKey);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/{instanceKey}/unsubscribe")
|
||||
public R<Void> unsubscribe(@PathVariable("instanceKey") String instanceKey) {
|
||||
assertInstancePerm(instanceKey);
|
||||
instanceService.cancelSubscription(instanceKey);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/** 强制同步组织与设备缓存(限频:每实例5分钟一次) */
|
||||
@PostMapping("/{instanceKey}/sync")
|
||||
@Log(title = "强制同步视频平台组织与设备缓存", businessType = BusinessType.OTHER)
|
||||
public R<?> forceSync(@PathVariable("instanceKey") String instanceKey) {
|
||||
assertInstancePerm(instanceKey);
|
||||
long now = System.currentTimeMillis();
|
||||
Long last = SYNC_RATE_LIMIT.get(instanceKey);
|
||||
if (last != null && (now - last) < 5 * 60 * 1000) {
|
||||
long remaining = 5 * 60 * 1000 - (now - last);
|
||||
return R.error("操作过于频繁,请在" + (remaining / 1000) + "秒后重试");
|
||||
}
|
||||
SYNC_RATE_LIMIT.put(instanceKey, now);
|
||||
|
||||
// 分页拉取所有设备(循环直到没有下一页)
|
||||
int totalDeviceCount = 0;
|
||||
int currentPage = 1;
|
||||
int pageSize = 100; // 每页拉取100条
|
||||
|
||||
while (true) {
|
||||
Map<String, Object> forceParam = new java.util.HashMap<>();
|
||||
forceParam.put("forceSync", "true");
|
||||
forceParam.put("page", currentPage);
|
||||
forceParam.put("count", pageSize);
|
||||
|
||||
// 触发设备同步(具体落库由Service实现到中间表)
|
||||
List<Map<String, Object>> devices = instanceService.listDevices(instanceKey, forceParam);
|
||||
|
||||
if (devices == null || devices.isEmpty()) {
|
||||
break; // 没有更多数据,退出循环
|
||||
}
|
||||
|
||||
totalDeviceCount += devices.size();
|
||||
|
||||
// 如果返回数量小于pageSize,说明已经是最后一页
|
||||
if (devices.size() < pageSize) {
|
||||
break;
|
||||
}
|
||||
|
||||
currentPage++;
|
||||
}
|
||||
|
||||
// 同步组织信息(如果有)
|
||||
Map<String, Object> orgParam = new java.util.HashMap<>();
|
||||
orgParam.put("forceSync", "true");
|
||||
List<Map<String, Object>> orgs = instanceService.listOrganizations(instanceKey, orgParam);
|
||||
|
||||
Map<String, Object> result = new java.util.HashMap<>();
|
||||
result.put("orgCount", orgs == null ? 0 : orgs.size());
|
||||
result.put("deviceCount", totalDeviceCount);
|
||||
result.put("instanceKey", instanceKey);
|
||||
result.put("pages", currentPage);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备通道列表(从中间表返回)
|
||||
*
|
||||
* @deprecated 请使用 listChannels 从通道表查询
|
||||
*/
|
||||
@Deprecated
|
||||
@GetMapping("/{instanceKey}/devices/{deviceId}/channels")
|
||||
public R<List<Map<String, Object>>> listDeviceChannels(
|
||||
@PathVariable("instanceKey") String instanceKey, @PathVariable("deviceId") String deviceId) {
|
||||
assertInstancePerm(instanceKey);
|
||||
return R.ok(instanceService.listDeviceChannels(instanceKey, deviceId));
|
||||
}
|
||||
|
||||
/** 查询通道列表(从通道表查询) 支持筛选: keyword(通道名称/ID), status(通道状态), ptzType(PTZ类型) */
|
||||
@GetMapping("/{instanceKey}/channels")
|
||||
public R<List<Map<String, Object>>> listChannels(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@RequestParam(required = false) String deviceId,
|
||||
@RequestParam Map<String, String> params) {
|
||||
Map<String, Object> filters = new java.util.HashMap<>(params);
|
||||
assertInstancePerm(instanceKey);
|
||||
return R.ok(instanceService.listChannels(instanceKey, deviceId, filters));
|
||||
}
|
||||
|
||||
/** 获取通道流地址(预览/回放) */
|
||||
@GetMapping("/{instanceKey}/devices/{deviceId}/channels/{channelId}/stream")
|
||||
public R<Map<String, String>> getStreamUrl(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@PathVariable("channelId") String channelId,
|
||||
@RequestParam Map<String, String> params) {
|
||||
String streamType = params.getOrDefault("streamType", "main");
|
||||
assertInstancePerm(instanceKey);
|
||||
return R.ok(instanceService.getStreamUrl(instanceKey, deviceId, channelId, streamType));
|
||||
}
|
||||
|
||||
/** PTZ控制 */
|
||||
@PostMapping("/{instanceKey}/devices/{deviceId}/channels/{channelId}/ptz")
|
||||
@Log(title = "视频设备PTZ控制", businessType = BusinessType.OTHER)
|
||||
public R<Void> controlPTZ(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@PathVariable("channelId") String channelId,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
String command = String.valueOf(body.getOrDefault("command", "stop"));
|
||||
int speed = 50;
|
||||
try {
|
||||
Object s = body.get("speed");
|
||||
if (s != null) speed = Integer.parseInt(String.valueOf(s));
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
assertInstancePerm(instanceKey);
|
||||
instanceService.controlPTZ(instanceKey, deviceId, channelId, command, speed);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/** 批量导入设备(勾选使用流程) */
|
||||
@PostMapping("/{instanceKey}/devices/import")
|
||||
@Log(title = "批量导入视频设备", businessType = BusinessType.IMPORT)
|
||||
public R<Map<String, Integer>> importDevices(
|
||||
@PathVariable("instanceKey") String instanceKey, @RequestBody Map<String, Object> body) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> devices = (List<Map<String, Object>>) body.get("devices");
|
||||
String productKey = (String) body.get("productKey");
|
||||
assertInstancePerm(instanceKey);
|
||||
Map<String, Integer> result =
|
||||
deviceImportService.importDevices(instanceKey, devices, productKey);
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/** 获取预览流地址 */
|
||||
@GetMapping("/{instanceKey}/devices/{deviceId}/channels/{channelId}/preview")
|
||||
public R<Map<String, String>> getPreviewUrl(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@PathVariable("channelId") String channelId,
|
||||
@RequestParam(defaultValue = "main") String streamType) {
|
||||
assertInstancePerm(instanceKey);
|
||||
return R.ok(instanceService.getStreamUrl(instanceKey, deviceId, channelId, streamType));
|
||||
}
|
||||
|
||||
/** 获取回放流地址 */
|
||||
@GetMapping("/{instanceKey}/devices/{deviceId}/channels/{channelId}/playback")
|
||||
public R<Map<String, Object>> getPlaybackUrl(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@PathVariable("channelId") String channelId,
|
||||
@RequestParam Map<String, String> params) {
|
||||
assertInstancePerm(instanceKey);
|
||||
|
||||
// 从参数中获取开始时间和结束时间
|
||||
String startTimeStr = params.get("startTime");
|
||||
String endTimeStr = params.get("endTime");
|
||||
|
||||
if (startTimeStr == null || endTimeStr == null) {
|
||||
return R.error("缺少参数: startTime 或 endTime", null);
|
||||
}
|
||||
|
||||
// 解析时间字符串为时间戳(毫秒)
|
||||
long startTime;
|
||||
long endTime;
|
||||
try {
|
||||
// 支持格式:yyyy-MM-dd HH:mm:ss 或时间戳(毫秒)
|
||||
if (startTimeStr.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")) {
|
||||
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
startTime = sdf.parse(startTimeStr).getTime();
|
||||
} else {
|
||||
startTime = Long.parseLong(startTimeStr);
|
||||
// 如果是10位秒级时间戳,转换为毫秒
|
||||
if (startTimeStr.length() == 10) {
|
||||
startTime = startTime * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
if (endTimeStr.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")) {
|
||||
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
endTime = sdf.parse(endTimeStr).getTime();
|
||||
} else {
|
||||
endTime = Long.parseLong(endTimeStr);
|
||||
// 如果是10位秒级时间戳,转换为毫秒
|
||||
if (endTimeStr.length() == 10) {
|
||||
endTime = endTime * 1000;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return R.error("时间格式错误: " + e.getMessage(), null);
|
||||
}
|
||||
|
||||
return R.ok(instanceService.getPlaybackUrl(instanceKey, deviceId, channelId, startTime, endTime));
|
||||
}
|
||||
|
||||
/** 查询录像记录 */
|
||||
@GetMapping("/{instanceKey}/devices/{deviceId}/channels/{channelId}/records")
|
||||
public R<List<Map<String, Object>>> queryRecords(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@PathVariable("channelId") String channelId,
|
||||
@RequestParam Map<String, String> params) {
|
||||
long startTime = Long.parseLong(params.getOrDefault("startTime", "0"));
|
||||
long endTime =
|
||||
Long.parseLong(params.getOrDefault("endTime", String.valueOf(System.currentTimeMillis())));
|
||||
assertInstancePerm(instanceKey);
|
||||
return R.ok(instanceService.queryRecords(instanceKey, deviceId, channelId, startTime, endTime));
|
||||
}
|
||||
|
||||
// ==================== 国标录像下载 ====================
|
||||
|
||||
/** 开始国标录像下载 */
|
||||
@PostMapping("/{instanceKey}/devices/{deviceId}/channels/{channelId}/gb-records/download/start")
|
||||
public R<Map<String, Object>> startGBRecordDownload(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@PathVariable("channelId") String channelId,
|
||||
@RequestBody Map<String, Object> data) {
|
||||
assertInstancePerm(instanceKey);
|
||||
return instanceService.startGBRecordDownload(instanceKey, deviceId, channelId, data);
|
||||
}
|
||||
|
||||
/** 停止国标录像下载 */
|
||||
@PostMapping("/{instanceKey}/devices/{deviceId}/channels/{channelId}/gb-records/download/stop")
|
||||
public R<Void> stopGBRecordDownload(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@PathVariable("channelId") String channelId,
|
||||
@RequestBody Map<String, Object> data) {
|
||||
assertInstancePerm(instanceKey);
|
||||
return instanceService.stopGBRecordDownload(instanceKey, deviceId, channelId, data);
|
||||
}
|
||||
|
||||
/** 获取国标录像下载进度 */
|
||||
@GetMapping("/{instanceKey}/devices/{deviceId}/channels/{channelId}/gb-records/download/progress")
|
||||
public R<Map<String, Object>> getGBRecordDownloadProgress(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@PathVariable("channelId") String channelId,
|
||||
@RequestParam String stream) {
|
||||
assertInstancePerm(instanceKey);
|
||||
return instanceService.getGBRecordDownloadProgress(instanceKey, deviceId, channelId, stream);
|
||||
}
|
||||
|
||||
// ==================== 云端录像 ====================
|
||||
|
||||
/** 查询云端录像日期列表 */
|
||||
@GetMapping("/{instanceKey}/devices/{deviceId}/channels/{channelId}/cloud-records/dates")
|
||||
public R<List<String>> queryCloudRecordDates(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@PathVariable("channelId") String channelId,
|
||||
@RequestParam Map<String, String> params) {
|
||||
assertInstancePerm(instanceKey);
|
||||
return instanceService.queryCloudRecordDates(instanceKey, deviceId, channelId, params);
|
||||
}
|
||||
|
||||
/** 查询云端录像列表 */
|
||||
@GetMapping("/{instanceKey}/devices/{deviceId}/channels/{channelId}/cloud-records")
|
||||
public R<Map<String, Object>> queryCloudRecords(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@PathVariable("channelId") String channelId,
|
||||
@RequestParam Map<String, String> params) {
|
||||
assertInstancePerm(instanceKey);
|
||||
return instanceService.queryCloudRecords(instanceKey, deviceId, channelId, params);
|
||||
}
|
||||
|
||||
/** 加载云端录像文件 */
|
||||
@PostMapping("/{instanceKey}/devices/{deviceId}/channels/{channelId}/cloud-records/load")
|
||||
public R<Map<String, Object>> loadCloudRecord(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@PathVariable("channelId") String channelId,
|
||||
@RequestBody Map<String, Object> data) {
|
||||
assertInstancePerm(instanceKey);
|
||||
return instanceService.loadCloudRecord(instanceKey, deviceId, channelId, data);
|
||||
}
|
||||
|
||||
/** 云端录像定位 */
|
||||
@PostMapping("/{instanceKey}/devices/{deviceId}/channels/{channelId}/cloud-records/seek")
|
||||
public R<Void> seekCloudRecord(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@PathVariable("channelId") String channelId,
|
||||
@RequestBody Map<String, Object> data) {
|
||||
assertInstancePerm(instanceKey);
|
||||
return instanceService.seekCloudRecord(instanceKey, deviceId, channelId, data);
|
||||
}
|
||||
|
||||
/** 设置云端录像倍速 */
|
||||
@PostMapping("/{instanceKey}/devices/{deviceId}/channels/{channelId}/cloud-records/speed")
|
||||
public R<Void> setCloudRecordSpeed(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@PathVariable("channelId") String channelId,
|
||||
@RequestBody Map<String, Object> data) {
|
||||
assertInstancePerm(instanceKey);
|
||||
return instanceService.setCloudRecordSpeed(instanceKey, deviceId, channelId, data);
|
||||
}
|
||||
|
||||
/** 获取云端录像下载地址 */
|
||||
@GetMapping("/{instanceKey}/cloud-records/play/path")
|
||||
public R<Map<String, Object>> getCloudRecordPlayPath(
|
||||
@PathVariable("instanceKey") String instanceKey, @RequestParam Integer recordId) {
|
||||
assertInstancePerm(instanceKey);
|
||||
return instanceService.getCloudRecordPlayPath(instanceKey, recordId);
|
||||
}
|
||||
|
||||
/** 获取设备完整信息(跨主表和扩展表查询) */
|
||||
@GetMapping("/{instanceKey}/devices/{deviceId}/detail")
|
||||
public R<Map<String, Object>> getDeviceDetail(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId) {
|
||||
assertInstancePerm(instanceKey);
|
||||
return R.ok(instanceService.getDeviceDetail(instanceKey, deviceId));
|
||||
}
|
||||
|
||||
@PutMapping("/{instanceKey}/devices/{deviceId}")
|
||||
@Log(title = "更新视频设备信息", businessType = BusinessType.UPDATE)
|
||||
public R<Map<String, Object>> updateDevice(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId,
|
||||
@RequestBody Map<String, Object> updates) {
|
||||
assertInstancePerm(instanceKey);
|
||||
Map<String, Object> detail = instanceService.updateDevice(instanceKey, deviceId, updates);
|
||||
return R.ok(detail);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{instanceKey}/devices/{deviceId}")
|
||||
@Log(title = "删除视频设备", businessType = BusinessType.DELETE)
|
||||
public R<Void> deleteDevice(
|
||||
@PathVariable("instanceKey") String instanceKey,
|
||||
@PathVariable("deviceId") String deviceId) {
|
||||
assertInstancePerm(instanceKey);
|
||||
instanceService.deleteDevice(instanceKey, deviceId);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
private void assertInstancePerm(String instanceKey) {
|
||||
IoTUser user = loginIoTUnionUser(SecurityUtils.getUnionId());
|
||||
if (user == null || user.isAdmin()) return;
|
||||
VideoPlatformInstance inst = instanceService.getByInstanceKey(instanceKey);
|
||||
if (inst == null) throw new BaseException("平台实例不存在");
|
||||
String uid = user.getUnionId();
|
||||
if (uid == null || !uid.equals(inst.getCreatorId())) {
|
||||
throw new BaseException("无权操作该平台实例");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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') >= date_format(#{params.beginTime},'%y%m%d')
|
||||
</if>
|
||||
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
|
||||
AND date_format(create_time,'%y%m%d') <= 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') >= date_format(#{params.beginTime},'%y%m%d')
|
||||
</if>
|
||||
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
|
||||
AND date_format(create_time,'%y%m%d') <= 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') >= date_format(#{params.beginTime},'%y%m%d')
|
||||
</if>
|
||||
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
|
||||
AND date_format(create_time,'%y%m%d') <= 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') >= date_format(#{params.beginTime},'%y%m%d')
|
||||
</if>
|
||||
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
|
||||
AND date_format(create_time,'%y%m%d') <= 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)
|
||||
#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($parentheseIndex != -1)
|
||||
@Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
|
||||
#elseif($column.javaType == 'Date')
|
||||
@Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd")
|
||||
#if($column.isPk==1)
|
||||
@Id
|
||||
#else
|
||||
@Excel(name = "${comment}")
|
||||
@Column(name = "${column.columnName}" )
|
||||
#end
|
||||
#end
|
||||
#if($column.isPk==1)
|
||||
@Id
|
||||
#else
|
||||
@Column(name = "${column.columnName}")
|
||||
#end
|
||||
private $column.javaType $column.javaField;
|
||||
private $column.javaType $column.javaField;
|
||||
|
||||
#end
|
||||
#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);
|
||||
/**
|
||||
* 查询${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;
|
||||
}
|
||||
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}")
|
||||
#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
|
||||
#end
|
||||
private $column.javaType $column.javaField;
|
||||
|
||||
#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,8 +70,7 @@
|
||||
<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,11 +68,9 @@
|
||||
#end
|
||||
#end
|
||||
<el-form-item>
|
||||
<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 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-form-item>
|
||||
</el-form>
|
||||
|
||||
|
||||
@@ -68,11 +68,8 @@
|
||||
#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,11 +68,8 @@
|
||||
#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">
|
||||
<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 > #{$javaField}
|
||||
</if>
|
||||
#elseif($queryType == "GTE")
|
||||
<if test="$javaField != null #if($javaType ==
|
||||
'String' ) and $javaField.trim() != ''#end">and $columnName >= #{$javaField
|
||||
}
|
||||
</if>
|
||||
#elseif($queryType == "LT")
|
||||
<if test="$javaField != null #if($javaType ==
|
||||
'String' ) and $javaField.trim() != ''#end">and $columnName < #{$javaField}
|
||||
</if>
|
||||
#elseif($queryType == "LTE")
|
||||
<if test="$javaField != null #if($javaType ==
|
||||
'String' ) and $javaField.trim() != ''#end">and $columnName <= #{$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>
|
||||
<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 > #{$javaField}
|
||||
</if>
|
||||
#elseif($queryType == "GTE")
|
||||
<if test="$javaField != null #if($javaType ==
|
||||
'String' ) and $javaField.trim() != ''#end">and $columnName >= #{$javaField
|
||||
}
|
||||
</if>
|
||||
#elseif($queryType == "LT")
|
||||
<if test="$javaField != null #if($javaType ==
|
||||
'String' ) and $javaField.trim() != ''#end">and $columnName < #{$javaField}
|
||||
</if>
|
||||
#elseif($queryType == "LTE")
|
||||
<if test="$javaField != null #if($javaType ==
|
||||
'String' ) and $javaField.trim() != ''#end">and $columnName <= #{$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
|
||||
</where>
|
||||
#if($table.tree)
|
||||
order by ${tree_parent_code}
|
||||
#end
|
||||
</select>
|
||||
</select>
|
||||
|
||||
<select id="select${ClassName}ById" parameterType="${pkColumn.javaType}"
|
||||
resultMap="${ClassName}Result">
|
||||
#if($table.crud)
|
||||
<include refid="select${ClassName}Vo"/>
|
||||
<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}}
|
||||
#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>
|
||||
</update>
|
||||
|
||||
<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}ById" parameterType="${pkColumn.javaType}">
|
||||
delete
|
||||
from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}}
|
||||
</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>
|
||||
<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,93 @@
|
||||
<?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>
|
||||
<dependency>
|
||||
<groupId>cn.universal.iot</groupId>
|
||||
<artifactId>cn-universal-common</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>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</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>
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package cn.universal.databridge.engine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 封装参数化SQL结果:SQL文本 + 有序参数列表
|
||||
*/
|
||||
public class ParamSql {
|
||||
private final String sql;
|
||||
private final List<Object> params;
|
||||
|
||||
public ParamSql(String sql, List<Object> params) {
|
||||
this.sql = sql;
|
||||
this.params = params != null ? new ArrayList<>(params) : new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
public List<Object> getParams() {
|
||||
return Collections.unmodifiableList(params);
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package cn.universal.databridge.engine;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 参数化模板引擎:支持 #{path} 值占位
|
||||
* 所有值均以 '?' 参数占位绑定,避免引号与转义问题
|
||||
*/
|
||||
public class ParamTemplateEngine {
|
||||
|
||||
private static final Pattern VAR_PATTERN = Pattern.compile("#\\{([a-zA-Z0-9_.]+)\\}");
|
||||
|
||||
public ParamSql process(String template, Map<String, Object> variables, SqlDialectAdapter adapter) {
|
||||
if (StrUtil.isBlank(template)) {
|
||||
return new ParamSql("", List.of());
|
||||
}
|
||||
String sql = template;
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
// 处理 #{var.path} 占位符
|
||||
Matcher m = VAR_PATTERN.matcher(sql);
|
||||
StringBuffer sb = new StringBuffer();
|
||||
while (m.find()) {
|
||||
String path = m.group(1);
|
||||
Object value = getNestedValue(variables, path);
|
||||
|
||||
// 将值转换为适合数据库的格式
|
||||
Object paramValue = convertValueForDatabase(value, path);
|
||||
params.add(paramValue);
|
||||
|
||||
// properties 和 data 字段如果是 Map 序列化的 JSON 字符串,需要特殊处理
|
||||
String placeholder = "?";
|
||||
if (("properties".equals(path) || "data".equals(path)) && paramValue instanceof String) {
|
||||
String strValue = (String) paramValue;
|
||||
// 检查是否是 JSON 对象格式(以 { 开头且以 } 结尾)
|
||||
if (strValue.trim().startsWith("{") && strValue.trim().endsWith("}")) {
|
||||
placeholder = adapter.wrapJsonParameter();
|
||||
}
|
||||
}
|
||||
m.appendReplacement(sb, Matcher.quoteReplacement(placeholder));
|
||||
}
|
||||
m.appendTail(sb);
|
||||
sql = sb.toString();
|
||||
|
||||
return new ParamSql(sql, params);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object getNestedValue(Map<String, Object> variables, String path) {
|
||||
if (path == null || path.isEmpty()) return null;
|
||||
String[] keys = path.split("\\.");
|
||||
Object curr = variables;
|
||||
for (String k : keys) {
|
||||
if (curr == null) return null;
|
||||
if (curr instanceof Map) {
|
||||
curr = ((Map<String, Object>) curr).get(k);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// 若直接访问 properties 或 data,返回 JSON 字符串,避免各库的 JSON 绑定差异
|
||||
if (curr instanceof Map && ("properties".equals(path) || "data".equals(path))) {
|
||||
return cn.hutool.json.JSONUtil.toJsonStr(curr);
|
||||
}
|
||||
return curr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将值转换为适合数据库存储的格式
|
||||
* @param value 原始值
|
||||
* @param path 变量路径
|
||||
* @return 转换后的值
|
||||
*/
|
||||
private Object convertValueForDatabase(Object value, String path) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 如果已经是字符串,直接返回
|
||||
if (value instanceof String) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 其他类型转为字符串,避免类型不匹配问题
|
||||
if (value instanceof Number || value instanceof Boolean) {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
if (value instanceof java.util.Date) {
|
||||
return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(value);
|
||||
}
|
||||
|
||||
// Map 类型序列化为 JSON
|
||||
if (value instanceof Map) {
|
||||
return cn.hutool.json.JSONUtil.toJsonStr(value);
|
||||
}
|
||||
|
||||
// 其他类型默认转为字符串
|
||||
return value.toString();
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package cn.universal.databridge.engine;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SQL方言适配器接口:不同数据库的JSON宏、文本转换等差异在此适配
|
||||
*/
|
||||
public interface SqlDialectAdapter {
|
||||
|
||||
/**
|
||||
* 生成 JSON 片段(返回 JSON 类型/对象),参数位置使用占位符 '?'
|
||||
* @param keys 键名(静态字符串,已安全)
|
||||
* @param valueCount 值数量(等于 keys.size())
|
||||
* @return SQL 片段,如 JSON_OBJECT('k1', ?, 'k2', ?)
|
||||
*/
|
||||
String jsonFragment(List<String> keys, int valueCount);
|
||||
|
||||
/**
|
||||
* 生成 JSON 文本片段(返回文本),参数位置使用占位符 '?'
|
||||
* @param keys 键名(静态字符串,已安全)
|
||||
* @param valueCount 值数量(等于 keys.size())
|
||||
* @return SQL 片段,如 json_build_object(...)::TEXT
|
||||
*/
|
||||
String jsonTextFragment(List<String> keys, int valueCount);
|
||||
|
||||
/**
|
||||
* 包装JSON字符串参数以转换为特定数据库的JSON类型
|
||||
* @return 转换表达式,默认不转换直接返回 ?
|
||||
*/
|
||||
default String wrapJsonParameter() {
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package cn.universal.databridge.engine.dialect;
|
||||
|
||||
import cn.universal.databridge.engine.SqlDialectAdapter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** MySQL 方言适配 */
|
||||
public class MySqlDialectAdapter implements SqlDialectAdapter {
|
||||
private String buildPairs(List<String> keys) {
|
||||
return keys.stream()
|
||||
.map(k -> "'" + k.replace("'", "''") + "', ?")
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String jsonFragment(List<String> keys, int valueCount) {
|
||||
return "JSON_OBJECT(" + buildPairs(keys) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String jsonTextFragment(List<String> keys, int valueCount) {
|
||||
// MySQL 的 JSON_OBJECT 返回 JSON 文本即可
|
||||
return jsonFragment(keys, valueCount);
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package cn.universal.databridge.engine.dialect;
|
||||
|
||||
import cn.universal.databridge.engine.SqlDialectAdapter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Oracle 方言适配 */
|
||||
public class OracleDialectAdapter implements SqlDialectAdapter {
|
||||
private String buildPairs(List<String> keys) {
|
||||
// Oracle: JSON_OBJECT('k1' VALUE ?, 'k2' VALUE ?)
|
||||
return keys.stream()
|
||||
.map(k -> "'" + k.replace("'", "''") + "' VALUE ?")
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String jsonFragment(List<String> keys, int valueCount) {
|
||||
return "JSON_OBJECT(" + buildPairs(keys) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String jsonTextFragment(List<String> keys, int valueCount) {
|
||||
// Oracle 的 JSON_OBJECT 返回 JSON 文本字符串
|
||||
return jsonFragment(keys, valueCount);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package cn.universal.databridge.engine.dialect;
|
||||
|
||||
import cn.universal.databridge.engine.SqlDialectAdapter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** PostgreSQL 方言适配 */
|
||||
public class PostgresDialectAdapter implements SqlDialectAdapter {
|
||||
private String buildPairs(List<String> keys) {
|
||||
return keys.stream()
|
||||
.map(k -> "'" + k.replace("'", "''") + "', ?")
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String jsonFragment(List<String> keys, int valueCount) {
|
||||
return "json_build_object(" + buildPairs(keys) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String jsonTextFragment(List<String> keys, int valueCount) {
|
||||
return jsonFragment(keys, valueCount) + "::TEXT";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String wrapJsonParameter() {
|
||||
// PostgreSQL: 将 JSON 字符串转为 jsonb 类型
|
||||
return "?::jsonb";
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package cn.universal.databridge.engine.dialect;
|
||||
|
||||
import cn.universal.databridge.engine.SqlDialectAdapter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* SQL Server 方言适配
|
||||
* 注意:SQL Server 2016之前不支持JSON_OBJECT,2016+语法也不同
|
||||
* 这里使用字符串拼接方式构造JSON(兼容所有版本)
|
||||
*/
|
||||
public class SqlServerDialectAdapter implements SqlDialectAdapter {
|
||||
|
||||
@Override
|
||||
public String jsonFragment(List<String> keys, int valueCount) {
|
||||
// SQL Server 使用 CONCAT 函数拼接 JSON 字符串
|
||||
// 简化处理:所有值都当作字符串处理(加引号)
|
||||
// 如果需要精确区分数值,应在应用层处理
|
||||
StringBuilder sb = new StringBuilder("CONCAT('{'");
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
if (i > 0) {
|
||||
sb.append(", ','");
|
||||
}
|
||||
String key = keys.get(i).replace("'", "''");
|
||||
sb.append(", '\"" + key + "\":', COALESCE('\"' + CAST(? AS NVARCHAR(MAX)) + '\"', 'null')");
|
||||
}
|
||||
sb.append(", '}'");
|
||||
return sb.toString() + ")"; // 添加 CONCAT 的闭合括号
|
||||
}
|
||||
|
||||
@Override
|
||||
public String jsonTextFragment(List<String> keys, int valueCount) {
|
||||
// SQL Server 的 JSON 构造本身就是文本
|
||||
return jsonFragment(keys, valueCount);
|
||||
}
|
||||
}
|
||||
+46
-44
@@ -34,61 +34,63 @@ 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 // 运行中
|
||||
}
|
||||
}
|
||||
|
||||
+57
-53
@@ -34,72 +34,76 @@ 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 // 错误状态
|
||||
}
|
||||
}
|
||||
|
||||
+83
-86
@@ -34,112 +34,109 @@ 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, POSTGRESQL, H2, ORACLE, SQLSERVER, 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 // 双向流转
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ public class DataBridgeManager {
|
||||
@Resource private TemplateEngine templateEngine;
|
||||
|
||||
/** 处理设备数据 */
|
||||
@Async("dataBridgeExecutor")
|
||||
@Async("taskExecutor")
|
||||
public void processDeviceData(List<BaseUPRequest> requests) {
|
||||
if (CollectionUtil.isEmpty(requests)) {
|
||||
return;
|
||||
|
||||
+202
-197
@@ -44,219 +44,224 @@ 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) {
|
||||
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);
|
||||
|
||||
// 等待一段时间后重试
|
||||
/**
|
||||
* 启动数据输入任务
|
||||
*/
|
||||
public void startInputTask(Long configId) {
|
||||
try {
|
||||
Thread.sleep(30000); // 30秒后重试
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DataBridgeConfig config = dataBridgeConfigService.getById(configId);
|
||||
if (config == null) {
|
||||
throw new RuntimeException("配置不存在");
|
||||
}
|
||||
// TODO: 需要实现新的输入配置判断逻辑,因为Direction枚举已被移除
|
||||
|
||||
/** 处理输入数据 */
|
||||
private void processInputData(List<BaseUPRequest> data, DataBridgeConfig config) {
|
||||
if (CollectionUtil.isEmpty(data)) {
|
||||
return;
|
||||
}
|
||||
ResourceConnection connection = resourceConnectionService.getById(config.getTargetResourceId());
|
||||
if (connection == null || connection.getStatus() != 1) {
|
||||
throw new RuntimeException("资源连接不存在或已禁用");
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
int successCount = 0;
|
||||
int failedCount = 0;
|
||||
String errorMessage = null;
|
||||
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);
|
||||
|
||||
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);
|
||||
runningTasks.remove(configId);
|
||||
log.error("启动数据输入任务失败,配置ID: {}, 错误: {}", configId, e.getMessage(), e);
|
||||
throw 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());
|
||||
}
|
||||
/**
|
||||
* 停止数据输入任务
|
||||
*/
|
||||
public void stopInputTask(Long configId) {
|
||||
try {
|
||||
DataBridgeConfig config = dataBridgeConfigService.getById(configId);
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** 记录输入执行日志 */
|
||||
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();
|
||||
ResourceConnection connection = resourceConnectionService.getById(config.getTargetResourceId());
|
||||
if (connection == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataInputLogService.save(log);
|
||||
} catch (Exception e) {
|
||||
log.error("记录输入执行日志失败: {}", e.getMessage(), e);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取调度间隔时间(毫秒) */
|
||||
private long getScheduleInterval(DataBridgeConfig config) {
|
||||
// 从配置中读取间隔时间,默认60秒
|
||||
// TODO: 可以从 inputConfig 中解析间隔时间
|
||||
return 60000; // 60秒
|
||||
}
|
||||
/**
|
||||
* 检查任务是否正在运行
|
||||
*/
|
||||
public Boolean isTaskRunning(Long configId) {
|
||||
return runningTasks.getOrDefault(configId, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动定时拉取任务
|
||||
*/
|
||||
@Async("taskExecutor")
|
||||
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秒
|
||||
}
|
||||
}
|
||||
|
||||
+45
-49
@@ -26,58 +26,54 @@ 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);
|
||||
}
|
||||
|
||||
+40
-44
@@ -26,52 +26,48 @@ 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);
|
||||
}
|
||||
|
||||
+3
@@ -27,6 +27,7 @@ import jakarta.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -89,6 +90,7 @@ public class ResourceConnectionService {
|
||||
|
||||
/** 更新资源连接 */
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CacheEvict(cacheNames = {"data_bridage_resource_connection"},allEntries = true)
|
||||
public void updateResourceConnection(ResourceConnection connection) {
|
||||
// 1. 验证连接(使用ConfigValidator进行基本验证)
|
||||
ConfigValidator.validateResourceConnection(connection);
|
||||
@@ -171,6 +173,7 @@ public class ResourceConnectionService {
|
||||
|
||||
/** 删除连接 */
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CacheEvict(cacheNames = {"data_bridage_resource_connection"},allEntries = true)
|
||||
public void deleteConnection(Long id) {
|
||||
// 检查是否被桥接配置使用
|
||||
List<DataBridgeConfig> configs = dataBridgeConfigService.getConfigsByTargetResourceId(id);
|
||||
|
||||
+147
-139
@@ -30,162 +30,170 @@ 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", "桥接配置不能为空");
|
||||
/**
|
||||
* 验证桥接配置
|
||||
*/
|
||||
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个字符");
|
||||
}
|
||||
}
|
||||
|
||||
// 验证名称
|
||||
validateName(config.getName(), "桥接配置名称");
|
||||
/**
|
||||
* 验证资源连接
|
||||
*/
|
||||
public static void validateResourceConnection(ResourceConnection connection) {
|
||||
if (connection == null) {
|
||||
throw new DataBridgeException("CONNECTION_NULL", "资源连接不能为空");
|
||||
}
|
||||
|
||||
// 验证源范围
|
||||
if (config.getSourceScope() == null) {
|
||||
throw new DataBridgeException("INVALID_SOURCE_SCOPE", "源范围不能为空");
|
||||
// 验证名称
|
||||
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", "连接方向不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
// 验证目标资源ID
|
||||
if (config.getTargetResourceId() == null || config.getTargetResourceId() <= 0) {
|
||||
throw new DataBridgeException("INVALID_TARGET_RESOURCE_ID", "目标资源ID无效");
|
||||
/**
|
||||
* 验证名称
|
||||
*/
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
// 验证桥接类型
|
||||
if (config.getBridgeType() == null) {
|
||||
throw new DataBridgeException("INVALID_BRIDGE_TYPE", "桥接类型不能为空");
|
||||
/**
|
||||
* 验证产品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.getTemplate() == null || config.getTemplate().trim().isEmpty()) {
|
||||
throw new DataBridgeException("INVALID_TEMPLATE", "模板内容不能为空");
|
||||
/**
|
||||
* 验证主机地址
|
||||
*/
|
||||
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", "主机地址格式无效");
|
||||
}
|
||||
}
|
||||
|
||||
// 验证统一配置JSON格式
|
||||
if (config.getConfig() != null && !config.getConfig().trim().isEmpty()) {
|
||||
validateJsonFormat(config.getConfig(), "统一配置");
|
||||
/**
|
||||
* 验证数据库名
|
||||
*/
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
// 验证Magic脚本
|
||||
if (config.getMagicScript() != null && !config.getMagicScript().trim().isEmpty()) {
|
||||
// Magic脚本内容验证可以在这里添加
|
||||
/**
|
||||
* 验证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());
|
||||
}
|
||||
}
|
||||
|
||||
// 验证状态
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+94
@@ -59,6 +59,14 @@ public class ConnectionTester {
|
||||
switch (connection.getType()) {
|
||||
case MYSQL:
|
||||
return testMySQLConnection(connection);
|
||||
case POSTGRESQL:
|
||||
return testPostgreSQLConnection(connection);
|
||||
case H2:
|
||||
return testH2Connection(connection);
|
||||
case ORACLE:
|
||||
return testOracleConnection(connection);
|
||||
case SQLSERVER:
|
||||
return testSQLServerConnection(connection);
|
||||
case KAFKA:
|
||||
return testKafkaConnection(connection);
|
||||
case MQTT:
|
||||
@@ -112,6 +120,92 @@ public class ConnectionTester {
|
||||
}
|
||||
}
|
||||
|
||||
/** 测试PostgreSQL连接 */
|
||||
private ConnectionTestResult testPostgreSQLConnection(ResourceConnection connection) {
|
||||
try {
|
||||
String jdbcUrl =
|
||||
String.format(
|
||||
"jdbc:postgresql://%s:%d/%s",
|
||||
connection.getHost(), connection.getPort(), connection.getDatabaseName());
|
||||
|
||||
try (SimpleDataSource dataSource =
|
||||
new SimpleDataSource(jdbcUrl, connection.getUsername(), connection.getPassword())) {
|
||||
try (Connection conn = dataSource.getConnection()) {
|
||||
conn.createStatement().executeQuery("SELECT 1");
|
||||
return ConnectionTestResult.success("PostgreSQL连接测试成功");
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("PostgreSQL连接测试失败", e);
|
||||
return ConnectionTestResult.failure("PostgreSQL连接失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 测试H2连接 */
|
||||
private ConnectionTestResult testH2Connection(ResourceConnection connection) {
|
||||
try {
|
||||
String jdbcUrl =
|
||||
String.format(
|
||||
"jdbc:h2:tcp://%s:%d/%s",
|
||||
connection.getHost(), connection.getPort(), connection.getDatabaseName());
|
||||
|
||||
try (SimpleDataSource dataSource =
|
||||
new SimpleDataSource(jdbcUrl, connection.getUsername(), connection.getPassword())) {
|
||||
try (Connection conn = dataSource.getConnection()) {
|
||||
conn.createStatement().executeQuery("SELECT 1");
|
||||
return ConnectionTestResult.success("H2连接测试成功");
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("H2连接测试失败", e);
|
||||
return ConnectionTestResult.failure("H2连接失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 测试Oracle连接 */
|
||||
private ConnectionTestResult testOracleConnection(ResourceConnection connection) {
|
||||
try {
|
||||
// Oracle使用SID或Service Name
|
||||
String jdbcUrl =
|
||||
String.format(
|
||||
"jdbc:oracle:thin:@%s:%d:%s",
|
||||
connection.getHost(), connection.getPort(), connection.getDatabaseName());
|
||||
|
||||
try (SimpleDataSource dataSource =
|
||||
new SimpleDataSource(jdbcUrl, connection.getUsername(), connection.getPassword())) {
|
||||
try (Connection conn = dataSource.getConnection()) {
|
||||
conn.createStatement().executeQuery("SELECT 1 FROM DUAL");
|
||||
return ConnectionTestResult.success("Oracle连接测试成功");
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("Oracle连接测试失败", e);
|
||||
return ConnectionTestResult.failure("Oracle连接失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 测试SQL Server连接 */
|
||||
private ConnectionTestResult testSQLServerConnection(ResourceConnection connection) {
|
||||
try {
|
||||
// SQL Server连接URL,添加encrypt=false和trustServerCertificate=true以避免SSL证书验证问题
|
||||
String jdbcUrl =
|
||||
String.format(
|
||||
"jdbc:sqlserver://%s:%d;databaseName=%s;encrypt=false;trustServerCertificate=true;characterEncoding=UTF-8",
|
||||
connection.getHost(), connection.getPort(), connection.getDatabaseName());
|
||||
|
||||
try (SimpleDataSource dataSource =
|
||||
new SimpleDataSource(jdbcUrl, connection.getUsername(), connection.getPassword())) {
|
||||
try (Connection conn = dataSource.getConnection()) {
|
||||
conn.createStatement().executeQuery("SELECT 1");
|
||||
return ConnectionTestResult.success("SQL Server连接测试成功");
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
log.error("SQL Server连接测试失败", e);
|
||||
return ConnectionTestResult.failure("SQL Server连接失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** 测试Kafka连接 */
|
||||
private ConnectionTestResult testKafkaConnection(ResourceConnection connection) {
|
||||
Properties props = new Properties();
|
||||
|
||||
+491
-374
@@ -23,7 +23,8 @@ import javax.sql.DataSource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 数据桥接连接池管理器 独立管理数据桥接的数据库连接池,避免与框架的tk.mybatis冲突
|
||||
* 数据桥接连接池管理器
|
||||
* 独立管理数据桥接的数据库连接池,避免与框架的tk.mybatis冲突
|
||||
*
|
||||
* @author gitee.com/NexIoT
|
||||
* @version 1.0
|
||||
@@ -32,384 +33,500 @@ 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建数据源
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
// 连接池缓存 - 使用独立的命名空间避免冲突
|
||||
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();
|
||||
}
|
||||
|
||||
// 创建新的连接池
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试数据源连接
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据源是否健康
|
||||
*
|
||||
* @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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 启动保活任务 */
|
||||
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);
|
||||
}
|
||||
|
||||
/** 关闭所有连接池 */
|
||||
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();
|
||||
/**
|
||||
* 获取或创建数据源
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("关闭数据桥接连接池失败: {} - {}", key, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新的连接池
|
||||
return connectionPoolCache.computeIfAbsent(cacheKey, key -> {
|
||||
log.info("创建数据桥接连接池: {}", key);
|
||||
return createDataBridgeDataSource(connection);
|
||||
});
|
||||
connectionPoolCache.clear();
|
||||
log.info("所有数据桥接连接池已关闭");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接池状态
|
||||
*
|
||||
* @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()));
|
||||
}
|
||||
/**
|
||||
* 创建数据桥接专用的数据源
|
||||
*
|
||||
* @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());
|
||||
|
||||
// 连接测试配置 - 根据数据库类型设置
|
||||
String dbType = connection.getType().name().toUpperCase();
|
||||
config.setConnectionTestQuery(getConnectionTestQuery(dbType));
|
||||
String initSql = getConnectionInitSql(dbType);
|
||||
if (initSql != null && !initSql.isEmpty()) {
|
||||
config.setConnectionInitSql(initSql);
|
||||
}
|
||||
|
||||
// 数据桥接专用配置
|
||||
config.setInitializationFailTimeout(15000); // 15秒初始化超时
|
||||
config.setRegisterMbeans(false); // 关闭JMX监控,避免冲突
|
||||
config.setLeakDetectionThreshold(30000); // 30秒连接泄漏检测
|
||||
config.setAutoCommit(true);
|
||||
config.setKeepaliveTime(30000); // 30秒保活
|
||||
|
||||
// 数据库特定属性配置
|
||||
addDatabaseSpecificProperties(config, dbType);
|
||||
|
||||
// 创建数据源
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加PostgreSQL特定属性
|
||||
*
|
||||
* @param config HikariCP配置
|
||||
*/
|
||||
private void addPostgreSQLProperties(HikariConfig config) {
|
||||
config.addDataSourceProperty("tcpKeepAlive", "true");
|
||||
config.addDataSourceProperty("socketTimeout", "20");
|
||||
config.addDataSourceProperty("loginTimeout", "20");
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加Oracle特定属性
|
||||
*
|
||||
* @param config HikariCP配置
|
||||
*/
|
||||
private void addOracleProperties(HikariConfig config) {
|
||||
config.addDataSourceProperty("oracle.net.CONNECT_TIMEOUT", "20000");
|
||||
config.addDataSourceProperty("oracle.jdbc.ReadTimeout", "20000");
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加SQL Server特定属性
|
||||
*
|
||||
* @param config HikariCP配置
|
||||
*/
|
||||
private void addSQLServerProperties(HikariConfig config) {
|
||||
config.addDataSourceProperty("loginTimeout", "20");
|
||||
config.addDataSourceProperty("socketTimeout", "20000");
|
||||
config.addDataSourceProperty("encrypt", "false");
|
||||
config.addDataSourceProperty("trustServerCertificate", "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加H2特定属性
|
||||
*
|
||||
* @param config HikariCP配置
|
||||
*/
|
||||
private void addH2Properties(HikariConfig config) {
|
||||
// H2数据库通常不需要特殊配置
|
||||
config.addDataSourceProperty("AUTO_SERVER", "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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试数据源连接
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据源是否健康
|
||||
*
|
||||
* @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) {
|
||||
String dbType = connection.getType().name().toUpperCase();
|
||||
String host = connection.getHost();
|
||||
Integer port = connection.getPort();
|
||||
String databaseName = getDatabaseName(connection);
|
||||
|
||||
return switch (dbType) {
|
||||
case "MYSQL" -> 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",
|
||||
host, port, databaseName);
|
||||
case "POSTGRESQL" -> String.format("jdbc:postgresql://%s:%d/%s", host, port, databaseName);
|
||||
case "H2" -> String.format("jdbc:h2:tcp://%s:%d/%s", host, port, databaseName);
|
||||
case "ORACLE" -> String.format("jdbc:oracle:thin:@%s:%d:%s", host, port, databaseName);
|
||||
case "SQLSERVER" -> String.format("jdbc:sqlserver://%s:%d;databaseName=%s;characterEncoding=UTF-8", host, port, databaseName);
|
||||
default -> throw new IllegalArgumentException("不支持的数据库类型: " + dbType);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库名称
|
||||
*
|
||||
* @param connection 连接配置
|
||||
* @return 数据库名称
|
||||
*/
|
||||
private String getDatabaseName(ResourceConnection connection) {
|
||||
// 使用数据库名称字段,如果为空则根据数据库类型使用默认值
|
||||
String databaseName = connection.getDatabaseName();
|
||||
if (databaseName != null && !databaseName.trim().isEmpty()) {
|
||||
return databaseName;
|
||||
}
|
||||
// 根据数据库类型返回默认数据库名
|
||||
String dbType = connection.getType().name().toUpperCase();
|
||||
return switch (dbType) {
|
||||
case "MYSQL" -> "mysql";
|
||||
case "POSTGRESQL" -> "postgres";
|
||||
case "H2" -> "test";
|
||||
case "ORACLE" -> "ORCL";
|
||||
case "SQLSERVER" -> "master";
|
||||
default -> "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 "H2" -> "org.h2.Driver";
|
||||
case "ORACLE" -> "oracle.jdbc.driver.OracleDriver";
|
||||
case "SQLSERVER" -> "com.microsoft.sqlserver.jdbc.SQLServerDriver";
|
||||
default -> throw new IllegalArgumentException("不支持的数据库类型: " + dbType);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接测试SQL - 根据数据库类型返回不同的测试SQL
|
||||
*
|
||||
* @param dbType 数据库类型
|
||||
* @return 测试SQL
|
||||
*/
|
||||
private String getConnectionTestQuery(String dbType) {
|
||||
return switch (dbType) {
|
||||
case "ORACLE" -> "SELECT 1 FROM DUAL";
|
||||
case "SQLSERVER" -> "SELECT 1";
|
||||
default -> "SELECT 1";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接初始化SQL - 根据数据库类型返回不同的初始化SQL
|
||||
*
|
||||
* @param dbType 数据库类型
|
||||
* @return 初始化SQL,如果不需要则返回null
|
||||
*/
|
||||
private String getConnectionInitSql(String dbType) {
|
||||
return switch (dbType) {
|
||||
case "MYSQL" -> "SET SESSION wait_timeout=28800";
|
||||
default -> null; // 其他数据库不需要初始化SQL
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加数据库特定属性
|
||||
*
|
||||
* @param config HikariCP配置
|
||||
* @param dbType 数据库类型
|
||||
*/
|
||||
private void addDatabaseSpecificProperties(HikariConfig config, String dbType) {
|
||||
switch (dbType) {
|
||||
case "MYSQL":
|
||||
addMySQLProperties(config);
|
||||
break;
|
||||
case "POSTGRESQL":
|
||||
addPostgreSQLProperties(config);
|
||||
break;
|
||||
case "ORACLE":
|
||||
addOracleProperties(config);
|
||||
break;
|
||||
case "SQLSERVER":
|
||||
addSQLServerProperties(config);
|
||||
break;
|
||||
case "H2":
|
||||
addH2Properties(config);
|
||||
break;
|
||||
default:
|
||||
// 其他数据库使用默认配置
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成缓存键 - 使用独立前缀避免冲突
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
});
|
||||
} 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();
|
||||
}
|
||||
|
||||
// 关闭所有连接池
|
||||
connectionPoolCache.forEach((key, dataSource) -> {
|
||||
try {
|
||||
if (dataSource instanceof HikariDataSource) {
|
||||
((HikariDataSource) dataSource).close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("关闭数据桥接连接池失败: {} - {}", key, e.getMessage());
|
||||
}
|
||||
});
|
||||
return status.toString();
|
||||
}
|
||||
connectionPoolCache.clear();
|
||||
log.info("所有数据桥接连接池已关闭");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接池状态
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接池数量
|
||||
*
|
||||
* @return 连接池数量
|
||||
*/
|
||||
public int getPoolCount() {
|
||||
return connectionPoolCache.size();
|
||||
}
|
||||
}
|
||||
|
||||
+129
-128
@@ -18,7 +18,8 @@ import java.util.regex.Pattern;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* SQL模板处理器 智能处理SQL模板中的变量替换,避免引号冲突
|
||||
* SQL模板处理器
|
||||
* 智能处理SQL模板中的变量替换,避免引号冲突
|
||||
*
|
||||
* @author gitee.com/NexIoT
|
||||
* @version 1.0
|
||||
@@ -27,142 +28,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 "";
|
||||
/**
|
||||
* 处理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();
|
||||
}
|
||||
|
||||
// 使用正则表达式匹配所有 #{...} 占位符
|
||||
Pattern pattern = Pattern.compile("#\\{([a-zA-Z0-9_.]+)\\}");
|
||||
Matcher matcher = pattern.matcher(template);
|
||||
/**
|
||||
* 智能转换为SQL值,根据上下文选择合适的引号
|
||||
*
|
||||
* @param value 要转换的值
|
||||
* @param template 完整模板
|
||||
* @param position 当前位置
|
||||
* @return SQL安全格式的字符串
|
||||
*/
|
||||
private static String convertToSqlValue(Object value, String template, int position) {
|
||||
if (value == null) {
|
||||
return "NULL";
|
||||
}
|
||||
|
||||
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字符串中
|
||||
boolean inJsonContext = isInJsonContext(template, position);
|
||||
|
||||
if (inJsonContext) {
|
||||
// 在JSON上下文中,使用双引号
|
||||
return convertToJsonValue(value);
|
||||
} else {
|
||||
// 在普通SQL上下文中,使用单引号
|
||||
return SqlValueConverter.convertToSqlValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查上下文,判断是否在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 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对象中(包含大括号)
|
||||
String beforePosition = template.substring(0, position);
|
||||
int lastOpenBrace = beforePosition.lastIndexOf("{");
|
||||
int lastCloseBrace = beforePosition.lastIndexOf("}");
|
||||
|
||||
// 如果最近的大括号是开括号,说明在JSON对象中
|
||||
if (lastOpenBrace > lastCloseBrace) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 获取嵌套属性值
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为JSON值
|
||||
*
|
||||
* @param value 要转换的值
|
||||
* @return JSON格式的字符串
|
||||
*/
|
||||
private static String convertToJsonValue(Object value) {
|
||||
if (value == null) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+15
-9
@@ -16,7 +16,8 @@ import cn.hutool.json.JSONUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* SQL值转换工具类 提供将Java对象转换为SQL安全格式的通用方法
|
||||
* SQL值转换工具类
|
||||
* 提供将Java对象转换为SQL安全格式的通用方法
|
||||
*
|
||||
* @author gitee.com/NexIoT
|
||||
* @version 1.0
|
||||
@@ -26,8 +27,12 @@ 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安全格式的字符串
|
||||
@@ -36,7 +41,7 @@ public class SqlValueConverter {
|
||||
if (value == null) {
|
||||
return "NULL";
|
||||
}
|
||||
|
||||
|
||||
if (value instanceof String) {
|
||||
// 转义单引号(防止SQL注入和语法错误)
|
||||
String strValue = (String) value;
|
||||
@@ -64,7 +69,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]);
|
||||
@@ -73,7 +78,8 @@ public class SqlValueConverter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将变量值转换为SQL安全格式(JSON字符串专用) 使用双引号包围JSON字符串,避免单引号冲突
|
||||
* 将变量值转换为SQL安全格式(JSON字符串专用)
|
||||
* 使用双引号包围JSON字符串,避免单引号冲突
|
||||
*
|
||||
* @param value 要转换的值
|
||||
* @return SQL安全格式的字符串
|
||||
@@ -82,7 +88,7 @@ public class SqlValueConverter {
|
||||
if (value == null) {
|
||||
return "NULL";
|
||||
}
|
||||
|
||||
|
||||
if (value instanceof String) {
|
||||
// JSON字符串:使用双引号包围,转义内部双引号
|
||||
String strValue = (String) value;
|
||||
@@ -110,12 +116,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);
|
||||
}
|
||||
}
|
||||
|
||||
+84
-85
@@ -1,99 +1,98 @@
|
||||
<?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>
|
||||
|
||||
+31
-32
@@ -1,39 +1,38 @@
|
||||
<?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 <= #{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 <= #{endTime}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<!-- 删除过期日志 -->
|
||||
<delete id="deleteExpiredLogs">
|
||||
DELETE
|
||||
FROM iot_data_input_log
|
||||
WHERE create_time < #{expireTime}
|
||||
</delete>
|
||||
<!-- 删除过期日志 -->
|
||||
<delete id="deleteExpiredLogs">
|
||||
DELETE
|
||||
FROM iot_data_input_log
|
||||
WHERE create_time < #{expireTime}
|
||||
</delete>
|
||||
|
||||
</mapper>
|
||||
|
||||
+72
-73
@@ -1,85 +1,84 @@
|
||||
<?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>
|
||||
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
package cn.universal.databridge.engine;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* ParamTemplateEngine 单元测试
|
||||
*/
|
||||
class ParamTemplateEngineTest {
|
||||
|
||||
private ParamTemplateEngine engine;
|
||||
private Map<String, Object> variables;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
engine = new ParamTemplateEngine();
|
||||
|
||||
// 准备测试数据
|
||||
variables = new HashMap<>();
|
||||
variables.put("deviceId", "dev123");
|
||||
variables.put("productKey", "pk001");
|
||||
variables.put("deviceName", "sensor01");
|
||||
variables.put("iotId", "iot456");
|
||||
variables.put("deviceNode", "node1");
|
||||
variables.put("messageType", "property");
|
||||
|
||||
// 嵌套属性
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("temperature", 25.5);
|
||||
properties.put("humidity", 60);
|
||||
properties.put("illuminationDesc", "明亮");
|
||||
variables.put("properties", properties);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMysqlTemplate() {
|
||||
SqlDialectAdapter adapter = new MySqlDialectAdapter();
|
||||
|
||||
String template = "INSERT INTO device_data(\n" +
|
||||
" device_id, product_key, device_name, iot_id,\n" +
|
||||
" device_node, message_type, properties, raw_data\n" +
|
||||
") VALUES(\n" +
|
||||
" #{deviceId}, #{productKey}, #{deviceName}, #{iotId},\n" +
|
||||
" #{deviceNode}, #{messageType}, #{properties}, \n" +
|
||||
" ${json('csq', properties.temperature, 'tips', properties.illuminationDesc)}\n" +
|
||||
");";
|
||||
|
||||
ParamSql result = engine.process(template, variables, adapter);
|
||||
|
||||
assertNotNull(result);
|
||||
assertTrue(result.getSql().contains("JSON_OBJECT"));
|
||||
assertTrue(result.getSql().contains("?"));
|
||||
assertEquals(9, result.getParams().size());
|
||||
assertEquals("dev123", result.getParams().get(0));
|
||||
assertEquals("pk001", result.getParams().get(1));
|
||||
assertEquals(25.5, result.getParams().get(3)); // csq value
|
||||
assertEquals("明亮", result.getParams().get(4)); // tips value
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPostgreSqlTemplate() {
|
||||
SqlDialectAdapter adapter = new PostgreSqlDialectAdapter();
|
||||
|
||||
String template = "INSERT INTO device_data(\n" +
|
||||
" device_id, product_key, device_name, iot_id,\n" +
|
||||
" device_node, message_type, properties, raw_data\n" +
|
||||
") VALUES(\n" +
|
||||
" #{deviceId}, #{productKey}, #{deviceName}, #{iotId},\n" +
|
||||
" #{deviceNode}, #{messageType}, #{properties}, \n" +
|
||||
" ${json_text('csq', properties.temperature, 'tips', properties.illuminationDesc)}\n" +
|
||||
");";
|
||||
|
||||
ParamSql result = engine.process(template, variables, adapter);
|
||||
|
||||
assertNotNull(result);
|
||||
assertTrue(result.getSql().contains("json_build_object"));
|
||||
assertTrue(result.getSql().contains("::TEXT"));
|
||||
assertTrue(result.getSql().contains("?"));
|
||||
assertEquals(9, result.getParams().size());
|
||||
assertEquals("dev123", result.getParams().get(0));
|
||||
assertEquals("明亮", result.getParams().get(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOracleTemplate() {
|
||||
SqlDialectAdapter adapter = new OracleDialectAdapter();
|
||||
|
||||
String template = "INSERT INTO device_data(\n" +
|
||||
" device_id, product_key, device_name, iot_id,\n" +
|
||||
" device_node, message_type, properties, raw_data\n" +
|
||||
") VALUES(\n" +
|
||||
" #{deviceId}, #{productKey}, #{deviceName}, #{iotId},\n" +
|
||||
" #{deviceNode}, #{messageType}, #{properties}, \n" +
|
||||
" ${json('csq', properties.temperature, 'tips', properties.illuminationDesc)}\n" +
|
||||
");";
|
||||
|
||||
ParamSql result = engine.process(template, variables, adapter);
|
||||
|
||||
assertNotNull(result);
|
||||
assertTrue(result.getSql().contains("JSON_OBJECT"));
|
||||
assertTrue(result.getSql().contains("VALUE"));
|
||||
assertTrue(result.getSql().contains("?"));
|
||||
assertEquals(9, result.getParams().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSqlServerTemplate() {
|
||||
SqlDialectAdapter adapter = new SqlServerDialectAdapter();
|
||||
|
||||
String template = "INSERT INTO device_data(\n" +
|
||||
" device_id, product_key, device_name, iot_id,\n" +
|
||||
" device_node, message_type, properties, raw_data\n" +
|
||||
") VALUES(\n" +
|
||||
" #{deviceId}, #{productKey}, #{deviceName}, #{iotId},\n" +
|
||||
" #{deviceNode}, #{messageType}, #{properties}, \n" +
|
||||
" ${json('csq', properties.temperature, 'tips', properties.illuminationDesc)}\n" +
|
||||
");";
|
||||
|
||||
ParamSql result = engine.process(template, variables, adapter);
|
||||
|
||||
assertNotNull(result);
|
||||
assertTrue(result.getSql().contains("JSON_OBJECT"));
|
||||
assertTrue(result.getSql().contains("?"));
|
||||
assertEquals(9, result.getParams().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNestedPropertyAccess() {
|
||||
SqlDialectAdapter adapter = new MySqlDialectAdapter();
|
||||
|
||||
String template = "SELECT #{properties.temperature}, #{properties.humidity}";
|
||||
|
||||
ParamSql result = engine.process(template, variables, adapter);
|
||||
|
||||
assertEquals("SELECT ?, ?", result.getSql());
|
||||
assertEquals(2, result.getParams().size());
|
||||
assertEquals(25.5, result.getParams().get(0));
|
||||
assertEquals(60, result.getParams().get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDirectPropertiesAccess() {
|
||||
SqlDialectAdapter adapter = new MySqlDialectAdapter();
|
||||
|
||||
String template = "INSERT INTO test(data) VALUES(#{properties})";
|
||||
|
||||
ParamSql result = engine.process(template, variables, adapter);
|
||||
|
||||
assertEquals("INSERT INTO test(data) VALUES(?)", result.getSql());
|
||||
assertEquals(1, result.getParams().size());
|
||||
// properties 应该被序列化为 JSON 字符串
|
||||
assertTrue(result.getParams().get(0) instanceof String);
|
||||
assertTrue(((String) result.getParams().get(0)).contains("temperature"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipleJsonMacros() {
|
||||
SqlDialectAdapter adapter = new MySqlDialectAdapter();
|
||||
|
||||
String template = "INSERT INTO test(col1, col2) VALUES(" +
|
||||
"${json('a', deviceId)}, " +
|
||||
"${json('b', productKey)}" +
|
||||
")";
|
||||
|
||||
ParamSql result = engine.process(template, variables, adapter);
|
||||
|
||||
assertTrue(result.getSql().contains("JSON_OBJECT"));
|
||||
assertEquals(2, result.getParams().size());
|
||||
assertEquals("dev123", result.getParams().get(0));
|
||||
assertEquals("pk001", result.getParams().get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyTemplate() {
|
||||
SqlDialectAdapter adapter = new MySqlDialectAdapter();
|
||||
|
||||
ParamSql result = engine.process("", variables, adapter);
|
||||
|
||||
assertEquals("", result.getSql());
|
||||
assertTrue(result.getParams().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNullTemplate() {
|
||||
SqlDialectAdapter adapter = new MySqlDialectAdapter();
|
||||
|
||||
ParamSql result = engine.process(null, variables, adapter);
|
||||
|
||||
assertEquals("", result.getSql());
|
||||
assertTrue(result.getParams().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNoVariablesTemplate() {
|
||||
SqlDialectAdapter adapter = new MySqlDialectAdapter();
|
||||
|
||||
String template = "SELECT * FROM device_data WHERE id = 1";
|
||||
|
||||
ParamSql result = engine.process(template, variables, adapter);
|
||||
|
||||
assertEquals(template, result.getSql());
|
||||
assertTrue(result.getParams().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMixedJsonAndJsonText() {
|
||||
SqlDialectAdapter adapter = new PostgreSqlDialectAdapter();
|
||||
|
||||
String template = "INSERT INTO test(col1, col2) VALUES(" +
|
||||
"${json('temp', properties.temperature)}, " +
|
||||
"${json_text('desc', properties.illuminationDesc)}" +
|
||||
")";
|
||||
|
||||
ParamSql result = engine.process(template, variables, adapter);
|
||||
|
||||
assertTrue(result.getSql().contains("json_build_object"));
|
||||
assertTrue(result.getSql().contains("::TEXT"));
|
||||
assertEquals(2, result.getParams().size());
|
||||
assertEquals(25.5, result.getParams().get(0));
|
||||
assertEquals("明亮", result.getParams().get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChineseCharacters() {
|
||||
SqlDialectAdapter adapter = new MySqlDialectAdapter();
|
||||
|
||||
variables.put("chineseValue", "测试中文");
|
||||
String template = "INSERT INTO test(name) VALUES(#{chineseValue})";
|
||||
|
||||
ParamSql result = engine.process(template, variables, adapter);
|
||||
|
||||
assertEquals("INSERT INTO test(name) VALUES(?)", result.getSql());
|
||||
assertEquals("测试中文", result.getParams().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSpecialCharactersInJson() {
|
||||
SqlDialectAdapter adapter = new PostgreSqlDialectAdapter();
|
||||
|
||||
Map<String, Object> props = (Map<String, Object>) variables.get("properties");
|
||||
props.put("specialDesc", "包含\"引号\"和'单引号'");
|
||||
|
||||
String template = "SELECT ${json_text('data', properties.specialDesc)}";
|
||||
|
||||
ParamSql result = engine.process(template, variables, adapter);
|
||||
|
||||
assertNotNull(result);
|
||||
assertTrue(result.getParams().get(0).toString().contains("引号"));
|
||||
}
|
||||
}
|
||||
+27
-27
@@ -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>
|
||||
|
||||
|
||||
+160
-171
@@ -22,192 +22,181 @@ 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 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 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
|
||||
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
|
||||
public List<SourceScope> getSupportedSourceScopes() {
|
||||
return List.of(
|
||||
SourceScope.ALL_PRODUCTS,
|
||||
SourceScope.SPECIFIC_PRODUCTS,
|
||||
SourceScope.APPLICATION
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@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";
|
||||
@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);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
@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);
|
||||
}
|
||||
} 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());
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
// 消息信息
|
||||
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());
|
||||
}
|
||||
@Override
|
||||
public Boolean validateConfig(DataBridgeConfig config) {
|
||||
return validateMagicScript(config.getMagicScript());
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+42
-42
@@ -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>
|
||||
|
||||
+43
-43
@@ -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>
|
||||
|
||||
+65
-52
@@ -1,67 +1,80 @@
|
||||
<?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>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</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>
|
||||
</dependency>
|
||||
|
||||
<!-- Oracle数据库驱动 -->
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc8</artifactId>
|
||||
<version>21.5.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- SQL Server数据库驱动 -->
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>12.4.2.jre8</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
-1
@@ -145,7 +145,7 @@ public class DefaultJdbcInputPlugin extends AbstractDataInputPlugin {
|
||||
case "h2" -> String.format("jdbc:h2:tcp://%s:%d/%s", host, port, databaseName);
|
||||
case "oracle" -> String.format("jdbc:oracle:thin:@%s:%d:%s", host, port, databaseName);
|
||||
case "sqlserver" ->
|
||||
String.format("jdbc:sqlserver://%s:%d;databaseName=%s", host, port, databaseName);
|
||||
String.format("jdbc:sqlserver://%s:%d;databaseName=%s;characterEncoding=UTF-8", host, port, databaseName);
|
||||
default -> throw new IllegalArgumentException("不支持的数据库类型: " + dbType);
|
||||
};
|
||||
}
|
||||
|
||||
+128
-10
@@ -106,25 +106,40 @@ public class DefaultJdbcOutPlugin extends AbstractDataOutputPlugin {
|
||||
BaseUPRequest request,
|
||||
DataBridgeConfig config,
|
||||
ResourceConnection connection) {
|
||||
|
||||
try {
|
||||
// 获取数据源
|
||||
DataSource dataSource = connectionManager.getOrCreateDataSource(connection);
|
||||
javax.sql.DataSource dataSource = connectionManager.getOrCreateDataSource(connection);
|
||||
org.springframework.jdbc.core.JdbcTemplate jdbcTemplate = new org.springframework.jdbc.core.JdbcTemplate(dataSource);
|
||||
|
||||
// 使用Spring的JdbcTemplate执行SQL
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
jdbcTemplate.update(templateResult);
|
||||
// 构建变量并进行参数化模板解析(避免引号问题,兼容多方言)
|
||||
java.util.Map<String, Object> variables = buildTemplateVariables(request, parseConfig(config));
|
||||
cn.universal.databridge.engine.ParamTemplateEngine engine = new cn.universal.databridge.engine.ParamTemplateEngine();
|
||||
cn.universal.databridge.engine.SqlDialectAdapter adapter = getAdapter(connection.getType());
|
||||
cn.universal.databridge.engine.ParamSql ps = engine.process(config.getTemplate(), variables, adapter);
|
||||
|
||||
jdbcTemplate.update(ps.getSql(), ps.getParams().toArray());
|
||||
} catch (Exception e) {
|
||||
log.error("执行SQL失败: {} - {}", templateResult, e.getMessage());
|
||||
|
||||
// 清除可能损坏的连接池
|
||||
connectionManager.removeDataSource(connection);
|
||||
|
||||
throw new RuntimeException("SQL执行失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private cn.universal.databridge.engine.SqlDialectAdapter getAdapter(cn.universal.databridge.entity.ResourceConnection.ResourceType type) {
|
||||
switch (type) {
|
||||
case MYSQL:
|
||||
case H2:
|
||||
return new cn.universal.databridge.engine.dialect.MySqlDialectAdapter();
|
||||
case POSTGRESQL:
|
||||
return new cn.universal.databridge.engine.dialect.PostgresDialectAdapter();
|
||||
case ORACLE:
|
||||
return new cn.universal.databridge.engine.dialect.OracleDialectAdapter();
|
||||
case SQLSERVER:
|
||||
return new cn.universal.databridge.engine.dialect.SqlServerDialectAdapter();
|
||||
default:
|
||||
return new cn.universal.databridge.engine.dialect.MySqlDialectAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> generateExecutableSqlList(Object processedData, DataBridgeConfig config) {
|
||||
List<String> sqlList = new ArrayList<>();
|
||||
|
||||
@@ -162,6 +177,7 @@ public class DefaultJdbcOutPlugin extends AbstractDataOutputPlugin {
|
||||
}
|
||||
|
||||
// 使用正则表达式匹配所有 #{...} 占位符,支持嵌套属性
|
||||
// 智能处理引号,兼容MySQL和PostgreSQL
|
||||
protected String processTemplate(String template, Map<String, Object> variables) {
|
||||
if (template == null || template.trim().isEmpty()) {
|
||||
log.warn("模板为空,无法处理");
|
||||
@@ -174,7 +190,25 @@ public class DefaultJdbcOutPlugin extends AbstractDataOutputPlugin {
|
||||
while (matcher.find()) {
|
||||
String paramPath = matcher.group(1); // 获取变量路径,如 "properties.csq"
|
||||
Object value = getNestedValue(variables, paramPath);
|
||||
String sqlValue = SqlValueConverter.convertToSqlJsonValue(value);
|
||||
|
||||
// 检测占位符在模板中的上下文,判断是否已经在引号内
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
boolean inSingleQuotes = isInQuotes(template, start, end, '\'');
|
||||
boolean inDoubleQuotes = isInQuotes(template, start, end, '"');
|
||||
|
||||
String sqlValue;
|
||||
if (inSingleQuotes) {
|
||||
// 如果已经在单引号内,只替换值,不添加引号,但需要转义单引号
|
||||
sqlValue = convertValueForQuotedContext(value, true);
|
||||
} else if (inDoubleQuotes) {
|
||||
// 如果已经在双引号内,只替换值,不添加引号,但需要转义双引号
|
||||
sqlValue = convertValueForQuotedContext(value, false);
|
||||
} else {
|
||||
// 如果不在引号内,使用标准的SQL值转换(添加引号)
|
||||
sqlValue = SqlValueConverter.convertToSqlValue(value);
|
||||
}
|
||||
|
||||
matcher.appendReplacement(sb, java.util.regex.Matcher.quoteReplacement(sqlValue));
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
@@ -182,4 +216,88 @@ public class DefaultJdbcOutPlugin extends AbstractDataOutputPlugin {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测占位符是否在引号内
|
||||
* @param template 模板字符串
|
||||
* @param start 占位符开始位置
|
||||
* @param end 占位符结束位置
|
||||
* @param quoteChar 引号字符(' 或 ")
|
||||
* @return 是否在引号内
|
||||
*/
|
||||
private boolean isInQuotes(String template, int start, int end, char quoteChar) {
|
||||
// 向前查找最近的未转义引号
|
||||
int quoteBefore = -1;
|
||||
for (int i = start - 1; i >= 0; i--) {
|
||||
if (template.charAt(i) == quoteChar) {
|
||||
// 检查是否被转义(考虑连续的反斜杠)
|
||||
int backslashCount = 0;
|
||||
for (int j = i - 1; j >= 0 && template.charAt(j) == '\\'; j--) {
|
||||
backslashCount++;
|
||||
}
|
||||
// 如果反斜杠数量是偶数,说明引号未被转义
|
||||
if (backslashCount % 2 == 0) {
|
||||
quoteBefore = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (quoteBefore == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 向后查找匹配的未转义引号
|
||||
int quoteAfter = -1;
|
||||
for (int i = end; i < template.length(); i++) {
|
||||
if (template.charAt(i) == quoteChar) {
|
||||
// 检查是否被转义
|
||||
int backslashCount = 0;
|
||||
for (int j = i - 1; j >= 0 && template.charAt(j) == '\\'; j--) {
|
||||
backslashCount++;
|
||||
}
|
||||
// 如果反斜杠数量是偶数,说明引号未被转义
|
||||
if (backslashCount % 2 == 0) {
|
||||
quoteAfter = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找到了匹配的引号对,说明在引号内
|
||||
return quoteAfter != -1 && quoteBefore < start && quoteAfter >= end;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为已在引号内的上下文转换值
|
||||
* @param value 要转换的值
|
||||
* @param isSingleQuote 是否是单引号上下文
|
||||
* @return 转换后的值(不包含引号,但已转义)
|
||||
*/
|
||||
private String convertValueForQuotedContext(Object value, boolean isSingleQuote) {
|
||||
if (value == null) {
|
||||
return "NULL";
|
||||
}
|
||||
|
||||
String quoteChar = isSingleQuote ? "'" : "\"";
|
||||
String escapeChar = isSingleQuote ? "''" : "\\\"";
|
||||
|
||||
if (value instanceof String) {
|
||||
String strValue = (String) value;
|
||||
// 转义引号
|
||||
return strValue.replace(quoteChar, escapeChar);
|
||||
} else if (value instanceof Number || value instanceof Boolean) {
|
||||
// 数字和布尔类型直接转换
|
||||
return value.toString();
|
||||
} else if (value instanceof java.util.Date) {
|
||||
// 日期类型转换为SQL标准格式
|
||||
return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(value);
|
||||
} else {
|
||||
// 其他类型:转为JSON字符串并转义引号
|
||||
String jsonValue = cn.hutool.json.JSONUtil.toJsonStr(value);
|
||||
return jsonValue.replace(quoteChar, escapeChar);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+45
-51
@@ -16,59 +16,53 @@ 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");
|
||||
}
|
||||
|
||||
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());
|
||||
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");
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
package cn.universal.databridge.plugin.jdbc;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* DefaultJdbcOutPlugin 智能引号处理测试
|
||||
* 测试兼容MySQL和PostgreSQL的引号处理功能
|
||||
*
|
||||
* @author gitee.com/NexIoT
|
||||
* @version 1.0
|
||||
* @since 2025/1/15
|
||||
*/
|
||||
@DisplayName("JDBC插件智能引号处理测试")
|
||||
public class DefaultJdbcOutPluginQuoteTest {
|
||||
|
||||
private DefaultJdbcOutPlugin plugin;
|
||||
private Map<String, Object> variables;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
plugin = new DefaultJdbcOutPlugin();
|
||||
|
||||
// 准备测试数据
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put("temperature", 14.3);
|
||||
properties.put("illuminationDesc", "bright");
|
||||
properties.put("csq", 25);
|
||||
|
||||
variables = new HashMap<>();
|
||||
variables.put("deviceId", "nex123321");
|
||||
variables.put("productKey", "Ru871cfJjhoM");
|
||||
variables.put("deviceName", "期望值验证");
|
||||
variables.put("iotId", "Ru871cfJjhoMnex123321");
|
||||
variables.put("deviceNode", "DEVICE");
|
||||
variables.put("messageType", "PROPERTIES");
|
||||
variables.put("properties", properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用反射调用受保护的processTemplate方法
|
||||
*/
|
||||
private String processTemplate(String template, Map<String, Object> variables) {
|
||||
try {
|
||||
java.lang.reflect.Method method = DefaultJdbcOutPlugin.class.getDeclaredMethod(
|
||||
"processTemplate", String.class, Map.class);
|
||||
method.setAccessible(true);
|
||||
return (String) method.invoke(plugin, template, variables);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("调用processTemplate方法失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试占位符在单引号内的情况(MySQL/PostgreSQL标准)")
|
||||
void testPlaceholderInSingleQuotes() {
|
||||
// 测试场景:'#{deviceId}' 应该替换为 'nex123321',不产生双重引号
|
||||
String template = "INSERT INTO device_data(device_id) VALUES('#{deviceId}');";
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// 应该只包含一对单引号,不应该有双重引号
|
||||
assertEquals("INSERT INTO device_data(device_id) VALUES('nex123321');", result);
|
||||
assertFalse(result.contains("''"), "不应该包含双重单引号");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试PostgreSQL json_build_object场景")
|
||||
void testPostgreSQLJsonBuildObject() {
|
||||
// 用户提供的实际场景
|
||||
String template =
|
||||
"INSERT INTO device_data(device_id, product_key, device_name, iot_id, " +
|
||||
"device_node, message_type, properties, raw_data) VALUES(" +
|
||||
"'#{deviceId}', '#{productKey}', '#{deviceName}', '#{iotId}', " +
|
||||
"'#{deviceNode}', '#{messageType}', '#{properties}', " +
|
||||
"json_build_object(" +
|
||||
"'csq', #{properties.temperature}, " +
|
||||
"'tips', #{properties.illuminationDesc}" +
|
||||
")::TEXT);";
|
||||
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// 验证单引号内的占位符被正确替换,不产生双重引号
|
||||
assertTrue(result.contains("'nex123321'"), "deviceId应该被正确替换");
|
||||
assertTrue(result.contains("'Ru871cfJjhoM'"), "productKey应该被正确替换");
|
||||
assertTrue(result.contains("'期望值验证'"), "deviceName应该被正确替换");
|
||||
|
||||
// 验证json_build_object中的占位符(不在引号内)被正确替换
|
||||
assertTrue(result.contains("14.3"), "temperature应该被替换为数字");
|
||||
assertTrue(result.contains("bright"), "illuminationDesc应该被替换");
|
||||
|
||||
// 不应该有双重引号
|
||||
assertFalse(result.contains("''nex123321''"), "不应该有双重单引号");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试占位符在双引号内的情况(PostgreSQL JSON)")
|
||||
void testPlaceholderInDoubleQuotes() {
|
||||
String template = "SELECT \"#{deviceId}\" as id;";
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// 应该只包含一对双引号,不应该有双重引号
|
||||
assertEquals("SELECT \"nex123321\" as id;", result);
|
||||
assertFalse(result.contains("\"\""), "不应该包含双重双引号");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试占位符不在引号内的情况")
|
||||
void testPlaceholderNotInQuotes() {
|
||||
String template = "SELECT #{deviceId} as id;";
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// 不在引号内时,应该添加单引号
|
||||
assertEquals("SELECT 'nex123321' as id;", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试字符串值中包含单引号的情况")
|
||||
void testStringWithSingleQuote() {
|
||||
variables.put("deviceName", "O'Brien");
|
||||
String template = "INSERT INTO device_data(device_name) VALUES('#{deviceName}');";
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// 单引号应该被转义为两个单引号
|
||||
assertTrue(result.contains("O''Brien"), "单引号应该被转义");
|
||||
assertEquals("INSERT INTO device_data(device_name) VALUES('O''Brien');", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试字符串值中包含双引号的情况")
|
||||
void testStringWithDoubleQuote() {
|
||||
variables.put("description", "He said \"Hello\"");
|
||||
String template = "SELECT \"#{description}\" as desc;";
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// 双引号应该被转义
|
||||
assertTrue(result.contains("\\\""), "双引号应该被转义");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试NULL值处理")
|
||||
void testNullValue() {
|
||||
variables.put("optionalField", null);
|
||||
String template = "INSERT INTO device_data(optional_field) VALUES('#{optionalField}');";
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// NULL值应该被替换为NULL关键字
|
||||
assertEquals("INSERT INTO device_data(optional_field) VALUES('NULL');", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试数字值处理")
|
||||
void testNumberValue() {
|
||||
String template = "INSERT INTO device_data(temperature) VALUES(#{properties.temperature});";
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// 数字值不在引号内时,应该直接使用,不添加引号
|
||||
assertTrue(result.contains("14.3"), "数字值应该被正确替换");
|
||||
assertFalse(result.contains("'14.3'"), "数字值不应该被引号包围");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试数字值在引号内的情况")
|
||||
void testNumberInQuotes() {
|
||||
String template = "INSERT INTO device_data(temperature) VALUES('#{properties.temperature}');";
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// 数字值在引号内时,应该直接替换,不添加引号
|
||||
assertEquals("INSERT INTO device_data(temperature) VALUES('14.3');", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试嵌套属性访问")
|
||||
void testNestedPropertyAccess() {
|
||||
String template =
|
||||
"INSERT INTO device_data(csq, temp) VALUES(" +
|
||||
"'#{properties.csq}', #{properties.temperature});";
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
assertTrue(result.contains("'25'"), "嵌套属性csq应该被正确替换");
|
||||
assertTrue(result.contains("14.3"), "嵌套属性temperature应该被正确替换");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试混合场景:部分在引号内,部分不在")
|
||||
void testMixedQuotes() {
|
||||
String template =
|
||||
"INSERT INTO device_data(device_id, product_key, temperature) VALUES(" +
|
||||
"'#{deviceId}', '#{productKey}', #{properties.temperature});";
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// 在引号内的应该只替换值
|
||||
assertTrue(result.contains("'nex123321'"), "引号内的deviceId应该被正确替换");
|
||||
assertTrue(result.contains("'Ru871cfJjhoM'"), "引号内的productKey应该被正确替换");
|
||||
|
||||
// 不在引号内的应该添加引号
|
||||
assertTrue(result.contains("'14.3'"), "不在引号内的temperature应该被添加引号");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试复杂的PostgreSQL JSON场景")
|
||||
void testComplexPostgreSQLJson() {
|
||||
String template =
|
||||
"INSERT INTO device_data(raw_data) VALUES(" +
|
||||
"json_build_object(" +
|
||||
"'deviceId', '#{deviceId}', " +
|
||||
"'temperature', #{properties.temperature}, " +
|
||||
"'description', '#{properties.illuminationDesc}'" +
|
||||
")::TEXT);";
|
||||
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// 验证JSON字符串中的占位符被正确替换
|
||||
assertTrue(result.contains("'nex123321'"), "JSON中的deviceId应该被正确替换");
|
||||
assertTrue(result.contains("14.3"), "JSON中的temperature应该被正确替换");
|
||||
assertTrue(result.contains("'bright'"), "JSON中的description应该被正确替换");
|
||||
|
||||
// 不应该有双重引号
|
||||
assertFalse(result.contains("''nex123321''"), "不应该有双重单引号");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试布尔值处理")
|
||||
void testBooleanValue() {
|
||||
variables.put("isActive", true);
|
||||
String template = "INSERT INTO device_data(is_active) VALUES(#{isActive});";
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// 布尔值不在引号内时,应该直接使用
|
||||
assertTrue(result.contains("true"), "布尔值应该被正确替换");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试空字符串处理")
|
||||
void testEmptyString() {
|
||||
variables.put("emptyField", "");
|
||||
String template = "INSERT INTO device_data(empty_field) VALUES('#{emptyField}');";
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// 空字符串应该被正确处理
|
||||
assertEquals("INSERT INTO device_data(empty_field) VALUES('');", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测试多个占位符在同一引号内")
|
||||
void testMultiplePlaceholdersInQuotes() {
|
||||
// 注意:这种情况在实际SQL中不常见,但测试边界情况
|
||||
String template = "SELECT '#{deviceId}_#{productKey}' as combined;";
|
||||
String result = processTemplate(template, variables);
|
||||
|
||||
System.out.println("模板: " + template);
|
||||
System.out.println("结果: " + result);
|
||||
|
||||
// 两个占位符都在同一个引号内,应该都被正确替换
|
||||
assertTrue(result.contains("nex123321"), "第一个占位符应该被替换");
|
||||
assertTrue(result.contains("Ru871cfJjhoM"), "第二个占位符应该被替换");
|
||||
}
|
||||
}
|
||||
|
||||
+33
-37
@@ -34,31 +34,29 @@ 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);
|
||||
|
||||
@@ -66,7 +64,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);
|
||||
@@ -106,33 +104,31 @@ 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);
|
||||
|
||||
|
||||
+22
-25
@@ -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,31 +41,29 @@ 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);
|
||||
@@ -75,7 +73,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);
|
||||
@@ -104,8 +102,7 @@ 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();
|
||||
|
||||
|
||||
+12
-12
@@ -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();
|
||||
|
||||
|
||||
+41
-41
@@ -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>
|
||||
|
||||
+46
-67
@@ -57,18 +57,17 @@ 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;
|
||||
@@ -77,22 +76,17 @@ 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);
|
||||
@@ -100,29 +94,20 @@ 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);
|
||||
@@ -143,13 +128,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());
|
||||
@@ -158,37 +143,32 @@ 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);
|
||||
}
|
||||
|
||||
@@ -197,7 +177,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");
|
||||
@@ -207,21 +187,20 @@ 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 {
|
||||
@@ -230,7 +209,7 @@ public class KafkaDataBridgePlugin extends AbstractDataOutputPlugin {
|
||||
log.warn("解析配置JSON失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return variables;
|
||||
}
|
||||
}
|
||||
}
|
||||
+157
-162
@@ -18,179 +18,174 @@ 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 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 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();
|
||||
}
|
||||
|
||||
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
|
||||
public List<SourceScope> getSupportedSourceScopes() {
|
||||
return List.of(
|
||||
SourceScope.ALL_PRODUCTS,
|
||||
SourceScope.SPECIFIC_PRODUCTS,
|
||||
SourceScope.APPLICATION
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private BaseUPRequest transformToIoTData(Object externalData, DataBridgeConfig config) {
|
||||
try {
|
||||
if (externalData instanceof String) {
|
||||
String payload = (String) externalData;
|
||||
JSONObject data = JSONUtil.parseObj(payload);
|
||||
@Override
|
||||
protected void sendToIoTPlatform(Object processedData, DataBridgeConfig config) {
|
||||
// TODO: 实现推送到IoT平台的逻辑
|
||||
// 这里可以通过消息队列、HTTP API等方式将数据推送到IoT平台
|
||||
log.debug("推送数据到IoT平台: 配置ID={}", config.getId());
|
||||
}
|
||||
|
||||
// 构建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);
|
||||
}
|
||||
@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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
// }
|
||||
@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: 提取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";
|
||||
// }
|
||||
// }
|
||||
}
|
||||
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";
|
||||
// }
|
||||
// }
|
||||
}
|
||||
+37
-37
@@ -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>
|
||||
|
||||
+22
-27
@@ -44,18 +44,17 @@ 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;
|
||||
@@ -64,11 +63,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);
|
||||
@@ -76,18 +75,14 @@ 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);
|
||||
@@ -111,7 +106,8 @@ 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 {
|
||||
@@ -124,14 +120,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;
|
||||
}
|
||||
|
||||
@@ -140,7 +136,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");
|
||||
@@ -150,21 +146,20 @@ 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 {
|
||||
@@ -173,7 +168,7 @@ public class MqttDataBridgePlugin extends AbstractDataOutputPlugin {
|
||||
log.warn("解析配置JSON失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return variables;
|
||||
}
|
||||
}
|
||||
}
|
||||
+121
-119
@@ -17,18 +17,17 @@ 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() {
|
||||
@@ -48,122 +47,125 @@ 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 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 sendToIoTPlatform(Object processedData, DataBridgeConfig config) {
|
||||
// TODO: 实现推送到IoT平台的逻辑
|
||||
// 这里可以通过消息队列、HTTP API等方式将数据推送到IoT平台
|
||||
log.debug("推送数据到IoT平台: 配置ID={}", config.getId());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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";
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@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";
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
|
||||
+3
-2
@@ -11,14 +11,15 @@ 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><b>注意:</b>@EnableAsync 已在主启动类中全局启用,此处无需重复配置</p>
|
||||
*
|
||||
* @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>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user