json dump 中文时遇到的问题如下

1
2
3
4
>>> import json
>>> s = "Chinese"
>>> print json.dumps(s)
"Chinese"
1
2
3
4
>>> import json
>>> s = "中文"
>>> print json.dumps(s)
"\u4e2d\u6587"

可以看到, json在dump中文的时候, 输出的是”中文”对应的ascii码, 这是因为json.dumps 序列化时对中文默认使用的ascii编码

如果需要输出真正的中文字符, 需要在dump的时候加入ensure_ascii=False参数

1
2
3
4
>>> import json
>>> s = "中文"
>>> print json.dumps(s, ensure_ascii=False)
"中文"

实际使用中, 在与微信企业企业号的主动发送消息接口交互时, 需要使用utf8字符编码encode

所以在这个场景下, 写法可能会是下面这种情况

json.dumps(s, ensure_ascii=False).encode('utf-8')

Python3: 注意以下操作只在Python3环境下是正确的, Python2的环境下.encode会报错

1
2
3
4
5
6
7
8
9
10
➜  ~ python3
Python 3.6.3 (v3.6.3:2c5fed86e0, Oct 3 2017, 00:32:08)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import json
>>> s = "中文"
>>> json.dumps(s, ensure_ascii=False)
'"中文"'
>>> json.dumps(s, ensure_ascii=False).encode('utf8')
b'"\xe4\xb8\xad\xe6\x96\x87"'

在实现微信企业号自建应用与自由服务器信息交互的时候, 报了 Input strings must be a multiple of 16 in length 这个错误, 回复全部英文内容给微信的时候是正常的.

1
2
3
4
5
6
7
sRespData = """<xml>
<ToUserName><![CDATA["""+user_id+"""]]></ToUserName>
<FromUserName><![CDATA["""+corp_id+"""]]></FromUserName>
<CreateTime>"""+create_time+"""</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA["""+content+"""]]></Content>
</xml>"""

如以上即将回复给微信的xml信息, content变量就是实际的消息内容, 当content内容出现中文字符时, 就会报以上错误.

解决方法:

content = content.encode('utf-8')

将content的内容以utf8字符编码进行encode即可

在Python2的代码中import httplib是没有问题的, 到了Python3, 这个模块的导入名称变成了http.client, 所以在如果你使用的是Python3, 需要导入这个模块, import http.client就可以了

在Python2中, 在首行声明了字符编码为utf8, 但是依然报错

1
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128)

解决方法如下:

在报错的python文件中加入以下几行代码

1
2
3
import sys
reload(sys)
sys.setdefaultencoding('utf8')

在一台Ubuntu安装软件的时候发现, 安装任何软件都会报一个类似Errors were encountered while processing Sub-process /usr/bin/dpkg returned an error code的错误, 尝试解决方式如下

清空/var/lib/dpkg/info目录下面的内容即可

原因是在你的配置文件中(我的是CentOS7, /etc/profile)出现了以下配置导致的: export GREP_OPTIONS=--color=auto

解决方法也很简单, 首先在/etc/profile中注释该行

# export GREP_OPTIONS=--color=auto

然后在terminal界面执行unset GREP_OPTIONS

再次执行grep操作, 就不会有恼人的Warning出现啦~

附录: 有可能影响到grep的这一行export配置并不在你的/etc/profile文件下,你可以尝试去~/.bashrc ~/.bashprofile ~/.zshrc等文件中查找

最近入到了 Zabbix agent on HOSTNAME is unreachable for 5 minutes 的问题, 但实际server到agent的网络通信是没有任何问题的, 这种情况下, 我们需要登录到agent这台主机, 查看zabbix_agent的日志来排查错误

查看zabbix_agent的启动进程

1
> ps aux | grep "zabbix"

如果是编译安装的agent, 手动指定配置文件启动的进程, 在这里你可以看到你引用的配置文件的地址

查看配置文件

在配置文件中你需要查看的是记录日志的位置

1
LogFile=/var/log/zabbix/zabbix_agentd.log

查看日志报错

1
> tail -f /var/log/zabbix/zabbix_agentd.log

在我这里的实际情况中, 出现了以下报错信息:

1
25863:20180116:103949.621 failed to accept an incoming connection: connection from "192.168.10.85" rejected, allowed hosts: "zabbix_server.lvrui.io"

