微信公众号获取用户 openid 及用户信息  
本次开发主要是在公众号中访问服务器一个表单页面提交用户信息到后台。以下内容均来自于微信公众号官方文档,文档写的已经很详细了这里稍作记录,便于日后查阅微信公众号开发文档:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#2 
 
大致流程如下
第一步:用户同意授权,获取 code
第二步:通过code换取网页授权 access_token
第三步:拉取用户信息(需 scope 为 snsapi_userinfo)
在微信公众号请求用户网页授权之前,开发者需要先到公众平台官网中的“开发 - 接口权限 - 网页服务 - 网页帐号 - 网页授权获取用户基本信息”的配置选项中,修改授权回调域名。请注意,这里填写的是域名(是一个字符串),而不是URL,因此请勿加 http:// 等协议头
 
跳转页面需要获取用户的基本信息(openId、头像、昵称、性别等)
菜单配置链接:
https://open.weixin.qq.com/connect/oauth2/authorize
?appid=xxxxxxxxx
&redirect_uri=xxxxxxx
&response_type=code
&scope=snsapi_userinfo
&state=state#wechat_redirect
特殊场景下的静默授权:
如果用户关注了该公众号且从公众号回话或者自定义菜单进入网页授权页,即使是 scope 为 snsapi_userinfo 88也是静默授权,用户无感知
 
参数 
是否必须 
说明 
 
 
appid 
是 
公众号的唯一标识 
 
redirect_uri 
是 
授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理 
 
response_type 
是 
返回类型,请填写code 
 
scope  
是  
应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )  
 
state 
否 
重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节 
 
#wechat_redirect 
是 
无论直接打开还是做页面302重定向时候,必须带此参数 
 
根据上述配置菜单后以下是获取openId及网页授权access_token的步骤
第一步:用户访问页面同意授权后,获取url上的code 如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。
code说明:
code 作为换取 access_token 的票据,每次用户授权带上的 code 将不一样,code 只能使用一次,5分钟未被使用自动过期。
 
js前端代码示例:
1、api.js
1 2 3 4 5 6 7 export  function  login (params ) {    return  Vue .axios ({         url : '/yqfk-wx/wechatHandler/login' ,         method : 'post' ,         data : params     }); } 
 
2、截取路径code并请求后台
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import  { login } from  '@/api/index' ;created ( ) {    const  code = this .getUrlParam ('code' )       this .getOpenId (code)  } methods : {    getOpenId (code ) {         const  params = { code : code };         login (params).then ((res ) =>  {             console .log ('接口数据' , res)         }).catch (() =>  {});     },     getUrlParam (name ) {         var  reg = new  RegExp ('(^|&)'  + name + '=([^&]*)(&|$)' );         let  url = window .location .href .split ('#' )[0 ];         let  search = url.split ('?' )[1 ];         if  (search) {             var  r = search.substr (0 ).match (reg);             if  (r !== null ) {                 return  unescape (r[2 ]);             }             return  null ;         } else  {             return  null ;         }     }, } 
 
第二步:通过code换取网页授权access_token 
在确保微信公众账号拥有授权作用域(scope参数)的权限的前提下(服务号获得高级接口后,默认拥有scope参数中的snsapi_base和snsapi_userinfo),则通过code换取网页授权access_token同时也获取了openid。通过code换取的是一个特殊的网页授权access_token,与基础支持中的access_token(该access_token用于调用其他接口)不同。
 
获取code后,请求以下链接获取access_token:
https://api.weixin.qq.com/sns/oauth2/access_token
?appid=APPID
&secret=SECRET
&code=CODE
&grant_type=authorization_code
参数 
是否必须 
说明 
 
 
appid 
是 
公众号的唯一标识 
 
secret 
是 
公众号的appsecret 
 
code 
是 
填写第一步获取的code参数 
 
grant_type 
是 
填写为authorization_code 
 
尤其注意:由于公众号的 secret 和获取到的 access_token 安全级别都非常高,必须只保存在 服务器 ,不允许传给 客户端 。后续刷新access_token、通过 access_token 获取 用户信息 等步骤,也必须从 服务器 发起。
 
java后端代码示例:
1、controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RequestMapping("/login") @ResponseBody public  Map<String, Object> wechatOauth (String code)  throws  Exception {    Map<String, Object> map = new  HashMap <>();          if (StringUtil.isEmpty(code) ){         throw  new  ServiceException ("code不能为空!" );     }          Map<String, String> accessToken = weChatCoreService.getAccessToken(code);     String  openId  =  accessToken.get("openid" );     map.put("openId" , openId);     return  getSucessMap(map); } 
 
