add 新增分片上传

This commit is contained in:
AprilWind 2024-04-24 10:57:12 +08:00
parent 0131163368
commit 6cc2eab3d7
6 changed files with 334 additions and 6 deletions

View File

@ -333,7 +333,7 @@ public class OssClient {
.key(key)
.build()
).join().uploadId();
return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).uploadId(uploadId).build();
return UploadResult.builder().filename(key).uploadId(uploadId).build();
} catch (Exception e) {
// 捕获异常并抛出自定义异常
throw new OssException("创建分片上传任务失败,请检查配置信息:[" + e.getMessage() + "]");
@ -479,6 +479,16 @@ public class OssClient {
return putPrivateUrl(getPath(properties.getPrefix(), suffix), md5Digest, second);
}
/**
* 创建分片上传任务
*
* @param suffix 对象键的后缀
* @return 包含上传后的文件信息
*/
public UploadResult initiateMultipart(String suffix){
return initiateMultipartUpload(getPath(properties.getPrefix(), suffix));
}
/**
* 获取文件输入流
*

View File

@ -3,20 +3,26 @@ package org.dromara.system.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.core.validate.QueryGroup;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.system.domain.bo.MultipartBo;
import org.dromara.system.domain.bo.SysOssBo;
import org.dromara.system.domain.vo.MultipartVo;
import org.dromara.system.domain.vo.SysOssUploadVo;
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.service.ISysOssService;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -105,4 +111,44 @@ public class SysOssController extends BaseController {
return toAjax(ossService.deleteWithValidByIds(List.of(ossIds), true));
}
/**
* 初始化分片上传任务
*
* @param originalName 文件原名
*/
@SaCheckPermission("system:oss:multipart")
@PostMapping(value = "/multipart/initiate")
public R<MultipartVo> initiateMultipart(@Size(min = 2, max = 255, message = "文件原名长度必须在2到255之间")
@NotBlank(message = "文件原名不能为空") String originalName) {
return R.ok(ossService.initiateMultipart(originalName));
}
/**
* 上传分段
*/
@SaCheckPermission("system:oss:multipart")
@PostMapping(value = "/multipart/upload")
public R<MultipartVo> uploadPart(@Validated(AddGroup.class) @RequestBody MultipartBo multipartBo) {
return R.ok(ossService.uploadPart(multipartBo));
}
/**
* 查询上传分段进度
*/
@SaCheckPermission("system:oss:multipart")
@PostMapping(value = "/multipart/list")
public R<MultipartVo> uploadPartList(@Validated(QueryGroup.class) @RequestBody MultipartBo multipartBo) {
return R.ok(ossService.uploadPartList(multipartBo));
}
/**
* 合并分段文件
*/
@SaCheckPermission("system:oss:multipart")
@Log(title = "OSS对象存储-分片", businessType = BusinessType.INSERT)
@PostMapping(value = "/multipart/complete")
public R<SysOssVo> completeMultipartUpload(@Validated(EditGroup.class) @RequestBody MultipartBo multipartBo) {
return R.ok(ossService.completeMultipartUpload(multipartBo));
}
}

View File

@ -0,0 +1,97 @@
package org.dromara.system.domain.bo;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.core.validate.QueryGroup;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* @author AprilWind
*/
@Data
public class MultipartBo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 文件名
*/
@NotBlank(message = "文件名不能为空", groups = {QueryGroup.class, AddGroup.class, EditGroup.class})
private String filename;
/**
* 用于分片上传任务的 Upload ID
* 在初始化分片上传时获取并在后续的分片上传和完成上传过程中使用
*/
@NotBlank(message = "上传任务ID不能为空", groups = {QueryGroup.class, AddGroup.class, EditGroup.class})
private String uploadId;
/**
* 分片编号从1开始递增
*/
@NotNull(message = "分片编号不能为空", groups = AddGroup.class)
@Size(min = 1, max = 10000, message = "分片编号必须介于1和10,000之间", groups = AddGroup.class)
private Integer partNumber;
/**
* 内容的 MD5 摘要
*/
@Size(max = 255, message = "内容的 MD5 摘要如果有的话不能超过255", groups = AddGroup.class)
private String md5Digest;
/**
* 最大返回的分片数默认为1000最大值1000
* 最多分片一万一次性返回会造成前端性能问题需要前端多次校验
*/
@NotNull(message = "最大返回的分片数不能为空", groups = QueryGroup.class)
private Integer maxParts;
/**
* 分片编号的标记用于分页查询默认为0表示从第一个分片开始查询
*/
@NotNull(message = "分片编号的标记不能为空", groups = QueryGroup.class)
private Integer partNumberMarker;
/**
* 原名
*/
@NotBlank(message = "原名不能为空", groups = EditGroup.class)
@Size(min = 2, max = 255, message = "文件原名长度必须在2到255之间", groups = EditGroup.class)
private String originalName;
/**
* 已上传列表最大长度一万
* 必须是唯一且按照递增顺序排列严格检查是否漏传
*/
@NotNull(message = "已上传列表不能为空")
@Size(min = 2, max = 10000, message = "已上传列表长度必须在2到10000之间", groups = EditGroup.class)
private List<PartUploadResult> partUploadList;
@Data
public static class PartUploadResult implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 分片编号从1开始递增
*/
@NotNull(message = "分片编号不能为空", groups = EditGroup.class)
@Size(min = 1, max = 10000, message = "分片编号必须介于1和10,000之间", groups = EditGroup.class)
private Integer partNumber;
/**
* 从上传部分的内容生成的实体标签
*/
@NotBlank(message = "实体标签不能为空", groups = EditGroup.class)
private String eTag;
}
}

View File

