昨晚下班回到家,打算把前不久做的图片动漫化的功能集成到微信公众号,效果是,用户发送一张图片到公众号,那么公众号就会回复一张动漫化的图片回去。
距离上次开发公众号相关的接口,已经过去很久了,也有点生疏了,但是还是记得相关的一些模糊细节,需要处理微信回调发送的信息,涉及到了加解密,还有主动调用微信接口,需要根据app id和app secret获取access token等等,由于打算快速开发,所以采用python来做相关的接口集成开发,用java的话没有python这么敏捷和快速。
于是网上找了一下有没有相关的库,总不能自己重新对着微信公众号文档造轮子吧,这样效率太低了,经过一番搜索,找到一个还不错的框架wechatpy
安装非常简单,使用pip即可
pip install wechatpy
# with cryptography (推荐)
pip install wechatpy[cryptography]# with pycryptodome
pip install wechatpy[pycrypto]
安装完毕之后,需要选择一个web框架,从轻量级和快速角度考虑,选择了flask框架来做web开发
安装如下:
pip install Flask
一个简单上手flask的示例
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
if __name__ == '__main__':
app.run(host='127.0.0.1', port=8000, debug=True)
运行之后,打开浏览器127.0.0.1:8000即可看到hello world!非常简单
那接下来继续,打开公众号管理后台,开发->基本配置,服务器配置栏,需要填写微信回调的url地址
然后设置令牌和消息加解密模式,微信会回调这个url接口,确认对接成功,那接下来我们就用wechatpy来处理这段逻辑
定义微信的回调url处理方法,从请求参数获取timestamp和nonce参数,如果是get请求,则进一步获取echostr和signature参数,然后调用check_signature校验是否是来自微信的请求,如果不是则拒绝,如果是则回复echostr,告知微信,这边已经对接好了
from wechatpy.utils import check_signature
@app.route('/wechat', methods=['GET', 'POST'])
def wechat():
timestamp = request.args.get("timestamp")
nonce = request.args.get("nonce")
if request.method == 'GET':
# token, signature, timestamp, nonce
echostr = request.args.get("echostr")
signature = request.args.get("signature")
if echostr:
try:
check_signature(const.token, signature, timestamp, nonce)
return echostr
except InvalidSignatureException:
logging.error("invalid message from request")
注意到这里只处理了GET请求,只有GET请求是不带任何request body的,GET请求是微信发送的Token验证
还有一种是POST请求,这个请求微信会携带一些消息体过来,主要是XML,这些包含了比如图片消息,文本消息,或者一些事件等等,具体可以查看微信公众号的相关文档
这里处理的是被动请求,也就是微信调用我们的服务器。
我们调用微信服务器接口属于主动请求,需要access token
继续完善这个wechat方法
from wechatpy.utils import check_signature
from wechatpy import parse_message
from wechatpy.crypto import WeChatCrypto
from wechatpy.exceptions import InvalidSignatureException, InvalidAppIdException
from wechatpy.replies import ImageReply
#....省略代码
@app.route('/', methods=['GET', 'POST'])
def wechat():
timestamp = request.args.get("timestamp")
nonce = request.args.get("nonce")
if request.method == 'GET':
#....省略代码
else:
xml = request.data
if xml:
msg_signature = request.args.get("msg_signature")
crypto = WeChatCrypto(const.token, const.encodeing_aes_key,
const.app_id)
try:
decrypted_xml = crypto.decrypt_message(
xml,
msg_signature,
timestamp,
nonce
)
msg = parse_message(decrypted_xml)
logging.info("message from wechat %s " % msg)
except (InvalidAppIdException, InvalidSignatureException):
logging.error("cannot decrypt message!")
else:
logging.error("no xml body, invalid request!")
return ""
在else分支处理post请求,注意最上面route我们配置了仅支持get和post请求,那么else分支一定是post方法
通过request.data获取xml数据,这里注意判断一下是否有数据,如果没有数据的情况下,那么直接打印日志no xml body
然后返回即可。
如果有xml数据,那么我们从请求里面获取msg_signature,然后解密消息,这个取决于你的微信公众号的配置,如果无所谓安全,可以直接明文传输,那样就省去了解密消息的步骤。
后面把消息解出来之后,然后打印消息内容
放到服务器上调试一下,往公众号发送文本消息你好,消息显示如下:
2021-03-10 04:00:10,543:INFO:message from wechat TextMessage(OrderedDict([('ToUserName', 'gh_292a97797f58'), ('FromUserName', 'oPXMnwgqQBKAz23ovLYhca6KtIMQ'), ('CreateTime', '1615348809'), ('MsgType', 'text'), ('Content', '你好'), ('MsgId', '23126237789150234')]))
文章一开始提到要做的内容是当用户发送一张图片到公众号,那么公众号就会回复一张动漫化的图片回去。
那么需要处理的消息是图片类型的消息,而不是文本消息,发送一张图片到公众号试试,发现打印消息如下:
2021-03-10 13:08:47,296:INFO:message from wechat ImageMessage(OrderedDict([('ToUserName', 'gh_292a97797f58'), ('FromUserName', 'oPXMnwgkC5M-DKNfigblDIXY3Irw'), ('CreateTime', '1615381726'), ('MsgType', 'image'), ('PicUrl', 'http://mmbiz.qpic.cn/sz_mmbiz_jpg/------'), ('MsgId', '23126702559805937'), ('MediaId', 'UdcKE2XkjLTQcJmRQulERp2GZeTZ5FncAht5XjrKU3Yh1sTucAaQ0E8-------')]))
可以看到图片类型的消息的type是image类型,再结合wechatpy的文档,继续编写代码
图片消息
classwechatpy.messages.ImageMessage(message)[源代码]
图片消息 详情请参阅 http://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.htmlImageMessage 的属性:
name value
type image
image 图片的 URL 地址
#.....省去代码
msg = parse_message(decrypted_xml)
logging.info("message from wechat %s " % msg)
if msg.type == "image":
logging.info("image type message url %s" % msg.image)
cartoon_file = utils.cartoon_image(msg.image)
media_id = utils.upload_image(cartoon_file)
if media_id is not None:
reply = ImageReply(message=msg)
reply.media_id = media_id
xml = reply.render()
return xml
当msg.type是image的时候,打印image的url地址,然后调用封装好的卡通化image的方法 cartoon_image,具体如何把图片卡通化的方法请参考笔者前面的文章,这里就不重复了。
然后上传卡通过后的图片到微信服务器,获取media_id,这里构建一个wechatclient,传入你的appid和appsecret即可
from wechatpy import WeChatClient
from wechatpy.client.api import WeChatMedia
client = WeChatClient(const.app_id, const.app_secret)
def upload_image(file_path):
res = WeChatMedia(client).upload("image", open(file_path, "rb"))
if "media_id" in res:
return res['media_id']
logging.error("upload image %s" % res)
return None
拿到media_id之后,就可以回复图片给用户了。
最后上一个效果图
image.png
网友评论