微信订阅通知实战



前言

截止至2021-11-2, 微信的订阅通知还在灰度测试当中,传说微信的模板消息即将下线,所以采用微信的订阅通知来发送通知.  本文为实战中的微信订阅通知功能开发记录, 目前已经在线上测试使用,仅供参考.



一、微信订阅通知是什么?

模板消息被滥用, 很多用户被骚扰, 微信为了管理,引入订阅通知, 分为一次性订阅,和长期订阅两种,一次性订阅: 订阅一次,能给用户发送一条消息.  长期订阅: 订阅一次, 目前来看无限次发送消息. 本文为长期订阅案例, 一次性订阅对于我们来说没有价值.


二、使用步骤


1.开通订阅通知服务

根据微信文档开通即可, 需要提供公司的 <<资质授权文件>>,审核几个小时一般就会有结果,通过后只能选取微信提供的模板,长期订阅的模板很少,就几个,不一定符合业务需求,只能凑合用.


2.获取access_token

     必须操作!

  1. yaml文件中添加
    wechat:
      appId: wx005a........6ff
      appSecret: 18000b98b...........71b3e058

     2.  AccessToken 类接收 "获取access_token接口"

@Data
public class AccessToken {

    private String accessToken;
    private Long expiresIn;
}

      3.WeixinToken  内存中保存access_token 

/**
 * @Title: WeixinToken
 * @Description: 持有微信Access_token的对象
 * @date 2021-10-25 11:09
 */
public class WeixinToken {

    public static String token;

}

        4.WeixinAccessTokenUtil 获取Access_token

@Component
public class WeixinAccessTokenUtil {

    private Logger logger = LoggerFactory.getLogger(WeixinAccessTokenUtil.class);

    public AccessToken getToken(String appid, String appSecrect) {

        AccessToken token = new AccessToken();
        String url = "https://api.weixin.qq.com/cgi-bin/token" + "?grant_type=client_credential&appid=" + appid+ "&secret=" + appSecrect;

        HttpClient httpClient = HttpClientBuilder.create().build();
        HttpGet httpGet = new HttpGet(url);
        ResponseHandler<String> responseHandler = new BasicResponseHandler();
        try {
            String response = httpClient.execute(httpGet,responseHandler);
            JsonObject returnData = new JsonParser().parse(response).getAsJsonObject();
            if(returnData != null){
                token.setAccessToken(returnData.get("access_token").getAsString());
                token.setExpiresIn(returnData.get("expires_in").getAsLong());
            }
        } catch (IOException e) {
            token = null;
            e.printStackTrace();
            logger.error("系统获取access_token出错了!");
        }
        return token;
    }

}

5.开启spring定时任务  加入@EnableScheduling 注解

@SpringBootApplication
@ServletComponentScan
@EnableScheduling
public class WeixinApplication {

    public static void main(String[] args) {
        SpringApplication.run(WeixinApplication.class, args);
    }

}

6.定时任务获取access_token (俩小时失效,必须俩小时之内更新access_token)

package com.qqrw.wx.service.impl;

import com.qqrw.wx.entity.WeixinToken;
import com.qqrw.wx.util.WeixinAccessTokenUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * @Title: AccessTokenTask
 * @Description: 获取微信access_token 定时任务
 * @date 2021-10-25 11:10
 */
@Component
public class AccessTokenTask {

    private Logger logger = LoggerFactory.getLogger(AccessTokenTask.class);

    @Autowired
    private WeixinAccessTokenUtil weixinAccessTokenUtil;

    @Value("${wechat.appId}")
    private String appId;

    @Value("${wechat.appSecret}")
    private String appSecret;

    @Scheduled(initialDelay = 1000, fixedDelay = 7000 * 1000)
    public void getWeixinAccessToken() {
        try {
            String token = weixinAccessTokenUtil.getToken(appId, appSecret).getAccessToken();
            WeixinToken.token = token;
            logger.info("获取到的微信accessToken为" + token);
        } catch (Exception e) {
            logger.error("获取微信adcessToken出错,信息如下");
            e.printStackTrace();
        }
    }

