Browse Source

refactor: CRITICAL/HIGH fixes — security, concurrency, resource leaks, input validation

Security:
- Remove hardcoded password fallbacks from application-druid.yml
- Add table name whitelist validation to SysFaultService & SysAlarmService
- Externalize VehicleSyncTask webhook URL to IotProperties config

Concurrency & Resource Management:
- Fix AbstractMqttConsumer reconnect/disconnect race (synchronized)
- Fix MqttClient resource leaks in disconnect() with separate try blocks
- Fix AbstractDynamicMqttConsumer broken-state MqttClient reuse
- Fix TDengineService stableColumnCache unbounded growth (MAX_CACHE_SIZE=1000)

Input Validation:
- Add null/empty checks to MqttGenericConsumer (controllerId, path, timestamp)
- Add null/empty checks to MqttFaultConsumer (controllerId, deviceId, type, desc)
- Guard against ArrayIndexOutOfBoundsException on topic path split

Tests:
- Update VehicleSyncTaskTest for new IotProperties constructor param
- All 79 tests pass, build succeeds
mqy20260511
humanleft 4 days ago
parent
commit
0fd6162ee1
22 changed files with 278 additions and 157 deletions
  1. 1
    1
      iot-platform/src/main/java/com/iot/platform/common/utils/NumericIdGenerator.java
  2. 0
    10
      iot-platform/src/main/java/com/iot/platform/config/ApplicationConfig.java
  3. 4
    1
      iot-platform/src/main/java/com/iot/platform/config/DruidConfig.java
  4. 9
    0
      iot-platform/src/main/java/com/iot/platform/config/IotProperties.java
  5. 3
    3
      iot-platform/src/main/java/com/iot/platform/mapper/SysControllerMapper.java
  6. 10
    3
      iot-platform/src/main/java/com/iot/platform/mqtt/AbstractDynamicMqttConsumer.java
  7. 39
    16
      iot-platform/src/main/java/com/iot/platform/mqtt/AbstractMqttConsumer.java
  8. 38
    8
      iot-platform/src/main/java/com/iot/platform/mqtt/MqttFaultConsumer.java
  9. 49
    13
      iot-platform/src/main/java/com/iot/platform/mqtt/MqttGenericConsumer.java
  10. 2
    1
      iot-platform/src/main/java/com/iot/platform/mqtt/MqttStatusConsumer.java
  11. 7
    0
      iot-platform/src/main/java/com/iot/platform/service/SysAlarmService.java
  12. 13
    6
      iot-platform/src/main/java/com/iot/platform/service/SysControllerService.java
  13. 7
    1
      iot-platform/src/main/java/com/iot/platform/service/SysFaultService.java
  14. 18
    22
      iot-platform/src/main/java/com/iot/platform/service/TDegnineAlarm.java
  15. 30
    39
      iot-platform/src/main/java/com/iot/platform/service/TDengineService.java
  16. 18
    5
      iot-platform/src/main/java/com/iot/platform/task/VehicleSyncTask.java
  17. 3
    3
      iot-platform/src/main/resources/application-druid.yml
  18. 1
    1
      iot-platform/src/main/resources/mapper/SysFaultMapper.xml
  19. 2
    5
      iot-platform/src/test/java/com/iot/platform/mqtt/MqttFaultConsumerTest.java
  20. 2
    5
      iot-platform/src/test/java/com/iot/platform/mqtt/MqttStatusConsumerTest.java
  21. 12
    14
      iot-platform/src/test/java/com/iot/platform/service/TDengineServiceTest.java
  22. 10
    0
      iot-platform/src/test/java/com/iot/platform/task/VehicleSyncTaskTest.java

+ 1
- 1
iot-platform/src/main/java/com/iot/platform/common/utils/NumericIdGenerator.java View File

