公众号H5开发调用微信 js-sdk 的那些事

需求

如果你在开发微信H5页面,并且需要调用微信的js接口,特意总结了一下调用的过程

步骤

 一,绑定安全域名

登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”
注意:
   1.  需要从公众号管理平台中下载校验凭证txt文本内容,然后上传至填写域名或路径指向的web服务器(或虚拟主机)的目录(若填写域名,将文件放置在域名根目录下,例如 wx.qq.com/MP_verify_1FKXuKnjqdNephyv.txt ;若填写路径,将文件放置在路径目录下,例如 wx.qq.com/mp/MP_verify_1FKXuKnjqdNephyv.txt ),并确保可以访问。
   2.  绑定域名的时候只填域名部分,必须经过 ICP备案,不需要http://协议前缀,不需要访问页面路径
    正确示例: www.baidu.com
    错误示例 :http://www.baidu.com , www.baidu.com/index

二、引入微信js-sdk文件

    在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.4.0.js

    如需进一步提升服务稳定性,当上述资源不可访问时,可改访问:http://res2.wx.qq.com/open/js/jweixin-1.4.0.js (支持https)

三、获取wx.config()参数

   在调用微信js-sdk接口之前,需要进行wx.config进行权限验证,如下是验证需要的参数

wx.config({
    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
    appId: '', // 必填,公众号的唯一标识
    timestamp: , // 必填,生成签名的时间戳
    nonceStr: '', // 必填,生成签名的随机串
    signature: '',// 必填,签名
    jsApiList: [] // 必填,需要使用的JS接口列表
});


    签名生成的步骤和jsApiList 列表微信文档中有,详见链接:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 通过官方文档,我们知道,生成签名需要一个随机字符串nonceStr,一个时间戳timestamp,一个凭证jsapi_ticket,还有一个调用接口页面的url(包括http或https协议头和页面路径),随后还需要一个sha1加密的算法
    这里,我只给大家分享一下生成签名的一些代码(java)
1. 首先,我们需要通过微信公众号的接口获取accessToken,调用微信接口来获取jsapi_ticket; 这时,我们需要的就是能够发送请求的工具,封装了一个HttpReq类:

public class HttpReq {

