前言

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

一、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. 如何对用户的绑定的身份证真实性进行实名认证(java)

    现在随着对用户实名制的要求,因此用户提交的身份证信息经查需要检查是否为真实信息,我们需要对用户提交的身份证信息进行核验,具体操作步骤如下: 第一步 到认证平台注册账号:云亿互通--实名认证服务 (yu ...

  2. 1.2 Linux是什么,有哪些特点?

    与大家熟知的 Windows 操作系统软件一样,Linux 也是一个操作系统软件,其 logo 是一只企鹅(如图 1 所示).与 Windows 不同之处在于,Linux 是一套开放源代码程序的.可以 ...

  3. 微服务生态组件之Spring Cloud LoadBalancer详解和源码分析

    Spring Cloud LoadBalancer 概述 Spring Cloud LoadBalancer目前Spring官方是放在spring-cloud-commons里,Spring Clou ...

  4. 如何在 pyqt 中解决启用 DPI 缩放后 QIcon 模糊的问题

    问题描述 如今显示器的分辨率越来越高,如果不启用 DPI 缩放,软件的字体和图标在高分屏下就会显得非常小,看得很累人.从 5.6 版本开始,Qt 便能支持 DPI 缩放功能,Qt6 开始这个功能是默认 ...

  5. 初学者都能懂得 Git 说明

    初学者都能懂得 Git 说明 本文写于 2020 年 8 月 10 日 网上有很多非常优秀的 Git 教程,但是他们都是面向有一定基础的开发者的. 可是对于没什么基础的初学者甚至是偶尔操作代码的设计师 ...

  6. ArcGIS和ArcEngine导出地图时,png格式支持背景透明

    1.ArcGIS支持导出PNG,背景透明 导出png时,背景色和透明色不能设置为空,必须设置为同一个颜色,通常使用白色. 2.ArcEngine支持导出PNG,背景透明 //1.创建export IE ...

  7. 896.Montonic Array - LeetCode

    Question 896. Monotonic Array Solution 题目大意: 类似于数学中的减函数,增函数和物理中的加速度为正或为负 思路: 先比较前两个是大于0还是小于0,如果等于0就比 ...

  8. 双webview模式,子窗口打不开或者无法切换

    iOS 真机调试时,发现window.open 无效.可以结合plusReady里面不执行一起参考,博主在当时遇到这个问题只查询了资料,而后并没有来得及自己亲自验证以下方法的可行性.来日再遇上mui的 ...

  9. Sass预处理器

    CSS预处理器 less,sass和stylus sass:较早,缩进风格 scss:完全兼容css3,使用大括号 编写css的编程语言,引入变量,函数,重复代码等功能,如果编译成css文件 Sass ...

  10. java中关于@override注解的使用

    @Override是伪代码,表示重写,作用有:1.可以当注释用,方便阅读:2.编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错.例如:如果想重写父类的方法,比如to ...