refactor(knowledge): keep latest version only

This commit is contained in:
2026-07-16 18:24:49 +08:00
parent 9ba8fb6e0c
commit e3634eb26c
8 changed files with 109 additions and 197 deletions

View File

@@ -23,7 +23,6 @@ from app.schemas.knowledge import (
KnowledgeLifecycleRequest,
KnowledgeBatchMetadataUpdateRequest,
KnowledgeMetadataUpdateRequest,
KnowledgeRollbackRequest,
KnowledgeStatusRequest,
)
from app.services.admin_service import OperationLogService
@@ -113,22 +112,6 @@ def detail(
)
@router.get("/knowledge/{knowledge_id}/versions")
def versions(
knowledge_id: int,
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
_require_super_admin(current_admin)
_knowledge(db, knowledge_id)
rows = db.scalars(
select(KnowledgeVersion)
.where(KnowledgeVersion.knowledge_id == knowledge_id)
.order_by(KnowledgeVersion.version_no.desc())
).all()
return api_success([_version_dict(item) for item in rows])
@router.get("/knowledge/version/{version_id}/content")
def version_content(
version_id: int,
@@ -218,29 +201,6 @@ async def synchronize(
return api_success(_job_dict(job))
@router.post("/knowledge/{knowledge_id}/version/{version_id}/rollback")
def rollback(
knowledge_id: int,
version_id: int,
payload: KnowledgeRollbackRequest,
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
_require_super_admin(current_admin)
knowledge, version = _knowledge_version(db, knowledge_id, version_id)
if knowledge.current_version_id == version.id:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="目标版本已是当前正式版本")
if payload.keepOpen and knowledge.lifecycle_status != "active":
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="归档知识库不能在回滚后开放")
try:
KnowledgePipelineService.rollback(db, knowledge, version, current_admin.id, payload.keepOpen)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc
OperationLogService.write(db, admin_id=current_admin.id, module="knowledge", action="rollback", target_id=knowledge.id)
db.commit()
return api_success(_knowledge_detail(knowledge))
@router.put("/knowledge/{knowledge_id}/open-status")
def open_status(
knowledge_id: int,
@@ -340,14 +300,6 @@ def _version(db: Session, version_id: int) -> KnowledgeVersion:
return item
def _knowledge_version(db: Session, knowledge_id: int, version_id: int) -> tuple[Knowledge, KnowledgeVersion]:
knowledge = _knowledge(db, knowledge_id)
version = _version(db, version_id)
if version.knowledge_id != knowledge.id:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="版本不属于该知识库")
return knowledge, version
def _knowledge_detail(item: Knowledge) -> dict:
return {
"id": item.id,

View File

@@ -3,10 +3,6 @@ from __future__ import annotations
from pydantic import BaseModel, Field
class KnowledgeRollbackRequest(BaseModel):
keepOpen: bool = False
class KnowledgeStatusRequest(BaseModel):
open: bool

View File

@@ -6,7 +6,7 @@ import re
from dataclasses import dataclass
from datetime import UTC, datetime
from sqlalchemy import func, select
from sqlalchemy import delete, func, select
from sqlalchemy.orm import Session
from app.models.knowledge import (
@@ -15,6 +15,7 @@ from app.models.knowledge import (
KnowledgeChunk,
KnowledgeManifest,
KnowledgePublishLog,
KnowledgeReviewRecord,
KnowledgeSection,
KnowledgeSourceSnapshot,
KnowledgeSyncJob,
@@ -24,7 +25,7 @@ from app.services.external_errors import ExternalServiceError
from app.services.feishu_async_service import AsyncFeishuKnowledgeService
from app.services.feishu_service import _feishu_retrieval_config
PROCESSING_RULE_VERSION = "knowledge-pipeline-v2-auto-publish"
PROCESSING_RULE_VERSION = "knowledge-pipeline-v3-latest-only"
MAX_SECTION_CHARS = 2800
MAX_CHUNK_CHARS = 650
@@ -114,6 +115,8 @@ class KnowledgePipelineService:
else:
job.status = "unchanged"
job.result_summary = json.dumps({"changed": False}, ensure_ascii=False)
if latest_version is not None:
cls._prune_old_versions(db, knowledge, latest_version)
job.stage = "completed"
job.progress = 100
job.finished_at = _now()
@@ -305,7 +308,7 @@ class KnowledgePipelineService:
keep_open: bool,
action: str,
) -> None:
"""Atomically activate a validated version and preserve rollback history."""
"""Atomically replace the active version with validated latest content."""
if version.quality_status == "blocked":
raise ValueError("质检未通过的版本不能生效")
if keep_open and knowledge.lifecycle_status != "active":
@@ -313,17 +316,6 @@ class KnowledgePipelineService:
if keep_open and knowledge.source_status != "normal":
raise ValueError("飞书源文章异常,不能替换正式版本")
previous_version_id = knowledge.current_version_id
if previous_version_id:
previous = db.get(KnowledgeVersion, previous_version_id)
if previous and previous.id != version.id:
previous.status = "superseded"
db.add(previous)
previous_pending_id = knowledge.pending_version_id
if previous_pending_id and previous_pending_id not in {previous_version_id, version.id}:
previous_pending = db.get(KnowledgeVersion, previous_pending_id)
if previous_pending and previous_pending.status not in {"published", "superseded"}:
previous_pending.status = "discarded"
db.add(previous_pending)
version.status = "published"
version.published_by = admin_id
version.published_at = _now()
@@ -347,34 +339,36 @@ class KnowledgePipelineService:
),
]
)
db.flush()
KnowledgePipelineService._prune_old_versions(db, knowledge, version)
@staticmethod
def rollback(db: Session, knowledge: Knowledge, version: KnowledgeVersion, admin_id: int, keep_open: bool) -> None:
if version.status not in {"published", "superseded"}:
raise ValueError("只能回滚到历史正式版本")
previous_id = knowledge.current_version_id
if previous_id:
previous = db.get(KnowledgeVersion, previous_id)
if previous:
previous.status = "superseded"
db.add(previous)
version.status = "published"
knowledge.current_version_id = version.id
knowledge.status = 1 if keep_open else 0
knowledge.last_published_at = _now()
db.add_all(
[
knowledge,
version,
KnowledgePublishLog(
knowledge_id=knowledge.id,
from_version_id=previous_id,
to_version_id=version.id,
action="rollback",
keep_open=knowledge.status,
operated_by=admin_id,
),
]
def _prune_old_versions(db: Session, knowledge: Knowledge, keep: KnowledgeVersion) -> None:
"""Keep only the active version and its derived retrieval data."""
old_version_ids = list(db.scalars(
select(KnowledgeVersion.id).where(
KnowledgeVersion.knowledge_id == knowledge.id,
KnowledgeVersion.id != keep.id,
)
).all())
if old_version_ids:
for model in (
KnowledgeReviewRecord,
KnowledgeCard,
KnowledgeChunk,
KnowledgeSection,
KnowledgeManifest,
):
db.execute(delete(model).where(model.version_id.in_(old_version_ids)))
db.execute(delete(KnowledgeVersion).where(KnowledgeVersion.id.in_(old_version_ids)))
# Snapshots are source copies for a version. Once that version is gone,
# retaining the old snapshot would still be retaining knowledge history.
db.execute(
delete(KnowledgeSourceSnapshot).where(
KnowledgeSourceSnapshot.knowledge_id == knowledge.id,
KnowledgeSourceSnapshot.id != keep.snapshot_id,
)
)
@staticmethod