微信访问网页时判断微信用户是否关注公众号

一、开放平台与公众平台

微信公众平台:
https://mp.weixin.qq.com/cgi-bin/loginpage
微信开放平台:
https://open.weixin.qq.com/
微信官方文档:https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html
两者的区别:
开放平台是网站或app使用的接口平台,利用开放平台可在自己的网站或app上开发微信帐户登录、微信分享等功能!
公众平台是微信号的一种,也具有开发功能,是在公众号中开发出更多功能,例如微网站等!
简单来讲,公众平台的开发功能是在微信平台的基础上的,而开放平台是在你自己的平台上开发的与微信相关的一些功能。

备注:本文开发内容是微信公众平台的内容,涉及官方文档如下:
获取全局token
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
网页授权
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
获取用户基本信息(UnionID机制)
https://developers.weixin.qq.com/doc/offiaccount/User_Management/Get_users_basic_information_UnionID.html#UinonId

二、 微信公众平台配置

个人学习时可申请测试帐号:
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Requesting_an_API_Test_Account.html
注意:公众平台接口调用仅支持80端口,且项目支持外网访问,个人学习时可在本地配置内网穿透,参考https://blog.csdn.net/qq_37718403/article/details/106093162

1. 获取appid, appsecret,添加白名单

正式帐号

前提:正式帐号接入微信公众平台的配置参考
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
官方描述:
在这里插入图片描述

AppID和AppSecret可在“微信公众平台-开发-基本配置”页中获得(需要已经成为开发者,且帐号没有异常状态)(appSecret只展示一次,需保存下来,否则需要重置获取)。
获取access_token时需要添加IP白名单。
在这里插入图片描述
点击查看
在这里插入图片描述

点击修改在这里插入图片描述

测试帐号

登录测试帐号
在这里插入图片描述

2. 添加网页

正式帐号

官方描述:
在这里插入图片描述
进入公众号设置=》功能设置=》网页授权域名
clipboard.png

点击设置,input框中输入授权回调页的域名参考第1点(只能填写一个),下载第3点中的txt文档,上传至服务器的根目录。
clipboard.png

测试帐号

点击【网页授权获取用户基本信息】的修改,将域名添加进去
在这里插入图片描述
在这里插入图片描述

三、java后端实现

微信开放接口全局返回码说明参考:https://mp.weixin.qq.com/wiki…

源码

WxController

package com.controller;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.ParseException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.util.WeiXinUtil;

import net.sf.json.JSONObject;

@Controller
@RequestMapping("/wx")
public class WxController {
	public static final String APPID="";
    public static final String APPSECRET ="";
    
    private static final Logger logger = Logger.getLogger(WxController.class);
	
	@RequestMapping(value = "/wxLogin", method = RequestMethod.GET)
	public String wxLogin(HttpServletRequest request, HttpServletResponse response)throws ParseException, UnsupportedEncodingException {
		//这个地址是成功后的回调地址,域名必须和公众号中配置的域名一致
		String backUrl="http://xiayehuimou.free.idcfengye.com/ssm_demo/wx/callBack";
		// 第一步:用户同意授权,获取code
		String url ="https://open.weixin.qq.com/connect/oauth2/authorize?appid="+ APPID
					+ "&redirect_uri="
					+URLEncoder.encode(backUrl, "utf-8")
					+ "&response_type=code"
					+ "&scope=snsapi_userinfo"
					+ "&state=STATE#wechat_redirect";
		logger.info("forward重定向地址{" + url + "}");
		return "redirect:" + url ;
	}
	
	@RequestMapping(value = "/callBack", method = RequestMethod.GET)
	public String callBack(ModelMap modelMap,HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String code =req.getParameter("code");
		//第二步:通过code换取网页授权access_token
		String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+APPID
				+ "&secret="+APPSECRET
				+ "&code="+code
				+ "&grant_type=authorization_code";
		System.out.println("url:"+url);
		JSONObject jsonObject = WeiXinUtil.httpsRequest(url, "GET", null);
		String openid = jsonObject.getString("openid");
		String access_token = jsonObject.getString("access_token");
		String refresh_token = jsonObject.getString("refresh_token");
		//第五步验证access_token是否失效;展示都不需要
		String chickUrl="https://api.weixin.qq.com/sns/auth?access_token="+access_token+"&openid="+openid;
		JSONObject chickuserInfo =WeiXinUtil.httpsRequest(chickUrl, "GET", null);
		System.out.println(chickuserInfo.toString());
		if(!"0".equals(chickuserInfo.getString("errcode"))){
			// 第三步:刷新access_token(如果需要)-----暂时没有使用,参考文档https://mp.weixin.qq.com/wiki,
			String refreshTokenUrl="https://api.weixin.qq.com/sns/oauth2/refresh_token?appid="+openid+"&grant_type=refresh_token&refresh_token="+refresh_token;
			JSONObject refreshInfo =  WeiXinUtil.httpsRequest(refreshTokenUrl, "GET", null);
			System.out.println(refreshInfo.toString());
			access_token=refreshInfo.getString("access_token");
		}
		// 第四步:拉取用户信息(需scope为 snsapi_userinfo)
		// 这个url的token是网页授权token(获取的基本信息内容相对较少,没有是否关注公众号字段)
		//String infoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token="+access_token
		//		+ "&openid="+openid
		//		+ "&lang=zh_CN";
		// 此处的token是全局token
		String infoUrl = "https://api.weixin.qq.com/cgi-bin/user/info?access_token="+WeiXinUtil.getToken(APPID,APPSECRET).getAccessToken()
				+ "&openid="+openid
				+ "&lang=zh_CN";
		System.out.println("infoUrl:"+infoUrl);
		JSONObject userInfo = WeiXinUtil.httpsRequest(infoUrl, "GET", null);
		System.out.println("JSON-----"+userInfo.toString());
		System.out.println("名字-----"+userInfo.getString("nickname"));
		System.out.println("头像-----"+userInfo.getString("headimgurl"));
		//获取到用户信息后就可以进行重定向,走自己的业务逻辑了。。。。。。
		//接来的逻辑就是你系统逻辑了,请自由发挥
		// 我此处是打开file.html页面
		return "file";
	}

}

