45 lines
1.3 KiB
JavaScript
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 };
|