WEB项目部署发版-通知用户在线更新方案
1.背景
vue是单页面应用(SPA) index.html
web页面初始化的时候加载相应的HTML、js、CSS,一旦页面加载完成,SPA不会因为用户的操作进行页面的重新加载。
优点: 用户体验好、快、内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
缺点:初次加载耗时多;发版后存在缓存问题;
2.浏览器的缓存机制
对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤;
浏览器缓存可以帮助我们在第一和第三步骤中优化性能。
比如:直接使用缓存而不发起请求
2.1 缓存位置
-
Service Worker
-
Memory Cache
-
Disk Cache
-
Push Cache
Service Worker:使用Service Worker 的话,必须使用HTTPS协议来保障安全。可自由控制缓存哪些文件,并且缓存是持续性的。
一般分为三个步骤:
-
注册Service Worker
-
监听到install事件以后就可以缓存需要的文件
-
在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存直接读取文件,否则就去请求数据。
Memory Cache:
Memory Cache
也就是内存中的缓存
读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。一旦我们关闭Tab页面,内存中的缓存也就被释放。
Disk Cache:
Disk Cache
也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中。
在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。
根据 HTTP Herder 中的字段判断哪些资源需要缓存。
Push Cache:
Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。
它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右。
2.2 强制缓存与协商缓存
发起HTTP请求将缓存过程分为两个部分 分别是强制缓存、协商缓存
强制缓存:向浏览器缓存查找请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过 程。
强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。Cache-Conctrol的优先级比Expires高。
协商缓存:就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,
Cache-Control:
public
:表示响应可以被客户端和代理服务器缓存。
private
: 表示响应只可以被客户端缓存。
max-age=xxx
:缓存xxx秒就过期,需要重新请求。
no-store
:不缓存任何响应。
no-cache
:资源被缓存,但是立即失效,下次会发起请求验证资源是否过期。
max-stale=xxx
:xxx秒内,即使缓存过期,也使用该缓存。
min-fresh=xxx
:希望在xxx秒内获取最新的响应。
3.方案设计
nginx.conf 文件配置 解决缓存问题
因为本人前端使用微服务,故只用设置主应用为no-cache即可。
只更新前端的时候 用户刷新问题
version.js const fs = require('fs') // 引入文件模块 const Timestamp = new Date().getTime() fs.writeFile('public/version.json', '{"version":' + Timestamp + '}\n', function (err) { if (err) { return console.log(err) } })
package.json "scripts": { "start": "vue-cli-service serve --mode development", "build": "vue-cli-service build", "test-build": "node version.js && vue-cli-service build --mode production_test", "lint": "vue-cli-service lint" },
vue.config.js const bpmVersion = process.env.NODE_ENV === 'production_test' ? require('./public/version.json') : { version: 'dev' } pages: { index: { // page 的入口 entry: 'src/main.js', // 模板来源 template: 'public/index.html', // 在 dist/index.html 的输出 filename: 'index.html', version: bpmVersion.version, // 版本号 // 在这个页面中包含的块,默认情况下会包含 // 提取出来的通用 chunk 和 vendor chunk。 chunks: ['chunk-vendors', 'chunk-common', 'index'] } },
index.html <meta name="version" content="<%= htmlWebpackPlugin.options.version%>" id="BPMVersion" />
路由守卫 import { notification, Button } from 'ant-design-vue'; import { ExclamationCircleOutlined } from '@ant-design/icons-vue'; import { h, } from 'vue'; const IS_UPDATEMODAL = false//是否弹窗显示 route.afterEach(async (to, from) => { if (to.path !== from.path && process.env.NODE_ENV === 'production_test') { const checkVersion = await store.dispatch('common/checkVersion') if (checkVersion == false) { if (IS_UPDATEMODAL) { updateModal() } else { window.location.reload(); } } } }) const updateModal = () => { const key = `open${Date.now()}`; notification.config({ maxCount: 1, }); notification.open({ duration: null, icon: () => h(ExclamationCircleOutlined, { style: 'color: #ffa900' }), message: '发现新版本', description: '检测到到最新版本,刷新后立即使用', btn: () => h(Button, { type: 'primary', size: 'small', onClick: () => window.location.reload(), }, { default: () => '立即刷新', }), key, onClose: close, }); }
store import dayjs from "dayjs"; async checkVersion() { return new Promise(resolve => { Axios.get('/version.json?v=' + new Date().getTime(), { headers: { 'Cache-Control': 'no-cache' }, baseURL: window.location.origin }) .then(res => { const version = res.data.version const clientVersion=Number(document.querySelector('#BPMVersion').content || '') console.info('%c Environment ' + '%c ' + process.env.NODE_ENV + ' ', 'padding: 1px; border-radius: 3px 0 0 3px;color: #fff; background:#606060', 'padding: 1px; border-radius: 0 3px 3px 0;color: #fff; background:#42c02e') console.info('%c Build Date ' + '%c ' + dayjs(new Date(clientVersion)).format('yyyy-MM-dd hh:mm:ss') + ' ', 'padding: 1px; border-radius: 3px 0 0 3px;color: #fff; background:#606060', 'padding: 1px; border-radius: 0 3px 3px 0;color: #fff; background:#1475b2') console.info('%c Last Build Date ' + '%c ' + dayjs(new Date(version)).format('yyyy-MM-dd hh:mm:ss') + ' ', 'padding: 1px; border-radius: 3px 0 0 3px;color: #fff; background:#606060', 'padding: 1px; border-radius: 0 3px 3px 0;color: #fff; background:#1475b2') // -end resolve(version === clientVersion) }) }) }