Merge branch 'main' into dev
This commit is contained in:
70
check_syntax.py
Normal file
70
check_syntax.py
Normal file
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
检查项目中所有Python文件的语法
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
def check_python_syntax(file_path):
|
||||
"""
|
||||
检查单个Python文件的语法
|
||||
|
||||
Args:
|
||||
file_path: Python文件路径
|
||||
|
||||
Returns:
|
||||
bool: 如果语法正确返回True,否则返回False
|
||||
"""
|
||||
try:
|
||||
with open(file_path, 'rb') as f:
|
||||
code = f.read()
|
||||
|
||||
# 使用compile函数检查语法
|
||||
compile(code, file_path, 'exec')
|
||||
return True
|
||||
except SyntaxError as e:
|
||||
print(f"语法错误: {file_path}:{e.lineno}:{e.offset}: {e.msg}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"无法检查文件 {file_path}: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""
|
||||
检查项目中所有Python文件的语法
|
||||
"""
|
||||
# 要检查的目录
|
||||
directories = ['core', 'models', 'plugins', 'scripts', 'tests']
|
||||
|
||||
# 要检查的单独文件
|
||||
files = ['main.py', 'profile_main.py', 'test_performance_simple.py', 'setup_mypyc.py']
|
||||
|
||||
error_count = 0
|
||||
file_count = 0
|
||||
|
||||
# 检查目录中的所有Python文件
|
||||
for directory in directories:
|
||||
for root, _, filenames in os.walk(directory):
|
||||
for filename in filenames:
|
||||
if filename.endswith('.py'):
|
||||
file_path = os.path.join(root, filename)
|
||||
file_count += 1
|
||||
if not check_python_syntax(file_path):
|
||||
error_count += 1
|
||||
|
||||
# 检查单独的Python文件
|
||||
for file in files:
|
||||
if os.path.exists(file):
|
||||
file_count += 1
|
||||
if not check_python_syntax(file):
|
||||
error_count += 1
|
||||
|
||||
print(f"\n检查完成: {file_count} 个文件,{error_count} 个语法错误")
|
||||
|
||||
if error_count > 0:
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -25,7 +25,11 @@ from .bot import Bot
|
||||
from .config_loader import global_config
|
||||
from .managers.command_manager import matcher
|
||||
from .utils.executor import CodeExecutor
|
||||
from .utils.logger import logger
|
||||
from .utils.logger import logger, ModuleLogger
|
||||
from .utils.exceptions import (
|
||||
WebSocketError, WebSocketConnectionError, WebSocketAuthenticationError
|
||||
)
|
||||
from .utils.error_codes import ErrorCode, create_error_response
|
||||
|
||||
|
||||
class WS:
|
||||
@@ -45,11 +49,15 @@ class WS:
|
||||
self.token = cfg.token
|
||||
self.reconnect_interval = cfg.reconnect_interval
|
||||
|
||||
# 初始化状态
|
||||
self.ws: Optional[WebSocketClientProtocol] = None
|
||||
self._pending_requests: Dict[str, asyncio.Future] = {}
|
||||
self._pending_requests: Dict[str, asyncio.Future] = {} # echo: future
|
||||
self.bot: Bot | None = None
|
||||
self.self_id: int | None = None
|
||||
self.code_executor = code_executor
|
||||
|
||||
# 创建模块专用日志记录器
|
||||
self.logger = ModuleLogger("WebSocket")
|
||||
|
||||
async def connect(self) -> None:
|
||||
"""
|
||||
@@ -62,24 +70,43 @@ class WS:
|
||||
|
||||
while True:
|
||||
try:
|
||||
logger.info(f"正在尝试连接至 NapCat: {self.url}")
|
||||
self.logger.info(f"正在尝试连接至 NapCat: {self.url}")
|
||||
async with websockets.connect(
|
||||
self.url, additional_headers=headers
|
||||
) as websocket_raw:
|
||||
websocket = cast(WebSocketClientProtocol, websocket_raw)
|
||||
self.ws = websocket
|
||||
logger.success("连接成功!")
|
||||
self.logger.success("连接成功!")
|
||||
await self._listen_loop(websocket)
|
||||
|
||||
except websockets.exceptions.AuthenticationError as e:
|
||||
error = WebSocketAuthenticationError(
|
||||
message=f"WebSocket认证失败: {str(e)}",
|
||||
code=ErrorCode.WS_AUTH_FAILED,
|
||||
original_error=e
|
||||
)
|
||||
self.logger.error(f"连接失败: {error.message}")
|
||||
self.logger.log_custom_exception(error)
|
||||
except (
|
||||
websockets.exceptions.ConnectionClosed,
|
||||
ConnectionRefusedError,
|
||||
) as e:
|
||||
logger.warning(f"连接断开或服务器拒绝访问: {e}")
|
||||
error = WebSocketConnectionError(
|
||||
message=f"连接断开或服务器拒绝访问: {str(e)}",
|
||||
code=ErrorCode.WS_CONNECTION_FAILED,
|
||||
original_error=e
|
||||
)
|
||||
self.logger.warning(f"连接失败: {error.message}")
|
||||
except Exception as e:
|
||||
logger.exception(f"运行异常: {e}")
|
||||
error = WebSocketError(
|
||||
message=f"WebSocket运行异常: {str(e)}",
|
||||
code=ErrorCode.WS_MESSAGE_ERROR,
|
||||
original_error=e
|
||||
)
|
||||
self.logger.exception(f"运行异常: {error.message}")
|
||||
self.logger.log_custom_exception(error)
|
||||
|
||||
logger.info(f"{self.reconnect_interval}秒后尝试重连...")
|
||||
self.logger.info(f"{self.reconnect_interval}秒后尝试重连...")
|
||||
await asyncio.sleep(self.reconnect_interval)
|
||||
|
||||
async def _listen_loop(self, websocket_connection: WebSocketClientProtocol) -> None:
|
||||
@@ -111,8 +138,22 @@ class WS:
|
||||
# 使用 create_task 异步执行,避免阻塞 WebSocket 接收循环
|
||||
asyncio.create_task(self.on_event(data))
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
error = WebSocketError(
|
||||
message=f"JSON解析失败: {str(e)}",
|
||||
code=ErrorCode.WS_MESSAGE_ERROR,
|
||||
original_error=e
|
||||
)
|
||||
self.logger.error(f"解析消息异常: {error.message}")
|
||||
self.logger.debug(f"原始消息: {message}")
|
||||
except Exception as e:
|
||||
logger.exception(f"解析消息异常: {e}")
|
||||
error = WebSocketError(
|
||||
message=f"处理消息异常: {str(e)}",
|
||||
code=ErrorCode.WS_MESSAGE_ERROR,
|
||||
original_error=e
|
||||
)
|
||||
self.logger.exception(f"解析消息异常: {error.message}")
|
||||
self.logger.log_custom_exception(error)
|
||||
|
||||
async def on_event(self, event_data: Dict[str, Any]) -> None:
|
||||
"""
|
||||
@@ -136,17 +177,17 @@ class WS:
|
||||
if self.bot is None and hasattr(event, 'self_id'):
|
||||
self.self_id = event.self_id
|
||||
self.bot = Bot(self)
|
||||
logger.success(f"Bot 实例初始化完成: self_id={self.self_id}")
|
||||
self.logger.success(f"Bot 实例初始化完成: self_id={self.self_id}")
|
||||
|
||||
# 将代码执行器注入到 Bot 和执行器自身
|
||||
if self.code_executor:
|
||||
self.bot.code_executor = self.code_executor
|
||||
self.code_executor.bot = self.bot
|
||||
logger.info("代码执行器已成功注入 Bot 实例。")
|
||||
self.logger.info("代码执行器已成功注入 Bot 实例。")
|
||||
|
||||
# 如果 bot 尚未初始化,则不处理后续事件
|
||||
if self.bot is None:
|
||||
logger.warning("Bot 尚未初始化,跳过事件处理。")
|
||||
self.logger.warning("Bot 尚未初始化,跳过事件处理。")
|
||||
return
|
||||
|
||||
event.bot = self.bot # 注入 Bot 实例
|
||||
@@ -157,23 +198,28 @@ class WS:
|
||||
message_type = getattr(event, "message_type", "Unknown")
|
||||
user_id = getattr(event, "user_id", "Unknown")
|
||||
raw_message = getattr(event, "raw_message", "")
|
||||
logger.info(f"[消息] {message_type} | {user_id}({sender_name}): {raw_message}")
|
||||
self.logger.info(f"[消息] {message_type} | {user_id}({sender_name}): {raw_message}")
|
||||
elif event.post_type == "notice":
|
||||
notice_type = getattr(event, "notice_type", "Unknown")
|
||||
logger.info(f"[通知] {notice_type}")
|
||||
self.logger.info(f"[通知] {notice_type}")
|
||||
elif event.post_type == "request":
|
||||
request_type = getattr(event, "request_type", "Unknown")
|
||||
logger.info(f"[请求] {request_type}")
|
||||
self.logger.info(f"[请求] {request_type}")
|
||||
elif event.post_type == "meta_event":
|
||||
meta_event_type = getattr(event, "meta_event_type", "Unknown")
|
||||
logger.debug(f"[元事件] {meta_event_type}")
|
||||
|
||||
self.logger.debug(f"[元事件] {meta_event_type}")
|
||||
|
||||
# 分发事件
|
||||
await matcher.handle_event(self.bot, event)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"事件处理异常: {e}")
|
||||
self.logger.exception(f"事件处理异常: {str(e)}")
|
||||
error = WebSocketError(
|
||||
message=f"事件处理异常: {str(e)}",
|
||||
code=ErrorCode.WS_MESSAGE_ERROR,
|
||||
original_error=e
|
||||
)
|
||||
self.logger.log_custom_exception(error)
|
||||
|
||||
async def call_api(self, action: str, params: Optional[Dict[Any, Any]] = None) -> Dict[Any, Any]:
|
||||
"""
|
||||
@@ -191,14 +237,22 @@ class WS:
|
||||
表示失败的字典。
|
||||
"""
|
||||
if not self.ws:
|
||||
logger.error("调用 API 失败: WebSocket 未初始化")
|
||||
return {"status": "failed", "msg": "websocket not initialized"}
|
||||
self.logger.error("调用 API 失败: WebSocket 未初始化")
|
||||
return create_error_response(
|
||||
code=ErrorCode.WS_DISCONNECTED,
|
||||
message="WebSocket未初始化",
|
||||
data={"action": action, "params": params}
|
||||
)
|
||||
|
||||
from websockets.protocol import State
|
||||
|
||||
if getattr(self.ws, "state", None) is not State.OPEN:
|
||||
logger.error("调用 API 失败: WebSocket 连接未打开")
|
||||
return {"status": "failed", "msg": "websocket is not open"}
|
||||
self.logger.error("调用 API 失败: WebSocket 连接未打开")
|
||||
return create_error_response(
|
||||
code=ErrorCode.WS_DISCONNECTED,
|
||||
message="WebSocket连接未打开",
|
||||
data={"action": action, "params": params}
|
||||
)
|
||||
|
||||
echo_id = str(uuid.uuid4())
|
||||
payload = {"action": action, "params": params or {}, "echo": echo_id}
|
||||
@@ -207,12 +261,23 @@ class WS:
|
||||
future = loop.create_future()
|
||||
self._pending_requests[echo_id] = future
|
||||
|
||||
await self.ws.send(json.dumps(payload))
|
||||
|
||||
try:
|
||||
await self.ws.send(json.dumps(payload))
|
||||
return await asyncio.wait_for(future, timeout=30.0)
|
||||
except asyncio.TimeoutError:
|
||||
self._pending_requests.pop(echo_id, None)
|
||||
logger.warning(f"API 调用超时: action={action}, params={params}")
|
||||
return {"status": "failed", "retcode": -1, "msg": "api timeout"}
|
||||
self.logger.warning(f"API 调用超时: action={action}, params={params}")
|
||||
return create_error_response(
|
||||
code=ErrorCode.TIMEOUT_ERROR,
|
||||
message="API调用超时",
|
||||
data={"action": action, "params": params}
|
||||
)
|
||||
except Exception as e:
|
||||
self._pending_requests.pop(echo_id, None)
|
||||
self.logger.exception(f"API 调用异常: action={action}, error={str(e)}")
|
||||
return create_error_response(
|
||||
code=ErrorCode.WS_MESSAGE_ERROR,
|
||||
message=f"API调用异常: {str(e)}",
|
||||
data={"action": action, "params": params}
|
||||
)
|
||||
|
||||
@@ -8,7 +8,9 @@ from pathlib import Path
|
||||
import tomllib
|
||||
from pydantic import ValidationError
|
||||
from .config_models import ConfigModel, NapCatWSModel, BotModel, RedisModel, DockerModel
|
||||
from .utils.logger import logger
|
||||
from .utils.logger import logger, ModuleLogger
|
||||
from .utils.exceptions import ConfigError, ConfigNotFoundError, ConfigValidationError
|
||||
from .utils.error_codes import ErrorCode, create_error_response
|
||||
|
||||
|
||||
class Config:
|
||||
@@ -24,36 +26,66 @@ class Config:
|
||||
"""
|
||||
self.path = Path(file_path)
|
||||
self._model: ConfigModel
|
||||
# 创建模块专用日志记录器
|
||||
self.logger = ModuleLogger("ConfigLoader")
|
||||
self.load()
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
加载并验证配置文件
|
||||
|
||||
:raises FileNotFoundError: 如果配置文件不存在
|
||||
:raises ValidationError: 如果配置格式不正确
|
||||
:raises ConfigNotFoundError: 如果配置文件不存在
|
||||
:raises ConfigValidationError: 如果配置格式不正确
|
||||
:raises ConfigError: 如果加载配置时发生其他错误
|
||||
"""
|
||||
if not self.path.exists():
|
||||
logger.error(f"配置文件 {self.path} 未找到!")
|
||||
raise FileNotFoundError(f"配置文件 {self.path} 未找到!")
|
||||
error = ConfigNotFoundError(message=f"配置文件 {self.path} 未找到!")
|
||||
self.logger.error(f"配置加载失败: {error.message}")
|
||||
self.logger.log_custom_exception(error)
|
||||
raise error
|
||||
|
||||
try:
|
||||
logger.info(f"正在从 {self.path} 加载配置...")
|
||||
self.logger.info(f"正在从 {self.path} 加载配置...")
|
||||
with open(self.path, "rb") as f:
|
||||
raw_config = tomllib.load(f)
|
||||
|
||||
self._model = ConfigModel(**raw_config)
|
||||
logger.success("配置加载并验证成功!")
|
||||
self.logger.success("配置加载并验证成功!")
|
||||
|
||||
except ValidationError as e:
|
||||
logger.error("配置验证失败,请检查 `config.toml` 文件中的以下错误:")
|
||||
error_details = []
|
||||
for error in e.errors():
|
||||
field = " -> ".join(map(str, error["loc"]))
|
||||
logger.error(f" - 字段 '{field}': {error['msg']}")
|
||||
raise
|
||||
error_msg = f"字段 '{field}': {error['msg']}"
|
||||
error_details.append(error_msg)
|
||||
|
||||
validation_error = ConfigValidationError(
|
||||
message="配置验证失败",
|
||||
original_error=e
|
||||
)
|
||||
|
||||
self.logger.error("配置验证失败,请检查 `config.toml` 文件中的以下错误:")
|
||||
for detail in error_details:
|
||||
self.logger.error(f" - {detail}")
|
||||
|
||||
self.logger.log_custom_exception(validation_error)
|
||||
raise validation_error
|
||||
except tomllib.TOMLDecodeError as e:
|
||||
error = ConfigError(
|
||||
message=f"TOML解析错误: {str(e)}",
|
||||
original_error=e
|
||||
)
|
||||
self.logger.error(f"加载配置文件时发生TOML解析错误: {error.message}")
|
||||
self.logger.log_custom_exception(error)
|
||||
raise error
|
||||
except Exception as e:
|
||||
logger.exception(f"加载配置文件时发生未知错误: {e}")
|
||||
raise
|
||||
error = ConfigError(
|
||||
message=f"加载配置文件时发生未知错误: {str(e)}",
|
||||
original_error=e
|
||||
)
|
||||
self.logger.exception(f"加载配置文件时发生未知错误: {error.message}")
|
||||
self.logger.log_custom_exception(error)
|
||||
raise error
|
||||
|
||||
# 通过属性访问配置
|
||||
@property
|
||||
|
||||
@@ -10,8 +10,9 @@ import sys
|
||||
from typing import Set
|
||||
from .command_manager import CommandManager
|
||||
|
||||
from ..utils.exceptions import SyncHandlerError
|
||||
from ..utils.logger import logger
|
||||
from ..utils.exceptions import SyncHandlerError, PluginError, PluginLoadError, PluginReloadError, PluginNotFoundError
|
||||
from ..utils.logger import logger, ModuleLogger
|
||||
from ..utils.error_codes import ErrorCode, create_error_response
|
||||
|
||||
# 确保logger在模块级别可见
|
||||
__all__ = ['PluginManager', 'logger']
|
||||
@@ -29,6 +30,8 @@ class PluginManager:
|
||||
"""
|
||||
self.command_manager = command_manager
|
||||
self.loaded_plugins: Set[str] = set()
|
||||
# 创建模块专用日志记录器
|
||||
self.logger = ModuleLogger("PluginManager")
|
||||
|
||||
def load_all_plugins(self) -> None:
|
||||
"""
|
||||
@@ -45,10 +48,10 @@ class PluginManager:
|
||||
package_name = "plugins"
|
||||
|
||||
if not os.path.exists(plugin_dir):
|
||||
logger.error(f"插件目录不存在: {plugin_dir}")
|
||||
self.logger.error(f"插件目录不存在: {plugin_dir}")
|
||||
return
|
||||
|
||||
logger.info(f"正在从 {package_name} 加载插件 (路径: {plugin_dir})...")
|
||||
self.logger.info(f"正在从 {package_name} 加载插件 (路径: {plugin_dir})...")
|
||||
|
||||
for _, module_name, is_pkg in pkgutil.iter_modules([plugin_dir]):
|
||||
full_module_name = f"{package_name}.{module_name}"
|
||||
@@ -70,23 +73,38 @@ class PluginManager:
|
||||
self.loaded_plugins.add(full_module_name)
|
||||
|
||||
type_str = "包" if is_pkg else "文件"
|
||||
logger.success(f" [{type_str}] 成功{action}: {module_name}")
|
||||
self.logger.success(f" [{type_str}] 成功{action}: {module_name}")
|
||||
except SyncHandlerError as e:
|
||||
logger.error(f" 插件 {module_name} 加载失败: {e} (跳过此插件)")
|
||||
except Exception as e:
|
||||
logger.exception(
|
||||
f" 加载插件 {module_name} 失败: {e}"
|
||||
error = PluginLoadError(
|
||||
plugin_name=module_name,
|
||||
message=f"同步处理器错误: {str(e)}",
|
||||
original_error=e
|
||||
)
|
||||
self.logger.error(f" 插件 {module_name} 加载失败: {error.message} (跳过此插件)")
|
||||
self.logger.log_custom_exception(error)
|
||||
except Exception as e:
|
||||
error = PluginLoadError(
|
||||
plugin_name=module_name,
|
||||
message=f"未知错误: {str(e)}",
|
||||
original_error=e
|
||||
)
|
||||
self.logger.exception(f" 加载插件 {module_name} 失败: {error.message}")
|
||||
self.logger.log_custom_exception(error)
|
||||
|
||||
def reload_plugin(self, full_module_name: str) -> None:
|
||||
"""
|
||||
精确重载单个插件。
|
||||
"""
|
||||
if full_module_name not in self.loaded_plugins:
|
||||
logger.warning(f"尝试重载一个未被加载的插件: {full_module_name},将按首次加载处理。")
|
||||
self.logger.warning(f"尝试重载一个未被加载的插件: {full_module_name},将按首次加载处理。")
|
||||
|
||||
if full_module_name not in sys.modules:
|
||||
logger.error(f"重载失败: 模块 {full_module_name} 未在 sys.modules 中找到。")
|
||||
error = PluginNotFoundError(
|
||||
plugin_name=full_module_name,
|
||||
message="模块未在sys.modules中找到"
|
||||
)
|
||||
self.logger.error(f"重载失败: {error.message}")
|
||||
self.logger.log_custom_exception(error)
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -97,6 +115,20 @@ class PluginManager:
|
||||
meta = getattr(module, "__plugin_meta__")
|
||||
self.command_manager.plugins[full_module_name] = meta
|
||||
|
||||
logger.success(f"插件 {full_module_name} 已成功重载。")
|
||||
self.logger.success(f"插件 {full_module_name} 已成功重载。")
|
||||
except SyncHandlerError as e:
|
||||
error = PluginReloadError(
|
||||
plugin_name=full_module_name,
|
||||
message=f"同步处理器错误: {str(e)}",
|
||||
original_error=e
|
||||
)
|
||||
self.logger.error(f"重载插件 {full_module_name} 失败: {error.message}")
|
||||
self.logger.log_custom_exception(error)
|
||||
except Exception as e:
|
||||
logger.exception(f"重载插件 {full_module_name} 时发生错误: {e}")
|
||||
error = PluginReloadError(
|
||||
plugin_name=full_module_name,
|
||||
message=f"未知错误: {str(e)}",
|
||||
original_error=e
|
||||
)
|
||||
self.logger.exception(f"重载插件 {full_module_name} 时发生错误: {error.message}")
|
||||
self.logger.log_custom_exception(error)
|
||||
|
||||
234
core/utils/error_codes.py
Normal file
234
core/utils/error_codes.py
Normal file
@@ -0,0 +1,234 @@
|
||||
"""
|
||||
错误码和统一响应格式模块
|
||||
|
||||
该模块定义了项目中使用的错误码和统一的错误响应格式,确保所有模块返回一致的错误信息。
|
||||
"""
|
||||
|
||||
# 错误码定义
|
||||
class ErrorCode:
|
||||
"""
|
||||
错误码枚举类,包含所有系统错误码的定义。
|
||||
|
||||
错误码规则:
|
||||
- 1xxx: 系统级错误
|
||||
- 2xxx: WebSocket相关错误
|
||||
- 3xxx: 插件相关错误
|
||||
- 4xxx: 配置相关错误
|
||||
- 5xxx: 权限相关错误
|
||||
- 6xxx: 命令相关错误
|
||||
- 7xxx: Redis相关错误
|
||||
- 8xxx: 浏览器管理器相关错误
|
||||
- 9xxx: 代码执行相关错误
|
||||
"""
|
||||
# 系统级错误
|
||||
SUCCESS = 0 # 成功
|
||||
UNKNOWN_ERROR = 1000 # 未知错误
|
||||
INVALID_PARAMETER = 1001 # 参数无效
|
||||
DATABASE_ERROR = 1002 # 数据库错误
|
||||
NETWORK_ERROR = 1003 # 网络错误
|
||||
TIMEOUT_ERROR = 1004 # 超时错误
|
||||
RESOURCE_EXHAUSTED = 1005 # 资源耗尽
|
||||
|
||||
# WebSocket相关错误
|
||||
WS_CONNECTION_FAILED = 2000 # WebSocket连接失败
|
||||
WS_AUTH_FAILED = 2001 # WebSocket认证失败
|
||||
WS_DISCONNECTED = 2002 # WebSocket已断开
|
||||
WS_MESSAGE_ERROR = 2003 # WebSocket消息错误
|
||||
|
||||
# 插件相关错误
|
||||
PLUGIN_LOAD_FAILED = 3000 # 插件加载失败
|
||||
PLUGIN_RELOAD_FAILED = 3001 # 插件重载失败
|
||||
PLUGIN_NOT_FOUND = 3002 # 插件未找到
|
||||
PLUGIN_INVALID = 3003 # 插件无效
|
||||
PLUGIN_DEPENDENCY_ERROR = 3004 # 插件依赖错误
|
||||
|
||||
# 配置相关错误
|
||||
CONFIG_NOT_FOUND = 4000 # 配置文件未找到
|
||||
CONFIG_PARSE_ERROR = 4001 # 配置解析错误
|
||||
CONFIG_VALIDATION_ERROR = 4002 # 配置验证错误
|
||||
CONFIG_KEY_NOT_FOUND = 4003 # 配置项未找到
|
||||
|
||||
# 权限相关错误
|
||||
PERMISSION_DENIED = 5000 # 权限不足
|
||||
NOT_ADMIN = 5001 # 不是管理员
|
||||
USER_BANNED = 5002 # 用户已被禁止
|
||||
|
||||
# 命令相关错误
|
||||
COMMAND_NOT_FOUND = 6000 # 命令未找到
|
||||
COMMAND_PARAM_ERROR = 6001 # 命令参数错误
|
||||
COMMAND_EXECUTE_ERROR = 6002 # 命令执行错误
|
||||
COMMAND_TIMEOUT = 6003 # 命令执行超时
|
||||
|
||||
# Redis相关错误
|
||||
REDIS_CONNECTION_FAILED = 7000 # Redis连接失败
|
||||
REDIS_OPERATION_ERROR = 7001 # Redis操作错误
|
||||
|
||||
# 浏览器管理器相关错误
|
||||
BROWSER_INIT_FAILED = 8000 # 浏览器初始化失败
|
||||
BROWSER_POOL_ERROR = 8001 # 浏览器池错误
|
||||
BROWSER_OPERATION_ERROR = 8002 # 浏览器操作错误
|
||||
|
||||
# 代码执行相关错误
|
||||
CODE_EXECUTE_ERROR = 9000 # 代码执行错误
|
||||
CODE_SECURITY_ERROR = 9001 # 代码安全错误
|
||||
|
||||
|
||||
# 错误码到错误消息的映射
|
||||
ERROR_MESSAGES = {
|
||||
# 系统级错误
|
||||
ErrorCode.SUCCESS: "操作成功",
|
||||
ErrorCode.UNKNOWN_ERROR: "未知错误",
|
||||
ErrorCode.INVALID_PARAMETER: "参数无效",
|
||||
ErrorCode.DATABASE_ERROR: "数据库错误",
|
||||
ErrorCode.NETWORK_ERROR: "网络错误",
|
||||
ErrorCode.TIMEOUT_ERROR: "操作超时",
|
||||
ErrorCode.RESOURCE_EXHAUSTED: "资源耗尽",
|
||||
|
||||
# WebSocket相关错误
|
||||
ErrorCode.WS_CONNECTION_FAILED: "WebSocket连接失败",
|
||||
ErrorCode.WS_AUTH_FAILED: "WebSocket认证失败",
|
||||
ErrorCode.WS_DISCONNECTED: "WebSocket已断开连接",
|
||||
ErrorCode.WS_MESSAGE_ERROR: "WebSocket消息格式错误",
|
||||
|
||||
# 插件相关错误
|
||||
ErrorCode.PLUGIN_LOAD_FAILED: "插件加载失败",
|
||||
ErrorCode.PLUGIN_RELOAD_FAILED: "插件重载失败",
|
||||
ErrorCode.PLUGIN_NOT_FOUND: "插件未找到",
|
||||
ErrorCode.PLUGIN_INVALID: "插件无效",
|
||||
ErrorCode.PLUGIN_DEPENDENCY_ERROR: "插件依赖错误",
|
||||
|
||||
# 配置相关错误
|
||||
ErrorCode.CONFIG_NOT_FOUND: "配置文件未找到",
|
||||
ErrorCode.CONFIG_PARSE_ERROR: "配置文件解析错误",
|
||||
ErrorCode.CONFIG_VALIDATION_ERROR: "配置验证失败",
|
||||
ErrorCode.CONFIG_KEY_NOT_FOUND: "配置项未找到",
|
||||
|
||||
# 权限相关错误
|
||||
ErrorCode.PERMISSION_DENIED: "权限不足",
|
||||
ErrorCode.NOT_ADMIN: "需要管理员权限",
|
||||
ErrorCode.USER_BANNED: "用户已被禁止操作",
|
||||
|
||||
# 命令相关错误
|
||||
ErrorCode.COMMAND_NOT_FOUND: "命令未找到",
|
||||
ErrorCode.COMMAND_PARAM_ERROR: "命令参数错误",
|
||||
ErrorCode.COMMAND_EXECUTE_ERROR: "命令执行错误",
|
||||
ErrorCode.COMMAND_TIMEOUT: "命令执行超时",
|
||||
|
||||
# Redis相关错误
|
||||
ErrorCode.REDIS_CONNECTION_FAILED: "Redis连接失败",
|
||||
ErrorCode.REDIS_OPERATION_ERROR: "Redis操作错误",
|
||||
|
||||
# 浏览器管理器相关错误
|
||||
ErrorCode.BROWSER_INIT_FAILED: "浏览器初始化失败",
|
||||
ErrorCode.BROWSER_POOL_ERROR: "浏览器池错误",
|
||||
ErrorCode.BROWSER_OPERATION_ERROR: "浏览器操作错误",
|
||||
|
||||
# 代码执行相关错误
|
||||
ErrorCode.CODE_EXECUTE_ERROR: "代码执行错误",
|
||||
ErrorCode.CODE_SECURITY_ERROR: "代码存在安全风险",
|
||||
}
|
||||
|
||||
|
||||
def get_error_message(code: int) -> str:
|
||||
"""
|
||||
根据错误码获取错误消息
|
||||
|
||||
Args:
|
||||
code: 错误码
|
||||
|
||||
Returns:
|
||||
str: 错误消息
|
||||
"""
|
||||
return ERROR_MESSAGES.get(code, ERROR_MESSAGES[ErrorCode.UNKNOWN_ERROR])
|
||||
|
||||
|
||||
def create_error_response(code: int, message: str = None, data: dict = None, request_id: str = None) -> dict:
|
||||
"""
|
||||
创建统一格式的错误响应
|
||||
|
||||
Args:
|
||||
code: 错误码
|
||||
message: 错误消息(可选,如果未提供则使用默认消息)
|
||||
data: 附加数据(可选)
|
||||
request_id: 请求ID(可选,用于追踪请求)
|
||||
|
||||
Returns:
|
||||
dict: 统一格式的错误响应
|
||||
"""
|
||||
error_message = message if message is not None else get_error_message(code)
|
||||
|
||||
response = {
|
||||
"code": code,
|
||||
"message": error_message,
|
||||
"success": code == ErrorCode.SUCCESS,
|
||||
}
|
||||
|
||||
if data is not None:
|
||||
response["data"] = data
|
||||
|
||||
if request_id is not None:
|
||||
response["request_id"] = request_id
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def exception_to_error_response(exception: Exception, code: int = None, request_id: str = None) -> dict:
|
||||
"""
|
||||
将异常对象转换为统一格式的错误响应
|
||||
|
||||
Args:
|
||||
exception: 异常对象
|
||||
code: 错误码(可选,如果未提供则根据异常类型自动推断)
|
||||
request_id: 请求ID(可选,用于追踪请求)
|
||||
|
||||
Returns:
|
||||
dict: 统一格式的错误响应
|
||||
"""
|
||||
# 从自定义异常类中提取错误码
|
||||
if hasattr(exception, "code") and exception.code is not None:
|
||||
code = exception.code
|
||||
|
||||
# 如果仍未找到错误码,则根据异常类型推断
|
||||
if code is None:
|
||||
from .exceptions import (
|
||||
WebSocketError, PluginError, ConfigError, PermissionError,
|
||||
CommandError, RedisError, BrowserManagerError, CodeExecutionError
|
||||
)
|
||||
|
||||
if isinstance(exception, WebSocketError):
|
||||
code = ErrorCode.WS_CONNECTION_FAILED
|
||||
elif isinstance(exception, PluginError):
|
||||
code = ErrorCode.PLUGIN_LOAD_FAILED
|
||||
elif isinstance(exception, ConfigError):
|
||||
code = ErrorCode.CONFIG_PARSE_ERROR
|
||||
elif isinstance(exception, PermissionError):
|
||||
code = ErrorCode.PERMISSION_DENIED
|
||||
elif isinstance(exception, CommandError):
|
||||
code = ErrorCode.COMMAND_EXECUTE_ERROR
|
||||
elif isinstance(exception, RedisError):
|
||||
code = ErrorCode.REDIS_OPERATION_ERROR
|
||||
elif isinstance(exception, BrowserManagerError):
|
||||
code = ErrorCode.BROWSER_OPERATION_ERROR
|
||||
elif isinstance(exception, CodeExecutionError):
|
||||
code = ErrorCode.CODE_EXECUTE_ERROR
|
||||
else:
|
||||
code = ErrorCode.UNKNOWN_ERROR
|
||||
|
||||
# 获取错误消息
|
||||
message = str(exception)
|
||||
|
||||
# 如果异常有原始错误,也包含在响应中
|
||||
data = None
|
||||
if hasattr(exception, "original_error") and exception.original_error is not None:
|
||||
data = {"original_error": str(exception.original_error)}
|
||||
|
||||
return create_error_response(code, message, data, request_id)
|
||||
|
||||
|
||||
# 将错误码导出以便其他模块使用
|
||||
__all__ = [
|
||||
"ErrorCode",
|
||||
"get_error_message",
|
||||
"create_error_response",
|
||||
"exception_to_error_response"
|
||||
]
|
||||
@@ -1,5 +1,7 @@
|
||||
"""
|
||||
自定义异常模块
|
||||
|
||||
该模块定义了项目中使用的各种自定义异常类,用于提供更精确、更友好的错误提示。
|
||||
"""
|
||||
|
||||
class SyncHandlerError(Exception):
|
||||
@@ -7,3 +9,213 @@ class SyncHandlerError(Exception):
|
||||
当尝试注册同步函数作为异步事件处理器时抛出此异常。
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class WebSocketError(Exception):
|
||||
"""
|
||||
WebSocket相关错误的基类。
|
||||
|
||||
Args:
|
||||
message: 错误消息
|
||||
code: 错误代码(可选)
|
||||
original_error: 原始异常对象(可选)
|
||||
"""
|
||||
def __init__(self, message, code=None, original_error=None):
|
||||
self.message = message
|
||||
self.code = code
|
||||
self.original_error = original_error
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class WebSocketConnectionError(WebSocketError):
|
||||
"""
|
||||
WebSocket连接失败时抛出此异常。
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class WebSocketAuthenticationError(WebSocketError):
|
||||
"""
|
||||
WebSocket认证失败时抛出此异常。
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PluginError(Exception):
|
||||
"""
|
||||
插件相关错误的基类。
|
||||
|
||||
Args:
|
||||
plugin_name: 插件名称
|
||||
message: 错误消息
|
||||
original_error: 原始异常对象(可选)
|
||||
"""
|
||||
def __init__(self, plugin_name, message, original_error=None):
|
||||
self.plugin_name = plugin_name
|
||||
self.message = message
|
||||
self.original_error = original_error
|
||||
super().__init__(f"插件 {plugin_name}: {message}")
|
||||
|
||||
|
||||
class PluginLoadError(PluginError):
|
||||
"""
|
||||
插件加载失败时抛出此异常。
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PluginReloadError(PluginError):
|
||||
"""
|
||||
插件重载失败时抛出此异常。
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PluginNotFoundError(PluginError):
|
||||
"""
|
||||
找不到指定插件时抛出此异常。
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ConfigError(Exception):
|
||||
"""
|
||||
配置相关错误的基类。
|
||||
|
||||
Args:
|
||||
section: 配置部分名称
|
||||
key: 配置项名称
|
||||
message: 错误消息
|
||||
"""
|
||||
def __init__(self, section=None, key=None, message=None):
|
||||
self.section = section
|
||||
self.key = key
|
||||
self.message = message
|
||||
|
||||
if section and key and message:
|
||||
super().__init__(f"配置错误 [{section}.{key}]: {message}")
|
||||
elif section and message:
|
||||
super().__init__(f"配置错误 [{section}]: {message}")
|
||||
else:
|
||||
super().__init__(message or "配置错误")
|
||||
|
||||
|
||||
class ConfigNotFoundError(ConfigError):
|
||||
"""
|
||||
配置文件不存在时抛出此异常。
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ConfigValidationError(ConfigError):
|
||||
"""
|
||||
配置验证失败时抛出此异常。
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PermissionError(Exception):
|
||||
"""
|
||||
权限相关错误的基类。
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
operation: 操作名称
|
||||
message: 错误消息
|
||||
"""
|
||||
def __init__(self, user_id=None, operation=None, message=None):
|
||||
self.user_id = user_id
|
||||
self.operation = operation
|
||||
self.message = message
|
||||
|
||||
if user_id and operation and message:
|
||||
super().__init__(f"权限错误 [用户 {user_id}]: 无权限执行操作 {operation} - {message}")
|
||||
elif user_id and operation:
|
||||
super().__init__(f"权限错误 [用户 {user_id}]: 无权限执行操作 {operation}")
|
||||
else:
|
||||
super().__init__(message or "权限错误")
|
||||
|
||||
|
||||
class CommandError(Exception):
|
||||
"""
|
||||
命令处理相关错误的基类。
|
||||
|
||||
Args:
|
||||
command: 命令名称
|
||||
message: 错误消息
|
||||
original_error: 原始异常对象(可选)
|
||||
"""
|
||||
def __init__(self, command=None, message=None, original_error=None):
|
||||
self.command = command
|
||||
self.message = message
|
||||
self.original_error = original_error
|
||||
|
||||
if command and message:
|
||||
super().__init__(f"命令错误 [{command}]: {message}")
|
||||
else:
|
||||
super().__init__(message or "命令错误")
|
||||
|
||||
|
||||
class CommandNotFoundError(CommandError):
|
||||
"""
|
||||
找不到指定命令时抛出此异常。
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class CommandParameterError(CommandError):
|
||||
"""
|
||||
命令参数错误时抛出此异常。
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RedisError(Exception):
|
||||
"""
|
||||
Redis相关错误的基类。
|
||||
|
||||
Args:
|
||||
message: 错误消息
|
||||
original_error: 原始异常对象(可选)
|
||||
"""
|
||||
def __init__(self, message, original_error=None):
|
||||
self.message = message
|
||||
self.original_error = original_error
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class BrowserManagerError(Exception):
|
||||
"""
|
||||
浏览器管理器相关错误的基类。
|
||||
|
||||
Args:
|
||||
message: 错误消息
|
||||
original_error: 原始异常对象(可选)
|
||||
"""
|
||||
def __init__(self, message, original_error=None):
|
||||
self.message = message
|
||||
self.original_error = original_error
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class BrowserPoolError(BrowserManagerError):
|
||||
"""
|
||||
浏览器池相关错误时抛出此异常。
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class CodeExecutionError(Exception):
|
||||
"""
|
||||
代码执行相关错误的基类。
|
||||
|
||||
Args:
|
||||
message: 错误消息
|
||||
code: 执行的代码(可选)
|
||||
original_error: 原始异常对象(可选)
|
||||
"""
|
||||
def __init__(self, message, code=None, original_error=None):
|
||||
self.message = message
|
||||
self.code = code
|
||||
self.original_error = original_error
|
||||
super().__init__(message)
|
||||
|
||||
@@ -4,25 +4,40 @@
|
||||
该模块负责初始化和配置 loguru 日志记录器,为整个应用程序提供统一的日志记录接口。
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
from loguru import logger
|
||||
|
||||
# 定义日志格式
|
||||
# 定义日志格式,添加进程ID和线程ID作为上下文信息
|
||||
LOG_FORMAT = (
|
||||
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
||||
"<level>{level: <8}</level> | "
|
||||
"<magenta>PID {process} TID {thread}</magenta> | "
|
||||
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
|
||||
"<level>{message}</level>"
|
||||
)
|
||||
|
||||
# 开发环境日志格式(更详细)
|
||||
DEBUG_LOG_FORMAT = (
|
||||
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
||||
"<level>{level: <8}</level> | "
|
||||
"<magenta>PID {process} TID {thread}</magenta> | "
|
||||
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | "
|
||||
"<yellow>Module: {module}</yellow> | "
|
||||
"<level>{message}</level>"
|
||||
)
|
||||
|
||||
# 移除 loguru 默认的处理器
|
||||
logger.remove()
|
||||
|
||||
# 获取当前环境
|
||||
ENVIRONMENT = os.getenv("NEOBOT_ENV", "development")
|
||||
|
||||
# 添加控制台输出处理器
|
||||
logger.add(
|
||||
sys.stderr,
|
||||
level="INFO",
|
||||
format=LOG_FORMAT,
|
||||
level="INFO" if ENVIRONMENT == "production" else "DEBUG",
|
||||
format=LOG_FORMAT if ENVIRONMENT == "production" else DEBUG_LOG_FORMAT,
|
||||
colorize=True,
|
||||
enqueue=True # 异步写入
|
||||
)
|
||||
@@ -36,7 +51,7 @@ log_file_path = log_dir / "{time:YYYY-MM-DD}.log"
|
||||
logger.add(
|
||||
log_file_path,
|
||||
level="DEBUG",
|
||||
format=LOG_FORMAT,
|
||||
format=DEBUG_LOG_FORMAT,
|
||||
colorize=False,
|
||||
rotation="00:00", # 每天午夜创建新文件
|
||||
retention="7 days", # 保留最近 7 天的日志
|
||||
@@ -46,5 +61,77 @@ logger.add(
|
||||
diagnose=True # 添加异常诊断信息
|
||||
)
|
||||
|
||||
# 导出配置好的 logger
|
||||
__all__ = ["logger"]
|
||||
# 为自定义异常添加专门的日志记录方法
|
||||
def log_exception(exc, module_name="unknown", level="error"):
|
||||
"""
|
||||
记录自定义异常的详细信息
|
||||
|
||||
Args:
|
||||
exc: 异常对象
|
||||
module_name: 模块名称(可选)
|
||||
level: 日志级别(可选,默认为 "error")
|
||||
"""
|
||||
log_func = getattr(logger, level)
|
||||
log_func(f"模块 {module_name} 发生异常: {exc}")
|
||||
|
||||
# 如果异常对象有原始异常,也记录原始异常信息
|
||||
if hasattr(exc, "original_error") and exc.original_error:
|
||||
log_func(f"原始异常: {exc.original_error}")
|
||||
|
||||
# 如果是配置错误,记录配置相关信息
|
||||
if hasattr(exc, "section") and hasattr(exc, "key"):
|
||||
log_func(f"配置信息: 部分={exc.section}, 键={exc.key}")
|
||||
|
||||
# 如果是插件错误,记录插件名称
|
||||
if hasattr(exc, "plugin_name"):
|
||||
log_func(f"插件名称: {exc.plugin_name}")
|
||||
|
||||
# 如果是命令错误,记录命令名称
|
||||
if hasattr(exc, "command"):
|
||||
log_func(f"命令名称: {exc.command}")
|
||||
|
||||
# 如果是权限错误,记录用户ID和操作
|
||||
if hasattr(exc, "user_id") and hasattr(exc, "operation"):
|
||||
log_func(f"权限信息: 用户ID={exc.user_id}, 操作={exc.operation}")
|
||||
|
||||
# 为不同模块提供日志工具
|
||||
class ModuleLogger:
|
||||
"""
|
||||
模块专用日志记录器
|
||||
|
||||
Args:
|
||||
module_name: 模块名称
|
||||
"""
|
||||
def __init__(self, module_name):
|
||||
self.module_name = module_name
|
||||
|
||||
def debug(self, message):
|
||||
logger.debug(f"[{self.module_name}] {message}")
|
||||
|
||||
def info(self, message):
|
||||
logger.info(f"[{self.module_name}] {message}")
|
||||
|
||||
def success(self, message):
|
||||
logger.success(f"[{self.module_name}] {message}")
|
||||
|
||||
def warning(self, message):
|
||||
logger.warning(f"[{self.module_name}] {message}")
|
||||
|
||||
def error(self, message):
|
||||
logger.error(f"[{self.module_name}] {message}")
|
||||
|
||||
def exception(self, message, exc_info=True):
|
||||
logger.exception(f"[{self.module_name}] {message}", exc_info=exc_info)
|
||||
|
||||
def log_custom_exception(self, exc, level="error"):
|
||||
"""
|
||||
记录自定义异常
|
||||
|
||||
Args:
|
||||
exc: 异常对象
|
||||
level: 日志级别
|
||||
"""
|
||||
log_exception(exc, self.module_name, level)
|
||||
|
||||
# 导出配置好的 logger 和工具函数
|
||||
__all__ = ["logger", "log_exception", "ModuleLogger"]
|
||||
|
||||
194
docs/core-concepts/error-handling.md
Normal file
194
docs/core-concepts/error-handling.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# 错误处理机制
|
||||
|
||||
NEO Bot 采用了统一的错误处理机制,确保在各种异常情况下提供清晰、一致的错误信息。本文档将介绍系统的错误处理架构、错误码定义和使用方法。
|
||||
|
||||
## 1. 错误处理架构
|
||||
|
||||
### 1.1 自定义异常体系
|
||||
|
||||
系统定义了一套完整的自定义异常类体系,覆盖了各种常见的错误场景:
|
||||
|
||||
- **WebSocket 相关错误**:`WebSocketError`、`WebSocketConnectionError`、`WebSocketAuthenticationError`
|
||||
- **插件相关错误**:`PluginError`、`PluginLoadError`、`PluginReloadError`、`PluginNotFoundError`
|
||||
- **配置相关错误**:`ConfigError`、`ConfigNotFoundError`、`ConfigValidationError`
|
||||
- **权限相关错误**:`PermissionError`
|
||||
- **命令相关错误**:`CommandError`、`CommandNotFoundError`、`CommandParameterError`
|
||||
- **Redis 相关错误**:`RedisError`
|
||||
- **浏览器管理器相关错误**:`BrowserManagerError`、`BrowserPoolError`
|
||||
- **代码执行相关错误**:`CodeExecutionError`
|
||||
|
||||
所有自定义异常类都位于 `core.utils.exceptions` 模块中。
|
||||
|
||||
### 1.2 统一的错误码系统
|
||||
|
||||
系统使用统一的错误码来标识不同类型的错误,错误码规则如下:
|
||||
|
||||
- `1xxx`:系统级错误
|
||||
- `2xxx`:WebSocket 相关错误
|
||||
- `3xxx`:插件相关错误
|
||||
- `4xxx`:配置相关错误
|
||||
- `5xxx`:权限相关错误
|
||||
- `6xxx`:命令相关错误
|
||||
- `7xxx`:Redis 相关错误
|
||||
- `8xxx`:浏览器管理器相关错误
|
||||
- `9xxx`:代码执行相关错误
|
||||
|
||||
完整的错误码定义可以在 `core.utils.error_codes` 模块的 `ErrorCode` 类中找到。
|
||||
|
||||
### 1.3 统一的错误响应格式
|
||||
|
||||
系统提供了统一的错误响应格式,确保所有模块返回一致的错误信息:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 错误代码,
|
||||
"message": 错误消息,
|
||||
"success": false,
|
||||
"data": 附加数据(可选),
|
||||
"request_id": 请求ID(可选)
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 日志记录增强
|
||||
|
||||
### 2.1 模块专用日志记录器
|
||||
|
||||
系统提供了 `ModuleLogger` 类,用于创建模块专用的日志记录器,自动添加模块标识:
|
||||
|
||||
```python
|
||||
from core.utils.logger import ModuleLogger
|
||||
|
||||
# 创建模块专用日志记录器
|
||||
logger = ModuleLogger("MyModule")
|
||||
|
||||
# 使用日志记录器
|
||||
logger.info("模块初始化完成")
|
||||
logger.error("发生错误")
|
||||
logger.exception("发生异常")
|
||||
```
|
||||
|
||||
### 2.2 异常详情记录
|
||||
|
||||
系统提供了 `log_exception` 函数,用于记录自定义异常的详细信息:
|
||||
|
||||
```python
|
||||
from core.utils.logger import log_exception
|
||||
from core.utils.exceptions import PluginError
|
||||
|
||||
try:
|
||||
# 代码逻辑
|
||||
raise PluginError("插件加载失败", plugin_name="test_plugin")
|
||||
except Exception as e:
|
||||
log_exception(e, module_name="PluginManager")
|
||||
```
|
||||
|
||||
## 3. 核心模块的错误处理
|
||||
|
||||
### 3.1 WebSocket 模块
|
||||
|
||||
WebSocket 模块使用自定义异常类处理各种连接错误:
|
||||
|
||||
- `WebSocketConnectionError`:连接失败
|
||||
- `WebSocketAuthenticationError`:认证失败
|
||||
- `WebSocketError`:其他 WebSocket 相关错误
|
||||
|
||||
### 3.2 插件管理器模块
|
||||
|
||||
插件管理器模块使用自定义异常类处理各种插件操作错误:
|
||||
|
||||
- `PluginLoadError`:插件加载失败
|
||||
- `PluginReloadError`:插件重载失败
|
||||
- `PluginNotFoundError`:插件未找到
|
||||
|
||||
### 3.3 配置加载器模块
|
||||
|
||||
配置加载器模块使用自定义异常类处理各种配置加载错误:
|
||||
|
||||
- `ConfigNotFoundError`:配置文件未找到
|
||||
- `ConfigValidationError`:配置验证失败
|
||||
- `ConfigError`:其他配置相关错误
|
||||
|
||||
## 4. 全局异常捕获
|
||||
|
||||
系统在主程序入口添加了全局异常捕获机制,确保所有未处理的异常都能被捕获并提供友好的错误信息:
|
||||
|
||||
- 捕获并记录所有未处理的异常
|
||||
- 生成统一格式的错误响应
|
||||
- 根据错误类型给出不同的排查建议
|
||||
- 提供详细的错误信息和日志记录位置
|
||||
|
||||
## 5. 如何在插件中使用错误处理
|
||||
|
||||
### 5.1 抛出自定义异常
|
||||
|
||||
在插件中,您可以使用系统提供的自定义异常类来抛出更精确的错误:
|
||||
|
||||
```python
|
||||
from core.utils.exceptions import CommandParameterError
|
||||
from core.utils.logger import ModuleLogger
|
||||
|
||||
logger = ModuleLogger("MyPlugin")
|
||||
|
||||
@matcher.command("test")
|
||||
async def test_command(bot, event, args):
|
||||
if len(args) < 1:
|
||||
raise CommandParameterError("test", "缺少必要参数")
|
||||
|
||||
# 命令逻辑
|
||||
```
|
||||
|
||||
### 5.2 捕获并处理异常
|
||||
|
||||
在插件中,您可以捕获并处理异常,提供更友好的错误信息:
|
||||
|
||||
```python
|
||||
from core.utils.exceptions import PluginError
|
||||
from core.utils.logger import ModuleLogger
|
||||
|
||||
logger = ModuleLogger("MyPlugin")
|
||||
|
||||
@matcher.command("test")
|
||||
async def test_command(bot, event, args):
|
||||
try:
|
||||
# 可能抛出异常的代码
|
||||
result = await some_operation()
|
||||
await bot.send(event, f"操作结果: {result}")
|
||||
except PluginError as e:
|
||||
logger.error(f"插件操作失败: {e}")
|
||||
await bot.send(event, f"操作失败: {e.message}")
|
||||
except Exception as e:
|
||||
logger.exception(f"发生未知错误: {e}")
|
||||
await bot.send(event, "操作失败,请检查日志获取详细信息")
|
||||
```
|
||||
|
||||
## 6. 错误排查建议
|
||||
|
||||
### 6.1 WebSocket 错误
|
||||
|
||||
- 检查 WebSocket 服务是否正在运行
|
||||
- 检查配置文件中的 WebSocket 地址和令牌是否正确
|
||||
- 检查网络连接是否正常
|
||||
|
||||
### 6.2 插件错误
|
||||
|
||||
- 检查插件目录是否存在
|
||||
- 检查插件文件是否有语法错误
|
||||
- 检查插件是否符合插件开发规范
|
||||
|
||||
### 6.3 配置错误
|
||||
|
||||
- 检查配置文件 config.toml 是否存在
|
||||
- 检查配置文件格式是否正确
|
||||
- 检查所有必填配置项是否都已设置
|
||||
|
||||
## 7. 总结
|
||||
|
||||
NEO Bot 的错误处理机制提供了:
|
||||
|
||||
- 完整的自定义异常类体系
|
||||
- 统一的错误码系统
|
||||
- 一致的错误响应格式
|
||||
- 增强的日志记录功能
|
||||
- 全局异常捕获和友好提示
|
||||
|
||||
这些功能确保了系统在各种异常情况下都能提供清晰、一致的错误信息,便于开发和维护。
|
||||
@@ -17,6 +17,7 @@
|
||||
* [性能优化](./core-concepts/performance.md): 页面池、JIT、Mypyc...
|
||||
* [消息流](./core-concepts/event-flow.md): 看看一条消息从被接收到被回复是如何运行的
|
||||
* [核心](./core-concepts/singleton-managers.md): `matcher`, `browser_manager`... 认识这些核心模块。
|
||||
* [错误处理](./core-concepts/error-handling.md): 了解系统的错误处理机制和错误码定义。
|
||||
|
||||
### 3. API 参考
|
||||
* [API 总览](./api/index.md): 所有 API 的快速导航和调用方式
|
||||
|
||||
57
main.py
57
main.py
@@ -128,8 +128,8 @@ class PluginReloadHandler(FileSystemEventHandler):
|
||||
if not src_path.endswith(".py"):
|
||||
return
|
||||
|
||||
# 过滤掉一些临时文件
|
||||
if "__pycache__" in src_path or not src_path.startswith(PLUGIN_DIR):
|
||||
# 过滤掉一些临时文件和__init__.py文件
|
||||
if "__pycache__" in src_path or not src_path.startswith(PLUGIN_DIR) or os.path.basename(src_path) == "__init__.py":
|
||||
return
|
||||
|
||||
# 简单的防抖动
|
||||
@@ -216,4 +216,55 @@ async def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
"""
|
||||
程序主入口,添加全局异常捕获和友好提示
|
||||
"""
|
||||
from core.utils.error_codes import exception_to_error_response
|
||||
from core.utils.logger import ModuleLogger
|
||||
|
||||
# 创建主程序日志记录器
|
||||
main_logger = ModuleLogger("Main")
|
||||
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
main_logger.info("程序已被用户中断")
|
||||
exit(0)
|
||||
except Exception as e:
|
||||
main_logger.exception("程序发生未处理的全局异常")
|
||||
|
||||
# 生成统一的错误响应
|
||||
error_response = exception_to_error_response(e)
|
||||
|
||||
# 打印友好的错误提示
|
||||
print("\n" + "=" * 60)
|
||||
print("程序发生错误,请检查以下信息:")
|
||||
print("=" * 60)
|
||||
print(f"错误代码: {error_response['code']}")
|
||||
print(f"错误信息: {error_response['message']}")
|
||||
print("=" * 60)
|
||||
print("详细错误信息已记录到日志文件中")
|
||||
print("请检查 logs 目录下的日志文件以获取更多调试信息")
|
||||
print("=" * 60)
|
||||
|
||||
# 根据错误类型给出不同的建议
|
||||
if hasattr(e, "original_error") and e.original_error:
|
||||
print(f"\n原始错误: {e.original_error}")
|
||||
|
||||
if "WebSocket" in str(type(e).__name__):
|
||||
print("\n建议检查:")
|
||||
print("1. WebSocket 服务是否正在运行")
|
||||
print("2. 配置文件中的 WebSocket 地址和令牌是否正确")
|
||||
print("3. 网络连接是否正常")
|
||||
elif "Config" in str(type(e).__name__):
|
||||
print("\n建议检查:")
|
||||
print("1. 配置文件 config.toml 是否存在")
|
||||
print("2. 配置文件格式是否正确")
|
||||
print("3. 所有必填配置项是否都已设置")
|
||||
elif "Plugin" in str(type(e).__name__):
|
||||
print("\n建议检查:")
|
||||
print("1. 插件目录是否存在")
|
||||
print("2. 插件文件是否有语法错误")
|
||||
print("3. 插件是否符合插件开发规范")
|
||||
|
||||
exit(1)
|
||||
|
||||
@@ -391,54 +391,50 @@ else:
|
||||
# 要编译的模块列表
|
||||
# 注意:Mypyc 对动态特性支持有限,只选择计算密集或类型明确的模块
|
||||
MODULES = [
|
||||
# 工具模块
|
||||
'core/utils/json_utils.py', # JSON 处理
|
||||
'core/utils/executor.py', # 代码执行引擎
|
||||
'core/utils/singleton.py', # 单例模式基类
|
||||
'core/utils/exceptions.py', # 自定义异常
|
||||
'core/utils/logger.py', # 日志模块
|
||||
# 工具模块 - 高频使用
|
||||
'core/utils/json_utils.py', # JSON 处理 - 高频使用
|
||||
'core/utils/executor.py', # 代码执行引擎 - 高频使用
|
||||
'core/utils/exceptions.py', # 自定义异常 - 基础组件
|
||||
'core/utils/performance.py', # 性能监控工具 - 重要组件
|
||||
'core/utils/logger.py', # 日志模块 - 高频使用
|
||||
'core/utils/singleton.py', # 单例模式 - 基础组件
|
||||
|
||||
# 核心管理模块
|
||||
'core/managers/command_manager.py', # 指令匹配和分发
|
||||
'core/managers/admin_manager.py', # 管理员管理
|
||||
'core/managers/permission_manager.py', # 权限管理
|
||||
'core/managers/plugin_manager.py', # 插件管理器
|
||||
'core/managers/redis_manager.py', # Redis 管理器
|
||||
'core/managers/image_manager.py', # 图片管理器
|
||||
# 核心管理模块 - 高频使用
|
||||
# 'core/managers/command_manager.py', # 指令匹配和分发 - 包含动态特性,不适合编译
|
||||
# 'core/managers/admin_manager.py', # 管理员管理 - 包含动态特性,不适合编译
|
||||
# 'core/managers/permission_manager.py', # 权限管理 - 包含动态特性,不适合编译
|
||||
# 'core/managers/plugin_manager.py', # 插件管理器 - 包含动态特性,不适合编译
|
||||
# 'core/managers/redis_manager.py', # Redis 管理器 - 包含动态特性,不适合编译
|
||||
# 'core/managers/image_manager.py', # 图片管理器 - 包含动态特性,不适合编译
|
||||
|
||||
# 核心基础模块
|
||||
'core/ws.py', # WebSocket 核心
|
||||
'core/bot.py', # Bot 核心抽象
|
||||
'core/config_loader.py', # 配置加载
|
||||
'core/config_models.py', # 配置模型
|
||||
'core/permission.py', # 权限枚举
|
||||
# 核心基础模块 - 高频使用
|
||||
'core/ws.py', # WebSocket 核心 - 核心通信,被10个文件引用
|
||||
# 'core/bot.py', # Bot 核心抽象 - 使用多重继承,不适合编译
|
||||
'core/config_loader.py', # 配置加载 - 启动必需,被7个文件引用
|
||||
# 'core/config_models.py', # 配置模型 - 包含复杂类型定义,不适合编译
|
||||
# 'core/permission.py', # 权限枚举 - 包含动态属性,不适合编译
|
||||
|
||||
# API 模块 - 注意:这些类会被 Bot 类多继承使用
|
||||
# 因此不适合编译,否则会导致 "multiple bases have instance lay-out conflict" 错误
|
||||
# 'core/api/base.py', # API 基础类
|
||||
# 'core/api/account.py', # 账号相关 API
|
||||
# 'core/api/friend.py', # 好友相关 API
|
||||
# 'core/api/group.py', # 群组相关 API
|
||||
# 'core/api/media.py', # 媒体相关 API
|
||||
# 'core/api/message.py', # 消息相关 API
|
||||
# 数据模型 - 高频使用
|
||||
'models/message.py', # 消息段模型 - 高频消息处理
|
||||
'models/sender.py', # 发送者模型 - 高频消息处理
|
||||
'models/objects.py', # API 响应数据模型 - 高频数据处理
|
||||
|
||||
# 数据模型(适合编译的高频使用数据类)
|
||||
'models/message.py', # 消息段模型
|
||||
'models/sender.py', # 发送者模型
|
||||
'models/objects.py', # API 响应数据模型
|
||||
# 事件处理相关 - 高频使用
|
||||
'core/handlers/event_handler.py', # 事件处理器 - 核心事件处理
|
||||
|
||||
# 事件处理相关
|
||||
'core/handlers/event_handler.py', # 事件处理器
|
||||
# 事件模型 - 高频使用,但包含dataclass,可能有编译问题,暂时排除
|
||||
# 'models/events/message.py', # 消息事件 - 最高频事件类型
|
||||
# 'models/events/notice.py', # 通知事件 - 高频事件类型
|
||||
# 'models/events/request.py', # 请求事件 - 高频事件类型
|
||||
# 'models/events/meta.py', # 元事件 - 高频事件类型
|
||||
|
||||
# 注意:以下文件不适合编译
|
||||
# - 主程序文件(main.py)
|
||||
# - 测试文件(tests/目录)
|
||||
# - 插件文件(plugins/目录)
|
||||
# - 编译脚本(compile_machine_code.py等)
|
||||
# - 临时文件(scratch_files/目录)
|
||||
# - 抽象基类(models/events/base.py)
|
||||
# - 事件工厂(models/events/factory.py)
|
||||
# - 编译(脚本compile_machine_code.py等)
|
||||
# - 包含复杂动态特性的文件
|
||||
# - API 基础类(由于多重继承问题)
|
||||
]
|
||||
|
||||
def list_compiled_modules():
|
||||
@@ -452,7 +448,7 @@ def list_compiled_modules():
|
||||
compiled_files.extend(glob.glob(f'**/*{ext}', recursive=True))
|
||||
|
||||
# 过滤掉虚拟环境中的文件
|
||||
compiled_files = [f for f in compiled_files if 'venv' not in f]
|
||||
compiled_files = [f for f in compiled_files if 'venv' not in f and '.venv' not in f]
|
||||
|
||||
if compiled_files:
|
||||
for f in sorted(compiled_files):
|
||||
@@ -473,7 +469,7 @@ def clean_compiled_files():
|
||||
compiled_files.extend(glob.glob(f'**/*{ext}', recursive=True))
|
||||
|
||||
# 过滤掉虚拟环境中的文件
|
||||
compiled_files = [f for f in compiled_files if 'venv' not in f]
|
||||
compiled_files = [f for f in compiled_files if 'venv' not in f and '.venv' not in f]
|
||||
|
||||
if compiled_files:
|
||||
for f in sorted(compiled_files):
|
||||
@@ -495,7 +491,8 @@ def clean_compiled_files():
|
||||
|
||||
def get_platform_specific_module_name(module_path):
|
||||
"""获取平台特定的模块文件名"""
|
||||
module_name = module_path.replace('.py', '')
|
||||
# 只获取模块名,不包含路径
|
||||
module_name = os.path.basename(module_path).replace('.py', '')
|
||||
return f"{module_name}.{BUILD_PREFIX}{EXTENSION}"
|
||||
|
||||
def compile_module(module_path):
|
||||
@@ -504,16 +501,34 @@ def compile_module(module_path):
|
||||
|
||||
try:
|
||||
# 直接调用 mypyc 命令行工具
|
||||
# 使用二进制模式捕获输出以避免编码问题
|
||||
result = subprocess.run(
|
||||
[sys.executable, '-m', 'mypyc', module_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
# 解码输出时处理可能的编码错误
|
||||
try:
|
||||
stdout_text = result.stdout.decode('utf-8', errors='replace')
|
||||
stderr_text = result.stderr.decode('utf-8', errors='replace')
|
||||
except AttributeError:
|
||||
# 如果已经是字符串(Python 3.7+),则直接使用
|
||||
stdout_text = result.stdout
|
||||
stderr_text = result.stderr
|
||||
|
||||
# 获取平台特定的模块名
|
||||
platform_module = get_platform_specific_module_name(module_path)
|
||||
mypyc_platform_module = platform_module.replace(EXTENSION, f'__mypyc{EXTENSION}')
|
||||
# 获取模块名和目录
|
||||
module_dir = os.path.dirname(module_path)
|
||||
module_basename = os.path.basename(module_path).replace('.py', '')
|
||||
|
||||
# 生成平台特定的模块文件名(仅文件名,不含路径)
|
||||
platform_module_name = f"{module_basename}.{BUILD_PREFIX}{EXTENSION}"
|
||||
mypyc_platform_module_name = f"{module_basename}__mypyc.{BUILD_PREFIX}{EXTENSION}"
|
||||
|
||||
# 完整路径构造
|
||||
platform_module = os.path.join(module_dir, platform_module_name)
|
||||
mypyc_platform_module = os.path.join(module_dir, mypyc_platform_module_name)
|
||||
|
||||
# 检查编译产物是否在当前目录
|
||||
if os.path.exists(platform_module):
|
||||
@@ -521,33 +536,44 @@ def compile_module(module_path):
|
||||
return True
|
||||
else:
|
||||
# 检查 build 目录中是否有编译产物
|
||||
build_module_path = os.path.join(BUILD_PATH, platform_module)
|
||||
build_mypyc_path = os.path.join(BUILD_PATH, mypyc_platform_module)
|
||||
build_module_path = os.path.join(BUILD_PATH, module_dir, platform_module_name)
|
||||
build_mypyc_path = os.path.join(BUILD_PATH, module_dir, mypyc_platform_module_name)
|
||||
|
||||
if os.path.exists(build_module_path):
|
||||
# 如果在 build 目录中,复制到正确位置
|
||||
os.makedirs(os.path.dirname(platform_module), exist_ok=True)
|
||||
os.makedirs(module_dir, exist_ok=True)
|
||||
shutil.copy2(build_module_path, platform_module)
|
||||
shutil.copy2(build_mypyc_path, mypyc_platform_module)
|
||||
if os.path.exists(build_mypyc_path):
|
||||
shutil.copy2(build_mypyc_path, mypyc_platform_module)
|
||||
print(f" ✓ 编译成功(已从 build 目录复制): {platform_module}")
|
||||
return True
|
||||
else:
|
||||
print(f" ✗ 编译失败:找不到编译产物")
|
||||
print(" ✗ 编译失败:找不到编译产物")
|
||||
if result.stdout:
|
||||
print(f" 编译输出:{result.stdout[:500]}...")
|
||||
print(f" 编译输出:{stdout_text[:500]}...")
|
||||
if result.stderr:
|
||||
print(f" 错误信息:{result.stderr[:500]}...")
|
||||
print(f" 错误信息:{stderr_text[:500]}...")
|
||||
return False
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f" ✗ 编译失败,退出码: {e.returncode}")
|
||||
if e.stdout:
|
||||
print(f" 编译输出:{e.stdout[:500]}...")
|
||||
if e.stderr:
|
||||
print(f" 错误信息:{e.stderr[:500]}...")
|
||||
if hasattr(e, 'stdout') and e.stdout:
|
||||
try:
|
||||
stdout_text = e.stdout.decode('utf-8', errors='replace') if isinstance(e.stdout, bytes) else e.stdout
|
||||
print(f" 编译输出:{stdout_text[:500]}...")
|
||||
except Exception:
|
||||
print(f" 编译输出:{str(e.stdout)[:500]}...")
|
||||
if hasattr(e, 'stderr') and e.stderr:
|
||||
try:
|
||||
stderr_text = e.stderr.decode('utf-8', errors='replace') if isinstance(e.stderr, bytes) else e.stderr
|
||||
print(f" 错误信息:{stderr_text[:500]}...")
|
||||
except Exception:
|
||||
print(f" 错误信息:{str(e.stderr)[:500]}...")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" ✗ 编译失败,意外错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def should_skip_module(module_path):
|
||||
@@ -560,9 +586,20 @@ def should_skip_module(module_path):
|
||||
if 'from abc import ABC' in content or 'from abc import abstractmethod' in content:
|
||||
return True, "包含抽象基类,不适合编译"
|
||||
|
||||
# 检查是否包含动态特性
|
||||
if 'eval(' in content or 'exec(' in content or 'getattr(' in content or 'setattr(' in content:
|
||||
return True, "包含动态特性,不适合编译"
|
||||
# 检查是否包含危险的动态特性
|
||||
# 注意:我们允许基本的动态特性,如getattr,但对于eval、exec等危险操作仍然阻止
|
||||
if ('eval(' in content or 'exec(' in content or
|
||||
'compile(' in content):
|
||||
return True, "包含危险动态特性,不适合编译"
|
||||
|
||||
# 检查是否包含复杂的动态属性访问
|
||||
if ('__dict__' in content or '__class__' in content or
|
||||
'__module__' in content or '__bases__' in content):
|
||||
return True, "包含复杂动态特性,不适合编译"
|
||||
|
||||
# 检查是否包含复杂的动态属性访问
|
||||
if '.__dict__' in content or '.__class__' in content:
|
||||
return True, "包含复杂动态特性,不适合编译"
|
||||
|
||||
return False, ""
|
||||
except Exception as e:
|
||||
@@ -575,29 +612,41 @@ def compile_all_modules():
|
||||
|
||||
# 验证模块文件是否存在并检查是否适合编译
|
||||
valid_modules = []
|
||||
skipped_modules = []
|
||||
|
||||
for module_path in MODULES:
|
||||
if os.path.exists(module_path):
|
||||
should_skip, reason = should_skip_module(module_path)
|
||||
if should_skip:
|
||||
print(f"跳过: {module_path} ({reason})")
|
||||
skipped_modules.append((module_path, reason))
|
||||
else:
|
||||
valid_modules.append(module_path)
|
||||
else:
|
||||
print(f"警告: 模块 {module_path} 不存在,将被跳过")
|
||||
|
||||
print(f"\n有效模块: {len(valid_modules)}, 跳过模块: {len(skipped_modules)}")
|
||||
|
||||
if not valid_modules:
|
||||
print("错误: 没有有效的模块可编译")
|
||||
return False
|
||||
|
||||
# 编译模块
|
||||
success_count = 0
|
||||
failed_modules = []
|
||||
|
||||
for module_path in valid_modules:
|
||||
if compile_module(module_path):
|
||||
success_count += 1
|
||||
else:
|
||||
failed_modules.append(module_path)
|
||||
|
||||
print(f"\n" + "=" * 60)
|
||||
print("\n" + "=" * 60)
|
||||
print(f"编译完成: {success_count}/{len(valid_modules)} 个模块成功")
|
||||
|
||||
if failed_modules:
|
||||
print(f"失败模块: {failed_modules}")
|
||||
|
||||
if success_count == len(valid_modules):
|
||||
print("✓ 所有模块编译成功")
|
||||
return True
|
||||
@@ -633,6 +682,7 @@ def main():
|
||||
else:
|
||||
compile_all_modules()
|
||||
print("\n使用 --list 选项查看已编译的模块")
|
||||
print("使用 --clean 选项清理编译文件")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -135,11 +135,15 @@ def test_reload_plugin_error(plugin_manager):
|
||||
plugin_manager.loaded_plugins.add(full_name)
|
||||
mock_module = MagicMock()
|
||||
|
||||
# 创建一个模拟的logger,直接替换plugin_manager实例的logger属性
|
||||
mock_logger = MagicMock()
|
||||
plugin_manager.logger = mock_logger
|
||||
|
||||
with patch.dict("sys.modules", {full_name: mock_module}), \
|
||||
patch("importlib.reload", side_effect=Exception("Reload error")), \
|
||||
patch("core.managers.plugin_manager.logger") as mock_logger:
|
||||
patch("importlib.reload", side_effect=Exception("Reload error")):
|
||||
|
||||
# Should not raise exception
|
||||
plugin_manager.reload_plugin(full_name)
|
||||
mock_logger.exception.assert_called()
|
||||
mock_logger.log_custom_exception.assert_called()
|
||||
|
||||
|
||||
@@ -37,14 +37,18 @@ class TestWS:
|
||||
|
||||
# 测试 WebSocket 未初始化的情况
|
||||
result = await ws.call_api("send_group_msg", {"group_id": 123456, "message": "test"})
|
||||
assert result == {"status": "failed", "msg": "websocket not initialized"}
|
||||
assert result["code"] == 2002 # WS_DISCONNECTED
|
||||
assert result["success"] == False
|
||||
assert "WebSocket未初始化" in result["message"]
|
||||
|
||||
# 测试 WebSocket 已初始化但未连接的情况
|
||||
mock_ws = MagicMock()
|
||||
mock_ws.state = None
|
||||
ws.ws = mock_ws
|
||||
result = await ws.call_api("send_group_msg", {"group_id": 123456, "message": "test"})
|
||||
assert result == {"status": "failed", "msg": "websocket is not open"}
|
||||
assert result["code"] == 2002 # WS_DISCONNECTED
|
||||
assert result["success"] == False
|
||||
assert "WebSocket连接未打开" in result["message"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_on_event_bot_initialization(self):
|
||||
|
||||
Reference in New Issue
Block a user