53 lines
1.5 KiB
JavaScript
53 lines
1.5 KiB
JavaScript
/*
|
|
* 限流 (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 };
|