微信公众号被动回复消息 Java实现

微信公众号被动回复消息

背景:微信扫描二维码,点击关注或取消关注公众号,同时公众号会给用户发消息。

如图:
在这里插入图片描述

微信公众号开发文档介绍了消息的多种类型,微信开发文档–>公众号–>基础消息能力–>「被动回复用户消息」 如下
在这里插入图片描述

当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。

微信服务器在将用户的消息发给公众号的开发者服务器地址(开发者中心处配置)后,微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次,如果在调试中,发现用户无法收到响应的消息,可以检查是否消息处理超时。关于重试的消息排重,有msgid的消息推荐使用msgid排重。事件类型消息推荐使用FromUserName + CreateTime 排重。

如果开发者希望增强安全性,可以在开发者中心处开启消息加密,这样,用户发给公众号的消息以及公众号被动回复用户消息都会继续加密,详见被动回复消息加解密说明。

假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。详见下面说明:
1、直接回复success(推荐方式) 2、直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)
一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:
1、开发者在5秒内未回复任何内容 2、开发者回复了异常数据,比如JSON数据等
另外,请注意,回复图片(不支持gif动图)等多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器,可以使用素材管理中的临时素材,也可以使用永久素材。

各消息类型需要的XML数据包结构如下:
1 回复文本消息
2 回复图片消息
3 回复语音消息
4 回复视频消息
5 回复音乐消息
6 回复图文消息

Java 代码

点击关注或者取消关注,微信那边会自动触发关注/取消关注事件,并请求事先配置好的地址

   /**
     * 接收微信推送事件
     *
     * @param request
     * @param response
     */
    @RequestMapping(value = "/check/signature", method = RequestMethod.POST, produces = {"application/xml; charset=UTF-8"})
    @ResponseBody
    public void wechatEvent(HttpServletRequest request, HttpServletResponse response) {
        log.error("--------------------接收微信推送事件-----------------------");
        try {
            // 为了防止消息乱码
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        wxService.handleEvent(request, response);
    }

下面是处理被动回复消息逻辑代码,需要根据具体的需求选择具体的消息类型,我这里以文本消息为例。
触发事件的时候会返回微信openId等其他信息。

   public void handleEvent(HttpServletRequest request, HttpServletResponse response) {
        InputStream inputStream = null;
        try {
            inputStream = request.getInputStream();
            Map<String, Object> map = XmlUtil.parseXML(inputStream);
            // openId
            String userOpenId = (String) map.get("FromUserName");
            // 微信账号
            String userName = (String) map.get("ToUserName");
            // 事件
            String event = (String) map.get("Event");
            // 区分消息类型
            String msgType = (String) map.get("MsgType");
            // 普通消息
            if ("text".equals(msgType)) {
                // todo 处理文本消息
            }
//            else if ("image".equals(msgType)) {
//            } else if ("voice".equals(msgType)) {
//            } else if ("video".equals(msgType)) {
//            } 
            // 事件推送消息
            else if ("event".equals(msgType)) {
                if ("subscribe".equals(event)) {
                    logger.info("用户扫码|关注|openId:{},userName:{}", userOpenId, userName);
                    String ticket = (String) map.get("Ticket");
                    if (StringUtils.isNotBlank(ticket)) {
                        redisCacheManager.set(ConstantsRedisKey.ADV_WX_LOGIN_TICKET.replace("ticketId", ticket), userOpenId, 10 * 60);
                    }
                    String mapToXml = handleEventSubscribe(map, userOpenId);
                    response.getWriter().print(mapToXml);
                    return;
                } else if ("SCAN".equals(event)) {
                    logger.info("用户扫码|登录|openId:{},userName:{}", userOpenId, userName);
                    String ticket = (String) map.get("Ticket");
                    if (StringUtils.isNotBlank(ticket)) {
                        redisCacheManager.set(ConstantsRedisKey.ADV_WX_LOGIN_TICKET.replace("ticketId", ticket), userOpenId, 10 * 60);
                    }
                    // todo 业务处理
                } else if ("unsubscribe".equals(event)) {
                    logger.info("用户取消关注,拜拜~,openId:{}", userOpenId);
                    // todo 取消关注 业务处理
                }
            }
            logger.info("接收参数:{}", map);
        } catch (IOException e) {
            logger.error("处理微信公众号请求异常:", e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException ioe) {
                    logger.error("关闭inputStream异常:", ioe);
                }
            }
        }
    }

    /**
     * 处理 subscribe 类型的event
     *
     * @param map
     * @param userOpenId
     * @return
     */
    private String handleEventSubscribe(Map<String, Object> map, String userOpenId) {
        String resXmlStr = getReturnMsgSubscribe(map);
        logger.info("用户扫码关注返回的xml:{}", resXmlStr);
        return resXmlStr;
    }


