infomankit 0.3.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infoman/__init__.py +1 -0
- infoman/cli/README.md +378 -0
- infoman/cli/__init__.py +7 -0
- infoman/cli/commands/__init__.py +3 -0
- infoman/cli/commands/init.py +312 -0
- infoman/cli/scaffold.py +634 -0
- infoman/cli/templates/Makefile.template +132 -0
- infoman/cli/templates/app/__init__.py.template +3 -0
- infoman/cli/templates/app/app.py.template +4 -0
- infoman/cli/templates/app/models_base.py.template +18 -0
- infoman/cli/templates/app/models_entity_init.py.template +11 -0
- infoman/cli/templates/app/models_schemas_init.py.template +11 -0
- infoman/cli/templates/app/repository_init.py.template +11 -0
- infoman/cli/templates/app/routers_init.py.template +15 -0
- infoman/cli/templates/app/services_init.py.template +11 -0
- infoman/cli/templates/app/static_index.html.template +39 -0
- infoman/cli/templates/app/static_main.js.template +31 -0
- infoman/cli/templates/app/static_style.css.template +111 -0
- infoman/cli/templates/app/utils_init.py.template +11 -0
- infoman/cli/templates/config/.env.dev.template +43 -0
- infoman/cli/templates/config/.env.prod.template +43 -0
- infoman/cli/templates/config/README.md.template +28 -0
- infoman/cli/templates/docker/.dockerignore.template +60 -0
- infoman/cli/templates/docker/Dockerfile.template +47 -0
- infoman/cli/templates/docker/README.md.template +240 -0
- infoman/cli/templates/docker/docker-compose.yml.template +81 -0
- infoman/cli/templates/docker/mysql_custom.cnf.template +42 -0
- infoman/cli/templates/docker/mysql_init.sql.template +15 -0
- infoman/cli/templates/project/.env.example.template +1 -0
- infoman/cli/templates/project/.gitignore.template +60 -0
- infoman/cli/templates/project/Makefile.template +38 -0
- infoman/cli/templates/project/README.md.template +137 -0
- infoman/cli/templates/project/deploy.sh.template +97 -0
- infoman/cli/templates/project/main.py.template +10 -0
- infoman/cli/templates/project/manage.sh.template +97 -0
- infoman/cli/templates/project/pyproject.toml.template +47 -0
- infoman/cli/templates/project/service.sh.template +203 -0
- infoman/config/__init__.py +25 -0
- infoman/config/base.py +67 -0
- infoman/config/db_cache.py +237 -0
- infoman/config/db_relation.py +181 -0
- infoman/config/db_vector.py +39 -0
- infoman/config/jwt.py +16 -0
- infoman/config/llm.py +16 -0
- infoman/config/log.py +627 -0
- infoman/config/mq.py +26 -0
- infoman/config/settings.py +65 -0
- infoman/llm/__init__.py +0 -0
- infoman/llm/llm.py +297 -0
- infoman/logger/__init__.py +57 -0
- infoman/logger/context.py +191 -0
- infoman/logger/core.py +358 -0
- infoman/logger/filters.py +157 -0
- infoman/logger/formatters.py +138 -0
- infoman/logger/handlers.py +276 -0
- infoman/logger/metrics.py +160 -0
- infoman/performance/README.md +583 -0
- infoman/performance/__init__.py +19 -0
- infoman/performance/cli.py +215 -0
- infoman/performance/config.py +166 -0
- infoman/performance/reporter.py +519 -0
- infoman/performance/runner.py +303 -0
- infoman/performance/standards.py +222 -0
- infoman/service/__init__.py +8 -0
- infoman/service/app.py +67 -0
- infoman/service/core/__init__.py +0 -0
- infoman/service/core/auth.py +105 -0
- infoman/service/core/lifespan.py +132 -0
- infoman/service/core/monitor.py +57 -0
- infoman/service/core/response.py +37 -0
- infoman/service/exception/__init__.py +7 -0
- infoman/service/exception/error.py +274 -0
- infoman/service/exception/exception.py +25 -0
- infoman/service/exception/handler.py +238 -0
- infoman/service/infrastructure/__init__.py +8 -0
- infoman/service/infrastructure/base.py +212 -0
- infoman/service/infrastructure/db_cache/__init__.py +8 -0
- infoman/service/infrastructure/db_cache/manager.py +194 -0
- infoman/service/infrastructure/db_relation/__init__.py +41 -0
- infoman/service/infrastructure/db_relation/manager.py +300 -0
- infoman/service/infrastructure/db_relation/manager_pro.py +408 -0
- infoman/service/infrastructure/db_relation/mysql.py +52 -0
- infoman/service/infrastructure/db_relation/pgsql.py +54 -0
- infoman/service/infrastructure/db_relation/sqllite.py +25 -0
- infoman/service/infrastructure/db_vector/__init__.py +40 -0
- infoman/service/infrastructure/db_vector/manager.py +201 -0
- infoman/service/infrastructure/db_vector/qdrant.py +322 -0
- infoman/service/infrastructure/mq/__init__.py +15 -0
- infoman/service/infrastructure/mq/manager.py +178 -0
- infoman/service/infrastructure/mq/nats/__init__.py +0 -0
- infoman/service/infrastructure/mq/nats/nats_client.py +57 -0
- infoman/service/infrastructure/mq/nats/nats_event_router.py +25 -0
- infoman/service/launch.py +284 -0
- infoman/service/middleware/__init__.py +7 -0
- infoman/service/middleware/base.py +41 -0
- infoman/service/middleware/logging.py +51 -0
- infoman/service/middleware/rate_limit.py +301 -0
- infoman/service/middleware/request_id.py +21 -0
- infoman/service/middleware/white_list.py +24 -0
- infoman/service/models/__init__.py +8 -0
- infoman/service/models/base.py +441 -0
- infoman/service/models/type/embed.py +70 -0
- infoman/service/routers/__init__.py +18 -0
- infoman/service/routers/health_router.py +311 -0
- infoman/service/routers/monitor_router.py +44 -0
- infoman/service/utils/__init__.py +8 -0
- infoman/service/utils/cache/__init__.py +0 -0
- infoman/service/utils/cache/cache.py +192 -0
- infoman/service/utils/module_loader.py +10 -0
- infoman/service/utils/parse.py +10 -0
- infoman/service/utils/resolver/__init__.py +8 -0
- infoman/service/utils/resolver/base.py +47 -0
- infoman/service/utils/resolver/resp.py +102 -0
- infoman/service/vector/__init__.py +20 -0
- infoman/service/vector/base.py +56 -0
- infoman/service/vector/qdrant.py +125 -0
- infoman/service/vector/service.py +67 -0
- infoman/utils/__init__.py +2 -0
- infoman/utils/decorators/__init__.py +8 -0
- infoman/utils/decorators/cache.py +137 -0
- infoman/utils/decorators/retry.py +99 -0
- infoman/utils/decorators/safe_execute.py +99 -0
- infoman/utils/decorators/timing.py +99 -0
- infoman/utils/encryption/__init__.py +8 -0
- infoman/utils/encryption/aes.py +66 -0
- infoman/utils/encryption/ecc.py +108 -0
- infoman/utils/encryption/rsa.py +112 -0
- infoman/utils/file/__init__.py +0 -0
- infoman/utils/file/handler.py +22 -0
- infoman/utils/hash/__init__.py +0 -0
- infoman/utils/hash/hash.py +61 -0
- infoman/utils/http/__init__.py +8 -0
- infoman/utils/http/client.py +62 -0
- infoman/utils/http/info.py +94 -0
- infoman/utils/http/result.py +19 -0
- infoman/utils/notification/__init__.py +8 -0
- infoman/utils/notification/feishu.py +35 -0
- infoman/utils/text/__init__.py +8 -0
- infoman/utils/text/extractor.py +111 -0
- infomankit-0.3.23.dist-info/METADATA +632 -0
- infomankit-0.3.23.dist-info/RECORD +143 -0
- infomankit-0.3.23.dist-info/WHEEL +4 -0
- infomankit-0.3.23.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import time
|
|
3
|
+
import asyncio
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from fastapi import Request, Response
|
|
6
|
+
from fastapi.responses import JSONResponse
|
|
7
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
8
|
+
from typing import Dict, List, Optional, Callable, Union
|
|
9
|
+
from infoman.logger import logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LimitStrategy(str, Enum):
|
|
13
|
+
IP = "ip"
|
|
14
|
+
PATH = "path"
|
|
15
|
+
USER = "user"
|
|
16
|
+
IP_PATH = "ip_path"
|
|
17
|
+
USER_PATH = "user_path"
|
|
18
|
+
GLOBAL = "global"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RateLimitExceeded(Exception):
|
|
22
|
+
|
|
23
|
+
def __init__(self, limit: int, window: int, retry_after: int):
|
|
24
|
+
self.limit = limit
|
|
25
|
+
self.window = window
|
|
26
|
+
self.retry_after = retry_after
|
|
27
|
+
message = f"Rate limit exceeded: {limit} requests per {window} seconds. Try again in {retry_after} seconds."
|
|
28
|
+
super().__init__(message)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RateLimitMiddleware(BaseHTTPMiddleware):
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
app,
|
|
36
|
+
max_requests: int = 100,
|
|
37
|
+
window: int = 60,
|
|
38
|
+
strategy: Union[LimitStrategy, str] = LimitStrategy.IP,
|
|
39
|
+
whitelist: List[str] = None,
|
|
40
|
+
blacklist: List[str] = None,
|
|
41
|
+
user_identifier: Callable[[Request], str] = None,
|
|
42
|
+
custom_response: Callable[[int], JSONResponse] = None,
|
|
43
|
+
path_pattern: Optional[str] = None,
|
|
44
|
+
exclude_paths: List[str] = None,
|
|
45
|
+
clean_interval: int = 60,
|
|
46
|
+
enable_statistics: bool = False,
|
|
47
|
+
redis_url: Optional[str] = None,
|
|
48
|
+
):
|
|
49
|
+
"""
|
|
50
|
+
初始化限流中间件
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
app: FastAPI应用
|
|
54
|
+
max_requests: 时间窗口内允许的最大请求数
|
|
55
|
+
window: 时间窗口大小(秒)
|
|
56
|
+
strategy: 限流策略,可以是LimitStrategy枚举或字符串
|
|
57
|
+
whitelist: IP白名单列表,这些IP不受限流影响
|
|
58
|
+
blacklist: IP黑名单列表,这些IP总是被拒绝
|
|
59
|
+
user_identifier: 从请求中提取用户标识的函数
|
|
60
|
+
custom_response: 自定义限流响应的函数
|
|
61
|
+
path_pattern: 只对匹配此正则表达式的路径应用限流
|
|
62
|
+
exclude_paths: 不进行限流的路径列表
|
|
63
|
+
clean_interval: 清理过期记录的间隔(秒)
|
|
64
|
+
enable_statistics: 是否启用详细统计
|
|
65
|
+
redis_url: Redis连接URL,如果提供则使用Redis存储请求记录
|
|
66
|
+
"""
|
|
67
|
+
super().__init__(app)
|
|
68
|
+
self.max_requests = max_requests
|
|
69
|
+
self.window = window
|
|
70
|
+
self.strategy = (
|
|
71
|
+
strategy if isinstance(strategy, LimitStrategy) else LimitStrategy(strategy)
|
|
72
|
+
)
|
|
73
|
+
self.whitelist = set(whitelist or [])
|
|
74
|
+
self.blacklist = set(blacklist or [])
|
|
75
|
+
self.user_identifier = user_identifier
|
|
76
|
+
self.custom_response = custom_response
|
|
77
|
+
self.path_pattern = re.compile(path_pattern) if path_pattern else None
|
|
78
|
+
self.exclude_paths = set(exclude_paths or [])
|
|
79
|
+
self.clean_interval = clean_interval
|
|
80
|
+
self.enable_statistics = enable_statistics
|
|
81
|
+
self.redis_url = redis_url
|
|
82
|
+
|
|
83
|
+
self.requests: Dict[str, List[float]] = {}
|
|
84
|
+
self.statistics = {
|
|
85
|
+
"total_requests": 0,
|
|
86
|
+
"limited_requests": 0,
|
|
87
|
+
"last_reset": time.time(),
|
|
88
|
+
"by_ip": {},
|
|
89
|
+
"by_path": {},
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
self.redis = None
|
|
93
|
+
if self.redis_url:
|
|
94
|
+
self._setup_redis()
|
|
95
|
+
self._setup_cleanup_task()
|
|
96
|
+
|
|
97
|
+
def _setup_redis(self):
|
|
98
|
+
try:
|
|
99
|
+
import redis.asyncio as redis
|
|
100
|
+
|
|
101
|
+
self.redis = redis.from_url(self.redis_url)
|
|
102
|
+
logger.info(f"Using Redis for rate limiting: {self.redis_url}")
|
|
103
|
+
except ImportError:
|
|
104
|
+
logger.warning(
|
|
105
|
+
"redis-py package not installed. Falling back to in-memory storage."
|
|
106
|
+
)
|
|
107
|
+
self.redis = None
|
|
108
|
+
|
|
109
|
+
def _setup_cleanup_task(self):
|
|
110
|
+
|
|
111
|
+
async def cleanup_task():
|
|
112
|
+
while True:
|
|
113
|
+
await asyncio.sleep(self.clean_interval)
|
|
114
|
+
self._cleanup_expired_records()
|
|
115
|
+
|
|
116
|
+
if (
|
|
117
|
+
self.enable_statistics
|
|
118
|
+
and time.time() - self.statistics["last_reset"] > 3600
|
|
119
|
+
):
|
|
120
|
+
self.statistics = {
|
|
121
|
+
"total_requests": 0,
|
|
122
|
+
"limited_requests": 0,
|
|
123
|
+
"last_reset": time.time(),
|
|
124
|
+
"by_ip": {},
|
|
125
|
+
"by_path": {},
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
asyncio.create_task(cleanup_task())
|
|
129
|
+
|
|
130
|
+
def _cleanup_expired_records(self):
|
|
131
|
+
current_time = time.time()
|
|
132
|
+
expired_keys = []
|
|
133
|
+
|
|
134
|
+
for key, timestamps in self.requests.items():
|
|
135
|
+
valid_timestamps = [
|
|
136
|
+
ts for ts in timestamps if ts > current_time - self.window
|
|
137
|
+
]
|
|
138
|
+
if not valid_timestamps:
|
|
139
|
+
expired_keys.append(key)
|
|
140
|
+
else:
|
|
141
|
+
self.requests[key] = valid_timestamps
|
|
142
|
+
|
|
143
|
+
for key in expired_keys:
|
|
144
|
+
del self.requests[key]
|
|
145
|
+
logger.debug(
|
|
146
|
+
f"Cleaned up {len(expired_keys)} expired records. Current records: {len(self.requests)}"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def _get_request_key(self, request: Request) -> str:
|
|
150
|
+
client_ip = request.client.host
|
|
151
|
+
path = request.url.path
|
|
152
|
+
|
|
153
|
+
if self.strategy == LimitStrategy.IP:
|
|
154
|
+
return f"ip:{client_ip}"
|
|
155
|
+
elif self.strategy == LimitStrategy.PATH:
|
|
156
|
+
return f"path:{path}"
|
|
157
|
+
elif self.strategy == LimitStrategy.USER:
|
|
158
|
+
user_id = (
|
|
159
|
+
self.user_identifier(request) if self.user_identifier else "anonymous"
|
|
160
|
+
)
|
|
161
|
+
return f"user:{user_id}"
|
|
162
|
+
elif self.strategy == LimitStrategy.IP_PATH:
|
|
163
|
+
return f"ip:{client_ip}:path:{path}"
|
|
164
|
+
elif self.strategy == LimitStrategy.USER_PATH:
|
|
165
|
+
user_id = (
|
|
166
|
+
self.user_identifier(request) if self.user_identifier else "anonymous"
|
|
167
|
+
)
|
|
168
|
+
return f"user:{user_id}:path:{path}"
|
|
169
|
+
elif self.strategy == LimitStrategy.GLOBAL:
|
|
170
|
+
return "global"
|
|
171
|
+
else:
|
|
172
|
+
return f"ip:{client_ip}"
|
|
173
|
+
|
|
174
|
+
async def _get_request_count(self, key: str, current_time: float) -> int:
|
|
175
|
+
if self.redis:
|
|
176
|
+
try:
|
|
177
|
+
timestamps = await self.redis.zrangebyscore(
|
|
178
|
+
key, current_time - self.window, current_time
|
|
179
|
+
)
|
|
180
|
+
return len(timestamps)
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.error(f"Redis error: {e}")
|
|
183
|
+
if key not in self.requests:
|
|
184
|
+
self.requests[key] = []
|
|
185
|
+
self.requests[key] = [
|
|
186
|
+
ts for ts in self.requests[key] if ts > current_time - self.window
|
|
187
|
+
]
|
|
188
|
+
return len(self.requests[key])
|
|
189
|
+
else:
|
|
190
|
+
if key not in self.requests:
|
|
191
|
+
self.requests[key] = []
|
|
192
|
+
self.requests[key] = [
|
|
193
|
+
ts for ts in self.requests[key] if ts > current_time - self.window
|
|
194
|
+
]
|
|
195
|
+
return len(self.requests[key])
|
|
196
|
+
|
|
197
|
+
async def _add_request_record(self, key: str, current_time: float) -> None:
|
|
198
|
+
if self.redis:
|
|
199
|
+
try:
|
|
200
|
+
await self.redis.zadd(key, {str(current_time): current_time})
|
|
201
|
+
await self.redis.expire(key, self.window * 2)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(f"Redis error: {e}")
|
|
204
|
+
if key not in self.requests:
|
|
205
|
+
self.requests[key] = []
|
|
206
|
+
self.requests[key].append(current_time)
|
|
207
|
+
else:
|
|
208
|
+
if key not in self.requests:
|
|
209
|
+
self.requests[key] = []
|
|
210
|
+
self.requests[key].append(current_time)
|
|
211
|
+
|
|
212
|
+
def _update_statistics(self, request: Request, limited: bool) -> None:
|
|
213
|
+
if not self.enable_statistics:
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
self.statistics["total_requests"] += 1
|
|
217
|
+
if limited:
|
|
218
|
+
self.statistics["limited_requests"] += 1
|
|
219
|
+
|
|
220
|
+
client_ip = request.client.host
|
|
221
|
+
if client_ip not in self.statistics["by_ip"]:
|
|
222
|
+
self.statistics["by_ip"][client_ip] = {"total": 0, "limited": 0}
|
|
223
|
+
self.statistics["by_ip"][client_ip]["total"] += 1
|
|
224
|
+
if limited:
|
|
225
|
+
self.statistics["by_ip"][client_ip]["limited"] += 1
|
|
226
|
+
|
|
227
|
+
path = request.url.path
|
|
228
|
+
if path not in self.statistics["by_path"]:
|
|
229
|
+
self.statistics["by_path"][path] = {"total": 0, "limited": 0}
|
|
230
|
+
self.statistics["by_path"][path]["total"] += 1
|
|
231
|
+
if limited:
|
|
232
|
+
self.statistics["by_path"][path]["limited"] += 1
|
|
233
|
+
|
|
234
|
+
def _create_rate_limit_response(self, retry_after: int) -> JSONResponse:
|
|
235
|
+
if self.custom_response:
|
|
236
|
+
return self.custom_response(retry_after)
|
|
237
|
+
|
|
238
|
+
return JSONResponse(
|
|
239
|
+
status_code=429,
|
|
240
|
+
content={
|
|
241
|
+
"error": "Too Many Requests",
|
|
242
|
+
"detail": f"Rate limit exceeded: {self.max_requests} requests per {self.window} seconds.",
|
|
243
|
+
"retry_after": retry_after,
|
|
244
|
+
},
|
|
245
|
+
headers={"Retry-After": str(retry_after)},
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def _should_limit_path(self, path: str) -> bool:
|
|
249
|
+
if path in self.exclude_paths:
|
|
250
|
+
return False
|
|
251
|
+
|
|
252
|
+
if self.path_pattern and not self.path_pattern.match(path):
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
return True
|
|
256
|
+
|
|
257
|
+
async def dispatch(self, request: Request, call_next) -> Response:
|
|
258
|
+
if not self._should_limit_path(request.url.path):
|
|
259
|
+
return await call_next(request)
|
|
260
|
+
|
|
261
|
+
client_ip = request.client.host
|
|
262
|
+
if client_ip in self.whitelist:
|
|
263
|
+
return await call_next(request)
|
|
264
|
+
if client_ip in self.blacklist:
|
|
265
|
+
return JSONResponse(
|
|
266
|
+
status_code=403,
|
|
267
|
+
content={
|
|
268
|
+
"error": "Forbidden",
|
|
269
|
+
"detail": "Your IP address is blacklisted.",
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
key = self._get_request_key(request)
|
|
274
|
+
current_time = int(time.monotonic() * 1000)
|
|
275
|
+
request_count = await self._get_request_count(key, current_time)
|
|
276
|
+
|
|
277
|
+
if request_count >= self.max_requests:
|
|
278
|
+
oldest_timestamp = (
|
|
279
|
+
min(self.requests.get(key, [current_time]))
|
|
280
|
+
if not self.redis
|
|
281
|
+
else current_time - self.window
|
|
282
|
+
)
|
|
283
|
+
retry_after = max(1, int(self.window - (current_time - oldest_timestamp)))
|
|
284
|
+
self._update_statistics(request, limited=True)
|
|
285
|
+
logger.warning(
|
|
286
|
+
f"Rate limit exceeded for {key}. "
|
|
287
|
+
f"Count: {request_count}/{self.max_requests}, "
|
|
288
|
+
f"Window: {self.window}s, "
|
|
289
|
+
f"Retry-After: {retry_after}s"
|
|
290
|
+
)
|
|
291
|
+
return self._create_rate_limit_response(retry_after)
|
|
292
|
+
|
|
293
|
+
await self._add_request_record(key, current_time)
|
|
294
|
+
|
|
295
|
+
self._update_statistics(request, limited=False)
|
|
296
|
+
response = await call_next(request)
|
|
297
|
+
remaining = self.max_requests - request_count - 1
|
|
298
|
+
response.headers["X-RateLimit-Limit"] = str(self.max_requests)
|
|
299
|
+
response.headers["X-RateLimit-Remaining"] = str(max(0, remaining))
|
|
300
|
+
response.headers["X-RateLimit-Reset"] = str(int(current_time + self.window))
|
|
301
|
+
return response
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# Time :2025/6/21 21:21
|
|
6
|
+
# Author :Maxwell
|
|
7
|
+
# Description:
|
|
8
|
+
"""
|
|
9
|
+
from fastapi import Request, Response
|
|
10
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
11
|
+
from typing import Callable
|
|
12
|
+
from infoman.utils.hash.hash import HashManager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RequestIDMiddleware(BaseHTTPMiddleware):
|
|
16
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
|
17
|
+
request_id = HashManager.time_hash()
|
|
18
|
+
request.state.request_id = request_id
|
|
19
|
+
response = await call_next(request)
|
|
20
|
+
response.headers["X-Request-ID"] = request_id
|
|
21
|
+
return response
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*-coding:utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
# Time :2025/6/18 14:45
|
|
6
|
+
# Author :Maxwell
|
|
7
|
+
# Description:
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from fastapi import Request
|
|
11
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
12
|
+
from fastapi.responses import PlainTextResponse
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class IPWhitelistMiddleware(BaseHTTPMiddleware):
|
|
16
|
+
def __init__(self, app, whitelist):
|
|
17
|
+
super().__init__(app)
|
|
18
|
+
self.whitelist = whitelist
|
|
19
|
+
|
|
20
|
+
async def dispatch(self, request: Request, call_next):
|
|
21
|
+
client_ip = request.client.host
|
|
22
|
+
if client_ip not in self.whitelist:
|
|
23
|
+
return PlainTextResponse(status_code=403, content="IP not allowed")
|
|
24
|
+
return await call_next(request)
|