Springboot实现文件断点续传-基于GridFS

news2024/11/26 0:56:55

Springboot实现文件断点续传-基于GridFS

需求介绍

我们后台是使用GridFS存储文件对象的,之前客户端都是Web浏览器,网络环境相对较为稳定,所以我们直接提供文件下载就行。但最近新增需求需要在移动端进行文件下载,这就有问题了。如果移动端在网络环境好的情况下下载文件肯定没问题,但移动设备的信号很容易波动、不稳定,下载链接很容易中断或出错,如果还是之前的方式就要重新下载了,这既浪费了时间又浪费了流量,所以在移动端以前的方式就不合适了。

这个时候我们就参考使用bt下载文件的模式来,实现断点续传功能,我们基于文件大小进行拆分,比如将文件分为10份,如果中间下载出现问题,只需要接着之前的部分接着下载就行,最后合并成一个文件。

接下来介绍后端java实现的方式。展示核心方法。

后端实现

service接口

定义文件传输接口,如下所示:

    /**
     * 通过文件id获取数据
     *
     * @param id       文件id
     * @param range    Range请求头
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     */
    void resumeDownload(String id, String range, HttpServletRequest request, HttpServletResponse response);

service实现类

下面是具体的实现方法,说明一下我是使用mongodb的一张表存储了文件信息,所以我是先去FileModel查询出文件信息,再到gridFS中获取具体文件流。如果实现方式和我不一样,那就不需要这个获取信息的步骤。

主要的思路就是让前端传range范围(前端通过其他接口已经获取了文件大小),如下图的filesize字段。

image-20230414095958511

比如bytes=0-1000,文件大小10000这就是返回文件的前百分之十,前端通过range就可以记录文件传输的情况,就算中途断开了,前端也可以接着之前的部分下载,这就实现了断点续传。

基于range不仅可以断点续传,还可以实现多线程文件下载,在APP端可以基于文件大小进行拆分,每个线程下载一部分文件,最后当文件下载完成后合并成一个文件。

    @Resource
    private FileRepository fileRepository;

    @Resource
    private GridFsTemplate gridFsTemplate;

    @Resource
    private GridFSBucket fsBucket;  

@Override
    public void resumeDownload(String id, String range, HttpServletRequest request, HttpServletResponse response) {
        Optional<FileModel> fileInfoByName = fileRepository.findById(id);
        if (!fileInfoByName.isPresent()) {
            return;
        }
        FileModel fileModel = fileInfoByName.get();
        String objectId = fileModel.getFsId();
        Query query = Query.query(Criteria.where("_id").is(objectId));
        GridFSFile gridFSFile = gridFsTemplate.findOne(query);
        try {
            assert gridFSFile != null;
            try (GridFSDownloadStream in = fsBucket.openDownloadStream(gridFSFile.getObjectId())) {
                GridFsResource resource = new GridFsResource(gridFSFile, in);
                InputStream inputStream = resource.getInputStream();

                long contentLength = fileModel.getSize();
                String contentType = fileModel.getContentType();
                String formFileName = fileModel.getName();
                String fileName = URLEncoder.encode(formFileName, "UTF-8").replaceAll("\\+", "%20");

                long start = 0;
                long end = contentLength - 1;
                if (range != null && range.startsWith("bytes=")) {
                    String[] values = range.split("=")[1].split("-");
                    start = Long.parseLong(values[0]);
                    if (values.length > 1) {
                        end = Long.parseLong(values[1]);
                        response.setHeader(HttpHeaders.CONTENT_TYPE, "application/octet-stream");
                    }
                } else {
                    response.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
                }

                if (start >= contentLength || end >= contentLength) {
                    response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                    return;
                }

                long rangeLength = end - start + 1;
                response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "fileName=\"" + fileName);
                response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(rangeLength));
                response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + start + "-" + end + "/" + contentLength);
                response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
                // 该语句的作用是设置HTTP响应的状态码为206,表示服务器已经成功处理了部分GET请求,客户端可以通过Range头信息指定需要获取的资源范围,服务器返回指定范围内的资源。这通常用于支持断点续传等功能。 设置后浏览器无法通过url直接请求下载
//                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                // 设置内容长度是为了让客户端能够正确接收到完整的响应数据,避免数据不完整或丢失的情况发生。如果不设置内容长度,客户端可能会在接收到部分数据后就关闭连接,导致服务器端的数据无法完整地传递给客户端。
                response.setContentLengthLong(rangeLength);
                IOUtils.copyLarge(inputStream, response.getOutputStream(), start, end);
                response.flushBuffer();
            }
        } catch (IOException e) {
            logger.error("Error while streaming file " + id, e);
        }
    }

controller层

这里就比较简单了,将service接口注入,并调用刚刚的方法就行

 @Resource
    FileService fileService;    

