前言

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

一、pom.xml依赖的引入

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  5.  
  6. <!-- mongodb -->
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-data-mongodb</artifactId>
  10. </dependency>
  11.  
  12. <!-- lombok -->
  13. <dependency>
  14. <groupId>org.projectlombok</groupId>
  15. <artifactId>lombok</artifactId>
  16. </dependency>
  17.  
  18. <!-- hutool -->
  19. <dependency>
  20. <groupId>cn.hutool</groupId>
  21. <artifactId>hutool-all</artifactId>
  22. <version>4.5.1</version>
  23. </dependency>

二.application.yml配置信息

  1. server:
  2. port: 31091
  3.  
  4. spring:
  5. servlet:
  6. multipart:
  7. max-file-size: 100MB
  8. data:
  9. mongodb:
  10. host: localhost
  11. port: 27017
  12. database: feng
  13.  
  14. fileUploadService:
  15. impl: fileMongoServiceImpl

三、MongoDB配置类

  1. /**
  2. * @Description MongoDB配置类
  3. * @author songwp
  4. * @date Apr 17, 2022
  5. * @version 1.0
  6. */
  7. @Configuration
  8. public class MongoConfig {
  9. /**
  10. * 数据库配置信息
  11. */
  12. @Value("${spring.data.mongodb.database}")
  13. private String db;
  14.  
  15. /**
  16. * GridFSBucket用于打开下载流
  17. * @param mongoClient
  18. * @return
  19. */
  20. @Bean
  21. public GridFSBucket getGridFSBucket(MongoClient mongoClient){
  22. MongoDatabase mongoDatabase = mongoClient.getDatabase(db);
  23. return GridFSBuckets.create(mongoDatabase);
  24. }
  25. }

四、MongoDB文件实体

  1. /**
  2. * @Description MongoDB文件实体
  3. * @author songwp
  4. * @date Apr 17, 2022
  5. * @version 1.0
  6. */
  7. @Document
  8. @Builder
  9. @Data
  10. public class MongoFile {
  11.  
  12. /**
  13. * 主键
  14. */
  15. @Id
  16. public String id;
  17.  
  18. /**
  19. * 文件名称
  20. */
  21. public String fileName;
  22.  
  23. /**
  24. * 文件大小
  25. */
  26. public long fileSize;
  27.  
  28. /**
  29. * 上传时间
  30. */
  31. public Date uploadDate;
  32.  
  33. /**
  34. * MD5值
  35. */
  36. public String md5;
  37.  
  38. /**
  39. * 文件内容
  40. */
  41. private Binary content;
  42.  
  43. /**
  44. * 文件类型
  45. */
  46. public String contentType;
  47.  
  48. /**
  49. * 文件后缀名
  50. */
  51. public String suffix;
  52.  
  53. /**
  54. * 文件描述
  55. */
  56. public String description;
  57.  
  58. /**
  59. * 大文件管理GridFS的ID
  60. */
  61. private String gridFsId;
  62.  
  63. }

 五、返回统一消息处理类

  1. /**
  2. * @Description 统一消息
  3. * @author songwp
  4. * @date Apr 17, 2022
  5. * @version 1.0
  6. */
  7. public class ResponseMessage<T> {
  8.  
  9. private String status;
  10. private String message;
  11. private T data;
  12.  
  13. public static ResponseMessage<?> ok() {
  14. return create("0", (String)null, (Object)null);
  15. }
  16.  
  17. public static ResponseMessage<?> ok(String message) {
  18. return create("0", message, (Object)null);
  19. }
  20.  
  21. public static <T> ResponseMessage<T> ok(String message, T data) {
  22. return create("0", message, data);
  23. }
  24.  
  25. public static <T> ResponseMessage<T> ok(T data) {
  26. return create("0", (String)null, data);
  27. }
  28.  
  29. public static ResponseMessage<?> error() {
  30. return create("1", (String)null, (Object)null);
  31. }
  32.  
  33. public static ResponseMessage<?> error(String message) {
  34. return create("1", message, (Object)null);
  35. }
  36.  
  37. public static <T> ResponseMessage<T> error(String message, T data) {
  38. return create("1", message, data);
  39. }
  40.  
  41. private static <T> ResponseMessage<T> create(String status, String message, T data) {
  42. ResponseMessage<T> t = new ResponseMessage();
  43. t.setStatus(status);
  44. t.setMessage(message);
  45. t.setData(data);
  46. return t;
  47. }
  48.  
  49. public ResponseMessage() {
  50. }
  51.  
  52. public String getStatus() {
  53. return this.status;
  54. }
  55.  
  56. public String getMessage() {
  57. return this.message;
  58. }
  59.  
  60. public T getData() {
  61. return this.data;
  62. }
  63.  
  64. public void setStatus(final String status) {
  65. this.status = status;
  66. }
  67.  
  68. public void setMessage(final String message) {
  69. this.message = message;
  70. }
  71.  
  72. public void setData(final T data) {
  73. this.data = data;
  74. }
  75.  
  76. }

