添加帖子详情请求限流

close #233
This commit is contained in:
BTMuli
2026-03-26 06:00:42 +08:00
parent 8a0543d627
commit 38111b6abd
2 changed files with 106 additions and 2 deletions

View File

@@ -50,6 +50,7 @@ import postReq from "@req/postReq.js";
import useAppStore from "@store/app.js";
import TGLogger from "@utils/TGLogger.js";
import TGNotify from "@utils/TGNotify.js";
import { postDetailRateLimiter } from "@utils/rateLimiter.js";
import { storeToRefs } from "pinia";
import { ref, shallowRef, watch } from "vue";
@@ -216,8 +217,13 @@ async function tryAuto(skip: boolean = false): Promise<void> {
}
}
if (likeCnt < 5 || viewCnt < 3) {
await TGLogger.Script(`[米游币任务]正在浏览帖子${post.post.post_id}`);
const detailResp = await postReq.post(post.post.post_id, ckPost);
const currentCount = postDetailRateLimiter.getRequestCount();
await TGLogger.Script(
`[米游币任务]正在浏览帖子${post.post.post_id} (当前 1 分钟内请求数:${currentCount}/10)`,
);
const detailResp = await postDetailRateLimiter.execute(() =>
postReq.post(post.post.post_id, ckPost),
);
if ("retcode" in detailResp) {
await TGLogger.Script(
`[米游币任务]获取帖子${post.post.post_id}失败:${detailResp.retcode} ${detailResp.message}`,

98
src/utils/rateLimiter.ts Normal file
View File

@@ -0,0 +1,98 @@
/**
* 请求限流工具
* @since Beta v0.9.9
*/
/**
* 限流器配置
*/
type RateLimiterConfig = {
/** 时间窗口(毫秒) */
windowMs: number;
/** 最大请求次数 */
maxRequests: number;
};
/**
* 限流器类
* @since Beta v0.9.9
*/
class RateLimiter {
private config: RateLimiterConfig;
private requests: Array<number>;
constructor(config: RateLimiterConfig) {
this.config = config;
this.requests = [];
}
/**
* 执行限流请求
* @since Beta v0.9.9
* @param fn - 要执行的异步函数
* @returns 函数执行结果
*/
async execute<T>(fn: () => Promise<T>): Promise<T> {
await this.wait();
return fn();
}
/**
* 等待直到可以发起请求
* @since Beta v0.9.9
*/
private async wait(): Promise<void> {
const now = Date.now();
const windowStart = now - this.config.windowMs;
// 移除时间窗口外的请求记录
this.requests = this.requests.filter((time) => time > windowStart);
// 如果请求数已达上限,计算需要等待的时间
if (this.requests.length >= this.config.maxRequests) {
const oldestRequest = this.requests[0];
const waitTime = oldestRequest + this.config.windowMs - now;
if (waitTime > 0) {
await new Promise<void>((resolve) => setTimeout(resolve, waitTime));
// 重新计算时间窗口
await this.wait();
return;
}
}
// 记录当前请求时间
this.requests.push(Date.now());
}
/**
* 获取当前窗口内的请求数
* @since Beta v0.9.9
* @returns 请求数
*/
getRequestCount(): number {
const now = Date.now();
const windowStart = now - this.config.windowMs;
return this.requests.filter((time) => time > windowStart).length;
}
/**
* 重置限流器
* @since Beta v0.9.9
*/
reset(): void {
this.requests = [];
}
}
/**
* 帖子详情请求限流器
* 限制1 分钟内最多 10 次请求
* @since Beta v0.9.9
*/
export const postDetailRateLimiter = new RateLimiter({
windowMs: 60 * 1000, // 1 分钟
maxRequests: 10, // 最多 10 次请求
});
export default RateLimiter;