1. 程式人生 > >微信公眾號開發之調起微信掃一掃介面

微信公眾號開發之調起微信掃一掃介面

參考微信JS-SDK說明文件 看到網上很多都說微信的說明文件很坑,在我看來,仔細閱讀的話,介紹還是很全的。

1.首先在JSP頁面引入http://res.wx.qq.com/open/js/jweixin-1.1.0.js

2.通過config介面注入許可權驗證配置

wx.config({
    debug: true, // 開啟除錯模式,呼叫的所有api的返回值會在客戶端alert出來,若要檢視傳入的引數,可以在pc端開啟,引數資訊會通過log打出,僅在pc端時才會列印。
    appId: '', // 必填,企業號的唯一標識,此處填寫企業號corpid
    timestamp: , // 必填,生成簽名的時間戳
    nonceStr: '', // 必填,生成簽名的隨機串
    signature: '',// 必填,簽名,見附錄1
    jsApiList: [] // 必填,需要使用的JS介面列表,所有JS介面列表見附錄2
});

3.通過ready介面處理成功驗證

wx.ready(function(){
    // config資訊驗證後會執行ready方法,所有介面呼叫都必須在config介面獲得結果之後,config是一個客戶端的非同步操作,所以如果需要在頁面載入時就呼叫相關介面,則須把相關介面放在ready函式中呼叫來確保正確執行。對於使用者觸發時才呼叫的介面,則可以直接呼叫,不需要放在ready函式中。
});

4.通過error介面處理失敗驗證

wx.error(function(res){
    // config資訊驗證失敗會執行error函式,如簽名過期導致驗證失敗,具體錯誤資訊可以開啟config的debug模式檢視,也可以在返回的res引數中檢視,對於SPA可以在這裡更新簽名。
});

5.調起微信掃一掃

wx.scanQRCode({
    desc: 'scanQRCode desc',
    needResult: 0, // 預設為0,掃描結果由微信處理,1則直接返回掃描結果,
    scanType: ["qrCode","barCode"], // 可以指定掃二維碼還是一維碼,預設二者都有
    success: function (res) {
       // 回撥
    }
    error: function(res){
          if(res.errMsg.indexOf('function_not_exist') > 0){
               alert('版本過低請升級')
            }
     }
});

具體程式碼實現如下:

scanBarcode.jsp

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="zh-CN">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=320.1,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
<head>
    <link rel="stylesheet" href="http://203.195.235.76/jssdk/css/style.css"/>
    <script src="http://res.wx.qq.com/open/js/jweixin-1.1.0.js"></script>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.js"></script>
    <link rel="stylesheet" href="../../../resources/css/example.css"/>
    <link rel="stylesheet" href="../../../resources/css/weui.min.css"/>
    <link rel="stylesheet" href="../../../resources/css/borrowScan.css"/>
</head>
<body>
<div class="container" style="text-align: center;">
    <div class="body_bd body_fd">
        <a class="weui-btn weui-btn-primary weui-btn-zdy" id="scanQRCode1">
            <p>掃描條碼</p>
        </a>
    </div>
</div>
<script type="text/javascript">
    $.ajax({
        url: "${pageContext.request.contextPath}/wechat/jsapisign",
        type: "post",
        data: {
            url: location.href.split('#')[0]
        },
        contentType: 'application/x-www-form-urlencoded;charset=utf-8',
        async: true,
        success: function (data) {
            wx.config({
                debug: false, // 開啟除錯模式,呼叫的所有api的返回值會在客戶端alert出來,若要檢視傳入的引數,可以在pc端開啟,引數資訊會通過log打出,僅在pc端時才會列印。
                appId: data.appid, // 必填,公眾號的唯一標識
                timestamp: data.timestamp, // 必填,生成簽名的時間戳
                nonceStr: data.nonceStr, // 必填,生成簽名的隨機串
                signature: data.signature,// 必填,簽名,見附錄1
                jsApiList: ["scanQRCode"] // 必填,需要使用的JS介面列表,所有JS介面列表見附錄2
            });
        }
    });

    wx.ready(function () {
        // 9.1.2 掃描二維碼並返回結果
        document.querySelector('#scanQRCode1').onclick = function () {
            wx.scanQRCode({
                needResult: 1,
                desc: 'scanQRCode desc',
                success: function (res) {
                    //掃碼後獲取結果引數賦值給Input
                    var url = res.resultStr;
                    //商品條形碼,取","後面的
                    if (url.indexOf(",") >= 0) {
                        var tempArray = url.split(',');
                        var barCode = tempArray[1];
                        window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${basePath}/wechat/toBookDetail?barCode=" + barCode + "&response_type=code&scope=snsapi_base&state=BINDFACE#wechat_redirect";
                    } else {
                        alert("請對準條形碼掃碼!");
                    }
                }
            });
        };
    });

    //初始化jsapi介面 狀態
    wx.error(function (res) {
        alert("呼叫微信jsapi返回的狀態:" + res.errMsg);
    });
