live-forum/server/k6/scenario-single-api.js
2026-03-24 11:27:37 +08:00

257 lines
8.4 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.

/**
* 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(`========================================`);
}