From f1b6f56f3efad98d3fa412fad30496636ac94a14 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Sun, 16 Mar 2025 00:06:45 +0800 Subject: [PATCH] update --- app/api/endpoints/user.py | 2 + app/core/logger.py | 39 +++++++++-- app/core/security.py | 1 - app/middleware/request_logger.py | 116 +++++++++++++++++++------------ app/models/user.py | 6 +- jobs.sqlite | Bin 24576 -> 24576 bytes 6 files changed, 112 insertions(+), 52 deletions(-) diff --git a/app/api/endpoints/user.py b/app/api/endpoints/user.py index bf39efc..98ede7d 100644 --- a/app/api/endpoints/user.py +++ b/app/api/endpoints/user.py @@ -197,7 +197,9 @@ async def password_login( request: Request = None ): """密码登录""" + print(f"login_data: {login_data}") user = db.query(UserDB).filter(UserDB.phone == login_data.phone).first() + print(f"user: {user}") if not user: return error_response(code=401, message="用户不存在") diff --git a/app/core/logger.py b/app/core/logger.py index a56fc7a..70cb805 100644 --- a/app/core/logger.py +++ b/app/core/logger.py @@ -3,20 +3,51 @@ from app.models.database import SessionLocal from app.models.request_log import RequestLogDB import json from threading import Thread +import logging +import traceback + def save_request_log(log_data: Dict[str, Any]): """保存请求日志到数据库""" - db = SessionLocal() + db = None try: + db = SessionLocal() + + # 确保headers和body可以序列化为JSON + if 'headers' in log_data and log_data['headers']: + try: + # 尝试将headers转换为JSON字符串再解析回来,确保可序列化 + json.dumps(log_data['headers']) + except (TypeError, OverflowError): + # 如果无法序列化,则转换为字符串 + log_data['headers'] = str(log_data['headers']) + + if 'body' in log_data and log_data['body']: + try: + # 尝试将body转换为JSON字符串再解析回来,确保可序列化 + json.dumps(log_data['body']) + except (TypeError, OverflowError): + # 如果无法序列化,则转换为字符串 + log_data['body'] = str(log_data['body']) + + # 创建日志记录 log = RequestLogDB(**log_data) db.add(log) db.commit() + print(f"请求日志已保存: {log_data['path']}") except Exception as e: - db.rollback() print(f"保存日志失败: {str(e)}") + print(traceback.format_exc()) + if db: + db.rollback() finally: - db.close() + if db: + db.close() def log_request_async(log_data: Dict[str, Any]): """在新线程中异步处理日志""" - Thread(target=save_request_log, args=(log_data,), daemon=True).start() \ No newline at end of file + try: + Thread(target=save_request_log, args=(log_data,), daemon=True).start() + except Exception as e: + print(f"启动日志线程失败: {str(e)}") + print(traceback.format_exc()) \ No newline at end of file diff --git a/app/core/security.py b/app/core/security.py index e8802e6..8fd82bd 100644 --- a/app/core/security.py +++ b/app/core/security.py @@ -26,7 +26,6 @@ def verify_token(token: str) -> Optional[str]: try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) sub: str = payload.get("sub") - print(f"payload: {payload}") return sub except JWTError: return None diff --git a/app/middleware/request_logger.py b/app/middleware/request_logger.py index 15c1d8c..e7f9732 100644 --- a/app/middleware/request_logger.py +++ b/app/middleware/request_logger.py @@ -5,6 +5,12 @@ from app.core.logger import log_request_async from app.core.security import verify_token, decode_jwt import json import copy +import asyncio +from starlette.responses import JSONResponse +import logging + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) class RequestLoggerMiddleware(BaseHTTPMiddleware): LOGGED_METHODS = {"POST", "PUT", "DELETE"} @@ -26,6 +32,17 @@ class RequestLoggerMiddleware(BaseHTTPMiddleware): filtered_data[field] = "***" return filtered_data + def filter_headers(self, headers: dict) -> dict: + """过滤敏感请求头""" + filtered_headers = copy.deepcopy(headers) + sensitive_headers = ["authorization", "cookie"] + + for header in sensitive_headers: + if header in filtered_headers: + filtered_headers[header] = "***" + + return filtered_headers + async def dispatch(self, request: Request, call_next): method = request.method @@ -34,54 +51,65 @@ class RequestLoggerMiddleware(BaseHTTPMiddleware): start_time = time.time() path = request.url.path - headers = dict(request.headers) + + # 不记录健康检查请求 + if path.endswith("/health") or path.endswith("/ping"): + return await call_next(request) + + # 过滤敏感请求头 + headers = self.filter_headers(dict(request.headers)) query_params = dict(request.query_params) - # 获取并过滤请求体 + # 创建请求体的副本,而不消费原始请求体 body = None try: + # 保存原始请求体内容 body_bytes = await request.body() + + # 重要:设置_body属性,使FastAPI可以再次读取请求体 + request._body = body_bytes + if body_bytes: - body = json.loads(body_bytes) - body = self.filter_sensitive_data(body, path) - print(f"请求体: {body}") - except: - pass + try: + body_str = body_bytes.decode() + body = json.loads(body_str) + body = self.filter_sensitive_data(body, path) + except json.JSONDecodeError: + body = {"raw": "无法解析的JSON数据"} + except UnicodeDecodeError: + body = {"raw": "无法解码的二进制数据"} + except Exception as e: + logger.error(f"读取请求体异常: {str(e)}") + body = {"error": "读取请求体异常"} - # 从 Authorization 头获取 token - # token = None - # auth_header = headers.get('authorization') - # if auth_header and auth_header.startswith('Bearer '): - # token = auth_header.split(' ')[1] - - # # 从 token 获取用户信息 - # user_id = None - # if token: - # try: - # payload = decode_jwt(token) - # if payload: - # user_id = payload.get("phone") - # except: - # pass - - # 处理请求 - response = await call_next(request) - - # 计算响应时间 - response_time = int((time.time() - start_time) * 1000) - - # 异步记录日志 - log_data = { - "path": path, - "method": method, - "headers": headers, - "query_params": query_params, - "body": body, - # "user_id": user_id, - "ip_address": request.client.host, - "status_code": response.status_code, - "response_time": response_time - } - log_request_async(log_data) - - return response \ No newline at end of file + # 处理请求,添加超时保护 + try: + response = await call_next(request) + + # 计算响应时间 + response_time = int((time.time() - start_time) * 1000) + + # 异步记录日志,捕获可能的异常 + try: + log_data = { + "path": path, + "method": method, + "headers": headers, + "query_params": query_params, + "body": body, + "ip_address": request.client.host if hasattr(request, "client") and request.client else "unknown", + "status_code": response.status_code, + "response_time": response_time + } + log_request_async(log_data) + except Exception as e: + logger.error(f"记录请求日志异常: {str(e)}") + + return response + + except Exception as e: + logger.error(f"请求处理异常: {str(e)}") + return JSONResponse( + status_code=500, + content={"code": 500, "message": "服务器内部错误"} + ) \ No newline at end of file diff --git a/app/models/user.py b/app/models/user.py index 77539c8..3ffc902 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -112,9 +112,9 @@ class UserUpdate(BaseModel): extra = "forbid" # 禁止额外字段 class UserPasswordLogin(BaseModel): - phone: str = Field(..., pattern="^1[3-9]\d{9}$") - password: str = Field(..., min_length=6, max_length=20) - role: UserRole = Field(default=UserRole.DELIVERYMAN) + phone: str = Field(..., pattern="^1[3-9]\d{9}$", description="手机号") + password: str = Field(..., min_length=6, max_length=20, description="密码") + role: UserRole = Field(default=UserRole.ADMIN, description="角色") class ChangePasswordRequest(BaseModel): phone: str = Field(..., pattern="^1[3-9]\d{9}$") diff --git a/jobs.sqlite b/jobs.sqlite index 0bc813c9ea3a38ebe8a6899ad0a1e9ea601d90c6..26d5ac0318728b263237340d44bc6c3668a2e5c0 100644 GIT binary patch delta 923 zcmZoTz}Rqrae_4C$B8n|j2|~9EaT@eV&Lv$H)7hku~CJcqa%`;)s|&K&E$>x5*+DQ zS93J5PSu_4=q@waM|_bMOF?Bx)s!Bl_<|{ur)c!>Iu>Up>IY{e=A~yOW=`>aQRvOk z!|dZ5GNoj)y~IsMiOCCn#S~?GSab99N;0No$aiEY#7@aj(QKSjJ0(NiuQ(~OG$})3 za)6|Ch-?pYN@68WH9ef+sj1m+nW;G`K&!z9Xk^Itu#^KuacY{pUsB3mwuc*NaD0AR zJU+Ej2qQD{ON)R;c7oliGpa)rU62Xj8uT*>W@UN=s7V zT798Ls{3UGc*|u3ddp$xC{9hz&r5;3A4LZPPzM88N9yEFQl{*(Ju*e5dGVl#nan1g zqsXt3A>i9k019#EU|@((37XOd#HD&2`An?NEEAO`H%copIsoJDH+9dBH~Sm2^C Z-c-Qx)+jACxm~_U7)!iu{w2@m004}AO-ld( delta 141 zcmZoTz}Rqrae_4C&WSS4j5{|bEaT_kVBoG~=V02ovGErhM@Jqrt1Zh!waFi)BskKq zuI6Z9o$55%(OqV8nfRj3z0LtllUK_LOupu)&MKD?<}Ei_(k-7+X7YM*xyjtpS(EwQ mwVAIji`Z=IF3Sv8$OTl$1yVSf)w^TzJ$X(>iOs*{*&F~Bu`Z?n