很多IT搬磚員,磕磕碰碰,都比較生怕遇到自己未接觸過的東西,但是作為開發,遲早有一天,某個上司跟你說,某個需求,需要接入微信公衆号,需要擷取微信使用者資訊。
雖然說微信提供了相關的文檔,但是免不了還是很多初學者看了又看還是一臉懵逼(當初我也是),是以我今天來出一份小白都能看懂的教程,甚至你轉行過來跟着一步步來,你也能學會,怎麼擷取微信使用者資訊。
前期準備工作Part 1 申請微信公衆号測試号(如果你有直接可以用的微信公衆号或者訂閱号,你可以不理這個Part 1):
申請完成後, 登入,點選頁面左側的開發-開發者工具:(我們來申請一個微信公衆平台測試帳号,因為我們絕大多數人沒有已認證的微信公衆号可以用)
進入到公衆平台測試帳号界面,
接下來你需要做填寫的有:
1.跟微信碰頭的 驗證Token位址以及Token參數值
這個是做什麼的?怎麼達到驗證一說?
就是所填寫的URL ,就是一個接口,然後下面填寫的Token值是個自定義參數。
在這個接口暢通的情況下,點選送出,微信這邊就會帶着時間戳timestamp、随機數nonce還有填的token值,使用微信的加密算法(後面代碼會有介紹)進行簽名,生成一個signature,然後還有額外的一個随機字元串echostr,去通路URL對應的接口。
接口裡面需要用簽名驗證的邏輯函數(後面代碼都會有介紹,這裡先簡單講講流程),拿到微信傳過來的時間戳timestamp、随機數nonce加上我們自己自定義的token值,也使用微信的加密算法進行簽名,生成一個signature;然後和微信傳過來的進行比對,如果一樣,OK,碰頭确認成功,把微信發過來的随機字元串echostr傳回去即可;如果不一樣,不好意思,碰頭不成功,傳回null即可(基本就是接口不通、生成簽名方法有誤)。
2.JS接口安全域名
這個就是一個你即将要編碼的項目,釋出部署後,提供接口對應的域名。
3.網頁授權域名
這裡一樣,也是項目的域名。
好了,總體我們看了一下,我們需要填寫的三個子產品的資訊,都有一個非常關鍵的東西,域名。
那怎麼有域名呢?本地寫demo的小夥伴眉頭不由自主一皺?
前期準備工作Part 2 内網穿透 整一個域名出來(如果你有已經可以用的域名,那麼你可以不理這個Part 2)
具體内網穿透,将本地127.0.0.1 對應一個外網可以通路的域名。 本來我是寫了具體操作示例的,但是涉嫌打廣告,導緻文章被下架了,是以這個part2,就隻能删除了。 請了解。
(微信公衆平台測試号的資訊還沒填?别急)
接下來我們建一個java項目,
第一個工具類, 用來調微信接口的,HttpClientUtil.java:
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @Author : JCccc
* @CreateTime : 2019/8/2
* @Description :
**/
public class HttpClientUtil {
public static String doGet(String url, Map<String, String> param) {
// 建立Httpclient對象
CloseableHttpClient httpclient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 建立uri
URIBuilder builder = new URIBuilder(url);
if (param != null) {
for (String key : param.keySet()) {
builder.addParameter(key, param.get(key));
}
}
URI uri = builder.build();
// 建立http GET請求
HttpGet httpGet = new HttpGet(uri);
// 執行請求
response = httpclient.execute(httpGet);
// 判斷傳回狀态是否為200
if (response.getStatusLine().getStatusCode() == 200) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doGet(String url) {
return doGet(url, null);
}
public static String doPost(String url, Map<String, String> param) {
// 建立Httpclient對象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 建立Http Post請求
HttpPost httpPost = new HttpPost(url);
// 建立參數清單
if (param != null) {
List<NameValuePair> paramList = new ArrayList<>();
for (String key : param.keySet()) {
paramList.add(new BasicNameValuePair(key, param.get(key)));
}
// 模拟表單
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
httpPost.setEntity(entity);
}
// 執行http請求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
public static String doPost(String url) {
return doPost(url, null);
}
public static String doPostJson(String url, String json) {
// 建立Httpclient對象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 建立Http Post請求
HttpPost httpPost = new HttpPost(url);
// 建立請求内容
StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
// 執行http請求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
}
第二個工具類,跟微信碰頭驗證用的,SignUtil.java:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* @Author : JCccc
* @CreateTime : 2019/8/2
* @Description :
**/
public class SignUtil {
private static String token = "weixinCoursexxxxx";//填你自己的
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String[] paramArr = new String[] { token, timestamp, nonce };
Arrays.sort(paramArr);
String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);
String ciphertext = null;
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] digest = md.digest(content.toString().getBytes());
ciphertext = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return ciphertext != null ? ciphertext.equals(signature.toUpperCase()) : false;
}
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
private static String byteToHexStr(byte mByte) {
char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}
}
然後最關鍵的東西,接口,WxLoginController.java:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.springmvc.util.HttpClientUtil;
import com.springmvc.util.SignUtil;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
/**
* @Author : JCccc
* @CreateTime : 2019/8/2
* @Description :
**/
@RestController
@RequestMapping("/wxAuth")
public class WxLoginController {
private static String APPID="xxxxxx";//填你自己的
private static String APPSECRET="xxxxx";//填你自己的
/**
* 用于給微信驗證token
* @param request
* @param response
* @return
* @throws IOException
*/
@RequestMapping("/checkToken")
public void checkToken(HttpServletRequest request,HttpServletResponse response) throws IOException {
// 微信加密簽名
String signature = request.getParameter("signature");
// 時間戳
String timestamp = request.getParameter("timestamp");
// 随機數
String nonce = request.getParameter("nonce");
// 随機字元串
String echostr = request.getParameter("echostr");
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
System.out.println("校驗token成功");
response.getWriter().print(echostr);
}
}
/**
* 用于擷取出回調位址 (引導使用者調用此接口,成功後自動調取回調位址然後取出使用者資訊)
* @param response
* @throws IOException
*/
@RequestMapping("/login")
public void wxLogin(HttpServletResponse response) throws IOException {
//請求擷取code的回調位址
//用線上環境的域名或者用内網穿透,不能用ip
String callBack = "http://3xXXXXXXXi.natappfree.cc/wxAuth/callBack";//域名填你自己的
//請求位址
String url = "https://open.weixin.qq.com/connect/oauth2/authorize" +
"?appid=" + APPID +
"&redirect_uri=" + URLEncoder.encode(callBack) +
"&response_type=code" +
"&scope=snsapi_userinfo" +
"&state=STATE#wechat_redirect";
System.out.println(url);
//重定向
response.sendRedirect(url);
}
/**
* 回調方法
* @param request
* @param response
* @throws IOException
*/
// 回調方法
@RequestMapping("/callBack")
public String wxCallBack(HttpServletRequest request, HttpServletResponse response) throws IOException {
String code = request.getParameter("code");
//擷取access_token
String url = "https://api.weixin.qq.com/sns/oauth2/access_token" +
"?appid=" + APPID +
"&secret=" + APPSECRET +
"&code=" + code +
"&grant_type=authorization_code";
String result = HttpClientUtil.doGet(url);
System.out.println("請求擷取access_token:" + result);
//傳回結果的json對象
JSONObject resultObject = JSON.parseObject(result);
//請求擷取userInfo
String infoUrl = "https://api.weixin.qq.com/sns/userinfo" +
"?access_token=" + resultObject.getString("access_token") +
"&openid=" + resultObject.getString("openid") +
"&lang=zh_CN";
String resultInfo = HttpClientUtil.doGet(infoUrl);
//此時已擷取到userInfo,再根據業務進行處理
System.out.println("請求擷取userInfo:" + resultInfo);
return "hello!";
}
}
好了,到這裡,我們其實已經準備就緒了。 就這麼點代碼足夠了。那麼接下來我們回去補全剛剛需要填的東西。
第一部分:
URL http://+域名+代碼裡面的驗證接口URI
Token 自己随便寫,但是要跟SignUtil.java裡面的token值保持一緻
(!!記得把項目跑起來,這裡填完送出是會調項目接口的!!)
第二部分:
http://+域名
第三部分:
域名
OK,到此已經完全接入完畢了,接下來就是測試了。
先确定,項目已經跑起來了。
測試之前,這是怎麼擷取使用者微信号資訊的。
首先你需要做的是,引導使用者在微信端,記得是微信端,必須在微信端,去調用我們寫的http://3xXXXXX.natappfree.cc/wxAuth/login 接口 。
也就是說使用者點選了我們公衆号某個按鈕,或者說是在微信用戶端不知道在哪裡點選了某個東西, 對應調用的接口是我們的
http://3xXXXXX.natappfree.cc/wxAuth/login 接口。
接下來會發生什麼(建議結合代碼看),
一.我們接口會帶上我們微信公衆号(測試号)的APPID,以及按照規則進行接口URL拼接,通路接口。
二.通路完之後,微信會自動從我們拼接的參數裡面擷取出我們的回調接口,callback,去調用我們的接口(帶着code值)。
三.我們的回調接口裡面擷取出微信送過來的code值又進行接口URL拼接,通路接口,去擷取access_token和使用者的openid等;
四.最後再進行接口URL拼接,通路接口,去擷取出使用者的各種資訊,昵稱、openid、頭像位址等等。
好像說了這麼多,我相信很多人還是不會怎麼弄,那麼我們來模拟一個場景順便來測試下最終結果(很多人看不到使用者資訊擷取的一瞬間還是會有疑慮的,那麼我們一起來測試下)。
我們來在我們的公衆号上面,建一個菜單,菜單裡面放入我們的 ‘引導接口‘, 然後我們用微信關注下公衆号,點選下,就明白了了。
我們用線上接口調試工具,去在微信公衆号測試号上面建一個菜單(當然那些用自己已有公衆号的夥伴不需要這樣,直接建個菜單,配置下連結就行)
然後,
把access_token複制下,
接着,創一個菜單,把我們的引導接口放在一個二級菜單裡面,點選
OK,我們的公衆測試号,菜單已經建立完了,用微信關注下公衆号(測試号在頁面掃描關注):