智教助手平台:完整初始化
- 前端:Vue3 + TS + Element Plus,24 个页面路由(课件/组题/教案/动画/思维导图/作文批改/命题/课堂/资源/社区等) - 后端:FastAPI + SQLAlchemy + SQLite,17 个路由模块,AI 服务层含降级模板 - AI:glm-5.x 推理模型已禁用思维链,确保输出真实内容 - 修复:ai_service 两处请求体注入 enable_thinking/thinking disabled - 测试账号:13900999999 / test1234
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
from datetime import datetime, time, timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from database import get_db
|
||||
from models.animation import Animation
|
||||
from models.courseware import Courseware
|
||||
from models.credit import CreditAccount, CreditTransaction
|
||||
from models.exercise import Exercise
|
||||
from models.lesson_plan import LessonPlan
|
||||
from models.material import Material
|
||||
from models.resource import Resource, ResourceFavorite
|
||||
from models.user import User
|
||||
from models.audit import AuditLog
|
||||
from schemas.admin import AdminDashboardOut, AdminGrantCredits, AdminUserOut, AdminUserUpdate
|
||||
from schemas.resource import ResourceOut, ResourceAuthorOut
|
||||
from services.auth import get_admin_user
|
||||
from services.audit import log_action
|
||||
from services.credits import grant_credits
|
||||
|
||||
router = APIRouter(prefix="/api/admin", tags=["管理后台"])
|
||||
|
||||
|
||||
def _user_with_credits(user: User, db: Session) -> dict:
|
||||
account = db.query(CreditAccount).filter(CreditAccount.user_id == user.id).first()
|
||||
data = AdminUserOut.model_validate(user).model_dump()
|
||||
data["credits"] = account.balance if account else 0
|
||||
return data
|
||||
|
||||
|
||||
@router.get("/dashboard", response_model=AdminDashboardOut)
|
||||
def dashboard(current_user: User = Depends(get_admin_user), db: Session = Depends(get_db)):
|
||||
today = datetime.now().date()
|
||||
today_start = datetime.combine(today, time.min)
|
||||
|
||||
total_users = db.query(func.count(User.id)).scalar() or 0
|
||||
active_users = db.query(func.count(User.id)).filter(User.is_active.is_(True)).scalar() or 0
|
||||
admin_users = db.query(func.count(User.id)).filter(User.role == "admin").scalar() or 0
|
||||
new_users_today = db.query(func.count(User.id)).filter(User.created_at >= today_start).scalar() or 0
|
||||
|
||||
total_resources = db.query(func.count(Resource.id)).filter(Resource.status == "active").scalar() or 0
|
||||
public_resources = db.query(func.count(Resource.id)).filter(Resource.status == "active", Resource.is_public == 1).scalar() or 0
|
||||
|
||||
total_coursewares = db.query(func.count(Courseware.id)).filter(Courseware.status != "archived").scalar() or 0
|
||||
total_animations = db.query(func.count(Animation.id)).filter(Animation.status != "archived").scalar() or 0
|
||||
total_exercises = db.query(func.count(Exercise.id)).scalar() or 0
|
||||
total_lesson_plans = db.query(func.count(LessonPlan.id)).scalar() or 0
|
||||
|
||||
total_views = db.query(func.coalesce(func.sum(Resource.views), 0)).scalar() or 0
|
||||
total_downloads = db.query(func.coalesce(func.sum(Resource.downloads), 0)).scalar() or 0
|
||||
total_favorites = db.query(func.count(ResourceFavorite.id)).scalar() or 0
|
||||
total_credits_used = db.query(func.coalesce(func.sum(CreditAccount.total_used), 0)).scalar() or 0
|
||||
|
||||
exam_count = 0
|
||||
try:
|
||||
from models.exam import Exam
|
||||
exam_count = db.query(func.count(Exam.id)).scalar() or 0
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return AdminDashboardOut(
|
||||
total_users=total_users,
|
||||
active_users=active_users,
|
||||
admin_users=admin_users,
|
||||
new_users_today=new_users_today,
|
||||
total_resources=total_resources,
|
||||
public_resources=public_resources,
|
||||
total_coursewares=total_coursewares,
|
||||
total_animations=total_animations,
|
||||
total_exercises=total_exercises,
|
||||
total_lesson_plans=total_lesson_plans,
|
||||
total_exams=exam_count,
|
||||
total_views=total_views,
|
||||
total_downloads=total_downloads,
|
||||
total_favorites=total_favorites,
|
||||
total_credits_used=total_credits_used,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/users", response_model=list[AdminUserOut])
|
||||
def list_users(
|
||||
keyword: str = Query("", description="搜索手机号或姓名"),
|
||||
role: str = Query("", description="按角色筛选"),
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
query = db.query(User)
|
||||
if keyword:
|
||||
like = f"%{keyword}%"
|
||||
query = query.filter((User.phone.contains(like)) | (User.name.contains(like)))
|
||||
if role:
|
||||
query = query.filter(User.role == role)
|
||||
users = query.order_by(User.created_at.desc()).offset(skip).limit(limit).all()
|
||||
return [_user_with_credits(u, db) for u in users]
|
||||
|
||||
|
||||
@router.put("/users/{user_id}", response_model=AdminUserOut)
|
||||
def update_user(
|
||||
user_id: int,
|
||||
data: AdminUserUpdate,
|
||||
request: Request,
|
||||
current_user: User = Depends(get_admin_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
changes = []
|
||||
if data.role is not None:
|
||||
if data.role not in {"teacher", "admin"}:
|
||||
raise HTTPException(status_code=400, detail="角色无效")
|
||||
changes.append(f"role:{user.role}->{data.role}")
|
||||
user.role = data.role
|
||||
if data.is_active is not None:
|
||||
changes.append(f"active:{user.is_active}->{data.is_active}")
|
||||
user.is_active = data.is_active
|
||||
if data.name is not None:
|
||||
changes.append(f"name:{user.name}->{data.name}")
|
||||
user.name = data.name
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
log_action(db, action="admin_update_user", user=current_user, request=request, target_type="user", target_id=user.id, detail=f"修改用户 {user.name or user.phone or user.id}:{', '.join(changes) or '无变更'}")
|
||||
return _user_with_credits(user, db)
|
||||
|
||||
|
||||
@router.post("/users/{user_id}/grant-credits", response_model=AdminUserOut)
|
||||
def grant_user_credits(
|
||||
user_id: int,
|
||||
data: AdminGrantCredits,
|
||||
request: Request,
|
||||
current_user: User = Depends(get_admin_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="用户不存在")
|
||||
grant_credits(db, user, data.amount, action="admin_grant", description=f"管理员 {current_user.name} 发放 {data.amount} 积分")
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
log_action(db, action="admin_grant_credits", user=current_user, request=request, target_type="user", target_id=user.id, detail=f"向 {user.name or user.phone or user.id} 发放 {data.amount} 积分")
|
||||
return _user_with_credits(user, db)
|
||||
|
||||
|
||||
@router.get("/resources", response_model=list[ResourceOut])
|
||||
def list_all_resources(
|
||||
keyword: str = Query(""),
|
||||
resource_type: str = Query(""),
|
||||
status: str = Query("active"),
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
from sqlalchemy import or_
|
||||
|
||||
query = db.query(Resource)
|
||||
if status:
|
||||
query = query.filter(Resource.status == status)
|
||||
if resource_type:
|
||||
query = query.filter(Resource.resource_type == resource_type)
|
||||
if keyword:
|
||||
like = f"%{keyword}%"
|
||||
query = query.filter(or_(Resource.title.contains(like), Resource.description.contains(like)))
|
||||
resources = query.order_by(Resource.created_at.desc()).offset(skip).limit(limit).all()
|
||||
result = []
|
||||
for res in resources:
|
||||
out = ResourceOut.model_validate(res)
|
||||
out.is_favorited = False
|
||||
result.append(out)
|
||||
return result
|
||||
|
||||
|
||||
@router.put("/resources/{resource_id}/moderate", response_model=dict)
|
||||
def moderate_resource(
|
||||
resource_id: int,
|
||||
request: Request,
|
||||
status: str = Query(..., description="active 或 archived"),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
if status not in {"active", "archived"}:
|
||||
raise HTTPException(status_code=400, detail="状态无效")
|
||||
res = db.query(Resource).filter(Resource.id == resource_id).first()
|
||||
if not res:
|
||||
raise HTTPException(status_code=404, detail="资源不存在")
|
||||
prev = res.status
|
||||
res.status = status
|
||||
db.commit()
|
||||
log_action(db, action="admin_moderate_resource", user=current_user, request=request, target_type="resource", target_id=res.id, detail=f"资源《{res.title}》状态 {prev} -> {status}")
|
||||
return {"success": True, "status": res.status}
|
||||
|
||||
|
||||
@router.get("/audit-logs", response_model=list[dict])
|
||||
def list_audit_logs(
|
||||
action: str | None = None,
|
||||
status: str | None = None,
|
||||
user_id: int | None = None,
|
||||
keyword: str | None = None,
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
current_user: User = Depends(get_admin_user),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""查询安全审计日志(仅管理员)。"""
|
||||
from sqlalchemy import or_
|
||||
q = db.query(AuditLog)
|
||||
if action:
|
||||
q = q.filter(AuditLog.action == action)
|
||||
if status:
|
||||
q = q.filter(AuditLog.status == status)
|
||||
if user_id:
|
||||
q = q.filter(AuditLog.user_id == user_id)
|
||||
if keyword:
|
||||
like = f"%{keyword}%"
|
||||
q = q.filter(or_(AuditLog.username.contains(like), AuditLog.detail.contains(like), AuditLog.ip.contains(like)))
|
||||
rows = q.order_by(AuditLog.created_at.desc()).offset(skip).limit(limit).all()
|
||||
return [
|
||||
{
|
||||
"id": r.id,
|
||||
"user_id": r.user_id,
|
||||
"username": r.username,
|
||||
"action": r.action,
|
||||
"target_type": r.target_type,
|
||||
"target_id": r.target_id,
|
||||
"detail": r.detail,
|
||||
"ip": r.ip,
|
||||
"user_agent": r.user_agent,
|
||||
"status": r.status,
|
||||
"created_at": r.created_at.isoformat() if r.created_at else None,
|
||||
}
|
||||
for r in rows
|
||||
]
|
||||
Reference in New Issue
Block a user