修改mock进出港和按路由运动不一致的情况

This commit is contained in:
Tian jianyong 2025-09-28 11:13:02 +08:00
parent a9c2e7e32d
commit 317721d1a8
4 changed files with 2091188 additions and 103 deletions

2090670
deploy/qaup_database_export.sql Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,115 @@
# PostgreSQL 数据库迁移指南
## 概述
本文档描述如何将本地 PostgreSQL 数据库完全覆盖导入到远程服务器。
**场景:**
- 本地数据库用户postgres
- 远程数据库用户qaup
- 数据库名称qaup
- 操作方式:完全覆盖
## 第一步:本地数据库导出
### 导出完整数据库
```bash
# 导出数据库(包含结构和数据,处理用户名差异)
pg_dump -h localhost -U postgres -d qaup --no-owner --no-privileges --clean --if-exists -f qaup_database_export.sql
```
**参数说明:**
- `--no-owner`: 不包含对象所有者信息,避免用户名冲突
- `--no-privileges`: 不包含权限设置,导入后重新设置权限
- `--clean --if-exists`: 清理现有对象(如果存在)
- `-f`: 指定输出文件名
## 第二步:远程服务器完全覆盖
```bash
# 1. 删除现有数据库
psql -U qaup -d postgres -c "DROP DATABASE IF EXISTS qaup;"
# 2. 重新创建数据库
psql -U qaup -d postgres -c "CREATE DATABASE qaup OWNER qaup;"
# 3. 设置PostGIS扩展
psql -U qaup -d qaup -c "CREATE EXTENSION IF NOT EXISTS postgis;"
# 4. 导入数据
psql -U qaup -d qaup -f qaup_database_export.sql
```
## 第三步:验证导入结果
### 检查数据库结构
```bash
# 查看所有表
psql -U qaup -d qaup -c "\dt"
# 查看特定表结构
psql -U qaup -d qaup -c "\d traffic_lights"
```
### 验证数据完整性
```bash
# 检查关键表的数据量
psql -U qaup -d qaup -c "SELECT count(*) FROM traffic_lights;"
psql -U qaup -d qaup -c "SELECT count(*) FROM intersections;"
psql -U qaup -d qaup -c "SELECT count(*) FROM sys_vehicle_info;"
```
### 检查用户权限
```bash
# 确保qaup用户拥有所有表的权限
psql -U qaup -d qaup -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO qaup;"
psql -U qaup -d qaup -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO qaup;"
```
## 注意事项
1. **备份重要性**:操作前确保远程数据库已备份(如有重要数据)
2. **网络连接**:确保网络稳定,大数据库传输可能需要较长时间
3. **磁盘空间**:确认远程服务器有足够存储空间
4. **权限检查**确保qaup用户有CREATE DATABASE权限方案A
5. **应用停机**:建议在应用停机时间窗口执行,避免数据不一致
## 故障排除
### 连接失败
```bash
# 检查用户权限
psql -U qaup -d postgres -c "SELECT current_user;"
```
### 导入错误
```bash
# 查看详细错误信息
psql -U qaup -d qaup -f qaup_database_export.sql 2>&1 | tee import.log
```
### 权限问题
```bash
# 重置所有权限
psql -U qaup -d qaup -c "
GRANT ALL PRIVILEGES ON DATABASE qaup TO qaup;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO qaup;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO qaup;
GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO qaup;
"
```
## 完成后清理
```bash
# 删除本地导出文件(可选)
rm qaup_database_export.sql
# 删除临时脚本如果使用了方案B
rm drop_tables.sql
```
---
**创建时间:** 2025-09-25
**适用版本:** PostgreSQL 12+
**测试环境:** QAUP-Management 1.0.1

View File

