python 开发时一些问题 ¶
一些特性记录 ¶
fastapi中间件 ¶
中间件 执行顺序类似于洋葱,最后添加的中间件在最外层
middleware.py
python
1import time
2
3from fastapi import Request
4from fastapi.encoders import jsonable_encoder
5from starlette.middleware.base import BaseHTTPMiddleware
6from starlette.responses import JSONResponse as StarletteJSONResponse
7
8import app.common.log_factory as log_factory
9from app.common.exception import AppException
10from app.common.utils.response_util import WrapperResponse
11
12log = log_factory.get_logger(__name__)
13
14
15# 全局异常处理中间件
16class HandleExceptionMiddleware(BaseHTTPMiddleware):
17 async def dispatch(self, request: Request, call_next):
18 try:
19 response = await call_next(request)
20 # 检查非200状态码
21 if response.status_code != 200:
22 content_str = ""
23 # 处理不同类型的响应
24 if hasattr(response, "body"):
25 try:
26 content = await response.body()
27 content_str = content.decode(errors='replace')[:500]
28 except Exception as e:
29 content_str = f"[读取body失败: {str(e)}]"
30 elif hasattr(response, "body_iterator"):
31 content_str = "[流式响应内容,无法直接读取]"
32 else:
33 content_str = f"[未知响应类型: {type(response)}]"
34
35 log.warning(
36 f"非200响应 - 路径: {request.url.path}, "
37 f"状态码: {response.status_code}, "
38 f"内容: {content_str}"
39 )
40 except AppException as e:
41 log.error('%s', e)
42 response = StarletteJSONResponse(
43 jsonable_encoder(WrapperResponse.error(msg=e.msg)),
44 status_code=e.code if isinstance(e.code, int) else 500
45 )
46 except Exception as e:
47 log.error('%s', e)
48 response = StarletteJSONResponse(
49 jsonable_encoder(WrapperResponse.error(msg="服务器内部异常")),
50 status_code=500
51 )
52 return response
53
54
55class RateLimiterMiddleware(BaseHTTPMiddleware):
56 def __init__(
57 self,
58 app,
59 limit: int = 60, # 单位时间内允许的最大请求数
60 window: int = 60 # 时间窗口,单位为秒
61 ):
62 super().__init__(app)
63 self.limit = limit
64 self.window = window
65 self.client_requests = {}
66
67 async def dispatch(self, request: Request, call_next):
68 log.info(f"请求进入:{request.url.path}")
69 try:
70 client_ip = request.client.host
71 current_time = time.time()
72
73 # 如果客户端 IP 不在记录中,初始化记录
74 if client_ip not in self.client_requests:
75 self.client_requests[client_ip] = {
76 "count": 1,
77 "start_time": current_time
78 }
79 else:
80 client_info = self.client_requests[client_ip]
81 elapsed_time = current_time - client_info["start_time"]
82
83 # 如果时间窗口已过,重置计数和开始时间
84 if elapsed_time > self.window:
85 client_info["count"] = 1
86 client_info["start_time"] = current_time
87 else:
88 # 如果未超过时间窗口,增加计数
89 client_info["count"] += 1
90
91 # 检查是否超过请求限制
92 if client_info["count"] > self.limit:
93 raise AppException(msg="请求频率过高,请稍后再试", code=429)
94
95 response = await call_next(request)
96 finally:
97 log.info(f"请求结束:{request.url.path}")
98 return response一些错误记录 ¶
pyCharm Dubeg启动报错 ¶
12025-10-24 10:34:17 - asyncio - ERROR - Exception in callback <Task pending name='Task-1' coro=<Server.serve() running at C:\Users\lei\Desktop\fastapi-demo\.venv\lib\site-packages\uvicorn\server.py:69> cb=[_run_until_complete_cb() at C:\Users\lei\AppData\Roaming\uv\python\cpython-3.10.18-windows-x86_64-none\lib\asyncio\base_events.py:184]>()
2handle: <Handle <Task pending name='Task-1' coro=<Server.serve() running at C:\Users\lei\Desktop\fastapi-demo\.venv\lib\site-packages\uvicorn\server.py:69> cb=[_run_until_complete_cb() at C:\Users\lei\AppData\Roaming\uv\python\cpython-3.10.18-windows-x86_64-none\lib\asyncio\base_events.py:184]>()>
3Traceback (most recent call last):
4 File "C:\Users\lei\AppData\Roaming\uv\python\cpython-3.10.18-windows-x86_64-none\lib\asyncio\events.py", line 80, in _run
5 self._context.run(self._callback, *self._args)
6TypeError: 'Task' object is not callable解决办法:禁用异步调试模式
进入 Help -> Find Action,搜索 Registry,找到并取消勾选 python.debug.asyncio.repl 选项。此操作可禁用PyCharm对异步代码的特殊处理,避免事件循环冲突