微信公眾號測試號開發小結

Anti_Magina_cass發表於2019-02-20

微信公眾號踩坑之旅

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();
    }
}

基本程式碼都再上面.

相關文章