diff --git a/config.toml b/config.toml index e7a7d12..d5943dc 100644 --- a/config.toml +++ b/config.toml @@ -21,13 +21,28 @@ permission_denied_message = "权限不足,需要 {permission_name} 权限" # Redis 配置 [redis] # Redis 主机地址 -host = "101.36.126.55" +host = "114.66.61.199" # Redis 端口 -port = 6379 +port = 37080 # Redis 数据库编号 db = 0 # Redis 密码 -password = "redis_5fCmnE" +password = "redis_n7Ke76" + +# MySQL 配置 +[mysql] +# MySQL 主机地址 +host = "114.66.61.199" +# MySQL 端口 +port = 42398 +# MySQL 用户名 +user = "neobot" +# MySQL 密码 +password = "neobot" +# MySQL 数据库名称 +db = "neobot" + + # Docker 配置 [docker] diff --git a/core/config_loader.py b/core/config_loader.py index 1e3450a..8910756 100644 --- a/core/config_loader.py +++ b/core/config_loader.py @@ -7,7 +7,7 @@ from pathlib import Path import tomllib from pydantic import ValidationError -from .config_models import ConfigModel, NapCatWSModel, BotModel, RedisModel, DockerModel, ImageManagerModel +from .config_models import ConfigModel, NapCatWSModel, BotModel, RedisModel, DockerModel, ImageManagerModel, MySQLModel from .utils.logger import ModuleLogger from .utils.exceptions import ConfigError, ConfigNotFoundError, ConfigValidationError @@ -59,9 +59,9 @@ class Config: error_details.append(error_msg) validation_error = ConfigValidationError( - message="配置验证失败", - original_error=e + message="配置验证失败" ) + validation_error.original_error = e self.logger.error("配置验证失败,请检查 `config.toml` 文件中的以下错误:") for detail in error_details: @@ -71,17 +71,17 @@ class Config: raise validation_error except tomllib.TOMLDecodeError as e: error = ConfigError( - message=f"TOML解析错误: {str(e)}", - original_error=e + message=f"TOML解析错误: {str(e)}" ) + error.original_error = e self.logger.error(f"加载配置文件时发生TOML解析错误: {error.message}") self.logger.log_custom_exception(error) raise error except Exception as e: error = ConfigError( - message=f"加载配置文件时发生未知错误: {str(e)}", - original_error=e + message=f"加载配置文件时发生未知错误: {str(e)}" ) + error.original_error = e self.logger.exception(f"加载配置文件时发生未知错误: {error.message}") self.logger.log_custom_exception(error) raise error @@ -107,6 +107,13 @@ class Config: 获取 Redis 配置 """ return self._model.redis + + @property + def mysql(self) -> MySQLModel: + """ + 获取 MySQL 配置 + """ + return self._model.mysql @property def docker(self) -> DockerModel: diff --git a/core/config_models.py b/core/config_models.py index 1b9bb2a..13c0d7b 100644 --- a/core/config_models.py +++ b/core/config_models.py @@ -36,6 +36,18 @@ class RedisModel(BaseModel): password: str +class MySQLModel(BaseModel): + """ + 对应 `config.toml` 中的 `[mysql]` 配置块。 + """ + host: str + port: int + user: str + password: str + db: str + charset: str = "utf8mb4" + + class DockerModel(BaseModel): """ 对应 `config.toml` 中的 `[docker]` 配置块。 @@ -64,6 +76,7 @@ class ConfigModel(BaseModel): napcat_ws: NapCatWSModel bot: BotModel redis: RedisModel + mysql: MySQLModel docker: DockerModel image_manager: ImageManagerModel diff --git a/core/managers/__init__.py b/core/managers/__init__.py index a30c6f5..a221ee3 100644 --- a/core/managers/__init__.py +++ b/core/managers/__init__.py @@ -8,6 +8,7 @@ from .command_manager import matcher as command_manager from .permission_manager import PermissionManager from .plugin_manager import PluginManager from .redis_manager import RedisManager +from .mysql_manager import MySQLManager from .browser_manager import BrowserManager from .image_manager import ImageManager @@ -26,6 +27,9 @@ plugin_manager = PluginManager(command_manager) # Redis 管理器 redis_manager = RedisManager() +# MySQL 管理器 +mysql_manager = MySQLManager() + # 浏览器管理器 browser_manager = BrowserManager() @@ -38,6 +42,7 @@ __all__ = [ "matcher", "plugin_manager", "redis_manager", + "mysql_manager", "browser_manager", "image_manager", ] diff --git a/core/managers/mysql_manager.py b/core/managers/mysql_manager.py new file mode 100644 index 0000000..136ac4b --- /dev/null +++ b/core/managers/mysql_manager.py @@ -0,0 +1,148 @@ +import aiomysql +from ..config_loader import global_config as config +from ..utils.logger import logger +from ..utils.singleton import Singleton + + +class MySQLManager(Singleton): + """ + MySQL 数据库连接管理器(异步单例) + """ + _pool = None + + def __init__(self): + """ + 初始化 MySQL 管理器 + """ + super().__init__() + + async def initialize(self): + """ + 异步初始化 MySQL 连接池并进行健康检查 + """ + if self._pool is None: + try: + mysql_config = config.mysql + host = mysql_config.host + port = mysql_config.port + user = mysql_config.user + password = mysql_config.password + db = mysql_config.db + charset = mysql_config.charset + + logger.info(f"正在尝试连接 MySQL: {host}:{port}, DB: {db}") + + self._pool = await aiomysql.create_pool( + host=host, + port=port, + user=user, + password=password, + db=db, + charset=charset, + autocommit=False, + maxsize=10, + minsize=1 + ) + + async with self._pool.acquire() as conn: + async with conn.cursor() as cur: + await cur.execute("SELECT 1") + result = await cur.fetchone() + if result and result[0] == 1: + logger.success("MySQL 连接成功!") + else: + logger.error("MySQL 连接失败: 健康检查失败") + except Exception as e: + logger.exception(f"MySQL 初始化时发生未知错误: {e}") + self._pool = None + + @property + def pool(self): + """ + 获取 MySQL 连接池实例 + """ + if self._pool is None: + raise ConnectionError("MySQL 未初始化或连接失败,请先调用 initialize()") + return self._pool + + async def execute(self, sql: str, args: tuple = None): + """ + 执行 SQL 语句(用于 INSERT、UPDATE、DELETE) + + Args: + sql: SQL 语句 + args: 参数元组 + + Returns: + 影响的行数 + """ + async with self._pool.acquire() as conn: + async with conn.cursor() as cur: + await cur.execute(sql, args) + await conn.commit() + return cur.rowcount + + async def fetchone(self, sql: str, args: tuple = None): + """ + 查询单条记录 + + Args: + sql: SQL 语句 + args: 参数元组 + + Returns: + 单条记录字典 + """ + async with self._pool.acquire() as conn: + async with conn.cursor(aiomysql.DictCursor) as cur: + await cur.execute(sql, args) + return await cur.fetchone() + + async def fetchall(self, sql: str, args: tuple = None): + """ + 查询多条记录 + + Args: + sql: SQL 语句 + args: 参数元组 + + Returns: + 记录列表 + """ + async with self._pool.acquire() as conn: + async with conn.cursor(aiomysql.DictCursor) as cur: + await cur.execute(sql, args) + return await cur.fetchall() + + async def begin_transaction(self): + """ + 开始事务 + + Returns: + 事务连接对象 + """ + conn = await self._pool.acquire() + return conn + + async def commit_transaction(self, conn): + """ + 提交事务 + + Args: + conn: 事务连接对象 + """ + await conn.commit() + await self._pool.release(conn) + + async def rollback_transaction(self, conn): + """ + 回滚事务 + + Args: + conn: 事务连接对象 + """ + await conn.rollback() + await self._pool.release(conn) + + +mysql_manager = MySQLManager() diff --git a/core/utils/exceptions.py b/core/utils/exceptions.py index acaf404..417b006 100644 --- a/core/utils/exceptions.py +++ b/core/utils/exceptions.py @@ -83,14 +83,15 @@ class ConfigError(Exception): 配置相关错误的基类。 Args: - section: 配置部分名称 - key: 配置项名称 - message: 错误消息 + section: 配置部分名称(可选) + key: 配置项名称(可选) + message: 错误消息(可选) """ def __init__(self, section=None, key=None, message=None): self.section = section self.key = key self.message = message + self.original_error = None if section and key and message: super().__init__(f"配置错误 [{section}.{key}]: {message}") diff --git a/requirements.txt b/requirements.txt index 18ad662..3e72413 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ aiohappyeyeballs==2.6.1 aiohttp==3.13.3 +aiomysql==0.2.0 aiosignal==1.4.0 annotated-types==0.7.0 anyio==4.12.1