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

时间:2021-09-13 19:38:56

微信小程序内判断是否关注公众号(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/

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

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

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

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;
}
}