之前写过一篇微信公众号开发–自定义菜单跳转页面并获取用户信息
由于当时是刚学习微信公众号开发当时的思路虽然可行,不过不是最好的,最近也用到了需要获取用户信息的地方,再次整理一下。
流程
注意点
###第一
如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。
必须是在微信客户端
第二
通过code换取网页授权access_tokencode作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。
- 1
- 2
- 3
在第二步中通过code换取网页授权access_token,这个access_token和其他接口中使用的access_token,不一样。
如果是静默授权,这里的access_token没什么用,反而是换取access_token时得到的openid非常有用
code只能用一次,注意这里的access_token和基础接口中的access_token不一样
实现步骤
第一步: 配置网页授权回调域名
开发 - 接口权限 - 网页服务 - 网页授权 - 网页授权获取用户基本信息
配置网页授权域名
我这个是测试号,花生壳做的域名
域名不需要配置http://开头,也不需要项目路径
第二步: 生成网页链接
这里有两种一种是scope为snsapi_base另一种是scope为snsapi_userinfo
snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid)
snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
先看第一种
官方给的示例
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx520c15f417810387&redirect_uri=https%3A%2F%2Fchong.qq.com%2Fphp%2Findex.php%3Fd%3D%26c%3DwxAdapter%26m%3DmobileDeal%26showwxpaytitle%3D1%26vb2ctag%3D4_2030_5_1194_60&response_type=code&scope=snsapi_base&state=123#wechat_redirect
- 1
- 2
- 3
前缀为
https://open.weixin.qq.com/connect/oauth2/authorize?appid=你的appId&redirect_uri=
- 1
后缀为
&response_type=code&scope=snsapi_base&state=123#wechat_redirect
- 1
顺序必能乱,中间是你的网址。网址还经过了转码处理,java使用java.net.URLEncoder提供的encode方法即可
好了,这里我们拼接一个我们的链接
http://gwchsk.imwork.net/myair/auth/user
- 1
我们在这个地址下获取用户信息,encode之后为
http%3A%2F%2Fgwchsk.imwork.net%2Fmyair%2Fauth%2Fuser
然后将前缀和后缀都拼接好,不要忘了前缀中的appId
拼接好之后在浏览器输入,找个能生成二维码的浏览器,再扫描一下
一个简单的Controller如下,我们只是看第一步是否可以得到code
package com.airport.controller;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;@Controller@RequestMapping("/auth")public class WechatUserController { @RequestMapping(path = "/user", method = RequestMethod.GET) public String user(String code) { System.out.println("得到的code:"+code); return "user"; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
用微信扫码之后就可以看到控制台输出了code
第三步:通过code换取网页授权access_token,这里会得到openId
通过code就可以换取access_token了,这里其实openId比access_token更有用
换取的结果
{"access_token":"0W71gcY7yvLBBL0JzxQC-sGJnBBOHcx95sBZqpWOTJpNm5C1Tm00UnE1MsNXaFb0JQmDwWHFtGi7vkuHT0ohdf5a8w2FI-JXKYtOKe5ehSs","expires_in":7200,"refresh_token":"QhEl0tbQERkdD-7-JKFZfYh68wqx-Frrs1OnHv5Wv7CarYhDhtk2nATc_x2q5liPMs0Pdlh-FOgJgcIDy0d57wqa431BJ2NVjZKWyWA-hpQ","openid":"oJhxHv8xKWX6ZYo4aeqBhTko94jk","scope":"snsapi_base"}
- 1
我写了一个方法
ExchangeCode2OpenId.java
package com.airport.util;import org.json.JSONException;import org.json.JSONObject;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import com.airport.context.WechatConst;/** * 通过code换取openId * * @author 程高伟 * * @date 2016年10月17日 下午7:05:34 */public class ExchangeCode2OpenId { private static Logger logger = LoggerFactory.getLogger(ExchangeCode2OpenId.class); public static String exchange(String code) { String openid = ""; String appId = WechatConst.appId; String appSecret = WechatConst.appSecret; // 换取access_token 其中包含了openid // 这里通过code换取的是一个特殊的网页授权access_token,与基础支持中的access_token(该access_token用于调用其他接口)不同。 String URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code" .replace("APPID", appId).replace("SECRET", appSecret).replace("CODE", code); String jsonStr = HttpUtil.sendGet(URL); logger.info("----------换取openid返回的结果:{}----------", jsonStr); JSONObject jsonObj = new JSONObject(jsonStr); try { openid = jsonObj.getString("openid"); } catch (JSONException e) { logger.info("----------换取openid发生了异常:{}----------", e.getMessage()); } return openid; }}
- 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
好了,有了openid,我们就可以调用基础接口(用户管理)中的获取用户信息方法了。
这里获取用户信息需要access_token,注意,这里的access_token不是上面通过code换取到的,而是要自己去获取。
获取地址
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
这样就可以获取用户信息了。
整个流程就是这个样子。
自定义菜单获取用户信息
将上一步我们拼接的url作为view型按钮的url
这里如果生成自定义菜单
如果报40033错误,请参考
自定义菜单创建好之后,就可以当用户点击自定义菜单的时候获取用户信息了。
具体的获取用户信息接口地址
@RequestMapping(value = "/user", method = RequestMethod.GET)public String user(String code, Model model, HttpSession session) { String openid = ExchangeCode2OpenId.exchange(code); WechatUser wechatUser = WechatUserUtil.getWechatUser(openid); if (null == wechatUser) { model.addAttribute("title", "请从微信公众号进入"); return "notInWechat"; } System.out.println("wechatUser:" + wechatUser); session.setAttribute("wechatUser", wechatUser); model.addAttribute("wechatUser", wechatUser); model.addAttribute("title", "用户信息"); return "user";}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
我们看一下效果
自定义菜单
点击后进入
点击最下面的按钮
确定后跳转到另一个页面
然后返回,仍然回到了用户信息页面
一切都很顺利
看一下打印输出
如果你也很正常,那恭喜你,你用的安卓设备
我们换成苹果的
再走一遍,先清空打印内容,再看一下iPhone操作的打印输出内容
iPhone会在点击返回的时候再请求一次,而上面我们说过code只能用一次,第二次已经不能用了,所以获取不到用户信息,结果导致出错
如果你用iPad,效果和安卓手机一样。
搞不清楚为什么苹果iPhone和iPad居然不一样。
好了,言归正传,如何解决这个问题,其实很简单,在第一次获取到用户信息后保存起来,具体存哪里根据你的喜好(session也好,缓存也好)
这里我将它放在session里。
献上关键代码
@RequestMapping(value = "/user", method = RequestMethod.GET)public String user(String code, Model model, HttpSession session) { WechatUser wechatUser = getWechatUser(code, session); if (null == wechatUser) { model.addAttribute("title", "请从微信公众号进入"); return "notInWechat"; } System.out.println("wechatUser:" + wechatUser); session.setAttribute("wechatUser", wechatUser); model.addAttribute("wechatUser", wechatUser); model.addAttribute("title", "用户信息"); return "user";}private WechatUser getWechatUser(String code, HttpSession session) { String openid = ""; WechatUser wechatUser = null; WechatUser wechatUserInSession = (WechatUser) session.getAttribute("wechatUser"); // 先看session if (null != wechatUserInSession) { wechatUser = wechatUserInSession; System.out.println("session中有用户信息"); } else {// session中没有 去获取 System.out.println("得到的code:" + code); if (StringUtils.isBlank(code)) { return null; }else{ openid = ExchangeCode2OpenId.exchange(code); if (StringUtils.isBlank(openid)) { return null; } } System.out.println("得到的openid:" + openid); // 这里使用的是普通接口(用户管理,获取用户基本信息) wechatUser = WechatUserUtil.getWechatUser(openid); String headImgurl = wechatUser.getHeadimgurl(); // 132*132的头像 if (StringUtils.isNotBlank(wechatUser.getHeadimgurl())) { headImgurl = headImgurl.substring(0, headImgurl.length() - 1); headImgurl += "132"; wechatUser.setHeadimgurl(headImgurl); } else { headImgurl = "/assets/img/default_head.png"; } wechatUser.setHeadimgurl(headImgurl); System.out.println("得到的用户信息:" + wechatUser); } return wechatUser;}
- 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
- 46
- 47
- 48
- 49
还有一种就是需要用户点击同意后再获取用户信息,就不写了,用到了再说,思路都是一样的。
WechatUserUtil.java
import java.io.UnsupportedEncodingException;import org.apache.commons.lang3.StringUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import com.google.gson.Gson;import com.google.gson.JsonSyntaxException;@Componentpublic class WechatUserUtil { @Autowired private AccessTokenUtil accessToken; private final Logger logger = LoggerFactory.getLogger(this.getClass()); public WechatUser getWechatUser(String openid) { String token = accessToken.getAccessToken(); String URL = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN"; String jsonResult = HttpUtil.sendGet(URL.replace("OPENID", openid).replace("ACCESS_TOKEN", token)); logger.info("----------获取到的用户信息,{}----------", jsonResult); Gson gson = new Gson(); WechatUser wechatUser = null; try { wechatUser = gson.fromJson(new String(jsonResult.getBytes("ISO-8859-1"), "UTF-8"), WechatUser.class); // 错误的openId if (StringUtils.isBlank(wechatUser.getOpenid())) { wechatUser = null; } } catch (JsonSyntaxException e) { logger.info("----------解析json出错----------"); e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return wechatUser; }}
- 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
WechatUser.java
packagecom.jrbac.model.wechat;importjava.io.Serializable;/** * 微信用户详细信息 * * @author GWCheng * */publicclassWechatUserimplementsSerializable{ /** * 用户的真实姓名,绑定后的名称 */ privateString name; /** * */ privatestaticfinallongserialVersionUID =8170898780424513965L; /** * 用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。 */ privateintsubscribe; /** * 用户的标识,对当前公众号唯一 */ privateString openid; /** * 用户的昵称 */ privateString nickname; /** * 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知 */ privateintsex; /** * 用户的语言,简体中文为zh_CN */ privateString language; /** * 用户所在城市 */ privateString city; /** * 用户所在省份 */ privateString province; /** * 用户所在国家 */ privateString country; /** * 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。 * 若用户更换头像,原有头像URL将失效。 */ privateString headimgurl; /** * 用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间 */ privatelongsubscribe_time; /** * 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。详见:获取用户个人信息(UnionID机制) */ privateString unionid; /** * 公众号运营者对粉丝的备注,公众号运营者可在微信公众平台用户管理界面对粉丝添加备注 */ privateString remark; /** * 用户所在的分组ID */ privatelonggroupid; publicString getName(){ returnname; } publicvoidsetName(String name){ this.name =name; } publicintgetSubscribe(){ returnsubscribe; } publicvoidsetSubscribe(intsubscribe){ this.subscribe =subscribe; } publicString getOpenid(){ returnopenid; } publicvoidsetOpenid(String openid){ this.openid =openid; } publicString getNickname(){ returnnickname; } publicvoidsetNickname(String nickname){ this.nickname =nickname; } publicintgetSex(){ returnsex; } publicvoidsetSex(intsex){ this.sex =sex; } publicString getLanguage(){ returnlanguage; } publicvoidsetLanguage(String language){ this.language =language; } publicString getCity(){ returncity; } publicvoidsetCity(String city){ this.city =city; } publicString getProvince(){ returnprovince; } publicvoidsetProvince(String province){ this.province =province; } publicString getCountry(){ returncountry; } publicvoidsetCountry(String country){ this.country =country; } publicString getHeadimgurl(){ returnheadimgurl; } publicvoidsetHeadimgurl(String headimgurl){ this.headimgurl =headimgurl; } publiclonggetSubscribe_time(){ returnsubscribe_time; } publicvoidsetSubscribe_time(longsubscribe_time){ this.subscribe_time =subscribe_time; } publicString getUnionid(){ returnunionid; } publicvoidsetUnionid(String unionid){ this.unionid =unionid; } publicString getRemark(){ returnremark; } publicvoidsetRemark(String remark){ this.remark =remark; } publiclonggetGroupid(){ returngroupid; } publicvoidsetGroupid(longgroupid){ this.groupid =groupid; } @Override publicString toString(){ return"WechatUser{"+ "name='"+name +'\''+ ", subscribe="+subscribe + ", openid='"+openid +'\''+ ", nickname='"+nickname +'\''+ ", sex="+sex + ", language='"+language +'\''+ ", city='"+city +'\''+ ", province='"+province +'\''+ ", country='"+country +'\''+ ", headimgurl='"+headimgurl +'\''+ ", subscribe_time="+subscribe_time + ", unionid='"+unionid +'\''+ ", remark='"+remark +'\''+ ", groupid="+groupid + '}'; }}
- 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
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
发送http请求的HttpUtil 和获取accessToken的AccessTokenUtil 和json字符串到对象的转换