在这里我们可以看到几个关键字眼

  • reject: 说明客户端拒绝了服务端的请求
  • allowed hosts: 允许访问客户端的服务端地址

通过以上关键字可以判断出, 客户端认到的server的实际IP地址是192.168.10.85, 但是客户端配置的server地址却是zabbix_server.lvrui.io, 客户端不能反解析出这个IP地址对应的域名, 所以拒绝了server的请求

最简单的解决方法就是将zabbix_agent配置文件中的Server ServerActive的配置都改成 192.168.10.85 即可

第二个办法, 你要是非要用域名的话, 你可以将监控模式改为主动模式, 主动去服务端推送数据

第三个办法, 是你需要在你的网络环境中, 配置对应zabbix_server的反向解析

​ 很多年前的一个”习惯”, 到了年末的时候, 喜欢感慨一番, 回顾这一年的风风雨雨, 展望一下美好的明年~ 正如开题所说, 这是很多年前的一个习惯了, 我已经很多年, 都不记得有多少年没有写过”年终总结”了, 今天, 现在, 是2017年的最后一个工作日(2017年12月29号)的凌晨0点过5分, 我写下这篇2017的年终总结, 希望这是个美好的开端, 将久违的习惯, 再找回来.

​ 2017年的年终总结为什么是五子连珠呢? 下过五子棋的筒子们肯定知道, 五子连珠是五子棋的赢法, 今年的总结浓缩为五子连珠再合适不过了. 不仅仅意味着今年对自己来说是个”胜利”之年, 而且真的有五子喜事, 且听我一一道来~

一子:票子 很久以前, 在社交平台我写过一个个人自评: “没钱没房没股份”的三无人员…. 有钱! 是个很模糊的定义, 对于食不果腹的人来说, 温饱就是有钱. 而我今天说的”票子”, 是我工作以来的第一份兼职收入! 今年年初的时候, 隋哥找到我, 给我提供了一份兼职工作, 是我之前敢想而不敢干的一个职业:讲师. 讲什么呢? 讲当下最流行的进程虚拟化技术Docker Docker 这项技术当时在公司确实有落地, 生产环境就在跑, 而且期间也趟了不少雷, 对于这项技术在生产环境的落地, 其实我还是有故事讲的. 但是我面对的对象不是 Docker 技术的使用者, 等待 Docker 技术落地的技术人员, 而是刚刚接触 Linux 运维领域的初学者. 对于初学者来说, 接受一个进程虚拟化技术其实并不容易. 因为在 Linux 培训中, 都是以物理机的角度去思考问题的, 而到了进程虚拟化阶段, 很多思想和理念和物理机的用法是背道而驰的, 所以这个讲师的兼职对我来说是一个很大的挑战.

​ 接到任务后, 确定大纲肯定是首当其冲, 期间和宫老师反复讨论大纲知识点的顺序. 为什么顺序需要讨论呢, 这么说吧, 我当时已经看过不下五本 Docker 相关的书籍, 每本书介绍 Docker 技术的顺序都不一样, 而对于初学者来说, 选对顺序非常重要! 最后结合自己学习 Docker 的过程, 宫老师给的建议和参考官方文档, 确定了一个让自己满意的大纲. 确定大纲之后就进入到紧张的备课阶段, 这时我才发现, 要把一个东西讲明白是多么不容易, 不仅仅要了解它的所有特性, 还得把这些特性结合实际情况解释清楚, 在生产环境中, 可能遇到问题后, 才会去找解决方案, 用到什么功能, 发掘什么功能, 但是讲课不一样, 不管实际情况的使用频率怎样, 你都得一一知晓他们的使用方法和使用场景, 备课的过程花了好几个晚上, 反复研究, 反复试验, 生怕讲课的时候断档.

​ 当我第一次走进讲台的时候, 是很紧张的, 但是上课之后, 从 Docker 的起源, 发展讲起, 慢慢的放松下来, 但是现在看来, 当时紧张导致语速快, 体验还是非常不好的. 按照大纲, 给学生们培训了4天的课程, 最后让我很意外, 原以为三天就可以讲完的内容, 结果到了第四天下课, 拖堂了两个小时才讲完. 中间思路发散的过多了, 不过也都是干货~ 就这样, 我带过了我人生中的第一批学生, 当老师很辛苦, 除了备课, 每天不停的说话几个小时, 真的脸都肿了~

