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
+146
View File
@@ -0,0 +1,146 @@
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session
from typing import List
import bcrypt
from ..database import get_db
from ..models import User
from ..schemas import UserCreate, UserUpdate, UserResponse
from ..core.audit_logger import audit_log
router = APIRouter(prefix="/api/admin", tags=["admin"])
def get_current_admin(request: Request, db: Session = Depends(get_db)):
"""依赖项:验证管理员权限"""
from .auth import verify_token
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
raise HTTPException(status_code=401, detail="未提供认证令牌")
token = auth_header.split(" ")[1]
user = verify_token(token, db)
if user.role != "admin":
raise HTTPException(status_code=403, detail="需要管理员权限")
return user
@router.get("/users", response_model=List[UserResponse])
def list_users(
request: Request,
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin)
):
"""获取用户列表(管理员)"""
users = db.query(User).all()
return [UserResponse.from_orm(u) for u in users]
@router.post("/users", response_model=UserResponse)
def create_user(
user_data: UserCreate,
request: Request,
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin)
):
"""创建新用户(管理员)"""
# 检查用户名是否已存在
existing = db.query(User).filter(User.username == user_data.username).first()
if existing:
raise HTTPException(status_code=400, detail="用户名已存在")
# bcrypt 密码哈希
hashed_password = bcrypt.hashpw(user_data.password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
user = User(
username=user_data.username,
password_hash=hashed_password,
role=user_data.role or "user"
)
db.add(user)
db.commit()
db.refresh(user)
audit_log(
action="create_user",
user=admin_user,
resource_type="user",
resource_id=str(user.id),
details={"username": user.username, "role": user.role},
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent", ""),
db=db
)
return UserResponse.from_orm(user)
@router.put("/users/{user_id}", response_model=UserResponse)
def update_user(
user_id: int,
user_update: UserUpdate,
request: Request,
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin)
):
"""更新用户(管理员)"""
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
changes = {}
if user_update.username is not None:
if user_update.username != user.username:
existing = db.query(User).filter(User.username == user_update.username).first()
if existing:
raise HTTPException(status_code=400, detail="用户名已存在")
changes["username"] = {"old": user.username, "new": user_update.username}
user.username = user_update.username
if user_update.password is not None:
changes["password"] = {"changed": True}
user.password_hash = bcrypt.hashpw(user_update.password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
if user_update.role is not None and user_update.role != user.role:
changes["role"] = {"old": user.role, "new": user_update.role}
user.role = user_update.role
db.commit()
db.refresh(user)
audit_log(
action="update_user",
user=admin_user,
resource_type="user",
resource_id=str(user.id),
details=changes,
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent", ""),
db=db
)
return UserResponse.from_orm(user)
@router.delete("/users/{user_id}")
def delete_user(
user_id: int,
request: Request,
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin)
):
"""删除用户(管理员)"""
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
if user.role == "admin":
raise HTTPException(status_code=400, detail="不能删除管理员用户")
username = user.username # 保存用于日志
db.delete(user)
db.commit()
audit_log(
action="delete_user",
user=admin_user,
resource_type="user",
resource_id=str(user_id),
details={"username": username},
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent", ""),
db=db
)
return {"message": "删除成功"}