flask微信公衆號後端自動回覆及部署(2)

對於一個比較完備的微信自動回覆功能 有幾個需要注意的點需要提前說明
項目github:https://github.com/sph116/flask_WX

1.本篇實現的自動回覆功能,將會分離自動回覆的問題回答的配置信息。如下圖,可交給業務崗人員進行編輯。此模板文件以上轉至github

在這裏插入圖片描述
其中關鍵詞用來匹配用戶輸入的關鍵詞,以進入不同的回覆模塊。
不同模塊下,具有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()

相關演示
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章