​ 当讲师, 把自己会的东西讲给别人听, 是个非常非常难得的机会, 一般人想找这种机会都找不着, 在隋老大的引荐下, 也算是突破了自己, 大胆的走上台前, 展示自己, 非常非常难得的机会. 当然这也让我收获了工作以来, 兼职的第一桶金

二子:房子 房子是所有中国的心病, 成也房子, 败也房子. 对于普通老百姓来说, 刚需住房, 能有一套自己房子, 也算是毕生的一个重大任务. 在2017年5月底, 在父母的倾力帮助下, 我收获了人生中的第一套房产. 中国有句古话, 叫安居乐业, 只有安居才能乐业, 眼瞅即将成家, 这套来之不易的房产正是未来乐业之根本.

​ 精装修的房子, 还好在装修这块不用操心, 挑挑家具, 把屋子填满即可. 在看过多家全屋定制之后, 选定了其中两家来定制家具. 其中有一家因为沙发的问题, 来回耽误了好久, 闹得很不愉快. 甚至邀请北京的同事来家里做客的时候, 坐的都是送错的沙发.

​ 2017年收获了自己的房产, 也正是因为收获了房产, 才有资格去日本度蜜月, 想想真是悲哀, 如果名下没有房产, 日本的签证需要每人冻结10万的存款, 工作这些年, 别说10万存款, 1万存款都没有攒下~

三子:车子 我之前对车没有什么感觉, 之前都是比较喜欢坐车, 可能很多同龄人都想买车开车的时候, 我还在想坐哪条公交线路会比较快呢, 骑自行车是不是更好呢, 对车一直都没有感觉, 也没有喜欢到非要买的欲望. 直到今年, 买车的想法愈加强烈, 今年在年初的时候就游走于各个汽车展销会, 收了不少广告传单. 当时一直在纠结买 B 级车还是 A 级车, 随着时间的推移, 交房买家具之后, 紧接着就要操办婚礼, 最终还是将目标定在了 A 级车. 在家里的支持下, 很快就提了车. 到现在已经开了5000多公里, 马上就可以首保了. 现在想想, 甭管是多少钱的房, 也甭管是多便宜的车, 也算是有房有车一族了, 在自己不如意的时候, 想想有房有车, 还是能宽心一些的~

四子:妻子 2017年9月16号是我大婚的日子, 有情人终成眷属, 861天, 爱情得到了升华. 人生的第一件大事, 娶妻大事算是落下帷幕, 成功脱单, 晋升到已婚贵族, 有了自己的小家. 还清楚的记得婚礼的前一天, 忙的真是不可开交, 中午饭都到了1点多才和自己的小伙伴去吃. 晚上的时候, 邀请的北京的朋友陆续抵达唐山, 先是去火车站接我的证婚人, 也是我的授业恩师, 隋老大. 响门儿晚宴后送隋老大夫妇入住酒店, 正好在酒店等待其他还没有到的北京筒子们~ 等人都到齐后, 短暂的寒暄, 已是晚上11点多, 顾不上和远道而来的筒子们一起吃个夜宵就赶紧赶回新房了. 到新房后, 床上都布置好了, 怕早上起来无法复原, 就在沙发上凑合了一夜, 当时我还在想, 万一这一晚睡不好, 第二天犯了颈椎病可咋办~

​ 非常幸运的是, 早上并没有任何不适, 虽然只迷迷糊糊睡了4个小时. 在主持人的全程引导下, 顺利的把媳妇儿娶到了家. 为了婚礼这天的6个小时, 太多太多的亲朋好友给予了大力帮助. 当初还计划着, 谁都不要帮忙, 都找外包干, 都上桌吃饭~ 真到了这天才知道, 家人朋友才是你最大的靠山, 请哪个外包团队干也没有家人朋友放心踏实, 再次衷心的感谢!

​ 媳妇娶到家的那天晚上, 我称了一下体重, 一周之前的这个时候, 体重是131斤, 今天再称, 降到了125斤, 婚礼前这一个礼拜竟然瘦了6斤! 即使是到我现在写这篇总结, 也依然没有恢复到之前的体重, 不管怎么样, 媳妇终于娶到家啦~

