- 上传下载业务实现
// 文件上传 @PostMapping("/image/upload") String upload(@RequestPart("userImage") MultipartFile userImage) throws Exception { fileService.putObject("image", userImage); return "success"; } /** * 下载 * * @param fileId * @param response * @throws Exception */ @GetMapping("/download/{fileId}") public void download(@PathVariable("fileId") String fileId, HttpServletResponse response) throws Exception { fileService.getObject(fileId, response); } 复制代码
- controller接收到前端上传的文件,马上调用接口fileService的上传接口,把文件上传到minio服务;
import com.example.awesomespring.bo.FileUploadResult; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; /** * @author zouwei * @className FileService * @date: 2022/8/4 下午3:45 * @description: */ public interface FileService { // 上传功能,把文件提交到minio服务,并把提交结果持久化 FileUploadResult putObject(String dirs, MultipartFile file) throws Exception; // 下载功能,把minio服务中的文件下载并写入响应 void getObject(String fileId, HttpServletResponse response) throws Exception; } 复制代码
- 我们来看看具体实现:
import lombok.Data; /** * @author zouwei * @className FileUploadResult * @date: 2022/8/4 下午11:59 * @description: */ @Data public class FileUploadResult { // 文件桶名称 private String bucketName; // 文件存储的路径 private String filePath; // 文件名称 private String filename; // 文件上传类型 private String contentType; // 文件大小 private int length; } 复制代码
import com.example.awesomespring.bo.FileUploadResult; import com.example.awesomespring.config.MinioConfigProperties; import com.example.awesomespring.dao.entity.FileUploadRecord; import com.example.awesomespring.dao.mapper.FileUploadRecordMapper; import com.example.awesomespring.service.FileService; import io.minio.*; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Paths; import java.util.Date; import java.util.UUID; /** * @author zouwei * @className FileServiceImpl * @date: 2022/8/4 下午3:45 * @description: */ @Service public class FileServiceImpl implements FileService { @Autowired private MinioClient client; @Autowired private MinioConfigProperties properties; @Autowired private FileUploadRecordMapper fileUploadRecordMapper; /** * 上传文件 * * @param dirs 目标文件夹; 比如image、video * @param file 上传的文件 * @return * @throws Exception */ @Override public FileUploadResult putObject(String dirs, MultipartFile file) throws Exception { FileUploadResult result = putObject(dirs, file, true); // 保存到数据库 FileUploadRecord row = new FileUploadRecord(); row.setFileName(result.getFilename()); row.setCreateTime(new Date()); row.setFilePath(result.getFilePath()); row.setContentType(result.getContentType()); row.setBucketName(result.getBucketName()); row.setId(UUID.randomUUID().toString()); row.setSize(result.getLength()); fileUploadRecordMapper.insert(row); return result; } /** * 下载文件并写入响应中 * * @param fileId * @param response * @throws Exception */ @Override public void getObject(String fileId, HttpServletResponse response) throws Exception { FileUploadRecord row = fileUploadRecordMapper.selectByPrimaryKey(fileId); String path = row.getFilePath(); // 构建下载参数 GetObjectArgs objectArgs = GetObjectArgs.builder() .bucket(bucketName()) .object(path) .build(); // 下载并写入响应中 try (InputStream input = client.getObject(objectArgs); OutputStream outputStream = response.getOutputStream()) { response.setContentType(row.getContentType()); response.setHeader("Accept-Ranges", "bytes"); response.setHeader("Content-Length", String.valueOf(length)); response.setHeader("Content-disposition", "attachment; filename=" + filename); outputStream.write(input.readAllBytes()); outputStream.flush(); } catch (Exception e) { // 建议包装成自定义异常,以便自定义异常处理捕获到 throw e; } } /** * 获取文件桶 * * @return */ private String bucketName() { return properties.getBucketName(); } /** * 如果文件桶不存在就创建 * * @param bucketName * @throws Exception */ private void createIfNotExistBucket(String bucketName) throws Exception { if (!client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) { client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); } } /** * 这个实现只针对于minio服务实现,所以不建议以接口暴露给外部调用 * * @param dirs * @param file * @param createIfNotExistBucket * @return * @throws Exception */ private FileUploadResult putObject(final String dirs, MultipartFile file, boolean createIfNotExistBucket) throws Exception { // 获取桶名称 final String bucketName = bucketName(); // 获取文件名称 final String filename = file.getOriginalFilename(); // 获取文件类型 final String contentType = file.getContentType(); // 拼接路径;因为不会把所有文件直接放在桶下面 String path = filename; if (StringUtils.isNotBlank(dirs)) { path = Paths.get(dirs, filename).toString(); } // 从上传来的文件中取流 try (InputStream fileStream = file.getInputStream()) { // 如果要求文件桶不存在就创建 if (createIfNotExistBucket) { createIfNotExistBucket(bucketName); } int length = fileStream.available(); // 准备好文件上传的参数 PutObjectArgs objectArgs = PutObjectArgs.builder() .bucket(bucketName) .object(path) .contentType(contentType) .stream(fileStream, length, -1) .build(); // 上传文件 client.putObject(objectArgs); // 返回上传结果 FileUploadResult result = new FileUploadResult(); result.setContentType(contentType); result.setFilePath(dirs); result.setFilename(filename); result.setBucketName(bucketName); result.setLength(length); return result; } catch (Exception e) { // 建议包装成自定义异常,以便自定义异常处理捕获到 throw e; } } } 复制代码
- 以上代码有几个需要注意的点:
1.文件上传成功到minio服务中后,并不会返回统一的哈希等唯一标识字段,所以我建议我们需要把上传结果保存一条记录到数据库。
2.我们提供给外面的下载链接应该尽可能的简单,比如:
http://127.0.0.1/download/{fileId}
;所以我在设计上传和下载的时候,上传结果用fileId来表示一个文件,下载的时候也只需要使用fileId就可以下载目标文件。3.在文件上传下载处理过程中,产生的Exception应该全部转换成自定义的异常抛出去,这样的话,可以方便后续的统一异常处理逻辑一次性解决服务端的异常问题。
至此,基于minio对象存储中间件的集成就完成了,小伙伴们可以根据自己的实际情况修改文件存储逻辑的具体实现。