This commit is contained in:
mio 2025-04-17 16:49:38 +08:00
parent 2ec600f217
commit 3ad1080339
8 changed files with 70 additions and 44 deletions

View File

@ -18,7 +18,7 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- name: Read current version - name: Read current version
id: read_version id: read_version
@ -62,7 +62,7 @@ jobs:
if: steps.check_diff.outputs.version_changed == 'false' if: steps.check_diff.outputs.version_changed == 'false'
uses: peter-evans/create-pull-request@v7 uses: peter-evans/create-pull-request@v7
with: with:
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
branch: create-pr/update_version branch: create-pr/update_version
title: ":tada: chore(version): 自动更新版本到 ${{ steps.update_version.outputs.new_version }}" title: ":tada: chore(version): 自动更新版本到 ${{ steps.update_version.outputs.new_version }}"
body: "This PR updates the version file." body: "This PR updates the version file."

4
.gitignore vendored
View File

@ -155,6 +155,6 @@ bot.py
.idea/ .idea/
resources/ resources/
/configs/config.py /configs/config.py
configs/config.yaml configs/config.yaml
.vscode/launch.json .vscode/launch.json
plugins_/ plugins_/

View File

@ -97,14 +97,15 @@ async def _handle_setting(
async def fix_db_schema(): async def fix_db_schema():
"""修复数据库架构问题""" """修复数据库架构问题"""
from tortoise import connections from tortoise.connection import connections
conn = connections.get("default") conn = connections.get("default")
# 检查是否存在superuser列并处理 # 检查是否存在superuser列并处理
try: try:
await conn.execute_query(""" await conn.execute_query("""
DO $$ DO $$
BEGIN BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name='plugin_info' AND column_name='superuser') THEN WHERE table_name='plugin_info' AND column_name='superuser') THEN
ALTER TABLE plugin_info DROP COLUMN superuser; ALTER TABLE plugin_info DROP COLUMN superuser;
END IF; END IF;
@ -122,7 +123,7 @@ async def _():
""" """
# 修复数据库架构问题 # 修复数据库架构问题
await fix_db_schema() await fix_db_schema()
plugin_list: list[PluginInfo] = [] plugin_list: list[PluginInfo] = []
limit_list: list[PluginLimit] = [] limit_list: list[PluginLimit] = []
module2id = {} module2id = {}
@ -150,26 +151,31 @@ async def _():
"is_show", "is_show",
"ignore_prompt", "ignore_prompt",
# 移除了 menu_type # 移除了 menu_type
# 确保 level, default_status, limit_superuser, cost_gold, impression, status, block_type 等用户配置不在此列表 # 确保 level, default_status, limit_superuser,
# cost_gold, impression, status, block_type 等用户配置不在此列表
] ]
) )
# # 验证更新是否成功 # # 验证更新是否成功
# updated_plugin = await PluginInfo.get(id=plugin.id) # updated_plugin = await PluginInfo.get(id=plugin.id)
# if updated_plugin.menu_type != plugin.menu_type: # if updated_plugin.menu_type != plugin.menu_type:
# logger.warning(f"插件 {plugin.name} 的menu_type更新失败: 期望值 '{plugin.menu_type}', 实际值 '{updated_plugin.menu_type}'") # logger.warning(
# f"插件 {plugin.name} 的menu_type更新失败: "
# f"期望值 '{plugin.menu_type}', "
# f"实际值 '{updated_plugin.menu_type}'"
# )
# # 尝试单独更新menu_type # # 尝试单独更新menu_type
# updated_plugin.menu_type = plugin.menu_type # updated_plugin.menu_type = plugin.menu_type
# await updated_plugin.save(update_fields=["menu_type"]) # await updated_plugin.save(update_fields=["menu_type"])
update_list.append(plugin) update_list.append(plugin)
if create_list: if create_list:
await PluginInfo.bulk_create(create_list, 10) await PluginInfo.bulk_create(create_list, 10)
# 对于批量更新操作,逐个更新替代批量操作 # 对于批量更新操作,逐个更新替代批量操作
# 这里不使用被注释的批量更新代码,而是在上面的循环中已经处理 # 这里不使用被注释的批量更新代码,而是在上面的循环中已经处理
await data_migration() await data_migration()
await PluginInfo.filter(module_path__in=load_plugin).update(load_status=True) await PluginInfo.filter(module_path__in=load_plugin).update(load_status=True)
await PluginInfo.filter(module_path__not_in=load_plugin).update(load_status=False) await PluginInfo.filter(module_path__not_in=load_plugin).update(load_status=False)
@ -441,4 +447,4 @@ async def group_migration():
if create_list: if create_list:
await GroupConsole.bulk_create(create_list, 10) await GroupConsole.bulk_create(create_list, 10)
group_file.unlink() group_file.unlink()
logger.info("迁移群组数据完成!") logger.info("迁移群组数据完成!")

