refactor: 整理项目根目录结构
整理内容: - 删除 60+ 临时测试输出文件 (*.txt) - 移动二进制文件到 bin/ 目录 - 移动 Shell 脚本到 scripts/ 目录 - scripts/dev/: check_gitea.sh, check_sub2api.sh, run_tests.sh - scripts/deploy/: deploy_*.sh, simple_deploy.sh - scripts/ops/: fix_nginx.sh, fix_ssl.sh, install_docker.sh - scripts/test/: test_*.sh, test_*.bat - 移动批处理文件到 scripts/ - 移动 Python 脚本到 tools/ - 清理临时日志文件 保留根目录必要文件: - go.mod, go.sum, go.work - Makefile, docker-compose.yml - .env.example, .gitignore - README.md, AGENTS.md, DEPLOY_GUIDE.md 验证: go build ./... && go test ./... 通过
This commit is contained in:
198
docs/testing/TEST_PLAN_1_BUSINESS_LOGIC.md
Normal file
198
docs/testing/TEST_PLAN_1_BUSINESS_LOGIC.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# 业务逻辑正确性测试方案
|
||||
|
||||
## 概述
|
||||
|
||||
本文档定义用户管理系统核心业务逻辑的正确性测试方案,涵盖用户生命周期管理、设备信任、登录日志、统计数据的端到端正确性验证。
|
||||
|
||||
---
|
||||
|
||||
## 1. 用户注册与审批流程测试
|
||||
|
||||
### 1.1 用户注册创建
|
||||
|
||||
**测试目标**:验证用户注册后状态流转与数据一致性的正确性
|
||||
|
||||
| 用例编号 | 用例描述 | 前置条件 | 测试步骤 | 预期结果 | 数据库验证 |
|
||||
|---------|---------|---------|---------|---------|-----------|
|
||||
| REG-001 | 管理员创建用户并设置初始状态为已激活 | 管理员已登录 | 1. 管理员创建用户,status=1(已激活)<br>2. 提交表单 | 1. API 返回 200<br>2. 用户状态为已激活<br>3. 无需邮箱激活即可登录 | `SELECT status FROM users WHERE username='xxx'` 返回 `1` |
|
||||
| REG-002 | 管理员创建用户并设置初始状态为未激活 | 管理员已登录 | 1. 管理员创建用户,status=0(未激活)<br>2. 提交表单 | 1. API 返回 200<br>2. 用户状态为未激活<br>3. 用户无法登录 | `SELECT status FROM users WHERE username='xxx'` 返回 `0` |
|
||||
| REG-003 | 用户自助注册流程状态正确 | 系统无管理员 | 1. 用户填写注册表单<br>2. 提交注册 | 1. 创建用户,status=0(未激活)<br>2. 发送激活邮件 | `SELECT status FROM users WHERE email='xxx'` 返回 `0` |
|
||||
| REG-004 | 重复用户名注册拒绝 | 无 | 1. 创建用户 "testuser"<br>2. 再次创建相同用户名 | 返回错误:用户名已存在 | 用户表仅有一条 username='testuser' 的记录 |
|
||||
| REG-005 | 创建用户时分配角色 | 存在可用角色 | 1. 创建用户并分配 role_ids=[2,3]<br>2. 查询用户角色 | 1. API 返回成功<br>2. 用户拥有指定角色 | `SELECT role_id FROM user_roles WHERE user_id=xxx` 包含 2,3 |
|
||||
|
||||
### 1.2 用户状态变更
|
||||
|
||||
**测试目标**:验证用户状态(激活/锁定/禁用)变更的正确性及对登录的影响
|
||||
|
||||
| 用例编号 | 用例描述 | 测试步骤 | 预期结果 | 数据库验证 |
|
||||
|---------|---------|---------|---------|-----------|
|
||||
| STA-001 | 管理员禁用用户后用户无法登录 | 1. 禁用用户(status=3)<br>2. 用户尝试登录 | 登录失败,提示"账号已禁用" | `SELECT status FROM users WHERE id=xxx` 返回 `3` |
|
||||
| STA-002 | 管理员锁定用户后用户无法登录 | 1. 锁定用户(status=2)<br>2. 用户尝试登录 | 登录失败,提示"账号已锁定" | `SELECT status FROM users WHERE id=xxx` 返回 `2` |
|
||||
| STA-003 | 管理员解锁用户后用户恢复登录 | 1. 用户当前 status=2<br>2. 管理员更新为 status=1 | 1. 更新成功<br>2. 用户可正常登录 | `SELECT status FROM users WHERE id=xxx` 返回 `1` |
|
||||
| STA-004 | 管理员激活未激活用户 | 1. 用户当前 status=0<br>2. 管理员激活用户 | 1. 更新成功<br>2. 用户可正常登录 | `SELECT status FROM users WHERE id=xxx` 返回 `1` |
|
||||
| STA-005 | 批量更新用户状态 | 1. 选择 5 个用户<br>2. 批量禁用 | 1. 全部更新成功<br>2. 所有用户 status=3 | `SELECT COUNT(*) FROM users WHERE status=3 AND id IN (...)` 返回 `5` |
|
||||
|
||||
### 1.3 用户删除
|
||||
|
||||
**测试目标**:验证用户删除后的数据完整性
|
||||
|
||||
| 用例编号 | 用例描述 | 测试步骤 | 预期结果 | 数据库验证 |
|
||||
|---------|---------|---------|---------|-----------|
|
||||
| DEL-001 | 删除用户后用户角色关联清除 | 1. 删除用户 ID=10<br>2. 查询 user_roles | 1. API 返回 200<br>2. 该用户无角色记录 | `SELECT COUNT(*) FROM user_roles WHERE user_id=10` 返回 `0` |
|
||||
| DEL-002 | 删除用户后登录日志保留用户ID | 1. 删除用户前查询登录日志<br>2. 删除用户<br>3. 查询登录日志 | 1. 删除前有日志<br>2. 删除后日志仍存在,user_id 字段保留 | 登录日志表 user_id 字段保留原值(非级联删除) |
|
||||
| DEL-003 | 删除用户后设备关联保留 | 1. 删除用户<br>2. 查询 devices 表 | devices 表中该用户的设备保留,user_id 不变 | `SELECT COUNT(*) FROM devices WHERE user_id=xxx` 结果不变 |
|
||||
| DEL-004 | 恢复删除(软删除)的用户 | 1. 系统启用软删除<br>2. 删除用户<br>3. 恢复用户 | 1. 删除成功<br>2. 恢复后用户状态恢复 | 用户记录恢复,status 恢复原值 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 统计数据正确性测试
|
||||
|
||||
### 2.1 用户统计正确性
|
||||
|
||||
**测试目标**:验证用户统计(total_users, active_users, new_users 等)的计算正确性
|
||||
|
||||
| 用例编号 | 用例描述 | 测试步骤 | 预期结果 | 数据库验证 |
|
||||
|---------|---------|---------|---------|-----------|
|
||||
| STAT-001 | 总用户数统计正确 | 1. 查询当前总用户数<br>2. 创建 3 个新用户<br>3. 再次查询 | 第二次查询比第一次多 3 | `SELECT COUNT(*) FROM users` 与 API 返回 total_users 一致 |
|
||||
| STAT-002 | 新增用户今日统计正确 | 1. 查看今日新增<br>2. 创建一个用户<br>3. 再次查看 | 新增用户数 +1 | `SELECT COUNT(*) FROM users WHERE created_at >= today_start` 与 new_users_today 一致 |
|
||||
| STAT-003 | 按状态统计正确 | 1. 创建 2 个用户后禁用其中一个<br>2. 查询统计数据 | disabled_users = 1 | `SELECT COUNT(*) FROM users WHERE status=3` 与 disabled_users 一致 |
|
||||
| STAT-004 | 创建用户后 dashboard 统计数据更新 | 1. 获取 dashboard stats<br>2. 创建 1 个用户<br>3. 再次获取 | total_users +1, new_users_today +1 | 两次 stats 对比差异正确 |
|
||||
| STAT-005 | 删除用户后统计更新 | 1. 获取 stats<br>2. 删除 1 个用户<br>3. 再次获取 | total_users -1 | 两次 stats 对比差异正确 |
|
||||
| STAT-006 | 批量创建用户统计准确 | 1. 批量导入 100 个用户<br>2. 查询统计 | total_users 增加 100 | `SELECT COUNT(*) FROM users` 增加 100 |
|
||||
|
||||
### 2.2 登录统计正确性
|
||||
|
||||
**测试目标**:验证登录成功/失败次数统计的正确性
|
||||
|
||||
| 用例编号 | 用例描述 | 测试步骤 | 预期结果 | 数据库验证 |
|
||||
|---------|---------|---------|---------|-----------|
|
||||
| LOGIN-001 | 登录成功日志记录正确 | 1. 用户成功登录<br>2. 查询登录日志 | 1. 日志存在<br>2. status=1(成功)<br>3. user_id 正确 | `SELECT status, user_id FROM login_logs ORDER BY id DESC LIMIT 1` |
|
||||
| LOGIN-002 | 登录失败日志记录正确 | 1. 用户使用错误密码登录<br>2. 查询登录日志 | 1. 日志存在<br>2. status=0(失败)<br>3. fail_reason 包含原因 | `SELECT status, fail_reason FROM login_logs ORDER BY id DESC LIMIT 1` |
|
||||
| LOGIN-003 | 登录统计今日成功次数正确 | 1. 查询 logins_today_success<br>2. 3 个用户成功登录<br>3. 再次查询 | 第二次比第一次多 3 | `SELECT COUNT(*) FROM login_logs WHERE status=1 AND created_at >= today_start` |
|
||||
| LOGIN-004 | 登录统计今日失败次数正确 | 1. 查询 logins_today_failed<br>2. 2 个用户密码错误登录失败<br>3. 再次查询 | 第二次比第一次多 2 | `SELECT COUNT(*) FROM login_logs WHERE status=0 AND created_at >= today_start` |
|
||||
|
||||
---
|
||||
|
||||
## 3. 设备信任管理正确性测试
|
||||
|
||||
### 3.1 设备信任状态变更
|
||||
|
||||
**测试目标**:验证设备信任/取消信任操作的正确性
|
||||
|
||||
| 用例编号 | 用例描述 | 测试步骤 | 预期结果 | 数据库验证 |
|
||||
|---------|---------|---------|---------|-----------|
|
||||
| DEV-001 | 用户信任当前设备 | 1. 用户登录新设备<br>2. 调用信任设备接口<br>3. 查询设备 | 1. API 返回 200<br>2. is_trusted=true<br>3. trust_expires_at 有值 | `SELECT is_trusted, trust_expires_at FROM devices WHERE id=xxx` |
|
||||
| DEV-002 | 用户取消信任设备 | 1. 设备 is_trusted=true<br>2. 调用取消信任<br>3. 查询设备 | 1. API 返回 200<br>2. is_trusted=false<br>3. trust_expires_at=null | 同上,is_trusted=false |
|
||||
| DEV-003 | 管理员信任任意设备 | 1. 管理员调用 adminTrustDevice<br>2. 查询设备 | is_trusted=true, trust_expires_at 为 30 天后 | 同上 |
|
||||
| DEV-004 | 管理员取消信任任意设备 | 1. 管理员调用 adminUntrustDevice<br>2. 查询设备 | is_trusted=false | 同上 |
|
||||
| DEV-005 | 管理员删除设备 | 1. 管理员调用 adminDeleteDevice<br>2. 查询设备 | 设备不存在 | `SELECT COUNT(*) FROM devices WHERE id=xxx` 返回 0 |
|
||||
| DEV-006 | 信任过期后状态正确 | 1. 设置 trust_expires_at 为过去时间<br>2. 查询设备状态 | is_trusted=false(过期检查逻辑) | `SELECT is_trusted FROM devices WHERE trust_expires_at < now()` |
|
||||
|
||||
### 3.2 设备与用户关联正确性
|
||||
|
||||
| 用例编号 | 用例描述 | 测试步骤 | 预期结果 | 数据库验证 |
|
||||
|---------|---------|---------|---------|-----------|
|
||||
| DEV-007 | 设备正确归属用户 | 1. 用户 A 登录设备<br>2. 用户 B 查看自己的设备 | 1. 设备属于用户 A<br>2. 用户 B 看不到 | devices 表 user_id 字段正确 |
|
||||
| DEV-008 | 管理员查看所有设备 | 1. 管理员调用 listAllDevices<br>2. 查看返回列表 | 包含所有用户的设备 | `SELECT COUNT(*) FROM devices` 与返回 total 一致 |
|
||||
| DEV-009 | 管理员按用户筛选设备 | 1. 设置 user_id_filter=5<br>2. 调用 listAllDevices | 仅返回 user_id=5 的设备 | SQL: `WHERE user_id=5` |
|
||||
|
||||
---
|
||||
|
||||
## 4. 权限与角色正确性测试
|
||||
|
||||
### 4.1 角色分配正确性
|
||||
|
||||
| 用例编号 | 用例描述 | 测试步骤 | 预期结果 | 数据库验证 |
|
||||
|---------|---------|---------|---------|-----------|
|
||||
| ROLE-001 | 分配角色后用户拥有对应权限 | 1. 用户无角色<br>2. 分配 role_id=2<br>3. 验证用户权限 | 用户拥有 role_id=2 的所有权限 | `SELECT permission_id FROM role_permissions WHERE role_id=2` 与用户实际权限对比 |
|
||||
| ROLE-002 | 分配多个角色权限合并 | 1. 分配 role_ids=[2,3]<br>2. 验证用户权限 | 用户拥有 role 2 和 role 3 的所有权限并集 | 权限数量 = role2 权限数 + role3 权限数(去重) |
|
||||
| ROLE-003 | 移除用户角色 | 1. 用户有角色<br>2. 移除角色<br>3. 验证权限减少 | 用户失去被移除角色的权限 | 移除前后的权限数量对比 |
|
||||
| ROLE-004 | 角色状态为禁用时用户无该角色权限 | 1. 角色 status=0(禁用)<br>2. 用户拥有该角色<br>3. 验证权限 | 用户不拥有该角色的任何权限 | 权限检查跳过 status=0 的角色 |
|
||||
|
||||
### 4.2 权限继承正确性
|
||||
|
||||
| 用例编号 | 用例描述 | 测试步骤 | 预期结果 | 数据库验证 |
|
||||
|---------|---------|---------|---------|-----------|
|
||||
| PERM-001 | 子权限继承父权限 | 1. 权限 A 是权限 B 的父级<br>2. 用户拥有 A<br>3. 检查 B 的访问 | 用户同时拥有 A 和 B | 权限树结构验证 |
|
||||
| PERM-002 | 权限树深度遍历正确 | 1. 用户拥有叶节点权限<br>2. 检查所有祖先权限 | 用户拥有完整路径上的所有权限 | 递归查询权限树 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 前端行为与后端数据一致性
|
||||
|
||||
### 5.1 表单提交与数据库
|
||||
|
||||
| 用例编号 | 用例描述 | 测试步骤 | 预期结果 |
|
||||
|---------|---------|---------|---------|
|
||||
| FE-001 | 创建用户表单提交后数据库正确 | 1. 填写用户名、邮箱、密码<br>2. 提交<br>3. 页面刷新后数据存在 | 数据库用户表有一条对应记录 |
|
||||
| FE-002 | 编辑用户信息后数据库同步 | 1. 修改用户昵称<br>2. 保存<br>3. 重新加载页面 | 数据库 nickname 字段已更新 |
|
||||
| FE-003 | 删除用户后列表刷新 | 1. 删除用户<br>2. 页面自动刷新 | 列表中不再显示该用户,数据库中已删除 |
|
||||
| FE-004 | 筛选条件变更后列表正确 | 1. 选择"仅活跃用户"<br>2. 查看列表 | 仅显示 status=1 的用户 |
|
||||
|
||||
### 5.2 批量操作一致性
|
||||
|
||||
| 用例编号 | 用例描述 | 测试步骤 | 预期结果 |
|
||||
|---------|---------|---------|---------|
|
||||
| FE-005 | 批量启用用户后数据库一致 | 1. 选中 10 个用户<br>2. 批量启用<br>3. 刷新页面 | 10 个用户 status=1 |
|
||||
| FE-006 | 批量导入用户后统计正确 | 1. 导入 CSV(50 个用户)<br>2. 查看统计 | total_users 增加 50 |
|
||||
| FE-007 | 批量导出数据完整 | 1. 导出当前用户列表<br>2. 比对记录数 | 导出数量 = 数据库实际数量 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 测试数据准备脚本
|
||||
|
||||
### 6.1 用户统计测试数据准备
|
||||
|
||||
```sql
|
||||
-- 清理测试数据
|
||||
DELETE FROM users WHERE username LIKE 'stat_test_%';
|
||||
|
||||
-- 插入不同状态的用户
|
||||
INSERT INTO users (username, email, password, status, created_at) VALUES
|
||||
('stat_test_active_1', 'active1@test.com', '$2a$10$...', 1, NOW()),
|
||||
('stat_test_active_2', 'active2@test.com', '$2a$10$...', 1, NOW()),
|
||||
('stat_test_locked', 'locked@test.com', '$2a$10$...', 2, NOW()),
|
||||
('stat_test_disabled', 'disabled@test.com', '$2a$10$...', 3, NOW()),
|
||||
('stat_test_inactive', 'inactive@test.com', '$2a$10$...', 0, NOW() - INTERVAL '2 days');
|
||||
```
|
||||
|
||||
### 6.2 登录日志测试数据准备
|
||||
|
||||
```sql
|
||||
-- 清理测试数据
|
||||
DELETE FROM login_logs WHERE user_id IN (SELECT id FROM users WHERE username LIKE 'login_test_%');
|
||||
|
||||
-- 准备测试用户
|
||||
INSERT INTO users (username, email, password, status) VALUES
|
||||
('login_test_user', 'logintest@test.com', '$2a$10$...', 1);
|
||||
|
||||
-- 插入登录日志
|
||||
INSERT INTO login_logs (user_id, login_type, status, ip, created_at) VALUES
|
||||
(currval('users_id_seq'), 1, 1, '192.168.1.1', NOW()),
|
||||
(currval('users_id_seq'), 1, 0, '192.168.1.2', NOW() - INTERVAL '1 hour'),
|
||||
(currval('users_id_seq'), 1, 1, '192.168.1.3', NOW() - INTERVAL '2 hours');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试执行检查清单
|
||||
|
||||
### 7.1 测试前检查
|
||||
|
||||
- [ ] 测试数据库已初始化
|
||||
- [ ] 测试用户已创建(有管理员权限)
|
||||
- [ ] API 服务运行正常
|
||||
- [ ] 前端开发服务器运行正常
|
||||
|
||||
### 7.2 测试后清理
|
||||
|
||||
- [ ] 测试数据已清理
|
||||
- [ ] 无残留测试用户
|
||||
- [ ] 统计数据已恢复
|
||||
|
||||
### 7.3 成功标准
|
||||
|
||||
- [ ] 所有测试用例通过
|
||||
- [ ] 数据库验证全部符合预期
|
||||
- [ ] 前端行为与数据库状态一致
|
||||
- [ ] 统计数据计算误差为 0
|
||||
584
docs/testing/TEST_PLAN_2_SCALE_TESTING.md
Normal file
584
docs/testing/TEST_PLAN_2_SCALE_TESTING.md
Normal file
@@ -0,0 +1,584 @@
|
||||
# 真实数据量性能与压力测试方案
|
||||
|
||||
## 概述
|
||||
|
||||
本文档定义在大规模数据场景下的系统性能与可用性测试方案,模拟 10 万级用户、百万级登录日志、权限树爆炸等极端情况,验证系统在各数据量级别下的功能可用性、性能表现和运维能力。
|
||||
|
||||
---
|
||||
|
||||
## 1. 测试目标与范围
|
||||
|
||||
### 1.1 测试目标
|
||||
|
||||
| 目标 | 指标 | 说明 |
|
||||
|-----|------|-----|
|
||||
| 用户规模支撑 | 100,000 用户 | 系统在 10 万用户规模下功能正常 |
|
||||
| 登录日志规模 | 1,000,000 条 | 百万级日志下的查询、导出性能 |
|
||||
| 权限树规模 | 500+ 权限节点 | 权限树爆炸场景下的加载性能 |
|
||||
| 筛选搜索可用性 | 响应时间 < 2s | 大数据量下筛选搜索不超时 |
|
||||
| 批量操作稳定性 | 1000 条/批 | 批量操作不出现 OOM 或超时 |
|
||||
|
||||
### 1.2 测试范围
|
||||
|
||||
- 用户列表查询与筛选
|
||||
- 登录日志查询与导出
|
||||
- 设备列表查询与管理
|
||||
- 权限树加载与渲染
|
||||
- 仪表盘统计加载
|
||||
- 批量导入/导出操作
|
||||
|
||||
---
|
||||
|
||||
## 2. 测试数据准备
|
||||
|
||||
### 2.1 数据规模规划
|
||||
|
||||
| 数据类型 | 小规模 | 中规模 | 大规模 | 极端规模 |
|
||||
|---------|-------|-------|-------|---------|
|
||||
| 用户数 | 1,000 | 10,000 | 50,000 | 100,000 |
|
||||
| 登录日志 | 10,000 | 100,000 | 500,000 | 1,000,000 |
|
||||
| 设备数 | 2,000 | 20,000 | 100,000 | 200,000 |
|
||||
| 角色数 | 10 | 50 | 100 | 200 |
|
||||
| 权限数 | 50 | 200 | 500 | 1,000 |
|
||||
| 用户角色关联 | 1,500 | 15,000 | 75,000 | 150,000 |
|
||||
| 登录日志保留天数 | 7 天 | 30 天 | 90 天 | 180 天 |
|
||||
|
||||
### 2.2 测试数据生成脚本
|
||||
|
||||
#### 2.2.1 用户数据生成
|
||||
|
||||
```python
|
||||
# scripts/generate_test_users.py
|
||||
import random
|
||||
import string
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
def generate_users(count: int, start_id: int = 1):
|
||||
"""生成测试用户数据"""
|
||||
statuses = [0, 1, 1, 1, 1, 2, 3] # 权重: 14%未激活, 57%活跃, 14%锁定, 14%禁用
|
||||
users = []
|
||||
|
||||
for i in range(count):
|
||||
user_id = start_id + i
|
||||
created_at = datetime.now() - timedelta(
|
||||
days=random.randint(0, 365),
|
||||
hours=random.randint(0, 23)
|
||||
)
|
||||
users.append({
|
||||
'id': user_id,
|
||||
'username': f'testuser_{user_id}',
|
||||
'email': f'testuser_{user_id}@test.com',
|
||||
'phone': f'138{random.randint(10000000, 99999999)}',
|
||||
'password': '$2a$10$dummy_hash_for_test_data',
|
||||
'status': random.choice(statuses),
|
||||
'created_at': created_at.isoformat(),
|
||||
'updated_at': created_at.isoformat(),
|
||||
})
|
||||
|
||||
return users
|
||||
|
||||
# 生成 10 万用户
|
||||
users = generate_users(100000)
|
||||
```
|
||||
|
||||
#### 2.2.2 登录日志数据生成
|
||||
|
||||
```sql
|
||||
-- 使用 PostgreSQL 生成大规模登录日志
|
||||
-- 登录日志生成函数
|
||||
CREATE OR REPLACE FUNCTION generate_login_logs(
|
||||
user_count INT,
|
||||
logs_per_user INT
|
||||
) RETURNS VOID AS $$
|
||||
DECLARE
|
||||
i INT;
|
||||
j INT;
|
||||
user_ids INT[];
|
||||
statuses INT[];
|
||||
login_types INT[];
|
||||
BEGIN
|
||||
-- 准备用户 ID 数组
|
||||
SELECT array_agg(id) INTO user_ids FROM users LIMIT user_count;
|
||||
|
||||
statuses := ARRAY[0, 1, 1, 1, 1, 1, 1, 1, 1, 1]; -- 90% 成功
|
||||
login_types := ARRAY[1, 2, 3, 4];
|
||||
|
||||
FOR i IN 1..logs_per_user LOOP
|
||||
FOR j IN 1..user_count LOOP
|
||||
INSERT INTO login_logs (
|
||||
user_id,
|
||||
login_type,
|
||||
device_id,
|
||||
ip,
|
||||
location,
|
||||
status,
|
||||
fail_reason,
|
||||
created_at
|
||||
) VALUES (
|
||||
user_ids[j],
|
||||
login_types[1 + floor(random() * 4)::int],
|
||||
'device_' || user_ids[j] || '_' || i,
|
||||
'192.168.' || (1 + floor(random() * 255))::int || '.' || (1 + floor(random() * 255))::int,
|
||||
CASE floor(random() * 5)::int
|
||||
WHEN 0 THEN '北京'
|
||||
WHEN 1 THEN '上海'
|
||||
WHEN 2 THEN '深圳'
|
||||
WHEN 3 THEN '杭州'
|
||||
ELSE '广州'
|
||||
END,
|
||||
statuses[1 + floor(random() * 10)::int],
|
||||
CASE
|
||||
WHEN statuses[1 + floor(random() * 10)::int] = 0
|
||||
THEN CASE floor(random() * 3)::int
|
||||
WHEN 0 THEN '密码错误'
|
||||
WHEN 1 THEN '账号已锁定'
|
||||
ELSE '账号已禁用'
|
||||
END
|
||||
ELSE NULL
|
||||
END,
|
||||
NOW() - (random() * 365 || ' days')::interval
|
||||
);
|
||||
END LOOP;
|
||||
|
||||
-- 每 10000 条输出进度
|
||||
IF i % 100 = 0 THEN
|
||||
RAISE NOTICE 'Generated % login cycles for % users', i, user_count;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- 执行生成 100 万条日志(100 用户 x 10000 次登录)
|
||||
-- SELECT generate_login_logs(100, 10000);
|
||||
```
|
||||
|
||||
#### 2.2.3 权限树数据生成
|
||||
|
||||
```sql
|
||||
-- 权限树生成脚本
|
||||
-- 生成 500 个权限节点
|
||||
|
||||
INSERT INTO permissions (name, code, parent_id, sort_order, created_at) VALUES
|
||||
-- 系统管理(父节点)
|
||||
('系统管理', 'system', NULL, 1, NOW()),
|
||||
('系统配置', 'system.config', 1, 1, NOW()),
|
||||
('系统日志', 'system.logs', 1, 2, NOW()),
|
||||
('用户管理', 'system.users', 1, 3, NOW()),
|
||||
('用户列表', 'system.users.list', 4, 1, NOW()),
|
||||
('用户创建', 'system.users.create', 4, 2, NOW()),
|
||||
('用户编辑', 'system.users.edit', 4, 3, NOW()),
|
||||
('用户删除', 'system.users.delete', 4, 4, NOW()),
|
||||
('用户导出', 'system.users.export', 4, 5, NOW()),
|
||||
('角色管理', 'system.roles', 1, 4, NOW()),
|
||||
('角色列表', 'system.roles.list', 10, 1, NOW()),
|
||||
('角色创建', 'system.roles.create', 10, 2, NOW()),
|
||||
('角色编辑', 'system.roles.edit', 10, 3, NOW()),
|
||||
('角色删除', 'system.roles.delete', 10, 4, NOW()),
|
||||
('分配权限', 'system.roles.assign', 10, 5, NOW()),
|
||||
('权限管理', 'system.permissions', 1, 5, NOW()),
|
||||
('设备管理', 'system.devices', 1, 6, NOW()),
|
||||
('设备列表', 'system.devices.list', 16, 1, NOW()),
|
||||
('设备详情', 'system.devices.view', 16, 2, NOW()),
|
||||
('设备删除', 'system.devices.delete', 16, 3, NOW()),
|
||||
('设备信任', 'system.devices.trust', 16, 4, NOW()),
|
||||
('登录日志', 'system.login_logs', 1, 7, NOW()),
|
||||
('登录日志列表', 'system.login_logs.list', 21, 1, NOW()),
|
||||
('登录日志导出', 'system.login_logs.export', 21, 2, NOW()),
|
||||
('操作日志', 'system.operation_logs', 1, 8, NOW()),
|
||||
('操作日志列表', 'system.operation_logs.list', 23, 1, NOW()),
|
||||
('操作日志详情', 'system.operation_logs.view', 23, 2, NOW()),
|
||||
('操作日志导出', 'system.operation_logs.export', 23, 3, NOW()),
|
||||
-- 业务管理
|
||||
('业务管理', 'business', NULL, 2, NOW()),
|
||||
('业务数据', 'business.data', 26, 1, NOW()),
|
||||
('数据查看', 'business.data.view', 27, 1, NOW()),
|
||||
('数据编辑', 'business.data.edit', 27, 2, NOW()),
|
||||
('数据删除', 'business.data.delete', 27, 3, NOW()),
|
||||
('数据导入', 'business.data.import', 27, 4, NOW()),
|
||||
('数据导出', 'business.data.export', 27, 5, NOW()),
|
||||
('报表管理', 'business.reports', 26, 2, NOW()),
|
||||
('报表查看', 'business.reports.view', 30, 1, NOW()),
|
||||
('报表生成', 'business.reports.generate', 30, 2, NOW()),
|
||||
('报表导出', 'business.reports.export', 30, 3, NOW()),
|
||||
-- 扩展权限(模拟权限树爆炸)
|
||||
('扩展功能A', 'ext_a', NULL, 3, NOW()),
|
||||
('扩展功能B', 'ext_b', NULL, 4, NOW()),
|
||||
('扩展功能C', 'ext_c', NULL, 5, NOW());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 测试用例
|
||||
|
||||
### 3.1 用户列表大规模测试
|
||||
|
||||
#### UL-001: 10 万用户分页查询性能
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | UL-001 |
|
||||
| 用例名称 | 10 万用户分页查询性能 |
|
||||
| 测试步骤 | 1. 准备 100,000 用户数据<br>2. 调用 GET /admin/users?page=1&page_size=20<br>3. 记录响应时间<br>4. 翻页到第 5000 页 |
|
||||
| 预期结果 | 1. 首次加载 < 1s<br>2. 任意页加载 < 2s<br>3. 返回数据完整 |
|
||||
| 性能指标 | p95 < 2000ms |
|
||||
| 数据库索引 | user_id (PK), status, created_at |
|
||||
|
||||
#### UL-002: 大数据量关键词搜索
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | UL-002 |
|
||||
| 用例名称 | 10 万用户关键词搜索响应时间 |
|
||||
| 测试步骤 | 1. 准备 100,000 用户数据<br>2. 执行关键词搜索 "testuser_50000"<br>3. 测量响应时间 |
|
||||
| 预期结果 | 1. 搜索响应 < 2s<br>2. 返回结果正确 |
|
||||
| 性能指标 | p95 < 2000ms |
|
||||
|
||||
#### UL-003: 多条件组合筛选
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | UL-003 |
|
||||
| 用例名称 | 多条件组合筛选性能 |
|
||||
| 测试步骤 | 1. 筛选 status=1, role_ids=2, created_from=2024-01-01<br>2. 测量响应时间 |
|
||||
| 预期结果 | 1. 响应 < 2s<br>2. 结果正确 |
|
||||
| 性能指标 | p95 < 2000ms |
|
||||
|
||||
#### UL-004: 用户导出 10 万级数据
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | UL-004 |
|
||||
| 用例名称 | 10 万用户 CSV 导出 |
|
||||
| 测试步骤 | 1. 导出全部 100,000 用户<br>2. 测量导出时间<br>3. 验证导出文件完整性 |
|
||||
| 预期结果 | 1. 导出完成 < 60s<br>2. 文件可正常打开<br>3. 数据量 = 100,000 条 |
|
||||
| 性能指标 | 内存占用 < 512MB, 时间 < 60s |
|
||||
|
||||
---
|
||||
|
||||
### 3.2 登录日志大规模测试
|
||||
|
||||
#### LL-001: 百万登录日志分页查询
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | LL-001 |
|
||||
| 用例名称 | 100 万登录日志分页查询 |
|
||||
| 测试步骤 | 1. 准备 1,000,000 登录日志<br>2. 调用 GET /admin/logs/login?page=1&page_size=50<br>3. 翻到第 10000 页 |
|
||||
| 预期结果 | 1. 首页加载 < 1s<br>2. 任意页加载 < 2s |
|
||||
| 性能指标 | p95 < 2000ms |
|
||||
|
||||
#### LL-002: 百万日志时间范围查询
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | LL-002 |
|
||||
| 用例名称 | 百万日志按时间范围筛选 |
|
||||
| 测试步骤 | 1. 查询最近 30 天日志<br>2. 测量响应时间 |
|
||||
| 预期结果 | 1. 响应 < 3s<br>2. 返回结果数量合理 |
|
||||
| 性能指标 | p95 < 3000ms |
|
||||
|
||||
#### LL-003: 百万日志导出 CSV
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | LL-003 |
|
||||
| 用例名称 | 100 万登录日志 CSV 导出 |
|
||||
| 测试步骤 | 1. 导出 1,000,000 条登录日志<br>2. 测量导出时间<br>3. 验证文件完整性 |
|
||||
| 预期结果 | 1. 导出时间 < 120s<br>2. CSV 文件可正常打开<br>3. 数据量正确 |
|
||||
| 性能指标 | 流式导出,内存占用 < 256MB |
|
||||
|
||||
#### LL-004: 百万日志导出 XLSX
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | LL-004 |
|
||||
| 用例名称 | 100 万登录日志 XLSX 导出 |
|
||||
| 测试步骤 | 1. 导出 1,000,000 条登录日志为 xlsx<br>2. 测量导出时间 |
|
||||
| 预期结果 | 1. 导出完成<br>2. 文件可正常打开 |
|
||||
| 性能指标 | 内存占用 < 1GB(XLSX 单文件限制) |
|
||||
|
||||
---
|
||||
|
||||
### 3.3 设备列表大规模测试
|
||||
|
||||
#### DV-001: 20 万设备分页查询
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | DV-001 |
|
||||
| 用例名称 | 20 万设备分页查询性能 |
|
||||
| 测试步骤 | 1. 准备 200,000 设备数据<br>2. 调用 GET /admin/devices?page=1&page_size=20 |
|
||||
| 预期结果 | 1. 响应 < 2s<br>2. 数据完整 |
|
||||
| 性能指标 | p95 < 2000ms |
|
||||
|
||||
#### DV-002: 设备列表多条件筛选
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | DV-002 |
|
||||
| 用例名称 | 设备列表多条件筛选 |
|
||||
| 测试步骤 | 1. 设置筛选:status=1, is_trusted=true, user_id=100<br>2. 测量响应时间 |
|
||||
| 预期结果 | 1. 响应 < 2s<br>2. 结果正确 |
|
||||
| 性能指标 | p95 < 2000ms |
|
||||
|
||||
---
|
||||
|
||||
### 3.4 权限树大规模测试
|
||||
|
||||
#### PR-001: 500 权限节点加载
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | PR-001 |
|
||||
| 用例名称 | 500 权限节点树加载性能 |
|
||||
| 测试步骤 | 1. 准备 500 个权限节点<br>2. 调用权限树接口<br>3. 测量前端渲染时间 |
|
||||
| 预期结果 | 1. 接口响应 < 500ms<br>2. 前端渲染 < 1s |
|
||||
| 性能指标 | 接口 p95 < 500ms, 前端渲染 < 1000ms |
|
||||
|
||||
#### PR-002: 1000 权限节点树爆炸测试
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | PR-002 |
|
||||
| 用例名称 | 1000 权限节点树爆炸场景 |
|
||||
| 测试步骤 | 1. 准备 1000 个权限节点(5 层深度)<br>2. 前端加载权限树<br>3. 测量性能 |
|
||||
| 预期结果 | 1. 加载不超时<br>2. UI 不卡顿 |
|
||||
| 性能指标 | 内存占用 < 100MB |
|
||||
|
||||
#### PR-003: 角色权限分配大规模场景
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | PR-003 |
|
||||
| 用例名称 | 角色分配 500+ 权限性能 |
|
||||
| 测试步骤 | 1. 选择有 500 个权限的角色<br>2. 勾选全部权限<br>3. 保存分配 |
|
||||
| 预期结果 | 1. 保存成功 < 2s<br>2. 权限正确入库 |
|
||||
| 性能指标 | p95 < 2000ms |
|
||||
|
||||
---
|
||||
|
||||
### 3.5 仪表盘统计性能测试
|
||||
|
||||
#### DS-001: 大数据量仪表盘加载
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | DS-001 |
|
||||
| 用例名称 | 10 万用户仪表盘统计性能 |
|
||||
| 测试步骤 | 1. 准备 100,000 用户<br>2. 打开仪表盘页面<br>3. 测量加载时间 |
|
||||
| 预期结果 | 1. 仪表盘加载 < 3s<br>2. 所有统计数据正确显示 |
|
||||
| 性能指标 | p95 < 3000ms |
|
||||
|
||||
#### DS-002: 仪表盘统计数据准确性
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | DS-002 |
|
||||
| 用例名称 | 大数据量统计准确性验证 |
|
||||
| 测试步骤 | 1. 获取仪表盘统计<br>2. 直接查询数据库验证 |
|
||||
| 预期结果 | API 返回值与数据库 COUNT 完全一致 |
|
||||
| 准确性 | 误差 = 0 |
|
||||
|
||||
---
|
||||
|
||||
### 3.6 批量操作压力测试
|
||||
|
||||
#### BO-001: 批量导入 1000 用户
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | BO-001 |
|
||||
| 用例名称 | CSV 批量导入 1000 用户 |
|
||||
| 测试步骤 | 1. 准备 1000 用户的 CSV 文件<br>2. 执行批量导入<br>3. 测量导入时间 |
|
||||
| 预期结果 | 1. 导入完成 < 30s<br>2. 全部用户创建成功<br>3. 统计数据显示正确 |
|
||||
| 性能指标 | < 30s, 内存 < 512MB |
|
||||
|
||||
#### BO-002: 批量启用/禁用用户
|
||||
|
||||
| 属性 | 内容 |
|
||||
|-----|------|
|
||||
| 用例编号 | BO-002 |
|
||||
| 用例名称 | 批量更新 1000 用户状态 |
|
||||
| 测试步骤 | 1. 选择 1000 个用户<br>2. 执行批量禁用<br>3. 测量响应时间 |
|
||||
| 预期结果 | 1. 操作完成 < 10s<br>2. 所有用户状态更新正确 |
|
||||
| 性能指标 | < 10s |
|
||||
|
||||
---
|
||||
|
||||
## 4. 性能基准与验收标准
|
||||
|
||||
### 4.1 响应时间标准
|
||||
|
||||
| 操作类型 | p50 | p95 | p99 | 最大值 |
|
||||
|---------|-----|-----|-----|-------|
|
||||
| 分页列表查询(20条) | < 200ms | < 1000ms | < 2000ms | < 5000ms |
|
||||
| 关键词搜索 | < 500ms | < 2000ms | < 3000ms | < 5000ms |
|
||||
| 详情查看 | < 100ms | < 500ms | < 1000ms | < 2000ms |
|
||||
| 创建/更新操作 | < 200ms | < 1000ms | < 2000ms | < 5000ms |
|
||||
| 删除操作 | < 200ms | < 500ms | < 1000ms | < 2000ms |
|
||||
| CSV 导出(10万条) | < 30s | < 60s | < 90s | < 120s |
|
||||
| XLSX 导出(10万条) | < 60s | < 120s | < 180s | < 300s |
|
||||
|
||||
### 4.2 资源使用标准
|
||||
|
||||
| 资源 | 正常范围 | 告警阈值 | 严重阈值 |
|
||||
|-----|---------|---------|---------|
|
||||
| API 服务器 CPU | < 50% | 70% | 85% |
|
||||
| API 服务器内存 | < 60% | 75% | 85% |
|
||||
| 数据库 CPU | < 40% | 60% | 80% |
|
||||
| 数据库内存 | < 50% | 70% | 85% |
|
||||
| 数据库连接数 | < 50 | 80 | 100 |
|
||||
| 磁盘 I/O | < 50% | 70% | 85% |
|
||||
|
||||
### 4.3 功能正确性标准
|
||||
|
||||
- [ ] 统计数据显示误差为 0
|
||||
- [ ] 筛选结果与数据库一致
|
||||
- [ ] 分页数据无遗漏无重复
|
||||
- [ ] 导出数据与列表数据一致
|
||||
- [ ] 批量操作无数据丢失
|
||||
|
||||
---
|
||||
|
||||
## 5. 测试执行方案
|
||||
|
||||
### 5.1 测试阶段规划
|
||||
|
||||
| 阶段 | 数据规模 | 测试内容 | 预计时间 |
|
||||
|-----|---------|---------|---------|
|
||||
| Phase 1 | 1,000 用户 | 基础功能验证 | 2h |
|
||||
| Phase 2 | 10,000 用户 | 中等规模性能基线 | 4h |
|
||||
| Phase 3 | 50,000 用户 | 大规模验证 | 8h |
|
||||
| Phase 4 | 100,000 用户 | 极端规模稳定性 | 8h |
|
||||
|
||||
### 5.2 测试环境要求
|
||||
|
||||
| 环境 | 配置 | 说明 |
|
||||
|-----|------|-----|
|
||||
| CPU | 8 核+ | API 和数据库服务器 |
|
||||
| 内存 | 16GB+ | API 和数据库服务器 |
|
||||
| 磁盘 | 100GB+ SSD | 存放测试数据 |
|
||||
| 数据库 | PostgreSQL 14+ | 测试数据库 |
|
||||
| 网络 | 1Gbps+ | 内部网络 |
|
||||
|
||||
### 5.3 测试监控指标
|
||||
|
||||
测试过程中监控以下指标:
|
||||
- API 响应时间分布
|
||||
- 数据库查询时间
|
||||
- 内存使用率
|
||||
- CPU 使用率
|
||||
- 数据库连接数
|
||||
- 错误率
|
||||
|
||||
---
|
||||
|
||||
## 6. 问题记录与复盘
|
||||
|
||||
### 6.1 性能问题分级
|
||||
|
||||
| 级别 | 定义 | 处理方式 |
|
||||
|-----|------|---------|
|
||||
| P0 | 功能不可用 | 立即修复 |
|
||||
| P1 | 性能严重不达标(> 3x) | 2天内修复 |
|
||||
| P2 | 性能轻微不达标(1.5-3x) | 1周内修复 |
|
||||
| P3 | 优化建议 | 规划中处理 |
|
||||
|
||||
### 6.2 复盘内容
|
||||
|
||||
每次大规模测试后记录:
|
||||
- 实际性能数据与预期对比
|
||||
- 发现的问题及处理方式
|
||||
- 优化建议
|
||||
- 下次测试重点
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试脚本工具
|
||||
|
||||
### 7.1 locust 压力测试脚本
|
||||
|
||||
```python
|
||||
# tests/load_test_locust.py
|
||||
from locust import HttpUser, task, between
|
||||
import random
|
||||
|
||||
class AdminUser(HttpUser):
|
||||
wait_time = between(1, 3)
|
||||
|
||||
@task(3)
|
||||
def list_users(self):
|
||||
page = random.randint(1, 100)
|
||||
self.client.get(f"/api/v1/admin/users?page={page}&page_size=20")
|
||||
|
||||
@task(1)
|
||||
def search_users(self):
|
||||
keyword = f"testuser_{random.randint(1, 100000)}"
|
||||
self.client.get(f"/api/v1/admin/users?keyword={keyword}")
|
||||
|
||||
@task(2)
|
||||
def list_login_logs(self):
|
||||
page = random.randint(1, 1000)
|
||||
self.client.get(f"/api/v1/logs/login?page={page}&page_size=50")
|
||||
|
||||
@task(1)
|
||||
def get_dashboard(self):
|
||||
self.client.get("/api/v1/admin/dashboard/stats")
|
||||
```
|
||||
|
||||
### 7.2 k6 性能测试脚本
|
||||
|
||||
```javascript
|
||||
// tests/load_test_k6.js
|
||||
import http from 'k6/http';
|
||||
import { check, sleep } from 'k6';
|
||||
|
||||
export const options = {
|
||||
stages: [
|
||||
{ duration: '2m', target: 100 }, // 预热
|
||||
{ duration: '5m', target: 100 }, // 稳定
|
||||
{ duration: '2m', target: 200 }, // 峰值
|
||||
{ duration: '5m', target: 200 }, // 持续
|
||||
{ duration: '2m', target: 0 }, // 冷却
|
||||
],
|
||||
thresholds: {
|
||||
http_req_duration: ['p(95)<2000'],
|
||||
http_req_failed: ['rate<0.01'],
|
||||
},
|
||||
};
|
||||
|
||||
export default function () {
|
||||
const res = http.get(`${__ENV.BASE_URL}/api/v1/admin/users?page=1&page_size=20`);
|
||||
check(res, {
|
||||
'status was 200': (r) => r.status === 200,
|
||||
'response time < 2s': (r) => r.timings.duration < 2000,
|
||||
});
|
||||
sleep(1);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 验收检查清单
|
||||
|
||||
### 8.1 功能验收
|
||||
|
||||
- [ ] 用户列表 10 万级数据加载正常
|
||||
- [ ] 登录日志 100 万级数据查询正常
|
||||
- [ ] 设备列表 20 万级数据管理正常
|
||||
- [ ] 权限树 500+ 节点加载正常
|
||||
- [ ] 批量导入/导出功能正常
|
||||
|
||||
### 8.2 性能验收
|
||||
|
||||
- [ ] 分页查询 p95 < 2s
|
||||
- [ ] 搜索查询 p95 < 2s
|
||||
- [ ] 10 万用户导出 < 60s
|
||||
- [ ] 100 万日志导出 < 120s
|
||||
- [ ] 仪表盘加载 < 3s
|
||||
|
||||
### 8.3 稳定性验收
|
||||
|
||||
- [ ] 连续压测 30 分钟无内存泄漏
|
||||
- [ ] 错误率 < 0.1%
|
||||
- [ ] 无 OOM 或崩溃
|
||||
Reference in New Issue
Block a user