当前位置: 首页 > news >正文

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

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

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 = nullprivate newEtag: string | null = nulldispatch: Record<string, (() => void)[]> = {}private stop = falseconstructor() {this.init()}async init() {console.log('初始化')const html: string = await this.getHtml()this.oldScript = this.parserScript(html)this.oldEtag = await this.getEtag()}// 获取htmlasync 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*>/gireturn 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) returnconst oldLen = this.oldScript.lengthconst newLen = Array.from(new Set(this.oldScript.concat(this.newScript))).lengthif (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) returnModal.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;}// 将触发的更新代理到容器的3000location /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

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

 

相关文章:

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

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 1. 场景 前端构建完上线&#xff0c;用户还停留还在老页面&#xff0c;用户不知道网页重新部署了&#xff0c;跳转页面的时候有时候js连接hash变了导致报错跳不过去&#xff0c;并且用户体验不到新功能…...

WPS的excel表格单元格拖动数字日期等 不自增原因

对着表格中的每个单元格右下角,在变成下图,黑十字后,拖动这个十字.就会在右侧出现一个小窗口. 里面菜单中可以选择按序数增加 但是,如果拖动,发现小窗口菜单不出现.说明这一栏开启了筛选功能.清空筛选条件后,即可恢复自增功能....

2308C++简单异步懒

Lazy Lazy由C20无栈协程实现.一个Lazy闭包一个懒求值的计算任务. 使用Lazy 想用Lazy,需要先#inlude<async_simple/coro/Lazy.h>,再实现返回类型为Lazy<T>的协程函数即可.如: # 包含<简单异步/协程/懒.h>懒<整>任务1(整 x){协中 x;//带有协中的函数…...

Linux常规操作命令

日升时奋斗&#xff0c;日落时自省 目录 1、vim 1.1、工作模式 1.2、末行模式操作相关命令 1.2.1、保存退出操作 1.2.2、查找替换 1.3、输入模式操作相关命令 1.3.1、移动相关命令 1.3.2、删除和剪切命令 1.3.3、复制操作 1.3.4、撤销 2、head 3、tail 4、ps 5、…...

日期切换

组件&#xff1a;<template><div class"time-picker"><el-radio-group size"small" v-model"timeType" change"changePickerType"><el-radio-button label"hour" v-if"isShow">时</el…...

怎么裁剪视频大小尺寸?简单的裁剪方法分享

怎么裁剪视频的画面大小尺寸呢&#xff1f;有时当我们下载下来一段视频&#xff0c;由于视频的画面大小比例不同&#xff0c;会有很多的黑边&#xff0c;我们不管是观看还是进行二次编辑都非常影响体验&#xff0c;而调整视频画面比例以适应观众的设备或平台&#xff0c;比如将…...

智慧工地源码,Spring Cloud+ Vue+UniApp开发,微服务架构

智慧工地源码&#xff0c;智慧工地云平台源码 智慧工地APP源码 智慧工地的核心是数字化&#xff0c;它通过传感器、监控设备、智能终端等技术手段&#xff0c;实现对工地各个环节的实时数据采集和传输&#xff0c;如环境温度、湿度、噪音等数据信息&#xff0c;将数据汇集到云…...

【Hystrix技术指南】(5)Command创建和执行实现

创建流程 构建HystrixCommand或者HystrixObservableCommand对象 *使用Hystrix的第一步是创建一个HystrixCommand或者HystrixObservableCommand对象来表示你需要发给依赖服务的请求。 若只期望依赖服务每次返回单一的回应&#xff0c;按如下方式构造一个HystrixCommand即可&a…...

学习笔记-JAVAJVM-JVM的基本结构及概念

申明&#xff1a;文章内容是本人学习极客时间课程所写&#xff0c;文字和图片基本来源于课程资料&#xff0c;在某些地方会插入一点自己的理解&#xff0c;未用于商业用途&#xff0c;侵删。 原资料地址&#xff1a;课程资料 什么是JVM 原文连接&#xff1a; 原文连接 JVM是J…...

ubuntu20.04 docker 下编译 tensorflow-gpu

