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

45 lines
1.3 KiB
JavaScript

/**
* 简单 IP+key 维度内存速率限制器
* 不依赖 express-rate-limit, 避免新增依赖
*/
'use strict';
function makeLimiter(opts) {
const windowMs = (opts && opts.windowMs) || 60 * 1000;
const max = (opts && opts.max) || 30;
const code = (opts && opts.code) || 'RATE_LIMIT';
const message = (opts && opts.message) || '请求过于频繁,请稍后再试';
const buckets = new Map(); // key → { count, resetAt }
setInterval(() => {
const now = Date.now();
for (const [k, v] of buckets.entries()) {
if (now > v.resetAt) buckets.delete(k);
}
}, windowMs).unref();
return function rateLimit(req, res, next) {
const key = (req.ip || req.connection.remoteAddress || 'unknown') + '|' + (req.path || '');
const now = Date.now();
let bucket = buckets.get(key);
if (!bucket || now > bucket.resetAt) {
bucket = { count: 0, resetAt: now + windowMs };
buckets.set(key, bucket);
}
bucket.count += 1;
if (bucket.count > max) {
const retry = Math.ceil((bucket.resetAt - now) / 1000);
res.set('Retry-After', String(retry));
return res.status(429).json({
error: true,
code,
message,
retry_after: retry
});
}
next();
};
}
module.exports = { makeLimiter };