五子:孩子 这算是2017年最大的惊喜吧! 10月22日, 我和媳妇去日本度蜜月, 刚到日本不久, 媳妇就不舒服, 恶心. 直到行程的四天, 开始频繁的呕吐. 因为媳妇儿之前肠胃就不好, 日本还都是生冷的食物, 当时以为是肠胃炎犯了, 还吃了从国内带的肠胃药. 但是丝毫没有管用, 反而越来越严重. 后来以为是水土不服. 直到到了国内, 又待了一天, 还不见好, 这才往怀孕的方面考虑, 最终, 在回国的第二天确定了这条喜讯. 到现在, 已经快15周啦. 想想真是后怕, 在怀孕初期, 坐飞机, 吃药, 劳累过度都是大忌, 但是宝宝依然挺了过来, 给宝宝的坚强点赞, 但却是苦了孩子妈了, 从日本回来妊娠呕吐反应极其严重, 期间还因为这住了院, 到现在也没有完全好, 唉, 真是忧心啊, 宝宝啊, 希望你明年的降生顺顺利利, 健健康康~

​ 2017年对我来说绝对是个不平凡的一年, 在这一年里发生了太多太多的事情, 第一次出国, 第一次玩密室逃脱, 第一次正式的投资, 第一次同时拿到5家公司的 offer, 第一次拿到了本科毕业证书……

​ 还记得2016年的新年计划, 计划2017年要用百度地图的足迹功能, 把北京的三环, 四环足迹补齐, 看来今年是做不到了, 今年太多太多重要的事情发生了, 2017年注定是我人生的里程碑之一! 而且是极其重要的里程碑之一!

​ 怀着感恩的心情, 送别2017年, 也因为2017年的私事太多, 反而在工作中, 没有过于突出的贡献, 那么我希望在即将到来的2018年, 在工作上有更突出的表现, 希望在2018年能晋升一个职级, 踏踏实实的等待宝宝的降生, 为马上到来的三口之家做好充足的准备, 真正是为了家在奋斗了!

​ 最后, 学书里前言的落款风格落一下今年的最后一个款儿~

2017年12月29日 星期五 于北京

吕瑞

tornado实现与企业微信消息的交互

现在企业微信开放了普通用户注册,大大方便了我们实现一些“脑洞大开”的想法。DevOps喊了好多年了,近期出现了两个DevOps的分支,一个是ChatOps,一个是AIOps。ChatOps是最近几年火起来的,其中比较出名是slack+hubot的双剑合璧。在slack频道中,发送的一些信息,根据hubot的配置规则,是可以将聊天信息转化为具体命令去服务器上执行的。基于此,国内使用率比较高的企业IM软件就是企业微信了。在企业中,我们可以创建自己的应用模块,在该模块中实现与用户的消息进行交互,本篇文章就来实现这一目标(本篇文章只介绍在已创建自建应用的情况下,与用户消息交互,不含注册企业微信号和创建企业号自建应用的教程,这个自己去百度.com一下就可以了,点一点,没什么难度)

注意:本篇文章不考虑tornado框架的完善性,只作为一个restful开发框架来做基本的演示

前期准备:

  • 一个企业微信账号(认不认证都可以)
  • 在企业微信账号管理后台中创建一个自建应用(企业应用–>自建应用)
  • 拿到企业账号的CorpID(我的企业–>企业信息)
  • 在企业微信平台获取一个Token(进入自建应用中–>接收消息–>启动API接收–>随机获取Token)
  • 在企业微信平台获取一个EncodingAESKey(进入自建应用中–>接收消息–>启动API接收–>随机获取EncodingAESKey)
  • 一个有公网IP的服务器(可以在阿里云等厂商购买)
  • 一个Python2.7的环境

下载企业微信提供的加解密模块

企业微信提供了Python版本的加解密模块,我们可以直接从企业微信官方网站上下载使用

下载完成后,你将得到以下文件:

  • ierror.py 错误状态码
  • Sample.py 示例代码
  • WXBizMsgCrypt.py 加解密核心模块
  • Readme.txt

为了使用WXBizMsgCrypt.py这个模块,需要先在Python环境中安装pycrypto模块,执行一下命令

1
> pip install pycrypto

搭建tornado运行框架

在与微信提供的模块的同级目录中,创建tornado服务应用app.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import tornado.web
import tornado.ioloop
from WXBizMsgCrypt import WXBizMsgCrypt
import sys
import xml.etree.cElementTree as ET
import xml.etree.ElementTree

class MainHandler(tornado.web.RequestHandler):

def get(self): # 建立连接,认证使用
pass

def post(self): # 信息收发,交互使用
pass