2、service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public  static  final  String  GET_BY_CODE  =  "https://api.weixin.qq.com/sns/oauth2/access_token?" ;public  static  final  String  GRANT_TYPE  =  "authorization_code" ;@Override public  Map<String,String> getAccessToken (String code)  throws  Exception {         String  AppId  =  SettingUtil.getString("AppId" );     String  Secret  =  SettingUtil.getString("Secret" );          String  requestUrl  =  GET_BY_CODE + "appid="  + AppId + "&secret="  + Secret + "&code="  + code + "&grant_type="  + GRANT_TYPE;     JSONObject  reponseJson  =  null ;     try  {                  reponseJson = HttpHelper.httpGet(requestUrl);     } catch  (Exception e) {         e.printStackTrace();     }     String  access_token  =  reponseJson.getString("access_token" );     String  openId  =  reponseJson.getString("openid" );     Map<String,String> map = new  HashMap <String,String>();     map.put("access_token" , access_token);     map.put("openid" , openId);     return  map; } 
 
正确返回结果如下:
1 2 3 4 5 6 7 {   "access_token" : "ACCESS_TOKEN" ,    "expires_in" : 7200 ,    "refresh_token" : "REFRESH_TOKEN" ,    "openid" : "OPENID" ,    "scope" : "SCOPE"  } 
 
第三步:拉取用户信息(需scope为 snsapi_userinfo) 如果网页授权作用域为 snsapi_userinfo,则此时开发者可以通过 access_token 和 openid 拉取用户信息了。
请求接口:
 https://api.weixin.qq.com/sns/userinfo
?access_token=ACCESS_TOKEN
&openid=OPENID
&lang=zh_CN
参数 
描述 
 
 
access_token 
网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同 
 
openid 
用户的唯一标识 
 
lang 
返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语 
 
java后端代码示例:
1、controller
注意:
该controller方法与第二步的controller为同一个,此处为补充部分代码
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @RequestMapping("/login") @ResponseBody public  Map<String, Object> wechatOauth (String code)  throws  Exception {         Map<String, Object> map = new  HashMap <>();          if (StringUtil.isEmpty(code) ){         throw  new  ServiceException ("code不能为空!" );     }          Map<String, String> accessToken = weChatCoreService.getAccessToken(code);     String  openId  =  accessToken.get("openid" );          SysUser  userDB  =  sysUserService.findByOpenId(openId);          if  (userDB == null ) {                  Map<String, String> WechatUserMap = weChatCoreService.getWechatUserInfo(accessToken);                  SysUser  wechatUser  =  new  SysUser ();         wechatUser.setNickName(WechatUserMap.get("nickname" ));         wechatUser.setAvatarUrl(WechatUserMap.get("headimgurl" ));         wechatUser.setCountry(WechatUserMap.get("country" ));         wechatUser.setOpenId(openId);                  userDB = sysUserService.wechatSave(wechatUser);     }          SysStaffInfo  queryStaffInfo  =  new  SysStaffInfo ();     queryStaffInfo.setUserId(userDB.getUserId());          List<SysStaffInfo> list = sysStaffInfoService.findList(queryStaffInfo);     if  (list.size() > 0 ) {         map.put("sysStaffInfo" , list.get(0 ));     }          map.put("openId" , openId);     map.put("sysUser" , userDB);     return  getSucessMap(map); } 
 
2、service代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public  static  final  String  GET_USERINFO_WEB  =  "https://api.weixin.qq.com/sns/userinfo?" ;@Override public  Map<String,String> getWechatUserInfo (Map<String, String> map)  {    String  openid  =  map.get("openid" );     String  access_token  =  map.get("access_token" );     String  requestUrl  =  GET_USERINFO_WEB + "access_token="  + access_token + "&openid="  + openid + "&lang=zh_CN" ;     JSONObject  reponseJson  =  null ;     try  {         reponseJson = HttpHelper.httpGet(requestUrl);     } catch  (Exception e) {         e.printStackTrace();     }     String  nickname  =  reponseJson.getString("nickname" );     String  sex  =  reponseJson.getString("sex" );     String  province  =  reponseJson.getString("province" );     String  city  =  reponseJson.getString("city" );     String  country  =  reponseJson.getString("country" );     String  headimgurl  =  reponseJson.getString("headimgurl" );     map.put("sex" , sex);     map.put("province" , province);     map.put("city" , city);     map.put("country" , country);     map.put("headimgurl" , headimgurl);     map.put("nickname" , nickname);     return  map; } 
 
返回说明
正确时返回的JSON数据包如下:
1 2 3 4 5 6 7 8 9 10 11 {   "openid" :  "OPENID" ,    "nickname" :  NICKNAME,    "sex" :  1 ,    "province" : "PROVINCE" ,    "city" : "CITY" ,    "country" : "COUNTRY" ,    "headimgurl" : "https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46" ,    "privilege" : [  "PRIVILEGE1"  "PRIVILEGE2"      ] ,    "unionid" :  "o6_bmasdasdsad6_2sgVt7hMZOPfL"  } 
 
以上
结语 
天生百种愁,挂在斜阳树。 绿叶阴阴占得春,草满莺啼处。 不见生尘步。空忆如簧语。 柳外重重叠叠山,遮不断、愁来路。 ———— 《卜算子·天生百种愁》宋代 徐俯