feat: 完善系统配置审计通知功能并优化异常处理

- 新增异常处理体系(BaseException及其子类)
- 优化密码、邮箱、用户名等基础类型
- 添加字典管理、登录日志、操作日志的E2E测试
- 完善API集成测试和安全测试
- 添加性能测试配置和脚本
- 优化OpenAPI配置和全局异常处理器
This commit is contained in:
张翔
2026-03-24 14:05:35 +08:00
parent be5d5ede90
commit e4721053bd
47 changed files with 3006 additions and 816 deletions
+234 -183
View File
@@ -1,246 +1,297 @@
# 性能测试指南
## 测试目的
## 概述
评估网关层对系统性能的影响,验证多模块架构下的性能表现
本目录包含Novalon管理系统的性能测试脚本,使用k6进行负载测试和压力测试
## 测试工具
## 前置条件
使用 k6 进行性能测试,支持以下测试场景
1. **后端服务运行**
```bash
cd novalon-manage-api/manage-app
mvn spring-boot:run
```
### 1. 基准测试 (Baseline Test)
- 持续负载:10个虚拟用户
- 持续时间:30秒
- 目的:建立性能基准
2. **数据库服务运行**
```bash
docker-compose up -d postgres
```
### 2. 压力测试 (Stress Test)
- 阶梯式负载:10 -> 50 -> 100 -> 50 -> 10
- 持续时间:5分钟
- 目的:测试系统在持续高负载下的表现
3. **k6安装**
```bash
# macOS
brew install k6
### 3. 尖峰测试 (Spike Test)
- 突发负载:10 -> 200 -> 10
- 持续时间:70秒
- 目的:测试系统应对突发流量的能力
# Linux
curl https://github.com/grafana/k6/releases/download/v0.50.0/k6-v0.50.0-linux-amd64.tar.gz -L | tar xvz
sudo mv k6-v0.50.0-linux-amd64/k6 /usr/local/bin/
```
## 运行测试
## 测试场景
### 前置条件
### 1. 基础性能测试
1. 启动所有服务:
**目标**:测试系统在低负载下的性能表现
**虚拟用户数**10
**持续时间**7分钟
**测试接口**:健康检查、登录、用户列表
**运行命令**
```bash
docker-compose up -d
k6 run load_test.js
```
2. 等待服务就绪
**预期结果**
- 95%的请求响应时间<500ms
- 错误率<1%
- 系统稳定运行
### 2. 中等负载测试
**目标**:测试系统在中负载下的性能表现
**虚拟用户数**50
**持续时间**14分钟
**测试接口**:健康检查、登录、用户列表、角色列表、字典列表
**运行命令**
```bash
curl http://localhost:8080/actuator/health
k6 run load_test.js
```
### 运行基准测试
**预期结果**
- 95%的请求响应时间<500ms
- 错误率<1%
- 系统稳定运行
### 3. 高负载测试
**目标**:测试系统在高负载下的性能表现
**虚拟用户数**100
**持续时间**21分钟
**测试接口**:健康检查、登录、用户列表、角色列表、字典列表、系统配置、通知列表、操作日志
**运行命令**
```bash
k6 run performance_tests/gateway_performance_test.js \
--env BASE_URL=http://localhost:8080 \
--env DURATION=30s \
--env VUS=10
k6 run load_test.js
```
### 运行压力测试
**预期结果**
- 95%的请求响应时间<500ms
- 99%的请求响应时间<1000ms
- 错误率<1%
- 系统稳定运行
### 4. 压力测试
**目标**:测试系统在极限负载下的性能表现
**虚拟用户数**100
**持续时间**12分钟
**测试接口**:所有主要接口
**运行命令**
```bash
k6 run --config performance_tests/config.json \
performance_tests/gateway_performance_test.js \
--env BASE_URL=http://localhost:8080 \
--scenario stress_test
k6 run load_test.js
```
### 运行尖峰测试
```bash
k6 run --config performance_tests/config.json \
performance_tests/gateway_performance_test.js \
--env BASE_URL=http://localhost:8080 \
--scenario spike_test
```
**预期结果**
- 识别系统性能瓶颈
- 验证系统稳定性
- 记录错误率
## 性能指标
### 关键指标
1. **响应时间 (Response Time)**
- P50: 50%的请求响应时间
- P95: 95%的请求响应时间
- P99: 99%的请求响应时间
- 目标:P95 < 500ms
| 指标 | 描述 | 目标值 |
|------|------|--------|
| HTTP请求响应时间 | 请求从发送到接收的总时间 | p95<500ms, p99<1000ms |
| HTTP请求失败率 | 失败请求占总请求的比例 | <1% |
| HTTP请求速率 | 每秒处理的请求数 | >100请求/秒 |
| 虚拟用户数 | 并发访问系统的用户数 | 根据测试场景 |
| 吞吐量 | 系统每秒处理的请求数 | 根据测试场景 |
2. **吞吐量 (Throughput)**
- RPS (Requests Per Second): 每秒请求数
- 目标:根据业务需求设定
### 性能阈值
3. **错误率 (Error Rate)**
- HTTP 请求失败率
- 目标:< 5%
```javascript
thresholds: {
http_req_duration: ['p(95)<500'], // 95%的请求响应时间<500ms
http_req_failed: ['rate<0.01'], // 错误率<1%
}
```
### 网关性能指标
## 测试结果分析
1. **认证延迟**
- JWT 验证时间
- 目标:< 10ms
### 查看测试结果
2. **授权延迟**
- RBAC 权限检查时间
- 目标:< 5ms
k6会自动生成测试报告,包括:
3. **路由延迟**
- 请求转发时间
- 目标:< 20ms
1. **控制台输出**:实时显示测试进度和结果
2. **HTML报告**:使用`--out`参数生成HTML报告
3. **JSON报告**:使用`--out`参数生成JSON报告
## 性能基准
### 生成HTML报告
### 无网关架构
- 平均响应时间:~200ms
- P95 响应时间:~350ms
- 吞吐量:~500 RPS
```bash
k6 run --out html=report.html load_test.js
open report.html
```
### 有网关架构(预期)
- 平均响应时间:~220ms (+10%)
- P95 响应时间:~400ms (+14%)
- 吞吐量:~450 RPS (-10%)
### 生成JSON报告
### 性能影响评估
网关层预期性能开销:
- 响应时间增加:10-15%
- 吞吐量下降:10-15%
- CPU 使用增加:5-10%
```bash
k6 run --out json=report.json load_test.js
```
## 性能优化建议
### 网关层优化
### 1. 数据库优化
1. **缓存优化**
- JWT Token 缓存
- 权限规则缓存
- 路由规则缓存
- 添加适当的索引
- 优化慢查询
- 使用连接池
- 考虑读写分离
2. **连接池优化**
- HTTP 客户端连接池
- 数据库连接池
### 2. 缓存优化
3. **异步处理**
- 非阻塞 I/O
- 响应式编程
- 使用Redis缓存热点数据
- 实现查询结果缓存
- 使用CDN缓存静态资源
### 应用优化
### 3. 应用优化
1. **数据库优化**
- 索引优化
- 查询优化
- 连接池配置
- 优化算法复杂度
- 减少不必要的数据库查询
- 使用异步处理
- 实现请求合并
2. **缓存策略**
- Redis 缓存
- 本地缓存
### 4. 基础设施优化
3. **代码优化**
- 减少序列化开销
- 优化算法复杂度
## 监控指标
使用 Spring Boot Actuator 进行轻量级监控:
### 健康检查
```bash
curl http://localhost:8080/actuator/health
```
### 应用信息
```bash
curl http://localhost:8080/actuator/info
```
### 性能指标
```bash
curl http://localhost:8080/actuator/metrics
```
### 系统指标
- JVM 内存使用
- GC 频率和时间
- 线程池使用情况
- HTTP 请求统计
## 结果分析
### 性能报告模板
```
测试场景:[基准测试/压力测试/尖峰测试]
测试时间:[YYYY-MM-DD HH:MM:SS]
测试时长:[XX秒]
虚拟用户数:[XX]
性能指标:
- 平均响应时间:[XXms]
- P95 响应时间:[XXms]
- P99 响应时间:[XXms]
- 吞吐量:[XX RPS]
- 错误率:[XX%]
网关性能:
- 认证延迟:[XXms]
- 授权延迟:[XXms]
- 路由延迟:[XXms]
系统资源:
- CPU 使用率:[XX%]
- 内存使用率:[XX%]
结论:
[性能是否满足要求,是否需要优化]
```
- 使用负载均衡
- 水平扩展应用实例
- 优化网络配置
- 使用更快的硬件
## 故障排查
### 常见问题
### 问题1:连接超时
1. **连接拒绝**
- 检查服务是否启动
- 检查端口是否正确
- 检查防火墙设置
**症状**:大量请求失败,错误率高
2. **高错误率**
- 检查日志文件
- 检查数据库连接
- 检查内存使用情况
**解决方案**
- 检查后端服务是否正常运行
- 检查数据库连接是否正常
- 增加连接池大小
- 优化数据库查询
3. **响应时间过长**
- 检查慢查询日志
- 检查网络延迟
- 检查 GC 情况
### 问题2:响应时间过长
## 持续集成
**症状**:95%或99%的请求响应时间超过阈值
将性能测试集成到 Woodpecker CI
**解决方案**
- 分析慢查询日志
- 添加数据库索引
- 优化应用逻辑
- 增加缓存
### 问题3:内存溢出
**症状**:应用崩溃或性能急剧下降
**解决方案**
- 增加JVM堆内存
- 分析内存泄漏
- 优化对象创建
- 使用对象池
## CI/CD集成
### GitHub Actions示例
```yaml
performance_test:
image: python:3.13
commands:
- cd api_integration_tests
- pip install -r requirements.txt
- pytest tests/test_real_e2e.py -v --no-cov
depends_on:
- deploy-staging
when:
- event: push
branch: develop
name: Performance Tests
on:
schedule:
- cron: '0 2 * * *' # 每天凌晨2点运行
workflow_dispatch:
jobs:
performance-test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_DB: manage_system
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 55432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Set up Java
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Build backend
run: |
cd novalon-manage-api
mvn clean package -DskipTests
- name: Start backend
run: |
cd novalon-manage-api/manage-app
java -jar target/manage-app-1.0.0.jar &
sleep 30
- name: Install k6
run: |
curl https://github.com/grafana/k6/releases/download/v0.50.0/k6-v0.50.0-linux-amd64.tar.gz -L | tar xvz
sudo mv k6-v0.50.0-linux-amd64/k6 /usr/local/bin/
- name: Run performance tests
run: |
cd performance_tests
k6 run --out json=performance-results.json load_test.js
- name: Upload performance results
uses: actions/upload-artifact@v3
with:
name: performance-results
path: performance_tests/performance-results.json
```
## 参考资料
## 最佳实践
- [k6 官方文档](https://k6.io/docs/)
- [Spring Boot Actuator 文档](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)
- [Spring WebFlux 性能优化](https://docs.spring.io/spring-framework/reference/web/webflux/reactive-spring.html)
1. **在非生产环境运行**:性能测试应该在测试环境或预发布环境运行
2. **使用真实数据**:使用与生产环境相似的数据量和数据分布
3. **监控系统资源**:在测试过程中监控CPU、内存、磁盘、网络使用情况
4. **多次运行**:多次运行测试以获得稳定的结果
5. **记录基准**:建立性能基准,便于比较和改进
6. **分析瓶颈**:根据测试结果分析性能瓶颈并优化
7. **持续优化**:将性能测试集成到CI/CD流水线,持续监控和优化
## 联系方式
如有问题或建议,请联系:
- **作者**:张翔
- **角色**:全栈质量保障与研发效能工程师
- **项目**Novalon管理系统
---
**文档版本**1.0
**最后更新**2026-03-24
+148 -30
View File
@@ -1,36 +1,154 @@
{
"scenarios": {
"baseline": {
"executor": "constant-vus",
"vus": 10,
"duration": "30s"
"name": "Novalon管理系统性能测试",
"stages": [
{
"name": "预热阶段",
"duration": "2m",
"target": 10,
"description": "2分钟内增加到10个虚拟用户"
},
"stress_test": {
"executor": "ramping-vus",
"startVUs": 10,
"stages": [
{ "duration": "1m", "target": 50 },
{ "duration": "2m", "target": 100 },
{ "duration": "1m", "target": 50 },
{ "duration": "1m", "target": 10 }
]
{
"name": "稳定阶段1",
"duration": "5m",
"target": 10,
"description": "保持10个虚拟用户5分钟"
},
"spike_test": {
"executor": "ramping-vus",
"startVUs": 10,
"stages": [
{ "duration": "30s", "target": 10 },
{ "duration": "10s", "target": 200 },
{ "duration": "30s", "target": 10 }
]
{
"name": "负载增加阶段",
"duration": "2m",
"target": 50,
"description": "2分钟内增加到50个虚拟用户"
},
{
"name": "稳定阶段2",
"duration": "5m",
"target": 50,
"description": "保持50个虚拟用户5分钟"
},
{
"name": "高负载阶段",
"duration": "2m",
"target": 100,
"description": "2分钟内增加到100个虚拟用户"
},
{
"name": "稳定阶段3",
"duration": "5m",
"target": 100,
"description": "保持100个虚拟用户5分钟"
},
{
"name": "冷却阶段",
"duration": "2m",
"target": 0,
"description": "2分钟内降到0个虚拟用户"
}
],
"thresholds": {
"http_req_duration": {
"p95": "<500",
"p99": "<1000",
"description": "95%的请求响应时间<500ms99%的请求响应时间<1000ms"
},
"http_req_failed": {
"rate": "<0.01",
"description": "错误率<1%"
},
"http_reqs": {
"rate": ">100",
"description": "请求速率>100请求/秒"
}
},
"thresholds": {
"http_req_duration": [
{ "target": "p(95)<500", "abortOnFail": true }
],
"http_req_failed": [
{ "target": "rate<0.05", "abortOnFail": true }
]
"endpoints": [
{
"name": "健康检查",
"method": "GET",
"path": "/actuator/health",
"expected_status": 200,
"max_duration": 100,
"description": "后端健康检查接口"
},
{
"name": "登录",
"method": "POST",
"path": "/api/auth/login",
"expected_status": 200,
"max_duration": 500,
"description": "用户登录接口"
},
{
"name": "用户列表",
"method": "GET",
"path": "/api/users?page=0&size=10",
"expected_status": 200,
"max_duration": 300,
"description": "获取用户列表接口"
},
{
"name": "角色列表",
"method": "GET",
"path": "/api/roles?page=0&size=10",
"expected_status": 200,
"max_duration": 300,
"description": "获取角色列表接口"
},
{
"name": "字典列表",
"method": "GET",
"path": "/api/dicts?page=0&size=10",
"expected_status": 200,
"max_duration": 300,
"description": "获取字典列表接口"
},
{
"name": "系统配置",
"method": "GET",
"path": "/api/configs?page=0&size=10",
"expected_status": 200,
"max_duration": 300,
"description": "获取系统配置接口"
},
{
"name": "通知列表",
"method": "GET",
"path": "/api/notices?page=0&size=10",
"expected_status": 200,
"max_duration": 300,
"description": "获取通知列表接口"
},
{
"name": "操作日志",
"method": "GET",
"path": "/api/operation-logs?page=0&size=10",
"expected_status": 200,
"max_duration": 300,
"description": "获取操作日志接口"
}
],
"scenarios": {
"basic": {
"name": "基础性能测试",
"description": "测试系统在低负载下的性能表现",
"stages": ["预热阶段", "稳定阶段1", "冷却阶段"],
"endpoints": ["健康检查", "登录", "用户列表"]
},
"moderate": {
"name": "中等负载测试",
"description": "测试系统在中负载下的性能表现",
"stages": ["预热阶段", "稳定阶段1", "负载增加阶段", "稳定阶段2", "冷却阶段"],
"endpoints": ["健康检查", "登录", "用户列表", "角色列表", "字典列表"]
},
"high": {
"name": "高负载测试",
"description": "测试系统在高负载下的性能表现",
"stages": ["预热阶段", "稳定阶段1", "负载增加阶段", "稳定阶段2", "高负载阶段", "稳定阶段3", "冷却阶段"],
"endpoints": ["健康检查", "登录", "用户列表", "角色列表", "字典列表", "系统配置", "通知列表", "操作日志"]
},
"stress": {
"name": "压力测试",
"description": "测试系统在极限负载下的性能表现",
"stages": ["高负载阶段", "稳定阶段3", "冷却阶段"],
"endpoints": ["健康检查", "登录", "用户列表", "角色列表", "字典列表", "系统配置", "通知列表", "操作日志"]
}
}
}
}
+145
View File
@@ -0,0 +1,145 @@
import http from 'k6/http';
import { check, sleep } from 'k6';
const BASE_URL = 'http://localhost:8084';
export const options = {
stages: [
{ duration: '2m', target: 10 }, // 2分钟内增加到10用户
{ duration: '5m', target: 10 }, // 保持10用户5分钟
{ duration: '2m', target: 50 }, // 2分钟内增加到50用户
{ duration: '5m', target: 50 }, // 保持50用户5分钟
{ duration: '2m', target: 100 }, // 2分钟内增加到100用户
{ duration: '5m', target: 100 }, // 保持100用户5分钟
{ duration: '2m', target: 0 }, // 2分钟内降到0用户
],
thresholds: {
http_req_duration: ['p(95)<500'], // 95%的请求响应时间<500ms
http_req_failed: ['rate<0.01'], // 错误率<1%
},
};
export default function () {
// 测试1: 健康检查
let healthRes = http.get(`${BASE_URL}/actuator/health`);
check(healthRes, {
'健康检查状态码200': (r) => r.status === 200,
'健康检查响应时间<100ms': (r) => r.timings.duration < 100,
});
// 测试2: 登录
let loginRes = http.post(
`${BASE_URL}/api/auth/login`,
JSON.stringify({
username: 'admin',
password: 'admin123',
}),
{
headers: { 'Content-Type': 'application/json' },
}
);
check(loginRes, {
'登录状态码200': (r) => r.status === 200,
'登录响应时间<500ms': (r) => r.timings.duration < 500,
'登录返回token': (r) => JSON.parse(r.body).token !== undefined,
});
// 测试3: 获取用户列表
const token = JSON.parse(loginRes.body).token;
let usersRes = http.get(
`${BASE_URL}/api/users?page=0&size=10`,
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
}
);
check(usersRes, {
'用户列表状态码200': (r) => r.status === 200,
'用户列表响应时间<300ms': (r) => r.timings.duration < 300,
'用户列表返回数据': (r) => JSON.parse(r.body).content !== undefined,
});
// 测试4: 获取角色列表
let rolesRes = http.get(
`${BASE_URL}/api/roles?page=0&size=10`,
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
}
);
check(rolesRes, {
'角色列表状态码200': (r) => r.status === 200,
'角色列表响应时间<300ms': (r) => r.timings.duration < 300,
'角色列表返回数据': (r) => JSON.parse(r.body).content !== undefined,
});
// 测试5: 获取字典列表
let dictRes = http.get(
`${BASE_URL}/api/dicts?page=0&size=10`,
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
}
);
check(dictRes, {
'字典列表状态码200': (r) => r.status === 200,
'字典列表响应时间<300ms': (r) => r.timings.duration < 300,
'字典列表返回数据': (r) => JSON.parse(r.body).content !== undefined,
});
// 测试6: 获取系统配置
let configRes = http.get(
`${BASE_URL}/api/configs?page=0&size=10`,
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
}
);
check(configRes, {
'系统配置状态码200': (r) => r.status === 200,
'系统配置响应时间<300ms': (r) => r.timings.duration < 300,
'系统配置返回数据': (r) => JSON.parse(r.body).content !== undefined,
});
// 测试7: 获取通知列表
let noticeRes = http.get(
`${BASE_URL}/api/notices?page=0&size=10`,
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
}
);
check(noticeRes, {
'通知列表状态码200': (r) => r.status === 200,
'通知列表响应时间<300ms': (r) => r.timings.duration < 300,
'通知列表返回数据': (r) => JSON.parse(r.body).content !== undefined,
});
// 测试8: 获取操作日志
let opLogRes = http.get(
`${BASE_URL}/api/operation-logs?page=0&size=10`,
{
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
}
);
check(opLogRes, {
'操作日志状态码200': (r) => r.status === 200,
'操作日志响应时间<300ms': (r) => r.timings.duration < 300,
'操作日志返回数据': (r) => JSON.parse(r.body).content !== undefined,
});
sleep(1); // 每个虚拟用户之间间隔1秒
}