减少数据库查询次数,提高数据库查询方面性能 (#2030)
Some checks failed
检查bot是否运行正常 / bot check (push) Has been cancelled
CodeQL Code Security Analysis / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
CodeQL Code Security Analysis / Analyze (${{ matrix.language }}) (none, python) (push) Has been cancelled
Sequential Lint and Type Check / ruff-call (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Force Sync to Aliyun / sync (push) Has been cancelled
Update Version / update-version (push) Has been cancelled
Sequential Lint and Type Check / pyright-call (push) Has been cancelled

* 🐛 修复数据库超时问题

* 🔧 移除帮助图片清理功能.

*  更新插件商店功能,允许在添加插件时指定源类型为 None。优化插件 ID 查找逻辑,增强代码可读性。新增 zhenxun/ui 模块导入。

* 🔧 优化数据访问和数据库上下文逻辑,移除不必要的全局变量和日志信息,调整日志级别为调试,提升代码可读性和性能。
This commit is contained in:
HibiKier 2025-08-26 16:53:14 +08:00 committed by GitHub
parent b12168b6b9
commit d9e65057cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 42 additions and 43 deletions

View File

@ -219,7 +219,7 @@ class StoreManager:
return plugin_info, is_external
@classmethod
async def add_plugin(cls, index_or_module: str, source: str | None) -> str:
async def add_plugin(cls, index_or_module: str, source: str | None = None) -> str:
"""添加插件
参数:
@ -518,11 +518,11 @@ class StoreManager:
raise PluginStoreException("插件ID不存在...")
return all_plugin_list[idx].module
elif isinstance(plugin_id, str):
result = (
None
if plugin_id not in [v.module for v in all_plugin_list]
else plugin_id
) or next(v for v in all_plugin_list if v.name == plugin_id).module
if not result:
raise PluginStoreException("插件 Module / 名称 不存在...")
return result
if plugin_id in [v.module for v in all_plugin_list]:
return plugin_id
for plugin_info in all_plugin_list:
if plugin_info.name.lower() == plugin_id.lower():
return plugin_info.module
raise PluginStoreException("插件 Module / 名称 不存在...")

View File

@ -16,7 +16,7 @@ from zhenxun.utils.platform import PlatformUtils
from ....base_model import Result
from ....config import QueryDateType
from ....utils import authentication, clear_help_image, get_system_status
from ....utils import authentication, get_system_status
from .data_source import ApiDataSource
from .model import (
ActiveGroup,
@ -234,7 +234,6 @@ async def _(param: BotManageUpdateParam):
bot_data.block_plugins = CommonUtils.convert_module_format(param.block_plugins)
bot_data.block_tasks = CommonUtils.convert_module_format(param.block_tasks)
await bot_data.save(update_fields=["block_plugins", "block_tasks"])
clear_help_image()
return Result.ok()
except Exception as e:
logger.error(f"{router.prefix}/update_bot_manage 调用错误", "WebUi", e=e)

View File

@ -7,7 +7,7 @@ from zhenxun.utils.enum import BlockType, PluginType
from zhenxun.utils.manager.virtual_env_package_manager import VirtualEnvPackageManager
from ....base_model import Result
from ....utils import authentication, clear_help_image
from ....utils import authentication
from .data_source import ApiDataSource
from .model import (
BatchUpdatePlugins,
@ -82,7 +82,6 @@ async def _() -> Result[PluginCount]:
async def _(param: UpdatePlugin) -> Result:
try:
await ApiDataSource.update_plugin(param)
clear_help_image()
return Result.ok(info="已经帮你写好啦!")
except (ValueError, KeyError):
return Result.fail("插件数据不存在...")
@ -110,7 +109,6 @@ async def _(param: PluginSwitch) -> Result:
db_plugin.block_type = None
db_plugin.status = True
await db_plugin.save()
clear_help_image()
return Result.ok(info="成功改变了开关状态!")
except Exception as e:
logger.error(f"{router.prefix}/change_switch 调用错误", "WebUi", e=e)
@ -177,7 +175,6 @@ async def _(
updated_count=result_dict["updated_count"],
errors=result_dict["errors"],
)
clear_help_image()
return Result.ok(result_model, "插件配置更新完成")
except Exception as e:
logger.error(f"{router.prefix}/plugins/batch_update 调用错误", "WebUi", e=e)
@ -197,7 +194,6 @@ async def _(payload: RenameMenuTypePayload) -> Result[str]:
old_name=payload.old_name, new_name=payload.new_name
)
if result.get("success"):
clear_help_image()
return Result.ok(
info=result.get(
"info",

View File

@ -12,7 +12,7 @@ import psutil
import ujson as json
from zhenxun.configs.config import Config
from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH
from zhenxun.configs.path_config import DATA_PATH
from .base_model import SystemFolderSize, SystemStatus, User
@ -68,22 +68,6 @@ def validate_path(path_str: str | None) -> tuple[Path | None, str | None]:
return None, f"路径验证失败: {e!s}"
GROUP_HELP_PATH = DATA_PATH / "group_help"
SIMPLE_HELP_IMAGE = IMAGE_PATH / "SIMPLE_HELP.png"
SIMPLE_DETAIL_HELP_IMAGE = IMAGE_PATH / "SIMPLE_DETAIL_HELP.png"
def clear_help_image():
"""清理帮助图片"""
if SIMPLE_HELP_IMAGE.exists():
SIMPLE_HELP_IMAGE.unlink()
if SIMPLE_DETAIL_HELP_IMAGE.exists():
SIMPLE_DETAIL_HELP_IMAGE.unlink()
for file in GROUP_HELP_PATH.iterdir():
if file.is_file():
file.unlink()
def get_user(uname: str) -> User | None:
"""获取账号密码

View File

@ -4,6 +4,7 @@ from typing_extensions import Self
from tortoise import fields
from zhenxun.services.data_access import DataAccess
from zhenxun.services.db_context import Model
from zhenxun.services.log import logger
from zhenxun.utils.enum import CacheType, DbLockType
@ -57,14 +58,15 @@ class BanConsole(Model):
"""
if not user_id and not group_id:
raise UserAndGroupIsNone()
dao = DataAccess(cls)
if user_id:
return (
await cls.safe_get_or_none(user_id=user_id, group_id=group_id)
await dao.safe_get_or_none(user_id=user_id, group_id=group_id)
if group_id
else await cls.safe_get_or_none(user_id=user_id, group_id__isnull=True)
else await dao.safe_get_or_none(user_id=user_id, group_id__isnull=True)
)
else:
return await cls.safe_get_or_none(user_id="", group_id=group_id)
return await dao.safe_get_or_none(user_id="", group_id=group_id)
@classmethod
async def check_ban_level(

View File

@ -7,6 +7,7 @@ from tortoise.backends.base.client import BaseDBAsyncClient
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.cache import CacheRoot
from zhenxun.services.data_access import DataAccess
from zhenxun.services.db_context import Model
from zhenxun.utils.enum import CacheType, DbLockType, PluginType
@ -254,13 +255,14 @@ class GroupConsole(Model):
返回:
Self: GroupConsole
"""
dao = DataAccess(cls)
if channel_id:
return await cls.safe_get_or_none(
return await dao.safe_get_or_none(
group_id=group_id,
channel_id=channel_id,
clean_duplicates=clean_duplicates,
)
return await cls.safe_get_or_none(
return await dao.safe_get_or_none(
group_id=group_id,
channel_id__isnull=True,
clean_duplicates=clean_duplicates,

View File

@ -37,7 +37,7 @@ class CacheDict(Generic[T]):
return 0
return data.expire_time
def __getitem__(self, key: str) -> T | None:
def __getitem__(self, key: str) -> T:
"""获取字典项
参数:
@ -47,8 +47,10 @@ class CacheDict(Generic[T]):
T: 字典值
"""
if value := self._data.get(key):
return value.value if self.expire_time(key) else None
return None
if self.expire_time(key):
raise KeyError(f"{key} 已过期")
return value.value
raise KeyError(f"{key} 不存在")
def __setitem__(self, key: str, value: T) -> None:
"""设置字典项

View File

@ -7,6 +7,8 @@ from zhenxun.services.log import logger
T = TypeVar("T", bound=Model)
cache = CacheRoot.cache_dict("DB_TEST_BAN", 10, int)
class DataAccess(Generic[T]):
"""数据访问层,根据配置决定是否使用缓存
@ -167,6 +169,7 @@ class DataAccess(Generic[T]):
return await with_db_timeout(
db_query_func(*args, **kwargs),
operation=f"{self.model_cls.__name__}.{db_query_func.__name__}",
source="DataAccess",
)
# 尝试从缓存获取
@ -179,9 +182,10 @@ class DataAccess(Generic[T]):
if cache_key is not None:
data = await self.cache.get(cache_key)
logger.debug(
f"{self.model_cls.__name__} self.cache.get(cache_key)"
f"{self.model_cls.__name__} key: {cache_key}"
f" 从缓存获取到的数据 {type(data)}: {data}"
)
if data == self._NULL_RESULT:
# 空结果缓存命中
self._cache_stats[self.cache_type]["null_hits"] += 1

View File

@ -227,6 +227,7 @@ class Model(TortoiseModel):
return await with_db_timeout(
cls.get_or_none(*args, using_db=using_db, **kwargs),
operation=f"{cls.__name__}.get_or_none",
source="DataBaseModel",
)
except MultipleObjectsReturned:
# 如果出现多个记录的情况,进行特殊处理
@ -239,6 +240,7 @@ class Model(TortoiseModel):
records = await with_db_timeout(
cls.filter(*args, **kwargs).all(),
operation=f"{cls.__name__}.filter.all",
source="DataBaseModel",
)
if not records:
@ -255,6 +257,7 @@ class Model(TortoiseModel):
await with_db_timeout(
record.delete(),
operation=f"{cls.__name__}.delete_duplicate",
source="DataBaseModel",
)
logger.info(
f"{cls.__name__} 删除重复记录:"
@ -269,11 +272,13 @@ class Model(TortoiseModel):
return await with_db_timeout(
cls.filter(*args, **kwargs).order_by("-id").first(),
operation=f"{cls.__name__}.filter.order_by.first",
source="DataBaseModel",
)
# 如果没有 id 字段,则返回第一个记录
return await with_db_timeout(
cls.filter(*args, **kwargs).first(),
operation=f"{cls.__name__}.filter.first",
source="DataBaseModel",
)
except asyncio.TimeoutError:
logger.error(

View File

@ -11,11 +11,15 @@ from .config import (
async def with_db_timeout(
coro, timeout: float = DB_TIMEOUT_SECONDS, operation: str | None = None
coro,
timeout: float = DB_TIMEOUT_SECONDS,
operation: str | None = None,
source: str | None = None,
):
"""带超时控制的数据库操作"""
start_time = time.time()
try:
logger.debug(f"开始执行数据库操作: {operation} 来源: {source}")
result = await asyncio.wait_for(coro, timeout=timeout)
elapsed = time.time() - start_time
if elapsed > SLOW_QUERY_THRESHOLD and operation:

View File

@ -50,6 +50,7 @@ class ZhenxunRepoConfig:
"zhenxun/utils",
"zhenxun/models",
"zhenxun/configs",
"zhenxun/ui",
]
ZHENXUN_BOT_VERSION_FILE_STRING = "__version__"
ZHENXUN_BOT_VERSION_FILE = Path() / ZHENXUN_BOT_VERSION_FILE_STRING