feat: 实现统一的错误处理机制和增强日志系统
添加错误码定义和统一响应格式 增强日志记录功能,支持模块专用日志记录器 实现全局异常捕获和友好错误提示 更新文档说明错误处理机制
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()
|
||||||
@@ -8,7 +8,9 @@ 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
|
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:
|
class Config:
|
||||||
@@ -24,36 +26,66 @@ class Config:
|
|||||||
"""
|
"""
|
||||||
self.path = Path(file_path)
|
self.path = Path(file_path)
|
||||||
self._model: ConfigModel
|
self._model: ConfigModel
|
||||||
|
# 创建模块专用日志记录器
|
||||||
|
self.logger = ModuleLogger("ConfigLoader")
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""
|
"""
|
||||||
加载并验证配置文件
|
加载并验证配置文件
|
||||||
|
|
||||||
:raises FileNotFoundError: 如果配置文件不存在
|
:raises ConfigNotFoundError: 如果配置文件不存在
|
||||||
:raises ValidationError: 如果配置格式不正确
|
:raises ConfigValidationError: 如果配置格式不正确
|
||||||
|
:raises ConfigError: 如果加载配置时发生其他错误
|
||||||
"""
|
"""
|
||||||
if not self.path.exists():
|
if not self.path.exists():
|
||||||
logger.error(f"配置文件 {self.path} 未找到!")
|
error = ConfigNotFoundError(message=f"配置文件 {self.path} 未找到!")
|
||||||
raise FileNotFoundError(f"配置文件 {self.path} 未找到!")
|
self.logger.error(f"配置加载失败: {error.message}")
|
||||||
|
self.logger.log_custom_exception(error)
|
||||||
|
raise error
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.info(f"正在从 {self.path} 加载配置...")
|
self.logger.info(f"正在从 {self.path} 加载配置...")
|
||||||
with open(self.path, "rb") as f:
|
with open(self.path, "rb") as f:
|
||||||
raw_config = tomllib.load(f)
|
raw_config = tomllib.load(f)
|
||||||
|
|
||||||
self._model = ConfigModel(**raw_config)
|
self._model = ConfigModel(**raw_config)
|
||||||
logger.success("配置加载并验证成功!")
|
self.logger.success("配置加载并验证成功!")
|
||||||
|
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
logger.error("配置验证失败,请检查 `config.toml` 文件中的以下错误:")
|
error_details = []
|
||||||
for error in e.errors():
|
for error in e.errors():
|
||||||
field = " -> ".join(map(str, error["loc"]))
|
field = " -> ".join(map(str, error["loc"]))
|
||||||
logger.error(f" - 字段 '{field}': {error['msg']}")
|
error_msg = f"字段 '{field}': {error['msg']}"
|
||||||
raise
|
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:
|
except Exception as e:
|
||||||
logger.exception(f"加载配置文件时发生未知错误: {e}")
|
error = ConfigError(
|
||||||
raise
|
message=f"加载配置文件时发生未知错误: {str(e)}",
|
||||||
|
original_error=e
|
||||||
|
)
|
||||||
|
self.logger.exception(f"加载配置文件时发生未知错误: {error.message}")
|
||||||
|
self.logger.log_custom_exception(error)
|
||||||
|
raise error
|
||||||
|
|
||||||
# 通过属性访问配置
|
# 通过属性访问配置
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ import sys
|
|||||||
from typing import Set
|
from typing import Set
|
||||||
from .command_manager import CommandManager
|
from .command_manager import CommandManager
|
||||||
|
|
||||||
from ..utils.exceptions import SyncHandlerError
|
from ..utils.exceptions import SyncHandlerError, PluginError, PluginLoadError, PluginReloadError, PluginNotFoundError
|
||||||
from ..utils.logger import logger
|
from ..utils.logger import logger, ModuleLogger
|
||||||
|
from ..utils.error_codes import ErrorCode, create_error_response
|
||||||
|
|
||||||
# 确保logger在模块级别可见
|
# 确保logger在模块级别可见
|
||||||
__all__ = ['PluginManager', 'logger']
|
__all__ = ['PluginManager', 'logger']
|
||||||
@@ -29,6 +30,8 @@ class PluginManager:
|
|||||||
"""
|
"""
|
||||||
self.command_manager = command_manager
|
self.command_manager = command_manager
|
||||||
self.loaded_plugins: Set[str] = set()
|
self.loaded_plugins: Set[str] = set()
|
||||||
|
# 创建模块专用日志记录器
|
||||||
|
self.logger = ModuleLogger("PluginManager")
|
||||||
|
|
||||||
def load_all_plugins(self) -> None:
|
def load_all_plugins(self) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -45,10 +48,10 @@ class PluginManager:
|
|||||||
package_name = "plugins"
|
package_name = "plugins"
|
||||||
|
|
||||||
if not os.path.exists(plugin_dir):
|
if not os.path.exists(plugin_dir):
|
||||||
logger.error(f"插件目录不存在: {plugin_dir}")
|
self.logger.error(f"插件目录不存在: {plugin_dir}")
|
||||||
return
|
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]):
|
for _, module_name, is_pkg in pkgutil.iter_modules([plugin_dir]):
|
||||||
full_module_name = f"{package_name}.{module_name}"
|
full_module_name = f"{package_name}.{module_name}"
|
||||||
@@ -70,23 +73,38 @@ class PluginManager:
|
|||||||
self.loaded_plugins.add(full_module_name)
|
self.loaded_plugins.add(full_module_name)
|
||||||
|
|
||||||
type_str = "包" if is_pkg else "文件"
|
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:
|
except SyncHandlerError as e:
|
||||||
logger.error(f" 插件 {module_name} 加载失败: {e} (跳过此插件)")
|
error = PluginLoadError(
|
||||||
except Exception as e:
|
plugin_name=module_name,
|
||||||
logger.exception(
|
message=f"同步处理器错误: {str(e)}",
|
||||||
f" 加载插件 {module_name} 失败: {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:
|
def reload_plugin(self, full_module_name: str) -> None:
|
||||||
"""
|
"""
|
||||||
精确重载单个插件。
|
精确重载单个插件。
|
||||||
"""
|
"""
|
||||||
if full_module_name not in self.loaded_plugins:
|
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:
|
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
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -97,6 +115,20 @@ class PluginManager:
|
|||||||
meta = getattr(module, "__plugin_meta__")
|
meta = getattr(module, "__plugin_meta__")
|
||||||
self.command_manager.plugins[full_module_name] = 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:
|
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)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# 导出核心工具
|
# 导出核心工具
|
||||||
from .logger import logger
|
from .logger import logger, ModuleLogger, log_exception
|
||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .json_utils import *
|
from .json_utils import *
|
||||||
from .singleton import singleton
|
from .singleton import singleton
|
||||||
@@ -20,9 +20,12 @@ from .performance import (
|
|||||||
performance_stats,
|
performance_stats,
|
||||||
global_stats
|
global_stats
|
||||||
)
|
)
|
||||||
|
from .error_codes import ErrorCode, get_error_message, create_error_response, exception_to_error_response
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'logger',
|
'logger',
|
||||||
|
'ModuleLogger',
|
||||||
|
'log_exception',
|
||||||
'timeit',
|
'timeit',
|
||||||
'profile',
|
'profile',
|
||||||
'aprofile',
|
'aprofile',
|
||||||
@@ -34,5 +37,9 @@ __all__ = [
|
|||||||
'global_stats',
|
'global_stats',
|
||||||
'run_in_thread_pool',
|
'run_in_thread_pool',
|
||||||
'initialize_executor',
|
'initialize_executor',
|
||||||
'singleton'
|
'singleton',
|
||||||
|
'ErrorCode',
|
||||||
|
'get_error_message',
|
||||||
|
'create_error_response',
|
||||||
|
'exception_to_error_response'
|
||||||
]
|
]
|
||||||
|
|||||||
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):
|
class SyncHandlerError(Exception):
|
||||||
@@ -7,3 +9,213 @@ class SyncHandlerError(Exception):
|
|||||||
当尝试注册同步函数作为异步事件处理器时抛出此异常。
|
当尝试注册同步函数作为异步事件处理器时抛出此异常。
|
||||||
"""
|
"""
|
||||||
pass
|
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 日志记录器,为整个应用程序提供统一的日志记录接口。
|
该模块负责初始化和配置 loguru 日志记录器,为整个应用程序提供统一的日志记录接口。
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
# 定义日志格式
|
# 定义日志格式,添加进程ID和线程ID作为上下文信息
|
||||||
LOG_FORMAT = (
|
LOG_FORMAT = (
|
||||||
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
||||||
"<level>{level: <8}</level> | "
|
"<level>{level: <8}</level> | "
|
||||||
|
"<magenta>PID {process} TID {thread}</magenta> | "
|
||||||
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
|
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
|
||||||
"<level>{message}</level>"
|
"<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 默认的处理器
|
# 移除 loguru 默认的处理器
|
||||||
logger.remove()
|
logger.remove()
|
||||||
|
|
||||||
|
# 获取当前环境
|
||||||
|
ENVIRONMENT = os.getenv("NEOBOT_ENV", "development")
|
||||||
|
|
||||||
# 添加控制台输出处理器
|
# 添加控制台输出处理器
|
||||||
logger.add(
|
logger.add(
|
||||||
sys.stderr,
|
sys.stderr,
|
||||||
level="INFO",
|
level="INFO" if ENVIRONMENT == "production" else "DEBUG",
|
||||||
format=LOG_FORMAT,
|
format=LOG_FORMAT if ENVIRONMENT == "production" else DEBUG_LOG_FORMAT,
|
||||||
colorize=True,
|
colorize=True,
|
||||||
enqueue=True # 异步写入
|
enqueue=True # 异步写入
|
||||||
)
|
)
|
||||||
@@ -36,7 +51,7 @@ log_file_path = log_dir / "{time:YYYY-MM-DD}.log"
|
|||||||
logger.add(
|
logger.add(
|
||||||
log_file_path,
|
log_file_path,
|
||||||
level="DEBUG",
|
level="DEBUG",
|
||||||
format=LOG_FORMAT,
|
format=DEBUG_LOG_FORMAT,
|
||||||
colorize=False,
|
colorize=False,
|
||||||
rotation="00:00", # 每天午夜创建新文件
|
rotation="00:00", # 每天午夜创建新文件
|
||||||
retention="7 days", # 保留最近 7 天的日志
|
retention="7 days", # 保留最近 7 天的日志
|
||||||
@@ -46,5 +61,77 @@ logger.add(
|
|||||||
diagnose=True # 添加异常诊断信息
|
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"]
|
||||||
|
|||||||
115
core/ws.py
115
core/ws.py
@@ -25,7 +25,11 @@ from .bot import Bot
|
|||||||
from .config_loader import global_config
|
from .config_loader import global_config
|
||||||
from .managers.command_manager import matcher
|
from .managers.command_manager import matcher
|
||||||
from .utils.executor import CodeExecutor
|
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:
|
class WS:
|
||||||
@@ -45,11 +49,15 @@ class WS:
|
|||||||
self.token = cfg.token
|
self.token = cfg.token
|
||||||
self.reconnect_interval = cfg.reconnect_interval
|
self.reconnect_interval = cfg.reconnect_interval
|
||||||
|
|
||||||
|
# 初始化状态
|
||||||
self.ws: Optional[WebSocketClientProtocol] = None
|
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.bot: Bot | None = None
|
||||||
self.self_id: int | None = None
|
self.self_id: int | None = None
|
||||||
self.code_executor = code_executor
|
self.code_executor = code_executor
|
||||||
|
|
||||||
|
# 创建模块专用日志记录器
|
||||||
|
self.logger = ModuleLogger("WebSocket")
|
||||||
|
|
||||||
async def connect(self) -> None:
|
async def connect(self) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -62,24 +70,43 @@ class WS:
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
logger.info(f"正在尝试连接至 NapCat: {self.url}")
|
self.logger.info(f"正在尝试连接至 NapCat: {self.url}")
|
||||||
async with websockets.connect(
|
async with websockets.connect(
|
||||||
self.url, additional_headers=headers
|
self.url, additional_headers=headers
|
||||||
) as websocket_raw:
|
) as websocket_raw:
|
||||||
websocket = cast(WebSocketClientProtocol, websocket_raw)
|
websocket = cast(WebSocketClientProtocol, websocket_raw)
|
||||||
self.ws = websocket
|
self.ws = websocket
|
||||||
logger.success("连接成功!")
|
self.logger.success("连接成功!")
|
||||||
await self._listen_loop(websocket)
|
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 (
|
except (
|
||||||
websockets.exceptions.ConnectionClosed,
|
websockets.exceptions.ConnectionClosed,
|
||||||
ConnectionRefusedError,
|
ConnectionRefusedError,
|
||||||
) as e:
|
) 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:
|
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)
|
await asyncio.sleep(self.reconnect_interval)
|
||||||
|
|
||||||
async def _listen_loop(self, websocket_connection: WebSocketClientProtocol) -> None:
|
async def _listen_loop(self, websocket_connection: WebSocketClientProtocol) -> None:
|
||||||
@@ -111,8 +138,22 @@ class WS:
|
|||||||
# 使用 create_task 异步执行,避免阻塞 WebSocket 接收循环
|
# 使用 create_task 异步执行,避免阻塞 WebSocket 接收循环
|
||||||
asyncio.create_task(self.on_event(data))
|
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:
|
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:
|
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'):
|
if self.bot is None and hasattr(event, 'self_id'):
|
||||||
self.self_id = event.self_id
|
self.self_id = event.self_id
|
||||||
self.bot = Bot(self)
|
self.bot = Bot(self)
|
||||||
logger.success(f"Bot 实例初始化完成: self_id={self.self_id}")
|
self.logger.success(f"Bot 实例初始化完成: self_id={self.self_id}")
|
||||||
|
|
||||||
# 将代码执行器注入到 Bot 和执行器自身
|
# 将代码执行器注入到 Bot 和执行器自身
|
||||||
if self.code_executor:
|
if self.code_executor:
|
||||||
self.bot.code_executor = self.code_executor
|
self.bot.code_executor = self.code_executor
|
||||||
self.code_executor.bot = self.bot
|
self.code_executor.bot = self.bot
|
||||||
logger.info("代码执行器已成功注入 Bot 实例。")
|
self.logger.info("代码执行器已成功注入 Bot 实例。")
|
||||||
|
|
||||||
# 如果 bot 尚未初始化,则不处理后续事件
|
# 如果 bot 尚未初始化,则不处理后续事件
|
||||||
if self.bot is None:
|
if self.bot is None:
|
||||||
logger.warning("Bot 尚未初始化,跳过事件处理。")
|
self.logger.warning("Bot 尚未初始化,跳过事件处理。")
|
||||||
return
|
return
|
||||||
|
|
||||||
event.bot = self.bot # 注入 Bot 实例
|
event.bot = self.bot # 注入 Bot 实例
|
||||||
@@ -157,23 +198,28 @@ class WS:
|
|||||||
message_type = getattr(event, "message_type", "Unknown")
|
message_type = getattr(event, "message_type", "Unknown")
|
||||||
user_id = getattr(event, "user_id", "Unknown")
|
user_id = getattr(event, "user_id", "Unknown")
|
||||||
raw_message = getattr(event, "raw_message", "")
|
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":
|
elif event.post_type == "notice":
|
||||||
notice_type = getattr(event, "notice_type", "Unknown")
|
notice_type = getattr(event, "notice_type", "Unknown")
|
||||||
logger.info(f"[通知] {notice_type}")
|
self.logger.info(f"[通知] {notice_type}")
|
||||||
elif event.post_type == "request":
|
elif event.post_type == "request":
|
||||||
request_type = getattr(event, "request_type", "Unknown")
|
request_type = getattr(event, "request_type", "Unknown")
|
||||||
logger.info(f"[请求] {request_type}")
|
self.logger.info(f"[请求] {request_type}")
|
||||||
elif event.post_type == "meta_event":
|
elif event.post_type == "meta_event":
|
||||||
meta_event_type = getattr(event, "meta_event_type", "Unknown")
|
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)
|
await matcher.handle_event(self.bot, event)
|
||||||
|
|
||||||
except Exception as e:
|
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]:
|
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:
|
if not self.ws:
|
||||||
logger.error("调用 API 失败: WebSocket 未初始化")
|
self.logger.error("调用 API 失败: WebSocket 未初始化")
|
||||||
return {"status": "failed", "msg": "websocket not initialized"}
|
return create_error_response(
|
||||||
|
code=ErrorCode.WS_DISCONNECTED,
|
||||||
|
message="WebSocket未初始化",
|
||||||
|
data={"action": action, "params": params}
|
||||||
|
)
|
||||||
|
|
||||||
from websockets.protocol import State
|
from websockets.protocol import State
|
||||||
|
|
||||||
if getattr(self.ws, "state", None) is not State.OPEN:
|
if getattr(self.ws, "state", None) is not State.OPEN:
|
||||||
logger.error("调用 API 失败: WebSocket 连接未打开")
|
self.logger.error("调用 API 失败: WebSocket 连接未打开")
|
||||||
return {"status": "failed", "msg": "websocket is not open"}
|
return create_error_response(
|
||||||
|
code=ErrorCode.WS_DISCONNECTED,
|
||||||
|
message="WebSocket连接未打开",
|
||||||
|
data={"action": action, "params": params}
|
||||||
|
)
|
||||||
|
|
||||||
echo_id = str(uuid.uuid4())
|
echo_id = str(uuid.uuid4())
|
||||||
payload = {"action": action, "params": params or {}, "echo": echo_id}
|
payload = {"action": action, "params": params or {}, "echo": echo_id}
|
||||||
@@ -207,12 +261,23 @@ class WS:
|
|||||||
future = loop.create_future()
|
future = loop.create_future()
|
||||||
self._pending_requests[echo_id] = future
|
self._pending_requests[echo_id] = future
|
||||||
|
|
||||||
await self.ws.send(json.dumps(payload))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
await self.ws.send(json.dumps(payload))
|
||||||
return await asyncio.wait_for(future, timeout=30.0)
|
return await asyncio.wait_for(future, timeout=30.0)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
self._pending_requests.pop(echo_id, None)
|
self._pending_requests.pop(echo_id, None)
|
||||||
logger.warning(f"API 调用超时: action={action}, params={params}")
|
self.logger.warning(f"API 调用超时: action={action}, params={params}")
|
||||||
return {"status": "failed", "retcode": -1, "msg": "api timeout"}
|
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}
|
||||||
|
)
|
||||||
|
|
||||||
|
|||||||
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/performance.md): 页面池、JIT、Mypyc...
|
||||||
* [消息流](./core-concepts/event-flow.md): 看看一条消息从被接收到被回复是如何运行的
|
* [消息流](./core-concepts/event-flow.md): 看看一条消息从被接收到被回复是如何运行的
|
||||||
* [核心](./core-concepts/singleton-managers.md): `matcher`, `browser_manager`... 认识这些核心模块。
|
* [核心](./core-concepts/singleton-managers.md): `matcher`, `browser_manager`... 认识这些核心模块。
|
||||||
|
* [错误处理](./core-concepts/error-handling.md): 了解系统的错误处理机制和错误码定义。
|
||||||
|
|
||||||
### 3. API 参考
|
### 3. API 参考
|
||||||
* [API 总览](./api/index.md): 所有 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"):
|
if not src_path.endswith(".py"):
|
||||||
return
|
return
|
||||||
|
|
||||||
# 过滤掉一些临时文件
|
# 过滤掉一些临时文件和__init__.py文件
|
||||||
if "__pycache__" in src_path or not src_path.startswith(PLUGIN_DIR):
|
if "__pycache__" in src_path or not src_path.startswith(PLUGIN_DIR) or os.path.basename(src_path) == "__init__.py":
|
||||||
return
|
return
|
||||||
|
|
||||||
# 简单的防抖动
|
# 简单的防抖动
|
||||||
@@ -216,4 +216,55 @@ async def main():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__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)
|
||||||
|
|||||||
@@ -151,7 +151,8 @@ def clean_compiled_files():
|
|||||||
|
|
||||||
def get_platform_specific_module_name(module_path):
|
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}"
|
return f"{module_name}.{BUILD_PREFIX}{EXTENSION}"
|
||||||
|
|
||||||
def compile_module(module_path):
|
def compile_module(module_path):
|
||||||
@@ -177,8 +178,17 @@ def compile_module(module_path):
|
|||||||
stderr_text = result.stderr
|
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):
|
if os.path.exists(platform_module):
|
||||||
@@ -186,12 +196,12 @@ def compile_module(module_path):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
# 检查 build 目录中是否有编译产物
|
# 检查 build 目录中是否有编译产物
|
||||||
build_module_path = os.path.join(BUILD_PATH, platform_module)
|
build_module_path = os.path.join(BUILD_PATH, module_dir, platform_module_name)
|
||||||
build_mypyc_path = os.path.join(BUILD_PATH, mypyc_platform_module)
|
build_mypyc_path = os.path.join(BUILD_PATH, module_dir, mypyc_platform_module_name)
|
||||||
|
|
||||||
if os.path.exists(build_module_path):
|
if os.path.exists(build_module_path):
|
||||||
# 如果在 build 目录中,复制到正确位置
|
# 如果在 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_module_path, platform_module)
|
||||||
if os.path.exists(build_mypyc_path):
|
if os.path.exists(build_mypyc_path):
|
||||||
shutil.copy2(build_mypyc_path, mypyc_platform_module)
|
shutil.copy2(build_mypyc_path, mypyc_platform_module)
|
||||||
@@ -341,298 +351,5 @@ def main():
|
|||||||
print("\n使用 --list 选项查看已编译的模块")
|
print("\n使用 --list 选项查看已编译的模块")
|
||||||
print("使用 --clean 选项清理编译文件")
|
print("使用 --clean 选项清理编译文件")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
跨平台 Python 模块编译脚本
|
|
||||||
|
|
||||||
将核心 Python 模块编译为机器码(.pyd 或 .so)以提升性能。
|
|
||||||
|
|
||||||
支持的平台:
|
|
||||||
- Windows: 生成 .pyd 文件
|
|
||||||
- Linux: 生成 .so 文件
|
|
||||||
|
|
||||||
使用方法:
|
|
||||||
python compile_machine_code.py [options]
|
|
||||||
|
|
||||||
选项:
|
|
||||||
--compile, -c 编译指定的模块(默认)
|
|
||||||
--list, -l 列出已编译的模块
|
|
||||||
--clean, -k 清理编译生成的文件
|
|
||||||
--help, -h 显示帮助信息
|
|
||||||
|
|
||||||
注意:
|
|
||||||
1. 需要安装 C 编译器 (Windows 上需要 Visual Studio Build Tools, Linux 上需要 GCC)
|
|
||||||
2. 需要安装 mypyc: pip install mypyc
|
|
||||||
3. 编译后的文件是平台相关的,不能跨平台复制
|
|
||||||
4. 建议在部署的目标环境上运行此脚本
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import glob
|
|
||||||
import subprocess
|
|
||||||
import shutil
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
# 检测当前平台
|
|
||||||
PLATFORM = sys.platform
|
|
||||||
if PLATFORM.startswith('win'):
|
|
||||||
EXTENSION = '.pyd'
|
|
||||||
BUILD_PREFIX = 'cp314-win_amd64'
|
|
||||||
BUILD_PATH = os.path.join('build', f'lib.win-amd64-cpython-314')
|
|
||||||
elif PLATFORM.startswith('linux'):
|
|
||||||
EXTENSION = '.so'
|
|
||||||
BUILD_PREFIX = 'cp314-x86_64-linux-gnu'
|
|
||||||
BUILD_PATH = os.path.join('build', f'lib.linux-x86_64-cpython-314')
|
|
||||||
else:
|
|
||||||
print(f"不支持的平台: {PLATFORM}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# 要编译的模块列表
|
|
||||||
# 注意: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/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', # 权限枚举
|
|
||||||
|
|
||||||
# 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 响应数据模型
|
|
||||||
|
|
||||||
# 事件处理相关
|
|
||||||
'core/handlers/event_handler.py', # 事件处理器
|
|
||||||
|
|
||||||
# 注意:以下文件不适合编译
|
|
||||||
# - 主程序文件(main.py)
|
|
||||||
# - 测试文件(tests/目录)
|
|
||||||
# - 插件文件(plugins/目录)
|
|
||||||
# - 编译脚本(compile_machine_code.py等)
|
|
||||||
# - 临时文件(scratch_files/目录)
|
|
||||||
# - 抽象基类(models/events/base.py)
|
|
||||||
# - 事件工厂(models/events/factory.py)
|
|
||||||
# - 包含复杂动态特性的文件
|
|
||||||
]
|
|
||||||
|
|
||||||
def list_compiled_modules():
|
|
||||||
"""列出已编译的模块"""
|
|
||||||
print(f"\n已编译的 {PLATFORM} 模块:")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
# 查找所有编译后的文件
|
|
||||||
compiled_files = []
|
|
||||||
for ext in [EXTENSION, f'__mypyc{EXTENSION}']:
|
|
||||||
compiled_files.extend(glob.glob(f'**/*{ext}', recursive=True))
|
|
||||||
|
|
||||||
# 过滤掉虚拟环境中的文件
|
|
||||||
compiled_files = [f for f in compiled_files if 'venv' not in f]
|
|
||||||
|
|
||||||
if compiled_files:
|
|
||||||
for f in sorted(compiled_files):
|
|
||||||
size = os.path.getsize(f) // 1024 # KB
|
|
||||||
print(f"{f} ({size} KB)")
|
|
||||||
else:
|
|
||||||
print(f"未找到已编译的 {EXTENSION} 文件")
|
|
||||||
|
|
||||||
print(f"\n总计: {len(compiled_files)} 个文件")
|
|
||||||
|
|
||||||
def clean_compiled_files():
|
|
||||||
"""清理编译生成的文件"""
|
|
||||||
print(f"\n清理编译生成的 {EXTENSION} 文件...")
|
|
||||||
|
|
||||||
# 查找所有编译后的文件
|
|
||||||
compiled_files = []
|
|
||||||
for ext in [EXTENSION, f'__mypyc{EXTENSION}']:
|
|
||||||
compiled_files.extend(glob.glob(f'**/*{ext}', recursive=True))
|
|
||||||
|
|
||||||
# 过滤掉虚拟环境中的文件
|
|
||||||
compiled_files = [f for f in compiled_files if 'venv' not in f]
|
|
||||||
|
|
||||||
if compiled_files:
|
|
||||||
for f in sorted(compiled_files):
|
|
||||||
try:
|
|
||||||
os.remove(f)
|
|
||||||
print(f"已删除: {f}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"删除失败 {f}: {e}")
|
|
||||||
|
|
||||||
# 清理 build 目录
|
|
||||||
if os.path.exists('build'):
|
|
||||||
try:
|
|
||||||
shutil.rmtree('build')
|
|
||||||
print("已删除 build 目录")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"删除 build 目录失败: {e}")
|
|
||||||
else:
|
|
||||||
print(f"没有可清理的 {EXTENSION} 文件")
|
|
||||||
|
|
||||||
def get_platform_specific_module_name(module_path):
|
|
||||||
"""获取平台特定的模块文件名"""
|
|
||||||
module_name = module_path.replace('.py', '')
|
|
||||||
return f"{module_name}.{BUILD_PREFIX}{EXTENSION}"
|
|
||||||
|
|
||||||
def compile_module(module_path):
|
|
||||||
"""编译单个模块"""
|
|
||||||
print(f"\n编译: {module_path}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 直接调用 mypyc 命令行工具
|
|
||||||
result = subprocess.run(
|
|
||||||
[sys.executable, '-m', 'mypyc', module_path],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# 获取平台特定的模块名
|
|
||||||
platform_module = get_platform_specific_module_name(module_path)
|
|
||||||
mypyc_platform_module = platform_module.replace(EXTENSION, f'__mypyc{EXTENSION}')
|
|
||||||
|
|
||||||
# 检查编译产物是否在当前目录
|
|
||||||
if os.path.exists(platform_module):
|
|
||||||
print(f" ✓ 编译成功: {platform_module}")
|
|
||||||
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)
|
|
||||||
|
|
||||||
if os.path.exists(build_module_path):
|
|
||||||
# 如果在 build 目录中,复制到正确位置
|
|
||||||
os.makedirs(os.path.dirname(platform_module), exist_ok=True)
|
|
||||||
shutil.copy2(build_module_path, platform_module)
|
|
||||||
shutil.copy2(build_mypyc_path, mypyc_platform_module)
|
|
||||||
print(f" ✓ 编译成功(已从 build 目录复制): {platform_module}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f" ✗ 编译失败:找不到编译产物")
|
|
||||||
if result.stdout:
|
|
||||||
print(f" 编译输出:{result.stdout[:500]}...")
|
|
||||||
if result.stderr:
|
|
||||||
print(f" 错误信息:{result.stderr[: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]}...")
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
print(f" ✗ 编译失败,意外错误: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def should_skip_module(module_path):
|
|
||||||
"""检查模块是否应该被跳过编译"""
|
|
||||||
try:
|
|
||||||
with open(module_path, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# 检查是否包含抽象基类相关代码
|
|
||||||
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, "包含动态特性,不适合编译"
|
|
||||||
|
|
||||||
return False, ""
|
|
||||||
except Exception as e:
|
|
||||||
return True, f"读取文件时出错: {e}"
|
|
||||||
|
|
||||||
def compile_all_modules():
|
|
||||||
"""编译所有指定的模块"""
|
|
||||||
print(f"\n开始编译 {len(MODULES)} 个模块 (平台: {PLATFORM})")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
# 验证模块文件是否存在并检查是否适合编译
|
|
||||||
valid_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})")
|
|
||||||
else:
|
|
||||||
valid_modules.append(module_path)
|
|
||||||
else:
|
|
||||||
print(f"警告: 模块 {module_path} 不存在,将被跳过")
|
|
||||||
|
|
||||||
if not valid_modules:
|
|
||||||
print("错误: 没有有效的模块可编译")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 编译模块
|
|
||||||
success_count = 0
|
|
||||||
for module_path in valid_modules:
|
|
||||||
if compile_module(module_path):
|
|
||||||
success_count += 1
|
|
||||||
|
|
||||||
print(f"\n" + "=" * 60)
|
|
||||||
print(f"编译完成: {success_count}/{len(valid_modules)} 个模块成功")
|
|
||||||
|
|
||||||
if success_count == len(valid_modules):
|
|
||||||
print("✓ 所有模块编译成功")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print("✗ 部分模块编译失败")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""主函数"""
|
|
||||||
parser = argparse.ArgumentParser(description='跨平台 Python 模块编译脚本')
|
|
||||||
|
|
||||||
group = parser.add_mutually_exclusive_group()
|
|
||||||
group.add_argument('--compile', '-c', action='store_true', default=True,
|
|
||||||
help='编译指定的模块 (默认)')
|
|
||||||
group.add_argument('--list', '-l', action='store_true',
|
|
||||||
help='列出已编译的模块')
|
|
||||||
group.add_argument('--clean', '-k', action='store_true',
|
|
||||||
help='清理编译生成的文件')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# 检查是否安装了 mypyc
|
|
||||||
try:
|
|
||||||
import mypyc
|
|
||||||
except ImportError:
|
|
||||||
print("错误: 未安装 mypyc,请先安装: pip install mypyc")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if args.list:
|
|
||||||
list_compiled_modules()
|
|
||||||
elif args.clean:
|
|
||||||
clean_compiled_files()
|
|
||||||
else:
|
|
||||||
compile_all_modules()
|
|
||||||
print("\n使用 --list 选项查看已编译的模块")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
@@ -135,11 +135,15 @@ def test_reload_plugin_error(plugin_manager):
|
|||||||
plugin_manager.loaded_plugins.add(full_name)
|
plugin_manager.loaded_plugins.add(full_name)
|
||||||
mock_module = MagicMock()
|
mock_module = MagicMock()
|
||||||
|
|
||||||
|
# 创建一个模拟的logger,直接替换plugin_manager实例的logger属性
|
||||||
|
mock_logger = MagicMock()
|
||||||
|
plugin_manager.logger = mock_logger
|
||||||
|
|
||||||
with patch.dict("sys.modules", {full_name: mock_module}), \
|
with patch.dict("sys.modules", {full_name: mock_module}), \
|
||||||
patch("importlib.reload", side_effect=Exception("Reload error")), \
|
patch("importlib.reload", side_effect=Exception("Reload error")):
|
||||||
patch("core.managers.plugin_manager.logger") as mock_logger:
|
|
||||||
|
|
||||||
# Should not raise exception
|
# Should not raise exception
|
||||||
plugin_manager.reload_plugin(full_name)
|
plugin_manager.reload_plugin(full_name)
|
||||||
mock_logger.exception.assert_called()
|
mock_logger.exception.assert_called()
|
||||||
|
mock_logger.log_custom_exception.assert_called()
|
||||||
|
|
||||||
|
|||||||
@@ -37,14 +37,18 @@ class TestWS:
|
|||||||
|
|
||||||
# 测试 WebSocket 未初始化的情况
|
# 测试 WebSocket 未初始化的情况
|
||||||
result = await ws.call_api("send_group_msg", {"group_id": 123456, "message": "test"})
|
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 已初始化但未连接的情况
|
# 测试 WebSocket 已初始化但未连接的情况
|
||||||
mock_ws = MagicMock()
|
mock_ws = MagicMock()
|
||||||
mock_ws.state = None
|
mock_ws.state = None
|
||||||
ws.ws = mock_ws
|
ws.ws = mock_ws
|
||||||
result = await ws.call_api("send_group_msg", {"group_id": 123456, "message": "test"})
|
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
|
@pytest.mark.asyncio
|
||||||
async def test_on_event_bot_initialization(self):
|
async def test_on_event_bot_initialization(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user