公众号自定义菜单开发

2020-09-13  本文已影响0人  KEEPINUP

写在前面

因为前边给公众号添加智能对话机器人,启用了公众号后台服务器配置。然后原来的公众号的后台自定义菜单就失效了,所以没办法,我们也只能去自己开发了,也就有了这篇文章。

这篇文章会用到给你的公众号添加一个智能机器人的一些代码,所以没看过之前文章的同学可以先去看一下。

虽然自定义菜单的流程和代码都完成了,但是自定义菜单需要认证的公众号才行,目前个人的公众号认证功能正在逐步开放中,应该不久就都可以了,如果你和我一样还没有收到个人认证的通知,那么就耐心等待一段时间吧。

获取 access_token

因为在自定义菜单的开发中我们需要用到 access_token,所以我们需要首先获取到 access_token,后边很多其他的业务也需要用到 access_token。

这是公众号文档里对 access_token 的说明,我们先看一下。

access_token 是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token 的存储至少要保留 512 个字符空间。access_token 的有效期目前为 2 个小时,需定时刷新,重复获取将导致上次获取的access_token 失效。

公众平台的API调用所需的access_token的使用及生成方式我们需要遵循以下几个条件和说明:

接口调用请求说明

https请求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

返回参数说明

请求成功的话,我们会获得下面 Json 数据:

{"access_token":"ACCESS_TOKEN","expires_in":7200}

代码实现

我们创建一个类来获取和刷新 access_token,basic.py

import urllib
import time
import json


class Basic:
    def __init__(self):
        self.__accessToken = ''
        self.__leftTime = 0

    def __real_get_access_token(self):
        appId = "你的appId"
        appSecret = "你的appSecret"
        postUrl = ("https://api.weixin.qq.com/cgi-bin/token?grant_type="
                   "client_credential&appid=%s&secret=%s" % (appId, appSecret))
        urlResp = urllib.request.urlopen(postUrl)
        urlResp = json.loads(urlResp.read())
        print(urlResp)
        self.__accessToken = urlResp['access_token']
        self.__leftTime = urlResp['expires_in']
        print(self.__accessToken)

    # 外部获取 access_token 接口,同样 leftTime 如果小于十秒我们就刷新 access_token
    def get_access_token(self):
        if self.__leftTime < 10:
            self.__real_get_access_token()
        return self.__accessToken

    # 刷新 leftTime,如果小于十秒我们就刷新 access_token
    def run(self):
        while(True):
            if self.__leftTime > 10:
                time.sleep(2)
                self.__leftTime -= 2
            else:
                self.__real_get_access_token()

然后我们单独运行一个获取刷新 access_token 的程序。

accessToken.py

from basic import Basic

basic = Basic()


def getAccessToken():
    return basic.get_access_token()


if __name__ == "__main__":
    basic.run()

后面其他的业务需要 access_token,都通过这个 accessToken 的 getAccessToken 方法来获取。后台会自动刷新。

自定义菜单

我们需要的 access_token 已经拿到了,那么我们就可以正式开始菜单的开发了。

自定义菜单要求:

自定义菜单按钮类型:

接口调用请求说明

http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN

代码实现

下面我们通过代码来看一实现一个 click、view、media_id 三种类型的按钮。

menu.py

import urllib
import accessToken

class Menu(object):
    postJson = """
    {
        "button":
        [
            {
                "type": "click",
                "name": "开发指引",
                "key":  "mpGuide"
            },
            {
                "name": "公众平台",
                "sub_button":
                [
                    {
                        "type": "view",
                        "name": "更新公告",
                        "url": "http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1418702138&token=&lang=zh_CN"
                    },
                    {
                        "type": "view",
                        "name": "接口权限说明",
                        "url": "http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1418702138&token=&lang=zh_CN"
                    },
                    {
                        "type": "view",
                        "name": "返回码说明",
                        "url": "http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433747234&token=&lang=zh_CN"
                    }
                ]
            },
            {
                "type": "media_id",
                "name": "旅行",
                "media_id": "z2zOokJvlzCXXNhSjF46gdx6rSghwX2xOD5GUV9nbX4"
            }
          ]
    }
    """.encode('utf-8')

    def __init__(self):
        pass
    def create(self, accessToken):
        postUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s" % accessToken
        urlResp = urllib.urlopen(url=postUrl, data=self.postData)
        print urlResp.read()

    def query(self, accessToken):
        postUrl = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=%s" % accessToken
        urlResp = urllib.urlopen(url=postUrl)
        print urlResp.read()

    def delete(self, accessToken):
        postUrl = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=%s" % accessToken
        urlResp = urllib.urlopen(url=postUrl)
        print urlResp.read()
        
    #获取自定义菜单配置接口
    def get_current_selfmenu_info(self, accessToken):
        postUrl = "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info?access_token=%s" % accessToken
        urlResp = urllib.urlopen(url=postUrl)
        print urlResp.read()

现在自定义的菜单生成了,我们通过 click 类型的 button 为例,来处理当点击菜单时收到的消息。微信后台会推送一个 event 类型的 xml 给我们。

<xml>
    <ToUserName><![CDATA[toUser]]></ToUserName>
    <FromUserName><![CDATA[FromUser]]></FromUserName>
    <CreateTime>123456789</CreateTime>
    <MsgType><![CDATA[event]]></MsgType>
    <Event><![CDATA[CLICK]]></Event>
    <EventKey><![CDATA[EVENTKEY]]></EventKey>
</xml>

整个消息的流程图:

流程图

我们根据消息格式和流程来写代码。
修改 main.py

