对于一个比较完备的微信自动回复功能 有几个需要注意的点需要提前说明

1.本篇实现的自动回复功能,将会分离自动回复的问题回答的配置信息。如下图,可交给业务岗人员进行编辑。此模板文件以上转至github

微信好友自动回复 java 微信自动回复 github_微信好友自动回复 java


其中关键词用来匹配用户输入的关键词,以进入不同的回复模块。

不同模块下,具有1级问题及2级问题,输入一级问题序号返回一级问题下的二级问题列表,输入二级问题序号,返回问题所匹配的答案。

2.每次用户发起的请求,微信都会对后端重复发起三次请求。若第一次请求未来得及返回数据,又再次接到第二次请求,将会导致重复的返回信息。所以我们需要对于接收的请求进行去重,重复的请求我们将只返回一条数据.。
微信的请求,会带上用户的id及时间戳,我的实现方式是使用redis进行去重判断。将用户id+时间戳作为key,每次接收请求若redis不存在此key则正常返回,若redis存在此key则返回空值。

3.我们根据序号来返回相关问题,但显然不同关键词模块下都有相同的序号。所以我们需要判断用户上一次输入,是在哪个关键词模块。这一部分的实现方式依然是使用redis,将用户名作为key值,用户所在的关键词模块为value,并设置3分钟左右的失效时间。每次接收到请求,根据用户id查找redis是否存在值吗,若不存在。则返回数据让用户重新选择输入关键词,若存在值。则进入相关的关键词模块。

相关源码:
flask app.py 使用gevent进行协程封装 提升并发量

from gevent import monkey; monkey.patch_all()   # 猴子补丁打在最前面 
import difflib
from gevent import pywsgi
from flask import Flask
from flask import request, jsonify
from wechatpy.utils import check_signature
from wechatpy.exceptions import InvalidSignatureException
from wechatpy.replies import TextReply, ArticlesReply
from wechatpy import parse_message
from Check_duplication import Redis
import pandas as pd
import re

app = Flask(__name__)

def Reply_text(msg, reply_text):
    """
    用于回复文本数据
    :param msg:
    :param reply_text:
    :return:
    """
    reply_data = TextReply(content=reply_text, message=msg)
    reply_xml = reply_data.render()
    return reply_xml

def Reply_Article(msg, reply_dic):
    reply = ArticlesReply(message=msg)
    reply.add_article(reply_dic)
    return reply.render()


def extract_que(que_path):
    """
    提取所有问题回复数据
    返回问题分级字典 和答案字典
    :param que_path:
    :return:
    """
    que_df = pd.read_excel(que_path)

    que_dict = {}  # 序号对应问题字典
    ans_dict = {}  # 问题对应答案字典
    for column in que_df:
        que_dict[column] = {}

        for que in list(que_df[column]):
            if type(que) == str:
                que = que.replace("\n", "")

                if "  " in que:
                    ques = que.split("答案:")[0]
                    ques = ques.replace(re.search("(\d+(\.\d+)?)", ques).group(), '').strip()
                    ans = que.split("答案:")[1]
                    ans_dict[ques] = ans.strip()

                else:
                    que_index = que.split(".")[0]
                    if len(que_index) == 2:
                        que = que[3:]
                    else:
                        que = que[2:]

                    que_dict[column][que_index] = {}

                    # que_dict[column][que_index][que] = {j[2: 5]: j.split('\n')[0][5:] for j in list(que_df[column]) if "  " in j and que_index+"." in j}
                    que_dict[column][que_index][que] = {}

                    ls = list(que_df[column])

                    # if que_index == "3":
                    #     pass
                    for j in ls:
                        if type(j) == str:
                            if "  " in j and que_index + "." in j:
                                j = j.strip()
                                if re.search("(\d+(\.\d+)?)", j).group().split('.')[0] != que_index:
                                    pass
                                else:
                                    if len(que_index) == 2:
                                        que_dict[column][que_index][que][j[0: 4]] = j.split('\n')[0][4:]
                                    else:
                                        que_dict[column][que_index][que][j[0: 3]] = j.split('\n')[0][3:]

    return que_dict, ans_dict

