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

news2024/11/22 14:13:46

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

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+[^>]*)?>(.*?)<\/script\s*>/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

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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/846068.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

springboot 对接 minio 分布式文件系统

1. minio介绍 Minio 是一个基于Go语言的对象存储服务。它实现了大部分亚马逊S3云存储服务接口&#xff0c;可以看做是是S3的开源版本&#xff0c;非常适合于存储大容量非结构化的数据&#xff0c;例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等&#xff0c;而一个对象…

计算机视觉大牛Liang-Chieh Chen从谷歌离职!加入字节跳动!

点击下方卡片&#xff0c;关注“CVer”公众号 AI/CV重磅干货&#xff0c;第一时间送达 点击进入—>【图像分割和论文投稿】交流群 这应该是国内首次报道&#xff01; Amusi 发现&#xff1a;Liang-Chieh Chen 巨佬在官网宣布&#xff1a;已离职谷歌&#xff0c;目前在字节跳…

以太网Ethernet通信协议

一、以太网简介 计算机网络可分为局域网(LAN)、 城域网(MAN)、广域网(WAN)、互联网(Initernet)。局域网按传输介质所使用的访问控制方法可分为&#xff1a;以太网(Ethernet)、光纤分布式数据接口(FDDI)、异步传输模式(ATM)、令牌环网(Token Ring)、交换网(Switching) 等&#x…

【JavaSE】什么是抽象类?什么是内部类?以及它们的作用是什么?

这篇文章我们主要学习的是两个知识点&#xff0c;可以来解决文章标题所提出来的三个问题。 目录 1.抽象类 1.1 抽象类概念 1.2 抽象类语法 1.3 抽象类特性 1.4 抽象类的作用 2.内部类 2.1 内部类的分类 2.2 实例内部类 2.3 静态内部类 2.4 匿名内部类 2.5 局部内部类…

标准化归一化 batch norm, layer norm, group norm, instance norm

Layer Normalization - EXPLAINED (in Transformer Neural Networks) Layer Normalization - EXPLAINED (in Transformer Neural Networks) 0~4min:什么是multi-head attention 5~7min:layer norm图示 7~9min:公式举例layer norm 9:54-end:layer norm的代码示例 group n…

2023金九银十软件测试面试题(800道)

今年你的目标是拿下大厂offer&#xff1f;还是多少万年薪&#xff1f;其实这些都离不开日积月累的过程。 为此我特意整理出一份&#xff08;超详细笔记/面试题&#xff09;它几乎涵盖了所有的测试开发技术栈&#xff0c;非常珍贵&#xff0c;人手一份 肝完进大厂 妥妥的&#…

时序预测 | MATLAB实现BO-BiLSTM贝叶斯优化双向长短期记忆神经网络时间序列预测

时序预测 | MATLAB实现BO-BiLSTM贝叶斯优化双向长短期记忆神经网络时间序列预测 目录 时序预测 | MATLAB实现BO-BiLSTM贝叶斯优化双向长短期记忆神经网络时间序列预测效果一览基本介绍模型搭建程序设计参考资料 效果一览 基本介绍 MATLAB实现BO-BiLSTM贝叶斯优化双向长短期记忆…

用队列实现栈——数据结构与算法

&#x1f636;‍&#x1f32b;️Take your time ! &#x1f636;‍&#x1f32b;️ &#x1f4a5;个人主页&#xff1a;&#x1f525;&#x1f525;&#x1f525;大魔王&#x1f525;&#x1f525;&#x1f525; &#x1f4a5;代码仓库&#xff1a;&#x1f525;&#x1f525;魔…

【JS交互篇】DOM操作基础

一、DOM概述 1.1 什么是DOM DOM(Document Object Model)文档对象模型&#xff0c;用来表示和操作html或xml文档内容的基础API;当网页被加载时&#xff0c;浏览器会创建页面的文档对象模型DOM&#xff0c;而DOM模型被构造为对象的树&#xff08;Dom Html Tree&#xff09;;DOM包…

