Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 85c3e7ec98 |
43
.gitignore
vendored
43
.gitignore
vendored
@ -54,45 +54,4 @@ logs/
|
||||
Users/
|
||||
|
||||
qaup-deploy/
|
||||
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/
|
||||
deploy/offline_packages/
|
||||
203
README.md
203
README.md
@ -17,7 +17,6 @@
|
||||
## 核心特性
|
||||
|
||||
### 🚗 冲突避免系统 (collision模块)
|
||||
|
||||
- **空间数据分析**: 基于PostGIS的空间计算和几何分析
|
||||
- **实时车辆监控**: WebSocket实时位置数据推送和展示
|
||||
- **机场区域管理**: 跑道、滑行道、停机坪等区域配置和监控
|
||||
@ -27,15 +26,15 @@
|
||||
|
||||
### 🛠️ 系统管理功能
|
||||
|
||||
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
|
||||
3. 岗位管理:配置系统用户所属担任职务。
|
||||
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
|
||||
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
|
||||
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
7. 参数管理:对系统动态配置常用参数。
|
||||
8. 通知公告:系统通知公告信息发布维护。
|
||||
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
|
||||
3. 岗位管理:配置系统用户所属担任职务。
|
||||
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
|
||||
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
|
||||
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
7. 参数管理:对系统动态配置常用参数。
|
||||
8. 通知公告:系统通知公告信息发布维护。
|
||||
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
10. 登录日志:系统登录日志记录查询包含登录异常。
|
||||
11. 在线用户:当前系统中活跃用户状态监控。
|
||||
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
|
||||
@ -56,194 +55,65 @@
|
||||
- **构建工具**: Maven 3.6+
|
||||
- **Java版本**: JDK 8
|
||||
|
||||
## 部署指南
|
||||
|
||||
### 方式一:自动部署(推荐)
|
||||
|
||||
使用提供的自动化部署脚本:
|
||||
## 快速启动
|
||||
|
||||
### 1. 环境准备
|
||||
```bash
|
||||
# 1. 进入部署目录
|
||||
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 8
|
||||
java -version
|
||||
|
||||
# Maven 3.6+
|
||||
mvn -version
|
||||
|
||||
# Docker & Docker Compose
|
||||
docker --version
|
||||
docker-compose --version
|
||||
```
|
||||
|
||||
**可选组件(用于本地开发):**
|
||||
|
||||
```bash
|
||||
# PostgreSQL + PostGIS(用于本地开发)
|
||||
# PostgreSQL + PostGIS
|
||||
psql --version
|
||||
|
||||
# Redis(用于本地开发)
|
||||
# Redis
|
||||
redis-server --version
|
||||
```
|
||||
|
||||
#### 2. 数据库配置
|
||||
|
||||
**Docker启动数据库:**
|
||||
### 2. 数据库配置
|
||||
1. 创建PostgreSQL数据库并启用PostGIS扩展
|
||||
2. 执行SQL初始化脚本:
|
||||
- `sql/create_qaup_database.sql` - 创建数据库
|
||||
- `sql/create_sys_vehicle_info_table.sql` - 车辆信息表
|
||||
- `sql/create_sys_driver_info_table.sql` - 司机信息表
|
||||
|
||||
### 3. 启动Redis服务
|
||||
```bash
|
||||
# 启动PostgreSQL + PostGIS
|
||||
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
|
||||
redis-server
|
||||
```
|
||||
|
||||
**本地数据库初始化:**
|
||||
|
||||
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. 环境配置
|
||||
|
||||
**更新应用配置:**
|
||||
### 4. 配置应用
|
||||
修改 `qaup-admin/src/main/resources/application.yml`:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/qaup
|
||||
username: qaup
|
||||
password: qaup123
|
||||
driver-class-name: org.postgresql.Driver
|
||||
url: jdbc:postgresql://localhost:5432/your_database
|
||||
username: your_username
|
||||
password: your_password
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
password: qaup123
|
||||
database: 0
|
||||
```
|
||||
|
||||
**环境变量配置:**
|
||||
确保各模块 `.env` 文件配置正确:
|
||||
|
||||
- `adxp-adapter/.env.example` - ADXP数据中台适配器配置
|
||||
- `qaup-admin/.env.example` - 主应用配置
|
||||
|
||||
#### 4. 编译和启动
|
||||
|
||||
### 5. 编译和启动
|
||||
```bash
|
||||
# 1. 清理并编译整个项目
|
||||
# 清理并编译整个项目
|
||||
mvn clean install
|
||||
|
||||
# 2. 进入管理模块
|
||||
# 启动应用
|
||||
cd qaup-admin
|
||||
|
||||
# 3. 启动应用(开发模式)
|
||||
mvn spring-boot:run
|
||||
|
||||
# 或打包启动(生产模式)
|
||||
mvn clean package -DskipTests
|
||||
# 或者运行打包后的jar
|
||||
java -jar target/qaup-admin.jar
|
||||
```
|
||||
|
||||
#### 5. 验证部署
|
||||
|
||||
**服务状态检查:**
|
||||
|
||||
```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
|
||||
```
|
||||
### 6. 访问系统
|
||||
- 管理后台: http://localhost:8080
|
||||
- WebSocket端点: ws://localhost:8080/ws
|
||||
- API文档: http://localhost:8080/swagger-ui/
|
||||
|
||||
## 项目合并说明
|
||||
|
||||
@ -257,19 +127,16 @@ docker system prune -a
|
||||
## 开发指南
|
||||
|
||||
### collision模块核心组件
|
||||
|
||||
- `QuapDataAdapter`: 数据访问适配器,连接若依系统数据
|
||||
- `WebSocketConfig`: WebSocket配置,支持实时数据推送
|
||||
- `VehicleLocationService`: 车辆位置管理服务
|
||||
- `GeopositionController`: WebSocket消息控制器
|
||||
|
||||
### 扩展开发
|
||||
|
||||
1. 新增spatial实体时,继承spatial基类并配置PostGIS映射
|
||||
2. WebSocket消息通过`/topic`前缀向客户端广播
|
||||
3. 使用`QuapDataAdapter`获取车辆和司机数据,避免直接访问DAO
|
||||
|
||||
## 版本信息
|
||||
|
||||
- 当前版本: 1.0.1
|
||||
- 更新日志: 详见 `change_log.md`
|
||||
- 更新日志: 详见 `change_log.md`
|
||||
@ -4,68 +4,13 @@
|
||||
# 1. 复制此文件为 .env: cp .env.example .env
|
||||
# 2. 修改 .env 中的配置值
|
||||
# 3. 启动应用时会自动加载 .env 文件中的环境变量
|
||||
# 4. 适用于ADXP主动连接架构
|
||||
# ============================================================
|
||||
|
||||
# ========== ADXP 数据中台连接配置 ==========
|
||||
# ADXP数据中台的主机地址和端口
|
||||
ADXP_HOST=10.32.38.2
|
||||
ADXP_PORT=8081
|
||||
|
||||
# 连接ADXP数据中台的用户名和密码
|
||||
# ========== ADXP 数据中台配置 ==========
|
||||
ADXP_HOST=localhost
|
||||
ADXP_PORT=7001
|
||||
ADXP_USERNAME=dianxin
|
||||
ADXP_PASSWORD=Dianxin#2025
|
||||
|
||||
# ========== 适配器服务配置 ==========
|
||||
# 适配器服务端口
|
||||
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内存
|
||||
ADXP_PASSWORD=dianxin@123
|
||||
|
||||
# ============================================================
|
||||
# 注意事项:
|
||||
@ -73,6 +18,4 @@ REDIS_MAX_MEMORY_POLICY=volatile-lru
|
||||
# 2. 字符串值不需要引号
|
||||
# 3. #开头的行为注释
|
||||
# 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主动连接)
|
||||
|
||||
### 🔄 **适配器主动连接架构**
|
||||
|
||||
- **启动即连接**: 适配器服务启动时自动连接到ADXP数据中台
|
||||
- **无需登录接口**: 移除 `/login` 端点,由适配器自动管理认证
|
||||
- **连接状态管理**: 提供 `/status` 和 `/reconnect` 端点进行连接管理
|
||||
- **WebSocket实时推送**: 支持实时向客户端推送航班消息
|
||||
- **健康监控**: 内置连接状态监控和自动重连机制
|
||||
|
||||
## 功能特性
|
||||
## 功能说明
|
||||
|
||||
将青岛机场数据中台 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
|
||||
# 1. 构建并启动服务
|
||||
docker compose up -d
|
||||
# 1. 编译
|
||||
mvn clean package
|
||||
|
||||
# 2. 查看日志
|
||||
docker compose logs -f
|
||||
|
||||
# 3. 停止服务
|
||||
docker compose down
|
||||
# 2. 运行
|
||||
java -jar target/adxp-adapter.jar \
|
||||
--adxp.host=10.10.10.100 \
|
||||
--adxp.port=7001
|
||||
```
|
||||
|
||||
### API接口(ADXP主动连接架构)
|
||||
### 方式2:Docker 运行(推荐)
|
||||
|
||||
#### 获取连接状态
|
||||
```bash
|
||||
# 1. 构建镜像
|
||||
docker build -t adxp-adapter:1.0.0 .
|
||||
|
||||
```
|
||||
GET /api/adxp/status
|
||||
# 2. 运行容器
|
||||
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
|
||||
|
||||
```
|
||||
POST /api/adxp/reconnect
|
||||
# 2. 启动服务
|
||||
docker-compose up -d
|
||||
|
||||
# 3. 查看日志
|
||||
docker-compose logs -f
|
||||
|
||||
# 4. 停止服务
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
强制断开当前连接并重新连接ADXP数据中台。
|
||||
|
||||
#### 获取消息接口
|
||||
|
||||
```
|
||||
GET /api/adxp/messages
|
||||
```
|
||||
|
||||
使用当前活动连接获取ADXP数据中台的航班消息。
|
||||
|
||||
#### 断开连接接口
|
||||
|
||||
```
|
||||
POST /api/adxp/disconnect
|
||||
```
|
||||
|
||||
主动断开与ADXP数据中台的连接。
|
||||
|
||||
#### 健康检查
|
||||
|
||||
```
|
||||
GET /api/adxp/health
|
||||
```
|
||||
|
||||
检查适配器服务健康状态和ADXP连接状态。
|
||||
|
||||
#### WebSocket实时推送
|
||||
|
||||
```
|
||||
ws://localhost:8086/ws/flight-notifications
|
||||
```
|
||||
|
||||
实时接收来自ADXP数据中台的航班消息推送。
|
||||
## API 文档
|
||||
|
||||
### 1. 登录
|
||||
|
||||
@ -101,7 +74,6 @@ Content-Type: application/json
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
@ -117,7 +89,6 @@ GET /api/adxp/messages?sessionId=550e8400-e29b-41d4-a716-446655440000
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
@ -150,7 +121,6 @@ GET /api/adxp/health
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "UP",
|
||||
@ -158,37 +128,18 @@ GET /api/adxp/health
|
||||
}
|
||||
```
|
||||
|
||||
## 快速测试
|
||||
|
||||
### API调用示例
|
||||
|
||||
#### 1. 检查连接状态
|
||||
## 测试
|
||||
|
||||
```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
|
||||
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
|
||||
# 3. 健康检查
|
||||
curl http://localhost:8086/api/adxp/health
|
||||
```
|
||||
|
||||
@ -200,8 +151,7 @@ curl http://localhost:8086/api/adxp/health
|
||||
|--------|------|--------|
|
||||
| `ADXP_HOST` | ADXP 数据中台主机地址 | localhost |
|
||||
| `ADXP_PORT` | ADXP 数据中台端口 | 7001 |
|
||||
| `ADXP_USERNAME` | ADXP 用户名 | dianxin |
|
||||
| `ADXP_PASSWORD` | ADXP 密码 | dianxin@123 |
|
||||
| `SPRING_PROFILES_ACTIVE` | Spring Profile | prod |
|
||||
|
||||
### application.yml
|
||||
|
||||
@ -209,32 +159,62 @@ curl http://localhost:8086/api/adxp/health
|
||||
adxp:
|
||||
host: ${ADXP_HOST:localhost}
|
||||
port: ${ADXP_PORT:7001}
|
||||
username: ${ADXP_USERNAME:dianxin}
|
||||
password: ${ADXP_PASSWORD:dianxin@123}
|
||||
```
|
||||
|
||||
## 部署说明
|
||||
|
||||
### 开发环境
|
||||
|
||||
使用 mock 服务器进行开发测试:
|
||||
|
||||
```bash
|
||||
# 1. 启动适配器(默认指向 localhost)
|
||||
docker compose up -d
|
||||
# 启动 mock ADXP 服务器
|
||||
cd /path/to/QAUP-Management/tools
|
||||
python3 mock_adxp.py --auto --interval 10
|
||||
|
||||
# 启动适配器(指向 localhost)
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 生产环境
|
||||
|
||||
```bash
|
||||
# 1. 创建环境变量文件
|
||||
cat > .env << EOF
|
||||
ADXP_HOST=10.10.10.100 # 真实数据中台 IP
|
||||
ADXP_PORT=7001 # 真实数据中台端口
|
||||
ADXP_USERNAME=dianxin # 真实数据中台用户名
|
||||
ADXP_PASSWORD=dianxin@123 # 真实数据中台密码
|
||||
EOF
|
||||
连接真实的 ADXP 数据中台:
|
||||
|
||||
# 2. 启动服务
|
||||
docker compose up -d
|
||||
```bash
|
||||
# 设置真实环境变量
|
||||
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/
|
||||
├── pom.xml # Maven 配置
|
||||
├── Dockerfile # Docker 镜像定义
|
||||
├── docker-compose.yml # Docker Compose 配置
|
||||
├── libs/ # 数据中台 SDK
|
||||
├── pom.xml # Maven 配置
|
||||
├── Dockerfile # Docker 镜像定义
|
||||
├── docker-compose.yml # Docker Compose 配置
|
||||
├── libs/ # SDK JAR 文件
|
||||
│ ├── adxp-client-2.6.9.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
|
||||
- **Apache CXF 3.2.4**: SOAP 协议支持
|
||||
- **Jackson 1.9.13**: JSON 序列化
|
||||
- **Spring Boot 2.7.18**: 应用框架
|
||||
- `adxp-client-2.6.9.jar`: 数据中台 SDK
|
||||
- `mq.allclient-9.0.jar`: IBM MQ 客户端
|
||||
- Apache CXF 3.2.4: SOAP 支持
|
||||
- Jackson 1.9.13: JSON 序列化
|
||||
|
||||
## 许可证
|
||||
|
||||
|
||||
@ -7,10 +7,8 @@ services:
|
||||
- "8086:8086"
|
||||
environment:
|
||||
# ADXP 数据中台连接配置(必须设置)
|
||||
ADXP_HOST: ${ADXP_HOST:-10.32.38.2}
|
||||
ADXP_PORT: ${ADXP_PORT:-8081}
|
||||
ADXP_USERNAME: ${ADXP_USERNAME:-dianxin}
|
||||
ADXP_PASSWORD: ${ADXP_PASSWORD:-Dianxin#2025}
|
||||
ADXP_HOST: ${ADXP_HOST:-10.10.10.100}
|
||||
ADXP_PORT: ${ADXP_PORT:-7001}
|
||||
# Spring Profile
|
||||
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod}
|
||||
volumes:
|
||||
|
||||
Binary file not shown.
@ -1,6 +1,8 @@
|
||||
package com.qaup.adxp.adapter.config;
|
||||
|
||||
import com.qaup.adxp.adapter.service.AdxpSdkService;
|
||||
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.Configuration;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
@ -11,9 +13,12 @@ import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry
|
||||
@EnableWebSocket
|
||||
public class WebSocketConfig implements WebSocketConfigurer {
|
||||
|
||||
@Autowired
|
||||
private AdxpSdkService adxpSdkService;
|
||||
|
||||
@Bean
|
||||
public AdxpWebSocketHandler adxpWebSocketHandler() {
|
||||
return new AdxpWebSocketHandler();
|
||||
return new AdxpWebSocketHandler(adxpSdkService);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -26,82 +26,51 @@ public class AdxpController {
|
||||
private AdxpWebSocketHandler adxpWebSocketHandler;
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
* 登录接口
|
||||
*/
|
||||
@GetMapping("/status")
|
||||
public ResponseEntity<Map<String, Object>> getStatus() {
|
||||
@PostMapping(value = "/login", produces = "application/json")
|
||||
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
|
||||
try {
|
||||
Map<String, Object> status = new HashMap<>();
|
||||
status.put("connected", adxpSdkService.isConnected());
|
||||
status.put("connectionInfo", adxpSdkService.getConnectionInfo());
|
||||
status.put("timestamp", System.currentTimeMillis());
|
||||
return ResponseEntity.ok(status);
|
||||
String sessionId = adxpSdkService.login(request.getUsername(), request.getPassword());
|
||||
return ResponseEntity.ok(LoginResponse.success(sessionId));
|
||||
} catch (Exception e) {
|
||||
log.error("获取连接状态失败", e);
|
||||
Map<String, Object> error = new HashMap<>();
|
||||
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);
|
||||
log.error("登录失败", e);
|
||||
return ResponseEntity.ok(LoginResponse.failure(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息接口 - 使用当前连接
|
||||
* 接收消息接口
|
||||
*/
|
||||
@GetMapping("/messages")
|
||||
public ResponseEntity<MessageResponse> getMessages() {
|
||||
public ResponseEntity<MessageResponse> getMessages(@RequestParam String sessionId) {
|
||||
try {
|
||||
if (!adxpSdkService.isConnected()) {
|
||||
return ResponseEntity.ok(MessageResponse.failure("未连接到ADXP数据中台"));
|
||||
}
|
||||
List<FlightMessage> messages = adxpSdkService.getMessages();
|
||||
List<FlightMessage> messages = adxpSdkService.receiveMessages(sessionId);
|
||||
return ResponseEntity.ok(MessageResponse.success(messages));
|
||||
} catch (Exception e) {
|
||||
log.error("接收消息失败", e);
|
||||
log.error("接收消息失败: sessionId={}", sessionId, e);
|
||||
return ResponseEntity.ok(MessageResponse.failure(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接接口
|
||||
* 登出接口
|
||||
*/
|
||||
@PostMapping("/disconnect")
|
||||
public ResponseEntity<Map<String, Object>> disconnect() {
|
||||
@PostMapping("/logout")
|
||||
public ResponseEntity<Map<String, Object>> logout(@RequestBody Map<String, String> request) {
|
||||
try {
|
||||
adxpSdkService.disconnectFromADXPServer();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
String sessionId = request.get("sessionId");
|
||||
adxpSdkService.logout(sessionId);
|
||||
|
||||
Map<String, Object> response = new HashMap<String, Object>();
|
||||
response.put("success", true);
|
||||
response.put("message", "已断开ADXP数据中台连接");
|
||||
response.put("connected", false);
|
||||
response.put("message", "登出成功");
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("断开连接失败", e);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
log.error("登出失败", e);
|
||||
Map<String, Object> response = new HashMap<String, Object>();
|
||||
response.put("success", false);
|
||||
response.put("message", "断开连接失败: " + e.getMessage());
|
||||
response.put("connected", adxpSdkService.isConnected());
|
||||
response.put("message", e.getMessage());
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
@ -111,20 +80,12 @@ public class AdxpController {
|
||||
*/
|
||||
@GetMapping("/health")
|
||||
public ResponseEntity<Map<String, Object>> health() {
|
||||
try {
|
||||
Map<String, Object> health = adxpSdkService.healthCheck();
|
||||
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);
|
||||
Map<String, Object> health = new HashMap<String, Object>();
|
||||
health.put("status", "UP");
|
||||
health.put("activeSessions", adxpSdkService.getActiveSessionCount());
|
||||
if (adxpWebSocketHandler != null) {
|
||||
health.put("websocketConnections", adxpWebSocketHandler.getSessionCount());
|
||||
}
|
||||
return ResponseEntity.ok(health);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package com.qaup.adxp.adapter.service;
|
||||
|
||||
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.ADXPClientFactory;
|
||||
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.MsgType;
|
||||
import com.taocares.adxp.model.HeadType;
|
||||
import com.taocares.adxp.AdxpConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Service
|
||||
public class AdxpSdkService {
|
||||
@ -33,199 +27,98 @@ public class AdxpSdkService {
|
||||
@Value("${adxp.port}")
|
||||
private int port;
|
||||
|
||||
@Value("${adxp.username:dianxin}")
|
||||
@Value("${adxp.username:}")
|
||||
private String defaultUsername;
|
||||
|
||||
@Value("${adxp.password:Dianxin#2025}")
|
||||
@Value("${adxp.password:}")
|
||||
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();
|
||||
|
||||
// 应用启动时主动连接
|
||||
@PostConstruct
|
||||
public void initConnection() {
|
||||
log.info("ADXP适配器启动,主动连接数据中台...");
|
||||
try {
|
||||
connectToADXP();
|
||||
} catch (Exception e) {
|
||||
log.error("ADXP数据中台初始连接失败: {}", e.getMessage());
|
||||
// 启动定时重连
|
||||
scheduleReconnect();
|
||||
}
|
||||
// Session 管理: sessionId -> SessionInfo
|
||||
private final Map<String, SessionInfo> sessions = new ConcurrentHashMap<>();
|
||||
|
||||
public Map<String, SessionInfo> getSessions() {
|
||||
return sessions;
|
||||
}
|
||||
|
||||
// 应用关闭时清理资源
|
||||
@PreDestroy
|
||||
public void cleanup() {
|
||||
log.info("ADXP适配器关闭,清理资源...");
|
||||
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");
|
||||
public static class SessionInfo {
|
||||
ADXPClient client;
|
||||
String username;
|
||||
String password;
|
||||
|
||||
messageListener.setDaemon(true);
|
||||
messageListener.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息接收方法
|
||||
*/
|
||||
private List<FlightMessage> receiveMessages() {
|
||||
if (!connected || adxpClient == null) {
|
||||
return Collections.emptyList();
|
||||
SessionInfo(ADXPClient client, String username, String password) {
|
||||
this.client = client;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
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 {
|
||||
MessageResult result = adxpClient.receiveMessage();
|
||||
log.info("正在登录 ADXP 数据中台: host={}, port={}, username={}", host, port, actualUsername);
|
||||
|
||||
// 使用统一的错误码处理方法
|
||||
if (handleMessageResultError(result)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 创建 SDK 客户端
|
||||
ADXPClient client = ADXPClientFactory.createWSClient(host, port);
|
||||
|
||||
// 调用登录
|
||||
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())) {
|
||||
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();
|
||||
}
|
||||
|
||||
@ -248,123 +141,34 @@ public class AdxpSdkService {
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("接收到 {} 条消息", messages.size());
|
||||
return messages;
|
||||
|
||||
} catch (Exception e) {
|
||||
handleConnectionError(e);
|
||||
return Collections.emptyList();
|
||||
log.error("接收消息异常: sessionId={}", sessionId, e);
|
||||
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() {
|
||||
return receiveMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接 - 主动断开与ADXP数据中台的连接
|
||||
*/
|
||||
public void disconnectFromADXPServer() {
|
||||
log.info("主动断开ADXP数据中台连接");
|
||||
disconnectFromADXP();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新连接方法
|
||||
*/
|
||||
private void reconnect() {
|
||||
synchronized (lock) {
|
||||
public void logout(String sessionId) {
|
||||
SessionInfo sessionInfo = sessions.remove(sessionId);
|
||||
if (sessionInfo != null) {
|
||||
try {
|
||||
log.info("尝试重新连接ADXP数据中台");
|
||||
// 先断开现有连接
|
||||
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("重新连接失败");
|
||||
}
|
||||
sessionInfo.client.logout(sessionInfo.username, sessionInfo.password);
|
||||
log.info("登出成功: sessionId={}", sessionId);
|
||||
} catch (Exception e) {
|
||||
connected = false;
|
||||
adxpClient = null;
|
||||
log.error("重新连接异常: {}", e.getMessage());
|
||||
log.error("登出异常: sessionId={}", sessionId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 健康检查
|
||||
* 获取当前会话数
|
||||
*/
|
||||
public Map<String, Object> healthCheck() {
|
||||
Map<String, Object> health = new HashMap<>();
|
||||
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;
|
||||
public int getActiveSessionCount() {
|
||||
return sessions.size();
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@Service
|
||||
@ -21,36 +23,97 @@ public class MessageListenerService {
|
||||
@Autowired
|
||||
private AdxpWebSocketHandler adxpWebSocketHandler;
|
||||
|
||||
private ExecutorService executorService;
|
||||
private final AtomicBoolean isRunning = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* 服务启动时的初始化方法
|
||||
* 注意:消息监听逻辑已移至AdxpSdkService中,本服务仅负责管理和监控
|
||||
*/
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
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
|
||||
public void stop() {
|
||||
if (isRunning.compareAndSet(true, false)) {
|
||||
if (executorService != null) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
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() {
|
||||
return String.format("MessageListenerService Stats: " +
|
||||
"isRunning=%s, " +
|
||||
"adxpConnected=%s, " +
|
||||
"sessions=%d, " +
|
||||
"webSocketClients=%d",
|
||||
isRunning.get(),
|
||||
adxpSdkService.isConnected(),
|
||||
adxpSdkService.getSessions().size(),
|
||||
adxpWebSocketHandler.getSessionCount());
|
||||
}
|
||||
}
|
||||
@ -1,30 +1,32 @@
|
||||
package com.qaup.adxp.adapter.websocket;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.qaup.adxp.adapter.dto.FlightMessage;
|
||||
import com.qaup.adxp.adapter.service.AdxpSdkService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class AdxpWebSocketHandler extends TextWebSocketHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AdxpWebSocketHandler.class);
|
||||
|
||||
private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
|
||||
private final AdxpSdkService adxpSdkService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final AtomicBoolean isRunning = new AtomicBoolean(false);
|
||||
|
||||
public AdxpWebSocketHandler() {
|
||||
public AdxpWebSocketHandler(AdxpSdkService adxpSdkService) {
|
||||
this.adxpSdkService = adxpSdkService;
|
||||
this.objectMapper = new ObjectMapper();
|
||||
// 配置ObjectMapper以更好地处理日期和其他序列化问题
|
||||
this.objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -62,47 +64,41 @@ public class AdxpWebSocketHandler extends TextWebSocketHandler {
|
||||
* 向所有连接的客户端广播消息
|
||||
*/
|
||||
public void broadcastMessages(List<FlightMessage> messages) {
|
||||
if (messages == null || messages.isEmpty() || sessions.isEmpty()) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 序列化消息列表为JSON
|
||||
String jsonMessage = objectMapper.writeValueAsString(messages);
|
||||
TextMessage textMessage = new TextMessage(jsonMessage);
|
||||
|
||||
// 清理已关闭的会话
|
||||
cleanupClosedSessions();
|
||||
int successCount = 0;
|
||||
int failedCount = 0;
|
||||
|
||||
int clientCount = sessions.size();
|
||||
|
||||
// 向所有活跃客户端广播消息
|
||||
for (WebSocketSession session : sessions) {
|
||||
if (session.isOpen()) {
|
||||
try {
|
||||
session.sendMessage(textMessage);
|
||||
successCount++;
|
||||
} catch (Exception e) {
|
||||
log.error("发送消息失败: sessionId={}", session.getId(), e);
|
||||
// 如果发送失败,移除会话
|
||||
sessions.remove(session);
|
||||
failedCount++;
|
||||
}
|
||||
} else {
|
||||
sessions.remove(session);
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("已向 {} 个客户端广播 {} 条消息", clientCount, messages.size());
|
||||
}
|
||||
log.debug("广播消息完成: 消息数量={}, 连接数={}, 成功发送={}, 失败={}",
|
||||
messages.size(), sessions.size(), successCount, failedCount);
|
||||
} catch (Exception 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() {
|
||||
return sessions.stream()
|
||||
.filter(WebSocketSession::isOpen)
|
||||
.map(session -> String.format("ID: %s, Remote Address: %s",
|
||||
.map(session -> String.format("ID: %s, Remote Address: %s, Open: %s",
|
||||
session.getId(),
|
||||
session.getRemoteAddress()))
|
||||
.collect(Collectors.toList());
|
||||
session.getRemoteAddress(),
|
||||
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 系统部署和更新说明
|
||||
|
||||
## 一、打包环境操作
|
||||
## 开发环境操作
|
||||
|
||||
### 1. 生成程序更新文件
|
||||
### 生成程序更新文件
|
||||
|
||||
修改代码后,生成仅包含应用jar的更新包:
|
||||
当代码修改后,需要生成新的jar文件供客户更新:
|
||||
|
||||
```bash
|
||||
./deploy/package-update.sh
|
||||
./deploy/simple/build-update.sh
|
||||
```
|
||||
|
||||
**输出**:
|
||||
|
||||
生成包含详细说明的更新包:
|
||||
- `qaup-admin.jar` - 应用程序
|
||||
- `UPDATE-INSTRUCTIONS.md` - 更新说明
|
||||
- `UPDATE-INSTRUCTIONS.md` - 详细更新说明
|
||||
- `VERSION-INFO.txt` - 版本信息
|
||||
|
||||
**注意**:如需单独jar文件,可直接从更新包中提取。
|
||||
|
||||
### 2. 生成完整部署包
|
||||
|
||||
生成包含所有组件的完整部署包(首次部署或重大更新):
|
||||
**注意**:如果只需要jar文件,可以从生成的更新包中提取 `qaup-admin.jar` 文件单独发送给客户。
|
||||
|
||||
### 生成完整部署包
|
||||
```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
|
||||
mkdir qaup-deploy
|
||||
tar -xzf qaup-deploy-20250105-143022.tar.gz -C qaup-deploy
|
||||
cd qaup-deploy
|
||||
```
|
||||
|
||||
# 1. 确认当前系统状态
|
||||
### 2. 执行部署
|
||||
```bash
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
### 3. 验证部署
|
||||
```bash
|
||||
# 检查服务状态
|
||||
docker compose ps
|
||||
|
||||
# 2. 备份重要数据(推荐)
|
||||
docker exec qaup-postgres pg_dump -U qaup qaup > backup-$(date +%Y%m%d).sql
|
||||
cp config.yml config.yml.backup
|
||||
# 访问系统
|
||||
curl http://localhost:8080/actuator/health
|
||||
```
|
||||
|
||||
#### 更新步骤
|
||||
## 程序更新(仅更新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
|
||||
# 1. 准备新版本文件(二选一)
|
||||
# 方案A:使用完整更新包
|
||||
cp /path/to/qaup-update-xxx/qaup-admin.jar new-app.jar
|
||||
# 解压更新包
|
||||
tar -xzf qaup-update-20250105-143022.tar.gz
|
||||
cd qaup-update-20250105-143022
|
||||
|
||||
# 方案B:直接使用jar文件
|
||||
cp /path/to/qaup-admin-xxx.jar new-app.jar
|
||||
# 查看更新说明
|
||||
cat UPDATE-INSTRUCTIONS.md
|
||||
|
||||
# 2. 执行更新
|
||||
./deploy-update.sh
|
||||
|
||||
# 3. 验证更新
|
||||
docker compose ps # 检查服务状态
|
||||
docker compose logs -f qaup-app # 查看应用日志
|
||||
curl http://localhost:8080/actuator/health # 验证应用健康
|
||||
# 复制jar文件到部署目录
|
||||
cd ../qaup-deploy
|
||||
cp ../qaup-update-20250105-143022/qaup-admin.jar new-app.jar
|
||||
```
|
||||
|
||||
### 3. 配置文件更新
|
||||
|
||||
如果需要更新配置文件:
|
||||
|
||||
#### 如果收到的是jar文件:
|
||||
```bash
|
||||
cd qaup-deploy
|
||||
|
||||
# 1. 备份当前配置
|
||||
cp config.yml config.yml.backup
|
||||
|
||||
# 2. 编辑配置文件
|
||||
vi config.yml
|
||||
|
||||
# 3. 重启应用
|
||||
docker compose restart qaup-app
|
||||
cp /path/to/qaup-admin-20250105-143022.jar new-app.jar
|
||||
```
|
||||
|
||||
### 4. 更新失败处理
|
||||
|
||||
#### 自动回滚
|
||||
|
||||
更新失败时脚本会自动尝试回滚。
|
||||
|
||||
#### 手动回滚
|
||||
|
||||
### 2. 执行程序更新
|
||||
```bash
|
||||
cd qaup-deploy
|
||||
./update.sh
|
||||
```
|
||||
|
||||
# 1. 停止应用
|
||||
docker compose stop qaup-app
|
||||
### 3. 验证更新结果
|
||||
```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
|
||||
|
||||
# 3. 启动应用
|
||||
docker compose start qaup-app
|
||||
# 启动应用
|
||||
docker compose start qaup-app
|
||||
```
|
||||
|
||||
#### 问题排查
|
||||
|
||||
### 检查问题:
|
||||
```bash
|
||||
# 查看应用日志
|
||||
docker compose logs qaup-app
|
||||
@ -139,210 +134,137 @@ docker compose logs qaup-app
|
||||
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
|
||||
cd qaup-deploy
|
||||
|
||||
# 1. 清理旧环境
|
||||
docker compose down
|
||||
rm -rf data/ # 注意:会删除所有数据
|
||||
|
||||
# 2. 重新部署
|
||||
./deploy-all.sh
|
||||
docker compose logs qaup-app
|
||||
netstat -tlnp | grep 8080
|
||||
```
|
||||
|
||||
#### 方案B:保留数据
|
||||
|
||||
```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: 数据库连接失败?
|
||||
|
||||
### Q: 数据库连接失败
|
||||
A: 检查数据库服务状态:
|
||||
|
||||
```bash
|
||||
docker compose logs qaup-postgres
|
||||
docker compose logs qaup-postgres
|
||||
docker exec qaup-postgres pg_isready -U qaup
|
||||
```
|
||||
|
||||
## 四、更新记录
|
||||
|
||||
建议记录每次更新信息:
|
||||
|
||||
```
|
||||
时间 | 版本号 | 人员 | 类型 | 结果 | 问题
|
||||
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. 数据库部署流程
|
||||
|
||||
#### 首次部署(全新环境)
|
||||
|
||||
### Q: 如何完全重新部署
|
||||
A: 如果更新出现严重问题,可以重新部署:
|
||||
```bash
|
||||
# 1. 启动基础服务(PostgreSQL + Redis)
|
||||
docker compose up -d qaup-postgres qaup-redis
|
||||
# 停止所有服务
|
||||
docker compose down
|
||||
|
||||
# 2. 等待数据库就绪
|
||||
sleep 30
|
||||
# 清理数据(注意:这会删除所有数据)
|
||||
rm -rf data/
|
||||
|
||||
# 3. 初始化数据库(执行Flyway迁移)
|
||||
# 应用启动时会自动执行Flyway迁移
|
||||
docker compose up -d qaup-app
|
||||
|
||||
# 4. 验证部署
|
||||
docker compose ps
|
||||
curl http://localhost:8080/actuator/health
|
||||
# 重新部署
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
#### 已有环境更新
|
||||
## 完整系统重新部署
|
||||
|
||||
如果需要重新部署整个系统(包括更新Docker镜像、配置等):
|
||||
|
||||
### 1. 获取新的部署包
|
||||
```bash
|
||||
# Flyway自动处理数据库迁移
|
||||
# 应用启动时会自动检测并执行需要的迁移
|
||||
docker compose restart qaup-app
|
||||
|
||||
# 检查迁移状态
|
||||
docker exec qaup-postgres psql -U qaup -d qaup -c "SELECT * FROM flyway_schema_history ORDER BY installed_rank;"
|
||||
# 解压新的部署包到新目录
|
||||
mkdir qaup-deploy-new
|
||||
tar -xzf qaup-deploy-new-20250110-100000.tar.gz -C qaup-deploy-new
|
||||
cd qaup-deploy-new
|
||||
```
|
||||
|
||||
### 3. 数据库迁移脚本管理
|
||||
|
||||
#### 脚本命名规范
|
||||
|
||||
- `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. 数据库维护操作
|
||||
|
||||
#### 数据备份和恢复(仅限紧急情况)
|
||||
|
||||
### 2. 迁移数据(如果需要保留数据)
|
||||
```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
|
||||
ORDER BY installed_rank;"
|
||||
#### 程序更新流程:
|
||||
```
|
||||
开发环境:
|
||||
1. 代码修改完成
|
||||
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. **脚本化回滚** - 创建回滚脚本
|
||||
3. **版本控制** - 通过Flyway版本管理
|
||||
### 开发环境脚本:
|
||||
- `deploy/simple/build-update.sh` - 生成程序更新包
|
||||
- `deploy/simple/package.sh` - 生成完整部署包
|
||||
|
||||
## 六、文件说明
|
||||
|
||||
### 打包环境脚本
|
||||
|
||||
- `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` - 合并迁移脚本(历史用途)
|
||||
### 客户环境脚本:
|
||||
- `deploy.sh` - 首次部署脚本
|
||||
- `update.sh` - 程序更新脚本
|
||||
- `DeployGuide.md` - 本说明文档
|
||||
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:
|
||||
airport:
|
||||
# 青岛机场坐标(客户部署时需要修改为实际机场坐标)
|
||||
center-longitude: 120.08782536
|
||||
center-latitude: 36.36236547
|
||||
center-longitude: 120.0834104
|
||||
center-latitude: 36.35406879
|
||||
|
||||
# 管理端点配置
|
||||
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部署指南
|
||||
|
||||
> **重要说明**: 本文档描述了现代化的极简部署方案,数据库迁移通过Flyway自动化处理,无需手动执行SQL脚本。
|
||||
|
||||
## 1. 系统环境准备
|
||||
|
||||
### Ubuntu系统要求
|
||||
- **操作系统**: Ubuntu 20.04 LTS 或更高版本
|
||||
- **内存**: 至少 8GB RAM
|
||||
- **存储**: 至少 20GB 可用空间
|
||||
- **存储**: 至少 10GB 可用空间
|
||||
- **CPU**: 4核或以上
|
||||
- **网络**: 稳定的互联网连接(用于下载Docker镜像)
|
||||
- **网络**: 稳定的互联网连接
|
||||
|
||||
### 安装必要软件
|
||||
```bash
|
||||
# 系统更新
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
|
||||
# 安装Java 21(用于本地编译)
|
||||
# 安装Java 21
|
||||
sudo apt install -y openjdk-21-jdk
|
||||
java -version # 验证安装
|
||||
|
||||
# 安装Maven(用于构建)
|
||||
# 安装Maven
|
||||
sudo apt install -y maven
|
||||
mvn -version # 验证安装
|
||||
|
||||
@ -29,361 +27,269 @@ sudo apt install -y docker.io
|
||||
sudo systemctl start docker
|
||||
sudo systemctl enable docker
|
||||
|
||||
# 安装Docker Compose
|
||||
sudo apt install -y docker-compose-plugin
|
||||
|
||||
# 将当前用户添加到docker组
|
||||
sudo usermod -aG docker $USER
|
||||
newgrp docker # 重新加载组权限
|
||||
|
||||
# 验证安装
|
||||
# 验证Docker
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
## 2. 部署方式选择
|
||||
|
||||
### 方式一:使用预构建部署包(推荐)
|
||||
## 2. 获取并编译项目
|
||||
|
||||
```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>
|
||||
cd QAUP-Management
|
||||
|
||||
# 2. 编译项目
|
||||
# 编译项目
|
||||
mvn clean package -DskipTests
|
||||
|
||||
# 3. 创建部署目录
|
||||
# 验证编译结果
|
||||
ls -la qaup-admin/target/qaup-admin.jar
|
||||
```
|
||||
|
||||
## 3. 部署方式
|
||||
|
||||
### 3.1 Docker Compose部署(推荐)
|
||||
|
||||
```bash
|
||||
# 创建部署目录
|
||||
mkdir -p ~/qaup-deploy
|
||||
cd ~/qaup-deploy
|
||||
|
||||
# 4. 复制部署文件
|
||||
# 复制部署文件
|
||||
cp /path/to/QAUP-Management/deploy/docker-compose.yml .
|
||||
cp /path/to/QAUP-Management/deploy/config.yml .
|
||||
cp /path/to/QAUP-Management/qaup-admin/target/qaup-admin.jar ./app.jar
|
||||
|
||||
# 5. 创建数据目录
|
||||
# 创建数据目录
|
||||
mkdir -p data/postgres data/redis logs backup
|
||||
|
||||
# 6. 启动服务
|
||||
# 启动服务
|
||||
docker compose up -d
|
||||
|
||||
# 7. 检查服务状态
|
||||
# 检查服务状态
|
||||
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
|
||||
# 进入部署目录
|
||||
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 down
|
||||
|
||||
# 重启应用服务(不影响数据库)
|
||||
# 重启应用服务
|
||||
docker compose restart qaup-app
|
||||
|
||||
# 查看服务状态
|
||||
docker compose ps
|
||||
|
||||
# 查看应用日志
|
||||
# 查看日志
|
||||
docker compose logs -f qaup-app
|
||||
|
||||
# 查看数据库日志
|
||||
docker compose logs qaup-postgres
|
||||
|
||||
# 查看Redis日志
|
||||
docker compose logs qaup-redis
|
||||
|
||||
# 查看系统资源使用
|
||||
docker stats
|
||||
```
|
||||
|
||||
### 3.2 监控数据库迁移状态
|
||||
```bash
|
||||
# 查看Flyway迁移历史
|
||||
docker exec -it qaup-postgres psql -U qaup -d qaup -c "SELECT * FROM flyway_schema_history ORDER BY installed_rank;"
|
||||
## 5. 访问系统
|
||||
|
||||
# 查看应用日志中的迁移信息
|
||||
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
|
||||
- **WebSocket**: ws://<your-server-ip>:8080/collision
|
||||
- **健康检查**: http://<your-server-ip>:8080/actuator/health
|
||||
- **API文档**: http://<your-server-ip>:8080/doc.html
|
||||
|
||||
### 4.2 初始登录
|
||||
### 初始登录
|
||||
- **用户名**: admin
|
||||
- **密码**: admin123
|
||||
|
||||
### 4.3 快速验证命令
|
||||
```bash
|
||||
# 健康检查(验证服务是否正常)
|
||||
curl http://localhost:8080/actuator/health
|
||||
## 6. 应用更新
|
||||
|
||||
# 检查容器中Java版本
|
||||
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 一键升级(推荐)
|
||||
|
||||
使用现代化的升级脚本,支持自动备份、版本检查和回滚:
|
||||
### 6.1 基本更新流程
|
||||
|
||||
```bash
|
||||
# 1. 复制新版本文件并重命名为 new-app.jar
|
||||
cp /path/to/new/qaup-admin.jar ~/qaup-deploy/new-app.jar
|
||||
|
||||
# 2. 进入部署目录
|
||||
# 备份当前版本
|
||||
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版本兼容性检查
|
||||
# ✅ 数据库自动备份
|
||||
# ✅ 应用服务重启
|
||||
# ✅ Flyway自动迁移
|
||||
# ✅ 健康检查验证
|
||||
# ✅ 自动回滚(如需要)
|
||||
# 重启应用
|
||||
docker compose restart qaup-app
|
||||
|
||||
# 验证更新
|
||||
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
|
||||
# 进入部署目录
|
||||
cd ~/qaup-deploy
|
||||
|
||||
# 1. 数据库备份(重要)
|
||||
docker exec qaup-postgres pg_dump -U qaup qaup > backup/qaup-backup-$(date +%Y%m%d_%H%M%S).sql
|
||||
# 1. 停止现有服务
|
||||
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)
|
||||
|
||||
# 3. 替换新版本
|
||||
# 5. 替换新版本
|
||||
cp /path/to/new/qaup-admin.jar ./app.jar
|
||||
|
||||
# 4. 重启应用服务(数据库保持运行)
|
||||
docker compose restart qaup-app
|
||||
# 6. 启动新的服务
|
||||
docker compose up -d
|
||||
|
||||
# 5. 等待启动和迁移完成(约2分钟)
|
||||
echo "等待应用启动和数据库迁移..."
|
||||
sleep 120
|
||||
# 7. 检查服务状态
|
||||
docker compose ps
|
||||
|
||||
# 6. 验证升级结果
|
||||
if curl -f -s http://localhost:8080/actuator/health > /dev/null; then
|
||||
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
|
||||
# 8. 验证更新
|
||||
curl -s http://localhost:8080/actuator/health | jq
|
||||
```
|
||||
|
||||
### 5.3 监控升级过程
|
||||
### 6.4 故障排除
|
||||
|
||||
#### 6.4.1 容器名称冲突
|
||||
|
||||
如果遇到容器名称冲突错误:
|
||||
```
|
||||
Error response from daemon: Conflict. The container name "/qaup-app" is already in use
|
||||
```
|
||||
|
||||
解决方法:
|
||||
```bash
|
||||
# 实时查看应用日志(包含Flyway迁移信息)
|
||||
docker compose logs -f qaup-app
|
||||
# 方式1:完全重建
|
||||
docker compose down
|
||||
docker rm -f $(docker ps -aq --filter name=qaup)
|
||||
docker compose up -d
|
||||
|
||||
# 检查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 5;"
|
||||
# 方式2:强制重新创建
|
||||
docker compose up -d --force-recreate
|
||||
|
||||
# 查看最近的迁移记录
|
||||
docker compose logs qaup-app | grep -i "flyway.*migrated"
|
||||
# 方式3:只重新创建特定服务
|
||||
docker compose up -d --force-recreate qaup-app
|
||||
```
|
||||
|
||||
### 5.4 故障排除
|
||||
#### 6.4.2 Java版本验证
|
||||
|
||||
#### 5.4.1 Java版本兼容性错误
|
||||
|
||||
如果遇到 `UnsupportedClassVersionError`:
|
||||
```
|
||||
Error: A JNI error has occurred, please check your installation and try again
|
||||
Error: Could not find or load main class
|
||||
```
|
||||
|
||||
**解决方案**:
|
||||
确认容器中Java版本与编译版本匹配:
|
||||
```bash
|
||||
# 1. 检查容器Java版本
|
||||
# 查看容器Java版本
|
||||
docker exec qaup-app java -version
|
||||
|
||||
# 2. 验证jar文件编译版本
|
||||
javap -cp app.jar -version
|
||||
|
||||
# 3. 如果版本不匹配,重新编译项目
|
||||
mvn clean package -DskipTests
|
||||
|
||||
# 4. 确认Docker镜像使用Java 21
|
||||
grep "eclipse-temurin.*21" docker-compose.yml
|
||||
# 如果需要,检查镜像信息
|
||||
docker inspect qaup-app | grep -i java
|
||||
```
|
||||
|
||||
#### 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
|
||||
# 检查数据库服务
|
||||
docker compose ps
|
||||
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 极简离线部署方案
|
||||
|
||||
Spring Boot + Redis + PostgreSQL with PostGIS(一键部署、一键升级)
|
||||
Spring Boot + Redis + PostgreSQL(一键部署、一键升级)
|
||||
|
||||
## 1. 设计原则
|
||||
|
||||
@ -8,151 +7,103 @@ Spring Boot + Redis + PostgreSQL with PostGIS(一键部署、一键升级)
|
||||
- **离线部署**:预打包所有依赖,无需联网
|
||||
- **一键操作**:部署和升级都是一条命令
|
||||
- **配置灵活**:关键配置可外部修改
|
||||
- **自动化迁移**:数据库版本自动管理(Flyway)
|
||||
|
||||
## 2. 打包准备(开发环境执行一次)
|
||||
|
||||
### 2.1 打包脚本(package-all.sh)
|
||||
|
||||
### 2.1 打包脚本(package.sh)
|
||||
```bash
|
||||
#!/bin/bash
|
||||
echo "=== QAUP 打包脚本 ==="
|
||||
|
||||
# 1. 构建应用(在macOS上)
|
||||
# 1. 构建应用
|
||||
mvn clean package -DskipTests
|
||||
|
||||
# 2. 拉取并导出镜像(在服务器上)
|
||||
docker pull m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine
|
||||
docker pull m.daocloud.io/docker.io/library/redis:8.0-alpine
|
||||
docker pull m.daocloud.io/docker.io/library/eclipse-temurin:21-jre
|
||||
|
||||
# 导出镜像
|
||||
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
|
||||
# 2. 拉取并导出镜像
|
||||
docker pull openjdk:21-jre-alpine
|
||||
docker pull redis:8-alpine
|
||||
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
|
||||
|
||||
# 3. 准备部署包
|
||||
mkdir -p qaup-deploy
|
||||
cp qaup-admin/target/qaup-admin.jar qaup-deploy/app.jar
|
||||
cp deploy/docker-compose.yml qaup-deploy/
|
||||
cp deploy/config.yml qaup-deploy/
|
||||
cp deploy/simple/* qaup-deploy/
|
||||
cp images.tar.gz qaup-deploy/
|
||||
|
||||
# 4. 打包
|
||||
tar -czf qaup-deploy-$(date +%Y%m%d-%H%M%S).tar.gz -C qaup-deploy .
|
||||
echo "打包完成: qaup-deploy-$(date +%Y%m%d-%H%M%S).tar.gz"
|
||||
cd qaup-deploy && zip -r ../qaup-deploy-$(date +%Y%m%d).zip .
|
||||
echo "打包完成: qaup-deploy-$(date +%Y%m%d).zip"
|
||||
```
|
||||
|
||||
### 2.2 部署包结构
|
||||
|
||||
### 2.2 部署包结构(极简版)
|
||||
```
|
||||
qaup-deploy.tar.gz
|
||||
├── images.tar.gz # Docker镜像包(PostGIS + Redis + Java21)
|
||||
├── docker-compose.yml # 服务编排(包含健康检查)
|
||||
├── app.jar # 应用程序
|
||||
├── config.yml # 外部配置(可修改)
|
||||
├── deploy-all.sh # 一键部署脚本
|
||||
├── deploy-update.sh # 一键升级脚本
|
||||
├── DeployGuide.md # 详细部署指南
|
||||
└── qaup_database_export.sql # 完整数据库备份(可选)
|
||||
qaup-deploy.zip
|
||||
├── images.tar.gz # Docker镜像包
|
||||
├── docker-compose.yml # 服务编排
|
||||
├── app.jar # 应用程序
|
||||
├── config.yml # 外部配置(可修改)
|
||||
├── init.sql # 数据库初始化脚本
|
||||
├── deploy.sh # 一键部署
|
||||
└── update.sh # 一键升级
|
||||
```
|
||||
|
||||
## 3. docker-compose.yml(当前实际版本)
|
||||
|
||||
## 3. docker-compose.yml(极简版)
|
||||
```yaml
|
||||
services:
|
||||
# PostgreSQL + PostGIS 数据库服务
|
||||
qaup-postgres:
|
||||
image: m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine
|
||||
container_name: qaup-postgres
|
||||
restart: unless-stopped
|
||||
app:
|
||||
image: openjdk:21-jre-alpine
|
||||
container_name: qaup-app
|
||||
restart: always
|
||||
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:
|
||||
POSTGRES_DB: qaup
|
||||
POSTGRES_USER: qaup
|
||||
POSTGRES_PASSWORD: qaup123
|
||||
volumes:
|
||||
- ./data/postgres:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "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
|
||||
- ./data:/var/lib/postgresql/data
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
```
|
||||
|
||||
## 4. 外部配置文件(config.yml)
|
||||
|
||||
```yaml
|
||||
# 服务器配置
|
||||
server:
|
||||
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:
|
||||
# 文件上传路径
|
||||
@ -162,101 +113,64 @@ qaup:
|
||||
api-host: 192.168.1.100
|
||||
api-port: 8090
|
||||
|
||||
# 日志配置
|
||||
# 日志配置(简化)
|
||||
logging:
|
||||
level:
|
||||
com.qaup: INFO
|
||||
com.qaup: info
|
||||
pattern:
|
||||
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
|
||||
file:
|
||||
name: /app/logs/qaup.log
|
||||
|
||||
# 注意:数据库和Redis配置通过环境变量设置
|
||||
console: "%d{HH:mm:ss} %-5level %logger{36} - %msg%n"
|
||||
```
|
||||
|
||||
## 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 自动迁移机制
|
||||
|
||||
- **应用启动时自动执行**:无需手动运行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)
|
||||
|
||||
## 6. 一键部署(deploy.sh)
|
||||
```bash
|
||||
#!/bin/bash
|
||||
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
|
||||
mkdir -p data
|
||||
|
||||
# 启动服务
|
||||
echo "启动服务..."
|
||||
docker compose up -d
|
||||
|
||||
# 等待服务启动
|
||||
echo "等待服务启动(60秒)..."
|
||||
sleep 60
|
||||
# 等待启动
|
||||
echo "等待服务启动(30秒)..."
|
||||
sleep 30
|
||||
|
||||
# 检查服务状态
|
||||
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 " 升级应用: ./deploy-update.sh"
|
||||
else
|
||||
echo "❌ 服务启动失败,请检查日志:"
|
||||
docker compose logs
|
||||
fi
|
||||
echo "✅ 部署完成!访问: http://localhost:8080"
|
||||
echo "默认账号: admin / admin123"
|
||||
```
|
||||
|
||||
## 7. 客户部署(1条命令)
|
||||
|
||||
```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
|
||||
#!/bin/bash
|
||||
echo "=== QAUP 一键升级 ==="
|
||||
@ -272,190 +186,86 @@ echo "验证Java版本兼容性..."
|
||||
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/')
|
||||
echo "本地Java版本: $LOCAL_JAVA_VERSION"
|
||||
echo "容器Java版本: eclipse-temurin:21-jre"
|
||||
fi
|
||||
|
||||
# 备份当前版本
|
||||
echo "备份当前版本..."
|
||||
cp app.jar app.jar.backup.$(date +%Y%m%d_%H%M%S)
|
||||
cp app.jar app.jar.backup
|
||||
echo "已备份当前版本"
|
||||
|
||||
# 数据库备份(重要)
|
||||
echo "备份数据库..."
|
||||
docker exec qaup-postgres pg_dump -U qaup qaup > backup/qaup-backup-$(date +%Y%m%d_%H%M%S).sql
|
||||
|
||||
# 停止应用(不影响数据库)
|
||||
# 停止应用
|
||||
echo "停止应用服务..."
|
||||
docker compose stop qaup-app
|
||||
docker compose stop app
|
||||
|
||||
# 替换应用
|
||||
cp new-app.jar app.jar
|
||||
echo "已更新应用文件"
|
||||
|
||||
# 启动应用(Flyway自动处理数据库迁移)
|
||||
# 启动应用
|
||||
echo "启动应用服务..."
|
||||
docker compose start qaup-app
|
||||
docker compose start app
|
||||
|
||||
# 等待启动和迁移完成
|
||||
echo "等待应用启动和数据库迁移(120秒)..."
|
||||
sleep 120
|
||||
# 等待启动
|
||||
echo "等待服务启动(30秒)..."
|
||||
sleep 30
|
||||
|
||||
# 检查升级结果
|
||||
if curl -f -s http://localhost:8080/actuator/health > /dev/null 2>&1; then
|
||||
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
|
||||
echo "✅ 升级完成!"
|
||||
echo "如有问题,可执行回滚: cp app.jar.backup app.jar && docker compose restart app"
|
||||
```
|
||||
|
||||
## 9. Java版本兼容性注意事项
|
||||
|
||||
### 9.1 编译与运行环境匹配
|
||||
|
||||
- **编译环境**: 确保项目在Java 21环境下编译
|
||||
- **运行环境**: Docker镜像使用 `eclipse-temurin:21-jre`
|
||||
- **运行环境**: Docker镜像必须与编译版本匹配(`openjdk:21-jre-alpine` 或 `eclipse-temurin:21-jre`)
|
||||
|
||||
### 9.2 常见版本错误
|
||||
|
||||
如果遇到 `UnsupportedClassVersionError`,确认以下配置:
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml 中必须使用Java 21镜像
|
||||
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 版本验证命令
|
||||
|
||||
```bash
|
||||
# 检查容器中Java版本
|
||||
docker exec qaup-app java -version
|
||||
docker exec <container-name> java -version
|
||||
|
||||
# 检查jar文件编译版本
|
||||
javap -cp app.jar -version
|
||||
|
||||
# 快速健康检查
|
||||
curl http://localhost:8080/actuator/health
|
||||
javap -cp <jar-file> -version <main-class>
|
||||
```
|
||||
|
||||
## 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. 操作总结
|
||||
## 9. 操作总结
|
||||
|
||||
### 开发环境(一次性)
|
||||
|
||||
```bash
|
||||
# 在macOS上构建
|
||||
mvn clean package -DskipTests
|
||||
./deploy/package-all.sh
|
||||
|
||||
# 或在服务器上打包
|
||||
./deploy/package-all.sh
|
||||
./package.sh # 打包部署文件
|
||||
```
|
||||
|
||||
### 客户环境
|
||||
|
||||
```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
|
||||
# 2. 执行更新
|
||||
./deploy-update.sh
|
||||
# 日常升级
|
||||
./update.sh
|
||||
|
||||
# 查看状态
|
||||
docker compose ps
|
||||
|
||||
# 查看日志
|
||||
docker compose logs -f qaup-app
|
||||
docker compose logs -f app
|
||||
```
|
||||
|
||||
## 12. 现代化优势总结
|
||||
## 10. 优势总结
|
||||
|
||||
✅ **极简部署**:解压 → 运行脚本,2步完成
|
||||
✅ **极简升级**:替换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
|
||||
- 密码: Yaddfepasswd13#$
|
||||
- 密码: Huawei@1234567890
|
||||
- 前端
|
||||
- IP:
|
||||
- 用户名:
|
||||
@ -28,7 +28,7 @@
|
||||
- IP: 10.32.38.3
|
||||
- 端口: 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
|
||||
- IP: 222.173.72.76
|
||||
|
||||
@ -5,180 +5,155 @@
|
||||
## 1. 根目录结构
|
||||
|
||||
```
|
||||
QAUP-Management/
|
||||
CollisionAvoidanceSystem/
|
||||
├── 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/ # 源代码目录
|
||||
│ ├── main/ # 主要源代码
|
||||
│ │ ├── java/ # Java源代码
|
||||
│ │ │ └── com/
|
||||
│ │ │ └── qaup/
|
||||
│ │ │ └── collision/ # 应用程序主包
|
||||
│ │ └── resources/ # 配置文件和静态资源
|
||||
│ │ ├── config/ # 特定配置文件 (如 airport_roads.yaml, airport_areas.yaml)
|
||||
│ │ ├── static/ # 静态Web资源
|
||||
│ │ └── templates/ # 模板文件
|
||||
│ └── test/ # 测试源代码
|
||||
├── libs/ # 本地依赖库
|
||||
└── pom.xml # Maven模块配置
|
||||
├── target/ # 编译输出目录
|
||||
├── .idea/ # IDE配置
|
||||
├── .mvn/ # Maven包装器配置
|
||||
├── .git/ # Git版本控制
|
||||
├── pom.xml # Maven项目配置
|
||||
├── mvnw / mvnw.cmd # Maven包装器脚本
|
||||
├── README.md # 项目说明
|
||||
├── VERSION.txt # 版本信息
|
||||
├── change_log.md # 变更日志
|
||||
├── development_log.md # 开发日志
|
||||
├── .gitignore # Git忽略配置
|
||||
└── .gitattributes # Git属性配置
|
||||
```
|
||||
|
||||
## 3. 应用程序主包结构 (`com.qaup.collision`)
|
||||
## 2. 源代码目录结构 (`src`)
|
||||
|
||||
```
|
||||
com.qaup.collision/
|
||||
├── adapter/ # 外部系统适配器
|
||||
├── area/ # 机场区域管理模块
|
||||
│ ├── model/ # 区域相关数据模型
|
||||
│ └── service/ # 区域管理服务
|
||||
src/
|
||||
├── main/ # 主要源代码
|
||||
│ ├── java/ # Java源代码
|
||||
│ │ └── com/
|
||||
│ │ └── 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/ # 通用组件目录
|
||||
│ ├── adapter/ # 通用适配器
|
||||
│ ├── config/ # 通用配置
|
||||
│ ├── exception/ # 通用异常处理
|
||||
│ ├── model/ # 核心数据模型
|
||||
│ ├── repository/ # 仓储模式实现
|
||||
│ └── service/ # 通用服务
|
||||
│ ├── model/ # 核心移动对象等数据模型
|
||||
│ │ ├── base/ # 基础类和常量
|
||||
│ │ ├── dto/ # 数据传输对象
|
||||
│ │ └── repository/ # 仓储模式实现
|
||||
│ └── config/ # 通用配置 (较少使用,优先模块内配置)
|
||||
├── config/ # 应用程序配置
|
||||
│ ├── properties/ # 配置属性映射类 (POJOs)
|
||||
│ ├── CollisionAvoidanceConfig.java # 碰撞避免系统配置
|
||||
│ ├── DatabasePerformanceConfig.java # 数据库性能配置
|
||||
│ ├── GracefulShutdownConfig.java # 优雅关闭配置
|
||||
│ ├── RoadNetworkConfig.java # 道路网络配置加载类
|
||||
│ ├── ThreadPoolConfig.java # 线程池配置
|
||||
│ └── YamlPropertySourceFactory.java # YAML加载工厂类
|
||||
│ ├── AirportAreaConfig.java # 机场区域配置加载类
|
||||
│ ├── YamlPropertySourceFactory.java # YAML加载工厂类
|
||||
│ ├── RedisConfig.java # Redis配置
|
||||
│ └── ThreadPoolConfig.java # 线程池配置
|
||||
├── controller/ # 控制器层 (REST API)
|
||||
│ ├── DataMonitorController.java # 数据监控控制器
|
||||
│ ├── HealthController.java # 健康检查控制器
|
||||
│ ├── IntersectionController.java # 交叉路口控制器
|
||||
│ ├── SpatialRuleController.java # 空间规则控制器
|
||||
│ ├── TrafficLightController.java # 交通信号灯控制器
|
||||
│ ├── UniversalVehicleApiController.java # 通用车辆API控制器
|
||||
│ └── UnmannedVehicleController.java # 无人车控制器
|
||||
├── datacollector/ # 数据采集模块
|
||||
│ ├── config/ # 数据采集配置
|
||||
│ ├── dao/ # 数据访问对象
|
||||
│ ├── dto/ # 数据传输对象
|
||||
│ ├── filter/ # 数据过滤器
|
||||
├── dataCollector/ # 数据采集模块
|
||||
│ ├── model/ # 数据采集相关模型
|
||||
│ ├── repository/ # 数据采集仓储
|
||||
│ ├── sdk/ # SDK集成
|
||||
│ ├── server/ # 服务器端实现
|
||||
│ │ └── enums/ # 枚举类型定义
|
||||
│ ├── service/ # 数据采集服务
|
||||
│ ├── util/ # 工具类
|
||||
│ └── websocket/ # WebSocket数据采集
|
||||
├── dataprocessing/ # 数据处理模块
|
||||
│ ├── config/ # 数据处理配置
|
||||
│ ├── model/ # 数据处理模型
|
||||
│ ├── parser/ # 数据解析器
|
||||
│ └── service/ # 数据处理服务
|
||||
├── event/ # 事件处理模块
|
||||
│ ├── handler/ # 事件处理器
|
||||
│ ├── model/ # 事件模型
|
||||
│ └── service/ # 事件服务
|
||||
├── geofence/ # 地理围栏模块
|
||||
│ ├── model/ # 地理围栏模型
|
||||
│ └── service/ # 地理围栏服务
|
||||
├── pathconflict/ # 路径冲突模块
|
||||
│ ├── event/ # 路径冲突事件
|
||||
│ ├── model/ # 路径冲突模型
|
||||
│ ├── repository/ # 路径冲突仓储
|
||||
│ └── service/ # 路径冲突服务
|
||||
├── road/ # 道路网络模块
|
||||
│ ├── dao/ # 数据访问对象
|
||||
│ └── config/ # 数据采集配置
|
||||
├── dataProcessing/ # 数据处理模块
|
||||
│ ├── service/ # 数据处理服务
|
||||
│ └── config/ # 数据处理配置
|
||||
├── areas/ # 新增:机场区域管理模块
|
||||
│ ├── model/ # 区域相关数据模型
|
||||
│ └── service/ # 区域管理服务
|
||||
├── roads/ # 道路网络模块
|
||||
│ ├── model/ # 道路网络运行时模型
|
||||
│ └── service/ # 道路网络服务
|
||||
├── rule/ # 规则引擎模块
|
||||
│ ├── event/ # 规则事件
|
||||
│ ├── model/ # 规则模型
|
||||
│ ├── repository/ # 规则仓储
|
||||
│ └── service/ # 规则服务
|
||||
└── websocket/ # WebSocket通信模块
|
||||
├── broadcaster/ # 消息广播器
|
||||
├── cache/ # WebSocket缓存
|
||||
├── config/ # WebSocket配置
|
||||
└── webSocket/ # WebSocket通信模块
|
||||
├── controller/ # WebSocket控制器
|
||||
├── event/ # WebSocket事件
|
||||
├── handler/ # WebSocket消息处理器
|
||||
├── listener/ # WebSocket监听器
|
||||
└── message/ # WebSocket消息格式
|
||||
└── config/ # WebSocket配置
|
||||
```
|
||||
|
||||
## 4. 数据模型说明
|
||||
|
||||
系统采用模块化的数据模型设计,各模块内部包含自己的数据模型定义。主要数据模型包括:
|
||||
## 4. 数据模型目录
|
||||
|
||||
### 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`)
|
||||
|
||||
包含用于绑定配置文件的POJO类,支持:
|
||||
- 机场区域配置绑定
|
||||
- 道路网络配置绑定
|
||||
- 几何图形配置绑定
|
||||
- 系统参数配置
|
||||
包含用于绑定配置文件的POJO类:
|
||||
|
||||
实际配置类根据模块需求动态添加和调整。
|
||||
```
|
||||
config/properties/
|
||||
├── AirportAreasProperties.java # 机场区域配置属性
|
||||
├── AirportRoadsProperties.java # 机场道路配置属性
|
||||
├── AreaProperties.java # 单个区域配置属性
|
||||
├── DimensionValue.java # 尺寸值配置
|
||||
├── GeometryProperties.java # 几何图形配置属性
|
||||
└── RoadProperties.java # 单个道路配置属性
|
||||
```
|
||||
|
||||
## 6. 资源文件目录 (`resources`)
|
||||
|
||||
@ -189,95 +164,103 @@ resources/
|
||||
│ ├── airport_areas.yaml # 机场区域配置
|
||||
│ ├── airport_roads.yaml # 机场道路配置
|
||||
│ └── airport_zones.yaml # 机场区域配置
|
||||
├── static/ # 静态Web资源
|
||||
│ ├── index.html # 主页面
|
||||
│ ├── geoposition-test.html # 地理位置测试页面
|
||||
│ ├── websocket-test.html # WebSocket测试页面
|
||||
│ └── js/ # JavaScript文件
|
||||
└── templates/ # 模板文件目录
|
||||
├── data/ # 数据文件目录
|
||||
├── scripts/ # 脚本文件目录
|
||||
└── static/ # 静态Web资源
|
||||
├── index.html # 主页面
|
||||
├── geoposition-test.html # 地理位置测试页面
|
||||
└── js/ # JavaScript文件
|
||||
```
|
||||
|
||||
## 7. 各模块功能说明
|
||||
|
||||
### 7.1 通用组件模块 (common)
|
||||
提供系统级通用功能和数据结构,包括:
|
||||
- 通用配置
|
||||
- 异常处理
|
||||
- 基础数据模型
|
||||
- 通用服务
|
||||
### 7.1 通用数据模型 (common/model)
|
||||
|
||||
### 7.2 机场区域管理模块 (area)
|
||||
负责管理机场内各种功能区域的边界、权限和规则:
|
||||
- 加载`airport_areas.yaml`配置文件
|
||||
- 构建空间索引以支持高效的几何查询
|
||||
- 提供区域查询接口(根据位置查找包含区域、获取限速等)
|
||||
- 支持区域权限验证和时间有效性检查
|
||||
- 使用JTS库进行复杂的空间几何计算
|
||||
数据模型模块定义了系统中使用的所有数据结构:
|
||||
|
||||
- **MovingObject**: 所有移动物体的抽象基类,定义共有属性和行为
|
||||
- **Aircraft**: 代表航空器的具体实现类
|
||||
- **SpecialVehicle**: 代表特勤车辆的具体实现类
|
||||
- **UnmannedVehicle**: 代表无人车的具体实现类
|
||||
- **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接口,用于:
|
||||
|
||||
- 数据查询和检索
|
||||
- 系统配置和管理
|
||||
- 状态报告和监控
|
||||
- 车辆控制命令发布
|
||||
|
||||
### 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)
|
||||
|
||||
@ -285,30 +268,10 @@ resources/
|
||||
|
||||
```
|
||||
doc/
|
||||
├── design/ # 设计文档目录
|
||||
│ ├── area_design.md # 区域设计文档
|
||||
│ ├── data_structure_design.md # 系统数据结构设计文档
|
||||
│ ├── directory_structure.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 # 无人车参数修复报告
|
||||
├── design_document.md # 系统数据结构设计文档
|
||||
├── directory_structure.md # 目录结构说明文档(本文档)
|
||||
├── cad_to_yaml_guide.md # CAD转YAML操作指南
|
||||
└── road_network_design.md # 道路网络配置集成设计方案
|
||||
```
|
||||
|
||||
## 9. 主要技术特性
|
||||
@ -327,39 +290,8 @@ doc/
|
||||
- 采用继承和组合的面向对象设计
|
||||
- 支持DTO模式进行数据传输
|
||||
- 实现仓储模式进行数据访问抽象
|
||||
- 模块化数据模型设计
|
||||
|
||||
### 9.4 实时通信
|
||||
- WebSocket支持实时数据推送
|
||||
- 线程池配置支持并发处理
|
||||
- 消息广播和路由机制
|
||||
|
||||
### 9.5 模块化架构
|
||||
- 清晰的模块划分和职责定义
|
||||
- 低耦合的模块间交互
|
||||
- 可扩展性强的系统架构
|
||||
|
||||
### 9.6 事件驱动
|
||||
- 基于事件的系统架构
|
||||
- 灵活的事件监听和处理机制
|
||||
- 松耦合的业务逻辑
|
||||
|
||||
### 9.7 规则引擎
|
||||
- 可配置的规则定义
|
||||
- 实时规则执行
|
||||
- 灵活的规则扩展机制
|
||||
|
||||
### 9.8 数据处理能力
|
||||
- 高性能的数据处理和分析
|
||||
- 实时数据解析和转换
|
||||
- 数据质量保障机制
|
||||
|
||||
### 9.9 地理围栏
|
||||
- 支持多种围栏类型
|
||||
- 实时围栏检测
|
||||
- 灵活的围栏规则配置
|
||||
|
||||
### 9.10 路径冲突检测
|
||||
- 高效的路径冲突检测算法
|
||||
- 智能的冲突解决策略
|
||||
- 实时冲突处理
|
||||
- Redis缓存支持高性能数据访问
|
||||
- 线程池配置支持并发处理
|
||||
@ -1,9 +1,5 @@
|
||||
## TODO Lists
|
||||
|
||||
### [2025-12-12]
|
||||
|
||||
- [x] (功能)修改机场中心点经纬度坐标,(120.08782536483503,36.36236546805307)
|
||||
|
||||
### [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使用虚拟线程处理请求
|
||||
* JDK21虚拟线程可以显著提高并发性能
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
|
||||
public AsyncTaskExecutor asyncTaskExecutor() {
|
||||
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
|
||||
|
||||
@ -89,6 +89,8 @@ data:
|
||||
# 机场数据源配置 - 开发环境
|
||||
airport-api:
|
||||
base-url: http://localhost:8090
|
||||
# 滑行路由
|
||||
glide-url: http://localhost:8099
|
||||
auth:
|
||||
username: dianxin
|
||||
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
|
||||
lettuce:
|
||||
pool:
|
||||
# 最小空闲连接数(保底连接)
|
||||
min-idle: 5
|
||||
# 最大空闲连接数
|
||||
max-idle: 20
|
||||
# 最大活跃连接数
|
||||
max-active: 50
|
||||
# 连接最大等待时间(毫秒),防止无限等待
|
||||
max-wait: 5000ms
|
||||
min-idle: 0
|
||||
max-idle: 8
|
||||
max-active: 8
|
||||
max-wait: -1ms
|
||||
|
||||
# Redis 内存优化配置(生产环境)
|
||||
redis:
|
||||
@ -83,9 +79,9 @@ data:
|
||||
# 适配器端口
|
||||
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}
|
||||
|
||||
|
||||
@ -40,8 +40,8 @@ spring:
|
||||
|
||||
# 默认激活的profile(开发环境)
|
||||
profiles:
|
||||
# active: dev,druid
|
||||
active: prod,druid
|
||||
active: dev,druid
|
||||
#active: prod,druid
|
||||
|
||||
# 文件上传
|
||||
servlet:
|
||||
@ -230,8 +230,8 @@ traffic:
|
||||
# 坐标系统配置(collision模块)
|
||||
coordinate-system:
|
||||
airport:
|
||||
center-longitude: 120.08782536
|
||||
center-latitude: 36.36236547
|
||||
center-longitude: 120.0834104
|
||||
center-latitude: 36.35406879
|
||||
|
||||
# 性能监控配置(collision模块扩展)
|
||||
management:
|
||||
@ -243,10 +243,11 @@ management:
|
||||
health:
|
||||
show-details: always
|
||||
metrics:
|
||||
export:
|
||||
simple:
|
||||
enabled: true
|
||||
enable:
|
||||
hikari: true
|
||||
jvm: true
|
||||
simple:
|
||||
metrics:
|
||||
export:
|
||||
enabled: true
|
||||
jmx:
|
||||
enabled: true
|
||||
|
||||
@ -42,7 +42,7 @@ public class AdxpFlightServiceHttpClient implements org.springframework.beans.fa
|
||||
private final RestTemplate restTemplate;
|
||||
private final ReentrantLock sessionLock = new ReentrantLock();
|
||||
|
||||
|
||||
private String sessionId;
|
||||
private String baseUrl;
|
||||
|
||||
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());
|
||||
log.info("ADXP HTTP客户端已初始化,等待连接状态确认");
|
||||
tryLogin();
|
||||
}
|
||||
|
||||
@Override
|
||||
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 {
|
||||
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();
|
||||
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(
|
||||
baseUrl + "/disconnect",
|
||||
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
|
||||
baseUrl + "/login",
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
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) {
|
||||
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() {
|
||||
if (sessionId == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
sessionLock.lock();
|
||||
try {
|
||||
String url = baseUrl + "/messages";
|
||||
String url = baseUrl + "/messages?sessionId=" + sessionId;
|
||||
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.GET,
|
||||
@ -106,6 +158,11 @@ public class AdxpFlightServiceHttpClient implements org.springframework.beans.fa
|
||||
if (body == null || !Boolean.TRUE.equals(body.get("success"))) {
|
||||
String message = body != null ? (String) body.get("message") : "Unknown error";
|
||||
log.warn("接收消息失败: {}", message);
|
||||
|
||||
// 如果是 session 过期,尝试重新登录
|
||||
if (message != null && message.contains("Session")) {
|
||||
tryLogin();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@ -256,60 +313,6 @@ public class AdxpFlightServiceHttpClient implements org.springframework.beans.fa
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
// 检查适配器是否连接到了ADXP数据中台
|
||||
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;
|
||||
}
|
||||
return sessionId != null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,6 @@ import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.WebSocketMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.client.WebSocketClient;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
@ -35,7 +34,7 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
||||
private final AtomicLong errorCount = new AtomicLong(0);
|
||||
|
||||
private WebSocketSession session;
|
||||
|
||||
private String sessionId;
|
||||
private Thread reconnectThread;
|
||||
|
||||
public AdxpFlightServiceWebSocketClient(WebSocketClient webSocketClient,
|
||||
@ -64,37 +63,39 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
||||
public void stop() {
|
||||
if (isRunning.compareAndSet(true, false)) {
|
||||
log.info("停止ADXP航班通知WebSocket客户端");
|
||||
disconnectWebSocket();
|
||||
disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到ADXP适配器的WebSocket服务
|
||||
*/
|
||||
@SuppressWarnings("removal") // 抑制WebSocketClient.doHandshake过期警告
|
||||
public void connect() {
|
||||
if (!properties.isConfigurationReady()) {
|
||||
log.warn("数据中台航班 SDK 配置不完整,WebSocket客户端将无法正常工作");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// 构建WebSocket URL - 新架构下不需要sessionId
|
||||
String wsUrl = String.format("ws://%s:%d/ws/flight-notifications",
|
||||
properties.getHost(), properties.getPort());
|
||||
|
||||
log.info("正在连接到ADXP适配器WebSocket服务: url={}", wsUrl);
|
||||
|
||||
// 连接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服务");
|
||||
// 首先通过HTTP登录获取sessionId
|
||||
sessionId = loginAndGetSessionId();
|
||||
if (sessionId == null) {
|
||||
log.error("无法获取sessionId,WebSocket连接失败");
|
||||
scheduleReconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建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) {
|
||||
log.error("❌ 连接ADXP适配器WebSocket服务失败", e);
|
||||
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连接
|
||||
*/
|
||||
public void disconnectWebSocket() {
|
||||
public void disconnect() {
|
||||
try {
|
||||
if (session != null && session.isOpen()) {
|
||||
session.close();
|
||||
log.info("WebSocket连接已关闭");
|
||||
}
|
||||
|
||||
// 断开ADXP适配器连接
|
||||
disconnectADXP();
|
||||
// 尝试登出
|
||||
logout();
|
||||
} catch (Exception e) {
|
||||
log.error("断开WebSocket连接时发生异常", e);
|
||||
errorCount.incrementAndGet();
|
||||
@ -127,33 +176,42 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开ADXP适配器连接
|
||||
* 登出
|
||||
*/
|
||||
private void disconnectADXP() {
|
||||
private void logout() {
|
||||
if (sessionId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String baseUrl = String.format("http://%s:%d/api/adxp",
|
||||
properties.getHost(), properties.getPort());
|
||||
|
||||
// 创建登出请求
|
||||
java.util.Map<String, String> logoutRequest = new java.util.HashMap<>();
|
||||
logoutRequest.put("sessionId", sessionId);
|
||||
|
||||
// 发送HTTP POST请求
|
||||
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()
|
||||
.uri(URI.create(disconnectUrl))
|
||||
.uri(URI.create(logoutUrl))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(java.net.http.HttpRequest.BodyPublishers.ofString("{}"))
|
||||
.POST(java.net.http.HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.build();
|
||||
|
||||
httpClient.send(request, java.net.http.HttpResponse.BodyHandlers.ofString());
|
||||
log.info("✅ 已断开ADXP适配器连接");
|
||||
log.info("✅ 已登出ADXP适配器服务");
|
||||
} catch (Exception e) {
|
||||
log.warn("断开ADXP适配器连接失败", e);
|
||||
log.warn("登出ADXP适配器服务失败", e);
|
||||
errorCount.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(@NonNull WebSocketSession session) throws Exception {
|
||||
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
|
||||
log.info("🟢 WebSocket连接已建立: sessionId={}", session.getId());
|
||||
this.session = session;
|
||||
isConnected.set(true);
|
||||
@ -161,7 +219,7 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
||||
}
|
||||
|
||||
@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) {
|
||||
handleTextMessage(session, (TextMessage) message);
|
||||
}
|
||||
@ -201,7 +259,7 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
||||
}
|
||||
|
||||
@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);
|
||||
errorCount.incrementAndGet();
|
||||
isConnected.set(false);
|
||||
@ -214,7 +272,7 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
||||
}
|
||||
|
||||
@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());
|
||||
isConnected.set(false);
|
||||
this.session = null;
|
||||
@ -386,14 +444,6 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
||||
public boolean isConnected() {
|
||||
return isConnected.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否启用
|
||||
* 主动连接架构下,返回连接状态
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排重新连接
|
||||
|
||||
@ -231,42 +231,16 @@ public class WebSocketMessageBroadcaster {
|
||||
// 使用Jackson ObjectMapper将UniversalMessage序列化为JSON字符串
|
||||
// 这样前端可以获得消息类型、时间戳、消息ID等完整信息
|
||||
String jsonMessage = objectMapper.writeValueAsString(message);
|
||||
this.collisionWebSocketHandler.broadcastMessage(jsonMessage);
|
||||
|
||||
// 根据消息类型决定是否缓存(避免高频消息阻塞Redis)
|
||||
// 高频实时消息(如位置更新)不缓存,追求实时性
|
||||
// 低频重要消息(如碰撞预警)缓存,保证不丢失
|
||||
if (shouldCacheMessage(message.getType())) {
|
||||
messageCacheService.cacheMessage(message);
|
||||
}
|
||||
|
||||
this.collisionWebSocketHandler.broadcastMessage(jsonMessage);
|
||||
|
||||
// 缓存消息用于重连恢复
|
||||
messageCacheService.cacheMessage(message);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to broadcast message via native WebSocket: " + e.getMessage());
|
||||
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
|
||||
|
||||
@ -7,8 +7,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 冲突检测WebSocket处理器
|
||||
@ -101,7 +99,7 @@ public class CollisionWebSocketHandler implements WebSocketHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息给指定会话(线程安全)- 添加发送失败跳过机制
|
||||
* 发送消息给指定会话(线程安全)
|
||||
*/
|
||||
private void sendMessage(WebSocketSession session, String message) {
|
||||
try {
|
||||
@ -114,66 +112,15 @@ public class CollisionWebSocketHandler implements WebSocketHandler {
|
||||
}
|
||||
}
|
||||
} catch (Exception 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());
|
||||
}
|
||||
LOGGER.error("发送消息失败 - 会话ID: {}", session.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 广播消息给所有连接的客户端 - 添加分批发送机制
|
||||
* 避免一次发送过多消息导致前端卡死
|
||||
* 广播消息给所有连接的客户端
|
||||
*/
|
||||
public void broadcastMessage(String 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);
|
||||
sessions.values().forEach(session -> sendMessage(session, message));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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 threading
|
||||
import atexit
|
||||
import random
|
||||
from typing import Any, Literal, final, TypedDict
|
||||
|
||||
# 导入统一的日志配置
|
||||
@ -201,7 +200,7 @@ def get_aircraft_route_params():
|
||||
"data": None
|
||||
}), 404
|
||||
|
||||
logger.info(f"进港路由参数查询: flightNo={flight_no}, params={route_params}")
|
||||
logging.info(f"进港路由参数查询: flightNo={flight_no}, params={route_params}")
|
||||
return jsonify({
|
||||
"status": 200,
|
||||
"msg": "进港路由参数查询成功",
|
||||
@ -225,7 +224,7 @@ def get_aircraft_route_params():
|
||||
"data": None
|
||||
}), 404
|
||||
|
||||
logger.info(f"出港路由参数查询: flightNo={flight_no}, params={route_params}")
|
||||
logging.info(f"出港路由参数查询: flightNo={flight_no}, params={route_params}")
|
||||
return jsonify({
|
||||
"status": 200,
|
||||
"msg": "出港路由参数查询成功",
|
||||
@ -959,8 +958,8 @@ class AirportCoordinateSystem:
|
||||
print("警告: pyproj库未安装,将回退到简化转换算法")
|
||||
self.use_pyproj = False
|
||||
# 回退到原来的参数
|
||||
self.center_lon = 120.08782536
|
||||
self.center_lat = 36.36236547
|
||||
self.center_lon = 120.0834104
|
||||
self.center_lat = 36.35406879
|
||||
self.utm_origin_x = 40507423
|
||||
self.utm_origin_y = 4026164
|
||||
self.meters_per_degree_lon = 89932
|
||||
@ -1069,11 +1068,11 @@ def convert_route_to_cgcs2000(route_data: dict) -> dict:
|
||||
try:
|
||||
# 检查当前坐标系类型
|
||||
current_coordinate_system = route_data.get("coordinateSystem", "WGS84")
|
||||
logger.info(f"转换路由数据坐标系: 当前={current_coordinate_system} -> 目标=CGCS2000")
|
||||
logging.info(f"转换路由数据坐标系: 当前={current_coordinate_system} -> 目标=CGCS2000")
|
||||
|
||||
# 如果已经是CGCS2000坐标系,直接返回
|
||||
if current_coordinate_system == "CGCS2000":
|
||||
logger.info("路由数据已经是CGCS2000坐标系,无需转换")
|
||||
logging.info("路由数据已经是CGCS2000坐标系,无需转换")
|
||||
return converted_route
|
||||
|
||||
# 如果是WGS84坐标系,需要转换为CGCS2000
|
||||
@ -1081,7 +1080,7 @@ def convert_route_to_cgcs2000(route_data: dict) -> dict:
|
||||
geo_path = converted_route.get("geoPath", {})
|
||||
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:
|
||||
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)
|
||||
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
|
||||
@ -1107,12 +1106,12 @@ def convert_route_to_cgcs2000(route_data: dict) -> dict:
|
||||
# 更新坐标系标识
|
||||
converted_route["coordinateSystem"] = "CGCS2000"
|
||||
|
||||
logger.info(f"WGS84到CGCS2000坐标转换完成,共转换 {len(features)} 个feature")
|
||||
logging.info(f"WGS84到CGCS2000坐标转换完成,共转换 {len(features)} 个feature")
|
||||
|
||||
return converted_route
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"路由数据坐标转换失败: {str(e)}")
|
||||
logging.error(f"路由数据坐标转换失败: {str(e)}")
|
||||
# 转换失败时返回原始数据,但确保坐标系标识为CGCS2000
|
||||
converted_route["coordinateSystem"] = "CGCS2000"
|
||||
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", "")
|
||||
if codes_str:
|
||||
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:
|
||||
logger.warning(f"航班 {flight_no} 缺少codes字段,无法验证方向")
|
||||
logging.warning(f"航班 {flight_no} 缺少codes字段,无法验证方向")
|
||||
code_order = []
|
||||
|
||||
# 收集所有LineString路径段,并记录每段的code标识
|
||||
@ -1161,7 +1160,7 @@ def merge_discontinuous_route_for_flight(route_data: dict, flight_no: str) -> di
|
||||
# 如果成功合并为单条路径
|
||||
if isinstance(merged_geometry, LineString):
|
||||
continuous_coords = list(merged_geometry.coords)
|
||||
logger.info(f"✅ 航班 {flight_no} 合并为连续路径,包含 {len(continuous_coords)} 个坐标点")
|
||||
logging.info(f"✅ 航班 {flight_no} 合并为连续路径,包含 {len(continuous_coords)} 个坐标点")
|
||||
|
||||
# 基于codes字段验证和修正路径方向
|
||||
if len(code_order) >= 2:
|
||||
@ -1182,14 +1181,14 @@ def merge_discontinuous_route_for_flight(route_data: dict, flight_no: str) -> di
|
||||
else:
|
||||
# 无法合并就报错,不能保持原样掩盖问题
|
||||
error_msg = f"❌ 航班 {flight_no} 路径无法合并为连续路径,存在不连续段"
|
||||
logger.error(error_msg)
|
||||
logging.error(error_msg)
|
||||
raise RuntimeError(error_msg)
|
||||
|
||||
except ImportError:
|
||||
logger.warning(f"Shapely库未安装,跳过路径合并")
|
||||
logging.warning(f"Shapely库未安装,跳过路径合并")
|
||||
return route_data
|
||||
except Exception as e:
|
||||
logger.error(f"航班 {flight_no} 路径合并失败: {str(e)}")
|
||||
logging.error(f"航班 {flight_no} 路径合并失败: {str(e)}")
|
||||
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])
|
||||
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进行判断
|
||||
expected_start = code_order[0] # F1
|
||||
# 对于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:
|
||||
logger.info(f"✅ 航班 {flight_no} 路径方向正确: 从{expected_start}开始")
|
||||
logging.info(f"✅ 航班 {flight_no} 路径方向正确: 从{expected_start}开始")
|
||||
else:
|
||||
# 方向相反,需要反转
|
||||
continuous_coords.reverse()
|
||||
logger.info(f"✅ 航班 {flight_no} 路径方向已修正: 从{expected_start}开始 (原起点: {start_code})")
|
||||
logging.info(f"✅ 航班 {flight_no} 路径方向已修正: 从{expected_start}开始 (原起点: {start_code})")
|
||||
|
||||
return continuous_coords
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"航班 {flight_no} 路径方向验证失败: {str(e)}")
|
||||
logging.error(f"航班 {flight_no} 路径方向验证失败: {str(e)}")
|
||||
return continuous_coords
|
||||
|
||||
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", [])
|
||||
|
||||
if len(features) == 0:
|
||||
logger.warning(f"⚠️ 航班 {flight_no} 路径数据为空,无路径段")
|
||||
logging.warning(f"⚠️ 航班 {flight_no} 路径数据为空,无路径段")
|
||||
return route_data
|
||||
|
||||
logger.info(f"✅ 航班 {flight_no} 路径数据完整:包含 {len(features)} 个路径段")
|
||||
logging.info(f"✅ 航班 {flight_no} 路径数据完整:包含 {len(features)} 个路径段")
|
||||
|
||||
# 验证每个路径段的数据完整性
|
||||
valid_segments = 0
|
||||
@ -1282,22 +1281,22 @@ def validate_route_data_integrity(route_data: dict, flight_no: str) -> dict:
|
||||
if len(coordinates) >= 2:
|
||||
valid_segments += 1
|
||||
total_coordinates += len(coordinates)
|
||||
logger.debug(f" 路径段 {i+1}: 代码={code}, 坐标点数={len(coordinates)} ✅")
|
||||
logging.debug(f" 路径段 {i+1}: 代码={code}, 坐标点数={len(coordinates)} ✅")
|
||||
else:
|
||||
logger.warning(f" 路径段 {i+1}: 代码={code}, 坐标点数不足({len(coordinates)}) ⚠️")
|
||||
logging.warning(f" 路径段 {i+1}: 代码={code}, 坐标点数不足({len(coordinates)}) ⚠️")
|
||||
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", "未知")
|
||||
logger.info(f"✅ 航班 {flight_no} 坐标系:{coordinate_system}")
|
||||
logging.info(f"✅ 航班 {flight_no} 坐标系:{coordinate_system}")
|
||||
|
||||
return route_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"航班 {flight_no} 路径数据完整性验证异常: {str(e)}")
|
||||
logging.error(f"航班 {flight_no} 路径数据完整性验证异常: {str(e)}")
|
||||
return route_data
|
||||
|
||||
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:
|
||||
# 检查坐标系类型
|
||||
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", {})
|
||||
features = geo_path.get("features", [])
|
||||
|
||||
logger.info(f"解析路径,共有 {len(features)} 个feature")
|
||||
logging.info(f"解析路径,共有 {len(features)} 个feature")
|
||||
|
||||
for feature in features:
|
||||
geometry = feature.get("geometry", {})
|
||||
@ -1335,23 +1334,23 @@ def parse_route_path(route_data: dict) -> list[tuple[float, float]]:
|
||||
if coordinate_system_type == "WGS84":
|
||||
# 如果已经是WGS84坐标,直接使用 (经度, 纬度)
|
||||
lat, lon = y, x
|
||||
logger.debug(f"WGS84坐标直接使用: lon={lon}, lat={lat}")
|
||||
logging.debug(f"WGS84坐标直接使用: lon={lon}, lat={lat}")
|
||||
else:
|
||||
# 如果是CGCS2000坐标,需要转换
|
||||
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))
|
||||
|
||||
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
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"路径解析失败: {str(e)}")
|
||||
logging.error(f"路径解析失败: {str(e)}")
|
||||
return []
|
||||
|
||||
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)
|
||||
if not flight_routes:
|
||||
logger.warning(f"未找到航班 {self.flight_no} 的路由数据,使用MU5123的路由作为默认")
|
||||
logging.warning(f"未找到航班 {self.flight_no} 的路由数据,使用MU5123的路由作为默认")
|
||||
flight_routes = aircraft_routes.get("MU5123", {})
|
||||
|
||||
# 解析进港路径
|
||||
@ -1406,20 +1405,20 @@ class AircraftRouteFollower:
|
||||
|
||||
# 验证MU5123进港路径的数据完整性,然后合并为连续路径
|
||||
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)
|
||||
|
||||
# 将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)
|
||||
|
||||
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", {})
|
||||
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号机位的大概位置)
|
||||
if self.arrival_points:
|
||||
@ -1431,7 +1430,7 @@ class AircraftRouteFollower:
|
||||
self.gate_position = (36.354068, 120.083410)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"路径解析失败: {str(e)}")
|
||||
logging.error(f"路径解析失败: {str(e)}")
|
||||
self.arrival_points = []
|
||||
self.departure_points = []
|
||||
self.gate_position = (36.354068, 120.083410)
|
||||
@ -1446,7 +1445,7 @@ class AircraftRouteFollower:
|
||||
self.is_at_gate = False
|
||||
self.flight_status = 'in_route' # 设置为路径运行状态
|
||||
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:
|
||||
self.current_route_type = "departure"
|
||||
@ -1456,10 +1455,10 @@ class AircraftRouteFollower:
|
||||
self.is_at_gate = False
|
||||
self.flight_status = 'in_route' # 设置为路径运行状态
|
||||
self.wait_until = 0.0 # 清除等待时间
|
||||
logger.info(f"航空器 {self.flight_no} 开始出港路径跟随")
|
||||
logging.info(f"航空器 {self.flight_no} 开始出港路径跟随")
|
||||
|
||||
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:
|
||||
"""
|
||||
@ -1522,7 +1521,7 @@ class AircraftRouteFollower:
|
||||
self.flight_status = 'at_gate' if self.is_at_gate else 'completed'
|
||||
# 设置5秒等待时间
|
||||
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
|
||||
|
||||
# 更新到下一个目标点
|
||||
@ -1622,12 +1621,12 @@ class AircraftRouteManager:
|
||||
if follower.route_points:
|
||||
aircraft["latitude"] = follower.route_points[0][0]
|
||||
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)
|
||||
if route_completed and follower.flight_status != 'waiting':
|
||||
# 路径完成,已设置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,
|
||||
speed: float, elapsed_time: float):
|
||||
@ -1641,12 +1640,12 @@ class AircraftRouteManager:
|
||||
if follower.route_points:
|
||||
aircraft["latitude"] = follower.route_points[0][0]
|
||||
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)
|
||||
if route_completed and follower.flight_status != 'waiting':
|
||||
# 路径完成,已设置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())
|
||||
|
||||
# 额外测试飞机数据(用于前端性能测试)
|
||||
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
|
||||
|
||||
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.update_thread = threading.Thread(target=self._update_loop, daemon=True)
|
||||
self.update_thread.start()
|
||||
logger.info("后台更新管理器已启动")
|
||||
logging.info("后台更新管理器已启动")
|
||||
|
||||
def stop(self):
|
||||
"""停止后台更新线程"""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user