如果你在开发微信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接口的页面引入如下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)
在调用微信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()即可进行授权验证;
在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); //扫码的结果
}
});
})
});
最后,大功告成!
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中,然后还是采用域名的方式去访问,才能生效;