public String getReturnMsgSubscribe(Map<String, Object> decryptMap) {
        logger.info("---开始封装xml---decryptMap:" + decryptMap.toString());
        TextMessage textMessage = new TextMessage();
        textMessage.setToUserName(decryptMap.get("FromUserName").toString());
        textMessage.setFromUserName(decryptMap.get("ToUserName").toString());
        textMessage.setCreateTime(System.currentTimeMillis());
        textMessage.setMsgType("text");
        textMessage.setContent("你好,欢迎关注XXX!\n" +
                "\n" +
                "关注XXX。立即登录PC端网址 \n" + domainname +
                " 即可完成注册!\n" +
                "\n" +
                "或," +
                "<a href='" + domainname + "'>点击这里立即完成注册</a>");
        return getXmlString(textMessage);
    }


    public String getXmlString(TextMessage textMessage) {
        String xml = "";
        if (textMessage != null) {
            xml = "<xml>";
            xml += "<ToUserName><![CDATA[";
            xml += textMessage.getToUserName();
            xml += "]]></ToUserName>";
            xml += "<FromUserName><![CDATA[";
            xml += textMessage.getFromUserName();
            xml += "]]></FromUserName>";
            xml += "<CreateTime>";
            xml += textMessage.getCreateTime();
            xml += "</CreateTime>";
            xml += "<MsgType><![CDATA[";
            xml += textMessage.getMsgType();
            xml += "]]></MsgType>";
            xml += "<Content><![CDATA[";
            xml += textMessage.getContent();
            xml += "]]></Content>";
            xml += "</xml>";
        }
        return xml;
    }
微信平台上配置触发事件的回调地址

登录微信公众号平台–>设置开发–>「基础配置」–>服务器配置

注意⚠️:
服务器地址 必须是外网可以访问的,否则微信调不通。

令牌 需要和代码里的token一致,因为提交服务器配置的时候,会触发配置好的那个外网地址,发送一个Get请求,进行验证签名。

消息加密密钥 自动生成即可。

消息加密方式 为了方便开发调试,可以选择兼容模式。

填好信息别忘记 提交。

服务器配置以后还可以修改的,每次修改也都会发Get请求验证签名。

在这里插入图片描述
签名验证代码:

 /***
     * 填写服务器URL点击提交时,微信服务器触发get请求用于检测签名
     * @return echoStr
     */
    @GetMapping("/check/signature")
    @ResponseBody
    public String wechatCheckSignature(HttpServletRequest request) {
        log.error("-------------------服务器配置|进行签名检测------------------------");
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echoStr = request.getParameter("echostr");
        boolean checkSignature = WechatPublicUtils.checkSignature(signature, timestamp, nonce, Constants.WX_SERVER_CONFIG_TOKEN);
        if (checkSignature) {
            return echoStr;
        }
        return null;
    }

校验签名工具类

public class WechatPublicUtils {

