feat(mysql): 添加MySQL数据库支持

- 在requirements.txt中添加aiomysql依赖
- 在config.toml中添加MySQL配置块
- 新增MySQLModel配置模型
- 实现MySQLManager单例管理器
- 更新Config类以支持MySQL配置加载
- 在__init__.py中导出mysql_manager
- 改进ConfigError异常处理
This commit is contained in:
2026-02-28 16:59:52 +08:00
parent 0bca97424b
commit ed4da64a7a
7 changed files with 203 additions and 13 deletions

View File

@@ -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]

View File

@@ -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
@@ -108,6 +108,13 @@ class Config:
"""
return self._model.redis
@property
def mysql(self) -> MySQLModel:
"""
获取 MySQL 配置
"""
return self._model.mysql
@property
def docker(self) -> DockerModel:
"""

View File

@@ -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

View File

@@ -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",
]

View File

@@ -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()

View File

@@ -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}")

View File

@@ -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