Annago

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

				//消息类型 -- 事件(event)
				if (MessageUtil.REQ_MESSAGE_TYPE_EVENT.equals(requestMap.get("msgType"))) {
					//关注(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;
	}
}

分类:

技术点:

相关文章: