Files
RuoYi-Vue3-FastAPI/ruoyi-fastapi-backend/plugins/core/runtime/service/migration.py
T
insistence 2a055ba648 feat: 新增插件系统 (#112)
* 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: 更新后端依赖文件
2026-07-28 20:35:18 +08:00

149 lines
5.7 KiB
Python

from typing import Literal
from plugins.core.runtime.support import PluginRuntimePayloadBuilder
from .dependency_container import PluginRuntimeDependencies
MigrationRecoveryStatus = Literal['success', 'failed']
MIGRATION_MANUAL_TRANSITIONS: dict[MigrationRecoveryStatus, set[str]] = {
'success': {'running', 'failed', 'unknown'},
'failed': {'running', 'unknown'},
}
class PluginMigrationUseCase:
"""
插件 migration 历史查询和人工恢复 use case。
"""
def __init__(self, dependencies: PluginRuntimeDependencies) -> None:
"""
初始化插件 migration use case。
:param dependencies: 插件运行时依赖容器
"""
self.dependencies = dependencies
async def list_plugin_migrations(self, plugin_id: str, status: str | None = None) -> dict[str, object]:
"""
查询插件 migration 历史。
:param plugin_id: 插件ID
:param status: 执行状态
:return: 插件 migration 历史负载
"""
try:
migrations = await self.dependencies.migration_history_gateway.list_plugin_migrations(plugin_id, status)
return {
'ok': True,
'message': '插件 migration 历史查询完成',
'pluginId': plugin_id,
'status': status,
'count': len(migrations),
'migrations': [self._dump_migration_model(migration) for migration in migrations],
}
except Exception as exc:
return PluginRuntimePayloadBuilder.build_exception_payload(
'查询插件 migration 历史失败',
exc,
plugin_id=plugin_id,
)
async def mark_plugin_migration_status(
self,
plugin_id: str,
migration_path: str,
status: MigrationRecoveryStatus,
*,
note: str | None = None,
) -> dict[str, object]:
"""
人工标记插件 migration 状态。
:param plugin_id: 插件ID
:param migration_path: migration 相对路径
:param status: 目标状态
:param note: 人工恢复备注
:return: 插件 migration 状态标记负载
"""
try:
error_message = None if status == 'success' else note or '人工标记为失败'
existing_migration = await self.dependencies.migration_history_gateway.get_plugin_migration(
plugin_id,
migration_path,
)
if not existing_migration:
return PluginRuntimePayloadBuilder.build_invalid_operation_payload(
plugin_id,
f'migration_mark_{status}',
message='插件 migration 历史不存在',
)
current_status = getattr(existing_migration, 'status', 'success') or 'success'
if current_status not in MIGRATION_MANUAL_TRANSITIONS[status]:
label = '成功' if status == 'success' else '失败'
return PluginRuntimePayloadBuilder.build_invalid_operation_payload(
plugin_id,
f'migration_mark_{status}',
message=f'插件 migration 当前状态为 {current_status},不能人工标记为{label}',
)
migration = await self.dependencies.migration_history_gateway.mark_plugin_migration_status(
plugin_id,
migration_path,
status,
error_message,
)
if not migration:
return PluginRuntimePayloadBuilder.build_invalid_operation_payload(
plugin_id,
f'migration_mark_{status}',
message='插件 migration 历史不存在',
)
label = '成功' if status == 'success' else '失败'
payload = {
'ok': True,
'message': f'插件 migration 已人工标记为{label}',
'operation': f'migration_mark_{status}',
'pluginId': plugin_id,
'migrationPath': migration_path,
'status': status,
'migration': self._dump_migration_model(migration),
}
if note:
payload['note'] = note
return payload
except Exception as exc:
return PluginRuntimePayloadBuilder.build_exception_payload(
'人工标记插件 migration 状态失败',
exc,
plugin_id=plugin_id,
)
@staticmethod
def _dump_migration_model(migration: object) -> dict[str, object]:
"""
序列化 migration 历史模型。
:param migration: migration 历史模型
:return: migration 历史字典
"""
model_dump = getattr(migration, 'model_dump', None)
if callable(model_dump):
return model_dump(by_alias=True)
return {
'pluginId': getattr(migration, 'plugin_id', None),
'migrationPath': getattr(migration, 'migration_path', None),
'migrationChecksum': getattr(migration, 'migration_checksum', None),
'version': getattr(migration, 'version', None),
'statementCount': getattr(migration, 'statement_count', 0),
'status': getattr(migration, 'status', None),
'errorMessage': getattr(migration, 'error_message', None),
'attemptCount': getattr(migration, 'attempt_count', 0),
'startedTime': getattr(migration, 'started_time', None),
'finishedTime': getattr(migration, 'finished_time', None),
'createTime': getattr(migration, 'create_time', None),
'updateTime': getattr(migration, 'update_time', None),
}