WeiXinUtil

package com.util;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.entity.Token;

/**
 * 类名: CommonUtil </br>
 * 描述: 通用工具类 </br>
 * 开发人员: howin </br>
 * 创建时间: 2016-08-19 </br>
 * 发布版本:V1.0 </br>
 */
public class WeiXinUtil {
	private static Logger log = LoggerFactory.getLogger(WeiXinUtil.class);
	private static long tokenTime = 0;
	private static long jsTicketTime = 0;
	private static Token token = null;
	private static String ticket = null;

	// (全局token)凭证获取(GET)限2000(次/天)
	public final static String token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";

	/**
	 * 发送https请求
	 * 
	 * @param requestUrl
	 *            请求地址
	 * @param requestMethod
	 *            请求方式(GET、POST)
	 * @param outputStr
	 *            提交的数据
	 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
	 */
	public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) {
		JSONObject jsonObject = null;
		try {
			// 创建SSLContext对象,并使用我们指定的信任管理器初始化
			TrustManager[] tm = { new MyX509TrustManager() };
			SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
			sslContext.init(null, tm, new java.security.SecureRandom());
			// 从上述SSLContext对象中得到SSLSocketFactory对象
			SSLSocketFactory ssf = sslContext.getSocketFactory();
			URL url = new URL(requestUrl);
			HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
			conn.setSSLSocketFactory(ssf);
			conn.setDoOutput(true);
			conn.setDoInput(true);
			conn.setUseCaches(false);
			// 设置请求方式(GET/POST)
			conn.setRequestMethod(requestMethod);
			// 当outputStr不为null时向输出流写数据
			if (null != outputStr) {
				OutputStream outputStream = conn.getOutputStream();
				// 注意编码格式
				outputStream.write(outputStr.getBytes("UTF-8"));
				outputStream.close();
			}
			// 从输入流读取返回内容
			InputStream inputStream = conn.getInputStream();
			InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
			String str = null;
			StringBuffer buffer = new StringBuffer();
			while ((str = bufferedReader.readLine()) != null) {
				buffer.append(str);
			}
			// 释放资源
			bufferedReader.close();
			inputStreamReader.close();
			inputStream.close();
			inputStream = null;
			conn.disconnect();
			jsonObject = JSONObject.fromObject(buffer.toString());
		} catch (ConnectException ce) {
			log.error("连接超时:{}", ce);
		} catch (Exception e) {
			log.error("https请求异常:{}", e);
		}
		return jsonObject;
	}

	/**
	 * 获取接口访问凭证
	 * 
	 * @param appid
	 *            凭证
	 * @param appsecret
	 *            密钥
	 * @return
	 */
	public static Token getToken(String appid, String appsecret) {
		long now = new Date().getTime();
		if (tokenTime != 0 && now - tokenTime < 7000000) {// token有效时间 7e6 毫秒
			return token;
		}
		String requestUrl = token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
		// 发起GET请求获取凭证
		JSONObject jsonObject = httpsRequest(requestUrl, "GET", null);
		if (null != jsonObject) {
			try {
				token = new Token();
				token.setAccessToken(jsonObject.getString("access_token"));
				token.setExpiresIn(jsonObject.getInt("expires_in"));
				tokenTime = now;
			} catch (JSONException e) {
				token = null;
				// 获取token失败
				log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"),
						jsonObject.getString("errmsg"));
			}
		}
		return token;
	}

	/**
	 * 获取jsapi_ticket访问凭证
	 */
	public static String getJsTicket() {
		long now = new Date().getTime();
		if (jsTicketTime != 0 && now - jsTicketTime < 7000000) {// token有效时间 7e6 毫秒
			return ticket;
		}
		// 得到token
		String accessToken = getToken(null, null).getAccessToken();

		// GET方法获得jsapi_ticket
		String requestUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi"
				.replace("ACCESS_TOKEN", accessToken);
		// 发起GET请求获取凭证
		JSONObject jsonObject = httpsRequest(requestUrl, "GET", null);
		if (jsonObject != null) {
			ticket = jsonObject.getString("ticket");
			jsTicketTime = now;
		}
		return ticket;
	}

	/**
	 * URL编码(utf-8)
	 * 
	 * @param source
	 * @return
	 */
	public static String urlEncodeUTF8(String source) {
		String result = source;
		try {
			result = java.net.URLEncoder.encode(source, "utf-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		return result;
	}

	/**
	 * 根据内容类型判断文件扩展名
	 * 
	 * @param contentType
	 *            内容类型
	 * @return
	 */
	public static String getFileExt(String contentType) {
		String fileExt = "";
		if ("image/jpeg".equals(contentType))
			fileExt = ".jpg";
		else if ("audio/mpeg".equals(contentType))
			fileExt = ".mp3";
		else if ("audio/amr".equals(contentType))
			fileExt = ".amr";
		else if ("video/mp4".equals(contentType))
			fileExt = ".mp4";
		else if ("video/mpeg4".equals(contentType))
			fileExt = ".mp4";
		return fileExt;
	}

	// 菜单创建(POST) 限100(次/天)
	public static String menu_create_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";

	/**
	 * 创建菜单
	 * 
	 * @param menu
	 *            菜单实例
	 * @param accessToken
	 *            有效的access_token
	 * @return 0表示成功,其他值表示失败
	 */
	/*
	 * public static int createMenu(Menu menu, String accessToken) { int result = 0;
	 * 
	 * // 拼装创建菜单的url String url = menu_create_url.replace("ACCESS_TOKEN",
	 * accessToken); // 将菜单对象转换成json字符串 String jsonMenu =
	 * JSONObject.fromObject(menu).toString(); // 调用接口创建菜单 JSONObject jsonObject =
	 * httpsRequest(url, "POST", jsonMenu); System.out.println(jsonMenu); if (null
	 * != jsonObject) { if (0 != jsonObject.getInt("errcode")) { result =
	 * jsonObject.getInt("errcode"); log.error("创建菜单失败 errcode:{} errmsg:{}",
	 * jsonObject.getInt("errcode"), jsonObject.getString("errmsg")); } }
	 * 
	 * return result; }
	 */
	/**
	 * 对Map数组进行排序
	 */
	public static String FormatQueryParaMap(HashMap<String, String> parameters) throws UnsupportedEncodingException {
		String buff = "";

		List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(parameters.entrySet());

		Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {
			public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
				return (o1.getKey()).toString().compareTo(o2.getKey());
			}
		});

		for (int i = 0; i < infoIds.size(); i++) {
			Map.Entry<String, String> item = infoIds.get(i);
			if (item.getKey() != "") {
				// buff += item.getKey() + "="+ URLEncoder.encode(item.getValue(), "utf-8") +
				// "&";
				buff += item.getKey() + "=" + item.getValue() + "&";
			}
		}
		if (buff.isEmpty() == false) {
			buff = buff.substring(0, buff.length() - 1);
		}
		return buff;
	}

	/**
	 * 生成32位随机字符串
	 */
	public static String CreateNoncestr() {
		String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
		String res = "";
		for (int i = 0; i < 16; i++) {
			Random rd = new Random();
			res += chars.charAt(rd.nextInt(chars.length() - 1));
		}
		return res;
	}

	/**
	 * SHA1加密
	 */
	public final static String Sha1(String s) {
		char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
		try {
			byte[] btInput = s.getBytes();
			// 获得MD5摘要算法的 MessageDigest 对象
			MessageDigest mdInst = MessageDigest.getInstance("sha-1");
			// 使用指定的字节更新摘要
			mdInst.update(btInput);
			// 获得密文
			byte[] md = mdInst.digest();
			// 把密文转换成十六进制的字符串形式
			int j = md.length;
			char str[] = new char[j * 2];
			int k = 0;
			for (int i = 0; i < j; i++) {
				byte byte0 = md[i];
				str[k++] = hexDigits[byte0 >>> 4 & 0xf];
				str[k++] = hexDigits[byte0 & 0xf];
			}
			return new String(str);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
}

MyX509TrustManager

package com.util;
 
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
/**
* 类名: MyX509TrustManager </br>
* 描述:信任管理器 </br>
* 开发人员: howin </br>
* 创建时间: 2016-08-19 </br>
* 发布版本:V1.0 </br>
 */
/*
 * 证书管理器的作用是让它新人我们指定的证书,
 * 此类中的代码意味着信任所有的证书,不管是不是权威机构颁发的。
 */
public class MyX509TrustManager implements X509TrustManager {
  // 检查客户端证书
  public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
  }
  // 检查服务器端证书
  public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
  }
  // 返回受信任的X509证书数组
  public X509Certificate[] getAcceptedIssuers() {
    return null;
  }
}

注意事项

如果使用测试号,用户必须要先关注测试号,否则获取code时不会执行回调方法
微信公众号测试账号获取授权须关注


版权声明:本文为qq_37718403原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。