From 2cfd37c2388cb4337bb45bcd4932fe4859b246fb Mon Sep 17 00:00:00 2001 From: mio <455457521@qq.com> Date: Wed, 16 Apr 2025 19:45:10 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E6=9B=B4=E6=96=B0=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=89=B9=E9=87=8F=E6=9B=B4=E6=96=B0=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zhenxun/builtin_plugins/web_ui/__init__.py | 24 +++--- .../web_ui/api/tabs/plugin_manage/__init__.py | 15 ++++ .../api/tabs/plugin_manage/data_source.py | 86 ++++++++++++++++++- .../web_ui/api/tabs/plugin_manage/model.py | 71 +++++++-------- 4 files changed, 149 insertions(+), 47 deletions(-) diff --git a/zhenxun/builtin_plugins/web_ui/__init__.py b/zhenxun/builtin_plugins/web_ui/__init__.py index d8d71025..a7776d89 100644 --- a/zhenxun/builtin_plugins/web_ui/__init__.py +++ b/zhenxun/builtin_plugins/web_ui/__init__.py @@ -10,9 +10,10 @@ from zhenxun.configs.config import Config as gConfig from zhenxun.configs.utils import PluginExtraData, RegisterConfig from zhenxun.services.log import logger, logger_ from zhenxun.utils.enum import PluginType +# 移除 AuthorizationError 导入 from .api.logs import router as ws_log_routes -from .api.logs.log_manager import LOG_STORAGE +from .api.logs.log_manager import LOG_STORAGE # 确保 LOG_STORAGE 导入 from .api.menu import router as menu_router from .api.tabs.dashboard import router as dashboard_router from .api.tabs.database import router as database_router @@ -20,18 +21,17 @@ from .api.tabs.main import router as main_router from .api.tabs.main import ws_router as status_routes from .api.tabs.manage import router as manage_router from .api.tabs.manage.chat import ws_router as chat_routes -from .api.tabs.plugin_manage import router as plugin_router -from .api.tabs.plugin_manage.store import router as store_router -from .api.tabs.system import router as system_router +from .api.tabs.plugin_manage import router as plugin_router # 插件管理路由 +from .api.tabs.system import router as system_router # 系统路由 from .auth import router as auth_router from .public import init_public +# 移除了 command, configure, store 路由的导入 __plugin_meta__ = PluginMetadata( name="WebUi", description="WebUi API", - usage=""" - """.strip(), - extra=PluginExtraData( + usage="\"\"\"\n \"\"\".strip(),", # 保留原始格式 + extra=PluginExtraData( # 确保调用 .to_dict() author="HibiKier", version="0.1", plugin_type=PluginType.HIDDEN, @@ -61,7 +61,7 @@ __plugin_meta__ = PluginMetadata( default_value=None, ), ], - ).to_dict(), + ).to_dict(), # 调用 .to_dict() ) driver = nonebot.get_driver() @@ -74,15 +74,15 @@ BaseApiRouter = APIRouter(prefix="/zhenxun/api") BaseApiRouter.include_router(auth_router) -BaseApiRouter.include_router(store_router) +# 移除 store_router 包含 BaseApiRouter.include_router(dashboard_router) BaseApiRouter.include_router(main_router) BaseApiRouter.include_router(manage_router) BaseApiRouter.include_router(database_router) -BaseApiRouter.include_router(plugin_router) -BaseApiRouter.include_router(system_router) +BaseApiRouter.include_router(plugin_router) # 包含插件管理路由 +BaseApiRouter.include_router(system_router) # 包含系统路由 BaseApiRouter.include_router(menu_router) - +# 移除 command, configure 路由包含 WsApiRouter = APIRouter(prefix="/zhenxun/socket") diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/__init__.py index e011e67f..5337505c 100644 --- a/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/__init__.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/__init__.py @@ -14,6 +14,7 @@ from .model import ( PluginInfo, PluginSwitch, UpdatePlugin, + BatchUpdatePlugins, ) router = APIRouter(prefix="/plugin") @@ -152,3 +153,17 @@ async def _(module: str) -> Result[PluginDetail]: except Exception as e: logger.error(f"{router.prefix}/get_plugin 调用错误", "WebUi", e=e) return Result.fail(f"{type(e)}: {e}") + + +@router.put("/plugins/batch_update", summary="批量更新插件配置") +async def batch_update_plugin_config_api(params: BatchUpdatePlugins): + """批量更新插件配置,如开关、类型等""" + result = await ApiDataSource.batch_update_plugins(params=params) + if result["errors"]: + # 可以根据需要返回更详细的错误信息,或者只返回一个笼统的失败信息 + # 这里我们返回包含错误详情的 200 OK,让前端处理 + # 或者可以抛出 HTTPException + # from fastapi import HTTPException + # raise HTTPException(status_code=400, detail={"message": "部分插件更新失败", "errors": result["errors"]}) + pass # 暂时只返回结果字典 + return result diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/data_source.py b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/data_source.py index ee0992d6..5e0414aa 100644 --- a/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/data_source.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/data_source.py @@ -7,8 +7,15 @@ from zhenxun.configs.config import Config from zhenxun.configs.utils import ConfigGroup from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo from zhenxun.utils.enum import BlockType, PluginType +from tortoise.exceptions import DoesNotExist -from .model import PluginConfig, PluginDetail, PluginInfo, UpdatePlugin +from .model import ( + BatchUpdatePlugins, + PluginConfig, + PluginDetail, + PluginInfo, + UpdatePlugin, +) class ApiDataSource: @@ -44,6 +51,7 @@ class ApiDataSource: level=plugin.level, status=plugin.status, author=plugin.author, + block_type=plugin.block_type, ) plugin_list.append(plugin_info) return plugin_list @@ -80,6 +88,82 @@ class ApiDataSource: Config.save(save_simple_data=True) return db_plugin + @classmethod + async def batch_update_plugins(cls, params: BatchUpdatePlugins) -> dict: + """批量更新插件数据 + + 参数: + params: BatchUpdatePlugins + + 返回: + dict: 更新结果, 例如 {'success': True, 'updated_count': 5, 'errors': []} + """ + # 分开处理,避免 bulk_update 对 CharEnumField 的潜在问题 + plugins_to_update_other_fields = [] + other_update_fields = set() + updated_count = 0 + errors = [] + + # 收集需要更新的插件和字段 + for item in params.updates: + try: + db_plugin = await DbPluginInfo.get(module=item.module) + plugin_changed_other = False + plugin_changed_block = False + + # 处理 block_type 和 status (单独保存) + if db_plugin.block_type != item.block_type: + db_plugin.block_type = item.block_type + db_plugin.status = item.block_type != BlockType.ALL # 同时更新 status + plugin_changed_block = True + + # 处理 menu_type (准备批量更新) + if item.menu_type is not None and db_plugin.menu_type != item.menu_type: + db_plugin.menu_type = item.menu_type + other_update_fields.add("menu_type") + plugin_changed_other = True + + # 处理 default_status (准备批量更新) + if item.default_status is not None and db_plugin.default_status != item.default_status: + db_plugin.default_status = item.default_status + other_update_fields.add("default_status") + plugin_changed_other = True + + # 单独保存 block_type 和 status 的更改 + if plugin_changed_block: + try: + await db_plugin.save(update_fields=["block_type", "status"]) + updated_count += 1 # 每次成功保存计为一个更新 + except Exception as e_save: + errors.append({"module": item.module, "error": f"Save block_type failed: {str(e_save)}"}) + # 如果保存失败,则不将其他字段加入批量更新,避免数据不一致 + plugin_changed_other = False + + # 如果其他字段有更改且 block_type 保存成功,则加入批量更新列表 + if plugin_changed_other: + plugins_to_update_other_fields.append(db_plugin) + + except DoesNotExist: + errors.append({"module": item.module, "error": "Plugin not found"}) + except Exception as e: + errors.append({"module": item.module, "error": str(e)}) + + # 执行其他字段的批量更新 + if plugins_to_update_other_fields and other_update_fields: + try: + await DbPluginInfo.bulk_update(plugins_to_update_other_fields, list(other_update_fields)) + # 注意:这里的 updated_count 可能需要调整,取决于是否将 bulk_update 的成功也计入 + # 为简单起见,我们只计算了上面单独 save 的次数 + # updated_count += len(plugins_to_update_other_fields) # 如果需要合并计数 + except Exception as e_bulk: + errors.append({"module": "batch_update_other", "error": f"Bulk update failed: {str(e_bulk)}"}) + + return { + "success": len(errors) == 0, # 只要没有错误就算成功 + "updated_count": updated_count, # 只计算 block_type 成功更新的数量 + "errors": errors, + } + @classmethod def __build_plugin_config( cls, module: str, cfg: str, config: ConfigGroup diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/model.py b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/model.py index 662814c9..48187ad5 100644 --- a/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/model.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/model.py @@ -1,6 +1,6 @@ from typing import Any -from pydantic import BaseModel +from pydantic import BaseModel, Field from zhenxun.utils.enum import BlockType @@ -37,19 +37,19 @@ class UpdatePlugin(BaseModel): module: str """模块""" default_status: bool - """默认开关""" + """是否默认开启""" limit_superuser: bool - """限制超级用户""" - cost_gold: int - """金币花费""" - menu_type: str - """插件菜单类型""" + """是否限制超级用户""" level: int - """插件所需群权限""" + """等级""" + cost_gold: int + """花费金币""" + menu_type: str + """菜单类型""" block_type: BlockType | None = None """禁用类型""" configs: dict[str, Any] | None = None - """配置项""" + """设置项""" class PluginInfo(BaseModel): @@ -58,27 +58,26 @@ class PluginInfo(BaseModel): """ module: str - """插件名称""" + """模块""" plugin_name: str - """插件中文名称""" + """插件名称""" default_status: bool - """默认开关""" + """是否默认开启""" limit_superuser: bool - """限制超级用户""" + """是否限制超级用户""" + level: int + """等级""" cost_gold: int """花费金币""" menu_type: str - """插件菜单类型""" + """菜单类型""" version: str - """插件版本""" - level: int - """群权限""" + """版本""" status: bool - """当前状态""" + """状态""" author: str | None = None """作者""" - block_type: BlockType | None = None - """禁用类型""" + block_type: BlockType | None = Field(None, description="插件禁用状态 (None: 启用)") class PluginConfig(BaseModel): @@ -86,20 +85,13 @@ class PluginConfig(BaseModel): 插件配置项 """ - module: str - """模块""" - key: str - """键""" - value: Any - """值""" - help: str | None = None - """帮助""" - default_value: Any - """默认值""" - type: Any = None - """值类型""" - type_inner: list[str] | None = None - """List Tuple等内部类型检验""" + module: str = Field(..., description="模块名") + key: str = Field(..., description="键") + value: Any = Field(None, description="值") + help: str | None = Field(None, description="帮助信息") + default_value: Any = Field(None, description="默认值") + type: str | None = Field(None, description="类型") + type_inner: list[str] | None = Field(None, description="内部类型") class PluginCount(BaseModel): @@ -117,6 +109,17 @@ class PluginCount(BaseModel): """其他插件""" +class BatchUpdatePluginItem(BaseModel): + module: str = Field(..., description="插件模块名") + default_status: bool | None = Field(None, description="默认状态(开关)") + menu_type: str | None = Field(None, description="菜单类型") + block_type: BlockType | None = Field(None, description="插件禁用状态 (None: 启用, ALL: 禁用)") + + +class BatchUpdatePlugins(BaseModel): + updates: list[BatchUpdatePluginItem] = Field(..., description="要批量更新的插件列表") + + class PluginDetail(PluginInfo): """ 插件详情