    @Scheduled(initialDelay = 3000, fixedDelay = 1000 * 1000)
    public void testWeixinAccessT() {
        logger.info(WeixinToken.token);
    }
}

7.其中http发送用的是httpclient, 如果没遗漏的话,就不需要其他依赖了.

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>${httpClient.version}</version>
        </dependency>

我们微信业务和内部业务项目是分离的,所以只在微信项目中实现接口供内部项目调用.具体的接口如下

1. WeixinMsg 接口用来接收业务相关数据

@Data
public class WeixinMsg {

    private String userCode;
    private String templateId;
    private Map<String,Object> map;

    public String getUserCode() {
        return userCode;
    }

    public void setUserCode(String userCode) {
        this.userCode = userCode;
    }

    public String getTemplateId() {
        return templateId;
    }

    public void setTemplateId(String templateId) {
        this.templateId = templateId;
    }

    public Map<String, Object> getMap() {
        return map;
    }

    public void setMap(Map<String, Object> map) {
        this.map = map;
    }
}

2.Controller中 , 其中R是自定义的返回结果实体,替换为你自己的返回结果Result即可!

 //发送微信订阅消息
    @CrossOrigin(maxAge = 3600, origins = "*")
    @PostMapping("/subscribe/sendWeixinMsg")
    public R sendWeixinMsg(@RequestBody WeixinMsg weixinMsg) {
        Assert.isTrue(null != weixinMsg.getUserCode() && null != weixinMsg.getTemplateId() && null != weixinMsg.getMap(), "参数不正确");
        return weixinSendMsgService.send(weixinMsg);
    }

3.接口

public interface WeixinSendMsgService {

    R send(WeixinMsg weixinMsg);

}

4.service代码,

UserWechatMapper 和 其中String openId = userWechatMapper.queryOpenIdByUserCode(userCode);是我们有专门页面,实现微信openId和业务之间Id的绑定. 可根据实际情况, 只要知道微信用户的openId即可,具体获取openId的功能,参考官方文档!

ImSmsWeixinMapper 和下方的private方法是业务代码,记录发送的日志,屏蔽或改成你自己的业务代码即可.

@Service("weixinSendMsgService")
public class WeixinSendMsgServiceImpl implements WeixinSendMsgService {

    private Logger logger = LoggerFactory.getLogger(WeixinSendMsgServiceImpl.class);

    @Resource
    private UserWechatMapper userWechatMapper;
    @Resource
    private ImSmsWeixinMapper imSmsWeixinMapper;

    @Override
    public R send(WeixinMsg weixinMsg) {
        System.out.println(weixinMsg);
        Date now = new Date();
        String userCode = weixinMsg.getUserCode();
        String openId = userWechatMapper.queryOpenIdByUserCode(userCode);
        String templateId = weixinMsg.getTemplateId();
        Map<String, Object> map = weixinMsg.getMap();
        Map<String, Map<String, Object>> data = new HashMap<>();

        if (StringUtils.isBlank(openId)) {
            logger.info("对用户{}发送模板{}消息失败,用户未绑定无法获取openId!", userCode, templateId);
            imSmsWeixinMapper.insertSelective(getFailLog(now, userCode, openId, templateId, data, "用户未绑定,无法发送模板消息"));
            return R.fail("微信消息发送失败,用户未绑定");
        }

        for (Map.Entry<String, Object> entry : map.entrySet()) {
            HashMap<String, Object> one = new HashMap<>();
            one.put("value", entry.getValue());
            data.put(entry.getKey(), one);
        }
        System.out.println(openId);
        System.out.println(data);

        String url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/bizsend?access_token=" + WeixinToken.token;
        try {
            HttpClient httpClient = HttpClientBuilder.create().build();
            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader("Accept", "application/json");
            httpPost.setHeader("Content-Type", "application/json");
            String charSet = "UTF-8";
            Map<String, Object> params = new HashMap<>();
            params.put("access_token", WeixinToken.token);
            params.put("touser", openId);
            params.put("template_id", templateId);
            params.put("data", data);
            StringEntity entity = new StringEntity(JsonUtils.toJson(params), charSet);
            httpPost.setEntity(entity);
            ResponseHandler<String> responseHandler = new BasicResponseHandler();
            String response = httpClient.execute(httpPost, responseHandler);
            JsonObject returnData = new JsonParser().parse(response).getAsJsonObject();
            int errcode = returnData.get("errcode").getAsInt();
            String errmsg = returnData.get("errmsg").getAsString();
            if ("ok".equals(errmsg)) {
                imSmsWeixinMapper.insertSelective(getSuccessLog(now, userCode, openId, templateId, data));
                logger.info("对用户{}发送模板{}消息成功!", userCode, templateId);
            } else {
                logger.error("对用户{}发送模板{}消息异常!", userCode, templateId);
                logger.error("发送数据为:[{}]!", data);
                logger.error("错误码:[{}]!", errcode);
                logger.error("错误信息:[{}]!", errmsg);
                imSmsWeixinMapper.insertSelective(getFailLog(now, userCode, openId, templateId, data, errmsg));
                return R.fail("微信消息发送失败,错误码" + errcode);
            }

        } catch (IOException e) {
            e.printStackTrace();
            logger.error("对用户{}发送模板{}消息异常!", userCode, templateId);
            logger.error("发送数据为:[{}]!", data);
            imSmsWeixinMapper.insertSelective(getFailLog(now, userCode, openId, templateId, data, "程序发送出现了异常,请检查" + e.getMessage()));
        }
        return R.ok();
    }

