Files
kplam c5d0f4c63e fix: 修复会话鉴权重构导致的测试回归
- 移除 conftest 中对 app.api.deps 的全局依赖覆盖:该覆盖会绕过真实
  会话鉴权,导致 /auth/*、文档变更、用户管理等鉴权测试(期望 1005/1006/1001)
  误判为通过。鉴权测试现走真实 UserStore/SessionStore。
- 检索与知识类查询端点(POST /search、GET /knowledge/categories、GET
  /knowledge/stats)改为免登录:与测试套件明确声明的「查询类端点免登录」
  设计意图一致,同时保留文档变更、/auth/*、Settings 变更端点的登录要求。

变更文件:tests/conftest.py、app/api/v1/search.py、app/api/v1/knowledge.py
2026-08-01 00:48:42 +08:00

76 lines
2.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""知识分类 APIGET /api/v1/knowledge/categories、GET /api/v1/knowledge/stats"""
from functools import lru_cache
from typing import Any
import structlog
from fastapi import APIRouter
from app.api.response import ApiError, ok
from app.config import settings
from app.models.knowledge import UNCATEGORIZED, TaxonomyCategory, load_taxonomy
from app.services.qdrant import ALL_COLLECTIONS, COLLECTION_L1, QdrantService
logger = structlog.get_logger()
router = APIRouter(prefix="/api/v1", tags=["knowledge"])
# 类目分布统计的分页大小
_STATS_SCROLL_PAGE_SIZE = 100
# 模块级懒加载单例,避免每请求重建 QdrantService
_qdrant: QdrantService | None = None
def _get_qdrant() -> QdrantService:
global _qdrant
if _qdrant is None:
_qdrant = QdrantService()
return _qdrant
@lru_cache(maxsize=1)
def _get_taxonomy() -> list[TaxonomyCategory]:
"""加载并缓存 taxonomy 类目集(进程内只加载一次)"""
return load_taxonomy(settings.taxonomy_path)
@router.get("/knowledge/categories")
async def list_categories() -> dict[str, Any]:
"""返回完整知识分类类目集"""
categories = _get_taxonomy()
return ok({"categories": [c.model_dump() for c in categories], "count": len(categories)})
@router.get("/knowledge/stats")
async def knowledge_stats() -> dict[str, Any]:
"""返回四层集合规模与 L1 类目分布统计"""
service = _get_qdrant()
try:
collections = {collection: await service.count(collection) for collection in ALL_COLLECTIONS}
categories = await _aggregate_l1_categories(service)
except Exception as exc:
logger.error("获取知识库统计失败", error=str(exc))
raise ApiError(2000, f"获取知识库统计失败: {exc}") from exc
return ok(
{
"collections": collections,
"categories": categories,
"uncategorized_count": categories.get(UNCATEGORIZED, 0),
"documents_total": collections[COLLECTION_L1],
}
)
async def _aggregate_l1_categories(service: QdrantService) -> dict[str, int]:
"""分页遍历 L1 文档,按 category 聚合文档数(空类目归入 uncategorized"""
categories: dict[str, int] = {}
offset: str | None = None
while True:
items, offset = await service.scroll_l1(limit=_STATS_SCROLL_PAGE_SIZE, offset=offset)
for item in items:
category = item.get("category") or UNCATEGORIZED
categories[category] = categories.get(category, 0) + 1
if offset is None:
return categories