- 产品编辑入口统一走 ProductFormFull(卖点/风格/人群/品牌词全字段); 修复开任务页 <form> 套 <form> 致"编辑产品"报错、改不了、跳回首个产品 - dashboard 入口卡片对齐实际路由: 系统管理(/config) 与 工作配置(/settings) 分开; settings ?tab=products 直达改用挂载后读 URL, 消除 hydration mismatch - 新增用户管理(users API/admin service/改密页) + alembic 022/023/024 - 上线部署: Dockerfile / docker-compose.prod+https / nginx https / .env.example - A8 三套正交叙事(痛点/场景/成分背书) + beige 调色去AI化 + 飞轮 text_import 高权重信号 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
121 lines
4.6 KiB
Python
121 lines
4.6 KiB
Python
"""
|
||
main.py — FastAPI 应用入口
|
||
统一响应包络 / 全局异常处理 / 健康检查 / 路由注册
|
||
"""
|
||
|
||
import logging
|
||
import os
|
||
|
||
from fastapi import FastAPI, Request
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from fastapi.responses import JSONResponse
|
||
from fastapi.staticfiles import StaticFiles
|
||
|
||
from app.core.config import get_settings
|
||
from app.core.response import CloverHTTPException, ErrorCode
|
||
|
||
logger = logging.getLogger(__name__)
|
||
settings = get_settings()
|
||
|
||
|
||
def _cors_origins() -> list[str]:
|
||
origins = [
|
||
item.strip()
|
||
for item in (settings.CORS_ALLOW_ORIGINS or "").split(",")
|
||
if item.strip()
|
||
]
|
||
if settings.APP_ENV == "production":
|
||
return [origin for origin in origins if origin != "*"]
|
||
dev_origins = set(origins or ["http://localhost:3000"])
|
||
if "http://localhost:3000" in dev_origins:
|
||
dev_origins.add("http://127.0.0.1:3000")
|
||
if "http://127.0.0.1:3000" in dev_origins:
|
||
dev_origins.add("http://localhost:3000")
|
||
return sorted(dev_origins)
|
||
|
||
|
||
def create_app() -> FastAPI:
|
||
app = FastAPI(
|
||
title="Clover API",
|
||
version="0.1.0",
|
||
docs_url="/docs" if settings.APP_ENV != "production" else None,
|
||
redoc_url=None,
|
||
)
|
||
|
||
# ── CORS ──────────────────────────────────────────
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=_cors_origins(),
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
# ── 全局异常处理(统一包络)──────────────────────
|
||
@app.exception_handler(CloverHTTPException)
|
||
async def clover_http_exception_handler(request: Request, exc: CloverHTTPException):
|
||
return JSONResponse(
|
||
status_code=exc.status_code,
|
||
content={"code": exc.biz_code, "message": exc.biz_message},
|
||
)
|
||
|
||
@app.exception_handler(Exception)
|
||
async def generic_exception_handler(request: Request, exc: Exception):
|
||
logger.error("Unhandled exception: %s %s → %s", request.method, request.url, exc)
|
||
return JSONResponse(
|
||
status_code=500,
|
||
content={"code": ErrorCode.SERVER_ERROR, "message": "服务器内部错误"},
|
||
)
|
||
|
||
# ── 健康检查(Docker 健康检查用)──────────────────
|
||
@app.get("/health")
|
||
async def health():
|
||
return {"status": "ok"}
|
||
|
||
# ── 路由注册(每个模块路由独立文件,这里汇总注册)─
|
||
_register_routers(app)
|
||
|
||
# ── 静态文件(G4坑修复:图片 /uploads 路由)──────
|
||
uploads_dir = settings.UPLOAD_ABS_ROOT
|
||
os.makedirs(uploads_dir, exist_ok=True)
|
||
app.mount("/uploads", StaticFiles(directory=uploads_dir), name="uploads")
|
||
|
||
return app
|
||
|
||
|
||
def _register_routers(app: FastAPI) -> None:
|
||
from app.api.v1.auth import router as auth_router
|
||
from app.api.v1.users import router as users_router
|
||
from app.api.v1.api_keys import router as api_keys_router
|
||
from app.api.v1.products import router as products_router
|
||
from app.api.v1.product_images import router as product_images_router
|
||
from app.api.v1.benchmarks import router as benchmarks_router
|
||
from app.api.v1.tasks import router as tasks_router
|
||
from app.api.v1.task_actions import router as task_actions_router
|
||
from app.api.v1.review import router as review_router
|
||
from app.api.v1.delivery import router as delivery_router
|
||
from app.api.v1.stream import router as stream_router
|
||
from app.api.v1.workspaces import router as workspaces_router
|
||
from app.api.v1.fission import router as fission_router
|
||
from app.api.v1.exports import router as exports_router
|
||
|
||
prefix = "/api/v1"
|
||
app.include_router(auth_router, prefix=prefix)
|
||
app.include_router(users_router, prefix=prefix)
|
||
app.include_router(api_keys_router, prefix=prefix)
|
||
# 导出 router 必须在 products_router 之前:否则 /products/export 被 /products/{product_id} 吞掉
|
||
app.include_router(exports_router, prefix=prefix)
|
||
app.include_router(products_router, prefix=prefix)
|
||
app.include_router(product_images_router, prefix=prefix)
|
||
app.include_router(benchmarks_router, prefix=prefix)
|
||
app.include_router(tasks_router, prefix=prefix)
|
||
app.include_router(task_actions_router, prefix=prefix)
|
||
app.include_router(review_router, prefix=prefix)
|
||
app.include_router(delivery_router, prefix=prefix)
|
||
app.include_router(stream_router, prefix=prefix)
|
||
app.include_router(workspaces_router, prefix=prefix)
|
||
app.include_router(fission_router, prefix=prefix)
|
||
|
||
|
||
app = create_app()
|