    private ImSmsWeixin getSuccessLog(Date now, String userCode, String openId, String templateId, Map<String, Map<String, Object>> data) {
        ImSmsWeixin imSmsWeixin = genLog(now, userCode, openId, templateId, data);
        imSmsWeixin.setState(1);
        return imSmsWeixin;
    }

    private ImSmsWeixin getFailLog(Date now, String userCode, String openId, String templateId, Map<String, Map<String, Object>> data, String errorMsg) {
        ImSmsWeixin imSmsWeixin = genLog(now, userCode, openId, templateId, data);
        imSmsWeixin.setState(2);
        imSmsWeixin.setErrorMsg(errorMsg);
        return imSmsWeixin;
    }

    private ImSmsWeixin genLog(Date now, String userCode, String openId, String templateId, Map<String, Map<String, Object>> data) {
        ImSmsWeixin imSmsWeixin = new ImSmsWeixin();
        imSmsWeixin.setUserCode(userCode);
        imSmsWeixin.setOpenId(openId);
        imSmsWeixin.setTemplateId(templateId);
        imSmsWeixin.setData(JsonUtils.toJson(data));
        imSmsWeixin.setSendTime(now);
        return imSmsWeixin;
    }


}

5. 一个问题:  开发过程中遇到     接口提示{"errcode":47001,"errmsg":"data.......}的问题, 检查过json, 网上说单引号双引号的问题,   我测试不是引号的问题,只要json格式正确即可.  主要是发送的方式问题.  开始的时候用的是UrlEncodeEntity这种方式发送的, 导致出现了47001问题, 上面代码发送方式完全没问题

6. 我在官网上找了,没找到可以提供测试的接口和方法, 只有线上测试了,  发送一个图文消息,  在文章末尾插入订阅消息插件 如图

 7.用户在文章中点击订阅通知,就能选择相关订阅, 订阅后,再点击订阅就不再弹出订阅框了.

接口完成后,在业务项目中调用接口即可.

public class WeixinMsgUtil {

    private static HttpClient httpClient = new DefaultHttpClient();

    public static void sendWeixinSms(String userCode, String templateId, Map data) {
        HashMap<String, Object> params = new HashMap<>();
        params.put("userCode",userCode);
        params.put("templateId",templateId);
        params.put("map",data);

        String url = "http://123.456.12.12:8080/api/subscribe/sendWeixinMsg";

        HttpPost httpPost = new HttpPost(url);
        httpPost.setHeader("Accept", "application/json");
        httpPost.setHeader("Content-Type", "application/json");
        String charSet = "UTF-8";

        StringEntity entity = new StringEntity(JsonUtil.toJson(params), charSet);
        httpPost.setEntity(entity);
        ResponseHandler<String> responseHandler = new BasicResponseHandler();
        try {
            httpClient.execute(httpPost, responseHandler);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值