0841b1a103
- 前端:Vue3 + TS + Element Plus,24 个页面路由(课件/组题/教案/动画/思维导图/作文批改/命题/课堂/资源/社区等) - 后端:FastAPI + SQLAlchemy + SQLite,17 个路由模块,AI 服务层含降级模板 - AI:glm-5.x 推理模型已禁用思维链,确保输出真实内容 - 修复:ai_service 两处请求体注入 enable_thinking/thinking disabled - 测试账号:13900999999 / test1234
268 lines
11 KiB
Python
268 lines
11 KiB
Python
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()
|