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

SpringBoot WebFlux+Angular流式传输大页数PDF遇问题求助

大页数PDF流式传输问题:部分文件需全量下载才能显示

我尝试实现大体积PDF的流式传输,目前页数少于几千的文件能正常运行,但大页数PDF必须下载全部内容才能开始显示。经过测试:

  • 226MB(304页)文件:仅需下载32MB即可开始展示,跳转页面时按需下载对应内容
  • 453MB(608页)文件:需下载61MB开始展示
  • 大页数文件:必须全量下载才能显示

后端服务代码(控制器仅调用此函数)

public HashMap<String, Long> getRanges(HttpHeaders headers, long size){
    List<HttpRange> rangeHeader = headers.getRange();
    Long start;
    Long end;
    Long contentLength;
    HashMap<String, Long> ranges = new HashMap<>();
    ranges.put("size", size);

    if (!rangeHeader.isEmpty()) {
        start = Long.parseLong(String.valueOf(rangeHeader.get(0).getRangeStart(size)));
        ranges.put("start", start);
        end = size - 1;

        if (rangeHeader.get(0).getRangeEnd(size) > 0) {
            end = Long.parseLong(String.valueOf(rangeHeader.get(0).getRangeEnd(size)));
        }

        ranges.put("end", end);
        contentLength = end - start + 1;
        ranges.put("contentLength", contentLength);

    } else {
        ranges.put("start", 0L);
        ranges.put("end", size-1);
        ranges.put("contentLength", size);
    }

    return ranges;
}

public Mono<ResponseEntity<Resource>> getStreaming(String path, HttpHeaders headers) {
    return Mono.fromCallable(() -> {
        try {
            MinioClient minioClient = MinioClient.builder()
                    .endpoint("http://127.0.0.1:9000")
                    .credentials("admin", "password1")
                    .build();

            long size = minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket("test")
                            .object(path)
                            .build()
            ).size();

            String typeFormat = minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket("test")
                            .object(path)
                            .build()
            ).contentType();

            GetObjectArgs.Builder argsBuilder = GetObjectArgs
                    .builder()
                    .bucket("test")
                    .object(path);

            HashMap<String, Long> ranges = getRanges(headers, size);

            InputStream obj = minioClient.getObject(argsBuilder
                    .offset(ranges.get("start"))
                    .length(ranges.get("contentLength"))
                    .build());

            InputStreamResource resource = new InputStreamResource(obj);

            HttpHeaders headers1 = new HttpHeaders();
            headers1.set("Accept-Ranges", "bytes");
            headers1.setContentLength(ranges.get("contentLength"));
            headers1.set("Content-Range", "bytes " + ranges.get("start") + "-" + ranges.get("end") + "/" + ranges.get("size"));

            if(!headers.getRange().isEmpty()){
                return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT)
                        .contentType(MediaType.valueOf(typeFormat))
                        .headers(headers1)
                        .body(resource);
            }else {
                return ResponseEntity.ok()
                        .contentType(MediaType.valueOf(typeFormat))
                        .headers(headers1)
                        .body(resource);
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    });
}

前端调用代码

<ngx-pdf-viewer [src]="'/service?path=pdf/test_262MB.pdf'"></ngx-pdf-viewer>

问题原因分析

PDF的流式加载能力完全依赖文件内部的结构设计:

  1. 线性结构PDF:这类PDF的页面内容按顺序存储,核心的交叉引用表(XRef Table) 放在文件末尾。阅读器必须读取到交叉引用表才能定位各个页面的字节位置,因此必须全量下载整个文件才能开始解析显示。大页数PDF如果是这种结构,就会出现你遇到的问题。
  2. Web优化格式PDF(Fast Web View):这类PDF将交叉引用表拆分并嵌入到文件的多个位置,同时把页面内容分块存储。阅读器可以先下载开头的交叉引用信息,快速定位到第一页的内容,实现流式加载。你测试的小页数大体积文件属于这种优化后的结构。

解决建议

  1. 检查PDF结构:用专业PDF工具(如Adobe Acrobat)打开大页数PDF,查看是否启用了"快速Web视图"(Fast Web View)。如果未启用,说明是线性结构。
  2. 转换为Web优化格式:使用工具将大页数PDF转换为优化格式,例如用Ghostscript执行以下命令:
    gs -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dFastWebView=true -sOutputFile=optimized.pdf input.pdf
    
  3. 后端代码确认:当前后端的Range请求处理逻辑基本正确,需确保MinIO的getObject方法正确返回部分内容的流,且InputStreamResource没有将整个文件缓存到内存中(当前代码实现符合要求)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 00:06:32