mirror of
https://github.com/insistence/RuoYi-Vue3-FastAPI.git
synced 2026-10-13 04:39:37 +00:00
* feat: 初始化插件系统 * refactor: 收口插件系统运行时重构 * perf: 优化插件系统类型提示 * fix&perf: 修复和优化插件系统 * fix: 修复gitignore规则误忽略插件文件的问题 * fix: 修复运行时插件根路径算错的问题 * fix: 加强插件发现和路由注册的防护措施 * revert: 回滚定时任务白名单 * fix: 移除未使用的应用路由注册探测 * revert: 恢复部分代码 * perf: 优化插件系统 * docs: 新增插件开发文档 * perf: 优化插件管理模块 * perf: 提升插件系统核心能力 * refactor: 重构生命周期 step runner * fix: 修复lint错误 * test: 清理测试用例 * test: 调整测试目录名称 * fix: 修复前后端目录硬编码的问题 * fix: 修复插件系统安全性缺口 * refactor: 重新设计插件生命周期 Migration 事务与回滚 * perf: 优化插件系统边界问题 * refactor: 重构当前插件系统的依赖体系设计 * perf: 优化代码 * perf: 优化代码 * fix: 修复代码合并问题 * fix: 修复bug * perf: 优化代码 * perf&fix: 优化代码和修复bug * docs: 优化文档格式 * feat: 适配Vue2版本 * docs: 更新README文档 * fix: 修复ruff lint错误 * chore: 更新后端依赖文件
172 lines
6.4 KiB
Python
172 lines
6.4 KiB
Python
import json
|
|
from collections.abc import Mapping
|
|
from typing import cast
|
|
|
|
from plugins.core.management.entity.vo.schemas import PluginOperationLogDetailModel, PluginOperationLogModel
|
|
from utils.log_util import logger
|
|
|
|
|
|
class PluginOperationLogBuilder:
|
|
"""
|
|
插件操作审计日志构建器。
|
|
|
|
使用 Builder 模式集中处理操作结果 payload 与审计日志模型、详情模型和导出行之间的转换。
|
|
"""
|
|
|
|
@staticmethod
|
|
def build_export_row(
|
|
operation_log: PluginOperationLogDetailModel,
|
|
operation_dict: dict[str, str] | None = None,
|
|
) -> dict[str, object]:
|
|
"""
|
|
构建插件批量操作审计日志导出行。
|
|
|
|
:param operation_log: 插件批量操作审计日志详情
|
|
:param operation_dict: 插件操作类型字典
|
|
:return: 插件批量操作审计日志导出行
|
|
"""
|
|
operation_dict = operation_dict or {}
|
|
|
|
return {
|
|
'operationId': operation_log.operation_id,
|
|
'operation': operation_dict.get(operation_log.operation, operation_log.operation),
|
|
'pluginIds': ','.join(operation_log.plugin_ids),
|
|
'dryRun': '是' if operation_log.dry_run else '否',
|
|
'continueOnError': '是' if operation_log.continue_on_error else '否',
|
|
'status': operation_log.status,
|
|
'summary': json.dumps(operation_log.summary, ensure_ascii=False),
|
|
'remark': operation_log.remark,
|
|
'createTime': operation_log.create_time,
|
|
}
|
|
|
|
@classmethod
|
|
def build_detail(cls, operation_log: Mapping[str, object]) -> PluginOperationLogDetailModel:
|
|
"""
|
|
构建插件批量操作审计日志详情。
|
|
|
|
:param operation_log: 插件批量操作审计日志字典
|
|
:return: 插件批量操作审计日志详情
|
|
"""
|
|
return PluginOperationLogDetailModel(
|
|
operationId=operation_log.get('operationId'),
|
|
operation=operation_log.get('operation') or '-',
|
|
pluginIds=cls.deserialize_json_list(operation_log.get('pluginIds')),
|
|
dryRun=operation_log.get('dryRun') == '0',
|
|
continueOnError=operation_log.get('continueOnError') == '0',
|
|
status=operation_log.get('status') or '-',
|
|
summary=cls.deserialize_json_dict(operation_log.get('summary')),
|
|
result=cls.deserialize_json_dict(operation_log.get('result')),
|
|
createTime=operation_log.get('createTime'),
|
|
remark=operation_log.get('remark'),
|
|
)
|
|
|
|
@staticmethod
|
|
def deserialize_json_dict(value: object) -> dict[str, object]:
|
|
"""
|
|
反序列化 JSON 字典。
|
|
|
|
:param value: JSON 字符串
|
|
:return: 字典对象
|
|
"""
|
|
if not isinstance(value, str) or not value:
|
|
return {}
|
|
try:
|
|
result = json.loads(value)
|
|
except json.JSONDecodeError as exc:
|
|
logger.warning(f'插件操作审计日志 JSON 字典解析失败:{exc}')
|
|
return {'parseError': 'JSON 解析失败'}
|
|
|
|
if not isinstance(result, dict):
|
|
logger.warning('插件操作审计日志 JSON 内容不是对象')
|
|
return {'parseError': 'JSON 内容不是对象'}
|
|
|
|
return cast('dict[str, object]', result)
|
|
|
|
@staticmethod
|
|
def deserialize_json_list(value: object) -> list[str]:
|
|
"""
|
|
反序列化 JSON 字符串列表。
|
|
|
|
:param value: JSON 字符串
|
|
:return: 字符串列表
|
|
"""
|
|
if not isinstance(value, str) or not value:
|
|
return []
|
|
try:
|
|
result = json.loads(value)
|
|
except json.JSONDecodeError:
|
|
return []
|
|
|
|
return [str(item) for item in result] if isinstance(result, list) else []
|
|
|
|
@classmethod
|
|
def build_model(
|
|
cls,
|
|
payload: Mapping[str, object],
|
|
*,
|
|
dry_run: bool,
|
|
continue_on_error: bool,
|
|
) -> PluginOperationLogModel:
|
|
"""
|
|
根据插件操作结果构建审计日志模型。
|
|
|
|
:param payload: 插件操作结果负载
|
|
:param dry_run: 是否预演
|
|
:param continue_on_error: 失败后是否继续执行后续插件
|
|
:return: 插件操作审计日志模型
|
|
"""
|
|
summary_value = payload.get('summary')
|
|
summary = cast('dict[str, object]', summary_value) if isinstance(summary_value, dict) else {}
|
|
plugin_ids = cls.resolve_plugin_ids(payload)
|
|
|
|
return PluginOperationLogModel(
|
|
operation=str(payload.get('operation', 'unknown')),
|
|
pluginIds=json.dumps(plugin_ids, ensure_ascii=False),
|
|
dryRun='0' if dry_run else '1',
|
|
continueOnError='0' if continue_on_error else '1',
|
|
status=cls.resolve_status(payload),
|
|
summary=json.dumps(summary, ensure_ascii=False),
|
|
result=json.dumps(dict(payload), ensure_ascii=False, default=str),
|
|
remark=str(payload.get('message', ''))[:500] or None,
|
|
)
|
|
|
|
@staticmethod
|
|
def resolve_plugin_ids(payload: Mapping[str, object]) -> list[str]:
|
|
"""
|
|
解析插件操作审计日志的目标插件 ID。
|
|
|
|
:param payload: 插件操作结果负载
|
|
:return: 目标插件 ID 列表
|
|
"""
|
|
plan_value = payload.get('plan')
|
|
plan = cast('dict[str, object]', plan_value) if isinstance(plan_value, dict) else {}
|
|
ordered_plugin_ids = plan.get('orderedPluginIds') if isinstance(plan.get('orderedPluginIds'), list) else []
|
|
if ordered_plugin_ids:
|
|
return [str(plugin_id) for plugin_id in ordered_plugin_ids]
|
|
plugin_id = payload.get('pluginId')
|
|
if plugin_id:
|
|
return [str(plugin_id)]
|
|
|
|
return []
|
|
|
|
@staticmethod
|
|
def resolve_status(payload: Mapping[str, object]) -> str:
|
|
"""
|
|
解析插件操作审计状态。
|
|
|
|
:param payload: 插件操作结果负载
|
|
:return: 审计状态
|
|
"""
|
|
if payload.get('dryRun'):
|
|
return 'dry_run'
|
|
plan_value = payload.get('plan')
|
|
plan = cast('dict[str, object]', plan_value) if isinstance(plan_value, dict) else {}
|
|
if plan.get('blockerCount', 0):
|
|
return 'blocked'
|
|
summary_value = payload.get('summary')
|
|
summary = cast('dict[str, object]', summary_value) if isinstance(summary_value, dict) else {}
|
|
if summary.get('failed', 0):
|
|
return 'failed'
|
|
|
|
return 'success' if payload.get('ok', False) else 'failed'
|