This commit is contained in:
amb
2026-09-01 20:51:46 +08:00
parent 70ece67910
commit b80161972a
18 changed files with 1878 additions and 359 deletions
+77 -19
View File
@@ -1,14 +1,17 @@
"""公共知识库服务(Phase 10/11/12 统一数据来源)。
HTML/MD/TXT/JSON/搜索全部通过此 Service 获取数据,不各自写查询逻辑。
支持目录树结构和分类过滤。
"""
from app.core.errors import NotFoundError
from app.core.security import hash_token
from app.models.document import Document
from app.models.document_category import DocumentCategory
from app.models.knowledge_base import KnowledgeBase
from app.repositories.doc_repo import DocumentRepository
from app.repositories.kb_repo import KnowledgeBaseRepository
from sqlalchemy import func, select
from sqlalchemy.orm import Session
@@ -26,12 +29,55 @@ class KbPublicService:
raise NotFoundError("知识库不存在。")
return kb
def get_category_tree(self, kb: KnowledgeBase) -> list[dict]:
"""获取目录树(含文档数量)。"""
stmt = (
select(DocumentCategory)
.where(DocumentCategory.knowledge_base_id == kb.id)
.order_by(DocumentCategory.sort_order, DocumentCategory.name)
)
all_cats = list(self._session.scalars(stmt).all())
# 获取每个分类的文档数量
count_stmt = (
select(Document.category_id, func.count())
.where(
Document.knowledge_base_id == kb.id,
Document.status != "DELETED",
)
.group_by(Document.category_id)
)
doc_count_map = dict(self._session.execute(count_stmt).all())
# 构建树
cat_map = {}
for cat in all_cats:
cat_map[cat.id] = {
"id": cat.id,
"name": cat.name,
"path": cat.path or "/",
"is_folder": getattr(cat, 'is_folder', True),
"sort_order": cat.sort_order,
"doc_count": doc_count_map.get(cat.id, 0),
"children": [],
}
roots = []
for cat in all_cats:
node = cat_map[cat.id]
if cat.parent_id and cat.parent_id in cat_map:
cat_map[cat.parent_id]["children"].append(node)
else:
roots.append(node)
return roots
def list_documents(
self, kb: KnowledgeBase, *, page: int = 1, page_size: int = 50
self, kb: KnowledgeBase, *, category_id: str | None = None, page: int = 1, page_size: int = 50
) -> tuple[list[Document], int]:
"""获取知识库的文档列表(排除 DELETED)。"""
"""获取知识库的文档列表(排除 DELETED)。支持按分类过滤。"""
return self._doc_repo.list_by_knowledge_base(
kb.id, page=page, page_size=page_size
kb.id, category_id=category_id, page=page, page_size=page_size
)
def get_document_by_token(self, kb: KnowledgeBase, doc_token: str) -> Document:
@@ -42,34 +88,38 @@ class KbPublicService:
raise NotFoundError("文档不存在。")
return doc
def get_document_markdown(self, doc: Document) -> str:
"""取文档 Markdown 内容"""
from app.storage.local_storage import get_storage
def get_document_content(self, doc: Document) -> str:
"""取文档内容(优先直接内容,其次 Markdown 文件)"""
# 直接文本内容
if doc.content:
return doc.content
if not doc.markdown_path:
return ""
storage = get_storage()
content = storage.read(doc.markdown_path)
return content.decode("utf-8")
# 从文件读取 Markdown
if doc.markdown_path:
from app.storage.local_storage import get_storage
storage = get_storage()
content = storage.read(doc.markdown_path)
return content.decode("utf-8")
return ""
def search_documents(
self, kb: KnowledgeBase, query: str, *, page: int = 1, page_size: int = 20
) -> tuple[list[dict], int]:
"""关键词搜索文档(Phase 12SQLite LIKE / FTS5)。"""
# MVP 简化:使用 LIKE 搜索标题+描述+关键词
from sqlalchemy import func, or_, select
conditions = [
Document.knowledge_base_id == kb.id,
Document.status == "READY",
]
like_pattern = f"%{query}%"
search_condition = or_(
Document.title.like(like_pattern),
Document.description.like(like_pattern),
Document.keywords.like(like_pattern),
Document.content_summary.like(like_pattern),
search_condition = (
Document.title.like(like_pattern)
| Document.description.like(like_pattern)
| Document.keywords.like(like_pattern)
| Document.content_summary.like(like_pattern)
| Document.content.like(like_pattern)
)
conditions.append(search_condition)
@@ -87,6 +137,13 @@ class KbPublicService:
results = []
for doc in docs:
# 获取分类路径
category_path = ""
if doc.category_id:
cat = self._session.get(DocumentCategory, doc.category_id)
if cat:
category_path = cat.path or ""
results.append({
"id": doc.id,
"title": doc.title or doc.original_filename,
@@ -95,6 +152,7 @@ class KbPublicService:
"file_type": doc.file_ext,
"updated_at": doc.updated_at,
"url_hint": doc.doc_token_hint,
"category_path": category_path,
})
return results, total