记录--微信调用jssdk全流程详解
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
微信调用jssdk全流程详解
系统框架使用的是前后端分离,前端使用vant,后端是springboot
一、网页授权的时序图
二、公众号配置
1. 绑定域名
登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。也就是这样:
点击设置之后,弹出这样一个输入框,输入服务器所在的域名:
2:引入js文件
直接在你的页面里引入js文件就行
<script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
三、前端方法
1. 初始化方法,从后台获取基本的参数
前端进入需要扫一扫功能的页面时候,在mounted方法里面,执行微信配置getWxConfig(),此方法主要是获取jssdk所需要的参数,先检查本地缓存的是否过期,过期则请求后台获取
export function getWxConfig() { //判断signature是否过期 if (expireSign()) { let data = {}; data.appId = localStorage.getItem('appId'); data.timestamp = localStorage.getItem('timestamp'); data.nonceStr = localStorage.getItem('nonceStr'); data.signature = localStorage.getItem('signature'); setWxConfig(data); } else { //如果过期了,请求后台获取 let url = location.href.split('#')[0]; //获取锚点之前的链接,防止出现invalid signature错误 wxSign({ url: url }) .then(res => { console.log(res); if (res.code == 200) { localStorage.setItem('appId', res.data.appId); localStorage.setItem('timestamp', res.data.timestamp); localStorage.setItem('nonceStr', res.data.nonceStr); localStorage.setItem('signature', res.data.signature); localStorage.setItem('expireSignTime', res.data.expireTime); setWxConfig(res.data); } else { localStorage.removeItem('expireSignTime'); Toast.fail('网络故障,请退出重新加载页面'); } }) .catch(error => { localStorage.removeItem('expireSignTime'); Toast.fail('网络故障,请退出重新加载页面'); }); } }
2:注入config配置
上面获取到后台的参数后,在页面使用wx.config接口注入权限验证配置
function setWxConfig(data) { console.log(data); wx.config({ debug: false, // true是开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: data.appId, // 必填,公众号的唯一标识 timestamp: data.timestamp, // 必填,签名的时间戳,后台生成的 nonceStr: data.nonceStr, // 必填,签名的随机串,后台生成的 signature: data.signature, // 必填,签名,后台生成的 jsApiList: ['scanQRCode'] // 必填,需要使用的JS接口列表,scanQRCode是调用扫一扫二维码 }); wx.error(function(res) { localStorage.removeItem('expireSignTime'); Toast.fail('网络故障,请退出重新页面'); }); }
3. 方法调用
config如果不报错,则在 wx.ready里面调用jssdk的方法
wx.ready(function () { wx.scanQRCode({ needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果 success: function (res) { let data = res.resultStr; // 当needResult 为 1 时,扫码返回的结果 let code = codeFormat(data); that.pointCode = code; that.saveSinglePoint(); }, }); });
四、后端方法
1. 获取access_token和jsapi_Ticket,缓存使用了文件的方式,也支持redis等方式
/** * 获取access_token和jsapi_ticket **/ public AppWechatEntity getJsapiTicket() { //logger.debug("--------------开始执行getJsapiTicket方法--------------"); //定义过期时间 AppWechatEntity appWechatEntity = new AppWechatEntity(); String accessTokenString = ""; String jsapiTicketString = ""; String jsapi_ticket = ""; String access_token = ""; jsapiTicketString = readWechatTokenFile(getJsapiTicketFilePath()); if (!StringUtils.isEmpty(jsapiTicketString)) { appWechatEntity = JSONObject.parseObject(jsapiTicketString, AppWechatEntity.class); long expireTime = appWechatEntity.getExpire_time(); long curTime = create_timestamp(); if (expireTime >= curTime && StrUtil.isNotEmpty(appWechatEntity.getJsapi_ticket())) { //logger.debug("已有的jsapi_ticket=" + jsapi_ticket); return appWechatEntity; } } long timestamp = create_timestamp() + 7000;//过期时间是2小时(7200s) access_token = getAccessTokenData("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + configBeanValue.appid + "&secret=" + configBeanValue.secret); Map accessTokenMap = new HashMap(); accessTokenMap.put("expire_time", timestamp); accessTokenMap.put("access_token", access_token); accessTokenString = JSONObject.toJSONString(accessTokenMap); jsapi_ticket = getJsapiTicketData("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + access_token + "&type=jsapi"); Map jsapiTicketMap = new HashMap(); jsapiTicketMap.put("expire_time", timestamp); jsapiTicketMap.put("jsapi_ticket", jsapi_ticket); jsapiTicketString = JSONObject.toJSONString(jsapiTicketMap); // 写文件 try { FileUtils.writeStringToFile(new File(getAccessTokenFilePath()), accessTokenString, CharsetUtil.CHARSET_UTF_8); FileUtils.writeStringToFile(new File(getJsapiTicketFilePath()), jsapiTicketString, CharsetUtil.CHARSET_UTF_8); //logger.debug("写入文件成功"); } catch (IOException e) { log.debug("写文件异常:" + e.getMessage()); e.printStackTrace(); } appWechatEntity.setJsapi_ticket(jsapi_ticket); appWechatEntity.setExpire_time(timestamp); appWechatEntity.setAccess_token(access_token); //logger.debug("--------------结束执行getJsapiTicket方法--------------"); return appWechatEntity; }
2.根据jsapi_ticket获取signature
上面获取了jsapi_ticket之后,使用jsapi_ticket,noncestr,timestamp和前端传入的url组装成签名字符串,使用sha1进行加密,返回到前端
/** * 生成signature **/ @Override public AppWechatEntity sign(String url) { Map<String, String> ret = new HashMap(); String nonceStr = create_nonce_str(); String timestamp = Long.toString(create_timestamp()); String string1; String signature = ""; //获取jsapi_ticket和过期时间 AppWechatEntity appWechatEntity = getJsapiTicket(); String jsapiTicket = appWechatEntity.getJsapi_ticket(); Long expireTime = appWechatEntity.getExpire_time(); //注意这里参数名必须全部小写,且必须有序 string1 = "jsapi_ticket=" + jsapiTicket + "&noncestr=" + nonceStr + "×tamp=" + timestamp + "&url=" + url; try { MessageDigest crypt = MessageDigest.getInstance("SHA-1"); crypt.reset(); crypt.update(string1.getBytes(StandardCharsets.UTF_8)); signature = byteToHex(crypt.digest()); } catch (Exception e) { e.printStackTrace(); } appWechatEntity.setAppId(configBeanValue.appid); appWechatEntity.setExpire_time(expireTime); appWechatEntity.setNonceStr(nonceStr); appWechatEntity.setTimestamp(timestamp); appWechatEntity.setSignature(signature); return appWechatEntity; }
3. 后端全部的代码
/** * 生成signature **/ @Override public AppWechatEntity sign(String url) { Map<String, String> ret = new HashMap(); String nonceStr = create_nonce_str(); String timestamp = Long.toString(create_timestamp()); String string1; String signature = ""; //获取jsapi_ticket和过期时间 AppWechatEntity appWechatEntity = getJsapiTicket(); String jsapiTicket = appWechatEntity.getJsapi_ticket(); Long expireTime = appWechatEntity.getExpire_time(); //注意这里参数名必须全部小写,且必须有序 string1 = "jsapi_ticket=" + jsapiTicket + "&noncestr=" + nonceStr + "×tamp=" + timestamp + "&url=" + url; try { MessageDigest crypt = MessageDigest.getInstance("SHA-1"); crypt.reset(); crypt.update(string1.getBytes(StandardCharsets.UTF_8)); signature = byteToHex(crypt.digest()); } catch (Exception e) { e.printStackTrace(); } appWechatEntity.setAppId(configBeanValue.appid); appWechatEntity.setExpire_time(expireTime); appWechatEntity.setNonceStr(nonceStr); appWechatEntity.setTimestamp(timestamp); appWechatEntity.setSignature(signature); return appWechatEntity; } /** * 获取jsapi_ticket **/ public AppWechatEntity getJsapiTicket() { //logger.debug("--------------开始执行getJsapiTicket方法--------------"); //定义过期时间 AppWechatEntity appWechatEntity = new AppWechatEntity(); String accessTokenString = ""; String jsapiTicketString = ""; String jsapi_ticket = ""; String access_token = ""; jsapiTicketString = readWechatTokenFile(getJsapiTicketFilePath()); if (!StringUtils.isEmpty(jsapiTicketString)) { appWechatEntity = JSONObject.parseObject(jsapiTicketString, AppWechatEntity.class); long expireTime = appWechatEntity.getExpire_time(); long curTime = create_timestamp(); if (expireTime >= curTime && StrUtil.isNotEmpty(appWechatEntity.getJsapi_ticket())) { //logger.debug("已有的jsapi_ticket=" + jsapi_ticket); return appWechatEntity; } } long timestamp = create_timestamp() + 7000;//过期时间是2小时(7200s) access_token = getAccessTokenData("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + configBeanValue.appid + "&secret=" + configBeanValue.secret); Map accessTokenMap = new HashMap(); accessTokenMap.put("expire_time", timestamp); accessTokenMap.put("access_token", access_token); accessTokenString = JSONObject.toJSONString(accessTokenMap); jsapi_ticket = getJsapiTicketData("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + access_token + "&type=jsapi"); Map jsapiTicketMap = new HashMap(); jsapiTicketMap.put("expire_time", timestamp); jsapiTicketMap.put("jsapi_ticket", jsapi_ticket); jsapiTicketString = JSONObject.toJSONString(jsapiTicketMap); // 写文件 try { FileUtils.writeStringToFile(new File(getAccessTokenFilePath()), accessTokenString, CharsetUtil.CHARSET_UTF_8); FileUtils.writeStringToFile(new File(getJsapiTicketFilePath()), jsapiTicketString, CharsetUtil.CHARSET_UTF_8); //logger.debug("写入文件成功"); } catch (IOException e) { log.debug("写文件异常:" + e.getMessage()); e.printStackTrace(); } appWechatEntity.setJsapi_ticket(jsapi_ticket); appWechatEntity.setExpire_time(timestamp); appWechatEntity.setAccess_token(access_token); //logger.debug("--------------结束执行getJsapiTicket方法--------------"); return appWechatEntity; } public String getAccessToken() { //logger.debug("--------------开始执行getAccessToken方法--------------"); String access_token = ""; String accessTokenString = ""; AppWechatEntity appWechatEntity = new AppWechatEntity(); accessTokenString = readWechatTokenFile(getAccessTokenFilePath()); if (StringUtils.isNotEmpty(accessTokenString)) { appWechatEntity = JSONObject.parseObject(accessTokenString, AppWechatEntity.class); access_token = appWechatEntity.getAccess_token(); long expireTime = appWechatEntity.getExpire_time(); long curTime = create_timestamp(); if (expireTime >= curTime) { //logger.debug("已有的access_token=" + access_token); return access_token; } } long timestamp = create_timestamp() + 6000;//过期时间是2小时,但是可以提前进行更新,防止前端正好过期 access_token = getAccessTokenData("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + configBeanValue.appid + "&secret=" + configBeanValue.secret); Map accessTokenMap = new HashMap(); accessTokenMap.put("expire_time", timestamp); accessTokenMap.put("access_token", access_token); accessTokenString = JSONObject.toJSONString(accessTokenMap); // 写文件 try { FileUtils.writeStringToFile(new File(getAccessTokenFilePath()), accessTokenString, CharsetUtil.CHARSET_UTF_8); //logger.debug("写入文件成功"); } catch (IOException e) { log.debug("写文件异常:" + e.getMessage()); e.printStackTrace(); } //logger.debug("新的access_token=" + access_token); //logger.debug("--------------结束执行getAccessToken方法--------------"); return access_token; } private String readWechatTokenFile(String filePath) { String content = ""; try { if (new File(filePath).exists()) { FileReader fileReader = new FileReader(filePath, CharsetUtil.CHARSET_UTF_8); content = fileReader.readString(); } else { new File(filePath).createNewFile(); } } catch (IOException e) { log.error("读文件异常:" + e.getMessage()); e.printStackTrace(); } return content; } private String getAccessTokenData(String url) { String str = ""; String result = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8); if (StringUtils.isBlank(result)) return str; str = parseData("access_token", "expires_in", result); return str; } private String getJsapiTicketData(String url) { String str = ""; String result = HttpUtil.get(url, CharsetUtil.CHARSET_UTF_8); if (StringUtils.isBlank(result)) return str; str = parseData("ticket", "expires_in", result); return str; } private String parseData(String tokenName, String expiresInName, String data) { String tokenConent = ""; JSONObject jsonObject = JSONObject.parseObject(data); try { tokenConent = jsonObject.get(tokenName).toString(); if (StringUtils.isEmpty(tokenConent)) { log.error("token获取失败,获取结果" + data); return tokenConent; } } catch (Exception e) { log.error("token 结果解析失败,token参数名称: " + tokenName + "有效期参数名称:" + expiresInName + "token请求结果:" + data); e.printStackTrace(); return tokenConent; } return tokenConent; } private String byteToHex(final byte[] hash) { Formatter formatter = new Formatter(); for (byte b : hash) { formatter.format("%02x", b); } String result = formatter.toString(); formatter.close(); return result; } private String create_nonce_str() { return IdUtil.simpleUUID(); } private Long create_timestamp() { return System.currentTimeMillis() / 1000; } private String getJsapiTicketFilePath() { return configBeanValue.tokenpath + "/" + configBeanValue.appid + "_jsapiTicket.txt"; } private String getAccessTokenFilePath() { return configBeanValue.tokenpath + "/" + configBeanValue.appid + "_accessToken.txt"; }
五.vue weixin-js-sdk进行微信分享
第一步:安装weixin-js-sdk
npm install weixin-js-sdk
第二步:
在assets文件下新建个common文件夹 ,然后再新建个utils.js文件
import wx from "weixin-js-sdk"; /* * 微信分享 * 获取微信加签信息 * @param{data}:获取的微信加签 * @param{shareData}:分享配置参数 */ export const wexinShare = (data, shareData) => { let appId = data.appId; let timestamp = data.timestamp; let nonceStr = data.nonceStr; let signature = data.signature; wx.config({ debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: appId, // 必填,公众号的唯一标识 timestamp: timestamp, // 必填,生成签名的时间戳 nonceStr: nonceStr, // 必填,生成签名的随机串 signature: signature, // 必填,签名,见附录1 jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); wx.ready(function () { //分享到朋友圈”及“分享到QQ空间” wx.updateTimelineShareData({ title: shareData.title, // 分享标题 link: shareData.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致 imgUrl: shareData.imgUrl, // 分享图标 success: function (res) { // 设置成功 // console.log("分享成功返回的信息为:", res); } }) //“分享给朋友”及“分享到QQ” wx.updateAppMessageShareData({ title: shareData.title, // 分享标题 desc: shareData.desc, // 分享描述 link: shareData.link, // 分享链接 imgUrl: shareData.imgUrl, // 分享图标 success: function (res) { // console.log("分享成功返回的信息为:", res);; } }) }); wx.error(function (res) { // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。 console.log('验证失败返回的信息:', res); }); }
第三步:调用
import {wexinShare} from "@/common/utils.js";
//请求微信配置参数接口(获取签名),由后台给接口给 get_share(){ var that = this; var urls = that.$qs.stringify(window.location.href.split('#')[0]); //看后台请求接口是get/post that.$axios.post("/xxx",urls).then((res) => { if (res.data) { //微信加签 var obj = { appId: res.data.appId, nonceStr: res.data.nonceStr, signature: res.data.signature, timestamp: res.data.timestamp } //分享数据,这段主要是为了在hash模式下分享出去的链接不被浏览器截取,保证完全把链接分享出去 var shareWxLink = window.location.href.split('#')[0] + 'static/redirect.html?redirect=' + encodeURIComponent(window.location.href) + '&pid_uid=' + userinfo.uid; // console.log('shareWxLink', shareWxLink) let shareData = { title: '标题', // 分享标题 link: shareWxLink, imgUrl: 'http://xxx.jpg', // 分享图标 desc: '这里填写简介文字' } //引用 wexinShare(obj, that, shareData); } else { that.$toast('获取sdk参数失败!'); } }) }