侧边栏壁纸
博主头像
神的孩子都在跳舞博主等级

HARD WORK PAYS OFF

  • 累计撰写 22 篇文章
  • 累计创建 64 个标签
  • 累计收到 0 条评论

微信公众号 | 数据的存储 (下)

神的孩子都在跳舞
2022-04-28 / 0 评论 / 0 点赞 / 1,379 阅读 / 5,575 字 / 正在检测是否收录...

本篇的主题

  • 连续性会话设计
  • 微信服务器对图片消息的处理

1_135947_1.png

需求分析

在上一篇文章中,使用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

非正常操作1
非正常操作1

非正常操作2

非正常操作2

小结

  • 这个公众号目前仅仅实现两个功能看到代码的复杂度已经很高了,所以分模块分文件的重要性,能帮助你的思路不会乱。按功能划分是逻辑性的直观体现。
  • 就以上案例来说,对于收集的照片和房号,使用对话界面很难去人性化的展示给用户,目前仅仅是在后台的mongo compass中去整理,之后可以考虑写一个页面来展示,有了关键数据你想要做什么都可以。
  • 这个案例主要是介绍如何使用mongoDB储存简单实现含有状态的会话,之后如果想要设计更复杂的会话,同样按照状态管理的思路去设计你的会话模型。
    当然如果要实现智能聊天机器人,以公众号的方式实现起来会很复杂,建议使用微信对话开放平台,我会在将来的案例中去实现。
0
博主关闭了所有页面的评论