Django实现微信公众号简单自动回复

  在上篇博客阿里云部署django实现公网访问已经实现了了django在阿里云上的部署,接下来记录django实现微信公众号简单回复的开发过程,以方便日后查看

  内容概要:

  (1)微信公众号声请

  (2)微信公众号开发者配置

  (3)文本回复实现

  (4)图片回复实现

1. 微信公众号声请

  微信公众号的申请就不作介绍了,参考微信公众平台开发者文档中的入门指引

2. 微信公众号开发者配置

  开发者配置是微信公众号开发的第一步,显得极其重要

  公众平台官网登录之后,找到“基本配置”菜单栏,如下图:

  

  重点说明URL(服务器地址的配置),即与微信服务器直接通讯的服务器地址,我这里设置的是http://外网ip/wx/

  同时django中的配置如下:(说明:我的django工程为mysite,微信应用为wechat)

  (1)mysite目录下的urls.py配置如下

#from django.contrib import admin
#from django.urls import path
#from django.conf.urls import include,url

from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^blog/', include(('blog.urls',"blog"),namespace="blog")),
    url(r'^account/', include(('account.urls','account'),namespace='account')),
    url(r'^wx/', include(('wechat.urls','wechat'),namespace='wechat')),
]

  (2)wechat目录下的urls.py配置如下

from django.conf.urls import url
from .views import WeChat

urlpatterns = [url(r'^$', WeChat.as_view())]

   注:第一次我的URL配置为http://外网ip/wx,但在进行微信回复时提示"You called this URL via POST, but the URL doesn't end in a slash and you have APPEND_SL.....",百度后将修改settings:APPEND_SLASH=False也没有成功,后将配置改为http://外网ip/wx/成功了,若大家遇到同样的问题,可以多做尝试,主要原因还是因为表单的提交要将from的action地址改为/结尾

  (3)token验证

  token验证流程如下图:

  

  代码实现:

# Create your views here.
# -*- coding: utf-8 -*-
from django.shortcuts import render

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import View
from django.template import loader, Context
from xml.etree import ElementTree as ET
import time
import hashlib
from .analysis import Analysis
from django.utils.encoding import smart_str

class WeChat(View):
  #这里我当时写成了防止跨站请求伪造,其实不是这样的,恰恰相反。因为django默认是开启了csrf防护中间件的
  #所以这里使用@csrf_exempt是单独为这个函数去掉这个防护功能。
    @csrf_exempt
    def dispatch(self, *args, **kwargs):
        return super(WeChat, self).dispatch(*args, **kwargs)

    #微信的介入验证是GET方法
    #微信正常的收发消息是POST方法
    @csrf_exempt
    def get(self, request):
        print("welcome wx")
        #下面这四个参数是在接入时,微信的服务器发送过来的参数
        signature = request.GET.get('signature', None)
        #print(signature)
        timestamp = request.GET.get('timestamp', None)
        nonce = request.GET.get('nonce', None)
        echostr = request.GET.get('echostr', None)

        #这个token是我们自己来定义的,并且这个要填写在开发文档中的Token的位置
        token = 'fateli'

        #把token,timestamp, nonce放在一个序列中,并且按字符排序
        hashlist = [token, timestamp, nonce]
        hashlist.sort()

        #将上面的序列合成一个字符串
        hashstr = ''.join([s for s in hashlist])

        #通过python标准库中的sha1加密算法,处理上面的字符串,形成新的字符串。
        s1 = hashlib.sha1()
        s1.update(hashstr.encode("utf8")) 
        hashstr = s1.hexdigest()
        #print(hashstr)
        #把我们生成的字符串和微信服务器发送过来的字符串比较,
        #如果相同,就把服务器发过来的echostr字符串返回去
        if hashstr == signature:
            return HttpResponse(echostr)
        else:
            return HttpResponse("field")

   配置成功后就可以开始后续的消息回复工作了。若出现为问题,一定要仔细阅读开发者文档说明。

3. 文本回复实现

  回复的实现主要是要清除协议,其后就很简单了。

(1)接受文本格式

<xml>
<ToUserName><![CDATA[公众号]]></ToUserName>
 <FromUserName><![CDATA[粉丝号]]></FromUserName>
 <CreateTime>1460537339</CreateTime>
 <MsgType><![CDATA[text]]></MsgType>
 <Content><![CDATA[欢迎开启公众号开发者模式]]></Content>
 <MsgId>6272960105994287618</MsgId>
 </xml>

(2)回复文本格式

