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