    /**
     * 校验签名
     *
     * @param signature 微信签名
     * @param timestamp 时间戳
     * @param nonce     随机字符串
     * @param token     我们在公众号平台「基本配置」里定义的token
     * @return 验证结果
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce, String token) {
        // 将token、timestamp、nonce 进行字典序排序
        String[] arr = new String[]{token, timestamp, nonce};
        Arrays.sort(arr);
        // 字符串拼接
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }
        MessageDigest md = null;
        String tmpStr = null;
        try {
            md = MessageDigest.getInstance("SHA-1");
            // sha1加密
            byte[] digest = md.digest(content.toString().getBytes());
            tmpStr = byteToStr(digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        content = null;
        // sha1加密后的字符串与signature对比
        return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
    }

    private static String byteToStr(byte[] byteArray) {
        StringBuilder strDigest = new StringBuilder();
        for (byte b : byteArray) {
            strDigest.append(byteToHexStr(b));
        }
        return strDigest.toString();
    }

    private static String byteToHexStr(byte mByte) {
        char[] digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] tempArr = new char[2];
        tempArr[0] = digit[(mByte >>> 4) & 0X0F];
        tempArr[1] = digit[mByte & 0X0F];
        return new String(tempArr);
    }
}
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java微信公众号定时发送消息模板通常分为两部分: 第一部分是实现Wechat API与Java后端的通信。Wechat API是微信提供的接口,可以让我们通过Java代码与微信公众号进行交互。具体来说,我们需要在Java通过HTTP或HTTPS协议发送请求,获取微信公众号的access_token,再通过access_token来进行后续的操作,例如:获取用户消息、发送消息、创建菜单等等。Java可以使用Apache HttpClient或OkHttp等库来发送HTTP请求,拿到接口返回的JSON数据。 第二部分是实现定时发送消息Java可以使用Quartz框架用于实现定时任务。Quartz是一个开源的作业调度框架,可以用于在指定时间执行任务。我们可以利用Quartz提供的API,创建一个定时任务,定期调用我们的后端Java代码发送微信公众号消息。在定时任务的实现,我们需要考虑任务的频率、执行时间、失败策略等问题。 当以上两部分都实现以后,我们就可以在Java后端实现微信公众号定时发送消息的功能了。具体来说,我们需要将发送消息的代码放到定时任务,指定要发送的消息内容,以及要发送给的用户。然后启动定时任务即可,Java后端会自动按照设定的规则发送微信公众号消息。 值得注意的是,微信官方文档要求所有公众号对接的应用都必须是官方认证的,所以在实现这个功能之前,我们需要先将我们的应用向微信官方申请认证,获得相应的API调用权限。 ### 回答2: Java微信公众号定时发送消息模板是一种利用Java语言实现微信公众平台的定时发送消息模板。该模板可以帮助微信公众号上的管理员在指定时间点自动发送指定的消息,从而提高工作效率和用户体验。 实现Java微信公众号定时发送消息模板的关键是使用了微信公众平台提供的接口。这些接口可以通过Java语言进行调用,从而实现微信公众号发送消息、设置菜单、自动回复、素材管理、用户管理等功能。在定时发送消息模板,我们需要利用这些接口设置定时任务,并在指定时间点调用接口发送消息。 具体实现过程如下: 1. 首先需要在微信公众平台上申请开发者账号,并获取相应的开发者ID、开发者密码和Token。 2. 在Java使用微信公众平台提供的SDK调用相关接口,实现发送消息、设置菜单、自动回复等功能。 3. 利用Java的定时任务框架,如Quartz、Spring定时任务等,在指定的时间点调用相应的微信接口实现定时发送消息。 4. 利用Java的模板功能,以标准化的格式组织需要发送的消息内容,使消息具有可读性和易管理性。 需要注意的是,为了避免被微信公众平台封禁账号,我们需要遵循微信公众平台的相关规定,比如定时发送的消息不能含有敏感词汇、不能频繁发送等。另外,定时发送的频次也需要控制在一定范围内,避免对用户造成困扰。 总之,Java微信公众号定时发送消息模板是一种非常实用的工具,可以帮助管理员提高工作效率和用户体验。但实现过程需要注意一些细节和规范,确保操作的合法性和安全性。 ### 回答3: Java微信公众号定时发送消息模板是指利用Java编程语言实现微信公众号定时发送消息功能,并且提供了消息模板方便开发者进行二次开发。这个模板主要包含以下几个方面: 一、微信公众号接入 首先,需要在微信公众号后台注册并获取开发者ID和开发者密钥,然后使用Java框架接入微信公众号的开放平台。接入之后,就可以使用微信公众号提供的API实现发送消息功能。 二、定时任务 接下来,需要编写Java代码实现定时任务功能。Java提供了Timer和TimerTask类可以很方便地实现定时任务功能。开发者可以选择设定每日,周,月等不同的定时任务。 三、发送消息 定时任务设置好后,需要编写Java代码实现发送消息的功能。这个功能可以通过调用微信公众号提供的API来实现消息的发送可以以文本消息图文消息,视频消息等不同的形式呈现。 四、编写消息模板 最后,为了让开发者方便使用,我们需要编写一个消息模板来进行二次开发。消息模板可以包含以下几个方面:微信公众号接入的配置信息、定时任务的配置信息、发送消息的配置信息,以及发送消息实现方法等。这个模板可以让开发者快速地实现微信公众号定时发送消息的功能。 通过这个模板,开发者可以很方便地搭建起微信公众号定时发送消息的系统,从而可以提高服务的质量和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值