0841b1a103
- 前端:Vue3 + TS + Element Plus,24 个页面路由(课件/组题/教案/动画/思维导图/作文批改/命题/课堂/资源/社区等) - 后端:FastAPI + SQLAlchemy + SQLite,17 个路由模块,AI 服务层含降级模板 - AI:glm-5.x 推理模型已禁用思维链,确保输出真实内容 - 修复:ai_service 两处请求体注入 enable_thinking/thinking disabled - 测试账号:13900999999 / test1234
302 lines
11 KiB
Python
302 lines
11 KiB
Python
from fastapi import APIRouter, Depends, Query
|
|
from sqlalchemy import or_
|
|
from sqlalchemy.orm import Session
|
|
|
|
from database import get_db
|
|
from models.animation import Animation
|
|
from models.classroom import ClassroomActivity
|
|
from models.community import Post
|
|
from models.courseware import Courseware
|
|
from models.exam import Exam
|
|
from models.exercise import Exercise
|
|
from models.lesson_plan import LessonPlan
|
|
from models.material import Material
|
|
from models.mindmap import MindMap
|
|
from models.resource import Resource
|
|
from models.user import User
|
|
from schemas.search import SearchResponse
|
|
from services.auth import get_optional_current_user
|
|
|
|
router = APIRouter(prefix="/api/search", tags=["全局搜索"])
|
|
|
|
|
|
def _has_keyword(*values, keyword: str) -> bool:
|
|
if not keyword:
|
|
return True
|
|
haystack = " ".join(str(value or "") for value in values).lower()
|
|
return keyword.lower() in haystack
|
|
|
|
|
|
def _tag_list(value) -> list[str]:
|
|
if not isinstance(value, list):
|
|
return []
|
|
return [str(item) for item in value if str(item).strip()][:6]
|
|
|
|
|
|
def _item(
|
|
*,
|
|
id,
|
|
source: str,
|
|
source_label: str,
|
|
title: str,
|
|
route: str,
|
|
description: str = "",
|
|
subject: str = "",
|
|
grade: str = "",
|
|
tags=None,
|
|
action_route: str = "",
|
|
is_public: bool = False,
|
|
updated_at=None,
|
|
) -> dict:
|
|
return {
|
|
"id": id,
|
|
"source": source,
|
|
"source_label": source_label,
|
|
"title": title or "未命名",
|
|
"description": description or "",
|
|
"subject": subject or "",
|
|
"grade": grade or "",
|
|
"tags": _tag_list(tags),
|
|
"route": route,
|
|
"action_route": action_route,
|
|
"is_public": bool(is_public),
|
|
"updated_at": updated_at,
|
|
}
|
|
|
|
|
|
def _sort_key(item: dict):
|
|
return item["updated_at"] or ""
|
|
|
|
|
|
def _resource_query(keyword: str, current_user: User | None, db: Session, limit: int) -> list[dict]:
|
|
query = db.query(Resource).filter(Resource.status == "active")
|
|
if current_user:
|
|
query = query.filter((Resource.user_id == current_user.id) | (Resource.is_public == 1))
|
|
else:
|
|
query = query.filter(Resource.is_public == 1)
|
|
if keyword:
|
|
query = query.filter(or_(
|
|
Resource.title.contains(keyword),
|
|
Resource.description.contains(keyword),
|
|
Resource.subject.contains(keyword),
|
|
Resource.grade.contains(keyword),
|
|
))
|
|
return [
|
|
_item(
|
|
id=res.id,
|
|
source="resource",
|
|
source_label="资源",
|
|
title=res.title,
|
|
description=res.description,
|
|
subject=res.subject,
|
|
grade=res.grade,
|
|
tags=res.tags,
|
|
route=f"/resources/{res.id}",
|
|
action_route=f"/resources/{res.id}",
|
|
is_public=bool(res.is_public),
|
|
updated_at=res.updated_at,
|
|
)
|
|
for res in query.order_by(Resource.updated_at.desc()).limit(limit).all()
|
|
]
|
|
|
|
|
|
def _community_query(keyword: str, current_user: User | None, db: Session, limit: int) -> list[dict]:
|
|
query = db.query(Post)
|
|
if keyword:
|
|
query = query.filter(or_(Post.title.contains(keyword), Post.content.contains(keyword)))
|
|
return [
|
|
_item(
|
|
id=post.id,
|
|
source="community",
|
|
source_label="模板社区",
|
|
title=post.title,
|
|
description=post.content,
|
|
tags=post.tags,
|
|
route=f"/community/{post.id}",
|
|
action_route=f"/community/{post.id}",
|
|
is_public=True,
|
|
updated_at=post.updated_at,
|
|
)
|
|
for post in query.order_by(Post.updated_at.desc()).limit(limit).all()
|
|
]
|
|
|
|
|
|
def _my_work_queries(keyword: str, current_user: User | None, db: Session, limit: int) -> list[dict]:
|
|
if not current_user:
|
|
return []
|
|
user_id = current_user.id
|
|
items = []
|
|
|
|
coursewares = db.query(Courseware).filter(Courseware.user_id == user_id, Courseware.status != "archived").all()
|
|
for work in coursewares:
|
|
if _has_keyword(work.title, work.description, work.subject, work.grade, " ".join(_tag_list(work.tags)), keyword=keyword):
|
|
items.append(_item(
|
|
id=work.id,
|
|
source="courseware",
|
|
source_label="我的课件",
|
|
title=work.title,
|
|
description=work.description,
|
|
subject=work.subject,
|
|
grade=work.grade,
|
|
tags=work.tags,
|
|
route=f"/courseware/{work.id}",
|
|
action_route=f"/courseware/{work.id}",
|
|
updated_at=work.updated_at,
|
|
))
|
|
|
|
animations = db.query(Animation).filter(Animation.user_id == user_id).all()
|
|
for work in animations:
|
|
if _has_keyword(work.title, work.description, work.anim_type, keyword=keyword):
|
|
items.append(_item(
|
|
id=work.id,
|
|
source="animation",
|
|
source_label="我的动画",
|
|
title=work.title,
|
|
description=work.description,
|
|
subject=str(work.anim_type or ""),
|
|
route=f"/animation?open={work.id}",
|
|
action_route=f"/animation?open={work.id}",
|
|
updated_at=work.updated_at,
|
|
))
|
|
|
|
exercises = db.query(Exercise).filter(Exercise.user_id == user_id).all()
|
|
for work in exercises:
|
|
if _has_keyword(work.title, work.subject, " ".join(_tag_list(work.knowledge_points)), keyword=keyword):
|
|
items.append(_item(
|
|
id=work.id,
|
|
source="exercise",
|
|
source_label="我的练习",
|
|
title=work.title,
|
|
subject=work.subject,
|
|
tags=work.knowledge_points,
|
|
route=f"/exercise?open={work.id}",
|
|
action_route=f"/exercise?open={work.id}",
|
|
updated_at=work.updated_at,
|
|
))
|
|
|
|
lesson_plans = db.query(LessonPlan).filter(LessonPlan.user_id == user_id).all()
|
|
for work in lesson_plans:
|
|
if _has_keyword(work.title, work.subject, work.grade, " ".join(_tag_list(work.objectives)), keyword=keyword):
|
|
items.append(_item(
|
|
id=work.id,
|
|
source="lesson_plan",
|
|
source_label="我的教案",
|
|
title=work.title,
|
|
subject=work.subject,
|
|
grade=work.grade,
|
|
tags=work.objectives,
|
|
route=f"/lesson-plan?open={work.id}",
|
|
action_route=f"/lesson-plan?open={work.id}",
|
|
updated_at=work.updated_at,
|
|
))
|
|
|
|
mind_maps = db.query(MindMap).filter(MindMap.user_id == user_id).all()
|
|
for work in mind_maps:
|
|
if _has_keyword(work.title, work.subject, keyword=keyword):
|
|
items.append(_item(
|
|
id=work.id,
|
|
source="mindmap",
|
|
source_label="我的导图",
|
|
title=work.title,
|
|
subject=work.subject,
|
|
route=f"/mindmap?open={work.id}",
|
|
action_route=f"/mindmap?open={work.id}",
|
|
updated_at=work.updated_at,
|
|
))
|
|
|
|
exams = db.query(Exam).filter(Exam.user_id == user_id).all()
|
|
for work in exams:
|
|
if _has_keyword(work.title, work.subject, work.grade, " ".join(_tag_list(work.knowledge_points)), keyword=keyword):
|
|
items.append(_item(
|
|
id=work.id,
|
|
source="exam",
|
|
source_label="我的试卷",
|
|
title=work.title,
|
|
subject=work.subject,
|
|
grade=work.grade,
|
|
tags=work.knowledge_points,
|
|
route=f"/exam?open={work.id}",
|
|
action_route=f"/exam?open={work.id}",
|
|
updated_at=work.updated_at,
|
|
))
|
|
|
|
items.sort(key=_sort_key, reverse=True)
|
|
return items[:limit]
|
|
|
|
|
|
def _material_query(keyword: str, current_user: User | None, db: Session, limit: int) -> list[dict]:
|
|
if not current_user:
|
|
return []
|
|
query = db.query(Material).filter(Material.user_id == current_user.id, Material.status == "active")
|
|
if keyword:
|
|
query = query.filter(or_(
|
|
Material.title.contains(keyword),
|
|
Material.filename.contains(keyword),
|
|
Material.summary.contains(keyword),
|
|
Material.subject.contains(keyword),
|
|
Material.grade.contains(keyword),
|
|
))
|
|
return [
|
|
_item(
|
|
id=item.id,
|
|
source="material",
|
|
source_label="素材",
|
|
title=item.title,
|
|
description=item.summary,
|
|
subject=item.subject,
|
|
grade=item.grade,
|
|
tags=item.tags,
|
|
route="/materials",
|
|
action_route=f"/courseware/create",
|
|
updated_at=item.updated_at,
|
|
)
|
|
for item in query.order_by(Material.updated_at.desc()).limit(limit).all()
|
|
]
|
|
|
|
|
|
def _classroom_query(keyword: str, current_user: User | None, db: Session, limit: int) -> list[dict]:
|
|
if not current_user:
|
|
return []
|
|
activities = db.query(ClassroomActivity).filter(ClassroomActivity.user_id == current_user.id).all()
|
|
items = []
|
|
for activity in activities:
|
|
config = activity.config or {}
|
|
if _has_keyword(activity.title, activity.description, config.get("question"), activity.activity_type, keyword=keyword):
|
|
items.append(_item(
|
|
id=activity.id,
|
|
source="classroom",
|
|
source_label="课堂活动",
|
|
title=activity.title,
|
|
description=activity.description or config.get("question") or "",
|
|
subject=activity.activity_type or "",
|
|
route=f"/classroom?open={activity.id}",
|
|
action_route=f"/classroom?open={activity.id}",
|
|
updated_at=activity.updated_at,
|
|
))
|
|
items.sort(key=_sort_key, reverse=True)
|
|
return items[:limit]
|
|
|
|
|
|
@router.get("", response_model=SearchResponse)
|
|
def global_search(
|
|
keyword: str = Query("", max_length=100),
|
|
limit: int = Query(6, ge=1, le=20),
|
|
current_user: User | None = Depends(get_optional_current_user),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
keyword = keyword.strip()
|
|
resources = _resource_query(keyword, current_user, db, limit)
|
|
community = _community_query(keyword, current_user, db, limit)
|
|
works = _my_work_queries(keyword, current_user, db, limit)
|
|
materials = _material_query(keyword, current_user, db, limit)
|
|
classroom = _classroom_query(keyword, current_user, db, limit)
|
|
return {
|
|
"keyword": keyword,
|
|
"total": len(resources) + len(community) + len(works) + len(materials) + len(classroom),
|
|
"resources": resources,
|
|
"community": community,
|
|
"works": works,
|
|
"materials": materials,
|
|
"classroom": classroom,
|
|
}
|