首先第一步是验证,需要配置验证路由
在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())