<xml>
 <ToUserName><![CDATA[粉丝号]]></ToUserName>
 <FromUserName><![CDATA[公众号]]></FromUserName>
 <CreateTime>1460541339</CreateTime>
 <MsgType><![CDATA[text]]></MsgType>
 <Content><![CDATA[test]]></Content>
 </xml>

(3)代码实现

  新建analysis.py

from xml.etree import ElementTree as ET
import time

class Analysis:
    def __init__(self, xmlData):
        print("接收到的数据:" + xmlData)

    def prase(self, xmlText):
        xmlData = ET.fromstring(xmlText)
        msgType = xmlData.find("MsgType").text
        toUserName = xmlData.find("ToUserName").text
        fromUserName= xmlData.find("FromUserName").text

        if msgType == 'text':
            content = xmlData.find("Content").text

            TextMsgObj = TextMsg(toUserName, fromUserName, content)
            return TextMsgObj.structReply()

        elif msgType == 'image':
            mediaId = xmlData.find("MediaId").text

            ImageMsgObj = ImageMsg(toUserName,fromUserName,mediaId)
            return ImageMsgObj.structReply()

class TextMsg:
    def __init__(self,toUser,fromUser,recvMsg):
        self._toUser = toUser
        self._fromUser = fromUser
        self._recvMsg = recvMsg
        self._nowTime = int(time.time())

    def structReply(self):
        content = self._recvMsg
        text = """
                <xml>
                <ToUserName><![CDATA[{0}]]></ToUserName>
                <FromUserName><![CDATA[{1}]]></FromUserName>
                <CreateTime>{2}</CreateTime>
                <MsgType><![CDATA[text]]></MsgType>
                <Content><![CDATA[{3}]]></Content>
                </xml>
                """.format(self._fromUser, self._toUser,self._nowTime,content)   #前面两个参数的顺序需要特别注意

        return text

   POST代码如下:

@csrf_exempt
    def post(self, request):
        print("POST请求")
        analysisObj = Analysis(smart_str(request.body))
        toWxData = analysisObj.prase(smart_str(request.body))
        print(toWxData)
        return HttpResponse(smart_str(toWxData))

4. 图片回复实现

  实现了文本回复后图片恢复也就很简单了,过程一样,只是协议字段有区别

(1)接受文本格式

<xml>
<ToUserName><![CDATA[公众号]]></ToUserName>
 <FromUserName><![CDATA[粉丝号]]></FromUserName>
 <CreateTime>1460536575</CreateTime>
 <MsgType><![CDATA[image]]></MsgType>
 <PicUrl><![CDATA[http://mmbiz.qpic.cn/xxxxxx /0]]></PicUrl>
 <MsgId>6272956824639273066</MsgId>
 <MediaId><![CDATA[gyci5a-xxxxx-OL]]></MediaId>
 </xml>

(2)回复文本格式

<xml>
 <ToUserName><![CDATA[粉丝号]]></ToUserName>
 <FromUserName><![CDATA[公众号]]></FromUserName>
 <CreateTime>1460536576</CreateTime>
 <MsgType><![CDATA[image]]></MsgType>
 <Image>
 <MediaId><![CDATA[gyci5oxxxxxxv3cOL]]></MediaId>
 </Image>
 </xml>

注意回复文本格式中只有MediaId,后续博客进行说明

(3)代码实现

class ImageMsg:
    def __init__(self,toUser,fromUser,mediaId):
        self._toUser = toUser
        self._fromUser = fromUser
        self._rediaId = mediaId
        self._nowTime = int(time.time())
        self._mediaId = mediaId

    def structReply(self):
        text = """
                <xml>
                <ToUserName><![CDATA[{0}]]></ToUserName>
                <FromUserName><![CDATA[{1}]]></FromUserName>
                <CreateTime>{2}</CreateTime>
                <MsgType><![CDATA[image]]></MsgType>
                <Image>
                <MediaId><![CDATA[{3}]]></MediaId>
                </Image>
                </xml>
                """.format(self._fromUser, self._toUser,self._nowTime,self._mediaId)   #前面两个参数的顺序需要特别注意

        return text

  

  在开发过程中遇到问题,可以使用微信公众平台提供的在线接口调试工具。

原计划是继续进行菜单项的开发,但由于是个人订阅号,无法卡通认证,也就无法获取API开发权限,目前只能到此。
posted @ 2018-10-22 18:01  Fate0729  阅读(4818)  评论(0编辑  收藏  举报