微信菜单获取二维码图片的优化指南——该公众号暂时无法提供服务

首先,来介绍一下微信菜单获取二维码图片的场景:

这里写图片描述

点击「我的二维码」,服务器端推送一张名片式的二维码图片(什么叫名片式二维码?怎么生成?可参照 Java生成名片式的二维码源码分享 )。

假设现在你已经知道怎么生成名片式的二维码图片了。想要在用户点击的时候将图片推送给用户——这是我们本篇文章想要介绍的内容。

开发微信公众号离不开一些优秀的中间件,比如说我用的是——weixin-java-tools(微信支付、开放平台、小程序、企业号和公众号(包括服务号和订阅号) Java SDK开发工具包 ,GitHub地址是https://github.com/wechat-group/weixin-java-tools)。

再假设你已经把weixin-java-tools嵌入到了工程项目,那么接下来,请步入今天的主题,如何推送图片给用户?注意事项有哪些?

开发微信公众号,需要启用服务器配置,这样才能自定义微信菜单。

这里写图片描述

服务器地址(URL)的值,即,开发者需要推送的二维码将会转发到该URL中。

接下来,新建该URL的控制器,大致代码如下:

package com.honzh.spring.controller;

import java.io.IOException;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.honzh.spring.service.WechatCoreService;

import me.chanjar.weixin.mp.api.WxMpConfigStorage;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;

@Controller
@RequestMapping(value = "/wechat")
public class WeChatController extends BaseController {
    @Autowired
    protected WxMpConfigStorage configStorage;
    @Autowired
    protected WxMpService wxMpService;
    @Autowired
    protected WechatCoreService coreService;

    /**
     * 微信接口
     * 
     * @return
     * @throws IOException
     */
    @RequestMapping("")
    public void index(HttpServletResponse response) {

        response.setContentType("text/html;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);

        try {

            String signature = request.getParameter("signature");
            String nonce = request.getParameter("nonce");
            String timestamp = request.getParameter("timestamp");

            if (!this.wxMpService.checkSignature(timestamp, nonce, signature)) {
                // 消息签名不正确,说明不是公众平台发过来的消息
                response.getWriter().println("非法请求");
                return;
            }

            String echoStr = request.getParameter("echostr");
            if (StringUtils.isNotBlank(echoStr)) {
                // 说明是一个仅仅用来验证的请求,回显echostr
                String echoStrOut = String.copyValueOf(echoStr.toCharArray());
                response.getWriter().println(echoStrOut);
                return;
            }

            String encryptType = StringUtils.isBlank(request.getParameter("encrypt_type")) ? "raw"
                    : request.getParameter("encrypt_type");

            if ("raw".equals(encryptType)) {
                // 明文传输的消息
                WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(request.getInputStream());
                WxMpXmlOutMessage outMessage = this.coreService.route(inMessage);

                if (outMessage == null) {
                    logger.info("outMessage== null ");
                    response.getWriter().println("");
                    return;
                }

                logger.info("outMessage输出消息为: " + outMessage);
                response.getWriter().write(outMessage.toXml());
                return;
            }

            if ("aes".equals(encryptType)) {
                // 是aes加密的消息
                String msgSignature = request.getParameter("msg_signature");
                WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(request.getInputStream(), this.configStorage,
                        timestamp, nonce, msgSignature);
                this.logger.info("\n消息解密后内容为:\n{} ", inMessage.toString());
                WxMpXmlOutMessage outMessage = this.coreService.route(inMessage);
                this.logger.info(response.toString());
                response.getWriter().write(outMessage.toEncryptedXml(this.configStorage));
                return;
            }

            response.getWriter().println("不可识别的加密类型");
            return;

        } catch (Exception e) {
            logger.error(e.getMessage());
            logger.error(e.getMessage(), e);

            return;
        }
    }
}

该controller的作用就是接收用户的消息请求,比如说获取「我的二维码」,然后再将该请求转发给weixin-java-tools中间件,weixin-java-tools处理完成后,再将该请求转发给微信,微信再推送给用户。再这个过程当中,需要特别注意以下代码:

if (outMessage == null) {
    response.getWriter().println("");
    return;
}

当outMessage 为null时,回给微信的内容为空字符串

这段代码的作用是什么呢?

该公众号暂时无法提供服务——的解决方案。

**这里写图片描述**

为什么会这样呢?针对图片性质的素材推送,weixin-java-tools在处理的时候,往往在图片还在上传过程中就返回了null值,也就是说WxMpXmlOutMessage outMessage = this.coreService.route(inMessage);的处理结果为null,那么此时会发生什么呢?

首先来看一下微信公众平台的通信过程:

这里写图片描述

网络正常情况下,用户发起一个请求,到接收到一个响应的时间都很立即化,但针对图片的情况就比较特殊,尤其是名片式的二维码。

这里写图片描述

在weixin-java-tools的实际应用当中,图片还在提交中时,就响应了null的WxMpXmlOutMessage对象。

这里写图片描述

我不清楚这个问题到底是不是weixin-java-tools团队出的差错,还是怎么滴,但必须要解决。

根据微信开发者文档给出的提示,有可能是在处理图片的过程中超过了5秒,导致微信提示“该公众号暂时无法提供服务,请稍后再试”。

假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。详见下面说明:

1、直接回复success(推荐方式) 2、直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)

一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:

1、开发者在5秒内未回复任何内容 2、开发者回复了异常数据,比如JSON数据等

另外,请注意,回复图片(不支持gif动图)等多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器,可以使用素材管理中的临时素材,也可以使用永久素材。

鉴于此,WxMpXmlOutMessage为null的时候就回复“”(空字符串回去)。

另外,在使用weixin-java-tools的时候,需要首先配置refreshRouter:

  public void refreshRouter() {
    final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
    // 记录所有事件的日志 (异步执行)
    newRouter.rule().handler(this.logHandler).next();

    // 接收客服会话管理事件
    newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT)
            .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION).handler(this.kfSessionHandler).end();
    newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT)
            .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION).handler(this.kfSessionHandler).end();
    newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT)
            .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION).handler(this.kfSessionHandler).end();

    // 自定义菜单事件
    newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT).event(WxConsts.BUTTON_CLICK)
            .handler(this.menuHandler).end();

    // 点击菜单连接事件
    newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT).event(WxConsts.BUTTON_VIEW)
            .handler(this.nullHandler).end();

    // 关注事件
    newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT).event(WxConsts.EVT_SUBSCRIBE)
            .handler(this.subscribeHandler).end();

    // 取消关注事件
    newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT).event(WxConsts.EVT_UNSUBSCRIBE)
            .handler(this.unsubscribeHandler).end();

    // 扫码事件
    newRouter.rule().async(false).msgType(WxConsts.XML_MSG_EVENT).event(WxConsts.EVT_SCAN)
            .handler(this.scanHandler).end();

    // 默认
    newRouter.rule().async(false).handler(this.msgHandler).end();
       this.router = newRouter;
   }

