feat: 增强 agent_assets 功能,支持更多资产操作

This commit is contained in:
caoxiaozhu
2026-05-18 02:48:51 +00:00
parent 68f663f2f4
commit 55e0591a5e
6 changed files with 1654 additions and 148 deletions

View File

@@ -2,19 +2,32 @@ from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends, Header, HTTPException, Query, status
from fastapi import APIRouter, Body, Depends, Header, HTTPException, Query, status
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session
from app.api.deps import get_db
from app.api.deps import (
CurrentUserContext,
get_current_user,
get_db,
require_admin_user,
require_rule_editor_user,
require_rule_reviewer_user,
)
from app.schemas.agent_asset import (
AgentAssetCreate,
AgentAssetListItem,
AgentAssetOnlyOfficeCallbackRead,
AgentAssetOnlyOfficeCallbackWrite,
AgentAssetOnlyOfficeConfigRead,
AgentAssetRead,
AgentAssetReviewCreate,
AgentAssetReviewRead,
AgentAssetVersionCompareRead,
AgentAssetUpdate,
AgentAssetVersionCreate,
AgentAssetVersionRead,
AgentAssetVersionTimelineItemRead,
)
from app.schemas.common import ErrorResponse
from app.services.agent_assets import AgentAssetService
@@ -29,6 +42,10 @@ RequestIdHeader = Annotated[
str | None,
Header(description="外部请求 ID用于串联审计日志和上游调用链。"),
]
CurrentUser = Annotated[CurrentUserContext, Depends(get_current_user)]
AdminUser = Annotated[CurrentUserContext, Depends(require_admin_user)]
RuleEditorUser = Annotated[CurrentUserContext, Depends(require_rule_editor_user)]
RuleReviewerUser = Annotated[CurrentUserContext, Depends(require_rule_reviewer_user)]
def _handle_asset_error(exc: Exception) -> None:
@@ -93,6 +110,185 @@ def get_agent_asset(asset_id: str, db: DbSession) -> AgentAssetRead:
return asset
@router.get(
"/{asset_id}/spreadsheet/onlyoffice-config",
response_model=AgentAssetOnlyOfficeConfigRead,
summary="读取规则 Excel 的 ONLYOFFICE 配置",
description="为规则详情页中的 Excel 规则表生成 ONLYOFFICE 配置。",
)
def get_agent_asset_spreadsheet_onlyoffice_config(
asset_id: str,
current_user: CurrentUser,
db: DbSession,
version: Annotated[
str | None,
Query(description="可选的规则版本号;不传时默认当前版本。"),
] = None,
) -> AgentAssetOnlyOfficeConfigRead:
try:
return AgentAssetService(db).build_rule_spreadsheet_onlyoffice_config(
asset_id,
current_user,
version=version,
)
except Exception as exc:
_handle_asset_error(exc)
@router.get(
"/{asset_id}/spreadsheet/content",
response_class=FileResponse,
summary="下载或预览规则 Excel 文件",
description="按版本返回规则的 Excel 快照,用于浏览器预览或下载。",
)
def get_agent_asset_spreadsheet_content(
asset_id: str,
_: CurrentUser,
db: DbSession,
version: Annotated[
str | None,
Query(description="可选的规则版本号;不传时默认当前版本。"),
] = None,
) -> FileResponse:
try:
file_path, media_type, filename = AgentAssetService(db).get_rule_spreadsheet_content(
asset_id,
version=version,
)
except Exception as exc:
_handle_asset_error(exc)
return FileResponse(file_path, media_type=media_type, filename=filename)
@router.get(
"/{asset_id}/spreadsheet/onlyoffice/content",
response_class=FileResponse,
summary="供 ONLYOFFICE 读取规则 Excel 源文件",
description="使用短时令牌供 ONLYOFFICE 拉取规则表源文件。",
)
def get_agent_asset_spreadsheet_onlyoffice_content(
asset_id: str,
db: DbSession,
version: Annotated[
str,
Query(min_length=1, description="规则版本号。"),
],
access_token: Annotated[
str,
Query(min_length=1, description="ONLYOFFICE 临时访问令牌。"),
],
) -> FileResponse:
try:
service = AgentAssetService(db)
service.validate_rule_spreadsheet_access_token(asset_id, version, access_token)
file_path, media_type, filename = service.get_rule_spreadsheet_content(
asset_id,
version=version,
)
except FileNotFoundError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc)) from exc
except Exception as exc:
_handle_asset_error(exc)
return FileResponse(file_path, media_type=media_type, filename=filename)
@router.post(
"/{asset_id}/spreadsheet/upload",
response_model=AgentAssetRead,
status_code=status.HTTP_201_CREATED,
summary="上传规则 Excel 文件",
description="为指定规则上传新的 Excel 快照,并自动生成新规则版本。",
)
def upload_agent_asset_spreadsheet(
asset_id: str,
content: Annotated[
bytes,
Body(
media_type="application/octet-stream",
description="待上传的 Excel 文件二进制内容。",
),
],
filename: Annotated[str, Query(min_length=1, description="原始文件名。")],
current_user: RuleEditorUser,
db: DbSession,
x_request_id: RequestIdHeader = None,
) -> AgentAssetRead:
try:
return AgentAssetService(db).upload_rule_spreadsheet(
asset_id,
filename=filename,
content=content,
actor=current_user.name,
request_id=x_request_id,
)
except Exception as exc:
_handle_asset_error(exc)
@router.post(
"/{asset_id}/spreadsheet/import-content",
response_model=AgentAssetRead,
status_code=status.HTTP_201_CREATED,
summary="导入规则 Excel 表格内容",
description="读取上传 Excel 中的工作表内容,写回当前规则表;保留当前规则文件名与规则身份。",
)
def import_agent_asset_spreadsheet_content(
asset_id: str,
content: Annotated[
bytes,
Body(
media_type="application/octet-stream",
description="待导入的 Excel 文件二进制内容。",
),
],
filename: Annotated[str, Query(min_length=1, description="上传文件原始文件名。")],
current_user: RuleEditorUser,
db: DbSession,
x_request_id: RequestIdHeader = None,
) -> AgentAssetRead:
try:
return AgentAssetService(db).import_rule_spreadsheet_content(
asset_id,
filename=filename,
content=content,
actor=current_user.name,
request_id=x_request_id,
)
except Exception as exc:
_handle_asset_error(exc)
@router.post(
"/{asset_id}/spreadsheet/onlyoffice/callback",
response_model=AgentAssetOnlyOfficeCallbackRead,
summary="接收规则 Excel 的 ONLYOFFICE 回调",
description="接收 ONLYOFFICE 回写内容,并自动生成新的规则版本。",
)
def handle_agent_asset_spreadsheet_onlyoffice_callback(
asset_id: str,
payload: AgentAssetOnlyOfficeCallbackWrite,
db: DbSession,
version: Annotated[
str,
Query(min_length=1, description="打开编辑器时对应的规则版本号。"),
],
) -> AgentAssetOnlyOfficeCallbackRead:
try:
AgentAssetService(db).handle_rule_spreadsheet_onlyoffice_callback(
asset_id,
version=version,
payload=payload.model_dump(),
)
except Exception as exc:
_handle_asset_error(exc)
return AgentAssetOnlyOfficeCallbackRead()
@router.post(
"",
response_model=AgentAssetRead,
@@ -237,11 +433,22 @@ def create_agent_asset_version(
def create_agent_asset_review(
asset_id: str,
payload: AgentAssetReviewCreate,
current_user: CurrentUser,
db: DbSession,
x_actor: ActorHeader = None,
x_request_id: RequestIdHeader = None,
) -> AgentAssetReviewRead:
try:
role_codes = {item.strip() for item in current_user.role_codes}
if payload.review_status.value == "pending":
if not (
current_user.is_admin
or "manager" in role_codes
or "finance" in role_codes
):
raise PermissionError("只有财务人员或高级管理人员可以提交审核。")
elif not (current_user.is_admin or "manager" in role_codes):
raise PermissionError("只有高级管理人员可以审核规则。")
return AgentAssetService(db).create_review(
asset_id,
payload,
@@ -270,6 +477,7 @@ def create_agent_asset_review(
)
def activate_agent_asset(
asset_id: str,
_: RuleReviewerUser,
db: DbSession,
x_actor: ActorHeader = None,
x_request_id: RequestIdHeader = None,
@@ -282,3 +490,68 @@ def activate_agent_asset(
)
except Exception as exc:
_handle_asset_error(exc)
@router.post(
"/{asset_id}/versions/{version}/restore",
response_model=AgentAssetRead,
summary="基于历史版本恢复工作稿",
description="复制指定历史版本内容生成新的工作版本,用于误上线后的快速恢复与重新审核。",
)
def restore_agent_asset_version(
asset_id: str,
version: str,
current_user: RuleReviewerUser,
db: DbSession,
x_actor: ActorHeader = None,
x_request_id: RequestIdHeader = None,
) -> AgentAssetRead:
try:
return AgentAssetService(db).restore_version_as_working_copy(
asset_id,
version,
actor=(x_actor or current_user.name or "system").strip() or "system",
request_id=x_request_id,
)
except Exception as exc:
_handle_asset_error(exc)
@router.get(
"/{asset_id}/version-timeline",
response_model=list[AgentAssetVersionTimelineItemRead],
summary="读取规则版本流转时间线",
description="返回规则版本创建、提交审核、审核结果和正式上线等流转事件。",
)
def get_agent_asset_version_timeline(
asset_id: str,
_: CurrentUser,
db: DbSession,
) -> list[AgentAssetVersionTimelineItemRead]:
try:
return AgentAssetService(db).list_version_timeline(asset_id)
except Exception as exc:
_handle_asset_error(exc)
@router.get(
"/{asset_id}/versions/compare",
response_model=AgentAssetVersionCompareRead,
summary="比较两个规则表版本",
description="对比两个 Excel 规则表版本的工作表变化与单元格级差异。",
)
def compare_agent_asset_spreadsheet_versions(
asset_id: str,
_: CurrentUser,
db: DbSession,
base_version: Annotated[str, Query(min_length=1, description="基准版本号")],
target_version: Annotated[str, Query(min_length=1, description="对比版本号")],
) -> AgentAssetVersionCompareRead:
try:
return AgentAssetService(db).compare_spreadsheet_versions(
asset_id,
base_version=base_version,
target_version=target_version,
)
except Exception as exc:
_handle_asset_error(exc)