六、统一文件下载vo

  1. /**
  2. * @Description 统一文件下载vo
  3. * @author songwp
  4. * @date Apr 8, 2022
  5. * @version 1.0
  6. */
  7. @Data
  8. public class FileExportVo {
  9.  
  10. private String fileId;
  11.  
  12. private String fileName;
  13.  
  14. private String contentType;
  15.  
  16. private String suffix;
  17.  
  18. private long fileSize;
  19.  
  20. @JsonIgnore
  21. private byte[] data;
  22.  
  23. public FileExportVo(MongoFile mongoFile) {
  24. BeanUtil.copyProperties(mongoFile, this);
  25. if (Objects.nonNull(mongoFile.getContent())) {
  26. this.data = mongoFile.getContent().getData();
  27. }
  28. this.fileId = mongoFile.getId();
  29. }
  30.  
  31. }

 七、MD5工具类

  1. /**
  2. * @Description MD5工具类
  3. * @date Apr 8, 2022
  4. * @version 1.0
  5. */
  6. public class MD5Util {
  7. /**
  8. * 获取该输入流的MD5值
  9. */
  10. public static String getMD5(InputStream is) throws NoSuchAlgorithmException, IOException {
  11. StringBuffer md5 = new StringBuffer();
  12. MessageDigest md = MessageDigest.getInstance("MD5");
  13. byte[] dataBytes = new byte[1024];
  14.  
  15. int nread = 0;
  16. while ((nread = is.read(dataBytes)) != -1) {
  17. md.update(dataBytes, 0, nread);
  18. };
  19. byte[] mdbytes = md.digest();
  20.  
  21. // convert the byte to hex format
  22. for (int i = 0; i < mdbytes.length; i++) {
  23. md5.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
  24. }
  25. return md5.toString();
  26. }
  27. }

