springboot下载文件 范围下载

springboot,下载,文件,范围 · 浏览次数 : 39

小编点评

这段代码展示了如何使用 Spring Boot 下载文件并设置响应内容范围。 **主要代码分析:** 1. **`download()` 方法**: * 检查版本号是否为空。 * 获取文件路径、大小和 Range 信息。 * 使用 `FileUtil.getInputStream()` 获取文件流。 * 根据 Range 计算获取的字节范围和实际读取的字节数量。 * 将内容写入响应输出流。 * 关闭输入和输出流。 2. **`getOffset()` 方法**: * 解析 Range 字符串。 * 根据 "bytes=" 和 "-" 的位置获取范围的开始和结束位置。 * 处理不同的范围格式(开始和结束位置已包含或范围包含)。 3. **测试代码**: * 创建一个 `OkHttpClient` 的 HTTP Client。 * 构造一个请求,设置 Range 头信息。 * 执行请求并读取响应体。 * 打印读取到的字节数量。 **主要注意:** * Range 必须以 "bytes=" 或 "-"开头,并以 "-" 结尾。 * 如果 Range 超过文件大小的 10%,会抛出异常。 * 如果 Range 小于文件大小,会只获取内容的起始字节。 * 如果 Content-Length 与 Range 中所指定的字节长度不匹配,会抛出异常。 **总结:** 这段代码展示了如何使用 Spring Boot 下载文件并设置响应内容范围。通过对 Range 解析和处理,可以确保正确获取和读取文件内容。

正文

springboot下载文件 范围下载

关键词:springboot,download,Range,Content-Range,Content-Length,http code 206 Partial Content

下载文件的一部分,我们在request header:Range 中指定要获取的文件的字节范围。要注意http response header:Content-Length 一定要与Range中所表示的获取bytes的范围长度相等。
如果Content-Length大于要Range所表示的bytes长度,那么客户端在发起请求后,下载完相应的Range范围的bytes后,连接不会自动关闭,直到连接超时,抛出异常。
如果Content-Length小于要Range所表示的bytes长度,那么发起请求后,只会获得Content-Length所指定的较小范围的bytes,导致获取的数据不完整。

controller:

    @SneakyThrows
    @GetMapping("/download")
    public void download(HttpServletResponse response,
                         @RequestParam String versionNum,
                         @RequestHeader(value = HttpHeaders.RANGE, required = false) String range) {
        if (StringUtils.isEmpty(versionNum)) {
            throw new ServiceException("版本号不能为空");
        }
        // jar包的上一级目录
        String canonicalPath = FileUtil.getCanonicalPath(new File(".."));
        String filePath = canonicalPath + "/device-upgrade-pack-repo/" + versionNum + "/" + versionNum + ".tar";
        long fileSize = FileUtil.file(filePath).length();

        // 获得文件流
        BufferedInputStream inputStream = FileUtil.getInputStream(filePath);

        // offset表示从哪个字节开始读取,length表示读取多少个字节
        long offset = 0;
        long length;
        // 如果range为空,那么就是读取整个文件
        if (StringUtils.isEmpty(range)) {
            length = fileSize;
        } else {
            // 根据range计算offset和length
            Pair<Long, Long> pair = getOffset(range);
            offset = pair.getLeft();
            length = pair.getRight();
            // length == -1 表示 end range 为最后一个字节
            if (length == -1) {
                length = fileSize - offset + 1;
            }
            // 跳过文件的offset个字节
            inputStream.skip(offset);
        }

        response.setContentType("application/octet-stream");
        response.setHeader("Content-disposition",
                "attachment;filename=" + versionNum + ".tar");
        response.setHeader("Content-Length", String.valueOf(length));
        response.setHeader("Content-Range", "bytes" + " " +
                offset + "-" + (offset + length - 1) + "/" + fileSize);
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        ServletOutputStream os = response.getOutputStream();

        byte[] buf = new byte[1024];
        int bytesRead;
        // sum 是当前读取的字节数
        int sum = 0;
        while (sum < length && (bytesRead = inputStream.read(buf, 0, (length - sum) <= buf.length ? (int) (length - sum) : buf.length)) != -1) {
            os.write(buf, 0, bytesRead);
            sum += bytesRead;
        }

        inputStream.close();
        os.close();
    }

解析range:

    /**
     * left is offset, right is length
     *
     * @param range
     * @return
     */
    private Pair<Long, Long> getOffset(String range) {
        if (!range.contains("bytes=") || !range.contains("-")) {
            throw new ServiceException("range 格式错误");
        }
        range = range.substring(range.lastIndexOf("=") + 1).trim();
        String[] split = StringUtils.split(range, "-");

        if (range.startsWith("-")) {
            return Pair.of(0L, Long.parseLong(split[0]) + 1);
        } else if (range.endsWith("-")) {
            return Pair.of(Long.parseLong(split[0]), -1L);
        } else {
            if (split.length != 2) {
                throw new ServiceException("range 格式错误");
            }
            return Pair.of(Long.parseLong(split[0]), Long.parseLong(split[1]) - Long.parseLong(split[0]) + 1);
        }
    }

