背景
前端时间负责小程序后端的老哥离职,所以由本人负责与前端对接开发 - 小程序消息推送,简单封装了个工具类,在此记录一下。
前提
服务通知推送需要申请小程序模板,具体申请细节可百度了解,登录地址:微信公众平台
对接文档地址:微信官方文档
获取用户登录唯一code:唯一code,使用 code 换取 openid、unionid、session_key 等信息
代码实现
- 配置类
public class WxProperties {
/**
* 小程序appid
*/
public static String appid = "登录小程序官网账户获取";
/**
* 小程序应用密钥
*/
public static String appsecret = "登录小程序官网账户获取";
/**
* 小程序获取token的地址 APPID 和 APPSECRET 记得替换为真实 appId和密钥
*/
public static String accessTokenUrl ="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";;
/**
* 小程序消息推送模板id
*/
public static String templateId = "前面申请的模板id";
/**
* 小程序消息推送地址 ACCESS_TOKEN 记得替换为真实 access_token
*/
public static String sendMessageUrl = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN";
/**
* 小程序获取openid地址 APPID 和 APPSECRET 和 JSCODE(通过 wx.login 接口获得临时登录凭证 code,微信前端唯一标识) 需替换
*/
public static String openIdUrl = "https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=APPSECRET&js_code=JSCODE&grant_type=authorization_code";
/**
* 是否启用订阅消息
*/
public static Boolean sendMessageFlag = true;
}
- 微信推送接口请求参数封装
public class WxSendMessageParam {
/**
* 接口调用凭证
*/
@JsonProperty("access_token")
private String accessToken;
/**
* 接收者(用户)的 openid
*/
private String touser;
/**
* 模板内容
*/
private HashMap data;
/**
* 订阅模板id
*/
@JsonProperty("template_id")
private String templateId;
/**
* 点击模板卡片后的跳转页面
*/
private String page;
/**
* 跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版
*/
@JsonProperty("miniprogram_state")
private String miniprogramState;
}
- 获取access_token 工具
public class AccessTokenUtil {
/**
* 获取到的凭证
*/
private String token;
/**
* 凭证有效时间(秒)
*/
private int expiresIn;
public static AccessTokenUtil getAccessToken() {
AccessTokenUtil token = new AccessTokenUtil();
String url = WxProperties.accessTokenUrl.replace(WxCommonConstants.APPID, WxProperties.appid).replace(WxCommonConstants.APPSECRET, WxProperties.appsecret);
try {
log.info("appid :" + WxProperties.appid);
log.info("appsecret :" + WxProperties.appsecret);
log.info("accessTokenUrl :" + WxProperties.accessTokenUrl);
log.info("调用微信获取凭证url :" + url);
String resp = HttpUtil.get(url);
log.info("调用微信获取凭证接口返回 :" + resp);
HashMap resMap = JSONUtil.toBean(resp, HashMap.class);
if (MapUtil.isNotEmpty(resMap)) {
token.setToken(MapUtil.getStr(resMap, WxCommonConstants.ACCESS_TOKEN));
token.setExpiresIn(MapUtil.getInt(resMap, WxCommonConstants.EXPIRES_IN));
}
log.info("token :" + token.getToken());
return token;
} catch (Exception e) {
log.error("获取access_token失败:" + ExceptionUtil.stacktraceToString(e));
return token;
}
}
}
- 微信推送工具
@Component
public class WxSendUtil {
/**
* 数值3,用于请求次数的判断
*/
private static final Integer COUNT = 3;
@Autowired
RedisService redisService;
private static WxSendUtil sendUtil;
@PostConstruct
public void init() {
sendUtil = this;
}
/**
* 请求推送接口,并处理响应
*
* @param param 微信小程序订阅消息参数类
* @param accessToken 接口调用凭证 (传空时会重新获取)
* @param count 用于次数统计,初始传0
* @return
* @author wrx
* @date 2021/11/3 21:45
*/
public static R sendAndHandle(WxSendMessageParam param, String accessToken, Integer count) {
// 重发推送请求前,判断是否已请求2次,是则结束程序
if (++count >= COUNT) {
log.info("已请求2次 向用户推送消息失败");
return R.FAIL("向用户推送消息失败");
}
log.info("进入sendAndHandle:{}",count);
// 请求推送接口,获取响应map
Map<String, Object> map = reqWxSendAPI(param, accessToken);
String errCode = MapUtil.getStr(map, WxCommonConstants.ERRCODE);
if (WxCommonConstants.SUCCESS.equals(errCode)) {
log.info(StrUtil.format("第{}次 向用户推送消息成功",count));
return new R<>("向用户推送消息成功");
} else {
// ①走到此说明推送失败了
// ②如果不是因为token过期导致请求失败,直接结束程序
log.info(StrUtil.format("第{}次 错误 errorCode:" + errCode + ",错误errormsg:" + MapUtil.getStr(map, WxCommonConstants.ERRMSG)),count);
if (!WxCommonConstants.ERROR_CODE_40001.equals(errCode)) {
log.info(StrUtil.format("第{}次 向用户推送消息失败"),count);
return R.FAIL("向用户推送消息失败");
}else{
//③走到此说明说明是token过期导致失败则重新获取token,再次发起推送请求
return sendAndHandle(param, StrUtil.EMPTY, count);
}
}
}
/**
* 请求微信推送api接口
*
* @param param 微信小程序订阅消息参数类
* @param accessToken 接口调用凭证 (传空时会重新获取)
* @return
* @author wrx
* @date 2021/11/3 19:56
*/
private static Map<String, Object> reqWxSendAPI(WxSendMessageParam param, String accessToken) {
// ①如果accessToken为空,则重新获取
param.setAccessToken(StrUtil.isBlank(accessToken) ? getAndRefreshToken() : accessToken);
// ②发送推送请求
String url = WxProperties.sendMessageUrl + param.getAccessToken();
log.info("发送订阅消息请求 :" + url + "&" + JsonUtil.toJson(param));
String resp = HttpUtil.post(url, JsonUtil.toJson(param));
log.info("发送订阅消息请求响应 :" + resp);
Map<String, Object> map = JSONUtil.toBean(resp, HashMap.class);
if (MapUtil.isEmpty(map)) {
log.info("请求微信推送接口时,发生了未知的情况,响应为空");
throw new ServiceException(WxCodeMsg.REQ_API_EXCEPTION.fillArgs("未知的响应结果"));
}
return map;
}
/**
* 获取并刷新缓存中的access_token
*
* @return token
* @author wrx
* @date 2021/11/3 17:02
*/
private static String getAndRefreshToken() {
AccessTokenUtil token = AccessTokenUtil.getAccessToken();
log.info("获取到的token:" + JSONUtil.toJsonStr(token));
String accessToken = token.getToken();
//更新redis中的微信凭证和过期时间
sendUtil.redisService.set(WxCommonConstants.ACCESS_TOKEN_REDIS_PREFIX, accessToken, token.getExpiresIn());
return accessToken;
}
}
- 业务层组装WxSendMessageParam 并远程调用工具
@Slf4j
@Component
public class HkWxSendUtil {
/**
* 微信小程序推送消息远程服务
*/
@Autowired
RemoteWxSendMessageService remoteWxSendMessageService;
private static HkWxSendUtil sendUtil;
@PostConstruct
public void init() {
sendUtil = this;
}
/**
* 发送小程序推送消息
*
* @param createrId 单证创建人id
* @param sourceMap 存储模板内容的源map
* @param pageUrl 点击推送详情要跳转的页面
* @param templateId 模板id
* @return
* @author wrx
* @date 2021/11/3 16:22
*/
public static void sendMessage(HashMap<String, Object> sourceMap,String openId,String pageUrl,String templateId) {
WxSendMessageParam messageParam = new WxSendMessageParam();
messageParam.setTouser(openId);
messageParam.setData(sourceMap);
messageParam.setTemplateId(templateId);
messageParam.setPage(pageUrl);
log.info("微信订阅消息参数 :" + JSONUtil.toJsonStr(messageParam));
R r = sendUtil.remoteWxSendMessageService.newSendWxMessage(messageParam, SecurityConstants.FROM_IN);
log.info("微信订阅消息返回 :" + JSONUtil.toJsonStr(r));
}
/**
* 添加模板内容
*
* @param source 存储模板内容的源map
* @param keyword key
* @param value value
* @return
* @author wrx
* @date 2021/11/3 14:26
*/
public static void putWxParamMap(HashMap<String, Object> source, String keyword, Object value) {
HashMap<String, Object> paramMap = new HashMap<>(2);
paramMap.put("value",value);
source.put(keyword,paramMap);
}
}
- 调用推送工具(伪代码)
main:
log.info("进入小程序推送方法");
//① 业务校验
//② 组装推送对象
HashMap<String,Object> sourceMap = new HashMap<>(8);
HkWxSendUtil.putWxParamMap(sourceMap,"模板key1","key1对应的值");
HkWxSendUtil.putWxParamMap(sourceMap,"模板key2","key2对应的值");
HkWxSendUtil.putWxParamMap(sourceMap,"模板key3","key3对应的值");
HkWxSendUtil.putWxParamMap(sourceMap,"模板key4","key4对应的值");
HkWxSendUtil.putWxParamMap(sourceMap,"模板key5","key5对应的值");
//③调用业务层工具推送
HkWxSendUtil.sendMessage(sourceMap,openId, pageUrl,templateId);