首先说一下思路
1、在微信小程序后台配置模板
2、在小程序上让用户触发订阅消息提示
将配好的模板id,使用wx.requestSubscribeMessage,使用户同意订阅消息,获取一次可以发订阅的机会
3、后台发送消息subscribeMessage.send
1 小程序后台配置
1.1 小程序后台添加我的模板,添加成功后如图,红色部分就是后面发消息需要用到的模板id
1.2 模板配好了,需要注意的就是下面图片中的详情内容,关系到你向小程序发送模板时候的data数据,必须遵循这个格式。
2 小程序前台配置
2.1 小程序前端,调起小程序订阅消息界面,官方文档。
这里解释一个概念,一次性模板和永久模板,订阅状态和发送订阅消息次数的关系。
一次性模板:除了特殊行业和游戏,普通使用的模板都是一次性模板,即每一调用都需要用户同意订阅,如果用户点了 总是则默认同意。
订阅状态:指在弹出的订阅消息界面,用户可以选择同意/不同意订阅【订单发货提醒】和总是保持以上选项,可一次弹出最多三个模板的选择。这是只是用户单方面的选择是否要接受你的消息,如果用户只是点了同意,而没有勾选总是按钮,那个你将获得用户所勾选的模板每个模板一次发送订阅消息的机会;
例如:你放了三个模板【abc】,用户勾选了a,没有勾选bc点击同意。那个你就只能通过a模板推送一条订阅消息。
如果用户点了总是,那个下次将不会弹出提示框而是默认用户之前的选择,同意的自动同意,多一次机会,拒绝的自动拒绝。而且用户只能通过小程序设置里面修改通知,开发者没有办法修改这个,最多是通过订阅状态判断,然后提供授权按钮给客户,诱导客户修改授权。
订阅消息次数:图上诉所讲,用户每同意一次按钮,你就有一次机会,可以累加。但是如果用户否一天点了拒收此类信息,那么订阅消息次数将会清零。
2.2 前端代码
注:调起事件要用bindtap
触发。
按钮
.js代码
//定义模板id集合
var tempIdList = ['2rowP6hlpGVa2_GGjS_zPeyEZeRrKMVVRZH851xhqbk','hREKVlK3qkzftKdw_TEVJ7xe3tq2amSWsky5L-B5PNA']
wx.requestSubscribeMessage({
tmplIds: tempIdList,
success (res) {
// res格式
//{
// errMsg: "requestSubscribeMessage:ok",
//zun-LzcQyW-edafCVvzPkK4de2Rllr1fFpw2A_x0oXE: "accept"
// }
if (res["errMsg"] == "requestSubscribeMessage:ok") {
console.log("调用订阅消息成功!")
}
//下面方法判断用户具体同意或拒绝的模板。可以存放数据库或者做其他操作,我这里只是简单的判断输出
tempIdList.map(function(item, index){
var state ="";
if (res[item] == "accept") {
state = "用户同意订阅";
}else if(res[item] == "reject"){
state = "用户不同意订阅";
}else if (res[item] == "ban"){
state = "后台被禁用";
}
console.log(“模板” + item + “状态” + state);
})
},
fail (){
console.log("弹出订阅信息失败")
},
complete(){
console.log("申请订阅结束")
}
})
3 推送订阅消息给用户
3.1获取调用接口凭证Access_token获取AccessToken
通过appid和secret换取,返回token和有效时间(官方描述俩小时),建议存放redis中,设置时间一小时,没有了就重新获取,有的话就取redis的。
/**
* 小程序获取推送订阅消息需要的AccessToken
* @return
*/
public static String getAccessToken(String appid, String secret) {
String accesstoken = (String) RedisUtil.GetObjectByTiket("message_accesstoken");
if (StringUtils.isBlank(accesstoken)) {
String params = "grant_type=" + "client_credential" + "&appid=" + appid + "&secret=" + secret;
String sr = HttpRequestHelper.sendGet("https://api.weixin.qq.com/cgi-bin/token", params);
//解析相应内容(转换成json对象)
JSONObject json = JSONObject.fromObject(sr);
//获取会话密钥(session_key)
String access_token = json.get("access_token").toString();
if (StringUtils.isNotBlank(access_token)) {
accesstoken = access_token;
RedisUtil.SetTiketObject("message_accesstoken", access_token, RedisUtil.EXRP_HOUR);
}
}
return accesstoken;
}
3.2向订阅消息接口推送消息
参数格式一定要按照消息模板中的参数格式去配置,不然的话会报错,请求接口基础参数如下:
touser——用户openid
template_id——模板ID
page——配置则跳转小程序中对应的页面,路径以app.json中配置的路径为准,可以通过?传参;不配置则无跳转
miniprogram_state——代表订阅消息跳转小程序的类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版,
lang——进入小程序查看”的语言类型,支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),默认为zh_CN
进入小程序查看”的语言类型,支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),默认为zh_CN。
注: 参数设置建议采用JSONObject对象进行封装,我下面的参数结构是亲测成功的,可以正常使用,这里踩了很多坑,好多数据结构都不对,参数错误。
发送成功errcode为0,失败则有具体的errcode。
/**
* 订单发货通知
* @param openid 购买人openid
* @param accesstoken 接口调用凭证
* @param orderno 32位以内数字、字母或符号
* @param goodsthind 20以内字符
* @param expressname 10个以内纯汉字或20个以内纯字母或符号
* @param expressno 32位以内数字、字母或符号
* @param delivertime 2019年10月1日 15:01
* @return
*/
public static MessageResult sendOderFahuoMessage(String miniprogram_state, String messageTempId, String openid,String accesstoken,String orderno,String goodsthind, String expressname, String expressno, String delivertime) {
//消息内容
JSONObject all = new JSONObject();
all.put("touser", openid);
all.put("template_id", messageTempId);
all.put("page", "pages/follipoo/order-info/order-info?orderno="+orderno);
all.put("miniprogram_state",miniprogram_state);
all.put("lang", "zh_CN");
JSONObject character_string1 = new JSONObject();
character_string1.put("value", orderno);
JSONObject thing2 = new JSONObject();
thing2.put("value", goodsthind);
JSONObject name3 = new JSONObject();
name3.put("value", expressname);
JSONObject character_string4 = new JSONObject();
character_string4.put("value", expressno);
JSONObject date10 = new JSONObject();
date10.put("value", delivertime);
JSONObject data = new JSONObject();
data.put("character_string1", character_string1);
data.put("thing2", thing2);
data.put("name3", name3);
data.put("character_string4", character_string4);
data.put("date10", date10);
all.put("data", data);
//正式环境无需miniprogram_state
String params = JsonUtil.objectToJson(all);
String sr = HttpRequestHelper.sendPost2("https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token="+accesstoken, params);
//解析相应内容(转换成json对象)
JSONObject json = JSONObject.fromObject(sr);
//获取会话密钥(session_key)
String errcode = json.get("errcode").toString();
String errmsg = json.get("errmsg").toString();
MessageResult result = new MessageResult();
if (errcode.equals("0")) {
result.setState(true);
}else {
result.setState(false);
result.setErrCode(errcode);
if (!errcode.equals("47003")) {
//这里是我按照官方的错误代码提示自己写了一个根据code区分报错信息的方法,这里就不放出来了。
errmsg = parseErrCode(errcode);
}
result.setErrMsg(errmsg);
}
return result;
};
发送请求的工具
HttpRequestHelper
/**
* 向指定URL发送GET方法的请求
*
* @param url
* 发送请求的URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + (StringHelp.IsNullOrEmpty(param)?"": "?" + param);
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
/**
*用于请求订阅消息接口
*注:"connectionType", "application/json"
**/
public static String sendPost2(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", "*/*");
conn.setRequestProperty("connectionType", "application/json");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送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;
}
最后整理一下遇到的问题:
1、用户未同意订阅消息,则消息推送的错误code为43101,提示用户拒绝接受消息。
2、如果用户选择了不同意订阅消息,并勾选了总是保持以上操作的话,微信是不会再弹出授权订阅消息提示的,只能用户自己手动修改小程序设置里的授权管理。可以通过wx.getString()获取用户点击了总是操作的模板订阅情况。
3、网上有网友遇到过在推送消息时,data里面参数的值value存在空格的话会提示数据类型不满足规范,但是我没有遇到过,单纯记录一下。
4、获取access_token时官方返回接口为 {“access_token”:“ACCESS_TOKEN”,“expires_in”:7200},所以时间是2小时,官方也不建议频繁的去获取,所以我才用redis存放,设置一小时的生命周期。
5、用户每同意一次授权,则同意的模板将会获得一次推送消息的机会,暂时不确定有没有时间限制,次数可以累加;但是如果用户再授权设置中选择拒收此类消息的话推送机会会清零,即使用户重新选择接受。
6、个人建议appid,secret,模板id,发送版本等最好写在配置文件里,然后传给接口,毕竟小程序从开发版-体验版-正式版要经历好几个过程,很多东西需要重新搞。