关于Web端PDF大文件预览

555 阅读3分钟

最近项目内需要做超过200M以上的PDF文件预览,之前基于PDFJS 所做的小文件预览就不太能支持了,因为文件过大,请求之后网页直接就崩掉了。

由于业务预览大文件的需求还是比较着急的,所以我们先上线了一版中间方案,也就是先判断文件的大小,如果文件大小超过某个值,就提示文件过大,无法线上预览,可点击下载后进行查看。

在中间方案上线过程中,我们开始调研如何去用最终方案来做。

这里有两个思路:

  1. OBS存储的地址,用window.open的方式去做打开进行预览。
  2. 采用文件切片,按固定页数进行切片后请求。

OBS是什么呢,其实就是华为云对象存储的服务,简单来说就是把海量文件存储到华为云服务的这个桶中,存储成功后,返回一个obs地址。

而这个地址,在浏览器是无法直接预览的,window.open对此地址执行的是下载动作,此处是OBS官方给的解释:

基于安全合规要求,华为云对象存储服务OBS禁止通过OBS的默认域名(桶访问域名或静态网站访问域名)在线预览桶内对象,即使用上述域名从浏览器访问桶内对象(如视频、图片、网页等)时,不会显示对象内容,而是以附件形式下载。

各区域将自以下两个时间点起生效:

自2022年1月1日起生效:华北-北京一、华北-北京四、华北-乌兰察布一、华东-上海一、华东-上海二、华南-广州、西南-贵阳一

自2022年3月25日起生效:中国-香港、亚太-曼谷、亚太-新加坡、非洲-约翰内斯堡、拉美-墨西哥城一、拉美-墨西哥城二、拉美-圣保罗一、拉美-圣地亚哥

如果想将此地址改为文件预览的模式,官方也给了解决方案:

如何在浏览器中在线预览OBS中的对象?

简单来说就是需要运维的配合,需要将此地址的元数据进行配置修改,将其改成可预览的模式。

这个方案跟运维沟通过后,运维同事也配合做了修改。但是有一个小问题,就是在文件上传的时候,将文件的ContentDisposition设置为可预览的文件。这么一改,原来window.open去做文件下载的代码就得更改下载方案,这就涉及到文件下载的前端改造。并且window.open的预览模式,也只支持打开新窗口的方式去预览,无法按照UI的设计去实现。

于是我们考虑做分片预览,就是在上传完成后,将大文件pdf按固定页数切片存放对应的OBS地址。比如一个100页的文件,上传完后,后端负责按照每20页切片一次,则一个文件存放的obs地址 是5个。而前端只需要循环调用,当前一片渲染即将完成时就去请求下一片,直到文件加载完成为止。

关键代码如下:

/** * * @param {Array} renderList 渲染pdf数据 * @param {number} time 延迟时间毫秒 */const sleepRenderPdf = (renderList = [], time = 400) => {  const len = state.pdfInstanceQueueList[state.pageNumber].pageNumList.length  let list = [...renderList] || []  const pageNumberList = list.splice(0, 1)  pageNumberList.forEach(async (pageNum) => {    const { documentLoadingTask } = state.pdfInstanceQueueList[state.pageNumber]    const pdfDocument = await documentLoadingTask.promise    const page = await pdfDocument.getPage(pageNum)    const pageRotation = rotation.value + page.rotate    const num = state.pageNumber * state.pageSize    const [divRoot, canvas, div1, div2] = createElementDom(num + pageNum)    const { width: actualWidth, height: actualHeight } = getPageDimensions(      (pageRotation / 90) % 2        ? page.view[2] / page.view[3]        : page.view[3] / page.view[2]    )    const styleW = Math.floor(actualWidth)    const styleH = Math.floor(actualHeight)    canvas.style.width = `${styleW}px`    canvas.style.height = `${styleH}px`    await renderPage(page, divRoot, canvas, actualWidth, pageRotation)    if (list.length > 0) {      timer.value = setTimeout(() => {        sleepRenderPdf(list, 400)      }, time)    }    if (props.textLayer) {      await renderPageTextLayer(page, div1, actualWidth, pageRotation)    }    if (props.annotationLayer) {      await renderPageAnnotationLayer(page, div2, actualWidth, pageRotation)    }    if (pageNum === len) {      console.log('🍉。。。。' + state.pageNumber + '批加载完了')      if (state.pdfTotal > 0 && state.pageNumber <= state.pdfTotal) {        state.pageNumber = state.pageNumber + 1        console.log('🍉。。。。我要请求第' + state.pageNumber + '批了')        fetchPdfFileView({ fileKey: DEFAULT_FILE_KEY, pageNumber: state.pageNumber })      }    }  })}