解决小程序内嵌h5缓存问题
该解决方案非原创,出处是收费的,地址就不贴出来了。
下文是结合公司项目重新整理的,分享一下,独乐了不如众乐乐。
缓存问题
大家都知道,浏览器缓存是个非常有用的特性,它能够提升性能、减少延迟,还可以减少带宽、降低网络负荷。关于浏览器的缓存机制可以总结成下面 2 句话:
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
更进一步,我们可以粗略了解一下强制缓存和协商缓存的运行机理。若强制缓存(Expires 和 Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified/If-Modified-Since 和 Etag/If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回 200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回 304,继续使用缓存。这段文字是想让读者拓展一下知识面,如果想要更输入了解,可以通过上面的一些关键字(强缓存、协商缓存、Expire、Cache-Control 等)去查找更详细的资料。
微信的 web-view 组件就是一个嵌在小程序里的浏览器,它在缓存上并没有完全遵照上述的规则,也即它的缓存并不能及时得到清理。想必下面的操作大家都有尝试过:
- 手动退出小程序,再次进入;
- 将微信从后台退出再打开并重新进入小程序;
- 修改 Nginx 关于 Cache-Control 的配置;
- 用 debugx5.qq.com 手动清除安卓微信浏览器缓存;
- iOS 利用微信自带清楚缓存功能。
无法及时刷新缓存会导致发布了最新的页面,而小程序里仍然是以前的页面,从而会带来许多问题,如前后端的数据不一致,新的特性无法及时起作用,修改的问题没有得到解决等等。这里需要说明一下:web-view 在过一段时间(时间不定,一天或者几小时,无明显规律)是可以进行缓存刷新的,而本 Chat 要解决的是及时刷新的问题。
解决问题
解决思路
浏览器访问资源是通过 URL 地址,如果内嵌 H5 的地址不发生变化,那么 web-view 访问资源会从缓存里取,而缓存里并没有最新的数据,这就导致了服务端的最新资源根本无法到达浏览器,这也就解释了为什么修改 Nginx 的 Cache-Control 配置也无法生效的原因。所以,要想彻底解决及时刷新,必须让 web-view 去访问新的地址。我们假定小程序访问的 URL 地址为:
https://www.yourdomain.com/101/#/index
其中 101 就是构建的一个版本号,每次递增,保证次次不同即可。
先来看一遍大概的流程图
解决小程序内嵌h5缓存问题流程图.png小程序获取最新版本号
- 在原生小程序中,我们利用 app 的 onShow 钩子函数 [1] 来完成最新的 URL 获取,同时还要保证只有获取了版本号之后才能加载其他的页面,因此这里要用到同步接口调用 [2]。请参考下面代码:
//这里加入同步请求到服务器获取最新路径
onShow: function (options) {
this.getFEVersion()
},
getFEVersion: function () {
//下面是利用Promise进行同步调用的写法
return new Promise(function (resolve, reject) {
wx.request({
//下面是本机调试的一个地址,上线时请改成自己服务端的地址
url: ‘http://192.168.0.168:8090/getFEVersion’,
data: {},
method: ‘POST’,
header: {
‘content-type’: ‘application/json’,
},
success: function (res) {
if (res.data.success) {
const app = getApp();
//res.data.version 是从服务端返回的最新fe的版本号,即上面的数字101
app.globalData.feUrl = ‘https://www.yourdomain.com/' + res.data.version + ‘/#/index’
}
resolve();
},
fail: function (error) {
console.log(error);
reject();
}
})
});
},
- Taro中,同样的思路
// 接口
const H5Rquest = {
// 获取小程序内嵌h5对应的版本号(对应nginx匹配的文件夹路径)
getH5Version() {
return fetch.get(`/api/xxx/${version}/xxx/getH5Version`);
},
};
// 调用接口
componentDidMount() {
H5Rquest.getH5Version()
.then((res) => {
console.log(res);
if (!res || !res.data) {
return WxActions.fnShowToast('无法获取版本号');
}
let params = getCurrentUserParams(this.props.chooseChild);
params = {
...params,
gitlen: baseH5,
id: this.$router.params.noticeId,
isEdit: this.$router.params.isEdit,
};
const src = `${h5HostName}/${res.data}/#/newsBulletin_announce?${qs.stringify(params)}`;
console.log(src);// 调试模式可以看到跳转的路径是否跟需要的一致
this.setState({src});
})
.catch((err) => {
console.log(err);// 报错信息已经统一拦截输出
});
}
// html代码
render() {
const {src} = this.state;
return <WebView src={src} />;
}
Nginx 配置
Nginx 正则规则,~ 表示区分大小写的正则匹配,\d 是数字的匹配的正则表达式,正好可以匹配 101 这样的数字表达式。
server{
##其他配置
##……
##其他配置
location ~ /\d {
root /mnt/projects/FE/;
}
}
在服务器上存放项目的路径为 /mnt/projects/FE/101,下图是 Vue 项目构建好的样子。
000.png服务端接口
下面是服务端接口的参考代码,我们可以将最新的版本号存入数据库:
@RequestMapping(value = “getFEVersion”, method = RequestMethod.POST)
*public* Object *getFEVersion*(HttpServletRequest request) {
ResponseVo responseVo = *new* ResponseVo();
//从数据库中获得最新的版本号
responseVo.setSuccess(“101”);
*return* responseVo;
}
总结
到此,我们将小程序发布上线,重启 Nginx 使得配置生效,后续每次构建前端工程,都生成一个新的版本号,如 102、103 等等,将该版本号填入数据库中,后端接口都会及时返回最新版本号,使得小程序总是以最新的路径加载,从而做到 web-view 都能及时的加载最新的 H5 页面。
另外,在链接后面添加时间戳应该也是可以的,具体没验证过,不过哪怕可以,也会造成性能浪费的问题,因为相当于每次进入都要重新下载代码,会导致用户体验不好。代码如下
const src = `${h5HostName}/index.html?stamp=${new Date().getTime()}#/WatchBaby?${qs.stringify(
skipParams,
)}`;
console.log(src);
综上所述,通过接口生成版本号是比较保险的。
注解
[1]:在 app 的 onShow 钩子函数是小程序启动,或从后台进入前台显示时触发,而且它是所有其他页面 onShow 钩子函数中第一个被执行,因此在这里进行最新版本获取。
[2]:由于需要获得最新版本才能进行其他业务,因此使用同步接口,以此来保证各个生命周期执行的顺序性。