Files
company-celebration/packages/server/load-test/vote-processor.cjs
empty 5c5d0ad85c feat: 优化100人并发投票支持 + 修复奖项名称硬编码问题
服务端优化:
- Socket.IO 配置优化,支持高并发 WebSocket 连接
- 添加 connectTimeout、perMessageDeflate 等参数

压测脚本:
- 新增 vote-real-scenario.yaml 真实场景压测配置
- 支持 100人瞬间爆发、每人7票、不同投票速度模拟
- 添加随机延迟函数模拟真实用户行为
- 强制 WebSocket 传输避免 HTTP 连接限制

前端修复:
- 修复 PostcardItem、PostcardDisplay 奖项名称硬编码问题
- 组件现在从后端 awards 配置动态获取奖项名称
- 修复 LiveVotingView、AdminControl 传递 awards 数据
- 新增 gala 压测命令到 package.json

测试验证:
- 100人并发压测通过,成功率 100%
- P95 延迟 0.7ms,远低于 500ms 阈值
- 系统可稳定支持 3.3 倍现场负载
2026-01-29 00:09:03 +08:00

205 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Artillery 压力测试处理器
* 用于生成测试数据和自定义逻辑
*
* 注意:使用 CommonJS 格式以兼容 Artillery
*/
// 节目ID列表
const programIds = [
'p1',
'p2',
'p3',
'p4',
'p5',
'p6',
'p7',
];
// 奖项票种列表 (与 shared/constants 一致)
const ticketTypes = [
'creative',
'visual',
'atmosphere',
'performance',
'teamwork',
'popularity',
'potential',
];
// 用户计数器 (确保每个虚拟用户有唯一ID)
let userCounter = 0;
/**
* 生成唯一用户ID
*/
function generateUserId(userContext, events, done) {
userCounter++;
const timestamp = Date.now();
userContext.vars.userId = `stress_user_${timestamp}_${userCounter}`;
userContext.vars.voteIndex = 0; // 初始化投票索引
userContext.vars.usedTickets = []; // 已使用的票种
return done();
}
/**
* 随机选择一个节目
*/
function selectRandomProgram(userContext, events, done) {
const randomIndex = Math.floor(Math.random() * programIds.length);
userContext.vars.selectedProgram = programIds[randomIndex];
return done();
}
/**
* 随机选择一个未使用的票种
* 符合业务逻辑:每种票只能用一次
*/
function selectRandomTicketType(userContext, events, done) {
const usedTickets = userContext.vars.usedTickets || [];
const availableTickets = ticketTypes.filter(t => !usedTickets.includes(t));
if (availableTickets.length === 0) {
// 所有票已用完,重置 (用于压测场景)
userContext.vars.usedTickets = [];
userContext.vars.selectedTicket = ticketTypes[0];
} else {
const randomIndex = Math.floor(Math.random() * availableTickets.length);
userContext.vars.selectedTicket = availableTickets[randomIndex];
userContext.vars.usedTickets.push(availableTickets[randomIndex]);
}
return done();
}
/**
* 顺序选择票种 (确保不重复)
*/
function selectNextTicketType(userContext, events, done) {
const voteIndex = userContext.vars.voteIndex || 0;
userContext.vars.selectedTicket = ticketTypes[voteIndex % ticketTypes.length];
userContext.vars.voteIndex = voteIndex + 1;
return done();
}
/**
* 记录投票结果 (用于调试)
*/
function logVoteResult(userContext, events, done) {
const { userId, selectedProgram, selectedTicket } = userContext.vars;
console.log(`[VOTE] User: ${userId} -> Program: ${selectedProgram} (${selectedTicket})`);
return done();
}
/**
* 生成随机延迟 (模拟真实用户行为)
*/
function randomDelay(userContext, events, done) {
const minDelay = 100; // 最小 100ms
const maxDelay = 500; // 最大 500ms
const delay = Math.floor(Math.random() * (maxDelay - minDelay)) + minDelay;
userContext.vars.thinkTime = delay / 1000; // 转换为秒
return done();
}
/**
* 随机入场延迟 0.1-3秒 (模拟分散入场)
*/
function randomEntryDelay(userContext, events, done) {
const delay = (Math.floor(Math.random() * 30) + 1) / 10; // 0.1 - 3.0 秒
userContext.vars.entryDelay = delay;
return done();
}
/**
* 随机短延迟 0.2-0.8秒 (投票间隔)
*/
function randomVoteDelay(userContext, events, done) {
const delay = (Math.floor(Math.random() * 6) + 2) / 10; // 0.2 - 0.8 秒
userContext.vars.voteDelay = delay;
return done();
}
/**
* 随机入场延迟 0-5秒 (观望用户)
*/
function randomLateEntryDelay(userContext, events, done) {
const delay = Math.floor(Math.random() * 50) / 10; // 0 - 5.0 秒
userContext.vars.entryDelay = delay;
return done();
}
/**
* 顺序选择票种 - 确保每人投完7种不同的票按顺序
* 对应7个奖项creative, visual, atmosphere, performance, teamwork, popularity, potential
*/
function selectSequentialTicket(userContext, events, done) {
const voteIndex = userContext.vars.voteIndex || 0;
// 按顺序取票种确保7票投给不同奖项
userContext.vars.selectedTicket = ticketTypes[voteIndex % ticketTypes.length];
userContext.vars.voteIndex = voteIndex + 1;
return done();
}
/**
* 正常投票延迟 1-2秒正常投票者
*/
function randomNormalDelay(userContext, events, done) {
const delay = (Math.floor(Math.random() * 10) + 10) / 10; // 1.0 - 2.0 秒
userContext.vars.normalDelay = delay;
return done();
}
/**
* 长延迟 5-10秒观望型用户先不投
*/
function randomLongDelay(userContext, events, done) {
const delay = (Math.floor(Math.random() * 50) + 50) / 10; // 5.0 - 10.0 秒
userContext.vars.longDelay = delay;
return done();
}
/**
* 慢速投票延迟 3-5秒犹豫不决型
*/
function randomSlowDelay(userContext, events, done) {
const delay = (Math.floor(Math.random() * 20) + 30) / 10; // 3.0 - 5.0 秒
userContext.vars.slowDelay = delay;
return done();
}
/**
* 在连接前打印信息 (调试用)
*/
function beforeConnect(requestParams, context, ee, next) {
console.log(`[CONNECT] Connecting user: ${context.vars.userId}`);
return next();
}
/**
* 处理响应 (调试用)
*/
function afterResponse(requestParams, response, context, ee, next) {
if (response.body && response.body.error) {
console.log(`[ERROR] ${context.vars.userId}: ${response.body.error}`);
}
return next();
}
// CommonJS exports
exports.generateUserId = generateUserId;
exports.selectRandomProgram = selectRandomProgram;
exports.selectRandomTicketType = selectRandomTicketType;
exports.selectNextTicketType = selectNextTicketType;
exports.selectSequentialTicket = selectSequentialTicket;
exports.logVoteResult = logVoteResult;
exports.randomDelay = randomDelay;
exports.randomEntryDelay = randomEntryDelay;
exports.randomVoteDelay = randomVoteDelay;
exports.randomLateEntryDelay = randomLateEntryDelay;
exports.randomNormalDelay = randomNormalDelay;
exports.randomLongDelay = randomLongDelay;
exports.randomSlowDelay = randomSlowDelay;
exports.beforeConnect = beforeConnect;
exports.afterResponse = afterResponse;