@app.route('/check_token', methods=['GET'])
def Check_token():
    """
    用来验证微信公众号后台链接
    :return:
    """
    rq_dict = request.args
    if len(rq_dict) == 0:
        return ""
    signature = request.args.get('signature')   # 提取请求参数
    timestamp = request.args.get('timestamp')
    nonce = request.args.get('nonce')
    echostr = request.args.get('echostr')
    try:
        check_signature(token='jxgj8888', signature=signature, timestamp=timestamp, nonce=nonce)  # 使用wechat库验证
    except InvalidSignatureException as e:
        return ''
    else:
        return echostr  # 返回数据




@app.route('/check_token', methods=['POST'])
def Reply_user():
    """
    用于自动回复客服消息
    :return:
    """

    que_dict, ans_dict = extract_que('问题数据路径')   # 加载问题及回复信息
    req_key_word = que_dict.keys()                                                  # 所有可回复的关键词

    wechat_send_data = request.data                           # 接收消息提醒 为xml格式
    msg = parse_message(wechat_send_data)                     # wechat模块解析数据
    FromUserName = msg.source           # 消息的发送用户
    CreateTime = msg.create_time        # 消息的创建时间
    ToUserName = msg.target             # 消息的目标用户


    duplication_flag = Redis.check_duplication("{}{}".format(FromUserName, CreateTime))  # 消息查重
    if duplication_flag == 1:
        pass
    else:
        print("推送重复")
        return ''   # 若重复 返回1

    if msg.type == "event":               # 为事件消息
        if msg.event == "subscribe":      # 关注事件回复
            return Reply_text(msg, '====自动回复=====\n   欢迎关注, 回复 {} 即可获取相关信息。').format("、".join(req_key_word))
        elif msg.event == "unsubscribe":  # 取关事件回复
            return Reply_text(msg, "====自动回复=====\n  下次再见。")
        elif msg.event == "click" and msg.key == "zhinengkefu":
            return Reply_text(msg, '====自动回复=====\n  回复 {} 即可获取相关信息。').format("、".join(req_key_word))
        else:
            return ''


    elif msg.type == "text":              # 为文本消息

        text_type = Redis.save_uesr_rec(FromUserName)     # 查询用户上次五分钟之内的浏览记录
        send_text = msg.content  # 用户发送的文本消息

        if text_type != False:     # 用户具有上次浏览记录
            text_type = text_type.decode("utf-8")

            if send_text in req_key_word:   # 如果输入为关键词
                Redis.save_uesr_rec(user_id=FromUserName, type='save', rec=send_text)   # 存储本次浏览记录
                ques = que_dict[send_text]
                reply_text = '====自动回复=====\n{}\n  请回复问题前序号, 例"1"'.format(
                    ''.join(["{}.{}\n".format(i, list(ques[i].keys())[0]) for i in ques]))
                return Reply_text(msg, reply_text)

            elif text_type in req_key_word:  # 具有上次浏览记录
                if re.search("(\d+(\.\d+)?)", send_text):   # 如果可以提取出数字
                    Redis.save_uesr_rec(user_id=FromUserName, type='save', rec=text_type)  # 存储本次浏览记录
                    ques_index = re.search("(\d+(\.\d+)?)", send_text).group()
                    try:
                        if "." not in ques_index:  # 询问1级标题
                            ques = list(que_dict[text_type][str(send_text)].values())[0]
                            reply_text = '====自动回复=====\n{}\n  请回复问题前序号, 例"1.1"'.format(
                                ''.join(["{}{}\n".format(i, ques[i]) for i in ques]))
                        else:       # 询问二级标题
                            ques = list(que_dict[text_type][str(send_text.split('.')[0])].values())[0]
                            que_ans = ans_dict[ques[str(send_text)]]
                            
                                return Reply_Article(msg, reply_dic)
                            reply_text = '====自动回复=====\n{}\n'.format(que_ans)
                    except Exception as e:
                        print("失败 输入信息为 {}-{} {}".format(send_text, text_type, e))
                        reply_text = '====自动回复=====\n  我不懂您的意思, 请回复 {} 即可获取相关信息。'.format("、".join(req_key_word))

                else:  # 无法提取出数字 首先进行模糊匹配 匹配失败 返回 如下
                    pro_que = difflib.get_close_matches(send_text, ans_dict.keys(), 1, cutoff=0.6)
                    if pro_que == []:
                        reply_text = '====自动回复=====\n  我不懂您的意思, 请回复 {} 即可获取相关信息。'.format("、".join(req_key_word))
                    else:
                        que = pro_que[0]
                        que_ans = ans_dict[que]
                        reply_text = '====自动回复=====\n 请问您要询问的问题是否是?\n {} \n回复:{}'.format(que, que_ans)
                return Reply_text(msg, reply_text)


        else:      #  用户不具有上次浏览记录
            if send_text in req_key_word:   # 根据用户回复关键字 返回相关问题
                Redis.save_uesr_rec(user_id=FromUserName, type='save', rec=send_text)   # 存储本次浏览记录
                ques = que_dict[send_text]
                reply_text = '====自动回复=====\n{}\n  请回复问题前序号, 例"1"'.format(
                    ''.join(["{}.{}\n".format(i, list(ques[i].keys())[0]) for i in ques]))
            else:  # 若无关键字 首先进行模糊匹配 匹配失败 返回建议信息

                pro_que = difflib.get_close_matches(send_text, ans_dict.keys(), 1, cutoff=0.6)
                if pro_que == []:
                    reply_text = '====自动回复=====\n  我不懂您的意思, 请回复 {} 即可获取相关信息。'.format("、".join(req_key_word))
                else:
                    que = pro_que[0]
                    que_ans = ans_dict[que]
                    reply_text = '====自动回复=====\n 请问您要询问的问题是否是?\n {} \n回复:{}'.format(que, que_ans)
            return Reply_text(msg, reply_text)


