bing bong:一款适配 QQ 的 Rss 机器人
Long time no see !
距离上篇博客已经有近半年了。这半年,先是课业的繁重,后是工作的紧张,让我一直没有时间腾出手来写博客。所幸开学(离职)在即,我也总算能怀着一种轻松的心态来水一篇文章啦!~
这篇文章主要来写一写 bing-bong 的程序结构。Linux 社的朋友应该都知道,我最近写了一个用于订阅 Rss 的 QQ 机器人(什么你问为什么知道?当然是因为 Linux 茶话会是 bing-bong 的官方唯一指定试点群啊!)。因为时间的原因,它并没能像我想象的一样完美,反而在我看来有着各种各样的缺陷。不管是为了让大家更好地参与修改,还是为我将来的重构理清思路,重新梳理一下程序的逻辑都是势在必行的。
来源
依据惯例,首先介绍一下项目的来源。因为在 README 中有类似介绍,此处就不赘述了。
设计
众所周知,RSS 场景是典型的订阅-发布模型,十分适合使用消息队列完成程序功能。作者在设计时也率先考虑到了这一点。
有了消息队列,我们需要关注的就只有两个大的方面了——消息的生成、接收。
- 消息的生成
一个很简单的想法,即使用定时任务抓取消息队列中的所有 topic 地址,获取到 feed 内容后判断是否发送过,如果未发送过则发送该条内容并将其标记为已发送。
- 消息的接收
消息队列的订阅者接受到广播来的消息后,由机器人负责发送消息即可。
结构
以下是和程序逻辑有关的目录结构:
1 | . |
实现
首先实现机器人,按照我们的设计可以抽象出机器人的功能:
1 | type robot interface { |
任意一个能够实现这个接口的机器人都能够满足我们的要求。
接着是消息队列,对于使用CSP 并发模型的 Golang 来说,简单实现一个用于消息传递的消息队列再容易不过了,具体代码可参见message/mq.go
。
现在已经有了消息的消费者和传递途径,我们的最终目标(至少在现在看来)就是实现生产者了。生产者将获取消息队列的 topic 列表(即所有存在的 url)并进行请求,如果发现更新则构建消息,将消息写入到消息队列的对应 topic 中(其中 topic 的请求位于internal/rss.go
,构建消息位于utils/message.go
)。
呼~看起来是写完了,但事实真的如此吗?考察以上设计能否满足以下需求:
- 机器人响应人的指令(如新增订阅、修改订阅等)
- 每次启动程序,机器人自动读取上次的订阅关系
- feed 查重
可以预见,要让机器人响应指令,需要让机器人监听消息事件并对消息做出相应响应,而事实上一个机器人也往往需要在初始化步骤后才能开始正常工作,故我最终将机器人接口设计为了:
1 | type robot interface { |
并给出了 QQ 版本的样例实现 (放在client/QQ.go
)。
对于 2、3 需求,很明显我们需要将订阅关系和已经发送过的 feed 信息持久化,这时候数据库就派上了用场。我在db.sql
中存储了建表语句,并在model/db.go
中初始化了数据库连接,封装了一系列数据库操作方法。
至此,我们的程序基本是大功告成了。
缺陷
如果留意我上述的实现代码,很容易会发现许多问题:
- 为什么消息队列的接收者不是单纯的 chan string,而要封装成一个大大的 Receiver?
- 按照你的逻辑,为什么一个人订阅多个 url,要起多个协程?只跑一个不行吗?
- 将消息队列的订阅与接受消息的监听耦合在一起,真的大丈夫吗?
- RSS 判重难道没有比 hash 更好的方法吗?
- 每个指令都需要读取数据库,不会影响性能吗?
- 一个如此简单的机器人却要接入 mysql 这种复杂数据库,不觉得会加大部署难度吗?
- 用户还需要自己执行数据库初始化语句,不会很麻烦吗?
- 数据库初始化写在 init 里,单测压根没法搞啊?
- ……
没错,这些(甚至更多)都是这个程序的缺陷。在将来版本的迭代中,我将尽力修复这些痛点。
结语
感谢大家看我唠叨了这么多!最后的最后,欢迎大家为项目点个 star 鸭!~