上篇文章讲了如何在SAE搭建Hello World,那么这篇文章主要写如何实现微信的验证和自动回复。
首先修改SAE中的index.wsgi文件如下:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
# coding: UTF-8
import os
import sae
import web
from weixinInterface import WeixinInterface
urls = (
'/' , 'Hello' ,
'/weixin' , 'WeixinInterface'
)
app_root = os.path.dirname(__file__)
templates_root = os.path.join(app_root, 'templates' )
render = web.template.render(templates_root)
class Hello:
def GET( self ):
#print "你好"
return render.hello( "你好" )
app = web.application(urls, globals ()).wsgifunc()
application = sae.create_wsgi_app(app)
|
即创建一个weixinInterface类来处理微信的请求。
在目录下创建weixinInterface文件,定义WeixinInterface类。
一、实现微信验证。
回顾微信开发者的验证方式(一下摘自微信官方文档):
公众平台用户提交信息后,微信服务器将发送GET请求到填写的URL上,并且带上四个参数:
参数 |
描述 |
signature |
微信加密签名 |
timestamp |
时间戳 |
nonce |
随机数 |
echostr |
随机字符串 |
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,否则接入失败。
signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
加密/校验流程:
1. 将token、timestamp、nonce三个参数进行字典序排序
2. 将三个参数字符串拼接成一个字符串进行sha1加密
3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
那么首先要解决的就是get方法的问题。在WeixinInterface模块中添加代码如下:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#coding:UTF-8
import hashlib
import web
class WeixinInterface:
def GET( self ):
#获取输入参数
data = web. input ()
signature = data.signature
timestamp = data.timestamp
nonce = data.nonce
echostr = data.echostr
#自己的token
token = "yourtoken"
#字典序排序
list = [token,timestamp,nonce]
list .sort()
#sha1加密算法
sha1 = hashlib.sha1()
map (sha1.update, list )
hashcode = sha1.hexdigest()
#如果是来自微信的请求,则回复echostr
if hashcode = = signature:
#print "true"
return echostr
|
现在,放回微信公众平台的注册界面,填上你的验证地址即:http://yourdomain.sinaapp.com/weixin,token填自己的token,要和上面程序中一致。点击提交,如果没有意外现在就应该验证成功了。
二、实现简单的文本回复
现在阅读微信公众平台的文档,先只注意接收文本信息和回复文本信息的格式:
消息推送
当普通微信用户向公众账号发消息时,微信服务器将POST该消息到填写的URL上。结构如下:
文本消息
1
2
3
4
5
6
7
8
|
< xml >
< ToUserName > <![CDATA[toUser]]> </ ToUserName >
< FromUserName > <![CDATA[fromUser]]> </ FromUserName >
< CreateTime >1348831860</ CreateTime >
< MsgType ><![CDATA1]></ MsgType >
< Content > <![CDATA[this is a test]]> </ Content >
< MsgId >1234567890123456</ MsgId >
</ xml >
|
参数 |
描述 |
ToUserName |
开发者微信号 |
FromUserName |
发送方帐号(一个OpenID) |
CreateTime |
消息创建时间 (整型) |
MsgType |
text |
Content |
文本消息内容 |
MsgId |
消息id,64位整型 |
消息回复
对于每一个POST请求,开发者在响应包中返回特定xml结构,对该消息进行响应(现支持回复文本、图文、语音、视频、音乐和对收到的消息进行星标操作)。 微信服务器在五秒内收不到响应会断掉连接。 回复xml结构如下:
回复文本消息
1
2
3
4
5
6
7
8
|
< xml >
< ToUserName > <![CDATA[toUser]]> </ ToUserName >
< FromUserName > <![CDATA[fromUser]]> </ FromUserName >
< CreateTime >12345678</ CreateTime >
< MsgType ><![CDATA1]></ MsgType >
< Content > <![CDATA[content]]> </ Content >
< FuncFlag >0</ FuncFlag >
</ xml >
|
参数 |
描述 |
ToUserName |
接收方帐号(收到的OpenID) |
FromUserName |
开发者微信号 |
CreateTime |
消息创建时间 |
MsgType |
text |
Content |
回复的消息内容,长度不超过2048字节 |
FuncFlag |
位0x0001被标志时,星标刚收到的消息。 |
那么显然的是,我们需要解析xml文件。SAE预装了lxml库,能够方便的解析xml。首先切换到config.yaml,添加代码,效果如下:
1
2
3
4
5
6
7
8
|
---
name: tedxfactory798
version: 1
libraries:
- name: webpy
version: "0.36"
- name: lxml
version: "2.3.4"
|
下面切换回weixinInterface文件,代码如下:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
#coding:UTF-8
import hashlib
import web
import lxml
import time
import os
from lxml import etree
class WeixinInterface:
def __init__( self ):
self .app_root = os.path.dirname(__file__)
self .templates_root = os.path.join( self .app_root, 'templates' )
self .render = web.template.render( self .templates_root)
def GET( self ):
#获取输入参数
data = web. input ()
signature = data.signature
timestamp = data.timestamp
nonce = data.nonce
echostr = data.echostr
#自己的token
token = "yourtoken"
#字典序排序
list = [token,timestamp,nonce]
list .sort()
#sha1加密算法
sha1 = hashlib.sha1()
map (sha1.update, list )
hashcode = sha1.hexdigest()
#如果是来自微信的请求,则回复echostr
if hashcode = = signature:
#print "true"
return echostr
def POST( self ):
#从获取的xml构造xml dom树
str_xml = web.data()
xml = etree.fromstring(str_xml)
#提取信息
content = xml.find( "Content" ).text
msgType = xml.find( "MsgType" ).text
fromUser = xml.find( "FromUserName" ).text
toUser = xml.find( "ToUserName" ).text
#模板渲染
return self .render.reply_text(fromUser,toUser, int (time.time()),u "大家好我现在还只会卖萌,你刚才说的是:" + content)
|
那么我们还需要建立一个模板,在template目录下,reply_text.xml:
1
2
3
4
5
6
7
8
9
|
$def with (toUser,fromUser,createTime,content,funcFlag=0)
< xml >
< ToUserName > <![CDATA[$toUser]]> </ ToUserName >
< FromUserName > <![CDATA[$fromUser]]> </ FromUserName >
< CreateTime >$createTime</ CreateTime >
< MsgType ><![CDATA1]></ MsgType >
< Content > <![CDATA[$content]]> </ Content >
< FuncFlag >$funcFlag</ FuncFlag >
</ xml >
|
保存全部文件,注意一下toUser和fromUser的顺序,和接受的时候正好相反。
近期评论