微信小程序内判断是否关注公众号(JAVA)

思路来源(第二种):
https://blog.csdn.net/Yanheeee/article/details/117295643
/**
* 总体思路:
* 1.微信公众号和小程序都绑定到微信开放平台, 所以会有一个共同的unionid, 每次用户登录都会返回一个unionid
* 2.获取所有公众号已关注用户的信息 : 通过微信接口获取到一个已关注的用户列表(公众号的openid和unionid), 保存到数据库内(保存openid, unionid)
* 3.通过监听关注/取关事件(用户关注时, 微信会给我们发送一条消息[xml]), 来更新表的内容(关注->增加, 取消关注->删除)
* 4.登录小程序后, 通过用户登录的unionid查表判断是否已关注
*/

1. 微信开放平台配置步骤

https://open.weixin.qq.com/

注意!!!!!!-----------------
配置好启用后, 微信公众号的自动回复, 关键词回复, 底部菜单栏会失效!!!

1. 效验代码
//如果有SpringSecurity, 需要开启匿名访问

	/**
* get用于微信校验, post用来接收微信信息(后面接收微信消息时配置)
* 微信校验
*/
@GetMapping("/weixinVerify")
public void weixinVerify(WeixinCheckVo weixinCheckVo, HttpServletResponse response) throws IOException {
//通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
if (CheckUtil.checkSignature(weixinCheckVo.getSignature(), weixinCheckVo.getTimestamp(), weixinCheckVo.getNonce())) {
response.getWriter().print(weixinCheckVo.getEchostr());
}
}
WeixinCheckVo
@Data
public class WeixinCheckVo { /**
* 微信加密签名
*/
private String signature; /**
* 时间戳
*/
private String timestamp; /**
* 随机数
*/
private String nonce; /**
* 随机字符串
*/
private String echostr;
}
CheckUtil
public class CheckUtil {

