一、FastDFS
1. Maven依赖
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>1.26.5</version>
</dependency>
2. 配置application.yml文件
fdfs: # 链接超时 connect-timeout: 60 # 读取时间 so-timeout: 60 # 生成缩略图参数 thumb-image: width: 150 height: 150 tracker-list: 127.0.0.1:22122
3. FastDFS配置类
@Configuration @Import(FdfsClientConfig.class) // Jmx重复注册bean的问题 @EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING) public class DfsConfig { }
可参考:https://blog.51cto.com/14439672/2434896
二、MinIO
简介:MinIO是一个非常轻量的对象存储服务,非常适合存储非结构化的数据,比如图片、视频、日志文件、容器镜像等,文件最大是5TB,这对一般的文件存储足够了,而且功能强大,还提供了可视化界面。
1. pom.xml Maven依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.0.3</version>
</dependency>
2. application.yml 配置文件
minio:
endpoint: http://localhost:9000
access-key: minio
secret-key: minio
3. 配置类
package com.ruhuanxingyun.minio.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @description: Minio文件服务参数
* @author: ruphie
* @date: Create in 2020/8/26 22:03
* @company: ruhuanxingyun
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
/**
* 对象存储服务的URL
*/
private String endpoint;
/**
* 用户ID,可以唯一标识的账户
*/
private String accessKey;
/**
* 账户的密码
*/
private String secretKey;
}
4. 配置类
package com.ruhuanxingyun.minio.config;
import com.ruhuanxingyun.minio.common.Constants;
import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @description: Minio客户端
* @author: ruphie
* @date: Create in 2020/8/26 22:08
* @company: ruhuanxingyun
*/
@Configuration
public class MinioClientConfig {
@Autowired
private MinioProperties minioProperties;
@Bean
public MinioClient minioClient() {
MinioClient.Builder builder = MinioClient.builder();
MinioClient minioClient = builder.endpoint(minioProperties.getEndpoint())
.credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
.build();
try {
for (String bucket : Constants.BUCKETS) {
BucketExistsArgs args = BucketExistsArgs.builder()
.bucket(bucket)
.build();
// 检查存储桶是否存在
if (!minioClient.bucketExists(args)) {
MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder()
.bucket(bucket)
.build();
// 创建一个新的存储桶
minioClient.makeBucket(makeBucketArgs);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return minioClient;
}
}
5. 文件服务类
package com.ruhuanxingyun.minio.service;
import cn.hutool.core.util.StrUtil;
import com.ruhuanxingyun.minio.common.Constants;
import com.ruhuanxingyun.minio.util.FileUtils;
import io.minio.MinioClient;
import io.minio.ObjectWriteArgs;
import io.minio.PutObjectArgs;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
/**
* @description: 上传文件 服务层
* @author: ruphie
* @date: Create in 2020/8/26 22:15
* @company: ruhuanxingyun
*/
@Service
public class FileUploadService {
@Autowired
private MinioClient minioClient;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 上传文件
*
* @param file 文件
* @param fileMd5 文件MD5值
* @return 上传结果
*/
public String upload(MultipartFile file, String fileMd5) {
String md5 = FileUtils.getFileMd5(file);
// 前端兼容MD5值大小写,设备端传递小写
if (!StrUtil.equalsIgnoreCase(fileMd5, md5)) {
return "上传文件MD5值不匹配!";
}
InputStream is = null;
try {
is = file.getInputStream();
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(Constants.BUCKETS[0])
.object(file.getOriginalFilename())
.stream(is, file.getSize(), ObjectWriteArgs.MAX_PART_SIZE)
.contentType(file.getContentType())
.build();
// 通过InputStream上传对象
minioClient.putObject(putObjectArgs);
} catch (Exception e) {
e.printStackTrace();
return "文件上传失败!";
} finally {
IOUtils.closeQuietly(is);
}
return "文件上传成功!";
}
/**
* 重置文件上传进度
*
* @param uuid 唯一标识符
*/
public void reset(String uuid) {
String key = String.format("%s:%s", Constants.FILE_UPLOAD_PROGRESS, uuid);
stringRedisTemplate.delete(key);
}
/**
* 文件上传进度
*
* @param uuid 唯一标识符
* @return 进度
*/
public int progress(String uuid) {
String key = String.format("%s:%s", Constants.FILE_UPLOAD_PROGRESS, uuid);
String percent = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotEmpty(percent)) {
// 上传完全
if (StrUtil.equals("100", percent)) {
stringRedisTemplate.delete(key);
}
return Integer.parseInt(percent);
}
return 0;
}
}
package com.ruhuanxingyun.minio.service;
import com.ruhuanxingyun.minio.common.Constants;
import com.ruhuanxingyun.minio.util.FileUtils;
import io.minio.*;
import io.minio.http.Method;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.InputStream;
/**
* @description: 下载文件 服务层
* @author: ruphie
* @date: Create in 2020/12/8 21:46
* @company: ruhuanxingyun
*/
@Service
public class FileDownloadService {
@Autowired
private MinioClient minioClient;
/**
* 下载文件URL
*
* @param filePath 文件路径
* @return URL
*/
public String downloadUrl(String filePath) {
String bucketName = Constants.BUCKETS[0];
String objectName = FileUtils.getObjectName(filePath, bucketName);
GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
.expiry(GetPresignedObjectUrlArgs.DEFAULT_EXPIRY_TIME)
.build();
try {
return minioClient.getPresignedObjectUrl(args);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 下载文件
*
* @param filePath 文件路径
* @param response 响应流
*/
public void download(String filePath, HttpServletResponse response) {
String bucketName = Constants.BUCKETS[0];
String objectName = FileUtils.getObjectName(filePath, bucketName);
StatObjectArgs args = StatObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build();
GetObjectArgs getObjectArgs = GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build();
InputStream is = null;
BufferedInputStream ins = null;
BufferedOutputStream bos = null;
try {
// 获取对象的元数据
ObjectStat objectStat = minioClient.statObject(args);
is = minioClient.getObject(getObjectArgs);
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-store");
response.setHeader(HttpHeaders.PRAGMA, "no-cache");
response.setDateHeader(HttpHeaders.EXPIRES, 0);
response.setContentType(objectStat.contentType());
response.setContentLengthLong(objectStat.length());
response.setHeader("Content-Disposition", "attachment;filename=" + objectStat.name());
ins = new BufferedInputStream(is);
bos = new BufferedOutputStream(response.getOutputStream());
byte[] buffer = new byte[1024 * 1024];
int bytesRead = -1;
while ((bytesRead = ins.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
bos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(bos);
IOUtils.closeQuietly(ins);
IOUtils.closeQuietly(is);
}
}
}
package com.ruhuanxingyun.minio.service;
import com.ruhuanxingyun.minio.common.Constants;
import com.ruhuanxingyun.minio.util.FileUtils;
import io.minio.MinioClient;
import io.minio.RemoveObjectArgs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @description: 删除文件 服务层
* @author: ruphie
* @date: Create in 2020/12/8 21:49
* @company: ruhuanxingyun
*/
@Service
public class FileDeleteService {
@Autowired
private MinioClient minioClient;
/**
* 删除文件
*
* @param filePath 文件路径
* @return 删除状态
*/
public String delete(String filePath) {
String bucketName = Constants.BUCKETS[0];
String objectName = FileUtils.getObjectName(filePath, bucketName);
RemoveObjectArgs args = RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build();
try {
minioClient.removeObject(args);
return "删除文件成功!";
} catch (Exception e) {
e.printStackTrace();
return "删除文件失败!";
}
}
}
package com.ruhuanxingyun.minio.util;
import org.springframework.web.multipart.MultipartFile;
import java.math.BigInteger;
import java.security.MessageDigest;
/**
* @description: 文件工具类
* @author: ruphie
* @date: Create in 2020/12/8 22:25
* @company: ruhuanxingyun
*/
public class FileUtils {
/**
* 获取对象名
*
* @param filePath 文件路径
* @param bucket 存储桶
* @return 对象名
*/
public static String getObjectName(String filePath, String bucket) {
return filePath.replace(String.format("%s/", bucket), "");
}
/**
* 获取文件MD5值
*
* @param file 文件
* @return MD5
*/
public static String getFileMd5(MultipartFile file) {
try {
byte[] bytes = file.getBytes();
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] digest = messageDigest.digest(bytes);
return new BigInteger(1, digest).toString(16);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
6. 断点续传
可参考:MinIO官方文档

