微应用如何实现自动更新提示

news2024/10/6 18:33:50

首先, 先讲一下本次文章所讲的场景, 经过调研, 公司内部使用后台, 当有需求功能迭代的时候, 通常使用者会没有感知, 使用者只会在浏览器内一直打开这个页面, 当需要使用的时候, 再切换这个tab来使用.

这就导致使用者一直不知道系统更新了, 一直没有访问最新的页面(由于最新页面需要刷新浏览器, 重新对静态资源进行读取)

其实这个时候刷新一下网页就可以实现去访问新资源了

效果如下:

什么是微应用

先来介绍一下微应用的特点.

微应用就是有一个主应用服务负责登录和管理各个子应用, 通常企业中的后台就是这样的

比如有一个主应用app, 主应用中有很多很多子后台, 比如: 管理订单的(orderCenter), 管理用户信息的(userCenter), 管理考勤的(attendanceCenter)

前端微应用框架中, qiankun最受欢迎, 使用量和star数量都很高

如它的介绍: Blazing fast, simple and complete solution for micro frontends.

快速、简单、完整的微前端解决方案。

介绍就到这里, 如果还需要详细了解, 可以查看这篇文章:

基于 qiankun 的微前端应用实践

何时自动更新

当然解决问题的之前, 需要考虑清楚本次需求, 也就是最终要实现的效果

我们当然希望在项目重新构建完成之后, 在构建之前打开的网页能够知道项目重新构建了,然后提示用户去更新页面.

但是这个时候要能够选择是否更新, 因为如果用户正在填写表单, 那一更新页面, 表单数据没有提交, 用户肯定这个时候不希望再重新填写一遍. 这个时候要能够忽略更新, 然后不在提示, 等用户填写完成之后, 手动刷新就可以了.

如何实现自动更新

这部分其实就存在两个难点:

1.如何知道服务重新部署了?

方案1

在node端监听远程文件是否更改

如何监听文件是否更改?

node端可以很轻易监听本地文件是否变化, chokidar读取文件更改,不能监听远程服务的文件

但是当node服务与要监听的文件不是在同一台服务器, 需要去监听远程文件的变化, 就不简单

解决方案

可以通过get请求去读取远程服务器下的文件夹,会返回一个html,html中有最后修改时间和文件名

可以通过请求响应头里面的last-modified来判断是否更新

缺点:

1.需要有访问文件的权限, 通常在nginx层设置了不允许访问

2.需要node端轮询去查文件夹的最后更改时间

方案2:

项目部署之后, 通知node服务哪些系统重新部署了

如何通知?

可以请求一个node服务的接口, 参数传递哪个系统重新部署, 什么时候部署

缺点: 需要其他部门配合

2.如何通知应用更新?

方案1:

接入socket

什么是socket?

Socket 是网络编程中一种低级别的网络通信方式,它可以实现双向通信和实时通信。

这里主应用肯定是通讯的一方, 还需要有另一方, 来监听服务重新部署,所以这个方案需要有一个node后端服务, 来做通讯的另一方, 这个node服务可以与主应用进行通信.

但是socket是双向通信, 其实我们只需要服务端通知客户端就行了, 客户端无需与服务端通信, 所以这个方案不符合场景.

所以就有了第二种方案.

方案2:

SSE 全称是 Server Sent Event,翻译过来的意思就是 服务器派发事件。

一个网页获取新的数据通常需要发送一个请求到服务器,也就是向服务器请求的页面。

使用 server-sent 事件,服务器可以在任何时刻向我们的 Web 页面推送数据和信息。

这些被推送进来的信息可以在这个页面上作为 Events [0] + data 的形式来处理。

服务端

import { Router } from "express";
// import fetch from "node-fetch";

const sseServer = Router();
const sendData = { time: '' }

sseServer.all("*", function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  next();
})


sseServer.get('/events', (req, res) => {
  // 设置响应头
  res.setHeader('Content-Type', 'text/event-stream');
  // res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  setInterval(() => {
    // console.log(sendData)
    res.write(`data: ${JSON.stringify(sendData)}\n\n`);
  }, 5000);
});

客户端:

