1、由来以及项目环境
之所以写这个是因为,在工作当中突然就收到消息要做一个抽奖的活动,需要获取用户信息,再次就记录一下获取微信用户信息的整个流程,以及代码分享一下。
这个项目是使用spring boot的前后分离项目,然后剩下的就不具体细说了。按照惯例先把联系方式填写下qq704273004
2、讲述一下获取微信用户信息的原理或者说途径
在此讲一些原理和接口也是参考别人然后加上自己理解,使用的时候最好也自己理解一下。获取用户信息根据微信公众号的文档的流程来的,具体公众号的地址就不贴出来了 读者自行寻找就好了,大概获得用户信息的方法有三种,如有理解不对或是错误的地方之后再更改。
获取用户信息主要分为2中方式,一种是静默授权一种是用户感知授权,这其中主要区别是调用接口和是否弹出授权页面,以及获取用户信息使用的接口不一样。
个人理解静默授权需要关注公众号,用户感知授权可以不需要用户关注公号就取到用户信息。
同时是用户感知授权对应的授权是方式网页授权,静默授权的授权方式是全局授权,最大的区别是两个access_token的区别。
关于Access_token的异同:
有效期:两者有效时间都是7200s。使用范围:通过网页授权获得的access_token,只能获取到对应的微信用户信息,与微信用户是一对一关系;而普通的access_token在有效期内可以使用,可以获取所有用户信息。次数限制:普通access_token每天获取最多次数为2000次,而网页授权的access_token获取次数没有限制。
(引用博客连接)详细区别请点击
3、具体的使用方法及调用接口
方法一:使用全局的Access Token获取用户基本信息**
用户关注及恢复小时的时候,获取用户的OpenId使用全局获取access_token接口获取https://api./cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
在使用全局access_token获取OpenId的详细信息
https://api./cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
方法二:通过网页授权弹出授权页面获取用户基本信息**
微信公众号配置回调域名
构造url请求将回调指向自己服务器的接口或者页面,例如:
https://open./connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URL&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect
参数解释:APPID微信公众号的APPID;REDIRECT_URL重定向页面这个URL一般指向自己的服务器;scope=snsapi_userinfo该参数表示授权的作用域是请求用户信息
注意:scope的参数有两种一种是snsapi_base该参数只能获取OpenId并且不会弹出授权页面;snsapi_userinfo参数会弹出授权页面,并且可以用于取用户信息
REDIRECT_URL回调页面
当请求成功的时候会重定向到回调页面(接口),此时的URL将会增加俩参数一个是code一个是state。例如:
/om/wx/getUserInfo?code=CODE&state=1
使用code调用网页授权的获取access_token接口取得access_token
https://api./sns/oauth2/access_token?appid=APPID&secret=APPSECRET&code=CODE&grant_type=authorization_code
调用之后回返回OpenId以及网页授权的access_token
使用access_token和openId获取用户信息
https://api./sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
这样便去的了用户信息,我采用的方案便是这种,改方案的好处使用网页授权无需关心用户是否关注。
注意:到此大家肯定发现了方法一和方法二获取用户信息的接口不一样; 对于这两个接口要给大家说明一下:方法一中的获取用户信息接口有先决条件首先用户需要关注公众号,然后接口中的ACCESS_TOKEN必须是全局的!!! 方法二当中的:不需要用户关注,只需要授权即可,ACCESS_TOKEN是网页授权的次数没有限制
方法三:通过网页授权不弹出授权页面获得用户基本信息
个人感觉该方法就是上面两种方法的综合体,该方法有一定局限性,需要用户关注!!!
微信公众号配置回调域名网页授权请求构造(静默)
https://open./connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URL&response_type=code&scope=snsapi_base&state=1#wechat_redirect
和方法二的区别就是scope=snsapi_base作用域变成不弹出授权页面只进行跳转,只获取用户的OpenIdREDIRECT_URL回调页面
当请求成功的时候会重定向到回调页面(接口),此时的URL将会增加俩参数一个是code一个是state。例如:
/om/wx/getUserInfo?code=CODE&state=1
取到code获取全局的Access Token(下面的步骤就和方法一种的一样了)
https://api./cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
在使用全局access_token获取OpenId的详细信息
https://api./cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
注意点:
对添加的回调页面域名:首先,不能加入http:// ,只需要填写域名;另外,只需要写总的域名地址,不需要精确到最内层。 https的回调好像有问题,尽量使用http吧,这个我没有深入。
4、代码的实现(暂时只对方法2进行了实现)
上面的原理都已经讲过了在这里可能只贴代码不进行具体的讲解了
Controller层代码:
package com.jerei.ebase.modules.ommt.controller;import com.baomidou.mybatisplus.mapper.EntityWrapper;import com.mon.result.Result;import com.mon.utils.RedisUtils;import com.jerei.ebase.modules.ommt.entity.WeiXinUser;import com.jerei.ebase.modules.ommt.entity.WxUserSignEntity;import com.jerei.ebase.modules.ommt.service.WeiXinUserInfoService;import com.jerei.ebase.modules.ommt.service.WxUserSignService;import com.jerei.ebase.modules.ommt.util.ProjectConst;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.servlet.ModelAndView;import org.springframework.web.servlet.view.RedirectView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;import java.util.HashMap;import java.util.Map;/*** @author 柚子* @Description: om-meeting-pro* @Title: 重定向之后调取的url* @Created by zhongshuiayou on /4/25 16:36*/@RestController@RequestMapping("/wx")public class WeiXinUserInfoController {private static Logger logger = LoggerFactory.getLogger(WeiXinUserInfoController.class);@Autowiredprivate WeiXinUserInfoService userService;@Autowiredprivate WxUserSignService wxUserSignService;/*** 进行网页授权,便于获取到用户的绑定的内容* @param request* @param session* @param map* @return*/@RequestMapping("/getUserInfo")public ModelAndView check(HttpServletRequest request , HttpSession session, Map<String, Object> map) {//首先判断一下session中,是否有保存着的当前用户的信息,有的话,就不需要进行重复请求信息//这个地方作用主要是一次授权可以维持一定时间不需要再次授权WeiXinUser weiXinUser = null ;if(session.getAttribute("currentUser") != null){weiXinUser = (WeiXinUser) session.getAttribute("currentUser");}else {/*** 进行获取openId,必须的一个参数,这个是当进行了授权页面的时候,再重定向了我们自己的一个页面的时候*/String code = request.getParameter("code");try {//得到当前用户的信息(具体信息就看weixinUser这个javabean)weiXinUser = getTheCode(session, code);logger.info(weiXinUser.toString());WxUserSignEntity wxUserSignEntity = wxUserSignService.selectOne(new EntityWrapper<WxUserSignEntity>().eq("open_id",weiXinUser.getOpenId()));if (wxUserSignEntity==null|| wxUserSignEntity.getOpenId()==null) {//保存用户信息的业务代码wxUserSignEntity = new WxUserSignEntity();wxUserSignEntity.setOpenId(weiXinUser.getOpenId());wxUserSignEntity.setNickname(weiXinUser.getNickname());wxUserSignEntity.setHeadImgUrl(weiXinUser.getHeadImgUrl());wxUserSignEntity.setCity(weiXinUser.getCity());wxUserSignEntity.setLanguage(weiXinUser.getLanguage());wxUserSignEntity.setSex(weiXinUser.getSex());wxUserSignEntity.setProvince(weiXinUser.getProvince());wxUserSignEntity.setCountry(weiXinUser.getCountry());wxUserSignService.insert(wxUserSignEntity);}else{//返回参与活动成功之后的页面return new ModelAndView(new RedirectView(ProjectConst.Get_WEIXINPAGE_Code));}//将获取到的用户信息,放入到session中System.out.println(weiXinUser.toString());session.setAttribute("currentUser", weiXinUser);} catch (Exception e) {e.printStackTrace();}}map.put("weiXinUser", weiXinUser);return new ModelAndView(new RedirectView(ProjectConst.Get_WEIXINPAGE_Code));}/*** 获取用户的openId* @param session* @param code* @return 返回封装的微信用户的对象*/private WeiXinUser getTheCode(HttpSession session, String code) {Map<String , String> authInfo = new HashMap<>();String openId = "";if (code != null){// 调用根据用户的code得AccessToken 已经openIdauthInfo= userService.getAuthInfo(code);//获取到openIdopenId = authInfo.get("Openid");}//获取到微信用户的信息WeiXinUser userinfo = userService.getUserInfo(authInfo.get("AccessToken") ,openId);return userinfo;}}
Service层接口:
package com.jerei.ebase.modules.ommt.service;import com.jerei.ebase.modules.ommt.entity.WeiXinUser;import java.util.Map;/*** @author 柚子* @Description: om-meeting-pro* @Title: 获取微信用户信息service接口* @Created by zhongshuiayou on /4/25 13:59*/public interface WeiXinUserInfoService {/*** 获取到微信个人用户的信息* @param accessToken* @param openId* @return*/WeiXinUser getUserInfo(String accessToken, String openId);/***用于获取网页授权后的信息字段,其中主要是获取openId* @param code 授权码* @return*/Map<String , String > getAuthInfo(String code);/*** 进行网页授权的认证* @param code 授权码* @return*/Map<String,String> oauth2GetOpenid(String code);}
Service实现层:
package com.jerei.ebase.modules.ommt.service.impl;import com.alibaba.fastjson.JSONObject;import com.jerei.ebase.modules.ommt.entity.WeiXinUser;import com.jerei.ebase.modules.ommt.service.WeiXinUserInfoService;import com.jerei.ebase.modules.ommt.util.ProjectConst;import com.jerei.ebase.modules.ommt.util.WxUtils;import org.springframework.stereotype.Service;import java.util.HashMap;import java.util.Map;/*** @author 柚子* @Description: om-meeting-pro* @Title: 获取用户信息实现层* @Created by zhongshuiayou on /4/25 14:01*/@Servicepublic class WeiXinUserInfoImlp implements WeiXinUserInfoService {@Overridepublic WeiXinUser getUserInfo(String accessToken, String openId) {WeiXinUser weixinUserInfo = null;// 拼接获取用户信息接口的请求地址access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CNString requestUrl = ProjectConst.GET_PAGEUSERS_URL+"access_token="+accessToken+"&openid="+openId+"&lang=zh_CN";// 获取用户信息(返回的是Json格式内容)JSONObject jsonObject = WxUtils.doGetStr(requestUrl);if (null != jsonObject) {try {//封装获取到的用户信息weixinUserInfo = new WeiXinUser();// 用户的标识weixinUserInfo.setOpenId(jsonObject.getString("openid"));// 昵称weixinUserInfo.setNickname(WxUtils.filterEmoji(jsonObject.getString("nickname")));// 用户的性别(1是男性,2是女性,0是未知)weixinUserInfo.setSex(jsonObject.getInteger("sex"));// 用户所在国家weixinUserInfo.setCountry(jsonObject.getString("country"));// 用户所在省份weixinUserInfo.setProvince(jsonObject.getString("province"));// 用户所在城市weixinUserInfo.setCity(jsonObject.getString("city"));// 用户头像weixinUserInfo.setHeadImgUrl(jsonObject.getString("headimgurl"));} catch (Exception e) {//这个地方可以去掉不会出现这种问题(原文博主是用地三种方式取的,我已经将其修改成为第二种)//可以只留着else里面的错误信息//if (0 == weixinUserInfo.getSubscribe()) {//System.out.println("用户并没有关注本公众号");//} else {int errorCode = jsonObject.getInteger("errcode");String errorMsg = jsonObject.getString("errmsg");System.out.println("由于"+errorCode +"错误码;错误信息为:"+errorMsg+";导致获取用户信息失败");//}}}return weixinUserInfo;}@Overridepublic Map<String, String> getAuthInfo(String code) {//进行授权验证,获取到OpenID字段等信息包括AccessTokenMap<String, String> result = oauth2GetOpenid(code);// 从这里可以得到用户openidString openId = result.get("Openid");return result;}@Overridepublic Map<String, String> oauth2GetOpenid(String code) {//自己的配置appid(公众号进行查阅)String appid = ProjectConst.PROJECT_APPID;//自己的配置APPSECRET;(公众号进行查阅)String appsecret = ProjectConst.PROJECT_APPSECRET;//拼接用户授权接口信息String requestUrl = ProjectConst.GET_WEBAUTH_URL+"appid="+appid+"&secret="+appsecret+"&code="+code+"&grant_type=authorization_code";//存储获取到的授权字段信息Map<String, String> result = new HashMap<String, String>();try {//这个地方用户自行判断是否要保存该信息(此处主要实现的功能是通过网页授权获取openId,access_token)JSONObject OpenidJSONO = WxUtils.doGetStr(requestUrl);//OpenidJSONO可以得到的内容:access_token expires_in refresh_token openid scopeString Openid = String.valueOf(OpenidJSONO.get("openid"));String AccessToken = String.valueOf(OpenidJSONO.get("access_token"));//用户保存的作用域String Scope = String.valueOf(OpenidJSONO.get("scope"));String refresh_token = String.valueOf(OpenidJSONO.get("refresh_token"));result.put("Openid", Openid);result.put("AccessToken", AccessToken);result.put("scope", Scope);result.put("refresh_token", refresh_token);} catch (Exception e) {e.printStackTrace();}return result;}}
实体类接口:
由于采用的是方法2的方式所以有一些字段是没有数据的,原文博客的博主是采用方法3取得因此在此我没有进行修改,基础字段是一样,我采用lombok因此没有get和set方法,需要的同学自行加上就好。
package com.jerei.ebase.modules.ommt.entity;import lombok.Data;/*** @author 柚子* @Description: om-meeting-pro* @Title: 获取微信用户信息实体* @Created by zhongshuiayou on /4/25 8:26*/@Datapublic class WeiXinUser {/*** 用户的标识*/private String openId;/*** 关注状态(1是关注,0是未关注),未关注时获取不到其余信息*/private int subscribe;/*** 用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间*/private String subscribeTime;/*** 昵称*/private String nickname;/*** 用户的性别(1是男性,2是女性,0是未知)*/private int sex;/*** 用户所在国家*/private String country;/*** 用户所在省份*/private String province;/*** 用户所在城市*/private String city;/*** 用户的语言,简体中文为zh_CN*/private String language;/*** 用户头像*/private String headImgUrl;@Overridepublic String toString() {return "WeiXinUser{" +"openId='" + openId + '\'' +", subscribe=" + subscribe +", subscribeTime='" + subscribeTime + '\'' +", nickname='" + nickname + '\'' +", sex=" + sex +", country='" + country + '\'' +", province='" + province + '\'' +", city='" + city + '\'' +", language='" + language + '\'' +", headImgUrl='" + headImgUrl + '\'' +'}';}}
用到的工具类以及静态常量:
package com.jerei.ebase.modules.ommt.util;import com.alibaba.fastjson.JSONObject;import org.apache.http.HttpEntity;import org.apache.http.HttpResponse;import org.apache.http.client.ClientProtocolException;import org.apache.http.client.methods.HttpGet;import org.apache.http.impl.client.DefaultHttpClient;import org.apache.http.util.EntityUtils;import java.io.IOException;/*** @author 柚子* @Description: om-meeting-pro* @Title: 获取微信用户信息工具类* @Created by zhongshuiayou on /4/25 13:49*/public class WxUtils {/*** Get请求,方便到一个url接口来获取结果* @param url* @return*/public static JSONObject doGetStr(String url) {DefaultHttpClient defaultHttpClient = new DefaultHttpClient();HttpGet httpGet = new HttpGet(url);JSONObject jsonObject = null;try {HttpResponse response = defaultHttpClient.execute(httpGet);HttpEntity entity = response.getEntity();if (entity != null) {String result = EntityUtils.toString(entity, "UTF-8");jsonObject = JSONObject.parseObject(result);System.out.println(jsonObject.toJSONString());}} catch (ClientProtocolException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return jsonObject;}/*** 检测是否有emoji字符* @param source 需要判断的字符串* @return 一旦含有就抛出*/public static boolean containsEmoji(String source) {int len = source.length();for (int i = 0; i < len; i++) {char codePoint = source.charAt(i);if (!notisEmojiCharacter(codePoint)) {//判断确认有表情字符return true;}}return false;}/*** 非emoji表情字符判断* @param codePoint* @return*/private static boolean notisEmojiCharacter(char codePoint) {return (codePoint == 0x0) ||(codePoint == 0x9) ||(codePoint == 0xA) ||(codePoint == 0xD) ||((codePoint >= 0x20) && (codePoint <= 0xD7FF)) ||((codePoint >= 0xE000) && (codePoint <= 0xFFFD)) ||((codePoint >= 0x10000) && (codePoint <= 0x10FFFF));}/*** 过滤emoji 或者 其他非文字类型的字符* @param source 需要过滤的字符串* @return*/public static String filterEmoji(String source) {if (!containsEmoji(source)) {//如果不包含,直接返回return source;}//该buf保存非emoji的字符StringBuilder buf = null;int len = source.length();for (int i = 0; i < len; i++) {char codePoint = source.charAt(i);if (notisEmojiCharacter(codePoint)) {if (buf == null) {buf = new StringBuilder(source.length());}buf.append(codePoint);}}if (buf == null) {//如果没有找到非emoji的字符,则返回无内容的字符串return "";} else {if (buf.length() == len) {buf = null;return source;} else {return buf.toString();}}}}
/*** @author zhongshuiayou* @Description: om-meeting-pro* @Title: 静态常量(该静态常量当中基本列出了所有的常用获取微信用户信息接口,使用者按需取就可以了)* @Created by zhongshuiayou on /4/25 13:25*/public class ProjectConst {//测试号 APPID 和APPSECRETpublic static final String PROJECT_APPID ="";public static final String PROJECT_APPSECRET ="";/*** 用于获取当前与微信公众号交互的用户信息的接口(第一个接口需要用户关注第二个不需要)*/public static final String GET_WEIXIN_USER_URL = "https://api./cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID";public final static String GET_PAGEUSERS_URL = "https://api./sns/userinfo?";/*** 用于进行网页授权验证的接口URL,通过这个才可以得到opendID等字段信息(用户网页授权access_token 获取接口地址)*/public final static String GET_WEBAUTH_URL = "https://api./sns/oauth2/access_token?";/*** 这个主要是为了获取用户信息成功之后的跳转页面* 注意:参数:REDIRECT_URL 表示的是当授权成功后,跳转到的自己设定的页面,所以这个要根据自己的需要进行修改* 使用位置:是在控制器当中的modelAndView的返回中使用的*/public final static String Get_WEIXINPAGE_Code = "https://open./connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URL&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect";/*** 获取access_token的URL(基础接口的token也叫全局access_token获取接口)*/public static final String ACCESS_TOKEN_URL = "https://api./cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";}
文章在再这里基本上就结束了,这样就能获取到用户信息,至于实际当中用什么方法取要看业务需求,如果大家还有疑问可以通过qq联系我,谢谢。
5、上面实现代码参考
我也是参考了之前大神的文章写的,原博文当中的疑问我也已经搞明白,有不明白的可以咨询我。
原文地址:方法参考原文地址