feat: enhance metadata schema API with scope filter and delete endpoint [AC-IDSMETA-13]

- Add scope filter and include_deprecated parameter to list endpoint
- Add delete metadata schema endpoint
- Fix JSONB contains query for PostgreSQL
- Add metadata config entry to dashboard help section
This commit is contained in:
MerCry 2026-03-03 00:13:57 +08:00
parent 6b6b7fb5e7
commit ee220b0b10
4 changed files with 124 additions and 8 deletions

View File

@ -401,6 +401,15 @@
<p>配置敏感词过滤和内容审核规则保障输出安全</p>
</div>
</div>
<div class="help-item" @click="navigateTo('/admin/metadata-schemas')">
<div class="help-icon primary">
<el-icon><Setting /></el-icon>
</div>
<div class="help-text">
<h4>元数据配置</h4>
<p>配置知识库意图规则等的动态元数据字段定义</p>
</div>
</div>
</div>
</el-card>
</el-col>
@ -411,7 +420,7 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { FolderOpened, Document, ChatDotSquare, Monitor, Cpu, InfoFilled, Connection, Timer, DataLine, Aim, DocumentCopy, Share, Warning } from '@element-plus/icons-vue'
import { FolderOpened, Document, ChatDotSquare, Monitor, Cpu, InfoFilled, Connection, Timer, DataLine, Aim, DocumentCopy, Share, Warning, Setting } from '@element-plus/icons-vue'
import { getDashboardStats, type DashboardStats } from '@/api/dashboard'
const router = useRouter()

View File

@ -38,7 +38,7 @@ def get_current_tenant_id() -> str:
"",
operation_id="listMetadataSchemas",
summary="List metadata schemas",
description="[AC-IDSMETA-13] 获取元数据字段定义列表,支持按状态过滤",
description="[AC-IDSMETA-13] 获取元数据字段定义列表,支持按状态和范围过滤",
)
async def list_schemas(
tenant_id: Annotated[str, Depends(get_current_tenant_id)],
@ -46,13 +46,24 @@ async def list_schemas(
status: Annotated[str | None, Query(
description="按状态过滤: draft/active/deprecated"
)] = None,
scope: Annotated[str | None, Query(
description="按适用范围过滤: kb_document/intent_rule/script_flow/prompt_template"
)] = None,
include_deprecated: Annotated[bool, Query(
description="是否包含已废弃的字段"
)] = False,
) -> JSONResponse:
"""
[AC-IDSMETA-13] 列出元数据字段定义
Args:
status: 按状态过滤
scope: 按适用范围过滤
include_deprecated: 是否包含已废弃的字段 status 未指定时生效
"""
logger.info(
f"[AC-IDSMETA-13] Listing metadata field definitions: "
f"tenant={tenant_id}, status={status}"
f"tenant={tenant_id}, status={status}, scope={scope}, include_deprecated={include_deprecated}"
)
if status and status not in [s.value for s in MetadataFieldStatus]:
@ -68,7 +79,11 @@ async def list_schemas(
)
service = MetadataFieldDefinitionService(session)
fields = await service.list_field_definitions(tenant_id, status)
if include_deprecated and not status:
fields = await service.get_field_definitions_for_read(tenant_id, scope)
else:
fields = await service.list_field_definitions(tenant_id, status, scope)
return JSONResponse(
content={
@ -358,3 +373,42 @@ async def validate_metadata_for_create(
"errors": errors,
}
)
@router.delete(
"/{field_id}",
operation_id="deleteMetadataSchema",
summary="Delete metadata schema",
description="[AC-IDSMETA-13] 删除元数据字段定义",
)
async def delete_schema(
field_id: str,
tenant_id: Annotated[str, Depends(get_current_tenant_id)],
session: Annotated[AsyncSession, Depends(get_session)],
) -> JSONResponse:
"""
[AC-IDSMETA-13] 删除元数据字段定义
"""
logger.info(
f"[AC-IDSMETA-13] Deleting metadata field definition: "
f"tenant={tenant_id}, field_id={field_id}"
)
service = MetadataFieldDefinitionService(session)
success = await service.delete_field_definition(tenant_id, field_id)
if not success:
return JSONResponse(
status_code=404,
content={
"code": "NOT_FOUND",
"message": f"Field definition not found: {field_id}",
}
)
return JSONResponse(
content={
"success": True,
"message": "Field definition deleted successfully",
}
)

View File

@ -87,8 +87,14 @@ class KBService:
"""
[AC-ASA-01] Upload document and create indexing job.
"""
import urllib.parse
doc_id = uuid.uuid4()
file_path = os.path.join(self._upload_dir, f"{tenant_id}_{doc_id}_{file_name}")
# 安全处理文件名:使用 UUID 作为存储文件名,保留原始文件名在数据库中
file_ext = os.path.splitext(file_name)[1] if file_name else ""
safe_file_name = f"{tenant_id}_{doc_id}{file_ext}"
file_path = os.path.join(self._upload_dir, safe_file_name)
with open(file_path, "wb") as f:
f.write(file_content)

View File

@ -9,7 +9,8 @@ import uuid
from datetime import datetime
from typing import Any
from sqlalchemy import select
from sqlalchemy import select, func, cast, text
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import col
@ -61,7 +62,12 @@ class MetadataFieldDefinitionService:
stmt = stmt.where(MetadataFieldDefinition.status == status)
if scope:
stmt = stmt.where(MetadataFieldDefinition.scope.contains([scope]))
stmt = stmt.where(
func.jsonb_contains(
cast(MetadataFieldDefinition.scope, JSONB),
func.cast(f'["{scope}"]', JSONB)
)
)
stmt = stmt.order_by(col(MetadataFieldDefinition.created_at).desc())
@ -272,7 +278,12 @@ class MetadataFieldDefinitionService:
)
if scope:
stmt = stmt.where(MetadataFieldDefinition.scope.contains([scope]))
stmt = stmt.where(
func.jsonb_contains(
cast(MetadataFieldDefinition.scope, JSONB),
func.cast(f'["{scope}"]', JSONB)
)
)
stmt = stmt.order_by(col(MetadataFieldDefinition.created_at).desc())
@ -470,3 +481,39 @@ class MetadataFieldDefinitionService:
}
for f in fields
}
async def delete_field_definition(
self,
tenant_id: str,
field_id: str,
) -> bool:
"""
删除元数据字段定义
Args:
tenant_id: 租户 ID
field_id: 字段定义 ID
Returns:
是否删除成功
"""
try:
import uuid
field_uuid = uuid.UUID(field_id)
except ValueError:
return False
stmt = select(MetadataFieldDefinition).where(
MetadataFieldDefinition.tenant_id == tenant_id,
MetadataFieldDefinition.id == field_uuid,
)
result = await self._session.execute(stmt)
field = result.scalar_one_or_none()
if not field:
return False
await self._session.delete(field)
await self._session.commit()
return True