const configEnv = import.meta.env
const source = new EventSource(`${configEnv.VITE_BASE_API}/sseServer/events`);
let oldStr = ''
// 监听 message 事件
source.onmessage = event => {
  let time = JSON.parse(event.data).time
  if (oldStr.length === 0) {
    oldStr = time
    console.log('connect success')
  }else if(oldStr !== time) {
    window.alert('系统更新啦~~~')
    oldStr = time
  }
}

SSE 与 Socket 有什么区别?

方式协议交互通道内容编码重连事件类型总结
SSEHTTP服务端单向推送默认文本默认支持断线重连支持自定义消息类型轻量级
WebSocketWS(基于 TCP 传输层的应用层协议,RFC6455[1] 对于它的定义标准)双向推送默认二进制手动实现NO扩展性、功能性强大

缺点:

Server Sent Events(SSE)对服务器端确实有一定要求:

  1. 支持长连接 - SSE 依赖于 HTTP 长连接,服务器需要能够保持连接,定期推送数据。

  2. 高并发 - 由于是长连接,每个客户端都会占用服务器的连接资源,高并发场景下会对服务器产生较大压力。

  3. 数据序列化 - 服务器需要将数据序列化为 SSE 格式的文本流进行推送,这会带来一定的 CPU 和内存消耗。

  4. 心跳机制 - 为了防止长时间空闲的连接被中间件关闭,服务器需要定期推送空数据给客户端(心跳数据)。

以上方案流程如下:

前端服务轮询实现

上面的解决方案, 都是需要node服务的

但是有没有更加简洁和方便的方案呢?

比如说: 只需要前端服务进行修改就能实现这样的需求

然后我看到了一篇文章的解决方案: 前端重新部署如何通知用户刷新网页?

根据打完包之后生成的script src 的hash值去判断,每次打包都会生成唯一的hash值,只要轮询去判断不一样了,那一定是重新部署了.

缺点:

1.需要将所有子应用配置更改

2.需要轮询去查看文化hash值是否发生改变

最佳解决方案

基于上面这个方法, 我想到了一种更加可行的方案

服务build的时候肯定是有node环境的, node环境是可以写文件的, build就是将静态资源打包

那如果build的时候维护一个版本号, 这个版本号每次build都不一样, 那是不是就可以用来判断是否更新啦

而且build的时候可以将这个版本号跟静态资源一同保存起来

所以写一个打包插件, 就能够将最新的版本号保存起来.那什么作为版本号呢? 思来想去, 其实代码提交的时候就会有一个commitId, 这个id是唯一, 且不会重复的.

node端去读取git提交时候的commitId, 代码如下:

import { execSync } from 'node:child_process'
export function getGitCommitHash() {
  try {
    return execSync('git rev-parse --short HEAD').toString().replace('\n', '').trim()
  }
  catch (err) {
    console.warn(`[web-auto-notify-plugin] Not a git repository!`)
    throw err
  }
}

由于公司中微应用是vue搭建的, 所以打包工具是vue-cli, 所以只需要写一个webpack插件就可以了

import type { Options } from '../../core/src/index'
import {
  DIRECTORY_NAME,
  JSON_FILE_NAME,
  generateJSONFileContent,
  getVersion,
} from '../../core/src/index'
import type { Compilation, Compiler } from 'webpack'

const pluginName = 'WebAutoNotifyPlugin'

type PluginOptions = Options & {
  indexHtmlFilePath?: string
}


class WebAutoNotifyPlugin {
  options: PluginOptions
  constructor(options: PluginOptions) {
    this.options = options || {}
  }

  apply(compiler: Compiler) {
    const { publicPath } = compiler.options.output
    if (this.options.injectFileBase === undefined)
      this.options.injectFileBase = typeof publicPath === 'string' ? publicPath : '/'

    const { versionType, customVersion, silence } = this.options
    let version = ''
    if (versionType === 'custom')
      version = getVersion(versionType, customVersion!)
    else
      version = getVersion(versionType!)

    compiler.hooks.emit.tap(pluginName, (compilation: Compilation) => {
      // const outputPath = compiler.outputPath
      const jsonFileContent = generateJSONFileContent(version, silence)
      // @ts-expect-error
      compilation.assets[`${this.options.injectFileBase}${DIRECTORY_NAME}/${JSON_FILE_NAME}.json`] = {
        source: () => jsonFileContent,
        size: () => jsonFileContent.length,
      }
    })
  }
}

export { WebAutoNotifyPlugin }

webpack内置了很多生命周期钩子, 方便插件使用.

