微信公眾號測試號開發小結
微信公眾號踩坑之旅
2019-02-20 15:26:27 By Magina
開發過程中使用的是微信測試公眾號
線上申請:
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
第二步準備工作.獲取開發環境的影射外網地址:ngrok百度自行下載,
開啟ngrok.exe -> ngrok http 8080 // 此處同樣可以指定其他埠.例如8087
此時:外網穿透已經成功.http和https理論上都可以使用.我使用的是http.
下面去測試號 頁面查詢網頁授權域名: 注意.不帶http://
再設定js介面域名
此時頁面的配置基本完成.我們需要接入到 微信介面配置資訊:
微信提供一系列的規則.測試號相對簡單,只需提供介面url和token,token自定義.需要和我們後臺專案設定的一致.url指向後臺專案的介面校驗.
專案搭建不多做介紹.
官方提供資訊:
開發者提交資訊後,微信伺服器將傳送GET請求到填寫的伺服器地址URL上,GET請求攜帶引數如下表所示:
signature
微信加密簽名,signature結合了開發者填寫的token引數和請求中的timestamp引數、nonce引數。
timestamp
時間戳
nonce
隨機數
echostr
隨機字串
開發者通過檢驗signature對請求進行校驗(下面有校驗方式)。若確認此次GET請求來自微信伺服器,請原樣返回echostr引數內容,則接入生效,成為開發者成功,否則接入失敗。加密/校驗流程如下:
1)將token、timestamp、nonce三個引數進行字典序排序
2)將三個引數字串拼接成一個字串進行sha1加密
3)開發者獲得加密後的字串可與signature對比,標識該請求來源於微信
專案程式碼controller:
// token
private final String token = "cass";
@GetMapping(value = "/checkSignature")
public void testInfo(HttpServletRequest request, HttpServletResponse response) throws Exception {
log.info("=============================進入Wechat==================================");
log.info(request.toString());
System.out.println("開始簽名校驗");
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
//排序
String sortString = sort(token, timestamp, nonce);//按照微信官方文件提供的規則 組合String
//加密
String mytoken = Decript.SHA1(sortString);
//校驗簽名
if (mytoken != null && mytoken != "" && mytoken.equals(signature)) {
System.out.println("簽名校驗通過。");
response.getWriter().print(echostr); //如果檢驗成功輸出echostr,微信伺服器接收到此輸出,才會確認檢驗完成。
}
}
public static String sort(String token, String timestamp, String nonce) {
String[] strArray = {token, timestamp, nonce};
Arrays.sort(strArray);
StringBuilder sbuilder = new StringBuilder();
for (String str : strArray) {
sbuilder.append(str);
}
return sbuilder.toString();
}
Decript(sha1加密類):
public class Decript {
public static String SHA1(String decript) {
try {
MessageDigest digest = MessageDigest
.getInstance("SHA-1");
digest.update(decript.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 位元組陣列轉換為 十六進位制 數
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
}
很簡單理解.微信官方根據你給的token會生成一個簽名.然後引數給開發者.開發者也必須生存一個簽名.簽名equals相等.ok.你即可以成為開發者
專案url:http://1527ade6.ngrok.io/Wechat/checkSignature
坑: 這個url裡面一定不能帶埠號.就算用nginx配置過也需要改掉.專案裡被坑過
token:cass
此時.開發者賬號已經接入成功了.
我們可以先通過微信官方的api介面 測試一些功能.比如自定義選單跳轉後臺
介面api:https://mp.weixin.qq.com/debug/cgi-bin/apiinfo?
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wx00ab3357d8686b43&secret=39d83b4cad24fdcf519bb998c9b9e30f
返回結果:
* 200 OK
* Connection: close
* Date: Wed, 20 Feb 2019 07:36:19 GMT
* Content-Type: application/json; encoding=utf-8
* Content-Length: 173
*
{
"access_token": "18_NWcKxgaJP6EnY7QO-QuJSZlxIxpAt6ekwIHm-B7xRkHuKJwOwYra6YW2qRQc_9rCrm2d1o7yURT3Yw5H8ClA6gIfg2wSa9pZAcWsx-NEi5am2T6WU_kDKMk9TYoQZEiAGAHQV",
"expires_in": 7200
}
如果返回非200就是有問題.這步很簡單.如有問題百度
https://api.weixin.qq.com/cgi-bin/menu/get?access_token=18_NWcKxgaJP6EnY7QO-QuJSZlxIxpAt6ekwIHm-B7xRkHuKJwOwYra6YW2qRQc_9rCrm2d1o7yURT3Yw5H8ClA6gIfg2wSa9pZAcWsx-NEi5am2T6WU_kDKMk9TYoQZEiAGAHQV
返回結果:
* 200 OK
* Connection: keep-alive
* Date: Wed, 20 Feb 2019 08:10:35 GMT
* Content-Type: application/json; encoding=utf-8
* Content-Length: 408
*
{
"menu": {
"button": [
{
"type": "view",
"name": "測試案例",
"url": "http://87af154f.ngrok.io/Wechat/testData",
"sub_button": [ ]
},
{
"type": "view",
"name": "搖珠計劃",
"url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=WWWWWredirect_uri=http://87af154f.ngrok.io/Wechat/tologin/userinforesponse_type=codescope=snsapi_userinfostate=STATE#wechat_redirect",
"sub_button": [ ]
}
]
}}
提示:
Request successful
這是我的正常返回值.檢視選單介面
下面來自定義選單.拼接menu的json:
{
"button": [
{
"type": "view",
"name": "測試十三",
"url": "http://1527ade6.ngrok.io/Wechat/testData",
"sub_button": [ ]
},
{
"type": "view",
"name": "計劃十三",
"url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx00ab3357d8686b43&redirect_uri=http://1527ade6.ngrok.io/Wechat/tologin/userinfo&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect",
"sub_button": [ ]
}
]
}
來說下這邊的坑:
1.json不需要帶menu層.只需要button這層
2:url最好帶上http.這裡的http只得是本身跳轉的url和rediect_uri的url.微信的是https不需要管
3.下面的url等下講解
http://1527ade6.ngrok.io/Wechat/testData 對應的是專案介面的localhost:8080/Wechat/testData
程式碼:
@GetMapping(value = "/testData")
public String testInfo() throws Exception {
log.info("測試通過了啊");
return "測試通過了啊";
}
自定義選單建立成功.ok這時候我們拿自己的微信去掃二維碼關注看下結果
這是我手機關注後的選單顯示.
簡單的自定義選單已經完成.
下面說下"計劃十三"這個button的url:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx00ab3357d8686b43&redirect_uri=http://1527ade6.ngrok.io/Wechat/tologin/userinfo&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
這個是微信網頁授權的url.訪問微信的介面,會回撥到http://1527ade6.ngrok.io/Wechat/tologin/userinfo我們這個介面.在這個介面裡面.我們回得到當前使用者的微信基本資訊.從授權說起.
官方文件:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
想從微信按鈕跳轉到我們自定義網頁(第三方).需要使用者對此網頁授權.才能繼續.微信提供靜默授權和手動授權.很簡單.靜默就是你點選了按鈕.微信後臺自動幫你授權了.再跳轉第三方網頁.手動就會彈出一個授權的頁面點選授權才能跳轉第三方網頁.官方給出2中的url:
scope為snsapi_base靜默授權
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx3431726cd6f932f4&redirect_uri=http://pds.movitech.cn:8686/Wechat/tologin/userinfo&response_type=code&scope=snsapi_base&state=123#wechat_redirect
scope為snsapi_userinfo手動授權.
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx3431726cd6f932f4&redirect_uri=http://pds.movitech.cn:8686/Wechat/tologin/userinfo&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
只需要把appid和rediect_uri替換調就可以
介面例項,我的專案例子是這樣:前後端分離.此介面既可以給前端呼叫,也可以給微信呼叫.呼叫授權跳轉介面.如果此使用者不再專案系統裡面用手機號碼註冊過.就返回前端一個false.否則就查詢出planList業務資料給前端.
@GetMapping("/tologin/userinfo")
@ApiOperation(value = "傳入code登入微信介面", notes = "前端傳入code,調微信介面,獲取微信使用者資訊,返回值:status為true則是此微訊號繫結過,返回planList計劃列表.status為flase" +
"微訊號尚未繫結手機號碼,需要跳轉手機驗證碼頁面")
public Response check(HttpServletRequest request, HttpSession session, Map<String, Object> map) throws BusinessException {
log.info("=======================進入tologin/userinfo");
//首先判斷一下session中,是否有儲存著的當前使用者的資訊,有的話,就不需要進行重複請求資訊
WeiXinUser weiXinUser = null;
if (session.getAttribute("currentUser") != null) {
weiXinUser = (WeiXinUser) session.getAttribute("currentUser");
} else {
/**
* 進行獲取openId,必須的一個引數,這個是當進行了授權頁面的時候,再重定向了我們自己的一個頁面的時候,
* 會在request頁面中,新增這個欄位資訊,要結合這個ProjectConst.Get_WEIXINPAGE_Code這個常量思考
*/
String code = request.getParameter("code");
// code = "021Ct0F42we4lP0ohqI42664F42Ct0Fg";
try {
//得到當前使用者的資訊(具體資訊就看weixinUser這個javabean)
weiXinUser = getTheCode(session, code);
//將獲取到的使用者資訊,放入到session中
session.setAttribute("currentUser", weiXinUser);
} catch (Exception e) {
e.printStackTrace();
}
}
if(ObjectUtils.isEmpty(weiXinUser)){
return Response.fail("3999","查詢微信使用者失敗!");
}
List planList = wechatService.getPlanListByOpenId(weiXinUser.getOpenId());
if(CollectionUtils.isEmpty(planList) && null != planList){
map.put("status",true);
map.put("planList",planList);
}else{
map.put("status",false);
}
map.put("weiXinUser", weiXinUser);
return Response.succeed(map);
}
看到這裡很疑惑.為什麼直接從String code = request.getParameter("code");
這個是當進行了授權頁面的時候,再重定向了我們自己的一個頁面的時候,
微信會在request頁面中,新增這個欄位資訊.不信的話我們可以測試:
開啟微信開發者工具.1獲取token.2.獲取button.3複製button的url:https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx00ab3357d8686b43&redirect_uri=http://1527ade6.ngrok.io/Wechat/tologin/userinfo&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
貼上到位址列enter.演示利用微信工具獲取code.最後一個介面的請求引數裡面可以看到
看到上圖console.回撥地址裡面其實就是?code=XXXXXXXXXXXXXXXXXXX去請求我們的介面
其他相關程式碼:
WeiXinUser
/**
* @desc 對於微信使用者本身存在的資訊的一個javabean,不需要在資料庫中進行處理
**/
@Setter
@Getter
public class WeiXinUser {
// 使用者的標識
private String openId;
// 關注狀態(1是關注,0是未關注),未關注時獲取不到其餘資訊
private int subscribe;
// 使用者關注時間,為時間戳。如果使用者曾多次關注,則取最後關注時間
private String subscribeTime;
// 暱稱
private String nickname;
// 使用者的性別(1是男性,2是女性,0是未知)
private int sex;
// 使用者所在國家
private String country;
// 使用者所在省份
private String province;
// 使用者所在城市
private String city;
// 使用者的語言,簡體中文為zh_CN
private String language;
// 使用者頭像
private String headImgUrl;
@Override
public String toString() {
return "WeiXinUser{" +
"openId='" + openId + '\'' +
", 暱稱='" + nickname + '\'' +
", 性別=" + (sex == 1? "男" : "女") +
", 國籍='" + country + '\'' +
", 省份='" + province + '\'' +
", 城市='" + city + '\'' +
'}';
}
}
AccessToken
@Getter
@Setter
public class AccessToken {
// 錯誤code
private Integer errcode;
// 錯誤msg
private String errmsg;
// 獲取到的憑證
private String access_token;
// 憑證有效時間,單位:秒
private Long expires_in;
@Override
public String toString() {
return "AccessToken{" +
"errcode='" + errcode + '\'' +
", errmsg='" + errmsg + '\'' +
", access_token='" + access_token + '\'' +
", expires_in=" + expires_in +
'}';
}
}
ProjectConst:
/**
* @author Magina
* @create 2019-02-20 17:36:12 By Magina
* @desc 專案相關的靜態量
**/
public class ProjectConst {
/**
* 用於獲取當前與微信公眾號互動的使用者資訊的介面(一般是用第一個介面地址)
*/
public static final String GET_WEIXIN_USER_URL = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID";
public final static String GetPageUsersUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
/**
* 用於進行網頁授權驗證的介面URL,通過這個才可以得到opendID等欄位資訊
*/
public final static String GET_WEBAUTH_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
/**
* 用於進行當點選按鈕的時候,能夠在網頁授權之後獲取到code,再跳轉到自己設定的一個URL路徑上的介面,這個主要是為了獲取之後於
* 獲取openId的介面相結合
* 注意:引數:toselfURL 表示的是當授權成功後,跳轉到的自己設定的頁面,所以這個要根據自己的需要進行修改
*/
public final static String Get_WEIXINPAGE_Code = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=toselfURL&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect";
/**
* 獲取access_token的URL
*/
public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
/**
* SMS服務的引數
*/
// public static final String SMS_ACCOUNT_SID = "AC83609469e581a0349a3aa499a55938b9";
// public static final String SMS_AUTH_TOKEN = "f7020503662698bce7e3e5465278c31e";
}
oauth2GetOpenid:
/**
* 進行使用者授權,獲取到需要的授權欄位,比如openId
* @param code 識別得到使用者id必須的一個值
* 得到網頁授權憑證和使用者id
* @return
*/
@Override
public Map<String, String> oauth2GetOpenid(String code) {
//自己的配置appid(公眾號進行查閱)
String appid = projectAppid;
//自己的配置APPSECRET;(公眾號進行查閱)
String appsecret = projectAppsecret;
//拼接使用者授權介面資訊
String requestUrl = ProjectConst.GET_WEBAUTH_URL.replace("APPID", appid).replace("SECRET", appsecret).replace("CODE", code);
//儲存獲取到的授權欄位資訊
Map<String, String> result = new HashMap<String, String>();
try {
JSONObject OpenidJSONO = WeiXinUtils.doGetStr(requestUrl);
//OpenidJSONO可以得到的內容:access_token expires_in refresh_token openid scope
String Openid = String.valueOf(OpenidJSONO.get("openid"));
System.out.println("openId:"+Openid);
String AccessToken = String.valueOf(OpenidJSONO.get("access_token"));
//使用者儲存的作用域
String Scope = String.valueOf(OpenidJSONO.get("scope"));
String refresh_token = String.valueOf(OpenidJSONO.get("refresh_token"));
result.put("Openid", Openid);
result.put("AccessToken", AccessToken);
result.put("scope", Scope);
result.put("refresh_token", refresh_token);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
wechatService.getAuthInfo(code);
/**
* 獲取到微信使用者的唯一的OpendID
* @param code 這是要獲取OpendId的必須的一個引數
* @return
*/
@Override
public Map<String , String> getAuthInfo(String code) {
//進行授權驗證,獲取到OpenID欄位等資訊
Map<String, String> result = oauth2GetOpenid(code);
// 從這裡可以得到使用者openid
String openId = result.get("Openid");
return result;
}
wechatService.getUserInfo(accessToken, openId):
@Override
public WeiXinUser getUserInfo(String accessToken, String openId) {
WeiXinUser weixinUserInfo = null;
// 拼接獲取使用者資訊介面的請求地址
String requestUrl = ProjectConst.GET_WEIXIN_USER_URL.replace("ACCESS_TOKEN", accessToken).replace(
"OPENID", openId);
// 獲取使用者資訊(返回的是Json格式內容)
JSONObject jsonObject = WeiXinUtils.doGetStr(requestUrl);
if (null != jsonObject) {
try {
//封裝獲取到的使用者資訊
weixinUserInfo = new WeiXinUser();
// 使用者的標識
weixinUserInfo.setOpenId(jsonObject.getString("openid"));
// 暱稱
weixinUserInfo.setNickname(jsonObject.getString("nickname"));
// 使用者的性別(1是男性,2是女性,0是未知)
weixinUserInfo.setSex(jsonObject.getInt("sex"));
// 使用者所在國家
weixinUserInfo.setCountry(jsonObject.getString("country"));
// 使用者所在省份
weixinUserInfo.setProvince(jsonObject.getString("province"));
// 使用者所在城市
weixinUserInfo.setCity(jsonObject.getString("city"));
// 使用者頭像
weixinUserInfo.setHeadImgUrl(jsonObject.getString("headimgurl"));
} catch (Exception e) {
if (0 == weixinUserInfo.getSubscribe()) {
System.out.println("使用者並沒有關注本公眾號");
} else {
int errorCode = jsonObject.getInt("errcode");
String errorMsg = jsonObject.getString("errmsg");
System.out.println("由於"+errorCode +"錯誤碼;錯誤資訊為:"+errorMsg+";導致獲取使用者資訊失敗");
}
}
}
return weixinUserInfo;
}
WeiXinUtils
/**
* @desc 使用者獲取access_token,眾號呼叫各介面時都需使用access_token
**/
public class WeiXinUtils {
/**
* Get請求,方便到一個url介面來獲取結果
*
* @param url
* @return
*/
public static JSONObject doGetStr(String url) {
DefaultHttpClient defaultHttpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
JSONObject jsonObject = null;
try {
HttpResponse response = defaultHttpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null) {
String result = EntityUtils.toString(entity, "UTF-8");
jsonObject = JSONObject.fromObject(result);
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return jsonObject;
}
/**
* 獲取access_token
* @return
*/
public static AccessToken getAccessToken(String projectAppId,String projectAppsecret){
AccessToken accessToken = new AccessToken();
String appid = projectAppId;
//自己的配置APPSECRET;(公眾號進行查閱)
String appsecret = projectAppsecret;
String url = ProjectConst.ACCESS_TOKEN_URL.replace("APPID" ,appid).replace("APPSECRET",appsecret);
JSONObject jsonObject = doGetStr(url);
if(jsonObject !=null){
accessToken.setAccess_token(jsonObject.getString("access_token"));
accessToken.setExpires_in(jsonObject.getLong("expires_in"));
}
return accessToken;
}
public static String authorizeUrl(String callbackUrl,String projectAppid){
StringBuilder sb = new StringBuilder();
try {
sb.append("https://open.weixin.qq.com/connect/oauth2/authorize?appid=").
append(projectAppid).
append("&redirect_uri=").
append(URLEncoder.encode(callbackUrl,"UTF-8")).
append("&response_type=code").
//append("&scope=snsapi_base ").
append("&scope=snsapi_userinfo").
append("&state=STATE").
append("#wechat_redirect");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return sb.toString();
}
}
基本程式碼都再上面.
相關文章
- 本地測試微信公眾號
- 微信公眾號開發
- Nodejs微信公眾號開發NodeJS
- .net開發微信公眾號
- 微信公眾號開發-分享
- 微信公眾號開發 —— 微信網頁授權小記網頁
- 微信公眾號開發之坑(一)
- Python+Tornado開發微信公眾號Python
- PHP微信公眾號開發——公共方法PHP
- 【Java】微信公眾號開發筆記Java筆記
- Laravel+easywechat 開發微信公眾號Laravel
- 微信公眾號讚賞功能開通方法 微信公眾號讚賞如何開通
- 微信公眾號開發推送事件排重事件
- 微信公眾號支付開發手記(node)
- 記一次微信公眾號開發
- 微信公眾號開發Django JSSDK授權DjangoJS
- 微信公眾號開發(一)基礎配置
- Spring Boot 開發微信公眾號後臺Spring Boot
- 微信公眾號開發之客服訊息
- 微信小程式及公眾號H5自動化測試攻略微信小程式H5
- 微信公眾號如何開啟關聯的小程式?
- 微信公眾號智慧回答
- 微信公眾號託管
- NodeJS介紹以及開發微信公眾號ExampleNodeJS
- Python開發微信公眾號後臺(系列二)Python
- 微信公眾號開發Django 網頁授權Django網頁
- 微信公眾號開發教程(一) 驗證接入
- 微信公眾號Java開發記錄(一)接入Java
- Nodejs開發微信公眾號中控服務NodeJS
- 微信公眾號開發-後端demo(隨錄)後端
- 微信公眾號下發紅包 -- PHPPHP
- 微信公眾號開發5-自定義選單-微信開發phpPHP
- 微信公眾號連結自定義分享總結
- 微信公眾號投票活動製作教程 微信公眾號投票怎麼弄?
- 微搜網·微信公眾號大全
- 微信開發系列之一 - 微信公眾號開發的開發環境搭建開發環境
- 微信公眾號的留言功能
- 微信公眾號介面導讀