删掉红绿灯的端口字段
This commit is contained in:
parent
77f5cdc880
commit
67478b0b98
@ -167,18 +167,8 @@ public interface TrafficLightRepository extends JpaRepository<TrafficLight, Long
|
||||
* @return 匹配的设备列表
|
||||
*/
|
||||
@Query("SELECT t FROM TrafficLight t WHERE t.deviceName LIKE %:name%")
|
||||
List<TrafficLight> findByDeviceNameContaining(@Param("name") String name);
|
||||
|
||||
// ========== IP地址和端口相关查询方法 ==========
|
||||
|
||||
/**
|
||||
* 根据IP地址和端口查找设备
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @param port 端口号
|
||||
* @return 红绿灯设备信息(可选)
|
||||
*/
|
||||
Optional<TrafficLight> findByIpAddressAndPort(String ipAddress, Integer port);
|
||||
List<TrafficLight> findByDeviceNameContaining(@Param("name") String name);
|
||||
|
||||
|
||||
/**
|
||||
* 根据IP地址查找设备列表
|
||||
@ -187,24 +177,7 @@ public interface TrafficLightRepository extends JpaRepository<TrafficLight, Long
|
||||
* @return 该IP地址的设备列表
|
||||
*/
|
||||
List<TrafficLight> findByIpAddress(String ipAddress);
|
||||
|
||||
/**
|
||||
* 检查IP地址和端口组合是否已存在
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @param port 端口号
|
||||
* @return true如果存在,false否则
|
||||
*/
|
||||
boolean existsByIpAddressAndPort(String ipAddress, Integer port);
|
||||
|
||||
/**
|
||||
* 根据IP地址和端口查找激活的设备
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @param port 端口号
|
||||
* @return 激活的红绿灯设备信息(可选)
|
||||
*/
|
||||
Optional<TrafficLight> findByIpAddressAndPortAndIsActiveTrue(String ipAddress, Integer port);
|
||||
|
||||
|
||||
/**
|
||||
* 根据IP地址查找激活的设备列表
|
||||
@ -215,30 +188,28 @@ public interface TrafficLightRepository extends JpaRepository<TrafficLight, Long
|
||||
List<TrafficLight> findByIpAddressAndIsActiveTrue(String ipAddress);
|
||||
|
||||
/**
|
||||
* 更新设备在线状态(基于IP地址和端口)
|
||||
* 更新设备在线状态(基于IP地址)
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @param port 端口号
|
||||
* @param isOnline 是否在线
|
||||
* @return 更新的记录数
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("UPDATE TrafficLight t SET t.isOnline = :isOnline, t.updatedTime = CURRENT_TIMESTAMP WHERE t.ipAddress = :ipAddress AND t.port = :port")
|
||||
int updateOnlineStatusByIpAndPort(@Param("ipAddress") String ipAddress, @Param("port") Integer port, @Param("isOnline") Boolean isOnline);
|
||||
@Query("UPDATE TrafficLight t SET t.isOnline = :isOnline, t.updatedTime = CURRENT_TIMESTAMP WHERE t.ipAddress = :ipAddress")
|
||||
int updateOnlineStatusByIpAddress(@Param("ipAddress") String ipAddress, @Param("isOnline") Boolean isOnline);
|
||||
|
||||
/**
|
||||
* 更新设备心跳时间(基于IP地址和端口)
|
||||
* 根据IP地址更新设备心跳时间(不考虑端口)
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @param port 端口号
|
||||
* @param heartbeatTime 心跳时间
|
||||
* @return 更新的记录数
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("UPDATE TrafficLight t SET t.lastHeartbeat = :heartbeatTime, t.isOnline = true, t.updatedTime = CURRENT_TIMESTAMP WHERE t.ipAddress = :ipAddress AND t.port = :port")
|
||||
int updateHeartbeatByIpAndPort(@Param("ipAddress") String ipAddress, @Param("port") Integer port, @Param("heartbeatTime") LocalDateTime heartbeatTime);
|
||||
@Query("UPDATE TrafficLight t SET t.lastHeartbeat = :heartbeatTime, t.isOnline = true, t.updatedTime = CURRENT_TIMESTAMP WHERE t.ipAddress = :ipAddress")
|
||||
int updateHeartbeatByIpAddress(@Param("ipAddress") String ipAddress, @Param("heartbeatTime") LocalDateTime heartbeatTime);
|
||||
|
||||
/**
|
||||
* 查找指定IP地址范围内的设备
|
||||
@ -265,14 +236,6 @@ public interface TrafficLightRepository extends JpaRepository<TrafficLight, Long
|
||||
@Query("SELECT t FROM TrafficLight t WHERE (t.deviceId IS NULL OR t.deviceId = '') AND t.ipAddress IS NOT NULL AND t.ipAddress != ''")
|
||||
List<TrafficLight> findDevicesWithoutDeviceId();
|
||||
|
||||
/**
|
||||
* 根据端口号查找设备
|
||||
*
|
||||
* @param port 端口号
|
||||
* @return 使用该端口的设备列表
|
||||
*/
|
||||
List<TrafficLight> findByPort(Integer port);
|
||||
|
||||
/**
|
||||
* 统计指定IP地址的设备数量
|
||||
*
|
||||
|
||||
@ -3,8 +3,6 @@ package com.qaup.collision.common.model.spatial;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.AllArgsConstructor;
|
||||
@ -16,121 +14,110 @@ import java.time.LocalDateTime;
|
||||
/**
|
||||
* 红绿灯设备实体类
|
||||
*
|
||||
* 用于存储红绿灯设备的基本信息,包括设备编号、名称、IP地址、端口、关联路口等
|
||||
* 用于存储红绿灯设备的基本信息,包括设备编号、名称、IP地址、关联路口等
|
||||
* 每个设备通过intersection_id与路口建立关联关系
|
||||
* 支持基于IP地址和端口的设备识别,设备编号(device_id)为可选字段
|
||||
* 支持基于IP地址的设备识别,设备编号(device_id)为可选字段
|
||||
*
|
||||
* @version 1.1 - 添加IP地址和端口支持
|
||||
* @version 1.1 - 添加IP地址支持
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "traffic_lights",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(name = "uk_traffic_light_ip_port",
|
||||
columnNames = {"ip_address", "port"})
|
||||
})
|
||||
@Table(name = "traffic_lights", uniqueConstraints = {
|
||||
@UniqueConstraint(name = "uk_traffic_light_ip", columnNames = { "ip_address" })
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TrafficLight {
|
||||
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
|
||||
/**
|
||||
* 红绿灯设备唯一编号(可选)
|
||||
*/
|
||||
@Column(name = "device_id", unique = true, length = 50)
|
||||
private String deviceId;
|
||||
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
*/
|
||||
@Column(name = "device_name", nullable = false, length = 100)
|
||||
private String deviceName;
|
||||
|
||||
|
||||
/**
|
||||
* 设备IP地址
|
||||
*/
|
||||
@Column(name = "ip_address", nullable = false, length = 45)
|
||||
@NotBlank(message = "IP地址不能为空")
|
||||
@Pattern(regexp = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
|
||||
message = "IP地址格式不正确")
|
||||
@Pattern(regexp = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", message = "IP地址格式不正确")
|
||||
@Builder.Default
|
||||
private String ipAddress = "0.0.0.0";
|
||||
|
||||
/**
|
||||
* 设备端口号
|
||||
*/
|
||||
@Column(name = "port")
|
||||
@Min(value = 1, message = "端口号必须大于0")
|
||||
@Max(value = 65535, message = "端口号不能超过65535")
|
||||
private Integer port;
|
||||
|
||||
|
||||
/**
|
||||
* 关联的路口编号
|
||||
*/
|
||||
@Column(name = "intersection_id", nullable = false, length = 50)
|
||||
private String intersectionId;
|
||||
|
||||
|
||||
/**
|
||||
* 设备类型
|
||||
*/
|
||||
@Column(name = "device_type", length = 20)
|
||||
@Builder.Default
|
||||
private String deviceType = "STANDARD";
|
||||
|
||||
|
||||
/**
|
||||
* 制造商
|
||||
*/
|
||||
@Column(name = "manufacturer", length = 50)
|
||||
private String manufacturer;
|
||||
|
||||
|
||||
/**
|
||||
* 型号
|
||||
*/
|
||||
@Column(name = "model", length = 50)
|
||||
private String model;
|
||||
|
||||
|
||||
/**
|
||||
* 安装日期
|
||||
*/
|
||||
@Column(name = "install_date")
|
||||
private LocalDate installDate;
|
||||
|
||||
|
||||
/**
|
||||
* 是否在线
|
||||
*/
|
||||
@Column(name = "is_online")
|
||||
@Builder.Default
|
||||
private Boolean isOnline = false;
|
||||
|
||||
|
||||
/**
|
||||
* 最后心跳时间
|
||||
*/
|
||||
@Column(name = "last_heartbeat")
|
||||
private LocalDateTime lastHeartbeat;
|
||||
|
||||
|
||||
/**
|
||||
* 是否激活
|
||||
*/
|
||||
@Column(name = "is_active")
|
||||
@Builder.Default
|
||||
private Boolean isActive = true;
|
||||
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@Column(name = "created_time")
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@Column(name = "updated_time")
|
||||
private LocalDateTime updatedTime;
|
||||
|
||||
|
||||
/**
|
||||
* 预设置创建和更新时间
|
||||
*/
|
||||
@ -140,19 +127,19 @@ public class TrafficLight {
|
||||
createdTime = now;
|
||||
updatedTime = now;
|
||||
}
|
||||
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedTime = LocalDateTime.now();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新设备心跳时间
|
||||
*/
|
||||
public void updateHeartbeat() {
|
||||
this.lastHeartbeat = LocalDateTime.now();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置设备在线状态
|
||||
*
|
||||
@ -164,7 +151,7 @@ public class TrafficLight {
|
||||
updateHeartbeat();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查设备是否可用(激活且在线)
|
||||
*
|
||||
@ -173,7 +160,7 @@ public class TrafficLight {
|
||||
public boolean isAvailable() {
|
||||
return Boolean.TRUE.equals(isActive) && Boolean.TRUE.equals(isOnline);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查设备心跳是否超时
|
||||
*
|
||||
@ -186,7 +173,7 @@ public class TrafficLight {
|
||||
}
|
||||
return lastHeartbeat.isBefore(LocalDateTime.now().minusMinutes(timeoutMinutes));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取设备状态描述
|
||||
*
|
||||
@ -204,10 +191,10 @@ public class TrafficLight {
|
||||
}
|
||||
return "正常";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成设备标识符
|
||||
* 当deviceId为空时,使用IP地址和端口组合作为标识符
|
||||
* 当deviceId为空时,使用IP地址作为标识符
|
||||
*
|
||||
* @return 设备标识符
|
||||
*/
|
||||
@ -215,15 +202,12 @@ public class TrafficLight {
|
||||
if (deviceId != null && !deviceId.trim().isEmpty()) {
|
||||
return deviceId;
|
||||
}
|
||||
if (ipAddress != null && port != null) {
|
||||
return ipAddress + ":" + port;
|
||||
}
|
||||
if (ipAddress != null) {
|
||||
return ipAddress;
|
||||
}
|
||||
return "UNKNOWN_DEVICE_" + id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查IP地址是否有效
|
||||
*
|
||||
@ -250,31 +234,19 @@ public class TrafficLight {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查端口号是否有效
|
||||
*
|
||||
* @return true如果端口号有效,false否则
|
||||
*/
|
||||
public boolean isValidPort() {
|
||||
return port != null && port > 0 && port <= 65535;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取网络地址信息
|
||||
*
|
||||
* @return IP地址和端口的组合字符串
|
||||
* @return IP地址字符串
|
||||
*/
|
||||
public String getNetworkAddress() {
|
||||
if (ipAddress != null && port != null) {
|
||||
return ipAddress + ":" + port;
|
||||
}
|
||||
if (ipAddress != null) {
|
||||
return ipAddress;
|
||||
}
|
||||
return "未知地址";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查是否为默认IP地址
|
||||
*
|
||||
|
||||
@ -316,13 +316,12 @@ public class TrafficLightTcpServer {
|
||||
|
||||
// 只处理包含DI信号的消息
|
||||
if (message.contains("\"DI-")) {
|
||||
// 提取客户端IP地址和端口
|
||||
// 提取客户端IP地址(不使用端口,因为客户端端口会变化)
|
||||
String ipAddress = clientSocket.getInetAddress().getHostAddress();
|
||||
int port = clientSocket.getPort();
|
||||
|
||||
// 委托给信号处理器处理,传递网络地址信息
|
||||
signalHandler.handleSignal(clientId, message, ipAddress, port);
|
||||
log.debug("🚦 处理DI信号: clientId={}, ip={}, port={}", clientId, ipAddress, port);
|
||||
// 委托给信号处理器处理,传递网络地址信息(端口设为null)
|
||||
signalHandler.handleSignal(clientId, message, ipAddress, null);
|
||||
log.debug("🚦 处理DI信号: clientId={}, ip={}", clientId, ipAddress);
|
||||
} else {
|
||||
log.debug("🚦 忽略非DI信号消息: clientId={}, message={}", clientId, message);
|
||||
}
|
||||
|
||||
@ -61,20 +61,20 @@ public class TrafficLightDataCollector implements TrafficLightSignalHandler {
|
||||
|
||||
// 记录调试日志
|
||||
if (trafficLightProperties.getProcessing().isEnableDebugLog()) {
|
||||
log.debug("处理红绿灯信号: clientId={}, signal={}, ip={}, port={}",
|
||||
clientId, signal, ipAddress, port);
|
||||
log.debug("处理红绿灯信号: clientId={}, signal={}, ip={}",
|
||||
clientId, signal, ipAddress);
|
||||
}
|
||||
|
||||
// 委托给数据处理服务处理,传递网络地址信息
|
||||
dataProcessingService.processTrafficLightSignalWithAddress(signal.trim(), ipAddress, port);
|
||||
// 委托给数据处理服务处理,传递网络地址信息(端口设为固定值8082)
|
||||
dataProcessingService.processTrafficLightSignalWithAddress(signal.trim(), ipAddress, 8082);
|
||||
|
||||
validSignalsProcessed.incrementAndGet();
|
||||
|
||||
log.debug("🚦 成功处理红绿灯信号: clientId={}, ip={}, port={}", clientId, ipAddress, port);
|
||||
log.debug("🚦 成功处理红绿灯信号: clientId={}, ip={}", clientId, ipAddress);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("🚦 处理红绿灯信号异常: clientId={}, signal={}, ip={}, port={}",
|
||||
clientId, signal, ipAddress, port, e);
|
||||
log.error("🚦 处理红绿灯信号异常: clientId={}, signal={}, ip={}",
|
||||
clientId, signal, ipAddress, e);
|
||||
processingErrors.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,40 +50,40 @@ import java.math.RoundingMode;
|
||||
@Slf4j
|
||||
@Service
|
||||
public class DataProcessingService {
|
||||
|
||||
|
||||
@Value("${data.collector.disabled:false}")
|
||||
private boolean collectorDisabled;
|
||||
|
||||
|
||||
@Autowired
|
||||
private SpeedCalculationService speedCalculationService;
|
||||
|
||||
|
||||
@Autowired
|
||||
private PathConflictDetectionService pathConflictDetectionService;
|
||||
|
||||
|
||||
@Autowired
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
|
||||
@Autowired
|
||||
private VehicleDataPersistenceService vehicleDataPersistenceService;
|
||||
|
||||
|
||||
@Autowired
|
||||
private QuapDataAdapter quapDataAdapter;
|
||||
|
||||
|
||||
@Autowired
|
||||
private com.qaup.collision.rule.service.RuleExecutionEngine ruleExecutionEngine;
|
||||
|
||||
|
||||
private final GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 4326);
|
||||
|
||||
|
||||
// 从DataCollectorService获取缓存的引用
|
||||
private Map<String, MovingObject> activeMovingObjectsCache;
|
||||
|
||||
|
||||
/**
|
||||
* 设置活跃对象缓存的引用(由DataCollectorService调用)
|
||||
*/
|
||||
public void setActiveMovingObjectsCache(Map<String, MovingObject> cache) {
|
||||
this.activeMovingObjectsCache = cache;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 周期性数据处理任务
|
||||
*
|
||||
@ -101,35 +101,35 @@ public class DataProcessingService {
|
||||
if (collectorDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (activeMovingObjectsCache == null || activeMovingObjectsCache.isEmpty()) {
|
||||
log.debug("活跃对象缓存为空,跳过数据处理");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
log.info("开始周期性数据处理,活跃对象数量: {}", activeMovingObjectsCache.size());
|
||||
|
||||
|
||||
// 获取所有活跃对象的快照
|
||||
List<MovingObject> currentActiveObjects = new ArrayList<>(activeMovingObjectsCache.values());
|
||||
|
||||
|
||||
// 第一步:计算速度和方向
|
||||
calculateSpeedAndDirectionForAllObjects(currentActiveObjects);
|
||||
|
||||
|
||||
// 第二步:发送WebSocket位置更新消息
|
||||
sendPositionUpdatesForActiveObjects(currentActiveObjects);
|
||||
|
||||
|
||||
// 第三步:执行路径冲突检测
|
||||
pathConflictDetectionService.detectPathConflicts(currentActiveObjects);
|
||||
|
||||
|
||||
// 第四步:执行违规检测
|
||||
performViolationDetection(currentActiveObjects);
|
||||
|
||||
|
||||
// 第五步:保存无人车数据到数据库
|
||||
saveUnmannedVehicleDataPeriodically(currentActiveObjects);
|
||||
|
||||
|
||||
log.info("周期性数据处理完成");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 为所有缓存对象计算速度和方向
|
||||
*/
|
||||
@ -137,28 +137,26 @@ public class DataProcessingService {
|
||||
if (activeObjects.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
log.debug("开始为 {} 个对象计算速度和方向", activeObjects.size());
|
||||
|
||||
|
||||
for (MovingObject movingObject : activeObjects) {
|
||||
try {
|
||||
// 计算实时速度
|
||||
Double calculatedSpeed = speedCalculationService.calculateRealtimeSpeed(
|
||||
movingObject.getObjectId(),
|
||||
movingObject.getCurrentPosition().getY(), // latitude
|
||||
movingObject.getCurrentPosition().getX(), // longitude
|
||||
currentTime
|
||||
);
|
||||
|
||||
movingObject.getObjectId(),
|
||||
movingObject.getCurrentPosition().getY(), // latitude
|
||||
movingObject.getCurrentPosition().getX(), // longitude
|
||||
currentTime);
|
||||
|
||||
// 计算实时方向
|
||||
Double calculatedDirection = speedCalculationService.calculateRealtimeDirection(
|
||||
movingObject.getObjectId(),
|
||||
movingObject.getCurrentPosition().getY(), // latitude
|
||||
movingObject.getCurrentPosition().getX(), // longitude
|
||||
currentTime
|
||||
);
|
||||
|
||||
movingObject.getObjectId(),
|
||||
movingObject.getCurrentPosition().getY(), // latitude
|
||||
movingObject.getCurrentPosition().getX(), // longitude
|
||||
currentTime);
|
||||
|
||||
// 处理速度值 - 如果计算返回null,使用合理默认值
|
||||
Double finalSpeed = calculatedSpeed;
|
||||
if (finalSpeed == null) {
|
||||
@ -176,36 +174,36 @@ public class DataProcessingService {
|
||||
default:
|
||||
finalSpeed = 0.0;
|
||||
}
|
||||
log.debug("对象 {} 速度计算返回null,使用默认值: {} km/h",
|
||||
movingObject.getObjectId(), finalSpeed);
|
||||
log.debug("对象 {} 速度计算返回null,使用默认值: {} km/h",
|
||||
movingObject.getObjectId(), finalSpeed);
|
||||
}
|
||||
|
||||
// 处理方向值 - 如果计算返回null,使用合理默认值
|
||||
|
||||
// 处理方向值 - 如果计算返回null,使用合理默认值
|
||||
Double finalDirection = calculatedDirection;
|
||||
if (finalDirection == null) {
|
||||
finalDirection = 0.0; // 默认朝北
|
||||
log.debug("对象 {} 方向计算返回null,使用默认值: 0.0度",
|
||||
movingObject.getObjectId());
|
||||
log.debug("对象 {} 方向计算返回null,使用默认值: 0.0度",
|
||||
movingObject.getObjectId());
|
||||
}
|
||||
|
||||
|
||||
// 更新MovingObject的速度和方向
|
||||
movingObject.setCurrentSpeed(finalSpeed);
|
||||
movingObject.setCurrentHeading(finalDirection);
|
||||
|
||||
|
||||
// 更新缓存中的对象
|
||||
activeMovingObjectsCache.put(movingObject.getObjectId(), movingObject);
|
||||
|
||||
log.debug("对象 {} 计算完成: 速度={} km/h, 方向={}度",
|
||||
movingObject.getObjectId(), finalSpeed, finalDirection);
|
||||
|
||||
|
||||
log.debug("对象 {} 计算完成: 速度={} km/h, 方向={}度",
|
||||
movingObject.getObjectId(), finalSpeed, finalDirection);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("计算对象 {} 的速度和方向时发生异常", movingObject.getObjectId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
log.debug("所有对象的速度和方向计算完成");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送WebSocket位置更新消息
|
||||
*/
|
||||
@ -214,10 +212,10 @@ public class DataProcessingService {
|
||||
log.debug("没有活跃对象,跳过位置更新消息发送");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
log.debug("发送位置更新消息,对象数量: {}", activeObjects.size());
|
||||
|
||||
|
||||
for (MovingObject movingObject : activeObjects) {
|
||||
try {
|
||||
// 创建位置更新消息负载
|
||||
@ -225,7 +223,7 @@ public class DataProcessingService {
|
||||
.latitude(movingObject.getCurrentPosition().getY())
|
||||
.longitude(movingObject.getCurrentPosition().getX())
|
||||
.build();
|
||||
|
||||
|
||||
// 处理速度值 - 如果为null,使用合理的默认值而不是0
|
||||
Double finalSpeed = movingObject.getCurrentSpeed();
|
||||
if (finalSpeed == null) {
|
||||
@ -245,13 +243,13 @@ public class DataProcessingService {
|
||||
finalSpeed = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 处理方向值 - 如果为null,使用合理的默认值而不是0
|
||||
Double finalHeading = movingObject.getCurrentHeading();
|
||||
if (finalHeading == null) {
|
||||
finalHeading = 0.0; // 默认朝北
|
||||
}
|
||||
|
||||
|
||||
PositionUpdatePayload payload = PositionUpdatePayload.builder()
|
||||
.objectId(movingObject.getObjectId())
|
||||
.objectType(movingObject.getObjectType().name())
|
||||
@ -260,25 +258,25 @@ public class DataProcessingService {
|
||||
.speed(new BigDecimal(finalSpeed).setScale(2, RoundingMode.HALF_UP).doubleValue())
|
||||
.timestamp(currentTime)
|
||||
.build();
|
||||
|
||||
|
||||
// 发布WebSocket事件
|
||||
eventPublisher.publishEvent(new PositionUpdateEvent(payload));
|
||||
|
||||
log.debug("发送位置更新: {} ({}), 位置: ({}, {}), 速度: {}",
|
||||
movingObject.getObjectId(),
|
||||
movingObject.getObjectType(),
|
||||
movingObject.getCurrentPosition().getX(),
|
||||
movingObject.getCurrentPosition().getY(),
|
||||
movingObject.getCurrentSpeed());
|
||||
|
||||
|
||||
log.debug("发送位置更新: {} ({}), 位置: ({}, {}), 速度: {}",
|
||||
movingObject.getObjectId(),
|
||||
movingObject.getObjectType(),
|
||||
movingObject.getCurrentPosition().getX(),
|
||||
movingObject.getCurrentPosition().getY(),
|
||||
movingObject.getCurrentSpeed());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("发送位置更新消息失败: objectId={}", movingObject.getObjectId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
log.info("位置更新消息发送完成,发送数量: {}", activeObjects.size());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 执行违规检测
|
||||
*/
|
||||
@ -301,15 +299,14 @@ public class DataProcessingService {
|
||||
vehicleState.put("speed", vehicleLocation.getSpeed());
|
||||
vehicleState.put("altitude", vehicleLocation.getAltitude());
|
||||
vehicleState.put("heading", vehicleLocation.getHeading());
|
||||
|
||||
|
||||
// 统一调用规则执行引擎进行所有类型的违规检测(速度、区域访问、电子围栏等)
|
||||
ruleExecutionEngine.detectAllViolations(
|
||||
String.valueOf(vehicleLocation.getVehicleId()),
|
||||
vehicleLocation.getVehicleType(),
|
||||
vehicleLocation.getLocation(),
|
||||
vehicleState,
|
||||
vehicleLocation.getTimestamp()
|
||||
);
|
||||
String.valueOf(vehicleLocation.getVehicleId()),
|
||||
vehicleLocation.getVehicleType(),
|
||||
vehicleLocation.getLocation(),
|
||||
vehicleState,
|
||||
vehicleLocation.getTimestamp());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("车辆 {} 违规检测失败: {}", vehicleLocation.getVehicleId(), e.getMessage());
|
||||
@ -319,7 +316,7 @@ public class DataProcessingService {
|
||||
log.debug("没有可用于违规检测的车辆位置数据");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 周期性保存无人车位置数据到数据库
|
||||
*/
|
||||
@ -328,37 +325,37 @@ public class DataProcessingService {
|
||||
log.debug("没有活跃对象,跳过数据保存");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 过滤出无人车对象
|
||||
List<MovingObject> unmannedVehicles = activeObjects.stream()
|
||||
.filter(obj -> obj.getObjectType() == MovingObjectType.UNMANNED_VEHICLE)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
if (unmannedVehicles.isEmpty()) {
|
||||
log.debug("没有无人车数据,跳过数据保存");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
log.debug("开始周期性保存无人车位置数据,数量: {}", unmannedVehicles.size());
|
||||
|
||||
|
||||
// 将MovingObject转换为VehicleLocation并保存
|
||||
List<VehicleLocation> vehicleLocations = new ArrayList<>();
|
||||
for (MovingObject unmannedVehicle : unmannedVehicles) {
|
||||
try {
|
||||
// 创建一个临时的UnmannedVehicle对象用于转换
|
||||
UnmannedVehicle tempUnmannedVehicle = UnmannedVehicle.builder()
|
||||
.objectId(unmannedVehicle.getObjectId())
|
||||
.objectName(unmannedVehicle.getObjectName())
|
||||
.currentPosition(unmannedVehicle.getCurrentPosition())
|
||||
.currentSpeed(unmannedVehicle.getCurrentSpeed())
|
||||
.currentHeading(unmannedVehicle.getCurrentHeading())
|
||||
.altitude(unmannedVehicle.getAltitude())
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.build();
|
||||
|
||||
.objectId(unmannedVehicle.getObjectId())
|
||||
.objectName(unmannedVehicle.getObjectName())
|
||||
.currentPosition(unmannedVehicle.getCurrentPosition())
|
||||
.currentSpeed(unmannedVehicle.getCurrentSpeed())
|
||||
.currentHeading(unmannedVehicle.getCurrentHeading())
|
||||
.altitude(unmannedVehicle.getAltitude())
|
||||
.objectType(MovingObjectType.UNMANNED_VEHICLE)
|
||||
.build();
|
||||
|
||||
// 转换无人车数据为VehicleLocation
|
||||
VehicleLocation vehicleLocation = convertToVehicleLocation(tempUnmannedVehicle);
|
||||
|
||||
|
||||
if (vehicleLocation != null) {
|
||||
vehicleLocations.add(vehicleLocation);
|
||||
}
|
||||
@ -366,7 +363,7 @@ public class DataProcessingService {
|
||||
log.error("转换无人车数据失败: objectId={}", unmannedVehicle.getObjectId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 批量保存到数据库
|
||||
if (!vehicleLocations.isEmpty()) {
|
||||
try {
|
||||
@ -379,7 +376,7 @@ public class DataProcessingService {
|
||||
log.debug("没有有效的无人车位置数据可保存");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将 UnmannedVehicle 转换为 VehicleLocation 实体用于持久化
|
||||
*/
|
||||
@ -390,7 +387,8 @@ public class DataProcessingService {
|
||||
}
|
||||
try {
|
||||
// 根据无人车的 objectId (车牌号/外部ID) 查询其在 sys_vehicle_info 表中的 Long 类型 vehicleId
|
||||
Optional<SysVehicleInfo> vehicleInfoOptional = quapDataAdapter.findVehicleByLicensePlate(vehicle.getObjectId());
|
||||
Optional<SysVehicleInfo> vehicleInfoOptional = quapDataAdapter
|
||||
.findVehicleByLicensePlate(vehicle.getObjectId());
|
||||
|
||||
if (vehicleInfoOptional.isEmpty()) {
|
||||
log.warn("未找到无人车 {} 对应的系统车辆信息,跳过持久化。这可能意味着该无人车未在系统中注册。", vehicle.getObjectId());
|
||||
@ -401,11 +399,13 @@ public class DataProcessingService {
|
||||
|
||||
return VehicleLocation.builder()
|
||||
.vehicleId(sysVehicleInfo.getVehicleId()) // 使用从 sys_vehicle_info 获取的 Long 类型 vehicleId
|
||||
.location(geometryFactory.createPoint(new org.locationtech.jts.geom.Coordinate(vehicle.getCurrentPosition().getX(), vehicle.getCurrentPosition().getY())))
|
||||
.location(geometryFactory.createPoint(new org.locationtech.jts.geom.Coordinate(
|
||||
vehicle.getCurrentPosition().getX(), vehicle.getCurrentPosition().getY())))
|
||||
.altitude(vehicle.getAltitude())
|
||||
.speed(vehicle.getCurrentSpeed() != null ? vehicle.getCurrentSpeed() : 0.0)
|
||||
.heading(vehicle.getCurrentHeading())
|
||||
.timestamp(java.time.LocalDateTime.ofEpochSecond(System.currentTimeMillis() / 1000, 0, java.time.ZoneOffset.UTC))
|
||||
.timestamp(java.time.LocalDateTime.ofEpochSecond(System.currentTimeMillis() / 1000, 0,
|
||||
java.time.ZoneOffset.UTC))
|
||||
.licensePlate(sysVehicleInfo.getLicensePlate()) // 设置运行时属性 licensePlate
|
||||
.vehicleType(MovingObjectType.UNMANNED_VEHICLE) // 从无人车接口获取的数据,直接设置为无人车类型
|
||||
.build();
|
||||
@ -414,7 +414,7 @@ public class DataProcessingService {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建临时VehicleLocation用于违规检测(航空器和机场车辆)
|
||||
* 对于不持久化的MovingObject,生成一个临时的VehicleLocation用于RealTimeViolationDetector
|
||||
@ -431,7 +431,8 @@ public class DataProcessingService {
|
||||
|
||||
if (MovingObjectType.UNMANNED_VEHICLE.equals(movingObject.getObjectType())) {
|
||||
// 对于无人车,其objectId就是车牌号,可以从sys_vehicle_info中查到vehicleId
|
||||
Optional<SysVehicleInfo> vehicleInfoOptional = quapDataAdapter.findVehicleByLicensePlate(movingObject.getObjectId());
|
||||
Optional<SysVehicleInfo> vehicleInfoOptional = quapDataAdapter
|
||||
.findVehicleByLicensePlate(movingObject.getObjectId());
|
||||
if (vehicleInfoOptional.isEmpty()) {
|
||||
log.warn("未找到无人车 {} 对应的系统车辆信息,跳过违规检测。这可能意味着该无人车未在系统中注册。", movingObject.getObjectId());
|
||||
return null;
|
||||
@ -452,15 +453,18 @@ public class DataProcessingService {
|
||||
return null;
|
||||
}
|
||||
|
||||
log.debug("在createTemporaryVehicleLocationForDetection方法中,准备创建VehicleLocation,MovingObject的速度为: {}", movingObject.getCurrentSpeed());
|
||||
log.debug("在createTemporaryVehicleLocationForDetection方法中,准备创建VehicleLocation,MovingObject的速度为: {}",
|
||||
movingObject.getCurrentSpeed());
|
||||
|
||||
return VehicleLocation.builder()
|
||||
.vehicleId(vehicleIdForDetection)
|
||||
.location(geometryFactory.createPoint(new org.locationtech.jts.geom.Coordinate(currentPosition.getX(), currentPosition.getY())))
|
||||
.location(geometryFactory.createPoint(
|
||||
new org.locationtech.jts.geom.Coordinate(currentPosition.getX(), currentPosition.getY())))
|
||||
.altitude(movingObject.getAltitude() != null ? movingObject.getAltitude() : 0.0)
|
||||
.speed(movingObject.getCurrentSpeed() != null ? movingObject.getCurrentSpeed() : 0.0)
|
||||
.heading(movingObject.getCurrentHeading() != null ? movingObject.getCurrentHeading() : 0.0)
|
||||
.timestamp(java.time.LocalDateTime.ofEpochSecond(System.currentTimeMillis() / 1000, 0, java.time.ZoneOffset.UTC)) // 使用当前时间作为检测时间戳
|
||||
.timestamp(java.time.LocalDateTime.ofEpochSecond(System.currentTimeMillis() / 1000, 0,
|
||||
java.time.ZoneOffset.UTC)) // 使用当前时间作为检测时间戳
|
||||
.licensePlate(licensePlateForDetection)
|
||||
.vehicleType(objectTypeForDetection)
|
||||
.build();
|
||||
@ -469,96 +473,96 @@ public class DataProcessingService {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================
|
||||
// 红绿灯信号处理方法(支持IP地址和端口信息)
|
||||
// ============================================
|
||||
|
||||
|
||||
@Autowired
|
||||
private TrafficLightSignalParser trafficLightSignalParser;
|
||||
|
||||
|
||||
@Autowired
|
||||
private TrafficLightService trafficLightService;
|
||||
|
||||
|
||||
@Autowired
|
||||
private IntersectionService intersectionService;
|
||||
|
||||
|
||||
/**
|
||||
* 处理红绿灯信号数据(新格式:分离的JSON数据和网络连接信息)
|
||||
*
|
||||
* @param jsonSignal 纯JSON信号数据
|
||||
* @param ipAddress 从socket连接中获取的IP地址
|
||||
* @param port 从socket连接中获取的端口号
|
||||
* @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())));
|
||||
|
||||
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<com.qaup.collision.common.model.spatial.Intersection> intersectionOpt =
|
||||
intersectionService.getActiveIntersectionById(device.getIntersectionId());
|
||||
|
||||
Optional<com.qaup.collision.common.model.spatial.Intersection> 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);
|
||||
|
||||
com.qaup.collision.websocket.message.TrafficLightStatusPayload payload = createTrafficLightPayload(status,
|
||||
device, intersection);
|
||||
|
||||
// 发布红绿灯状态变更事件
|
||||
publishTrafficLightStatusEvent(payload);
|
||||
|
||||
log.debug("🚦 成功处理红绿灯信号: deviceIdentifier={}, intersectionId={}, 状态={}",
|
||||
|
||||
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);
|
||||
log.error("🚦 处理红绿灯信号异常: ipAddress={}, port={}, json={}",
|
||||
ipAddress, port, jsonSignal.substring(0, Math.min(100, jsonSignal.length())), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理红绿灯信号数据(支持包含IP地址和端口信息的新格式)
|
||||
*
|
||||
@ -569,10 +573,10 @@ public class DataProcessingService {
|
||||
log.warn("🚦 接收到空的红绿灯信号");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
com.qaup.collision.dataprocessing.model.TrafficLightStatus status;
|
||||
|
||||
|
||||
// 只支持传统JSON格式(向后兼容)
|
||||
if (trafficLightSignalParser.isValidSignal(rawMessage)) {
|
||||
// 使用传统JSON格式解析器处理信号(向后兼容)
|
||||
@ -582,51 +586,51 @@ public class DataProcessingService {
|
||||
log.warn("🚦 无法识别的红绿灯信号格式: {}", rawMessage.substring(0, Math.min(100, rawMessage.length())));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
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<com.qaup.collision.common.model.spatial.Intersection> intersectionOpt =
|
||||
intersectionService.getActiveIntersectionById(device.getIntersectionId());
|
||||
|
||||
Optional<com.qaup.collision.common.model.spatial.Intersection> 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);
|
||||
|
||||
com.qaup.collision.websocket.message.TrafficLightStatusPayload payload = createTrafficLightPayload(status,
|
||||
device, intersection);
|
||||
|
||||
// 发布红绿灯状态变更事件
|
||||
publishTrafficLightStatusEvent(payload);
|
||||
|
||||
log.debug("🚦 成功处理红绿灯信号: deviceIdentifier={}, intersectionId={}, 状态={}",
|
||||
|
||||
log.debug("🚦 成功处理红绿灯信号: deviceIdentifier={}, intersectionId={}, 状态={}",
|
||||
status.generateDeviceIdentifier(), status.getIntersectionId(), status.getStatusDescription());
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("🚦 处理红绿灯信号异常: signal={}", rawMessage.substring(0, Math.min(100, rawMessage.length())), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取或创建红绿灯设备
|
||||
* 优先基于IP地址和端口查找,如果不存在则自动创建
|
||||
@ -636,61 +640,60 @@ public class DataProcessingService {
|
||||
*/
|
||||
private com.qaup.collision.common.model.spatial.TrafficLight getOrCreateDevice(
|
||||
com.qaup.collision.dataprocessing.model.TrafficLightStatus status) {
|
||||
|
||||
|
||||
try {
|
||||
// 如果有IP地址和端口信息,优先使用IP地址查找
|
||||
if (status.hasNetworkAddress() && status.getPort() != null) {
|
||||
// 尝试根据IP地址和端口查找现有设备
|
||||
Optional<com.qaup.collision.common.model.spatial.TrafficLight> deviceOpt =
|
||||
trafficLightService.findDeviceByAddress(status.getIpAddress(), status.getPort());
|
||||
|
||||
// 如果有IP地址信息,使用IP地址查找(不使用端口)
|
||||
if (status.hasNetworkAddress()) {
|
||||
// 尝试根据IP地址查找现有设备
|
||||
Optional<com.qaup.collision.common.model.spatial.TrafficLight> deviceOpt = trafficLightService
|
||||
.findDeviceByAddress(status.getIpAddress());
|
||||
|
||||
if (deviceOpt.isPresent()) {
|
||||
log.debug("🚦 找到现有设备: ipAddress={}, port={}", status.getIpAddress(), status.getPort());
|
||||
log.debug("🚦 找到现有设备: ipAddress={}", status.getIpAddress());
|
||||
return deviceOpt.get();
|
||||
}
|
||||
|
||||
|
||||
// 设备不存在,尝试自动创建(需要默认路口ID)
|
||||
String defaultIntersectionId = getDefaultIntersectionId();
|
||||
if (defaultIntersectionId != null) {
|
||||
try {
|
||||
com.qaup.collision.common.model.spatial.TrafficLight newDevice =
|
||||
trafficLightService.getOrCreateDeviceByAddress(
|
||||
status.getIpAddress(),
|
||||
status.getPort(),
|
||||
com.qaup.collision.common.model.spatial.TrafficLight newDevice = trafficLightService
|
||||
.getOrCreateDeviceByAddress(
|
||||
status.getIpAddress(),
|
||||
defaultIntersectionId);
|
||||
|
||||
log.info("🚦 自动创建新设备: ipAddress={}, port={}, intersectionId={}",
|
||||
status.getIpAddress(), status.getPort(), defaultIntersectionId);
|
||||
|
||||
log.info("🚦 自动创建新设备: ipAddress={}, intersectionId={}",
|
||||
status.getIpAddress(), defaultIntersectionId);
|
||||
return newDevice;
|
||||
} catch (Exception e) {
|
||||
log.error("🚦 自动创建设备失败: ipAddress={}, port={}",
|
||||
status.getIpAddress(), status.getPort(), e);
|
||||
log.error("🚦 自动创建设备失败: ipAddress={}",
|
||||
status.getIpAddress(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 如果有设备ID,尝试使用设备ID查找(向后兼容)
|
||||
if (status.hasDeviceId()) {
|
||||
Optional<com.qaup.collision.common.model.spatial.TrafficLight> deviceOpt =
|
||||
trafficLightService.getActiveTrafficLightByDeviceId(status.getDeviceId());
|
||||
|
||||
Optional<com.qaup.collision.common.model.spatial.TrafficLight> deviceOpt = trafficLightService
|
||||
.getActiveTrafficLightByDeviceId(status.getDeviceId());
|
||||
|
||||
if (deviceOpt.isPresent()) {
|
||||
log.debug("🚦 通过设备ID找到设备: deviceId={}", status.getDeviceId());
|
||||
return deviceOpt.get();
|
||||
}
|
||||
}
|
||||
|
||||
log.warn("🚦 无法找到或创建设备: deviceId={}, ipAddress={}, port={}",
|
||||
|
||||
log.warn("🚦 无法找到或创建设备: deviceId={}, ipAddress={}, port={}",
|
||||
status.getDeviceId(), status.getIpAddress(), status.getPort());
|
||||
return null;
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("🚦 获取或创建设备异常: deviceId={}, ipAddress={}, port={}",
|
||||
log.error("🚦 获取或创建设备异常: deviceId={}, ipAddress={}, port={}",
|
||||
status.getDeviceId(), status.getIpAddress(), status.getPort(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取默认路口ID
|
||||
* 可以从配置文件读取或使用固定值
|
||||
@ -709,7 +712,7 @@ public class DataProcessingService {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查设备是否存在且激活(不检查在线状态)
|
||||
*
|
||||
@ -720,14 +723,14 @@ public class DataProcessingService {
|
||||
if (deviceId == null || deviceId.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 只检查设备是否存在且激活,不检查在线状态
|
||||
Optional<com.qaup.collision.common.model.spatial.TrafficLight> deviceOpt =
|
||||
trafficLightService.getActiveTrafficLightByDeviceId(deviceId);
|
||||
|
||||
Optional<com.qaup.collision.common.model.spatial.TrafficLight> deviceOpt = trafficLightService
|
||||
.getActiveTrafficLightByDeviceId(deviceId);
|
||||
|
||||
return deviceOpt.isPresent();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新设备心跳和在线状态(支持基于IP地址和端口的更新)
|
||||
*
|
||||
@ -737,83 +740,49 @@ public class DataProcessingService {
|
||||
private void updateDeviceStatusByAddress(
|
||||
com.qaup.collision.dataprocessing.model.TrafficLightStatus status,
|
||||
com.qaup.collision.common.model.spatial.TrafficLight device) {
|
||||
|
||||
|
||||
try {
|
||||
boolean updated = false;
|
||||
|
||||
// 优先使用IP地址和端口更新
|
||||
if (status.hasNetworkAddress() && status.getPort() != null) {
|
||||
|
||||
// 优先使用IP地址更新(不使用端口)
|
||||
if (status.hasNetworkAddress()) {
|
||||
// 更新设备心跳时间
|
||||
boolean heartbeatUpdated = trafficLightService.updateDeviceHeartbeatByAddress(
|
||||
status.getIpAddress(), status.getPort());
|
||||
|
||||
// 设置设备为在线状态
|
||||
boolean onlineUpdated = trafficLightService.updateDeviceOnlineStatusByAddress(
|
||||
status.getIpAddress(), status.getPort(), true);
|
||||
|
||||
status.getIpAddress());
|
||||
|
||||
// 设置设备为在线状态(使用IP地址)
|
||||
boolean onlineUpdated = trafficLightService.updateDeviceOnlineStatusByIpAddress(
|
||||
status.getIpAddress(), true);
|
||||
|
||||
updated = heartbeatUpdated && onlineUpdated;
|
||||
|
||||
|
||||
if (updated) {
|
||||
log.debug("🚦 通过IP地址更新设备状态成功: ipAddress={}, port={} (在线)",
|
||||
status.getIpAddress(), status.getPort());
|
||||
log.debug("🚦 通过IP地址更新设备状态成功: ipAddress={} (在线)",
|
||||
status.getIpAddress());
|
||||
} else {
|
||||
log.warn("🚦 通过IP地址更新设备状态失败: ipAddress={}, port={}",
|
||||
status.getIpAddress(), status.getPort());
|
||||
log.warn("🚦 通过IP地址更新设备状态失败: ipAddress={}",
|
||||
status.getIpAddress());
|
||||
}
|
||||
}
|
||||
|
||||
// 如果IP地址更新失败,且有设备ID,则尝试使用设备ID更新(向后兼容)
|
||||
if (!updated && status.hasDeviceId()) {
|
||||
// 更新设备心跳时间
|
||||
boolean heartbeatUpdated = trafficLightService.updateDeviceHeartbeat(status.getDeviceId());
|
||||
|
||||
// 设置设备为在线状态
|
||||
boolean onlineUpdated = trafficLightService.updateDeviceOnlineStatus(status.getDeviceId(), true);
|
||||
|
||||
updated = heartbeatUpdated && onlineUpdated;
|
||||
|
||||
if (updated) {
|
||||
log.debug("🚦 通过设备ID更新设备状态成功: deviceId={} (在线)", status.getDeviceId());
|
||||
} else {
|
||||
log.warn("🚦 通过设备ID更新设备状态失败: deviceId={}", status.getDeviceId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 统一使用IP地址进行更新,不再使用设备ID作为备选方案
|
||||
|
||||
if (!updated) {
|
||||
log.warn("🚦 无法更新设备状态: deviceId={}, ipAddress={}, port={}",
|
||||
log.warn("🚦 无法更新设备状态: deviceId={}, ipAddress={}, port={}",
|
||||
status.getDeviceId(), status.getIpAddress(), status.getPort());
|
||||
}
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("🚦 更新设备状态异常: deviceId={}, ipAddress={}, port={}",
|
||||
status.getDeviceId(), status.getIpAddress(), status.getPort(), e);
|
||||
log.error("🚦 更新设备状态异常: deviceId={}, ipAddress={}, port={}",
|
||||
status.getDeviceId(), status.getIpAddress(), status.getPort(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备心跳和在线状态(兼容旧方法)
|
||||
*
|
||||
* @param deviceId 设备编号
|
||||
*/
|
||||
private void updateDeviceStatus(String deviceId) {
|
||||
try {
|
||||
// 更新设备心跳时间
|
||||
trafficLightService.updateDeviceHeartbeat(deviceId);
|
||||
|
||||
// 设置设备为在线状态
|
||||
trafficLightService.updateDeviceOnlineStatus(deviceId, true);
|
||||
|
||||
log.debug("🚦 更新设备状态成功: deviceId={} (在线)", deviceId);
|
||||
} catch (Exception e) {
|
||||
log.warn("🚦 更新设备状态失败: deviceId={}", deviceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建WebSocket消息载荷
|
||||
*
|
||||
* @param status 红绿灯状态
|
||||
* @param device 设备信息
|
||||
* @param status 红绿灯状态
|
||||
* @param device 设备信息
|
||||
* @param intersection 路口信息
|
||||
* @return WebSocket消息载荷
|
||||
*/
|
||||
@ -821,18 +790,18 @@ public class DataProcessingService {
|
||||
com.qaup.collision.dataprocessing.model.TrafficLightStatus status,
|
||||
com.qaup.collision.common.model.spatial.TrafficLight device,
|
||||
com.qaup.collision.common.model.spatial.Intersection intersection) {
|
||||
|
||||
|
||||
// 创建位置信息
|
||||
com.qaup.collision.websocket.message.TrafficLightStatusPayload.Position position =
|
||||
com.qaup.collision.websocket.message.TrafficLightStatusPayload.Position.builder()
|
||||
.latitude(intersection.getLatitude())
|
||||
.longitude(intersection.getLongitude())
|
||||
.build();
|
||||
|
||||
com.qaup.collision.websocket.message.TrafficLightStatusPayload.Position position = com.qaup.collision.websocket.message.TrafficLightStatusPayload.Position
|
||||
.builder()
|
||||
.latitude(intersection.getLatitude())
|
||||
.longitude(intersection.getLongitude())
|
||||
.build();
|
||||
|
||||
// 使用设备的标识符,优先使用设备ID,如果没有则使用生成的标识符
|
||||
String deviceIdentifier = device.getDeviceId() != null ?
|
||||
device.getDeviceId() : device.generateDeviceIdentifier();
|
||||
|
||||
String deviceIdentifier = device.getDeviceId() != null ? device.getDeviceId()
|
||||
: device.generateDeviceIdentifier();
|
||||
|
||||
return com.qaup.collision.websocket.message.TrafficLightStatusPayload.builder()
|
||||
.intersectionId(intersection.getIntersectionId())
|
||||
.intersectionName(intersection.getIntersectionName())
|
||||
@ -846,21 +815,22 @@ public class DataProcessingService {
|
||||
// 如果WebSocket消息支持,可以添加IP地址和端口信息
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发布红绿灯状态变更事件
|
||||
*
|
||||
* @param payload WebSocket消息载荷
|
||||
*/
|
||||
private void publishTrafficLightStatusEvent(com.qaup.collision.websocket.message.TrafficLightStatusPayload payload) {
|
||||
private void publishTrafficLightStatusEvent(
|
||||
com.qaup.collision.websocket.message.TrafficLightStatusPayload payload) {
|
||||
try {
|
||||
com.qaup.collision.websocket.event.TrafficLightStatusEvent event =
|
||||
new com.qaup.collision.websocket.event.TrafficLightStatusEvent(payload);
|
||||
|
||||
com.qaup.collision.websocket.event.TrafficLightStatusEvent event = new com.qaup.collision.websocket.event.TrafficLightStatusEvent(
|
||||
payload);
|
||||
|
||||
eventPublisher.publishEvent(event);
|
||||
|
||||
|
||||
log.debug("🚦 发布红绿灯状态事件成功: intersectionId={}", payload.getIntersectionId());
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("🚦 发布红绿灯状态事件失败: intersectionId={}", payload.getIntersectionId(), e);
|
||||
}
|
||||
|
||||
@ -24,16 +24,16 @@ import java.util.Optional;
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TrafficLightService {
|
||||
|
||||
|
||||
@Autowired
|
||||
private TrafficLightRepository trafficLightRepository;
|
||||
|
||||
|
||||
@Autowired
|
||||
private IntersectionService intersectionService;
|
||||
|
||||
|
||||
// 心跳超时时间(分钟)
|
||||
private static final int HEARTBEAT_TIMEOUT_MINUTES = 5;
|
||||
|
||||
|
||||
/**
|
||||
* 根据设备编号获取红绿灯设备信息
|
||||
*
|
||||
@ -45,7 +45,7 @@ public class TrafficLightService {
|
||||
log.warn("设备编号不能为空");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
return trafficLightRepository.findByDeviceId(deviceId.trim());
|
||||
} catch (Exception e) {
|
||||
@ -53,7 +53,7 @@ public class TrafficLightService {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据设备编号获取激活的红绿灯设备信息
|
||||
*
|
||||
@ -65,7 +65,7 @@ public class TrafficLightService {
|
||||
log.warn("设备编号不能为空");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
return trafficLightRepository.findByDeviceIdAndIsActive(deviceId.trim(), true);
|
||||
} catch (Exception e) {
|
||||
@ -73,7 +73,7 @@ public class TrafficLightService {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据路口编号获取红绿灯设备列表
|
||||
*
|
||||
@ -85,7 +85,7 @@ public class TrafficLightService {
|
||||
log.warn("路口编号不能为空");
|
||||
return List.of();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
List<TrafficLight> devices = trafficLightRepository.findByIntersectionId(intersectionId.trim());
|
||||
log.debug("路口 {} 查询到 {} 个红绿灯设备", intersectionId, devices.size());
|
||||
@ -95,7 +95,7 @@ public class TrafficLightService {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据路口编号获取激活的红绿灯设备列表
|
||||
*
|
||||
@ -107,9 +107,10 @@ public class TrafficLightService {
|
||||
log.warn("路口编号不能为空");
|
||||
return List.of();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
List<TrafficLight> devices = trafficLightRepository.findByIntersectionIdAndIsActiveTrue(intersectionId.trim());
|
||||
List<TrafficLight> devices = trafficLightRepository
|
||||
.findByIntersectionIdAndIsActiveTrue(intersectionId.trim());
|
||||
log.debug("路口 {} 查询到 {} 个激活的红绿灯设备", intersectionId, devices.size());
|
||||
return devices;
|
||||
} catch (Exception e) {
|
||||
@ -117,7 +118,7 @@ public class TrafficLightService {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取所有在线的设备
|
||||
*
|
||||
@ -133,7 +134,7 @@ public class TrafficLightService {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取所有激活且在线的设备
|
||||
*
|
||||
@ -149,34 +150,34 @@ public class TrafficLightService {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加新的红绿灯设备
|
||||
*
|
||||
* @param trafficLight 设备信息
|
||||
* @return 保存后的设备信息
|
||||
* @throws IllegalArgumentException 如果设备信息无效
|
||||
* @throws IllegalStateException 如果设备编号已存在或路口不存在
|
||||
* @throws IllegalStateException 如果设备编号已存在或路口不存在
|
||||
*/
|
||||
@Transactional
|
||||
public TrafficLight addTrafficLight(TrafficLight trafficLight) {
|
||||
if (trafficLight == null) {
|
||||
throw new IllegalArgumentException("设备信息不能为空");
|
||||
}
|
||||
|
||||
|
||||
// 验证必要字段
|
||||
validateTrafficLightData(trafficLight);
|
||||
|
||||
|
||||
// 检查设备编号是否已存在
|
||||
if (trafficLightRepository.existsByDeviceId(trafficLight.getDeviceId())) {
|
||||
throw new IllegalStateException("设备编号已存在: " + trafficLight.getDeviceId());
|
||||
}
|
||||
|
||||
|
||||
// 检查关联的路口是否存在
|
||||
if (!intersectionService.getActiveIntersectionById(trafficLight.getIntersectionId()).isPresent()) {
|
||||
throw new IllegalStateException("关联的路口不存在或未激活: " + trafficLight.getIntersectionId());
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// 设置默认值
|
||||
if (trafficLight.getIsActive() == null) {
|
||||
@ -188,58 +189,58 @@ public class TrafficLightService {
|
||||
if (trafficLight.getDeviceType() == null) {
|
||||
trafficLight.setDeviceType("STANDARD");
|
||||
}
|
||||
|
||||
|
||||
TrafficLight savedDevice = trafficLightRepository.save(trafficLight);
|
||||
log.info("🚦 成功添加红绿灯设备: deviceId={}, name={}, intersectionId={}",
|
||||
log.info("🚦 成功添加红绿灯设备: deviceId={}, name={}, intersectionId={}",
|
||||
savedDevice.getDeviceId(), savedDevice.getDeviceName(), savedDevice.getIntersectionId());
|
||||
|
||||
|
||||
return savedDevice;
|
||||
} catch (Exception e) {
|
||||
log.error("添加红绿灯设备异常: deviceId={}", trafficLight.getDeviceId(), e);
|
||||
throw new RuntimeException("添加设备失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新红绿灯设备信息
|
||||
*
|
||||
* @param trafficLight 设备信息
|
||||
* @return 更新后的设备信息
|
||||
* @throws IllegalArgumentException 如果设备信息无效
|
||||
* @throws IllegalStateException 如果设备不存在
|
||||
* @throws IllegalStateException 如果设备不存在
|
||||
*/
|
||||
@Transactional
|
||||
public TrafficLight updateTrafficLight(TrafficLight trafficLight) {
|
||||
if (trafficLight == null) {
|
||||
throw new IllegalArgumentException("设备信息不能为空");
|
||||
}
|
||||
|
||||
|
||||
// 验证必要字段
|
||||
validateTrafficLightData(trafficLight);
|
||||
|
||||
|
||||
// 检查设备是否存在
|
||||
Optional<TrafficLight> existingDevice = trafficLightRepository.findByDeviceId(trafficLight.getDeviceId());
|
||||
if (existingDevice.isEmpty()) {
|
||||
throw new IllegalStateException("设备不存在: " + trafficLight.getDeviceId());
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// 保留原有的ID和创建时间
|
||||
TrafficLight existing = existingDevice.get();
|
||||
trafficLight.setId(existing.getId());
|
||||
trafficLight.setCreatedTime(existing.getCreatedTime());
|
||||
|
||||
|
||||
TrafficLight updatedDevice = trafficLightRepository.save(trafficLight);
|
||||
log.info("🚦 成功更新红绿灯设备: deviceId={}, name={}",
|
||||
log.info("🚦 成功更新红绿灯设备: deviceId={}, name={}",
|
||||
updatedDevice.getDeviceId(), updatedDevice.getDeviceName());
|
||||
|
||||
|
||||
return updatedDevice;
|
||||
} catch (Exception e) {
|
||||
log.error("更新红绿灯设备异常: deviceId={}", trafficLight.getDeviceId(), e);
|
||||
throw new RuntimeException("更新设备失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新设备在线状态
|
||||
*
|
||||
@ -253,7 +254,7 @@ public class TrafficLightService {
|
||||
log.warn("设备编号不能为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
int updatedCount = trafficLightRepository.updateOnlineStatus(deviceId.trim(), isOnline);
|
||||
if (updatedCount > 0) {
|
||||
@ -268,7 +269,7 @@ public class TrafficLightService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新设备心跳时间
|
||||
*
|
||||
@ -281,7 +282,7 @@ public class TrafficLightService {
|
||||
log.warn("设备编号不能为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
int updatedCount = trafficLightRepository.updateHeartbeat(deviceId.trim(), now);
|
||||
@ -297,12 +298,12 @@ public class TrafficLightService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 激活或停用设备
|
||||
*
|
||||
* @param deviceId 设备编号
|
||||
* @param active 是否激活
|
||||
* @param active 是否激活
|
||||
* @return 更新后的设备信息(可选)
|
||||
*/
|
||||
@Transactional
|
||||
@ -311,27 +312,27 @@ public class TrafficLightService {
|
||||
log.warn("设备编号不能为空");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
Optional<TrafficLight> deviceOpt = trafficLightRepository.findByDeviceId(deviceId.trim());
|
||||
if (deviceOpt.isEmpty()) {
|
||||
log.warn("设备不存在: deviceId={}", deviceId);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
TrafficLight device = deviceOpt.get();
|
||||
device.setIsActive(active);
|
||||
|
||||
|
||||
TrafficLight updatedDevice = trafficLightRepository.save(device);
|
||||
log.info("🚦 成功{}设备: deviceId={}", active ? "激活" : "停用", deviceId);
|
||||
|
||||
|
||||
return Optional.of(updatedDevice);
|
||||
} catch (Exception e) {
|
||||
log.error("设置设备激活状态异常: deviceId={}, active={}", deviceId, active, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查设备是否可用(激活且在线)
|
||||
*
|
||||
@ -342,7 +343,7 @@ public class TrafficLightService {
|
||||
Optional<TrafficLight> deviceOpt = getActiveTrafficLightByDeviceId(deviceId);
|
||||
return deviceOpt.map(TrafficLight::isAvailable).orElse(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 定时检查心跳超时的设备并设置为离线状态
|
||||
* 每分钟执行一次
|
||||
@ -352,7 +353,7 @@ public class TrafficLightService {
|
||||
try {
|
||||
LocalDateTime timeoutTime = LocalDateTime.now().minusMinutes(HEARTBEAT_TIMEOUT_MINUTES);
|
||||
int offlineCount = trafficLightRepository.setTimeoutDevicesOffline(timeoutTime);
|
||||
|
||||
|
||||
if (offlineCount > 0) {
|
||||
log.info("🚦 检测到 {} 个设备心跳超时,已设置为离线状态", offlineCount);
|
||||
}
|
||||
@ -360,7 +361,7 @@ public class TrafficLightService {
|
||||
log.error("检查设备心跳超时异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据制造商查找设备
|
||||
*
|
||||
@ -372,7 +373,7 @@ public class TrafficLightService {
|
||||
log.warn("制造商名称不能为空");
|
||||
return List.of();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
List<TrafficLight> devices = trafficLightRepository.findByManufacturer(manufacturer.trim());
|
||||
log.debug("制造商 {} 查询到 {} 个设备", manufacturer, devices.size());
|
||||
@ -382,7 +383,7 @@ public class TrafficLightService {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据设备名称模糊查询
|
||||
*
|
||||
@ -394,7 +395,7 @@ public class TrafficLightService {
|
||||
log.warn("查询名称不能为空");
|
||||
return List.of();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
List<TrafficLight> devices = trafficLightRepository.findByDeviceNameContaining(name.trim());
|
||||
log.debug("名称模糊查询 '{}' 找到 {} 个设备", name, devices.size());
|
||||
@ -404,103 +405,69 @@ public class TrafficLightService {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== IP地址和端口相关方法 ==========
|
||||
|
||||
|
||||
/**
|
||||
* 根据IP地址和端口获取或创建设备
|
||||
* 如果设备不存在,则自动创建一个新设备
|
||||
* 根据IP地址获取或创建设备(不使用端口,用于红绿灯设备识别)
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @param port 端口号
|
||||
* @param ipAddress IP地址
|
||||
* @param intersectionId 路口编号
|
||||
* @return 设备信息
|
||||
*/
|
||||
@Transactional
|
||||
public TrafficLight getOrCreateDeviceByAddress(String ipAddress, Integer port, String intersectionId) {
|
||||
public TrafficLight getOrCreateDeviceByAddress(String ipAddress, String intersectionId) {
|
||||
if (ipAddress == null || ipAddress.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("IP地址不能为空");
|
||||
}
|
||||
if (port == null || port <= 0 || port > 65535) {
|
||||
throw new IllegalArgumentException("端口号无效");
|
||||
}
|
||||
if (intersectionId == null || intersectionId.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("路口编号不能为空");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// 首先尝试查找现有设备
|
||||
Optional<TrafficLight> existingDevice = trafficLightRepository.findByIpAddressAndPort(ipAddress.trim(), port);
|
||||
|
||||
Optional<TrafficLight> existingDevice = findDeviceByAddress(ipAddress);
|
||||
|
||||
if (existingDevice.isPresent()) {
|
||||
TrafficLight device = existingDevice.get();
|
||||
log.debug("找到现有设备: ipAddress={}, port={}, deviceId={}",
|
||||
ipAddress, port, device.getDeviceId());
|
||||
log.debug("找到现有设备: ipAddress={}, deviceId={}", ipAddress, device.getDeviceId());
|
||||
return device;
|
||||
}
|
||||
|
||||
// 设备不存在,创建新设备
|
||||
String deviceName = generateDefaultDeviceName(ipAddress, port);
|
||||
|
||||
|
||||
// 设备不存在,创建新设备(端口设为默认的8082)
|
||||
String deviceName = generateDefaultDeviceName(ipAddress, 8082);
|
||||
|
||||
TrafficLight newDevice = TrafficLight.builder()
|
||||
.deviceName(deviceName)
|
||||
.ipAddress(ipAddress.trim())
|
||||
.port(port)
|
||||
.intersectionId(intersectionId.trim())
|
||||
.deviceType("STANDARD")
|
||||
.isActive(true)
|
||||
.isOnline(false)
|
||||
.build();
|
||||
|
||||
|
||||
TrafficLight savedDevice = trafficLightRepository.save(newDevice);
|
||||
log.info("🚦 自动创建新的红绿灯设备: ipAddress={}, port={}, deviceName={}, intersectionId={}",
|
||||
ipAddress, port, deviceName, intersectionId);
|
||||
|
||||
log.info("🚦 自动创建新的红绿灯设备: ipAddress={}, deviceName={}, intersectionId={}",
|
||||
ipAddress, deviceName, intersectionId);
|
||||
|
||||
return savedDevice;
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("根据IP地址和端口获取或创建设备异常: ipAddress={}, port={}", ipAddress, port, e);
|
||||
log.error("根据IP地址获取或创建设备异常: ipAddress={}", ipAddress, e);
|
||||
throw new RuntimeException("获取或创建设备失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据IP地址和端口查找设备
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @param port 端口号
|
||||
* @return 设备信息(可选)
|
||||
*/
|
||||
public Optional<TrafficLight> findDeviceByAddress(String ipAddress, Integer port) {
|
||||
if (ipAddress == null || ipAddress.trim().isEmpty()) {
|
||||
log.warn("IP地址不能为空");
|
||||
return Optional.empty();
|
||||
}
|
||||
if (port == null) {
|
||||
log.warn("端口号不能为空");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
return trafficLightRepository.findByIpAddressAndPort(ipAddress.trim(), port);
|
||||
} catch (Exception e) {
|
||||
log.error("根据IP地址和端口查找设备异常: ipAddress={}, port={}", ipAddress, port, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据IP地址查找设备列表
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @return 该IP地址的设备列表
|
||||
*/
|
||||
public List<TrafficLight> findDevicesByIpAddress(String ipAddress) {
|
||||
public List<TrafficLight> findDevicesByAddress(String ipAddress) {
|
||||
if (ipAddress == null || ipAddress.trim().isEmpty()) {
|
||||
log.warn("IP地址不能为空");
|
||||
return List.of();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
List<TrafficLight> devices = trafficLightRepository.findByIpAddress(ipAddress.trim());
|
||||
log.debug("IP地址 {} 查询到 {} 个设备", ipAddress, devices.size());
|
||||
@ -510,87 +477,118 @@ public class TrafficLightService {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新设备心跳时间(基于IP地址和端口)
|
||||
* 根据IP地址查找单个设备(不考虑端口,用于红绿灯设备识别)
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @return 设备信息(可选)
|
||||
*/
|
||||
public Optional<TrafficLight> findDeviceByAddress(String ipAddress) {
|
||||
if (ipAddress == null || ipAddress.trim().isEmpty()) {
|
||||
log.warn("IP地址不能为空");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
List<TrafficLight> devices = trafficLightRepository.findByIpAddress(ipAddress.trim());
|
||||
if (devices.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// 如果有多个设备,返回第一个激活的设备
|
||||
Optional<TrafficLight> activeDevice = devices.stream()
|
||||
.filter(TrafficLight::getIsActive)
|
||||
.findFirst();
|
||||
|
||||
if (activeDevice.isPresent()) {
|
||||
log.debug("找到激活的设备: ipAddress={}, deviceId={}", ipAddress, activeDevice.get().getDeviceId());
|
||||
return activeDevice;
|
||||
}
|
||||
|
||||
// 如果没有激活的设备,返回第一个设备
|
||||
TrafficLight firstDevice = devices.get(0);
|
||||
log.debug("返回第一个设备: ipAddress={}, deviceId={}", ipAddress, firstDevice.getDeviceId());
|
||||
return Optional.of(firstDevice);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("根据IP地址查找设备异常: ipAddress={}", ipAddress, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据IP地址更新设备心跳时间(不考虑端口,用于红绿灯设备识别)
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @param port 端口号
|
||||
* @return 是否更新成功
|
||||
*/
|
||||
@Transactional
|
||||
public boolean updateDeviceHeartbeatByAddress(String ipAddress, Integer port) {
|
||||
public boolean updateDeviceHeartbeatByAddress(String ipAddress) {
|
||||
if (ipAddress == null || ipAddress.trim().isEmpty()) {
|
||||
log.warn("IP地址不能为空");
|
||||
return false;
|
||||
}
|
||||
if (port == null) {
|
||||
log.warn("端口号不能为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
int updatedCount = trafficLightRepository.updateHeartbeatByIpAndPort(ipAddress.trim(), port, now);
|
||||
int updatedCount = trafficLightRepository.updateHeartbeatByIpAddress(ipAddress.trim(), now);
|
||||
if (updatedCount > 0) {
|
||||
log.debug("成功更新设备心跳时间: ipAddress={}, port={}, heartbeat={}", ipAddress, port, now);
|
||||
log.debug("成功更新设备心跳时间: ipAddress={}, heartbeat={}", ipAddress, now);
|
||||
return true;
|
||||
} else {
|
||||
log.warn("设备不存在,无法更新心跳时间: ipAddress={}, port={}", ipAddress, port);
|
||||
log.warn("设备不存在,无法更新心跳时间: ipAddress={}", ipAddress);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("更新设备心跳时间异常: ipAddress={}, port={}", ipAddress, port, e);
|
||||
log.error("更新设备心跳时间异常: ipAddress={}", ipAddress, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 更新设备在线状态(基于IP地址和端口)
|
||||
* 根据IP地址更新设备在线状态
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @param port 端口号
|
||||
* @param isOnline 是否在线
|
||||
* @return 是否更新成功
|
||||
*/
|
||||
@Transactional
|
||||
public boolean updateDeviceOnlineStatusByAddress(String ipAddress, Integer port, boolean isOnline) {
|
||||
public boolean updateDeviceOnlineStatusByIpAddress(String ipAddress, boolean isOnline) {
|
||||
if (ipAddress == null || ipAddress.trim().isEmpty()) {
|
||||
log.warn("IP地址不能为空");
|
||||
return false;
|
||||
}
|
||||
if (port == null) {
|
||||
log.warn("端口号不能为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
int updatedCount = trafficLightRepository.updateOnlineStatusByIpAndPort(ipAddress.trim(), port, isOnline);
|
||||
int updatedCount = trafficLightRepository.updateOnlineStatusByIpAddress(ipAddress.trim(), isOnline);
|
||||
if (updatedCount > 0) {
|
||||
log.debug("成功更新设备在线状态: ipAddress={}, port={}, isOnline={}", ipAddress, port, isOnline);
|
||||
log.debug("成功更新设备在线状态: ipAddress={}, isOnline={}", ipAddress, isOnline);
|
||||
return true;
|
||||
} else {
|
||||
log.warn("设备不存在,无法更新在线状态: ipAddress={}, port={}", ipAddress, port);
|
||||
log.warn("设备不存在,无法更新在线状态: ipAddress={}", ipAddress);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("更新设备在线状态异常: ipAddress={}, port={}, isOnline={}", ipAddress, port, isOnline, e);
|
||||
log.error("更新设备在线状态异常: ipAddress={}", ipAddress, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据IP地址前缀查找设备
|
||||
*
|
||||
* @param ipPrefix IP地址前缀(如 "192.168.1")
|
||||
* @return 匹配的设备列表
|
||||
*/
|
||||
public List<TrafficLight> findDevicesByIpPrefix(String ipPrefix) {
|
||||
public List<TrafficLight> findDevicesByPrefix(String ipPrefix) {
|
||||
if (ipPrefix == null || ipPrefix.trim().isEmpty()) {
|
||||
log.warn("IP地址前缀不能为空");
|
||||
return List.of();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
List<TrafficLight> devices = trafficLightRepository.findByIpAddressPrefix(ipPrefix.trim());
|
||||
log.debug("IP地址前缀 '{}' 查询到 {} 个设备", ipPrefix, devices.size());
|
||||
@ -600,7 +598,7 @@ public class TrafficLightService {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查找没有设备ID的设备
|
||||
*
|
||||
@ -616,7 +614,7 @@ public class TrafficLightService {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查找使用默认IP地址的设备
|
||||
*
|
||||
@ -632,40 +630,18 @@ public class TrafficLightService {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据端口号查找设备
|
||||
*
|
||||
* @param port 端口号
|
||||
* @return 使用该端口的设备列表
|
||||
*/
|
||||
public List<TrafficLight> findDevicesByPort(Integer port) {
|
||||
if (port == null || port <= 0 || port > 65535) {
|
||||
log.warn("端口号无效: {}", port);
|
||||
return List.of();
|
||||
}
|
||||
|
||||
try {
|
||||
List<TrafficLight> devices = trafficLightRepository.findByPort(port);
|
||||
log.debug("端口 {} 查询到 {} 个设备", port, devices.size());
|
||||
return devices;
|
||||
} catch (Exception e) {
|
||||
log.error("根据端口号查找设备异常: port={}", port, e);
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成默认设备名称
|
||||
*
|
||||
* @param ipAddress IP地址
|
||||
* @param port 端口号
|
||||
* @param port 端口号
|
||||
* @return 默认设备名称
|
||||
*/
|
||||
private String generateDefaultDeviceName(String ipAddress, Integer port) {
|
||||
return "TrafficLight_" + ipAddress.replace(".", "_") + "_" + port;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取设备统计信息
|
||||
*
|
||||
@ -677,7 +653,7 @@ public class TrafficLightService {
|
||||
long activeCount = trafficLightRepository.countByIsActiveTrue();
|
||||
long onlineCount = trafficLightRepository.countByIsOnlineTrue();
|
||||
long defaultIpCount = trafficLightRepository.countDevicesWithDefaultIp();
|
||||
|
||||
|
||||
return DeviceStatistics.builder()
|
||||
.totalCount(totalCount)
|
||||
.activeCount(activeCount)
|
||||
@ -692,7 +668,7 @@ public class TrafficLightService {
|
||||
return DeviceStatistics.builder().build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证设备数据
|
||||
*
|
||||
@ -703,30 +679,25 @@ public class TrafficLightService {
|
||||
// 设备ID或IP地址至少有一个不为空
|
||||
boolean hasDeviceId = trafficLight.getDeviceId() != null && !trafficLight.getDeviceId().trim().isEmpty();
|
||||
boolean hasIpAddress = trafficLight.getIpAddress() != null && !trafficLight.getIpAddress().trim().isEmpty();
|
||||
|
||||
|
||||
if (!hasDeviceId && !hasIpAddress) {
|
||||
throw new IllegalArgumentException("设备编号或IP地址至少有一个不能为空");
|
||||
}
|
||||
|
||||
|
||||
if (trafficLight.getDeviceName() == null || trafficLight.getDeviceName().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("设备名称不能为空");
|
||||
}
|
||||
|
||||
|
||||
if (trafficLight.getIntersectionId() == null || trafficLight.getIntersectionId().trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("关联路口编号不能为空");
|
||||
}
|
||||
|
||||
|
||||
// 如果有IP地址,验证格式
|
||||
if (hasIpAddress && !trafficLight.isValidIpAddress()) {
|
||||
throw new IllegalArgumentException("IP地址格式无效: " + trafficLight.getIpAddress());
|
||||
}
|
||||
|
||||
// 如果有端口,验证范围
|
||||
if (trafficLight.getPort() != null && !trafficLight.isValidPort()) {
|
||||
throw new IllegalArgumentException("端口号无效: " + trafficLight.getPort());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设备统计信息数据类
|
||||
*/
|
||||
@ -740,12 +711,12 @@ public class TrafficLightService {
|
||||
private long inactiveCount;
|
||||
private long onlineCount;
|
||||
private long offlineCount;
|
||||
private long defaultIpCount; // 使用默认IP地址的设备数量
|
||||
private long validIpCount; // 有有效IP地址的设备数量
|
||||
|
||||
private long defaultIpCount; // 使用默认IP地址的设备数量
|
||||
private long validIpCount; // 有有效IP地址的设备数量
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("设备统计 - 总计:%d, 激活:%d, 停用:%d, 在线:%d, 离线:%d, 默认IP:%d, 有效IP:%d",
|
||||
return String.format("设备统计 - 总计:%d, 激活:%d, 停用:%d, 在线:%d, 离线:%d, 默认IP:%d, 有效IP:%d",
|
||||
totalCount, activeCount, inactiveCount, onlineCount, offlineCount, defaultIpCount, validIpCount);
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,6 @@ class TrafficLightRepositoryMethodTest {
|
||||
.deviceId("TL_001")
|
||||
.deviceName("测试红绿灯1")
|
||||
.ipAddress("192.168.1.100")
|
||||
.port(8082)
|
||||
.intersectionId("INTERSECTION_001")
|
||||
.deviceType("STANDARD")
|
||||
.isActive(true)
|
||||
@ -47,7 +46,6 @@ class TrafficLightRepositoryMethodTest {
|
||||
.id(2L)
|
||||
.deviceName("无设备ID红绿灯")
|
||||
.ipAddress("192.168.1.101")
|
||||
.port(8083)
|
||||
.intersectionId("INTERSECTION_002")
|
||||
.deviceType("STANDARD")
|
||||
.isActive(true)
|
||||
@ -55,21 +53,7 @@ class TrafficLightRepositoryMethodTest {
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindByIpAddressAndPort_MethodSignature() {
|
||||
// 测试方法签名和Mock行为
|
||||
when(trafficLightRepository.findByIpAddressAndPort("192.168.1.100", 8082))
|
||||
.thenReturn(Optional.of(testDevice1));
|
||||
|
||||
Optional<TrafficLight> result = trafficLightRepository.findByIpAddressAndPort("192.168.1.100", 8082);
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
assertEquals("TL_001", result.get().getDeviceId());
|
||||
assertEquals("192.168.1.100", result.get().getIpAddress());
|
||||
assertEquals(Integer.valueOf(8082), result.get().getPort());
|
||||
|
||||
verify(trafficLightRepository).findByIpAddressAndPort("192.168.1.100", 8082);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testFindByIpAddress_MethodSignature() {
|
||||
@ -86,43 +70,38 @@ class TrafficLightRepositoryMethodTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExistsByIpAddressAndPort_MethodSignature() {
|
||||
void testExistsByAddressAndPort_MethodSignature() {
|
||||
// 测试检查IP地址和端口组合是否存在的方法签名
|
||||
when(trafficLightRepository.existsByIpAddressAndPort("192.168.1.100", 8082))
|
||||
.thenReturn(true);
|
||||
when(trafficLightRepository.existsByIpAddressAndPort("192.168.1.200", 8082))
|
||||
.thenReturn(false);
|
||||
|
||||
assertTrue(trafficLightRepository.existsByIpAddressAndPort("192.168.1.100", 8082));
|
||||
assertFalse(trafficLightRepository.existsByIpAddressAndPort("192.168.1.200", 8082));
|
||||
|
||||
verify(trafficLightRepository).existsByIpAddressAndPort("192.168.1.100", 8082);
|
||||
verify(trafficLightRepository).existsByIpAddressAndPort("192.168.1.200", 8082);
|
||||
when(trafficLightRepository.findByIpAddress("192.168.1.100"))
|
||||
.thenReturn(Arrays.asList(testDevice1));
|
||||
when(trafficLightRepository.findByIpAddress("192.168.1.200"))
|
||||
.thenReturn(Arrays.asList(testDevice1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateOnlineStatusByIpAndPort_MethodSignature() {
|
||||
// 测试基于IP地址和端口更新在线状态的方法签名
|
||||
when(trafficLightRepository.updateOnlineStatusByIpAndPort("192.168.1.100", 8082, true))
|
||||
void testUpdateHeartbeatByIpAddress_MethodSignature() {
|
||||
// 测试基于IP地址更新心跳时间的方法签名
|
||||
LocalDateTime heartbeatTime = LocalDateTime.now();
|
||||
when(trafficLightRepository.updateHeartbeatByIpAddress("192.168.1.100", heartbeatTime))
|
||||
.thenReturn(1);
|
||||
|
||||
int result = trafficLightRepository.updateOnlineStatusByIpAndPort("192.168.1.100", 8082, true);
|
||||
int result = trafficLightRepository.updateHeartbeatByIpAddress("192.168.1.100", heartbeatTime);
|
||||
|
||||
assertEquals(1, result);
|
||||
verify(trafficLightRepository).updateOnlineStatusByIpAndPort("192.168.1.100", 8082, true);
|
||||
verify(trafficLightRepository).updateHeartbeatByIpAddress("192.168.1.100", heartbeatTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateHeartbeatByIpAndPort_MethodSignature() {
|
||||
// 测试基于IP地址和端口更新心跳时间的方法签名
|
||||
LocalDateTime heartbeatTime = LocalDateTime.now();
|
||||
when(trafficLightRepository.updateHeartbeatByIpAndPort("192.168.1.100", 8082, heartbeatTime))
|
||||
when(trafficLightRepository.updateHeartbeatByIpAddress("192.168.1.100", heartbeatTime))
|
||||
.thenReturn(1);
|
||||
|
||||
int result = trafficLightRepository.updateHeartbeatByIpAndPort("192.168.1.100", 8082, heartbeatTime);
|
||||
int result = trafficLightRepository.updateHeartbeatByIpAddress("192.168.1.100", heartbeatTime);
|
||||
|
||||
assertEquals(1, result);
|
||||
verify(trafficLightRepository).updateHeartbeatByIpAndPort("192.168.1.100", 8082, heartbeatTime);
|
||||
verify(trafficLightRepository).updateHeartbeatByIpAddress("192.168.1.100", heartbeatTime);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -150,19 +129,6 @@ class TrafficLightRepositoryMethodTest {
|
||||
verify(trafficLightRepository).findDevicesWithoutDeviceId();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindByPort_MethodSignature() {
|
||||
// 测试根据端口号查找设备的方法签名
|
||||
when(trafficLightRepository.findByPort(8082))
|
||||
.thenReturn(Arrays.asList(testDevice1));
|
||||
|
||||
List<TrafficLight> result = trafficLightRepository.findByPort(8082);
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(Integer.valueOf(8082), result.get(0).getPort());
|
||||
verify(trafficLightRepository).findByPort(8082);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCountByIpAddress_MethodSignature() {
|
||||
// 测试统计指定IP地址设备数量的方法签名
|
||||
@ -183,7 +149,6 @@ class TrafficLightRepositoryMethodTest {
|
||||
.deviceId("TL_DEFAULT")
|
||||
.deviceName("默认IP设备")
|
||||
.ipAddress("0.0.0.0")
|
||||
.port(8082)
|
||||
.intersectionId("INTERSECTION_003")
|
||||
.isActive(true)
|
||||
.build();
|
||||
@ -214,13 +179,12 @@ class TrafficLightRepositoryMethodTest {
|
||||
void testTrafficLightEntityMethods() {
|
||||
// 测试TrafficLight实体类的新方法
|
||||
assertEquals("TL_001", testDevice1.generateDeviceIdentifier());
|
||||
assertEquals("192.168.1.101:8083", testDevice2.generateDeviceIdentifier());
|
||||
assertEquals("192.168.1.101", testDevice2.generateDeviceIdentifier());
|
||||
|
||||
assertTrue(testDevice1.isValidIpAddress());
|
||||
assertTrue(testDevice1.isValidPort());
|
||||
|
||||
assertEquals("192.168.1.100:8082", testDevice1.getNetworkAddress());
|
||||
assertEquals("192.168.1.101:8083", testDevice2.getNetworkAddress());
|
||||
assertEquals("192.168.1.100", testDevice1.getNetworkAddress());
|
||||
assertEquals("192.168.1.101", testDevice2.getNetworkAddress());
|
||||
|
||||
assertFalse(testDevice1.isDefaultIpAddress());
|
||||
assertFalse(testDevice2.isDefaultIpAddress());
|
||||
|
||||
@ -19,7 +19,6 @@ class TrafficLightTest {
|
||||
.deviceId("TL_001")
|
||||
.deviceName("测试红绿灯")
|
||||
.ipAddress("192.168.1.100")
|
||||
.port(8082)
|
||||
.intersectionId("INTERSECTION_001")
|
||||
.deviceType("STANDARD")
|
||||
.isActive(true)
|
||||
@ -39,14 +38,13 @@ class TrafficLightTest {
|
||||
// 当没有设备ID时,应该返回IP:端口组合
|
||||
trafficLight.setDeviceId(null);
|
||||
String identifier = trafficLight.generateDeviceIdentifier();
|
||||
assertEquals("192.168.1.100:8082", identifier);
|
||||
assertEquals("192.168.1.100", identifier);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateDeviceIdentifier_OnlyIpAddress() {
|
||||
// 当只有IP地址没有端口时,应该返回IP地址
|
||||
trafficLight.setDeviceId(null);
|
||||
trafficLight.setPort(null);
|
||||
String identifier = trafficLight.generateDeviceIdentifier();
|
||||
assertEquals("192.168.1.100", identifier);
|
||||
}
|
||||
@ -56,7 +54,6 @@ class TrafficLightTest {
|
||||
// 当既没有设备ID也没有IP地址时,应该返回基于ID的标识符
|
||||
trafficLight.setDeviceId(null);
|
||||
trafficLight.setIpAddress(null);
|
||||
trafficLight.setPort(null);
|
||||
trafficLight.setId(123L);
|
||||
String identifier = trafficLight.generateDeviceIdentifier();
|
||||
assertEquals("UNKNOWN_DEVICE_123", identifier);
|
||||
@ -93,43 +90,9 @@ class TrafficLightTest {
|
||||
assertFalse(trafficLight.isValidIpAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsValidPort_ValidPort() {
|
||||
// 测试有效的端口号
|
||||
assertTrue(trafficLight.isValidPort());
|
||||
|
||||
trafficLight.setPort(1);
|
||||
assertTrue(trafficLight.isValidPort());
|
||||
|
||||
trafficLight.setPort(65535);
|
||||
assertTrue(trafficLight.isValidPort());
|
||||
|
||||
trafficLight.setPort(8080);
|
||||
assertTrue(trafficLight.isValidPort());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsValidPort_InvalidPort() {
|
||||
// 测试无效的端口号
|
||||
trafficLight.setPort(0);
|
||||
assertFalse(trafficLight.isValidPort());
|
||||
|
||||
trafficLight.setPort(-1);
|
||||
assertFalse(trafficLight.isValidPort());
|
||||
|
||||
trafficLight.setPort(65536);
|
||||
assertFalse(trafficLight.isValidPort());
|
||||
|
||||
trafficLight.setPort(null);
|
||||
assertFalse(trafficLight.isValidPort());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetNetworkAddress() {
|
||||
// 测试网络地址获取
|
||||
assertEquals("192.168.1.100:8082", trafficLight.getNetworkAddress());
|
||||
|
||||
trafficLight.setPort(null);
|
||||
// 测试网络地址获取
|
||||
assertEquals("192.168.1.100", trafficLight.getNetworkAddress());
|
||||
|
||||
trafficLight.setIpAddress(null);
|
||||
@ -151,13 +114,11 @@ class TrafficLightTest {
|
||||
TrafficLight light = TrafficLight.builder()
|
||||
.deviceName("新红绿灯")
|
||||
.ipAddress("10.0.0.100")
|
||||
.port(9090)
|
||||
.intersectionId("INTERSECTION_002")
|
||||
.build();
|
||||
|
||||
assertEquals("新红绿灯", light.getDeviceName());
|
||||
assertEquals("10.0.0.100", light.getIpAddress());
|
||||
assertEquals(Integer.valueOf(9090), light.getPort());
|
||||
assertEquals("INTERSECTION_002", light.getIntersectionId());
|
||||
assertEquals("0.0.0.0", TrafficLight.builder().build().getIpAddress()); // 测试默认值
|
||||
}
|
||||
@ -168,11 +129,10 @@ class TrafficLightTest {
|
||||
TrafficLight lightWithoutDeviceId = TrafficLight.builder()
|
||||
.deviceName("无设备ID的红绿灯")
|
||||
.ipAddress("172.16.0.1")
|
||||
.port(8083)
|
||||
.intersectionId("INTERSECTION_003")
|
||||
.build();
|
||||
|
||||
assertNull(lightWithoutDeviceId.getDeviceId());
|
||||
assertEquals("172.16.0.1:8083", lightWithoutDeviceId.generateDeviceIdentifier());
|
||||
assertEquals("172.16.0.1", lightWithoutDeviceId.generateDeviceIdentifier());
|
||||
}
|
||||
}
|
||||
@ -53,7 +53,6 @@ class DataProcessingServiceTrafficLightTest {
|
||||
.deviceId("TL_001")
|
||||
.deviceName("测试红绿灯")
|
||||
.ipAddress("192.168.1.100")
|
||||
.port(8082)
|
||||
.intersectionId("INTERSECTION_001")
|
||||
.deviceType("STANDARD")
|
||||
.isActive(true)
|
||||
@ -91,13 +90,13 @@ class DataProcessingServiceTrafficLightTest {
|
||||
// Mock解析器行为
|
||||
when(trafficLightSignalParser.parseSignalWithAddress(jsonSignal, ipAddress, port)).thenReturn(testStatus);
|
||||
|
||||
// Mock设备查找
|
||||
when(trafficLightService.findDeviceByAddress("192.168.1.100", 8082))
|
||||
// Mock设备查找(现在使用IP地址查找)
|
||||
when(trafficLightService.findDeviceByAddress("192.168.1.100"))
|
||||
.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设备状态更新(现在使用IP地址更新心跳,设备ID更新在线状态)
|
||||
when(trafficLightService.updateDeviceHeartbeatByAddress("192.168.1.100")).thenReturn(true);
|
||||
when(trafficLightService.updateDeviceOnlineStatus("TL_001", true)).thenReturn(true);
|
||||
|
||||
// Mock路口查找
|
||||
when(intersectionService.getActiveIntersectionById("INTERSECTION_001"))
|
||||
@ -108,9 +107,9 @@ class DataProcessingServiceTrafficLightTest {
|
||||
|
||||
// 验证调用
|
||||
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(trafficLightService).findDeviceByAddress("192.168.1.100");
|
||||
verify(trafficLightService).updateDeviceHeartbeatByAddress("192.168.1.100");
|
||||
verify(trafficLightService).updateDeviceOnlineStatus("TL_001", true);
|
||||
verify(intersectionService).getActiveIntersectionById("INTERSECTION_001");
|
||||
verify(eventPublisher).publishEvent(any(TrafficLightStatusEvent.class));
|
||||
}
|
||||
@ -165,7 +164,6 @@ class DataProcessingServiceTrafficLightTest {
|
||||
|
||||
TrafficLightStatus newDeviceStatus = TrafficLightStatus.builder()
|
||||
.ipAddress("10.0.0.1")
|
||||
.port(9090)
|
||||
.nsStatus(SignalState.RED)
|
||||
.ewStatus(SignalState.RED)
|
||||
.timestamp(System.currentTimeMillis() * 1000)
|
||||
@ -174,9 +172,9 @@ class DataProcessingServiceTrafficLightTest {
|
||||
|
||||
TrafficLight newDevice = TrafficLight.builder()
|
||||
.id(2L)
|
||||
.deviceId("TL_AUTO_10_0_0_1")
|
||||
.deviceName("TrafficLight_10_0_0_1_9090")
|
||||
.ipAddress("10.0.0.1")
|
||||
.port(9090)
|
||||
.intersectionId("DEFAULT_INTERSECTION")
|
||||
.deviceType("STANDARD")
|
||||
.isActive(true)
|
||||
@ -196,15 +194,15 @@ class DataProcessingServiceTrafficLightTest {
|
||||
// Mock解析器行为
|
||||
when(trafficLightSignalParser.parseSignalWithAddress(jsonSignal, ipAddress, port)).thenReturn(newDeviceStatus);
|
||||
|
||||
// Mock设备查找(不存在)和创建
|
||||
when(trafficLightService.findDeviceByAddress("10.0.0.1", 9090))
|
||||
// Mock设备查找(不存在)和创建(现在使用IP地址)
|
||||
when(trafficLightService.findDeviceByAddress("10.0.0.1"))
|
||||
.thenReturn(Optional.empty());
|
||||
when(trafficLightService.getOrCreateDeviceByAddress("10.0.0.1", 9090, "DEFAULT_INTERSECTION"))
|
||||
when(trafficLightService.getOrCreateDeviceByAddress("10.0.0.1", "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设备状态更新(现在使用IP地址更新心跳,设备ID更新在线状态)
|
||||
when(trafficLightService.updateDeviceHeartbeatByAddress("10.0.0.1")).thenReturn(true);
|
||||
when(trafficLightService.updateDeviceOnlineStatus("TL_AUTO_10_0_0_1", true)).thenReturn(true);
|
||||
|
||||
// Mock路口查找
|
||||
when(intersectionService.getActiveIntersectionById("DEFAULT_INTERSECTION"))
|
||||
@ -214,10 +212,10 @@ class DataProcessingServiceTrafficLightTest {
|
||||
dataProcessingService.processTrafficLightSignalWithAddress(jsonSignal, ipAddress, port);
|
||||
|
||||
// 验证调用
|
||||
verify(trafficLightService).findDeviceByAddress("10.0.0.1", 9090);
|
||||
verify(trafficLightService).getOrCreateDeviceByAddress("10.0.0.1", 9090, "DEFAULT_INTERSECTION");
|
||||
verify(trafficLightService).updateDeviceHeartbeatByAddress("10.0.0.1", 9090);
|
||||
verify(trafficLightService).updateDeviceOnlineStatusByAddress("10.0.0.1", 9090, true);
|
||||
verify(trafficLightService).findDeviceByAddress("10.0.0.1");
|
||||
verify(trafficLightService).getOrCreateDeviceByAddress("10.0.0.1", "DEFAULT_INTERSECTION");
|
||||
verify(trafficLightService).updateDeviceHeartbeatByAddress("10.0.0.1");
|
||||
verify(trafficLightService).updateDeviceOnlineStatus("TL_AUTO_10_0_0_1", true);
|
||||
verify(eventPublisher).publishEvent(any(TrafficLightStatusEvent.class));
|
||||
}
|
||||
|
||||
@ -298,18 +296,18 @@ class DataProcessingServiceTrafficLightTest {
|
||||
when(trafficLightSignalParser.parseSignalWithAddress(jsonSignal, ipAddress, port))
|
||||
.thenReturn(statusWithoutDevice);
|
||||
|
||||
// Mock设备查找失败
|
||||
when(trafficLightService.findDeviceByAddress("172.16.0.1", 8083))
|
||||
// Mock设备查找失败(现在使用IP地址)
|
||||
when(trafficLightService.findDeviceByAddress("172.16.0.1"))
|
||||
.thenReturn(Optional.empty());
|
||||
when(trafficLightService.getOrCreateDeviceByAddress("172.16.0.1", 8083, "DEFAULT_INTERSECTION"))
|
||||
when(trafficLightService.getOrCreateDeviceByAddress("172.16.0.1", "DEFAULT_INTERSECTION"))
|
||||
.thenThrow(new RuntimeException("创建设备失败"));
|
||||
|
||||
// 执行测试
|
||||
dataProcessingService.processTrafficLightSignalWithAddress(jsonSignal, ipAddress, port);
|
||||
|
||||
// 验证尝试了设备查找和创建,但没有后续处理
|
||||
verify(trafficLightService).findDeviceByAddress("172.16.0.1", 8083);
|
||||
verify(trafficLightService).getOrCreateDeviceByAddress("172.16.0.1", 8083, "DEFAULT_INTERSECTION");
|
||||
verify(trafficLightService).findDeviceByAddress("172.16.0.1");
|
||||
verify(trafficLightService).getOrCreateDeviceByAddress("172.16.0.1", "DEFAULT_INTERSECTION");
|
||||
verifyNoInteractions(intersectionService);
|
||||
verifyNoInteractions(eventPublisher);
|
||||
}
|
||||
|
||||
@ -15,6 +15,8 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
@ -44,7 +46,6 @@ class TrafficLightServiceEnhancedTest {
|
||||
.deviceId("TL_001")
|
||||
.deviceName("测试红绿灯1")
|
||||
.ipAddress("192.168.1.100")
|
||||
.port(8082)
|
||||
.intersectionId("INTERSECTION_001")
|
||||
.deviceType("STANDARD")
|
||||
.isActive(true)
|
||||
@ -55,7 +56,6 @@ class TrafficLightServiceEnhancedTest {
|
||||
.id(2L)
|
||||
.deviceName("无设备ID红绿灯")
|
||||
.ipAddress("192.168.1.101")
|
||||
.port(8083)
|
||||
.intersectionId("INTERSECTION_002")
|
||||
.deviceType("STANDARD")
|
||||
.isActive(true)
|
||||
@ -66,31 +66,29 @@ class TrafficLightServiceEnhancedTest {
|
||||
@Test
|
||||
void testGetOrCreateDeviceByAddress_ExistingDevice() {
|
||||
// 测试获取现有设备
|
||||
when(trafficLightRepository.findByIpAddressAndPort("192.168.1.100", 8082))
|
||||
.thenReturn(Optional.of(testDevice1));
|
||||
when(trafficLightRepository.findByIpAddress("192.168.1.100"))
|
||||
.thenReturn(Arrays.asList(testDevice1));
|
||||
|
||||
TrafficLight result = trafficLightService.getOrCreateDeviceByAddress("192.168.1.100", 8082, "INTERSECTION_001");
|
||||
TrafficLight result = trafficLightService.getOrCreateDeviceByAddress("192.168.1.100", "INTERSECTION_001");
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("TL_001", result.getDeviceId());
|
||||
assertEquals("192.168.1.100", result.getIpAddress());
|
||||
assertEquals(Integer.valueOf(8082), result.getPort());
|
||||
|
||||
verify(trafficLightRepository).findByIpAddressAndPort("192.168.1.100", 8082);
|
||||
verify(trafficLightRepository).findByIpAddress("192.168.1.100");
|
||||
verify(trafficLightRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetOrCreateDeviceByAddress_NewDevice() {
|
||||
// 测试创建新设备
|
||||
when(trafficLightRepository.findByIpAddressAndPort("10.0.0.1", 9090))
|
||||
.thenReturn(Optional.empty());
|
||||
when(trafficLightRepository.findByIpAddress("10.0.0.1"))
|
||||
.thenReturn(null);
|
||||
|
||||
TrafficLight newDevice = TrafficLight.builder()
|
||||
.id(3L)
|
||||
.deviceName("TrafficLight_10_0_0_1_9090")
|
||||
.ipAddress("10.0.0.1")
|
||||
.port(9090)
|
||||
.intersectionId("INTERSECTION_003")
|
||||
.deviceType("STANDARD")
|
||||
.isActive(true)
|
||||
@ -99,67 +97,57 @@ class TrafficLightServiceEnhancedTest {
|
||||
|
||||
when(trafficLightRepository.save(any(TrafficLight.class))).thenReturn(newDevice);
|
||||
|
||||
TrafficLight result = trafficLightService.getOrCreateDeviceByAddress("10.0.0.1", 9090, "INTERSECTION_003");
|
||||
TrafficLight result = trafficLightService.getOrCreateDeviceByAddress("10.0.0.1", "INTERSECTION_003");
|
||||
|
||||
assertNotNull(result);
|
||||
assertEquals("TrafficLight_10_0_0_1_9090", result.getDeviceName());
|
||||
assertEquals("10.0.0.1", result.getIpAddress());
|
||||
assertEquals(Integer.valueOf(9090), result.getPort());
|
||||
assertEquals("INTERSECTION_003", result.getIntersectionId());
|
||||
|
||||
verify(trafficLightRepository).findByIpAddressAndPort("10.0.0.1", 9090);
|
||||
verify(trafficLightRepository).findByIpAddress("10.0.0.1");
|
||||
verify(trafficLightRepository).save(any(TrafficLight.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetOrCreateDeviceByAddress_InvalidParameters() {
|
||||
void testGetOrCreateDeviceByIpAddress_InvalidParameters() {
|
||||
// 测试无效参数
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
trafficLightService.getOrCreateDeviceByAddress(null, 8082, "INTERSECTION_001"));
|
||||
trafficLightService.getOrCreateDeviceByAddress(null, "INTERSECTION_001"));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
trafficLightService.getOrCreateDeviceByAddress("", 8082, "INTERSECTION_001"));
|
||||
trafficLightService.getOrCreateDeviceByAddress("", "INTERSECTION_001"));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
trafficLightService.getOrCreateDeviceByAddress("192.168.1.1", null, "INTERSECTION_001"));
|
||||
trafficLightService.getOrCreateDeviceByAddress("192.168.1.1", null));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
trafficLightService.getOrCreateDeviceByAddress("192.168.1.1", 0, "INTERSECTION_001"));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
trafficLightService.getOrCreateDeviceByAddress("192.168.1.1", 65536, "INTERSECTION_001"));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
trafficLightService.getOrCreateDeviceByAddress("192.168.1.1", 8082, null));
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
trafficLightService.getOrCreateDeviceByAddress("192.168.1.1", 8082, ""));
|
||||
trafficLightService.getOrCreateDeviceByAddress("192.168.1.1", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindDeviceByAddress() {
|
||||
// 测试根据IP地址和端口查找设备
|
||||
when(trafficLightRepository.findByIpAddressAndPort("192.168.1.100", 8082))
|
||||
.thenReturn(Optional.of(testDevice1));
|
||||
when(trafficLightRepository.findByIpAddress("192.168.1.100"))
|
||||
.thenReturn(Arrays.asList(testDevice1));
|
||||
|
||||
Optional<TrafficLight> result = trafficLightService.findDeviceByAddress("192.168.1.100", 8082);
|
||||
Optional<TrafficLight> result = trafficLightService.findDeviceByAddress("192.168.1.100");
|
||||
|
||||
assertTrue(result.isPresent());
|
||||
assertEquals("TL_001", result.get().getDeviceId());
|
||||
|
||||
verify(trafficLightRepository).findByIpAddressAndPort("192.168.1.100", 8082);
|
||||
verify(trafficLightRepository).findByIpAddress("192.168.1.100");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindDeviceByAddress_NotFound() {
|
||||
// 测试设备不存在的情况
|
||||
when(trafficLightRepository.findByIpAddressAndPort("10.0.0.1", 8080))
|
||||
.thenReturn(Optional.empty());
|
||||
when(trafficLightRepository.findByIpAddress("10.0.0.1"))
|
||||
.thenReturn(null);
|
||||
|
||||
Optional<TrafficLight> result = trafficLightService.findDeviceByAddress("10.0.0.1", 8080);
|
||||
Optional<TrafficLight> result = trafficLightService.findDeviceByAddress("10.0.0.1");
|
||||
|
||||
assertFalse(result.isPresent());
|
||||
verify(trafficLightRepository).findByIpAddressAndPort("10.0.0.1", 8080);
|
||||
verify(trafficLightRepository).findByIpAddress("10.0.0.1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -168,7 +156,7 @@ class TrafficLightServiceEnhancedTest {
|
||||
when(trafficLightRepository.findByIpAddress("192.168.1.100"))
|
||||
.thenReturn(Arrays.asList(testDevice1));
|
||||
|
||||
List<TrafficLight> result = trafficLightService.findDevicesByIpAddress("192.168.1.100");
|
||||
List<TrafficLight> result = trafficLightService.findDevicesByAddress("192.168.1.100");
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertEquals("TL_001", result.get(0).getDeviceId());
|
||||
@ -177,39 +165,39 @@ class TrafficLightServiceEnhancedTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateDeviceHeartbeatByAddress() {
|
||||
void testupdateDeviceHeartbeatByIpAddress() {
|
||||
// 测试基于IP地址和端口更新心跳时间
|
||||
when(trafficLightRepository.updateHeartbeatByIpAndPort(eq("192.168.1.100"), eq(8082), any(LocalDateTime.class)))
|
||||
when(trafficLightRepository.updateHeartbeatByIpAddress(eq("192.168.1.100"), any(LocalDateTime.class)))
|
||||
.thenReturn(1);
|
||||
|
||||
boolean result = trafficLightService.updateDeviceHeartbeatByAddress("192.168.1.100", 8082);
|
||||
boolean result = trafficLightService.updateDeviceHeartbeatByAddress("192.168.1.100");
|
||||
|
||||
assertTrue(result);
|
||||
verify(trafficLightRepository).updateHeartbeatByIpAndPort(eq("192.168.1.100"), eq(8082), any(LocalDateTime.class));
|
||||
verify(trafficLightRepository).updateHeartbeatByIpAddress(eq("192.168.1.100"), any(LocalDateTime.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateDeviceHeartbeatByAddress_DeviceNotFound() {
|
||||
void testupdateDeviceHeartbeatByIpAddress_DeviceNotFound() {
|
||||
// 测试设备不存在时更新心跳
|
||||
when(trafficLightRepository.updateHeartbeatByIpAndPort(eq("10.0.0.1"), eq(8080), any(LocalDateTime.class)))
|
||||
when(trafficLightRepository.updateHeartbeatByIpAddress(eq("10.0.0.1"), any(LocalDateTime.class)))
|
||||
.thenReturn(0);
|
||||
|
||||
boolean result = trafficLightService.updateDeviceHeartbeatByAddress("10.0.0.1", 8080);
|
||||
boolean result = trafficLightService.updateDeviceHeartbeatByAddress("10.0.0.1");
|
||||
|
||||
assertFalse(result);
|
||||
verify(trafficLightRepository).updateHeartbeatByIpAndPort(eq("10.0.0.1"), eq(8080), any(LocalDateTime.class));
|
||||
verify(trafficLightRepository).updateHeartbeatByIpAddress(eq("10.0.0.1"), any(LocalDateTime.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUpdateDeviceOnlineStatusByAddress() {
|
||||
// 测试基于IP地址和端口更新在线状态
|
||||
when(trafficLightRepository.updateOnlineStatusByIpAndPort("192.168.1.100", 8082, true))
|
||||
when(trafficLightRepository.updateHeartbeatByIpAddress(eq("192.168.1.100"), any(LocalDateTime.class)))
|
||||
.thenReturn(1);
|
||||
|
||||
boolean result = trafficLightService.updateDeviceOnlineStatusByAddress("192.168.1.100", 8082, true);
|
||||
boolean result = trafficLightService.updateDeviceHeartbeatByAddress("192.168.1.100");
|
||||
|
||||
assertTrue(result);
|
||||
verify(trafficLightRepository).updateOnlineStatusByIpAndPort("192.168.1.100", 8082, true);
|
||||
verify(trafficLightRepository).updateHeartbeatByIpAddress(eq("192.168.1.100"), any(LocalDateTime.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -218,7 +206,7 @@ class TrafficLightServiceEnhancedTest {
|
||||
when(trafficLightRepository.findByIpAddressPrefix("192.168.1"))
|
||||
.thenReturn(Arrays.asList(testDevice1, testDevice2));
|
||||
|
||||
List<TrafficLight> result = trafficLightService.findDevicesByIpPrefix("192.168.1");
|
||||
List<TrafficLight> result = trafficLightService.findDevicesByPrefix("192.168.1");
|
||||
|
||||
assertEquals(2, result.size());
|
||||
verify(trafficLightRepository).findByIpAddressPrefix("192.168.1");
|
||||
@ -245,7 +233,6 @@ class TrafficLightServiceEnhancedTest {
|
||||
.deviceId("TL_DEFAULT")
|
||||
.deviceName("默认IP设备")
|
||||
.ipAddress("0.0.0.0")
|
||||
.port(8082)
|
||||
.intersectionId("INTERSECTION_003")
|
||||
.isActive(true)
|
||||
.build();
|
||||
@ -260,34 +247,6 @@ class TrafficLightServiceEnhancedTest {
|
||||
verify(trafficLightRepository).findDevicesWithDefaultIp();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindDevicesByPort() {
|
||||
// 测试根据端口号查找设备
|
||||
when(trafficLightRepository.findByPort(8082))
|
||||
.thenReturn(Arrays.asList(testDevice1));
|
||||
|
||||
List<TrafficLight> result = trafficLightService.findDevicesByPort(8082);
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertEquals(Integer.valueOf(8082), result.get(0).getPort());
|
||||
verify(trafficLightRepository).findByPort(8082);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFindDevicesByPort_InvalidPort() {
|
||||
// 测试无效端口号
|
||||
List<TrafficLight> result1 = trafficLightService.findDevicesByPort(null);
|
||||
assertTrue(result1.isEmpty());
|
||||
|
||||
List<TrafficLight> result2 = trafficLightService.findDevicesByPort(0);
|
||||
assertTrue(result2.isEmpty());
|
||||
|
||||
List<TrafficLight> result3 = trafficLightService.findDevicesByPort(65536);
|
||||
assertTrue(result3.isEmpty());
|
||||
|
||||
verifyNoInteractions(trafficLightRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetStatisticsWithIpInfo() {
|
||||
// 测试获取包含IP地址信息的统计数据
|
||||
@ -318,19 +277,18 @@ class TrafficLightServiceEnhancedTest {
|
||||
// 测试验证包含IP地址的设备数据
|
||||
TrafficLight validDevice = TrafficLight.builder()
|
||||
.ipAddress("192.168.1.100")
|
||||
.port(8082)
|
||||
.deviceName("测试设备")
|
||||
.intersectionId("INTERSECTION_001")
|
||||
.build();
|
||||
|
||||
// 这个测试验证包含IP地址的设备数据验证逻辑
|
||||
// 由于validateTrafficLightData是私有方法,我们通过getOrCreateDeviceByAddress来间接测试
|
||||
when(trafficLightRepository.findByIpAddressAndPort("192.168.1.100", 8082)).thenReturn(Optional.empty());
|
||||
when(trafficLightRepository.findByIpAddress("192.168.1.100")).thenReturn(null);
|
||||
when(trafficLightRepository.save(any(TrafficLight.class))).thenReturn(validDevice);
|
||||
|
||||
// 这应该不会抛出异常,因为设备有有效的IP地址和端口
|
||||
assertDoesNotThrow(() -> {
|
||||
trafficLightService.getOrCreateDeviceByAddress("192.168.1.100", 8082, "INTERSECTION_001");
|
||||
trafficLightService.getOrCreateDeviceByAddress("192.168.1.100", "INTERSECTION_001");
|
||||
});
|
||||
}
|
||||
|
||||
@ -339,35 +297,31 @@ class TrafficLightServiceEnhancedTest {
|
||||
// 测试各种参数验证
|
||||
|
||||
// IP地址为空
|
||||
Optional<TrafficLight> result1 = trafficLightService.findDeviceByAddress(null, 8082);
|
||||
Optional<TrafficLight> result1 = trafficLightService.findDeviceByAddress(null);
|
||||
assertTrue(result1.isEmpty());
|
||||
|
||||
Optional<TrafficLight> result2 = trafficLightService.findDeviceByAddress("", 8082);
|
||||
Optional<TrafficLight> result2 = trafficLightService.findDeviceByAddress("");
|
||||
assertTrue(result2.isEmpty());
|
||||
|
||||
// 端口为空
|
||||
Optional<TrafficLight> result3 = trafficLightService.findDeviceByAddress("192.168.1.1", null);
|
||||
assertTrue(result3.isEmpty());
|
||||
|
||||
// IP地址为空时的其他方法
|
||||
List<TrafficLight> result4 = trafficLightService.findDevicesByIpAddress(null);
|
||||
List<TrafficLight> result4 = trafficLightService.findDevicesByAddress(null);
|
||||
assertTrue(result4.isEmpty());
|
||||
|
||||
List<TrafficLight> result5 = trafficLightService.findDevicesByIpAddress("");
|
||||
List<TrafficLight> result5 = trafficLightService.findDevicesByAddress("");
|
||||
assertTrue(result5.isEmpty());
|
||||
|
||||
// 心跳更新参数验证
|
||||
boolean result6 = trafficLightService.updateDeviceHeartbeatByAddress(null, 8082);
|
||||
boolean result6 = trafficLightService.updateDeviceHeartbeatByAddress(null);
|
||||
assertFalse(result6);
|
||||
|
||||
boolean result7 = trafficLightService.updateDeviceHeartbeatByAddress("192.168.1.1", null);
|
||||
boolean result7 = trafficLightService.updateDeviceHeartbeatByAddress("");
|
||||
assertFalse(result7);
|
||||
|
||||
// 在线状态更新参数验证
|
||||
boolean result8 = trafficLightService.updateDeviceOnlineStatusByAddress(null, 8082, true);
|
||||
boolean result8 = trafficLightService.updateDeviceOnlineStatus(null, true);
|
||||
assertFalse(result8);
|
||||
|
||||
boolean result9 = trafficLightService.updateDeviceOnlineStatusByAddress("192.168.1.1", null, true);
|
||||
boolean result9 = trafficLightService.updateDeviceOnlineStatus("", true);
|
||||
assertFalse(result9);
|
||||
|
||||
verifyNoInteractions(trafficLightRepository);
|
||||
|
||||
@ -1,115 +0,0 @@
|
||||
-- 红绿灯IP地址增强功能数据库迁移脚本
|
||||
-- 创建时间: 2025-01-06
|
||||
-- 描述: 为traffic_lights表添加IP地址和端口字段,并修改device_id为可选字段
|
||||
-- 版本: V1.1
|
||||
|
||||
-- 开始事务
|
||||
BEGIN;
|
||||
|
||||
-- 1. 添加IP地址字段(必填,默认值为'0.0.0.0')
|
||||
ALTER TABLE traffic_lights
|
||||
ADD COLUMN IF NOT EXISTS ip_address VARCHAR(45) NOT NULL DEFAULT '0.0.0.0';
|
||||
|
||||
-- 2. 添加端口字段(可选)
|
||||
ALTER TABLE traffic_lights
|
||||
ADD COLUMN IF NOT EXISTS port INTEGER;
|
||||
|
||||
-- 3. 修改device_id字段为可选(移除NOT NULL约束)
|
||||
-- 注意:PostgreSQL需要先检查是否存在NOT NULL约束
|
||||
DO $$
|
||||
BEGIN
|
||||
-- 检查device_id字段是否有NOT NULL约束,如果有则移除
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'traffic_lights'
|
||||
AND column_name = 'device_id'
|
||||
AND is_nullable = 'NO'
|
||||
) THEN
|
||||
ALTER TABLE traffic_lights ALTER COLUMN device_id DROP NOT NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 4. 为现有记录设置默认值
|
||||
-- 为现有记录设置默认IP地址和端口(如果字段为空)
|
||||
-- 为了避免唯一约束冲突,为每个记录生成唯一的IP地址和端口组合
|
||||
UPDATE traffic_lights
|
||||
SET ip_address = '0.0.0.0'
|
||||
WHERE ip_address IS NULL OR ip_address = '';
|
||||
|
||||
-- 为现有记录生成唯一的端口号,避免重复
|
||||
-- 使用记录ID + 8082作为基础端口,确保每个记录都有唯一的端口
|
||||
UPDATE traffic_lights
|
||||
SET port = 8082 + (id % 1000) -- 使用ID的模运算确保端口在合理范围内
|
||||
WHERE port IS NULL;
|
||||
|
||||
-- 5. 创建唯一约束:IP地址和端口组合必须唯一
|
||||
-- 先检查是否已存在该索引
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_indexes
|
||||
WHERE tablename = 'traffic_lights'
|
||||
AND indexname = 'idx_traffic_light_ip_port_unique'
|
||||
) THEN
|
||||
CREATE UNIQUE INDEX idx_traffic_light_ip_port_unique
|
||||
ON traffic_lights(ip_address, port);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 6. 创建IP地址索引(用于快速查询)
|
||||
CREATE INDEX IF NOT EXISTS idx_traffic_light_ip_address
|
||||
ON traffic_lights(ip_address);
|
||||
|
||||
-- 7. 添加字段注释
|
||||
COMMENT ON COLUMN traffic_lights.ip_address IS '红绿灯设备IP地址';
|
||||
COMMENT ON COLUMN traffic_lights.port IS '红绿灯设备端口号';
|
||||
|
||||
-- 8. 验证数据完整性
|
||||
-- 检查是否有重复的IP地址和端口组合
|
||||
DO $$
|
||||
DECLARE
|
||||
duplicate_count INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO duplicate_count
|
||||
FROM (
|
||||
SELECT ip_address, port, COUNT(*) as cnt
|
||||
FROM traffic_lights
|
||||
WHERE ip_address IS NOT NULL AND port IS NOT NULL
|
||||
GROUP BY ip_address, port
|
||||
HAVING COUNT(*) > 1
|
||||
) duplicates;
|
||||
|
||||
IF duplicate_count > 0 THEN
|
||||
RAISE WARNING '发现 % 个重复的IP地址和端口组合,请检查数据', duplicate_count;
|
||||
ELSE
|
||||
RAISE NOTICE '数据完整性检查通过,没有发现重复的IP地址和端口组合';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 9. 显示迁移结果
|
||||
DO $$
|
||||
DECLARE
|
||||
total_records INTEGER;
|
||||
records_with_ip INTEGER;
|
||||
records_with_port INTEGER;
|
||||
records_without_device_id INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO total_records FROM traffic_lights;
|
||||
SELECT COUNT(*) INTO records_with_ip FROM traffic_lights WHERE ip_address IS NOT NULL AND ip_address != '';
|
||||
SELECT COUNT(*) INTO records_with_port FROM traffic_lights WHERE port IS NOT NULL;
|
||||
SELECT COUNT(*) INTO records_without_device_id FROM traffic_lights WHERE device_id IS NULL OR device_id = '';
|
||||
|
||||
RAISE NOTICE '=== 数据库迁移完成 ===';
|
||||
RAISE NOTICE '总记录数: %', total_records;
|
||||
RAISE NOTICE '有IP地址的记录数: %', records_with_ip;
|
||||
RAISE NOTICE '有端口号的记录数: %', records_with_port;
|
||||
RAISE NOTICE '没有设备ID的记录数: %', records_without_device_id;
|
||||
END $$;
|
||||
|
||||
-- 提交事务
|
||||
COMMIT;
|
||||
|
||||
-- 迁移完成提示
|
||||
SELECT 'traffic_lights表IP地址增强迁移完成' AS migration_status;
|
||||
@ -21,8 +21,7 @@ CREATE TABLE IF NOT EXISTS traffic_lights (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
device_id VARCHAR(50) UNIQUE, -- 红绿灯设备编号(可选)
|
||||
device_name VARCHAR(100) NOT NULL, -- 设备名称
|
||||
ip_address VARCHAR(45) NOT NULL DEFAULT '0.0.0.0', -- 设备IP地址
|
||||
port INTEGER, -- 设备端口号
|
||||
ip_address VARCHAR(45) NOT NULL DEFAULT '0.0.0.0', -- 设备IP地址(唯一标识)
|
||||
intersection_id VARCHAR(50) NOT NULL, -- 关联的路口编号
|
||||
device_type VARCHAR(20) DEFAULT 'STANDARD', -- 设备类型
|
||||
manufacturer VARCHAR(50), -- 制造商
|
||||
@ -51,8 +50,7 @@ CREATE INDEX IF NOT EXISTS idx_traffic_light_device_id ON traffic_lights(device_
|
||||
CREATE INDEX IF NOT EXISTS idx_traffic_light_intersection ON traffic_lights(intersection_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_traffic_light_online ON traffic_lights(is_online);
|
||||
CREATE INDEX IF NOT EXISTS idx_traffic_light_active ON traffic_lights(is_active);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_traffic_light_ip_port_unique ON traffic_lights(ip_address, port);
|
||||
CREATE INDEX IF NOT EXISTS idx_traffic_light_ip_address ON traffic_lights(ip_address);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_traffic_light_ip_unique ON traffic_lights(ip_address);
|
||||
|
||||
-- 创建更新时间触发器函数
|
||||
CREATE OR REPLACE FUNCTION update_updated_time_column()
|
||||
@ -79,19 +77,13 @@ CREATE TRIGGER update_traffic_lights_updated_time
|
||||
INSERT INTO intersections (intersection_id, intersection_name, latitude, longitude, area_code, description)
|
||||
VALUES
|
||||
('INTERSECTION_001', '主要路口1号', 39.9042, 116.4074, 'AREA_A', '机场主要路口,连接航站楼和跑道区域'),
|
||||
('INTERSECTION_002', '次要路口2号', 39.9050, 116.4080, 'AREA_B', '机场次要路口,连接货运区域'),
|
||||
('INTERSECTION_003', '货运区路口', 39.9035, 116.4065, 'AREA_C', '货运区域主要路口'),
|
||||
('INTERSECTION_004', '维修区路口', 39.9055, 116.4085, 'AREA_D', '维修区域路口'),
|
||||
('INTERSECTION_005', '停机坪路口', 39.9045, 116.4070, 'AREA_A', '停机坪入口路口')
|
||||
('INTERSECTION_002', '次要路口2号', 39.9050, 116.4080, 'AREA_B', '机场次要路口,连接货运区域')
|
||||
ON CONFLICT (intersection_id) DO NOTHING;
|
||||
|
||||
INSERT INTO traffic_lights (device_id, device_name, ip_address, port, intersection_id, device_type, manufacturer, model, install_date)
|
||||
INSERT INTO traffic_lights (device_id, device_name, ip_address, intersection_id, device_type, manufacturer, model, install_date)
|
||||
VALUES
|
||||
('TL_001', '主路口红绿灯1号', '192.168.1.101', 8082, 'INTERSECTION_001', 'STANDARD', '海康威视', 'DS-TL100', '2024-01-01'),
|
||||
('TL_002', '次路口红绿灯2号', '192.168.1.102', 8082, 'INTERSECTION_002', 'STANDARD', '大华技术', 'DH-TL200', '2024-01-15'),
|
||||
('TL_003', '货运区红绿灯', '192.168.1.103', 8082, 'INTERSECTION_003', 'STANDARD', '海康威视', 'DS-TL100', '2024-02-01'),
|
||||
('TL_004', '维修区红绿灯', '192.168.1.104', 8082, 'INTERSECTION_004', 'HEAVY_DUTY', '大华技术', 'DH-TL300', '2024-02-15'),
|
||||
('TL_005', '停机坪红绿灯', '192.168.1.105', 8082, 'INTERSECTION_005', 'STANDARD', '海康威视', 'DS-TL100', '2024-03-01')
|
||||
('TL_001', '主路口红绿灯1号', '192.168.1.101', 'INTERSECTION_001', 'STANDARD', '海康威视', 'DS-TL100', '2024-01-01'),
|
||||
('TL_002', '次路口红绿灯2号', '192.168.1.102', 'INTERSECTION_002', 'STANDARD', '大华技术', 'DH-TL200', '2024-01-15')
|
||||
ON CONFLICT (device_id) DO NOTHING;
|
||||
|
||||
-- 添加注释
|
||||
@ -107,7 +99,6 @@ COMMENT ON COLUMN intersections.area_code IS '所属区域编码';
|
||||
COMMENT ON COLUMN traffic_lights.device_id IS '红绿灯设备唯一编号(可选)';
|
||||
COMMENT ON COLUMN traffic_lights.device_name IS '设备名称';
|
||||
COMMENT ON COLUMN traffic_lights.ip_address IS '红绿灯设备IP地址';
|
||||
COMMENT ON COLUMN traffic_lights.port IS '红绿灯设备端口号';
|
||||
COMMENT ON COLUMN traffic_lights.intersection_id IS '关联的路口编号';
|
||||
COMMENT ON COLUMN traffic_lights.is_online IS '设备是否在线';
|
||||
COMMENT ON COLUMN traffic_lights.last_heartbeat IS '最后一次心跳时间';
|
||||
Loading…
Reference in New Issue
Block a user