记录–前端重新部署如何通知用户

  • 记录–前端重新部署如何通知用户已关闭评论
  • 110 次浏览
  • A+
所属分类:Web前端
摘要

前端构建完上线,用户还停留还在老页面,用户不知道网页重新部署了,跳转页面的时候有时候js连接hash变了导致报错跳不过去,并且用户体验不到新功能。


这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

记录--前端重新部署如何通知用户

1. 场景

前端构建完上线,用户还停留还在老页面,用户不知道网页重新部署了,跳转页面的时候有时候js连接hash变了导致报错跳不过去,并且用户体验不到新功能。

2. 解决方案

  1. 每次打包写入一个json文件,或者对比生成的script的src引入的hash地址或者etag不同,轮询调用,判断是否更新
  2. 前端使用websocket长连接,具体是每次构建,打包后通知后端,更新后通过websocket通知前端

轮询调用可以改成在前置路由守卫中调用,无需控制时间,用户有操作才去调用判断。

3. 具体实现

3.1 轮询方式

参考小满的实现稍微修改下:

class Monitor {   private oldScript: string[] = []    private newScript: string[] = []    private oldEtag: string | null = null    private newEtag: string | null = null    dispatch: Record<string, (() => void)[]> = {}    private stop = false    constructor() {     this.init()   }    async init() {     console.log('初始化')     const html: string = await this.getHtml()     this.oldScript = this.parserScript(html)     this.oldEtag = await this.getEtag()   }  // 获取html   async getHtml() {     const html = await fetch('/').then((res) => res.text())     return html   }   // 获取etag是否变化   async getEtag() {     const res = await fetch('/')     return res.headers.get('etag')   }   // 解析script标签   parserScript(html: string) {     const reg = /<script(?:s+[^>]*)?>(.*?)</scripts*>/gi     return html.match(reg) as string[]   }   // 订阅   on(key: 'update', fn: () => void) {     ;(this.dispatch[key] || (this.dispatch[key] = [])).push(fn)     return this   }   // 停止   pause() {     this.stop = !this.stop   }        get value() {     return {       oldEtag: this.oldEtag,       newEtag: this.newEtag,       oldScript: this.oldScript,       newScript: this.newScript,     }   }   // 两层对比有任一个变化即可   compare() {     if (this.stop) return     const oldLen = this.oldScript.length     const newLen = Array.from(       new Set(this.oldScript.concat(this.newScript))     ).length     if (this.oldEtag !== this.newEtag || newLen !== oldLen) {       this.dispatch.update.forEach((fn) => {         fn()       })     }   }  // 检查更新    async check() {     const newHtml = await this.getHtml()     this.newScript = this.parserScript(newHtml)     this.newEtag = await this.getEtag()     this.compare()   } }  export const monitor = new Monitor()  // 路由前置守卫中调用 import { monitor } from './monitor'  monitor.on('update', () => {   console.log('更新数据', monitor.value)   Modal.confirm({     title: '更新提示',     icon: createVNode(ExclamationCircleOutlined),     content: '版本有更新,是否刷新页面!',     okText: '刷新',     cancelText: '不刷新',     onOk() {       // 更新操作       location.reload()     },     onCancel() {       monitor.pause()     },   }) })  router.beforeEach((to, from, next) => {     monitor.check() })

3.2 websocket方式

既然后端不好沟通,那就自己实现一个完整版。

具体流程如下:

记录--前端重新部署如何通知用户

3.2.1 代码实现

服务端使用koa实现:

// 引入依赖 koa koa-router koa-websocket short-uuid koa2-cors const Koa = require('koa') const Router = require('koa-router') const websockify = require('koa-websocket') const short = require('short-uuid') const cors = require('koa2-cors')  const app = new Koa() // 使用koa2-cors中间件解决跨域 app.use(cors())  const router = new Router()  //  使用 koa-websocket 将应用程序升级为 WebSocket 应用程序 const appWebSocket = websockify(app)  // 存储所有连接的客户端进行去重处理 const clients = new Set()  // 处理 WebSocket 连接 appWebSocket.ws.use((ctx, next) => {   // 存储新连接的客户端   clients.add(ctx.websocket)   // 处理连接关闭事件   ctx.websocket.on('close', () => {     clients.delete(ctx.websocket)   })   ctx.websocket.on('message', (data) => {     ctx.websocket(666)//JSON.stringify(data)   })   ctx.websocket.on('error', (err) => {      clients.delete(ctx.websocket)   })    return next(ctx) })  // 处理外部通知页面更新的接口 router.get('/api/webhook1', (ctx) => {   // 向所有连接的客户端发送消息,使用uuid确保不重复   clients.forEach((client) => {     client.send(short.generate())   })   ctx.body = 'Message pushed successfully!' })  // 将路由注册到应用程序 appWebSocket.use(router.routes()).use(router.allowedMethods())  // 启动服务器 appWebSocket.listen(3000, () => {   console.log('Server started on port 3000') })

前端页面代码:

websocket使用vueuse封装的,保持个心跳。

import { useWebSocket } from '@vueuse/core'  const { open, data } = useWebSocket('ws://dev.shands.cn/ws', {   heartbeat: {     message: 'ping',     interval: 5000,     pongTimeout: 10000,   },   immediate: true, // 自动连接   autoReconnect: {     retries: 6,     delay: 3000,   }, })   watch(data, (val) => {   if (val.length !== '3HkcPQUEdTpV6z735wxTum'.length) return   Modal.confirm({     title: '更新提示',     icon: createVNode(ExclamationCircleOutlined),     content: '版本有更新,是否刷新页面!',     okText: '刷新',     cancelText: '不刷新',     onOk() {      // 更新操作       location.reload()     },     onCancel() {},   }) })  // 建立连接 onMounted(() => {   open() }) // 断开链接 onUnmounted(() => {   close() })

3.2.2 发布部署

后端部署:

考虑服务器上没有安装node环境,直接使用docker进行部署,使用pm2运行node程序。

  1. 写一个DockerFile,发布镜像
// Dockerfile:  # 使用 Node.js 作为基础镜像 FROM node:14-alpine  # 设置工作目录 WORKDIR /app  # 复制 package.json 和 package-lock.json 到容器中 COPY package.json ./  # 安装项目依赖 RUN npm install RUN npm install -g pm2  # 复制所有源代码到容器中 COPY . .  # 暴露端口号 EXPOSE 3000  # 启动应用程序 CMD ["pm2-runtime","app.js"]

本地进行打包镜像发送到docker hub,使用docker build -t f5l5y5/websocket-server-image:v0.0.1 .命令生成镜像文件,使用docker push f5l5y5/websocket-server-image:v0.0.1 推送到自己的远程仓库

  1. 服务器拉取镜像,运行

拉取镜像:docker pull f5l5y5/websocket-server-image:v0.0.1

运行镜像: docker run -d -p 3000:3000 --name websocket-server f5l5y5/websocket-server-image:v0.0.1

可进入容器内部查看:docker exec -it <container_id> sh # 使用 sh 进入容器

查看容器运行情况:

记录--前端重新部署如何通知用户

 进入容器内部查看程序运行情况,pm2常用命令

此时访问/api/webhook1会找到项目的对应路由下,需要配置下nginx代理转发

  1. 配置nginx接口转发
map $http_upgrade $connection_upgrade {     default upgrade;     ''      close;   } server {         listen     80;         server_name  test-pms.shands.cn;         client_max_body_size 50M;          location / {             root /usr/local/openresty/nginx/html/test-pms-admin;             try_files $uri $uri/ /index.html;         }         // 将触发的更新代理到容器的3000         location /api/webhook1 {          proxy_pass http://localhost:3000/api/webhook1;          proxy_set_header Host $host;          proxy_set_header X-Real-IP $remote_addr;         }         // websocket 配置         location /ws {           # 反向代理到容器中的WebSocket接口           proxy_pass http://localhost:3000;           # 支持WebSocket协议           proxy_http_version 1.1;           proxy_set_header Upgrade $http_upgrade;           proxy_set_header Connection "Upgrade";         }        }

3.2.3 测试

url请求api/webhook即可

记录--前端重新部署如何通知用户

4. 总结

主要实践下两种方案:

  1. 轮询调用方案:轮询获取网页引入的脚本文件的hash值或者etag来实现。这种方案的优点是实现简单,但存在性能消耗和延迟较高的问题。

  2. WebSocket版本方案:在前端部署的同时建立一个WebSocket连接,将后端构建部署完成的通知发送给前端。当后端完成部署后,通过WebSocket向前端发送消息,提示用户刷新页面以加载最新版本。这种方案的优点是实时性好,用户体验较好,但需要在前端和后端都进行相应的配置和代码开发。

本文转载于:

https://juejin.cn/post/7264396960558399549

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 记录--前端重新部署如何通知用户