Spring+微信小程序 卡券打通

近期公司項目須要使用到微信卡券模塊,主要作的是在小程序打通微信卡券,實現小程序領取卡券的功能效果。 前端

簡單說下涉及的東西: spring

    Springboot—使用springboot作後端接口,很是便捷 而且根本是基於SSM json

    微信公衆號—須要認證,而且開通卡券功能。 小程序

    微信小程序--- 做爲項目前端,接受後臺接口返回的參數,並調用wx.addcard接口領取卡券。 後端

開發準備:在公衆號平臺上開通卡券模塊,並建立一張卡券,並且要讓卡券進入投放的狀態,記錄下其card_id。 微信小程序

    後臺函數代碼編寫參考了網上其它人的程序: api

建立一個OpenApi類,這裏我把它加一個註解變成控制器 緩存

 

1     private static String grantType = "client_credential";
2     public static String appId = "wxc9e5635bb78789db";            //微信公衆號appid
3     public static String secret = "1ee5c4ba6aca792196dbcfc73eabeed8";            //微信公衆號密鑰
4     public static AccessToken token = null;            //微信公衆號的accessToken對象,因爲請求次數有限制,這裏使用全局靜態變量保存起來
5     public static ApiTicket ticket = null;//使用全局靜態變量存儲ApiTicket對象,固然若是使用緩存框架保存固然更好,這邊只是作一個簡單示例
6     //用於下面返回隨機字符串的函數
7     private final static String string = "0123456789";
8     final private static char[] chars = string.toCharArray();

 

 

這裏注意 appid跟secret必須是公衆號的,否則會有錯誤。 springboot

 1     /**
 2      * @param api_ticket:
 3      * @param cardId:須要領取的卡券的cardId
 4      * @return
 5      * @Description: 生成卡券須要的簽名並返回參數
 6      */
 7     public static Map<String, String> sign(String api_ticket, String cardId) {
 8         Map<String, String> ret = new HashMap<String, String>();
 9         String nonce_str = create_nonce_str();
10         String timestamp = create_timestamp();
11         String signature = "";
12 
13         String param[] = new String[4];
14 
15         param[0] = nonce_str;
16         param[1] = timestamp;
17         param[2] = api_ticket;
18         param[3] = cardId;
19 
20         Arrays.sort(param);//對參數的value值進行字符串的字典序排序
21 
22         StringBuilder sb = new StringBuilder();
23         for (String b : param) {
24             sb.append(b);
25         }
26         System.out.println(sb);
27         //對上面拼接的字符串進行sha1加密,獲得signature
28         try {
29             MessageDigest crypt = MessageDigest.getInstance("SHA-1");
30             crypt.reset();
31             crypt.update(sb.toString().getBytes("UTF-8"));
32             signature = bytesToHexString(crypt.digest());
33         } catch (NoSuchAlgorithmException e) {
34             e.printStackTrace();
35         } catch (UnsupportedEncodingException e) {
36             e.printStackTrace();
37         }
38 
39         //返回領取卡券須要的參數,其中nonceStr和timestamp必須和簽名中的保持一致
40         ret.put("card_id", cardId);
41         ret.put("api_ticket", api_ticket);
42         ret.put("nonceStr", nonce_str);
43         ret.put("timestamp", timestamp);
44         ret.put("signature", signature);
45 
46         return ret;
47     }

 

 

 

該函數是簽名用的函數,注意這裏有隨機字符串參與簽名。 微信

 

    /**
     * 返回時間戳(秒)
     * @return
     */
    private static String create_timestamp() {
        return String.valueOf(new Date().getTime() / 1000);
    }

    /**
     * 返回隨機字符串
     * @return
     */
    private static String create_nonce_str() {
        String nonce = new String();
        for (int i = 0; i < 10; i++) {
            int rannum = (int) (Math.random() * 1000) % (chars.length);
            nonce += chars[rannum];
        }
        return nonce;
    }

 

 

 

 

 

