新增速率限制;增强兼容性

This commit is contained in:
2026-07-28 11:17:04 +08:00
parent d36283d28d
commit 69b9293bd7
3 changed files with 50 additions and 15 deletions
+40 -10
View File
@@ -1,4 +1,6 @@
import os, json, random, socket, logging, ipaddress, urllib.error, urllib.parse, urllib.request
import os, json, random, socket, logging, ipaddress, urllib.error, urllib.parse, urllib.request, threading
from collections import defaultdict, deque
from time import monotonic
from pathlib import Path
from datetime import date, datetime
from zoneinfo import ZoneInfo, available_timezones
@@ -90,6 +92,34 @@ for item in RIDDLE_DATA:
RIDDLE_RIDDLE_MAP[riddle_lower].append(item)
RIDDLE_ANSWER_MAP[answer_lower].append(item)
RATE_LIMIT = 30
RATE_WINDOW_SECONDS = 60
_request_times = defaultdict(deque)
_rate_limit_lock = threading.Lock()
@app.before_request
def enforce_rate_limit():
now = monotonic()
forwarded_for = request.headers.get("X-Forwarded-For", "")
client_ip = forwarded_for.split(",", 1)[0].strip() or request.remote_addr or "unknown"
with _rate_limit_lock:
timestamps = _request_times[client_ip]
cutoff = now - RATE_WINDOW_SECONDS
while timestamps and timestamps[0] <= cutoff:
timestamps.popleft()
if len(timestamps) >= RATE_LIMIT:
retry_after = max(1, int(timestamps[0] + RATE_WINDOW_SECONDS - now + 0.999))
response = jsonify({
"error": "请求过于频繁,请稍后再试",
"limit": f"{RATE_LIMIT}/min",
"retry_after": retry_after,
})
response.status_code = 429
response.headers["Retry-After"] = str(retry_after)
return response
timestamps.append(now)
@app.route("/favicon.ico")
def favicon():
@@ -135,7 +165,7 @@ def lunar():
args_lower = {k.lower(): v for k, v in request.args.items()}
if set(args_lower.keys()) - {"date"}:
return jsonify({
"error": f"{set(args_lower.keys()) - {"date"}}参数非法",
"error": "{}参数非法".format(set(args_lower.keys()) - {"date"}),
"allowed_params": ["date=YYYY-MM-DD"],
})
ds = args_lower.get("date", "").strip()
@@ -193,7 +223,7 @@ def luck():
args_lower = {k.lower(): v for k, v in request.args.items()}
if set(args_lower.keys()) - {"id", "title", "type"}:
return jsonify({
"error": f"{set(args_lower.keys()) - {"id", "title", "type"}}参数非法",
"error": "{}参数非法".format(set(args_lower.keys()) - {"id", "title", "type"}),
"allowed_params": ["id=xxx", "title=xxx", "type=xxx"],
})
luck_id = args_lower.get("id", "").strip()
@@ -251,7 +281,7 @@ def poem():
args_lower = {k.lower(): v for k, v in request.args.items()}
if set(args_lower.keys()) - {"id", "title", "author", "type", "full"}:
return jsonify({
"error": f"{set(args_lower.keys()) - {"id", "title", "author", "type", "full"}}参数非法",
"error": "{}参数非法".format(set(args_lower.keys()) - {"id", "title", "author", "type", "full"}),
"allowed_params": ["id=xxx", "title=xxx", "author=xxx", "type=xxx", "full=true/false"],
})
poem_id = args_lower.get("id", "").strip()
@@ -347,7 +377,7 @@ def ip():
args_lower = {k.lower(): v for k, v in request.args.items()}
if set(args_lower.keys()) - {"name"}:
return jsonify({
"error": f"{set(args_lower.keys()) - {"name"}}参数非法",
"error": "{}参数非法".format(set(args_lower.keys()) - {"name"}),
"allowed_params": ["name=xxx"],
})
query_name = args_lower.get("name", "").strip()
@@ -417,7 +447,7 @@ def time_api():
args_lower = {k.lower(): v for k, v in request.args.items()}
if set(args_lower.keys()) - {"tz"}:
return jsonify({
"error": f"{set(args_lower.keys()) - {"tz"}}参数非法",
"error": "{}参数非法".format(set(args_lower.keys()) - {"tz"}),
"allowed_params": ["tz=xxx"],
})
tz = args_lower.get("tz", "").strip()
@@ -482,7 +512,7 @@ def riddle():
args_lower = {k.lower(): v for k, v in request.args.items()}
if set(args_lower.keys()) - {"riddle", "answer"}:
return jsonify({
"error": f"{set(args_lower.keys()) - {"riddle", "answer"}}参数非法",
"error": "{}参数非法".format(set(args_lower.keys()) - {"riddle", "answer"}),
"allowed_params": ["riddle=xxx", "answer=xxx"],
})
query_riddle = args_lower.get("riddle", "").strip()
@@ -532,7 +562,7 @@ def sci():
args_lower = {k.lower(): v for k, v in request.args.items()}
if set(args_lower.keys()) - {"nane", "name"}:
return jsonify({
"error": f"{set(args_lower.keys()) - {"nane", "name"}}参数非法",
"error": "{}参数非法".format(set(args_lower.keys()) - {"nane", "name"}),
"allowed_params": ["nane=xxx"],
})
publication_name = args_lower.get("nane", "").strip() or args_lower.get("name", "").strip()
@@ -604,7 +634,7 @@ def agent():
extra_params = set(args_lower.keys()) - allowed_keys
if extra_params:
return jsonify({
"error": f"{extra_params}参数非法",
"error": "{}参数非法".format(extra_params),
"allowed_params": ["url=xxx", "ref=xxx", "pwd=xxx"],
})
target_url = args_lower.get("url", "").strip()
@@ -692,7 +722,7 @@ def pan_file_download():
extra_params = set(args_lower.keys()) - allowed_keys
if extra_params:
return jsonify({
"error": f"{extra_params}参数非法",
"error": "{}参数非法".format(extra_params),
"allowed_params": ["name=xxx", "download=true/false"],
})
file_name = args_lower.get("name", "").strip()