    /**
* weixin绑定token信息
*/
private static String token = "MyToken"; /**
* 验证签名
*
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
if(StringUtils.isBlank(signature) || StringUtils.isBlank(timestamp) || StringUtils.isBlank(nonce)){
return false;
}
String[] arr = new String[] { token, timestamp, nonce };
sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
String tmpStr = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] digest = md.digest(content.toString().getBytes());
tmpStr = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
} /**
* 将字节数组转换为十六进制字符串
*
* @param byteArray
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
} /**
* 将字节转换为十六进制字符串
*
* @param mByte
* @return
*/
private static String byteToHexStr(byte mByte) {
char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
} /**
* 字典排序
* @param array
*/
public static void sort(String[] array) {
for (int i = 0; i < array.length - 1; i++) {
for (int j = i + 1; j < array.length; j++) {
if (array[j].compareTo(array[i]) < 0) {
String temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
}

2. 登陆小程序判断用户是否关注

1. 需要的jar
	<!-- hutool工具集 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.9</version>
</dependency> <!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
2. 获取access_token
	/**
* 首先获access_token(access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需要使用access_token)
* 需要用微信公众号的appid和secret(小程序的和微信公众号的不一样!!!)
* 或者使用微信在线调试工具 -> https://mp.weixin.qq.com/debug (基础支持-获取access_token接口 /token)
*/
public static JSONObject getAccess_token() {
String url = "https://api.weixin.qq.com/cgi-bin/token";
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("appid", "微信公众号的appid");
paramMap.put("secret", "微信公众号的secret");
paramMap.put("grant_type", "client_credential");
String result = HttpRequest.get(url).charset(CharsetUtil.CHARSET_GBK)
.header(Header.USER_AGENT, "Hutool http")//头信息,多个头信息多次调用此方法即可
.form(paramMap)//表单内容
.timeout(20000)//超时,毫秒
.execute().body();
JSONObject jsonObject = JSONObject.parseObject(result); //字符串转Map
return jsonObject;
}
3. 获取已关注的用户openid和unionid, 并建立已关注用户表(自增id, unionid, openid)
	/**
* 获取所有已关注的用户openid(适用于 -> 关注人数小于1W人的公众号)
*/
public static JSONArray getAllUserInfo(String access_token) {
String url = "https://api.weixin.qq.com/cgi-bin/user/get";
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("access_token", access_token);
String result = HttpRequest.get(url).charset(CharsetUtil.CHARSET_GBK)
.header(Header.USER_AGENT, "Hutool http")//头信息,多个头信息多次调用此方法即可
.form(paramMap)//表单内容
.timeout(20000)//超时,毫秒
.execute().body();
JSONObject jsonObject = JSONObject.parseObject(result); //字符串转Map
JSONObject data = (JSONObject)jsonObject.get("data");
JSONArray json = (JSONArray)data.get("openid");
return json;
} /**
* 通过用户的openid(遍历JSONArray数组), 获取每一位已关注的用户unionid, 然后批量插入数据库
*/
public void insertUserOpenIdAndUinonId(JSONArray jsonArray) {
//key放unionid, value放openid, 批量插入数据
Map<String, String> mapList = new HashMap<>();
for (Object openid : jsonArray) {
String url = "https://api.weixin.qq.com/cgi-bin/user/info";
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("access_token", access_token);
paramMap.put("openid", openid.get(i));
String result = HttpRequest.get(url).charset(CharsetUtil.CHARSET_GBK)
.header(Header.USER_AGENT, "Hutool http")//头信息,多个头信息多次调用此方法即可
.form(paramMap)//表单内容
.timeout(20000)//超时,毫秒
.execute().body();
JSONObject jsonObject = JSONObject.parseObject(result); //字符串转Map
mapList.put(jsonObject.getString("unionid"), jsonObject.getString("openid"));
}
//批量插入数据
xxxMapper.batchInsert(mapList);
} void batchInsert(@Param("mapList") Map<String, String> mapList); <insert id="batchInsert" parameterType="java.util.Map">
insert into table (unionid, openid) values
<foreach collection="mapList" item="value" index="key" separator=",">
(#{key}, #{value})
</foreach>
</insert>
4. 进入后小程序判断用户是否关注
//小程序登陆
public static JSONObject getWxMini(String code, String appid, String appSecret) {
String url = "https://sz.api.weixin.qq.com/sns/jscode2session";
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("appid", "小程序的adppid");
paramMap.put("secret", "小程序的secret");
paramMap.put("js_code", code);
paramMap.put("grant_type", "authorization_code");
String result = HttpRequest.get(url).charset(CharsetUtil.CHARSET_GBK)
.header(Header.USER_AGENT, "Hutool http")//头信息,多个头信息多次调用此方法即可
.form(paramMap)//表单内容
.timeout(20000)//超时,毫秒
.execute().body();
JSONObject jsonObject = JSONObject.parseObject(result); //字符串转Map
return jsonObject;
} //判断用户是否关注了公众号
if (jsonObject.getString("unionid") != null) {
//判断数据库内是否有这个unionId, 如果有则已关注, 如果没有则没有关注
ajax.put("subscribe", userService.checkUnionId(json.getString("unionid")));
ajax.put("unionid", json.getString("unionid"));
} <select id="checkUnionId" parameterType="string" resultType="int">
select count(-1) from table
where unionid = #{unionid}
</select>

3. 微信监听关注/取关事件

1. 接收微信的事件
	/**
* 事件类型:subscribe(订阅)
*/
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe"; /**
* 事件类型:unsubscribe(取消订阅)
*/
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe"; /**
* 接收微信的事件
*/
@PostMapping(value = "/weixinVerify", produces = "application/xml")
public void weixinVerify(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType("text/html; charset=utf-8");
response.setContentType("application/xml; charset=utf-8");
Map<String, String> map = new HashMap<>();
String method = request.getMethod(); if ("POST".equals(method)) {
String xmlStr = "";
String msgrsp = "";
PrintWriter out = response.getWriter(); try {
//解析微信发来的XML
xmlStr = XmlUtil.inputStream2StringNew(request.getInputStream());
Map<String, String> requestMap = XmlUtil.parseXml(xmlStr);
//发送方帐号(公众号的open_id)
String openId = requestMap.get("fromUserName"); //关注(subscribe)
if (MessageUtil.EVENT_TYPE_SUBSCRIBE.equals(requestMap.get("event"))) {
//通过openid获取用户unionid并添加到已关注表中(weixin_unionid_openid)
JSONObject userInfo = GetReqUtil.getUserInfo(openId);
map.put("openId", openId);
map.put("unionId", userInfo.getString("unionid"));
//新增关注的用户信息
userMapper.insertSubscribeInfo(map);
} //取消关注(unsubscribe)
if (MessageUtil.EVENT_TYPE_UNSUBSCRIBE.equals(requestMap.get("event"))) {
//删除取关的用户信息
userMapper.deleteSubscribeInfo(openId);
}
out.print(msgrsp);
out.close();
} catch (Exception e) {
logger.error("解析失败:" + xmlStr);
e.printStackTrace();
}
}
} <insert id="insertSubscribeInfo" parameterType="map">
insert into table (openid, unionid) values (#{openId}, #{unionId})
</insert>
<delete id="deleteSubscribeInfo" parameterType="string">
delete from table where openid = #{openId}
</delete>
XmlUtil
public class XmlUtil {

    // 将输入流使用指定编码转化为字符串
public static String inputStream2StringNew(InputStream inputStream) throws Exception {
// 建立输入流读取类
InputStreamReader reader = new InputStreamReader(inputStream);
// 设定每次读取字符个数
char[] data = new char[512];
int dataSize = 0;
// 循环读取
StringBuilder stringBuilder = new StringBuilder();
while ((dataSize = reader.read(data)) != -1) {
stringBuilder.append(data, 0, dataSize);
}
return stringBuilder.toString();
} // 将 xml 文件解析为指定类型的实体对象。此方法只能解析简单的只有一层的xml
private static DocumentBuilderFactory documentBuilderFactory = null; //屏蔽某些编译时的警告信息(在强制类型转换的时候编译器会给出警告)
public static Map<String, String> parseXml(String Str) throws Exception {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<String, String>(); // 从request中取得输入流
// 读取输入流
SAXReader reader = new SAXReader();
org.dom4j.Document document = reader.read(new ByteArrayInputStream(Str.getBytes()));
// 得到xml根元素
org.dom4j.Element root = document.getRootElement();
// 得到根元素的所有子节点
List<org.dom4j.Element> elementList = root.elements(); // 遍历所有子节点
for (org.dom4j.Element e : elementList)
map.put(getName(e.getName()), e.getText());
return map;
} private static String getName(String name) {
if (StringUtils.isBlank(name)) {
return null;
}
String frist = String.valueOf(name.charAt(0)).toLowerCase();
return frist + name.substring(1, name.length());
}
}
GetReqUtil
public class GetReqUtil {

	/**
* 获取用户信息
* 1. 每次把获取的access_token放入redis, 每天调用限制2000次
* 2. 如果别处调用access_token, 导致access_token失效, 则重新获取
*/
public static JSONObject getUserInfo(String openId) {
//静态工具类之前注入出错, 所以使用getBean(往期的Tools里有SpringUtils)
RedisCache redisCache = SpringUtils.getBean(RedisCache.class);
Object key = redisCache.getCacheObject("weixin_accessToken");
String access_token; //默认access_token过期时间为2小时, redis中储存为110分钟(每天接口调用上线为2000次), 每次调用都会产生新的access_token
if (StringUtils.isEmpty(key)) {
JSONObject aToken = GetReqUtil.getAccess_token();
redisCache.setCacheObject("weixin_accessToken", aToken.getString("access_token"), 110, TimeUnit.MINUTES);
access_token = aToken.getString("access_token");
}else {
access_token = key + "";
} String url = "https://api.weixin.qq.com/cgi-bin/user/info";
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("access_token", access_token);
paramMap.put("openid", openId); String result = HttpRequest.get(url).charset(CharsetUtil.CHARSET_GBK)
.header(Header.USER_AGENT, "Hutool http")//头信息,多个头信息多次调用此方法即可
.form(paramMap)//表单内容
.timeout(20000)//超时,毫秒
.execute().body();
JSONObject jsonObject = JSONObject.parseObject(result); //字符串转Map //如果别处调用access_token, 导致access_token失效, 则重新获取
if ("40001".equals(jsonObject.getString("errcode"))) {
JSONObject aToken = GetReqUtil.getAccess_token();
redisCache.setCacheObject("weixin_accessToken", aToken.getString("access_token"), 110, TimeUnit.MINUTES); paramMap.put("access_token", aToken.getString("access_token"));
String result2 = HttpRequest.get(url).charset(CharsetUtil.CHARSET_GBK)
.header(Header.USER_AGENT, "Hutool http")//头信息,多个头信息多次调用此方法即可
.form(paramMap)//表单内容
.timeout(20000)//超时,毫秒
.execute().body();
return JSONObject.parseObject(result2); //字符串转Map
}
return jsonObject;
}
}

微信小程序内判断是否关注公众号(JAVA)的更多相关文章

  1. 微信H5页面内实现一键关注公众号

    H5页面内实现关注公众号的微信JSSDK没有相关接口开放,因此就得动点脑筋来实现该功能了.下面的方法就是通过一种非常蹊跷的方式实现的. 首先,需要在公众号内发表一篇原创文章,注意是原创文章,然后由另一 ...

  2. 原创:微信小程序亲测体验,公众号入口曝光!

    扫描即可体验知乐微信小程序,并且看到入口 你可以在这里看到相应的小程序:微信小程序商店 发现内有历史列表入口 真实小程序 搜索 操作栏 放置到桌面示意图必须搜索全称,才可以搜索到小程序 推荐给朋友,可 ...

  3. 微信小程序内链微信公众号的方法

    最近接了一个需求,要求在微信小程序内部添加关注微信公众号的方式并给出解决方案,经过几天的翻官网文档,查周边资料,问资深技术员,初步给出两个解决方案: 题外话: 搬砖容易,建设难,搬砖的小伙伴请注明文章 ...

  4. 微信小程序内嵌业务域名内的网页

    微信小程序在2017年11月左右开放了内嵌网页的功能,即新组件<web-view>.官方文档链接:https://mp.weixin.qq.com/debug/wxadoc/dev/com ...

  5. 微信小程序内嵌网页能力开放 小程序支持内嵌网页文档说明

    为了方便开发者灵活配置微信小程序,张小龙现在开放了小程序的内嵌网页功能,这是一个非常大的惊喜啊,以后意味着你只要开放一个手机端网站,就可以制作一个小程序了哦.操作方法1.开发者登录微信小程序后台,选择 ...

  6. 微信小程序内训笔记

    2016年9月22日凌晨微信官方正式宣布“小程序”开始内测,有“微信之父”之称.腾讯集团高级执行副总裁张小龙在2016年末对外宣布“小程序“应用将于2017年1月9日正式推出 这一次微信还是按照惯例, ...

  7. [小程序开发] 微信小程序内嵌网页web-view开发教程

    为了便于开发者灵活配置小程序,微信小程序开放了内嵌网页能力.这意味着小程序的内容不再局限于pages和large,我们可以借助内嵌网页丰富小程序的内容.下面附上详细的开发教程(含视频操作以及注意事项) ...

  8. 微信小程序内嵌网页的一些(最佳)实践

    前言 3 个月前,微信小程序推出了 web-view 组件引发了一波小高潮,笔者所在的大前端团队写过一篇浅析,详情可见:浅谈微信小程序前端生态. 我们曾大胆猜想,这一功能,可能直接导致小程序数量增长迎 ...

  9. 微信小程序内联h5页面,实现分享

    在小程序内直联h5的页面(pages/webview/webview.js),该页面为<web-view>的容器,使用<web-view>组件 <web-view wx: ...

随机推荐

  1. 编辑器扩展 --- 自动化处理之AssetPostprocessor资源导入

    AssetPostprocessor资源导入管线 AssetPostprocessor用于在资源导入时自动做一些设置,比如当导入大量图片时,自动设置图片的类型,大小等.AssetPostprocess ...

  2. easyexcel

    导出: package com.example.demo.excel.demo0; import com.alibaba.excel.annotation.ExcelProperty; import ...

  3. Golang gomail 发送邮件 --初使用

    gomail是一个第三方库,可以发送邮件 安装:go get -u github.com/go-gomail/gomail 使用示例: m := gomail.NewMessage() m.SetHe ...

  4. Linux 自旋锁,互斥量(互斥锁),读写锁

    自旋锁(Spin Lock) 自旋锁类似于互斥量,不过自旋锁不是通过休眠阻塞进程,而是在取得锁之前一直处于忙等待的阻塞状态.这个忙等的阻塞状态,也叫做自旋. 自旋锁通常作为底层原语实现其他类型的锁. ...

  5. vue 输入框禁止输入空格 ,只能输入数字,禁止输入数字

    正则表达式: @input="form.userName = form.userName.replace(/\s+/g,'')"    ( 禁止输入空格)    @input=&q ...

  6. php open_basedir绕过

    描述 php为了安全性考虑,有一项 open_basedir 的设置,它可将用户访问文件的活动范围限制在指定的区域.根据你web服务器环境,open_basedir可以在几个地方设置. 首先 在php ...

  7. Java实现一个死锁

    Java实现一个死锁 有一个藏宝图(treasureMapFragment)分成两份jack持有treasureMapFragment1残片1,json持有残片2.jack要求必须先看到json的残图 ...

  8. K8s 系列(四) - 浅谈 Informer

    1. 概述 进入 K8s 的世界,会发现有很多的 Controller,它们都是为了完成某类资源(如 pod 是通过 DeploymentController, ReplicaSetControlle ...

  9. 对easyui-validatebox的验证类型的扩展

    easyui为我们提供了validatebox类型的组件,使用它可以完成自动验证,十分方便.要注意的是,easyui中的各个组件都是有继承关系的.通过查看api,textbox继承validatebo ...

  10. html 随笔-水平控件不对齐的解决办法

    分别在左右两个控件的css代码中加上 vertical-align:top. 便可对齐:(推荐使用,因为这样可以避免脱标流). 来源: https://www.jianshu.com/p/f00d51 ...