首先第一步是验证,需要配置验证路由
在urls中
url(r'^wechat/',wechat)
然后在views视图中走验证的逻辑
然后这里有一个csrf_token问题。所以需要导入一个包
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from .messages import Signature,Message
# 访问wechat不用携带csrf_token
@csrf_exempt
def wechat(request):
# 走认证的的逻辑
if request.method == 'GET':
# 返回验证结果
return HttpResponse(Signature.check_signature(request))
验证的详细逻辑在另一个新建的message.py文件中
class Signature(object):
# 读取token值
TOKEN = settings.WECHAT_TOKEN
@classmethod
def check_signature(cls,request):
try:
echostr = request.GET.get('echostr')
nonce = request.GET.get('nonce')
signature = request.GET.get('signature')
timestamp = request.GET.get('timestamp')
# 1.将token,timestamp,nonce 三个参数进行字典序排序
tmpList = [cls.TOKEN, timestamp, nonce]
tmpList.sort()
# 2.将三个参数拼接成一个字符串
tmpstr = ''.join(tmpList)
# 3.对字符串进行哈希算法加密
hashcode = hashlib.sha1(tmpstr.encode('utf-8')).hexdigest()
# 4.验证加密后的字符串与signature是否相同,相同表示该请求源自于微信
if hashcode == signature:
# 返回随机字符串
return echostr
except Exception as e:
return None
其中token的值在settings中设置的
而在视图中的另一个功能是接收用户传过来的数据,也就是post
elif request.method == 'POST':
msg = Message(request.body)
result = msg.process_from_msgtype()
return HttpResponse(result,content_type='xml/application')
同样在message中定义
处理消息的类
class Message(object):
'''<xml><ToUserName><![CDATA[{}]]></ToUserName><FromUserName><![CDATA[{}]]></FromUserName><CreateTime>{}</CreateTime><MsgType><![CDATA[voice]]></MsgType><Voice><MediaId><![CDATA[{}]]></MediaId></Voice></xml>'''
def __init__(self,body):
'''
:param body: 微信服务器端发过来的数据 xml
'''
self.body = body
self.MsgType = '' # 消息类型
self.FromUserName = '' # 发送消息用户
self.ToUserName = '' # 接收消息用户
self.Content = '' # 接收文本信息内容
self.CreateTime = '' # 消息推送时间
self.MediaId = '' # 媒体文件id
self.MsgId = '' # 消息id
self.Event = '' # 事件类型
self.xml_trans_attr()
# 将xml数据转换为对象的属性值
def xml_trans_attr(self):
# 将字符串转换为节点树
eleTree = ElementTree.fromstring(self.body)
if eleTree.tag == 'xml':
# for循环遍历节点树
for child in eleTree:
# 节点名称为key,节点数据为值,添加到字典中
# 节点名称为属性名 节点数据为属性值 修改该对象属性
setattr(self,child.tag,child.text)
# 处理各类消息的函数
def process_from_msgtype(self):
result = ''
# 判断消息类型
if self.MsgType == 'text':
# 文本消息
result = self.text_msg()
elif self.MsgType == 'image':
# 处理图片消息
result = self.image_msg()
elif self.MsgType == 'voice':
# 处理语音消息
result = self.voice()
elif self.MsgType == 'video':
# 处理视频消息
pass
elif self.MsgType == 'event':
# 处理事件消息
if self.Event == 'subscribe':
# 关注事件
pass
elif self.Event == 'unsubscribe':
# 取消关注事件
pass
elif self.Event == 'CLICK':
# 点击按钮事件
# 判断点击的是哪个按钮
if self.EventKey == '2-3':
self.Content = '郑州'
result = self.text_msg()
elif self.Event == 'VIEW':
# 跳转网页事件
pass
# 返回最终的处理结果
return result
# 处理文本事件
def text_msg(self):
# 取出文本消息内容
if self.Content:
# 根据Content 拼接天气预报地址
url = 'http://api.map.baidu.com/telematics/v3/weather?location={}&output=json&ak=TueGDhCvwI6fOrQnLM0qmXxY9N0OkOiQ&callback=?'.format(self.Content)
# 发送请求 拿回数据
rs_dict = requests.get(url).json()
# 判断城市名称是否正确
if rs_dict['error'] != 0:
# 不正确,返回错误的信息
return TEXT_TEMPLATE.format(self.FromUserName,
self.ToUserName,time.time(),'您输入的城市不存在,请检查后重试[玫瑰]')
else:
wea_dict = rs_dict['results'][0]
# 城市
city = wea_dict['currentCity']
# pm值
pm25 = float(wea_dict['pm25'])
# 污染程度
pollute = ''
# 判断pm值的范围
if 35>=pm25 >0:
pollute = '优[呲牙]'
elif pm25 <= 75:
pollute = '良[白眼]'
elif pm25 <= 115:
pollute = '轻度污染[晕]'
elif pm25 <= 150:
pollute = '中度污染[难过]'
elif pm25 <= 250:
pollute = '重度污染[吐]'
else:
pollute = '严重污染[再见]'
# 取出今天天气信息字典
today = wea_dict['weather_data'][0]
# 返回的文本信息
content = ' [玫瑰][玫瑰][玫瑰]{}[玫瑰][玫瑰][玫瑰]\n pm值:{}\n 污染指数:{} \n 实时温度:{}\n 天气:{}\n 风级:{}\n 温度:{}\n'.format(city,pm25,pollute,today['date'],today['weather'],today['wind'],today['temperature'])
# 拼接完整的回复内容
return TEXT_TEMPLATE.format(self.FromUserName,
self.ToUserName,time.time(),content)
# 处理图片事件
def image_msg(self):
return IMAGE_TEMPLATE.format(self.FromUserName,self.ToUserName,
time.time(),self.MediaId)
# 处理语音事件
def voice(self):
return VOICE_TEMPLATE.format(self.FromUserName,self.ToUserName,
time.time(),self.MediaId)
另外还有一个功能就是创建自定义菜单
views视图中的体现
@csrf_exempt
def wechat(request):
# 走认证的的逻辑
if request.method == 'GET':
result = Signature.check_signature(request)
if result:
# 创建自定义菜单
Menu.create_menu()
# 返回验证结果
return HttpResponse(result)
elif request.method == 'POST':
msg = Message(request.body)
result = msg.process_from_msgtype()
return HttpResponse(result,content_type='xml/application')
详细功能在在message文件中实现
因为创建自定义功能需要先获取access_token 所以先定义获取access_token的类
# 获取access_token
class Access_Token(object):
# 拼接获取获取access_token的地址
url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}'.format(settings.WECHAT_APPID,settings.WECHAT_SECRET)
access_token = ''
exprie_time = 0
# 获取access_token值的函数
@classmethod
def get_access_token(cls):
# 判断过期时间
if cls.access_token and cls.exprie_time >= float(time.time()):
return cls.access_token
else:
# 过期
cls.get_access_token_form_url()
return cls.access_token
@classmethod
def set_access_token(cls,access_token):
cls.access_token = access_token
# 请求拿到access_token
@classmethod
def get_access_token_form_url(cls):
# 发起请求拿回数据
response = requests.get(cls.url).json()
# 判断请求结果中是否包含access_token
if response.get('access_token',None):
cls.set_access_token(response['access_token'])
expires_in = response['expires_in']
# 计算过期时间
cls.exprie_time = float(time.time()) + float(expires_in)
else:
print(response)
然后再创建自定义类
class Menu(object):
@classmethod
def create_menu(cls):
# 拼接url地址
url = 'https://api.weixin.qq.com/cgi-bin/menu/create?access_token={}'.format(Access_Token.get_access_token())
data = {
"button": [
{
"name": "2",
"sub_button": [
{
"type": "view",
"name": "2-1",
"url": "http://www.soso.com/"
},
{
"type": "view",
"name": "2-2",
"url": "http://mp.weixin.qq.com",
},
{
"type": "click",
"name": "2-3",
"key": "2-3"
}]
},
{
"name": "3",
"sub_button": [
{
"type": "scancode_push",
"name": "3-1",
"key": "3-1",
"sub_button": []
},
{
"type": "scancode_push",
"name": "3-2",
"key": "3-2",
"sub_button": []
},
{
"name": "3-3",
"type": "location_select",
"key": "3-3"
}
]
},
{
"name": "4",
"sub_button": [
{
"type": "pic_sysphoto",
"name": "4-1",
"key": "4-1",
"sub_button": []
},
{
"type": "pic_photo_or_album",
"name": "4-2",
"key": "4-2",
"sub_button": []
},
{
"type": "pic_weixin",
"name": "4-3",
"key": "4-3",
"sub_button": []
}
]
}
]
}
response = requests.post(url,data=json.dumps(data))
print(response.json())