fix: 修复多个关键问题
- 修复前端路由守卫:未登录时不显示提示,直接跳转登录页 - 修复API拦截器:401错误不显示提示,直接跳转 - 增强验证码显示:图片尺寸从120x40增加到200x80 - 增大验证码字体:从28号增加到48号 - 优化验证码字符:排除易混淆的0和1 - 减少干扰线:从5条减少到3条,添加背景色优化 - 增强登录API日志:添加详细的调试日志 - 增强验证码生成和验证日志 - 优化异常处理和错误追踪 影响文件: - src/router/index.ts - src/api/request.ts - app/services/auth_service.py - app/api/v1/auth.py - app/schemas/user.py 测试状态: - 前端构建通过 - 后端语法检查通过 - 验证码显示效果优化完成 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
359
tests/performance/locustfile.py
Normal file
359
tests/performance/locustfile.py
Normal file
@@ -0,0 +1,359 @@
|
||||
"""
|
||||
性能测试 - Locust文件
|
||||
|
||||
测试内容:
|
||||
- 并发用户测试
|
||||
- 接口响应时间
|
||||
- 吞吐量测试
|
||||
- 负载测试
|
||||
- 压力测试
|
||||
"""
|
||||
|
||||
from locust import HttpUser, task, between, events
|
||||
from locust.runners import MasterRunner
|
||||
import time
|
||||
import random
|
||||
|
||||
|
||||
# 测试数据
|
||||
TEST_USERS = [
|
||||
{"username": "admin", "password": "Admin123"},
|
||||
{"username": "user1", "password": "Test123"},
|
||||
{"username": "user2", "password": "Test123"},
|
||||
]
|
||||
|
||||
ASSET_NAMES = ["联想台式机", "戴尔笔记本", "惠普打印机", "苹果显示器", "罗技鼠标"]
|
||||
DEVICE_TYPES = [1, 2, 3, 4, 5]
|
||||
ORGANIZATIONS = [1, 2, 3, 4, 5]
|
||||
|
||||
|
||||
class AssetManagementUser(HttpUser):
|
||||
"""
|
||||
资产管理系统用户模拟
|
||||
|
||||
模拟真实用户的行为模式
|
||||
"""
|
||||
|
||||
# 等待时间: 用户操作之间间隔1-3秒
|
||||
wait_time = between(1, 3)
|
||||
|
||||
def on_start(self):
|
||||
"""用户登录时执行"""
|
||||
self.login()
|
||||
self.token = None
|
||||
self.headers = {}
|
||||
|
||||
def login(self):
|
||||
"""登录获取token"""
|
||||
user = random.choice(TEST_USERS)
|
||||
|
||||
# 先获取验证码
|
||||
captcha_resp = self.client.get("/api/v1/auth/captcha")
|
||||
if captcha_resp.status_code == 200:
|
||||
captcha_data = captcha_resp.json()
|
||||
captcha_key = captcha_data["data"]["captcha_key"]
|
||||
|
||||
# 登录
|
||||
login_resp = self.client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={
|
||||
"username": user["username"],
|
||||
"password": user["password"],
|
||||
"captcha": "1234", # 测试环境固定验证码
|
||||
"captcha_key": captcha_key
|
||||
}
|
||||
)
|
||||
|
||||
if login_resp.status_code == 200:
|
||||
self.token = login_resp.json()["data"]["access_token"]
|
||||
self.headers = {
|
||||
"Authorization": f"Bearer {self.token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
@task(10)
|
||||
def view_asset_list(self):
|
||||
"""查看资产列表 (高频操作)"""
|
||||
self.client.get(
|
||||
"/api/v1/assets",
|
||||
headers=self.headers,
|
||||
params={
|
||||
"page": random.randint(1, 5),
|
||||
"page_size": 20
|
||||
}
|
||||
)
|
||||
|
||||
@task(5)
|
||||
def search_assets(self):
|
||||
"""搜索资产 (中频操作)"""
|
||||
keywords = ["联想", "戴尔", "台式机", "笔记本", "打印机"]
|
||||
keyword = random.choice(keywords)
|
||||
|
||||
self.client.get(
|
||||
"/api/v1/assets",
|
||||
headers=self.headers,
|
||||
params={"keyword": keyword}
|
||||
)
|
||||
|
||||
@task(3)
|
||||
def view_asset_detail(self):
|
||||
"""查看资产详情 (中频操作)"""
|
||||
asset_id = random.randint(1, 100)
|
||||
self.client.get(
|
||||
f"/api/v1/assets/{asset_id}",
|
||||
headers=self.headers
|
||||
)
|
||||
|
||||
@task(2)
|
||||
def view_statistics(self):
|
||||
"""查看统计数据 (低频操作)"""
|
||||
self.client.get(
|
||||
"/api/v1/statistics/overview",
|
||||
headers=self.headers
|
||||
)
|
||||
|
||||
@task(1)
|
||||
def create_asset(self):
|
||||
"""创建资产 (低频操作)"""
|
||||
asset_data = {
|
||||
"asset_name": f"{random.choice(ASSET_NAMES)}-{int(time.time())}",
|
||||
"device_type_id": random.choice(DEVICE_TYPES),
|
||||
"organization_id": random.choice(ORGANIZATIONS),
|
||||
"model": f"测试型号-{int(time.time())}",
|
||||
"serial_number": f"SN-{int(time.time())}",
|
||||
"location": f"测试位置-{random.randint(1, 10)}"
|
||||
}
|
||||
|
||||
self.client.post(
|
||||
"/api/v1/assets",
|
||||
headers=self.headers,
|
||||
json=asset_data
|
||||
)
|
||||
|
||||
@task(1)
|
||||
def filter_assets(self):
|
||||
"""筛选资产 (低频操作)"""
|
||||
statuses = ["in_stock", "in_use", "maintenance", "scrapped"]
|
||||
status = random.choice(statuses)
|
||||
|
||||
self.client.get(
|
||||
"/api/v1/assets",
|
||||
headers=self.headers,
|
||||
params={"status": status}
|
||||
)
|
||||
|
||||
|
||||
class AssetManagementUserRead(AssetManagementUser):
|
||||
"""
|
||||
只读用户
|
||||
只执行查询操作,不执行写操作
|
||||
"""
|
||||
|
||||
@task(10)
|
||||
def view_asset_list(self):
|
||||
"""查看资产列表"""
|
||||
self.client.get(
|
||||
"/api/v1/assets",
|
||||
headers=self.headers,
|
||||
params={"page": random.randint(1, 10), "page_size": 20}
|
||||
)
|
||||
|
||||
@task(5)
|
||||
def view_asset_detail(self):
|
||||
"""查看资产详情"""
|
||||
asset_id = random.randint(1, 100)
|
||||
self.client.get(
|
||||
f"/api/v1/assets/{asset_id}",
|
||||
headers=self.headers
|
||||
)
|
||||
|
||||
@task(3)
|
||||
def search_assets(self):
|
||||
"""搜索资产"""
|
||||
keywords = ["联想", "戴尔", "惠普"]
|
||||
self.client.get(
|
||||
"/api/v1/assets",
|
||||
headers=self.headers,
|
||||
params={"keyword": random.choice(keywords)}
|
||||
)
|
||||
|
||||
@task(2)
|
||||
def view_statistics(self):
|
||||
"""查看统计数据"""
|
||||
self.client.get(
|
||||
"/api/v1/statistics/overview",
|
||||
headers=self.headers
|
||||
)
|
||||
|
||||
|
||||
# 自定义事件处理器
|
||||
@events.request.add_listener
|
||||
def on_request(request_type, name, response_time, response_length, **kwargs):
|
||||
"""
|
||||
请求事件监听器
|
||||
记录慢请求
|
||||
"""
|
||||
if response_time > 1000: # 响应时间超过1秒
|
||||
print(f"慢请求警告: {name} 耗时 {response_time}ms")
|
||||
|
||||
|
||||
@events.test_stop.add_listener
|
||||
def on_test_stop(environment, **kwargs):
|
||||
"""
|
||||
测试结束事件
|
||||
输出测试统计
|
||||
"""
|
||||
if not isinstance(environment.runner, MasterRunner):
|
||||
print("\n" + "="*50)
|
||||
print("性能测试完成")
|
||||
print("="*50)
|
||||
|
||||
stats = environment.stats
|
||||
print(f"\n总请求数: {stats.total.num_requests}")
|
||||
print(f"失败请求数: {stats.total.num_failures}")
|
||||
print(f"平均响应时间: {stats.total.avg_response_time}ms")
|
||||
print(f"中位数响应时间: {stats.total.median_response_time}ms")
|
||||
print(f"95%请求响应时间: {stats.total.get_response_time_percentile(0.95)}ms")
|
||||
print(f"99%请求响应时间: {stats.total.get_response_time_percentile(0.99)}ms")
|
||||
print(f"请求/秒 (RPS): {stats.total.total_rps}")
|
||||
print(f"失败率: {stats.total.fail_ratio * 100:.2f}%")
|
||||
|
||||
# 性能指标评估
|
||||
print("\n性能评估:")
|
||||
avg_response = stats.total.avg_response_time
|
||||
if avg_response < 200:
|
||||
print("✓ 响应时间: 优秀 (< 200ms)")
|
||||
elif avg_response < 500:
|
||||
print("✓ 响应时间: 良好 (< 500ms)")
|
||||
elif avg_response < 1000:
|
||||
print("⚠ 响应时间: 一般 (< 1000ms)")
|
||||
else:
|
||||
print("✗ 响应时间: 差 (> 1000ms)")
|
||||
|
||||
rps = stats.total.total_rps
|
||||
if rps > 100:
|
||||
print("✓ 吞吐量: 优秀 (> 100 RPS)")
|
||||
elif rps > 50:
|
||||
print("✓ 吞吐量: 良好 (> 50 RPS)")
|
||||
elif rps > 20:
|
||||
print("⚠ 吞吐量: 一般 (> 20 RPS)")
|
||||
else:
|
||||
print("✗ 吞吐量: 差 (< 20 RPS)")
|
||||
|
||||
fail_ratio = stats.total.fail_ratio * 100
|
||||
if fail_ratio < 1:
|
||||
print("✓ 失败率: 优秀 (< 1%)")
|
||||
elif fail_ratio < 5:
|
||||
print("✓ 失败率: 良好 (< 5%)")
|
||||
else:
|
||||
print("✗ 失败率: 差 (> 5%)")
|
||||
|
||||
print("="*50 + "\n")
|
||||
|
||||
|
||||
# 性能测试目标
|
||||
PERFORMANCE_TARGETS = {
|
||||
"avg_response_time": 500, # 平均响应时间 < 500ms
|
||||
"p95_response_time": 1000, # 95%响应时间 < 1000ms
|
||||
"rps": 50, # 吞吐量 > 50 RPS
|
||||
"fail_ratio": 0.01 # 失败率 < 1%
|
||||
}
|
||||
|
||||
|
||||
class PerformanceTestRunner:
|
||||
"""
|
||||
性能测试运行器
|
||||
提供不同场景的性能测试
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.scenarios = {
|
||||
"smoke": self.smoke_test,
|
||||
"normal": self.normal_load_test,
|
||||
"stress": self.stress_test,
|
||||
"spike": self.spike_test,
|
||||
"endurance": self.endurance_test
|
||||
}
|
||||
|
||||
def smoke_test(self):
|
||||
"""
|
||||
冒烟测试
|
||||
少量用户,验证系统基本功能
|
||||
"""
|
||||
return {
|
||||
"num_users": 10,
|
||||
"spawn_rate": 2,
|
||||
"run_time": "1m"
|
||||
}
|
||||
|
||||
def normal_load_test(self):
|
||||
"""
|
||||
正常负载测试
|
||||
模拟日常使用情况
|
||||
"""
|
||||
return {
|
||||
"num_users": 50,
|
||||
"spawn_rate": 5,
|
||||
"run_time": "5m"
|
||||
}
|
||||
|
||||
def stress_test(self):
|
||||
"""
|
||||
压力测试
|
||||
逐步增加用户直到系统达到极限
|
||||
"""
|
||||
return {
|
||||
"num_users": 200,
|
||||
"spawn_rate": 10,
|
||||
"run_time": "10m"
|
||||
}
|
||||
|
||||
def spike_test(self):
|
||||
"""
|
||||
尖峰测试
|
||||
突然大量用户访问
|
||||
"""
|
||||
return {
|
||||
"num_users": 500,
|
||||
"spawn_rate": 50,
|
||||
"run_time": "2m"
|
||||
}
|
||||
|
||||
def endurance_test(self):
|
||||
"""
|
||||
耐力测试
|
||||
长时间稳定负载
|
||||
"""
|
||||
return {
|
||||
"num_users": 100,
|
||||
"spawn_rate": 10,
|
||||
"run_time": "30m"
|
||||
}
|
||||
|
||||
|
||||
# 使用说明
|
||||
"""
|
||||
运行性能测试:
|
||||
|
||||
1. 冒烟测试 (10用户, 1分钟):
|
||||
locust -f locustfile.py --headless -u 10 -r 2 -t 1m
|
||||
|
||||
2. 正常负载测试 (50用户, 5分钟):
|
||||
locust -f locustfile.py --headless -u 50 -r 5 -t 5m
|
||||
|
||||
3. 压力测试 (200用户, 10分钟):
|
||||
locust -f locustfile.py --headless -u 200 -r 10 -t 10m
|
||||
|
||||
4. 尖峰测试 (500用户, 2分钟):
|
||||
locust -f locustfile.py --headless -u 500 -r 50 -t 2m
|
||||
|
||||
5. Web界面模式:
|
||||
locust -f locustfile.py --host=http://localhost:8000
|
||||
然后访问 http://localhost:8089
|
||||
|
||||
6. 分布式测试 (Master):
|
||||
locust -f locustfile.py --master --expect-workers=4
|
||||
|
||||
7. 分布式测试 (Worker):
|
||||
locust -f locustfile.py --worker --master-host=<master-ip>
|
||||
"""
|
||||
Reference in New Issue
Block a user