Django实现微信公众号简单自动回复(复读机)

Posted Fate0729

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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开发权限,目前只能到此。

以上是关于Django实现微信公众号简单自动回复(复读机)的主要内容,如果未能解决你的问题,请参考以下文章

微信开发公众号自动回复文字和图文链接

基于springboot微信公众号开发,几分钟学会微信自动回复

微信公众号自动回复加超链接最新可用实现方案

微信公众号输入关键字后返回一个H5分享链接怎么实现?

微信公众号接入图灵机器人实现自动回复消息

JAVA微信公众号开发回复消息能回复多条吗?具体怎么代码实现?