feat: 添加风险规则及 agent assets 功能增强
This commit is contained in:
@@ -36,7 +36,6 @@ from app.schemas.agent_asset import (
|
||||
AgentAssetSpreadsheetDiffCellRead,
|
||||
AgentAssetSpreadsheetDiffSheetRead,
|
||||
AgentAssetUpdate,
|
||||
AgentAssetVersionCompareRead,
|
||||
AgentAssetVersionCreate,
|
||||
AgentAssetVersionRead,
|
||||
AgentAssetVersionTimelineItemRead,
|
||||
@@ -511,18 +510,16 @@ class AgentAssetService:
|
||||
return self._build_onlyoffice_spreadsheet_config(
|
||||
asset_id=asset_id,
|
||||
current_user=current_user,
|
||||
resolved_version=resolved_version,
|
||||
metadata=metadata,
|
||||
editable=resolved_version == PREVIEW_RULE_CURRENT_VERSION,
|
||||
)
|
||||
|
||||
asset = self._require_spreadsheet_rule(asset_id)
|
||||
resolved_version, metadata = self._resolve_current_spreadsheet_meta(asset)
|
||||
_, metadata = self._resolve_current_spreadsheet_meta(asset)
|
||||
editable = self._can_edit_current_spreadsheet(current_user)
|
||||
return self._build_onlyoffice_spreadsheet_config(
|
||||
asset_id=asset.id,
|
||||
current_user=current_user,
|
||||
resolved_version=resolved_version,
|
||||
metadata=metadata,
|
||||
editable=editable,
|
||||
)
|
||||
@@ -555,7 +552,6 @@ class AgentAssetService:
|
||||
def validate_rule_spreadsheet_access_token(
|
||||
self,
|
||||
asset_id: str,
|
||||
version: str,
|
||||
access_token: str,
|
||||
) -> None:
|
||||
onlyoffice_settings = resolve_onlyoffice_settings()
|
||||
@@ -571,7 +567,6 @@ class AgentAssetService:
|
||||
if (
|
||||
payload.get("scope") != "agent-asset-spreadsheet"
|
||||
or payload.get("asset_id") != asset_id
|
||||
or payload.get("version") != version
|
||||
):
|
||||
raise ValueError("ONLYOFFICE 文件访问令牌无效。")
|
||||
|
||||
@@ -604,7 +599,6 @@ class AgentAssetService:
|
||||
)
|
||||
changed_sheet_count = self._count_changed_sheets(sheet_changes, cell_changes)
|
||||
changed_cell_count = len(cell_changes)
|
||||
next_version = self._next_available_version(asset)
|
||||
|
||||
metadata = self._store_current_rule_spreadsheet(
|
||||
asset,
|
||||
@@ -613,45 +607,10 @@ class AgentAssetService:
|
||||
actor=actor,
|
||||
source=source,
|
||||
)
|
||||
snapshot_metadata = self.spreadsheet_manager.store_rule_library_spreadsheet_snapshot(
|
||||
library=self._resolve_spreadsheet_rule_library(asset),
|
||||
asset_id=asset.id,
|
||||
version=next_version,
|
||||
file_name=file_name,
|
||||
content=content,
|
||||
actor_name=actor,
|
||||
source=source,
|
||||
)
|
||||
operation_label = (
|
||||
change_note
|
||||
or (
|
||||
"ONLYOFFICE 在线编辑"
|
||||
if source == "onlyoffice"
|
||||
else f"上传并覆盖当前规则表:{normalized_name}"
|
||||
)
|
||||
)
|
||||
summary = self._build_spreadsheet_change_summary(
|
||||
operation_label,
|
||||
sheet_changes,
|
||||
cell_changes,
|
||||
)
|
||||
version_content = self.spreadsheet_manager.build_version_markdown(
|
||||
rule_name=asset.name,
|
||||
version=next_version,
|
||||
metadata=snapshot_metadata,
|
||||
)
|
||||
self.create_version(
|
||||
asset.id,
|
||||
AgentAssetVersionCreate(
|
||||
version=next_version,
|
||||
content=version_content,
|
||||
content_type=AgentAssetContentType.MARKDOWN,
|
||||
change_note=summary,
|
||||
created_by=actor,
|
||||
),
|
||||
actor=actor,
|
||||
request_id=request_id,
|
||||
)
|
||||
self.audit_service.log_action(
|
||||
actor=actor,
|
||||
action="edit_rule_spreadsheet",
|
||||
@@ -660,13 +619,11 @@ class AgentAssetService:
|
||||
before_json={"storage_key": current_metadata.storage_key},
|
||||
after_json={
|
||||
"summary": summary,
|
||||
"version": next_version,
|
||||
"changed_sheet_count": changed_sheet_count,
|
||||
"changed_cell_count": changed_cell_count,
|
||||
"sheet_changes": [item.model_dump() for item in sheet_changes],
|
||||
"cell_changes": [item.model_dump() for item in cell_changes[:500]],
|
||||
"storage_key": metadata.storage_key,
|
||||
"snapshot_storage_key": snapshot_metadata.storage_key,
|
||||
},
|
||||
request_id=request_id,
|
||||
)
|
||||
@@ -705,7 +662,7 @@ class AgentAssetService:
|
||||
self,
|
||||
asset_id: str,
|
||||
*,
|
||||
version: str,
|
||||
version: str | None = None,
|
||||
payload: dict[str, Any],
|
||||
actor_name: str | None = None,
|
||||
) -> None:
|
||||
@@ -721,8 +678,6 @@ class AgentAssetService:
|
||||
callback = self._parse_onlyoffice_callback(payload)
|
||||
if callback.status not in {2, 6} or not callback.download_url:
|
||||
return
|
||||
if str(version or "").strip() not in {"", "current", self._resolve_working_version(asset)}:
|
||||
return
|
||||
|
||||
_, current_metadata = self._resolve_current_spreadsheet_meta(asset)
|
||||
request = Request(
|
||||
@@ -924,44 +879,6 @@ class AgentAssetService:
|
||||
|
||||
return sorted(events, key=lambda item: item.event_time)
|
||||
|
||||
def compare_spreadsheet_versions(
|
||||
self,
|
||||
asset_id: str,
|
||||
*,
|
||||
base_version: str,
|
||||
target_version: str,
|
||||
) -> AgentAssetVersionCompareRead:
|
||||
self._ensure_ready()
|
||||
asset = self._require_spreadsheet_rule(asset_id)
|
||||
resolved_base, base_meta = self._resolve_spreadsheet_version_meta(
|
||||
asset,
|
||||
version=base_version,
|
||||
)
|
||||
resolved_target, target_meta = self._resolve_spreadsheet_version_meta(
|
||||
asset,
|
||||
version=target_version,
|
||||
)
|
||||
|
||||
base_workbook = self._load_spreadsheet_for_compare(base_meta)
|
||||
target_workbook = self._load_spreadsheet_for_compare(target_meta)
|
||||
sheet_changes, cell_changes = self._collect_workbook_changes(
|
||||
base_workbook,
|
||||
target_workbook,
|
||||
)
|
||||
added_sheet_count = sum(1 for item in sheet_changes if item.change_type == "added")
|
||||
removed_sheet_count = sum(1 for item in sheet_changes if item.change_type == "removed")
|
||||
|
||||
return AgentAssetVersionCompareRead(
|
||||
base_version=resolved_base,
|
||||
target_version=resolved_target,
|
||||
added_sheet_count=added_sheet_count,
|
||||
removed_sheet_count=removed_sheet_count,
|
||||
changed_sheet_count=self._count_changed_sheets(sheet_changes, cell_changes),
|
||||
changed_cell_count=len(cell_changes),
|
||||
sheet_changes=sheet_changes,
|
||||
cell_changes=cell_changes[:500],
|
||||
)
|
||||
|
||||
def list_spreadsheet_change_records(
|
||||
self,
|
||||
asset_id: str,
|
||||
@@ -981,8 +898,7 @@ class AgentAssetService:
|
||||
id=log.id,
|
||||
actor=log.actor,
|
||||
changed_at=log.created_at,
|
||||
version=str((log.after_json or {}).get("version") or "").strip() or None,
|
||||
summary=str((log.after_json or {}).get("summary") or "ONLYOFFICE 在线编辑保存。"),
|
||||
summary=str((log.after_json or {}).get("summary") or "表格内容已保存。"),
|
||||
sheet_changes=[
|
||||
AgentAssetSpreadsheetDiffSheetRead.model_validate(item)
|
||||
for item in ((log.after_json or {}).get("sheet_changes") or [])
|
||||
@@ -1292,7 +1208,6 @@ class AgentAssetService:
|
||||
*,
|
||||
asset_id: str,
|
||||
current_user: CurrentUserContext,
|
||||
resolved_version: str,
|
||||
metadata: RuleSpreadsheetMeta,
|
||||
editable: bool,
|
||||
) -> AgentAssetOnlyOfficeConfigRead:
|
||||
@@ -1307,21 +1222,21 @@ class AgentAssetService:
|
||||
|
||||
backend_base_url = onlyoffice_settings.backend_url.rstrip("/")
|
||||
public_url = onlyoffice_settings.public_url.rstrip("/")
|
||||
access_token = self._build_onlyoffice_access_token(asset_id, resolved_version)
|
||||
access_token = self._build_onlyoffice_access_token(asset_id)
|
||||
document_url = (
|
||||
f"{backend_base_url}{settings.api_v1_prefix}/agent-assets/{asset_id}/spreadsheet/onlyoffice/content"
|
||||
f"?version={resolved_version}&access_token={access_token}"
|
||||
f"?access_token={access_token}"
|
||||
)
|
||||
callback_url = (
|
||||
f"{backend_base_url}{settings.api_v1_prefix}/agent-assets/{asset_id}/spreadsheet/onlyoffice/callback"
|
||||
f"?version={resolved_version}&actor_name={quote(current_user.name)}"
|
||||
f"?actor_name={quote(current_user.name)}"
|
||||
)
|
||||
|
||||
config: dict[str, Any] = {
|
||||
"documentType": "cell",
|
||||
"document": {
|
||||
"fileType": Path(metadata.file_name).suffix.lstrip(".").lower() or "xlsx",
|
||||
"key": self._build_onlyoffice_document_key(asset_id, resolved_version, metadata),
|
||||
"key": self._build_onlyoffice_document_key(asset_id, metadata),
|
||||
"title": metadata.file_name,
|
||||
"url": document_url,
|
||||
"permissions": {
|
||||
@@ -1462,19 +1377,6 @@ class AgentAssetService:
|
||||
major, minor, patch = [int(item) for item in parts]
|
||||
return f"v{major}.{minor}.{patch + 1}"
|
||||
|
||||
@staticmethod
|
||||
def _can_edit_spreadsheet_version(
|
||||
asset: AgentAsset,
|
||||
current_user: CurrentUserContext,
|
||||
version: str,
|
||||
) -> bool:
|
||||
role_codes = {str(item).strip() for item in current_user.role_codes}
|
||||
can_edit = current_user.is_admin or "manager" in role_codes or "finance" in role_codes
|
||||
return (
|
||||
can_edit
|
||||
and AgentAssetService._resolve_working_version(asset) == str(version or "").strip()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _can_edit_current_spreadsheet(current_user: CurrentUserContext) -> bool:
|
||||
role_codes = {str(item).strip() for item in current_user.role_codes}
|
||||
@@ -1483,23 +1385,21 @@ class AgentAssetService:
|
||||
@staticmethod
|
||||
def _build_onlyoffice_document_key(
|
||||
asset_id: str,
|
||||
version: str,
|
||||
metadata: RuleSpreadsheetMeta,
|
||||
) -> str:
|
||||
fingerprint = metadata.checksum or metadata.updated_at or metadata.file_name
|
||||
raw_key = f"{asset_id}-{version}-{fingerprint}"
|
||||
raw_key = f"{asset_id}-{fingerprint}"
|
||||
return "".join(
|
||||
character if character.isalnum() or character in {"-", "_", ".", "="} else "_"
|
||||
for character in raw_key
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _build_onlyoffice_access_token(asset_id: str, version: str) -> str:
|
||||
def _build_onlyoffice_access_token(asset_id: str) -> str:
|
||||
onlyoffice_settings = resolve_onlyoffice_settings()
|
||||
payload = {
|
||||
"scope": "agent-asset-spreadsheet",
|
||||
"asset_id": asset_id,
|
||||
"version": version,
|
||||
}
|
||||
return jwt.encode(payload, onlyoffice_settings.jwt_secret, algorithm="HS256")
|
||||
|
||||
@@ -1646,7 +1546,6 @@ class AgentAssetService:
|
||||
|
||||
@staticmethod
|
||||
def _build_spreadsheet_change_summary(
|
||||
operation_label: str,
|
||||
sheet_changes: list[AgentAssetSpreadsheetDiffSheetRead],
|
||||
cell_changes: list[AgentAssetSpreadsheetDiffCellRead],
|
||||
) -> str:
|
||||
@@ -1655,15 +1554,15 @@ class AgentAssetService:
|
||||
| {item.sheet_name for item in cell_changes}
|
||||
)
|
||||
if not sheet_names:
|
||||
return f"{operation_label}:文件内容已保存,未发现单元格级差异。"
|
||||
return "文件内容已保存,未发现单元格级差异。"
|
||||
|
||||
preview = "、".join(sheet_names[:3])
|
||||
if len(sheet_names) > 3:
|
||||
preview = f"{preview} 等"
|
||||
sheet_text = f"涉及 {len(sheet_names)} 个工作表({preview})"
|
||||
if cell_changes:
|
||||
return f"{operation_label}:{sheet_text},共 {len(cell_changes)} 处单元格改动。"
|
||||
return f"{operation_label}:{sheet_text},工作表结构发生变化。"
|
||||
return f"{sheet_text},共 {len(cell_changes)} 处单元格改动。"
|
||||
return f"{sheet_text},工作表结构发生变化。"
|
||||
|
||||
def _next_available_version(self, asset: AgentAsset) -> str:
|
||||
candidate = self._increment_version(self._resolve_working_version(asset))
|
||||
|
||||
Reference in New Issue
Block a user