248 lines
6.6 KiB
JavaScript
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
|
||
|
|
|
||
|
|
================================================================================
|
||
|
|
测试完成
|
||
|
|
================================================================================
|
||
|
|
`,
|
||
|
|
};
|
||
|
|
}
|