if __name__ == '__main__':
    app.debug = True  # 1.0以后版本不通过本方法启动调试模式
    server = pywsgi.WSGIServer(('0.0.0.0', 80), app)
    server.serve_forever()

    # app.run(debug=True, processes=True)

redis操作模块 Check_duplication.py

import redis

class Operation_Redis():

    """
    redis 数据库连接池 及增删改查
    """

    def __init__(self):
        self.host = ""
        self.psw = ""
        self.port = 6379
        self.db = 1
        self.pool = redis.ConnectionPool(host=self.host, password=self.psw, port=self.port, db=self.db, max_connections=50)

    def check_duplication(self, username_time):
        """
        检测键值是否存在于redis 若存在 若存在返回空字符串 不存在则插入
        :param username_time:
        :return:
        """
        r = redis.Redis(connection_pool=self.pool, decode_responses=True)
        flag = r.exists(username_time, '')    # 判断键值是否存在   hexists
        if flag == 0:
            r.set(username_time, '', ex=86400)  # 存储
            return 1
        elif flag == 1:
            return ''


    def save_uesr_rec(self, user_id, type='', rec=''):
        """
        判断用户上一次停留位置
        用户查看问题后 将位置存储mysql 超过五分钟删除
        下次查看 若大于5分钟 则返回重新浏览
        小于 则进入上次停留位置
        :param user_id:
        :param rec:
        :return:
        """
        r = redis.Redis(connection_pool=self.pool, decode_responses=True)
        flag = r.exists(user_id)  # 判断键是否存在   # hexists
        if flag == 1:
            rec = r.get(user_id)
            r.delete(user_id)
            if type != "":
                r.set(user_id, rec, ex=300)

            return rec
        else:
            if type != "":
                r.set(user_id, rec, ex=300)
            return False

Redis = Operation_Redis()

相关演示

微信好友自动回复 java 微信自动回复 github_自动回复_02