八、MongoDB文件仓储

  1. /**
  2. * @Description MongoDB文件仓储
  3. * @author songwp
  4. * @date Apr 17, 2022
  5. * @version 1.0
  6. */
  7. public interface MongoFileRepository extends MongoRepository<MongoFile, String> {

九、文件上传业务接口

  1. /**
  2. * @Description 文件上传接口
  3. * @author songwp
  4. * @date Apr 17, 2022
  5. * @version 1.0
  6. */
  7. public interface FileUploadService {
  8.  
  9. /**
  10. * 文件上传
  11. * @param file
  12. * @return
  13. */
  14. FileExportVo uploadFile(MultipartFile file) throws Exception;
  15.  
  16. /**
  17. * 多文件上传
  18. * @param files
  19. * @return
  20. */
  21. List<FileExportVo> uploadFiles(List<MultipartFile> files);
  22.  
  23. /**
  24. * 文件下载
  25. * @param fileId
  26. * @return
  27. */
  28. FileExportVo downloadFile(String fileId);
  29.  
  30. /**
  31. * 文件删除
  32. * @param fileId
  33. */
  34. void removeFile(String fileId);
  35.  
  36. }

十、MongoDB文件上传实现类

  1. /**
  2. * @Description MongoDB文件上传实现类
  3. * @author songwp
  4. * @date Apr 17, 2022
  5. * @version 1.0
  6. */
  7. @Slf4j
  8. @Service("fileMongoServiceImpl")
  9. @RequiredArgsConstructor(onConstructor = @__(@Autowired))
  10. public class FileMongoServiceImpl implements FileUploadService {
  11.  
  12. private final MongoFileRepository mongoFileRepository;
  13. private final MongoTemplate mongoTemplate;
  14. private final GridFsTemplate gridFsTemplate;
  15. private final GridFSBucket gridFSBucket;
  16.  
  17. /**
  18. * 多文件上传
  19. * @param files
  20. * @return
  21. */
  22. @Override
  23. public List<FileExportVo> uploadFiles(List<MultipartFile> files) {
  24.  
  25. return files.stream().map(file -> {
  26. try {
  27. return this.uploadFile(file);
  28. } catch (Exception e) {
  29. log.error("文件上传失败", e);
  30. return null;
  31. }
  32. }).filter(Objects::nonNull).collect(Collectors.toList());
  33. }
  34.  
  35. /**
  36. * 文件上传
  37. * @param file
  38. * @return
  39. * @throws Exception
  40. */
  41. @Override
  42. public FileExportVo uploadFile(MultipartFile file) throws Exception {
  43. if (file.getSize() > 16777216) {
  44. return this.saveGridFsFile(file);
  45. } else {
  46. return this.saveBinaryFile(file);
  47. }
  48. }
  49.  
  50. /**
  51. * 文件下载
  52. * @param fileId
  53. * @return
  54. */
  55. @Override
  56. public FileExportVo downloadFile(String fileId) {
  57. Optional<MongoFile> option = this.getBinaryFileById(fileId);
  58.  
  59. if (option.isPresent()) {
  60. MongoFile mongoFile = option.get();
  61. if(Objects.isNull(mongoFile.getContent())){
  62. option = this.getGridFsFileById(fileId);
  63. }
  64. }
  65.  
  66. return option.map(FileExportVo::new).orElse(null);
  67. }
  68.  
  69. /**
  70. * 文件删除
  71. * @param fileId
  72. */
  73. @Override
  74. public void removeFile(String fileId) {
  75. Optional<MongoFile> option = this.getBinaryFileById(fileId);
  76.  
  77. if (option.isPresent()) {
  78. if (Objects.nonNull(option.get().getGridFsId())) {
  79. this.removeGridFsFile(fileId);
  80. } else {
  81. this.removeBinaryFile(fileId);
  82. }
  83. }
  84. }
  85.  
  86. /**
  87. * 删除Binary文件
  88. * @param fileId
  89. */
  90. public void removeBinaryFile(String fileId) {
  91. mongoFileRepository.deleteById(fileId);
  92. }
  93.  
  94. /**
  95. * 删除GridFs文件
  96. * @param fileId
  97. */
  98. public void removeGridFsFile(String fileId) {
  99. // TODO 根据id查询文件
  100. MongoFile mongoFile = mongoTemplate.findById(fileId, MongoFile.class );
  101. if(Objects.nonNull(mongoFile)){
  102. // TODO 根据文件ID删除fs.files和fs.chunks中的记录
  103. Query deleteFileQuery = new Query().addCriteria(Criteria.where("filename").is(mongoFile.getGridFsId()));
  104. gridFsTemplate.delete(deleteFileQuery);
  105. // TODO 删除集合mongoFile中的数据
  106. Query deleteQuery = new Query(Criteria.where("id").is(fileId));
  107. mongoTemplate.remove(deleteQuery, MongoFile.class);
  108. }
  109. }
  110.  
  111. /**
  112. * 保存Binary文件(小文件)
  113. * @param file
  114. * @return
  115. * @throws Exception
  116. */
  117. public FileExportVo saveBinaryFile(MultipartFile file) throws Exception {
  118.  
  119. String suffix = getFileSuffix(file);
  120.  
  121. MongoFile mongoFile = mongoFileRepository.save(
  122. MongoFile.builder()
  123. .fileName(file.getOriginalFilename())
  124. .fileSize(file.getSize())
  125. .content(new Binary(file.getBytes()))
  126. .contentType(file.getContentType())
  127. .uploadDate(new Date())
  128. .suffix(suffix)
  129. .md5(MD5Util.getMD5(file.getInputStream()))
  130. .build()
  131. );
  132.  
  133. return new FileExportVo(mongoFile);
  134. }
  135.  
  136. /**
  137. * 保存GridFs文件(大文件)
  138. * @param file
  139. * @return
  140. * @throws Exception
  141. */
  142. public FileExportVo saveGridFsFile(MultipartFile file) throws Exception {
  143. String suffix = getFileSuffix(file);
  144.  
  145. String gridFsId = this.storeFileToGridFS(file.getInputStream(), file.getContentType());
  146.  
  147. MongoFile mongoFile = mongoTemplate.save(
  148. MongoFile.builder()
  149. .fileName(file.getOriginalFilename())
  150. .fileSize(file.getSize())
  151. .contentType(file.getContentType())
  152. .uploadDate(new Date())
  153. .suffix(suffix)
  154. .md5(MD5Util.getMD5(file.getInputStream()))
  155. .gridFsId(gridFsId)
  156. .build()
  157. );
  158.  
  159. return new FileExportVo(mongoFile);
  160. }
  161.  
  162. /**
  163. * 上传文件到Mongodb的GridFs中
  164. * @param in
  165. * @param contentType
  166. * @return
  167. */
  168. public String storeFileToGridFS(InputStream in, String contentType){
  169. String gridFsId = IdUtil.simpleUUID();
  170. // TODO 将文件存储进GridFS中
  171. gridFsTemplate.store(in, gridFsId , contentType);
  172. return gridFsId;
  173. }
  174.  
  175. /**
  176. * 获取Binary文件
  177. * @param id
  178. * @return
  179. */
  180. public Optional<MongoFile> getBinaryFileById(String id) {
  181. return mongoFileRepository.findById(id);
  182. }
  183.  
  184. /**
  185. * 获取Grid文件
  186. * @param id
  187. * @return
  188. */
  189. public Optional<MongoFile> getGridFsFileById(String id){
  190. MongoFile mongoFile = mongoTemplate.findById(id , MongoFile.class );
  191. if(Objects.nonNull(mongoFile)){
  192. Query gridQuery = new Query().addCriteria(Criteria.where("filename").is(mongoFile.getGridFsId()));
  193. try {
  194. // TODO 根据id查询文件
  195. GridFSFile fsFile = gridFsTemplate.findOne(gridQuery);
  196. // TODO 打开流下载对象
  197. GridFSDownloadStream in = gridFSBucket.openDownloadStream(fsFile.getObjectId());
  198. if(in.getGridFSFile().getLength() > 0){
  199. // TODO 获取流对象
  200. GridFsResource resource = new GridFsResource(fsFile, in);
  201. // TODO 获取数据
  202. mongoFile.setContent(new Binary(IoUtil.readBytes(resource.getInputStream())));
  203. return Optional.of(mongoFile);
  204. }else {
  205. return Optional.empty();
  206. }
  207. }catch (IOException e){
  208. log.error("获取MongoDB大文件失败", e);
  209. }
  210. }
  211.  
  212. return Optional.empty();
  213. }
  214.  
  215. /**
  216. * 获取文件后缀
  217. * @param file
  218. * @return
  219. */
  220. private String getFileSuffix(MultipartFile file) {
  221. String suffix = "";
  222. if (Objects.requireNonNull(file.getOriginalFilename()).contains(".")) {
  223. suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
  224. }
  225. return suffix;
  226. }

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

  1. /**
  2. * @Description 文件上传接口
  3. * @author songwp
  4. * @date Apr 17, 2022
  5. * @version 1.0
  6. */
  7. @Slf4j
  8. @RestController
  9. @RequestMapping("/file")
  10. public class FileUploadController {
  11.  
  12. /**
  13. * 文件上传实现类
  14. */
  15. @Resource(name="${fileUploadService.impl}")
  16. private FileUploadService fileUploadService;
  17.  
  18. /**
  19. * 文件上传
  20. * @param file
  21. * @return
  22. */
  23. @PostMapping("/upload")
  24. public ResponseMessage<?> uploadFile(@RequestParam(value = "file") MultipartFile file) {
  25. try {
  26. return ResponseMessage.ok("上传成功", fileUploadService.uploadFile(file));
  27. } catch (Exception e) {
  28. log.error("文件上传失败:", e);
  29. return ResponseMessage.error(e.getMessage());
  30. }
  31. }
  32.  
  33. /**
  34. * 多文件上传
  35. * @param files
  36. * @return
  37. */
  38. @PostMapping("/uploadFiles")
  39. public ResponseMessage<?> uploadFile(@RequestParam(value = "files") List<MultipartFile> files) {
  40. try {
  41. return ResponseMessage.ok("上传成功", fileUploadService.uploadFiles(files));
  42. } catch (Exception e) {
  43. return ResponseMessage.error(e.getMessage());
  44. }
  45. }
  46.  
  47. /**
  48. * 文件下载
  49. * @param fileId
  50. * @return
  51. */
  52. @GetMapping("/download/{fileId}")
  53. public ResponseEntity<Object> fileDownload(@PathVariable(name = "fileId") String fileId) {
  54. FileExportVo fileExportVo = fileUploadService.downloadFile(fileId);
  55.  
  56. if (Objects.nonNull(fileExportVo)) {
  57. return ResponseEntity.ok()
  58. .header(HttpHeaders.CONTENT_DISPOSITION, "fileName=\"" + fileExportVo.getFileName() + "\"")
  59. .header(HttpHeaders.CONTENT_TYPE, fileExportVo.getContentType())
  60. .header(HttpHeaders.CONTENT_LENGTH, fileExportVo.getFileSize() + "").header("Connection", "close")
  61. .body(fileExportVo.getData());
  62. } else {
  63. return ResponseEntity.status(HttpStatus.NOT_FOUND).body("file does not exist");
  64. }
  65. }
  66.  
  67. /**
  68. * 文件删除
  69. * @param fileId
  70. * @return
  71. */
  72. @DeleteMapping("/remove/{fileId}")
  73. public ResponseMessage<?> removeFile(@PathVariable(name = "fileId") String fileId) {
  74. fileUploadService.removeFile(fileId);
  75. return ResponseMessage.ok("删除成功");
  76. }
  77.  
  78. }

十二、接口测试

 1.单个文件上传

2.多文件上传

3.文件下载

4.文件删除

 

 

  

  

SpringBoot之MongoDB附件操作的更多相关文章

  1. java操作mongodb & springboot整合mongodb

    简单的研究原生API操作MongoDB以及封装的工具类操作,最后也会研究整合spring之后作为dao层的完整的操作. 1.原生的API操作 pom.xml <!-- https://mvnre ...

  2. 实例讲解Springboot整合MongoDB进行CRUD操作的两种方式

    1 简介 Springboot是最简单的使用Spring的方式,而MongoDB是最流行的NoSQL数据库.两者在分布式.微服务架构中使用率极高,本文将用实例介绍如何在Springboot中整合Mon ...

  3. MongoDB系列:三、springboot整合mongoDB的简单demo

    在上篇 MongoDB常用操作练习 中,我们在命令提示符窗口使用简单的mongdb的方法操作数据库,实现增删改查及其他的功能.在本篇中,我们将mongodb与spring boot进行整合,也就是在j ...

  4. SpringBoot与mongodb的结合

    本文系列文章: ​ 使用Shell 操作 MongoDB的技巧 ​ MongoTemplate的使用技巧及其注意事项 敬请期待. 前言 最近公司想要做一个用户行为数据的收集,最开始想用mysql来存储 ...

  5. SpringBoot结合MongoDB入门

    MongoDB 是一个基于分布式文件存储的数据库.由 C++ 语言编写.旨在为 WEB 应用提供可扩展的高性能数据存储解决方案. MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系 ...

  6. SpringBoot 整合mongoDB并自定义连接池

    SpringBoot 整合mongoDB并自定义连接池 得力于SpringBoot的特性,整合mongoDB是很容易的,我们整合mongoDB的目的就是想用它给我们提供的mongoTemplate,它 ...

  7. springboot连接mongodb进行CRUD

    springboot连接mongodb进行CRUD的过程: 在执行以下操作前已安装了mongodb并创建了用户和数据库,使用Robo 3T可成功连接. 1.创建springboot项目,加入以下mav ...

  8. SpringBoot使用MongoDB异常问题

    一 环境介绍 SpringBoot1.5.13.RELEASE(本地) Spring Data MongoDB Java 8 MongoDB(青云) 二 问题描述 使用Studio3T或者Compas ...

  9. Springboot整合MongoDB的Docker开发,其它应用也类似

    1 前言 Docker是容器开发的事实标准,而Springboot是Java微服务常用框架,二者必然是会走到一起的.本文将讲解如何开发Springboot项目,把它做成Docker镜像,并运行起来. ...

随机推荐

  1. XCTF练习题---WEB---get_post

    XCTF练习题---WEB---get_post flag:cyberpeace{5526ac8044f1c5cfb5c421d34dff7822} 解题步骤: 1.观察题目,打开场景 2.观察页面内 ...

  2. socket编程实现tcp服务器_C/C++

    1. 需求分析 实现一个回声服务器的C/S(客户端client/服务器server)程序,功能为客户端连接到服务器后,发送一串字符串,服务器接受信息后,返回对应字符串的大写形式给客户端显示. 例如: ...

  3. Unreal 输入系统 解析

    前言 输入系统,输入某个键,响应到GamePlay层做对应的事.例如 点击鼠标,前进还是开枪之类,是如何响应的.这里只说应用层逻辑,硬件层逻辑不讲述. 详解 1.问题来源 先看下面一个例子:跳跃的事件 ...

  4. C#/VB.NET 实现Word和ODT文档相互转换

    ODT文档格式一种开放文档格式(OpenDocument Text).通常,ODT格式的文件可以使用LibreOffice Writer.MS Word或其他一些文档编辑器来打开.我们在处理文档时,可 ...

  5. Redis GEO 地理位置

    目录 GEO指令 GEOADD GEODIST GEOPOP GEOHASH GEORADIUS GEORADIUSBYMEMBER 指令补充 删除操作 避免单集合数量过多 存储原理 GEOADD存储 ...

  6. K8S面试应知必回

    目录 面试不要不懂装懂,不会就是不会,不可能每个人都接触过所有的知识! 1. 基础问题 1.1 Service是怎么关联Pod的?(课程Service章节) 1.2 HPA V1 V2的区别 1.3 ...

  7. 【java并发编程】Lock & Condition 协调同步生产消费

    一.协调生产/消费的需求 本文内容主要想向大家介绍一下Lock结合Condition的使用方法,为了更好的理解Lock锁与Condition锁信号,我们来手写一个ArrayBlockingQueue. ...

  8. 服务器/网络/虚拟化/云平台自动化运维-ansible

    ansible与netconf的对比 首先明确一个概念,netconf是协议,ansible是python编写的工具 netconf 使用YANG建模,XML进行数据填充,使用netconf协议进行传 ...

  9. 忽略https域名校验不通过

    curl curl 报错: curl: (51) Unable to communicate securely with peer: requested domain name does not ma ...

  10. supervisor安装以及监控管理rabbitmq消费者进程

    简介:Supervisor是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台daemon,并监控进程状态,异常退出时能自动重启. 1.安装 apt-get install ...