SpringBoot - 集成MongoDB實現文件上傳

前言

  • 記錄下SpringBoot集成MongoDB實現文件上傳的步驟

  • MongoDB - 5.0.6安裝包

鏈接:https://pan.baidu.com/s/1lCcPvYYNWncb6lbvrZdeVg 
提取碼:0tf1

環境

SpringBoot - 2.5.12
MongoDB - 5.0.6

代碼實現

  • pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mongodb -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<!-- hutool -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>4.5.1</version>
</dependency>
  • application.yml
server:
  port: 31091

spring:
  servlet:
    multipart:
      max-file-size: 100MB
  data:
    mongodb:
      host: 127.0.0.1
      port: 27017
      database: admin
      username: root
      password: sunday

fileUploadService:
  impl: fileMongoServiceImpl
  • MongoConfig.java
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSBuckets;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description MongoDB配置類
 */
@Configuration
public class MongoConfig {
    /**
     * 數據庫配置信息
     */
    @Value("${spring.data.mongodb.database}")
    private String db;

    /**
     * GridFSBucket用於打開下載流
     * @param mongoClient
     * @return
     */
    @Bean
    public GridFSBucket getGridFSBucket(MongoClient mongoClient){
        MongoDatabase mongoDatabase = mongoClient.getDatabase(db);
        return GridFSBuckets.create(mongoDatabase);
    }
}
  • FileUploadController.java
import com.coisini.mongodb.model.ResponseMessage;
import com.coisini.mongodb.service.FileUploadService;
import com.coisini.mongodb.vo.FileExportVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;

/**
 * @Description 文件上傳接口
 */
@Slf4j
@RestController
@RequestMapping("/file")
public class FileUploadController {

    /**
     * 文件上傳實現類
     */
    @Resource(name="${fileUploadService.impl}")
    private FileUploadService fileUploadService;

    /**
     * 文件上傳
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public ResponseMessage<?> uploadFile(@RequestParam(value = "file") MultipartFile file) {
        try {
            return ResponseMessage.ok("上傳成功", fileUploadService.uploadFile(file));
        } catch (Exception e) {
            log.error("文件上傳失敗:", e);
            return ResponseMessage.error(e.getMessage());
        }
    }

    /**
     * 多文件上傳
     * @param files
     * @return
     */
    @PostMapping("/uploadFiles")
    public ResponseMessage<?> uploadFile(@RequestParam(value = "files") List<MultipartFile> files) {
        try {
            return ResponseMessage.ok("上傳成功", fileUploadService.uploadFiles(files));
        } catch (Exception e) {
            return ResponseMessage.error(e.getMessage());
        }
    }

    /**
     * 文件下載
     * @param fileId
     * @return
     */
    @GetMapping("/download/{fileId}")
    public ResponseEntity<Object> fileDownload(@PathVariable(name = "fileId") String fileId) {
        FileExportVo fileExportVo = fileUploadService.downloadFile(fileId);

        if (Objects.nonNull(fileExportVo)) {
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "fileName=\"" + fileExportVo.getFileName() + "\"")
                    .header(HttpHeaders.CONTENT_TYPE, fileExportVo.getContentType())
                    .header(HttpHeaders.CONTENT_LENGTH, fileExportVo.getFileSize() + "").header("Connection", "close")
                    .body(fileExportVo.getData());
        } else {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("file does not exist");
        }
    }

    /**
     * 文件刪除
     * @param fileId
     * @return
     */
    @DeleteMapping("/remove/{fileId}")
    public ResponseMessage<?> removeFile(@PathVariable(name = "fileId") String fileId) {
        fileUploadService.removeFile(fileId);
        return ResponseMessage.ok("刪除成功");
    }

}
  • FileUploadService.java
import com.coisini.mongodb.vo.FileExportVo;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;

/**
 * @Description 文件上傳接口
 */
public interface FileUploadService {

    /**
     * 文件上傳
     * @param file
     * @return
     */
    FileExportVo uploadFile(MultipartFile file) throws Exception;

    /**
     * 多文件上傳
     * @param files
     * @return
     */
    List<FileExportVo> uploadFiles(List<MultipartFile> files);

    /**
     * 文件下載
     * @param fileId
     * @return
     */
    FileExportVo downloadFile(String fileId);

    /**
     * 文件刪除
     * @param fileId
     */
    void removeFile(String fileId);

}
  • FileMongoServiceImpl.java
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import com.coisini.mongodb.model.MongoFile;
import com.coisini.mongodb.repository.MongoFileRepository;
import com.coisini.mongodb.service.FileUploadService;
import com.coisini.mongodb.util.MD5Util;
import com.coisini.mongodb.vo.FileExportVo;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.model.GridFSFile;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.Binary;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @Description MongoDB文件上傳實現類
 */
