merge main
This commit is contained in:
+109
-1
@@ -3,7 +3,7 @@ import logging
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, UploadFile, File, Form, Depends, HTTPException
|
||||
from fastapi import FastAPI, UploadFile, File, Form, Depends, HTTPException, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
@@ -518,12 +518,120 @@ def create_app() -> FastAPI:
|
||||
# Routes
|
||||
application.include_router(api_router, prefix="/api")
|
||||
application.include_router(api_router_v2, prefix="/api/v2")
|
||||
from app.api.v3 import api_router_v3
|
||||
application.include_router(api_router_v3, prefix="/api/v3")
|
||||
|
||||
# === API v3 请求日志中间件 ===
|
||||
import json as _json
|
||||
import time as _time
|
||||
from app.services.api_v3.logging_service import log_request, log_response, log_request_error
|
||||
|
||||
@application.middleware("http")
|
||||
async def v3_request_logger(request: Request, call_next):
|
||||
"""记录所有 /api/v3/ 请求和响应。"""
|
||||
if not str(request.url.path).startswith("/api/v3"):
|
||||
return await call_next(request)
|
||||
|
||||
start_time = _time.perf_counter()
|
||||
|
||||
# 提取 API Key ID
|
||||
auth_header = request.headers.get("Authorization", "")
|
||||
api_key_id = "unknown"
|
||||
if auth_header.startswith("Bearer "):
|
||||
api_key_id = auth_header[7:15] + "..."
|
||||
|
||||
# 读取请求体
|
||||
body = None
|
||||
if request.method in ("POST", "PUT", "PATCH"):
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
log_request(
|
||||
method=request.method,
|
||||
path=str(request.url.path),
|
||||
api_key_id=api_key_id,
|
||||
body=body,
|
||||
)
|
||||
|
||||
try:
|
||||
response = await call_next(request)
|
||||
except Exception as exc:
|
||||
duration_ms = int((_time.perf_counter() - start_time) * 1000)
|
||||
log_request_error(
|
||||
method=request.method,
|
||||
path=str(request.url.path),
|
||||
api_key_id=api_key_id,
|
||||
error=str(exc),
|
||||
)
|
||||
return JSONResponse(
|
||||
content={"code": 50000, "data": None, "message": f"服务器内部错误: {str(exc)[:200]}"},
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
duration_ms = int((_time.perf_counter() - start_time) * 1000)
|
||||
|
||||
# 读取响应体
|
||||
response_body = None
|
||||
try:
|
||||
response_body = _json.loads(response.body)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
log_response(
|
||||
method=request.method,
|
||||
path=str(request.url.path),
|
||||
api_key_id=api_key_id,
|
||||
status_code=response.status_code,
|
||||
body=response_body,
|
||||
duration_ms=duration_ms,
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
# === API v3 统一异常处理 ===
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
from app.services.api_v3.pricing_service import PricingNotConfiguredError
|
||||
|
||||
@application.exception_handler(HTTPException)
|
||||
async def v3_http_exception_handler(request: Request, exc: HTTPException):
|
||||
"""仅对 /api/v3/ 路径返回统一格式,HTTP 状态码固定 200。"""
|
||||
if not str(request.url.path).startswith("/api/v3"):
|
||||
# 非 v3 路径返回标准 HTTPException 响应,保持原始状态码
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content={"detail": exc.detail},
|
||||
)
|
||||
detail = exc.detail
|
||||
message = detail.get("message", str(detail)) if isinstance(detail, dict) else str(detail)
|
||||
code_map = {400: 40000, 401: 40100, 403: 40300, 404: 40400, 429: 42900, 422: 42200, 500: 50000, 504: 50400}
|
||||
code = code_map.get(exc.status_code, exc.status_code * 100)
|
||||
return JSONResponse(content={"code": code, "data": None, "message": message}, status_code=200)
|
||||
|
||||
@application.exception_handler(PricingNotConfiguredError)
|
||||
async def v3_pricing_not_configured_handler(request: Request, exc: PricingNotConfiguredError):
|
||||
if not str(request.url.path).startswith("/api/v3"):
|
||||
raise exc
|
||||
return JSONResponse(content={"code": 40001, "data": None, "message": str(exc)}, status_code=200)
|
||||
|
||||
@application.exception_handler(Exception)
|
||||
async def v3_general_exception_handler(request: Request, exc: Exception):
|
||||
if not str(request.url.path).startswith("/api/v3"):
|
||||
raise exc
|
||||
return JSONResponse(content={"code": 50000, "data": None, "message": f"服务器内部错误: {str(exc)[:200]}"}, status_code=200)
|
||||
|
||||
# Static files for uploads
|
||||
upload_dir = os.path.abspath(settings.UPLOAD_LOCAL_PATH)
|
||||
os.makedirs(upload_dir, exist_ok=True)
|
||||
application.mount("/uploads", StaticFiles(directory=upload_dir), name="uploads")
|
||||
|
||||
# 挂载 API v3 生成文件静态目录
|
||||
generate_dir = os.path.join(os.path.dirname(upload_dir), "generate")
|
||||
os.makedirs(generate_dir, exist_ok=True)
|
||||
application.mount("/generate", StaticFiles(directory=generate_dir), name="generate")
|
||||
|
||||
@application.get("/internal/health")
|
||||
async def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
Reference in New Issue
Block a user