微信公众号版日记系统

前言

这次准备尝试一种新的笔记记录方法,先定个规则:

正文使用正常的格式

而思考内容都记录在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&timestamp=1362713741&nonce=1362771581 注意看?后面,以name1=value1&name2=value2&...这样的形式将数据传给服务器。如果服务器采用bottle框架,就能用request.GET.get()函数来获得传入的数据,flask框架可以用request.args.get()函数。

看的示例代码是用bottle的,于是去搜索flask下如何获得query string参数,搜到了这篇文章

获得参数之后,我们的服务器通过检验signature对请求进行校验。若验证为真,则原样返回echostr参数内容,之后微信公号接入生效,我们就成功成为了开发者。

具体的校验流程如下:

  1. 将token、timestamp、nonce三个参数进行字典序排序
  2. 将三个参数字符串拼接成一个字符串进行sha1加密
  3. 开发者获得加密后的字符串可与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,然后用postmanhttp://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解码模块

results matching ""

    No results matching ""