Files
yu-zhi-ran/platform/backend/app/main.py
T
Yuzhiran Dev 24e46eced5 feat: Product landing page at / with responsive design
- landing.html: Full product website (hero, features, how-it-works, platform coverage, pricing, FAQ, CTA, footer)
- main.py: Route / to landing.html (overrides StaticFiles default index.html)
- Workspace dashboard remains at /index.html
- Responsive: desktop nav, hamburger mobile menu, single-column on small screens
- No Vue/Element Plus dependency; standalone HTML+CSS+JS (~30KB)

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-16 08:27:38 +08:00

158 lines
5.5 KiB
Python

import logging
from fastapi import FastAPI, Depends, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session
from datetime import datetime
from pathlib import Path
from .database import engine, get_db, init_db
from .models import Base
from .api import topics, system, articles, publishing, auth, admin, audit, optimizer_logs, cases, task_logs, task_configs, prompt_configs, llm_configs, system_configs, topic_config, calendar, metrics, assets, tasks, platform_config, collector_mgmt, assistant, config_items, role_configs, menu_configs, search_providers, search_rankings
from .initial_data import import_initial_data
from .core.scheduler import scheduler
app = FastAPI(title="宇之然内容创作平台", version="0.1.0")
logger = logging.getLogger(__name__)
@app.on_event("startup")
def start_scheduler():
scheduler.start()
logger.info("Background scheduler started")
@app.on_event("shutdown")
def stop_scheduler():
scheduler.shutdown()
logger.info("Background scheduler shut down")
# CORS - local-only origins
_local_origins = [
"http://localhost:8001",
"http://127.0.0.1:8001",
"http://localhost:8000",
"http://127.0.0.1:8000",
]
app.add_middleware(
CORSMiddleware,
allow_origins=_local_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 初始化数据库
try:
Base.metadata.create_all(bind=engine)
except Exception:
pass
init_db()
import_initial_data()
# 清理超时任务(运行超过 1 小时的任务视为故障)
from datetime import datetime, timezone, timedelta
from .database import SessionLocal
from .models import ContentTask
try:
cleanup_db = SessionLocal()
cutoff = datetime.now(timezone.utc) - timedelta(hours=1)
stale = cleanup_db.query(ContentTask).filter(
ContentTask.status == "running",
ContentTask.started_at.isnot(None),
ContentTask.started_at < cutoff
).all()
for t in stale:
t.status = "failed"
t.error_msg = "任务超时(服务重启导致状态丢失)"
if t.started_at:
t.duration = int((datetime.now(timezone.utc) - t.started_at).total_seconds())
logger.warning(f"标记超时任务: {t.task_id}")
if stale:
cleanup_db.commit()
logger.info(f"已清理 {len(stale)} 个超时任务")
cleanup_db.close()
except Exception as e:
logger.warning(f"任务清理失败: {e}")
# 注册 API 路由
app.include_router(topics.router)
app.include_router(system.router)
app.include_router(articles.router)
app.include_router(publishing.router)
app.include_router(auth.router)
app.include_router(admin.router)
app.include_router(audit.router)
app.include_router(optimizer_logs.router)
app.include_router(cases.router)
app.include_router(task_configs.router)
app.include_router(prompt_configs.router)
app.include_router(task_logs.router)
app.include_router(llm_configs.router)
app.include_router(system_configs.router)
app.include_router(topic_config.router)
app.include_router(calendar.router)
app.include_router(metrics.router)
app.include_router(assets.router)
app.include_router(tasks.router)
app.include_router(platform_config.router)
app.include_router(collector_mgmt.router)
app.include_router(assistant.router)
app.include_router(config_items.router)
app.include_router(role_configs.router)
app.include_router(menu_configs.router)
app.include_router(menu_configs.public_router)
app.include_router(search_providers.router)
app.include_router(search_rankings.router)
# 挂载自动生成的图片(必须先于前端根挂载)
PROJECT_ROOT_DIR = Path(__file__).parent.parent.parent.parent
AUTOMATION_IMAGES_DIR = PROJECT_ROOT_DIR / "automation" / "images"
if AUTOMATION_IMAGES_DIR.exists():
app.mount("/automation/images", StaticFiles(directory=str(AUTOMATION_IMAGES_DIR)), name="images")
logger.info(f"Images mounted at /automation/images from {AUTOMATION_IMAGES_DIR}")
# 挂载前端
FRONTEND_DIR = Path(__file__).parent.parent.parent / "frontend"
if FRONTEND_DIR.exists() and (FRONTEND_DIR / "landing.html").exists():
# Landing page at / (overrides StaticFiles default index.html)
@app.get("/")
async def landing_page():
return FileResponse(str(FRONTEND_DIR / "landing.html"), media_type="text/html")
@app.get("/landing.html")
async def landing_direct():
return FileResponse(str(FRONTEND_DIR / "landing.html"), media_type="text/html")
# Workspace dashboard at /index.html (existing management UI)
app.mount("/", StaticFiles(directory=str(FRONTEND_DIR), html=True), name="frontend")
logger.info(f"Frontend mounted at / from {FRONTEND_DIR} (landing at /)")
# SW 特殊处理
sw_path = FRONTEND_DIR / "sw.js"
if sw_path.exists():
@app.get("/sw.js")
async def service_worker():
return FileResponse(
sw_path,
media_type="application/javascript",
headers={"Cache-Control": "no-cache", "Service-Worker-Allowed": "/"}
)
# 离线页面
offline_path = FRONTEND_DIR / "offline.html"
if offline_path.exists():
@app.get("/offline.html")
async def offline_page():
return FileResponse(offline_path, media_type="text/html")
else:
@app.get("/")
def root():
return {"service": "API only", "docs": "/docs"}
if __name__ == "__main__":
import uvicorn
uvicorn.run("app.main:app", host="0.0.0.0", port=8001)