feat: 重构核心架构,增强类型安全与插件管理

本次提交对核心模块进行了深度重构,引入 Pydantic 增强配置管理的类型安全性,并全面优化了插件管理系统。

主要变更详情:

1. 核心架构与配置
   - 重构配置加载模块:引入 Pydantic 模型 (`core/config_models.py`),提供严格的配置项类型检查、验证及默认值管理。
   - 统一模块结构:规范化模块导入路径,移除冗余的 `__init__.py` 文件,提升项目结构的清晰度。
   - 性能优化:集成 Redis 缓存支持 (`RedisManager`),有效降低高频 API 调用开销,提升响应速度。

2. 插件系统升级
   - 实现热重载机制:新增插件文件变更监听功能,支持开发过程中自动重载插件,提升开发效率。
   - 优化生命周期管理:改进插件加载与卸载逻辑,支持精确卸载指定插件及其关联的命令、事件处理器和定时任务。

3. 功能特性增强
   - 新增媒体 API:引入 `MediaAPI` 模块,封装图片、语音等富媒体资源的获取与处理接口。
   - 完善权限体系:重构权限管理系统,实现管理员与操作员的分级控制,支持更细粒度的命令权限校验。

4. 代码质量与稳定性
   - 全面类型修复:解决 `mypy` 静态类型检查发现的大量类型错误(包括 `CommandManager`、`EventFactory` 及 `Bot` API 签名不匹配问题)。
   - 增强错误处理:优化消息处理管道的异常捕获机制,完善关键路径的日志记录,提升系统运行稳定性。
This commit is contained in:
2026-01-08 23:42:53 +08:00
parent c2de743098
commit 5d07a84283
35 changed files with 829 additions and 608 deletions

View File

