智教助手平台:完整初始化
- 前端: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,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()
|
||||
Reference in New Issue
Block a user