微信小程序内判断是否关注公众号(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. vue3.0入门(一)

    前言 最近在b站上学习了飞哥的vue教程 学习案例已上传,下载地址 使用方式 使用在线cdn 下载js文件并自托管,引入到项目后使用 使用npm安装后,用cli来构建项目 声明式渲染 Vue2需引入v ...

  2. JS方式实现隐藏手机号码中间4位数

    1.截取 function test (value) { const start = value.slice(0, 3) const end = value.slice(-4) return `${s ...

  3. 《手把手教你》系列技巧篇(二十三)-java+ selenium自动化测试-webdriver处理浏览器多窗口切换下卷(详细教程)

    1.简介 上一篇讲解和分享了如何获取浏览器窗口的句柄,那么今天这一篇就是讲解获取后我们要做什么,就是利用获取的句柄进行浏览器窗口的切换来分别定位不同页面中的元素进行操作. 2.为什么要切换窗口? Se ...

  4. MySQL之连接查询和子查询

    多表连接的基本语法 多表连接,就是将几张表拼接为一张表,然后进行查询 select 字段1, 字段2, ... from 表1 {inner|lift|right} join 表2 on 连接条件; ...

  5. uniapp 设置背景图片

    uniapp 由于其特殊机制,导致了背景图片不能引用本地图片.只能通过 转成 base64 来进行设置 附上链接:https://oktools.net/image2base64 图片转成base64 ...

  6. 手把手教你在 SuperEdge 上用 EdgeX Foundry 接入 IoT 设备

    作者 连泓乔,华南理工计算机科学与技术大三在读,主要研究容器领域,Kubernetes.容器等云原生技术爱好者,SuperEdge 优秀贡献者. 王冬,腾讯云研发工程师,专注于 Kubernetes. ...

  7. Spring事物入门简介及AOP陷阱分析

    转载请注明出处: https://www.cnblogs.com/qnlcy/p/15237377.html 一.事务的定义 事务(Transaction),是指访问并可能更新数据库中各种数据项的一个 ...

  8. Python__bs4模块

    1 - 导入模块 from bs4 import BeautifulSoup 2 - 创建对象 fp = open('./test.html','r',encoding='utf-8') soup = ...

  9. springmvc配置过程中遇到的一些问题总结

    springmvc配置过程中遇到的一些问题总结 1.配置tomcat过程中的错误: 2.配置web.xml中DispatchServlet报红(配置好已有依赖条件下) 解决的办法: 因为新添加依赖,m ...

  10. Excel删除重复数据及用公式筛选重复项并标记颜色突出显示

    当表格记录比较多时,常常会有重复数据,而重复记录往往只希望保存一条,因此需要把多余的删除:在 Excel 中,删除重复数据有两种方法,一种是用"删除重复数据"删除,另一种是用&qu ...