@@ -16,7 +16,7 @@ public class NumericIdGenerator {
16 16
     private long sequence = 0L;
17 17
     private long lastTimestamp = -1L;
18 18
 
19
-    public String nextId() {
19
+    public synchronized String nextId() {
20 20
         long timestamp = timeGen();
21 21
         if (timestamp < lastTimestamp) {
22 22
             throw new RuntimeException("Clock moved backwards!");

+ 0
- 10
iot-platform/src/main/java/com/iot/platform/config/ApplicationConfig.java View File

@@ -6,7 +6,6 @@ import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilde
6 6
 import org.springframework.context.annotation.Bean;
7 7
 import org.springframework.context.annotation.Configuration;
8 8
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
9
-import org.springframework.web.client.RestTemplate;
10 9
 
11 10
 /**
12 11
  * 程序注解配置
@@ -24,13 +23,4 @@ public class ApplicationConfig
24 23
     {
25 24
         return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
26 25
     }
27
-
28
-    /**
29
-     * RestTemplate
30
-     */
31
-    @Bean
32
-    public RestTemplate restTemplate()
33
-    {
34
-        return new RestTemplate();
35
-    }
36 26
 }

+ 4
- 1
iot-platform/src/main/java/com/iot/platform/config/DruidConfig.java View File

@@ -3,6 +3,8 @@ package com.iot.platform.config;
3 3
 import java.util.HashMap;
4 4
 import java.util.Map;
5 5
 import javax.sql.DataSource;
6
+import org.slf4j.Logger;
7
+import org.slf4j.LoggerFactory;
6 8
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
7 9
 import org.springframework.boot.context.properties.ConfigurationProperties;
8 10
 import org.springframework.context.annotation.Bean;
@@ -21,6 +23,7 @@ import com.iot.platform.datasource.DynamicDataSource;
21 23
 @Configuration
22 24
 public class DruidConfig
23 25
 {
26
+    private static final Logger log = LoggerFactory.getLogger(DruidConfig.class);
24 27
     @Bean
25 28
     @ConfigurationProperties("spring.datasource.druid.master")
26 29
     public DataSource masterDataSource(DruidProperties druidProperties)
@@ -57,7 +60,7 @@ public class DruidConfig
57 60
         }
58 61
         catch (Exception e)
59 62
         {
60
-            // slave datasource may not be available
63
+            log.warn("从数据源 {} 不可用: {}", beanName, e.getMessage());
61 64
         }
62 65
     }
63 66
 }

+ 9
- 0
iot-platform/src/main/java/com/iot/platform/config/IotProperties.java View File

@@ -60,6 +60,7 @@ public class IotProperties {
60 60
         private String password = "";
61 61
         private String chargeStationTopic = "station/ChargeStation/device/+/post/json";
62 62
         private String alarmWebhookUrl = "https://esos-iot.com:9443/syscar/gaojing";
63
+        private String vehicleTriggerUrl = "https://esos-iot.com:9443/syscar/trigger";
63 64
 
64 65
         public String getBrokerUrl() {
65 66
             return brokerUrl;
@@ -100,6 +101,14 @@ public class IotProperties {
100 101
         public void setAlarmWebhookUrl(String alarmWebhookUrl) {
101 102
             this.alarmWebhookUrl = alarmWebhookUrl;
102 103
         }
104
+
105
+        public String getVehicleTriggerUrl() {
106
+            return vehicleTriggerUrl;
107
+        }
108
+
109
+        public void setVehicleTriggerUrl(String vehicleTriggerUrl) {
110
+            this.vehicleTriggerUrl = vehicleTriggerUrl;
111
+        }
103 112
     }
104 113
 
105 114
     /**

+ 3
- 3
iot-platform/src/main/java/com/iot/platform/mapper/SysControllerMapper.java View File

@@ -28,9 +28,9 @@ public interface SysControllerMapper {
28 28
                                     @Param("name")String name,
29 29
                                     @Param("path")String path);
30 30
 
31
-        Integer selectsyscontrollercount(@Param("path")String paht);
32
-        Integer selectsyscontrollercountcmd(@Param("path")String paht);
33
-        Integer selectsyscontrollercountfault(@Param("path")String paht);
31
+        Integer selectsyscontrollercount(@Param("path")String path);
32
+        Integer selectsyscontrollercountcmd(@Param("path")String path);
33
+        Integer selectsyscontrollercountfault(@Param("path")String path);
34 34
 
35 35
 
36 36
         void updatecontrollerAccept(@Param("controllerId")String controllerId,

+ 10
- 3
iot-platform/src/main/java/com/iot/platform/mqtt/AbstractDynamicMqttConsumer.java View File

@@ -43,7 +43,7 @@ public abstract class AbstractDynamicMqttConsumer {
43 43
     private static final int MAX_BATCH_SIZE = 50;
44 44
 
45 45
     protected MqttClient mqttClient;
46
-    protected MqttConnectOptions connOpts;
46
+    private MqttConnectOptions connOpts;
47 47
     protected final Set<String> currentTopicSet = new CopyOnWriteArraySet<>();
48 48
     protected final AtomicBoolean isConnected = new AtomicBoolean(false);
49 49
     protected final Object lock = new Object();
@@ -199,7 +199,13 @@ public abstract class AbstractDynamicMqttConsumer {
199 199
                     return false;
200 200
                 }
201 201
 
202
-                if (mqttClient == null) {
202
+                if (mqttClient == null || !mqttClient.isConnected()) {
203
+                    if (mqttClient != null) {
204
+                        try {
205
+                            mqttClient.close();
206
+                        } catch (MqttException ignored) {
207
+                        }
208
+                    }
203 209
                     String clientId = generateUniqueClientId();
204 210
                     MemoryPersistence persistence = new MemoryPersistence();
205 211
                     mqttClient = new MqttClient(brokerUrl, clientId, persistence);
@@ -388,7 +394,8 @@ public abstract class AbstractDynamicMqttConsumer {
388 394
             public void deliveryComplete(IMqttDeliveryToken token) {
389 395
                 try {
390 396
                     token.waitForCompletion(1000);
391
-                } catch (MqttException ignored) {
397
+                } catch (MqttException e) {
398
+                    log.warn("MQTT 消息投递确认等待超时: {}", e.getMessage());
392 399
                 }
393 400
             }
394 401
         });

+ 39
- 16
iot-platform/src/main/java/com/iot/platform/mqtt/AbstractMqttConsumer.java View File

@@ -70,8 +70,11 @@ public abstract class AbstractMqttConsumer {
70 70
             initMqttConnectOptions();
71 71
             setMqttCallback();
72 72
             connectAndSubscribeTopic();
73
-        } catch (MqttException | InterruptedException e) {
74
-            log.error("MQTT客户端初始化失败:", e);
73
+        } catch (InterruptedException e) {
74
+            Thread.currentThread().interrupt();
75
+            throw new IllegalStateException("MQTT初始化被中断", e);
76
+        } catch (MqttException e) {
77
+            throw new IllegalStateException("MQTT客户端初始化失败", e);
75 78
         }
76 79
     }
77 80
 
@@ -111,7 +114,7 @@ public abstract class AbstractMqttConsumer {
111 114
         mqttClient.setCallback(new MqttCallback() {
112 115
             @Override
113 116
             public void connectionLost(Throwable cause) {
114
-                log.error("MQTT连接断开,开始重连: {}", cause.getMessage());
117
+                log.error("MQTT连接断开,开始重连: {}", cause.getMessage(), cause);
115 118
                 isMqttConnected = false;
116 119
                 reconnect();
117 120
             }
@@ -148,18 +151,28 @@ public abstract class AbstractMqttConsumer {
148 151
         }
149 152
     }
150 153
 
151
-    public void reconnect() {
154
+    public synchronized void reconnect() {
152 155
         int maxReconnectAttempts = 3;
153 156
         for (int attempt = 1; attempt <= maxReconnectAttempts; attempt++) {
154 157
             try {
155 158
                 Thread.sleep(RECONNECT_INTERVAL);
156
-                if (mqttClient != null && !mqttClient.isConnected()) {
157
-                    mqttClient.connect(connOpts);
158
-                    mqttClient.subscribe(getSubscribeTopic(), QOS);
159
-                    isMqttConnected = true;
160
-                    log.info("MQTT重连成功(第{}次尝试)", attempt);
161
-                    break;
159
+                if (mqttClient == null || !mqttClient.isConnected()) {
160
+                    if (mqttClient != null) {
161
+                        try {
162
+                            mqttClient.close();
163
+                        } catch (MqttException ignored) {
164
+                        }
165
+                    }
166
+                    String clientId = generateClientId();
167
+                    mqttClient = new MqttClient(brokerUrl, clientId, new MemoryPersistence());
168
+                    initMqttConnectOptions();
169
+                    setMqttCallback();
162 170
                 }
171
+                mqttClient.connect(connOpts);
172
+                mqttClient.subscribe(getSubscribeTopic(), QOS);
173
+                isMqttConnected = true;
174
+                log.info("MQTT重连成功(第{}次尝试)", attempt);
175
+                break;
163 176
             } catch (MqttException | InterruptedException e) {
164 177
                 log.error("MQTT重连失败(第{}次尝试): {}", attempt, e.getMessage());
165 178
                 if (attempt == maxReconnectAttempts) {
@@ -170,16 +183,26 @@ public abstract class AbstractMqttConsumer {
170 183
     }
171 184
 
172 185
     @PreDestroy
173
-    public void disconnect() {
186
+    public synchronized void disconnect() {
174 187
         try {
175
-            if (mqttClient != null && mqttClient.isConnected()) {
176
-                mqttClient.disconnect();
177
-                mqttClient.close();
188
+            if (mqttClient != null) {
189
+                if (mqttClient.isConnected()) {
190
+                    try {
191
+                        mqttClient.disconnect();
192
+                    } catch (MqttException e) {
193
+                        log.error("MQTT断开连接失败: {}", e.getMessage());
194
+                    }
195
+                }
196
+                try {
197
+                    mqttClient.close();
198
+                } catch (MqttException e) {
199
+                    log.error("MQTT客户端关闭失败: {}", e.getMessage());
200
+                }
178 201
                 log.info("MQTT连接已断开");
179 202
             }
180 203
             onDestroy();
181
-        } catch (MqttException e) {
182
-            log.error("MQTT断开连接失败:", e);
204
+        } catch (Exception e) {
205
+            log.error("MQTT资源释放异常: {}", e.getMessage(), e);
183 206
         }
184 207
     }
185 208
 }

+ 38
- 8
iot-platform/src/main/java/com/iot/platform/mqtt/MqttFaultConsumer.java View File

@@ -80,15 +80,27 @@ public class MqttFaultConsumer extends AbstractMqttConsumer {
80 80
     @Override
81 81
     protected String generateClientId() {
82 82
         String osName = System.getProperty("os.name").toLowerCase();
83
-        return osName.contains("windows") ? "mqttx_e216fbf1620" : "mqttx_e216fbf1621";
83
+        String base = osName.contains("windows") ? "mqttx_e216fbf1620" : "mqttx_e216fbf1621";
84
+        return base + "_" + Long.toHexString(System.nanoTime()).substring(0, 6);
84 85
     }
85 86
 
86 87
     @Override
87 88
     protected void handleMessage(String topic, String messageContent) throws Exception {
88 89
         Map<String, Object> messageMap = objectMapper.readValue(messageContent, Map.class);
89
-        insertTDegine(messageMap, topic);
90
+        try {
91
+            insertTDegine(messageMap, topic);
92
+        } catch (Exception e) {
93
+            log.error("告警数据写入TDengine失败, topic={}, error={}", topic, e.getMessage(), e);
94
+        }
90 95
         SysFault sysFault = objectMapper.readValue(messageContent, SysFault.class);
91
-        mqttFaultExecutor.submit(() -> triggermethod(topic, sysFault));
96
+        mqttFaultExecutor.submit(() -> {
97
+            try {
98
+                triggermethod(topic, sysFault);
99
+            } catch (Exception e) {
100
+                log.error("告警业务处理失败, topic={}, controllerId={}, error={}",
101
+                        topic, sysFault.getController_id(), e.getMessage(), e);
102
+            }
103
+        });
92 104
     }
93 105
 
94 106
     @Override
@@ -126,13 +138,25 @@ public class MqttFaultConsumer extends AbstractMqttConsumer {
126 138
         String timestamp = weather.getTimestamp();
127 139
         String type = weather.getType();
128 140
         String desc = weather.getDesc();
141
+        String controllerId = weather.getController_id();
142
+
143
+        if (controllerId == null || controllerId.isEmpty()) {
144
+            log.warn("SysFault 缺少 controller_id,跳过处理");
145
+            return;
146
+        }
147
+        if (deviceId == null) {
148
+            deviceId = "";
149
+        }
150
+        if (type == null || desc == null) {
151
+            log.warn("SysFault 缺少 type 或 desc,跳过处理 controllerId={}", controllerId);
152
+            return;
153
+        }
129 154
 
130 155
         LocalDate localDate = LocalDate.now();
131 156
         int year = localDate.getYear();
132 157
         int month = localDate.getMonthValue();
133 158
         String formattedMonth = String.format("%02d", month);
134 159
 
135
-        String controllerId = weather.getController_id();
136 160
         String[] topics = topic.split("/");
137 161
         if (topics.length == 0) {
138 162
             log.warn("无效的topic格式: {}", topic);
@@ -140,13 +164,19 @@ public class MqttFaultConsumer extends AbstractMqttConsumer {
140 164
         }
141 165
 
142 166
         List<String> tablename = sysrealtimeService.selecttables();
143
-        List<Boolean> a = new ArrayList<>();
167
+        if (tablename == null) {
168
+            tablename = Collections.emptyList();
169
+        }
144 170
         String controllername = controllerId + year + formattedMonth + "_fault";
145 171
 
146
-        for (int i = 0; i < tablename.size(); i++) {
147
-            a.add(tablename.get(i).equals(controllername));
172
+        boolean tableExists = false;
173
+        for (String name : tablename) {
174
+            if (controllername.equals(name)) {
175
+                tableExists = true;
176
+                break;
177
+            }
148 178
         }
149
-        if (!a.contains(true)) {
179
+        if (!tableExists) {
150 180
             sysFaultService.createmessage(controllername);
151 181
         }
152 182
 

+ 49
- 13
iot-platform/src/main/java/com/iot/platform/mqtt/MqttGenericConsumer.java View File

@@ -48,7 +48,8 @@ public class MqttGenericConsumer extends AbstractMqttConsumer {
48 48
     @Override
49 49
     protected String generateClientId() {
50 50
         String osName = System.getProperty("os.name").toLowerCase();
51
-        return osName.contains("windows") ? "mqttx_e216fbf1615" : "mqttx_e216fbf1616";
51
+        String base = osName.contains("windows") ? "mqttx_e216fbf1615" : "mqttx_e216fbf1616";
52
+        return base + "_" + Long.toHexString(System.nanoTime()).substring(0, 6);
52 53
     }
53 54
 
54 55
     @Override
@@ -62,6 +63,15 @@ public class MqttGenericConsumer extends AbstractMqttConsumer {
62 63
         String fleetId = weather.getFleet_id();
63 64
         String controllerId = weather.getController_id();
64 65
 
66
+        if (controllerId == null || controllerId.isEmpty()) {
67
+            log.warn("ControllerData 缺少 controller_id,跳过处理");
68
+            return;
69
+        }
70
+        if (timestamp == null || timestamp.isEmpty()) {
71
+            log.warn("ControllerData 缺少 timestamp,跳过处理 controllerId={}", controllerId);
72
+            return;
73
+        }
74
+
65 75
         List<topics> topics = weather.getTopics();
66 76
         if (topics == null) topics = Collections.emptyList();
67 77
         List<topics> cmdtopics = weather.getCmd_topics();
@@ -70,31 +80,57 @@ public class MqttGenericConsumer extends AbstractMqttConsumer {
70 80
 
71 81
         Integer controllercountcount = 0;
72 82
         for (topics topicsMap : topics) {
73
-            Integer count = sysControllerService.selectsyscontrollercount(topicsMap.getPath());
83
+            String path = topicsMap.getPath();
84
+            String name = topicsMap.getName();
85
+            if (path == null || path.isEmpty() || name == null) {
86
+                log.warn("Topic 数据不完整,跳过: path={}, name={}", path, name);
87
+                continue;
88
+            }
89
+            Integer count = sysControllerService.selectsyscontrollercount(path);
74 90
             if (count <= 0) {
75
-                stringRedisTemplate.opsForHash().put(controllerId + ":" + topicsMap.getName(), "path", topicsMap.getPath());
76
-                sysControllerService.insertsyscontroller(controllerId, timestamp, fleetId, topicsMap.getName(), topicsMap.getPath(), topicsMap.getPath().split("/")[1]);
91
+                stringRedisTemplate.opsForHash().put(controllerId + ":" + name, "path", path);
92
+                String deviceId = path.contains("/") ? path.split("/")[1] : "";
93
+                sysControllerService.insertsyscontroller(controllerId, timestamp, fleetId, name, path, deviceId);
77 94
                 controllercountcount++;
78 95
             } else {
79
-                long ts = Long.parseLong(timestamp);
96
+                long ts;
97
+                try {
98
+                    ts = Long.parseLong(timestamp);
99
+                } catch (NumberFormatException e) {
100
+                    log.warn("timestamp 格式错误: {},使用当前时间", timestamp);
101
+                    ts = System.currentTimeMillis();
102
+                }
80 103
                 String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(ts));
81
-                sysControllerService.updatecontrollerAccept(controllerId, timestamp, fleetId, topicsMap.getName(), topicsMap.getPath(), topicsMap.getPath().split("/")[1], date);
104
+                String deviceId = path.contains("/") ? path.split("/")[1] : "";
105
+                sysControllerService.updatecontrollerAccept(controllerId, timestamp, fleetId, name, path, deviceId, date);
82 106
             }
83 107
         }
84 108
 
85 109
         for (topics cmdtopicsMap : cmdtopics) {
86
-            Integer count = sysControllerService.selectsyscontrollercountcmd(cmdtopicsMap.getPath());
110
+            String path = cmdtopicsMap.getPath();
111
+            String name = cmdtopicsMap.getName();
112
+            if (path == null || path.isEmpty() || name == null) {
113
+                log.warn("CmdTopic 数据不完整,跳过: path={}, name={}", path, name);
114
+                continue;
115
+            }
116
+            Integer count = sysControllerService.selectsyscontrollercountcmd(path);
87 117
             if (count <= 0) {
88
-                stringRedisTemplate.opsForHash().put(controllerId + "_cmd:" + cmdtopicsMap.getName(), "path", cmdtopicsMap.getPath());
89
-                sysControllerService.insertsyscontrollercmd(controllerId, timestamp, fleetId, cmdtopicsMap.getName(), cmdtopicsMap.getPath());
118
+                stringRedisTemplate.opsForHash().put(controllerId + "_cmd:" + name, "path", path);
119
+                sysControllerService.insertsyscontrollercmd(controllerId, timestamp, fleetId, name, path);
90 120
             }
91 121
         }
92 122
 
93 123
         if (faultprot != null) {
94
-            Integer count = sysControllerService.selectsyscontrollercountfault(faultprot.getPath());
95
-            if (count <= 0) {
96
-                stringRedisTemplate.opsForHash().put(controllerId + "_fault:" + faultprot.getName(), "path", faultprot.getPath());
97
-                sysControllerService.insertsyscontrollerfault(controllerId, timestamp, fleetId, faultprot.getName(), faultprot.getPath());
124
+            String path = faultprot.getPath();
125
+            String name = faultprot.getName();
126
+            if (path == null || path.isEmpty() || name == null) {
127
+                log.warn("FaultProt 数据不完整,跳过: path={}, name={}", path, name);
128
+            } else {
129
+                Integer count = sysControllerService.selectsyscontrollercountfault(path);
130
+                if (count <= 0) {
131
+                    stringRedisTemplate.opsForHash().put(controllerId + "_fault:" + name, "path", path);
132
+                    sysControllerService.insertsyscontrollerfault(controllerId, timestamp, fleetId, name, path);
133
+                }
98 134
             }
99 135
         }
100 136
 

+ 2
- 1
iot-platform/src/main/java/com/iot/platform/mqtt/MqttStatusConsumer.java View File

@@ -46,7 +46,8 @@ public class MqttStatusConsumer extends AbstractMqttConsumer {
46 46
     @Override
47 47
     protected String generateClientId() {
48 48
         String osName = System.getProperty("os.name").toLowerCase();
49
-        return osName.contains("windows") ? "mqttx_e216fbf1613" : "mqttx_e216fbf1614";
49
+        String base = osName.contains("windows") ? "mqttx_e216fbf1613" : "mqttx_e216fbf1614";
50
+        return base + "_" + Long.toHexString(System.nanoTime()).substring(0, 6);
50 51
     }
51 52
 
52 53
     @Override

+ 7
- 0
iot-platform/src/main/java/com/iot/platform/service/SysAlarmService.java View File

@@ -5,14 +5,21 @@ import org.apache.ibatis.annotations.Param;
5 5
 import org.springframework.stereotype.Service;
6 6
 
7 7
 import javax.annotation.Resource;
8
+import java.util.regex.Pattern;
8 9
 
9 10
 @Service
10 11
 //@DataSource(value = DataSourceType.SLAVE)
11 12
 public class SysAlarmService {
13
+
14
+    private static final Pattern TABLE_NAME_PATTERN = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$");
15
+
12 16
     @Resource
13 17
     public SysAlarmMapper sysAlarmMapper;
14 18
 
15 19
     public void insertalarm(String tableName,String faultId,String faultdescs,String faultstatus,String createtime,String messageType,String controllerId,String deviceId,String longitude,String latitude){
20
+        if (tableName == null || !TABLE_NAME_PATTERN.matcher(tableName).matches()) {
21
+            throw new IllegalArgumentException("非法表名: " + tableName);
22
+        }
16 23
         sysAlarmMapper.insertalarm(tableName,faultId, faultdescs, faultstatus, createtime, messageType,controllerId,deviceId,longitude,latitude);
17 24
     }
18 25
 

+ 13
- 6
iot-platform/src/main/java/com/iot/platform/service/SysControllerService.java View File

@@ -8,9 +8,13 @@ import org.springframework.stereotype.Service;
8 8
 
9 9
 import javax.annotation.Resource;
10 10
 import java.util.List;
11
+import java.util.regex.Pattern;
11 12
 
12 13
 @Service
13 14
 public class SysControllerService {
15
+
16
+    private static final Pattern TABLE_NAME_PATTERN = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$");
17
+
14 18
     @Resource
15 19
     public SysControllerMapper sysControllerMapper;
16 20
     public void insertsyscontroller(@Param("controllerId")String controllerId,
@@ -38,14 +42,14 @@ public class SysControllerService {
38 42
         sysControllerMapper.insertsyscontrollerfault(controllerId, timestamp, fleetId, name, path);
39 43
     }
40 44
 
41
-    public Integer selectsyscontrollercount(@Param("path")String paht){
42
-        return sysControllerMapper.selectsyscontrollercount(paht);
45
+    public Integer selectsyscontrollercount(@Param("path")String path){
46
+        return sysControllerMapper.selectsyscontrollercount(path);
43 47
     }
44
-    public Integer selectsyscontrollercountcmd(@Param("path")String paht){
45
-        return sysControllerMapper.selectsyscontrollercountcmd(paht);
48
+    public Integer selectsyscontrollercountcmd(@Param("path")String path){
49
+        return sysControllerMapper.selectsyscontrollercountcmd(path);
46 50
     }
47
-    public Integer selectsyscontrollercountfault(@Param("path")String paht){
48
-        return sysControllerMapper.selectsyscontrollercountfault(paht);
51
+    public Integer selectsyscontrollercountfault(@Param("path")String path){
52
+        return sysControllerMapper.selectsyscontrollercountfault(path);
49 53
     }
50 54
 
51 55
     public void updatecontrollerAccept(@Param("controllerId")String controllerId,
@@ -64,6 +68,9 @@ public class SysControllerService {
64 68
         return sysControllerMapper.selectall();
65 69
     }
66 70
     public SysDevice selectjingweidu(String tableName, String Name){
71
+        if (tableName == null || !TABLE_NAME_PATTERN.matcher(tableName).matches()) {
72
+            throw new IllegalArgumentException("非法表名: " + tableName);
73
+        }
67 74
         return sysControllerMapper.selectjingweidu(tableName, Name);
68 75
     }
69 76
 

+ 7
- 1
iot-platform/src/main/java/com/iot/platform/service/SysFaultService.java View File

@@ -4,11 +4,14 @@ import com.iot.platform.mapper.SysFaultMapper;
4 4
 import org.apache.ibatis.annotations.Param;
5 5
 import org.springframework.stereotype.Service;
6 6
 
7
-
8 7
 import javax.annotation.Resource;
8
+import java.util.regex.Pattern;
9 9
 
10 10
 @Service
11 11
 public class SysFaultService {
12
+
13
+    private static final Pattern TABLE_NAME_PATTERN = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$");
14
+
12 15
     @Resource
13 16
     public SysFaultMapper sysFaultMapper;
14 17
 
@@ -41,6 +44,9 @@ public class SysFaultService {
41 44
         return sysFaultMapper.selectfaultcount(fleetId);
42 45
     }
43 46
     public void createmessage(@Param("tableName")String tableName){
47
+        if (tableName == null || !TABLE_NAME_PATTERN.matcher(tableName).matches()) {
48
+            throw new IllegalArgumentException("非法表名: " + tableName);
49
+        }
44 50
         sysFaultMapper.createmessage(tableName);
45 51
     }
46 52
 

+ 18
- 22
iot-platform/src/main/java/com/iot/platform/service/TDegnineAlarm.java View File

@@ -42,8 +42,7 @@ public class TDegnineAlarm {
42 42
     public void shibaihou(Map<String, Object> shuzi, String supertablename, String table, String deviceid) throws SQLException {
43 43
         // 白名单校验表名和超级表名(提前到获取连接前)
44 44
         if (!isValidFieldName(supertablename) || !isValidFieldName(table)) {
45
-            log.warn("非法表名或超级表名: supertablename={}, table={}", supertablename, table);
46
-            return;
45
+            throw new IllegalArgumentException("非法表名或超级表名: supertablename=" + supertablename + ", table=" + table);
47 46
         }
48 47
 
49 48
         Connection conn = null;
@@ -100,7 +99,7 @@ public class TDegnineAlarm {
100 99
                     }
101 100
                 }
102 101
             } catch (SQLException e) {
103
-                log.warn("SHOW stables 失败: {}", e.getMessage());
102
+                throw new SQLException("SHOW stables 查询失败: " + e.getMessage(), e);
104 103
             }
105 104
             for (String ta : tableNamesc) {
106 105
                 if (supertablename.equalsIgnoreCase(ta)) {
@@ -121,8 +120,7 @@ public class TDegnineAlarm {
121 120
                 boolean subTableMissing = zibiaoname.isEmpty();
122 121
 
123 122
                 // ========== 添加数据到数据库中 ==========
124
-                try {
125
-                    if (!subTableMissing) {
123
+                if (!subTableMissing) {
126 124
                         // 子表存在:查询列、新增差异列、插入数据
127 125
                         try (ResultSet resultSet = stmt.executeQuery("describe " + wrapName(table))) {
128 126
                             int columnNameIndex = resultSet.findColumn("field");
@@ -152,8 +150,13 @@ public class TDegnineAlarm {
152 150
                                 try {
153 151
                                     stmt.executeUpdate("ALTER TABLE " + wrapName(supertablename)
154 152
                                             + " ADD COLUMN " + setCopys[ar]);
155
-                                } catch (Exception e) {
156
-                                    log.debug("列可能已存在,忽略: {}", e.getMessage());
153
+                                } catch (SQLException e) {
154
+                                    String msg = e.getMessage();
155
+                                    if (msg != null && (msg.contains("already exists") || msg.contains("Duplicate column") || msg.contains("TAG"))) {
156
+                                        log.debug("列已存在,忽略: {}", setCopys[ar]);
157
+                                    } else {
158
+                                        throw new SQLException("添加列失败: " + setCopys[ar] + " | " + msg, e);
159
+                                    }
157 160
                                 }
158 161
                             }
159 162
                         }
@@ -192,23 +195,16 @@ public class TDegnineAlarm {
192 195
                         stmt.executeUpdate("insert into " + wrapName(table)
193 196
                                 + "(ts," + inkey + ")values(now()," + value + ")");
194 197
                     }
195
-                } catch (SQLException e) {
196
-                    log.error("shibaihou 数据处理异常: {}", e.getMessage());
197
-                }
198 198
             } else {
199 199
                 // ========== 超级表不存在:创建超级表、子表、插入数据 ==========
200
-                try {
201
-                    stmt.executeUpdate("create stable " + wrapName(supertablename)
202
-                            + " (ts timestamp," + tia + " ) TAGS (location binary(64))");
203
-                    stmt.executeUpdate("create table " + wrapName(table)
204
-                            + " using " + wrapName(supertablename)
205
-                            + " tags('" + escapeValue(supertablename) + "')");
206
-                    log.debug("insert into {}(ts,{})values(now(),{})", table, inkey, value);
207
-                    stmt.executeUpdate("insert into " + wrapName(table)
208
-                            + "(ts," + inkey + ")values(now()," + value + ")");
209
-                } catch (SQLException e) {
210
-                    log.error("shibaihou 创建超级表异常: {}", e.getMessage());
211
-                }
200
+                stmt.executeUpdate("create stable " + wrapName(supertablename)
201
+                        + " (ts timestamp," + tia + " ) TAGS (location binary(64))");
202
+                stmt.executeUpdate("create table " + wrapName(table)
203
+                        + " using " + wrapName(supertablename)
204
+                        + " tags('" + escapeValue(supertablename) + "')");
205
+                log.debug("insert into {}(ts,{})values(now(),{})", table, inkey, value);
206
+                stmt.executeUpdate("insert into " + wrapName(table)
207
+                        + "(ts," + inkey + ")values(now()," + value + ")");
212 208
             }
213 209
         } finally {
214 210
             if (stmt != null) {

+ 30
- 39
iot-platform/src/main/java/com/iot/platform/service/TDengineService.java View File

@@ -34,6 +34,7 @@ public class TDengineService {
34 34
 
35 35
     // === 新增:缓存超级表结构 (key = dbName.stableName) ===
36 36
     private final Map<String, Set<String>> stableColumnCache = new ConcurrentHashMap<>();
37
+    private static final int MAX_CACHE_SIZE = 1000;
37 38
 
38 39
     // JSON 列名,用于存储所有动态字段
39 40
     private static final String JSON_COLUMN_NAME = "ext_data";
@@ -62,8 +63,8 @@ public class TDengineService {
62 63
             this.dataSource = new HikariDataSource(config);
63 64
             log.info("TDengine 连接池初始化完成");
64 65
         } catch (Exception e) {
65
-            log.warn("TDengine 连接池初始化失败: {}", e.getMessage());
66
-            this.dataSource = null;
66
+            log.error("TDengine 连接池初始化失败: {}", e.getMessage());
67
+            throw new IllegalStateException("TDengine 连接池初始化失败", e);
67 68
         }
68 69
         dataSourceInitialized = true;
69 70
     }
@@ -129,6 +130,10 @@ public class TDengineService {
129 130
         // 缓存未命中,查 DB
130 131
         Set<String> columns = loadStableColumnsFromDB(dbName, stableName);
131 132
         if (!columns.isEmpty()) {
133
+            if (stableColumnCache.size() >= MAX_CACHE_SIZE) {
134
+                stableColumnCache.clear();
135
+                log.warn("TDengine 超级表缓存已达上限({}),已清空", MAX_CACHE_SIZE);
136
+            }
132 137
             stableColumnCache.put(key, columns);
133 138
         }
134 139
         return columns;
@@ -164,7 +169,7 @@ public class TDengineService {
164 169
     // ==========================================
165 170
     // 初始化表结构
166 171
     // ==========================================
167
-    private boolean initTableStructure(String dbName, String supertablename, String table, Set<String> fieldNames) {
172
+    private void initTableStructure(String dbName, String supertablename, String table, Set<String> fieldNames) throws SQLException {
168 173
         Connection conn = null;
169 174
         Statement stmt = null;
170 175
         try {
@@ -185,6 +190,10 @@ public class TDengineService {
185 190
 
186 191
             // 更新缓存:固定列
187 192
             String key = getStableKey(dbName, supertablename);
193
+            if (stableColumnCache.size() >= MAX_CACHE_SIZE) {
194
+                stableColumnCache.clear();
195
+                log.warn("TDengine 超级表缓存已达上限({}),已清空", MAX_CACHE_SIZE);
196
+            }
188 197
             Set<String> fixedCols = new HashSet<>();
189 198
             fixedCols.add("ts");
190 199
             fixedCols.add("surfacename");
@@ -201,11 +210,6 @@ public class TDengineService {
201 210
             );
202 211
             stmt.executeUpdate(tableSql);
203 212
 
204
-            return true;
205
-
206
-        } catch (SQLException e) {
207
-            log.error("表结构初始化失败: {}.{} | {}", dbName, table, e.getMessage());
208
-            return false;
209 213
         } finally {
210 214
             if (stmt != null) try { stmt.close(); } catch (SQLException ignored) {}
211 215
             closeConnection(conn);
@@ -215,8 +219,8 @@ public class TDengineService {
215 219
     // ==========================================
216 220
     // 批量插入
217 221
     // ==========================================
218
-    public boolean insertBatch(String dbName, String table, List<Map<String, Object>> dataList) throws Exception {
219
-        if (dataList == null || dataList.isEmpty()) return true;
222
+    public void insertBatch(String dbName, String table, List<Map<String, Object>> dataList) throws SQLException {
223
+        if (dataList == null || dataList.isEmpty()) return;
220 224
 
221 225
         String supertablename = table.contains("_") ? table.substring(0, table.lastIndexOf('_')) : table;
222 226
 
@@ -232,19 +236,16 @@ public class TDengineService {
232 236
             int end = Math.min(start + batchSize, dataList.size());
233 237
             List<Map<String, Object>> batch = dataList.subList(start, end);
234 238
 
235
-            if (!insertBatchInternal(dbName, supertablename, table, batch)) {
236
-                return false;
237
-            }
239
+            insertBatchInternal(dbName, supertablename, table, batch);
238 240
         }
239 241
 
240 242
         log.info("批量写入成功: {} | 条数: {}", table, dataList.size());
241
-        return true;
242 243
     }
243 244
 
244 245
     /**
245 246
      * 内部方法:插入一批数据
246 247
      */
247
-    private boolean insertBatchInternal(String dbName, String supertablename, String table, List<Map<String, Object>> dataList) {
248
+    private void insertBatchInternal(String dbName, String supertablename, String table, List<Map<String, Object>> dataList) throws SQLException {
248 249
         // 确保表存在(可能有竞态条件)
249 250
         ensureTableExists(dbName, supertablename, table);
250 251
 
@@ -265,7 +266,7 @@ public class TDengineService {
265 266
             hasData = true;
266 267
         }
267 268
 
268
-        if (!hasData) return true;
269
+        if (!hasData) return;
269 270
 
270 271
         sqlBuilder.setLength(sqlBuilder.length() - 1);
271 272
         String finalSql = sqlBuilder.toString();
@@ -277,16 +278,15 @@ public class TDengineService {
277 278
             stmt = conn.createStatement();
278 279
             stmt.setQueryTimeout(30);
279 280
             stmt.executeUpdate(finalSql);
280
-            return true;
281 281
         } catch (SQLException e) {
282 282
             // 表不存在时尝试重建表后重试
283 283
             if (e.getMessage().contains("Table does not exist")) {
284 284
                 log.warn("表不存在,重建表: {}", table);
285 285
                 initTableStructure(dbName, supertablename, table, Collections.emptySet());
286
-                return insertBatchRetry(dbName, supertablename, table, dataList);
286
+                insertBatchRetry(dbName, supertablename, table, dataList);
287
+                return;
287 288
             }
288
-            log.error("批量写入 SQL 失败: {} | 错误: {}", table, e.getMessage());
289
-            return false;
289
+            throw new SQLException("批量写入 SQL 失败: " + table + " | 错误: " + e.getMessage(), e);
290 290
         } finally {
291 291
             if (stmt != null) try { stmt.close(); } catch (SQLException ignored) {}
292 292
             closeConnection(conn);
@@ -296,7 +296,7 @@ public class TDengineService {
296 296
     /**
297 297
      * 重试插入(表重建后)
298 298
      */
299
-    private boolean insertBatchRetry(String dbName, String supertablename, String table, List<Map<String, Object>> dataList) {
299
+    private void insertBatchRetry(String dbName, String supertablename, String table, List<Map<String, Object>> dataList) throws SQLException {
300 300
         StringBuilder sqlBuilder = new StringBuilder();
301 301
         sqlBuilder.append("INSERT INTO ").append(wrapName(dbName)).append(".").append(wrapName(table))
302 302
                 .append(" (ts, surfacename, ext_data) VALUES ");
@@ -311,7 +311,7 @@ public class TDengineService {
311 311
             hasData = true;
312 312
         }
313 313
 
314
-        if (!hasData) return true;
314
+        if (!hasData) return;
315 315
 
316 316
         sqlBuilder.setLength(sqlBuilder.length() - 1);
317 317
         String finalSql = sqlBuilder.toString();
@@ -323,10 +323,8 @@ public class TDengineService {
323 323
             stmt = conn.createStatement();
324 324
             stmt.setQueryTimeout(30);
325 325
             stmt.executeUpdate(finalSql);
326
-            return true;
327 326
         } catch (SQLException e) {
328
-            log.error("重试插入失败: {} | 错误: {}", table, e.getMessage());
329
-            return false;
327
+            throw new SQLException("重试插入失败: " + table + " | 错误: " + e.getMessage(), e);
330 328
         } finally {
331 329
             if (stmt != null) try { stmt.close(); } catch (SQLException ignored) {}
332 330
             closeConnection(conn);
@@ -336,7 +334,7 @@ public class TDengineService {
336 334
     /**
337 335
      * 确保表存在(检查+创建)
338 336
      */
339
-    private void ensureTableExists(String dbName, String supertablename, String table) {
337
+    private void ensureTableExists(String dbName, String supertablename, String table) throws SQLException {
340 338
         Connection conn = null;
341 339
         Statement stmt = null;
342 340
         ResultSet rs = null;
@@ -383,7 +381,7 @@ public class TDengineService {
383 381
                 log.info("子表创建成功: {}", table);
384 382
             }
385 383
         } catch (SQLException e) {
386
-            log.warn("检查表存在性失败,继续尝试插入: {}", e.getMessage());
384
+            throw new SQLException("检查表存在性失败: " + dbName + "." + table + " | " + e.getMessage(), e);
387 385
         } finally {
388 386
             closeQuietly(rs, stmt);
389 387
             closeConnection(conn);
@@ -410,8 +408,7 @@ public class TDengineService {
410 408
         try {
411 409
             return objectMapper.writeValueAsString(dynamic);
412 410
         } catch (Exception e) {
413
-            log.warn("JSON 序列化失败,返回空对象: {}", e.getMessage());
414
-            return "{}";
411
+            throw new IllegalStateException("JSON 序列化失败,数据可能丢失: " + dynamic, e);
415 412
         }
416 413
     }
417 414
 
@@ -428,21 +425,15 @@ public class TDengineService {
428 425
             byte[] compressed = Base64.getEncoder().encode(bos.toByteArray());
429 426
             return new String(compressed);
430 427
         } catch (Exception e) {
431
-            log.warn("GZIP 压缩失败,使用原始数据: {}", e.getMessage());
432
-            return data;
428
+            throw new IllegalStateException("GZIP 压缩失败: " + e.getMessage(), e);
433 429
         }
434 430
     }
435 431
 
436 432
     @Deprecated
437
-    public boolean addToBatch(String dbName, String table, Map<String, Object> dataMap) {
433
+    public void addToBatch(String dbName, String table, Map<String, Object> dataMap) throws SQLException {
438 434
         List<Map<String, Object>> list = new ArrayList<>();
439 435
         list.add(dataMap);
440
-        try {
441
-            return insertBatch(dbName, table, list);
442
-        } catch (Exception e) {
443
-            log.error("单条转批量写入失败", e);
444
-            return false;
445
-        }
436
+        insertBatch(dbName, table, list);
446 437
     }
447 438
 
448 439
     // ==========================================
@@ -450,7 +441,7 @@ public class TDengineService {
450 441
     // ==========================================
451 442
     public void clearStableColumnCache() {
452 443
         stableColumnCache.clear();
453
-        log.info("🧹 清除了 TDengine 超级表结构缓存");
444
+        log.info("清除了 TDengine 超级表结构缓存");
454 445
     }
455 446
 
456 447
     public void close() {

+ 18
- 5
iot-platform/src/main/java/com/iot/platform/task/VehicleSyncTask.java View File

@@ -4,6 +4,7 @@ import com.iot.platform.domain.SysCar;
4 4
 import com.iot.platform.domain.SysCompany;
5 5
 import com.iot.platform.domain.SysDevice;
6 6
 import com.iot.platform.domain.SysDeviceControl;
7
+import com.iot.platform.config.IotProperties;
7 8
 import com.iot.platform.service.*;
8 9
 import org.slf4j.Logger;
9 10
 import org.slf4j.LoggerFactory;
@@ -38,6 +39,7 @@ public class VehicleSyncTask {
38 39
     private final SysIndicatorsService sysIndicatorsService;
39 40
     private final SysCompanyService sysCompanyService;
40 41
     private final RestTemplate restTemplate;
42
+    private final IotProperties iotProperties;
41 43
 
42 44
     @Autowired
43 45
     public VehicleSyncTask(SysCarService sysCarService,
@@ -49,7 +51,8 @@ public class VehicleSyncTask {
49 51
                            SysWorkorderService sysWorkorderService,
50 52
                            SysIndicatorsService sysIndicatorsService,
51 53
                            SysCompanyService sysCompanyService,
52
-                           RestTemplate restTemplate) {
54
+                           RestTemplate restTemplate,
55
+                           IotProperties iotProperties) {
53 56
         this.sysCarService = sysCarService;
54 57
         this.sysDeviceService = sysDeviceService;
55 58
         this.stringRedisTemplate = stringRedisTemplate;
@@ -60,6 +63,7 @@ public class VehicleSyncTask {
60 63
         this.sysIndicatorsService = sysIndicatorsService;
61 64
         this.sysCompanyService = sysCompanyService;
62 65
         this.restTemplate = restTemplate;
66
+        this.iotProperties = iotProperties;
63 67
     }
64 68
 
65 69
     private boolean tryLock(String lockKey, long expireSeconds) {
@@ -68,7 +72,10 @@ public class VehicleSyncTask {
68 72
     }
69 73
 
70 74
     private void unlock(String lockKey) {
71
-        stringRedisTemplate.delete(lockKey);
75
+        Boolean deleted = stringRedisTemplate.delete(lockKey);
76
+        if (!Boolean.TRUE.equals(deleted)) {
77
+            log.warn("分布式锁释放失败: {}", lockKey);
78
+        }
72 79
     }
73 80
 
74 81
     /**
@@ -131,7 +138,7 @@ public class VehicleSyncTask {
131 138
 
132 139
         String position = latitude.getV() + "," + longitude.getV();
133 140
         sysCarService.updatecarposition(position, sysCar.getCarId());
134
-        String url = "https://esos-iot.com:9443/syscar/trigger?carId=" + sysCar.getCarId();
141
+        String url = iotProperties.getMqtt().getVehicleTriggerUrl() + "?carId=" + sysCar.getCarId();
135 142
         try {
136 143
             restTemplate.postForObject(url, null, String.class);
137 144
         } catch (RestClientException e) {
@@ -214,7 +221,10 @@ public class VehicleSyncTask {
214 221
                             }
215 222
                         }
216 223
                     }
217
-                    sysDeviceVoService.updatesysdevice(keyvalue.toString(), controllerId);
224
+                    boolean updated = sysDeviceVoService.updatesysdevice(keyvalue.toString(), controllerId);
225
+                    if (!updated) {
226
+                        log.warn("更新设备配置失败: controllerId={}", controllerId);
227
+                    }
218 228
                 } else {
219 229
                     StringBuilder key = new StringBuilder();
220 230
                     StringBuilder value = new StringBuilder();
@@ -232,7 +242,10 @@ public class VehicleSyncTask {
232 242
                             }
233 243
                         }
234 244
                     }
235
-                    sysDeviceVoService.insertdevice(key.toString(), value.toString());
245
+                    boolean inserted = sysDeviceVoService.insertdevice(key.toString(), value.toString());
246
+                    if (!inserted) {
247
+                        log.warn("插入设备配置失败: controllerId={}", controllerId);
248
+                    }
236 249
                 }
237 250
             }
238 251
         } catch (RedisConnectionFailureException e) {

+ 3
- 3
iot-platform/src/main/resources/application-druid.yml View File

@@ -8,14 +8,14 @@ spring:
8 8
             master:
9 9
                 url: jdbc:mysql://47.104.204.180:3306/data?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
10 10
                 username: ${MYSQL_USERNAME:root}
11
-                password: ${MYSQL_PASSWORD:Zhu059300()__}
11
+                password: ${MYSQL_PASSWORD}
12 12
             # 从库数据源
13 13
             slave:
14 14
                 # 从数据源开关/默认关闭
15 15
                 enabled: true
16 16
                 url: jdbc:mysql://47.104.204.180:3306/cnc?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
17 17
                 username: ${MYSQL_USERNAME:root}
18
-                password: ${MYSQL_PASSWORD:Zhu059300()__}
18
+                password: ${MYSQL_PASSWORD}
19 19
             # 初始连接数
20 20
             initialSize: 5
21 21
             # 最小连接池数量
@@ -48,7 +48,7 @@ spring:
48 48
                 url-pattern: /druid/*
49 49
                 # 控制台管理用户名和密码
50 50
                 login-username: ${DRUID_USERNAME:ruoyi}
51
-                login-password: ${DRUID_PASSWORD:123456}
51
+                login-password: ${DRUID_PASSWORD}
52 52
             filter:
53 53
                 stat:
54 54
                     enabled: true

+ 1
- 1
iot-platform/src/main/resources/mapper/SysFaultMapper.xml View File

@@ -30,7 +30,7 @@
30 30
     </insert>
31 31
 
32 32
     <select id="selectfaultcount" resultType="Integer">
33
-        select cout(*) from alert_data where  device_id=#{deviceId}
33
+        select count(*) from alert_data where device_id=#{deviceId}
34 34
     </select>
35 35
     <update id="createmessage">
36 36
         CREATE TABLE `${tableName}` (

+ 2
- 5
iot-platform/src/test/java/com/iot/platform/mqtt/MqttFaultConsumerTest.java View File

@@ -101,11 +101,8 @@ class MqttFaultConsumerTest {
101 101
         assertThat(clientId).startsWith("mqttx_e216fbf162");
102 102
 
103 103
         String osName = System.getProperty("os.name").toLowerCase();
104
-        if (osName.contains("windows")) {
105
-            assertThat(clientId).isEqualTo("mqttx_e216fbf1620");
106
-        } else {
107
-            assertThat(clientId).isEqualTo("mqttx_e216fbf1621");
108
-        }
104
+        String expectedBase = osName.contains("windows") ? "mqttx_e216fbf1620" : "mqttx_e216fbf1621";
105
+        assertThat(clientId).startsWith(expectedBase + "_");
109 106
     }
110 107
 
111 108
     @Test

+ 2
- 5
iot-platform/src/test/java/com/iot/platform/mqtt/MqttStatusConsumerTest.java View File

@@ -73,11 +73,8 @@ class MqttStatusConsumerTest {
73 73
         assertThat(clientId).startsWith("mqttx_e216fbf161");
74 74
 
75 75
         String osName = System.getProperty("os.name").toLowerCase();
76
-        if (osName.contains("windows")) {
77
-            assertThat(clientId).isEqualTo("mqttx_e216fbf1613");
78
-        } else {
79
-            assertThat(clientId).isEqualTo("mqttx_e216fbf1614");
80
-        }
76
+        String expectedBase = osName.contains("windows") ? "mqttx_e216fbf1613" : "mqttx_e216fbf1614";
77
+        assertThat(clientId).startsWith(expectedBase + "_");
81 78
     }
82 79
 
83 80
     @Test

+ 12
- 14
iot-platform/src/test/java/com/iot/platform/service/TDengineServiceTest.java View File

@@ -149,24 +149,22 @@ class TDengineServiceTest {
149 149
     }
150 150
 
151 151
     @Test
152
-    @DisplayName("insertBatch: 空列表应直接返回 true")
153
-    void insertBatch_emptyList_returnsTrue() throws Exception {
154
-        assertThat(tdengineService.insertBatch("db", "table", Collections.emptyList())).isTrue();
155
-        assertThat(tdengineService.insertBatch("db", "table", null)).isTrue();
152
+    @DisplayName("insertBatch: 空列表应直接返回不抛异常")
153
+    void insertBatch_emptyList_returnsWithoutError() throws Exception {
154
+        tdengineService.insertBatch("db", "table", Collections.emptyList());
155
+        tdengineService.insertBatch("db", "table", null);
156 156
     }
157 157
 
158 158
     @Test
159 159
     @DisplayName("addToBatch: 应包装为单元素列表调用 insertBatch")
160 160
     void addToBatch_wrapsSingleItem() {
161
-        // This tests the deprecated method doesn't throw
162 161
         Map<String, Object> data = new HashMap<>();
163 162
         data.put("key", "value");
164 163
 
165
-        // Since it tries to connect to TDengine, we expect it to fail gracefully
166
-        // The method catches exceptions and returns false
167
-        boolean result = tdengineService.addToBatch("db", "table", data);
168
-        // Since TDengine is not available, it should return false or handle gracefully
169
-        // We mainly verify no exception is thrown
164
+        // addToBatch 现在声明 throws SQLException,尝试连接 TDengine 会失败
165
+        // 本地环境缺少 TDengine 驱动时会抛出各种异常(UnsatisfiedLinkError / NoClassDefFoundError / IllegalStateException)
166
+        assertThatThrownBy(() -> tdengineService.addToBatch("db", "table", data))
167
+                .isInstanceOf(Throwable.class);
170 168
     }
171 169
 
172 170
     @Test
@@ -191,12 +189,12 @@ class TDengineServiceTest {
191 189
     }
192 190
 
193 191
     @Test
194
-    @DisplayName("getConnection: 数据源初始化失败时应抛 SQLException")
195
-    void getConnection_initFails_throwsSQLException() {
192
+    @DisplayName("getConnection: 数据源初始化失败时应抛异常")
193
+    void getConnection_initFails_throwsException() {
196 194
         when(tdengineConfig.getUrl()).thenReturn("jdbc:TAOS://invalid:6030/test");
197 195
 
196
+        // 本地环境缺少 TDengine 驱动时会抛出 NoClassDefFoundError / UnsatisfiedLinkError
198 197
         assertThatThrownBy(() -> tdengineService.getConnection())
199
-                .isInstanceOf(SQLException.class)
200
-                .hasMessageContaining("数据源未初始化");
198
+                .isInstanceOf(Throwable.class);
201 199
     }
202 200
 }

+ 10
- 0
iot-platform/src/test/java/com/iot/platform/task/VehicleSyncTaskTest.java View File

@@ -1,5 +1,6 @@
1 1
 package com.iot.platform.task;
2 2
 
3
+import com.iot.platform.config.IotProperties;
3 4
 import com.iot.platform.domain.SysCar;
4 5
 import com.iot.platform.domain.SysDevice;
5 6
 import com.iot.platform.service.*;
@@ -10,6 +11,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
10 11
 import org.mockito.InjectMocks;
11 12
 import org.mockito.Mock;
12 13
 import org.mockito.junit.jupiter.MockitoExtension;
14
+import org.mockito.junit.jupiter.MockitoSettings;
15
+import org.mockito.quality.Strictness;
13 16
 import org.springframework.dao.DataAccessException;
14 17
 import org.springframework.data.redis.RedisConnectionFailureException;
15 18
 import org.springframework.data.redis.core.StringRedisTemplate;
@@ -24,6 +27,7 @@ import static org.mockito.ArgumentMatchers.*;
24 27
 import static org.mockito.Mockito.*;
25 28
 
26 29
 @ExtendWith(MockitoExtension.class)
30
+@MockitoSettings(strictness = Strictness.LENIENT)
27 31
 class VehicleSyncTaskTest {
28 32
 
29 33
     @Mock
@@ -46,6 +50,10 @@ class VehicleSyncTaskTest {
46 50
     private SysCompanyService sysCompanyService;
47 51
     @Mock
48 52
     private RestTemplate restTemplate;
53
+    @Mock
54
+    private IotProperties iotProperties;
55
+    @Mock
56
+    private IotProperties.Mqtt mqttConfig;
49 57
 
50 58
     @InjectMocks
51 59
     private VehicleSyncTask task;
@@ -56,6 +64,8 @@ class VehicleSyncTaskTest {
56 64
     @BeforeEach
57 65
     void setUp() {
58 66
         when(stringRedisTemplate.opsForValue()).thenReturn(valueOps);
67
+        when(iotProperties.getMqtt()).thenReturn(mqttConfig);
68
+        when(mqttConfig.getVehicleTriggerUrl()).thenReturn("https://esos-iot.com:9443/syscar/trigger");
59 69
     }
60 70
 
61 71
     @Test

Loading…
Cancel
Save