"""/api/v1/search/* — 搜索建议(autocomplete)。 - GET /api/v1/search/suggestions?q=prefix 返回:{"query", "titles": [...], "keywords": [...]} - titles: 真实文章标题(按 published_at DESC),B 方案 - keywords: 高频词(按 weight DESC),A 方案 - 冷启动:任一表空时自动 fallback 到实时 ILIKE / ts_stat - 鉴权:跟 articles 一致(需要登录) 性能:两个查询都走 GIN 数组索引(prefix_keys @> ARRAY['美']),亚毫秒。 """ from __future__ import annotations from fastapi import APIRouter, Depends, Query from sqlalchemy.ext.asyncio import AsyncSession from app.core.deps import get_current_user from app.database import get_session from app.models.user import User from app.schemas.search import ( SearchKeywordItem, SearchSuggestionsResponse, SearchTitleSuggestionItem, ) from app.services.search import SearchService router = APIRouter(prefix="/search", tags=["search"]) @router.get("/suggestions", response_model=SearchSuggestionsResponse) async def get_suggestions( q: str = Query(..., min_length=1, max_length=20, description="搜索前缀"), limit: int = Query(10, ge=1, le=20, description="每组最多返回多少"), _user: User = Depends(get_current_user), # 需要登录,跟 articles 一致 session: AsyncSession = Depends(get_session), ): """搜索建议:输入 prefix,返回真实标题 + 高频词两组候选。 用法:前端搜索框 onChange 时调用,debounce 200ms。 """ svc = SearchService(session) raw = await svc.suggestions(q=q, limit=limit) return SearchSuggestionsResponse( query=raw["query"], titles=[SearchTitleSuggestionItem(**t) for t in raw["titles"]], keywords=[SearchKeywordItem(**k) for k in raw["keywords"]], )