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

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
+8 -4
View File
@@ -1,10 +1,12 @@
# API-Python
> 基于Python的本地Web API项目
> 基于Python3.9+的Web API项目
#### v260716补丁
1. 优化import、修改README
2.复import异常
#### v260728
1. 优化(修复)import
2.改README
3. 新增IP请求速率限制(默认30/min)
4. 增强兼容性
#### v260716
1. 新增代理接口(包含白名单及密码限制)
@@ -19,3 +21,5 @@
#### v260512
1. 新增农历、运势、唐诗、地址、时间、谜语等接口及对应数据
+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()
+2 -1
View File
@@ -1,4 +1,5 @@
flask
flask-cors
lunar-python
py-ip2region
py-ip2region
tzdata