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 then
                    ngx.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 then
                        ngx.log(ngx.ERR, "failed to open file: ", err)
                        return nil, err
                    end
                    local content = file:read("*all")
                    file:close()
                    return content
                end

                -- 文件路径
                local file_path = "/data/web-site/dist/web.version.json"

                -- 读取文件内容
                local file_content, err = read_file_content(file_path)
                if not file_content then
                    ngx.log(ngx.ERR, "failed to read file: ", err)
                    wss:send_close(1000, "file not found")
                    return
                end

                while true do
                    -- 接收客户端消息
                    local message, typ, err = wss:recv_frame()
                    if not message then
                        ngx.log(ngx.ERR, "failed to receive frame: ", err)
                        return ngx.exit(444)
                    end
                
                    if typ == "close" then
                        -- 当客户端发送关闭信号时,关闭连接
                        wss:send_close()
                        break
                    elseif typ == "text" then
                        -- 当客户端发送文本信息时,对其进行处理
                        ngx.log(ngx.INFO, "received message: ", message)
                
                        -- 发送文本消息给客户端
                        wss:send_text(file_content)
                    end
                end
                -- 关闭 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: nobody

worker_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 Server
    server {
        # Port to listen on, can also be set in IP:PORT format
        listen  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 then
                    ngx.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 then
                        ngx.log(ngx.ERR, "failed to open file: ", err)
                        return nil, err
                    end
                    local content = file:read("*all")
                    file:close()
                    return content
                end

                -- 文件路径
                local file_path = "/tmp/web.version.json"

                -- 读取文件内容
                local file_content, err = read_file_content(file_path)
                if not file_content then
                    ngx.log(ngx.ERR, "failed to read file: ", err)
                    wss:send_close(1000, "file not found")
                    return
                end

                while true do
                    -- 接收客户端消息
                    local message, typ, err = wss:recv_frame()
                    if not message then
                        ngx.log(ngx.ERR, "failed to receive frame: ", err)
                        return ngx.exit(444)
                    end
                
                    if typ == "close" then
                        -- 当客户端发送关闭信号时,关闭连接
                        wss:send_close()
                        break
                    elseif typ == "text" then
                        -- 当客户端发送文本信息时,对其进行处理
                        ngx.log(ngx.INFO, "received message: ", message)
                
                        -- 发送文本消息给客户端
                        wss:send_text(file_content)
                    end
                end
                -- 关闭 WebSocket 连接
                wss:send_close(1000, "bye")
            }
        }
    }
}

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

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

相关文章

D64【python 接口自动化学习】- python基础之数据库

day64 SQL-DQL-基础查询 学习日期:20241110 学习目标:MySQL数据库-- 133 SQL-DQL-基础查询 学习笔记: 基础数据查询 基础数据查询-过滤 总结 基础查询的语法:select 字段列表|* from 表过滤查询的语法:select 字段…

Unity插件-Smart Inspector 免费的,接近虚幻引擎的蓝图Tab管理

