初始化代码提交。

This commit is contained in:
刘小平 2024-05-23 18:24:31 +08:00
parent 6bf1a31121
commit 7623f8ea96
48 changed files with 4509 additions and 33 deletions

View File

@ -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)

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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> {
}

View File

@ -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

View File

@ -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>

62
minio-plus-core/pom.xml Normal file
View File

@ -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>

View File

@ -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**************************************************/
}

View File

@ -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;
}
}

View File

@ -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<>();
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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 {
/**
* 桶策略枚举类
* 文档documenttxtrtfofddocdocxxlsxlsxpptpptxpdf
* 压缩包packageziprar7ztarwimgzbz2
* 音频 audio mp3wavflacaccoggaiffm4awmamidi
* 视频 video mp4avimovwmvflvmkvmpegmpg rmvb
* 图片 原始 image jpegjpgpngbmpwebpgif
* 图片 缩略图 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;
}
}

View File

@ -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 "";
}
}

View File

@ -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;
}
}

View File

@ -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";
}
}
}

View File

@ -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;
}
}

View File

@ -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<>();
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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(),"删除失败");
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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)

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.liuxp.minioplus.config.MinioPlusConfig

View File

@ -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>

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.liuxp.data.security.core.aop.config.DataSecurityAopConfiguration

258
pom.xml Normal file
View File

@ -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>
<!-- &lt;!&ndash; 要将源码放上去,需要加入这个插件 &ndash;&gt;-->
<!-- <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>-->
<!-- &lt;!&ndash;first execution : for preparing JaCoCo runtime agent&ndash;&gt;-->
<!-- <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>