实现微信公众号发送消息给指定用户

一、前言

在实际项目开发中,需要实现消息中心向关注微信公众号的指定用户发送消息通知,在翻阅了网上很多资料及微信官方开发文档后,最终顺利完成功能开发,但是其中走过的路艰辛且曲折,因此特将开发过程中踩过的坑及心得记录下来,以期给他人带来方便。

二、公众号消息通知开发

1.微信公众号开发配置

实现消息发送前,需要提前在业务系统关联的公众号上,做一些基础配置。配置如下所示:
(1)基础配置
开发者ID开发者密码不必多说,懂者自懂,IP白名单设置部署业务系统的IP地址,否则,微信的回调请求地址无法响应请求。服务器地址配置的是微信验证Token是否生效的接口,秘钥自己生成,加解密方式自己根据业务场景选择。这些都设置好后,就万事具备了接下来进入开发环节。
在这里插入图片描述

(2)消息模板配置
这里根据自己的业务场景合理选择消息模板,用于后续的消息通知生成
在这里插入图片描述

(3)服务器连通性测试配置
这里必须要把图片中提到的txt文件放到服务器的目录下,确保外部可以访问到。
在这里插入图片描述

在这里设置微信回调地址分服务器域名,否则调用微信授权认证接口后,会报以下错误:
在这里插入图片描述

2.编码实现

这里实现前文中提到的服务器配置中设置的服务器URL请求接口,验证配置的令牌是否生效。

/**
 * @author zhaolc
 * @version 1.0
 * @description TODO
 * @createTime 2021年02月26日 13:36:00
 */
@Slf4j
@RestController
public class WeChatController {

    private static final String TOKEN = "cc2fb962b7bb37833008d65e905614c8";

    private static final String APPID="wxf1e42af3ecad28c7";

    private static final String APPSECRET="cc2fb962b7bb37833008d65e905614c8";
    
    /**
     * 微信验证token
     *
     * @param signature
     * @param timestamp
     * @param nonce
     * @param echostr
     * @return
     */
    @GetMapping("/checkTokenNotice.do")
    public String checkToken(@RequestParam("signature") String signature, @RequestParam("timestamp") String timestamp,
                             @RequestParam("nonce") String nonce, @RequestParam("echostr") String echostr) {
        //排序
        String[] arr = {TOKEN, timestamp, nonce};
        Arrays.sort(arr);
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }

        //sha1Hex 加密
        MessageDigest md = null;
        String temp = null;
        try {
            md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(content.toString().getBytes());
            temp = byteToStr(digest);
            log.info("加密后的token:" + temp);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        if ((temp.toLowerCase()).equals(signature)) {
            return echostr;
        }
        return null;
    }


    private static String byteToStr(byte[] byteArray){
        String strDigest = "";
        for (int i = 0; i < byteArray.length; i++) {
            strDigest += byteToHexStr(byteArray[i]);
        }
        return strDigest;
    }

    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];
        String s = new String(tempArr);
        return s;
    }


}

这个接口是微信请求用户授权地址,具体如何触发授权,这个需由具体业务需求而定,这里不做赘述。因为我只需要用到微信用户的openID,所以本文中用到的是静默授权登录(scope=snsapi_base),即不需要微信用户显式授权,不足之处是获取的用户信息较少。所以这里需要具体业务做选择。

    @Override
    public String weChatAuth(SendSmsParam req) {
        //这个url的域名必须要进行在公众号中进行注册验证,这个地址是成功后的回调地址
        String callBackUrl = "";
        if ("uat".equals(mark)) {
            callBackUrl = backUrl + "/uat/api/installment/vip/login/getOpenid.pub?mobile=" + req.getMobile();
        } else {
            callBackUrl = backUrl + "/api/installment/vip/login/getOpenid.pub?mobile=" + req.getMobile();
        }
        // 静默授权登录(scope=snsapi_base),一种为非静默授权登录(scope=snsapi_userinfo)
        String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appID + "&redirect_uri=" + URLEncoder.encode(callBackUrl) + "&response_type=code"
                + "&scope=snsapi_base" + "&state=STATE#wechat_redirect";
        log.info("forward重定向地址{" + url + "}");
        return "redirect:" + url;
    }

