Web项目版本更新及时通知

背景

单页应用,项目更新时,部分用户会出更新不及时,导致异常的问题。

技术方案

给出版本号,项目每次更新时通知用户,版本已经更新需要刷新页面。

  • 版本号更新方案
  • 版本号变更后通知用户
  • 哪些用户需要通知?
    • 刷新页面进入的不通知
    • 第一次进入页面的不通知
    • 只有正在页面上使用的用户,项目发知变更时通知

拉取最新版本号

1. 创建版本号文件

文件名 web.version.json

{"name": "web-site","commitId": "GitCommitId","date": "BuildDate","nexttick": 10000
}

2. 更新版本号文件

每次部署时对版本号进行更新
commit ID 当前项目的最新的提交ID
当前日期
多久检查一次

#!/bin/bash
# 修改日期
sed -i "s/\"BuildDate\"/\"$(date '+%Y%m%d')\"/g" ./public/web.version.json;
# commit id
sed -i "s/\"GitCommitId\"/\"$(git rev-parse HEAD)\"/g" ./public/web.version.json;

替换完成的结果

{"name": "web-site","commitId": "c09ecb450f4fb214143121769b0aa1546991dab6","date": "20241112","nexttick": 10000
}

3. 部署文件到 生产环境

内部上线流程,将文件部署到生产环境。 自由发挥这里不做细述。

4. 获取版本号文件

由于是跟静态文件直接部署在一起可以直接通过 http 请求获取到它。

4.1 http 轮询
  • 获取版本文件
  • 比对版本文件
  • 通知用户消息弹窗

提取配置的主要代码

const LOCALVERSIONNAME = 'version';interface IVersion {name: string;commitId: string;date: string;nexttick: number;
}export const getRemoteVersion = () =>fetch('/web.version.json', {headers: {'Content-Type': 'application/json','Cache-Control': 'no-cache',},}).then((response) => {if (!response.ok) {throw new Error(`HTTP error! Status: ${response.status}`);}return response.blob();}).then((data) => {return data.text().then((jsonStr) => {return JSON.parse(jsonStr);});});export const getLocalVersion = (current: IVersion): IVersion => {const version = window.localStorage.getItem(LOCALVERSIONNAME);if (version === null) {window.localStorage.setItem(LOCALVERSIONNAME, JSON.stringify(current));return current;}try {return JSON.parse(version) as IVersion;} catch {return current;}
};export const checkVersion = () => {return getRemoteVersion().then((remoteVersion: IVersion) => {const localVersion = getLocalVersion(remoteVersion) as IVersion;return { localVersion, remoteVersion };}).then(({ localVersion, remoteVersion }) => {return new Promise((resolve, reject) => {if (localVersion.date !== remoteVersion.date ||localVersion.commitId !== remoteVersion.commitId) {window.localStorage.setItem(LOCALVERSIONNAME,JSON.stringify(remoteVersion));resolve(remoteVersion);} else {reject(remoteVersion);}});});
};export default { getRemoteVersion, getLocalVersion, checkVersion };
4.2 websocket 长链接通知。

配置 openrestry 主要逻辑

将 文件 web.version.json 返回回来。

location /ws {content_by_lua_block {local ws = require "resty.websocket.server"local wss, err = ws:new()if not wss thenngx.log(ngx.ERR, "failed to new websocket: ", err)return ngx.exit(500)end-- 函数用于读取文件内容local function read_file_content(file_path)local file, err = io.open(file_path, "r")if not file thenngx.log(ngx.ERR, "failed to open file: ", err)return nil, errendlocal content = file:read("*all")file:close()return contentend-- 文件路径local file_path = "/data/web-site/dist/web.version.json"-- 读取文件内容local file_content, err = read_file_content(file_path)if not file_content thenngx.log(ngx.ERR, "failed to read file: ", err)wss:send_close(1000, "file not found")returnendwhile true do-- 接收客户端消息local message, typ, err = wss:recv_frame()if not message thenngx.log(ngx.ERR, "failed to receive frame: ", err)return ngx.exit(444)endif typ == "close" then-- 当客户端发送关闭信号时,关闭连接wss:send_close()breakelseif typ == "text" then-- 当客户端发送文本信息时,对其进行处理ngx.log(ngx.INFO, "received message: ", message)-- 发送文本消息给客户端wss:send_text(file_content)endend-- 关闭 WebSocket 连接wss:send_close(1000, "bye")}}

