Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 85c3e7ec98 |
43
.gitignore
vendored
43
.gitignore
vendored
@ -54,45 +54,4 @@ logs/
|
|||||||
Users/
|
Users/
|
||||||
|
|
||||||
qaup-deploy/
|
qaup-deploy/
|
||||||
deploy/offline_packages/
|
deploy/offline_packages/
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# Python
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
__pycache__/
|
|
||||||
*.so
|
|
||||||
.Python
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
.venv/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env/
|
|
||||||
*.log
|
|
||||||
|
|
||||||
######################################################################
|
|
||||||
# Node.js
|
|
||||||
node_modules/
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
.npm
|
|
||||||
.yarn-integrity
|
|
||||||
dist/
|
|
||||||
.cache/
|
|
||||||
203
README.md
203
README.md
@ -17,7 +17,6 @@
|
|||||||
## 核心特性
|
## 核心特性
|
||||||
|
|
||||||
### 🚗 冲突避免系统 (collision模块)
|
### 🚗 冲突避免系统 (collision模块)
|
||||||
|
|
||||||
- **空间数据分析**: 基于PostGIS的空间计算和几何分析
|
- **空间数据分析**: 基于PostGIS的空间计算和几何分析
|
||||||
- **实时车辆监控**: WebSocket实时位置数据推送和展示
|
- **实时车辆监控**: WebSocket实时位置数据推送和展示
|
||||||
- **机场区域管理**: 跑道、滑行道、停机坪等区域配置和监控
|
- **机场区域管理**: 跑道、滑行道、停机坪等区域配置和监控
|
||||||
@ -27,15 +26,15 @@
|
|||||||
|
|
||||||
### 🛠️ 系统管理功能
|
### 🛠️ 系统管理功能
|
||||||
|
|
||||||
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||||
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
|
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
|
||||||
3. 岗位管理:配置系统用户所属担任职务。
|
3. 岗位管理:配置系统用户所属担任职务。
|
||||||
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
|
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
|
||||||
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
|
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
|
||||||
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||||
7. 参数管理:对系统动态配置常用参数。
|
7. 参数管理:对系统动态配置常用参数。
|
||||||
8. 通知公告:系统通知公告信息发布维护。
|
8. 通知公告:系统通知公告信息发布维护。
|
||||||
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||||
10. 登录日志:系统登录日志记录查询包含登录异常。
|
10. 登录日志:系统登录日志记录查询包含登录异常。
|
||||||
11. 在线用户:当前系统中活跃用户状态监控。
|
11. 在线用户:当前系统中活跃用户状态监控。
|
||||||
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
|
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
|
||||||
@ -56,194 +55,65 @@
|
|||||||
- **构建工具**: Maven 3.6+
|
- **构建工具**: Maven 3.6+
|
||||||
- **Java版本**: JDK 8
|
- **Java版本**: JDK 8
|
||||||
|
|
||||||
## 部署指南
|
## 快速启动
|
||||||
|
|
||||||
### 方式一:自动部署(推荐)
|
|
||||||
|
|
||||||
使用提供的自动化部署脚本:
|
|
||||||
|
|
||||||
|
### 1. 环境准备
|
||||||
```bash
|
```bash
|
||||||
# 1. 进入部署目录
|
# Java 8
|
||||||
cd deploy/
|
|
||||||
|
|
||||||
# 2. 赋予执行权限
|
|
||||||
chmod +x deploy-all.sh
|
|
||||||
|
|
||||||
# 3. 执行自动部署
|
|
||||||
./deploy-all.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
自动化部署脚本将自动完成:
|
|
||||||
|
|
||||||
- ✅ 环境检查和依赖验证
|
|
||||||
- ✅ Docker及Docker Compose安装检查
|
|
||||||
- ✅ 磁盘空间检查(至少3GB)
|
|
||||||
- ✅ 端口占用检测
|
|
||||||
- ✅ Docker镜像处理(本地载入或在线拉取)
|
|
||||||
- ✅ 冲突容器清理
|
|
||||||
- ✅ 数据目录创建和权限设置
|
|
||||||
- ✅ 配置文件验证
|
|
||||||
- ✅ 基础设施服务启动(PostgreSQL + PostGIS、Redis)
|
|
||||||
- ✅ 应用服务启动和数据库迁移
|
|
||||||
- ✅ 健康状态检查和验证
|
|
||||||
|
|
||||||
### 方式二:手动部署
|
|
||||||
|
|
||||||
#### 1. 环境准备
|
|
||||||
|
|
||||||
**必需组件:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Java 8+
|
|
||||||
java -version
|
java -version
|
||||||
|
|
||||||
# Maven 3.6+
|
# Maven 3.6+
|
||||||
mvn -version
|
mvn -version
|
||||||
|
|
||||||
# Docker & Docker Compose
|
# PostgreSQL + PostGIS
|
||||||
docker --version
|
|
||||||
docker-compose --version
|
|
||||||
```
|
|
||||||
|
|
||||||
**可选组件(用于本地开发):**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# PostgreSQL + PostGIS(用于本地开发)
|
|
||||||
psql --version
|
psql --version
|
||||||
|
|
||||||
# Redis(用于本地开发)
|
# Redis
|
||||||
redis-server --version
|
redis-server --version
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. 数据库配置
|
### 2. 数据库配置
|
||||||
|
1. 创建PostgreSQL数据库并启用PostGIS扩展
|
||||||
**Docker启动数据库:**
|
2. 执行SQL初始化脚本:
|
||||||
|
- `sql/create_qaup_database.sql` - 创建数据库
|
||||||
|
- `sql/create_sys_vehicle_info_table.sql` - 车辆信息表
|
||||||
|
- `sql/create_sys_driver_info_table.sql` - 司机信息表
|
||||||
|
|
||||||
|
### 3. 启动Redis服务
|
||||||
```bash
|
```bash
|
||||||
# 启动PostgreSQL + PostGIS
|
redis-server
|
||||||
docker run -d \
|
|
||||||
--name qaup-postgres \
|
|
||||||
-e POSTGRES_DB=qaup \
|
|
||||||
-e POSTGRES_USER=qaup \
|
|
||||||
-e POSTGRES_PASSWORD=qaup123 \
|
|
||||||
-e POSTGRES_INITDB_ARGS="--encoding=UTF-8 --lc-collate=C --lc-ctype=C" \
|
|
||||||
-p 5432:5432 \
|
|
||||||
m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine
|
|
||||||
|
|
||||||
# 启动Redis
|
|
||||||
docker run -d \
|
|
||||||
--name qaup-redis \
|
|
||||||
-e REDIS_PASSWORD=qaup123 \
|
|
||||||
-p 6379:6379 \
|
|
||||||
m.daocloud.io/docker.io/library/redis:8.0-alpine redis-server --requirepass qaup123
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**本地数据库初始化:**
|
### 4. 配置应用
|
||||||
|
|
||||||
1. 执行SQL初始化脚本:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 创建数据库和基础表
|
|
||||||
psql -U qaup -d qaup -f sql/create_qaup_database.sql
|
|
||||||
psql -U qaup -d qaup -f sql/create_sys_vehicle_info_table.sql
|
|
||||||
psql -U qaup -d qaup -f sql/create_sys_driver_info_table.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. 环境配置
|
|
||||||
|
|
||||||
**更新应用配置:**
|
|
||||||
修改 `qaup-admin/src/main/resources/application.yml`:
|
修改 `qaup-admin/src/main/resources/application.yml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
spring:
|
spring:
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:postgresql://localhost:5432/qaup
|
url: jdbc:postgresql://localhost:5432/your_database
|
||||||
username: qaup
|
username: your_username
|
||||||
password: qaup123
|
password: your_password
|
||||||
driver-class-name: org.postgresql.Driver
|
|
||||||
redis:
|
redis:
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 6379
|
port: 6379
|
||||||
password: qaup123
|
|
||||||
database: 0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**环境变量配置:**
|
### 5. 编译和启动
|
||||||
确保各模块 `.env` 文件配置正确:
|
|
||||||
|
|
||||||
- `adxp-adapter/.env.example` - ADXP数据中台适配器配置
|
|
||||||
- `qaup-admin/.env.example` - 主应用配置
|
|
||||||
|
|
||||||
#### 4. 编译和启动
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 清理并编译整个项目
|
# 清理并编译整个项目
|
||||||
mvn clean install
|
mvn clean install
|
||||||
|
|
||||||
# 2. 进入管理模块
|
# 启动应用
|
||||||
cd qaup-admin
|
cd qaup-admin
|
||||||
|
|
||||||
# 3. 启动应用(开发模式)
|
|
||||||
mvn spring-boot:run
|
mvn spring-boot:run
|
||||||
|
|
||||||
# 或打包启动(生产模式)
|
# 或者运行打包后的jar
|
||||||
mvn clean package -DskipTests
|
|
||||||
java -jar target/qaup-admin.jar
|
java -jar target/qaup-admin.jar
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 5. 验证部署
|
### 6. 访问系统
|
||||||
|
- 管理后台: http://localhost:8080
|
||||||
**服务状态检查:**
|
- WebSocket端点: ws://localhost:8080/ws
|
||||||
|
- API文档: http://localhost:8080/swagger-ui/
|
||||||
```bash
|
|
||||||
# 检查应用健康状态
|
|
||||||
curl http://localhost:8080/actuator/health
|
|
||||||
|
|
||||||
# 检查数据库连接
|
|
||||||
docker exec qaup-postgres pg_isready -U qaup
|
|
||||||
|
|
||||||
# 检查Redis连接
|
|
||||||
docker exec qaup-redis redis-cli -a qaup123 ping
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. 系统访问
|
|
||||||
|
|
||||||
- **管理后台**: <http://localhost:8080>
|
|
||||||
- **初始登录**:
|
|
||||||
- 用户名: `admin`
|
|
||||||
- 密码: `admin123`
|
|
||||||
- **WebSocket端点**: ws://localhost:8080/ws
|
|
||||||
- **API文档**: <http://localhost:8080/swagger-ui/>
|
|
||||||
- **健康检查**: <http://localhost:8080/actuator/health>
|
|
||||||
|
|
||||||
### 7. 管理命令
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 查看服务状态
|
|
||||||
docker compose ps
|
|
||||||
|
|
||||||
# 查看应用日志
|
|
||||||
docker compose logs -f qaup-app
|
|
||||||
|
|
||||||
# 查看数据库日志
|
|
||||||
docker compose logs -f qaup-postgres
|
|
||||||
|
|
||||||
# 查看Redis日志
|
|
||||||
docker compose logs -f qaup-redis
|
|
||||||
|
|
||||||
# 重启应用
|
|
||||||
docker compose restart qaup-app
|
|
||||||
|
|
||||||
# 停止所有服务
|
|
||||||
docker compose down
|
|
||||||
|
|
||||||
# 查看数据库迁移状态
|
|
||||||
docker exec qaup-postgres psql -U qaup -d qaup -c \
|
|
||||||
"SELECT version,description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 5;"
|
|
||||||
|
|
||||||
# 清理Docker资源
|
|
||||||
docker system prune -a
|
|
||||||
```
|
|
||||||
|
|
||||||
## 项目合并说明
|
## 项目合并说明
|
||||||
|
|
||||||
@ -257,19 +127,16 @@ docker system prune -a
|
|||||||
## 开发指南
|
## 开发指南
|
||||||
|
|
||||||
### collision模块核心组件
|
### collision模块核心组件
|
||||||
|
|
||||||
- `QuapDataAdapter`: 数据访问适配器,连接若依系统数据
|
- `QuapDataAdapter`: 数据访问适配器,连接若依系统数据
|
||||||
- `WebSocketConfig`: WebSocket配置,支持实时数据推送
|
- `WebSocketConfig`: WebSocket配置,支持实时数据推送
|
||||||
- `VehicleLocationService`: 车辆位置管理服务
|
- `VehicleLocationService`: 车辆位置管理服务
|
||||||
- `GeopositionController`: WebSocket消息控制器
|
- `GeopositionController`: WebSocket消息控制器
|
||||||
|
|
||||||
### 扩展开发
|
### 扩展开发
|
||||||
|
|
||||||
1. 新增spatial实体时,继承spatial基类并配置PostGIS映射
|
1. 新增spatial实体时,继承spatial基类并配置PostGIS映射
|
||||||
2. WebSocket消息通过`/topic`前缀向客户端广播
|
2. WebSocket消息通过`/topic`前缀向客户端广播
|
||||||
3. 使用`QuapDataAdapter`获取车辆和司机数据,避免直接访问DAO
|
3. 使用`QuapDataAdapter`获取车辆和司机数据,避免直接访问DAO
|
||||||
|
|
||||||
## 版本信息
|
## 版本信息
|
||||||
|
|
||||||
- 当前版本: 1.0.1
|
- 当前版本: 1.0.1
|
||||||
- 更新日志: 详见 `change_log.md`
|
- 更新日志: 详见 `change_log.md`
|
||||||
@ -4,68 +4,13 @@
|
|||||||
# 1. 复制此文件为 .env: cp .env.example .env
|
# 1. 复制此文件为 .env: cp .env.example .env
|
||||||
# 2. 修改 .env 中的配置值
|
# 2. 修改 .env 中的配置值
|
||||||
# 3. 启动应用时会自动加载 .env 文件中的环境变量
|
# 3. 启动应用时会自动加载 .env 文件中的环境变量
|
||||||
# 4. 适用于ADXP主动连接架构
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
# ========== ADXP 数据中台连接配置 ==========
|
# ========== ADXP 数据中台配置 ==========
|
||||||
# ADXP数据中台的主机地址和端口
|
ADXP_HOST=localhost
|
||||||
ADXP_HOST=10.32.38.2
|
ADXP_PORT=7001
|
||||||
ADXP_PORT=8081
|
|
||||||
|
|
||||||
# 连接ADXP数据中台的用户名和密码
|
|
||||||
ADXP_USERNAME=dianxin
|
ADXP_USERNAME=dianxin
|
||||||
ADXP_PASSWORD=Dianxin#2025
|
ADXP_PASSWORD=dianxin@123
|
||||||
|
|
||||||
# ========== 适配器服务配置 ==========
|
|
||||||
# 适配器服务端口
|
|
||||||
SERVER_PORT=8086
|
|
||||||
|
|
||||||
# 应用环境标识
|
|
||||||
SPRING_PROFILES_ACTIVE=prod
|
|
||||||
|
|
||||||
# ========== 日志配置 ==========
|
|
||||||
# QAUP应用日志级别
|
|
||||||
LOG_LEVEL_QAUP=info
|
|
||||||
# Spring框架日志级别
|
|
||||||
LOG_LEVEL_SPRING=warn
|
|
||||||
# ADXP SDK日志级别
|
|
||||||
LOG_LEVEL_ADXP=info
|
|
||||||
|
|
||||||
# ========== 健康检查和监控配置 ==========
|
|
||||||
# 健康检查端点路径(默认:/actuator/health)
|
|
||||||
MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,metrics
|
|
||||||
# 健康检查详细程度(simple/detailed)
|
|
||||||
MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS=simple
|
|
||||||
|
|
||||||
# ========== 数据采集配置 ==========
|
|
||||||
# 数据采集间隔(毫秒)
|
|
||||||
DATA_COLLECTOR_INTERVAL=250
|
|
||||||
# 重连延迟(毫秒)
|
|
||||||
RECONNECT_DELAY_MILLIS=3000
|
|
||||||
|
|
||||||
# ========== WebSocket配置 ==========
|
|
||||||
# WebSocket端点路径
|
|
||||||
WEBSOCKET_ENDPOINT=/ws/flight-notifications
|
|
||||||
|
|
||||||
# ========== Redis配置(用于缓存和会话管理) ==========
|
|
||||||
# Redis主机地址
|
|
||||||
REDIS_HOST=localhost
|
|
||||||
REDIS_PORT=6379
|
|
||||||
REDIS_DATABASE=0
|
|
||||||
REDIS_PASSWORD=
|
|
||||||
# Redis最大内存限制
|
|
||||||
REDIS_MAX_MEMORY=256mb
|
|
||||||
# Redis内存淘汰策略
|
|
||||||
REDIS_MAX_MEMORY_POLICY=volatile-lru
|
|
||||||
|
|
||||||
# ========== 生产环境示例配置 ==========
|
|
||||||
# 生产环境时请根据实际情况修改以下配置:
|
|
||||||
# ADXP_HOST=10.32.38.2 # 真实的ADXP数据中台IP地址
|
|
||||||
# ADXP_PORT=8081 # 真实的ADXP数据中台端口
|
|
||||||
# ADXP_USERNAME=dianxin # 真实的用户名
|
|
||||||
# ADXP_PASSWORD=Dianxin#2025 # 真实的密码
|
|
||||||
# LOG_LEVEL_QAUP=info # 生产环境建议使用info级别
|
|
||||||
# REDIS_MAX_MEMORY=1gb # 生产环境建议增加Redis内存
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 注意事项:
|
# 注意事项:
|
||||||
@ -73,6 +18,4 @@ REDIS_MAX_MEMORY_POLICY=volatile-lru
|
|||||||
# 2. 字符串值不需要引号
|
# 2. 字符串值不需要引号
|
||||||
# 3. #开头的行为注释
|
# 3. #开头的行为注释
|
||||||
# 4. 请勿将 .env 文件提交到Git仓库
|
# 4. 请勿将 .env 文件提交到Git仓库
|
||||||
# 5. 生产环境请务必修改默认密码
|
|
||||||
# 6. ADXP适配器采用主动连接架构,启动时自动连接数据中台
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
438
adxp-adapter/QUICKSTART.md
Normal file
438
adxp-adapter/QUICKSTART.md
Normal file
@ -0,0 +1,438 @@
|
|||||||
|
# ADXP SDK Adapter 快速开始
|
||||||
|
|
||||||
|
## 方案概述
|
||||||
|
|
||||||
|
ADXP SDK (adxp-client-2.6.9.jar) 基于 JDK 8 开发,无法在 Java 11+ 环境运行。本方案通过独立的适配器服务解决兼容性问题。
|
||||||
|
|
||||||
|
### 架构
|
||||||
|
|
||||||
|
```
|
||||||
|
主应用 (Java 17) → HTTP → ADXP Adapter (Java 8, Docker) → SOAP → 数据中台
|
||||||
|
└─ port 8080 └─ port 8086 └─ port 7001
|
||||||
|
```
|
||||||
|
|
||||||
|
### Apple Silicon (M1/M2/M3) 用户
|
||||||
|
|
||||||
|
✅ **推荐使用 Docker**,无需安装 Java 8(Homebrew 的 openjdk@8 只有 x86_64 版本)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 快速开始(Docker 方式,推荐)
|
||||||
|
|
||||||
|
### 步骤 1: 启动 Mock 服务器(模拟数据中台)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在项目根目录
|
||||||
|
cd tools
|
||||||
|
python3 mock_adxp.py --host 0.0.0.0 --port 7001 --auto --interval 10
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明**: Mock 服务器监听 `0.0.0.0:7001`,每 10 秒自动生成航班消息
|
||||||
|
|
||||||
|
### 步骤 2: 编译适配器服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ../adxp-adapter
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
```
|
||||||
|
|
||||||
|
**预期输出**: `BUILD SUCCESS`,生成 `target/adxp-adapter.jar`
|
||||||
|
|
||||||
|
### 步骤 3: 构建 Docker 镜像
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t adxp-adapter:1.0.0 .
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明**: 使用 OpenJDK 8(非 Alpine),包含 `net-tools`(SDK 需要 `ifconfig` 命令)
|
||||||
|
|
||||||
|
### 步骤 4: 启动 Docker 容器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
-p 8086:8086 \
|
||||||
|
-e ADXP_HOST=host.docker.internal \
|
||||||
|
-e ADXP_PORT=7001 \
|
||||||
|
--name adxp-adapter \
|
||||||
|
adxp-adapter:1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明**:
|
||||||
|
- `host.docker.internal` 指向宿主机(访问本地 mock 服务器)
|
||||||
|
- 环境变量可覆盖配置文件
|
||||||
|
|
||||||
|
### 步骤 5: 验证适配器服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 健康检查
|
||||||
|
curl http://localhost:8086/api/adxp/health
|
||||||
|
|
||||||
|
# 预期输出: {"activeSessions":0,"status":"UP"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 6: 测试完整流程
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 登录
|
||||||
|
LOGIN_RESPONSE=$(curl -s -X POST http://localhost:8086/api/adxp/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"dianxin","password":"dianxin@123"}')
|
||||||
|
|
||||||
|
echo "登录响应: $LOGIN_RESPONSE"
|
||||||
|
# 预期: {"success":true,"sessionId":"xxx-xxx-xxx","message":"登录成功"}
|
||||||
|
|
||||||
|
# 2. 提取 SessionId
|
||||||
|
SESSION_ID=$(echo "$LOGIN_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['sessionId'])")
|
||||||
|
echo "SessionId: $SESSION_ID"
|
||||||
|
|
||||||
|
# 3. 接收消息
|
||||||
|
curl -s "http://localhost:8086/api/adxp/messages?sessionId=$SESSION_ID" | python3 -m json.tool
|
||||||
|
|
||||||
|
# 4. 退出登录
|
||||||
|
curl -s -X POST http://localhost:8086/api/adxp/logout \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"sessionId\":\"$SESSION_ID\"}" | python3 -m json.tool
|
||||||
|
|
||||||
|
# 预期: {"success":true,"message":"登出成功"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 7: 启动主应用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ../qaup-admin
|
||||||
|
mvn spring-boot:run -Dspring-boot.run.profiles=dev,druid
|
||||||
|
```
|
||||||
|
|
||||||
|
**主应用会自动通过 HTTP 调用适配器服务!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Docker 常用命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看日志
|
||||||
|
docker logs -f adxp-adapter
|
||||||
|
|
||||||
|
# 重启容器
|
||||||
|
docker restart adxp-adapter
|
||||||
|
|
||||||
|
# 停止并删除容器
|
||||||
|
docker stop adxp-adapter && docker rm adxp-adapter
|
||||||
|
|
||||||
|
# 重新构建镜像(代码修改后)
|
||||||
|
cd adxp-adapter
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
docker stop adxp-adapter && docker rm adxp-adapter
|
||||||
|
docker build -t adxp-adapter:1.0.0 .
|
||||||
|
docker run -d -p 8086:8086 -e ADXP_HOST=host.docker.internal -e ADXP_PORT=7001 --name adxp-adapter adxp-adapter:1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ 本地运行(需要 Java 8)
|
||||||
|
|
||||||
|
**仅适用于 x86_64 机器或已安装 Java 8 的环境**
|
||||||
|
|
||||||
|
### 步骤 1: 启动 Mock 服务器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd tools
|
||||||
|
python3 mock_adxp.py --host 0.0.0.0 --port 7001 --auto --interval 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 2: 编译
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ../adxp-adapter
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 3: 启动适配器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 如果系统默认是 Java 8
|
||||||
|
java -jar target/adxp-adapter.jar
|
||||||
|
|
||||||
|
# 或指定 Java 8 路径
|
||||||
|
/path/to/jdk8/bin/java -jar target/adxp-adapter.jar
|
||||||
|
|
||||||
|
# 或使用启动脚本(自动查找 Java 8)
|
||||||
|
./start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤 4: 测试
|
||||||
|
|
||||||
|
同 Docker 方式的步骤 6
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
### 主应用配置 (application-dev.yml)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
data:
|
||||||
|
collector:
|
||||||
|
adxp-adapter:
|
||||||
|
host: localhost # 适配器服务地址
|
||||||
|
port: 8086 # 适配器服务端口
|
||||||
|
username: dianxin
|
||||||
|
password: dianxin@123
|
||||||
|
reconnect-delay-millis: 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
### 适配器服务配置 (application.yml)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
adxp:
|
||||||
|
host: localhost # 真实数据中台地址(或 mock 服务器)
|
||||||
|
port: 7001 # 数据中台端口
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐳 Docker Compose 部署(生产环境)
|
||||||
|
|
||||||
|
### 1. 准备环境变量
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建 .env 文件
|
||||||
|
cat > .env << EOF
|
||||||
|
ADXP_HOST=10.10.10.100 # 真实数据中台 IP
|
||||||
|
ADXP_PORT=7001 # 真实数据中台端口
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 编译和构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd adxp-adapter
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
docker build -t adxp-adapter:1.0.0 .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 Docker Compose
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
docker-compose logs -f adxp-adapter
|
||||||
|
|
||||||
|
# 查看状态
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 验证部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 健康检查
|
||||||
|
curl http://localhost:8086/api/adxp/health
|
||||||
|
|
||||||
|
# 测试登录
|
||||||
|
curl -X POST http://localhost:8086/api/adxp/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"dianxin","password":"dianxin@123"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 停止服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 环境切换
|
||||||
|
|
||||||
|
### 开发环境(使用 Mock)
|
||||||
|
|
||||||
|
**主应用配置** (`qaup-admin/src/main/resources/application-dev.yml`):
|
||||||
|
```yaml
|
||||||
|
data:
|
||||||
|
collector:
|
||||||
|
adxp-adapter:
|
||||||
|
host: localhost
|
||||||
|
port: 8086
|
||||||
|
username: dianxin
|
||||||
|
password: dianxin@123
|
||||||
|
reconnect-delay-millis: 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
**适配器配置** (`.env` 或环境变量):
|
||||||
|
```bash
|
||||||
|
ADXP_HOST=host.docker.internal # 指向宿主机 mock 服务器
|
||||||
|
ADXP_PORT=7001
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mock 服务器**:
|
||||||
|
```bash
|
||||||
|
python3 tools/mock_adxp.py --host 0.0.0.0 --port 7001 --auto --interval 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生产环境(连接真实数据中台)
|
||||||
|
|
||||||
|
**主应用配置** (`qaup-admin/src/main/resources/application-prod.yml`):
|
||||||
|
```yaml
|
||||||
|
data:
|
||||||
|
collector:
|
||||||
|
adxp-adapter:
|
||||||
|
host: 192.168.1.100 # 适配器服务的真实 IP
|
||||||
|
port: 8086
|
||||||
|
username: ${ADXP_USERNAME} # 从环境变量读取
|
||||||
|
password: ${ADXP_PASSWORD}
|
||||||
|
reconnect-delay-millis: 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
**适配器配置** (`.env` 文件):
|
||||||
|
```bash
|
||||||
|
ADXP_HOST=10.10.10.100 # 真实数据中台 IP
|
||||||
|
ADXP_PORT=7001
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 故障排查
|
||||||
|
|
||||||
|
### 问题 1: Docker 容器启动失败
|
||||||
|
|
||||||
|
**检查日志**:
|
||||||
|
```bash
|
||||||
|
docker logs adxp-adapter
|
||||||
|
```
|
||||||
|
|
||||||
|
**常见错误**:
|
||||||
|
- `Cannot find ifconfig` → Dockerfile 使用了 Alpine 镜像,应使用 `openjdk:8-jdk`
|
||||||
|
- `Connection refused` → Mock 服务器未启动或端口错误
|
||||||
|
|
||||||
|
### 问题 2: 登录失败 (code=802/803)
|
||||||
|
|
||||||
|
**原因**: SDK 无法连接到数据中台
|
||||||
|
|
||||||
|
**排查步骤**:
|
||||||
|
```bash
|
||||||
|
# 1. 检查 mock 服务器是否运行
|
||||||
|
lsof -ti:7001
|
||||||
|
|
||||||
|
# 2. 测试网络连接
|
||||||
|
curl http://localhost:7001/LoginService?wsdl
|
||||||
|
|
||||||
|
# 3. 检查适配器环境变量
|
||||||
|
docker exec adxp-adapter env | grep ADXP
|
||||||
|
|
||||||
|
# 4. 查看适配器日志
|
||||||
|
docker logs adxp-adapter 2>&1 | grep -E "ERROR|Exception"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题 3: HTTP 406 Not Acceptable
|
||||||
|
|
||||||
|
**原因**: DTO 类缺少 getter/setter 方法
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
- 确保 `LoginResponse`、`MessageResponse` 有显式的 getter/setter(不要只依赖 Lombok)
|
||||||
|
- 确保 `jackson-databind` 依赖存在
|
||||||
|
|
||||||
|
**验证**:
|
||||||
|
```bash
|
||||||
|
# 查看 pom.xml 中的 Jackson 依赖
|
||||||
|
grep -A 5 "jackson-databind" adxp-adapter/pom.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题 4: Apple Silicon 兼容性
|
||||||
|
|
||||||
|
**错误**: `openjdk@8: The x86_64 architecture is required`
|
||||||
|
|
||||||
|
**解决方案**: 使用 Docker(推荐),或使用 Rosetta 2 运行 x86_64 版本
|
||||||
|
|
||||||
|
### 问题 5: Mock 响应格式错误
|
||||||
|
|
||||||
|
**症状**: SDK 返回 code=803 "发送请求错误"
|
||||||
|
|
||||||
|
**排查**:
|
||||||
|
```bash
|
||||||
|
# 查看 mock 服务器日志
|
||||||
|
tail -f /tmp/mock_adxp.log
|
||||||
|
|
||||||
|
# 检查 SOAP 响应格式
|
||||||
|
curl -X POST http://localhost:7001/LoginService \
|
||||||
|
-H "Content-Type: text/xml" \
|
||||||
|
-d '<?xml version="1.0"?>
|
||||||
|
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
||||||
|
<soapenv:Body><log:login xmlns:log="http://LoginService">
|
||||||
|
<username>dianxin</username><password>dianxin@123</password>
|
||||||
|
</log:login></soapenv:Body>
|
||||||
|
</soapenv:Envelope>'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 监控和日志
|
||||||
|
|
||||||
|
### 健康检查端点
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 适配器健康状态
|
||||||
|
curl http://localhost:8086/api/adxp/health
|
||||||
|
# 返回: {"activeSessions":0,"status":"UP"}
|
||||||
|
|
||||||
|
# Spring Boot Actuator
|
||||||
|
curl http://localhost:8086/actuator/health
|
||||||
|
```
|
||||||
|
|
||||||
|
### 日志查看
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Docker 容器日志
|
||||||
|
docker logs -f adxp-adapter
|
||||||
|
|
||||||
|
# 实时过滤错误
|
||||||
|
docker logs -f adxp-adapter 2>&1 | grep -E "ERROR|WARN"
|
||||||
|
|
||||||
|
# Mock 服务器日志
|
||||||
|
tail -f /tmp/mock_adxp.log
|
||||||
|
|
||||||
|
# 主应用日志
|
||||||
|
tail -f qaup-admin/app.log | grep -E "adxp|flight"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 性能监控
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 容器资源使用
|
||||||
|
docker stats adxp-adapter
|
||||||
|
|
||||||
|
# 网络延迟测试
|
||||||
|
time curl -s http://localhost:8086/api/adxp/health
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 性能指标
|
||||||
|
|
||||||
|
- **HTTP 延迟**: < 5ms(局域网)
|
||||||
|
- **适配器内存**: ~256MB(基础)+ SDK 使用
|
||||||
|
- **并发支持**: 多个主应用实例可共享一个适配器
|
||||||
|
- **会话管理**: 自动重连,无需手动维护
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ 验收检查清单
|
||||||
|
|
||||||
|
部署前请确认:
|
||||||
|
|
||||||
|
- [ ] Mock 服务器正常运行(开发环境)
|
||||||
|
- [ ] 适配器 Docker 镜像构建成功
|
||||||
|
- [ ] 适配器容器启动成功
|
||||||
|
- [ ] 健康检查返回 `{"status":"UP"}`
|
||||||
|
- [ ] 登录测试成功,返回 sessionId
|
||||||
|
- [ ] 主应用能通过 HTTP 调用适配器
|
||||||
|
- [ ] 日志中无 ERROR 级别错误
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 下一步
|
||||||
|
|
||||||
|
1. ✅ **开发环境测试完成** - Mock 服务器 + 适配器正常工作
|
||||||
|
2. 🔧 **生产部署** - 修改 ADXP_HOST 连接真实数据中台
|
||||||
|
3. 📊 **监控集成** - 添加 Prometheus metrics(可选)
|
||||||
|
4. 🔒 **安全加固** - 添加 API 认证/授权(可选)
|
||||||
|
5. 📦 **K8s 部署** - 创建 Deployment 和 Service 配置(可选)
|
||||||
@ -2,17 +2,7 @@
|
|||||||
|
|
||||||
ADXP 数据中台 SDK 适配器服务 - 基于 JDK 8 运行的独立微服务
|
ADXP 数据中台 SDK 适配器服务 - 基于 JDK 8 运行的独立微服务
|
||||||
|
|
||||||
## 新架构特性(ADXP主动连接)
|
## 功能说明
|
||||||
|
|
||||||
### 🔄 **适配器主动连接架构**
|
|
||||||
|
|
||||||
- **启动即连接**: 适配器服务启动时自动连接到ADXP数据中台
|
|
||||||
- **无需登录接口**: 移除 `/login` 端点,由适配器自动管理认证
|
|
||||||
- **连接状态管理**: 提供 `/status` 和 `/reconnect` 端点进行连接管理
|
|
||||||
- **WebSocket实时推送**: 支持实时向客户端推送航班消息
|
|
||||||
- **健康监控**: 内置连接状态监控和自动重连机制
|
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
将青岛机场数据中台 SDK (adxp-client-2.6.9.jar) 封装为 REST API 服务,解决 SDK 与现代 Java 版本的兼容性问题。
|
将青岛机场数据中台 SDK (adxp-client-2.6.9.jar) 封装为 REST API 服务,解决 SDK 与现代 Java 版本的兼容性问题。
|
||||||
|
|
||||||
@ -25,68 +15,51 @@ ADXP 数据中台 SDK 适配器服务 - 基于 JDK 8 运行的独立微服务
|
|||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
### 方式1:Docker Compose(推荐)
|
### 方式1:本地运行(需要 JDK 8)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 构建并启动服务
|
# 1. 编译
|
||||||
docker compose up -d
|
mvn clean package
|
||||||
|
|
||||||
# 2. 查看日志
|
# 2. 运行
|
||||||
docker compose logs -f
|
java -jar target/adxp-adapter.jar \
|
||||||
|
--adxp.host=10.10.10.100 \
|
||||||
# 3. 停止服务
|
--adxp.port=7001
|
||||||
docker compose down
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### API接口(ADXP主动连接架构)
|
### 方式2:Docker 运行(推荐)
|
||||||
|
|
||||||
#### 获取连接状态
|
```bash
|
||||||
|
# 1. 构建镜像
|
||||||
|
docker build -t adxp-adapter:1.0.0 .
|
||||||
|
|
||||||
```
|
# 2. 运行容器
|
||||||
GET /api/adxp/status
|
docker run -d \
|
||||||
|
-p 8086:8086 \
|
||||||
|
-e ADXP_HOST=10.10.10.100 \
|
||||||
|
-e ADXP_PORT=7001 \
|
||||||
|
--name adxp-adapter \
|
||||||
|
adxp-adapter:1.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
返回当前ADXP数据中台的连接状态和会话信息。
|
### 方式3:Docker Compose(最简单)
|
||||||
|
|
||||||
#### 强制重连
|
```bash
|
||||||
|
# 1. 设置环境变量
|
||||||
|
export ADXP_HOST=10.10.10.100
|
||||||
|
export ADXP_PORT=7001
|
||||||
|
|
||||||
```
|
# 2. 启动服务
|
||||||
POST /api/adxp/reconnect
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 3. 查看日志
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# 4. 停止服务
|
||||||
|
docker-compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
强制断开当前连接并重新连接ADXP数据中台。
|
## API 文档
|
||||||
|
|
||||||
#### 获取消息接口
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/adxp/messages
|
|
||||||
```
|
|
||||||
|
|
||||||
使用当前活动连接获取ADXP数据中台的航班消息。
|
|
||||||
|
|
||||||
#### 断开连接接口
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /api/adxp/disconnect
|
|
||||||
```
|
|
||||||
|
|
||||||
主动断开与ADXP数据中台的连接。
|
|
||||||
|
|
||||||
#### 健康检查
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /api/adxp/health
|
|
||||||
```
|
|
||||||
|
|
||||||
检查适配器服务健康状态和ADXP连接状态。
|
|
||||||
|
|
||||||
#### WebSocket实时推送
|
|
||||||
|
|
||||||
```
|
|
||||||
ws://localhost:8086/ws/flight-notifications
|
|
||||||
```
|
|
||||||
|
|
||||||
实时接收来自ADXP数据中台的航班消息推送。
|
|
||||||
|
|
||||||
### 1. 登录
|
### 1. 登录
|
||||||
|
|
||||||
@ -101,7 +74,6 @@ Content-Type: application/json
|
|||||||
```
|
```
|
||||||
|
|
||||||
响应:
|
响应:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"success": true,
|
"success": true,
|
||||||
@ -117,7 +89,6 @@ GET /api/adxp/messages?sessionId=550e8400-e29b-41d4-a716-446655440000
|
|||||||
```
|
```
|
||||||
|
|
||||||
响应:
|
响应:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"success": true,
|
"success": true,
|
||||||
@ -150,7 +121,6 @@ GET /api/adxp/health
|
|||||||
```
|
```
|
||||||
|
|
||||||
响应:
|
响应:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"status": "UP",
|
"status": "UP",
|
||||||
@ -158,37 +128,18 @@ GET /api/adxp/health
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 快速测试
|
## 测试
|
||||||
|
|
||||||
### API调用示例
|
|
||||||
|
|
||||||
#### 1. 检查连接状态
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl http://localhost:8086/api/adxp/status
|
# 1. 登录
|
||||||
```
|
curl -X POST http://localhost:8086/api/adxp/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"dianxin","password":"dianxin@123"}'
|
||||||
|
|
||||||
#### 2. 强制重连
|
# 2. 接收消息(替换为实际的 sessionId)
|
||||||
|
curl "http://localhost:8086/api/adxp/messages?sessionId=YOUR_SESSION_ID"
|
||||||
|
|
||||||
```bash
|
# 3. 健康检查
|
||||||
curl -X POST http://localhost:8086/api/adxp/reconnect
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. 获取消息
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl http://localhost:8086/api/adxp/messages
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4. 断开连接
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X POST http://localhost:8086/api/adxp/disconnect
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 5. 健康检查
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl http://localhost:8086/api/adxp/health
|
curl http://localhost:8086/api/adxp/health
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -200,8 +151,7 @@ curl http://localhost:8086/api/adxp/health
|
|||||||
|--------|------|--------|
|
|--------|------|--------|
|
||||||
| `ADXP_HOST` | ADXP 数据中台主机地址 | localhost |
|
| `ADXP_HOST` | ADXP 数据中台主机地址 | localhost |
|
||||||
| `ADXP_PORT` | ADXP 数据中台端口 | 7001 |
|
| `ADXP_PORT` | ADXP 数据中台端口 | 7001 |
|
||||||
| `ADXP_USERNAME` | ADXP 用户名 | dianxin |
|
| `SPRING_PROFILES_ACTIVE` | Spring Profile | prod |
|
||||||
| `ADXP_PASSWORD` | ADXP 密码 | dianxin@123 |
|
|
||||||
|
|
||||||
### application.yml
|
### application.yml
|
||||||
|
|
||||||
@ -209,32 +159,62 @@ curl http://localhost:8086/api/adxp/health
|
|||||||
adxp:
|
adxp:
|
||||||
host: ${ADXP_HOST:localhost}
|
host: ${ADXP_HOST:localhost}
|
||||||
port: ${ADXP_PORT:7001}
|
port: ${ADXP_PORT:7001}
|
||||||
username: ${ADXP_USERNAME:dianxin}
|
|
||||||
password: ${ADXP_PASSWORD:dianxin@123}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 部署说明
|
## 部署说明
|
||||||
|
|
||||||
### 开发环境
|
### 开发环境
|
||||||
|
|
||||||
|
使用 mock 服务器进行开发测试:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 启动适配器(默认指向 localhost)
|
# 启动 mock ADXP 服务器
|
||||||
docker compose up -d
|
cd /path/to/QAUP-Management/tools
|
||||||
|
python3 mock_adxp.py --auto --interval 10
|
||||||
|
|
||||||
|
# 启动适配器(指向 localhost)
|
||||||
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
### 生产环境
|
### 生产环境
|
||||||
|
|
||||||
```bash
|
连接真实的 ADXP 数据中台:
|
||||||
# 1. 创建环境变量文件
|
|
||||||
cat > .env << EOF
|
|
||||||
ADXP_HOST=10.10.10.100 # 真实数据中台 IP
|
|
||||||
ADXP_PORT=7001 # 真实数据中台端口
|
|
||||||
ADXP_USERNAME=dianxin # 真实数据中台用户名
|
|
||||||
ADXP_PASSWORD=dianxin@123 # 真实数据中台密码
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# 2. 启动服务
|
```bash
|
||||||
docker compose up -d
|
# 设置真实环境变量
|
||||||
|
export ADXP_HOST=10.10.10.100 # 真实数据中台 IP
|
||||||
|
export ADXP_PORT=7001
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
docker-compose -f docker-compose.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## 监控
|
||||||
|
|
||||||
|
- **健康检查**: `http://localhost:8086/api/adxp/health`
|
||||||
|
- **Actuator**: `http://localhost:8086/actuator/health`
|
||||||
|
- **日志文件**: `logs/adxp-adapter.log`
|
||||||
|
|
||||||
|
## 故障排查
|
||||||
|
|
||||||
|
### 登录失败
|
||||||
|
|
||||||
|
1. 检查 ADXP 服务器是否可达
|
||||||
|
2. 验证用户名密码是否正确
|
||||||
|
3. 查看日志: `docker-compose logs adxp-adapter`
|
||||||
|
|
||||||
|
### Session 过期
|
||||||
|
|
||||||
|
客户端需要实现重新登录逻辑,定期刷新 session。
|
||||||
|
|
||||||
|
### 网络问题
|
||||||
|
|
||||||
|
确保容器能够访问 ADXP 服务器:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 进入容器测试网络
|
||||||
|
docker exec -it adxp-adapter sh
|
||||||
|
ping ${ADXP_HOST}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 开发说明
|
## 开发说明
|
||||||
@ -243,21 +223,31 @@ docker compose up -d
|
|||||||
|
|
||||||
```
|
```
|
||||||
adxp-adapter/
|
adxp-adapter/
|
||||||
├── pom.xml # Maven 配置
|
├── pom.xml # Maven 配置
|
||||||
├── Dockerfile # Docker 镜像定义
|
├── Dockerfile # Docker 镜像定义
|
||||||
├── docker-compose.yml # Docker Compose 配置
|
├── docker-compose.yml # Docker Compose 配置
|
||||||
├── libs/ # 数据中台 SDK
|
├── libs/ # SDK JAR 文件
|
||||||
│ ├── adxp-client-2.6.9.jar
|
│ ├── adxp-client-2.6.9.jar
|
||||||
│ └── mq.allclient-9.0.jar
|
│ └── mq.allclient-9.0.jar
|
||||||
└── src/ # 源代码
|
└── src/main/java/com/qaup/adxp/adapter/
|
||||||
|
├── AdxpAdapterApplication.java # 主应用类
|
||||||
|
├── controller/
|
||||||
|
│ └── AdxpController.java # REST API 控制器
|
||||||
|
├── service/
|
||||||
|
│ └── AdxpSdkService.java # SDK 封装服务
|
||||||
|
└── dto/
|
||||||
|
├── LoginRequest.java
|
||||||
|
├── LoginResponse.java
|
||||||
|
├── FlightMessage.java
|
||||||
|
└── MessageResponse.java
|
||||||
```
|
```
|
||||||
|
|
||||||
### 核心依赖
|
### 依赖说明
|
||||||
|
|
||||||
- **adxp-client-2.6.9.jar**: 数据中台 SDK
|
- `adxp-client-2.6.9.jar`: 数据中台 SDK
|
||||||
- **Apache CXF 3.2.4**: SOAP 协议支持
|
- `mq.allclient-9.0.jar`: IBM MQ 客户端
|
||||||
- **Jackson 1.9.13**: JSON 序列化
|
- Apache CXF 3.2.4: SOAP 支持
|
||||||
- **Spring Boot 2.7.18**: 应用框架
|
- Jackson 1.9.13: JSON 序列化
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
|
|||||||
@ -7,10 +7,8 @@ services:
|
|||||||
- "8086:8086"
|
- "8086:8086"
|
||||||
environment:
|
environment:
|
||||||
# ADXP 数据中台连接配置(必须设置)
|
# ADXP 数据中台连接配置(必须设置)
|
||||||
ADXP_HOST: ${ADXP_HOST:-10.32.38.2}
|
ADXP_HOST: ${ADXP_HOST:-10.10.10.100}
|
||||||
ADXP_PORT: ${ADXP_PORT:-8081}
|
ADXP_PORT: ${ADXP_PORT:-7001}
|
||||||
ADXP_USERNAME: ${ADXP_USERNAME:-dianxin}
|
|
||||||
ADXP_PASSWORD: ${ADXP_PASSWORD:-Dianxin#2025}
|
|
||||||
# Spring Profile
|
# Spring Profile
|
||||||
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod}
|
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod}
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
Binary file not shown.
@ -1,6 +1,8 @@
|
|||||||
package com.qaup.adxp.adapter.config;
|
package com.qaup.adxp.adapter.config;
|
||||||
|
|
||||||
|
import com.qaup.adxp.adapter.service.AdxpSdkService;
|
||||||
import com.qaup.adxp.adapter.websocket.AdxpWebSocketHandler;
|
import com.qaup.adxp.adapter.websocket.AdxpWebSocketHandler;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||||
@ -11,9 +13,12 @@ import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry
|
|||||||
@EnableWebSocket
|
@EnableWebSocket
|
||||||
public class WebSocketConfig implements WebSocketConfigurer {
|
public class WebSocketConfig implements WebSocketConfigurer {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AdxpSdkService adxpSdkService;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public AdxpWebSocketHandler adxpWebSocketHandler() {
|
public AdxpWebSocketHandler adxpWebSocketHandler() {
|
||||||
return new AdxpWebSocketHandler();
|
return new AdxpWebSocketHandler(adxpSdkService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -26,82 +26,51 @@ public class AdxpController {
|
|||||||
private AdxpWebSocketHandler adxpWebSocketHandler;
|
private AdxpWebSocketHandler adxpWebSocketHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取连接状态
|
* 登录接口
|
||||||
*/
|
*/
|
||||||
@GetMapping("/status")
|
@PostMapping(value = "/login", produces = "application/json")
|
||||||
public ResponseEntity<Map<String, Object>> getStatus() {
|
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> status = new HashMap<>();
|
String sessionId = adxpSdkService.login(request.getUsername(), request.getPassword());
|
||||||
status.put("connected", adxpSdkService.isConnected());
|
return ResponseEntity.ok(LoginResponse.success(sessionId));
|
||||||
status.put("connectionInfo", adxpSdkService.getConnectionInfo());
|
|
||||||
status.put("timestamp", System.currentTimeMillis());
|
|
||||||
return ResponseEntity.ok(status);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("获取连接状态失败", e);
|
log.error("登录失败", e);
|
||||||
Map<String, Object> error = new HashMap<>();
|
return ResponseEntity.ok(LoginResponse.failure(e.getMessage()));
|
||||||
error.put("connected", false);
|
|
||||||
error.put("error", e.getMessage());
|
|
||||||
return ResponseEntity.ok(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 强制重连
|
|
||||||
*/
|
|
||||||
@PostMapping("/reconnect")
|
|
||||||
public ResponseEntity<Map<String, Object>> reconnect() {
|
|
||||||
try {
|
|
||||||
adxpSdkService.forceReconnect();
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
result.put("success", true);
|
|
||||||
result.put("message", "重连成功");
|
|
||||||
result.put("connected", adxpSdkService.isConnected());
|
|
||||||
return ResponseEntity.ok(result);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("重连失败", e);
|
|
||||||
Map<String, Object> error = new HashMap<>();
|
|
||||||
error.put("success", false);
|
|
||||||
error.put("message", "重连失败: " + e.getMessage());
|
|
||||||
error.put("connected", false);
|
|
||||||
return ResponseEntity.ok(error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取消息接口 - 使用当前连接
|
* 接收消息接口
|
||||||
*/
|
*/
|
||||||
@GetMapping("/messages")
|
@GetMapping("/messages")
|
||||||
public ResponseEntity<MessageResponse> getMessages() {
|
public ResponseEntity<MessageResponse> getMessages(@RequestParam String sessionId) {
|
||||||
try {
|
try {
|
||||||
if (!adxpSdkService.isConnected()) {
|
List<FlightMessage> messages = adxpSdkService.receiveMessages(sessionId);
|
||||||
return ResponseEntity.ok(MessageResponse.failure("未连接到ADXP数据中台"));
|
|
||||||
}
|
|
||||||
List<FlightMessage> messages = adxpSdkService.getMessages();
|
|
||||||
return ResponseEntity.ok(MessageResponse.success(messages));
|
return ResponseEntity.ok(MessageResponse.success(messages));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("接收消息失败", e);
|
log.error("接收消息失败: sessionId={}", sessionId, e);
|
||||||
return ResponseEntity.ok(MessageResponse.failure(e.getMessage()));
|
return ResponseEntity.ok(MessageResponse.failure(e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 断开连接接口
|
* 登出接口
|
||||||
*/
|
*/
|
||||||
@PostMapping("/disconnect")
|
@PostMapping("/logout")
|
||||||
public ResponseEntity<Map<String, Object>> disconnect() {
|
public ResponseEntity<Map<String, Object>> logout(@RequestBody Map<String, String> request) {
|
||||||
try {
|
try {
|
||||||
adxpSdkService.disconnectFromADXPServer();
|
String sessionId = request.get("sessionId");
|
||||||
Map<String, Object> response = new HashMap<>();
|
adxpSdkService.logout(sessionId);
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<String, Object>();
|
||||||
response.put("success", true);
|
response.put("success", true);
|
||||||
response.put("message", "已断开ADXP数据中台连接");
|
response.put("message", "登出成功");
|
||||||
response.put("connected", false);
|
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("断开连接失败", e);
|
log.error("登出失败", e);
|
||||||
Map<String, Object> response = new HashMap<>();
|
Map<String, Object> response = new HashMap<String, Object>();
|
||||||
response.put("success", false);
|
response.put("success", false);
|
||||||
response.put("message", "断开连接失败: " + e.getMessage());
|
response.put("message", e.getMessage());
|
||||||
response.put("connected", adxpSdkService.isConnected());
|
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,20 +80,12 @@ public class AdxpController {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/health")
|
@GetMapping("/health")
|
||||||
public ResponseEntity<Map<String, Object>> health() {
|
public ResponseEntity<Map<String, Object>> health() {
|
||||||
try {
|
Map<String, Object> health = new HashMap<String, Object>();
|
||||||
Map<String, Object> health = adxpSdkService.healthCheck();
|
health.put("status", "UP");
|
||||||
if (adxpWebSocketHandler != null) {
|
health.put("activeSessions", adxpSdkService.getActiveSessionCount());
|
||||||
health.put("websocketConnections", adxpWebSocketHandler.getSessionCount());
|
if (adxpWebSocketHandler != null) {
|
||||||
}
|
health.put("websocketConnections", adxpWebSocketHandler.getSessionCount());
|
||||||
return ResponseEntity.ok(health);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("健康检查失败", e);
|
|
||||||
Map<String, Object> health = new HashMap<>();
|
|
||||||
health.put("status", "DOWN");
|
|
||||||
health.put("message", "健康检查失败: " + e.getMessage());
|
|
||||||
health.put("connected", false);
|
|
||||||
health.put("activeConnections", 0);
|
|
||||||
return ResponseEntity.ok(health);
|
|
||||||
}
|
}
|
||||||
|
return ResponseEntity.ok(health);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package com.qaup.adxp.adapter.service;
|
package com.qaup.adxp.adapter.service;
|
||||||
|
|
||||||
import com.qaup.adxp.adapter.dto.FlightMessage;
|
import com.qaup.adxp.adapter.dto.FlightMessage;
|
||||||
import com.qaup.adxp.adapter.websocket.AdxpWebSocketHandler;
|
|
||||||
import com.taocares.adxp.client.ADXPClient;
|
import com.taocares.adxp.client.ADXPClient;
|
||||||
import com.taocares.adxp.client.ADXPClientFactory;
|
import com.taocares.adxp.client.ADXPClientFactory;
|
||||||
import com.taocares.adxp.model.LoginResult;
|
import com.taocares.adxp.model.LoginResult;
|
||||||
@ -9,18 +8,13 @@ import com.taocares.adxp.model.MessageResult;
|
|||||||
import com.taocares.adxp.model.MessageList;
|
import com.taocares.adxp.model.MessageList;
|
||||||
import com.taocares.adxp.model.MsgType;
|
import com.taocares.adxp.model.MsgType;
|
||||||
import com.taocares.adxp.model.HeadType;
|
import com.taocares.adxp.model.HeadType;
|
||||||
import com.taocares.adxp.AdxpConstants;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import javax.annotation.PreDestroy;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class AdxpSdkService {
|
public class AdxpSdkService {
|
||||||
@ -33,199 +27,98 @@ public class AdxpSdkService {
|
|||||||
@Value("${adxp.port}")
|
@Value("${adxp.port}")
|
||||||
private int port;
|
private int port;
|
||||||
|
|
||||||
@Value("${adxp.username:dianxin}")
|
@Value("${adxp.username:}")
|
||||||
private String defaultUsername;
|
private String defaultUsername;
|
||||||
|
|
||||||
@Value("${adxp.password:Dianxin#2025}")
|
@Value("${adxp.password:}")
|
||||||
private String defaultPassword;
|
private String defaultPassword;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AdxpWebSocketHandler adxpWebSocketHandler;
|
|
||||||
|
|
||||||
// 单一连接状态管理
|
|
||||||
private volatile boolean connected = false;
|
|
||||||
private ADXPClient adxpClient = null;
|
|
||||||
|
|
||||||
// 定时重连任务
|
|
||||||
private final ScheduledExecutorService reconnectScheduler = Executors.newSingleThreadScheduledExecutor();
|
|
||||||
private final Object lock = new Object();
|
|
||||||
|
|
||||||
// 应用启动时主动连接
|
// Session 管理: sessionId -> SessionInfo
|
||||||
@PostConstruct
|
private final Map<String, SessionInfo> sessions = new ConcurrentHashMap<>();
|
||||||
public void initConnection() {
|
|
||||||
log.info("ADXP适配器启动,主动连接数据中台...");
|
public Map<String, SessionInfo> getSessions() {
|
||||||
try {
|
return sessions;
|
||||||
connectToADXP();
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("ADXP数据中台初始连接失败: {}", e.getMessage());
|
|
||||||
// 启动定时重连
|
|
||||||
scheduleReconnect();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用关闭时清理资源
|
public static class SessionInfo {
|
||||||
@PreDestroy
|
ADXPClient client;
|
||||||
public void cleanup() {
|
String username;
|
||||||
log.info("ADXP适配器关闭,清理资源...");
|
String password;
|
||||||
disconnectFromADXP();
|
|
||||||
reconnectScheduler.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 主动连接到ADXP数据中台
|
|
||||||
private void connectToADXP() {
|
|
||||||
synchronized (lock) {
|
|
||||||
try {
|
|
||||||
log.info("正在主动连接 ADXP 数据中台: host={}, port={}, username={}",
|
|
||||||
host, port, defaultUsername);
|
|
||||||
|
|
||||||
// 创建客户端
|
|
||||||
adxpClient = ADXPClientFactory.createWSClient(host, port);
|
|
||||||
|
|
||||||
// 登录
|
|
||||||
LoginResult result = adxpClient.login(defaultUsername, defaultPassword);
|
|
||||||
|
|
||||||
boolean loginSuccess = result != null && Boolean.TRUE.equals(result.isSuccess());
|
|
||||||
|
|
||||||
if (loginSuccess) {
|
|
||||||
connected = true;
|
|
||||||
log.info("用户: {} 登录平台成功", defaultUsername);
|
|
||||||
// 启动消息监听
|
|
||||||
startMessageListener();
|
|
||||||
} else {
|
|
||||||
connected = false;
|
|
||||||
String errorMsg = result != null ?
|
|
||||||
String.format("code=%s, message=%s", result.getCode(), result.getMessage()) :
|
|
||||||
"登录响应为空";
|
|
||||||
log.error("用户: {} 登录平台失败: {}", defaultUsername, errorMsg);
|
|
||||||
throw new RuntimeException("ADXP登录失败: " + errorMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
connected = false;
|
|
||||||
adxpClient = null;
|
|
||||||
log.error("ADXP数据中台连接失败: {}", e.getMessage());
|
|
||||||
throw new RuntimeException("ADXP连接失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 断开连接
|
|
||||||
private void disconnectFromADXP() {
|
|
||||||
synchronized (lock) {
|
|
||||||
try {
|
|
||||||
if (adxpClient != null) {
|
|
||||||
adxpClient.logout(defaultUsername, defaultPassword);
|
|
||||||
}
|
|
||||||
connected = false;
|
|
||||||
adxpClient = null;
|
|
||||||
log.info("已断开ADXP数据中台连接");
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("断开连接时发生错误", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定时重连任务
|
|
||||||
private void scheduleReconnect() {
|
|
||||||
reconnectScheduler.scheduleAtFixedRate(() -> {
|
|
||||||
if (!connected) {
|
|
||||||
try {
|
|
||||||
log.info("尝试重连ADXP数据中台...");
|
|
||||||
connectToADXP();
|
|
||||||
log.info("重连成功!");
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug("重连失败: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 30, 30, TimeUnit.SECONDS); // 每30秒重连一次
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查连接状态 - 供外部使用
|
|
||||||
*/
|
|
||||||
public boolean isConnected() {
|
|
||||||
return connected && adxpClient != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 旧的healthCheck方法已移除,使用返回Map的新版本
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前连接信息
|
|
||||||
*/
|
|
||||||
public String getConnectionInfo() {
|
|
||||||
if (connected) {
|
|
||||||
return String.format("已连接到ADXP数据中台 (host=%s, port=%s)", host, port);
|
|
||||||
} else {
|
|
||||||
return "未连接到ADXP数据中台";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 强制重连 - 供外部调用
|
|
||||||
*/
|
|
||||||
public void forceReconnect() {
|
|
||||||
log.info("外部请求强制重连ADXP数据中台");
|
|
||||||
disconnectFromADXP();
|
|
||||||
try {
|
|
||||||
connectToADXP();
|
|
||||||
log.info("强制重连成功");
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("强制重连失败", e);
|
|
||||||
throw new RuntimeException("重连失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 启动消息监听线程
|
|
||||||
*/
|
|
||||||
private void startMessageListener() {
|
|
||||||
Thread messageListener = new Thread(() -> {
|
|
||||||
log.info("ADXP消息监听线程已启动");
|
|
||||||
while (connected && adxpClient != null) {
|
|
||||||
try {
|
|
||||||
List<FlightMessage> messages = receiveMessages();
|
|
||||||
if (!messages.isEmpty()) {
|
|
||||||
log.info("接收到 {} 条消息", messages.size());
|
|
||||||
// 处理接收到的消息
|
|
||||||
processMessages(messages);
|
|
||||||
}
|
|
||||||
Thread.sleep(1000); // 每秒检查一次
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
log.info("消息监听线程被中断");
|
|
||||||
break;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("消息监听异常", e);
|
|
||||||
try {
|
|
||||||
Thread.sleep(5000); // 异常时等待5秒再重试
|
|
||||||
} catch (InterruptedException ie) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.info("ADXP消息监听线程已结束");
|
|
||||||
}, "ADXP-Message-Listener");
|
|
||||||
|
|
||||||
messageListener.setDaemon(true);
|
SessionInfo(ADXPClient client, String username, String password) {
|
||||||
messageListener.start();
|
this.client = client;
|
||||||
}
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
/**
|
|
||||||
* 消息接收方法
|
|
||||||
*/
|
|
||||||
private List<FlightMessage> receiveMessages() {
|
|
||||||
if (!connected || adxpClient == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ADXPClient getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录数据中台
|
||||||
|
*/
|
||||||
|
public String login(String username, String password) {
|
||||||
|
// 如果未提供用户名或密码,则使用配置文件中的默认值
|
||||||
|
String actualUsername = (username == null || username.isEmpty()) ? defaultUsername : username;
|
||||||
|
String actualPassword = (password == null || password.isEmpty()) ? defaultPassword : password;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MessageResult result = adxpClient.receiveMessage();
|
log.info("正在登录 ADXP 数据中台: host={}, port={}, username={}", host, port, actualUsername);
|
||||||
|
|
||||||
// 使用统一的错误码处理方法
|
// 创建 SDK 客户端
|
||||||
if (handleMessageResultError(result)) {
|
ADXPClient client = ADXPClientFactory.createWSClient(host, port);
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
// 调用登录
|
||||||
|
LoginResult result = client.login(actualUsername, actualPassword);
|
||||||
|
|
||||||
|
// 生成 session ID(即使登录响应解析失败,也创建 session)
|
||||||
|
String sessionId = UUID.randomUUID().toString();
|
||||||
|
sessions.put(sessionId, new SessionInfo(client, actualUsername, actualPassword));
|
||||||
|
|
||||||
if (result == null || !Boolean.TRUE.equals(result.isSuccess())) {
|
if (result == null || !Boolean.TRUE.equals(result.isSuccess())) {
|
||||||
|
String errorMsg = result != null ?
|
||||||
|
String.format("code=%s, message=%s", result.getCode(), result.getMessage()) :
|
||||||
|
"LoginResult is null (但 SDK 后台线程可能已启动)";
|
||||||
|
log.warn("登录响应解析失败: {} - 但仍创建 session,尝试接收消息", errorMsg);
|
||||||
|
} else {
|
||||||
|
log.info("登录成功: sessionId={}", sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionId;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("登录异常", e);
|
||||||
|
throw new RuntimeException("登录异常: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接收消息
|
||||||
|
*/
|
||||||
|
public List<FlightMessage> receiveMessages(String sessionId) {
|
||||||
|
SessionInfo sessionInfo = sessions.get(sessionId);
|
||||||
|
if (sessionInfo == null) {
|
||||||
|
throw new IllegalStateException("Session 不存在或已过期: " + sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
MessageResult result = sessionInfo.client.receiveMessage();
|
||||||
|
|
||||||
|
if (result == null || !Boolean.TRUE.equals(result.isSuccess())) {
|
||||||
|
String errorMsg = result != null ?
|
||||||
|
String.format("code=%s", result.getCode()) :
|
||||||
|
"MessageResult is null";
|
||||||
|
log.warn("接收消息失败: {}", errorMsg);
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,123 +141,34 @@ public class AdxpSdkService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.debug("接收到 {} 条消息", messages.size());
|
||||||
return messages;
|
return messages;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
handleConnectionError(e);
|
log.error("接收消息异常: sessionId={}", sessionId, e);
|
||||||
return Collections.emptyList();
|
throw new RuntimeException("接收消息异常: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleConnectionError(Exception e) {
|
|
||||||
log.error("ADXP连接错误: {}", e.getMessage());
|
|
||||||
// 对于所有连接错误,直接尝试重新连接
|
|
||||||
log.info("尝试重新连接ADXP服务器");
|
|
||||||
reconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理接收到的消息并广播到WebSocket客户端
|
|
||||||
*/
|
|
||||||
private void processMessages(List<FlightMessage> messages) {
|
|
||||||
if (messages != null && !messages.isEmpty()) {
|
|
||||||
try {
|
|
||||||
log.info("接收到 {} 条消息,准备广播", messages.size());
|
|
||||||
// 将消息广播到WebSocket客户端
|
|
||||||
if (adxpWebSocketHandler != null) {
|
|
||||||
adxpWebSocketHandler.broadcastMessages(messages);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("广播消息失败: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理消息结果错误码
|
|
||||||
*/
|
|
||||||
private boolean handleMessageResultError(MessageResult result) {
|
|
||||||
if (result == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int code = result.getCode();
|
|
||||||
// 使用正确的常量类引用
|
|
||||||
if (code == AdxpConstants.RC_CLIENT_NOT_LOGGED_IN ||
|
|
||||||
code == AdxpConstants.RC_TOKEN_EXPIRED ||
|
|
||||||
code == AdxpConstants.RC_CLIENT_EXCEPTION ||
|
|
||||||
code == AdxpConstants.RC_REMOTE_EXCEPTION) {
|
|
||||||
log.info("遇到错误码 {},需要重新连接", code);
|
|
||||||
reconnect();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取消息 - 供外部调用
|
* 登出
|
||||||
*/
|
*/
|
||||||
public List<FlightMessage> getMessages() {
|
public void logout(String sessionId) {
|
||||||
return receiveMessages();
|
SessionInfo sessionInfo = sessions.remove(sessionId);
|
||||||
}
|
if (sessionInfo != null) {
|
||||||
|
|
||||||
/**
|
|
||||||
* 断开连接 - 主动断开与ADXP数据中台的连接
|
|
||||||
*/
|
|
||||||
public void disconnectFromADXPServer() {
|
|
||||||
log.info("主动断开ADXP数据中台连接");
|
|
||||||
disconnectFromADXP();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重新连接方法
|
|
||||||
*/
|
|
||||||
private void reconnect() {
|
|
||||||
synchronized (lock) {
|
|
||||||
try {
|
try {
|
||||||
log.info("尝试重新连接ADXP数据中台");
|
sessionInfo.client.logout(sessionInfo.username, sessionInfo.password);
|
||||||
// 先断开现有连接
|
log.info("登出成功: sessionId={}", sessionId);
|
||||||
if (adxpClient != null) {
|
|
||||||
try {
|
|
||||||
adxpClient.logout(defaultUsername, defaultPassword);
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重新创建客户端并登录
|
|
||||||
adxpClient = ADXPClientFactory.createWSClient(host, port);
|
|
||||||
LoginResult loginResult = adxpClient.login(defaultUsername, defaultPassword);
|
|
||||||
|
|
||||||
if (loginResult != null && Boolean.TRUE.equals(loginResult.isSuccess())) {
|
|
||||||
connected = true;
|
|
||||||
log.info("重新连接成功");
|
|
||||||
} else {
|
|
||||||
connected = false;
|
|
||||||
log.error("重新连接失败");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
connected = false;
|
log.error("登出异常: sessionId={}", sessionId, e);
|
||||||
adxpClient = null;
|
|
||||||
log.error("重新连接异常: {}", e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 健康检查
|
* 获取当前会话数
|
||||||
*/
|
*/
|
||||||
public Map<String, Object> healthCheck() {
|
public int getActiveSessionCount() {
|
||||||
Map<String, Object> health = new HashMap<>();
|
return sessions.size();
|
||||||
health.put("status", connected ? "UP" : "DOWN");
|
|
||||||
health.put("connected", connected);
|
|
||||||
health.put("host", host);
|
|
||||||
health.put("port", port);
|
|
||||||
|
|
||||||
if (connected) {
|
|
||||||
health.put("message", "ADXP数据中台连接正常");
|
|
||||||
} else {
|
|
||||||
health.put("message", "ADXP数据中台连接异常");
|
|
||||||
}
|
|
||||||
|
|
||||||
return health;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.annotation.PreDestroy;
|
import javax.annotation.PreDestroy;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -21,36 +23,97 @@ public class MessageListenerService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private AdxpWebSocketHandler adxpWebSocketHandler;
|
private AdxpWebSocketHandler adxpWebSocketHandler;
|
||||||
|
|
||||||
|
private ExecutorService executorService;
|
||||||
private final AtomicBoolean isRunning = new AtomicBoolean(false);
|
private final AtomicBoolean isRunning = new AtomicBoolean(false);
|
||||||
|
|
||||||
/**
|
|
||||||
* 服务启动时的初始化方法
|
|
||||||
* 注意:消息监听逻辑已移至AdxpSdkService中,本服务仅负责管理和监控
|
|
||||||
*/
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void start() {
|
public void start() {
|
||||||
if (isRunning.compareAndSet(false, true)) {
|
if (isRunning.compareAndSet(false, true)) {
|
||||||
log.info("消息监听服务已启动 - 消息监听逻辑已移至AdxpSdkService");
|
executorService = Executors.newSingleThreadExecutor(r -> {
|
||||||
|
Thread t = new Thread(r, "ADXP-Message-Listener");
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
executorService.submit(this::listenForMessages);
|
||||||
|
log.info("消息监听服务已启动");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreDestroy
|
@PreDestroy
|
||||||
public void stop() {
|
public void stop() {
|
||||||
if (isRunning.compareAndSet(true, false)) {
|
if (isRunning.compareAndSet(true, false)) {
|
||||||
|
if (executorService != null) {
|
||||||
|
executorService.shutdownNow();
|
||||||
|
}
|
||||||
log.info("消息监听服务已停止");
|
log.info("消息监听服务已停止");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void listenForMessages() {
|
||||||
|
log.info("开始监听数据中台消息");
|
||||||
|
|
||||||
|
// 获取所有活跃的会话ID
|
||||||
|
while (isRunning.get()) {
|
||||||
|
try {
|
||||||
|
// 获取当前会话数量
|
||||||
|
int sessionCount = adxpSdkService.getSessions().size();
|
||||||
|
if (sessionCount == 0) {
|
||||||
|
log.debug("当前没有活跃的会话,等待连接...");
|
||||||
|
Thread.sleep(1000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历所有会话并接收消息
|
||||||
|
adxpSdkService.getSessions().forEach((sessionId, sessionInfo) -> {
|
||||||
|
try {
|
||||||
|
// 调用SDK接收消息(这可能会阻塞直到有消息到达)
|
||||||
|
java.util.List<com.qaup.adxp.adapter.dto.FlightMessage> messages =
|
||||||
|
adxpSdkService.receiveMessages(sessionId);
|
||||||
|
|
||||||
|
// 如果有消息,广播给所有WebSocket客户端
|
||||||
|
if (messages != null && !messages.isEmpty()) {
|
||||||
|
adxpWebSocketHandler.broadcastMessages(messages);
|
||||||
|
log.info("接收到 {} 条航班消息并广播给 {} 个WebSocket客户端",
|
||||||
|
messages.size(), adxpWebSocketHandler.getSessionCount());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("处理会话消息失败: sessionId={}", sessionId, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 短暂休眠避免过于频繁的轮询
|
||||||
|
Thread.sleep(100);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.info("消息监听线程被中断");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("消息监听过程中发生错误", e);
|
||||||
|
|
||||||
|
// 出错后短暂休眠再重试
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("消息监听服务已停止监听");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取服务统计信息
|
* 获取服务统计信息
|
||||||
*/
|
*/
|
||||||
public String getStats() {
|
public String getStats() {
|
||||||
return String.format("MessageListenerService Stats: " +
|
return String.format("MessageListenerService Stats: " +
|
||||||
"isRunning=%s, " +
|
"isRunning=%s, " +
|
||||||
"adxpConnected=%s, " +
|
"sessions=%d, " +
|
||||||
"webSocketClients=%d",
|
"webSocketClients=%d",
|
||||||
isRunning.get(),
|
isRunning.get(),
|
||||||
adxpSdkService.isConnected(),
|
adxpSdkService.getSessions().size(),
|
||||||
adxpWebSocketHandler.getSessionCount());
|
adxpWebSocketHandler.getSessionCount());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,30 +1,32 @@
|
|||||||
package com.qaup.adxp.adapter.websocket;
|
package com.qaup.adxp.adapter.websocket;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
|
||||||
import com.qaup.adxp.adapter.dto.FlightMessage;
|
import com.qaup.adxp.adapter.dto.FlightMessage;
|
||||||
|
import com.qaup.adxp.adapter.service.AdxpSdkService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.web.socket.CloseStatus;
|
import org.springframework.web.socket.CloseStatus;
|
||||||
import org.springframework.web.socket.TextMessage;
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
import org.springframework.web.socket.WebSocketHandler;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.stream.Collectors;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class AdxpWebSocketHandler extends TextWebSocketHandler {
|
public class AdxpWebSocketHandler extends TextWebSocketHandler {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(AdxpWebSocketHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(AdxpWebSocketHandler.class);
|
||||||
|
|
||||||
private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
|
private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
|
||||||
|
private final AdxpSdkService adxpSdkService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
private final AtomicBoolean isRunning = new AtomicBoolean(false);
|
||||||
|
|
||||||
public AdxpWebSocketHandler() {
|
public AdxpWebSocketHandler(AdxpSdkService adxpSdkService) {
|
||||||
|
this.adxpSdkService = adxpSdkService;
|
||||||
this.objectMapper = new ObjectMapper();
|
this.objectMapper = new ObjectMapper();
|
||||||
// 配置ObjectMapper以更好地处理日期和其他序列化问题
|
|
||||||
this.objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -62,47 +64,41 @@ public class AdxpWebSocketHandler extends TextWebSocketHandler {
|
|||||||
* 向所有连接的客户端广播消息
|
* 向所有连接的客户端广播消息
|
||||||
*/
|
*/
|
||||||
public void broadcastMessages(List<FlightMessage> messages) {
|
public void broadcastMessages(List<FlightMessage> messages) {
|
||||||
if (messages == null || messages.isEmpty() || sessions.isEmpty()) {
|
if (messages == null || messages.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 序列化消息列表为JSON
|
|
||||||
String jsonMessage = objectMapper.writeValueAsString(messages);
|
String jsonMessage = objectMapper.writeValueAsString(messages);
|
||||||
TextMessage textMessage = new TextMessage(jsonMessage);
|
TextMessage textMessage = new TextMessage(jsonMessage);
|
||||||
|
|
||||||
// 清理已关闭的会话
|
int successCount = 0;
|
||||||
cleanupClosedSessions();
|
int failedCount = 0;
|
||||||
|
|
||||||
int clientCount = sessions.size();
|
|
||||||
|
|
||||||
// 向所有活跃客户端广播消息
|
|
||||||
for (WebSocketSession session : sessions) {
|
for (WebSocketSession session : sessions) {
|
||||||
if (session.isOpen()) {
|
if (session.isOpen()) {
|
||||||
try {
|
try {
|
||||||
session.sendMessage(textMessage);
|
session.sendMessage(textMessage);
|
||||||
|
successCount++;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("发送消息失败: sessionId={}", session.getId(), e);
|
log.error("发送消息失败: sessionId={}", session.getId(), e);
|
||||||
|
// 如果发送失败,移除会话
|
||||||
sessions.remove(session);
|
sessions.remove(session);
|
||||||
|
failedCount++;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
sessions.remove(session);
|
||||||
|
failedCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
log.debug("广播消息完成: 消息数量={}, 连接数={}, 成功发送={}, 失败={}",
|
||||||
log.debug("已向 {} 个客户端广播 {} 条消息", clientCount, messages.size());
|
messages.size(), sessions.size(), successCount, failedCount);
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("序列化或广播消息失败", e);
|
log.error("序列化或广播消息失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理已关闭的WebSocket会话
|
|
||||||
*/
|
|
||||||
private void cleanupClosedSessions() {
|
|
||||||
sessions.removeIf(session -> !session.isOpen());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前连接数
|
* 获取当前连接数
|
||||||
*/
|
*/
|
||||||
@ -133,10 +129,10 @@ public class AdxpWebSocketHandler extends TextWebSocketHandler {
|
|||||||
*/
|
*/
|
||||||
public List<String> getConnectionDetails() {
|
public List<String> getConnectionDetails() {
|
||||||
return sessions.stream()
|
return sessions.stream()
|
||||||
.filter(WebSocketSession::isOpen)
|
.map(session -> String.format("ID: %s, Remote Address: %s, Open: %s",
|
||||||
.map(session -> String.format("ID: %s, Remote Address: %s",
|
|
||||||
session.getId(),
|
session.getId(),
|
||||||
session.getRemoteAddress()))
|
session.getRemoteAddress(),
|
||||||
.collect(Collectors.toList());
|
session.isOpen()))
|
||||||
|
.collect(java.util.stream.Collectors.toList());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
51
adxp-adapter/start.sh
Executable file
51
adxp-adapter/start.sh
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# ADXP Adapter 启动脚本
|
||||||
|
|
||||||
|
# 项目路径
|
||||||
|
PROJECT_DIR="/Users/tianjianyong/apps/Company/QAUP-Management/adxp-adapter"
|
||||||
|
JAR_FILE="$PROJECT_DIR/target/adxp-adapter.jar"
|
||||||
|
|
||||||
|
# 检查项目目录是否存在
|
||||||
|
if [ ! -d "$PROJECT_DIR" ]; then
|
||||||
|
echo "❌ 项目目录不存在: $PROJECT_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 进入项目目录
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
|
||||||
|
# 检查JAR文件是否存在,如果不存在则编译
|
||||||
|
if [ ! -f "$JAR_FILE" ]; then
|
||||||
|
echo "🔨 正在编译adxp-adapter项目..."
|
||||||
|
mvn clean package
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ 编译失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查JAR文件是否存在
|
||||||
|
if [ ! -f "$JAR_FILE" ]; then
|
||||||
|
echo "❌ JAR文件不存在: $JAR_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🚀 正在启动ADXP Adapter服务..."
|
||||||
|
echo "📄 JAR文件: $JAR_FILE"
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
java -jar "$JAR_FILE" &
|
||||||
|
|
||||||
|
# 等待几秒钟让服务启动
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# 检查服务是否启动成功
|
||||||
|
if pgrep -f "adxp-adapter" > /dev/null; then
|
||||||
|
echo "✅ ADXP Adapter服务启动成功"
|
||||||
|
echo "🌐 WebSocket端点: ws://localhost:8086/ws/flight-notifications"
|
||||||
|
echo "📊 健康检查: http://localhost:8086/actuator/health"
|
||||||
|
else
|
||||||
|
echo "❌ ADXP Adapter服务启动失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
118
deploy/BUILD-GUIDE.md
Normal file
118
deploy/BUILD-GUIDE.md
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
# QAUP 构建打包指南
|
||||||
|
|
||||||
|
## 方案:macOS构建 + Ubuntu打包
|
||||||
|
|
||||||
|
### 开发环境操作(macOS)
|
||||||
|
|
||||||
|
#### 1. 构建jar文件
|
||||||
|
```bash
|
||||||
|
# 在项目根目录
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 上传jar文件到打包服务器
|
||||||
|
```bash
|
||||||
|
# 方式一:使用scp
|
||||||
|
scp qaup-admin/target/qaup-admin.jar code@your-server:/home/code/apps/QAUP_Management/qaup-admin/target/
|
||||||
|
|
||||||
|
# 方式二:使用自动化脚本(需要先配置服务器信息)
|
||||||
|
./deploy/simple/build-and-upload.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 打包服务器操作(Ubuntu)
|
||||||
|
|
||||||
|
#### 1. 确认jar文件已上传
|
||||||
|
```bash
|
||||||
|
ls -la qaup-admin/target/qaup-admin.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 执行打包
|
||||||
|
```bash
|
||||||
|
./deploy/simple/package-server.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 下载部署包
|
||||||
|
```bash
|
||||||
|
# 生成的文件类似:qaup-deploy-20250105-143022.tar.gz
|
||||||
|
ls -la qaup-deploy-*.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
## 日常更新流程
|
||||||
|
|
||||||
|
### 程序更新(推荐)
|
||||||
|
```bash
|
||||||
|
# macOS开发环境
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
scp qaup-admin/target/qaup-admin.jar production-server:/path/to/qaup-deploy/new-app.jar
|
||||||
|
|
||||||
|
# 生产服务器
|
||||||
|
cd qaup-deploy
|
||||||
|
./update.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意:** 从v1.0.1版本开始,系统集成了Flyway数据库迁移功能:
|
||||||
|
- 应用启动时会自动执行数据库迁移
|
||||||
|
- 新版本的数据库结构变更会自动应用
|
||||||
|
- 无需手动执行SQL脚本
|
||||||
|
- 迁移失败时应用会启动失败,确保数据一致性
|
||||||
|
|
||||||
|
### 完整重新部署
|
||||||
|
```bash
|
||||||
|
# macOS → 打包服务器 → 生产服务器
|
||||||
|
# 按照上述完整流程操作
|
||||||
|
```
|
||||||
|
|
||||||
|
## 服务器配置要求
|
||||||
|
|
||||||
|
### 打包服务器(Ubuntu)
|
||||||
|
- ✅ Docker已安装
|
||||||
|
- ✅ 网络连接正常(拉取镜像)
|
||||||
|
- ✅ 足够磁盘空间(约2GB)
|
||||||
|
|
||||||
|
### 生产服务器(Ubuntu)
|
||||||
|
- ✅ Docker已安装
|
||||||
|
- ✅ 解压工具(unzip)
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### Q: Docker拉取镜像很慢
|
||||||
|
A: 使用国内镜像加速器,脚本已配置daocloud加速器
|
||||||
|
|
||||||
|
### Q: jar文件上传失败
|
||||||
|
A: 检查网络连接和服务器路径是否正确
|
||||||
|
|
||||||
|
### Q: 打包过程中断
|
||||||
|
A: 重新运行package-server.sh脚本,会自动清理并重新开始
|
||||||
|
|
||||||
|
### Q: 数据库迁移失败
|
||||||
|
A: 检查应用日志中的Flyway错误信息:
|
||||||
|
```bash
|
||||||
|
docker compose logs qaup-app | grep -i flyway
|
||||||
|
```
|
||||||
|
常见问题:
|
||||||
|
- 数据库连接失败:检查数据库服务是否正常
|
||||||
|
- 迁移脚本错误:检查SQL语法和数据库权限
|
||||||
|
- 版本冲突:可能需要手动修复迁移历史表
|
||||||
|
|
||||||
|
### Q: 应用启动失败(Flyway相关)
|
||||||
|
A: 查看详细错误信息:
|
||||||
|
```bash
|
||||||
|
# 查看应用日志
|
||||||
|
docker compose logs qaup-app
|
||||||
|
|
||||||
|
# 检查数据库迁移状态
|
||||||
|
docker exec -it qaup-postgres psql -U qaup qaup -c "SELECT * FROM flyway_schema_history ORDER BY installed_rank;"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 脚本说明
|
||||||
|
|
||||||
|
### 开发环境脚本:
|
||||||
|
- `build-and-upload.sh` - 自动构建和上传(需配置服务器信息)
|
||||||
|
- 手动操作:`mvn package` + `scp`
|
||||||
|
|
||||||
|
### 打包服务器脚本:
|
||||||
|
- `package-server.sh` - 使用已有jar文件进行打包
|
||||||
|
|
||||||
|
### 生产服务器脚本:
|
||||||
|
- `deploy.sh` - 首次部署
|
||||||
|
- `update.sh` - 程序更新
|
||||||
@ -1,136 +1,131 @@
|
|||||||
# QAUP 系统部署和更新说明
|
# QAUP 系统部署和更新说明
|
||||||
|
|
||||||
## 一、打包环境操作
|
## 开发环境操作
|
||||||
|
|
||||||
### 1. 生成程序更新文件
|
### 生成程序更新文件
|
||||||
|
|
||||||
修改代码后,生成仅包含应用jar的更新包:
|
当代码修改后,需要生成新的jar文件供客户更新:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./deploy/package-update.sh
|
./deploy/simple/build-update.sh
|
||||||
```
|
```
|
||||||
|
生成包含详细说明的更新包:
|
||||||
**输出**:
|
|
||||||
|
|
||||||
- `qaup-admin.jar` - 应用程序
|
- `qaup-admin.jar` - 应用程序
|
||||||
- `UPDATE-INSTRUCTIONS.md` - 更新说明
|
- `UPDATE-INSTRUCTIONS.md` - 详细更新说明
|
||||||
- `VERSION-INFO.txt` - 版本信息
|
- `VERSION-INFO.txt` - 版本信息
|
||||||
|
|
||||||
**注意**:如需单独jar文件,可直接从更新包中提取。
|
**注意**:如果只需要jar文件,可以从生成的更新包中提取 `qaup-admin.jar` 文件单独发送给客户。
|
||||||
|
|
||||||
### 2. 生成完整部署包
|
|
||||||
|
|
||||||
生成包含所有组件的完整部署包(首次部署或重大更新):
|
|
||||||
|
|
||||||
|
### 生成完整部署包
|
||||||
```bash
|
```bash
|
||||||
./deploy/package-all.sh
|
./deploy/simple/package.sh
|
||||||
```
|
```
|
||||||
|
生成包含所有组件的完整部署包,用于首次部署或重大版本更新。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 二、生产环境操作
|
## 客户环境操作
|
||||||
|
|
||||||
### 1. 首次部署
|
### 首次部署
|
||||||
|
|
||||||
#### 步骤
|
|
||||||
|
|
||||||
1. **解压部署包**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir qaup-deploy
|
|
||||||
|
|
||||||
tar -xzf qaup-deploy-xxx.tar.gz -C qaup-deploy
|
|
||||||
cd qaup-deploy
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **执行部署**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./deploy-all.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **验证部署**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose ps # 检查服务状态
|
|
||||||
curl http://localhost:8080/actuator/health # 验证应用健康
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 程序更新(仅更新jar文件)
|
|
||||||
|
|
||||||
#### 前置准备
|
|
||||||
|
|
||||||
|
### 1. 解压部署包
|
||||||
```bash
|
```bash
|
||||||
|
mkdir qaup-deploy
|
||||||
|
tar -xzf qaup-deploy-20250105-143022.tar.gz -C qaup-deploy
|
||||||
cd qaup-deploy
|
cd qaup-deploy
|
||||||
|
```
|
||||||
|
|
||||||
# 1. 确认当前系统状态
|
### 2. 执行部署
|
||||||
|
```bash
|
||||||
|
./deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 验证部署
|
||||||
|
```bash
|
||||||
|
# 检查服务状态
|
||||||
docker compose ps
|
docker compose ps
|
||||||
|
|
||||||
# 2. 备份重要数据(推荐)
|
# 访问系统
|
||||||
docker exec qaup-postgres pg_dump -U qaup qaup > backup-$(date +%Y%m%d).sql
|
curl http://localhost:8080/actuator/health
|
||||||
cp config.yml config.yml.backup
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 更新步骤
|
## 程序更新(仅更新jar文件)
|
||||||
|
|
||||||
|
### 更新前准备
|
||||||
|
|
||||||
|
1. **确认当前系统状态**:
|
||||||
|
```bash
|
||||||
|
cd qaup-deploy
|
||||||
|
docker compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **备份重要数据**(可选但推荐):
|
||||||
|
```bash
|
||||||
|
# 备份数据库
|
||||||
|
docker exec qaup-postgres pg_dump -U qaup qaup > backup-$(date +%Y%m%d).sql
|
||||||
|
|
||||||
|
# 备份配置文件
|
||||||
|
cp config.yml config.yml.backup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 更新步骤
|
||||||
|
|
||||||
|
### 1. 准备新版本文件
|
||||||
|
|
||||||
|
#### 如果收到的是完整更新包(.tar.gz文件):
|
||||||
```bash
|
```bash
|
||||||
# 1. 准备新版本文件(二选一)
|
# 解压更新包
|
||||||
# 方案A:使用完整更新包
|
tar -xzf qaup-update-20250105-143022.tar.gz
|
||||||
cp /path/to/qaup-update-xxx/qaup-admin.jar new-app.jar
|
cd qaup-update-20250105-143022
|
||||||
|
|
||||||
# 方案B:直接使用jar文件
|
# 查看更新说明
|
||||||
cp /path/to/qaup-admin-xxx.jar new-app.jar
|
cat UPDATE-INSTRUCTIONS.md
|
||||||
|
|
||||||
# 2. 执行更新
|
# 复制jar文件到部署目录
|
||||||
./deploy-update.sh
|
cd ../qaup-deploy
|
||||||
|
cp ../qaup-update-20250105-143022/qaup-admin.jar new-app.jar
|
||||||
# 3. 验证更新
|
|
||||||
docker compose ps # 检查服务状态
|
|
||||||
docker compose logs -f qaup-app # 查看应用日志
|
|
||||||
curl http://localhost:8080/actuator/health # 验证应用健康
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 配置文件更新
|
#### 如果收到的是jar文件:
|
||||||
|
|
||||||
如果需要更新配置文件:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd qaup-deploy
|
cd qaup-deploy
|
||||||
|
cp /path/to/qaup-admin-20250105-143022.jar new-app.jar
|
||||||
# 1. 备份当前配置
|
|
||||||
cp config.yml config.yml.backup
|
|
||||||
|
|
||||||
# 2. 编辑配置文件
|
|
||||||
vi config.yml
|
|
||||||
|
|
||||||
# 3. 重启应用
|
|
||||||
docker compose restart qaup-app
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. 更新失败处理
|
### 2. 执行程序更新
|
||||||
|
|
||||||
#### 自动回滚
|
|
||||||
|
|
||||||
更新失败时脚本会自动尝试回滚。
|
|
||||||
|
|
||||||
#### 手动回滚
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd qaup-deploy
|
./update.sh
|
||||||
|
```
|
||||||
|
|
||||||
# 1. 停止应用
|
### 3. 验证更新结果
|
||||||
docker compose stop qaup-app
|
```bash
|
||||||
|
# 检查服务状态
|
||||||
|
docker compose ps
|
||||||
|
|
||||||
# 2. 恢复备份文件
|
# 检查应用日志
|
||||||
|
docker compose logs -f qaup-app
|
||||||
|
|
||||||
|
# 访问系统确认功能正常
|
||||||
|
curl http://localhost:8080/actuator/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## 更新失败处理
|
||||||
|
|
||||||
|
如果更新失败,脚本会自动尝试回滚。如果自动回滚也失败:
|
||||||
|
|
||||||
|
### 手动回滚:
|
||||||
|
```bash
|
||||||
|
# 停止应用
|
||||||
|
docker compose stop qaup-app
|
||||||
|
|
||||||
|
# 恢复备份文件
|
||||||
cp app.jar.backup.* app.jar
|
cp app.jar.backup.* app.jar
|
||||||
|
|
||||||
# 3. 启动应用
|
# 启动应用
|
||||||
docker compose start qaup-app
|
docker compose start qaup-app
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 问题排查
|
### 检查问题:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 查看应用日志
|
# 查看应用日志
|
||||||
docker compose logs qaup-app
|
docker compose logs qaup-app
|
||||||
@ -139,210 +134,137 @@ docker compose logs qaup-app
|
|||||||
docker compose ps -a
|
docker compose ps -a
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. 完全重新部署
|
## 配置文件更新
|
||||||
|
|
||||||
如果需要重新部署整个系统(包括更新镜像、配置):
|
如果新版本需要更新配置文件:
|
||||||
|
|
||||||
#### 方案A:不保留数据
|
1. **备份当前配置**:
|
||||||
|
```bash
|
||||||
|
cp config.yml config.yml.backup
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **更新配置**:
|
||||||
|
```bash
|
||||||
|
# 编辑配置文件
|
||||||
|
vi config.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **重启应用**:
|
||||||
|
```bash
|
||||||
|
docker compose restart qaup-app
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q: 更新后无法访问系统
|
||||||
|
A: 检查端口是否被占用,查看应用日志:
|
||||||
```bash
|
```bash
|
||||||
cd qaup-deploy
|
docker compose logs qaup-app
|
||||||
|
netstat -tlnp | grep 8080
|
||||||
# 1. 清理旧环境
|
|
||||||
docker compose down
|
|
||||||
rm -rf data/ # 注意:会删除所有数据
|
|
||||||
|
|
||||||
# 2. 重新部署
|
|
||||||
./deploy-all.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 方案B:保留数据
|
### Q: 数据库连接失败
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 备份旧数据
|
|
||||||
cd qaup-deploy
|
|
||||||
docker exec qaup-postgres pg_dump -U qaup qaup > ../data-backup.sql
|
|
||||||
|
|
||||||
# 2. 清理旧环境
|
|
||||||
docker compose down
|
|
||||||
|
|
||||||
# 3. 部署新系统
|
|
||||||
mkdir qaup-deploy-new
|
|
||||||
tar -xzf qaup-deploy-new-xxx.tar.gz -C qaup-deploy-new
|
|
||||||
cd qaup-deploy-new
|
|
||||||
./deploy-all.sh
|
|
||||||
|
|
||||||
# 4. 恢复数据
|
|
||||||
docker exec -i qaup-postgres psql -U qaup qaup < ../data-backup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
## 三、常见问题
|
|
||||||
|
|
||||||
### Q: 更新后无法访问系统?
|
|
||||||
|
|
||||||
A: 检查端口和应用日志:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker compose logs qaup-app
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q: 端口冲突?
|
|
||||||
|
|
||||||
A: 检查端口占用:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
netstat -tlnp | grep 8080
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q: 数据库连接失败?
|
|
||||||
|
|
||||||
A: 检查数据库服务状态:
|
A: 检查数据库服务状态:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose logs qaup-postgres
|
docker compose logs qaup-postgres
|
||||||
docker exec qaup-postgres pg_isready -U qaup
|
docker exec qaup-postgres pg_isready -U qaup
|
||||||
```
|
```
|
||||||
|
|
||||||
## 四、更新记录
|
### Q: 如何完全重新部署
|
||||||
|
A: 如果更新出现严重问题,可以重新部署:
|
||||||
建议记录每次更新信息:
|
|
||||||
|
|
||||||
```
|
|
||||||
时间 | 版本号 | 人员 | 类型 | 结果 | 问题
|
|
||||||
2025-01-05 | v1.0.1→v1.0.2 | 张三 | 程序更新 | 成功 | -
|
|
||||||
2025-01-10 | v1.0.2→v1.0.3 | 李四 | 程序更新 | 失败 | 配置错误,已回滚
|
|
||||||
```
|
|
||||||
|
|
||||||
## 五、数据库部署和更新策略
|
|
||||||
|
|
||||||
### 1. 数据库版本管理
|
|
||||||
|
|
||||||
系统使用Flyway进行数据库版本管理,所有迁移脚本位于`qaup-admin/src/main/resources/db/migration/`目录下。
|
|
||||||
|
|
||||||
**迁移脚本列表**:
|
|
||||||
|
|
||||||
- `V1.0.0__Initial_baseline.sql` - 基线结构(86KB)
|
|
||||||
- `V1.0.1__Initial_data.sql` - 初始数据(50KB)
|
|
||||||
- `README.md` - 迁移脚本说明
|
|
||||||
|
|
||||||
#### Flyway配置(在docker-compose.yml中)
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
SPRING_FLYWAY_ENABLED: true # 启用Flyway
|
|
||||||
SPRING_FLYWAY_BASELINE_ON_MIGRATE: true # 基线迁移(支持已有数据库)
|
|
||||||
SPRING_FLYWAY_VALIDATE_ON_MIGRATE: true # 验证迁移
|
|
||||||
SPRING_FLYWAY_CLEAN_DISABLED: true # 禁止清理生产数据库
|
|
||||||
SPRING_FLYWAY_LOCATIONS: classpath:db/migration # 迁移脚本路径
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 数据库部署流程
|
|
||||||
|
|
||||||
#### 首次部署(全新环境)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 启动基础服务(PostgreSQL + Redis)
|
# 停止所有服务
|
||||||
docker compose up -d qaup-postgres qaup-redis
|
docker compose down
|
||||||
|
|
||||||
# 2. 等待数据库就绪
|
# 清理数据(注意:这会删除所有数据)
|
||||||
sleep 30
|
rm -rf data/
|
||||||
|
|
||||||
# 3. 初始化数据库(执行Flyway迁移)
|
# 重新部署
|
||||||
# 应用启动时会自动执行Flyway迁移
|
./deploy.sh
|
||||||
docker compose up -d qaup-app
|
|
||||||
|
|
||||||
# 4. 验证部署
|
|
||||||
docker compose ps
|
|
||||||
curl http://localhost:8080/actuator/health
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 已有环境更新
|
## 完整系统重新部署
|
||||||
|
|
||||||
|
如果需要重新部署整个系统(包括更新Docker镜像、配置等):
|
||||||
|
|
||||||
|
### 1. 获取新的部署包
|
||||||
```bash
|
```bash
|
||||||
# Flyway自动处理数据库迁移
|
# 解压新的部署包到新目录
|
||||||
# 应用启动时会自动检测并执行需要的迁移
|
mkdir qaup-deploy-new
|
||||||
docker compose restart qaup-app
|
tar -xzf qaup-deploy-new-20250110-100000.tar.gz -C qaup-deploy-new
|
||||||
|
cd qaup-deploy-new
|
||||||
# 检查迁移状态
|
|
||||||
docker exec qaup-postgres psql -U qaup -d qaup -c "SELECT * FROM flyway_schema_history ORDER BY installed_rank;"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 数据库迁移脚本管理
|
### 2. 迁移数据(如果需要保留数据)
|
||||||
|
|
||||||
#### 脚本命名规范
|
|
||||||
|
|
||||||
- `V{版本号}__{描述}.sql` - 版本化迁移脚本
|
|
||||||
- `V1.0.0__Initial_baseline.sql` - 基线结构
|
|
||||||
- `V1.0.1__Initial_data.sql` - 初始数据
|
|
||||||
- `V1.0.2__Add_new_feature.sql` - 新功能
|
|
||||||
|
|
||||||
#### 迁移类型
|
|
||||||
|
|
||||||
1. **结构迁移** - 修改表结构、索引等
|
|
||||||
2. **数据迁移** - 数据清洗、转换等
|
|
||||||
3. **函数/存储过程迁移** - 业务逻辑更新
|
|
||||||
|
|
||||||
### 4. 数据库维护操作
|
|
||||||
|
|
||||||
#### 数据备份和恢复(仅限紧急情况)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 备份数据
|
# 从旧系统备份数据
|
||||||
docker exec qaup-postgres pg_dump -U qaup qaup > backup-$(date +%Y%m%d_%H%M%S).sql
|
cd ../qaup-deploy
|
||||||
|
docker exec qaup-postgres pg_dump -U qaup qaup > ../data-backup.sql
|
||||||
|
|
||||||
# 恢复数据(生产环境需要谨慎)
|
# 停止旧系统
|
||||||
cat backup-20250115_143000.sql | docker exec -i qaup-postgres psql -U qaup -d qaup
|
docker compose down
|
||||||
|
|
||||||
|
# 在新系统中恢复数据
|
||||||
|
cd ../qaup-deploy-new
|
||||||
|
./deploy.sh
|
||||||
|
docker exec -i qaup-postgres psql -U qaup qaup < ../data-backup.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 数据库状态监控
|
## 操作流程总结
|
||||||
|
|
||||||
```bash
|
### 开发环境 → 客户环境流程
|
||||||
# 查看Flyway迁移状态(应用启动日志)
|
|
||||||
docker compose logs qaup-app | grep -i flyway
|
|
||||||
|
|
||||||
# 直接查询迁移历史
|
#### 程序更新流程:
|
||||||
docker exec qaup-postgres psql -U qaup -d qaup -c "
|
```
|
||||||
SELECT version, description, installed_on
|
开发环境:
|
||||||
FROM flyway_schema_history
|
1. 代码修改完成
|
||||||
ORDER BY installed_rank;"
|
2. ./deploy/simple/build-update.sh # 生成更新包
|
||||||
|
3. 发送更新包或jar文件给客户
|
||||||
|
|
||||||
# 检查应用健康状态
|
客户环境:
|
||||||
curl http://localhost:8080/actuator/health
|
1. cd qaup-deploy
|
||||||
|
2. cp new-jar-file.jar new-app.jar # 重命名文件
|
||||||
|
3. ./update.sh # 执行更新
|
||||||
|
4. 验证更新结果
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. 数据库更新最佳实践
|
#### 完整部署流程:
|
||||||
|
```
|
||||||
|
开发环境:
|
||||||
|
1. ./deploy/simple/package.sh # 生成完整部署包
|
||||||
|
2. 发送部署包给客户
|
||||||
|
|
||||||
#### 开发环境
|
客户环境:
|
||||||
|
1. mkdir qaup-deploy && tar -xzf qaup-deploy-xxx.tar.gz -C qaup-deploy # 解压部署包
|
||||||
|
2. cd qaup-deploy
|
||||||
|
3. ./deploy.sh # 执行部署
|
||||||
|
4. 验证部署结果
|
||||||
|
```
|
||||||
|
|
||||||
- 可以使用`flyway.clean()`清理数据库
|
## 更新记录
|
||||||
- 可以使用`flyway.migrate().clean()`重建
|
|
||||||
|
|
||||||
#### 生产环境
|
建议记录每次更新的信息:
|
||||||
|
- 更新时间
|
||||||
|
- 版本号
|
||||||
|
- 更新人员
|
||||||
|
- 更新类型(程序更新/完整部署)
|
||||||
|
- 是否成功
|
||||||
|
- 遇到的问题
|
||||||
|
|
||||||
- 禁止使用`flyway.clean()`
|
示例:
|
||||||
- 优先使用增量迁移脚本
|
```
|
||||||
- 重大变更需要测试环境验证
|
2025-01-05 14:30 - v1.0.1 → v1.0.2 - 张三 - 程序更新 - 成功
|
||||||
|
2025-01-10 09:15 - v1.0.2 → v1.0.3 - 李四 - 程序更新 - 失败,已回滚
|
||||||
|
2025-01-15 16:00 - v1.0.3 → v1.1.0 - 王五 - 完整部署 - 成功
|
||||||
|
```
|
||||||
|
|
||||||
#### 回滚策略
|
## 文件说明
|
||||||
|
|
||||||
1. **自动化回滚** - 备份恢复
|
### 开发环境脚本:
|
||||||
2. **脚本化回滚** - 创建回滚脚本
|
- `deploy/simple/build-update.sh` - 生成程序更新包
|
||||||
3. **版本控制** - 通过Flyway版本管理
|
- `deploy/simple/package.sh` - 生成完整部署包
|
||||||
|
|
||||||
## 六、文件说明
|
### 客户环境脚本:
|
||||||
|
- `deploy.sh` - 首次部署脚本
|
||||||
### 打包环境脚本
|
- `update.sh` - 程序更新脚本
|
||||||
|
- `DeployGuide.md` - 本说明文档
|
||||||
- `deploy/package-update.sh` - 生成程序更新包
|
|
||||||
- `deploy/package-all.sh` - 生成完整部署包
|
|
||||||
|
|
||||||
### 生产环境脚本
|
|
||||||
|
|
||||||
- `deploy-all.sh` - 首次部署/完全重新部署
|
|
||||||
- `deploy-update.sh` - 程序更新脚本(自动处理数据库迁移)
|
|
||||||
- `DeployGuide.md` - 本说明文档
|
|
||||||
|
|
||||||
### 数据库脚本目录
|
|
||||||
|
|
||||||
- `src/main/resources/db/migration/` - Flyway迁移脚本
|
|
||||||
- `sql/qaup_database_complete_init.sql` - 完整初始化脚本(参考用途)
|
|
||||||
- `sql/unified_database_migration.sql` - 合并迁移脚本(历史用途)
|
|
||||||
65
deploy/build-and-upload.sh
Executable file
65
deploy/build-and-upload.sh
Executable file
@ -0,0 +1,65 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# QAUP macOS构建和上传脚本
|
||||||
|
# 在macOS开发环境运行,构建jar并上传到打包服务器
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 配置打包服务器信息(请根据实际情况修改)
|
||||||
|
PACK_SERVER_HOST="10.0.0.58"
|
||||||
|
PACK_SERVER_USER="code"
|
||||||
|
PACK_SERVER_PATH="/home/code/apps/QAUP_Management"
|
||||||
|
|
||||||
|
# 颜色输出
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
print_message() {
|
||||||
|
echo -e "${1}${2}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_message $BLUE "=== QAUP macOS构建和上传脚本 ==="
|
||||||
|
|
||||||
|
# 1. 构建应用
|
||||||
|
print_message $BLUE "构建应用..."
|
||||||
|
mvn clean package -DskipTests -q
|
||||||
|
|
||||||
|
# 检查构建结果
|
||||||
|
JAR_FILE="qaup-admin/target/qaup-admin.jar"
|
||||||
|
if [ ! -f "$JAR_FILE" ]; then
|
||||||
|
print_message $RED "❌ jar文件构建失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_message $GREEN "✓ jar文件构建成功"
|
||||||
|
|
||||||
|
# 2. 上传jar文件到打包服务器
|
||||||
|
print_message $BLUE "上传jar文件到打包服务器..."
|
||||||
|
|
||||||
|
# 检查是否配置了服务器信息
|
||||||
|
if [ "$PACK_SERVER_HOST" = "your-pack-server" ]; then
|
||||||
|
print_message $RED "❌ 请先配置打包服务器信息"
|
||||||
|
echo "编辑脚本,修改以下变量:"
|
||||||
|
echo "PACK_SERVER_HOST=\"your-pack-server-ip\""
|
||||||
|
echo "PACK_SERVER_USER=\"your-username\""
|
||||||
|
echo "PACK_SERVER_PATH=\"/path/to/QAUP_Management\""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 上传jar文件
|
||||||
|
scp "$JAR_FILE" "$PACK_SERVER_USER@$PACK_SERVER_HOST:$PACK_SERVER_PATH/qaup-admin/target/"
|
||||||
|
|
||||||
|
print_message $GREEN "✓ jar文件上传成功"
|
||||||
|
|
||||||
|
# 3. 在打包服务器上执行打包
|
||||||
|
print_message $BLUE "在打包服务器上执行打包..."
|
||||||
|
ssh "$PACK_SERVER_USER@$PACK_SERVER_HOST" "cd $PACK_SERVER_PATH && ./deploy/simple/package-server.sh"
|
||||||
|
|
||||||
|
print_message $GREEN "✅ 完整打包流程完成"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "下一步操作:"
|
||||||
|
echo "1. 从打包服务器下载生成的 qaup-deploy-*.tar.gz 文件"
|
||||||
|
echo "2. 将部署包传输到生产服务器进行部署"
|
||||||
110
deploy/build-update.sh
Executable file
110
deploy/build-update.sh
Executable file
@ -0,0 +1,110 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# QAUP 程序更新打包脚本
|
||||||
|
# 用于生成仅包含jar文件的更新包
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
|
# 颜色输出
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
print_message() {
|
||||||
|
echo -e "${1}${2}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_message $BLUE "=== QAUP 程序更新打包 ==="
|
||||||
|
|
||||||
|
# 1. 构建应用
|
||||||
|
print_message $BLUE "构建应用..."
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
mvn clean package -DskipTests -q
|
||||||
|
|
||||||
|
# 检查jar文件是否生成成功
|
||||||
|
JAR_FILE="$PROJECT_ROOT/qaup-admin/target/qaup-admin.jar"
|
||||||
|
if [ ! -f "$JAR_FILE" ]; then
|
||||||
|
print_message $RED "❌ jar文件构建失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. 创建更新包目录
|
||||||
|
UPDATE_DIR="qaup-update-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
mkdir -p "$UPDATE_DIR"
|
||||||
|
|
||||||
|
# 3. 复制jar文件
|
||||||
|
print_message $BLUE "准备更新文件..."
|
||||||
|
cp "$JAR_FILE" "$UPDATE_DIR/qaup-admin.jar"
|
||||||
|
|
||||||
|
# 4. 创建更新说明
|
||||||
|
cat > "$UPDATE_DIR/UPDATE-INSTRUCTIONS.md" << 'EOF'
|
||||||
|
# QAUP 程序更新包
|
||||||
|
|
||||||
|
## 包含内容
|
||||||
|
- `qaup-admin.jar` - 新版本应用程序
|
||||||
|
|
||||||
|
## 更新步骤
|
||||||
|
|
||||||
|
### 1. 上传文件到服务器
|
||||||
|
将 `qaup-admin.jar` 上传到服务器的 qaup-deploy 目录
|
||||||
|
|
||||||
|
### 2. 重命名文件
|
||||||
|
```bash
|
||||||
|
cd qaup-deploy
|
||||||
|
cp qaup-admin.jar new-app.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 执行更新
|
||||||
|
```bash
|
||||||
|
./update.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 验证更新
|
||||||
|
```bash
|
||||||
|
# 检查服务状态
|
||||||
|
docker compose ps
|
||||||
|
|
||||||
|
# 检查应用日志
|
||||||
|
docker compose logs -f qaup-app
|
||||||
|
|
||||||
|
# 访问系统确认功能正常
|
||||||
|
curl http://localhost:8080/actuator/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
- 更新前建议备份数据库
|
||||||
|
- 如果更新失败,脚本会自动回滚
|
||||||
|
- 如有问题,请查看完整的部署更新说明文档
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 5. 创建版本信息
|
||||||
|
cat > "$UPDATE_DIR/VERSION-INFO.txt" << EOF
|
||||||
|
QAUP 程序更新包
|
||||||
|
构建时间: $(date)
|
||||||
|
构建主机: $(hostname)
|
||||||
|
Git提交: $(git rev-parse --short HEAD 2>/dev/null || echo "未知")
|
||||||
|
Maven版本: $(mvn --version | head -1)
|
||||||
|
Java版本: $(java -version 2>&1 | head -1)
|
||||||
|
文件大小: $(du -sh "$JAR_FILE" | cut -f1)
|
||||||
|
文件MD5: $(md5sum "$JAR_FILE" | cut -d' ' -f1)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 6. 创建更新包
|
||||||
|
PACKAGE_NAME="${UPDATE_DIR}.tar.gz"
|
||||||
|
tar -czf "$PACKAGE_NAME" "$UPDATE_DIR"
|
||||||
|
|
||||||
|
# 7. 清理临时目录
|
||||||
|
rm -rf "$UPDATE_DIR"
|
||||||
|
|
||||||
|
print_message $GREEN "✅ 程序更新包创建完成: $PACKAGE_NAME"
|
||||||
|
print_message $BLUE "包大小: $(du -sh "$PACKAGE_NAME" | cut -f1)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "使用说明:"
|
||||||
|
echo "1. 将 $PACKAGE_NAME 发送给客户"
|
||||||
|
echo "2. 客户解压后按照 UPDATE-INSTRUCTIONS.md 操作"
|
||||||
|
echo "3. 或者直接发送 qaup-admin.jar 文件给客户"
|
||||||
@ -254,8 +254,8 @@ traffic:
|
|||||||
coordinate-system:
|
coordinate-system:
|
||||||
airport:
|
airport:
|
||||||
# 青岛机场坐标(客户部署时需要修改为实际机场坐标)
|
# 青岛机场坐标(客户部署时需要修改为实际机场坐标)
|
||||||
center-longitude: 120.08782536
|
center-longitude: 120.0834104
|
||||||
center-latitude: 36.36236547
|
center-latitude: 36.35406879
|
||||||
|
|
||||||
# 管理端点配置
|
# 管理端点配置
|
||||||
management:
|
management:
|
||||||
|
|||||||
71
deploy/create-init-sql.sh
Executable file
71
deploy/create-init-sql.sh
Executable file
@ -0,0 +1,71 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 创建简化的数据库初始化脚本
|
||||||
|
# 从现有的数据库架构文件生成init.sql
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||||
|
|
||||||
|
echo "=== 创建数据库初始化脚本 ==="
|
||||||
|
|
||||||
|
# 输出文件
|
||||||
|
INIT_SQL="$SCRIPT_DIR/init.sql"
|
||||||
|
|
||||||
|
# 创建初始化脚本
|
||||||
|
cat > "$INIT_SQL" << 'EOF'
|
||||||
|
-- QAUP 数据库初始化脚本
|
||||||
|
-- 简化版本:只创建必要的扩展和导入数据
|
||||||
|
|
||||||
|
-- 创建PostGIS扩展
|
||||||
|
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||||
|
CREATE EXTENSION IF NOT EXISTS postgis_topology;
|
||||||
|
|
||||||
|
-- 显示扩展信息
|
||||||
|
SELECT 'PostGIS扩展安装完成' as message;
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 如果存在数据库架构文件,追加到初始化脚本
|
||||||
|
SCHEMA_FILE="$PROJECT_ROOT/deploy/docker/postgres/qaup_database_schema.sql"
|
||||||
|
if [ -f "$SCHEMA_FILE" ]; then
|
||||||
|
echo "-- 导入数据库架构" >> "$INIT_SQL"
|
||||||
|
cat "$SCHEMA_FILE" >> "$INIT_SQL"
|
||||||
|
echo "✓ 已添加数据库架构"
|
||||||
|
else
|
||||||
|
echo "⚠ 未找到数据库架构文件: $SCHEMA_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加完整初始数据(优先使用完整数据文件)
|
||||||
|
COMPLETE_DATA_FILE="$PROJECT_ROOT/deploy/docker/postgres/export/initial_data_complete.sql"
|
||||||
|
if [ -f "$COMPLETE_DATA_FILE" ]; then
|
||||||
|
echo "" >> "$INIT_SQL"
|
||||||
|
echo "-- 导入完整初始数据" >> "$INIT_SQL"
|
||||||
|
cat "$COMPLETE_DATA_FILE" >> "$INIT_SQL"
|
||||||
|
echo "✓ 已添加完整初始数据文件"
|
||||||
|
else
|
||||||
|
echo "⚠ 未找到完整初始数据文件: $COMPLETE_DATA_FILE"
|
||||||
|
echo "-- 注意:数据库将只包含基础结构,无初始数据" >> "$INIT_SQL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加完成信息
|
||||||
|
cat >> "$INIT_SQL" << 'EOF'
|
||||||
|
|
||||||
|
-- 显示数据库信息
|
||||||
|
SELECT
|
||||||
|
current_database() as database_name,
|
||||||
|
current_user as current_user,
|
||||||
|
version() as postgresql_version;
|
||||||
|
|
||||||
|
-- 显示表数量
|
||||||
|
SELECT
|
||||||
|
schemaname,
|
||||||
|
COUNT(*) as table_count
|
||||||
|
FROM pg_tables
|
||||||
|
WHERE schemaname = 'public'
|
||||||
|
GROUP BY schemaname;
|
||||||
|
|
||||||
|
SELECT 'QAUP数据库初始化完成' as message;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✅ 初始化脚本创建完成: $INIT_SQL"
|
||||||
|
echo "文件大小: $(du -sh "$INIT_SQL" | cut -f1)"
|
||||||
@ -1,262 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# QAUP 一键部署脚本
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# 颜色输出
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
print_message() {
|
|
||||||
echo -e "${1}${2}${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_message $BLUE "=== QAUP 一键部署 ==="
|
|
||||||
|
|
||||||
# 环境检查
|
|
||||||
print_message $BLUE "1. 检查部署环境..."
|
|
||||||
|
|
||||||
# 检查操作系统
|
|
||||||
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
|
|
||||||
print_message $YELLOW "⚠️ 建议在Linux环境中运行"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查必要文件
|
|
||||||
REQUIRED_FILES=("app.jar" "docker-compose.yml" "config.yml" "images.tar.gz")
|
|
||||||
for file in "${REQUIRED_FILES[@]}"; do
|
|
||||||
if [ ! -f "$file" ]; then
|
|
||||||
print_message $RED "❌ 缺失必要文件: $file"
|
|
||||||
print_message $BLUE "请确保所有文件都存在,或重新解压部署包"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
print_message $GREEN "✓ 所有必要文件检查通过"
|
|
||||||
|
|
||||||
# 检查Docker
|
|
||||||
if ! command -v docker &> /dev/null; then
|
|
||||||
print_message $RED "❌ Docker 未安装"
|
|
||||||
print_message $BLUE "请安装 Docker: sudo apt install docker.io"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查Docker服务
|
|
||||||
if ! sudo systemctl is-active --quiet docker; then
|
|
||||||
print_message $YELLOW "⚠️ Docker 服务未启动,正在启动..."
|
|
||||||
sudo systemctl start docker
|
|
||||||
sleep 5
|
|
||||||
fi
|
|
||||||
|
|
||||||
DOCKER_VERSION=$(docker --version)
|
|
||||||
print_message $GREEN "✓ Docker 版本: $DOCKER_VERSION"
|
|
||||||
|
|
||||||
# 检查Docker Compose
|
|
||||||
if ! docker compose version &> /dev/null && ! docker-compose version &> /dev/null; then
|
|
||||||
print_message $RED "❌ Docker Compose 未安装"
|
|
||||||
print_message $BLUE "请安装 Docker Compose: sudo apt install docker-compose"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
print_message $GREEN "✓ Docker Compose 检查通过"
|
|
||||||
|
|
||||||
# 检查磁盘空间(至少需要3GB)
|
|
||||||
AVAILABLE_SPACE=$(df . | tail -1 | awk '{print $4}')
|
|
||||||
REQUIRED_SPACE=$((3 * 1024 * 1024)) # 3GB in KB
|
|
||||||
if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_SPACE" ]; then
|
|
||||||
print_message $RED "❌ 磁盘空间不足"
|
|
||||||
print_message $BLUE "可用空间: $(($AVAILABLE_SPACE / 1024 / 1024))GB, 需要: 3GB"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
print_message $GREEN "✓ 磁盘空间充足: $(($AVAILABLE_SPACE / 1024 / 1024))GB"
|
|
||||||
|
|
||||||
# 检查端口占用
|
|
||||||
PORTS=(8080 5432 6379)
|
|
||||||
print_message $BLUE "2. 检查端口占用..."
|
|
||||||
for port in "${PORTS[@]}"; do
|
|
||||||
if netstat -tuln 2>/dev/null | grep -q ":$port " || ss -tuln 2>/dev/null | grep -q ":$port "; then
|
|
||||||
print_message $YELLOW "⚠️ 端口 $port 已被占用"
|
|
||||||
print_message $BLUE "请停止占用该端口的服务或修改端口配置"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
print_message $GREEN "✓ 端口检查完成"
|
|
||||||
|
|
||||||
# 检查Docker镜像
|
|
||||||
print_message $BLUE "3. 检查Docker镜像..."
|
|
||||||
if [ -f "images.tar.gz" ]; then
|
|
||||||
print_message $BLUE "载入预构建镜像..."
|
|
||||||
if ! docker load -i images.tar.gz; then
|
|
||||||
print_message $RED "❌ 镜像载入失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
print_message $GREEN "✓ 镜像载入成功"
|
|
||||||
else
|
|
||||||
print_message $YELLOW "⚠️ 未找到镜像包,将尝试在线拉取"
|
|
||||||
|
|
||||||
# 尝试拉取镜像
|
|
||||||
IMAGES=("m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine" \
|
|
||||||
"m.daocloud.io/docker.io/library/redis:8.0-alpine" \
|
|
||||||
"m.daocloud.io/docker.io/library/eclipse-temurin:21-jre")
|
|
||||||
|
|
||||||
for image in "${IMAGES[@]}"; do
|
|
||||||
if ! docker pull --platform linux/amd64 "$image"; then
|
|
||||||
print_message $RED "❌ 镜像拉取失败: $image"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
print_message $GREEN "✓ 所有镜像拉取成功"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 停止可能存在的冲突容器
|
|
||||||
print_message $BLUE "4. 清理冲突容器..."
|
|
||||||
docker compose down 2>/dev/null || true
|
|
||||||
docker rm -f $(docker ps -aq --filter name=qaup) 2>/dev/null || true
|
|
||||||
print_message $GREEN "✓ 冲突容器清理完成"
|
|
||||||
|
|
||||||
# 创建数据目录
|
|
||||||
print_message $BLUE "5. 创建数据目录..."
|
|
||||||
mkdir -p data/postgres data/redis logs backup
|
|
||||||
|
|
||||||
# 设置目录权限
|
|
||||||
chmod 755 data logs backup
|
|
||||||
chmod 700 data/postgres data/redis
|
|
||||||
print_message $GREEN "✓ 数据目录创建完成"
|
|
||||||
|
|
||||||
# 验证配置文件
|
|
||||||
print_message $BLUE "6. 验证配置文件..."
|
|
||||||
|
|
||||||
# 检查docker-compose.yml语法
|
|
||||||
if ! docker compose config -q; then
|
|
||||||
print_message $RED "❌ docker-compose.yml 配置语法错误"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
print_message $GREEN "✓ docker-compose.yml 语法正确"
|
|
||||||
|
|
||||||
# 检查应用配置文件
|
|
||||||
if ! grep -q "qaup:" config.yml; then
|
|
||||||
print_message $RED "❌ config.yml 配置不完整"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
print_message $GREEN "✓ 应用配置文件正常"
|
|
||||||
|
|
||||||
# 启动基础设施服务
|
|
||||||
print_message $BLUE "7. 启动基础设施服务..."
|
|
||||||
docker compose up -d qaup-postgres qaup-redis
|
|
||||||
|
|
||||||
# 等待数据库启动
|
|
||||||
print_message $BLUE "等待数据库启动(30秒)..."
|
|
||||||
sleep 30
|
|
||||||
|
|
||||||
# 检查数据库状态
|
|
||||||
if docker exec qaup-postgres pg_isready -U qaup > /dev/null 2>&1; then
|
|
||||||
print_message $GREEN "✓ PostgreSQL 数据库启动成功"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ PostgreSQL 启动失败"
|
|
||||||
print_message $BLUE "数据库日志:"
|
|
||||||
docker compose logs qaup-postgres
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查Redis状态
|
|
||||||
if docker exec qaup-redis redis-cli ping > /dev/null 2>&1; then
|
|
||||||
print_message $GREEN "✓ Redis 缓存服务启动成功"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ Redis 启动失败"
|
|
||||||
print_message $BLUE "Redis日志:"
|
|
||||||
docker compose logs qaup-redis
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 启动应用服务
|
|
||||||
print_message $BLUE "8. 启动应用服务..."
|
|
||||||
docker compose up -d qaup-app
|
|
||||||
|
|
||||||
# 等待应用启动和数据库迁移
|
|
||||||
print_message $BLUE "9. 等待应用启动和数据库迁移..."
|
|
||||||
print_message $BLUE " 这可能需要2-3分钟,请耐心等待..."
|
|
||||||
|
|
||||||
WAIT_TIME=0
|
|
||||||
HEALTH_URL="http://localhost:8080/actuator/health"
|
|
||||||
|
|
||||||
while [ $WAIT_TIME -lt 180 ]; do
|
|
||||||
sleep 10
|
|
||||||
WAIT_TIME=$((WAIT_TIME + 10))
|
|
||||||
|
|
||||||
# 检查应用健康状态
|
|
||||||
if curl -f -s "$HEALTH_URL" > /dev/null 2>&1; then
|
|
||||||
print_message $GREEN "✓ 应用启动成功!"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 显示等待进度
|
|
||||||
if [ $((WAIT_TIME % 30)) -eq 0 ]; then
|
|
||||||
print_message $BLUE " 已等待 ${WAIT_TIME} 秒..."
|
|
||||||
|
|
||||||
# 检查是否有迁移相关的日志
|
|
||||||
MIGRATION_LOGS=$(docker compose logs qaup-app 2>/dev/null | grep -i "flyway\|migration" | tail -3)
|
|
||||||
if [ -n "$MIGRATION_LOGS" ]; then
|
|
||||||
print_message $BLUE " 迁移进度:"
|
|
||||||
echo "$MIGRATION_LOGS" | while read line; do
|
|
||||||
print_message $BLUE " $line"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# 最终验证
|
|
||||||
print_message $BLUE "10. 最终验证..."
|
|
||||||
|
|
||||||
if curl -f -s "$HEALTH_URL" > /dev/null 2>&1; then
|
|
||||||
# 获取服务状态
|
|
||||||
APP_STATUS=$(docker compose ps qaup-app --format json | jq -r '.[0].State' 2>/dev/null || echo "unknown")
|
|
||||||
DB_STATUS=$(docker compose ps qaup-postgres --format json | jq -r '.[0].State' 2>/dev/null || echo "unknown")
|
|
||||||
REDIS_STATUS=$(docker compose ps qaup-redis --format json | jq -r '.[0].State' 2>/dev/null || echo "unknown")
|
|
||||||
|
|
||||||
print_message $GREEN "🎉 部署成功完成!"
|
|
||||||
echo ""
|
|
||||||
print_message $GREEN "📊 服务状态:"
|
|
||||||
print_message $BLUE " 应用服务: $APP_STATUS"
|
|
||||||
print_message $BLUE " 数据库: $DB_STATUS"
|
|
||||||
print_message $BLUE " 缓存服务: $REDIS_STATUS"
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "🌐 访问信息:"
|
|
||||||
print_message $BLUE " Web管理: http://localhost:8080"
|
|
||||||
print_message $BLUE " 健康检查: $HEALTH_URL"
|
|
||||||
print_message $BLUE " 数据库: localhost:5432 (qaup/qaup123)"
|
|
||||||
print_message $BLUE " Redis: localhost:6379"
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "👤 初始登录:"
|
|
||||||
print_message $BLUE " 用户名: admin"
|
|
||||||
print_message $BLUE " 密码: admin123"
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "📋 管理命令:"
|
|
||||||
print_message $BLUE " 查看状态: docker compose ps"
|
|
||||||
print_message $BLUE " 查看日志: docker compose logs -f qaup-app"
|
|
||||||
print_message $BLUE " 查看迁移: docker exec qaup-postgres psql -U qaup -d qaup -c \"SELECT version,description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 5;\""
|
|
||||||
print_message $BLUE " 停止服务: docker compose down"
|
|
||||||
print_message $BLUE " 重启应用: docker compose restart qaup-app"
|
|
||||||
print_message $BLUE " 升级应用: ./deploy-update.sh"
|
|
||||||
echo ""
|
|
||||||
print_message $GREEN "🚀 QAUP 系统已就绪!"
|
|
||||||
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 应用启动失败"
|
|
||||||
print_message $BLUE "请检查以下信息:"
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "📋 应用日志:"
|
|
||||||
docker compose logs --tail=50 qaup-app
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "📋 数据库日志:"
|
|
||||||
docker compose logs --tail=20 qaup-postgres
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "📋 容器状态:"
|
|
||||||
docker compose ps
|
|
||||||
echo ""
|
|
||||||
print_message $YELLOW "💡 可能的解决方案:"
|
|
||||||
print_message $BLUE "1. 检查端口是否被占用: netstat -tuln | grep ':8080'"
|
|
||||||
print_message $BLUE "2. 检查磁盘空间: df -h"
|
|
||||||
print_message $BLUE "3. 重启Docker服务: sudo systemctl restart docker"
|
|
||||||
print_message $BLUE "4. 清理Docker资源: docker system prune -a"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@ -1,238 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# QAUP 一键升级脚本
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# 颜色输出
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
print_message() {
|
|
||||||
echo -e "${1}${2}${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_message $BLUE "=== QAUP 一键升级 ==="
|
|
||||||
|
|
||||||
# 检查必要文件
|
|
||||||
REQUIRED_FILES=("app.jar" "docker-compose.yml" "config.yml")
|
|
||||||
for file in "${REQUIRED_FILES[@]}"; do
|
|
||||||
if [ ! -f "$file" ]; then
|
|
||||||
print_message $RED "❌ 缺失必要文件: $file"
|
|
||||||
print_message $BLUE "请确保在正确的部署目录中运行此脚本"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# 1. 环境检查和准备
|
|
||||||
print_message $BLUE "1. 环境检查..."
|
|
||||||
|
|
||||||
# 检查Docker服务状态
|
|
||||||
if ! docker compose ps > /dev/null 2>&1; then
|
|
||||||
print_message $RED "❌ 无法连接到Docker服务"
|
|
||||||
print_message $BLUE "请确保Docker服务正在运行: sudo systemctl status docker"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查应用服务状态
|
|
||||||
APP_STATUS=$(docker compose ps qaup-app --format json 2>/dev/null | jq -r '.[0].State' 2>/dev/null || echo "exited")
|
|
||||||
if [ "$APP_STATUS" != "running" ]; then
|
|
||||||
print_message $YELLOW "⚠️ 应用服务当前状态: $APP_STATUS"
|
|
||||||
print_message $BLUE "正在启动应用服务..."
|
|
||||||
docker compose up -d qaup-app
|
|
||||||
sleep 30
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 获取当前应用版本信息
|
|
||||||
print_message $BLUE "2. 检查应用版本..."
|
|
||||||
if [ -f "new-app.jar" ]; then
|
|
||||||
NEW_JAR_SIZE=$(stat -f%z new-app.jar 2>/dev/null || stat -c%s new-app.jar 2>/dev/null || echo "unknown")
|
|
||||||
CURRENT_JAR_SIZE=$(stat -f%z app.jar 2>/dev/null || stat -c%s app.jar 2>/dev/null || echo "unknown")
|
|
||||||
print_message $BLUE " 当前版本大小: $CURRENT_JAR_SIZE bytes"
|
|
||||||
print_message $BLUE " 新版本大小: $NEW_JAR_SIZE bytes"
|
|
||||||
|
|
||||||
if [ "$NEW_JAR_SIZE" = "$CURRENT_JAR_SIZE" ]; then
|
|
||||||
print_message $YELLOW "⚠️ 新旧版本大小相同,请确认版本是否正确"
|
|
||||||
read -p "是否继续升级?(y/N): " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
print_message $BLUE "升级已取消"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 未找到新版本文件: new-app.jar"
|
|
||||||
print_message $BLUE "请先将新版本文件重命名为 new-app.jar"
|
|
||||||
print_message $BLUE " 例如: cp qaup-admin-1.0.2.jar new-app.jar"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 3. 数据库备份
|
|
||||||
print_message $BLUE "3. 备份数据库..."
|
|
||||||
BACKUP_DIR="backup"
|
|
||||||
mkdir -p "$BACKUP_DIR"
|
|
||||||
|
|
||||||
BACKUP_TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
||||||
BACKUP_FILE="$BACKUP_DIR/qaup_db_backup_$BACKUP_TIMESTAMP.sql"
|
|
||||||
|
|
||||||
if docker exec qaup-postgres pg_dump -U qaup qaup > "$BACKUP_FILE" 2>/dev/null; then
|
|
||||||
BACKUP_SIZE=$(stat -f%z "$BACKUP_FILE" 2>/dev/null || stat -c%s "$BACKUP_FILE" 2>/dev/null || echo "unknown")
|
|
||||||
print_message $GREEN "✓ 数据库备份成功: $BACKUP_FILE ($BACKUP_SIZE bytes)"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 数据库备份失败"
|
|
||||||
print_message $BLUE "升级已取消,请检查数据库连接"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 4. 备份当前应用
|
|
||||||
print_message $BLUE "4. 备份当前应用..."
|
|
||||||
BACKUP_JAR="$BACKUP_DIR/app.jar.backup.$BACKUP_TIMESTAMP"
|
|
||||||
cp app.jar "$BACKUP_JAR"
|
|
||||||
print_message $GREEN "✓ 应用备份成功: $BACKUP_JAR"
|
|
||||||
|
|
||||||
# 5. 获取升级前数据库迁移状态
|
|
||||||
print_message $BLUE "5. 记录升级前迁移状态..."
|
|
||||||
BEFORE_MIGRATION=$(docker exec qaup-postgres psql -U qaup -d qaup -t -c "SELECT version,description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 5;" 2>/dev/null || echo "无法获取迁移状态")
|
|
||||||
print_message $BLUE "升级前迁移状态已记录"
|
|
||||||
|
|
||||||
# 6. 停止应用服务
|
|
||||||
print_message $BLUE "6. 停止应用服务..."
|
|
||||||
docker compose stop qaup-app
|
|
||||||
sleep 10
|
|
||||||
print_message $GREEN "✓ 应用服务已停止"
|
|
||||||
|
|
||||||
# 7. 替换应用文件
|
|
||||||
print_message $BLUE "7. 更新应用文件..."
|
|
||||||
if cp new-app.jar app.jar; then
|
|
||||||
print_message $GREEN "✓ 应用文件更新成功"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 应用文件更新失败"
|
|
||||||
print_message $BLUE "正在恢复备份..."
|
|
||||||
cp "$BACKUP_JAR" app.jar
|
|
||||||
docker compose start qaup-app
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 8. 启动应用服务
|
|
||||||
print_message $BLUE "8. 启动应用服务..."
|
|
||||||
docker compose up -d qaup-app
|
|
||||||
sleep 15
|
|
||||||
|
|
||||||
# 9. 监控应用启动和数据库迁移
|
|
||||||
print_message $BLUE "9. 监控应用启动和数据库迁移..."
|
|
||||||
print_message $BLUE " 这可能需要2-3分钟,请耐心等待..."
|
|
||||||
|
|
||||||
WAIT_TIME=0
|
|
||||||
HEALTH_URL="http://localhost:8080/actuator/health"
|
|
||||||
|
|
||||||
while [ $WAIT_TIME -lt 180 ]; do
|
|
||||||
sleep 10
|
|
||||||
WAIT_TIME=$((WAIT_TIME + 10))
|
|
||||||
|
|
||||||
# 检查应用健康状态
|
|
||||||
if curl -f -s "$HEALTH_URL" > /dev/null 2>&1; then
|
|
||||||
print_message $GREEN "✓ 应用启动成功!"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 显示等待进度
|
|
||||||
if [ $((WAIT_TIME % 30)) -eq 0 ]; then
|
|
||||||
print_message $BLUE " 已等待 ${WAIT_TIME} 秒..."
|
|
||||||
|
|
||||||
# 检查应用日志中的迁移信息
|
|
||||||
MIGRATION_LOGS=$(docker compose logs qaup-app --tail=10 2>/dev/null | grep -E "(Flyway|migration|Migration)" | tail -2)
|
|
||||||
if [ -n "$MIGRATION_LOGS" ]; then
|
|
||||||
print_message $BLUE " 迁移进度:"
|
|
||||||
echo "$MIGRATION_LOGS" | while read line; do
|
|
||||||
print_message $BLUE " $line"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查是否有错误
|
|
||||||
ERROR_LOGS=$(docker compose logs qaup-app --tail=5 2>/dev/null | grep -i -E "(error|exception|fail)" || true)
|
|
||||||
if [ -n "$ERROR_LOGS" ]; then
|
|
||||||
print_message $YELLOW " 检测到可能的错误:"
|
|
||||||
echo "$ERROR_LOGS" | while read line; do
|
|
||||||
print_message $YELLOW " $line"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# 10. 验证升级结果
|
|
||||||
print_message $BLUE "10. 验证升级结果..."
|
|
||||||
|
|
||||||
if curl -f -s "$HEALTH_URL" > /dev/null 2>&1; then
|
|
||||||
# 获取升级后数据库迁移状态
|
|
||||||
print_message $BLUE " 检查数据库迁移状态..."
|
|
||||||
AFTER_MIGRATION=$(docker exec qaup-postgres psql -U qaup -d qaup -t -c "SELECT version,description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 3;" 2>/dev/null || echo "无法获取迁移状态")
|
|
||||||
|
|
||||||
print_message $GREEN "🎉 升级成功完成!"
|
|
||||||
echo ""
|
|
||||||
print_message $GREEN "📊 升级信息:"
|
|
||||||
print_message $BLUE " 应用状态: 运行中"
|
|
||||||
print_message $BLUE " 健康检查: 通过"
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "💾 备份信息:"
|
|
||||||
print_message $BLUE " 应用备份: $BACKUP_JAR"
|
|
||||||
print_message $BLUE " 数据库备份: $BACKUP_FILE"
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "📋 升级前迁移状态:"
|
|
||||||
echo "$BEFORE_MIGRATION" | while read line; do
|
|
||||||
print_message $BLUE " $line"
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "📋 升级后迁移状态:"
|
|
||||||
echo "$AFTER_MIGRATION" | while read line; do
|
|
||||||
print_message $BLUE " $line"
|
|
||||||
done
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "🔍 验证命令:"
|
|
||||||
print_message $BLUE " 查看应用日志: docker compose logs -f qaup-app"
|
|
||||||
print_message $BLUE " 检查数据库连接: docker exec qaup-postgres psql -U qaup -d qaup -c 'SELECT version();'"
|
|
||||||
print_message $BLUE " 回滚命令: ./rollback.sh $BACKUP_TIMESTAMP"
|
|
||||||
|
|
||||||
# 创建回滚脚本
|
|
||||||
cat > rollback_$BACKUP_TIMESTAMP.sh << EOF
|
|
||||||
#!/bin/bash
|
|
||||||
echo "正在回滚到版本 $BACKUP_TIMESTAMP..."
|
|
||||||
docker compose stop qaup-app
|
|
||||||
cp backup/app.jar.backup.$BACKUP_TIMESTAMP app.jar
|
|
||||||
docker compose start qaup-app
|
|
||||||
echo "回滚完成,请检查应用状态"
|
|
||||||
EOF
|
|
||||||
chmod +x rollback_$BACKUP_TIMESTAMP.sh
|
|
||||||
print_message $BLUE " 自动回滚脚本: rollback_$BACKUP_TIMESTAMP.sh"
|
|
||||||
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 升级失败"
|
|
||||||
print_message $BLUE "正在执行自动回滚..."
|
|
||||||
|
|
||||||
# 自动回滚
|
|
||||||
docker compose stop qaup-app
|
|
||||||
cp "$BACKUP_JAR" app.jar
|
|
||||||
docker compose start qaup-app
|
|
||||||
|
|
||||||
sleep 30
|
|
||||||
|
|
||||||
# 验证回滚
|
|
||||||
if curl -f -s "$HEALTH_URL" > /dev/null 2>&1; then
|
|
||||||
print_message $GREEN "✓ 自动回滚成功"
|
|
||||||
print_message $BLUE " 应用已恢复到升级前版本"
|
|
||||||
print_message $BLUE " 请检查应用日志: docker compose logs qaup-app"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 回滚失败"
|
|
||||||
print_message $BLUE " 请手动检查并恢复服务"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "📋 详细信息:"
|
|
||||||
print_message $BLUE " 应用备份: $BACKUP_JAR"
|
|
||||||
print_message $BLUE " 数据库备份: $BACKUP_FILE"
|
|
||||||
print_message $BLUE " 升级前状态: $APP_STATUS"
|
|
||||||
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
47
deploy/deploy.sh
Executable file
47
deploy/deploy.sh
Executable file
@ -0,0 +1,47 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# QAUP 一键部署脚本
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== QAUP 一键部署 ==="
|
||||||
|
|
||||||
|
# 检查Docker
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
echo "❌ Docker 未安装"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 载入镜像
|
||||||
|
echo "载入Docker镜像..."
|
||||||
|
docker load -i images.tar.gz
|
||||||
|
|
||||||
|
# 创建数据目录
|
||||||
|
echo "创建数据目录..."
|
||||||
|
mkdir -p data/postgres data/redis logs
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
echo "启动服务..."
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# 等待服务启动
|
||||||
|
echo "等待服务启动(60秒)..."
|
||||||
|
sleep 60
|
||||||
|
|
||||||
|
# 检查服务状态
|
||||||
|
if curl -f -s http://localhost:8080/actuator/health > /dev/null 2>&1; then
|
||||||
|
echo "✅ 部署成功!"
|
||||||
|
echo ""
|
||||||
|
echo "访问地址: http://localhost:8080"
|
||||||
|
echo "数据库: localhost:5432 (qaup/qaup123)"
|
||||||
|
echo "Redis: localhost:6379"
|
||||||
|
echo ""
|
||||||
|
echo "管理命令:"
|
||||||
|
echo " 查看状态: docker compose ps"
|
||||||
|
echo " 查看日志: docker compose logs -f qaup-app"
|
||||||
|
echo " 停止服务: docker compose down"
|
||||||
|
echo " 升级应用: ./update.sh"
|
||||||
|
else
|
||||||
|
echo "❌ 服务启动失败,请检查日志:"
|
||||||
|
docker compose logs
|
||||||
|
fi
|
||||||
@ -1,381 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# QAUP 服务器端打包脚本
|
|
||||||
# 在Ubuntu打包服务器上运行,使用已构建的jar文件进行打包
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# 颜色输出
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
print_message() {
|
|
||||||
echo -e "${1}${2}${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_message $BLUE "=== QAUP 服务器端打包脚本 ==="
|
|
||||||
|
|
||||||
# 检查基础环境
|
|
||||||
print_message $BLUE "1. 检查基础环境..."
|
|
||||||
|
|
||||||
# 检查操作系统
|
|
||||||
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
|
|
||||||
print_message $YELLOW "⚠️ 警告: 当前操作系统为 $OSTYPE,建议在Linux上运行"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查Java 21
|
|
||||||
if ! command -v java &> /dev/null; then
|
|
||||||
print_message $RED "❌ Java 未安装"
|
|
||||||
print_message $BLUE "请安装 Java 21: sudo apt install openjdk-21-jdk"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
JAVA_VERSION=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
|
|
||||||
REQUIRED_VERSION="21"
|
|
||||||
if [[ "$(printf '%s\n' "$REQUIRED_VERSION" "$JAVA_VERSION" | sort -V | head -n1)" != "$REQUIRED_VERSION" ]]; then
|
|
||||||
print_message $RED "❌ Java 版本不兼容"
|
|
||||||
print_message $BLUE "当前版本: $JAVA_VERSION, 需要: $REQUIRED_VERSION+"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
print_message $GREEN "✓ Java 版本检查通过: $JAVA_VERSION"
|
|
||||||
|
|
||||||
# 检查Maven
|
|
||||||
if ! command -v mvn &> /dev/null; then
|
|
||||||
print_message $RED "❌ Maven 未安装"
|
|
||||||
print_message $BLUE "请安装 Maven: sudo apt install maven"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
print_message $GREEN "✓ Maven 版本: $(mvn -version | head -1)"
|
|
||||||
|
|
||||||
# 检查Docker和Docker Compose
|
|
||||||
if ! command -v docker &> /dev/null; then
|
|
||||||
print_message $RED "❌ Docker 未安装"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
DOCKER_VERSION=$(docker --version)
|
|
||||||
print_message $GREEN "✓ Docker 版本: $DOCKER_VERSION"
|
|
||||||
|
|
||||||
if ! docker compose version &> /dev/null && ! docker-compose version &> /dev/null; then
|
|
||||||
print_message $RED "❌ Docker Compose 未安装"
|
|
||||||
print_message $BLUE "请安装 Docker Compose: sudo apt install docker-compose"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
print_message $GREEN "✓ Docker Compose 检查通过"
|
|
||||||
|
|
||||||
# 检查磁盘空间(至少需要5GB)
|
|
||||||
AVAILABLE_SPACE=$(df . | tail -1 | awk '{print $4}')
|
|
||||||
REQUIRED_SPACE=$((5 * 1024 * 1024)) # 5GB in KB
|
|
||||||
if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_SPACE" ]; then
|
|
||||||
print_message $RED "❌ 磁盘空间不足"
|
|
||||||
print_message $BLUE "可用空间: $(($AVAILABLE_SPACE / 1024 / 1024))GB, 需要: 5GB"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
print_message $GREEN "✓ 磁盘空间充足: $(($AVAILABLE_SPACE / 1024 / 1024))GB"
|
|
||||||
|
|
||||||
# 检查jar文件
|
|
||||||
print_message $BLUE "2. 检查jar文件..."
|
|
||||||
JAR_FILE="qaup-admin/target/qaup-admin.jar"
|
|
||||||
if [ ! -f "$JAR_FILE" ]; then
|
|
||||||
print_message $YELLOW "未找到jar文件,尝试构建..."
|
|
||||||
|
|
||||||
# 尝试构建项目
|
|
||||||
if [ -f "pom.xml" ]; then
|
|
||||||
print_message $BLUE "执行 Maven 构建..."
|
|
||||||
mvn clean package -DskipTests
|
|
||||||
|
|
||||||
if [ ! -f "$JAR_FILE" ]; then
|
|
||||||
print_message $RED "❌ 构建失败: $JAR_FILE 仍然不存在"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 未找到jar文件: $JAR_FILE"
|
|
||||||
print_message $BLUE "请先在macOS上构建jar文件并上传到服务器"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_message $GREEN "✓ 找到jar文件: $JAR_FILE"
|
|
||||||
print_message $BLUE " 文件大小: $(du -sh $JAR_FILE | cut -f1)"
|
|
||||||
|
|
||||||
# 验证jar文件是否为有效的Java应用
|
|
||||||
if ! jar -tf "$JAR_FILE" > /dev/null 2>&1; then
|
|
||||||
print_message $RED "❌ jar文件损坏或不是有效的JAR文件"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
print_message $GREEN "✓ jar文件验证通过"
|
|
||||||
|
|
||||||
# 准备镜像版本
|
|
||||||
print_message $BLUE "3. 准备Docker镜像..."
|
|
||||||
POSTGRES_IMAGE="m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine"
|
|
||||||
REDIS_IMAGE="m.daocloud.io/docker.io/library/redis:8.0-alpine"
|
|
||||||
OPENJDK_IMAGE="m.daocloud.io/docker.io/library/eclipse-temurin:21-jre"
|
|
||||||
|
|
||||||
# 检查网络连接
|
|
||||||
if ! ping -c 1 m.daocloud.io &> /dev/null; then
|
|
||||||
print_message $YELLOW "⚠️ 网络连接测试失败,可能影响镜像拉取"
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_message $BLUE "拉取基础镜像..."
|
|
||||||
print_message $BLUE " - PostgreSQL + PostGIS"
|
|
||||||
if ! docker pull --platform linux/amd64 $POSTGRES_IMAGE; then
|
|
||||||
print_message $RED "❌ PostgreSQL镜像拉取失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_message $BLUE " - Redis"
|
|
||||||
if ! docker pull --platform linux/amd64 $REDIS_IMAGE; then
|
|
||||||
print_message $RED "❌ Redis镜像拉取失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_message $BLUE " - Java 21 Runtime"
|
|
||||||
if ! docker pull --platform linux/amd64 $OPENJDK_IMAGE; then
|
|
||||||
print_message $RED "❌ Java镜像拉取失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 导出镜像
|
|
||||||
print_message $BLUE "4. 导出Docker镜像..."
|
|
||||||
mkdir -p qaup-deploy
|
|
||||||
|
|
||||||
# 检查磁盘空间(镜像包需要额外空间)
|
|
||||||
EXPECTED_IMAGE_SIZE=$((2 * 1024)) # 预计2GB
|
|
||||||
if [ "$AVAILABLE_SPACE" -lt "$((EXPECTED_IMAGE_SIZE * 1024))" ]; then
|
|
||||||
print_message $YELLOW "⚠️ 磁盘空间可能不足,预计需要额外2GB空间"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! docker save $POSTGRES_IMAGE $REDIS_IMAGE $OPENJDK_IMAGE | gzip > qaup-deploy/images.tar.gz; then
|
|
||||||
print_message $RED "❌ 镜像导出失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
IMAGE_SIZE=$(du -sh qaup-deploy/images.tar.gz | cut -f1)
|
|
||||||
print_message $GREEN "✓ 镜像包大小: $IMAGE_SIZE"
|
|
||||||
|
|
||||||
# 复制必要文件
|
|
||||||
print_message $BLUE "5. 准备部署文件..."
|
|
||||||
|
|
||||||
# 复制核心应用文件
|
|
||||||
cp "$JAR_FILE" qaup-deploy/app.jar
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
print_message $GREEN "✓ 应用JAR文件已复制"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 应用JAR文件复制失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 复制Docker配置
|
|
||||||
cp deploy/docker-compose.yml qaup-deploy/ || {
|
|
||||||
print_message $RED "❌ docker-compose.yml 复制失败"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
print_message $GREEN "✓ Docker编排配置已复制"
|
|
||||||
|
|
||||||
# 复制应用配置
|
|
||||||
cp deploy/config.yml qaup-deploy/ || {
|
|
||||||
print_message $RED "❌ config.yml 复制失败"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
print_message $GREEN "✓ 应用配置文件已复制"
|
|
||||||
|
|
||||||
# 复制部署脚本
|
|
||||||
cp deploy/deploy-all.sh qaup-deploy/ || {
|
|
||||||
print_message $RED "❌ deploy-all.sh 复制失败"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
chmod +x qaup-deploy/deploy-all.sh
|
|
||||||
|
|
||||||
cp deploy/deploy-update.sh qaup-deploy/ || {
|
|
||||||
print_message $RED "❌ deploy-update.sh 复制失败"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
chmod +x qaup-deploy/deploy-update.sh
|
|
||||||
|
|
||||||
cp deploy/qaup-service.sh qaup-deploy/ || {
|
|
||||||
print_message $RED "❌ qaup-service.sh 复制失败"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
chmod +x qaup-deploy/qaup-service.sh
|
|
||||||
print_message $GREEN "✓ 部署脚本已复制并设置执行权限"
|
|
||||||
|
|
||||||
# 复制数据库相关文件(可选)
|
|
||||||
if [ -f "deploy/qaup_database_export.sql" ]; then
|
|
||||||
cp deploy/qaup_database_export.sql qaup-deploy/qaup_database_export.sql
|
|
||||||
print_message $GREEN "✓ 数据库导出文件已复制"
|
|
||||||
else
|
|
||||||
print_message $YELLOW "⚠️ 数据库导出文件不存在"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 复制文档文件
|
|
||||||
if [ -f "deploy/DeployGuide.md" ]; then
|
|
||||||
cp deploy/DeployGuide.md qaup-deploy/
|
|
||||||
print_message $GREEN "✓ 部署指南已复制"
|
|
||||||
else
|
|
||||||
print_message $YELLOW "⚠️ 部署指南不存在"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 创建必需目录
|
|
||||||
mkdir -p qaup-deploy/{backup,logs,data/postgres,data/redis}
|
|
||||||
print_message $GREEN "✓ 目录结构已创建"
|
|
||||||
|
|
||||||
# 创建README文件
|
|
||||||
cat > qaup-deploy/README.md << 'EOF'
|
|
||||||
# QAUP 部署包
|
|
||||||
|
|
||||||
## 目录结构
|
|
||||||
```
|
|
||||||
qaup-deploy/
|
|
||||||
├── app.jar # 应用JAR文件
|
|
||||||
├── docker-compose.yml # Docker编排配置
|
|
||||||
├── config.yml # 应用配置文件
|
|
||||||
├── deploy-all.sh # 一键部署脚本
|
|
||||||
├── deploy-update.sh # 一键升级脚本
|
|
||||||
├── qaup-service.sh # 统一服务管理脚本
|
|
||||||
├── images.tar.gz # Docker镜像包
|
|
||||||
├── README.md # 本文件
|
|
||||||
├── backup/ # 备份目录
|
|
||||||
├── logs/ # 日志目录
|
|
||||||
└── data/ # 数据目录
|
|
||||||
├── postgres/ # PostgreSQL数据
|
|
||||||
└── redis/ # Redis数据
|
|
||||||
```
|
|
||||||
|
|
||||||
## 快速部署
|
|
||||||
```bash
|
|
||||||
# 解压和部署
|
|
||||||
tar -xzf qaup-deploy.tar.gz
|
|
||||||
cd qaup-deploy
|
|
||||||
./deploy-all.sh
|
|
||||||
|
|
||||||
# 检查状态
|
|
||||||
docker compose ps
|
|
||||||
curl http://localhost:8080/actuator/health
|
|
||||||
```
|
|
||||||
|
|
||||||
## 统一服务管理
|
|
||||||
```bash
|
|
||||||
# 使用统一服务管理脚本
|
|
||||||
./qaup-service.sh help # 查看帮助
|
|
||||||
./qaup-service.sh start # 启动所有服务
|
|
||||||
./qaup-service.sh status # 查看服务状态
|
|
||||||
./qaup-service.sh logs qaup-app # 查看应用日志
|
|
||||||
./qaup-service.sh health # 健康检查
|
|
||||||
./qaup-service.sh backup # 数据备份
|
|
||||||
```
|
|
||||||
|
|
||||||
## 应用升级
|
|
||||||
```bash
|
|
||||||
# 方法1: 使用统一管理脚本(推荐)
|
|
||||||
./qaup-service.sh update # 将新版本文件重命名为new-app.jar后使用
|
|
||||||
|
|
||||||
# 方法2: 使用专用升级脚本
|
|
||||||
cp /path/to/new/qaup-admin.jar ./new-app.jar
|
|
||||||
./deploy-update.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## 管理命令
|
|
||||||
```bash
|
|
||||||
# 查看状态
|
|
||||||
./qaup-service.sh status
|
|
||||||
# 或
|
|
||||||
docker compose ps
|
|
||||||
|
|
||||||
# 查看日志
|
|
||||||
./qaup-service.sh logs qaup-app
|
|
||||||
# 或
|
|
||||||
docker compose logs -f qaup-app
|
|
||||||
|
|
||||||
# 停止服务
|
|
||||||
./qaup-service.sh stop
|
|
||||||
# 或
|
|
||||||
docker compose down
|
|
||||||
|
|
||||||
# 重启应用
|
|
||||||
./qaup-service.sh restart
|
|
||||||
# 或
|
|
||||||
docker compose restart qaup-app
|
|
||||||
```
|
|
||||||
|
|
||||||
## 默认信息
|
|
||||||
- **Web访问**: http://localhost:8080
|
|
||||||
- **数据库**: localhost:5432 (qaup/qaup123)
|
|
||||||
- **Redis**: localhost:6379
|
|
||||||
- **初始账号**: admin/admin123
|
|
||||||
|
|
||||||
## 支持
|
|
||||||
如遇问题,请查看日志:
|
|
||||||
```bash
|
|
||||||
docker compose logs qaup-app
|
|
||||||
```
|
|
||||||
EOF
|
|
||||||
|
|
||||||
print_message $GREEN "✓ README文件已创建"
|
|
||||||
|
|
||||||
# 验证部署包完整性
|
|
||||||
print_message $BLUE "6. 验证部署包完整性..."
|
|
||||||
REQUIRED_FILES=("app.jar" "docker-compose.yml" "config.yml" "deploy-all.sh" "deploy-update.sh" "qaup-service.sh" "images.tar.gz")
|
|
||||||
for file in "${REQUIRED_FILES[@]}"; do
|
|
||||||
if [ ! -f "qaup-deploy/$file" ]; then
|
|
||||||
print_message $RED "❌ 缺失必要文件: $file"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
print_message $GREEN "✓ 所有必要文件验证通过"
|
|
||||||
|
|
||||||
# 创建部署包
|
|
||||||
print_message $BLUE "7. 创建部署包..."
|
|
||||||
PACKAGE_NAME="qaup-deploy-$(date +%Y%m%d-%H%M%S).tar.gz"
|
|
||||||
|
|
||||||
if tar -czf "$PACKAGE_NAME" -C qaup-deploy .; then
|
|
||||||
print_message $GREEN "✅ 打包完成: $PACKAGE_NAME"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 打包失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PACKAGE_SIZE=$(du -sh "$PACKAGE_NAME" | cut -f1)
|
|
||||||
DEPLOY_DIR_SIZE=$(du -sh qaup-deploy | cut -f1)
|
|
||||||
print_message $GREEN "✓ 部署包大小: $PACKAGE_SIZE"
|
|
||||||
print_message $BLUE " 临时目录大小: $DEPLOY_DIR_SIZE"
|
|
||||||
|
|
||||||
# 清理临时文件
|
|
||||||
print_message $BLUE "8. 清理临时文件..."
|
|
||||||
rm -rf qaup-deploy
|
|
||||||
print_message $GREEN "✓ 临时目录已清理"
|
|
||||||
|
|
||||||
print_message $GREEN "🎉 打包成功完成!"
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "📋 部署说明(生产环境):"
|
|
||||||
echo ""
|
|
||||||
echo "⚠️ 由于生产环境安全要求,必须手工文件上传"
|
|
||||||
echo ""
|
|
||||||
echo "1. 按安全策略传输部署包到目标服务器:"
|
|
||||||
echo " scp $PACKAGE_NAME user@生产服务器IP:/opt/qaup/"
|
|
||||||
echo " 或使用SFTP、文件传输工具等"
|
|
||||||
echo ""
|
|
||||||
echo "2. 在生产服务器执行部署:"
|
|
||||||
echo " ssh user@生产服务器IP"
|
|
||||||
echo " cd /opt/qaup"
|
|
||||||
echo " mkdir qaup-deploy && tar -xzf $PACKAGE_NAME -C qaup-deploy"
|
|
||||||
echo " cd qaup-deploy"
|
|
||||||
echo " chmod +x *.sh"
|
|
||||||
echo " ./deploy-all.sh"
|
|
||||||
echo ""
|
|
||||||
echo "3. 详细部署说明请查看:"
|
|
||||||
echo " - 部署指南: DeployGuide.md"
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "📊 打包统计:"
|
|
||||||
echo " - 部署包: $PACKAGE_NAME ($PACKAGE_SIZE)"
|
|
||||||
echo " - Java版本: $JAVA_VERSION"
|
|
||||||
echo " - 打包时间: $(date)"
|
|
||||||
echo " - 服务器: $(hostname)"
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "📖 文档信息:"
|
|
||||||
echo " - 部署指南已包含在部署包中: DeployGuide.md"
|
|
||||||
echo " - 包含完整的生产环境部署和更新说明"
|
|
||||||
echo ""
|
|
||||||
print_message $GREEN "🚀 可以开始部署了!"
|
|
||||||
82
deploy/package-server.sh
Executable file
82
deploy/package-server.sh
Executable file
@ -0,0 +1,82 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# QAUP 服务器端打包脚本
|
||||||
|
# 在Ubuntu打包服务器上运行,使用已构建的jar文件进行打包
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 颜色输出
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
print_message() {
|
||||||
|
echo -e "${1}${2}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_message $BLUE "=== QAUP 服务器端打包脚本 ==="
|
||||||
|
|
||||||
|
# 检查jar文件是否存在
|
||||||
|
JAR_FILE="qaup-admin/target/qaup-admin.jar"
|
||||||
|
if [ ! -f "$JAR_FILE" ]; then
|
||||||
|
print_message $RED "❌ 未找到jar文件: $JAR_FILE"
|
||||||
|
echo "请先在macOS上构建jar文件并上传到服务器"
|
||||||
|
echo "或者将jar文件放到正确位置"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_message $GREEN "✓ 找到jar文件: $JAR_FILE"
|
||||||
|
|
||||||
|
# 检查Docker是否可用
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
print_message $RED "❌ Docker 未安装"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 准备镜像版本
|
||||||
|
POSTGRES_IMAGE="m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine"
|
||||||
|
REDIS_IMAGE="m.daocloud.io/docker.io/library/redis:8.0-alpine"
|
||||||
|
OPENJDK_IMAGE="m.daocloud.io/docker.io/library/eclipse-temurin:21-jre"
|
||||||
|
|
||||||
|
print_message $BLUE "拉取基础镜像..."
|
||||||
|
docker pull --platform linux/amd64 $POSTGRES_IMAGE
|
||||||
|
docker pull --platform linux/amd64 $REDIS_IMAGE
|
||||||
|
docker pull --platform linux/amd64 $OPENJDK_IMAGE
|
||||||
|
|
||||||
|
# 导出镜像
|
||||||
|
print_message $BLUE "导出镜像..."
|
||||||
|
mkdir -p qaup-deploy
|
||||||
|
docker save $POSTGRES_IMAGE $REDIS_IMAGE $OPENJDK_IMAGE | gzip > qaup-deploy/images.tar.gz
|
||||||
|
|
||||||
|
# 复制必要文件
|
||||||
|
print_message $BLUE "准备部署文件..."
|
||||||
|
cp "$JAR_FILE" qaup-deploy/app.jar
|
||||||
|
cp deploy/simple/docker-compose.yml qaup-deploy/
|
||||||
|
cp deploy/simple/config.yml qaup-deploy/
|
||||||
|
|
||||||
|
# 复制数据库相关文件
|
||||||
|
mkdir -p qaup-deploy/sql
|
||||||
|
cp deploy/docker/postgres/qaup_database_schema.sql qaup-deploy/sql/ 2>/dev/null || echo "⚠ 数据库架构文件不存在"
|
||||||
|
cp deploy/docker/postgres/export/initial_data_complete.sql qaup-deploy/sql/ 2>/dev/null || echo "⚠ 完整初始数据文件不存在"
|
||||||
|
|
||||||
|
# 创建数据库初始化脚本
|
||||||
|
./deploy/simple/create-init-sql.sh
|
||||||
|
cp deploy/simple/init.sql qaup-deploy/
|
||||||
|
|
||||||
|
cp deploy/simple/deploy.sh qaup-deploy/
|
||||||
|
cp deploy/simple/update.sh qaup-deploy/
|
||||||
|
cp deploy/simple/DeployGuide.md qaup-deploy/
|
||||||
|
|
||||||
|
# 创建部署包
|
||||||
|
PACKAGE_NAME="qaup-deploy-$(date +%Y%m%d-%H%M%S).tar.gz"
|
||||||
|
tar -czf "$PACKAGE_NAME" -C qaup-deploy .
|
||||||
|
|
||||||
|
print_message $GREEN "✅ 打包完成: $PACKAGE_NAME"
|
||||||
|
print_message $BLUE "部署包大小: $(du -sh $PACKAGE_NAME | cut -f1)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "部署说明:"
|
||||||
|
echo "1. 将 $PACKAGE_NAME 传输到目标服务器"
|
||||||
|
echo "2. mkdir qaup-deploy && tar -xzf $PACKAGE_NAME -C qaup-deploy"
|
||||||
|
echo "3. cd qaup-deploy && ./deploy.sh"
|
||||||
@ -1,157 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# QAUP 程序更新打包脚本
|
|
||||||
# 用于生成仅包含jar文件的更新包
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
||||||
|
|
||||||
# 颜色输出
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
print_message() {
|
|
||||||
echo -e "${1}${2}${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_message $BLUE "=== QAUP 程序更新打包 ==="
|
|
||||||
|
|
||||||
# 1. 构建应用
|
|
||||||
print_message $BLUE "构建应用..."
|
|
||||||
cd "$PROJECT_ROOT"
|
|
||||||
mvn clean package -DskipTests -q
|
|
||||||
|
|
||||||
# 检查jar文件是否生成成功
|
|
||||||
JAR_FILE="$PROJECT_ROOT/qaup-admin/target/qaup-admin.jar"
|
|
||||||
if [ ! -f "$JAR_FILE" ]; then
|
|
||||||
print_message $RED "❌ jar文件构建失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 2. 创建更新包目录
|
|
||||||
UPDATE_DIR="qaup-update-$(date +%Y%m%d-%H%M%S)"
|
|
||||||
mkdir -p "$UPDATE_DIR"
|
|
||||||
|
|
||||||
# 3. 复制jar文件
|
|
||||||
print_message $BLUE "准备更新文件..."
|
|
||||||
cp "$JAR_FILE" "$UPDATE_DIR/qaup-admin.jar"
|
|
||||||
|
|
||||||
# 4. 创建更新说明
|
|
||||||
cat > "$UPDATE_DIR/UPDATE-INSTRUCTIONS.md" << 'EOF'
|
|
||||||
# QAUP 程序更新包
|
|
||||||
|
|
||||||
## 包含内容
|
|
||||||
- `qaup-admin.jar` - 新版本应用程序
|
|
||||||
|
|
||||||
## ⚠️ 生产环境安全要求
|
|
||||||
由于生产环境安全限制,必须手工文件上传,禁止使用自动化脚本传输。
|
|
||||||
|
|
||||||
## 更新步骤
|
|
||||||
|
|
||||||
### 1. 文件传输(手工方式)
|
|
||||||
将更新包 `qaup-admin.jar` 按安全策略传输到生产服务器:
|
|
||||||
```bash
|
|
||||||
# 方案A:使用SCP
|
|
||||||
scp qaup-admin.jar user@生产服务器IP:/opt/qaup/qaup-deploy/
|
|
||||||
|
|
||||||
# 方案B:使用SFTP
|
|
||||||
# 通过SFTP工具上传文件到 /opt/qaup/qaup-deploy/ 目录
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 重命名文件
|
|
||||||
```bash
|
|
||||||
ssh user@生产服务器IP
|
|
||||||
cd /opt/qaup/qaup-deploy
|
|
||||||
cp qaup-admin.jar new-app.jar
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 执行更新
|
|
||||||
```bash
|
|
||||||
./deploy-update.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 验证更新
|
|
||||||
```bash
|
|
||||||
# 检查服务状态
|
|
||||||
docker compose ps
|
|
||||||
|
|
||||||
# 检查应用日志
|
|
||||||
docker compose logs -f qaup-app
|
|
||||||
|
|
||||||
# 访问系统确认功能正常
|
|
||||||
curl http://localhost:8080/actuator/health
|
|
||||||
```
|
|
||||||
|
|
||||||
## 安全注意事项
|
|
||||||
- 更新前建议备份数据库:`docker exec qaup-postgres pg_dump -U qaup qaup > backup-$(date +%Y%m%d).sql`
|
|
||||||
- 如果更新失败,脚本会自动回滚
|
|
||||||
- 如有更多问题,请查看完整的部署指南:DeployGuide.md
|
|
||||||
- 仅授权人员可访问生产服务器
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# 5. 创建版本信息
|
|
||||||
cat > "$UPDATE_DIR/VERSION-INFO.txt" << EOF
|
|
||||||
QAUP 程序更新包
|
|
||||||
构建时间: $(date)
|
|
||||||
构建主机: $(hostname)
|
|
||||||
Git提交: $(git rev-parse --short HEAD 2>/dev/null || echo "未知")
|
|
||||||
Maven版本: $(mvn --version | head -1)
|
|
||||||
Java版本: $(java -version 2>&1 | head -1)
|
|
||||||
文件大小: $(du -sh "$JAR_FILE" | cut -f1)
|
|
||||||
文件MD5: $(md5sum "$JAR_FILE" | cut -d' ' -f1)
|
|
||||||
|
|
||||||
更新包内容:
|
|
||||||
- qaup-admin.jar (主程序)
|
|
||||||
- UPDATE-INSTRUCTIONS.md (更新说明)
|
|
||||||
- VERSION-INFO.txt (版本信息)
|
|
||||||
|
|
||||||
适用场景:
|
|
||||||
- 生产环境程序热更新
|
|
||||||
- 支持自动回滚机制
|
|
||||||
- 适合已部署环境的增量更新
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# 6. 验证更新包完整性
|
|
||||||
print_message $BLUE "验证更新包完整性..."
|
|
||||||
REQUIRED_FILES=("qaup-admin.jar" "UPDATE-INSTRUCTIONS.md" "VERSION-INFO.txt")
|
|
||||||
for file in "${REQUIRED_FILES[@]}"; do
|
|
||||||
if [ ! -f "$UPDATE_DIR/$file" ]; then
|
|
||||||
print_message $RED "❌ 缺失必要文件: $file"
|
|
||||||
rm -rf "$UPDATE_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
print_message $GREEN "✓ 所有必要文件验证通过"
|
|
||||||
|
|
||||||
# 7. 创建更新包
|
|
||||||
PACKAGE_NAME="${UPDATE_DIR}.tar.gz"
|
|
||||||
tar -czf "$PACKAGE_NAME" "$UPDATE_DIR"
|
|
||||||
|
|
||||||
# 8. 清理临时目录
|
|
||||||
rm -rf "$UPDATE_DIR"
|
|
||||||
|
|
||||||
print_message $GREEN "✅ 程序更新包创建完成: $PACKAGE_NAME"
|
|
||||||
print_message $BLUE "包大小: $(du -sh "$PACKAGE_NAME" | cut -f1)"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "📋 更新说明(生产环境):"
|
|
||||||
echo ""
|
|
||||||
echo "⚠️ 由于生产环境安全要求,必须手工文件传输"
|
|
||||||
echo ""
|
|
||||||
echo "1. 按安全策略传输更新包到目标服务器:"
|
|
||||||
echo " scp $PACKAGE_NAME user@生产服务器IP:/opt/qaup/"
|
|
||||||
echo " 或使用SFTP、文件传输工具等"
|
|
||||||
echo ""
|
|
||||||
echo "2. 在生产服务器执行更新:"
|
|
||||||
echo " ssh user@生产服务器IP"
|
|
||||||
echo " cd /opt/qaup/qaup-deploy"
|
|
||||||
echo " tar -xzf ../$PACKAGE_NAME"
|
|
||||||
echo " cp qaup-admin.jar new-app.jar"
|
|
||||||
echo " ./deploy-update.sh"
|
|
||||||
echo ""
|
|
||||||
echo "3. 详细更新说明请查看:"
|
|
||||||
echo " - 更新说明: UPDATE-INSTRUCTIONS.md"
|
|
||||||
@ -1,441 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# QAUP 统一服务管理脚本
|
|
||||||
# 支持管理QAUP核心服务、数据库、缓存和ADXP适配器
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# 颜色输出
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
# 配置变量
|
|
||||||
COMPOSE_FILE="docker-compose.yml"
|
|
||||||
ADXP_COMPOSE_FILE="docker-compose.adxp.yml"
|
|
||||||
APP_SERVICE="qaup-app"
|
|
||||||
DB_SERVICE="qaup-postgres"
|
|
||||||
REDIS_SERVICE="qaup-redis"
|
|
||||||
ADXP_SERVICE="adxp-adapter"
|
|
||||||
|
|
||||||
print_message() {
|
|
||||||
echo -e "${1}${2}${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_header() {
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "=== QAUP 统一服务管理 ==="
|
|
||||||
print_message $BLUE "当前目录: $(pwd)"
|
|
||||||
print_message $BLUE "时间: $(date)"
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# 检查必要文件
|
|
||||||
check_files() {
|
|
||||||
if [ ! -f "$COMPOSE_FILE" ]; then
|
|
||||||
print_message $RED "❌ 找不到 $COMPOSE_FILE 文件"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -f "config.yml" ]; then
|
|
||||||
print_message $RED "❌ 找不到 config.yml 文件"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 启动所有服务
|
|
||||||
start_all() {
|
|
||||||
print_message $BLUE "启动所有QAUP服务..."
|
|
||||||
|
|
||||||
# 启动基础设施服务
|
|
||||||
print_message $BLUE "1. 启动数据库服务..."
|
|
||||||
docker compose up -d $DB_SERVICE
|
|
||||||
|
|
||||||
print_message $BLUE "2. 启动缓存服务..."
|
|
||||||
docker compose up -d $REDIS_SERVICE
|
|
||||||
|
|
||||||
# 等待基础设施就绪
|
|
||||||
print_message $BLUE "等待基础设施服务就绪..."
|
|
||||||
sleep 30
|
|
||||||
|
|
||||||
# 检查数据库状态
|
|
||||||
if ! docker exec $DB_SERVICE pg_isready -U qaup > /dev/null 2>&1; then
|
|
||||||
print_message $RED "❌ 数据库启动失败"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查Redis状态
|
|
||||||
if ! docker exec $REDIS_SERVICE redis-cli ping > /dev/null 2>&1; then
|
|
||||||
print_message $RED "❌ Redis启动失败"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_message $GREEN "✓ 基础设施服务就绪"
|
|
||||||
|
|
||||||
# 启动应用服务
|
|
||||||
print_message $BLUE "3. 启动应用服务..."
|
|
||||||
docker compose up -d $APP_SERVICE
|
|
||||||
|
|
||||||
# 启动ADXP适配器(如果配置文件存在)
|
|
||||||
if [ -f "$ADXP_COMPOSE_FILE" ]; then
|
|
||||||
print_message $BLUE "4. 启动ADXP适配器..."
|
|
||||||
docker compose -f $COMPOSE_FILE -f $ADXP_COMPOSE_FILE up -d $ADXP_SERVICE
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_message $GREEN "🎉 所有服务启动完成!"
|
|
||||||
show_status
|
|
||||||
}
|
|
||||||
|
|
||||||
# 停止所有服务
|
|
||||||
stop_all() {
|
|
||||||
print_message $BLUE "停止所有QAUP服务..."
|
|
||||||
|
|
||||||
# 停止ADXP适配器
|
|
||||||
if [ -f "$ADXP_COMPOSE_FILE" ]; then
|
|
||||||
docker compose -f $COMPOSE_FILE -f $ADXP_COMPOSE_FILE down $ADXP_SERVICE 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 停止应用服务
|
|
||||||
docker compose stop $APP_SERVICE 2>/dev/null || true
|
|
||||||
|
|
||||||
# 停止缓存服务
|
|
||||||
docker compose stop $REDIS_SERVICE 2>/dev/null || true
|
|
||||||
|
|
||||||
# 停止数据库服务
|
|
||||||
docker compose stop $DB_SERVICE 2>/dev/null || true
|
|
||||||
|
|
||||||
print_message $GREEN "✓ 所有服务已停止"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 重启所有服务
|
|
||||||
restart_all() {
|
|
||||||
print_message $BLUE "重启所有QAUP服务..."
|
|
||||||
stop_all
|
|
||||||
sleep 5
|
|
||||||
start_all
|
|
||||||
}
|
|
||||||
|
|
||||||
# 查看服务状态
|
|
||||||
show_status() {
|
|
||||||
print_message $BLUE "服务状态:"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 显示服务状态表格
|
|
||||||
printf "%-20s %-15s %-15s %-10s\n" "服务名" "状态" "端口" "健康检查"
|
|
||||||
printf "%-20s %-15s %-15s %-10s\n" "--------------------" "---------------" "---------------" "----------"
|
|
||||||
|
|
||||||
# 检查各个服务状态
|
|
||||||
services=(
|
|
||||||
"$APP_SERVICE:8080:qaup-app"
|
|
||||||
"$DB_SERVICE:5432:qaup-postgres"
|
|
||||||
"$REDIS_SERVICE:6379:qaup-redis"
|
|
||||||
)
|
|
||||||
|
|
||||||
for service_info in "${services[@]}"; do
|
|
||||||
service=$(echo $service_info | cut -d: -f1)
|
|
||||||
port=$(echo $service_info | cut -d: -f2)
|
|
||||||
container=$(echo $service_info | cut -d: -f3)
|
|
||||||
|
|
||||||
status=$(docker compose ps $service --format json 2>/dev/null | jq -r '.[0].State' 2>/dev/null || echo "unknown")
|
|
||||||
|
|
||||||
# 健康检查
|
|
||||||
if [ "$status" = "running" ]; then
|
|
||||||
if [ "$service" = "$APP_SERVICE" ]; then
|
|
||||||
health=$(curl -f -s http://localhost:8080/actuator/health > /dev/null 2>&1 && echo "正常" || echo "异常")
|
|
||||||
elif [ "$service" = "$DB_SERVICE" ]; then
|
|
||||||
health=$(docker exec $DB_SERVICE pg_isready -U qaup > /dev/null 2>&1 && echo "正常" || echo "异常")
|
|
||||||
elif [ "$service" = "$REDIS_SERVICE" ]; then
|
|
||||||
health=$(docker exec $REDIS_SERVICE redis-cli ping > /dev/null 2>&1 && echo "正常" || echo "异常")
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
health="未运行"
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf "%-20s %-15s %-15s %-10s\n" "$service" "$status" "$port" "$health"
|
|
||||||
done
|
|
||||||
|
|
||||||
# 检查ADXP适配器状态
|
|
||||||
if [ -f "$ADXP_COMPOSE_FILE" ]; then
|
|
||||||
adxp_status=$(docker compose -f $COMPOSE_FILE -f $ADXP_COMPOSE_FILE ps $ADXP_SERVICE --format json 2>/dev/null | jq -r '.[0].State' 2>/dev/null || echo "unknown")
|
|
||||||
adxp_health=$(curl -f -s http://localhost:8086/health > /dev/null 2>&1 && echo "正常" || echo "异常")
|
|
||||||
printf "%-20s %-15s %-15s %-10s\n" "$ADXP_SERVICE" "$adxp_status" "8086" "$adxp_health"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# 查看日志
|
|
||||||
show_logs() {
|
|
||||||
local service=${1:-""}
|
|
||||||
local lines=${2:-50}
|
|
||||||
|
|
||||||
if [ -n "$service" ]; then
|
|
||||||
print_message $BLUE "查看 $service 服务日志 (最后 $lines 行):"
|
|
||||||
docker compose logs --tail=$lines -f $service
|
|
||||||
else
|
|
||||||
print_message $BLUE "查看所有服务日志 (最后 $lines 行):"
|
|
||||||
docker compose logs --tail=$lines -f
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 健康检查
|
|
||||||
health_check() {
|
|
||||||
print_message $BLUE "执行健康检查..."
|
|
||||||
|
|
||||||
local errors=0
|
|
||||||
|
|
||||||
# 检查Docker服务
|
|
||||||
if ! docker info > /dev/null 2>&1; then
|
|
||||||
print_message $RED "❌ Docker服务不可用"
|
|
||||||
((errors++))
|
|
||||||
else
|
|
||||||
print_message $GREEN "✓ Docker服务正常"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查应用健康状态
|
|
||||||
if curl -f -s http://localhost:8080/actuator/health > /dev/null 2>&1; then
|
|
||||||
print_message $GREEN "✓ 应用服务健康检查通过"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 应用服务健康检查失败"
|
|
||||||
((errors++))
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查数据库连接
|
|
||||||
if docker exec $DB_SERVICE pg_isready -U qaup > /dev/null 2>&1; then
|
|
||||||
print_message $GREEN "✓ 数据库连接正常"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 数据库连接失败"
|
|
||||||
((errors++))
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查Redis连接
|
|
||||||
if docker exec $REDIS_SERVICE redis-cli ping > /dev/null 2>&1; then
|
|
||||||
print_message $GREEN "✓ Redis连接正常"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ Redis连接失败"
|
|
||||||
((errors++))
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查磁盘空间
|
|
||||||
available_space=$(df . | tail -1 | awk '{print $4}')
|
|
||||||
if [ "$available_space" -gt $((1024*1024)) ]; then # 1GB
|
|
||||||
print_message $GREEN "✓ 磁盘空间充足"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 磁盘空间不足"
|
|
||||||
((errors++))
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查端口占用
|
|
||||||
ports=(8080 5432 6379 8086)
|
|
||||||
for port in "${ports[@]}"; do
|
|
||||||
if netstat -tuln 2>/dev/null | grep -q ":$port " || ss -tuln 2>/dev/null | grep -q ":$port "; then
|
|
||||||
print_message $GREEN "✓ 端口 $port 占用正常"
|
|
||||||
else
|
|
||||||
print_message $YELLOW "⚠️ 端口 $port 未占用"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ $errors -eq 0 ]; then
|
|
||||||
print_message $GREEN "🎉 所有健康检查通过!"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 发现 $errors 个问题"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 数据备份
|
|
||||||
backup_data() {
|
|
||||||
local backup_dir="backup"
|
|
||||||
local timestamp=$(date +%Y%m%d_%H%M%S)
|
|
||||||
|
|
||||||
print_message $BLUE "开始数据备份..."
|
|
||||||
|
|
||||||
mkdir -p "$backup_dir"
|
|
||||||
|
|
||||||
# 备份数据库
|
|
||||||
local db_backup="$backup_dir/qaup_db_backup_$timestamp.sql"
|
|
||||||
print_message $BLUE "1. 备份数据库..."
|
|
||||||
if docker exec $DB_SERVICE pg_dump -U qaup qaup > "$db_backup" 2>/dev/null; then
|
|
||||||
print_message $GREEN "✓ 数据库备份成功: $db_backup"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 数据库备份失败"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 备份Redis数据
|
|
||||||
local redis_backup="$backup_dir/redis_backup_$timestamp.rdb"
|
|
||||||
print_message $BLUE "2. 备份Redis数据..."
|
|
||||||
if docker exec $REDIS_SERVICE redis-cli BGSAVE > /dev/null 2>&1; then
|
|
||||||
sleep 5
|
|
||||||
docker cp $REDIS_SERVICE:/data/dump.rdb "$redis_backup" 2>/dev/null
|
|
||||||
if [ -f "$redis_backup" ]; then
|
|
||||||
print_message $GREEN "✓ Redis备份成功: $redis_backup"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ Redis备份失败"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
print_message $YELLOW "⚠️ Redis备份跳过"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 备份应用配置
|
|
||||||
local config_backup="$backup_dir/config_backup_$timestamp.yml"
|
|
||||||
print_message $BLUE "3. 备份应用配置..."
|
|
||||||
if cp config.yml "$config_backup"; then
|
|
||||||
print_message $GREEN "✓ 配置备份成功: $config_backup"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 配置备份失败"
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_message $GREEN "🎉 数据备份完成!"
|
|
||||||
ls -la "$backup_dir"/*_$timestamp.*
|
|
||||||
}
|
|
||||||
|
|
||||||
# 清理数据
|
|
||||||
clean_data() {
|
|
||||||
print_message $YELLOW "⚠️ 这将删除所有数据目录,确认继续吗?(y/N): "
|
|
||||||
read -r confirmation
|
|
||||||
if [[ ! $confirmation =~ ^[Yy]$ ]]; then
|
|
||||||
print_message $BLUE "操作已取消"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_message $BLUE "清理数据目录..."
|
|
||||||
|
|
||||||
# 停止所有服务
|
|
||||||
stop_all
|
|
||||||
|
|
||||||
# 删除数据目录
|
|
||||||
rm -rf data/ logs/ backup/
|
|
||||||
|
|
||||||
print_message $GREEN "✓ 数据目录已清理"
|
|
||||||
print_message $BLUE "请运行 './qaup-service.sh start' 重新初始化数据"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 更新应用
|
|
||||||
update_app() {
|
|
||||||
if [ ! -f "new-app.jar" ]; then
|
|
||||||
print_message $RED "❌ 未找到新版本文件: new-app.jar"
|
|
||||||
print_message $BLUE "请先将新版本文件重命名为 new-app.jar"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_message $BLUE "开始应用更新..."
|
|
||||||
|
|
||||||
# 备份当前应用
|
|
||||||
local backup_dir="backup"
|
|
||||||
local timestamp=$(date +%Y%m%d_%H%M%S)
|
|
||||||
mkdir -p "$backup_dir"
|
|
||||||
|
|
||||||
if [ -f "app.jar" ]; then
|
|
||||||
cp app.jar "$backup_dir/app.jar.backup.$timestamp"
|
|
||||||
print_message $BLUE "✓ 当前应用已备份"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 停止应用服务
|
|
||||||
docker compose stop $APP_SERVICE
|
|
||||||
|
|
||||||
# 替换应用文件
|
|
||||||
if cp new-app.jar app.jar; then
|
|
||||||
print_message $GREEN "✓ 应用文件更新成功"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 应用文件更新失败"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 启动应用服务
|
|
||||||
docker compose up -d $APP_SERVICE
|
|
||||||
|
|
||||||
# 等待应用启动
|
|
||||||
print_message $BLUE "等待应用启动..."
|
|
||||||
sleep 30
|
|
||||||
|
|
||||||
# 验证更新结果
|
|
||||||
if curl -f -s http://localhost:8080/actuator/health > /dev/null 2>&1; then
|
|
||||||
print_message $GREEN "🎉 应用更新成功!"
|
|
||||||
else
|
|
||||||
print_message $RED "❌ 应用更新失败,正在回滚..."
|
|
||||||
# 回滚应用
|
|
||||||
if [ -f "$backup_dir/app.jar.backup.$timestamp" ]; then
|
|
||||||
docker compose stop $APP_SERVICE
|
|
||||||
cp "$backup_dir/app.jar.backup.$timestamp" app.jar
|
|
||||||
docker compose up -d $APP_SERVICE
|
|
||||||
print_message $BLUE "✓ 应用已回滚"
|
|
||||||
fi
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# 显示帮助信息
|
|
||||||
show_help() {
|
|
||||||
print_message $BLUE "QAUP 统一服务管理脚本"
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "使用方法:"
|
|
||||||
print_message $BLUE " ./qaup-service.sh <command> [options]"
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "可用命令:"
|
|
||||||
print_message $BLUE " start 启动所有服务"
|
|
||||||
print_message $BLUE " stop 停止所有服务"
|
|
||||||
print_message $BLUE " restart 重启所有服务"
|
|
||||||
print_message $BLUE " status 查看服务状态"
|
|
||||||
print_message $BLUE " logs [service] 查看日志 (可指定服务名)"
|
|
||||||
print_message $BLUE " health 执行健康检查"
|
|
||||||
print_message $BLUE " backup 备份数据"
|
|
||||||
print_message $BLUE " clean 清理所有数据"
|
|
||||||
print_message $BLUE " update 更新应用"
|
|
||||||
print_message $BLUE " help 显示此帮助信息"
|
|
||||||
echo ""
|
|
||||||
print_message $BLUE "示例:"
|
|
||||||
print_message $BLUE " ./qaup-service.sh start"
|
|
||||||
print_message $BLUE " ./qaup-service.sh logs qaup-app"
|
|
||||||
print_message $BLUE " ./qaup-service.sh health"
|
|
||||||
print_message $BLUE " ./qaup-service.sh backup"
|
|
||||||
}
|
|
||||||
|
|
||||||
# 主函数
|
|
||||||
main() {
|
|
||||||
check_files
|
|
||||||
|
|
||||||
case "${1:-help}" in
|
|
||||||
start)
|
|
||||||
start_all
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
stop_all
|
|
||||||
;;
|
|
||||||
restart)
|
|
||||||
restart_all
|
|
||||||
;;
|
|
||||||
status)
|
|
||||||
show_status
|
|
||||||
;;
|
|
||||||
logs)
|
|
||||||
show_logs "$2" "${3:-50}"
|
|
||||||
;;
|
|
||||||
health)
|
|
||||||
health_check
|
|
||||||
;;
|
|
||||||
backup)
|
|
||||||
backup_data
|
|
||||||
;;
|
|
||||||
clean)
|
|
||||||
clean_data
|
|
||||||
;;
|
|
||||||
update)
|
|
||||||
update_app
|
|
||||||
;;
|
|
||||||
help|--help|-h)
|
|
||||||
show_help
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
print_message $RED "❌ 未知命令: $1"
|
|
||||||
show_help
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# 执行主函数
|
|
||||||
main "$@"
|
|
||||||
46
deploy/update.sh
Executable file
46
deploy/update.sh
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# QAUP 一键升级脚本
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== QAUP 一键升级 ==="
|
||||||
|
|
||||||
|
# 检查新版本文件
|
||||||
|
if [ ! -f "new-app.jar" ]; then
|
||||||
|
echo "❌ 请先将新版本文件重命名为 new-app.jar"
|
||||||
|
echo " 例如: cp qaup-admin-1.0.2.jar new-app.jar"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 备份当前版本
|
||||||
|
echo "备份当前版本..."
|
||||||
|
cp app.jar app.jar.backup.$(date +%Y%m%d-%H%M%S)
|
||||||
|
|
||||||
|
# 停止应用
|
||||||
|
echo "停止应用服务..."
|
||||||
|
docker compose stop qaup-app
|
||||||
|
|
||||||
|
# 替换应用
|
||||||
|
echo "更新应用文件..."
|
||||||
|
cp new-app.jar app.jar
|
||||||
|
|
||||||
|
# 启动应用
|
||||||
|
echo "启动应用服务..."
|
||||||
|
docker compose start qaup-app
|
||||||
|
|
||||||
|
# 等待启动
|
||||||
|
echo "等待服务启动(30秒)..."
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
# 检查服务状态
|
||||||
|
if curl -f -s http://localhost:8080/actuator/health > /dev/null 2>&1; then
|
||||||
|
echo "✅ 升级成功!"
|
||||||
|
echo "备份文件已保存,如有问题可手动回滚"
|
||||||
|
else
|
||||||
|
echo "❌ 升级失败,尝试回滚..."
|
||||||
|
docker compose stop qaup-app
|
||||||
|
cp app.jar.backup.* app.jar 2>/dev/null || echo "未找到备份文件"
|
||||||
|
docker compose start qaup-app
|
||||||
|
echo "请检查日志: docker compose logs qaup-app"
|
||||||
|
fi
|
||||||
@ -1,26 +1,24 @@
|
|||||||
# Ubuntu部署指南
|
# Ubuntu部署指南
|
||||||
|
|
||||||
> **重要说明**: 本文档描述了现代化的极简部署方案,数据库迁移通过Flyway自动化处理,无需手动执行SQL脚本。
|
|
||||||
|
|
||||||
## 1. 系统环境准备
|
## 1. 系统环境准备
|
||||||
|
|
||||||
### Ubuntu系统要求
|
### Ubuntu系统要求
|
||||||
- **操作系统**: Ubuntu 20.04 LTS 或更高版本
|
- **操作系统**: Ubuntu 20.04 LTS 或更高版本
|
||||||
- **内存**: 至少 8GB RAM
|
- **内存**: 至少 8GB RAM
|
||||||
- **存储**: 至少 20GB 可用空间
|
- **存储**: 至少 10GB 可用空间
|
||||||
- **CPU**: 4核或以上
|
- **CPU**: 4核或以上
|
||||||
- **网络**: 稳定的互联网连接(用于下载Docker镜像)
|
- **网络**: 稳定的互联网连接
|
||||||
|
|
||||||
### 安装必要软件
|
### 安装必要软件
|
||||||
```bash
|
```bash
|
||||||
# 系统更新
|
# 系统更新
|
||||||
sudo apt update && sudo apt upgrade -y
|
sudo apt update && sudo apt upgrade -y
|
||||||
|
|
||||||
# 安装Java 21(用于本地编译)
|
# 安装Java 21
|
||||||
sudo apt install -y openjdk-21-jdk
|
sudo apt install -y openjdk-21-jdk
|
||||||
java -version # 验证安装
|
java -version # 验证安装
|
||||||
|
|
||||||
# 安装Maven(用于构建)
|
# 安装Maven
|
||||||
sudo apt install -y maven
|
sudo apt install -y maven
|
||||||
mvn -version # 验证安装
|
mvn -version # 验证安装
|
||||||
|
|
||||||
@ -29,361 +27,269 @@ sudo apt install -y docker.io
|
|||||||
sudo systemctl start docker
|
sudo systemctl start docker
|
||||||
sudo systemctl enable docker
|
sudo systemctl enable docker
|
||||||
|
|
||||||
# 安装Docker Compose
|
|
||||||
sudo apt install -y docker-compose-plugin
|
|
||||||
|
|
||||||
# 将当前用户添加到docker组
|
# 将当前用户添加到docker组
|
||||||
sudo usermod -aG docker $USER
|
sudo usermod -aG docker $USER
|
||||||
newgrp docker # 重新加载组权限
|
|
||||||
|
|
||||||
# 验证安装
|
# 验证Docker
|
||||||
docker --version
|
docker --version
|
||||||
docker compose version
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2. 部署方式选择
|
## 2. 获取并编译项目
|
||||||
|
|
||||||
### 方式一:使用预构建部署包(推荐)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 解压部署包
|
# 获取项目
|
||||||
tar -xzf qaup-deploy.tar.gz
|
|
||||||
cd qaup-deploy
|
|
||||||
|
|
||||||
# 2. 一键部署
|
|
||||||
./deploy-all.sh
|
|
||||||
|
|
||||||
# 3. 检查服务状态
|
|
||||||
docker compose ps
|
|
||||||
```
|
|
||||||
|
|
||||||
### 方式二:源码编译部署
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. 获取项目源码
|
|
||||||
git clone <your-repo-url>
|
git clone <your-repo-url>
|
||||||
cd QAUP-Management
|
cd QAUP-Management
|
||||||
|
|
||||||
# 2. 编译项目
|
# 编译项目
|
||||||
mvn clean package -DskipTests
|
mvn clean package -DskipTests
|
||||||
|
|
||||||
# 3. 创建部署目录
|
# 验证编译结果
|
||||||
|
ls -la qaup-admin/target/qaup-admin.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 部署方式
|
||||||
|
|
||||||
|
### 3.1 Docker Compose部署(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建部署目录
|
||||||
mkdir -p ~/qaup-deploy
|
mkdir -p ~/qaup-deploy
|
||||||
cd ~/qaup-deploy
|
cd ~/qaup-deploy
|
||||||
|
|
||||||
# 4. 复制部署文件
|
# 复制部署文件
|
||||||
cp /path/to/QAUP-Management/deploy/docker-compose.yml .
|
cp /path/to/QAUP-Management/deploy/docker-compose.yml .
|
||||||
cp /path/to/QAUP-Management/deploy/config.yml .
|
cp /path/to/QAUP-Management/deploy/config.yml .
|
||||||
cp /path/to/QAUP-Management/qaup-admin/target/qaup-admin.jar ./app.jar
|
cp /path/to/QAUP-Management/qaup-admin/target/qaup-admin.jar ./app.jar
|
||||||
|
|
||||||
# 5. 创建数据目录
|
# 创建数据目录
|
||||||
mkdir -p data/postgres data/redis logs backup
|
mkdir -p data/postgres data/redis logs backup
|
||||||
|
|
||||||
# 6. 启动服务
|
# 启动服务
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# 7. 检查服务状态
|
# 检查服务状态
|
||||||
docker compose ps
|
docker compose ps
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.1 部署包内容说明
|
### 3.2 快速测试部署
|
||||||
|
|
||||||
```
|
> **注意**: 仅用于快速测试
|
||||||
qaup-deploy/
|
|
||||||
├── deploy-all.sh # 一键部署脚本
|
|
||||||
├── deploy-update.sh # 一键更新脚本
|
|
||||||
├── docker-compose.yml # Docker编排配置
|
|
||||||
├── config.yml # 应用配置文件
|
|
||||||
├── app.jar # 应用JAR文件
|
|
||||||
└── images.tar.gz # 预拉取的Docker镜像包
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 现代化特性说明
|
|
||||||
|
|
||||||
✅ **自动化数据库迁移**: Flyway在应用启动时自动执行数据库迁移
|
|
||||||
✅ **PostGIS支持**: 内置地理空间数据处理能力
|
|
||||||
✅ **健康检查**: 自动监控服务状态和依赖关系
|
|
||||||
✅ **零停机升级**: 只重启应用服务,数据库保持运行
|
|
||||||
✅ **离线部署**: 预打包镜像,无需互联网连接
|
|
||||||
|
|
||||||
## 3. 服务管理
|
|
||||||
|
|
||||||
### 3.1 基本服务管理
|
|
||||||
```bash
|
```bash
|
||||||
# 进入部署目录
|
# 创建运行目录
|
||||||
cd ~/qaup-deploy
|
mkdir -p ~/qaup-test
|
||||||
|
cd ~/qaup-test
|
||||||
|
|
||||||
# 启动所有服务
|
# 复制jar文件
|
||||||
|
cp /path/to/QAUP-Management/qaup-admin/target/qaup-admin.jar ./
|
||||||
|
|
||||||
|
# 启动数据库和缓存服务
|
||||||
|
docker run --name qaup-postgres -e POSTGRES_DB=qaup -e POSTGRES_USER=qaup -e POSTGRES_PASSWORD=qaup123 -p 5432:5432 -d postgis/postgis:17-3.5-alpine
|
||||||
|
docker run --name qaup-redis -p 6379:6379 -d redis:8.0-alpine
|
||||||
|
|
||||||
|
# 等待服务启动
|
||||||
|
sleep 30
|
||||||
|
|
||||||
|
# 运行应用
|
||||||
|
java -jar qaup-admin.jar --spring.profiles.active=dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 服务管理
|
||||||
|
|
||||||
|
### 启动/停止/重启
|
||||||
|
```bash
|
||||||
|
# 启动服务
|
||||||
|
cd ~/qaup-deploy
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# 停止所有服务
|
# 停止服务
|
||||||
docker compose down
|
docker compose down
|
||||||
|
|
||||||
# 重启应用服务(不影响数据库)
|
# 重启应用服务
|
||||||
docker compose restart qaup-app
|
docker compose restart qaup-app
|
||||||
|
|
||||||
# 查看服务状态
|
# 查看服务状态
|
||||||
docker compose ps
|
docker compose ps
|
||||||
|
|
||||||
# 查看应用日志
|
# 查看日志
|
||||||
docker compose logs -f qaup-app
|
docker compose logs -f qaup-app
|
||||||
|
|
||||||
# 查看数据库日志
|
|
||||||
docker compose logs qaup-postgres
|
|
||||||
|
|
||||||
# 查看Redis日志
|
|
||||||
docker compose logs qaup-redis
|
|
||||||
|
|
||||||
# 查看系统资源使用
|
|
||||||
docker stats
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.2 监控数据库迁移状态
|
## 5. 访问系统
|
||||||
```bash
|
|
||||||
# 查看Flyway迁移历史
|
|
||||||
docker exec -it qaup-postgres psql -U qaup -d qaup -c "SELECT * FROM flyway_schema_history ORDER BY installed_rank;"
|
|
||||||
|
|
||||||
# 查看应用日志中的迁移信息
|
### 系统地址
|
||||||
docker compose logs qaup-app | grep -i flyway
|
|
||||||
|
|
||||||
# 查看最近的迁移记录
|
|
||||||
docker exec -it qaup-postgres psql -U qaup -d qaup -c "SELECT version, description, installed_on FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 3;"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. 访问系统
|
|
||||||
|
|
||||||
### 4.1 系统地址
|
|
||||||
- **Web管理**: http://<your-server-ip>:8080
|
- **Web管理**: http://<your-server-ip>:8080
|
||||||
- **WebSocket**: ws://<your-server-ip>:8080/collision
|
- **WebSocket**: ws://<your-server-ip>:8080/collision
|
||||||
- **健康检查**: http://<your-server-ip>:8080/actuator/health
|
- **健康检查**: http://<your-server-ip>:8080/actuator/health
|
||||||
- **API文档**: http://<your-server-ip>:8080/doc.html
|
|
||||||
|
|
||||||
### 4.2 初始登录
|
### 初始登录
|
||||||
- **用户名**: admin
|
- **用户名**: admin
|
||||||
- **密码**: admin123
|
- **密码**: admin123
|
||||||
|
|
||||||
### 4.3 快速验证命令
|
## 6. 应用更新
|
||||||
```bash
|
|
||||||
# 健康检查(验证服务是否正常)
|
|
||||||
curl http://localhost:8080/actuator/health
|
|
||||||
|
|
||||||
# 检查容器中Java版本
|
### 6.1 基本更新流程
|
||||||
docker exec qaup-app java -version
|
|
||||||
|
|
||||||
# 数据库连接测试
|
|
||||||
docker exec -it qaup-postgres psql -U qaup -d qaup -c "SELECT version();"
|
|
||||||
|
|
||||||
# Redis连接测试
|
|
||||||
docker exec -it qaup-redis redis-cli ping
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. 应用更新
|
|
||||||
|
|
||||||
### 5.1 一键升级(推荐)
|
|
||||||
|
|
||||||
使用现代化的升级脚本,支持自动备份、版本检查和回滚:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 复制新版本文件并重命名为 new-app.jar
|
# 备份当前版本
|
||||||
cp /path/to/new/qaup-admin.jar ~/qaup-deploy/new-app.jar
|
|
||||||
|
|
||||||
# 2. 进入部署目录
|
|
||||||
cd ~/qaup-deploy
|
cd ~/qaup-deploy
|
||||||
|
cp app.jar app.jar.backup.$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
# 3. 执行自动升级
|
# 替换新版本
|
||||||
./deploy-update.sh
|
cp /path/to/new/qaup-admin.jar ./app.jar
|
||||||
|
|
||||||
# 升级过程包括:
|
# 重启应用
|
||||||
# ✅ Java版本兼容性检查
|
docker compose restart qaup-app
|
||||||
# ✅ 数据库自动备份
|
|
||||||
# ✅ 应用服务重启
|
# 验证更新
|
||||||
# ✅ Flyway自动迁移
|
curl http://localhost:8080/actuator/health
|
||||||
# ✅ 健康检查验证
|
|
||||||
# ✅ 自动回滚(如需要)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.2 手动升级流程
|
### 6.2 Java版本冲突解决
|
||||||
|
|
||||||
|
如果遇到 `UnsupportedClassVersionError` 错误,表示编译的jar文件与容器中Java版本不匹配:
|
||||||
|
|
||||||
|
**错误示例**:
|
||||||
|
```
|
||||||
|
Exception in thread "main" java.lang.UnsupportedClassVersionError:
|
||||||
|
com/qaup/QuapApplication has been compiled by a more recent version of the Java Runtime
|
||||||
|
(class file version 65.0), this version of the Java Runtime only recognizes
|
||||||
|
class file versions up to 61.0
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
|
||||||
|
1. **检查Docker镜像配置**:
|
||||||
|
确保 `docker-compose.yml` 中使用Java 21镜像:
|
||||||
|
```yaml
|
||||||
|
# QAUP 应用服务
|
||||||
|
qaup-app:
|
||||||
|
image: m.daocloud.io/docker.io/library/eclipse-temurin:21-jre
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **修改并重新部署**:
|
||||||
|
```bash
|
||||||
|
# 停止服务
|
||||||
|
docker compose down
|
||||||
|
|
||||||
|
# 备份原文件
|
||||||
|
cp docker-compose.yml docker-compose.yml.bak
|
||||||
|
|
||||||
|
# 修改Java版本(如果需要)
|
||||||
|
sed -i 's/eclipse-temurin:17-jre/eclipse-temurin:21-jre/' docker-compose.yml
|
||||||
|
|
||||||
|
# 重新启动服务
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **验证Java版本**:
|
||||||
|
```bash
|
||||||
|
# 检查容器中Java版本
|
||||||
|
docker exec qaup-app java -version
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 完整更新流程
|
||||||
|
|
||||||
|
当遇到容器名称冲突或其他问题时,使用完整的更新流程:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 进入部署目录
|
# 进入部署目录
|
||||||
cd ~/qaup-deploy
|
cd ~/qaup-deploy
|
||||||
|
|
||||||
# 1. 数据库备份(重要)
|
# 1. 停止现有服务
|
||||||
docker exec qaup-postgres pg_dump -U qaup qaup > backup/qaup-backup-$(date +%Y%m%d_%H%M%S).sql
|
docker compose down
|
||||||
|
|
||||||
# 2. 备份当前版本
|
# 2. 确认所有容器已停止
|
||||||
|
docker ps -a | grep qaup
|
||||||
|
|
||||||
|
# 3. 如果还有残留容器,强制删除
|
||||||
|
docker rm -f $(docker ps -aq --filter name=qaup)
|
||||||
|
|
||||||
|
# 4. 备份当前jar文件
|
||||||
cp app.jar app.jar.backup.$(date +%Y%m%d_%H%M%S)
|
cp app.jar app.jar.backup.$(date +%Y%m%d_%H%M%S)
|
||||||
|
|
||||||
# 3. 替换新版本
|
# 5. 替换新版本
|
||||||
cp /path/to/new/qaup-admin.jar ./app.jar
|
cp /path/to/new/qaup-admin.jar ./app.jar
|
||||||
|
|
||||||
# 4. 重启应用服务(数据库保持运行)
|
# 6. 启动新的服务
|
||||||
docker compose restart qaup-app
|
docker compose up -d
|
||||||
|
|
||||||
# 5. 等待启动和迁移完成(约2分钟)
|
# 7. 检查服务状态
|
||||||
echo "等待应用启动和数据库迁移..."
|
docker compose ps
|
||||||
sleep 120
|
|
||||||
|
|
||||||
# 6. 验证升级结果
|
# 8. 验证更新
|
||||||
if curl -f -s http://localhost:8080/actuator/health > /dev/null; then
|
curl -s http://localhost:8080/actuator/health | jq
|
||||||
echo "✅ 升级成功!"
|
|
||||||
else
|
|
||||||
echo "❌ 升级失败,开始回滚..."
|
|
||||||
# 恢复版本
|
|
||||||
LATEST_BACKUP=$(ls -t app.jar.backup.* 2>/dev/null | head -1)
|
|
||||||
cp "$LATEST_BACKUP" app.jar
|
|
||||||
docker compose restart qaup-app
|
|
||||||
fi
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.3 监控升级过程
|
### 6.4 故障排除
|
||||||
|
|
||||||
|
#### 6.4.1 容器名称冲突
|
||||||
|
|
||||||
|
如果遇到容器名称冲突错误:
|
||||||
|
```
|
||||||
|
Error response from daemon: Conflict. The container name "/qaup-app" is already in use
|
||||||
|
```
|
||||||
|
|
||||||
|
解决方法:
|
||||||
```bash
|
```bash
|
||||||
# 实时查看应用日志(包含Flyway迁移信息)
|
# 方式1:完全重建
|
||||||
docker compose logs -f qaup-app
|
docker compose down
|
||||||
|
docker rm -f $(docker ps -aq --filter name=qaup)
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
# 检查Flyway迁移状态
|
# 方式2:强制重新创建
|
||||||
docker exec -it qaup-postgres psql -U qaup -d qaup -c "SELECT version, description, installed_on FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 5;"
|
docker compose up -d --force-recreate
|
||||||
|
|
||||||
# 查看最近的迁移记录
|
# 方式3:只重新创建特定服务
|
||||||
docker compose logs qaup-app | grep -i "flyway.*migrated"
|
docker compose up -d --force-recreate qaup-app
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.4 故障排除
|
#### 6.4.2 Java版本验证
|
||||||
|
|
||||||
#### 5.4.1 Java版本兼容性错误
|
确认容器中Java版本与编译版本匹配:
|
||||||
|
|
||||||
如果遇到 `UnsupportedClassVersionError`:
|
|
||||||
```
|
|
||||||
Error: A JNI error has occurred, please check your installation and try again
|
|
||||||
Error: Could not find or load main class
|
|
||||||
```
|
|
||||||
|
|
||||||
**解决方案**:
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 检查容器Java版本
|
# 查看容器Java版本
|
||||||
docker exec qaup-app java -version
|
docker exec qaup-app java -version
|
||||||
|
|
||||||
# 2. 验证jar文件编译版本
|
# 如果需要,检查镜像信息
|
||||||
javap -cp app.jar -version
|
docker inspect qaup-app | grep -i java
|
||||||
|
|
||||||
# 3. 如果版本不匹配,重新编译项目
|
|
||||||
mvn clean package -DskipTests
|
|
||||||
|
|
||||||
# 4. 确认Docker镜像使用Java 21
|
|
||||||
grep "eclipse-temurin.*21" docker-compose.yml
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 5.4.2 数据库连接问题
|
## 7. 数据备份
|
||||||
|
|
||||||
如果迁移失败:
|
```bash
|
||||||
|
# 数据库备份
|
||||||
|
docker exec qaup-postgres pg_dump -U qaup qaup > backup-$(date +%Y%m%d).sql
|
||||||
|
|
||||||
|
# 日志轮转(保留最近7天)
|
||||||
|
find ~/qaup-deploy/logs -name "*.log" -mtime +7 -delete
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 常见问题
|
||||||
|
|
||||||
|
### 应用启动失败
|
||||||
|
```bash
|
||||||
|
# 查看详细日志
|
||||||
|
docker compose logs qaup-app
|
||||||
|
|
||||||
|
# 检查数据库连接
|
||||||
|
docker compose logs qaup-postgres
|
||||||
|
docker exec qaup-postgres pg_isready -U qaup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 端口被占用
|
||||||
|
```bash
|
||||||
|
# 检查端口使用情况
|
||||||
|
netstat -tlnp | grep :8080
|
||||||
|
|
||||||
|
# 停止占用端口的进程
|
||||||
|
sudo kill -9 $(lsof -t -i:8080)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据库连接失败
|
||||||
```bash
|
```bash
|
||||||
# 检查数据库服务
|
# 检查数据库服务
|
||||||
docker compose ps
|
docker compose ps
|
||||||
docker exec qaup-postgres pg_isready -U qaup
|
docker exec qaup-postgres pg_isready -U qaup
|
||||||
```
|
```
|
||||||
|
|
||||||
## 9. 快速操作指南
|
|
||||||
|
|
||||||
### 9.1 开发环境快速部署
|
|
||||||
```bash
|
|
||||||
# 克隆项目并编译
|
|
||||||
git clone <your-repo-url>
|
|
||||||
cd QAUP-Management
|
|
||||||
mvn clean package -DskipTests
|
|
||||||
|
|
||||||
# 使用部署脚本打包
|
|
||||||
./deploy/package-all.sh
|
|
||||||
|
|
||||||
# 解压并部署
|
|
||||||
tar -xzf qaup-deploy.tar.gz
|
|
||||||
cd qaup-deploy
|
|
||||||
./deploy-all.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.2 生产环境升级
|
|
||||||
```bash
|
|
||||||
cd qaup-deploy
|
|
||||||
|
|
||||||
# 复制新版本
|
|
||||||
cp /path/to/new/qaup-admin.jar ./new-app.jar
|
|
||||||
|
|
||||||
# 执行自动升级
|
|
||||||
./deploy-update.sh
|
|
||||||
|
|
||||||
# 验证升级结果
|
|
||||||
curl http://localhost:8080/actuator/health
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.3 日常运维检查
|
|
||||||
```bash
|
|
||||||
cd qaup-deploy
|
|
||||||
|
|
||||||
# 检查服务状态
|
|
||||||
docker compose ps
|
|
||||||
|
|
||||||
# 查看最近日志
|
|
||||||
docker compose logs --tail=50 qaup-app
|
|
||||||
|
|
||||||
# 检查数据库迁移状态
|
|
||||||
docker exec -it qaup-postgres psql -U qaup -d qaup -c "SELECT version, description, installed_on FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 3;"
|
|
||||||
|
|
||||||
# 系统健康检查
|
|
||||||
curl -s http://localhost:8080/actuator/health | jq
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.4 缓存优化
|
|
||||||
```bash
|
|
||||||
# 监控Redis内存使用
|
|
||||||
docker exec -it qaup-redis redis-cli INFO memory
|
|
||||||
|
|
||||||
# 清理过期缓存
|
|
||||||
docker exec -it qaup-redis redis-cli FLUSHDB
|
|
||||||
```
|
|
||||||
|
|
||||||
## 10. 现代化部署优势
|
|
||||||
|
|
||||||
### 10.1 核心特性
|
|
||||||
✅ **极简操作**: 解压 → 运行脚本,2步完成部署
|
|
||||||
✅ **自动迁移**: Flyway处理数据库版本管理,无需手动SQL
|
|
||||||
✅ **零停机升级**: 只重启应用服务,数据库持续运行
|
|
||||||
✅ **PostGIS支持**: 内置地理空间数据处理能力
|
|
||||||
✅ **健康监控**: 自动化服务依赖检查和状态监控
|
|
||||||
✅ **离线部署**: 预打包Docker镜像,无需互联网
|
|
||||||
✅ **一键回滚**: 升级失败自动回滚到上一版本
|
|
||||||
✅ **版本兼容**: Java 21运行时,确保版本一致性
|
|
||||||
|
|
||||||
### 10.2 与传统方案对比
|
|
||||||
|
|
||||||
| 特性 | 传统方案 | 现代化方案 |
|
|
||||||
|------|----------|------------|
|
|
||||||
| 部署步骤 | 手动配置SQL、修改配置、重启服务 | 解压脚本、自动迁移、一键完成 |
|
|
||||||
| 数据库迁移 | 手动执行SQL脚本,易出错 | Flyway自动版本管理 |
|
|
||||||
| 停机时间 | 需要停机维护 | 零停机升级 |
|
|
||||||
| 回滚复杂度 | 手动恢复数据库和配置 | 自动回滚机制 |
|
|
||||||
| 环境一致性 | 容易出现环境差异 | 容器化确保一致性 |
|
|
||||||
| 地理空间 | 需要额外安装PostGIS | 内置PostGIS支持 |
|
|
||||||
|
|
||||||
### 10.3 技术架构
|
|
||||||
- **运行时**: Java 21 + Spring Boot 3
|
|
||||||
- **数据库**: PostgreSQL 17 + PostGIS 3.5
|
|
||||||
- **缓存**: Redis 8
|
|
||||||
- **容器化**: Docker + Docker Compose
|
|
||||||
- **迁移工具**: Flyway自动化版本管理
|
|
||||||
- **监控**: Spring Boot Actuator + 健康检查
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 支持和帮助
|
|
||||||
|
|
||||||
如遇到问题,请检查:
|
|
||||||
1. 系统要求和依赖软件版本
|
|
||||||
2. Docker和Docker Compose安装状态
|
|
||||||
3. 网络连接和防火墙配置
|
|
||||||
4. 磁盘空间和文件权限
|
|
||||||
5. 应用日志和数据库日志
|
|
||||||
|
|
||||||
更多详细信息请参考:
|
|
||||||
- [deploy-design.md](./deploy-design.md) - 完整部署设计文档
|
|
||||||
- [environment.md](./environment.md) - 环境配置指南
|
|
||||||
- [ADXP_WebSocket.md](./ADXP_WebSocket.md) - WebSocket服务说明
|
|
||||||
142
doc/deploy/database_migration.md
Normal file
142
doc/deploy/database_migration.md
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Java版本兼容性注意事项
|
||||||
|
|
||||||
|
如果在执行数据库迁移后更新应用,需要注意Java版本兼容性:
|
||||||
|
|
||||||
|
### 版本匹配检查
|
||||||
|
- **编译版本**: 确保应用jar文件在Java 21环境下编译
|
||||||
|
- **运行版本**: Docker容器中运行Java 21
|
||||||
|
|
||||||
|
### 版本不匹配错误示例
|
||||||
|
如果遇到 `UnsupportedClassVersionError` 错误:
|
||||||
|
```
|
||||||
|
class file has been compiled by a more recent version of the Java Runtime
|
||||||
|
(class file version 65.0), this version of the Java Runtime only recognizes
|
||||||
|
class file versions up to 61.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
1. 确认Docker镜像使用Java 21:
|
||||||
|
```yaml
|
||||||
|
image: eclipse-temurin:21-jre
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 检查容器Java版本:
|
||||||
|
```bash
|
||||||
|
docker exec <container-name> java -version
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**创建时间:** 2025-09-25
|
||||||
|
**适用版本:** PostgreSQL 12+
|
||||||
|
**测试环境:** QAUP-Management 1.0.1
|
||||||
@ -1,6 +1,5 @@
|
|||||||
# QAUP 极简离线部署方案
|
# QAUP 极简离线部署方案
|
||||||
|
Spring Boot + Redis + PostgreSQL(一键部署、一键升级)
|
||||||
Spring Boot + Redis + PostgreSQL with PostGIS(一键部署、一键升级)
|
|
||||||
|
|
||||||
## 1. 设计原则
|
## 1. 设计原则
|
||||||
|
|
||||||
@ -8,151 +7,103 @@ Spring Boot + Redis + PostgreSQL with PostGIS(一键部署、一键升级)
|
|||||||
- **离线部署**:预打包所有依赖,无需联网
|
- **离线部署**:预打包所有依赖,无需联网
|
||||||
- **一键操作**:部署和升级都是一条命令
|
- **一键操作**:部署和升级都是一条命令
|
||||||
- **配置灵活**:关键配置可外部修改
|
- **配置灵活**:关键配置可外部修改
|
||||||
- **自动化迁移**:数据库版本自动管理(Flyway)
|
|
||||||
|
|
||||||
## 2. 打包准备(开发环境执行一次)
|
## 2. 打包准备(开发环境执行一次)
|
||||||
|
|
||||||
### 2.1 打包脚本(package-all.sh)
|
### 2.1 打包脚本(package.sh)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
echo "=== QAUP 打包脚本 ==="
|
echo "=== QAUP 打包脚本 ==="
|
||||||
|
|
||||||
# 1. 构建应用(在macOS上)
|
# 1. 构建应用
|
||||||
mvn clean package -DskipTests
|
mvn clean package -DskipTests
|
||||||
|
|
||||||
# 2. 拉取并导出镜像(在服务器上)
|
# 2. 拉取并导出镜像
|
||||||
docker pull m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine
|
docker pull openjdk:21-jre-alpine
|
||||||
docker pull m.daocloud.io/docker.io/library/redis:8.0-alpine
|
docker pull redis:8-alpine
|
||||||
docker pull m.daocloud.io/docker.io/library/eclipse-temurin:21-jre
|
docker pull postgres:17-3.5-alpine
|
||||||
|
docker save openjdk:21-jre-alpine redis:8-alpine postgres:17-3.5-alpine | gzip > images.tar.gz
|
||||||
# 导出镜像
|
|
||||||
docker save m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine \
|
|
||||||
m.daocloud.io/docker.io/library/redis:8.0-alpine \
|
|
||||||
m.daocloud.io/docker.io/library/eclipse-temurin:21-jre | gzip > images.tar.gz
|
|
||||||
|
|
||||||
# 3. 准备部署包
|
# 3. 准备部署包
|
||||||
mkdir -p qaup-deploy
|
mkdir -p qaup-deploy
|
||||||
cp qaup-admin/target/qaup-admin.jar qaup-deploy/app.jar
|
cp qaup-admin/target/qaup-admin.jar qaup-deploy/app.jar
|
||||||
cp deploy/docker-compose.yml qaup-deploy/
|
cp deploy/simple/* qaup-deploy/
|
||||||
cp deploy/config.yml qaup-deploy/
|
|
||||||
cp images.tar.gz qaup-deploy/
|
cp images.tar.gz qaup-deploy/
|
||||||
|
|
||||||
# 4. 打包
|
# 4. 打包
|
||||||
tar -czf qaup-deploy-$(date +%Y%m%d-%H%M%S).tar.gz -C qaup-deploy .
|
cd qaup-deploy && zip -r ../qaup-deploy-$(date +%Y%m%d).zip .
|
||||||
echo "打包完成: qaup-deploy-$(date +%Y%m%d-%H%M%S).tar.gz"
|
echo "打包完成: qaup-deploy-$(date +%Y%m%d).zip"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.2 部署包结构
|
### 2.2 部署包结构(极简版)
|
||||||
|
|
||||||
```
|
```
|
||||||
qaup-deploy.tar.gz
|
qaup-deploy.zip
|
||||||
├── images.tar.gz # Docker镜像包(PostGIS + Redis + Java21)
|
├── images.tar.gz # Docker镜像包
|
||||||
├── docker-compose.yml # 服务编排(包含健康检查)
|
├── docker-compose.yml # 服务编排
|
||||||
├── app.jar # 应用程序
|
├── app.jar # 应用程序
|
||||||
├── config.yml # 外部配置(可修改)
|
├── config.yml # 外部配置(可修改)
|
||||||
├── deploy-all.sh # 一键部署脚本
|
├── init.sql # 数据库初始化脚本
|
||||||
├── deploy-update.sh # 一键升级脚本
|
├── deploy.sh # 一键部署
|
||||||
├── DeployGuide.md # 详细部署指南
|
└── update.sh # 一键升级
|
||||||
└── qaup_database_export.sql # 完整数据库备份(可选)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 3. docker-compose.yml(当前实际版本)
|
## 3. docker-compose.yml(极简版)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
# PostgreSQL + PostGIS 数据库服务
|
app:
|
||||||
qaup-postgres:
|
image: openjdk:21-jre-alpine
|
||||||
image: m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine
|
container_name: qaup-app
|
||||||
container_name: qaup-postgres
|
restart: always
|
||||||
restart: unless-stopped
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- ./app.jar:/app.jar
|
||||||
|
- ./config.yml:/config.yml
|
||||||
|
command: ["java", "-jar", "/app.jar", "--spring.config.location=/config.yml"]
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- redis
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:8-alpine
|
||||||
|
container_name: qaup-redis
|
||||||
|
restart: always
|
||||||
|
command: ["redis-server", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:17-3.5-alpine
|
||||||
|
container_name: qaup-db
|
||||||
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: qaup
|
POSTGRES_DB: qaup
|
||||||
POSTGRES_USER: qaup
|
POSTGRES_USER: qaup
|
||||||
POSTGRES_PASSWORD: qaup123
|
POSTGRES_PASSWORD: qaup123
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/postgres:/var/lib/postgresql/data
|
- ./data:/var/lib/postgresql/data
|
||||||
ports:
|
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||||
- "5432:5432"
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U qaup"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
# Redis 缓存服务
|
|
||||||
qaup-redis:
|
|
||||||
image: m.daocloud.io/docker.io/library/redis:8.0-alpine
|
|
||||||
container_name: qaup-redis
|
|
||||||
restart: unless-stopped
|
|
||||||
command: ["redis-server", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
|
|
||||||
volumes:
|
|
||||||
- ./data/redis:/data
|
|
||||||
ports:
|
|
||||||
- "6379:6379"
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
# QAUP 应用服务(Java 21)
|
|
||||||
qaup-app:
|
|
||||||
image: m.daocloud.io/docker.io/library/eclipse-temurin:21-jre
|
|
||||||
container_name: qaup-app
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
SPRING_PROFILES_ACTIVE: prod
|
|
||||||
LOG_PATH: /app/logs
|
|
||||||
# 数据库连接配置
|
|
||||||
DB_HOST: qaup-postgres
|
|
||||||
DB_PORT: 5432
|
|
||||||
DB_NAME: qaup
|
|
||||||
DB_USER: qaup
|
|
||||||
DB_PASSWORD: qaup123
|
|
||||||
# Flyway配置(自动化数据库迁移)
|
|
||||||
SPRING_FLYWAY_ENABLED: true
|
|
||||||
SPRING_FLYWAY_BASELINE_ON_MIGRATE: true
|
|
||||||
SPRING_FLYWAY_VALIDATE_ON_MIGRATE: true
|
|
||||||
SPRING_FLYWAY_CLEAN_DISABLED: true
|
|
||||||
volumes:
|
|
||||||
- ./app.jar:/app/app.jar
|
|
||||||
- ./config.yml:/app/config.yml
|
|
||||||
- ./logs:/app/logs
|
|
||||||
- ./backup:/app/backup
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
depends_on:
|
|
||||||
qaup-postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
qaup-redis:
|
|
||||||
condition: service_healthy
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 5
|
|
||||||
start_period: 120s
|
|
||||||
command: [
|
|
||||||
"sh", "-c",
|
|
||||||
"echo 'Waiting for database to be ready...' &&
|
|
||||||
sleep 10 &&
|
|
||||||
echo 'Starting QAUP application with Flyway migration...' &&
|
|
||||||
java -jar /app/app.jar --spring.config.location=/app/config.yml"
|
|
||||||
]
|
|
||||||
|
|
||||||
networks:
|
|
||||||
default:
|
|
||||||
name: qaup-network
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 4. 外部配置文件(config.yml)
|
## 4. 外部配置文件(config.yml)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# 服务器配置
|
# 服务器配置
|
||||||
server:
|
server:
|
||||||
port: 8080
|
port: 8080
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://qaup-db:5432/qaup
|
||||||
|
username: qaup
|
||||||
|
password: qaup123
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
|
# Redis配置
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: qaup-redis
|
||||||
|
port: 6379
|
||||||
|
database: 0
|
||||||
|
|
||||||
# 应用配置
|
# 应用配置
|
||||||
qaup:
|
qaup:
|
||||||
# 文件上传路径
|
# 文件上传路径
|
||||||
@ -162,101 +113,64 @@ qaup:
|
|||||||
api-host: 192.168.1.100
|
api-host: 192.168.1.100
|
||||||
api-port: 8090
|
api-port: 8090
|
||||||
|
|
||||||
# 日志配置
|
# 日志配置(简化)
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
com.qaup: INFO
|
com.qaup: info
|
||||||
pattern:
|
pattern:
|
||||||
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
console: "%d{HH:mm:ss} %-5level %logger{36} - %msg%n"
|
||||||
file:
|
|
||||||
name: /app/logs/qaup.log
|
|
||||||
|
|
||||||
# 注意:数据库和Redis配置通过环境变量设置
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. 自动化数据库迁移(Flyway)
|
## 5. 数据库初始化(init.sql)
|
||||||
|
```sql
|
||||||
|
-- 创建基础表结构
|
||||||
|
CREATE TABLE IF NOT EXISTS sys_user (
|
||||||
|
user_id SERIAL PRIMARY KEY,
|
||||||
|
user_name VARCHAR(30) NOT NULL,
|
||||||
|
nick_name VARCHAR(30) NOT NULL,
|
||||||
|
password VARCHAR(100) DEFAULT '',
|
||||||
|
status CHAR(1) DEFAULT '0',
|
||||||
|
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
### 5.1 迁移脚本位置
|
-- 插入默认管理员
|
||||||
|
INSERT INTO sys_user (user_name, nick_name, password)
|
||||||
|
VALUES ('admin', '管理员', '$2a$10$7JB720yubVSOfvam/RtmyO4wV/hOoLsKpHpJI.VqSb/VXaVzqQqhu')
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
```
|
-- 其他标准数据...
|
||||||
qaup-admin/src/main/resources/db/migration/
|
|
||||||
├── V1.0.0__Initial_baseline.sql # 数据库基线结构(86KB)
|
|
||||||
├── V1.0.1__Initial_data.sql # 初始数据(50KB)
|
|
||||||
└── README.md # 迁移脚本编写规范
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5.2 自动迁移机制
|
## 6. 一键部署(deploy.sh)
|
||||||
|
|
||||||
- **应用启动时自动执行**:无需手动运行SQL脚本
|
|
||||||
- **版本控制**:所有迁移按版本顺序执行
|
|
||||||
- **幂等性**:安全重复执行,不会重复创建对象
|
|
||||||
- **健康检查**:依赖数据库健康状态启动应用
|
|
||||||
|
|
||||||
### 5.3 迁移状态查询
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 查看应用日志中的迁移信息
|
|
||||||
docker compose logs qaup-app | grep -i flyway
|
|
||||||
|
|
||||||
# 直接查询数据库
|
|
||||||
docker exec -it qaup-postgres psql -U qaup -d qaup -c "SELECT * FROM flyway_schema_history ORDER BY installed_rank;"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. 一键部署(deploy-all.sh)
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
echo "=== QAUP 一键部署 ==="
|
echo "=== QAUP 一键部署 ==="
|
||||||
|
|
||||||
# 检查Docker
|
|
||||||
if ! command -v docker &> /dev/null; then
|
|
||||||
echo "❌ Docker 未安装"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 载入镜像
|
# 载入镜像
|
||||||
echo "载入Docker镜像..."
|
echo "载入Docker镜像..."
|
||||||
docker load -i images.tar.gz
|
docker load -i images.tar.gz
|
||||||
|
|
||||||
# 创建数据目录
|
# 创建数据目录
|
||||||
echo "创建数据目录..."
|
mkdir -p data
|
||||||
mkdir -p data/postgres data/redis logs
|
|
||||||
|
|
||||||
# 启动服务
|
# 启动服务
|
||||||
echo "启动服务..."
|
echo "启动服务..."
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
# 等待服务启动
|
# 等待启动
|
||||||
echo "等待服务启动(60秒)..."
|
echo "等待服务启动(30秒)..."
|
||||||
sleep 60
|
sleep 30
|
||||||
|
|
||||||
# 检查服务状态
|
echo "✅ 部署完成!访问: http://localhost:8080"
|
||||||
if curl -f -s http://localhost:8080/actuator/health > /dev/null 2>&1; then
|
echo "默认账号: admin / admin123"
|
||||||
echo "✅ 部署成功!"
|
|
||||||
echo ""
|
|
||||||
echo "访问地址: http://localhost:8080"
|
|
||||||
echo "数据库: localhost:5432 (qaup/qaup123)"
|
|
||||||
echo "Redis: localhost:6379"
|
|
||||||
echo ""
|
|
||||||
echo "管理命令:"
|
|
||||||
echo " 查看状态: docker compose ps"
|
|
||||||
echo " 查看日志: docker compose logs -f qaup-app"
|
|
||||||
echo " 停止服务: docker compose down"
|
|
||||||
echo " 升级应用: ./deploy-update.sh"
|
|
||||||
else
|
|
||||||
echo "❌ 服务启动失败,请检查日志:"
|
|
||||||
docker compose logs
|
|
||||||
fi
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 7. 客户部署(1条命令)
|
## 7. 客户部署(1条命令)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tar -xzf qaup-deploy.tar.gz && cd qaup-deploy && ./deploy-all.sh
|
unzip qaup-deploy.zip && cd qaup-deploy && ./deploy.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## 8. 一键升级(deploy-update.sh)
|
## 8. 一键升级(update.sh)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
echo "=== QAUP 一键升级 ==="
|
echo "=== QAUP 一键升级 ==="
|
||||||
@ -272,190 +186,86 @@ echo "验证Java版本兼容性..."
|
|||||||
if command -v java &> /dev/null; then
|
if command -v java &> /dev/null; then
|
||||||
LOCAL_JAVA_VERSION=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
|
LOCAL_JAVA_VERSION=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
|
||||||
echo "本地Java版本: $LOCAL_JAVA_VERSION"
|
echo "本地Java版本: $LOCAL_JAVA_VERSION"
|
||||||
echo "容器Java版本: eclipse-temurin:21-jre"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 备份当前版本
|
# 备份当前版本
|
||||||
echo "备份当前版本..."
|
cp app.jar app.jar.backup
|
||||||
cp app.jar app.jar.backup.$(date +%Y%m%d_%H%M%S)
|
echo "已备份当前版本"
|
||||||
|
|
||||||
# 数据库备份(重要)
|
# 停止应用
|
||||||
echo "备份数据库..."
|
|
||||||
docker exec qaup-postgres pg_dump -U qaup qaup > backup/qaup-backup-$(date +%Y%m%d_%H%M%S).sql
|
|
||||||
|
|
||||||
# 停止应用(不影响数据库)
|
|
||||||
echo "停止应用服务..."
|
echo "停止应用服务..."
|
||||||
docker compose stop qaup-app
|
docker compose stop app
|
||||||
|
|
||||||
# 替换应用
|
# 替换应用
|
||||||
cp new-app.jar app.jar
|
cp new-app.jar app.jar
|
||||||
echo "已更新应用文件"
|
echo "已更新应用文件"
|
||||||
|
|
||||||
# 启动应用(Flyway自动处理数据库迁移)
|
# 启动应用
|
||||||
echo "启动应用服务..."
|
echo "启动应用服务..."
|
||||||
docker compose start qaup-app
|
docker compose start app
|
||||||
|
|
||||||
# 等待启动和迁移完成
|
# 等待启动
|
||||||
echo "等待应用启动和数据库迁移(120秒)..."
|
echo "等待服务启动(30秒)..."
|
||||||
sleep 120
|
sleep 30
|
||||||
|
|
||||||
# 检查升级结果
|
echo "✅ 升级完成!"
|
||||||
if curl -f -s http://localhost:8080/actuator/health > /dev/null 2>&1; then
|
echo "如有问题,可执行回滚: cp app.jar.backup app.jar && docker compose restart app"
|
||||||
echo "✅ 升级成功!"
|
|
||||||
echo ""
|
|
||||||
echo "Flyway迁移状态:"
|
|
||||||
docker exec -it qaup-postgres psql -U qaup -d qaup -c "SELECT version, description, installed_rank FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 5;"
|
|
||||||
else
|
|
||||||
echo "❌ 升级失败,开始回滚..."
|
|
||||||
# 恢复jar文件
|
|
||||||
LATEST_BACKUP=$(ls -t app.jar.backup.* 2>/dev/null | head -1)
|
|
||||||
if [ -n "$LATEST_BACKUP" ]; then
|
|
||||||
cp "$LATEST_BACKUP" app.jar
|
|
||||||
docker compose restart qaup-app
|
|
||||||
echo "✅ 已回滚到版本: $LATEST_BACKUP"
|
|
||||||
else
|
|
||||||
echo "❌ 未找到备份文件,请手动处理"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 9. Java版本兼容性注意事项
|
## 9. Java版本兼容性注意事项
|
||||||
|
|
||||||
### 9.1 编译与运行环境匹配
|
### 9.1 编译与运行环境匹配
|
||||||
|
|
||||||
- **编译环境**: 确保项目在Java 21环境下编译
|
- **编译环境**: 确保项目在Java 21环境下编译
|
||||||
- **运行环境**: Docker镜像使用 `eclipse-temurin:21-jre`
|
- **运行环境**: Docker镜像必须与编译版本匹配(`openjdk:21-jre-alpine` 或 `eclipse-temurin:21-jre`)
|
||||||
|
|
||||||
### 9.2 常见版本错误
|
### 9.2 常见版本错误
|
||||||
|
|
||||||
如果遇到 `UnsupportedClassVersionError`,确认以下配置:
|
如果遇到 `UnsupportedClassVersionError`,确认以下配置:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# docker-compose.yml 中必须使用Java 21镜像
|
# docker-compose.yml 中必须使用Java 21镜像
|
||||||
services:
|
services:
|
||||||
qaup-app:
|
app:
|
||||||
|
# 错误示例(可能导致版本不匹配)
|
||||||
|
# image: openjdk:17-jre-alpine
|
||||||
|
|
||||||
# 正确示例
|
# 正确示例
|
||||||
image: m.daocloud.io/docker.io/library/eclipse-temurin:21-jre
|
image: openjdk:21-jre-alpine
|
||||||
```
|
```
|
||||||
|
|
||||||
### 9.3 版本验证命令
|
### 9.3 版本验证命令
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 检查容器中Java版本
|
# 检查容器中Java版本
|
||||||
docker exec qaup-app java -version
|
docker exec <container-name> java -version
|
||||||
|
|
||||||
# 检查jar文件编译版本
|
# 检查jar文件编译版本
|
||||||
javap -cp app.jar -version
|
javap -cp <jar-file> -version <main-class>
|
||||||
|
|
||||||
# 快速健康检查
|
|
||||||
curl http://localhost:8080/actuator/health
|
|
||||||
```
|
```
|
||||||
|
## 9. 操作总结
|
||||||
## 10. 系统维护操作
|
|
||||||
|
|
||||||
### 10.1 日常运维命令
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 查看服务状态
|
|
||||||
docker compose ps
|
|
||||||
|
|
||||||
# 查看应用日志
|
|
||||||
docker compose logs -f qaup-app
|
|
||||||
|
|
||||||
# 查看数据库日志
|
|
||||||
docker compose logs qaup-postgres
|
|
||||||
|
|
||||||
# 查看Redis日志
|
|
||||||
docker compose logs qaup-redis
|
|
||||||
|
|
||||||
# 查看系统资源使用
|
|
||||||
docker stats
|
|
||||||
|
|
||||||
# 数据库连接测试
|
|
||||||
docker exec -it qaup-postgres psql -U qaup -d qaup -c "SELECT version();"
|
|
||||||
|
|
||||||
# Redis连接测试
|
|
||||||
docker exec -it qaup-redis redis-cli ping
|
|
||||||
```
|
|
||||||
|
|
||||||
### 10.2 监控数据库迁移状态
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 查看Flyway迁移历史
|
|
||||||
docker exec -it qaup-postgres psql -U qaup -d qaup -c "SELECT * FROM flyway_schema_history ORDER BY installed_rank;"
|
|
||||||
|
|
||||||
# 查看应用日志中的迁移信息
|
|
||||||
docker compose logs qaup-app | grep -i flyway
|
|
||||||
|
|
||||||
# 查看最近的迁移记录
|
|
||||||
docker exec -it qaup-postgres psql -U qaup -d qaup -c "SELECT version, description, installed_on FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 3;"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 10.3 数据备份和恢复
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 手动备份数据库
|
|
||||||
docker exec qaup-postgres pg_dump -U qaup qaup > backup/manual-backup-$(date +%Y%m%d_%H%M%S).sql
|
|
||||||
|
|
||||||
# 恢复数据库(需要停止应用)
|
|
||||||
docker compose stop qaup-app
|
|
||||||
docker exec -i qaup-postgres psql -U qaup qaup < backup/qaup-backup-20250120_143000.sql
|
|
||||||
docker compose start qaup-app
|
|
||||||
```
|
|
||||||
|
|
||||||
### 10.4 完全重置(开发测试用)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 停止所有服务
|
|
||||||
docker compose down
|
|
||||||
|
|
||||||
# 删除数据目录(⚠️ 注意:这会删除所有数据)
|
|
||||||
rm -rf data/
|
|
||||||
|
|
||||||
# 重新部署
|
|
||||||
./deploy-all.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## 11. 操作总结
|
|
||||||
|
|
||||||
### 开发环境(一次性)
|
### 开发环境(一次性)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 在macOS上构建
|
./package.sh # 打包部署文件
|
||||||
mvn clean package -DskipTests
|
|
||||||
./deploy/package-all.sh
|
|
||||||
|
|
||||||
# 或在服务器上打包
|
|
||||||
./deploy/package-all.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 客户环境
|
### 客户环境
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 首次部署
|
# 首次部署
|
||||||
tar -xzf qaup-deploy.tar.gz && cd qaup-deploy && ./deploy-all.sh
|
unzip qaup-deploy.zip && cd qaup-deploy && ./deploy.sh
|
||||||
|
|
||||||
# 程序更新
|
# 日常升级
|
||||||
# 1. 复制新jar文件并重命名为 new-app.jar
|
./update.sh
|
||||||
# 2. 执行更新
|
|
||||||
./deploy-update.sh
|
|
||||||
|
|
||||||
# 查看状态
|
# 查看状态
|
||||||
docker compose ps
|
docker compose ps
|
||||||
|
|
||||||
# 查看日志
|
# 查看日志
|
||||||
docker compose logs -f qaup-app
|
docker compose logs -f app
|
||||||
```
|
```
|
||||||
|
|
||||||
## 12. 现代化优势总结
|
## 10. 优势总结
|
||||||
|
|
||||||
✅ **极简部署**:解压 → 运行脚本,2步完成
|
✅ **极简部署**:解压 → 运行脚本,2步完成
|
||||||
✅ **极简升级**:替换jar → 运行脚本,自动完成
|
✅ **极简升级**:替换jar → 运行脚本,自动完成
|
||||||
✅ **离线友好**:所有依赖预打包,无需联网
|
✅ **离线友好**:所有依赖预打包,无需联网
|
||||||
✅ **配置灵活**:关键配置外部文件,可随时修改
|
✅ **配置灵活**:关键配置外部文件,可随时修改
|
||||||
✅ **自动化迁移**:Flyway自动处理数据库版本管理
|
✅ **自动初始化**:数据库结构和标准数据自动创建
|
||||||
✅ **健康检查**:服务依赖和健康状态监控
|
✅ **一键回滚**:升级失败可快速回滚到上一版本
|
||||||
✅ **PostGIS支持**:内置地理空间数据库能力
|
|
||||||
✅ **一键回滚**:升级失败可快速回滚到上一版本
|
|
||||||
✅ **零停机升级**:只重启应用服务,数据库保持运行
|
|
||||||
✅ **版本兼容**:Java 21运行时,确保版本一致性
|
|
||||||
|
|||||||
@ -1,696 +0,0 @@
|
|||||||
# QAUP系统部署指南
|
|
||||||
|
|
||||||
本文档提供了QAUP系统的完整部署指南,包括环境配置、服务启动、监控和故障排除。
|
|
||||||
|
|
||||||
## 目录结构
|
|
||||||
|
|
||||||
- [1. 系统架构概述](#1-系统架构概述)
|
|
||||||
- [2. 环境准备](#2-环境准备)
|
|
||||||
- [3. 配置管理](#3-配置管理)
|
|
||||||
- [4. 服务部署](#4-服务部署)
|
|
||||||
- [5. 监控与维护](#6-监控与维护)
|
|
||||||
- [6. 故障排除](#7-故障排除)
|
|
||||||
- [7. 性能优化](#8-性能优化)
|
|
||||||
|
|
||||||
## 1. 系统架构概述
|
|
||||||
|
|
||||||
QAUP系统采用微服务架构,主要组件包括:
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ 前端界面 │ │ 后端服务 │ │ ADXP适配器 │
|
|
||||||
│ (qaup-ui) │◄──►│ (qaup-collision) │◄──►│ (adxp-adapter) │
|
|
||||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
||||||
│ │ │
|
|
||||||
│ ▼ │
|
|
||||||
│ ┌─────────────────┐ │
|
|
||||||
│ │ 外部接口 │ │
|
|
||||||
│ │ (地图、车辆) │ │
|
|
||||||
│ └─────────────────┘ │
|
|
||||||
│ │ │
|
|
||||||
└───────────────────────┼───────────────────────┘
|
|
||||||
▼
|
|
||||||
┌─────────────────┐ ┌─────────────────┐
|
|
||||||
│ 数据存储 │ │ 缓存服务 │
|
|
||||||
│ (PostgreSQL) │ │ (Redis) │
|
|
||||||
└─────────────────┘ └─────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### 核心服务
|
|
||||||
|
|
||||||
- **qaup-collision**: 核心碰撞检测和数据处理服务
|
|
||||||
- **adxp-adapter**: ADXP数据适配器,负责与机场系统通信
|
|
||||||
- **qaup-admin**: 管理后台服务
|
|
||||||
- **qaup-ui**: 前端用户界面
|
|
||||||
|
|
||||||
## 2. 环境准备
|
|
||||||
|
|
||||||
### 2.1 系统要求
|
|
||||||
|
|
||||||
#### 最低配置
|
|
||||||
|
|
||||||
- **操作系统**: Linux (Ubuntu 20.04+) / Windows Server 2019+
|
|
||||||
- **内存**: 8GB RAM
|
|
||||||
- **存储**: 50GB SSD
|
|
||||||
- **网络**: 千兆以太网
|
|
||||||
|
|
||||||
#### 推荐配置
|
|
||||||
|
|
||||||
- **操作系统**: Linux (Ubuntu 22.04 LTS)
|
|
||||||
- **内存**: 16GB RAM 或更高
|
|
||||||
- **存储**: 100GB SSD
|
|
||||||
- **网络**: 千兆以太网
|
|
||||||
|
|
||||||
### 2.2 软件依赖
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 必需软件版本
|
|
||||||
- Docker: 24.0+
|
|
||||||
- Docker Compose: 2.20+
|
|
||||||
- JDK: 21+ (OpenJDK 推荐)
|
|
||||||
- Maven: 3.8+
|
|
||||||
- Node.js: 18+ (用于前端构建)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.3 端口分配
|
|
||||||
|
|
||||||
| 服务 | 端口 | 描述 |
|
|
||||||
|-----|------|------|
|
|
||||||
| qaup-collision | 8080 | 核心服务API |
|
|
||||||
| qaup-admin | 8081 | 管理后台API |
|
|
||||||
| qaup-ui | 80/443 | 前端界面 |
|
|
||||||
| adxp-adapter | 8086 | ADXP适配器 |
|
|
||||||
| PostgreSQL | 5432 | 数据库服务 |
|
|
||||||
| Redis | 6379 | 缓存服务 |
|
|
||||||
|
|
||||||
## 3. 配置管理
|
|
||||||
|
|
||||||
### 3.1 环境变量配置
|
|
||||||
|
|
||||||
在项目根目录创建 `.env` 文件:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# ============================================
|
|
||||||
# QAUP系统环境变量配置
|
|
||||||
# ============================================
|
|
||||||
|
|
||||||
# 数据库配置
|
|
||||||
DB_HOST=localhost
|
|
||||||
DB_PORT=5432
|
|
||||||
DB_NAME=qaup
|
|
||||||
DB_USERNAME=qaup
|
|
||||||
DB_PASSWORD=qaup123
|
|
||||||
|
|
||||||
# Redis配置
|
|
||||||
REDIS_HOST=localhost
|
|
||||||
REDIS_PORT=6379
|
|
||||||
REDIS_PASSWORD=
|
|
||||||
|
|
||||||
# 应用服务配置
|
|
||||||
SERVER_PORT=8080
|
|
||||||
ADMIN_PORT=8081
|
|
||||||
ADAPTER_PORT=8086
|
|
||||||
|
|
||||||
# ADXP适配器配置
|
|
||||||
ADXP_SERVER_URL=http://localhost:8090
|
|
||||||
ADXP_USERNAME=admin
|
|
||||||
ADXP_PASSWORD=admin123
|
|
||||||
ADXP_ENABLED=true
|
|
||||||
|
|
||||||
# 日志配置
|
|
||||||
LOG_LEVEL=INFO
|
|
||||||
LOG_PATH=/app/logs
|
|
||||||
|
|
||||||
# 数据采集配置
|
|
||||||
DATA_COLLECTOR_INTERVAL=250
|
|
||||||
DATA_COLLECTOR_ENABLED=true
|
|
||||||
|
|
||||||
# 外部服务配置
|
|
||||||
AIRPORT_VEHICLE_API_URL=http://10.32.38.3:8090
|
|
||||||
MAP_SERVICE_URL=http://221.215.103.144:8090/iserver/services/map-QDJC_DT-GX3/rest/maps
|
|
||||||
|
|
||||||
# 安全配置
|
|
||||||
JWT_SECRET=your-jwt-secret-key-here
|
|
||||||
ENCRYPT_KEY=your-encryption-key-here
|
|
||||||
|
|
||||||
# 监控配置
|
|
||||||
ACTUATOR_ENABLED=true
|
|
||||||
HEALTH_CHECK_INTERVAL=30
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 配置文件结构
|
|
||||||
|
|
||||||
```
|
|
||||||
config/
|
|
||||||
├── application.yml # 主配置文件
|
|
||||||
├── application-dev.yml # 开发环境配置
|
|
||||||
├── application-test.yml # 测试环境配置
|
|
||||||
├── application-prod.yml # 生产环境配置
|
|
||||||
├── application-druid.yml # 数据库连接池配置
|
|
||||||
└── logback-spring.xml # 日志配置
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 敏感信息管理
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 使用密钥管理敏感配置
|
|
||||||
docker secret create db_password db_password.txt
|
|
||||||
docker secret create jwt_secret jwt_secret.txt
|
|
||||||
|
|
||||||
# 在docker-compose.yml中引用
|
|
||||||
secrets:
|
|
||||||
- db_password
|
|
||||||
- jwt_secret
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. 服务部署
|
|
||||||
|
|
||||||
### 4.1 快速启动
|
|
||||||
|
|
||||||
使用提供的服务管理脚本:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 进入部署目录
|
|
||||||
cd deploy/
|
|
||||||
|
|
||||||
# 启动所有服务
|
|
||||||
./qaup-service.sh start
|
|
||||||
|
|
||||||
# 查看服务状态
|
|
||||||
./qaup-service.sh status
|
|
||||||
|
|
||||||
# 查看服务日志
|
|
||||||
./qaup-service.sh logs qaup-app
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 手动部署步骤
|
|
||||||
|
|
||||||
#### 4.2.1 数据库初始化
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 启动数据库服务
|
|
||||||
docker compose up -d postgres
|
|
||||||
|
|
||||||
# 等待数据库启动
|
|
||||||
docker exec qaup-postgres pg_isready -U qaup
|
|
||||||
|
|
||||||
# 执行数据库迁移
|
|
||||||
docker exec qaup-app java -jar qaup-admin.jar --spring.profiles.active=prod,druid --spring.jpa.hibernate.ddl-auto=update
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4.2.2 应用服务部署
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 构建应用镜像
|
|
||||||
docker compose build qaup-app
|
|
||||||
|
|
||||||
# 启动应用服务
|
|
||||||
docker compose up -d qaup-app
|
|
||||||
|
|
||||||
# 等待应用启动
|
|
||||||
curl http://localhost:8080/actuator/health
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 4.2.3 ADXP适配器部署
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 构建ADXP适配器镜像
|
|
||||||
docker compose -f docker-compose.yml -f docker-compose.adxp.yml build adxp-adapter
|
|
||||||
|
|
||||||
# 启动ADXP适配器
|
|
||||||
docker compose -f docker-compose.yml -f docker-compose.adxp.yml up -d adxp-adapter
|
|
||||||
|
|
||||||
# 检查适配器状态
|
|
||||||
curl http://localhost:8086/health
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.3 Docker Compose配置
|
|
||||||
|
|
||||||
创建 `docker-compose.yml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgis/postgis:15-3.3
|
|
||||||
container_name: qaup-postgres
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: ${DB_NAME:-qaup}
|
|
||||||
POSTGRES_USER: ${DB_USERNAME:-qaup}
|
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD:-qaup123}
|
|
||||||
ports:
|
|
||||||
- "${DB_PORT:-5432}:5432"
|
|
||||||
volumes:
|
|
||||||
- postgres_data:/var/lib/postgresql/data
|
|
||||||
- ./sql/init:/docker-entrypoint-initdb.d
|
|
||||||
networks:
|
|
||||||
- qaup-network
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME:-qaup}"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: redis:7-alpine
|
|
||||||
container_name: qaup-redis
|
|
||||||
ports:
|
|
||||||
- "${REDIS_PORT:-6379}:6379"
|
|
||||||
volumes:
|
|
||||||
- redis_data:/data
|
|
||||||
networks:
|
|
||||||
- qaup-network
|
|
||||||
command: redis-server --appendonly yes
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
qaup-app:
|
|
||||||
build:
|
|
||||||
context: ../qaup-collision
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
container_name: qaup-app
|
|
||||||
environment:
|
|
||||||
SPRING_PROFILES_ACTIVE: prod,druid
|
|
||||||
DB_HOST: postgres
|
|
||||||
REDIS_HOST: redis
|
|
||||||
DB_USERNAME: ${DB_USERNAME}
|
|
||||||
DB_PASSWORD: ${DB_PASSWORD}
|
|
||||||
ports:
|
|
||||||
- "${SERVER_PORT:-8080}:8080"
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
redis:
|
|
||||||
condition: service_healthy
|
|
||||||
networks:
|
|
||||||
- qaup-network
|
|
||||||
volumes:
|
|
||||||
- ./logs:/app/logs
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
postgres_data:
|
|
||||||
redis_data:
|
|
||||||
|
|
||||||
networks:
|
|
||||||
qaup-network:
|
|
||||||
driver: bridge
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. 前端部署
|
|
||||||
|
|
||||||
### 5.1 前端构建
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 进入前端目录
|
|
||||||
cd qaup-ui/
|
|
||||||
|
|
||||||
# 安装依赖
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# 构建生产版本
|
|
||||||
npm run build:prod
|
|
||||||
|
|
||||||
# 使用Nginx部署
|
|
||||||
docker run -d -p 80:80 -v $(pwd)/dist:/usr/share/nginx/html nginx:alpine
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 Nginx配置
|
|
||||||
|
|
||||||
创建 `nginx.conf`:
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
# 前端路由支持
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
# API代理
|
|
||||||
location /api/ {
|
|
||||||
proxy_pass http://qaup-app:8080/;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
# WebSocket支持
|
|
||||||
location /ws/ {
|
|
||||||
proxy_pass http://qaup-app:8080/ws/;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 静态资源缓存
|
|
||||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
|
||||||
expires 1y;
|
|
||||||
add_header Cache-Control "public, immutable";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6. 监控与维护
|
|
||||||
|
|
||||||
### 6.1 健康检查
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 脚本化健康检查
|
|
||||||
#!/bin/bash
|
|
||||||
echo "QAUP系统健康检查 - $(date)"
|
|
||||||
|
|
||||||
services=("qaup-app:8080" "qaup-admin:8081" "adxp-adapter:8086")
|
|
||||||
for service in "${services[@]}"; do
|
|
||||||
name=$(echo $service | cut -d: -f1)
|
|
||||||
port=$(echo $service | cut -d: -f2)
|
|
||||||
|
|
||||||
if curl -f -s http://localhost:$port/actuator/health > /dev/null; then
|
|
||||||
echo "✓ $name 服务正常"
|
|
||||||
else
|
|
||||||
echo "✗ $name 服务异常"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# 检查数据库连接
|
|
||||||
if docker exec qaup-postgres pg_isready -U qaup > /dev/null; then
|
|
||||||
echo "✓ 数据库连接正常"
|
|
||||||
else
|
|
||||||
echo "✗ 数据库连接异常"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查Redis连接
|
|
||||||
if docker exec qaup-redis redis-cli ping > /dev/null; then
|
|
||||||
echo "✓ Redis连接正常"
|
|
||||||
else
|
|
||||||
echo "✗ Redis连接异常"
|
|
||||||
fi
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.2 日志管理
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 日志轮转配置
|
|
||||||
# /etc/logrotate.d/qaup
|
|
||||||
/app/logs/*.log {
|
|
||||||
daily
|
|
||||||
missingok
|
|
||||||
rotate 30
|
|
||||||
compress
|
|
||||||
delaycompress
|
|
||||||
notifempty
|
|
||||||
create 644 qaup qaup
|
|
||||||
postrotate
|
|
||||||
docker kill -s USR1 qaup-app
|
|
||||||
endscript
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.3 性能监控
|
|
||||||
|
|
||||||
使用Actuator端点监控:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 应用指标
|
|
||||||
curl http://localhost:8080/actuator/metrics
|
|
||||||
|
|
||||||
# 数据库连接池监控
|
|
||||||
curl http://localhost:8080/actuator/druid
|
|
||||||
|
|
||||||
# 系统信息
|
|
||||||
curl http://localhost:8080/actuator/info
|
|
||||||
```
|
|
||||||
|
|
||||||
## 7. 故障排除
|
|
||||||
|
|
||||||
### 7.1 常见问题
|
|
||||||
|
|
||||||
#### 服务启动失败
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 检查端口占用
|
|
||||||
netstat -tlnp | grep :8080
|
|
||||||
|
|
||||||
# 检查Docker容器日志
|
|
||||||
docker logs qaup-app --tail 100
|
|
||||||
|
|
||||||
# 检查资源使用
|
|
||||||
docker stats
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 数据库连接问题
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 检查数据库状态
|
|
||||||
docker exec qaup-postgres pg_isready -U qaup
|
|
||||||
|
|
||||||
# 检查网络连接
|
|
||||||
docker exec qaup-app ping postgres
|
|
||||||
|
|
||||||
# 检查数据库日志
|
|
||||||
docker logs qaup-postgres --tail 50
|
|
||||||
```
|
|
||||||
|
|
||||||
#### ADXP适配器问题
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 检查适配器配置
|
|
||||||
docker exec adxp-adapter cat /app/config/adxp.properties
|
|
||||||
|
|
||||||
# 测试ADXP连接
|
|
||||||
curl -X GET "http://localhost:8090/api/health" \
|
|
||||||
-H "Authorization: Bearer $ADXP_TOKEN"
|
|
||||||
|
|
||||||
# 检查消息队列
|
|
||||||
docker exec qaup-app java -jar qaup-admin.jar \
|
|
||||||
--spring.profiles.active=test \
|
|
||||||
--adxp.test.connection=true
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7.2 紧急恢复
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 快速重启所有服务
|
|
||||||
./qaup-service.sh restart
|
|
||||||
|
|
||||||
# 重建数据库(慎用)
|
|
||||||
docker-compose down -v
|
|
||||||
docker volume prune -f
|
|
||||||
./qaup-service.sh start
|
|
||||||
|
|
||||||
# 从备份恢复
|
|
||||||
docker exec -i qaup-postgres psql -U qaup -d qaup < backup.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7.3 调试模式
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 启用调试日志
|
|
||||||
export LOG_LEVEL=DEBUG
|
|
||||||
./qaup-service.sh restart
|
|
||||||
|
|
||||||
# 进入容器调试
|
|
||||||
docker exec -it qaup-app /bin/bash
|
|
||||||
|
|
||||||
# 查看实时日志
|
|
||||||
tail -f /app/logs/qaup-app.log | grep -E "(ERROR|WARN|Exception)"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 8. 性能优化
|
|
||||||
|
|
||||||
### 8.1 JVM调优
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 生产环境JVM参数
|
|
||||||
JAVA_OPTS="
|
|
||||||
-Xms2g -Xmx4g
|
|
||||||
-XX:+UseG1GC
|
|
||||||
-XX:MaxGCPauseMillis=200
|
|
||||||
-XX:+UnlockExperimentalVMOptions
|
|
||||||
-XX:+UseJVMCICompiler
|
|
||||||
-Djava.security.egd=file:/dev/./urandom
|
|
||||||
-Dspring.jmx.enabled=false
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8.2 数据库优化
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- PostgreSQL配置优化
|
|
||||||
ALTER SYSTEM SET shared_buffers = '512MB';
|
|
||||||
ALTER SYSTEM SET effective_cache_size = '2GB';
|
|
||||||
ALTER SYSTEM SET maintenance_work_mem = '64MB';
|
|
||||||
ALTER SYSTEM SET checkpoint_completion_target = 0.9;
|
|
||||||
ALTER SYSTEM SET wal_buffers = '16MB';
|
|
||||||
ALTER SYSTEM SET default_statistics_target = 100;
|
|
||||||
ALTER SYSTEM SET random_page_cost = 1.1;
|
|
||||||
ALTER SYSTEM SET effective_io_concurrency = 200;
|
|
||||||
|
|
||||||
SELECT pg_reload_conf();
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8.3 Redis优化
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Redis配置优化
|
|
||||||
maxmemory 512mb
|
|
||||||
maxmemory-policy allkeys-lru
|
|
||||||
save 900 1
|
|
||||||
save 300 10
|
|
||||||
save 60 10000
|
|
||||||
```
|
|
||||||
|
|
||||||
## 9. 安全配置
|
|
||||||
|
|
||||||
### 9.1 网络安全
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# docker-compose.yml中的网络安全配置
|
|
||||||
networks:
|
|
||||||
qaup-network:
|
|
||||||
driver: bridge
|
|
||||||
internal: true # 内部网络
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.2 SSL/TLS配置
|
|
||||||
|
|
||||||
```nginx
|
|
||||||
# Nginx SSL配置
|
|
||||||
server {
|
|
||||||
listen 443 ssl http2;
|
|
||||||
server_name your-domain.com;
|
|
||||||
|
|
||||||
ssl_certificate /etc/ssl/certs/qaup.crt;
|
|
||||||
ssl_certificate_key /etc/ssl/private/qaup.key;
|
|
||||||
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
|
|
||||||
ssl_prefer_server_ciphers off;
|
|
||||||
|
|
||||||
# HSTS
|
|
||||||
add_header Strict-Transport-Security "max-age=63072000" always;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.3 访问控制
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Spring Security配置示例
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
public class SecurityConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
||||||
http
|
|
||||||
.authorizeHttpRequests(authz -> authz
|
|
||||||
.requestMatchers("/actuator/health").permitAll()
|
|
||||||
.requestMatchers("/api/**").authenticated()
|
|
||||||
.anyRequest().authenticated()
|
|
||||||
)
|
|
||||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt());
|
|
||||||
|
|
||||||
return http.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 10. 备份与恢复
|
|
||||||
|
|
||||||
### 10.1 数据备份
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# backup.sh - 数据备份脚本
|
|
||||||
|
|
||||||
BACKUP_DIR="/backup/qaup/$(date +%Y%m%d_%H%M%S)"
|
|
||||||
mkdir -p $BACKUP_DIR
|
|
||||||
|
|
||||||
# 数据库备份
|
|
||||||
docker exec qaup-postgres pg_dump -U qaup qaup > $BACKUP_DIR/database.sql
|
|
||||||
|
|
||||||
# 配置文件备份
|
|
||||||
cp .env $BACKUP_DIR/
|
|
||||||
cp -r config/ $BACKUP_DIR/
|
|
||||||
|
|
||||||
# 日志备份
|
|
||||||
tar -czf $BACKUP_DIR/logs.tar.gz /app/logs/
|
|
||||||
|
|
||||||
echo "备份完成: $BACKUP_DIR"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 10.2 数据恢复
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# restore.sh - 数据恢复脚本
|
|
||||||
|
|
||||||
BACKUP_FILE=$1
|
|
||||||
|
|
||||||
if [ -z "$BACKUP_FILE" ]; then
|
|
||||||
echo "使用方法: ./restore.sh <backup_file>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 停止应用服务
|
|
||||||
docker compose stop qaup-app
|
|
||||||
|
|
||||||
# 恢复数据库
|
|
||||||
docker exec -i qaup-postgres psql -U qaup qaup < $BACKUP_FILE/database.sql
|
|
||||||
|
|
||||||
# 重启服务
|
|
||||||
docker compose start qaup-app
|
|
||||||
|
|
||||||
echo "恢复完成"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 附录
|
|
||||||
|
|
||||||
### A. 部署检查清单
|
|
||||||
|
|
||||||
- [ ] 系统环境准备完成
|
|
||||||
- [ ] Docker和依赖软件安装
|
|
||||||
- [ ] 配置文件准备和验证
|
|
||||||
- [ ] 数据库初始化完成
|
|
||||||
- [ ] 所有服务启动成功
|
|
||||||
- [ ] 健康检查通过
|
|
||||||
- [ ] 前端访问正常
|
|
||||||
- [ ] 监控和日志配置完成
|
|
||||||
- [ ] 备份策略实施
|
|
||||||
|
|
||||||
### B. 紧急联系信息
|
|
||||||
|
|
||||||
- **技术支持**: <tech-support@qaup.com>
|
|
||||||
- **运维团队**: <ops@qaup.com>
|
|
||||||
- **值班电话**: +86-xxx-xxxx-xxxx
|
|
||||||
|
|
||||||
### C. 相关文档
|
|
||||||
|
|
||||||
- [API文档](./api_documentation.md)
|
|
||||||
- [配置指南](./configuration_guide.md)
|
|
||||||
- [JDK21升级指南](./JDK21-升级指南.md)
|
|
||||||
- [环境配置](./environment.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**文档版本**: v2.0
|
|
||||||
**最后更新**: 2025-01-17
|
|
||||||
**维护者**: QAUP Development Team
|
|
||||||
@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
- 服务器
|
- 服务器
|
||||||
- 后台
|
- 后台
|
||||||
- IP: 10.64.58.228
|
- IP: 10.100.23.10
|
||||||
- 用户名: root
|
- 用户名: root
|
||||||
- 密码: Yaddfepasswd13#$
|
- 密码: Huawei@1234567890
|
||||||
- 前端
|
- 前端
|
||||||
- IP:
|
- IP:
|
||||||
- 用户名:
|
- 用户名:
|
||||||
@ -28,7 +28,7 @@
|
|||||||
- IP: 10.32.38.3
|
- IP: 10.32.38.3
|
||||||
- 端口: 8090
|
- 端口: 8090
|
||||||
- 地图服务
|
- 地图服务
|
||||||
- URL: http://10.98.23.81:8090/iserver/services/map-QDJC_DT-GX3/rest/maps
|
- URL: http://221.215.103.144:8090/iserver/services/map-QDJC_DT-GX3/rest/maps
|
||||||
- 安全设备
|
- 安全设备
|
||||||
- VPN
|
- VPN
|
||||||
- IP: 222.173.72.76
|
- IP: 222.173.72.76
|
||||||
|
|||||||
@ -5,180 +5,155 @@
|
|||||||
## 1. 根目录结构
|
## 1. 根目录结构
|
||||||
|
|
||||||
```
|
```
|
||||||
QAUP-Management/
|
CollisionAvoidanceSystem/
|
||||||
├── doc/ # 文档目录
|
├── doc/ # 文档目录
|
||||||
├── qaup-admin/ # 管理后台模块
|
|
||||||
├── qaup-collision/ # 碰撞避免系统核心模块
|
|
||||||
├── qaup-common/ # 公共模块
|
|
||||||
├── qaup-framework/ # 框架模块
|
|
||||||
├── qaup-generator/ # 代码生成器
|
|
||||||
├── qaup-quartz/ # 定时任务模块
|
|
||||||
├── qaup-system/ # 系统管理模块
|
|
||||||
├── qaup-ui/ # 前端UI模块
|
|
||||||
├── deploy/ # 部署脚本
|
|
||||||
├── scripts/ # 工具脚本
|
|
||||||
├── sql/ # 数据库脚本
|
|
||||||
├── .git/ # Git版本控制
|
|
||||||
├── pom.xml # Maven父项目配置
|
|
||||||
├── README.md # 项目说明
|
|
||||||
├── VERSION.md # 版本信息
|
|
||||||
├── CHANGELOG.md # 变更日志
|
|
||||||
├── .gitignore # Git忽略配置
|
|
||||||
└── qaup.sh # 项目启动脚本
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. 核心模块目录结构 (`qaup-collision`)
|
|
||||||
|
|
||||||
```
|
|
||||||
qaup-collision/
|
|
||||||
├── src/ # 源代码目录
|
├── src/ # 源代码目录
|
||||||
│ ├── main/ # 主要源代码
|
├── target/ # 编译输出目录
|
||||||
│ │ ├── java/ # Java源代码
|
├── .idea/ # IDE配置
|
||||||
│ │ │ └── com/
|
├── .mvn/ # Maven包装器配置
|
||||||
│ │ │ └── qaup/
|
├── .git/ # Git版本控制
|
||||||
│ │ │ └── collision/ # 应用程序主包
|
├── pom.xml # Maven项目配置
|
||||||
│ │ └── resources/ # 配置文件和静态资源
|
├── mvnw / mvnw.cmd # Maven包装器脚本
|
||||||
│ │ ├── config/ # 特定配置文件 (如 airport_roads.yaml, airport_areas.yaml)
|
├── README.md # 项目说明
|
||||||
│ │ ├── static/ # 静态Web资源
|
├── VERSION.txt # 版本信息
|
||||||
│ │ └── templates/ # 模板文件
|
├── change_log.md # 变更日志
|
||||||
│ └── test/ # 测试源代码
|
├── development_log.md # 开发日志
|
||||||
├── libs/ # 本地依赖库
|
├── .gitignore # Git忽略配置
|
||||||
└── pom.xml # Maven模块配置
|
└── .gitattributes # Git属性配置
|
||||||
```
|
```
|
||||||
|
|
||||||
## 3. 应用程序主包结构 (`com.qaup.collision`)
|
## 2. 源代码目录结构 (`src`)
|
||||||
|
|
||||||
```
|
```
|
||||||
com.qaup.collision/
|
src/
|
||||||
├── adapter/ # 外部系统适配器
|
├── main/ # 主要源代码
|
||||||
├── area/ # 机场区域管理模块
|
│ ├── java/ # Java源代码
|
||||||
│ ├── model/ # 区域相关数据模型
|
│ │ └── com/
|
||||||
│ └── service/ # 区域管理服务
|
│ │ └── dongni/
|
||||||
|
│ │ └── collisionavoidance/ # 应用程序主包
|
||||||
|
│ └── resources/ # 配置文件和静态资源
|
||||||
|
│ ├── config/ # 特定配置文件 (如 airport_roads.yaml, airport_areas.yaml)
|
||||||
|
│ ├── data/ # 数据文件
|
||||||
|
│ ├── scripts/ # 脚本文件
|
||||||
|
│ └── static/ # 静态Web资源
|
||||||
|
└── test/ # 测试源代码
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 应用程序主包结构 (`com.dongni.collisionavoidance`)
|
||||||
|
|
||||||
|
```
|
||||||
|
com.dongni.collisionavoidance/
|
||||||
|
├── CollisionAvoidanceApplication.java # 应用程序入口类
|
||||||
├── common/ # 通用组件目录
|
├── common/ # 通用组件目录
|
||||||
│ ├── adapter/ # 通用适配器
|
│ ├── model/ # 核心移动对象等数据模型
|
||||||
│ ├── config/ # 通用配置
|
│ │ ├── base/ # 基础类和常量
|
||||||
│ ├── exception/ # 通用异常处理
|
│ │ ├── dto/ # 数据传输对象
|
||||||
│ ├── model/ # 核心数据模型
|
│ │ └── repository/ # 仓储模式实现
|
||||||
│ ├── repository/ # 仓储模式实现
|
│ └── config/ # 通用配置 (较少使用,优先模块内配置)
|
||||||
│ └── service/ # 通用服务
|
|
||||||
├── config/ # 应用程序配置
|
├── config/ # 应用程序配置
|
||||||
│ ├── properties/ # 配置属性映射类 (POJOs)
|
│ ├── properties/ # 配置属性映射类 (POJOs)
|
||||||
│ ├── CollisionAvoidanceConfig.java # 碰撞避免系统配置
|
|
||||||
│ ├── DatabasePerformanceConfig.java # 数据库性能配置
|
|
||||||
│ ├── GracefulShutdownConfig.java # 优雅关闭配置
|
|
||||||
│ ├── RoadNetworkConfig.java # 道路网络配置加载类
|
│ ├── RoadNetworkConfig.java # 道路网络配置加载类
|
||||||
│ ├── ThreadPoolConfig.java # 线程池配置
|
│ ├── AirportAreaConfig.java # 机场区域配置加载类
|
||||||
│ └── YamlPropertySourceFactory.java # YAML加载工厂类
|
│ ├── YamlPropertySourceFactory.java # YAML加载工厂类
|
||||||
|
│ ├── RedisConfig.java # Redis配置
|
||||||
|
│ └── ThreadPoolConfig.java # 线程池配置
|
||||||
├── controller/ # 控制器层 (REST API)
|
├── controller/ # 控制器层 (REST API)
|
||||||
│ ├── DataMonitorController.java # 数据监控控制器
|
├── dataCollector/ # 数据采集模块
|
||||||
│ ├── HealthController.java # 健康检查控制器
|
|
||||||
│ ├── IntersectionController.java # 交叉路口控制器
|
|
||||||
│ ├── SpatialRuleController.java # 空间规则控制器
|
|
||||||
│ ├── TrafficLightController.java # 交通信号灯控制器
|
|
||||||
│ ├── UniversalVehicleApiController.java # 通用车辆API控制器
|
|
||||||
│ └── UnmannedVehicleController.java # 无人车控制器
|
|
||||||
├── datacollector/ # 数据采集模块
|
|
||||||
│ ├── config/ # 数据采集配置
|
|
||||||
│ ├── dao/ # 数据访问对象
|
|
||||||
│ ├── dto/ # 数据传输对象
|
|
||||||
│ ├── filter/ # 数据过滤器
|
|
||||||
│ ├── model/ # 数据采集相关模型
|
│ ├── model/ # 数据采集相关模型
|
||||||
│ ├── repository/ # 数据采集仓储
|
│ │ └── enums/ # 枚举类型定义
|
||||||
│ ├── sdk/ # SDK集成
|
|
||||||
│ ├── server/ # 服务器端实现
|
|
||||||
│ ├── service/ # 数据采集服务
|
│ ├── service/ # 数据采集服务
|
||||||
│ ├── util/ # 工具类
|
│ ├── dao/ # 数据访问对象
|
||||||
│ └── websocket/ # WebSocket数据采集
|
│ └── config/ # 数据采集配置
|
||||||
├── dataprocessing/ # 数据处理模块
|
├── dataProcessing/ # 数据处理模块
|
||||||
│ ├── config/ # 数据处理配置
|
│ ├── service/ # 数据处理服务
|
||||||
│ ├── model/ # 数据处理模型
|
│ └── config/ # 数据处理配置
|
||||||
│ ├── parser/ # 数据解析器
|
├── areas/ # 新增:机场区域管理模块
|
||||||
│ └── service/ # 数据处理服务
|
│ ├── model/ # 区域相关数据模型
|
||||||
├── event/ # 事件处理模块
|
│ └── service/ # 区域管理服务
|
||||||
│ ├── handler/ # 事件处理器
|
├── roads/ # 道路网络模块
|
||||||
│ ├── model/ # 事件模型
|
|
||||||
│ └── service/ # 事件服务
|
|
||||||
├── geofence/ # 地理围栏模块
|
|
||||||
│ ├── model/ # 地理围栏模型
|
|
||||||
│ └── service/ # 地理围栏服务
|
|
||||||
├── pathconflict/ # 路径冲突模块
|
|
||||||
│ ├── event/ # 路径冲突事件
|
|
||||||
│ ├── model/ # 路径冲突模型
|
|
||||||
│ ├── repository/ # 路径冲突仓储
|
|
||||||
│ └── service/ # 路径冲突服务
|
|
||||||
├── road/ # 道路网络模块
|
|
||||||
│ ├── model/ # 道路网络运行时模型
|
│ ├── model/ # 道路网络运行时模型
|
||||||
│ └── service/ # 道路网络服务
|
│ └── service/ # 道路网络服务
|
||||||
├── rule/ # 规则引擎模块
|
└── webSocket/ # WebSocket通信模块
|
||||||
│ ├── event/ # 规则事件
|
|
||||||
│ ├── model/ # 规则模型
|
|
||||||
│ ├── repository/ # 规则仓储
|
|
||||||
│ └── service/ # 规则服务
|
|
||||||
└── websocket/ # WebSocket通信模块
|
|
||||||
├── broadcaster/ # 消息广播器
|
|
||||||
├── cache/ # WebSocket缓存
|
|
||||||
├── config/ # WebSocket配置
|
|
||||||
├── controller/ # WebSocket控制器
|
├── controller/ # WebSocket控制器
|
||||||
├── event/ # WebSocket事件
|
└── config/ # WebSocket配置
|
||||||
├── handler/ # WebSocket消息处理器
|
|
||||||
├── listener/ # WebSocket监听器
|
|
||||||
└── message/ # WebSocket消息格式
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 4. 数据模型说明
|
## 4. 数据模型目录
|
||||||
|
|
||||||
系统采用模块化的数据模型设计,各模块内部包含自己的数据模型定义。主要数据模型包括:
|
|
||||||
|
|
||||||
### 4.1 通用数据模型 (`common/model`)
|
### 4.1 通用数据模型 (`common/model`)
|
||||||
包含系统核心的、跨模块共享的数据结构,如:
|
|
||||||
- 地理位置数据模型
|
|
||||||
- 移动状态数据模型
|
|
||||||
- 基础常量和枚举
|
|
||||||
|
|
||||||
### 4.2 区域数据模型 (`area/model`)
|
包含系统核心的、跨模块共享的数据结构:
|
||||||
|
|
||||||
|
```
|
||||||
|
common/model/
|
||||||
|
├── Aircraft.java # 航空器实体类
|
||||||
|
├── GeoPosition.java # 地理位置数据结构
|
||||||
|
├── MovementState.java # 移动状态封装
|
||||||
|
├── MovingObject.java # 移动物体抽象基类
|
||||||
|
├── MovingObjectType.java # 移动物体类型枚举
|
||||||
|
├── PositionRecord.java # 位置记录
|
||||||
|
├── SpecialVehicle.java # 特勤车辆实体类
|
||||||
|
├── UnmannedVehicle.java # 无人车实体类
|
||||||
|
├── Velocity.java # 速度和局部坐标系信息
|
||||||
|
├── base/ # 基础类和常量
|
||||||
|
│ ├── Constant.java # 系统常量定义
|
||||||
|
│ └── Response.java # 统一响应格式
|
||||||
|
├── dto/ # 数据传输对象
|
||||||
|
│ ├── AircraftDTO.java # 航空器数据传输对象
|
||||||
|
│ └── SpecialVehicleDTO.java # 特勤车辆数据传输对象
|
||||||
|
└── repository/ # 仓储模式实现
|
||||||
|
└── MovingObjectRepository.java # 移动物体仓储接口
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 机场区域数据模型 (`areas/model`)
|
||||||
|
|
||||||
包含机场区域管理模块的数据结构:
|
包含机场区域管理模块的数据结构:
|
||||||
- AreaInfo.java: 区域信息实体类 (含JTS几何对象)
|
|
||||||
- AreaType.java: 区域类型枚举
|
|
||||||
|
|
||||||
### 4.3 道路网络数据模型 (`road/model`)
|
```
|
||||||
|
areas/model/
|
||||||
|
├── AreaInfo.java # 区域信息实体类 (含JTS几何对象)
|
||||||
|
└── AreaType.java # 区域类型枚举
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 道路网络数据模型 (`roads/model`)
|
||||||
|
|
||||||
包含道路网络模块内部使用的运行时数据结构:
|
包含道路网络模块内部使用的运行时数据结构:
|
||||||
- RoadInfo.java: 运行时道路信息 (含JTS对象)
|
|
||||||
|
|
||||||
### 4.4 数据采集模型 (`datacollector/model`)
|
```
|
||||||
包含数据采集模块的专用数据结构,如:
|
roads/model/
|
||||||
- 车辆位置信息
|
├── RoadInfo.java # 运行时道路信息 (含JTS对象)
|
||||||
- 车辆状态信息
|
└── RoadDirectionality.java # 道路方向枚举
|
||||||
- 命令响应
|
```
|
||||||
- 各种枚举类型定义
|
|
||||||
|
|
||||||
### 4.5 路径冲突数据模型 (`pathconflict/model`)
|
### 4.4 数据采集模型 (`dataCollector/model`)
|
||||||
包含路径冲突检测模块的数据结构:
|
|
||||||
- 冲突点信息
|
|
||||||
- 冲突路径信息
|
|
||||||
- 冲突解决策略
|
|
||||||
|
|
||||||
### 4.6 地理围栏数据模型 (`geofence/model`)
|
包含数据采集模块的专用数据结构:
|
||||||
包含地理围栏模块的数据结构:
|
|
||||||
- 围栏信息
|
|
||||||
- 围栏规则
|
|
||||||
- 围栏触发事件
|
|
||||||
|
|
||||||
### 4.7 规则引擎数据模型 (`rule/model`)
|
|
||||||
包含规则引擎模块的数据结构:
|
|
||||||
- 规则定义
|
|
||||||
- 规则条件
|
|
||||||
- 规则动作
|
|
||||||
|
|
||||||
|
```
|
||||||
|
dataCollector/model/
|
||||||
|
├── CommandResponse.java # 命令响应
|
||||||
|
├── VehicleCommand.java # 车辆命令
|
||||||
|
├── VehicleLocationInfo.java # 车辆位置信息
|
||||||
|
├── VehicleStateInfo.java # 车辆状态信息
|
||||||
|
└── enums/ # 枚举类型定义
|
||||||
|
├── CommandReason.java # 命令原因枚举
|
||||||
|
├── CommandType.java # 命令类型枚举
|
||||||
|
└── SignalState.java # 信号状态枚举
|
||||||
|
```
|
||||||
|
|
||||||
## 5. 配置属性类目录 (`config/properties`)
|
## 5. 配置属性类目录 (`config/properties`)
|
||||||
|
|
||||||
包含用于绑定配置文件的POJO类,支持:
|
包含用于绑定配置文件的POJO类:
|
||||||
- 机场区域配置绑定
|
|
||||||
- 道路网络配置绑定
|
|
||||||
- 几何图形配置绑定
|
|
||||||
- 系统参数配置
|
|
||||||
|
|
||||||
实际配置类根据模块需求动态添加和调整。
|
```
|
||||||
|
config/properties/
|
||||||
|
├── AirportAreasProperties.java # 机场区域配置属性
|
||||||
|
├── AirportRoadsProperties.java # 机场道路配置属性
|
||||||
|
├── AreaProperties.java # 单个区域配置属性
|
||||||
|
├── DimensionValue.java # 尺寸值配置
|
||||||
|
├── GeometryProperties.java # 几何图形配置属性
|
||||||
|
└── RoadProperties.java # 单个道路配置属性
|
||||||
|
```
|
||||||
|
|
||||||
## 6. 资源文件目录 (`resources`)
|
## 6. 资源文件目录 (`resources`)
|
||||||
|
|
||||||
@ -189,95 +164,103 @@ resources/
|
|||||||
│ ├── airport_areas.yaml # 机场区域配置
|
│ ├── airport_areas.yaml # 机场区域配置
|
||||||
│ ├── airport_roads.yaml # 机场道路配置
|
│ ├── airport_roads.yaml # 机场道路配置
|
||||||
│ └── airport_zones.yaml # 机场区域配置
|
│ └── airport_zones.yaml # 机场区域配置
|
||||||
├── static/ # 静态Web资源
|
├── data/ # 数据文件目录
|
||||||
│ ├── index.html # 主页面
|
├── scripts/ # 脚本文件目录
|
||||||
│ ├── geoposition-test.html # 地理位置测试页面
|
└── static/ # 静态Web资源
|
||||||
│ ├── websocket-test.html # WebSocket测试页面
|
├── index.html # 主页面
|
||||||
│ └── js/ # JavaScript文件
|
├── geoposition-test.html # 地理位置测试页面
|
||||||
└── templates/ # 模板文件目录
|
└── js/ # JavaScript文件
|
||||||
```
|
```
|
||||||
|
|
||||||
## 7. 各模块功能说明
|
## 7. 各模块功能说明
|
||||||
|
|
||||||
### 7.1 通用组件模块 (common)
|
### 7.1 通用数据模型 (common/model)
|
||||||
提供系统级通用功能和数据结构,包括:
|
|
||||||
- 通用配置
|
|
||||||
- 异常处理
|
|
||||||
- 基础数据模型
|
|
||||||
- 通用服务
|
|
||||||
|
|
||||||
### 7.2 机场区域管理模块 (area)
|
数据模型模块定义了系统中使用的所有数据结构:
|
||||||
负责管理机场内各种功能区域的边界、权限和规则:
|
|
||||||
- 加载`airport_areas.yaml`配置文件
|
- **MovingObject**: 所有移动物体的抽象基类,定义共有属性和行为
|
||||||
- 构建空间索引以支持高效的几何查询
|
- **Aircraft**: 代表航空器的具体实现类
|
||||||
- 提供区域查询接口(根据位置查找包含区域、获取限速等)
|
- **SpecialVehicle**: 代表特勤车辆的具体实现类
|
||||||
- 支持区域权限验证和时间有效性检查
|
- **UnmannedVehicle**: 代表无人车的具体实现类
|
||||||
- 使用JTS库进行复杂的空间几何计算
|
- **GeoPosition**: 表示地理位置的数据结构
|
||||||
|
- **Velocity**: 表示速度和局部坐标系位置信息的数据结构
|
||||||
|
- **MovementState**: 封装移动物体在特定时刻的完整状态
|
||||||
|
- **MovingObjectType**: 定义了系统支持的移动物体类型的枚举
|
||||||
|
- **base/**: 包含系统基础类和常量定义
|
||||||
|
- **dto/**: 包含数据传输对象,用于API交互
|
||||||
|
- **repository/**: 包含仓储模式实现,提供数据访问抽象
|
||||||
|
|
||||||
|
### 7.2 机场区域管理模块 (areas)
|
||||||
|
|
||||||
|
**新增模块**,负责管理机场内各种功能区域的边界、权限和规则:
|
||||||
|
|
||||||
|
- **areas/model**: 定义区域信息数据结构,包含JTS几何对象用于空间计算
|
||||||
|
- **areas/service**: 提供`AirportAreaService`,负责:
|
||||||
|
- 加载`airport_areas.yaml`配置文件
|
||||||
|
- 构建空间索引以支持高效的几何查询
|
||||||
|
- 提供区域查询接口(根据位置查找包含区域、获取限速等)
|
||||||
|
- 支持区域权限验证和时间有效性检查
|
||||||
|
- 使用JTS库进行复杂的空间几何计算
|
||||||
|
|
||||||
|
### 7.3 数据采集模块 (dataCollector)
|
||||||
|
|
||||||
### 7.3 数据采集模块 (datacollector)
|
|
||||||
负责从各种数据源获取移动物体的实时位置和状态信息:
|
负责从各种数据源获取移动物体的实时位置和状态信息:
|
||||||
- 支持多数据源集成
|
|
||||||
- 提供数据过滤和预处理功能
|
|
||||||
- 数据持久化和缓存
|
|
||||||
- WebSocket实时数据采集
|
|
||||||
|
|
||||||
### 7.4 数据处理模块 (dataprocessing)
|
- **model/**: 包含数据采集专用的数据结构
|
||||||
|
- 车辆位置信息、状态信息、命令响应等
|
||||||
|
- 枚举类型定义(命令类型、信号状态等)
|
||||||
|
- **service/**: 数据采集服务实现
|
||||||
|
- **dao/**: 数据访问对象,处理外部数据源交互
|
||||||
|
- **config/**: 数据采集相关配置
|
||||||
|
|
||||||
|
支持的数据源:
|
||||||
|
- 航空器数据采集(ADS-B、雷达等)
|
||||||
|
- 特勤车辆数据采集(GPS、地面雷达等)
|
||||||
|
- 无人车数据采集(车载传感器等)
|
||||||
|
|
||||||
|
### 7.4 数据处理模块 (dataProcessing)
|
||||||
|
|
||||||
处理和分析采集到的数据:
|
处理和分析采集到的数据:
|
||||||
- 数据解析和转换
|
|
||||||
- 轨迹计算和预测
|
- 轨迹计算和预测
|
||||||
- 碰撞风险评估
|
- 碰撞风险评估
|
||||||
- 数据质量检查和过滤
|
- 数据质量检查和过滤
|
||||||
|
- 历史数据管理和分析
|
||||||
|
|
||||||
### 7.5 事件处理模块 (event)
|
### 7.5 控制器层 (controller)
|
||||||
负责系统事件的统一管理和分发:
|
|
||||||
- 事件定义和注册
|
|
||||||
- 事件监听和处理
|
|
||||||
- 事件驱动的业务逻辑
|
|
||||||
|
|
||||||
### 7.6 地理围栏模块 (geofence)
|
|
||||||
提供地理围栏功能:
|
|
||||||
- 围栏定义和管理
|
|
||||||
- 实时位置围栏检测
|
|
||||||
- 围栏触发事件处理
|
|
||||||
|
|
||||||
### 7.7 路径冲突模块 (pathconflict)
|
|
||||||
负责检测和解决路径冲突:
|
|
||||||
- 路径冲突检测算法
|
|
||||||
- 冲突解决策略
|
|
||||||
- 冲突事件处理
|
|
||||||
|
|
||||||
### 7.8 道路网络模块 (road)
|
|
||||||
负责管理和查询机场静态道路网络信息:
|
|
||||||
- 加载`airport_roads.yaml`配置
|
|
||||||
- 初始化道路数据和空间索引
|
|
||||||
- 提供道路查询接口(如根据位置查找道路、获取限速等)
|
|
||||||
|
|
||||||
### 7.9 规则引擎模块 (rule)
|
|
||||||
提供灵活的规则配置和执行功能:
|
|
||||||
- 规则定义和管理
|
|
||||||
- 实时规则执行
|
|
||||||
- 规则触发事件处理
|
|
||||||
|
|
||||||
### 7.10 WebSocket模块 (websocket)
|
|
||||||
提供实时通信功能:
|
|
||||||
- 推送实时位置更新
|
|
||||||
- 发送碰撞警告
|
|
||||||
- 支持客户端实时监控
|
|
||||||
- 多客户端消息广播
|
|
||||||
|
|
||||||
### 7.11 配置模块 (config)
|
|
||||||
包含应用程序的配置类和配置加载机制:
|
|
||||||
- 配置文件加载和解析
|
|
||||||
- 配置属性绑定
|
|
||||||
- 模块化配置管理
|
|
||||||
- 系统参数配置
|
|
||||||
|
|
||||||
### 7.12 控制器层 (controller)
|
|
||||||
提供RESTful API接口,用于:
|
提供RESTful API接口,用于:
|
||||||
|
|
||||||
- 数据查询和检索
|
- 数据查询和检索
|
||||||
- 系统配置和管理
|
- 系统配置和管理
|
||||||
- 状态报告和监控
|
- 状态报告和监控
|
||||||
- 车辆控制命令发布
|
|
||||||
|
### 7.6 WebSocket模块 (webSocket)
|
||||||
|
|
||||||
|
提供实时通信功能:
|
||||||
|
|
||||||
|
- 推送实时位置更新
|
||||||
|
- 发送碰撞警告
|
||||||
|
- 支持客户端实时监控
|
||||||
|
|
||||||
|
### 7.7 配置模块 (config)
|
||||||
|
|
||||||
|
包含应用程序的配置类和配置加载机制:
|
||||||
|
|
||||||
|
- **config/properties**: 存放用于绑定配置文件的POJO类,支持:
|
||||||
|
- 机场区域配置绑定(`AirportAreasProperties`、`AreaProperties`)
|
||||||
|
- 道路网络配置绑定(`AirportRoadsProperties`、`RoadProperties`)
|
||||||
|
- 几何图形配置绑定(`GeometryProperties`、`DimensionValue`)
|
||||||
|
- 系统参数配置、Bean配置(如`RedisConfig`、`ThreadPoolConfig`)
|
||||||
|
- 特定配置加载器(如`RoadNetworkConfig`、`AirportAreaConfig`、`YamlPropertySourceFactory`)
|
||||||
|
- 服务注册、安全、数据库连接等配置
|
||||||
|
|
||||||
|
### 7.8 道路网络模块 (roads)
|
||||||
|
|
||||||
|
负责管理和查询机场静态道路网络信息:
|
||||||
|
|
||||||
|
- **roads/model**: 定义运行时的道路数据结构(`RoadInfo`),包含JTS几何对象和处理过的属性
|
||||||
|
- **roads/service**: 提供`RoadNetworkService`,负责加载`airport_roads.yaml`配置,初始化道路数据和空间索引,并提供查询接口(如根据位置查找道路、获取限速等)
|
||||||
|
|
||||||
## 8. 文档目录 (doc)
|
## 8. 文档目录 (doc)
|
||||||
|
|
||||||
@ -285,30 +268,10 @@ resources/
|
|||||||
|
|
||||||
```
|
```
|
||||||
doc/
|
doc/
|
||||||
├── design/ # 设计文档目录
|
├── design_document.md # 系统数据结构设计文档
|
||||||
│ ├── area_design.md # 区域设计文档
|
├── directory_structure.md # 目录结构说明文档(本文档)
|
||||||
│ ├── data_structure_design.md # 系统数据结构设计文档
|
├── cad_to_yaml_guide.md # CAD转YAML操作指南
|
||||||
│ ├── directory_structure.md # 目录结构说明文档(本文档)
|
└── road_network_design.md # 道路网络配置集成设计方案
|
||||||
│ ├── airport_area_design.md # 机场区域设计方案
|
|
||||||
│ ├── road_network_design.md # 道路网络设计方案
|
|
||||||
│ ├── speed_calculation.md # 速度计算设计
|
|
||||||
│ └── 机场预警系统架构设计方案.md # 机场预警系统架构设计
|
|
||||||
├── guide/ # 操作指南
|
|
||||||
│ ├── cad_to_yaml_guide.md # CAD转YAML操作指南
|
|
||||||
│ ├── configuration_guide.md # 配置指南
|
|
||||||
│ └── commands.md # 常用命令
|
|
||||||
├── requirement/ # 需求文档
|
|
||||||
│ ├── requirements.md # 系统需求
|
|
||||||
│ ├── area.md # 区域需求
|
|
||||||
│ └── route.md # 路径需求
|
|
||||||
├── deploy/ # 部署文档
|
|
||||||
│ ├── ADXP_WebSocket.md # ADXP WebSocket部署
|
|
||||||
│ ├── Ubuntu部署指南.md # Ubuntu部署指南
|
|
||||||
│ └── deploy-design.md # 部署设计
|
|
||||||
└── work/ # 工作文档
|
|
||||||
├── API接口修复完成报告.md # API接口修复报告
|
|
||||||
├── 架构优化报告.md # 架构优化报告
|
|
||||||
└── 无人车经纬度参数错误修复.md # 无人车参数修复报告
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 9. 主要技术特性
|
## 9. 主要技术特性
|
||||||
@ -327,39 +290,8 @@ doc/
|
|||||||
- 采用继承和组合的面向对象设计
|
- 采用继承和组合的面向对象设计
|
||||||
- 支持DTO模式进行数据传输
|
- 支持DTO模式进行数据传输
|
||||||
- 实现仓储模式进行数据访问抽象
|
- 实现仓储模式进行数据访问抽象
|
||||||
- 模块化数据模型设计
|
|
||||||
|
|
||||||
### 9.4 实时通信
|
### 9.4 实时通信
|
||||||
- WebSocket支持实时数据推送
|
- WebSocket支持实时数据推送
|
||||||
- 线程池配置支持并发处理
|
- Redis缓存支持高性能数据访问
|
||||||
- 消息广播和路由机制
|
- 线程池配置支持并发处理
|
||||||
|
|
||||||
### 9.5 模块化架构
|
|
||||||
- 清晰的模块划分和职责定义
|
|
||||||
- 低耦合的模块间交互
|
|
||||||
- 可扩展性强的系统架构
|
|
||||||
|
|
||||||
### 9.6 事件驱动
|
|
||||||
- 基于事件的系统架构
|
|
||||||
- 灵活的事件监听和处理机制
|
|
||||||
- 松耦合的业务逻辑
|
|
||||||
|
|
||||||
### 9.7 规则引擎
|
|
||||||
- 可配置的规则定义
|
|
||||||
- 实时规则执行
|
|
||||||
- 灵活的规则扩展机制
|
|
||||||
|
|
||||||
### 9.8 数据处理能力
|
|
||||||
- 高性能的数据处理和分析
|
|
||||||
- 实时数据解析和转换
|
|
||||||
- 数据质量保障机制
|
|
||||||
|
|
||||||
### 9.9 地理围栏
|
|
||||||
- 支持多种围栏类型
|
|
||||||
- 实时围栏检测
|
|
||||||
- 灵活的围栏规则配置
|
|
||||||
|
|
||||||
### 9.10 路径冲突检测
|
|
||||||
- 高效的路径冲突检测算法
|
|
||||||
- 智能的冲突解决策略
|
|
||||||
- 实时冲突处理
|
|
||||||
@ -1,9 +1,5 @@
|
|||||||
## TODO Lists
|
## TODO Lists
|
||||||
|
|
||||||
### [2025-12-12]
|
|
||||||
|
|
||||||
- [x] (功能)修改机场中心点经纬度坐标,(120.08782536483503,36.36236546805307)
|
|
||||||
|
|
||||||
### [2025-10-09]
|
### [2025-10-09]
|
||||||
|
|
||||||
- [ ] (功能)模拟无人车平台,返回任务列表
|
- [ ] (功能)模拟无人车平台,返回任务列表
|
||||||
|
|||||||
@ -1,131 +0,0 @@
|
|||||||
# ============================================================
|
|
||||||
# QAUP 主应用环境变量配置模板
|
|
||||||
# 使用说明:
|
|
||||||
# 1. 复制此文件为 .env: cp .env.example .env
|
|
||||||
# 2. 修改 .env 中的配置值
|
|
||||||
# 3. 启动应用时会自动加载 .env 文件中的环境变量
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
# ========== 服务器配置 ==========
|
|
||||||
# 服务器端口
|
|
||||||
SERVER_PORT=8080
|
|
||||||
|
|
||||||
# 应用环境标识
|
|
||||||
SPRING_PROFILES_ACTIVE=prod,druid
|
|
||||||
|
|
||||||
# ========== 数据库配置 ==========
|
|
||||||
# 数据库连接信息
|
|
||||||
DATABASE_URL=jdbc:postgresql://localhost:5432/qaup
|
|
||||||
DATABASE_USERNAME=qaup
|
|
||||||
DATABASE_PASSWORD=your_password_here
|
|
||||||
DATABASE_DRIVER=org.postgresql.Driver
|
|
||||||
|
|
||||||
# 数据库连接池配置
|
|
||||||
DATABASE_POOL_MAX_ACTIVE=50
|
|
||||||
DATABASE_POOL_MAX_IDLE=20
|
|
||||||
DATABASE_POOL_MIN_IDLE=5
|
|
||||||
DATABASE_POOL_MAX_WAIT=60000
|
|
||||||
|
|
||||||
# ========== Redis配置 ==========
|
|
||||||
# Redis连接配置
|
|
||||||
REDIS_HOST=localhost
|
|
||||||
REDIS_PORT=6379
|
|
||||||
REDIS_DATABASE=0
|
|
||||||
REDIS_PASSWORD=
|
|
||||||
REDIS_MAX_MEMORY=1gb
|
|
||||||
REDIS_MAX_MEMORY_POLICY=volatile-lru
|
|
||||||
|
|
||||||
# ========== 日志配置 ==========
|
|
||||||
# 应用日志级别
|
|
||||||
LOG_LEVEL_QAUP=info
|
|
||||||
LOG_LEVEL_SPRING=warn
|
|
||||||
LOG_LEVEL_HIBERNATE=warn
|
|
||||||
LOG_LEVEL_DRUID=warn
|
|
||||||
|
|
||||||
# ========== ADXP适配器配置 ==========
|
|
||||||
# ADXP适配器服务地址
|
|
||||||
ADXP_HOST=localhost
|
|
||||||
ADXP_PORT=8086
|
|
||||||
|
|
||||||
# 连接重试配置
|
|
||||||
RECONNECT_DELAY_MILLIS=3000
|
|
||||||
|
|
||||||
# ========== 机场API配置 ==========
|
|
||||||
# 机场数据源API
|
|
||||||
AIRPORT_API_BASE_URL=http://localhost:8090
|
|
||||||
AIRPORT_API_USERNAME=dianxin
|
|
||||||
AIRPORT_API_PASSWORD=dianxin@123
|
|
||||||
|
|
||||||
# 滑行路由API
|
|
||||||
AIRPORT_API_GLIDE_URL=http://localhost:8099
|
|
||||||
|
|
||||||
# ========== 无人车API配置 ==========
|
|
||||||
# 无人车厂商数据源
|
|
||||||
VEHICLE_API_BASE_URL=http://localhost:8091
|
|
||||||
VEHICLE_API_TIMEOUT=1000
|
|
||||||
VEHICLE_API_RETRY=3
|
|
||||||
|
|
||||||
# ========== 数据采集配置 ==========
|
|
||||||
# 数据采集间隔(毫秒)
|
|
||||||
DATA_COLLECTOR_INTERVAL=250
|
|
||||||
|
|
||||||
# 检测间隔(毫秒)
|
|
||||||
DATA_DETECTION_INTERVAL=1000
|
|
||||||
|
|
||||||
# ========== 文件上传配置 ==========
|
|
||||||
# 文件上传路径
|
|
||||||
UPLOAD_PATH=/app/uploadPath
|
|
||||||
|
|
||||||
# 文件上传大小限制
|
|
||||||
UPLOAD_MAX_FILE_SIZE=10MB
|
|
||||||
UPLOAD_MAX_REQUEST_SIZE=20MB
|
|
||||||
|
|
||||||
# ========== Token配置 ==========
|
|
||||||
# JWT令牌密钥
|
|
||||||
TOKEN_SECRET=abcdefghijklmnopqrstuvwxyz
|
|
||||||
# 令牌过期时间(分钟)
|
|
||||||
TOKEN_EXPIRE_TIME=30
|
|
||||||
|
|
||||||
# ========== 安全配置 ==========
|
|
||||||
# 密码最大错误次数
|
|
||||||
PASSWORD_MAX_RETRY_COUNT=5
|
|
||||||
# 密码锁定时间(分钟)
|
|
||||||
PASSWORD_LOCK_TIME=10
|
|
||||||
|
|
||||||
# ========== 性能调优配置 ==========
|
|
||||||
# Tomcat线程配置
|
|
||||||
TOMCAT_MAX_THREADS=800
|
|
||||||
TOMCAT_MIN_SPARE_THREADS=100
|
|
||||||
TOMCAT_ACCEPT_COUNT=1000
|
|
||||||
|
|
||||||
# MyBatis批处理配置
|
|
||||||
MYBATIS_BATCH_SIZE=50
|
|
||||||
MYBATIS_FETCH_SIZE=50
|
|
||||||
|
|
||||||
# ========== 监控和健康检查 ==========
|
|
||||||
# Actuator健康检查配置
|
|
||||||
MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,metrics,loggers
|
|
||||||
MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS=simple
|
|
||||||
|
|
||||||
# ========== 生产环境优化配置 ==========
|
|
||||||
# 生产环境建议配置:
|
|
||||||
# SPRING_PROFILES_ACTIVE=prod,druid
|
|
||||||
# DATABASE_URL=jdbc:postgresql://your-db-host:5432/qaup
|
|
||||||
# DATABASE_PASSWORD=your_secure_password
|
|
||||||
# REDIS_HOST=your-redis-host
|
|
||||||
# REDIS_MAX_MEMORY=2gb
|
|
||||||
# LOG_LEVEL_QAUP=info
|
|
||||||
# UPLOAD_PATH=/home/qaup/uploadPath
|
|
||||||
# TOKEN_SECRET=your_very_long_and_secure_secret_key
|
|
||||||
# AIRPORT_API_BASE_URL=http://your-airport-api-host:8090
|
|
||||||
# VEHICLE_API_BASE_URL=http://your-vehicle-api-host:8091
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# 注意事项:
|
|
||||||
# 1. 等号两边不要有空格
|
|
||||||
# 2. 密码等敏感信息请妥善保管
|
|
||||||
# 3. #开头的行为注释
|
|
||||||
# 4. 请勿将 .env 文件提交到Git仓库
|
|
||||||
# 5. 生产环境请务必使用强密码和HTTPS
|
|
||||||
# 6. 根据实际部署环境调整各项配置值
|
|
||||||
# ============================================================
|
|
||||||
@ -43,7 +43,6 @@ public class QuapApplication
|
|||||||
* 配置Spring MVC使用虚拟线程处理请求
|
* 配置Spring MVC使用虚拟线程处理请求
|
||||||
* JDK21虚拟线程可以显著提高并发性能
|
* JDK21虚拟线程可以显著提高并发性能
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("null")
|
|
||||||
@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
|
@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
|
||||||
public AsyncTaskExecutor asyncTaskExecutor() {
|
public AsyncTaskExecutor asyncTaskExecutor() {
|
||||||
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
|
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
|
||||||
|
|||||||
@ -89,6 +89,8 @@ data:
|
|||||||
# 机场数据源配置 - 开发环境
|
# 机场数据源配置 - 开发环境
|
||||||
airport-api:
|
airport-api:
|
||||||
base-url: http://localhost:8090
|
base-url: http://localhost:8090
|
||||||
|
# 滑行路由
|
||||||
|
glide-url: http://localhost:8099
|
||||||
auth:
|
auth:
|
||||||
username: dianxin
|
username: dianxin
|
||||||
password: dianxin@123
|
password: dianxin@123
|
||||||
|
|||||||
91
qaup-admin/src/main/resources/application-prod copy.yml
Normal file
91
qaup-admin/src/main/resources/application-prod copy.yml
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# ============================================================
|
||||||
|
# QAUP 生产环境配置
|
||||||
|
# 关键配置项支持环境变量覆盖
|
||||||
|
# 使用方式:创建 .env 文件,通过启动脚本加载环境变量
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
# 服务器配置
|
||||||
|
server:
|
||||||
|
port: ${SERVER_PORT:8080}
|
||||||
|
servlet:
|
||||||
|
context-path: /
|
||||||
|
tomcat:
|
||||||
|
uri-encoding: UTF-8
|
||||||
|
accept-count: 1000
|
||||||
|
threads:
|
||||||
|
max: 800
|
||||||
|
min-spare: 100
|
||||||
|
|
||||||
|
# Spring配置
|
||||||
|
spring:
|
||||||
|
# Redis配置(支持环境变量)
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: ${REDIS_HOST:localhost}
|
||||||
|
port: ${REDIS_PORT:6379}
|
||||||
|
database: ${REDIS_DATABASE:0}
|
||||||
|
password: ${REDIS_PASSWORD:}
|
||||||
|
timeout: 10s
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
min-idle: 0
|
||||||
|
max-idle: 8
|
||||||
|
max-active: 8
|
||||||
|
max-wait: -1ms
|
||||||
|
|
||||||
|
# Redis 内存优化配置(生产环境)
|
||||||
|
redis:
|
||||||
|
# 最大内存限制(生产环境建议1-2GB)
|
||||||
|
max-memory: ${REDIS_MAX_MEMORY:1gb}
|
||||||
|
# 内存淘汰策略
|
||||||
|
max-memory-policy: ${REDIS_MAX_MEMORY_POLICY:volatile-lru}
|
||||||
|
|
||||||
|
# 日志配置(生产环境简洁日志)
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.qaup: ${LOG_LEVEL_QAUP:info}
|
||||||
|
org.springframework: ${LOG_LEVEL_SPRING:warn}
|
||||||
|
org.hibernate: warn
|
||||||
|
com.alibaba.druid: warn
|
||||||
|
pattern:
|
||||||
|
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"
|
||||||
|
|
||||||
|
# 数据采集配置(支持环境变量)
|
||||||
|
data:
|
||||||
|
collector:
|
||||||
|
interval: ${DATA_COLLECTOR_INTERVAL:250}
|
||||||
|
detection:
|
||||||
|
interval: ${DATA_DETECTION_INTERVAL:1000}
|
||||||
|
|
||||||
|
# 机场数据源配置(支持环境变量)
|
||||||
|
airport-api:
|
||||||
|
base-url: ${AIRPORT_API_BASE_URL:http://localhost:8090}
|
||||||
|
auth:
|
||||||
|
username: ${AIRPORT_API_USERNAME:dianxin}
|
||||||
|
password: ${AIRPORT_API_PASSWORD:dianxin@123}
|
||||||
|
|
||||||
|
# 无人车厂商数据源配置(支持环境变量)
|
||||||
|
vehicle-api:
|
||||||
|
base-url: ${VEHICLE_API_BASE_URL:http://localhost:8091}
|
||||||
|
timeout: ${VEHICLE_API_TIMEOUT:1000}
|
||||||
|
retry-attempts: ${VEHICLE_API_RETRY:3}
|
||||||
|
|
||||||
|
# ADXP 适配器服务配置(生产环境)
|
||||||
|
adxp-adapter:
|
||||||
|
# 适配器主机地址
|
||||||
|
host: ${ADXP_HOST:localhost}
|
||||||
|
# 适配器端口
|
||||||
|
port: ${ADXP_PORT:8086}
|
||||||
|
# 登录用户名
|
||||||
|
username: ${ADXP_USERNAME}
|
||||||
|
# 登录密码
|
||||||
|
password: ${ADXP_PASSWORD}
|
||||||
|
# 重连延迟(毫秒)
|
||||||
|
reconnect-delay-millis: ${RECONNECT_DELAY_MILLIS:3000}
|
||||||
|
|
||||||
|
# 红绿灯系统配置
|
||||||
|
traffic:
|
||||||
|
light:
|
||||||
|
tcp:
|
||||||
|
enabled: ${TRAFFIC_LIGHT_TCP_ENABLED:true}
|
||||||
|
port: ${TRAFFIC_LIGHT_TCP_PORT:8082}
|
||||||
@ -28,14 +28,10 @@ spring:
|
|||||||
timeout: 10s
|
timeout: 10s
|
||||||
lettuce:
|
lettuce:
|
||||||
pool:
|
pool:
|
||||||
# 最小空闲连接数(保底连接)
|
min-idle: 0
|
||||||
min-idle: 5
|
max-idle: 8
|
||||||
# 最大空闲连接数
|
max-active: 8
|
||||||
max-idle: 20
|
max-wait: -1ms
|
||||||
# 最大活跃连接数
|
|
||||||
max-active: 50
|
|
||||||
# 连接最大等待时间(毫秒),防止无限等待
|
|
||||||
max-wait: 5000ms
|
|
||||||
|
|
||||||
# Redis 内存优化配置(生产环境)
|
# Redis 内存优化配置(生产环境)
|
||||||
redis:
|
redis:
|
||||||
@ -83,9 +79,9 @@ data:
|
|||||||
# 适配器端口
|
# 适配器端口
|
||||||
port: ${ADXP_PORT:8086}
|
port: ${ADXP_PORT:8086}
|
||||||
# 登录用户名
|
# 登录用户名
|
||||||
username: ${ADXP_USERNAME:dianxin}
|
username: ${ADXP_USERNAME}
|
||||||
# 登录密码
|
# 登录密码
|
||||||
password: ${ADXP_PASSWORD:Dianxin#2025}
|
password: ${ADXP_PASSWORD}
|
||||||
# 重连延迟(毫秒)
|
# 重连延迟(毫秒)
|
||||||
reconnect-delay-millis: ${RECONNECT_DELAY_MILLIS:3000}
|
reconnect-delay-millis: ${RECONNECT_DELAY_MILLIS:3000}
|
||||||
|
|
||||||
|
|||||||
@ -40,8 +40,8 @@ spring:
|
|||||||
|
|
||||||
# 默认激活的profile(开发环境)
|
# 默认激活的profile(开发环境)
|
||||||
profiles:
|
profiles:
|
||||||
# active: dev,druid
|
active: dev,druid
|
||||||
active: prod,druid
|
#active: prod,druid
|
||||||
|
|
||||||
# 文件上传
|
# 文件上传
|
||||||
servlet:
|
servlet:
|
||||||
@ -230,8 +230,8 @@ traffic:
|
|||||||
# 坐标系统配置(collision模块)
|
# 坐标系统配置(collision模块)
|
||||||
coordinate-system:
|
coordinate-system:
|
||||||
airport:
|
airport:
|
||||||
center-longitude: 120.08782536
|
center-longitude: 120.0834104
|
||||||
center-latitude: 36.36236547
|
center-latitude: 36.35406879
|
||||||
|
|
||||||
# 性能监控配置(collision模块扩展)
|
# 性能监控配置(collision模块扩展)
|
||||||
management:
|
management:
|
||||||
@ -243,10 +243,11 @@ management:
|
|||||||
health:
|
health:
|
||||||
show-details: always
|
show-details: always
|
||||||
metrics:
|
metrics:
|
||||||
|
export:
|
||||||
|
simple:
|
||||||
|
enabled: true
|
||||||
enable:
|
enable:
|
||||||
hikari: true
|
hikari: true
|
||||||
jvm: true
|
jvm: true
|
||||||
simple:
|
jmx:
|
||||||
metrics:
|
enabled: true
|
||||||
export:
|
|
||||||
enabled: true
|
|
||||||
|
|||||||
@ -42,7 +42,7 @@ public class AdxpFlightServiceHttpClient implements org.springframework.beans.fa
|
|||||||
private final RestTemplate restTemplate;
|
private final RestTemplate restTemplate;
|
||||||
private final ReentrantLock sessionLock = new ReentrantLock();
|
private final ReentrantLock sessionLock = new ReentrantLock();
|
||||||
|
|
||||||
|
private String sessionId;
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
|
|
||||||
public AdxpFlightServiceHttpClient(FlightSdkProperties properties) {
|
public AdxpFlightServiceHttpClient(FlightSdkProperties properties) {
|
||||||
@ -62,39 +62,91 @@ public class AdxpFlightServiceHttpClient implements org.springframework.beans.fa
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.baseUrl = String.format("http://%s:%d/api/adxp", properties.getHost(), properties.getPort());
|
this.baseUrl = String.format("http://%s:%d/api/adxp", properties.getHost(), properties.getPort());
|
||||||
log.info("ADXP HTTP客户端已初始化,等待连接状态确认");
|
tryLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
log.info("ADXP HTTP客户端已销毁");
|
sessionLock.lock();
|
||||||
|
try {
|
||||||
|
if (sessionId != null) {
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
sessionId = null;
|
||||||
|
sessionLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disconnect() {
|
private void tryLogin() {
|
||||||
|
sessionLock.lock();
|
||||||
try {
|
try {
|
||||||
log.info("正在断开 ADXP 适配器连接");
|
log.info("正在登录 ADXP 适配器服务: url={}", baseUrl);
|
||||||
|
|
||||||
|
// 登录适配器服务,适配器会使用这些认证信息连接真实数据中台
|
||||||
|
Map<String, Object> loginRequest = new HashMap<>();
|
||||||
|
loginRequest.put("username", properties.getUsername());
|
||||||
|
loginRequest.put("password", properties.getPassword());
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(new HashMap<>(), headers);
|
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(loginRequest, headers);
|
||||||
|
|
||||||
restTemplate.exchange(
|
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
|
||||||
baseUrl + "/disconnect",
|
baseUrl + "/login",
|
||||||
HttpMethod.POST,
|
HttpMethod.POST,
|
||||||
entity,
|
entity,
|
||||||
new ParameterizedTypeReference<Map<String, Object>>() {}
|
new ParameterizedTypeReference<Map<String, Object>>() {}
|
||||||
);
|
);
|
||||||
|
|
||||||
log.info("已断开 ADXP 适配器连接");
|
Map<String, Object> body = response.getBody();
|
||||||
|
if (body != null && Boolean.TRUE.equals(body.get("success"))) {
|
||||||
|
this.sessionId = (String) body.get("sessionId");
|
||||||
|
log.info("已登录 ADXP 适配器服务: sessionId={}", sessionId);
|
||||||
|
} else {
|
||||||
|
String message = body != null ? (String) body.get("message") : "Unknown error";
|
||||||
|
log.warn("登录 ADXP 适配器服务失败: {}", message);
|
||||||
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("断开 ADXP 适配器连接失败", e);
|
log.warn("登录 ADXP 适配器服务失败,适配器服务将暂时不可用", e);
|
||||||
|
} finally {
|
||||||
|
sessionLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logout() {
|
||||||
|
try {
|
||||||
|
log.info("正在登出 ADXP 适配器服务: sessionId={}", sessionId);
|
||||||
|
|
||||||
|
Map<String, String> logoutRequest = new HashMap<>();
|
||||||
|
logoutRequest.put("sessionId", sessionId);
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
HttpEntity<Map<String, String>> entity = new HttpEntity<>(logoutRequest, headers);
|
||||||
|
|
||||||
|
restTemplate.exchange(
|
||||||
|
baseUrl + "/logout",
|
||||||
|
HttpMethod.POST,
|
||||||
|
entity,
|
||||||
|
new ParameterizedTypeReference<Map<String, String>>() {}
|
||||||
|
);
|
||||||
|
|
||||||
|
log.info("已登出 ADXP 适配器服务");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("登出 ADXP 适配器服务失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<FlightNotificationDTO> fetchFlightNotifications() {
|
public List<FlightNotificationDTO> fetchFlightNotifications() {
|
||||||
|
if (sessionId == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
sessionLock.lock();
|
sessionLock.lock();
|
||||||
try {
|
try {
|
||||||
String url = baseUrl + "/messages";
|
String url = baseUrl + "/messages?sessionId=" + sessionId;
|
||||||
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
|
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
|
||||||
url,
|
url,
|
||||||
HttpMethod.GET,
|
HttpMethod.GET,
|
||||||
@ -106,6 +158,11 @@ public class AdxpFlightServiceHttpClient implements org.springframework.beans.fa
|
|||||||
if (body == null || !Boolean.TRUE.equals(body.get("success"))) {
|
if (body == null || !Boolean.TRUE.equals(body.get("success"))) {
|
||||||
String message = body != null ? (String) body.get("message") : "Unknown error";
|
String message = body != null ? (String) body.get("message") : "Unknown error";
|
||||||
log.warn("接收消息失败: {}", message);
|
log.warn("接收消息失败: {}", message);
|
||||||
|
|
||||||
|
// 如果是 session 过期,尝试重新登录
|
||||||
|
if (message != null && message.contains("Session")) {
|
||||||
|
tryLogin();
|
||||||
|
}
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,60 +313,6 @@ public class AdxpFlightServiceHttpClient implements org.springframework.beans.fa
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
// 检查适配器是否连接到了ADXP数据中台
|
return sessionId != null;
|
||||||
return isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查ADXP适配器连接状态
|
|
||||||
*/
|
|
||||||
public boolean isConnected() {
|
|
||||||
try {
|
|
||||||
String url = baseUrl + "/status";
|
|
||||||
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
|
|
||||||
url,
|
|
||||||
HttpMethod.GET,
|
|
||||||
null,
|
|
||||||
new ParameterizedTypeReference<Map<String, Object>>() {}
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, Object> body = response.getBody();
|
|
||||||
return body != null && Boolean.TRUE.equals(body.get("connected"));
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("检查ADXP适配器连接状态失败", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 强制重连ADXP适配器
|
|
||||||
*/
|
|
||||||
public boolean reconnect() {
|
|
||||||
try {
|
|
||||||
String url = baseUrl + "/reconnect";
|
|
||||||
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
|
|
||||||
url,
|
|
||||||
HttpMethod.POST,
|
|
||||||
null,
|
|
||||||
new ParameterizedTypeReference<Map<String, Object>>() {}
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, Object> body = response.getBody();
|
|
||||||
boolean success = body != null && Boolean.TRUE.equals(body.get("success"));
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
log.info("✅ 已强制重连ADXP适配器");
|
|
||||||
} else {
|
|
||||||
String message = body != null ? (String) body.get("message") : "Unknown error";
|
|
||||||
log.warn("❌ 重连ADXP适配器失败: {}", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("❌ 重连ADXP适配器时发生异常", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import org.springframework.web.socket.WebSocketHandler;
|
|||||||
import org.springframework.web.socket.WebSocketMessage;
|
import org.springframework.web.socket.WebSocketMessage;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
import org.springframework.web.socket.client.WebSocketClient;
|
import org.springframework.web.socket.client.WebSocketClient;
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -35,7 +34,7 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
|||||||
private final AtomicLong errorCount = new AtomicLong(0);
|
private final AtomicLong errorCount = new AtomicLong(0);
|
||||||
|
|
||||||
private WebSocketSession session;
|
private WebSocketSession session;
|
||||||
|
private String sessionId;
|
||||||
private Thread reconnectThread;
|
private Thread reconnectThread;
|
||||||
|
|
||||||
public AdxpFlightServiceWebSocketClient(WebSocketClient webSocketClient,
|
public AdxpFlightServiceWebSocketClient(WebSocketClient webSocketClient,
|
||||||
@ -64,37 +63,39 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
|||||||
public void stop() {
|
public void stop() {
|
||||||
if (isRunning.compareAndSet(true, false)) {
|
if (isRunning.compareAndSet(true, false)) {
|
||||||
log.info("停止ADXP航班通知WebSocket客户端");
|
log.info("停止ADXP航班通知WebSocket客户端");
|
||||||
disconnectWebSocket();
|
disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接到ADXP适配器的WebSocket服务
|
* 连接到ADXP适配器的WebSocket服务
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("removal") // 抑制WebSocketClient.doHandshake过期警告
|
|
||||||
public void connect() {
|
public void connect() {
|
||||||
if (!properties.isConfigurationReady()) {
|
if (!properties.isConfigurationReady()) {
|
||||||
log.warn("数据中台航班 SDK 配置不完整,WebSocket客户端将无法正常工作");
|
log.warn("数据中台航班 SDK 配置不完整,WebSocket客户端将无法正常工作");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 构建WebSocket URL - 新架构下不需要sessionId
|
// 首先通过HTTP登录获取sessionId
|
||||||
String wsUrl = String.format("ws://%s:%d/ws/flight-notifications",
|
sessionId = loginAndGetSessionId();
|
||||||
properties.getHost(), properties.getPort());
|
if (sessionId == null) {
|
||||||
|
log.error("无法获取sessionId,WebSocket连接失败");
|
||||||
log.info("正在连接到ADXP适配器WebSocket服务: url={}", wsUrl);
|
scheduleReconnect();
|
||||||
|
return;
|
||||||
// 连接WebSocket - 使用doHandshake(虽过期但仍可用)
|
|
||||||
WebSocketSession newSession = webSocketClient.doHandshake(this, URI.create(wsUrl).toString()).get();
|
|
||||||
|
|
||||||
// 在afterConnectionEstablished中也会设置session,这里只是避免编译警告
|
|
||||||
if (newSession != null && newSession.isOpen()) {
|
|
||||||
session = newSession;
|
|
||||||
isConnected.set(true);
|
|
||||||
log.info("✅ 已连接到ADXP适配器WebSocket服务");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构建WebSocket URL
|
||||||
|
String wsUrl = String.format("ws://%s:%d/ws/flight-notifications",
|
||||||
|
properties.getHost(), properties.getPort());
|
||||||
|
|
||||||
|
log.info("正在连接到ADXP适配器WebSocket服务: url={}", wsUrl);
|
||||||
|
|
||||||
|
// 连接WebSocket
|
||||||
|
session = webSocketClient.doHandshake(this, URI.create(wsUrl).toString()).get();
|
||||||
|
isConnected.set(true);
|
||||||
|
log.info("✅ 已连接到ADXP适配器WebSocket服务");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("❌ 连接ADXP适配器WebSocket服务失败", e);
|
log.error("❌ 连接ADXP适配器WebSocket服务失败", e);
|
||||||
errorCount.incrementAndGet();
|
errorCount.incrementAndGet();
|
||||||
@ -103,20 +104,68 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过HTTP登录获取sessionId
|
||||||
|
*/
|
||||||
|
private String loginAndGetSessionId() {
|
||||||
|
try {
|
||||||
|
String baseUrl = String.format("http://%s:%d/api/adxp",
|
||||||
|
properties.getHost(), properties.getPort());
|
||||||
|
|
||||||
|
// 创建登录请求
|
||||||
|
java.util.Map<String, Object> loginRequest = new java.util.HashMap<>();
|
||||||
|
loginRequest.put("username", properties.getUsername());
|
||||||
|
loginRequest.put("password", properties.getPassword());
|
||||||
|
|
||||||
|
// 发送HTTP POST请求
|
||||||
|
java.net.http.HttpClient httpClient = java.net.http.HttpClient.newHttpClient();
|
||||||
|
String loginUrl = baseUrl + "/login";
|
||||||
|
|
||||||
|
String requestBody = objectMapper.writeValueAsString(loginRequest);
|
||||||
|
java.net.http.HttpRequest request = java.net.http.HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(loginUrl))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.POST(java.net.http.HttpRequest.BodyPublishers.ofString(requestBody))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
java.net.http.HttpResponse<String> response = httpClient.send(request,
|
||||||
|
java.net.http.HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
if (response.statusCode() == 200) {
|
||||||
|
java.util.Map<String, Object> responseBody = objectMapper.readValue(
|
||||||
|
response.body(), new TypeReference<java.util.Map<String, Object>>() {});
|
||||||
|
|
||||||
|
if (Boolean.TRUE.equals(responseBody.get("success"))) {
|
||||||
|
String sessionId = (String) responseBody.get("sessionId");
|
||||||
|
log.info("✅ 登录ADXP适配器成功: sessionId={}", sessionId);
|
||||||
|
return sessionId;
|
||||||
|
} else {
|
||||||
|
String message = (String) responseBody.get("message");
|
||||||
|
log.warn("❌ 登录ADXP适配器失败: {}", message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("❌ 登录ADXP适配器HTTP请求失败: status={}", response.statusCode());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("❌ 登录ADXP适配器时发生异常", e);
|
||||||
|
errorCount.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 断开WebSocket连接
|
* 断开WebSocket连接
|
||||||
*/
|
*/
|
||||||
public void disconnectWebSocket() {
|
public void disconnect() {
|
||||||
try {
|
try {
|
||||||
if (session != null && session.isOpen()) {
|
if (session != null && session.isOpen()) {
|
||||||
session.close();
|
session.close();
|
||||||
log.info("WebSocket连接已关闭");
|
log.info("WebSocket连接已关闭");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 断开ADXP适配器连接
|
// 尝试登出
|
||||||
disconnectADXP();
|
logout();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("断开WebSocket连接时发生异常", e);
|
log.error("断开WebSocket连接时发生异常", e);
|
||||||
errorCount.incrementAndGet();
|
errorCount.incrementAndGet();
|
||||||
@ -127,33 +176,42 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 断开ADXP适配器连接
|
* 登出
|
||||||
*/
|
*/
|
||||||
private void disconnectADXP() {
|
private void logout() {
|
||||||
|
if (sessionId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String baseUrl = String.format("http://%s:%d/api/adxp",
|
String baseUrl = String.format("http://%s:%d/api/adxp",
|
||||||
properties.getHost(), properties.getPort());
|
properties.getHost(), properties.getPort());
|
||||||
|
|
||||||
|
// 创建登出请求
|
||||||
|
java.util.Map<String, String> logoutRequest = new java.util.HashMap<>();
|
||||||
|
logoutRequest.put("sessionId", sessionId);
|
||||||
|
|
||||||
// 发送HTTP POST请求
|
// 发送HTTP POST请求
|
||||||
java.net.http.HttpClient httpClient = java.net.http.HttpClient.newHttpClient();
|
java.net.http.HttpClient httpClient = java.net.http.HttpClient.newHttpClient();
|
||||||
String disconnectUrl = baseUrl + "/disconnect";
|
String logoutUrl = baseUrl + "/logout";
|
||||||
|
|
||||||
|
String requestBody = objectMapper.writeValueAsString(logoutRequest);
|
||||||
java.net.http.HttpRequest request = java.net.http.HttpRequest.newBuilder()
|
java.net.http.HttpRequest request = java.net.http.HttpRequest.newBuilder()
|
||||||
.uri(URI.create(disconnectUrl))
|
.uri(URI.create(logoutUrl))
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.POST(java.net.http.HttpRequest.BodyPublishers.ofString("{}"))
|
.POST(java.net.http.HttpRequest.BodyPublishers.ofString(requestBody))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
httpClient.send(request, java.net.http.HttpResponse.BodyHandlers.ofString());
|
httpClient.send(request, java.net.http.HttpResponse.BodyHandlers.ofString());
|
||||||
log.info("✅ 已断开ADXP适配器连接");
|
log.info("✅ 已登出ADXP适配器服务");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("断开ADXP适配器连接失败", e);
|
log.warn("登出ADXP适配器服务失败", e);
|
||||||
errorCount.incrementAndGet();
|
errorCount.incrementAndGet();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterConnectionEstablished(@NonNull WebSocketSession session) throws Exception {
|
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||||
log.info("🟢 WebSocket连接已建立: sessionId={}", session.getId());
|
log.info("🟢 WebSocket连接已建立: sessionId={}", session.getId());
|
||||||
this.session = session;
|
this.session = session;
|
||||||
isConnected.set(true);
|
isConnected.set(true);
|
||||||
@ -161,7 +219,7 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(@NonNull WebSocketSession session, @NonNull WebSocketMessage<?> message) throws Exception {
|
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
|
||||||
if (message instanceof TextMessage) {
|
if (message instanceof TextMessage) {
|
||||||
handleTextMessage(session, (TextMessage) message);
|
handleTextMessage(session, (TextMessage) message);
|
||||||
}
|
}
|
||||||
@ -201,7 +259,7 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleTransportError(@NonNull WebSocketSession session, @NonNull Throwable exception) throws Exception {
|
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
|
||||||
log.error("❌ WebSocket传输错误", exception);
|
log.error("❌ WebSocket传输错误", exception);
|
||||||
errorCount.incrementAndGet();
|
errorCount.incrementAndGet();
|
||||||
isConnected.set(false);
|
isConnected.set(false);
|
||||||
@ -214,7 +272,7 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterConnectionClosed(@NonNull WebSocketSession session, @NonNull CloseStatus closeStatus) throws Exception {
|
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
|
||||||
log.info("🟡 WebSocket连接已关闭: reason={}, code={}", closeStatus.getReason(), closeStatus.getCode());
|
log.info("🟡 WebSocket连接已关闭: reason={}, code={}", closeStatus.getReason(), closeStatus.getCode());
|
||||||
isConnected.set(false);
|
isConnected.set(false);
|
||||||
this.session = null;
|
this.session = null;
|
||||||
@ -386,14 +444,6 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
|||||||
public boolean isConnected() {
|
public boolean isConnected() {
|
||||||
return isConnected.get();
|
return isConnected.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否启用
|
|
||||||
* 主动连接架构下,返回连接状态
|
|
||||||
*/
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 安排重新连接
|
* 安排重新连接
|
||||||
|
|||||||
@ -231,42 +231,16 @@ public class WebSocketMessageBroadcaster {
|
|||||||
// 使用Jackson ObjectMapper将UniversalMessage序列化为JSON字符串
|
// 使用Jackson ObjectMapper将UniversalMessage序列化为JSON字符串
|
||||||
// 这样前端可以获得消息类型、时间戳、消息ID等完整信息
|
// 这样前端可以获得消息类型、时间戳、消息ID等完整信息
|
||||||
String jsonMessage = objectMapper.writeValueAsString(message);
|
String jsonMessage = objectMapper.writeValueAsString(message);
|
||||||
this.collisionWebSocketHandler.broadcastMessage(jsonMessage);
|
this.collisionWebSocketHandler.broadcastMessage(jsonMessage);
|
||||||
|
|
||||||
// 根据消息类型决定是否缓存(避免高频消息阻塞Redis)
|
// 缓存消息用于重连恢复
|
||||||
// 高频实时消息(如位置更新)不缓存,追求实时性
|
messageCacheService.cacheMessage(message);
|
||||||
// 低频重要消息(如碰撞预警)缓存,保证不丢失
|
|
||||||
if (shouldCacheMessage(message.getType())) {
|
|
||||||
messageCacheService.cacheMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("Failed to broadcast message via native WebSocket: " + e.getMessage());
|
System.err.println("Failed to broadcast message via native WebSocket: " + e.getMessage());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断消息是否应该缓存
|
|
||||||
*
|
|
||||||
* @param messageType 消息类型
|
|
||||||
* @return true=需要缓存,false=不需要缓存
|
|
||||||
*/
|
|
||||||
private boolean shouldCacheMessage(String messageType) {
|
|
||||||
// 高频实时消息 - 不缓存(追求实时性,避免Redis性能瓶颈)
|
|
||||||
// 这些消息更新频繁,用户关心的是最新状态,历史数据价值不高
|
|
||||||
if (MessageTypeConstants.POSITION_UPDATE.equals(messageType) ||
|
|
||||||
MessageTypeConstants.TRAFFIC_LIGHT_STATUS.equals(messageType) ||
|
|
||||||
MessageTypeConstants.HEARTBEAT.equals(messageType) ||
|
|
||||||
MessageTypeConstants.VEHICLE_STATUS_UPDATE.equals(messageType)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 低频重要消息 - 缓存(保证不丢失)
|
|
||||||
// 包括:碰撞预警、规则违规、路径冲突、电子围栏、航班通知、车辆指令等
|
|
||||||
// 这些事件频率低但重要性高,用户需要完整的历史记录
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成唯一消息ID
|
* 生成唯一消息ID
|
||||||
|
|||||||
@ -7,8 +7,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 冲突检测WebSocket处理器
|
* 冲突检测WebSocket处理器
|
||||||
@ -101,7 +99,7 @@ public class CollisionWebSocketHandler implements WebSocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送消息给指定会话(线程安全)- 添加发送失败跳过机制
|
* 发送消息给指定会话(线程安全)
|
||||||
*/
|
*/
|
||||||
private void sendMessage(WebSocketSession session, String message) {
|
private void sendMessage(WebSocketSession session, String message) {
|
||||||
try {
|
try {
|
||||||
@ -114,66 +112,15 @@ public class CollisionWebSocketHandler implements WebSocketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 发送失败时,快速清理无效会话,避免影响其他会话
|
LOGGER.error("发送消息失败 - 会话ID: {}", session.getId(), e);
|
||||||
// 使用快速失败策略,不重试,避免加重系统负载
|
|
||||||
String sessionId = session.getId();
|
|
||||||
LOGGER.warn("发送消息失败,快速跳过 - 会话ID: {}, 错误: {}", sessionId, e.getMessage());
|
|
||||||
|
|
||||||
// 异步清理无效会话(不阻塞主流程)
|
|
||||||
try {
|
|
||||||
sessions.remove(sessionId);
|
|
||||||
if (session.isOpen()) {
|
|
||||||
session.close(CloseStatus.SERVICE_RESTARTED);
|
|
||||||
}
|
|
||||||
} catch (Exception closeEx) {
|
|
||||||
LOGGER.debug("关闭无效会话失败 - 会话ID: {}, 错误: {}", sessionId, closeEx.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 广播消息给所有连接的客户端 - 添加分批发送机制
|
* 广播消息给所有连接的客户端
|
||||||
* 避免一次发送过多消息导致前端卡死
|
|
||||||
*/
|
*/
|
||||||
public void broadcastMessage(String message) {
|
public void broadcastMessage(String message) {
|
||||||
// 获取所有活跃会话
|
sessions.values().forEach(session -> sendMessage(session, message));
|
||||||
List<WebSocketSession> activeSessions = sessions.values().stream()
|
|
||||||
.filter(WebSocketSession::isOpen)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (activeSessions.isEmpty()) {
|
|
||||||
LOGGER.debug("没有活跃会话,跳过广播");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 控制单次发送数量,避免前端过载
|
|
||||||
int batchSize = 10; // 每批最多发送10个会话
|
|
||||||
int totalSessions = activeSessions.size();
|
|
||||||
|
|
||||||
LOGGER.debug("开始广播消息,总会话数: {}, 分批大小: {}", totalSessions, batchSize);
|
|
||||||
|
|
||||||
// 分批发送
|
|
||||||
for (int i = 0; i < totalSessions; i += batchSize) {
|
|
||||||
int endIndex = Math.min(i + batchSize, totalSessions);
|
|
||||||
List<WebSocketSession> batch = activeSessions.subList(i, endIndex);
|
|
||||||
|
|
||||||
LOGGER.debug("发送批次 {}-{} (共{}个会话)", i + 1, endIndex, batch.size());
|
|
||||||
|
|
||||||
// 发送当前批次
|
|
||||||
batch.forEach(session -> sendMessage(session, message));
|
|
||||||
|
|
||||||
// 如果还有更多批次,短暂休眠避免过载
|
|
||||||
if (endIndex < totalSessions) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(10); // 休眠10毫秒
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
LOGGER.warn("广播线程休眠被中断", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOGGER.debug("广播消息完成,总会话数: {}", totalSessions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,41 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# Flyway 迁移详细信息查看脚本
|
|
||||||
# 显示迁移历史、当前状态等详细信息
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
echo "📊 Flyway 数据库迁移详细信息"
|
|
||||||
echo "=========================================="
|
|
||||||
|
|
||||||
# 切换到项目根目录
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
cd qaup-admin
|
|
||||||
|
|
||||||
# 显示Flyway Info详细信息
|
|
||||||
echo "🔍 迁移历史和状态:"
|
|
||||||
mvn flyway:info \
|
|
||||||
-Dspring.profiles.active=dev \
|
|
||||||
-Dflyway.url=jdbc:postgresql://10.0.0.58:5432/qaup \
|
|
||||||
-Dflyway.user=qaup \
|
|
||||||
-Dflyway.password=qaup123 \
|
|
||||||
-q
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "=========================================="
|
|
||||||
echo "📋 常用 Flyway 命令参考:"
|
|
||||||
echo ""
|
|
||||||
echo "1. 查看迁移状态:"
|
|
||||||
echo " ./check-flyway-info.sh"
|
|
||||||
echo ""
|
|
||||||
echo "2. 验证迁移脚本:"
|
|
||||||
echo " ./check-flyway.sh"
|
|
||||||
echo ""
|
|
||||||
echo "3. 执行迁移 (谨慎使用):"
|
|
||||||
echo " mvn flyway:migrate -Dspring.profiles.active=dev -Dflyway.url=jdbc:postgresql://10.0.0.58:5432/qaup -Dflyway.user=qaup -Dflyway.password=qaup123"
|
|
||||||
echo ""
|
|
||||||
echo "4. 修复迁移记录 (谨慎使用):"
|
|
||||||
echo " mvn flyway:repair -Dspring.profiles.active=dev -Dflyway.url=jdbc:postgresql://10.0.0.58:5432/qaup -Dflyway.user=qaup -Dflyway.password=qaup123"
|
|
||||||
echo ""
|
|
||||||
echo "5. 基线迁移 (谨慎使用):"
|
|
||||||
echo " mvn flyway:baseline -Dspring.profiles.active=dev -Dflyway.url=jdbc:postgresql://10.0.0.58:5432/qaup -Dflyway.user=qaup -Dflyway.password=qaup123"
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# ============================================================
|
|
||||||
# Flyway 数据库迁移状态检查脚本
|
|
||||||
# 用于验证数据库结构与迁移脚本的一致性
|
|
||||||
# ============================================================
|
|
||||||
|
|
||||||
echo "🔍 正在检查 Flyway 数据库迁移状态..."
|
|
||||||
echo "📊 数据库连接信息:"
|
|
||||||
echo " 主机: 10.0.0.58"
|
|
||||||
echo " 端口: 5432"
|
|
||||||
echo " 数据库: qaup"
|
|
||||||
echo " 用户: qaup"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# 切换到项目根目录
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
cd qaup-admin
|
|
||||||
|
|
||||||
# 执行Flyway验证命令
|
|
||||||
echo "🚀 正在执行 Flyway 验证..."
|
|
||||||
mvn flyway:validate \
|
|
||||||
-Dspring.profiles.active=dev \
|
|
||||||
-Dflyway.url=jdbc:postgresql://10.0.0.58:5432/qaup \
|
|
||||||
-Dflyway.user=qaup \
|
|
||||||
-Dflyway.password=qaup123 \
|
|
||||||
-q
|
|
||||||
|
|
||||||
# 检查执行结果
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo ""
|
|
||||||
echo "✅ Flyway 验证成功!"
|
|
||||||
echo "📝 建议:"
|
|
||||||
echo " - 数据库结构与迁移脚本一致"
|
|
||||||
echo " - 没有发现 schema drift"
|
|
||||||
echo " - 可以安全进行部署"
|
|
||||||
echo ""
|
|
||||||
echo "🔍 详细信息请查看上方输出"
|
|
||||||
else
|
|
||||||
echo ""
|
|
||||||
echo "❌ Flyway 验证失败!"
|
|
||||||
echo "🔧 建议:"
|
|
||||||
echo " - 检查数据库连接"
|
|
||||||
echo " - 验证迁移脚本是否被修改"
|
|
||||||
echo " - 查看详细错误信息"
|
|
||||||
echo " - 联系开发团队进行修复"
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "📋 其他有用的Flyway命令:"
|
|
||||||
echo " 查看迁移状态: ./check-flyway-info.sh"
|
|
||||||
echo " 执行迁移: mvn flyway:migrate -Dspring.profiles.active=dev -Dflyway.url=jdbc:postgresql://10.0.0.58:5432/qaup -Dflyway.user=qaup -Dflyway.password=qaup123"
|
|
||||||
BIN
tools/__pycache__/logging_config.cpython-313.pyc
Normal file
BIN
tools/__pycache__/logging_config.cpython-313.pyc
Normal file
Binary file not shown.
BIN
tools/__pycache__/mock_airport.cpython-313.pyc
Normal file
BIN
tools/__pycache__/mock_airport.cpython-313.pyc
Normal file
Binary file not shown.
BIN
tools/__pycache__/mock_unmanned_vehicle.cpython-313.pyc
Normal file
BIN
tools/__pycache__/mock_unmanned_vehicle.cpython-313.pyc
Normal file
Binary file not shown.
@ -5,7 +5,6 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
import atexit
|
import atexit
|
||||||
import random
|
|
||||||
from typing import Any, Literal, final, TypedDict
|
from typing import Any, Literal, final, TypedDict
|
||||||
|
|
||||||
# 导入统一的日志配置
|
# 导入统一的日志配置
|
||||||
@ -201,7 +200,7 @@ def get_aircraft_route_params():
|
|||||||
"data": None
|
"data": None
|
||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
logger.info(f"进港路由参数查询: flightNo={flight_no}, params={route_params}")
|
logging.info(f"进港路由参数查询: flightNo={flight_no}, params={route_params}")
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"msg": "进港路由参数查询成功",
|
"msg": "进港路由参数查询成功",
|
||||||
@ -225,7 +224,7 @@ def get_aircraft_route_params():
|
|||||||
"data": None
|
"data": None
|
||||||
}), 404
|
}), 404
|
||||||
|
|
||||||
logger.info(f"出港路由参数查询: flightNo={flight_no}, params={route_params}")
|
logging.info(f"出港路由参数查询: flightNo={flight_no}, params={route_params}")
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"msg": "出港路由参数查询成功",
|
"msg": "出港路由参数查询成功",
|
||||||
@ -959,8 +958,8 @@ class AirportCoordinateSystem:
|
|||||||
print("警告: pyproj库未安装,将回退到简化转换算法")
|
print("警告: pyproj库未安装,将回退到简化转换算法")
|
||||||
self.use_pyproj = False
|
self.use_pyproj = False
|
||||||
# 回退到原来的参数
|
# 回退到原来的参数
|
||||||
self.center_lon = 120.08782536
|
self.center_lon = 120.0834104
|
||||||
self.center_lat = 36.36236547
|
self.center_lat = 36.35406879
|
||||||
self.utm_origin_x = 40507423
|
self.utm_origin_x = 40507423
|
||||||
self.utm_origin_y = 4026164
|
self.utm_origin_y = 4026164
|
||||||
self.meters_per_degree_lon = 89932
|
self.meters_per_degree_lon = 89932
|
||||||
@ -1069,11 +1068,11 @@ def convert_route_to_cgcs2000(route_data: dict) -> dict:
|
|||||||
try:
|
try:
|
||||||
# 检查当前坐标系类型
|
# 检查当前坐标系类型
|
||||||
current_coordinate_system = route_data.get("coordinateSystem", "WGS84")
|
current_coordinate_system = route_data.get("coordinateSystem", "WGS84")
|
||||||
logger.info(f"转换路由数据坐标系: 当前={current_coordinate_system} -> 目标=CGCS2000")
|
logging.info(f"转换路由数据坐标系: 当前={current_coordinate_system} -> 目标=CGCS2000")
|
||||||
|
|
||||||
# 如果已经是CGCS2000坐标系,直接返回
|
# 如果已经是CGCS2000坐标系,直接返回
|
||||||
if current_coordinate_system == "CGCS2000":
|
if current_coordinate_system == "CGCS2000":
|
||||||
logger.info("路由数据已经是CGCS2000坐标系,无需转换")
|
logging.info("路由数据已经是CGCS2000坐标系,无需转换")
|
||||||
return converted_route
|
return converted_route
|
||||||
|
|
||||||
# 如果是WGS84坐标系,需要转换为CGCS2000
|
# 如果是WGS84坐标系,需要转换为CGCS2000
|
||||||
@ -1081,7 +1080,7 @@ def convert_route_to_cgcs2000(route_data: dict) -> dict:
|
|||||||
geo_path = converted_route.get("geoPath", {})
|
geo_path = converted_route.get("geoPath", {})
|
||||||
features = geo_path.get("features", [])
|
features = geo_path.get("features", [])
|
||||||
|
|
||||||
logger.info(f"开始转换WGS84坐标到CGCS2000,共有 {len(features)} 个feature")
|
logging.info(f"开始转换WGS84坐标到CGCS2000,共有 {len(features)} 个feature")
|
||||||
|
|
||||||
for feature in features:
|
for feature in features:
|
||||||
geometry = feature.get("geometry", {})
|
geometry = feature.get("geometry", {})
|
||||||
@ -1099,7 +1098,7 @@ def convert_route_to_cgcs2000(route_data: dict) -> dict:
|
|||||||
x, y = coordinate_system.wgs84_to_cgcs2000(latitude, longitude)
|
x, y = coordinate_system.wgs84_to_cgcs2000(latitude, longitude)
|
||||||
converted_coordinates.append([x, y])
|
converted_coordinates.append([x, y])
|
||||||
|
|
||||||
logger.debug(f"WGS84坐标转换: lon={longitude}, lat={latitude} -> CGCS2000: x={x}, y={y}")
|
logging.debug(f"WGS84坐标转换: lon={longitude}, lat={latitude} -> CGCS2000: x={x}, y={y}")
|
||||||
|
|
||||||
# 更新坐标数据
|
# 更新坐标数据
|
||||||
geometry["coordinates"] = converted_coordinates
|
geometry["coordinates"] = converted_coordinates
|
||||||
@ -1107,12 +1106,12 @@ def convert_route_to_cgcs2000(route_data: dict) -> dict:
|
|||||||
# 更新坐标系标识
|
# 更新坐标系标识
|
||||||
converted_route["coordinateSystem"] = "CGCS2000"
|
converted_route["coordinateSystem"] = "CGCS2000"
|
||||||
|
|
||||||
logger.info(f"WGS84到CGCS2000坐标转换完成,共转换 {len(features)} 个feature")
|
logging.info(f"WGS84到CGCS2000坐标转换完成,共转换 {len(features)} 个feature")
|
||||||
|
|
||||||
return converted_route
|
return converted_route
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"路由数据坐标转换失败: {str(e)}")
|
logging.error(f"路由数据坐标转换失败: {str(e)}")
|
||||||
# 转换失败时返回原始数据,但确保坐标系标识为CGCS2000
|
# 转换失败时返回原始数据,但确保坐标系标识为CGCS2000
|
||||||
converted_route["coordinateSystem"] = "CGCS2000"
|
converted_route["coordinateSystem"] = "CGCS2000"
|
||||||
return converted_route
|
return converted_route
|
||||||
@ -1135,9 +1134,9 @@ def merge_discontinuous_route_for_flight(route_data: dict, flight_no: str) -> di
|
|||||||
codes_str = route_data.get("codes", "")
|
codes_str = route_data.get("codes", "")
|
||||||
if codes_str:
|
if codes_str:
|
||||||
code_order = [code.strip() for code in codes_str.split(",")]
|
code_order = [code.strip() for code in codes_str.split(",")]
|
||||||
logger.info(f"航班 {flight_no} codes顺序: {code_order}")
|
logging.info(f"航班 {flight_no} codes顺序: {code_order}")
|
||||||
else:
|
else:
|
||||||
logger.warning(f"航班 {flight_no} 缺少codes字段,无法验证方向")
|
logging.warning(f"航班 {flight_no} 缺少codes字段,无法验证方向")
|
||||||
code_order = []
|
code_order = []
|
||||||
|
|
||||||
# 收集所有LineString路径段,并记录每段的code标识
|
# 收集所有LineString路径段,并记录每段的code标识
|
||||||
@ -1161,7 +1160,7 @@ def merge_discontinuous_route_for_flight(route_data: dict, flight_no: str) -> di
|
|||||||
# 如果成功合并为单条路径
|
# 如果成功合并为单条路径
|
||||||
if isinstance(merged_geometry, LineString):
|
if isinstance(merged_geometry, LineString):
|
||||||
continuous_coords = list(merged_geometry.coords)
|
continuous_coords = list(merged_geometry.coords)
|
||||||
logger.info(f"✅ 航班 {flight_no} 合并为连续路径,包含 {len(continuous_coords)} 个坐标点")
|
logging.info(f"✅ 航班 {flight_no} 合并为连续路径,包含 {len(continuous_coords)} 个坐标点")
|
||||||
|
|
||||||
# 基于codes字段验证和修正路径方向
|
# 基于codes字段验证和修正路径方向
|
||||||
if len(code_order) >= 2:
|
if len(code_order) >= 2:
|
||||||
@ -1182,14 +1181,14 @@ def merge_discontinuous_route_for_flight(route_data: dict, flight_no: str) -> di
|
|||||||
else:
|
else:
|
||||||
# 无法合并就报错,不能保持原样掩盖问题
|
# 无法合并就报错,不能保持原样掩盖问题
|
||||||
error_msg = f"❌ 航班 {flight_no} 路径无法合并为连续路径,存在不连续段"
|
error_msg = f"❌ 航班 {flight_no} 路径无法合并为连续路径,存在不连续段"
|
||||||
logger.error(error_msg)
|
logging.error(error_msg)
|
||||||
raise RuntimeError(error_msg)
|
raise RuntimeError(error_msg)
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.warning(f"Shapely库未安装,跳过路径合并")
|
logging.warning(f"Shapely库未安装,跳过路径合并")
|
||||||
return route_data
|
return route_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"航班 {flight_no} 路径合并失败: {str(e)}")
|
logging.error(f"航班 {flight_no} 路径合并失败: {str(e)}")
|
||||||
return route_data
|
return route_data
|
||||||
|
|
||||||
|
|
||||||
@ -1224,26 +1223,26 @@ def _verify_and_correct_path_direction(continuous_coords: list, original_feature
|
|||||||
start_code = find_point_code(continuous_coords[0])
|
start_code = find_point_code(continuous_coords[0])
|
||||||
end_code = find_point_code(continuous_coords[-1])
|
end_code = find_point_code(continuous_coords[-1])
|
||||||
|
|
||||||
logger.info(f"航班 {flight_no} 端点检查: 起点在{start_code}, 终点在{end_code}, 期望顺序{code_order}")
|
logging.info(f"航班 {flight_no} 端点检查: 起点在{start_code}, 终点在{end_code}, 期望顺序{code_order}")
|
||||||
|
|
||||||
# 检查方向是否正确 - 只使用实际存在的路径段codes进行判断
|
# 检查方向是否正确 - 只使用实际存在的路径段codes进行判断
|
||||||
expected_start = code_order[0] # F1
|
expected_start = code_order[0] # F1
|
||||||
# 对于MU5123进港,应该从F1开始,但终点138不是路径段code,所以检查起点即可
|
# 对于MU5123进港,应该从F1开始,但终点138不是路径段code,所以检查起点即可
|
||||||
|
|
||||||
logger.info(f"航班 {flight_no} 方向检查: 期望从{expected_start}开始, 实际起点{start_code}, 终点{end_code}")
|
logging.info(f"航班 {flight_no} 方向检查: 期望从{expected_start}开始, 实际起点{start_code}, 终点{end_code}")
|
||||||
|
|
||||||
# 通用路径方向检查逻辑
|
# 通用路径方向检查逻辑
|
||||||
if start_code == expected_start:
|
if start_code == expected_start:
|
||||||
logger.info(f"✅ 航班 {flight_no} 路径方向正确: 从{expected_start}开始")
|
logging.info(f"✅ 航班 {flight_no} 路径方向正确: 从{expected_start}开始")
|
||||||
else:
|
else:
|
||||||
# 方向相反,需要反转
|
# 方向相反,需要反转
|
||||||
continuous_coords.reverse()
|
continuous_coords.reverse()
|
||||||
logger.info(f"✅ 航班 {flight_no} 路径方向已修正: 从{expected_start}开始 (原起点: {start_code})")
|
logging.info(f"✅ 航班 {flight_no} 路径方向已修正: 从{expected_start}开始 (原起点: {start_code})")
|
||||||
|
|
||||||
return continuous_coords
|
return continuous_coords
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"航班 {flight_no} 路径方向验证失败: {str(e)}")
|
logging.error(f"航班 {flight_no} 路径方向验证失败: {str(e)}")
|
||||||
return continuous_coords
|
return continuous_coords
|
||||||
|
|
||||||
def validate_route_data_integrity(route_data: dict, flight_no: str) -> dict:
|
def validate_route_data_integrity(route_data: dict, flight_no: str) -> dict:
|
||||||
@ -1262,10 +1261,10 @@ def validate_route_data_integrity(route_data: dict, flight_no: str) -> dict:
|
|||||||
features = geo_path.get("features", [])
|
features = geo_path.get("features", [])
|
||||||
|
|
||||||
if len(features) == 0:
|
if len(features) == 0:
|
||||||
logger.warning(f"⚠️ 航班 {flight_no} 路径数据为空,无路径段")
|
logging.warning(f"⚠️ 航班 {flight_no} 路径数据为空,无路径段")
|
||||||
return route_data
|
return route_data
|
||||||
|
|
||||||
logger.info(f"✅ 航班 {flight_no} 路径数据完整:包含 {len(features)} 个路径段")
|
logging.info(f"✅ 航班 {flight_no} 路径数据完整:包含 {len(features)} 个路径段")
|
||||||
|
|
||||||
# 验证每个路径段的数据完整性
|
# 验证每个路径段的数据完整性
|
||||||
valid_segments = 0
|
valid_segments = 0
|
||||||
@ -1282,22 +1281,22 @@ def validate_route_data_integrity(route_data: dict, flight_no: str) -> dict:
|
|||||||
if len(coordinates) >= 2:
|
if len(coordinates) >= 2:
|
||||||
valid_segments += 1
|
valid_segments += 1
|
||||||
total_coordinates += len(coordinates)
|
total_coordinates += len(coordinates)
|
||||||
logger.debug(f" 路径段 {i+1}: 代码={code}, 坐标点数={len(coordinates)} ✅")
|
logging.debug(f" 路径段 {i+1}: 代码={code}, 坐标点数={len(coordinates)} ✅")
|
||||||
else:
|
else:
|
||||||
logger.warning(f" 路径段 {i+1}: 代码={code}, 坐标点数不足({len(coordinates)}) ⚠️")
|
logging.warning(f" 路径段 {i+1}: 代码={code}, 坐标点数不足({len(coordinates)}) ⚠️")
|
||||||
else:
|
else:
|
||||||
logger.warning(f" 路径段 {i+1}: 非LineString类型 ({geometry.get('type')}) ⚠️")
|
logging.warning(f" 路径段 {i+1}: 非LineString类型 ({geometry.get('type')}) ⚠️")
|
||||||
|
|
||||||
logger.info(f"✅ 航班 {flight_no} 数据验证完成:{valid_segments}/{len(features)} 有效路径段,共 {total_coordinates} 个坐标点")
|
logging.info(f"✅ 航班 {flight_no} 数据验证完成:{valid_segments}/{len(features)} 有效路径段,共 {total_coordinates} 个坐标点")
|
||||||
|
|
||||||
# 验证坐标系信息
|
# 验证坐标系信息
|
||||||
coordinate_system = route_data.get("coordinateSystem", "未知")
|
coordinate_system = route_data.get("coordinateSystem", "未知")
|
||||||
logger.info(f"✅ 航班 {flight_no} 坐标系:{coordinate_system}")
|
logging.info(f"✅ 航班 {flight_no} 坐标系:{coordinate_system}")
|
||||||
|
|
||||||
return route_data
|
return route_data
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"航班 {flight_no} 路径数据完整性验证异常: {str(e)}")
|
logging.error(f"航班 {flight_no} 路径数据完整性验证异常: {str(e)}")
|
||||||
return route_data
|
return route_data
|
||||||
|
|
||||||
def parse_route_path(route_data: dict) -> list[tuple[float, float]]:
|
def parse_route_path(route_data: dict) -> list[tuple[float, float]]:
|
||||||
@ -1315,12 +1314,12 @@ def parse_route_path(route_data: dict) -> list[tuple[float, float]]:
|
|||||||
try:
|
try:
|
||||||
# 检查坐标系类型
|
# 检查坐标系类型
|
||||||
coordinate_system_type = route_data.get("coordinateSystem", "CGCS2000")
|
coordinate_system_type = route_data.get("coordinateSystem", "CGCS2000")
|
||||||
logger.info(f"路由数据坐标系: {coordinate_system_type}")
|
logging.info(f"路由数据坐标系: {coordinate_system_type}")
|
||||||
|
|
||||||
geo_path = route_data.get("geoPath", {})
|
geo_path = route_data.get("geoPath", {})
|
||||||
features = geo_path.get("features", [])
|
features = geo_path.get("features", [])
|
||||||
|
|
||||||
logger.info(f"解析路径,共有 {len(features)} 个feature")
|
logging.info(f"解析路径,共有 {len(features)} 个feature")
|
||||||
|
|
||||||
for feature in features:
|
for feature in features:
|
||||||
geometry = feature.get("geometry", {})
|
geometry = feature.get("geometry", {})
|
||||||
@ -1335,23 +1334,23 @@ def parse_route_path(route_data: dict) -> list[tuple[float, float]]:
|
|||||||
if coordinate_system_type == "WGS84":
|
if coordinate_system_type == "WGS84":
|
||||||
# 如果已经是WGS84坐标,直接使用 (经度, 纬度)
|
# 如果已经是WGS84坐标,直接使用 (经度, 纬度)
|
||||||
lat, lon = y, x
|
lat, lon = y, x
|
||||||
logger.debug(f"WGS84坐标直接使用: lon={lon}, lat={lat}")
|
logging.debug(f"WGS84坐标直接使用: lon={lon}, lat={lat}")
|
||||||
else:
|
else:
|
||||||
# 如果是CGCS2000坐标,需要转换
|
# 如果是CGCS2000坐标,需要转换
|
||||||
lat, lon = coordinate_system.cgcs2000_to_wgs84(x, y)
|
lat, lon = coordinate_system.cgcs2000_to_wgs84(x, y)
|
||||||
logger.debug(f"CGCS2000坐标转换: {x},{y} -> lat={lat}, lon={lon}")
|
logging.debug(f"CGCS2000坐标转换: {x},{y} -> lat={lat}, lon={lon}")
|
||||||
|
|
||||||
path_points.append((lat, lon))
|
path_points.append((lat, lon))
|
||||||
|
|
||||||
logger.info(f"路径解析完成,坐标系={coordinate_system_type},共生成 {len(path_points)} 个坐标点")
|
logging.info(f"路径解析完成,坐标系={coordinate_system_type},共生成 {len(path_points)} 个坐标点")
|
||||||
|
|
||||||
# 暂时禁用过滤,保留所有路径点用于测试
|
# 暂时禁用过滤,保留所有路径点用于测试
|
||||||
logger.info(f"保留所有 {len(path_points)} 个路径点")
|
logging.info(f"保留所有 {len(path_points)} 个路径点")
|
||||||
|
|
||||||
return path_points
|
return path_points
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"路径解析失败: {str(e)}")
|
logging.error(f"路径解析失败: {str(e)}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
|
def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
|
||||||
@ -1398,7 +1397,7 @@ class AircraftRouteFollower:
|
|||||||
# 根据航班号获取对应的路由数据
|
# 根据航班号获取对应的路由数据
|
||||||
flight_routes = aircraft_routes.get(self.flight_no)
|
flight_routes = aircraft_routes.get(self.flight_no)
|
||||||
if not flight_routes:
|
if not flight_routes:
|
||||||
logger.warning(f"未找到航班 {self.flight_no} 的路由数据,使用MU5123的路由作为默认")
|
logging.warning(f"未找到航班 {self.flight_no} 的路由数据,使用MU5123的路由作为默认")
|
||||||
flight_routes = aircraft_routes.get("MU5123", {})
|
flight_routes = aircraft_routes.get("MU5123", {})
|
||||||
|
|
||||||
# 解析进港路径
|
# 解析进港路径
|
||||||
@ -1406,20 +1405,20 @@ class AircraftRouteFollower:
|
|||||||
|
|
||||||
# 验证MU5123进港路径的数据完整性,然后合并为连续路径
|
# 验证MU5123进港路径的数据完整性,然后合并为连续路径
|
||||||
if self.flight_no == "MU5123" and arrival_route:
|
if self.flight_no == "MU5123" and arrival_route:
|
||||||
logger.info(f"验证 {self.flight_no} 进港路径数据完整性")
|
logging.info(f"验证 {self.flight_no} 进港路径数据完整性")
|
||||||
arrival_route = validate_route_data_integrity(arrival_route, self.flight_no)
|
arrival_route = validate_route_data_integrity(arrival_route, self.flight_no)
|
||||||
|
|
||||||
# 将31个路径段合并为连续路径,让飞机能够完整运行
|
# 将31个路径段合并为连续路径,让飞机能够完整运行
|
||||||
logger.info(f"将 {self.flight_no} 的分散路径段合并为连续路径")
|
logging.info(f"将 {self.flight_no} 的分散路径段合并为连续路径")
|
||||||
arrival_route = merge_discontinuous_route_for_flight(arrival_route, self.flight_no)
|
arrival_route = merge_discontinuous_route_for_flight(arrival_route, self.flight_no)
|
||||||
|
|
||||||
self.arrival_points = parse_route_path(arrival_route)
|
self.arrival_points = parse_route_path(arrival_route)
|
||||||
logger.info(f"航空器 {self.flight_no} 进港路径: {len(self.arrival_points)} 个点")
|
logging.info(f"航空器 {self.flight_no} 进港路径: {len(self.arrival_points)} 个点")
|
||||||
|
|
||||||
# 解析出港路径
|
# 解析出港路径
|
||||||
departure_route = flight_routes.get("departure", {})
|
departure_route = flight_routes.get("departure", {})
|
||||||
self.departure_points = parse_route_path(departure_route)
|
self.departure_points = parse_route_path(departure_route)
|
||||||
logger.info(f"航空器 {self.flight_no} 出港路径: {len(self.departure_points)} 个点")
|
logging.info(f"航空器 {self.flight_no} 出港路径: {len(self.departure_points)} 个点")
|
||||||
|
|
||||||
# 设置机位坐标(138号机位的大概位置)
|
# 设置机位坐标(138号机位的大概位置)
|
||||||
if self.arrival_points:
|
if self.arrival_points:
|
||||||
@ -1431,7 +1430,7 @@ class AircraftRouteFollower:
|
|||||||
self.gate_position = (36.354068, 120.083410)
|
self.gate_position = (36.354068, 120.083410)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"路径解析失败: {str(e)}")
|
logging.error(f"路径解析失败: {str(e)}")
|
||||||
self.arrival_points = []
|
self.arrival_points = []
|
||||||
self.departure_points = []
|
self.departure_points = []
|
||||||
self.gate_position = (36.354068, 120.083410)
|
self.gate_position = (36.354068, 120.083410)
|
||||||
@ -1446,7 +1445,7 @@ class AircraftRouteFollower:
|
|||||||
self.is_at_gate = False
|
self.is_at_gate = False
|
||||||
self.flight_status = 'in_route' # 设置为路径运行状态
|
self.flight_status = 'in_route' # 设置为路径运行状态
|
||||||
self.wait_until = 0.0 # 清除等待时间
|
self.wait_until = 0.0 # 清除等待时间
|
||||||
logger.info(f"航空器 {self.flight_no} 开始进港路径跟随")
|
logging.info(f"航空器 {self.flight_no} 开始进港路径跟随")
|
||||||
|
|
||||||
elif route_type == "departure" and self.departure_points:
|
elif route_type == "departure" and self.departure_points:
|
||||||
self.current_route_type = "departure"
|
self.current_route_type = "departure"
|
||||||
@ -1456,10 +1455,10 @@ class AircraftRouteFollower:
|
|||||||
self.is_at_gate = False
|
self.is_at_gate = False
|
||||||
self.flight_status = 'in_route' # 设置为路径运行状态
|
self.flight_status = 'in_route' # 设置为路径运行状态
|
||||||
self.wait_until = 0.0 # 清除等待时间
|
self.wait_until = 0.0 # 清除等待时间
|
||||||
logger.info(f"航空器 {self.flight_no} 开始出港路径跟随")
|
logging.info(f"航空器 {self.flight_no} 开始出港路径跟随")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.warning(f"航空器 {self.flight_no} 无效的路径类型: {route_type}")
|
logging.warning(f"航空器 {self.flight_no} 无效的路径类型: {route_type}")
|
||||||
|
|
||||||
def update_position_on_route(self, aircraft: dict[str, Any], speed_kmh: float, elapsed_time: float) -> bool:
|
def update_position_on_route(self, aircraft: dict[str, Any], speed_kmh: float, elapsed_time: float) -> bool:
|
||||||
"""
|
"""
|
||||||
@ -1522,7 +1521,7 @@ class AircraftRouteFollower:
|
|||||||
self.flight_status = 'at_gate' if self.is_at_gate else 'completed'
|
self.flight_status = 'at_gate' if self.is_at_gate else 'completed'
|
||||||
# 设置5秒等待时间
|
# 设置5秒等待时间
|
||||||
self.wait_until = current_time + self.waiting_duration
|
self.wait_until = current_time + self.waiting_duration
|
||||||
logger.info(f"航空器 {self.flight_no} 完成 {self.current_route_type} 路径,等待{self.waiting_duration}秒后重新开始")
|
logging.info(f"航空器 {self.flight_no} 完成 {self.current_route_type} 路径,等待{self.waiting_duration}秒后重新开始")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# 更新到下一个目标点
|
# 更新到下一个目标点
|
||||||
@ -1622,12 +1621,12 @@ class AircraftRouteManager:
|
|||||||
if follower.route_points:
|
if follower.route_points:
|
||||||
aircraft["latitude"] = follower.route_points[0][0]
|
aircraft["latitude"] = follower.route_points[0][0]
|
||||||
aircraft["longitude"] = follower.route_points[0][1]
|
aircraft["longitude"] = follower.route_points[0][1]
|
||||||
logger.info(f"航空器 {aircraft.get('flightNo')} 等待结束,重新开始进港路径")
|
logging.info(f"航空器 {aircraft.get('flightNo')} 等待结束,重新开始进港路径")
|
||||||
|
|
||||||
route_completed = follower.update_position_on_route(aircraft, speed, elapsed_time)
|
route_completed = follower.update_position_on_route(aircraft, speed, elapsed_time)
|
||||||
if route_completed and follower.flight_status != 'waiting':
|
if route_completed and follower.flight_status != 'waiting':
|
||||||
# 路径完成,已设置5秒等待时间,无需立即重新开始
|
# 路径完成,已设置5秒等待时间,无需立即重新开始
|
||||||
logger.info(f"航空器 {aircraft.get('flightNo')} 进港完成,将等待{follower.waiting_duration}秒后重新开始")
|
logging.info(f"航空器 {aircraft.get('flightNo')} 进港完成,将等待{follower.waiting_duration}秒后重新开始")
|
||||||
|
|
||||||
def _update_departure_only_aircraft(self, aircraft: dict[str, Any], follower: AircraftRouteFollower,
|
def _update_departure_only_aircraft(self, aircraft: dict[str, Any], follower: AircraftRouteFollower,
|
||||||
speed: float, elapsed_time: float):
|
speed: float, elapsed_time: float):
|
||||||
@ -1641,12 +1640,12 @@ class AircraftRouteManager:
|
|||||||
if follower.route_points:
|
if follower.route_points:
|
||||||
aircraft["latitude"] = follower.route_points[0][0]
|
aircraft["latitude"] = follower.route_points[0][0]
|
||||||
aircraft["longitude"] = follower.route_points[0][1]
|
aircraft["longitude"] = follower.route_points[0][1]
|
||||||
logger.info(f"航空器 {aircraft.get('flightNo')} 等待结束,重新开始出港路径")
|
logging.info(f"航空器 {aircraft.get('flightNo')} 等待结束,重新开始出港路径")
|
||||||
|
|
||||||
route_completed = follower.update_position_on_route(aircraft, speed, elapsed_time)
|
route_completed = follower.update_position_on_route(aircraft, speed, elapsed_time)
|
||||||
if route_completed and follower.flight_status != 'waiting':
|
if route_completed and follower.flight_status != 'waiting':
|
||||||
# 路径完成,已设置5秒等待时间,无需立即重新开始
|
# 路径完成,已设置5秒等待时间,无需立即重新开始
|
||||||
logger.info(f"航空器 {aircraft.get('flightNo')} 出港完成,将等待{follower.waiting_duration}秒后重新开始")
|
logging.info(f"航空器 {aircraft.get('flightNo')} 出港完成,将等待{follower.waiting_duration}秒后重新开始")
|
||||||
|
|
||||||
|
|
||||||
# 创建全局路径跟随管理器
|
# 创建全局路径跟随管理器
|
||||||
@ -1703,83 +1702,6 @@ def initialize_aircraft_data():
|
|||||||
# 初始化飞机数据
|
# 初始化飞机数据
|
||||||
aircraft_data.extend(initialize_aircraft_data())
|
aircraft_data.extend(initialize_aircraft_data())
|
||||||
|
|
||||||
# 额外测试飞机数据(用于前端性能测试)
|
|
||||||
additional_aircraft_data = []
|
|
||||||
|
|
||||||
def initialize_additional_aircraft():
|
|
||||||
"""初始化 60 架测试飞机"""
|
|
||||||
global additional_aircraft_data
|
|
||||||
|
|
||||||
# 机场中心坐标(基于 CGCS2000 投影坐标)
|
|
||||||
airport_center_lat = 36.36236547
|
|
||||||
airport_center_lon = 120.08782536
|
|
||||||
|
|
||||||
# 机场覆盖范围(大约 2km x 2km 区域)
|
|
||||||
lat_range = 0.02 # 大约 2.2km
|
|
||||||
lon_range = 0.02 # 大约 1.8km
|
|
||||||
|
|
||||||
# 创建 60 架测试飞机
|
|
||||||
for i in range(1, 61):
|
|
||||||
# 随机分布在机场范围内
|
|
||||||
lat_offset = (random.random() - 0.5) * lat_range
|
|
||||||
lon_offset = (random.random() - 0.5) * lon_range
|
|
||||||
|
|
||||||
aircraft_lat = airport_center_lat + lat_offset
|
|
||||||
aircraft_lon = airport_center_lon + lon_offset
|
|
||||||
|
|
||||||
# 前 55 架静止,后 5 架移动
|
|
||||||
is_moving = i > 55
|
|
||||||
speed = random.uniform(20.0, 50.0) if is_moving else 0.0
|
|
||||||
|
|
||||||
# 为移动的飞机设置简单的往复移动路径
|
|
||||||
if is_moving:
|
|
||||||
# 在当前位置周围创建一个小范围的移动路径
|
|
||||||
move_radius = 0.001 # 大约 100 米范围
|
|
||||||
start_lat = aircraft_lat - move_radius
|
|
||||||
start_lon = aircraft_lon - move_radius
|
|
||||||
end_lat = aircraft_lat + move_radius
|
|
||||||
end_lon = aircraft_lon + move_radius
|
|
||||||
moving_to_end = True
|
|
||||||
else:
|
|
||||||
# 静止飞机使用当前位置作为起点和终点
|
|
||||||
start_lat = aircraft_lat
|
|
||||||
start_lon = aircraft_lon
|
|
||||||
end_lat = aircraft_lat
|
|
||||||
end_lon = aircraft_lon
|
|
||||||
moving_to_end = False
|
|
||||||
|
|
||||||
aircraft = {
|
|
||||||
"flightNo": f"PT{i:03d}", # 测试飞机航班号:PT001-PT060
|
|
||||||
"longitude": aircraft_lon,
|
|
||||||
"latitude": aircraft_lat,
|
|
||||||
"time": int(time.time() * 1000),
|
|
||||||
"altitude": 0.0,
|
|
||||||
"trackNumber": 2000 + i, # 跟踪号:2001-2060
|
|
||||||
"speed": speed,
|
|
||||||
"use_route_following": False, # 不使用复杂路径跟随
|
|
||||||
"start_point": {"latitude": start_lat, "longitude": start_lon},
|
|
||||||
"end_point": {"latitude": end_lat, "longitude": end_lon},
|
|
||||||
"moving_to_end": moving_to_end
|
|
||||||
}
|
|
||||||
|
|
||||||
additional_aircraft_data.append(aircraft)
|
|
||||||
|
|
||||||
# 统计信息
|
|
||||||
stationary_count = sum(1 for a in additional_aircraft_data if a["speed"] == 0)
|
|
||||||
moving_count = len(additional_aircraft_data) - stationary_count
|
|
||||||
|
|
||||||
print(f"✅ 已添加测试飞机:")
|
|
||||||
print(f" - 总飞机数: {len(additional_aircraft_data)}")
|
|
||||||
print(f" - 静止飞机: {stationary_count} 架")
|
|
||||||
print(f" - 移动飞机: {moving_count} 架")
|
|
||||||
print(f" - 移动速度: 20-50 km/h")
|
|
||||||
|
|
||||||
# 添加到主飞机数据列表
|
|
||||||
aircraft_data.extend(additional_aircraft_data)
|
|
||||||
|
|
||||||
# 初始化测试飞机
|
|
||||||
initialize_additional_aircraft()
|
|
||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
|
|
||||||
def calculate_distance_to_target(vehicle: Mapping[str, float], target_lat: float, target_lon: float) -> float:
|
def calculate_distance_to_target(vehicle: Mapping[str, float], target_lat: float, target_lon: float) -> float:
|
||||||
@ -2024,7 +1946,7 @@ class BackgroundUpdateManager:
|
|||||||
self.running = True
|
self.running = True
|
||||||
self.update_thread = threading.Thread(target=self._update_loop, daemon=True)
|
self.update_thread = threading.Thread(target=self._update_loop, daemon=True)
|
||||||
self.update_thread.start()
|
self.update_thread.start()
|
||||||
logger.info("后台更新管理器已启动")
|
logging.info("后台更新管理器已启动")
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""停止后台更新线程"""
|
"""停止后台更新线程"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user