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.文件删除

posted @   [奋斗]  阅读(334)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
点击右上角即可分享
微信分享提示