微信小程式內判斷是否關注公眾號(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/
注意!!!!!!-----------------
配置好啟用後, 微信公眾號的自動回覆, 關鍵詞回覆, 底部選單欄會失效!!!
微信公眾號的自動回覆-關鍵詞回覆-底部選單欄配置程式碼:
https://www.cnblogs.com/Annago/p/15411110.html
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;
}
}