View File

@ -93,6 +93,8 @@ WsApiRouter.include_router(chat_routes)
async def _(): async def _():
try: try:
_tasks = []
async def log_sink(message: str): async def log_sink(message: str):
loop = None loop = None
if not loop: if not loop:
@ -102,7 +104,10 @@ async def _():
logger.warning("Web Ui log_sink", e=e) logger.warning("Web Ui log_sink", e=e)
if not loop: if not loop:
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
loop.create_task(LOG_STORAGE.add(message.rstrip("\n"))) task = loop.create_task(LOG_STORAGE.add(message.rstrip("\n")))
_tasks.append(task)
while _tasks and _tasks[0].done():
_tasks.pop(0)
logger_.add( logger_.add(
log_sink, colorize=True, filter=default_filter, format=default_format log_sink, colorize=True, filter=default_filter, format=default_format

View File

@ -9,13 +9,13 @@ from ....base_model import Result
from ....utils import authentication from ....utils import authentication
from .data_source import ApiDataSource from .data_source import ApiDataSource
from .model import ( from .model import (
BatchUpdatePlugins,
PluginCount, PluginCount,
PluginDetail, PluginDetail,
PluginInfo, PluginInfo,
PluginSwitch, PluginSwitch,
UpdatePlugin,
BatchUpdatePlugins,
RenameMenuTypePayload, RenameMenuTypePayload,
UpdatePlugin,
) )
router = APIRouter(prefix="/plugin") router = APIRouter(prefix="/plugin")
@ -165,23 +165,31 @@ async def batch_update_plugin_config_api(params: BatchUpdatePlugins):
# 这里我们返回包含错误详情的 200 OK让前端处理 # 这里我们返回包含错误详情的 200 OK让前端处理
# 或者可以抛出 HTTPException # 或者可以抛出 HTTPException
# from fastapi import HTTPException # from fastapi import HTTPException
# raise HTTPException(status_code=400, detail={"message": "部分插件更新失败", "errors": result["errors"]}) # raise HTTPException(
# status_code=400,
# detail={"message": "部分插件更新失败", "errors": result["errors"]}
# )
pass # 暂时只返回结果字典 pass # 暂时只返回结果字典
return result return result
# 新增:重命名菜单类型路由 # 新增:重命名菜单类型路由
@router.put( @router.put(
"/menu_type/rename", "/menu_type/rename",
dependencies=[authentication()], dependencies=[authentication()],
response_model=Result, response_model=Result,
summary="重命名菜单类型" summary="重命名菜单类型"
) )
async def rename_menu_type_api(payload: RenameMenuTypePayload) -> Result: async def rename_menu_type_api(payload: RenameMenuTypePayload) -> Result:
try: try:
result = await ApiDataSource.rename_menu_type(old_name=payload.old_name, new_name=payload.new_name) result = await ApiDataSource.rename_menu_type(
old_name=payload.old_name, new_name=payload.new_name)
if result.get("success"): if result.get("success"):
return Result.ok(info=result.get("info", f"成功将 {result.get('updated_count', 0)} 个插件的菜单类型从 '{payload.old_name}' 修改为 '{payload.new_name}'")) return Result.ok(info=result.get(
"info",
f"成功将 {result.get('updated_count', 0)} 个插件的菜单类型"
f"'{payload.old_name}' 修改为 '{payload.new_name}'"
))
else: else:
# 这种情况理论上不会发生,因为 rename_menu_type 失败会抛异常 # 这种情况理论上不会发生,因为 rename_menu_type 失败会抛异常
return Result.fail(info=result.get("info", "重命名失败")) return Result.fail(info=result.get("info", "重命名失败"))

View File

@ -2,12 +2,12 @@ import re
import cattrs import cattrs
from fastapi import Query from fastapi import Query
from tortoise.exceptions import DoesNotExist
from zhenxun.configs.config import Config from zhenxun.configs.config import Config
from zhenxun.configs.utils import ConfigGroup from zhenxun.configs.utils import ConfigGroup
from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo from zhenxun.models.plugin_info import PluginInfo as DbPluginInfo
from zhenxun.utils.enum import BlockType, PluginType from zhenxun.utils.enum import BlockType, PluginType
from tortoise.exceptions import DoesNotExist
from .model import ( from .model import (
BatchUpdatePlugins, BatchUpdatePlugins,
@ -134,7 +134,7 @@ class ApiDataSource:
errors.append( errors.append(
{ {
"module": item.module, "module": item.module,
"error": f"Save block_type failed: {str(e_save)}", "error": f"Save block_type failed: {e_save!s}",
} }
) )
plugin_changed_other = False plugin_changed_other = False
@ -158,7 +158,7 @@ class ApiDataSource:
errors.append( errors.append(
{ {
"module": "batch_update_other", "module": "batch_update_other",
"error": f"Bulk update failed: {str(e_bulk)}", "error": f"Bulk update failed: {e_bulk!s}",
} }
) )
@ -217,8 +217,12 @@ class ApiDataSource:
if not old_name or not new_name: if not old_name or not new_name:
raise ValueError("旧名称和新名称都不能为空") raise ValueError("旧名称和新名称都不能为空")
if old_name == new_name: if old_name == new_name:
return {"success": True, "updated_count": 0, "info": "新旧名称相同,无需更新"} return {
"success": True,
"updated_count": 0,
"info": "新旧名称相同,无需更新"
}
# 检查新名称是否已存在(理论上前端会校验,后端再保险一次) # 检查新名称是否已存在(理论上前端会校验,后端再保险一次)
exists = await DbPluginInfo.filter(menu_type=new_name).exists() exists = await DbPluginInfo.filter(menu_type=new_name).exists()
if exists: if exists:
@ -226,11 +230,12 @@ class ApiDataSource:
try: try:
# 使用 filter().update() 进行批量更新 # 使用 filter().update() 进行批量更新
updated_count = await DbPluginInfo.filter(menu_type=old_name).update(menu_type=new_name) updated_count = await DbPluginInfo.filter(menu_type=old_name).update(
menu_type=new_name)
return {"success": True, "updated_count": updated_count} return {"success": True, "updated_count": updated_count}
except Exception as e: except Exception as e:
# 可以添加更详细的日志记录 # 可以添加更详细的日志记录
raise RuntimeError(f"数据库更新菜单类型失败: {str(e)}") raise RuntimeError(f"数据库更新菜单类型失败: {e!s}")
@classmethod @classmethod
async def get_plugin_detail(cls, module: str) -> PluginDetail: async def get_plugin_detail(cls, module: str) -> PluginDetail:

View File

@ -113,11 +113,13 @@ class BatchUpdatePluginItem(BaseModel):
module: str = Field(..., description="插件模块名") module: str = Field(..., description="插件模块名")
default_status: bool | None = Field(None, description="默认状态(开关)") default_status: bool | None = Field(None, description="默认状态(开关)")
menu_type: str | None = Field(None, description="菜单类型") menu_type: str | None = Field(None, description="菜单类型")
block_type: BlockType | None = Field(None, description="插件禁用状态 (None: 启用, ALL: 禁用)") block_type: BlockType | None = Field(
None, description="插件禁用状态 (None: 启用, ALL: 禁用)")
class BatchUpdatePlugins(BaseModel): class BatchUpdatePlugins(BaseModel):
updates: list[BatchUpdatePluginItem] = Field(..., description="要批量更新的插件列表") updates: list[BatchUpdatePluginItem] = Field(
..., description="要批量更新的插件列表")
class PluginDetail(PluginInfo): class PluginDetail(PluginInfo):

View File

@ -366,13 +366,13 @@ class ConfigsManager:
if not module or not key: if not module or not key:
raise ValueError("add_plugin_config: module和key不能为为空") raise ValueError("add_plugin_config: module和key不能为为空")
key_upper = key.upper() key_upper = key.upper()
self.add_module.append(f"{module}:{key}".lower()) self.add_module.append(f"{module}:{key}".lower())
# 检查配置是否已存在 # 检查配置是否已存在
config_exists = module in self._data and key_upper in self._data[module].configs config_exists = module in self._data and key_upper in self._data[module].configs
# 如果配置已存在,仅更新元数据,保留原始值 # 如果配置已存在,仅更新元数据,保留原始值
if config_exists: if config_exists:
config = self._data[module].configs[key_upper] config = self._data[module].configs[key_upper]
@ -397,7 +397,7 @@ class ConfigsManager:
default_value=default_value, default_value=default_value,
type=type, type=type,
) )
# 同时更新simple_data # 同时更新simple_data
if module not in self._simple_data: if module not in self._simple_data:
self._simple_data[module] = {} self._simple_data[module] = {}
@ -512,7 +512,7 @@ class ConfigsManager:
_yaml.dump(self._simple_data, f) _yaml.dump(self._simple_data, f)
# 原子替换 # 原子替换
temp_simple_file.replace(self._simple_file) temp_simple_file.replace(self._simple_file)
path = path or self.file path = path or self.file
data = {} data = {}
for module in self._data: for module in self._data:
@ -522,7 +522,7 @@ class ConfigsManager:
del value["type"] del value["type"]
del value["arg_parser"] del value["arg_parser"]
data[module][config] = value data[module][config] = value
# 使用临时文件进行原子写入 # 使用临时文件进行原子写入
temp_file = Path(str(path) + ".tmp") temp_file = Path(str(path) + ".tmp")
with open(temp_file, "w", encoding="utf8") as f: with open(temp_file, "w", encoding="utf8") as f:
@ -530,7 +530,7 @@ class ConfigsManager:
# 原子替换 # 原子替换
temp_file.replace(path) temp_file.replace(path)
except Exception as e: except Exception as e:
logger.error(f"保存配置文件失败: {str(e)}") logger.error(f"保存配置文件失败: {e!s}")
def reload(self): def reload(self):
"""重新加载配置文件""" """重新加载配置文件"""
@ -540,24 +540,24 @@ class ConfigsManager:
if self._simple_file.exists(): if self._simple_file.exists():
with open(self._simple_file, encoding="utf8") as f: with open(self._simple_file, encoding="utf8") as f:
self._simple_data = _yaml.load(f) self._simple_data = _yaml.load(f)
# 检查加载的数据是否为None # 检查加载的数据是否为None
if self._simple_data is None: if self._simple_data is None:
logger.error("配置文件为空或格式错误,保留原有配置") logger.error("配置文件为空或格式错误,保留原有配置")
self._simple_data = {} self._simple_data = {}
# 更新配置值 # 更新配置值
for key in self._simple_data.keys(): for key in self._simple_data.keys():
for k in self._simple_data[key].keys(): for k in self._simple_data[key].keys():
if key in self._data and k in self._data[key].configs: if key in self._data and k in self._data[key].configs:
self._data[key].configs[k].value = self._simple_data[key][k] self._data[key].configs[k].value = self._simple_data[key][k]
self.save() self.save()
except ScannerError as e: except ScannerError as e:
logger.error(f"配置文件解析失败: {str(e)},保留现有配置") logger.error(f"配置文件解析失败: {e!s},保留现有配置")
self._data = backup_data self._data = backup_data
except Exception as e: except Exception as e:
logger.error(f"重新加载配置失败: {str(e)}") logger.error(f"重新加载配置失败: {e!s}")
# 发生错误时恢复到备份配置 # 发生错误时恢复到备份配置
self._data = backup_data self._data = backup_data