</script>
</body>
</html>

微信驗籤程式碼 WxController.java

  /**
     * 微信驗籤
     * @param url
     * @return
     */
    @RequestMapping(value = "/jsapisign", method = {RequestMethod.GET, RequestMethod.POST}, produces = MEDIATYPE_CHARSET_JSON_UTF8)
    @ResponseBody
    public String jsApiSign(String url) {
        //新增微信js簽名信息
        Map<String, String> signMap = WXJsapiticket.jsApiSign(url);
        return JSON.toJSONString(signMap);
    }

用到的方法類

WXJsapiticket.java

public class WXJsapiticket {
    private static Logger logger = LoggerFactory.getLogger(WxController.class);
    /**
     * 微信jsapi驗籤
     * @param url
     * @return
     */
    public static Map<String, String> jsApiSign(String url) {
        Map<String, String> ret = new HashMap<String, String>();
        String nonce_str = CheckUtil.create_nonce_str();
        String timestamp = CheckUtil.create_timestamp();
        String jsapi_ticket = getJsApiTicket();
        String string1 = CheckUtil.getString1(nonce_str,timestamp,jsapi_ticket,url);
        String signature = CheckUtil.getSha1(string1);
        ret.put("appid", WXConstants.APPID);//取你自己的公眾號appid
        ret.put("url", url);
        ret.put("jsapi_ticket", jsapi_ticket);
        ret.put("nonceStr", nonce_str);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);
        logger.info("jsApiSign------url=" + url + "-----jsapi_ticket=" + jsapi_ticket + "--------nonceStr=" + nonce_str + "--------timestamp=" + timestamp + "-------signature=" + signature);
        return ret;
    }
    public static String getJsApiTicket(){
        Map<String,Object> map = JsApiTicketCache.getInstance().getJsApiTicketAndExpiresIn();
        return (String) map.get("jsapi_ticket");
    }
}

CheckUtil.java

public class CheckUtil {
    public static final String  token = "xiaodou"; //開發者自行定義Token
    /**
     * 對所有待簽名引數按照欄位名的ASCII 碼從小到大排序(字典序)後,使用URL鍵值對的格式 (即 key1=value1&key2=value2…)拼接成字串string1
     * @param nonce_str
     * @param timestamp
     * @param jsapi_ticket
     * @param url
     * @return
     */
    public static String getString1(String nonce_str,String timestamp,String jsapi_ticket,String url){
        //1.定義陣列存放nonce_str,timestamp,jsapi_ticket,url
        String[] arr = {"noncestr="+nonce_str,"timestamp="+timestamp,"jsapi_ticket="+jsapi_ticket,"url="+url};
        //2.對陣列進行排序
        Arrays.sort(arr);
        //3.生成字串
        StringBuffer sb = new StringBuffer();
        for(String s : arr){
            sb.append(s);
            sb.append("&");
        }
        sb.deleteCharAt(sb.length()-1);
        return sb.toString();
    }
    public static String getSha1(String str){
        if(str==null||str.length()==0){
            return null;
        }
        char hexDigits[] = {'0','1','2','3','4','5','6','7','8','9',
                'a','b','c','d','e','f'};
        try {
            MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
            mdTemp.update(str.getBytes("UTF-8"));
            byte[] md = mdTemp.digest();
            int j = md.length;
            char buf[] = new char[j*2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
                buf[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(buf);
        } catch (Exception e) {
            // TODO: handle exception
            return null;
        }
    }
    public static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }
    public static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }
}

JsApiTicketCatch.java(對jsapi_ticket的快取可自行設定,redis或者存到資料庫都是可行的)

/**
 * 快取ticket
 */
