如何处理大并发下微信公众号模板消息回调
目前我们部分公众号粉丝量达到400万以上,当给大量的粉丝发送模板消息的时候,微信会将如下xml的成功回调消息返给我们系统端造成短时间内系统压力很大【目前是Nginx+PHP集群架构】,影响我们正常业务的处理,如何解决这个问题呢??
<xml> <ToUserName><![CDATA[gh_7f083739789a]]></ToUserName> <FromUserName><![CDATA[oia2TjuEGTNoeX76QEjQNrcURxG8]]></FromUserName> <CreateTime>1395658920</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[TEMPLATESENDJOBFINISH]]></Event> <MsgID>200163836</MsgID> <Status><![CDATA[success]]></Status> </xml>
方案一:抽离出模板消息这个业务单独用高性能的服务去处理这个请求。目前我们的业务场景不适合,因为模板消息里面包含了用户信息、业务处理逻辑等。
方案二:采用Nginx+Lua进行处理【业务中PHP框架导致请求一个hello world这样简单请求用时都在百毫秒左右】
我采用Nginx+Lua 将回调成功的请求挡在调用后端服务前进行处理,具体方案如下:
- 微信公众号后台配置回调地址【我们公众号采用是第三方平台授权方式】配置位置如下:消息与事件接受URL https://abcapi.baidu.com/weixin/callback/$APPID$
- Nginx核心配置文件如下:
location ^~ /weixin/callback/ { default_type 'text/plain'; rewrite_by_lua_file 'conf/scripts/weixin_callback_filter.lua'; }
- 核心lua 脚本如下:
root@devops:/usr/local/nginx/conf/scripts# cat weixin_callback_filter.lua -- 将请求交给PHP处理 function return_declined() ngx.req.set_uri('/index.php' .. ngx.var.uri .. '?' .. ngx.var.args, true); ngx.exit(ngx.DECLINED); end -- 读取请求体 ngx.req.read_body(); local content = ngx.req.get_body_data(); if (not content) then -- 如果请求体是空的, 不处理 return_declined(); end -- 如果含有TEMPLATESENDJOBFINISH字符串, 响应success local event = content:match('<Event><!%[CDATA%[(.*)]]></Event>'); if (event == 'TEMPLATESENDJOBFINISH' or event == 'VIEW') then ngx.say('success'); ngx.exit(ngx.HTTP_OK); end -- 读取加密字符串 local encrypted = content:match('<Encrypt><!%[CDATA%[(.*)]]></Encrypt>'); if (not encrypted) then -- 如果加密字符串是空的, 不处理 return_declined(); else -- base64解码 local _encrypted = ngx.decode_base64(encrypted); if (not _encrypted) then -- 解码失败? ngx.log(ngx.ERR, 'ngx.decode_base64() failed: ' .. encrypted); return_declined(); end encrypted = _encrypted; end -- 此处key需要替换为上图中的消息加解密Key local aes_key = ngx.decode_base64('d9172b8a31042d1d60c8af719acddd89DdWeilyi663='); local aes = require 'resty.aes'; -- 解密 local decrypted = aes:new(aes_key, nil, aes.cipher(256, 'cbc'), {iv=aes_key:sub(0, 16)}, 5):decrypt(encrypted); if (not decrypted) then -- 解密失败? ngx.log(ngx.ERR, 'openssl.cipher:decrypt() failed: ' .. content); return_declined(); end -- ngx.log(ngx.ERR, 'event: ' .. decrypted:match('<Event><!%[CDATA%[([%a]+)]')); -- 如果含有TEMPLATESENDJOBFINISH字符串, 响应success if (decrypted:find('TEMPLATESENDJOBFINISH') or decrypted:find('VIEW')) then ngx.say('success'); ngx.exit(ngx.HTTP_OK); end return_declined();
- 后期如果官方微信公众号模板消息那边发生变化的话,要根据实际情况对lua脚本进行修改
- 相关文章已同步到头条号:不良帅的江湖