开发一个云端个人助理 · 1

最后更新于

Image
Image

前几天给 RSSHub 提交了一个 PR,把我所就读的大学的两个学院官网做成了 RSS 源。

RSS 好是好,但就是缺少即时性,于是打算利用企业微信机器人做一个及时推送的服务, 并把服务范围扩大。

关于配置模式的思考

{
	"service": [
		{
			"src": "./components/core/swpu/is.js",
			"config": {
				"channel": ["xyxw", "tzgg"],
				"uid": "123456",
				"push": {
					"wechat": {
						"username": "WangRenJie"
					}
				}
			}
		}
	]
}

我想到了几种配置方案:

  1. 用户为单位,每个用户下面配置他订阅的路由,以及他的推送信息。
  2. 路由为单位,每个路由下面记录订阅该路由的用户。

由于暂时只有我一个人使用,便使用了第二种方案,并把本该配置在用户字段下的”订阅频道“提到了全局。

架构设计

由于不确定各个模块的具体逻辑,这方面我也做了很多技术权衡:

A.每个模块负责返回最新推文

这种模式源于 RSSHub 的实现模式。控制器依次注册每个模块,传递一个上下文方法。

模块内部有一个定时器,每隔一段时间透过上下文更新推文。

推文数据保存在控制器,一旦数据不一致则执行推送流程。

如此,推送逻辑就由控制器处理。好处是,如果以后想抓取其他网站的文章,可以很方便地扩展。但是,后续的开发流程中数据可能污染污染控制器(因为每个网站的推文结构不一样,比如 B 站就会有 video 字段。)

B.每个模块执行全部自己的逻辑

这个方法简单粗暴,模块只需暴露一个start()方法,控制器直接调用即可。

	start(interval) {
        this.instance.forEach((instance) => {
			instance.start();
		});
	}

如果后续要扩展,比如用户选择推送逻辑,也可以提取出公共组件。

并且,以后可能会有一些不需要推送的服务,例如网课签到。

这也是最终采用的代码。

C.每个模块提供一个更新、推送方法

看似简洁,事实上这是一个很糟糕的设计,数据到了上游,但是数据消费却到了下游。

理解发布-订阅模式

事实上,这并非是一个标准的发布-订阅模式。

我们先来看看入口函数如何调用个人助理:

assistant.regsiter("./components/core/swpu/is.js", {
	channel: ["tzgg", "xyxw"],
	uid: "123456",
	push: ["wechat"],
}) && assistant.start();

这确实是标准的订阅模式。

如果 push 中写进一个具体的推送方法,并且每个模块仅仅返回最新的推文,那么发布者(控制器)则会存储一系列推送函数,一旦模块返回了最新的推文,就依次执行发布流程。

但本项目中,推送流程其实由控制器自己调度了(事实上,每个模块都是控制器的组成部分,此处推送逻辑在模块里面)。所以仅仅实现了订阅模式。

后续开发

基于发布订阅模式,我保留了许多方法,例如list() destory(),后续可以搭配 Web 应用进行远程管理。

国内开放 API 的 IM 平台还比较少,后续将接入国外的一些平台。值得一提的是,配置结构可能就要采取”订阅源“和”用户“分开存储的逻辑。

项目地址: github.com/rivertwilight/assistant