Files
FastapiAdmin/backend/app/core/router_class.py
T
zhangtao 8949f6dc46 refactor(test, config, router): 整理代码结构与配置,优化导入顺序
1. 调整多个测试文件中TestClient的导入顺序,统一放到conftest导入之后
2. 删除后端配置中无用的WebSocket限流配置,统一使用全局200次/10秒的限流策略
3. 精简平台、系统模块的路由初始化注释文档
4. 优化conftest.py中的数据库初始化导入顺序
5. 调整初始化日志输出格式,更清晰易懂
6. 前端开发环境配置切回本地后端地址,注释远程服务地址
7. 重构路由类的变量命名,提升可读性
8. 简化动态路由热重载逻辑,移除冗余缓存变量
9. 重构注册路由的代码,统一限流配置,调整导入顺序和挂载逻辑
10. 删除冗余的v1接口层统一导出文件
2026-06-21 21:47:32 +08:00

88 lines
3.8 KiB
Python

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 starlette.background import BackgroundTask
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:
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(_auth).create(data=OperationLogCreateSchema(**log_data))
except Exception:
logger.exception("操作日志写入失败: path={}", log_data.get("request_path"))
class OperationLogRoute(APIRoute):
"""操作日志路由 — 自动记录请求/响应并后台异步写入"""
def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]:
original_route_handler = super().get_route_handler()
async def custom_route_handler(request: Request) -> Response:
start = time.time()
response: Response = await original_route_handler(request)
if not settings.OPERATION_LOG_RECORD or 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:
oper_param: dict[str, Any] = {}
content_type = request.headers.get("Content-Type", "")
if 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")
if request.path_params:
oper_param["path_params"] = dict(request.path_params)
log_payload = json.dumps(oper_param, ensure_ascii=False)
if len(log_payload) > 2000:
log_payload = "请求参数过长"
is_json = "application/json" in response.headers.get("Content-Type", "")
response_data = response.body if is_json else b"{}"
ctx = getattr(request.state, "ctx", None)
current_user_id = ctx.user_id if ctx else None
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(),
"process_time": f"{(time.time() - start):.2f}s",
"description": route.summary if route else "",
"created_id": current_user_id,
"updated_id": current_user_id,
}
response.background = BackgroundTask(_write_operation_log_async, log_data)
except Exception:
logger.warning("操作日志采集异常: {}", request.url.path, exc_info=True)
return response
return custom_route_handler