1、增加调用 AI 视频生成能力和虚拟素材库管理的对外api

2、增加后台apikkey管理
3、增加apikey单独的模型定价
4、增加apikey调用情况
5、完善所有数据的注释增加
This commit is contained in:
2026-08-06 13:13:28 +08:00
parent a55d4d649c
commit 0c511f3451
102 changed files with 13986 additions and 41 deletions
+105 -1
View File
@@ -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
@@ -554,12 +554,116 @@ 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"):
raise exc # 交给其他处理器
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"}