springboot 对接华为云obs云存储

2024-08-27 23:04

本文主要是介绍springboot 对接华为云obs云存储,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

官网文档:华为云分片上传
https://support.huaweicloud.com/sdk-java-devg-obs/obs_21_0614.html

注意:前台使用的是vue-simple-uploader组件
所以使用的对象,都是通过前台传递过来的参数,进行获取的,后台接受到参数后,将对应信息保存,主要是依赖上传分片接口传递到obs返回对应的地址

1. 笔记:

分段上传分为如下3个步骤:1.初始化分段上传任务(ObsClient.initiateMultipartUpload)。
2.逐个或并行上传段(ObsClient.uploadPart)。
3.合并段(ObsClient.completeMultipartUpload)或取消分段上传任务(ObsClient.abortMultipartUpload)。一、初始化分段上传任务:
1.简介:
使用分段上传方式传输数据前,必须先通知OBS初始化一个分段上传任务。该操作会返回一个OBS服务端创建的全局唯一标识(Upload ID),用于标识本次分段上传任务。
您可以根据这个唯一标识来发起相关的操作,如取消分段上传任务、列举分段上传任务、列举已上传的段等。
2.方法定义:
obsClient.initiateMultipartUpload(InitiateMultipartUploadRequest request)二、上传段(Java SDK)
1.简介:
初始化分段上传任务后,通过分段上传任务的ID,上传段到指定桶中。除了最后一段以外,其他段的大小范围是100KB~5GB;最后一段的大小范围是0~5GB。
上传的段的编号也有范围限制,其范围是1~10000。
上传段时,除了指定上传ID,还必须指定段编号。您可以选择1和10000之间的任意段编号。段编号在您正在上传的对象中唯一地标示了段及其位置。
如果您使用之前上传的段的同一段编号上传新段,则之前上传的段将被覆盖。无论您何时上传段,OBS都将在其响应中返回ETag标头。
对于每个段上传任务,(您必须记录每个段编号和ETag值)。您在后续的合并请求中需要添加这些值以完成多段上传。2.方法定义:
obsClient.uploadPart(UploadPartRequest request)2.1 请求中包含的参数
参数名称  参数类型   			是否必选    简述
bucketName  String   			必选		桶名objectKey   String   			必选       对象名。对象名是对象在存储桶中的唯一标识。对象名是对象在桶中的完整路径,路径中不包含桶名。partNumber  int      			必选		段号。	uploadId	String   			必选 		分段上传任务的ID。任务ID可以通过初始化分段上传任务生成。例如:000001648453845DBB78F2340DD460D8。	input      java.io.InputStream  可选  		待上传对象的数据流。   约束限制:file为null,需要设置input字段不为空;input为null ,需要设置file字段不为空。二者需要选其一。file	   java.io.File 		可选		待上传对象的文件。offset	   long					可选		源文件中某一分段的起始偏移大小。取值范围:非负整数,不大于待上传对象的大小,单位:字节。默认取值:0partSize   Long					可选		当前段的长度。	约束限制:上传段接口要求除最后一段以外,其他的段大小都要大于100KB。但是上传段接口并不会立即校验上传段的大小(因为不知道是否为最后一段),只有调用合并段接口时才会校验。取值范围:100KB~5GB,单位:字节。默认取值:102400字节2.2 响应结果返回的参数
partNumber  段号
etag  String    
ETag是段内容的唯一标识,可以通过该值识别段内容是否有变化。
OBS会将服务端收到段数据的ETag值(段数据的MD5值)返回给用户。
比如上传段时ETag为A,下载段时ETag为B,则说明段内容发生了变化。
ETag只反映变化的内容,而不是其元数据。上传的段或拷贝操作创建的段,都有唯一的ETag。三、 合并段
1.简介:
如果用户上传完所有的段,就可以调用合并段接口,系统将在服务端将用户指定的段合并成一个完整的对象。
在执行“合并段”操作以前,用户不能下载已经上传的数据。
在合并段时需要将多段上传任务初始化时记录的附加消息头信息拷贝到对象元数据中,其处理过程和普通上传对象带这些消息头的处理过程相同。
在并发合并段的情况下,仍然遵循Last Write Win策略,但“Last Write”的时间定义为段任务的初始化时间。已经上传的段,只要没有取消对应的多段上传任务,都要占用用户的容量配额;
对应的多段上传任务“合并段”操作完成后,只有指定的多段数据占用容量配额,
用户上传的其他此多段任务对应的段数据如果没有包含在“合并段”操作制定的段列表中,
“合并段”完成后系统将删除多余的段数据,且同时释放容量配额。合并段时,OBS通过按升序的段编号规范化多段来创建对象。
如果在初始化上传段任务中提供了任何对象元数据,则OBS会将该元数据与对象相关联。成功完成请求后,段将不再存在。
合并段请求必须包括(上传ID、段编号和相应的ETag值的列表)。
OBS响应包括可唯一地识别组合对象数据的ETag。此ETag无需成为对象数据的MD5哈希。2.方法定义
obsClient.completeMultipartUpload(CompleteMultipartUploadRequest request)2.1 请求中包含的参数
参数名称  参数类型   		是否必选    简述
bucketName  String          必选		桶名objectKey   String          必选        对象名。对象名是对象在存储桶中的唯一标识。对象名是对象在桶中的完整路径,路径中不包含桶名。partEtag    List<PartEtag>  必选        待合并的段列表。uploadId    String          必选        分段上传任务的ID,例如:000001648453845DBB78F2340DD460D8 长度为32的字符串。2.2 PartEtag 包含参数
etag       String         必选   	段的ETag值。分段的Base64编码的128位MD5摘要。partNumber  Integer		  必选 	    段号。分段号可以是不连续的。   取值范围是[1,10000]的非负整数2.3 CompleteMultipartUploadResult 返回结果说明:     参数名称		   参数类型		  描述statusCode    	   int			 HTTP状态码。responseHeaders  Map<String, Object>  响应消息头列表,由多个元组构成。元组中String代表响应消息头的名称,Object代表响应消息头的值。etag			String		bucketName      String       合并段所在的桶的桶名。objectKey		String		 合并段后得到的对象名。对象名是对象在存储桶中的唯一标识。对象名是对象在桶中的完整路径,路径中不包含桶名。例如,您对象的访问地址为examplebucket.obs.cn-north-4.myhuaweicloud.com/folder/test.txt 中,对象名为folder/test.txt。location        String  	 合并段后得到的对象的url。  例如:https://example-Bucket.obs.regions.myhuaweicloud.com/example-ObjectversionId       String  	 合并段后得到的对象版本号。如果桶的多版本状态为开启,则会返回对象的版本号。objectUrl      	String		 合并段后得到的对象的全路径。encodingType    String		 用于指定对响应中的对象名objectKey进行指定类型的编码。如果objectKey包含xml 1.0标准不支持的控制字符,可通过设置该参数对响应中的objectKey进行编码。

