adxp-adapter自主管理登录状态
This commit is contained in:
parent
7641184388
commit
f08a4dcf0a
203
README.md
203
README.md
@ -17,6 +17,7 @@
|
||||
## 核心特性
|
||||
|
||||
### 🚗 冲突避免系统 (collision模块)
|
||||
|
||||
- **空间数据分析**: 基于PostGIS的空间计算和几何分析
|
||||
- **实时车辆监控**: WebSocket实时位置数据推送和展示
|
||||
- **机场区域管理**: 跑道、滑行道、停机坪等区域配置和监控
|
||||
@ -26,15 +27,15 @@
|
||||
|
||||
### 🛠️ 系统管理功能
|
||||
|
||||
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
|
||||
3. 岗位管理:配置系统用户所属担任职务。
|
||||
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
|
||||
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
|
||||
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
7. 参数管理:对系统动态配置常用参数。
|
||||
8. 通知公告:系统通知公告信息发布维护。
|
||||
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
|
||||
3. 岗位管理:配置系统用户所属担任职务。
|
||||
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
|
||||
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
|
||||
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
7. 参数管理:对系统动态配置常用参数。
|
||||
8. 通知公告:系统通知公告信息发布维护。
|
||||
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
10. 登录日志:系统登录日志记录查询包含登录异常。
|
||||
11. 在线用户:当前系统中活跃用户状态监控。
|
||||
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
|
||||
@ -55,65 +56,194 @@
|
||||
- **构建工具**: Maven 3.6+
|
||||
- **Java版本**: JDK 8
|
||||
|
||||
## 快速启动
|
||||
## 部署指南
|
||||
|
||||
### 方式一:自动部署(推荐)
|
||||
|
||||
使用提供的自动化部署脚本:
|
||||
|
||||
### 1. 环境准备
|
||||
```bash
|
||||
# Java 8
|
||||
# 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 -version
|
||||
|
||||
# Maven 3.6+
|
||||
mvn -version
|
||||
|
||||
# PostgreSQL + PostGIS
|
||||
# Docker & Docker Compose
|
||||
docker --version
|
||||
docker-compose --version
|
||||
```
|
||||
|
||||
**可选组件(用于本地开发):**
|
||||
|
||||
```bash
|
||||
# PostgreSQL + PostGIS(用于本地开发)
|
||||
psql --version
|
||||
|
||||
# Redis
|
||||
# Redis(用于本地开发)
|
||||
redis-server --version
|
||||
```
|
||||
|
||||
### 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` - 司机信息表
|
||||
#### 2. 数据库配置
|
||||
|
||||
**Docker启动数据库:**
|
||||
|
||||
### 3. 启动Redis服务
|
||||
```bash
|
||||
redis-server
|
||||
# 启动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
|
||||
```
|
||||
|
||||
### 4. 配置应用
|
||||
**本地数据库初始化:**
|
||||
|
||||
1. 执行SQL初始化脚本:
|
||||
|
||||
```bash
|
||||
# 创建数据库和基础表
|
||||
psql -U qaup -d qaup -f sql/create_qaup_database.sql
|
||||
psql -U qaup -d qaup -f sql/create_sys_vehicle_info_table.sql
|
||||
psql -U qaup -d qaup -f sql/create_sys_driver_info_table.sql
|
||||
```
|
||||
|
||||
#### 3. 环境配置
|
||||
|
||||
**更新应用配置:**
|
||||
修改 `qaup-admin/src/main/resources/application.yml`:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/your_database
|
||||
username: your_username
|
||||
password: your_password
|
||||
url: jdbc:postgresql://localhost:5432/qaup
|
||||
username: qaup
|
||||
password: qaup123
|
||||
driver-class-name: org.postgresql.Driver
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
password: qaup123
|
||||
database: 0
|
||||
```
|
||||
|
||||
### 5. 编译和启动
|
||||
**环境变量配置:**
|
||||
确保各模块 `.env` 文件配置正确:
|
||||
|
||||
- `adxp-adapter/.env.example` - ADXP数据中台适配器配置
|
||||
- `qaup-admin/.env.example` - 主应用配置
|
||||
|
||||
#### 4. 编译和启动
|
||||
|
||||
```bash
|
||||
# 清理并编译整个项目
|
||||
# 1. 清理并编译整个项目
|
||||
mvn clean install
|
||||
|
||||
# 启动应用
|
||||
# 2. 进入管理模块
|
||||
cd qaup-admin
|
||||
|
||||
# 3. 启动应用(开发模式)
|
||||
mvn spring-boot:run
|
||||
|
||||
# 或者运行打包后的jar
|
||||
# 或打包启动(生产模式)
|
||||
mvn clean package -DskipTests
|
||||
java -jar target/qaup-admin.jar
|
||||
```
|
||||
|
||||
### 6. 访问系统
|
||||
- 管理后台: http://localhost:8080
|
||||
- WebSocket端点: ws://localhost:8080/ws
|
||||
- API文档: http://localhost:8080/swagger-ui/
|
||||
#### 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
|
||||
```
|
||||
|
||||
## 项目合并说明
|
||||
|
||||
@ -127,16 +257,19 @@ java -jar target/qaup-admin.jar
|
||||
## 开发指南
|
||||
|
||||
### 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,13 +4,68 @@
|
||||
# 1. 复制此文件为 .env: cp .env.example .env
|
||||
# 2. 修改 .env 中的配置值
|
||||
# 3. 启动应用时会自动加载 .env 文件中的环境变量
|
||||
# 4. 适用于ADXP主动连接架构
|
||||
# ============================================================
|
||||
|
||||
# ========== ADXP 数据中台配置 ==========
|
||||
ADXP_HOST=localhost
|
||||
ADXP_PORT=7001
|
||||
# ========== ADXP 数据中台连接配置 ==========
|
||||
# ADXP数据中台的主机地址和端口
|
||||
ADXP_HOST=10.32.38.2
|
||||
ADXP_PORT=8081
|
||||
|
||||
# 连接ADXP数据中台的用户名和密码
|
||||
ADXP_USERNAME=dianxin
|
||||
ADXP_PASSWORD=dianxin@123
|
||||
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内存
|
||||
|
||||
# ============================================================
|
||||
# 注意事项:
|
||||
@ -18,4 +73,6 @@ ADXP_PASSWORD=dianxin@123
|
||||
# 2. 字符串值不需要引号
|
||||
# 3. #开头的行为注释
|
||||
# 4. 请勿将 .env 文件提交到Git仓库
|
||||
# 5. 生产环境请务必修改默认密码
|
||||
# 6. ADXP适配器采用主动连接架构,启动时自动连接数据中台
|
||||
# ============================================================
|
||||
@ -2,7 +2,17 @@
|
||||
|
||||
ADXP 数据中台 SDK 适配器服务 - 基于 JDK 8 运行的独立微服务
|
||||
|
||||
## 功能说明
|
||||
## 新架构特性(ADXP主动连接)
|
||||
|
||||
### 🔄 **适配器主动连接架构**
|
||||
|
||||
- **启动即连接**: 适配器服务启动时自动连接到ADXP数据中台
|
||||
- **无需登录接口**: 移除 `/login` 端点,由适配器自动管理认证
|
||||
- **连接状态管理**: 提供 `/status` 和 `/reconnect` 端点进行连接管理
|
||||
- **WebSocket实时推送**: 支持实时向客户端推送航班消息
|
||||
- **健康监控**: 内置连接状态监控和自动重连机制
|
||||
|
||||
## 功能特性
|
||||
|
||||
将青岛机场数据中台 SDK (adxp-client-2.6.9.jar) 封装为 REST API 服务,解决 SDK 与现代 Java 版本的兼容性问题。
|
||||
|
||||
@ -28,7 +38,55 @@ docker compose logs -f
|
||||
docker compose down
|
||||
```
|
||||
|
||||
## API 文档
|
||||
### API接口(ADXP主动连接架构)
|
||||
|
||||
#### 获取连接状态
|
||||
|
||||
```
|
||||
GET /api/adxp/status
|
||||
```
|
||||
|
||||
返回当前ADXP数据中台的连接状态和会话信息。
|
||||
|
||||
#### 强制重连
|
||||
|
||||
```
|
||||
POST /api/adxp/reconnect
|
||||
```
|
||||
|
||||
强制断开当前连接并重新连接ADXP数据中台。
|
||||
|
||||
#### 获取消息接口
|
||||
|
||||
```
|
||||
GET /api/adxp/messages
|
||||
```
|
||||
|
||||
使用当前活动连接获取ADXP数据中台的航班消息。
|
||||
|
||||
#### 断开连接接口
|
||||
|
||||
```
|
||||
POST /api/adxp/disconnect
|
||||
```
|
||||
|
||||
主动断开与ADXP数据中台的连接。
|
||||
|
||||
#### 健康检查
|
||||
|
||||
```
|
||||
GET /api/adxp/health
|
||||
```
|
||||
|
||||
检查适配器服务健康状态和ADXP连接状态。
|
||||
|
||||
#### WebSocket实时推送
|
||||
|
||||
```
|
||||
ws://localhost:8086/ws/flight-notifications
|
||||
```
|
||||
|
||||
实时接收来自ADXP数据中台的航班消息推送。
|
||||
|
||||
### 1. 登录
|
||||
|
||||
@ -43,6 +101,7 @@ Content-Type: application/json
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
@ -58,6 +117,7 @@ GET /api/adxp/messages?sessionId=550e8400-e29b-41d4-a716-446655440000
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
@ -90,6 +150,7 @@ GET /api/adxp/health
|
||||
```
|
||||
|
||||
响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "UP",
|
||||
@ -99,18 +160,36 @@ GET /api/adxp/health
|
||||
|
||||
## 快速测试
|
||||
|
||||
### API调用示例
|
||||
|
||||
#### 1. 检查连接状态
|
||||
|
||||
```bash
|
||||
curl http://localhost:8086/api/adxp/status
|
||||
```
|
||||
|
||||
#### 2. 强制重连
|
||||
|
||||
```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
|
||||
# 健康检查
|
||||
curl http://localhost:8086/api/adxp/health
|
||||
# 预期输出: {"status":"UP","activeSessions":0}
|
||||
|
||||
# 登录
|
||||
curl -X POST http://localhost:8086/api/adxp/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"dianxin","password":"dianxin@123"}'
|
||||
|
||||
# 接收消息(替换为实际的 sessionId)
|
||||
curl "http://localhost:8086/api/adxp/messages?sessionId=YOUR_SESSION_ID"
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
@ -7,8 +7,10 @@ services:
|
||||
- "8086:8086"
|
||||
environment:
|
||||
# ADXP 数据中台连接配置(必须设置)
|
||||
ADXP_HOST: ${ADXP_HOST:-10.10.10.100}
|
||||
ADXP_PORT: ${ADXP_PORT:-7001}
|
||||
ADXP_HOST: ${ADXP_HOST:-10.32.38.2}
|
||||
ADXP_PORT: ${ADXP_PORT:-8081}
|
||||
ADXP_USERNAME: ${ADXP_USERNAME:-dianxin}
|
||||
ADXP_PASSWORD: ${ADXP_PASSWORD:-Dianxin#2025}
|
||||
# Spring Profile
|
||||
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod}
|
||||
volumes:
|
||||
|
||||
@ -26,51 +26,82 @@ public class AdxpController {
|
||||
private AdxpWebSocketHandler adxpWebSocketHandler;
|
||||
|
||||
/**
|
||||
* 登录接口
|
||||
* 获取连接状态
|
||||
*/
|
||||
@PostMapping(value = "/login", produces = "application/json")
|
||||
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
|
||||
@GetMapping("/status")
|
||||
public ResponseEntity<Map<String, Object>> getStatus() {
|
||||
try {
|
||||
String sessionId = adxpSdkService.login(request.getUsername(), request.getPassword());
|
||||
return ResponseEntity.ok(LoginResponse.success(sessionId));
|
||||
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);
|
||||
} catch (Exception e) {
|
||||
log.error("登录失败", e);
|
||||
return ResponseEntity.ok(LoginResponse.failure(e.getMessage()));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收消息接口
|
||||
* 获取消息接口 - 使用当前连接
|
||||
*/
|
||||
@GetMapping("/messages")
|
||||
public ResponseEntity<MessageResponse> getMessages(@RequestParam String sessionId) {
|
||||
public ResponseEntity<MessageResponse> getMessages() {
|
||||
try {
|
||||
List<FlightMessage> messages = adxpSdkService.receiveMessages(sessionId);
|
||||
if (!adxpSdkService.isConnected()) {
|
||||
return ResponseEntity.ok(MessageResponse.failure("未连接到ADXP数据中台"));
|
||||
}
|
||||
List<FlightMessage> messages = adxpSdkService.getMessages();
|
||||
return ResponseEntity.ok(MessageResponse.success(messages));
|
||||
} catch (Exception e) {
|
||||
log.error("接收消息失败: sessionId={}", sessionId, e);
|
||||
log.error("接收消息失败", e);
|
||||
return ResponseEntity.ok(MessageResponse.failure(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出接口
|
||||
* 断开连接接口
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
public ResponseEntity<Map<String, Object>> logout(@RequestBody Map<String, String> request) {
|
||||
@PostMapping("/disconnect")
|
||||
public ResponseEntity<Map<String, Object>> disconnect() {
|
||||
try {
|
||||
String sessionId = request.get("sessionId");
|
||||
adxpSdkService.logout(sessionId);
|
||||
|
||||
Map<String, Object> response = new HashMap<String, Object>();
|
||||
adxpSdkService.disconnectFromADXPServer();
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", true);
|
||||
response.put("message", "登出成功");
|
||||
response.put("message", "已断开ADXP数据中台连接");
|
||||
response.put("connected", false);
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("登出失败", e);
|
||||
Map<String, Object> response = new HashMap<String, Object>();
|
||||
log.error("断开连接失败", e);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", false);
|
||||
response.put("message", e.getMessage());
|
||||
response.put("message", "断开连接失败: " + e.getMessage());
|
||||
response.put("connected", adxpSdkService.isConnected());
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
@ -80,12 +111,20 @@ public class AdxpController {
|
||||
*/
|
||||
@GetMapping("/health")
|
||||
public ResponseEntity<Map<String, Object>> 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());
|
||||
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);
|
||||
}
|
||||
return ResponseEntity.ok(health);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,9 +12,13 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class AdxpSdkService {
|
||||
@ -27,98 +31,200 @@ public class AdxpSdkService {
|
||||
@Value("${adxp.port}")
|
||||
private int port;
|
||||
|
||||
@Value("${adxp.username:}")
|
||||
@Value("${adxp.username:dianxin}")
|
||||
private String defaultUsername;
|
||||
|
||||
@Value("${adxp.password:}")
|
||||
@Value("${adxp.password:Dianxin#2025}")
|
||||
private String defaultPassword;
|
||||
|
||||
// Session 管理: sessionId -> SessionInfo
|
||||
private final Map<String, SessionInfo> sessions = new ConcurrentHashMap<>();
|
||||
|
||||
public Map<String, SessionInfo> getSessions() {
|
||||
return sessions;
|
||||
}
|
||||
// 连接状态管理
|
||||
private volatile boolean connected = false;
|
||||
private volatile String currentSessionId = null;
|
||||
private ADXPClient currentClient = null;
|
||||
|
||||
public static class SessionInfo {
|
||||
ADXPClient client;
|
||||
String username;
|
||||
String password;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 定时重连任务
|
||||
private final ScheduledExecutorService reconnectScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
private final Object lock = new Object();
|
||||
|
||||
/**
|
||||
* 登录数据中台
|
||||
*/
|
||||
public String login(String username, String password) {
|
||||
// 如果未提供用户名或密码,则使用配置文件中的默认值
|
||||
String actualUsername = (username == null || username.isEmpty()) ? defaultUsername : username;
|
||||
String actualPassword = (password == null || password.isEmpty()) ? defaultPassword : password;
|
||||
|
||||
// 应用启动时主动连接
|
||||
@PostConstruct
|
||||
public void initConnection() {
|
||||
log.info("ADXP适配器启动,主动连接数据中台...");
|
||||
try {
|
||||
log.info("正在登录 ADXP 数据中台: host={}, port={}, username={}", host, port, actualUsername);
|
||||
|
||||
// 创建 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;
|
||||
|
||||
connectToADXP();
|
||||
// 启动重连监控
|
||||
startReconnectMonitor();
|
||||
} catch (Exception e) {
|
||||
log.error("登录异常", e);
|
||||
throw new RuntimeException("登录异常: " + e.getMessage(), e);
|
||||
log.error("ADXP数据中台初始连接失败: {}", e.getMessage());
|
||||
// 启动定时重连
|
||||
scheduleReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// 应用关闭时清理资源
|
||||
@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);
|
||||
|
||||
// 创建客户端
|
||||
currentClient = ADXPClientFactory.createWSClient(host, port);
|
||||
|
||||
// 登录
|
||||
LoginResult result = currentClient.login(defaultUsername, defaultPassword);
|
||||
|
||||
currentSessionId = UUID.randomUUID().toString();
|
||||
connected = true;
|
||||
|
||||
if (result == null || !Boolean.TRUE.equals(result.isSuccess())) {
|
||||
String errorMsg = result != null ?
|
||||
String.format("code=%s, message=%s", result.getCode(), result.getMessage()) :
|
||||
"登录响应为空";
|
||||
log.warn("登录响应解析失败: {} - 但连接可能已建立", errorMsg);
|
||||
} else {
|
||||
log.info("ADXP数据中台连接成功! sessionId={}", currentSessionId);
|
||||
}
|
||||
|
||||
// 启动消息监听
|
||||
startMessageListener();
|
||||
|
||||
} catch (Exception e) {
|
||||
connected = false;
|
||||
currentClient = null;
|
||||
currentSessionId = null;
|
||||
log.error("ADXP数据中台连接失败: {}", e.getMessage());
|
||||
throw new RuntimeException("ADXP连接失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 断开连接
|
||||
private void disconnectFromADXP() {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
if (currentClient != null) {
|
||||
currentClient.logout(defaultUsername, defaultPassword);
|
||||
}
|
||||
connected = false;
|
||||
currentSessionId = null;
|
||||
currentClient = null;
|
||||
log.info("已断开ADXP数据中台连接");
|
||||
} catch (Exception e) {
|
||||
log.error("断开连接时发生错误", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 启动重连监控
|
||||
private void startReconnectMonitor() {
|
||||
// 这里可以添加连接健康检查
|
||||
log.info("连接监控已启动");
|
||||
}
|
||||
|
||||
// 定时重连任务
|
||||
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 && currentClient != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前连接信息
|
||||
*/
|
||||
public String getConnectionInfo() {
|
||||
if (connected) {
|
||||
return String.format("已连接到ADXP数据中台 (host=%s, port=%s, sessionId=%s)",
|
||||
host, port, currentSessionId);
|
||||
} else {
|
||||
return "未连接到ADXP数据中台";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制重连 - 供外部调用
|
||||
*/
|
||||
public void forceReconnect() {
|
||||
log.info("外部请求强制重连ADXP数据中台");
|
||||
disconnectFromADXP();
|
||||
try {
|
||||
connectToADXP();
|
||||
log.info("强制重连成功");
|
||||
} catch (Exception e) {
|
||||
log.error("强制重连失败", e);
|
||||
throw new RuntimeException("重连失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收消息
|
||||
* 启动消息监听线程
|
||||
*/
|
||||
public List<FlightMessage> receiveMessages(String sessionId) {
|
||||
SessionInfo sessionInfo = sessions.get(sessionId);
|
||||
if (sessionInfo == null) {
|
||||
throw new IllegalStateException("Session 不存在或已过期: " + sessionId);
|
||||
private void startMessageListener() {
|
||||
Thread messageListener = new Thread(() -> {
|
||||
log.info("ADXP消息监听线程已启动");
|
||||
while (connected && currentClient != null) {
|
||||
try {
|
||||
List<FlightMessage> messages = receiveMessagesInternal();
|
||||
if (!messages.isEmpty()) {
|
||||
log.info("接收到 {} 条消息", messages.size());
|
||||
// 这里可以将消息发送到WebSocket或消息队列
|
||||
processMessages(messages);
|
||||
}
|
||||
Thread.sleep(1000); // 每秒检查一次
|
||||
} catch (InterruptedException e) {
|
||||
log.info("消息监听线程被中断");
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
log.error("消息监听异常", e);
|
||||
try {
|
||||
Thread.sleep(5000); // 异常时等待5秒再重试
|
||||
} catch (InterruptedException ie) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("ADXP消息监听线程已结束");
|
||||
}, "ADXP-Message-Listener");
|
||||
|
||||
messageListener.setDaemon(true);
|
||||
messageListener.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部消息接收方法
|
||||
*/
|
||||
private List<FlightMessage> receiveMessagesInternal() {
|
||||
if (!connected || currentClient == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
try {
|
||||
MessageResult result = sessionInfo.client.receiveMessage();
|
||||
MessageResult result = currentClient.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();
|
||||
}
|
||||
|
||||
@ -141,34 +247,77 @@ public class AdxpSdkService {
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("接收到 {} 条消息", messages.size());
|
||||
return messages;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("接收消息异常: sessionId={}", sessionId, e);
|
||||
throw new RuntimeException("接收消息异常: " + e.getMessage(), e);
|
||||
log.error("接收消息异常", e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 登出
|
||||
* 处理接收到的消息
|
||||
*/
|
||||
public void logout(String sessionId) {
|
||||
SessionInfo sessionInfo = sessions.remove(sessionId);
|
||||
if (sessionInfo != null) {
|
||||
try {
|
||||
sessionInfo.client.logout(sessionInfo.username, sessionInfo.password);
|
||||
log.info("登出成功: sessionId={}", sessionId);
|
||||
} catch (Exception e) {
|
||||
log.error("登出异常: sessionId={}", sessionId, e);
|
||||
}
|
||||
private void processMessages(List<FlightMessage> messages) {
|
||||
// TODO: 实现消息处理逻辑
|
||||
// 可以发送到消息队列、WebSocket推送等
|
||||
for (FlightMessage message : messages) {
|
||||
log.debug("处理消息: serviceCode={}", message.getServiceCode());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会话数
|
||||
* 获取消息 - 供外部调用
|
||||
*/
|
||||
public List<FlightMessage> getMessages() {
|
||||
return receiveMessagesInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接 - 主动断开与ADXP数据中台的连接
|
||||
* 注意:在ADXP主动连接架构下,适配器启动时自动连接
|
||||
* 该方法主要用于管理连接状态或强制断开连接
|
||||
*/
|
||||
public void disconnectFromADXPServer() {
|
||||
log.info("主动断开ADXP数据中台连接");
|
||||
disconnectFromADXP();
|
||||
}
|
||||
|
||||
/**
|
||||
* 向后兼容的logout方法 - 调用disconnectFromADXPServer
|
||||
* @deprecated 请使用 {@link #disconnectFromADXPServer()} 替代
|
||||
*/
|
||||
@Deprecated
|
||||
public void logout() {
|
||||
log.warn("logout()方法已废弃,请使用 disconnectFromADXPServer() 替代");
|
||||
disconnectFromADXPServer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接状态统计
|
||||
*/
|
||||
public int getActiveSessionCount() {
|
||||
return sessions.size();
|
||||
return connected ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 健康检查
|
||||
*/
|
||||
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);
|
||||
health.put("sessionId", currentSessionId);
|
||||
health.put("activeConnections", getActiveSessionCount());
|
||||
|
||||
if (connected) {
|
||||
health.put("message", "ADXP数据中台连接正常");
|
||||
} else {
|
||||
health.put("message", "ADXP数据中台连接异常");
|
||||
}
|
||||
|
||||
return health;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,11 +3,15 @@
|
||||
## 一、打包环境操作
|
||||
|
||||
### 1. 生成程序更新文件
|
||||
|
||||
修改代码后,生成仅包含应用jar的更新包:
|
||||
|
||||
```bash
|
||||
./deploy/package-update.sh
|
||||
```
|
||||
|
||||
**输出**:
|
||||
|
||||
- `qaup-admin.jar` - 应用程序
|
||||
- `UPDATE-INSTRUCTIONS.md` - 更新说明
|
||||
- `VERSION-INFO.txt` - 版本信息
|
||||
@ -15,7 +19,9 @@
|
||||
**注意**:如需单独jar文件,可直接从更新包中提取。
|
||||
|
||||
### 2. 生成完整部署包
|
||||
|
||||
生成包含所有组件的完整部署包(首次部署或重大更新):
|
||||
|
||||
```bash
|
||||
./deploy/package-all.sh
|
||||
```
|
||||
@ -27,19 +33,25 @@
|
||||
### 1. 首次部署
|
||||
|
||||
#### 步骤
|
||||
|
||||
1. **解压部署包**:
|
||||
|
||||
```bash
|
||||
mkdir qaup-deploy
|
||||
tar -xzf qaup-deploy-xxx.tar.gz -C qaup-deploy
|
||||
cd 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 # 验证应用健康
|
||||
@ -48,6 +60,7 @@ cd qaup-deploy
|
||||
### 2. 程序更新(仅更新jar文件)
|
||||
|
||||
#### 前置准备
|
||||
|
||||
```bash
|
||||
cd qaup-deploy
|
||||
|
||||
@ -60,6 +73,7 @@ cp config.yml config.yml.backup
|
||||
```
|
||||
|
||||
#### 更新步骤
|
||||
|
||||
```bash
|
||||
# 1. 准备新版本文件(二选一)
|
||||
# 方案A:使用完整更新包
|
||||
@ -78,7 +92,9 @@ cp /path/to/qaup-admin-xxx.jar new-app.jar
|
||||
```
|
||||
|
||||
### 3. 配置文件更新
|
||||
|
||||
如果需要更新配置文件:
|
||||
|
||||
```bash
|
||||
cd qaup-deploy
|
||||
|
||||
@ -95,9 +111,11 @@ vi config.yml
|
||||
### 4. 更新失败处理
|
||||
|
||||
#### 自动回滚
|
||||
|
||||
更新失败时脚本会自动尝试回滚。
|
||||
|
||||
#### 手动回滚
|
||||
|
||||
```bash
|
||||
cd qaup-deploy
|
||||
|
||||
@ -112,6 +130,7 @@ cp app.jar.backup.* app.jar
|
||||
```
|
||||
|
||||
#### 问题排查
|
||||
|
||||
```bash
|
||||
# 查看应用日志
|
||||
docker compose logs qaup-app
|
||||
@ -121,9 +140,11 @@ docker compose ps -a
|
||||
```
|
||||
|
||||
### 5. 完全重新部署
|
||||
|
||||
如果需要重新部署整个系统(包括更新镜像、配置):
|
||||
|
||||
#### 方案A:不保留数据
|
||||
|
||||
```bash
|
||||
cd qaup-deploy
|
||||
|
||||
@ -136,6 +157,7 @@ rm -rf data/ # 注意:会删除所有数据
|
||||
```
|
||||
|
||||
#### 方案B:保留数据
|
||||
|
||||
```bash
|
||||
# 1. 备份旧数据
|
||||
cd qaup-deploy
|
||||
@ -157,21 +179,34 @@ cd qaup-deploy-new
|
||||
## 三、常见问题
|
||||
|
||||
### Q: 更新后无法访问系统?
|
||||
|
||||
A: 检查端口和应用日志:
|
||||
|
||||
```bash
|
||||
docker compose logs qaup-app
|
||||
netstat -tlnp | grep 8080
|
||||
```
|
||||
|
||||
### Q: 端口冲突?
|
||||
|
||||
A: 检查端口占用:
|
||||
|
||||
```bash
|
||||
netstat -tlnp | grep 8080
|
||||
```
|
||||
|
||||
### Q: 数据库连接失败?
|
||||
|
||||
A: 检查数据库服务状态:
|
||||
|
||||
```bash
|
||||
docker compose logs qaup-postgres
|
||||
docker exec qaup-postgres pg_isready -U qaup
|
||||
```
|
||||
|
||||
## 四、更新记录
|
||||
|
||||
建议记录每次更新信息:
|
||||
|
||||
```
|
||||
时间 | 版本号 | 人员 | 类型 | 结果 | 问题
|
||||
2025-01-05 | v1.0.1→v1.0.2 | 张三 | 程序更新 | 成功 | -
|
||||
@ -181,14 +216,17 @@ docker exec qaup-postgres pg_isready -U qaup
|
||||
## 五、数据库部署和更新策略
|
||||
|
||||
### 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 # 基线迁移(支持已有数据库)
|
||||
@ -200,6 +238,7 @@ SPRING_FLYWAY_LOCATIONS: classpath:db/migration # 迁移脚本路径
|
||||
### 2. 数据库部署流程
|
||||
|
||||
#### 首次部署(全新环境)
|
||||
|
||||
```bash
|
||||
# 1. 启动基础服务(PostgreSQL + Redis)
|
||||
docker compose up -d qaup-postgres qaup-redis
|
||||
@ -217,6 +256,7 @@ curl http://localhost:8080/actuator/health
|
||||
```
|
||||
|
||||
#### 已有环境更新
|
||||
|
||||
```bash
|
||||
# Flyway自动处理数据库迁移
|
||||
# 应用启动时会自动检测并执行需要的迁移
|
||||
@ -229,12 +269,14 @@ docker exec qaup-postgres psql -U qaup -d qaup -c "SELECT * FROM flyway_schema_h
|
||||
### 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. **函数/存储过程迁移** - 业务逻辑更新
|
||||
@ -242,6 +284,7 @@ docker exec qaup-postgres psql -U qaup -d qaup -c "SELECT * FROM flyway_schema_h
|
||||
### 4. 数据库维护操作
|
||||
|
||||
#### 数据备份和恢复(仅限紧急情况)
|
||||
|
||||
```bash
|
||||
# 备份数据
|
||||
docker exec qaup-postgres pg_dump -U qaup qaup > backup-$(date +%Y%m%d_%H%M%S).sql
|
||||
@ -251,6 +294,7 @@ cat backup-20250115_143000.sql | docker exec -i qaup-postgres psql -U qaup -d qa
|
||||
```
|
||||
|
||||
#### 数据库状态监控
|
||||
|
||||
```bash
|
||||
# 查看Flyway迁移状态(应用启动日志)
|
||||
docker compose logs qaup-app | grep -i flyway
|
||||
@ -268,15 +312,18 @@ curl http://localhost:8080/actuator/health
|
||||
### 5. 数据库更新最佳实践
|
||||
|
||||
#### 开发环境
|
||||
|
||||
- 可以使用`flyway.clean()`清理数据库
|
||||
- 可以使用`flyway.migrate().clean()`重建
|
||||
|
||||
#### 生产环境
|
||||
|
||||
- 禁止使用`flyway.clean()`
|
||||
- 优先使用增量迁移脚本
|
||||
- 重大变更需要测试环境验证
|
||||
|
||||
#### 回滚策略
|
||||
|
||||
1. **自动化回滚** - 备份恢复
|
||||
2. **脚本化回滚** - 创建回滚脚本
|
||||
3. **版本控制** - 通过Flyway版本管理
|
||||
@ -284,15 +331,18 @@ curl http://localhost:8080/actuator/health
|
||||
## 六、文件说明
|
||||
|
||||
### 打包环境脚本
|
||||
|
||||
- `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` - 合并迁移脚本(历史用途)
|
||||
- `sql/unified_database_migration.sql` - 合并迁移脚本(历史用途)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
# QAUP 极简离线部署方案
|
||||
|
||||
Spring Boot + Redis + PostgreSQL with PostGIS(一键部署、一键升级)
|
||||
|
||||
## 1. 设计原则
|
||||
@ -12,6 +13,7 @@ Spring Boot + Redis + PostgreSQL with PostGIS(一键部署、一键升级)
|
||||
## 2. 打包准备(开发环境执行一次)
|
||||
|
||||
### 2.1 打包脚本(package-all.sh)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
echo "=== QAUP 打包脚本 ==="
|
||||
@ -42,6 +44,7 @@ echo "打包完成: qaup-deploy-$(date +%Y%m%d-%H%M%S).tar.gz"
|
||||
```
|
||||
|
||||
### 2.2 部署包结构
|
||||
|
||||
```
|
||||
qaup-deploy.tar.gz
|
||||
├── images.tar.gz # Docker镜像包(PostGIS + Redis + Java21)
|
||||
@ -55,6 +58,7 @@ qaup-deploy.tar.gz
|
||||
```
|
||||
|
||||
## 3. docker-compose.yml(当前实际版本)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
# PostgreSQL + PostGIS 数据库服务
|
||||
@ -143,6 +147,7 @@ networks:
|
||||
```
|
||||
|
||||
## 4. 外部配置文件(config.yml)
|
||||
|
||||
```yaml
|
||||
# 服务器配置
|
||||
server:
|
||||
@ -172,6 +177,7 @@ logging:
|
||||
## 5. 自动化数据库迁移(Flyway)
|
||||
|
||||
### 5.1 迁移脚本位置
|
||||
|
||||
```
|
||||
qaup-admin/src/main/resources/db/migration/
|
||||
├── V1.0.0__Initial_baseline.sql # 数据库基线结构(86KB)
|
||||
@ -180,12 +186,14 @@ qaup-admin/src/main/resources/db/migration/
|
||||
```
|
||||
|
||||
### 5.2 自动迁移机制
|
||||
|
||||
- **应用启动时自动执行**:无需手动运行SQL脚本
|
||||
- **版本控制**:所有迁移按版本顺序执行
|
||||
- **幂等性**:安全重复执行,不会重复创建对象
|
||||
- **健康检查**:依赖数据库健康状态启动应用
|
||||
|
||||
### 5.3 迁移状态查询
|
||||
|
||||
```bash
|
||||
# 查看应用日志中的迁移信息
|
||||
docker compose logs qaup-app | grep -i flyway
|
||||
@ -195,6 +203,7 @@ docker exec -it qaup-postgres psql -U qaup -d qaup -c "SELECT * FROM flyway_sche
|
||||
```
|
||||
|
||||
## 6. 一键部署(deploy-all.sh)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
echo "=== QAUP 一键部署 ==="
|
||||
@ -241,11 +250,13 @@ fi
|
||||
```
|
||||
|
||||
## 7. 客户部署(1条命令)
|
||||
|
||||
```bash
|
||||
tar -xzf qaup-deploy.tar.gz && cd qaup-deploy && ./deploy-all.sh
|
||||
```
|
||||
|
||||
## 8. 一键升级(deploy-update.sh)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
echo "=== QAUP 一键升级 ==="
|
||||
@ -311,11 +322,14 @@ fi
|
||||
## 9. Java版本兼容性注意事项
|
||||
|
||||
### 9.1 编译与运行环境匹配
|
||||
|
||||
- **编译环境**: 确保项目在Java 21环境下编译
|
||||
- **运行环境**: Docker镜像使用 `eclipse-temurin:21-jre`
|
||||
|
||||
### 9.2 常见版本错误
|
||||
|
||||
如果遇到 `UnsupportedClassVersionError`,确认以下配置:
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml 中必须使用Java 21镜像
|
||||
services:
|
||||
@ -325,6 +339,7 @@ services:
|
||||
```
|
||||
|
||||
### 9.3 版本验证命令
|
||||
|
||||
```bash
|
||||
# 检查容器中Java版本
|
||||
docker exec qaup-app java -version
|
||||
@ -339,6 +354,7 @@ curl http://localhost:8080/actuator/health
|
||||
## 10. 系统维护操作
|
||||
|
||||
### 10.1 日常运维命令
|
||||
|
||||
```bash
|
||||
# 查看服务状态
|
||||
docker compose ps
|
||||
@ -363,6 +379,7 @@ 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;"
|
||||
@ -375,6 +392,7 @@ docker exec -it qaup-postgres psql -U qaup -d qaup -c "SELECT version, descripti
|
||||
```
|
||||
|
||||
### 10.3 数据备份和恢复
|
||||
|
||||
```bash
|
||||
# 手动备份数据库
|
||||
docker exec qaup-postgres pg_dump -U qaup qaup > backup/manual-backup-$(date +%Y%m%d_%H%M%S).sql
|
||||
@ -386,6 +404,7 @@ docker compose start qaup-app
|
||||
```
|
||||
|
||||
### 10.4 完全重置(开发测试用)
|
||||
|
||||
```bash
|
||||
# 停止所有服务
|
||||
docker compose down
|
||||
@ -400,6 +419,7 @@ rm -rf data/
|
||||
## 11. 操作总结
|
||||
|
||||
### 开发环境(一次性)
|
||||
|
||||
```bash
|
||||
# 在macOS上构建
|
||||
mvn clean package -DskipTests
|
||||
@ -410,6 +430,7 @@ mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
### 客户环境
|
||||
|
||||
```bash
|
||||
# 首次部署
|
||||
tar -xzf qaup-deploy.tar.gz && cd qaup-deploy && ./deploy-all.sh
|
||||
|
||||
696
doc/deploy/deployment_guide.md
Normal file
696
doc/deploy/deployment_guide.md
Normal file
@ -0,0 +1,696 @@
|
||||
# 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
|
||||
131
qaup-admin/.env.example
Normal file
131
qaup-admin/.env.example
Normal file
@ -0,0 +1,131 @@
|
||||
# ============================================================
|
||||
# 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,6 +43,7 @@ public class QuapApplication
|
||||
* 配置Spring MVC使用虚拟线程处理请求
|
||||
* JDK21虚拟线程可以显著提高并发性能
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
|
||||
public AsyncTaskExecutor asyncTaskExecutor() {
|
||||
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
|
||||
|
||||
@ -83,9 +83,9 @@ data:
|
||||
# 适配器端口
|
||||
port: ${ADXP_PORT:8086}
|
||||
# 登录用户名
|
||||
username: ${ADXP_USERNAME}
|
||||
username: ${ADXP_USERNAME:dianxin}
|
||||
# 登录密码
|
||||
password: ${ADXP_PASSWORD}
|
||||
password: ${ADXP_PASSWORD:Dianxin#2025}
|
||||
# 重连延迟(毫秒)
|
||||
reconnect-delay-millis: ${RECONNECT_DELAY_MILLIS:3000}
|
||||
|
||||
|
||||
@ -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,91 +62,39 @@ public class AdxpFlightServiceHttpClient implements org.springframework.beans.fa
|
||||
}
|
||||
|
||||
this.baseUrl = String.format("http://%s:%d/api/adxp", properties.getHost(), properties.getPort());
|
||||
tryLogin();
|
||||
log.info("ADXP HTTP客户端已初始化,等待连接状态确认");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
sessionLock.lock();
|
||||
try {
|
||||
if (sessionId != null) {
|
||||
logout();
|
||||
}
|
||||
} finally {
|
||||
sessionId = null;
|
||||
sessionLock.unlock();
|
||||
}
|
||||
log.info("ADXP HTTP客户端已销毁");
|
||||
}
|
||||
|
||||
private void tryLogin() {
|
||||
sessionLock.lock();
|
||||
private void disconnect() {
|
||||
try {
|
||||
log.info("正在登录 ADXP 适配器服务: url={}", baseUrl);
|
||||
|
||||
// 登录适配器服务,适配器会使用这些认证信息连接真实数据中台
|
||||
Map<String, Object> loginRequest = new HashMap<>();
|
||||
loginRequest.put("username", properties.getUsername());
|
||||
loginRequest.put("password", properties.getPassword());
|
||||
log.info("正在断开 ADXP 适配器连接");
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(loginRequest, headers);
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(new HashMap<>(), headers);
|
||||
|
||||
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
|
||||
baseUrl + "/login",
|
||||
restTemplate.exchange(
|
||||
baseUrl + "/disconnect",
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
new ParameterizedTypeReference<Map<String, Object>>() {}
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
log.info("已断开 ADXP 适配器连接");
|
||||
} catch (Exception 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);
|
||||
log.warn("断开 ADXP 适配器连接失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<FlightNotificationDTO> fetchFlightNotifications() {
|
||||
if (sessionId == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
sessionLock.lock();
|
||||
try {
|
||||
String url = baseUrl + "/messages?sessionId=" + sessionId;
|
||||
String url = baseUrl + "/messages";
|
||||
ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
|
||||
url,
|
||||
HttpMethod.GET,
|
||||
@ -158,11 +106,6 @@ 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();
|
||||
}
|
||||
|
||||
@ -313,6 +256,60 @@ public class AdxpFlightServiceHttpClient implements org.springframework.beans.fa
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return sessionId != null;
|
||||
// 检查适配器是否连接到了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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +35,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,7 +64,7 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
||||
public void stop() {
|
||||
if (isRunning.compareAndSet(true, false)) {
|
||||
log.info("停止ADXP航班通知WebSocket客户端");
|
||||
disconnect();
|
||||
disconnectWebSocket();
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,15 +79,7 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
||||
}
|
||||
|
||||
try {
|
||||
// 首先通过HTTP登录获取sessionId
|
||||
sessionId = loginAndGetSessionId();
|
||||
if (sessionId == null) {
|
||||
log.error("无法获取sessionId,WebSocket连接失败");
|
||||
scheduleReconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建WebSocket URL
|
||||
// 构建WebSocket URL - 新架构下不需要sessionId
|
||||
String wsUrl = String.format("ws://%s:%d/ws/flight-notifications",
|
||||
properties.getHost(), properties.getPort());
|
||||
|
||||
@ -111,68 +103,20 @@ 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 disconnect() {
|
||||
public void disconnectWebSocket() {
|
||||
try {
|
||||
if (session != null && session.isOpen()) {
|
||||
session.close();
|
||||
log.info("WebSocket连接已关闭");
|
||||
}
|
||||
|
||||
// 尝试登出
|
||||
logout();
|
||||
// 断开ADXP适配器连接
|
||||
disconnectADXP();
|
||||
} catch (Exception e) {
|
||||
log.error("断开WebSocket连接时发生异常", e);
|
||||
errorCount.incrementAndGet();
|
||||
@ -183,36 +127,27 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出
|
||||
* 断开ADXP适配器连接
|
||||
*/
|
||||
private void logout() {
|
||||
if (sessionId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
private void disconnectADXP() {
|
||||
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 logoutUrl = baseUrl + "/logout";
|
||||
String disconnectUrl = baseUrl + "/disconnect";
|
||||
|
||||
String requestBody = objectMapper.writeValueAsString(logoutRequest);
|
||||
java.net.http.HttpRequest request = java.net.http.HttpRequest.newBuilder()
|
||||
.uri(URI.create(logoutUrl))
|
||||
.uri(URI.create(disconnectUrl))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(java.net.http.HttpRequest.BodyPublishers.ofString(requestBody))
|
||||
.POST(java.net.http.HttpRequest.BodyPublishers.ofString("{}"))
|
||||
.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();
|
||||
}
|
||||
}
|
||||
@ -451,6 +386,14 @@ public class AdxpFlightServiceWebSocketClient implements WebSocketHandler {
|
||||
public boolean isConnected() {
|
||||
return isConnected.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否启用
|
||||
* 主动连接架构下,返回连接状态
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排重新连接
|
||||
|
||||
Loading…
Reference in New Issue
Block a user