网上搜资料时,网友都说官方文档太垃圾了不易看懂,如何如何的。现在个人整理了一个通俗易懂易上手的,希望可以帮助到刚接触微信接口的你。
请看流程图!看懂图,就懂了一半了:
其实整体流程大体只需三步:用户点击登录按钮(其实就相当于一个链接) ---》 用户点击授权登录 ----》 实现获取用户信息代码。
然后获取用户信息代码只需三步:获取code ----》 通过code获取access_token和openId ---》 通过access_token和openId获取用户信息(包含union)。
以上便是整体套路,当然官网上也有,但具体如何实现呢?
不着急,咱们一步一步来!
第一步:微信登录按钮
它其实就是一个连接,不过想得到这个链接,有一点点麻烦。
1、设置。 微信公众平台---》接口权限---》网页授权---》修改 ---》设置网页授权域名(域名,不含http://),其实就是微信调你的java方法的项目路径或项目域名,如:www.zzff.net/pp ---》点击设置后弹出页面(大致意思,将MP_verify_31qRIDcjN8ZD1lVJ.txt放在你项目路径下面,如:www.ffzz.net/pp/MP_verify_31qRIDcjN8ZD1lVJ.txt 能访问到) ---》点击确认,授权回调页面域名设置成功!
2、拼链接。 https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxx公众号IDxxxxx & redirect_uri = 授权回调页面域名/你的action(即微信授权后跳向的地址)
& response_type=code(固定的) & scope = snsapi_userinfo(或者snsapi_base默认授权) & state=STATE#wechat_redirect
如:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
这个链接中参数的具体含义,官方解释如下:
参数 | 是否必须 | 说明 |
appid |
是 |
公众号的唯一标识 |
redirect_uri |
是 |
授权后重定向的回调链接地址,请使用urlencode对链接进行处理 |
response_type |
是 |
返回类型,请填写code |
scope |
是 |
应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息) |
state |
否 |
重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节 |
#wechat_redirect |
是 |
无论直接打开还是做页面302重定向时候,必须带此参数 |
第二步:授权确认登录
这一步最简单,第一步登录链接拼好后,在手机微信中打开,微信便会跳转到确认授权页面,点击确认授权即可。(这一步,不用开发者做处理!)
第三步:获取用户信息 (重点)
这一步便是真正的代码实现的地方。开篇便讲了,它只需三步:获取code ----》 通过code获取access_token和openId ---》 通过access_token和openId获取用户信息。
First: 获取code
Second: 获取网页授权access_token和openId
Third:通过access_token和openId获取用户信息。
关于union机制的细节:如果开发者需要公众号微信登录和APP微信登录共用一个微信ID,那个就需要union机制了。其实很简单,只需在微信开放平台(open.weixin.qq.com)绑定公众号,获取用户信息时就会返回union字段。
具体代码实现为:实体 ---- 方法 --- 工具
实体Oauth2Token:
package com.wfcm.wxUitls;
/** * 类名: WeixinOauth2Token </br> * 描述: 网页授权信息 </br> * 创建时间: 2015-11-27 </br> * 发布版本:V1.0 </br> */ public class Oauth2Token { // 网页授权接口调用凭证 private String accessToken; // 凭证有效时长 private int expiresIn; // 用于刷新凭证 private String refreshToken; // 用户标识 private String openId; // 用户授权作用域 private String scope;
public String getAccessToken() { return accessToken; }
public void setAccessToken(String accessToken) { this.accessToken = accessToken; }
public int getExpiresIn() { return expiresIn; }
public void setExpiresIn(int expiresIn) { this.expiresIn = expiresIn; }
public String getRefreshToken() { return refreshToken; }
public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; }
public String getOpenId() { return openId; }
public void setOpenId(String openId) { this.openId = openId; }
public String getScope() { return scope; }
public void setScope(String scope) { this.scope = scope; } }
|
实体SNSUserInfo:
package com.wfcm.wxUitls;
import java.util.List;
/** * 类名: SNSUserInfo </br> * 描述: 通过网页授权获取的用户信息 </br> * 开发人员: wzf </br> * 创建时间: 2015-11-27 </br> * 发布版本:V1.0 </br> */ public class SNSUserInfo { // 用户标识 private String openId; // 用户昵称 private String nickname; // 性别(1是男性,2是女性,0是未知) private int sex; // 国家 private String country; // 省份 private String province; // 城市 private String city; // 用户头像链接 private String headImgUrl; // 用户特权信息 private List<String> privilegeList; private String unionid;
public String getUnionid() { return unionid; }
public void setUnionid(String unionid) { this.unionid = unionid; }
public String getOpenId() { return openId; }
public void setOpenId(String openId) { this.openId = openId; }
public String getNickname() { return nickname; }
public void setNickname(String nickname) { this.nickname = nickname; }
public int getSex() { return sex; }
public void setSex(int sex) { this.sex = sex; }
public String getCountry() { return country; }
public void setCountry(String country) { this.country = country; }
public String getProvince() { return province; }
public void setProvince(String province) { this.province = province; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getHeadImgUrl() { return headImgUrl; }
public void setHeadImgUrl(String headImgUrl) { this.headImgUrl = headImgUrl; }
public List<String> getPrivilegeList() { return privilegeList; }
public void setPrivilegeList(List<String> privilegeList) { this.privilegeList = privilegeList; } }
|
方法WxController(其中authorize() 方法就是请求第一步获取的链接):
package com.wfcm.controller;
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Formatter; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import org.apache.http.client.utils.URLEncodedUtils; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.util.IOUtils; import com.wfcm.annotation.IgnoreSign; import com.wfcm.annotation.IgnoreToken; import com.wfcm.entity.WfMemberEntity; import com.wfcm.service.WfMemberService; import com.wfcm.service.WfMemberSessionService; import com.wfcm.utils.FastJSONUtils; import com.wfcm.utils.NetUtil; import com.wfcm.utils.R; import com.wfcm.wxUitls.AccessToken; import com.wfcm.wxUitls.JsapiTicket; import com.wfcm.wxUitls.Oauth2Token; import com.wfcm.wxUitls.SNSUserInfo;
@Controller @RequestMapping("/wx") @ResponseBody public class WxController {
private static Logger log = LoggerFactory.getLogger(WxController.class); /** * 向指定URL发送GET方法的请求 * * @param url * 发送请求的URL * @param param * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return URL 所代表远程资源的响应结果 * * 用户同意授权,获取code */ @RequestMapping("/authorize") @ResponseBody @IgnoreToken public static R authorize() { String appid = "wxbb000000000e"; //String uri ="wftest.zzff.net/wx/weixinLogin"; String uri = urlEncodeUTF8("wftest.zzff.net/api/wx/weixinLogin"); String result = ""; BufferedReader in = null; try { String urlNameString = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+appid+"&redirect_uri="+uri+"&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect"; URL realUrl = new URL(urlNameString); // 打开和URL之间的连接 URLConnection connection = realUrl.openConnection(); // 设置通用的请求属性 connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); // 建立实际的连接 connection.connect(); // 获取所有响应头字段 Map<String, List<String>> map = connection.getHeaderFields(); // 遍历所有的响应头字段 for (String key : map.keySet()) { System.out.println(key + "--->" + map.get(key)); } // 定义 BufferedReader输入流来读取URL的响应 in = new BufferedReader(new InputStreamReader( connection.getInputStream())); String line =null; while ((line = in.readLine()) != null) { result += line; } /* com.alibaba.fastjson.JSONObject jsonObj= FastJSONUtils.getJSONObject(result); String access_token = jsonObj.getString("access_token"); long expires_in = Long.valueOf(jsonObj.getString("expires_in")); */ } catch (Exception e) { System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } // 使用finally块来关闭输入流 finally { try { if (in != null) { in.close(); } } catch (Exception e2) { e2.printStackTrace(); } } return R.ok(result); } @RequestMapping("/weixinLogin") @ResponseBody @IgnoreToken @IgnoreSign public void weixinLogin(HttpServletRequest request,HttpServletResponse response) throws Exception { // 用户同意授权后,能获取到code Map<String, String[]> params = request.getParameterMap();//针对get获取get参数 String[] codes = params.get("code");//拿到code的值 String code = codes[0];//code //String[] states = params.get("state"); //String state = states[0];//state System.out.println("****************code:"+code); // 用户同意授权 if (!"authdeny".equals(code)) { // 获取网页授权access_token Oauth2Token oauth2Token = getOauth2AccessToken("wxb0000000000e", "4c22222233333335555a9", code); System.out.println("***********************************oauth2Token信息:"+oauth2Token.toString()); // 网页授权接口访问凭证 String accessToken = oauth2Token.getAccessToken(); // 用户标识 String openId = oauth2Token.getOpenId(); // 获取用户信息 SNSUserInfo snsUserInfo = getSNSUserInfo(accessToken, openId); System.out.println("***********************************用户信息unionId:"+snsUserInfo.getUnionid()+"***:"+snsUserInfo.getNickname()); // 设置要传递的参数 //具体业务start
//具体业务end
String url = "http://wftest.zzff.net/#/biddd?from=login&tokenId="+snsUserInfo.getUnionid(); response.sendRedirect(url); return ; } }
/** * 获取网页授权凭证 * * @param appId 公众账号的唯一标识 * @param appSecret 公众账号的密钥 * @param code * @return WeixinAouth2Token */ public static Oauth2Token getOauth2AccessToken(String appId, String appSecret, String code) { Oauth2Token wat = null; // 拼接请求地址 String requestUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code"; requestUrl = requestUrl.replace("APPID", appId); requestUrl = requestUrl.replace("SECRET", appSecret); requestUrl = requestUrl.replace("CODE", code); // 获取网页授权凭证 com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(NetUtil.get(requestUrl)); if (null != jsonObject) { try { wat = new Oauth2Token(); wat.setAccessToken(jsonObject.getString("access_token")); wat.setExpiresIn(jsonObject.getInteger("expires_in")); wat.setRefreshToken(jsonObject.getString("refresh_token")); wat.setOpenId(jsonObject.getString("openid")); wat.setScope(jsonObject.getString("scope")); } catch (Exception e) { wat = null; int errorCode = jsonObject.getInteger("errcode"); String errorMsg = jsonObject.getString("errmsg"); log.error("获取网页授权凭证失败 errcode:{} errmsg:{}", errorCode, errorMsg); } } return wat; } /** * 通过网页授权获取用户信息 * * @param accessToken 网页授权接口调用凭证 * @param openId 用户标识 * @return SNSUserInfo */ public static SNSUserInfo getSNSUserInfo(String accessToken, String openId) { SNSUserInfo snsUserInfo = null; // 拼接请求地址 String requestUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID"; requestUrl = requestUrl.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId); // 通过网页授权获取用户信息 com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(NetUtil.get(requestUrl));
if (null != jsonObject) { try { snsUserInfo = new SNSUserInfo(); // 用户的标识 snsUserInfo.setOpenId(jsonObject.getString("openid")); // 昵称 snsUserInfo.setNickname(jsonObject.getString("nickname")); // 性别(1是男性,2是女性,0是未知) snsUserInfo.setSex(jsonObject.getInteger("sex")); // 用户所在国家 snsUserInfo.setCountry(jsonObject.getString("country")); // 用户所在省份 snsUserInfo.setProvince(jsonObject.getString("province")); // 用户所在城市 snsUserInfo.setCity(jsonObject.getString("city")); // 用户头像 snsUserInfo.setHeadImgUrl(jsonObject.getString("headimgurl")); // 用户特权信息 List<String> list = JSON.parseArray(jsonObject.getString("privilege"),String.class); snsUserInfo.setPrivilegeList(list); //与开放平台共用的唯一标识,只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。 snsUserInfo.setUnionid(jsonObject.getString("unionid")); } catch (Exception e) { snsUserInfo = null; int errorCode = jsonObject.getInteger("errcode"); String errorMsg = jsonObject.getString("errmsg"); log.error("获取用户信息失败 errcode:{} errmsg:{}", errorCode, errorMsg); } } return snsUserInfo; } /** * 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; } private static String byteToHex(final byte[] hash) { Formatter formatter = new Formatter(); for (byte b : hash) { formatter.format("%02x", b); } String result = formatter.toString(); formatter.close(); return result; }
private static String create_nonce_str() { return UUID.randomUUID().toString(); }
private static String create_timestamp() { return Long.toString(System.currentTimeMillis() / 1000); } }
|
工具NetUtil:
package com.wfcm.utils;
import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map;
import org.apache.commons.httpclient.NameValuePair; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicNameValuePair;
/** * Created by Song on 2016/11/28. * 基于HttpClient提供网络访问工具 */ public final class NetUtil { public static CloseableHttpClient httpClient = HttpClientBuilder.create().build();
/** * get请求获取String类型数据 * @param url 请求链接 * @return */ public static String get(String url){ StringBuffer sb = new StringBuffer(); HttpGet httpGet = new HttpGet(url); try { HttpResponse response = httpClient.execute(httpGet); //1
HttpEntity entity = response.getEntity(); InputStreamReader reader = new InputStreamReader(entity.getContent(),"utf-8"); char [] charbufer; while (0<reader.read(charbufer=new char[10])){ sb.append(charbufer); } }catch (IOException e){//1 e.printStackTrace(); }finally { httpGet.releaseConnection(); } return sb.toString(); }
/** * post方式请求数据 * @param url 请求链接 * @param data post数据体 * @return */ @SuppressWarnings("unchecked") public static String post(String url, Map<String,String> data){ StringBuffer sb = new StringBuffer(); HttpPost httpPost = new HttpPost(url); List<NameValuePair> valuePairs = new ArrayList<NameValuePair>(); if(null != data) { for (String key : data.keySet()) { valuePairs.addAll((Collection<? extends NameValuePair>) new BasicNameValuePair(key, data.get(key))); } } try { httpPost.setEntity(new UrlEncodedFormEntity((List<? extends org.apache.http.NameValuePair>) valuePairs)); HttpResponse response = httpClient.execute(httpPost); HttpEntity httpEntity = response.getEntity(); BufferedInputStream bis = new BufferedInputStream(httpEntity.getContent()); byte [] buffer; while (0<bis.read(buffer=new byte[128])){ sb.append(new String(buffer,"utf-8")); } }catch (UnsupportedEncodingException e){//数据格式有误 e.printStackTrace(); }catch (IOException e){//请求出错 e.printStackTrace(); }finally { httpPost.releaseConnection(); } return sb.toString(); } }
|
R类:
package com.wfcm.utils;
import java.util.HashMap; import java.util.Map;
/** * 返回数据 * * @author xlf * @email xlfbe696@gmail.com * @date 2017年4月19日 上午11:58:56 */ public class R extends HashMap<String, Object> { private static final long serialVersionUID = 1L;
public static final String SUCCESS = "success"; public static final String EXCEPTION = "exception"; public static final Integer SUCCESSCODE = 0; public static final Integer EXCEPTIONCODE = 500;
public R() { put("errCode", 0); put("msg", SUCCESS); } public R(int code, String msg){ put("errCode", code); put("msg", msg); }
public static R error() { return error(500, "未知异常,请联系管理员"); }
public static R error(String msg) { return error(500, msg); }
public static R error(int code, String msg) { R r = new R(); r.put("errCode", code); r.put("msg", msg); return r; }
public static R ok(String msg) { R r = new R(); r.put("msg", msg); return r; }
public static R ok(Map<String, Object> map) { R r = new R(); r.putAll(map); return r; }
public static R ok() { return new R(); }
public R put(String key, Object value) { super.put(key, value); return this; } }
|
OK,大功告成!整体流程已经搭建起来,读懂了这些代码差不多就明白了整个流程了,然后再看官方文档,你会觉得读起来很顺畅,而不是刚开始那种味同嚼蜡的感觉。你只需再根据官方文档仔细检查检查流程,有没有需要完善的地方,就可以了。
还等什么呢,赶快敲实现功能吧!!!
所有内容皆为个人总结或转载别人的文章,只为学习技术。 若您觉得文章有用,欢迎点赞分享! 若无意对您的文章造成侵权,请您留言,博主看到后会及时处理,谢谢。