性能优化 (#2126)

* 性能优化

* 代码改进

* 优化浏览器代际切换逻辑

* 统一缓存与生命周期

* 添加aiomysql依赖

* 优化插件路径处理逻辑,简化条件判断;在虚拟环境包管理器中添加编码和错误处理参数以增强稳定性

* 🚨 auto fix by pre-commit hooks

* 优化Windows下的关闭逻辑

* 代码优化

* bugfix:修复配置重载问题

* bugfix:修复插件加载启动竞态问题

* 收敛事件入口和权限上下文

* 优化 Windows launcher 关闭重启兜底

---------

Co-authored-by: HibiKier <775757368@qq.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Copaan
2026-04-26 15:50:15 +08:00
committed by GitHub
co-authored by HibiKier pre-commit-ci[bot]
parent 24c316cd2c
commit 5d92ccd3b0
56 changed files with 3092 additions and 806 deletions
+12 -34
View File
@@ -1,5 +1,4 @@
import asyncio
from collections import OrderedDict
from collections.abc import AsyncGenerator, Awaitable, Callable, Sequence
from contextlib import asynccontextmanager
import os
@@ -21,6 +20,7 @@ from rich.progress import (
import ujson as json
from zhenxun.configs.config import BotConfig
from zhenxun.services.cache.bounded_ttl import BoundedTTLCache
from zhenxun.services.log import logger
from zhenxun.utils.decorator.retry import Retry
from zhenxun.utils.exception import AllURIsFailedError
@@ -168,7 +168,12 @@ class AsyncHttpx:
_CONTENT_CACHE_TTL: ClassVar[float] = 3.0
_CONTENT_CACHE_MAX_ITEMS: ClassVar[int] = 256
_CONTENT_CACHE_MAX_BYTES: ClassVar[int] = 2 * 1024 * 1024
_content_cache: ClassVar[OrderedDict[str, tuple[float, bytes]]] = OrderedDict()
_content_cache: ClassVar[BoundedTTLCache[str, bytes]] = BoundedTTLCache(
"HTTP_IMAGE_CONTENT",
ttl_seconds=_CONTENT_CACHE_TTL,
max_items=_CONTENT_CACHE_MAX_ITEMS,
max_total_bytes=_CONTENT_CACHE_MAX_BYTES,
)
_content_inflight: ClassVar[dict[str, asyncio.Task[Response]]] = {}
_content_cache_lock: ClassVar[asyncio.Lock] = asyncio.Lock()
@@ -191,29 +196,6 @@ class AsyncHttpx:
return True
return "qpic.cn" in lower_url or "qlogo.cn" in lower_url
@classmethod
def _get_cached_content_nolock(cls, key: str) -> bytes | None:
entry = cls._content_cache.get(key)
if not entry:
return None
expire_at, content = entry
if expire_at <= time.monotonic():
cls._content_cache.pop(key, None)
return None
cls._content_cache.move_to_end(key)
return content
@classmethod
def _cleanup_content_cache_nolock(cls) -> None:
now = time.monotonic()
while cls._content_cache:
expire_at, _ = next(iter(cls._content_cache.values()))
if expire_at > now:
break
cls._content_cache.popitem(last=False)
while len(cls._content_cache) > cls._CONTENT_CACHE_MAX_ITEMS:
cls._content_cache.popitem(last=False)
@classmethod
async def _try_cache_content(cls, key: str, response: Response) -> None:
content = response.content
@@ -223,13 +205,7 @@ class AsyncHttpx:
is_image = content_type.startswith("image/") or cls._is_probably_image_url(key)
if not is_image:
return
async with cls._content_cache_lock:
cls._content_cache[key] = (
time.monotonic() + cls._CONTENT_CACHE_TTL,
content,
)
cls._content_cache.move_to_end(key)
cls._cleanup_content_cache_nolock()
await cls._content_cache.set(key, content)
@classmethod
def _prepare_temporary_client_config(cls, client_kwargs: dict) -> dict:
@@ -450,9 +426,11 @@ class AsyncHttpx:
return res.content
cache_key = url
if cached := await cls._content_cache.get(cache_key):
return cached
async with cls._content_cache_lock:
cached = cls._get_cached_content_nolock(cache_key)
if cached is not None:
if cached := await cls._content_cache.get(cache_key):
return cached
task = cls._content_inflight.get(cache_key)
if task is None: