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.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
- 在使用全局access_token获取OpenId的详细信息
https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
方法二:通过网页授权弹出授权页面获取用户基本信息**
-
微信公众号配置回调域名
-
构造url请求将回调指向自己服务器的接口或者页面,例如:
https://open.weixin.qq.com/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。例如:
http://xxxx.xxxx.cn/om/wx/getUserInfo?code=CODE&state=1
-
使用code调用网页授权的获取access_token接口取得access_token
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=APPSECRET&code=CODE&grant_type=authorization_code
调用之后回返回OpenId以及网页授权的access_token -
使用access_token和openId获取用户信息
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
这样便去的了用户信息,我采用的方案便是这种,改方案的好处使用网页授权无需关心用户是否关注。
注意:到此大家肯定发现了方法一和方法二获取用户信息的接口不一样;
对于这两个接口要给大家说明一下:方法一中的获取用户信息接口有先决条件首先用户需要关注公众号,然后接口中的ACCESS_TOKEN必须是全局的!!!
方法二当中的:不需要用户关注,只需要授权即可,ACCESS_TOKEN是网页授权的次数没有限制
方法三:通过网页授权不弹出授权页面获得用户基本信息
个人感觉该方法就是上面两种方法的综合体,该方法有一定局限性,需要用户关注!!!
- 微信公众号配置回调域名
- 网页授权请求构造(静默)
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URL&response_type=code&scope=snsapi_base&state=1#wechat_redirect
和方法二的区别就是scope=snsapi_base作用域变成不弹出授权页面只进行跳转,只获取用户的OpenId - REDIRECT_URL回调页面
当请求成功的时候会重定向到回调页面(接口),此时的URL将会增加俩参数一个是code一个是state。例如:
http://xxxx.xxxx.cn/om/wx/getUserInfo?code=CODE&state=1
取到code - 获取全局的Access Token(下面的步骤就和方法一种的一样了)
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
- 在使用全局access_token获取OpenId的详细信息
https://api.weixin.qq.com/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.jerei.ebase.common.result.Result;
import com.jerei.ebase.common.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 2019/4/25 16:36
*/
@RestController
@RequestMapping("/wx")
public class WeiXinUserInfoController {
private static Logger logger = LoggerFactory.getLogger(WeiXinUserInfoController.class);
@Autowired
private WeiXinUserInfoService userService;
@Autowired
private 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 已经openId
authInfo= userService.getAuthInfo(code);
//获取到openId
openId = 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 2019/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 2019/4/25 14:01
*/
@Service
public class WeiXinUserInfoImlp implements WeiXinUserInfoService {
@Override
public WeiXinUser getUserInfo(String accessToken, String openId) {
WeiXinUser weixinUserInfo = null;
// 拼接获取用户信息接口的请求地址access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
String 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;
}
@Override
public Map<String, String> getAuthInfo(String code) {
//进行授权验证,获取到OpenID字段等信息包括AccessToken
Map<String, String> result = oauth2GetOpenid(code);
// 从这里可以得到用户openid
String openId = result.get("Openid");
return result;
}
@Override
public 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 scope
String 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 2019/4/25 8:26
*/
@Data
public 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;
@Override
public 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 2019/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 2019/4/25 13:25
*/
public class ProjectConst {
//测试号 APPID 和APPSECRET
public static final String PROJECT_APPID ="";
public static final String PROJECT_APPSECRET ="";
/**
* 用于获取当前与微信公众号交互的用户信息的接口(第一个接口需要用户关注第二个不需要)
*/
public static final String GET_WEIXIN_USER_URL = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID";
public final static String GET_PAGEUSERS_URL = "https://api.weixin.qq.com/sns/userinfo?";
/**
* 用于进行网页授权验证的接口URL,通过这个才可以得到opendID等字段信息(用户网页授权access_token 获取接口地址)
*/
public final static String GET_WEBAUTH_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?";
/**
* 这个主要是为了获取用户信息成功之后的跳转页面
* 注意:参数:REDIRECT_URL 表示的是当授权成功后,跳转到的自己设定的页面,所以这个要根据自己的需要进行修改
* 使用位置:是在控制器当中的modelAndView的返回中使用的
*/
public final static String Get_WEIXINPAGE_Code = "https://open.weixin.qq.com/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.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
}
文章在再这里基本上就结束了,这样就能获取到用户信息,至于实际当中用什么方法取要看业务需求,如果大家还有疑问可以通过qq联系我,谢谢。
5、上面实现代码参考
我也是参考了之前大神的文章写的,原博文当中的疑问我也已经搞明白,有不明白的可以咨询我。
原文地址:方法参考原文地址