1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 微应用如何实现自动更新提示

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

时间:2020-04-27 19:23:03

相关推荐

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

首先, 先讲一下本次文章所讲的场景, 经过调研, 公司内部使用后台, 当有需求功能迭代的时候, 通常使用者会没有感知, 使用者只会在浏览器内一直打开这个页面, 当需要使用的时候, 再切换这个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.envconst source = new EventSource(`${configEnv.VITE_BASE_API}/sseServer/events`);let oldStr = ''// 监听 message 事件source.onmessage = event => {let time = JSON.parse(event.data).timeif (oldStr.length === 0) {oldStr = timeconsole.log('connect success')}else if(oldStr !== time) {window.alert('系统更新啦~~~')oldStr = time}}

SSE 与 Socket 有什么区别?

缺点:

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

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

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

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

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

以上方案流程如下:

前端服务轮询实现

上面的解决方案, 都是需要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: PluginOptionsconstructor(options: PluginOptions) {this.options = options || {}}apply(compiler: Compiler) {const {publicPath } = compiler.options.outputif (this.options.injectFileBase === undefined)this.options.injectFileBase = typeof publicPath === 'string' ? publicPath : '/'const {versionType, customVersion, silence } = this.optionslet version = ''if (versionType === 'custom')version = getVersion(versionType, customVersion!)elseversion = getVersion(versionType!)compiler.hooks.emit.tap(pluginName, (compilation: Compilation) => {// const outputPath = compiler.outputPathconst jsonFileContent = generateJSONFileContent(version, silence)// @ts-expect-errorcompilation.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 errorwindow.addEventListener("error",err => {const errTagName = (err?.target as any)?.tagName;if (errTagName === "SCRIPT") {checkSystemUpdate();}},true);

最终的流程如下:

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

/0522skylar/web-auto-notify

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

/package/web-auto-notify-webpack

/package/web-auto-notify-client

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。