ubuntu20.04 安装tensorflow-gpu 配置&#xff1a; 系统 ubuntu 20.04 LTS 显卡 GTX 1060 6G 1 安装cudatoolkit &#xff08;我选 CUDA Toolkit 12.2 &#xff09; NVIDIA CUDA Installation Guide for Linux https://docs.nvidia.com/cuda/cuda-installation-guide-linux/in…...

❤ VUE3 项目路由拦截器配置(二)

❤ VUE3 项目 路由拦截器进一步 配置 路由拦截抽离为单个模块permission.ts 路由配置规则 白名单&#xff08;直接进入&#xff09; PC页面和PC子页面&#xff08;直接进入&#xff09; 后台页面&#xff08;验证token &#xff09; 没有token> 后台登录页面 有token> 后…...

Filament 如何自定义登录页面

官方的页面太简约了&#xff0c;而且可供修改的范围太少了 通过发布官方资源可以看到 resources/views/vendor/filament-panels/pages/auth/login.blade.php <x-filament-panels::page.simple>if (filament()->hasRegistration())<x-slot name"subheading&…...

百度智能云“千帆大模型平台”最新升级:接入Llama 2等33个模型!

今年3月&#xff0c;百度智能云推出“千帆大模型平台”。作为全球首个一站式的企业级大模型平台&#xff0c;千帆不但提供包括文心一言在内的大模型服务及第三方大模型服务&#xff0c;还提供大模型开发和应用的整套工具链&#xff0c;能够帮助企业解决大模型开发和应用过程中的…...

[保研/考研机试] KY129 简单计算器 浙江大学复试上机题 C++实现

描述 读入一个只包含 , -, *, / 的非负整数计算表达式&#xff0c;计算该表达式的值。 输入描述&#xff1a; 测试输入包含若干测试用例&#xff0c;每个测试用例占一行&#xff0c;每行不超过200个字符&#xff0c;整数和运算符之间用一个空格分隔。没有非法表达式。当一行中…...

推出 Elasticsearch 查询语言 (ES|QL)

作者&#xff1a;Costin Leau 我很高兴地宣布&#xff0c;经过大约一年的开发&#xff0c;Elasticsearch 查询语言 (ES|QL) 已准备好与世界共享&#xff0c;并已登陆 Elasticsearch 存储库。 ES|QL 是 Elasticsearch 原生的强大声明性语言&#xff0c;专为可组合性、表现力和速…...

机器学习 day32(神经网络如何解决高方差和高偏差)

解决高偏差和高方差的新方法 之前&#xff0c;我们需要通过选取多项式次数以及正则化参数λ&#xff0c;来平衡高方差和高偏差 只要训练集不是特别大&#xff0c;那么一个大型的神经网络总能很好的适应训练集&#xff0c;即它的Jtrain很低由此可以得出&#xff0c;若要减小Jt…...

Web前端之NodeJS、Vue

文章目录 一、Babel转码器1.1 Babel安装流程1.2 Babel命令行转码 二、Promise对象三、测试方式四、Vue&#xff08;渐进式JS框架&#xff09;4.1 准备4.2 创建一个项目4.3 运行一个项目 五、模板语法5.1 文本5.2 原始html5.3 属性Attribute5.4 使用JavaScript表达式 六、条件渲…...

冠达管理:银行定增是利好还是利空?

银行定增是指银行经过向特定投资者定向发行股票的方法进行增发。这种方法被认为可认为银行提供本钱充足、拓展融资渠道、增强抵挡危险的才能。但是&#xff0c;关于银行定增是否对商场和投资者带来积极影响的讨论一向存在。本文将从多个角度进行剖析&#xff0c;以讨论银行定增…...

背上小书包准备run之TypeScript篇

这TypeScript我真不知道面试会咋问。。。 哦以前还写过一篇基础⬇️ Typescript 基础易理解-------冲冲冲_ts和js有什么区别_慢谷的博客-CSDN博客 typescript是啥&#xff1f;与javascript的区别&#xff1f; TypeScript是一个强类型的JavaScript超集&#xff0c;可编译为纯…...

什么是绩效管理?绩效管理包括哪些内容?

阅读本文您可以了解&#xff1a;1、绩效管理的定义&#xff1b;2、绩效管理的内容&#xff1b; 一、什么是绩效管理 绩效管理是一种组织和管理方法&#xff0c;旨在确保员工的工作与组织的目标保持一致&#xff0c;以及激励和提高员工的工作表现。它涉及设定明确的目标和标准&…...