public class JsApiTicketCache {
    //快取jsapi_ticket的Map,map中包含jsapiTicket,expiresIn和快取的時間戳time
    private Map<String, String> map = new HashMap<String,String>();
    private static JsApiTicketCache jsApiTicketCache = null;
    private JsApiTicketCache() { }
    // 靜態工廠方法
    public static JsApiTicketCache getInstance() {
        if (jsApiTicketCache == null) {
            jsApiTicketCache = new JsApiTicketCache();
        }
        return jsApiTicketCache;
    }
    public Map<String, String> getMap() {
        return map;
    }
    public void setMap(Map<String, String> map) {
        this.map = map;
    }
    /**
     * 獲取 jsapi_ticket expires_in
     * @return
     */
    public Map<String,Object> getJsApiTicketAndExpiresIn() {
        Map<String,Object> result = new HashMap<String,Object>();
        JsApiTicketCache jsApiTicketCache = JsApiTicketCache.getInstance();
        Map<String, String> map = jsApiTicketCache.getMap();
        String time = map.get("time");
        String jsapiTicket = map.get("jsapi_ticket");
        String expiresIn = map.get("expires_in");
        Long nowDate = new Date().getTime();
        if (jsapiTicket != null && time != null && expiresIn!=null) {
            //這裡設定過期時間為微信規定的過期時間減去5分鐘
            int outTime = (Integer.parseInt(expiresIn)-300) * 1000;
            if (nowDate - Long.parseLong(time) < outTime) {
                System.out.println("-----從快取讀取jsapi_ticket:" + jsapiTicket);
                //從快取中拿資料為返回結果賦值
                result.put("jsapi_ticket", jsapiTicket);
                result.put("expires_in", expiresIn);
            }
        } else {
            JsapiTicket info = WeiXinUtil.getjsapiTicket();//實際中這裡要改為你自己呼叫微信介面去獲取jsapi_ticket和expires_in
            System.out.println("-----通過呼叫微信介面獲取jsapi_ticket:" + info.getJsapiTicket());
            //將資訊放置快取中
            map.put("time", nowDate + "");
            map.put("jsapi_ticket", info.getJsapiTicket());
            map.put("expires_in", info.getExpiresIn()+"");
            //為返回結果賦值
            result.put("jsapi_ticket", info.getJsapiTicket());
            result.put("expires_in", info.getExpiresIn());
        }
        return result;
    }

}

WeiXinUtil.java

jsapi_ticket_url="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";//GET方式請求獲得jsapi_ticket
 /**
     * 獲取jsapi_ticket
     * @return
     */
    public static JsapiTicket getjsapiTicket() {
        JsapiTicket jsapiTicket = null ;
        String accessToken = (String) AccessTokenCache.getInstance().getAcessTokenAndExpiresIn().get("access_token");
        String requestUrl = WXConstants.jsapi_ticket_url.replace("ACCESS_TOKEN", accessToken);
        JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
        // 如果請求成功
        if (null != jsonObject) {
            try {
                jsapiTicket = new JsapiTicket();
                jsapiTicket.setJsapiTicket(jsonObject.getString("ticket"));
                jsapiTicket.setExpiresIn(jsonObject.getInt("expires_in"));
            } catch (JSONException e) {
                jsapiTicket = null;
                // 獲取token失敗
                log.error("獲取ticket失敗 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
            }
        }
        return jsapiTicket;
    }

JsapiTicket.java

public class JsapiTicket {
    private String id;
    private String jsapiTicket;
    private int expiresIn;

    public String getJsapiTicket() {
        return jsapiTicket;
    }

    public void setJsapiTicket(String jsapiTicket) {
        this.jsapiTicket = jsapiTicket;
    }

    public int getExpiresIn() {
        return expiresIn;
    }

    public void setExpiresIn(int expiresIn) {
        this.expiresIn = expiresIn;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

在這裡總結一下開發中需要注意的地方(我遇到的坑):

1.確認url是頁面完整的url(請在當前頁面alert(location.href.split('#')[0])確認),包括'http(s)://'部分,以及'?'後面的GET引數部分,但不包括'#'hash後面的部分。

2.在生成string1時,拼接的引數均為小寫,特別注意noncestr,而在jsp頁面中為nonceStr(所有的引數一定要根據微信介面中的定義,否則會導致驗籤失敗)。