guanghulab/server/portal/lib/rate-limit.js

53 lines
1.5 KiB
JavaScript
Raw Permalink Normal View History

/*
* 限流 (cc-004 系统自主防御)
*
* 2C2G 是公网唯一入口, 不限流 = 被扫一波就死. express-rate-limit 内存窗口够用,
* 不需要 Redis. IP-based, 信任 nginx X-Forwarded-For (server.js 不主动设
* trust proxy, express-rate-limit req.ip 我们这里在 nginx 后面所以
* server.js trust 第一跳, 只信我们自己的 nginx).
*
* 中文 429 回执 (cc-004 给团队看的级别).
*
* 配额:
* readLimiter 60 req/min/IP /api/health /api/manifest /api/persona-db
* writeLimiter 15 req/min/IP /api/conversations* /api/active-model
* chatLimiter 10 req/min/IP /api/chat (SSE 推理转发, 保护 AutoDL quota)
*/
"use strict";
const rateLimit = require("express-rate-limit");
const handler = (label, perMin) => (_req, res, _next, _options) => {
res.status(429).json({
error: true,
code: "rate_limited",
message: `请求太频繁啦, 每分钟最多 ${perMin} 次 · ${label}. 稍等再试.`
});
};
const readLimiter = rateLimit({
windowMs: 60_000,
max: 60,
standardHeaders: true,
legacyHeaders: false,
handler: handler("读类接口", 60)
});
const writeLimiter = rateLimit({
windowMs: 60_000,
max: 15,
standardHeaders: true,
legacyHeaders: false,
handler: handler("写类接口", 15)
});
const chatLimiter = rateLimit({
windowMs: 60_000,
max: 10,
standardHeaders: true,
legacyHeaders: false,
handler: handler("推理接口", 10)
});
module.exports = { readLimiter, writeLimiter, chatLimiter };