    public static String doGet(String url) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        String result = "";
        try {
            // 通过址默认配置创建一个httpClient实例
            httpClient = HttpClients.createDefault();
            // 创建httpGet远程连接实例
            HttpGet httpGet = new HttpGet(url);
            // 设置请求头信息,鉴权
//            httpGet.setHeader("Authorization", "Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0");
            // 设置配置请求参数
            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(35000)// 连接主机服务超时时间
                    .setConnectionRequestTimeout(35000)// 请求超时时间
                    .setSocketTimeout(60000)// 数据读取超时时间
                    .build();
            // 为httpGet实例设置配置
            httpGet.setConfig(requestConfig);
            // 执行get请求得到返回对象
            response = httpClient.execute(httpGet);
            // 通过返回对象获取返回数据
            HttpEntity entity = response.getEntity();
            // 通过EntityUtils中的toString方法将结果转换为字符串
            result = EntityUtils.toString(entity);
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (null != response) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != httpClient) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }


    public static String doPost(String url, Map paramMap) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse httpResponse = null;
        String result = "";
        // 创建httpClient实例
        httpClient = HttpClients.createDefault();
        // 创建httpPost远程连接实例
        HttpPost httpPost = new HttpPost(url);
        // 配置请求参数实例
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(35000)// 设置连接主机服务超时时间
                .setConnectionRequestTimeout(35000)// 设置连接请求超时时间
                .setSocketTimeout(60000)// 设置读取数据连接超时时间
                .build();
        // 为httpPost实例设置配置
        httpPost.setConfig(requestConfig);
        // 设置请求头
        httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
        // 封装post请求参数
        if (null != paramMap && paramMap.size() > 0) {
            List nvps = new ArrayList();
            // 通过map集成entrySet方法获取entity
            Set> entrySet = paramMap.entrySet();
            // 循环遍历,获取迭代器
            Iterator> iterator = entrySet.iterator();
            while (iterator.hasNext()) {
                Map.Entry mapEntry = iterator.next();
                nvps.add(new BasicNameValuePair(mapEntry.getKey(), mapEntry.getValue().toString()));
            }

            // 为httpPost设置封装好的请求参数
            try {
                httpPost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        try {
            // httpClient对象执行post请求,并返回响应参数对象
            httpResponse = httpClient.execute(httpPost);
            // 从响应对象中获取响应内容
            HttpEntity entity = httpResponse.getEntity();
            result = EntityUtils.toString(entity);
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (null != httpResponse) {
                try {
                    httpResponse.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != httpClient) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }
}

使用get或者post请求都可以调用,返回的结果是String类型,如果需要解析成JSONObject,需要使用JSONObject.parseObject();

获取到jsapi_ticket后,需要做一个变量的缓存(因为微信给我们提供的这个接口有调用次数限制),这里我采用设置一个全局过期时间,如果过期了,就重新发送请求获取,(因为只有一个变量需要缓存,就没有使用其他框架来做缓存)大家也可以参考一下:

/**jsapi_ticket*/
private static String jsapiTicket;

/**jsapi_ticket过期时间*/
private static long jsapiTicketExpires;

    /**设置jsapiTicket*/
    private int setJsapiTicket(){
        long curTime = System.currentTimeMillis();
        if(curTime < jsapiTicketExpires && jsapiTicket != null){
            System.out.println("jsapiTicket 没有过期,不再重新获取!");
            return 1;
        }
        JSONObject accessTokenRes = getAccessToken(); //获取AccessToken的方法需要大家自己发送请求(涉及到业务方隐私)
        if(accessTokenRes == null){
            System.out.println("获取 accessToken 失败!");
            return 0;
        }
        String accessToken = accessTokenRes.getString("access_token");
        try{
            String res = HttpReq.doGet("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+ accessToken +"&type=jsapi");
            JSONObject result = JSON.parseObject(res);
            if(result.getString("errmsg").equals("ok")){
                jsapiTicket = result.getString("ticket");
                jsapiTicketExpires = curTime + 7200*1000;
            }

        }catch (Exception e){
            System.out.println("error on get JsapiTicket ", e);
            return 0;
        }
        return 1;
    }

在获取全局变量jsapiTicket之前,需要进行一次setJsapiTicket,并根据其返回状态码进行判断是否发送请求

/**设置jsapiTicket*/
int setTicketRes = setJsapiTicket();
if( setTicketRes == 0 || jsapiTicket == null){
  System.out.println("获取 jsapiTicket 失败!");
  return null;
}
String jsapi_ticket = jsapiTicket; //获取jsapi_ticket

2. 获取到jsapi_ticket之后,我们需要自己生成一个长度为16的随机字符串(官方文档里的随机字符串长度为16,所以这里生成长度为16的),具体生成代码如下:

public class RandomStr {

    private static char ch[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
            'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
            'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
            'x', 'y', 'z', '0', '1' };//最后又重复两个0和1,因为需要凑足数组长度为64

    private static Random random = new Random();

    //生成指定长度的随机字符串
    public static  String createRandomString(int length) {
        if (length > 0) {
            int index = 0;
            char[] temp = new char[length];
            int num = random.nextInt();
            for (int i = 0; i < length % 5; i++) {
                temp[index++] = ch[num & 63];//取后面六位,记得对应的二进制是以补码形式存在的。
                num >>= 6;//63的二进制为:111111
                // 为什么要右移6位?因为数组里面一共有64个有效字符。为什么要除5取余?因为一个int型要用4个字节表示,也就是32位。
            }
            for (int i = 0; i < length / 5; i++) {
                num = random.nextInt();
                for (int j = 0; j < 5; j++) {
                    temp[index++] = ch[num & 63];
                    num >>= 6;
                }
            }
            return new String(temp, 0, length);
        }
        else if (length == 0) {
            return "";
        }
        else {
            throw new IllegalArgumentException();
        }
    }

    public static void main(String[] args) {
        System.out.println(createRandomString(16));
    }
}

调用RandomStr类的静态方法createRandomString,传入你需要创建随机字符串的长度,即可生成

3. 生成当前时间的秒数(timestamp):

//生成的时间戳单位为毫秒,除以1000变为秒数
long timestamp = System.currentTimeMillis() / 1000;

4. 按照指定顺序拼接字符串,然后使用sha1算法生成签名(注意:拼接的字符串必须是如下顺序  "jsapi_ticket="+ jsapi_ticket +"&noncestr="+ noncestr +"×tamp="+ timestamp +"&url="+url)

long timestamp = System.currentTimeMillis() / 1000;    //当前的时间戳(秒)
String noncestr = RandomStr.createRandomString(16);    //长度为16的随机字符串
String url = "----"; //需要调用sdk的页面路径
String jsapi_ticket = jsapiTicket;    //通过调用微信接口生成的jsapi_ticket
/**加密字符串*/
String str = "jsapi_ticket="+ jsapi_ticket +"&noncestr="+ noncestr +"×tamp="+ timestamp +"&url="+url;
/**通过加密拼接的字符串获取签名*/
String signature = Sha1.encode(str);    //signature即为生成的签名,Sha1算法代码见下方

sha1算法的代码如下:

public class Sha1 {

    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5',
            '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    private static String getFormattedText(byte[] bytes) {
        int len = bytes.length;
        StringBuilder buf = new StringBuilder(len * 2);
        // 把密文转换成十六进制的字符串形式
        for (int j = 0; j < len; j++) {
            buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
            buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
        }
        return buf.toString();
    }

    public static String encode(String str) {
        if (str == null) {
            return null;
        }
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
            messageDigest.update(str.getBytes());
            return getFormattedText(messageDigest.digest());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

如此一来,我们需要的签名,生成签名的时间戳和随机字符串都有了,前台通过调用Controller就可以直接获取到,然后进行wx.config()即可进行授权验证;

四、调用wx.config()来做微信接口调用的验证

在conroller 中添加一个请求,用来调用以上代码model得到签名(signature)、生成签名的时间戳(timestamp)和字符串(nonceStr)

然后在前台需要调用微信接口的页面中调用,拿到对应参数,然后进行wx.config(),校验成功后就可以在wx.ready()的回调中调用微信的接口了;(以微信扫一扫为例)

$.ajax({
    type:"post",
    url:"/getWxConfig.do",  //获取wx.config 相关信息
    success:function(result){
       console.log(result)
       wx.config({
            // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
            debug: false,
            // 必填,公众号的唯一标识
            appId: result.data.appId,
            // 必填,生成签名的时间戳
            timestamp: result.data.timestamp,
            // 必填,生成签名的随机串
            nonceStr:result.data.nonceStr,
            // 必填,签名,见附录1
            signature:result.data.signature,
            // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
            jsApiList : ['scanQRCode' ]
        });
    },
    error : function(err){
        console.error(err)
    }
})

/**wx.config()错误后的返回信息*/
wx.error(function(res) {
    console.log("出错了:" + res.errMsg);
});

/**微信准备好之后,所有调用微信接口的内容需要放到这里*/
wx.ready(function() {
    //点击按钮扫描二维码
    $('#scanQRCode').on('click',function(){
        wx.scanQRCode({
            needResult : 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
            scanType : [ "qrCode"], // 可以指定扫二维码还是一维码,默认二者都有
            success : function(res) {
                console.log(res);   //扫码的结果
            }
        });
    })
});

最后,大功告成!

公众号H5开发调用微信 js-sdk 的那些事_第1张图片

FAQ

   1. 调用wx.config()返回 config:fail,Error: invalid signature : 很可能是因为后台生成签名的url和当前调用wx.config()的页面不一致,一定要注意是当前访问的全路径(带http或https协议头和页面路径)

   2. 调用wx.config()返回 config:fail,Error: invalid url domain : 很可能是因为在公众号管理平台中没有将当前域名设置为合法域名,或者没有将下载的凭证放置到域名访问的web服务器根路径中(要保证通过域名+凭证.txt能够访问到凭证中的内容);再者,如果是本地调试的话,需要将你的合法域名配置到host中,然后还是采用域名的方式去访问,才能生效;

 

你可能感兴趣的:(微信,微信公众号H5开发)