本文旨在提供一個Java開發微信公眾號服務器的一個入門級別教程,由于本人水平有限,文中若出現錯誤歡迎同學們指正!作者郵箱luo.xuelin@nclantuo.com。本文歡迎轉載,但請注明出處來自SteinsGate博客!
俗話說的好,什么教程都比不上官方開發者文檔。所以,微信開發者開發文檔在此:開發者文檔
目錄
<a name="part1">一、網頁授權獲取用戶信息</a>
1、網頁授權的說明
網頁授權 即用戶在微信客戶端訪問第三方網頁時,可以通過網頁授權機制,來獲取用戶的基本信息,從而實現業務邏輯。例如登陸、注冊等。
openid 即用戶相對于一個公眾號來說的唯一標識,可以理解成在此公眾號內的用戶ID
配置回調域名
在微信公眾號使用網頁授權之前,開發者需要到公眾平臺的 開發 > 接口權限 菜單中的網頁授權獲取用戶基本信息欄配置授權回調域名,且只需要填寫域名,不需要加http://等協議頭。
回調域名必須為全域名。比如配置的域名為www.baidu.com,配置以后此域名下的頁面http://www.baidu.com/code.html、http://www.baidu.com/test/login.html都可以進行網頁授權。但http://tieba.baidu.com之類的無法進行網頁授權。
網頁授權兩種scope的區別
以 snsapi_base 為scope發起的網頁授權,是用來獲取進入頁面用戶的openid的,并且是靜默授權并自動跳轉到回調頁面。用戶并不會感知到授權過程直接進入了第三方的頁面。
以 snsapi_userinfo 為scope發起的網頁授權,是用來獲取用戶的基本信息的。但這種授權需要用戶手動確認,并且由于用戶確認過,所以開發者也可以獲取到未關注用戶的基本信息。
特殊場景下的靜默授權
上面已提到,對于已snsapi_base為scope的網頁授權,就靜默授權,用戶不會感知到。
對于已關注公眾號的用戶,如果是從公眾號的會話或者是菜單進入的網頁授權頁面,即使scope是snsapi_userinfo,也是靜默授權。
網頁授權access_token與普通access_token的區別
網頁授權是通過Oauth2.0機制實現的,在用戶授權給公眾號后,公眾號可以獲取到一個網頁授權特有的接口調用憑據(網頁授權access_token),通過網頁授權access_token可以進行授權后接口調用,如獲取用戶基本信息。
其他的微信接口,需要通過 獲取access_token 接口來獲取普通的access_token
2、網頁授權的流程
網頁授權的流程
網頁授權的流程大致可以分為以下五步:
- 生成網頁授權URL
- 引導用戶進入授權頁面并同意授權,獲取code
- 通過code換去網頁授權access_token
- 如果需要,開發者可以刷新網頁授權access_token,避免過期
- 通過網頁授權access_token和openid獲取用戶基本信息
2.1、生成網頁授權URL
接口地址
https://open.weixin.qq.com/connect/oauth2/authorize?
參數 | 是否必須 | 說明 |
---|---|---|
appid | 是 | 公眾號的唯一標識 |
redirect_uri | 是 | 授權后重定向的回調鏈接地址,請使用urlencode對鏈接進行處理 |
response_type | 是 | 返回類型,填code |
scope | 是 | snsapi_userinfo、snsapi_base |
state | 否 | 重定向后會帶上state參數,開發者可以填寫a-zA-Z0-9的參數值,最多128字節 |
wechat_redirect | 是 | 無論直接打開還是做頁面302重定向時候,必須帶此參數 |
一個完整的網頁授權URL如下:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxd91aa2e2fab6&redirect_uri=http%3A%2F%2Fwww.baidu.com&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
當用戶點進入URL時就會進入授權界面,如下:
2.2、用戶同意授權后
如果用戶同意授權,頁面就會跳轉到redirect_uri
code:code作為換取access_token的票據,每次用戶授權帶上的code是不一樣的,code只能用一次,且5分鐘未使用自動過期。
跳轉到重定向地址后,開發者可以直接在請求參數中獲取到code。
2.3、code換取access_token
此處獲取到的access_token與其他接口需要使用到的access_token是不同的。如果網頁授權使用的scope是snsapi_base,則在此處獲取到access_token的同時,也獲取到了openid,snsapi_base的流程也到此結束。
請求地址
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 |
返回說明
請求正確時的返回數據
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE"
}
參數 | 說明 |
---|---|
access_token | 網頁授權接口調用憑證 |
expires_in | access_token接口調用憑證超時時間,單位(秒) |
refresh_token | 用戶刷新access_token需要用到的token |
openid | 用戶唯一標識 |
scope | 用戶授權的作用域 |
示例代碼
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
Map<String, String> params = new HashMap<String, String>();
params.put("APPID", appid);
params.put("SECRET", appsecret);
params.put("CODE", code);
JSONObject json = HttpUtil.get(StringFormat.format(url, params));
if(json != null){
boolean isErr = json.containsKey("errcode");
if(isErr){//請求失敗
String errCode = json.getString("errcode");
String errMsg = json.getString("errmsg");
}else{
String accessToken = json.getString("access_token");
int expires = json.getInt("expires_in");
String refreshToken = json.getString("refresh_token");
String openid = json.getString("openid");
String scope = json.getString("scope");
}
}
2.4、驗證access_token是否有效
請求地址
https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID
參數說明
參數 | 是否必須 | 說明 |
---|---|---|
access_token | 是 | 網頁授權接口調用憑證 |
openid | 是 | 用戶的唯一標識 |
返回說明
請求正確時的返回數據
{ "errcode":0,"errmsg":"ok"}
示例代碼
String url = "https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID";
Map<String, String> params = new HashMap<String, String>();
params.put("ACCESS_TOKEN", access_token);
params.put("OPENID", openid);
JSONObject json = HttpUtil.get(StringFormat.format(url, params));
if(json != null){
String errCode = json.getString("errcode");
String errMsg = json.getString("errmsg");
}
2.5、刷新access_token(若有必要)
由于access_token有效期比較短,當access_token失效后,可以使用refresh_token進行刷新,refresh_token的有效期為30天。refresh_token失效后需要用戶重新授權。
請求地址
https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
參數說明
參數 | 是否必須 | 說明 |
---|---|---|
appid | 是 | 公眾號的唯一標識 |
grant_type | 是 | 填寫refresh_token |
refresh_token | 是 | 填寫獲取到的refresh_token參數 |
返回說明
請求正確時的返回數據
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE"
}
參數 | 說明 |
---|---|
access_token | 網頁授權接口調用憑證 |
expires_in | access_token接口調用憑證超時時間,單位(秒) |
refresh_token | 用戶刷新access_token需要用到的token |
openid | 用戶唯一標識 |
scope | 用戶授權的作用域 |
示例代碼
String url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
Map<String, String> params = new HashMap<String, String>();
params.put("APPID", appid);
params.put("REFRESH_TOKEN", refresh_token);
JSONObject json = HttpUtil.get(StringFormat.format(url, params));
if(json != null){
boolean isErr = json.containsKey("errcode");
if(isErr){//請求失敗
String errCode = json.getString("errcode");
String errMsg = json.getString("errmsg");
}else{
String accessToken = json.getString("access_token");
int expires = json.getInt("expires_in");
String refreshToken = json.getString("refresh_token");
String openid = json.getString("openid");
String scope = json.getString("scope");
}
}
2.6、獲取用戶信息(當scope為snsapi_userinfo)
請求地址
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
參數說明
參數 | 是否必須 | 說明 |
---|---|---|
access_token | 是 | 網頁授權接口調用憑證 |
openid | 是 | 用戶的唯一標識 |
lang | 是 | 返回國家地區語言版本,zh_CN 簡體,zh_TW 繁體,en 英語 |
返回說明
請求正確時的返回數據
{
"openid":"OPENID",
"nickname":"NICKNAME",
"sex":"1",
"province":"PROVINCE"
"city":"CITY",
"country":"COUNTRY",
"headimgurl":"http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ
4eMsv84eavHiaiceqxibJxCfHe/46",
"privilege":[ "PRIVILEGE1","PRIVILEGE2"],
"unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
參數 | 說明 |
---|---|
openid | 用戶的唯一標識 |
nickname | 用戶昵稱 |
sex | 用戶的性別,值為1時是男性,值為2時是女性,值為0時是未知 |
province | 用戶個人資料填寫的省份 |
city | 普通用戶個人資料填寫的城市 |
country | 國家,如中國為CN |
headimgurl | 用戶頭像,最后一個數值代表正方形頭像大小(有0、46、64、96、132數值可選,0代表640*640正方形頭像),用戶沒有頭像時該項為空。若用戶更換頭像,原有頭像URL將失效。 |
privilege | 用戶特權信息,json 數組,如微信沃卡用戶為(chinaunicom) |
unionid | 只有在用戶將公眾號綁定到微信開放平臺帳號后,才會出現該字段。 |
示例代碼
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("https://api.weixin.qq.com/sns/userinfo?access_token="+ access_token +"&openid="+ openid +"&lang="+ lang +"");
CloseableHttpResponse response = httpclient.execute(httpget);
JSONObject json = null;
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
HttpEntity entity = response.getEntity();
InputStream inStream = entity.getContent(); // 獲取請求參數數據
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
outStream.close();
inStream.close();
String j = new String(outStream.toByteArray(), "utf-8");
json = JSONObject.fromObject(j);
System.out.println(result);
}
} finally {
response.close();
}
if(json != null){
boolean isErr = json.containsKey("errcode");
if(isErr){//請求失敗
System.out.println(json);
}else{
System.out.println(json);
}
}
<a name="part2">二、根據openid獲取用戶信息</a>
使用此接口的前提是已有用戶的openid作為參數。
需要注意的是,此處用到的access_token與網頁授權中用到的access_token是完全不同的。此處access_token的獲取方式參見Java微信公眾號開發 - 開始接入
接口地址
https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
參數說明
參數 | 是否必須 | 說明 |
---|---|---|
access_token | 是 | 微信接口全局調用憑證 |
openid | 是 | 用戶的唯一標識 |
lang | 是 | 返回國家地區語言版本,zh_CN 簡體,zh_TW 繁體,en 英語 |
返回說明
請求正確時的返回數據
{
"subscribe": 1,
"openid": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M",
"nickname": "Band",
"sex": 1,
"language": "zh_CN",
"city": "廣州",
"province": "廣東",
"country": "中國",
"headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4
eMsv84eavHiaiceqxibJxCfHe/0",
"subscribe_time": 1382694957,
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
"remark": "",
"groupid": 0,
"tagid_list":[128,2]
}
參數 | 說明 |
---|---|
subscribe | 用戶是否訂閱該公眾號標識,值為0時,代表此用戶沒有關注該公眾號,拉取不到其余信息。 |
openid | 用戶的標識,對當前公眾號唯一 |
nickname | 用戶的昵稱 |
sex | 用戶的性別,值為1時是男性,值為2時是女性,值為0時是未知 |
city | 用戶所在城市 |
country | 用戶所在國家 |
province | 用戶所在省份 |
language | 用戶的語言,簡體中文為zh_CN |
headimgurl | 用戶頭像,最后一個數值代表正方形頭像大?。ㄓ?、46、64、96、132數值可選,0代表640*640正方形頭像),用戶沒有頭像時該項為空。若用戶更換頭像,原有頭像URL將失效。 |
subscribe_time | 用戶關注時間,為時間戳。如果用戶曾多次關注,則取最后關注時間 |
unionid | 只有在用戶將公眾號綁定到微信開放平臺帳號后,才會出現該字段。 |
remark | 公眾號運營者對粉絲的備注,公眾號運營者可在微信公眾平臺用戶管理界面對粉絲添加備注 |
groupid | 用戶所在的分組ID(兼容舊的用戶分組接口) |
tagid_list | 用戶被打上的標簽ID列表 |
示例代碼
String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=";
url = url + access + "&openid=" + openId + "&lang=" + lang;
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet(url);
CloseableHttpResponse response = httpclient.execute(httpget);
JSONObject json = null;
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
HttpEntity entity = response.getEntity();
InputStream inStream = entity.getContent(); // 獲取請求參數數據
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
outStream.close();
inStream.close();
String j = new String(outStream.toByteArray(), "utf-8");
json = JSONObject.fromObject(j);
}
} finally {
response.close();
}
if(json != null){
boolean isErr = json.containsKey("errcode");
if(isErr){//請求失敗
System.out.println(json);
}else{
System.out.println(json);
}
}