from flask import Flask
from flask import request
import hashlib
import re
import tuling
import receive
import reply
from menu import Menu
import accessToken


app = Flask(__name__)


@app.route("/")
def index():
    return "Hello World!"

# 公众号后台消息路由入口
@app.route("/wechat", methods=["GET", "POST"])
def wechat():
    # 验证使用的GET方法
    if request.method == "GET":
        signature = request.args.get('signature')
        timestamp = request.args.get('timestamp')
        nonce = request.args.get('nonce')
        echostr = request.args.get('echostr')
        token = "公众号后台填写的token"

        # 进行排序
        dataList = [token, timestamp, nonce]
        dataList.sort()
        result = "".join(dataList)

        #哈希加密算法得到hashcode
        sha1 = hashlib.sha1()
        sha1.update(result.encode("utf-8"))
        hashcode = sha1.hexdigest()

        if hashcode == signature:
            return echostr
        else:
            return ""
    else:
        recMsg = receive.parse_xml(request.data)
        if isinstance(recMsg, receive.Msg):
            toUser = recMsg.FromUserName
            fromUser = recMsg.ToUserName
            if recMsg.MsgType == 'text':
                content = recMsg.Content
                # userId 长度小于等于32位
                if len(toUser) > 31:
                    userid = str(toUser[0:30])
                else:
                    userid = str(toUser)
                userid = re.sub(r'[^A-Za-z0-9]+', '', userid)
                tulingReplay = tuling.tulingReply(content, userid)
                replyMsg = reply.TextMsg(toUser, fromUser, tulingReplay)
                return replyMsg.send()
            elif recMsg.MsgType == 'image':
                mediaId = recMsg.MediaId
                replyMsg = reply.ImageMsg(toUser, fromUser, mediaId)
                return replyMsg.send()
        if isinstance(recMsg, receive.EventMsg):
            if recMsg.Event == 'subscribe':
                subscribe_reply = "终于等到你了。~\n" \
                                      "在这里,我们可以一起学习知识,\n" \
                                      "一起努力成长。\n" \
                                      "你烦闷时,我还可以陪你聊天解闷哦~"
                replyMsg = reply.TextMsg(toUser, fromUser, subscribe_reply)
                return replyMsg.send()
            elif recMsg.Event == 'CLICK':
                if recMsg.Eventkey == 'mpGuide':
                    content = u"编写中,尚未完成".encode('utf-8')
                    replyMsg = reply.TextMsg(toUser, fromUser, content)
                    return replyMsg.send()
            elif recMsg.Event == 'VIEW':
                pass

        return reply.Msg().send()

if __name__ == "__main__":
    menu = Menu()
    access_token = accessToken.getAccessToken()
    menu.create(access_token)
    app.run(host='0.0.0.0', port=80)    #公众号后台只开放了80端口

修改 receive.py:

import xml.etree.ElementTree as ET

def parse_xml(receiveData):
    if len(receiveData) == 0:
        return None
    xmlData = ET.fromstring(receiveData)
    msgType = xmlData.find('MsgType').text
    if msgType == 'text':
        return TextMsg(xmlData)
    elif msgType == 'image':
        return ImageMsg(xmlData)
    elif msgType == 'event':
        event_type = xmlData.find('Event').text
        if event_type in ('subscribe', 'unsubscribe'):
            return Subscribe(xmlData)
        elif event_type == 'CLICK':
            return Click(xmlData)
        elif event_type == 'VIEW':
            return View(xmlData)

class Msg(object):
    def __init__(self, xmlData):
        self.ToUserName = xmlData.find('ToUserName').text
        self.FromUserName = xmlData.find('FromUserName').text
        self.CreateTime = xmlData.find('CreateTime').text
        self.MsgType = xmlData.find('MsgType').text
        self.MsgId = xmlData.find('MsgId').text

class TextMsg(Msg):
    def __init__(self, xmlData):
        Msg.__init__(self, xmlData)
        self.Content = xmlData.find('Content').text

class ImageMsg(Msg):
    def __init__(self, xmlData):
        Msg.__init__(self, xmlData)
        self.PicUrl = xmlData.find('PicUrl').text
        self.MediaId = xmlData.find('MediaId').text

class EventMsg(object):
    def __init__(self, xmlData):
        self.ToUserName = xmlData.find('ToUserName').text
        self.FromUserName = xmlData.find('FromUserName').text
        self.CreateTime = xmlData.find('CreateTime').text
        self.MsgType = xmlData.find('MsgType').text
        self.Event = xmlData.find('Event').text

class Subscribe(EventMsg):
    def __init__(self, xmlData):
        EventMsg.__init__(self, xmlData)

class Click(EventMsg):
    def __init__(self, xmlData):
        EventMsg.__init__(self, xmlData)
        self.EventKey = xmlData.find('EventKey').text

class View(EventMsg):
    def __init__(self, xmlData):
        EventMsg.__init__(self, xmlData)
        self.EventKey = xmlData.find('EventKey').text
        self.MenuId = xmlData.find('MenuId').text

然后我们重启后台服务器,就可以测试我们的自定义菜单了,我们上边只对 click 的事件进行了处理,view 类型、media_id 类型的本身就更容易实现,我们这里就不详细展开这两种类型了,其中 media_id 类型的需要一个 media_id 的参数,也就是你公众号后台的素材的 id,我们可以参考微信公众号开发文档中的素材获取来获得。

好了,我们的自定义菜单到这就完成了,我们可以根据我们自己公众号的不同需求来定义自己的菜单了。

上一篇下一篇

猜你喜欢

热点阅读