微信公众号版日记系统
前言
这次准备尝试一种新的笔记记录方法,先定个规则:
正文使用正常的格式
而思考内容都记录在blockquote区域里
任务
任务需求:
- 将公网应用网站改写为公众号后台服务
- 可以通过公众号使用:
- 使用专用指令,可打印出过往所有笔记
- 一次接收手机端输入的文字(不包含表情/图片/声音/视频...)
- 在服务端合理保存
- 同时兼容的命令行工具远程交互/使用/管理
- 可以通过本地命令行工具监察/管理网站:
- 获得当前笔记数量/访问数量等等基础数据
- 可以获得所有笔记备份的归档下载
本次任务的重点应该是如何利用微信公众号的API,已经如何调整网站的API供微信公众号使用
继续看其他卡片: 微信后台: 公众号 ~ 在之前开发基础上, 完成 极简交互式笔记的 微信后台 版本
如何创建一个公众号?
- 公众号和企业号的差别?
- 如何验证一个公众号服务器?
先从这个文档的链接入手,了解微信公众号
创建公众号
在填写服务器配置时出现token验证失效,因此去google搜索了一番,感觉是服务器端要加入一个验证token的代码,再进一步搜索之后,在这个网站上找到了一段示例代码,还搜到另一篇文章,再结合微信公众号的接入说明,大概理解了要怎么操作,下面我就介绍token的校验过程
Token校验过程
首先,微信服务器会向我们的服务器发送GET请求,并携带4个参数:
- signature: 加密签名
- timestamp: 时间戳
- nonce: 随机数
- echostr: 随机字符串
这4个参数是以query string的方式传给服务器的,举一个示例url:http://yoursaeappid.sinaapp.com//?signature=730e3111ed7303fef52513c8733b431a0f933c7c43&echostr=5853059253416844429×tamp=1362713741&nonce=1362771581 注意看?后面,以name1=value1&name2=value2&...这样的形式将数据传给服务器。如果服务器采用bottle框架,就能用request.GET.get()函数来获得传入的数据,flask框架可以用request.args.get()函数。
看的示例代码是用bottle的,于是去搜索flask下如何获得query string参数,搜到了这篇文章。
获得参数之后,我们的服务器通过检验signature对请求进行校验。若验证为真,则原样返回echostr参数内容,之后微信公号接入生效,我们就成功成为了开发者。
具体的校验流程如下:
- 将token、timestamp、nonce三个参数进行字典序排序
- 将三个参数字符串拼接成一个字符串进行sha1加密
- 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
翻译成python代码就是
def check_signature():
# 第0步:获取参数
token = "mytoken", # 这里将mytoken替换成自己设置的token
timestamp = request.GET.get('timestamp', None) #用bottle里的request.GET.get函数来获得传入的'timestamp'的值
nonce = request.GET.get('nonce', None) #同上方法获得'nonce'值
signature = request.GET.get('signature', None) #同上方法获得'signature'的值
echostr = request.GET.get('echostr', None) #同上方法获得'echostr'的值
# 第1步:将token、timestamp、nonce三个参数进行字典序排序
mylist = sorted([token, timestamp, nonce]) #将token, timestamp和nonce组成一个列表,然后进行排序
# 第2步:将三个参数字符串拼接成一个字符串进行sha1加密
mystr = ''.join(mylist) #将三个参数字符串拼接成一个字符串
import hashlib
mystr_encoded = hashlib.sha1(mystr).hexdigest() # 对拼接字符串进行sha1加密
# 第3步:开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
if mystr_encoded == signature:
return echostr
else:
return None
将上述函数映射到服务器的某个路径里,然后用自己设置的token替换掉代码里的mytoken,将代码部署到服务器,再回到微信公众号后台的开发者中心进行配置,填入相应的url和token提交即可。服务器只需要验证一次就行,之后就可以对url对应的服务器端业务逻辑进行修改。
我是把上面的函数放到 @app.route('/wechat')下面,那么我要填的url就是http://alanapp.sinaapp.com/wechat
接着继续看zmx
微信后台: 消息响应 ~ 在之前开发基础上, 完成 极简交互式笔记的 微信后台 版本
微信各个终端的消息和公众号后台以及服务器的关系?! 消息的格式是 XML 的,什么是 XML ? 如何处理?!
接受消息
微信公众号接受用户输入之后,会发送类似这样的XML信息给我们的服务器
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
那么首先要对XML进行解析,下面示例把XML信息写入到一个字典里。
回过头去看文档19.7. xml.etree.ElementTree — The ElementTree XML API — Python 2.7.10 > documentation
# 为了演示,先把XML存在一个字符ss里
ss = '''\
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
'''
import xml.etree.ElementTree as ET #导入相应库
root = ET.fromstring(ss) # 对字符串ss进行解析
mydict = {child.tag:child.text for child in root} # 这里用到了字典推导式,跟列表推导式用法相似
print mydict
print出来的结果是
{'FromUserName': 'fromUser',
'MsgId': '1234567890123456',
'ToUserName': 'toUser',
'Content': 'this is a test',
'MsgType': 'text',
'CreateTime': '1348831860'}
根据官方文档,我们服务器要回复的也是XML格式的信息,例如
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
</xml>
考虑怎么把字典转换回XML,网上去搜,找到这篇文章。但后来想到我们要返回的xml结构很简单,直接用Sting.format函数来替换文本进行重构就行。
那么我们先尝试做一个echo server,把用户发送的信息原封不动回复回去,那么我们需要做的就是
- 获得post请求中body部分的信息
- bottle 用 request.body.read()
- flask 用 request.data
- 将得到的body数据解析为字典
- 更新CreateTime
- 交换FromUserName和ToUserName
- 重构XML
在服务器端代码里加上:
@app.route('/mypath', method="POST") # mypath需要跟微信公众号里注册的信息一致
def check_signature():
# 获取post请求body内容
data = request.body.read()
# 解析xml
import xml.etree.ElementTree as ET
root = ET.fromstring(data)
mydict = {child.tag:child.text for child in root}
# 更新时间
import time
mydict['CreateTime'] = int(time.time())
# 现在不对内容做任何操作,只是原样返回
# 重构xml
myxml = '''\
<xml>
<ToUserName><![CDATA[{}]]></ToUserName>
<FromUserName><![CDATA[{}]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[{}]]></Content></xml>
'''.format(mydict['FromUserName'],mydict['ToUserName'],mydict['Content'])
return myxml
这样我们就完成了一个echo server,可以在微信公众号里进行测试,它会重复你发送的所有文字信息。在这个基础上,我们可以加一些代码,来对用户发送的信息进行处理,然后将相应的回复发回给用户。
知道了微信收发信息的格式之后,就可以进行本地调试了,之前教练Haidao推荐了一个chrome上的应用postman,可以用来测试HTTP的get,post等请求,这次使用了觉得非常不错。
我的调试过程是先运行本地服务器
$ dev_server.py
,然后用postman
向http://localhost:8080/wechat
发送一个post请求,请求的body信息是:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
然后看response里信息,一般是如下的格式
<xml>
<ToUserName><![CDATA[fromUser]]></ToUserName>
<FromUserName><![CDATA[toUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[response text]]></Content>
</xml>
然后对比请求和恢复,看:
- ToUserName和FromUserName有没有对换
- CreateTime有没有更新
- response里的Content是不是想要的结果
之后再不断改变请求信息里的Content,重新发送post请求,看response里的Content信息是不是想要的结果。
尝试了echo server之后,接下来可以做的是
- 设计自己的应答逻辑
- 加回对微信signature的验证模块,保证API只被微信公众号调用
- 根据官方的示例代码,添加EncodingAESKey解码模块