最近项目内需要做超过200M以上的PDF文件预览,之前基于PDFJS 所做的小文件预览就不太能支持了,因为文件过大,请求之后网页直接就崩掉了。
由于业务预览大文件的需求还是比较着急的,所以我们先上线了一版中间方案,也就是先判断文件的大小,如果文件大小超过某个值,就提示文件过大,无法线上预览,可点击下载后进行查看。
在中间方案上线过程中,我们开始调研如何去用最终方案来做。
这里有两个思路:
- OBS存储的地址,用window.open的方式去做打开进行预览。
- 采用文件切片,按固定页数进行切片后请求。
OBS是什么呢,其实就是华为云对象存储的服务,简单来说就是把海量文件存储到华为云服务的这个桶中,存储成功后,返回一个obs地址。
而这个地址,在浏览器是无法直接预览的,window.open对此地址执行的是下载动作,此处是OBS官方给的解释:
基于安全合规要求,华为云对象存储服务OBS禁止通过OBS的默认域名(桶访问域名或静态网站访问域名)在线预览桶内对象,即使用上述域名从浏览器访问桶内对象(如视频、图片、网页等)时,不会显示对象内容,而是以附件形式下载。
各区域将自以下两个时间点起生效:
自2022年1月1日起生效:华北-北京一、华北-北京四、华北-乌兰察布一、华东-上海一、华东-上海二、华南-广州、西南-贵阳一
自2022年3月25日起生效:中国-香港、亚太-曼谷、亚太-新加坡、非洲-约翰内斯堡、拉美-墨西哥城一、拉美-墨西哥城二、拉美-圣保罗一、拉美-圣地亚哥
如果想将此地址改为文件预览的模式,官方也给了解决方案:
简单来说就是需要运维的配合,需要将此地址的元数据进行配置修改,将其改成可预览的模式。
这个方案跟运维沟通过后,运维同事也配合做了修改。但是有一个小问题,就是在文件上传的时候,将文件的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 }) } } })}