話說好久之前開發了一次微信公衆號,那時用的是官方文檔推薦的框架 webpy 開發的,基本沒怎麼維護,加上最近雲服務器也到期了(主要是最近比較懶...)數據被清了纔想起來維護😂,人呀真是不能懈怠。python
最近又想起了搞事情了,準備再玩玩微信公衆號。選技術棧時猶豫了,是用繼續用 webpy 呢? 仍是搞個新的框架玩玩?調研了一番驚奇的發現 webpy 的做者居然去見 馬克思 老人家了,哎真是天妒英才!git
最終決定用 Django 做爲技術棧,本篇記錄下本身的開發歷程。因爲本人才疏學淺,不免會有不足之處,但願各位留言指正共同窗習進步。(本文參考《微信公衆號開發WiKi》)github
(1). 系統開發環境:Windows 10
(2). IntelliJ IDEA 2019.02
(3). Python 3.7.2
(4). django 2.2.3
(5). 部署環境:Ubuntu 18.04.1 LTS \n \l
複製代碼
# 安裝虛擬環境配置工具virtualenv
pip install virtualenv
# 配置虛擬環境
virtualenv env
# windows激活虛擬環境
source env/Scripts/activate
# 若是時Linux環境,則使用下面的命令
source env/bin/activate
# 退出虛擬環境
deactivate
# 導出環境配置所須要的第三方庫
pip freeze >> requirements.txt
複製代碼
# 安裝
pip install django
# 建立django 項目
django-admin startproject WePublic
# 建立開發微信公衆號app
cd WePublic
python manage.py startapp mypublic
複製代碼
# 至於用什麼代碼管理工具就無論了
# 這裏我用的是git + github, 配置什麼的就不詳細說了,這裏只是配置下.gitignore文件
echo env/ >> .gitignore
echo __pycache__/ >> .gitignore
複製代碼
具體的微信公衆號的申請就不描述了,咱們這裏直接進行開發配置。這會提交提交配置會報錯,別急,由於你的配置還沒和微信服務器進行交互呢! web
來先曬一下程序總體的目錄結構,本着實用原則,後續會用到那進行那塊的詳細描述,Django 的其餘用法後續會進行學習整理分享:算法
ubuntu@VM-0-8-ubuntu:~/WePublic$ tree -CF -L 2
.
├── db.sqlite3
├── env/
│ ├── bin/
│ ├── include/
│ └── lib/
├── manage.py
├── mypublic/
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations/
│ ├── models.py
│ ├── __pycache__/
│ ├── receive.py
│ ├── reply.py
│ ├── tests.py
│ └── views.py
├── README.md
├── requirements.txt
└── WePublic/
├── __init__.py
├── __pycache__/
├── settings.py
├── urls.py
└── wsgi.py
複製代碼
來再上一張微信微信消息發送的邏輯圖: sql
根據上面的微信消息發送邏輯,要完成微信認證須要進行一下操做:django
(1)接收到參數分別提取出 signature、timestamp、nonce、echostr 字段;
(2)將 token、timestamp、nonce 字段組成字典排序獲得 list;
(3)哈希算法加密list,獲得 hashcode;
(4)判斷 signature 和 hashcode 值是否相等,若是相等把 echostr 返回微信後臺,供微信後臺認證 token;不相等的話就不處理,返回個自定義的字符串;
(5)認證成功後,就能夠繼續其餘業務服務開發了。
複製代碼
GitHub代碼參考commits id:a7cf530ubuntu
# views.py
from django.shortcuts import HttpResponse
from django.views.decorators.csrf import csrf_exempt
import hashlib
# Create your views here.
# django默認開啓了csrf防禦,@csrf_exempt是去掉防禦
# 微信服務器進行參數交互,主要是和微信服務器進行身份的驗證
@csrf_exempt
def check_signature(request):
if request.method == "GET":
print("request: ", request)
# 接受微信服務器get請求發過來的參數
# 將參數list中排序合成字符串,再用sha1加密獲得新的字符串與微信發過來的signature對比,若是相同就返回echostr給服務器,校驗經過
# ISSUES: TypeError: '<' not supported between instances of 'NoneType' and 'str'
# 解決方法:當獲取的參數值爲空是傳空,而不是傳None
signature = request.GET.get('signature', '')
timestamp = request.GET.get('timestamp', '')
nonce = request.GET.get('nonce', '')
echostr = request.GET.get('echostr', '')
# 微信公衆號處配置的token
token = str("你在微信公衆號中配置的Token")
hashlist = [token, timestamp, nonce]
hashlist.sort()
print("[token, timestamp, nonce]: ", hashlist)
hashstr = ''.join([s for s in hashlist]).encode('utf-8')
print('hashstr before sha1: ', hashstr)
hashstr = hashlib.sha1(hashstr).hexdigest()
print('hashstr sha1: ', hashstr)
if hashstr == signature:
return HttpResponse(echostr)
else:
return HttpResponse("weixin index")
elif request.method == "POST":
# autoreply方法時用來回復消息的,此時能夠先將此處的兩行代碼修改爲return "success"
otherContent = autoreply(request)
return HttpResponse(otherContent)
else:
print("你的方法不正確....")
複製代碼
接下來在服務器端部署代碼並啓動代碼:windows
git pull github master
source env/bin/activate
pip install -r requirements.txt
sudo python manage.py runserver 0.0.0.0:80
複製代碼
這會再在微信公衆號開發配置頁面,點擊提交就能夠配置成功了。api
進行完微信服務器認證後,咱們來實現「你說我學」,即用戶發送文本消息給微信公衆號,公衆號模仿人發送消息返回給微信用戶粉絲,不須要經過公衆平臺網頁人爲的操做。
微信接口的消息時以 XML 格式接受和傳送的,因此首先進行接口消息的 XML 消息解析吧,具體的字段含義與用法請參考《官方文檔》,此處就再也不累述。官方文檔參考
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : receive.py @Time : 2019/8/6 10:15 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """
import xml.etree.ElementTree as ET
def parse_xml(webData):
if len(webData) == 0:
return None
xmlData = ET.fromstring(webData)
msg_type = xmlData.find('MsgType').text
if msg_type == 'text':
return TextMsg(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')
複製代碼
咱們上面解析了接受到了粉絲髮送過來的消息了,解析完咱們能拿到一些關鍵的數據字段(ToUserName、FromUserName、CreateTime、MsgType、MsgId),接下來能夠把這些字段組裝成回覆消息的 XML 文件模板,具體的字段含義與用法請參考《官方文檔》,此處就再也不累述。官方文檔參考
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : reply.py @Time : 2019/8/6 10:15 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : 回覆消息給關注微信公衆號的用戶 """
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)
複製代碼
接收到消息,若是斷定消息類型未文本消息,條用autoreply方法解析 XML 文件,組裝文本消息模板,將文本消息自動回覆給粉絲。
特別提醒:假如服務器沒法保證在五秒內處理回覆,則必須回覆「success」或者「」(空串),不然微信後臺會發起三次重試。解釋一下爲什麼有這麼奇怪的規定。發起重試是微信後臺爲了盡能夠保證粉絲髮送的內容開發者都可以收到。若是開發者不進行回覆,微信後臺沒辦法確認開發者已收到消息,只好重試。
# views.py
from django.shortcuts import HttpResponse
from django.views.decorators.csrf import csrf_exempt
import hashlib
from . import receive
from . import reply
# Create your views here.
# django默認開啓了csrf防禦,@csrf_exempt是去掉防禦
# 微信服務器進行參數交互,主要是和微信服務器進行身份的驗證
@csrf_exempt
def check_signature(request):
if request.method == "GET":
print("request: ", request)
# 接受微信服務器get請求發過來的參數
# 將參數list中排序合成字符串,再用sha1加密獲得新的字符串與微信發過來的signature對比,若是相同就返回echostr給服務器,校驗經過
# ISSUES: TypeError: '<' not supported between instances of 'NoneType' and 'str'
# 解決方法:當獲取的參數值爲空是傳空,而不是傳None
signature = request.GET.get('signature', '')
timestamp = request.GET.get('timestamp', '')
nonce = request.GET.get('nonce', '')
echostr = request.GET.get('echostr', '')
# 微信公衆號處配置的token
token = str("Txy159wx")
hashlist = [token, timestamp, nonce]
hashlist.sort()
print("[token, timestamp, nonce]: ", hashlist)
hashstr = ''.join([s for s in hashlist]).encode('utf-8')
print('hashstr before sha1: ', hashstr)
hashstr = hashlib.sha1(hashstr).hexdigest()
print('hashstr sha1: ', hashstr)
if hashstr == signature:
return HttpResponse(echostr)
else:
return HttpResponse("weixin index")
elif request.method == "POST":
otherContent = autoreply(request)
return HttpResponse(otherContent)
else:
print("你的方法不正確....")
def autoreply(request):
try:
webData = request.body
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 = recMsg.Content.decode('utf-8')
replyMsg = reply.TextMsg(toUser, fromUser, content)
return replyMsg.send()
else:
print("暫不處理")
return "success"
except Exception as e:
print(e)
複製代碼
GitHub代碼參考commits id:5f8581d 好的,下來就是在 Linux 服務器將該 Django 項目啓動起來。爲了測試咱們發送的消息是否能被接受發送,能夠調用 微信公衆號文本接口網頁調試工具,填寫參數參考截圖,成功調用接口返回成功結果顯示以下圖右半部分。
一樣這時候給公衆號發送文本消息能夠獲得本身想要返回結果,好噠,到這個階段咱們算是把微信公衆號開發的初步階段調試OK了。接下來咱們繼續進行更多服務的開發。咱們來實現「圖尚往來」,即用戶發送圖片消息給微信公衆號,公衆號被動發送相同的圖片消息給微信用戶粉絲,不須要經過公衆平臺網頁人爲的操做。
微信接口的消息時以 XML 格式接受和傳送的,因此首先進行接口消息的 XML 消息解析吧,具體的字段含義與用法請參考《官方文檔》,此處就再也不累述。官方文檔參考
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : receive.py @Time : 2019/8/6 10:15 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """
import xml.etree.ElementTree as ET
def parse_xml(webData):
if len(webData) == 0:
return None
xmlData = ET.fromstring(webData)
msg_type = xmlData.find('MsgType').text
if msg_type == 'text':
return TextMsg(xmlData)
elif msg_type == 'image':
return ImageMsg(xmlData)
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
複製代碼
咱們上面解析了接受到了粉絲髮送過來的消息了,解析完咱們能拿到一些關鍵的數據字段(ToUserName、FromUserName、CreateTime、MsgType、MsgId、MediaId),接下來能夠把這些字段組裝成回覆消息的 XML 文件模板,具體的字段含義與用法請參考《官方文檔》,此處就再也不累述。官方文檔參考
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : reply.py @Time : 2019/8/6 10:15 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : 回覆消息給關注微信公衆號的用戶 """
import time
class Msg(object):
def __init__(self):
pass
def send(self):
return 'success'
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)
複製代碼
接收到消息,若是斷定消息類型爲圖片消息,條用autoreply方法解析 XML 文件,組裝圖片消息模板,將圖片消息自動回覆給粉絲。
特別提醒:假如服務器沒法保證在五秒內處理回覆,則必須回覆「success」或者「」(空串),不然微信後臺會發起三次重試。解釋一下爲什麼有這麼奇怪的規定。發起重試是微信後臺爲了盡能夠保證粉絲髮送的內容開發者都可以收到。若是開發者不進行回覆,微信後臺沒辦法確認開發者已收到消息,只好重試。
# views.py
# ...
def autoreply(request):
try:
webData = request.body
print("Handle POST webData is: ", webData)
recMsg = receive.parse_xml(webData)
if isinstance(recMsg, receive.Msg):
toUser = recMsg.FromUserName
fromUser = recMsg.ToUserName
if recMsg.MsgType == 'text':
content = recMsg.Content.decode('utf-8')
replyMsg = reply.TextMsg(toUser, fromUser, content)
return replyMsg.send()
if recMsg.MsgType == 'image':
# Issues1: 'ImageMsg' object has no attribute 'MeidaId'
# Issues2: 發送圖片返回了:qCs1WNDj5p9-FULnsVoNoAIeKQUfLsamrfuXn-Goo32RwoDT8wkhh3QGNjZT0D5a
# Issues3: 'str' object has no attribute 'decode'
# Issues4: '該公衆號提供的服務出現故障,請稍後再試'
mediaId = recMsg.MediaId
replyMsg = reply.ImageMsg(toUser, fromUser, mediaId)
return replyMsg.send()
else:
return reply.Msg().send()
else:
print("暫不處理")
# return "success"
return reply.Msg().send()
except Exception as e:
print(e)
複製代碼
GitHub代碼參考commits id:2425cab 好的,下來就是在 Linux 服務器將該 Django 項目啓動起來。爲了測試咱們發送的消息是否能被接受發送,能夠調用 微信公衆號文本接口網頁調試工具,圖片信息接口的調試與文本消息的調試相似。如下是圖片調試成功的實測效果截圖。
咱們來實現「鸚鵡學舌」,即用戶發送語音消息給微信公衆號,公衆號被動發送相同的語音消息給微信用戶粉絲,不須要經過公衆平臺網頁人爲的操做。
微信接口的消息時以 XML 格式接受和傳送的,因此首先進行接口消息的 XML 消息解析吧,具體的字段含義與用法請參考《官方文檔》,此處就再也不累述。官方文檔參考
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : receive.py @Time : 2019/8/6 10:15 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """
import xml.etree.ElementTree as ET
def parse_xml(webData):
if len(webData) == 0:
return None
xmlData = ET.fromstring(webData)
msg_type = xmlData.find('MsgType').text
if msg_type == 'text':
return TextMsg(xmlData)
elif msg_type == 'image':
return ImageMsg(xmlData)
elif msg_type == 'voice':
return VoiceMsg(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 VoiceMsg(Msg):
def __init__(self, xmlData):
Msg.__init__(self, xmlData)
self.MediaId = xmlData.find('MediaId').text
self.Format = xmlData.find('Format').text
複製代碼
咱們上面解析了接受到了粉絲髮送過來的消息了,解析完咱們能拿到一些關鍵的數據字段(ToUserName、FromUserName、CreateTime、MsgType、MsgId、Format、MediaId),接下來能夠把這些字段組裝成回覆消息的 XML 文件模板,具體的字段含義與用法請參考《官方文檔》,此處就再也不累述。官方文檔參考
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : reply.py @Time : 2019/8/6 10:15 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : 回覆消息給關注微信公衆號的用戶 """
import time
class Msg(object):
def __init__(self):
pass
def send(self):
return 'success'
class VoiceMsg(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[voice]]></MsgType> <Voice> <MediaId><![CDATA[{MediaId}]]></MediaId> </Voice> </xml> """
return XmlForm.format(**self.__dict)
複製代碼
接收到消息,若是斷定消息類型爲圖片消息,條用autoreply方法解析 XML 文件,組裝語音消息模板,將語音消息自動回覆給粉絲。
特別提醒:假如服務器沒法保證在五秒內處理回覆,則必須回覆「success」或者「」(空串),不然微信後臺會發起三次重試。解釋一下爲什麼有這麼奇怪的規定。發起重試是微信後臺爲了盡能夠保證粉絲髮送的內容開發者都可以收到。若是開發者不進行回覆,微信後臺沒辦法確認開發者已收到消息,只好重試。
# views.py
# ...
def autoreply(request):
try:
webData = request.body
print("Handle POST webData is: ", webData)
recMsg = receive.parse_xml(webData)
if isinstance(recMsg, receive.Msg):
toUser = recMsg.FromUserName
fromUser = recMsg.ToUserName
if recMsg.MsgType == 'text':
content = recMsg.Content.decode('utf-8')
replyMsg = reply.TextMsg(toUser, fromUser, content)
return replyMsg.send()
if recMsg.MsgType == 'image':
# Issues1: 'ImageMsg' object has no attribute 'MeidaId'
# Issues2: 發送圖片返回了:qCs1WNDj5p9-FULnsVoNoAIeKQUfLsamrfuXn-Goo32RwoDT8wkhh3QGNjZT0D5a
# Issues3: 'str' object has no attribute 'decode'
# Issues4: '該公衆號提供的服務出現故障,請稍後再試'
mediaId = recMsg.MediaId
replyMsg = reply.ImageMsg(toUser, fromUser, mediaId)
return replyMsg.send()
else:
return reply.Msg().send()
else:
print("暫不處理")
# return "success"
return reply.Msg().send()
except Exception as e:
print(e)
複製代碼
GitHub代碼參考commits id:d435471 好的,下來就是在 Linux 服務器將該 Django 項目啓動起來。爲了測試咱們發送的消息是否能被接受發送,能夠調用 微信公衆號文本接口網頁調試工具,語音信息接口的調試與文本消息的調試相似。如下是語音調試成功的實測效果截圖。
咱們來實現「鸚鵡學舌」,即用戶發送語音消息給微信公衆號,公衆號被動發送相同的語音消息給微信用戶粉絲,不須要經過公衆平臺網頁人爲的操做。
微信接口的消息時以 XML 格式接受和傳送的,因此首先進行接口消息的 XML 消息解析吧,具體的字段含義與用法請參考《官方文檔》,此處就再也不累述。官方文檔參考
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : receive.py @Time : 2019/8/6 10:15 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """
複製代碼
咱們上面解析了接受到了粉絲髮送過來的消息了,解析完咱們能拿到一些關鍵的數據字段(ToUserName、FromUserName、CreateTime、MsgType、MsgId、ThumbMediaId、MediaId),接下來能夠把這些字段組裝成回覆消息的 XML 文件模板,具體的字段含義與用法請參考《官方文檔》,此處就再也不累述。官方文檔參考
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : reply.py @Time : 2019/8/6 10:15 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : 回覆消息給關注微信公衆號的用戶 """
複製代碼
接收到消息,若是斷定消息類型爲圖片消息,條用autoreply方法解析 XML 文件,組裝視頻消息模板,將視頻消息自動回覆給粉絲。
特別提醒:假如服務器沒法保證在五秒內處理回覆,則必須回覆「success」或者「」(空串),不然微信後臺會發起三次重試。解釋一下爲什麼有這麼奇怪的規定。發起重試是微信後臺爲了盡能夠保證粉絲髮送的內容開發者都可以收到。若是開發者不進行回覆,微信後臺沒辦法確認開發者已收到消息,只好重試。
# views.py
# ...
複製代碼
GitHub代碼參考commits id: 好的,下來就是在 Linux 服務器將該 Django 項目啓動起來。爲了測試咱們發送的消息是否能被接受發送,能夠調用 微信公衆號文本接口網頁調試工具,語音信息接口的調試與文本消息的調試相似。如下是語音調試成功的實測效果截圖。