mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-23 21:18:09 +00:00
1. 重构后端API路由、CRUD与模块结构,整合日志管理,移除废弃demo代码 2. 优化前端组件类型定义、样式与路由配置,修复权限判断逻辑 3. 调整默认排序规则、滚动条样式与工具类函数,更新依赖与配置文件 4. 修复多处类型不匹配与默认值问题,完善表单与菜单验证逻辑
144 lines
5.5 KiB
Python
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
|