目录
前言
能有这篇文章纯属我的无聊之举。微信公众平台的接入文档里采用的是python的web.py框架(一个很小很轻量级的web框架,但是网上的资料和官网的资料都太少了),本文在接入文档的代码里稍作改动,使其成为一个django项目便于运行。
微信公众平台接入文档链接
关于微信平台转发的xml消息的结构等其他问题在这里都有很详尽的解释:
原web.py框架下代码
1 main.py
# -*- coding: utf-8 -*-
# filename: main.py
import web
from handle import Handle
urls = (
'/wx', 'Handle',
)
if __name__ == '__main__':
app = web.application(urls, globals())
app.run()
2 handle.py(验证服务器配置)
# -*- coding: utf-8 -*-
# filename: handle.py
import hashlib
import web
class Handle(object):
def GET(self):
try:
data = web.input()
if len(data) == 0:
return "hello, this is handle view"
signature = data.signature
timestamp = data.timestamp
nonce = data.nonce
echostr = data.echostr
token = "xxxx" #请按照公众平台官网\基本配置中信息填写
list = [token, timestamp, nonce]
list.sort()
sha1 = hashlib.sha1()
map(sha1.update, list)
hashcode = sha1.hexdigest()
print "handle/GET func: hashcode, signature: ", hashcode, signature
if hashcode == signature:
return echostr
else:
return ""
except Exception, Argument:
return Argument
3 handle.py(正式)
# -*- coding: utf-8 -*-#
# filename: handle.py
import hashlib
import reply
import receive
import web
class Handle(object):
def POST(self):
try:
webData = web.data()
print "Handle Post webdata is ", webData
#后台打日志
recMsg = receive.parse_xml(webData)
if isinstance(recMsg, receive.Msg) and recMsg.MsgType == 'text':
toUser = recMsg.FromUserName
fromUser = recMsg.ToUserName
content = "test"
replyMsg = reply.TextMsg(toUser, fromUser, content)
return replyMsg.send()
else:
print "暂且不处理"
return "success"
except Exception, Argment:
return Argment
4 receive.py
# -*- coding: utf-8 -*-#
# filename: receive.py
import xml.etree.ElementTree as ET
def parse_xml(web_data):
if len(web_data) == 0:
return None
xmlData = ET.fromstring(web_data)
msg_type = xmlData.find('MsgType').text
if msg_type == 'text':
return TextMsg(xmlData)
elif msg_type == 'image':
return ImageMsg(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.encode("utf-8")
class ImageMsg(Msg):
def __init__(self, xmlData):
Msg.__init__(self, xmlData)
self.PicUrl = xmlData.find('PicUrl').text
self.MediaId = xmlData.find('MediaId').text
5 reply.py
# -*- coding: utf-8 -*-#
# filename: reply.py
import time
class Msg(object):
def __init__(self):
pass
def send(self):
return "success"
class TextMsg(Msg):
def __init__(self, toUserName, fromUserName, content):
self.__dict = dict()
self.__dict['ToUserName'] = toUserName
self.__dict['FromUserName'] = fromUserName
self.__dict['CreateTime'] = int(time.time())
self.__dict['Content'] = content
def send(self):
XmlForm = """
<xml>
<ToUserName><![CDATA[{ToUserName}]]></ToUserName>
<FromUserName><![CDATA[{FromUserName}]]></FromUserName>
<CreateTime>{CreateTime}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[{Content}]]></Content>
</xml>
"""
return XmlForm.format(**self.__dict)
class ImageMsg(Msg):
def __init__(self, toUserName, fromUserName, mediaId):
self.__dict = dict()
self.__dict['ToUserName'] = toUserName
self.__dict['FromUserName'] = fromUserName
self.__dict['CreateTime'] = int(time.time())
self.__dict['MediaId'] = mediaId
def send(self):
XmlForm = """
<xml>
<ToUserName><![CDATA[{ToUserName}]]></ToUserName>
<FromUserName><![CDATA[{FromUserName}]]></FromUserName>
<CreateTime>{CreateTime}</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<Image>
<MediaId><![CDATA[{MediaId}]]></MediaId>
</Image>
</xml>
"""
return XmlForm.format(**self.__dict)
改造项目之前的解析
首先,django项目并不需要main.py文件,所以我们可以将其删去。main.py里的路径映射移到django项目目录下的urls.py就可以。
handle.py在web.py中承担了接受数据和返回视图的作用,而django项目中承担此功能的部分是app目录下的views.py。
receive.py与reply.py作为处理微信公众平台转发的xml数据的部分本身不需要更改,但由于我们可能把项目转接到一个大的django项目下,为防止重名且明晰用途,我们将这两个文件重命名为wxRecieve.py与wxReply.py。
改造代码
1 建立django项目和app
这一步骤我不详讲,网上的教程可以说一搜一大把,或者也可以去看看笔者写的django教程。
在本文中,我们默认读者已经知道django是如何运作的,至少基本上能够看懂python。
记得在settings.py中加上你的app。
2 项目路径
我们设定django项目名为djagowx,app名为wxrun,则整个目录的结构如下:
djangowx
│ db.sqlite3
│ manage.py
│
├─djangowx
│ │ asgi.py
│ │ settings.py
│ │ urls.py
│ │ wsgi.py
│ │ __init__.py
│ │
│ └─__pycache__
│ /
│
└─wxrun
│ admin.py
│ apps.py
│ models.py
│ tests.py
│ urls.py
│ views.py
│ __init__.py
│
├─migrations
│ │ __init__.py
│ │
│ └─__pycache__
│ /
│
└─__pycache__
/
3 修改项目文件
其余文件不做改动,我们在项目文件夹下新建两个文件wxRecieve.py与wxReply.py,内容复制于接入文档里的receive.py、reply.py,负责读取与生成xml消息。
修改app目录下的views.py:
import hashlib
from django.http import HttpResponse
import wxReceive as receive
import wxReply as reply
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def token(request):
if(request.method=='GET'):
signature=request.GET['signature']
timestamp=request.GET['timestamp']
nonce=request.GET['nonce']
echostr=request.GET['echostr']
token="mytheodorwx20200923"
list = [token, timestamp, nonce]
list.sort()
sha1 = hashlib.sha1()
map(sha1.update, list)
hashcode = sha1.hexdigest()
print("handle/GET func: hashcode, signature: ", hashcode, signature)
if hashcode == signature:
return HttpResponse(echostr)
else:
return HttpResponse("Error Response")
else:
Recxml=receive.parsexml(request.body) #读取信息,至于为什么不是POST,是因为这不是字典也不是表单
if(isinstance(Recxml,receive.Msg) and Recxml.MsgType=="text"):
toUser = Recxml.FromUserName
fromUser = Recxml.ToUserName
'''
你的逻辑判断和返回模块(拿文字内容和文字返回内容举例)
content="你要返回的数据"
'''
replyMsg = reply.TextMsg(toUser, fromUser, content)
return HttpResponse(reply.send())
修改项目文件夹下的urls.py:
from django.urls import path
from wxrun import views
urlpatterns=[
path('你的url路径',views.token),
]
#4 关于验证服务器使用的加密设置
不知道是不是我的操作有问题,按照接入文档进行排序然后sha1加密生成的hashcode并不等于signature。由于token和服务器网址都在后台填写,并且验证服务器只需要返回echostr,本质上没有特别大的安全风险,所以我将views.py的代码进行了部分删减,如果有同样问题的可以使用如下代码:
import hashlib
from django.http import HttpResponse
import wxReceive as receive
import wxReply as reply
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def token(request):
if(request.method=='GET'):
echostr=request.GET['echostr']
return HttpResponse(echostr)
else:
Recxml=receive.parsexml(request.body) #读取信息,至于为什么不是POST,是因为这不是字典也不是表单
if(isinstance(Recxml,receive.Msg) and Recxml.MsgType=="text"):
toUser = Recxml.FromUserName
fromUser = Recxml.ToUserName
'''
你的逻辑判断和返回模块(拿文字内容和文字返回内容举例)
content="你要返回的数据"
'''
replyMsg = reply.TextMsg(toUser, fromUser, content)
return HttpResponse(reply.send())