@ApiOperation(value = "断点续传,通过文件id获取文件")
    @GetMapping("/resumeDownload/{id}")
    public void resumeDownload(@PathVariable String id,
                               @RequestHeader(value = "Range", required = false) String range,
                               HttpServletRequest request,
                               HttpServletResponse response) {
        fileService.resumeDownload(id, range, request, response);
    }

问题记录

在实现过程中还是遇到了些问题

Web容器问题

原本项目中使用的是undertow而不是tomcat,但执行上面的方法文件能下载但会报错。报错信息如下所示:

image-20230414101719926

这就很奇怪了,文件代码都正常执行了,但还是会报错。在网上搜也没有找到具体的原因。如有大佬知到原因的请告知在下。

解决方法:我将undertow换回了tomcat就正常不报错了。对比两者不同如下:

Tomcat和Undertow都是Java Web服务器,但它们的性能特征略有不同。

Tomcat是一个成熟的Web服务器,经过多年的发展和改进,已经成为Java Web服务器的事实标准。它支持各种Web协议和技术,如HTTP、WebSocket、Servlet、JSP、EL等。Tomcat的性能非常好,可以处理大量的并发请求,但在处理静态文件和小型请求时,它的性能可能会稍有下降。

Undertow是一个轻量级的Web服务器,它专注于处理高性能的Web请求。它的设计目标是快速、轻量级和灵活,因此它可以在小型设备和云环境中运行。Undertow采用非阻塞I/O和事件驱动架构,可以很好地处理高并发请求,特别是在处理小型请求和静态文件时,它的性能比Tomcat更好。

总的来说,如果您需要一个成熟的Web服务器,可以处理各种Web协议和技术,那么Tomcat是一个不错的选择。如果您需要一个快速、轻量级和灵活的Web服务器,可以处理高并发请求,特别是小型请求和静态文件,那么Undertow可能更适合您的需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/424111.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++函数重载

C函数重载&#xff08;Function Overloading&#xff09;是指在同一个作用域&#xff0c;可以定义多个名称相同但参数列表不同的函数。在调用这些同名函数时&#xff0c;编译器根据实参与各个形参的类型、个数或顺序等特征来确定调用哪一个函数。 通过函数重载&#xff0c;我们…

UE4/5多人游戏详解(三、创建会话,委托绑定回调函数)

目录 基础的创建 回调函数绑定到委托&#xff1a; 实现创建会话的函数createGameSession&#xff1a; 回调函数实现判断验证是否成功&#xff1a; 添加会话设置&#xff1a; 测试 基础的创建 [提示&#xff1a;中途如果有无法编译则删除Binaries,saved,Intermediate后重…

倍增?最近公共祖先?——从定义到实现,帮你一步步吃掉它!

倍增&#xff1f;最近公共祖先&#xff1f;——从定义到实现&#xff0c;帮你一步步吃掉它&#xff01; 一、倍增倍增——翻倍的增长 倍增是一种思想&#xff0c;实际上的操作就是通过不断翻倍来缩短我们的处理时间&#xff1a; 它可以把线性级别的处理优化到指数级。 举个…

5.redis-哨兵模式

01-哨兵模式概述 如果master宕机, 我们该怎么办? ①关闭所有slave②选举新的master, 建立新的主从结构 存在的问题 ①关闭期间, 谁来提供数据服务②选举新master的标准是什么③原来的master恢复了怎么办 哨兵模式 sentinel是一个分布式系统&#xff0c;用于对主从结构中的每…

【多线程】Thread类

1. Java中如何进行多线程编程&#xff1f;线程是操作系统中的概念&#xff0c;操作系统内核实现了线程这样的机制&#xff0c;并且对用户层提供了一些 API 供用户使用(如 Linux 中的 pthread 库)。所以本身关于线程的操作&#xff0c;是依赖操作系统提供的的 API&#xff0c;而…

练习,异常,异常处理,try-catch,throws

package com.jshedu.homework_;/*** author Mr.jia* version 1.0*/ //匿名内部类 public class Homework04 {public static void main(String[] args) {Cellphone cellphone new Cellphone();//1.匿名内部类&#xff0c;同时也是一个对象/*new computer() {Overridepublic dou…

JavaClient With HDFS

序言 在使用Java创建连接HDFS的客户端时,可以设置很多参数,具体有哪些参数呢,只要是在部署HDFS服务中可以设置的参数,都是可以在连接的时候设置. 我没有去验证所有的配置是否都可以验证,只是推测cuiyaonan2000163.com 依据 创建HDFS的构造函数如下所示: 网上比较常用的是get…

gdb 跟踪调式core

自己编译的问题出现段错误: 编译:使用gdb调试core文件来查找程序中出现段错误的位置时,要注意的是可执行程序在编译的时候需要加上-g编译命令选项。 gdb调试core文件的步骤 gdb调试core文件的步骤常见的有如下几种,推荐第一种。 具体步骤一: (1)启动gdb,进入core文…

【剑指 offer】旋转数组的最小数字

