react + node + web worker + eventSource 图片数据自动合成功能技术要点

481 阅读2分钟

功能简介

  • 上传 excel、图片
  • 拖拽excel中的列字段到图片相应位置
  • 最终得到包含 excel 数据的多个图片的压缩包
  • 在合成图片过程中可以查看合成进度
  • 在下载压缩包时,会调用七牛云的 mkzip 压缩多文件进行下载

功能所需技术点

  • node 服务利用 node-xlsx 解析 excel 文件,前端通过 xlsx 生成 excel 文件,所以实现下载 excel 文件有两种方式,

    1. 后端返回文件流,前端利用 a 标签 download 实现下载
    2. 后端返回 excel 数组数据,前端利用 xlsx 生成 excel文件实现下载
  • 拖拽标签🏷到图片功能,利用 react-rnd 实现,监听 onDragStop,确定标签在图片上的位置并存储,供给之后合成图片使用

  • node 服务遍历excel数据,请求模板服务(此服务展示图片,并将字段显示为真实数据展示),当页面显示完成时,利用 puppeteer 中文文档

    1. 截图
    const browser = await puppeteer.launch({headless: false});
    const page = await browser.newPage()
    // 设置截图宽高
    puppeteerPage.setViewport({ width, height })
    await puppeteerPage.goto(url, { waitUntil: 'networkidle0' })
    // 截图到指定位置
    await puppeteerPage.screenshot({ path: filePath })
    
    1. 存储的过程需要读本地图片上传至七牛云
    const imgStream = fs.createReadStream(filePath)
    // 利用七牛云 api 实现上传
    
    1. 存储生成图片并上传的进度信息,将图片地址,表格数据id,模板id 都存储
  • 合成进度的查看,根据记录id分别查询合成成功和失败的条数,(成功+失败)/总条数为进度信息,前端使用 EventSource

    • SSE 与 WebSocket 作用相似,都是建立浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。
    • 总体来说,WebSocket 更强大和灵活。因为它是全双工通道,可以双向通信;SSE 是单向通道,只能服务器向浏览器发送,因为流信息本质上就是下载。如果浏览器向服务器发送信息,就变成了另一次 HTTP 请求。
    const source = new EventSource(url)
    source.onopen = () => {
      console.log('成功连接服务器')
    }
    source.onmessage = (event) => { // 监听未命名事件
      dispatch(setList(JSON.parse(event.data)))
    }
    // 当组件卸载时,关闭
    source.close()
    

    服务端

    ctx.res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
    })
    // 每6秒向客户端推送一次数据
    ctx.res.write(`retry:6000\ndata: ${JSON.stringify(data)}\n\n`)
    
  • 下载压缩包,利用七牛云mkzip,多文件压缩 node.js sdk持久化数据处理实现参考,请求七牛云文件压缩后,返回 persistentId,然后前端使用 Web Worker 阮一峰 开启定时器轮询持久化任务状态接口,code 返回 0 时去请求下载

    export function createWorker(f, param) {
      const blob = new Blob([`(${f.toString()})(persistentId=${JSON.stringify(param)})`])
      const url = window.URL.createObjectURL(blob)
      const worker = new Worker(url)
      return worker
    }
    // 开启一个子线程
    const pollingWorker = createWorker((id) => {
      setInterval(() => {
        // 这里一个坑,url 必须写全地址
        fetch(`${location.origin}/qiniuapi/status/get/prefop?id=${id}`)
          .then((res) => res.json()).then(({ code }) => {
            // 这里将请求到的结果发给主线程
            self.postMessage(code)
          })
      }, 2000)
    }, persistentId)
    pollingWorker.onmessage = (e) => {
      console.log('onmessage', e.data)
      // 主线程获取到的数据为 0 时,开始下载,下载完后关闭 worker 子线程
      if (e.data === 0) {
        const a = document.createElement('a')
        a.href = link
        a.download = '下载'
        document.body.appendChild(a)
        a.click()
        document.body.removeChild(a)
        pollingWorker.terminate()
      } else if (e.data > 2) {
        message.error('下载失败,请重试')
      }
    }
    pollingWorker.postMessage('init')
    

实现效果

  1. 拖动列字段到图片模板
  2. 进度信息用户无感自动刷新,用户不需要任何操作,进度即可自动刷新
  3. 点击下载按钮会等待一段时间,然后进行附件下载