当前位置: 首页 > news >正文

OSS文件上传(三):断点续传

        在大文件上传场景中,网络中断或客户端闪退常导致上传失败。传统方案需要重新上传整个文件,而断点续传技术允许从中断处继续上传,大幅提升用户体验并节省资源。OSS原生支持断点续传,但有时我们需要更灵活的自定义实现。本文将对比分析OSS原生方案与Redis自定义方案的差异,并详细讲解Redis实现方案。

初始化配置

 (1)依赖配置(Maven)

首先在pom.xml中添加 OSS SDK 相关依赖:

<dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.17.4</version>
</dependency>

如果使用的是Java 9及以上的版本,则需要添加以下JAXB相关依赖:

<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version>
</dependency>
<dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId><version>2.3.3</version>
</dependency>
(2)OSS 配置

application.yml中配置 OSS 连接信息:

aliyun:oss:endpoint: oss-cn-beijing.aliyuncs.com # 地域Endpointaccess-key-id: your-access-key-idaccess-key-secret: your-access-key-secretbucket-name: your-bucket-name # 存储桶名称

创建配置类初始化 OSS 客户端:

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;@Data
@Component
@ConfigurationProperties(prefix = "aliyun")
public class AliYunConfig {private String accessKey; // 阿里云AccessKeyIdprivate String accessKeySecret; // 阿里云AccessKeySecretprivate String ossBucket; // 存储桶名称private String ossEndpoint; // OSS地域节点@Beanpublic OSS oSSClient() {return new OSSClient(ossEndpoint, accessKey, accessKeySecret);}
}

创建统一结果返回类:

package com.netflow.utils;import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;/*** 统一结果返回类* @param <T> 返回数据的类型*/public class Msg<T> implements Serializable {private static final long serialVersionUID = 1L;// 状态码private int code;// 消息private String message;// 返回数据private T data;// 时间戳private long timestamp;// 附加数据(可选)private Map<String, Object> extra;// 构造方法private Msg() {this.timestamp = System.currentTimeMillis();}private Msg(int code, String message) {this();this.code = code;this.message = message;}private Msg(int code, String message, T data) {this(code, message);this.data = data;}// 成功静态工厂方法public static <T> Msg<T> success() {return new Msg<>(200, "操作成功");}public static <T> Msg<T> success(String message) {return new Msg<>(200, message);}public static <T> Msg<T> success(T data) {return new Msg<>(200, "操作成功", data);}public static <T> Msg<T> success(String message, T data) {return new Msg<>(200, message, data);}// 失败静态工厂方法public static <T> Msg<T> fail() {return new Msg<>(500, "操作失败");}public static <T> Msg<T> fail(String message) {return new Msg<>(500, message);}public static <T> Msg<T> fail(int code, String message) {return new Msg<>(code, message);}// 链式调用方法public Msg<T> code(int code) {this.code = code;return this;}public Msg<T> message(String message) {this.message = message;return this;}public Msg<T> data(T data) {this.data = data;return this;}public Msg<T> extra(String key, Object value) {if (this.extra == null) {this.extra = new HashMap<>();}this.extra.put(key, value);return this;}// Getter方法public int getCode() {return code;}public String getMessage() {return message;}public T getData() {return data;}public long getTimestamp() {return timestamp;}public Map<String, Object> getExtra() {return extra;}@Overridepublic String toString() {return "Msg{" +"code=" + code +", message='" + message + '\'' +", data=" + data +", timestamp=" + timestamp +", extra=" + extra +'}';}
}

