You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

POST方法从Blob Storage下载Buffer生成空白PDF求助

问题描述

使用POST方法从Azure Blob Storage下载PDF到Buffer时,先出现Content-Length不匹配错误(写入216523字节,预期215706字节),修复后能成功触发下载,但打开PDF后内容为空白。因隐私要求代码已脱敏,且无法使用pipe(Postman请求耗时过长),寻求可行解决方案。

相关代码
app.post(
    "/api/**client-endpoint**/download/attachments",
    async (req, res, next) => {
        try {
            let claims = await authentication.authJwtToken(req, req.context);
            req.context.log("Validated claims: ", JSON.stringify(claims));
            let allowedPermissions = [permissionConstants.PERMISSION_ADMIN_CUSTOMER_VIEW];
            let auth = await authorization.authorize(req.context, claims, permissionConstants.AUTH_MODE_CLIENT, allowedPermissions, req.body.clientId, undefined, undefined, constants.AUTH_TOKEN_TYPE_ADMIN);

            let client = await coreClientDao.mssqlGetClientByIdAuthObject(constants.**CLIENTNAME**_CLIENT_ID, req.context);

            const { clientId, customerId, fileUrl } = req.body;
            
            if (!fileUrl) {
                return res.status(400).send({ message: 'fileUrl is required.' });
            }

            req.context.log("Processing file for clientId: ", clientId);

            let blobUrl = fileUrl;

            if (!blobUrl) {
                req.context.log(`Skipping attachment, URL is not yet ready.`, new Date().toString());
                return res.status(400).send({ message: 'Attachment is not ready.' });
            } else {
                let sharedKeyCredential = new StorageSharedKeyCredential(utility.dec(client.storageName), utility.dec(client.storageKey));
                let blobServiceClient = new BlobServiceClient(
                    `https://${utility.dec(client.storageName)}.blob.core.windows.net`,
                    sharedKeyCredential
                );

                let urlParts = new URL(blobUrl);
                let pathParts = urlParts.pathname.split('/');
                let blobName = pathParts.slice(2).join('/'); 

                let sourceBlob = blobServiceClient.getContainerClient(constants.BLOB_CONTAINER_ATTACHMENT).getBlobClient(blobName);

                req.context.log('Downloading blob:', blobName);

                const pdfFile = await sourceBlob.downloadToBuffer({
                    onProgress: (progress) => {
                      req.context.log(`Downloaded ${progress.loadedBytes} bytes`);
                    }
                  });
                  
                res.setHeader('Content-Type', 'application/pdf');
                res.setHeader('Content-Disposition', `attachment; filename="${blobName}"`);
                res.set('Transfer-Encoding', 'identity');
                res.send(pdfFile);
            }
        } catch (error) {
            utility.catchErrorRespnse(res, req.context, error);
        }
    }
);
解决方案建议
  • 验证Buffer完整性:在downloadToBuffer后添加日志,打印pdfFile.length,对比Blob存储中目标文件的实际大小。如果长度不匹配,说明下载的Buffer不完整或指向错误文件。
  • 修正Blob名称解析:URL路径可能包含编码字符(如空格转%20),需解码后再拼接Blob名称:
    let blobName = decodeURIComponent(pathParts.slice(2).join('/'));
    
  • 移除强制传输编码设置:注释掉res.set('Transfer-Encoding', 'identity');,让Express自动处理响应头,避免手动设置导致内容传输异常。
  • 确认Blob文件属性:通过sourceBlob.getProperties()获取文件的contentType,确保存储的PDF文件类型为application/pdf,避免内容格式不匹配。
  • 优化下载参数:给downloadToBuffer添加重试参数,提升下载可靠性:
    const pdfFile = await sourceBlob.downloadToBuffer({
        onProgress: (progress) => {
          req.context.log(`Downloaded ${progress.loadedBytes} bytes`);
        },
        maxRetryRequests: 3
      });
    
  • 替换响应方法:将res.send(pdfFile)改为res.end(pdfFile),直接输出Buffer,避免Express对内容做额外处理导致损坏。

内容的提问来源于stack exchange,提问作者hellblazer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 19:15:14