@ -0,0 +1,68 @@
package org.dromara.system.domain.vo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.dromara.system.domain.SysOss;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* @author AprilWind
*/
@Data
@AutoMapper(target = SysOss.class)
public class MultipartVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 文件名
*/
private String filename;
/**
* 用于分片上传任务的 Upload ID
* 在初始化分片上传时获取并在后续的分片上传和完成上传过程中使用
*/
private String uploadId;
/**
* 分片编号从1开始递增
*/
private Integer partNumber;
/**
* 用于私有预签名 URL
* 用于上传文件或分片的预签名 URL确保文件或分片在上传时具有私有访问权限
*/
private String privateUrl;
/**
* 已上传列表最大长度一千
* 必须是唯一且按照递增顺序排列严格检查是否漏传
*/
private List<PartUploadResult> partUploadList;
@Data
@AllArgsConstructor
public static class PartUploadResult implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 分片编号从1开始递增
*/
private Integer partNumber;
/**
* 从上传部分的内容生成的实体标签
*/
private String eTag;
}
}

View File

@ -1,10 +1,12 @@
package org.dromara.system.service;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.domain.bo.MultipartBo;
import org.dromara.system.domain.bo.SysOssBo;
import org.dromara.system.domain.vo.MultipartVo;
import org.dromara.system.domain.vo.SysOssVo;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
@ -77,4 +79,36 @@ public interface ISysOssService {
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 初始化分片上传任务
*
* @param originalName 文件原名
* @return 分片上传对象信息
*/
MultipartVo initiateMultipart(String originalName);
/**
* 上传文件的分段分片上传
*
* @param multipartBo SysOssMultipartBo 分段上传的参数对象
* @return SysOssMultipartVo 分片上传成功后的对象信息
*/
MultipartVo uploadPart(MultipartBo multipartBo);
/**
* 获取上传分段进度
*
* @param multipartBo 分片上传对象信息
* @return 分片上传对象信息
*/
MultipartVo uploadPartList(MultipartBo multipartBo);
/**
* 合并分段
*
* @param multipartBo 分片上传对象信息
* @return OSS对象存储视图对象
*/
SysOssVo completeMultipartUpload(MultipartBo multipartBo);
}

View File

@ -19,11 +19,14 @@ import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.oss.core.OssClient;
import org.dromara.common.oss.entity.PartUploadResult;
import org.dromara.common.oss.entity.UploadResult;
import org.dromara.common.oss.enumd.AccessPolicyType;
import org.dromara.common.oss.factory.OssFactory;
import org.dromara.system.domain.SysOss;
import org.dromara.system.domain.bo.MultipartBo;
import org.dromara.system.domain.bo.SysOssBo;
import org.dromara.system.domain.vo.MultipartVo;
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.mapper.SysOssMapper;
import org.dromara.system.service.ISysOssService;
@ -40,6 +43,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 文件上传 服务层实现
@ -237,6 +241,74 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 初始化分片上传任务
*
* @param originalName 文件原名
* @return 分片上传对象信息
*/
@Override
public MultipartVo initiateMultipart(String originalName) {
String suffix = StringUtils.substring(originalName, originalName.lastIndexOf("."), originalName.length());
OssClient storage = OssFactory.instance();
UploadResult uploadResult = storage.initiateMultipart(suffix);
return MapstructUtils.convert(uploadResult, MultipartVo.class);
}
/**
* 上传文件的分段分片上传
*
* @param multipartBo MultipartBo 分段上传的参数对象
* @return MultipartVo 分片上传成功后的对象信息
*/
@Override
public MultipartVo uploadPart(MultipartBo multipartBo) {
OssClient storage = OssFactory.instance();
String privateUrl = storage.uploadPartFutures(multipartBo.getFilename(), multipartBo.getUploadId(), multipartBo.getPartNumber(), multipartBo.getMd5Digest(), 10800);
MultipartVo multipartVo = MapstructUtils.convert(multipartBo, MultipartVo.class);
multipartVo.setPrivateUrl(privateUrl);
return multipartVo;
}
/**
* 获取上传分段进度
*
* @param multipartBo 分片上传对象信息
* @return 分片上传对象信息
*/
@Override
public MultipartVo uploadPartList(MultipartBo multipartBo) {
OssClient storage = OssFactory.instance();
List<PartUploadResult> listParts = storage.listParts(multipartBo.getFilename(), multipartBo.getUploadId(), multipartBo.getMaxParts(), multipartBo.getPartNumberMarker());
MultipartVo multipartVo = MapstructUtils.convert(multipartBo, MultipartVo.class);
multipartVo.setPartUploadList(listParts.stream()
.map(x -> new MultipartVo.PartUploadResult(x.getPartNumber(), x.getETag()))
.collect(Collectors.toList()));
return multipartVo;
}
/**
* 合并分段
*
* @param multipartBo 分片上传对象信息
* @return OSS对象存储视图对象
*/
@Override
public SysOssVo completeMultipartUpload(MultipartBo multipartBo) {
String originalName = multipartBo.getOriginalName();
String suffix = StringUtils.substring(originalName, originalName.lastIndexOf("."), originalName.length());
List<PartUploadResult> listParts = multipartBo.getPartUploadList().stream()
.map(x -> PartUploadResult.builder()
.partNumber(x.getPartNumber())
.eTag(x.getETag())
.build())
.collect(Collectors.toList());
OssClient storage = OssFactory.instance();
UploadResult uploadResult = storage.completeMultipartUpload(multipartBo.getFilename(), multipartBo.getUploadId(), listParts);
// 保存文件信息
return buildResultEntity(originalName, suffix, storage.getConfigKey(), uploadResult);
}
/**
* 桶类型为 private 的URL 修改为临时URL时长为120s
*
@ -251,4 +323,5 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
}
return oss;
}
}