- 上一篇传送门—— 微信公众号 | 数据的存储 (上)
- 本次工程项目在 wechat_server: 微信公众号服务器 代码仓库中更新
本篇的主题
- 连续性会话设计
- 微信服务器对图片消息的处理
需求分析
在上一篇文章中,使用mongoDB数据库实现了用户数据、回话数据的储存。这次做个更复杂点的需求,就是最近疫情需要每日做抗原检测,做完后需要在本楼的群里分享抗原信息给楼主统计。
使用公众号实现当用户发送“抗原检测”时,服务器返回需要提供的房号,在用户输入房号后,服务器再返回需要用户提供照片。当用户同时完成房号和照片信息提交后,服务器返回提交成功,否则继续提示用户需要提交相应的信息。
思路
这需要一个新的回话暂且叫NATSession,在这个session中需要有体现会话是否结束的flag,只有当用户提交房号和照片后才能正确结束会话,否则会提示相应操作。这样就保证了用户前后几次的操作是在一个回话内,服务器就能对回话进行的状态做出相应的反应。
如果用户并没有按照提示进行操作:
a. 需要输入房号,却提交照片————成功提交照片,并提示输入房号
b. 需要输入照片,却再次提交文字————不触发当前会话,进入其他中间件处理
c. 在回话未完成时再次输入“抗原检测”————打开之前的未完成会话,进行下一步提示
d. 同一天提交完成多次“抗原检测”————只是以最后一次为准(这里就简单处理了)
模型设计
//nATSchema.js
const Schema = require('mongoose').Schema
const nATSSchema = new Schema({
sessionID: {
type: String,
unique: true
},
userID: String, //提交的用户
pic: String, //照片的url
isFinish: Boolean, //是否回话完成
address: String, //房间号
picID: String, //照片ID,微信服务器返回
hasAddress: Boolean, //是否完成地址
hasPic: Boolean, //是否完成照片
date: Number //日期
})
module.exports = nATSSchema
中间件设计
这个nat中间件分为两块,一块是文字消息处理,所以要挂在文字处理中间件上;
另一块是图片消息处理,这个之前没有涉及到,新建个图片处理中间件,挂载在其上;
//消息处理中间件
//textMsgMW.js
const textMsgMW = require('express').Router()
//基础会话创建
textMsgMW.route('/wx')
.post(async(req, res, next) => {
//...
})
//每日经文中间件
textMsgMW.use(require('./textMsg/dailyVerseMW'))
//抗原信息房号处理中间件
textMsgMW.use(require('./textMsg/nATMW'))
//其他文字中间件
textMsgMW.use(require('./textMsg/elseTextMW'))
module.exports = textMsgMW
//picMsgMW.js
const picMsgMW = require('express').Router()
//抗原图片上传中间件
picMsgMW.use(require('./picMsg/nATPicMW'))
//其他图片中间件
//这里暂时用来处理当用户在不需要上传图片时传入图片的处理
//以后用做默认情况处理
picMsgMW.use(require('./picMsg/elsePicMW'))
module.exports = picMsgMW
//抗原检测文字处理
//nATMW.js
const nATMW = require('express').Router()
nATMW.route('/wx')
.post(async (req, res, next) => {
if (req.jsonData.MsgType[0] == 'text') {
const { jsonData } = req
const mongoose = require('mongoose')
if (jsonData.Content[0].trim() == '抗原检测') {
//在创建新的会话前先验证有无未完成的会话
const NAT = mongoose.model('nat', require('../../../model/nATSchema'))
const unfinishNAT = await NAT.findOne({ userID: req.user.userID, isFinish: false })
const User = mongoose.model('user', require('../../../model/userSchema'))
//再验证有无当日的会话,如果有就删除,这里不是重点就随意简化处理下
await NAT.deleteMany({ userID: req.user.userID, isFinish: true, date: require('../../../tools/getDate')('today') })
if (!unfinishNAT) {
// 当无未完成会话情况下,添加新的抗原检测会话
const natSession = {
sessionType: 'NAT',
createDate: require('../../../tools/getDate')('today'),
sessionID: jsonData.MsgId[0]
}
//在用户数据中添加会话
await User.updateOne({ userID: req.user.userID }, { $push: { sessions: natSession } })
//创建抗原会话
const nat = {
sessionID: natSession.sessionID,
userID: req.user.userID,
pic: '',
isFinish: false,
address: '',
hasAddress: false,
picID: '',
hasPic: false,
date: natSession.createDate
}
const newNAT = new NAT(nat)
//防止因网络延迟产生的重复数据
NAT.findOne({ sessionID: natSession.sessionID }).then(isExist => {
if (!isExist) newNAT.save()
})
//下一步提示用户输入楼号和房号
const msg = '请输入您所在的楼号和房号'
const xmlSendData = require('../../../tools/sendMsg')(jsonData.FromUserName[0], jsonData.ToUserName[0], msg)
res.end(xmlSendData)
} else {
if (!unfinishNAT.hasAddress) {
//当有未完成会话且少了房号情况下
//提示用户输入楼号和房号
const msg = '请输入您所在的楼号和房号'
const xmlSendData = require('../../../tools/sendMsg')(jsonData.FromUserName[0], jsonData.ToUserName[0], msg)
res.end(xmlSendData)
} else if (!unfinishNAT.hasPic) {
//当有未完成会话且少了照片情况下
//提示用户上传照片
const msg = '请添加抗原照片'
const xmlSendData = require('../../../tools/sendMsg')(jsonData.FromUserName[0], jsonData.ToUserName[0], msg)
res.end(xmlSendData)
}
}
} else {
//如果有未完成的NAT检测时,收到的文字信息作为房间号使用
const NAT = mongoose.model('nat', require('../../../model/nATSchema'))
const unfinishNAT = await NAT.findOne({ userID: req.user.userID, isFinish: false })
if (unfinishNAT) {
await NAT.updateOne({ userID: req.user.userID, isFinish: false }, { $set: { address: jsonData.Content[0], hasAddress: true } })
if (!unfinishNAT.hasPic) {
//如果该回话没有照片,提示用户上传照片
const msg = '请添加抗原照片'
const xmlSendData = require('../../../tools/sendMsg')(jsonData.FromUserName[0], jsonData.ToUserName[0], msg)
res.end(xmlSendData)
} else {
//提示用户完成操作
const msg = '感谢使用,您的抗原信息已添加完成!'
const xmlSendData = require('../../../tools/sendMsg')(jsonData.FromUserName[0], jsonData.ToUserName[0], msg)
await NAT.updateOne({ userID: jsonData.FromUserName[0], isFinish: false }, {
$set: {
isFinish: true
}
})
res.end(xmlSendData)
}
} else {
next()
}
}
}
})
module.exports = nATMW
//抗原照片中间件
//nATPicMW.js
const nATPicMW = require('express').Router()
nATPicMW.route('/wx')
.post(async (req, res, next) => {
if (req.jsonData.MsgType[0] == 'image') {
const { jsonData } = req
const mongoose = require('mongoose')
const NAT = mongoose.model('nat', require('../../../model/nATSchema'))
const unfinishNAT = await NAT.findOne({ userID: jsonData.FromUserName[0], isFinish: false })
if (unfinishNAT && !unfinishNAT.hasPic) {
//如果有未完成会话收到的图片作为抗原照片使用
await NAT.updateOne({userID: jsonData.FromUserName[0], isFinish: false},{$set:{
pic: jsonData.PicUrl[0],
hasPic: true,
picID: jsonData.MediaId[0]
}})
unfinishNAT.pic = jsonData.PicUrl[0]
unfinishNAT.hasPic = true
if (unfinishNAT.hasAddress && unfinishNAT.hasPic) {
//提示用户完成操作
const msg = '感谢使用,您的抗原信息已添加完成!'
const xmlSendData = require('../../../tools/sendMsg')(jsonData.FromUserName[0], jsonData.ToUserName[0], msg)
await NAT.updateOne({userID: jsonData.FromUserName[0], isFinish: false},{$set:{
isFinish: true
}})
res.end(xmlSendData)
} else {
//提示用户继续补完信息
const msg = '请输入您所在的楼号和房号'
const xmlSendData = require('../../../tools/sendMsg')(jsonData.FromUserName[0], jsonData.ToUserName[0], msg)
res.end(xmlSendData)
}
} else {
next()
}
} else next()
})
module.exports = nATPicMW
以上的逻辑看起来复杂是因为要处理之前说的用户错误操作的逻辑,这个需要慢慢的梳理思路,不断调试添加,并不是直接一气呵成的。
我的做法是先写用户顺利按照提示操作的逻辑,然后再一条条添加用户错误操作的处理。
实现效果
正常流程
非正常操作1
非正常操作2
小结
- 这个公众号目前仅仅实现两个功能看到代码的复杂度已经很高了,所以分模块分文件的重要性,能帮助你的思路不会乱。按功能划分是逻辑性的直观体现。
- 就以上案例来说,对于收集的照片和房号,使用对话界面很难去人性化的展示给用户,目前仅仅是在后台的mongo compass中去整理,之后可以考虑写一个页面来展示,有了关键数据你想要做什么都可以。
- 这个案例主要是介绍如何使用mongoDB储存简单实现含有状态的会话,之后如果想要设计更复杂的会话,同样按照状态管理的思路去设计你的会话模型。
当然如果要实现智能聊天机器人,以公众号的方式实现起来会很复杂,建议使用微信对话开放平台,我会在将来的案例中去实现。