mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2025-12-14 21:52:56 +08:00
⚡ 减少数据库查询次数,提高数据库查询方面性能 (#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
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:
parent
b12168b6b9
commit
d9e65057cf
@ -219,7 +219,7 @@ class StoreManager:
|
|||||||
return plugin_info, is_external
|
return plugin_info, is_external
|
||||||
|
|
||||||
@classmethod
|
@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不存在...")
|
raise PluginStoreException("插件ID不存在...")
|
||||||
return all_plugin_list[idx].module
|
return all_plugin_list[idx].module
|
||||||
elif isinstance(plugin_id, str):
|
elif isinstance(plugin_id, str):
|
||||||
result = (
|
if plugin_id in [v.module for v in all_plugin_list]:
|
||||||
None
|
return plugin_id
|
||||||
if plugin_id not in [v.module for v in all_plugin_list]
|
|
||||||
else plugin_id
|
for plugin_info in all_plugin_list:
|
||||||
) or next(v for v in all_plugin_list if v.name == plugin_id).module
|
if plugin_info.name.lower() == plugin_id.lower():
|
||||||
if not result:
|
return plugin_info.module
|
||||||
raise PluginStoreException("插件 Module / 名称 不存在...")
|
|
||||||
return result
|
raise PluginStoreException("插件 Module / 名称 不存在...")
|
||||||
|
|||||||
@ -16,7 +16,7 @@ from zhenxun.utils.platform import PlatformUtils
|
|||||||
|
|
||||||
from ....base_model import Result
|
from ....base_model import Result
|
||||||
from ....config import QueryDateType
|
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 .data_source import ApiDataSource
|
||||||
from .model import (
|
from .model import (
|
||||||
ActiveGroup,
|
ActiveGroup,
|
||||||
@ -234,7 +234,6 @@ async def _(param: BotManageUpdateParam):
|
|||||||
bot_data.block_plugins = CommonUtils.convert_module_format(param.block_plugins)
|
bot_data.block_plugins = CommonUtils.convert_module_format(param.block_plugins)
|
||||||
bot_data.block_tasks = CommonUtils.convert_module_format(param.block_tasks)
|
bot_data.block_tasks = CommonUtils.convert_module_format(param.block_tasks)
|
||||||
await bot_data.save(update_fields=["block_plugins", "block_tasks"])
|
await bot_data.save(update_fields=["block_plugins", "block_tasks"])
|
||||||
clear_help_image()
|
|
||||||
return Result.ok()
|
return Result.ok()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{router.prefix}/update_bot_manage 调用错误", "WebUi", e=e)
|
logger.error(f"{router.prefix}/update_bot_manage 调用错误", "WebUi", e=e)
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from zhenxun.utils.enum import BlockType, PluginType
|
|||||||
from zhenxun.utils.manager.virtual_env_package_manager import VirtualEnvPackageManager
|
from zhenxun.utils.manager.virtual_env_package_manager import VirtualEnvPackageManager
|
||||||
|
|
||||||
from ....base_model import Result
|
from ....base_model import Result
|
||||||
from ....utils import authentication, clear_help_image
|
from ....utils import authentication
|
||||||
from .data_source import ApiDataSource
|
from .data_source import ApiDataSource
|
||||||
from .model import (
|
from .model import (
|
||||||
BatchUpdatePlugins,
|
BatchUpdatePlugins,
|
||||||
@ -82,7 +82,6 @@ async def _() -> Result[PluginCount]:
|
|||||||
async def _(param: UpdatePlugin) -> Result:
|
async def _(param: UpdatePlugin) -> Result:
|
||||||
try:
|
try:
|
||||||
await ApiDataSource.update_plugin(param)
|
await ApiDataSource.update_plugin(param)
|
||||||
clear_help_image()
|
|
||||||
return Result.ok(info="已经帮你写好啦!")
|
return Result.ok(info="已经帮你写好啦!")
|
||||||
except (ValueError, KeyError):
|
except (ValueError, KeyError):
|
||||||
return Result.fail("插件数据不存在...")
|
return Result.fail("插件数据不存在...")
|
||||||
@ -110,7 +109,6 @@ async def _(param: PluginSwitch) -> Result:
|
|||||||
db_plugin.block_type = None
|
db_plugin.block_type = None
|
||||||
db_plugin.status = True
|
db_plugin.status = True
|
||||||
await db_plugin.save()
|
await db_plugin.save()
|
||||||
clear_help_image()
|
|
||||||
return Result.ok(info="成功改变了开关状态!")
|
return Result.ok(info="成功改变了开关状态!")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{router.prefix}/change_switch 调用错误", "WebUi", e=e)
|
logger.error(f"{router.prefix}/change_switch 调用错误", "WebUi", e=e)
|
||||||
@ -177,7 +175,6 @@ async def _(
|
|||||||
updated_count=result_dict["updated_count"],
|
updated_count=result_dict["updated_count"],
|
||||||
errors=result_dict["errors"],
|
errors=result_dict["errors"],
|
||||||
)
|
)
|
||||||
clear_help_image()
|
|
||||||
return Result.ok(result_model, "插件配置更新完成")
|
return Result.ok(result_model, "插件配置更新完成")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{router.prefix}/plugins/batch_update 调用错误", "WebUi", e=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
|
old_name=payload.old_name, new_name=payload.new_name
|
||||||
)
|
)
|
||||||
if result.get("success"):
|
if result.get("success"):
|
||||||
clear_help_image()
|
|
||||||
return Result.ok(
|
return Result.ok(
|
||||||
info=result.get(
|
info=result.get(
|
||||||
"info",
|
"info",
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import psutil
|
|||||||
import ujson as json
|
import ujson as json
|
||||||
|
|
||||||
from zhenxun.configs.config import Config
|
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
|
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}"
|
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:
|
def get_user(uname: str) -> User | None:
|
||||||
"""获取账号密码
|
"""获取账号密码
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ from typing_extensions import Self
|
|||||||
|
|
||||||
from tortoise import fields
|
from tortoise import fields
|
||||||
|
|
||||||
|
from zhenxun.services.data_access import DataAccess
|
||||||
from zhenxun.services.db_context import Model
|
from zhenxun.services.db_context import Model
|
||||||
from zhenxun.services.log import logger
|
from zhenxun.services.log import logger
|
||||||
from zhenxun.utils.enum import CacheType, DbLockType
|
from zhenxun.utils.enum import CacheType, DbLockType
|
||||||
@ -57,14 +58,15 @@ class BanConsole(Model):
|
|||||||
"""
|
"""
|
||||||
if not user_id and not group_id:
|
if not user_id and not group_id:
|
||||||
raise UserAndGroupIsNone()
|
raise UserAndGroupIsNone()
|
||||||
|
dao = DataAccess(cls)
|
||||||
if user_id:
|
if user_id:
|
||||||
return (
|
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
|
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:
|
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
|
@classmethod
|
||||||
async def check_ban_level(
|
async def check_ban_level(
|
||||||
|
|||||||
@ -7,6 +7,7 @@ from tortoise.backends.base.client import BaseDBAsyncClient
|
|||||||
from zhenxun.models.plugin_info import PluginInfo
|
from zhenxun.models.plugin_info import PluginInfo
|
||||||
from zhenxun.models.task_info import TaskInfo
|
from zhenxun.models.task_info import TaskInfo
|
||||||
from zhenxun.services.cache import CacheRoot
|
from zhenxun.services.cache import CacheRoot
|
||||||
|
from zhenxun.services.data_access import DataAccess
|
||||||
from zhenxun.services.db_context import Model
|
from zhenxun.services.db_context import Model
|
||||||
from zhenxun.utils.enum import CacheType, DbLockType, PluginType
|
from zhenxun.utils.enum import CacheType, DbLockType, PluginType
|
||||||
|
|
||||||
@ -254,13 +255,14 @@ class GroupConsole(Model):
|
|||||||
返回:
|
返回:
|
||||||
Self: GroupConsole
|
Self: GroupConsole
|
||||||
"""
|
"""
|
||||||
|
dao = DataAccess(cls)
|
||||||
if channel_id:
|
if channel_id:
|
||||||
return await cls.safe_get_or_none(
|
return await dao.safe_get_or_none(
|
||||||
group_id=group_id,
|
group_id=group_id,
|
||||||
channel_id=channel_id,
|
channel_id=channel_id,
|
||||||
clean_duplicates=clean_duplicates,
|
clean_duplicates=clean_duplicates,
|
||||||
)
|
)
|
||||||
return await cls.safe_get_or_none(
|
return await dao.safe_get_or_none(
|
||||||
group_id=group_id,
|
group_id=group_id,
|
||||||
channel_id__isnull=True,
|
channel_id__isnull=True,
|
||||||
clean_duplicates=clean_duplicates,
|
clean_duplicates=clean_duplicates,
|
||||||
|
|||||||
8
zhenxun/services/cache/cache_containers.py
vendored
8
zhenxun/services/cache/cache_containers.py
vendored
@ -37,7 +37,7 @@ class CacheDict(Generic[T]):
|
|||||||
return 0
|
return 0
|
||||||
return data.expire_time
|
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: 字典值
|
T: 字典值
|
||||||
"""
|
"""
|
||||||
if value := self._data.get(key):
|
if value := self._data.get(key):
|
||||||
return value.value if self.expire_time(key) else None
|
if self.expire_time(key):
|
||||||
return None
|
raise KeyError(f"键 {key} 已过期")
|
||||||
|
return value.value
|
||||||
|
raise KeyError(f"键 {key} 不存在")
|
||||||
|
|
||||||
def __setitem__(self, key: str, value: T) -> None:
|
def __setitem__(self, key: str, value: T) -> None:
|
||||||
"""设置字典项
|
"""设置字典项
|
||||||
|
|||||||
@ -7,6 +7,8 @@ from zhenxun.services.log import logger
|
|||||||
|
|
||||||
T = TypeVar("T", bound=Model)
|
T = TypeVar("T", bound=Model)
|
||||||
|
|
||||||
|
cache = CacheRoot.cache_dict("DB_TEST_BAN", 10, int)
|
||||||
|
|
||||||
|
|
||||||
class DataAccess(Generic[T]):
|
class DataAccess(Generic[T]):
|
||||||
"""数据访问层,根据配置决定是否使用缓存
|
"""数据访问层,根据配置决定是否使用缓存
|
||||||
@ -167,6 +169,7 @@ class DataAccess(Generic[T]):
|
|||||||
return await with_db_timeout(
|
return await with_db_timeout(
|
||||||
db_query_func(*args, **kwargs),
|
db_query_func(*args, **kwargs),
|
||||||
operation=f"{self.model_cls.__name__}.{db_query_func.__name__}",
|
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:
|
if cache_key is not None:
|
||||||
data = await self.cache.get(cache_key)
|
data = await self.cache.get(cache_key)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{self.model_cls.__name__} self.cache.get(cache_key)"
|
f"{self.model_cls.__name__} key: {cache_key}"
|
||||||
f" 从缓存获取到的数据 {type(data)}: {data}"
|
f" 从缓存获取到的数据 {type(data)}: {data}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if data == self._NULL_RESULT:
|
if data == self._NULL_RESULT:
|
||||||
# 空结果缓存命中
|
# 空结果缓存命中
|
||||||
self._cache_stats[self.cache_type]["null_hits"] += 1
|
self._cache_stats[self.cache_type]["null_hits"] += 1
|
||||||
|
|||||||
@ -227,6 +227,7 @@ class Model(TortoiseModel):
|
|||||||
return await with_db_timeout(
|
return await with_db_timeout(
|
||||||
cls.get_or_none(*args, using_db=using_db, **kwargs),
|
cls.get_or_none(*args, using_db=using_db, **kwargs),
|
||||||
operation=f"{cls.__name__}.get_or_none",
|
operation=f"{cls.__name__}.get_or_none",
|
||||||
|
source="DataBaseModel",
|
||||||
)
|
)
|
||||||
except MultipleObjectsReturned:
|
except MultipleObjectsReturned:
|
||||||
# 如果出现多个记录的情况,进行特殊处理
|
# 如果出现多个记录的情况,进行特殊处理
|
||||||
@ -239,6 +240,7 @@ class Model(TortoiseModel):
|
|||||||
records = await with_db_timeout(
|
records = await with_db_timeout(
|
||||||
cls.filter(*args, **kwargs).all(),
|
cls.filter(*args, **kwargs).all(),
|
||||||
operation=f"{cls.__name__}.filter.all",
|
operation=f"{cls.__name__}.filter.all",
|
||||||
|
source="DataBaseModel",
|
||||||
)
|
)
|
||||||
|
|
||||||
if not records:
|
if not records:
|
||||||
@ -255,6 +257,7 @@ class Model(TortoiseModel):
|
|||||||
await with_db_timeout(
|
await with_db_timeout(
|
||||||
record.delete(),
|
record.delete(),
|
||||||
operation=f"{cls.__name__}.delete_duplicate",
|
operation=f"{cls.__name__}.delete_duplicate",
|
||||||
|
source="DataBaseModel",
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"{cls.__name__} 删除重复记录:"
|
f"{cls.__name__} 删除重复记录:"
|
||||||
@ -269,11 +272,13 @@ class Model(TortoiseModel):
|
|||||||
return await with_db_timeout(
|
return await with_db_timeout(
|
||||||
cls.filter(*args, **kwargs).order_by("-id").first(),
|
cls.filter(*args, **kwargs).order_by("-id").first(),
|
||||||
operation=f"{cls.__name__}.filter.order_by.first",
|
operation=f"{cls.__name__}.filter.order_by.first",
|
||||||
|
source="DataBaseModel",
|
||||||
)
|
)
|
||||||
# 如果没有 id 字段,则返回第一个记录
|
# 如果没有 id 字段,则返回第一个记录
|
||||||
return await with_db_timeout(
|
return await with_db_timeout(
|
||||||
cls.filter(*args, **kwargs).first(),
|
cls.filter(*args, **kwargs).first(),
|
||||||
operation=f"{cls.__name__}.filter.first",
|
operation=f"{cls.__name__}.filter.first",
|
||||||
|
source="DataBaseModel",
|
||||||
)
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|||||||
@ -11,11 +11,15 @@ from .config import (
|
|||||||
|
|
||||||
|
|
||||||
async def with_db_timeout(
|
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()
|
start_time = time.time()
|
||||||
try:
|
try:
|
||||||
|
logger.debug(f"开始执行数据库操作: {operation} 来源: {source}")
|
||||||
result = await asyncio.wait_for(coro, timeout=timeout)
|
result = await asyncio.wait_for(coro, timeout=timeout)
|
||||||
elapsed = time.time() - start_time
|
elapsed = time.time() - start_time
|
||||||
if elapsed > SLOW_QUERY_THRESHOLD and operation:
|
if elapsed > SLOW_QUERY_THRESHOLD and operation:
|
||||||
|
|||||||
@ -50,6 +50,7 @@ class ZhenxunRepoConfig:
|
|||||||
"zhenxun/utils",
|
"zhenxun/utils",
|
||||||
"zhenxun/models",
|
"zhenxun/models",
|
||||||
"zhenxun/configs",
|
"zhenxun/configs",
|
||||||
|
"zhenxun/ui",
|
||||||
]
|
]
|
||||||
ZHENXUN_BOT_VERSION_FILE_STRING = "__version__"
|
ZHENXUN_BOT_VERSION_FILE_STRING = "__version__"
|
||||||
ZHENXUN_BOT_VERSION_FILE = Path() / ZHENXUN_BOT_VERSION_FILE_STRING
|
ZHENXUN_BOT_VERSION_FILE = Path() / ZHENXUN_BOT_VERSION_FILE_STRING
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user