目前有一个已微信认证的订阅号类型公众号,一个微信认证小程序,小程序和公众号互相关联。尚不清楚是否必须微信认证或特定类型,因为目前没遇到类型不匹配或相关的问题,发送微信小程序一次性订阅消息的相关限制较少
1、功能介绍
2、消息类型
一次性订阅消息(本文实现的消息类型)
一次性订阅消息用于解决用户使用小程序后,后续服务环节的通知问题。用户订阅后,开发者可不限时间地下发一条对应的服务消息;每条消息可单独订阅或退订。
长期订阅消息
一次性订阅消息可满足小程序的大部分服务场景需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态来多次发送消息提醒。为便于服务,我们提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。
目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务。
设备订阅消息
设备订阅消息是一种特殊类型的订阅消息,它属于长期订阅消息类型,且需要完成「设备接入」才能使用。
设备订阅消息用于在设备触发某些需要人工介入的事件时(例如设备发生故障、设备耗材不足等),向用户发送消息通知。详见设备订阅消息文档。
详细介绍还是自己看官方文档吧:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/subscribe-message.html
1、订阅消息模板选用
登录小程序微信公众平台,点击功能——订阅消息
点击公共模板库,搜索选用需要的模板
只能选用5个关键词,输入场景说明后点击提交
如果没有自己想要的关键词,点击页面上的点击申请
去申请自己需要的关键词。注意,每个月有5次申请机会,申请的关键词是你要新增的关键词,原本模板中有的就不要再重复申请了,申请通过后点选用模板可以选用原本有的关键词和你申请通过的关键词
2、对接拉起小程序登录弹窗,解析获取用户openId,将openId保存到数据库,关联到用户信息或用户表加个字段
官方文档:https://uniapp.dcloud.net.cn/api/plugins/login.html
uni.login({
success: (result) => {
//保存获取到的code
this.jsCode = result.code;
request({
url: '后台接口地址',
method: "POST",
data: { code: result.code },
})
.then((res) => {
this.openid = res.data.openid;
this.session_key = res.data.session_key;
})
.catch((err) => {});
},
fail: (error) => {},
});
官方文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html
// 对传入code进行解密获取openid
LinkedMultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("appid",appid);
params.add("secret",secret);
// 小程序调接口传的code
params.add("js_code",code);
params.add("grant_type","authorization_code");
String url = "https://api.weixin.qq.com/sns/jscode2session?appid="+appid+"&secret="+secret+"&js_code="+code+"&grant_type=authorization_code";
String result = restTemplate.getForObject(url, String.class);
JSONObject jsonObject = JSON.parseObject(result);
Map<String, String> map = new HashMap<>();
try {
map.put("openid", jsonObject.get("openid").toString());
map.put("session_key", jsonObject.get("session_key").toString());
log.info("code解密成功");
} catch (Exception e){
log.error("code解析失败,"+jsonObject.getString("errmsg"));
throw new RuntimeException("code解析失败,"+jsonObject.getString("errmsg"));
}
return map;
3、引导用户主动授权订阅消息发送
appletAuthorize.js
文件官方文档:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/subscribe-message/wx.requestSubscribeMessage.html
import Vue from 'vue'
const templateIds = [
// 审批结果通知
'审批结果通知模板id',
// 领用单领用通知
'领用单领用通知模板id',
// 待审核通知
'待审核通知模板id',
// 数据报表生成通知
'数据报表生成通知模板id',
// 工单完成提醒
'工单完成提醒模板id'
]
const appletAuthorize = () => {
let tmplIds = []
uni.getStorage({
key: "currAuthorizeStep",
success: ({
data
}) => {
console.log("currAuthorizeStep", data);
let temp = data
for (var i = 0; i < 3; i++) {
if (temp === templateIds.length) {
temp = 0
}
tmplIds.push(templateIds[temp])
temp++
}
uni.setStorage({
key: "currAuthorizeStep",
data: temp,
success: (result) => {},
fail: (error) => {},
});
if (tmplIds.length === 0) {
return
}
wx.requestSubscribeMessage({
tmplIds: tmplIds,
success(res) {
console.log("订阅消息唤起成功 =====>", res)
},
fail(err) {
console.log("订阅消息唤起失败 =====>", err)
}
})
},
fail: (error) => {},
});
};
Vue.prototype.$appletAuthorize = appletAuthorize
export default appletAuthorize;
注意:
(1)在登录时候setStorage
uni.setStorage({
key: "currAuthorizeStep",
data: 0,
success: (result) => {},
fail: (error) => {},
});
(2)用templateIds把模板id都定义出来,每次取三个授权,是因为wx.requestSubscribeMessage订阅的消息模板的id每次最多只能传3个,不然会订阅失败
this.$appletAuthorize()
4、订阅消息模板有了,用户openId有了,用户订阅消息授权有了,接下来是发送订阅消息了
官方文档:
(1)https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-message-management/subscribe-message/sendMessage.html
(2)https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
参数拼接工具类
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* 发送订阅消息参数整合
*
* @author Administrator
*/
@Component
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Slf4j
public class AppletParamUtil {
/**
* 待审核通知参数
* @param thing1Value
* @param thing11Value
* @param thing12Value
* @param time4Value
* @param thing10Value
* @return
*/
public static Map<String, Object> getNeedConfirmParam (String thing1Value,
String thing11Value,
String thing12Value,
String time4Value,
String thing10Value) {
Map<String, Object> objMap = new HashMap<>(5);
Map<String, String> map = new HashMap<>(1);
// 申请人
map.put("value", thing1Value);
objMap.put("thing1", map);
// 申请人部门
map = new HashMap<>(1);
map.put("value", thing11Value);
objMap.put("thing11", map);
// 申请人工种
map = new HashMap<>(1);
map.put("value", thing12Value);
objMap.put("thing12", map);
// 申请时间
map = new HashMap<>(1);
map.put("value", time4Value);
objMap.put("time4", map);
// 申请物品
map = new HashMap<>(1);
map.put("value", thing10Value);
objMap.put("thing10", map);
return objMap;
}
/**
* 数据报表生成通知
* @param thing1Value
* @param thing2Value
* @return
*/
public static Map<String, Object> getDataReportParam (String thing1Value, String thing2Value) {
Map<String, Object> objMap = new HashMap<>(2);
Map<String, String> map = new HashMap<>(1);
// 报表名称
map.put("value", thing1Value);
objMap.put("thing1", map);
// 数据统计周期
map = new HashMap<>(1);
map.put("value", thing2Value);
objMap.put("thing2", map);
return objMap;
}
}
订阅消息发送工具类
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.druid.support.json.JSONUtils;
import com.ruoyi.RemoteSysConfigService;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.system.api.RemoteUserService;
import com.ruoyi.system.api.domain.SysUser;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 发送小程序信息
*
* @author Administrator
*/
@Component
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Slf4j
public class AppletMsgSendUtil {
private final RestTemplate restTemplate;
private final StringRedisTemplate redisTemplate;
/**
* 发送小程序订阅信息
* @param templateId 所需下发的订阅模板id
* @param page 点击模板卡片后的跳转页面
* @param data 模板内容
* @return
*/
public void msgSend(String templateId, String page, Map<String, Object> data) {
try {
//region 获取access_token
String accessToken = getAppletToken();
//endregion
//region 推送小程序信息
String msgUrl = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + accessToken;
// 参数处理
Map<String, Object> params = new HashMap<>(6);
params.put("touser", openId);
params.put("template_id", templateId);
params.put("page", page);
params.put("lang", "zh_CN");
params.put("miniprogram_state", "formal");
params.put("data", data);
String jsonData = JSONUtils.toJSONString(params);
HttpEntity<String> request = new HttpEntity<>(jsonData);
String msgResult = restTemplate.postForObject(msgUrl, request, String.class);
JSONObject msgResultObject = JSONUtil.parseObj(msgResult);
if (!"0".equals(msgResultObject.get("errcode").toString())) {
throw new RuntimeException("发送订阅消息失败," + msgResultObject.get("errmsg"));
}
//endregion
} catch (Exception e) {
throw new RuntimeException("发送订阅消息失败," + e);
}
//endregion
}
/**
* 获取小程序token
* @return
*/
public String getAppletToken () {
// 先从缓存查看有没有
String appletToken = redisTemplate.opsForValue().get("AppletToken");
if (!StringUtil.isBlank(appletToken)) {
return appletToken;
}
//设置查询参数与请求url
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
queryParams.add("appid", appid);
queryParams.add("secret", secret);
String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
UriComponentsBuilder tokenBuilder = UriComponentsBuilder.fromHttpUrl(tokenUrl).queryParams(queryParams);
//获取token
String tokenResult = restTemplate.getForObject(tokenBuilder.toUriString(), String.class);
JSONObject tokenObject = JSONUtil.parseObj(tokenResult);
appletToken = tokenObject.getStr("access_token");
if (StringUtil.isBlank(appletToken)) {
throw new RuntimeException("小程序token获取失败," + tokenObject.getStr("errmsg"));
}
//将token存到redis,有效期100分钟。官方接口返回的token有效期120分钟
redisTemplate.opsForValue().set("AppletToken", appletToken);
redisTemplate.expire("AppletToken", 100, TimeUnit.MINUTES);
return appletToken;
}
}
用法
// 发送消息通知领取人已领取物品
Map<String, Object> objMap = AppletParamUtil.getDataReportParam(thing1Value,thing2Value);
msgSendUtil.msgSend(模板id, 需要跳转的小程序页面, objMap);
注意:参数拼接工具类里面方法的各个参数是根据模板而定的,模板有多少个参数,工具类方法就有多少个参数
另外,附上apipost的订阅消息发送调用方式。参数含义看上面贴出的官方文档
url:https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=你的token
method:POST
body:
{ “touser”: “”,
“template_id”: “”,
“page”: “”,
“lang”:“zh_CN”,
“miniprogram_state”: “formal”,
“data”: {} }
有什么不懂的去微信开放社区问,去发帖。https://developers.weixin.qq.com/community/develop/mixflow
参考:
(1)https://developers.weixin.qq.com/community/develop/doc/0008aa1fd40ec80b4710e9b2260000
(2)https://developers.weixin.qq.com/community/develop/doc/000ce8a9298950338310bc75966800