feat: 宇之然平台 v2.0 - 完整重构版

核心功能:
- 新增 JWT 认证系统,支持管理员登录/登出
- 前后端合并为单一 FastAPI 应用 (端口 8001)
- 系统概览页:6 个统计卡片,点击跳转筛选
- 选题管理页:批量操作 (刷新/创作/优化),时间列展示
- 系统日志页:整合日志查看功能
- 用户管理页:管理员可创建/删除用户
- 移动端适配:响应式布局,底部导航栏
- 标题居中显示

技术改进:
- 添加 generated_at 字段支持创作时间记录
- 状态更新时自动同步 updated_at
- 所有 API 路由添加 JWT 认证保护
- 前端 authFetch 封装自动附加 Token
- 升级 FastAPI 0.136, Pydantic 2.13 等依赖

修复:
- 修复 API 500 错误 (数据库列缺失)
- 修复 formatRelativeTime 未定义错误
- 修复登录 Token 存储和自动附加逻辑
This commit is contained in:
lt
2026-04-27 10:01:33 +08:00
parent 3cb2df51c8
commit 2bd3c83cf5
14 changed files with 1241 additions and 645 deletions
+35 -29
View File
@@ -1,23 +1,24 @@
import logging
from fastapi import FastAPI, Depends, HTTPException
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
import os
from .database import engine, get_db, init_db
from .models import Base
from .api import topics, system, articles, publisher
from .initial_data import import_topics_from_json
from .api import topics, system, articles, publishing, auth, admin, audit
from .initial_data import import_initial_data
app = FastAPI(title="宇之然内容创作平台", version="0.1.0")
logger = logging.getLogger(__name__)
# CORS - 生产环境应限制 origins
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # TODO: 生产环境改为具体域名
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
@@ -26,40 +27,45 @@ app.add_middleware(
# 初始化数据库
Base.metadata.create_all(bind=engine)
init_db()
import_topics_from_json() # 首次自动导入
import_initial_data()
# 注册路由
# 注册 API 路由
app.include_router(topics.router)
app.include_router(system.router)
app.include_router(articles.router)
app.include_router(publisher.router)
app.include_router(publishing.router)
app.include_router(auth.router)
app.include_router(admin.router)
app.include_router(audit.router)
# 挂载前端静态文件
# 挂载前端
FRONTEND_DIR = Path(__file__).parent.parent.parent / "frontend"
STATIC_DIR = FRONTEND_DIR / "static"
# 检查前端文件是否存在,若不存在下载Element Plus等依赖
if not FRONTEND_DIR.exists():
FRONTEND_DIR.mkdir(parents=True, exist_ok=True)
logger = logging.getLogger(__name__)
logger.warning(f"Frontend dir not found: {FRONTEND_DIR}, will serve API only")
# 默认静态文件服务(若前端存在)
if FRONTEND_DIR.exists() and (FRONTEND_DIR / "index.html").exists():
app.mount("/", StaticFiles(directory=str(FRONTEND_DIR), html=True), name="frontend")
if STATIC_DIR.exists():
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
logging.getLogger(__name__).info(f"Frontend mounted at / from {FRONTEND_DIR}")
logger.info(f"Frontend mounted at / from {FRONTEND_DIR}")
# 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",
"version": "0.1.0",
"docs": "/docs",
"frontend_missing": str(FRONTEND_DIR)
}
return {"service": "API only", "docs": "/docs"}
if __name__ == "__main__":
import uvicorn
uvicorn.run("app.main:app", host="0.0.0.0", port=8000, reload=True)
uvicorn.run("app.main:app", host="0.0.0.0", port=8001)