初始化代码提交。
This commit is contained in:
parent
6bf1a31121
commit
7623f8ea96
|
@ -122,6 +122,7 @@ PS:原图尺寸小于缩略图压缩尺寸时,储存原图。
|
|||
|
||||
## 4.1 文件元数据信息表 | file_metadata_info
|
||||
|
||||
|
||||
| Name | Type | Length | Not Null | Virtual | Key | Comment |
|
||||
| ---------------- | ---------- | -------- | ---------- | --------- | ------ | ------------------------ |
|
||||
| id | bigint | 20 | True | False | True | 自增ID |
|
||||
|
@ -188,4 +189,3 @@ CREATE TABLE `file_metadata_info` (
|
|||
# 9 参考资料 | Reference
|
||||
|
||||
* [MinIO S3 APIs](minio-plus-doc/minio-s3-api.md)
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>minio-plus-application</artifactId>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>minio-plus-application-mysql</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-spring-boot-config</artifactId>
|
||||
</dependency>
|
||||
<!--Swagger工具包 knife4j -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.11.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,18 @@
|
|||
package org.liuxp.minioplus.application;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author contact@liuxp.me
|
||||
* @since 2024-05-22
|
||||
*/
|
||||
|
||||
@SpringBootApplication
|
||||
public class MinioPlusApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MinioPlusApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package org.liuxp.minioplus.application.context;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 返回值通用结构
|
||||
* @author contact@liuxp.me
|
||||
* @since 2024/05/22
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value = "Response")
|
||||
@ToString
|
||||
public class Response<T> {
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
@ApiModelProperty(value = "状态码")
|
||||
protected int code;
|
||||
|
||||
/**
|
||||
* 提示信息
|
||||
*/
|
||||
@ApiModelProperty(value = "操作成功")
|
||||
protected String message;
|
||||
/**
|
||||
* 返回给页面的数据内容,不同接口格式不同
|
||||
*/
|
||||
@ApiModelProperty(value = "响应业务参数", dataType = "T")
|
||||
protected T data;
|
||||
|
||||
public Response() {
|
||||
this.setCode(ResponseCodeEnum.SUCCESS.getCode());
|
||||
this.setMessage(ResponseCodeEnum.SUCCESS.getMessage());
|
||||
}
|
||||
|
||||
public static Response<Void> success() {
|
||||
Response<Void> response = new Response<>();
|
||||
response.setCode(ResponseCodeEnum.SUCCESS.getCode());
|
||||
response.setMessage(ResponseCodeEnum.SUCCESS.getMessage());
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> Response<T> success(T data) {
|
||||
Response<T> response = new Response<T>();
|
||||
response.setCode(ResponseCodeEnum.SUCCESS.getCode());
|
||||
response.setMessage(ResponseCodeEnum.SUCCESS.getMessage());
|
||||
response.setData(data);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> Response<T> error(Integer errorCode, String message) {
|
||||
Response<T> response = new Response<>();
|
||||
response.setCode(errorCode);
|
||||
response.setMessage(message);
|
||||
return response;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package org.liuxp.minioplus.application.context;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 返回值枚举类
|
||||
* @author contact@liuxp.me
|
||||
* @since 2024/05/22
|
||||
*/
|
||||
public enum ResponseCodeEnum {
|
||||
|
||||
/**
|
||||
* 调用成功
|
||||
*/
|
||||
SUCCESS(0, "调用成功"),
|
||||
/**
|
||||
* 调用失败
|
||||
*/
|
||||
FAIL(-1, "系统异常,请重试");
|
||||
|
||||
/**
|
||||
* 返回给前端的状态编码 0表示成功
|
||||
*/
|
||||
private Integer code;
|
||||
/**
|
||||
* 编码对应的解释
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* @param code
|
||||
* @param message
|
||||
*/
|
||||
ResponseCodeEnum(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public static ResponseCodeEnum getByCode(Integer code) {
|
||||
for (ResponseCodeEnum e : ResponseCodeEnum.values()) {
|
||||
if (Objects.equals(e.code, code)) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return ResponseCodeEnum.FAIL;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
package org.liuxp.minioplus.application.controller;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.liuxp.minioplus.application.context.Response;
|
||||
import org.liuxp.minioplus.config.MinioPlusProperties;
|
||||
import org.liuxp.minioplus.core.common.dto.FileCheckDTO;
|
||||
import org.liuxp.minioplus.core.common.dto.FileCompleteDTO;
|
||||
import org.liuxp.minioplus.core.common.vo.CompleteResultVo;
|
||||
import org.liuxp.minioplus.core.common.vo.FileCheckResultVo;
|
||||
import org.liuxp.minioplus.core.engine.StorageEngineService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 对象存储标准接口定义
|
||||
* 本类的方法是给前端使用的方法
|
||||
* @author contact@liuxp.me
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/storage")
|
||||
@Api(tags = "TOS对象存储")
|
||||
@Slf4j
|
||||
public class StorageController {
|
||||
|
||||
/**
|
||||
* 重定向
|
||||
*/
|
||||
private static final String REDIRECT_PREFIX = "redirect:";
|
||||
|
||||
private static final String FAILURE = "failure";
|
||||
|
||||
/**
|
||||
* 存储引擎Service接口定义
|
||||
*/
|
||||
@Resource
|
||||
private StorageEngineService storageEngineService;
|
||||
|
||||
/**
|
||||
* 配置文件
|
||||
*/
|
||||
@Resource
|
||||
MinioPlusProperties properties;
|
||||
|
||||
/**
|
||||
* 文件预检查
|
||||
* 文件上传前的预检查:秒传、分块上传和断点续传等特性均基于该方法实现
|
||||
* @param fileCheckDTO 文件预检查入参
|
||||
* @return 检查结果
|
||||
*/
|
||||
@ApiOperation(value = "文件预检查")
|
||||
@PostMapping("/upload/check")
|
||||
@ResponseBody
|
||||
public Response<FileCheckResultVo> check(@RequestBody @Validated FileCheckDTO fileCheckDTO) {
|
||||
|
||||
// 取得当前登录用户信息
|
||||
String userId = "mockUser";
|
||||
|
||||
FileCheckResultVo resultVo = storageEngineService.check(fileCheckDTO,userId);
|
||||
|
||||
if(resultVo!=null){
|
||||
for (FileCheckResultVo.Part part : resultVo.getPartList()) {
|
||||
part.setUrl(remakeUrl(part.getUrl()));
|
||||
}
|
||||
}
|
||||
|
||||
return Response.success(resultVo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传完成
|
||||
* @param fileKey 文件KEY
|
||||
* @return 是否成功
|
||||
*/
|
||||
@ApiOperation(value = "上传完成")
|
||||
@PostMapping("/upload/complete/{fileKey}")
|
||||
@ResponseBody
|
||||
public Response<Object> complete(@PathVariable("fileKey") String fileKey, @RequestBody FileCompleteDTO fileCompleteDTO) {
|
||||
|
||||
// 取得当前登录用户信息
|
||||
String userId = "mockUser";
|
||||
|
||||
// 打印调试日志
|
||||
log.debug("合并文件开始fileKey="+fileKey+",partMd5List="+fileCompleteDTO.getPartMd5List());
|
||||
CompleteResultVo completeResultVo = storageEngineService.complete(fileKey,fileCompleteDTO.getPartMd5List(),userId);
|
||||
|
||||
if(completeResultVo!=null){
|
||||
for (FileCheckResultVo.Part part : completeResultVo.getPartList()) {
|
||||
part.setUrl(remakeUrl(part.getUrl()));
|
||||
}
|
||||
}
|
||||
|
||||
return Response.success(completeResultVo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片上传
|
||||
* @param fileKey 文件KEY
|
||||
* @param request 文件流
|
||||
* @return 是否成功
|
||||
*/
|
||||
@ApiOperation(value = "图片上传")
|
||||
@PutMapping("/upload/image/{fileKey}")
|
||||
@ResponseBody
|
||||
public Response<Boolean> uploadImage(@PathVariable String fileKey, HttpServletRequest request) {
|
||||
Boolean isUpload = false;
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = request.getInputStream();
|
||||
isUpload = storageEngineService.uploadImage(fileKey, IoUtil.readBytes(inputStream));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}finally {
|
||||
IoUtil.close(inputStream);
|
||||
}
|
||||
|
||||
return Response.success(isUpload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件下载
|
||||
* @param fileKey 文件KEY
|
||||
* @return 文件下载地址
|
||||
*/
|
||||
@ApiOperation(value = "文件下载")
|
||||
@GetMapping("/download/{fileKey}")
|
||||
public String download(@PathVariable String fileKey) {
|
||||
|
||||
// 取得当前登录用户信息
|
||||
String userId = "mockUser";
|
||||
// 取得文件读取路径
|
||||
String url = storageEngineService.download(fileKey, userId);
|
||||
// 不存在时返回404 返回重定向地址
|
||||
return REDIRECT_PREFIX + remakeUrl(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件删除
|
||||
* @param fileKey 文件KEY
|
||||
* @return 文件下载地址
|
||||
*/
|
||||
@ApiOperation(value = "文件删除")
|
||||
@PostMapping("/remove/{fileKey}")
|
||||
@ResponseBody
|
||||
public Response<Boolean> remove(@PathVariable String fileKey) {
|
||||
|
||||
// 取得当前登录用户信息
|
||||
String userId = "mockUser";
|
||||
// 取得文件读取路径
|
||||
Boolean remove = storageEngineService.remove(fileKey, userId);
|
||||
// 返回重定向地址
|
||||
return Response.success(remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片原图预览
|
||||
* @param fileKey 文件KEY
|
||||
* @return 原图地址
|
||||
*/
|
||||
@ApiOperation(value = "图片预览 - 原图")
|
||||
@GetMapping("/preview/original/{fileKey}")
|
||||
public String previewOriginal(@PathVariable String fileKey) {
|
||||
|
||||
// 取得当前登录用户信息
|
||||
String userId = "mockUser";
|
||||
// 取得文件读取路径
|
||||
String url = storageEngineService.image(fileKey, userId);
|
||||
// 返回重定向地址
|
||||
return REDIRECT_PREFIX + remakeUrl(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片缩略图预览
|
||||
* @param fileKey 文件KEY
|
||||
* @return 缩略图地址
|
||||
*/
|
||||
@ApiOperation(value = "图片预览 - 缩略图")
|
||||
@GetMapping("/preview/medium/{fileKey}")
|
||||
public String previewMedium(@PathVariable String fileKey) {
|
||||
|
||||
// 取得当前登录用户信息
|
||||
String userId = "mockUser";
|
||||
// 取得文件读取路径
|
||||
String url = storageEngineService.preview(fileKey, userId);
|
||||
// 返回重定向地址
|
||||
return REDIRECT_PREFIX + remakeUrl(url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 重写文件地址
|
||||
* @param url 文件地址
|
||||
* @return 重写后的文件地址
|
||||
*/
|
||||
private String remakeUrl(String url){
|
||||
|
||||
if(StrUtil.isNotBlank(properties.getBrowserUrl())&& !url.contains(FAILURE)){
|
||||
return url.replace(properties.getBackend(), properties.getBrowserUrl());
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package org.liuxp.minioplus.application.dao;
|
||||
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.liuxp.minioplus.application.entity.FileMetadataInfoEntity;
|
||||
import org.liuxp.minioplus.application.mapper.FileMetadataInfoMapper;
|
||||
import org.liuxp.minioplus.core.common.dto.FileMetadataInfoDTO;
|
||||
import org.liuxp.minioplus.core.common.dto.FileMetadataInfoSaveDTO;
|
||||
import org.liuxp.minioplus.core.common.dto.FileMetadataInfoUpdateDTO;
|
||||
import org.liuxp.minioplus.core.common.vo.FileMetadataInfoVo;
|
||||
import org.liuxp.minioplus.core.repository.MetadataRepository;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件元数据接口实现类
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2024/05/22
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class MetadataRepositoryImpl extends ServiceImpl<FileMetadataInfoMapper, FileMetadataInfoEntity> implements MetadataRepository {
|
||||
|
||||
@Override
|
||||
public List<FileMetadataInfoVo> list(FileMetadataInfoDTO searchDTO) {
|
||||
|
||||
// 组装查询参数
|
||||
QueryWrapper<FileMetadataInfoEntity> queryWrapper = buildParams(searchDTO);
|
||||
|
||||
List<FileMetadataInfoEntity> fileMetadataInfoEntityList = super.list(queryWrapper);
|
||||
|
||||
List<FileMetadataInfoVo> fileMetadataInfoVoList = new ArrayList<>();
|
||||
|
||||
for (FileMetadataInfoEntity fileMetadataInfoEntity : fileMetadataInfoEntityList) {
|
||||
FileMetadataInfoVo fileMetadataInfoVo = new FileMetadataInfoVo();
|
||||
BeanUtils.copyProperties(fileMetadataInfoEntity, fileMetadataInfoVo);
|
||||
fileMetadataInfoVoList.add(fileMetadataInfoVo);
|
||||
}
|
||||
|
||||
return fileMetadataInfoVoList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileMetadataInfoVo one(FileMetadataInfoDTO searchDTO) {
|
||||
|
||||
// 组装查询参数
|
||||
QueryWrapper<FileMetadataInfoEntity> queryWrapper = buildParams(searchDTO);
|
||||
queryWrapper.last("limit 1");
|
||||
|
||||
FileMetadataInfoEntity fileMetadataInfoEntity = super.getOne(queryWrapper);
|
||||
|
||||
FileMetadataInfoVo fileMetadataInfoVo = new FileMetadataInfoVo();
|
||||
|
||||
if(null!=fileMetadataInfoEntity){
|
||||
BeanUtils.copyProperties(fileMetadataInfoEntity, fileMetadataInfoVo);
|
||||
}
|
||||
|
||||
return fileMetadataInfoVo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileMetadataInfoVo save(FileMetadataInfoSaveDTO saveDTO) {
|
||||
|
||||
FileMetadataInfoEntity fileMetadataInfoEntity = new FileMetadataInfoEntity();
|
||||
BeanUtils.copyProperties(saveDTO, fileMetadataInfoEntity);
|
||||
fileMetadataInfoEntity.setCreateTime(new Date());
|
||||
fileMetadataInfoEntity.setUpdateTime(new Date());
|
||||
|
||||
boolean result = super.save(fileMetadataInfoEntity);
|
||||
|
||||
FileMetadataInfoVo fileMetadataInfoVo = new FileMetadataInfoVo();
|
||||
if(result){
|
||||
BeanUtils.copyProperties(fileMetadataInfoEntity, fileMetadataInfoVo);
|
||||
}
|
||||
|
||||
return fileMetadataInfoVo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileMetadataInfoVo update(FileMetadataInfoUpdateDTO updateDTO) {
|
||||
|
||||
FileMetadataInfoEntity fileMetadataInfoEntity = new FileMetadataInfoEntity();
|
||||
BeanUtils.copyProperties(updateDTO, fileMetadataInfoEntity);
|
||||
fileMetadataInfoEntity.setUpdateTime(new Date());
|
||||
boolean result = super.updateById(fileMetadataInfoEntity);
|
||||
|
||||
FileMetadataInfoVo fileMetadataInfoVo = new FileMetadataInfoVo();
|
||||
if(result){
|
||||
BeanUtils.copyProperties(fileMetadataInfoEntity, fileMetadataInfoVo);
|
||||
}
|
||||
|
||||
return fileMetadataInfoVo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean remove(Long id) {
|
||||
return super.removeById(id);
|
||||
}
|
||||
|
||||
private QueryWrapper<FileMetadataInfoEntity> buildParams(FileMetadataInfoDTO searchDTO){
|
||||
// 组装查询参数
|
||||
QueryWrapper<FileMetadataInfoEntity> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq(null!=searchDTO.getId(),"id",searchDTO.getId());
|
||||
queryWrapper.eq(CharSequenceUtil.isNotBlank(searchDTO.getFileKey()),"file_key",searchDTO.getFileKey());
|
||||
queryWrapper.eq(CharSequenceUtil.isNotBlank(searchDTO.getFileMd5()),"file_md5",searchDTO.getFileMd5());
|
||||
queryWrapper.eq(CharSequenceUtil.isNotBlank(searchDTO.getStorageBucket()),"bucket",searchDTO.getStorageBucket());
|
||||
queryWrapper.eq(null!=searchDTO.getIsPrivate(),"is_private",searchDTO.getIsPrivate());
|
||||
queryWrapper.eq(null!=searchDTO.getIsPart(),"is_part",searchDTO.getIsPart());
|
||||
queryWrapper.eq(CharSequenceUtil.isNotBlank(searchDTO.getCreateUser()),"create_user",searchDTO.getCreateUser());
|
||||
|
||||
return queryWrapper;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package org.liuxp.minioplus.application.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 文件元数据信息表Entity
|
||||
* @author contact@liuxp.me
|
||||
* @since 2024-05-22
|
||||
**/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@TableName(value = "file_metadata_info")
|
||||
public class FileMetadataInfoEntity {
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
@ApiModelProperty("主键")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("文件KEY")
|
||||
@TableField(value = "file_key")
|
||||
private String fileKey;
|
||||
|
||||
@ApiModelProperty("文件md5")
|
||||
@TableField(value = "file_md5")
|
||||
private String fileMd5;
|
||||
|
||||
@ApiModelProperty("文件名")
|
||||
@TableField(value = "file_name")
|
||||
private String fileName;
|
||||
|
||||
@ApiModelProperty("MIME类型")
|
||||
@TableField(value = "file_mime_type")
|
||||
private String fileMimeType;
|
||||
|
||||
@ApiModelProperty("文件后缀")
|
||||
@TableField(value = "file_suffix")
|
||||
private String fileSuffix;
|
||||
|
||||
@ApiModelProperty("文件长度")
|
||||
@TableField(value = "file_size")
|
||||
private Long fileSize;
|
||||
|
||||
@ApiModelProperty("预览图 0:无 1:有")
|
||||
@TableField(value = "is_preview")
|
||||
private Boolean isPreview;
|
||||
|
||||
@ApiModelProperty("是否私有 0:否 1:是")
|
||||
@TableField(value = "is_private")
|
||||
private Boolean isPrivate;
|
||||
|
||||
@ApiModelProperty("存储桶")
|
||||
@TableField(value = "bucket")
|
||||
private String storageBucket;
|
||||
|
||||
@ApiModelProperty("存储桶路径")
|
||||
@TableField(value = "bucket_path")
|
||||
private String storagePath;
|
||||
|
||||
@ApiModelProperty("上传任务id,用于合并切片")
|
||||
@TableField(value = "upload_id")
|
||||
private String uploadTaskId;
|
||||
|
||||
@ApiModelProperty("状态 0:未完成 1:已完成")
|
||||
@TableField(value = "is_finished")
|
||||
private Boolean isFinished;
|
||||
|
||||
@ApiModelProperty("是否分块 0:否 1:是")
|
||||
@TableField(value = "is_part")
|
||||
private Boolean isPart;
|
||||
|
||||
@ApiModelProperty("分块数量")
|
||||
@TableField(value = "part_number")
|
||||
private Integer partNumber;
|
||||
|
||||
@ApiModelProperty("创建人")
|
||||
@TableField("create_user")
|
||||
private String createUser;
|
||||
|
||||
@ApiModelProperty("创建时间")
|
||||
@TableField("create_time")
|
||||
private Date createTime;
|
||||
|
||||
@ApiModelProperty("修改人")
|
||||
@TableField("update_user")
|
||||
private String updateUser;
|
||||
|
||||
@ApiModelProperty("修改时间")
|
||||
@TableField("update_time")
|
||||
private Date updateTime;
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package org.liuxp.minioplus.application.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.liuxp.minioplus.application.entity.FileMetadataInfoEntity;
|
||||
|
||||
/**
|
||||
* 文件元数据信息表Mapper
|
||||
* @author contact@liuxp.me
|
||||
* @since 2024/05/22
|
||||
*/
|
||||
@Mapper
|
||||
public interface FileMetadataInfoMapper extends BaseMapper<FileMetadataInfoEntity> {
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
spring:
|
||||
#应用唯一标识
|
||||
application:
|
||||
name: MinIOPlus
|
||||
#配置文件
|
||||
profiles:
|
||||
active: dev
|
||||
# 解决Feign接口名称重复报错问题
|
||||
main:
|
||||
allow-bean-definition-overriding: true
|
||||
#jdk proxy
|
||||
aop:
|
||||
proxy-target-class: false
|
||||
auto: false
|
||||
jackson:
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
time-zone: GMT+8
|
||||
default-property-inclusion: non_null # 全局jackson配置
|
||||
mvc:
|
||||
pathmatch:
|
||||
# Springfox使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatcher所以需要配置此参数
|
||||
matching-strategy: ant_path_matcher
|
||||
#数据源配置
|
||||
datasource:
|
||||
#驱动名称
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
#链接地址
|
||||
url: jdbc:mysql://localhost:3306/minio_plus?characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai
|
||||
#用户名
|
||||
username: root
|
||||
#密码
|
||||
password: root
|
||||
##################################################################
|
||||
# 服务器
|
||||
##################################################################
|
||||
server:
|
||||
port: 9010
|
||||
##################################################################
|
||||
### MinIO Plus Config
|
||||
##################################################################
|
||||
minioplus:
|
||||
# 存储引擎地址,如配置为local则不用配置
|
||||
backend: http://localhost:9000
|
||||
# 浏览器访问地址,文件、图片上传下载访问地址代理,如果minio被nginx代理,需要配置这个参数为代理后的前端访问地址
|
||||
browser-url: http://localhost
|
||||
# 授权key
|
||||
key: minioadmin
|
||||
# 密钥
|
||||
secret: minioadmin
|
||||
# 上传预签名URL有效期,单位为分钟,可选参数,默认值为60分钟
|
||||
upload-expiry: 120
|
||||
# 下载和预览预签名URL有效期,单位为分钟,可选参数,默认值为60分钟
|
||||
download-expiry: 20
|
||||
# 可选参数,分块配置
|
||||
part:
|
||||
# 可选参数,是否开启分块能力。默认为true
|
||||
enable: true
|
||||
# 可选参数,分块大小,配置单位为byte,默认为5242880
|
||||
size: 5242880
|
||||
# 可选参数,分块上传时建议并发数,默认为3
|
||||
iis: 2
|
||||
# 可选参数,缩略图配置
|
||||
thumbnail:
|
||||
# 可选参数,是否开启缩略图。默认为true
|
||||
enable: true
|
||||
# 可选参数,缩略图尺寸,默认为300
|
||||
size: 300
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>minio-plus-parent</artifactId>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>minio-plus-application</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>minio-plus-application-mysql</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>minio-plus-parent</artifactId>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>minio-plus-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
<!-- minio -->
|
||||
<dependency>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-spring-boot-config</artifactId>
|
||||
</dependency>
|
||||
<!--Swagger工具包 knife4j -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-annotations</artifactId>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/io.swagger/swagger-annotations -->
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
</dependency>
|
||||
<!-- google图片压缩 -->
|
||||
<dependency>
|
||||
<groupId>net.coobird</groupId>
|
||||
<artifactId>thumbnailator</artifactId>
|
||||
<version>${thumbnailator.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,56 @@
|
|||
package org.liuxp.minioplus.core.common.bo;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建上传url
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @date 2023/6/29
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class CreateUploadUrlReqBO {
|
||||
/**
|
||||
* 文件md5
|
||||
*/
|
||||
private String fileMd5;
|
||||
/**
|
||||
* 文件名(含扩展名)
|
||||
*/
|
||||
private String fullFileName;
|
||||
/**
|
||||
* "文件长度"
|
||||
*/
|
||||
private Long fileSize;
|
||||
/**
|
||||
* 是否断点续传 0:否 1:是,默认非断点续传
|
||||
*/
|
||||
private Boolean isSequel = Boolean.FALSE;
|
||||
/**********************************************************以下参数是断点续传必传的数据-start**************************************************/
|
||||
/**
|
||||
* 丢失的块号
|
||||
*/
|
||||
private List<Integer> missPartNum;
|
||||
/**
|
||||
* 文件id
|
||||
*/
|
||||
private String fileKey;
|
||||
/**
|
||||
* 需要补传的任务号
|
||||
*/
|
||||
private String uploadId;
|
||||
/**
|
||||
* 存储桶
|
||||
*/
|
||||
private String storageBucket;
|
||||
/**
|
||||
* 存储路径
|
||||
*/
|
||||
private String storagePath;
|
||||
/**********************************************************以上参数是断点续传必传的数据-end**************************************************/
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package org.liuxp.minioplus.core.common.bo;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.liuxp.minioplus.core.common.vo.FileCheckResultVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建上传链接请求参数
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @date 2023/6/29
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class CreateUploadUrlRespBO {
|
||||
|
||||
/**
|
||||
* 文件id-必填
|
||||
*/
|
||||
private String fileKey;
|
||||
/**
|
||||
* 分块数量-可选,分片后必须重新赋值
|
||||
* 默认1
|
||||
*/
|
||||
private Integer partCount = 1;
|
||||
/**
|
||||
* 切片上传任务id
|
||||
*/
|
||||
private String uploadTaskId;
|
||||
/**
|
||||
* minio存储信息-可选,使用minio存储引擎必填
|
||||
*/
|
||||
private MinioStorageBO minioStorage;
|
||||
/**
|
||||
* 分片信息-必填
|
||||
*/
|
||||
List<FileCheckResultVo.Part> parts;
|
||||
|
||||
/**
|
||||
* minio存储信息
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @date 2023/06/29
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
@ToString
|
||||
public static class MinioStorageBO {
|
||||
/**
|
||||
* 桶名字
|
||||
*/
|
||||
private String bucketName;
|
||||
/**
|
||||
* 文件存储路径
|
||||
*/
|
||||
private String storagePath;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package org.liuxp.minioplus.core.common.copy;
|
||||
|
||||
import io.minio.messages.ListPartsResult;
|
||||
import io.minio.messages.Part;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ListPartsResultCopy extends ListPartsResult {
|
||||
|
||||
public List<Part> partList() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package org.liuxp.minioplus.core.common.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 文件预检查DTO
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/6/26
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@ApiModel("文件预检查入参DTO")
|
||||
public class FileCheckDTO {
|
||||
|
||||
@ApiModelProperty(value = "文件md5", required = true)
|
||||
private String fileMd5;
|
||||
|
||||
@ApiModelProperty(value = "文件名(含扩展名)", required = true)
|
||||
private String fullFileName;
|
||||
|
||||
@ApiModelProperty(value = "文件长度", required = true)
|
||||
private Long fileSize;
|
||||
|
||||
@ApiModelProperty("是否私有 false:否 true:是")
|
||||
private Boolean isPrivate;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package org.liuxp.minioplus.core.common.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件完成入参DTO
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/8/26
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@ApiModel("文件完成入参DTO")
|
||||
public class FileCompleteDTO {
|
||||
|
||||
@ApiModelProperty(value = "文件md5", required = true)
|
||||
private List<String> partMd5List;
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package org.liuxp.minioplus.core.common.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 文件元数据查询实体类
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/6/25
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class FileMetadataInfoDTO {
|
||||
|
||||
@ApiModelProperty("主键")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("文件KEY")
|
||||
private String fileKey;
|
||||
|
||||
@ApiModelProperty("文件md5")
|
||||
private String fileMd5;
|
||||
|
||||
@ApiModelProperty("存储引擎")
|
||||
private String storageEngine;
|
||||
|
||||
@ApiModelProperty("存储桶")
|
||||
private String storageBucket;
|
||||
|
||||
@ApiModelProperty("是否私有 false:否 true:是")
|
||||
private Boolean isPrivate;
|
||||
|
||||
@ApiModelProperty("状态 false:未完成 true:已完成")
|
||||
private Boolean isFinished;
|
||||
|
||||
@ApiModelProperty("是否分块 false:否 true:是")
|
||||
private Boolean isPart;
|
||||
|
||||
@ApiModelProperty("创建人")
|
||||
private String createUser;
|
||||
|
||||
@ApiModelProperty(value = "当前页,最小为1")
|
||||
private int current;
|
||||
|
||||
@ApiModelProperty(value = "每页显示记录数")
|
||||
private int size;
|
||||
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package org.liuxp.minioplus.core.common.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 文件元数据信息保存入参
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023-06-26
|
||||
**/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@ApiModel("文件元数据信息保存入参")
|
||||
public class FileMetadataInfoSaveDTO {
|
||||
/**
|
||||
* 文件KEY
|
||||
*/
|
||||
@ApiModelProperty("文件KEY")
|
||||
private String fileKey;
|
||||
/**
|
||||
* 文件md5
|
||||
*/
|
||||
@ApiModelProperty("文件md5")
|
||||
private String fileMd5;
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
@ApiModelProperty("文件名")
|
||||
private String fileName;
|
||||
/**
|
||||
* MIME类型
|
||||
*/
|
||||
@ApiModelProperty("MIME类型")
|
||||
private String fileMimeType;
|
||||
/**
|
||||
* 文件后缀
|
||||
*/
|
||||
@ApiModelProperty("文件后缀")
|
||||
private String fileSuffix;
|
||||
/**
|
||||
* 文件长度
|
||||
*/
|
||||
@ApiModelProperty("文件长度")
|
||||
private Long fileSize;
|
||||
|
||||
/**
|
||||
* 存储桶
|
||||
*/
|
||||
@ApiModelProperty("存储桶")
|
||||
private String storageBucket;
|
||||
/**
|
||||
* 存储路径
|
||||
*/
|
||||
@ApiModelProperty("存储路径")
|
||||
private String storagePath;
|
||||
|
||||
/**
|
||||
* 上传任务id,用于合并切片
|
||||
*/
|
||||
@ApiModelProperty("上传任务id,用于合并切片")
|
||||
private String uploadTaskId;
|
||||
/**
|
||||
* 状态 0:未完成 1:已完成
|
||||
*/
|
||||
@ApiModelProperty("状态 false:未完成 true:已完成")
|
||||
private Boolean isFinished;
|
||||
/**
|
||||
* 是否分块 0:否 1:是
|
||||
*/
|
||||
@ApiModelProperty("是否分块 false:否 true:是")
|
||||
private Boolean isPart;
|
||||
/**
|
||||
* 分块数量
|
||||
*/
|
||||
@ApiModelProperty("分块数量")
|
||||
private Integer partNumber;
|
||||
/**
|
||||
* 预览图 0:无 1:有
|
||||
*/
|
||||
@ApiModelProperty("预览图 false:无 true:有")
|
||||
private Boolean isPreview;
|
||||
/**
|
||||
* 是否私有 0:否 1:是
|
||||
*/
|
||||
@ApiModelProperty("是否私有 false:否 true:是")
|
||||
private Boolean isPrivate;
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@ApiModelProperty("创建人")
|
||||
private String createUser;
|
||||
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
@ApiModelProperty("修改人")
|
||||
private String updateUser;
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package org.liuxp.minioplus.core.common.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 文件元数据信息修改入参
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023-06-26
|
||||
**/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@ApiModel("文件元数据信息修改入参")
|
||||
public class FileMetadataInfoUpdateDTO {
|
||||
|
||||
@ApiModelProperty("主键")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("文件KEY")
|
||||
private String fileKey;
|
||||
|
||||
@ApiModelProperty("文件md5")
|
||||
private String fileMd5;
|
||||
|
||||
@ApiModelProperty("文件名")
|
||||
private String fileName;
|
||||
|
||||
@ApiModelProperty("MIME类型")
|
||||
private String fileMimeType;
|
||||
|
||||
@ApiModelProperty("文件后缀")
|
||||
private String fileSuffix;
|
||||
|
||||
@ApiModelProperty("文件长度")
|
||||
private Long fileSize;
|
||||
|
||||
@ApiModelProperty("业务领域类型名称")
|
||||
private String fileDomainName;
|
||||
|
||||
@ApiModelProperty("存储引擎")
|
||||
private String storageEngine;
|
||||
|
||||
@ApiModelProperty("存储桶")
|
||||
private String storageBucket;
|
||||
|
||||
@ApiModelProperty("存储路径")
|
||||
private String storagePath;
|
||||
|
||||
@ApiModelProperty("上传任务id,用于合并切片")
|
||||
private String uploadTaskId;
|
||||
|
||||
@ApiModelProperty("状态 false:未完成 true:已完成")
|
||||
private Boolean isFinished;
|
||||
|
||||
@ApiModelProperty("是否分块 false:否 true:是")
|
||||
private Boolean isPart;
|
||||
|
||||
@ApiModelProperty("分块数量")
|
||||
private Integer partNumber;
|
||||
|
||||
@ApiModelProperty("预览图 false:无 true:有")
|
||||
private Boolean isPreview;
|
||||
|
||||
@ApiModelProperty("是否私有 false:否 true:是")
|
||||
private Boolean isPrivate;
|
||||
|
||||
@ApiModelProperty("修改人")
|
||||
private String updateUser;
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package org.liuxp.minioplus.core.common.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 文件保存DTO
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023-06-26
|
||||
**/
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
@ApiModel("文件保存入参DTO")
|
||||
public class FileSaveDTO {
|
||||
|
||||
@ApiModelProperty(value = "文件名(含扩展名)",required = true)
|
||||
private String fullFileName;
|
||||
|
||||
@ApiModelProperty("是否私有 false:否 true:是")
|
||||
private Boolean isPrivate;
|
||||
|
||||
@ApiModelProperty("创建人")
|
||||
private String createUser;
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package org.liuxp.minioplus.core.common.dto.minio;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.minio.messages.Part;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 创建分片上传需要的参数
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @date 2023/06/28
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class MultipartUploadCreateDTO {
|
||||
/**
|
||||
* 桶名字
|
||||
*/
|
||||
private String bucketName;
|
||||
/**
|
||||
* 区名字
|
||||
*/
|
||||
private String region;
|
||||
/**
|
||||
* 对象名字
|
||||
*/
|
||||
private String objectName;
|
||||
/**
|
||||
* 请求头
|
||||
*/
|
||||
private Multimap<String, String> headers;
|
||||
/**
|
||||
* 查询参数
|
||||
*/
|
||||
private Multimap<String, String> extraQueryParams;
|
||||
/**
|
||||
* minio的id
|
||||
*/
|
||||
private String uploadId;
|
||||
/**
|
||||
* 最大块数量
|
||||
*/
|
||||
private Integer maxParts;
|
||||
/**
|
||||
* 块信息
|
||||
*/
|
||||
private Part[] parts;
|
||||
/**
|
||||
* 块编号
|
||||
*/
|
||||
private Integer partNumberMarker;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.liuxp.minioplus.core.common.enums;
|
||||
|
||||
/**
|
||||
* 返回给前端的code编码对应内容的枚举
|
||||
*/
|
||||
public enum ResponseCodeEnum {
|
||||
|
||||
/**
|
||||
* 调用成功
|
||||
*/
|
||||
SUCCESS(0, "调用成功"),
|
||||
/**
|
||||
* 调用失败
|
||||
*/
|
||||
FAIL(-1, "调用失败");
|
||||
|
||||
/**
|
||||
* 返回给前端的状态编码 0表示成功
|
||||
*/
|
||||
private final Integer code;
|
||||
/**
|
||||
* 编码对应的解释
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* @param code
|
||||
* @param message
|
||||
*/
|
||||
ResponseCodeEnum(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package org.liuxp.minioplus.core.common.enums;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 桶策略枚举类
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/06/26
|
||||
*/
|
||||
@Getter
|
||||
public enum StorageBucketEnums {
|
||||
|
||||
/**
|
||||
* 桶策略枚举类
|
||||
* 文档(document):txt、rtf、ofd、doc、docx、xls、xlsx、ppt、pptx、pdf
|
||||
* 压缩包(package):zip、rar、7z、tar、wim、gz、bz2
|
||||
* 音频( audio ) :mp3、wav、flac、acc、ogg、aiff、m4a、wma、midi
|
||||
* 视频( video ) :mp4、avi、mov、wmv、flv、mkv、mpeg、mpg 、rmvb
|
||||
* 图片 – 原始( image ) :jpeg、jpg、png、bmp、webp、gif
|
||||
* 图片 – 缩略图( image-preview ) :按照宽度 300 像素压缩
|
||||
* 其他( other ) :未在上述格式中的文件
|
||||
* 其他规则:文件在桶中存储时,按照 /年/月 划分路径
|
||||
* 用以规避Linux ext3文件系统下单个目录最多创建32000个目录的问题,参考了阿里云的处理办法
|
||||
*/
|
||||
DOCUMENT("document", "文档文件桶",new String[]{"txt", "rtf", "ofd", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}),
|
||||
PACKAGE("package","压缩文件桶",new String[]{"zip", "rar", "7z", "tar", "wim", "gz", "bz2"}),
|
||||
AUDIO("audio", "音频文件桶",new String[]{"mp3", "wav", "flac", "acc", "ogg", "aiff", "m4a", "wma", "midi"}),
|
||||
VIDEO("video", "视频文件桶",new String[]{"mp4", "avi", "mov", "wmv", "flv", "mkv", "mpeg", "mpg", "rmvb"}),
|
||||
IMAGE("image", "图片文件桶",new String[]{"jpeg", "jpg", "png", "bmp", "webp", "gif"}),
|
||||
IMAGE_PREVIEW("image-preview", "缩略图文件桶",new String[]{"jpeg_large", "jpg_large", "png_large", "bmp_large", "webp_large", "gif_large"}),
|
||||
OTHER("other", "其他文件桶",new String[]{"*"});
|
||||
|
||||
private final String code;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String[] types;
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*
|
||||
* @param code 编码
|
||||
* @param name 名称
|
||||
*/
|
||||
StorageBucketEnums(String code, String name,String[] types) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编码取得枚举
|
||||
*
|
||||
* @param code 编码
|
||||
* @return 枚举
|
||||
*/
|
||||
public static StorageBucketEnums getByCode(String code) {
|
||||
for (StorageBucketEnums fileDomain : StorageBucketEnums.values()) {
|
||||
if (code.equals(fileDomain.getCode())) {
|
||||
return fileDomain;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编码取得名称
|
||||
*
|
||||
* @param code 编码
|
||||
* @return 名称
|
||||
*/
|
||||
public static String getNameByCode(String code) {
|
||||
for (StorageBucketEnums fileDomain : StorageBucketEnums.values()) {
|
||||
if (code.equals(fileDomain.getCode())) {
|
||||
return fileDomain.getName();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据扩展名取得桶编码
|
||||
* @param suffix 扩展名
|
||||
* @return 桶编码
|
||||
*/
|
||||
public static String getBucketByFileSuffix(String suffix) {
|
||||
|
||||
return Arrays.stream(StorageBucketEnums.values())
|
||||
.filter(item -> ArrayUtil.contains(item.types, suffix))
|
||||
.findFirst()
|
||||
.orElse(StorageBucketEnums.OTHER)
|
||||
.code;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package org.liuxp.minioplus.core.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 存储引擎枚举类
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/06/26
|
||||
*/
|
||||
@Getter
|
||||
public enum StorageEngineEnums {
|
||||
|
||||
/**
|
||||
* 存储引擎枚举
|
||||
*/
|
||||
MINIO("minio","MinIO存储引擎"),
|
||||
LOCAL("local","本地文件存储"),
|
||||
TIS("tis","TIS存储引擎");
|
||||
|
||||
private final String code;
|
||||
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
* @param code 编码
|
||||
* @param name 名称
|
||||
*/
|
||||
StorageEngineEnums(String code, String name) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编码取得枚举
|
||||
* @param code 编码
|
||||
* @return 枚举
|
||||
*/
|
||||
public static StorageEngineEnums getByCode(String code) {
|
||||
for (StorageEngineEnums fileDomain : StorageEngineEnums.values()) {
|
||||
if (code.equals(fileDomain.getCode())) {
|
||||
return fileDomain;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据编码取得名称
|
||||
* @param code 编码
|
||||
* @return 名称
|
||||
*/
|
||||
public static String getNameByCode(String code) {
|
||||
for (StorageEngineEnums fileDomain : StorageEngineEnums.values()) {
|
||||
if (code.equals(fileDomain.getCode())) {
|
||||
return fileDomain.getName();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package org.liuxp.minioplus.core.common.exception;
|
||||
|
||||
/**
|
||||
* MinioPlus专用异常定义
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/08/14
|
||||
*/
|
||||
public class MinioPlusBusinessException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 772046747932011086L;
|
||||
|
||||
private int errorCode;
|
||||
|
||||
private String errorMessage;
|
||||
|
||||
public int getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public void setErrorCode(int errorCode) {
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public MinioPlusBusinessException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public MinioPlusBusinessException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public MinioPlusBusinessException(int errorCode, String errorMessage) {
|
||||
this.errorCode = errorCode;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,601 @@
|
|||
package org.liuxp.minioplus.core.common.utils;
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>后缀名与ContentType对照表</h2>
|
||||
* <p>该工具类中保存了常见资源后缀名所对应的ContentType类型,便于返回资源数据时声明其ContentType格式。详见public String getContentType(String suffix)方法。</p>
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @version 1.0
|
||||
*/
|
||||
public class ContentTypeUtil {
|
||||
|
||||
private static final String SEPARATOR = ".";
|
||||
|
||||
/**
|
||||
*
|
||||
* <h2>通过后缀名获取对应的ContentType</h2>
|
||||
* <p>由文件的后缀名得到相应的ContentType以便浏览器识别该资源。该方法将返回ContentType类型字符串,型如“application/octet-stream”。</p>
|
||||
* @author 青阳龙野(kohgylw)
|
||||
* @param suffix java.lang.String 资源的后缀名,必须以“.”开头,例如“.jpg”
|
||||
* @return java.lang.String 传入后缀所对应的ContentType,若无对应类型则统一返回“application/octet-stream”(二进制流)
|
||||
*/
|
||||
public static String getContentType(String suffix) {
|
||||
|
||||
String _suffix = suffix;
|
||||
|
||||
if(!suffix.contains(SEPARATOR)){
|
||||
_suffix = SEPARATOR + suffix;
|
||||
}
|
||||
|
||||
switch (_suffix) {
|
||||
case ".hta":
|
||||
return "application/hta";
|
||||
case ".tdf":
|
||||
return "application/x-tdf";
|
||||
case ".eml":
|
||||
case ".mht":
|
||||
case ".mhtml":
|
||||
case ".nws":
|
||||
return "message/rfc822";
|
||||
case ".vdx":
|
||||
case ".vsd":
|
||||
case ".vss":
|
||||
case ".vst":
|
||||
case ".vsw":
|
||||
case ".vsx":
|
||||
case ".vtx":
|
||||
return "application/vnd.visio";
|
||||
case ".csi":
|
||||
return "application/x-csi";
|
||||
case ".plt":
|
||||
return "application/x-plt";
|
||||
case ".906":
|
||||
return "application/x-906";
|
||||
case ".rnx":
|
||||
return "application/vnd.rn-realplayer";
|
||||
case ".xlw":
|
||||
return "application/x-xlw";
|
||||
case ".mp2":
|
||||
return "audio/mp2";
|
||||
case ".pl":
|
||||
return "application/x-perl";
|
||||
case ".mp3":
|
||||
return "audio/mp3";
|
||||
case ".mp1":
|
||||
return "audio/mp1";
|
||||
case ".wav":
|
||||
return "audio/wav";
|
||||
case ".rp":
|
||||
return "image/vnd.rn-realpix";
|
||||
case ".hmr":
|
||||
return "application/x-hmr";
|
||||
case ".top":
|
||||
return "drawing/x-top";
|
||||
case ".avi":
|
||||
return "video/avi";
|
||||
case ".pdf":
|
||||
return "application/pdf";
|
||||
case ".htt":
|
||||
return "text/webviewhtml";
|
||||
case ".torrent":
|
||||
return "application/x-bittorrent";
|
||||
case ".dcx":
|
||||
return "application/x-dcx";
|
||||
case ".biz":
|
||||
case ".cml":
|
||||
case ".dcd":
|
||||
case ".dtd":
|
||||
case ".ent":
|
||||
case ".fo":
|
||||
case ".math":
|
||||
case ".mml":
|
||||
case ".mtx":
|
||||
case ".rdf":
|
||||
case ".spp":
|
||||
case ".svg":
|
||||
case ".tld":
|
||||
case ".tsd":
|
||||
case ".vml":
|
||||
case ".vxml":
|
||||
case ".wsdl":
|
||||
case ".xdr":
|
||||
case ".xml":
|
||||
case ".xq":
|
||||
case ".xql":
|
||||
case ".xquery":
|
||||
case ".xsd":
|
||||
case ".xsl":
|
||||
case ".xslt":
|
||||
return "text/xml";
|
||||
case ".sam":
|
||||
return "application/x-sam";
|
||||
case ".wk3":
|
||||
return "application/x-wk3";
|
||||
case ".cdf":
|
||||
return "application/x-netcdf";
|
||||
case ".uls":
|
||||
return "text/iuls";
|
||||
case ".p12":
|
||||
case ".pfx":
|
||||
return "application/x-pkcs12";
|
||||
case ".aif":
|
||||
case ".aifc":
|
||||
case ".aiff":
|
||||
return "audio/aiff";
|
||||
case ".wk4":
|
||||
return "application/x-wk4";
|
||||
case ".lar":
|
||||
return "application/x-laplayer-reg";
|
||||
case ".sat":
|
||||
return "application/x-sat";
|
||||
case ".htm":
|
||||
case ".html":
|
||||
case ".htx":
|
||||
case ".jsp":
|
||||
case ".plg":
|
||||
case ".stm":
|
||||
case ".xhtml":
|
||||
return "text/html";
|
||||
case ".ltr":
|
||||
return "application/x-ltr";
|
||||
case ".wpl":
|
||||
return "application/vnd.ms-wpl";
|
||||
case ".anv":
|
||||
return "application/x-anv";
|
||||
case ".p10":
|
||||
return "application/pkcs10";
|
||||
case ".gl2":
|
||||
return "application/x-gl2";
|
||||
case ".css":
|
||||
return "text/css";
|
||||
case ".vpg":
|
||||
return "application/x-vpeg005";
|
||||
case ".dwg":
|
||||
return "application/x-dwg";
|
||||
case ".slk":
|
||||
return "drawing/x-slk";
|
||||
case ".wma":
|
||||
return "audio/x-ms-wma";
|
||||
case ".sty":
|
||||
return "application/x-sty";
|
||||
case ".wm":
|
||||
return "video/x-ms-wm";
|
||||
case ".cut":
|
||||
return "application/x-cut";
|
||||
case ".a11":
|
||||
return "application/x-a11";
|
||||
case ".sdw":
|
||||
return "application/x-sdw";
|
||||
case ".dgn":
|
||||
return "application/x-dgn";
|
||||
case ".cel":
|
||||
return "application/x-cel";
|
||||
case ".dxb":
|
||||
return "application/x-dxb";
|
||||
case ".tga":
|
||||
return "application/x-tga";
|
||||
case ".dxf":
|
||||
return "application/x-dxf";
|
||||
case ".mil":
|
||||
return "application/x-mil";
|
||||
case ".wmf":
|
||||
return "application/x-wmf";
|
||||
case ".latex":
|
||||
return "application/x-latex";
|
||||
case ".mdb":
|
||||
return "application/msaccess";
|
||||
case ".wks":
|
||||
return "application/x-wks";
|
||||
case ".wkq":
|
||||
return "application/x-wkq";
|
||||
case ".xap":
|
||||
return "application/x-silverlight-app";
|
||||
case ".tg4":
|
||||
return "application/x-tg4";
|
||||
case ".mp2v":
|
||||
case ".mpv2":
|
||||
return "video/mpeg";
|
||||
case ".ras":
|
||||
return "application/x-ras";
|
||||
case ".pko":
|
||||
return "application/vnd.ms-pki.pko";
|
||||
case ".m1v":
|
||||
case ".m2v":
|
||||
case ".mpe":
|
||||
case ".mps":
|
||||
return "video/x-mpeg";
|
||||
case ".wsc":
|
||||
return "text/scriptlet";
|
||||
case ".rsml":
|
||||
return "application/vnd.rn-rsml";
|
||||
case ".cdr":
|
||||
return "application/x-cdr";
|
||||
case ".001":
|
||||
return "application/x-001";
|
||||
case ".la1":
|
||||
return "audio/x-liquid-file";
|
||||
case ".asp":
|
||||
return "text/asp";
|
||||
case ".301":
|
||||
return "application/x-301";
|
||||
case ".etd":
|
||||
return "application/x-ebx";
|
||||
case ".gif":
|
||||
return "image/gif";
|
||||
case ".mns":
|
||||
return "audio/x-musicnet-stream";
|
||||
case ".dwf":
|
||||
return "Model/vnd.dwf";
|
||||
case ".asa":
|
||||
return "text/asa";
|
||||
case ".bmp":
|
||||
return "application/x-bmp";
|
||||
case ".":
|
||||
return "application/x-";
|
||||
case ".323":
|
||||
return "text/h323";
|
||||
case ".fdf":
|
||||
return "application/vnd.fdf";
|
||||
case ".pic":
|
||||
return "application/x-pic";
|
||||
case ".cot":
|
||||
return "application/x-cot";
|
||||
case ".sdp":
|
||||
return "application/sdp";
|
||||
case ".pgl":
|
||||
return "application/x-pgl";
|
||||
case ".doc":
|
||||
case ".dot":
|
||||
case ".rtf":
|
||||
case ".wiz":
|
||||
return "application/msword";
|
||||
case ".net":
|
||||
return "image/pnetvue";
|
||||
case ".m3u":
|
||||
return "audio/mpegurl";
|
||||
case ".js":
|
||||
case ".ls":
|
||||
case ".mocha":
|
||||
return "application/x-javascript";
|
||||
case ".rv":
|
||||
return "video/vnd.rn-realvideo";
|
||||
case ".sst":
|
||||
return "application/vnd.ms-pki.certstore";
|
||||
case ".wvx":
|
||||
return "video/x-ms-wvx";
|
||||
case ".rle":
|
||||
return "application/x-rle";
|
||||
case ".rlc":
|
||||
return "application/x-rlc";
|
||||
case ".rat":
|
||||
return "application/rat-file";
|
||||
case ".vda":
|
||||
return "application/x-vda";
|
||||
case ".mxp":
|
||||
return "application/x-mmxp";
|
||||
case ".r3t":
|
||||
return "text/vnd.rn-realtext3d";
|
||||
case ".lbm":
|
||||
return "application/x-lbm";
|
||||
case ".p7r":
|
||||
return "application/x-pkcs7-certreqresp";
|
||||
case ".wbmp":
|
||||
return "image/vnd.wap.wbmp";
|
||||
case ".dbx":
|
||||
return "application/x-dbx";
|
||||
case ".p7c":
|
||||
case ".p7m":
|
||||
return "application/pkcs7-mime";
|
||||
case ".img":
|
||||
return "application/x-img";
|
||||
case ".bot":
|
||||
return "application/x-bot";
|
||||
case ".ws":
|
||||
case ".ws2":
|
||||
return "application/x-ws";
|
||||
case ".ram":
|
||||
case ".rmm":
|
||||
return "audio/x-pn-realaudio";
|
||||
case ".sol":
|
||||
case ".sor":
|
||||
case ".txt":
|
||||
return "text/plain";
|
||||
case ".p7b":
|
||||
case ".spc":
|
||||
return "application/x-pkcs7-certificates";
|
||||
case ".png":
|
||||
return "image/png";
|
||||
case ".crl":
|
||||
return "application/pkix-crl";
|
||||
case ".cg4":
|
||||
case ".g4":
|
||||
case ".ig4":
|
||||
return "application/x-g4";
|
||||
case ".m4e":
|
||||
case ".mp4":
|
||||
return "video/mpeg4";
|
||||
case ".movie":
|
||||
return "video/x-sgi-movie";
|
||||
case ".mpeg":
|
||||
case ".mpg":
|
||||
case ".mpv":
|
||||
return "video/mpg";
|
||||
case ".ipa":
|
||||
return "application/vnd.iphone";
|
||||
case ".cal":
|
||||
return "application/x-cals";
|
||||
case ".drw":
|
||||
return "application/x-drw";
|
||||
case ".dbf":
|
||||
return "application/x-dbf";
|
||||
case ".pls":
|
||||
case ".xpl":
|
||||
return "audio/scpls";
|
||||
case ".dbm":
|
||||
return "application/x-dbm";
|
||||
case ".ssm":
|
||||
return "application/streamingmedia";
|
||||
case ".gbr":
|
||||
return "application/x-gbr";
|
||||
case ".907":
|
||||
return "drawing/907";
|
||||
case ".mpga":
|
||||
return "audio/rn-mpeg";
|
||||
case ".mfp":
|
||||
case ".swf":
|
||||
return "application/x-shockwave-flash";
|
||||
case ".hpg":
|
||||
return "application/x-hpgl";
|
||||
case ".rmvb":
|
||||
return "application/vnd.rn-realmedia-vbr";
|
||||
case ".rmf":
|
||||
return "application/vnd.adobe.rmf";
|
||||
case ".class":
|
||||
case ".java":
|
||||
return "java/*";
|
||||
case ".mi":
|
||||
return "application/x-mi";
|
||||
case ".xls":
|
||||
return "application/vnd.ms-excel";
|
||||
case ".pci":
|
||||
return "application/x-pci";
|
||||
case ".man":
|
||||
return "application/x-troff-man";
|
||||
case ".pcl":
|
||||
return "application/x-pcl";
|
||||
case ".mpd":
|
||||
case ".mpp":
|
||||
case ".mpt":
|
||||
case ".mpw":
|
||||
case ".mpx":
|
||||
return "application/vnd.ms-project";
|
||||
case ".xfdf":
|
||||
return "application/vnd.adobe.xfdf";
|
||||
case ".pcx":
|
||||
return "application/x-pcx";
|
||||
case ".edn":
|
||||
return "application/vnd.adobe.edn";
|
||||
case ".ptn":
|
||||
return "application/x-ptn";
|
||||
case ".iff":
|
||||
return "application/x-iff";
|
||||
case ".wri":
|
||||
return "application/x-wri";
|
||||
case ".smi":
|
||||
case ".smil":
|
||||
return "application/smil";
|
||||
case ".wrk":
|
||||
return "application/x-wrk";
|
||||
case ".sis":
|
||||
case ".sisx":
|
||||
return "application/vnd.symbian.install";
|
||||
case ".ai":
|
||||
case ".eps":
|
||||
case ".ps":
|
||||
return "application/postscript";
|
||||
case ".htc":
|
||||
return "text/x-component";
|
||||
case ".cmp":
|
||||
return "application/x-cmp";
|
||||
case ".p7s":
|
||||
return "application/pkcs7-signature";
|
||||
case ".xwd":
|
||||
return "application/x-xwd";
|
||||
case ".mac":
|
||||
return "application/x-mac";
|
||||
case ".cmx":
|
||||
return "application/x-cmx";
|
||||
case ".out":
|
||||
return "application/x-out";
|
||||
case ".hgl":
|
||||
return "application/x-hgl";
|
||||
case ".iii":
|
||||
return "application/x-iphone";
|
||||
case ".au":
|
||||
case ".snd":
|
||||
return "audio/basic";
|
||||
case ".smk":
|
||||
return "application/x-smk";
|
||||
case ".igs":
|
||||
return "application/x-igs";
|
||||
case ".ins":
|
||||
case ".isp":
|
||||
return "application/x-internet-signup";
|
||||
case ".epi":
|
||||
return "application/x-epi";
|
||||
case ".rt":
|
||||
return "text/vnd.rn-realtext";
|
||||
case ".pot":
|
||||
case ".ppa":
|
||||
case ".pps":
|
||||
case ".ppt":
|
||||
case ".pwz":
|
||||
return "application/vnd.ms-powerpoint";
|
||||
case ".nrf":
|
||||
return "application/x-nrf";
|
||||
case ".frm":
|
||||
return "application/x-frm";
|
||||
case ".rmx":
|
||||
return "application/vnd.rn-realsystem-rmx";
|
||||
case ".hqx":
|
||||
return "application/mac-binhex40";
|
||||
case ".mpa":
|
||||
return "video/x-mpg";
|
||||
case ".sld":
|
||||
return "application/x-sld";
|
||||
case ".tif":
|
||||
case ".tiff":
|
||||
return "image/tiff";
|
||||
case ".sit":
|
||||
return "application/x-stuffit";
|
||||
case ".slb":
|
||||
return "application/x-slb";
|
||||
case ".mnd":
|
||||
return "audio/x-musicnet-download";
|
||||
case ".wmd":
|
||||
return "application/x-ms-wmd";
|
||||
case ".fif":
|
||||
return "application/fractals";
|
||||
case ".dib":
|
||||
return "application/x-dib";
|
||||
case ".wp6":
|
||||
return "application/x-wp6";
|
||||
case ".lmsff":
|
||||
return "audio/x-la-lms";
|
||||
case ".cat":
|
||||
return "application/vnd.ms-pki.seccat";
|
||||
case ".wmz":
|
||||
return "application/x-ms-wmz";
|
||||
case ".cgm":
|
||||
return "application/x-cgm";
|
||||
case ".icb":
|
||||
return "application/x-icb";
|
||||
case ".rm":
|
||||
return "application/vnd.rn-realmedia";
|
||||
case ".rmj":
|
||||
return "application/vnd.rn-realsystem-rmj";
|
||||
case ".pdx":
|
||||
return "application/vnd.adobe.pdx";
|
||||
case ".red":
|
||||
return "application/x-red";
|
||||
case ".xdp":
|
||||
return "application/vnd.adobe.xdp";
|
||||
case ".wax":
|
||||
return "audio/x-ms-wax";
|
||||
case ".IVF":
|
||||
return "video/x-ivf";
|
||||
case ".x_b":
|
||||
return "application/x-x_b";
|
||||
case ".asf":
|
||||
case ".asx":
|
||||
return "video/x-ms-asf";
|
||||
case ".mid":
|
||||
case ".midi":
|
||||
case ".rmi":
|
||||
return "audio/mid";
|
||||
case ".c4t":
|
||||
return "application/x-c4t";
|
||||
case ".xfd":
|
||||
return "application/vnd.adobe.xfd";
|
||||
case ".wmv":
|
||||
return "video/x-ms-wmv";
|
||||
case ".hpl":
|
||||
return "application/x-hpl";
|
||||
case ".gp4":
|
||||
return "application/x-gp4";
|
||||
case ".wmx":
|
||||
return "video/x-ms-wmx";
|
||||
case ".wml":
|
||||
return "text/vnd.wap.wml";
|
||||
case ".cer":
|
||||
case ".crt":
|
||||
case ".der":
|
||||
return "application/x-x509-ca-cert";
|
||||
case ".ppm":
|
||||
return "application/x-ppm";
|
||||
case ".x_t":
|
||||
return "application/x-x_t";
|
||||
case ".ra":
|
||||
return "audio/vnd.rn-realaudio";
|
||||
case ".rmp":
|
||||
return "application/vnd.rn-rn_music_package";
|
||||
case ".pr":
|
||||
return "application/x-pr";
|
||||
case ".rec":
|
||||
return "application/vnd.rn-recording";
|
||||
case ".wr1":
|
||||
return "application/x-wr1";
|
||||
case ".dll":
|
||||
case ".exe":
|
||||
return "application/x-msdownload";
|
||||
case ".jfif":
|
||||
case ".jpe":
|
||||
case ".jpeg":
|
||||
case ".jpg":
|
||||
return "image/jpeg";
|
||||
case ".c90":
|
||||
return "application/x-c90";
|
||||
case ".ico":
|
||||
return "image/x-icon";
|
||||
case ".rjs":
|
||||
return "application/vnd.rn-realsystem-rjs";
|
||||
case ".awf":
|
||||
return "application/vnd.adobe.workflow";
|
||||
case ".emf":
|
||||
return "application/x-emf";
|
||||
case ".rjt":
|
||||
return "application/vnd.rn-realsystem-rjt";
|
||||
case ".wb2":
|
||||
return "application/x-wb2";
|
||||
case ".wb1":
|
||||
return "application/x-wb1";
|
||||
case ".wb3":
|
||||
return "application/x-wb3";
|
||||
case ".cit":
|
||||
return "application/x-cit";
|
||||
case ".rgb":
|
||||
return "application/x-rgb";
|
||||
case ".rms":
|
||||
return "application/vnd.rn-realmedia-secure";
|
||||
case ".stl":
|
||||
return "application/vnd.ms-pki.stl";
|
||||
case ".prf":
|
||||
return "application/pics-rules";
|
||||
case ".uin":
|
||||
return "application/x-icq";
|
||||
case ".wq1":
|
||||
return "application/x-wq1";
|
||||
case ".acp":
|
||||
return "audio/x-mei-aac";
|
||||
case ".hrf":
|
||||
return "application/x-hrf";
|
||||
case ".rpm":
|
||||
return "audio/x-pn-realaudio-plugin";
|
||||
case ".vcf":
|
||||
return "text/x-vcard";
|
||||
case ".lavs":
|
||||
return "audio/x-liquid-secure";
|
||||
case ".pc5":
|
||||
return "application/x-pc5";
|
||||
case ".apk":
|
||||
return "application/vnd.android.package-archive";
|
||||
case ".odc":
|
||||
return "text/x-ms-odc";
|
||||
case ".spl":
|
||||
return "application/futuresplash";
|
||||
case ".wpd":
|
||||
return "application/x-wpd";
|
||||
case ".wpg":
|
||||
return "application/x-wpg";
|
||||
case ".prn":
|
||||
return "application/x-prn";
|
||||
case ".prt":
|
||||
return "application/x-prt";
|
||||
case ".fax":
|
||||
return "image/fax";
|
||||
default:
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package org.liuxp.minioplus.core.common.utils;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import net.coobird.thumbnailator.Thumbnails;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 对象存储工具类
|
||||
* @author contact@liuxp.me
|
||||
* @since 2024/05/23
|
||||
*/
|
||||
public class MinioPlusCommonUtil {
|
||||
|
||||
/**
|
||||
* 取得对象名称
|
||||
* @param md5 文件MD5值
|
||||
* @return 对象名称
|
||||
*/
|
||||
public static String getObjectName(String md5){
|
||||
return MinioPlusCommonUtil.getPathByDate() + "/" + md5;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前时间取得路径
|
||||
* @return 路径
|
||||
*/
|
||||
public static String getPathByDate(){
|
||||
return LocalDateTimeUtil.format(LocalDateTimeUtil.now(), "yyyy/MM");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件流和重设宽度进行图片压缩
|
||||
* @param inputStream 文件流
|
||||
* @param width 宽度
|
||||
* @return 压缩后的图片文件流
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public static ByteArrayOutputStream resizeImage(InputStream inputStream, int width)throws IOException {
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
Thumbnails.of(inputStream)
|
||||
.width(width)
|
||||
.outputQuality(0.9f)
|
||||
.toOutputStream(outputStream);
|
||||
|
||||
return outputStream;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package org.liuxp.minioplus.core.common.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件完整性校验结果VO
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023-06-26
|
||||
**/
|
||||
@Getter
|
||||
@Setter
|
||||
@ApiModel("文件完整性校验结果")
|
||||
public class CompleteResultVo {
|
||||
|
||||
@ApiModelProperty("是否完成")
|
||||
private Boolean isComplete;
|
||||
|
||||
@ApiModelProperty("上传任务编号")
|
||||
private String uploadTaskId;
|
||||
|
||||
@ApiModelProperty("补传的分块信息")
|
||||
private List<FileCheckResultVo.Part> partList = new ArrayList<>();
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package org.liuxp.minioplus.core.common.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件检查结果VO
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023-06-26
|
||||
**/
|
||||
@Getter
|
||||
@Setter
|
||||
@ApiModel("文件检查结果")
|
||||
public class FileCheckResultVo {
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@ApiModelProperty("主键")
|
||||
private Long id;
|
||||
/**
|
||||
* 文件KEY
|
||||
*/
|
||||
@ApiModelProperty("文件KEY")
|
||||
private String fileKey;
|
||||
/**
|
||||
* 文件md5
|
||||
*/
|
||||
@ApiModelProperty("文件md5")
|
||||
private String fileMd5;
|
||||
/**
|
||||
* 文件名
|
||||
*/
|
||||
@ApiModelProperty("文件名")
|
||||
private String fileName;
|
||||
|
||||
/**
|
||||
* MIME类型
|
||||
*/
|
||||
@ApiModelProperty("MIME类型")
|
||||
private String fileMimeType;
|
||||
/**
|
||||
* 文件后缀
|
||||
*/
|
||||
@ApiModelProperty("文件后缀")
|
||||
private String fileSuffix;
|
||||
/**
|
||||
* 文件长度
|
||||
*/
|
||||
@ApiModelProperty("文件长度")
|
||||
private Long fileSize;
|
||||
/**
|
||||
* 是否秒传
|
||||
*/
|
||||
@ApiModelProperty("是否秒传")
|
||||
private Boolean isDone;
|
||||
/**
|
||||
* 分块数量
|
||||
*/
|
||||
@ApiModelProperty("分块数量")
|
||||
private Integer partCount;
|
||||
|
||||
/**
|
||||
* 分块大小
|
||||
*/
|
||||
@ApiModelProperty("分块大小")
|
||||
private Integer partSize;
|
||||
|
||||
/**
|
||||
* 分块信息
|
||||
*/
|
||||
@ApiModelProperty("分块信息")
|
||||
private List<Part> partList = new ArrayList<>();
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Part {
|
||||
/**
|
||||
* minio的上传id
|
||||
*/
|
||||
@ApiModelProperty("minio的上传id")
|
||||
private String uploadId;
|
||||
/**
|
||||
* 上传地址
|
||||
*/
|
||||
@ApiModelProperty("上传地址")
|
||||
private String url;
|
||||
/**
|
||||
* 开始位置
|
||||
*/
|
||||
@ApiModelProperty("开始位置")
|
||||
private Long startPosition;
|
||||
/**
|
||||
* 结束位置
|
||||
*/
|
||||
@ApiModelProperty("结束位置")
|
||||
private Long endPosition;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package org.liuxp.minioplus.core.common.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 文件元数据信息VO
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023-06-26
|
||||
**/
|
||||
@Getter
|
||||
@Setter
|
||||
@ApiModel("文件元数据信息")
|
||||
public class FileMetadataInfoVo {
|
||||
|
||||
@ApiModelProperty("主键")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("文件KEY")
|
||||
private String fileKey;
|
||||
|
||||
@ApiModelProperty("文件md5")
|
||||
private String fileMd5;
|
||||
|
||||
@ApiModelProperty("文件名")
|
||||
private String fileName;
|
||||
|
||||
@ApiModelProperty("MIME类型")
|
||||
private String fileMimeType;
|
||||
|
||||
@ApiModelProperty("文件后缀")
|
||||
private String fileSuffix;
|
||||
|
||||
@ApiModelProperty("文件长度")
|
||||
private Long fileSize;
|
||||
|
||||
@ApiModelProperty("业务领域类型名称")
|
||||
private String fileDomainName;
|
||||
|
||||
@ApiModelProperty("存储引擎")
|
||||
private String storageEngine;
|
||||
|
||||
@ApiModelProperty("存储桶")
|
||||
private String storageBucket;
|
||||
|
||||
@ApiModelProperty("存储路径")
|
||||
private String storagePath;
|
||||
|
||||
@ApiModelProperty("minio切片任务id")
|
||||
private String uploadTaskId;
|
||||
|
||||
@ApiModelProperty("状态 0:未完成 1:已完成")
|
||||
private Boolean isFinished;
|
||||
|
||||
@ApiModelProperty("是否分块 0:否 1:是")
|
||||
private Boolean isPart;
|
||||
|
||||
@ApiModelProperty("分块数量")
|
||||
private Integer partNumber;
|
||||
|
||||
@ApiModelProperty("预览图 0:无 1:有")
|
||||
private Boolean isPreview;
|
||||
|
||||
@ApiModelProperty("是否私有 0:否 1:是")
|
||||
private Boolean isPrivate;
|
||||
|
||||
@ApiModelProperty("创建人")
|
||||
private String createUser;
|
||||
|
||||
@ApiModelProperty("创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@ApiModelProperty("修改人")
|
||||
private String updateUser;
|
||||
|
||||
@ApiModelProperty("修改时间")
|
||||
private Date updateTime;
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package org.liuxp.minioplus.core.engine;
|
||||
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import org.liuxp.minioplus.core.common.dto.FileCheckDTO;
|
||||
import org.liuxp.minioplus.core.common.dto.FileMetadataInfoSaveDTO;
|
||||
import org.liuxp.minioplus.core.common.vo.CompleteResultVo;
|
||||
import org.liuxp.minioplus.core.common.vo.FileCheckResultVo;
|
||||
import org.liuxp.minioplus.core.common.vo.FileMetadataInfoVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 存储引擎Service接口定义
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/06/26
|
||||
*/
|
||||
public interface StorageEngineService {
|
||||
|
||||
/**
|
||||
* 文件上传预检查
|
||||
*
|
||||
* @param dto dto
|
||||
* @param userId 用户编号
|
||||
* @return {@link FileCheckResultVo}
|
||||
*/
|
||||
FileCheckResultVo check(FileCheckDTO dto,String userId);
|
||||
|
||||
|
||||
/**
|
||||
* 合并已分块的文件
|
||||
*
|
||||
* @param fileKey 文件关键
|
||||
* @param partMd5List 文件分块md5列表
|
||||
* @param userId 用户编号
|
||||
*
|
||||
* @return {@link CompleteResultVo}
|
||||
*/
|
||||
CompleteResultVo complete(String fileKey, List<String> partMd5List,String userId);
|
||||
|
||||
/**
|
||||
* 上传图片
|
||||
* @param fileKey 文件KEY
|
||||
* @param file 文件
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean uploadImage(String fileKey, byte[] file);
|
||||
|
||||
/**
|
||||
* 取得文件下载地址
|
||||
*
|
||||
* @param fileKey 文件KEY
|
||||
* @param userId 用户编号
|
||||
* @return 地址
|
||||
*/
|
||||
String download(String fileKey, String userId);
|
||||
|
||||
/**
|
||||
* 取得原图地址
|
||||
*
|
||||
* @param fileKey 文件KEY
|
||||
* @param userId 用户编号
|
||||
* @return 地址
|
||||
*/
|
||||
String image(String fileKey, String userId);
|
||||
|
||||
/**
|
||||
* 取得缩略图地址
|
||||
*
|
||||
* @param fileKey 文件KEY
|
||||
* @param userId 用户编号
|
||||
* @return 地址
|
||||
*/
|
||||
String preview(String fileKey, String userId);
|
||||
|
||||
/**
|
||||
* 写入文件流
|
||||
* @param saveDTO 文件元数据信息保存入参
|
||||
* @param fileBytes 文件流
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean createFile(FileMetadataInfoSaveDTO saveDTO, byte[] fileBytes);
|
||||
|
||||
/**
|
||||
* 读取文件流
|
||||
* @param fileKey 文件KEY
|
||||
* @return 文件流
|
||||
*/
|
||||
Pair<FileMetadataInfoVo,byte[]> read(String fileKey);
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param fileKey 文件KEY
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean remove(String fileKey);
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param fileKey 文件KEY
|
||||
* @param userId 用户编号
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean remove(String fileKey, String userId);
|
||||
|
||||
}
|
|
@ -0,0 +1,884 @@
|
|||
package org.liuxp.minioplus.core.engine.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import io.minio.CreateMultipartUploadResponse;
|
||||
import io.minio.ListPartsResponse;
|
||||
import io.minio.ObjectWriteResponse;
|
||||
import io.minio.messages.Part;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.liuxp.minioplus.config.MinioPlusProperties;
|
||||
import org.liuxp.minioplus.core.common.bo.CreateUploadUrlReqBO;
|
||||
import org.liuxp.minioplus.core.common.bo.CreateUploadUrlRespBO;
|
||||
import org.liuxp.minioplus.core.common.copy.ListPartsResultCopy;
|
||||
import org.liuxp.minioplus.core.common.dto.FileCheckDTO;
|
||||
import org.liuxp.minioplus.core.common.dto.FileMetadataInfoDTO;
|
||||
import org.liuxp.minioplus.core.common.dto.FileMetadataInfoSaveDTO;
|
||||
import org.liuxp.minioplus.core.common.dto.FileMetadataInfoUpdateDTO;
|
||||
import org.liuxp.minioplus.core.common.dto.minio.MultipartUploadCreateDTO;
|
||||
import org.liuxp.minioplus.core.common.enums.ResponseCodeEnum;
|
||||
import org.liuxp.minioplus.core.common.enums.StorageBucketEnums;
|
||||
import org.liuxp.minioplus.core.common.exception.MinioPlusBusinessException;
|
||||
import org.liuxp.minioplus.core.common.utils.MinioPlusCommonUtil;
|
||||
import org.liuxp.minioplus.core.common.vo.CompleteResultVo;
|
||||
import org.liuxp.minioplus.core.common.vo.FileCheckResultVo;
|
||||
import org.liuxp.minioplus.core.common.vo.FileMetadataInfoVo;
|
||||
import org.liuxp.minioplus.core.engine.StorageEngineService;
|
||||
import org.liuxp.minioplus.core.repository.MetadataRepository;
|
||||
import org.liuxp.minioplus.core.repository.MinioRepository;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 存储引擎Service接口实现类
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/06/26
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class StorageEngineServiceImpl implements StorageEngineService {
|
||||
|
||||
@Resource
|
||||
MetadataRepository metadataRepository;
|
||||
|
||||
@Resource
|
||||
MinioPlusProperties properties;
|
||||
|
||||
@Resource
|
||||
MinioRepository minioRepository;
|
||||
|
||||
private static final String FAILURE_URL = "/storage/failure";
|
||||
|
||||
/**
|
||||
* MinIO中上传编号名称
|
||||
*/
|
||||
private static final String UPLOAD_ID = "uploadId";
|
||||
|
||||
/**
|
||||
* 文件上传预检查
|
||||
*
|
||||
* 1.当前用户上传过,已完成,秒传
|
||||
* 2.当前用户上传过,未完成,断点续传
|
||||
* 3.其他用户上传过,未完成,断点续传,存储当前用户的记录
|
||||
* 4.其他用户上传过,已完成,秒传,存储当前用户的记录
|
||||
*
|
||||
* @param dto 文件预检查入参DTO
|
||||
* @return {@link FileCheckResultVo}
|
||||
*/
|
||||
@Override
|
||||
public FileCheckResultVo check(FileCheckDTO dto,String userId) {
|
||||
|
||||
// 根据MD5查询文件是否已上传过
|
||||
FileMetadataInfoDTO searchDTO = new FileMetadataInfoDTO();
|
||||
searchDTO.setFileMd5(dto.getFileMd5());
|
||||
List<FileMetadataInfoVo> list = metadataRepository.list(searchDTO);
|
||||
|
||||
FileMetadataInfoSaveDTO saveDTO = new FileMetadataInfoSaveDTO();
|
||||
CreateUploadUrlReqBO bo = new CreateUploadUrlReqBO();
|
||||
|
||||
// 没有任何用户上传过该文件,直接创建新的元数据与链接
|
||||
if(CollUtil.isEmpty(list)){
|
||||
// 获取上传链接
|
||||
BeanUtils.copyProperties(dto, bo);
|
||||
CreateUploadUrlRespBO createUploadUrlRespBO = this.createUploadUrl(bo);
|
||||
|
||||
FileMetadataInfoVo metadataInfo = saveMetadataInfo(saveDTO, createUploadUrlRespBO, dto, userId);
|
||||
|
||||
return this.buildResult(metadataInfo, createUploadUrlRespBO.getParts(), createUploadUrlRespBO.getPartCount(), Boolean.FALSE);
|
||||
}
|
||||
|
||||
|
||||
Optional<FileMetadataInfoVo> userUploaded = list.stream().filter(item -> userId.equals(item.getCreateUser())).findFirst();
|
||||
|
||||
// 当前用户没有上传过
|
||||
if (!userUploaded.isPresent()) {
|
||||
// 优先寻找其他用户未上传完成的任务
|
||||
FileMetadataInfoVo otherUserUploadTask = list.stream()
|
||||
.filter(FileMetadataInfoVo::getIsFinished)
|
||||
.findAny()
|
||||
.orElseGet(() -> list.stream()
|
||||
.filter(item -> !item.getIsFinished()).findFirst().get());
|
||||
|
||||
if (Boolean.FALSE.equals(otherUserUploadTask.getIsFinished())) {
|
||||
// 上传过未完成-断点续传
|
||||
bo.setIsSequel(Boolean.TRUE);
|
||||
CreateUploadUrlRespBO respBO = this.breakResume(otherUserUploadTask);
|
||||
|
||||
// 插入自己的元数据
|
||||
BeanUtils.copyProperties(otherUserUploadTask, saveDTO);
|
||||
saveDTO.setFileName(dto.getFullFileName());
|
||||
saveDTO.setCreateUser(userId);
|
||||
saveDTO.setIsPrivate(dto.getIsPrivate());
|
||||
saveDTO.setUploadTaskId(respBO.getUploadTaskId());
|
||||
FileMetadataInfoVo metadataInfoVo = metadataRepository.save(saveDTO);
|
||||
|
||||
return this.buildResult(metadataInfoVo, respBO.getParts(), respBO.getPartCount(), Boolean.FALSE);
|
||||
} else {
|
||||
// 秒传
|
||||
BeanUtils.copyProperties(otherUserUploadTask, saveDTO);
|
||||
saveDTO.setFileName(dto.getFullFileName());
|
||||
saveDTO.setCreateUser(userId);
|
||||
saveDTO.setIsPrivate(dto.getIsPrivate());
|
||||
FileMetadataInfoVo metadataInfoVo = metadataRepository.save(saveDTO);
|
||||
return this.buildResult(metadataInfoVo, new ArrayList<>(1), 0, Boolean.TRUE);
|
||||
}
|
||||
} else if (Boolean.TRUE.equals(userUploaded.get().getIsFinished())) {
|
||||
// 上传过并且已经完成- 秒传
|
||||
return this.buildResult(userUploaded.get(), new ArrayList<>(1), 0, Boolean.TRUE);
|
||||
} else {
|
||||
// 上传过未完成-断点续传
|
||||
String suffix = FileUtil.getSuffix(dto.getFullFileName());
|
||||
String bucketName = StorageBucketEnums.getBucketByFileSuffix(suffix);
|
||||
// 是图片类型
|
||||
if (bucketName.equals(StorageBucketEnums.IMAGE.getCode())) {
|
||||
// 分块信息集合
|
||||
List<FileCheckResultVo.Part> partList = new ArrayList<>();
|
||||
|
||||
FileCheckResultVo.Part part = new FileCheckResultVo.Part();
|
||||
// 图片上传时,直接使用fileKey作为uploadId
|
||||
part.setUploadId(userUploaded.get().getFileKey());
|
||||
part.setUrl("/storage/upload/image/"+userUploaded.get().getFileKey());
|
||||
part.setStartPosition(0L);
|
||||
part.setEndPosition(dto.getFileSize());
|
||||
partList.add(part);
|
||||
|
||||
return this.buildResult(userUploaded.get(), partList, 0, Boolean.FALSE);
|
||||
}
|
||||
|
||||
bo.setIsSequel(Boolean.TRUE);
|
||||
CreateUploadUrlRespBO respBO = this.breakResume(userUploaded.get());
|
||||
if(CollUtil.isNotEmpty(respBO.getParts()) && !respBO.getUploadTaskId().equals(userUploaded.get().getUploadTaskId())){
|
||||
userUploaded.get().setUploadTaskId(respBO.getUploadTaskId());
|
||||
FileMetadataInfoUpdateDTO updateDTO = new FileMetadataInfoUpdateDTO();
|
||||
updateDTO.setId(userUploaded.get().getId());
|
||||
updateDTO.setUploadTaskId(userUploaded.get().getUploadTaskId());
|
||||
updateDTO.setUpdateUser(userId);
|
||||
metadataRepository.update(updateDTO);
|
||||
}
|
||||
|
||||
return this.buildResult(userUploaded.get(), respBO.getParts(), respBO.getPartCount(), Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建结果
|
||||
* 构建文件预检结果
|
||||
*
|
||||
* @param metadataInfo 元数据信息
|
||||
* @param partList 块信息
|
||||
* @param partCount 块数量
|
||||
* @param isDone 是否秒传
|
||||
* @return {@link FileCheckResultVo}
|
||||
*/
|
||||
private FileCheckResultVo buildResult(FileMetadataInfoVo metadataInfo, List<FileCheckResultVo.Part> partList, Integer partCount, Boolean isDone) {
|
||||
FileCheckResultVo fileCheckResultVo = new FileCheckResultVo();
|
||||
// 主键
|
||||
fileCheckResultVo.setId(metadataInfo.getId());
|
||||
// 文件KEY
|
||||
fileCheckResultVo.setFileKey(metadataInfo.getFileKey());
|
||||
// 文件md5
|
||||
fileCheckResultVo.setFileMd5(metadataInfo.getFileMd5());
|
||||
// 文件名
|
||||
fileCheckResultVo.setFileName(metadataInfo.getFileName());
|
||||
// MIME类型
|
||||
fileCheckResultVo.setFileMimeType(metadataInfo.getFileMimeType());
|
||||
// 文件后缀
|
||||
fileCheckResultVo.setFileSuffix(metadataInfo.getFileSuffix());
|
||||
// 文件长度
|
||||
fileCheckResultVo.setFileSize(metadataInfo.getFileSize());
|
||||
// 是否秒传
|
||||
fileCheckResultVo.setIsDone(isDone);
|
||||
// 分块数量
|
||||
fileCheckResultVo.setPartCount(partCount);
|
||||
// 分块大小
|
||||
fileCheckResultVo.setPartSize(properties.getPart().getSize());
|
||||
// 分块信息
|
||||
fileCheckResultVo.setPartList(partList);
|
||||
return fileCheckResultVo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文件源信息
|
||||
*
|
||||
* @param saveDTO 元数据保存实体类
|
||||
* @param createUploadUrlRespBO 上传链接参数
|
||||
* @param dto 文件检测参数
|
||||
* @param userId 用户
|
||||
* @return {@link FileMetadataInfoVo}
|
||||
*/
|
||||
private FileMetadataInfoVo saveMetadataInfo(FileMetadataInfoSaveDTO saveDTO, CreateUploadUrlRespBO createUploadUrlRespBO,
|
||||
FileCheckDTO dto, String userId) {
|
||||
// 保存文件元数据
|
||||
String suffix = FileUtil.getSuffix(dto.getFullFileName());
|
||||
// 文件KEY
|
||||
saveDTO.setFileKey(createUploadUrlRespBO.getFileKey());
|
||||
// 文件md5
|
||||
saveDTO.setFileMd5(dto.getFileMd5());
|
||||
// 文件名
|
||||
saveDTO.setFileName(dto.getFullFileName());
|
||||
// MIME类型
|
||||
saveDTO.setFileMimeType(FileUtil.getMimeType(dto.getFullFileName()));
|
||||
// 文件后缀
|
||||
saveDTO.setFileSuffix(suffix);
|
||||
// 文件长度
|
||||
saveDTO.setFileSize(dto.getFileSize());
|
||||
|
||||
// minio引擎特殊存储
|
||||
if (createUploadUrlRespBO.getMinioStorage() != null) {
|
||||
// 存储桶
|
||||
saveDTO.setStorageBucket(createUploadUrlRespBO.getMinioStorage().getBucketName());
|
||||
// 存储路径
|
||||
saveDTO.setStoragePath(createUploadUrlRespBO.getMinioStorage().getStoragePath());
|
||||
}
|
||||
// 上传任务id
|
||||
saveDTO.setUploadTaskId(createUploadUrlRespBO.getUploadTaskId());
|
||||
// 状态 0:未完成 1:已完成
|
||||
saveDTO.setIsFinished(Boolean.FALSE);
|
||||
// 是否分片
|
||||
saveDTO.setIsPart(createUploadUrlRespBO.getPartCount() > 1);
|
||||
// 分片数量
|
||||
saveDTO.setPartNumber(createUploadUrlRespBO.getPartCount());
|
||||
// 预览图 0:无 1:有
|
||||
saveDTO.setIsPreview(saveDTO.getStorageBucket().equals(StorageBucketEnums.IMAGE.getCode()) && properties.getThumbnail().isEnable());
|
||||
// 是否私有 0:否 1:是
|
||||
saveDTO.setIsPrivate(dto.getIsPrivate());
|
||||
// 创建人
|
||||
saveDTO.setCreateUser(userId);
|
||||
// 修改人
|
||||
saveDTO.setUpdateUser(userId);
|
||||
return metadataRepository.save(saveDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并已分块的文件
|
||||
*
|
||||
* @param fileKey 文件关键
|
||||
* @return {@link Boolean}
|
||||
*/
|
||||
@Override
|
||||
public CompleteResultVo complete(String fileKey, List<String> partMd5List,String userId) {
|
||||
|
||||
CompleteResultVo completeResultVo = new CompleteResultVo();
|
||||
|
||||
FileMetadataInfoDTO searchDto = new FileMetadataInfoDTO();
|
||||
// 用户id
|
||||
searchDto.setCreateUser(userId);
|
||||
// 文件key
|
||||
searchDto.setFileKey(fileKey);
|
||||
|
||||
FileMetadataInfoVo metadata = metadataRepository.one(searchDto);
|
||||
|
||||
if(metadata == null){
|
||||
log.error(fileKey+"不存在");
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(),fileKey+"不存在");
|
||||
}
|
||||
|
||||
if(Boolean.TRUE.equals(metadata.getIsFinished())){
|
||||
// 如果文件已上传完成,直接返回true,不进行合并
|
||||
completeResultVo.setIsComplete(true);
|
||||
return completeResultVo;
|
||||
}
|
||||
|
||||
if(metadata.getStorageBucket().equals(StorageBucketEnums.IMAGE.getCode())){
|
||||
// 图片时,生成图片的上传链接
|
||||
List<FileCheckResultVo.Part> partList = new ArrayList<>();
|
||||
|
||||
FileCheckResultVo.Part part = new FileCheckResultVo.Part();
|
||||
part.setUploadId(metadata.getFileKey());
|
||||
part.setUrl("/storage/upload/image/"+metadata.getFileKey());
|
||||
part.setStartPosition(0L);
|
||||
part.setEndPosition(metadata.getFileSize());
|
||||
partList.add(part);
|
||||
|
||||
completeResultVo.setIsComplete(false);
|
||||
completeResultVo.setUploadTaskId(metadata.getUploadTaskId());
|
||||
completeResultVo.setPartList(partList);
|
||||
|
||||
return completeResultVo;
|
||||
}
|
||||
|
||||
completeResultVo = this.completeMultipartUpload(metadata, partMd5List);
|
||||
|
||||
if (Boolean.TRUE.equals(completeResultVo.getIsComplete())) {
|
||||
|
||||
// 更新自己上传的文件元数据状态
|
||||
FileMetadataInfoUpdateDTO updateDTO = new FileMetadataInfoUpdateDTO();
|
||||
updateDTO.setId(metadata.getId());
|
||||
updateDTO.setIsFinished(Boolean.TRUE);
|
||||
updateDTO.setUpdateUser(metadata.getUpdateUser());
|
||||
metadataRepository.update(updateDTO);
|
||||
|
||||
// 搜索数据库中所有未完成的相同MD5元数据,更新为完成状态
|
||||
searchDto = new FileMetadataInfoDTO();
|
||||
searchDto.setFileMd5(metadata.getFileMd5());
|
||||
searchDto.setIsFinished(false);
|
||||
List<FileMetadataInfoVo> others = metadataRepository.list(searchDto);
|
||||
if(CollUtil.isNotEmpty(others)){
|
||||
for (FileMetadataInfoVo other : others) {
|
||||
updateDTO = new FileMetadataInfoUpdateDTO();
|
||||
updateDTO.setId(other.getId());
|
||||
updateDTO.setIsFinished(Boolean.TRUE);
|
||||
updateDTO.setUpdateUser(metadata.getUpdateUser());
|
||||
metadataRepository.update(updateDTO);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if(!metadata.getUploadTaskId().equals(completeResultVo.getUploadTaskId())){
|
||||
FileMetadataInfoUpdateDTO updateDTO = new FileMetadataInfoUpdateDTO();
|
||||
updateDTO.setId(metadata.getId());
|
||||
updateDTO.setUploadTaskId(completeResultVo.getUploadTaskId());
|
||||
updateDTO.setUpdateUser(metadata.getUpdateUser());
|
||||
metadataRepository.update(updateDTO);
|
||||
}
|
||||
}
|
||||
return completeResultVo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean uploadImage(String fileKey, byte[] file) {
|
||||
|
||||
FileMetadataInfoDTO searchDto = new FileMetadataInfoDTO();
|
||||
searchDto.setFileKey(fileKey);
|
||||
FileMetadataInfoVo metadata = metadataRepository.one(searchDto);
|
||||
|
||||
try {
|
||||
|
||||
FileMetadataInfoSaveDTO saveDto = new FileMetadataInfoSaveDTO();
|
||||
saveDto.setStorageBucket(metadata.getStorageBucket());
|
||||
saveDto.setStoragePath(metadata.getStoragePath());
|
||||
saveDto.setFileMd5(metadata.getFileMd5());
|
||||
saveDto.setFileSize(metadata.getFileSize());
|
||||
saveDto.setFileMimeType(metadata.getFileMimeType());
|
||||
saveDto.setIsPreview(metadata.getIsPreview());
|
||||
|
||||
Boolean isCreateFile = createFile(saveDto,file);
|
||||
|
||||
FileMetadataInfoUpdateDTO updateDTO = new FileMetadataInfoUpdateDTO();
|
||||
updateDTO.setId(metadata.getId());
|
||||
updateDTO.setIsFinished(Boolean.TRUE);
|
||||
updateDTO.setUpdateUser(metadata.getUpdateUser());
|
||||
metadataRepository.update(updateDTO);
|
||||
|
||||
return isCreateFile;
|
||||
|
||||
}catch(Exception e){
|
||||
log.error("文件上传失败",e);
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(),"文件上传失败");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String download(String fileKey, String userId) {
|
||||
|
||||
FileMetadataInfoVo metadata = getFileMetadataInfo(fileKey, userId);
|
||||
|
||||
try{
|
||||
|
||||
// 文件权限校验,元数据为空或者当前登录用户不是文件所有者时抛出异常
|
||||
this.authentication(metadata, fileKey, userId);
|
||||
|
||||
return minioRepository.getDownloadUrl(metadata.getFileName(),metadata.getFileMimeType(),metadata.getStorageBucket(),metadata.getStoragePath() + "/"+ metadata.getFileMd5());
|
||||
}catch(Exception e){
|
||||
// 打印日志
|
||||
log.error(e.getMessage());
|
||||
// 返回失效地址,任何异常,统一返回给前端图片已失效
|
||||
return FAILURE_URL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String image(String fileKey, String userId) {
|
||||
|
||||
FileMetadataInfoVo metadata = getFileMetadataInfo(fileKey, userId);
|
||||
|
||||
try{
|
||||
|
||||
// 文件权限校验,元数据为空或者当前登录用户不是文件所有者时抛出异常
|
||||
this.authentication(metadata, fileKey, userId);
|
||||
|
||||
return minioRepository.getPreviewUrl(metadata.getFileMimeType(),metadata.getStorageBucket(),metadata.getStoragePath() + "/"+ metadata.getFileMd5());
|
||||
|
||||
}catch(Exception e){
|
||||
// 打印日志
|
||||
log.error(e.getMessage());
|
||||
// 返回失效地址,任何异常,统一返回给前端图片已失效
|
||||
return FAILURE_URL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String preview(String fileKey, String userId) {
|
||||
|
||||
FileMetadataInfoVo metadata = getFileMetadataInfo(fileKey, userId);
|
||||
|
||||
try{
|
||||
|
||||
// 文件权限校验,元数据为空或者当前登录用户不是文件所有者时抛出异常
|
||||
this.authentication(metadata, fileKey, userId);
|
||||
// 判断是否存在缩略图,设置桶名称
|
||||
String bucketName = Boolean.TRUE.equals(metadata.getIsPreview()) ? StorageBucketEnums.IMAGE_PREVIEW.getCode() : metadata.getStorageBucket();
|
||||
// 创建图片预览地址
|
||||
return minioRepository.getPreviewUrl(metadata.getFileMimeType(),bucketName,metadata.getStoragePath() + "/"+ metadata.getFileMd5());
|
||||
|
||||
}catch(Exception e){
|
||||
// 打印日志
|
||||
log.error(e.getMessage());
|
||||
// 返回失效地址,任何异常,统一返回给前端图片已失效
|
||||
return FAILURE_URL;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean createFile(FileMetadataInfoSaveDTO saveDTO, byte[] fileBytes) {
|
||||
|
||||
// 写入文件
|
||||
minioRepository.write(saveDTO.getStorageBucket(), MinioPlusCommonUtil.getObjectName(saveDTO.getFileMd5()), new ByteArrayInputStream(fileBytes), saveDTO.getFileSize(), saveDTO.getFileMimeType());
|
||||
|
||||
// 判断是否生成缩略图
|
||||
if(Boolean.TRUE.equals(saveDTO.getIsPreview())){
|
||||
|
||||
try{
|
||||
ByteArrayOutputStream largeImage = MinioPlusCommonUtil.resizeImage(new ByteArrayInputStream(fileBytes), properties.getThumbnail().getSize());
|
||||
byte[] largeImageBytes = largeImage.toByteArray();
|
||||
minioRepository.write(StorageBucketEnums.IMAGE_PREVIEW.getCode(), MinioPlusCommonUtil.getObjectName(saveDTO.getFileMd5()), new ByteArrayInputStream(largeImageBytes), largeImageBytes.length, saveDTO.getFileMimeType());
|
||||
}catch(Exception e){
|
||||
log.error("缩略图写入失败",e);
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(),"缩略图写入失败");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<FileMetadataInfoVo,byte[]> read(String fileKey) {
|
||||
|
||||
// 查询文件元数据
|
||||
FileMetadataInfoDTO fileMetadataInfo = new FileMetadataInfoDTO();
|
||||
fileMetadataInfo.setFileKey(fileKey);
|
||||
FileMetadataInfoVo fileMetadataInfoVo = metadataRepository.one(fileMetadataInfo);
|
||||
|
||||
if (null == fileMetadataInfoVo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 读取流
|
||||
byte[] fileBytes = minioRepository.read(fileMetadataInfoVo.getStorageBucket(), fileMetadataInfoVo.getStoragePath() + "/" + fileMetadataInfoVo.getFileMd5());
|
||||
|
||||
return Pair.of(fileMetadataInfoVo,fileBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean remove(String fileKey) {
|
||||
|
||||
// 查询元数据信息
|
||||
FileMetadataInfoDTO fileMetadataInfo = new FileMetadataInfoDTO();
|
||||
fileMetadataInfo.setFileKey(fileKey);
|
||||
FileMetadataInfoVo metadata = metadataRepository.one(fileMetadataInfo);
|
||||
|
||||
if (null != metadata) {
|
||||
remove(metadata);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean remove(String fileKey, String userId) {
|
||||
|
||||
// 查询元数据信息
|
||||
FileMetadataInfoDTO fileMetadataInfo = new FileMetadataInfoDTO();
|
||||
fileMetadataInfo.setFileKey(fileKey);
|
||||
FileMetadataInfoVo metadata = metadataRepository.one(fileMetadataInfo);
|
||||
|
||||
if (null != metadata && userId.equals(metadata.getCreateUser())) {
|
||||
remove(metadata);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void remove(FileMetadataInfoVo metadata){
|
||||
// 删除元数据信息
|
||||
metadataRepository.remove(metadata.getId());
|
||||
|
||||
FileMetadataInfoDTO fileMetadataInfo = new FileMetadataInfoDTO();
|
||||
fileMetadataInfo.setFileMd5(metadata.getFileMd5());
|
||||
List<FileMetadataInfoVo> metadataList = metadataRepository.list(fileMetadataInfo);
|
||||
|
||||
if(CollUtil.isEmpty(metadataList)){
|
||||
// 当不存在任何该MD5值的文件元数据时,删除物理文件
|
||||
minioRepository.remove(metadata.getStorageBucket(), metadata.getStoragePath() + "/" + metadata.getFileMd5());
|
||||
if(Boolean.TRUE.equals(metadata.getIsPreview())){
|
||||
// 当存在缩略图时,同步删除缩略图
|
||||
minioRepository.remove(StorageBucketEnums.IMAGE_PREVIEW.getCode(), metadata.getStoragePath() + "/" + metadata.getFileMd5());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户取得文件元数据信息
|
||||
* 当userId匹配时直接返回,不匹配时检查是否存在公有元数据
|
||||
* @param fileKey 文件KEY
|
||||
* @param userId 用户主键
|
||||
* @return 文件元数据信息
|
||||
*/
|
||||
private FileMetadataInfoVo getFileMetadataInfo(String fileKey, String userId) {
|
||||
FileMetadataInfoDTO fileMetadataInfo = new FileMetadataInfoDTO();
|
||||
fileMetadataInfo.setFileKey(fileKey);
|
||||
// 取得md5元数据
|
||||
List<FileMetadataInfoVo> fileMetadataInfoVoList = metadataRepository.list(fileMetadataInfo);
|
||||
|
||||
for (FileMetadataInfoVo fileMetadataInfoVo : fileMetadataInfoVoList) {
|
||||
if (Boolean.FALSE.equals(fileMetadataInfoVo.getIsPrivate()) || fileMetadataInfoVo.getCreateUser().equals(userId)) {
|
||||
return fileMetadataInfoVo;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件权限校验
|
||||
* 元数据为空时抛出异常
|
||||
* 文件为私有文件且当前登录用户不是文件所有者时抛出异常
|
||||
*
|
||||
* @param metadata 文件元数据
|
||||
* @param fileKey 文件key
|
||||
* @param userId 用户主键
|
||||
*/
|
||||
private void authentication(FileMetadataInfoVo metadata, String fileKey, String userId){
|
||||
if (null == metadata) {
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(), fileKey + "不存在!");
|
||||
}
|
||||
|
||||
// 元数据信息存在,判断权限
|
||||
if(Boolean.TRUE.equals(metadata.getIsPrivate()) && !userId.equals(metadata.getCreateUser())){
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(), fileKey + "用户userId="+userId+"没有访问权限!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算分块的数量
|
||||
*
|
||||
* @param fileSize 文件大小
|
||||
* @return {@link Integer}
|
||||
*/
|
||||
private Integer computeChunkNum(Long fileSize) {
|
||||
// 计算分块数量
|
||||
double tempNum = (double) fileSize / properties.getPart().getSize();
|
||||
// 向上取整
|
||||
return ((Double) Math.ceil(tempNum)).intValue();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建响应给前端的分片信息
|
||||
*
|
||||
* @param uploadCreateDTO 分片dto
|
||||
* @param queryParams 查询参数
|
||||
* @param fileSize 文件大小
|
||||
* @param start 开始位置
|
||||
* @param partNumber 块号
|
||||
* @return {@link FileCheckResultVo.Part}
|
||||
*/
|
||||
private FileCheckResultVo.Part buildResultPart(MultipartUploadCreateDTO uploadCreateDTO, Map<String, String> queryParams, Long fileSize, long start, Integer partNumber) {
|
||||
// 计算起始位置
|
||||
long end = Math.min(start + properties.getPart().getSize(), fileSize);
|
||||
queryParams.put("partNumber", String.valueOf(partNumber));
|
||||
String uploadUrl = minioRepository.getPresignedObjectUrl(uploadCreateDTO.getBucketName(), uploadCreateDTO.getObjectName(), queryParams);
|
||||
FileCheckResultVo.Part part = new FileCheckResultVo.Part();
|
||||
part.setUploadId(uploadCreateDTO.getUploadId());
|
||||
// 上传地址
|
||||
part.setUrl(uploadUrl);
|
||||
// 开始位置
|
||||
part.setStartPosition(start);
|
||||
// 结束位置
|
||||
part.setEndPosition(end);
|
||||
return part;
|
||||
}
|
||||
|
||||
/**
|
||||
* 断点续传-创建断点的URL
|
||||
*
|
||||
* @return {@link CreateUploadUrlRespBO}
|
||||
*/
|
||||
public CreateUploadUrlRespBO breakResume(FileMetadataInfoVo fileMetadataVo) {
|
||||
|
||||
CreateUploadUrlRespBO result = new CreateUploadUrlRespBO();
|
||||
result.setParts(new ArrayList<>());
|
||||
result.setPartCount(fileMetadataVo.getPartNumber());
|
||||
|
||||
// 分块数量
|
||||
Integer chunkNum = fileMetadataVo.getPartNumber();
|
||||
// 获取分块信息
|
||||
ListPartsResponse listPartsResponse = this.buildResultPart(fileMetadataVo);
|
||||
List<Part> parts = listPartsResponse.result().partList();
|
||||
if (!chunkNum.equals(parts.size())) {
|
||||
// 找到丢失的片
|
||||
boolean[] exists = new boolean[chunkNum + 1];
|
||||
// 遍历数组,标记存在的块号
|
||||
for (Part item : parts) {
|
||||
int partNumber = item.partNumber();
|
||||
exists[partNumber] = true;
|
||||
}
|
||||
// 查找丢失的块号
|
||||
List<Integer> missingNumbers = new ArrayList<>();
|
||||
for (int i = 1; i <= chunkNum; i++) {
|
||||
if (!exists[i]) {
|
||||
missingNumbers.add(i);
|
||||
}
|
||||
}
|
||||
CreateUploadUrlReqBO bo = new CreateUploadUrlReqBO();
|
||||
// 文件md5
|
||||
bo.setFileMd5(fileMetadataVo.getFileMd5());
|
||||
// 文件名(含扩展名)
|
||||
bo.setFullFileName(fileMetadataVo.getFileName());
|
||||
// "文件长度"
|
||||
bo.setFileSize(fileMetadataVo.getFileSize());
|
||||
// 是否断点续传 0:否 1:是,默认非断点续传
|
||||
bo.setIsSequel(Boolean.TRUE);
|
||||
// 丢失的块号-断点续传时必传
|
||||
bo.setMissPartNum(missingNumbers);
|
||||
|
||||
if(missingNumbers.size() != chunkNum){
|
||||
// 任务id,任务id可能会失效
|
||||
bo.setUploadId(fileMetadataVo.getUploadTaskId());
|
||||
}
|
||||
|
||||
// 存储桶
|
||||
bo.setStorageBucket(fileMetadataVo.getStorageBucket());
|
||||
// 存储路径
|
||||
bo.setStoragePath(fileMetadataVo.getStoragePath());
|
||||
// 文件id
|
||||
bo.setFileKey(fileMetadataVo.getFileKey());
|
||||
result = this.createUploadUrl(bo);
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并分片
|
||||
*/
|
||||
public CompleteResultVo completeMultipartUpload(FileMetadataInfoVo meteData, List<String> partMd5List) {
|
||||
|
||||
CompleteResultVo completeResultVo = new CompleteResultVo();
|
||||
|
||||
// 获取所有的分片信息
|
||||
ListPartsResponse listMultipart = this.buildResultPart(meteData);
|
||||
|
||||
List<Integer> missingNumbers =new ArrayList<>();
|
||||
|
||||
// 分块数量
|
||||
Integer chunkNum = meteData.getPartNumber();
|
||||
|
||||
if(partMd5List==null || chunkNum != partMd5List.size()){
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(),"文件分块MD5校验码数量与实际分块不一致");
|
||||
}
|
||||
|
||||
// 校验文件完整性
|
||||
for (int i = 1; i <= chunkNum; i++) {
|
||||
boolean findPart = false;
|
||||
for (Part part : listMultipart.result().partList()) {
|
||||
if(part.partNumber() == i && part.etag().equals(partMd5List.get(i-1))){
|
||||
findPart = true;
|
||||
}
|
||||
}
|
||||
if(!findPart){
|
||||
missingNumbers.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
if(CollUtil.isNotEmpty(missingNumbers)){
|
||||
CreateUploadUrlReqBO bo = new CreateUploadUrlReqBO();
|
||||
// 文件md5
|
||||
bo.setFileMd5(meteData.getFileMd5());
|
||||
// 文件名(含扩展名)
|
||||
bo.setFullFileName(meteData.getFileName());
|
||||
// "文件长度"
|
||||
bo.setFileSize(meteData.getFileSize());
|
||||
// 是否断点续传 0:否 1:是,默认非断点续传
|
||||
bo.setIsSequel(Boolean.TRUE);
|
||||
// 丢失的块号-断点续传时必传
|
||||
bo.setMissPartNum(missingNumbers);
|
||||
if(missingNumbers.size() != chunkNum){
|
||||
// 任务id,任务id可能会失效
|
||||
bo.setUploadId(meteData.getUploadTaskId());
|
||||
}
|
||||
// 存储桶
|
||||
bo.setStorageBucket(meteData.getStorageBucket());
|
||||
// 存储路径
|
||||
bo.setStoragePath(meteData.getStoragePath());
|
||||
// 文件id
|
||||
bo.setFileKey(meteData.getFileKey());
|
||||
CreateUploadUrlRespBO createUploadUrlRespBO = this.createUploadUrl(bo);
|
||||
|
||||
completeResultVo.setIsComplete(false);
|
||||
completeResultVo.setUploadTaskId(createUploadUrlRespBO.getUploadTaskId());
|
||||
completeResultVo.setPartList(createUploadUrlRespBO.getParts());
|
||||
}else{
|
||||
// 合并分块
|
||||
ObjectWriteResponse writeResponse = minioRepository.completeMultipartUpload(MultipartUploadCreateDTO.builder()
|
||||
.bucketName(meteData.getStorageBucket())
|
||||
.uploadId(meteData.getUploadTaskId())
|
||||
.objectName(listMultipart.object())
|
||||
.parts(listMultipart.result().partList().toArray(new Part[]{}))
|
||||
.build());
|
||||
completeResultVo.setIsComplete(null!=writeResponse);
|
||||
completeResultVo.setPartList(new ArrayList<>());
|
||||
}
|
||||
|
||||
return completeResultVo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分片信息
|
||||
*
|
||||
* @param meteData 文件元数据信息
|
||||
* @return {@link ListPartsResponse} 分片任务信息
|
||||
*/
|
||||
private ListPartsResponse buildResultPart(FileMetadataInfoVo meteData){
|
||||
String objectName = MinioPlusCommonUtil.getObjectName(meteData.getFileMd5());
|
||||
|
||||
try {
|
||||
// 获取所有的分片信息
|
||||
return minioRepository.listMultipart(MultipartUploadCreateDTO.builder()
|
||||
.bucketName(meteData.getStorageBucket())
|
||||
.objectName(objectName)
|
||||
.maxParts(meteData.getPartNumber())
|
||||
.uploadId(meteData.getUploadTaskId())
|
||||
.partNumberMarker(0)
|
||||
.build());
|
||||
}catch (MinioPlusBusinessException e){
|
||||
log.error("获取分片信息失败,partList返回空",e);
|
||||
MultipartUploadCreateDTO multipartUploadCreateDTO = MultipartUploadCreateDTO.builder()
|
||||
.bucketName(meteData.getStorageBucket())
|
||||
.objectName(objectName)
|
||||
.maxParts(meteData.getPartNumber())
|
||||
.uploadId(meteData.getUploadTaskId())
|
||||
.partNumberMarker(0)
|
||||
.build();
|
||||
|
||||
ListPartsResultCopy listPartsResult = new ListPartsResultCopy();
|
||||
|
||||
return new ListPartsResponse(null,multipartUploadCreateDTO.getBucketName(),multipartUploadCreateDTO.getRegion(),multipartUploadCreateDTO.getObjectName(),listPartsResult);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public CreateUploadUrlRespBO createUploadUrl(CreateUploadUrlReqBO bo) {
|
||||
// 计算分块数量
|
||||
Integer chunkNum = this.computeChunkNum(bo.getFileSize());
|
||||
// 分块信息集合
|
||||
List<FileCheckResultVo.Part> partList = new ArrayList<>();
|
||||
// 存储桶
|
||||
String bucketName;
|
||||
// 存储路径
|
||||
String storagePath;
|
||||
// 文件key
|
||||
String fileKey;
|
||||
// 额外的查询参数字段
|
||||
Map<String, String> queryParams = new HashMap<>(2);
|
||||
// 断点续传
|
||||
if (Boolean.TRUE.equals(bo.getIsSequel()) && CollUtil.isNotEmpty(bo.getMissPartNum()) && CharSequenceUtil.isNotBlank(bo.getUploadId())) {
|
||||
// 断点续传需要使用已创建的任务信息构建分片信息
|
||||
MultipartUploadCreateDTO uploadCreateDTO = MultipartUploadCreateDTO.builder()
|
||||
.bucketName(bo.getStorageBucket())
|
||||
.objectName(MinioPlusCommonUtil.getObjectName(bo.getFileMd5()))
|
||||
.build();
|
||||
// 存储桶
|
||||
bucketName = uploadCreateDTO.getBucketName();
|
||||
// 存储路径
|
||||
storagePath = uploadCreateDTO.getObjectName();
|
||||
// 文件key
|
||||
fileKey = bo.getFileKey();
|
||||
queryParams.put(UPLOAD_ID, bo.getUploadId());
|
||||
uploadCreateDTO.setUploadId(bo.getUploadId());
|
||||
// 开始位置
|
||||
long start = (long) (bo.getMissPartNum().get(0) - 1) * properties.getPart().getSize();
|
||||
for (int partNumber : bo.getMissPartNum()) {
|
||||
FileCheckResultVo.Part part = this.buildResultPart(uploadCreateDTO, queryParams, bo.getFileSize(), start, partNumber);
|
||||
// 更改下一次的开始位置
|
||||
start = start + properties.getPart().getSize();
|
||||
partList.add(part);
|
||||
}
|
||||
} else {
|
||||
// 获取文件后缀
|
||||
String suffix = FileUtil.getSuffix(bo.getFullFileName());
|
||||
if (CharSequenceUtil.isBlank(suffix)) {
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(), "无法获取文件的拓展名");
|
||||
}
|
||||
// 文件key
|
||||
fileKey = IdUtil.fastSimpleUUID();
|
||||
// 存储路径
|
||||
storagePath = MinioPlusCommonUtil.getPathByDate();
|
||||
|
||||
// 存储桶
|
||||
bucketName = StorageBucketEnums.getBucketByFileSuffix(suffix);
|
||||
// 创建桶
|
||||
minioRepository.createBucket(bucketName);
|
||||
// 如果是图片并开启了压缩,不需要分片,返回项目上的接口地址
|
||||
if (bucketName.equals(StorageBucketEnums.IMAGE.getCode()) && properties.getThumbnail().isEnable()) {
|
||||
|
||||
FileCheckResultVo.Part part = new FileCheckResultVo.Part();
|
||||
// 图片上传时,直接使用fileKey作为uploadId
|
||||
part.setUploadId(fileKey);
|
||||
part.setUrl("/storage/upload/image/"+fileKey);
|
||||
part.setStartPosition(0L);
|
||||
part.setEndPosition(bo.getFileSize());
|
||||
partList.add(part);
|
||||
|
||||
queryParams.put(UPLOAD_ID, fileKey);
|
||||
} else {
|
||||
// 创建分片请求,获取uploadId
|
||||
MultipartUploadCreateDTO uploadCreateDTO = MultipartUploadCreateDTO.builder()
|
||||
.bucketName(bucketName)
|
||||
.objectName(MinioPlusCommonUtil.getObjectName(bo.getFileMd5()))
|
||||
.build();
|
||||
CreateMultipartUploadResponse createMultipartUploadResponse = minioRepository.createMultipartUpload(uploadCreateDTO);
|
||||
queryParams.put(UPLOAD_ID, createMultipartUploadResponse.result().uploadId());
|
||||
uploadCreateDTO.setUploadId(createMultipartUploadResponse.result().uploadId());
|
||||
long start = 0;
|
||||
for (Integer partNumber = 1; partNumber <= chunkNum; partNumber++) {
|
||||
FileCheckResultVo.Part part = this.buildResultPart(uploadCreateDTO, queryParams, bo.getFileSize(), start, partNumber);
|
||||
// 更改下一次的开始位置
|
||||
start = start + properties.getPart().getSize();
|
||||
partList.add(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
CreateUploadUrlRespBO respBO = new CreateUploadUrlRespBO();
|
||||
CreateUploadUrlRespBO.MinioStorageBO minioStorageBO = new CreateUploadUrlRespBO.MinioStorageBO();
|
||||
// 桶名字
|
||||
minioStorageBO.setBucketName(bucketName);
|
||||
// 文件存储路径
|
||||
minioStorageBO.setStoragePath(storagePath);
|
||||
// 文件id-必填
|
||||
respBO.setFileKey(fileKey);
|
||||
// 分块数量-可选,分片后必须重新赋值 默认1
|
||||
respBO.setPartCount(chunkNum);
|
||||
// 切片上传任务id
|
||||
respBO.setUploadTaskId(queryParams.get(UPLOAD_ID));
|
||||
// minio存储信息-可选,使用minio存储引擎必填
|
||||
respBO.setMinioStorage(minioStorageBO);
|
||||
// 分片信息-必填
|
||||
respBO.setParts(partList);
|
||||
return respBO;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package org.liuxp.minioplus.core.repository;
|
||||
|
||||
import org.liuxp.minioplus.core.common.dto.FileMetadataInfoDTO;
|
||||
import org.liuxp.minioplus.core.common.dto.FileMetadataInfoSaveDTO;
|
||||
import org.liuxp.minioplus.core.common.dto.FileMetadataInfoUpdateDTO;
|
||||
import org.liuxp.minioplus.core.common.vo.FileMetadataInfoVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件元数据服务接口定义
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/06/26
|
||||
*/
|
||||
public interface MetadataRepository {
|
||||
|
||||
/**
|
||||
* 根据条件列表查询
|
||||
*
|
||||
* @param searchDTO 查询条件
|
||||
* @return List<FileMetadataInfoEntity> 列表结果集
|
||||
*/
|
||||
List<FileMetadataInfoVo> list(FileMetadataInfoDTO searchDTO);
|
||||
|
||||
/**
|
||||
* 根据条件单条查询
|
||||
*
|
||||
* @param searchDTO 查询条件
|
||||
* @return FileMetadataInfoEntity 单条结果
|
||||
*/
|
||||
FileMetadataInfoVo one(FileMetadataInfoDTO searchDTO);
|
||||
|
||||
/**
|
||||
* 新增
|
||||
*
|
||||
* @param saveDTO 数据实体
|
||||
* @return 执行结果
|
||||
*/
|
||||
FileMetadataInfoVo save(FileMetadataInfoSaveDTO saveDTO);
|
||||
|
||||
/**
|
||||
* 修改数据
|
||||
*
|
||||
* @param updateDTO 数据实体
|
||||
* @return 执行结果
|
||||
*/
|
||||
FileMetadataInfoVo update(FileMetadataInfoUpdateDTO updateDTO);
|
||||
|
||||
/**
|
||||
* 删除数据
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 执行结果
|
||||
*/
|
||||
Boolean remove(Long id);
|
||||
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package org.liuxp.minioplus.core.repository;
|
||||
|
||||
import io.minio.CreateMultipartUploadResponse;
|
||||
import io.minio.ListPartsResponse;
|
||||
import io.minio.ObjectWriteResponse;
|
||||
import org.liuxp.minioplus.core.common.dto.minio.MultipartUploadCreateDTO;
|
||||
import org.liuxp.minioplus.core.repository.impl.CustomMinioClient;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MinIO文件存储引擎接口定义
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/07/06
|
||||
*/
|
||||
public interface MinioRepository {
|
||||
|
||||
/**
|
||||
* 取得MinioClient
|
||||
* @return CustomMinioClient
|
||||
*/
|
||||
CustomMinioClient getClient();
|
||||
|
||||
/**
|
||||
* 创建桶
|
||||
* @param bucketName 桶名称
|
||||
*/
|
||||
void createBucket(String bucketName);
|
||||
|
||||
/**
|
||||
* 创建分片请求,获取uploadId
|
||||
* @param multipartUploadCreate 创建分片上传需要的参数
|
||||
* @return 分片结果
|
||||
*/
|
||||
CreateMultipartUploadResponse createMultipartUpload(MultipartUploadCreateDTO multipartUploadCreate);
|
||||
|
||||
/**
|
||||
* 合并分片
|
||||
* @param multipartUploadCreate 分片参数
|
||||
* @return 是否成功
|
||||
*/
|
||||
ObjectWriteResponse completeMultipartUpload(MultipartUploadCreateDTO multipartUploadCreate);
|
||||
|
||||
/**
|
||||
* 获取分片信息列表
|
||||
* @param multipartUploadCreate 分片参数
|
||||
* @return 分片信息
|
||||
*/
|
||||
ListPartsResponse listMultipart(MultipartUploadCreateDTO multipartUploadCreate);
|
||||
|
||||
/**
|
||||
* 获得对象上传的url
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称
|
||||
* @param queryParams 查询参数
|
||||
* @return {@link String}
|
||||
*/
|
||||
String getPresignedObjectUrl(String bucketName, String objectName, Map<String, String> queryParams);
|
||||
|
||||
/**
|
||||
* 取得下载链接
|
||||
* @param fileName 文件全名含扩展名
|
||||
* @param contentType 数据类型
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称含路径
|
||||
* @return 下载地址
|
||||
*/
|
||||
String getDownloadUrl(String fileName, String contentType, String bucketName, String objectName);
|
||||
|
||||
/**
|
||||
* 取得图片预览链接
|
||||
* @param contentType 数据类型
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称含路径
|
||||
* @return 图片预览链接
|
||||
*/
|
||||
String getPreviewUrl(String contentType, String bucketName, String objectName);
|
||||
|
||||
/**
|
||||
* 写入文件
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称含路径
|
||||
* @param stream 文件流
|
||||
* @param size 文件长度
|
||||
* @param contentType 文件类型
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean write(String bucketName, String objectName, InputStream stream, long size, String contentType);
|
||||
|
||||
/**
|
||||
* 读取文件
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称含路径
|
||||
* @return 文件流
|
||||
*/
|
||||
byte[] read(String bucketName, String objectName);
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称含路径
|
||||
*/
|
||||
void remove(String bucketName, String objectName);
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package org.liuxp.minioplus.core.repository.impl;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.minio.CreateMultipartUploadResponse;
|
||||
import io.minio.ListPartsResponse;
|
||||
import io.minio.MinioClient;
|
||||
import io.minio.ObjectWriteResponse;
|
||||
import io.minio.errors.*;
|
||||
import io.minio.messages.Part;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* 继承MinioClient重写
|
||||
* @author contact@liuxp.me
|
||||
* @since 2024/5/22
|
||||
*/
|
||||
public class CustomMinioClient extends MinioClient {
|
||||
|
||||
|
||||
public CustomMinioClient(MinioClient client) {
|
||||
super(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建分片上传请求
|
||||
*
|
||||
* @param bucketName 存储桶
|
||||
* @param region 区域
|
||||
* @param objectName 对象名
|
||||
* @param headers 消息头
|
||||
* @param extraQueryParams 额外查询参数
|
||||
*/
|
||||
@Override
|
||||
public CreateMultipartUploadResponse createMultipartUpload(String bucketName, String region, String objectName, Multimap<String, String> headers, Multimap<String, String> extraQueryParams) throws ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, IOException, InvalidKeyException, XmlParserException, InvalidResponseException, InternalException {
|
||||
return super.createMultipartUpload(bucketName, region, objectName, headers, extraQueryParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成分片上传,执行合并文件
|
||||
*
|
||||
* @param bucketName 存储桶
|
||||
* @param region 区域
|
||||
* @param objectName 对象名
|
||||
* @param uploadId 上传ID
|
||||
* @param parts 分片
|
||||
* @param extraHeaders 额外消息头
|
||||
* @param extraQueryParams 额外查询参数
|
||||
*/
|
||||
@Override
|
||||
public ObjectWriteResponse completeMultipartUpload(String bucketName, String region, String objectName, String uploadId, Part[] parts, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, IOException, InvalidKeyException, XmlParserException, InvalidResponseException, InternalException {
|
||||
return super.completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询分片数据
|
||||
*
|
||||
* @param bucketName 存储桶
|
||||
* @param region 区域
|
||||
* @param objectName 对象名
|
||||
* @param uploadId 上传ID
|
||||
* @param extraHeaders 额外消息头
|
||||
* @param extraQueryParams 额外查询参数
|
||||
*/
|
||||
@Override
|
||||
public ListPartsResponse listParts(String bucketName, String region, String objectName, Integer maxParts, Integer partNumberMarker, String uploadId, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, IOException, InvalidKeyException, XmlParserException, InvalidResponseException, InternalException {
|
||||
return super.listParts(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
package org.liuxp.minioplus.core.repository.impl;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import io.minio.*;
|
||||
import io.minio.http.Method;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.liuxp.minioplus.config.MinioPlusProperties;
|
||||
import org.liuxp.minioplus.core.common.dto.minio.MultipartUploadCreateDTO;
|
||||
import org.liuxp.minioplus.core.common.enums.ResponseCodeEnum;
|
||||
import org.liuxp.minioplus.core.common.exception.MinioPlusBusinessException;
|
||||
import org.liuxp.minioplus.core.repository.MinioRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* MinIO文件存储引擎接口定义实现类
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/07/06
|
||||
*/
|
||||
@Slf4j
|
||||
@Repository
|
||||
public class MinioRepositoryImpl implements MinioRepository {
|
||||
|
||||
private CustomMinioClient minioClient = null;
|
||||
|
||||
@Resource
|
||||
private MinioPlusProperties properties;
|
||||
|
||||
/**
|
||||
* 取得MinioClient
|
||||
* @return CustomMinioClient
|
||||
*/
|
||||
@Override
|
||||
public CustomMinioClient getClient(){
|
||||
|
||||
if(null==this.minioClient){
|
||||
MinioClient client = MinioClient.builder()
|
||||
.endpoint(properties.getBackend())
|
||||
.credentials(properties.getKey(), properties.getSecret())
|
||||
.build();
|
||||
this.minioClient = new CustomMinioClient(client);
|
||||
}
|
||||
|
||||
return this.minioClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建桶
|
||||
*
|
||||
* @param bucketName bucket名称
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows(Exception.class)
|
||||
public void createBucket(String bucketName) {
|
||||
boolean found = this.getClient().bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
|
||||
if (!found) {
|
||||
log.info("create bucket: [{}]", bucketName);
|
||||
this.getClient().makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CreateMultipartUploadResponse createMultipartUpload(MultipartUploadCreateDTO multipartUploadCreate) {
|
||||
try {
|
||||
return this.getClient().createMultipartUpload(multipartUploadCreate.getBucketName(), multipartUploadCreate.getRegion(), multipartUploadCreate.getObjectName(), multipartUploadCreate.getHeaders(), multipartUploadCreate.getExtraQueryParams());
|
||||
} catch (Exception e) {
|
||||
log.error("文件分片获取上传编号失败:{}", e.getMessage(), e);
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(), "获取上传编号失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并分片
|
||||
*
|
||||
* @param multipartUploadCreate 分片参数
|
||||
* @return 是否成功
|
||||
*/
|
||||
@Override
|
||||
public ObjectWriteResponse completeMultipartUpload(MultipartUploadCreateDTO multipartUploadCreate) {
|
||||
try {
|
||||
return this.getClient().completeMultipartUpload(multipartUploadCreate.getBucketName(), multipartUploadCreate.getRegion(), multipartUploadCreate.getObjectName(), multipartUploadCreate.getUploadId(), multipartUploadCreate.getParts(), multipartUploadCreate.getHeaders(), multipartUploadCreate.getExtraQueryParams());
|
||||
} catch (Exception e) {
|
||||
log.error("合并分片失败,uploadId:{},ObjectName:{},失败原因:{},", multipartUploadCreate.getUploadId(), multipartUploadCreate.getObjectName(), e.getMessage(), e);
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(), "合并分片失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取分片信息列表
|
||||
*
|
||||
* @param multipartUploadCreate 请求参数
|
||||
* @return {@link ListPartsResponse}
|
||||
*/
|
||||
@Override
|
||||
public ListPartsResponse listMultipart(MultipartUploadCreateDTO multipartUploadCreate) {
|
||||
try {
|
||||
return this.getClient().listParts(multipartUploadCreate.getBucketName(), multipartUploadCreate.getRegion(), multipartUploadCreate.getObjectName(), multipartUploadCreate.getMaxParts(), multipartUploadCreate.getPartNumberMarker(), multipartUploadCreate.getUploadId(), multipartUploadCreate.getHeaders(), multipartUploadCreate.getExtraQueryParams());
|
||||
} catch (Exception e) {
|
||||
log.error("查询分片失败:{}", e.getMessage(), e);
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(), "查询分片失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获得对象上传的url
|
||||
*
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称
|
||||
* @param queryParams 查询参数
|
||||
* @return {@link String}
|
||||
*/
|
||||
@Override
|
||||
public String getPresignedObjectUrl(String bucketName, String objectName, Map<String, String> queryParams) {
|
||||
try {
|
||||
return this.getClient().getPresignedObjectUrl(
|
||||
GetPresignedObjectUrlArgs.builder()
|
||||
.method(Method.PUT)
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.expiry(properties.getUploadExpiry(), TimeUnit.MINUTES)
|
||||
.extraQueryParams(queryParams)
|
||||
.build());
|
||||
} catch (Exception e) {
|
||||
log.error("获取预签名URL失败:{}", e.getMessage(), e);
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(), "获取预签名URL失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得下载链接
|
||||
* @param fileName 文件全名含扩展名
|
||||
* @param contentType 数据类型
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称含路径
|
||||
* @return 下载地址
|
||||
*/
|
||||
@Override
|
||||
public String getDownloadUrl(String fileName, String contentType, String bucketName, String objectName) {
|
||||
|
||||
Map<String, String> reqParams = new HashMap<>();
|
||||
reqParams.put("response-content-disposition", "attachment;filename=\""+fileName+"\"");
|
||||
reqParams.put("response-content-type", contentType);
|
||||
|
||||
try {
|
||||
return this.getClient().getPresignedObjectUrl(
|
||||
GetPresignedObjectUrlArgs.builder()
|
||||
.method(Method.GET)
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.expiry(properties.getDownloadExpiry(), TimeUnit.MINUTES)
|
||||
.extraQueryParams(reqParams)
|
||||
.build());
|
||||
} catch (Exception e) {
|
||||
log.error("获取预签名下载URL失败:{}", e.getMessage(), e);
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(), "获取预签名下载URL失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreviewUrl(String contentType, String bucketName, String objectName) {
|
||||
|
||||
Map<String, String> reqParams = new HashMap<>();
|
||||
reqParams.put("response-content-type", contentType);
|
||||
reqParams.put("response-content-disposition", "inline");
|
||||
|
||||
try {
|
||||
return this.getClient().getPresignedObjectUrl(
|
||||
GetPresignedObjectUrlArgs.builder()
|
||||
.method(Method.GET)
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.expiry(properties.getDownloadExpiry(), TimeUnit.MINUTES)
|
||||
.extraQueryParams(reqParams)
|
||||
.build());
|
||||
} catch (Exception e) {
|
||||
log.error("获取预签名预览URL失败:{}", e.getMessage(), e);
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(), "获取预签名预览URL失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean write(String bucketName,String objectName, InputStream stream, long size, String contentType) {
|
||||
|
||||
try{
|
||||
|
||||
// 检查存储桶是否已经存在
|
||||
boolean isExist = this.getClient().bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
|
||||
if (!isExist) {
|
||||
// 创建存储桶。
|
||||
this.getClient().makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
|
||||
|
||||
}
|
||||
|
||||
// 使用putObject上传一个文件到存储桶中。
|
||||
this.getClient().putObject(PutObjectArgs.builder()
|
||||
.bucket(bucketName)
|
||||
.object(objectName)
|
||||
.stream(stream,size,0L)
|
||||
.contentType(contentType)
|
||||
.build());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("文件写入失败",e);
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(),"文件写入失败");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read(String bucketName,String objectName) {
|
||||
|
||||
// 从远程MinIO服务读取文件流
|
||||
try (InputStream inputStream = this.getClient().getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build())) {
|
||||
// 文件流转换为字节码
|
||||
return IoUtil.readBytes(inputStream);
|
||||
} catch (Exception e) {
|
||||
log.error("文件读取失败",e);
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(),"文件读取失败");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String bucketName, String objectName) {
|
||||
try {
|
||||
this.getClient().removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
|
||||
} catch (Exception e) {
|
||||
log.error("删除失败",e);
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(),"删除失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package org.liuxp.minioplus.core.service;
|
||||
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import org.liuxp.minioplus.core.common.dto.FileMetadataInfoDTO;
|
||||
import org.liuxp.minioplus.core.common.dto.FileSaveDTO;
|
||||
import org.liuxp.minioplus.core.common.vo.FileMetadataInfoVo;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 存储组件Service层公共方法
|
||||
* 本类的方法是给后端业务研发提供的公共方法,后端业务研发可以@Resources引用本类使用
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/06/26
|
||||
*/
|
||||
public interface StorageService {
|
||||
|
||||
/**
|
||||
* 根据文件id查询
|
||||
* @param id 文件ID
|
||||
* @return 文件元数据信息
|
||||
*/
|
||||
FileMetadataInfoVo oneById(Long id);
|
||||
|
||||
/**
|
||||
* 根据文件key查询
|
||||
* @param key 文件key
|
||||
* @return 文件元数据信息
|
||||
*/
|
||||
FileMetadataInfoVo oneByKey(String key);
|
||||
|
||||
/**
|
||||
* 列表数据查询
|
||||
* @param fileMetadataInfo 查询入参
|
||||
* @return 文件元数据信息集合
|
||||
*/
|
||||
List<FileMetadataInfoVo> list(FileMetadataInfoDTO fileMetadataInfo);
|
||||
|
||||
/**
|
||||
* 创建文件
|
||||
* 尽量不要用本方法处理大文件,大文件建议使用前端直传
|
||||
* @param fileSaveDTO 文件保存入参
|
||||
* @param fileBytes 文件字节流
|
||||
* @return 文件元数据信息
|
||||
*/
|
||||
FileMetadataInfoVo createFile(FileSaveDTO fileSaveDTO, byte[] fileBytes);
|
||||
|
||||
/**
|
||||
* 创建文件
|
||||
* 尽量不要用本方法处理大文件,大文件建议使用前端直传
|
||||
* @param fileSaveDTO 文件保存入参
|
||||
* @param inputStream 文件输入字节流
|
||||
* @return 文件元数据信息
|
||||
*/
|
||||
FileMetadataInfoVo createFile(FileSaveDTO fileSaveDTO, InputStream inputStream);
|
||||
|
||||
/**
|
||||
* 创建文件
|
||||
* 尽量不要用本方法处理大文件,大文件建议使用前端直传
|
||||
* @param fileSaveDTO 文件保存入参
|
||||
* @param url 文件地址
|
||||
* @return 文件元数据信息
|
||||
*/
|
||||
FileMetadataInfoVo createFile(FileSaveDTO fileSaveDTO, String url);
|
||||
|
||||
/**
|
||||
* 根据文件key读取文件字节流
|
||||
* @param fileKey 文件key
|
||||
* @return 文件字节流
|
||||
*/
|
||||
Pair<FileMetadataInfoVo,byte[]> read(String fileKey);
|
||||
|
||||
/**
|
||||
* 根据文件key删除文件
|
||||
* @param fileKey 文件key
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean remove(String fileKey);
|
||||
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package org.liuxp.minioplus.core.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import org.liuxp.minioplus.config.MinioPlusProperties;
|
||||
import org.liuxp.minioplus.core.common.dto.FileMetadataInfoDTO;
|
||||
import org.liuxp.minioplus.core.common.dto.FileMetadataInfoSaveDTO;
|
||||
import org.liuxp.minioplus.core.common.dto.FileSaveDTO;
|
||||
import org.liuxp.minioplus.core.common.enums.ResponseCodeEnum;
|
||||
import org.liuxp.minioplus.core.common.enums.StorageBucketEnums;
|
||||
import org.liuxp.minioplus.core.common.exception.MinioPlusBusinessException;
|
||||
import org.liuxp.minioplus.core.common.utils.ContentTypeUtil;
|
||||
import org.liuxp.minioplus.core.common.utils.MinioPlusCommonUtil;
|
||||
import org.liuxp.minioplus.core.common.vo.FileMetadataInfoVo;
|
||||
import org.liuxp.minioplus.core.engine.StorageEngineService;
|
||||
import org.liuxp.minioplus.core.repository.MetadataRepository;
|
||||
import org.liuxp.minioplus.core.service.StorageService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 存储组件Service层公共方法实现类
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/06/26
|
||||
*/
|
||||
@Service
|
||||
public class StorageServiceImpl implements StorageService {
|
||||
|
||||
/**
|
||||
* 存储引擎Service接口定义
|
||||
*/
|
||||
@Resource
|
||||
StorageEngineService storageEngineService;
|
||||
|
||||
/**
|
||||
* 文件元数据服务接口定义
|
||||
*/
|
||||
@Resource
|
||||
MetadataRepository fileMetadataRepository;
|
||||
|
||||
/**
|
||||
* MinioPlus配置信息注入类
|
||||
*/
|
||||
@Resource
|
||||
MinioPlusProperties properties;
|
||||
|
||||
@Override
|
||||
public FileMetadataInfoVo oneById(Long id) {
|
||||
|
||||
FileMetadataInfoDTO fileMetadataInfo = new FileMetadataInfoDTO();
|
||||
fileMetadataInfo.setId(id);
|
||||
return fileMetadataRepository.one(fileMetadataInfo);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileMetadataInfoVo oneByKey(String key) {
|
||||
|
||||
FileMetadataInfoDTO fileMetadataInfo = new FileMetadataInfoDTO();
|
||||
fileMetadataInfo.setFileKey(key);
|
||||
return fileMetadataRepository.one(fileMetadataInfo);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileMetadataInfoVo> list(FileMetadataInfoDTO fileMetadataInfo) {
|
||||
// 列表查询,取得全部符合条件的数据
|
||||
return fileMetadataRepository.list(fileMetadataInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileMetadataInfoVo createFile(FileSaveDTO fileSaveDTO, byte[] fileBytes) {
|
||||
|
||||
// 组装文件保存入参
|
||||
FileMetadataInfoSaveDTO saveDTO = buildSaveDto(fileSaveDTO,fileBytes);
|
||||
|
||||
// 查询MinIO中是否存在相同MD5值的文件
|
||||
FileMetadataInfoDTO fileMetadataInfo = new FileMetadataInfoDTO();
|
||||
fileMetadataInfo.setFileMd5(saveDTO.getFileMd5());
|
||||
List<FileMetadataInfoVo> alreadyFileList = this.list(fileMetadataInfo);
|
||||
|
||||
boolean sameMd5 = false;
|
||||
|
||||
if(CollUtil.isNotEmpty(alreadyFileList)){
|
||||
for (FileMetadataInfoVo fileMetadataInfoVo : alreadyFileList) {
|
||||
if(Boolean.TRUE.equals(fileMetadataInfoVo.getIsFinished())){
|
||||
saveDTO.setStorageBucket(fileMetadataInfoVo.getStorageBucket());
|
||||
saveDTO.setStoragePath(fileMetadataInfoVo.getStoragePath());
|
||||
sameMd5 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (FileMetadataInfoVo fileMetadataInfoVo : alreadyFileList) {
|
||||
if(Boolean.TRUE.equals(fileMetadataInfoVo.getIsFinished())&&fileMetadataInfoVo.getCreateUser().equals(saveDTO.getCreateUser())){
|
||||
// 当存在该用户上传的相同md5值文件时,直接返回,元数据不再创建
|
||||
return fileMetadataInfoVo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!sameMd5){
|
||||
// 新文件时,执行写入逻辑
|
||||
storageEngineService.createFile(saveDTO, fileBytes);
|
||||
}
|
||||
|
||||
return fileMetadataRepository.save(saveDTO);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileMetadataInfoVo createFile(FileSaveDTO fileSaveDTO, InputStream inputStream) {
|
||||
return createFile(fileSaveDTO,IoUtil.readBytes(inputStream));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileMetadataInfoVo createFile(FileSaveDTO fileSaveDTO, String url) {
|
||||
// 请求文件
|
||||
HttpResponse httpResponse = HttpUtil.createGet(url).execute();
|
||||
// 获得输入流
|
||||
InputStream inputStream = httpResponse.bodyStream();
|
||||
// 调用处理函数
|
||||
return createFile(fileSaveDTO,inputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<FileMetadataInfoVo,byte[]> read(String fileKey) {
|
||||
return storageEngineService.read(fileKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean remove(String fileKey) {
|
||||
return storageEngineService.remove(fileKey);
|
||||
}
|
||||
|
||||
FileMetadataInfoSaveDTO buildSaveDto(FileSaveDTO fileSaveDTO, byte[] fileBytes){
|
||||
|
||||
if(null==fileBytes){
|
||||
throw new MinioPlusBusinessException(ResponseCodeEnum.FAIL.getCode(),"文件流不能为空");
|
||||
}
|
||||
// 计算文件MD5值
|
||||
String md5 = SecureUtil.md5().digestHex(fileBytes);
|
||||
// 生成UUID作为文件KEY
|
||||
String key = IdUtil.fastSimpleUUID();
|
||||
String suffix = FileUtil.getSuffix(fileSaveDTO.getFullFileName());
|
||||
String fileMimeType = ContentTypeUtil.getContentType(suffix);
|
||||
|
||||
// 根据文件后缀取得桶
|
||||
String storageBucket = StorageBucketEnums.getBucketByFileSuffix(suffix);
|
||||
|
||||
// 取得存储路径
|
||||
String storagePath = MinioPlusCommonUtil.getPathByDate();
|
||||
|
||||
// 是否存在缩略图
|
||||
Boolean isPreview = properties.getThumbnail().isEnable() && StorageBucketEnums.IMAGE.getCode().equals(storageBucket);
|
||||
|
||||
// 创建文件元数据信息
|
||||
FileMetadataInfoSaveDTO fileMetadataInfoSaveDTO = new FileMetadataInfoSaveDTO();
|
||||
fileMetadataInfoSaveDTO.setFileKey(key);
|
||||
fileMetadataInfoSaveDTO.setFileMd5(md5);
|
||||
fileMetadataInfoSaveDTO.setFileName(fileSaveDTO.getFullFileName());
|
||||
fileMetadataInfoSaveDTO.setFileMimeType(fileMimeType);
|
||||
fileMetadataInfoSaveDTO.setFileSuffix(suffix);
|
||||
fileMetadataInfoSaveDTO.setFileSize((long) fileBytes.length);
|
||||
fileMetadataInfoSaveDTO.setStorageBucket(storageBucket);
|
||||
fileMetadataInfoSaveDTO.setStoragePath(storagePath);
|
||||
fileMetadataInfoSaveDTO.setUploadTaskId("");
|
||||
fileMetadataInfoSaveDTO.setIsFinished(true);
|
||||
fileMetadataInfoSaveDTO.setIsPart(false);
|
||||
fileMetadataInfoSaveDTO.setPartNumber(0);
|
||||
fileMetadataInfoSaveDTO.setIsPreview(isPreview);
|
||||
fileMetadataInfoSaveDTO.setIsPrivate(fileSaveDTO.getIsPrivate());
|
||||
fileMetadataInfoSaveDTO.setCreateUser(fileSaveDTO.getCreateUser());
|
||||
fileMetadataInfoSaveDTO.setUpdateUser(fileSaveDTO.getCreateUser());
|
||||
|
||||
return fileMetadataInfoSaveDTO;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.liuxp.minioplus.core.service.impl.StorageServiceImpl,\
|
||||
org.liuxp.minioplus.core.engine.impl.StorageEngineServiceImpl,\
|
||||
org.liuxp.minioplus.core.repository.impl.MinioRepositoryImpl
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
本页面列举了MinIO支持的亚马逊 S3 API 接口列表。
|
||||
|
||||
**MinIO 强烈建议使用 [S3-Compatible SDK](https://min.io/docs/minio/linux/developers/minio-drivers.html#minio-drivers) 进行对象存储操作.**
|
||||
**MinIO 建议使用 [S3-Compatible SDK](https://min.io/docs/minio/linux/developers/minio-drivers.html#minio-drivers) 进行对象存储操作.**
|
||||
|
||||
## 对象API | Object APIs
|
||||
|
||||
|
@ -50,11 +50,11 @@ PutObjectAcl
|
|||
|
||||
## 桶API | Bucket APIs
|
||||
|
||||
* [CreateBucket](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html)
|
||||
* [DeleteBucket](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucket.html)
|
||||
* [DeleteBucketEncryption](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketEncryption.html)
|
||||
* [DeleteBucketTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketTagging.html)
|
||||
* [GetBucketEncryption](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketEncryption.html)
|
||||
* 创建存储桶 [CreateBucket](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html)
|
||||
* 删除存储桶 [DeleteBucket](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucket.html)
|
||||
* 重置存储桶的加密方式为SSE-S3 [DeleteBucketEncryption](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketEncryption.html)
|
||||
* 删除存储桶相关标签 [DeleteBucketTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketTagging.html)
|
||||
* 获取存储桶的加密方式 [GetBucketEncryption](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketEncryption.html)
|
||||
* [GetBucketLocation](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html)
|
||||
* [GetBucketTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketTagging.html)
|
||||
* [GetBucketVersioning](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketVersioning.html)
|
||||
|
@ -71,7 +71,7 @@ PutObjectAcl
|
|||
* [PutBucketReplication](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html)
|
||||
* [DeleteBucketReplication](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketReplication.html)
|
||||
|
||||
### Bucket Lifecycle
|
||||
### 桶生命周期 | Bucket Lifecycle
|
||||
|
||||
* [GetBucketLifecycle](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycle.html)
|
||||
* [GetBucketLifecycleConfiguration](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html)
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>minio-plus-parent</artifactId>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>minio-plus-spring-boot-config</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- 自定义的配置类生成元数据信息 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,16 @@
|
|||
package org.liuxp.minioplus.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* MinioPlusProperties自动配置类
|
||||
* @author contact@liuxp.me
|
||||
*/
|
||||
public class MinioPlusConfig {
|
||||
|
||||
@Bean
|
||||
public MinioPlusProperties tosProperties() {
|
||||
return new MinioPlusProperties();
|
||||
}
|
||||
|
||||
}
|
|
@ -10,6 +10,7 @@ import org.springframework.stereotype.Component;
|
|||
/**
|
||||
* MinioPlus配置类
|
||||
* @author contact@liuxp.me
|
||||
* @since 2024/05/22
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
|
@ -18,20 +19,9 @@ import org.springframework.stereotype.Component;
|
|||
public class MinioPlusProperties {
|
||||
|
||||
/**
|
||||
* 存储引擎
|
||||
* 枚举值 minio,local
|
||||
* MinIO引擎地址
|
||||
*/
|
||||
private String engine;
|
||||
|
||||
/**
|
||||
* 引擎地址,如配置为local则为本地根目录
|
||||
*/
|
||||
private String engineBackend;
|
||||
|
||||
/**
|
||||
* 文件元数据服务地址
|
||||
*/
|
||||
private String metadataBackend;
|
||||
private String backend;
|
||||
|
||||
/**
|
||||
* 存储引擎key
|
||||
|
@ -83,7 +73,7 @@ public class MinioPlusProperties {
|
|||
private boolean enable = true;
|
||||
|
||||
/**
|
||||
* 分块大小,配置单位为byte,默认为5242880
|
||||
* 分块大小,配置单位为byte,默认为5242880(5MB)
|
||||
*/
|
||||
private int size = 5242880;
|
||||
|
||||
|
@ -105,19 +95,10 @@ public class MinioPlusProperties {
|
|||
private boolean enable = true;
|
||||
|
||||
/**
|
||||
* 大缩略图尺寸,默认为600
|
||||
* 缩略图尺寸,默认为300
|
||||
*/
|
||||
private int sizeLarge = 600;
|
||||
private int size = 300;
|
||||
|
||||
/**
|
||||
* 中缩略图尺寸,默认为300
|
||||
*/
|
||||
private int sizeMedium = 300;
|
||||
|
||||
/**
|
||||
* 小缩略图尺寸,默认为100
|
||||
*/
|
||||
private int sizeSmall = 100;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.liuxp.minioplus.config.MinioPlusConfig
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-spring-boot-starter</artifactId>
|
||||
<version>${revision}</version>
|
||||
|
||||
|
||||
</project>
|
|
@ -0,0 +1,2 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.liuxp.data.security.core.aop.config.DataSecurityAopConfiguration
|
|
@ -0,0 +1,258 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>minio-plus-parent</name>
|
||||
<description>MinIO-Plus 是一个 MinIO 的二次封装与增强工具,在 MinIO 的基础上只做增强,不侵入 MinIO 代码,只为简化开发、提高效率而生。成为 MinIO 在项目中落地的润滑剂。</description>
|
||||
<url>https://github.com/lxp135/minio-plus</url>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>liuxp</id>
|
||||
<name>刘小平</name>
|
||||
<email>contact@liuxp.me</email>
|
||||
<roles>
|
||||
<role>Java Development Engineer</role>
|
||||
</roles>
|
||||
<timezone>2023-10-19 20:00:00</timezone>
|
||||
</developer>
|
||||
</developers>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The Apache Software License, Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
</license>
|
||||
</licenses>
|
||||
<scm>
|
||||
<connection>scm:git@github.com:lxp135/minio-plus.git</connection>
|
||||
<developerConnection>scm:git@github.com:lxp135/minio-plus.git</developerConnection>
|
||||
<url>git@github.com:lxp135/minio-plus.git</url>
|
||||
</scm>
|
||||
|
||||
<modules>
|
||||
<module>minio-plus-core</module>
|
||||
<module>minio-plus-spring-boot-config</module>
|
||||
<module>minio-plus-spring-boot-starter</module>
|
||||
<module>minio-plus-application</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<maven-source-plugin.version>3.0.1</maven-source-plugin.version>
|
||||
<revision>1.0.0-SNAPSHOT</revision>
|
||||
<spring-boot.version>2.6.11</spring-boot.version>
|
||||
<spring-cloud.version>2021.0.5</spring-cloud.version>
|
||||
<spring-cloud-alibaba.version>2021.0.4.0</spring-cloud-alibaba.version>
|
||||
<mybatisplus.version>3.5.3.1</mybatisplus.version>
|
||||
<lombok.version>1.18.24</lombok.version>
|
||||
<hutool.version>5.8.15</hutool.version>
|
||||
<knife4j.version>2.0.2</knife4j.version>
|
||||
<swagger.version>1.5.22</swagger.version>
|
||||
<minio.version>8.3.3</minio.version>
|
||||
<thumbnailator.version>0.4.8</thumbnailator.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
|
||||
<version>${spring-cloud-alibaba.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<!-- spring-boot-dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>${mybatisplus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
<!--Swagger工具包 knife4j -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
<version>${knife4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-annotations</artifactId>
|
||||
<version>${knife4j.version}</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/io.swagger/swagger-annotations -->
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
</dependency>
|
||||
<!-- mybatis -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus</artifactId>
|
||||
<version>${mybatisplus.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- minio -->
|
||||
<dependency>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
<version>${minio.version}</version>
|
||||
</dependency>
|
||||
<!-- google图片压缩 -->
|
||||
<dependency>
|
||||
<groupId>net.coobird</groupId>
|
||||
<artifactId>thumbnailator</artifactId>
|
||||
<version>${thumbnailator.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-spring-boot-config</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-spring-boot-starter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-spring-mvc</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>buildnumber-maven-plugin</artifactId>
|
||||
<version>1.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>generate-buildNumber</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>create</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<scmConnectionUrl>scm:git:</scmConnectionUrl>
|
||||
<shortRevisionLength>8</shortRevisionLength>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>generate-timestamp</id>
|
||||
<phase>validate</phase>
|
||||
<goals>
|
||||
<goal>create-timestamp</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<timestampPropertyName>buildTime</timestampPropertyName>
|
||||
<timestampFormat>yyyyMMdd</timestampFormat>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<!-- <!– 要将源码放上去,需要加入这个插件 –>-->
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.apache.maven.plugins</groupId>-->
|
||||
<!-- <artifactId>maven-source-plugin</artifactId>-->
|
||||
<!-- <version>${maven-source-plugin.version}</version>-->
|
||||
<!-- <configuration>-->
|
||||
<!-- <attach>true</attach>-->
|
||||
<!-- </configuration>-->
|
||||
<!-- <executions>-->
|
||||
<!-- <execution>-->
|
||||
<!-- <phase>compile</phase>-->
|
||||
<!-- <goals>-->
|
||||
<!-- <goal>jar</goal>-->
|
||||
<!-- </goals>-->
|
||||
<!-- </execution>-->
|
||||
<!-- </executions>-->
|
||||
<!-- </plugin>-->
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.jacoco</groupId>-->
|
||||
<!-- <artifactId>jacoco-maven-plugin</artifactId>-->
|
||||
<!-- <version>${jacoco.version}</version>-->
|
||||
<!-- <executions>-->
|
||||
<!-- <!–first execution : for preparing JaCoCo runtime agent–>-->
|
||||
<!-- <execution>-->
|
||||
<!-- <id>prepare-agent</id>-->
|
||||
<!-- <goals>-->
|
||||
<!-- <goal>prepare-agent</goal>-->
|
||||
<!-- </goals>-->
|
||||
<!-- </execution>-->
|
||||
<!-- </executions>-->
|
||||
<!-- </plugin>-->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>flatten-maven-plugin</artifactId>
|
||||
<version>1.3.0</version>
|
||||
<configuration>
|
||||
<!--是否更新pom文件,记得设置为true,不然无法更新module里的pom版本号-->
|
||||
<updatePomFile>true</updatePomFile>
|
||||
<flattenMode>resolveCiFriendliesOnly</flattenMode>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>flatten</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>flatten</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>flatten.clean</id>
|
||||
<phase>clean</phase>
|
||||
<goals>
|
||||
<goal>clean</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
Loading…
Reference in New Issue