from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles import os import logging from app.core.config import settings from app.api.v1 import ( auth, client, admin, analytics, admin_config, feature, checkin, user_profile, stats, algorithm, logs, ai_chat, ai_pattern, ai_identify, ) from app.db import init_db from datetime import datetime from pathlib import Path app = FastAPI(title=settings.PROJECT_NAME) log = logging.getLogger("designercep.startup") SERVER_DIR = Path(__file__).resolve().parents[2] ARCHIVES_DIR = SERVER_DIR / "archives" # Ensure archives directory exists os.makedirs(ARCHIVES_DIR, exist_ok=True) # ========== CORS 配置 ========== IS_DEV = settings.ENV == "development" if IS_DEV: # 开发环境:保持宽松 print("⚠️ Running in Development Mode: CORS allows *") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) else: # 生产环境:严格配置 allowed_origins = settings.ALLOWED_ORIGINS.split(",") print(f"🔒 Running in Production Mode: CORS allowed origins: {allowed_origins}") app.add_middleware( CORSMiddleware, allow_origins=allowed_origins, allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_headers=["*"], ) # ✅ CEP 环境特殊处理 (Origin: null or cep://) @app.middleware("http") async def cep_cors_middleware(request: Request, call_next): origin = request.headers.get("origin") # CEP 的 Origin 是 null 或 cep:// if origin in ["null", None] or (origin and origin.startswith("cep://")): response = await call_next(request) response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Credentials"] = "true" response.headers["Access-Control-Allow-Methods"] = ( "GET, POST, PUT, DELETE, OPTIONS" ) response.headers["Access-Control-Allow-Headers"] = "*" return response return await call_next(request) app.include_router( auth.router, prefix=f"{settings.API_V1_STR}/auth", tags=["authentication"] ) app.include_router( client.router, prefix=f"{settings.API_V1_STR}/client", tags=["client"] ) app.include_router(admin.router, prefix=f"{settings.API_V1_STR}/admin", tags=["admin"]) app.include_router( analytics.router, prefix=f"{settings.API_V1_STR}/analytics", tags=["analytics"] ) # 新增路由 app.include_router( admin_config.router, prefix=settings.API_V1_STR, tags=["admin-config"] ) app.include_router(feature.router, prefix=settings.API_V1_STR, tags=["feature"]) app.include_router(checkin.router, prefix=settings.API_V1_STR, tags=["checkin"]) app.include_router( user_profile.router, prefix=settings.API_V1_STR, tags=["user-profile"] ) app.include_router(stats.router, prefix=settings.API_V1_STR, tags=["stats"]) app.include_router(algorithm.router, prefix=settings.API_V1_STR, tags=["algorithm"]) app.include_router(logs.router, prefix=settings.API_V1_STR, tags=["logs"]) app.include_router(ai_chat.router, prefix=settings.API_V1_STR, tags=["ai-chat"]) app.include_router(ai_pattern.router, prefix=settings.API_V1_STR, tags=["ai-pattern"]) app.include_router(ai_identify.router, prefix=settings.API_V1_STR, tags=["ai-identify"]) # Health Check @app.get("/health") def health_check(): return { "status": "healthy", "timestamp": datetime.now().isoformat(), "env": "development" if IS_DEV else "production", } # ========== Static Files (Development Only) ========== if IS_DEV: # Mount archives directory for download app.mount("/download", StaticFiles(directory=ARCHIVES_DIR), name="download") else: print( "ℹ️ Production Mode: Static files are NOT mounted by FastAPI (handled by Caddy/Nginx)." ) @app.get("/") def read_root(): from fastapi.responses import RedirectResponse if IS_DEV: # 重定向到 Shell 登录页 return RedirectResponse(url="/shell/index.html") return {"message": "DesignerCEP API is running"} @app.on_event("startup") def on_startup(): # 应用启动时初始化数据库(创建表) init_db() for warning in settings.runtime_warnings(): log.warning("[Config] %s", warning) if __name__ == "__main__": import uvicorn uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)