2. 展示对应代码

2.1 表结构

-- pfqv_v2_test_prod0603.file_upload_log definitionCREATE TABLE `file_upload_log` (`id` bigint NOT NULL COMMENT '主键ID',`file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文件名',`identifier` char(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文件标识, MD5(文件加密后的名称)',`object_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '对象名。对象名是对象在存储桶中的唯一标识。对象名是对象在桶中的完整路径,路径中不包含桶名。',`chunk_size` bigint DEFAULT NULL COMMENT '分片大小',`file_chunk_num` int DEFAULT NULL COMMENT '文件分片总数',`upload_chunk_number` int DEFAULT NULL COMMENT '已上传的分片数量',`upload_chunk_index` int DEFAULT NULL COMMENT '上次上传的分片位置',`total_size` bigint DEFAULT NULL COMMENT '文件分片总大小',`upload_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'OBS返回的uploadId',`part_etags_json` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'partEtags对象,合并文件时所需列表',`file_url` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文件上传成功后的链接',`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',`create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '创建者',`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '更新者',`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `idx_identifier` (`identifier`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='记录文件上传进度记录';

2.2 分片上传代码

2.2.1 查看状态接口

Controller 控制层

    /*** 检查上传进度*/@GetMapping("/uploadVideo")public AjaxResult check(@ModelAttribute FileUploadRequest req) {CheckResponse checkVo =  fsUploadFileService.checkUploadStatus(req);return AjaxResult.success(checkVo);}

Service

 CheckResponse checkUploadStatus(FileUploadRequest req);

ServiceImpl

 /*** 该方法用于检查文件上传的状态,并返回相应的结果。* 如果文件未上传,则初始化上传并记录相关信息;* 如果文件已经部分上传,则返回已上传的分片信息,方便继续上传未完成的部分。* @param req* @return*/@Transactional(rollbackFor = Exception.class)@Overridepublic CheckResponse checkUploadStatus(FileUploadRequest req) {// 压缩文件// 文件的唯一标识符,通常是文件内容的 MD5 值String md5 = req.getIdentifier();// 文件总共分为多少个分片Integer chunkTotal = req.getTotalChunks();// 每个分片的大小Long chunkSize = req.getChunkSize();// 文件的总大小Long totalSize = req.getTotalSize();// 当前分片的位置 - 当前分片的序号
//        Integer chunkNumber = req.getChunkNumber();CheckResponse checkVo = new CheckResponse();Long userId = SecurityUtils.getUserId();// 2.获取 bucketName 和 objectKey// 获取文件的扩展名String filename = req.getFilename();String extension =filename.substring(filename.lastIndexOf('.') + 1);// 目标存储的文件名,由 md5 和文件扩展名组成String objectKey = md5 + "." + extension;String folderName = "";if (StringUtils.equalsAnyIgnoreCase(extension, MimeTypeUtils.IMAGE_EXTENSION)){folderName = "5S/images/";}else{folderName = "5S/video/";}objectKey = folderName + objectKey;// 从配置中获取的 OBS 存储桶名称//        //拼接上传路径
//        String separator = "/";
//        String objectKey = "creation" + separator + agencyCode +separator+competitionId + separator+userId+separator+fileName;FileUploadLog fileUploadLog = fileUploadLogMapper.selectFileUploadLogByFileMd5(md5);List<Integer> chunkIndexes = new ArrayList<>();// 上传文件不存在if(fileUploadLog == null){try {//代表不存在(未上传)checkVo.setUploaded(false);// 表示没有上传的分片checkVo.setChunkList(chunkIndexes);//插入一条InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectKey);InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);final String uploadId = result.getUploadId();checkVo.setUploadId(uploadId);FileUploadLog fileUploadLogInsert = new FileUploadLog();fileUploadLogInsert.setId(IdUtils.longUUID());fileUploadLogInsert.setUploadId(uploadId);fileUploadLogInsert.setIdentifier(md5);fileUploadLogInsert.setChunkSize(chunkSize);fileUploadLogInsert.setObjectKey(objectKey);fileUploadLogInsert.setFileChunkNum(chunkTotal);
//            fileUploadLogInsert.setUploadChunkIndex(chunkNumber);fileUploadLogInsert.setCreateTime(DateUtils.getNowDate());fileUploadLogInsert.setCreateBy(userId.toString());fileUploadLogInsert.setTotalSize(totalSize);fileUploadLogMapper.insertFileUploadLog(fileUploadLogInsert);} catch (ObsException e) {log.error("OBS 上传分片失败", e);// 打印详细的OBS错误信息System.out.println("HTTP Code:" + e.getResponseCode());System.out.println("Error Code:" + e.getErrorCode());System.out.println("Error Message:" + e.getErrorMessage());System.out.println("Request ID:" + e.getErrorRequestId());System.out.println("Host ID:" + e.getErrorHostId());e.printStackTrace();}}else {// 文件已部分或完全上传的情况// 已上传的最后一个分片序号Integer uploadChunkIndex = fileUploadLog.getUploadChunkIndex();checkVo.setUploaded(false);for (int i= 1; i <= uploadChunkIndex; i++)  {// 将 uploadChunkIndex 之前的所有分片编号添加到 chunkIndexes 列表中chunkIndexes.add(i);}checkVo.setChunkList(chunkIndexes);// 表示所有分片都已上传完成if(uploadChunkIndex.longValue() == chunkTotal){checkVo.setUploaded(true);checkVo.setFileName(fileUploadLog.getFileUrl());}}return checkVo;}

分片上传接口

Controller

    /*** 上传分片*/@PostMapping("/uploadVideo")@ApiOperation(value = "断点续传-上传分片")public AjaxResult uploadChunk(@ModelAttribute  FileUploadRequest req) throws IOException {return fsUploadFileService.uploadChunk(req);}

Service

  AjaxResult uploadChunk(FileUploadRequest req);

ServiceImpl

@Transactional(rollbackFor = Exception.class)@Overridepublic  AjaxResult uploadChunk(FileUploadRequest req) {try {String md5 = req.getIdentifier();  // 当前上传文件的 MD5 值MultipartFile file = req.getFile(); //当前分片二进制Integer index = req.getChunkNumber();  // 当前分片的位置Integer chunkTotal = req.getTotalChunks();  // 分片总数Long totalSize = req.getTotalSize();Long currentChunkSize = req.getCurrentChunkSize();String uploadId = req.getUploadId();// 获取 bucketName 和 objectKeyLong userId = SecurityUtils.getUserId();FileUploadLog fileUploadLog = fileUploadLogMapper.selectFileUploadLogByFileMd5(md5);if (fileUploadLog == null) {return AjaxResult.error("上传记录不存在");}Long chunkSize = fileUploadLog.getChunkSize();String objectKey = fileUploadLog.getObjectKey();// 上传段UploadPartRequest uploadPartRequest = new UploadPartRequest();Long offset = (index - 1) * chunkSize;uploadPartRequest.setBucketName(bucketName);uploadPartRequest.setObjectKey(objectKey);uploadPartRequest.setUploadId(uploadId);uploadPartRequest.setPartNumber(index);uploadPartRequest.setInput(file.getInputStream());uploadPartRequest.setPartSize(currentChunkSize);uploadPartRequest.setOffset(offset);obsClient.uploadPart(uploadPartRequest);// 列举已上传的段,其中uploadId来自于initiateMultipartUploadListPartsRequest request = new ListPartsRequest(bucketName, objectKey);request.setUploadId(uploadId);ListPartsResult result = obsClient.listParts(request);List<Multipart> multipartList = result.getMultipartList();LinkedList<PartEtag> partEtags = new LinkedList();if (multipartList != null && multipartList.size() > 0){multipartList.stream().forEach(multipart -> {PartEtag partEtag = new PartEtag();partEtag.setEtag(multipart.getEtag());partEtag.setPartNumber(multipart.getPartNumber());partEtags.add(partEtag);});}FileUploadLog fileUploadLogUpdate = new FileUploadLog();if (partEtags.size() == chunkTotal.intValue()){fileUploadLogUpdate.setId(fileUploadLog.getId());fileUploadLogUpdate.setFileName(file.getName());
//                fileUploadLogUpdate.setUploadChunkNumber((fileUploadLog.getUploadChunkNumber() != null ? fileUploadLog.getUploadChunkNumber() : 0) + 1);
//                fileUploadLogUpdate.setUploadChunkIndex(index);fileUploadLogUpdate.setPartEtagsJson(partEtags.toString());fileUploadLogUpdate.setUpdateTime(DateUtils.getNowDate());fileUploadLogUpdate.setUpdateBy(userId.toString());CompleteMultipartUploadRequest completeRequest =new CompleteMultipartUploadRequest(bucketName, objectKey, uploadId, partEtags);CompleteMultipartUploadResult completeResult = obsClient.completeMultipartUpload(completeRequest);String objectUrl = completeResult.getObjectUrl();objectUrl = objectUrl.replace("ringpai-oa.obs.cn-north-4.myhuaweicloud.com:443", "oas.ringpai.com");fileUploadLogUpdate.setFileUrl(objectUrl);// 在所有操作成功后才更新数据库fileUploadLogMapper.updateFileUploadLog(fileUploadLogUpdate);return AjaxResult.success("文件上传成功", fileUploadLogUpdate.getFileUrl());}else{Map<String,Object> map = new HashMap();map.put("index",index);map.put("partEtags",partEtags);return AjaxResult.success("文件分片上传成功", map);}} catch (ObsException e) {log.error("OBS 上传分片失败", e);// 打印详细的OBS错误信息System.out.println("HTTP Code:" + e.getResponseCode());System.out.println("Error Code:" + e.getErrorCode());System.out.println("Error Message:" + e.getErrorMessage());System.out.println("Request ID:" + e.getErrorRequestId());System.out.println("Host ID:" + e.getErrorHostId());// 抛出运行时异常以回滚事务,防止数据库被更新throw new RuntimeException("OBS 上传分片失败", e);} catch (Exception e) {log.error("上传分片失败", e);// 抛出运行时异常以回滚事务,防止数据库被更新throw new RuntimeException("上传分片失败", e);}finally {}}

3. 使用到的前台对象

FileUploadRequest

package com.ruoyi.codecleanliness5s.domain.vo;import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
/*** 每一个上传块都会包含如下分块信息:* chunkNumber: 当前块的次序,第一个块是 1,注意不是从 0 开始的。* totalChunks: 文件被分成块的总数。* chunkSize: 分块大小,根据 totalSize 和这个值你就可以计算出总共的块数。注意最后一块的大小可能会比这个要大。* currentChunkSize: 当前块的大小,实际大小。* totalSize: 文件总大小。* identifier: 这个就是每个文件的唯一标示,md5码* filename: 文件名。* relativePath: 文件夹上传的时候文件的相对路径属性。* 一个分块可以被上传多次,当然这肯定不是标准行为,,这种重传也但是在实际上传过程中是可能发生这种事情的是本库的特性之一。* <p>* 根据响应码认为成功或失败的:* 200 文件上传完成* 201 文加快上传成功* 500 第一块上传失败,取消整个文件上传* 507 服务器出错自动重试该文件块上传*/
@Data
public class FileUploadRequest {/*** 主键ID*/private Long id;/*** 当前文件块,从1开始*/private Integer chunkNumber;/*** 分块大小*/private Long chunkSize;/*** 当前分块大小*/private Long currentChunkSize;/*** 总大小*/private Long totalSize;/*** 文件标识*/private String identifier;/*** 文件名*/private String filename;/*** 相对路径*/private String relativePath;/*** 总块数*/private Integer totalChunks;/*** 二进制文件*/private MultipartFile file;private String uploadId;
}

CheckResponse

package com.ruoyi.codecleanliness5s.domain.vo;import lombok.Data;import java.util.List;@Data
public class CheckResponse {// 上传状态private boolean uploaded;// 分片列表private List<Integer> chunkList;private String fileName;private String uploadId;
}

这篇关于springboot 对接华为云obs云存储的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1113037

相关文章

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2

Spring Cloud LoadBalancer 负载均衡详解

《SpringCloudLoadBalancer负载均衡详解》本文介绍了如何在SpringCloud中使用SpringCloudLoadBalancer实现客户端负载均衡,并详细讲解了轮询策略和... 目录1. 在 idea 上运行多个服务2. 问题引入3. 负载均衡4. Spring Cloud Load

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

在 Spring Boot 中使用 @Autowired和 @Bean注解的示例详解

《在SpringBoot中使用@Autowired和@Bean注解的示例详解》本文通过一个示例演示了如何在SpringBoot中使用@Autowired和@Bean注解进行依赖注入和Bean... 目录在 Spring Boot 中使用 @Autowired 和 @Bean 注解示例背景1. 定义 Stud

定价129元!支持双频 Wi-Fi 5的华为AX1路由器发布

《定价129元!支持双频Wi-Fi5的华为AX1路由器发布》华为上周推出了其最新的入门级Wi-Fi5路由器——华为路由AX1,建议零售价129元,这款路由器配置如何?详细请看下文介... 华为 Wi-Fi 5 路由 AX1 已正式开售,新品支持双频 1200 兆、配有四个千兆网口、提供可视化智能诊断功能,建

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为