mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-20 02:08:15 +08:00
add 新增分片上传
This commit is contained in:
parent
0131163368
commit
6cc2eab3d7
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件输入流
|
||||
*
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user