application = tornado.web.Application([
(r"/", MainHandler),
])

if __name__ == "__main__":

try:
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
except KeyboardInterrupt as e:
print("exit")
exit(0)

此处的tornado框架只为演示,框架其他功能未完善

与企业微信认证接口并建立连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def get(self):
# 声明并赋值企业微信中的信息
sToken = "iXIPhJ35"
sEncodingAESKey = "LRB2JiYSMUsszYrcbTd9fWTLwlYEhrEjb7Z9coWRBoJ"
sCorpID = "yy4f13b9b0a629296d"
wxcpt=WXBizMsgCrypt(sToken,sEncodingAESKey,sCorpID) # 使用企业微信提供的模块创建加密解密对象

# 获取URL中的参数
sVerifyMsgSig=self.get_argument('msg_signature')
sVerifyTimeStamp=self.get_argument('timestamp')
sVerifyNonce=self.get_argument('nonce')
sVerifyEchoStr=self.get_argument('echostr')

# 使用解密加密对象进行URL认证
ret,sEchoStr=wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,sVerifyNonce,sVerifyEchoStr)
if ret != 0:
print "ERR: VerifyURL ret:" + ret
sys.exit(1)

# 将解密后的字符串发回给企业微信端
self.write(sEchoStr)

认证建立连接的大概流程就是企业微信用自己的加密算法加密一个字符串通过get请求的方式发送给你 –> 你的接口拿到这次get请求后,依据URL中的参数,反解出企业微信发来的加密字符串 –> 解密后,将该字符串返回给企业微信端 –> 企业微信收到明文的字符串后会对比自己的初始发送的字符串 –> 对比成功即验证成功,建立连接

与企业微信进行信息交互

1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
def post(self):
# 声明并赋值企业微信中的信息,这点与上面get请求的认证是一样的
sToken = "iXIPhJ35"
sEncodingAESKey = "LRB2JiYSMUsszYrcbTd9fWTLwlYEhrEjb7Z9coWRBoJ"
sCorpID = "yy4f13b9b0a629296d"
wxcpt = WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID) # 使用企业微信提供的模块创建加密解密对象

# 获取URL中的参数(注意上面的get函数中的密文是通过get请求直接加在url中传递的,而交互模式中,密文是单独post过来的)
sVerifyMsgSig = self.get_argument('msg_signature')
sVerifyTimeStamp = self.get_argument('timestamp')
sVerifyNonce = self.get_argument('nonce')
sReqData = self.request.body

# 将以上信息进行解密
ret, sMsg = wxcpt.DecryptMsg(sReqData, sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce)
if (ret != 0):
print "ERR: VerifyURL ret:"
sys.exit(1)

# 根据接收到的xml消息,实例化成xml对象
xml_tree = ET.fromstring(sMsg)

# 判断是否是发送消息的行为
if xml.etree.ElementTree.iselement(xml_tree.find("Content")):
content = xml_tree.find("Content").text # 获取用户发送的消息
# 判断是否是点击菜单栏的行为
elif xml.etree.ElementTree.iselement(xml_tree.find("EventKey")):
content = xml_tree.find("EventKey").text # 获取用户点击菜单的id
else:
content = "other type"

# 将我们收到的信息再发回给企业微信,需要从request的xml获取以下信息
user_id = xml_tree.find("FromUserName").text # 用户id
corp_id = xml_tree.find("ToUserName").text # 企业id
create_time = xml_tree.find("CreateTime").text # 时间戳(用request的时间戳即可)

# 以下为需要回复给企业微信用户的消息的具体内容
content = "1.1.1.1\n2.2.2.2\n3.3.3.3"

# 由于不是很熟悉xml的操作,也不明白为啥微信不用json,非要任性的用xml
# 在这里只能用字符串拼出一个xml文件出来了 -_- 不要喷
# 以下xml即为发送回企业微信的完整消息体,该消息体不能直接明文发送,需要加密后发送
sRespData = """<xml>
<ToUserName><![CDATA["""+user_id+"""]]></ToUserName>
<FromUserName><![CDATA["""+corp_id+"""]]></FromUserName>
<CreateTime>"""+create_time+"""</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA["""+content+"""]]></Content>
</xml>"""

# 将上面的明文消息体使用企业微信的加密模块进行加密操作
ret,sEncryptMsg = wxcpt.EncryptMsg(sRespData, sVerifyNonce, sVerifyTimeStamp)
if ret!=0:
print "ERR: EncryptMsg ret: " + str(ret)
sys.exit(1)

