企业微信JS-SDK是企业微信面向网页开发者提供的基于企业微信内的网页开发工具包。
通过使用企业微信JS-SDK,网页开发者可借助企业微信高效地使用拍照、选图、语音、位置等手机系统的能力,同时可以直接使用企业微信分享、扫一扫等企业微信特有的能力,为企业微信用户提供更优质的网页体验。

使用说明

所有的JS接口只能在企业微信应用的可信域名下调用(包括子域名),且可信域名必须有ICP备案且在管理端验证域名归属。
验证域名归属的方法在企业微信的管理后台“我的应用”里,进入应用,设置应用可信域名。

步骤一:引入js文件

在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.2.0.js
在vue中使用npm进行安装

	npm install weixin-js-sdk

使用require引入

	var wx = require("weixin-js-sdk");

步骤二:通过config接口注入权限验证配置

所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用)。

wx.config({beta: true,// 必须这么写,否则wx.invoke调用形式的jsapi会有问题debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。appId: '', // 必填,企业微信的corpIDtimestamp: , // 必填,生成签名的时间戳nonceStr: '', // 必填,生成签名的随机串signature: '',// 必填,签名,见 附录-JS-SDK使用权限签名算法jsApiList: [] // 必填,需要使用的JS接口列表,凡是要调用的接口都需要传进来
});

去后端获取config接口必要的参数

权限的签名算法需要使用到当前网页的URL,不包含#及其后面部分
前端获取当前网页的url

location.href.split("#")[0] ///向服务端提供授权url参数,并且不需要#后面的部分