✨个人主页&#xff1a;bit me&#x1f447; ✨当前专栏&#xff1a;算法训练营&#x1f447; 旋 转 数 组 的 最 小 数 字核心考点&#xff1a;数组理解&#xff0c;二分查找&#xff0c;临界条件 描述&#xff1a; 有一个长度为 n 的非降序数组&#xff0c;比如[1,2,3,4,5]…

ABAP 创建、修改、删除内部交货单(VL31N/VL32N)

一、干货 VL31N创建的BAPI&#xff1a; 1.GN_DELIVERY_CREATE 通用交货单使用的bapi&#xff0c;推荐使用 2.BAPI_DELIVERYPROCESSING_EXEC 简单&#xff0c;但是字段比较少 3.BBP_INB_DELIVERY_CREATE 听说有bug&#xff0c;我就没有使用这个了 VL32N修改/删除BAPI: BAPI_INB…

每日学术速递4.14

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Deep RL at Scale: Sorting Waste in Office Buildings with a Fleet of Mobile Manipulators 标题&#xff1a;大规模深度强化学习&#xff1a;使用移动机械手对办公楼中的垃圾进行…

VS2022编译libui库

libui是一个 C 中简单且可移植(但并非不灵活)的 GUI 库,它使用每个平台原生的GUI技术进行绘制。 官网地址:链接 本文将使用VS2022编译libui库,操作系统为Windows10。 1. 下载源代码 首先在官网下载源代码,由于此代码不依赖第三库,故只需下载源代码即可进行编译。 我下…

R730服务器环境搭建(centos7、lanproxy、docker、k8s)

文章目录前言一、centos7安装1.制作u盘启动盘2.开始装系统&#xff1a;二、环境安装&#xff08;lanproxy、docker、k8s&#xff09;1.lanproxy安装2.docker安装&#xff08;如果通过k8sOfflineSetup安装k8s可以跳过这一步&#xff0c;因为会自动安装docker&#xff09;3.安装k…

安装 KeyShot 流程

| 安装 KeyShot 流程 KeyShot 安装程序将指导您完成安装过程。 在 Windows 上&#xff0c;安装过程会要求您考虑以下事项终用户协议 为使用计算机的所有人或仅为当前用户安装 KeyShot 安装文件夹的位置 资源文件夹的位置 ——资源文件夹包含许多可以与 KeyShot 一起使用的纹…

NSSCTF doublegame题解

运行一下&#xff0c;是一个贪吃蛇游戏 先玩一玩&#xff0c;蛇的移动速度太快了&#xff0c;玩不了 查壳 64位文件&#xff0c;无壳 进入IDA分析 发现这个EXE文件是开了程序基址随机化&#xff0c;就是每次用IDA打开指令的地址不一样 我们要想使用x64dbg和IDA的时候&#…

Docker的基本操作

文章目录一、 Docker的基本操作1.1 镜像1.1.1 介绍1.1.2 镜像操作1.2 容器1.2.1 介绍1.2.2 容器操作1.3 数据卷1.3 介绍1.3.2 数据卷操作一、 Docker的基本操作 1.1 镜像 1.1.1 介绍 在 Docker 中&#xff0c;镜像&#xff08;Image&#xff09;是一种轻量级、可移植的、可扩…

营销平台一站式集成 高效实现自动化

市面上广告投放渠道渠道那么多&#xff0c;图文、动图、短视频等广告形式也越来越多&#xff0c;许多企业都会有这些疑问&#xff1a; 「腾讯广告、百度营销、巨量引擎哪个广告渠道的客户适合我们公司&#xff1f;」 「这么多广告渠道&#xff0c;哪家的点击率、转化率比较高…

1.Antlr4-简介入门

1.简介: ANTLR v4是一款功能强大的语法分析器生成器&#xff0c;可以用来读取、处理、执行和转换结构化文本或二进制文件。它被广泛应用于学术界和工业界构建各种语言、工具和框架。 2 关键字&#xff1a; import, fragment, lexer, parser, grammar, returns, locals, throw…

运维——记一次接口超时的问题与解决方法(HttpException: Read timed out)

前言&#xff1a;近期,一个线上的项目,请求出现了大量接口超时的问题,找了几个小时原因,最终发现是因为数据库服务器的磁盘满了,在此记录一下寻找的过程以及发现的问题,以备后续参考。 环境&#xff1a; 项目服务器(CentOS 64-bit 7.9) OpenJDK 1.8.0_272 数据库服务器(CentO…

打怪升级之FPGA组成原理(LE部分)

FPGA芯片逻辑单元的原理 不论你使用哪一款FPGA芯片&#xff0c;其核心可编程逻辑单元都是从一段内存种按顺序读取执行并执行的过程。具体来说&#xff0c;FOGA芯片内部包括可编程逻辑块(LAB)、可配置输入输出单元(IOE)、时钟管理模块、嵌入式RAM(BRAN&#xff0c;在Cyclone IV…