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

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

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

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

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SAP Fiori 问题收集

事务代码篇 启动工作台&#xff1a;/N/UI2/FLP 错误日志&#xff1a; /n/IWFND/ERROR_LOG 服务清单&#xff1a; /n/IWFND/MAINT_SERVICE 创建语义对象&#xff1a;/N/UI2/SEMOBJ 创建目录&#xff1a;/N/UI2/FLPD_CONF&#xff08;cross-client&#xff09;或 /N/UI2…

【ES】笔记-箭头函数的实践于应用场景

箭头函数的实践于应用场景 需求-1 点击 div 2s后颜色变成[粉色]从数组中返回偶数的元素 需求-1 点击 div 2s后颜色变成[粉色] html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport…

uniapp之当你问起“tab方法触发时eventchange也跟着触发了咋办”时

我相信没有大佬会在这个问题上卡两个小时吧&#xff0c;记下来大家就当看个乐子了。 当时问题就是&#xff0c;点击tab头切换的时候&#xff0c;作为tab滑动事件的eventchange同时触发了&#xff0c;使得接口请求了两次 大概是没睡好&#xff0c;我当时脑子老想着怎么阻止它冒…

怎么系统的学习机器学习、深度学习?当然是看书了

目录 前言 内容简介 学完本书&#xff0c;你将能够 作者简介 本书目录 京东自购链接 前言 近年来&#xff0c;机器学习方法凭借其理解海量数据和自主决策的能力&#xff0c;已在医疗保健、 机器人、生物学、物理学、大众消费和互联网服务等行业得到了广泛的应用。自从Ale…

【GPT-3 】创建能写博客的AI工具

一、说明 如何使用OpenAI API&#xff0c;GPT-3和Python创建AI博客写作工具。 在本教程中&#xff0c;我们将从 OpenAI API 中断的地方继续&#xff0c;并创建我们自己的 AI 版权工具&#xff0c;我们可以使用它使用 GPT-3 人工智能 &#xff08;AI&#xff09; API 创建独特的…

uniapp 微信小程序 封装公共的请求js(api版本)

一、新建api文件夹 在项目目录下创建api文件夹&#xff0c;内放files跟index.js文件夹&#xff0c;files文件夹内放每个页面对应的js请求接口 1、index.js /*** api接口的统一出口*/ const api {}; const requireComponent require.context(./files, false, /\.js$/) requi…

4个简化IT服务台任务的ChatGPT功能

最近几个月&#xff0c;ChatGPT 风靡全球&#xff0c;这是一个 AI 聊天机器人&#xff0c;使用户能够生成脚本、文章、锻炼图表等。这项技术在各行各业都有无穷无尽的应用&#xff0c;在本文中&#xff0c;我们将研究这种现代技术如何帮助服务台团队增强服务交付和客户体验。 什…

林【2019】

关键字&#xff1a; 哈夫曼树权值最小、哈夫曼编码、邻接矩阵时间复杂度、二叉树后序遍历、二叉排序树最差时间复杂度、非连通无向图顶点数&#xff08;完全图&#xff09;、带双亲的孩子链表、平衡二叉树调整、AOE网关键路径 一、判断 二、单选 三、填空 四、应用题 五、算…

Blazor 简单组件(0):简单介绍

文章目录 前言说明环境安装 前言 Blazor 这个技术还是比较新&#xff0c;相关的UI组件还在完善&#xff0c;我这里提供一下我个人的组件开发。 说明 本UI组件是基于BootstrapBlazor(以下简称BB)开发。 BootstrapBlazor 文档 环境安装 C#小轮子&#xff1a;Visual Studio自…

第一百二十三天学习记录:C++提高:STL-vector容器(下)(黑马教学视频)

vector插入和删除 功能描述&#xff1a; 对vector容器进行插入、删除操作 函数原型&#xff1a; push_back(ele); //尾部插入元素ele pop_back(); //删除最后一个元素 insert(const_iterator pos, ele); //迭代器指向位置pos插入元素ele insert(const_iterator pos, int cou…

