You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

199 lines
7.2KB

  1. import json
  2. import time
  3. from fastapi import FastAPI, Request,HTTPException
  4. from starlette.middleware.base import BaseHTTPMiddleware
  5. from starlette.responses import JSONResponse
  6. from pydantic import BaseModel
  7. from datetime import datetime
  8. import logging
  9. from common.log import logger
  10. class Result(BaseModel):
  11. code: int
  12. message: str
  13. status: str
  14. class ResponseData(BaseModel):
  15. data: dict|list|None
  16. result: Result
  17. timestamp: str
  18. # 设置最大请求大小(100MB)
  19. MAX_REQUEST_SIZE = 100 * 1024 * 1024 # 100MB
  20. async def http_context_v1(request: Request, call_next):
  21. # 记录请求信息
  22. request_info = {
  23. "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
  24. "method": request.method,
  25. "url": str(request.url),
  26. "body": await request.body() if request.method in ["POST", "PUT", "PATCH"] else None,
  27. }
  28. logger.info(f"请求: {json.dumps(request_info, separators=(',', ':'), default=str, ensure_ascii=False)}")
  29. # 调用下一个中间件或路由处理程序
  30. response = await call_next(request)
  31. # 如果响应状态码为 200,则格式化响应为统一格式
  32. if response.status_code == 200:
  33. try:
  34. response_body = b""
  35. async for chunk in response.body_iterator:
  36. response_body += chunk
  37. response_body_str = response_body.decode("utf-8")
  38. business_data = json.loads(response_body_str)
  39. except Exception as e:
  40. business_data = {"error": f"Unable to decode response body: {str(e)}"}
  41. if "code" in business_data:
  42. message=business_data.get("message","请求失败!")
  43. result = ResponseData(
  44. data=None,
  45. result=Result(code=business_data.get("code",500), message=message, status="failed"),
  46. timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
  47. )
  48. else:
  49. # 构造统一格式的响应
  50. result = ResponseData(
  51. data=business_data,
  52. result=Result(code=200, message="请求成功!", status="succeed"),
  53. timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
  54. )
  55. response_info = {
  56. "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
  57. "status_code": response.status_code,
  58. "headers": dict(response.headers),
  59. "body": result.dict(),
  60. }
  61. logger.info(f"响应: {json.dumps(response_info, separators=(',', ':'), default=str, ensure_ascii=False)}")
  62. # 返回修改后的响应
  63. return JSONResponse(content=result.model_dump())
  64. else:
  65. message = "请求失败!"
  66. # 如果响应状态码不为 200,则记录响应信息
  67. try:
  68. response_body = b""
  69. async for chunk in response.body_iterator:
  70. response_body += chunk
  71. response_body_str = response_body.decode("utf-8")
  72. business_data = json.loads(response_body_str)
  73. except Exception as e:
  74. business_data = {"error": f"Unable to decode response body: {str(e)}"}
  75. # 根据不同状态码定制 message 字段
  76. if response.status_code == 404:
  77. message = e.detail
  78. elif response.status_code == 400:
  79. message = "请求参数错误"
  80. elif response.status_code == 500:
  81. message = "服务器内部错误"
  82. # 你可以根据不同的状态码设置更详细的错误消息
  83. # 构造统一格式的响应
  84. result = ResponseData(
  85. data={}, # 返回空数据
  86. result=Result(
  87. code=response.status_code,
  88. message=message, # 根据状态码返回详细信息
  89. status="failed"
  90. ),
  91. timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
  92. )
  93. response_info = {
  94. "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
  95. "status_code": response.status_code,
  96. "headers": dict(response.headers),
  97. "body": result.dict(),
  98. }
  99. logger.info(f"响应: {json.dumps(response_info, separators=(',', ':'), default=str, ensure_ascii=False)}")
  100. # 返回修改后的响应
  101. return JSONResponse(content=result.model_dump(), status_code=response.status_code)
  102. async def http_context(request: Request, call_next):
  103. # 检查请求大小
  104. content_length = request.headers.get("content-length")
  105. if content_length and int(content_length) > MAX_REQUEST_SIZE:
  106. return JSONResponse(
  107. content={
  108. "data": None,
  109. "result": {
  110. "code": 413,
  111. "message": "请求体过大,最大支持 100MB",
  112. "status": "failed",
  113. },
  114. "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
  115. },
  116. status_code=413, # 413 Payload Too Large
  117. )
  118. # 记录请求信息(只读取 body,防止重复读取导致错误)
  119. request_info = {
  120. "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
  121. "method": request.method,
  122. "url": str(request.url),
  123. "body": None, # 只在 POST/PUT/PATCH 时读取 body
  124. }
  125. if request.method in ["POST", "PUT", "PATCH"]:
  126. try:
  127. request_info["body"] = await request.body()
  128. except Exception as e:
  129. request_info["body"] = f"Error reading body: {str(e)}"
  130. logger.info(f"请求: {json.dumps(request_info, separators=(',', ':'), default=str, ensure_ascii=False)}")
  131. # 调用下一个中间件或路由
  132. response = await call_next(request)
  133. # 处理响应
  134. response_body = b""
  135. async for chunk in response.body_iterator:
  136. response_body += chunk
  137. response_body_str = response_body.decode("utf-8")
  138. try:
  139. business_data = json.loads(response_body_str)
  140. except Exception as e:
  141. business_data = {"error": f"无法解析响应体: {str(e)}"}
  142. # 构造统一格式的响应
  143. if response.status_code == 200:
  144. result = ResponseData(
  145. data=business_data,
  146. result=Result(code=200, message="请求成功!", status="succeed"),
  147. timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
  148. )
  149. else:
  150. message = "请求失败!"
  151. if response.status_code == 404:
  152. message = "资源未找到"
  153. elif response.status_code == 400:
  154. message = "请求参数错误"
  155. elif response.status_code == 500:
  156. message = "服务器内部错误"
  157. result = ResponseData(
  158. data=None,
  159. result=Result(code=response.status_code, message=message, status="failed"),
  160. timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f"),
  161. )
  162. response_info = {
  163. "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
  164. "status_code": response.status_code,
  165. "headers": dict(response.headers),
  166. "body": result.model_dump(),
  167. }
  168. logger.info(f"响应: {json.dumps(response_info, separators=(',', ':'), default=str, ensure_ascii=False)}")
  169. return JSONResponse(content=result.model_dump(), status_code=response.status_code)