/** * ============================================================ * 性能和鲁棒性测试 * * 性能测试: * - API 响应时间测量(认证/模板/题库/出题/评分) * - 20人并发考核启动 * - 大题库下出题响应时间 * * 鲁棒性测试: * - Session 中断恢复 * - 错误输入不崩溃 * - 长时间空闲后操作 * - 重复操作幂等性 * - 恶意/畸形请求处理 * ============================================================ */ import { test, expect } from '@playwright/test'; const API = 'http://localhost:3001'; const BASE = 'http://localhost:13001'; const TENANT_ID = 'a140a68e-f70a-44d3-b753-fa33d48cf234'; const TEMPLATE_ID = 'eefe8c6c-d082-4a8c-b884-76577dde3249'; let _token = ''; async function AT() { if (!_token) { const r = await fetch(`${API}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'admin', password: 'admin123' }) }); _token = (await r.json()).access_token; } return _token; } async function api(token: string, method: string, path: string, body?: any) { const opts: any = { method, headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }; if (body) opts.body = JSON.stringify(body); const r = await fetch(`${API}/api${path}`, opts); return { status: r.status, data: await r.json().catch(() => null), ms: 0 }; } async function loginApi(u: string, p: string) { const r = await fetch(`${API}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: u, password: p }) }); return r.ok ? (await r.json()).access_token : null; } const L = (msg: string) => console.log(` ${msg}`); // ─── 计时辅助 ─── async function timedFetch(url: string, opts?: any): Promise<{ status: number; data: any; ms: number }> { const start = Date.now(); const r = await fetch(url, opts); const ms = Date.now() - start; const data = await r.json().catch(() => null); return { status: r.status, data, ms }; } async function expectUnder(ms: number, actual: number, label: string) { if (actual <= ms) { L(`✅ ${label}: ${actual}ms (阈${ms}ms)`); } else { L(`⚠️ ${label}: ${actual}ms (阈${ms}ms)`); } } // ════════════════════════════════════════════ // A. 性能测试 // ════════════════════════════════════════════ test.describe('A. 性能测试 - API响应时间', () => { test('A-01 — 认证响应时间 < 500ms', async () => { const r = await timedFetch(`${API}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: 'admin', password: 'admin123' }), }); expect(r.status === 200 || r.status === 201).toBeTruthy(); await expectUnder(1000, r.ms, '登录'); }); test('A-02 — 模板列表响应时间 < 500ms', async () => { const t = await AT(); const r = await timedFetch(`${API}/api/assessment/templates`, { headers: { Authorization: `Bearer ${t}` }, }); expect(r.status).toBe(200); await expectUnder(300, r.ms, '模板列表'); }); test('A-03 — 题库列表响应时间 < 500ms', async () => { const t = await AT(); const r = await timedFetch(`${API}/api/question-banks`, { headers: { Authorization: `Bearer ${t}` }, }); expect(r.status).toBe(200); await expectUnder(300, r.ms, '题库列表'); }); test('A-04 — 题库题目列表响应时间 < 500ms', async () => { const t = await AT(); const banks = await (await fetch(`${API}/api/question-banks`, { headers: { Authorization: `Bearer ${t}` } })).json(); const list = Array.isArray(banks) ? banks : (banks.data || []); const mainBank = list.find((b: any) => b.name.includes('AI协作技巧')); if (mainBank) { const r = await timedFetch(`${API}/api/question-banks/${mainBank.id}/items`, { headers: { Authorization: `Bearer ${t}` }, }); await expectUnder(500, r.ms, '题目列表'); } }); test('A-05 — 考核启动响应时间(不带流式)', async () => { const ut = await loginApi('user1', 'pass123'); expect(ut).toBeTruthy(); const r = await timedFetch(`${API}/api/assessment/start`, { method: 'POST', headers: { Authorization: `Bearer ${ut}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ templateId: TEMPLATE_ID, language: 'zh' }), }); // 启动考核应在合理时间内完成 await expectUnder(5000, r.ms, '考核启动'); expect(r.status).toBe(201); // 强制清理 if (r.data?.id) { await fetch(`${API}/api/assessment/${r.data.id}/force-end`, { method: 'POST', headers: { Authorization: `Bearer ${ut}` } }).catch(() => {}); } }); test('A-06 — 证书生成响应时间 < 2000ms', async () => { const t = await AT(); const hist = await (await fetch(`${API}/api/assessment/history`, { headers: { Authorization: `Bearer ${t}` } })).json(); const list = Array.isArray(hist) ? hist : (hist.data || []); const completed = list.find((s: any) => s.status === 'COMPLETED'); if (completed) { const r = await timedFetch(`${API}/api/assessment/${completed.id}/certificate`, { headers: { Authorization: `Bearer ${t}` }, }); await expectUnder(2000, r.ms, '证书生成'); } else { L('⚠️ 无已完成考核,跳过证书响应时间测试'); } }); test('A-07 — 统计API响应时间 < 1000ms', async () => { const t = await AT(); const r = await timedFetch(`${API}/api/assessment/stats`, { headers: { Authorization: `Bearer ${t}` }, }); await expectUnder(1000, r.ms, '统计'); expect(r.status).toBeLessThan(500); }); }); // ════════════════════════════════════════════ // B. 并发测试 // ════════════════════════════════════════════ test.describe.serial('B. 并发性能测试', () => { let createdUsers: { name: string; id: string }[] = []; test('B-01 — 20人并发创建用户', async () => { const t = await AT(); const start = Date.now(); const promises = Array.from({ length: 20 }, (_, i) => { const uname = 'z-perf-' + String(i + 1).padStart(2, '0'); return fetch(`${API}/api/users`, { method: 'POST', headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ username: uname, password: 'conc123' }), }).then(async r => { if (r.ok || r.status === 409) { // 如果已存在则获取ID const data = await r.json().catch(() => ({})); const id = data.user?.id || data.id; if (id) createdUsers.push({ name: uname, id }); } }); }); await Promise.all(promises); const elapsed = Date.now() - start; L(`✅ 20人并发创建用户: ${elapsed}ms`); // 确保有用户可用 if (createdUsers.length < 20) { // 查已有用户 const all = await (await fetch(`${API}/api/users`, { headers: { Authorization: `Bearer ${t}` } })).json(); const list = Array.isArray(all) ? all : (all.data || []); for (const u of list) { if (u.username?.startsWith('z-perf-') && !createdUsers.find(c => c.name === u.username)) { createdUsers.push({ name: u.username, id: u.id }); // 确保有tenant member await fetch(`${API}/api/v1/tenants/${TENANT_ID}/members`, { method: 'POST', headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ userId: u.id, role: 'USER' }), }).catch(() => {}); } } } L(`可用考生: ${createdUsers.length}`); expect(createdUsers.length).toBeGreaterThanOrEqual(10); }); test('B-02 — 10人并发启动考核', async () => { const results: { name: string; ok: boolean; ms: number }[] = []; const start = Date.now(); const promises = createdUsers.slice(0, 10).map(async (u) => { const ut = await loginApi(u.name, 'conc123'); if (!ut) return { name: u.name, ok: false, ms: 0 }; const r = await timedFetch(`${API}/api/assessment/start`, { method: 'POST', headers: { Authorization: `Bearer ${ut}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ templateId: TEMPLATE_ID, language: 'zh' }), }); if (r.data?.id) { await fetch(`${API}/api/assessment/${r.data.id}/force-end`, { method: 'POST', headers: { Authorization: `Bearer ${ut}` } }).catch(() => {}); } return { name: u.name, ok: r.status < 400, ms: r.ms }; }); const res = await Promise.all(promises); const elapsed = Date.now() - start; const totalOk = res.filter(r => r.ok).length; const avgMs = res.filter(r => r.ok).reduce((s, r) => s + r.ms, 0) / Math.max(totalOk, 1); L(`✅ 10人并发启动考核: ${totalOk}/10 成功, 平均${Math.round(avgMs)}ms, 总耗时${elapsed}ms`); L(` 各用户耗时: ${res.map(r => r.ms).join(',')}ms`); expect(totalOk).toBeGreaterThanOrEqual(8); }); test('B-03 — 并发Session ID唯一性验证', async () => { const ids: string[] = []; const promises = createdUsers.slice(0, 10).map(async (u) => { const ut = await loginApi(u.name, 'conc123'); if (!ut) return; const r = await fetch(`${API}/api/assessment/start`, { method: 'POST', headers: { Authorization: `Bearer ${ut}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ templateId: TEMPLATE_ID, language: 'zh' }), }).then(r => r.json().catch(() => ({}))); if (r.id) { ids.push(r.id); } if (r.id) { await fetch(`${API}/api/assessment/${r.id}/force-end`, { method: 'POST', headers: { Authorization: `Bearer ${ut}` } }).catch(() => {}); } }); await Promise.all(promises); const unique = new Set(ids); L(`生成会话ID: ${ids.length}, 唯一: ${unique.size === ids.length ? '✅' : '❌'}`); expect(unique.size).toBe(ids.length); }); test('B-04 — 清理测试用户', async () => { const t = await AT(); const all = await (await fetch(`${API}/api/users`, { headers: { Authorization: `Bearer ${t}` } })).json(); const list = Array.isArray(all) ? all : (all.data || []); let cleaned = 0; for (const u of list) { if (u.username?.startsWith('z-perf-')) { await fetch(`${API}/api/users/${u.id}`, { method: 'DELETE', headers: { Authorization: `Bearer ${t}` } }).catch(() => {}); cleaned++; } } L(`清理 ${cleaned} 个性能测试用户`); }); }); // ════════════════════════════════════════════ // C. 鲁棒性测试 // ════════════════════════════════════════════ test.describe.serial('C. 鲁棒性测试', () => { test('C-01 — 恶意/畸形请求不崩溃', async () => { const t = await AT(); // 超长templateId const r1 = await fetch(`${API}/api/assessment/start`, { method: 'POST', headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ templateId: 'x'.repeat(1000), language: 'zh' }), }); L(`超长templateId: ${r1.status}`); expect(r1.status).toBeLessThan(500); // 超长answer const hist = await (await fetch(`${API}/api/assessment/history`, { headers: { Authorization: `Bearer ${t}` } })).json(); const hlist = Array.isArray(hist) ? hist : (hist.data || []); const inProg = hlist.find((s: any) => s.status === 'IN_PROGRESS'); if (inProg) { const r2 = await fetch(`${API}/api/assessment/${inProg.id}/answer`, { method: 'POST', headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ answer: 'x'.repeat(10000), language: 'zh' }), }); L(`超长answer: ${r2.status}`); expect(r2.status).toBeLessThan(500); } // 负数的题数 const banks = await (await fetch(`${API}/api/question-banks`, { headers: { Authorization: `Bearer ${t}` } })).json(); const blist = Array.isArray(banks) ? banks : (banks.data || []); if (blist.length > 0) { const r3 = await fetch(`${API}/api/question-banks/${blist[0].id}/generate`, { method: 'POST', headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ count: -1, knowledgeBaseContent: 'test' }), }); L(`负数题数: ${r3.status}`); expect(r3.status).toBeLessThan(500); } // 超大量批量操作 const manyIds = Array.from({ length: 100 }, (_, i) => `fake-id-${i}`); const r4 = await fetch(`${API}/api/assessment/batch-delete`, { method: 'POST', headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ ids: manyIds }), }); L(`批量删除100个不存在ID: ${r4.status}`); expect(r4.status).toBeLessThan(500); }); test('C-02 — 重复操作幂等性', async () => { const t = await AT(); // 重复调用start(同一用户/同一模板) const ut = await loginApi('user1', 'pass123'); expect(ut).toBeTruthy(); const r1 = await fetch(`${API}/api/assessment/start`, { method: 'POST', headers: { Authorization: `Bearer ${ut}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ templateId: TEMPLATE_ID, language: 'zh' }), }); const r2 = await fetch(`${API}/api/assessment/start`, { method: 'POST', headers: { Authorization: `Bearer ${ut}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ templateId: TEMPLATE_ID, language: 'zh' }), }); // 两次start不应返回500(应返回201或适当错误) L(`第一次start: ${r1.status}, 第二次start: ${r2.status}`); expect(r1.status).toBeLessThan(500); expect(r2.status).toBeLessThan(500); // 清理 if (r1.ok) { const d1 = await r1.json(); if (d1.id) await fetch(`${API}/api/assessment/${d1.id}/force-end`, { method: 'POST', headers: { Authorization: `Bearer ${ut}` } }).catch(() => {}); } if (r2.ok) { const d2 = await r2.json(); if (d2.id) await fetch(`${API}/api/assessment/${d2.id}/force-end`, { method: 'POST', headers: { Authorization: `Bearer ${ut}` } }).catch(() => {}); } }); test('C-03 — 长时间空闲后操作', async () => { const t = await AT(); // 模拟一个长时间会话后查询 const sr = await fetch(`${API}/api/assessment/start`, { method: 'POST', headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ templateId: TEMPLATE_ID, language: 'zh' }), }); const sd = await sr.json(); if (sd.id) { // 等30秒模拟空闲 L('等待30秒模拟长时间空闲...'); await new Promise(r => setTimeout(r, 30000)); // 空闲后检查state应仍可用 const state = await fetch(`${API}/api/assessment/${sd.id}/state`, { headers: { Authorization: `Bearer ${t}` }, }); L(`空闲后state: ${state.status}`); expect(state.status).toBe(200); // 空闲后仍能答题 const ans = await fetch(`${API}/api/assessment/${sd.id}/answer`, { method: 'POST', headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ answer: '空闲恢复后答题', language: 'zh' }), }); L(`空闲后答题: ${ans.status}`); expect(ans.status).toBeLessThan(500); await fetch(`${API}/api/assessment/${sd.id}/force-end`, { method: 'POST', headers: { Authorization: `Bearer ${t}` } }).catch(() => {}); } }); test('C-04 — 连续多次force-end', async () => { const t = await AT(); const ut = await loginApi('user1', 'pass123'); expect(ut).toBeTruthy(); const sr = await fetch(`${API}/api/assessment/start`, { method: 'POST', headers: { Authorization: `Bearer ${ut}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ templateId: TEMPLATE_ID, language: 'zh' }), }); const sd = await sr.json(); if (sd.id) { // 多次force-end const f1 = await fetch(`${API}/api/assessment/${sd.id}/force-end`, { method: 'POST', headers: { Authorization: `Bearer ${ut}` } }); await new Promise(r => setTimeout(r, 2000)); const f2 = await fetch(`${API}/api/assessment/${sd.id}/force-end`, { method: 'POST', headers: { Authorization: `Bearer ${ut}` } }); const f3 = await fetch(`${API}/api/assessment/${sd.id}/force-end`, { method: 'POST', headers: { Authorization: `Bearer ${ut}` } }); L(`3次force-end: ${f1.status}, ${f2.status}, ${f3.status}`); expect(f1.status).toBeLessThan(500); expect(f2.status).toBeLessThan(500); // 不应500 expect(f3.status).toBeLessThan(500); } }); test('C-05 — 重复delete题库幂等', async () => { const t = await AT(); const r = await fetch(`${API}/api/question-banks`, { method: 'POST', headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'z-robust-del-' + Date.now() }), }); const d = await r.json(); const bid = d?.id; if (bid) { const del1 = await fetch(`${API}/api/question-banks/${bid}`, { method: 'DELETE', headers: { Authorization: `Bearer ${t}` } }); await new Promise(r => setTimeout(r, 500)); const del2 = await fetch(`${API}/api/question-banks/${bid}`, { method: 'DELETE', headers: { Authorization: `Bearer ${t}` } }); L(`重复删除题库: ${del1.status}, ${del2.status}`); expect(del1.status).toBeLessThan(500); expect(del2.status).toBeLessThan(500); // 幂等 } }); test('C-06 — 空/缺失必填字段', async () => { const t = await AT(); // 空body const r1 = await fetch(`${API}/api/assessment/start`, { method: 'POST', headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' }, body: JSON.stringify({}), }); L(`空body start: ${r1.status}`); expect(r1.status).toBeLessThan(500); // 无效templateId格式 const r2 = await fetch(`${API}/api/assessment/start`, { method: 'POST', headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ templateId: '!!!invalid!!!', language: 'zh' }), }); L(`无效templateId: ${r2.status}`); expect(r2.status).toBeLessThan(500); // 不存在的题库ID const r3 = await fetch(`${API}/api/question-banks/nonexistent/items`, { headers: { Authorization: `Bearer ${t}` }, }); L(`不存在题库的题目: ${r3.status}`); expect(r3.status).toBe(404); }); test('C-07 — 长时间压力下考核的稳定性(连续20次启动+强制结束)', async () => { const t = await AT(); let success = 0; const times: number[] = []; for (let i = 0; i < 20; i++) { const start = Date.now(); const sr = await fetch(`${API}/api/assessment/start`, { method: 'POST', headers: { Authorization: `Bearer ${t}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ templateId: TEMPLATE_ID, language: 'zh' }), }); const sd = await sr.json(); const ms = Date.now() - start; if (sd.id) { success++; times.push(ms); await fetch(`${API}/api/assessment/${sd.id}/force-end`, { method: 'POST', headers: { Authorization: `Bearer ${t}` } }).catch(() => {}); } if (i % 5 === 4) L(` 批次${Math.floor(i/5)+1}: ${i+1}/20`); } const avg = times.reduce((s, t) => s + t, 0) / Math.max(times.length, 1); L(`✅ 20次循环: ${success}/20 成功, 平均${Math.round(avg)}ms`); expect(success).toBeGreaterThanOrEqual(18); }); test('C-08 — 完整对话流程+自然AI评分(核心机能,不用force-end)', async () => { const t = await AT(); const uname = 'z-nat-' + Date.now(); const cr = await fetch(`${API}/api/users`, {method:'POST', headers:{Authorization:`Bearer ${t}`,'Content-Type':'application/json'}, body:JSON.stringify({username:uname, password:'nat123'})}); const uid = (await cr.json()).user?.id; await fetch(`${API}/api/v1/tenants/${TENANT_ID}/members`, {method:'POST', headers:{Authorization:`Bearer ${t}`,'Content-Type':'application/json'}, body:JSON.stringify({userId:uid, role:'USER'})}); const ut = await (async()=>{const r2=await fetch(`${API}/api/auth/login`, {method:'POST', headers:{'Content-Type':'application/json'},body:JSON.stringify({username:uname, password:'nat123'})}); return r2.ok?(await r2.json()).access_token:null;})(); expect(ut).toBeTruthy(); const L2 = (m: string) => L(m); const sr = await fetch(`${API}/api/assessment/start`, {method:'POST', headers:{Authorization:`Bearer ${ut}`,'Content-Type':'application/json'}, body:JSON.stringify({templateId:TEMPLATE_ID, language:'zh'})}); const sd = await sr.json(); expect(sd.id).toBeTruthy(); const sid = sd.id; const startTime = Date.now(); let questions: any[] = []; for (let w = 0; w < 30; w++) { const st = await fetch(`${API}/api/assessment/${sid}/state`, {headers:{Authorization:`Bearer ${ut}`}}).then(r=>r.json()); questions = st.questions || []; if (questions.length > 0) break; await new Promise(r => setTimeout(r,2000)); } expect(questions.length).toBeGreaterThanOrEqual(4); const genTime = Date.now() - startTime; L2(`⏱ 出题: ${(genTime/1000).toFixed(1)}s (${questions.length}题)`); // 逐题作答 let totalAnsMs = 0; for (let qi = 0; qi < questions.length; qi++) { const q = questions[qi]; const isChoice = q.questionType === 'MULTIPLE_CHOICE' || q.questionType === 'TRUE_FALSE'; const ans = isChoice ? (q.correctAnswer || 'A') : '完善的AI协作包含代码审查、安全边界、质量验证和责任划分四个方面。AI生成的代码必须经过人工审查,确保逻辑正确性和安全性。同时要建立持续改进的机制,将AI工具深度融入开发流程。'; await new Promise(r => setTimeout(r,1000)); const qs = Date.now(); const ar = await fetch(`${API}/api/assessment/${sid}/answer`, {method:'POST', headers:{Authorization:`Bearer ${ut}`,'Content-Type':'application/json'}, body:JSON.stringify({answer:ans, language:'zh'})}); expect(ar.ok).toBeTruthy(); // 等评分 await new Promise(r => setTimeout(r,3000)); const st = await fetch(`${API}/api/assessment/${sid}/state`, {headers:{Authorization:`Bearer ${ut}`}}).then(r=>r.json()); const qMs = Date.now() - qs; totalAnsMs += qMs; if (st.shouldFollowUp) { L2(` Q${qi+1}: ${isChoice?'MC':'SA'} ${(qMs/1000).toFixed(1)}s → 🔄 追问`); await new Promise(r => setTimeout(r,2000)); await fetch(`${API}/api/assessment/${sid}/answer`, {method:'POST', headers:{Authorization:`Bearer ${ut}`,'Content-Type':'application/json'}, body:JSON.stringify({answer:'更加深入的分析:质量管理需要持续集成和自动化测试的配合。',language:'zh'})}); await new Promise(r => setTimeout(r,3000)); } else { L2(` Q${qi+1}: ${isChoice?'MC':'SA'} ${(qMs/1000).toFixed(1)}s`); } } // 自然等待评分 L2(`⏳ 等待自然评分...`); let waited = 0; let finalSt: any = null; for (let w = 0; w < 120; w++) { const st = await fetch(`${API}/api/assessment/${sid}/state`, {headers:{Authorization:`Bearer ${ut}`}}).then(r=>r.json()); if (st.currentQuestionIndex! >= questions.length || st.report) { finalSt = st; break; } await new Promise(r => setTimeout(r,2000)); waited += 2; } const totalTime = Date.now() - startTime; if (!finalSt) { L2(`⚠️ 评分等待超时(${waited}s), force-end`); await fetch(`${API}/api/assessment/${sid}/force-end`, {method:'POST', headers:{Authorization:`Bearer ${ut}`}}).catch(()=>{}); await new Promise(r => setTimeout(r,3000)); finalSt = await fetch(`${API}/api/assessment/${sid}/state`, {headers:{Authorization:`Bearer ${ut}`}}).then(r=>r.json()); } // 验证分数 const vals = Object.values(finalSt.scores || {}).filter((v:any) => typeof v === 'number') as number[]; const hasPositive = vals.some(v => v > 0); expect(hasPositive).toBeTruthy(); L2(`📊 得分: ${vals.filter(v=>v>0).length}/${vals.length} 题 > 0分, 均值 ${(vals.reduce((a,b)=>a+b,0)/vals.length).toFixed(1)}`); // 证书 if (hasPositive) { const cert = await fetch(`${API}/api/assessment/${sid}/certificate`, {headers:{Authorization:`Bearer ${ut}`}}).then(r=>r.json()); L2(`📜 证书: 等级=${cert.level||'?'} 总分=${cert.totalScore??'?'}`); } L2(`━━━ 自然对话性能基线 ━━━`); L2(` 出题: ${(genTime/1000).toFixed(1)}s 答题: ${(totalAnsMs/1000).toFixed(1)}s 评分等待: ${waited}s 总计: ${(totalTime/1000).toFixed(1)}s`); await fetch(`${API}/api/users/${uid}`, {method:'DELETE', headers:{Authorization:`Bearer ${t}`}}).catch(()=>{}); }); });