/** * k6 压测脚本 - 场景三:单接口极限压测 * * 用于测试单个接口的极限 QPS/TPS * 通过环境变量 API_NAME 指定要测试的接口 * * 支持的 API_NAME 值: * - GetPosts : 帖子列表(默认) * - GetPostDetail : 帖子详情 * - GetPostComments : 评论列表 * - LikePost : 帖子点赞 * - Rankings : 主播榜单 * - LiveStreamers : 直播列表 * - GetUserInfo : 用户信息 * * 运行命令: * k6 run --out web-dashboard -e API_NAME=GetPosts scenario-single-api.js * k6 run --out web-dashboard -e API_NAME=GetPostDetail -e POST_ID=1 scenario-single-api.js * * 快速测试: * k6 run --out web-dashboard -e QUICK_TEST=true -e API_NAME=GetPosts scenario-single-api.js */ import { SINGLE_API_OPTIONS, QUICK_TEST_OPTIONS, TEST_DATA } from './config.js'; import { httpGet, httpPost, checkResponse, parseResponse, randomInt, getTokenCount, API } from './utils/helpers.js'; // ============================================ // 压测配置 // ============================================ const isQuickTest = __ENV.QUICK_TEST === 'true'; const apiName = __ENV.API_NAME || 'GetPosts'; // 可选的帖子ID(用于需要帖子ID的接口) const envPostId = __ENV.POST_ID ? parseInt(__ENV.POST_ID) : null; export const options = isQuickTest ? QUICK_TEST_OPTIONS : SINGLE_API_OPTIONS; // ============================================ // 全局变量(setup 阶段获取) // ============================================ let cachedPostIds = []; // ============================================ // 初始化阶段 // ============================================ export function setup() { const tokenCount = getTokenCount(); console.log(`========================================`); console.log(`场景:单接口极限压测`); console.log(`目标接口:${apiName}`); console.log(`Token 数量:${tokenCount}`); console.log(`模式:${isQuickTest ? '快速测试' : '正式压测'}`); console.log(`========================================`); if (tokenCount === 0) { throw new Error('没有可用的 Token,请先在 tokens.txt 中添加 Token'); } // 预先获取帖子ID列表(用于需要帖子ID的接口) let postIds = []; if (['GetPostDetail', 'GetPostComments', 'LikePost'].includes(apiName)) { if (envPostId) { postIds = [envPostId]; console.log(`使用指定的帖子ID: ${envPostId}`); } else { // 从帖子列表获取 console.log(`正在获取帖子ID列表...`); const res = httpGet(API.POSTS.LIST, { SortType: 1, PageIndex: 1, PageSize: 50, // 获取更多帖子以分散请求 }, 1); const data = parseResponse(res); if (data && data.items && data.items.length > 0) { postIds = data.items.map(item => item.postId); console.log(`获取到 ${postIds.length} 个帖子ID`); } else { throw new Error('无法获取帖子列表,请确保数据库中有帖子数据'); } } } return { tokenCount, postIds }; } // ============================================ // 主测试函数 // ============================================ export default function(data) { const vuId = __VU; const iteration = __ITER; const postIds = data.postIds || []; let res; switch (apiName) { // ================== // 读接口(GET) // ================== case 'GetPosts': // 帖子列表 - 随机分页 res = httpGet(API.POSTS.LIST, { SortType: 1, PageIndex: randomInt(1, 10), PageSize: TEST_DATA.pageSize, }, vuId); checkResponse(res, 'GetPosts'); break; case 'GetPostDetail': // 帖子详情 if (postIds.length > 0) { const postId = postIds[(vuId + iteration) % postIds.length]; res = httpGet(API.POSTS.DETAIL, { PostId: postId, }, vuId); checkResponse(res, 'GetPostDetail'); } break; case 'GetPostComments': // 评论列表 if (postIds.length > 0) { const postId = postIds[(vuId + iteration) % postIds.length]; res = httpGet(API.COMMENTS.LIST, { PostId: postId, SortType: 2, PageIndex: randomInt(1, 5), PageSize: TEST_DATA.commentPageSize, }, vuId); checkResponse(res, 'GetPostComments'); } break; case 'Rankings': // 主播榜单 res = httpGet(API.HOME.RANKINGS, { category: TEST_DATA.category, limit: TEST_DATA.rankingLimit, }, vuId); checkResponse(res, 'Rankings'); break; case 'RankingsMore': // 更多排行 res = httpGet(API.HOME.RANKINGS_MORE, { category: TEST_DATA.category, page: randomInt(1, 5), pageSize: 20, }, vuId); checkResponse(res, 'RankingsMore'); break; case 'LiveStreamers': // 直播列表 res = httpGet(API.HOME.LIVE_STREAMERS, { page: randomInt(1, 5), pageSize: 10, }, vuId); checkResponse(res, 'LiveStreamers'); break; case 'GetUserInfo': // 用户信息 res = httpGet(API.USER.INFO, {}, vuId); checkResponse(res, 'GetUserInfo'); break; case 'Banners': // 轮播图 res = httpGet(API.HOME.BANNERS, {}, vuId); checkResponse(res, 'Banners'); break; case 'GetMessages': // 消息列表 res = httpGet(API.MESSAGES.LIST, { MessageType: 3, PageIndex: 1, PageSize: 10, }, vuId); checkResponse(res, 'GetMessages'); break; // ================== // 写接口(POST) // ================== case 'LikePost': // 帖子点赞 if (postIds.length > 0) { const postId = postIds[(vuId + iteration) % postIds.length]; // 交替点赞/取消,避免状态冲突 const action = (iteration % 2 === 0) ? 1 : 2; res = httpPost(API.POSTS.LIKE, { postId: postId, action: action, }, vuId); checkResponse(res, 'LikePost'); } break; case 'LikeComment': // 评论点赞(需要先获取评论ID) if (postIds.length > 0) { const postId = postIds[(vuId + iteration) % postIds.length]; // 先获取评论 const commentsRes = httpGet(API.COMMENTS.LIST, { PostId: postId, SortType: 2, PageIndex: 1, PageSize: 10, }, vuId); const commentsData = parseResponse(commentsRes); if (commentsData && commentsData.items && commentsData.items.length > 0) { const commentId = commentsData.items[0].commentId; const action = (iteration % 2 === 0) ? 1 : 2; res = httpPost(API.COMMENTS.LIKE, { commentId: commentId, action: action, }, vuId); checkResponse(res, 'LikeComment'); } } break; default: console.log(`未知的 API_NAME: ${apiName}`); break; } } // ============================================ // 结束阶段 // ============================================ export function teardown(data) { console.log(`========================================`); console.log(`单接口压测结束`); console.log(`接口:${apiName}`); console.log(`========================================`); }