真我V3 5G(RMX2200 RMX2201)解锁刷机全过程

安卓系统新Rom包为GSI&#xff0c;更具有通用性&#xff0c;可以比较放心刷。 原厂系统垃圾多、广告多&#xff0c;甚至热点功能不支持ipv6&#xff0c;严重偏离热点机的定位。 主要参考 https://www.bilibili.com/read/cv20730877/https://www.bilibili.com/read/cv2073087…

DSV-080-2NCP-N-MM两位两通常闭先导式电磁阀

该插装阀具有正向关断作用&#xff0c;设计用于负荷保持状态。 动作状况 断电时&#xff0c;DSV -080-2NCP-*-M*为止回阀&#xff0c;允许介质从1到2&#xff0c;同时阻止介质从2到1。 通电时&#xff0c;提升阀提升&#xff0c;打开从 2到1的通通道。 手动关闭选择:按下按…

分布式协议与算法——CAP理论、ACID理论、BASE理论

CAP理论 CAP理论&#xff0c;对分布式系统的特性做了高度抽象&#xff0c;比如抽象成了一致性、可用性和分区容错性&#xff0c;并对特性间的冲突&#xff08;也就是CAP不可能三角&#xff09;做了总结。 CAP三指标 CAP理论对分布式系统的特性做了高度抽象&#xff0c;形成了…

境内金融信息服务报备33家机构名单

2022年01月04日&#xff0c;国家互联网信息办公室关于发布第一批境内金融信息服务机构报备编号的公告&#xff0c;公开发布第一批20家金融信息服务机构的名称及报备编号。 2022年10月28日&#xff0c;国家互联网信息办公室关于发布第二批境内金融信息服务机构报备编号的公告&am…

Linux常用命令学习总结

Linux命令分类 1. Linux目录操作命令2. Linux文件名称3. Linux磁盘命令4. Linux进程与防火墙5. Linux用户与组的关系6. Linux权限操作(chmod命令)7. Linux中的文件类型文件的寻找 最近系统地学习下Linux命令的使用&#xff0c;因此作如下记录&#xff0c;以便随时复习和翻阅。 …

栈和队列的实现以及OJ题讲解

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; 刷题专栏&#x1f440; C语言&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大家三连关注&…

Android安卓实战项目(9)—漂亮的健身APP主页控件+开机动画+BMI计算(源码在文末)可用于比赛项目或者作业参考中

Android安卓实战项目&#xff08;9&#xff09;—漂亮的健身控件APP开机动画BMI计算&#xff08;源码在文末&#x1f415;&#x1f415;&#x1f415;&#xff09; 介绍&#xff1a; BMI&#xff08;Body Mass Index&#xff0c;身体质量指数&#xff09;是一种常用的健康指标…

Spring Cloud Alibaba (一)

1 微服务介绍 1.1 系统架构演变 随着互联网的发展&#xff0c;网站应用的规模也在不断的扩大&#xff0c;进而导致系统架构也在不断的进行变化。 从互联网早起到现在&#xff0c;系统架构大体经历了下面几个过程: 单体应用架构--->垂直应用架构--->分布 式架构--->S…

LeetCode 29题:两数相除

题目 给你两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求 不使用 乘法、除法和取余运算。 整数除法应该向零截断&#xff0c;也就是截去&#xff08;truncate&#xff09;其小数部分。例如&#xff0c;8.345 将被截断为 8 &#xff0c;-2.…

一生一芯1——windows与Ubuntu双系统安装

UltraISO下载 下载链接&#xff1a;https://pan.baidu.com/s/18ukDs6yL64qU6thYyZEo-Q?pwdo8he 提取码&#xff1a;o8he 一路傻瓜安装&#xff0c;安装后点击继续试用 Ubuntu系统下载 这里我使用的是官网的22.04版本&#xff0c;由于大于4G&#xff0c;无法上传至百度网盘…