昨晚下班回到家倘核,打算把前不久做的圖片動(dòng)漫化的功能集成到微信公眾號(hào)陕见,效果是秘血,用戶發(fā)送一張圖片到公眾號(hào),那么公眾號(hào)就會(huì)回復(fù)一張動(dòng)漫化的圖片回去评甜。
距離上次開發(fā)公眾號(hào)相關(guān)的接口灰粮,已經(jīng)過去很久了,也有點(diǎn)生疏了忍坷,但是還是記得相關(guān)的一些模糊細(xì)節(jié)粘舟,需要處理微信回調(diào)發(fā)送的信息,涉及到了加解密佩研,還有主動(dòng)調(diào)用微信接口柑肴,需要根據(jù)app id和app secret獲取access token等等,由于打算快速開發(fā)旬薯,所以采用python來做相關(guān)的接口集成開發(fā)晰骑,用java的話沒有python這么敏捷和快速。
于是網(wǎng)上找了一下有沒有相關(guān)的庫(kù)绊序,總不能自己重新對(duì)著微信公眾號(hào)文檔造輪子吧些侍,這樣效率太低了,經(jīng)過一番搜索政模,找到一個(gè)還不錯(cuò)的框架wechatpy
安裝非常簡(jiǎn)單岗宣,使用pip即可
pip install wechatpy
# with cryptography (推薦)
pip install wechatpy[cryptography]# with pycryptodome
pip install wechatpy[pycrypto]
安裝完畢之后,需要選擇一個(gè)web框架淋样,從輕量級(jí)和快速角度考慮耗式,選擇了flask框架來做web開發(fā)
安裝如下:
pip install Flask
一個(gè)簡(jiǎn)單上手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)
運(yùn)行之后,打開瀏覽器127.0.0.1:8000即可看到hello world!非常簡(jiǎn)單
那接下來繼續(xù)刊咳,打開公眾號(hào)管理后臺(tái)彪见,開發(fā)->基本配置,服務(wù)器配置欄娱挨,需要填寫微信回調(diào)的url地址
然后設(shè)置令牌和消息加解密模式余指,微信會(huì)回調(diào)這個(gè)url接口,確認(rèn)對(duì)接成功跷坝,那接下來我們就用wechatpy來處理這段邏輯
定義微信的回調(diào)url處理方法酵镜,從請(qǐng)求參數(shù)獲取timestamp和nonce參數(shù),如果是get請(qǐng)求柴钻,則進(jìn)一步獲取echostr和signature參數(shù)淮韭,然后調(diào)用check_signature校驗(yàn)是否是來自微信的請(qǐng)求,如果不是則拒絕贴届,如果是則回復(fù)echostr靠粪,告知微信,這邊已經(jīng)對(duì)接好了
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請(qǐng)求毫蚓,只有GET請(qǐng)求是不帶任何request body的占键,GET請(qǐng)求是微信發(fā)送的Token驗(yàn)證
還有一種是POST請(qǐng)求,這個(gè)請(qǐng)求微信會(huì)攜帶一些消息體過來元潘,主要是XML畔乙,這些包含了比如圖片消息,文本消息柬批,或者一些事件等等,具體可以查看微信公眾號(hào)的相關(guān)文檔
這里處理的是被動(dòng)請(qǐng)求袖订,也就是微信調(diào)用我們的服務(wù)器氮帐。
我們調(diào)用微信服務(wù)器接口屬于主動(dòng)請(qǐng)求,需要access token
繼續(xù)完善這個(gè)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請(qǐng)求洛姑,注意最上面route我們配置了僅支持get和post請(qǐng)求上沐,那么else分支一定是post方法
通過request.data獲取xml數(shù)據(jù),這里注意判斷一下是否有數(shù)據(jù)楞艾,如果沒有數(shù)據(jù)的情況下参咙,那么直接打印日志no xml body
然后返回即可。
如果有xml數(shù)據(jù)硫眯,那么我們從請(qǐng)求里面獲取msg_signature蕴侧,然后解密消息,這個(gè)取決于你的微信公眾號(hào)的配置两入,如果無(wú)所謂安全净宵,可以直接明文傳輸,那樣就省去了解密消息的步驟。
后面把消息解出來之后择葡,然后打印消息內(nèi)容
放到服務(wù)器上調(diào)試一下紧武,往公眾號(hào)發(fā)送文本消息你好,消息顯示如下:
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')]))
文章一開始提到要做的內(nèi)容是當(dāng)用戶發(fā)送一張圖片到公眾號(hào)敏储,那么公眾號(hào)就會(huì)回復(fù)一張動(dòng)漫化的圖片回去阻星。
那么需要處理的消息是圖片類型的消息,而不是文本消息已添,發(fā)送一張圖片到公眾號(hào)試試妥箕,發(fā)現(xiàn)打印消息如下:
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類型,再結(jié)合wechatpy的文檔,繼續(xù)編寫代碼
圖片消息
classwechatpy.messages.ImageMessage(message)[源代碼]
圖片消息 詳情請(qǐng)參閱 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
當(dāng)msg.type是image的時(shí)候酝碳,打印image的url地址矾踱,然后調(diào)用封裝好的卡通化image的方法 cartoon_image,具體如何把圖片卡通化的方法請(qǐng)參考筆者前面的文章疏哗,這里就不重復(fù)了呛讲。
然后上傳卡通過后的圖片到微信服務(wù)器,獲取media_id,這里構(gòu)建一個(gè)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之后贝搁,就可以回復(fù)圖片給用戶了。
最后上一個(gè)效果圖