微信小程序内判断是否关注公众号(JAVA)
微信小程序内判断是否关注公众号(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)的更多相关文章
- 微信H5页面内实现一键关注公众号
H5页面内实现关注公众号的微信JSSDK没有相关接口开放,因此就得动点脑筋来实现该功能了.下面的方法就是通过一种非常蹊跷的方式实现的. 首先,需要在公众号内发表一篇原创文章,注意是原创文章,然后由另一 ...
- 原创:微信小程序亲测体验,公众号入口曝光!
扫描即可体验知乐微信小程序,并且看到入口 你可以在这里看到相应的小程序:微信小程序商店 发现内有历史列表入口 真实小程序 搜索 操作栏 放置到桌面示意图必须搜索全称,才可以搜索到小程序 推荐给朋友,可 ...
- 微信小程序内链微信公众号的方法
最近接了一个需求,要求在微信小程序内部添加关注微信公众号的方式并给出解决方案,经过几天的翻官网文档,查周边资料,问资深技术员,初步给出两个解决方案: 题外话: 搬砖容易,建设难,搬砖的小伙伴请注明文章 ...
- 微信小程序内嵌业务域名内的网页
微信小程序在2017年11月左右开放了内嵌网页的功能,即新组件<web-view>.官方文档链接:https://mp.weixin.qq.com/debug/wxadoc/dev/com ...
- 微信小程序内嵌网页能力开放 小程序支持内嵌网页文档说明
为了方便开发者灵活配置微信小程序,张小龙现在开放了小程序的内嵌网页功能,这是一个非常大的惊喜啊,以后意味着你只要开放一个手机端网站,就可以制作一个小程序了哦.操作方法1.开发者登录微信小程序后台,选择 ...
- 微信小程序内训笔记
2016年9月22日凌晨微信官方正式宣布“小程序”开始内测,有“微信之父”之称.腾讯集团高级执行副总裁张小龙在2016年末对外宣布“小程序“应用将于2017年1月9日正式推出 这一次微信还是按照惯例, ...
- [小程序开发] 微信小程序内嵌网页web-view开发教程
为了便于开发者灵活配置小程序,微信小程序开放了内嵌网页能力.这意味着小程序的内容不再局限于pages和large,我们可以借助内嵌网页丰富小程序的内容.下面附上详细的开发教程(含视频操作以及注意事项) ...
- 微信小程序内嵌网页的一些(最佳)实践
前言 3 个月前,微信小程序推出了 web-view 组件引发了一波小高潮,笔者所在的大前端团队写过一篇浅析,详情可见:浅谈微信小程序前端生态. 我们曾大胆猜想,这一功能,可能直接导致小程序数量增长迎 ...
- 微信小程序内联h5页面,实现分享
在小程序内直联h5的页面(pages/webview/webview.js),该页面为<web-view>的容器,使用<web-view>组件 <web-view wx: ...
随机推荐
- win命令
netstat -nao | findstr "8888"taskkill /pid 15064 /f清理端口被占用win+r进入cmdcmd窗口中输入notepad进入记事本sh ...
- 在EXCEL中,判断同列数据重复,并标识出来
推荐方法:建立辅助列,查找B列数据是否重复.=IF(COUNTIF(B:B,B1)>1,"重复","")按住公式单元格右下角实心十字,向下拖拽复制公式.= ...
- 用C++实现的Eratosthenes筛法程序
运行示例 只输出素数总数的运行示例 PS H:\Read\num\x64\Release> .\esieve.exe Eratosthenes sieve: a method to find o ...
- AbpVnext使用分布式IDistributedCache Redis缓存(自定义扩展方法)
AbpVnext使用分布式IDistributedCache缓存from Redis(带自定义扩展方法) 我的依赖包的主要版本以及Redis依赖如下 1:添加依赖 <PackageReferen ...
- Linux常用命令(二)之权限管理、文件搜索、帮助、压缩命令及管道
在(一)中提到过rwx的含义,但是我们还需深入理解,明白其真正的含义和权限,对于文件和目录,rwx权限是不同的,尤其是目录的权限往往是被忽略的: 对于目录,其权限和对应的操作: r-ls w-touc ...
- 存储系统管理(二)——Linux系统的swap分区、磁盘加密、磁盘阵列
磁盘驱动器上的空间 , 用作当前未使用部分内存的溢出.这样 , 系统就能在主内存中留出空间用于储存当前正在处理的数据 , 并在系统面临主内存空间不足的风险时提供应急溢出. swap分区的建立: fdi ...
- Shell中的运算
1.运算方式及运算符号 2.SHELL 中常用的运算命令 3.相关操作演示 1.用脚本写一个10秒倒计时 脚本的执行: 2.编写脚本,1分10秒的倒计时 执行脚本: 3.编写脚本,制作一个计算器 脚本 ...
- MongoDB(3)- Database 数据库相关
Database MongoDB 将数据记录存储为文档(特别是 BSON 文档) 这些文档在集合中聚集在一起 数据库存储一个或多个文档集合. 在 MongoDB 里面存在数据库的概念,但没有模式 Sh ...
- Docker(36)- docker run 的流程和原理
背景 目前项目组上, Docker 用的非常重,所有微服务都是通过 docker 来部署的 所以不能仅仅会命令,还得会一些原理的东西,特此补一篇基础点的,后面再更加深入一些 docker 原理 本篇学 ...
- C语言中volatile、register、const、static、extern、 auto关键字的作用
一.volatile详解 volatile的本意是"易变的" 因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据.当要求使用volat ...