Files
FastapiAdmin/backend/app/core/router_class.py
T
zhangtao 73f2823692 refactor: 大规模代码整理与功能优化
1. 重构后端API路由、CRUD与模块结构,整合日志管理,移除废弃demo代码
2. 优化前端组件类型定义、样式与路由配置,修复权限判断逻辑
3. 调整默认排序规则、滚动条样式与工具类函数,更新依赖与配置文件
4. 修复多处类型不匹配与默认值问题,完善表单与菜单验证逻辑
2026-06-17 01:56:31 +08:00

144 lines
5.5 KiB
Python

import asyncio
import json
import time
from collections.abc import Callable, Coroutine
from typing import Any
from fastapi import Request, Response
from fastapi.routing import APIRoute
from app.config.setting import settings
from app.core.base_schema import AuthSchema
from app.core.database import async_db_session
from app.core.logger import logger
async def _write_operation_log_async(log_data: dict) -> None:
"""
后台异步写入操作日志,不阻塞请求响应。
在独立的 DB session 中写入,避免复用请求上下文中的 session。
"""
from app.api.v1.module_system.log.schema import OperationLogCreateSchema
from app.api.v1.module_system.log.service import OperationLogService
try:
async with async_db_session() as _session:
async with _session.begin():
_auth = AuthSchema(db=_session)
await OperationLogService.create_service(
data=OperationLogCreateSchema(**log_data),
auth=_auth,
)
except Exception as e:
logger.warning(f"后台异步写入操作日志失败: {e}")
"""
在 FastAPI 中,route_class 参数用于自定义路由的行为。
通过设置 route_class,你可以定义一个自定义的路由类,从而在每个路由处理之前或之后执行特定的操作。
这对于日志记录、权限验证、性能监控等场景非常有用。
"""
class OperationLogRoute(APIRoute):
"""操作日志路由装饰器"""
def get_route_handler(
self,
) -> Callable[[Request], Coroutine[Any, Any, Response]]:
"""
自定义路由处理程序,在每个路由处理之前或之后执行特定的操作。
参数:
- request (Request): FastAPI请求对象。
返回:
- Response: FastAPI响应对象。
"""
original_route_handler = super().get_route_handler()
async def custom_route_handler(request: Request) -> Response:
"""
自定义路由处理程序,在每个路由处理之前或之后执行特定的操作。
参数:
- request (Request): FastAPI请求对象。
描述:
- 该方法在每个路由处理之前被调用,用于记录操作日志。
返回:
- Response: FastAPI响应对象。
"""
start_time = time.time()
# 请求前的处理
response: Response = await original_route_handler(request)
# 请求后的处理(异步写入操作日志,不阻塞响应)
if not settings.OPERATION_LOG_RECORD:
return response
if request.method not in settings.OPERATION_RECORD_METHOD:
return response
route: APIRoute = request.scope.get("route", None)
if route and route.name in settings.IGNORE_OPERATION_FUNCTION:
return response
# ── 收集日志数据(在请求上下文中同步完成)──
try:
payload = b"{}"
req_content_type = request.headers.get("Content-Type", "")
oper_param: dict[str, Any] = {}
if req_content_type and req_content_type.startswith(
("multipart/form-data", "application/x-www-form-urlencoded")
):
form_data = await request.form()
oper_param["form"] = dict(form_data.items())
else:
payload = await request.body()
if payload:
try:
oper_param["body"] = json.loads(payload.decode())
except (json.JSONDecodeError, UnicodeDecodeError):
oper_param["body"] = payload.decode("utf-8", errors="ignore")
path_params = request.path_params
if path_params:
oper_param["path_params"] = dict(path_params)
log_payload = json.dumps(oper_param, ensure_ascii=False)
if len(log_payload) > 2000:
log_payload = "请求参数过长"
response_data = (
response.body
if "application/json" in response.headers.get("Content-Type", "")
else b"{}"
)
process_time = f"{(time.time() - start_time):.2f}s"
current_user_id = request.scope.get("user_id")
# 构造日志数据字典,传给后台任务
log_data: dict[str, Any] = {
"request_path": request.url.path,
"request_method": request.method,
"request_payload": log_payload,
"response_code": response.status_code,
"response_json": (
response_data.decode()
if isinstance(response_data, (bytes, bytearray))
else str(response_data)
),
"process_time": process_time,
"description": route.summary if route else "",
"created_id": current_user_id,
"updated_id": current_user_id,
}
# ── 后台异步写入,不阻塞响应 ──
asyncio.create_task(_write_operation_log_async(log_data))
except Exception:
logger.warning(f"操作日志采集异常: {request.url.path}", exc_info=True)
return response
return custom_route_handler