之前写的小程序项目有一个向用户推送消息的功能,一开始打算使用小程序提供的模板消息
但是后来发现小程序发送模板消息有很大的局限性,小程序发送模板消息需要收集用户提交表单生成的formId,而这个formId只能使用一次,且有效时间为七天,也就是说你不能主动通过微信向用户发送消息,只有当用户主动触发表单事件,你拿到生成的fomId才能调用API发送消息,所以用起来很不方便。
所以改用小程序绑定公众号,通过公众号来发送模板消息,不过需要用到公众号与用户对应的openid,这个openid和你在小程序里获得的openid不一样,小程序得到的openid是用户与你的小程序对应的唯一id,所以调用公众号发送模板消息接口自然要使用用户对应公众号的openid。
那么怎么在小程序里获得用户对应公众号的openid呢?
这里需要在小程序里用web-view组件打开公众号授权网页点击查看公众号网页授权文档
授权有两种方式:静默授权和手动授权。区别请查看网页授权文档
授权后会携带参数到你设置的redirect_uri页面,我直接把redirect_uri填写成我的后台接口接收code,你也可以写个页面来接收,不过需要在小程序里配置业务域名
我的大致流程就是用户在小程序里完成登录后会直接打开公众号授权页面,因为是静默授权,所以用户只能看到空白页面,此后我后台的接口就可以接收到授权成功传给我的参数,但是在小程序里就无法跳回主界面了,我们使用了页面加载定时器,在授权页面打开后三秒跳回小程序主界面
`Page({
data: {
url: ‘https://open.weixin.qq.com/connect/oauth2/authorize?appid=公众号APPID&redirect_uri=后台接口或页面&response_type=code&scope=snsapi_base&state=123#wechat_redirect’
},
onLoad:function(){
console.log(‘我返回了’)
setTimeout(function () {
wx.reLaunch({
url: ‘…/info/info’,
})
}, 3000)
},
})`
这里需要在公众号配置授权回调页面域名,你的redirect_uri必须在此域名下
重点来了,你必须在微信开放平台创建账号并将你的小程序和服务号绑定在其下,这样你就可以获取unionid,unionid对应开放平台下的小程序和公众号是唯一的,也就是说一个微信用户在你的小程序中的onionid和在你公众号下的onionid是相同的,这样我们就可以在用户登录小程序时获得用户的openid和unionid,在完成授权后又可以调用接口获得openid2和unionid,通过unionid我们就可以将小程序用户和公众号用户关联起来,换句话说就是你可以通过查询unionid知道完成网页授权的用户是你的哪个小程序用户了,在之后的发送模板消息功能就可以获得你小程序用户的openid2(用户对应公众号的openid)来调接口了
获得小程序openid和unionid:
@SuppressWarnings("unchecked")//获取小程序用户的openid,unionid
private Map getSessionByCode(String code) {//code是从小程序调用wx.login拿到的code
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId1 + "&secret=" + appSecret1 + "&js_code=" + code + "&grant_type=client_credential";
// 发送请求
String data = HttpUtil.get(url);
ObjectMapper mapper = new ObjectMapper();
Map json = null;
try {
json = mapper.readValue(data, Map.class);
} catch (Exception e) {
e.printStackTrace();
}
// 形如{"session_key":"6w7Br3JsRQzBiGZwvlZAiA==","openid":"oQO565cXXXXXEvc4Q_YChUE8PqB60Y"}的字符串
return json;
}
拿到openid和unionid就存入你的用户表中
获得公众号openid2和unionid:
@RequestMapping(value="/getOpenid2")//获得公众号openid,此接口为授权回调接口redirect_uri
public @ResponseBody void getOpenid(HttpServletRequest request) throws Exception{
String code=request.getParameter("code");//公众号授权code
String openid2=getOpenidByCode(code).get("openid");//获得openid2
Map userinfo=WxTemplate.getUserinfo(openid2,appId2,appSecret2);//获取用户详细信息
String unionid=userinfo.get("unionid");//unionid
loginService.addOpenid2ByUnionid(openid2,unionid);//根据unionid将openid2存入用户表中
}
HttpUtil:
public class HttpUtil {
private static final String Charset = "utf-8";
/**
* 发送请求,如果失败,会返回null
* @param url
* @param map
* @return
*/
public static String post(String url, Map map) {
// 处理请求地址
try {
HttpClient client = HttpClientBuilder.create().build();
URI uri = new URI(url);
HttpPost post = new HttpPost(uri);
// 添加参数
List params = new ArrayList();
for (String str : map.keySet()) {
params.add(new BasicNameValuePair(str, map.get(str)));
}
post.setEntity(new UrlEncodedFormEntity(params, Charset));
// 执行请求
HttpResponse response = client.execute(post);
if (response.getStatusLine().getStatusCode() == 200) {
// 处理请求结果
StringBuffer buffer = new StringBuffer();
InputStream in = null;
try {
in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,Charset));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流
if (in != null)
try {
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return buffer.toString();
} else {
return null;
}
} catch (Exception e1) {
e1.printStackTrace();
}
return null;
}
/**
* 发送请求,如果失败会返回null
* @param url
* @param str
* @return
*/
public static String post(String url, String str) {
// 处理请求地址
try {
HttpClient client = HttpClientBuilder.create().build();
URI uri = new URI(url);
HttpPost post = new HttpPost(uri);
post.setEntity(new StringEntity(str, Charset));
// 执行请求
HttpResponse response = client.execute(post);
if (response.getStatusLine().getStatusCode() == 200) {
// 处理请求结果
StringBuffer buffer = new StringBuffer();
InputStream in = null;
try {
in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,"utf-8"));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
} finally {
// 关闭流
if (in != null)
in.close();
}
return buffer.toString();
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 发送GET方式的请求,并返回结果字符串。
*
* 时间:2017年2月27日,作者:http://wallimn.iteye.com
* @param url
* @return 如果失败,返回为null
*/
public static String get(String url) {
try {
HttpClient client = HttpClientBuilder.create().build();
URI uri = new URI(url);
HttpGet get = new HttpGet(uri);
HttpResponse response = client.execute(get);
if (response.getStatusLine().getStatusCode() == 200) {
StringBuffer buffer = new StringBuffer();
InputStream in = null;
try {
in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,Charset));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
} finally {
if (in != null)
in.close();
}
return buffer.toString();
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
WxTemplate:
public class WxTemplate {
@SuppressWarnings("unchecked")
public static Map getUserinfo(String openid2,String appId2,String appSecret2) {//获得用户关于公众号的详细信息
String access_token=getAccess_token(appId2,appSecret2);
String userInfo=HttpUtil.get("https://api.weixin.qq.com/cgi-bin/user/info?access_token="+access_token+"&openid="+openid2+"&lang=zh_CN");
ObjectMapper mapper2 = new ObjectMapper();
Map json2=null;
try {
json2 = mapper2.readValue(userInfo, Map.class);
} catch (Exception e) {
e.printStackTrace();
}
return json2;
}
@SuppressWarnings("unchecked")
public static void sendMsg(String openid2,String id,String type,String checkTime,String appId2,String appSecret2) {//发送模板消息
WxMssVo wx=new WxMssVo();//发送模板消息请求参数封装对象
wx.setTouser(openid2);
wx.setTemplate_id("1FePKYxjLfISyDLvuzDXjOmIk3QmG3EMbusfNQD76Lk");
Map miniprogram=new HashMap<>();
miniprogram.put("appid","你的小程序appid");
miniprogram.put("pagepath","pages/checked/checked?id="+id);
wx.setMiniprogram(miniprogram);
Map first=new HashMap<>();
Map keyword1=new HashMap<>();
Map keyword2=new HashMap<>();
Map remark=new HashMap<>();
first.put("value","您的"+type+"申请已通过审核");
first.put("color","#173177");
keyword1.put("value","申请通过");
keyword1.put("color","#173177");
keyword2.put("value",checkTime);
keyword2.put("color","#173177");
remark.put("value","点击查看详情");
remark.put("color","#173177");
Map> map= new HashMap<>();
map.put("first",first);
map.put("keyword1",keyword1);
map.put("keyword2",keyword2);
map.put("remark",remark);
wx.setData(map);
String jsonString = JSON.toJSONString(wx);
String access_token=getAccess_token(appId2,appSecret2);
String data= HttpUtil.post("https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="+access_token,jsonString);
System.out.println(data);
}
@SuppressWarnings("unchecked")
private static String getAccess_token(String appId2,String appSecret2) {//获得公众号access_token
String access_token=CacheManager.get("access_token");//从缓存中获取access_token
if(access_token==null){
access_token=HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appId2+"&secret="+appSecret2);
ObjectMapper mapper = new ObjectMapper();
Map json;
try {
json = mapper.readValue(access_token, Map.class);
access_token=json.get("access_token");
CacheManager.set("access_token",access_token,7200*1000);//将access_token存入缓存,设置过期时间为两个小时
} catch (Exception e) {
e.printStackTrace();
}
}
return access_token;
}
}
发送模板消息请求参数封装对象:
public class WxMssVo {
private String touser;//openid2
private String template_id;//模板id
private Map miniprogram;//小程序appid
private Map> data;//数据
public Map getMiniprogram() {
return miniprogram;
}
public void setMiniprogram(Map miniprogram) {
this.miniprogram = miniprogram;
}
public String getTouser() {
return touser;
}
public void setTouser(String touser) {
this.touser = touser;
}
public String getTemplate_id() {
return template_id;
}
public void setTemplate_id(String template_id) {
this.template_id = template_id;
}
public Map> getData() {
return data;
}
public void setData(Map> data) {
this.data = data;
}
}
缓存管理类:
public class CacheManager {
@SuppressWarnings("rawtypes")
private static Map cache = new ConcurrentHashMap();
/**
* 启动定时任务清理过期缓存,避免内存溢出
*/
static {
Timer t = new Timer();
t.schedule(new ClearTimerTask(cache), 0, 7000 * 1000);
}
/**
* 设置缓存,不过期
* @param key
* @param t
*/
public static void set(String key, T t) {
cache.put(key, new CacheData(t, 0));
}
/**
* 设置缓存,指定过期时间expire(单位毫秒)
* @param key
* @param t
* @param expire 过期时间
*/
public static void set(String key, T t, long expire) {
cache.put(key, new CacheData(t, expire));
}
/**
* 根据key获取指定缓存
* @param key
* @return
*/
@SuppressWarnings("unchecked")
public static T get(String key) {
CacheData data = cache.get(key);
if(null == data) {
return null;
}
if(data.isExpire()) {
remove(key);
return null;
}
return data.getData();
}
/**
* 移除指定key缓存
* @param key
*/
public static void remove(String key) {
cache.remove(key);
}
/**
* 移除所有缓存
*/
public static void removeAll() {
cache.clear();
}
private static class CacheData {
// 缓存数据
private T data;
// 过期时间(单位,毫秒)
private long expireTime;
public CacheData(T t, long expire) {
this.data = t;
if(expire <= 0) {
this.expireTime = 0L;
} else {
this.expireTime = Calendar.getInstance().getTimeInMillis() + expire;
}
}
/**
* 判断缓存数据是否过期
* @return true表示过期,false表示未过期
*/
public boolean isExpire() {
if(expireTime <= 0) {
return false;
}
if(expireTime > Calendar.getInstance().getTimeInMillis()) {
return false;
}
return true;
}
public T getData() {
return data;
}
}
/**
* 清理过期数据定时任务
*/
private static class ClearTimerTask extends TimerTask {
@SuppressWarnings("rawtypes")
Map cache;
@SuppressWarnings("rawtypes")
public ClearTimerTask(Map cache) {
this.cache = cache;
}
@Override
public void run() {
Set keys = cache.keySet();
for(String key : keys) {
CacheData> data = cache.get(key);
if(data.expireTime <= 0) {
continue;
}
if(data.expireTime > Calendar.getInstance().getTimeInMillis()) {
continue;
}
cache.remove(key);
System.out.println("remove"+key);
}
}
}
}
模板id需要去模板库中挑选或者自己申请新的模板,不过审核时间有点长
代码有不足之处请各位大佬指出