2026上海装修公司TOP10排行榜,不增项高口碑公司分享!

历时15个月&#xff0c;覆盖上海16区&#xff0c;基于2147家合规装企、2100余位竣工业主深度回访&#xff0c;严选10家“不增项、高口碑”的标杆企业&#xff0c;附避坑指南第一章&#xff1a;引言——“不增项&#xff0c;是装修公司对业主最基本的诚意”“装修前对比了五六家…...

别再只用pack了!Tkinter Scrollbar滚动条与Listbox/Text组件的三种布局实战(附完整代码)

别再只用pack了&#xff01;Tkinter Scrollbar滚动条与Listbox/Text组件的三种布局实战&#xff08;附完整代码&#xff09; 在构建Tkinter GUI应用时&#xff0c;滚动条(Scrollbar)与内容组件(Listbox/Text)的联动布局是开发者经常遇到的痛点问题。很多教程只简单演示pack布局…...

品牌升级后卖不动,先别怪设计公司

品牌升级了&#xff0c;为什么销量没变化&#xff1f;很多企业做品牌升级&#xff0c;心里其实都憋着一口气。老板觉得产品不差&#xff0c;工厂不差&#xff0c;渠道也不是完全没有基础&#xff0c;就是品牌看起来有点旧&#xff0c;包装有点老&#xff0c;表达有点土&#xf…...

终极Windows图像查看器:为什么90%的用户选择ImageGlass替代自带软件?

终极Windows图像查看器&#xff1a;为什么90%的用户选择ImageGlass替代自带软件&#xff1f; 【免费下载链接】ImageGlass &#x1f3de; A lightweight, versatile image viewer 项目地址: https://gitcode.com/gh_mirrors/im/ImageGlass 在数字图像日益丰富的今天&…...

如何使用gpt-repository-loader:将代码仓库转换为LLM友好格式的终极指南

如何使用gpt-repository-loader&#xff1a;将代码仓库转换为LLM友好格式的终极指南 【免费下载链接】gpt-repository-loader Convert code repos into an LLM prompt-friendly format. Mostly built by GPT-4. 项目地址: https://gitcode.com/gh_mirrors/gp/gpt-repository-…...

终极Windows 11优化指南:如何使用Win11Debloat让你的系统更快更干净

终极Windows 11优化指南&#xff1a;如何使用Win11Debloat让你的系统更快更干净 【免费下载链接】Win11Debloat A simple, lightweight PowerShell script that allows you to remove pre-installed apps, disable telemetry, as well as perform various other changes to dec…...

用 FastMCP 构建出行龙虾技能:从 MCP Server 到 Python/Node.js 双版本 Skill Client

参考实现: python:https://github.com/lonngxiang/travel-skill nodejs:https://github.com/lonngxiang/travel-skill-nodejs 本文完整拆解一个生产级 MCP 技能的技术实现——服务端用 FastMCP 框架,客户端提供 Python 和 Node.js 两套实现,最终通过 SKILL.md 接入 AI Age…...

extract-video-ppt:一站式智能视频内容提取的革命性解决方案

extract-video-ppt&#xff1a;一站式智能视频内容提取的革命性解决方案 【免费下载链接】extract-video-ppt extract the ppt in the video 项目地址: https://gitcode.com/gh_mirrors/ex/extract-video-ppt 在数字化学习与知识传播的时代&#xff0c;视频已成为信息传…...

豆包无水印解析,一键提取超高效

我用夸克网盘给你分享了「瞬净-豆包版 链接&#xff1a;https://pan.quark.cn/s/ec800a1df2a7 通过网盘分享的文件&#xff1a;瞬净-豆包版 链接: https://pan.baidu.com/s/16JnPo8i99rczp5LQj7oNPA?pwd1314 提取码: 1314...

spring-boot-starter-validation字段数据校验

validation 概述 spring-boot-starter-validation 是 Spring Boot 官方提供的用于数据校验的启动器&#xff0c;它基于 Bean Validation API (JSR 380) 标准&#xff0c;并默认使用 Hibernate Validator 作为其实现。这个框架能让你通过声明式的注解&#xff0c;轻松地对控制器…...