最近作了個領取微信卡券的小程序,看了不少文檔資料以及花了不少時間纔算搞定的,不過也算是好事多磨,這邊記錄分享一下,也算給一點提高。javascript
1、開發前準備前端
1:申請 微信公衆號 和 微信小程序,這是兩個不一樣的東西,都須要單獨申請、不一樣的賬號;java
2:微信公衆號須要開通微信卡券的功能;json
3:在微信公衆號裏面去綁定微信小程序;小程序
4:申請微信開放平臺,並將微信公衆號 和 微信小程序綁定到該開放平臺。(注:綁定到開發平臺下的做用只是爲了獲取unionid,由於同一用戶在 微信公衆號 和 小程序下得到的openid是不同的,若是微信公衆號 和 小程序都須要領取卡券,則最好經過unionid來跟蹤用戶;若是你只是開發微信小程序的領取卡券,則徹底能夠忽略第4點,博主本人也沒有去綁定到微信開放平臺,感受步驟好多,特別麻煩!)後端
2、開始開發微信小程序
1:獲取微信卡券api
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025272緩存
這邊能夠直接經過微信公衆號提供的接口獲取或者建立微信的卡券,此處不過多介紹,只是提一下這邊要獲取的access_token,網址以下https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183,代碼直接以下:服務器
private static String grantType = "client_credential"; public static String appId = ""; //微信公衆號appid public static String secret = ""; //微信公衆號密鑰 public static AccessToken token = null; //微信公衆號的accessToken對象,因爲請求次數有限制,這裏使用全局靜態變量保存起來 public static AccessToken getToken() throws WeixinException, JsonParseException, JsonMappingException, IOException{ 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; }
其中須要jackson和weixin4j的jar包,比較廣泛,請自行下載;而AccessToken對象也比較簡單,就errcode、errmsg、access_token、expires_in這四個參數,比較簡單,在文章結尾貼代碼
2:升級微信卡券
其實這個步驟也能夠省略,升級微信卡券的目的是能夠直接從微信卡券跳轉到對應的小程序,博主就偷懶了,直接跳過了這個步驟;
不過升級卡券也比較簡單,就是調用調用微信公衆號的更改微信卡券接口(URL:https://api.weixin.qq.com/card/update?access_token=TOKEN),添加幾個字段,能夠參考微信官方文檔3.1,連接以下:https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&key=1490190158&version=1&lang=zh_CN&platform=2
3:領取卡券
3.1:先獲取openId
小程序端代碼,經過調用wx.login獲取code,再調用https://api.weixin.qq.com/sns/jscode2session接口獲取openid,博主看到不少例子是直接從小程序端調用這個接口,但我事實中發現是行不通的,由於這個域名沒法添加到小程序的request合法域名中,微信給的說明是不要在前端調用這個接口,須要經過後臺,那沒辦法嘍
wx.login({ success: function (res) { var service_url = 'https://???/???/weixin/api/login?code=' + res.code;//須要將服務器域名添加到小程序的request合法域名中,並且必須是https開頭 wx.request({ url: l, data: {}, method: 'GET', success: function (res) { console.log(res); if (res.data != null && res.data != undefined && res.data != '') { wx.setStorageSync("openid", res.data.openid);//將獲取的openid存到緩存中 } } }); } });
後端java代碼
/** * 小程序後臺登陸,向微信平臺發送獲取access_token請求,並返回openId * @param code * @return 用戶憑證 * @throws WeixinException * @throws IOException * @throws JsonMappingException * @throws JsonParseException */ @RequestMapping("login") @ResponseBody public Map<String, Object> login(String code, HttpServletRequest request) throws WeixinException, JsonParseException, JsonMappingException, IOException { if (code == null || code.equals("")) { throw new WeixinException("invalid null, code is null."); } Map<String, Object> ret = new HashMap<String, Object>(); //拼接參數 String param = "?grant_type=" + grant_type + "&appid=" + appid + "&secret=" + secret + "&js_code=" + code; System.out.println("https://api.weixin.qq.com/sns/jscode2session" + param); //建立請求對象 HttpsClient http = new HttpsClient(); //調用獲取access_token接口 Response res = http.get("https://api.weixin.qq.com/sns/jscode2session" + param); //根據請求結果斷定,是否驗證成功 JSONObject jsonObj = res.asJSONObject(); if (jsonObj != null) { Object errcode = jsonObj.get("errcode"); if (errcode != null) { //返回異常信息 throw new WeixinException(getCause(Integer.parseInt(errcode.toString()))); } ObjectMapper mapper = new ObjectMapper(); OAuthJsToken oauthJsToken = mapper.readValue(jsonObj.toJSONString(),OAuthJsToken.class); ret.put("openid", oauthJsToken.getOpenid()); } return ret; }
其中OAuthJsToken對象的字段爲:openid、expires_in、session_key(會話密鑰) ,在文章結尾貼代碼;
3.2:生成領取卡券的簽名,並調用wx.addCard方法領取卡券
這邊寫貼java後端代碼
public static ApiTicket ticket = null;//使用全局靜態變量存儲ApiTicket對象,固然若是使用緩存框架保存固然更好,這邊只是作一個簡單示例 /** * @Description: 獲取領取卡券獲取簽名等參數 * @param cardId:須要領取的卡券的cardId * @return * @throws WeixinException * @throws JsonParseException * @throws JsonMappingException * @throws IOException */ @RequestMapping("getCardSign") @ResponseBody public Map<String, String> getCardSign(String cardId) throws WeixinException, JsonParseException, JsonMappingException, IOException{ 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; } /** * @Description: 生成卡券須要的簽名並返回參數 * @param api_ticket: * @param cardId:須要領取的卡券的cardId * @return */ public static Map<String, String> sign(String api_ticket, String cardId) { Map<String, String> ret = new HashMap<String, String>(); String nonce_str = create_nonce_str(); String timestamp = create_timestamp(); String signature = ""; String param[] = new String[4]; param[0] = nonce_str; param[1] = timestamp; param[2] = api_ticket; param[3] = cardId; Arrays.sort(param);//對參數的value值進行字符串的字典序排序 StringBuilder sb = new StringBuilder(); for(String b : param){ sb.append(b); } System.out.println(sb); //對上面拼接的字符串進行sha1加密,獲得signature try{ MessageDigest crypt = MessageDigest.getInstance("SHA-1"); crypt.reset(); crypt.update(sb.toString().getBytes("UTF-8")); signature = byteToHex(crypt.digest()); }catch (NoSuchAlgorithmException e){ e.printStackTrace(); }catch (UnsupportedEncodingException e){ e.printStackTrace(); } //返回領取卡券須要的參數,其中nonceStr和timestamp必須和簽名中的保持一致 ret.put("card_id", cardId); ret.put("api_ticket", api_ticket); ret.put("nonceStr", nonce_str); ret.put("timestamp", timestamp); ret.put("signature", signature); return ret; }
其中ApiTicket對象的屬性有:errcode、errmsg、ticket、expires_in,在文章結尾貼出該代碼
再貼小程序端代碼
var that = this; var service_url = 'https://???/???/weixin/api/getCardSign?cardId=' + cardId;//須要將服務器域名添加到小程序的request合法域名中,並且必須是https開頭 wx.request({ url: service_url, data: {}, method: 'GET', success: function (res) { console.log(res); wx.addCard({ cardList: [{ cardId: that.data.cardId, cardExt: '{"code":"","openid":"","timestamp":' + res.data.timestamp + ',"nonce_str":"' + res.data.nonceStr + '","signature":"' + res.data.signature + '"}' }],//這裏須要注意的是cardExt參數的value值是 String類型,不要使用對象發送;另外openid若是在建立優惠券的時候沒有指定,則這邊爲空,千萬不要填寫當前用戶的openid success: function (result) { console.log(res); wx.showToast({ title: '領取成功', icon: 'success', duration: 2000 }); }, fail: function (res) { console.log('領取失敗'); console.log(res); } }) } });
ok,若是領取成功,能夠直接到微信卡包裏面查看。下面貼出AccessToken、ApiTicket、OAuthJsToken的java模型代碼
public class BaseResponse { private int errcode; private String errmsg; public int getErrcode() { return errcode; } public void setErrcode(int errcode) { this.errcode = errcode; } public String getErrmsg() { return errmsg; } public void setErrmsg(String errmsg) { this.errmsg = errmsg; } } public class AccessToken extends BaseResponse{ private String access_token; private long expires_in; public String getAccess_token() { return access_token; } public void setAccess_token(String access_token) { this.access_token = access_token; } public long getExpires_in() { return expires_in; } public void setExpires_in(long expires_in) { this.expires_in = System.currentTimeMillis() + (expires_in - 100) * 1000;//原expires_in是有效時長,好比:7200,現改成過時的時間戳 } } public class ApiTicket extends BaseResponse{ private String ticket; private long expires_in; public String getTicket() { return ticket; } public void setTicket(String ticket) { this.ticket = ticket; } public long getExpires_in() { return expires_in; } public void setExpires_in(long expires_in) { this.expires_in = System.currentTimeMillis() + (expires_in - 100) * 1000;//原expires_in是有效時長,好比:7200,現改成過時的時間戳 } } public class OAuthJsToken { private String openid; //用戶惟一標識 private int expires_in = 7200; //憑證有效時間,單位:秒 private String session_key; //會話密匙 private long exprexpiredTime; //過時時間 public String getOpenid() { return openid; } public void setOpenid(String openid) { this.openid = openid; } public int getExpires_in() { return expires_in; } public void setExpires_in(int expires_in) { this.expires_in = expires_in; this.exprexpiredTime = System.currentTimeMillis() + expires_in * 1000; } public String getSession_key() { return session_key; } public void setSession_key(String session_key) { this.session_key = session_key; } public long getExprexpiredTime() { return exprexpiredTime; } public void setExprexpiredTime(long exprexpiredTime) { this.exprexpiredTime = exprexpiredTime; } /** * 判斷用戶憑證是否過時 * * @return 過時返回 true,不然返回false */ public boolean isExprexpired() { return System.currentTimeMillis() >= this.exprexpiredTime; } }