@ -0,0 +1,298 @@
# 项目相关配置
qaup:
# 名称
name: Qaup
# 版本
version: 1.0.1
# 版权年份
copyrightYear: 2025
# 文件路径 示例( Windows配置D:/qaup/uploadPathLinux配置 /home/qaup/uploadPath
profile: ${UPLOAD_PATH:/app/uploadPath}
# 获取ip地址开关
addressEnabled: false
# 验证码类型 math 数字计算 char 字符验证
captchaType: math
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 8080
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 2000
# 使用JDK21虚拟线程不需要限制线程数
threads:
# 虚拟线程模式下可以处理更多请求
max: 2000
# Tomcat启动初始化的线程数
min-spare: 50
# 日志配置
logging:
level:
com.qaup: debug
org.springframework: warn
# 用户配置
user:
password:
# 密码最大错误次数
maxRetryCount: 5
# 密码锁定时间默认10分钟
lockTime: 10
# Spring配置
spring:
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages
profiles:
active: dev,druid
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
# 服务模块
devtools:
restart:
# 热部署开关
enabled: true
# 重启目录
additional-paths: src/main/java
# 排除目录
exclude: WEB-INF/**
jackson:
# 日期格式化
date-format: yyyy-MM-dd HH:mm:ss
serialization:
# 格式化输出
indent_output: false
# 忽略无法转换的对象
fail_on_empty_beans: false
deserialization:
# 允许对象忽略json中不存在的属性
fail_on_unknown_properties: false
# redis 配置
data:
redis:
# 地址
host: 172.17.0.4
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# ==================== CollisionAvoidanceSystem 配置整合 ====================
# Flyway数据库迁移配置
flyway:
# 启用Flyway
enabled: true
# 迁移脚本位置
locations: classpath:db/migration
# 基线迁移设置
baseline-on-migrate: true
baseline-version: 1.0.0
baseline-description: "Initial baseline from existing database"
# 迁移验证
validate-on-migrate: true
# 生产环境禁用clean操作
clean-disabled: true
# 不允许乱序迁移
out-of-order: false
# 编码设置
encoding: UTF-8
# 占位符配置
placeholder-replacement: false
# JPA配置collision模块空间数据处理
jpa:
hibernate:
ddl-auto: none # 使用数据库管理方式
show-sql: false
properties:
hibernate:
format_sql: false
jdbc:
lob:
non_contextual_creation: true
batch_size: 50
fetch_size: 50
cache:
use_second_level_cache: false
use_query_cache: false
order_inserts: true
order_updates: true
batch_versioned_data: true
generate_statistics: false
# token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期默认30分钟
expireTime: 30
# MyBatis配置
mybatis:
# 搜索指定包别名
typeAliasesPackage: com.qaup.system.domain,com.qaup.common.core.domain.entity,com.qaup.generator.domain,com.qaup.quartz.domain,com.qaup.collision.common.model.spatial,com.qaup.collision.datacollector.model.dto,com.qaup.collision.geofence.model.entity
# 配置mapper的扫描找到所有的mapper.xml映射文件
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 加载全局的配置文件
configLocation: classpath:mybatis/mybatis-config.xml
# PageHelper分页插件
pagehelper:
helperDialect: postgresql
reasonable: true
supportMethodsArguments: true
params: count=countSql
# SpringDoc 配置
springdoc:
swagger-ui:
path: /swagger-ui.html # Swagger UI 访问路径
url: /v3/api-docs # OpenAPI JSON 文档路径
disable-swagger-default-url: true
api-docs:
path: /v3/api-docs # OpenAPI JSON 文档路径
# 防止XSS攻击
xss:
# 过滤开关
enabled: true
# 排除链接(多个用逗号分隔)
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
# 数据采集配置collision模块
data:
collector:
# 数据采集间隔,单位:毫秒(高频采集保证数据新鲜度)
interval: 250
# 检测和推送间隔配置
detection:
# 检测间隔单位毫秒控制围栏检测、冲突检测、违规检测和WebSocket推送频率
interval: 1000
# 机场数据源配置
airport-api:
base-url: http://10.64.58.228:8090
endpoints:
login: /login
refresh: /userInfoController/refreshToken
aircraft: /openApi/getCurrentFlightPositions
vehicle: /openApi/getCurrentVehiclePositions
arrival-route: /runwayPathPlanningController/findArrTaxiwayByRunwayAndContactCrossAndSeat
departure-route: /runwayPathPlanningController/findDepTaxiwayByRunwayAndContactCrossAndSeat
aircraft-status: /aircraftStatusController/getAircraftStatus
flight-notification: /openApi/getInboundAndOutboundFlightsNotification
auth:
username: dianxin
password: dianxin@123
# 无人车厂商数据源配置
vehicle-api:
base-url: http://10.64.58.228:8091
endpoints:
vehicle-command: /api/VehicleCommandInfo
# 通用车辆状态API端点符合universal_autonomous_vehicle_api规范
universal-status: /api/v1/vehicles/{vehicleId}/status
timeout: 1000
retry-attempts: 3
# 无人车数据持久化配置
unmanned-vehicle:
persistence:
enabled: true
batch-size: 50
location-retention-days: 90
command-retention-days: 365
command:
timeout: 1000
retry-attempts: 3
validation:
enabled: true
strict-mode: false
retention:
redis-expire-seconds: 60
postgresql-days: 30
# 红绿灯系统配置collision模块
traffic:
light:
tcp:
# 是否启用TCP服务器
enabled: true
# TCP监听端口
port: 8082
# 最大连接数
max-connections: 50
# 连接超时时间(毫秒)
connection-timeout: 30000
# 心跳超时时间(分钟)
heartbeat-timeout-minutes: 5
intersection:
# 默认路口ID当信号中没有指定时使用
default-id: "DEFAULT_INTERSECTION"
processing:
# 是否启用统计功能
enable-statistics: true
# 统计信息输出间隔(毫秒)
statistics-interval: 60000
# 是否启用调试日志
enable-debug-log: false
# 信号处理超时时间(毫秒)
signal-process-timeout: 5000
# 坐标系统配置collision模块
coordinate-system:
airport:
center-longitude: 120.0834104
center-latitude: 36.35406879
# 性能监控配置collision模块扩展
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
metrics:
export:
simple:
enabled: true
enable:
hikari: true
jvm: true
jmx:
enabled: true

View File

@ -103,32 +103,21 @@ AIRCRAFT_SIZE_M = 30.0
VEHICLE_SIZE_M = 10.0
# 航班数据配置 - 用于进出港查询接口(简化版)
# 修改为与路由参数配置一致的航班
current_time = time.time()
# 移除时间周期控制,仅保留基础信息供航班通知使用
flight_data = {
"CA1234": { # 出港航班
"flightNo": "CA1234",
"type": "OUT",
"runway": "17",
"contactCross": "A2",
"seat": "201",
"cycle_start": current_time,
"active_duration": 60, # 1分钟活跃期
"gap_duration": 5, # 5秒间隔期
"fixed_time": int(current_time * 1000), # 固定的滑出时间
"status": "active"
"seat": "201"
},
"MU5123": { # 进港航班 - 使用真实路由
"flightNo": "MU5123",
"type": "IN",
"runway": "35",
"contactCross": "F1", # F1滑行道
"seat": "138", # 138机位
"cycle_start": current_time,
"active_duration": 60, # 1分钟活跃期
"gap_duration": 5, # 5秒间隔期
"fixed_time": int(current_time * 1000), # 固定的落地时间
"status": "active"
"seat": "138" # 138机位
}
}
@ -1395,6 +1384,11 @@ class AircraftRouteFollower:
self.gate_position = None # 机位坐标
self._status_start_time = time.time() # 状态开始时间,用于简化状态管理
# 新增状态管理属性
self.flight_status = 'idle' # 飞机状态:'idle', 'in_route', 'at_gate', 'waiting'
self.wait_until = 0.0 # 等待结束时间戳
self.waiting_duration = 5.0 # 路径完成后的等待时间(秒)
# 解析航空器路由路径
self._parse_routes()
@ -1450,6 +1444,8 @@ class AircraftRouteFollower:
self.current_point_index = 0
self.route_progress = 0.0
self.is_at_gate = False
self.flight_status = 'in_route' # 设置为路径运行状态
self.wait_until = 0.0 # 清除等待时间
logging.info(f"航空器 {self.flight_no} 开始进港路径跟随")
elif route_type == "departure" and self.departure_points:
@ -1458,6 +1454,8 @@ class AircraftRouteFollower:
self.current_point_index = 0
self.route_progress = 0.0
self.is_at_gate = False
self.flight_status = 'in_route' # 设置为路径运行状态
self.wait_until = 0.0 # 清除等待时间
logging.info(f"航空器 {self.flight_no} 开始出港路径跟随")
else:
@ -1475,9 +1473,19 @@ class AircraftRouteFollower:
Returns:
bool: 是否到达路径终点
"""
current_time = time.time()
# 检查是否在等待期
if current_time < self.wait_until:
self.flight_status = 'waiting'
return False # 还在等待中,不更新位置
if not self.route_points or self.current_point_index >= len(self.route_points):
return True # 路径已完成
# 设置状态为路径运行中
self.flight_status = 'in_route'
# 获取当前位置
current_lat = to_float_safe(aircraft.get("latitude", 0))
current_lon = to_float_safe(aircraft.get("longitude", 0))
@ -1494,27 +1502,45 @@ class AircraftRouteFollower:
speed_mps = speed_kmh * 1000.0 / 3600.0 # 转换为米/秒
move_distance = speed_mps * elapsed_time
# 如果已经接近目标点(5米内),移动到下一个路径点
if distance_to_target <= 5.0:
self.current_point_index += 1
self.route_progress = min(1.0, self.current_point_index / max(1, len(self.route_points) - 1))
# 优化路径跟随:如果移动距离大于到目标点的距离,直接跳到下一个合适的点
remaining_distance = move_distance
if self.current_point_index >= len(self.route_points):
# 路径完成
aircraft["latitude"] = target_lat
aircraft["longitude"] = target_lon
self.is_at_gate = (self.current_route_type == "arrival")
logging.info(f"航空器 {self.flight_no} 完成 {self.current_route_type} 路径")
return True
while remaining_distance > 0 and self.current_point_index < len(self.route_points):
# 如果已经接近目标点或剩余移动距离大于目标距离,移动到下一个路径点
if distance_to_target <= remaining_distance:
# 移动到当前目标点
remaining_distance -= distance_to_target
self.current_point_index += 1
self.route_progress = min(1.0, self.current_point_index / max(1, len(self.route_points) - 1))
# 更新目标到下一个路径点
target_lat, target_lon = self.route_points[self.current_point_index]
distance_to_target = haversine_distance(current_lat, current_lon, target_lat, target_lon)
if self.current_point_index >= len(self.route_points):
# 路径完成,设置到真正的路径终点
final_lat, final_lon = self.route_points[-1] # 路径的最后一个点
aircraft["latitude"] = final_lat
aircraft["longitude"] = final_lon
self.is_at_gate = (self.current_route_type == "arrival")
self.flight_status = 'at_gate' if self.is_at_gate else 'completed'
# 设置5秒等待时间
self.wait_until = current_time + self.waiting_duration
logging.info(f"航空器 {self.flight_no} 完成 {self.current_route_type} 路径,等待{self.waiting_duration}秒后重新开始")
return True
# 计算移动方向
if distance_to_target > 0:
# 计算移动比例
move_ratio = min(1.0, move_distance / distance_to_target)
# 更新到下一个目标点
current_lat, current_lon = target_lat, target_lon # 当前位置更新为刚到达的点
if self.current_point_index < len(self.route_points):
target_lat, target_lon = self.route_points[self.current_point_index]
distance_to_target = haversine_distance(current_lat, current_lon, target_lat, target_lon)
else:
# 已经超出路径范围,跳出循环
break
else:
# 不能完全到达目标点,按比例移动
break
# 计算最终位置
if remaining_distance > 0 and distance_to_target > 0:
# 按比例移动到目标点
move_ratio = remaining_distance / distance_to_target
# 线性插值计算新位置
new_lat = current_lat + (target_lat - current_lat) * move_ratio
@ -1523,6 +1549,10 @@ class AircraftRouteFollower:
# 更新航空器位置
aircraft["latitude"] = new_lat
aircraft["longitude"] = new_lon
else:
# 已到达当前目标点,更新位置
aircraft["latitude"] = current_lat
aircraft["longitude"] = current_lon
return False
@ -1583,40 +1613,40 @@ class AircraftRouteManager:
def _update_arrival_only_aircraft(self, aircraft: dict[str, Any], follower: AircraftRouteFollower,
speed: float, elapsed_time: float):
"""更新纯进港模式飞机位置"""
if follower.current_route_type != "arrival":
current_time = time.time()
# 检查是否需要开始新路径(等待期结束后或初始状态)
if follower.flight_status in ['idle', 'completed', 'at_gate', 'waiting'] and current_time >= follower.wait_until:
follower.start_route("arrival")
# 设置到进港路径起点
if follower.route_points:
aircraft["latitude"] = follower.route_points[0][0]
aircraft["longitude"] = follower.route_points[0][1]
logging.info(f"航空器 {aircraft.get('flightNo')} 等待结束,重新开始进港路径")
route_completed = follower.update_position_on_route(aircraft, speed, elapsed_time)
if route_completed:
# 进港完成后重新开始,立即跳转到起点
follower.start_route("arrival")
if follower.route_points:
aircraft["latitude"] = follower.route_points[0][0]
aircraft["longitude"] = follower.route_points[0][1]
logging.info(f"航空器 {aircraft.get('flightNo')} 进港完成,重新开始从起点: ({follower.route_points[0][0]:.6f}, {follower.route_points[0][1]:.6f})")
if route_completed and follower.flight_status != 'waiting':
# 路径完成已设置5秒等待时间无需立即重新开始
logging.info(f"航空器 {aircraft.get('flightNo')} 进港完成,将等待{follower.waiting_duration}秒后重新开始")
def _update_departure_only_aircraft(self, aircraft: dict[str, Any], follower: AircraftRouteFollower,
speed: float, elapsed_time: float):
"""更新纯出港模式飞机位置"""
if follower.current_route_type != "departure":
current_time = time.time()
# 检查是否需要开始新路径(等待期结束后或初始状态)
if follower.flight_status in ['idle', 'completed', 'at_gate', 'waiting'] and current_time >= follower.wait_until:
follower.start_route("departure")
# 设置到出港路径起点
if follower.route_points:
aircraft["latitude"] = follower.route_points[0][0]
aircraft["longitude"] = follower.route_points[0][1]
logging.info(f"航空器 {aircraft.get('flightNo')} 等待结束,重新开始出港路径")
route_completed = follower.update_position_on_route(aircraft, speed, elapsed_time)
if route_completed:
# 出港完成后重新开始,立即跳转到起点
follower.start_route("departure")
if follower.route_points:
aircraft["latitude"] = follower.route_points[0][0]
aircraft["longitude"] = follower.route_points[0][1]
logging.info(f"航空器 {aircraft.get('flightNo')} 出港完成,重新开始从起点: ({follower.route_points[0][0]:.6f}, {follower.route_points[0][1]:.6f})")
if route_completed and follower.flight_status != 'waiting':
# 路径完成已设置5秒等待时间无需立即重新开始
logging.info(f"航空器 {aircraft.get('flightNo')} 出港完成,将等待{follower.waiting_duration}秒后重新开始")
# 创建全局路径跟随管理器
@ -2327,65 +2357,37 @@ def get_departure_taxiway_route():
})
def get_active_flights() -> list[dict[str, Any]]:
"""获取当前活跃的进出港航班(简化版)"""
current_time = time.time()
"""获取当前活跃的进出港航班(基于路径状态)"""
active_flights = []
current_time = int(time.time() * 1000)
for flight_no, flight_info in flight_data.items():
# 安全读取并转换为数值
cycle_start = to_float_safe(flight_info.get("cycle_start"))
active_duration = to_float_safe(flight_info.get("active_duration"))
gap_duration = to_float_safe(flight_info.get("gap_duration"))
# 兜底默认值
if cycle_start is None:
cycle_start = current_time
if active_duration is None or active_duration <= 0:
active_duration = 600.0
if gap_duration is None or gap_duration < 0:
gap_duration = 10.0
# 计算从周期开始的时间与总周期
elapsed_time = float(current_time) - float(cycle_start)
total_cycle = float(active_duration) + float(gap_duration)
# 避免除零
if total_cycle <= 0:
total_cycle = float(active_duration) if active_duration > 0 else 1.0
# 计算在当前周期中的位置
cycle_position = float(elapsed_time) % float(total_cycle)
# 检查是否在活跃期内
if cycle_position <= float(active_duration):
# 在活跃期内,根据航班类型返回信息
flight_type = flight_info.get("type")
# 遍历所有路径跟随管理器中的飞机
for flight_no, follower in route_manager.followers.items():
# 检查飞机是否在活跃状态(正在路径中或在机位)
if follower.flight_status in ['in_route', 'at_gate']:
# 获取航班基础信息
if flight_no in flight_data:
flight_info = flight_data[flight_no]
# 航班使用固定信息
flight_data_response = {
"flightNo": flight_info.get("flightNo"),
"type": flight_type,
"runway": flight_info.get("runway"),
"contactCross": flight_info.get("contactCross"),
"seat": flight_info.get("seat"),
"time": flight_info.get("fixed_time") # 使用固定时间
}
active_flights.append(flight_data_response)
# 根据路径类型确定航班通知类型
flight_type = flight_info.get("type")
event_type = "落地" if flight_type == "IN" else "滑出"
# 构造航班通知数据
flight_notification = {
"flightNo": flight_no,
"type": flight_type,
"runway": flight_info.get("runway"),
"contactCross": flight_info.get("contactCross"),
"seat": flight_info.get("seat"),
"time": current_time
}
active_flights.append(flight_notification)
logging.info(f"航班 {flight_no} {event_type}通知中 (路径状态: {follower.flight_status}, 进度: {follower.route_progress:.2f})")
else:
logging.warning(f"航班 {flight_no} 在路径跟随器中但不在flight_data中")
# 确定事件类型用于日志
actual_type = flight_data_response.get("type", flight_info.get("type"))
event_type = "落地" if actual_type == "IN" else "滑出"
logging.info(f"航班 {flight_no} {event_type}通知中 (活跃期: {cycle_position:.1f}s/{int(active_duration)}s)")
else:
# 在间隔期内,更新固定时间为下一个周期
cycle_number = int(float(elapsed_time) // float(total_cycle)) + 1
next_cycle_start = float(cycle_start) + cycle_number * float(total_cycle)
# 简化时间计算,与路径起点时间同步
flight_info["fixed_time"] = int(next_cycle_start * 1000) # 使用周期开始时间作为通知时间
gap_part = cycle_position - float(active_duration)
logging.info(f"航班 {flight_no} 在间隔期,准备下一个周期 (间隔期: {gap_part:.1f}s/{int(gap_duration)}s)")
return active_flights
@app.route('/openApi/getInboundAndOutboundFlightsNotification', methods=['GET', 'OPTIONS'])