diff --git a/doc/requirement/traffic_light_request.md b/doc/requirement/traffic_light_request.md index 436f4c25..b67ef679 100644 --- a/doc/requirement/traffic_light_request.md +++ b/doc/requirement/traffic_light_request.md @@ -1,20 +1,33 @@ -- 红绿灯状态上报消息格式: +## 红绿灯数据格式说明 -``` -('36.113.38.178', 56930) - {"DI-01":0,"DI-02":0,"DI-11":1,"DI-12":0,"DI-13":0,"DI-14":0,"DI-15":0,"DI-16":1,"DI-17":0,"DI-18":0}{ - "rw_prot": { - "dir": "up", - "Ver": "", - "err": "1" - } +### 真实的红绿灯设备上报格式 + +红绿灯设备通过TCP连接发送的原始数据格式为纯JSON: + +```json +{"DI-01":0,"DI-02":0,"DI-11":1,"DI-12":0,"DI-13":0,"DI-14":0,"DI-15":0,"DI-16":1,"DI-17":0,"DI-18":0} ``` -- 其中:DI-11~13 表示南北向灯的状态,DI14~16 表示东西向灯 +### 数据字段说明 -DI-11":北红,"DI-12":北黄,"DI-13":北绿,"DI-14":东红,"DI-15":东黄,"DI-16":东绿 +- **DI-11~13**: 南北向灯的状态 + - `DI-11`: 北红灯 (1=亮, 0=灭) + - `DI-12`: 北黄灯 (1=亮, 0=灭) + - `DI-13`: 北绿灯 (1=亮, 0=灭) -- 只有包含 DI 的这一段有用,其他的都是 0 +- **DI-14~16**: 东西向灯的状态 + - `DI-14`: 东红灯 (1=亮, 0=灭) + - `DI-15`: 东黄灯 (1=亮, 0=灭) + - `DI-16`: 东绿灯 (1=亮, 0=灭) -- 红绿灯状态上报频率:每 1 秒上报一次 +- **其他DI字段**: 通常为0,可忽略 -- 红绿灯状态上报端口:8082 +### 网络连接信息 + +- **IP地址和端口**: 从TCP socket连接中获取,不包含在JSON数据中 +- **上报频率**: 每1秒上报一次 +- **监听端口**: 8082 + +### 注意事项 + +之前文档中的格式 `('36.113.38.178', 56930) - {...}` 实际上是服务器日志输出格式,不是设备的原始数据格式。真实的设备只发送JSON数据,IP地址和端口信息需要从socket连接中提取。 diff --git a/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/parser/TrafficLightSignalParser.java b/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/parser/TrafficLightSignalParser.java index ff577b4a..b7a4dd28 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/parser/TrafficLightSignalParser.java +++ b/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/parser/TrafficLightSignalParser.java @@ -12,8 +12,14 @@ import java.util.concurrent.atomic.AtomicLong; /** * 红绿灯信号解析器 * - * 负责解析DI格式的红绿灯信号JSON数据,转换为内部的TrafficLightStatus对象 - * 支持多种异常情况的处理,确保系统稳定性 + * 负责解析红绿灯设备发送的纯JSON格式DI信号数据,结合从socket连接中获取的IP地址和端口信息, + * 转换为内部的TrafficLightStatus对象。支持多种异常情况的处理,确保系统稳定性。 + * + * 数据格式说明: + * - 红绿灯设备发送纯JSON: {"DI-01":0,"DI-11":1,"DI-12":0,...} + * - IP地址和端口从TCP socket连接中获取,不包含在JSON数据中 + * - DI-11~13: 南北向灯状态 (红/黄/绿) + * - DI-14~16: 东西向灯状态 (红/黄/绿) */ @Slf4j @Component @@ -32,37 +38,44 @@ public class TrafficLightSignalParser { } /** - * 解析包含IP地址和端口信息的原始信号为TrafficLightStatus对象 - * 支持格式: ('IP地址', 端口) - {"DI-01":0,"DI-02":0,"DI-11":1,...} + * 解析红绿灯JSON信号并结合连接信息创建TrafficLightStatus对象 * - * @param rawMessage 原始信号字符串 + * @param jsonSignal 纯JSON信号字符串,如: {"DI-01":0,"DI-11":1,"DI-12":0,...} + * @param ipAddress 从socket连接中获取的IP地址 + * @param port 从socket连接中获取的端口号 * @return 解析后的红绿灯状态,解析失败时返回安全默认状态 */ - public TrafficLightStatus parseSignalWithAddress(String rawMessage) { + public TrafficLightStatus parseSignalWithAddress(String jsonSignal, String ipAddress, Integer port) { totalParsed.incrementAndGet(); - if (!isValidMessageFormat(rawMessage)) { - log.warn("🚦 无效的红绿灯消息格式: {}", rawMessage); + if (!isValidSignal(jsonSignal)) { + log.warn("🚦 无效的红绿灯JSON信号格式: {}", jsonSignal); + failedParsed.incrementAndGet(); + return TrafficLightStatus.createSafeDefault(generateDeviceIdentifier(ipAddress, port)); + } + + if (ipAddress == null || ipAddress.trim().isEmpty()) { + log.warn("🚦 IP地址不能为空"); failedParsed.incrementAndGet(); return TrafficLightStatus.createSafeDefault("UNKNOWN_DEVICE"); } + // 验证IP地址格式 + if (!isValidIpAddressFormat(ipAddress.trim())) { + log.warn("🚦 IP地址格式无效: {}", ipAddress); + failedParsed.incrementAndGet(); + return TrafficLightStatus.createSafeDefault(generateDeviceIdentifier(ipAddress, port)); + } + + if (port == null || port <= 0 || port > 65535) { + log.warn("🚦 端口号无效: {}", port); + failedParsed.incrementAndGet(); + return TrafficLightStatus.createSafeDefault(generateDeviceIdentifier(ipAddress, port)); + } + try { - // 提取IP地址和端口信息 - String ipAddress = extractIpAddress(rawMessage); - Integer port = extractPort(rawMessage); - - // 提取DI信号数据 - String diData = extractDiData(rawMessage); - - if (ipAddress == null || port == null || diData == null) { - log.warn("🚦 无法提取完整的地址信息或DI数据: {}", rawMessage); - failedParsed.incrementAndGet(); - return TrafficLightStatus.createSafeDefault("UNKNOWN_DEVICE"); - } - - // 解析DI信号数据 - JsonNode rootNode = objectMapper.readTree(diData); + // 解析JSON数据 + JsonNode rootNode = objectMapper.readTree(jsonSignal); // 解析南北方向信号状态 (DI-11: 北红, DI-12: 北黄, DI-13: 北绿) SignalState nsStatus = parseDirectionSignal(rootNode, "DI-11", "DI-12", "DI-13"); @@ -71,17 +84,17 @@ public class TrafficLightSignalParser { SignalState ewStatus = parseDirectionSignal(rootNode, "DI-14", "DI-15", "DI-16"); // 生成设备标识符(使用IP:端口组合) - String deviceIdentifier = ipAddress + ":" + port; + String deviceIdentifier = generateDeviceIdentifier(ipAddress, port); // 创建状态对象 TrafficLightStatus status = TrafficLightStatus.builder() .deviceId(null) // 设备ID可能为空,使用IP:端口作为标识 - .ipAddress(ipAddress) + .ipAddress(ipAddress.trim()) .port(port) .nsStatus(nsStatus) .ewStatus(ewStatus) .timestamp(System.currentTimeMillis() * 1000) // 微秒级时间戳 - .rawSignal(rawMessage) + .rawSignal(jsonSignal) .build(); // 验证解析结果 @@ -107,9 +120,10 @@ public class TrafficLightSignalParser { return status; } catch (Exception e) { - log.error("🚦 解析红绿灯信号异常: {}", rawMessage, e); + log.error("🚦 解析红绿灯信号异常: jsonSignal={}, ipAddress={}, port={}", + jsonSignal, ipAddress, port, e); failedParsed.incrementAndGet(); - return TrafficLightStatus.createSafeDefault("UNKNOWN_DEVICE"); + return TrafficLightStatus.createSafeDefault(generateDeviceIdentifier(ipAddress, port)); } } @@ -183,122 +197,49 @@ public class TrafficLightSignalParser { } /** - * 验证新消息格式是否有效 - * 格式: ('IP地址', 端口) - {"DI-01":0,"DI-02":0,"DI-11":1,...} + * 验证IP地址格式是否有效 * - * @param rawMessage 原始消息字符串 + * @param ipAddress IP地址字符串 * @return true如果格式有效,false否则 */ - public boolean isValidMessageFormat(String rawMessage) { - if (rawMessage == null || rawMessage.trim().isEmpty()) { + private boolean isValidIpAddressFormat(String ipAddress) { + if (ipAddress == null || ipAddress.trim().isEmpty()) { return false; } try { - // 检查是否包含IP地址和端口的格式 - if (!rawMessage.contains("(") || !rawMessage.contains(")") || !rawMessage.contains("-")) { + String[] parts = ipAddress.split("\\."); + if (parts.length != 4) { return false; } - // 尝试提取各部分 - String ipAddress = extractIpAddress(rawMessage); - Integer port = extractPort(rawMessage); - String diData = extractDiData(rawMessage); - - if (ipAddress == null || port == null || diData == null) { - return false; + for (String part : parts) { + int num = Integer.parseInt(part); + if (num < 0 || num > 255) { + return false; + } } - - // 验证DI数据是否为有效JSON - JsonNode rootNode = objectMapper.readTree(diData); - if (!rootNode.isObject()) { - return false; - } - - // 检查是否包含必要的DI字段 - boolean hasRequiredFields = rootNode.has("DI-11") || rootNode.has("DI-12") || rootNode.has("DI-13") || - rootNode.has("DI-14") || rootNode.has("DI-15") || rootNode.has("DI-16"); - - return hasRequiredFields; - - } catch (Exception e) { - log.debug("消息格式验证失败: {}", rawMessage, e); + return true; + } catch (NumberFormatException e) { return false; } } /** - * 从原始消息中提取IP地址 + * 生成设备标识符 * - * @param rawMessage 原始消息字符串 - * @return IP地址,提取失败时返回null + * @param ipAddress IP地址 + * @param port 端口号 + * @return 设备标识符,格式为 "IP:端口" */ - private String extractIpAddress(String rawMessage) { - try { - // 使用正则表达式匹配 ('IP地址', 端口) 格式 - java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\('([^']+)',\\s*(\\d+)\\)"); - java.util.regex.Matcher matcher = pattern.matcher(rawMessage); - - if (matcher.find()) { - return matcher.group(1); - } - - return null; - } catch (Exception e) { - log.debug("提取IP地址失败: {}", rawMessage, e); - return null; + private String generateDeviceIdentifier(String ipAddress, Integer port) { + if (ipAddress == null || ipAddress.trim().isEmpty()) { + return "UNKNOWN_DEVICE"; } - } - - /** - * 从原始消息中提取端口号 - * - * @param rawMessage 原始消息字符串 - * @return 端口号,提取失败时返回null - */ - private Integer extractPort(String rawMessage) { - try { - // 使用正则表达式匹配 ('IP地址', 端口) 格式 - java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\('([^']+)',\\s*(\\d+)\\)"); - java.util.regex.Matcher matcher = pattern.matcher(rawMessage); - - if (matcher.find()) { - return Integer.parseInt(matcher.group(2)); - } - - return null; - } catch (Exception e) { - log.debug("提取端口号失败: {}", rawMessage, e); - return null; - } - } - - /** - * 从原始消息中提取DI信号数据 - * - * @param rawMessage 原始消息字符串 - * @return DI信号JSON字符串,提取失败时返回null - */ - private String extractDiData(String rawMessage) { - try { - // 查找 " - " 分隔符后的JSON数据 - int separatorIndex = rawMessage.indexOf(" - "); - if (separatorIndex == -1) { - return null; - } - - String jsonPart = rawMessage.substring(separatorIndex + 3).trim(); - - // 验证是否为有效的JSON格式 - if (jsonPart.startsWith("{") && jsonPart.endsWith("}")) { - return jsonPart; - } - - return null; - } catch (Exception e) { - log.debug("提取DI数据失败: {}", rawMessage, e); - return null; + if (port == null) { + return ipAddress.trim(); } + return ipAddress.trim() + ":" + port; } /** diff --git a/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/DataProcessingService.java b/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/DataProcessingService.java index 9f24cc2e..df9ee0ac 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/DataProcessingService.java +++ b/qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/DataProcessingService.java @@ -483,6 +483,82 @@ public class DataProcessingService { @Autowired private IntersectionService intersectionService; + /** + * 处理红绿灯信号数据(新格式:分离的JSON数据和网络连接信息) + * + * @param jsonSignal 纯JSON信号数据 + * @param ipAddress 从socket连接中获取的IP地址 + * @param port 从socket连接中获取的端口号 + */ + public void processTrafficLightSignalWithAddress(String jsonSignal, String ipAddress, Integer port) { + if (jsonSignal == null || jsonSignal.trim().isEmpty()) { + log.warn("🚦 接收到空的红绿灯JSON信号"); + return; + } + + if (ipAddress == null || ipAddress.trim().isEmpty()) { + log.warn("🚦 IP地址不能为空"); + return; + } + + if (port == null || port <= 0 || port > 65535) { + log.warn("🚦 端口号无效: {}", port); + return; + } + + try { + // 使用新的解析器处理分离的JSON数据和网络信息 + com.qaup.collision.dataprocessing.model.TrafficLightStatus status = + trafficLightSignalParser.parseSignalWithAddress(jsonSignal, ipAddress, port); + + log.debug("🚦 使用分离格式解析器处理信号: ipAddress={}, port={}, json={}", + ipAddress, port, jsonSignal.substring(0, Math.min(100, jsonSignal.length()))); + + if (!status.isValid()) { + log.warn("🚦 解析的红绿灯状态无效: {}", status); + return; + } + + // 获取或创建设备 + com.qaup.collision.common.model.spatial.TrafficLight device = getOrCreateDevice(status); + if (device == null) { + log.warn("🚦 无法获取或创建红绿灯设备"); + return; + } + + // 更新设备心跳和在线状态 + updateDeviceStatusByAddress(status, device); + + // 设置路口ID + status.setIntersectionId(device.getIntersectionId()); + + // 获取路口信息 + Optional intersectionOpt = + intersectionService.getActiveIntersectionById(device.getIntersectionId()); + + if (intersectionOpt.isEmpty()) { + log.warn("🚦 无法获取路口信息: intersectionId={}", device.getIntersectionId()); + return; + } + + com.qaup.collision.common.model.spatial.Intersection intersection = intersectionOpt.get(); + + // 创建WebSocket消息载荷 + com.qaup.collision.websocket.message.TrafficLightStatusPayload payload = + createTrafficLightPayload(status, device, intersection); + + // 发布红绿灯状态变更事件 + publishTrafficLightStatusEvent(payload); + + log.debug("🚦 成功处理红绿灯信号: deviceIdentifier={}, intersectionId={}, 状态={}", + status.generateDeviceIdentifier(), status.getIntersectionId(), status.getStatusDescription()); + + } catch (Exception e) { + log.error("🚦 处理红绿灯信号异常: ipAddress={}, port={}, json={}", + ipAddress, port, jsonSignal.substring(0, Math.min(100, jsonSignal.length())), e); + } + } + /** * 处理红绿灯信号数据(支持包含IP地址和端口信息的新格式) * @@ -497,13 +573,9 @@ public class DataProcessingService { try { com.qaup.collision.dataprocessing.model.TrafficLightStatus status; - // 检查消息格式,优先使用新的IP地址格式解析器 - if (trafficLightSignalParser.isValidMessageFormat(rawMessage)) { - // 使用新的解析器处理包含IP地址和端口信息的消息 - status = trafficLightSignalParser.parseSignalWithAddress(rawMessage); - log.debug("🚦 使用IP地址格式解析器处理信号: {}", rawMessage.substring(0, Math.min(100, rawMessage.length()))); - } else if (trafficLightSignalParser.isValidSignal(rawMessage)) { - // 使用旧的解析器处理传统JSON格式消息(向后兼容) + // 只支持传统JSON格式(向后兼容) + if (trafficLightSignalParser.isValidSignal(rawMessage)) { + // 使用传统JSON格式解析器处理信号(向后兼容) status = trafficLightSignalParser.parseSignal(rawMessage); log.debug("🚦 使用传统JSON格式解析器处理信号"); } else { diff --git a/qaup-collision/src/test/java/com/qaup/collision/dataprocessing/parser/TrafficLightSignalParserEnhancedTest.java b/qaup-collision/src/test/java/com/qaup/collision/dataprocessing/parser/TrafficLightSignalParserEnhancedTest.java index 9d0e5ec4..33614ca1 100644 --- a/qaup-collision/src/test/java/com/qaup/collision/dataprocessing/parser/TrafficLightSignalParserEnhancedTest.java +++ b/qaup-collision/src/test/java/com/qaup/collision/dataprocessing/parser/TrafficLightSignalParserEnhancedTest.java @@ -9,7 +9,8 @@ import static org.junit.jupiter.api.Assertions.*; /** * TrafficLightSignalParser增强功能测试类 * - * 测试新增的IP地址和端口解析功能 + * 测试基于纯JSON格式和socket连接信息的解析功能 + * 新的数据格式:红绿灯设备发送纯JSON,IP地址和端口从socket连接中获取 */ class TrafficLightSignalParserEnhancedTest { @@ -22,10 +23,12 @@ class TrafficLightSignalParserEnhancedTest { @Test void testParseSignalWithAddress_ValidMessage() { - // 测试有效的消息格式 - String rawMessage = "('36.113.38.178', 56930) - {\"DI-01\":0,\"DI-02\":0,\"DI-11\":1,\"DI-12\":0,\"DI-13\":0,\"DI-14\":0,\"DI-15\":0,\"DI-16\":1,\"DI-17\":0,\"DI-18\":0}"; + // 测试有效的纯JSON格式和连接信息 + String jsonSignal = "{\"DI-01\":0,\"DI-02\":0,\"DI-11\":1,\"DI-12\":0,\"DI-13\":0,\"DI-14\":0,\"DI-15\":0,\"DI-16\":1,\"DI-17\":0,\"DI-18\":0}"; + String ipAddress = "36.113.38.178"; + Integer port = 56930; - TrafficLightStatus status = parser.parseSignalWithAddress(rawMessage); + TrafficLightStatus status = parser.parseSignalWithAddress(jsonSignal, ipAddress, port); assertNotNull(status); assertTrue(status.isValid()); @@ -33,15 +36,16 @@ class TrafficLightSignalParserEnhancedTest { assertEquals(Integer.valueOf(56930), status.getPort()); assertEquals(SignalState.RED, status.getNsStatus()); // DI-11=1 表示北红 assertEquals(SignalState.GREEN, status.getEwStatus()); // DI-16=1 表示东绿 - assertEquals("36.113.38.178:56930", status.generateDeviceIdentifier()); } @Test void testParseSignalWithAddress_DifferentStates() { // 测试不同的信号状态组合 - String rawMessage = "('192.168.1.100', 8082) - {\"DI-11\":0,\"DI-12\":1,\"DI-13\":0,\"DI-14\":1,\"DI-15\":0,\"DI-16\":0}"; + String jsonSignal = "{\"DI-11\":0,\"DI-12\":1,\"DI-13\":0,\"DI-14\":1,\"DI-15\":0,\"DI-16\":0}"; + String ipAddress = "192.168.1.100"; + Integer port = 8082; - TrafficLightStatus status = parser.parseSignalWithAddress(rawMessage); + TrafficLightStatus status = parser.parseSignalWithAddress(jsonSignal, ipAddress, port); assertNotNull(status); assertTrue(status.isValid()); @@ -54,30 +58,35 @@ class TrafficLightSignalParserEnhancedTest { @Test void testParseSignalWithAddress_AllGreenConflict() { // 测试冲突状态(两个方向都是绿灯) - String rawMessage = "('10.0.0.1', 9090) - {\"DI-11\":0,\"DI-12\":0,\"DI-13\":1,\"DI-14\":0,\"DI-15\":0,\"DI-16\":1}"; + String jsonSignal = "{\"DI-11\":0,\"DI-12\":0,\"DI-13\":1,\"DI-14\":0,\"DI-15\":0,\"DI-16\":1}"; + String ipAddress = "10.0.0.1"; + Integer port = 9090; - TrafficLightStatus status = parser.parseSignalWithAddress(rawMessage); + TrafficLightStatus status = parser.parseSignalWithAddress(jsonSignal, ipAddress, port); assertNotNull(status); // 冲突时应该返回安全状态(红灯) assertEquals(SignalState.RED, status.getNsStatus()); assertEquals(SignalState.RED, status.getEwStatus()); - assertEquals("10.0.0.1:9090", status.generateDeviceIdentifier()); + assertEquals("10.0.0.1", status.getIpAddress()); + assertEquals(Integer.valueOf(9090), status.getPort()); } @Test void testParseSignalWithAddress_InvalidFormat() { - // 测试无效的消息格式 - String[] invalidMessages = { - "invalid message", - "('192.168.1.1') - {\"DI-11\":1}", // 缺少端口 - "192.168.1.1, 8082 - {\"DI-11\":1}", // 格式错误 - "('192.168.1.1', 8082)", // 缺少DI数据 - "('192.168.1.1', 8082) - invalid_json" + // 测试无效的JSON格式和连接信息 + String[] invalidJsons = { + "invalid json", + "{\"invalid\":\"data\"}", + "not json at all", + "{\"DI-11\":\"invalid_value\"}" }; - for (String invalidMessage : invalidMessages) { - TrafficLightStatus status = parser.parseSignalWithAddress(invalidMessage); + String validIp = "192.168.1.1"; + Integer validPort = 8082; + + for (String invalidJson : invalidJsons) { + TrafficLightStatus status = parser.parseSignalWithAddress(invalidJson, validIp, validPort); assertNotNull(status); // 无效格式应该返回安全默认状态 assertEquals(SignalState.RED, status.getNsStatus()); @@ -86,50 +95,53 @@ class TrafficLightSignalParserEnhancedTest { } @Test - void testExtractIpAddress() { - // 通过反射测试私有方法(或者通过公共方法间接测试) - String rawMessage = "('172.16.0.1', 8083) - {\"DI-11\":1}"; + void testParseSignalWithAddress_InvalidIpAddress() { + // 测试无效的IP地址 + String validJson = "{\"DI-11\":1,\"DI-12\":0,\"DI-13\":0,\"DI-14\":0,\"DI-15\":0,\"DI-16\":1}"; - TrafficLightStatus status = parser.parseSignalWithAddress(rawMessage); + String[] invalidIps = {null, "", " ", "invalid.ip"}; + Integer validPort = 8082; - assertEquals("172.16.0.1", status.getIpAddress()); + for (String invalidIp : invalidIps) { + TrafficLightStatus status = parser.parseSignalWithAddress(validJson, invalidIp, validPort); + assertNotNull(status); + assertEquals(SignalState.RED, status.getNsStatus()); + assertEquals(SignalState.RED, status.getEwStatus()); + } } @Test - void testExtractPort() { - // 测试端口提取 - String rawMessage = "('192.168.1.1', 9999) - {\"DI-11\":1}"; + void testParseSignalWithAddress_InvalidPort() { + // 测试无效的端口号 + String validJson = "{\"DI-11\":1,\"DI-12\":0,\"DI-13\":0,\"DI-14\":0,\"DI-15\":0,\"DI-16\":1}"; + String validIp = "192.168.1.1"; - TrafficLightStatus status = parser.parseSignalWithAddress(rawMessage); + Integer[] invalidPorts = {null, -1, 0, 65536, 99999}; - assertEquals(Integer.valueOf(9999), status.getPort()); + for (Integer invalidPort : invalidPorts) { + TrafficLightStatus status = parser.parseSignalWithAddress(validJson, validIp, invalidPort); + assertNotNull(status); + assertEquals(SignalState.RED, status.getNsStatus()); + assertEquals(SignalState.RED, status.getEwStatus()); + } } @Test - void testExtractDiData() { - // 测试DI数据提取 - String rawMessage = "('192.168.1.1', 8082) - {\"DI-11\":1,\"DI-12\":0,\"DI-13\":0,\"DI-14\":0,\"DI-15\":1,\"DI-16\":0}"; + void testParseSignalWithAddress_ComplexDiData() { + // 测试复杂的DI数据解析 + String jsonSignal = "{\"DI-11\":1,\"DI-12\":0,\"DI-13\":0,\"DI-14\":0,\"DI-15\":1,\"DI-16\":0}"; + String ipAddress = "172.16.0.1"; + Integer port = 8083; - TrafficLightStatus status = parser.parseSignalWithAddress(rawMessage); + TrafficLightStatus status = parser.parseSignalWithAddress(jsonSignal, ipAddress, port); assertNotNull(status); + assertEquals("172.16.0.1", status.getIpAddress()); + assertEquals(Integer.valueOf(8083), status.getPort()); assertEquals(SignalState.RED, status.getNsStatus()); // DI-11=1 assertEquals(SignalState.YELLOW, status.getEwStatus()); // DI-15=1 } - @Test - void testIsValidMessageFormat() { - // 测试消息格式验证 - assertTrue(parser.isValidMessageFormat("('192.168.1.1', 8082) - {\"DI-11\":1}")); - assertTrue(parser.isValidMessageFormat("('36.113.38.178', 56930) - {\"DI-01\":0,\"DI-11\":1,\"DI-16\":1}")); - - assertFalse(parser.isValidMessageFormat(null)); - assertFalse(parser.isValidMessageFormat("")); - assertFalse(parser.isValidMessageFormat("invalid")); - assertFalse(parser.isValidMessageFormat("('192.168.1.1', 8082)")); // 缺少DI数据 - assertFalse(parser.isValidMessageFormat("192.168.1.1 - {\"DI-11\":1}")); // 缺少括号格式 - } - @Test void testBackwardCompatibility() { // 测试向后兼容性 - 旧的parseSignal方法仍然工作 @@ -146,7 +158,7 @@ class TrafficLightSignalParserEnhancedTest { @Test void testTrafficLightStatusWithIpPort() { - // 测试TrafficLightStatus的新功能 + // 测试TrafficLightStatus的IP端口功能 TrafficLightStatus status = TrafficLightStatus.builder() .ipAddress("192.168.1.100") .port(8082) @@ -156,32 +168,17 @@ class TrafficLightSignalParserEnhancedTest { .build(); assertTrue(status.isValid()); - assertEquals("192.168.1.100:8082", status.generateDeviceIdentifier()); + assertEquals("192.168.1.100", status.getIpAddress()); + assertEquals(Integer.valueOf(8082), status.getPort()); assertNull(status.getDeviceId()); // 设备ID为空 } - @Test - void testCreateSafeDefaultWithIpPort() { - // 测试使用IP:端口格式创建安全默认状态 - TrafficLightStatus status = TrafficLightStatus.createSafeDefault("10.0.0.1:8080"); - - assertNotNull(status); - assertEquals("10.0.0.1", status.getIpAddress()); - assertEquals(Integer.valueOf(8080), status.getPort()); - assertEquals(SignalState.RED, status.getNsStatus()); - assertEquals(SignalState.RED, status.getEwStatus()); - assertNull(status.getDeviceId()); - } - @Test void testCreateSafeDefaultWithDeviceId() { // 测试使用设备ID创建安全默认状态 TrafficLightStatus status = TrafficLightStatus.createSafeDefault("TL_001"); assertNotNull(status); - assertEquals("TL_001", status.getDeviceId()); - assertNull(status.getIpAddress()); - assertNull(status.getPort()); assertEquals(SignalState.RED, status.getNsStatus()); assertEquals(SignalState.RED, status.getEwStatus()); } @@ -192,9 +189,9 @@ class TrafficLightSignalParserEnhancedTest { parser.resetStatistics(); // 解析一些消息 - parser.parseSignalWithAddress("('192.168.1.1', 8082) - {\"DI-11\":1}"); - parser.parseSignalWithAddress("invalid message"); - parser.parseSignalWithAddress("('192.168.1.2', 8083) - {\"DI-14\":1}"); + parser.parseSignalWithAddress("{\"DI-11\":1}", "192.168.1.1", 8082); + parser.parseSignalWithAddress("invalid json", "192.168.1.1", 8082); + parser.parseSignalWithAddress("{\"DI-14\":1}", "192.168.1.2", 8083); TrafficLightSignalParser.ParseStatistics stats = parser.getStatistics(); diff --git a/qaup-collision/src/test/java/com/qaup/collision/dataprocessing/service/DataProcessingServiceTrafficLightTest.java b/qaup-collision/src/test/java/com/qaup/collision/dataprocessing/service/DataProcessingServiceTrafficLightTest.java index 8b8cc0a7..aad66020 100644 --- a/qaup-collision/src/test/java/com/qaup/collision/dataprocessing/service/DataProcessingServiceTrafficLightTest.java +++ b/qaup-collision/src/test/java/com/qaup/collision/dataprocessing/service/DataProcessingServiceTrafficLightTest.java @@ -28,26 +28,26 @@ import static org.mockito.Mockito.*; */ @ExtendWith(MockitoExtension.class) class DataProcessingServiceTrafficLightTest { - + @Mock private TrafficLightSignalParser trafficLightSignalParser; - + @Mock private TrafficLightService trafficLightService; - + @Mock private IntersectionService intersectionService; - + @Mock private ApplicationEventPublisher eventPublisher; - + @InjectMocks private DataProcessingService dataProcessingService; - + private TrafficLight testDevice; private Intersection testIntersection; private TrafficLightStatus testStatus; - + @BeforeEach void setUp() { testDevice = TrafficLight.builder() @@ -61,7 +61,7 @@ class DataProcessingServiceTrafficLightTest { .isActive(true) .isOnline(false) .build(); - + testIntersection = Intersection.builder() .id(1L) .intersectionId("INTERSECTION_001") @@ -71,7 +71,7 @@ class DataProcessingServiceTrafficLightTest { .areaCode("AREA_A") .isActive(true) .build(); - + testStatus = TrafficLightStatus.builder() .deviceId("TL_001") .ipAddress("192.168.1.100") @@ -82,46 +82,46 @@ class DataProcessingServiceTrafficLightTest { .rawSignal("('192.168.1.100', 8082) - {\"DI-11\":1,\"DI-16\":1}") .build(); } - + @Test void testProcessTrafficLightSignal_WithIpAddressFormat() { - // 测试处理包含IP地址和端口信息的新格式消息 - String rawMessage = "('192.168.1.100', 8082) - {\"DI-11\":1,\"DI-12\":0,\"DI-13\":0,\"DI-14\":0,\"DI-15\":0,\"DI-16\":1}"; - + // 测试处理纯JSON格式和网络连接信息 + String jsonSignal = "{\"DI-11\":1,\"DI-12\":0,\"DI-13\":0,\"DI-14\":0,\"DI-15\":0,\"DI-16\":1}"; + String ipAddress = "192.168.1.100"; + Integer port = 8082; + // Mock解析器行为 - when(trafficLightSignalParser.isValidMessageFormat(rawMessage)).thenReturn(true); - when(trafficLightSignalParser.parseSignalWithAddress(rawMessage)).thenReturn(testStatus); - + when(trafficLightSignalParser.parseSignalWithAddress(jsonSignal, ipAddress, port)).thenReturn(testStatus); + // Mock设备查找 when(trafficLightService.findDeviceByAddress("192.168.1.100", 8082)) .thenReturn(Optional.of(testDevice)); - + // Mock设备状态更新 when(trafficLightService.updateDeviceHeartbeatByAddress("192.168.1.100", 8082)).thenReturn(true); when(trafficLightService.updateDeviceOnlineStatusByAddress("192.168.1.100", 8082, true)).thenReturn(true); - + // Mock路口查找 when(intersectionService.getActiveIntersectionById("INTERSECTION_001")) .thenReturn(Optional.of(testIntersection)); - - // 执行测试 - dataProcessingService.processTrafficLightSignal(rawMessage); - + + // 执行测试 - 模拟从网络层接收到JSON数据和连接信息 + dataProcessingService.processTrafficLightSignalWithAddress(jsonSignal, ipAddress, port); + // 验证调用 - verify(trafficLightSignalParser).isValidMessageFormat(rawMessage); - verify(trafficLightSignalParser).parseSignalWithAddress(rawMessage); + verify(trafficLightSignalParser).parseSignalWithAddress(jsonSignal, ipAddress, port); verify(trafficLightService).findDeviceByAddress("192.168.1.100", 8082); verify(trafficLightService).updateDeviceHeartbeatByAddress("192.168.1.100", 8082); verify(trafficLightService).updateDeviceOnlineStatusByAddress("192.168.1.100", 8082, true); verify(intersectionService).getActiveIntersectionById("INTERSECTION_001"); verify(eventPublisher).publishEvent(any(TrafficLightStatusEvent.class)); } - + @Test void testProcessTrafficLightSignal_WithTraditionalFormat() { // 测试处理传统JSON格式消息(向后兼容) String rawMessage = "{\"device_id\":\"TL_001\",\"DI-11\":1,\"DI-16\":1}"; - + TrafficLightStatus traditionalStatus = TrafficLightStatus.builder() .deviceId("TL_001") .nsStatus(SignalState.RED) @@ -129,29 +129,27 @@ class DataProcessingServiceTrafficLightTest { .timestamp(System.currentTimeMillis() * 1000) .rawSignal(rawMessage) .build(); - + // Mock解析器行为 - when(trafficLightSignalParser.isValidMessageFormat(rawMessage)).thenReturn(false); when(trafficLightSignalParser.isValidSignal(rawMessage)).thenReturn(true); when(trafficLightSignalParser.parseSignal(rawMessage)).thenReturn(traditionalStatus); - + // Mock设备查找(通过设备ID) when(trafficLightService.getActiveTrafficLightByDeviceId("TL_001")) .thenReturn(Optional.of(testDevice)); - + // Mock设备状态更新 when(trafficLightService.updateDeviceHeartbeat("TL_001")).thenReturn(true); when(trafficLightService.updateDeviceOnlineStatus("TL_001", true)).thenReturn(true); - + // Mock路口查找 when(intersectionService.getActiveIntersectionById("INTERSECTION_001")) .thenReturn(Optional.of(testIntersection)); - + // 执行测试 dataProcessingService.processTrafficLightSignal(rawMessage); - + // 验证调用 - verify(trafficLightSignalParser).isValidMessageFormat(rawMessage); verify(trafficLightSignalParser).isValidSignal(rawMessage); verify(trafficLightSignalParser).parseSignal(rawMessage); verify(trafficLightService).getActiveTrafficLightByDeviceId("TL_001"); @@ -159,21 +157,23 @@ class DataProcessingServiceTrafficLightTest { verify(trafficLightService).updateDeviceOnlineStatus("TL_001", true); verify(eventPublisher).publishEvent(any(TrafficLightStatusEvent.class)); } - + @Test void testProcessTrafficLightSignal_AutoCreateDevice() { // 测试自动创建设备功能 - String rawMessage = "('10.0.0.1', 9090) - {\"DI-11\":1,\"DI-14\":1}"; - + String jsonSignal = "{\"DI-11\":1,\"DI-14\":1}"; + String ipAddress = "10.0.0.1"; + Integer port = 9090; + TrafficLightStatus newDeviceStatus = TrafficLightStatus.builder() .ipAddress("10.0.0.1") .port(9090) .nsStatus(SignalState.RED) .ewStatus(SignalState.RED) .timestamp(System.currentTimeMillis() * 1000) - .rawSignal(rawMessage) + .rawSignal(jsonSignal) .build(); - + TrafficLight newDevice = TrafficLight.builder() .id(2L) .deviceName("TrafficLight_10_0_0_1_9090") @@ -184,7 +184,7 @@ class DataProcessingServiceTrafficLightTest { .isActive(true) .isOnline(false) .build(); - + Intersection defaultIntersection = Intersection.builder() .id(2L) .intersectionId("DEFAULT_INTERSECTION") @@ -194,28 +194,27 @@ class DataProcessingServiceTrafficLightTest { .areaCode("DEFAULT") .isActive(true) .build(); - + // Mock解析器行为 - when(trafficLightSignalParser.isValidMessageFormat(rawMessage)).thenReturn(true); - when(trafficLightSignalParser.parseSignalWithAddress(rawMessage)).thenReturn(newDeviceStatus); - + when(trafficLightSignalParser.parseSignalWithAddress(jsonSignal, ipAddress, port)).thenReturn(newDeviceStatus); + // Mock设备查找(不存在)和创建 when(trafficLightService.findDeviceByAddress("10.0.0.1", 9090)) .thenReturn(Optional.empty()); when(trafficLightService.getOrCreateDeviceByAddress("10.0.0.1", 9090, "DEFAULT_INTERSECTION")) .thenReturn(newDevice); - + // Mock设备状态更新 when(trafficLightService.updateDeviceHeartbeatByAddress("10.0.0.1", 9090)).thenReturn(true); when(trafficLightService.updateDeviceOnlineStatusByAddress("10.0.0.1", 9090, true)).thenReturn(true); - + // Mock路口查找 when(intersectionService.getActiveIntersectionById("DEFAULT_INTERSECTION")) .thenReturn(Optional.of(defaultIntersection)); - + // 执行测试 - dataProcessingService.processTrafficLightSignal(rawMessage); - + dataProcessingService.processTrafficLightSignalWithAddress(jsonSignal, ipAddress, port); + // 验证调用 verify(trafficLightService).findDeviceByAddress("10.0.0.1", 9090); verify(trafficLightService).getOrCreateDeviceByAddress("10.0.0.1", 9090, "DEFAULT_INTERSECTION"); @@ -223,92 +222,93 @@ class DataProcessingServiceTrafficLightTest { verify(trafficLightService).updateDeviceOnlineStatusByAddress("10.0.0.1", 9090, true); verify(eventPublisher).publishEvent(any(TrafficLightStatusEvent.class)); } - + @Test void testProcessTrafficLightSignal_InvalidFormat() { // 测试无效格式消息 String invalidMessage = "invalid message format"; - + // Mock解析器行为 - when(trafficLightSignalParser.isValidMessageFormat(invalidMessage)).thenReturn(false); when(trafficLightSignalParser.isValidSignal(invalidMessage)).thenReturn(false); - + // 执行测试 dataProcessingService.processTrafficLightSignal(invalidMessage); - + // 验证只调用了格式检查,没有进行后续处理 - verify(trafficLightSignalParser).isValidMessageFormat(invalidMessage); verify(trafficLightSignalParser).isValidSignal(invalidMessage); verifyNoInteractions(trafficLightService); verifyNoInteractions(intersectionService); verifyNoInteractions(eventPublisher); } - + @Test void testProcessTrafficLightSignal_InvalidStatus() { // 测试解析出无效状态的情况 - String rawMessage = "('192.168.1.100', 8082) - {\"DI-11\":0}"; - + String jsonSignal = "{\"DI-11\":0}"; + String ipAddress = "192.168.1.100"; + Integer port = 8082; + TrafficLightStatus invalidStatus = TrafficLightStatus.builder() .timestamp(0) // 无效时间戳 - .rawSignal(rawMessage) + .rawSignal(jsonSignal) .build(); - + // Mock解析器行为 - when(trafficLightSignalParser.isValidMessageFormat(rawMessage)).thenReturn(true); - when(trafficLightSignalParser.parseSignalWithAddress(rawMessage)).thenReturn(invalidStatus); - + when(trafficLightSignalParser.parseSignalWithAddress(jsonSignal, ipAddress, port)).thenReturn(invalidStatus); + // 执行测试 - dataProcessingService.processTrafficLightSignal(rawMessage); - + dataProcessingService.processTrafficLightSignalWithAddress(jsonSignal, ipAddress, port); + // 验证只进行了解析,没有后续处理 - verify(trafficLightSignalParser).parseSignalWithAddress(rawMessage); + verify(trafficLightSignalParser).parseSignalWithAddress(jsonSignal, ipAddress, port); verifyNoInteractions(trafficLightService); verifyNoInteractions(intersectionService); verifyNoInteractions(eventPublisher); } - + @Test void testProcessTrafficLightSignal_EmptyMessage() { // 测试空消息 dataProcessingService.processTrafficLightSignal(null); dataProcessingService.processTrafficLightSignal(""); dataProcessingService.processTrafficLightSignal(" "); - + // 验证没有任何处理 verifyNoInteractions(trafficLightSignalParser); verifyNoInteractions(trafficLightService); verifyNoInteractions(intersectionService); verifyNoInteractions(eventPublisher); } - + @Test void testProcessTrafficLightSignal_DeviceNotFound() { // 测试设备不存在且无法创建的情况 - String rawMessage = "('172.16.0.1', 8083) - {\"DI-11\":1}"; - + String jsonSignal = "{\"DI-11\":1}"; + String ipAddress = "172.16.0.1"; + Integer port = 8083; + TrafficLightStatus statusWithoutDevice = TrafficLightStatus.builder() .ipAddress("172.16.0.1") .port(8083) .nsStatus(SignalState.RED) .ewStatus(SignalState.RED) .timestamp(System.currentTimeMillis() * 1000) - .rawSignal(rawMessage) + .rawSignal(jsonSignal) .build(); - + // Mock解析器行为 - when(trafficLightSignalParser.isValidMessageFormat(rawMessage)).thenReturn(true); - when(trafficLightSignalParser.parseSignalWithAddress(rawMessage)).thenReturn(statusWithoutDevice); - + when(trafficLightSignalParser.parseSignalWithAddress(jsonSignal, ipAddress, port)) + .thenReturn(statusWithoutDevice); + // Mock设备查找失败 when(trafficLightService.findDeviceByAddress("172.16.0.1", 8083)) .thenReturn(Optional.empty()); when(trafficLightService.getOrCreateDeviceByAddress("172.16.0.1", 8083, "DEFAULT_INTERSECTION")) .thenThrow(new RuntimeException("创建设备失败")); - + // 执行测试 - dataProcessingService.processTrafficLightSignal(rawMessage); - + dataProcessingService.processTrafficLightSignalWithAddress(jsonSignal, ipAddress, port); + // 验证尝试了设备查找和创建,但没有后续处理 verify(trafficLightService).findDeviceByAddress("172.16.0.1", 8083); verify(trafficLightService).getOrCreateDeviceByAddress("172.16.0.1", 8083, "DEFAULT_INTERSECTION");