最近在开发小程序中遇到一个需求:小程序的用户关注指定公众号,则小程序里需要通知用户的提醒消息可以做在公众号中。
首先我先把实现的整套流程先述说一遍,免得码友们走了弯路。
(我写的这个流程是非常正确的。但是代码是不能直接用的只能进行参照。但帖上去的代码都是正确的。我是以自己的知识并且参照该链接https://www.jianshu.com/p/9fc64dd53831实现出来的。如果看不懂我写的可以观看该链接比较容易理解。本人文笔不太好,非喜勿喷。)
准备工作
1.首先要创建一个微信开发平台 ,进行开发者资质认证(费用:300rmb)把小程序和公众号绑定在该微信开发平台下并且小程序和公众号要进行微信认证(300rmb+300rmb)。
注:这一步是一定要做的,为什么要做?我来为大家解说一下。像我们开发小程序或者公众号的都知道,每个用户在不同的程序或者公众号中都会有一个唯一标识openid和unionid他们的值随着不同的程序而改变。既然我们要把小程序和公众号关联起来让公众号锁定小程序中的用户并对他们进行消息推送。那么小程序和公众号他们的用户信息必定会有一个共同的值来确定用户的唯一。那就是用户的unionid。在微信开发平台下面绑定了小程序和公众号那么他们的unionid就会一致,通过unionid就可以实现小程序与用户端的用户共享了。
2.用小程序的appid和appsecret获取小程序的access_token获取code来进行微信授权获取到微信信息(储存到数据库)。但是因为用户微信的appid和unionid属于保密的信息所以还要需要一个解压接口来得到。下面我会贴上代码。
3.用公众号的appid和appsecret获取公众号的access_token来获取已关注的用户openid,再根据用户的openid获取用户的所有信息包括unionid(存到数据库)
4.接下来就是通过小程序用户的unionid去找到对应的公众号的unionid获取公众号下的openid,通过公众号api的模板消息推送接口就可实现该功能了。
代码实现(因本人目前只专注于后端开发,以下都是后端代码。也是后端开发需要实现该功能所做的事情)
第一步:创建微信开发平台并绑定小程序和公众号(这一步操作特别简单就不详细说了)
第二步:通过前端页面穿入的code进行授权获取用户信息
/**
* Created by zhouf on 2018/6/14.
* 通过微信code获得微信用户的openid
*/
public class GetWxOpenidByWxCode {
@Override
public void mainProcess(Context cx) throws Exception {
String wx_appid = "小程序的appid";
String wx_secret = "小程序的secret";
String wx_code = "前端传过来的code";
//通过三个以上三个参数发送请求
String url = "https://api.weixin.qq.com/sns/jscode2session appid="+wx_appid+"&secret="+app_secret+"&js_code="+wx_code+"&grant_type=authorization_code";
//通过封装好的get请求获取到用户的sessionKey和用户在小程序的openid这样
Map result = HttpUtils.doGetHttps(url);
LinkedHashMap linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("session_key",result.get("session_key"));
linkedHashMap.put("openid",result.get("openid"));
}
}
/**
* Created by zf on 2018/12/11.
* 通过解密获取微信小程序的unionid api开放数据校验与解密
*/
public class GetWxAppletInfoByDecode {
public void mainProcess(Context cx) throws Exception {
String openid = "";
String session_key = "";
String encryptedData = "";
String iv = "";
String return_str = decryptionUserInfo(encryptedData,session_key,iv);
int i = return_str.indexOf("unionId");
int l = return_str.indexOf("watermark");
System.out.println(i+"-------"+l);
System.out.println(return_str.substring(i, l));
String unionid = return_str.substring(i + 10, l - 3);
}
/**
* 小程序解密用户数据
*
* @param encryptedData
* @param sessionKey
* @param iv
* @return
*/
public static String decryptionUserInfo(String encryptedData, String sessionKey, String iv) {
String result = "";
// 被加密的数据
byte[] dataByte = Base64.decode(encryptedData);
// 加密秘钥
byte[] keyByte = Base64.decode(sessionKey);
// 偏移量
byte[] ivByte = Base64.decode(iv);
try {
// 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
int base = 16;
if (keyByte.length % base != 0) {
int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
byte[] temp = new byte[groups * base];
fill(temp, (byte) 0);
System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
keyByte = temp;
}
// 初始化
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
parameters.init(new IvParameterSpec(ivByte));
cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
byte[] resultByte = cipher.doFinal(dataByte);
if (null != resultByte && resultByte.length > 0) {
result = new String(resultByte, "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
第三步:获取公众号的用户信息
/**
* 获取已关注服务号的所有用户信息
* bug:这里只能取到一万个用户(解决办法:微信公众号官网文档)
* Created by zf on 2018/12/13.
*/
public class GetUserInfoByGZH
{
private static String send_url = "https://api.weixin.qq.com/cgi-bin/user/get?access_token=";
static String access_token = "";
public static void main(String[] str) throws Exception{
List
第四步:通过公众号api的模板消息推送接口就可实现该功能了。
//开始发送模板消息
public static Map sendAppletTemplateMessage(Map request_param) throws Exception {
//获取小程序的access_token并把它当做参数拼接到模板消息发送接口当中。(access_token一般都是通过get方式拼接传入微信接口)
Map access_token_map = GetWxAccessToken2();
String access_token = (String) access_token_map.get("access_token");
String url = send_url+access_token;
//通过工具类把请求接口和参数进行请求并返回调用结果
Map result = HttpUtils.doPostHttps(url, request_param); //{"errcode":0,"errmsg":"ok","msgid":68036453858394114}
return result;
}
//用户报名活动成功将活动基本信息告知给用户
public static Map joinActivitySuccessMessage(String openid,int activity_id) throws Exception{
Context cx = new Context(null,null,null,null);
ActivityDao activityDao = new ActivityDao(cx);
Map avtMap = activityDao.get(activity_id);
//请求数据
Map request_data = new HashMap<>();
//消息模板id
String template_id = Constants.joinActivitySuccess_Template_id;
//公众号模板消息所要跳转的url
String url = "https://studentgt.xmcedu.cn";
//用户的openid
String user_openid = openid;
String activity_introduce = (String)avtMap.get("activity_introduce");
//消息模板参数
Timestamp ts = (Timestamp)avtMap.get("activity_begin_time");
String tsStr = "";
DateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
try {
tsStr = sdf.format(ts);
} catch (Exception e) {
e.printStackTrace();
}
String first = "你好,你已报名成功";
String keyword1 = (String)avtMap.get("activity_name"); //活动主题
String keyword2 = tsStr; //活动时间
String keyword3 = activity_introduce.substring(0,10)+"......."; //活动地点
String remark = "点击此消息,跳转链接观看详情。";
request_data.put("touser", user_openid);
request_data.put("template_id",template_id);
request_data.put("url",url);
//data参数
Map data = new HashMap<>();
templateParam(data,"first",first,"#173177");
templateParam(data, "keyword1", keyword1, "#173177");
templateParam(data, "keyword2", keyword2, "#173177");
templateParam(data, "keyword3", keyword3, "#173177");
templateParam(data, "remark", remark, "#173177");
//miniprogram参数:需要点击通知跳转至小程序才需要该参数,该参数有时点击通知则优先跳转到小程序,该参数不存在是则默认跳转到mp_template_msg参数中的url中。
Map miniprogram = new HashMap<>();
//这里的appid是需要跳转的小程序的appid
miniprogram.put("appid", Constants.wx_appid);
miniprogram.put("pagepath","pages/home/active/active");
request_data.put("miniprogram",miniprogram);
request_data.put("data",data);
//返回结果信息
Map send_return = sendAppletTemplateMessage(request_data);
System.out.println(""+send_return.toString());
return request_data;
}
//新作业通知
public static Map newJobMessageInform(String openid,int notice_id) throws Exception{
Context cx = new Context(null,null,null,null);
NoticeManagerDao noticeManagerDao = new NoticeManagerDao(cx);
Map noticeMap = noticeManagerDao.get(notice_id);
//请求数据
Map request_data = new HashMap<>();
//消息模板id
String template_id = Constants.new_job_Template_id;
//公众号模板消息所要跳转的url
String url = "https://studentgt.xmcedu.cn";
//用户的openid
String user_openid = openid;
String content = noticeMap.get("content")+"...........";
User userDao = new User(cx);
Map cMap = userDao.get((int)noticeMap.get("promulgator_id"));
String user_duty = (String)cMap.get("user_duty");
//消息模板参数
Timestamp ts = (Timestamp)noticeMap.get("up_date");
String tsStr = "";
DateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
try {
tsStr = sdf.format(ts);
} catch (Exception e) {
e.printStackTrace();
}
String first = "您好,您有新的作业指令下达";
String keyword1 = (String)cMap.get("user_duty"); //科目
String keyword2 = tsStr; //日期
String keyword3 = content.substring(0,10)+"......."; //内容
String remark = "您点击此消息,跳转链接观看详情。";
request_data.put("touser", user_openid);
request_data.put("template_id",template_id);
request_data.put("url",url);
//data参数
Map data = new HashMap<>();
templateParam(data,"first",first,"#173177");
templateParam(data, "keyword1", keyword1, "#173177");
templateParam(data, "keyword2", keyword2, "#173177");
templateParam(data, "keyword3", keyword3, "#173177");
templateParam(data, "remark", remark, "#173177");
//miniprogram参数:需要点击通知跳转至小程序才需要该参数,该参数有时点击通知则优先跳转到小程序,该参数不存在是则默认跳转到mp_template_msg参数中的url中。
Map miniprogram = new HashMap<>();
//这里的appid是需要跳转的小程序的appid
miniprogram.put("appid", Constants.wx_appid);
miniprogram.put("pagepath","pages/home/homeWork/homeWork");
request_data.put("miniprogram",miniprogram);
request_data.put("data",data);
//返回结果信息
Map send_return = sendAppletTemplateMessage(request_data);
System.out.println(""+send_return.toString());
return request_data;
}
public static Map templateParam(Map params, String name, String value, String color){
Map param = new HashMap<>();
param.put("value",value);
param.put("color",color);
params.put(name, param);
return params;
}
/**
* 获取access_token
* @return
* @throws Exception
*/
public static Map GetWxAccessToken2() throws Exception {
String access_token="";
XmcCacheUtil2 xmcCacheUtil = XmcCacheUtil2.getInstance();
Object access_token_obj = xmcCacheUtil.get("WxDataCache", "access_token_obj");
Map obj = new HashMap<>();
if(access_token_obj!=null){
obj = (Map) access_token_obj;
Long expires_time = Long.valueOf(obj.get("expires_time").toString());
Long now_time = System.currentTimeMillis();
if(expires_time-now_time>300){
access_token = obj.get("access_token").toString();
}
}
//查询缓存
if(access_token.equals("")){
String url = "https://api.weixin.qq.com/cgi-bin/token";
String grant_type="client_credential";
Map map = new HashMap<>();
map.put("grant_type",grant_type);
map.put("appid", Constants.wx_appid_gzh);
map.put("secret", Constants.wx_secret_gzh);
//Map rs = HttpUtils.doGet(url, map, "utf-8");
url = url+"?appid="+Constants.wx_appid_gzh+"&secret="+Constants.wx_secret_gzh+"&grant_type="+grant_type;
Map rs = HttpUtils.doGetHttps(url);
access_token = rs.get("access_token").toString();
Long expires_time = System.currentTimeMillis()+Integer.valueOf(rs.get("expires_in").toString())*1000;
rs.put("expires_time",expires_time);
access_token_obj = rs;
xmcCacheUtil.put("WxDataCache", "access_token_obj", access_token_obj);
obj = rs;
}
return obj;
}