@Slf4j
@Service("fileMongoServiceImpl")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class FileMongoServiceImpl implements FileUploadService {

    private final MongoFileRepository mongoFileRepository;
    private final MongoTemplate mongoTemplate;
    private final GridFsTemplate gridFsTemplate;
    private final GridFSBucket gridFSBucket;

    /**
     * 多文件上傳
     * @param files
     * @return
     */
    @Override
    public List<FileExportVo> uploadFiles(List<MultipartFile> files) {

        return files.stream().map(file -> {
            try {
                return this.uploadFile(file);
            } catch (Exception e) {
                log.error("文件上傳失敗", e);
                return null;
            }
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

    /**
     * 文件上傳
     * @param file
     * @return
     * @throws Exception
     */
    @Override
    public FileExportVo uploadFile(MultipartFile file) throws Exception {
        if (file.getSize() > 16777216) {
            return this.saveGridFsFile(file);
        } else {
            return this.saveBinaryFile(file);
        }
    }

    /**
     * 文件下載
     * @param fileId
     * @return
     */
    @Override
    public FileExportVo downloadFile(String fileId) {
        Optional<MongoFile> option = this.getBinaryFileById(fileId);

        if (option.isPresent()) {
            MongoFile mongoFile = option.get();
            if(Objects.isNull(mongoFile.getContent())){
                option = this.getGridFsFileById(fileId);
            }
        }

        return option.map(FileExportVo::new).orElse(null);
    }

    /**
     * 文件刪除
     * @param fileId
     */
    @Override
    public void removeFile(String fileId) {
        Optional<MongoFile> option = this.getBinaryFileById(fileId);

        if (option.isPresent()) {
            if (Objects.nonNull(option.get().getGridFsId())) {
                this.removeGridFsFile(fileId);
            } else {
                this.removeBinaryFile(fileId);
            }
        }
    }

    /**
     * 刪除Binary文件
     * @param fileId
     */
    public void removeBinaryFile(String fileId) {
        mongoFileRepository.deleteById(fileId);
    }

    /**
     * 刪除GridFs文件
     * @param fileId
     */
    public void removeGridFsFile(String fileId) {
        // TODO 根據id查詢文件
        MongoFile mongoFile = mongoTemplate.findById(fileId, MongoFile.class );
        if(Objects.nonNull(mongoFile)){
            // TODO 根據文件ID刪除fs.files和fs.chunks中的記錄
            Query deleteFileQuery = new Query().addCriteria(Criteria.where("filename").is(mongoFile.getGridFsId()));
            gridFsTemplate.delete(deleteFileQuery);
            // TODO 刪除集合mongoFile中的數據
            Query deleteQuery = new Query(Criteria.where("id").is(fileId));
            mongoTemplate.remove(deleteQuery, MongoFile.class);
        }
    }

    /**
     * 保存Binary文件(小文件)
     * @param file
     * @return
     * @throws Exception
     */
    public FileExportVo saveBinaryFile(MultipartFile file) throws Exception {

        String suffix = getFileSuffix(file);

        MongoFile mongoFile = mongoFileRepository.save(
                MongoFile.builder()
                        .fileName(file.getOriginalFilename())
                        .fileSize(file.getSize())
                        .content(new Binary(file.getBytes()))
                        .contentType(file.getContentType())
                        .uploadDate(new Date())
                        .suffix(suffix)
                        .md5(MD5Util.getMD5(file.getInputStream()))
                        .build()
        );

        return new FileExportVo(mongoFile);
    }

    /**
     * 保存GridFs文件(大文件)
     * @param file
     * @return
     * @throws Exception
     */
    public FileExportVo saveGridFsFile(MultipartFile file) throws Exception {
        String suffix = getFileSuffix(file);

        String gridFsId = this.storeFileToGridFS(file.getInputStream(), file.getContentType());

        MongoFile mongoFile = mongoTemplate.save(
                MongoFile.builder()
                        .fileName(file.getOriginalFilename())
                        .fileSize(file.getSize())
                        .contentType(file.getContentType())
                        .uploadDate(new Date())
                        .suffix(suffix)
                        .md5(MD5Util.getMD5(file.getInputStream()))
                        .gridFsId(gridFsId)
                        .build()
        );

        return new FileExportVo(mongoFile);
    }

    /**
     * 上傳文件到Mongodb的GridFs中
     * @param in
     * @param contentType
     * @return
     */
    public String storeFileToGridFS(InputStream in, String contentType){
        String gridFsId = IdUtil.simpleUUID();
        // TODO 將文件存儲進GridFS中
        gridFsTemplate.store(in, gridFsId , contentType);
        return gridFsId;
    }

    /**
     * 獲取Binary文件
     * @param id
     * @return
     */
    public Optional<MongoFile> getBinaryFileById(String id) {
        return mongoFileRepository.findById(id);
    }

    /**
     * 獲取Grid文件
     * @param id
     * @return
     */
    public Optional<MongoFile> getGridFsFileById(String id){
        MongoFile mongoFile = mongoTemplate.findById(id , MongoFile.class );
        if(Objects.nonNull(mongoFile)){
            Query gridQuery = new Query().addCriteria(Criteria.where("filename").is(mongoFile.getGridFsId()));
            try {
                // TODO 根據id查詢文件
                GridFSFile fsFile = gridFsTemplate.findOne(gridQuery);
                // TODO 打開流下載對象
                GridFSDownloadStream in = gridFSBucket.openDownloadStream(fsFile.getObjectId());
                if(in.getGridFSFile().getLength() > 0){
                    // TODO 獲取流對象
                    GridFsResource resource = new GridFsResource(fsFile, in);
                    // TODO 獲取數據
                    mongoFile.setContent(new Binary(IoUtil.readBytes(resource.getInputStream())));
                    return Optional.of(mongoFile);
                }else {
                    return Optional.empty();
                }
            }catch (IOException e){
                log.error("獲取MongoDB大文件失敗", e);
            }
        }

        return Optional.empty();
    }

    /**
     * 獲取文件後綴
     * @param file
     * @return
     */
    private String getFileSuffix(MultipartFile file) {
        String suffix = "";
        if (Objects.requireNonNull(file.getOriginalFilename()).contains(".")) {
            suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
        }
        return suffix;
    }

}
  • MongoFileRepository.java
import com.coisini.mongodb.model.MongoFile;
import org.springframework.data.mongodb.repository.MongoRepository;

/**
 * @Description MongoDB文件倉儲
 */
public interface MongoFileRepository extends MongoRepository<MongoFile, String> {

}
  • MongoFile.java
import lombok.Builder;
import lombok.Data;
import org.bson.types.Binary;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date;

/**
 * @Description MongoDB文件實體
 */
@Document
@Builder
@Data
public class MongoFile {

    /**
     * 主鍵
     */
    @Id
    public String id;

    /**
     * 文件名稱
     */
    public String fileName;

    /**
     * 文件大小
     */
    public long fileSize;

    /**
     * 上傳時間
     */
    public Date uploadDate;

    /**
     * MD5值
     */
    public String md5;

    /**
     * 文件內容
     */
    private Binary content;

    /**
     * 文件類型
     */
    public String contentType;

    /**
     * 文件後綴名
     */
    public String suffix;

    /**
     * 文件描述
     */
    public String description;

    /**
     * 大文件管理GridFS的ID
     */
    private String gridFsId;

}
  • ResponseMessage.java
/**
 * @Description 統一消息
 */
public class ResponseMessage<T> {

    private String status;
    private String message;
    private T data;

    // 省略

}
  • FileExportVo.java
import java.util.Objects;

/**
 * @Description 統一文件下載vo
 */
@Data
public class FileExportVo {

    private String fileId;

    private String fileName;

    private String contentType;

    private String suffix;

    private long fileSize;

    @JsonIgnore
    private byte[] data;

    public FileExportVo(MongoFile mongoFile) {
        BeanUtil.copyProperties(mongoFile, this);
        if (Objects.nonNull(mongoFile.getContent())) {
            this.data = mongoFile.getContent().getData();
        }
        this.fileId = mongoFile.getId();
    }

}
  • MD5Util.java
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * @Description MD5工具類
 */
public class MD5Util {
    /**
     * 獲取該輸入流的MD5值
     */
    public static String getMD5(InputStream is) throws NoSuchAlgorithmException, IOException {
        StringBuffer md5 = new StringBuffer();
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] dataBytes = new byte[1024];

        int nread = 0;
        while ((nread = is.read(dataBytes)) != -1) {
            md.update(dataBytes, 0, nread);
        };
        byte[] mdbytes = md.digest();

        // convert the byte to hex format
        for (int i = 0; i < mdbytes.length; i++) {
            md5.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
        }
        return md5.toString();
    }
}

測試

  • 文件上傳

在這裏插入圖片描述


  • 多文件上傳

在這裏插入圖片描述


  • 文件下載

在這裏插入圖片描述


  • 文件刪除

在這裏插入圖片描述



源碼


- End -
- 個人筆記 -
- 僅供參考 -

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章