plugins是可以用自身原型方法apply来实例化的对象。apply只在安装插件被Webpack compiler执行一次。apply方法传入一个webpck compiler的引用,来访问编译器回调。

compiler.hooks.emit.tap中的回调就是创建一个json文件, 这个json文件中就保存一个version字段.

这样就解决了第一个难点, 如何知道服务器重新部署了.

接着来解决第二个问题, 如何通知应用更新?

在客户端引入一个npm依赖包, 然后在项目中引入就行.

更新的话, 肯定就要去请求这个静态资源了, 但是什么时候去请求呢?我觉得在以下几种情况就需要去请求了

1.首次加载页面时

2.静态资源获取失败(404)

3.页面refocus或者revisible

但是还有一种情况, 就是用户正在使用时, 这个时候也需要提示更新了, 所以要有个定时任务, 来请求

当这个页面隐藏时, 就关闭定时器就可以了.

如何判断静态资源404

// listener script resource loading error
window.addEventListener(
  "error",
  err => {
    const errTagName = (err?.target as any)?.tagName;
    if (errTagName === "SCRIPT") {
      checkSystemUpdate();
    }
  },
  true
);

最终的流程如下:

好了, 全部的代码这里就不贴了, 优点占篇幅, 所以我留下git仓库地址:

https://github.com/0522skylar/web-auto-notify

可以先下载npm包体验一下:

https://www.npmjs.com/package/web-auto-notify-webpack

https://www.npmjs.com/package/web-auto-notify-client

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

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

相关文章

日志框架——Log4j2

日志框架——Log4j2 日志框架Log4j21. 概述2. Log4j2主要由几个重要的组件构成:3.项目中使用3.1 引入相关依赖pom.xml3.2 加入日志配置文件src/main/resources/log4j2.xml3.3 测试 日志框架Log4j2 1. 概述 Apache Log4j2是一个开源的日志记录组件,使用非常的广泛。…

【Protobuf速成指南】什么是Protobuf?

文章目录 一、序列化和反序列化1.1 概念1.2 场景1.3 如何序列化 二、Protobuf介绍1. 自身特点2.使用特点 一、序列化和反序列化 1.1 概念 🎯[总结]: 序列化:把对象转换为字节序列的过程称为对象的序列化。反序列化:把字节序列恢复为对象的过…

MySQL数据库 8.DML操作

目录 ​编辑 🤔前言: 🤔DML介绍: 🤔语法详情: 😀1.插入数据: 特点: 1.给指定字段添加数据: 代码示例: 运行结果: 2.给所有的字段添加数据&…

好的用户体验和性能:现代前端的双赢之路

部分数据来源:ChatGPT 引言 随着 Web 应用程序的复杂度和重要性逐渐上升,前端开发人员已经开始更加注重应用程序的可用性和性能。在这个快速变化的时代,前端开发是日益增长的一个领域。 在当前的前端领域,用户体验和性能是前端开…

解决在谷歌浏览器下载时文件名包含逗号导致页面显示网页不可用问题

项目场景: 自己开发的文件服务项目在使用时测试反馈在下载文件时,文件名包含逗号时下载失败,无法跳转到下载链接页面。 项目使用springboot开发,文件的上传基于SpringMVC的表单文件上传。但是下载时由于需要下载原文件名&#xf…

【IC设计】基于Verilog的8层矩阵乘法设计

文章目录 项目要求基本要求截断要求低位截断高位饱和 参考结果 项目实现实现思路实现代码matrix_multiplier_16.vtb_mm_mlp.v VCS&Verdi综合前仿真dc综合VCS&Verdi综合后仿真不足之处 项目要求 基本要求 输入有9个矩阵,权重矩阵有8个,分别是We…

win11安装open-ssh server

帮助链接: 安装 OpenSSH | Microsoft Learn step1: 本机管理模式的power shell下查询安装状态 Get-WindowsCapability -Online | Where-Object Name -like OpenSSH* Name : OpenSSH.Client~~~~0.0.1.0 State : InstalledName : OpenSSH.Server~~~~0.0.1.0 Stat…

Kafka生产者与消费者api示例

生产者api示例 一个正常的生产逻辑需要具备以下几个步骤 配置生产者参数及创建相应的生产者实例 构建待发送的消息 发送消息 关闭生产者实例 采用默认分区方式将消息散列的发送到各个分区当中 package com.doitedu;import org.apache.kafka.clients.producer.KafkaProduce…

