第一步:申请模板获取模板ID: 登录微信公众平台 -> 功能 -> 订阅消息 -> 对里面的模板进行选用或者自定义
公共模板库里面的模板分为一次性订阅 和永久订阅,这个是根据你创建小程序时选择的行业进行区分的。
https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/subscribe-message.html 官方文档中有详细说明
第二步: 消息订阅接口:需要在小程序端调起订阅接口,需要前端完成。官方地址:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/subscribe-message/wx.requestSubscribeMessage.html
第三步: 服务端发送消息到服务通知,官方文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html
我这里采用的是https的调用方式
代码实现:
- 核心代码
package com.minapp.management.service;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import com.minapp.management.config.WxProperties;
import com.minapp.management.entity.dto.AppletTemplateMessageSendDTO;
import com.minapp.management.utils.CacheManager;
import com.minapp.management.utils.HttpClientUtil;
import lombok.Synchronized;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;
@Component
public class WxAppletRemoteService {
private Logger logger = LoggerFactory.getLogger(WxAppletRemoteService.class);
private static final String WXAPPLETURl="https://api.weixin.qq.com/cgi-bin";
@Autowired
private WxProperties properties;
@Autowired
private ObjectMapper objectMapper;
/**
* 获取AccessToken
* @return
*/
@Synchronized
public String getAccessToken() {
String appid=null;
String secret=null;
// 这里我是从配置文件中取得appid和appsecret
appid=properties.getAppId();
secret=properties.getAppSecret();
//查询token是否存在
String key="dowin"+appid+"_AccessToken";
// 使用缓存先查询AccessToken是否存在,我这里使用的是本地缓存,如果只是存一个token,本地缓存足以,不需要使用专门缓存的服务,比如reids。
String accessToken = CacheManager.getData(key);
// 存在直接返回,不存在重新获取AccessToken
if (!Strings.isNullOrEmpty(accessToken)){
return accessToken;
}
// 获取AccessToken的url
String grantType="client_credential";
String url=WXAPPLETURl+"/token?grant_type="+grantType+"&appid="+appid+"&secret="+secret;
// 获取到AccessToken
String token = HttpClientUtil.get(url);
Map<String,Object> map = null;
try {
map = objectMapper.readValue(token, Map.class);
} catch (IOException e) {
logger.error("小程序异常通知-获取AccessToken-转化异常",e);
}
String access_token = String.valueOf(map.get("access_token"));
// 把AccessToken存入缓存中,并设置过期时间,因为access_token的过期时间是两小时,我们缓存的时间一定要小于两小时,
CacheManager.setData(key,access_token,5000);
if (map.get("errcode")!=null || map.get("errmsg")!=null){
String errcode = String.valueOf(map.get("errcode"));
String errmsg = String.valueOf(map.get("errmsg"));
if (!errcode.equals("0")){
logger.error("获取token失败:code="+errcode+"msg="+errmsg);
return null;
}
}
return access_token;
}
/**
* 同一消息发送接口
* AppletTemplateMessageSendDTO 是一个传输类
*/
public String uniformMessageSend(AppletTemplateMessageSendDTO data) {
String token = getAccessToken();
// 调用发型接口
String url=WXAPPLETURl+"/message/subscribe/send?access_token="+token;
String returnData = HttpClientUtil.post(url,JSON.toJSONString(data));
Map<String,Object> map = null;
try {
map = objectMapper.readValue(returnData, Map.class);
} catch (IOException e) {
logger.error("小程序异常通知-同一消息发送-转化异常",e);
}
String errcode = String.valueOf(map.get("errcode"));
String errmsg = String.valueOf(map.get("errmsg"));
if (!errcode.equals("0")){
logger.error("消息发送失败:code="+errcode+"msg="+errmsg);
}
return errcode;
}
}
- 传输实体类
package com.minapp.management.entity.dto;
import lombok.Data;
import java.io.Serializable;
//小程序模板消息发送模板
@Data
public class AppletTemplateMessageSendDTO implements Serializable {
//接收者(用户)的 openid
private String touser;
//所需下发的订阅模板id
private String template_id;
//所跳转的页面
private String page;
//模板消息内容
private Object data;
}
- 本地缓存类
package com.minapp.management.utils;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
// 本地缓存类
public class CacheManager {
private static Map<String, CacheData> CACHE_DATA = new ConcurrentHashMap<>();
public static <T> T getData(String key, Load<T> load, int expire) {
T data = getData(key);
if (data == null && load != null) {
data = load.load();
if (data != null) {
setData(key, data, expire);
}
}
return data;
}
// 根据key获取数据
public static <T> T getData(String key) {
CacheData<T> data = CACHE_DATA.get(key);
if (data != null && (data.getExpire() <= 0 || data.getSaveTime() >= System.currentTimeMillis())) {
return data.getData();
}
return null;
}
// 存入数据并设置过期时间
public static <T> void setData(String key, T data, int expire) {
CACHE_DATA.put(key, new CacheData(data, expire));
}
// 根据key清除数据
public static void clear(String key) {
CACHE_DATA.remove(key);
}
// 清除所有缓存数据
public static void clearAll() {
CACHE_DATA.clear();
}
public interface Load<T> {
T load();
}
private static class CacheData<T> {
CacheData(T t, int expire) {
this.data = t;
this.expire = expire <= 0 ? 0 : expire * 1000;
this.saveTime = System.currentTimeMillis() + this.expire;
}
private T data;
private long saveTime; // 存活时间
private long expire; // 过期时间 小于等于0标识永久存活
public T getData() {
return data;
}
public long getExpire() {
return expire;
}
public long getSaveTime() {
return saveTime;
}
}
}
测试:
AppletTemplateMessageSendDTO appletTemplateMessageSendDTO = new AppletTemplateMessageSendDTO();
appletTemplateMessageSendDTO.setTouser("openid");
appletTemplateMessageSendDTO.setTemplate_id("模板id");
appletTemplateMessageSendDTO.setPage("跳转的页面");
Map<String,Object> map = new HashMap<>();
Map<String,Object> thing6 = new HashMap<>();
Map<String,Object> time8 = new HashMap<>();
Map<String,Object> phone_number7 = new HashMap<>();
Map<String,Object> thing10 = new HashMap<>();
thing6.put("value","xqd");
time8.put("value","2020-12-29");
phone_number7.put("value", "17600026666");
thing10.put("value","测试一下订阅消息");
map.put("thing6",thing6);
map.put("time8",time8);
map.put("phone_number7",phone_number7);
map.put("thing10",thing10);
appletTemplateMessageSendDTO.setData(map);
wxAppletRemoteService.uniformMessageSend(appletTemplateMessageSendDTO);
发送成功