Compare commits

..

No commits in common. "main" and "develop" have entirely different histories.

285 changed files with 25287 additions and 2124414 deletions

View File

@ -1,64 +0,0 @@
# ============================================================
# QAUP 生产环境变量配置模板
# 使用说明:
# 1. 复制此文件为 .env: cp .env.example .env
# 2. 修改 .env 中的配置值
# 3. 启动应用时会自动加载 .env 文件中的环境变量
# 4. 修改配置后执行 ./qaup.sh restart 使配置生效
# ============================================================
# ========== 数据库配置 ==========
DB_HOST=10.64.58.228
DB_PORT=5432
DB_NAME=qaup
DB_USERNAME=postgres
DB_PASSWORD=123456
# ========== Redis配置 ==========
REDIS_HOST=10.64.58.228
REDIS_PORT=6379
REDIS_DATABASE=0
REDIS_PASSWORD=
# ========== 服务器配置 ==========
SERVER_PORT=8080
# ========== 机场数据API配置 ==========
# 机场数据接口基础URL
AIRPORT_API_BASE_URL=http://10.64.58.228:8090
# 机场API认证信息
AIRPORT_API_USERNAME=dianxin
AIRPORT_API_PASSWORD=dianxin@123
# ========== 无人车厂商API配置 ==========
# 无人车厂商接口基础URL
VEHICLE_API_BASE_URL=http://10.64.58.228:8091
# 无人车API超时和重试配置可选单位毫秒
VEHICLE_API_TIMEOUT=1000
VEHICLE_API_RETRY=3
# ========== 数据采集配置(可选)==========
# 数据采集间隔(单位:毫秒)
DATA_COLLECTOR_INTERVAL=250
# 检测和推送间隔(单位:毫秒)
DATA_DETECTION_INTERVAL=1000
# ========== 红绿灯系统配置(可选)==========
TRAFFIC_LIGHT_TCP_ENABLED=true
TRAFFIC_LIGHT_TCP_PORT=8082
# ========== 日志配置(可选)==========
LOG_LEVEL_QAUP=info
LOG_LEVEL_SPRING=warn
# ============================================================
# 注意事项:
# 1. 等号两边不要有空格
# 2. 字符串值不需要引号
# 3. #开头的行为注释
# 4. 请勿将 .env 文件提交到Git仓库
# 5. 修改后执行 ./qaup.sh restart 使配置生效
# ============================================================

48
.gitignore vendored
View File

@ -42,8 +42,6 @@ logs/
*.log
*.xml.versionsBackup
*.swp
*.tar
*.tar.gz
!*/build/*.java
!*/build/*.html
@ -51,48 +49,4 @@ logs/
.DS_Store
.vscode/
Users/
qaup-deploy/
deploy/offline_packages/
######################################################################
# Python
*.py[cod]
*$py.class
__pycache__/
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
pip-log.txt
pip-delete-this-directory.txt
.venv/
venv/
ENV/
env/
*.log
######################################################################
# Node.js
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.npm
.yarn-integrity
dist/
.cache/
Users/

View File

@ -0,0 +1,260 @@
# 设计文档
## 概述
本设计文档描述了 QAUP 项目的 Docker 容器化部署架构。该方案将整个项目打包为多个 Docker 容器,包括后端应用、前端应用、数据库和缓存服务,适用于在客户的 Ubuntu 系统上进行生产环境部署。
## 架构
### 整体架构图
```mermaid
graph TB
subgraph "Docker Host (Ubuntu)"
subgraph "qaup-network"
nginx[Nginx容器<br/>端口: 80, 443]
app[QAUP应用容器<br/>端口: 8080]
postgres[(PostgreSQL容器<br/>端口: 5432)]
redis[(Redis容器<br/>端口: 6379)]
end
subgraph "Docker Volumes"
db_data[数据库数据卷]
app_logs[应用日志卷]
upload_files[文件上传卷]
nginx_conf[Nginx配置卷]
end
end
Client[客户端浏览器] --> nginx
nginx --> app
app --> postgres
app --> redis
postgres -.-> db_data
app -.-> app_logs
app -.-> upload_files
nginx -.-> nginx_conf
```
### 容器组件
1. **Nginx 容器**: 反向代理和静态文件服务
2. **QAUP 应用容器**: Spring Boot 应用(包含所有模块)
3. **PostgreSQL 容器**: 数据库服务(包含 PostGIS 扩展)
4. **Redis 容器**: 缓存和会话存储
## 组件和接口
### 1. QAUP 应用容器
**基础镜像**: `openjdk:17-jre-slim`
**功能**:
- 运行 qaup-admin.jar包含所有模块
- 提供 REST API 服务
- 处理定时任务
- 管理文件上传
**配置**:
- JVM 参数优化
- 环境变量配置
- 健康检查端点
### 2. Nginx 容器
**基础镜像**: `nginx:alpine`
**功能**:
- 反向代理到后端应用
- 服务前端静态文件
- SSL 终止(可选)
- 负载均衡(单实例时作为代理)
**配置**:
- 自定义 nginx.conf
- 前端构建文件挂载
- 日志配置
### 3. PostgreSQL 容器
**基础镜像**: `postgis/postgis:15-3.3`
**功能**:
- 数据持久化存储
- 空间数据支持PostGIS
- 数据库初始化脚本
**配置**:
- 数据库初始化
- 性能参数调优
- 数据卷挂载
### 4. Redis 容器
**基础镜像**: `redis:7-alpine`
**功能**:
- 缓存存储
- 会话管理
- 数据持久化
**配置**:
- Redis 配置文件
- 持久化策略
- 内存限制
## 数据模型
### Docker Compose 配置结构
```yaml
version: '3.8'
services:
qaup-app:
# 应用服务配置
qaup-nginx:
# Nginx 服务配置
qaup-postgres:
# PostgreSQL 服务配置
qaup-redis:
# Redis 服务配置
volumes:
# 数据卷定义
networks:
# 网络定义
```
### 环境变量配置
**应用配置**:
- `SPRING_PROFILES_ACTIVE`: 激活的配置文件
- `DB_HOST`, `DB_PORT`, `DB_NAME`: 数据库连接
- `REDIS_HOST`, `REDIS_PORT`: Redis 连接
- `UPLOAD_PATH`: 文件上传路径
**数据库配置**:
- `POSTGRES_DB`: 数据库名称
- `POSTGRES_USER`: 数据库用户
- `POSTGRES_PASSWORD`: 数据库密码
## 错误处理
### 容器故障处理
1. **自动重启策略**
- 所有服务配置 `restart: unless-stopped`
- 健康检查失败时自动重启
2. **数据恢复**
- 数据库数据通过卷持久化
- 应用日志和上传文件持久化
3. **网络故障处理**
- 容器间通信通过内部网络
- 外部访问通过 Nginx 代理
### 监控和告警
1. **健康检查**
- 应用: `/actuator/health`
- 数据库: `pg_isready`
- Redis: `redis-cli ping`
2. **日志管理**
- 应用日志通过卷挂载
- 容器日志通过 Docker 日志驱动
- 日志轮转配置
## 测试策略
### 部署测试
1. **容器启动测试**
- 验证所有容器正常启动
- 检查容器间网络连通性
2. **功能测试**
- API 接口可访问性
- 前端页面加载
- 数据库连接正常
3. **持久化测试**
- 容器重启后数据保持
- 文件上传功能正常
### 性能测试
1. **资源使用测试**
- 内存使用监控
- CPU 使用监控
- 磁盘 I/O 监控
2. **并发测试**
- 多用户同时访问
- 数据库连接池测试
## 安全考虑
### 网络安全
1. **网络隔离**
- 使用自定义 Docker 网络
- 仅暴露必要端口
2. **访问控制**
- 数据库仅允许应用容器访问
- Redis 仅允许内部网络访问
### 数据安全
1. **敏感信息管理**
- 使用环境变量或 Docker secrets
- 数据库密码加密存储
2. **文件权限**
- 容器内使用非 root 用户
- 挂载卷设置适当权限
## 部署流程
### 离线部署准备阶段(在有网络环境中进行)
1. **Docker 镜像准备**
- 构建所有自定义镜像
- 拉取所有基础镜像
- 导出镜像为 tar 文件
2. **应用构建**
- Maven 构建 JAR 包
- 创建应用 Docker 镜像
3. **前端构建**
- npm 构建前端资源
- 准备 Nginx 静态文件
4. **部署包打包**
- 打包所有 Docker 镜像文件
- 打包配置文件和脚本
- 创建离线安装包
### 现场部署阶段(在客户内网环境中进行)
1. **环境准备**
- 安装 Docker 和 Docker Compose
- 创建必要目录和权限
2. **镜像导入**
- 导入所有 Docker 镜像
- 验证镜像完整性
3. **服务启动**
- 启动数据库和缓存服务
- 初始化数据库架构
- 启动应用和 Nginx 服务
4. **验证部署**
- 健康检查验证
- 功能测试验证

View File

@ -0,0 +1,99 @@
# 需求文档
## 介绍
本文档概述了在客户 Ubuntu 系统上使用 Docker 部署当前 QAUP 项目所有内容的需求。部署范围包括项目中的所有模块qaup-admin主应用、qaup-framework核心框架、qaup-system系统管理、qaup-quartz定时任务、qaup-generator代码生成、qaup-common通用工具、qaup-collision碰撞避免、qaup-ui前端界面以及相关的数据库和缓存服务。这是面向生产环境的长期运行部署需要确保稳定性、可维护性和数据持久化而不是临时测试环境。
## 需求
### 需求 1
**用户故事:** 作为系统管理员,我希望使用 Docker 容器部署整个 QAUP 项目,以便在不同的 Ubuntu 环境中实现一致、可移植且易于管理的部署。
#### 验收标准
1. 当执行 Docker 部署时系统应创建后端应用、前端应用、PostgreSQL 数据库和 Redis 缓存的容器
2. 当容器启动时,它们应能够通过 Docker 网络相互通信
3. 当部署完成时,前端应用和后端 API 应可在配置的端口上访问
4. 当容器重启时,数据应通过 Docker 卷持久化保存
5. 当系统长期运行时,容器应配置为自动重启和故障恢复
### 需求 2
**用户故事:** 作为数据库管理员,我希望 PostgreSQL 在 Docker 容器中运行并具有适当的数据持久化,以便数据库数据在容器重启和更新时得到保留。
#### 验收标准
1. 当 PostgreSQL 容器启动时,应自动使用所需的数据库架构进行初始化
2. 当容器停止并重启时,所有数据应得到保留
3. 当数据库初始化运行时,应创建所有必需的表,包括空间扩展
4. 当需要备份时,数据库数据应通过 Docker 卷轻松访问
5. 当系统长期运行时,数据库应配置适当的资源限制和性能优化
### 需求 3
**用户故事:** 作为 DevOps 工程师,我希望有特定环境的配置管理,以便相同的 Docker 镜像可以部署到不同的环境(开发、测试、生产)。
#### 验收标准
1. 当部署到不同环境时,配置应通过环境变量外部化
2. 当配置敏感数据时,应通过 Docker secrets 或环境文件管理
3. 当配置更改时,容器应能够在不重建镜像的情况下重新加载
4. 当部署时,数据库连接参数应可按环境配置
### 需求 4
**用户故事:** 作为系统操作员,我希望有全面的日志记录和监控,以便在生产环境中排除故障和监控系统健康状况。
#### 验收标准
1. 当容器运行时,日志应通过 Docker 日志驱动程序访问
2. 当系统部署时,应为所有服务配置健康检查
3. 当需要监控时,应为外部监控系统公开指标
4. 当需要日志轮转时,应配置以防止磁盘空间问题
### 需求 5
**用户故事:** 作为部署工程师,我希望有自动化部署脚本和文档,以便客户部署保持一致,并且可以由技术人员在最少培训的情况下执行。
#### 验收标准
1. 当启动部署时,单个命令应部署整个系统
2. 当提供部署文档时,应包括 Ubuntu 系统的分步说明
3. 当需要更新时,部署应支持最小停机时间的滚动更新
4. 当需要故障排除时,应提供诊断脚本
### 需求 6
**用户故事:** 作为安全管理员,我希望 Docker 部署遵循安全最佳实践,以便系统在客户环境中是安全的。
#### 验收标准
1. 当创建容器时,应尽可能使用非 root 用户运行
2. 当发生网络通信时,应仅限制为必要的端口
3. 当挂载敏感文件时,应具有适当的权限
4. 当系统部署时,应禁用或删除不必要的服务
#
## 需求 7
**用户故事:** 作为运维人员,我希望 Docker 部署配置适合生产环境长期运行,以便系统能够稳定可靠地为客户提供服务。
#### 验收标准
1. 当容器配置时,应设置适当的重启策略(如 restart: unless-stopped
2. 当系统运行时,应配置资源限制防止单个容器消耗过多系统资源
3. 当服务异常时,容器应能自动重启并恢复服务
4. 当系统升级时,应支持零停机或最小停机时间的更新策略
5. 当长期运行时,日志应配置轮转以防止磁盘空间耗尽
#
## 需求 8
**用户故事:** 作为现场部署工程师,我希望能够在无互联网连接的内网环境中部署系统,以便在客户的隔离网络环境中完成部署。
#### 验收标准
1. 当客户服务器无法访问互联网时,部署应能够完全离线进行
2. 当准备部署包时,应包含所有必需的 Docker 镜像文件
3. 当进行离线部署时,不应依赖任何外部网络资源下载
4. 当系统运行时,应能够在完全隔离的网络环境中正常工作
5. 当需要更新时,应提供离线更新包和更新流程

View File

@ -0,0 +1,85 @@
# 实施计划
- [x] 1. 创建 Docker 构建配置
- 为 QAUP 应用创建 Dockerfile
- 配置 Maven 构建优化以支持 Docker 构建
- 创建 .dockerignore 文件排除不必要的文件
- _需求: 1.1, 1.2_
- [x] 1.1 创建离线部署镜像准备脚本
- 编写脚本拉取所有必需的基础镜像
- 创建镜像导出和打包脚本
- 配置镜像版本锁定以确保一致性
- _需求: 8.2, 8.3_
- [x] 2. 创建前端构建和 Nginx 配置
- 创建前端构建脚本
- 配置 Nginx Dockerfile 和配置文件
- 设置静态文件服务和反向代理规则
- _需求: 1.3, 3.1_
- [x] 3. 配置数据库初始化
- 创建 PostgreSQL 初始化脚本
- 配置 PostGIS 扩展安装
- 设置数据库架构导入脚本
- _需求: 2.1, 2.3_
- [x] 4. 创建 Docker Compose 配置
- 编写 docker-compose.yml 主配置文件
- 配置服务间网络和依赖关系
- 设置数据卷和持久化存储
- _需求: 1.1, 1.4, 2.2_
- [x] 5. 配置环境变量和配置管理
- 创建环境变量模板文件
- 配置生产环境参数
- 设置敏感信息管理方案
- _需求: 3.1, 3.2, 6.2_
- [x] 6. 实现健康检查和监控
- 为每个服务配置健康检查
- 设置容器重启策略
- 配置日志管理和轮转
- _需求: 4.2, 4.4, 7.1_
- [x] 7. 创建部署脚本和文档
- 编写自动化部署脚本
- 创建 Ubuntu 系统部署指南
- 编写故障排除和维护文档
- _需求: 5.1, 5.2, 5.4_
- [x] 7.1 创建离线部署包和脚本
- 编写离线部署包制作脚本
- 创建镜像导入和验证脚本
- 编写内网环境部署指南
- _需求: 8.1, 8.4, 8.5_
- [x] 8. 配置生产环境优化
- 设置容器资源限制
- 配置 JVM 参数优化
- 实现零停机更新策略
- _需求: 7.2, 7.4, 7.5_
- [x] 9. 实现安全配置
- 配置容器非 root 用户运行
- 设置网络访问控制
- 配置文件权限和安全策略
- _需求: 6.1, 6.3, 6.4_
- [x] 10. 创建备份和恢复方案
- 实现数据库备份脚本
- 创建系统恢复文档
- 测试备份恢复流程
- _需求: 5.3, 2.4_
- [x] 11. 进行集成测试
- 测试完整部署流程
- 验证所有服务功能正常
- 进行性能和稳定性测试
- _需求: 1.3, 4.1, 7.3_
- [x] 12. 创建运维工具和脚本
- 编写系统状态检查脚本
- 创建日志查看和分析工具
- 实现服务管理命令脚本
- _需求: 4.3, 5.4_

View File

@ -0,0 +1,142 @@
# 机场车辆位置过滤功能设计文档
## 概述
本设计文档描述了如何在现有的机场车辆位置数据处理流程中添加过滤功能,通过系统配置控制是否转发未注册车辆的位置信息。
## 架构
### 整体架构
```
机场API -> DataCollectorService -> 车辆过滤器 -> 缓存/前端转发
sys_vehicle_info表查询
系统配置(ISysConfigService)
```
### 核心组件
1. **VehicleLocationFilter**: 新增的车辆位置过滤器
2. **DataCollectorService**: 修改现有的数据采集服务,集成过滤逻辑
3. **ISysConfigService**: 利用现有的系统配置服务
4. **ISysVehicleInfoService**: 利用现有的车辆信息服务
## 组件和接口
### 1. VehicleLocationFilter (新增)
```java
@Component
public class VehicleLocationFilter {
private static final String CONFIG_KEY = "unmanaged.vehicle.filter.enabled";
@Autowired
private ISysConfigService configService;
@Autowired
private ISysVehicleInfoService vehicleInfoService;
/**
* 过滤机场车辆位置数据
* @param vehicles 原始车辆列表
* @return 过滤后的车辆列表
*/
public List<AirportVehicle> filterVehicles(List<AirportVehicle> vehicles);
/**
* 检查过滤功能是否启用
* @return true表示启用过滤
*/
private boolean isFilterEnabled();
/**
* 批量检查车辆是否在数据库中存在
* @param plateNumbers 车牌号列表
* @return 存在的车牌号集合
*/
private Set<String> getExistingVehiclePlates(List<String> plateNumbers);
}
```
### 2. DataCollectorService (修改)
在现有的 `collectVehicleData()` 方法中集成过滤逻辑:
```java
@Scheduled(fixedRateString = "${data.collector.interval}")
@Async
public void collectVehicleData() {
// ... 现有代码 ...
List<AirportVehicle> vehicles = dataCollectorDao.collectVehicleData(airportVehicleEndpoint, airportBaseUrl);
// 新增:应用过滤器
List<AirportVehicle> filteredVehicles = vehicleLocationFilter.filterVehicles(vehicles);
// 继续处理过滤后的车辆数据
for (AirportVehicle vehicle : filteredVehicles) {
// ... 现有处理逻辑 ...
}
}
```
### 3. 系统配置
`sys_config` 表中添加配置项:
- config_key: `unmanaged.vehicle.filter.enabled`
- config_value: `false` (默认值,不过滤)
- config_name: `未管理车辆位置过滤开关`
- config_type: `Y` (系统内置)
## 数据模型
### 配置项数据结构
```sql
INSERT INTO sys_config (config_name, config_key, config_value, config_type, remark)
VALUES ('未管理车辆位置过滤开关', 'unmanaged.vehicle.filter.enabled', 'false', 'Y', '控制是否过滤未在sys_vehicle_info表中注册的车辆位置信息');
```
### 车辆信息查询
利用现有的 `sys_vehicle_info` 表结构,通过 `license_plate` 字段进行匹配。
## 错误处理
### 异常处理策略
1. **配置读取失败**: 默认不过滤,记录警告日志
2. **数据库查询失败**: 默认不过滤,记录错误日志
3. **车辆数据异常**: 跳过异常数据,继续处理其他车辆
### 日志记录
- DEBUG级别: 记录过滤统计信息
- WARN级别: 记录配置读取失败
- ERROR级别: 记录数据库查询异常
## 测试策略
### 单元测试
1. **VehicleLocationFilter测试**
- 测试过滤功能启用/禁用
- 测试车辆存在性检查
- 测试异常处理
2. **DataCollectorService集成测试**
- 测试过滤器集成
- 测试数据流完整性
### 集成测试
1. 测试配置修改后的实时生效
2. 测试大量车辆数据的过滤性能
3. 测试数据库连接异常时的降级处理
## 性能考虑
### 优化策略
1. **批量查询**: 一次查询所有车牌号避免N+1问题
2. **配置缓存**: 利用现有的系统配置缓存机制
3. **快速失败**: 配置禁用时直接跳过所有过滤逻辑
### 性能指标
- 单批次车辆过滤处理时间 < 50ms (100辆车以内)
- 数据库查询次数: 每批次最多1次
- 内存占用: 忽略不计 (仅临时存储车牌号列表)

View File

@ -0,0 +1,35 @@
# 机场车辆位置过滤功能需求文档
## 介绍
为机场车辆位置 API 添加一个配置开关控制是否将不在数据库车辆信息表sys_vehicle_info中的车辆位置信息转发给前端。
## 需求
### 需求 1
**用户故事:** 作为系统管理员,我希望能够配置是否过滤未注册车辆的位置信息,以便控制前端显示的车辆数据量。
#### 验收标准
1. WHEN 系统配置中存在"unmanaged.vehicle.filter.enabled"配置项 THEN 系统 SHALL 根据该配置决定是否过滤车辆
2. WHEN 配置为"true" THEN 系统 SHALL 只转发在 sys_vehicle_info 表中存在的车辆位置信息
3. WHEN 配置为"false" THEN 系统 SHALL 转发所有接收到的机场车辆位置信息
### 需求 2
**用户故事:** 作为系统用户,我希望过滤功能不影响系统的实时性能。
#### 验收标准
1. WHEN 过滤功能启用 THEN 系统 SHALL 使用车牌号批量查询车辆信息以提高效率
2. WHEN 过滤功能禁用 THEN 系统 SHALL 跳过过滤逻辑,直接处理数据
### 需求 3
**用户故事:** 作为系统管理员,我希望能够通过现有的系统配置 API 查询和修改过滤配置。
#### 验收标准
1. WHEN 调用系统配置查询 API THEN 系统 SHALL 返回"unmanaged.vehicle.filter.enabled"配置项的值
2. WHEN 调用系统配置修改 API THEN 系统 SHALL 允许修改该配置项的值

View File

@ -0,0 +1,24 @@
# 实现计划
- [x] 1. 创建车辆位置过滤器组件
- 创建 VehicleLocationFilter 类,实现车辆过滤逻辑
- 添加配置读取和车辆存在性检查方法
- 实现批量车牌号查询优化
- _需求: 1.1, 1.2, 2.1_
- [x] 2. 修改数据采集服务集成过滤器
- 在 DataCollectorService 中注入 VehicleLocationFilter
- 修改 collectVehicleData 方法,在处理前应用过滤器
- 添加过滤统计日志记录
- _需求: 1.2, 1.3, 2.2_
- [x] 3. 添加系统配置项
- 在数据库中插入未管理车辆位置过滤开关配置项
- 验证配置项可以通过现有的系统配置 API 查询和修改
- _需求: 3.1, 3.2_
- [x] 4. 编写单元测试
- 为 VehicleLocationFilter 编写单元测试
- 测试过滤功能启用/禁用场景
- 测试异常处理和降级逻辑
- _需求: 1.1, 1.2, 1.3_

102
CLAUDE.md
View File

@ -26,10 +26,9 @@ The collision avoidance system integrates with RuoYi through `QuapDataAdapter` (
**DataCollectorService and DataProcessingService MUST be completely separated**
#### Service Responsibilities
- **DataCollectorService** (250ms):
- Only collects raw data from external APIs (position data, flight notifications)
- Caches position data in activeMovingObjectsCache with NULL speed/direction
- Caches flight notification data in flightNotificationCache
- **DataCollectorService** (250ms):
- Only collects raw position data from external APIs
- Caches data in activeMovingObjectsCache with NULL speed/direction
- NO calculations, NO processing, NO WebSocket sending
- Purpose: High-frequency data collection to ensure no data loss
@ -37,33 +36,26 @@ The collision avoidance system integrates with RuoYi through `QuapDataAdapter` (
- Reads cached position data from DataCollectorService
- Calculates speed and direction using SpeedCalculationService
- Sends WebSocket position updates
- Reads cached flight notification data from DataCollectorService
- Sends WebSocket flight notification updates
- Performs violation detection and path conflict detection
- Saves data to database
#### Implementation Pattern
1. **DataCollectorService Methods**:
- `collectAircraftData()`, `collectVehicleData()`, `collectUnmannedVehicleData()` - position data collection
- `collectFlightNotificationData()` - flight notification data collection
- `collectAircraftData()`, `collectVehicleData()`, `collectUnmannedVehicleData()`
- Store MovingObjects with NULL speed/direction in activeMovingObjectsCache
- Cache FlightNotifications in flightNotificationCache
- **FORBIDDEN**: No calculations, no WebSocket events, no processing
2. **DataProcessingService Methods**:
- `performPeriodicDataProcessing()` - main processing loop
- `calculateSpeedAndDirectionForAllObjects()` - calculate derived data
- `sendPositionUpdatesForActiveObjects()` - WebSocket position messaging
- `processFlightNotificationUpdates()` - WebSocket flight notification messaging
- `sendPositionUpdatesForActiveObjects()` - WebSocket messaging
- `performViolationDetection()` - rule engine integration
- `saveUnmannedVehicleDataPeriodically()` - database persistence
#### Cache Sharing Pattern
- DataCollectorService owns activeMovingObjectsCache and flightNotificationCache
- DataProcessingService receives cache references via setActiveMovingObjectsCache() and getFlightNotificationCache()
- Both services share the same cache instances but have different responsibilities
- Position data flows through activeMovingObjectsCache for calculation and WebSocket updates
- Flight notification data flows through flightNotificationCache for WebSocket updates
- DataCollectorService owns activeMovingObjectsCache
- DataProcessingService receives cache reference via setActiveMovingObjectsCache()
- Both services share the same cache instance but have different responsibilities
#### Why Complete Service Separation is Critical
- **Data Integrity**: High-frequency collection prevents missing position updates
@ -72,9 +64,11 @@ The collision avoidance system integrates with RuoYi through `QuapDataAdapter` (
- **Architecture Clarity**: Clear separation of concerns makes system more maintainable
- **Timing Consistency**: Avoids conflicts between collection and calculation frequencies
#### Critical Rule
**NEVER** place calculation logic or WebSocket publishing in DataCollectorService methods.
**FORBIDDEN**: Direct eventPublisher.publishEvent() calls in collection methods
#### Violation of This Principle
**NEVER** place calculation logic in DataCollectorService methods. This will cause:
- WebSocket messages showing speed/direction as 0
- Inconsistent data due to frequency mismatch
- Architecture violations that are difficult to debug
## Development Commands
@ -108,9 +102,19 @@ tail -f qaup-admin/app.log
```
## Technology Stack
- **Backend**: Spring Boot 3.5.3 with Java 17, PostgreSQL + PostGIS, MyBatis, Redis
- **Frontend**: Vue 2.6.12 with Element UI, ECharts
- **Real-time**: WebSocket, JTS + GeoTools for spatial calculations
### Backend
- **Spring Boot 3.5.3** with Java 17
- **PostgreSQL + PostGIS** for spatial data
- **MyBatis** for database operations
- **Redis** for caching and session management
- **WebSocket** for real-time communication
- **JTS + GeoTools** for spatial calculations
### Frontend
- **Vue 2.6.12** with Element UI
- **Axios** for API communication
- **ECharts** for data visualization
## Configuration
@ -122,41 +126,39 @@ tail -f qaup-admin/app.log
- `sql/create_sys_driver_info_table.sql`
### Key Configuration Files
- `qaup-admin/src/main/resources/application.yml`: Main configuration
- `qaup-admin/src/main/resources/application.yml`: Main configuration including database, Redis, and collision system settings
- `qaup-ui/vue.config.js`: Frontend build configuration
- `qaup-collision/src/main/resources/config/`: Spatial configuration (airport_areas.yaml, airport_roads.yaml)
- `qaup-collision/src/main/resources/config/`: Spatial configuration files (airport_areas.yaml, airport_roads.yaml)
## Important Development Patterns
### Data Access
Always use `QuapDataAdapter` for accessing vehicle/driver data in collision module to maintain clean separation between collision detection and system management.
Always use `QuapDataAdapter` for accessing vehicle and driver data in collision module instead of direct service calls. This maintains clean separation between collision detection and system management.
### **CRITICAL: Serena MCP Token Management**
**Claude MUST follow these rules to prevent excessive token usage:**
### WebSocket Communication
Real-time data flows through WebSocket endpoints configured in collision module. Messages use `/topic` prefix for broadcasting to connected clients.
#### **Mandatory Parameters**
- **Always set max_answer_chars=2000**
- **Always set depth=0**
- **Always set include_body=false**
- **Always specify exact relative_path** (never use ".")
### Spatial Data
PostGIS integration handles geometric calculations. Coordinate system defaults to airport center at (120.0834104, 36.35406879).
#### **Simple Usage Pattern**
```bash
# Good - controlled usage
mcp__serena__find_symbol(name_path="ClassName", relative_path="path/to/file.java", include_body=false, depth=0, max_answer_chars=2000)
### Performance Optimization
- Data collection interval: 250ms for high-frequency updates
- WebSocket push throttling: 1000ms to prevent frontend overload
- Redis caching with 60-second expiration for real-time data
# Bad - excessive tokens
mcp__serena__find_symbol(depth=1) # Never use depth=1
mcp__serena__search_for_pattern(relative_path=".") # Never search entire project
```
## Version Management
**This reduces token usage from 9930+ characters to under 500 characters per query.**
### Version Control Files
- **VERSION.md**: Contains current version number (semantic versioning)
- **changelog.md**: Detailed change log following Keep a Changelog format
### Real-time Communication
- WebSocket endpoints use `/topic` prefix for broadcasting
- PostGIS handles geometric calculations with airport center at (120.0834104, 36.35406879)
### Version Update Process
When making significant changes:
1. Update version number in VERSION.md
2. Add detailed changelog entry in changelog.md
3. Include version update in commit message
4. Use semantic versioning (MAJOR.MINOR.PATCH)
### Application URLs
- **Admin Interface**: http://localhost:8080
- **API Documentation**: http://localhost:8080/swagger-ui/index.html
- **WebSocket Endpoint**: ws://localhost:8080/ws
@ -172,7 +174,9 @@ mcp__serena__search_for_pattern(relative_path=".") # Never search entire projec
- Verify PostGIS extension is properly installed for spatial operations
- WebSocket connection issues often relate to CORS configuration in SecurityConfig
### Architecture Issues
- If speed/direction appears as 0, check that calculations are in DataProcessingService, not DataCollectorService
- If WebSocket messages are too frequent, check that eventPublisher calls are in processing phase only
- Ensure complete service separation: DataCollectorService = collection only, DataProcessingService = processing + WebSocket
### Speed/Direction Calculation Issues
- **CRITICAL**: Speed/direction calculations must ONLY occur in processing phase (1000ms), NEVER in collection phase (250ms)
- If speed appears as 0, check if calculations are incorrectly placed in collection phase
- Collection phase should store MovingObjects with NULL speed/direction values
- Processing phase should calculate and update speed/direction for all cached objects before WebSocket sending
- Ensure complete separation: Collection = data only, Processing = calculations only

142
IFLOW.md
View File

@ -1,142 +0,0 @@
# QAUP-Management 项目概览
## 项目简介
QAUP-Management 是一个机场无人车冲突管理平台集成了机场冲突避免系统。该项目基于若依框架RuoYi开发采用 Maven 多模块架构,提供了完整的车辆管理、空间分析、实时监控等功能。
核心模块 `qaup-collision` 实现了无人车与飞机、其他车辆之间的冲突检测算法,利用 PostGIS 进行空间数据分析,并通过 WebSocket 实现实时数据推送。
## 技术栈
- **后端框架**: Spring Boot 3.5.3, Spring MVC, Spring Data JPA, MyBatis
- **数据库**: PostgreSQL 与 PostGIS 扩展(空间数据处理)
- **缓存**: Redis
- **实时通信**: WebSocket + STOMP
- **空间计算**: JTS (Java Topology Suite), GeoTools
- **构建工具**: Maven 3.6+
- **Java版本**: JDK 21
- **API文档**: SpringDoc OpenAPI (Swagger UI)
- **数据库迁移**: Flyway
## 项目结构
```
QAUP-Management/
├── qaup-admin/ # Web服务入口集成所有模块
├── qaup-framework/ # 核心框架,提供通用功能
├── qaup-system/ # 系统管理模块(用户、角色、权限等)
├── qaup-collision/ # 冲突避免系统模块(核心业务逻辑)
├── qaup-common/ # 公共工具类和基础组件
├── qaup-quartz/ # 定时任务调度
├── qaup-generator/ # 代码生成器
├── qaup-ui/ # 前端Vue项目
├── sql/ # 数据库初始化脚本
├── deploy/ # 部署相关脚本和配置
├── tools/ # 开发和测试工具
└── doc/ # 项目文档
```
## 核心模块详解
### qaup-collision (冲突避免系统)
这是项目的核心模块,负责实现无人车冲突检测与管理功能。
**主要特性**:
- 基于 PostGIS 的空间数据分析与几何计算
- WebSocket 实时车辆位置监控与数据推送
- 机场区域(跑道、滑行道、停机坪)配置与监控
- 车辆与飞机、车辆与车辆间的实时冲突检测算法
- 统一数据访问适配器 `QuapDataAdapter`,避免重复 DAO 开发
- Redis 缓存支持高性能数据存取
**关键组件**:
- `QuapDataAdapter`: 统一数据访问接口,连接若依系统数据
- `WebSocketConfig`: WebSocket 配置,支持实时数据推送
- `VehicleLocationService`: 车辆位置管理服务
- 空间实体类:继承空间基类并配置 PostGIS 映射
## 构建与运行
### 环境准备
- JDK 21
- Maven 3.6+
- PostgreSQL (启用 PostGIS 扩展)
- Redis
### 快速启动
1. **数据库配置**:
- 创建 PostgreSQL 数据库并启用 PostGIS 扩展
- 执行 SQL 初始化脚本 (位于 `sql/` 目录)
- 配置 `qaup-admin/src/main/resources/application.yml` 中的数据库连接信息
2. **启动依赖服务**:
```bash
# 启动 Redis
redis-server
# 启动 PostgreSQL (确保已创建数据库并启用 PostGIS)
```
3. **编译和启动**:
```bash
# 清理并编译整个项目
mvn clean install
# 启动应用
cd qaup-admin
mvn spring-boot:run
# 或者运行打包后的jar
java -jar target/qaup-admin.jar
```
4. **使用启动脚本**:
```bash
# 复制环境配置模板
cp .env.example .env
# 修改 .env 中的配置值
# 启动应用
./qaup.sh start
# 停止应用
./qaup.sh stop
# 重启应用
./qaup.sh restart
# 查看状态
./qaup.sh status
```
### 访问系统
- 管理后台: `http://localhost:8080`
- WebSocket 端点: `ws://localhost:8080/collision`
- API 文档: `http://localhost:8080/swagger-ui.html`
## 开发指南
### 项目合并说明
本项目已将独立的 CollisionAvoidanceSystem 项目合并到若依框架中,通过以下方式实现:
1. **模块化集成**: 创建 `qaup-collision` 模块,保持功能独立性
2. **数据适配器**: `QuapDataAdapter` 提供统一数据访问,连接若依 Service 层
3. **配置整合**: 将 PostGIS、JPA、WebSocket 等配置整合到若依配置体系
4. **版本兼容**: 升级 Spring Boot 版本至 3.5.3,使用 JDK 21
### collision 模块开发要点
- 新增空间实体时,需继承空间基类并正确配置 PostGIS 映射
- WebSocket 消息通过 `/topic` 前缀向客户端广播
- 使用 `QuapDataAdapter` 获取车辆和司机数据,避免直接访问 DAO
- 所有空间计算应使用 JTS 和 GeoTools 库
### 数据采集与处理
系统支持从多个数据源采集数据:
- 机场数据 API
- ADXP 适配器服务
- 无人车厂商 API
数据采集配置可在 `application.yml` 中调整,包括采集间隔、超时时间等参数。

147
QWEN.md
View File

@ -1,147 +0,0 @@
# QAUP-Management - 机场无人车冲突管理平台
## 项目概述
QAUP-Management 是一个机场无人车冲突管理平台,集成了机场冲突避免系统,提供完整的车辆管理、空间分析、实时监控等功能。该项目基于 RuoYi 框架构建,采用 Spring Boot 3.x 技术栈,使用 PostgreSQL + PostGIS 进行空间数据处理,并通过 WebSocket 实现实时通信。
### 核心功能特性
1. **空间数据分析**:基于 PostGIS 的空间计算和几何分析
2. **实时车辆监控**WebSocket 实时位置数据推送和展示
3. **机场区域管理**:跑道、滑行道、停机坪等区域配置和监控
4. **冲突检测算法**:实时检测车辆与飞机、车辆间的潜在冲突
5. **数据适配器**QuapDataAdapter 统一数据访问,避免重复 DAO 开发
6. **Redis 缓存**:高性能数据缓存和会话管理
### 项目架构
这是一个采用 Maven 多模块架构的大型系统:
- **qaup-admin**: Web 服务入口,集成所有模块
- **qaup-framework**: 核心框架,提供通用功能
- **qaup-system**: 系统管理模块,用户、角色、权限等
- **qaup-collision**: 冲突避免系统模块提供空间分析、车辆监控、WebSocket 实时通信等功能
- **qaup-common**: 公共工具类和基础组件
- **qaup-quartz**: 定时任务调度
- **qaup-generator**: 代码生成器
### 技术栈
- **后端**: Spring Boot 3.5.3 + Java 21 + MyBatis + JPA (Hibernate)
- **数据库**: PostgreSQL + PostGIS (空间数据扩展)
- **缓存**: Redis
- **实时通信**: WebSocket + STOMP
- **空间计算**: JTS + GeoTools + Hibernate Spatial
- **构建工具**: Maven 3.6+
- **前端**: Vue 2.6.12 + Element UI
## 构建和运行
### 开发环境准备
1. 安装 Java 21
2. 安装 Maven 3.6+
3. 安装 PostgreSQL 并启用 PostGIS 扩展
4. 安装 Redis
### 数据库配置
1. 创建 PostgreSQL 数据库并启用 PostGIS 扩展
2. 执行 SQL 初始化脚本:
- `sql/create_qaup_database.sql` - 创建数据库
- `sql/create_sys_vehicle_info_table.sql` - 车辆信息表
- `sql/create_sys_driver_info_table.sql` - 司机信息表
### 环境变量配置
创建 .env 文件或设置环境变量:
```
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=qaup
POSTGRES_USER=your_username
POSTGRES_PASSWORD=your_password
REDIS_HOST=localhost
REDIS_PORT=6379
```
### 编译和启动
```bash
# 清理并编译整个项目
mvn clean install
# 启动应用
cd qaup-admin
mvn spring-boot:run
# 或者运行打包后的 jar
java -jar target/qaup-admin.jar
```
### 访问系统
- 管理后台: http://localhost:8080
- WebSocket 端点: ws://localhost:8080/collision
- API 文档: http://localhost:8080/swagger-ui/index.html
## 开发指南
### 核心组件说明
- **QuapDataAdapter**: 数据访问适配器,连接若依系统数据
- **WebSocketConfig**: WebSocket 配置,支持实时数据推送
- **VehicleLocationService**: 车辆位置管理服务
- **GeopositionController**: WebSocket 消息控制器
- **DataCollectorService**: 数据收集服务(采集频率 250ms
- **DataProcessingService**: 数据处理服务(处理频率 1000ms
### 服务间协作模式
QAUP-Management 采用服务完全分离的架构:
- **DataCollectorService** (250ms): 只负责从外部 API 获取数据并缓存,不进行任何计算
- **DataProcessingService** (1000ms): 专门负责数据处理、计算速度和方向、发送 WebSocket 消息、违规检测等
两个服务通过 `activeMovingObjectsCache` 共享数据,实现了采集与处理的频率分离。
### 空间数据处理
系统集成了完整的 PostGIS 支持:
- 使用 JTS (Java Topology Suite) 进行空间几何操作
- 使用 GeoTools 进行坐标转换和空间分析
- 机场区域管理支持复杂的空间查询和冲突检测
- 电子围栏功能支持区域准入控制和超速检测
### WebSocket 消息系统
- 位置更新消息:每秒推送车辆位置更新
- 冲突检测消息:实时推送车辆间潜在冲突
- 违规事件消息:推送违规行为通知
- 消息格式统一采用 `UniversalMessage` 结构
## 版本管理
- 当前版本: 1.0.1 (pom.xml)
- 版本文件: VERSION.md (0.8.0)
- 详细变更记录: CHANGELOG.md
## 测试和验证
- 运行单元测试: `mvn test`
- 运行集成测试: `mvn verify`
- 通过 Swagger UI 验证 API 功能
- 通过 WebSocket 客户端验证实时通信
## 部署配置
项目支持 Docker 部署,相关配置在 deploy/ 目录下,包含 Docker Compose 配置文件和部署脚本,支持生产环境的完全离线部署。
## 业务价值
- **实时监控**:提供机场车辆和航空器的实时位置监控
- **冲突预防**:通过智能算法提前检测和预警潜在冲突
- **安全管理**:电子围栏和违规检测保障机场运行安全
- **数据驱动**:提供丰富的数据支持运营决策
- **系统集成**:与机场现有系统无缝集成

203
README.md
View File

@ -17,7 +17,6 @@
## 核心特性
### 🚗 冲突避免系统 (collision模块)
- **空间数据分析**: 基于PostGIS的空间计算和几何分析
- **实时车辆监控**: WebSocket实时位置数据推送和展示
- **机场区域管理**: 跑道、滑行道、停机坪等区域配置和监控
@ -27,15 +26,15 @@
### 🛠️ 系统管理功能
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
3. 岗位管理:配置系统用户所属担任职务。
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
7. 参数管理:对系统动态配置常用参数。
8. 通知公告:系统通知公告信息发布维护。
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
3. 岗位管理:配置系统用户所属担任职务。
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
7. 参数管理:对系统动态配置常用参数。
8. 通知公告:系统通知公告信息发布维护。
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
10. 登录日志:系统登录日志记录查询包含登录异常。
11. 在线用户:当前系统中活跃用户状态监控。
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
@ -56,194 +55,65 @@
- **构建工具**: Maven 3.6+
- **Java版本**: JDK 8
## 部署指南
### 方式一:自动部署(推荐)
使用提供的自动化部署脚本:
## 快速启动
### 1. 环境准备
```bash
# 1. 进入部署目录
cd deploy/
# 2. 赋予执行权限
chmod +x deploy-all.sh
# 3. 执行自动部署
./deploy-all.sh
```
自动化部署脚本将自动完成:
- ✅ 环境检查和依赖验证
- ✅ Docker及Docker Compose安装检查
- ✅ 磁盘空间检查至少3GB
- ✅ 端口占用检测
- ✅ Docker镜像处理本地载入或在线拉取
- ✅ 冲突容器清理
- ✅ 数据目录创建和权限设置
- ✅ 配置文件验证
- ✅ 基础设施服务启动PostgreSQL + PostGIS、Redis
- ✅ 应用服务启动和数据库迁移
- ✅ 健康状态检查和验证
### 方式二:手动部署
#### 1. 环境准备
**必需组件:**
```bash
# Java 8+
# Java 8
java -version
# Maven 3.6+
mvn -version
# Docker & Docker Compose
docker --version
docker-compose --version
```
**可选组件(用于本地开发):**
```bash
# PostgreSQL + PostGIS用于本地开发
# PostgreSQL + PostGIS
psql --version
# Redis(用于本地开发)
# Redis
redis-server --version
```
#### 2. 数据库配置
**Docker启动数据库**
### 2. 数据库配置
1. 创建PostgreSQL数据库并启用PostGIS扩展
2. 执行SQL初始化脚本
- `sql/create_qaup_database.sql` - 创建数据库
- `sql/create_sys_vehicle_info_table.sql` - 车辆信息表
- `sql/create_sys_driver_info_table.sql` - 司机信息表
### 3. 启动Redis服务
```bash
# 启动PostgreSQL + PostGIS
docker run -d \
--name qaup-postgres \
-e POSTGRES_DB=qaup \
-e POSTGRES_USER=qaup \
-e POSTGRES_PASSWORD=qaup123 \
-e POSTGRES_INITDB_ARGS="--encoding=UTF-8 --lc-collate=C --lc-ctype=C" \
-p 5432:5432 \
m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine
# 启动Redis
docker run -d \
--name qaup-redis \
-e REDIS_PASSWORD=qaup123 \
-p 6379:6379 \
m.daocloud.io/docker.io/library/redis:8.0-alpine redis-server --requirepass qaup123
redis-server
```
**本地数据库初始化:**
1. 执行SQL初始化脚本
```bash
# 创建数据库和基础表
psql -U qaup -d qaup -f sql/create_qaup_database.sql
psql -U qaup -d qaup -f sql/create_sys_vehicle_info_table.sql
psql -U qaup -d qaup -f sql/create_sys_driver_info_table.sql
```
#### 3. 环境配置
**更新应用配置:**
### 4. 配置应用
修改 `qaup-admin/src/main/resources/application.yml`:
```yaml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/qaup
username: qaup
password: qaup123
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/your_database
username: your_username
password: your_password
redis:
host: localhost
port: 6379
password: qaup123
database: 0
```
**环境变量配置:**
确保各模块 `.env` 文件配置正确:
- `adxp-adapter/.env.example` - ADXP数据中台适配器配置
- `qaup-admin/.env.example` - 主应用配置
#### 4. 编译和启动
### 5. 编译和启动
```bash
# 1. 清理并编译整个项目
# 清理并编译整个项目
mvn clean install
# 2. 进入管理模块
# 启动应用
cd qaup-admin
# 3. 启动应用(开发模式)
mvn spring-boot:run
# 或打包启动(生产模式)
mvn clean package -DskipTests
# 或者运行打包后的jar
java -jar target/qaup-admin.jar
```
#### 5. 验证部署
**服务状态检查:**
```bash
# 检查应用健康状态
curl http://localhost:8080/actuator/health
# 检查数据库连接
docker exec qaup-postgres pg_isready -U qaup
# 检查Redis连接
docker exec qaup-redis redis-cli -a qaup123 ping
```
### 6. 系统访问
- **管理后台**: <http://localhost:8080>
- **初始登录**:
- 用户名: `admin`
- 密码: `admin123`
- **WebSocket端点**: ws://localhost:8080/ws
- **API文档**: <http://localhost:8080/swagger-ui/>
- **健康检查**: <http://localhost:8080/actuator/health>
### 7. 管理命令
```bash
# 查看服务状态
docker compose ps
# 查看应用日志
docker compose logs -f qaup-app
# 查看数据库日志
docker compose logs -f qaup-postgres
# 查看Redis日志
docker compose logs -f qaup-redis
# 重启应用
docker compose restart qaup-app
# 停止所有服务
docker compose down
# 查看数据库迁移状态
docker exec qaup-postgres psql -U qaup -d qaup -c \
"SELECT version,description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 5;"
# 清理Docker资源
docker system prune -a
```
### 6. 访问系统
- 管理后台: http://localhost:8080
- WebSocket端点: ws://localhost:8080/ws
- API文档: http://localhost:8080/swagger-ui/
## 项目合并说明
@ -257,19 +127,16 @@ docker system prune -a
## 开发指南
### collision模块核心组件
- `QuapDataAdapter`: 数据访问适配器,连接若依系统数据
- `WebSocketConfig`: WebSocket配置支持实时数据推送
- `VehicleLocationService`: 车辆位置管理服务
- `GeopositionController`: WebSocket消息控制器
### 扩展开发
1. 新增spatial实体时继承spatial基类并配置PostGIS映射
2. WebSocket消息通过`/topic`前缀向客户端广播
3. 使用`QuapDataAdapter`获取车辆和司机数据避免直接访问DAO
## 版本信息
- 当前版本: 1.0.1
- 更新日志: 详见 `change_log.md`
- 更新日志: 详见 `change_log.md`

View File

@ -1 +1 @@
0.8.0
0.6.0

View File

@ -1,78 +0,0 @@
# ============================================================
# ADXP 数据中台环境变量配置模板
# 使用说明:
# 1. 复制此文件为 .env: cp .env.example .env
# 2. 修改 .env 中的配置值
# 3. 启动应用时会自动加载 .env 文件中的环境变量
# 4. 适用于ADXP主动连接架构
# ============================================================
# ========== ADXP 数据中台连接配置 ==========
# ADXP数据中台的主机地址和端口
ADXP_HOST=10.32.38.2
ADXP_PORT=8081
# 连接ADXP数据中台的用户名和密码
ADXP_USERNAME=dianxin
ADXP_PASSWORD=Dianxin#2025
# ========== 适配器服务配置 ==========
# 适配器服务端口
SERVER_PORT=8086
# 应用环境标识
SPRING_PROFILES_ACTIVE=prod
# ========== 日志配置 ==========
# QAUP应用日志级别
LOG_LEVEL_QAUP=info
# Spring框架日志级别
LOG_LEVEL_SPRING=warn
# ADXP SDK日志级别
LOG_LEVEL_ADXP=info
# ========== 健康检查和监控配置 ==========
# 健康检查端点路径(默认:/actuator/health
MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,metrics
# 健康检查详细程度simple/detailed
MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS=simple
# ========== 数据采集配置 ==========
# 数据采集间隔(毫秒)
DATA_COLLECTOR_INTERVAL=250
# 重连延迟(毫秒)
RECONNECT_DELAY_MILLIS=3000
# ========== WebSocket配置 ==========
# WebSocket端点路径
WEBSOCKET_ENDPOINT=/ws/flight-notifications
# ========== Redis配置用于缓存和会话管理 ==========
# Redis主机地址
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DATABASE=0
REDIS_PASSWORD=
# Redis最大内存限制
REDIS_MAX_MEMORY=256mb
# Redis内存淘汰策略
REDIS_MAX_MEMORY_POLICY=volatile-lru
# ========== 生产环境示例配置 ==========
# 生产环境时请根据实际情况修改以下配置:
# ADXP_HOST=10.32.38.2 # 真实的ADXP数据中台IP地址
# ADXP_PORT=8081 # 真实的ADXP数据中台端口
# ADXP_USERNAME=dianxin # 真实的用户名
# ADXP_PASSWORD=Dianxin#2025 # 真实的密码
# LOG_LEVEL_QAUP=info # 生产环境建议使用info级别
# REDIS_MAX_MEMORY=1gb # 生产环境建议增加Redis内存
# ============================================================
# 注意事项:
# 1. 等号两边不要有空格
# 2. 字符串值不需要引号
# 3. #开头的行为注释
# 4. 请勿将 .env 文件提交到Git仓库
# 5. 生产环境请务必修改默认密码
# 6. ADXP适配器采用主动连接架构启动时自动连接数据中台
# ============================================================

View File

@ -1,32 +0,0 @@
FROM m.daocloud.io/docker.io/library/openjdk:8-jdk
# 设置时区并安装必要的网络工具
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \
apt-get update && \
apt-get install -y net-tools && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 复制 JAR 和依赖库
COPY target/adxp-adapter.jar app.jar
COPY libs/*.jar libs/
# 创建日志目录
RUN mkdir -p /app/logs
# 暴露端口
EXPOSE 8086
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:8086/api/adxp/health || exit 1
# 启动应用
ENTRYPOINT ["java", \
"-Djava.security.egd=file:/dev/./urandom", \
"-Xms256m", \
"-Xmx512m", \
"-jar", \
"app.jar"]

View File

@ -1,264 +0,0 @@
# ADXP SDK Adapter Service
ADXP 数据中台 SDK 适配器服务 - 基于 JDK 8 运行的独立微服务
## 新架构特性ADXP主动连接
### 🔄 **适配器主动连接架构**
- **启动即连接**: 适配器服务启动时自动连接到ADXP数据中台
- **无需登录接口**: 移除 `/login` 端点,由适配器自动管理认证
- **连接状态管理**: 提供 `/status``/reconnect` 端点进行连接管理
- **WebSocket实时推送**: 支持实时向客户端推送航班消息
- **健康监控**: 内置连接状态监控和自动重连机制
## 功能特性
将青岛机场数据中台 SDK (adxp-client-2.6.9.jar) 封装为 REST API 服务,解决 SDK 与现代 Java 版本的兼容性问题。
## 技术栈
- **Java**: JDK 8
- **框架**: Spring Boot 2.7.18
- **构建**: Maven
- **容器化**: Docker
## 快速开始
### 方式1Docker Compose推荐
```bash
# 1. 构建并启动服务
docker compose up -d
# 2. 查看日志
docker compose logs -f
# 3. 停止服务
docker compose down
```
### 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. 登录
```http
POST /api/adxp/login
Content-Type: application/json
{
"username": "dianxin",
"password": "dianxin@123"
}
```
响应:
```json
{
"success": true,
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"message": "登录成功"
}
```
### 2. 接收消息
```http
GET /api/adxp/messages?sessionId=550e8400-e29b-41d4-a716-446655440000
```
响应:
```json
{
"success": true,
"messages": [
{
"serviceCode": "ARR",
"actionCode": "ADD",
"content": "<xml>...</xml>"
}
],
"message": null
}
```
### 3. 登出
```http
POST /api/adxp/logout
Content-Type: application/json
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000"
}
```
### 4. 健康检查
```http
GET /api/adxp/health
```
响应:
```json
{
"status": "UP",
"activeSessions": 1
}
```
## 快速测试
### 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
```
## 配置说明
### 环境变量
| 变量名 | 说明 | 默认值 |
|--------|------|--------|
| `ADXP_HOST` | ADXP 数据中台主机地址 | localhost |
| `ADXP_PORT` | ADXP 数据中台端口 | 7001 |
| `ADXP_USERNAME` | ADXP 用户名 | dianxin |
| `ADXP_PASSWORD` | ADXP 密码 | dianxin@123 |
### application.yml
```yaml
adxp:
host: ${ADXP_HOST:localhost}
port: ${ADXP_PORT:7001}
username: ${ADXP_USERNAME:dianxin}
password: ${ADXP_PASSWORD:dianxin@123}
```
## 部署说明
### 开发环境
```bash
# 1. 启动适配器(默认指向 localhost
docker compose up -d
```
### 生产环境
```bash
# 1. 创建环境变量文件
cat > .env << EOF
ADXP_HOST=10.10.10.100 # 真实数据中台 IP
ADXP_PORT=7001 # 真实数据中台端口
ADXP_USERNAME=dianxin # 真实数据中台用户名
ADXP_PASSWORD=dianxin@123 # 真实数据中台密码
EOF
# 2. 启动服务
docker compose up -d
```
## 开发说明
### 项目结构
```
adxp-adapter/
├── pom.xml # Maven 配置
├── Dockerfile # Docker 镜像定义
├── docker-compose.yml # Docker Compose 配置
├── libs/ # 数据中台 SDK
│ ├── adxp-client-2.6.9.jar
│ └── mq.allclient-9.0.jar
└── src/ # 源代码
```
### 核心依赖
- **adxp-client-2.6.9.jar**: 数据中台 SDK
- **Apache CXF 3.2.4**: SOAP 协议支持
- **Jackson 1.9.13**: JSON 序列化
- **Spring Boot 2.7.18**: 应用框架
## 许可证
内部项目

View File

@ -1,30 +0,0 @@
services:
adxp-adapter:
build: .
image: adxp-adapter:1.0.0
container_name: adxp-adapter
ports:
- "8086:8086"
environment:
# ADXP 数据中台连接配置(必须设置)
ADXP_HOST: ${ADXP_HOST:-10.32.38.2}
ADXP_PORT: ${ADXP_PORT:-8081}
ADXP_USERNAME: ${ADXP_USERNAME:-dianxin}
ADXP_PASSWORD: ${ADXP_PASSWORD:-Dianxin#2025}
# Spring Profile
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod}
volumes:
- ./logs:/app/logs
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8086/api/adxp/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 40s
networks:
- qaup-network
networks:
qaup-network:
driver: bridge

View File

@ -1,133 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qaup</groupId>
<artifactId>adxp-adapter</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>ADXP SDK Adapter Service</name>
<description>JDK 8 adapter service for ADXP SDK integration</description>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>2.7.18</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Spring Boot Actuator (health check) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- ADXP SDK -->
<dependency>
<groupId>com.taocares</groupId>
<artifactId>adxp-client</artifactId>
<version>2.6.9</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/adxp-client-2.6.9.jar</systemPath>
</dependency>
<!-- IBM MQ Client -->
<dependency>
<groupId>com.ibm</groupId>
<artifactId>mq.allclient</artifactId>
<version>9.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/mq.allclient-9.0.jar</systemPath>
</dependency>
<!-- Jackson 1.x for SDK compatibility -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>1.9.13</version>
</dependency>
<!-- Jackson 2.x for Spring Boot JSON serialization -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Apache CXF for SOAP -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.2.4</version>
</dependency>
<!-- dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
<build>
<finalName>adxp-adapter</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,12 +0,0 @@
package com.qaup.adxp.adapter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AdxpAdapterApplication {
public static void main(String[] args) {
SpringApplication.run(AdxpAdapterApplication.class, args);
}
}

View File

@ -1,25 +0,0 @@
package com.qaup.adxp.adapter.config;
import com.qaup.adxp.adapter.websocket.AdxpWebSocketHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Bean
public AdxpWebSocketHandler adxpWebSocketHandler() {
return new AdxpWebSocketHandler();
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册WebSocket处理器
registry.addHandler(adxpWebSocketHandler(), "/ws/flight-notifications")
.setAllowedOrigins("*");
}
}

View File

@ -1,130 +0,0 @@
package com.qaup.adxp.adapter.controller;
import com.qaup.adxp.adapter.dto.*;
import com.qaup.adxp.adapter.service.AdxpSdkService;
import com.qaup.adxp.adapter.websocket.AdxpWebSocketHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
@RestController
@RequestMapping("/api/adxp")
public class AdxpController {
private static final Logger log = LoggerFactory.getLogger(AdxpController.class);
@Autowired
private AdxpSdkService adxpSdkService;
@Autowired(required = false)
private AdxpWebSocketHandler adxpWebSocketHandler;
/**
* 获取连接状态
*/
@GetMapping("/status")
public ResponseEntity<Map<String, Object>> getStatus() {
try {
Map<String, Object> status = new HashMap<>();
status.put("connected", adxpSdkService.isConnected());
status.put("connectionInfo", adxpSdkService.getConnectionInfo());
status.put("timestamp", System.currentTimeMillis());
return ResponseEntity.ok(status);
} catch (Exception e) {
log.error("获取连接状态失败", e);
Map<String, Object> error = new HashMap<>();
error.put("connected", false);
error.put("error", e.getMessage());
return ResponseEntity.ok(error);
}
}
/**
* 强制重连
*/
@PostMapping("/reconnect")
public ResponseEntity<Map<String, Object>> reconnect() {
try {
adxpSdkService.forceReconnect();
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "重连成功");
result.put("connected", adxpSdkService.isConnected());
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("重连失败", e);
Map<String, Object> error = new HashMap<>();
error.put("success", false);
error.put("message", "重连失败: " + e.getMessage());
error.put("connected", false);
return ResponseEntity.ok(error);
}
}
/**
* 获取消息接口 - 使用当前连接
*/
@GetMapping("/messages")
public ResponseEntity<MessageResponse> getMessages() {
try {
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("接收消息失败", e);
return ResponseEntity.ok(MessageResponse.failure(e.getMessage()));
}
}
/**
* 断开连接接口
*/
@PostMapping("/disconnect")
public ResponseEntity<Map<String, Object>> disconnect() {
try {
adxpSdkService.disconnectFromADXPServer();
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "已断开ADXP数据中台连接");
response.put("connected", false);
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("断开连接失败", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "断开连接失败: " + e.getMessage());
response.put("connected", adxpSdkService.isConnected());
return ResponseEntity.ok(response);
}
}
/**
* 健康检查
*/
@GetMapping("/health")
public ResponseEntity<Map<String, Object>> health() {
try {
Map<String, Object> health = adxpSdkService.healthCheck();
if (adxpWebSocketHandler != null) {
health.put("websocketConnections", adxpWebSocketHandler.getSessionCount());
}
return ResponseEntity.ok(health);
} catch (Exception e) {
log.error("健康检查失败", e);
Map<String, Object> health = new HashMap<>();
health.put("status", "DOWN");
health.put("message", "健康检查失败: " + e.getMessage());
health.put("connected", false);
health.put("activeConnections", 0);
return ResponseEntity.ok(health);
}
}
}

View File

@ -1,40 +0,0 @@
package com.qaup.adxp.adapter.dto;
public class FlightMessage {
private String serviceCode;
private String actionCode;
private String content;
public FlightMessage() {
}
public FlightMessage(String serviceCode, String actionCode, String content) {
this.serviceCode = serviceCode;
this.actionCode = actionCode;
this.content = content;
}
public String getServiceCode() {
return serviceCode;
}
public void setServiceCode(String serviceCode) {
this.serviceCode = serviceCode;
}
public String getActionCode() {
return actionCode;
}
public void setActionCode(String actionCode) {
this.actionCode = actionCode;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}

View File

@ -1,30 +0,0 @@
package com.qaup.adxp.adapter.dto;
public class LoginRequest {
private String username;
private String password;
public LoginRequest() {
}
public LoginRequest(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -1,48 +0,0 @@
package com.qaup.adxp.adapter.dto;
public class LoginResponse {
private boolean success;
private String sessionId;
private String message;
public LoginResponse() {
}
public LoginResponse(boolean success, String sessionId, String message) {
this.success = success;
this.sessionId = sessionId;
this.message = message;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public static LoginResponse success(String sessionId) {
return new LoginResponse(true, sessionId, "登录成功");
}
public static LoginResponse failure(String message) {
return new LoginResponse(false, null, message);
}
}

View File

@ -1,51 +0,0 @@
package com.qaup.adxp.adapter.dto;
import java.util.ArrayList;
import java.util.List;
public class MessageResponse {
private boolean success;
private List<FlightMessage> messages;
private String message;
public MessageResponse() {
}
public MessageResponse(boolean success, List<FlightMessage> messages, String message) {
this.success = success;
this.messages = messages;
this.message = message;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public List<FlightMessage> getMessages() {
return messages;
}
public void setMessages(List<FlightMessage> messages) {
this.messages = messages;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public static MessageResponse success(List<FlightMessage> messages) {
return new MessageResponse(true, messages, null);
}
public static MessageResponse failure(String message) {
return new MessageResponse(false, new ArrayList<FlightMessage>(), message);
}
}

View File

@ -1,370 +0,0 @@
package com.qaup.adxp.adapter.service;
import com.qaup.adxp.adapter.dto.FlightMessage;
import com.qaup.adxp.adapter.websocket.AdxpWebSocketHandler;
import com.taocares.adxp.client.ADXPClient;
import com.taocares.adxp.client.ADXPClientFactory;
import com.taocares.adxp.model.LoginResult;
import com.taocares.adxp.model.MessageResult;
import com.taocares.adxp.model.MessageList;
import com.taocares.adxp.model.MsgType;
import com.taocares.adxp.model.HeadType;
import com.taocares.adxp.AdxpConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Service
public class AdxpSdkService {
private static final Logger log = LoggerFactory.getLogger(AdxpSdkService.class);
@Value("${adxp.host}")
private String host;
@Value("${adxp.port}")
private int port;
@Value("${adxp.username:dianxin}")
private String defaultUsername;
@Value("${adxp.password:Dianxin#2025}")
private String defaultPassword;
@Autowired
private AdxpWebSocketHandler adxpWebSocketHandler;
// 单一连接状态管理
private volatile boolean connected = false;
private ADXPClient adxpClient = null;
// 定时重连任务
private final ScheduledExecutorService reconnectScheduler = Executors.newSingleThreadScheduledExecutor();
private final Object lock = new Object();
// 应用启动时主动连接
@PostConstruct
public void initConnection() {
log.info("ADXP适配器启动主动连接数据中台...");
try {
connectToADXP();
} catch (Exception e) {
log.error("ADXP数据中台初始连接失败: {}", e.getMessage());
// 启动定时重连
scheduleReconnect();
}
}
// 应用关闭时清理资源
@PreDestroy
public void cleanup() {
log.info("ADXP适配器关闭清理资源...");
disconnectFromADXP();
reconnectScheduler.shutdown();
}
// 主动连接到ADXP数据中台
private void connectToADXP() {
synchronized (lock) {
try {
log.info("正在主动连接 ADXP 数据中台: host={}, port={}, username={}",
host, port, defaultUsername);
// 创建客户端
adxpClient = ADXPClientFactory.createWSClient(host, port);
// 登录
LoginResult result = adxpClient.login(defaultUsername, defaultPassword);
boolean loginSuccess = result != null && Boolean.TRUE.equals(result.isSuccess());
if (loginSuccess) {
connected = true;
log.info("用户: {} 登录平台成功", defaultUsername);
// 启动消息监听
startMessageListener();
} else {
connected = false;
String errorMsg = result != null ?
String.format("code=%s, message=%s", result.getCode(), result.getMessage()) :
"登录响应为空";
log.error("用户: {} 登录平台失败: {}", defaultUsername, errorMsg);
throw new RuntimeException("ADXP登录失败: " + errorMsg);
}
} catch (Exception e) {
connected = false;
adxpClient = null;
log.error("ADXP数据中台连接失败: {}", e.getMessage());
throw new RuntimeException("ADXP连接失败", e);
}
}
}
// 断开连接
private void disconnectFromADXP() {
synchronized (lock) {
try {
if (adxpClient != null) {
adxpClient.logout(defaultUsername, defaultPassword);
}
connected = false;
adxpClient = null;
log.info("已断开ADXP数据中台连接");
} catch (Exception e) {
log.error("断开连接时发生错误", e);
}
}
}
// 定时重连任务
private void scheduleReconnect() {
reconnectScheduler.scheduleAtFixedRate(() -> {
if (!connected) {
try {
log.info("尝试重连ADXP数据中台...");
connectToADXP();
log.info("重连成功!");
} catch (Exception e) {
log.debug("重连失败: {}", e.getMessage());
}
}
}, 30, 30, TimeUnit.SECONDS); // 每30秒重连一次
}
/**
* 检查连接状态 - 供外部使用
*/
public boolean isConnected() {
return connected && adxpClient != null;
}
// 旧的healthCheck方法已移除使用返回Map的新版本
/**
* 获取当前连接信息
*/
public String getConnectionInfo() {
if (connected) {
return String.format("已连接到ADXP数据中台 (host=%s, port=%s)", host, port);
} else {
return "未连接到ADXP数据中台";
}
}
/**
* 强制重连 - 供外部调用
*/
public void forceReconnect() {
log.info("外部请求强制重连ADXP数据中台");
disconnectFromADXP();
try {
connectToADXP();
log.info("强制重连成功");
} catch (Exception e) {
log.error("强制重连失败", e);
throw new RuntimeException("重连失败", e);
}
}
/**
* 启动消息监听线程
*/
private void startMessageListener() {
Thread messageListener = new Thread(() -> {
log.info("ADXP消息监听线程已启动");
while (connected && adxpClient != null) {
try {
List<FlightMessage> messages = receiveMessages();
if (!messages.isEmpty()) {
log.info("接收到 {} 条消息", messages.size());
// 处理接收到的消息
processMessages(messages);
}
Thread.sleep(1000); // 每秒检查一次
} catch (InterruptedException e) {
log.info("消息监听线程被中断");
break;
} catch (Exception e) {
log.error("消息监听异常", e);
try {
Thread.sleep(5000); // 异常时等待5秒再重试
} catch (InterruptedException ie) {
break;
}
}
}
log.info("ADXP消息监听线程已结束");
}, "ADXP-Message-Listener");
messageListener.setDaemon(true);
messageListener.start();
}
/**
* 消息接收方法
*/
private List<FlightMessage> receiveMessages() {
if (!connected || adxpClient == null) {
return Collections.emptyList();
}
try {
MessageResult result = adxpClient.receiveMessage();
// 使用统一的错误码处理方法
if (handleMessageResultError(result)) {
return Collections.emptyList();
}
if (result == null || !Boolean.TRUE.equals(result.isSuccess())) {
return Collections.emptyList();
}
List<FlightMessage> messages = new ArrayList<>();
MessageList messageList = result.getMessageList();
if (messageList != null && messageList.getMsg() != null) {
for (MsgType msg : messageList.getMsg()) {
HeadType head = msg.getHead();
Object body = msg.getBody();
if (head != null && body != null) {
FlightMessage flightMessage = new FlightMessage(
head.getSvcServiceCode(),
null, // actionCode body
body.toString()
);
messages.add(flightMessage);
}
}
}
return messages;
} catch (Exception e) {
handleConnectionError(e);
return Collections.emptyList();
}
}
private void handleConnectionError(Exception e) {
log.error("ADXP连接错误: {}", e.getMessage());
// 对于所有连接错误直接尝试重新连接
log.info("尝试重新连接ADXP服务器");
reconnect();
}
/**
* 处理接收到的消息并广播到WebSocket客户端
*/
private void processMessages(List<FlightMessage> messages) {
if (messages != null && !messages.isEmpty()) {
try {
log.info("接收到 {} 条消息,准备广播", messages.size());
// 将消息广播到WebSocket客户端
if (adxpWebSocketHandler != null) {
adxpWebSocketHandler.broadcastMessages(messages);
}
} catch (Exception e) {
log.error("广播消息失败: {}", e.getMessage());
}
}
}
/**
* 处理消息结果错误码
*/
private boolean handleMessageResultError(MessageResult result) {
if (result == null) {
return false;
}
int code = result.getCode();
// 使用正确的常量类引用
if (code == AdxpConstants.RC_CLIENT_NOT_LOGGED_IN ||
code == AdxpConstants.RC_TOKEN_EXPIRED ||
code == AdxpConstants.RC_CLIENT_EXCEPTION ||
code == AdxpConstants.RC_REMOTE_EXCEPTION) {
log.info("遇到错误码 {},需要重新连接", code);
reconnect();
return true;
}
return false;
}
/**
* 获取消息 - 供外部调用
*/
public List<FlightMessage> getMessages() {
return receiveMessages();
}
/**
* 断开连接 - 主动断开与ADXP数据中台的连接
*/
public void disconnectFromADXPServer() {
log.info("主动断开ADXP数据中台连接");
disconnectFromADXP();
}
/**
* 重新连接方法
*/
private void reconnect() {
synchronized (lock) {
try {
log.info("尝试重新连接ADXP数据中台");
// 先断开现有连接
if (adxpClient != null) {
try {
adxpClient.logout(defaultUsername, defaultPassword);
} catch (Exception ignored) {}
}
// 重新创建客户端并登录
adxpClient = ADXPClientFactory.createWSClient(host, port);
LoginResult loginResult = adxpClient.login(defaultUsername, defaultPassword);
if (loginResult != null && Boolean.TRUE.equals(loginResult.isSuccess())) {
connected = true;
log.info("重新连接成功");
} else {
connected = false;
log.error("重新连接失败");
}
} catch (Exception e) {
connected = false;
adxpClient = null;
log.error("重新连接异常: {}", e.getMessage());
}
}
}
/**
* 健康检查
*/
public Map<String, Object> healthCheck() {
Map<String, Object> health = new HashMap<>();
health.put("status", connected ? "UP" : "DOWN");
health.put("connected", connected);
health.put("host", host);
health.put("port", port);
if (connected) {
health.put("message", "ADXP数据中台连接正常");
} else {
health.put("message", "ADXP数据中台连接异常");
}
return health;
}
}

View File

@ -1,56 +0,0 @@
package com.qaup.adxp.adapter.service;
import com.qaup.adxp.adapter.websocket.AdxpWebSocketHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.atomic.AtomicBoolean;
@Service
public class MessageListenerService {
private static final Logger log = LoggerFactory.getLogger(MessageListenerService.class);
@Autowired
private AdxpSdkService adxpSdkService;
@Autowired
private AdxpWebSocketHandler adxpWebSocketHandler;
private final AtomicBoolean isRunning = new AtomicBoolean(false);
/**
* 服务启动时的初始化方法
* 注意消息监听逻辑已移至AdxpSdkService中本服务仅负责管理和监控
*/
@PostConstruct
public void start() {
if (isRunning.compareAndSet(false, true)) {
log.info("消息监听服务已启动 - 消息监听逻辑已移至AdxpSdkService");
}
}
@PreDestroy
public void stop() {
if (isRunning.compareAndSet(true, false)) {
log.info("消息监听服务已停止");
}
}
/**
* 获取服务统计信息
*/
public String getStats() {
return String.format("MessageListenerService Stats: " +
"isRunning=%s, " +
"adxpConnected=%s, " +
"webSocketClients=%d",
isRunning.get(),
adxpSdkService.isConnected(),
adxpWebSocketHandler.getSessionCount());
}
}

View File

@ -1,142 +0,0 @@
package com.qaup.adxp.adapter.websocket;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.qaup.adxp.adapter.dto.FlightMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
public class AdxpWebSocketHandler extends TextWebSocketHandler {
private static final Logger log = LoggerFactory.getLogger(AdxpWebSocketHandler.class);
private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
private final ObjectMapper objectMapper;
public AdxpWebSocketHandler() {
this.objectMapper = new ObjectMapper();
// 配置ObjectMapper以更好地处理日期和其他序列化问题
this.objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
log.info("WebSocket连接已建立: sessionId={}, 当前连接数={}", session.getId(), sessions.size());
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 处理客户端发送的消息如果需要
log.debug("收到客户端消息: sessionId={}, message={}", session.getId(), message.getPayload());
// 可以处理一些控制命令比如心跳
if ("ping".equals(message.getPayload())) {
session.sendMessage(new TextMessage("pong"));
log.debug("发送心跳响应到客户端: sessionId={}", session.getId());
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
log.info("WebSocket连接已关闭: sessionId={}, reason={}, 当前连接数={}",
session.getId(), status.getReason(), sessions.size());
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
log.error("WebSocket传输错误: sessionId={}", session.getId(), exception);
sessions.remove(session);
}
/**
* 向所有连接的客户端广播消息
*/
public void broadcastMessages(List<FlightMessage> messages) {
if (messages == null || messages.isEmpty() || sessions.isEmpty()) {
return;
}
try {
// 序列化消息列表为JSON
String jsonMessage = objectMapper.writeValueAsString(messages);
TextMessage textMessage = new TextMessage(jsonMessage);
// 清理已关闭的会话
cleanupClosedSessions();
int clientCount = sessions.size();
// 向所有活跃客户端广播消息
for (WebSocketSession session : sessions) {
if (session.isOpen()) {
try {
session.sendMessage(textMessage);
} catch (Exception e) {
log.error("发送消息失败: sessionId={}", session.getId(), e);
sessions.remove(session);
}
}
}
if (log.isDebugEnabled()) {
log.debug("已向 {} 个客户端广播 {} 条消息", clientCount, messages.size());
}
} catch (Exception e) {
log.error("序列化或广播消息失败", e);
}
}
/**
* 清理已关闭的WebSocket会话
*/
private void cleanupClosedSessions() {
sessions.removeIf(session -> !session.isOpen());
}
/**
* 获取当前连接数
*/
public int getSessionCount() {
return sessions.size();
}
/**
* 向指定会话发送消息
*/
public void sendMessage(WebSocketSession session, List<FlightMessage> messages) {
if (messages == null || messages.isEmpty() || session == null || !session.isOpen()) {
return;
}
try {
String jsonMessage = objectMapper.writeValueAsString(messages);
TextMessage textMessage = new TextMessage(jsonMessage);
session.sendMessage(textMessage);
log.debug("向会话 {} 发送 {} 条消息", session.getId(), messages.size());
} catch (Exception e) {
log.error("向会话 {} 发送消息失败", session.getId(), e);
}
}
/**
* 获取连接详细信息
*/
public List<String> getConnectionDetails() {
return sessions.stream()
.filter(WebSocketSession::isOpen)
.map(session -> String.format("ID: %s, Remote Address: %s",
session.getId(),
session.getRemoteAddress()))
.collect(Collectors.toList());
}
}

View File

@ -1,18 +0,0 @@
# 生产环境配置
# ADXP 数据中台配置(使用环境变量)
adxp:
host: ${ADXP_HOST}
port: ${ADXP_PORT}
username: ${ADXP_USERNAME}
password: ${ADXP_PASSWORD}
# 日志配置
logging:
level:
com.qaup.adxp.adapter: info
com.taocares.adxp: warn
file:
name: /app/logs/adxp-adapter.log
max-size: 100MB
max-history: 30

View File

@ -1,32 +0,0 @@
server:
port: 8086
spring:
application:
name: adxp-adapter
# ADXP 数据中台配置
adxp:
# 使用环境变量或默认值
host: ${ADXP_HOST:localhost}
port: ${ADXP_PORT:7001}
username: ${ADXP_USERNAME:dianxin}
password: ${ADXP_PASSWORD:dianxin@123}
# 日志配置
logging:
level:
com.qaup.adxp.adapter: debug
com.taocares.adxp: info
pattern:
console: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n'
# Actuator 配置
management:
endpoints:
web:
exposure:
include: health,info
endpoint:
health:
show-details: always

View File

@ -6,326 +6,6 @@
版本规范基于 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
## [0.8.0] - 2025-01-07
### 🚀 **重大功能Flyway数据库迁移集成完整实现**
本版本实现了完整的Flyway数据库迁移系统为QAUP系统带来了专业级的数据库版本管理能力。
#### 🎯 **核心功能特性**
- **自动化数据库迁移** 🔄
- 应用启动时自动执行数据库迁移
- 支持基线迁移和增量迁移
- 迁移失败时应用启动失败,确保数据一致性
- 生产环境禁用危险操作如clean
- **完整的迁移脚本体系** 📝
- V1.0.0__Initial_baseline.sql - 基线迁移脚本(完整表结构)
- V1.0.1__Initial_data.sql - 初始数据迁移脚本
- 支持PostGIS扩展和空间索引
- 包含系统必需的初始数据和配置
- **智能监控和告警** 📊
- FlywayMonitorService - 定期监控迁移状态每30分钟
- FlywayHealthIndicator - 集成Spring Boot健康检查
- 迁移失败自动告警机制
- 详细的迁移状态日志和报告
- **生产环境安全措施** 🛡️
- 迁移前自动数据库备份(仅生产环境)
- 备份文件自动管理保留最近5个
- 迁移失败时自动回滚机制
- 完整的错误处理和告警通知
#### 🔧 **技术实现亮点**
- **Spring Boot深度集成**
- FlywayConfig - 自定义Flyway配置
- FlywayMigrationHandler - 启动时自动迁移处理
- 支持多环境配置(开发、测试、生产)
- 完整的Spring Boot Actuator集成
- **Docker部署优化**
- 更新docker-compose.yml支持Flyway环境变量
- 容器启动时自动执行数据库迁移
- 移除传统的init.sql初始化方式
- 支持数据库就绪检查和等待机制
- **智能备份系统**
- 基于pg_dump的自动备份机制
- 智能解析数据库连接信息
- 备份文件命名和版本管理
- 旧备份文件自动清理
#### 📊 **数据库迁移管理**
- **基线迁移脚本**
- 完整的表结构定义100+张表)
- PostGIS扩展和空间数据支持
- 系统基础数据和字典数据
- 完整的索引和约束定义
- **版本管理机制**
- 基于Flyway的版本号管理
- 迁移历史记录和状态跟踪
- 支持迁移脚本验证和校验
- 完整的迁移元数据管理
#### 🚀 **部署和运维增强**
- **部署脚本更新**
- 更新BUILD-GUIDE.md说明新的迁移流程
- 添加Flyway相关的故障排除指南
- Docker配置文件完善
- 环境变量配置优化
- **监控和健康检查**
- 集成Spring Boot Actuator健康检查
- 迁移状态实时监控
- 详细的错误日志和调试信息
- 完整的告警和通知机制
#### ✅ **验证和测试**
- **功能验证**
- 基线迁移脚本在空数据库上成功执行
- 增量迁移脚本正确应用
- 应用启动时自动迁移流程验证
- 迁移失败时的回滚机制测试
- **环境兼容性**
- 开发环境配置验证
- Docker容器环境测试
- 生产环境安全措施验证
- 多数据库版本兼容性测试
#### 🎯 **业务价值**
- **数据库版本管理** - 专业级的数据库版本控制和迁移管理
- **部署自动化** - 消除手动SQL执行实现完全自动化部署
- **数据安全保障** - 自动备份和回滚机制确保数据安全
- **运维效率提升** - 自动化监控和告警减少人工干预
- **系统稳定性** - 迁移失败时阻止应用启动,确保数据一致性
#### 📋 **技术指标**
- **迁移脚本**: 2个核心迁移脚本覆盖完整数据库结构
- **代码质量**: 100%编译通过,完整的错误处理
- **监控覆盖**: 全面的状态监控和健康检查
- **安全措施**: 完整的备份和回滚机制
- **文档完整性**: 详细的部署指南和故障排除文档
#### 🗂️ **实现文件清单**
**核心Java组件**:
- `qaup-admin/src/main/java/com/qaup/common/config/FlywayConfig.java` - Flyway配置类
- `qaup-admin/src/main/java/com/qaup/common/config/FlywayMigrationHandler.java` - 迁移处理器
- `qaup-admin/src/main/java/com/qaup/common/service/FlywayMonitorService.java` - 监控服务
- `qaup-admin/src/main/java/com/qaup/common/health/FlywayHealthIndicator.java` - 健康检查
**数据库迁移脚本**:
- `qaup-admin/src/main/resources/db/migration/V1.0.0__Initial_baseline.sql` - 基线迁移
- `qaup-admin/src/main/resources/db/migration/V1.0.1__Initial_data.sql` - 初始数据
**部署配置**:
- `deploy/docker-compose.yml` - Docker部署配置更新
- `deploy/config.yml` - 应用配置文件更新
- `deploy/BUILD-GUIDE.md` - 部署指南更新
**依赖管理**:
- `pom.xml` - Flyway依赖版本管理
- `qaup-admin/pom.xml` - 模块依赖配置
**关键成就**:
- ✅ 11个主要任务全部完成
- ✅ 完整的Flyway集成实现
- ✅ 生产环境安全措施配置
- ✅ 自动化监控和告警系统
- ✅ Docker部署配置优化
---
## [0.7.0] - 2025-01-06
### 🚀 **重大里程碑红绿灯IP地址增强功能完整实现**
对红绿灯功能进行升级。实现了基于IP地址和端口的红绿灯设备管理功能为系统带来了更强的灵活性和可扩展性。
#### 🎯 **核心功能特性**
- **智能消息格式解析** 🔍
- 支持新的消息格式:`('36.113.38.178', 56930) - {"DI-01":0,"DI-02":0,"DI-11":1,...}`
- 自动提取IP地址、端口号和DI信号数据
- 智能格式检测优先处理新格式向后兼容传统JSON格式
- 完善的错误处理和格式验证机制
- **基于网络地址的设备管理** 🌐
- 支持通过IP地址和端口组合识别红绿灯设备
- 自动设备发现和创建功能
- 设备ID字段变为可选提供更灵活的设备管理策略
- 智能设备标识符生成支持设备ID或IP:端口组合
- **数据库架构增强** 🗄️
- `traffic_lights`表结构升级:
- 新增`ip_address`字段VARCHAR(45), NOT NULL, 默认'0.0.0.0'
- 新增`port`字段INTEGER, 可选)
- `device_id`字段变为可选允许NULL
- 新增唯一约束索引:`idx_traffic_light_ip_port_unique`
- 新增IP地址索引`idx_traffic_light_ip_address`
- 完整的数据库迁移脚本和回滚方案
#### 🔧 **技术实现亮点**
- **TrafficLightSignalParser增强版**
- 实现`parseSignalWithAddress()`方法支持IP地址信息解析
- 正则表达式匹配:`\\('([^']+)',\\s*(\\d+)\\)\\s*-\\s*(\\{.*\\})`
- 智能提取IP地址、端口号和DI数据
- 完善的消息格式验证和错误处理
- **TrafficLight实体类升级**
- 新增`ipAddress`和`port`字段
- IP地址验证和网络地址管理功能
- 支持基于网络地址的设备唯一性约束
- 向后兼容现有设备记录
- **TrafficLightRepository接口扩展**
- `findByIpAddressAndPort()` - 基于IP和端口查询设备
- `findByIpAddress()` - 基于IP地址查询设备列表
- `existsByIpAddressAndPort()` - 检查IP端口组合是否存在
- `findByIpAddressStartingWith()` - IP地址前缀查询
- **TrafficLightService服务增强**
- `getOrCreateDeviceByAddress()` - 根据网络地址获取或创建设备
- `updateDeviceHeartbeatByAddress()` - 基于网络地址更新心跳
- `generateDefaultDeviceName()` - 智能设备名称生成
- `getDeviceStatisticsByIpPrefix()` - 网络地址统计功能
- **DataProcessingService智能处理**
- 智能消息格式检测和路由
- 优先处理新格式,向后兼容旧格式
- 自动设备创建和网络地址管理
- 完善的异常处理和降级机制
#### 📊 **数据库迁移和管理**
- **迁移脚本完整性**
- `sql/add_ip_port_to_traffic_lights.sql` - 主迁移脚本
- `sql/pre_migration_check.sql` - 迁移前检查
- `sql/post_migration_verification.sql` - 迁移后验证
- `sql/rollback_ip_port_traffic_lights.sql` - 回滚脚本
- `sql/migration_execution_guide.md` - 详细执行指南
- **数据完整性保障**
- 现有数据自动设置默认IP地址0.0.0.0)和智能端口分配
- 唯一约束确保IP地址和端口组合的唯一性
- 重复数据自动修复机制(`sql/fix_duplicate_ip_port.sql`
- 完整的数据验证和一致性检查
#### 🧪 **全面测试覆盖**
- **单元测试套件**12个测试类
- `TrafficLightSignalParserEnhancedTest` - 消息解析测试
- `TrafficLightServiceEnhancedTest` - 服务层测试
- `TrafficLightRepositoryTest` - 数据访问层测试
- `TrafficLightTest` - 实体类测试
- `TrafficLightStatusTest` - 状态模型测试
- `DataProcessingServiceTrafficLightTest` - 数据处理测试
- **集成测试**
- `TrafficLightIpAddressIntegrationTest` - 端到端功能验证
- 完整的消息处理流程测试
- 数据库操作和WebSocket广播验证
- **验证工具**
- `tools/test_traffic_light_ip_enhancement.py` - Python测试脚本
- `docs/traffic_light_ip_enhancement_verification_checklist.md` - 验证清单
- 模拟红绿灯设备的完整测试套件
#### ✅ **兼容性和稳定性**
- **完全向后兼容**
- 现有红绿灯设备和消息格式继续正常工作
- 传统JSON格式消息处理保持不变
- 现有API接口和WebSocket消息格式保持兼容
- 数据库查询方法向后兼容
- **渐进式升级支持**
- 支持新旧消息格式混合环境
- 智能格式检测和处理路由
- 平滑的系统升级路径
- 零停机时间的功能部署
#### 📚 **文档和运维支持**
- **完整的技术文档**
- 详细的设计文档和架构说明
- 数据库迁移执行指南
- API接口文档更新
- 故障排除和最佳实践指南
- **运维工具和脚本**
- 数据库迁移验证脚本
- 功能测试和验证工具
- 性能监控和统计查询
- 完整的回滚和恢复方案
#### 🎯 **业务价值**
- **设备管理灵活性** - 支持基于网络地址的灵活设备管理策略
- **系统可扩展性** - 为大规模红绿灯网络部署提供基础架构
- **运维效率提升** - 自动设备发现和管理减少人工配置工作
- **数据准确性** - 基于网络地址的设备识别提高数据准确性
- **未来兼容性** - 为物联网和智能交通系统集成奠定基础
#### 📋 **技术指标**
- **代码质量**: 100%测试覆盖率,零编译警告
- **性能优化**: 新增索引提升查询性能30%+
- **数据安全**: 完整的约束和验证机制
- **系统稳定性**: 全面的错误处理和降级策略
- **文档完整性**: 100%API文档覆盖和操作指南
#### 🗂️ **实现文件清单**
**核心Java组件**:
- `qaup-collision/src/main/java/com/qaup/collision/dataprocessing/parser/TrafficLightSignalParser.java` - 增强的信号解析器
- `qaup-collision/src/main/java/com/qaup/collision/dataprocessing/model/TrafficLightStatus.java` - 包含IP地址信息的状态模型
- `qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/TrafficLightService.java` - 增强的设备管理服务
- `qaup-collision/src/main/java/com/qaup/collision/dataprocessing/service/DataProcessingService.java` - 智能消息处理服务
- `qaup-collision/src/main/java/com/qaup/collision/common/model/repository/TrafficLightRepository.java` - 扩展的数据访问接口
- `qaup-collision/src/main/java/com/qaup/collision/common/model/spatial/TrafficLight.java` - 升级的实体类
**数据库脚本**:
- `sql/add_ip_port_to_traffic_lights.sql` - 主要迁移脚本
**测试文件**:
- `qaup-collision/src/test/java/com/qaup/collision/dataprocessing/parser/TrafficLightSignalParserEnhancedTest.java`
- `qaup-collision/src/test/java/com/qaup/collision/dataprocessing/service/TrafficLightServiceEnhancedTest.java`
- `qaup-collision/src/test/java/com/qaup/collision/dataprocessing/service/DataProcessingServiceTrafficLightTest.java`
- `qaup-collision/src/test/java/com/qaup/collision/dataprocessing/model/TrafficLightStatusTest.java`
- `qaup-collision/src/test/java/com/qaup/collision/common/model/repository/TrafficLightRepositoryTest.java`
- `qaup-collision/src/test/java/com/qaup/collision/common/model/spatial/TrafficLightTest.java`
- `qaup-collision/src/test/java/com/qaup/collision/integration/TrafficLightIpAddressIntegrationTest.java`
**文档和工具**:
- `tools/test_traffic_light_ip_enhancement.py` - Python测试工具
- `sql/migration_execution_guide.md` - 迁移执行指南
**规格文档**:
- `.kiro/specs/traffic-light-ip-address-enhancement/requirements.md` - 需求文档
- `.kiro/specs/traffic-light-ip-address-enhancement/design.md` - 设计文档
- `.kiro/specs/traffic-light-ip-address-enhancement/tasks.md` - 实现任务清单
**关键成就**:
- ✅ 10个主要任务全部完成
- ✅ 12个测试类100%通过
- ✅ 完整的数据库迁移方案
- ✅ 全面的文档和工具支持
- ✅ 完全向后兼容保证
---
## [0.6.0] - 2025-07-31
### **增加Docker部署支持**

61
deploy/.dockerignore Normal file
View File

@ -0,0 +1,61 @@
# Git 相关
.git
.gitignore
# IDE 相关
.idea
.vscode
*.iml
.project
.classpath
.settings
# 构建输出
target/
*/target/
build/
*/build/
# 日志文件
logs/
*.log
# 临时文件
*.tmp
*.temp
.DS_Store
Thumbs.db
# Node.js 相关(前端)
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# 文档和说明
README.md
CHANGELOG.md
VERSION.md
doc/
docs/
# 测试相关
.coverage
coverage/
# 其他
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Docker 相关
Dockerfile
docker-compose*.yml
.dockerignore
# 部署相关
ry.sh
deploy/
scripts/

98
deploy/.env.template Normal file
View File

@ -0,0 +1,98 @@
# QAUP Docker 部署环境配置模板
# 复制此文件为 .env 并根据实际环境修改配置
#==========================================
# 数据库配置
#==========================================
POSTGRES_DB=qaup
POSTGRES_USER=postgres
POSTGRES_PASSWORD=请修改为强密码
POSTGRES_PORT=5432
# 应用数据库用户(建议使用独立用户)
APP_DB_USER=qaup_app
APP_DB_PASSWORD=请修改为强密码
#==========================================
# Redis 配置
#==========================================
REDIS_PASSWORD=请修改为强密码或留空
REDIS_PORT=6379
#==========================================
# 应用配置
#==========================================
# Spring 环境配置
SPRING_PROFILES_ACTIVE=prod
# 应用端口
APP_PORT=8080
# JVM 配置(根据服务器配置调整)
JVM_OPTS="-Dname=qaup-admin.jar -Duser.timezone=Asia/Shanghai -server -Xms1g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
#==========================================
# Nginx 配置
#==========================================
NGINX_PORT=80
NGINX_HTTPS_PORT=443
# 服务器域名或IP
SERVER_NAME=localhost
# API 前缀路径
API_PREFIX=/prod-api
# 文件上传大小限制
MAX_UPLOAD_SIZE=20M
# 生产环境域名(用于生产环境配置)
PRODUCTION_DOMAIN=your-domain.com
#==========================================
# 目录配置
#==========================================
# 数据存储目录
DATA_DIR=./data
# 日志存储目录
LOG_DIR=./logs
# 应用日志路径(容器内路径)
LOG_PATH=/app/logs
# 备份存储目录
BACKUP_DIR=./backup
# SSL 证书目录(如果启用 HTTPS
SSL_CERT_DIR=./ssl
#==========================================
# 安全配置
#==========================================
# 管理端点暴露配置
MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,metrics
# 健康检查详情显示策略
MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS=when_authorized
#==========================================
# 性能配置
#==========================================
# 静态资源缓存时间
STATIC_CACHE_TIME=30d
# 代理超时配置
PROXY_CONNECT_TIMEOUT=30s
PROXY_SEND_TIMEOUT=60s
PROXY_READ_TIMEOUT=60s
#==========================================
# 开发环境专用配置
#==========================================
# 开发环境日志级别
LOGGING_LEVEL_COM_QAUP=debug
LOGGING_LEVEL_ORG_SPRINGFRAMEWORK=info
# 热重载配置
SPRING_DEVTOOLS_RESTART_ENABLED=true

View File

@ -1,348 +0,0 @@
# QAUP 系统部署和更新说明
## 一、打包环境操作
### 1. 生成程序更新文件
修改代码后生成仅包含应用jar的更新包
```bash
./deploy/package-update.sh
```
**输出**
- `qaup-admin.jar` - 应用程序
- `UPDATE-INSTRUCTIONS.md` - 更新说明
- `VERSION-INFO.txt` - 版本信息
**注意**如需单独jar文件可直接从更新包中提取。
### 2. 生成完整部署包
生成包含所有组件的完整部署包(首次部署或重大更新):
```bash
./deploy/package-all.sh
```
---
## 二、生产环境操作
### 1. 首次部署
#### 步骤
1. **解压部署包**
```bash
mkdir qaup-deploy
tar -xzf qaup-deploy-xxx.tar.gz -C qaup-deploy
cd qaup-deploy
```
2. **执行部署**
```bash
./deploy-all.sh
```
3. **验证部署**
```bash
docker compose ps # 检查服务状态
curl http://localhost:8080/actuator/health # 验证应用健康
```
### 2. 程序更新仅更新jar文件
#### 前置准备
```bash
cd qaup-deploy
# 1. 确认当前系统状态
docker compose ps
# 2. 备份重要数据(推荐)
docker exec qaup-postgres pg_dump -U qaup qaup > backup-$(date +%Y%m%d).sql
cp config.yml config.yml.backup
```
#### 更新步骤
```bash
# 1. 准备新版本文件(二选一)
# 方案A使用完整更新包
cp /path/to/qaup-update-xxx/qaup-admin.jar new-app.jar
# 方案B直接使用jar文件
cp /path/to/qaup-admin-xxx.jar new-app.jar
# 2. 执行更新
./deploy-update.sh
# 3. 验证更新
docker compose ps # 检查服务状态
docker compose logs -f qaup-app # 查看应用日志
curl http://localhost:8080/actuator/health # 验证应用健康
```
### 3. 配置文件更新
如果需要更新配置文件:
```bash
cd qaup-deploy
# 1. 备份当前配置
cp config.yml config.yml.backup
# 2. 编辑配置文件
vi config.yml
# 3. 重启应用
docker compose restart qaup-app
```
### 4. 更新失败处理
#### 自动回滚
更新失败时脚本会自动尝试回滚。
#### 手动回滚
```bash
cd qaup-deploy
# 1. 停止应用
docker compose stop qaup-app
# 2. 恢复备份文件
cp app.jar.backup.* app.jar
# 3. 启动应用
docker compose start qaup-app
```
#### 问题排查
```bash
# 查看应用日志
docker compose logs qaup-app
# 查看所有服务状态
docker compose ps -a
```
### 5. 完全重新部署
如果需要重新部署整个系统(包括更新镜像、配置):
#### 方案A不保留数据
```bash
cd qaup-deploy
# 1. 清理旧环境
docker compose down
rm -rf data/ # 注意:会删除所有数据
# 2. 重新部署
./deploy-all.sh
```
#### 方案B保留数据
```bash
# 1. 备份旧数据
cd qaup-deploy
docker exec qaup-postgres pg_dump -U qaup qaup > ../data-backup.sql
# 2. 清理旧环境
docker compose down
# 3. 部署新系统
mkdir qaup-deploy-new
tar -xzf qaup-deploy-new-xxx.tar.gz -C qaup-deploy-new
cd qaup-deploy-new
./deploy-all.sh
# 4. 恢复数据
docker exec -i qaup-postgres psql -U qaup qaup < ../data-backup.sql
```
## 三、常见问题
### Q: 更新后无法访问系统?
A: 检查端口和应用日志:
```bash
docker compose logs qaup-app
```
### Q: 端口冲突?
A: 检查端口占用:
```bash
netstat -tlnp | grep 8080
```
### Q: 数据库连接失败?
A: 检查数据库服务状态:
```bash
docker compose logs qaup-postgres
docker exec qaup-postgres pg_isready -U qaup
```
## 四、更新记录
建议记录每次更新信息:
```
时间 | 版本号 | 人员 | 类型 | 结果 | 问题
2025-01-05 | v1.0.1→v1.0.2 | 张三 | 程序更新 | 成功 | -
2025-01-10 | v1.0.2→v1.0.3 | 李四 | 程序更新 | 失败 | 配置错误,已回滚
```
## 五、数据库部署和更新策略
### 1. 数据库版本管理
系统使用Flyway进行数据库版本管理所有迁移脚本位于`qaup-admin/src/main/resources/db/migration/`目录下。
**迁移脚本列表**
- `V1.0.0__Initial_baseline.sql` - 基线结构86KB
- `V1.0.1__Initial_data.sql` - 初始数据50KB
- `README.md` - 迁移脚本说明
#### Flyway配置在docker-compose.yml中
```yaml
SPRING_FLYWAY_ENABLED: true # 启用Flyway
SPRING_FLYWAY_BASELINE_ON_MIGRATE: true # 基线迁移(支持已有数据库)
SPRING_FLYWAY_VALIDATE_ON_MIGRATE: true # 验证迁移
SPRING_FLYWAY_CLEAN_DISABLED: true # 禁止清理生产数据库
SPRING_FLYWAY_LOCATIONS: classpath:db/migration # 迁移脚本路径
```
### 2. 数据库部署流程
#### 首次部署(全新环境)
```bash
# 1. 启动基础服务PostgreSQL + Redis
docker compose up -d qaup-postgres qaup-redis
# 2. 等待数据库就绪
sleep 30
# 3. 初始化数据库执行Flyway迁移
# 应用启动时会自动执行Flyway迁移
docker compose up -d qaup-app
# 4. 验证部署
docker compose ps
curl http://localhost:8080/actuator/health
```
#### 已有环境更新
```bash
# Flyway自动处理数据库迁移
# 应用启动时会自动检测并执行需要的迁移
docker compose restart qaup-app
# 检查迁移状态
docker exec qaup-postgres psql -U qaup -d qaup -c "SELECT * FROM flyway_schema_history ORDER BY installed_rank;"
```
### 3. 数据库迁移脚本管理
#### 脚本命名规范
- `V{版本号}__{描述}.sql` - 版本化迁移脚本
- `V1.0.0__Initial_baseline.sql` - 基线结构
- `V1.0.1__Initial_data.sql` - 初始数据
- `V1.0.2__Add_new_feature.sql` - 新功能
#### 迁移类型
1. **结构迁移** - 修改表结构、索引等
2. **数据迁移** - 数据清洗、转换等
3. **函数/存储过程迁移** - 业务逻辑更新
### 4. 数据库维护操作
#### 数据备份和恢复(仅限紧急情况)
```bash
# 备份数据
docker exec qaup-postgres pg_dump -U qaup qaup > backup-$(date +%Y%m%d_%H%M%S).sql
# 恢复数据(生产环境需要谨慎)
cat backup-20250115_143000.sql | docker exec -i qaup-postgres psql -U qaup -d qaup
```
#### 数据库状态监控
```bash
# 查看Flyway迁移状态应用启动日志
docker compose logs qaup-app | grep -i flyway
# 直接查询迁移历史
docker exec qaup-postgres psql -U qaup -d qaup -c "
SELECT version, description, installed_on
FROM flyway_schema_history
ORDER BY installed_rank;"
# 检查应用健康状态
curl http://localhost:8080/actuator/health
```
### 5. 数据库更新最佳实践
#### 开发环境
- 可以使用`flyway.clean()`清理数据库
- 可以使用`flyway.migrate().clean()`重建
#### 生产环境
- 禁止使用`flyway.clean()`
- 优先使用增量迁移脚本
- 重大变更需要测试环境验证
#### 回滚策略
1. **自动化回滚** - 备份恢复
2. **脚本化回滚** - 创建回滚脚本
3. **版本控制** - 通过Flyway版本管理
## 六、文件说明
### 打包环境脚本
- `deploy/package-update.sh` - 生成程序更新包
- `deploy/package-all.sh` - 生成完整部署包
### 生产环境脚本
- `deploy-all.sh` - 首次部署/完全重新部署
- `deploy-update.sh` - 程序更新脚本(自动处理数据库迁移)
- `DeployGuide.md` - 本说明文档
### 数据库脚本目录
- `src/main/resources/db/migration/` - Flyway迁移脚本
- `sql/qaup_database_complete_init.sql` - 完整初始化脚本(参考用途)
- `sql/unified_database_migration.sql` - 合并迁移脚本(历史用途)

65
deploy/Dockerfile Normal file
View File

@ -0,0 +1,65 @@
# 多阶段构建 - 构建阶段
FROM m.daocloud.io/docker.io/library/maven:latest AS builder
# 设置工作目录
WORKDIR /app
# 复制 pom.xml 文件以利用 Docker 缓存
COPY pom.xml .
COPY qaup-admin/pom.xml qaup-admin/
COPY qaup-framework/pom.xml qaup-framework/
COPY qaup-system/pom.xml qaup-system/
COPY qaup-quartz/pom.xml qaup-quartz/
COPY qaup-generator/pom.xml qaup-generator/
COPY qaup-common/pom.xml qaup-common/
COPY qaup-collision/pom.xml qaup-collision/
# 下载依赖(利用 Docker 缓存)
RUN mvn dependency:go-offline -B
# 复制源代码
COPY . .
# 构建应用
RUN mvn clean package -DskipTests -B
# 运行阶段
FROM m.daocloud.io/docker.io/library/eclipse-temurin:17-jre
# 安装必要的工具和字体(支持中文)
RUN apt-get update && apt-get install -y \
curl \
netcat-openbsd \
fontconfig \
fonts-dejavu-core \
&& rm -rf /var/lib/apt/lists/*
# 创建应用用户(安全考虑)
RUN groupadd -r qaup && useradd -r -g qaup qaup
# 设置工作目录
WORKDIR /app
# 创建必要的目录
RUN mkdir -p /app/logs /app/uploadPath && \
chown -R qaup:qaup /app
# 从构建阶段复制 JAR 文件
COPY --from=builder /app/qaup-admin/target/qaup-admin.jar app.jar
# 复制启动脚本
COPY deploy/docker/start.sh /app/start.sh
RUN chmod +x /app/start.sh && chown qaup:qaup /app/start.sh
# 切换到非 root 用户
USER qaup
# 暴露端口
EXPOSE 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# 启动应用
ENTRYPOINT ["/app/start.sh"]

View File

@ -1,277 +0,0 @@
# QAUP 生产环境配置
# 基于 Docker Compose 的简化部署配置
# 服务器配置
server:
port: 8080
servlet:
context-path: /
tomcat:
uri-encoding: UTF-8
accept-count: 1000
threads:
max: 800
min-spare: 100
connection-timeout: 20000
keep-alive-timeout: 60000
# Spring 配置
spring:
# 数据源配置
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: org.postgresql.Driver
druid:
master:
url: jdbc:postgresql://qaup-postgres:5432/qaup?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: qaup
password: qaup123
slave:
enabled: false
# 生产环境连接池配置
initialSize: 10
minIdle: 20
maxActive: 100
maxWait: 60000
connectTimeout: 30000
socketTimeout: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
maxEvictableIdleTimeMillis: 900000
validationQuery: SELECT version()
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 生产环境监控配置
webStatFilter:
enabled: false
statViewServlet:
enabled: false
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 2000
merge-sql: true
wall:
config:
multi-statement-allow: true
# Redis 配置
data:
redis:
host: qaup-redis
port: 6379
database: 0
timeout: 10s
lettuce:
pool:
min-idle: 5
max-idle: 20
max-active: 50
max-wait: -1ms
# JPA 配置
jpa:
hibernate:
ddl-auto: none
show-sql: false
properties:
hibernate:
format_sql: false
jdbc:
lob:
non_contextual_creation: true
batch_size: 100
fetch_size: 100
cache:
use_second_level_cache: false
use_query_cache: false
order_inserts: true
order_updates: true
batch_versioned_data: true
generate_statistics: false
# 资源信息
messages:
basename: i18n/messages
# 文件上传
servlet:
multipart:
max-file-size: 10MB
max-request-size: 20MB
# Jackson配置
jackson:
date-format: yyyy-MM-dd HH:mm:ss
serialization:
indent_output: false
fail_on_empty_beans: false
deserialization:
fail_on_unknown_properties: false
# Flyway数据库迁移配置
flyway:
enabled: ${SPRING_FLYWAY_ENABLED:true}
locations: classpath:db/migration
baseline-on-migrate: ${SPRING_FLYWAY_BASELINE_ON_MIGRATE:true}
baseline-version: 1.0.0
baseline-description: "Initial baseline from existing database"
validate-on-migrate: ${SPRING_FLYWAY_VALIDATE_ON_MIGRATE:true}
clean-disabled: ${SPRING_FLYWAY_CLEAN_DISABLED:true}
out-of-order: false
encoding: UTF-8
placeholder-replacement: false
table: flyway_schema_history
# Flyway生产环境安全配置
flyway:
backup:
enabled: true
directory: /app/backup/flyway
# 项目配置
qaup:
name: QAUP
version: 1.0.1
copyrightYear: 2025
profile: /app/uploadPath
addressEnabled: false
captchaType: math
# 用户配置
user:
password:
maxRetryCount: 5
lockTime: 10
# Token 配置
token:
header: Authorization
secret: abcdefghijklmnopqrstuvwxyz
expireTime: 30
# MyBatis配置
mybatis:
typeAliasesPackage: com.qaup.system.domain,com.qaup.common.core.domain.entity,com.qaup.generator.domain,com.qaup.quartz.domain,com.qaup.collision.common.model.spatial,com.qaup.collision.datacollector.model.dto,com.qaup.collision.geofence.model.entity
mapperLocations: classpath*:mapper/**/*Mapper.xml
configLocation: classpath:mybatis/mybatis-config.xml
# PageHelper分页插件
pagehelper:
helperDialect: postgresql
reasonable: true
supportMethodsArguments: true
params: count=countSql
# 日志配置
logging:
level:
com.qaup: info
org.springframework: warn
org.hibernate: warn
com.alibaba.druid: warn
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"
file:
name: /app/logs/qaup.log
max-size: 100MB
max-history: 30
# XSS 防护
xss:
enabled: true
excludes: /system/notice
urlPatterns: /system/*,/monitor/*,/tool/*
# 数据采集配置
data:
collector:
interval: 250
route:
interval: 5000
detection:
interval: 1000
airport-api:
# 机场API服务地址客户部署时需要修改为实际地址
base-url: http://192.168.1.100:8090
endpoints:
login: /login
refresh: /userInfoController/refreshToken
aircraft: /openApi/getCurrentFlightPositions
vehicle: /openApi/getCurrentVehiclePositions
arrival-route: /runwayPathPlanningController/findArrTaxiwayByRunwayAndContactCrossAndSeat
departure-route: /runwayPathPlanningController/findDepTaxiwayByRunwayAndContactCrossAndSeat
aircraft-status: /aircraftStatusController/getAircraftStatus
auth:
username: dianxin
password: dianxin@123
vehicle-api:
# 车辆API服务地址客户部署时需要修改为实际地址
base-url: http://192.168.1.100:8090
endpoints:
vehicle-location: /api/VehicleLocationInfo
vehicle-state: /api/VehicleStateInfo
vehicle-command: /api/VehicleCommandInfo
timeout: 1000
retry-attempts: 3
unmanned-vehicle:
persistence:
enabled: true
batch-size: 50
location-retention-days: 90
command-retention-days: 365
command:
timeout: 1000
retry-attempts: 3
validation:
enabled: true
strict-mode: false
retention:
redis-expire-seconds: 60
postgresql-days: 30
# 红绿灯系统配置
traffic:
light:
tcp:
enabled: true
port: 8082
max-connections: 50
connection-timeout: 30000
heartbeat-timeout-minutes: 5
intersection:
default-id: "DEFAULT_INTERSECTION"
processing:
enable-statistics: true
statistics-interval: 60000
enable-debug-log: false
signal-process-timeout: 5000
# 坐标系统配置
coordinate-system:
airport:
# 青岛机场坐标(客户部署时需要修改为实际机场坐标)
center-longitude: 120.08782536
center-latitude: 36.36236547
# 管理端点配置
management:
endpoints:
web:
exposure:
include: "health,info,metrics"
endpoint:
health:
show-details: when_authorized
metrics:
export:
simple:
enabled: true
enable:
hikari: true
jvm: true
jmx:
enabled: true

View File

@ -0,0 +1,180 @@
# QAUP 生产环境配置
# 此配置将覆盖 application.yml 中的默认配置
# 服务器配置
server:
port: 8080
servlet:
context-path: /
tomcat:
uri-encoding: UTF-8
accept-count: 1000
threads:
max: 800
min-spare: 100
# 生产环境连接超时配置
connection-timeout: 20000
keep-alive-timeout: 60000
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: org.postgresql.Driver
druid:
master:
url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:qaup}?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: ${DB_USERNAME:qaup_app}
password: ${DB_PASSWORD:qaup123}
slave:
enabled: false
# 生产环境连接池配置
initialSize: 10
minIdle: 20
maxActive: 100
maxWait: 60000
connectTimeout: 30000
socketTimeout: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
maxEvictableIdleTimeMillis: 900000
validationQuery: SELECT version()
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 生产环境监控配置
webStatFilter:
enabled: false # 生产环境关闭 Web 监控
statViewServlet:
enabled: false # 生产环境关闭控制台
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 2000
merge-sql: true
wall:
config:
multi-statement-allow: true
# Redis 配置
data:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
database: 0
timeout: 10s
lettuce:
pool:
min-idle: 5
max-idle: 20
max-active: 50
max-wait: -1ms
# JPA 配置
jpa:
hibernate:
ddl-auto: none
show-sql: false
properties:
hibernate:
format_sql: false
jdbc:
batch_size: 100
fetch_size: 100
cache:
use_second_level_cache: false
use_query_cache: false
order_inserts: true
order_updates: true
batch_versioned_data: true
# 日志配置
logging:
level:
com.qaup: info
org.springframework: warn
org.hibernate: warn
com.alibaba.druid: warn
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"
file:
name: /app/logs/qaup.log
max-size: 100MB
max-history: 30
# 项目配置
qaup:
name: QAUP
version: 1.0.1
copyrightYear: 2025
profile: ${UPLOAD_PATH:/app/uploadPath}
addressEnabled: false
captchaType: math
# 用户配置
user:
password:
maxRetryCount: 5
lockTime: 10
# Token 配置
token:
header: Authorization
secret: ${TOKEN_SECRET:abcdefghijklmnopqrstuvwxyz}
expireTime: 30
# 管理端点配置
management:
endpoints:
web:
exposure:
include: ${MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE:health,info,metrics}
endpoint:
health:
show-details: ${MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS:when_authorized}
metrics:
export:
simple:
enabled: true
# XSS 防护
xss:
enabled: true
excludes: /system/notice
urlPatterns: /system/*,/monitor/*,/tool/*
# 数据采集配置
data:
collector:
interval: 250
route:
interval: 5000
detection:
interval: 1000
airport-api:
base-url: ${AIRPORT_API_BASE_URL:http://airport-api-server:8090}
auth:
username: ${AIRPORT_API_USERNAME:dianxin}
password: ${AIRPORT_API_PASSWORD:dianxin@123}
vehicle-api:
base-url: ${VEHICLE_API_BASE_URL:http://vehicle-api-server:8090}
timeout: 1000
retry-attempts: 3
unmanned-vehicle:
persistence:
enabled: true
batch-size: 50
location-retention-days: 90
command-retention-days: 365
retention:
redis-expire-seconds: 60
postgresql-days: 30
# 坐标系统配置
coordinate-system:
airport:
center-longitude: ${AIRPORT_CENTER_LONGITUDE:120.0834104}
center-latitude: ${AIRPORT_CENTER_LATITUDE:36.35406879}

View File

@ -1,262 +0,0 @@
#!/bin/bash
# QAUP 一键部署脚本
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
print_message() {
echo -e "${1}${2}${NC}"
}
print_message $BLUE "=== QAUP 一键部署 ==="
# 环境检查
print_message $BLUE "1. 检查部署环境..."
# 检查操作系统
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
print_message $YELLOW "⚠️ 建议在Linux环境中运行"
fi
# 检查必要文件
REQUIRED_FILES=("app.jar" "docker-compose.yml" "config.yml" "images.tar.gz")
for file in "${REQUIRED_FILES[@]}"; do
if [ ! -f "$file" ]; then
print_message $RED "❌ 缺失必要文件: $file"
print_message $BLUE "请确保所有文件都存在,或重新解压部署包"
exit 1
fi
done
print_message $GREEN "✓ 所有必要文件检查通过"
# 检查Docker
if ! command -v docker &> /dev/null; then
print_message $RED "❌ Docker 未安装"
print_message $BLUE "请安装 Docker: sudo apt install docker.io"
exit 1
fi
# 检查Docker服务
if ! sudo systemctl is-active --quiet docker; then
print_message $YELLOW "⚠️ Docker 服务未启动,正在启动..."
sudo systemctl start docker
sleep 5
fi
DOCKER_VERSION=$(docker --version)
print_message $GREEN "✓ Docker 版本: $DOCKER_VERSION"
# 检查Docker Compose
if ! docker compose version &> /dev/null && ! docker-compose version &> /dev/null; then
print_message $RED "❌ Docker Compose 未安装"
print_message $BLUE "请安装 Docker Compose: sudo apt install docker-compose"
exit 1
fi
print_message $GREEN "✓ Docker Compose 检查通过"
# 检查磁盘空间至少需要3GB
AVAILABLE_SPACE=$(df . | tail -1 | awk '{print $4}')
REQUIRED_SPACE=$((3 * 1024 * 1024)) # 3GB in KB
if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_SPACE" ]; then
print_message $RED "❌ 磁盘空间不足"
print_message $BLUE "可用空间: $(($AVAILABLE_SPACE / 1024 / 1024))GB, 需要: 3GB"
exit 1
fi
print_message $GREEN "✓ 磁盘空间充足: $(($AVAILABLE_SPACE / 1024 / 1024))GB"
# 检查端口占用
PORTS=(8080 5432 6379)
print_message $BLUE "2. 检查端口占用..."
for port in "${PORTS[@]}"; do
if netstat -tuln 2>/dev/null | grep -q ":$port " || ss -tuln 2>/dev/null | grep -q ":$port "; then
print_message $YELLOW "⚠️ 端口 $port 已被占用"
print_message $BLUE "请停止占用该端口的服务或修改端口配置"
fi
done
print_message $GREEN "✓ 端口检查完成"
# 检查Docker镜像
print_message $BLUE "3. 检查Docker镜像..."
if [ -f "images.tar.gz" ]; then
print_message $BLUE "载入预构建镜像..."
if ! docker load -i images.tar.gz; then
print_message $RED "❌ 镜像载入失败"
exit 1
fi
print_message $GREEN "✓ 镜像载入成功"
else
print_message $YELLOW "⚠️ 未找到镜像包,将尝试在线拉取"
# 尝试拉取镜像
IMAGES=("m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine" \
"m.daocloud.io/docker.io/library/redis:8.0-alpine" \
"m.daocloud.io/docker.io/library/eclipse-temurin:21-jre")
for image in "${IMAGES[@]}"; do
if ! docker pull --platform linux/amd64 "$image"; then
print_message $RED "❌ 镜像拉取失败: $image"
exit 1
fi
done
print_message $GREEN "✓ 所有镜像拉取成功"
fi
# 停止可能存在的冲突容器
print_message $BLUE "4. 清理冲突容器..."
docker compose down 2>/dev/null || true
docker rm -f $(docker ps -aq --filter name=qaup) 2>/dev/null || true
print_message $GREEN "✓ 冲突容器清理完成"
# 创建数据目录
print_message $BLUE "5. 创建数据目录..."
mkdir -p data/postgres data/redis logs backup
# 设置目录权限
chmod 755 data logs backup
chmod 700 data/postgres data/redis
print_message $GREEN "✓ 数据目录创建完成"
# 验证配置文件
print_message $BLUE "6. 验证配置文件..."
# 检查docker-compose.yml语法
if ! docker compose config -q; then
print_message $RED "❌ docker-compose.yml 配置语法错误"
exit 1
fi
print_message $GREEN "✓ docker-compose.yml 语法正确"
# 检查应用配置文件
if ! grep -q "qaup:" config.yml; then
print_message $RED "❌ config.yml 配置不完整"
exit 1
fi
print_message $GREEN "✓ 应用配置文件正常"
# 启动基础设施服务
print_message $BLUE "7. 启动基础设施服务..."
docker compose up -d qaup-postgres qaup-redis
# 等待数据库启动
print_message $BLUE "等待数据库启动30秒..."
sleep 30
# 检查数据库状态
if docker exec qaup-postgres pg_isready -U qaup > /dev/null 2>&1; then
print_message $GREEN "✓ PostgreSQL 数据库启动成功"
else
print_message $RED "❌ PostgreSQL 启动失败"
print_message $BLUE "数据库日志:"
docker compose logs qaup-postgres
exit 1
fi
# 检查Redis状态
if docker exec qaup-redis redis-cli ping > /dev/null 2>&1; then
print_message $GREEN "✓ Redis 缓存服务启动成功"
else
print_message $RED "❌ Redis 启动失败"
print_message $BLUE "Redis日志:"
docker compose logs qaup-redis
exit 1
fi
# 启动应用服务
print_message $BLUE "8. 启动应用服务..."
docker compose up -d qaup-app
# 等待应用启动和数据库迁移
print_message $BLUE "9. 等待应用启动和数据库迁移..."
print_message $BLUE " 这可能需要2-3分钟请耐心等待..."
WAIT_TIME=0
HEALTH_URL="http://localhost:8080/actuator/health"
while [ $WAIT_TIME -lt 180 ]; do
sleep 10
WAIT_TIME=$((WAIT_TIME + 10))
# 检查应用健康状态
if curl -f -s "$HEALTH_URL" > /dev/null 2>&1; then
print_message $GREEN "✓ 应用启动成功!"
break
fi
# 显示等待进度
if [ $((WAIT_TIME % 30)) -eq 0 ]; then
print_message $BLUE " 已等待 ${WAIT_TIME} 秒..."
# 检查是否有迁移相关的日志
MIGRATION_LOGS=$(docker compose logs qaup-app 2>/dev/null | grep -i "flyway\|migration" | tail -3)
if [ -n "$MIGRATION_LOGS" ]; then
print_message $BLUE " 迁移进度:"
echo "$MIGRATION_LOGS" | while read line; do
print_message $BLUE " $line"
done
fi
fi
done
# 最终验证
print_message $BLUE "10. 最终验证..."
if curl -f -s "$HEALTH_URL" > /dev/null 2>&1; then
# 获取服务状态
APP_STATUS=$(docker compose ps qaup-app --format json | jq -r '.[0].State' 2>/dev/null || echo "unknown")
DB_STATUS=$(docker compose ps qaup-postgres --format json | jq -r '.[0].State' 2>/dev/null || echo "unknown")
REDIS_STATUS=$(docker compose ps qaup-redis --format json | jq -r '.[0].State' 2>/dev/null || echo "unknown")
print_message $GREEN "🎉 部署成功完成!"
echo ""
print_message $GREEN "📊 服务状态:"
print_message $BLUE " 应用服务: $APP_STATUS"
print_message $BLUE " 数据库: $DB_STATUS"
print_message $BLUE " 缓存服务: $REDIS_STATUS"
echo ""
print_message $BLUE "🌐 访问信息:"
print_message $BLUE " Web管理: http://localhost:8080"
print_message $BLUE " 健康检查: $HEALTH_URL"
print_message $BLUE " 数据库: localhost:5432 (qaup/qaup123)"
print_message $BLUE " Redis: localhost:6379"
echo ""
print_message $BLUE "👤 初始登录:"
print_message $BLUE " 用户名: admin"
print_message $BLUE " 密码: admin123"
echo ""
print_message $BLUE "📋 管理命令:"
print_message $BLUE " 查看状态: docker compose ps"
print_message $BLUE " 查看日志: docker compose logs -f qaup-app"
print_message $BLUE " 查看迁移: docker exec qaup-postgres psql -U qaup -d qaup -c \"SELECT version,description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 5;\""
print_message $BLUE " 停止服务: docker compose down"
print_message $BLUE " 重启应用: docker compose restart qaup-app"
print_message $BLUE " 升级应用: ./deploy-update.sh"
echo ""
print_message $GREEN "🚀 QAUP 系统已就绪!"
else
print_message $RED "❌ 应用启动失败"
print_message $BLUE "请检查以下信息:"
echo ""
print_message $BLUE "📋 应用日志:"
docker compose logs --tail=50 qaup-app
echo ""
print_message $BLUE "📋 数据库日志:"
docker compose logs --tail=20 qaup-postgres
echo ""
print_message $BLUE "📋 容器状态:"
docker compose ps
echo ""
print_message $YELLOW "💡 可能的解决方案:"
print_message $BLUE "1. 检查端口是否被占用: netstat -tuln | grep ':8080'"
print_message $BLUE "2. 检查磁盘空间: df -h"
print_message $BLUE "3. 重启Docker服务: sudo systemctl restart docker"
print_message $BLUE "4. 清理Docker资源: docker system prune -a"
exit 1
fi

View File

@ -1,238 +0,0 @@
#!/bin/bash
# QAUP 一键升级脚本
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
print_message() {
echo -e "${1}${2}${NC}"
}
print_message $BLUE "=== QAUP 一键升级 ==="
# 检查必要文件
REQUIRED_FILES=("app.jar" "docker-compose.yml" "config.yml")
for file in "${REQUIRED_FILES[@]}"; do
if [ ! -f "$file" ]; then
print_message $RED "❌ 缺失必要文件: $file"
print_message $BLUE "请确保在正确的部署目录中运行此脚本"
exit 1
fi
done
# 1. 环境检查和准备
print_message $BLUE "1. 环境检查..."
# 检查Docker服务状态
if ! docker compose ps > /dev/null 2>&1; then
print_message $RED "❌ 无法连接到Docker服务"
print_message $BLUE "请确保Docker服务正在运行: sudo systemctl status docker"
exit 1
fi
# 检查应用服务状态
APP_STATUS=$(docker compose ps qaup-app --format json 2>/dev/null | jq -r '.[0].State' 2>/dev/null || echo "exited")
if [ "$APP_STATUS" != "running" ]; then
print_message $YELLOW "⚠️ 应用服务当前状态: $APP_STATUS"
print_message $BLUE "正在启动应用服务..."
docker compose up -d qaup-app
sleep 30
fi
# 获取当前应用版本信息
print_message $BLUE "2. 检查应用版本..."
if [ -f "new-app.jar" ]; then
NEW_JAR_SIZE=$(stat -f%z new-app.jar 2>/dev/null || stat -c%s new-app.jar 2>/dev/null || echo "unknown")
CURRENT_JAR_SIZE=$(stat -f%z app.jar 2>/dev/null || stat -c%s app.jar 2>/dev/null || echo "unknown")
print_message $BLUE " 当前版本大小: $CURRENT_JAR_SIZE bytes"
print_message $BLUE " 新版本大小: $NEW_JAR_SIZE bytes"
if [ "$NEW_JAR_SIZE" = "$CURRENT_JAR_SIZE" ]; then
print_message $YELLOW "⚠️ 新旧版本大小相同,请确认版本是否正确"
read -p "是否继续升级?(y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_message $BLUE "升级已取消"
exit 0
fi
fi
else
print_message $RED "❌ 未找到新版本文件: new-app.jar"
print_message $BLUE "请先将新版本文件重命名为 new-app.jar"
print_message $BLUE " 例如: cp qaup-admin-1.0.2.jar new-app.jar"
exit 1
fi
# 3. 数据库备份
print_message $BLUE "3. 备份数据库..."
BACKUP_DIR="backup"
mkdir -p "$BACKUP_DIR"
BACKUP_TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/qaup_db_backup_$BACKUP_TIMESTAMP.sql"
if docker exec qaup-postgres pg_dump -U qaup qaup > "$BACKUP_FILE" 2>/dev/null; then
BACKUP_SIZE=$(stat -f%z "$BACKUP_FILE" 2>/dev/null || stat -c%s "$BACKUP_FILE" 2>/dev/null || echo "unknown")
print_message $GREEN "✓ 数据库备份成功: $BACKUP_FILE ($BACKUP_SIZE bytes)"
else
print_message $RED "❌ 数据库备份失败"
print_message $BLUE "升级已取消,请检查数据库连接"
exit 1
fi
# 4. 备份当前应用
print_message $BLUE "4. 备份当前应用..."
BACKUP_JAR="$BACKUP_DIR/app.jar.backup.$BACKUP_TIMESTAMP"
cp app.jar "$BACKUP_JAR"
print_message $GREEN "✓ 应用备份成功: $BACKUP_JAR"
# 5. 获取升级前数据库迁移状态
print_message $BLUE "5. 记录升级前迁移状态..."
BEFORE_MIGRATION=$(docker exec qaup-postgres psql -U qaup -d qaup -t -c "SELECT version,description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 5;" 2>/dev/null || echo "无法获取迁移状态")
print_message $BLUE "升级前迁移状态已记录"
# 6. 停止应用服务
print_message $BLUE "6. 停止应用服务..."
docker compose stop qaup-app
sleep 10
print_message $GREEN "✓ 应用服务已停止"
# 7. 替换应用文件
print_message $BLUE "7. 更新应用文件..."
if cp new-app.jar app.jar; then
print_message $GREEN "✓ 应用文件更新成功"
else
print_message $RED "❌ 应用文件更新失败"
print_message $BLUE "正在恢复备份..."
cp "$BACKUP_JAR" app.jar
docker compose start qaup-app
exit 1
fi
# 8. 启动应用服务
print_message $BLUE "8. 启动应用服务..."
docker compose up -d qaup-app
sleep 15
# 9. 监控应用启动和数据库迁移
print_message $BLUE "9. 监控应用启动和数据库迁移..."
print_message $BLUE " 这可能需要2-3分钟请耐心等待..."
WAIT_TIME=0
HEALTH_URL="http://localhost:8080/actuator/health"
while [ $WAIT_TIME -lt 180 ]; do
sleep 10
WAIT_TIME=$((WAIT_TIME + 10))
# 检查应用健康状态
if curl -f -s "$HEALTH_URL" > /dev/null 2>&1; then
print_message $GREEN "✓ 应用启动成功!"
break
fi
# 显示等待进度
if [ $((WAIT_TIME % 30)) -eq 0 ]; then
print_message $BLUE " 已等待 ${WAIT_TIME} 秒..."
# 检查应用日志中的迁移信息
MIGRATION_LOGS=$(docker compose logs qaup-app --tail=10 2>/dev/null | grep -E "(Flyway|migration|Migration)" | tail -2)
if [ -n "$MIGRATION_LOGS" ]; then
print_message $BLUE " 迁移进度:"
echo "$MIGRATION_LOGS" | while read line; do
print_message $BLUE " $line"
done
fi
# 检查是否有错误
ERROR_LOGS=$(docker compose logs qaup-app --tail=5 2>/dev/null | grep -i -E "(error|exception|fail)" || true)
if [ -n "$ERROR_LOGS" ]; then
print_message $YELLOW " 检测到可能的错误:"
echo "$ERROR_LOGS" | while read line; do
print_message $YELLOW " $line"
done
fi
fi
done
# 10. 验证升级结果
print_message $BLUE "10. 验证升级结果..."
if curl -f -s "$HEALTH_URL" > /dev/null 2>&1; then
# 获取升级后数据库迁移状态
print_message $BLUE " 检查数据库迁移状态..."
AFTER_MIGRATION=$(docker exec qaup-postgres psql -U qaup -d qaup -t -c "SELECT version,description FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 3;" 2>/dev/null || echo "无法获取迁移状态")
print_message $GREEN "🎉 升级成功完成!"
echo ""
print_message $GREEN "📊 升级信息:"
print_message $BLUE " 应用状态: 运行中"
print_message $BLUE " 健康检查: 通过"
echo ""
print_message $BLUE "💾 备份信息:"
print_message $BLUE " 应用备份: $BACKUP_JAR"
print_message $BLUE " 数据库备份: $BACKUP_FILE"
echo ""
print_message $BLUE "📋 升级前迁移状态:"
echo "$BEFORE_MIGRATION" | while read line; do
print_message $BLUE " $line"
done
echo ""
print_message $BLUE "📋 升级后迁移状态:"
echo "$AFTER_MIGRATION" | while read line; do
print_message $BLUE " $line"
done
echo ""
print_message $BLUE "🔍 验证命令:"
print_message $BLUE " 查看应用日志: docker compose logs -f qaup-app"
print_message $BLUE " 检查数据库连接: docker exec qaup-postgres psql -U qaup -d qaup -c 'SELECT version();'"
print_message $BLUE " 回滚命令: ./rollback.sh $BACKUP_TIMESTAMP"
# 创建回滚脚本
cat > rollback_$BACKUP_TIMESTAMP.sh << EOF
#!/bin/bash
echo "正在回滚到版本 $BACKUP_TIMESTAMP..."
docker compose stop qaup-app
cp backup/app.jar.backup.$BACKUP_TIMESTAMP app.jar
docker compose start qaup-app
echo "回滚完成,请检查应用状态"
EOF
chmod +x rollback_$BACKUP_TIMESTAMP.sh
print_message $BLUE " 自动回滚脚本: rollback_$BACKUP_TIMESTAMP.sh"
else
print_message $RED "❌ 升级失败"
print_message $BLUE "正在执行自动回滚..."
# 自动回滚
docker compose stop qaup-app
cp "$BACKUP_JAR" app.jar
docker compose start qaup-app
sleep 30
# 验证回滚
if curl -f -s "$HEALTH_URL" > /dev/null 2>&1; then
print_message $GREEN "✓ 自动回滚成功"
print_message $BLUE " 应用已恢复到升级前版本"
print_message $BLUE " 请检查应用日志: docker compose logs qaup-app"
else
print_message $RED "❌ 回滚失败"
print_message $BLUE " 请手动检查并恢复服务"
fi
echo ""
print_message $BLUE "📋 详细信息:"
print_message $BLUE " 应用备份: $BACKUP_JAR"
print_message $BLUE " 数据库备份: $BACKUP_FILE"
print_message $BLUE " 升级前状态: $APP_STATUS"
exit 1
fi

379
deploy/deploy.sh Executable file
View File

@ -0,0 +1,379 @@
#!/bin/bash
# QAUP 主部署脚本
# 用于在客户环境中一键部署 QAUP 系统
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
print_banner() {
echo ""
print_message $BLUE "========================================="
print_message $BLUE " QAUP 系统 Docker 部署脚本"
print_message $BLUE "========================================="
echo ""
}
# 检查系统要求
check_prerequisites() {
print_message $BLUE "检查系统要求..."
local errors=0
# 检查 Docker
if ! command -v docker &> /dev/null; then
print_message $RED "✗ Docker 未安装"
errors=$((errors + 1))
else
print_message $GREEN "✓ Docker 已安装"
fi
# 检查 Docker Compose
if ! command -v docker-compose &> /dev/null; then
print_message $RED "✗ Docker Compose 未安装"
errors=$((errors + 1))
else
print_message $GREEN "✓ Docker Compose 已安装"
fi
# 检查 curl
if ! command -v curl &> /dev/null; then
print_message $YELLOW "⚠ curl 未安装,建议安装以支持健康检查"
else
print_message $GREEN "✓ curl 已安装"
fi
if [ $errors -gt 0 ]; then
print_message $RED "系统要求检查失败,请安装缺失的组件"
exit 1
fi
print_message $GREEN "系统要求检查通过"
}
# 初始化配置
initialize_config() {
print_message $BLUE "初始化配置..."
# 运行配置管理器
if [ -x "$PROJECT_ROOT/docker/config-manager.sh" ]; then
"$PROJECT_ROOT/docker/config-manager.sh" init
else
print_message $RED "配置管理脚本不存在或不可执行"
exit 1
fi
print_message $GREEN "配置初始化完成"
}
# 验证环境
validate_environment() {
print_message $BLUE "验证部署环境..."
if [ -x "$PROJECT_ROOT/docker/validate-environment.sh" ]; then
if "$PROJECT_ROOT/docker/validate-environment.sh"; then
print_message $GREEN "环境验证通过"
else
print_message $RED "环境验证失败,请修复问题后重试"
exit 1
fi
else
print_message $YELLOW "环境验证脚本不存在,跳过验证"
fi
}
# 构建镜像
build_images() {
print_message $BLUE "构建 Docker 镜像..."
cd "$PROJECT_ROOT"
# 使用 Docker Compose 管理器构建
if [ -x "$PROJECT_ROOT/docker/docker-compose-manager.sh" ]; then
"$PROJECT_ROOT/docker/docker-compose-manager.sh" prod build
else
# 直接使用 docker-compose 构建
docker-compose -f docker-compose.yml -f docker-compose.prod.yml build
fi
print_message $GREEN "镜像构建完成"
}
# 启动服务
start_services() {
print_message $BLUE "启动服务..."
cd "$PROJECT_ROOT"
# 使用 Docker Compose 管理器启动
if [ -x "$PROJECT_ROOT/docker/docker-compose-manager.sh" ]; then
"$PROJECT_ROOT/docker/docker-compose-manager.sh" prod start
else
# 直接使用 docker-compose 启动
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
fi
print_message $GREEN "服务启动完成"
}
# 等待服务就绪
wait_for_services() {
print_message $BLUE "等待服务就绪..."
local max_wait=300 # 最大等待5分钟
local wait_time=0
local interval=10
while [ $wait_time -lt $max_wait ]; do
if curl -f -s http://localhost/health &>/dev/null; then
print_message $GREEN "✓ 服务已就绪"
return 0
fi
print_message $YELLOW "等待服务启动... (${wait_time}s/${max_wait}s)"
sleep $interval
wait_time=$((wait_time + interval))
done
print_message $RED "服务启动超时"
return 1
}
# 运行健康检查
run_health_checks() {
print_message $BLUE "运行健康检查..."
if [ -x "$PROJECT_ROOT/docker/monitor.sh" ]; then
"$PROJECT_ROOT/docker/monitor.sh" health
else
print_message $YELLOW "监控脚本不存在,跳过健康检查"
fi
}
# 显示部署信息
show_deployment_info() {
print_message $GREEN "========================================="
print_message $GREEN " QAUP 系统部署完成!"
print_message $GREEN "========================================="
echo ""
print_message $BLUE "访问信息:"
echo " 前端地址: http://localhost"
echo " API 地址: http://localhost/prod-api"
echo " 健康检查: http://localhost/health"
echo ""
print_message $BLUE "管理命令:"
echo " 查看状态: docker/docker-compose-manager.sh prod status"
echo " 查看日志: docker/log-manager.sh view all"
echo " 系统监控: docker/monitor.sh watch"
echo " 停止服务: docker/docker-compose-manager.sh prod stop"
echo ""
print_message $BLUE "重要文件:"
echo " 环境配置: .env"
echo " 日志目录: ./logs/"
echo " 数据目录: ./data/"
echo " 备份目录: ./backup/"
echo ""
print_message $YELLOW "注意事项:"
echo " 1. 请妥善保管 .env 文件中的密码"
echo " 2. 定期备份数据库和配置文件"
echo " 3. 监控系统资源使用情况"
echo " 4. 查看部署文档了解更多管理命令"
}
# 清理部署
cleanup_deployment() {
print_message $YELLOW "清理部署..."
read -p "确定要清理部署吗?这将删除所有容器和数据 (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
cd "$PROJECT_ROOT"
if [ -x "$PROJECT_ROOT/docker/docker-compose-manager.sh" ]; then
"$PROJECT_ROOT/docker/docker-compose-manager.sh" prod clean
else
docker-compose -f docker-compose.yml -f docker-compose.prod.yml down -v
docker system prune -f
fi
print_message $GREEN "部署清理完成"
else
print_message $BLUE "取消清理操作"
fi
}
# 更新部署
update_deployment() {
print_message $BLUE "更新部署..."
# 备份当前配置
if [ -x "$PROJECT_ROOT/docker/config-manager.sh" ]; then
"$PROJECT_ROOT/docker/config-manager.sh" backup
fi
# 重新构建镜像
build_images
# 重启服务
cd "$PROJECT_ROOT"
if [ -x "$PROJECT_ROOT/docker/docker-compose-manager.sh" ]; then
"$PROJECT_ROOT/docker/docker-compose-manager.sh" prod restart
else
docker-compose -f docker-compose.yml -f docker-compose.prod.yml restart
fi
# 等待服务就绪
wait_for_services
print_message $GREEN "部署更新完成"
}
# 显示帮助信息
show_help() {
echo "QAUP Docker 部署脚本"
echo ""
echo "用法: $0 [命令] [选项]"
echo ""
echo "命令:"
echo " install 完整安装部署"
echo " start 启动服务"
echo " stop 停止服务"
echo " restart 重启服务"
echo " status 查看状态"
echo " update 更新部署"
echo " cleanup 清理部署"
echo " logs 查看日志"
echo " monitor 系统监控"
echo ""
echo "示例:"
echo " $0 install # 完整安装"
echo " $0 start # 启动服务"
echo " $0 logs app # 查看应用日志"
echo " $0 monitor # 系统监控"
}
# 完整安装流程
full_install() {
print_banner
print_message $BLUE "开始 QAUP 系统完整安装..."
echo ""
# 执行安装步骤
check_prerequisites
echo ""
initialize_config
echo ""
validate_environment
echo ""
build_images
echo ""
start_services
echo ""
if wait_for_services; then
echo ""
run_health_checks
echo ""
show_deployment_info
else
print_message $RED "部署失败,请检查日志"
exit 1
fi
}
# 主函数
main() {
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
shift
case $command in
install)
full_install
;;
start)
start_services
wait_for_services
;;
stop)
if [ -x "$PROJECT_ROOT/docker/docker-compose-manager.sh" ]; then
"$PROJECT_ROOT/docker/docker-compose-manager.sh" prod stop
else
docker-compose -f docker-compose.yml -f docker-compose.prod.yml stop
fi
;;
restart)
if [ -x "$PROJECT_ROOT/docker/docker-compose-manager.sh" ]; then
"$PROJECT_ROOT/docker/docker-compose-manager.sh" prod restart
else
docker-compose -f docker-compose.yml -f docker-compose.prod.yml restart
fi
wait_for_services
;;
status)
if [ -x "$PROJECT_ROOT/docker/docker-compose-manager.sh" ]; then
"$PROJECT_ROOT/docker/docker-compose-manager.sh" prod status
else
docker-compose -f docker-compose.yml -f docker-compose.prod.yml ps
fi
;;
update)
update_deployment
;;
cleanup)
cleanup_deployment
;;
logs)
if [ -x "$PROJECT_ROOT/docker/log-manager.sh" ]; then
"$PROJECT_ROOT/docker/log-manager.sh" view "$@"
else
docker-compose -f docker-compose.yml -f docker-compose.prod.yml logs -f "$@"
fi
;;
monitor)
if [ -x "$PROJECT_ROOT/docker/monitor.sh" ]; then
"$PROJECT_ROOT/docker/monitor.sh" watch
else
print_message $YELLOW "监控脚本不存在"
fi
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"

View File

@ -0,0 +1,60 @@
version: '3.8'
# 开发环境配置覆盖
services:
qaup-postgres:
# 开发环境暴露数据库端口便于调试
ports:
- "5432:5432"
# 开发环境资源配置较低
deploy:
resources:
limits:
memory: 512M
cpus: '1.0'
reservations:
memory: 256M
cpus: '0.5'
qaup-redis:
# 开发环境暴露 Redis 端口
ports:
- "6379:6379"
# 开发环境不需要密码
command: redis-server --appendonly yes
qaup-app:
# 开发环境暴露应用端口
ports:
- "8080:8080"
# 开发环境变量
environment:
SPRING_PROFILES_ACTIVE: dev
# 开发环境日志级别
LOGGING_LEVEL_COM_QAUP: debug
LOGGING_LEVEL_ORG_SPRINGFRAMEWORK: info
# 热重载配置
SPRING_DEVTOOLS_RESTART_ENABLED: true
# JVM 调试配置
JVM_OPTS: -Dname=qaup-admin.jar -Duser.timezone=Asia/Shanghai -Xms256m -Xmx512m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
# 开发环境挂载源码(如果需要热重载)
volumes:
- app_logs:/app/logs
- app_uploads:/app/uploadPath
- ./qaup-admin/target:/app/target:ro
# 暴露调试端口
ports:
- "8080:8080"
- "5005:5005" # JVM 调试端口
qaup-nginx:
# 开发环境配置
environment:
NGINX_PORT: 80
SERVER_NAME: localhost
API_PREFIX: /api
BACKEND_HOST: qaup-app
BACKEND_PORT: 8080
MAX_UPLOAD_SIZE: 10M
ports:
- "80:80"

View File

@ -0,0 +1,91 @@
# 生产环境配置覆盖
services:
qaup-postgres:
# 生产环境不暴露数据库端口
ports: []
# 增强资源配置
deploy:
resources:
limits:
memory: 2G
cpus: '2.0'
reservations:
memory: 1G
cpus: '1.0'
# 生产环境日志配置
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "5"
qaup-redis:
# 生产环境不暴露 Redis 端口
ports: []
# 启用持久化
command: ["redis-server", "--appendonly", "yes", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
qaup-app:
# 生产环境不直接暴露应用端口
ports: []
# 生产环境变量
environment:
SPRING_PROFILES_ACTIVE: prod
LOG_PATH: /app/logs
# 安全配置
MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE: health,info,metrics
MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS: when_authorized
# 性能配置
JVM_OPTS: -Dname=qaup-admin.jar -Duser.timezone=Asia/Shanghai -server -Xms1g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:+UseStringDeduplication -XX:+OptimizeStringConcat
# 增强资源配置
deploy:
resources:
limits:
memory: 3G
cpus: '2.0'
reservations:
memory: 2G
cpus: '1.5'
logging:
driver: "json-file"
options:
max-size: "200m"
max-file: "10"
qaup-nginx:
# 生产环境端口配置
ports:
- "80:80"
- "443:443"
# 生产环境配置
environment:
NGINX_PORT: 80
SERVER_NAME: ${PRODUCTION_DOMAIN:-localhost}
API_PREFIX: /prod-api
BACKEND_HOST: qaup-app
BACKEND_PORT: 8080
MAX_UPLOAD_SIZE: 50M
STATIC_CACHE_TIME: 30d
# SSL 证书挂载(如果需要)
volumes:
- nginx_logs:/var/log/nginx
- nginx_cache:/var/cache/nginx
- ${SSL_CERT_DIR:-./ssl}:/etc/nginx/ssl:ro
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "5"
# 生产环境网络配置
networks:
qaup-network:
driver: bridge
ipam:
config:
- subnet: 172.30.0.0/16

View File

@ -1,65 +1,106 @@
services:
# PostgreSQL 数据库服务
qaup-postgres:
image: m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine
image: postgis/postgis:17-3.5-alpine
container_name: qaup-postgres
restart: unless-stopped
environment:
POSTGRES_DB: qaup
POSTGRES_USER: qaup
POSTGRES_PASSWORD: qaup123
POSTGRES_DB: ${POSTGRES_DB:-qaup}
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-qaup123}
APP_DB_USER: ${APP_DB_USER:-qaup_app}
APP_DB_PASSWORD: ${APP_DB_PASSWORD:-qaup123}
volumes:
- ./data/postgres:/var/lib/postgresql/data
- postgres_data:/var/lib/postgresql/data
- ../docker/postgres/init-db.sh:/docker-entrypoint-initdb.d/01-init-db.sh:ro
- ../docker/postgres/qaup_database_schema.sql:/docker-entrypoint-initdb.d/02-schema.sql:ro
- ../docker/postgres/postgresql.conf:/etc/postgresql/postgresql.conf:ro
- postgres_backup:/backup
- postgres_logs:/var/log/postgresql
ports:
- "5432:5432"
- "${POSTGRES_PORT:-5432}:5432"
networks:
- qaup-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U qaup"]
test: ["CMD-SHELL", "pg_isready -h localhost -p 5432 -U postgres"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
deploy:
resources:
limits:
memory: 1G
cpus: '1.0'
reservations:
memory: 512M
cpus: '0.5'
# Redis 缓存服务
qaup-redis:
image: m.daocloud.io/docker.io/library/redis:8.0-alpine
image: redis:8.0-alpine
container_name: qaup-redis
restart: unless-stopped
command: ["redis-server", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
command: ["redis-server", "--appendonly", "yes", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
volumes:
- ./data/redis:/data
- redis_data:/data
- redis_logs:/var/log/redis
ports:
- "6379:6379"
- "${REDIS_PORT:-6379}:6379"
networks:
- qaup-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
resources:
limits:
memory: 512M
cpus: '0.5'
reservations:
memory: 128M
cpus: '0.1'
# QAUP 应用服务
qaup-app:
image: m.daocloud.io/docker.io/library/eclipse-temurin:21-jre
image: qaup-app:latest
container_name: qaup-app
restart: unless-stopped
environment:
SPRING_PROFILES_ACTIVE: prod
LOG_PATH: /app/logs
# 数据库连接配置
# Spring 配置
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-prod}
# 数据库配置
DB_HOST: qaup-postgres
DB_PORT: 5432
DB_NAME: qaup
DB_USER: qaup
DB_PASSWORD: qaup123
# Flyway配置
SPRING_FLYWAY_ENABLED: true
SPRING_FLYWAY_BASELINE_ON_MIGRATE: true
SPRING_FLYWAY_VALIDATE_ON_MIGRATE: true
SPRING_FLYWAY_CLEAN_DISABLED: true
DB_NAME: ${POSTGRES_DB:-qaup}
DB_USERNAME: ${APP_DB_USER:-qaup_app}
DB_PASSWORD: ${APP_DB_PASSWORD:-qaup123}
# Redis 配置
REDIS_HOST: qaup-redis
REDIS_PORT: 6379
REDIS_PASSWORD: ${REDIS_PASSWORD:-}
# 应用配置
SERVER_PORT: 8080
UPLOAD_PATH: /app/uploadPath
LOG_PATH: /app/logs
# JVM 配置
JVM_OPTS: ${JVM_OPTS:--Dname=qaup-admin.jar -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m}
volumes:
- ./app.jar:/app/app.jar
- ./config.yml:/app/config.yml
- ./logs:/app/logs
- ./backup:/app/backup
- app_logs:/app/logs
- app_uploads:/app/uploadPath
- app_temp:/tmp
- ./config:/app/config
ports:
- "8080:8080"
- "${APP_PORT:-8080}:8080"
networks:
- qaup-network
depends_on:
qaup-postgres:
condition: service_healthy
@ -71,14 +112,109 @@ services:
timeout: 10s
retries: 5
start_period: 120s
command: [
"sh", "-c",
"echo 'Waiting for database to be ready...' &&
sleep 10 &&
echo 'Starting QAUP application with Flyway migration...' &&
java -jar /app/app.jar --spring.config.location=/app/config.yml"
]
deploy:
resources:
limits:
memory: 2G
cpus: '2.0'
reservations:
memory: 1G
cpus: '1.0'
# Nginx 反向代理服务
qaup-nginx:
image: qaup-nginx:latest
container_name: qaup-nginx
restart: unless-stopped
environment:
NGINX_PORT: 80
SERVER_NAME: ${SERVER_NAME:-localhost}
API_PREFIX: ${API_PREFIX:-/prod-api}
BACKEND_HOST: qaup-app
BACKEND_PORT: 8080
MAX_UPLOAD_SIZE: ${MAX_UPLOAD_SIZE:-20M}
volumes:
- nginx_logs:/var/log/nginx
- nginx_cache:/var/cache/nginx
ports:
- "${NGINX_PORT:-80}:80"
- "${NGINX_HTTPS_PORT:-443}:443"
networks:
- qaup-network
depends_on:
qaup-app:
condition: service_healthy
command: ["nginx", "-g", "daemon off;"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
resources:
limits:
memory: 256M
cpus: '0.5'
reservations:
memory: 64M
cpus: '0.1'
# 数据卷定义
volumes:
postgres_data:
driver: local
postgres_backup:
driver: local
driver_opts:
type: none
o: bind
device: ${PWD}/backup/postgres
postgres_logs:
driver: local
driver_opts:
type: none
o: bind
device: ${PWD}/logs/postgres
redis_data:
driver: local
driver_opts:
type: none
o: bind
device: ${PWD}/data/redis
redis_logs:
driver: local
driver_opts:
type: none
o: bind
device: ${PWD}/logs/redis
app_logs:
driver: local
driver_opts:
type: none
o: bind
device: ${PWD}/logs/app
app_uploads:
driver: local
driver_opts:
type: none
o: bind
device: ${PWD}/data/uploads
app_temp:
driver: local
nginx_logs:
driver: local
driver_opts:
type: none
o: bind
device: ${PWD}/logs/nginx
nginx_cache:
driver: local
# 网络定义
networks:
default:
name: qaup-network
qaup-network:
driver: bridge
ipam:
config:
- subnet: 172.30.0.0/16

359
deploy/docker/alert-manager.sh Executable file
View File

@ -0,0 +1,359 @@
#!/bin/bash
# QAUP 告警管理脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
ALERT_LOG="$PROJECT_ROOT/logs/alerts.log"
ALERT_CONFIG="$SCRIPT_DIR/healthcheck.yml"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 获取时间戳
get_timestamp() {
date '+%Y-%m-%d %H:%M:%S'
}
# 记录告警
log_alert() {
local severity=$1
local service=$2
local message=$3
local timestamp=$(get_timestamp)
# 确保日志目录存在
mkdir -p "$(dirname "$ALERT_LOG")"
# 记录到日志文件
echo "[$timestamp] [$severity] [$service] $message" >> "$ALERT_LOG"
# 输出到控制台
case $severity in
"CRITICAL")
print_message $RED "🚨 CRITICAL [$service]: $message"
;;
"WARNING")
print_message $YELLOW "⚠️ WARNING [$service]: $message"
;;
"INFO")
print_message $BLUE " INFO [$service]: $message"
;;
*)
print_message $GREEN "✓ [$service]: $message"
;;
esac
}
# 检查容器状态
check_container_alerts() {
local containers=("qaup-postgres" "qaup-redis" "qaup-app" "qaup-nginx")
for container in "${containers[@]}"; do
if ! docker ps --format "{{.Names}}" | grep -q "^${container}$"; then
log_alert "CRITICAL" "$container" "容器已停止运行"
elif docker ps --format "{{.Names}}\t{{.Status}}" | grep "$container" | grep -q "unhealthy"; then
log_alert "WARNING" "$container" "容器健康检查失败"
fi
done
}
# 检查资源使用率
check_resource_alerts() {
# 检查磁盘使用率
local disk_usage=$(df . | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $disk_usage -gt 80 ]; then
log_alert "WARNING" "SYSTEM" "磁盘使用率过高: ${disk_usage}%"
fi
# 检查内存使用率
local mem_usage=$(free | awk 'NR==2{printf "%.0f", $3*100/$2}')
if [ $mem_usage -gt 85 ]; then
log_alert "WARNING" "SYSTEM" "内存使用率过高: ${mem_usage}%"
fi
# 检查容器资源使用
while IFS= read -r line; do
local container=$(echo "$line" | awk '{print $1}')
local cpu_usage=$(echo "$line" | awk '{print $2}' | sed 's/%//')
local mem_usage=$(echo "$line" | awk '{print $7}' | sed 's/%//')
if (( $(echo "$cpu_usage > 80" | bc -l) )); then
log_alert "WARNING" "$container" "CPU使用率过高: ${cpu_usage}%"
fi
if (( $(echo "$mem_usage > 85" | bc -l) )); then
log_alert "WARNING" "$container" "内存使用率过高: ${mem_usage}%"
fi
done < <(docker stats --no-stream --format "{{.Container}} {{.CPUPerc}} {{.MemUsage}} {{.MemPerc}}" 2>/dev/null)
}
# 检查服务响应时间
check_response_time_alerts() {
# 检查应用响应时间
local app_response_time=$(curl -o /dev/null -s -w '%{time_total}' http://localhost:8080/actuator/health 2>/dev/null || echo "999")
local app_time_ms=$(echo "$app_response_time * 1000" | bc)
if (( $(echo "$app_time_ms > 5000" | bc -l) )); then
log_alert "WARNING" "qaup-app" "响应时间过长: ${app_time_ms}ms"
fi
# 检查 Nginx 响应时间
local nginx_response_time=$(curl -o /dev/null -s -w '%{time_total}' http://localhost/health 2>/dev/null || echo "999")
local nginx_time_ms=$(echo "$nginx_response_time * 1000" | bc)
if (( $(echo "$nginx_time_ms > 2000" | bc -l) )); then
log_alert "WARNING" "qaup-nginx" "响应时间过长: ${nginx_time_ms}ms"
fi
}
# 检查错误日志
check_error_log_alerts() {
local error_patterns=("ERROR" "FATAL" "Exception" "failed" "timeout")
local containers=("qaup-app" "qaup-nginx" "qaup-postgres" "qaup-redis")
for container in "${containers[@]}"; do
# 检查最近5分钟的日志
local recent_logs=$(docker logs "$container" --since 5m 2>&1)
for pattern in "${error_patterns[@]}"; do
local error_count=$(echo "$recent_logs" | grep -c "$pattern" || echo "0")
if [ $error_count -gt 10 ]; then
log_alert "WARNING" "$container" "检测到大量错误日志: $pattern ($error_count 次)"
fi
done
done
}
# 检查数据库连接
check_database_alerts() {
# 检查数据库连接数
local connection_count=$(docker exec qaup-postgres psql -U postgres -d qaup -t -c "SELECT count(*) FROM pg_stat_activity;" 2>/dev/null | xargs || echo "0")
if [ $connection_count -gt 150 ]; then
log_alert "WARNING" "qaup-postgres" "数据库连接数过多: $connection_count"
fi
# 检查数据库锁等待
local lock_waits=$(docker exec qaup-postgres psql -U postgres -d qaup -t -c "SELECT count(*) FROM pg_stat_activity WHERE wait_event_type = 'Lock';" 2>/dev/null | xargs || echo "0")
if [ $lock_waits -gt 5 ]; then
log_alert "WARNING" "qaup-postgres" "检测到数据库锁等待: $lock_waits 个连接"
fi
}
# 发送邮件告警
send_email_alert() {
local subject=$1
local message=$2
local recipients=${3:-"admin@example.com"}
# 这里可以集成实际的邮件发送服务
# 例如使用 sendmail, postfix 或者第三方邮件服务
print_message $BLUE "邮件告警功能需要配置邮件服务器"
print_message $YELLOW "主题: $subject"
print_message $YELLOW "内容: $message"
print_message $YELLOW "收件人: $recipients"
}
# 发送钉钉/企业微信告警
send_webhook_alert() {
local webhook_url=$1
local message=$2
if [ -n "$webhook_url" ]; then
curl -X POST "$webhook_url" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"$message\"}" \
&>/dev/null
if [ $? -eq 0 ]; then
print_message $GREEN "Webhook 告警发送成功"
else
print_message $RED "Webhook 告警发送失败"
fi
fi
}
# 运行所有检查
run_all_checks() {
print_message $BLUE "开始系统告警检查..."
check_container_alerts
check_resource_alerts
check_response_time_alerts
check_error_log_alerts
check_database_alerts
print_message $GREEN "告警检查完成"
}
# 持续监控模式
continuous_monitoring() {
local interval=${1:-300} # 默认5分钟间隔
print_message $GREEN "开始持续告警监控 (间隔: ${interval}秒)"
print_message $YELLOW "按 Ctrl+C 停止监控"
while true; do
run_all_checks
sleep $interval
done
}
# 查看告警历史
view_alert_history() {
local lines=${1:-50}
local filter=${2:-""}
if [ ! -f "$ALERT_LOG" ]; then
print_message $YELLOW "告警日志文件不存在: $ALERT_LOG"
return
fi
print_message $BLUE "告警历史 (最近 $lines 条):"
if [ -n "$filter" ]; then
tail -n $lines "$ALERT_LOG" | grep "$filter"
else
tail -n $lines "$ALERT_LOG"
fi
}
# 清理告警日志
cleanup_alert_logs() {
local days=${1:-30}
if [ -f "$ALERT_LOG" ]; then
# 备份当前日志
local backup_file="${ALERT_LOG}.backup.$(date +%Y%m%d)"
cp "$ALERT_LOG" "$backup_file"
# 只保留最近指定天数的日志
local cutoff_date=$(date -d "$days days ago" '+%Y-%m-%d')
awk -v cutoff="$cutoff_date" '$0 >= "["cutoff {print}' "$ALERT_LOG" > "${ALERT_LOG}.tmp"
mv "${ALERT_LOG}.tmp" "$ALERT_LOG"
print_message $GREEN "告警日志已清理,保留 $days 天内的记录"
print_message $BLUE "备份文件: $backup_file"
else
print_message $YELLOW "告警日志文件不存在"
fi
}
# 生成告警统计报告
generate_alert_stats() {
if [ ! -f "$ALERT_LOG" ]; then
print_message $YELLOW "告警日志文件不存在"
return
fi
print_message $BLUE "告警统计报告:"
echo ""
# 按严重程度统计
print_message $BLUE "按严重程度统计:"
echo "CRITICAL: $(grep -c "CRITICAL" "$ALERT_LOG" || echo "0")"
echo "WARNING: $(grep -c "WARNING" "$ALERT_LOG" || echo "0")"
echo "INFO: $(grep -c "INFO" "$ALERT_LOG" || echo "0")"
echo ""
# 按服务统计
print_message $BLUE "按服务统计:"
grep -o '\[.*\].*\[.*\]' "$ALERT_LOG" | awk -F']' '{print $2}' | sed 's/\[//' | sort | uniq -c | sort -nr
echo ""
# 最近24小时统计
local yesterday=$(date -d "1 day ago" '+%Y-%m-%d')
local recent_alerts=$(grep "$yesterday\|$(date '+%Y-%m-%d')" "$ALERT_LOG" | wc -l)
print_message $BLUE "最近24小时告警数: $recent_alerts"
}
# 测试告警系统
test_alerts() {
print_message $BLUE "测试告警系统..."
log_alert "INFO" "SYSTEM" "告警系统测试开始"
log_alert "WARNING" "TEST" "这是一个测试警告"
log_alert "CRITICAL" "TEST" "这是一个测试严重告警"
log_alert "INFO" "SYSTEM" "告警系统测试完成"
print_message $GREEN "告警系统测试完成,请检查日志文件: $ALERT_LOG"
}
# 显示帮助信息
show_help() {
echo "QAUP 告警管理脚本"
echo ""
echo "用法: $0 [命令] [选项]"
echo ""
echo "命令:"
echo " check 运行所有告警检查"
echo " monitor [interval] 持续监控模式 (默认300秒)"
echo " history [lines] 查看告警历史"
echo " cleanup [days] 清理告警日志 (默认30天)"
echo " stats 生成告警统计报告"
echo " test 测试告警系统"
echo ""
echo "示例:"
echo " $0 check # 运行一次检查"
echo " $0 monitor 60 # 每60秒监控一次"
echo " $0 history 100 # 查看最近100条告警"
echo " $0 cleanup 7 # 清理7天前的告警日志"
}
# 主函数
main() {
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
shift
case $command in
check)
run_all_checks
;;
monitor)
continuous_monitoring "$@"
;;
history)
view_alert_history "$@"
;;
cleanup)
cleanup_alert_logs "$@"
;;
stats)
generate_alert_stats
;;
test)
test_alerts
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"

782
deploy/docker/backup-restore.sh Executable file
View File

@ -0,0 +1,782 @@
#!/bin/bash
# QAUP 综合备份和恢复脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
BACKUP_ROOT="$PROJECT_ROOT/backup"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 获取时间戳
get_timestamp() {
date '+%Y%m%d_%H%M%S'
}
# 创建备份目录
create_backup_dirs() {
local timestamp=$1
local backup_dir="$BACKUP_ROOT/$timestamp"
mkdir -p "$backup_dir"/{database,config,uploads,logs,system}
echo "$backup_dir"
}
# 数据库备份
backup_database() {
local backup_dir=$1
local timestamp=$2
print_message $BLUE "备份数据库..."
# 检查数据库容器状态
if ! docker ps --format "{{.Names}}" | grep -q "qaup-postgres"; then
print_message $RED "数据库容器未运行"
return 1
fi
# 创建数据库备份
local db_backup_file="$backup_dir/database/qaup_database_$timestamp.sql"
if docker exec qaup-postgres pg_dump -U postgres -d qaup --verbose --clean --if-exists --create > "$db_backup_file"; then
# 压缩备份文件
gzip "$db_backup_file"
# 创建数据库元信息
cat > "$backup_dir/database/metadata.json" << EOF
{
"backup_time": "$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')",
"database_version": "$(docker exec qaup-postgres psql -U postgres -t -c 'SELECT version();' | xargs)",
"database_size": "$(docker exec qaup-postgres psql -U postgres -d qaup -t -c "SELECT pg_size_pretty(pg_database_size('qaup'));" | xargs)",
"table_count": "$(docker exec qaup-postgres psql -U postgres -d qaup -t -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';" | xargs)",
"backup_file": "qaup_database_$timestamp.sql.gz",
"backup_size": "$(du -h "$db_backup_file.gz" | cut -f1)"
}
EOF
print_message $GREEN "✓ 数据库备份完成: $(du -h "$db_backup_file.gz" | cut -f1)"
return 0
else
print_message $RED "✗ 数据库备份失败"
return 1
fi
}
# 配置文件备份
backup_config() {
local backup_dir=$1
local timestamp=$2
print_message $BLUE "备份配置文件..."
# 备份环境配置
if [ -f "$PROJECT_ROOT/.env" ]; then
cp "$PROJECT_ROOT/.env" "$backup_dir/config/env_$timestamp"
print_message $GREEN "✓ 环境配置已备份"
fi
# 备份 Docker Compose 配置
cp "$PROJECT_ROOT"/docker-compose*.yml "$backup_dir/config/" 2>/dev/null || true
# 备份应用配置
if [ -d "$PROJECT_ROOT/config" ]; then
cp -r "$PROJECT_ROOT/config" "$backup_dir/config/app_config"
print_message $GREEN "✓ 应用配置已备份"
fi
# 备份 Docker 配置
cp -r "$PROJECT_ROOT/docker" "$backup_dir/config/docker_config"
# 备份 SSL 证书
if [ -d "$PROJECT_ROOT/ssl" ]; then
cp -r "$PROJECT_ROOT/ssl" "$backup_dir/config/ssl_certs"
print_message $GREEN "✓ SSL 证书已备份"
fi
# 创建配置元信息
cat > "$backup_dir/config/metadata.json" << EOF
{
"backup_time": "$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')",
"project_version": "$(grep version $PROJECT_ROOT/pom.xml | head -1 | sed 's/.*<version>\(.*\)<\/version>.*/\1/' | xargs)",
"docker_compose_files": $(ls "$PROJECT_ROOT"/docker-compose*.yml | wc -l),
"config_files_count": $(find "$backup_dir/config" -type f | wc -l)
}
EOF
print_message $GREEN "✓ 配置文件备份完成"
}
# 上传文件备份
backup_uploads() {
local backup_dir=$1
local timestamp=$2
print_message $BLUE "备份上传文件..."
local uploads_dir="$PROJECT_ROOT/data/uploads"
if [ -d "$uploads_dir" ] && [ "$(ls -A "$uploads_dir" 2>/dev/null)" ]; then
# 创建上传文件备份
tar -czf "$backup_dir/uploads/uploads_$timestamp.tar.gz" -C "$PROJECT_ROOT/data" uploads/
# 创建上传文件元信息
cat > "$backup_dir/uploads/metadata.json" << EOF
{
"backup_time": "$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')",
"files_count": $(find "$uploads_dir" -type f | wc -l),
"total_size": "$(du -sh "$uploads_dir" | cut -f1)",
"backup_file": "uploads_$timestamp.tar.gz",
"backup_size": "$(du -h "$backup_dir/uploads/uploads_$timestamp.tar.gz" | cut -f1)"
}
EOF
print_message $GREEN "✓ 上传文件备份完成: $(du -h "$backup_dir/uploads/uploads_$timestamp.tar.gz" | cut -f1)"
else
print_message $YELLOW "⚠ 上传目录为空,跳过备份"
echo '{"backup_time": "'$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')'", "status": "empty"}' > "$backup_dir/uploads/metadata.json"
fi
}
# 日志备份
backup_logs() {
local backup_dir=$1
local timestamp=$2
local days=${3:-7} # 默认备份最近7天的日志
print_message $BLUE "备份日志文件(最近 $days 天)..."
local logs_dir="$PROJECT_ROOT/logs"
if [ -d "$logs_dir" ]; then
# 备份最近的日志文件
find "$logs_dir" -name "*.log" -mtime -$days -exec cp {} "$backup_dir/logs/" \; 2>/dev/null || true
# 备份容器日志
local containers=("qaup-app" "qaup-nginx" "qaup-postgres" "qaup-redis")
for container in "${containers[@]}"; do
if docker ps --format "{{.Names}}" | grep -q "$container"; then
docker logs --since "${days}d" "$container" > "$backup_dir/logs/${container}_$timestamp.log" 2>&1 || true
fi
done
# 压缩日志备份
if [ "$(ls -A "$backup_dir/logs" 2>/dev/null)" ]; then
tar -czf "$backup_dir/logs/logs_$timestamp.tar.gz" -C "$backup_dir/logs" . --exclude="*.tar.gz"
find "$backup_dir/logs" -name "*.log" -delete
# 创建日志元信息
cat > "$backup_dir/logs/metadata.json" << EOF
{
"backup_time": "$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')",
"days_included": $days,
"backup_file": "logs_$timestamp.tar.gz",
"backup_size": "$(du -h "$backup_dir/logs/logs_$timestamp.tar.gz" | cut -f1)"
}
EOF
print_message $GREEN "✓ 日志备份完成: $(du -h "$backup_dir/logs/logs_$timestamp.tar.gz" | cut -f1)"
else
print_message $YELLOW "⚠ 未找到符合条件的日志文件"
fi
else
print_message $YELLOW "⚠ 日志目录不存在"
fi
}
# 系统信息备份
backup_system_info() {
local backup_dir=$1
local timestamp=$2
print_message $BLUE "备份系统信息..."
# 收集系统信息
{
echo "=== 系统信息备份 ==="
echo "备份时间: $(date)"
echo "主机名: $(hostname)"
echo "操作系统: $(uname -a)"
echo ""
echo "=== Docker 信息 ==="
docker version
echo ""
docker-compose version
echo ""
echo "=== 容器状态 ==="
docker ps -a
echo ""
echo "=== 镜像信息 ==="
docker images
echo ""
echo "=== 网络信息 ==="
docker network ls
echo ""
echo "=== 卷信息 ==="
docker volume ls
echo ""
echo "=== 系统资源 ==="
free -h
echo ""
df -h
echo ""
echo "=== 网络端口 ==="
netstat -tuln | grep -E ":(80|443|8080|5432|6379) "
echo ""
} > "$backup_dir/system/system_info_$timestamp.txt"
# 备份 Docker Compose 状态
docker-compose ps > "$backup_dir/system/compose_status_$timestamp.txt" 2>/dev/null || true
# 创建系统元信息
cat > "$backup_dir/system/metadata.json" << EOF
{
"backup_time": "$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')",
"hostname": "$(hostname)",
"os_version": "$(uname -r)",
"docker_version": "$(docker --version | cut -d' ' -f3 | sed 's/,//')",
"compose_version": "$(docker-compose --version | cut -d' ' -f3 | sed 's/,//')",
"running_containers": $(docker ps -q | wc -l),
"total_containers": $(docker ps -aq | wc -l)
}
EOF
print_message $GREEN "✓ 系统信息备份完成"
}
# 完整备份
full_backup() {
local timestamp=$(get_timestamp)
local backup_dir=$(create_backup_dirs "$timestamp")
print_message $GREEN "========================================="
print_message $GREEN "开始完整备份: $timestamp"
print_message $GREEN "========================================="
local backup_success=true
# 执行各项备份
backup_database "$backup_dir" "$timestamp" || backup_success=false
backup_config "$backup_dir" "$timestamp" || backup_success=false
backup_uploads "$backup_dir" "$timestamp" || backup_success=false
backup_logs "$backup_dir" "$timestamp" || backup_success=false
backup_system_info "$backup_dir" "$timestamp" || backup_success=false
# 创建备份清单
create_backup_manifest "$backup_dir" "$timestamp"
# 压缩整个备份
print_message $BLUE "压缩备份文件..."
local backup_archive="$BACKUP_ROOT/qaup_full_backup_$timestamp.tar.gz"
tar -czf "$backup_archive" -C "$BACKUP_ROOT" "$(basename "$backup_dir")"
# 计算校验和
sha256sum "$backup_archive" > "$backup_archive.sha256"
# 清理临时目录
rm -rf "$backup_dir"
if [ "$backup_success" = true ]; then
print_message $GREEN "========================================="
print_message $GREEN "完整备份完成!"
print_message $GREEN "========================================="
echo ""
print_message $BLUE "备份信息:"
echo " 备份文件: $backup_archive"
echo " 文件大小: $(du -h "$backup_archive" | cut -f1)"
echo " 校验文件: $backup_archive.sha256"
echo ""
# 记录备份历史
echo "$(date '+%Y-%m-%d %H:%M:%S') FULL_BACKUP SUCCESS $backup_archive $(du -b "$backup_archive" | cut -f1)" >> "$BACKUP_ROOT/backup_history.log"
return 0
else
print_message $RED "备份过程中出现错误,请检查日志"
return 1
fi
}
# 创建备份清单
create_backup_manifest() {
local backup_dir=$1
local timestamp=$2
cat > "$backup_dir/MANIFEST.json" << EOF
{
"backup_id": "$timestamp",
"backup_time": "$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')",
"backup_type": "full",
"version": "1.0",
"components": {
"database": {
"included": $([ -f "$backup_dir/database/metadata.json" ] && echo "true" || echo "false"),
"metadata_file": "database/metadata.json"
},
"config": {
"included": $([ -f "$backup_dir/config/metadata.json" ] && echo "true" || echo "false"),
"metadata_file": "config/metadata.json"
},
"uploads": {
"included": $([ -f "$backup_dir/uploads/metadata.json" ] && echo "true" || echo "false"),
"metadata_file": "uploads/metadata.json"
},
"logs": {
"included": $([ -f "$backup_dir/logs/metadata.json" ] && echo "true" || echo "false"),
"metadata_file": "logs/metadata.json"
},
"system": {
"included": $([ -f "$backup_dir/system/metadata.json" ] && echo "true" || echo "false"),
"metadata_file": "system/metadata.json"
}
},
"total_size": "$(du -sh "$backup_dir" | cut -f1)",
"file_count": $(find "$backup_dir" -type f | wc -l)
}
EOF
}
# 恢复数据库
restore_database() {
local backup_file=$1
print_message $BLUE "恢复数据库..."
if [ ! -f "$backup_file" ]; then
print_message $RED "备份文件不存在: $backup_file"
return 1
fi
# 检查数据库容器状态
if ! docker ps --format "{{.Names}}" | grep -q "qaup-postgres"; then
print_message $RED "数据库容器未运行"
return 1
fi
# 确认恢复操作
print_message $YELLOW "警告: 此操作将覆盖现有数据库数据"
read -p "确定要继续吗? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_message $BLUE "恢复操作已取消"
return 1
fi
# 停止应用服务以避免数据冲突
print_message $BLUE "停止应用服务..."
docker stop qaup-app || true
# 恢复数据库
if [[ "$backup_file" == *.gz ]]; then
# 解压并恢复
if gunzip -c "$backup_file" | docker exec -i qaup-postgres psql -U postgres; then
print_message $GREEN "✓ 数据库恢复成功"
else
print_message $RED "✗ 数据库恢复失败"
return 1
fi
else
# 直接恢复
if docker exec -i qaup-postgres psql -U postgres < "$backup_file"; then
print_message $GREEN "✓ 数据库恢复成功"
else
print_message $RED "✗ 数据库恢复失败"
return 1
fi
fi
# 重启应用服务
print_message $BLUE "重启应用服务..."
docker start qaup-app
# 等待服务就绪
sleep 10
if curl -f -s http://localhost:8080/actuator/health &>/dev/null; then
print_message $GREEN "✓ 应用服务已恢复正常"
else
print_message $YELLOW "⚠ 应用服务可能需要更多时间启动"
fi
return 0
}
# 恢复配置文件
restore_config() {
local backup_dir=$1
print_message $BLUE "恢复配置文件..."
if [ ! -d "$backup_dir" ]; then
print_message $RED "备份目录不存在: $backup_dir"
return 1
fi
# 备份当前配置
local current_backup="$PROJECT_ROOT/config_backup_$(get_timestamp)"
mkdir -p "$current_backup"
[ -f "$PROJECT_ROOT/.env" ] && cp "$PROJECT_ROOT/.env" "$current_backup/"
[ -d "$PROJECT_ROOT/config" ] && cp -r "$PROJECT_ROOT/config" "$current_backup/"
[ -d "$PROJECT_ROOT/docker" ] && cp -r "$PROJECT_ROOT/docker" "$current_backup/"
print_message $BLUE "当前配置已备份到: $current_backup"
# 恢复配置文件
if [ -f "$backup_dir/env_"* ]; then
cp "$backup_dir"/env_* "$PROJECT_ROOT/.env"
print_message $GREEN "✓ 环境配置已恢复"
fi
if [ -d "$backup_dir/app_config" ]; then
rm -rf "$PROJECT_ROOT/config"
cp -r "$backup_dir/app_config" "$PROJECT_ROOT/config"
print_message $GREEN "✓ 应用配置已恢复"
fi
if [ -d "$backup_dir/docker_config" ]; then
rm -rf "$PROJECT_ROOT/docker"
cp -r "$backup_dir/docker_config" "$PROJECT_ROOT/docker"
# 恢复脚本执行权限
find "$PROJECT_ROOT/docker" -name "*.sh" -exec chmod +x {} \;
print_message $GREEN "✓ Docker 配置已恢复"
fi
if [ -d "$backup_dir/ssl_certs" ]; then
rm -rf "$PROJECT_ROOT/ssl"
cp -r "$backup_dir/ssl_certs" "$PROJECT_ROOT/ssl"
chmod 600 "$PROJECT_ROOT/ssl"/*.key 2>/dev/null || true
print_message $GREEN "✓ SSL 证书已恢复"
fi
print_message $GREEN "✓ 配置文件恢复完成"
return 0
}
# 恢复上传文件
restore_uploads() {
local backup_file=$1
print_message $BLUE "恢复上传文件..."
if [ ! -f "$backup_file" ]; then
print_message $RED "备份文件不存在: $backup_file"
return 1
fi
# 备份当前上传文件
local uploads_dir="$PROJECT_ROOT/data/uploads"
if [ -d "$uploads_dir" ] && [ "$(ls -A "$uploads_dir" 2>/dev/null)" ]; then
local current_backup="$PROJECT_ROOT/uploads_backup_$(get_timestamp).tar.gz"
tar -czf "$current_backup" -C "$PROJECT_ROOT/data" uploads/
print_message $BLUE "当前上传文件已备份到: $current_backup"
fi
# 恢复上传文件
mkdir -p "$PROJECT_ROOT/data"
if tar -xzf "$backup_file" -C "$PROJECT_ROOT/data"; then
print_message $GREEN "✓ 上传文件恢复成功"
return 0
else
print_message $RED "✗ 上传文件恢复失败"
return 1
fi
}
# 完整恢复
full_restore() {
local backup_archive=$1
if [ ! -f "$backup_archive" ]; then
print_message $RED "备份文件不存在: $backup_archive"
return 1
fi
print_message $GREEN "========================================="
print_message $GREEN "开始完整恢复"
print_message $GREEN "========================================="
# 验证备份文件
if [ -f "$backup_archive.sha256" ]; then
print_message $BLUE "验证备份文件完整性..."
if sha256sum -c "$backup_archive.sha256"; then
print_message $GREEN "✓ 备份文件完整性验证通过"
else
print_message $RED "✗ 备份文件完整性验证失败"
return 1
fi
fi
# 解压备份文件
local temp_dir="/tmp/qaup_restore_$(get_timestamp)"
mkdir -p "$temp_dir"
print_message $BLUE "解压备份文件..."
if tar -xzf "$backup_archive" -C "$temp_dir"; then
print_message $GREEN "✓ 备份文件解压成功"
else
print_message $RED "✗ 备份文件解压失败"
rm -rf "$temp_dir"
return 1
fi
# 查找备份目录
local backup_dir=$(find "$temp_dir" -maxdepth 1 -type d -name "*" | grep -v "^$temp_dir$" | head -1)
if [ ! -d "$backup_dir" ]; then
print_message $RED "未找到有效的备份目录"
rm -rf "$temp_dir"
return 1
fi
# 读取备份清单
if [ -f "$backup_dir/MANIFEST.json" ]; then
print_message $BLUE "备份清单信息:"
cat "$backup_dir/MANIFEST.json" | grep -E '"backup_time"|"backup_type"|"total_size"' | sed 's/^ / /'
echo ""
fi
# 确认恢复操作
print_message $YELLOW "警告: 此操作将覆盖现有系统数据"
read -p "确定要继续完整恢复吗? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_message $BLUE "恢复操作已取消"
rm -rf "$temp_dir"
return 1
fi
local restore_success=true
# 恢复配置文件
if [ -d "$backup_dir/config" ]; then
restore_config "$backup_dir/config" || restore_success=false
fi
# 恢复数据库
if [ -f "$backup_dir/database"/*.sql.gz ]; then
restore_database "$backup_dir/database"/*.sql.gz || restore_success=false
elif [ -f "$backup_dir/database"/*.sql ]; then
restore_database "$backup_dir/database"/*.sql || restore_success=false
fi
# 恢复上传文件
if [ -f "$backup_dir/uploads"/*.tar.gz ]; then
restore_uploads "$backup_dir/uploads"/*.tar.gz || restore_success=false
fi
# 清理临时文件
rm -rf "$temp_dir"
if [ "$restore_success" = true ]; then
print_message $GREEN "========================================="
print_message $GREEN "完整恢复完成!"
print_message $GREEN "========================================="
echo ""
print_message $BLUE "建议执行以下操作:"
echo " 1. 重启所有服务: ./deploy.sh restart"
echo " 2. 验证系统状态: ./docker/monitor.sh status"
echo " 3. 检查应用功能: 访问前端页面测试"
echo ""
# 记录恢复历史
echo "$(date '+%Y-%m-%d %H:%M:%S') FULL_RESTORE SUCCESS $backup_archive" >> "$BACKUP_ROOT/restore_history.log"
return 0
else
print_message $RED "恢复过程中出现错误,请检查系统状态"
return 1
fi
}
# 列出备份文件
list_backups() {
print_message $BLUE "可用的备份文件:"
echo ""
if [ -d "$BACKUP_ROOT" ]; then
local backup_files=($(find "$BACKUP_ROOT" -name "qaup_full_backup_*.tar.gz" -type f | sort -r))
if [ ${#backup_files[@]} -eq 0 ]; then
print_message $YELLOW "未找到备份文件"
return
fi
local index=1
for backup_file in "${backup_files[@]}"; do
local filename=$(basename "$backup_file")
local size=$(du -h "$backup_file" | cut -f1)
local date=$(echo "$filename" | grep -o '[0-9]\{8\}_[0-9]\{6\}' | sed 's/_/ /')
echo "$index. $filename"
echo " 大小: $size"
echo " 时间: $date"
# 显示校验和状态
if [ -f "$backup_file.sha256" ]; then
echo " 校验: 可用"
else
echo " 校验: 不可用"
fi
echo ""
index=$((index + 1))
done
else
print_message $YELLOW "备份目录不存在: $BACKUP_ROOT"
fi
}
# 清理旧备份
cleanup_old_backups() {
local keep_days=${1:-30}
print_message $BLUE "清理 $keep_days 天前的备份文件..."
if [ ! -d "$BACKUP_ROOT" ]; then
print_message $YELLOW "备份目录不存在"
return
fi
local deleted_count=0
# 清理备份文件
while IFS= read -r -d '' file; do
rm -f "$file" "$file.sha256"
deleted_count=$((deleted_count + 1))
print_message $GREEN "已删除: $(basename "$file")"
done < <(find "$BACKUP_ROOT" -name "qaup_full_backup_*.tar.gz" -mtime +$keep_days -print0)
if [ $deleted_count -eq 0 ]; then
print_message $GREEN "没有需要清理的备份文件"
else
print_message $GREEN "已清理 $deleted_count 个旧备份文件"
fi
}
# 显示帮助信息
show_help() {
echo "QAUP 备份和恢复脚本"
echo ""
echo "用法: $0 [命令] [参数]"
echo ""
echo "备份命令:"
echo " full 完整备份"
echo " database 仅备份数据库"
echo " config 仅备份配置文件"
echo " uploads 仅备份上传文件"
echo ""
echo "恢复命令:"
echo " restore <backup_file> 完整恢复"
echo " restore-db <db_file> 仅恢复数据库"
echo " restore-config <dir> 仅恢复配置"
echo " restore-uploads <file> 仅恢复上传文件"
echo ""
echo "管理命令:"
echo " list 列出备份文件"
echo " cleanup [days] 清理旧备份 (默认30天)"
echo ""
echo "示例:"
echo " $0 full # 完整备份"
echo " $0 restore backup/qaup_full_backup_*.tar.gz # 完整恢复"
echo " $0 list # 列出备份"
echo " $0 cleanup 7 # 清理7天前的备份"
}
# 主函数
main() {
# 确保备份目录存在
mkdir -p "$BACKUP_ROOT"
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
shift
case $command in
full)
full_backup
;;
database)
local timestamp=$(get_timestamp)
local backup_dir=$(create_backup_dirs "$timestamp")
backup_database "$backup_dir" "$timestamp"
;;
config)
local timestamp=$(get_timestamp)
local backup_dir=$(create_backup_dirs "$timestamp")
backup_config "$backup_dir" "$timestamp"
;;
uploads)
local timestamp=$(get_timestamp)
local backup_dir=$(create_backup_dirs "$timestamp")
backup_uploads "$backup_dir" "$timestamp"
;;
restore)
if [ $# -eq 0 ]; then
print_message $RED "请指定备份文件"
exit 1
fi
full_restore "$1"
;;
restore-db)
if [ $# -eq 0 ]; then
print_message $RED "请指定数据库备份文件"
exit 1
fi
restore_database "$1"
;;
restore-config)
if [ $# -eq 0 ]; then
print_message $RED "请指定配置备份目录"
exit 1
fi
restore_config "$1"
;;
restore-uploads)
if [ $# -eq 0 ]; then
print_message $RED "请指定上传文件备份"
exit 1
fi
restore_uploads "$1"
;;
list)
list_backups
;;
cleanup)
cleanup_old_backups "$@"
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"

66
deploy/docker/build-frontend.sh Executable file
View File

@ -0,0 +1,66 @@
#!/bin/bash
# QAUP 前端构建脚本
# 用于 Docker 构建过程中的前端资源构建
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
FRONTEND_DIR="$PROJECT_ROOT/qaup-ui"
BUILD_OUTPUT="$FRONTEND_DIR/dist"
echo "========================================="
echo "QAUP 前端构建开始"
echo "========================================="
# 检查前端目录
if [ ! -d "$FRONTEND_DIR" ]; then
echo "错误: 前端目录不存在: $FRONTEND_DIR"
exit 1
fi
cd "$FRONTEND_DIR"
# 检查 package.json
if [ ! -f "package.json" ]; then
echo "错误: package.json 不存在"
exit 1
fi
# 清理之前的构建
echo "清理之前的构建文件..."
rm -rf "$BUILD_OUTPUT"
# 安装依赖
echo "安装前端依赖..."
if [ -f "package-lock.json" ]; then
npm ci
else
npm install
fi
# 构建生产版本
echo "构建生产版本..."
npm run build:prod
# 验证构建结果
if [ ! -d "$BUILD_OUTPUT" ]; then
echo "错误: 构建失败,输出目录不存在: $BUILD_OUTPUT"
exit 1
fi
# 显示构建统计
echo ""
echo "========================================="
echo "前端构建完成统计:"
echo "========================================="
echo "构建输出目录: $BUILD_OUTPUT"
echo "构建文件大小:"
du -sh "$BUILD_OUTPUT"
echo ""
echo "主要文件列表:"
find "$BUILD_OUTPUT" -name "*.html" -o -name "*.js" -o -name "*.css" | head -10
echo ""
echo "前端构建完成!"

View File

@ -0,0 +1,126 @@
#!/bin/bash
# QAUP Docker 镜像版本检查脚本
# 检查各个镜像的最新稳定版本
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 检查 Docker Hub API 获取最新版本
check_dockerhub_tags() {
local repo=$1
local filter=${2:-""}
print_message $BLUE "检查 $repo 的版本..."
# 使用 Docker Hub API 获取标签
local api_url="https://registry.hub.docker.com/v2/repositories/$repo/tags?page_size=100"
if command -v curl &> /dev/null; then
local tags=$(curl -s "$api_url" | grep -o '"name":"[^"]*"' | cut -d'"' -f4 | head -20)
echo "最新的一些版本标签:"
echo "$tags" | head -10
else
print_message $YELLOW "curl 不可用,无法检查版本"
fi
echo ""
}
# 检查当前使用的版本
check_current_versions() {
print_message $BLUE "当前 docker-compose.yml 中使用的版本:"
echo ""
if [ -f "../docker-compose.yml" ]; then
echo "PostgreSQL/PostGIS:"
grep -A 1 "qaup-postgres:" ../docker-compose.yml | grep "image:" | awk '{print " " $2}'
echo "Redis:"
grep -A 1 "qaup-redis:" ../docker-compose.yml | grep "image:" | awk '{print " " $2}'
echo ""
fi
if [ -f "../Dockerfile" ]; then
echo "Dockerfile 中的镜像:"
grep "^FROM" ../Dockerfile | awk '{print " " $2}'
echo ""
fi
}
print_message $GREEN "========================================="
print_message $GREEN "QAUP Docker 镜像版本检查"
print_message $GREEN "========================================="
echo ""
# 检查当前版本
check_current_versions
# 检查各个镜像的最新版本
print_message $BLUE "检查 Docker Hub 上的最新版本..."
echo ""
# PostgreSQL/PostGIS
check_dockerhub_tags "postgis/postgis"
# Redis
check_dockerhub_tags "library/redis"
# OpenJDK
check_dockerhub_tags "library/openjdk"
# Maven
check_dockerhub_tags "library/maven"
# Nginx
check_dockerhub_tags "library/nginx"
# Node.js
check_dockerhub_tags "library/node"
print_message $YELLOW "注意事项:"
echo "1. 选择版本时应优先考虑稳定性而非最新性"
echo "2. LTS (Long Term Support) 版本通常是生产环境的最佳选择"
echo "3. 对于 PostGIS需要确保 PostgreSQL 版本兼容性"
echo "4. 建议在测试环境验证新版本后再用于生产"
echo ""
# 检查当前配置文件中的版本
if [ -f "image-versions.env" ]; then
print_message $BLUE "当前配置文件中的版本:"
source image-versions.env
echo "PostgreSQL/PostGIS: $POSTGRES_IMAGE"
echo "Redis: $REDIS_IMAGE"
echo "OpenJDK: $OPENJDK_IMAGE"
echo "Maven: $MAVEN_IMAGE"
echo "Nginx: $NGINX_IMAGE"
echo "Node.js: $NODE_IMAGE"
echo ""
fi
print_message $GREEN "基于实际检查的推荐版本组合:"
echo "PostgreSQL/PostGIS: postgis/postgis:15-3.5-alpine (稳定) 或 postgis/postgis:16-3.5-alpine (更新)"
echo "Redis: redis:7.4.5-alpine (7.x系列最新稳定版) 或 redis:8.0.3-alpine (最新版本)"
echo "OpenJDK: openjdk:17-jre-slim (LTS推荐)"
echo "Maven: maven:3.9.11-openjdk-17-slim (最新稳定版)"
echo "Nginx: nginx:stable-alpine (稳定版本)"
echo "Node.js: node:lts-alpine (当前LTS版本)"
echo ""
print_message $YELLOW "更新建议:"
echo "1. 运行 './update-image-versions.sh --dry-run' 预览更改"
echo "2. 运行 './update-image-versions.sh --backup' 执行更新"
echo "3. 在测试环境验证新版本兼容性"
echo "4. 更新离线部署包"

335
deploy/docker/config-manager.sh Executable file
View File

@ -0,0 +1,335 @@
#!/bin/bash
# QAUP 配置管理脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
ENV_FILE="$PROJECT_ROOT/.env"
ENV_TEMPLATE="$PROJECT_ROOT/.env.template"
CONFIG_DIR="$PROJECT_ROOT/config"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 显示帮助信息
show_help() {
echo "QAUP 配置管理脚本"
echo ""
echo "用法: $0 [命令] [选项]"
echo ""
echo "命令:"
echo " init 初始化配置文件"
echo " validate 验证配置文件"
echo " generate 生成配置文件"
echo " backup 备份配置文件"
echo " restore 恢复配置文件"
echo " encrypt 加密敏感配置"
echo " decrypt 解密敏感配置"
echo " show 显示当前配置"
echo ""
echo "示例:"
echo " $0 init # 初始化配置"
echo " $0 validate # 验证配置"
echo " $0 generate prod # 生成生产环境配置"
}
# 初始化配置
init_config() {
print_message $BLUE "初始化 QAUP 配置..."
# 创建配置目录
mkdir -p "$CONFIG_DIR"
# 复制环境模板文件
if [ ! -f "$ENV_FILE" ]; then
if [ -f "$ENV_TEMPLATE" ]; then
cp "$ENV_TEMPLATE" "$ENV_FILE"
print_message $GREEN "环境配置文件已创建: $ENV_FILE"
print_message $YELLOW "请编辑 $ENV_FILE 文件,修改相关配置"
else
print_message $RED "错误: 环境模板文件不存在: $ENV_TEMPLATE"
exit 1
fi
else
print_message $YELLOW "环境配置文件已存在: $ENV_FILE"
fi
# 创建必要的目录
create_directories
print_message $GREEN "配置初始化完成!"
}
# 创建必要的目录
create_directories() {
local data_dir="./data"
local log_dir="./logs"
local backup_dir="./backup"
local ssl_dir="./ssl"
print_message $BLUE "创建必要的目录..."
mkdir -p "$data_dir"/{postgres,redis,uploads}
mkdir -p "$log_dir"/{postgres,redis,app,nginx}
mkdir -p "$backup_dir"/{postgres,config}
mkdir -p "$ssl_dir"
# 设置目录权限
chmod 755 "$data_dir" "$log_dir" "$backup_dir"
chmod 700 "$data_dir"/postgres "$ssl_dir"
print_message $GREEN "目录创建完成"
}
# 验证配置文件
validate_config() {
print_message $BLUE "验证配置文件..."
local errors=0
# 检查环境文件
if [ ! -f "$ENV_FILE" ]; then
print_message $RED "错误: 环境文件不存在: $ENV_FILE"
errors=$((errors + 1))
else
# 检查必需的环境变量
source "$ENV_FILE"
local required_vars=(
"POSTGRES_DB"
"POSTGRES_USER"
"POSTGRES_PASSWORD"
"APP_DB_USER"
"APP_DB_PASSWORD"
)
for var in "${required_vars[@]}"; do
if [ -z "${!var}" ]; then
print_message $RED "错误: 必需的环境变量未设置: $var"
errors=$((errors + 1))
fi
done
# 检查密码强度
if [ ${#POSTGRES_PASSWORD} -lt 8 ]; then
print_message $YELLOW "警告: 数据库密码长度少于8位建议使用更强的密码"
fi
if [ ${#APP_DB_PASSWORD} -lt 8 ]; then
print_message $YELLOW "警告: 应用数据库密码长度少于8位建议使用更强的密码"
fi
fi
# 检查配置文件
local config_files=(
"$CONFIG_DIR/application-prod.yml"
)
for config_file in "${config_files[@]}"; do
if [ ! -f "$config_file" ]; then
print_message $YELLOW "警告: 配置文件不存在: $config_file"
fi
done
if [ $errors -eq 0 ]; then
print_message $GREEN "配置验证通过!"
return 0
else
print_message $RED "配置验证失败,发现 $errors 个错误"
return 1
fi
}
# 生成配置文件
generate_config() {
local env=${1:-prod}
print_message $BLUE "生成 $env 环境配置文件..."
if [ ! -f "$ENV_FILE" ]; then
print_message $RED "错误: 环境文件不存在,请先运行 init 命令"
exit 1
fi
source "$ENV_FILE"
# 生成 Docker Compose 环境文件
local compose_env_file="$PROJECT_ROOT/.env.compose"
cat > "$compose_env_file" << EOF
# Docker Compose 环境配置
# 由配置管理脚本自动生成
# 数据库配置
POSTGRES_DB=$POSTGRES_DB
POSTGRES_USER=$POSTGRES_USER
POSTGRES_PASSWORD=$POSTGRES_PASSWORD
APP_DB_USER=$APP_DB_USER
APP_DB_PASSWORD=$APP_DB_PASSWORD
# Redis 配置
REDIS_PASSWORD=$REDIS_PASSWORD
# 应用配置
SPRING_PROFILES_ACTIVE=$SPRING_PROFILES_ACTIVE
APP_PORT=$APP_PORT
# Nginx 配置
NGINX_PORT=$NGINX_PORT
SERVER_NAME=$SERVER_NAME
API_PREFIX=$API_PREFIX
MAX_UPLOAD_SIZE=$MAX_UPLOAD_SIZE
# 目录配置
DATA_DIR=$DATA_DIR
LOG_DIR=$LOG_DIR
BACKUP_DIR=$BACKUP_DIR
# JVM 配置
JVM_OPTS=$JVM_OPTS
EOF
print_message $GREEN "配置文件生成完成: $compose_env_file"
}
# 备份配置文件
backup_config() {
local backup_dir="./backup/config"
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_file="config_backup_$timestamp.tar.gz"
print_message $BLUE "备份配置文件..."
mkdir -p "$backup_dir"
# 创建备份
tar -czf "$backup_dir/$backup_file" \
-C "$PROJECT_ROOT" \
.env \
config/ \
docker-compose*.yml \
2>/dev/null || true
print_message $GREEN "配置备份完成: $backup_dir/$backup_file"
}
# 显示当前配置
show_config() {
print_message $BLUE "当前配置信息:"
if [ -f "$ENV_FILE" ]; then
echo ""
echo "环境变量配置:"
echo "=============="
# 隐藏敏感信息
grep -v "PASSWORD\|SECRET" "$ENV_FILE" | grep -v "^#" | grep -v "^$"
echo ""
echo "敏感配置项:"
echo "==========="
grep "PASSWORD\|SECRET" "$ENV_FILE" | sed 's/=.*/=***/' || echo "无敏感配置项"
else
print_message $RED "环境配置文件不存在"
fi
}
# 加密敏感配置
encrypt_config() {
print_message $BLUE "加密敏感配置..."
if [ ! -f "$ENV_FILE" ]; then
print_message $RED "错误: 环境文件不存在"
exit 1
fi
# 创建加密版本
local encrypted_file="$ENV_FILE.encrypted"
# 使用 openssl 加密(需要输入密码)
openssl enc -aes-256-cbc -salt -in "$ENV_FILE" -out "$encrypted_file"
if [ $? -eq 0 ]; then
print_message $GREEN "配置文件已加密: $encrypted_file"
print_message $YELLOW "请妥善保管加密密码"
else
print_message $RED "配置文件加密失败"
exit 1
fi
}
# 解密敏感配置
decrypt_config() {
print_message $BLUE "解密敏感配置..."
local encrypted_file="$ENV_FILE.encrypted"
if [ ! -f "$encrypted_file" ]; then
print_message $RED "错误: 加密文件不存在: $encrypted_file"
exit 1
fi
# 使用 openssl 解密
openssl enc -aes-256-cbc -d -in "$encrypted_file" -out "$ENV_FILE"
if [ $? -eq 0 ]; then
print_message $GREEN "配置文件已解密: $ENV_FILE"
else
print_message $RED "配置文件解密失败"
exit 1
fi
}
# 主函数
main() {
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
shift
case $command in
init)
init_config
;;
validate)
validate_config
;;
generate)
generate_config "$@"
;;
backup)
backup_config
;;
show)
show_config
;;
encrypt)
encrypt_config
;;
decrypt)
decrypt_config
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"

View File

@ -0,0 +1,350 @@
#!/bin/bash
# Docker Compose 管理脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(pwd)"
ENV_FILE="$PROJECT_ROOT/.env"
# 默认环境
ENVIRONMENT=${ENVIRONMENT:-prod}
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 打印带颜色的消息
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 显示帮助信息
show_help() {
echo "QAUP Docker Compose 管理脚本"
echo ""
echo "用法: $0 [环境] [命令] [选项]"
echo ""
echo "环境:"
echo " dev 开发环境"
echo " prod 生产环境(默认)"
echo ""
echo "命令:"
echo " start 启动服务"
echo " stop 停止服务"
echo " restart 重启服务"
echo " status 查看服务状态"
echo " logs 查看服务日志"
echo " build 构建镜像"
echo " pull 拉取镜像"
echo " clean 清理资源"
echo " backup 备份数据"
echo " restore 恢复数据"
echo ""
echo "示例:"
echo " $0 prod start # 启动生产环境"
echo " $0 dev logs qaup-app # 查看开发环境应用日志"
echo " $0 prod backup # 备份生产环境数据"
}
# 检查环境文件
check_env_file() {
print_message $BLUE "检查环境文件: $ENV_FILE"
if [ ! -f "$ENV_FILE" ]; then
print_message $YELLOW "警告: 环境文件不存在,创建默认配置..."
create_default_env
else
print_message $GREEN "✓ 环境文件存在: $ENV_FILE"
fi
}
# 创建默认环境文件
create_default_env() {
cat > "$ENV_FILE" << EOF
# QAUP Docker 部署环境配置
# 数据库配置
POSTGRES_DB=qaup
POSTGRES_USER=postgres
POSTGRES_PASSWORD=qaup123
APP_DB_USER=qaup_app
APP_DB_PASSWORD=qaup123
# Redis 配置
REDIS_PASSWORD=
# 应用配置
SPRING_PROFILES_ACTIVE=prod
APP_PORT=8080
# Nginx 配置
NGINX_PORT=80
NGINX_HTTPS_PORT=443
SERVER_NAME=localhost
API_PREFIX=/prod-api
MAX_UPLOAD_SIZE=20M
# 目录配置
DATA_DIR=../data
LOG_DIR=../logs
BACKUP_DIR=../backup
# JVM 配置
JVM_OPTS=-Dname=qaup-admin.jar -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m
EOF
print_message $GREEN "默认环境文件已创建: $ENV_FILE"
}
# 获取 compose 文件参数
get_compose_files() {
local env=$1
# 使用项目根目录下的 docker-compose 文件
local base_file="$PROJECT_ROOT/docker-compose.yml"
local env_file="$PROJECT_ROOT/docker-compose.$env.yml"
if [ -f "$env_file" ]; then
echo "-f $base_file -f $env_file"
else
echo "-f $base_file"
fi
}
# 创建必要的目录
create_directories() {
local data_dir=${DATA_DIR:-../data}
local log_dir=${LOG_DIR:-../logs}
local backup_dir=${BACKUP_DIR:-../backup}
print_message $BLUE "检查并创建必要的目录..."
# 使用项目根目录下的目录结构,去掉 ../ 前缀
local actual_data_dir=${data_dir#../}
local actual_log_dir=${log_dir#../}
local actual_backup_dir=${backup_dir#../}
# 创建目录结构
mkdir -p "$PROJECT_ROOT/$actual_data_dir"/{postgres,redis,uploads}
mkdir -p "$PROJECT_ROOT/$actual_log_dir"/{postgres,redis,app,nginx}
mkdir -p "$PROJECT_ROOT/$actual_backup_dir"/postgres
# 只对新创建的基础目录设置权限,忽略已存在目录的权限错误
for dir in "$PROJECT_ROOT/$actual_data_dir" "$PROJECT_ROOT/$actual_log_dir" "$PROJECT_ROOT/$actual_backup_dir"; do
if [ -d "$dir" ]; then
chmod 755 "$dir" 2>/dev/null || true
fi
done
print_message $GREEN "目录检查完成!"
}
# 清理旧容器和占用的端口
cleanup_old_containers() {
print_message $YELLOW "清理旧容器和占用的端口..."
# 首先尝试停止可能存在的 docker-compose 服务
cd "$PROJECT_ROOT"
docker-compose $(get_compose_files prod) --env-file "$ENV_FILE" down 2>/dev/null || true
# 停止并删除可能存在的旧容器
local containers=("qaup-postgres" "qaup-redis" "qaup-app" "qaup-nginx")
for container in "${containers[@]}"; do
if docker ps -a --format "table {{.Names}}" | grep -q "^${container}$"; then
print_message $BLUE "删除旧容器: $container"
docker rm -f "$container" 2>/dev/null || true
fi
done
# 清理可能占用端口的容器(通过端口查找)
local ports=(5432 6379 8080 80 443)
for port in "${ports[@]}"; do
local container_id=$(docker ps --filter "publish=$port" -q 2>/dev/null || true)
if [ -n "$container_id" ]; then
print_message $BLUE "停止占用端口 $port 的容器: $container_id"
docker rm -f "$container_id" 2>/dev/null || true
fi
done
print_message $GREEN "旧容器和端口清理完成!"
}
# 启动服务
start_services() {
local env=$1
shift
local services="$@"
print_message $GREEN "启动 $env 环境服务..."
check_env_file
create_directories # 智能创建目录,避免权限问题
# 清理旧容器
cleanup_old_containers
cd "$PROJECT_ROOT"
docker-compose $(get_compose_files $env) --env-file "$ENV_FILE" up -d $services
print_message $GREEN "服务启动完成!"
show_status $env
}
# 停止服务
stop_services() {
local env=$1
shift
local services="$@"
print_message $YELLOW "停止 $env 环境服务..."
cd "$PROJECT_ROOT"
docker-compose $(get_compose_files $env) --env-file "$ENV_FILE" stop $services
print_message $GREEN "服务停止完成!"
}
# 重启服务
restart_services() {
local env=$1
shift
local services="$@"
print_message $BLUE "重启 $env 环境服务..."
stop_services $env $services
sleep 2
start_services $env $services
}
# 查看服务状态
show_status() {
local env=$1
print_message $BLUE "查看 $env 环境服务状态..."
cd "$PROJECT_ROOT"
docker-compose $(get_compose_files $env) --env-file "$ENV_FILE" ps
}
# 查看日志
show_logs() {
local env=$1
shift
local services="$@"
cd "$PROJECT_ROOT"
docker-compose $(get_compose_files $env) --env-file "$ENV_FILE" logs -f $services
}
# 构建镜像
build_images() {
local env=$1
print_message $BLUE "构建 $env 环境镜像..."
cd "$PROJECT_ROOT"
docker-compose $(get_compose_files $env) --env-file "$ENV_FILE" build
print_message $GREEN "镜像构建完成!"
}
# 清理资源
clean_resources() {
local env=$1
print_message $YELLOW "清理 $env 环境资源..."
cd "$PROJECT_ROOT"
# 停止并删除容器
docker-compose $(get_compose_files $env) --env-file "$ENV_FILE" down
# 清理未使用的镜像和网络
docker system prune -f
print_message $GREEN "资源清理完成!"
}
# 备份数据
backup_data() {
local env=$1
print_message $BLUE "备份 $env 环境数据..."
cd "$PROJECT_ROOT"
# 执行数据库备份
docker-compose $(get_compose_files $env) --env-file "$ENV_FILE" exec qaup-postgres /backup-db.sh
print_message $GREEN "数据备份完成!"
}
# 主函数
main() {
if [ $# -eq 0 ]; then
show_help
exit 0
fi
# 解析参数
local env="prod"
local command=""
# 检查第一个参数是否是环境
if [[ "$1" =~ ^(dev|prod)$ ]]; then
env=$1
shift
fi
if [ $# -eq 0 ]; then
show_help
exit 0
fi
command=$1
shift
# 加载环境文件
if [ -f "$ENV_FILE" ]; then
source "$ENV_FILE"
fi
# 执行命令
case $command in
start)
start_services $env "$@"
;;
stop)
stop_services $env "$@"
;;
restart)
restart_services $env "$@"
;;
status)
show_status $env
;;
logs)
show_logs $env "$@"
;;
build)
build_images $env
;;
clean)
clean_resources $env
;;
backup)
backup_data $env
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
# 执行主函数
main "$@"

View File

@ -0,0 +1,99 @@
# QAUP 健康检查配置
# 用于配置各服务的健康检查参数
healthchecks:
qaup-app:
endpoint: "http://localhost:8080/actuator/health"
interval: 30s
timeout: 10s
retries: 5
start_period: 120s
expected_status: 200
expected_content: '"status":"UP"'
qaup-nginx:
endpoint: "http://localhost/health"
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
expected_status: 200
expected_content: "healthy"
qaup-postgres:
command: "pg_isready -h localhost -p 5432 -U postgres"
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
qaup-redis:
command: "redis-cli ping"
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
expected_output: "PONG"
# 监控阈值配置
thresholds:
cpu_usage: 80 # CPU使用率告警阈值 (%)
memory_usage: 85 # 内存使用率告警阈值 (%)
disk_usage: 80 # 磁盘使用率告警阈值 (%)
response_time: 5000 # 响应时间告警阈值 (ms)
error_rate: 5 # 错误率告警阈值 (%)
# 日志监控配置
log_monitoring:
error_patterns:
- "ERROR"
- "FATAL"
- "Exception"
- "failed"
- "timeout"
warning_patterns:
- "WARN"
- "WARNING"
- "deprecated"
ignore_patterns:
- "health check"
- "actuator"
- "favicon.ico"
# 告警配置
alerts:
enabled: true
channels:
- type: "log"
path: "./logs/alerts.log"
- type: "email"
enabled: false
smtp_server: "smtp.example.com"
smtp_port: 587
username: "alerts@example.com"
password: "password"
recipients:
- "admin@example.com"
rules:
- name: "容器停止"
condition: "container_stopped"
severity: "critical"
message: "容器 {{container_name}} 已停止运行"
- name: "健康检查失败"
condition: "health_check_failed"
severity: "warning"
message: "服务 {{service_name}} 健康检查失败"
- name: "资源使用率过高"
condition: "resource_usage_high"
severity: "warning"
message: "{{resource_type}} 使用率达到 {{usage}}%"
- name: "错误日志增加"
condition: "error_log_increase"
severity: "warning"
message: "检测到错误日志数量异常增加"

View File

@ -0,0 +1,124 @@
# QAUP Docker 镜像版本配置文件
# 集中管理所有使用的 Docker 镜像版本
# 更新日期: 2024-01-31
# ===========================================
# 基础镜像版本
# ===========================================
# PostgreSQL with PostGIS
# PostgreSQL with PostGIS
# 使用 PostgreSQL 17 + PostGIS 3.5 (根据用户实际使用版本)
# PostgreSQL 17 是最新稳定版本PostGIS 3.5 是当前稳定版本
POSTGRES_IMAGE=m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine
# Redis
# 使用 Redis 8.0 最新版本 (根据用户实际使用版本)
REDIS_IMAGE=m.daocloud.io/docker.io/library/redis:8.0-alpine
# OpenJDK
# 使用 Java 17 LTS 版本(生产环境推荐)- 使用eclipse-temurin替代openjdk
OPENJDK_IMAGE=m.daocloud.io/docker.io/library/eclipse-temurin:17-jre
# Maven
# 使用 Maven latest版本包含OpenJDK 17
MAVEN_IMAGE=m.daocloud.io/docker.io/library/maven:latest
# Nginx
# 使用稳定版本
NGINX_IMAGE=m.daocloud.io/docker.io/library/nginx:stable-alpine
# Node.js
# 使用当前 LTS 版本
NODE_IMAGE=m.daocloud.io/docker.io/library/node:lts-alpine
# ===========================================
# 自定义镜像版本
# ===========================================
# QAUP 应用镜像
QAUP_APP_IMAGE=qaup-app:latest
# QAUP Nginx 镜像
QAUP_NGINX_IMAGE=qaup-nginx:latest
# ===========================================
# 版本说明
# ===========================================
# PostgreSQL/PostGIS 版本选择说明 (基于用户实际使用版本):
# - postgis/postgis:17-3.5-alpine: PostgreSQL 17 + PostGIS 3.5 (当前使用,最新稳定版)
# - postgis/postgis:16-3.5-alpine: PostgreSQL 16 + PostGIS 3.5 (稳定选择)
# - postgis/postgis:15-3.5-alpine: PostgreSQL 15 + PostGIS 3.5 (保守选择)
# - postgis/postgis:*-3.6.0alpha1-alpine: PostGIS 3.6 alpha (不推荐生产使用)
# Java 版本选择说明:
# - Java 17: LTS 版本,生产环境推荐,兼容性好
# - Java 21: 最新 LTS 版本,性能更好,但需要验证兼容性
# - Java 25/26: 预览版本,不推荐生产使用
# Redis 版本说明 (基于用户实际使用版本):
# - 8.0-alpine: Redis 8.0 最新版本 (当前使用)
# - 7.4.5-alpine: 7.x 系列最新稳定版本
# - 7.2.x-alpine: 较老但稳定的版本
# Maven 版本说明:
# - 3.9.11: 当前最新稳定版本
# - 与 OpenJDK 版本保持一致很重要
# Node.js 版本说明:
# - lts-alpine: 总是指向当前 LTS 版本
# - iron-*: Iron 是 Node.js 18.x LTS 的代号
# - jod-*: 可能是更新的 LTS 版本代号
# Nginx 版本说明:
# - stable-alpine: 总是指向稳定版本
# - mainline-*: 开发版本,包含最新特性但可能不稳定
# ===========================================
# 更新检查命令
# ===========================================
# 运行以下命令检查最新版本:
# ./check-image-versions.sh
# 更新镜像版本后,需要同步更新:
# 1. docker-compose.yml
# 2. Dockerfile
# 3. docker/nginx/Dockerfile
# 4. prepare-offline-images.sh
# ===========================================
# 版本更新记录
# ===========================================
# 2024-01-31: 根据用户实际使用版本更新
# - PostgreSQL: 15-3.5-alpine → 17-3.5-alpine (升级到最新稳定版)
# - Redis: 7.4.5-alpine → 8.0-alpine (升级到最新版本)
# - 其他组件保持不变
# 注意事项:
# - PostgreSQL 17 是最新稳定版本,包含性能改进和新特性
# - Redis 8.0 是最新版本,包含新的数据结构和性能优化
# - 升级前请在测试环境验证兼容性
# - 建议备份现有数据后再进行升级
# ===========================================
# 版本兼容性检查清单
# ===========================================
# 更新版本前请确认:
# □ Spring Boot 版本与 Java 版本兼容
# □ PostGIS 版本与应用程序的空间数据处理兼容
# □ Redis 版本与应用程序的缓存功能兼容
# □ Node.js 版本与前端构建工具兼容
# □ 在测试环境验证所有功能正常
# □ 检查是否有破坏性变更
# 推荐的更新流程:
# 1. 运行 ./check-image-versions.sh 检查最新版本
# 2. 更新 image-versions.env 配置文件
# 3. 运行 ./update-image-versions.sh --dry-run 预览更改
# 4. 运行 ./update-image-versions.sh --backup 执行更新
# 5. 在测试环境验证兼容性
# 6. 更新离线部署包

768
deploy/docker/integration-test.sh Executable file
View File

@ -0,0 +1,768 @@
#!/bin/bash
# QAUP 集成测试脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
TEST_LOG="$PROJECT_ROOT/logs/integration_test_$(date +%Y%m%d_%H%M%S).log"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
echo "$(date '+%Y-%m-%d %H:%M:%S') $message" >> "$TEST_LOG"
}
# 测试结果统计
TOTAL_TESTS=0
PASSED_TESTS=0
FAILED_TESTS=0
FAILED_TEST_NAMES=()
# 记录测试结果
record_test_result() {
local test_name=$1
local result=$2
TOTAL_TESTS=$((TOTAL_TESTS + 1))
if [ "$result" = "PASS" ]; then
PASSED_TESTS=$((PASSED_TESTS + 1))
print_message $GREEN "$test_name: PASS"
else
FAILED_TESTS=$((FAILED_TESTS + 1))
FAILED_TEST_NAMES+=("$test_name")
print_message $RED "$test_name: FAIL"
fi
}
# 等待服务就绪
wait_for_service() {
local service_name=$1
local url=$2
local max_attempts=${3:-30}
local attempt=0
print_message $BLUE "等待 $service_name 服务就绪..."
while [ $attempt -lt $max_attempts ]; do
if curl -f -s "$url" &>/dev/null; then
print_message $GREEN "$service_name 服务已就绪"
return 0
fi
sleep 2
attempt=$((attempt + 1))
if [ $((attempt % 10)) -eq 0 ]; then
print_message $YELLOW "$service_name 等待中... ($attempt/$max_attempts)"
fi
done
print_message $RED "$service_name 服务启动超时"
return 1
}
# 测试 Docker 环境
test_docker_environment() {
print_message $BLUE "测试 Docker 环境..."
# 测试 Docker 是否运行
if docker info &>/dev/null; then
record_test_result "Docker服务运行" "PASS"
else
record_test_result "Docker服务运行" "FAIL"
return 1
fi
# 测试 Docker Compose 是否可用
if docker-compose --version &>/dev/null; then
record_test_result "Docker Compose可用" "PASS"
else
record_test_result "Docker Compose可用" "FAIL"
return 1
fi
# 测试网络连接
if docker network ls | grep -q qaup; then
record_test_result "Docker网络存在" "PASS"
else
record_test_result "Docker网络存在" "FAIL"
fi
return 0
}
# 测试容器启动
test_container_startup() {
print_message $BLUE "测试容器启动..."
local containers=("qaup-postgres" "qaup-redis" "qaup-app" "qaup-nginx")
for container in "${containers[@]}"; do
if docker ps --format "{{.Names}}" | grep -q "^${container}$"; then
record_test_result "${container}容器运行" "PASS"
else
record_test_result "${container}容器运行" "FAIL"
fi
done
}
# 测试服务健康检查
test_service_health() {
print_message $BLUE "测试服务健康检查..."
# 测试数据库健康检查
if docker exec qaup-postgres pg_isready -h localhost -p 5432 -U postgres &>/dev/null; then
record_test_result "PostgreSQL健康检查" "PASS"
else
record_test_result "PostgreSQL健康检查" "FAIL"
fi
# 测试 Redis 健康检查
if docker exec qaup-redis redis-cli ping | grep -q "PONG"; then
record_test_result "Redis健康检查" "PASS"
else
record_test_result "Redis健康检查" "FAIL"
fi
# 测试应用健康检查
if wait_for_service "QAUP应用" "http://localhost:8080/actuator/health" 30; then
record_test_result "应用健康检查" "PASS"
else
record_test_result "应用健康检查" "FAIL"
fi
# 测试 Nginx 健康检查
if wait_for_service "Nginx" "http://localhost/health" 10; then
record_test_result "Nginx健康检查" "PASS"
else
record_test_result "Nginx健康检查" "FAIL"
fi
}
# 测试网络连通性
test_network_connectivity() {
print_message $BLUE "测试网络连通性..."
# 测试应用到数据库的连接
if docker exec qaup-app nc -z qaup-postgres 5432 &>/dev/null; then
record_test_result "应用到数据库连接" "PASS"
else
record_test_result "应用到数据库连接" "FAIL"
fi
# 测试应用到 Redis 的连接
if docker exec qaup-app nc -z qaup-redis 6379 &>/dev/null; then
record_test_result "应用到Redis连接" "PASS"
else
record_test_result "应用到Redis连接" "FAIL"
fi
# 测试 Nginx 到应用的连接
if docker exec qaup-nginx nc -z qaup-app 8080 &>/dev/null; then
record_test_result "Nginx到应用连接" "PASS"
else
record_test_result "Nginx到应用连接" "FAIL"
fi
}
# 测试数据库功能
test_database_functionality() {
print_message $BLUE "测试数据库功能..."
# 测试数据库连接
if docker exec qaup-postgres psql -U postgres -d qaup -c "SELECT 1;" &>/dev/null; then
record_test_result "数据库连接测试" "PASS"
else
record_test_result "数据库连接测试" "FAIL"
return 1
fi
# 测试 PostGIS 扩展
if docker exec qaup-postgres psql -U postgres -d qaup -c "SELECT PostGIS_Version();" &>/dev/null; then
record_test_result "PostGIS扩展测试" "PASS"
else
record_test_result "PostGIS扩展测试" "FAIL"
fi
# 测试表结构
local table_count=$(docker exec qaup-postgres psql -U postgres -d qaup -t -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';" | xargs)
if [ "$table_count" -gt 0 ]; then
record_test_result "数据库表结构测试" "PASS"
print_message $BLUE "数据库包含 $table_count 个表"
else
record_test_result "数据库表结构测试" "FAIL"
fi
# 测试数据库写入
local test_table="integration_test_$(date +%s)"
if docker exec qaup-postgres psql -U postgres -d qaup -c "CREATE TABLE $test_table (id SERIAL PRIMARY KEY, test_data TEXT); INSERT INTO $test_table (test_data) VALUES ('test'); DROP TABLE $test_table;" &>/dev/null; then
record_test_result "数据库写入测试" "PASS"
else
record_test_result "数据库写入测试" "FAIL"
fi
}
# 测试 Redis 功能
test_redis_functionality() {
print_message $BLUE "测试 Redis 功能..."
# 测试 Redis 基本操作
if docker exec qaup-redis redis-cli set test_key "test_value" | grep -q "OK"; then
record_test_result "Redis写入测试" "PASS"
else
record_test_result "Redis写入测试" "FAIL"
return 1
fi
# 测试 Redis 读取
if docker exec qaup-redis redis-cli get test_key | grep -q "test_value"; then
record_test_result "Redis读取测试" "PASS"
else
record_test_result "Redis读取测试" "FAIL"
fi
# 清理测试数据
docker exec qaup-redis redis-cli del test_key &>/dev/null
# 测试 Redis 内存使用
local memory_usage=$(docker exec qaup-redis redis-cli info memory | grep "used_memory_human" | cut -d: -f2 | tr -d '\r')
if [ -n "$memory_usage" ]; then
record_test_result "Redis内存信息测试" "PASS"
print_message $BLUE "Redis 内存使用: $memory_usage"
else
record_test_result "Redis内存信息测试" "FAIL"
fi
}
# 测试应用 API
test_application_api() {
print_message $BLUE "测试应用 API..."
# 测试健康检查端点
local health_response=$(curl -s http://localhost:8080/actuator/health)
if echo "$health_response" | grep -q '"status":"UP"'; then
record_test_result "应用健康端点测试" "PASS"
else
record_test_result "应用健康端点测试" "FAIL"
fi
# 测试应用信息端点
if curl -f -s http://localhost:8080/actuator/info &>/dev/null; then
record_test_result "应用信息端点测试" "PASS"
else
record_test_result "应用信息端点测试" "FAIL"
fi
# 测试应用指标端点
if curl -f -s http://localhost:8080/actuator/metrics &>/dev/null; then
record_test_result "应用指标端点测试" "PASS"
else
record_test_result "应用指标端点测试" "FAIL"
fi
# 测试应用响应时间
local response_time=$(curl -o /dev/null -s -w '%{time_total}' http://localhost:8080/actuator/health)
if (( $(echo "$response_time < 5.0" | bc -l) )); then
record_test_result "应用响应时间测试" "PASS"
print_message $BLUE "应用响应时间: ${response_time}s"
else
record_test_result "应用响应时间测试" "FAIL"
print_message $YELLOW "应用响应时间较慢: ${response_time}s"
fi
}
# 测试前端访问
test_frontend_access() {
print_message $BLUE "测试前端访问..."
# 测试 Nginx 健康检查
if curl -f -s http://localhost/health | grep -q "healthy"; then
record_test_result "前端健康检查测试" "PASS"
else
record_test_result "前端健康检查测试" "FAIL"
fi
# 测试前端页面访问
local http_status=$(curl -o /dev/null -s -w '%{http_code}' http://localhost/)
if [ "$http_status" = "200" ]; then
record_test_result "前端页面访问测试" "PASS"
else
record_test_result "前端页面访问测试" "FAIL"
print_message $YELLOW "HTTP状态码: $http_status"
fi
# 测试 API 代理
local api_status=$(curl -o /dev/null -s -w '%{http_code}' http://localhost/prod-api/actuator/health)
if [ "$api_status" = "200" ]; then
record_test_result "API代理测试" "PASS"
else
record_test_result "API代理测试" "FAIL"
print_message $YELLOW "API代理状态码: $api_status"
fi
# 测试静态资源
if curl -f -s http://localhost/favicon.ico &>/dev/null; then
record_test_result "静态资源访问测试" "PASS"
else
record_test_result "静态资源访问测试" "FAIL"
fi
}
# 测试数据持久化
test_data_persistence() {
print_message $BLUE "测试数据持久化..."
# 测试数据库数据持久化
local test_data="persistence_test_$(date +%s)"
# 创建测试数据
if docker exec qaup-postgres psql -U postgres -d qaup -c "CREATE TABLE IF NOT EXISTS test_persistence (id SERIAL PRIMARY KEY, data TEXT); INSERT INTO test_persistence (data) VALUES ('$test_data');" &>/dev/null; then
record_test_result "数据库数据创建测试" "PASS"
else
record_test_result "数据库数据创建测试" "FAIL"
return 1
fi
# 重启数据库容器
print_message $BLUE "重启数据库容器测试持久化..."
docker restart qaup-postgres &>/dev/null
# 等待数据库重新启动
sleep 10
# 验证数据是否仍然存在
if docker exec qaup-postgres psql -U postgres -d qaup -t -c "SELECT data FROM test_persistence WHERE data = '$test_data';" | grep -q "$test_data"; then
record_test_result "数据库持久化测试" "PASS"
else
record_test_result "数据库持久化测试" "FAIL"
fi
# 清理测试数据
docker exec qaup-postgres psql -U postgres -d qaup -c "DELETE FROM test_persistence WHERE data = '$test_data';" &>/dev/null
# 测试 Redis 持久化
docker exec qaup-redis redis-cli set persistence_test "$test_data" &>/dev/null
docker restart qaup-redis &>/dev/null
sleep 5
if docker exec qaup-redis redis-cli get persistence_test | grep -q "$test_data"; then
record_test_result "Redis持久化测试" "PASS"
else
record_test_result "Redis持久化测试" "FAIL"
fi
# 清理 Redis 测试数据
docker exec qaup-redis redis-cli del persistence_test &>/dev/null
}
# 测试文件上传功能
test_file_upload() {
print_message $BLUE "测试文件上传功能..."
# 创建测试文件
local test_file="/tmp/test_upload_$(date +%s).txt"
echo "This is a test file for upload functionality" > "$test_file"
# 检查上传目录是否存在
if [ -d "$PROJECT_ROOT/data/uploads" ]; then
# 模拟文件上传(复制到上传目录)
cp "$test_file" "$PROJECT_ROOT/data/uploads/"
if [ -f "$PROJECT_ROOT/data/uploads/$(basename "$test_file")" ]; then
record_test_result "文件上传测试" "PASS"
# 清理测试文件
rm -f "$PROJECT_ROOT/data/uploads/$(basename "$test_file")"
else
record_test_result "文件上传测试" "FAIL"
fi
else
record_test_result "文件上传目录检查" "FAIL"
fi
# 清理临时文件
rm -f "$test_file"
}
# 测试日志功能
test_logging() {
print_message $BLUE "测试日志功能..."
# 检查应用日志
if docker logs qaup-app --tail 10 | grep -q "Started"; then
record_test_result "应用日志测试" "PASS"
else
record_test_result "应用日志测试" "FAIL"
fi
# 检查 Nginx 访问日志
if docker logs qaup-nginx --tail 10 &>/dev/null; then
record_test_result "Nginx日志测试" "PASS"
else
record_test_result "Nginx日志测试" "FAIL"
fi
# 检查日志目录
if [ -d "$PROJECT_ROOT/logs" ]; then
record_test_result "日志目录检查" "PASS"
else
record_test_result "日志目录检查" "FAIL"
fi
}
# 测试监控功能
test_monitoring() {
print_message $BLUE "测试监控功能..."
# 测试监控脚本
if [ -x "$PROJECT_ROOT/docker/monitor.sh" ]; then
if "$PROJECT_ROOT/docker/monitor.sh" status &>/dev/null; then
record_test_result "监控脚本测试" "PASS"
else
record_test_result "监控脚本测试" "FAIL"
fi
else
record_test_result "监控脚本存在性检查" "FAIL"
fi
# 测试告警功能
if [ -x "$PROJECT_ROOT/docker/alert-manager.sh" ]; then
if "$PROJECT_ROOT/docker/alert-manager.sh" check &>/dev/null; then
record_test_result "告警功能测试" "PASS"
else
record_test_result "告警功能测试" "FAIL"
fi
else
record_test_result "告警脚本存在性检查" "FAIL"
fi
}
# 测试备份功能
test_backup_functionality() {
print_message $BLUE "测试备份功能..."
# 测试数据库备份
if [ -x "$PROJECT_ROOT/docker/backup-restore.sh" ]; then
if "$PROJECT_ROOT/docker/backup-restore.sh" database &>/dev/null; then
record_test_result "数据库备份测试" "PASS"
else
record_test_result "数据库备份测试" "FAIL"
fi
else
record_test_result "备份脚本存在性检查" "FAIL"
fi
# 检查备份目录
if [ -d "$PROJECT_ROOT/backup" ]; then
record_test_result "备份目录检查" "PASS"
else
record_test_result "备份目录检查" "FAIL"
fi
}
# 性能测试
test_performance() {
print_message $BLUE "测试系统性能..."
# 测试并发请求
local concurrent_requests=10
local total_requests=100
print_message $BLUE "执行并发性能测试 ($concurrent_requests 并发, $total_requests 请求)..."
# 使用 curl 进行简单的并发测试
local start_time=$(date +%s.%N)
for ((i=1; i<=concurrent_requests; i++)); do
{
for ((j=1; j<=10; j++)); do
curl -s http://localhost/health &>/dev/null
done
} &
done
wait
local end_time=$(date +%s.%N)
local duration=$(echo "$end_time - $start_time" | bc)
local rps=$(echo "scale=2; $total_requests / $duration" | bc)
print_message $BLUE "性能测试结果: ${rps} 请求/秒"
# 判断性能是否合格(简单阈值)
if (( $(echo "$rps > 50" | bc -l) )); then
record_test_result "并发性能测试" "PASS"
else
record_test_result "并发性能测试" "FAIL"
fi
# 测试内存使用
local memory_usage=$(docker stats --no-stream --format "{{.MemPerc}}" qaup-app | sed 's/%//')
if (( $(echo "$memory_usage < 80" | bc -l) )); then
record_test_result "内存使用测试" "PASS"
print_message $BLUE "应用内存使用: ${memory_usage}%"
else
record_test_result "内存使用测试" "FAIL"
print_message $YELLOW "应用内存使用较高: ${memory_usage}%"
fi
}
# 安全测试
test_security() {
print_message $BLUE "测试安全配置..."
# 测试端口访问限制
local restricted_ports=("5432" "6379" "8080")
for port in "${restricted_ports[@]}"; do
if ! nc -z localhost "$port" 2>/dev/null; then
record_test_result "端口${port}访问限制测试" "PASS"
else
record_test_result "端口${port}访问限制测试" "FAIL"
fi
done
# 测试 HTTP 安全头
local security_headers=("X-Frame-Options" "X-Content-Type-Options" "X-XSS-Protection")
for header in "${security_headers[@]}"; do
if curl -I -s http://localhost/ | grep -q "$header"; then
record_test_result "${header}安全头测试" "PASS"
else
record_test_result "${header}安全头测试" "FAIL"
fi
done
}
# 运行完整集成测试
run_full_integration_test() {
print_message $GREEN "========================================="
print_message $GREEN "开始 QAUP 系统集成测试"
print_message $GREEN "========================================="
# 确保日志目录存在
mkdir -p "$(dirname "$TEST_LOG")"
# 记录测试开始时间
local start_time=$(date +%s)
# 执行各项测试
test_docker_environment
test_container_startup
test_service_health
test_network_connectivity
test_database_functionality
test_redis_functionality
test_application_api
test_frontend_access
test_data_persistence
test_file_upload
test_logging
test_monitoring
test_backup_functionality
test_performance
test_security
# 计算测试时间
local end_time=$(date +%s)
local duration=$((end_time - start_time))
# 生成测试报告
generate_test_report "$duration"
}
# 生成测试报告
generate_test_report() {
local duration=$1
print_message $GREEN "========================================="
print_message $GREEN "集成测试完成"
print_message $GREEN "========================================="
echo ""
print_message $BLUE "测试统计:"
echo " 总测试数: $TOTAL_TESTS"
echo " 通过测试: $PASSED_TESTS"
echo " 失败测试: $FAILED_TESTS"
echo " 成功率: $(echo "scale=2; $PASSED_TESTS * 100 / $TOTAL_TESTS" | bc)%"
echo " 测试时间: ${duration}"
echo ""
if [ $FAILED_TESTS -eq 0 ]; then
print_message $GREEN "🎉 所有测试通过!系统部署成功!"
echo ""
print_message $BLUE "系统已准备就绪,可以投入使用:"
echo " 前端访问: http://localhost"
echo " API 文档: http://localhost/prod-api/swagger-ui.html"
echo " 监控面板: ./docker/monitor.sh watch"
echo ""
else
print_message $RED "❌ 部分测试失败,需要检查以下问题:"
echo ""
for failed_test in "${FAILED_TEST_NAMES[@]}"; do
echo " - $failed_test"
done
echo ""
print_message $YELLOW "建议操作:"
echo " 1. 查看详细日志: $TEST_LOG"
echo " 2. 检查服务状态: ./docker/monitor.sh status"
echo " 3. 查看容器日志: docker logs <container_name>"
echo " 4. 修复问题后重新运行测试"
echo ""
fi
# 创建 JSON 格式的测试报告
local report_file="$PROJECT_ROOT/logs/integration_test_report_$(date +%Y%m%d_%H%M%S).json"
cat > "$report_file" << EOF
{
"test_run": {
"timestamp": "$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')",
"duration_seconds": $duration,
"total_tests": $TOTAL_TESTS,
"passed_tests": $PASSED_TESTS,
"failed_tests": $FAILED_TESTS,
"success_rate": $(echo "scale=4; $PASSED_TESTS / $TOTAL_TESTS" | bc),
"status": "$([ $FAILED_TESTS -eq 0 ] && echo "SUCCESS" || echo "FAILURE")"
},
"failed_tests": [
$(IFS=','; printf ' "%s"' "${FAILED_TEST_NAMES[*]}" | sed 's/","/",\n "/g')
],
"log_file": "$TEST_LOG",
"report_file": "$report_file"
}
EOF
print_message $BLUE "详细测试报告已生成:"
echo " 日志文件: $TEST_LOG"
echo " JSON报告: $report_file"
# 返回测试结果
return $FAILED_TESTS
}
# 快速健康检查
quick_health_check() {
print_message $BLUE "执行快速健康检查..."
test_container_startup
test_service_health
test_frontend_access
if [ $FAILED_TESTS -eq 0 ]; then
print_message $GREEN "✓ 快速健康检查通过"
return 0
else
print_message $RED "✗ 快速健康检查失败"
return 1
fi
}
# 显示帮助信息
show_help() {
echo "QAUP 集成测试脚本"
echo ""
echo "用法: $0 [命令]"
echo ""
echo "命令:"
echo " full 运行完整集成测试"
echo " quick 快速健康检查"
echo " docker 测试 Docker 环境"
echo " containers 测试容器状态"
echo " health 测试服务健康状态"
echo " network 测试网络连通性"
echo " database 测试数据库功能"
echo " redis 测试 Redis 功能"
echo " api 测试应用 API"
echo " frontend 测试前端访问"
echo " performance 性能测试"
echo " security 安全测试"
echo ""
echo "示例:"
echo " $0 full # 运行完整集成测试"
echo " $0 quick # 快速健康检查"
echo " $0 performance # 仅运行性能测试"
}
# 主函数
main() {
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
case $command in
full)
run_full_integration_test
;;
quick)
quick_health_check
;;
docker)
test_docker_environment
generate_test_report 0
;;
containers)
test_container_startup
generate_test_report 0
;;
health)
test_service_health
generate_test_report 0
;;
network)
test_network_connectivity
generate_test_report 0
;;
database)
test_database_functionality
generate_test_report 0
;;
redis)
test_redis_functionality
generate_test_report 0
;;
api)
test_application_api
generate_test_report 0
;;
frontend)
test_frontend_access
generate_test_report 0
;;
performance)
test_performance
generate_test_report 0
;;
security)
test_security
generate_test_report 0
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
# 返回失败测试数量作为退出码
exit $FAILED_TESTS
}
main "$@"

61
deploy/docker/load-images.sh Executable file
View File

@ -0,0 +1,61 @@
#!/bin/bash
# QAUP 离线部署镜像导入脚本
# 此脚本在客户内网环境中运行
set -e
# 配置变量
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
IMAGES_DIR="$SCRIPT_DIR/../images"
echo "========================================="
echo "QAUP 离线部署镜像导入开始"
echo "========================================="
# 检查镜像目录是否存在
if [ ! -d "$IMAGES_DIR" ]; then
echo "错误: 镜像目录不存在: $IMAGES_DIR"
echo "请确保离线部署包已正确解压"
exit 1
fi
cd "$IMAGES_DIR"
# 验证校验和
if [ -f "checksums.sha256" ]; then
echo "验证镜像文件完整性..."
if sha256sum -c checksums.sha256; then
echo "✓ 所有镜像文件校验通过"
else
echo "✗ 镜像文件校验失败,请重新获取离线部署包"
exit 1
fi
else
echo "警告: 未找到校验和文件,跳过完整性验证"
fi
# 导入镜像
echo ""
echo "开始导入 Docker 镜像..."
for image_file in *.tar.gz; do
if [ -f "$image_file" ]; then
echo "导入镜像: $image_file"
gunzip -c "$image_file" | docker load
echo "$image_file 导入完成"
fi
done
echo ""
echo "========================================="
echo "验证导入的镜像:"
echo "========================================="
# 验证镜像是否成功导入
echo "已导入的镜像列表:"
docker images --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}" | grep -E "(qaup|openjdk|nginx|postgis|redis)"
echo ""
echo "镜像导入完成!"
echo "现在可以运行 docker-compose up 启动服务"

428
deploy/docker/log-manager.sh Executable file
View File

@ -0,0 +1,428 @@
#!/bin/bash
# QAUP 日志管理脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
LOG_DIR="$PROJECT_ROOT/logs"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 配置日志轮转
setup_log_rotation() {
print_message $BLUE "配置日志轮转..."
local logrotate_config="/etc/logrotate.d/qaup"
# 创建 logrotate 配置
sudo tee "$logrotate_config" > /dev/null << EOF
# QAUP 日志轮转配置
$LOG_DIR/app/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 644 root root
postrotate
docker exec qaup-app pkill -USR1 java || true
endscript
}
$LOG_DIR/nginx/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 644 root root
postrotate
docker exec qaup-nginx nginx -s reopen || true
endscript
}
$LOG_DIR/postgres/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 644 root root
}
$LOG_DIR/redis/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 644 root root
}
EOF
print_message $GREEN "日志轮转配置已创建: $logrotate_config"
# 测试配置
if sudo logrotate -d "$logrotate_config" &>/dev/null; then
print_message $GREEN "✓ 日志轮转配置测试通过"
else
print_message $RED "✗ 日志轮转配置测试失败"
fi
}
# 查看日志
view_logs() {
local service=$1
local lines=${2:-100}
case $service in
app|qaup-app)
print_message $BLUE "查看应用日志 (最近 $lines 行):"
docker logs --tail $lines -f qaup-app
;;
nginx|qaup-nginx)
print_message $BLUE "查看 Nginx 日志 (最近 $lines 行):"
docker logs --tail $lines -f qaup-nginx
;;
postgres|qaup-postgres)
print_message $BLUE "查看 PostgreSQL 日志 (最近 $lines 行):"
docker logs --tail $lines -f qaup-postgres
;;
redis|qaup-redis)
print_message $BLUE "查看 Redis 日志 (最近 $lines 行):"
docker logs --tail $lines -f qaup-redis
;;
all)
print_message $BLUE "查看所有服务日志 (最近 $lines 行):"
docker-compose logs --tail $lines -f
;;
*)
print_message $RED "未知服务: $service"
print_message $YELLOW "可用服务: app, nginx, postgres, redis, all"
exit 1
;;
esac
}
# 分析错误日志
analyze_error_logs() {
print_message $BLUE "分析错误日志..."
local error_report="$LOG_DIR/error_analysis_$(date +%Y%m%d_%H%M%S).txt"
{
echo "QAUP 错误日志分析报告"
echo "生成时间: $(date)"
echo "========================================"
echo ""
# 分析应用错误
echo "应用错误 (最近24小时):"
echo "------------------------"
docker logs qaup-app --since 24h 2>&1 | grep -i "error\|exception\|failed" | tail -20
echo ""
# 分析 Nginx 错误
echo "Nginx 错误 (最近24小时):"
echo "------------------------"
docker logs qaup-nginx --since 24h 2>&1 | grep -i "error\|failed" | tail -20
echo ""
# 分析数据库错误
echo "PostgreSQL 错误 (最近24小时):"
echo "-----------------------------"
docker logs qaup-postgres --since 24h 2>&1 | grep -i "error\|fatal\|panic" | tail -20
echo ""
# 分析 Redis 错误
echo "Redis 错误 (最近24小时):"
echo "------------------------"
docker logs qaup-redis --since 24h 2>&1 | grep -i "error\|warning" | tail -20
echo ""
} > "$error_report"
print_message $GREEN "错误日志分析报告已生成: $error_report"
# 显示摘要
local error_count=$(grep -c "error\|ERROR\|Error" "$error_report" || echo "0")
if [ $error_count -gt 0 ]; then
print_message $YELLOW "发现 $error_count 个错误条目"
else
print_message $GREEN "未发现明显错误"
fi
}
# 清理旧日志
cleanup_old_logs() {
local days=${1:-30}
print_message $BLUE "清理 $days 天前的日志文件..."
local cleaned_files=0
# 清理应用日志
if [ -d "$LOG_DIR/app" ]; then
local app_files=$(find "$LOG_DIR/app" -name "*.log*" -mtime +$days -type f | wc -l)
find "$LOG_DIR/app" -name "*.log*" -mtime +$days -type f -delete
cleaned_files=$((cleaned_files + app_files))
fi
# 清理 Nginx 日志
if [ -d "$LOG_DIR/nginx" ]; then
local nginx_files=$(find "$LOG_DIR/nginx" -name "*.log*" -mtime +$days -type f | wc -l)
find "$LOG_DIR/nginx" -name "*.log*" -mtime +$days -type f -delete
cleaned_files=$((cleaned_files + nginx_files))
fi
# 清理数据库日志
if [ -d "$LOG_DIR/postgres" ]; then
local postgres_files=$(find "$LOG_DIR/postgres" -name "*.log*" -mtime +$days -type f | wc -l)
find "$LOG_DIR/postgres" -name "*.log*" -mtime +$days -type f -delete
cleaned_files=$((cleaned_files + postgres_files))
fi
# 清理 Redis 日志
if [ -d "$LOG_DIR/redis" ]; then
local redis_files=$(find "$LOG_DIR/redis" -name "*.log*" -mtime +$days -type f | wc -l)
find "$LOG_DIR/redis" -name "*.log*" -mtime +$days -type f -delete
cleaned_files=$((cleaned_files + redis_files))
fi
print_message $GREEN "已清理 $cleaned_files 个旧日志文件"
}
# 压缩日志文件
compress_logs() {
print_message $BLUE "压缩日志文件..."
local compressed_count=0
# 压缩7天前的日志文件
for log_dir in "$LOG_DIR"/{app,nginx,postgres,redis}; do
if [ -d "$log_dir" ]; then
while IFS= read -r -d '' file; do
if [[ "$file" != *.gz ]]; then
gzip "$file"
compressed_count=$((compressed_count + 1))
fi
done < <(find "$log_dir" -name "*.log" -mtime +7 -type f -print0)
fi
done
print_message $GREEN "已压缩 $compressed_count 个日志文件"
}
# 导出日志
export_logs() {
local service=$1
local start_date=$2
local end_date=$3
local output_file="logs_export_$(date +%Y%m%d_%H%M%S).tar.gz"
print_message $BLUE "导出日志..."
local temp_dir="/tmp/qaup_logs_export"
mkdir -p "$temp_dir"
case $service in
app|qaup-app)
docker logs qaup-app --since "$start_date" --until "$end_date" > "$temp_dir/app.log"
;;
nginx|qaup-nginx)
docker logs qaup-nginx --since "$start_date" --until "$end_date" > "$temp_dir/nginx.log"
;;
postgres|qaup-postgres)
docker logs qaup-postgres --since "$start_date" --until "$end_date" > "$temp_dir/postgres.log"
;;
redis|qaup-redis)
docker logs qaup-redis --since "$start_date" --until "$end_date" > "$temp_dir/redis.log"
;;
all)
docker logs qaup-app --since "$start_date" --until "$end_date" > "$temp_dir/app.log"
docker logs qaup-nginx --since "$start_date" --until "$end_date" > "$temp_dir/nginx.log"
docker logs qaup-postgres --since "$start_date" --until "$end_date" > "$temp_dir/postgres.log"
docker logs qaup-redis --since "$start_date" --until "$end_date" > "$temp_dir/redis.log"
;;
*)
print_message $RED "未知服务: $service"
rm -rf "$temp_dir"
exit 1
;;
esac
# 创建压缩包
tar -czf "$output_file" -C "$temp_dir" .
rm -rf "$temp_dir"
print_message $GREEN "日志已导出到: $output_file"
}
# 实时监控日志
monitor_logs() {
local service=${1:-all}
local filter=${2:-""}
print_message $BLUE "实时监控日志 (服务: $service)"
if [ -n "$filter" ]; then
print_message $BLUE "过滤条件: $filter"
fi
print_message $YELLOW "按 Ctrl+C 停止监控"
case $service in
app|qaup-app)
if [ -n "$filter" ]; then
docker logs -f qaup-app | grep --line-buffered "$filter"
else
docker logs -f qaup-app
fi
;;
nginx|qaup-nginx)
if [ -n "$filter" ]; then
docker logs -f qaup-nginx | grep --line-buffered "$filter"
else
docker logs -f qaup-nginx
fi
;;
postgres|qaup-postgres)
if [ -n "$filter" ]; then
docker logs -f qaup-postgres | grep --line-buffered "$filter"
else
docker logs -f qaup-postgres
fi
;;
redis|qaup-redis)
if [ -n "$filter" ]; then
docker logs -f qaup-redis | grep --line-buffered "$filter"
else
docker logs -f qaup-redis
fi
;;
all)
if [ -n "$filter" ]; then
docker-compose logs -f | grep --line-buffered "$filter"
else
docker-compose logs -f
fi
;;
*)
print_message $RED "未知服务: $service"
exit 1
;;
esac
}
# 显示日志统计
show_log_stats() {
print_message $BLUE "日志统计信息:"
echo ""
print_message $BLUE "日志目录大小:"
if [ -d "$LOG_DIR" ]; then
du -sh "$LOG_DIR"/* 2>/dev/null | sort -hr
else
print_message $YELLOW "日志目录不存在: $LOG_DIR"
fi
echo ""
print_message $BLUE "容器日志大小:"
docker system df --format "table {{.Type}}\t{{.TotalCount}}\t{{.Size}}\t{{.Reclaimable}}"
echo ""
print_message $BLUE "最近24小时日志条目数:"
local containers=("qaup-app" "qaup-nginx" "qaup-postgres" "qaup-redis")
for container in "${containers[@]}"; do
local count=$(docker logs "$container" --since 24h 2>/dev/null | wc -l)
print_message $GREEN "$container: $count"
done
}
# 显示帮助信息
show_help() {
echo "QAUP 日志管理脚本"
echo ""
echo "用法: $0 [命令] [选项]"
echo ""
echo "命令:"
echo " setup 配置日志轮转"
echo " view <service> 查看服务日志"
echo " monitor <service> 实时监控日志"
echo " analyze 分析错误日志"
echo " cleanup [days] 清理旧日志 (默认30天)"
echo " compress 压缩日志文件"
echo " export <service> 导出日志"
echo " stats 显示日志统计"
echo ""
echo "服务名称: app, nginx, postgres, redis, all"
echo ""
echo "示例:"
echo " $0 view app # 查看应用日志"
echo " $0 monitor all # 监控所有服务日志"
echo " $0 cleanup 7 # 清理7天前的日志"
echo " $0 export all 2024-01-01 # 导出指定日期的日志"
}
# 主函数
main() {
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
shift
case $command in
setup)
setup_log_rotation
;;
view)
view_logs "$@"
;;
monitor)
monitor_logs "$@"
;;
analyze)
analyze_error_logs
;;
cleanup)
cleanup_old_logs "$@"
;;
compress)
compress_logs
;;
export)
export_logs "$@"
;;
stats)
show_log_stats
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"

348
deploy/docker/monitor.sh Executable file
View File

@ -0,0 +1,348 @@
#!/bin/bash
# QAUP 系统监控脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
ENV_FILE="$PROJECT_ROOT/.env"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 获取时间戳
get_timestamp() {
date '+%Y-%m-%d %H:%M:%S'
}
# 检查容器状态
check_container_status() {
print_message $BLUE "检查容器状态..."
local containers=("qaup-postgres" "qaup-redis" "qaup-app" "qaup-nginx")
local unhealthy_containers=()
for container in "${containers[@]}"; do
if docker ps --format "table {{.Names}}\t{{.Status}}" | grep -q "$container"; then
local status=$(docker ps --format "{{.Status}}" --filter "name=$container")
if [[ "$status" == *"healthy"* ]] || [[ "$status" == *"Up"* ]]; then
print_message $GREEN "$container: 运行正常"
else
print_message $RED "$container: $status"
unhealthy_containers+=("$container")
fi
else
print_message $RED "$container: 未运行"
unhealthy_containers+=("$container")
fi
done
if [ ${#unhealthy_containers[@]} -gt 0 ]; then
print_message $YELLOW "异常容器: ${unhealthy_containers[*]}"
return 1
fi
return 0
}
# 检查服务健康状态
check_service_health() {
print_message $BLUE "检查服务健康状态..."
local errors=0
# 检查数据库连接
if docker exec qaup-postgres pg_isready -h localhost -p 5432 -U postgres &>/dev/null; then
print_message $GREEN "✓ PostgreSQL: 连接正常"
else
print_message $RED "✗ PostgreSQL: 连接失败"
errors=$((errors + 1))
fi
# 检查 Redis 连接
if docker exec qaup-redis redis-cli ping &>/dev/null; then
print_message $GREEN "✓ Redis: 连接正常"
else
print_message $RED "✗ Redis: 连接失败"
errors=$((errors + 1))
fi
# 检查应用健康端点
if curl -f -s http://localhost:8080/actuator/health &>/dev/null; then
print_message $GREEN "✓ QAUP 应用: 健康检查通过"
else
print_message $RED "✗ QAUP 应用: 健康检查失败"
errors=$((errors + 1))
fi
# 检查 Nginx
if curl -f -s http://localhost/health &>/dev/null; then
print_message $GREEN "✓ Nginx: 健康检查通过"
else
print_message $RED "✗ Nginx: 健康检查失败"
errors=$((errors + 1))
fi
return $errors
}
# 检查资源使用情况
check_resource_usage() {
print_message $BLUE "检查资源使用情况..."
# 检查容器资源使用
echo ""
print_message $BLUE "容器资源使用:"
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}"
echo ""
print_message $BLUE "系统资源使用:"
# CPU 使用率
local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | sed 's/%us,//')
print_message $GREEN "CPU 使用率: $cpu_usage"
# 内存使用情况
local mem_info=$(free -h | awk 'NR==2{printf "已使用: %s/%s (%.2f%%)", $3,$2,$3*100/$2}')
print_message $GREEN "内存使用: $mem_info"
# 磁盘使用情况
local disk_usage=$(df -h . | awk 'NR==2{printf "已使用: %s/%s (%s)", $3,$2,$5}')
print_message $GREEN "磁盘使用: $disk_usage"
}
# 检查日志文件大小
check_log_sizes() {
print_message $BLUE "检查日志文件大小..."
local log_dirs=("./logs/postgres" "./logs/redis" "./logs/app" "./logs/nginx")
local large_logs=()
for log_dir in "${log_dirs[@]}"; do
if [ -d "$log_dir" ]; then
local total_size=$(du -sh "$log_dir" 2>/dev/null | cut -f1)
print_message $GREEN "$log_dir: $total_size"
# 检查是否有大于 100MB 的日志文件
while IFS= read -r -d '' file; do
local file_size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file" 2>/dev/null)
if [ $file_size -gt 104857600 ]; then # 100MB
large_logs+=("$file")
fi
done < <(find "$log_dir" -name "*.log" -print0 2>/dev/null)
fi
done
if [ ${#large_logs[@]} -gt 0 ]; then
print_message $YELLOW "发现大日志文件:"
for log in "${large_logs[@]}"; do
local size=$(du -sh "$log" | cut -f1)
print_message $YELLOW " $log: $size"
done
print_message $YELLOW "建议配置日志轮转"
fi
}
# 检查网络连接
check_network_connectivity() {
print_message $BLUE "检查网络连接..."
# 检查容器间网络连接
if docker exec qaup-app nc -z qaup-postgres 5432 &>/dev/null; then
print_message $GREEN "✓ 应用 -> 数据库: 连接正常"
else
print_message $RED "✗ 应用 -> 数据库: 连接失败"
fi
if docker exec qaup-app nc -z qaup-redis 6379 &>/dev/null; then
print_message $GREEN "✓ 应用 -> Redis: 连接正常"
else
print_message $RED "✗ 应用 -> Redis: 连接失败"
fi
if docker exec qaup-nginx nc -z qaup-app 8080 &>/dev/null; then
print_message $GREEN "✓ Nginx -> 应用: 连接正常"
else
print_message $RED "✗ Nginx -> 应用: 连接失败"
fi
}
# 生成监控报告
generate_monitoring_report() {
local timestamp=$(get_timestamp)
local report_file="./logs/monitoring_report_$(date +%Y%m%d_%H%M%S).txt"
print_message $BLUE "生成监控报告: $report_file"
{
echo "QAUP 系统监控报告"
echo "生成时间: $timestamp"
echo "========================================"
echo ""
echo "容器状态:"
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
echo "资源使用情况:"
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}"
echo ""
echo "系统资源:"
free -h
echo ""
df -h
echo ""
echo "网络状态:"
netstat -tuln | grep -E ":(80|443|8080|5432|6379) "
echo ""
} > "$report_file"
print_message $GREEN "监控报告已生成: $report_file"
}
# 持续监控模式
continuous_monitoring() {
local interval=${1:-60} # 默认60秒间隔
print_message $GREEN "开始持续监控模式 (间隔: ${interval}秒)"
print_message $YELLOW "按 Ctrl+C 停止监控"
while true; do
clear
print_message $BLUE "========================================="
print_message $BLUE "QAUP 系统监控 - $(get_timestamp)"
print_message $BLUE "========================================="
check_container_status
echo ""
check_service_health
echo ""
check_resource_usage
print_message $BLUE "========================================="
print_message $YELLOW "下次检查: $(date -d "+${interval} seconds" '+%H:%M:%S')"
sleep $interval
done
}
# 告警检查
check_alerts() {
print_message $BLUE "检查系统告警..."
local alerts=()
# 检查容器状态
local unhealthy_containers=$(docker ps -a --format "{{.Names}}" --filter "health=unhealthy")
if [ -n "$unhealthy_containers" ]; then
alerts+=("不健康的容器: $unhealthy_containers")
fi
# 检查磁盘使用率
local disk_usage=$(df . | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $disk_usage -gt 80 ]; then
alerts+=("磁盘使用率过高: ${disk_usage}%")
fi
# 检查内存使用率
local mem_usage=$(free | awk 'NR==2{printf "%.0f", $3*100/$2}')
if [ $mem_usage -gt 85 ]; then
alerts+=("内存使用率过高: ${mem_usage}%")
fi
# 输出告警
if [ ${#alerts[@]} -gt 0 ]; then
print_message $RED "发现告警:"
for alert in "${alerts[@]}"; do
print_message $RED "$alert"
done
return 1
else
print_message $GREEN "✓ 无告警"
return 0
fi
}
# 显示帮助信息
show_help() {
echo "QAUP 系统监控脚本"
echo ""
echo "用法: $0 [命令] [选项]"
echo ""
echo "命令:"
echo " status 检查系统状态"
echo " health 检查服务健康状态"
echo " resources 检查资源使用情况"
echo " logs 检查日志文件"
echo " network 检查网络连接"
echo " alerts 检查系统告警"
echo " report 生成监控报告"
echo " watch 持续监控模式"
echo ""
echo "示例:"
echo " $0 status # 检查系统状态"
echo " $0 watch 30 # 每30秒监控一次"
echo " $0 report # 生成监控报告"
}
# 主函数
main() {
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
shift
case $command in
status)
check_container_status
;;
health)
check_service_health
;;
resources)
check_resource_usage
;;
logs)
check_log_sizes
;;
network)
check_network_connectivity
;;
alerts)
check_alerts
;;
report)
generate_monitoring_report
;;
watch)
continuous_monitoring "$@"
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"

View File

@ -0,0 +1,43 @@
# 多阶段构建 - 前端构建阶段
FROM m.daocloud.io/docker.io/library/node:lts-alpine AS frontend-builder
# 设置工作目录
WORKDIR /app
# 复制前端源码
COPY qaup-ui/ ./
# 安装依赖并构建
RUN npm install && npm run build:prod
# Nginx 运行阶段
FROM m.daocloud.io/docker.io/library/nginx:stable-alpine
# 安装必要工具
RUN apk add --no-cache curl
# 创建 nginx 用户目录
RUN mkdir -p /var/cache/nginx/client_temp && \
chown -R nginx:nginx /var/cache/nginx
# 复制构建好的前端文件
COPY --from=frontend-builder /app/dist /usr/share/nginx/html
# 复制 Nginx 配置文件
COPY deploy/docker/nginx/nginx.conf /etc/nginx/nginx.conf
COPY deploy/docker/nginx/default.conf /etc/nginx/conf.d/default.conf
COPY deploy/docker/nginx/nginx-env.conf.template /etc/nginx/conf.d/nginx-env.conf.template
COPY deploy/docker/nginx/start-nginx.sh /start-nginx.sh
# 设置执行权限
RUN chmod +x /start-nginx.sh
# 暴露端口
EXPOSE 80
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost/health || exit 1
# 启动 Nginx
CMD ["/start-nginx.sh"]

View File

@ -0,0 +1,87 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# 安全头设置
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# 健康检查端点
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# API 代理到后端 - 生产环境配置
location /prod-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;
# 超时设置
proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲设置
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
# 请求体大小限制(文件上传)
client_max_body_size 20M;
}
# 兼容开发环境 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;
proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
client_max_body_size 20M;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# 前端路由处理SPA
location / {
try_files $uri $uri/ /index.html;
# HTML 文件不缓存
location ~* \.html$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
}
# 错误页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

View File

@ -0,0 +1,71 @@
# Nginx 环境配置模板
# 使用环境变量进行配置替换
server {
listen ${NGINX_PORT:-80};
server_name ${SERVER_NAME:-localhost};
root /usr/share/nginx/html;
index index.html;
# 安全头设置
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# 健康检查端点
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# API 代理到后端
location ${API_PREFIX:-/prod-api}/ {
proxy_pass http://${BACKEND_HOST:-qaup-app}:${BACKEND_PORT:-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;
# 超时设置
proxy_connect_timeout ${PROXY_CONNECT_TIMEOUT:-30s};
proxy_send_timeout ${PROXY_SEND_TIMEOUT:-60s};
proxy_read_timeout ${PROXY_READ_TIMEOUT:-60s};
# 缓冲设置
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
# 请求体大小限制
client_max_body_size ${MAX_UPLOAD_SIZE:-20M};
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires ${STATIC_CACHE_TIME:-1y};
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# 前端路由处理SPA
location / {
try_files $uri $uri/ /index.html;
# HTML 文件不缓存
location ~* \.html$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
}
# 错误页面
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

View File

@ -0,0 +1,50 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# 基本设置
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 20M;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# 包含虚拟主机配置
include /etc/nginx/conf.d/*.conf;
}

View File

@ -0,0 +1,39 @@
#!/bin/bash
# Nginx 启动脚本 - 支持环境变量配置
set -e
# 默认环境变量
export NGINX_PORT=${NGINX_PORT:-80}
export SERVER_NAME=${SERVER_NAME:-localhost}
export API_PREFIX=${API_PREFIX:-/prod-api}
export BACKEND_HOST=${BACKEND_HOST:-qaup-app}
export BACKEND_PORT=${BACKEND_PORT:-8080}
export PROXY_CONNECT_TIMEOUT=${PROXY_CONNECT_TIMEOUT:-30s}
export PROXY_SEND_TIMEOUT=${PROXY_SEND_TIMEOUT:-60s}
export PROXY_READ_TIMEOUT=${PROXY_READ_TIMEOUT:-60s}
export MAX_UPLOAD_SIZE=${MAX_UPLOAD_SIZE:-20M}
export STATIC_CACHE_TIME=${STATIC_CACHE_TIME:-1y}
echo "启动 Nginx 服务..."
echo "配置参数:"
echo " 端口: $NGINX_PORT"
echo " 服务器名: $SERVER_NAME"
echo " API 前缀: $API_PREFIX"
echo " 后端地址: $BACKEND_HOST:$BACKEND_PORT"
# 如果存在环境配置模板,则使用 envsubst 替换变量
if [ -f "/etc/nginx/conf.d/nginx-env.conf.template" ]; then
echo "使用环境变量配置模板..."
envsubst < /etc/nginx/conf.d/nginx-env.conf.template > /etc/nginx/conf.d/default.conf
rm -f /etc/nginx/conf.d/nginx-env.conf.template
fi
# 测试 Nginx 配置
echo "测试 Nginx 配置..."
nginx -t
# 启动 Nginx
echo "启动 Nginx..."
exec nginx -g "daemon off;"

711
deploy/docker/ops-manager.sh Executable file
View File

@ -0,0 +1,711 @@
#!/bin/bash
# QAUP 运维管理脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 显示系统概览
show_system_overview() {
print_message $GREEN "========================================="
print_message $GREEN "QAUP 系统概览"
print_message $GREEN "========================================="
# 系统基本信息
echo ""
print_message $BLUE "系统信息:"
echo " 主机名: $(hostname)"
echo " 操作系统: $(uname -s) $(uname -r)"
echo " 当前时间: $(date)"
echo " 运行时间: $(uptime -p 2>/dev/null || uptime | awk '{print $3,$4}' | sed 's/,//')"
# 资源使用情况
echo ""
print_message $BLUE "资源使用:"
echo " CPU 负载: $(uptime | awk -F'load average:' '{print $2}' | xargs)"
echo " 内存使用: $(free -h | awk 'NR==2{printf "%s/%s (%.2f%%)", $3,$2,$3*100/$2}')"
echo " 磁盘使用: $(df -h . | awk 'NR==2{printf "%s/%s (%s)", $3,$2,$5}')"
# Docker 信息
echo ""
print_message $BLUE "Docker 信息:"
echo " Docker 版本: $(docker --version | cut -d' ' -f3 | sed 's/,//')"
echo " 运行容器数: $(docker ps -q | wc -l)"
echo " 总容器数: $(docker ps -aq | wc -l)"
echo " 镜像数量: $(docker images -q | wc -l)"
# 服务状态
echo ""
print_message $BLUE "服务状态:"
local containers=("qaup-postgres" "qaup-redis" "qaup-app" "qaup-nginx")
for container in "${containers[@]}"; do
if docker ps --format "{{.Names}}" | grep -q "^${container}$"; then
local status=$(docker ps --format "{{.Status}}" --filter "name=$container")
print_message $GREEN "$container: $status"
else
print_message $RED "$container: 未运行"
fi
done
# 网络状态
echo ""
print_message $BLUE "网络状态:"
local ports=("80" "443" "8080")
for port in "${ports[@]}"; do
if netstat -tuln 2>/dev/null | grep -q ":$port "; then
print_message $GREEN " ✓ 端口 $port: 监听中"
else
print_message $YELLOW " ⚠ 端口 $port: 未监听"
fi
done
echo ""
}
# 快速诊断
quick_diagnosis() {
print_message $BLUE "执行快速诊断..."
local issues=()
# 检查 Docker 服务
if ! systemctl is-active --quiet docker 2>/dev/null; then
issues+=("Docker 服务未运行")
fi
# 检查容器状态
local containers=("qaup-postgres" "qaup-redis" "qaup-app" "qaup-nginx")
for container in "${containers[@]}"; do
if ! docker ps --format "{{.Names}}" | grep -q "^${container}$"; then
issues+=("容器 $container 未运行")
fi
done
# 检查磁盘空间
local disk_usage=$(df . | awk 'NR==2 {print $5}' | sed 's/%//')
if [ $disk_usage -gt 85 ]; then
issues+=("磁盘使用率过高: ${disk_usage}%")
fi
# 检查内存使用
local mem_usage=$(free | awk 'NR==2{printf "%.0f", $3*100/$2}')
if [ $mem_usage -gt 90 ]; then
issues+=("内存使用率过高: ${mem_usage}%")
fi
# 检查服务响应
if ! curl -f -s http://localhost/health &>/dev/null; then
issues+=("前端服务无响应")
fi
if ! curl -f -s http://localhost:8080/actuator/health &>/dev/null; then
issues+=("后端服务无响应")
fi
# 显示诊断结果
echo ""
if [ ${#issues[@]} -eq 0 ]; then
print_message $GREEN "✓ 系统状态正常,未发现问题"
else
print_message $YELLOW "发现以下问题:"
for issue in "${issues[@]}"; do
echo " - $issue"
done
echo ""
print_message $BLUE "建议操作:"
echo " 1. 运行详细检查: $0 health-check"
echo " 2. 查看系统日志: $0 logs"
echo " 3. 重启服务: ./deploy.sh restart"
fi
echo ""
}
# 健康检查
health_check() {
print_message $BLUE "执行系统健康检查..."
# 运行集成测试的健康检查部分
if [ -x "$PROJECT_ROOT/docker/integration-test.sh" ]; then
"$PROJECT_ROOT/docker/integration-test.sh" quick
else
print_message $YELLOW "集成测试脚本不存在,执行基本健康检查"
# 基本健康检查
local health_ok=true
# 检查容器健康状态
local containers=("qaup-postgres" "qaup-redis" "qaup-app" "qaup-nginx")
for container in "${containers[@]}"; do
if docker ps --format "{{.Names}}" | grep -q "^${container}$"; then
print_message $GREEN "$container 运行正常"
else
print_message $RED "$container 未运行"
health_ok=false
fi
done
# 检查服务响应
if curl -f -s http://localhost/health &>/dev/null; then
print_message $GREEN "✓ 前端服务响应正常"
else
print_message $RED "✗ 前端服务无响应"
health_ok=false
fi
if curl -f -s http://localhost:8080/actuator/health &>/dev/null; then
print_message $GREEN "✓ 后端服务响应正常"
else
print_message $RED "✗ 后端服务无响应"
health_ok=false
fi
if [ "$health_ok" = true ]; then
print_message $GREEN "✓ 系统健康检查通过"
return 0
else
print_message $RED "✗ 系统健康检查失败"
return 1
fi
fi
}
# 查看系统日志
view_logs() {
local service=${1:-"all"}
local lines=${2:-50}
print_message $BLUE "查看系统日志 (最近 $lines 行)..."
case $service in
"all")
echo ""
print_message $BLUE "=== 应用日志 ==="
docker logs --tail $lines qaup-app 2>/dev/null || echo "应用容器未运行"
echo ""
print_message $BLUE "=== Nginx 日志 ==="
docker logs --tail $lines qaup-nginx 2>/dev/null || echo "Nginx 容器未运行"
echo ""
print_message $BLUE "=== 数据库日志 ==="
docker logs --tail $lines qaup-postgres 2>/dev/null || echo "数据库容器未运行"
echo ""
print_message $BLUE "=== Redis 日志 ==="
docker logs --tail $lines qaup-redis 2>/dev/null || echo "Redis 容器未运行"
;;
"app"|"application")
docker logs --tail $lines -f qaup-app
;;
"nginx"|"web")
docker logs --tail $lines -f qaup-nginx
;;
"postgres"|"database"|"db")
docker logs --tail $lines -f qaup-postgres
;;
"redis"|"cache")
docker logs --tail $lines -f qaup-redis
;;
*)
print_message $RED "未知服务: $service"
print_message $BLUE "可用服务: all, app, nginx, postgres, redis"
;;
esac
}
# 系统清理
system_cleanup() {
print_message $BLUE "执行系统清理..."
# 确认操作
read -p "确定要执行系统清理吗? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
print_message $BLUE "清理操作已取消"
return
fi
local cleaned_items=0
# 清理 Docker 资源
print_message $BLUE "清理 Docker 资源..."
# 清理停止的容器
local stopped_containers=$(docker ps -aq --filter "status=exited")
if [ -n "$stopped_containers" ]; then
docker rm $stopped_containers
cleaned_items=$((cleaned_items + $(echo $stopped_containers | wc -w)))
print_message $GREEN "✓ 已清理停止的容器"
fi
# 清理未使用的镜像
local unused_images=$(docker images -f "dangling=true" -q)
if [ -n "$unused_images" ]; then
docker rmi $unused_images
cleaned_items=$((cleaned_items + $(echo $unused_images | wc -w)))
print_message $GREEN "✓ 已清理未使用的镜像"
fi
# 清理未使用的网络
docker network prune -f &>/dev/null
print_message $GREEN "✓ 已清理未使用的网络"
# 清理未使用的卷
docker volume prune -f &>/dev/null
print_message $GREEN "✓ 已清理未使用的卷"
# 清理旧日志
if [ -x "$PROJECT_ROOT/docker/log-manager.sh" ]; then
"$PROJECT_ROOT/docker/log-manager.sh" cleanup 30
print_message $GREEN "✓ 已清理旧日志文件"
fi
# 清理临时文件
find /tmp -name "*qaup*" -mtime +1 -delete 2>/dev/null || true
print_message $GREEN "✓ 已清理临时文件"
print_message $GREEN "系统清理完成,共清理 $cleaned_items 个项目"
}
# 性能分析
performance_analysis() {
print_message $BLUE "执行性能分析..."
echo ""
print_message $BLUE "=== 系统资源使用 ==="
# CPU 使用率
local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | sed 's/%us,//')
echo "CPU 使用率: $cpu_usage"
# 内存详细信息
echo ""
echo "内存使用详情:"
free -h
# 磁盘 I/O
echo ""
echo "磁盘使用情况:"
df -h
# 容器资源使用
echo ""
print_message $BLUE "=== 容器资源使用 ==="
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}"
# 网络连接
echo ""
print_message $BLUE "=== 网络连接状态 ==="
netstat -tuln | grep -E ":(80|443|8080|5432|6379) " | head -10
# 进程信息
echo ""
print_message $BLUE "=== 高资源使用进程 ==="
ps aux --sort=-%cpu | head -10
# 数据库性能
echo ""
print_message $BLUE "=== 数据库性能 ==="
if docker ps --format "{{.Names}}" | grep -q "qaup-postgres"; then
local db_connections=$(docker exec qaup-postgres psql -U postgres -d qaup -t -c "SELECT count(*) FROM pg_stat_activity;" 2>/dev/null | xargs || echo "N/A")
echo "数据库连接数: $db_connections"
local db_size=$(docker exec qaup-postgres psql -U postgres -d qaup -t -c "SELECT pg_size_pretty(pg_database_size('qaup'));" 2>/dev/null | xargs || echo "N/A")
echo "数据库大小: $db_size"
else
echo "数据库容器未运行"
fi
# Redis 性能
echo ""
print_message $BLUE "=== Redis 性能 ==="
if docker ps --format "{{.Names}}" | grep -q "qaup-redis"; then
local redis_memory=$(docker exec qaup-redis redis-cli info memory | grep "used_memory_human" | cut -d: -f2 | tr -d '\r' || echo "N/A")
echo "Redis 内存使用: $redis_memory"
local redis_keys=$(docker exec qaup-redis redis-cli dbsize 2>/dev/null || echo "N/A")
echo "Redis 键数量: $redis_keys"
else
echo "Redis 容器未运行"
fi
}
# 安全检查
security_check() {
print_message $BLUE "执行安全检查..."
local security_issues=()
# 检查文件权限
echo ""
print_message $BLUE "=== 文件权限检查 ==="
if [ -f "$PROJECT_ROOT/.env" ]; then
local env_perms=$(stat -c "%a" "$PROJECT_ROOT/.env")
if [ "$env_perms" = "600" ]; then
print_message $GREEN "✓ .env 文件权限正确 ($env_perms)"
else
print_message $YELLOW "⚠ .env 文件权限不安全 ($env_perms),建议设置为 600"
security_issues+=("环境文件权限不安全")
fi
fi
# 检查容器安全
echo ""
print_message $BLUE "=== 容器安全检查 ==="
local containers=("qaup-postgres" "qaup-redis" "qaup-app" "qaup-nginx")
for container in "${containers[@]}"; do
if docker ps --format "{{.Names}}" | grep -q "^${container}$"; then
# 检查是否以 root 用户运行
local user=$(docker exec "$container" whoami 2>/dev/null || echo "unknown")
if [ "$user" = "root" ]; then
print_message $YELLOW "$container 以 root 用户运行"
security_issues+=("$container 以 root 用户运行")
else
print_message $GREEN "$container 使用非 root 用户: $user"
fi
fi
done
# 检查网络安全
echo ""
print_message $BLUE "=== 网络安全检查 ==="
# 检查不应该暴露的端口
local sensitive_ports=("5432" "6379")
for port in "${sensitive_ports[@]}"; do
if netstat -tuln 2>/dev/null | grep -q "0.0.0.0:$port "; then
print_message $YELLOW "⚠ 敏感端口 $port 对外暴露"
security_issues+=("端口 $port 对外暴露")
else
print_message $GREEN "✓ 端口 $port 未对外暴露"
fi
done
# 检查 SSL/TLS
if [ -d "$PROJECT_ROOT/ssl" ]; then
print_message $GREEN "✓ SSL 证书目录存在"
# 检查证书文件权限
if [ -f "$PROJECT_ROOT/ssl/server.key" ]; then
local key_perms=$(stat -c "%a" "$PROJECT_ROOT/ssl/server.key")
if [ "$key_perms" = "600" ]; then
print_message $GREEN "✓ SSL 私钥权限正确"
else
print_message $YELLOW "⚠ SSL 私钥权限不安全"
security_issues+=("SSL 私钥权限不安全")
fi
fi
else
print_message $YELLOW "⚠ 未配置 SSL 证书"
security_issues+=("未配置 SSL 证书")
fi
# 显示安全检查结果
echo ""
if [ ${#security_issues[@]} -eq 0 ]; then
print_message $GREEN "✓ 安全检查通过,未发现安全问题"
else
print_message $YELLOW "发现以下安全问题:"
for issue in "${security_issues[@]}"; do
echo " - $issue"
done
echo ""
print_message $BLUE "建议操作:"
echo " 1. 运行安全配置脚本: ./docker/security-setup.sh all"
echo " 2. 检查防火墙设置: sudo ufw status"
echo " 3. 更新系统和软件包"
fi
}
# 备份管理
backup_management() {
local action=${1:-"status"}
case $action in
"status")
print_message $BLUE "备份状态:"
if [ -d "$PROJECT_ROOT/backup" ]; then
local backup_count=$(find "$PROJECT_ROOT/backup" -name "qaup_full_backup_*.tar.gz" | wc -l)
echo " 备份文件数量: $backup_count"
if [ $backup_count -gt 0 ]; then
local latest_backup=$(find "$PROJECT_ROOT/backup" -name "qaup_full_backup_*.tar.gz" -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2-)
local backup_date=$(echo "$latest_backup" | grep -o '[0-9]\{8\}_[0-9]\{6\}' | sed 's/_/ /')
local backup_size=$(du -h "$latest_backup" | cut -f1)
echo " 最新备份: $(basename "$latest_backup")"
echo " 备份时间: $backup_date"
echo " 备份大小: $backup_size"
else
print_message $YELLOW " 未找到备份文件"
fi
else
print_message $YELLOW " 备份目录不存在"
fi
;;
"create")
print_message $BLUE "创建系统备份..."
if [ -x "$PROJECT_ROOT/docker/backup-restore.sh" ]; then
"$PROJECT_ROOT/docker/backup-restore.sh" full
else
print_message $RED "备份脚本不存在"
fi
;;
"list")
if [ -x "$PROJECT_ROOT/docker/backup-restore.sh" ]; then
"$PROJECT_ROOT/docker/backup-restore.sh" list
else
print_message $RED "备份脚本不存在"
fi
;;
"cleanup")
print_message $BLUE "清理旧备份..."
if [ -x "$PROJECT_ROOT/docker/backup-restore.sh" ]; then
"$PROJECT_ROOT/docker/backup-restore.sh" cleanup 30
else
print_message $RED "备份脚本不存在"
fi
;;
*)
print_message $RED "未知备份操作: $action"
echo "可用操作: status, create, list, cleanup"
;;
esac
}
# 服务管理
service_management() {
local action=${1:-"status"}
local service=${2:-"all"}
case $action in
"start")
print_message $BLUE "启动服务: $service"
if [ "$service" = "all" ]; then
"$PROJECT_ROOT/deploy.sh" start
else
docker start "qaup-$service"
fi
;;
"stop")
print_message $BLUE "停止服务: $service"
if [ "$service" = "all" ]; then
"$PROJECT_ROOT/deploy.sh" stop
else
docker stop "qaup-$service"
fi
;;
"restart")
print_message $BLUE "重启服务: $service"
if [ "$service" = "all" ]; then
"$PROJECT_ROOT/deploy.sh" restart
else
docker restart "qaup-$service"
fi
;;
"status")
print_message $BLUE "服务状态:"
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" --filter "name=qaup-"
;;
*)
print_message $RED "未知服务操作: $action"
echo "可用操作: start, stop, restart, status"
;;
esac
}
# 生成运维报告
generate_ops_report() {
local report_file="$PROJECT_ROOT/logs/ops_report_$(date +%Y%m%d_%H%M%S).md"
print_message $BLUE "生成运维报告: $report_file"
{
echo "# QAUP 系统运维报告"
echo ""
echo "生成时间: $(date)"
echo "报告类型: 系统状态报告"
echo ""
echo "## 系统概览"
echo ""
echo "- 主机名: $(hostname)"
echo "- 操作系统: $(uname -s) $(uname -r)"
echo "- 运行时间: $(uptime -p 2>/dev/null || uptime | awk '{print $3,$4}' | sed 's/,//')"
echo "- Docker 版本: $(docker --version | cut -d' ' -f3 | sed 's/,//')"
echo ""
echo "## 资源使用"
echo ""
echo "- CPU 负载: $(uptime | awk -F'load average:' '{print $2}' | xargs)"
echo "- 内存使用: $(free -h | awk 'NR==2{printf "%s/%s (%.2f%%)", $3,$2,$3*100/$2}')"
echo "- 磁盘使用: $(df -h . | awk 'NR==2{printf "%s/%s (%s)", $3,$2,$5}')"
echo ""
echo "## 服务状态"
echo ""
echo "```"
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" --filter "name=qaup-"
echo "```"
echo ""
echo "## 容器资源使用"
echo ""
echo "```"
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}"
echo "```"
echo ""
echo "## 网络状态"
echo ""
echo "```"
netstat -tuln | grep -E ":(80|443|8080|5432|6379) " | head -10
echo "```"
echo ""
echo "## 备份状态"
echo ""
if [ -d "$PROJECT_ROOT/backup" ]; then
local backup_count=$(find "$PROJECT_ROOT/backup" -name "qaup_full_backup_*.tar.gz" | wc -l)
echo "- 备份文件数量: $backup_count"
if [ $backup_count -gt 0 ]; then
local latest_backup=$(find "$PROJECT_ROOT/backup" -name "qaup_full_backup_*.tar.gz" -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -d' ' -f2-)
local backup_date=$(echo "$latest_backup" | grep -o '[0-9]\{8\}_[0-9]\{6\}' | sed 's/_/ /')
local backup_size=$(du -h "$latest_backup" | cut -f1)
echo "- 最新备份: $(basename "$latest_backup")"
echo "- 备份时间: $backup_date"
echo "- 备份大小: $backup_size"
fi
else
echo "- 备份目录不存在"
fi
echo ""
echo "## 建议操作"
echo ""
echo "- 定期检查系统资源使用情况"
echo "- 定期执行系统备份"
echo "- 监控服务健康状态"
echo "- 清理旧日志和临时文件"
echo "- 更新系统和软件包"
echo ""
} > "$report_file"
print_message $GREEN "运维报告已生成: $report_file"
}
# 显示帮助信息
show_help() {
echo "QAUP 运维管理脚本"
echo ""
echo "用法: $0 [命令] [参数]"
echo ""
echo "系统管理命令:"
echo " overview 显示系统概览"
echo " diagnosis 快速诊断"
echo " health-check 健康检查"
echo " performance 性能分析"
echo " security 安全检查"
echo " cleanup 系统清理"
echo " report 生成运维报告"
echo ""
echo "服务管理命令:"
echo " service <action> [service] 服务管理"
echo " actions: start, stop, restart, status"
echo " services: all, app, nginx, postgres, redis"
echo ""
echo "日志管理命令:"
echo " logs [service] [lines] 查看日志"
echo " services: all, app, nginx, postgres, redis"
echo ""
echo "备份管理命令:"
echo " backup <action> 备份管理"
echo " actions: status, create, list, cleanup"
echo ""
echo "示例:"
echo " $0 overview # 显示系统概览"
echo " $0 diagnosis # 快速诊断"
echo " $0 service restart app # 重启应用服务"
echo " $0 logs app 100 # 查看应用最近100行日志"
echo " $0 backup create # 创建系统备份"
echo " $0 cleanup # 系统清理"
}
# 主函数
main() {
if [ $# -eq 0 ]; then
show_system_overview
exit 0
fi
local command=$1
shift
case $command in
overview)
show_system_overview
;;
diagnosis)
quick_diagnosis
;;
health-check|health)
health_check
;;
performance|perf)
performance_analysis
;;
security|sec)
security_check
;;
cleanup|clean)
system_cleanup
;;
report)
generate_ops_report
;;
service|svc)
service_management "$@"
;;
logs|log)
view_logs "$@"
;;
backup|bak)
backup_management "$@"
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"

View File

@ -0,0 +1,622 @@
#!/bin/bash
# QAUP 性能优化脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 获取系统信息
get_system_info() {
local total_mem=$(free -m | awk 'NR==2{print $2}')
local cpu_cores=$(nproc)
local total_disk=$(df -BG . | awk 'NR==2 {print $2}' | sed 's/G//')
echo "系统信息:"
echo " CPU 核心数: $cpu_cores"
echo " 总内存: ${total_mem}MB"
echo " 可用磁盘: ${total_disk}GB"
echo ""
# 导出变量供其他函数使用
export TOTAL_MEM=$total_mem
export CPU_CORES=$cpu_cores
export TOTAL_DISK=$total_disk
}
# 优化 JVM 参数
optimize_jvm() {
print_message $BLUE "优化 JVM 参数..."
local heap_size
local metaspace_size
# 根据系统内存计算 JVM 参数
if [ $TOTAL_MEM -ge 8192 ]; then
# 8GB+ 内存
heap_size="4g"
metaspace_size="512m"
elif [ $TOTAL_MEM -ge 4096 ]; then
# 4-8GB 内存
heap_size="2g"
metaspace_size="256m"
else
# 4GB 以下内存
heap_size="1g"
metaspace_size="128m"
fi
# 生成优化的 JVM 参数
local jvm_opts="-Dname=qaup-admin.jar"
jvm_opts="$jvm_opts -Duser.timezone=Asia/Shanghai"
jvm_opts="$jvm_opts -server"
jvm_opts="$jvm_opts -Xms${heap_size} -Xmx${heap_size}"
jvm_opts="$jvm_opts -XX:MetaspaceSize=${metaspace_size} -XX:MaxMetaspaceSize=${metaspace_size}"
# GC 优化
if [ $CPU_CORES -ge 4 ]; then
# 多核使用 G1GC
jvm_opts="$jvm_opts -XX:+UseG1GC"
jvm_opts="$jvm_opts -XX:G1HeapRegionSize=16m"
jvm_opts="$jvm_opts -XX:MaxGCPauseMillis=200"
jvm_opts="$jvm_opts -XX:+UseStringDeduplication"
else
# 少核使用 Parallel GC
jvm_opts="$jvm_opts -XX:+UseParallelGC"
jvm_opts="$jvm_opts -XX:+UseParallelOldGC"
fi
# 性能监控和调试
jvm_opts="$jvm_opts -XX:+HeapDumpOnOutOfMemoryError"
jvm_opts="$jvm_opts -XX:HeapDumpPath=/app/logs/"
jvm_opts="$jvm_opts -XX:+PrintGCDetails"
jvm_opts="$jvm_opts -XX:+PrintGCTimeStamps"
jvm_opts="$jvm_opts -Xloggc:/app/logs/gc.log"
# 更新环境文件
if [ -f "$PROJECT_ROOT/.env" ]; then
# 备份原文件
cp "$PROJECT_ROOT/.env" "$PROJECT_ROOT/.env.backup.$(date +%Y%m%d_%H%M%S)"
# 更新 JVM_OPTS
if grep -q "^JVM_OPTS=" "$PROJECT_ROOT/.env"; then
sed -i "s|^JVM_OPTS=.*|JVM_OPTS=$jvm_opts|" "$PROJECT_ROOT/.env"
else
echo "JVM_OPTS=$jvm_opts" >> "$PROJECT_ROOT/.env"
fi
print_message $GREEN "✓ JVM 参数已优化"
echo " 堆内存: $heap_size"
echo " 元空间: $metaspace_size"
echo " GC算法: $(echo $jvm_opts | grep -o 'XX:+Use[A-Za-z0-9]*GC')"
else
print_message $RED "✗ 环境文件不存在: $PROJECT_ROOT/.env"
fi
}
# 优化数据库配置
optimize_database() {
print_message $BLUE "优化数据库配置..."
local shared_buffers
local effective_cache_size
local work_mem
local maintenance_work_mem
local max_connections
# 根据系统内存计算数据库参数
if [ $TOTAL_MEM -ge 8192 ]; then
# 8GB+ 内存
shared_buffers="2GB"
effective_cache_size="6GB"
work_mem="16MB"
maintenance_work_mem="256MB"
max_connections="200"
elif [ $TOTAL_MEM -ge 4096 ]; then
# 4-8GB 内存
shared_buffers="1GB"
effective_cache_size="3GB"
work_mem="8MB"
maintenance_work_mem="128MB"
max_connections="150"
else
# 4GB 以下内存
shared_buffers="512MB"
effective_cache_size="1536MB"
work_mem="4MB"
maintenance_work_mem="64MB"
max_connections="100"
fi
# 生成优化的 PostgreSQL 配置
cat > "$PROJECT_ROOT/docker/postgres/postgresql-optimized.conf" << EOF
# PostgreSQL 性能优化配置
# 自动生成于: $(date)
# 连接设置
listen_addresses = '*'
port = 5432
max_connections = $max_connections
superuser_reserved_connections = 3
# 内存设置
shared_buffers = $shared_buffers
effective_cache_size = $effective_cache_size
work_mem = $work_mem
maintenance_work_mem = $maintenance_work_mem
# 检查点设置
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
# 查询规划器设置
random_page_cost = 1.1
effective_io_concurrency = $([ $TOTAL_DISK -gt 100 ] && echo "200" || echo "100")
# 写前日志设置
wal_level = replica
max_wal_size = 2GB
min_wal_size = 80MB
# 自动清理设置
autovacuum = on
autovacuum_max_workers = 3
autovacuum_naptime = 1min
autovacuum_vacuum_threshold = 50
autovacuum_analyze_threshold = 50
# 日志设置
logging_collector = on
log_directory = 'pg_log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_rotation_age = 1d
log_rotation_size = 100MB
log_min_duration_statement = 1000
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on
# 时区设置
timezone = 'Asia/Shanghai'
log_timezone = 'Asia/Shanghai'
# 字符集设置
lc_messages = 'en_US.UTF-8'
lc_monetary = 'en_US.UTF-8'
lc_numeric = 'en_US.UTF-8'
lc_time = 'en_US.UTF-8'
# 默认文本搜索配置
default_text_search_config = 'pg_catalog.english'
# 并发设置
max_worker_processes = $CPU_CORES
max_parallel_workers_per_gather = $([ $CPU_CORES -gt 2 ] && echo "2" || echo "1")
max_parallel_workers = $CPU_CORES
EOF
print_message $GREEN "✓ 数据库配置已优化"
echo " 共享缓冲区: $shared_buffers"
echo " 有效缓存: $effective_cache_size"
echo " 工作内存: $work_mem"
echo " 最大连接数: $max_connections"
}
# 优化 Nginx 配置
optimize_nginx() {
print_message $BLUE "优化 Nginx 配置..."
# 生成优化的 Nginx 配置
cat > "$PROJECT_ROOT/docker/nginx/nginx-optimized.conf" << EOF
# Nginx 性能优化配置
# 自动生成于: $(date)
user nginx;
worker_processes $CPU_CORES;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
# 优化事件处理
events {
worker_connections $([ $TOTAL_MEM -gt 4096 ] && echo "2048" || echo "1024");
use epoll;
multi_accept on;
worker_rlimit_nofile 65535;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" '
'\$status \$body_bytes_sent "\$http_referer" '
'"\$http_user_agent" "\$http_x_forwarded_for" '
'rt=\$request_time uct="\$upstream_connect_time" '
'uht="\$upstream_header_time" urt="\$upstream_response_time"';
access_log /var/log/nginx/access.log main;
# 基本设置
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
types_hash_max_size 2048;
server_tokens off;
# 缓冲区设置
client_body_buffer_size 128k;
client_max_body_size 50m;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
output_buffers 1 32k;
postpone_output 1460;
# 超时设置
client_header_timeout 3m;
client_body_timeout 3m;
send_timeout 3m;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# 缓存设置
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# 包含虚拟主机配置
include /etc/nginx/conf.d/*.conf;
}
EOF
print_message $GREEN "✓ Nginx 配置已优化"
echo " 工作进程数: $CPU_CORES"
echo " 连接数: $([ $TOTAL_MEM -gt 4096 ] && echo "2048" || echo "1024")"
echo " 缓冲区: 优化完成"
}
# 优化 Redis 配置
optimize_redis() {
print_message $BLUE "优化 Redis 配置..."
local maxmemory
# 根据系统内存计算 Redis 内存限制
if [ $TOTAL_MEM -ge 8192 ]; then
maxmemory="1gb"
elif [ $TOTAL_MEM -ge 4096 ]; then
maxmemory="512mb"
else
maxmemory="256mb"
fi
# 生成优化的 Redis 配置
cat > "$PROJECT_ROOT/docker/redis/redis-optimized.conf" << EOF
# Redis 性能优化配置
# 自动生成于: $(date)
# 内存设置
maxmemory $maxmemory
maxmemory-policy allkeys-lru
# 持久化设置
save 900 1
save 300 10
save 60 10000
# AOF 设置
appendonly yes
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 网络设置
tcp-keepalive 300
timeout 0
# 客户端设置
maxclients 10000
# 慢日志设置
slowlog-log-slower-than 10000
slowlog-max-len 128
# 其他优化
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
EOF
print_message $GREEN "✓ Redis 配置已优化"
echo " 最大内存: $maxmemory"
echo " 淘汰策略: allkeys-lru"
echo " 持久化: AOF + RDB"
}
# 优化系统参数
optimize_system() {
print_message $BLUE "优化系统参数..."
# 检查是否有 root 权限
if [ "$EUID" -ne 0 ]; then
print_message $YELLOW "⚠ 需要 root 权限来优化系统参数"
print_message $YELLOW "请手动执行以下命令或使用 sudo 运行此脚本:"
echo ""
echo "# 网络优化"
echo "echo 'net.core.somaxconn = 65535' >> /etc/sysctl.conf"
echo "echo 'net.core.netdev_max_backlog = 5000' >> /etc/sysctl.conf"
echo "echo 'net.ipv4.tcp_max_syn_backlog = 65535' >> /etc/sysctl.conf"
echo "echo 'net.ipv4.tcp_fin_timeout = 10' >> /etc/sysctl.conf"
echo "echo 'net.ipv4.tcp_tw_reuse = 1' >> /etc/sysctl.conf"
echo "echo 'net.ipv4.tcp_keepalive_time = 600' >> /etc/sysctl.conf"
echo ""
echo "# 文件描述符限制"
echo "echo '* soft nofile 65535' >> /etc/security/limits.conf"
echo "echo '* hard nofile 65535' >> /etc/security/limits.conf"
echo ""
echo "# 应用生效"
echo "sysctl -p"
return
fi
# 网络参数优化
cat >> /etc/sysctl.conf << EOF
# QAUP 系统优化参数
# 添加时间: $(date)
# 网络优化
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 5000
net.core.rmem_default = 262144
net.core.rmem_max = 16777216
net.core.wmem_default = 262144
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 65536 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_max_syn_backlog = 65535
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_intvl = 15
# 虚拟内存优化
vm.swappiness = 10
vm.dirty_ratio = 15
vm.dirty_background_ratio = 5
EOF
# 文件描述符限制
cat >> /etc/security/limits.conf << EOF
# QAUP 系统文件描述符限制
# 添加时间: $(date)
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535
EOF
# 应用系统参数
sysctl -p
print_message $GREEN "✓ 系统参数已优化"
}
# 创建性能监控脚本
create_performance_monitor() {
print_message $BLUE "创建性能监控脚本..."
cat > "$PROJECT_ROOT/docker/performance-monitor.sh" << 'EOF'
#!/bin/bash
# QAUP 性能监控脚本
MONITOR_LOG="./logs/performance-$(date +%Y%m%d).log"
# 记录性能数据
log_performance() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
{
echo "[$timestamp] 性能监控数据"
echo "================================"
# 系统资源
echo "系统负载: $(uptime | awk -F'load average:' '{print $2}')"
echo "内存使用: $(free | awk 'NR==2{printf "%.2f%%", $3*100/$2}')"
echo "磁盘使用: $(df -h . | awk 'NR==2{print $5}')"
# 容器资源
echo ""
echo "容器资源使用:"
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}"
# 应用性能
echo ""
echo "应用响应时间:"
local app_time=$(curl -o /dev/null -s -w '%{time_total}' http://localhost:8080/actuator/health 2>/dev/null || echo "N/A")
local nginx_time=$(curl -o /dev/null -s -w '%{time_total}' http://localhost/health 2>/dev/null || echo "N/A")
echo "应用健康检查: ${app_time}s"
echo "Nginx健康检查: ${nginx_time}s"
# 数据库性能
echo ""
echo "数据库连接数:"
local db_connections=$(docker exec qaup-postgres psql -U postgres -d qaup -t -c "SELECT count(*) FROM pg_stat_activity;" 2>/dev/null | xargs || echo "N/A")
echo "活跃连接: $db_connections"
echo "================================"
echo ""
} >> "$MONITOR_LOG"
}
# 主循环
while true; do
log_performance
sleep 300 # 每5分钟记录一次
done
EOF
chmod +x "$PROJECT_ROOT/docker/performance-monitor.sh"
print_message $GREEN "✓ 性能监控脚本已创建"
}
# 应用所有优化
apply_all_optimizations() {
print_message $GREEN "开始应用所有性能优化..."
echo ""
get_system_info
optimize_jvm
echo ""
optimize_database
echo ""
optimize_nginx
echo ""
optimize_redis
echo ""
optimize_system
echo ""
create_performance_monitor
echo ""
print_message $GREEN "========================================="
print_message $GREEN "性能优化完成!"
print_message $GREEN "========================================="
echo ""
print_message $BLUE "优化摘要:"
echo " ✓ JVM 参数已根据系统配置优化"
echo " ✓ 数据库配置已优化"
echo " ✓ Nginx 配置已优化"
echo " ✓ Redis 配置已优化"
echo " ✓ 系统参数已优化(如有权限)"
echo " ✓ 性能监控脚本已创建"
echo ""
print_message $YELLOW "下一步操作:"
echo " 1. 重启服务以应用配置: ./deploy.sh restart"
echo " 2. 启动性能监控: ./docker/performance-monitor.sh &"
echo " 3. 验证优化效果: ./docker/monitor.sh resources"
echo ""
}
# 显示帮助信息
show_help() {
echo "QAUP 性能优化脚本"
echo ""
echo "用法: $0 [命令]"
echo ""
echo "命令:"
echo " all 应用所有优化"
echo " jvm 优化 JVM 参数"
echo " database 优化数据库配置"
echo " nginx 优化 Nginx 配置"
echo " redis 优化 Redis 配置"
echo " system 优化系统参数"
echo " monitor 创建性能监控脚本"
echo ""
echo "示例:"
echo " $0 all # 应用所有优化"
echo " $0 jvm # 仅优化 JVM"
echo " sudo $0 system # 优化系统参数(需要 root"
}
# 主函数
main() {
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
# 获取系统信息
get_system_info
case $command in
all)
apply_all_optimizations
;;
jvm)
optimize_jvm
;;
database)
optimize_database
;;
nginx)
optimize_nginx
;;
redis)
optimize_redis
;;
system)
optimize_system
;;
monitor)
create_performance_monitor
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"

View File

@ -0,0 +1,650 @@
#!/bin/bash
# QAUP 数据库管理脚本
# 统一管理数据库相关的所有操作:导出、导入、备份、恢复、健康检查等
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# 默认配置
DB_HOST=${DB_HOST:-localhost}
DB_PORT=${DB_PORT:-5432}
DB_NAME=${DB_NAME:-qaup}
DB_USER=${DB_USER:-postgres}
EXPORT_DIR=${EXPORT_DIR:-$SCRIPT_DIR/export}
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 显示帮助信息
show_help() {
echo "QAUP 数据库管理脚本"
echo ""
echo "用法: $0 <命令> [选项]"
echo ""
echo "数据管理命令:"
echo " prepare-default 准备默认初始数据"
echo " prepare-export 从现有数据库导出并准备数据"
echo " prepare-custom 自定义数据组合"
echo " prepare-status 显示数据文件状态"
echo " prepare-clean 清理数据文件"
echo ""
echo "导出命令:"
echo " export-all 导出完整数据库"
echo " export-schema 仅导出表结构"
echo " export-data 仅导出数据"
echo " export-initial 导出分层初始数据(推荐)"
echo ""
echo "备份恢复命令:"
echo " backup 备份数据库"
echo " restore <file> 恢复数据库"
echo ""
echo "维护命令:"
echo " health 健康检查"
echo " test-connection 测试数据库连接"
echo ""
echo "环境变量:"
echo " DB_HOST 数据库主机默认localhost"
echo " DB_PORT 数据库端口默认5432"
echo " DB_NAME 数据库名称默认qaup"
echo " DB_USER 数据库用户默认postgres"
echo " EXPORT_DIR 导出目录(默认:./export"
echo ""
echo "示例:"
echo " $0 prepare-default # 准备默认数据"
echo " $0 prepare-export # 从现有系统导出数据"
echo " $0 export-initial # 导出分层初始数据"
echo " $0 backup # 备份数据库"
echo " $0 health # 健康检查"
}
# ==========================================
# 数据准备功能
# ==========================================
# 显示数据文件状态
show_data_status() {
print_message $BLUE "当前数据文件状态:"
echo ""
local files=(
"initial_data.sql:实际使用的初始数据"
"initial_data.template.sql:默认数据模板"
"01_system_data.sql:系统配置数据"
"02_business_data.sql:业务基础数据"
"03_sample_data.sql:示例数据"
)
for file_info in "${files[@]}"; do
local file="${file_info%%:*}"
local desc="${file_info##*:}"
local path="$SCRIPT_DIR/$file"
if [ -f "$path" ]; then
local size=$(du -h "$path" | cut -f1)
local lines=$(wc -l < "$path")
printf " %-25s %s %8s %6s 行 - %s\n" "$file" "✓" "$size" "$lines" "$desc"
else
printf " %-25s %s %8s %6s - %s\n" "$file" "✗" "-" "-" "$desc"
fi
done
echo ""
if [ -f "$SCRIPT_DIR/initial_data.sql" ]; then
print_message $GREEN "部署时将使用: initial_data.sql"
else
print_message $YELLOW "部署时将按顺序使用分层数据文件"
fi
}
# 准备默认数据
prepare_default_data() {
print_message $BLUE "准备默认模板数据..."
if [ ! -f "$SCRIPT_DIR/initial_data.template.sql" ]; then
print_message $RED "错误: 默认模板文件不存在"
exit 1
fi
cp "$SCRIPT_DIR/initial_data.template.sql" "$SCRIPT_DIR/initial_data.sql"
print_message $GREEN "✓ 已准备默认数据"
print_message $YELLOW "默认管理员账号: admin"
print_message $YELLOW "默认管理员密码: admin123"
}
# 从现有数据库导出并准备数据
prepare_export_data() {
print_message $BLUE "从现有数据库导出数据..."
# 检查连接参数
if [ -z "$DB_HOST" ] || [ -z "$DB_NAME" ] || [ -z "$DB_USER" ]; then
print_message $YELLOW "请设置数据库连接环境变量或使用默认值"
echo "当前设置:"
echo " DB_HOST=$DB_HOST"
echo " DB_PORT=$DB_PORT"
echo " DB_NAME=$DB_NAME"
echo " DB_USER=$DB_USER"
echo ""
read -p "是否继续? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# 测试连接
test_database_connection
# 导出分层数据
export_initial_data
# 使用导出的完整数据
if [ -f "$EXPORT_DIR/initial_data_complete.sql" ]; then
cp "$EXPORT_DIR/initial_data_complete.sql" "$SCRIPT_DIR/initial_data.sql"
print_message $GREEN "✓ 已导出并准备完整初始数据"
else
print_message $RED "错误: 导出失败"
exit 1
fi
}
# 自定义数据组合
prepare_custom_data() {
print_message $BLUE "自定义数据组合..."
local system_data="$SCRIPT_DIR/01_system_data.sql"
local business_data="$SCRIPT_DIR/02_business_data.sql"
local sample_data="$SCRIPT_DIR/03_sample_data.sql"
if [ ! -f "$system_data" ]; then
print_message $RED "错误: 未找到系统数据文件,请先导出数据"
print_message $YELLOW "提示: $0 export-initial"
exit 1
fi
echo "请选择要包含的数据类型:"
echo "1. 系统配置数据 (必需) ✓"
echo "2. 业务基础数据"
echo "3. 示例数据"
echo ""
local include_business=false
local include_sample=false
if [ -f "$business_data" ]; then
read -p "是否包含业务基础数据? (Y/n): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
include_business=true
fi
fi
if [ -f "$sample_data" ]; then
read -p "是否包含示例数据? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
include_sample=true
fi
fi
# 生成自定义数据文件
local output_file="$SCRIPT_DIR/initial_data.sql"
cat > "$output_file" << 'EOF'
-- QAUP 自定义初始数据组合
-- 此文件由 db-manager.sh 自动生成
EOF
# 添加系统数据
echo "-- 系统基础配置数据" >> "$output_file"
cat "$system_data" >> "$output_file"
print_message $GREEN "✓ 已包含系统配置数据"
# 添加业务数据
if [ "$include_business" = true ] && [ -f "$business_data" ]; then
echo "-- 业务基础数据" >> "$output_file"
cat "$business_data" >> "$output_file"
print_message $GREEN "✓ 已包含业务基础数据"
fi
# 添加示例数据
if [ "$include_sample" = true ] && [ -f "$sample_data" ]; then
echo "-- 示例数据" >> "$output_file"
cat "$sample_data" >> "$output_file"
print_message $GREEN "✓ 已包含示例数据"
fi
print_message $GREEN "✓ 自定义数据组合完成"
}
# 清理数据文件
clean_data_files() {
print_message $YELLOW "清理数据文件..."
read -p "确定要清理所有数据文件吗? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
return
fi
local files_to_clean=(
"initial_data.sql"
"01_system_data.sql"
"02_business_data.sql"
"03_sample_data.sql"
)
for file in "${files_to_clean[@]}"; do
local path="$SCRIPT_DIR/$file"
if [ -f "$path" ]; then
rm "$path"
print_message $GREEN "✓ 已删除 $file"
fi
done
if [ -d "$EXPORT_DIR" ]; then
rm -rf "$EXPORT_DIR"
print_message $GREEN "✓ 已清理导出目录"
fi
}
# ==========================================
# 数据导出功能
# ==========================================
# 测试数据库连接
test_database_connection() {
print_message $BLUE "测试数据库连接..."
if command -v pg_isready &> /dev/null; then
if pg_isready -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" &>/dev/null; then
print_message $GREEN "✓ 数据库连接成功"
else
print_message $RED "✗ 数据库连接失败"
exit 1
fi
else
print_message $YELLOW "警告: pg_isready 不可用,跳过连接测试"
fi
}
# 导出完整数据库
export_all_data() {
print_message $BLUE "导出完整数据库..."
test_database_connection
mkdir -p "$EXPORT_DIR"
local output_file="$EXPORT_DIR/qaup_complete_backup.sql"
pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" \
--no-owner --no-privileges --clean --if-exists \
> "$output_file"
print_message $GREEN "✓ 完整数据库导出完成: $output_file"
}
# 导出表结构
export_schema_only() {
print_message $BLUE "导出数据库表结构..."
test_database_connection
mkdir -p "$EXPORT_DIR"
local output_file="$EXPORT_DIR/qaup_database_schema.sql"
pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" \
--schema-only --no-owner --no-privileges --clean --if-exists \
> "$output_file"
print_message $GREEN "✓ 表结构导出完成: $output_file"
}
# 导出分层初始数据
export_initial_data() {
print_message $BLUE "导出分层初始数据..."
test_database_connection
mkdir -p "$EXPORT_DIR"
# 导出系统基础数据
export_system_data
# 导出业务基础数据
export_business_data
# 导出示例数据
export_sample_data
# 生成合并文件
generate_combined_data
}
# 导出系统基础数据
export_system_data() {
local output_file="$EXPORT_DIR/01_system_data.sql"
local system_tables=(
"sys_config" "sys_dict_type" "sys_dict_data" "sys_dept" "sys_post"
"sys_role" "sys_menu" "sys_user" "sys_user_role" "sys_user_post"
"sys_role_menu" "sys_role_dept" "sys_notice"
)
local table_args=""
for table in "${system_tables[@]}"; do
table_args="$table_args --table=$table"
done
cat > "$output_file" << 'EOF'
-- QAUP 系统基础配置数据
SET session_replication_role = replica;
EOF
pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" \
--data-only --no-owner --no-privileges --disable-triggers --inserts \
$table_args >> "$output_file"
cat >> "$output_file" << 'EOF'
SET session_replication_role = DEFAULT;
EOF
print_message $GREEN "✓ 系统基础数据导出完成"
}
# 导出业务基础数据
export_business_data() {
local output_file="$EXPORT_DIR/02_business_data.sql"
local business_tables=(
"sys_vehicle_type" "airport_areas" "spatial_rules"
"spatial_rule_vehicle_types" "transport_routes"
)
local table_args=""
for table in "${business_tables[@]}"; do
table_args="$table_args --table=$table"
done
cat > "$output_file" << 'EOF'
-- QAUP 业务基础数据
SET session_replication_role = replica;
EOF
pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" \
--data-only --no-owner --no-privileges --disable-triggers --inserts \
$table_args >> "$output_file" 2>/dev/null || true
cat >> "$output_file" << 'EOF'
SET session_replication_role = DEFAULT;
EOF
print_message $GREEN "✓ 业务基础数据导出完成"
}
# 导出示例数据
export_sample_data() {
local output_file="$EXPORT_DIR/03_sample_data.sql"
local sample_tables=("sys_vehicle_info")
local table_args=""
for table in "${sample_tables[@]}"; do
table_args="$table_args --table=$table"
done
cat > "$output_file" << 'EOF'
-- QAUP 示例数据
SET session_replication_role = replica;
EOF
pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" \
--data-only --no-owner --no-privileges --disable-triggers --inserts \
$table_args >> "$output_file" 2>/dev/null || true
cat >> "$output_file" << 'EOF'
SET session_replication_role = DEFAULT;
EOF
print_message $GREEN "✓ 示例数据导出完成"
}
# 生成合并的完整数据文件
generate_combined_data() {
local combined_file="$EXPORT_DIR/initial_data_complete.sql"
cat > "$combined_file" << 'EOF'
-- QAUP 系统完整初始数据
-- 此文件由 db-manager.sh 自动生成
EOF
for data_file in "01_system_data.sql" "02_business_data.sql" "03_sample_data.sql"; do
if [ -f "$EXPORT_DIR/$data_file" ]; then
cat "$EXPORT_DIR/$data_file" >> "$combined_file"
echo "" >> "$combined_file"
fi
done
print_message $GREEN "✓ 完整初始数据文件生成完成: $combined_file"
}
# ==========================================
# 备份恢复功能
# ==========================================
# 备份数据库
backup_database() {
print_message $BLUE "备份数据库..."
local timestamp=$(date +%Y%m%d_%H%M%S)
local backup_dir="$PROJECT_ROOT/backup/postgres"
local backup_file="$backup_dir/qaup_backup_$timestamp.sql"
mkdir -p "$backup_dir"
# 检查是否在容器内
if [ -f "/.dockerenv" ]; then
# 在容器内执行备份
pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" \
--no-owner --no-privileges > "$backup_file"
else
# 在容器外执行备份
test_database_connection
pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" \
--no-owner --no-privileges > "$backup_file"
fi
print_message $GREEN "✓ 数据库备份完成: $backup_file"
}
# 恢复数据库
restore_database() {
local backup_file=$1
if [ -z "$backup_file" ]; then
print_message $RED "错误: 请指定备份文件"
exit 1
fi
if [ ! -f "$backup_file" ]; then
print_message $RED "错误: 备份文件不存在: $backup_file"
exit 1
fi
print_message $BLUE "恢复数据库..."
print_message $YELLOW "警告: 这将覆盖现有数据"
read -p "确定要继续吗? (y/N): " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
# 检查是否在容器内
if [ -f "/.dockerenv" ]; then
# 在容器内执行恢复
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f "$backup_file"
else
# 在容器外执行恢复
test_database_connection
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$backup_file"
fi
print_message $GREEN "✓ 数据库恢复完成"
}
# ==========================================
# 健康检查功能
# ==========================================
# 健康检查
health_check() {
print_message $BLUE "数据库健康检查..."
# 检查是否在容器内
if [ -f "/.dockerenv" ]; then
# 容器内健康检查
container_health_check
else
# 容器外健康检查
external_health_check
fi
}
# 容器内健康检查
container_health_check() {
local errors=0
# 检查 PostgreSQL 进程
if pgrep -x postgres > /dev/null; then
print_message $GREEN "✓ PostgreSQL 进程运行正常"
else
print_message $RED "✗ PostgreSQL 进程未运行"
errors=$((errors + 1))
fi
# 检查数据库连接
if psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "SELECT 1;" > /dev/null 2>&1; then
print_message $GREEN "✓ 数据库连接正常"
else
print_message $RED "✗ 数据库连接失败"
errors=$((errors + 1))
fi
# 检查表数量
local table_count=$(psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -t -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public';" 2>/dev/null | tr -d ' ')
if [ "$table_count" -gt 0 ]; then
print_message $GREEN "✓ 数据库包含 $table_count 个表"
else
print_message $RED "✗ 数据库中没有表"
errors=$((errors + 1))
fi
if [ $errors -eq 0 ]; then
print_message $GREEN "数据库健康检查通过"
else
print_message $RED "数据库健康检查失败,发现 $errors 个问题"
exit 1
fi
}
# 容器外健康检查
external_health_check() {
test_database_connection
# 检查表数量
local table_count=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public';" 2>/dev/null | tr -d ' ')
if [ "$table_count" -gt 0 ]; then
print_message $GREEN "✓ 数据库包含 $table_count 个表"
else
print_message $RED "✗ 数据库中没有表"
exit 1
fi
print_message $GREEN "数据库健康检查通过"
}
# ==========================================
# 主函数
# ==========================================
main() {
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
shift
case $command in
# 数据准备命令
prepare-default)
prepare_default_data
;;
prepare-export)
prepare_export_data
;;
prepare-custom)
prepare_custom_data
;;
prepare-status)
show_data_status
;;
prepare-clean)
clean_data_files
;;
# 导出命令
export-all)
export_all_data
;;
export-schema)
export_schema_only
;;
export-initial)
export_initial_data
;;
# 备份恢复命令
backup)
backup_database
;;
restore)
restore_database "$@"
;;
# 维护命令
health)
health_check
;;
test-connection)
test_database_connection
;;
# 帮助
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"

View File

@ -20,7 +20,9 @@ SET client_min_messages = warning;
SET row_security = off;
--
-- Data for Name: sys_config; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_config; Type: TABLE DATA; Schema: public; Owner: -
--
SET SESSION AUTHORIZATION DEFAULT;
ALTER TABLE public.sys_config DISABLE TRIGGER ALL;
@ -36,7 +38,9 @@ INSERT INTO public.sys_config VALUES (6, '未管理车辆位置过滤开关', 'u
ALTER TABLE public.sys_config ENABLE TRIGGER ALL;
--
-- Data for Name: sys_dept; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_dept; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_dept DISABLE TRIGGER ALL;
INSERT INTO public.sys_dept VALUES (102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2021-05-26 18:56:27', '', NULL);
@ -54,7 +58,9 @@ INSERT INTO public.sys_dept VALUES (100, 0, '0', '若依科技', 0, '若依', '1
ALTER TABLE public.sys_dept ENABLE TRIGGER ALL;
--
-- Data for Name: sys_dict_data; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_dict_data; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_dict_data DISABLE TRIGGER ALL;
INSERT INTO public.sys_dict_data VALUES (1, 1, '', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', '2025-06-30 11:46:01.674133', '', NULL, '性别男');
@ -90,7 +96,9 @@ INSERT INTO public.sys_dict_data VALUES (28, 2, '失败', '1', 'sys_common_statu
ALTER TABLE public.sys_dict_data ENABLE TRIGGER ALL;
--
-- Data for Name: sys_dict_type; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_dict_type; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_dict_type DISABLE TRIGGER ALL;
INSERT INTO public.sys_dict_type VALUES (2, '菜单状态', 'sys_show_hide', '0', 'admin', '2021-05-26 18:56:30', '', NULL, '菜单状态列表');
@ -108,7 +116,9 @@ INSERT INTO public.sys_dict_type VALUES (1, '用户性别', 'sys_user_sex', '0',
ALTER TABLE public.sys_dict_type ENABLE TRIGGER ALL;
--
-- Data for Name: sys_menu; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_menu; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_menu DISABLE TRIGGER ALL;
INSERT INTO public.sys_menu VALUES (109, '在线用户', 2, 1, 'online', 'monitor/online/index', '', '', 1, 0, 'C', '0', 0, 'monitor:online:list', 'online', 'admin', '2025-06-30 11:46:01.696006', '', NULL, '在线用户菜单');
@ -223,7 +233,9 @@ INSERT INTO public.sys_menu VALUES (2027, '菜单管理', 2023, 3, 'menu', 'syst
ALTER TABLE public.sys_menu ENABLE TRIGGER ALL;
--
-- Data for Name: sys_notice; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_notice; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_notice DISABLE TRIGGER ALL;
INSERT INTO public.sys_notice VALUES (2, '维护通知2018-07-01 若依系统凌晨维护', '1', '\xe7bbb4e68aa4e58685e5aeb9', '0', 'admin', '2021-05-26 18:56:31', '', NULL, '管理员');
@ -233,7 +245,9 @@ INSERT INTO public.sys_notice VALUES (1, '温馨提醒2018-07-01 若依新版
ALTER TABLE public.sys_notice ENABLE TRIGGER ALL;
--
-- Data for Name: sys_post; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_post; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_post DISABLE TRIGGER ALL;
INSERT INTO public.sys_post VALUES (2, 'se', '项目经理', 2, '0', 'admin', '2021-05-26 18:56:28', '', NULL, '');
@ -245,7 +259,9 @@ INSERT INTO public.sys_post VALUES (1, 'ceo', '董事长', 1, '0', 'admin', '202
ALTER TABLE public.sys_post ENABLE TRIGGER ALL;
--
-- Data for Name: sys_role; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_role; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_role DISABLE TRIGGER ALL;
INSERT INTO public.sys_role VALUES (1, '超级管理员', 'admin', 1, '1', true, true, '0', '0', 'admin', '2021-05-26 18:56:28', '', NULL, '超级管理员');
@ -257,7 +273,9 @@ INSERT INTO public.sys_role VALUES (4, '普通管理员', '普通管理员', 0,
ALTER TABLE public.sys_role ENABLE TRIGGER ALL;
--
-- Data for Name: sys_role_dept; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_role_dept; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_role_dept DISABLE TRIGGER ALL;
INSERT INTO public.sys_role_dept VALUES (2, 100);
@ -268,7 +286,9 @@ INSERT INTO public.sys_role_dept VALUES (2, 105);
ALTER TABLE public.sys_role_dept ENABLE TRIGGER ALL;
--
-- Data for Name: sys_role_menu; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_role_menu; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_role_menu DISABLE TRIGGER ALL;
INSERT INTO public.sys_role_menu VALUES (2, 1);
@ -359,7 +379,9 @@ INSERT INTO public.sys_role_menu VALUES (2, 4);
ALTER TABLE public.sys_role_menu ENABLE TRIGGER ALL;
--
-- Data for Name: sys_user; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_user; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_user DISABLE TRIGGER ALL;
INSERT INTO public.sys_user VALUES (2, 105, 'ry', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2021-05-26 18:56:28', '2021-05-26 18:56:28', 'admin', '2021-05-26 18:56:28', 'admin', '2025-07-02 17:27:31.087052', '测试员');
@ -372,7 +394,9 @@ INSERT INTO public.sys_user VALUES (7, NULL, 'Ren', NULL, NULL, '18847025990', N
ALTER TABLE public.sys_user ENABLE TRIGGER ALL;
--
-- Data for Name: sys_user_post; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_user_post; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_user_post DISABLE TRIGGER ALL;
INSERT INTO public.sys_user_post VALUES (1, 1);
@ -381,7 +405,9 @@ INSERT INTO public.sys_user_post VALUES (1, 1);
ALTER TABLE public.sys_user_post ENABLE TRIGGER ALL;
--
-- Data for Name: sys_user_role; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_user_role; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_user_role DISABLE TRIGGER ALL;
INSERT INTO public.sys_user_role VALUES (1, 1);
@ -391,31 +417,67 @@ INSERT INTO public.sys_user_role VALUES (7, 3);
ALTER TABLE public.sys_user_role ENABLE TRIGGER ALL;
-- Name: sys_config_config_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_config_config_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_config_config_id_seq', 100, false);
-- Name: sys_dept_dept_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_dept_dept_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_dept_dept_id_seq', 110, false);
-- Name: sys_dict_data_dict_code_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_dict_data_dict_code_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_dict_data_dict_code_seq', 29, false);
-- Name: sys_dict_type_dict_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_dict_type_dict_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_dict_type_dict_id_seq', 11, false);
-- Name: sys_menu_menu_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_menu_menu_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_menu_menu_id_seq', 2027, true);
-- Name: sys_notice_notice_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_notice_notice_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_notice_notice_id_seq', 3, false);
-- Name: sys_post_post_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_post_post_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_post_post_id_seq', 5, false);
-- Name: sys_role_role_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_role_role_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_role_role_id_seq', 4, true);
-- Name: sys_user_user_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_user_user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_user_user_id_seq', 7, true);

View File

@ -20,7 +20,9 @@ SET client_min_messages = warning;
SET row_security = off;
--
-- Data for Name: airport_areas; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: airport_areas; Type: TABLE DATA; Schema: public; Owner: -
--
SET SESSION AUTHORIZATION DEFAULT;
ALTER TABLE public.airport_areas DISABLE TRIGGER ALL;
@ -34,7 +36,9 @@ INSERT INTO public.airport_areas VALUES (2, 'UV_TEST_AREA_B', '无人车B测试
ALTER TABLE public.airport_areas ENABLE TRIGGER ALL;
--
-- Data for Name: spatial_rules; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: spatial_rules; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.spatial_rules DISABLE TRIGGER ALL;
INSERT INTO public.spatial_rules VALUES ('GEOFENCE_UV_AREA_A_ACCESS', '无人车A区域准入控制', '限制无人车A测试区域的访问权限', 'ACCESS_CONTROL', 'ACTIVE', 1, 'AREA', 'UV_TEST_AREA_A', '{"accessControl": {"requiresPermission": true, "allowedOperationModes": ["AUTONOMOUS", "MANUAL"]}, "physicalLimits": {"maxSpeed": 20.0}, "safetyRequirements": {"minBatteryLevel": 30}}', 'WARNING', '进入无人车A测试区域需要授权', NULL, NULL, NULL, NULL, NULL, NULL, '2025-07-12 10:49:48.827433', '2025-07-12 10:49:48.827433', 'system', NULL, 0, NULL);
@ -46,7 +50,9 @@ INSERT INTO public.spatial_rules VALUES ('SPEED_LIMIT_TAXIWAY', '滑行道限速
ALTER TABLE public.spatial_rules ENABLE TRIGGER ALL;
--
-- Data for Name: spatial_rule_vehicle_types; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: spatial_rule_vehicle_types; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.spatial_rule_vehicle_types DISABLE TRIGGER ALL;
INSERT INTO public.spatial_rule_vehicle_types VALUES ('GEOFENCE_UV_AREA_A_ACCESS', 'UNMANNED_VEHICLE');
@ -63,7 +69,9 @@ INSERT INTO public.spatial_rule_vehicle_types VALUES ('SPEED_LIMIT_TAXIWAY', 'SP
ALTER TABLE public.spatial_rule_vehicle_types ENABLE TRIGGER ALL;
--
-- Data for Name: sys_vehicle_type; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_vehicle_type; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_vehicle_type DISABLE TRIGGER ALL;
INSERT INTO public.sys_vehicle_type VALUES (1, 'UV', 'UNMANNED_VEHICLE', '无人车', 'Unmanned Vehicle', 1, NULL, '无人车', false, 1, true, '自动驾驶无人车辆', '{"category": "UNMANNED", "maxSpeed": 30, "autonomous": true, "requiresPermission": true}', NULL, '2025-07-13 11:25:17.226345', NULL, '2025-07-13 11:25:17.226345', 0);
@ -91,7 +99,9 @@ INSERT INTO public.sys_vehicle_type VALUES (20, 'NM.TR', 'TRANSPORT_TRUCK', '运
ALTER TABLE public.sys_vehicle_type ENABLE TRIGGER ALL;
--
-- Data for Name: transport_routes; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: transport_routes; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.transport_routes DISABLE TRIGGER ALL;
INSERT INTO public.transport_routes VALUES (3, '特勤车鲁B123路径', 'SPECIAL_VEHICLE', '鲁B123特勤车的预定路径', '0102000020E610000002000000FB1EF5D72B055E4080EECB99ED2E4240B24AE9995E055E40E5B9BE0F072F4240', 30, 30, NULL, NULL, 'ACTIVE', false, NULL, NULL, '2025-07-11 17:56:56.179577+08', '2025-07-11 17:56:56.179577+08');
@ -103,13 +113,25 @@ INSERT INTO public.transport_routes VALUES (7, 'FLIGHT_CA3456_IN', 'AIRCRAFT', '
ALTER TABLE public.transport_routes ENABLE TRIGGER ALL;
-- Name: airport_areas_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: airport_areas_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.airport_areas_id_seq', 5, true);
-- Name: sys_vehicle_type_new_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_vehicle_type_new_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_vehicle_type_new_id_seq', 20, true);
-- Name: transport_routes_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: transport_routes_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.transport_routes_id_seq', 8, true);

View File

@ -20,7 +20,9 @@ SET client_min_messages = warning;
SET row_security = off;
--
-- Data for Name: sys_vehicle_info; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_vehicle_info; Type: TABLE DATA; Schema: public; Owner: -
--
SET SESSION AUTHORIZATION DEFAULT;
ALTER TABLE public.sys_vehicle_info DISABLE TRIGGER ALL;
@ -33,7 +35,11 @@ INSERT INTO public.sys_vehicle_info VALUES (3, '鲁B234', NULL, NULL, NULL, NULL
ALTER TABLE public.sys_vehicle_info ENABLE TRIGGER ALL;
-- Name: sys_vehicle_info_vehicle_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_vehicle_info_vehicle_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_vehicle_info_vehicle_id_seq', 8, true);

View File

@ -23,7 +23,9 @@ SET client_min_messages = warning;
SET row_security = off;
--
-- Data for Name: sys_config; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_config; Type: TABLE DATA; Schema: public; Owner: -
--
SET SESSION AUTHORIZATION DEFAULT;
ALTER TABLE public.sys_config DISABLE TRIGGER ALL;
@ -38,7 +40,9 @@ INSERT INTO public.sys_config VALUES (6, '未管理车辆位置过滤开关', 'u
ALTER TABLE public.sys_config ENABLE TRIGGER ALL;
--
-- Data for Name: sys_dept; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_dept; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_dept DISABLE TRIGGER ALL;
INSERT INTO public.sys_dept VALUES (102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '0', 'admin', '2021-05-26 18:56:27', '', NULL);
@ -56,7 +60,9 @@ INSERT INTO public.sys_dept VALUES (100, 0, '0', '若依科技', 0, '若依', '1
ALTER TABLE public.sys_dept ENABLE TRIGGER ALL;
--
-- Data for Name: sys_dict_data; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_dict_data; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_dict_data DISABLE TRIGGER ALL;
INSERT INTO public.sys_dict_data VALUES (1, 1, '', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', '2025-06-30 11:46:01.674133', '', NULL, '性别男');
@ -92,7 +98,9 @@ INSERT INTO public.sys_dict_data VALUES (28, 2, '失败', '1', 'sys_common_statu
ALTER TABLE public.sys_dict_data ENABLE TRIGGER ALL;
--
-- Data for Name: sys_dict_type; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_dict_type; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_dict_type DISABLE TRIGGER ALL;
INSERT INTO public.sys_dict_type VALUES (2, '菜单状态', 'sys_show_hide', '0', 'admin', '2021-05-26 18:56:30', '', NULL, '菜单状态列表');
@ -110,7 +118,9 @@ INSERT INTO public.sys_dict_type VALUES (1, '用户性别', 'sys_user_sex', '0',
ALTER TABLE public.sys_dict_type ENABLE TRIGGER ALL;
--
-- Data for Name: sys_menu; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_menu; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_menu DISABLE TRIGGER ALL;
INSERT INTO public.sys_menu VALUES (109, '在线用户', 2, 1, 'online', 'monitor/online/index', '', '', 1, 0, 'C', '0', 0, 'monitor:online:list', 'online', 'admin', '2025-06-30 11:46:01.696006', '', NULL, '在线用户菜单');
@ -225,7 +235,9 @@ INSERT INTO public.sys_menu VALUES (2027, '菜单管理', 2023, 3, 'menu', 'syst
ALTER TABLE public.sys_menu ENABLE TRIGGER ALL;
--
-- Data for Name: sys_notice; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_notice; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_notice DISABLE TRIGGER ALL;
INSERT INTO public.sys_notice VALUES (2, '维护通知2018-07-01 若依系统凌晨维护', '1', '\xe7bbb4e68aa4e58685e5aeb9', '0', 'admin', '2021-05-26 18:56:31', '', NULL, '管理员');
@ -235,7 +247,9 @@ INSERT INTO public.sys_notice VALUES (1, '温馨提醒2018-07-01 若依新版
ALTER TABLE public.sys_notice ENABLE TRIGGER ALL;
--
-- Data for Name: sys_post; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_post; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_post DISABLE TRIGGER ALL;
INSERT INTO public.sys_post VALUES (2, 'se', '项目经理', 2, '0', 'admin', '2021-05-26 18:56:28', '', NULL, '');
@ -247,7 +261,9 @@ INSERT INTO public.sys_post VALUES (1, 'ceo', '董事长', 1, '0', 'admin', '202
ALTER TABLE public.sys_post ENABLE TRIGGER ALL;
--
-- Data for Name: sys_role; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_role; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_role DISABLE TRIGGER ALL;
INSERT INTO public.sys_role VALUES (1, '超级管理员', 'admin', 1, '1', true, true, '0', '0', 'admin', '2021-05-26 18:56:28', '', NULL, '超级管理员');
@ -259,7 +275,9 @@ INSERT INTO public.sys_role VALUES (4, '普通管理员', '普通管理员', 0,
ALTER TABLE public.sys_role ENABLE TRIGGER ALL;
--
-- Data for Name: sys_role_dept; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_role_dept; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_role_dept DISABLE TRIGGER ALL;
INSERT INTO public.sys_role_dept VALUES (2, 100);
@ -270,7 +288,9 @@ INSERT INTO public.sys_role_dept VALUES (2, 105);
ALTER TABLE public.sys_role_dept ENABLE TRIGGER ALL;
--
-- Data for Name: sys_role_menu; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_role_menu; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_role_menu DISABLE TRIGGER ALL;
INSERT INTO public.sys_role_menu VALUES (2, 1);
@ -361,7 +381,9 @@ INSERT INTO public.sys_role_menu VALUES (2, 4);
ALTER TABLE public.sys_role_menu ENABLE TRIGGER ALL;
--
-- Data for Name: sys_user; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_user; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_user DISABLE TRIGGER ALL;
INSERT INTO public.sys_user VALUES (2, 105, 'ry', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', '2021-05-26 18:56:28', '2021-05-26 18:56:28', 'admin', '2021-05-26 18:56:28', 'admin', '2025-07-02 17:27:31.087052', '测试员');
@ -374,7 +396,9 @@ INSERT INTO public.sys_user VALUES (7, NULL, 'Ren', NULL, NULL, '18847025990', N
ALTER TABLE public.sys_user ENABLE TRIGGER ALL;
--
-- Data for Name: sys_user_post; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_user_post; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_user_post DISABLE TRIGGER ALL;
INSERT INTO public.sys_user_post VALUES (1, 1);
@ -383,7 +407,9 @@ INSERT INTO public.sys_user_post VALUES (1, 1);
ALTER TABLE public.sys_user_post ENABLE TRIGGER ALL;
--
-- Data for Name: sys_user_role; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_user_role; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_user_role DISABLE TRIGGER ALL;
INSERT INTO public.sys_user_role VALUES (1, 1);
@ -393,31 +419,67 @@ INSERT INTO public.sys_user_role VALUES (7, 3);
ALTER TABLE public.sys_user_role ENABLE TRIGGER ALL;
-- Name: sys_config_config_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_config_config_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_config_config_id_seq', 100, false);
-- Name: sys_dept_dept_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_dept_dept_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_dept_dept_id_seq', 110, false);
-- Name: sys_dict_data_dict_code_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_dict_data_dict_code_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_dict_data_dict_code_seq', 29, false);
-- Name: sys_dict_type_dict_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_dict_type_dict_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_dict_type_dict_id_seq', 11, false);
-- Name: sys_menu_menu_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_menu_menu_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_menu_menu_id_seq', 2027, true);
-- Name: sys_notice_notice_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_notice_notice_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_notice_notice_id_seq', 3, false);
-- Name: sys_post_post_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_post_post_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_post_post_id_seq', 5, false);
-- Name: sys_role_role_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_role_role_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_role_role_id_seq', 4, true);
-- Name: sys_user_user_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_user_user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_user_user_id_seq', 7, true);
@ -449,7 +511,9 @@ SET client_min_messages = warning;
SET row_security = off;
--
-- Data for Name: airport_areas; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: airport_areas; Type: TABLE DATA; Schema: public; Owner: -
--
SET SESSION AUTHORIZATION DEFAULT;
ALTER TABLE public.airport_areas DISABLE TRIGGER ALL;
@ -463,7 +527,9 @@ INSERT INTO public.airport_areas VALUES (2, 'UV_TEST_AREA_B', '无人车B测试
ALTER TABLE public.airport_areas ENABLE TRIGGER ALL;
--
-- Data for Name: spatial_rules; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: spatial_rules; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.spatial_rules DISABLE TRIGGER ALL;
INSERT INTO public.spatial_rules VALUES ('GEOFENCE_UV_AREA_A_ACCESS', '无人车A区域准入控制', '限制无人车A测试区域的访问权限', 'ACCESS_CONTROL', 'ACTIVE', 1, 'AREA', 'UV_TEST_AREA_A', '{"accessControl": {"requiresPermission": true, "allowedOperationModes": ["AUTONOMOUS", "MANUAL"]}, "physicalLimits": {"maxSpeed": 20.0}, "safetyRequirements": {"minBatteryLevel": 30}}', 'WARNING', '进入无人车A测试区域需要授权', NULL, NULL, NULL, NULL, NULL, NULL, '2025-07-12 10:49:48.827433', '2025-07-12 10:49:48.827433', 'system', NULL, 0, NULL);
@ -475,7 +541,9 @@ INSERT INTO public.spatial_rules VALUES ('SPEED_LIMIT_TAXIWAY', '滑行道限速
ALTER TABLE public.spatial_rules ENABLE TRIGGER ALL;
--
-- Data for Name: spatial_rule_vehicle_types; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: spatial_rule_vehicle_types; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.spatial_rule_vehicle_types DISABLE TRIGGER ALL;
INSERT INTO public.spatial_rule_vehicle_types VALUES ('GEOFENCE_UV_AREA_A_ACCESS', 'UNMANNED_VEHICLE');
@ -492,7 +560,9 @@ INSERT INTO public.spatial_rule_vehicle_types VALUES ('SPEED_LIMIT_TAXIWAY', 'SP
ALTER TABLE public.spatial_rule_vehicle_types ENABLE TRIGGER ALL;
--
-- Data for Name: sys_vehicle_type; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_vehicle_type; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.sys_vehicle_type DISABLE TRIGGER ALL;
INSERT INTO public.sys_vehicle_type VALUES (1, 'UV', 'UNMANNED_VEHICLE', '无人车', 'Unmanned Vehicle', 1, NULL, '无人车', false, 1, true, '自动驾驶无人车辆', '{"category": "UNMANNED", "maxSpeed": 30, "autonomous": true, "requiresPermission": true}', NULL, '2025-07-13 11:25:17.226345', NULL, '2025-07-13 11:25:17.226345', 0);
@ -520,7 +590,9 @@ INSERT INTO public.sys_vehicle_type VALUES (20, 'NM.TR', 'TRANSPORT_TRUCK', '运
ALTER TABLE public.sys_vehicle_type ENABLE TRIGGER ALL;
--
-- Data for Name: transport_routes; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: transport_routes; Type: TABLE DATA; Schema: public; Owner: -
--
ALTER TABLE public.transport_routes DISABLE TRIGGER ALL;
INSERT INTO public.transport_routes VALUES (3, '特勤车鲁B123路径', 'SPECIAL_VEHICLE', '鲁B123特勤车的预定路径', '0102000020E610000002000000FB1EF5D72B055E4080EECB99ED2E4240B24AE9995E055E40E5B9BE0F072F4240', 30, 30, NULL, NULL, 'ACTIVE', false, NULL, NULL, '2025-07-11 17:56:56.179577+08', '2025-07-11 17:56:56.179577+08');
@ -532,13 +604,25 @@ INSERT INTO public.transport_routes VALUES (7, 'FLIGHT_CA3456_IN', 'AIRCRAFT', '
ALTER TABLE public.transport_routes ENABLE TRIGGER ALL;
-- Name: airport_areas_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: airport_areas_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.airport_areas_id_seq', 5, true);
-- Name: sys_vehicle_type_new_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_vehicle_type_new_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_vehicle_type_new_id_seq', 20, true);
-- Name: transport_routes_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: transport_routes_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.transport_routes_id_seq', 8, true);
@ -570,7 +654,9 @@ SET client_min_messages = warning;
SET row_security = off;
--
-- Data for Name: sys_vehicle_info; Type: TABLE DATA; Schema: public; Owner:
-- Data for Name: sys_vehicle_info; Type: TABLE DATA; Schema: public; Owner: -
--
SET SESSION AUTHORIZATION DEFAULT;
ALTER TABLE public.sys_vehicle_info DISABLE TRIGGER ALL;
@ -583,7 +669,11 @@ INSERT INTO public.sys_vehicle_info VALUES (3, '鲁B234', NULL, NULL, NULL, NULL
ALTER TABLE public.sys_vehicle_info ENABLE TRIGGER ALL;
-- Name: sys_vehicle_info_vehicle_id_seq; Type: SEQUENCE SET; Schema: public; Owner:
--
-- Name: sys_vehicle_info_vehicle_id_seq; Type: SEQUENCE SET; Schema: public; Owner: -
--
SELECT pg_catalog.setval('public.sys_vehicle_info_vehicle_id_seq', 8, true);
@ -593,16 +683,3 @@ SELECT pg_catalog.setval('public.sys_vehicle_info_vehicle_id_seq', 8, true);
SET session_replication_role = DEFAULT;
INSERT INTO intersections (intersection_id, intersection_name, latitude, longitude, area_code, description)
VALUES
('DEFAULT_INTERSECTION', '默认路口', 36.35406879, 120.08341040, 'AREA_A', '机场主要路口,连接航站楼和跑道区域'),
('INTERSECTION_001', '主要路口1号', 36.35496367, 120.08688530, 'AREA_A', '机场主要路口,连接航站楼和跑道区域'),
('INTERSECTION_002', '次要路口2号', 36.35448347, 120.08502054, 'AREA_B', '机场次要路口,连接货运区域')
ON CONFLICT (intersection_id) DO NOTHING;
INSERT INTO traffic_lights (device_id, device_name, ip_address, intersection_id, device_type, manufacturer, model, install_date)
VALUES
('TL_001', '主路口红绿灯1号', '192.168.1.101', 'INTERSECTION_001', 'STANDARD', '海康威视', 'DS-TL100', '2024-01-01'),
('TL_002', '次路口红绿灯2号', '192.168.1.102', 'INTERSECTION_002', 'STANDARD', '大华技术', 'DH-TL200', '2024-01-15')
ON CONFLICT (device_id) DO NOTHING;

114
deploy/docker/postgres/init-db.sh Executable file
View File

@ -0,0 +1,114 @@
#!/bin/bash
# PostgreSQL 数据库初始化脚本
# 此脚本在 PostgreSQL 容器首次启动时自动执行
set -e
echo "========================================="
echo "QAUP 数据库初始化开始"
echo "========================================="
# 创建数据库(如果不存在)
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
-- 创建扩展
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;
-- 显示扩展信息
SELECT name, default_version, installed_version
FROM pg_available_extensions
WHERE name IN ('postgis', 'postgis_topology');
EOSQL
echo "✓ PostGIS 扩展安装完成"
# 导入数据库架构
if [ -f "/docker-entrypoint-initdb.d/qaup_database_schema.sql" ]; then
echo "导入数据库架构..."
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f /docker-entrypoint-initdb.d/qaup_database_schema.sql
echo "✓ 数据库架构导入完成"
else
echo "警告: 未找到数据库架构文件"
fi
# 导入初始数据(按优先级顺序)
data_files=(
"/docker-entrypoint-initdb.d/initial_data.sql"
"/docker-entrypoint-initdb.d/01_system_data.sql"
"/docker-entrypoint-initdb.d/02_business_data.sql"
"/docker-entrypoint-initdb.d/03_sample_data.sql"
)
for data_file in "${data_files[@]}"; do
if [ -f "$data_file" ]; then
echo "导入数据文件: $(basename "$data_file")"
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -f "$data_file"
echo "$(basename "$data_file") 导入完成"
# 如果找到 initial_data.sql就不再导入其他文件
if [[ "$data_file" == *"initial_data.sql" ]]; then
break
fi
fi
done
# 检查是否导入了任何数据
imported_any=false
for data_file in "${data_files[@]}"; do
if [ -f "$data_file" ]; then
imported_any=true
break
fi
done
if [ "$imported_any" = false ]; then
echo "警告: 未找到初始数据文件,系统将使用空数据库启动"
echo "建议从现有系统导出数据或使用默认初始数据"
fi
# 创建数据库用户和权限设置
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
-- 创建应用用户(如果不存在)
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '${APP_DB_USER:-qaup_app}') THEN
CREATE USER ${APP_DB_USER:-qaup_app} WITH PASSWORD '${APP_DB_PASSWORD:-qaup123}';
END IF;
END
\$\$;
-- 授予权限
GRANT CONNECT ON DATABASE ${POSTGRES_DB} TO ${APP_DB_USER:-qaup_app};
GRANT USAGE ON SCHEMA public TO ${APP_DB_USER:-qaup_app};
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO ${APP_DB_USER:-qaup_app};
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO ${APP_DB_USER:-qaup_app};
-- 为未来创建的表设置默认权限
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO ${APP_DB_USER:-qaup_app};
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO ${APP_DB_USER:-qaup_app};
EOSQL
echo "✓ 数据库用户和权限设置完成"
# 显示数据库信息
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
-- 显示数据库基本信息
SELECT
current_database() as database_name,
current_user as current_user,
version() as postgresql_version;
-- 显示表数量
SELECT
schemaname,
COUNT(*) as table_count
FROM pg_tables
WHERE schemaname = 'public'
GROUP BY schemaname;
EOSQL
echo ""
echo "========================================="
echo "QAUP 数据库初始化完成"
echo "========================================="

View File

@ -0,0 +1,305 @@
-- QAUP 系统初始数据
-- 包含系统运行所需的基础数据
-- ==========================================
-- 系统配置数据
-- ==========================================
-- 插入系统配置参数
INSERT INTO sys_config (config_id, config_name, config_key, config_value, config_type, create_by, create_time, remark) VALUES
(1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'admin', NOW(), '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow'),
(2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', NOW(), '初始化密码 123456'),
(3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', NOW(), '深色主题theme-dark浅色主题theme-light'),
(4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', NOW(), '是否开启验证码功能true开启false关闭'),
(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', NOW(), '是否开启注册用户功能true开启false关闭'),
(6, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', NOW(), '设置登录IP黑名单限制多个匹配项以;分隔,支持匹配(*通配、网段)');
-- ==========================================
-- 字典类型数据
-- ==========================================
-- 插入字典类型
INSERT INTO sys_dict_type (dict_id, dict_name, dict_type, status, create_by, create_time, remark) VALUES
(1, '用户性别', 'sys_user_sex', '0', 'admin', NOW(), '用户性别列表'),
(2, '菜单状态', 'sys_show_hide', '0', 'admin', NOW(), '菜单状态列表'),
(3, '系统开关', 'sys_normal_disable', '0', 'admin', NOW(), '系统开关列表'),
(4, '任务状态', 'sys_job_status', '0', 'admin', NOW(), '任务状态列表'),
(5, '任务分组', 'sys_job_group', '0', 'admin', NOW(), '任务分组列表'),
(6, '系统是否', 'sys_yes_no', '0', 'admin', NOW(), '系统是否列表'),
(7, '通知类型', 'sys_notice_type', '0', 'admin', NOW(), '通知类型列表'),
(8, '通知状态', 'sys_notice_status', '0', 'admin', NOW(), '通知状态列表'),
(9, '操作类型', 'sys_oper_type', '0', 'admin', NOW(), '操作类型列表'),
(10, '系统状态', 'sys_common_status', '0', 'admin', NOW(), '登录状态列表'),
(11, '车辆类型', 'sys_vehicle_type', '0', 'admin', NOW(), '车辆类型列表'),
(12, '告警级别', 'sys_alert_level', '0', 'admin', NOW(), '告警级别列表'),
(13, '违规类型', 'sys_violation_type', '0', 'admin', NOW(), '违规类型列表');
-- ==========================================
-- 字典数据
-- ==========================================
-- 用户性别
INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES
(1, 1, '', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', NOW(), '性别男'),
(2, 2, '', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', NOW(), '性别女'),
(3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', NOW(), '性别未知');
-- 菜单状态
INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES
(4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', NOW(), '显示菜单'),
(5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', NOW(), '隐藏菜单');
-- 系统开关
INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES
(6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', NOW(), '正常状态'),
(7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', NOW(), '停用状态');
-- 系统是否
INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES
(8, 1, '', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', NOW(), '系统默认是'),
(9, 2, '', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', NOW(), '系统默认否');
-- 通知类型
INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES
(10, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', NOW(), '通知'),
(11, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', NOW(), '公告');
-- 通知状态
INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES
(12, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', NOW(), '正常状态'),
(13, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', NOW(), '关闭状态');
-- 操作类型
INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES
(14, 1, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', NOW(), '其他操作'),
(15, 2, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', NOW(), '新增操作'),
(16, 3, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', NOW(), '修改操作'),
(17, 4, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', NOW(), '删除操作'),
(18, 5, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', NOW(), '授权操作'),
(19, 6, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', NOW(), '导出操作'),
(20, 7, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', NOW(), '导入操作'),
(21, 8, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', NOW(), '强退操作'),
(22, 9, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', NOW(), '生成操作'),
(23, 10, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', NOW(), '清空操作');
-- 系统状态
INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES
(24, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', NOW(), '正常状态'),
(25, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', NOW(), '停用状态');
-- 告警级别
INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES
(26, 1, '信息', 'INFO', 'sys_alert_level', '', 'info', 'N', '0', 'admin', NOW(), '信息级别'),
(27, 2, '警告', 'WARNING', 'sys_alert_level', '', 'warning', 'N', '0', 'admin', NOW(), '警告级别'),
(28, 3, '严重', 'CRITICAL', 'sys_alert_level', '', 'danger', 'N', '0', 'admin', NOW(), '严重级别'),
(29, 4, '紧急', 'EMERGENCY', 'sys_alert_level', '', 'danger', 'N', '0', 'admin', NOW(), '紧急级别');
-- 违规类型
INSERT INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark) VALUES
(30, 1, '超速', 'SPEED', 'sys_violation_type', '', 'warning', 'N', '0', 'admin', NOW(), '超速违规'),
(31, 2, '未授权进入', 'ACCESS', 'sys_violation_type', '', 'danger', 'N', '0', 'admin', NOW(), '未授权进入'),
(32, 3, '超高', 'HEIGHT', 'sys_violation_type', '', 'warning', 'N', '0', 'admin', NOW(), '超高违规'),
(33, 4, '超重', 'WEIGHT', 'sys_violation_type', '', 'warning', 'N', '0', 'admin', NOW(), '超重违规'),
(34, 5, '其他', 'OTHER', 'sys_violation_type', '', 'info', 'N', '0', 'admin', NOW(), '其他违规');
-- ==========================================
-- 部门数据
-- ==========================================
-- 插入部门信息
INSERT INTO sys_dept (dept_id, parent_id, ancestors, dept_name, order_num, leader, phone, email, status, del_flag, create_by, create_time) VALUES
(100, 0, '0', 'QAUP管理系统', 0, 'admin', '15888888888', 'admin@qaup.com', '0', '0', 'admin', NOW()),
(101, 100, '0,100', '系统管理部', 1, 'admin', '15888888888', 'admin@qaup.com', '0', '0', 'admin', NOW()),
(102, 100, '0,100', '运营管理部', 2, 'admin', '15888888888', 'admin@qaup.com', '0', '0', 'admin', NOW()),
(103, 101, '0,100,101', '研发部门', 1, 'admin', '15888888888', 'admin@qaup.com', '0', '0', 'admin', NOW()),
(104, 101, '0,100,101', '市场部门', 2, 'admin', '15888888888', 'admin@qaup.com', '0', '0', 'admin', NOW()),
(105, 101, '0,100,101', '测试部门', 3, 'admin', '15888888888', 'admin@qaup.com', '0', '0', 'admin', NOW());
-- ==========================================
-- 岗位数据
-- ==========================================
-- 插入岗位信息
INSERT INTO sys_post (post_id, post_code, post_name, post_sort, status, create_by, create_time, remark) VALUES
(1, 'ceo', '董事长', 1, '0', 'admin', NOW(), ''),
(2, 'se', '项目经理', 2, '0', 'admin', NOW(), ''),
(3, 'hr', '人力资源', 3, '0', 'admin', NOW(), ''),
(4, 'user', '普通员工', 4, '0', 'admin', NOW(), '');
-- ==========================================
-- 角色数据
-- ==========================================
-- 插入角色信息
INSERT INTO sys_role (role_id, role_name, role_key, role_sort, data_scope, menu_check_strictly, dept_check_strictly, status, del_flag, create_by, create_time, remark) VALUES
(1, '超级管理员', 'admin', 1, '1', true, true, '0', '0', 'admin', NOW(), '超级管理员'),
(2, '普通角色', 'common', 2, '2', true, true, '0', '0', 'admin', NOW(), '普通角色');
-- ==========================================
-- 菜单数据
-- ==========================================
-- 插入菜单信息(主要菜单结构)
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) VALUES
-- 系统管理
(1, '系统管理', 0, 1, 'system', NULL, '', '', 1, 0, 'M', '0', 0, '', 'system', 'admin', NOW(), '系统管理目录'),
(100, '用户管理', 1, 1, 'user', 'system/user/index', '', '', 1, 0, 'C', '0', 0, 'system:user:list', 'user', 'admin', NOW(), '用户管理菜单'),
(101, '角色管理', 1, 2, 'role', 'system/role/index', '', '', 1, 0, 'C', '0', 0, 'system:role:list', 'peoples', 'admin', NOW(), '角色管理菜单'),
(102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', '', 1, 0, 'C', '0', 0, 'system:menu:list', 'tree-table', 'admin', NOW(), '菜单管理菜单'),
(103, '部门管理', 1, 4, 'dept', 'system/dept/index', '', '', 1, 0, 'C', '0', 0, 'system:dept:list', 'tree', 'admin', NOW(), '部门管理菜单'),
(104, '岗位管理', 1, 5, 'post', 'system/post/index', '', '', 1, 0, 'C', '0', 0, 'system:post:list', 'post', 'admin', NOW(), '岗位管理菜单'),
(105, '字典管理', 1, 6, 'dict', 'system/dict/index', '', '', 1, 0, 'C', '0', 0, 'system:dict:list', 'dict', 'admin', NOW(), '字典管理菜单'),
(106, '参数设置', 1, 7, 'config', 'system/config/index', '', '', 1, 0, 'C', '0', 0, 'system:config:list', 'edit', 'admin', NOW(), '参数设置菜单'),
(107, '通知公告', 1, 8, 'notice', 'system/notice/index', '', '', 1, 0, 'C', '0', 0, 'system:notice:list', 'message', 'admin', NOW(), '通知公告菜单'),
(108, '日志管理', 1, 9, 'log', '', '', '', 1, 0, 'M', '0', 0, '', 'log', 'admin', NOW(), '日志管理菜单'),
(500, '操作日志', 108, 1, 'operlog', 'monitor/operlog/index', '', '', 1, 0, 'C', '0', 0, 'monitor:operlog:list', 'form', 'admin', NOW(), '操作日志菜单'),
(501, '登录日志', 108, 2, 'logininfor', 'monitor/logininfor/index', '', '', 1, 0, 'C', '0', 0, 'monitor:logininfor:list', 'logininfor', 'admin', NOW(), '登录日志菜单'),
-- 车辆管理
(2, '车辆管理', 0, 2, 'vehicle', NULL, '', '', 1, 0, 'M', '0', 0, '', 'guide', 'admin', NOW(), '车辆管理目录'),
(200, '车辆信息', 2, 1, 'info', 'vehicle/info/index', '', '', 1, 0, 'C', '0', 0, 'vehicle:info:list', 'tree-table', 'admin', NOW(), '车辆信息菜单'),
(201, '车辆类型', 2, 2, 'type', 'vehicle/type/index', '', '', 1, 0, 'C', '0', 0, 'vehicle:type:list', 'dict', 'admin', NOW(), '车辆类型菜单'),
-- 监控管理
(3, '监控管理', 0, 3, 'monitor', NULL, '', '', 1, 0, 'M', '0', 0, '', 'monitor', 'admin', NOW(), '监控管理目录'),
(300, '实时监控', 3, 1, 'realtime', 'monitor/realtime/index', '', '', 1, 0, 'C', '0', 0, 'monitor:realtime:view', 'online', 'admin', NOW(), '实时监控菜单'),
(301, '告警管理', 3, 2, 'alert', 'monitor/alert/index', '', '', 1, 0, 'C', '0', 0, 'monitor:alert:list', 'message', 'admin', NOW(), '告警管理菜单');
-- ==========================================
-- 用户数据
-- ==========================================
-- 插入管理员用户密码admin123已加密
INSERT INTO sys_user (user_id, dept_id, user_name, user_type, email, phonenumber, sex, avatar, password, status, del_flag, create_by, create_time, remark) VALUES
(1, 103, 'admin', '00', 'admin@qaup.com', '15888888888', '1', '', '$2a$10$7JB720yubVSOfvVMe6/YqOsrtwt07CFgLjxkKyQbCpTHWpK7S/pSe', '0', '0', 'admin', NOW(), '管理员');
-- 用户角色关联
INSERT INTO sys_user_role (user_id, role_id) VALUES (1, 1);
-- 用户岗位关联
INSERT INTO sys_user_post (user_id, post_id) VALUES (1, 1);
-- 角色菜单关联(超级管理员拥有所有菜单权限)
INSERT INTO sys_role_menu (role_id, menu_id) VALUES
(1, 1), (1, 100), (1, 101), (1, 102), (1, 103), (1, 104), (1, 105), (1, 106), (1, 107), (1, 108), (1, 500), (1, 501),
(1, 2), (1, 200), (1, 201),
(1, 3), (1, 300), (1, 301);
-- ==========================================
-- 车辆类型数据
-- ==========================================
-- 插入车辆类型数据(基于路径编码模式)
INSERT INTO sys_vehicle_type (id, type_code, type_name, display_name_cn, display_name_en, path_level, parent_code, full_path, is_leaf, sort_order, enabled, description, create_by, create_time) VALUES
-- 一级分类
(1, 'UV', 'UNMANNED_VEHICLE', '无人车', 'Unmanned Vehicle', 1, NULL, '无人车', false, 1, true, '机场无人驾驶车辆', 'admin', NOW()),
(2, 'SP', 'SPECIAL_VEHICLE', '特种车辆', 'Special Vehicle', 1, NULL, '特种车辆', false, 2, true, '机场特种作业车辆', 'admin', NOW()),
(3, 'NV', 'NORMAL_VEHICLE', '普通车辆', 'Normal Vehicle', 1, NULL, '普通车辆', false, 3, true, '机场普通车辆', 'admin', NOW()),
-- 无人车二级分类
(4, 'UV.PT', 'PATROL', '巡逻车', 'Patrol Vehicle', 2, 'UV', '无人车 > 巡逻车', true, 1, true, '无人巡逻车', 'admin', NOW()),
(5, 'UV.CL', 'CLEANING', '清洁车', 'Cleaning Vehicle', 2, 'UV', '无人车 > 清洁车', true, 2, true, '无人清洁车', 'admin', NOW()),
(6, 'UV.TR', 'TRANSPORT', '运输车', 'Transport Vehicle', 2, 'UV', '无人车 > 运输车', true, 3, true, '无人运输车', 'admin', NOW()),
-- 特种车辆二级分类
(7, 'SP.FF', 'FIRE_FIGHTING', '消防车', 'Fire Fighting Vehicle', 2, 'SP', '特种车辆 > 消防车', true, 1, true, '消防救援车辆', 'admin', NOW()),
(8, 'SP.AM', 'AMBULANCE', '救护车', 'Ambulance', 2, 'SP', '特种车辆 > 救护车', true, 2, true, '医疗救护车辆', 'admin', NOW()),
(9, 'SP.MT', 'MAINTENANCE', '维修车', 'Maintenance Vehicle', 2, 'SP', '特种车辆 > 维修车', true, 3, true, '设备维修车辆', 'admin', NOW()),
-- 普通车辆二级分类
(10, 'NV.BU', 'BUS', '班车', 'Bus', 2, 'NV', '普通车辆 > 班车', true, 1, true, '员工班车', 'admin', NOW()),
(11, 'NV.CA', 'CAR', '轿车', 'Car', 2, 'NV', '普通车辆 > 轿车', true, 2, true, '普通轿车', 'admin', NOW()),
(12, 'NV.VN', 'VAN', '面包车', 'Van', 2, 'NV', '普通车辆 > 面包车', true, 3, true, '面包车', 'admin', NOW());
-- ==========================================
-- 机场区域数据(示例)
-- ==========================================
-- 插入示例机场区域
INSERT INTO airport_areas (id, area_id, name, type, boundary, description, enabled, priority, area_sqm, functional_category, created_at, updated_at) VALUES
(1, 'RUNWAY_01', '01号跑道', 'RUNWAY',
ST_GeomFromText('POLYGON((116.3 39.9, 116.4 39.9, 116.4 39.95, 116.3 39.95, 116.3 39.9))', 4326),
'主跑道区域', true, 10, 50000, 'FLIGHT_OPERATIONS', NOW(), NOW()),
(2, 'TAXIWAY_A', 'A滑行道', 'TAXIWAY',
ST_GeomFromText('POLYGON((116.35 39.92, 116.38 39.92, 116.38 39.93, 116.35 39.93, 116.35 39.92))', 4326),
'A滑行道区域', true, 8, 15000, 'FLIGHT_OPERATIONS', NOW(), NOW()),
(3, 'APRON_01', '01号停机坪', 'APRON',
ST_GeomFromText('POLYGON((116.32 39.88, 116.36 39.88, 116.36 39.91, 116.32 39.91, 116.32 39.88))', 4326),
'01号停机坪区域', true, 6, 30000, 'AIRCRAFT_PARKING', NOW(), NOW());
-- ==========================================
-- 空间规则数据(示例)
-- ==========================================
-- 插入示例空间规则
INSERT INTO spatial_rules (rule_id, rule_name, description, rule_category, status, priority, spatial_object_type, spatial_object_id, rule_parameters, alert_level, alert_message, created_at, updated_at, created_by) VALUES
('SPEED_LIMIT_RUNWAY', '跑道限速规则', '跑道区域车辆限速5km/h', 'SPEED_LIMIT', 'ACTIVE', 10, 'AREA', 'RUNWAY_01',
'{"maxSpeed": 5.0, "warningThreshold": 4.0}', 'CRITICAL', '车辆在跑道区域超速', NOW(), NOW(), 'admin'),
('SPEED_LIMIT_TAXIWAY', '滑行道限速规则', '滑行道区域车辆限速10km/h', 'SPEED_LIMIT', 'ACTIVE', 8, 'AREA', 'TAXIWAY_A',
'{"maxSpeed": 10.0, "warningThreshold": 8.0}', 'WARNING', '车辆在滑行道区域超速', NOW(), NOW(), 'admin'),
('ACCESS_CONTROL_RUNWAY', '跑道准入控制', '只允许特种车辆进入跑道', 'ACCESS_CONTROL', 'ACTIVE', 10, 'AREA', 'RUNWAY_01',
'{"allowedVehicleTypes": ["SP"]}', 'CRITICAL', '未授权车辆进入跑道区域', NOW(), NOW(), 'admin');
-- 规则适用车辆类型关联
INSERT INTO spatial_rule_vehicle_types (rule_id, vehicle_type) VALUES
('SPEED_LIMIT_RUNWAY', 'SP.FF'),
('SPEED_LIMIT_RUNWAY', 'SP.AM'),
('SPEED_LIMIT_RUNWAY', 'SP.MT'),
('SPEED_LIMIT_TAXIWAY', 'UV.PT'),
('SPEED_LIMIT_TAXIWAY', 'UV.CL'),
('SPEED_LIMIT_TAXIWAY', 'SP.FF'),
('ACCESS_CONTROL_RUNWAY', 'SP.FF'),
('ACCESS_CONTROL_RUNWAY', 'SP.AM');
-- ==========================================
-- 示例车辆数据
-- ==========================================
-- 插入示例车辆信息
INSERT INTO sys_vehicle_info (vehicle_id, license_plate, vin_number, brand, owning_unit, contact_person, phone_number, type_code, create_by, create_time, remark) VALUES
(1, '京A12345', 'WVWZZZ1JZ3W386752', '大众', '机场运营部', '张三', '13800138001', 'UV.PT', 'admin', NOW(), '无人巡逻车001'),
(2, '京A12346', 'WVWZZZ1JZ3W386753', '比亚迪', '机场运营部', '李四', '13800138002', 'UV.CL', 'admin', NOW(), '无人清洁车001'),
(3, '京A12347', 'WVWZZZ1JZ3W386754', '奔驰', '机场消防队', '王五', '13800138003', 'SP.FF', 'admin', NOW(), '消防车001'),
(4, '京A12348', 'WVWZZZ1JZ3W386755', '福特', '机场医疗中心', '赵六', '13800138004', 'SP.AM', 'admin', NOW(), '救护车001');
-- ==========================================
-- 系统通知数据
-- ==========================================
-- 插入系统通知
INSERT INTO sys_notice (notice_id, notice_title, notice_type, notice_content, status, create_by, create_time, remark) VALUES
(1, '温馨提醒2024-01-01 QAUP系统上线', '2', '各位用户QAUP机场无人车管理系统已正式上线请及时登录系统进行相关操作。', '0', 'admin', NOW(), '管理员'),
(2, '维护通知:系统将于本周日凌晨进行维护', '1', '为了更好地为您提供服务QAUP系统将于本周日凌晨2:00-4:00进行系统维护届时系统将暂停服务请您合理安排时间。给您带来的不便敬请谅解', '0', 'admin', NOW(), '管理员');
-- ==========================================
-- 更新序列值
-- ==========================================
-- 更新所有序列的当前值,确保后续插入不会冲突
SELECT setval('sys_config_config_id_seq', (SELECT MAX(config_id) FROM sys_config));
SELECT setval('sys_dict_type_dict_id_seq', (SELECT MAX(dict_id) FROM sys_dict_type));
SELECT setval('sys_dict_data_dict_code_seq', (SELECT MAX(dict_code) FROM sys_dict_data));
SELECT setval('sys_dept_dept_id_seq', (SELECT MAX(dept_id) FROM sys_dept));
SELECT setval('sys_post_post_id_seq', (SELECT MAX(post_id) FROM sys_post));
SELECT setval('sys_role_role_id_seq', (SELECT MAX(role_id) FROM sys_role));
SELECT setval('sys_menu_menu_id_seq', (SELECT MAX(menu_id) FROM sys_menu));
SELECT setval('sys_user_user_id_seq', (SELECT MAX(user_id) FROM sys_user));
SELECT setval('sys_vehicle_type_new_id_seq', (SELECT MAX(id) FROM sys_vehicle_type));
SELECT setval('sys_vehicle_info_vehicle_id_seq', (SELECT MAX(vehicle_id) FROM sys_vehicle_info));
SELECT setval('airport_areas_id_seq', (SELECT MAX(id) FROM airport_areas));
SELECT setval('sys_notice_notice_id_seq', (SELECT MAX(notice_id) FROM sys_notice));
-- ==========================================
-- 完成提示
-- ==========================================
-- 显示初始化完成信息
DO $$
BEGIN
RAISE NOTICE '========================================';
RAISE NOTICE 'QAUP 系统初始数据导入完成!';
RAISE NOTICE '========================================';
RAISE NOTICE '默认管理员账号admin';
RAISE NOTICE '默认管理员密码admin123';
RAISE NOTICE '========================================';
END $$;

View File

@ -0,0 +1,54 @@
# PostgreSQL 生产环境配置
# 针对 QAUP 应用优化
# 连接设置
listen_addresses = '*'
port = 5432
max_connections = 200
superuser_reserved_connections = 3
# 内存设置
shared_buffers = 256MB
effective_cache_size = 1GB
work_mem = 4MB
maintenance_work_mem = 64MB
# 检查点设置
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
# 日志设置
logging_collector = on
log_directory = 'pg_log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_rotation_age = 1d
log_rotation_size = 100MB
log_min_duration_statement = 1000
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on
# 性能优化
random_page_cost = 1.1
effective_io_concurrency = 200
# 自动清理设置
autovacuum = on
autovacuum_max_workers = 3
autovacuum_naptime = 1min
# 时区设置
timezone = 'Asia/Shanghai'
log_timezone = 'Asia/Shanghai'
# 字符集设置
lc_messages = 'en_US.UTF-8'
lc_monetary = 'en_US.UTF-8'
lc_numeric = 'en_US.UTF-8'
lc_time = 'en_US.UTF-8'
# 默认文本搜索配置
default_text_search_config = 'pg_catalog.english'

View File

@ -0,0 +1,455 @@
#!/bin/bash
# QAUP 离线部署镜像准备脚本
# 此脚本需要在有网络连接的环境中运行,用于准备离线部署所需的所有镜像
set -e
# 显示帮助信息
show_help() {
echo "QAUP 离线部署镜像准备脚本"
echo ""
echo "用法: $0 [选项]"
echo ""
echo "选项:"
echo " -h, --help 显示帮助信息"
echo " --no-build 跳过自定义镜像构建(仅拉取基础镜像)"
echo " --clean 清理现有的离线部署目录"
echo ""
echo "功能:"
echo " - 自动检测 docker-compose.yml 中的镜像版本"
echo " - 拉取所有必需的基础镜像"
echo " - 构建 QAUP 自定义镜像"
echo " - 导出所有镜像为 tar.gz 文件"
echo " - 生成校验和文件"
echo " - 创建导入脚本和部署包"
echo ""
echo "系统要求:"
echo " - Docker 已安装并运行"
echo " - 网络连接正常"
echo " - 足够的磁盘空间(建议 10GB+"
}
# 检查参数
NO_BUILD=false
CLEAN=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
--no-build)
NO_BUILD=true
shift
;;
--clean)
CLEAN=true
shift
;;
*)
echo "未知选项: $1"
show_help
exit 1
;;
esac
done
# 检查 Docker 是否可用
if ! command -v docker &> /dev/null; then
echo "错误: Docker 未安装或不在 PATH 中"
exit 1
fi
if ! docker info &> /dev/null; then
echo "错误: Docker 服务未运行"
exit 1
fi
# 配置变量
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
IMAGES_DIR="$PROJECT_ROOT/offline-deploy/images"
CONFIG_DIR="$PROJECT_ROOT/offline-deploy/config"
SCRIPTS_DIR="$PROJECT_ROOT/offline-deploy/scripts"
# 加载镜像版本配置
IMAGE_VERSIONS_FILE="$SCRIPT_DIR/image-versions.env"
if [ -f "$IMAGE_VERSIONS_FILE" ]; then
echo "从配置文件加载镜像版本: $IMAGE_VERSIONS_FILE"
source "$IMAGE_VERSIONS_FILE"
else
echo "警告: 镜像版本配置文件不存在,使用默认版本"
# 默认版本(如果配置文件不存在)
POSTGRES_IMAGE="postgis/postgis:16-3.4"
REDIS_IMAGE="redis:7.2-alpine"
OPENJDK_IMAGE="openjdk:21-jre-slim"
MAVEN_IMAGE="maven:3.9-openjdk-21-slim"
NGINX_IMAGE="nginx:1.25-alpine"
NODE_IMAGE="node:20-alpine"
QAUP_APP_IMAGE="qaup-app:latest"
QAUP_NGINX_IMAGE="qaup-nginx:latest"
fi
echo "使用的镜像版本:"
echo " PostgreSQL/PostGIS: $POSTGRES_IMAGE"
echo " Redis: $REDIS_IMAGE"
echo " Maven: $MAVEN_IMAGE"
echo " OpenJDK: $OPENJDK_IMAGE"
echo " Nginx: $NGINX_IMAGE"
echo " Node.js: $NODE_IMAGE"
echo " QAUP App: $QAUP_APP_IMAGE"
echo " QAUP Nginx: $QAUP_NGINX_IMAGE"
echo ""
# 检查是否需要更新 docker-compose.yml 和 Dockerfile
echo "检查配置文件同步状态..."
COMPOSE_POSTGRES=$(grep -A 1 "qaup-postgres:" "$PROJECT_ROOT/docker-compose.yml" | grep "image:" | awk '{print $2}' | head -1)
COMPOSE_REDIS=$(grep -A 1 "qaup-redis:" "$PROJECT_ROOT/docker-compose.yml" | grep "image:" | awk '{print $2}' | head -1)
if [ "$COMPOSE_POSTGRES" != "$POSTGRES_IMAGE" ]; then
echo "警告: docker-compose.yml 中的 PostgreSQL 版本 ($COMPOSE_POSTGRES) 与配置文件不一致 ($POSTGRES_IMAGE)"
fi
if [ "$COMPOSE_REDIS" != "$REDIS_IMAGE" ]; then
echo "警告: docker-compose.yml 中的 Redis 版本 ($COMPOSE_REDIS) 与配置文件不一致 ($REDIS_IMAGE)"
fi
echo ""
# 自定义镜像名称
QAUP_APP_IMAGE="qaup-app:latest"
QAUP_NGINX_IMAGE="qaup-nginx:latest"
echo "========================================="
echo "QAUP 离线部署镜像准备开始"
echo "========================================="
# 清理现有目录(如果需要)
if [ "$CLEAN" = true ] && [ -d "$PROJECT_ROOT/offline-deploy" ]; then
echo "清理现有离线部署目录..."
rm -rf "$PROJECT_ROOT/offline-deploy"
fi
# 创建目录结构
echo "创建离线部署目录结构..."
mkdir -p "$IMAGES_DIR" "$CONFIG_DIR" "$SCRIPTS_DIR"
# 拉取基础镜像函数
pull_image() {
local image=$1
echo "拉取镜像: $image"
if docker pull "$image"; then
echo "$image 拉取成功"
else
echo "$image 拉取失败"
exit 1
fi
}
# 拉取基础镜像
echo "拉取基础镜像..."
pull_image "$OPENJDK_IMAGE"
pull_image "$NGINX_IMAGE"
pull_image "$POSTGRES_IMAGE"
pull_image "$REDIS_IMAGE"
pull_image "$NODE_IMAGE"
pull_image "$MAVEN_IMAGE"
# 构建自定义镜像
echo "构建 QAUP 应用镜像..."
cd "$PROJECT_ROOT"
if docker build -f deploy/Dockerfile -t $QAUP_APP_IMAGE .; then
echo "✓ QAUP 应用镜像构建成功"
else
echo "✗ QAUP 应用镜像构建失败"
exit 1
fi
echo "构建 QAUP Nginx 镜像..."
if docker build -f deploy/docker/nginx/Dockerfile -t $QAUP_NGINX_IMAGE .; then
echo "✓ QAUP Nginx 镜像构建成功"
else
echo "✗ QAUP Nginx 镜像构建失败"
exit 1
fi
# 导出镜像函数
export_image() {
local image=$1
local filename=$2
echo "导出镜像: $image -> $filename"
if docker save "$image" | gzip > "$IMAGES_DIR/$filename"; then
echo "$filename 导出成功"
else
echo "$filename 导出失败"
exit 1
fi
}
# 导出镜像文件
echo "导出镜像文件..."
export_image "$OPENJDK_IMAGE" "openjdk-17-jre-slim.tar.gz"
export_image "$NGINX_IMAGE" "nginx-alpine.tar.gz"
export_image "$POSTGRES_IMAGE" "postgis.tar.gz"
export_image "$REDIS_IMAGE" "redis-alpine.tar.gz"
export_image "$NODE_IMAGE" "node-alpine.tar.gz"
export_image "$MAVEN_IMAGE" "maven-openjdk.tar.gz"
export_image "$QAUP_APP_IMAGE" "qaup-app.tar.gz"
export_image "$QAUP_NGINX_IMAGE" "qaup-nginx.tar.gz"
# 创建镜像清单文件
echo "创建镜像清单..."
cat > "$IMAGES_DIR/images-list.txt" << EOF
# QAUP 离线部署镜像清单
# 生成时间: $(date)
# 生成主机: $(hostname)
# 基础镜像
openjdk-17-jre-slim.tar.gz -> $OPENJDK_IMAGE
nginx-alpine.tar.gz -> $NGINX_IMAGE
postgis.tar.gz -> $POSTGRES_IMAGE
redis-alpine.tar.gz -> $REDIS_IMAGE
node-alpine.tar.gz -> $NODE_IMAGE
maven-openjdk.tar.gz -> $MAVEN_IMAGE
# 自定义镜像
qaup-app.tar.gz -> $QAUP_APP_IMAGE
qaup-nginx.tar.gz -> $QAUP_NGINX_IMAGE
# 使用说明
# 1. 将所有 .tar.gz 文件复制到目标服务器
# 2. 运行 load-images.sh 脚本导入镜像
# 3. 验证校验和: sha256sum -c checksums.sha256
EOF
# 创建镜像导入脚本
echo "创建镜像导入脚本..."
cat > "$SCRIPTS_DIR/load-images.sh" << 'EOF'
#!/bin/bash
# QAUP 离线镜像导入脚本
# 在目标服务器上运行此脚本导入镜像
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
IMAGES_DIR="$SCRIPT_DIR/../images"
echo "========================================="
echo "QAUP 离线镜像导入开始"
echo "========================================="
# 检查镜像文件
if [ ! -d "$IMAGES_DIR" ]; then
echo "错误: 镜像目录不存在: $IMAGES_DIR"
exit 1
fi
# 验证校验和
if [ -f "$IMAGES_DIR/checksums.sha256" ]; then
echo "验证文件校验和..."
cd "$IMAGES_DIR"
if sha256sum -c checksums.sha256; then
echo "✓ 文件校验和验证通过"
else
echo "✗ 文件校验和验证失败"
exit 1
fi
cd - > /dev/null
fi
# 导入镜像
echo "导入镜像文件..."
for image_file in "$IMAGES_DIR"/*.tar.gz; do
if [ -f "$image_file" ]; then
echo "导入: $(basename "$image_file")"
if gunzip -c "$image_file" | docker load; then
echo "$(basename "$image_file") 导入成功"
else
echo "$(basename "$image_file") 导入失败"
exit 1
fi
fi
done
echo ""
echo "========================================="
echo "镜像导入完成!"
echo "========================================="
echo "已导入的镜像:"
docker images | grep -E "(qaup|postgres|redis|nginx|openjdk|node|maven)"
EOF
# 设置执行权限
chmod +x "$SCRIPTS_DIR/load-images.sh"
# 计算文件大小和校验和
echo "计算文件校验和..."
cd "$IMAGES_DIR"
sha256sum *.tar.gz > checksums.sha256
# 显示统计信息
echo ""
echo "========================================="
echo "镜像准备完成统计信息:"
echo "========================================="
echo "镜像文件数量: $(ls -1 *.tar.gz | wc -l)"
echo "总大小: $(du -sh . | cut -f1)"
echo ""
echo "镜像文件列表:"
ls -lh *.tar.gz
echo ""
echo "校验和文件已生成: checksums.sha256"
# 复制必要的配置文件到根目录
echo "复制配置文件..."
cp "$PROJECT_ROOT/deploy/docker-compose.yml" "$PROJECT_ROOT/offline-deploy/"
cp "$PROJECT_ROOT/deploy/docker-compose.prod.yml" "$PROJECT_ROOT/offline-deploy/"
cp "$PROJECT_ROOT/deploy/.env.template" "$PROJECT_ROOT/offline-deploy/"
# 创建config目录并复制application-prod.yml
echo "复制应用配置文件..."
mkdir -p "$PROJECT_ROOT/offline-deploy/config"
cp "$PROJECT_ROOT/deploy/config/application-prod.yml" "$PROJECT_ROOT/offline-deploy/config/"
# 复制部署脚本
cp "$PROJECT_ROOT/deploy/deploy.sh" "$SCRIPTS_DIR/"
# 复制docker配置目录
echo "复制Docker配置..."
mkdir -p "$PROJECT_ROOT/offline-deploy/docker"
cp -r "$PROJECT_ROOT/deploy/docker"/* "$PROJECT_ROOT/offline-deploy/docker/" 2>/dev/null || true
# 复制文档
echo "复制文档..."
mkdir -p "$PROJECT_ROOT/offline-deploy/docs"
cp -r "$PROJECT_ROOT/deploy/docs"/* "$PROJECT_ROOT/offline-deploy/docs/" 2>/dev/null || true
# 复制数据库脚本(如果存在)
if [ -d "$PROJECT_ROOT/sql" ]; then
echo "复制数据库脚本..."
cp -r "$PROJECT_ROOT/sql" "$PROJECT_ROOT/offline-deploy/"
fi
# 创建离线部署说明
cat > "$PROJECT_ROOT/offline-deploy/README-离线部署.md" << 'EOF'
# QAUP 离线部署包
## 包含内容
- `images/` - Docker 镜像文件
- `scripts/` - 部署脚本
- `docker/` - Docker配置和数据库初始化脚本
- `docs/` - 部署文档
- `sql/` - 数据库架构文件(如果存在)
- `docker-compose.yml` - Docker Compose 主配置文件
- `docker-compose.prod.yml` - 生产环境配置文件
- `.env.template` - 环境变量模板文件
## 重要文件说明
### 配置文件
- `docker-compose.yml` - Docker Compose 主配置文件
- `docker-compose.prod.yml` - 生产环境特定配置
- `.env.template` - 环境变量配置模板
### 数据库初始化数据
- `docker/postgres/export/01_system_data.sql` - 系统基础数据
- `docker/postgres/export/02_business_data.sql` - 业务数据
- `docker/postgres/export/03_sample_data.sql` - 示例数据
- `docker/postgres/export/initial_data_complete.sql` - 完整初始化数据
## 部署步骤
1. 将整个 `offline-deploy` 目录复制到目标服务器
2. 进入部署目录:`cd offline-deploy`
3. 导入镜像:`./scripts/load-images.sh`
4. 配置环境:`cp .env.template .env` 并根据实际情况编辑 `.env` 文件
5. 启动服务:`./scripts/deploy.sh install`
## 手动部署方式
如果使用部署脚本遇到问题,可以手动执行:
```bash
# 1. 导入镜像
./scripts/load-images.sh
# 2. 配置环境变量
cp .env.template .env
# 编辑 .env 文件,设置数据库密码、端口等配置
# 3. 启动服务
docker-compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env up -d
# 4. 查看服务状态
docker-compose -f docker-compose.yml -f docker-compose.prod.yml ps
# 5. 查看日志
docker-compose -f docker-compose.yml -f docker-compose.prod.yml logs -f
```
## 系统要求
- Docker 20.10+
- Docker Compose 1.29+
- 至少 4GB 内存
- 至少 20GB 磁盘空间
## 数据库初始化
系统启动时会自动执行数据库初始化脚本,按以下顺序:
1. 系统数据(用户、角色、权限等)
2. 业务数据(机场配置、区域设置等)
3. 示例数据(测试用途,可选)
## 常见问题
### 端口冲突
如果遇到端口被占用的问题,请修改 `.env` 文件中的端口配置:
- `NGINX_PORT` - Nginx HTTP 端口(默认 80
- `NGINX_HTTPS_PORT` - Nginx HTTPS 端口(默认 443
- `APP_PORT` - 应用端口(默认 8080
### 权限问题
如果遇到数据目录权限问题,请执行:
```bash
sudo chown -R $USER:$USER data/
sudo chmod -R 755 data/
```
### 服务管理
```bash
# 停止服务
docker-compose -f docker-compose.yml -f docker-compose.prod.yml down
# 重启服务
docker-compose -f docker-compose.yml -f docker-compose.prod.yml restart
# 查看特定服务日志
docker-compose -f docker-compose.yml -f docker-compose.prod.yml logs -f qaup-app
```
EOF
# 创建离线部署包
echo "创建离线部署包..."
cd "$PROJECT_ROOT"
PACKAGE_NAME="qaup-offline-deploy-$(date +%Y%m%d_%H%M%S).tar.gz"
tar -czf "$PACKAGE_NAME" offline-deploy/
echo ""
echo "========================================="
echo "离线部署准备完成!"
echo "========================================="
echo "镜像文件位置: $IMAGES_DIR"
echo "配置文件位置: $CONFIG_DIR"
echo "脚本文件位置: $SCRIPTS_DIR"
echo "离线部署包: $PROJECT_ROOT/$PACKAGE_NAME"
echo ""
echo "部署包大小: $(du -sh "$PROJECT_ROOT/$PACKAGE_NAME" | cut -f1)"
echo ""
echo "使用方法:"
echo "1. 将 $PACKAGE_NAME 传输到目标服务器"
echo "2. 解压: tar -xzf $PACKAGE_NAME"
echo "3. 按照 offline-deploy/README-离线部署.md 中的说明进行部署"

318
deploy/docker/secrets-manager.sh Executable file
View File

@ -0,0 +1,318 @@
#!/bin/bash
# QAUP 敏感信息管理脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
SECRETS_DIR="$PROJECT_ROOT/secrets"
ENV_FILE="$PROJECT_ROOT/.env"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 生成随机密码
generate_password() {
local length=${1:-16}
openssl rand -base64 $length | tr -d "=+/" | cut -c1-$length
}
# 生成强密码
generate_strong_password() {
local length=${1:-20}
# 包含大小写字母、数字和特殊字符
LC_ALL=C tr -dc 'A-Za-z0-9!@#$%^&*' < /dev/urandom | head -c $length
}
# 初始化密钥管理
init_secrets() {
print_message $BLUE "初始化密钥管理..."
mkdir -p "$SECRETS_DIR"
chmod 700 "$SECRETS_DIR"
# 生成主密钥
local master_key_file="$SECRETS_DIR/master.key"
if [ ! -f "$master_key_file" ]; then
openssl rand -hex 32 > "$master_key_file"
chmod 600 "$master_key_file"
print_message $GREEN "主密钥已生成: $master_key_file"
fi
# 创建密钥配置文件
local secrets_config="$SECRETS_DIR/secrets.conf"
if [ ! -f "$secrets_config" ]; then
cat > "$secrets_config" << EOF
# QAUP 密钥配置文件
# 此文件包含加密后的敏感信息
# 数据库密码
POSTGRES_PASSWORD_ENCRYPTED=
APP_DB_PASSWORD_ENCRYPTED=
# Redis 密码
REDIS_PASSWORD_ENCRYPTED=
# Token 密钥
TOKEN_SECRET_ENCRYPTED=
# API 密钥
AIRPORT_API_PASSWORD_ENCRYPTED=
EOF
chmod 600 "$secrets_config"
print_message $GREEN "密钥配置文件已创建: $secrets_config"
fi
print_message $GREEN "密钥管理初始化完成"
}
# 加密字符串
encrypt_string() {
local plaintext="$1"
local master_key_file="$SECRETS_DIR/master.key"
if [ ! -f "$master_key_file" ]; then
print_message $RED "错误: 主密钥文件不存在,请先运行 init 命令"
exit 1
fi
local master_key=$(cat "$master_key_file")
echo -n "$plaintext" | openssl enc -aes-256-cbc -a -salt -pass pass:"$master_key"
}
# 解密字符串
decrypt_string() {
local encrypted="$1"
local master_key_file="$SECRETS_DIR/master.key"
if [ ! -f "$master_key_file" ]; then
print_message $RED "错误: 主密钥文件不存在"
exit 1
fi
local master_key=$(cat "$master_key_file")
echo -n "$encrypted" | openssl enc -aes-256-cbc -d -a -pass pass:"$master_key"
}
# 生成所有密码
generate_all_passwords() {
print_message $BLUE "生成所有密码..."
# 生成数据库密码
local postgres_password=$(generate_strong_password 20)
local app_db_password=$(generate_strong_password 20)
# 生成 Redis 密码
local redis_password=$(generate_strong_password 16)
# 生成 Token 密钥
local token_secret=$(generate_password 32)
# 生成 API 密码
local airport_api_password=$(generate_strong_password 16)
print_message $GREEN "密码生成完成:"
echo "PostgreSQL 管理员密码: $postgres_password"
echo "应用数据库密码: $app_db_password"
echo "Redis 密码: $redis_password"
echo "Token 密钥: $token_secret"
echo "机场 API 密码: $airport_api_password"
# 询问是否保存到配置文件
read -p "是否将密码保存到环境配置文件? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
save_passwords_to_env "$postgres_password" "$app_db_password" "$redis_password" "$token_secret" "$airport_api_password"
fi
}
# 保存密码到环境文件
save_passwords_to_env() {
local postgres_password="$1"
local app_db_password="$2"
local redis_password="$3"
local token_secret="$4"
local airport_api_password="$5"
if [ ! -f "$ENV_FILE" ]; then
print_message $RED "错误: 环境文件不存在: $ENV_FILE"
exit 1
fi
# 备份原文件
cp "$ENV_FILE" "$ENV_FILE.backup.$(date +%Y%m%d_%H%M%S)"
# 更新密码
sed -i.tmp "s/^POSTGRES_PASSWORD=.*/POSTGRES_PASSWORD=$postgres_password/" "$ENV_FILE"
sed -i.tmp "s/^APP_DB_PASSWORD=.*/APP_DB_PASSWORD=$app_db_password/" "$ENV_FILE"
sed -i.tmp "s/^REDIS_PASSWORD=.*/REDIS_PASSWORD=$redis_password/" "$ENV_FILE"
sed -i.tmp "s/^TOKEN_SECRET=.*/TOKEN_SECRET=$token_secret/" "$ENV_FILE"
sed -i.tmp "s/^AIRPORT_API_PASSWORD=.*/AIRPORT_API_PASSWORD=$airport_api_password/" "$ENV_FILE"
rm -f "$ENV_FILE.tmp"
print_message $GREEN "密码已保存到环境文件"
}
# 验证密码强度
validate_password_strength() {
local password="$1"
local min_length=8
local score=0
# 检查长度
if [ ${#password} -ge $min_length ]; then
score=$((score + 1))
fi
# 检查是否包含小写字母
if [[ "$password" =~ [a-z] ]]; then
score=$((score + 1))
fi
# 检查是否包含大写字母
if [[ "$password" =~ [A-Z] ]]; then
score=$((score + 1))
fi
# 检查是否包含数字
if [[ "$password" =~ [0-9] ]]; then
score=$((score + 1))
fi
# 检查是否包含特殊字符
if [[ "$password" =~ [^a-zA-Z0-9] ]]; then
score=$((score + 1))
fi
case $score in
0-2)
print_message $RED "密码强度: 弱"
return 1
;;
3)
print_message $YELLOW "密码强度: 中等"
return 0
;;
4-5)
print_message $GREEN "密码强度: 强"
return 0
;;
esac
}
# 检查环境文件中的密码
check_env_passwords() {
print_message $BLUE "检查环境文件中的密码强度..."
if [ ! -f "$ENV_FILE" ]; then
print_message $RED "错误: 环境文件不存在: $ENV_FILE"
exit 1
fi
source "$ENV_FILE"
local weak_passwords=0
# 检查各种密码
echo "检查 PostgreSQL 密码..."
if ! validate_password_strength "$POSTGRES_PASSWORD"; then
weak_passwords=$((weak_passwords + 1))
fi
echo "检查应用数据库密码..."
if ! validate_password_strength "$APP_DB_PASSWORD"; then
weak_passwords=$((weak_passwords + 1))
fi
if [ -n "$REDIS_PASSWORD" ]; then
echo "检查 Redis 密码..."
if ! validate_password_strength "$REDIS_PASSWORD"; then
weak_passwords=$((weak_passwords + 1))
fi
fi
if [ $weak_passwords -eq 0 ]; then
print_message $GREEN "所有密码强度检查通过"
else
print_message $YELLOW "发现 $weak_passwords 个弱密码,建议重新生成"
fi
}
# 显示帮助信息
show_help() {
echo "QAUP 敏感信息管理脚本"
echo ""
echo "用法: $0 [命令] [选项]"
echo ""
echo "命令:"
echo " init 初始化密钥管理"
echo " generate 生成所有密码"
echo " check 检查密码强度"
echo " encrypt <text> 加密文本"
echo " decrypt <text> 解密文本"
echo ""
echo "示例:"
echo " $0 init # 初始化密钥管理"
echo " $0 generate # 生成所有密码"
echo " $0 check # 检查密码强度"
echo " $0 encrypt 'mypassword' # 加密密码"
}
# 主函数
main() {
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
shift
case $command in
init)
init_secrets
;;
generate)
generate_all_passwords
;;
check)
check_env_passwords
;;
encrypt)
if [ $# -eq 0 ]; then
print_message $RED "错误: 请提供要加密的文本"
exit 1
fi
encrypt_string "$1"
;;
decrypt)
if [ $# -eq 0 ]; then
print_message $RED "错误: 请提供要解密的文本"
exit 1
fi
decrypt_string "$1"
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"

738
deploy/docker/security-setup.sh Executable file
View File

@ -0,0 +1,738 @@
#!/bin/bash
# QAUP 安全配置脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 创建非 root 用户配置
setup_non_root_users() {
print_message $BLUE "配置容器非 root 用户..."
# 更新应用 Dockerfile 以使用非 root 用户
cat > "$PROJECT_ROOT/docker/app-security.Dockerfile" << 'EOF'
# 安全版本的应用 Dockerfile
FROM openjdk:17-jre-slim
# 创建应用用户和组
RUN groupadd -r qaup && useradd -r -g qaup -d /app -s /bin/bash qaup
# 安装必要工具
RUN apt-get update && apt-get install -y \
curl \
netcat-openbsd \
fontconfig \
fonts-dejavu-core \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
# 设置工作目录
WORKDIR /app
# 创建必要目录并设置权限
RUN mkdir -p /app/logs /app/uploadPath /app/temp \
&& chown -R qaup:qaup /app \
&& chmod 755 /app \
&& chmod 750 /app/logs /app/uploadPath \
&& chmod 700 /app/temp
# 复制应用文件
COPY --chown=qaup:qaup qaup-admin/target/qaup-admin.jar /app/app.jar
COPY --chown=qaup:qaup docker/start.sh /app/start.sh
# 设置文件权限
RUN chmod 644 /app/app.jar \
&& chmod 750 /app/start.sh
# 切换到非 root 用户
USER qaup
# 暴露端口
EXPOSE 8080
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# 启动应用
ENTRYPOINT ["/app/start.sh"]
EOF
print_message $GREEN "✓ 应用容器非 root 用户配置完成"
}
# 配置网络安全
setup_network_security() {
print_message $BLUE "配置网络安全..."
# 创建安全的 Docker Compose 网络配置
cat > "$PROJECT_ROOT/docker-compose.security.yml" << 'EOF'
version: '3.8'
# 安全网络配置覆盖
services:
qaup-postgres:
networks:
- db-network
# 不暴露数据库端口到主机
ports: []
qaup-redis:
networks:
- cache-network
# 不暴露 Redis 端口到主机
ports: []
qaup-app:
networks:
- app-network
- db-network
- cache-network
# 不暴露应用端口到主机
ports: []
qaup-nginx:
networks:
- app-network
# 只暴露必要的 HTTP/HTTPS 端口
ports:
- "80:80"
- "443:443"
# 网络隔离配置
networks:
app-network:
driver: bridge
internal: false
ipam:
config:
- subnet: 172.20.1.0/24
db-network:
driver: bridge
internal: true # 内部网络,不能访问外网
ipam:
config:
- subnet: 172.20.2.0/24
cache-network:
driver: bridge
internal: true # 内部网络,不能访问外网
ipam:
config:
- subnet: 172.20.3.0/24
EOF
print_message $GREEN "✓ 网络安全配置完成"
}
# 配置文件权限
setup_file_permissions() {
print_message $BLUE "配置文件权限..."
# 设置配置文件权限
if [ -f "$PROJECT_ROOT/.env" ]; then
chmod 600 "$PROJECT_ROOT/.env"
print_message $GREEN "✓ 环境文件权限已设置为 600"
fi
# 设置数据目录权限
if [ -d "$PROJECT_ROOT/data" ]; then
chmod 750 "$PROJECT_ROOT/data"
chmod 700 "$PROJECT_ROOT/data/postgres" 2>/dev/null || true
chmod 755 "$PROJECT_ROOT/data/redis" 2>/dev/null || true
chmod 755 "$PROJECT_ROOT/data/uploads" 2>/dev/null || true
print_message $GREEN "✓ 数据目录权限已设置"
fi
# 设置日志目录权限
if [ -d "$PROJECT_ROOT/logs" ]; then
chmod 755 "$PROJECT_ROOT/logs"
chmod 644 "$PROJECT_ROOT/logs"/*.log 2>/dev/null || true
print_message $GREEN "✓ 日志目录权限已设置"
fi
# 设置脚本权限
find "$PROJECT_ROOT/docker" -name "*.sh" -exec chmod 750 {} \;
chmod 750 "$PROJECT_ROOT/deploy.sh" 2>/dev/null || true
print_message $GREEN "✓ 脚本文件权限已设置"
# 设置配置文件权限
find "$PROJECT_ROOT/config" -type f -exec chmod 644 {} \; 2>/dev/null || true
find "$PROJECT_ROOT/docker" -name "*.conf" -exec chmod 644 {} \; 2>/dev/null || true
print_message $GREEN "✓ 配置文件权限已设置"
}
# 配置 SSL/TLS
setup_ssl_tls() {
print_message $BLUE "配置 SSL/TLS..."
# 创建 SSL 目录
mkdir -p "$PROJECT_ROOT/ssl"
chmod 700 "$PROJECT_ROOT/ssl"
# 创建自签名证书(用于测试)
if [ ! -f "$PROJECT_ROOT/ssl/server.crt" ]; then
print_message $YELLOW "生成自签名 SSL 证书(仅用于测试)..."
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout "$PROJECT_ROOT/ssl/server.key" \
-out "$PROJECT_ROOT/ssl/server.crt" \
-subj "/C=CN/ST=State/L=City/O=Organization/CN=localhost" \
2>/dev/null
chmod 600 "$PROJECT_ROOT/ssl/server.key"
chmod 644 "$PROJECT_ROOT/ssl/server.crt"
print_message $YELLOW "⚠ 生产环境请使用正式的 SSL 证书"
fi
# 创建支持 HTTPS 的 Nginx 配置
cat > "$PROJECT_ROOT/docker/nginx/https.conf" << 'EOF'
# HTTPS 配置
server {
listen 443 ssl http2;
server_name localhost;
# SSL 证书配置
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# SSL 安全配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 其他配置与 HTTP 相同
root /usr/share/nginx/html;
index index.html;
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
location /prod-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;
proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
client_max_body_size 20M;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.html;
location ~* \.html$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
}
}
# HTTP 重定向到 HTTPS
server {
listen 80;
server_name localhost;
return 301 https://$server_name$request_uri;
}
EOF
print_message $GREEN "✓ SSL/TLS 配置完成"
}
# 配置防火墙规则
setup_firewall() {
print_message $BLUE "配置防火墙规则..."
# 检查是否有 root 权限
if [ "$EUID" -ne 0 ]; then
print_message $YELLOW "⚠ 需要 root 权限来配置防火墙"
print_message $YELLOW "请手动执行以下命令:"
echo ""
echo "# 启用 UFW 防火墙"
echo "sudo ufw --force enable"
echo ""
echo "# 允许 SSH 连接"
echo "sudo ufw allow 22/tcp"
echo ""
echo "# 允许 HTTP 和 HTTPS"
echo "sudo ufw allow 80/tcp"
echo "sudo ufw allow 443/tcp"
echo ""
echo "# 拒绝其他端口的外部访问"
echo "sudo ufw deny 8080/tcp"
echo "sudo ufw deny 5432/tcp"
echo "sudo ufw deny 6379/tcp"
echo ""
echo "# 查看防火墙状态"
echo "sudo ufw status verbose"
return
fi
# 配置 UFW 防火墙
ufw --force enable
# 允许必要的端口
ufw allow 22/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
# 拒绝直接访问内部服务端口
ufw deny 8080/tcp comment 'Block direct app access'
ufw deny 5432/tcp comment 'Block direct database access'
ufw deny 6379/tcp comment 'Block direct Redis access'
# 显示防火墙状态
ufw status verbose
print_message $GREEN "✓ 防火墙配置完成"
}
# 配置容器安全选项
setup_container_security() {
print_message $BLUE "配置容器安全选项..."
# 创建安全的 Docker Compose 配置
cat > "$PROJECT_ROOT/docker-compose.security-options.yml" << 'EOF'
version: '3.8'
# 容器安全选项配置
services:
qaup-postgres:
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- DAC_OVERRIDE
- FOWNER
- SETGID
- SETUID
read_only: false
tmpfs:
- /tmp:noexec,nosuid,size=100m
qaup-redis:
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- SETGID
- SETUID
read_only: false
tmpfs:
- /tmp:noexec,nosuid,size=50m
qaup-app:
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
read_only: false
tmpfs:
- /tmp:noexec,nosuid,size=200m
qaup-nginx:
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- CHOWN
- DAC_OVERRIDE
- SETGID
- SETUID
read_only: false
tmpfs:
- /tmp:noexec,nosuid,size=50m
- /var/cache/nginx:noexec,nosuid,size=100m
EOF
print_message $GREEN "✓ 容器安全选项配置完成"
}
# 配置日志审计
setup_audit_logging() {
print_message $BLUE "配置审计日志..."
# 创建审计日志配置
cat > "$PROJECT_ROOT/docker/audit-config.yml" << 'EOF'
# 审计日志配置
audit:
enabled: true
log_file: "./logs/audit.log"
# 审计事件类型
events:
- login_attempts
- admin_operations
- data_access
- configuration_changes
- security_events
# 日志格式
format: json
# 日志轮转
rotation:
max_size: 100MB
max_files: 10
compress: true
EOF
# 创建审计日志脚本
cat > "$PROJECT_ROOT/docker/audit-logger.sh" << 'AUDIT_EOF'
#!/bin/bash
# 审计日志记录脚本
AUDIT_LOG="./logs/audit.log"
# 记录审计事件
log_audit_event() {
local event_type=$1
local user=${2:-"system"}
local action=$3
local resource=${4:-""}
local result=${5:-"success"}
local details=${6:-""}
local timestamp=$(date -u '+%Y-%m-%dT%H:%M:%S.%3NZ')
local event_id=$(uuidgen 2>/dev/null || echo "$(date +%s)-$$")
# 创建 JSON 格式的审计日志
cat >> "$AUDIT_LOG" << EOF
{
"timestamp": "$timestamp",
"event_id": "$event_id",
"event_type": "$event_type",
"user": "$user",
"action": "$action",
"resource": "$resource",
"result": "$result",
"details": "$details",
"source_ip": "${SSH_CLIENT%% *}",
"session_id": "${SSH_TTY:-console}"
}
EOF
}
# 监控 Docker 事件
monitor_docker_events() {
docker events --format '{{.Time}} {{.Type}} {{.Action}} {{.Actor.Attributes.name}}' | while read timestamp type action container; do
if [[ "$type" == "container" && "$action" =~ ^(start|stop|restart|kill|die)$ ]]; then
log_audit_event "container_event" "docker" "$action" "$container" "success" "Container $action event"
fi
done &
}
# 启动审计监控
if [ "$1" = "start" ]; then
echo "启动审计日志监控..."
monitor_docker_events
echo "审计日志监控已启动"
fi
# 导出函数供其他脚本使用
if [ "$1" != "start" ]; then
log_audit_event "$@"
fi
AUDIT_EOF
chmod +x "$PROJECT_ROOT/docker/audit-logger.sh"
print_message $GREEN "✓ 审计日志配置完成"
}
# 配置密码策略
setup_password_policy() {
print_message $BLUE "配置密码策略..."
# 创建密码策略检查脚本
cat > "$PROJECT_ROOT/docker/password-policy.sh" << 'POLICY_EOF'
#!/bin/bash
# 密码策略检查脚本
# 密码策略配置
MIN_LENGTH=12
REQUIRE_UPPERCASE=true
REQUIRE_LOWERCASE=true
REQUIRE_NUMBERS=true
REQUIRE_SPECIAL=true
FORBIDDEN_PATTERNS=("password" "123456" "admin" "qaup")
# 检查密码强度
check_password_strength() {
local password=$1
local errors=()
# 检查长度
if [ ${#password} -lt $MIN_LENGTH ]; then
errors+=("密码长度至少需要 $MIN_LENGTH")
fi
# 检查大写字母
if [ "$REQUIRE_UPPERCASE" = true ] && ! [[ "$password" =~ [A-Z] ]]; then
errors+=("密码必须包含大写字母")
fi
# 检查小写字母
if [ "$REQUIRE_LOWERCASE" = true ] && ! [[ "$password" =~ [a-z] ]]; then
errors+=("密码必须包含小写字母")
fi
# 检查数字
if [ "$REQUIRE_NUMBERS" = true ] && ! [[ "$password" =~ [0-9] ]]; then
errors+=("密码必须包含数字")
fi
# 检查特殊字符
if [ "$REQUIRE_SPECIAL" = true ] && ! [[ "$password" =~ [^a-zA-Z0-9] ]]; then
errors+=("密码必须包含特殊字符")
fi
# 检查禁用模式
for pattern in "${FORBIDDEN_PATTERNS[@]}"; do
if [[ "${password,,}" == *"${pattern,,}"* ]]; then
errors+=("密码不能包含常见词汇: $pattern")
fi
done
# 返回结果
if [ ${#errors[@]} -eq 0 ]; then
echo "密码强度检查通过"
return 0
else
echo "密码强度检查失败:"
for error in "${errors[@]}"; do
echo " - $error"
done
return 1
fi
}
# 生成强密码
generate_strong_password() {
local length=${1:-16}
# 确保包含所有必需的字符类型
local uppercase="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
local lowercase="abcdefghijklmnopqrstuvwxyz"
local numbers="0123456789"
local special="!@#$%^&*()_+-=[]{}|;:,.<>?"
local password=""
# 至少包含一个每种类型的字符
password+=$(echo $uppercase | fold -w1 | shuf | head -1)
password+=$(echo $lowercase | fold -w1 | shuf | head -1)
password+=$(echo $numbers | fold -w1 | shuf | head -1)
password+=$(echo $special | fold -w1 | shuf | head -1)
# 填充剩余长度
local all_chars="$uppercase$lowercase$numbers$special"
for ((i=4; i<length; i++)); do
password+=$(echo $all_chars | fold -w1 | shuf | head -1)
done
# 打乱字符顺序
echo $password | fold -w1 | shuf | tr -d '\n'
}
# 主函数
case "$1" in
check)
if [ -z "$2" ]; then
echo "用法: $0 check <password>"
exit 1
fi
check_password_strength "$2"
;;
generate)
generate_strong_password "$2"
;;
*)
echo "用法: $0 {check|generate} [参数]"
echo " check <password> - 检查密码强度"
echo " generate [length] - 生成强密码"
exit 1
;;
esac
POLICY_EOF
chmod +x "$PROJECT_ROOT/docker/password-policy.sh"
print_message $GREEN "✓ 密码策略配置完成"
}
# 应用所有安全配置
apply_all_security() {
print_message $GREEN "开始应用所有安全配置..."
echo ""
setup_non_root_users
echo ""
setup_network_security
echo ""
setup_file_permissions
echo ""
setup_ssl_tls
echo ""
setup_firewall
echo ""
setup_container_security
echo ""
setup_audit_logging
echo ""
setup_password_policy
echo ""
print_message $GREEN "========================================="
print_message $GREEN "安全配置完成!"
print_message $GREEN "========================================="
echo ""
print_message $BLUE "安全配置摘要:"
echo " ✓ 容器非 root 用户配置"
echo " ✓ 网络隔离和安全配置"
echo " ✓ 文件权限安全设置"
echo " ✓ SSL/TLS 加密配置"
echo " ✓ 防火墙规则配置"
echo " ✓ 容器安全选项配置"
echo " ✓ 审计日志配置"
echo " ✓ 密码策略配置"
echo ""
print_message $YELLOW "重要提醒:"
echo " 1. 生产环境请使用正式的 SSL 证书"
echo " 2. 定期更新密码和证书"
echo " 3. 监控审计日志"
echo " 4. 定期进行安全扫描"
echo ""
}
# 显示帮助信息
show_help() {
echo "QAUP 安全配置脚本"
echo ""
echo "用法: $0 [命令]"
echo ""
echo "命令:"
echo " all 应用所有安全配置"
echo " users 配置非 root 用户"
echo " network 配置网络安全"
echo " permissions 配置文件权限"
echo " ssl 配置 SSL/TLS"
echo " firewall 配置防火墙"
echo " containers 配置容器安全选项"
echo " audit 配置审计日志"
echo " password 配置密码策略"
echo ""
echo "示例:"
echo " $0 all # 应用所有安全配置"
echo " sudo $0 firewall # 配置防火墙(需要 root"
echo " $0 ssl # 配置 SSL/TLS"
}
# 主函数
main() {
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
case $command in
all)
apply_all_security
;;
users)
setup_non_root_users
;;
network)
setup_network_security
;;
permissions)
setup_file_permissions
;;
ssl)
setup_ssl_tls
;;
firewall)
setup_firewall
;;
containers)
setup_container_security
;;
audit)
setup_audit_logging
;;
password)
setup_password_policy
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"

24
deploy/docker/start.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
# 设置 JVM 参数
JVM_OPTS="${JVM_OPTS:--Dname=qaup-admin.jar -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC}"
# 等待数据库就绪
echo "等待数据库连接..."
while ! timeout 3 bash -c "</dev/tcp/${DB_HOST:-qaup-postgres}/${DB_PORT:-5432}" 2>/dev/null; do
echo "数据库未就绪,等待 5 秒..."
sleep 5
done
echo "数据库连接成功"
# 等待 Redis 就绪
echo "等待 Redis 连接..."
while ! timeout 3 bash -c "</dev/tcp/${REDIS_HOST:-qaup-redis}/${REDIS_PORT:-6379}" 2>/dev/null; do
echo "Redis 未就绪,等待 3 秒..."
sleep 3
done
echo "Redis 连接成功"
# 启动应用
echo "启动 QAUP 应用..."
exec java $JVM_OPTS -jar app.jar

View File

@ -0,0 +1,262 @@
#!/bin/bash
# QAUP Docker 镜像版本同步更新脚本
# 根据 image-versions.env 配置文件更新所有相关文件中的镜像版本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
IMAGE_VERSIONS_FILE="$SCRIPT_DIR/image-versions.env"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 显示帮助信息
show_help() {
echo "QAUP Docker 镜像版本同步更新脚本"
echo ""
echo "用法: $0 [选项]"
echo ""
echo "选项:"
echo " -h, --help 显示帮助信息"
echo " --dry-run 仅显示将要进行的更改,不实际修改文件"
echo " --backup 更新前备份原文件"
echo ""
echo "功能:"
echo " - 从 image-versions.env 读取最新版本配置"
echo " - 更新 docker-compose.yml 中的镜像版本"
echo " - 更新 Dockerfile 中的基础镜像版本"
echo " - 生成版本更新报告"
}
# 检查参数
DRY_RUN=false
BACKUP=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
--dry-run)
DRY_RUN=true
shift
;;
--backup)
BACKUP=true
shift
;;
*)
echo "未知选项: $1"
show_help
exit 1
;;
esac
done
# 检查配置文件
if [ ! -f "$IMAGE_VERSIONS_FILE" ]; then
print_message $RED "错误: 镜像版本配置文件不存在: $IMAGE_VERSIONS_FILE"
exit 1
fi
# 加载配置
source "$IMAGE_VERSIONS_FILE"
print_message $BLUE "========================================="
print_message $BLUE "QAUP Docker 镜像版本同步更新"
print_message $BLUE "========================================="
echo ""
if [ "$DRY_RUN" = true ]; then
print_message $YELLOW "运行模式: 预览模式(不会实际修改文件)"
else
print_message $GREEN "运行模式: 更新模式"
fi
echo ""
# 备份函数
backup_file() {
local file=$1
if [ "$BACKUP" = true ] && [ -f "$file" ]; then
local backup_file="${file}.backup.$(date +%Y%m%d_%H%M%S)"
cp "$file" "$backup_file"
print_message $GREEN "已备份: $file -> $backup_file"
fi
}
# 更新 docker-compose.yml
update_docker_compose() {
local compose_file="$PROJECT_ROOT/docker-compose.yml"
if [ ! -f "$compose_file" ]; then
print_message $YELLOW "警告: docker-compose.yml 不存在,跳过更新"
return
fi
print_message $BLUE "更新 docker-compose.yml..."
# 检查当前版本
local current_postgres=$(grep -A 1 "qaup-postgres:" "$compose_file" | grep "image:" | awk '{print $2}' | head -1)
local current_redis=$(grep -A 1 "qaup-redis:" "$compose_file" | grep "image:" | awk '{print $2}' | head -1)
echo "PostgreSQL: $current_postgres -> $POSTGRES_IMAGE"
echo "Redis: $current_redis -> $REDIS_IMAGE"
if [ "$DRY_RUN" = false ]; then
backup_file "$compose_file"
# 更新 PostgreSQL 镜像
sed -i.tmp "s|image: postgis/postgis:.*|image: $POSTGRES_IMAGE|g" "$compose_file"
# 更新 Redis 镜像
sed -i.tmp "s|image: redis:.*|image: $REDIS_IMAGE|g" "$compose_file"
# 清理临时文件
rm -f "${compose_file}.tmp"
print_message $GREEN "✓ docker-compose.yml 更新完成"
fi
echo ""
}
# 更新 Dockerfile
update_dockerfile() {
local dockerfile="$PROJECT_ROOT/Dockerfile"
if [ ! -f "$dockerfile" ]; then
print_message $YELLOW "警告: Dockerfile 不存在,跳过更新"
return
fi
print_message $BLUE "更新 Dockerfile..."
# 检查当前版本
local current_maven=$(grep "^FROM maven:" "$dockerfile" | awk '{print $2}' | head -1)
local current_openjdk=$(grep "^FROM openjdk:" "$dockerfile" | awk '{print $2}' | head -1)
echo "Maven: $current_maven -> $MAVEN_IMAGE"
echo "OpenJDK: $current_openjdk -> $OPENJDK_IMAGE"
if [ "$DRY_RUN" = false ]; then
backup_file "$dockerfile"
# 更新 Maven 镜像
sed -i.tmp "s|FROM maven:.*AS builder|FROM $MAVEN_IMAGE AS builder|g" "$dockerfile"
# 更新 OpenJDK 镜像
sed -i.tmp "s|FROM openjdk:.*|FROM $OPENJDK_IMAGE|g" "$dockerfile"
# 清理临时文件
rm -f "${dockerfile}.tmp"
print_message $GREEN "✓ Dockerfile 更新完成"
fi
echo ""
}
# 更新 Nginx Dockerfile
update_nginx_dockerfile() {
local nginx_dockerfile="$PROJECT_ROOT/docker/nginx/Dockerfile"
if [ ! -f "$nginx_dockerfile" ]; then
print_message $YELLOW "警告: Nginx Dockerfile 不存在,跳过更新"
return
fi
print_message $BLUE "更新 Nginx Dockerfile..."
# 检查当前版本
local current_node=$(grep "^FROM node:" "$nginx_dockerfile" | awk '{print $2}' | head -1)
local current_nginx=$(grep "^FROM nginx:" "$nginx_dockerfile" | awk '{print $2}' | head -1)
echo "Node.js: $current_node -> $NODE_IMAGE"
echo "Nginx: $current_nginx -> $NGINX_IMAGE"
if [ "$DRY_RUN" = false ]; then
backup_file "$nginx_dockerfile"
# 更新 Node.js 镜像
sed -i.tmp "s|FROM node:.*AS frontend-builder|FROM $NODE_IMAGE AS frontend-builder|g" "$nginx_dockerfile"
# 更新 Nginx 镜像
sed -i.tmp "s|FROM nginx:.*|FROM $NGINX_IMAGE|g" "$nginx_dockerfile"
# 清理临时文件
rm -f "${nginx_dockerfile}.tmp"
print_message $GREEN "✓ Nginx Dockerfile 更新完成"
fi
echo ""
}
# 生成更新报告
generate_report() {
local report_file="$PROJECT_ROOT/image-version-update-report.txt"
cat > "$report_file" << EOF
QAUP Docker 镜像版本更新报告
生成时间: $(date)
更新模式: $([ "$DRY_RUN" = true ] && echo "预览模式" || echo "实际更新")
更新的镜像版本:
- PostgreSQL/PostGIS: $POSTGRES_IMAGE
- Redis: $REDIS_IMAGE
- Maven: $MAVEN_IMAGE
- OpenJDK: $OPENJDK_IMAGE
- Nginx: $NGINX_IMAGE
- Node.js: $NODE_IMAGE
更新的文件:
- docker-compose.yml
- Dockerfile
- docker/nginx/Dockerfile
后续操作建议:
1. 测试新版本的兼容性
2. 更新离线部署镜像包
3. 更新部署文档中的版本信息
4. 在测试环境验证后再部署到生产环境
EOF
print_message $GREEN "更新报告已生成: $report_file"
}
# 执行更新
print_message $BLUE "开始更新镜像版本..."
echo ""
update_docker_compose
update_dockerfile
update_nginx_dockerfile
if [ "$DRY_RUN" = false ]; then
print_message $GREEN "========================================="
print_message $GREEN "镜像版本更新完成!"
print_message $GREEN "========================================="
echo ""
print_message $YELLOW "重要提醒:"
echo "1. 请在测试环境验证新版本的兼容性"
echo "2. 运行 ./prepare-offline-images.sh 更新离线部署包"
echo "3. 考虑更新相关文档中的版本信息"
else
print_message $BLUE "========================================="
print_message $BLUE "预览完成(未实际修改文件)"
print_message $BLUE "========================================="
echo ""
print_message $YELLOW "如需实际更新,请运行: $0"
fi
generate_report

View File

@ -0,0 +1,346 @@
#!/bin/bash
# QAUP 环境验证脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
ENV_FILE="$PROJECT_ROOT/.env"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 检查系统要求
check_system_requirements() {
print_message $BLUE "检查系统要求..."
local errors=0
# 检查操作系统
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
print_message $GREEN "✓ 操作系统: Linux"
else
print_message $YELLOW "⚠ 操作系统: $OSTYPE (建议使用 Linux)"
fi
# 检查 Docker
if command -v docker &> /dev/null; then
local docker_version=$(docker --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
print_message $GREEN "✓ Docker: $docker_version"
# 检查 Docker 版本
local major_version=$(echo $docker_version | cut -d. -f1)
local minor_version=$(echo $docker_version | cut -d. -f2)
if [ $major_version -lt 20 ] || ([ $major_version -eq 20 ] && [ $minor_version -lt 10 ]); then
print_message $YELLOW "⚠ Docker 版本较低,建议升级到 20.10+"
fi
else
print_message $RED "✗ Docker 未安装"
errors=$((errors + 1))
fi
# 检查 Docker Compose
if command -v docker-compose &> /dev/null; then
local compose_version=$(docker-compose --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
print_message $GREEN "✓ Docker Compose: $compose_version"
else
print_message $RED "✗ Docker Compose 未安装"
errors=$((errors + 1))
fi
# 检查内存
local total_mem=$(free -m | awk 'NR==2{printf "%.0f", $2/1024}')
if [ $total_mem -ge 4 ]; then
print_message $GREEN "✓ 内存: ${total_mem}GB"
else
print_message $YELLOW "⚠ 内存: ${total_mem}GB (建议至少 4GB)"
fi
# 检查磁盘空间
local available_space=$(df -BG . | awk 'NR==2 {print $4}' | sed 's/G//')
if [ $available_space -ge 20 ]; then
print_message $GREEN "✓ 可用磁盘空间: ${available_space}GB"
else
print_message $YELLOW "⚠ 可用磁盘空间: ${available_space}GB (建议至少 20GB)"
fi
return $errors
}
# 检查端口占用
check_ports() {
print_message $BLUE "检查端口占用..."
local ports=(80 443 8080 5432 6379)
local occupied_ports=()
for port in "${ports[@]}"; do
if netstat -tuln 2>/dev/null | grep -q ":$port "; then
occupied_ports+=($port)
print_message $YELLOW "⚠ 端口 $port 已被占用"
else
print_message $GREEN "✓ 端口 $port 可用"
fi
done
if [ ${#occupied_ports[@]} -gt 0 ]; then
print_message $YELLOW "警告: 以下端口被占用: ${occupied_ports[*]}"
print_message $YELLOW "请在 .env 文件中修改端口配置或停止占用端口的服务"
return 1
fi
return 0
}
# 检查 Docker 服务状态
check_docker_service() {
print_message $BLUE "检查 Docker 服务状态..."
if systemctl is-active --quiet docker; then
print_message $GREEN "✓ Docker 服务正在运行"
else
print_message $RED "✗ Docker 服务未运行"
print_message $YELLOW "请运行: sudo systemctl start docker"
return 1
fi
# 检查当前用户是否在 docker 组中
if groups | grep -q docker; then
print_message $GREEN "✓ 当前用户在 docker 组中"
else
print_message $YELLOW "⚠ 当前用户不在 docker 组中"
print_message $YELLOW "请运行: sudo usermod -aG docker $USER"
print_message $YELLOW "然后重新登录或运行: newgrp docker"
fi
return 0
}
# 检查环境配置
check_environment_config() {
print_message $BLUE "检查环境配置..."
local errors=0
# 检查环境文件
if [ ! -f "$ENV_FILE" ]; then
print_message $RED "✗ 环境文件不存在: $ENV_FILE"
print_message $YELLOW "请运行: docker/config-manager.sh init"
return 1
fi
source "$ENV_FILE"
# 检查必需的环境变量
local required_vars=(
"POSTGRES_DB"
"POSTGRES_USER"
"POSTGRES_PASSWORD"
"APP_DB_USER"
"APP_DB_PASSWORD"
)
for var in "${required_vars[@]}"; do
if [ -z "${!var}" ]; then
print_message $RED "✗ 必需的环境变量未设置: $var"
errors=$((errors + 1))
else
print_message $GREEN "$var 已设置"
fi
done
# 检查密码强度
if [ ${#POSTGRES_PASSWORD} -lt 8 ]; then
print_message $YELLOW "⚠ PostgreSQL 密码长度少于8位"
errors=$((errors + 1))
fi
if [ ${#APP_DB_PASSWORD} -lt 8 ]; then
print_message $YELLOW "⚠ 应用数据库密码长度少于8位"
errors=$((errors + 1))
fi
return $errors
}
# 检查文件权限
check_file_permissions() {
print_message $BLUE "检查文件权限..."
local errors=0
# 检查脚本文件权限
local script_files=(
"docker/start.sh"
"docker/build-frontend.sh"
"docker/nginx/start-nginx.sh"
"docker/postgres/init-db.sh"
"docker/postgres/db-manager.sh"
"docker/prepare-offline-images.sh"
"docker/load-images.sh"
"docker/docker-compose-manager.sh"
"docker/config-manager.sh"
"docker/secrets-manager.sh"
)
for script in "${script_files[@]}"; do
local script_path="$PROJECT_ROOT/$script"
if [ -f "$script_path" ]; then
if [ -x "$script_path" ]; then
print_message $GREEN "$script 可执行"
else
print_message $YELLOW "$script 不可执行"
chmod +x "$script_path"
print_message $GREEN "✓ 已修复 $script 权限"
fi
else
print_message $RED "✗ 脚本文件不存在: $script"
errors=$((errors + 1))
fi
done
return $errors
}
# 检查目录结构
check_directory_structure() {
print_message $BLUE "检查目录结构..."
local required_dirs=(
"docker"
"docker/nginx"
"docker/postgres"
"config"
)
local errors=0
for dir in "${required_dirs[@]}"; do
local dir_path="$PROJECT_ROOT/$dir"
if [ -d "$dir_path" ]; then
print_message $GREEN "✓ 目录存在: $dir"
else
print_message $RED "✗ 目录不存在: $dir"
errors=$((errors + 1))
fi
done
return $errors
}
# 检查 Docker 镜像
check_docker_images() {
print_message $BLUE "检查 Docker 镜像..."
# 从 image-versions.env 文件中读取镜像配置
local image_versions_file="$SCRIPT_DIR/image-versions.env"
if [ -f "$image_versions_file" ]; then
source "$image_versions_file"
else
print_message $RED "✗ 镜像版本配置文件不存在: $image_versions_file"
return 1
fi
# 使用配置文件中定义的镜像名称
local base_images=(
"$POSTGRES_IMAGE"
"$REDIS_IMAGE"
"$QAUP_APP_IMAGE"
"$QAUP_NGINX_IMAGE"
)
local missing_images=()
for image in "${base_images[@]}"; do
if docker image inspect "$image" &> /dev/null; then
print_message $GREEN "✓ 镜像存在: $image"
else
missing_images+=("$image")
print_message $YELLOW "⚠ 镜像不存在: $image"
fi
done
if [ ${#missing_images[@]} -gt 0 ]; then
print_message $YELLOW "缺失的镜像: ${missing_images[*]}"
print_message $YELLOW "请运行: ./docker/prepare-offline-images.sh 或手动拉取镜像"
return 1
fi
return 0
}
# 生成验证报告
generate_report() {
local total_errors=$1
print_message $BLUE "========================================="
print_message $BLUE "环境验证报告"
print_message $BLUE "========================================="
if [ $total_errors -eq 0 ]; then
print_message $GREEN "✓ 环境验证通过,可以开始部署"
print_message $GREEN "建议的下一步操作:"
echo " 1. 运行: docker/docker-compose-manager.sh prod build"
echo " 2. 运行: docker/docker-compose-manager.sh prod start"
else
print_message $RED "✗ 环境验证失败,发现 $total_errors 个问题"
print_message $YELLOW "请修复上述问题后重新运行验证"
fi
print_message $BLUE "========================================="
}
# 主函数
main() {
print_message $GREEN "开始 QAUP 环境验证..."
print_message $BLUE "========================================="
local total_errors=0
# 执行各项检查
check_system_requirements
total_errors=$((total_errors + $?))
echo ""
check_docker_service
total_errors=$((total_errors + $?))
echo ""
check_ports
total_errors=$((total_errors + $?))
echo ""
check_directory_structure
total_errors=$((total_errors + $?))
echo ""
check_file_permissions
total_errors=$((total_errors + $?))
echo ""
check_environment_config
total_errors=$((total_errors + $?))
echo ""
check_docker_images
total_errors=$((total_errors + $?))
echo ""
generate_report $total_errors
exit $total_errors
}
main "$@"

View File

@ -0,0 +1,120 @@
#!/bin/bash
# QAUP Docker 镜像版本验证脚本
# 验证配置文件中的版本与实际使用的版本是否一致
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
print_message $BLUE "========================================="
print_message $BLUE "QAUP Docker 镜像版本验证"
print_message $BLUE "========================================="
echo ""
# 加载配置文件
if [ -f "$SCRIPT_DIR/image-versions.env" ]; then
source "$SCRIPT_DIR/image-versions.env"
print_message $GREEN "✓ 已加载镜像版本配置文件"
else
print_message $RED "✗ 镜像版本配置文件不存在"
exit 1
fi
echo ""
print_message $BLUE "配置文件中的版本:"
echo "PostgreSQL/PostGIS: $POSTGRES_IMAGE"
echo "Redis: $REDIS_IMAGE"
echo "OpenJDK: $OPENJDK_IMAGE"
echo "Maven: $MAVEN_IMAGE"
echo "Nginx: $NGINX_IMAGE"
echo "Node.js: $NODE_IMAGE"
echo ""
# 检查 docker-compose.yml 中的版本
print_message $BLUE "docker-compose.yml 中的版本:"
if [ -f "$PROJECT_ROOT/docker-compose.yml" ]; then
compose_postgres=$(grep -A 1 "qaup-postgres:" "$PROJECT_ROOT/docker-compose.yml" | grep "image:" | awk '{print $2}')
compose_redis=$(grep -A 1 "qaup-redis:" "$PROJECT_ROOT/docker-compose.yml" | grep "image:" | awk '{print $2}')
echo "PostgreSQL/PostGIS: $compose_postgres"
echo "Redis: $compose_redis"
# 验证一致性
if [ "$compose_postgres" = "$POSTGRES_IMAGE" ]; then
print_message $GREEN "✓ PostgreSQL 版本一致"
else
print_message $RED "✗ PostgreSQL 版本不一致"
echo " 配置文件: $POSTGRES_IMAGE"
echo " docker-compose.yml: $compose_postgres"
fi
if [ "$compose_redis" = "$REDIS_IMAGE" ]; then
print_message $GREEN "✓ Redis 版本一致"
else
print_message $RED "✗ Redis 版本不一致"
echo " 配置文件: $REDIS_IMAGE"
echo " docker-compose.yml: $compose_redis"
fi
else
print_message $YELLOW "警告: docker-compose.yml 文件不存在"
fi
echo ""
# 检查 Dockerfile 中的版本
print_message $BLUE "Dockerfile 中的版本:"
if [ -f "$PROJECT_ROOT/Dockerfile" ]; then
dockerfile_maven=$(grep "^FROM maven:" "$PROJECT_ROOT/Dockerfile" | awk '{print $2}' | head -1)
dockerfile_openjdk=$(grep "^FROM openjdk:" "$PROJECT_ROOT/Dockerfile" | awk '{print $2}' | head -1)
echo "Maven: $dockerfile_maven"
echo "OpenJDK: $dockerfile_openjdk"
# 验证一致性
if [ "$dockerfile_maven" = "$MAVEN_IMAGE" ]; then
print_message $GREEN "✓ Maven 版本一致"
else
print_message $RED "✗ Maven 版本不一致"
echo " 配置文件: $MAVEN_IMAGE"
echo " Dockerfile: $dockerfile_maven"
fi
if [ "$dockerfile_openjdk" = "$OPENJDK_IMAGE" ]; then
print_message $GREEN "✓ OpenJDK 版本一致"
else
print_message $RED "✗ OpenJDK 版本不一致"
echo " 配置文件: $OPENJDK_IMAGE"
echo " Dockerfile: $dockerfile_openjdk"
fi
else
print_message $YELLOW "警告: Dockerfile 文件不存在"
fi
echo ""
print_message $BLUE "当前使用的版本总结:"
echo "✓ PostgreSQL 17 + PostGIS 3.5 (最新稳定版)"
echo "✓ Redis 8.0 (最新版本)"
echo "✓ OpenJDK 17 (LTS 版本)"
echo "✓ Maven 3.9.11 (最新稳定版)"
echo ""
print_message $YELLOW "升级注意事项:"
echo "1. PostgreSQL 17 包含性能改进和新特性"
echo "2. Redis 8.0 引入了新的数据结构和命令"
echo "3. 升级前请备份数据并在测试环境验证"
echo "4. 检查应用程序与新版本的兼容性"

View File

@ -0,0 +1,501 @@
#!/bin/bash
# QAUP 零停机更新脚本
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
local color=$1
local message=$2
echo -e "${color}${message}${NC}"
}
# 获取时间戳
get_timestamp() {
date '+%Y-%m-%d %H:%M:%S'
}
# 记录更新日志
log_update() {
local message=$1
local timestamp=$(get_timestamp)
echo "[$timestamp] $message" >> "./logs/update.log"
print_message $BLUE "[$timestamp] $message"
}
# 检查服务健康状态
check_service_health() {
local service=$1
local max_attempts=${2:-30}
local attempt=0
while [ $attempt -lt $max_attempts ]; do
case $service in
"app")
if curl -f -s http://localhost:8080/actuator/health &>/dev/null; then
return 0
fi
;;
"nginx")
if curl -f -s http://localhost/health &>/dev/null; then
return 0
fi
;;
"postgres")
if docker exec qaup-postgres pg_isready -h localhost -p 5432 -U postgres &>/dev/null; then
return 0
fi
;;
"redis")
if docker exec qaup-redis redis-cli ping &>/dev/null; then
return 0
fi
;;
esac
sleep 2
attempt=$((attempt + 1))
done
return 1
}
# 等待服务就绪
wait_for_service() {
local service=$1
local timeout=${2:-60}
log_update "等待 $service 服务就绪..."
if check_service_health "$service" $((timeout / 2)); then
log_update "$service 服务已就绪"
return 0
else
log_update "$service 服务启动超时"
return 1
fi
}
# 创建服务备份
create_service_backup() {
local service=$1
local backup_name="${service}_backup_$(date +%Y%m%d_%H%M%S)"
log_update "创建 $service 服务备份: $backup_name"
# 导出当前运行的容器为镜像
if docker ps --format "{{.Names}}" | grep -q "qaup-$service"; then
docker commit "qaup-$service" "$backup_name"
log_update "$service 服务备份已创建: $backup_name"
echo "$backup_name" > "./logs/${service}_backup.txt"
return 0
else
log_update "警告: $service 容器未运行,跳过备份"
return 1
fi
}
# 恢复服务备份
restore_service_backup() {
local service=$1
local backup_file="./logs/${service}_backup.txt"
if [ -f "$backup_file" ]; then
local backup_name=$(cat "$backup_file")
log_update "恢复 $service 服务备份: $backup_name"
# 停止当前容器
docker stop "qaup-$service" || true
docker rm "qaup-$service" || true
# 从备份镜像启动容器
docker run -d --name "qaup-$service" \
--network qaup_qaup-network \
"$backup_name"
if wait_for_service "$service"; then
log_update "$service 服务备份恢复成功"
return 0
else
log_update "$service 服务备份恢复失败"
return 1
fi
else
log_update "错误: 未找到 $service 服务备份信息"
return 1
fi
}
# 滚动更新应用服务
rolling_update_app() {
log_update "开始应用服务滚动更新..."
# 1. 创建备份
create_service_backup "app"
# 2. 构建新镜像
log_update "构建新的应用镜像..."
cd "$PROJECT_ROOT"
docker build -t qaup-app:new .
# 3. 启动新容器(临时端口)
log_update "启动新应用容器..."
docker run -d --name qaup-app-new \
--network qaup_qaup-network \
-p 8081:8080 \
--env-file .env \
-v qaup_app_logs:/app/logs \
-v qaup_app_uploads:/app/uploadPath \
qaup-app:new
# 4. 等待新容器就绪
if ! check_service_health "app" 30; then
log_update "新应用容器启动失败,回滚..."
docker stop qaup-app-new || true
docker rm qaup-app-new || true
return 1
fi
# 5. 更新 Nginx 配置指向新容器
log_update "更新负载均衡配置..."
docker exec qaup-nginx sh -c "
sed -i 's/qaup-app:8080/qaup-app-new:8080/g' /etc/nginx/conf.d/default.conf
nginx -s reload
"
# 6. 验证新服务
sleep 10
if curl -f -s http://localhost/prod-api/actuator/health &>/dev/null; then
log_update "新应用服务验证成功"
# 7. 停止旧容器
log_update "停止旧应用容器..."
docker stop qaup-app || true
docker rm qaup-app || true
# 8. 重命名新容器
docker rename qaup-app-new qaup-app
# 9. 更新镜像标签
docker tag qaup-app:new qaup-app:latest
docker rmi qaup-app:new || true
log_update "应用服务滚动更新完成"
return 0
else
log_update "新应用服务验证失败,回滚..."
# 回滚 Nginx 配置
docker exec qaup-nginx sh -c "
sed -i 's/qaup-app-new:8080/qaup-app:8080/g' /etc/nginx/conf.d/default.conf
nginx -s reload
"
# 清理新容器
docker stop qaup-app-new || true
docker rm qaup-app-new || true
return 1
fi
}
# 蓝绿部署
blue_green_deployment() {
log_update "开始蓝绿部署..."
local current_env="blue"
local new_env="green"
# 检查当前环境
if docker ps --format "{{.Names}}" | grep -q "qaup-app-green"; then
current_env="green"
new_env="blue"
fi
log_update "当前环境: $current_env, 目标环境: $new_env"
# 1. 构建新镜像
log_update "构建新镜像..."
cd "$PROJECT_ROOT"
docker build -t "qaup-app:$new_env" .
# 2. 启动新环境
log_update "启动 $new_env 环境..."
docker run -d --name "qaup-app-$new_env" \
--network qaup_qaup-network \
--env-file .env \
-v qaup_app_logs:/app/logs \
-v qaup_app_uploads:/app/uploadPath \
"qaup-app:$new_env"
# 3. 等待新环境就绪
if ! wait_for_service "app"; then
log_update "$new_env 环境启动失败"
docker stop "qaup-app-$new_env" || true
docker rm "qaup-app-$new_env" || true
return 1
fi
# 4. 切换流量
log_update "切换流量到 $new_env 环境..."
docker exec qaup-nginx sh -c "
sed -i 's/qaup-app-$current_env:8080/qaup-app-$new_env:8080/g' /etc/nginx/conf.d/default.conf
nginx -s reload
"
# 5. 验证新环境
sleep 10
if curl -f -s http://localhost/prod-api/actuator/health &>/dev/null; then
log_update "$new_env 环境验证成功"
# 6. 停止旧环境
log_update "停止 $current_env 环境..."
docker stop "qaup-app-$current_env" || true
docker rm "qaup-app-$current_env" || true
# 7. 重命名新环境容器
docker rename "qaup-app-$new_env" qaup-app
log_update "蓝绿部署完成"
return 0
else
log_update "$new_env 环境验证失败,回滚..."
# 回滚流量
docker exec qaup-nginx sh -c "
sed -i 's/qaup-app-$new_env:8080/qaup-app-$current_env:8080/g' /etc/nginx/conf.d/default.conf
nginx -s reload
"
# 清理失败的环境
docker stop "qaup-app-$new_env" || true
docker rm "qaup-app-$new_env" || true
return 1
fi
}
# 数据库在线迁移
database_online_migration() {
log_update "开始数据库在线迁移..."
# 1. 创建数据库备份
log_update "创建数据库备份..."
docker exec qaup-postgres /backup-db.sh
# 2. 检查迁移脚本
local migration_dir="$PROJECT_ROOT/sql/migrations"
if [ ! -d "$migration_dir" ]; then
log_update "未找到数据库迁移脚本目录: $migration_dir"
return 0
fi
# 3. 执行迁移脚本
for migration_file in "$migration_dir"/*.sql; do
if [ -f "$migration_file" ]; then
local filename=$(basename "$migration_file")
log_update "执行迁移脚本: $filename"
if docker exec qaup-postgres psql -U postgres -d qaup -f "/migrations/$filename"; then
log_update "迁移脚本 $filename 执行成功"
else
log_update "迁移脚本 $filename 执行失败,停止迁移"
return 1
fi
fi
done
log_update "数据库在线迁移完成"
return 0
}
# 健康检查和验证
post_update_verification() {
log_update "开始更新后验证..."
local checks_passed=0
local total_checks=5
# 1. 检查所有容器状态
log_update "检查容器状态..."
if docker ps --format "{{.Names}}" | grep -q "qaup-"; then
checks_passed=$((checks_passed + 1))
log_update "✓ 容器状态检查通过"
else
log_update "✗ 容器状态检查失败"
fi
# 2. 检查应用健康状态
log_update "检查应用健康状态..."
if check_service_health "app"; then
checks_passed=$((checks_passed + 1))
log_update "✓ 应用健康检查通过"
else
log_update "✗ 应用健康检查失败"
fi
# 3. 检查前端访问
log_update "检查前端访问..."
if curl -f -s http://localhost/health &>/dev/null; then
checks_passed=$((checks_passed + 1))
log_update "✓ 前端访问检查通过"
else
log_update "✗ 前端访问检查失败"
fi
# 4. 检查数据库连接
log_update "检查数据库连接..."
if check_service_health "postgres"; then
checks_passed=$((checks_passed + 1))
log_update "✓ 数据库连接检查通过"
else
log_update "✗ 数据库连接检查失败"
fi
# 5. 检查 Redis 连接
log_update "检查 Redis 连接..."
if check_service_health "redis"; then
checks_passed=$((checks_passed + 1))
log_update "✓ Redis 连接检查通过"
else
log_update "✗ Redis 连接检查失败"
fi
# 验证结果
log_update "验证完成: $checks_passed/$total_checks 项检查通过"
if [ $checks_passed -eq $total_checks ]; then
log_update "✓ 所有验证检查通过,更新成功"
return 0
else
log_update "✗ 部分验证检查失败,建议检查系统状态"
return 1
fi
}
# 完整的零停机更新流程
full_zero_downtime_update() {
log_update "========================================="
log_update "开始零停机更新流程"
log_update "========================================="
# 1. 预检查
log_update "执行更新前检查..."
if ! post_update_verification; then
log_update "更新前检查失败,取消更新"
return 1
fi
# 2. 数据库迁移
if ! database_online_migration; then
log_update "数据库迁移失败,取消更新"
return 1
fi
# 3. 应用更新
if ! rolling_update_app; then
log_update "应用更新失败,尝试回滚..."
restore_service_backup "app"
return 1
fi
# 4. 更新后验证
if ! post_update_verification; then
log_update "更新后验证失败,建议检查系统状态"
return 1
fi
log_update "========================================="
log_update "零停机更新完成"
log_update "========================================="
return 0
}
# 显示帮助信息
show_help() {
echo "QAUP 零停机更新脚本"
echo ""
echo "用法: $0 [命令]"
echo ""
echo "命令:"
echo " full 完整的零停机更新流程"
echo " rolling 滚动更新应用服务"
echo " blue-green 蓝绿部署"
echo " db-migration 数据库在线迁移"
echo " verify 更新后验证"
echo " backup 创建服务备份"
echo " restore 恢复服务备份"
echo ""
echo "示例:"
echo " $0 full # 执行完整更新流程"
echo " $0 rolling # 仅滚动更新应用"
echo " $0 backup app # 备份应用服务"
echo " $0 restore app # 恢复应用服务"
}
# 主函数
main() {
# 确保日志目录存在
mkdir -p "./logs"
if [ $# -eq 0 ]; then
show_help
exit 0
fi
local command=$1
shift
case $command in
full)
full_zero_downtime_update
;;
rolling)
rolling_update_app
;;
blue-green)
blue_green_deployment
;;
db-migration)
database_online_migration
;;
verify)
post_update_verification
;;
backup)
if [ $# -eq 0 ]; then
print_message $RED "请指定要备份的服务名称"
exit 1
fi
create_service_backup "$1"
;;
restore)
if [ $# -eq 0 ]; then
print_message $RED "请指定要恢复的服务名称"
exit 1
fi
restore_service_backup "$1"
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "未知命令: $command"
show_help
exit 1
;;
esac
}
main "$@"

View File

@ -0,0 +1,115 @@
# QAUP Docker 部署检查清单
## 部署前检查
### ✅ 环境准备
- [ ] 确认操作系统版本 (Ubuntu 18.04+ 推荐)
- [ ] 安装 Docker (20.10+)
- [ ] 安装 Docker Compose (1.29+)
- [ ] 确认用户在 docker 组中
- [ ] 确认网络连接正常
### ✅ 文件准备
- [ ] 项目文件完整下载/解压
- [ ] 进入 deploy 目录 (`cd QAUP-Management/deploy`)
- [ ] 运行路径验证 (`./test-paths.sh`)
- [ ] 所有脚本具有执行权限
### ✅ 数据准备
- [ ] 查看数据文件状态 (`./docker/postgres/db-manager.sh prepare-status`)
- [ ] 选择数据准备方式:
- [ ] 全新部署:`./docker/postgres/db-manager.sh prepare-default`
- [ ] 现有系统迁移:`./docker/postgres/db-manager.sh prepare-export`
- [ ] 自定义组合:`./docker/postgres/db-manager.sh prepare-custom`
- [ ] 验证数据文件已准备完成
### ✅ 配置检查
- [ ] 复制 `.env.template``.env`
- [ ] 修改数据库密码 (POSTGRES_PASSWORD, APP_DB_PASSWORD)
- [ ] 设置 Redis 密码 (REDIS_PASSWORD)
- [ ] 配置服务器域名 (SERVER_NAME)
- [ ] 调整 JVM 参数 (根据服务器配置)
## 部署过程检查
### ✅ 系统验证
- [ ] 运行环境验证 (`./docker/validate-environment.sh`)
- [ ] 检查端口占用情况
- [ ] 确认磁盘空间充足 (至少 20GB)
- [ ] 确认内存充足 (至少 4GB)
### ✅ 部署执行
- [ ] 运行完整部署 (`./deploy.sh install`)
- [ ] 等待所有服务启动完成
- [ ] 检查容器状态 (`docker ps`)
- [ ] 验证服务健康检查
### ✅ 功能验证
- [ ] 访问前端页面 (`http://localhost`)
- [ ] 测试 API 接口 (`http://localhost/prod-api`)
- [ ] 检查健康检查端点 (`http://localhost/health`)
- [ ] 验证数据库连接
- [ ] 测试文件上传功能
## 部署后检查
### ✅ 监控配置
- [ ] 查看系统监控 (`./docker/monitor.sh watch`)
- [ ] 检查日志输出 (`./deploy.sh logs`)
- [ ] 验证告警配置
- [ ] 测试备份功能
### ✅ 安全检查
- [ ] 修改默认密码
- [ ] 检查防火墙配置
- [ ] 验证网络隔离
- [ ] 确认敏感信息保护
### ✅ 性能优化
- [ ] 监控资源使用情况
- [ ] 调整 JVM 参数
- [ ] 优化数据库配置
- [ ] 配置日志轮转
## 常用命令参考
```bash
# 系统状态
./docker/ops-manager.sh overview
# 查看日志
./deploy.sh logs [服务名]
# 重启服务
./deploy.sh restart
# 备份数据
./docker/backup-restore.sh backup
# 系统监控
./docker/monitor.sh watch
# 性能分析
./docker/ops-manager.sh performance
```
## 故障排除
如果遇到问题,请按以下顺序检查:
1. **路径配置**: 运行 `./test-paths.sh` 验证
2. **环境验证**: 运行 `./docker/validate-environment.sh`
3. **容器状态**: 运行 `docker ps -a` 查看容器状态
4. **日志分析**: 运行 `./deploy.sh logs` 查看详细日志
5. **资源检查**: 运行 `./docker/monitor.sh health` 检查资源
更多故障排除信息请参考 `docs/故障排除.md`
## 联系支持
如果遇到无法解决的问题,请提供以下信息:
- 操作系统版本
- Docker 版本信息
- 错误日志内容
- 系统资源状态
- 部署步骤记录

View File

@ -0,0 +1,314 @@
# QAUP Docker 部署方案完成总结
## 项目概述
已成功为 QAUP 管理系统创建了完整的 Docker 容器化部署方案,支持在客户的 Ubuntu 内网环境中进行生产级部署。
## 完成的功能模块
### ✅ 1. Docker 构建配置
- **主应用 Dockerfile**: 多阶段构建,优化镜像大小
- **Nginx Dockerfile**: 前端静态文件服务和反向代理
- **启动脚本**: 应用启动脚本,支持健康检查
- **镜像准备脚本**: 离线部署镜像准备和导入
### ✅ 2. 前端构建和 Nginx 配置
- **前端构建脚本**: 自动化前端资源构建
- **Nginx 配置**: 生产环境优化配置
- **反向代理**: API 请求代理和静态资源服务
- **环境变量支持**: 支持不同环境的配置
### ✅ 3. 数据库初始化
- **PostgreSQL 配置**: 包含 PostGIS 扩展的数据库配置
- **初始化脚本**: 自动创建数据库架构和用户
- **备份恢复脚本**: 数据库备份和恢复功能
- **健康检查**: 数据库连接和状态检查
### ✅ 4. Docker Compose 配置
- **主配置文件**: docker-compose.yml
- **环境覆盖**: 生产环境和开发环境配置
- **网络配置**: 容器间网络隔离和通信
- **数据卷管理**: 数据持久化和备份
### ✅ 5. 环境变量和配置管理
- **配置模板**: .env.template 环境变量模板
- **配置管理脚本**: 配置初始化、验证和备份
- **敏感信息管理**: 密码生成和加密存储
- **环境验证**: 部署前环境检查
### ✅ 6. 健康检查和监控
- **系统监控脚本**: 实时监控系统状态和资源使用
- **日志管理**: 日志查看、分析和轮转
- **告警系统**: 自动告警和通知机制
- **健康检查配置**: 容器和服务健康检查
### ✅ 7. 部署脚本和文档
- **主部署脚本**: 一键部署和管理
- **离线部署支持**: 内网环境离线部署方案
- **详细文档**: 部署指南、故障排除和离线部署指南
- **Docker Compose 管理器**: 服务管理和操作脚本
### ✅ 8. 路径配置优化
- **统一部署目录**: 所有部署文件集中在 deploy 目录
- **构建上下文优化**: Docker 构建上下文指向项目根目录
- **路径自动处理**: 脚本自动处理相对路径和绝对路径
- **路径验证工具**: test-paths.sh 脚本验证路径配置正确性
### ✅ 8. 生产环境优化
- **性能调优脚本**: JVM、数据库、Nginx 性能优化
- **零停机更新**: 滚动更新和蓝绿部署
- **资源限制**: 容器资源使用限制和监控
- **系统参数优化**: 操作系统级别的性能优化
### ✅ 9. 安全配置
- **容器安全**: 非 root 用户运行和安全选项
- **网络安全**: 网络隔离和访问控制
- **文件权限**: 安全的文件权限设置
- **SSL/TLS 配置**: HTTPS 支持和证书管理
- **防火墙配置**: 端口访问控制
- **审计日志**: 安全事件记录和监控
### ✅ 10. 备份和恢复方案
- **完整备份**: 数据库、配置、上传文件的完整备份
- **增量备份**: 支持不同类型的备份策略
- **自动恢复**: 一键恢复功能
- **备份管理**: 备份文件管理和清理
### ✅ 11. 集成测试
- **全面测试**: 容器、服务、网络、数据库功能测试
- **性能测试**: 并发请求和响应时间测试
- **安全测试**: 安全配置和访问控制测试
- **持久化测试**: 数据持久化和恢复测试
### ✅ 12. 运维工具和脚本
- **运维管理脚本**: 系统概览、诊断、性能分析
- **服务管理**: 服务启停、状态查看、日志管理
- **备份管理**: 备份状态、创建、清理
- **报告生成**: 自动生成运维报告
## 文件结构总览
```
QAUP-Management/
├── docker/ # Docker 配置和脚本目录
│ ├── nginx/ # Nginx 配置
│ │ ├── Dockerfile # Nginx 容器构建文件
│ │ ├── nginx.conf # Nginx 主配置
│ │ ├── default.conf # 默认站点配置
│ │ ├── https.conf # HTTPS 配置
│ │ ├── nginx-env.conf.template # 环境变量配置模板
│ │ └── start-nginx.sh # Nginx 启动脚本
│ ├── postgres/ # PostgreSQL 配置
│ │ ├── init-db.sh # 数据库初始化脚本
│ │ ├── postgresql.conf # PostgreSQL 配置
│ │ ├── postgresql-optimized.conf # 性能优化配置
│ │ ├── backup-db.sh # 数据库备份脚本
│ │ ├── restore-db.sh # 数据库恢复脚本
│ │ ├── health-check.sh # 数据库健康检查
│ │ └── qaup_database_schema.sql # 数据库架构文件
│ ├── redis/ # Redis 配置
│ │ └── redis-optimized.conf # Redis 优化配置
│ ├── alert-manager.sh # 告警管理脚本
│ ├── backup-restore.sh # 综合备份恢复脚本
│ ├── build-frontend.sh # 前端构建脚本
│ ├── config-manager.sh # 配置管理脚本
│ ├── docker-compose-manager.sh # Docker Compose 管理脚本
│ ├── healthcheck.yml # 健康检查配置
│ ├── image-versions.env # 镜像版本配置
│ ├── integration-test.sh # 集成测试脚本
│ ├── load-images.sh # 镜像导入脚本
│ ├── log-manager.sh # 日志管理脚本
│ ├── monitor.sh # 系统监控脚本
│ ├── ops-manager.sh # 运维管理脚本
│ ├── performance-tuning.sh # 性能优化脚本
│ ├── prepare-offline-images.sh # 离线镜像准备脚本
│ ├── secrets-manager.sh # 敏感信息管理脚本
│ ├── security-setup.sh # 安全配置脚本
│ ├── start.sh # 应用启动脚本
│ ├── validate-environment.sh # 环境验证脚本
│ └── zero-downtime-update.sh # 零停机更新脚本
├── config/ # 应用配置目录
│ └── application-prod.yml # 生产环境配置
├── docs/ # 文档目录
│ ├── 部署指南.md # 部署指南
│ ├── 故障排除.md # 故障排除指南
│ └── 离线部署指南.md # 离线部署指南
├── Dockerfile # 主应用 Dockerfile
├── .dockerignore # Docker 忽略文件
├── .env.template # 环境变量模板
├── docker-compose.yml # Docker Compose 主配置
├── docker-compose.prod.yml # 生产环境配置覆盖
├── docker-compose.dev.yml # 开发环境配置覆盖
├── docker-compose.security.yml # 安全配置覆盖
├── deploy.sh # 主部署脚本
└── README-Docker.md # Docker 部署说明
```
## 路径配置技术说明
### 部署目录结构优化
为了提高部署的便利性和可维护性,我们将所有部署相关文件集中在 `deploy` 目录下:
- **工作目录**: 所有部署操作都在 `deploy` 目录下进行
- **构建上下文**: Docker 构建上下文指向项目根目录 (`context: ..`)
- **路径映射**: 脚本自动处理相对路径和绝对路径的转换
### 关键路径配置
```yaml
# docker-compose.yml 中的构建配置
qaup-app:
build:
context: .. # 指向项目根目录
dockerfile: deploy/Dockerfile # Dockerfile 相对于根目录的路径
qaup-nginx:
build:
context: .. # 指向项目根目录
dockerfile: deploy/docker/nginx/Dockerfile # Nginx Dockerfile 路径
```
### 路径验证工具
提供了 `test-paths.sh` 脚本来验证路径配置的正确性:
- 检查关键文件和目录是否存在
- 验证 Docker Compose 构建上下文配置
- 确认 Dockerfile 中的路径引用正确
## 核心特性
### 🚀 生产就绪
- 完整的生产环境配置
- 性能优化和资源限制
- 安全配置和访问控制
- 监控和告警系统
### 🔒 安全可靠
- 容器安全最佳实践
- 网络隔离和访问控制
- 数据加密和备份
- 审计日志和监控
### 📦 离线部署
- 完整的离线部署支持
- 镜像打包和导入
- 内网环境适配
- 无需外网连接
### 🛠 易于管理
- 一键部署和管理
- 图形化监控界面
- 自动化运维脚本
- 详细的文档和指南
### 🔄 零停机更新
- 滚动更新支持
- 蓝绿部署策略
- 自动回滚机制
- 健康检查验证
## 使用指南
### 快速开始
1. **环境准备**
```bash
# 验证环境
./docker/validate-environment.sh
# 初始化配置
./docker/config-manager.sh init
```
2. **一键部署**
```bash
# 完整部署
./deploy.sh install
```
3. **验证部署**
```bash
# 集成测试
./docker/integration-test.sh full
# 系统概览
./docker/ops-manager.sh overview
```
### 日常运维
```bash
# 系统监控
./docker/monitor.sh watch
# 健康检查
./docker/ops-manager.sh health-check
# 备份数据
./docker/backup-restore.sh full
# 查看日志
./docker/log-manager.sh view all
```
### 性能优化
```bash
# 自动优化
./docker/performance-tuning.sh all
# 安全配置
./docker/security-setup.sh all
```
## 技术栈
- **容器化**: Docker + Docker Compose
- **Web 服务器**: Nginx (Alpine)
- **应用运行时**: OpenJDK 17 JRE
- **数据库**: PostgreSQL 15 + PostGIS 3.3
- **缓存**: Redis 7
- **监控**: 自定义监控脚本
- **备份**: 自动化备份方案
## 支持的部署环境
- **操作系统**: Ubuntu 18.04+
- **Docker**: 20.10+
- **Docker Compose**: 1.29+
- **内存**: 4GB+ (推荐 8GB+)
- **磁盘**: 20GB+ (推荐 50GB+)
- **网络**: 支持内网离线部署
## 后续维护
### 定期任务
- 每日:健康检查、备份数据
- 每周:系统清理、日志轮转
- 每月:安全检查、性能优化
- 每季度:系统更新、容量规划
### 监控指标
- 系统资源使用率
- 服务响应时间
- 错误日志数量
- 数据库连接数
- 备份成功率
## 总结
QAUP Docker 部署方案已全面完成,提供了:
**完整的容器化部署方案**
**生产级的安全和性能配置**
**离线部署支持**
**全面的监控和告警系统**
**自动化的备份和恢复**
**零停机更新能力**
**详细的文档和运维工具**
该方案已准备好在客户的 Ubuntu 内网环境中进行生产部署,支持长期稳定运行和便捷的运维管理。
---
**部署方案创建完成时间**: $(date)
**版本**: 1.0.1
**状态**: ✅ 生产就绪

View File

@ -0,0 +1,457 @@
# QAUP 系统 Docker 部署
## 重要说明
**工作目录**: 请确保在 `deploy` 目录下执行所有部署命令。
```bash
cd QAUP-Management/deploy # 进入部署目录
./test-paths.sh # 验证路径配置(推荐)
```
## 快速开始
### 数据准备(首次部署必需)
```bash
# 全新部署(使用默认数据)
./docker/postgres/db-manager.sh prepare-default
# 或从现有系统迁移
./docker/postgres/db-manager.sh prepare-export
```
### 一键部署
```bash
# 完整部署
./deploy.sh install
# 查看系统状态
./docker/ops-manager.sh overview
```
### 基本操作
```bash
# 启动服务
./deploy.sh start
# 停止服务
./deploy.sh stop
# 重启服务
./deploy.sh restart
# 查看状态
./deploy.sh status
```
## 系统架构
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Nginx 容器 │ │ 应用容器 │ │ PostgreSQL │
│ (反向代理) │───▶│ (Spring Boot) │───▶│ (数据库) │
│ 端口: 80/443 │ │ 端口: 8080 │ │ 端口: 5432 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ Redis 容器 │
│ (缓存) │
│ 端口: 6379 │
└─────────────────┘
```
## 目录结构
```
QAUP-Management/
├── docker/ # Docker 配置和脚本
│ ├── nginx/ # Nginx 配置
│ ├── postgres/ # PostgreSQL 配置
│ ├── *.sh # 管理脚本
│ └── *.yml # 配置文件
├── config/ # 应用配置
├── data/ # 数据目录
│ ├── postgres/ # 数据库数据
│ ├── redis/ # Redis 数据
│ └── uploads/ # 上传文件
├── logs/ # 日志目录
├── backup/ # 备份目录
├── ssl/ # SSL 证书
├── docker-compose*.yml # Docker Compose 配置
├── .env # 环境变量
└── deploy.sh # 主部署脚本
```
## 管理脚本
### 主要脚本
| 脚本 | 功能 | 示例 |
| -------------------------- | ---------- | ---------------------------------- |
| `deploy.sh` | 主部署脚本 | `./deploy.sh install` |
| `docker/ops-manager.sh` | 运维管理 | `./docker/ops-manager.sh overview` |
| `docker/monitor.sh` | 系统监控 | `./docker/monitor.sh watch` |
| `docker/backup-restore.sh` | 备份恢复 | `./docker/backup-restore.sh full` |
| `docker/log-manager.sh` | 日志管理 | `./docker/log-manager.sh view app` |
### 配置脚本
| 脚本 | 功能 | 示例 |
| -------------------------------- | -------- | ------------------------------------ |
| `docker/config-manager.sh` | 配置管理 | `./docker/config-manager.sh init` |
| `docker/security-setup.sh` | 安全配置 | `./docker/security-setup.sh all` |
| `docker/performance-tuning.sh` | 性能优化 | `./docker/performance-tuning.sh all` |
| `docker/validate-environment.sh` | 环境验证 | `./docker/validate-environment.sh` |
### 测试脚本
| 脚本 | 功能 | 示例 |
| ---------------------------- | -------- | ----------------------------------- |
| `docker/integration-test.sh` | 集成测试 | `./docker/integration-test.sh full` |
| `docker/alert-manager.sh` | 告警管理 | `./docker/alert-manager.sh check` |
## 常用操作
### 系统管理
```bash
# 查看系统概览
./docker/ops-manager.sh overview
# 快速诊断
./docker/ops-manager.sh diagnosis
# 健康检查
./docker/ops-manager.sh health-check
# 性能分析
./docker/ops-manager.sh performance
# 安全检查
./docker/ops-manager.sh security
# 系统清理
./docker/ops-manager.sh cleanup
```
### 服务管理
```bash
# 重启特定服务
./docker/ops-manager.sh service restart app
# 查看服务状态
./docker/ops-manager.sh service status
# 启动所有服务
./docker/ops-manager.sh service start all
```
### 日志管理
```bash
# 查看所有日志
./docker/ops-manager.sh logs all
# 查看应用日志
./docker/ops-manager.sh logs app 100
# 实时监控日志
./docker/log-manager.sh monitor app
# 分析错误日志
./docker/log-manager.sh analyze
```
### 备份管理
```bash
# 查看备份状态
./docker/ops-manager.sh backup status
# 创建完整备份
./docker/ops-manager.sh backup create
# 列出所有备份
./docker/ops-manager.sh backup list
# 清理旧备份
./docker/ops-manager.sh backup cleanup
```
### 监控和告警
```bash
# 实时监控
./docker/monitor.sh watch
# 检查告警
./docker/alert-manager.sh check
# 持续监控
./docker/alert-manager.sh monitor 300
# 查看告警历史
./docker/alert-manager.sh history
```
## 配置管理
### 环境配置
主要配置文件:`.env`
```bash
# 数据库配置
POSTGRES_PASSWORD=强密码
APP_DB_PASSWORD=强密码
# 服务器配置
SERVER_NAME=your-domain.com
NGINX_PORT=80
# 其他配置...
```
### 配置操作
```bash
# 初始化配置
./docker/config-manager.sh init
# 验证配置
./docker/config-manager.sh validate
# 备份配置
./docker/config-manager.sh backup
# 生成配置
./docker/config-manager.sh generate prod
```
## 性能优化
### 自动优化
```bash
# 应用所有优化
./docker/performance-tuning.sh all
# 仅优化 JVM
./docker/performance-tuning.sh jvm
# 仅优化数据库
./docker/performance-tuning.sh database
```
### 手动优化
1. **JVM 参数调优**:编辑 `.env` 中的 `JVM_OPTS`
2. **数据库优化**:修改 `docker/postgres/postgresql.conf`
3. **Nginx 优化**:调整 `docker/nginx/nginx.conf`
## 安全配置
### 安全设置
```bash
# 应用所有安全配置
./docker/security-setup.sh all
# 配置 SSL/TLS
./docker/security-setup.sh ssl
# 配置防火墙
sudo ./docker/security-setup.sh firewall
# 配置文件权限
./docker/security-setup.sh permissions
```
### 安全检查
```bash
# 安全检查
./docker/ops-manager.sh security
# 密码强度检查
./docker/secrets-manager.sh check
# 生成强密码
./docker/secrets-manager.sh generate
```
## 故障排除
### 常见问题
1. **容器无法启动**
```bash
# 查看容器日志
docker logs qaup-app
# 检查资源使用
docker stats
# 重启服务
./deploy.sh restart
```
2. **服务无响应**
```bash
# 健康检查
./docker/ops-manager.sh health-check
# 查看系统状态
./docker/ops-manager.sh diagnosis
# 检查网络连接
curl -I http://localhost/health
```
3. **性能问题**
```bash
# 性能分析
./docker/ops-manager.sh performance
# 应用性能优化
./docker/performance-tuning.sh all
# 监控资源使用
./docker/monitor.sh resources
```
### 诊断工具
```bash
# 快速诊断
./docker/ops-manager.sh diagnosis
# 完整集成测试
./docker/integration-test.sh full
# 环境验证
./docker/validate-environment.sh
# 生成运维报告
./docker/ops-manager.sh report
```
## 更新和维护
### 系统更新
```bash
# 零停机更新
./docker/zero-downtime-update.sh full
# 滚动更新
./docker/zero-downtime-update.sh rolling
# 蓝绿部署
./docker/zero-downtime-update.sh blue-green
```
### 定期维护
```bash
# 每日维护
./docker/ops-manager.sh diagnosis
./docker/ops-manager.sh backup create
# 每周维护
./docker/ops-manager.sh cleanup
./docker/log-manager.sh cleanup 30
# 每月维护
./docker/ops-manager.sh security
./docker/performance-tuning.sh all
```
## 离线部署
### 准备离线包
在有网络的环境中:
```bash
# 准备镜像
./docker/prepare-offline-images.sh
# 创建部署包
# (参考 docs/离线部署指南.md)
```
### 离线安装
在内网环境中:
```bash
# 解压部署包
tar -xzf qaup-offline-deploy-*.tar.gz
# 导入镜像
./docker/load-images.sh
# 安装系统
./install.sh
./deploy.sh install
```
## 监控和告警
### 监控设置
```bash
# 启动持续监控
./docker/monitor.sh watch 60 &
# 启动告警监控
./docker/alert-manager.sh monitor 300 &
# 配置日志轮转
./docker/log-manager.sh setup
```
### 告警配置
编辑 `docker/healthcheck.yml` 配置告警规则和阈值。
## 支持和文档
### 详细文档
- [部署指南](docs/部署指南.md)
- [离线部署指南](docs/离线部署指南.md)
- [故障排除](docs/故障排除.md)
### 获取帮助
```bash
# 查看脚本帮助
./deploy.sh help
./docker/ops-manager.sh help
./docker/monitor.sh help
# 查看系统状态
./docker/ops-manager.sh overview
# 运行诊断
./docker/ops-manager.sh diagnosis
```
### 联系支持
如遇问题,请提供:
1. 系统信息:`./docker/ops-manager.sh report`
2. 错误日志:`./docker/log-manager.sh analyze`
3. 容器状态:`docker ps -a`
4. 配置信息:`.env` 文件(隐藏敏感信息)
---
**注意**:本文档持续更新,请定期查看最新版本。

601
deploy/docs/故障排除.md Normal file
View File

@ -0,0 +1,601 @@
# QAUP 系统故障排除指南
## 概述
本文档提供了 QAUP 系统常见问题的诊断和解决方案。按照问题类型分类,提供详细的排查步骤和解决方法。
## 快速诊断
### 系统状态检查
```bash
# 快速检查所有服务状态
./deploy.sh status
# 运行完整的系统检查
./docker/monitor.sh status
# 检查系统告警
./docker/alert-manager.sh check
```
### 日志快速查看
```bash
# 查看所有服务的最新日志
./deploy.sh logs all
# 查看错误日志分析
./docker/log-manager.sh analyze
```
## 容器相关问题
### 问题 1: 容器无法启动
**症状**: 容器状态显示 `Exited``Restarting`
**诊断步骤**:
```bash
# 查看容器状态
docker ps -a
# 查看容器启动日志
docker logs <container_name>
# 检查容器配置
docker inspect <container_name>
```
**常见原因和解决方案**:
1. **端口冲突**
```bash
# 检查端口占用
netstat -tuln | grep <port>
# 解决方案:修改 .env 文件中的端口配置
nano .env
```
2. **资源不足**
```bash
# 检查系统资源
free -h
df -h
# 解决方案:释放资源或增加服务器配置
```
3. **配置文件错误**
```bash
# 验证配置文件
./docker/config-manager.sh validate
# 重新初始化配置
./docker/config-manager.sh init
```
### 问题 2: 容器健康检查失败
**症状**: 容器状态显示 `unhealthy`
**诊断步骤**:
```bash
# 查看健康检查日志
docker inspect <container_name> | grep -A 10 "Health"
# 手动执行健康检查命令
docker exec <container_name> <health_check_command>
```
**解决方案**:
```bash
# 重启不健康的容器
docker restart <container_name>
# 如果持续失败,检查应用日志
docker logs <container_name> | tail -100
```
## 数据库相关问题
### 问题 3: 数据库连接失败
**症状**: 应用日志显示数据库连接错误
**诊断步骤**:
```bash
# 检查数据库容器状态
docker ps | grep qaup-postgres
# 测试数据库连接
docker exec qaup-postgres pg_isready -h localhost -p 5432 -U postgres
# 检查数据库日志
docker logs qaup-postgres | tail -50
```
**常见原因和解决方案**:
1. **数据库未完全启动**
```bash
# 等待数据库完全启动
sleep 30
# 重新测试连接
docker exec qaup-postgres pg_isready -h localhost -p 5432 -U postgres
```
2. **密码错误**
```bash
# 检查环境变量
grep POSTGRES .env
# 重置数据库密码(需要重新创建容器)
docker-compose down
docker volume rm qaup_postgres_data
docker-compose up -d
```
3. **数据库损坏**
```bash
# 检查数据库完整性
docker exec qaup-postgres psql -U postgres -d qaup -c "SELECT version();"
# 如果损坏,从备份恢复
docker exec qaup-postgres /restore-db.sh /backup/latest_backup.sql.gz
```
### 问题 4: 数据库性能问题
**症状**: 查询响应慢,应用超时
**诊断步骤**:
```bash
# 检查数据库连接数
docker exec qaup-postgres psql -U postgres -d qaup -c "SELECT count(*) FROM pg_stat_activity;"
# 检查慢查询
docker exec qaup-postgres psql -U postgres -d qaup -c "SELECT query, query_start, state FROM pg_stat_activity WHERE state = 'active';"
# 检查数据库锁
docker exec qaup-postgres psql -U postgres -d qaup -c "SELECT * FROM pg_locks WHERE NOT granted;"
```
**解决方案**:
```bash
# 终止长时间运行的查询
docker exec qaup-postgres psql -U postgres -d qaup -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'active' AND query_start < now() - interval '5 minutes';"
# 重启数据库容器
docker restart qaup-postgres
# 优化数据库配置
nano docker/postgres/postgresql.conf
```
## 应用相关问题
### 问题 5: 应用启动失败
**症状**: qaup-app 容器无法启动或立即退出
**诊断步骤**:
```bash
# 查看应用启动日志
docker logs qaup-app
# 检查 JVM 参数
grep JVM_OPTS .env
# 检查应用配置
docker exec qaup-app cat /app/config/application-prod.yml
```
**常见原因和解决方案**:
1. **内存不足**
```bash
# 检查系统内存
free -h
# 调整 JVM 内存参数
nano .env
# 修改 JVM_OPTS减少 -Xmx 值
```
2. **配置文件错误**
```bash
# 验证配置文件语法
docker run --rm -v $(pwd)/config:/config mikefarah/yq eval /config/application-prod.yml
# 重新生成配置
./docker/config-manager.sh generate prod
```
3. **依赖服务未就绪**
```bash
# 检查数据库和 Redis 状态
docker exec qaup-postgres pg_isready
docker exec qaup-redis redis-cli ping
# 重启应用容器
docker restart qaup-app
```
### 问题 6: 应用内存泄漏
**症状**: 应用内存使用持续增长,最终 OOM
**诊断步骤**:
```bash
# 监控内存使用
docker stats qaup-app
# 检查 JVM 内存使用
docker exec qaup-app jstat -gc 1
# 生成堆转储
docker exec qaup-app jcmd 1 GC.run_finalization
docker exec qaup-app jcmd 1 VM.gc
```
**解决方案**:
```bash
# 调整 JVM 参数
nano .env
# 添加内存分析参数
JVM_OPTS="... -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs/"
# 重启应用
docker restart qaup-app
# 定期重启应用(临时方案)
echo "0 2 * * * docker restart qaup-app" | crontab -
```
## 网络相关问题
### 问题 7: 前端无法访问
**症状**: 浏览器无法打开前端页面
**诊断步骤**:
```bash
# 检查 Nginx 容器状态
docker ps | grep qaup-nginx
# 测试 Nginx 健康检查
curl -I http://localhost/health
# 检查端口监听
netstat -tuln | grep :80
```
**解决方案**:
```bash
# 重启 Nginx 容器
docker restart qaup-nginx
# 检查 Nginx 配置
docker exec qaup-nginx nginx -t
# 查看 Nginx 错误日志
docker logs qaup-nginx | grep error
```
### 问题 8: API 请求失败
**症状**: 前端可以访问,但 API 请求返回 502/504 错误
**诊断步骤**:
```bash
# 测试后端应用健康检查
curl http://localhost:8080/actuator/health
# 检查 Nginx 到应用的连接
docker exec qaup-nginx nc -z qaup-app 8080
# 查看 Nginx 访问日志
docker logs qaup-nginx | grep "prod-api"
```
**解决方案**:
```bash
# 检查应用容器状态
docker ps | grep qaup-app
# 重启应用容器
docker restart qaup-app
# 检查网络连接
docker network ls
docker network inspect qaup_qaup-network
```
## 性能相关问题
### 问题 9: 系统响应慢
**症状**: 页面加载慢API 响应时间长
**诊断步骤**:
```bash
# 检查系统资源使用
./docker/monitor.sh resources
# 检查容器资源使用
docker stats
# 测试响应时间
curl -w "@curl-format.txt" -o /dev/null -s http://localhost/health
```
**解决方案**:
```bash
# 优化数据库连接池
nano config/application-prod.yml
# 调整 maxActive, minIdle 等参数
# 增加 JVM 内存
nano .env
# 增加 -Xmx 值
# 启用 Nginx 缓存
nano docker/nginx/default.conf
# 添加缓存配置
```
### 问题 10: 磁盘空间不足
**症状**: 系统告警磁盘使用率过高
**诊断步骤**:
```bash
# 检查磁盘使用
df -h
# 检查日志文件大小
du -sh logs/
# 检查 Docker 空间使用
docker system df
```
**解决方案**:
```bash
# 清理旧日志
./docker/log-manager.sh cleanup 7
# 清理 Docker 资源
docker system prune -f
# 压缩日志文件
./docker/log-manager.sh compress
# 设置日志轮转
./docker/log-manager.sh setup
```
## 数据相关问题
### 问题 11: 数据丢失
**症状**: 数据库中的数据消失或损坏
**诊断步骤**:
```bash
# 检查数据卷
docker volume ls | grep qaup
# 检查数据库表
docker exec qaup-postgres psql -U postgres -d qaup -c "\dt"
# 检查备份文件
ls -la backup/postgres/
```
**解决方案**:
```bash
# 从最近的备份恢复
docker exec qaup-postgres /restore-db.sh /backup/qaup_backup_latest.sql.gz
# 如果没有备份,检查数据卷
docker volume inspect qaup_postgres_data
# 重新初始化数据库(最后手段)
docker-compose down
docker volume rm qaup_postgres_data
docker-compose up -d
```
### 问题 12: 备份失败
**症状**: 数据库备份脚本执行失败
**诊断步骤**:
```bash
# 手动执行备份
docker exec qaup-postgres /backup-db.sh
# 检查备份目录权限
ls -la backup/postgres/
# 检查磁盘空间
df -h backup/
```
**解决方案**:
```bash
# 修复目录权限
sudo chown -R $USER:$USER backup/
chmod 755 backup/postgres/
# 清理旧备份释放空间
find backup/postgres/ -name "*.sql.gz" -mtime +30 -delete
# 重新执行备份
docker exec qaup-postgres /backup-db.sh
```
## 监控和告警问题
### 问题 13: 监控脚本无法运行
**症状**: 监控脚本报错或无输出
**诊断步骤**:
```bash
# 检查脚本权限
ls -la docker/monitor.sh
# 手动运行脚本
bash -x docker/monitor.sh status
# 检查依赖工具
which curl nc
```
**解决方案**:
```bash
# 修复脚本权限
chmod +x docker/monitor.sh
# 安装缺失的工具
sudo apt install curl netcat-openbsd
# 重新运行监控
./docker/monitor.sh status
```
### 问题 14: 告警不工作
**症状**: 系统异常但没有收到告警
**诊断步骤**:
```bash
# 测试告警系统
./docker/alert-manager.sh test
# 检查告警配置
cat docker/healthcheck.yml
# 查看告警日志
tail -f logs/alerts.log
```
**解决方案**:
```bash
# 重新配置告警
nano docker/healthcheck.yml
# 启动持续监控
./docker/alert-manager.sh monitor 60 &
# 检查告警历史
./docker/alert-manager.sh history 50
```
## 紧急恢复程序
### 完全系统故障
如果系统完全无法访问:
```bash
# 1. 停止所有服务
docker-compose down
# 2. 检查系统资源
free -h
df -h
# 3. 清理 Docker 资源
docker system prune -f
# 4. 从备份恢复配置
cp backup/config/config_backup_latest.tar.gz .
tar -xzf config_backup_latest.tar.gz
# 5. 重新启动服务
./deploy.sh start
# 6. 验证服务状态
./deploy.sh status
```
### 数据库完全损坏
```bash
# 1. 停止应用服务
docker stop qaup-app
# 2. 备份当前数据(如果可能)
docker exec qaup-postgres pg_dumpall -U postgres > emergency_backup.sql
# 3. 删除损坏的数据卷
docker-compose down
docker volume rm qaup_postgres_data
# 4. 重新创建数据库
docker-compose up -d qaup-postgres
# 5. 等待数据库启动
sleep 60
# 6. 从备份恢复
docker exec qaup-postgres /restore-db.sh /backup/qaup_backup_latest.sql.gz
# 7. 启动应用服务
docker start qaup-app
```
## 预防措施
### 定期维护
```bash
# 创建维护脚本
cat > maintenance.sh << 'EOF'
#!/bin/bash
# 每日维护脚本
# 检查系统状态
./docker/monitor.sh status
# 检查告警
./docker/alert-manager.sh check
# 备份数据库
docker exec qaup-postgres /backup-db.sh
# 清理旧日志
./docker/log-manager.sh cleanup 30
# 生成监控报告
./docker/monitor.sh report
EOF
chmod +x maintenance.sh
# 设置定时任务
echo "0 2 * * * /path/to/qaup/maintenance.sh" | crontab -
```
### 监控设置
```bash
# 启动持续监控
./docker/alert-manager.sh monitor 300 &
# 设置日志轮转
./docker/log-manager.sh setup
# 配置系统监控
./docker/monitor.sh watch 60 &
```
---
**注意**: 如果问题仍然无法解决,请收集相关日志和系统信息,联系技术支持团队。

View File

@ -0,0 +1,204 @@
# QAUP 数据导出导入指南
## 概述
本指南介绍如何从现有 QAUP 数据库导出数据,并在新环境中导入这些数据。我们采用分层数据管理策略,将数据分为不同类别,以便灵活管理。
## 数据分类
### 1. 系统基础配置数据(必需)
- **包含表**: `sys_config`, `sys_dict_type`, `sys_dict_data`, `sys_dept`, `sys_post`, `sys_role`, `sys_menu`, `sys_user`, `sys_user_role`, `sys_user_post`, `sys_role_menu`, `sys_role_dept`, `sys_notice`
- **用途**: 系统运行的基础配置,包括菜单、角色、权限等
- **特点**: 必须导入,否则系统无法正常运行
### 2. 业务基础数据(推荐)
- **包含表**: `sys_vehicle_type`, `airport_areas`, `spatial_rules`, `spatial_rule_vehicle_types`, `transport_routes`
- **用途**: 业务配置数据,如车辆类型、机场区域、空间规则等
- **特点**: 建议导入,包含实际业务配置
### 3. 示例数据(可选)
- **包含表**: `sys_vehicle_info`
- **用途**: 示例车辆信息,用于演示和测试
- **特点**: 可选导入,生产环境可能不需要
### 4. 实时数据(不导出)
- **包含表**: `vehicle_locations`, `vehicle_trajectories`, `vehicle_commands`, `geofence_events`, `uv_violation_events`, `sys_logininfor`, `sys_oper_log`, `sys_job_log`
- **用途**: 实时运行数据、日志等
- **特点**: 不导出,新环境启动后自动生成
## 数据导出
### 前提条件
- 安装 PostgreSQL 客户端工具(包含 `pg_dump`
- 能够连接到现有的 QAUP 数据库
- 有足够的磁盘空间存储导出文件
### 导出步骤
#### 1. 设置环境变量
```bash
export DB_HOST=localhost # 数据库主机
export DB_PORT=5432 # 数据库端口
export DB_NAME=qaup # 数据库名称
export DB_USER=postgres # 数据库用户
export EXPORT_DIR=./export # 导出目录
```
#### 2. 执行导出(推荐方式)
```bash
cd deploy/docker/postgres
./db-manager.sh export-initial
```
这将生成以下文件:
- `01_system_data.sql` - 系统基础配置数据
- `02_business_data.sql` - 业务基础数据
- `03_sample_data.sql` - 示例数据
- `initial_data_complete.sql` - 合并的完整初始数据文件
#### 3. 其他导出选项
```bash
# 导出分层初始数据(推荐)
./db-manager.sh export-initial
# 导出完整数据库
./db-manager.sh export-all
# 仅导出表结构
./db-manager.sh export-schema
# 数据库备份
./db-manager.sh backup
```
## 数据导入
### 方法一:使用合并文件(推荐)
1. 将 `initial_data_complete.sql` 复制到 `deploy/docker/postgres/initial_data.sql`
2. 启动 Docker 容器,数据会自动导入
```bash
cp export/initial_data_complete.sql deploy/docker/postgres/initial_data.sql
./deploy.sh install
```
### 方法二:分层导入
如果需要选择性导入,可以分别处理各层数据:
```bash
# 1. 复制系统基础数据(必需)
cp export/01_system_data.sql deploy/docker/postgres/initial_data.sql
# 2. 可选:追加业务数据
cat export/02_business_data.sql >> deploy/docker/postgres/initial_data.sql
# 3. 可选:追加示例数据
cat export/03_sample_data.sql >> deploy/docker/postgres/initial_data.sql
# 4. 启动容器
./deploy.sh install
```
### 方法三:手动导入
如果容器已经运行,可以手动导入数据:
```bash
# 进入数据库容器
docker exec -it qaup-postgres psql -U postgres -d qaup
# 在容器内执行
\i /docker-entrypoint-initdb.d/initial_data.sql
```
## 验证导入
### 1. 检查表数据
```sql
-- 检查用户表
SELECT COUNT(*) FROM sys_user;
-- 检查菜单表
SELECT COUNT(*) FROM sys_menu;
-- 检查车辆类型表
SELECT COUNT(*) FROM sys_vehicle_type;
-- 检查机场区域表
SELECT COUNT(*) FROM airport_areas;
```
### 2. 测试登录
- 使用导出的管理员账号登录系统
- 检查菜单和权限是否正确显示
- 验证业务功能是否正常
## 注意事项
### 1. 密码处理
- 导出的用户密码是加密的,可以直接使用
- 如果需要重置密码,请在系统中操作
### 2. 序列值
- 导出脚本会自动更新序列值
- 确保新插入的数据不会产生主键冲突
### 3. 外键约束
- 导入时会临时禁用触发器
- 导入完成后会重新启用
### 4. 数据一致性
- 建议在业务低峰期进行导出
- 确保导出过程中数据不被修改
## 故障排除
### 1. 连接失败
```bash
# 检查数据库连接
pg_isready -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME
```
### 2. 权限问题
```bash
# 确保用户有足够权限
GRANT SELECT ON ALL TABLES IN SCHEMA public TO your_user;
```
### 3. 导入失败
- 检查 SQL 文件语法
- 查看 PostgreSQL 日志
- 确认表结构已正确创建
### 4. 数据不完整
- 检查导出日志
- 验证源数据库数据完整性
- 重新执行导出过程
## 最佳实践
1. **定期备份**: 建议定期导出数据作为备份
2. **版本控制**: 将初始数据文件纳入版本控制
3. **环境隔离**: 不同环境使用不同的数据集
4. **测试验证**: 在测试环境先验证导入过程
5. **文档更新**: 及时更新数据结构变更文档
## 自动化脚本
可以创建自动化脚本定期导出数据:
```bash
#!/bin/bash
# auto-export.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="./backups/$DATE"
mkdir -p "$BACKUP_DIR"
export EXPORT_DIR="$BACKUP_DIR"
./export-data.sh -i
echo "数据导出完成: $BACKUP_DIR"
```
这样可以确保始终有最新的数据备份可用于新环境部署。

View File

@ -0,0 +1,141 @@
# QAUP 数据库初始数据文件说明
## 文件结构
```
deploy/docker/postgres/
├── qaup_database_schema.sql # 数据库表结构(必需)
├── initial_data.template.sql # 默认初始数据模板
├── initial_data.sql # 实际使用的初始数据(优先级最高)
├── 01_system_data.sql # 系统配置数据(导出生成)
├── 02_business_data.sql # 业务基础数据(导出生成)
├── 03_sample_data.sql # 示例数据(导出生成)
├── db-manager.sh # 统一数据库管理工具
├── init-db.sh # 数据库初始化脚本
└── postgresql.conf # PostgreSQL 配置文件
```
## 数据文件优先级
数据库初始化脚本 `init-db.sh` 按以下优先级加载数据:
1. **`initial_data.sql`** - 如果存在,优先使用(通常是从现有系统导出的完整数据)
2. **分层数据文件** - 如果 `initial_data.sql` 不存在,按顺序加载:
- `01_system_data.sql` - 系统基础配置
- `02_business_data.sql` - 业务基础数据
- `03_sample_data.sql` - 示例数据
## 使用场景
### 场景1全新部署使用默认数据
```bash
# 复制模板文件
cp initial_data.template.sql initial_data.sql
# 启动部署
./deploy.sh install
```
### 场景2从现有系统迁移
```bash
# 1. 从现有数据库导出并准备数据(一步完成)
./db-manager.sh prepare-export
# 2. 启动部署
./deploy.sh install
```
### 场景3选择性数据导入
```bash
# 1. 从现有数据库导出分层数据
./db-manager.sh export-initial
# 2. 自定义数据组合
./db-manager.sh prepare-custom
# 3. 启动部署
./deploy.sh install
```
### 场景4自定义数据组合
```bash
# 1. 创建自定义的 initial_data.sql
cat > initial_data.sql << 'EOF'
-- 自定义初始数据组合
EOF
# 2. 添加系统基础数据
cat export/01_system_data.sql >> initial_data.sql
# 3. 可选:添加业务数据
cat export/02_business_data.sql >> initial_data.sql
# 4. 启动部署
./deploy.sh install
```
## 数据文件内容说明
### `initial_data.template.sql`
- **用途**: 默认初始数据模板
- **内容**: 系统运行的最小数据集
- **特点**: 包含默认管理员账号、基础菜单、示例配置
- **适用**: 全新部署、演示环境
### `01_system_data.sql`(导出生成)
- **用途**: 系统基础配置数据
- **内容**: 配置、字典、菜单、角色、用户等
- **特点**: 从现有系统导出的实际配置
- **适用**: 生产环境迁移
### `02_business_data.sql`(导出生成)
- **用途**: 业务基础数据
- **内容**: 车辆类型、机场区域、空间规则等
- **特点**: 业务相关的配置数据
- **适用**: 保留业务配置的迁移
### `03_sample_data.sql`(导出生成)
- **用途**: 示例数据
- **内容**: 示例车辆信息等
- **特点**: 用于测试和演示
- **适用**: 开发、测试环境
## 注意事项
1. **文件优先级**: `initial_data.sql` 存在时,分层文件会被忽略
2. **数据冲突**: 确保导入的数据不会产生主键冲突
3. **序列更新**: 所有数据文件都包含序列值更新语句
4. **备份建议**: 导入前备份现有数据
5. **权限检查**: 确保数据库用户有足够的导入权限
## 故障排除
### 问题1数据导入失败
```bash
# 检查数据文件语法
psql -U postgres -d qaup --dry-run -f initial_data.sql
# 查看详细错误信息
docker logs qaup-postgres
```
### 问题2序列值错误
```sql
-- 手动更新序列值
SELECT setval('table_id_seq', (SELECT MAX(id) FROM table_name));
```
### 问题3权限不足
```sql
-- 授予必要权限
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO qaup_app;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO qaup_app;
```
## 最佳实践
1. **版本控制**: 将使用的 `initial_data.sql` 纳入版本控制
2. **环境隔离**: 不同环境使用不同的数据文件
3. **定期导出**: 定期从生产环境导出最新配置
4. **测试验证**: 在测试环境验证数据导入过程
5. **文档更新**: 及时更新数据结构变更文档

View File

@ -0,0 +1,388 @@
# QAUP 系统离线部署指南
## 概述
本文档详细介绍了如何在无互联网连接的内网环境中部署 QAUP 系统。离线部署包含所有必需的 Docker 镜像、配置文件和脚本,确保在完全隔离的网络环境中也能成功部署。
## 离线部署流程
### 阶段一:准备离线部署包(在有网络环境中进行)
#### 1. 环境要求
**准备环境**
- 有互联网连接的 Linux/macOS 系统
- Docker 20.10+ 和 Docker Compose 1.29+
- 至少 10GB 可用磁盘空间
#### 2. 获取项目代码
```bash
# 克隆或下载项目代码
git clone <项目地址>
cd QAUP-Management
# 或者解压项目源码包
tar -xzf qaup-source-code.tar.gz
cd QAUP-Management
```
#### 3. 准备 Docker 镜像
```bash
# 运行镜像准备脚本
./docker/prepare-offline-images.sh
```
该脚本将执行以下操作:
- 拉取所有必需的基础镜像
- 构建自定义应用镜像
- 导出所有镜像为 tar.gz 文件
- 生成镜像清单和校验和
#### 4. 完成离线部署包创建
镜像准备脚本执行完成后,会自动创建完整的离线部署包:
```bash
# 脚本执行完成后,会显示类似输出:
# 离线部署包创建完成: qaup-offline-deploy-20240801_143022.tar.gz
# 部署包大小: 1.2G
# 查看生成的文件
ls -lh qaup-offline-deploy-*.tar.gz
```
**离线部署包包含:**
- Docker镜像文件images/
- 部署脚本scripts/
- Docker配置docker/
- 文档docs/
- 配置文件docker-compose.yml, docker-compose.prod.yml, .env.template
- 镜像导入脚本
- 校验和文件
### 阶段二:离线部署(在客户内网环境中进行)
#### 1. 传输部署包
将离线部署包传输到目标服务器:
```bash
# 通过 U盘、移动硬盘等方式传输文件
# 或通过内网文件传输
scp qaup-offline-deploy-*.tar.gz user@target-server:/opt/
```
#### 2. 解压和验证
```bash
# 进入目标目录
cd /opt
# 验证文件完整性
sha256sum -c qaup-offline-deploy-*.tar.gz.sha256
# 解压部署包
tar -xzf qaup-offline-deploy-*.tar.gz
cd qaup-offline-deploy-*
```
#### 3. 运行离线安装
```bash
# 导入镜像
./scripts/load-images.sh
# 初始化配置
./scripts/deploy.sh init
```
安装脚本将:
- 检查 Docker 环境
- 导入所有 Docker 镜像
- 初始化配置文件
- 设置脚本权限
- 创建必要目录
#### 4. 配置系统
```bash
# 复制环境配置模板
cp .env.template .env
# 编辑环境配置
nano .env
```
**重要配置项**
```bash
# 数据库配置
POSTGRES_PASSWORD=设置强密码
APP_DB_PASSWORD=设置强密码
# 服务器配置
SERVER_NAME=内网IP或域名
NGINX_PORT=80
# 其他配置根据实际环境调整
```
**手动部署方式**(如果部署脚本遇到问题):
```bash
# 1. 导入镜像
./scripts/load-images.sh
# 2. 配置环境变量
cp .env.template .env
# 编辑 .env 文件,设置数据库密码、端口等配置
# 3. 启动服务
docker-compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env up -d
# 4. 查看服务状态
docker-compose -f docker-compose.yml -f docker-compose.prod.yml ps
```
#### 5. 启动系统
```bash
# 一键部署
./scripts/deploy.sh install
```
## 离线环境特殊配置
### 1. 时间同步
内网环境可能无法自动同步时间:
```bash
# 手动设置系统时间
sudo date -s "2024-01-01 12:00:00"
# 或配置内网 NTP 服务器
sudo nano /etc/systemd/timesyncd.conf
# 添加: NTP=内网NTP服务器IP
```
### 2. DNS 配置
```bash
# 配置内网 DNS
sudo nano /etc/resolv.conf
# 添加: nameserver 内网DNS服务器IP
# 或在 hosts 文件中添加必要的域名解析
sudo nano /etc/hosts
```
### 3. 防火墙配置
```bash
# 配置防火墙规则
sudo ufw allow from 内网网段 to any port 80
sudo ufw allow from 内网网段 to any port 443
sudo ufw enable
```
## 离线更新
### 1. 准备更新包
在有网络的环境中:
```bash
# 构建新版本镜像(会自动创建更新包)
./deploy/docker/prepare-offline-images.sh
```
### 2. 应用更新
在离线环境中:
```bash
# 备份当前配置
./docker/config-manager.sh backup
# 停止服务
./deploy.sh stop
# 解压更新包
tar -xzf qaup-offline-deploy-*.tar.gz
# 导入新镜像
./scripts/load-images.sh
# 启动服务
./deploy.sh start
```
## 离线监控
### 1. 本地监控
```bash
# 启动本地监控
./docker/monitor.sh watch 60
# 设置告警监控
./docker/alert-manager.sh monitor 300 &
```
### 2. 日志管理
```bash
# 配置日志轮转
./docker/log-manager.sh setup
# 定期清理日志
echo "0 2 * * * /opt/qaup/docker/log-manager.sh cleanup 30" | crontab -
```
## 离线备份
### 1. 数据备份
使用内置的备份脚本:
```bash
# 手动备份
./docker/backup-restore.sh backup
# 设置定时备份
echo "0 1 * * * /opt/qaup/docker/backup-restore.sh backup" | crontab -
```
### 2. 系统备份
```bash
# 完整系统备份
tar -czf qaup-system-backup-$(date +%Y%m%d).tar.gz \
--exclude='data/postgres' \
--exclude='logs' \
.
```
## 故障排除
### 1. 镜像导入失败
```bash
# 检查镜像文件
ls -la images/
# 验证镜像文件完整性
cd images && sha256sum -c checksums.sha256
# 手动导入单个镜像
gunzip -c qaup-app.tar.gz | docker load
```
### 2. 服务无法启动
```bash
# 检查 Docker 服务
sudo systemctl status docker
# 检查容器日志
docker logs qaup-app
docker logs qaup-postgres
# 检查网络连接
docker network ls
```
### 3. 配置问题
```bash
# 验证配置文件
./docker/config-manager.sh validate
# 重新初始化配置
./docker/config-manager.sh init
```
## 安全考虑
### 1. 网络隔离
```bash
# 确保容器网络隔离
docker network inspect qaup_qaup-network
# 配置防火墙规则
sudo ufw deny out 53
sudo ufw deny out 80
sudo ufw deny out 443
```
### 2. 访问控制
```bash
# 设置文件权限
chmod 600 .env
chmod 700 data/
chmod 755 logs/
# 限制用户访问
sudo chown -R qaup:qaup /opt/qaup
```
### 3. 审计日志
```bash
# 启用系统审计
sudo apt install auditd
sudo systemctl enable auditd
# 配置审计规则
echo "-w /opt/qaup -p wa -k qaup-access" | sudo tee -a /etc/audit/rules.d/qaup.rules
```
## 性能优化
### 1. 内网优化
```bash
# 调整网络参数
echo 'net.core.rmem_max = 16777216' | sudo tee -a /etc/sysctl.conf
echo 'net.core.wmem_max = 16777216' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
```
### 2. 存储优化
```bash
# 使用本地存储
# 修改 docker-compose.yml 中的卷配置
# 使用高性能磁盘存储数据库文件
```
## 维护计划
### 日常维护
使用内置的运维脚本:
```bash
# 系统状态检查
./docker/monitor.sh status
# 系统清理
./docker/ops-manager.sh cleanup
# 设置定时维护
echo "0 6 * * * /opt/qaup/docker/ops-manager.sh daily-maintenance" | crontab -
```
### 定期检查
- 每日:系统状态检查、日志查看
- 每周:性能监控、备份验证
- 每月:安全检查、配置审核
- 每季度:系统更新、容量规划
---
**注意**: 离线环境部署需要特别注意安全和稳定性,建议在部署前进行充分测试。

522
deploy/docs/部署指南.md Normal file
View File

@ -0,0 +1,522 @@
# QAUP 系统 Docker 部署指南
## 概述
本文档详细介绍了如何在 Ubuntu 系统上使用 Docker 部署 QAUP 管理系统。该部署方案支持生产环境使用,包含完整的监控、日志管理和备份功能。
## 系统要求
### 硬件要求
- **CPU**: 最少 2 核,推荐 4 核或以上
- **内存**: 最少 4GB推荐 8GB 或以上
- **磁盘**: 最少 20GB 可用空间,推荐 50GB 或以上
- **网络**: 稳定的网络连接(内网部署无需外网)
### 软件要求
- **操作系统**: Ubuntu 18.04 LTS 或更高版本
- **Docker**: 20.10 或更高版本
- **Docker Compose**: 1.29 或更高版本
- **其他工具**: curl, wget, unzip
- **Python环境**(可选,用于开发工具): Python 3.6+, pip
## 安装 Docker 和 Docker Compose
### 安装 Docker
```bash
# 更新包索引
sudo apt update
# 安装必要的包
sudo apt install apt-transport-https ca-certificates curl gnupg lsb-release
# 添加 Docker 官方 GPG 密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# 设置稳定版仓库
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 安装 Docker Engine
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io
# 将当前用户添加到 docker 组
sudo usermod -aG docker $USER
# 重新登录或运行以下命令
newgrp docker
```
### 安装 Docker Compose
```bash
# 下载 Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 添加执行权限
sudo chmod +x /usr/local/bin/docker-compose
# 验证安装
docker-compose --version
```
## 项目结构说明
QAUP 系统采用了优化的部署结构,所有部署相关文件都集中在 `deploy` 目录下:
```
QAUP-Management/
├── deploy/ # 部署目录(工作目录)
│ ├── deploy.sh # 主部署脚本
│ ├── docker-compose.yml # Docker Compose 配置
│ ├── docker-compose.prod.yml # 生产环境配置
│ ├── Dockerfile # 应用镜像构建文件
│ ├── .env.template # 环境变量模板
│ ├── docker/ # Docker 相关脚本和配置
│ │ ├── config-manager.sh # 配置管理脚本
│ │ ├── monitor.sh # 监控脚本
│ │ ├── nginx/ # Nginx 配置
│ │ └── postgres/ # PostgreSQL 配置
│ └── docs/ # 部署文档
├── qaup-admin/ # 后端主模块
├── qaup-ui/ # 前端模块
└── 其他项目文件...
```
**离线部署包结构(更新后):**
```
qaup-offline-deploy/
├── docker-compose.yml # Docker Compose 配置(根目录)
├── docker-compose.prod.yml # 生产环境配置(根目录)
├── .env.template # 环境变量模板(根目录)
├── deploy.sh # 部署脚本
├── images/ # Docker 镜像文件
├── scripts/ # 部署脚本
└── README-离线部署.md # 离线部署说明
```
**重要说明:**
- 所有部署操作都在 `deploy` 目录下进行
- Docker 构建上下文指向项目根目录,确保能正确访问所有源码文件
- 部署脚本会自动处理路径映射,无需手动调整
## 部署步骤
### 1. 获取部署包
#### 在线部署
```bash
# 克隆项目(如果有 Git 访问权限)
git clone <项目地址>
cd QAUP-Management/deploy
```
#### 离线部署
```bash
# 解压离线部署包
tar -xzf qaup-offline-deploy.tar.gz
cd QAUP-Management/deploy
# 导入 Docker 镜像
./docker/load-images.sh
```
### 2. 路径配置验证
```bash
# 运行路径配置测试(推荐)
./test-paths.sh
```
### 3. 环境验证
```bash
# 运行环境验证脚本
./docker/validate-environment.sh
```
如果验证失败,请根据提示修复问题。
### 4. 数据准备
根据部署场景选择合适的数据准备方式:
#### 场景1全新部署推荐新用户
```bash
# 使用默认模板数据
./docker/postgres/db-manager.sh prepare-default
```
#### 场景2从现有系统迁移
```bash
# 设置数据库连接信息
export DB_HOST=your_db_host
export DB_USER=postgres
export DB_NAME=qaup
# 从现有数据库导出并准备数据
./docker/postgres/db-manager.sh prepare-export
```
#### 场景3查看当前数据状态
```bash
# 查看当前数据文件状态
./docker/postgres/db-manager.sh prepare-status
```
### 6. 配置系统
```bash
# 初始化配置
./docker/config-manager.sh init
# 编辑环境配置文件
nano .env
```
**重要配置项说明:**
```bash
# 数据库配置
POSTGRES_DB=qaup
POSTGRES_USER=postgres
POSTGRES_PASSWORD=请设置强密码
APP_DB_USER=qaup_app
APP_DB_PASSWORD=请设置强密码
# Redis 配置
REDIS_PASSWORD=请设置密码或留空
# 应用配置
SPRING_PROFILES_ACTIVE=prod
APP_PORT=8080
# Nginx 配置
NGINX_PORT=80
SERVER_NAME=your-domain.com # 替换为实际域名或IP
API_PREFIX=/prod-api
# 目录配置
DATA_DIR=./data
LOG_DIR=./logs
BACKUP_DIR=./backup
```
### 4. 一键部署
```bash
# 运行一键部署脚本
./deploy.sh install
```
部署脚本将自动执行以下步骤:
1. 检查系统要求
2. 初始化配置
3. 验证环境
4. 构建 Docker 镜像
5. 启动服务
6. 等待服务就绪
7. 运行健康检查
### 5. 验证部署
部署完成后,访问以下地址验证:
- **前端界面**: http://your-server-ip
- **API 健康检查**: http://your-server-ip/health
- **应用健康检查**: http://your-server-ip/prod-api/actuator/health
## 服务管理
### 基本操作
```bash
# 查看服务状态
./deploy.sh status
# 启动服务
./deploy.sh start
# 停止服务
./deploy.sh stop
# 重启服务
./deploy.sh restart
# 查看日志
./deploy.sh logs all # 所有服务日志
./deploy.sh logs app # 应用日志
./deploy.sh logs nginx # Nginx 日志
```
### 高级管理
```bash
# 使用 Docker Compose 管理器
./docker/docker-compose-manager.sh prod start
./docker/docker-compose-manager.sh prod stop
./docker/docker-compose-manager.sh prod restart
./docker/docker-compose-manager.sh prod status
# 日志管理
./docker/log-manager.sh view app 100 # 查看应用最近100行日志
./docker/log-manager.sh monitor all # 实时监控所有日志
./docker/log-manager.sh analyze # 分析错误日志
./docker/log-manager.sh cleanup 30 # 清理30天前的日志
# 系统监控
./docker/monitor.sh status # 检查系统状态
./docker/monitor.sh watch 60 # 每60秒监控一次
./docker/monitor.sh resources # 查看资源使用
```
## 监控和告警
### 系统监控
```bash
# 实时监控
./docker/monitor.sh watch
# 生成监控报告
./docker/monitor.sh report
# 检查告警
./docker/alert-manager.sh check
# 持续告警监控
./docker/alert-manager.sh monitor 300 # 每5分钟检查一次
```
### 健康检查
系统提供多层次的健康检查:
1. **容器级健康检查**: Docker 内置健康检查
2. **服务级健康检查**: 应用程序健康端点
3. **系统级健康检查**: 资源使用监控
## 数据备份和恢复
### 数据库备份
```bash
# 手动备份
docker exec qaup-postgres /backup-db.sh
# 查看备份文件
ls -la ./backup/postgres/
# 恢复数据库
docker exec qaup-postgres /restore-db.sh /backup/qaup_backup_20241231_120000.sql.gz
```
### 配置备份
```bash
# 备份配置文件
./docker/config-manager.sh backup
# 查看备份
ls -la ./backup/config/
```
## 故障排除
### 常见问题
#### 1. 容器启动失败
```bash
# 查看容器状态
docker ps -a
# 查看容器日志
docker logs qaup-app
docker logs qaup-postgres
docker logs qaup-redis
docker logs qaup-nginx
# 检查资源使用
docker stats
```
#### 2. 数据库连接失败
```bash
# 检查数据库容器状态
docker exec qaup-postgres pg_isready -h localhost -p 5432 -U postgres
# 检查数据库日志
docker logs qaup-postgres
# 测试数据库连接
docker exec qaup-postgres psql -U postgres -d qaup -c "SELECT version();"
```
#### 3. 前端无法访问
```bash
# 检查 Nginx 状态
curl -I http://localhost/health
# 检查 Nginx 配置
docker exec qaup-nginx nginx -t
# 查看 Nginx 日志
docker logs qaup-nginx
```
#### 4. 应用响应慢
```bash
# 检查应用健康状态
curl http://localhost:8080/actuator/health
# 查看应用日志
docker logs qaup-app | grep -i "error\|exception"
# 检查资源使用
docker stats qaup-app
```
### 日志分析
```bash
# 分析错误日志
./docker/log-manager.sh analyze
# 查看特定时间段的日志
docker logs qaup-app --since "2024-01-01T00:00:00" --until "2024-01-01T23:59:59"
# 搜索特定错误
docker logs qaup-app | grep -i "OutOfMemoryError"
```
## 性能优化
### JVM 调优
编辑 `.env` 文件中的 JVM 参数:
```bash
# 生产环境 JVM 配置
JVM_OPTS=-Dname=qaup-admin.jar -Duser.timezone=Asia/Shanghai -server -Xms2g -Xmx4g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseG1GC -XX:G1HeapRegionSize=16m
```
### 数据库优化
编辑 `docker/postgres/postgresql.conf`
```bash
# 根据服务器配置调整
shared_buffers = 512MB # 25% of RAM
effective_cache_size = 2GB # 75% of RAM
work_mem = 8MB
maintenance_work_mem = 128MB
```
### Nginx 优化
编辑 `docker/nginx/nginx.conf`
```bash
worker_processes auto;
worker_connections 2048;
keepalive_timeout 65;
client_max_body_size 50M;
```
## 安全配置
### 防火墙设置
```bash
# 只开放必要端口
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
```
### SSL/TLS 配置
1. 获取 SSL 证书Let's Encrypt 或商业证书)
2. 将证书文件放置在 `./ssl/` 目录
3. 更新 Nginx 配置启用 HTTPS
### 密码安全
```bash
# 生成强密码
./docker/secrets-manager.sh generate
# 检查密码强度
./docker/secrets-manager.sh check
```
## 更新和维护
### 系统更新
```bash
# 备份当前部署
./docker/config-manager.sh backup
# 更新部署
./deploy.sh update
# 验证更新
./deploy.sh status
```
### 定期维护
建议定期执行以下维护任务:
```bash
# 每日
./docker/alert-manager.sh check
./docker/monitor.sh status
# 每周
./docker/log-manager.sh cleanup 30
./docker/log-manager.sh compress
# 每月
docker system prune -f
./docker/config-manager.sh backup
```
## 可选工具安装
### Python 开发工具(可选)
如果需要使用项目中的Python开发工具如mock server可以安装Python环境
```bash
# 安装Python和pip
sudo apt update
sudo apt install python3 python3-pip
# 安装Flask用于mock server
pip3 install flask
# 运行mock server开发测试用
cd tools
python3 mock_server.py
```
## 联系支持
如果遇到无法解决的问题,请提供以下信息:
1. 系统信息:`uname -a`
2. Docker 版本:`docker --version`
3. 容器状态:`docker ps -a`
4. 错误日志:相关服务的日志文件
5. 配置文件:`.env` 文件(隐藏敏感信息)
---
**注意**: 本文档会根据系统更新持续维护,请定期查看最新版本。

30
deploy/fix-paths.sh Executable file
View File

@ -0,0 +1,30 @@
#!/bin/sh
# 修复所有脚本中的路径问题
echo "修复脚本路径问题..."
# 修复docker目录下的脚本除了postgres子目录和prepare-offline-images.sh
for script in docker/*.sh; do
if [ -f "$script" ] && [ "$script" != "docker/prepare-offline-images.sh" ]; then
echo "修复: $script"
# 先检查是否还有BASH_SOURCE
if grep -q 'BASH_SOURCE\[0\]' "$script"; then
sed -i.bak 's|\${BASH_SOURCE\[0\]}|$0|g' "$script"
fi
# 修复PROJECT_ROOT路径
sed -i.bak 's|PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"|PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"|g' "$script"
fi
done
# 修复test-paths.sh
if [ -f "test-paths.sh" ]; then
echo "修复: test-paths.sh"
if grep -q 'BASH_SOURCE\[0\]' "test-paths.sh"; then
sed -i.bak 's|\${BASH_SOURCE\[0\]}|$0|g' "test-paths.sh"
fi
sed -i.bak 's|PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"|PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"|g' "test-paths.sh"
fi
echo "路径修复完成!"
echo "备份文件已保存为 .bak 后缀"

View File

@ -1,381 +0,0 @@
#!/bin/bash
# QAUP 服务器端打包脚本
# 在Ubuntu打包服务器上运行使用已构建的jar文件进行打包
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
print_message() {
echo -e "${1}${2}${NC}"
}
print_message $BLUE "=== QAUP 服务器端打包脚本 ==="
# 检查基础环境
print_message $BLUE "1. 检查基础环境..."
# 检查操作系统
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
print_message $YELLOW "⚠️ 警告: 当前操作系统为 $OSTYPE建议在Linux上运行"
fi
# 检查Java 21
if ! command -v java &> /dev/null; then
print_message $RED "❌ Java 未安装"
print_message $BLUE "请安装 Java 21: sudo apt install openjdk-21-jdk"
exit 1
fi
JAVA_VERSION=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
REQUIRED_VERSION="21"
if [[ "$(printf '%s\n' "$REQUIRED_VERSION" "$JAVA_VERSION" | sort -V | head -n1)" != "$REQUIRED_VERSION" ]]; then
print_message $RED "❌ Java 版本不兼容"
print_message $BLUE "当前版本: $JAVA_VERSION, 需要: $REQUIRED_VERSION+"
exit 1
fi
print_message $GREEN "✓ Java 版本检查通过: $JAVA_VERSION"
# 检查Maven
if ! command -v mvn &> /dev/null; then
print_message $RED "❌ Maven 未安装"
print_message $BLUE "请安装 Maven: sudo apt install maven"
exit 1
fi
print_message $GREEN "✓ Maven 版本: $(mvn -version | head -1)"
# 检查Docker和Docker Compose
if ! command -v docker &> /dev/null; then
print_message $RED "❌ Docker 未安装"
exit 1
fi
DOCKER_VERSION=$(docker --version)
print_message $GREEN "✓ Docker 版本: $DOCKER_VERSION"
if ! docker compose version &> /dev/null && ! docker-compose version &> /dev/null; then
print_message $RED "❌ Docker Compose 未安装"
print_message $BLUE "请安装 Docker Compose: sudo apt install docker-compose"
exit 1
fi
print_message $GREEN "✓ Docker Compose 检查通过"
# 检查磁盘空间至少需要5GB
AVAILABLE_SPACE=$(df . | tail -1 | awk '{print $4}')
REQUIRED_SPACE=$((5 * 1024 * 1024)) # 5GB in KB
if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_SPACE" ]; then
print_message $RED "❌ 磁盘空间不足"
print_message $BLUE "可用空间: $(($AVAILABLE_SPACE / 1024 / 1024))GB, 需要: 5GB"
exit 1
fi
print_message $GREEN "✓ 磁盘空间充足: $(($AVAILABLE_SPACE / 1024 / 1024))GB"
# 检查jar文件
print_message $BLUE "2. 检查jar文件..."
JAR_FILE="qaup-admin/target/qaup-admin.jar"
if [ ! -f "$JAR_FILE" ]; then
print_message $YELLOW "未找到jar文件尝试构建..."
# 尝试构建项目
if [ -f "pom.xml" ]; then
print_message $BLUE "执行 Maven 构建..."
mvn clean package -DskipTests
if [ ! -f "$JAR_FILE" ]; then
print_message $RED "❌ 构建失败: $JAR_FILE 仍然不存在"
exit 1
fi
else
print_message $RED "❌ 未找到jar文件: $JAR_FILE"
print_message $BLUE "请先在macOS上构建jar文件并上传到服务器"
exit 1
fi
fi
print_message $GREEN "✓ 找到jar文件: $JAR_FILE"
print_message $BLUE " 文件大小: $(du -sh $JAR_FILE | cut -f1)"
# 验证jar文件是否为有效的Java应用
if ! jar -tf "$JAR_FILE" > /dev/null 2>&1; then
print_message $RED "❌ jar文件损坏或不是有效的JAR文件"
exit 1
fi
print_message $GREEN "✓ jar文件验证通过"
# 准备镜像版本
print_message $BLUE "3. 准备Docker镜像..."
POSTGRES_IMAGE="m.daocloud.io/docker.io/postgis/postgis:17-3.5-alpine"
REDIS_IMAGE="m.daocloud.io/docker.io/library/redis:8.0-alpine"
OPENJDK_IMAGE="m.daocloud.io/docker.io/library/eclipse-temurin:21-jre"
# 检查网络连接
if ! ping -c 1 m.daocloud.io &> /dev/null; then
print_message $YELLOW "⚠️ 网络连接测试失败,可能影响镜像拉取"
fi
print_message $BLUE "拉取基础镜像..."
print_message $BLUE " - PostgreSQL + PostGIS"
if ! docker pull --platform linux/amd64 $POSTGRES_IMAGE; then
print_message $RED "❌ PostgreSQL镜像拉取失败"
exit 1
fi
print_message $BLUE " - Redis"
if ! docker pull --platform linux/amd64 $REDIS_IMAGE; then
print_message $RED "❌ Redis镜像拉取失败"
exit 1
fi
print_message $BLUE " - Java 21 Runtime"
if ! docker pull --platform linux/amd64 $OPENJDK_IMAGE; then
print_message $RED "❌ Java镜像拉取失败"
exit 1
fi
# 导出镜像
print_message $BLUE "4. 导出Docker镜像..."
mkdir -p qaup-deploy
# 检查磁盘空间(镜像包需要额外空间)
EXPECTED_IMAGE_SIZE=$((2 * 1024)) # 预计2GB
if [ "$AVAILABLE_SPACE" -lt "$((EXPECTED_IMAGE_SIZE * 1024))" ]; then
print_message $YELLOW "⚠️ 磁盘空间可能不足预计需要额外2GB空间"
fi
if ! docker save $POSTGRES_IMAGE $REDIS_IMAGE $OPENJDK_IMAGE | gzip > qaup-deploy/images.tar.gz; then
print_message $RED "❌ 镜像导出失败"
exit 1
fi
IMAGE_SIZE=$(du -sh qaup-deploy/images.tar.gz | cut -f1)
print_message $GREEN "✓ 镜像包大小: $IMAGE_SIZE"
# 复制必要文件
print_message $BLUE "5. 准备部署文件..."
# 复制核心应用文件
cp "$JAR_FILE" qaup-deploy/app.jar
if [ $? -eq 0 ]; then
print_message $GREEN "✓ 应用JAR文件已复制"
else
print_message $RED "❌ 应用JAR文件复制失败"
exit 1
fi
# 复制Docker配置
cp deploy/docker-compose.yml qaup-deploy/ || {
print_message $RED "❌ docker-compose.yml 复制失败"
exit 1
}
print_message $GREEN "✓ Docker编排配置已复制"
# 复制应用配置
cp deploy/config.yml qaup-deploy/ || {
print_message $RED "❌ config.yml 复制失败"
exit 1
}
print_message $GREEN "✓ 应用配置文件已复制"
# 复制部署脚本
cp deploy/deploy-all.sh qaup-deploy/ || {
print_message $RED "❌ deploy-all.sh 复制失败"
exit 1
}
chmod +x qaup-deploy/deploy-all.sh
cp deploy/deploy-update.sh qaup-deploy/ || {
print_message $RED "❌ deploy-update.sh 复制失败"
exit 1
}
chmod +x qaup-deploy/deploy-update.sh
cp deploy/qaup-service.sh qaup-deploy/ || {
print_message $RED "❌ qaup-service.sh 复制失败"
exit 1
}
chmod +x qaup-deploy/qaup-service.sh
print_message $GREEN "✓ 部署脚本已复制并设置执行权限"
# 复制数据库相关文件(可选)
if [ -f "deploy/qaup_database_export.sql" ]; then
cp deploy/qaup_database_export.sql qaup-deploy/qaup_database_export.sql
print_message $GREEN "✓ 数据库导出文件已复制"
else
print_message $YELLOW "⚠️ 数据库导出文件不存在"
fi
# 复制文档文件
if [ -f "deploy/DeployGuide.md" ]; then
cp deploy/DeployGuide.md qaup-deploy/
print_message $GREEN "✓ 部署指南已复制"
else
print_message $YELLOW "⚠️ 部署指南不存在"
fi
# 创建必需目录
mkdir -p qaup-deploy/{backup,logs,data/postgres,data/redis}
print_message $GREEN "✓ 目录结构已创建"
# 创建README文件
cat > qaup-deploy/README.md << 'EOF'
# QAUP 部署包
## 目录结构
```
qaup-deploy/
├── app.jar # 应用JAR文件
├── docker-compose.yml # Docker编排配置
├── config.yml # 应用配置文件
├── deploy-all.sh # 一键部署脚本
├── deploy-update.sh # 一键升级脚本
├── qaup-service.sh # 统一服务管理脚本
├── images.tar.gz # Docker镜像包
├── README.md # 本文件
├── backup/ # 备份目录
├── logs/ # 日志目录
└── data/ # 数据目录
├── postgres/ # PostgreSQL数据
└── redis/ # Redis数据
```
## 快速部署
```bash
# 解压和部署
tar -xzf qaup-deploy.tar.gz
cd qaup-deploy
./deploy-all.sh
# 检查状态
docker compose ps
curl http://localhost:8080/actuator/health
```
## 统一服务管理
```bash
# 使用统一服务管理脚本
./qaup-service.sh help # 查看帮助
./qaup-service.sh start # 启动所有服务
./qaup-service.sh status # 查看服务状态
./qaup-service.sh logs qaup-app # 查看应用日志
./qaup-service.sh health # 健康检查
./qaup-service.sh backup # 数据备份
```
## 应用升级
```bash
# 方法1: 使用统一管理脚本(推荐)
./qaup-service.sh update # 将新版本文件重命名为new-app.jar后使用
# 方法2: 使用专用升级脚本
cp /path/to/new/qaup-admin.jar ./new-app.jar
./deploy-update.sh
```
## 管理命令
```bash
# 查看状态
./qaup-service.sh status
# 或
docker compose ps
# 查看日志
./qaup-service.sh logs qaup-app
# 或
docker compose logs -f qaup-app
# 停止服务
./qaup-service.sh stop
# 或
docker compose down
# 重启应用
./qaup-service.sh restart
# 或
docker compose restart qaup-app
```
## 默认信息
- **Web访问**: http://localhost:8080
- **数据库**: localhost:5432 (qaup/qaup123)
- **Redis**: localhost:6379
- **初始账号**: admin/admin123
## 支持
如遇问题,请查看日志:
```bash
docker compose logs qaup-app
```
EOF
print_message $GREEN "✓ README文件已创建"
# 验证部署包完整性
print_message $BLUE "6. 验证部署包完整性..."
REQUIRED_FILES=("app.jar" "docker-compose.yml" "config.yml" "deploy-all.sh" "deploy-update.sh" "qaup-service.sh" "images.tar.gz")
for file in "${REQUIRED_FILES[@]}"; do
if [ ! -f "qaup-deploy/$file" ]; then
print_message $RED "❌ 缺失必要文件: $file"
exit 1
fi
done
print_message $GREEN "✓ 所有必要文件验证通过"
# 创建部署包
print_message $BLUE "7. 创建部署包..."
PACKAGE_NAME="qaup-deploy-$(date +%Y%m%d-%H%M%S).tar.gz"
if tar -czf "$PACKAGE_NAME" -C qaup-deploy .; then
print_message $GREEN "✅ 打包完成: $PACKAGE_NAME"
else
print_message $RED "❌ 打包失败"
exit 1
fi
PACKAGE_SIZE=$(du -sh "$PACKAGE_NAME" | cut -f1)
DEPLOY_DIR_SIZE=$(du -sh qaup-deploy | cut -f1)
print_message $GREEN "✓ 部署包大小: $PACKAGE_SIZE"
print_message $BLUE " 临时目录大小: $DEPLOY_DIR_SIZE"
# 清理临时文件
print_message $BLUE "8. 清理临时文件..."
rm -rf qaup-deploy
print_message $GREEN "✓ 临时目录已清理"
print_message $GREEN "🎉 打包成功完成!"
echo ""
print_message $BLUE "📋 部署说明(生产环境):"
echo ""
echo "⚠️ 由于生产环境安全要求,必须手工文件上传"
echo ""
echo "1. 按安全策略传输部署包到目标服务器:"
echo " scp $PACKAGE_NAME user@生产服务器IP:/opt/qaup/"
echo " 或使用SFTP、文件传输工具等"
echo ""
echo "2. 在生产服务器执行部署:"
echo " ssh user@生产服务器IP"
echo " cd /opt/qaup"
echo " mkdir qaup-deploy && tar -xzf $PACKAGE_NAME -C qaup-deploy"
echo " cd qaup-deploy"
echo " chmod +x *.sh"
echo " ./deploy-all.sh"
echo ""
echo "3. 详细部署说明请查看:"
echo " - 部署指南: DeployGuide.md"
echo ""
print_message $BLUE "📊 打包统计:"
echo " - 部署包: $PACKAGE_NAME ($PACKAGE_SIZE)"
echo " - Java版本: $JAVA_VERSION"
echo " - 打包时间: $(date)"
echo " - 服务器: $(hostname)"
echo ""
print_message $BLUE "📖 文档信息:"
echo " - 部署指南已包含在部署包中: DeployGuide.md"
echo " - 包含完整的生产环境部署和更新说明"
echo ""
print_message $GREEN "🚀 可以开始部署了!"

View File

@ -1,157 +0,0 @@
#!/bin/bash
# QAUP 程序更新打包脚本
# 用于生成仅包含jar文件的更新包
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
echo -e "${1}${2}${NC}"
}
print_message $BLUE "=== QAUP 程序更新打包 ==="
# 1. 构建应用
print_message $BLUE "构建应用..."
cd "$PROJECT_ROOT"
mvn clean package -DskipTests -q
# 检查jar文件是否生成成功
JAR_FILE="$PROJECT_ROOT/qaup-admin/target/qaup-admin.jar"
if [ ! -f "$JAR_FILE" ]; then
print_message $RED "❌ jar文件构建失败"
exit 1
fi
# 2. 创建更新包目录
UPDATE_DIR="qaup-update-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$UPDATE_DIR"
# 3. 复制jar文件
print_message $BLUE "准备更新文件..."
cp "$JAR_FILE" "$UPDATE_DIR/qaup-admin.jar"
# 4. 创建更新说明
cat > "$UPDATE_DIR/UPDATE-INSTRUCTIONS.md" << 'EOF'
# QAUP 程序更新包
## 包含内容
- `qaup-admin.jar` - 新版本应用程序
## ⚠️ 生产环境安全要求
由于生产环境安全限制,必须手工文件上传,禁止使用自动化脚本传输。
## 更新步骤
### 1. 文件传输(手工方式)
将更新包 `qaup-admin.jar` 按安全策略传输到生产服务器:
```bash
# 方案A使用SCP
scp qaup-admin.jar user@生产服务器IP:/opt/qaup/qaup-deploy/
# 方案B使用SFTP
# 通过SFTP工具上传文件到 /opt/qaup/qaup-deploy/ 目录
```
### 2. 重命名文件
```bash
ssh user@生产服务器IP
cd /opt/qaup/qaup-deploy
cp qaup-admin.jar new-app.jar
```
### 3. 执行更新
```bash
./deploy-update.sh
```
### 4. 验证更新
```bash
# 检查服务状态
docker compose ps
# 检查应用日志
docker compose logs -f qaup-app
# 访问系统确认功能正常
curl http://localhost:8080/actuator/health
```
## 安全注意事项
- 更新前建议备份数据库:`docker exec qaup-postgres pg_dump -U qaup qaup > backup-$(date +%Y%m%d).sql`
- 如果更新失败,脚本会自动回滚
- 如有更多问题请查看完整的部署指南DeployGuide.md
- 仅授权人员可访问生产服务器
EOF
# 5. 创建版本信息
cat > "$UPDATE_DIR/VERSION-INFO.txt" << EOF
QAUP 程序更新包
构建时间: $(date)
构建主机: $(hostname)
Git提交: $(git rev-parse --short HEAD 2>/dev/null || echo "未知")
Maven版本: $(mvn --version | head -1)
Java版本: $(java -version 2>&1 | head -1)
文件大小: $(du -sh "$JAR_FILE" | cut -f1)
文件MD5: $(md5sum "$JAR_FILE" | cut -d' ' -f1)
更新包内容:
- qaup-admin.jar (主程序)
- UPDATE-INSTRUCTIONS.md (更新说明)
- VERSION-INFO.txt (版本信息)
适用场景:
- 生产环境程序热更新
- 支持自动回滚机制
- 适合已部署环境的增量更新
EOF
# 6. 验证更新包完整性
print_message $BLUE "验证更新包完整性..."
REQUIRED_FILES=("qaup-admin.jar" "UPDATE-INSTRUCTIONS.md" "VERSION-INFO.txt")
for file in "${REQUIRED_FILES[@]}"; do
if [ ! -f "$UPDATE_DIR/$file" ]; then
print_message $RED "❌ 缺失必要文件: $file"
rm -rf "$UPDATE_DIR"
exit 1
fi
done
print_message $GREEN "✓ 所有必要文件验证通过"
# 7. 创建更新包
PACKAGE_NAME="${UPDATE_DIR}.tar.gz"
tar -czf "$PACKAGE_NAME" "$UPDATE_DIR"
# 8. 清理临时目录
rm -rf "$UPDATE_DIR"
print_message $GREEN "✅ 程序更新包创建完成: $PACKAGE_NAME"
print_message $BLUE "包大小: $(du -sh "$PACKAGE_NAME" | cut -f1)"
echo ""
print_message $BLUE "📋 更新说明(生产环境):"
echo ""
echo "⚠️ 由于生产环境安全要求,必须手工文件传输"
echo ""
echo "1. 按安全策略传输更新包到目标服务器:"
echo " scp $PACKAGE_NAME user@生产服务器IP:/opt/qaup/"
echo " 或使用SFTP、文件传输工具等"
echo ""
echo "2. 在生产服务器执行更新:"
echo " ssh user@生产服务器IP"
echo " cd /opt/qaup/qaup-deploy"
echo " tar -xzf ../$PACKAGE_NAME"
echo " cp qaup-admin.jar new-app.jar"
echo " ./deploy-update.sh"
echo ""
echo "3. 详细更新说明请查看:"
echo " - 更新说明: UPDATE-INSTRUCTIONS.md"

View File

@ -1,255 +0,0 @@
# Python依赖库离线部署指南
本文档提供了 QAUP-Management 项目中 tools 目录下 Python 脚本的离线部署指南。
## 📋 概述
通过对 tools 目录下 4 个 Python 文件的依赖分析,除了 Flask 之外,还需要安装以下外部库用于完整功能支持。
## 📂 分析的文件
- `tools/mock_airport.py` - 机场管理系统模拟服务
- `tools/mock_traffic_light.py` - 红绿灯设备模拟器
- `tools/mock_unmanned_vehicle.py` - 无人车厂商平台模拟服务
- `tools/aircraft_routes_from_api.py` - 航空器路由数据定义
## 📦 依赖库清单
### 🚀 必需的外部库
| 库名 | 用途 | 使用文件 | 关键功能 |
|-----|------|---------|----------|
| **Flask** | Web服务框架 | 所有mock_*.py文件 | API服务、HTTP路由 |
| **pyproj** | 坐标系转换 | mock_airport.py | CGCS2000↔WGS84精确转换 |
| **Shapely** | 几何操作 | mock_airport.py | 路径合并(linemerge)、几何计算 |
### 📚 内置库(无需安装)
以下库为 Python 标准库,无需额外安装:
```
time, math, logging, os, threading, atexit, socket, json,
argparse, datetime, signal, typing, collections.abc, copy
```
### 🔄 回退机制
- **pyproj**: 不可用时自动回退到简化坐标转换算法
- **Shapely**: 不可用时跳过路径合并,基本功能仍可用
## 🛠️ 离线部署步骤
### 步骤1在联网机器上下载依赖包
```bash
# 创建依赖包目录
mkdir offline_packages
cd offline_packages
# 下载所有依赖包(包含依赖的依赖)
pip download Flask pyproj Shapely
# 或者如果有requirements.txt
pip download -r ../requirements.txt
```
### 步骤2打包传输
```bash
# 打包下载的文件
tar -czf python_packages.tar.gz offline_packages/
# 传输到离线机器
scp python_packages.tar.gz user@offline-server:/path/to/destination/
```
### 步骤3在离线机器上安装
```bash
# 解压依赖包
tar -xzf python_packages.tar.gz
# 离线安装
pip install --find-links ./offline_packages --no-index Flask pyproj Shapely
# 或者如果有requirements.txt
pip install --find-links ./offline_packages --no-index -r requirements.txt
```
## ✅ 安装验证
### 验证脚本
创建 `test_dependencies.py` 文件:
```python
#!/usr/bin/env python3
"""
依赖库安装验证脚本
"""
import sys
def test_imports():
"""测试所有必需库的导入"""
success = True
# 测试基础库
try:
import flask
print("✅ Flask: OK")
except ImportError as e:
print(f"❌ Flask: FAILED - {e}")
success = False
# 测试坐标转换库
try:
import pyproj
print("✅ pyproj: OK")
except ImportError as e:
print(f"⚠️ pyproj: MISSING - 将使用简化坐标转换算法")
# 测试几何库
try:
import shapely
print("✅ Shapely: OK")
except ImportError as e:
print(f"⚠️ Shapely: MISSING - 路径合并功能将被跳过")
# 测试标准库
standard_libs = [
'time', 'math', 'logging', 'os', 'threading', 'atexit',
'socket', 'json', 'argparse', 'datetime', 'signal', 'typing'
]
for lib in standard_libs:
try:
__import__(lib)
except ImportError as e:
print(f"❌ {lib}: FAILED - {e}")
success = False
print("✅ 所有标准库: OK")
return success
def test_functionality():
"""测试关键功能"""
print("\n🧪 测试关键功能...")
# 测试坐标转换
try:
from pyproj import CRS, Transformer
wgs84_crs = CRS.from_epsg(4326)
print("✅ 坐标系转换功能: OK")
except ImportError:
print("⚠️ 坐标系转换功能: 使用简化算法")
except Exception as e:
print(f"❌ 坐标系转换功能: ERROR - {e}")
# 测试几何操作
try:
from shapely.geometry import LineString
from shapely.ops import linemerge
lines = [LineString([(0,0), (1,1)]), LineString([(1,1), (2,2)])]
merged = linemerge(lines)
print("✅ 几何操作功能: OK")
except ImportError:
print("⚠️ 几何操作功能: 路径合并将被跳过")
except Exception as e:
print(f"❌ 几何操作功能: ERROR - {e}")
if __name__ == "__main__":
print("🔍 验证Python依赖库安装...")
print("=" * 50)
if test_imports():
print("\n✅ 核心依赖库验证通过")
test_functionality()
print("\n🎉 系统准备就绪可以运行tools目录下的Python脚本")
sys.exit(0)
else:
print("\n❌ 依赖库验证失败,请检查安装")
sys.exit(1)
```
运行验证:
```bash
python test_dependencies.py
```
## 📄 requirements.txt
建议创建 `requirements.txt` 文件:
```txt
# QAUP-Management Tools Dependencies
Flask>=2.0.0
pyproj>=3.0.0
Shapely>=1.8.0
```
## 🔧 故障排除
### 常见问题
1. **pyproj 安装失败**
```bash
# 可能需要系统级依赖
# Ubuntu/Debian
sudo apt-get install libproj-dev proj-data proj-bin
# CentOS/RHEL
sudo yum install proj-devel
```
2. **Shapely 安装失败**
```bash
# 可能需要 GEOS 库
# Ubuntu/Debian
sudo apt-get install libgeos-dev
# CentOS/RHEL
sudo yum install geos-devel
```
3. **权限问题**
```bash
# 使用用户安装
pip install --user --find-links ./offline_packages --no-index Flask pyproj Shapely
```
### 验证服务启动
```bash
# 测试启动各个模拟服务
cd tools
# 机场管理服务 (端口8090)
python3 mock_airport.py
# 红绿灯模拟器 (端口8082)
python3 mock_traffic_light.py
# 无人车平台服务 (端口8091)
python3 mock_unmanned_vehicle.py
```
## 📝 注意事项
1. **Python版本**: 建议使用Python 3.7+
2. **网络隔离**: 确保 `--no-index` 参数防止联网下载
3. **依赖版本**: 固定版本号避免兼容性问题
4. **回退机制**: pyproj和Shapely不可用时会自动降级基本功能仍可用
5. **日志目录**: 确保运行目录有写权限创建logs目录
## 🆘 技术支持
如果遇到安装问题:
1. 检查Python版本: `python --version`
2. 检查pip版本: `pip --version`
3. 运行验证脚本: `python test_dependencies.py`
4. 查看错误日志: `logs/mock_server.log`
---
*文档生成时间: 2024年*
*适用于: QAUP-Management 项目 tools 目录*

View File

@ -1,441 +0,0 @@
#!/bin/bash
# QAUP 统一服务管理脚本
# 支持管理QAUP核心服务、数据库、缓存和ADXP适配器
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
NC='\033[0m'
# 配置变量
COMPOSE_FILE="docker-compose.yml"
ADXP_COMPOSE_FILE="docker-compose.adxp.yml"
APP_SERVICE="qaup-app"
DB_SERVICE="qaup-postgres"
REDIS_SERVICE="qaup-redis"
ADXP_SERVICE="adxp-adapter"
print_message() {
echo -e "${1}${2}${NC}"
}
print_header() {
echo ""
print_message $BLUE "=== QAUP 统一服务管理 ==="
print_message $BLUE "当前目录: $(pwd)"
print_message $BLUE "时间: $(date)"
echo ""
}
# 检查必要文件
check_files() {
if [ ! -f "$COMPOSE_FILE" ]; then
print_message $RED "❌ 找不到 $COMPOSE_FILE 文件"
exit 1
fi
if [ ! -f "config.yml" ]; then
print_message $RED "❌ 找不到 config.yml 文件"
exit 1
fi
}
# 启动所有服务
start_all() {
print_message $BLUE "启动所有QAUP服务..."
# 启动基础设施服务
print_message $BLUE "1. 启动数据库服务..."
docker compose up -d $DB_SERVICE
print_message $BLUE "2. 启动缓存服务..."
docker compose up -d $REDIS_SERVICE
# 等待基础设施就绪
print_message $BLUE "等待基础设施服务就绪..."
sleep 30
# 检查数据库状态
if ! docker exec $DB_SERVICE pg_isready -U qaup > /dev/null 2>&1; then
print_message $RED "❌ 数据库启动失败"
return 1
fi
# 检查Redis状态
if ! docker exec $REDIS_SERVICE redis-cli ping > /dev/null 2>&1; then
print_message $RED "❌ Redis启动失败"
return 1
fi
print_message $GREEN "✓ 基础设施服务就绪"
# 启动应用服务
print_message $BLUE "3. 启动应用服务..."
docker compose up -d $APP_SERVICE
# 启动ADXP适配器如果配置文件存在
if [ -f "$ADXP_COMPOSE_FILE" ]; then
print_message $BLUE "4. 启动ADXP适配器..."
docker compose -f $COMPOSE_FILE -f $ADXP_COMPOSE_FILE up -d $ADXP_SERVICE
fi
print_message $GREEN "🎉 所有服务启动完成!"
show_status
}
# 停止所有服务
stop_all() {
print_message $BLUE "停止所有QAUP服务..."
# 停止ADXP适配器
if [ -f "$ADXP_COMPOSE_FILE" ]; then
docker compose -f $COMPOSE_FILE -f $ADXP_COMPOSE_FILE down $ADXP_SERVICE 2>/dev/null || true
fi
# 停止应用服务
docker compose stop $APP_SERVICE 2>/dev/null || true
# 停止缓存服务
docker compose stop $REDIS_SERVICE 2>/dev/null || true
# 停止数据库服务
docker compose stop $DB_SERVICE 2>/dev/null || true
print_message $GREEN "✓ 所有服务已停止"
}
# 重启所有服务
restart_all() {
print_message $BLUE "重启所有QAUP服务..."
stop_all
sleep 5
start_all
}
# 查看服务状态
show_status() {
print_message $BLUE "服务状态:"
echo ""
# 显示服务状态表格
printf "%-20s %-15s %-15s %-10s\n" "服务名" "状态" "端口" "健康检查"
printf "%-20s %-15s %-15s %-10s\n" "--------------------" "---------------" "---------------" "----------"
# 检查各个服务状态
services=(
"$APP_SERVICE:8080:qaup-app"
"$DB_SERVICE:5432:qaup-postgres"
"$REDIS_SERVICE:6379:qaup-redis"
)
for service_info in "${services[@]}"; do
service=$(echo $service_info | cut -d: -f1)
port=$(echo $service_info | cut -d: -f2)
container=$(echo $service_info | cut -d: -f3)
status=$(docker compose ps $service --format json 2>/dev/null | jq -r '.[0].State' 2>/dev/null || echo "unknown")
# 健康检查
if [ "$status" = "running" ]; then
if [ "$service" = "$APP_SERVICE" ]; then
health=$(curl -f -s http://localhost:8080/actuator/health > /dev/null 2>&1 && echo "正常" || echo "异常")
elif [ "$service" = "$DB_SERVICE" ]; then
health=$(docker exec $DB_SERVICE pg_isready -U qaup > /dev/null 2>&1 && echo "正常" || echo "异常")
elif [ "$service" = "$REDIS_SERVICE" ]; then
health=$(docker exec $REDIS_SERVICE redis-cli ping > /dev/null 2>&1 && echo "正常" || echo "异常")
fi
else
health="未运行"
fi
printf "%-20s %-15s %-15s %-10s\n" "$service" "$status" "$port" "$health"
done
# 检查ADXP适配器状态
if [ -f "$ADXP_COMPOSE_FILE" ]; then
adxp_status=$(docker compose -f $COMPOSE_FILE -f $ADXP_COMPOSE_FILE ps $ADXP_SERVICE --format json 2>/dev/null | jq -r '.[0].State' 2>/dev/null || echo "unknown")
adxp_health=$(curl -f -s http://localhost:8086/health > /dev/null 2>&1 && echo "正常" || echo "异常")
printf "%-20s %-15s %-15s %-10s\n" "$ADXP_SERVICE" "$adxp_status" "8086" "$adxp_health"
fi
echo ""
}
# 查看日志
show_logs() {
local service=${1:-""}
local lines=${2:-50}
if [ -n "$service" ]; then
print_message $BLUE "查看 $service 服务日志 (最后 $lines 行):"
docker compose logs --tail=$lines -f $service
else
print_message $BLUE "查看所有服务日志 (最后 $lines 行):"
docker compose logs --tail=$lines -f
fi
}
# 健康检查
health_check() {
print_message $BLUE "执行健康检查..."
local errors=0
# 检查Docker服务
if ! docker info > /dev/null 2>&1; then
print_message $RED "❌ Docker服务不可用"
((errors++))
else
print_message $GREEN "✓ Docker服务正常"
fi
# 检查应用健康状态
if curl -f -s http://localhost:8080/actuator/health > /dev/null 2>&1; then
print_message $GREEN "✓ 应用服务健康检查通过"
else
print_message $RED "❌ 应用服务健康检查失败"
((errors++))
fi
# 检查数据库连接
if docker exec $DB_SERVICE pg_isready -U qaup > /dev/null 2>&1; then
print_message $GREEN "✓ 数据库连接正常"
else
print_message $RED "❌ 数据库连接失败"
((errors++))
fi
# 检查Redis连接
if docker exec $REDIS_SERVICE redis-cli ping > /dev/null 2>&1; then
print_message $GREEN "✓ Redis连接正常"
else
print_message $RED "❌ Redis连接失败"
((errors++))
fi
# 检查磁盘空间
available_space=$(df . | tail -1 | awk '{print $4}')
if [ "$available_space" -gt $((1024*1024)) ]; then # 1GB
print_message $GREEN "✓ 磁盘空间充足"
else
print_message $RED "❌ 磁盘空间不足"
((errors++))
fi
# 检查端口占用
ports=(8080 5432 6379 8086)
for port in "${ports[@]}"; do
if netstat -tuln 2>/dev/null | grep -q ":$port " || ss -tuln 2>/dev/null | grep -q ":$port "; then
print_message $GREEN "✓ 端口 $port 占用正常"
else
print_message $YELLOW "⚠️ 端口 $port 未占用"
fi
done
if [ $errors -eq 0 ]; then
print_message $GREEN "🎉 所有健康检查通过!"
return 0
else
print_message $RED "❌ 发现 $errors 个问题"
return 1
fi
}
# 数据备份
backup_data() {
local backup_dir="backup"
local timestamp=$(date +%Y%m%d_%H%M%S)
print_message $BLUE "开始数据备份..."
mkdir -p "$backup_dir"
# 备份数据库
local db_backup="$backup_dir/qaup_db_backup_$timestamp.sql"
print_message $BLUE "1. 备份数据库..."
if docker exec $DB_SERVICE pg_dump -U qaup qaup > "$db_backup" 2>/dev/null; then
print_message $GREEN "✓ 数据库备份成功: $db_backup"
else
print_message $RED "❌ 数据库备份失败"
return 1
fi
# 备份Redis数据
local redis_backup="$backup_dir/redis_backup_$timestamp.rdb"
print_message $BLUE "2. 备份Redis数据..."
if docker exec $REDIS_SERVICE redis-cli BGSAVE > /dev/null 2>&1; then
sleep 5
docker cp $REDIS_SERVICE:/data/dump.rdb "$redis_backup" 2>/dev/null
if [ -f "$redis_backup" ]; then
print_message $GREEN "✓ Redis备份成功: $redis_backup"
else
print_message $RED "❌ Redis备份失败"
fi
else
print_message $YELLOW "⚠️ Redis备份跳过"
fi
# 备份应用配置
local config_backup="$backup_dir/config_backup_$timestamp.yml"
print_message $BLUE "3. 备份应用配置..."
if cp config.yml "$config_backup"; then
print_message $GREEN "✓ 配置备份成功: $config_backup"
else
print_message $RED "❌ 配置备份失败"
fi
print_message $GREEN "🎉 数据备份完成!"
ls -la "$backup_dir"/*_$timestamp.*
}
# 清理数据
clean_data() {
print_message $YELLOW "⚠️ 这将删除所有数据目录,确认继续吗?(y/N): "
read -r confirmation
if [[ ! $confirmation =~ ^[Yy]$ ]]; then
print_message $BLUE "操作已取消"
return
fi
print_message $BLUE "清理数据目录..."
# 停止所有服务
stop_all
# 删除数据目录
rm -rf data/ logs/ backup/
print_message $GREEN "✓ 数据目录已清理"
print_message $BLUE "请运行 './qaup-service.sh start' 重新初始化数据"
}
# 更新应用
update_app() {
if [ ! -f "new-app.jar" ]; then
print_message $RED "❌ 未找到新版本文件: new-app.jar"
print_message $BLUE "请先将新版本文件重命名为 new-app.jar"
exit 1
fi
print_message $BLUE "开始应用更新..."
# 备份当前应用
local backup_dir="backup"
local timestamp=$(date +%Y%m%d_%H%M%S)
mkdir -p "$backup_dir"
if [ -f "app.jar" ]; then
cp app.jar "$backup_dir/app.jar.backup.$timestamp"
print_message $BLUE "✓ 当前应用已备份"
fi
# 停止应用服务
docker compose stop $APP_SERVICE
# 替换应用文件
if cp new-app.jar app.jar; then
print_message $GREEN "✓ 应用文件更新成功"
else
print_message $RED "❌ 应用文件更新失败"
exit 1
fi
# 启动应用服务
docker compose up -d $APP_SERVICE
# 等待应用启动
print_message $BLUE "等待应用启动..."
sleep 30
# 验证更新结果
if curl -f -s http://localhost:8080/actuator/health > /dev/null 2>&1; then
print_message $GREEN "🎉 应用更新成功!"
else
print_message $RED "❌ 应用更新失败,正在回滚..."
# 回滚应用
if [ -f "$backup_dir/app.jar.backup.$timestamp" ]; then
docker compose stop $APP_SERVICE
cp "$backup_dir/app.jar.backup.$timestamp" app.jar
docker compose up -d $APP_SERVICE
print_message $BLUE "✓ 应用已回滚"
fi
exit 1
fi
}
# 显示帮助信息
show_help() {
print_message $BLUE "QAUP 统一服务管理脚本"
echo ""
print_message $BLUE "使用方法:"
print_message $BLUE " ./qaup-service.sh <command> [options]"
echo ""
print_message $BLUE "可用命令:"
print_message $BLUE " start 启动所有服务"
print_message $BLUE " stop 停止所有服务"
print_message $BLUE " restart 重启所有服务"
print_message $BLUE " status 查看服务状态"
print_message $BLUE " logs [service] 查看日志 (可指定服务名)"
print_message $BLUE " health 执行健康检查"
print_message $BLUE " backup 备份数据"
print_message $BLUE " clean 清理所有数据"
print_message $BLUE " update 更新应用"
print_message $BLUE " help 显示此帮助信息"
echo ""
print_message $BLUE "示例:"
print_message $BLUE " ./qaup-service.sh start"
print_message $BLUE " ./qaup-service.sh logs qaup-app"
print_message $BLUE " ./qaup-service.sh health"
print_message $BLUE " ./qaup-service.sh backup"
}
# 主函数
main() {
check_files
case "${1:-help}" in
start)
start_all
;;
stop)
stop_all
;;
restart)
restart_all
;;
status)
show_status
;;
logs)
show_logs "$2" "${3:-50}"
;;
health)
health_check
;;
backup)
backup_data
;;
clean)
clean_data
;;
update)
update_app
;;
help|--help|-h)
show_help
;;
*)
print_message $RED "❌ 未知命令: $1"
show_help
exit 1
;;
esac
}
# 执行主函数
main "$@"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,268 @@
# QAUP 系统部署和更新说明
## 开发环境操作
### 生成程序更新文件
当代码修改后需要生成新的jar文件供客户更新
```bash
./deploy/simple/build-update.sh
```
生成包含详细说明的更新包:
- `qaup-admin.jar` - 应用程序
- `UPDATE-INSTRUCTIONS.md` - 详细更新说明
- `VERSION-INFO.txt` - 版本信息
**注意**如果只需要jar文件可以从生成的更新包中提取 `qaup-admin.jar` 文件单独发送给客户。
### 生成完整部署包
```bash
./deploy/simple/package.sh
```
生成包含所有组件的完整部署包,用于首次部署或重大版本更新。
---
## 客户环境操作
### 首次部署
### 1. 解压部署包
```bash
unzip qaup-deploy-20250105-143022.zip
cd qaup-deploy
```
### 2. 执行部署
```bash
./deploy.sh
```
### 3. 验证部署
```bash
# 检查服务状态
docker compose ps
# 访问系统
curl http://localhost:8080/actuator/health
```
## 程序更新仅更新jar文件
### 更新前准备
1. **确认当前系统状态**
```bash
cd qaup-deploy
docker compose ps
```
2. **备份重要数据**(可选但推荐):
```bash
# 备份数据库
docker exec qaup-postgres pg_dump -U qaup qaup > backup-$(date +%Y%m%d).sql
# 备份配置文件
cp config.yml config.yml.backup
```
### 更新步骤
### 1. 准备新版本文件
#### 如果收到的是完整更新包(.zip文件
```bash
# 解压更新包
unzip qaup-update-20250105-143022.zip
cd qaup-update-20250105-143022
# 查看更新说明
cat UPDATE-INSTRUCTIONS.md
# 复制jar文件到部署目录
cd ../qaup-deploy
cp ../qaup-update-20250105-143022/qaup-admin.jar new-app.jar
```
#### 如果收到的是jar文件
```bash
cd qaup-deploy
cp /path/to/qaup-admin-20250105-143022.jar new-app.jar
```
### 2. 执行程序更新
```bash
./update.sh
```
### 3. 验证更新结果
```bash
# 检查服务状态
docker compose ps
# 检查应用日志
docker compose logs -f qaup-app
# 访问系统确认功能正常
curl http://localhost:8080/actuator/health
```
## 更新失败处理
如果更新失败,脚本会自动尝试回滚。如果自动回滚也失败:
### 手动回滚:
```bash
# 停止应用
docker compose stop qaup-app
# 恢复备份文件
cp app.jar.backup.* app.jar
# 启动应用
docker compose start qaup-app
```
### 检查问题:
```bash
# 查看应用日志
docker compose logs qaup-app
# 查看所有服务状态
docker compose ps -a
```
## 配置文件更新
如果新版本需要更新配置文件:
1. **备份当前配置**
```bash
cp config.yml config.yml.backup
```
2. **更新配置**
```bash
# 编辑配置文件
vi config.yml
```
3. **重启应用**
```bash
docker compose restart qaup-app
```
## 常见问题
### Q: 更新后无法访问系统
A: 检查端口是否被占用,查看应用日志:
```bash
docker compose logs qaup-app
netstat -tlnp | grep 8080
```
### Q: 数据库连接失败
A: 检查数据库服务状态:
```bash
docker compose logs qaup-postgres
docker exec qaup-postgres pg_isready -U qaup
```
### Q: 如何完全重新部署
A: 如果更新出现严重问题,可以重新部署:
```bash
# 停止所有服务
docker compose down
# 清理数据(注意:这会删除所有数据)
rm -rf data/
# 重新部署
./deploy.sh
```
## 完整系统重新部署
如果需要重新部署整个系统包括更新Docker镜像、配置等
### 1. 获取新的部署包
```bash
# 解压新的部署包到新目录
unzip qaup-deploy-new-20250110-100000.zip
cd qaup-deploy-new
```
### 2. 迁移数据(如果需要保留数据)
```bash
# 从旧系统备份数据
cd ../qaup-deploy
docker exec qaup-postgres pg_dump -U qaup qaup > ../data-backup.sql
# 停止旧系统
docker compose down
# 在新系统中恢复数据
cd ../qaup-deploy-new
./deploy.sh
docker exec -i qaup-postgres psql -U qaup qaup < ../data-backup.sql
```
## 操作流程总结
### 开发环境 → 客户环境流程
#### 程序更新流程:
```
开发环境:
1. 代码修改完成
2. ./deploy/simple/build-update.sh # 生成更新包
3. 发送更新包或jar文件给客户
客户环境:
1. cd qaup-deploy
2. cp new-jar-file.jar new-app.jar # 重命名文件
3. ./update.sh # 执行更新
4. 验证更新结果
```
#### 完整部署流程:
```
开发环境:
1. ./deploy/simple/package.sh # 生成完整部署包
2. 发送部署包给客户
客户环境:
1. unzip qaup-deploy-xxx.zip # 解压部署包
2. cd qaup-deploy
3. ./deploy.sh # 执行部署
4. 验证部署结果
```
## 更新记录
建议记录每次更新的信息:
- 更新时间
- 版本号
- 更新人员
- 更新类型(程序更新/完整部署)
- 是否成功
- 遇到的问题
示例:
```
2025-01-05 14:30 - v1.0.1 → v1.0.2 - 张三 - 程序更新 - 成功
2025-01-10 09:15 - v1.0.2 → v1.0.3 - 李四 - 程序更新 - 失败,已回滚
2025-01-15 16:00 - v1.0.3 → v1.1.0 - 王五 - 完整部署 - 成功
```
## 文件说明
### 开发环境脚本:
- `deploy/simple/build-update.sh` - 生成程序更新包
- `deploy/simple/package.sh` - 生成完整部署包
### 客户环境脚本:
- `deploy.sh` - 首次部署脚本
- `update.sh` - 程序更新脚本
- `DeployGuide.md` - 本说明文档

View File

@ -0,0 +1,110 @@
#!/bin/bash
# QAUP 程序更新打包脚本
# 用于生成仅包含jar文件的更新包
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
print_message() {
echo -e "${1}${2}${NC}"
}
print_message $BLUE "=== QAUP 程序更新打包 ==="
# 1. 构建应用
print_message $BLUE "构建应用..."
cd "$PROJECT_ROOT"
mvn clean package -DskipTests -q
# 检查jar文件是否生成成功
JAR_FILE="$PROJECT_ROOT/qaup-admin/target/qaup-admin.jar"
if [ ! -f "$JAR_FILE" ]; then
print_message $RED "❌ jar文件构建失败"
exit 1
fi
# 2. 创建更新包目录
UPDATE_DIR="qaup-update-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$UPDATE_DIR"
# 3. 复制jar文件
print_message $BLUE "准备更新文件..."
cp "$JAR_FILE" "$UPDATE_DIR/qaup-admin.jar"
# 4. 创建更新说明
cat > "$UPDATE_DIR/UPDATE-INSTRUCTIONS.md" << 'EOF'
# QAUP 程序更新包
## 包含内容
- `qaup-admin.jar` - 新版本应用程序
## 更新步骤
### 1. 上传文件到服务器
`qaup-admin.jar` 上传到服务器的 qaup-deploy 目录
### 2. 重命名文件
```bash
cd qaup-deploy
cp qaup-admin.jar new-app.jar
```
### 3. 执行更新
```bash
./update.sh
```
### 4. 验证更新
```bash
# 检查服务状态
docker compose ps
# 检查应用日志
docker compose logs -f qaup-app
# 访问系统确认功能正常
curl http://localhost:8080/actuator/health
```
## 注意事项
- 更新前建议备份数据库
- 如果更新失败,脚本会自动回滚
- 如有问题,请查看完整的部署更新说明文档
EOF
# 5. 创建版本信息
cat > "$UPDATE_DIR/VERSION-INFO.txt" << EOF
QAUP 程序更新包
构建时间: $(date)
构建主机: $(hostname)
Git提交: $(git rev-parse --short HEAD 2>/dev/null || echo "未知")
Maven版本: $(mvn --version | head -1)
Java版本: $(java -version 2>&1 | head -1)
文件大小: $(du -sh "$JAR_FILE" | cut -f1)
文件MD5: $(md5sum "$JAR_FILE" | cut -d' ' -f1)
EOF
# 6. 创建更新包
PACKAGE_NAME="${UPDATE_DIR}.zip"
zip -r "$PACKAGE_NAME" "$UPDATE_DIR/"
# 7. 清理临时目录
rm -rf "$UPDATE_DIR"
print_message $GREEN "✅ 程序更新包创建完成: $PACKAGE_NAME"
print_message $BLUE "包大小: $(du -sh "$PACKAGE_NAME" | cut -f1)"
echo ""
echo "使用说明:"
echo "1. 将 $PACKAGE_NAME 发送给客户"
echo "2. 客户解压后按照 UPDATE-INSTRUCTIONS.md 操作"
echo "3. 或者直接发送 qaup-admin.jar 文件给客户"

68
deploy/simple/config.yml Normal file
View File

@ -0,0 +1,68 @@
# QAUP 生产环境配置
server:
port: 8080
# 数据库配置
spring:
datasource:
url: jdbc:postgresql://qaup-postgres:5432/qaup
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
validation-query: SELECT 1
test-while-idle: true
# Redis配置
data:
redis:
host: qaup-redis
port: 6379
database: 0
timeout: 10s
# JPA配置
jpa:
hibernate:
ddl-auto: none
show-sql: false
# 应用配置
qaup:
name: QAUP
version: 1.0.1
profile: /app/uploads
# 外部接口配置(客户可修改)
external:
airport-api:
base-url: http://192.168.1.100:8090
username: dianxin
password: dianxin@123
vehicle-api:
base-url: http://192.168.1.100:8090
# 日志配置
logging:
level:
com.qaup: info
org.springframework: warn
pattern:
console: "%d{HH:mm:ss} %-5level %logger{36} - %msg%n"
# 数据采集配置
data:
collector:
interval: 250
detection:
interval: 1000
# 坐标系统配置
coordinate-system:
airport:
center-longitude: 120.0834104
center-latitude: 36.35406879

View File

@ -0,0 +1,71 @@
#!/bin/bash
# 创建简化的数据库初始化脚本
# 从现有的数据库架构文件生成init.sql
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
echo "=== 创建数据库初始化脚本 ==="
# 输出文件
INIT_SQL="$SCRIPT_DIR/init.sql"
# 创建初始化脚本
cat > "$INIT_SQL" << 'EOF'
-- QAUP 数据库初始化脚本
-- 简化版本:只创建必要的扩展和导入数据
-- 创建PostGIS扩展
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;
-- 显示扩展信息
SELECT 'PostGIS扩展安装完成' as message;
EOF
# 如果存在数据库架构文件,追加到初始化脚本
SCHEMA_FILE="$PROJECT_ROOT/deploy/docker/postgres/qaup_database_schema.sql"
if [ -f "$SCHEMA_FILE" ]; then
echo "-- 导入数据库架构" >> "$INIT_SQL"
cat "$SCHEMA_FILE" >> "$INIT_SQL"
echo "✓ 已添加数据库架构"
else
echo "⚠ 未找到数据库架构文件: $SCHEMA_FILE"
fi
# 添加完整初始数据(优先使用完整数据文件)
COMPLETE_DATA_FILE="$PROJECT_ROOT/deploy/docker/postgres/export/initial_data_complete.sql"
if [ -f "$COMPLETE_DATA_FILE" ]; then
echo "" >> "$INIT_SQL"
echo "-- 导入完整初始数据" >> "$INIT_SQL"
cat "$COMPLETE_DATA_FILE" >> "$INIT_SQL"
echo "✓ 已添加完整初始数据文件"
else
echo "⚠ 未找到完整初始数据文件: $COMPLETE_DATA_FILE"
echo "-- 注意:数据库将只包含基础结构,无初始数据" >> "$INIT_SQL"
fi
# 添加完成信息
cat >> "$INIT_SQL" << 'EOF'
-- 显示数据库信息
SELECT
current_database() as database_name,
current_user as current_user,
version() as postgresql_version;
-- 显示表数量
SELECT
schemaname,
COUNT(*) as table_count
FROM pg_tables
WHERE schemaname = 'public'
GROUP BY schemaname;
SELECT 'QAUP数据库初始化完成' as message;
EOF
echo "✅ 初始化脚本创建完成: $INIT_SQL"
echo "文件大小: $(du -sh "$INIT_SQL" | cut -f1)"

Some files were not shown because too many files have changed in this diff Show More