@@ -3,6 +3,7 @@ from .message import MessageAPI
from .group import GroupAPI
from .friend import FriendAPI
from .account import AccountAPI
from .media import MediaAPI
__all__ = [
"BaseAPI",
@@ -10,4 +11,5 @@ __all__ = [
"GroupAPI",
"FriendAPI",
"AccountAPI",
"MediaAPI",
]

View File

@@ -162,3 +162,56 @@ class AccountAPI(BaseAPI):
"""
return await self.call_api("clean_cache")
async def get_stranger_info(self, user_id: int, no_cache: bool = False) -> Any:
"""
获取陌生人信息。
Args:
user_id (int): 目标用户的 QQ 号。
no_cache (bool, optional): 是否不使用缓存。Defaults to False.
Returns:
Any: 包含陌生人信息的字典或对象。
"""
return await self.call_api("get_stranger_info", {"user_id": user_id, "no_cache": no_cache})
async def get_friend_list(self, no_cache: bool = False) -> list:
"""
获取好友列表。
Args:
no_cache (bool, optional): 是否不使用缓存。Defaults to False.
Returns:
list: 好友列表。
"""
cache_key = f"neobot:cache:get_friend_list:{self.self_id}"
if not no_cache:
cached_data = await redis_manager.get(cache_key)
if cached_data:
return json.loads(cached_data)
res = await self.call_api("get_friend_list")
await redis_manager.set(cache_key, json.dumps(res), ex=3600) # 缓存 1 小时
return res
async def get_group_list(self, no_cache: bool = False) -> list:
"""
获取群列表。
Args:
no_cache (bool, optional): 是否不使用缓存。Defaults to False.
Returns:
list: 群列表。
"""
cache_key = f"neobot:cache:get_group_list:{self.self_id}"
if not no_cache:
cached_data = await redis_manager.get(cache_key)
if cached_data:
return json.loads(cached_data)
res = await self.call_api("get_group_list")
await redis_manager.set(cache_key, json.dumps(res), ex=3600) # 缓存 1 小时
return res

View File

@@ -1,24 +1,50 @@
"""
API 基础模块
定义了 API 调用的基础接口。
定义了 API 调用的基础接口和统一处理逻辑
"""
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional
from typing import Any, Dict, Optional, TYPE_CHECKING
from ..utils.logger import logger
if TYPE_CHECKING:
from ..ws import WS
class BaseAPI(ABC):
class BaseAPI:
"""
API 基础抽象
API 基础类,提供了统一的 `call_api` 方法,包含日志记录和异常处理。
"""
_ws: "WS"
self_id: int
def __init__(self, ws_client: "WS", self_id: int):
self._ws = ws_client
self.self_id = self_id
@abstractmethod
async def call_api(self, action: str, params: Optional[Dict[str, Any]] = None) -> Any:
"""
调用 API
调用 OneBot v11 API并提供统一的日志和异常处理。
:param action: API 动作名称
:param params: API 参数
:return: API 响应结果
:return: API 响应结果的数据部分
:raises Exception: 当 API 调用失败或发生网络错误时
"""
raise NotImplementedError
if params is None:
params = {}
try:
logger.debug(f"调用API -> action: {action}, params: {params}")
response = await self._ws.call_api(action, params)
logger.debug(f"API响应 <- {response}")
if response.get("status") == "failed":
logger.warning(f"API调用失败: {response}")
return response.get("data")
except Exception as e:
logger.error(f"API调用异常: action={action}, params={params}, error={e}")
raise

View File

@@ -4,7 +4,7 @@
该模块定义了 `GroupAPI` Mixin 类,提供了所有与群组管理、成员操作
等相关的 OneBot v11 API 封装。
"""
from typing import List, Dict, Any
from typing import List, Dict, Any, Optional
import json
from ..managers.redis_manager import redis_manager
from .base import BaseAPI
@@ -46,7 +46,7 @@ class GroupAPI(BaseAPI):
"""
return await self.call_api("set_group_ban", {"group_id": group_id, "user_id": user_id, "duration": duration})
async def set_group_anonymous_ban(self, group_id: int, anonymous: Dict[str, Any] = None, duration: int = 1800, flag: str = None) -> Dict[str, Any]:
async def set_group_anonymous_ban(self, group_id: int, anonymous: Optional[Dict[str, Any]] = None, duration: int = 1800, flag: Optional[str] = None) -> Dict[str, Any]:
"""
禁言群组中的匿名用户。
@@ -61,7 +61,7 @@ class GroupAPI(BaseAPI):
Returns:
Dict[str, Any]: OneBot API 的响应数据。
"""
params = {"group_id": group_id, "duration": duration}
params: Dict[str, Any] = {"group_id": group_id, "duration": duration}
if anonymous:
params["anonymous"] = anonymous
if flag:
@@ -187,17 +187,18 @@ class GroupAPI(BaseAPI):
await redis_manager.redis.set(cache_key, json.dumps(res), ex=3600) # 缓存 1 小时
return GroupInfo(**res)
async def get_group_list(self) -> List[GroupInfo]:
async def get_group_list(self) -> Any:
"""
获取机器人加入的所有群组的列表。
Returns:
List[GroupInfo]: 包含所有群组信息的 `GroupInfo` 对象列表。
Any: 包含所有群组信息的列表(可能是字典列表或对象列表
"""
res = await self.call_api("get_group_list")
# 增加日志记录 API 原始返回
logger.debug(f"OneBot API 'get_group_list' raw response: {res}")
return res
# 健壮性处理:处理标准的 OneBot v11 响应格式
if isinstance(res, dict) and res.get("status") == "ok":

39
core/api/media.py Normal file
View File

@@ -0,0 +1,39 @@
"""
媒体API模块
封装了与图片、语音等媒体文件相关的API。
"""
from typing import Dict, Any
from .base import BaseAPI
class MediaAPI(BaseAPI):
"""
媒体相关API
"""
async def can_send_image(self) -> Dict[str, Any]:
"""
检查是否可以发送图片
:return: OneBot v11标准响应
"""
return await self.call_api(action="can_send_image")
async def can_send_record(self) -> Dict[str, Any]:
"""
检查是否可以发送语音
:return: OneBot v11标准响应
"""
return await self.call_api(action="can_send_record")
async def get_image(self, file: str) -> Dict[str, Any]:
"""
获取图片信息
:param file: 图片文件名或路径
:return: OneBot v11标准响应
"""
return await self.call_api(action="get_image", params={"file": file})

View File

@@ -8,7 +8,8 @@ from typing import Union, List, Dict, Any, TYPE_CHECKING
from .base import BaseAPI
if TYPE_CHECKING:
from models import MessageSegment, OneBotEvent
from models.message import MessageSegment
from models.events.base import OneBotEvent
class MessageAPI(BaseAPI):
@@ -156,24 +157,6 @@ class MessageAPI(BaseAPI):
"""
return await self.call_api("send_private_forward_msg", {"user_id": user_id, "messages": messages})
async def can_send_image(self) -> Dict[str, Any]:
"""
检查当前机器人账号是否可以发送图片。
Returns:
Dict[str, Any]: OneBot API 的响应数据。
"""
return await self.call_api("can_send_image")
async def can_send_record(self) -> Dict[str, Any]:
"""
检查当前机器人账号是否可以发送语音。
Returns:
Dict[str, Any]: OneBot API 的响应数据。
"""
return await self.call_api("can_send_record")
def _process_message(self, message: Union[str, "MessageSegment", List["MessageSegment"]]) -> Union[str, List[Dict[str, Any]]]:
"""
内部方法:将消息内容处理成 OneBot API 可接受的格式。
@@ -192,7 +175,7 @@ class MessageAPI(BaseAPI):
return message
# 避免循环导入,在运行时导入
from models import MessageSegment
from models.message import MessageSegment
if isinstance(message, MessageSegment):
return [self._segment_to_dict(message)]