3
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
"""公共知识库服务(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.knowledge_base import KnowledgeBase
|
||||
from app.repositories.doc_repo import DocumentRepository
|
||||
from app.repositories.kb_repo import KnowledgeBaseRepository
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
class KbPublicService:
|
||||
def __init__(self, session: Session) -> None:
|
||||
self._session = session
|
||||
self._kb_repo = KnowledgeBaseRepository(session)
|
||||
self._doc_repo = DocumentRepository(session)
|
||||
|
||||
def get_kb_by_token(self, token: str) -> KnowledgeBase:
|
||||
"""通过 token 获取知识库。不存在/禁用/删除 → 404。"""
|
||||
token_hash = hash_token(token)
|
||||
kb = self._kb_repo.get_by_token_hash(token_hash)
|
||||
if kb is None or not kb.enabled or kb.status == "DELETED":
|
||||
raise NotFoundError("知识库不存在。")
|
||||
return kb
|
||||
|
||||
def list_documents(
|
||||
self, kb: KnowledgeBase, *, page: int = 1, page_size: int = 50
|
||||
) -> tuple[list[Document], int]:
|
||||
"""获取知识库的文档列表(仅 READY 状态)。"""
|
||||
return self._doc_repo.list_by_knowledge_base(
|
||||
kb.id, page=page, page_size=page_size, status="READY"
|
||||
)
|
||||
|
||||
def get_document_by_token(self, kb: KnowledgeBase, doc_token: str) -> Document:
|
||||
"""通过 token 获取单个文档。"""
|
||||
doc_token_hash = hash_token(doc_token)
|
||||
doc = self._doc_repo.get_by_doc_token_hash(doc_token_hash)
|
||||
if doc is None or doc.knowledge_base_id != kb.id or doc.status != "READY":
|
||||
raise NotFoundError("文档不存在。")
|
||||
return doc
|
||||
|
||||
def get_document_markdown(self, doc: Document) -> str:
|
||||
"""读取文档的 Markdown 内容。"""
|
||||
from app.storage.local_storage import get_storage
|
||||
|
||||
if not doc.markdown_path:
|
||||
return ""
|
||||
storage = get_storage()
|
||||
content = storage.read(doc.markdown_path)
|
||||
return content.decode("utf-8")
|
||||
|
||||
def search_documents(
|
||||
self, kb: KnowledgeBase, query: str, *, page: int = 1, page_size: int = 20
|
||||
) -> tuple[list[dict], int]:
|
||||
"""关键词搜索文档(Phase 12:SQLite 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),
|
||||
)
|
||||
conditions.append(search_condition)
|
||||
|
||||
count_stmt = select(func.count()).select_from(Document).where(*conditions)
|
||||
total = self._session.scalar(count_stmt) or 0
|
||||
|
||||
stmt = (
|
||||
select(Document)
|
||||
.where(*conditions)
|
||||
.order_by(Document.created_at.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
)
|
||||
docs = list(self._session.scalars(stmt).all())
|
||||
|
||||
results = []
|
||||
for doc in docs:
|
||||
results.append({
|
||||
"id": doc.id,
|
||||
"title": doc.title or doc.original_filename,
|
||||
"description": doc.description,
|
||||
"keywords": doc.keywords,
|
||||
"file_type": doc.file_ext,
|
||||
"updated_at": doc.updated_at,
|
||||
"url_hint": doc.doc_token_hint,
|
||||
})
|
||||
|
||||
return results, total
|
||||
Reference in New Issue
Block a user