ログ -- フロントエンドの再デプロイメントをユーザーに通知する方法

ここで私がインターネット上でまとめた知識の一部を皆さんに共有しますので、皆さんのお役に立てれば幸いです。

1. シーン

フロントエンドが構築されて起動された後も、ユーザーは古いページに留まります。ユーザーは、Web ページが再デプロイされたことを認識しません。ページにジャンプすると、js 接続のハッシュが変更され、エラーが発生することがあります。ジャンプしてしまい、ユーザーは新しい機能を体験できません。

2. 解決策

  1. 各パッケージは json ファイルを書き込むか、生成されたscript的src引入的hash地址或者etagファイルを比較し、ポーリングして呼び出して、更新するかどうかを決定します。
  2. フロントエンドは WebSocket の長期接続を使用します。具体的にはビルドするたびに、パッケージ化後にバックエンドに通知され、更新後に WebSocket を通じてフロントエンドに通知されます。

ポーリング呼び出しは、時間を制御することなく、プレルートガードで呼び出すように変更でき、ユーザーは呼び出して操作があるかどうかを判断するだけで済みます。

3. 具体的な実装

3.1 ポーリング方式

Xiaoman の実装を参照して、少し変更します。

class Monitor {
  private oldScript: string[] = []

  private newScript: string[] = []

  private oldEtag: string | null = null

  private newEtag: string | null = null

  dispatch: Record<string, (() => void)[]> = {}

  private stop = false

  constructor() {
    this.init()
  }

  async init() {
    console.log('初始化')
    const html: string = await this.getHtml()
    this.oldScript = this.parserScript(html)
    this.oldEtag = await this.getEtag()
  }
 // 获取html
  async 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*>/gi
    return 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) return
    const oldLen = this.oldScript.length
    const newLen = Array.from(
      new Set(this.oldScript.concat(this.newScript))
    ).length
    if (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 Webソケットモード

バックエンドは通信が難しいため、完全版を自分で実装してください。

具体的なプロセスは次のとおりです。

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) return
  Modal.confirm({
    title: '更新提示',
    icon: createVNode(ExclamationCircleOutlined),
    content: '版本有更新,是否刷新页面!',
    okText: '刷新',
    cancelText: '不刷新',
    onOk() {
     // 更新操作
      location.reload()
    },
    onCancel() {},
  })
})

// 建立连接
onMounted(() => {
  open()
})
// 断开链接
onUnmounted(() => {
  close()
})

3.2.2 リリースの展開

バックエンドの展開:

サーバーにノード環境がインストールされていないことを考慮して、docker を使用して直接デプロイし、pm2 を使用してノード プログラムを実行します。

  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 ハブに送信し、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;
        }
        // 将触发的更新代理到容器的3000
        location /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. まとめ

主なアプローチは 2 つあります。

  1. ポーリング呼び出しスキーム: Web ページによって導入されたスクリプト ファイルのハッシュ値または etag を取得するためのポーリング。このソリューションの利点は実装が簡単であることですが、パフォーマンスの消費が高く、遅延が発生するという問題があります。

  2. WebSocket版本方案:在前端部署的同时建立一个WebSocket连接,将后端构建部署完成的通知发送给前端。当后端完成部署后,通过WebSocket向前端发送消息,提示用户刷新页面以加载最新版本。这种方案的优点是实时性好,用户体验较好,但需要在前端和后端都进行相应的配置和代码开发。

本文转载于:

https://juejin.cn/post/7264396960558399549

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

 

おすすめ

転載: blog.csdn.net/qq_40716795/article/details/132158510