SpringBoot之MongoDB附件操作
前言
近期自己针对附件上传进一步学习,为了弥足项目中文件上传的漏洞,保证文件上传功能的健壮性和可用性,现在我将自己在这一块的心得总结如下:
一、pom.xml依赖的引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <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配置信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | server: port: 31091 spring: servlet: multipart: max-file-size: 100MB data: mongodb: host: localhost port: 27017 database: feng fileUploadService: impl: fileMongoServiceImpl |
三、MongoDB配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * @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文件实体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | /** * @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; } |
五、返回统一消息处理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | /** * @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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /** * @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工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /** * @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文件仓储
1 2 3 4 5 6 7 | /** * @Description MongoDB文件仓储 * @author songwp * @date Apr 17, 2022 * @version 1.0 */ public interface MongoFileRepository extends MongoRepository<MongoFile, String> {} |
九、文件上传业务接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | /** * @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文件上传实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 | /** * @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; } |
十一、文件上传接口-控制器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | /** * @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.单个文件上传
2.多文件上传
3.文件下载
4.文件删除
古今成大事者,不唯有超世之才,必有坚韧不拔之志!
标签:
springBoot
, monodb
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了