// 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 ================================================================================ 测试完成 ================================================================================ `, }; }