Files
jiaoyu/backend/routers/resource.py
T
Zhang Jing Xuan 0841b1a103 智教助手平台:完整初始化
- 前端:Vue3 + TS + Element Plus,24 个页面路由(课件/组题/教案/动画/思维导图/作文批改/命题/课堂/资源/社区等)
- 后端:FastAPI + SQLAlchemy + SQLite,17 个路由模块,AI 服务层含降级模板
- AI:glm-5.x 推理模型已禁用思维链,确保输出真实内容
- 修复:ai_service 两处请求体注入 enable_thinking/thinking disabled
- 测试账号:13900999999 / test1234
2026-07-29 16:29:10 +08:00

268 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()