前言

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

一、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(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("删除成功");
} }

十二、接口测试

 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. 攻防世界-MISC:pure_color

    这是攻防世界高手进阶区的第六题,题目如下: 点击下载附件一,得到一张空白的png图片 用StegSolve打开,然后点击箭头(如下图所示) 多点击几次,即可得到flag 所以,这道题的flag如下: ...

  2. 攻防世界-MISC:神奇的Modbus

    这是攻防世界高手进阶区的第三题,题目如下: 点击下载附件一,得到一个流量包,题目中提到的modbus,百度百科的解释如下: 用wireshark打开流量包,搜索modbus 然后鼠标右键选择追踪流,再 ...

  3. XCTF练习题---MISC---reverseMe

    XCTF练习题---MISC---reverseMe flag:flag{4f7548f93c7bef1dc6a0542cf04e796e} 解题步骤: 1.观察题目,下载附件 2.拿到手以后发现是个 ...

  4. python3 Softmax函数

    Softmax函数公式 Softmax的作用简单的说就计算一组数值中每个值的占比 import torch import torch.nn.functional as F # 原始数据tensor y ...

  5. mysql中的date、datetime、timestamp你还不知道怎么使用吗

    在后端开发中经常会碰到涉及数据库的场景,不知道屏幕前的你有这样的疑惑没有,每每在遇到时间.日期字段总是让人头疼,分不清到底是选date还是datetime,亦或是timestamp,真是抓耳挠腮啊,怎 ...

  6. [笔记] 有向无环图 DAG

    最小链覆盖 (最长反链) 最小链覆盖 \(=n-\) 最大匹配. 考虑首先每个点自成一条链,此时恰好有 \(n\) 条链,最终答案一定是合并(首尾相接)若干条链形成的. 将两点匹配的含义其实就是将链合 ...

  7. 完全卸载nginx的详细步骤

    一个执着于技术的公众号 前言 在开局配置Nginx时有可能会配置错误,报各种错误代码.看不懂或者懒得去看这个报错时,其实最简单的方式是卸载并重装咯.今天就带大家一起学习下,如何彻底卸载nginx程序. ...

  8. Ansible的参数介绍

    安装完成ansible后查看ansible的参数:ansible -h ansible 命令格式:Usage: ansible <host-pattern> [options] ansib ...

  9. 1903021121-刘明伟 实验二 JAVA第2周作业—代码插入

    项目 内容 课程班级博客链接 19信计班(本)  作业要求链接  第2周作业 要求 截图(只截运行结果) 扩展阅读 https://www.cnblogs.com/thelovelybugfly/p/ ...

  10. ubuntu 20.04 source mirror(aliyun)

    x64 deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse deb-src http://m ...