近期公司项目须要使用到微信卡券模块,主要作的是在小程序打通微信卡券,实现小程序领取卡券的功能效果。 前端
简单说下涉及的东西: 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) // 卡券添加结果 } }) |
不少人在这里会出现签名错误。我也是纠结了一天 才挑出问题,这里列一下有可能出现签名错误的缘由。
有问题。 签名校验地址: https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=cardsign
openid跟code参数,可是实际上没有这两个值的话是不用填在cardExt里面的,好比我是经过公众平台直接建立的卡券,因此没有code和openid这两个参数,那么我上面的传值就干脆不写。
后来我才知道,你在后台参与签名用的参数,在前端一样的得再cardExt中传过去,不然就会签名错误!这点但愿注意下,确实坑。。可是也只能怪我本身
不够细心。
另外就是cardExt这个参数是要拼接成字符串json形式传值的,请不要直接传一个cardExt对象过去,或者直接构建一个cardExt对象,而后使用
JSON.stringify()函数转化一下,
排除掉签名错误的问题就大功告成了。。