diff --git a/README.md b/README.md index 498ac0ac..dccbf728 100644 --- a/README.md +++ b/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. 系统访问 + +- **管理后台**: +- **初始登录**: + - 用户名: `admin` + - 密码: `admin123` +- **WebSocket端点**: ws://localhost:8080/ws +- **API文档**: +- **健康检查**: + +### 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` \ No newline at end of file +- 更新日志: 详见 `change_log.md` diff --git a/adxp-adapter/.env.example b/adxp-adapter/.env.example index 434ef758..2098990a 100644 --- a/adxp-adapter/.env.example +++ b/adxp-adapter/.env.example @@ -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适配器采用主动连接架构,启动时自动连接数据中台 # ============================================================ \ No newline at end of file diff --git a/adxp-adapter/README.md b/adxp-adapter/README.md index eca9101b..48d1f305 100644 --- a/adxp-adapter/README.md +++ b/adxp-adapter/README.md @@ -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" ``` ## 配置说明 diff --git a/adxp-adapter/docker-compose.yml b/adxp-adapter/docker-compose.yml index 2e9db42f..283a11ab 100644 --- a/adxp-adapter/docker-compose.yml +++ b/adxp-adapter/docker-compose.yml @@ -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: diff --git a/adxp-adapter/src/main/java/com/qaup/adxp/adapter/controller/AdxpController.java b/adxp-adapter/src/main/java/com/qaup/adxp/adapter/controller/AdxpController.java index bb1d77e8..65067f92 100644 --- a/adxp-adapter/src/main/java/com/qaup/adxp/adapter/controller/AdxpController.java +++ b/adxp-adapter/src/main/java/com/qaup/adxp/adapter/controller/AdxpController.java @@ -26,51 +26,82 @@ public class AdxpController { private AdxpWebSocketHandler adxpWebSocketHandler; /** - * 登录接口 + * 获取连接状态 */ - @PostMapping(value = "/login", produces = "application/json") - public ResponseEntity login(@RequestBody LoginRequest request) { + @GetMapping("/status") + public ResponseEntity> getStatus() { try { - String sessionId = adxpSdkService.login(request.getUsername(), request.getPassword()); - return ResponseEntity.ok(LoginResponse.success(sessionId)); + Map 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 error = new HashMap<>(); + error.put("connected", false); + error.put("error", e.getMessage()); + return ResponseEntity.ok(error); + } + } + + /** + * 强制重连 + */ + @PostMapping("/reconnect") + public ResponseEntity> reconnect() { + try { + adxpSdkService.forceReconnect(); + Map 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 error = new HashMap<>(); + error.put("success", false); + error.put("message", "重连失败: " + e.getMessage()); + error.put("connected", false); + return ResponseEntity.ok(error); } } /** - * 接收消息接口 + * 获取消息接口 - 使用当前连接 */ @GetMapping("/messages") - public ResponseEntity getMessages(@RequestParam String sessionId) { + public ResponseEntity getMessages() { try { - List messages = adxpSdkService.receiveMessages(sessionId); + if (!adxpSdkService.isConnected()) { + return ResponseEntity.ok(MessageResponse.failure("未连接到ADXP数据中台")); + } + List 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> logout(@RequestBody Map request) { + @PostMapping("/disconnect") + public ResponseEntity> disconnect() { try { - String sessionId = request.get("sessionId"); - adxpSdkService.logout(sessionId); - - Map response = new HashMap(); + adxpSdkService.disconnectFromADXPServer(); + Map 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 response = new HashMap(); + log.error("断开连接失败", e); + Map 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> health() { - Map health = new HashMap(); - health.put("status", "UP"); - health.put("activeSessions", adxpSdkService.getActiveSessionCount()); - if (adxpWebSocketHandler != null) { - health.put("websocketConnections", adxpWebSocketHandler.getSessionCount()); + try { + Map health = adxpSdkService.healthCheck(); + if (adxpWebSocketHandler != null) { + health.put("websocketConnections", adxpWebSocketHandler.getSessionCount()); + } + return ResponseEntity.ok(health); + } catch (Exception e) { + log.error("健康检查失败", e); + Map 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); } } diff --git a/adxp-adapter/src/main/java/com/qaup/adxp/adapter/service/AdxpSdkService.java b/adxp-adapter/src/main/java/com/qaup/adxp/adapter/service/AdxpSdkService.java index cb6bcb56..3292f6a9 100644 --- a/adxp-adapter/src/main/java/com/qaup/adxp/adapter/service/AdxpSdkService.java +++ b/adxp-adapter/src/main/java/com/qaup/adxp/adapter/service/AdxpSdkService.java @@ -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 sessions = new ConcurrentHashMap<>(); - public Map 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 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 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 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 messages) { + // TODO: 实现消息处理逻辑 + // 可以发送到消息队列、WebSocket推送等 + for (FlightMessage message : messages) { + log.debug("处理消息: serviceCode={}", message.getServiceCode()); } } /** - * 获取当前会话数 + * 获取消息 - 供外部调用 + */ + public List 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 healthCheck() { + Map 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; } } diff --git a/deploy/DeployGuide.md b/deploy/DeployGuide.md index f4763d7a..caa4dad8 100644 --- a/deploy/DeployGuide.md +++ b/deploy/DeployGuide.md @@ -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` - 合并迁移脚本(历史用途) \ No newline at end of file +- `sql/unified_database_migration.sql` - 合并迁移脚本(历史用途) diff --git a/doc/deploy/deploy-design.md b/doc/deploy/deploy-design.md index 177688c0..d0cf2836 100644 --- a/doc/deploy/deploy-design.md +++ b/doc/deploy/deploy-design.md @@ -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 diff --git a/doc/deploy/deployment_guide.md b/doc/deploy/deployment_guide.md new file mode 100644 index 00000000..97bb5fdb --- /dev/null +++ b/doc/deploy/deployment_guide.md @@ -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 " + 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. 紧急联系信息 + +- **技术支持**: +- **运维团队**: +- **值班电话**: +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 diff --git a/qaup-admin/.env.example b/qaup-admin/.env.example new file mode 100644 index 00000000..e260e49e --- /dev/null +++ b/qaup-admin/.env.example @@ -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. 根据实际部署环境调整各项配置值 +# ============================================================ \ No newline at end of file diff --git a/qaup-admin/src/main/java/com/qaup/QuapApplication.java b/qaup-admin/src/main/java/com/qaup/QuapApplication.java index ba9cdbf5..21934c4a 100644 --- a/qaup-admin/src/main/java/com/qaup/QuapApplication.java +++ b/qaup-admin/src/main/java/com/qaup/QuapApplication.java @@ -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()); diff --git a/qaup-admin/src/main/resources/application-prod.yml b/qaup-admin/src/main/resources/application-prod.yml index d41dbcaf..6ddea35e 100644 --- a/qaup-admin/src/main/resources/application-prod.yml +++ b/qaup-admin/src/main/resources/application-prod.yml @@ -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} diff --git a/qaup-collision/src/main/java/com/qaup/collision/datacollector/sdk/AdxpFlightServiceHttpClient.java b/qaup-collision/src/main/java/com/qaup/collision/datacollector/sdk/AdxpFlightServiceHttpClient.java index f4994234..1a20e6cf 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/datacollector/sdk/AdxpFlightServiceHttpClient.java +++ b/qaup-collision/src/main/java/com/qaup/collision/datacollector/sdk/AdxpFlightServiceHttpClient.java @@ -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 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> entity = new HttpEntity<>(loginRequest, headers); + HttpEntity> entity = new HttpEntity<>(new HashMap<>(), headers); - ResponseEntity> response = restTemplate.exchange( - baseUrl + "/login", + restTemplate.exchange( + baseUrl + "/disconnect", HttpMethod.POST, entity, new ParameterizedTypeReference>() {} ); - Map 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 logoutRequest = new HashMap<>(); - logoutRequest.put("sessionId", sessionId); - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - HttpEntity> entity = new HttpEntity<>(logoutRequest, headers); - - restTemplate.exchange( - baseUrl + "/logout", - HttpMethod.POST, - entity, - new ParameterizedTypeReference>() {} - ); - - log.info("已登出 ADXP 适配器服务"); - } catch (Exception e) { - log.warn("登出 ADXP 适配器服务失败", e); + log.warn("断开 ADXP 适配器连接失败", e); } } public List fetchFlightNotifications() { - if (sessionId == null) { - return Collections.emptyList(); - } - sessionLock.lock(); try { - String url = baseUrl + "/messages?sessionId=" + sessionId; + String url = baseUrl + "/messages"; ResponseEntity> 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> response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {} + ); + + Map 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> response = restTemplate.exchange( + url, + HttpMethod.POST, + null, + new ParameterizedTypeReference>() {} + ); + + Map 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; + } } } diff --git a/qaup-collision/src/main/java/com/qaup/collision/datacollector/websocket/AdxpFlightServiceWebSocketClient.java b/qaup-collision/src/main/java/com/qaup/collision/datacollector/websocket/AdxpFlightServiceWebSocketClient.java index bf2d3338..a15784a0 100644 --- a/qaup-collision/src/main/java/com/qaup/collision/datacollector/websocket/AdxpFlightServiceWebSocketClient.java +++ b/qaup-collision/src/main/java/com/qaup/collision/datacollector/websocket/AdxpFlightServiceWebSocketClient.java @@ -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 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 response = httpClient.send(request, - java.net.http.HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() == 200) { - java.util.Map responseBody = objectMapper.readValue( - response.body(), new TypeReference>() {}); - - 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 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(); + } /** * 安排重新连接