Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

48 lines
1.4 KiB
JavaScript

/*
* 极简内存限流 · 给 vault Express 用
* 守护: 铸渊 · ICE-GL-ZY001
*
* 设计:
* - 滑动窗口 (固定 60 秒桶)
* - per-IP 计数, 超阈值返 429 + Retry-After
* - 内存占用小 (单 Map · key=ip · 每分钟自清)
*
* 注: nginx 在前层已经 allow 127.0.0.1; deny all + basic-auth, 这里是
* 纵深防御 (CodeQL js/missing-rate-limiting).
*/
"use strict";
function createRateLimit({ windowMs = 60_000, max = 120, message } = {}) {
const buckets = new Map(); // ip → { count, resetAt }
const msg = message || "请求太频繁, 请 1 分钟后再试.";
return function rateLimit(req, res, next) {
const ip = req.ip || (req.socket && req.socket.remoteAddress) || "unknown";
const now = Date.now();
let b = buckets.get(ip);
if (!b || b.resetAt < now) {
b = { count: 0, resetAt: now + windowMs };
buckets.set(ip, b);
}
b.count++;
if (b.count > max) {
const retry = Math.max(1, Math.ceil((b.resetAt - now) / 1000));
res.set("Retry-After", String(retry));
return res.status(429).json({
error: true,
code: "rate_limited",
message: msg + " (Retry-After=" + retry + "s)"
});
}
// 偶尔扫垃圾 (轻量, 不阻塞主路径)
if (buckets.size > 1024) {
for (const [k, v] of buckets) {
if (v.resetAt < now) buckets.delete(k);
}
}
next();
};
}
module.exports = { createRateLimit };