这个接口是微信授权成功后的回调地址,用户授权后,获得授权码,再请求获取openId接口,得到openId,进行进一步的业务操作。

 @Override
    public void getOpenidNotice(HttpServletRequest request, HttpServletResponse response) {
        String code = request.getParameter("code");
        String mobile = request.getParameter("mobile");
        log.info("手机号码[{}]", mobile);
        try {
            request.setCharacterEncoding("UTF-8");
            //通过获取access_token获得openid和access_token
            String backResult = HttpUtil.get("https://api.weixin.qq.com/sns/oauth2/access_token?appid="
                    + appID + "&secret=" + appSecret + "&code=" + code +
                    "&grant_type=authorization_code");
            //根据用户Access_token和openid获取用户信息
            log.info("用户openId[{}]", backResult);
        }
    }

调用消息发送接口发送微信消息到指定用户。

 public Boolean sendWeChatMsg(List<MessageRecordDTO> messageRecordList) {
        Boolean sendFlag = true;
        //从redis中获取accessToken
        String accessToken = redisCacheService.getMap(MAP_NAME, MAP_KEY);
        if (StrUtil.isBlank(accessToken)) {
            accessToken = this.getAndSetAccessToken();
        }
        for (MessageRecordDTO messageRecordDTO : messageRecordList) {
            UserPO userPO = userManager.getById(messageRecordDTO.getUserId());
            if (ObjectUtil.isNull(userPO)) {
                continue;
            }
            // 根据配置的消息模板构建消息体发送。
            Map<String, MsgElement> dataMap = getMsgElementMap(messageRecordDTO);
            String extend2 = userPO.getExtend2();
            if (StrUtil.isNotBlank(extend2)) {
                WeChatMsgDTO weChatMsgDTO = new WeChatMsgDTO(extend2, msgTemplateId, dataMap);
                String json = JSON.toJSONString(weChatMsgDTO);
                String result = "";
                try {
                    result = HttpUtil.post("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken, json);
                } catch (Exception e) {
                    log.error("发送微信消息失败", e);
                    sendFlag = false;
                    messageRecordDTO.setSendState("2");
                    if (messageRecordDTO.getFirstFlag() == null) {
                        DelayTaskScheduler.putWithDefaultStrategy(
                                new SyncRetryWechatNoticeTask(), new SyncRetryWechatNoticeMessage(messageRecordDTO, weChatMsgService), null);
                    }
                }
                log.info("消息记录[{}],微信消息发送结果[{}]", messageRecordDTO.getId(), result);
               // 发送成功后,可继续进行业务操作
            }
        }
        return sendFlag;
    }

	private Map<String, MsgElement> getMsgElementMap(MessageRecordDTO messageRecordDTO) {
        Map<String, MsgElement> elementMap = new HashMap<>();
        elementMap.put("first", new MsgElement("亲爱的用户,您有一条新的消息", "#000000"));
        elementMap.put("keyword1", new MsgElement(ENMessageTypeEnum.getLabelByValue(messageRecordDTO.getMsgType()), "#000000"));
        elementMap.put("keyword2", new MsgElement(this.getSendMsg(messageRecordDTO), "#000000"));
        elementMap.put("keyword3", new MsgElement(DateTimeUtil.getDateTimeFormat(messageRecordDTO.getCreateTime()), "#000000"));
        return elementMap;
    }

这里涉及到每次发送时,需要accessToken,微信设置的过期时间是2小时,我的处理方式是将accessToken存放到redis中,淘汰时间设置同微信官方过期时间,交由redis来维护accessToken。实现代码如下所示:

 private String getAndSetAccessToken() {
        String accessToken = "";
        String backResult = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appID + "&secret=" + appSecret);
        if (StrUtil.isNotBlank(backResult)) {
            WeChatTokenDTO weChatTokenDTO = JSON.parseObject(backResult, WeChatTokenDTO.class);
            accessToken = weChatTokenDTO.getAccess_token();
            redisCacheService.setMap(MAP_NAME, MAP_KEY, accessToken, weChatTokenDTO.getExpires_in());
        }
        return accessToken;
    }

发送微信消息流程图如下所示:
在这里插入图片描述

三、总结

关于微信公众号发送用户消息,微信官方文档已经很详细了,但是笔者在实践过程中,还是遇到了很多坑,前后用时几天,才将整个流程串通,因此将心路历程记录下来。结合官方文档及笔者的总结后,基本不会再遇到太大的坑了。

  • 19
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值