diff --git a/README.md b/README.md
index 57f8d844..72641550 100644
--- a/README.md
+++ b/README.md
@@ -112,7 +112,7 @@ AccessToken: PUBLIC_ZHENXUN_TEST
| [插件库](https://github.com/zhenxun-org/zhenxun_bot_plugins) | 插件 | [zhenxun-org](https://github.com/zhenxun-org) | 原 plugins 文件夹插件 |
| [插件索引库](https://github.com/zhenxun-org/zhenxun_bot_plugins_index) | 插件 | [zhenxun-org](https://github.com/zhenxun-org) | 扩展插件索引库 |
| [一键安装](https://github.com/soloxiaoye2022/zhenxun_bot-deploy) | 安装 | [soloxiaoye2022](https://github.com/soloxiaoye2022) | 第三方 |
-| [WebUi](https://github.com/HibiKier/zhenxun_bot_webui) | 管理 | [hibikier](https://github.com/HibiKier) | 基于真寻 WebApi 的 webui 实现 [预览](#-webui界面展示) |
+| [WebUi](https://github.com/zhenxun-org/zhenxun_bot) | 管理 | [hibikier](https://github.com/HibiKier) | 基于真寻 WebApi 的 webui 实现 [预览](#-webui界面展示) |
| [安卓 app(WebUi)](https://github.com/YuS1aN/zhenxun_bot_android_ui) | 安装 | [YuS1aN](https://github.com/YuS1aN) | 第三方 |
@@ -126,6 +126,28 @@ AccessToken: PUBLIC_ZHENXUN_TEST
- 提供了 cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`PluginCdBlock` 等
- **更多详细请通过 [传送门](https://zhenxun-org.github.io/zhenxun_bot/) 查看文档!**
+## 🐣 小白整合
+
+如果你系统是 **Windows** 且不想下载 Python
+可以使用整合包(Python3.10+zhenxun+webui)
+
+文档地址:[整合包文档](https://hibikier.github.io/zhenxun_bot/beginner/)
+
+
+下载地址
+
+- **百度云:**
+ https://pan.baidu.com/s/1ph4yzx1vdNbkxm9VBKDdgQ?pwd=971j
+
+- **天翼云:**
+ https://cloud.189.cn/web/share?code=jq67r2i2E7Fb
+ 访问码:8wxm
+
+- **Google Drive:**
+ https://drive.google.com/file/d/1cc3Dqjk0x5hWGLNeMkrFwWl8BvsK6KfD/view?usp=drive_link
+
+
+
## 🛠️ 简单部署
```bash
@@ -272,12 +294,12 @@ DB_URL 是基于 Tortoise ORM 的数据库连接字符串,用于指定项目
## ❔ 需要帮助?
> [!TIP]
-> 发起 [issue](https://github.com/HibiKier/zhenxun_bot/issues/new/choose) 前,我们希望你能够阅读过或者了解 [提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)
+> 发起 [issue](https://github.com/zhenxun-org/zhenxun_bot/issues/new/choose) 前,我们希望你能够阅读过或者了解 [提问的智慧](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md)
>
> - 善用[搜索引擎](https://www.google.com/)
> - 查阅 issue 中是否有类似问题,如果没有请按照模板发起 issue
-欢迎前往 [issue](https://github.com/HibiKier/zhenxun_bot/issues/new/choose) 中提出你遇到的问题,或者加入我们的 [用户群](https://qm.qq.com/q/mRNtLSl6uc) 或 [技术群](https://qm.qq.com/q/YYYt5rkMYc)与我们联系
+欢迎前往 [issue](https://github.com/zhenxun-org/zhenxun_bot/issues/new/choose) 中提出你遇到的问题,或者加入我们的 [用户群](https://qm.qq.com/q/mRNtLSl6uc) 或 [技术群](https://qm.qq.com/q/YYYt5rkMYc)与我们联系
## 🛠️ 进度追踪
@@ -287,6 +309,8 @@ Project [zhenxun_bot](https://github.com/users/HibiKier/projects/2)
首席设计师:[酥酥/coldly-ss](https://github.com/coldly-ss)
+LOGO 设计:[FrostN0v0](https://github.com/FrostN0v0)
+
## 🙏 感谢
[botuniverse / onebot](https://github.com/botuniverse/onebot) :超棒的机器人协议
@@ -326,34 +350,68 @@ Project [zhenxun_bot](https://github.com/users/HibiKier/projects/2)
-## 📸 WebUI 界面展示
+## 📸 WebUI 界面展示(仅展示默认主题下的 pc 端)
-
-

-
-
-

-
-
-

-
-
-

-
+#### 登录界面
-
-

-
-
-

-
+
+
+#### API 设置
+
+
+
+#### 仪表盘
+
+
+
+#### 仪表盘(展开)
+
+
+
+#### 控制台
+
+
+
+#### 插件列表
+
+
+
+#### 插件列表(配置项)
+
+
+
+#### 插件商店
+
+
+
+#### 好友/群组管理
+
+
+
+#### 请求管理
+
+
+
+#### 数据库管理
+
+
+
+### 文件管理
+
+
+
+### 文件管理(文本查看)
+
+
+
+### 文件管理(图片查看)
+
+
+
+### 关于
+
+
-
-

-
-
-

-
diff --git a/bot.py b/bot.py
index 52cd29fc..aa047a71 100644
--- a/bot.py
+++ b/bot.py
@@ -14,9 +14,9 @@ driver.register_adapter(OneBotV11Adapter)
# driver.register_adapter(DoDoAdapter)
# driver.register_adapter(DiscordAdapter)
-from zhenxun.services.db_context import disconnect, init
+from zhenxun.services.db_context import disconnect
-driver.on_startup(init)
+# driver.on_startup(init)
driver.on_shutdown(disconnect)
# nonebot.load_builtin_plugins("echo")
diff --git a/docs_image/pc-about.jpg b/docs_image/pc-about.jpg
new file mode 100644
index 00000000..0bef7a9e
Binary files /dev/null and b/docs_image/pc-about.jpg differ
diff --git a/docs_image/pc-api.jpg b/docs_image/pc-api.jpg
new file mode 100644
index 00000000..59cee887
Binary files /dev/null and b/docs_image/pc-api.jpg differ
diff --git a/docs_image/pc-command.jpg b/docs_image/pc-command.jpg
new file mode 100644
index 00000000..0e310e29
Binary files /dev/null and b/docs_image/pc-command.jpg differ
diff --git a/docs_image/pc-dashboard.jpg b/docs_image/pc-dashboard.jpg
new file mode 100644
index 00000000..0478a850
Binary files /dev/null and b/docs_image/pc-dashboard.jpg differ
diff --git a/docs_image/pc-dashboard1.jpg b/docs_image/pc-dashboard1.jpg
new file mode 100644
index 00000000..3a0bc958
Binary files /dev/null and b/docs_image/pc-dashboard1.jpg differ
diff --git a/docs_image/pc-database.jpg b/docs_image/pc-database.jpg
new file mode 100644
index 00000000..68c60aa3
Binary files /dev/null and b/docs_image/pc-database.jpg differ
diff --git a/docs_image/pc-login.jpg b/docs_image/pc-login.jpg
new file mode 100644
index 00000000..65fe8b46
Binary files /dev/null and b/docs_image/pc-login.jpg differ
diff --git a/docs_image/pc-manage.jpg b/docs_image/pc-manage.jpg
new file mode 100644
index 00000000..e5f8902a
Binary files /dev/null and b/docs_image/pc-manage.jpg differ
diff --git a/docs_image/pc-manage1.jpg b/docs_image/pc-manage1.jpg
new file mode 100644
index 00000000..4756c629
Binary files /dev/null and b/docs_image/pc-manage1.jpg differ
diff --git a/docs_image/pc-plugin.jpg b/docs_image/pc-plugin.jpg
new file mode 100644
index 00000000..147e26eb
Binary files /dev/null and b/docs_image/pc-plugin.jpg differ
diff --git a/docs_image/pc-plugin1.jpg b/docs_image/pc-plugin1.jpg
new file mode 100644
index 00000000..58694e6d
Binary files /dev/null and b/docs_image/pc-plugin1.jpg differ
diff --git a/docs_image/pc-store.jpg b/docs_image/pc-store.jpg
new file mode 100644
index 00000000..4c9b68e4
Binary files /dev/null and b/docs_image/pc-store.jpg differ
diff --git a/docs_image/pc-system.jpg b/docs_image/pc-system.jpg
new file mode 100644
index 00000000..9908a2bd
Binary files /dev/null and b/docs_image/pc-system.jpg differ
diff --git a/docs_image/pc-system1.jpg b/docs_image/pc-system1.jpg
new file mode 100644
index 00000000..3333a1b5
Binary files /dev/null and b/docs_image/pc-system1.jpg differ
diff --git a/docs_image/pc-system2.jpg b/docs_image/pc-system2.jpg
new file mode 100644
index 00000000..649a5bc9
Binary files /dev/null and b/docs_image/pc-system2.jpg differ
diff --git a/docs_image/webui00.png b/docs_image/webui00.png
deleted file mode 100644
index 71f7d368..00000000
Binary files a/docs_image/webui00.png and /dev/null differ
diff --git a/docs_image/webui01.png b/docs_image/webui01.png
deleted file mode 100644
index cd415685..00000000
Binary files a/docs_image/webui01.png and /dev/null differ
diff --git a/docs_image/webui02.png b/docs_image/webui02.png
deleted file mode 100644
index 0fcc4f05..00000000
Binary files a/docs_image/webui02.png and /dev/null differ
diff --git a/docs_image/webui03.png b/docs_image/webui03.png
deleted file mode 100644
index 2e7426e3..00000000
Binary files a/docs_image/webui03.png and /dev/null differ
diff --git a/docs_image/webui04.png b/docs_image/webui04.png
deleted file mode 100644
index 5810f71b..00000000
Binary files a/docs_image/webui04.png and /dev/null differ
diff --git a/docs_image/webui05.png b/docs_image/webui05.png
deleted file mode 100644
index d5f5e304..00000000
Binary files a/docs_image/webui05.png and /dev/null differ
diff --git a/docs_image/webui06.png b/docs_image/webui06.png
deleted file mode 100644
index 7541f679..00000000
Binary files a/docs_image/webui06.png and /dev/null differ
diff --git a/docs_image/webui07.png b/docs_image/webui07.png
deleted file mode 100644
index 1628ade7..00000000
Binary files a/docs_image/webui07.png and /dev/null differ
diff --git a/zhenxun/builtin_plugins/__init__.py b/zhenxun/builtin_plugins/__init__.py
index fbaeb280..f2688905 100644
--- a/zhenxun/builtin_plugins/__init__.py
+++ b/zhenxun/builtin_plugins/__init__.py
@@ -16,6 +16,7 @@ from zhenxun.models.sign_user import SignUser
from zhenxun.models.user_console import UserConsole
from zhenxun.services.log import logger
from zhenxun.utils.decorator.shop import shop_register
+from zhenxun.utils.manager.priority_manager import PriorityLifecycle
from zhenxun.utils.manager.resource_manager import ResourceManager
from zhenxun.utils.platform import PlatformUtils
@@ -70,7 +71,7 @@ from public.bag_users t1
"""
-@driver.on_startup
+@PriorityLifecycle.on_startup(priority=5)
async def _():
await ResourceManager.init_resources()
"""签到与用户的数据迁移"""
diff --git a/zhenxun/builtin_plugins/admin/welcome_message/data_source.py b/zhenxun/builtin_plugins/admin/welcome_message/data_source.py
index 2ccb33ee..c8e486ed 100644
--- a/zhenxun/builtin_plugins/admin/welcome_message/data_source.py
+++ b/zhenxun/builtin_plugins/admin/welcome_message/data_source.py
@@ -14,6 +14,7 @@ from zhenxun.services.log import logger
from zhenxun.utils._build_image import BuildImage
from zhenxun.utils._image_template import ImageTemplate
from zhenxun.utils.http_utils import AsyncHttpx
+from zhenxun.utils.manager.priority_manager import PriorityLifecycle
from zhenxun.utils.platform import PlatformUtils
BASE_PATH = DATA_PATH / "welcome_message"
@@ -91,7 +92,7 @@ def migrate(path: Path):
json.dump(new_data, f, ensure_ascii=False, indent=4)
-@driver.on_startup
+@PriorityLifecycle.on_startup(priority=5)
def _():
"""数据迁移
diff --git a/zhenxun/builtin_plugins/init/init_config.py b/zhenxun/builtin_plugins/init/init_config.py
index 112d29de..eef63635 100644
--- a/zhenxun/builtin_plugins/init/init_config.py
+++ b/zhenxun/builtin_plugins/init/init_config.py
@@ -11,6 +11,7 @@ from zhenxun.configs.config import Config
from zhenxun.configs.path_config import DATA_PATH
from zhenxun.configs.utils import RegisterConfig
from zhenxun.services.log import logger
+from zhenxun.utils.manager.priority_manager import PriorityLifecycle
_yaml = YAML(pure=True)
_yaml.allow_unicode = True
@@ -102,7 +103,7 @@ def _generate_simple_config(exists_module: list[str]):
temp_file.unlink()
-@driver.on_startup
+@PriorityLifecycle.on_startup(priority=0)
def _():
"""
初始化插件数据配置
@@ -125,3 +126,4 @@ def _():
with plugins2config_file.open("w", encoding="utf8") as wf:
_yaml.dump(_data, wf)
_generate_simple_config(exists_module)
+ Config.reload()
diff --git a/zhenxun/builtin_plugins/init/init_plugin.py b/zhenxun/builtin_plugins/init/init_plugin.py
index dbeddb54..5bf50409 100644
--- a/zhenxun/builtin_plugins/init/init_plugin.py
+++ b/zhenxun/builtin_plugins/init/init_plugin.py
@@ -20,6 +20,7 @@ from zhenxun.utils.enum import (
PluginLimitType,
PluginType,
)
+from zhenxun.utils.manager.priority_manager import PriorityLifecycle
from .manager import manager
@@ -95,7 +96,7 @@ async def _handle_setting(
)
-@driver.on_startup
+@PriorityLifecycle.on_startup(priority=5)
async def _():
"""
初始化插件数据配置
diff --git a/zhenxun/builtin_plugins/init/init_task.py b/zhenxun/builtin_plugins/init/init_task.py
index cead7d72..b9bab56d 100644
--- a/zhenxun/builtin_plugins/init/init_task.py
+++ b/zhenxun/builtin_plugins/init/init_task.py
@@ -10,6 +10,7 @@ from zhenxun.models.group_console import GroupConsole
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.log import logger
from zhenxun.utils.common_utils import CommonUtils
+from zhenxun.utils.manager.priority_manager import PriorityLifecycle
driver: Driver = nonebot.get_driver()
@@ -132,7 +133,7 @@ async def create_schedule(task: Task):
logger.error(f"动态创建定时任务 {task.name}({task.module}) 失败", e=e)
-@driver.on_startup
+@PriorityLifecycle.on_startup(priority=5)
async def _():
"""
初始化插件数据配置
diff --git a/zhenxun/builtin_plugins/plugin_store/data_source.py b/zhenxun/builtin_plugins/plugin_store/data_source.py
index d21ba9cd..1df053f9 100644
--- a/zhenxun/builtin_plugins/plugin_store/data_source.py
+++ b/zhenxun/builtin_plugins/plugin_store/data_source.py
@@ -18,6 +18,12 @@ from zhenxun.utils.utils import is_number
from .config import BASE_PATH, DEFAULT_GITHUB_URL, EXTRA_GITHUB_URL
+BAT_FILE = Path() / "win启动.bat"
+
+WIN_COMMAND = ["./Python310/python.exe", "-m", "pip", "install", "-r"]
+
+DEFAULT_COMMAND = ["poetry", "run", "pip", "install", "-r"]
+
def row_style(column: str, text: str) -> RowStyle:
"""被动技能文本风格
@@ -50,8 +56,10 @@ def install_requirement(plugin_path: Path):
return
try:
+ command = WIN_COMMAND if BAT_FILE.exists() else DEFAULT_COMMAND
+ command.append(str(existing_requirements))
result = subprocess.run(
- ["poetry", "run", "pip", "install", "-r", str(existing_requirements)],
+ command,
check=True,
capture_output=True,
text=True,
diff --git a/zhenxun/builtin_plugins/scripts.py b/zhenxun/builtin_plugins/scripts.py
index 0be7527c..b5fca300 100644
--- a/zhenxun/builtin_plugins/scripts.py
+++ b/zhenxun/builtin_plugins/scripts.py
@@ -1,12 +1,8 @@
-import nonebot
-from nonebot.drivers import Driver
-
from zhenxun.models.group_console import GroupConsole
-
-driver: Driver = nonebot.get_driver()
+from zhenxun.utils.manager.priority_manager import PriorityLifecycle
-@driver.on_startup
+@PriorityLifecycle.on_startup(priority=5)
async def _():
"""开启/禁用插件格式修改"""
_, is_create = await GroupConsole.get_or_create(group_id=133133133)
diff --git a/zhenxun/builtin_plugins/sign_in/__init__.py b/zhenxun/builtin_plugins/sign_in/__init__.py
index 0b48a0e7..0986e476 100644
--- a/zhenxun/builtin_plugins/sign_in/__init__.py
+++ b/zhenxun/builtin_plugins/sign_in/__init__.py
@@ -10,7 +10,6 @@ from nonebot_plugin_alconna import (
store_true,
)
from nonebot_plugin_apscheduler import scheduler
-from nonebot_plugin_uninfo import Uninfo
from zhenxun.configs.utils import (
Command,
@@ -23,7 +22,7 @@ from zhenxun.utils.depends import UserName
from zhenxun.utils.message import MessageUtils
from ._data_source import SignManage
-from .goods_register import driver # noqa: F401
+from .goods_register import Uninfo
from .utils import clear_sign_data_pic
__plugin_meta__ = PluginMetadata(
diff --git a/zhenxun/builtin_plugins/sign_in/goods_register.py b/zhenxun/builtin_plugins/sign_in/goods_register.py
index f7a65359..6c8e39bb 100644
--- a/zhenxun/builtin_plugins/sign_in/goods_register.py
+++ b/zhenxun/builtin_plugins/sign_in/goods_register.py
@@ -1,7 +1,6 @@
from decimal import Decimal
import nonebot
-from nonebot.drivers import Driver
from nonebot_plugin_uninfo import Uninfo
from zhenxun.models.sign_user import SignUser
@@ -9,14 +8,7 @@ from zhenxun.models.user_console import UserConsole
from zhenxun.utils.decorator.shop import shop_register
from zhenxun.utils.platform import PlatformUtils
-driver: Driver = nonebot.get_driver()
-
-
-# @driver.on_startup
-# async def _():
-# """
-# 导入内置的三个商品
-# """
+driver = nonebot.get_driver()
@shop_register(
diff --git a/zhenxun/builtin_plugins/sign_in/utils.py b/zhenxun/builtin_plugins/sign_in/utils.py
index 9faf1120..910b90d8 100644
--- a/zhenxun/builtin_plugins/sign_in/utils.py
+++ b/zhenxun/builtin_plugins/sign_in/utils.py
@@ -16,6 +16,7 @@ from zhenxun.models.sign_log import SignLog
from zhenxun.models.sign_user import SignUser
from zhenxun.utils.http_utils import AsyncHttpx
from zhenxun.utils.image_utils import BuildImage
+from zhenxun.utils.manager.priority_manager import PriorityLifecycle
from zhenxun.utils.platform import PlatformUtils
from .config import (
@@ -54,7 +55,7 @@ LG_MESSAGE = [
]
-@driver.on_startup
+@PriorityLifecycle.on_startup(priority=5)
async def init_image():
SIGN_RESOURCE_PATH.mkdir(parents=True, exist_ok=True)
SIGN_TODAY_CARD_PATH.mkdir(exist_ok=True, parents=True)
diff --git a/zhenxun/builtin_plugins/web_ui/__init__.py b/zhenxun/builtin_plugins/web_ui/__init__.py
index 90772bc5..619d56bf 100644
--- a/zhenxun/builtin_plugins/web_ui/__init__.py
+++ b/zhenxun/builtin_plugins/web_ui/__init__.py
@@ -10,7 +10,9 @@ 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
+from zhenxun.utils.manager.priority_manager import PriorityLifecycle
+from .api.configure import router as configure_router
from .api.logs import router as ws_log_routes
from .api.logs.log_manager import LOG_STORAGE
from .api.menu import router as menu_router
@@ -81,6 +83,7 @@ BaseApiRouter.include_router(database_router)
BaseApiRouter.include_router(plugin_router)
BaseApiRouter.include_router(system_router)
BaseApiRouter.include_router(menu_router)
+BaseApiRouter.include_router(configure_router)
WsApiRouter = APIRouter(prefix="/zhenxun/socket")
@@ -89,7 +92,7 @@ WsApiRouter.include_router(status_routes)
WsApiRouter.include_router(chat_routes)
-@driver.on_startup
+@PriorityLifecycle.on_startup(priority=0)
async def _():
try:
# 存储任务引用的列表,防止任务被垃圾回收
diff --git a/zhenxun/builtin_plugins/web_ui/api/configure/__init__.py b/zhenxun/builtin_plugins/web_ui/api/configure/__init__.py
new file mode 100644
index 00000000..0ecde197
--- /dev/null
+++ b/zhenxun/builtin_plugins/web_ui/api/configure/__init__.py
@@ -0,0 +1,133 @@
+import asyncio
+import os
+from pathlib import Path
+import re
+import subprocess
+import sys
+import time
+
+from fastapi import APIRouter
+from fastapi.responses import JSONResponse
+import nonebot
+
+from zhenxun.configs.config import BotConfig, Config
+
+from ...base_model import Result
+from .data_source import test_db_connection
+from .model import Setting
+
+router = APIRouter(prefix="/configure")
+
+driver = nonebot.get_driver()
+
+port = driver.config.port
+
+BAT_FILE = Path() / "win启动.bat"
+
+FILE_NAME = ".configure_restart"
+
+
+@router.post(
+ "/set_configure",
+ response_model=Result,
+ response_class=JSONResponse,
+ description="设置基础配置",
+)
+async def _(setting: Setting) -> Result:
+ global port
+ password = Config.get_config("web-ui", "password")
+ if password or BotConfig.db_url:
+ return Result.fail("配置已存在,请先删除DB_URL内容和前端密码再进行设置。")
+ env_file = Path() / ".env.dev"
+ if not env_file.exists():
+ return Result.fail("配置文件.env.dev不存在。")
+ env_text = env_file.read_text(encoding="utf-8")
+ if setting.db_url:
+ if setting.db_url.startswith("sqlite"):
+ base_dir = Path().resolve()
+ # 清理和验证数据库路径
+ db_path_str = setting.db_url.split(":")[-1].strip()
+ # 移除任何可能的路径遍历尝试
+ db_path_str = re.sub(r"[\\/]\.\.[\\/]", "", db_path_str)
+ # 规范化路径
+ db_path = Path(db_path_str).resolve()
+ parent_path = db_path.parent
+
+ # 验证路径是否在项目根目录内
+ try:
+ if not parent_path.absolute().is_relative_to(base_dir):
+ return Result.fail("数据库路径不在项目根目录内。")
+ except ValueError:
+ return Result.fail("无效的数据库路径。")
+
+ # 创建目录
+ try:
+ parent_path.mkdir(parents=True, exist_ok=True)
+ except Exception as e:
+ return Result.fail(f"创建数据库目录失败: {e!s}")
+
+ env_text = env_text.replace('DB_URL = ""', f'DB_URL = "{setting.db_url}"')
+ if setting.superusers:
+ superusers = ", ".join([f'"{s}"' for s in setting.superusers])
+ env_text = re.sub(r"SUPERUSERS=\[.*?\]", f"SUPERUSERS=[{superusers}]", env_text)
+ if setting.host:
+ env_text = env_text.replace("HOST = 127.0.0.1", f"HOST = {setting.host}")
+ if setting.port:
+ env_text = env_text.replace("PORT = 8080", f"PORT = {setting.port}")
+ port = setting.port
+ if setting.username:
+ Config.set_config("web-ui", "username", setting.username)
+ Config.set_config("web-ui", "password", setting.password, True)
+ env_file.write_text(env_text, encoding="utf-8")
+ if BAT_FILE.exists():
+ for file in os.listdir(Path()):
+ if file.startswith(FILE_NAME):
+ Path(file).unlink()
+ flag_file = Path() / f"{FILE_NAME}_{int(time.time())}"
+ flag_file.touch()
+ return Result.ok(BAT_FILE.exists(), info="设置成功,请重启真寻以完成配置!")
+
+
+@router.get(
+ "/test_db",
+ response_model=Result,
+ response_class=JSONResponse,
+ description="设置基础配置",
+)
+async def _(db_url: str) -> Result:
+ result = await test_db_connection(db_url)
+ if isinstance(result, str):
+ return Result.fail(result)
+ return Result.ok(info="数据库连接成功!")
+
+
+async def run_restart_command(bat_path: Path, port: int):
+ """在后台执行重启命令"""
+ await asyncio.sleep(1) # 确保 FastAPI 已返回响应
+ subprocess.Popen([bat_path, str(port)], shell=True) # noqa: ASYNC220
+ sys.exit(0) # 退出当前进程
+
+
+@router.post(
+ "/restart",
+ response_model=Result,
+ response_class=JSONResponse,
+ description="重启",
+)
+async def _() -> Result:
+ if not BAT_FILE.exists():
+ return Result.fail("自动重启仅支持意见整合包,请尝试手动重启")
+ flag_file = next(
+ (Path() / file for file in os.listdir(Path()) if file.startswith(FILE_NAME)),
+ None,
+ )
+ if not flag_file or not flag_file.exists():
+ return Result.fail("重启标志文件不存在...")
+ set_time = flag_file.name.split("_")[-1]
+ if time.time() - float(set_time) > 10 * 60:
+ return Result.fail("重启标志文件已过期,请重新设置配置。")
+ flag_file.unlink()
+ try:
+ return Result.ok(info="执行重启命令成功")
+ finally:
+ asyncio.create_task(run_restart_command(BAT_FILE, port)) # noqa: RUF006
diff --git a/zhenxun/builtin_plugins/web_ui/api/configure/data_source.py b/zhenxun/builtin_plugins/web_ui/api/configure/data_source.py
new file mode 100644
index 00000000..ad8c73c9
--- /dev/null
+++ b/zhenxun/builtin_plugins/web_ui/api/configure/data_source.py
@@ -0,0 +1,18 @@
+from tortoise import Tortoise
+
+
+async def test_db_connection(db_url: str) -> bool | str:
+ try:
+ # 初始化 Tortoise ORM
+ await Tortoise.init(
+ db_url=db_url,
+ modules={"models": ["__main__"]}, # 这里不需要实际模型
+ )
+ # 测试连接
+ await Tortoise.get_connection("default").execute_query("SELECT 1")
+ return True
+ except Exception as e:
+ return str(e)
+ finally:
+ # 关闭连接
+ await Tortoise.close_connections()
diff --git a/zhenxun/builtin_plugins/web_ui/api/configure/model.py b/zhenxun/builtin_plugins/web_ui/api/configure/model.py
new file mode 100644
index 00000000..4a6b3486
--- /dev/null
+++ b/zhenxun/builtin_plugins/web_ui/api/configure/model.py
@@ -0,0 +1,16 @@
+from pydantic import BaseModel
+
+
+class Setting(BaseModel):
+ superusers: list[str]
+ """超级用户列表"""
+ db_url: str
+ """数据库地址"""
+ host: str
+ """主机地址"""
+ port: int
+ """端口"""
+ username: str
+ """前端用户名"""
+ password: str
+ """前端密码"""
diff --git a/zhenxun/builtin_plugins/web_ui/api/menu/data_source.py b/zhenxun/builtin_plugins/web_ui/api/menu/data_source.py
index 14f5c928..e54bf9e5 100644
--- a/zhenxun/builtin_plugins/web_ui/api/menu/data_source.py
+++ b/zhenxun/builtin_plugins/web_ui/api/menu/data_source.py
@@ -5,54 +5,63 @@ from zhenxun.services.log import logger
from .model import MenuData, MenuItem
+default_menus = [
+ MenuItem(
+ name="仪表盘",
+ module="dashboard",
+ router="/dashboard",
+ icon="dashboard",
+ default=True,
+ ),
+ MenuItem(
+ name="真寻控制台",
+ module="command",
+ router="/command",
+ icon="command",
+ ),
+ MenuItem(name="插件列表", module="plugin", router="/plugin", icon="plugin"),
+ MenuItem(name="插件商店", module="store", router="/store", icon="store"),
+ MenuItem(name="好友/群组", module="manage", router="/manage", icon="user"),
+ MenuItem(
+ name="数据库管理",
+ module="database",
+ router="/database",
+ icon="database",
+ ),
+ MenuItem(name="系统信息", module="system", router="/system", icon="system"),
+ MenuItem(name="关于我们", module="about", router="/about", icon="about"),
+]
-class MenuManage:
+
+class MenuManager:
def __init__(self) -> None:
self.file = DATA_PATH / "web_ui" / "menu.json"
self.menu = []
if self.file.exists():
try:
+ temp_menu = []
self.menu = json.load(self.file.open(encoding="utf8"))
+ self_menu_name = [menu["name"] for menu in self.menu]
+ for module in [m.module for m in default_menus]:
+ if module in self_menu_name:
+ temp_menu.append(
+ MenuItem(
+ **next(m for m in self.menu if m["module"] == module)
+ )
+ )
+ else:
+ temp_menu.append(self.__get_menu_model(module))
+ self.menu = temp_menu
except Exception as e:
logger.warning("菜单文件损坏,已重新生成...", "WebUi", e=e)
if not self.menu:
- self.menu = [
- MenuItem(
- name="仪表盘",
- module="dashboard",
- router="/dashboard",
- icon="dashboard",
- default=True,
- ),
- MenuItem(
- name="真寻控制台",
- module="command",
- router="/command",
- icon="command",
- ),
- MenuItem(
- name="插件列表", module="plugin", router="/plugin", icon="plugin"
- ),
- MenuItem(
- name="插件商店", module="store", router="/store", icon="store"
- ),
- MenuItem(
- name="好友/群组", module="manage", router="/manage", icon="user"
- ),
- MenuItem(
- name="数据库管理",
- module="database",
- router="/database",
- icon="database",
- ),
- MenuItem(
- name="文件管理", module="system", router="/system", icon="system"
- ),
- MenuItem(
- name="关于我们", module="about", router="/about", icon="about"
- ),
- ]
- self.save()
+ self.menu = default_menus
+ self.save()
+
+ def __get_menu_model(self, module: str):
+ return default_menus[
+ next(i for i, m in enumerate(default_menus) if m.module == module)
+ ]
def get_menus(self):
return MenuData(menus=self.menu)
@@ -64,4 +73,4 @@ class MenuManage:
json.dump(temp, f, ensure_ascii=False, indent=4)
-menu_manage = MenuManage()
+menu_manage = MenuManager()
diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/dashboard/data_source.py b/zhenxun/builtin_plugins/web_ui/api/tabs/dashboard/data_source.py
index 6c312db3..87011c93 100644
--- a/zhenxun/builtin_plugins/web_ui/api/tabs/dashboard/data_source.py
+++ b/zhenxun/builtin_plugins/web_ui/api/tabs/dashboard/data_source.py
@@ -13,6 +13,7 @@ from zhenxun.models.bot_connect_log import BotConnectLog
from zhenxun.models.chat_history import ChatHistory
from zhenxun.models.statistics import Statistics
from zhenxun.services.log import logger
+from zhenxun.utils.manager.priority_manager import PriorityLifecycle
from zhenxun.utils.platform import PlatformUtils
from ....base_model import BaseResultModel, QueryModel
@@ -31,7 +32,7 @@ driver: Driver = nonebot.get_driver()
CONNECT_TIME = 0
-@driver.on_startup
+@PriorityLifecycle.on_startup(priority=5)
async def _():
global CONNECT_TIME
CONNECT_TIME = int(time.time())
diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/database/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/database/__init__.py
index b963e291..91fbc5c0 100644
--- a/zhenxun/builtin_plugins/web_ui/api/tabs/database/__init__.py
+++ b/zhenxun/builtin_plugins/web_ui/api/tabs/database/__init__.py
@@ -8,6 +8,7 @@ from zhenxun.configs.config import BotConfig
from zhenxun.models.plugin_info import PluginInfo
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.log import logger
+from zhenxun.utils.manager.priority_manager import PriorityLifecycle
from ....base_model import BaseResultModel, QueryModel, Result
from ....utils import authentication
@@ -21,7 +22,7 @@ router = APIRouter(prefix="/database")
driver: Driver = nonebot.get_driver()
-@driver.on_startup
+@PriorityLifecycle.on_startup(priority=5)
async def _():
for plugin in nonebot.get_loaded_plugins():
module = plugin.name
diff --git a/zhenxun/builtin_plugins/web_ui/api/tabs/system/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/system/__init__.py
index ffcd05be..949a69de 100644
--- a/zhenxun/builtin_plugins/web_ui/api/tabs/system/__init__.py
+++ b/zhenxun/builtin_plugins/web_ui/api/tabs/system/__init__.py
@@ -9,7 +9,7 @@ from fastapi.responses import JSONResponse
from zhenxun.utils._build_image import BuildImage
from ....base_model import Result, SystemFolderSize
-from ....utils import authentication, get_system_disk
+from ....utils import authentication, get_system_disk, validate_path
from .model import AddFile, DeleteFile, DirFile, RenameFile, SaveFile
router = APIRouter(prefix="/system")
@@ -25,22 +25,29 @@ IMAGE_TYPE = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "svg"]
description="获取文件列表",
)
async def _(path: str | None = None) -> Result[list[DirFile]]:
- base_path = Path(path) if path else Path()
- data_list = []
- for file in os.listdir(base_path):
- file_path = base_path / file
- is_image = any(file.endswith(f".{t}") for t in IMAGE_TYPE)
- data_list.append(
- DirFile(
- is_file=not file_path.is_dir(),
- is_image=is_image,
- name=file,
- parent=path,
- size=None if file_path.is_dir() else file_path.stat().st_size,
- mtime=file_path.stat().st_mtime,
+ try:
+ base_path, error = validate_path(path)
+ if error:
+ return Result.fail(error)
+ if not base_path:
+ return Result.fail("无效的路径")
+ data_list = []
+ for file in os.listdir(base_path):
+ file_path = base_path / file
+ is_image = any(file.endswith(f".{t}") for t in IMAGE_TYPE)
+ data_list.append(
+ DirFile(
+ is_file=not file_path.is_dir(),
+ is_image=is_image,
+ name=file,
+ parent=path,
+ size=None if file_path.is_dir() else file_path.stat().st_size,
+ mtime=file_path.stat().st_mtime,
+ )
)
- )
- return Result.ok(data_list)
+ return Result.ok(data_list)
+ except Exception as e:
+ return Result.fail(f"获取文件列表失败: {e!s}")
@router.get(
@@ -62,8 +69,12 @@ async def _(full_path: str | None = None) -> Result[list[SystemFolderSize]]:
description="删除文件",
)
async def _(param: DeleteFile) -> Result:
- path = Path(param.full_path)
- if not path or not path.exists():
+ path, error = validate_path(param.full_path)
+ if error:
+ return Result.fail(error)
+ if not path:
+ return Result.fail("无效的路径")
+ if not path.exists():
return Result.warning_("文件不存在...")
try:
path.unlink()
@@ -80,8 +91,12 @@ async def _(param: DeleteFile) -> Result:
description="删除文件夹",
)
async def _(param: DeleteFile) -> Result:
- path = Path(param.full_path)
- if not path or not path.exists() or path.is_file():
+ path, error = validate_path(param.full_path)
+ if error:
+ return Result.fail(error)
+ if not path:
+ return Result.fail("无效的路径")
+ if not path.exists() or path.is_file():
return Result.warning_("文件夹不存在...")
try:
shutil.rmtree(path.absolute())
@@ -98,10 +113,14 @@ async def _(param: DeleteFile) -> Result:
description="重命名文件",
)
async def _(param: RenameFile) -> Result:
- path = (
- (Path(param.parent) / param.old_name) if param.parent else Path(param.old_name)
- )
- if not path or not path.exists():
+ parent_path, error = validate_path(param.parent)
+ if error:
+ return Result.fail(error)
+ if not parent_path:
+ return Result.fail("无效的路径")
+
+ path = (parent_path / param.old_name) if param.parent else Path(param.old_name)
+ if not path.exists():
return Result.warning_("文件不存在...")
try:
path.rename(path.parent / param.name)
@@ -118,10 +137,14 @@ async def _(param: RenameFile) -> Result:
description="重命名文件夹",
)
async def _(param: RenameFile) -> Result:
- path = (
- (Path(param.parent) / param.old_name) if param.parent else Path(param.old_name)
- )
- if not path or not path.exists() or path.is_file():
+ parent_path, error = validate_path(param.parent)
+ if error:
+ return Result.fail(error)
+ if not parent_path:
+ return Result.fail("无效的路径")
+
+ path = (parent_path / param.old_name) if param.parent else Path(param.old_name)
+ if not path.exists() or path.is_file():
return Result.warning_("文件夹不存在...")
try:
new_path = path.parent / param.name
@@ -139,7 +162,13 @@ async def _(param: RenameFile) -> Result:
description="新建文件",
)
async def _(param: AddFile) -> Result:
- path = (Path(param.parent) / param.name) if param.parent else Path(param.name)
+ parent_path, error = validate_path(param.parent)
+ if error:
+ return Result.fail(error)
+ if not parent_path:
+ return Result.fail("无效的路径")
+
+ path = (parent_path / param.name) if param.parent else Path(param.name)
if path.exists():
return Result.warning_("文件已存在...")
try:
@@ -157,7 +186,13 @@ async def _(param: AddFile) -> Result:
description="新建文件夹",
)
async def _(param: AddFile) -> Result:
- path = (Path(param.parent) / param.name) if param.parent else Path(param.name)
+ parent_path, error = validate_path(param.parent)
+ if error:
+ return Result.fail(error)
+ if not parent_path:
+ return Result.fail("无效的路径")
+
+ path = (parent_path / param.name) if param.parent else Path(param.name)
if path.exists():
return Result.warning_("文件夹已存在...")
try:
@@ -175,7 +210,11 @@ async def _(param: AddFile) -> Result:
description="读取文件",
)
async def _(full_path: str) -> Result:
- path = Path(full_path)
+ path, error = validate_path(full_path)
+ if error:
+ return Result.fail(error)
+ if not path:
+ return Result.fail("无效的路径")
if not path.exists():
return Result.warning_("文件不存在...")
try:
@@ -193,9 +232,13 @@ async def _(full_path: str) -> Result:
description="读取文件",
)
async def _(param: SaveFile) -> Result[str]:
- path = Path(param.full_path)
+ path, error = validate_path(param.full_path)
+ if error:
+ return Result.fail(error)
+ if not path:
+ return Result.fail("无效的路径")
try:
- async with aiofiles.open(path, "w", encoding="utf-8") as f:
+ async with aiofiles.open(str(path), "w", encoding="utf-8") as f:
await f.write(param.content)
return Result.ok("更新成功!")
except Exception as e:
@@ -210,7 +253,11 @@ async def _(param: SaveFile) -> Result[str]:
description="读取图片base64",
)
async def _(full_path: str) -> Result[str]:
- path = Path(full_path)
+ path, error = validate_path(full_path)
+ if error:
+ return Result.fail(error)
+ if not path:
+ return Result.fail("无效的路径")
if not path.exists():
return Result.warning_("文件不存在...")
try:
diff --git a/zhenxun/builtin_plugins/web_ui/config.py b/zhenxun/builtin_plugins/web_ui/config.py
index bddcb062..4a88aad9 100644
--- a/zhenxun/builtin_plugins/web_ui/config.py
+++ b/zhenxun/builtin_plugins/web_ui/config.py
@@ -1,6 +1,12 @@
+import sys
+
from fastapi.middleware.cors import CORSMiddleware
import nonebot
-from strenum import StrEnum
+
+if sys.version_info >= (3, 11):
+ from enum import StrEnum
+else:
+ from strenum import StrEnum
from zhenxun.configs.path_config import DATA_PATH, TEMP_PATH
diff --git a/zhenxun/builtin_plugins/web_ui/public/data_source.py b/zhenxun/builtin_plugins/web_ui/public/data_source.py
index 9f5a657e..51b29533 100644
--- a/zhenxun/builtin_plugins/web_ui/public/data_source.py
+++ b/zhenxun/builtin_plugins/web_ui/public/data_source.py
@@ -18,6 +18,7 @@ async def update_webui_assets():
download_url = await GithubUtils.parse_github_url(
WEBUI_DIST_GITHUB_URL
).get_archive_download_urls()
+ logger.info("开始下载 webui_assets 资源...", COMMAND_NAME)
if await AsyncHttpx.download_file(
download_url, webui_assets_path, follow_redirects=True
):
diff --git a/zhenxun/builtin_plugins/web_ui/utils.py b/zhenxun/builtin_plugins/web_ui/utils.py
index a7e22a07..84459114 100644
--- a/zhenxun/builtin_plugins/web_ui/utils.py
+++ b/zhenxun/builtin_plugins/web_ui/utils.py
@@ -2,6 +2,7 @@ import contextlib
from datetime import datetime, timedelta, timezone
import os
from pathlib import Path
+import re
from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
@@ -28,6 +29,45 @@ if token_file.exists():
token_data = json.load(open(token_file, encoding="utf8"))
+def validate_path(path_str: str | None) -> tuple[Path | None, str | None]:
+ """验证路径是否安全
+
+ 参数:
+ path_str: 用户输入的路径
+
+ 返回:
+ tuple[Path | None, str | None]: (验证后的路径, 错误信息)
+ """
+ try:
+ if not path_str:
+ return Path().resolve(), None
+
+ # 1. 移除任何可能的路径遍历尝试
+ path_str = re.sub(r"[\\/]\.\.[\\/]", "", path_str)
+
+ # 2. 规范化路径并转换为绝对路径
+ path = Path(path_str).resolve()
+
+ # 3. 获取项目根目录
+ root_dir = Path().resolve()
+
+ # 4. 验证路径是否在项目根目录内
+ try:
+ if not path.is_relative_to(root_dir):
+ return None, "访问路径超出允许范围"
+ except ValueError:
+ return None, "无效的路径格式"
+
+ # 5. 验证路径是否包含任何危险字符
+ if any(c in str(path) for c in ["..", "~", "*", "?", ">", "<", "|", '"']):
+ return None, "路径包含非法字符"
+
+ # 6. 验证路径长度是否合理
+ return (None, "路径长度超出限制") if len(str(path)) > 4096 else (path, None)
+ except Exception as e:
+ 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"
diff --git a/zhenxun/services/db_context.py b/zhenxun/services/db_context.py
index 9a44fa74..33678965 100644
--- a/zhenxun/services/db_context.py
+++ b/zhenxun/services/db_context.py
@@ -1,9 +1,12 @@
+import nonebot
from nonebot.utils import is_coroutine_callable
from tortoise import Tortoise
from tortoise.connection import connections
from tortoise.models import Model as Model_
from zhenxun.configs.config import BotConfig
+from zhenxun.utils.exception import HookPriorityException
+from zhenxun.utils.manager.priority_manager import PriorityLifecycle
from .log import logger
@@ -11,6 +14,9 @@ SCRIPT_METHOD = []
MODELS: list[str] = []
+driver = nonebot.get_driver()
+
+
class Model(Model_):
"""
自动添加模块
@@ -26,7 +32,7 @@ class Model(Model_):
SCRIPT_METHOD.append((cls.__module__, func))
-class DbUrlIsNode(Exception):
+class DbUrlIsNode(HookPriorityException):
"""
数据库链接地址为空
"""
@@ -42,9 +48,19 @@ class DbConnectError(Exception):
pass
+@PriorityLifecycle.on_startup(priority=1)
async def init():
if not BotConfig.db_url:
- raise DbUrlIsNode("数据库配置为空,请在.env.dev中配置DB_URL...")
+ # raise DbUrlIsNode("数据库配置为空,请在.env.dev中配置DB_URL...")
+ error = f"""
+**********************************************************************
+🌟 **************************** 配置为空 ************************* 🌟
+🚀 请打开 WebUi 进行基础配置 🚀
+🌐 配置地址:http://{driver.config.host}:{driver.config.port}/#/configure 🌐
+***********************************************************************
+***********************************************************************
+ """
+ raise DbUrlIsNode("\n" + error.strip())
try:
await Tortoise.init(
db_url=BotConfig.db_url,
diff --git a/zhenxun/services/plugin_init.py b/zhenxun/services/plugin_init.py
index 159e042c..a622a9e8 100644
--- a/zhenxun/services/plugin_init.py
+++ b/zhenxun/services/plugin_init.py
@@ -6,6 +6,7 @@ from nonebot.utils import is_coroutine_callable
from pydantic import BaseModel
from zhenxun.services.log import logger
+from zhenxun.utils.manager.priority_manager import PriorityLifecycle
driver = nonebot.get_driver()
@@ -100,6 +101,6 @@ class PluginInitManager:
logger.error(f"执行: {module_path}:remove 失败", e=e)
-@driver.on_startup
+@PriorityLifecycle.on_startup(priority=5)
async def _():
await PluginInitManager.install_all()
diff --git a/zhenxun/utils/_build_mat.py b/zhenxun/utils/_build_mat.py
index de73e69d..a3de3087 100644
--- a/zhenxun/utils/_build_mat.py
+++ b/zhenxun/utils/_build_mat.py
@@ -1,12 +1,17 @@
from io import BytesIO
from pathlib import Path
import random
+import sys
from pydantic import BaseModel, Field
-from strenum import StrEnum
from ._build_image import BuildImage
+if sys.version_info >= (3, 11):
+ from enum import StrEnum
+else:
+ from strenum import StrEnum
+
class MatType(StrEnum):
LINE = "LINE"
diff --git a/zhenxun/utils/enum.py b/zhenxun/utils/enum.py
index 2ddf5297..ba012617 100644
--- a/zhenxun/utils/enum.py
+++ b/zhenxun/utils/enum.py
@@ -1,4 +1,16 @@
-from strenum import StrEnum
+import sys
+
+if sys.version_info >= (3, 11):
+ from enum import StrEnum
+else:
+ from strenum import StrEnum
+
+
+class PriorityLifecycleType(StrEnum):
+ STARTUP = "STARTUP"
+ """启动"""
+ SHUTDOWN = "SHUTDOWN"
+ """关闭"""
class BankHandleType(StrEnum):
diff --git a/zhenxun/utils/exception.py b/zhenxun/utils/exception.py
index db8c0656..8ec925ec 100644
--- a/zhenxun/utils/exception.py
+++ b/zhenxun/utils/exception.py
@@ -1,3 +1,15 @@
+class HookPriorityException(BaseException):
+ """
+ 钩子优先级异常
+ """
+
+ def __init__(self, info: str = "") -> None:
+ self.info = info
+
+ def __str__(self) -> str:
+ return self.info
+
+
class NotFoundError(Exception):
"""
未发现
diff --git a/zhenxun/utils/github_utils/models.py b/zhenxun/utils/github_utils/models.py
index e3e5dfe3..fb690616 100644
--- a/zhenxun/utils/github_utils/models.py
+++ b/zhenxun/utils/github_utils/models.py
@@ -1,13 +1,18 @@
import contextlib
+import sys
from typing import Protocol
from aiocache import cached
from nonebot.compat import model_dump
from pydantic import BaseModel, Field
-from strenum import StrEnum
from zhenxun.utils.http_utils import AsyncHttpx
+if sys.version_info >= (3, 11):
+ from enum import StrEnum
+else:
+ from strenum import StrEnum
+
from .const import (
CACHED_API_TTL,
GIT_API_COMMIT_FORMAT,
diff --git a/zhenxun/utils/manager/message_manager.py b/zhenxun/utils/manager/message_manager.py
index e714c8d8..ee34369d 100644
--- a/zhenxun/utils/manager/message_manager.py
+++ b/zhenxun/utils/manager/message_manager.py
@@ -22,6 +22,4 @@ class MessageManager:
@classmethod
def get(cls, uid: str) -> list[str]:
- if uid in cls.data:
- return cls.data[uid]
- return []
+ return cls.data[uid] if uid in cls.data else []
diff --git a/zhenxun/utils/manager/priority_manager.py b/zhenxun/utils/manager/priority_manager.py
new file mode 100644
index 00000000..1c59635c
--- /dev/null
+++ b/zhenxun/utils/manager/priority_manager.py
@@ -0,0 +1,57 @@
+from collections.abc import Callable
+from typing import ClassVar
+
+import nonebot
+from nonebot.utils import is_coroutine_callable
+
+from zhenxun.services.log import logger
+from zhenxun.utils.enum import PriorityLifecycleType
+from zhenxun.utils.exception import HookPriorityException
+
+driver = nonebot.get_driver()
+
+
+class PriorityLifecycle:
+ _data: ClassVar[dict[PriorityLifecycleType, dict[int, list[Callable]]]] = {}
+
+ @classmethod
+ def add(cls, hook_type: PriorityLifecycleType, func: Callable, priority: int):
+ if hook_type not in cls._data:
+ cls._data[hook_type] = {}
+ if priority not in cls._data[hook_type]:
+ cls._data[hook_type][priority] = []
+ cls._data[hook_type][priority].append(func)
+
+ @classmethod
+ def on_startup(cls, *, priority: int):
+ def wrapper(func):
+ cls.add(PriorityLifecycleType.STARTUP, func, priority)
+ return func
+
+ return wrapper
+
+ @classmethod
+ def on_shutdown(cls, *, priority: int):
+ def wrapper(func):
+ cls.add(PriorityLifecycleType.SHUTDOWN, func, priority)
+ return func
+
+ return wrapper
+
+
+@driver.on_startup
+async def _():
+ priority_data = PriorityLifecycle._data.get(PriorityLifecycleType.STARTUP)
+ if not priority_data:
+ return
+ priority_list = sorted(priority_data.keys())
+ priority = 0
+ try:
+ for priority in priority_list:
+ for func in priority_data[priority]:
+ if is_coroutine_callable(func):
+ await func()
+ else:
+ func()
+ except HookPriorityException as e:
+ logger.error(f"打断优先级 [{priority}] on_startup 方法. {type(e)}: {e}")