如何将从 http 调用收到的 pdf 加载到查看器中?

How to load a pdf received from an http call into the viewer?

我正在从以下位置获取 pdf:

return this.http.get(path, { observe: 'response', responseType: 'arraybuffer' });

并将其作为 blob 处理:

let blob: Blob = new Blob([response.body], { type: 'application/pdf' });

我现在希望将该文件存储在本地并通过本地 url 引用它。我需要使用 url 的原因是因为 viewer.loadModel() 需要它作为参数。我试过使用 let url = window.URL.createObjectURL(this.blobToFile(blob, 'viewer.pdf')); 来获得 url.

当我这样做时,我得到:https://localhost:3000/35ef3b8e-e59f-4fe9-a8cc-1e2e542ffb2a 不过,查看者不知道后面没有“.pdf”的文件类型。如果我把 .pdf 放在 url: https://localhost:3000/35ef3b8e-e59f-4fe9-a8cc-1e2e542ffb2a.pdf 后面,它似乎加载了一些数据,但我收到以下错误:

core.js:6241 ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'getData' of undefined
TypeError: Cannot read property 'getData' of undefined
    at v (Viewer3D.js:1335)
    at PDFLoader.js:1358
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:386)
    at Object.onInvoke (core.js:41697)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:385)
    at Zone.push../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:143)
    at zone.js:891
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
    at Object.onInvokeTask (core.js:41675)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:420)
    at resolvePromise (zone.js:832)
    at zone.js:739
    at zone.js:755
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:386)
    at Object.onInvoke (core.js:41697)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:385)
    at Zone.push../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:143)
    at zone.js:891
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
    at Object.onInvokeTask (core.js:41675)

在下面的最后一行:(Viewer3D.js:1335)

Viewer3D.prototype.loadModel = async function(url, options, onSuccessCallback, onErrorCallback, onWorkerStart) {
        var self = this;

        // Kind of sucks, but I couldn't think of a better way because loaders
        // are so
        var reservation = self.impl._reserveLoadingFile();     // Reserve a slot for a loading file
        options = options || {};

        if (typeof options.skipPropertyDb === "undefined") {
            var skipParam = getParameterByName("skipPropertyDb") || "";
            options.skipPropertyDb = skipParam === "true" ? true : (skipParam === "false" ? false : undefined);
        }

        var loaderInstance;
        function onDone( error, model ) {
            self.impl._removeLoadingFile(loaderInstance);
            if (error) {
                self.dispatchEvent({ type: et.LOADER_LOAD_ERROR_EVENT, error: error, loader: loaderInstance })
                onError( error.code, error.msg, error.args );
                return;
            }

            model.getData().underlayRaster = options.underlayRaster && model.getLeaflet();

有没有一种方法可以将 blob 中的 pdf 存储在本地,然后获取一个本地 URL 来使用它?或者有没有一种方法可以传递不记名令牌以便加载程序可以访问存储?我不确定还能做什么,因为 viewer.loadModel() 似乎只有在我使用文件的本地绝对路径时才有效(例如 https://localhost:3000/Content/Documents/qcad1.pdf ")。我还想提一下,我无法通过模型导数 api 处理它,因为如果我这样做,它会丢失 pdf 向量。

更多背景信息: 这是我加载模型的地方...

Autodesk.Viewing.Initializer({ env: 'Local' }, () => {
            this.viewer = new Autodesk.Viewing.GuiViewer3D(document.getElementById('forgeViewer'));
            this.viewer.start();
            this.viewer.setTheme('dark-theme');
            this.viewer.loadExtension('Autodesk.Viewing.MarkupsCore');

            this.viewer.loadModel(this.pdfUrl, {}, this.initializeMeasureSettings.bind(this),
                () => {
                    console.log('File could not be loaded');

                    // On error was hoping to load a sample model but it loses context to the viewer somehow
                    //this.viewer.loadModel(this.samplePDF, {}, this.initializeMeasureSettings);
                });

            this.viewer.addEventListener('measurement-changed', this.onMeasurementChanged.bind(this));
            this.viewer.addEventListener('measurement-completed', this.onMeasurementCompleted.bind(this));
            this.viewer.addEventListener('finished-calibration', this.onCalibrationFinished.bind(this));
            this.viewer.addEventListener('delete-measurement', this.onObjectDeleted.bind(this));
            this.viewer.addEventListener('extensionLoaded', this.extensionLoaded.bind(this));

        });

恐怕 loadModel 方法不支持任何方式来指定自定义 HTTP 请求 headers 所以如果对您的 PDF 文件的访问受到访问令牌的保护,那将是棘手。

您可以尝试“破解”loadModel 函数内部的逻辑,不是通过将 .pdf 直接附加到数据 URI,而是通过在 URL 中伪造扩展名查询或散列,例如:

https://localhost:3000/35ef3b8e-e59f-4fe9-a8cc-1e2e542ffb2a?name=foo.pdf

https://localhost:3000/35ef3b8e-e59f-4fe9-a8cc-1e2e542ffb2a#foo.pdf

这个可能足以说服浏览者使用PDF加载器,同时它不会破坏URL的意思。

所以我最终深入研究了 pdfLoader,我找到了一种传递不记名令牌的方法,PDFJS 在从 url!

加载文件时使用该令牌

就在调用 viewer.loadModel 之前,我设置了:

Autodesk.Viewing.endpoint.HTTP_REQUEST_HEADERS = { Authorization: `Bearer ${this.authenticationService.getToken}` };

此访问PDFLoader.js:749params.httpHeaders = av.endpoint.HTTP_REQUEST_HEADERS;.

这个 params 对象最终被传递到 Promise.resolve(第 1370 行)中的 PDFJS.getDocument(params)

这是 pdf.js 中的一项标准功能,但也许 Autodesk 可以通过允许它在 viewer.loadModel() 的选项对象中传递来使其更容易。

无论如何,这让我可以从后端安全地下载文件并将它们加载到查看器中。