上一篇文章基本上把技术上的实现都讲了一遍,证明实现这个功能是切实可行的。下面就对建好的服务雏形进行完善和增加功能。
先放这个系列的文章:
一、默认回复
前面设置的默认回复就是简单的一句话,一点都不够智能,当然了,我再怎么改造,只要不用到聊天机器人这种功能,那就不会特别智能。但聊天机器人的API都是要收费的哎,而如果我用python在服务器上自己搭一个,那我真怕服务器会受不了这么大的压力。
那要怎么办呢?至少每次给用户的回复都不一样吧。这时我想到了hitokoto
这个API,接口基本上没什么限制,每次都会返回一个新的句子,挺适合我的需求的。
下面新建/api/api.js,用于请求接口数据:
1 | // 此处放置有关请求API接口的代码 |
请求接口有了,下面就要使用这个接口,这里用到上篇文章说的链式调用处理信息。首先建立/service/default.js
,这里同样要实现功能的分发,因为后面可能会为默认回复增加不同的功能。
1 | var db = require('./db') |
因为获取一句话同样是一个小功能,所以就先判断是否是专门获取一句话的,如果是的话,就不再添加我不懂我不懂之类的废话了,哈哈。
然后就是链式调用的实现了:
1 | // 普通文字消息 |
可以这么做的原因是如果上一个过滤器匹配到命令,它的返回值就不会为空,反之则会为空。这里当然是用的最笨的过滤器实现方式–手动添加,不过至少功能是实现了。
然后就可以尝试发送一条系统不懂的消息了:
怎么样?效果还可以吧
但这时我遇到一个问题,我请求hitokoto
源是要消耗一段时间的,但微信服务器等不及呀,它就启动了它的三次重试机制。那我就只好增加一个过滤机制了,打开app.js
,在系统启动时新增一个全局变量:
1 | // 定义缓存消息id的数组,防止重复响应 |
然后在/routes/wx.js
中先判断是否是重复消息,不是的话才允许向下执行:
1 | // 因为系统还会请求其他服务器,返回可能会有延时,因此在这里判断并保存每个消息的openid和msgid(可能是在if语句外面,不过目前只是处理文字信息,先不考虑那么多了吧) |
这样就能防止重复对一个请求做出响应了。但不足的是代码实现不够优雅,后面应该增加一个前置过滤器来处理这种事情。
二、账单分页
这个功能的实现就比较简单了,预期的实现效果是:
1 | 请求:账单 |
首先就是对功能分发的改造:
1 | // 匹配以账单开头的命令 |
查询账单时就是多了一个计算页数的过程,然后分页的逻辑nedb
中已经有了,直接用就是了:
1 | // 查询账单 |
然后就有了这种效果:
刚刚在正式的公众号看时间显示有点问题,待会儿看看怎么回事。
三、统计功能
统计功能的预期实现效果:
1 | 请求:统计 |
这个功能也是很简单就能实现的:
1 | // 匹配统计开头的命令 |
然后实现效果如下:
四、固定开支
我们每个月都会有固定开支,如工资、花呗、分期,这些固定的项目其实是不需要用户手动去输入的,系统完全可以帮用户做到。
预期效果:
1 | 请求:月开销预设 |
要实现这样的效果,就要缓存用户上一步的指令,并且要知道指令的总步骤数量及当前进行到哪一步。
既然缓存了,就要有删除的步骤,一种方式是定义一个定时器,每隔一段时间清除失效的指令。另一种方式是在用户下一次请求时判断指令是否失效,如果失效就重新开始。
这里也要考虑到如果用户一直不发下个请求呢?难道服务器要一直缓存直到内存爆炸?所以定时器一定要有,但太频繁也不好,所以还是要在读取指令前判断用户之前的指令是否失效。
其实到现在这个步骤,整个后台应用就显得有点复杂了,我们必须要把过滤器的思路贯彻到底,重新改写响应逻辑,实现路由入口处的简洁。
首先,路由入口会收到两种消息类型:普通消息及事件推送,为了区分它们,我们先建立消息类型分发器msgTypeDispatcher.js
:
1 | const config = require('../config/index') |
将消息分发的逻辑抽取出来之后,/routes/wx.js
接口的代码就变得非常简洁了:
1 | msgDiapatcher(json.xml).then(msg => { |
因为设定固定开支的步骤一共需要两步,那么我们就要保存上一步的状态,为了统一所有具有多个步骤的操作,新增一个步骤过滤器,在过滤器中可以直接执行相应步骤的代码。而且为了方便以后拓展,通过设定step来判断步骤执行到第几步。
1 | const presetManager = require('./preset') |
建立完成后在消息分发器中调用:
1 | if (payload.MsgType === 'text' || payload.Content) { |
经过上面的处理,步骤消息就能到达指定的方法中,并且该方法也不必去管怎么去缓存当前步骤,怎么去删除缓存的事情,在我看来还算是一个较好的解决方案。下面把处理固定开支的代码放上:
1 | const db = require('./db') |
最后的实现效果就是:
当然,这样只是实现了记录的功能,那么自动执行任务还没有做。我的预想是每晚3点定时去判断每个用户当天的收支预设信息,如果是当天,则向账单中添加记录。又因为项目中不可能只有这一个定时器,因此干脆一了百了,再做一个定时器管理的功能。
js中与定时有关的也就是setTimeout
和setInterval
了,而且现在需要做的也只是定时(整点)执行任务,那么我们就可以计算当前时间距离下一个整点还有多久,然后setTimeout
到下一个整点(也可以是下下个整点,计算时间并相加即可),启动setInterval
执行我们真正需要执行的任务。
1 | const presetTimer = require('./presetTimer') |
这样写了之后,我们在项目的app.js
中调用这个文件的init
方法即可。
然后对于具体的固定收支添加账单的逻辑就比较简单了,就不再放到上面了,有兴趣的可以到我的github上看源码。