SpringBoot之MongoDB附件操作

前言

近期自己针对附件上传进一步学习,为了弥足项目中文件上传的漏洞,保证文件上传功能的健壮性和可用性,现在我将自己在这一块的心得总结如下:

一、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: localhost       port: 27017       database: feng  fileUploadService:   impl: fileMongoServiceImpl 

 三、MongoDB配置类

/**  * @Description MongoDB配置类  * @author songwp  * @date Apr 17, 2022  * @version 1.0  */ @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);     } } 

 四、MongoDB文件实体

/**  * @Description MongoDB文件实体  * @author songwp  * @date Apr 17, 2022  * @version 1.0  */ @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;  } 

 五、返回统一消息处理类

/**  * @Description 统一消息  * @author songwp  * @date Apr 17, 2022  * @version 1.0  */ public class ResponseMessage<T> {      private String status;     private String message;     private T data;      public static ResponseMessage<?> ok() {         return create("0", (String)null, (Object)null);     }      public static ResponseMessage<?> ok(String message) {         return create("0", message, (Object)null);     }      public static <T> ResponseMessage<T> ok(String message, T data) {         return create("0", message, data);     }      public static <T> ResponseMessage<T> ok(T data) {         return create("0", (String)null, data);     }      public static ResponseMessage<?> error() {         return create("1", (String)null, (Object)null);     }      public static ResponseMessage<?> error(String message) {         return create("1", message, (Object)null);     }      public static <T> ResponseMessage<T> error(String message, T data) {         return create("1", message, data);     }      private static <T> ResponseMessage<T> create(String status, String message, T data) {         ResponseMessage<T> t = new ResponseMessage();         t.setStatus(status);         t.setMessage(message);         t.setData(data);         return t;     }      public ResponseMessage() {     }      public String getStatus() {         return this.status;     }      public String getMessage() {         return this.message;     }      public T getData() {         return this.data;     }      public void setStatus(final String status) {         this.status = status;     }      public void setMessage(final String message) {         this.message = message;     }      public void setData(final T data) {         this.data = data;     }  } 

六、统一文件下载vo

/**  * @Description 统一文件下载vo  * @author songwp  * @date Apr 8, 2022  * @version 1.0  */ @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();     }  } 

 七、MD5工具类

/**  * @Description MD5工具类  * @date Apr 8, 2022  * @version 1.0  */ 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();     } } 

八、MongoDB文件仓储

/**  * @Description MongoDB文件仓储  * @author songwp  * @date Apr 17, 2022  * @version 1.0  */ public interface MongoFileRepository extends MongoRepository<MongoFile, String> { 

九、文件上传业务接口

/**  * @Description 文件上传接口  * @author songwp  * @date Apr 17, 2022  * @version 1.0  */ 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);  } 

十、MongoDB文件上传实现类

/**  * @Description MongoDB文件上传实现类  * @author songwp  * @date Apr 17, 2022  * @version 1.0  */ @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;     } 

十一、文件上传接口-控制器

/**  * @Description 文件上传接口  * @author songwp  * @date Apr 17, 2022  * @version 1.0  */ @Slf4j @RestController @RequestMapping("/file") public class FileUploadController {      /**      * 文件上传实现类      */     @Resource     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("删除成功");     }  } 

十二、接口测试

 1.单个文件上传

SpringBoot之MongoDB附件操作

 

    2.多文件上传

SpringBoot之MongoDB附件操作

 

  3.文件下载

SpringBoot之MongoDB附件操作

 

  4.文件删除

SpringBoot之MongoDB附件操作

 

 

 

 

  

  

发表评论

相关文章