客户端获取配置

通过 websocket 将配置获取回来

const socket = new WebSocket('ws://localhost:8055/ws')
socket.addEventListener("open", (event) => {console.log(event); socket.send('v')
});
socket.addEventListener('message', (event) => {console.log(event.data)
})

通知用户

onMounted(() => {const toCheckVersion = () =>checkVersion().then(() => {const id = `${Date.now()}`;Notification.info({id,title: 'Site Update',content: 'Please refresh the page to use the latest version',duration: 0,footer: () =>h(Space, {}, [h(Button,{type: 'primary',size: 'small',onClick: () => window.location.reload(),},'OK'),]),});}).catch((remoteVersion) => {setTimeout(toCheckVersion, remoteVersion.nexttick);});toCheckVersion();});

在这里插入图片描述

完整 openrestry 代码

# Based on https://www.nginx.com/resources/wiki/start/topics/examples/full/#nginx-conf
# user              www www;  ## Default: nobodyworker_processes  auto;
error_log         "/opt/bitnami/openresty/nginx/logs/error.log";
pid               "/opt/bitnami/openresty/nginx/tmp/nginx.pid";events {worker_connections  1024;
}http {include       mime.types;default_type  application/octet-stream;log_format    main '$remote_addr - $remote_user [$time_local] ''"$request" $status  $body_bytes_sent "$http_referer" ''"$http_user_agent" "$http_x_forwarded_for"';access_log    "/opt/bitnami/openresty/nginx/logs/access.log" main;add_header    X-Frame-Options SAMEORIGIN;client_body_temp_path  "/opt/bitnami/openresty/nginx/tmp/client_body" 1 2;proxy_temp_path        "/opt/bitnami/openresty/nginx/tmp/proxy" 1 2;fastcgi_temp_path      "/opt/bitnami/openresty/nginx/tmp/fastcgi" 1 2;scgi_temp_path         "/opt/bitnami/openresty/nginx/tmp/scgi" 1 2;uwsgi_temp_path        "/opt/bitnami/openresty/nginx/tmp/uwsgi" 1 2;sendfile           on;tcp_nopush         on;tcp_nodelay        off;gzip               on;gzip_http_version  1.0;gzip_comp_level    2;gzip_proxied       any;gzip_types         text/plain text/css application/javascript text/xml application/xml+rss;keepalive_timeout  65;ssl_protocols      TLSv1.2 TLSv1.3;ssl_ciphers        HIGH:!aNULL:!MD5;client_max_body_size 80M;server_tokens off;# HTTP Serverserver {# Port to listen on, can also be set in IP:PORT formatlisten  8080;location /status {stub_status on;access_log   off;allow 127.0.0.1;deny all;}location /ws {content_by_lua_block {local ws = require "resty.websocket.server"local wss, err = ws:new()if not wss thenngx.log(ngx.ERR, "failed to new websocket: ", err)return ngx.exit(500)end-- 函数用于读取文件内容local function read_file_content(file_path)local file, err = io.open(file_path, "r")if not file thenngx.log(ngx.ERR, "failed to open file: ", err)return nil, errendlocal content = file:read("*all")file:close()return contentend-- 文件路径local file_path = "/tmp/web.version.json"-- 读取文件内容local file_content, err = read_file_content(file_path)if not file_content thenngx.log(ngx.ERR, "failed to read file: ", err)wss:send_close(1000, "file not found")returnendwhile true do-- 接收客户端消息local message, typ, err = wss:recv_frame()if not message thenngx.log(ngx.ERR, "failed to receive frame: ", err)return ngx.exit(444)endif typ == "close" then-- 当客户端发送关闭信号时,关闭连接wss:send_close()breakelseif typ == "text" then-- 当客户端发送文本信息时,对其进行处理ngx.log(ngx.INFO, "received message: ", message)-- 发送文本消息给客户端wss:send_text(file_content)endend-- 关闭 WebSocket 连接wss:send_close(1000, "bye")}}}
}

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

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

