/** * k6 压测工具函数 * 直播论坛项目 */ import http from 'k6/http'; import { check, sleep } from 'k6'; import { SharedArray } from 'k6/data'; import { BASE_URL, HTTP_TIMEOUT, THINK_TIME } from '../config.js'; // ============================================ // Token 管理 // ============================================ // 使用 SharedArray 加载 Token(所有 VU 共享,节省内存) const tokens = new SharedArray('tokens', function() { // 读取 tokens.txt 文件 const data = open('../tokens.txt'); // 按行分割,过滤空行和注释行 return data.split('\n') .map(line => line.trim()) .filter(line => line && !line.startsWith('#')); }); /** * 获取 Token 总数 */ export function getTokenCount() { return tokens.length; } /** * 根据 VU 编号获取 Token * 如果 VU 数量超过 Token 数量,会循环使用 * @param {number} vuId - VU 编号 (__VU) * @returns {string} Token(已添加 Bearer 前缀) */ export function getToken(vuId) { if (tokens.length === 0) { console.error('警告: tokens.txt 中没有有效的 Token!'); return ''; } const index = (vuId - 1) % tokens.length; const token = tokens[index]; return `Bearer ${token}`; } /** * 获取请求头(带认证) * @param {number} vuId - VU 编号 * @returns {object} HTTP Headers */ export function getHeaders(vuId) { return { 'Content-Type': 'application/json', 'Authorization': getToken(vuId), }; } // ============================================ // HTTP 请求封装 // ============================================ /** * 发送 GET 请求 * @param {string} path - API 路径 * @param {object} params - URL 参数 * @param {number} vuId - VU 编号 * @returns {object} HTTP Response */ export function httpGet(path, params = {}, vuId) { const url = buildUrl(path, params); const response = http.get(url, { headers: getHeaders(vuId), timeout: HTTP_TIMEOUT, tags: { api: path.split('?')[0] }, }); return response; } /** * 发送 POST 请求 * @param {string} path - API 路径 * @param {object} body - 请求体 * @param {number} vuId - VU 编号 * @returns {object} HTTP Response */ export function httpPost(path, body = {}, vuId) { const url = `${BASE_URL}${path}`; const response = http.post(url, JSON.stringify(body), { headers: getHeaders(vuId), timeout: HTTP_TIMEOUT, tags: { api: path }, }); return response; } /** * 构建带参数的 URL * @param {string} path - API 路径 * @param {object} params - URL 参数 * @returns {string} 完整 URL */ function buildUrl(path, params) { let url = `${BASE_URL}${path}`; const queryString = Object.entries(params) .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) .join('&'); if (queryString) { url += (path.includes('?') ? '&' : '?') + queryString; } return url; } // ============================================ // 响应检查 // ============================================ /** * 检查响应是否成功 * @param {object} response - HTTP Response * @param {string} name - 检查名称 * @returns {boolean} 是否成功 */ export function checkResponse(response, name) { const result = check(response, { [`${name} - status is 200`]: (r) => r.status === 200, [`${name} - code is 0`]: (r) => { try { const body = JSON.parse(r.body); return body.code === 0; } catch { return false; } }, }); // 如果失败,打印详细信息(仅在调试时有用) if (!result && response.status !== 200) { console.log(`${name} 失败: status=${response.status}, body=${response.body.substring(0, 200)}`); } return result; } /** * 解析响应体 * @param {object} response - HTTP Response * @returns {object|null} 解析后的数据 */ export function parseResponse(response) { try { const body = JSON.parse(response.body); if (body.code === 0) { return body.data; } return null; } catch { return null; } } // ============================================ // 模拟用户行为 // ============================================ /** * 模拟用户思考时间(随机等待) */ export function thinkTime() { const duration = Math.random() * (THINK_TIME.max - THINK_TIME.min) + THINK_TIME.min; sleep(duration); } /** * 短暂等待(API 调用间隔) */ export function shortWait() { sleep(0.5 + Math.random() * 0.5); } // ============================================ // 数据生成 // ============================================ /** * 生成随机评论内容 * @returns {string} 评论内容 */ export function randomComment() { const comments = [ '压测评论内容_' + Date.now(), '这是一条测试评论_' + Math.random().toString(36).substring(7), '好帖子!' + Date.now(), '支持一下~' + Math.random().toString(36).substring(7), '学习了!' + Date.now(), ]; return comments[Math.floor(Math.random() * comments.length)]; } /** * 生成随机整数 * @param {number} min - 最小值 * @param {number} max - 最大值 * @returns {number} 随机整数 */ export function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } // ============================================ // API 路径常量 // ============================================ export const API = { // 认证 AUTH: { LOGIN: '/api/Auth/WechatMpLogin', REFRESH: '/api/Auth/RefreshToken', }, // 首页 HOME: { BANNERS: '/api/Banners/GetBanners', CATEGORIES: '/api/Home/StreamerCategories', RANKINGS: '/api/Home/Rankings', RANKINGS_MORE: '/api/Home/RankingsMore', LIVE_STREAMERS: '/api/Home/LiveStreamers', }, // 帖子 POSTS: { LIST: '/api/Posts/GetPosts', DETAIL: '/api/Posts/GetPostDetail', PUBLISH: '/api/Posts/PublishPosts', LIKE: '/api/Posts/LikePost', DELETE: '/api/Posts/DeletePosts', MY_POSTS: '/api/Posts/GetMyPosts', }, // 评论 COMMENTS: { LIST: '/api/PostComments/GetPostComments', PUBLISH: '/api/PostComments/PublishPostComments', REPLIES: '/api/PostComments/GetCommentsReplies', LIKE: '/api/PostComments/LikeComment', }, // 送花 FLOWERS: { SEND: '/api/Flowers/SendFlower', }, // 用户 USER: { INFO: '/api/UsersInfo/GetUserInfo', UPDATE: '/api/UsersInfo/UpdateUserInfo', }, // 消息 MESSAGES: { LIST: '/api/Messages/GetMessages', PARTICIPATE: '/api/Messages/ParticipateActivity', }, // 配置 CONFIG: { APP: '/api/Config/GetAppConfig', AGREEMENT: '/api/Config/GetAgreement', }, };