# 最后将密文回复给企业微信,即完成企业微信用户与企业自己服务器之间的信息交互
self.write(sEncryptMsg)

信息交互的大概流程是企业微信用自己的加密算法加密用户发送的消息字符串,通过post请求的方式发送给你 –> 你的接口拿到post请求后,依据URL中的参数,和post过来的密文,反解出企业微信发来的加密消息 –> 解密后,将回复给用户的消息重新包进xml消息中 –> 将完整的xml进行加密 –> 将密文回复给企业微信端

没有下一步了 就是这么简单

以下是完整的代码

1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import tornado.web
import tornado.ioloop
from WXBizMsgCrypt import WXBizMsgCrypt
import sys
import xml.etree.cElementTree as ET
import xml.etree.ElementTree

class MainHandler(tornado.web.RequestHandler):

def get(self):
sToken = "iXIPhJ35"
sEncodingAESKey = "LRB2JiYSMUsszYrcbTd9fWTLwlYEhrEjb7Z9coWRBoJ"
sCorpID = "yy4f13b9b0a629296d"
wxcpt=WXBizMsgCrypt(sToken,sEncodingAESKey,sCorpID)

sVerifyMsgSig=self.get_argument('msg_signature')
sVerifyTimeStamp=self.get_argument('timestamp')
sVerifyNonce=self.get_argument('nonce')
sVerifyEchoStr=self.get_argument('echostr')

ret,sEchoStr=wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,sVerifyNonce,sVerifyEchoStr)
if ret != 0:
print "ERR: VerifyURL ret:" + ret
sys.exit(1)

self.write(sEchoStr)

def post(self):
sToken = "iXIPhJ35"
sEncodingAESKey = "LRB2JiYSMUsszYrcbTd9fWTLwlYEhrEjb7Z9coWRBoJ"
sCorpID = "yy4f13b9b0a629296d"
wxcpt = WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID)

sVerifyMsgSig = self.get_argument('msg_signature')
sVerifyTimeStamp = self.get_argument('timestamp')
sVerifyNonce = self.get_argument('nonce')
sReqData = self.request.body

ret, sMsg = wxcpt.DecryptMsg(sReqData, sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce)
if (ret != 0):
print "ERR: VerifyURL ret:"
sys.exit(1)

xml_tree = ET.fromstring(sMsg)

if xml.etree.ElementTree.iselement(xml_tree.find("Content")):
content = xml_tree.find("Content").text
elif xml.etree.ElementTree.iselement(xml_tree.find("EventKey")):
content = xml_tree.find("EventKey").text
else:
content = "other type"

user_id = xml_tree.find("FromUserName").text
corp_id = xml_tree.find("ToUserName").text
create_time = xml_tree.find("CreateTime").text

content = "1.1.1.1\n2.2.2.2\n3.3.3.3"

sRespData = """<xml>
<ToUserName><![CDATA["""+user_id+"""]]></ToUserName>
<FromUserName><![CDATA["""+corp_id+"""]]></FromUserName>
<CreateTime>"""+create_time+"""</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA["""+content+"""]]></Content>
</xml>"""

ret,sEncryptMsg = wxcpt.EncryptMsg(sRespData, sVerifyNonce, sVerifyTimeStamp)
if ret!=0:
print "ERR: EncryptMsg ret: " + str(ret)
sys.exit(1)

self.write(sEncryptMsg)

application = tornado.web.Application([
(r"/", MainHandler),
])

if __name__ == "__main__":

try:
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
except KeyboardInterrupt as e:
print("exit")
exit(0)

以上代码只是示例代码,细心的童鞋可能会发现get和post中有部分重复代码,而且该tornado框架本身也没有提供log/异步非阻塞等特性的支持,只是为了演示企业微信与自由服务器信息交互的流程与使用方法,无论你是使用Falcon还是Flask框架,逻辑都差不多,注意各个框架的回文函数即可,比如tornado是self.write(), Flask是直接return

Python3编译安装后pip3命令报ssl错误

首先确保ssl相关的库文件已经安装

1
> yum install -y openssl openssl-devel

在编译的时候加上--with-ssl参数即可

1
2
3
4
cd Python-3.6.2
./configure --with-ssl
make
sudo make install