一、内置断点续传(setEnableCheckpoint(true)

核心流程

OSS通过分片上传(Multipart Upload)实现断点续传:

  1. 初始化:创建分片上传任务,获取唯一UploadID

  2. 分片上传:将文件切分为多个分片(每片5-10MB),并行上传

  3. 记录状态:OSS服务端自动记录已上传的分片信息

  4. 中断恢复:重新上传时查询UploadID对应的上传状态,仅上传缺失分片

  5. 成合并:所有分片上传完成后,调用Complete接口合并文件

基本实现:
    @PostMapping("/upload3")public Msg<String> upload(@RequestParam("file") MultipartFile file) {try {String objectName = util(file);ObjectMetadata meta = new ObjectMetadata();// 指定上传的内容类型。// meta.setContentType("text/plain");// 文件上传时设置访问权限ACL。// meta.setObjectAcl(CannedAccessControlList.Private);// 通过UploadFileRequest设置多个参数。// 依次填写Bucket名称(例如examplebucket)以及Object完整路径(例如exampledir/exampleobject.txt),Object完整路径中不能包含Bucket名称。UploadFileRequest uploadFileRequest = new UploadFileRequest(aliYunConfig.getOssBucket(),objectName);// 通过UploadFileRequest设置单个参数。// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。uploadFileRequest.setUploadFile("E:\\SpringBoot\\test\\examplefile.txt");// 指定上传并发线程数,默认值为1。uploadFileRequest.setTaskNum(5);// 指定上传的分片大小,单位为字节,取值范围为100 KB~5 GB。默认值为100 KB。uploadFileRequest.setPartSize(1 * 1024 * 1024);// 开启断点续传,默认关闭。uploadFileRequest.setEnableCheckpoint(true);// 记录本地分片上传结果的文件。上传过程中的进度信息会保存在该文件中,如果某一分片上传失败,再次上传时会根据文件中记录的点继续上传。上传完成后,该文件会被删除。// 如果未设置该值,默认与待上传的本地文件同路径,名称为${uploadFile}.ucp。//uploadFileRequest.setCheckpointFile("yourCheckpointFile");// 文件的元数据。uploadFileRequest.setObjectMetadata(meta);// 设置上传回调,参数为Callback类型。//uploadFileRequest.setCallback("yourCallbackEvent");// 断点续传上传。ossClient.uploadFile(uploadFileRequest);return Msg.success("断点续传成功");} catch (OSSException oe) {System.out.println("Caught an OSSException, which means your request made it to OSS, "+ "but was rejected with an error response for some reason.");System.out.println("Error Message:" + oe.getErrorMessage());System.out.println("Error Code:" + oe.getErrorCode());System.out.println("Request ID:" + oe.getRequestId());System.out.println("Host ID:" + oe.getHostId());} catch (Throwable ce) {System.out.println("Caught an ClientException, which means the client encountered "+ "a serious internal problem while trying to communicate with OSS, "+ "such as not being able to access the network.");System.out.println("Error Message:" + ce.getMessage());} finally {// 关闭OSSClient。if (ossClient != null) {ossClient.shutdown();}}return Msg.success("断点续传失败");}/*** UUID做前缀加工文件名* @param file* @return*/private String util(MultipartFile file){//获取初始文件名String fileName = file.getOriginalFilename();//获取文件后缀String suffixName = fileName.substring(fileName.lastIndexOf("."));//UUID作为文件名String newFileName = UUID.randomUUID().toString()+ "-"+ fileName + suffixName;return newFileName;}
原理:
  • 状态存储:上传进度保存在客户端本地文件系统(如临时文件)。
  • 实现方式:SDK 自动管理,无需额外组件。
  • 适用场景:单机环境或无需跨设备 / 进程共享断点信息的场景。
优点:
  • 简单易用:只需一行代码开启,无需配置外部存储。
  • 轻量级:无需维护 Redis 等外部服务。
缺点:
  • 局限性
    • 仅支持单个客户端实例(换设备 / 重启应用后断点丢失)。
    • 无法在分布式系统中共享断点状态。
  • 可靠性风险:本地文件可能损坏或丢失。

二、Redis自定义断点续传

当需要跨客户端恢复、自定义分片策略或对接非阿里云存储时,Redis方案更灵活:

核心流程:
基本实现:

实现原理:
  • 状态存储:上传进度保存在Redis 服务器(内存或持久化存储)。
  • 实现方式:开发者自定义逻辑,将断点信息序列化后存入 Redis(如使用 Hash 结构存储文件 ID、已上传块号等)。
  • 适用场景:分布式系统、多设备协同或需要长期保留断点的场景。

优点:
  • 跨设备 / 进程共享:支持多客户端协作上传同一文件(如浏览器和移动端同时上传)。
  • 高可用性:Redis 支持集群和持久化,减少断点丢失风险。
  • 扩展性:可结合 Redis 的过期策略自动清理长期未活跃的断点。
缺点:
  • 复杂度高:需要额外维护 Redis 服务,并编写序列化 / 反序列化逻辑。
  • 性能依赖:网络延迟可能影响断点状态的读写速度。

三、对比选择

核心差异对比表

特性内置断点续传 (setEnableCheckpoint)Redis 断点续传
状态存储位置客户端本地文件系统Redis 服务器(内存 / 磁盘)
跨设备支持❌ 仅支持单设备✅ 多设备 / 进程共享
实现复杂度✅ 简单(SDK 自动管理)❌ 需要自定义代码和 Redis 维护
断点持久化❌ 客户端重启可能丢失✅ Redis 可持久化(如 RDB/AOF)
分布式系统适用❌ 不适用✅ 天然支持
典型场景单机上传、临时文件上传企业级应用、多端协同上传

Redis方案优化建议

  1. 状态过期策略:设置Redis Key过期时间(如TTL=7天)

    redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
  2. 分片数据存储

    • 方案1:直接存储Base64(适合小文件)

    • 方案2:将分片暂存OSS,Redis仅记录ETag(推荐大文件)

并发控制

// 使用Redis分布式锁保证分片状态更新原子性
RLock lock = redissonClient.getLock("LOCK:" + fileMd5);
lock.lock();
try {// 更新上传状态
} finally {lock.unlock();
}

何时选择哪种方式?

  • 选内置断点续传

    • 简单应用,无需跨设备 / 进程断点共享。
    • 临时文件上传,断点丢失影响较小。
    • 快速实现,不想引入外部依赖。
  • 选 Redis 断点续传

    • 分布式系统或微服务架构。
    • 需要多用户 / 设备协作上传同一文件。
    • 断点信息需要长期保留或审计。
    • 已有 Redis 基础设施,希望复用资源。

总结:

        内置断点续传是快速实现单设备断点功能的首选,而 Redis 方案则提供了更强大的分布式和持久化能力,适合复杂场景。开发者应根据实际场景选择:优先使用原生能力降低复杂度,在需要深度定制时采用Redis方案。两种方案的核心思想都是"分片上传+状态记录",理解这一原理能帮助我们在任何存储系统中实现可靠的文件传输。

OSS文件上传----完结!!!

http://www.lryc.cn/news/594678.html

相关文章:

  • CentOS 系统上部署一个简单的 Web 应用程序
  • Git上传与下载GitHub仓库
  • 计算机网络:概述层---计算机网络的性能指标
  • FastMCP全篇教程以及解决400 Bad Request和session termination的问题
  • 网络服务(第三次作业)
  • 果园里的温柔之手:Deepoc具身智能如何重塑采摘机器人的“生命感知”
  • GoLand安装指南
  • QT6 源,七章对话框与多窗体(5) 文件对话框 QFileDialog 篇二:源码带注释
  • Android 默认图库播放视频没有自动循环功能,如何添加2
  • 文远知行推出与联想共研的100%车规级HPC 3.0计算平台
  • SpringDoc 基本使用指南
  • Boost库智能指针boost::shared_ptr详解和常用场景使用错误示例以及解决方法
  • 如何防止QQ浏览器录屏,盗录视频资源?
  • Pytorch02:深度学习基础示例——猫狗识别
  • MySQL(05) mysql锁,MVCC、Innodb行锁
  • 网络协议与层次对应表
  • Spring Boot 集成 RabbitMQ:普通队列、延迟队列与死信队列全解析
  • 我的网页聊天室设计
  • Python100个库分享第38个—lxml(爬虫篇)
  • sky-take-out项目中Redis的使用
  • 【Linux】Prometheus 监控 Kafka 集群
  • 基于大数据的旅游推荐系统 Python+Django+Hive+Vue.js
  • 关于 URL 中 “+“ 号变成空格的问题
  • 机器学习对词法分析、句法分析、浅层语义分析的积极影响
  • 人工智能真的能编程吗?研究勾勒出自主软件工程的障碍
  • [Python] -项目实战10- 用 Python 自动化批量重命名文件
  • 识别并计算滑块距离
  • 远程登录服务器黑屏如何处理?
  • 日历类生辰八字九九三伏入梅出梅算法
  • 某日在某个月份中不存在导致软件出现异常的问题排查(判断闰年以及月份中的天数,附完整源码)