测试下载接口:

    @Test
    void downloadObject() {
        OkHttpClient httpClient = HttpUtil.getHttpClient();
        Request request = new Request.Builder()
                .get()
                .url("http://localhost:8081/device/version/download?versionNum=1.1.1.2023")
                .header("Range", "bytes=0-10")
                .build();

        try (Response response = httpClient.newCall(request).execute()) {
            InputStream inputStream = response.body().byteStream();
            byte[] buf = new byte[5];
            int readBytes;

            while ((readBytes = inputStream.read(buf)) != -1) {
                if (readBytes < buf.length) {
                    buf = Arrays.copyOf(buf, readBytes);
                }
                System.out.println(Arrays.toString(buf) + "   " + readBytes);
            }
            inputStream.close();
            response.close();
            System.out.println("end");
        } catch (IOException e) {
            throw new ServiceException("请求异常");
        }
    }


输出结果:
[49, 46, 49, 46, 49]   5
[46, 50, 48, 50, 51]   5
[46]   1
end

与springboot下载文件 范围下载相似的内容:

springboot下载文件 范围下载

# springboot下载文件 范围下载 关键词:springboot,download,Range,Content-Range,Content-Length,http code 206 Partial Content 下载文件的一部分,我们在request header:Range 中指定要获取

文件下载与上传

# 文件上传与下载 工具:hutool springboot 文件上传与下载,hutool目录操作、文件解压、yaml文件操作。此例子展示了一个文件压缩包上传到服务器,解压放到某个特定目录的,并且校验md5值 ## html ``` 文件上传界面 ​ file_name ​ ``` ## sprin

k8s部署springboot

# 综述 首先介绍发布步骤。 1.从代码仓库下载代码,比如GitLab; 2.接着是进行打包,比如使用Maven; 3.编写Dockerfile文件,把步骤2产生的包制作成镜像; 4.上传步骤3的镜像到远程仓库,比如Harhor; 5.编写Deployment文件; 6.提交Deployment文件

K8S发布应用步骤详解

前言 首先以SpringBoot应用为例介绍一下k8s的发布步骤。 1.从代码仓库下载代码,比如GitLab; 2.接着是进行打包,比如使用Maven; 3.编写Dockerfile文件,把步骤2产生的包制作成镜像; 4.上传步骤3的镜像到远程仓库,比如Harhor; 5.编写Deployment文

k8s发布应用

# 前言 首先以SpringBoot应用为例介绍一下k8s的发布步骤。 1.从代码仓库下载代码,比如GitLab; 2.接着是进行打包,比如使用Maven; 3.编写Dockerfile文件,把步骤2产生的包制作成镜像; 4.上传步骤3的镜像到远程仓库,比如Harhor; 5.编写Deploymen

K8S部署应用详解

# 前言 首先以SpringBoot应用为例介绍一下k8s的发布步骤。 1.从代码仓库下载代码,比如GitLab; 2.接着是进行打包,比如使用Maven; 3.编写Dockerfile文件,把步骤2产生的包制作成镜像; 4.上传步骤3的镜像到远程仓库,比如Harhor; 5.编写Deploymen

docker制作springboot镜像

以下步骤在具有Docker环境的Linux机器上操作。 把springboot-1.0.0.jar放到/usr/local/springboot目录下,并在该目录下创建Dockerfile文件,内容为: FROM openjdk:8-jdk-alpine ADD springboot-1.0.0.j

Java与React轻松导出Excel/PDF数据

前言 在B/S架构中,服务端导出是一种高效的方式。它将导出的逻辑放在服务端,前端仅需发起请求即可。通过在服务端完成导出后,前端再下载文件完成整个导出过程。服务端导出具有许多优点,如数据安全、适用于大规模数据场景以及不受前端性能影响等。 本文将使用前端框架React和服务端框架Spring Boot搭

[转帖]springboot中使用skywalking实现日志追踪

文章目录 SkyWalking分布式追踪系统介绍主要架构 环境引入依赖配置Log4j2下载编译好的8.7.0版本包使用探针实现日志追踪启动脚本启动Java服务访问服务 使用UI切换存储方式 SkyWalking分布式追踪系统 介绍 Skywalking是一个国产的开源框架,2015年有吴晟个人开源,

[转帖]SpringBoot配置SSL 坑点总结【密码验证失败、连接不安全】

文章目录 前言1.证书绑定问题2.证书和密码不匹配3.yaml配置文件问题3.1 解密类型和证书类型是相关的3.2 配置文件参数混淆 后记 前言 在SpringBoot服务中配置ssl,无非就是下载证书设置一下配置文件的问题,这里主要记录我在配置的过程中遇到的坑点。 如果是新手上道的话建议结合其他的