相关文章

Android音视频直播低延迟探究之:WLAN低延迟模式

Android WLAN低延迟模式 Android WLAN低延迟模式是 Android 10 引入的一种功能,允许对延迟敏感的应用将 Wi-Fi 配置为低延迟模式,以减少网络延迟,启动条件如下: Wi-Fi 已启用且设备可以访问互联网。应用已创建并获得 Wi-Fi 锁&a…

Appium配置2024.11.12

百度得知:谷歌从安卓9之后不再提供真机layout inspector查看,仅用于支持ide编写的app调试用 所以最新版android studio的android sdk目录下已经没有了布局查看工具... windows x64操作系统 小米k30 pro手机 安卓手机 Android 12 第一步&#xff1a…

前端使用Canvas实现网页电子签名(兼容移动端和PC端)

实现效果: 要使用Canvas实现移动端网页电子签名,可以按照以下步骤: 在HTML文件中创建一个Canvas元素,并设置其宽度和高度,以适配移动设备的屏幕大小。 // 创建一个canvas元素 let canvas document.createElement(&q…

使用 Python 实现高效网页爬虫——从获取链接到数据保存

前言 在这个时代,网络爬虫已成为数据分析与信息收集不可或缺的技术之一。本文将通过一个具体的Python项目来介绍如何构建一个简单的网络爬虫,它能够自动抓取指定网站的文章链接、标题、正文内容以及图片链接,并将这些信息保存为CSV文件。 目标网站 一、准备工作 在开始编…

跟着尚硅谷学vue2—进阶版4.0—Vuex1.0

5. Vuex 1. 理解 Vuex 1. 多组件共享数据-全局事件总线实现 红线是读&#xff0c;绿线是写 2. 多组件共享数据-vuex实现 vuex 不属于任何组件 3. 求和案例-纯vue版 核心代码 1.Count.vue <template><div><h1>当前求和为&#xff1a;{{ sum }}</h1&…

HTML之列表

练习题&#xff1a; 图所示为一个问卷调查网页&#xff0c;请制作出来。要求&#xff1a;大标题用h1标签&#xff1b;小题目用h3标签&#xff1b;前两个问题使用有序列表&#xff1b;最后一个问题使用无序列表。 代码&#xff1a; <!DOCTYPE html> <html> <he…

如何编写jenkins的流水线

如何编写jenkins的流水线 我们为什么需要编写流水线&#xff1f;新建一个jenkins pipeline的item初识pipeline界面pipeline代码关于取值声明和定义工具使用数据结构 我们为什么需要编写流水线&#xff1f; 这里假如你已经安装了好了jenkins&#xff0c;并且能够正常启动它。 通…

项目管理人员的自我评估与职业目标设定

在当今快速发展的商业环境中&#xff0c;项目管理人员的职业规划至关重要。它不仅涉及到个人职业发展的方向、目标和路径选择&#xff0c;还包括如何提升自身的专业技能、管理能力和行业知识。项目管理人员需要明确自己的职业目标、制定合理的职业发展计划、不断学习新知识和技…

状态空间方程离散化(Matlab符号函数)卡尔曼

// 卡尔曼滤波(4)&#xff1a;扩展卡尔曼滤波 - 知乎 // // matlab 连续系统状态空间表达式的离散化&状态转移矩阵求解_matlab状态方程离散化-CSDN博客 // // // %https://blog.csdn.net/weixin_44051006/article/details/107007916 clear all; clc; syms R1 R2 C1 C…

ubuntu24.04播放语音视频

直接打开ubuntu自带的video播放.mp4文件&#xff0c;弹窗报错如下&#xff1a; 播放此影片需要插件 MPEG-4 AAC 编码器安装方式&#xff1a; sudo apt install gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly sudo apt install ffmpeg验证AA…

