蓝图
我会在此介绍我心目中理想的 OneBot SDK 应有怎样的设计和实现。
总结 WudiLib
WudiLib 的优点
WudiLib 在设计时预留了一定的接口,使得使用者可以根据需要进行扩展,并且可以适应较为广泛的应用场景。
没有引入无意义的依赖,也没有显著浪费资源的代码。
WudiLib 的不足
类名、API 名设计不佳
我太不会起名字了……举个例子,各个消息的基类叫 Message
,消息事件的基类也叫 Message
,经常会出现冲突。
可扩展性还有提升空间
主要是事件的可扩展性。当前要扩展事件类型非常麻烦,而且还有很多限制。
SDK 设计
分为 sender、receiver、dispatcher 三部分,sender 负责发送请求并返回响应,receiver 接收事件上报,dispatcher 负责解析事件。
Dispatcher
sender 和 receiver 参考现有设计即可,关键是 dispatcher,这个东西并不存在于现在的 WudiLib 中。
添加事件
首先得收集所有事件类型的实体类,并且允许用户自定义添加。
用Attribute标明每一个类的条件,比如
[IfField("type", "message")]
public abstract class Message : Event { }
表示如果 type
是 message
,那就找 Message
相关的子类。
基本限制(规定):
- 如果子类和父类(包括间接的)标记了同一个字段,那必须相同。
- 子类比父类多标记的字段也应该受到限制。
- 严格版:
- 每个类只能比父类多指定一个字段,并且一个事件类的直接派生类多指定的那个字段必须是同一个。
- 宽松版(部分规定):
- 如果一个类派生了多个子类,并且其共同指定的字段完全相同,则一个类指定的字段必须是另一个的子集。
- 匹配时,指定字段多的优先匹配。
- 严格版:
例如:
[IfField("a", "a"),IfField("b", "b")] class A : SomeBase { }
[IfField("a", "a"),IfField("c", "c")] class B : SomeBase { }
这就不行,因为如果上报同时包含了"a", "b", "c",就会产生歧义。
[IfField("a", "a"),IfField("b", "b")] class A : SomeBase { }
[IfField("a", "a")] class B : SomeBase { }
这就可以,优先匹配A。
之所以说这些,是想方便 bot 开发者处理未在 SDK 中定义的事件,并尽可能帮助他们减少误用。
数据结构和算法没想好。
添加方式
考虑API和直接使用反射查找两种方式。
待定事项
是否允许多次注册?
- 禁止
- 按顺序处理
处理事件(添加事件处理器)
可以考虑在 dispatcher 中提供类似这样的方法来注册事件处理器。
RegisterMessage(Func<..., Task>);
RegisterEvent<EventType>(Func<..., Task>);
RegisterEvent<EventType, ResponseType>(Func<..., Task<ResponseType>>);
方法名考虑“Register”或“Subscribe”开头。
事件过滤器
When<EventType>(Func<..., Task<bool>/bool>).Do(..., Func<..., Task/Task<ResponseType>>);
When<EventType>(Func<..., Task<bool>/bool>).Respond(..., Func<..., Task<ResponseType>>);
反向 WebSocket 通信方式
反向 WebSocket 通信应该可以正确处理多个连接的情况,甚至应该支持在不同的连接上启用不同的功能。
框架设计
框架应当包含 DI、日志等功能。用 Microsoft.Extensions.Hosting
相关的类作为基础提供这些功能应当是不错的选择。
我不打算以 ASP.NET Core 为基础,因为 ASP.NET Core 含有过多与 Bot 框架无关的内容。
此外,我的最新思路是,做好云插件的支持。一个例子就是消防栓分身。我希望用户只要配置好 go-cqhttp 或者其他 OneBot 实现的反向 WebSocket 连接,就能连接到开发者的服务器处理事件,减少风控的同时又不抬高使用门槛。
术语表
- 开发者:云插件的开发者,即框架的使用者。开发插件供 bot 连接。
- 用户:云插件的使用者。用户使用 go-cqhttp 或其他 OneBot 实现连接到开发者提供的云插件上。
- bot:连接云插件的 QQ 账号。
- 管理员:bot 账号的管理者及其所用的账号。
云插件框架主要需求
- 做好权限控制,允许匿名用户连接(可配置)
- 对连接有信任等级
- 匿名用户:等级最低,只开放极其有限的功能
- 注册用户:指连接的 bot 账号是注册过此云插件的账号。注意此等级的信任等级取决于注册门槛。如果注册门槛不高,用户依然有很大可能是会伪造请求的恶意用户。开启消耗资源较多,或者可能会修改数据库的功能时需谨慎。
- 高信任用户:指开发者自己的 bot 号或者其他可以信任的 bot 号连接。
- 连接的 bot 默认给予开发者一定权限(如加好友自动通过,可配置)。注册用户可以调整设定(该 bot 对应的管理员账号、是否自动通过好友或加群请求等)。
- 提供功能开关,允许管理员针对每个群调整功能开关。
- 匿名用户的 QQ 号是靠用户主动上报获取的,可信度低,因此匿名用户不限连接数。注册用户(QQ 号和设定的密钥必须匹配)连接数可以受到限制。
设计想法
设计:
- 数据库有 bot 表,存储:bot 账号、对应的密钥、信任等级
- 有配置表,列:bot 账号、配置项名称、配置项的值(考虑JSON?)
- 配置
- 管理员账号
- 每个功能的开关
- 每个功能的配置
- 重载是否允许开发者加好友
- 是否允许宣称是该账号的匿名连接(允许)
- Host:得有个总 Host¹,但是每个 bot 使用单独的 Host² 吗?
- 当有连接到 Host¹ 的连接,就为其创建一个 Host²,断开即销毁
- 如果是已注册用户,就缓存该 Host²
- 包装一下事件类和 API 类
- 断开一段时间后才销毁
- 拒绝同时多个连接
- 检测到重连就使用同一个 Host²
- API类和事件监听类本身也是 Service
- 中间件:
- 允许注册中间件,中间件甚至允许修改事件本身
- 事件处理本身也是个中间件,搜索合适的处理器并把事件发给合适的事件处理器(并发执行)
- 有些事件处理可能希望是中间件模式(写在任意地方却可能希望修改事件本身)
- 优先级问题如何解决
- 触发命令就不处理事件
- 命令中间件触发就不往下走
- 根据命令处理结果。用特性:
[Condition<CommandResultService>(Property, Value)]
- 对 API 进行包装,用以拦截调用并交由中间件处理
- 如何实现呢?
- 与 HTTP 之类的服务不同,bot 服务可以包含多个响应,也可能调用平台的其他 API。
- 功能开关也由中间件处理
- 单 Host² 多账号:使用中间件实现
- 提供一个 scoped 的服务,用于把消息保存成平台无关的格式
- 有中间件可以读取 onebot 或者其他平台的事件,将其存储到该服务中
- 命令之类的也是由一个中间件处理(把处理结果也保存到相关的服务里)
- 比如提供
Respond
方法(同步还是异步呢,也许传入onFailure
委托更好)
- 比如提供
- 用户组(非面向开发者的决定信任等级的用户组,为面向用户的方便管理的用户组)
- 可能只有部分功能用到用户组功能
- 也许可以作为服务之一提供
- 表:
- 用户表
- 用户组表(组名,权限等级(int))
- 权限列表
- 抽象权限(string)
- 功能权限
- 用户-组表
- 用户/组-权限表(授予/拒绝)
- 默认都有 Invoke(名称待定)之类的权限,被禁止则无法使用所有功能
- API:计划简单地提供增删改查
- Attributes:
[PermissionLevel(int)]
必须有相应等级[Require(string)]
必须有相应名称的权限(抽象权限)- 默认情况下,必须有对应的“功能权限”才可调用/触发
- 这些都是加在“命令”上的,由命令中间件调用用户组相关服务判断是否触发。
- 日志
- 就用内置的
- 有 Warning 以上等级的日志,通过其他途径通知?但是这样需要开发者处理,毕竟代码是开发者写
- 单元测试
- 待定,看看 ASP.NET Core 怎么做的
- 框架本身的单元测试