但是对于这段代码的配置,我感到非常疑惑,到底什么时候要设置异步,什么时候设置同步,我还没有从weixin-java-tools那里获得回复。如果有朋友了解这段代码的作用,可以@我,在此感谢!

最后,我们来看一下,自定义菜单是如何回复图片的。

    long t1 = System.currentTimeMillis();

    int qrcode_interval_seconds = Integer
            .parseInt(Variables.INSTANCE.config.getQrcode_interval_seconds());
    logger.info("验证是否超过了{}秒", qrcode_interval_seconds);

    if (StringUtils.isEmpty(mem.getMediaId())) {
        logger.info("暂时还没有素材");
    } else {
        if (DateUtil.addSeconds(mem.getQrcode_time(), qrcode_interval_seconds).getTime() > DateUtil
                .getCurrentTime()) {
            logger.info("不到{}秒,直接获取MediaId", qrcode_interval_seconds);

            long t2 = System.currentTimeMillis();
            logger.info("二维码生成结束,时间为 {},时间差为:{}", t2, (t2 - t1));

            return new WechatImageBuilder().build(mem.getMediaId(), wxMessage, weixinService);
        }
    }

    logger.info("准备二维码图片,时间t1=" + t1);

    WxMpQrCodeTicket ticket = weixinService.getQrcodeService().qrCodeCreateTmpTicket(mem.getUid(),
            604800);
    String qrcode_url = ticket.getUrl();
    File file = QrcodeUtils.createQrcode(qrcode_url, mem.getHeadimgUrl(), mem.getThird_name());
    WxMediaUploadResult res = weixinService.getMaterialService().mediaUpload(WxConsts.MEDIA_IMAGE,
            file);

    long t2 = System.currentTimeMillis();
    logger.info("二维码生成结束,时间为 {},时间差为:{}", t2, (t2 - t1));
    thirdLoginMemService.updateQrcodeTime(mem.getId(), res.getMediaId());

    return new WechatImageBuilder().build(res.getMediaId(), wxMessage, weixinService);

}

这段代码的作用很重要,一般情况下:

新增临时素材
公众号经常有需要用到一些临时性的多媒体素材的场景,例如在使用接口特别是发送消息时,对多媒体文件、多媒体消息的获取和调用等操作,是通过media_id来进行的。
素材管理接口对所有认证的订阅号和服务号开放。通过本接口,公众号可以新增临时素材(即上传临时多媒体文件)。
请注意:
1、对于临时素材,每个素材(media_id)会在开发者上传或粉丝发送到微信服务器3天后自动删除(所以用户发送给开发者的素材,若开发者需要,应尽快下载到本地),以节省服务器资源。
2、media_id是可复用的。
3、素材的格式大小等要求与公众平台官网一致。具体是,图片大小不超过2M,支持png/jpeg/jpg/gif格式,语音大小不超过5M,长度不超过60秒,支持mp3/amr格式
4、需使用https调用本接口。
本接口即为原“上传多媒体文件”接口。
注意事项:
上传的临时多媒体文件有格式和大小限制,如下:
图片(image): 2M,支持PNG\JPEG\JPG\GIF格式
语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式
视频(video):10MB,支持MP4格式
缩略图(thumb):64KB,支持JPG格式
媒体文件在后台保存时间为3天,即3天后media_id失效。

上传临时素材的期限为3天,也就是说,在3天内,临时素材是有效的,那么在改段时间内,就不需要再次生成二维码了,直接将media_id返回即可。为了实现该做法,需要在上传临时素材后,保存一个时间和media_id,如果时间在有效范围内,就直接根据上一次的media_id结果返回给用户。否则,重新生成一次!


这篇文章写得很乱,完全达不到自己的要求,就先贴出来,后面再改。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉默王二

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值