feat(mysql): 添加MySQL数据库支持
- 在requirements.txt中添加aiomysql依赖 - 在config.toml中添加MySQL配置块 - 新增MySQLModel配置模型 - 实现MySQLManager单例管理器 - 更新Config类以支持MySQL配置加载 - 在__init__.py中导出mysql_manager - 改进ConfigError异常处理
This commit is contained in:
21
config.toml
21
config.toml
@@ -21,13 +21,28 @@ permission_denied_message = "权限不足,需要 {permission_name} 权限"
|
|||||||
# Redis 配置
|
# Redis 配置
|
||||||
[redis]
|
[redis]
|
||||||
# Redis 主机地址
|
# Redis 主机地址
|
||||||
host = "101.36.126.55"
|
host = "114.66.61.199"
|
||||||
# Redis 端口
|
# Redis 端口
|
||||||
port = 6379
|
port = 37080
|
||||||
# Redis 数据库编号
|
# Redis 数据库编号
|
||||||
db = 0
|
db = 0
|
||||||
# Redis 密码
|
# 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 配置
|
||||||
[docker]
|
[docker]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import tomllib
|
import tomllib
|
||||||
from pydantic import ValidationError
|
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.logger import ModuleLogger
|
||||||
from .utils.exceptions import ConfigError, ConfigNotFoundError, ConfigValidationError
|
from .utils.exceptions import ConfigError, ConfigNotFoundError, ConfigValidationError
|
||||||
|
|
||||||
@@ -59,9 +59,9 @@ class Config:
|
|||||||
error_details.append(error_msg)
|
error_details.append(error_msg)
|
||||||
|
|
||||||
validation_error = ConfigValidationError(
|
validation_error = ConfigValidationError(
|
||||||
message="配置验证失败",
|
message="配置验证失败"
|
||||||
original_error=e
|
|
||||||
)
|
)
|
||||||
|
validation_error.original_error = e
|
||||||
|
|
||||||
self.logger.error("配置验证失败,请检查 `config.toml` 文件中的以下错误:")
|
self.logger.error("配置验证失败,请检查 `config.toml` 文件中的以下错误:")
|
||||||
for detail in error_details:
|
for detail in error_details:
|
||||||
@@ -71,17 +71,17 @@ class Config:
|
|||||||
raise validation_error
|
raise validation_error
|
||||||
except tomllib.TOMLDecodeError as e:
|
except tomllib.TOMLDecodeError as e:
|
||||||
error = ConfigError(
|
error = ConfigError(
|
||||||
message=f"TOML解析错误: {str(e)}",
|
message=f"TOML解析错误: {str(e)}"
|
||||||
original_error=e
|
|
||||||
)
|
)
|
||||||
|
error.original_error = e
|
||||||
self.logger.error(f"加载配置文件时发生TOML解析错误: {error.message}")
|
self.logger.error(f"加载配置文件时发生TOML解析错误: {error.message}")
|
||||||
self.logger.log_custom_exception(error)
|
self.logger.log_custom_exception(error)
|
||||||
raise error
|
raise error
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error = ConfigError(
|
error = ConfigError(
|
||||||
message=f"加载配置文件时发生未知错误: {str(e)}",
|
message=f"加载配置文件时发生未知错误: {str(e)}"
|
||||||
original_error=e
|
|
||||||
)
|
)
|
||||||
|
error.original_error = e
|
||||||
self.logger.exception(f"加载配置文件时发生未知错误: {error.message}")
|
self.logger.exception(f"加载配置文件时发生未知错误: {error.message}")
|
||||||
self.logger.log_custom_exception(error)
|
self.logger.log_custom_exception(error)
|
||||||
raise error
|
raise error
|
||||||
@@ -108,6 +108,13 @@ class Config:
|
|||||||
"""
|
"""
|
||||||
return self._model.redis
|
return self._model.redis
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mysql(self) -> MySQLModel:
|
||||||
|
"""
|
||||||
|
获取 MySQL 配置
|
||||||
|
"""
|
||||||
|
return self._model.mysql
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def docker(self) -> DockerModel:
|
def docker(self) -> DockerModel:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -36,6 +36,18 @@ class RedisModel(BaseModel):
|
|||||||
password: str
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
class MySQLModel(BaseModel):
|
||||||
|
"""
|
||||||
|
对应 `config.toml` 中的 `[mysql]` 配置块。
|
||||||
|
"""
|
||||||
|
host: str
|
||||||
|
port: int
|
||||||
|
user: str
|
||||||
|
password: str
|
||||||
|
db: str
|
||||||
|
charset: str = "utf8mb4"
|
||||||
|
|
||||||
|
|
||||||
class DockerModel(BaseModel):
|
class DockerModel(BaseModel):
|
||||||
"""
|
"""
|
||||||
对应 `config.toml` 中的 `[docker]` 配置块。
|
对应 `config.toml` 中的 `[docker]` 配置块。
|
||||||
@@ -64,6 +76,7 @@ class ConfigModel(BaseModel):
|
|||||||
napcat_ws: NapCatWSModel
|
napcat_ws: NapCatWSModel
|
||||||
bot: BotModel
|
bot: BotModel
|
||||||
redis: RedisModel
|
redis: RedisModel
|
||||||
|
mysql: MySQLModel
|
||||||
docker: DockerModel
|
docker: DockerModel
|
||||||
image_manager: ImageManagerModel
|
image_manager: ImageManagerModel
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from .command_manager import matcher as command_manager
|
|||||||
from .permission_manager import PermissionManager
|
from .permission_manager import PermissionManager
|
||||||
from .plugin_manager import PluginManager
|
from .plugin_manager import PluginManager
|
||||||
from .redis_manager import RedisManager
|
from .redis_manager import RedisManager
|
||||||
|
from .mysql_manager import MySQLManager
|
||||||
from .browser_manager import BrowserManager
|
from .browser_manager import BrowserManager
|
||||||
from .image_manager import ImageManager
|
from .image_manager import ImageManager
|
||||||
|
|
||||||
@@ -26,6 +27,9 @@ plugin_manager = PluginManager(command_manager)
|
|||||||
# Redis 管理器
|
# Redis 管理器
|
||||||
redis_manager = RedisManager()
|
redis_manager = RedisManager()
|
||||||
|
|
||||||
|
# MySQL 管理器
|
||||||
|
mysql_manager = MySQLManager()
|
||||||
|
|
||||||
# 浏览器管理器
|
# 浏览器管理器
|
||||||
browser_manager = BrowserManager()
|
browser_manager = BrowserManager()
|
||||||
|
|
||||||
@@ -38,6 +42,7 @@ __all__ = [
|
|||||||
"matcher",
|
"matcher",
|
||||||
"plugin_manager",
|
"plugin_manager",
|
||||||
"redis_manager",
|
"redis_manager",
|
||||||
|
"mysql_manager",
|
||||||
"browser_manager",
|
"browser_manager",
|
||||||
"image_manager",
|
"image_manager",
|
||||||
]
|
]
|
||||||
|
|||||||
148
core/managers/mysql_manager.py
Normal file
148
core/managers/mysql_manager.py
Normal 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()
|
||||||
@@ -83,14 +83,15 @@ class ConfigError(Exception):
|
|||||||
配置相关错误的基类。
|
配置相关错误的基类。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
section: 配置部分名称
|
section: 配置部分名称(可选)
|
||||||
key: 配置项名称
|
key: 配置项名称(可选)
|
||||||
message: 错误消息
|
message: 错误消息(可选)
|
||||||
"""
|
"""
|
||||||
def __init__(self, section=None, key=None, message=None):
|
def __init__(self, section=None, key=None, message=None):
|
||||||
self.section = section
|
self.section = section
|
||||||
self.key = key
|
self.key = key
|
||||||
self.message = message
|
self.message = message
|
||||||
|
self.original_error = None
|
||||||
|
|
||||||
if section and key and message:
|
if section and key and message:
|
||||||
super().__init__(f"配置错误 [{section}.{key}]: {message}")
|
super().__init__(f"配置错误 [{section}.{key}]: {message}")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
aiohappyeyeballs==2.6.1
|
aiohappyeyeballs==2.6.1
|
||||||
aiohttp==3.13.3
|
aiohttp==3.13.3
|
||||||
|
aiomysql==0.2.0
|
||||||
aiosignal==1.4.0
|
aiosignal==1.4.0
|
||||||
annotated-types==0.7.0
|
annotated-types==0.7.0
|
||||||
anyio==4.12.1
|
anyio==4.12.1
|
||||||
|
|||||||
Reference in New Issue
Block a user