upload project source code
This commit is contained in:
148
后端源码/yifan.action-ai.cn/app/core/router_class.py
Normal file
148
后端源码/yifan.action-ai.cn/app/core/router_class.py
Normal file
@@ -0,0 +1,148 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
import json
|
||||
from typing import Any, Callable, Coroutine
|
||||
from fastapi import Request, Response
|
||||
from fastapi.routing import APIRoute
|
||||
from user_agents import parse
|
||||
|
||||
from app.core.database import async_db_session
|
||||
from app.config.setting import settings
|
||||
from app.utils.ip_local_util import IpLocalUtil
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.api.v1.module_system.log.schema import OperationLogCreateSchema
|
||||
from app.api.v1.module_system.log.service import OperationLogService
|
||||
|
||||
"""
|
||||
在 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.name in settings.IGNORE_OPERATION_FUNCTION:
|
||||
return response
|
||||
|
||||
user_agent = parse(request.headers.get("user-agent"))
|
||||
payload = b"{}"
|
||||
req_content_type = request.headers.get("Content-Type", "")
|
||||
|
||||
if req_content_type and (
|
||||
req_content_type.startswith('multipart/form-data') or req_content_type.startswith('application/x-www-form-urlencoded')
|
||||
):
|
||||
form_data = await request.form()
|
||||
oper_param = '\n'.join([f'{k}: {v}' for k, v in form_data.items()])
|
||||
payload = oper_param # 直接使用字符串格式的参数
|
||||
else:
|
||||
payload = await request.body()
|
||||
path_params = request.path_params
|
||||
oper_param = {}
|
||||
|
||||
# 处理请求体数据
|
||||
if payload:
|
||||
try:
|
||||
oper_param['body'] = json.loads(payload.decode())
|
||||
except (json.JSONDecodeError, UnicodeDecodeError):
|
||||
oper_param['body'] = payload.decode('utf-8', errors='ignore')
|
||||
|
||||
# 处理路径参数
|
||||
if path_params:
|
||||
oper_param['path_params'] = dict(path_params)
|
||||
|
||||
payload = json.dumps(oper_param, ensure_ascii=False)
|
||||
|
||||
# 日志表请求参数字段长度最大为2000,因此在此处判断长度
|
||||
if len(payload) > 2000:
|
||||
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"
|
||||
|
||||
# 获取当前用户ID,如果是登录接口则为空
|
||||
log_type = 1 # 1:登录日志 2:操作日志
|
||||
current_user_id = None
|
||||
|
||||
# 优化:只在操作日志场景下获取current_user_id
|
||||
if "user_id" in request.scope:
|
||||
current_user_id = request.scope.get("user_id")
|
||||
log_type = 2
|
||||
|
||||
request_ip = None
|
||||
x_forwarded_for = request.headers.get('X-Forwarded-For')
|
||||
if x_forwarded_for:
|
||||
# 取第一个 IP 地址,通常为客户端真实 IP
|
||||
request_ip = x_forwarded_for.split(',')[0].strip()
|
||||
else:
|
||||
# 若没有 X-Forwarded-For 头,则使用 request.client.host
|
||||
if request.client:
|
||||
request_ip = request.client.host
|
||||
|
||||
login_location = await IpLocalUtil.get_ip_location(request_ip) if request_ip else None
|
||||
|
||||
# 判断请求是否来自api文档
|
||||
referer = request.headers.get('referer')
|
||||
request_from_swagger = referer and referer.endswith('docs')
|
||||
request_from_redoc = referer and referer.endswith('redoc')
|
||||
|
||||
if request_from_swagger or request_from_redoc:
|
||||
# 如果请求来自api文档,则不记录日志
|
||||
pass
|
||||
else:
|
||||
async with async_db_session() as session:
|
||||
async with session.begin():
|
||||
auth = AuthSchema(db=session)
|
||||
await OperationLogService.create_log_service(data=OperationLogCreateSchema(
|
||||
type = log_type,
|
||||
request_path = request.url.path,
|
||||
request_method = request.method,
|
||||
request_payload = payload,
|
||||
request_ip = request_ip,
|
||||
login_location=login_location,
|
||||
request_os = user_agent.os.family,
|
||||
request_browser = user_agent.browser.family,
|
||||
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,
|
||||
created_id = current_user_id,
|
||||
updated_id = current_user_id,
|
||||
), auth = auth)
|
||||
|
||||
return response
|
||||
|
||||
return custom_route_handler
|
||||
Reference in New Issue
Block a user