refactor: 将弹幕URL获取接口从POST改为GET并接入缓存和限流 (v1.6.1) (#73)
* refactor: 将弹幕URL获取接口从POST改为GET并接入缓存和限流 (v1.6.1)
## 主要变更
### 1. 接口重构:移除POST接口,改为GET方式
- **移除**: `POST /api/v2/comment/by-url` 接口
- **新增**: `GET /api/v2/comment?url=${videoUrl}&format=json` 接口
- **优势**:
- 更符合RESTful规范(GET用于查询数据)
- 兼容第三方弹幕服务器的使用习惯(如 api.danmu.icu)
- 播放器可直接在地址栏拼接URL,无需构造POST请求体
- 更易于测试和调试
### 2. 缓存和限流机制接入 ⭐
- **缓存优先策略**:
- 通过url参数获取弹幕时,先检查缓存
- 缓存命中时直接返回,不计入限流次数
- 提高响应速度,减少不必要的网络请求
- **限流保护**:
- 缓存未命中时才执行限流检查
- 与通过commentId获取弹幕的限流逻辑保持一致
- 默认:1分钟内同一IP最多请求3次(可通过 RATE_LIMIT_MAX_REQUESTS 配置)
- 超过限制返回 HTTP 429 错误
- **统一限流返回格式**:
- 旧格式: `{ status: 429, body: "..." }`
- 新格式: `{ errorCode: 429, success: false, errorMessage: "Too many requests, please try again later" }`
- 保持与其他API错误响应格式一致
### 3. 代码修改详情
**danmu_api/worker.js** (+96/-57 行)
- 合并路由逻辑:`GET /api/v2/comment` 同时支持 commentId 和 url 参数
- 为url参数方式添加完整的"缓存检查 → 限流检查 → 获取弹幕"流程
- 统一限流返回格式,保持API响应一致性
- 简化限流逻辑,移除冗余的历史记录清理代码
- 添加参数验证:必须提供 commentId 或 url 参数之一
**danmu_api/apis/dandan-api.js** (+23/-13 行)
- 函数签名变更:`getCommentByUrl(req, queryFormat)` → `getCommentByUrl(videoUrl, queryFormat)`
- 移除POST请求体解析逻辑(`req.json()`)
- 改为直接接收URL字符串参数
- 更新错误提示信息
- 保留内部缓存检查逻辑(作为双重保障)
**README.md** (+6/-3 行)
- 更新API接口文档说明
- 更新测试示例和使用示例
- 移除POST请求相关说明
**danmu_api/configs/globals.js** (+2/-1 行)
- 版本号:1.6.0 → 1.6.1
### 4. 配置优化(附带修改)
**.env.example** (+13/-4 行)
- 完善 `EPISODE_TITLE_FILTER` 环境变量注释说明
- 添加默认过滤规则的完整示例
- 启用 `MAX_LAST_SELECT_MAP=100` 配置
**danmu_api/configs/envs.js** (+10/-6 行)
- 修改 `EPISODE_TITLE_FILTER` 逻辑:从"增量添加"改为"完全覆盖"
- 简化剧集过滤规则,移除过于复杂的正则表达式
**danmu_api/sources/base.js** (+8/-1 行)
- 添加动漫去重逻辑:检查 animeId 是否已存在,避免重复添加
* fix: 修复 SOURCE_ORDER=bahamut 时出现重复的问题
问题原因:
- 当用户配置 SOURCE_ORDER=bahamut 时,在 Cloudflare/Vercel/Netlify 平台上
- resolveSourceOrder 函数会自动追加 ',bahamut'
- 导致最终变成 'bahamut,bahamut',出现重复结果
修复方案:
- 删除自动追加 bahamut 的隐式逻辑
- 用户需要在 SOURCE_ORDER 中显式配置 bahamut 源
- 配置即所得,避免魔法行为
修改文件:
- danmu_api/configs/envs.js: 删除自动追加逻辑
- .env.example: 更新配置说明
- README.md: 更新文档说明
This commit is contained in:
1 parent
1e80b60632
commit
5d942dc4c4
7 files changed
+103
-62
No files matched your search
+10
-5
@@ -57,7 +57,7 @@ YOUKU_CONCURRENCY=8
|
||||
# 数据源查询顺序(可选值:360, vod, tencent, youku, iqiyi, imgo, bilibili, renren, hanjutv, bahamut)
|
||||
# 默认值:360,vod,renren,hanjutv(不包含 tencent, youku, iqiyi, imgo 和 bilibili)
|
||||
# 如需使用腾讯源、优酷源、爱奇艺源、芒果源或B站源,请手动添加:SOURCE_ORDER=360,vod,tencent,youku,iqiyi,imgo,bilibili,renren,hanjutv
|
||||
# 注意:bahamut 仅在 Cloudflare 和 Vercel 平台自动启用
|
||||
# 如需使用巴哈姆特源,请手动添加:SOURCE_ORDER=360,vod,renren,hanjutv,bahamut
|
||||
SOURCE_ORDER=360,vod,renren,hanjutv
|
||||
|
||||
# ==================== 平台排序配置 ====================
|
||||
@@ -70,9 +70,14 @@ SOURCE_ORDER=360,vod,renren,hanjutv
|
||||
# ==================== 剧集标题过滤 ====================
|
||||
|
||||
# 剧集标题过滤正则表达式(用于过滤花絮、预告等非正片内容)
|
||||
# 默认值:包含大量关键词的复杂正则表达式
|
||||
# 如需自定义,请参考代码中的 DEFAULT_EPISODE_TITLE_FILTER
|
||||
# EPISODE_TITLE_FILTER=
|
||||
# 默认值:下方完整规则
|
||||
# 说明:
|
||||
# - 如果设置了此环境变量,将完全覆盖默认的过滤规则(不是增量添加)
|
||||
# - 格式:使用 | 分隔多个关键词,例如:预告|花絮|特辑|番外
|
||||
# - 可以直接修改下方规则,按需增删关键词
|
||||
# 示例:只过滤预告和花絮
|
||||
# EPISODE_TITLE_FILTER=预告|花絮
|
||||
EPISODE_TITLE_FILTER=(特别|惊喜|纳凉)?企划|合伙人手记|超前(营业|vlog)?|速览|vlog|reaction|纯享|加更(版|篇)?|抢先(看|版|集|篇)?|抢鲜|预告|花絮(独家)?|特辑|彩蛋|专访|幕后(故事|花絮|独家)?|直播(陪看|回顾)?|未播(片段)?|衍生|番外|会员(专享|加长|尊享|专属|版)?|片花|精华|看点|速看|解读|影评|解说|吐槽|盘点|拍摄花絮|制作花絮|幕后花絮|未播花絮|独家花絮|花絮特辑|先导预告|终极预告|正式预告|官方预告|彩蛋片段|删减片段|未播片段|番外彩蛋|精彩片段|精彩看点|精彩回顾|精彩集锦|看点解析|看点预告|NG镜头|NG花絮|番外篇|番外特辑|制作特辑|拍摄特辑|幕后特辑|导演特辑|演员特辑|片尾曲|插曲|高光回顾|背景音乐|OST|音乐MV|歌曲MV|前季回顾|剧情回顾|往期回顾|内容总结|剧情盘点|精选合集|剪辑合集|混剪视频|独家专访|演员访谈|导演访谈|主创访谈|媒体采访|发布会采访|采访|陪看(记)?|试看版|短剧|精编|Plus|独家版|特别版|短片|发布会|解忧局|走心局|火锅局|巅峰时刻|坞里都知道|福持目标坞民|观察室|上班那点事儿|周top|赛段|直拍|REACTION|VLOG|全纪录|开播|先导|总宣|展演|集锦|旅行日记|精彩分享|剧情揭秘
|
||||
|
||||
# ==================== 屏蔽词配置 ====================
|
||||
|
||||
@@ -185,7 +190,7 @@ COMMENT_CACHE_MINUTES=1
|
||||
# 默认值:100
|
||||
# 说明:lastSelectMap 最多保存的条目数,超过限制时删除最早的条目(FIFO)
|
||||
# 用于存储查询关键字上次选择的 animeId,下次 match 自动匹配时优先选择该 anime
|
||||
# MAX_LAST_SELECT_MAP=100
|
||||
MAX_LAST_SELECT_MAP=100
|
||||
|
||||
# ==================== Redis 配置(Upstash)====================
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ LogVar 弹幕 API 服务器
|
||||
- `GET /api/v2/search/episodes`:根据关键词搜索所有匹配的剧集信息。
|
||||
- `GET /api/v2/bangumi/:animeId`:获取指定动漫的详细信息。
|
||||
- `GET /api/v2/comment/:commentId?format=json`:获取指定弹幕评论,支持返回相关评论和字符转换。
|
||||
- `POST /api/v2/comment/by-url`:通过视频URL直接获取弹幕。
|
||||
- `GET /api/v2/comment?url=${videoUrl}&format=json`:通过视频URL直接获取弹幕(兼容第三方弹幕服务器格式)。
|
||||
- `GET /api/logs`:获取最近的日志(最多 500 行,格式为 `[时间戳] 级别: 消息`)。
|
||||
- **弹幕格式输出**:支持 JSON 和 XML 两种格式输出,通过以下方式配置:
|
||||
- 环境变量:`DANMU_OUTPUT_FORMAT=json|xml`(默认:json)
|
||||
@@ -97,7 +97,7 @@ LogVar 弹幕 API 服务器
|
||||
- `GET http://{ip}:9321/87654321/api/v2/search/episodes?anime=生万物`
|
||||
- `GET http://{ip}:9321/87654321/api/v2/bangumi/1`
|
||||
- `GET http://{ip}:9321/87654321/api/v2/comment/1?format=json`
|
||||
- `POST http://{ip}:9321/87654321/api/v2/comment/by-url`
|
||||
- `GET http://{ip}:9321/87654321/api/v2/comment?url=https://v.qq.com/x/cover/xxx.html&format=json`
|
||||
- `GET http://{ip}:9321/87654321/api/logs`
|
||||
|
||||
## 使用 Docker 运行
|
||||
@@ -289,7 +289,7 @@ API 支持返回 Bilibili 标准 XML 格式的弹幕数据,通过查询参数
|
||||
**使用示例**:
|
||||
- 获取 JSON 格式:`GET /api/v2/comment/10001`
|
||||
- 获取 XML 格式:`GET /api/v2/comment/10001?format=xml`
|
||||
- 通过 URL 获取 XML:`POST /api/v2/comment/by-url?format=xml` (body: `{"videoUrl":"..."}`)
|
||||
- 通过 URL 获取弹幕:`GET /api/v2/comment?url=https://v.qq.com/x/cover/xxx.html&format=json`
|
||||
|
||||
> 注意:
|
||||
>
|
||||
@@ -321,7 +321,7 @@ API 支持返回 Bilibili 标准 XML 格式的弹幕数据,通过查询参数
|
||||
| CONVERT_TOP_BOTTOM_TO_SCROLL | 【可选】是否将顶部和底部弹幕转换为浮动弹幕,默认为`false`(不转换),启用后顶部弹幕(ct=5)和底部弹幕(ct=4)会被转换为浮动弹幕(ct=1),可选值:`true`、`false` |
|
||||
| CONVERT_COLOR_TO_WHITE | 【可选】是否将彩色弹幕转换为纯白弹幕,默认为`false`(不转换),启用后所有非白色的弹幕颜色会被转换为纯白色(16777215),可选值:`true`、`false` |
|
||||
| DANMU_OUTPUT_FORMAT | 【可选】弹幕输出格式,默认为`json`,可选值:`json`(JSON格式)、`xml`(XML格式),支持通过查询参数`?format=xml`或`?format=json`覆盖此设置,优先级:查询参数 > 环境变量 > 默认值 |
|
||||
| PROXY_URL | 【可选】代理地址,示例: `http://127.0.0.1:7897` ,目前只对巴哈姆特生效(注意:如果巴哈姆特请求不通,会拖慢搜索返回速度,所以除vercel/netlify/cloudflare之外默认不开启bahamut源,开启请先在SOURCE_ORDER环境变量中添加`bahamut`)如果你使用docker部署并且访问不了bahamut源,请配置代理地址;vercel/netlify/cf中理应都自然能联通,不用填写 |
|
||||
| PROXY_URL | 【可选】代理地址,示例: `http://127.0.0.1:7897` ,目前只对巴哈姆特生效(注意:如果巴哈姆特请求不通,会拖慢搜索返回速度,如需使用bahamut源请在SOURCE_ORDER环境变量中手动添加`bahamut`)如果你使用docker部署并且访问不了bahamut源,请配置代理地址;vercel/netlify/cf中理应都自然能联通,不用填写 |
|
||||
| TMDB_API_KEY | 【可选】TMDB API Key地址,目前只对巴哈姆特生效,配置后当巴哈第一次搜索无结果时自动从TMDB获取日语原名搜索,可以解决巴哈译名不同导致的搜索无结果问题,例如大陆常用译名`间谍过家家`在巴哈译名为`間諜家家酒`,正常搜索无法搜索到,配置后可以解决这一问题但会稍微影响请求速度,[TMDBAPI](https://www.themoviedb.org/settings/api)获取方法参考:[TMDB API Key申请 - 绿联NAS私有云](https://www.ugnas.com/tutorial-detail/id-226.html) |
|
||||
| RATE_LIMIT_MAX_REQUESTS | 【可选】限流配置:1分钟内同一IP最大请求次数,默认为`3`,设置为`0`表示不限流 |
|
||||
| LOG_LEVEL | 【可选】日志级别,默认为`info`,可选值:`error`(仅错误)、`warn`(错误和警告)、`info`(所有日志),生产环境建议使用`warn`,调试时使用`info` |
|
||||
|
||||
@@ -753,8 +753,8 @@ export async function getComment(path, queryFormat) {
|
||||
return formatDanmuResponse(responseData, queryFormat);
|
||||
}
|
||||
|
||||
// Extracted function for POST /api/v2/comment/by-url
|
||||
export async function getCommentByUrl(req, queryFormat) {
|
||||
// Extracted function for GET /api/v2/comment?url=xxx
|
||||
export async function getCommentByUrl(videoUrl, queryFormat) {
|
||||
const tencentSource = new TencentSource();
|
||||
const iqiyiSource = new IqiyiSource();
|
||||
const mangoSource = new MangoSource();
|
||||
@@ -763,25 +763,22 @@ export async function getCommentByUrl(req, queryFormat) {
|
||||
const otherSource = new OtherSource();
|
||||
|
||||
try {
|
||||
// 获取请求体
|
||||
const body = await req.json();
|
||||
|
||||
// 验证请求体是否有效
|
||||
if (!body || !body.videoUrl) {
|
||||
log("error", "Missing videoUrl parameter in request body");
|
||||
// 验证URL参数
|
||||
if (!videoUrl || typeof videoUrl !== 'string') {
|
||||
log("error", "Missing or invalid url parameter");
|
||||
return jsonResponse(
|
||||
{ errorCode: 400, success: false, errorMessage: "Missing videoUrl parameter", count: 0, comments: [] },
|
||||
{ errorCode: 400, success: false, errorMessage: "Missing or invalid url parameter", count: 0, comments: [] },
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
const videoUrl = body.videoUrl.trim();
|
||||
videoUrl = videoUrl.trim();
|
||||
|
||||
// 验证URL格式
|
||||
if (!videoUrl.startsWith('http')) {
|
||||
log("error", "Invalid videoUrl format");
|
||||
log("error", "Invalid url format, must start with http or https");
|
||||
return jsonResponse(
|
||||
{ errorCode: 400, success: false, errorMessage: "Invalid videoUrl format", count: 0, comments: [] },
|
||||
{ errorCode: 400, success: false, errorMessage: "Invalid url format, must start with http or https", count: 0, comments: [] },
|
||||
400
|
||||
);
|
||||
}
|
||||
@@ -844,7 +841,7 @@ export async function getCommentByUrl(req, queryFormat) {
|
||||
};
|
||||
return formatDanmuResponse(responseData, queryFormat);
|
||||
} catch (error) {
|
||||
// 处理 JSON 解析错误或其他异常
|
||||
// 处理异常
|
||||
log("error", `Failed to process comment by URL request: ${error.message}`);
|
||||
return jsonResponse(
|
||||
{ errorCode: 500, success: false, errorMessage: "Internal server error", count: 0, comments: [] },
|
||||
|
||||
@@ -106,9 +106,6 @@ export class Envs {
|
||||
*/
|
||||
static resolveSourceOrder(env, deployPlatform) {
|
||||
let sourceOrder = this.get('SOURCE_ORDER', '360,vod,renren,hanjutv', 'string');
|
||||
if (['cloudflare', 'vercel', 'netlify'].includes(deployPlatform)) {
|
||||
sourceOrder += ',bahamut';
|
||||
}
|
||||
|
||||
const orderArr = sourceOrder
|
||||
.split(',')
|
||||
@@ -147,14 +144,12 @@ export class Envs {
|
||||
'花絮特辑|先导预告|终极预告|正式预告|官方预告|彩蛋片段|删减片段|未播片段|番外彩蛋|精彩片段|精彩看点|精彩回顾|精彩集锦|看点解析|看点预告|' +
|
||||
'NG镜头|NG花絮|番外篇|番外特辑|制作特辑|拍摄特辑|幕后特辑|导演特辑|演员特辑|片尾曲|插曲|高光回顾|背景音乐|OST|音乐MV|歌曲MV|前季回顾|' +
|
||||
'剧情回顾|往期回顾|内容总结|剧情盘点|精选合集|剪辑合集|混剪视频|独家专访|演员访谈|导演访谈|主创访谈|媒体采访|发布会采访|采访|陪看(记)?|' +
|
||||
'试看版|短剧|精编|Plus|独家版|特别版|短片|发布会|解忧局|走心局|火锅局|巅峰时刻|坞里都知道|福持目标坞民|.{3,}篇|(?!.*(入局|破冰局|做局)).{2,}局|观察室|上班那点事儿|' +
|
||||
'试看版|短剧|精编|Plus|独家版|特别版|短片|发布会|解忧局|走心局|火锅局|巅峰时刻|坞里都知道|福持目标坞民|观察室|上班那点事儿|' +
|
||||
'周top|赛段|直拍|REACTION|VLOG|全纪录|开播|先导|总宣|展演|集锦|旅行日记|精彩分享|剧情揭秘';
|
||||
let keywords = defaultFilter;
|
||||
|
||||
const customFilter = this.get('EPISODE_TITLE_FILTER', '', 'string', false).replace(/^\|+|\|+$/g, '');
|
||||
if (customFilter) {
|
||||
keywords = `${keywords}|${customFilter}`;
|
||||
}
|
||||
// 读取环境变量,如果设置了则完全覆盖默认值
|
||||
const customFilter = this.get('EPISODE_TITLE_FILTER', '', 'string', false).trim();
|
||||
let keywords = customFilter || defaultFilter;
|
||||
|
||||
this.accessedEnvVars.set('EPISODE_TITLE_FILTER', keywords);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export const Globals = {
|
||||
accessedEnvVars: {},
|
||||
|
||||
// 静态常量
|
||||
VERSION: '1.6.0',
|
||||
VERSION: '1.6.1',
|
||||
MAX_LOGS: 500, // 日志存储,最多保存 500 行
|
||||
MAX_ANIMES: 100,
|
||||
MAX_LAST_SELECT_MAP: 100,
|
||||
|
||||
@@ -67,7 +67,13 @@ export default class BaseSource {
|
||||
return 0;
|
||||
})
|
||||
.forEach(anime => {
|
||||
curAnimes.push(anime);
|
||||
// 检查 curAnimes 中是否已存在相同 animeId 的动漫
|
||||
const existingIndex = curAnimes.findIndex(a => a.animeId === anime.animeId);
|
||||
if (existingIndex === -1) {
|
||||
// 不存在则添加
|
||||
curAnimes.push(anime);
|
||||
}
|
||||
// 如果已存在则跳过,避免重复
|
||||
});
|
||||
}
|
||||
}
|
||||
+67
-29
@@ -123,19 +123,70 @@ async function handleRequest(req, env, deployPlatform, clientIp) {
|
||||
return getBangumi(path);
|
||||
}
|
||||
|
||||
// POST /api/v2/comment/by-url
|
||||
if (path === "/api/v2/comment/by-url" && method === "POST") {
|
||||
// GET /api/v2/comment/:commentId or /api/v2/comment?url=xxx
|
||||
if (path.startsWith("/api/v2/comment") && method === "GET") {
|
||||
const queryFormat = url.searchParams.get('format');
|
||||
return getCommentByUrl(req, queryFormat);
|
||||
}
|
||||
const videoUrl = url.searchParams.get('url');
|
||||
|
||||
// GET /api/v2/comment/:commentId
|
||||
if (path.startsWith("/api/v2/comment/") && method === "GET") {
|
||||
// ⚠️ 限流设计说明:
|
||||
// 1. 先检查缓存,缓存命中时直接返回,不计入限流次数
|
||||
// 2. 只有缓存未命中时才执行限流检查和网络请求
|
||||
// 这样可以避免频繁访问同一弹幕时被限流,提高用户体验
|
||||
const queryFormat = url.searchParams.get('format');
|
||||
// 3. 这样可以避免频繁访问同一弹幕时被限流,提高用户体验
|
||||
|
||||
// 如果有url参数,则通过URL获取弹幕
|
||||
if (videoUrl) {
|
||||
// 先检查缓存
|
||||
const cachedComments = getCommentCache(videoUrl);
|
||||
if (cachedComments !== null) {
|
||||
log("info", `[Rate Limit] Cache hit for URL: ${videoUrl}, skipping rate limit check`);
|
||||
const responseData = { count: cachedComments.length, comments: cachedComments };
|
||||
return formatDanmuResponse(responseData, queryFormat);
|
||||
}
|
||||
|
||||
// 缓存未命中,执行限流检查(如果 rateLimitMaxRequests > 0 则启用限流)
|
||||
if (globals.rateLimitMaxRequests > 0) {
|
||||
const currentTime = Date.now();
|
||||
const oneMinute = 60 * 1000;
|
||||
|
||||
// 清理所有过期的 IP 记录
|
||||
cleanupExpiredIPs(currentTime);
|
||||
|
||||
// 检查该 IP 地址的历史请求
|
||||
if (!globals.requestHistory.has(clientIp)) {
|
||||
globals.requestHistory.set(clientIp, []);
|
||||
}
|
||||
|
||||
const history = globals.requestHistory.get(clientIp);
|
||||
const recentRequests = history.filter(timestamp => currentTime - timestamp <= oneMinute);
|
||||
|
||||
// 如果最近 1 分钟内的请求次数超过限制,返回 429 错误
|
||||
if (recentRequests.length >= globals.rateLimitMaxRequests) {
|
||||
log("warn", `[Rate Limit] IP ${clientIp} exceeded rate limit (${recentRequests.length}/${globals.rateLimitMaxRequests} requests in 1 minute)`);
|
||||
return jsonResponse(
|
||||
{ errorCode: 429, success: false, errorMessage: "Too many requests, please try again later" },
|
||||
429
|
||||
);
|
||||
}
|
||||
|
||||
// 记录本次请求时间戳
|
||||
recentRequests.push(currentTime);
|
||||
globals.requestHistory.set(clientIp, recentRequests);
|
||||
log("info", `[Rate Limit] IP ${clientIp} request count: ${recentRequests.length}/${globals.rateLimitMaxRequests}`);
|
||||
}
|
||||
|
||||
// 通过URL获取弹幕
|
||||
return getCommentByUrl(videoUrl, queryFormat);
|
||||
}
|
||||
|
||||
// 否则通过commentId获取弹幕
|
||||
if (!path.startsWith("/api/v2/comment/")) {
|
||||
log("error", "Missing commentId or url parameter");
|
||||
return jsonResponse(
|
||||
{ errorCode: 400, success: false, errorMessage: "Missing commentId or url parameter" },
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
const commentId = parseInt(path.split("/").pop());
|
||||
let urlForComment = findUrlById(commentId);
|
||||
|
||||
@@ -171,30 +222,17 @@ async function handleRequest(req, env, deployPlatform, clientIp) {
|
||||
|
||||
// 如果最近的请求数量大于等于配置的限制次数,则限制请求
|
||||
if (recentRequests.length >= globals.rateLimitMaxRequests) {
|
||||
// 更新请求历史(清理过期记录)
|
||||
if (recentRequests.length === 0) {
|
||||
globals.requestHistory.delete(clientIp);
|
||||
} else {
|
||||
globals.requestHistory.set(clientIp, recentRequests);
|
||||
}
|
||||
|
||||
return jsonResponse({
|
||||
status: 429, // HTTP 429 Too Many Requests
|
||||
body: `1分钟内同一IP只能请求弹幕${globals.rateLimitMaxRequests}次,请稍后重试`,
|
||||
});
|
||||
log("warn", `[Rate Limit] IP ${clientIp} exceeded rate limit (${recentRequests.length}/${globals.rateLimitMaxRequests} requests in 1 minute)`);
|
||||
return jsonResponse(
|
||||
{ errorCode: 429, success: false, errorMessage: "Too many requests, please try again later" },
|
||||
429
|
||||
);
|
||||
}
|
||||
|
||||
// 将当前请求的时间戳添加到该 IP 地址的请求历史队列中
|
||||
// 记录本次请求时间戳
|
||||
recentRequests.push(currentTime);
|
||||
|
||||
// 更新该 IP 地址的请求历史
|
||||
if (recentRequests.length === 0) {
|
||||
// 如果没有最近的请求,删除该 IP 的记录以避免内存泄漏
|
||||
globals.requestHistory.delete(clientIp);
|
||||
} else {
|
||||
globals.requestHistory.set(clientIp, recentRequests);
|
||||
}
|
||||
log("info", `[Rate Limit] Request counted for IP: ${clientIp}, count: ${recentRequests.length}/${globals.rateLimitMaxRequests}`);
|
||||
globals.requestHistory.set(clientIp, recentRequests);
|
||||
log("info", `[Rate Limit] IP ${clientIp} request count: ${recentRequests.length}/${globals.rateLimitMaxRequests}`);
|
||||
}
|
||||
|
||||
return getComment(path, queryFormat);
|
||||
|
||||
Reference in new issue
Block a user