音视频入门基础:MPEG2-TS专题(4)——使用工具分析MPEG2-TS传输流

一、引言 有很多工具可以分析MPEG2-TS文件/流&#xff0c;比如Elecard Stream Analyzer、PROMAX TS Analyser、easyice等。下面一一对它们进行简介&#xff08;个人感觉easyice功能更强大一点&#xff09;。 二、Elecard Stream Analyzer 使用Elecard Stream Analyzer工具可以…

C++基础 抽象类 类模板 STL库 QT环境

一、抽象类 1、纯虚函数 在多态中&#xff0c;通常父类中虚函数的实现是毫无意义的&#xff0c;主要都是调用子类重写的内容&#xff0c;因此可以将虚函数改为纯虚函数。 语法&#xff1a; virtual 返回值类型 函数名 (参数列表) 0; 2. 抽象类 1) 概念 有纯虚函数所在的类…

c语言选择排序

选择排序思想&#xff1a; 反复地从未排序部分选择最小&#xff08;或最大&#xff09;的元素&#xff0c;将其放到已排序部分的末尾&#xff1b; 首先用一个变量min来保存数组第一个元素的下标&#xff0c;然后用这个下标访问这个元素&#xff0c;将这个元素与它后面的元素相…

数字后端教程之Innovus report_property和get_property使用方法及应用案例

数字IC后端实现Innovus中使用report_property可以报告出各种各样object的属性&#xff0c;主要有cell&#xff0c;net&#xff0c;PG Net&#xff0c;Pin&#xff0c;时钟clock&#xff0c;时序库lib属性&#xff0c;Design属性&#xff0c;timing path&#xff0c;timin arc等…

Golang | Leetcode Golang题解之第560题和为K的子数组

题目&#xff1a; 题解&#xff1a; func subarraySum(nums []int, k int) int {count, pre : 0, 0m : map[int]int{}m[0] 1for i : 0; i < len(nums); i {pre nums[i]if _, ok : m[pre - k]; ok {count m[pre - k]}m[pre] 1}return count }

OpenCompass 评测 InternLM-1.8B 实践

1. API评测 首先我们创建模型的配置文件&#xff0c;我们需要定义评测模型的类型&#xff0c;是OpenAISDK类型&#xff0c;然后是模型名称&#xff0c;请求地址和API等等内容。然后我们通过命令行python run.py --models puyu_api.py --datasets demo_cmmlu_chat_gen.py --deb…

【算法】——二分查找合集

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 零&#xff1a;二分查找工具 1&#xff1a;最基础模版 2&#xff1a;mid落点问题 一&#xff1a;最…

Python 的 Pygame 库,编写简单的 Flappy Bird 游戏

Pygame 是一个用 Python 编写的开源游戏开发框架&#xff0c;专门用于编写 2D 游戏。它提供了丰富的工具和功能&#xff0c;使得开发者能够快速实现游戏中的图形渲染、声音播放、输入处理和动画效果等功能。Pygame 非常适合初学者和想要快速创建游戏原型的开发者。 Pygame 的主…

Ubuntu上搭建Flink Standalone集群

Ubuntu上搭建Flink Standalone集群 本文部分内容转自如下链接。 环境说明 ubuntu 22.06 先执行apt-get update更新环境 第1步 安装JDK 通过apt自动拉取 openjdk8 apt-get install openjdk-8-jdk执行java -version&#xff0c;如果能显示Java版本号&#xff0c;表示安装并…

【stablediffusion】ComfyUI | 恐怖如斯的放大模型DifFBIR,超分辨率放大、人脸修复、图像去噪 | 效果炸裂 | 强烈推荐

今天&#xff0c;我们将向您介绍一款令人兴奋的更新——Stable Diffusion的ComfyUI放大模型DifFBIR。这是一款基于Stable Diffusion技术的AI绘画工具&#xff0c;旨在为您提供一键式图像放大的便捷体验。无论您是AI绘画的新手还是专业人士&#xff0c;这个工具都能为您带来极大…