我们微信业务和内部业务项目是分离的,所以只在微信项目中实现接口供内部项目调用即可.具体的接口如下
前言
截止至2021-11-2, 微信的订阅通知还在灰度测试当中,传说微信的模板消息即将下线,所以采用微信的订阅通知来发送通知. 本文为实战中的微信订阅通知功能开发记录, 目前已经在线上测试使用,仅供参考.
一、微信订阅通知是什么?
模板消息被滥用, 很多用户被骚扰, 微信为了管理,引入订阅通知, 分为一次性订阅,和长期订阅两种,一次性订阅: 订阅一次,能给用户发送一条消息. 长期订阅: 订阅一次, 目前来看无限次发送消息. 本文为长期订阅案例, 一次性订阅对于我们来说没有价值.
二、使用步骤
1.开通订阅通知服务
根据微信文档开通即可, 需要提供公司的 <<资质授权文件>>,审核几个小时一般就会有结果,通过后只能选取微信提供的模板,长期订阅的模板很少,就几个,不一定符合业务需求,只能凑合用.
2.获取access_token
必须操作!
-
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();
}
}
}