如何处理大并发下微信公众号模板消息回调

  目前我们部分公众号粉丝量达到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 将回调成功的请求挡在调用后端服务前进行处理,具体方案如下:

  1. 微信公众号后台配置回调地址【我们公众号采用是第三方平台授权方式】配置位置如下:消息与事件接受URL https://abcapi.baidu.com/weixin/callback/$APPID$

     

     

  2. Nginx核心配置文件如下:
            location ^~ /weixin/callback/ {
                    default_type 'text/plain';
                    rewrite_by_lua_file 'conf/scripts/weixin_callback_filter.lua';
            }

     

  3. 核心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();
  4. 后期如果官方微信公众号模板消息那边发生变化的话,要根据实际情况对lua脚本进行修改
  5. 相关文章已同步到头条号:不良帅的江湖
posted @ 2021-11-30 14:22  不良帅的江湖  阅读(2331)  评论(1编辑  收藏  举报