1
0
mirror of https://github.com/ZeroCatDev/ClassworksKV.git synced 2025-07-01 20:09:23 +00:00

Integrate OpenTelemetry instrumentation into app.js, add global and method-based rate limiting middleware, and enhance CORS configuration. Update package.json to include new dependencies for OpenTelemetry and rate limiting tests.

This commit is contained in:
SunWuyuan 2025-05-11 11:30:26 +08:00
parent 1b05d73405
commit 6897a082e7
No known key found for this signature in database
GPG Key ID: A6A54CF66F56BB64
10 changed files with 2745 additions and 6 deletions

22
app.js
View File

@ -1,3 +1,4 @@
import "./instrumentation.js";
// import createError from "http-errors";
import express from "express";
import { join, dirname } from "path";
@ -8,6 +9,11 @@ import bodyParser from "body-parser";
import errorHandler from "./middleware/errorHandler.js";
import errors from "./utils/errors.js";
import { initReadme, getReadmeValue } from "./utils/siteinfo.js";
import {
globalLimiter,
apiLimiter,
methodBasedRateLimiter,
} from "./middleware/rateLimiter.js";
import kvRouter from "./routes/kv.js";
@ -15,7 +21,12 @@ var app = express();
import cors from "cors";
app.options("*", cors());
app.use(cors());
app.use(
cors({
exposedHeaders: ["ratelimit-policy", "retry-after", "ratelimit"], // 告诉浏览器这些响应头可以暴露
})
);
app.disable("x-powered-by");
// 获取当前文件的目录路径
const __filename = fileURLToPath(import.meta.url);
@ -24,6 +35,9 @@ const __dirname = dirname(__filename);
// 初始化 readme
initReadme();
// 应用全局限速
app.use(globalLimiter);
// view engine setup
app.set("views", join(__dirname, "views"));
app.set("view engine", "ejs");
@ -60,7 +74,7 @@ app.use((req, res, next) => {
app.get("/", (req, res) => {
res.render("index.ejs", { readmeValue: getReadmeValue() });
});
app.get("/check", (req, res) => {
app.get("/check", apiLimiter, (req, res) => {
res.json({
status: "success",
message: "API is running",
@ -68,8 +82,8 @@ app.get("/check", (req, res) => {
});
});
// Mount the KV store router
app.use("/", kvRouter);
// Mount the KV store router with method-based rate limiting
app.use("/", methodBasedRateLimiter, kvRouter);
// 兜底404路由 - 处理所有未匹配的路由
app.use((req, res, next) => {

42
instrumentation.js Normal file
View File

@ -0,0 +1,42 @@
import "dotenv/config";
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
if (process.env.AXIOM_TOKEN && process.env.AXIOM_DATASET) {
// Initialize OTLP trace exporter with the endpoint URL and headers
// Initialize OTLP trace exporter with the endpoint URL and headers
const traceExporter = new OTLPTraceExporter({
url: "https://api.axiom.co/v1/traces",
headers: {
Authorization: `Bearer ${process.env.AXIOM_TOKEN}`,
"X-Axiom-Dataset": process.env.AXIOM_DATASET,
},
});
const resourceAttributes = {
[SemanticResourceAttributes.SERVICE_NAME]: "node traces",
};
const resource = resourceFromAttributes(resourceAttributes);
// Configuring the OpenTelemetry Node SDK
const sdk = new NodeSDK({
// Adding a BatchSpanProcessor to batch and send traces
spanProcessor: new BatchSpanProcessor(traceExporter),
// Registering the resource to the SDK
resource: resource,
// Adding auto-instrumentations to automatically collect trace data
instrumentations: [getNodeAutoInstrumentations()],
});
console.log("✅成功加载 Axiom 遥测");
// Starting the OpenTelemetry SDK to begin collecting telemetry data
sdk.start();
} else {
console.log("❌未设置 Axiom 遥测");
}

93
middleware/rateLimiter.js Normal file
View File

@ -0,0 +1,93 @@
import rateLimit from "express-rate-limit";
// 获取客户端真实IP的函数
export const getClientIp = (req) => {
return (
req.headers["x-forwarded-for"] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket?.remoteAddress ||
"0.0.0.0"
);
};
// 配置全局限速中间件
export const globalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
limit: 200, // 每个IP在windowMs时间内最多允许200个请求
standardHeaders: "draft-7", // 返回标准的RateLimit头信息
legacyHeaders: false, // 禁用X-RateLimit-*头
message: "请求过于频繁,请稍后再试",
keyGenerator: getClientIp, // 使用真实IP作为限速键
skipSuccessfulRequests: false, // 成功的请求也计入限制
skipFailedRequests: false, // 失败的请求也计入限制
});
// API限速器
export const apiLimiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1分钟
limit: 20, // 每个IP在windowMs时间内最多允许20个请求
standardHeaders: "draft-7",
legacyHeaders: false,
message: "API请求过于频繁请稍后再试",
keyGenerator: getClientIp,
skipSuccessfulRequests: false,
skipFailedRequests: false,
});
// 写操作限速器(更严格)
export const writeLimiter = rateLimit({
windowMs: 1 * 60 * 1000, // 1分钟
limit: 10, // 每个IP在windowMs时间内最多允许10个写操作
standardHeaders: "draft-7",
legacyHeaders: false,
message: "写操作请求过于频繁,请稍后再试",
keyGenerator: getClientIp,
skipSuccessfulRequests: false,
skipFailedRequests: false,
});
// 删除操作限速器(最严格)
export const deleteLimiter = rateLimit({
windowMs: 1 * 60 * 1000, // 5分钟
limit: 1, // 每个IP在windowMs时间内最多允许5个删除操作
standardHeaders: "draft-7",
legacyHeaders: false,
message: "删除操作请求过于频繁,请稍后再试",
keyGenerator: getClientIp,
skipSuccessfulRequests: false,
skipFailedRequests: false,
});
// 认证相关路由限速器(防止暴力破解)
export const authLimiter = rateLimit({
windowMs: 30 * 60 * 1000, // 30分钟
limit: 5, // 每个IP在windowMs时间内最多允许5次认证尝试
standardHeaders: "draft-7",
legacyHeaders: false,
message: "认证请求过于频繁请30分钟后再试",
keyGenerator: getClientIp,
skipSuccessfulRequests: true, // 成功的认证不计入限制
skipFailedRequests: false, // 失败的认证计入限制
});
// 创建一个路由处理中间件根据HTTP方法应用不同的限速器
export const methodBasedRateLimiter = (req, res, next) => {
// 根据HTTP方法应用不同限速
if (req.method === "GET") {
// 读操作使用普通API限速
return apiLimiter(req, res, next);
} else if (
req.method === "POST" ||
req.method === "PUT" ||
req.method === "PATCH"
) {
// 写操作使用更严格的限速
return writeLimiter(req, res, next);
} else if (req.method === "DELETE") {
// 删除操作使用最严格的限速
return deleteLimiter(req, res, next);
}
// 其他方法使用API限速
return apiLimiter(req, res, next);
};

View File

@ -7,11 +7,22 @@
"prisma": "prisma generate",
"prisma:pull": "prisma db pull",
"dev": "NODE_ENV=development nodemon node .bin/www",
"setup": "node ./scripts/setup.js"
"setup": "node ./scripts/setup.js",
"test:rate-limit": "node ./scripts/test-rate-limit.js",
"test:stress": "node ./scripts/stress-test.js",
"test:distributed": "node ./scripts/distributed-test.js",
"test:all-limits": "node ./scripts/run-all-tests.js"
},
"type": "module",
"dependencies": {
"@opentelemetry/auto-instrumentations-node": "^0.58.1",
"@opentelemetry/exporter-trace-otlp-proto": "^0.200.0",
"@opentelemetry/resources": "^2.0.0",
"@opentelemetry/sdk-node": "^0.200.0",
"@opentelemetry/sdk-trace-base": "^2.0.0",
"@opentelemetry/semantic-conventions": "^1.33.0",
"@prisma/client": "6.7.0",
"axios": "^1.9.0",
"body-parser": "^1.20.3",
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",
@ -19,8 +30,8 @@
"dotenv": "^16.5.0",
"ejs": "^3.1.10",
"express": "~4.16.1",
"express-rate-limit": "^7.5.0",
"http-errors": "~1.6.3",
"jade": "~1.11.0",
"morgan": "~1.9.1",
"uuid": "^11.1.0"
},

1854
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

75
scripts/README.md Normal file
View File

@ -0,0 +1,75 @@
# 接口限速测试脚本
这个目录包含了用于测试API接口限速功能的脚本。
## 前置条件
在运行测试脚本之前,请确保安装了所需的依赖:
```bash
npm install axios
```
## 可用测试脚本
### 1. 功能测试 (test-rate-limit.js)
测试不同类型的限速功能是否正常工作包括全局限速、API限速、写操作限速和删除操作限速。
```bash
npm run test:rate-limit
# 或直接运行
node scripts/test-rate-limit.js
```
### 2. 压力测试 (stress-test.js)
对指定端点进行高并发请求,测试限速在高负载下的表现。
```bash
npm run test:stress
# 或直接运行
node scripts/stress-test.js
```
### 3. 分布式测试 (distributed-test.js)
模拟多个不同IP地址的请求测试基于IP的限速是否有效。
```bash
npm run test:distributed
# 或直接运行
node scripts/distributed-test.js
```
### 4. 运行所有测试 (run-all-tests.js)
按顺序运行所有测试,并在测试之间添加适当的延迟以重置限速计数器。
```bash
npm run test:all-limits
# 或直接运行
node scripts/run-all-tests.js
```
## 配置测试参数
每个测试脚本的开头都有配置参数,可以根据需要进行调整:
- `BASE_URL`: API服务器的基础URL默认为 http://localhost:3000
- `CONCURRENT_REQUESTS`: 并发请求数(仅适用于压力测试)
- `TOTAL_REQUESTS`: 总请求数
- `SIMULATED_IPS`: 模拟的IP数量仅适用于分布式测试
- `REQUESTS_PER_IP`: 每个IP的请求数仅适用于分布式测试
- `TEST_ENDPOINT`: 测试的API端点
## 测试结果说明
测试脚本会输出彩色的测试进度和结果统计信息:
- 绿色点(`.`): 成功的请求
- 黄色L(`L`): 被限速的请求
- 红色E(`E`): 错误的请求
- 红色X(`X`): 请求异常
测试完成后,会显示总体统计信息,包括总请求数、成功请求数、被限速请求数、错误请求数和限速比例。

182
scripts/distributed-test.js Normal file
View File

@ -0,0 +1,182 @@
import axios from 'axios';
import { v4 as uuidv4 } from 'uuid';
// 配置
const BASE_URL = 'http://localhost:3000'; // 修改为你的服务器地址和端口
const TEST_NAMESPACE = uuidv4(); // 生成随机命名空间用于测试
const SIMULATED_IPS = 10; // 模拟的IP数量
const REQUESTS_PER_IP = 30; // 每个IP的请求数
const TEST_ENDPOINT = '/check'; // 测试端点
const DELAY_BETWEEN_BATCHES = 500; // 批次间延迟(毫秒)
// 测试结果统计
const stats = {
totalRequests: 0,
success: 0,
rateLimited: 0,
errors: 0,
ipStats: {} // 每个IP的统计信息
};
// 颜色输出
const colors = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m'
};
/**
* 延迟函数
* @param {number} ms 延迟毫秒数
*/
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
/**
* 生成随机IP地址
*/
function generateRandomIP() {
const ip = [];
for (let i = 0; i < 4; i++) {
ip.push(Math.floor(Math.random() * 256));
}
return ip.join('.');
}
/**
* 初始化IP统计
*/
function initIPStats() {
const ips = [];
for (let i = 0; i < SIMULATED_IPS; i++) {
const ip = generateRandomIP();
ips.push(ip);
stats.ipStats[ip] = {
success: 0,
rateLimited: 0,
errors: 0,
totalRequests: 0
};
}
return ips;
}
/**
* 发送单个请求
* @param {string} ip 模拟的IP地址
* @param {number} index 请求索引
*/
async function sendRequest(ip, index) {
stats.totalRequests++;
stats.ipStats[ip].totalRequests++;
const url = `${BASE_URL}${TEST_ENDPOINT}`;
try {
const response = await axios({
method: 'GET',
url,
headers: {
'X-Forwarded-For': ip, // 模拟不同的IP地址
'User-Agent': `TestBot/${ip}` // 模拟不同的用户代理
},
validateStatus: () => true // 不抛出HTTP错误
});
// 处理响应
if (response.status === 429) { // 请求被限速
stats.rateLimited++;
stats.ipStats[ip].rateLimited++;
process.stdout.write(`${colors.yellow}L${colors.reset}`);
} else if (response.status >= 200 && response.status < 300) { // 成功
stats.success++;
stats.ipStats[ip].success++;
process.stdout.write(`${colors.green}.${colors.reset}`);
} else { // 其他错误
stats.errors++;
stats.ipStats[ip].errors++;
process.stdout.write(`${colors.red}E${colors.reset}`);
}
return response.status;
} catch (error) {
stats.errors++;
stats.ipStats[ip].errors++;
process.stdout.write(`${colors.red}X${colors.reset}`);
return 0;
}
}
/**
* 打印统计信息
* @param {Array<string>} ips IP地址列表
*/
function printStats(ips) {
console.log(`\n\n${colors.magenta}分布式测试结果:${colors.reset}`);
console.log(`总请求数: ${stats.totalRequests}`);
console.log(`成功请求: ${stats.success}`);
console.log(`被限速请求: ${stats.rateLimited}`);
console.log(`错误请求: ${stats.errors}`);
console.log(`总限速比例: ${Math.round((stats.rateLimited / stats.totalRequests) * 100)}%`);
console.log(`\n${colors.magenta}各IP测试结果:${colors.reset}`);
ips.forEach((ip, index) => {
const ipStat = stats.ipStats[ip];
const limitedPercent = Math.round((ipStat.rateLimited / ipStat.totalRequests) * 100);
console.log(`IP-${index+1} (${ip}): 总请求=${ipStat.totalRequests}, 成功=${ipStat.success}, 限速=${ipStat.rateLimited} (${limitedPercent}%), 错误=${ipStat.errors}`);
});
}
/**
* 为单个IP发送多个请求
* @param {string} ip IP地址
*/
async function sendRequestsForIP(ip) {
const promises = [];
for (let i = 0; i < REQUESTS_PER_IP; i++) {
promises.push(sendRequest(ip, i));
}
await Promise.all(promises);
}
/**
* 主函数
*/
async function main() {
console.log(`${colors.cyan}开始分布式测试限速功能...${colors.reset}`);
console.log(`目标端点: ${TEST_ENDPOINT}`);
console.log(`模拟IP数量: ${SIMULATED_IPS}`);
console.log(`每个IP请求数: ${REQUESTS_PER_IP}`);
console.log(`总请求数: ${SIMULATED_IPS * REQUESTS_PER_IP}`);
try {
// 初始化IP统计
const ips = initIPStats();
console.log(`\n${colors.cyan}测试进度:${colors.reset}`);
// 为每个IP发送请求
for (let i = 0; i < ips.length; i++) {
const ip = ips[i];
console.log(`\n${colors.blue}测试IP-${i+1} (${ip}):${colors.reset} `);
await sendRequestsForIP(ip);
// 在IP批次之间添加延迟
if (i < ips.length - 1) {
await delay(DELAY_BETWEEN_BATCHES);
}
}
printStats(ips);
console.log(`\n${colors.green}分布式测试完成!${colors.reset}`);
} catch (error) {
console.error(`\n${colors.red}测试过程中发生错误: ${error.message}${colors.reset}`);
}
}
// 执行主函数
main().catch(console.error);

83
scripts/run-all-tests.js Normal file
View File

@ -0,0 +1,83 @@
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
// 获取当前文件的目录路径
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// 颜色输出
const colors = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m'
};
/**
* 运行脚本
* @param {string} scriptName 脚本文件名
* @param {string} description 测试描述
*/
function runScript(scriptName, description) {
return new Promise((resolve, reject) => {
console.log(`\n${colors.cyan}=======================================${colors.reset}`);
console.log(`${colors.cyan}运行: ${description}${colors.reset}`);
console.log(`${colors.cyan}=======================================${colors.reset}\n`);
const scriptPath = join(__dirname, scriptName);
const child = spawn('node', [scriptPath], { stdio: 'inherit' });
child.on('close', (code) => {
if (code === 0) {
console.log(`\n${colors.green}${description}完成,退出码: ${code}${colors.reset}`);
resolve();
} else {
console.error(`\n${colors.red}${description}失败,退出码: ${code}${colors.reset}`);
reject(new Error(`脚本退出码: ${code}`));
}
});
child.on('error', (error) => {
console.error(`\n${colors.red}启动脚本时出错: ${error.message}${colors.reset}`);
reject(error);
});
});
}
/**
* 主函数
*/
async function main() {
console.log(`${colors.magenta}开始运行所有限速测试...${colors.reset}`);
try {
// 运行功能测试
await runScript('test-rate-limit.js', '功能测试');
// 等待一段时间以确保限速计数器重置
console.log(`\n${colors.yellow}等待30秒以确保限速计数器重置...${colors.reset}`);
await new Promise(resolve => setTimeout(resolve, 30000));
// 运行压力测试
await runScript('stress-test.js', '压力测试');
// 等待一段时间以确保限速计数器重置
console.log(`\n${colors.yellow}等待30秒以确保限速计数器重置...${colors.reset}`);
await new Promise(resolve => setTimeout(resolve, 30000));
// 运行分布式测试
await runScript('distributed-test.js', '分布式测试');
console.log(`\n${colors.green}所有测试已完成!${colors.reset}`);
} catch (error) {
console.error(`\n${colors.red}测试过程中发生错误: ${error.message}${colors.reset}`);
process.exit(1);
}
}
// 执行主函数
main().catch(console.error);

135
scripts/stress-test.js Normal file
View File

@ -0,0 +1,135 @@
import axios from 'axios';
import { v4 as uuidv4 } from 'uuid';
// 配置
const BASE_URL = 'http://localhost:3000'; // 修改为你的服务器地址和端口
const TEST_NAMESPACE = uuidv4(); // 生成随机命名空间用于测试
const CONCURRENT_REQUESTS = 50; // 并发请求数
const TOTAL_REQUESTS = 500; // 总请求数
const TEST_ENDPOINT = '/check'; // 测试端点
// 测试结果统计
const stats = {
success: 0,
rateLimited: 0,
errors: 0,
totalRequests: 0,
startTime: 0,
endTime: 0
};
// 颜色输出
const colors = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m'
};
/**
* 发送单个请求
* @param {number} index 请求索引
*/
async function sendRequest(index) {
stats.totalRequests++;
const url = `${BASE_URL}${TEST_ENDPOINT}`;
try {
const response = await axios({
method: 'GET',
url,
validateStatus: () => true // 不抛出HTTP错误
});
// 处理响应
if (response.status === 429) { // 请求被限速
stats.rateLimited++;
process.stdout.write(`${colors.yellow}L${colors.reset}`);
} else if (response.status >= 200 && response.status < 300) { // 成功
stats.success++;
process.stdout.write(`${colors.green}.${colors.reset}`);
} else { // 其他错误
stats.errors++;
process.stdout.write(`${colors.red}E${colors.reset}`);
}
// 每50个请求换行
if (index % 50 === 0 && index > 0) {
process.stdout.write('\n');
}
return response.status;
} catch (error) {
stats.errors++;
process.stdout.write(`${colors.red}X${colors.reset}`);
return 0;
}
}
/**
* 打印统计信息
*/
function printStats() {
const duration = (stats.endTime - stats.startTime) / 1000;
const rps = Math.round(stats.totalRequests / duration);
console.log(`\n\n${colors.magenta}压力测试结果:${colors.reset}`);
console.log(`总请求数: ${stats.totalRequests}`);
console.log(`成功请求: ${stats.success}`);
console.log(`被限速请求: ${stats.rateLimited}`);
console.log(`错误请求: ${stats.errors}`);
console.log(`限速比例: ${Math.round((stats.rateLimited / stats.totalRequests) * 100)}%`);
console.log(`测试持续时间: ${duration.toFixed(2)}`);
console.log(`平均请求速率: ${rps} 请求/秒`);
}
/**
* 批量发送请求
* @param {number} batchSize 批次大小
* @param {number} startIndex 起始索引
*/
async function sendBatch(batchSize, startIndex) {
const promises = [];
for (let i = 0; i < batchSize; i++) {
const index = startIndex + i;
if (index < TOTAL_REQUESTS) {
promises.push(sendRequest(index));
}
}
await Promise.all(promises);
}
/**
* 主函数
*/
async function main() {
console.log(`${colors.cyan}开始压力测试限速功能...${colors.reset}`);
console.log(`目标端点: ${TEST_ENDPOINT}`);
console.log(`并发请求数: ${CONCURRENT_REQUESTS}`);
console.log(`总请求数: ${TOTAL_REQUESTS}`);
console.log(`\n${colors.cyan}测试进度:${colors.reset}`);
try {
stats.startTime = Date.now();
// 分批发送请求
const batches = Math.ceil(TOTAL_REQUESTS / CONCURRENT_REQUESTS);
for (let i = 0; i < batches; i++) {
const startIndex = i * CONCURRENT_REQUESTS;
await sendBatch(CONCURRENT_REQUESTS, startIndex);
}
stats.endTime = Date.now();
printStats();
console.log(`\n${colors.green}压力测试完成!${colors.reset}`);
} catch (error) {
console.error(`\n${colors.red}测试过程中发生错误: ${error.message}${colors.reset}`);
}
}
// 执行主函数
main().catch(console.error);

