细节优化

This commit is contained in:
amb
2026-09-02 18:04:41 +08:00
parent cecb5f2714
commit 8ec7856bfc
21 changed files with 1260 additions and 569 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
"""访问日志服务。"""
from datetime import datetime, timezone
from datetime import datetime
from app.models.access_log import AccessLog
from sqlalchemy.orm import Session
@@ -24,7 +24,7 @@ class AccessLogService:
knowledge_base_id=knowledge_base_id,
document_id=document_id,
path=path,
accessed_at=datetime.now(timezone.utc).isoformat(),
accessed_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
user_agent=user_agent[:500] if user_agent else None,
request_type=request_type,
)
+16
View File
@@ -156,10 +156,26 @@ class DocumentService:
doc = self.get_or_404(doc_id, user)
file_size = doc.file_size
self._doc_repo.delete(doc)
self._delete_doc_files(doc)
# 回补配额
self._restore_quota(user, file_size)
self._session.commit()
def _delete_doc_files(self, doc: Document) -> None:
"""物理删除文档的原始文件与 Markdown 文件(软删后调用)。"""
from app.storage.local_storage import get_storage
storage = get_storage()
for key in (doc.storage_path, doc.markdown_path):
if not key:
continue
try:
storage.delete(key)
except Exception as exc: # noqa: BLE001
import logging
logging.getLogger(__name__).warning("清理文件失败 key=%s: %s", key, exc)
def _check_quota(self, user: User, file_size: int) -> None:
settings = get_settings()
if user.storage_used + file_size > settings.default_storage_quota:
+28 -2
View File
@@ -4,7 +4,9 @@ HTML/MD/TXT/JSON/搜索全部通过此 Service 获取数据,不各自写查询
支持目录树结构和分类过滤。
"""
from app.core.errors import NotFoundError
from datetime import datetime
from app.core.errors import LinkExpiredError, NotFoundError
from app.core.security import hash_token
from app.models.document import Document
from app.models.document_category import DocumentCategory
@@ -14,6 +16,8 @@ from app.repositories.kb_repo import KnowledgeBaseRepository
from sqlalchemy import func, select
from sqlalchemy.orm import Session
_TIME_FMT = "%Y-%m-%d %H:%M:%S"
class KbPublicService:
def __init__(self, session: Session) -> None:
@@ -22,13 +26,35 @@ class KbPublicService:
self._doc_repo = DocumentRepository(session)
def get_kb_by_token(self, token: str) -> KnowledgeBase:
"""通过 token 获取知识库。不存在/禁用/删除 → 404。"""
"""通过 token 获取知识库。
不存在/禁用/删除 → 404;存在但已过期 → 410LinkExpiredError)。
"""
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("知识库不存在。")
if kb.token_expires_at:
try:
expires = datetime.strptime(kb.token_expires_at, _TIME_FMT)
except ValueError:
expires = None
if expires is not None and datetime.now() > expires:
raise LinkExpiredError()
return kb
@staticmethod
def is_expired(kb: KnowledgeBase) -> bool:
"""判断知识库链接是否已过期(管理端展示用)。"""
if not kb.token_expires_at:
return False
try:
expires = datetime.strptime(kb.token_expires_at, _TIME_FMT)
except ValueError:
return False
return datetime.now() > expires
def get_category_tree(self, kb: KnowledgeBase) -> list[dict]:
"""获取目录树(含文档数量)。"""
stmt = (
+44
View File
@@ -1,6 +1,7 @@
"""知识库服务:CRUD + Token 管理 + 默认目录树。"""
from app.core.errors import NotFoundError, PermissionDeniedError
from app.core.logging import get_logger
from app.models.document_category import DocumentCategory
from app.models.knowledge_base import KnowledgeBase
from app.models.user import User
@@ -8,6 +9,8 @@ from app.repositories.kb_repo import KnowledgeBaseRepository
from app.services.token_service import TokenService
from sqlalchemy.orm import Session
logger = get_logger(__name__)
class KnowledgeBaseService:
def __init__(self, session: Session) -> None:
@@ -96,8 +99,35 @@ class KnowledgeBaseService:
def delete(self, kb_id: str, user: User) -> None:
kb = self.get_or_404(kb_id, user)
self._kb_repo.delete(kb)
self._cleanup_kb_files(kb)
self._session.commit()
def _cleanup_kb_files(self, kb: KnowledgeBase) -> None:
"""物理删除知识库下所有文档的原始文件与 Markdown 文件(软删后调用)。
文件删除失败不阻塞删除流程(记录日志,可由后续清理兜底)。
"""
from sqlalchemy import select
from app.models.document import Document
from app.storage.local_storage import get_storage
stmt = select(Document).where(Document.knowledge_base_id == kb.id)
docs = list(self._session.scalars(stmt).all())
storage = get_storage()
removed = 0
for doc in docs:
for key in (doc.storage_path, doc.markdown_path):
if not key:
continue
try:
storage.delete(key)
removed += 1
except Exception as exc: # noqa: BLE001
logger.warning("清理文件失败 key=%s: %s", key, exc)
if removed:
logger.info("KB %s 软删,已物理清理 %d 个文件", kb.id, removed)
def regenerate_token(self, kb_id: str, user: User) -> tuple[KnowledgeBase, str]:
"""重新生成 Token。旧链接立即失效。"""
kb = self.get_or_404(kb_id, user)
@@ -117,6 +147,20 @@ class KnowledgeBaseService:
self._session.commit()
return kb
def set_expiry(self, kb_id: str, user: User, expires_in_minutes: int | None) -> KnowledgeBase:
"""设置链接有效期。None = 长期有效;负数表示已过期(测试用)。"""
from datetime import datetime, timedelta
kb = self.get_or_404(kb_id, user)
if expires_in_minutes is None:
kb.token_expires_at = None
else:
kb.token_expires_at = (datetime.now() + timedelta(minutes=expires_in_minutes)).strftime(
"%Y-%m-%d %H:%M:%S"
)
self._session.commit()
return kb
def get_full_token(self, kb: KnowledgeBase) -> str | None:
"""解密 token 原文(供后台显示完整链接)。"""
if kb.token_encrypted: