Files
Developer 349d783fd1 refactor: clean up project structure
- Remove old review reports (keep latest only)
- Move docs/ to deploy/docs-backup/
- Move performance-testing/ to deploy/
- Clean up test output files
- Organize root directory
2026-04-06 23:36:03 +08:00

248 lines
6.6 KiB
JavaScript

// Sub2API Admin API Performance Test
// 管理后台 API 性能测试
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';
import { config, getBaseUrl, getAdminCredentials } from '../common/utils.js';
import { httpGet, httpPost, randomSleep } from '../common/utils.js';
// ============= 自定义指标 =============
const adminUserListDuration = new Trend('admin_user_list_duration');
const adminGroupStatsDuration = new Trend('admin_group_stats_duration');
const adminDashboardDuration = new Trend('admin_dashboard_duration');
const adminErrorRate = new Rate('admin_errors');
// ============= 测试配置 =============
export const options = {
scenarios: {
admin_load: {
executor: 'ramping-vus',
startVUs: 2,
stages: [
{ duration: '1m', target: 5 },
{ duration: '3m', target: 20 },
{ duration: '2m', target: 0 },
],
},
},
thresholds: {
'admin_user_list_duration': ['p(95)<1000', 'p(99)<2000'],
'admin_group_stats_duration': ['p(95)<1500', 'p(99)<3000'],
'admin_errors': ['rate<0.02'],
},
};
// ============= 辅助函数 =============
function getAdminHeaders() {
return {
'Authorization': `Bearer ${getAdminToken()}`,
'Content-Type': 'application/json',
};
}
let adminToken = null;
let tokenExpiry = 0;
function getAdminToken() {
const now = Date.now();
if (adminToken && now < tokenExpiry) {
return adminToken;
}
const credentials = getAdminCredentials();
const res = http.post(
`${getBaseUrl()}/api/v1/auth/login`,
JSON.stringify({
email: credentials.email,
password: credentials.password,
}),
{
headers: { 'Content-Type': 'application/json' },
tags: { name: 'admin_login' },
}
);
if (res.status === 200) {
adminToken = res.json('token');
tokenExpiry = now + (55 * 60 * 1000);
return adminToken;
}
throw new Error('Admin login failed');
}
// ============= 测试场景 =============
/**
* 测试用户列表
*/
function testUserList() {
const res = http.get(`${getBaseUrl()}/api/v1/admin/users?page=1&limit=20`, {
headers: getAdminHeaders(),
tags: { name: 'admin_user_list' },
});
adminUserListDuration.add(res.timings.duration);
adminErrorRate.add(res.status !== 200);
check(res, {
'User List: status is 200': (r) => r.status === 200,
'User List: has data': (r) => r.json('data') !== undefined,
});
return res;
}
/**
* 测试分组统计
*/
function testGroupStats(groupId = 1) {
const res = http.get(`${getBaseUrl()}/api/v1/admin/groups/${groupId}/stats`, {
headers: getAdminHeaders(),
tags: { name: 'admin_group_stats' },
});
adminGroupStatsDuration.add(res.timings.duration);
adminErrorRate.add(res.status !== 200);
check(res, {
'Group Stats: status is 200': (r) => r.status === 200,
'Group Stats: has stats': (r) => r.json() !== undefined,
});
return res;
}
/**
* 测试仪表板
*/
function testDashboard() {
const res = http.get(`${getBaseUrl()}/api/v1/admin/dashboard`, {
headers: getAdminHeaders(),
tags: { name: 'admin_dashboard' },
});
adminDashboardDuration.add(res.timings.duration);
adminErrorRate.add(res.status !== 200);
check(res, {
'Dashboard: status is 200': (r) => r.status === 200,
});
return res;
}
/**
* 测试余额调整
*/
function testBalanceAdjustment() {
// 首先获取一个用户 ID
const userListRes = testUserList();
if (userListRes.status !== 200 || !userListRes.json('data')?.length) {
return;
}
const userId = userListRes.json('data')[0].id;
const res = http.post(
`${getBaseUrl()}/api/v1/admin/users/${userId}/balance`,
JSON.stringify({
balance: 10.00,
operation: 'add',
notes: 'Performance test adjustment',
}),
{
headers: getAdminHeaders(),
tags: { name: 'admin_balance_adjust' },
}
);
check(res, {
'Balance Adjust: status is 200': (r) => r.status === 200,
});
adminErrorRate.add(res.status !== 200);
}
// ============= 主测试函数 =============
export default function () {
group('Admin API', () => {
const mode = __VU % 5;
switch (mode) {
case 0:
testDashboard();
break;
case 1:
testUserList();
randomSleep(0.1, 0.5);
testUserList();
break;
case 2:
testGroupStats(1);
testGroupStats(2);
break;
case 3:
testBalanceAdjustment();
break;
case 4:
// 综合测试
testDashboard();
testUserList();
testGroupStats(1);
break;
}
});
randomSleep(1, 3);
}
export function handleSummary(data) {
return {
'admin-performance-report.json': JSON.stringify(data, null, 2),
'admin-performance-summary.txt': `
================================================================================
Sub2API Admin API 性能测试报告
================================================================================
测试时间: ${new Date().toISOString()}
测试持续: ${(data.state.testRunDurationMs / 1000 / 60).toFixed(2)} 分钟
--------------------------------------------------------------------------------
核心指标
--------------------------------------------------------------------------------
总请求数: ${data.metrics?.requests_total?.values?.count || 0}
错误率: ${((data.metrics?.admin_errors?.values?.rate || 0) * 100).toFixed(2)}%
--------------------------------------------------------------------------------
操作类型性能
--------------------------------------------------------------------------------
仪表板:
平均响应: ${data.metrics?.admin_dashboard_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${data.metrics?.admin_dashboard_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
用户列表:
平均响应: ${data.metrics?.admin_user_list_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${data.metrics?.admin_user_list_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
P99 响应: ${data.metrics?.admin_user_list_duration?.values?.['p(99)']?.toFixed(2) || 0} ms
分组统计:
平均响应: ${data.metrics?.admin_group_stats_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${data.metrics?.admin_group_stats_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
P99 响应: ${data.metrics?.admin_group_stats_duration?.values?.['p(99)']?.toFixed(2) || 0} ms
================================================================================
测试完成
================================================================================
`,
};
}