后端新建一个jssdk配置参数的bean类JssdkConfig

	public class JssdkConfig {private String appId; //企业微信的corpIDprivate String timestamp; //生成签名的时间戳private String nonceStr; //生成签名的随机串private String signature; //签名,见 附录-JS-SDK使用权限签名算法getter/setter方法}

获取企业微信的corpID,签名的时间戳,签名的随机串:

jssdkConfig.setAppId(WeChatParames.corpId);//必填,企业微信的corpID
String timestamp = Long.toString(System.currentTimeMillis() / 1000); // 必填,生成签名的时间戳
String nonceStr = UUID.randomUUID().toString(); // 必填,生成签名的随机串
jssdkConfig.setTimestamp(timestamp);
jssdkConfig.setNonceStr(nonceStr);

获取签名
生成签名需要jsapi_ticket,所以必须先了解一下jsapi_ticket,jsapi_ticket是H5应用调用企业微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限(一小时内,一个企业最多可获取400次,且单个应用不能超过100次),频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,开发者必须在自己的服务全局缓存jsapi_ticket

获取签名的步骤如下

  • 获取企业的jsapi_ticket(jsapi_ticket必须进行缓存,每次获取都得先去缓存中获取,缓存中没有的话再去重新请求)
//获取企业的jsapi_ticket
String jsapiTicket = this.getJsapiTicket();//获取企业的jsapi_ticket具体方法
private String getJsapiTicket() {if(this.jsapiTicket != null) {return this.jsapiTicket;}if(缓存中存储的jsapi_ticket没有或者已经过期) {String accessToken = this.accessTokenService.getWeChatAccessToken().getAccessToken();String url = WeChatParames.jsapiTicketUrl.replace("{ACCESS_TOKEN}", accessToken);HttpClientResult httpClientResult = null;String jsapiTicket = null;try {httpClientResult = HttpClientUtils.doGet(url);} catch (Exception e) {e.printStackTrace();}JSONObject jsonObject = JSONObject.parseObject(httpClientResult.getContent());if("0".equals(jsonObject.get("errcode").toString())) {jsapiTicket = jsonObject.get("ticket").toString();this.jsapiTicket = jsapiTicket;System.out.println("jsapiTicket=" + jsapiTicket);} else {System.out.println("获取企业的jsapi_ticket" + jsonObject.get("errmsg").toString());}return jsapiTicket;
} else {//直接返回缓存中jsapi_ticket
}}
  • 进行sha1签名

参与签名的参数有四个: noncestr(随机字符串), jsapi_ticket, timestamp(时间戳), url(当前网页的URL, 不包含#及其后面部分)

将这些参数使用URL键值对的格式 (即 key1=value1&key2=value2…)拼接成字符串string1。
有两个注意点:1. 字段值采用原始值,不要进行URL转义;2. 必须严格按照如下格式拼接,不可变动字段顺序。
jsapi_ticket=JSAPITICKET&noncestr=NONCESTR&timestamp=TIMESTAMP&url=URL

//进行sha1签名
String sign = "jsapi_ticket="+ jsapiTicket +"&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + url;
String signature = "";
try {// 指定sha1算法MessageDigest digest = MessageDigest.getInstance("SHA-1");digest.update(sign.getBytes());// 获取字节数组byte messageDigest[] = digest.digest();// Create Hex StringStringBuffer hexString = new StringBuffer();// 字节数组转换为 十六进制 数for (int i = 0; i < messageDigest.length; i++) {String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);if (shaHex.length() < 2) {hexString.append(0);}hexString.append(shaHex);}signature = hexString.toString();} catch (NoSuchAlgorithmException e) {e.printStackTrace();jsonModel.setSuccess(false);jsonModel.setMsg("sha1签名出错");
}
jssdkConfig.setSignature(signature);

通过ready接口处理成功验证

wx.ready(function(){// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});

通过error接口处理失败验证

wx.error(function(res){// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});

Config信息验证成功后就可以调用js接口了,

接口调用说明

所有接口通过wx对象(也可使用jWeixin对象)来调用,参数是一个对象,除了每个接口本身需要传的参数之外,还有以下通用参数:
success:接口调用成功时执行的回调函数。
fail:接口调用失败时执行的回调函数。
complete:接口调用完成时执行的回调函数,无论成功或失败都会执行。
cancel:用户点击取消时的回调函数,仅部分有用户取消操作的api才会用到。
trigger: 监听Menu中的按钮点击时触发的方法,该方法仅支持Menu中的相关接口。

注意:不要尝试在trigger中使用ajax异步请求修改本次分享的内容,因为客户端分享操作是一个同步操作,这时候使用ajax的回包会还没有返回

以上几个函数都带有一个参数,类型为对象,其中除了每个接口本身返回的数据之外,还有一个通用属性errMsg,其值格式如下:

调用成功时:”xxx:ok” ,其中xxx为调用的接口名
用户取消时:”xxx:cancel”,其中xxx为调用的接口名
调用失败时:其值为具体错误信息

基础接口

判断当前客户端版本是否支持指定JS接口

wx.checkJsApi({jsApiList: ['chooseImage'], // 需要检测的JS接口列表,所有JS接口列表见附录2,success: function(res) {// 以键值对的形式返回,可用的api值true,不可用为false// 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"}}
});

预览文件接口

企业微信api文档使用下列方法进行调用,

wx.previewFile({url: '', // 需要预览文件的地址(必填,可以使用相对路径)name: '', // 需要预览文件的文件名(不填的话取url的最后部分)size: 1048576 // 需要预览文件的字节大小(必填)
});

实际上调用不起来,需要使用invoke方式进行调用,具体方法如下

wx.invoke("previewFile", {url: fileUrl,size: size //大小必须与文件实际大小保持一致,不然出现其他问题(预览后返回点击打开文件按钮出现继续下载按钮)});

此接口将url对应的文件下载后,在内置浏览器中预览。目前支持图片、音频、视频、文档等格式的文件。仅企业微信APP手机端可用。注意size的大小必须为文件的实际大小,不然预览文件不能正常预览

常见问题

企业微信jssdk文件预览url填的是文件服务器存放文件的地址,而在实际项目中有时候是存在oracle数据库中的

文件预览的url填写下载文件流的后端方法。

fileUrl ="http://域名/项目名/common/文件流接口名?idKey=附件id";

后端设置响应头

response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(name, "UTF-8") + "\"");
response.setCharacterEncoding("utf-8");
response.setContentType("application/octet-stream-dummy; charset=utf-8");

还要记得设置content-length响应头,不然显示文件的下载进度会出现问题。虽然这样已经使用jssdk的文件预览正常预览文件了。但是还有个安全性问题,因为企业微信是通过url去请求地址并下载文件,如果这个url对应的后端方法不进行登录拦截或者权限校验的话。那么只要知道这个地址和参数的话,就可以随意下载数据库中存储的文件了。如果通过cookie进行登录拦截的话,从2.4.6版本开始,IOS版企业微信浏览器升级为WkWebView,企业微信原生层面的网络请求读取不到WKWebview中设置的cookie,即使域名是相同的。在企业微信内长按保存,或者点击预览文件时,原生层面发起的网络请求将不会完整地带上所设置的Cookie,会导致图片保存失败或预览失败。暂时使用在登录过滤器中判断请求来源控制权限,

//企业微信调用文件预览,直接放行
String referer = request.getHeader("referer");
if("前端调用文件预览的地址".equals(referer)) {filterChain.doFilter(request, response);
}