250
scripts/test-rate-limit.js Normal file
View File

@ -0,0 +1,250 @@
import axios from 'axios';
import { v4 as uuidv4 } from 'uuid';
// 配置
const BASE_URL = 'http://localhost:3030'; // 修改为你的服务器地址和端口
const TEST_NAMESPACE = uuidv4(); // 生成随机命名空间用于测试
const DELAY_BETWEEN_REQUESTS = 100; // 请求间隔时间(毫秒)
// 测试结果统计
const stats = {
success: 0,
rateLimited: 0,
errors: 0,
totalRequests: 0
};
// 颜色输出
const colors = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m'
};
/**
* 延迟函数
* @param {number} ms 延迟毫秒数
*/
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
/**
* 发送请求并处理响应
* @param {string} method HTTP方法
* @param {string} endpoint 请求路径
* @param {object} data 请求数据
* @param {number} index 请求索引
*/
async function sendRequest(method, endpoint, data = null, index) {
stats.totalRequests++;
const url = `${BASE_URL}${endpoint}`;
try {
const startTime = Date.now();
const response = await axios({
method,
url,
data,
validateStatus: () => true // 不抛出HTTP错误
});
const duration = Date.now() - startTime;
// 处理响应
if (response.status === 429) { // 请求被限速
stats.rateLimited++;
console.log(`${colors.yellow}[${index}] ${method} ${endpoint} - 被限速 (${duration}ms)${colors.reset}`);
return { limited: true, status: response.status };
} else if (response.status >= 200 && response.status < 300) { // 成功
stats.success++;
console.log(`${colors.green}[${index}] ${method} ${endpoint} - 成功: ${response.status} (${duration}ms)${colors.reset}`);
return { limited: false, status: response.status, data: response.data };
} else { // 其他错误
stats.errors++;
console.log(`${colors.red}[${index}] ${method} ${endpoint} - 错误: ${response.status} (${duration}ms)${colors.reset}`);
return { limited: false, status: response.status, error: response.data };
}
} catch (error) {
stats.errors++;
console.log(`${colors.red}[${index}] ${method} ${endpoint} - 异常: ${error.message}${colors.reset}`);
return { limited: false, error: error.message };
}
}
/**
* 测试全局限速
*/
async function testGlobalRateLimit() {
console.log(`\n${colors.cyan}===== 测试全局限速 (200/15分钟) =====${colors.reset}`);
const requests = [];
// 发送250个请求 (应该有50个被限速)
for (let i = 0; i < 250; i++) {
requests.push(sendRequest('GET', '/check', null, i));
await delay(DELAY_BETWEEN_REQUESTS);
}
await Promise.all(requests);
printStats('全局限速测试');
}
/**
* 测试API限速
*/
async function testApiRateLimit() {
console.log(`\n${colors.cyan}===== 测试API限速 (50/5分钟) =====${colors.reset}`);
resetStats();
const requests = [];
// 发送60个请求 (应该有10个被限速)
for (let i = 0; i < 60; i++) {
requests.push(sendRequest('GET', '/check', null, i));
await delay(DELAY_BETWEEN_REQUESTS);
}
await Promise.all(requests);
printStats('API限速测试');
}
/**
* 测试写操作限速
*/
async function testWriteRateLimit() {
console.log(`\n${colors.cyan}===== 测试写操作限速 (10/分钟) =====${colors.reset}`);
resetStats();
const requests = [];
// 发送15个写请求 (应该有5个被限速)
for (let i = 0; i < 15; i++) {
const key = `test-key-${i}`;
const data = { value: `test-value-${i}`, timestamp: Date.now() };
requests.push(sendRequest('POST', `/${TEST_NAMESPACE}/${key}`, data, i));
await delay(DELAY_BETWEEN_REQUESTS);
}
await Promise.all(requests);
printStats('写操作限速测试');
}
/**
* 测试删除操作限速
*/
async function testDeleteRateLimit() {
console.log(`\n${colors.cyan}===== 测试删除操作限速 (5/5分钟) =====${colors.reset}`);
resetStats();
// 先创建几个键值对
for (let i = 0; i < 10; i++) {
const key = `delete-key-${i}`;
const data = { value: `delete-value-${i}` };
await sendRequest('POST', `/${TEST_NAMESPACE}/${key}`, data, `创建-${i}`);
await delay(DELAY_BETWEEN_REQUESTS);
}
resetStats();
const requests = [];
// 发送8个删除请求 (应该有3个被限速)
for (let i = 0; i < 8; i++) {
const key = `delete-key-${i}`;
requests.push(sendRequest('DELETE', `/${TEST_NAMESPACE}/${key}`, null, i));
await delay(DELAY_BETWEEN_REQUESTS);
}
await Promise.all(requests);
printStats('删除操作限速测试');
}
/**
* 测试认证限速
*/
async function testAuthRateLimit() {
console.log(`\n${colors.cyan}===== 测试认证限速 (5/30分钟) =====${colors.reset}`);
resetStats();
const requests = [];
// 发送8个认证请求 (应该有3个被限速)
for (let i = 0; i < 8; i++) {
requests.push(sendRequest('POST', '/auth', { username: 'test', password: 'wrong' }, i));
await delay(DELAY_BETWEEN_REQUESTS);
}
await Promise.all(requests);
printStats('认证限速测试');
}
/**
* 重置统计数据
*/
function resetStats() {
stats.success = 0;
stats.rateLimited = 0;
stats.errors = 0;
stats.totalRequests = 0;
}
/**
* 打印统计信息
*/
function printStats(testName) {
console.log(`\n${colors.magenta}${testName}结果:${colors.reset}`);
console.log(`总请求数: ${stats.totalRequests}`);
console.log(`成功请求: ${stats.success}`);
console.log(`被限速请求: ${stats.rateLimited}`);
console.log(`错误请求: ${stats.errors}`);
console.log(`限速比例: ${Math.round((stats.rateLimited / stats.totalRequests) * 100)}%`);
}
/**
* 主函数
*/
async function main() {
console.log(`${colors.cyan}开始测试限速功能...${colors.reset}`);
console.log(`使用测试命名空间: ${TEST_NAMESPACE}`);
try {
// 测试全局限速
await testGlobalRateLimit();
// 重置统计并等待一段时间
resetStats();
await delay(2000);
// 测试API限速
await testApiRateLimit();
// 重置统计并等待一段时间
resetStats();
await delay(2000);
// 测试写操作限速
await testWriteRateLimit();
// 重置统计并等待一段时间
resetStats();
await delay(2000);
// 测试删除操作限速
await testDeleteRateLimit();
// 重置统计并等待一段时间
resetStats();
await delay(2000);
// 测试认证限速
await testAuthRateLimit();
// 清理测试数据
console.log(`\n${colors.cyan}清理测试数据...${colors.reset}`);
await sendRequest('DELETE', `/${TEST_NAMESPACE}`, null, 'cleanup');
console.log(`\n${colors.green}所有测试完成!${colors.reset}`);
} catch (error) {
console.error(`${colors.red}测试过程中发生错误: ${error.message}${colors.reset}`);
}
}
// 执行主函数
main().catch(console.error);