去掉不必要的Websocket消息缓存,完善数据库初始化脚本

This commit is contained in:
Tian jianyong 2025-06-27 17:17:19 +08:00
parent cbfb56127f
commit 180dea1845
33 changed files with 410 additions and 180 deletions

3
.gitignore vendored
View File

@ -43,4 +43,5 @@ __pycache__/
.venv/
### 日志 ###
logs/
logs/
*.log

View File

@ -9,11 +9,9 @@ import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

View File

@ -23,7 +23,7 @@ public class Response<T> {
/**
* 响应消息
*/
private String message;
private String msg;
/**
* 响应数据
@ -36,7 +36,7 @@ public class Response<T> {
public static <T> Response<T> success(T data) {
return Response.<T>builder()
.status(200)
.message("操作成功")
.msg("操作成功")
.data(data)
.build();
}
@ -47,7 +47,7 @@ public class Response<T> {
public static <T> Response<T> success(String message, T data) {
return Response.<T>builder()
.status(200)
.message(message)
.msg(message)
.data(data)
.build();
}
@ -58,7 +58,7 @@ public class Response<T> {
public static <T> Response<T> error(Integer status, String message) {
return Response.<T>builder()
.status(status)
.message(message)
.msg(message)
.build();
}

View File

@ -2,7 +2,6 @@ package com.dongni.collisionavoidance.common.service;
import com.dongni.collisionavoidance.common.model.spatial.AirportArea;
import com.dongni.collisionavoidance.common.model.repository.AirportAreaRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.Coordinate;
@ -31,8 +30,6 @@ public class AirportAreaService {
private final AirportAreaRepository airportAreaRepository;
private final GeometryFactory geometryFactory = new GeometryFactory();
private final WKTReader wktReader = new WKTReader(geometryFactory);
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 保存机场区域配置
*/

View File

@ -5,7 +5,6 @@ import com.dongni.collisionavoidance.common.model.spatial.VehicleLocation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.WKTReader;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@ -23,9 +22,6 @@ public class SpatialQueryService {
private final VehicleLocationService vehicleLocationService;
private final AirportAreaService airportAreaService;
private final GeometryFactory geometryFactory = new GeometryFactory();
private final WKTReader wktReader = new WKTReader(geometryFactory);
/**
* 综合空间查询结果
*/

View File

@ -117,7 +117,10 @@ public class VehicleLocationCacheService {
.collect(Collectors.toList());
List<VehicleLocation> locations = vehicleLocationRedisTemplate.opsForValue().multiGet(keys);
if (locations == null) {
return Collections.emptyMap();
}
Map<String, VehicleLocation> resultMap = new HashMap<>();
for (int i = 0; i < vehicleIds.size(); i++) {
VehicleLocation location = locations.get(i);

View File

@ -6,9 +6,6 @@ import com.dongni.collisionavoidance.common.model.spatial.AirportArea;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
/**
* 车辆权限管理服务
* 处理特勤车辆的特殊权限和业务规则
@ -223,25 +220,4 @@ public class VehiclePermissionService {
return "未知权限";
}
/**
* 解析允许的车辆类型JSON数组
*/
private List<String> parseAllowedVehicleTypes(String allowedVehicleTypesJson) {
if (allowedVehicleTypesJson == null || allowedVehicleTypesJson.trim().isEmpty()) {
return List.of();
}
try {
// 简单的JSON数组解析实际项目中可以使用Jackson
String cleaned = allowedVehicleTypesJson.replaceAll("[\\[\\]\"\\s]", "");
if (cleaned.isEmpty()) {
return List.of();
}
return Arrays.asList(cleaned.split(","));
} catch (Exception e) {
log.warn("解析允许车辆类型失败: {}", allowedVehicleTypesJson, e);
return List.of();
}
}
}

View File

@ -115,7 +115,6 @@ public class DataCollectorDao {
* @return 无人车位置信息列表
*/
public List<UnmannedVehicle> getVehicleLocationInfo() {
System.out.println("接口被调用");
try {
String url = UriComponentsBuilder
.fromUriString(vehicleBaseUrl)
@ -137,12 +136,12 @@ public class DataCollectorDao {
if (response.getStatusCode().is2xxSuccessful()) {
List<UnmannedVehicle> responseBody = response.getBody();
if (responseBody != null) {
log.info("成功获取定位信息,数量: {}", responseBody.size());
log.info("成功获取无人车定位信息,数量: {}", responseBody.size());
return responseBody;
}
}
} catch (Exception e) {
log.error("获取定位信息时发生异常", e);
log.error("获取无人车定位信息时发生异常", e);
return Collections.emptyList();
}
return Collections.emptyList();

View File

@ -208,6 +208,32 @@ public class DataCollectorService {
log.info("开始处理 {} 条无人车数据", unmannedVehicles.size());
// 先推送WebSocket位置更新事件
for (UnmannedVehicle vehicle : unmannedVehicles) {
try {
PositionUpdatePayload.Position position = PositionUpdatePayload.Position.builder()
.latitude(vehicle.getCurrentPosition().getLatitude())
.longitude(vehicle.getCurrentPosition().getLongitude())
.build();
PositionUpdatePayload payload = PositionUpdatePayload.builder()
.objectId(vehicle.getVehicleId())
.objectType(MovingObjectType.UNMANNED_VEHICLE.name())
.position(position)
.heading(vehicle.getHeading())
.speed(vehicle.getVelocity() != null ? vehicle.getVelocity().getSpeed() : null)
.timestamp(System.currentTimeMillis() * 1000) // 微秒级时间戳
.build();
eventPublisher.publishEvent(new PositionUpdateEvent(payload));
log.debug("处理无人车数据并发布事件: {} (车辆ID: {}, 位置: {}, {})",
vehicle.getVehicleId(),
vehicle.getVehicleId(),
vehicle.getCurrentPosition().getLongitude(),
vehicle.getCurrentPosition().getLatitude());
} catch (Exception e) {
log.error("处理无人车数据异常: vehicleId={}", vehicle.getVehicleId(), e);
}
}
// 转换为VehicleLocation对象列表
List<com.dongni.collisionavoidance.common.model.spatial.VehicleLocation> vehicleLocations =
unmannedVehicles.stream()

View File

@ -254,13 +254,20 @@ public class UnmannedVehicleControlService {
ResponseEntity<VehicleStateInfo[]> response = restTemplate.exchange(
url, HttpMethod.POST, requestEntity, VehicleStateInfo[].class);
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null && response.getBody().length > 0) {
VehicleStateInfo stateInfo = response.getBody()[0];
logger.info("从外部系统获取车辆状态成功: vehicleId={}", vehicleId);
return stateInfo;
if (response != null && response.getStatusCode().is2xxSuccessful()) {
VehicleStateInfo[] body = response.getBody();
if (body != null && body.length > 0) {
VehicleStateInfo stateInfo = body[0];
logger.info("从外部系统获取车辆状态成功: vehicleId={}", vehicleId);
return stateInfo;
} else {
logger.warn("外部系统未返回车辆状态: vehicleId={}", vehicleId);
return null;
}
} else {
logger.warn("外部系统未返回车辆状态: vehicleId={}", vehicleId);
logger.warn("外部系统调用失败: vehicleId={}, status={}", vehicleId, response != null ? response.getStatusCode() : "无响应");
return null;
}

View File

@ -4,8 +4,6 @@ import com.dongni.collisionavoidance.common.model.MovingObjectType;
import com.dongni.collisionavoidance.rule.model.entity.SpatialRule;
import com.dongni.collisionavoidance.rule.model.enums.RuleCategory;
import org.locationtech.jts.geom.Point;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

View File

@ -22,7 +22,6 @@ import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.beans.factory.annotation.Value;
import java.util.UUID;
@ -39,10 +38,6 @@ public class WebSocketMessageBroadcaster {
// 统一推送主题控制台接收所有消息
private static final String UNIFIED_TOPIC = "/topic/realtime";
// 是否启用消息缓存测试环境中禁用以避免序列化问题
@Value("${websocket.message.cache.enabled:true}")
private boolean messageCacheEnabled;
@Autowired
public WebSocketMessageBroadcaster(
SimpMessagingTemplate messagingTemplate,
@ -174,10 +169,8 @@ public class WebSocketMessageBroadcaster {
// 立即推送到控制台
messagingTemplate.convertAndSend(UNIFIED_TOPIC, message);
// 缓存消息用于重连恢复测试环境中可能禁用
if (messageCacheEnabled) {
messageCacheService.cacheMessage(message);
}
// 缓存消息用于重连恢复
messageCacheService.cacheMessage(message);
} catch (Exception e) {
System.err.println("Failed to broadcast message: " + e.getMessage());

View File

@ -57,7 +57,6 @@ public class MessageCacheService {
* @param count 获取消息数量
* @return 最近的消息列表
*/
@SuppressWarnings("unchecked")
public List<UniversalMessage<?>> getRecentMessages(String messageType, int count) {
try {
String key = CACHE_KEY_PREFIX + messageType;

View File

@ -0,0 +1,281 @@
-- ============================================
-- ⚠️ 脚本执行前警告:本操作将删除并重建整个数据库,所有原有数据将丢失!
-- 如仅需初始化结构,请注释掉下方 DROP/CREATE DATABASE 语句。
-- ============================================
-- 0. 删除并重建数据库(请根据实际数据库名修改)
-- 需以超级用户身份连接到postgres或其他管理数据库后执行
--
-- DROP DATABASE IF EXISTS collision_avoidance;
-- CREATE DATABASE collision_avoidance WITH ENCODING='UTF8' TEMPLATE=template0;
-- \c collision_avoidance
-- ============================================
-- 碰撞避免系统数据库重建脚本PostgreSQL 17 + PostGIS
-- 适用场景:全新初始化/重建,确保所有表结构、索引、触发器、注释、权限一次性到位
-- 依赖扩展postgis, postgis_topology
-- 执行权限需超级用户权限启用扩展后续表操作可用普通DBA权限
-- ============================================
-- 1. 启用PostGIS扩展
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;
-- 2. 通用函数与触发器
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION cleanup_old_vehicle_locations()
RETURNS INTEGER AS $$
DECLARE
deleted_count INTEGER;
BEGIN
DELETE FROM vehicle_locations
WHERE created_at < CURRENT_TIMESTAMP - INTERVAL '30 days';
GET DIAGNOSTICS deleted_count = ROW_COUNT;
RAISE NOTICE 'Cleaned up % old vehicle location records', deleted_count;
RETURN deleted_count;
END;
$$ LANGUAGE plpgsql;
-- 3. 车辆位置表
CREATE TABLE vehicle_locations (
id BIGSERIAL PRIMARY KEY,
vehicle_id VARCHAR(50) NOT NULL,
vehicle_type VARCHAR(20) NOT NULL CHECK (vehicle_type IN ('AIRCRAFT', 'AIRPORT_VEHICLE', 'UNMANNED_VEHICLE')),
location GEOMETRY(POINT, 4326) NOT NULL,
altitude DOUBLE PRECISION,
heading DOUBLE PRECISION CHECK (heading >= 0 AND heading < 360),
speed DOUBLE PRECISION CHECK (speed >= 0),
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
data_quality VARCHAR(20),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_vehicle_locations_vehicle_id ON vehicle_locations(vehicle_id);
CREATE INDEX idx_vehicle_locations_vehicle_type ON vehicle_locations(vehicle_type);
CREATE INDEX idx_vehicle_locations_timestamp ON vehicle_locations(timestamp DESC);
CREATE INDEX idx_vehicle_locations_created_at ON vehicle_locations(created_at);
CREATE INDEX idx_vehicle_locations_location_gist ON vehicle_locations USING GIST(location);
CREATE INDEX idx_vehicle_locations_compound ON vehicle_locations(vehicle_id, timestamp DESC);
CREATE INDEX idx_vehicle_locations_type_time ON vehicle_locations(vehicle_type, timestamp DESC);
CREATE TRIGGER trigger_vehicle_locations_updated_at
BEFORE UPDATE ON vehicle_locations
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- 4. 机场区域表(纯地理属性)
CREATE TABLE airport_areas (
id BIGSERIAL PRIMARY KEY,
area_id VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(100) NOT NULL,
type VARCHAR(30) NOT NULL,
description VARCHAR(500),
boundary GEOMETRY(POLYGON, 4326) NOT NULL,
active_time TIMESTAMP WITH TIME ZONE,
expiry_time TIMESTAMP WITH TIME ZONE,
enabled BOOLEAN NOT NULL DEFAULT true,
priority INTEGER NOT NULL DEFAULT 1 CHECK (priority >= 1),
version BIGINT NOT NULL DEFAULT 1,
area_sqm DOUBLE PRECISION,
functional_category VARCHAR(50),
parent_area_id VARCHAR(50),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_airport_areas_area_id ON airport_areas(area_id);
CREATE INDEX idx_airport_areas_name ON airport_areas(name);
CREATE INDEX idx_airport_areas_type ON airport_areas(type);
CREATE INDEX idx_airport_areas_priority ON airport_areas(priority DESC);
CREATE INDEX idx_airport_areas_enabled ON airport_areas(enabled);
CREATE INDEX idx_airport_areas_version ON airport_areas(version);
CREATE INDEX idx_airport_areas_boundary_gist ON airport_areas USING GIST(boundary);
CREATE INDEX idx_airport_areas_type_enabled ON airport_areas(type, enabled);
CREATE INDEX idx_airport_areas_enabled_priority ON airport_areas(enabled, priority DESC);
CREATE INDEX idx_airport_areas_functional_category ON airport_areas(functional_category);
CREATE INDEX idx_airport_areas_parent_area_id ON airport_areas(parent_area_id);
CREATE TRIGGER trigger_airport_areas_updated_at
BEFORE UPDATE ON airport_areas
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
COMMENT ON TABLE airport_areas IS '机场区域表 - 纯地理功能概念定义机场内各个区域的地理边界和功能属性。安全控制规则在geofences表中配置。';
-- 5. 车辆轨迹表
CREATE TABLE vehicle_trajectories (
id BIGSERIAL PRIMARY KEY,
vehicle_id VARCHAR(50) NOT NULL,
trajectory_date DATE NOT NULL,
trajectory_line GEOMETRY(LINESTRING, 4326),
total_distance DOUBLE PRECISION,
max_speed DOUBLE PRECISION,
avg_speed DOUBLE PRECISION,
duration_seconds INTEGER,
point_count INTEGER,
start_time TIMESTAMP WITH TIME ZONE NOT NULL,
end_time TIMESTAMP WITH TIME ZONE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_vehicle_trajectories_vehicle_id ON vehicle_trajectories(vehicle_id);
CREATE INDEX idx_vehicle_trajectories_date ON vehicle_trajectories(trajectory_date DESC);
CREATE INDEX idx_vehicle_trajectories_start_time ON vehicle_trajectories(start_time DESC);
CREATE INDEX idx_vehicle_trajectories_line_gist ON vehicle_trajectories USING GIST(trajectory_line);
CREATE INDEX idx_vehicle_trajectories_compound ON vehicle_trajectories(vehicle_id, trajectory_date DESC);
-- 6. 无人车控制指令表
CREATE TABLE vehicle_commands (
id BIGSERIAL PRIMARY KEY,
trans_id VARCHAR(100) NOT NULL,
timestamp TIMESTAMP NOT NULL,
vehicle_id VARCHAR(50) NOT NULL,
command_type VARCHAR(20) NOT NULL CHECK (command_type IN ('ALERT', 'SIGNAL', 'WARNING', 'RESUME', 'PARKING')),
command_reason VARCHAR(30) NOT NULL CHECK (command_reason IN ('TRAFFIC_LIGHT', 'AIRCRAFT_CROSSING', 'SPECIAL_VEHICLE', 'AIRCRAFT_PUSH', 'RESUME_TRAFFIC', 'PARKING_SIDE')),
signal_state VARCHAR(10) CHECK (signal_state IN ('RED', 'YELLOW', 'GREEN')),
intersection_id VARCHAR(50),
target_location GEOMETRY(POINT, 4326) NOT NULL,
relative_speed DOUBLE PRECISION,
relative_motion_x DOUBLE PRECISION,
relative_motion_y DOUBLE PRECISION,
min_distance DOUBLE PRECISION,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_vehicle_commands_vehicle_id ON vehicle_commands(vehicle_id);
CREATE INDEX idx_vehicle_commands_timestamp ON vehicle_commands(timestamp DESC);
CREATE INDEX idx_vehicle_commands_trans_id ON vehicle_commands(trans_id);
CREATE INDEX idx_vehicle_commands_command_type ON vehicle_commands(command_type);
CREATE INDEX idx_vehicle_commands_created_at ON vehicle_commands(created_at);
CREATE INDEX idx_vehicle_commands_location_gist ON vehicle_commands USING GIST(target_location);
CREATE INDEX idx_vehicle_commands_vehicle_time ON vehicle_commands(vehicle_id, timestamp DESC);
CREATE INDEX idx_vehicle_commands_type_time ON vehicle_commands(command_type, timestamp DESC);
COMMENT ON TABLE vehicle_commands IS '无人车控制指令表,用于存储发送给无人车的控制指令,支持轨迹回放和日志审计';
-- 7. 空间规则表以V009为准
CREATE TABLE spatial_rules (
rule_id VARCHAR(50) PRIMARY KEY,
rule_name VARCHAR(200) NOT NULL,
description TEXT,
rule_category VARCHAR(50) NOT NULL CHECK (rule_category IN (
'ACCESS_CONTROL', 'SPEED_LIMIT', 'VEHICLE_RESTRICTION',
'HEIGHT_LIMIT', 'WEIGHT_LIMIT', 'TIME_RESTRICTION',
'PRIORITY_CONTROL', 'SAFETY_ZONE', 'CUSTOM'
)),
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' CHECK (status IN (
'ACTIVE', 'INACTIVE', 'SUSPENDED', 'EXPIRED'
)),
priority INTEGER NOT NULL DEFAULT 1 CHECK (priority >= 1),
spatial_object_type VARCHAR(30) NOT NULL CHECK (spatial_object_type IN (
'AREA', 'ROAD', 'INTERSECTION', 'CUSTOM_GEOMETRY'
)),
spatial_object_id VARCHAR(50),
custom_geometry GEOMETRY(POLYGON, 4326),
rule_parameters JSONB DEFAULT '{}',
alert_level VARCHAR(20) NOT NULL DEFAULT 'WARNING' CHECK (alert_level IN (
'INFO', 'WARNING', 'CRITICAL', 'EMERGENCY'
)),
alert_message VARCHAR(500),
effective_start_time TIMESTAMP,
effective_end_time TIMESTAMP,
daily_start_time TIME,
daily_end_time TIME,
weekday_pattern INTEGER,
time_patterns JSONB,
created_by VARCHAR(100),
updated_by VARCHAR(100),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
version BIGINT NOT NULL DEFAULT 1
);
CREATE INDEX idx_spatial_rules_status ON spatial_rules(status);
CREATE INDEX idx_spatial_rules_category ON spatial_rules(rule_category);
CREATE INDEX idx_spatial_rules_object_type ON spatial_rules(spatial_object_type);
CREATE INDEX idx_spatial_rules_priority ON spatial_rules(priority DESC);
CREATE INDEX idx_spatial_rules_spatial_object ON spatial_rules(spatial_object_type, spatial_object_id);
CREATE INDEX idx_spatial_rules_effective_time ON spatial_rules(effective_start_time, effective_end_time);
CREATE INDEX idx_spatial_rules_daily_time ON spatial_rules(daily_start_time, daily_end_time);
CREATE INDEX idx_spatial_rules_custom_geometry_gist ON spatial_rules USING GIST(custom_geometry);
CREATE INDEX idx_spatial_rules_parameters_gin ON spatial_rules USING GIN(rule_parameters);
CREATE INDEX idx_spatial_rules_time_patterns_gin ON spatial_rules USING GIN(time_patterns);
CREATE TRIGGER trigger_spatial_rules_updated_at
BEFORE UPDATE ON spatial_rules
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
COMMENT ON TABLE spatial_rules IS '空间规则表 - 统一管理所有空间安全规则,支持多层次时间控制';
-- 8. 空间规则允许车辆类型关联表
CREATE TABLE spatial_rule_vehicle_types (
rule_id VARCHAR(50) NOT NULL,
vehicle_type VARCHAR(30) NOT NULL CHECK (vehicle_type IN (
'AIRCRAFT', 'AIRPORT_VEHICLE', 'UNMANNED_VEHICLE', 'SPECIAL_VEHICLE'
)),
PRIMARY KEY (rule_id, vehicle_type),
FOREIGN KEY (rule_id) REFERENCES spatial_rules(rule_id) ON DELETE CASCADE
);
CREATE INDEX idx_spatial_rule_vehicle_types_rule_id ON spatial_rule_vehicle_types(rule_id);
CREATE INDEX idx_spatial_rule_vehicle_types_vehicle_type ON spatial_rule_vehicle_types(vehicle_type);
COMMENT ON TABLE spatial_rule_vehicle_types IS '空间规则允许车辆类型关联表';
-- 9. 规则违规事件表
CREATE TABLE rule_violation_events (
event_id VARCHAR(36) PRIMARY KEY,
rule_id VARCHAR(50) NOT NULL,
vehicle_id VARCHAR(50) NOT NULL,
vehicle_type VARCHAR(30) NOT NULL CHECK (vehicle_type IN (
'AIRCRAFT', 'AIRPORT_VEHICLE', 'UNMANNED_VEHICLE', 'SPECIAL_VEHICLE'
)),
violation_type VARCHAR(50) NOT NULL,
alert_level VARCHAR(20) NOT NULL CHECK (alert_level IN (
'INFO', 'WARNING', 'CRITICAL', 'EMERGENCY'
)),
violation_timestamp TIMESTAMP NOT NULL,
violation_location GEOMETRY(POINT, 4326),
violation_description TEXT,
vehicle_state JSONB,
rule_details JSONB,
severity_score INTEGER NOT NULL,
processed BOOLEAN NOT NULL DEFAULT FALSE,
processed_at TIMESTAMP,
processed_by VARCHAR(100),
processing_result TEXT,
response_actions JSONB,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
metadata JSONB
);
CREATE INDEX idx_violation_events_vehicle_id ON rule_violation_events(vehicle_id);
CREATE INDEX idx_violation_events_rule_id ON rule_violation_events(rule_id);
CREATE INDEX idx_violation_events_violation_type ON rule_violation_events(violation_type);
CREATE INDEX idx_violation_events_alert_level ON rule_violation_events(alert_level);
CREATE INDEX idx_violation_events_timestamp ON rule_violation_events(violation_timestamp DESC);
CREATE INDEX idx_violation_events_processed ON rule_violation_events(processed);
CREATE INDEX idx_violation_events_location_gist ON rule_violation_events USING GIST(violation_location);
COMMENT ON TABLE rule_violation_events IS '规则违规事件表 - 记录所有违规事件及其处理状态';
-- 10. 权限设置(可选)
-- GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO collision_avoidance_app;
-- GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO collision_avoidance_app;
-- 11. 示例数据(可选,按需取消注释)
/*
INSERT INTO airport_areas (area_id, name, type, description, boundary, priority, enabled, functional_category) VALUES
('runway_01', 'Runway 01', 'RUNWAY', '主跑道01长度3200米',
ST_GeomFromText('POLYGON((116.5 39.8, 116.52 39.8, 116.52 39.81, 116.5 39.81, 116.5 39.8))', 4326),
10, true, 'AIRCRAFT_OPERATIONS'),
('taxiway_a', 'Taxiway A', 'TAXIWAY', '连接跑道和停机坪的滑行道A',
ST_GeomFromText('POLYGON((116.51 39.79, 116.515 39.79, 116.515 39.795, 116.51 39.795, 116.51 39.79))', 4326),
5, true, 'AIRCRAFT_MOVEMENT'),
('terminal_area', 'Terminal Area', 'TERMINAL', 'T1航站楼及周边区域',
ST_GeomFromText('POLYGON((116.48 39.78, 116.50 39.78, 116.50 39.80, 116.48 39.80, 116.48 39.78))', 4326),
3, true, 'PASSENGER_SERVICES'),
('temp_construction_01', 'Temporary Construction Zone', 'TEMPORARY_CONSTRUCTION', '滑行道扩建临时施工区域',
ST_GeomFromText('POLYGON((116.49 39.785, 116.495 39.785, 116.495 39.790, 116.49 39.790, 116.49 39.785))', 4326),
1, true, 'CONSTRUCTION');
*/
-- 12. 结尾说明
-- ============================================
-- 数据库结构初始化完成。请根据实际业务需要补充数据、权限和后续配置。
-- ============================================

View File

@ -138,7 +138,7 @@ class AirportAreaServiceIntegrationTest {
assertThat(area).isNotNull();
assertThat(area.getName()).isEqualTo("测试区域");
assertThat(area.getType()).isEqualTo("TEST");
assertThat(area.getBoundary()).isNotNull();
assertThat((Object)area.getBoundary()).isNotNull();
assertThat(area.getBoundary().getSRID()).isEqualTo(4326);
}

View File

@ -228,7 +228,7 @@ class AirportAreaRepositoryTest {
assertThat(area).isPresent();
Polygon boundary = area.get().getBoundary();
assertThat(boundary).isNotNull();
assertThat((Object) boundary).isNotNull();
assertThat(boundary.getSRID()).isEqualTo(4326); // WGS84坐标系
assertThat(boundary.isValid()).isTrue();
assertThat(boundary.getArea()).isGreaterThan(0);
@ -271,7 +271,7 @@ class AirportAreaRepositoryTest {
// 测试中心点计算
Point centroid = aa.getCentroid();
assertThat(centroid).isNotNull();
assertThat((Object) centroid).isNotNull();
assertThat(centroid.getX()).isBetween(116.0, 117.0);
assertThat(centroid.getY()).isBetween(39.0, 40.0);

View File

@ -250,7 +250,7 @@ class VehicleLocationRepositoryTest {
assertThat(vehicle).isPresent();
Point location = vehicle.get().getLocation();
assertThat(location).isNotNull();
assertThat((Object)location).isNotNull();
assertThat(location.getSRID()).isEqualTo(4326); // WGS84坐标系
assertThat(location.getX()).isBetween(116.0, 117.0);
assertThat(location.getY()).isBetween(39.0, 40.0);

View File

@ -1,6 +1,5 @@
package com.dongni.collisionavoidance.controller;
import com.dongni.collisionavoidance.common.model.dto.Response;
import com.dongni.collisionavoidance.common.model.spatial.VehicleLocation;
import com.dongni.collisionavoidance.datacollector.model.dto.VehicleCommand;
import com.dongni.collisionavoidance.datacollector.model.dto.VehicleStateInfo;
@ -74,7 +73,7 @@ class UnmannedVehicleControllerTest {
.content(objectMapper.writeValueAsString(command)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(200))
.andExpect(jsonPath("$.message").value("控制指令执行成功"));
.andExpect(jsonPath("$.msg").value("控制指令执行成功"));
}
/**

View File

@ -6,8 +6,6 @@ import com.dongni.collisionavoidance.common.service.VehicleLocationService;
import com.dongni.collisionavoidance.datacollector.model.entity.VehicleCommandEntity;
import com.dongni.collisionavoidance.datacollector.model.enums.CommandReason;
import com.dongni.collisionavoidance.datacollector.model.enums.CommandType;
import com.dongni.collisionavoidance.datacollector.repository.VehicleCommandRepository;
import com.dongni.collisionavoidance.datacollector.service.VehicleDataPersistenceService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
@ -44,9 +42,6 @@ class VehicleDataPersistenceServiceIntegrationTest {
@Autowired
private VehicleLocationService vehicleLocationService;
@Autowired
private VehicleCommandRepository vehicleCommandRepository;
private final GeometryFactory geometryFactory = new GeometryFactory();
/**

View File

@ -130,7 +130,7 @@ class RuleExecutionEngineTest {
// 验证事件发布只验证开始事件因为完成事件可能因业务逻辑而不同
verify(ruleEventPublisher).publishRuleExecutionStarted(eq(testRule), eq(vehicleId),
any(), any(Map.class));
any(), anyMap());
}
@Test
@ -147,7 +147,7 @@ class RuleExecutionEngineTest {
assertNotNull(result);
// 验证执行开始事件
verify(ruleEventPublisher).publishRuleExecutionStarted(eq(testRule), eq(vehicleId),
any(), any(Map.class));
any(), anyMap());
}
@Test
@ -184,7 +184,7 @@ class RuleExecutionEngineTest {
// 验证每个规则都被执行
verify(ruleEventPublisher, times(2)).publishRuleExecutionStarted(
any(SpatialRule.class), eq(vehicleId), any(), any(Map.class));
any(SpatialRule.class), eq(vehicleId), any(), anyMap());
}
@Test

View File

@ -16,7 +16,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationEventPublisher;
@ -126,7 +125,7 @@ public class RuleViolationRealtimePushTest {
any(SpatialRule.class),
any(VehicleLocation.class),
any(RuleExecutionResult.class),
any(Map.class)
anyMap()
)).thenReturn(testViolationEvent);
// When: 创建违规事件
@ -146,7 +145,7 @@ public class RuleViolationRealtimePushTest {
any(SpatialRule.class),
any(VehicleLocation.class),
any(RuleExecutionResult.class),
any(Map.class)
anyMap()
);
}

View File

@ -1,7 +1,6 @@
package com.dongni.collisionavoidance.geofence.service;
import com.dongni.collisionavoidance.common.model.MovingObjectType;
import com.dongni.collisionavoidance.common.service.VehicleLocationService;
import com.dongni.collisionavoidance.common.model.spatial.VehicleLocation;
import com.dongni.collisionavoidance.geofence.model.enums.*;
import com.dongni.collisionavoidance.rule.event.RuleViolationEvent;
@ -57,9 +56,6 @@ public class SpatialRuleIntegrationTest {
@Autowired
private RealTimeViolationDetector realTimeViolationDetector;
@Autowired
private VehicleLocationService vehicleLocationService;
@Autowired
private SpatialRuleRepository spatialRuleRepository;

View File

@ -228,12 +228,4 @@ class RoadNetworkServiceIntegrationTest {
assertThat(roadNetworkService.getRoadById("road-invalid-geom")).isEmpty();
// Also check spatial index doesn't contain it (harder to test directly)
}
// Helper method example (if roadInfoMap was accessible or we add a getter for tests)
private boolean roadInfoMapContainsName(String name) {
// This requires access to roadInfoMap or a dedicated getter in the service
// return roadNetworkService.getRoadInfoMap().values().stream()
// .anyMatch(info -> name.equals(info.getName()));
return false; // Placeholder
}
}

View File

@ -2,9 +2,6 @@ package com.dongni.collisionavoidance.websocket.event;
import org.junit.jupiter.api.Test;
import com.dongni.collisionavoidance.websocket.event.CollisionWarningEvent;
import com.dongni.collisionavoidance.websocket.event.PositionUpdateEvent;
import com.dongni.collisionavoidance.websocket.event.TrafficLightStatusEvent;
import com.dongni.collisionavoidance.websocket.message.*;
import static org.junit.jupiter.api.Assertions.*;

View File

@ -24,29 +24,12 @@ spring:
# 禁用Flyway在测试中执行 - 使用JPA自动建表
flyway:
enabled: false
# 禁用MongoDB自动配置
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
- org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration
# 禁用数据服务
data:
mongodb:
auto-index-creation: false
redis:
repositories:
enabled: false
# 禁用Kafka
kafka:
bootstrap-servers:
producer:
bootstrap-servers:
consumer:
bootstrap-servers:
auto-startup: false
# 禁用调度和异步任务
task:
@ -64,12 +47,6 @@ spring:
# 测试模式标记
test-mode: true
# WebSocket配置
websocket:
message:
cache:
enabled: false # 禁用消息缓存以避免序列化问题
# 数据采集器配置
data:
collector:
@ -86,14 +63,6 @@ data:
processor:
enabled: false # 禁用数据处理器
# 规则检测配置
rule:
detection:
async:
enabled: false # 禁用异步规则检测,避免测试中的复杂事件链
# 日志配置
logging:
level:

View File

@ -133,6 +133,22 @@ if qn002_dist > 0:
# 修改车辆数据中 QN002 的初始位置和方向
vehicle_data = [
{
"vehicleNo": "TQ001", # 特勤车
"longitude": POINT_T4["longitude"],
"latitude": POINT_T4["latitude"],
"time": int(time.time() * 1000),
"direction": 270.0, # 向西方向为270度
"speed": DEFAULT_VEHICLE_SPEED
},
{
"vehicleNo": "BD001", # 摆渡车
"longitude": POINT_T5["longitude"],
"latitude": POINT_T5["latitude"],
"time": int(time.time() * 1000),
"direction": 0.0, # 向北方向为0度
"speed": DEFAULT_VEHICLE_SPEED
},
{
"vehicleNo": "QN001", # 无人车1
"longitude": POINT_T1["longitude"],
@ -148,17 +164,13 @@ vehicle_data = [
"time": int(time.time() * 1000),
"direction": 90.0, # 向东方向为90度
"speed": DEFAULT_VEHICLE_SPEED
},
{
"vehicleNo": "TQ001", # 特勤车
"longitude": POINT_T4["longitude"],
"latitude": POINT_T4["latitude"],
"time": int(time.time() * 1000),
"direction": 270.0, # 向西方向为270度
"speed": DEFAULT_VEHICLE_SPEED
}
]
# 拆分
airport_vehicle_data = [v for v in vehicle_data if not v["vehicleNo"].startswith("QN")]
unmanned_vehicle_data = [v for v in vehicle_data if v["vehicleNo"].startswith("QN")]
# 添加车辆状态类
class VehicleState:
def __init__(self, vehicle_no):
@ -872,7 +884,7 @@ def switch_traffic_light_state():
@app.route('/openApi/getCurrentVehiclePositions', methods=['GET', 'OPTIONS'])
def get_vehicle_positions():
"""获取当前车辆位置信息"""
"""获取当前车辆位置信息(仅机场车辆)"""
if request.method == 'OPTIONS':
return '', 204
if not check_auth():
@ -884,24 +896,29 @@ def get_vehicle_positions():
global last_vehicle_update_time
current_time = time.time()
elapsed_time = current_time - last_vehicle_update_time
try:
# 统一处理红绿灯状态切换
# switch_traffic_light_state() # 采用模拟的红绿灯脚本,注释掉此处的调用
# 只在达到更新间隔时更新位置
if elapsed_time >= UPDATE_INTERVAL:
for vehicle in vehicle_data:
for vehicle in airport_vehicle_data:
update_vehicle_position(vehicle, UPDATE_INTERVAL)
vehicle["time"] = int(current_time * 1000)
last_vehicle_update_time = current_time
response_data = []
for v in airport_vehicle_data:
v_out = {
"vehicleNo": v.get("vehicleNo"),
"longitude": v.get("longitude"),
"latitude": v.get("latitude"),
"time": v.get("time"),
"direction": v.get("direction"),
"speed": v.get("speed")
}
response_data.append(v_out)
return jsonify({
"status": 200,
"msg": "当前车辆实时位置数据",
"data": vehicle_data
"data": response_data
})
except Exception as e:
print(f"Error in get_vehicle_positions: {str(e)}")
return jsonify({
@ -1036,18 +1053,17 @@ def generate_unmanned_vehicle_location_data():
unmanned_vehicles = []
# 从现有vehicle_data中筛选无人车QN开头的车辆
for vehicle in vehicle_data:
if vehicle["vehicleNo"].startswith("QN"):
location_info = {
"transId": str(uuid.uuid4()),
"timestamp": int(time.time() * 1000),
"vehicleID": vehicle["vehicleNo"],
"latitude": vehicle["latitude"],
"longitude": vehicle["longitude"],
"speed": vehicle["speed"] / 3.6, # 转换为m/s
"direction": math.radians(vehicle["direction"]) # 转换为弧度
}
unmanned_vehicles.append(location_info)
for vehicle in unmanned_vehicle_data:
location_info = {
"transId": str(uuid.uuid4()),
"timestamp": int(time.time() * 1000),
"vehicleID": vehicle["vehicleNo"],
"latitude": vehicle["latitude"],
"longitude": vehicle["longitude"],
"speed": vehicle["speed"] / 3.6, # 转换为m/s
"direction": math.radians(vehicle["direction"]) # 转换为弧度
}
unmanned_vehicles.append(location_info)
return unmanned_vehicles
@ -1081,28 +1097,27 @@ def generate_unmanned_vehicle_state_data(vehicle_id=None, is_single=True):
vehicle_states_data.append(state_info)
else:
# 所有车辆状态查询
for vehicle in vehicle_data:
if vehicle["vehicleNo"].startswith("QN"):
vehicle_state = vehicle_states.get(vehicle["vehicleNo"])
state_info = {
"transId": str(uuid.uuid4()),
"timestamp": int(time.time() * 1000),
"vehicleID": vehicle["vehicleNo"],
"loginState": True,
"faultInfo": [],
"activeSafety": not vehicle_state.is_running if vehicle_state else False,
"RC": False,
"Command": 1 if vehicle_state and vehicle_state.current_command == "ALERT" else 0,
"airportInfo": [],
"vehicleMode": 2,
"gearState": 2,
"chassisReady": vehicle_state.is_running if vehicle_state else True,
"collisionStatus": False,
"clearance": 1 if vehicle_state and vehicle_state.is_running else 0,
"turnSignalStstus": 0,
"pointCloud": []
}
vehicle_states_data.append(state_info)
for vehicle in unmanned_vehicle_data:
vehicle_state = vehicle_states.get(vehicle["vehicleNo"])
state_info = {
"transId": str(uuid.uuid4()),
"timestamp": int(time.time() * 1000),
"vehicleID": vehicle["vehicleNo"],
"loginState": True,
"faultInfo": [],
"activeSafety": not vehicle_state.is_running if vehicle_state else False,
"RC": False,
"Command": 1 if vehicle_state and vehicle_state.current_command == "ALERT" else 0,
"airportInfo": [],
"vehicleMode": 2,
"gearState": 2,
"chassisReady": vehicle_state.is_running if vehicle_state else True,
"collisionStatus": False,
"clearance": 1 if vehicle_state and vehicle_state.is_running else 0,
"turnSignalStstus": 0,
"pointCloud": []
}
vehicle_states_data.append(state_info)
return vehicle_states_data
@ -1112,25 +1127,19 @@ def get_unmanned_vehicle_location():
"""无人车位置上报接口"""
if request.method == 'OPTIONS':
return '', 204
try:
# 更新车辆位置
current_time = time.time()
global last_vehicle_update_time
elapsed_time = current_time - last_vehicle_update_time
if elapsed_time >= UPDATE_INTERVAL:
for vehicle in vehicle_data:
for vehicle in unmanned_vehicle_data:
update_vehicle_position(vehicle, UPDATE_INTERVAL)
vehicle["time"] = int(current_time * 1000)
last_vehicle_update_time = current_time
# 生成无人车位置数据
location_data = generate_unmanned_vehicle_location_data()
print(f"返回无人车位置数据,数量: {len(location_data)}")
return jsonify(location_data)
except Exception as e:
print(f"Error in get_unmanned_vehicle_location: {str(e)}")
return jsonify([]), 500