小程序原模板消息已在2020年1月10号停止使用,之前通过收集form_id来推送模板消息的日子一去不复返,在看过新版小程序订阅消息后,我重新封装了前后端模板消息(订阅下消息)推送的实现
推送业务流程:点击事件拉起wx.requestSubscribeMessage模板消息授权->用户同意/拒绝->后台API推送
小程序端实现:
利用app.js全局函数来实现-在app.js中添加以下函数,通过版本库的比较来引导用户打开订阅授权以及每一个模板ID授权之后只弹出一次,过期再次重新拉起订阅授权
//拉起订阅模板消息授权 tempId为小程序后台添加的模板消息ID
subscribeMessage:function(tempId) {
let _this = this;
//需要订阅的消息模板,在微信公众平台手动配置获取模板ID
let message = tempId;
let messageEx = CACHE.get(tempId,'');
if (messageEx == 'accept') {
//用户已经允许过
return;
}
//如果总是拒绝(subscriptionsSetting,2.10.1库才支持)
if (_this.versionCompare('2.10.1')) {
wx.getSetting({
withSubscriptions: true, //是否同时获取用户订阅消息的订阅状态,默认不获取
success: (res) => {
if (res.subscriptionsSetting && res.subscriptionsSetting.itemSettings &&
res.subscriptionsSetting.itemSettings[message] == "reject") {
//打开设置去设置
_this.openConfirm('检测到您没打开推送权限,是否去设置打开?')
} else {
wx.requestSubscribeMessage({
tmplIds: [message],
success: (res) => {
if (res[message] == 'accept') {
//用户允许
CACHE.put(message,'accept',259200);//缓存3天
}
},
fail: (res) => {
console.info(res)
},
})
}
}
})
} else if (app.versionCompare('2.4.4')) {
wx.requestSubscribeMessage({
tmplIds: [message],
success: (res) => {
if (res[message] == 'accept') {
//用户允许
CACHE.put(message,'accept',259200);//缓存3天
}
},
fail: (res) => {
console.info(res)
},
})
}
},
//打开设置
openConfirm:function(message) {
wx.showModal({
content: message,
confirmText: "确认",
cancelText: "取消",
success: (res) => {
//点击“确认”时打开设置页面
if (res.confirm) {
wx.openSetting({
success: (res) => {
console.log(res.authSetting)
},
fail: (error) => {
console.log(error)
}
})
} else {
console.log('用户点击取消')
}
}
});
},
//基础库版本比较
versionCompare:function(v) {
const version = wx.getSystemInfoSync().SDKVersion
if (this.compareVersion(version, v) >= 0) {
return true
} else {
return false
}
},
//版本比较
compareVersion:function(v1, v2) {
v1 = v1.split('.')
v2 = v2.split('.')
var len = Math.max(v1.length, v2.length)
while (v1.length < len) {
v1.push('0')
}
while (v2.length < len) {
v2.push('0')
}
for (var i = 0; i < len; i++) {
var num1 = parseInt(v1[i])
var num2 = parseInt(v2[i])
if (num1 > num2) {
return 1
} else if (num1 < num2) {
return -1
}
}
return 0
},
使用方式:在小程序任何点击事件触发的方法中调用 如:
onClosePop(fn) {
app.subscribeMessage('BOEzbw5PLtJjuAcWA0V0051y-BZWK5x2EzqK6lOp6W4');
},
至此小程序前台已完成订阅消息授权的操作,接下来通过Java后台根据业务需求来推送消息
新建WeixinSmallAppTemplateService.java
package com.xcloud.api.wechat.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xcloud.api.wechat.entity.Template;
import com.xcloud.api.wechat.entity.TemplateParam;
import com.xcloud.common.utils.https.HttpClientUtil;
import com.xcloud.common.utils.wechat.ListInitializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Service
public class WeixinSmallAppTemplateService {
public static final String SEND_TEMPLATE_MESSAGE = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={{ACCESSTOKEN}}";
/**
* 推送小程序模板消息
* @param account 原始ID
* @param openid 用户微信ID
* @param templateCode 模板消息ID
* @param page 跳转页面
* @param initializer 初始化模板数据
*/
public void send(String account, String openid, String templateCode, ListInitializer<TemplateParam> initializer, String page, String accessToken) {
List<TemplateParam> map = new ArrayList<>();
initializer.init(map);
Template template = new Template();
//拼接数据
template.setTouser(openid);
template.setTemplate_id(templateCode);
template.setPage(page);
template.setData(map);
template.setMiniprogram_state(MINIAPP_STATE);//跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版
String json = template.toJSON();
String url = SEND_TEMPLATE_MESSAGE.replace("{{ACCESSTOKEN}}", accessToken);
String ret = HttpClientUtil.sendPost(url, json);
JSONObject jsonResult = JSON.parseObject(ret);
if(jsonResult!=null){
System.out.println(jsonResult);
int errorCode=jsonResult.getInteger("errcode");
String errorMessage=jsonResult.getString("errmsg");
if(errorCode==0){
log.error("Send Success");
}else{
log.error("订阅消息发送失败:"+errorCode+","+errorMessage);
}
}
}
}
所用到的工具类和方法
import lombok.*;
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class TemplateParam {
private String key;
private String value;
}
import lombok.*;
import java.util.List;
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Template {
private String touser; //接收者(用户)的 openid
private String template_id; //所需下发的订阅模板id
private String miniprogram_state; //跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版
private List<TemplateParam> data; //模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } }
private String page; //点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。
public String toJSON() {
StringBuffer buffer = new StringBuffer();
buffer.append("{");
buffer.append(String.format("\"touser\":\"%s\"", this.touser)).append(",");
buffer.append(String.format("\"template_id\":\"%s\"", this.template_id)).append(",");
buffer.append(String.format("\"page\":\"%s\"", this.page)).append(",");
buffer.append("\"data\":{");
TemplateParam param = null;
for (int i = 0; i < this.data.size(); i++) {
param = data.get(i);
// 判断是否追加逗号
if (i < this.data.size() - 1){
buffer.append(String.format("\"%s\": {\"value\":\"%s\"},", param.getKey(), param.getValue()));
}else{
buffer.append(String.format("\"%s\": {\"value\":\"%s\"}", param.getKey(), param.getValue()));
}
}
buffer.append("}");
buffer.append("}");
return buffer.toString();
}
}
/**
* 发送post请求 json格式
* @param url
* @param param
* @return
*/
public static String sendPost(String url, String param) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Content-Type", "application/json");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(param);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!"+e);
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally{
try{
if(out!=null){
out.close();
}
if(in!=null){
in.close();
}
}
catch(IOException ex){
ex.printStackTrace();
}
}
return result;
}
import java.util.List;
/**
* (description)
* created at 2014/9/30
*
* @author laoWang
*/
public interface ListInitializer<K> {
void init(List<K> m);
}
推送订阅消息调用方法:
/**
* 发送用户登记变更订阅提醒
* @param account
* @param openId
* @param userName
* @param userTitle
* @throws Exception
*/
public void sendGradeChangeTemplate(String account, String openId, String userName, String userTitle, String accessToken) throws Exception{
Date date = new Date();
//日期格式化显示,首先定义格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//显示2017-10-27 10:00:00格式
final String thing1 = userName;
final String thing2 = userTitle;
final String time3 = sdf.format(date);
try {
weixinSmallAppTemplateService.send(account, openId, "HWLCaIqUjLHYKvDAFXqVa497izzPHjFlcUWozps2lNE",
new ListInitializer<TemplateParam>() {
@Override
public void init(List<TemplateParam> m) {
m.add(new TemplateParam("thing1", thing1));
m.add(new TemplateParam("thing2", thing2));
m.add(new TemplateParam("time3", time3));
}
}, "pages/mine/mine",accessToken);
} catch (Exception e) {
log.error("小程序发送订阅消息失败: " + e.getMessage());
e.printStackTrace();
throw new Exception(e.getMessage());//最后一行
}
}
至此,完整的小程序订阅消息(原模板消息)前后端封装实现已经完成,以上代码中工具类注解包括accessToken获取请根据自己业务需要进行实现。