獲取token的方式跟微信小程序獲取token的方式同樣

    /**
     * 獲取token
     * @return
     * @throws WeixinException
     * @throws JsonParseException
     * @throws JsonMappingException
     * @throws IOException
     * @throws org.weixin4j.WeixinException
     */
    public static AccessToken getToken() throws WeixinException, JsonParseException, JsonMappingException, IOException, org.weixin4j.WeixinException {
        if (token == null || token.getExpires_in() < System.currentTimeMillis()) {
            //拼接參數
            String param = "?grant_type=" + grantType + "&appid=" + appId + "&secret=" + secret;
            //建立請求對象
            HttpsClient http = new HttpsClient();
            //調用獲取access_token接口
            Response res = http.get("https://api.weixin.qq.com/cgi-bin/token" + param);
            System.out.println(res.asString());
            ObjectMapper mapper = new ObjectMapper();
            token = mapper.readValue(res.asString(), AccessToken.class);
        }
        return token;
    }

 

 

    /**
     * Convert byte[] to hex string
     * @param src byte[] data
     * @return hex string
     */
    public static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder("");
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }
}

 

 

 

    /**
     * @param cardId:須要領取的卡券的cardId
     * @return
     * @throws WeixinException
     * @throws JsonParseException
     * @throws JsonMappingException
     * @throws IOException
     * @Description: 獲取領取卡券獲取簽名等參數
     */
    @RequestMapping("getCardSign")
    @ResponseBody
    public Map<String, String> getCardSign(String cardId) throws WeixinException, JsonParseException, JsonMappingException, IOException, org.weixin4j.WeixinException {
        Map<String, String> ret = new HashMap<String, String>();
        //先要獲取api_ticket,因爲請求api_ticket的接口訪問有次數限制,因此最好將得到到的api_ticket保存到緩存中,這邊作法比較簡單,直接使用的靜態變量
        if (ticket == null || ticket.getExpires_in() < System.currentTimeMillis()) {
            //建立請求對象
            HttpsClient http = new HttpsClient();

            ObjectMapper mapper = new ObjectMapper();

            AccessToken token = OpenApi.getToken();//這裏獲取的token就是最上方代碼保存的微信公衆號全局靜態變量token

            //經過access_token調用獲取api_ticket接口
            Response res = http.get("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + token.getAccess_token() + "&type=wx_card");
            System.out.println(res.asString());
            ticket = mapper.readValue(res.asString(), ApiTicket.class);
        }

        ret = sign(ticket.getTicket(), cardId);//生成領取卡券須要的簽名,並返回相關的參數

        for (Map.Entry entry : ret.entrySet()) {
            System.out.println(entry.getKey() + ", " + entry.getValue());
        }
        return ret;
    }

 

 

@ResponseBody 註解的用處是讓這個接口返回的是json數據。也能夠在控制器定義的時候 將@Controller 直接寫爲@RestController。那這裏就能夠不用加

@ResponseBody註解。

 

    接下來,小程序前端發起網絡請求訪問這個接口。返回簽名所須要的數據

小程序調用wx.addCard函數

wx.addCard({

cardList: [{

cardId: cardId,

cardExt: '{"nonce_str":"' + res.data.nonceStr + '","timestamp":"' + res.data.timestamp + '","signature":"' + res.data.signature + '"}'

}],

success: function (res) {

console.log("卡券添加結果",res.cardList) // 卡券添加結果

}

})

不少人在這裏會出現簽名錯誤。我也是糾結了一天 才挑出問題,這裏列一下有可能出現簽名錯誤的緣由。

  • 後端簽名的時候,appid跟secret沒有用公衆號的,而是用小程序的。通常後端出現問題的概率不高,這裏能夠用微信提供的接口自行驗證簽名是否

有問題。 簽名校驗地址: https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=cardsign

  • 個人問題主要是出如今前端。cardExtc參數傳值有誤,或者拼接出錯,都會出現簽名錯誤的提示。這裏須要注意的是,首先 官方文檔中,cardExt有

openid跟code參數,可是實際上沒有這兩個值的話是不用填在cardExt裏面的,好比我是經過公衆平臺直接建立的卡券,因此沒有code和openid這兩個參數,那麼我上面的傳值就乾脆不寫。

  • 其實,我看了網上其它人的參數,有些人有這個nonce_str參數,一開始我是沒傳這個進入,結果一直顯示簽名錯誤,弄了我半天也不知道找不出緣由。

後來我才知道,你在後臺參與簽名用的參數,在前端一樣的得再cardExt中傳過去,不然就會簽名錯誤!這點但願注意下,確實坑。。可是也只能怪我本身

不夠細心。

另外就是cardExt這個參數是要拼接成字符串json形式傳值的,請不要直接傳一個cardExt對象過去,或者直接構建一個cardExt對象,而後使用

JSON.stringify()函數轉化一下,

 

 

 

      

 

 

 


排除掉簽名錯誤的問題就大功告成了。。

相關文章
相關標籤/搜索