【人工智能】— 线性分类器、感知机、损失函数的选取、最小二乘法分类、模型复杂性和过度拟合、规范化

【人工智能】— 感知机、线性分类器、感知机、感知机、最小二乘法分类、模型复杂性和过度拟合、规范化 Linear predictions 线性预测分类线性分类器感知机感知机学习策略损失函数的选取距离的计算 最小二乘法分类求解最小二乘分类矩阵解法一般线性分类模型复杂性和过度拟合训练…

重估端到端原则

评价技术迭代的旧的定势眼光来自于该技术诞生时。 1970/80/90 年代,相比传输带宽技术,处理器更强。网络协议倾向于字段多,字段小且紧凑,尽可能减少传输量,用 “算法技巧” 等价,如果 TCP 序列号 48 位&…

【iOS】消息传递和消息转发机制

消息传递机制 在OC语言中,调用对象的方法被叫做消息传递。消息有名称和选择子(selector),可以接受参数,还可能有返回值。 在Objective-C中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。在底层…

C++进阶 —— 范围for(C++11新特性)

目录 一,范围for介绍 二,范围for注意事项 一,范围for介绍 范围for(range-based for loop)是C11新引入的特性,可遍历各种序列结构的容器(如数组、vector、list等);每次循…

【QT】Qt ApplicationManager Compositor源码分析

Qt ApplicationManager的Compositor功能分析 根据Qt ApplicationManager官网介绍,它基于Wayland协议实现了Compositor功能。下述为官网介绍。实际上,QtApplicationManager是使用了QtWayland模块来实现Compositor的。Wayland是一套旨在替代XWindow的 Com…

微机实验:第5章——存储器设计

存储器设计 将两片6116所有的存储单元都写入11H。 提示:6116的存储容量为2K*8b,片内地址为0000H-07FFH,两片一起构成F8000H-F8FFFH的内存空间。 仿真调试时可以看到:每片从0000H-07FFH的每个存储单元均显示11H。 CODE SEGMENTASSUME CS:C…

4-4 哈夫曼编码

博主简介:一个爱打游戏的计算机专业学生博主主页: 夏驰和徐策所属专栏:算法设计与分析 1.什么是哈夫曼编码? 哈夫曼编码(Huffman coding)是一种用于数据压缩的无损编码方法。它是由David A. Huffman在1952…

STM32F4_软件模拟SPI

目录 1. 硬件连接 2. SPI通讯协议 3. W25Q64 简介 4. 程序详解 4.1 main.c 4.2 SPI.c 4.3 SPI.h 4.4 W25Q128.c 4.5 W25Q128.h 4.6 OLED.c 4.7 OLED.h 4.8 OLED_Font.h 5. 实验结果 我们都知道,SPI 和 IIC 一样,都可以通过硬件方式和软件方…

JSON基础(待补充)

一、JSON初识 1.1基础认识 JSON是一种轻量级的数据交换格式,它基于JavaScript语言的对象表示法,可以在多种语言之间进行数据交换。JSON的基本数据类型有数值、字符串、布尔值、数组、对象和空值。JSON的格式简洁易读,也易于解析和处理。JSON…

【数据结构】由完全二叉树引申出的堆的实现

【数据结构】由完全二叉树引申出的堆的实现 一、什么是堆二、目标三、实现1、初始化工作2、堆的插入(堆的创建)2.1、向上调整建堆2.1.1、向上调整算法原理解析2.1.2、代码实现 2.2、向下调整建堆2.2.1、向下调整算法原理解析2.2.2、代码实现 2.3、“向上”和“向下”复杂度的差…

初识网络安全

目录 HTML前置基础知识 1、id和class区别: 2、一些常用的属性: 3、HTML字符编码和实体编码 4、URL介绍 网址的组成部分: TTL值 DNS工作原理和资源记录及其种类: 5、正确区分“加密”和“签名” 6、状态码 1xx &#xf…

如何安装pycharm

PyCharm是JetBrains公司推出的一款Python集成开发环境(IDE),可以提供高效的Python代码编写、调试和测试。以下是一些PyCharm的主要功能: 代码智能提示和自动补全功能;支持调试和测试Python代码;完整的Pyth…