(el-switch)操作(不使用 ts):Element-plus 中 Switch 将默认值修改为 “true“ 与 “false“(字符串)来控制开关

Ⅰ、Element-plus 提供的 Switch 开关组件与想要目标情况的对比&#xff1a; 1、Element-plus 提供 Switch 组件情况&#xff1a; 其一、Element-ui 自提供的 Switch 代码情况为(示例的代码)&#xff1a; // Element-plus 自提供的代码&#xff1a; // 此时是使用了 ts 语言环…

山东布谷科技直播系统源码热点分析:不同芯片实现高质量编码与渲染视频的GPU加速功能

在现代科技的迅猛发展下&#xff0c;直播系统源码平台被开发搭建出来&#xff0c;为人们的生活方式带来了很大的改变&#xff0c;直播系统源码平台的好友、短视频、直播、社区等功能让很多人越来越热衷于去在平台上刷视频、看直播、分享生活。用户的喜爱也督促了直播系统源码平…

Vue3 Props组件简单应用(子组件获取父组件数据)

去官网学习→Props | Vue.js 运行示例&#xff1a; 代码&#xff1a;App.vue <template><img alt"Vue logo" src"./assets/logo.png"><!-- 传递数据 key value--><Mycomponent :dataTest"content" :dataNmub&…

Java类型转换

总是忘&#xff0c;总是记混&#xff0c;气气气&#xff01; 基本类型 4整型、2浮点型、1布尔、1字符 关键字大小取值范围包装类型byte8-27~27-1Byteshort16-215~215-1Shortint32-231~231-1Integerlong64-263~263-1Longfloat323.4e-38~3.4e38Floatdouble641.7e-38~1.7e38Dou…

linux Ubuntu 更新镜像源、安装sudo、nvtop、tmux

1.更换镜像源 vi ~/.pip/pip.conf在打开的文件中输入: pip.conf [global] index-url https://pypi.tuna.tsinghua.edu.cn/simple按下:wq保存并退出。 2.安装nvtop 如果输入指令apt install nvtop报错&#xff1a; E: Unable to locate package nvtop 需要更新一下apt&a…

tomcat多实例与动静分离

多实例&#xff1a; 在一台服务器上配置多台tomcat服务 配置 tomcat 环境变量 修改 tomcat2 中的 server.xml 文件&#xff0c;要求各 tomcat 实例配置不能有重复的端口号 vim /usr/local/tomcat/tomcat2/conf/server.xml<Server port"8006" shutdown"SHUT…

数据结构:堆的实现(C实现)

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》 文章目录 一、堆二、实现思路1. 结构的定义2. 堆的构建 (HeapInit)3. 堆的销毁 (HeapDestroy)4. 堆的插入 (HeapPush)5. 堆的删除 (HeapPop)6. 取堆顶的数据 (HeapTop)7. 堆的数据个数 (HeapSize…

CentOS7安装Maven详细教程

&#x1f60a; 作者&#xff1a; Eric &#x1f496; 主页&#xff1a; https://blog.csdn.net/weixin_47316183?typeblog &#x1f389; 主题&#xff1a;CentOS7安装Maven详细教程 ⏱️ 创作时间&#xff1a; 2023年08月06日 第一步&#xff1a;上传或下载安装包&#x…

Oracle数据迁移

问题描述&#xff1a; oracle数据库的所有表结构、数据、索引等需要需从测试库迁移到正式库。 解决步骤&#xff1a; oracle数据库迁移&#xff0c;主要通过expdp从测试库所在的源服务器将指定的数据表或数据源导出为一个或多个数据文件&#xff08;.dmp文件&#xff09;&…

信息安全:防火墙技术原理与应用.

信息安全&#xff1a;防火墙技术原理与应用. 防火墙是网络安全区域边界保护的重要技术。为了应对网络威胁&#xff0c;联网的机构或公司将自己的网络与公共的不可信任的网络进行隔离&#xff0c;其方法是根据网络的安全信任程度和需要保护的对象&#xff0c;人为地划分若干安全…