智教助手平台:完整初始化

- 前端: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:
Zhang Jing Xuan
2026-07-29 16:29:10 +08:00
commit 0841b1a103
193 changed files with 44995 additions and 0 deletions
+267
View File
@@ -0,0 +1,267 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy import or_
from sqlalchemy.orm import Session
from typing import Optional
from database import get_db
from models.resource import Resource, ResourceFavorite
from models.user import User
from schemas.resource import ResourceCreate, ResourceOut, ResourceUpdate
from services.auth import get_current_user, get_optional_current_user
from routers.notification import push_notification
from services.audit import log_action
router = APIRouter(prefix="/api/resources", tags=["资源库"])
def _sync_favorite_count(res: Resource, db: Session) -> None:
res.likes = db.query(ResourceFavorite).filter(ResourceFavorite.resource_id == res.id).count()
def _serialize_resource(res: Resource, current_user: User | None, db: Session) -> Resource:
is_favorited = False
if current_user:
is_favorited = db.query(ResourceFavorite).filter(
ResourceFavorite.user_id == current_user.id,
ResourceFavorite.resource_id == res.id,
).first() is not None
setattr(res, "is_favorited", is_favorited)
setattr(res, "author", res.author)
return res
def _apply_sort(query, sort: str | None):
sort = (sort or "latest").lower()
if sort == "popular":
return query.order_by(Resource.views.desc(), Resource.created_at.desc())
if sort in {"downloads", "download"}:
return query.order_by(Resource.downloads.desc(), Resource.created_at.desc())
if sort in {"likes", "like"}:
return query.order_by(Resource.likes.desc(), Resource.created_at.desc())
return query.order_by(Resource.created_at.desc())
@router.post("/", response_model=ResourceOut, status_code=201)
def create_resource(data: ResourceCreate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
res = Resource(user_id=current_user.id, **data.model_dump())
db.add(res)
db.commit()
db.refresh(res)
return _serialize_resource(res, current_user, db)
@router.post("/publish", response_model=ResourceOut, status_code=201)
def publish_resource(request: Request, data: ResourceCreate, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
query = db.query(Resource).filter(Resource.user_id == current_user.id)
if data.content_ref:
query = query.filter(Resource.content_ref == data.content_ref)
else:
query = query.filter(Resource.title == data.title, Resource.resource_type == data.resource_type)
res = query.first()
is_new = res is None
if res:
for key, value in data.model_dump().items():
setattr(res, key, value)
res.status = "active"
else:
res = Resource(user_id=current_user.id, status="active", **data.model_dump())
db.add(res)
db.commit()
db.refresh(res)
log_action(db, action="publish_resource", user=current_user, request=request, target_type="resource", target_id=res.id, detail=f"{'新建' if is_new else '更新'}资源《{res.title}》({res.resource_type}")
return _serialize_resource(res, current_user, db)
@router.get("/", response_model=list[ResourceOut])
def list_resources(
resource_type: Optional[str] = None,
subject: Optional[str] = None,
keyword: Optional[str] = None,
sort: Optional[str] = Query("latest"),
skip: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
current_user: User | None = Depends(get_optional_current_user),
db: Session = Depends(get_db),
):
query = db.query(Resource).filter(Resource.status == "active")
if resource_type:
query = query.filter(Resource.resource_type == resource_type)
if subject:
query = query.filter(Resource.subject == subject)
if keyword:
query = query.filter(or_(
Resource.title.contains(keyword),
Resource.description.contains(keyword),
Resource.subject.contains(keyword),
Resource.grade.contains(keyword),
))
if current_user:
query = query.filter((Resource.user_id == current_user.id) | (Resource.is_public == 1))
else:
query = query.filter(Resource.is_public == 1)
resources = _apply_sort(query, sort).offset(skip).limit(limit).all()
return [_serialize_resource(res, current_user, db) for res in resources]
@router.put("/{resource_id}", response_model=ResourceOut)
def update_resource(
resource_id: int,
data: ResourceUpdate,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
res = db.query(Resource).filter(Resource.id == resource_id, Resource.user_id == current_user.id).first()
if not res:
raise HTTPException(status_code=404, detail="资源不存在")
for key, value in data.model_dump(exclude_unset=True).items():
setattr(res, key, value)
db.commit()
db.refresh(res)
return _serialize_resource(res, current_user, db)
@router.get("/favorites/me", response_model=list[ResourceOut])
def list_favorite_resources(
resource_type: Optional[str] = None,
subject: Optional[str] = None,
keyword: Optional[str] = None,
sort: Optional[str] = Query("latest"),
skip: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
query = (
db.query(Resource)
.join(ResourceFavorite, ResourceFavorite.resource_id == Resource.id)
.filter(ResourceFavorite.user_id == current_user.id, Resource.status == "active")
)
if resource_type:
query = query.filter(Resource.resource_type == resource_type)
if subject:
query = query.filter(Resource.subject == subject)
if keyword:
query = query.filter(or_(
Resource.title.contains(keyword),
Resource.description.contains(keyword),
Resource.subject.contains(keyword),
Resource.grade.contains(keyword),
))
order = ResourceFavorite.created_at.desc() if (sort or "latest") == "latest" else None
resources = (query.order_by(order) if order is not None else _apply_sort(query, sort)).offset(skip).limit(limit).all()
return [_serialize_resource(res, current_user, db) for res in resources]
@router.get("/mine", response_model=list[ResourceOut])
def list_my_resources(
resource_type: Optional[str] = None,
subject: Optional[str] = None,
keyword: Optional[str] = None,
sort: Optional[str] = Query("latest"),
skip: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
query = db.query(Resource).filter(Resource.user_id == current_user.id, Resource.status == "active")
if resource_type:
query = query.filter(Resource.resource_type == resource_type)
if subject:
query = query.filter(Resource.subject == subject)
if keyword:
query = query.filter(or_(
Resource.title.contains(keyword),
Resource.description.contains(keyword),
Resource.subject.contains(keyword),
Resource.grade.contains(keyword),
))
resources = (_apply_sort(query, sort) if (sort or "latest") != "latest" else query.order_by(Resource.updated_at.desc())).offset(skip).limit(limit).all()
return [_serialize_resource(res, current_user, db) for res in resources]
@router.get("/{resource_id}", response_model=ResourceOut)
def get_resource(
resource_id: int,
current_user: User | None = Depends(get_optional_current_user),
db: Session = Depends(get_db),
):
res = db.query(Resource).filter(Resource.id == resource_id).first()
if not res or res.status == "archived":
raise HTTPException(status_code=404, detail="资源不存在")
if not res.is_public and (not current_user or res.user_id != current_user.id):
raise HTTPException(status_code=403, detail="无权访问该资源")
res.views = (res.views or 0) + 1
db.commit()
db.refresh(res)
return _serialize_resource(res, current_user, db)
@router.post("/{resource_id}/download", response_model=dict)
def download_resource(
resource_id: int,
current_user: User | None = Depends(get_optional_current_user),
db: Session = Depends(get_db),
):
res = db.query(Resource).filter(Resource.id == resource_id, Resource.status == "active").first()
if not res:
raise HTTPException(status_code=404, detail="资源不存在")
if not res.is_public and (not current_user or res.user_id != current_user.id):
raise HTTPException(status_code=403, detail="无权访问该资源")
res.downloads = (res.downloads or 0) + 1
db.commit()
return {"success": True, "downloads": res.downloads, "file_url": res.file_url}
@router.post("/{resource_id}/like", response_model=dict)
def like_resource(
resource_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
res = db.query(Resource).filter(Resource.id == resource_id, Resource.status == "active").first()
if not res:
raise HTTPException(status_code=404, detail="资源不存在")
if not res.is_public and res.user_id != current_user.id:
raise HTTPException(status_code=403, detail="无权访问该资源")
favorite = db.query(ResourceFavorite).filter(
ResourceFavorite.user_id == current_user.id,
ResourceFavorite.resource_id == resource_id,
).first()
if not favorite:
db.add(ResourceFavorite(user_id=current_user.id, resource_id=resource_id))
db.flush()
push_notification(db, user_id=res.user_id, actor_id=current_user.id, ntype="like", title=f"{current_user.name} 收藏了你的资源", content=res.title[:60], link=f"/resources/{res.id}")
_sync_favorite_count(res, db)
db.commit()
return {"success": True, "likes": res.likes, "is_favorited": True}
@router.delete("/{resource_id}/like", response_model=dict)
def unlike_resource(
resource_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
res = db.query(Resource).filter(Resource.id == resource_id, Resource.status == "active").first()
if not res:
raise HTTPException(status_code=404, detail="资源不存在")
if not res.is_public and res.user_id != current_user.id:
raise HTTPException(status_code=403, detail="无权访问该资源")
favorite = db.query(ResourceFavorite).filter(
ResourceFavorite.user_id == current_user.id,
ResourceFavorite.resource_id == resource_id,
).first()
if favorite:
db.delete(favorite)
db.flush()
_sync_favorite_count(res, db)
db.commit()
return {"success": True, "likes": res.likes, "is_favorited": False}
@router.delete("/{resource_id}", status_code=204)
def delete_resource(resource_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
res = db.query(Resource).filter(Resource.id == resource_id, Resource.user_id == current_user.id).first()
if not res:
raise HTTPException(status_code=404, detail="资源不存在")
res.status = "archived"
db.commit()