习惯了虚幻的一张蓝图,关联所有Tab (才发现Unity,的Component一直被人吐槽,但实际上是:本身结构Unity 的GameObject-Comp结构,是好的不能再好了,只是配上 smart Inspector就更清晰了&#xff0…

2024 年Postman 如何安装汉化中文版?

2024 年 Postman 的汉化中文版安装教程

单元测试、集成测试、系统测试、验收测试、压力测试、性能测试、安全性测试、兼容性测试、回归测试(超详细的分类介绍及教学)

目录 1.单元测试 实现单元测试的方法: 注意事项: 2.集成测试 需注意事项: 实现集成测试的方法: 如何实现高效且可靠的集成测试: 3.系统测试 实现系统测试的方法: 须知注意事项: 4.验收测试 实现验…

MySQL 忘记 root 密码,使用跳过密码验证进行登录

操作系统版本:CentOS 7 MySQL 忘记 root 密码,使用跳过密码验证进行登录 修改 /etc/my.cnf 配置文件,在 [mysqld] 后面任意一行添加 skip-grant-tables vim /etc/my.cnf 重启 MySQL systemctl restart mysqld 登录 MySQL(无 -…

3D Web渲染引擎HOOPS Communicator:助力企业打造定制化3D可视化产品的强大工具

HOOPS Communicator为开发人员提供了多样化的定制手段,使其在3D网页可视化领域保持领先地位。很多潜在客户都关心如何利用HOOPS Communicator将其打造成自己产品的独特解决方案。展示我们现有合作伙伴的成功案例正是分享此信息的最佳方式。 每家合作伙伴都在产品中…

【stablediffusion】阿里发布新ID保持项目EcomID, 可从单个ID参考图像生成定制的保ID图像,ComfyUI可使用。

今天,我们将向您介绍一款令人兴奋的更新——阿里发布的ID保持项目EcomID。这是一款基于Stable Diffusion技术的AI绘画工具,旨在为您提供一键式生成高质量保ID图像的便捷体验。无论您是AI绘画的新手还是专业人士,这个工具都能为您带来极大的便…

计算机网络(11)和流量控制补充

这一篇对数据链路层中的和流量控制进行详细学习 流量控制(Flow Control)是计算机网络中确保数据流平稳传输的技术,旨在防止数据发送方发送过多数据,导致接收方的缓冲区溢出,进而造成数据丢失或传输失败。流量控制通常…

【VLANPWN】一款针对VLAN的安全研究和渗透测试工具

关于VLANPWN VLANPWN是一款针对VLAN的安全研究和渗透测试工具,该工具可以帮助广大研究人员通过对VLAN执行渗透测试,来研究和分析目标VLAN的安全状况。该工具专为红队研究人员和安全学习爱好者设计,旨在训练网络工程师提升网络的安全性能&…

ES6代理和反射新特性,详细讲解

代理与反射 es6新增了代理和反射特性&#xff0c;这两个特性为开发者提供了拦截并向基本操作嵌入额外行为的能力。 代理基础 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta charset"UTF-8"&g…

MYSQL 精通索引【快速理解】

目录 1、什么是索引&#xff1f; 2、索引结构 1.为什么不使用二叉树呢&#xff1f; 2.B树数据结果 3.B树 4.Hash结构 3、索引语法 1.创建索引 2.查看索引 3.删除索引 4、SQL性能分析 1.SQL执行频次 2.慢查询日志 3.profile详情 4.EXPLAIN 5、索引规则 1.最左前缀法则 2.索…

光驱验证 MD5 校验和

步骤 1&#xff1a;在 Ubuntu 上打包文件并生成 MD5 校验和 打包文件 使用 tar 命令将文件夹打包成 tar.gz 文件&#xff1a; tar -czvf my_files.tar.gz /path/to/folder 生成 MD5 校验和 使用 md5sum 命令生成打包文件的 MD5 校验和&#xff1a; md5sum my_files.tar.g…

《网络数据安全管理条例》将于2025年1月1日起正式施行,从业者应如何解读?

2024年9月&#xff0c;国务院总理李强签署国务院令&#xff0c;公布了《网络数据安全管理条例》&#xff08;以下简称《条例》&#xff09;&#xff0c;该条例将于2025年1月1日起正式施行。 这一条例的出台&#xff0c;标志着我国在网络数据安全领域的管理迈上了新的台阶&#…

【MMIN】缺失模态想象网络用于不确定缺失模态的情绪识别

代码地址&#xff1a;https://github.com/AIM3RUC/MMIN abstract&#xff1a; 在以往的研究中&#xff0c;多模态融合已被证明可以提高情绪识别的性能。然而&#xff0c;在实际应用中&#xff0c;我们经常会遇到模态丢失的问题&#xff0c;而哪些模态会丢失是不确定的。这使得…

【Java Web】监听器类型及其使用

文章目录 监听器使用监听器类型ServletContextListenerHttpSessionListenerServletRequestListenerServletContextAttributeListenerHttpSessionAttributeListenerServletRequestAttributeListenerHttpSessionBindingListener 监听器&#xff08;Listener&#xff09;组件用于监…

conda创建 、查看、 激活、删除 python 虚拟环境

1、创建 python 虚拟环境 ,假设该环境命名为 “name”。 conda create -n name python3.11 2、查看 python 虚拟环境。 conda info -e 3、激活使用 python 虚拟环境。 conda activate name 4、删除 python 虚拟环境 conda remove -n name --all ​​ 助力快速掌握数据集…

LaTeX之四:如何兼容中文(上手中文简历和中文论文)、在win/mac上安装新字体。

改成中文版 如果你已经修改了.cls文件和主文档&#xff0c;但编译后的PDF仍然显示英文版本&#xff0c;可能有以下几个原因&#xff1a; 编译器问题&#xff1a;确保你使用的是XeLaTeX或LuaLaTeX进行编译&#xff0c;因为它们对Unicode和中文支持更好。你可以在你的LaTeX编辑器…

视频遥控打药履带机器人技术详解

视频遥控打药履带机器人技术是一种集成了遥控操作、视频监控和履带行走系统的现代化农业植保技术。以下是对该技术的详细解析&#xff1a; 一、技术概述 视频遥控打药履带机器人主要由履带行走系统、药箱、喷雾系统、遥控系统以及视频监控系统等部分组成。通过遥控操作&#…

BB1-NHS ester被用于将各种生物活性分子与蛋白质或其他生物大分子进行共轭连接,2082771-52-4

CAS号&#xff1a;2082771-52-4 中文名&#xff1a;BB1-琥珀酰亚胺酯&#xff0c;BB1-活性酯 英文名&#xff1a;BB1-NHS ester&#xff0c;或BB1-Succinimidyl Ester 分子式&#xff1a;C32H32N6O4 分子量&#xff1a;564.63 纯度&#xff1a;≥95% 供应商&#xff1a;陕…

MongoDB在现代Web开发中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 MongoDB在现代Web开发中的应用 MongoDB在现代Web开发中的应用 MongoDB在现代Web开发中的应用 引言 MongoDB 概述 定义与原理 发展…