nex_docus/backend/app/api/v1/search.py

124 lines
4.0 KiB
Python

"""
文档搜索相关 API
"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, or_
import os
import glob
from app.core.database import get_db
from app.core.deps import get_current_user
from app.models.user import User
from app.models.project import Project, ProjectMember
from app.services.storage import storage_service
from app.schemas.response import success_response
router = APIRouter()
@router.get("/documents", response_model=dict)
async def search_documents(
keyword: str = Query(..., min_length=1, description="搜索关键词"),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""
文档搜索(简化版)
搜索范围:项目名称、项目描述、文件名
"""
if not keyword:
return success_response(data=[])
keyword_lower = keyword.lower()
# 获取用户有权限访问的项目
# 1. 用户创建的项目
owned_projects_result = await db.execute(
select(Project).where(Project.owner_id == current_user.id, Project.status == 1)
)
owned_projects = owned_projects_result.scalars().all()
# 2. 用户参与的项目
member_projects_result = await db.execute(
select(Project)
.join(ProjectMember, ProjectMember.project_id == Project.id)
.where(
ProjectMember.user_id == current_user.id,
Project.owner_id != current_user.id,
Project.status == 1
)
)
member_projects = member_projects_result.scalars().all()
# 合并所有可访问的项目
all_projects = owned_projects + member_projects
# 搜索结果列表
search_results = []
# 搜索项目和文件
for project in all_projects:
# 检查项目名称或描述是否匹配
project_matched = False
if keyword_lower in project.name.lower():
project_matched = True
elif project.description and keyword_lower in project.description.lower():
project_matched = True
# 如果项目本身匹配,添加到结果
if project_matched:
search_results.append({
"type": "project",
"project_id": project.id,
"project_name": project.name,
"project_description": project.description or "",
"match_type": "项目",
})
# 搜索项目中的文件名
try:
project_path = storage_service.get_secure_path(project.storage_key)
if not project_path.exists() or not project_path.is_dir():
continue
# 查找所有 .md 文件
md_files = list(project_path.rglob("*.md"))
for file_path in md_files:
# 跳过 _assets 目录中的文件
if "_assets" in file_path.parts:
continue
try:
# 获取相对路径
relative_path = str(file_path.relative_to(project_path))
# 获取文件名(不含扩展名)
file_name = file_path.stem
# 检查关键词是否在文件名或路径中
if keyword_lower in file_name.lower() or keyword_lower in relative_path.lower():
search_results.append({
"type": "file",
"project_id": project.id,
"project_name": project.name,
"file_path": relative_path,
"file_name": file_name,
"match_type": "文件",
})
except Exception:
# 忽略无法处理的文件
continue
except Exception:
# 忽略无法遍历的目录
continue
# 限制返回结果数量
search_results = search_results[:100]
return success_response(data=search_results, message=f"找到 {len(search_results)} 个结果")