|
@@ -1,9 +1,10 @@
|
|
|
from fastapi import FastAPI, HTTPException, Request
|
|
from fastapi import FastAPI, HTTPException, Request
|
|
|
from fastapi.responses import JSONResponse
|
|
from fastapi.responses import JSONResponse
|
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
+from starlette.responses import Response, StreamingResponse
|
|
|
from utils.params import DecodeContentParam, PatternContentParam, TopicSearchParam
|
|
from utils.params import DecodeContentParam, PatternContentParam, TopicSearchParam
|
|
|
from dotenv import load_dotenv, find_dotenv
|
|
from dotenv import load_dotenv, find_dotenv
|
|
|
-from typing import List, Dict, Any, Optional
|
|
|
|
|
|
|
+from typing import Any, Dict, List, Optional
|
|
|
|
|
|
|
|
from tasks.decode import begin_decode_task
|
|
from tasks.decode import begin_decode_task
|
|
|
from tasks.detail import get_decode_detail_by_task_id
|
|
from tasks.detail import get_decode_detail_by_task_id
|
|
@@ -15,6 +16,18 @@ import sys
|
|
|
|
|
|
|
|
logger.add(sink=sys.stderr, level="ERROR", backtrace=True, diagnose=True)
|
|
logger.add(sink=sys.stderr, level="ERROR", backtrace=True, diagnose=True)
|
|
|
|
|
|
|
|
|
|
+# 接口访问日志(与仅 ERROR 的 sink 并存,默认 stderr 仍会输出 INFO)
|
|
|
|
|
+_MAX_ACCESS_LOG_BYTES = 8192
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _preview_bytes(data: bytes) -> str:
|
|
|
|
|
+ if not data:
|
|
|
|
|
+ return ""
|
|
|
|
|
+ text = data.decode("utf-8", errors="replace")
|
|
|
|
|
+ if len(text) > _MAX_ACCESS_LOG_BYTES:
|
|
|
|
|
+ return text[:_MAX_ACCESS_LOG_BYTES] + f"...<truncated len={len(text)}>"
|
|
|
|
|
+ return text
|
|
|
|
|
+
|
|
|
# 响应消息映射
|
|
# 响应消息映射
|
|
|
RESPONSE_MSG_MAP = {
|
|
RESPONSE_MSG_MAP = {
|
|
|
0: "success",
|
|
0: "success",
|
|
@@ -35,6 +48,56 @@ app.add_middleware(
|
|
|
allow_headers=["*"],
|
|
allow_headers=["*"],
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+
|
|
|
|
|
+@app.middleware("http")
|
|
|
|
|
+async def api_access_log_middleware(request: Request, call_next):
|
|
|
|
|
+ """记录每个接口的请求(路径、查询、body)与响应(状态码、body)。"""
|
|
|
|
|
+ body_bytes = await request.body()
|
|
|
|
|
+
|
|
|
|
|
+ async def receive() -> dict:
|
|
|
|
|
+ return {"type": "http.request", "body": body_bytes, "more_body": False}
|
|
|
|
|
+
|
|
|
|
|
+ wrapped = Request(request.scope, receive)
|
|
|
|
|
+
|
|
|
|
|
+ req_body_preview = _preview_bytes(body_bytes) if body_bytes else ""
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "api_request method={} path={} query={} body={}",
|
|
|
|
|
+ request.method,
|
|
|
|
|
+ request.url.path,
|
|
|
|
|
+ str(request.query_params),
|
|
|
|
|
+ req_body_preview if req_body_preview else "<empty>",
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ response = await call_next(wrapped)
|
|
|
|
|
+
|
|
|
|
|
+ if isinstance(response, StreamingResponse):
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "api_response status={} path={} body=<streaming skipped>",
|
|
|
|
|
+ response.status_code,
|
|
|
|
|
+ request.url.path,
|
|
|
|
|
+ )
|
|
|
|
|
+ return response
|
|
|
|
|
+
|
|
|
|
|
+ resp_chunks: List[bytes] = []
|
|
|
|
|
+ async for chunk in response.body_iterator:
|
|
|
|
|
+ resp_chunks.append(chunk)
|
|
|
|
|
+ resp_body = b"".join(resp_chunks)
|
|
|
|
|
+ resp_preview = _preview_bytes(resp_body) if resp_body else ""
|
|
|
|
|
+ logger.info(
|
|
|
|
|
+ "api_response status={} path={} body={}",
|
|
|
|
|
+ response.status_code,
|
|
|
|
|
+ request.url.path,
|
|
|
|
|
+ resp_preview if resp_preview else "<empty>",
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ return Response(
|
|
|
|
|
+ content=resp_body,
|
|
|
|
|
+ status_code=response.status_code,
|
|
|
|
|
+ headers=dict(response.headers),
|
|
|
|
|
+ media_type=response.media_type,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
@app.exception_handler(HTTPException)
|
|
@app.exception_handler(HTTPException)
|
|
|
async def http_exception_handler(request: Request, exc: HTTPException):
|
|
async def http_exception_handler(request: Request, exc: HTTPException):
|
|
|
"""统一处理 HTTPException,保证返回结构与其它接口一致"""
|
|
"""统一处理 HTTPException,保证返回结构与其它接口一致"""
|
|
@@ -76,6 +139,8 @@ def decode_content(param: DecodeContentParam):
|
|
|
code = res.get("code", -1)
|
|
code = res.get("code", -1)
|
|
|
task_id = res.get("task_id")
|
|
task_id = res.get("task_id")
|
|
|
reason = res.get("reason", "")
|
|
reason = res.get("reason", "")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
|
|
|
return _build_api_response(
|
|
return _build_api_response(
|
|
|
code=code,
|
|
code=code,
|