SpringBoot 整合FastDFS/MinIO分布式文件系统
一、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. 断点续传:https://www.cnblogs.com/liyhbk/p/16810243.html
可参考:MinIO官方文档