删掉红绿灯的端口字段

This commit is contained in:
Tian jianyong 2025-08-07 17:33:44 +08:00
parent 77f5cdc880
commit 67478b0b98
12 changed files with 526 additions and 899 deletions

View File

@ -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地址的设备数量
*

View File

@ -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地址
*

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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方法中准备创建VehicleLocationMovingObject的速度为: {}", movingObject.getCurrentSpeed());
log.debug("在createTemporaryVehicleLocationForDetection方法中准备创建VehicleLocationMovingObject的速度为: {}",
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);
}

View File

@ -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);
}
}

View File

@ -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());

View File

@ -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());
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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;

View File

@ -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 '最后一次心跳时间';