@

一、MongoDB存储文件

1、MongoDB存储小文件

MongoDB是一个面向文档的数据库,使用BSON(Binary JSON:二进制JSON)格式来存储数据。

BSON格式

BSON支持在一个文档中最多存储16MB的二进制数据。如果存储的是小于16M的文件,可以直接将文件转换为二进制数据,以文档形式存入集合。

Java中文件和二进制转换也比较简单:

  • 文件转换为byte数组
public static byte[] fileToByte(File file) throws IOException{
byte[] bytes = null;
FileInputStream fis = null;
try{
fis = new FileInputStream(file);
bytes = new bytes[(int) file.length()];
fis.read(bytes);
}catch(IOException e){
e.printStackTrace();
throw e;
}finally{
fis.close();
}
return bytes;
}
  • byte数组转换为文件

public static void bytesToFile(byte[] bFile, String fileDest) { FileOutputStream fileOuputStream = null; try {
fileOuputStream = new FileOutputStream(fileDest);
fileOuputStream.write(bFile); } catch (IOException e) {
e.printStackTrace();
} finally {
if (fileOuputStream != null) {
try {
fileOuputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} }

如果是实现文件下载功能,可以把字节码直接写进流中。

2、MongoDB存储大文件

MongoDB单个文档的存储限制是16M,如果要存储大于16M的文件,就要用到MongoDB GridFS。

GridFS是Mongo的一个子模块,使用GridFS可以基于MongoDB来持久存储文件。并且支持分布式应用(文件分布存储和读取)。作为MongoDB中二进制数据存储在数据库中的解决方案,通常用来处理大文件。

GridFS不是MongoDB自身特性,只是一种将大型文件存储在MongoDB的文件规范,所有官方支持的驱动均实现了GridFS规范。GridFS制定大文件在数据库中如何处理,通过开发语言驱动来完成、通过API接口来存储检索大文件。

2.1、GridFS存储原理

GridFS使用两个集合(collection)存储文件。一个集合是chunks, 用于存储文件内容的二进制数据;一个集合是files,用于存储文件的元数据。

GridFS会将两个集合放在一个普通的buket中,并且这两个集合使用buket的名字作为前缀。MongoDB的GridFs默认使用fs命名的buket存放两个文件集合。因此存储文件的两个集合分别会命名为集合fs.files ,集合fs.chunks。

当把一个文件存储到GridFS时,如果文件大于chunksize (每个chunk块大小为256KB),会先将文件按照chunk的大小分割成多个chunk块,最终将chunk块的信息存储在fs.chunks集合的多个文档中。然后将文件信息存储在fs.files集合的唯一一份文档中。其中fs.chunks集合中多个文档中的file_id字段对应fs.files集中文档”_id”字段。

读文件时,先根据查询条件在files集合中找到对应的文档,同时得到“_id”字段,再根据“_id”在chunks集合中查询所有“files_id”等于“_id”的文档。最后根据“n”字段顺序读取chunk的“data”字段数据,还原文件。

GridFS存储过程

fs.files 集合存储文件的元数据,以类json格式文档形式存储。每在GridFS存储一个文件,则会在fs.files集合中对应生成一个文档。

fs.files集合中文档的存储内容

fs.chunks 集合存储文件文件内容的二进制数据,以类json格式文档形式存储。每在GridFS存储一个文件,GridFS就会将文件内容按照chunksize大小(chunk容量为256k)分成多个文件块,然后将文件块按照类json格式存在.chunks集合中,每个文件块对应fs.chunk集合中一个文档。一个存储文件会对应一到多个chunk文档。

fs.chunks集合中文档的存储内容

2.2、GridFS使用

2.2.1、使用shell命令

mongoDB提供mingofiles工具,可以使用命令行来操作GridFS。其实有四个主要命令,分别为:

  • put —存储命令
  • get —获取命令
  • list —列表命令
  • delete —删除命令

操作实例:

  • 存储文件

    向数据库中存储文件的格式:mongofiles -d 数据库名字 -l "要上传的文件的完整路径名" put "上传后的文件名"

 在filetest数据库中就会多出2个集合,它们存储了GridFS文件系统的所有文件信息,查询这两个集合就能看到上传的文件的一些信息:

  • 列出文件

    查看GridFS文件系统中所有文件:mongofiles -d 数据库名字 list

  • 获取文件

    从GridFS文件系统中下载一个文件到本地:mongofiles -d 数据库名字 -l "将文件保存在本地的完整路径名" get "GridFS文件系统中的文件名" ,如果不写-l以及后面的路径参数,则保存到当前位置。

  • 删除文件

    删除GridFS文件系统中的某个文件:mongofiles -d 数据库名字 delete " 文件名 "

2.2.2、使用API

MongoDB支持多种编程语言驱动。比如c、java、C#、nodeJs等。因此可以使用这些语言MongoDB驱动API操作,扩展GridFS。

以Java为例:

  • 依赖包和版本:

    org.mongodb:3.2.2

    mongo-java-driver:3.2.2

  • 公共方法

public MongoDatabase mongoDatabase() throws Exception{
MongoClient mongoClient = new MongoClient(mongoHost, mongoPort);
return mongoClient.getDatabase(dbName);
}
  • 上传文件
@Test
public void upload() throws Exception {
// 获取文件流
File file = new File("E:/zookeeper-3.4.8.tar.gz");
InputStream in = new FileInputStream(file);
// 创建一个容器,传入一个`MongoDatabase`类实例db
GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());
// 上传
ObjectId fileId = bucket.uploadFromStream(UUID.randomUUID().toString(), in);
System.out.println("上传完成。 文件ID:"+fileId);
}
  • 查找文件
@Test
public void findOne() throws Exception {
// 获取文件ID
String objectId = "57fbaffcec773716ecc54ef4";
// 创建一个容器,传入一个`MongoDatabase`类实例db
GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());
// 获取内容
GridFSFindIterable gridFSFindIterable = bucket.find(Filters.eq("_id", new ObjectId(objectId)));
GridFSFile gridFSFile = gridFSFindIterable.first();
System.out.println("filename: " + gridFSFile.getFilename());
}
  • 下载文件
@Test
public void download() throws Exception {
// 获取文件ID
String objectId = "57fbaffcec773716ecc54ef4";
// 获取文件流
File file = new File("D:/zookeeper-3.4.8.tar.gz");
// 创建一个容器,传入一个`MongoDatabase`类实例db
GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());
// 创建输出流
OutputStream os = new FileOutputStream(file);
// 下载
bucket.downloadToStream(new ObjectId(objectId), os);
System.out.println("下载完成。");
}
  • 删除文件
@Test
public void delete() throws Exception {
// 获取文件ID
String objectId = "57fbaffcec773716ecc54ef4";
// 创建一个容器,传入一个`MongoDatabase`类实例db
GridFSBucket bucket = GridFSBuckets.create(mongoDatabase());
// 删除
bucket.delete(new ObjectId(objectId));
System.out.println("删除完成。");
}

二、SpringBoot整合MongoDB存储文件

MongoDB可以将文件直接存储在文档或者通过GridFS存储大文件,这里同样进行SpringBoot整合MongoDB的两种实现。

1、MongoDB存储小文件

SpringBoot整合MongoDB将文件以文档形式直接存入集合,和普通的MongDB存储区别不大。

1.1、添加依赖

  • spring-boot-starter-data-mongodb:用来操作MongoDB
  • spring-boot-starter-thymeleaf:前端页面采用thymeleaf模板
       <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

1.2、配置

server.address=localhost
server.port=8081 # thymeleaf配置,开发环境不启用缓存,正式环境下请启用缓存,提高性能
spring.thymeleaf.cache=false
# thymeleaf对html元素格式要求严格,设置它的mode为HTML,忘记结束标签后不会报错
spring.thymeleaf.mode=HTML # 编码
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true # MongoDB 配置
# 连接url
spring.data.mongodb.uri=mongodb://test:test@localhost:27017/filetest # 文件上传限制
spring.servlet.multipart.max-file-size=30MB
spring.servlet.multipart.max-request-size=50MB

1.3、模型层

文件模型类:

/**
* @Author 三分恶
* @Date 2020/1/11
* @Description 文档类
*/
@Document
public class FileModel { @Id // 主键
private String id;
private String name; // 文件名称
private String contentType; // 文件类型
private long size;
private Date uploadDate;
private String md5;
private Binary content; // 文件内容
private String path; // 文件路径 /**
*省略getter/setter
*/
protected FileModel() {
} public FileModel(String name, String contentType, long size, Binary content) {
this.name = name;
this.contentType = contentType;
this.size = size;
this.content = content;
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FileModel)) return false;
FileModel fileModel = (FileModel) o;
return size == fileModel.size &&
Objects.equals(id, fileModel.id) &&
Objects.equals(name, fileModel.name) &&
Objects.equals(contentType, fileModel.contentType) &&
Objects.equals(uploadDate, fileModel.uploadDate) &&
Objects.equals(md5, fileModel.md5) &&
Objects.equals(content, fileModel.content) &&
Objects.equals(path, fileModel.path);
} @Override
public int hashCode() {
return Objects.hash(id, name, contentType, size, uploadDate, md5, content, path);
}
}

1.4、持久层

采用MongoRepository的方式操作MongoDB,只需在接口里继承MongoRepository,就可以调用MongoRepository的内置方法。

public interface FileRepository extends MongoRepository<FileModel,String> {
}

1.5、服务层

服务层接口:

public interface FileService {
/**
* 保存文件
*/
FileModel saveFile(FileModel file); /**
* 删除文件
*/
void removeFile(String id); /**
* 根据id获取文件
*/
Optional<FileModel> getFileById(String id); /**
* 分页查询,按上传时间降序
* @return
*/
List<FileModel> listFilesByPage(int pageIndex, int pageSize);
}

服务层实现类:

@Service
public class FileServiceImpl implements FileService {
@Autowired
private FileRepository fileRepository; @Override
public FileModel saveFile(FileModel file) {
return fileRepository.save(file);
} @Override
public void removeFile(String id) {
fileRepository.deleteById(id);
} @Override
public Optional<FileModel> getFileById(String id) {
return fileRepository.findById(id);
} @Override
public List<FileModel> listFilesByPage(int pageIndex, int pageSize) {
Page<FileModel> page = null;
List<FileModel> list = null;
Sort sort = Sort.by(Sort.Direction.DESC,"uploadDate");
Pageable pageable = PageRequest.of(pageIndex, pageSize, sort);
page = fileRepository.findAll(pageable);
list = page.getContent();
return list;
}
}

1.6、控制层

@Controller
public class FileController {
@Autowired
private FileService fileService; @Value("${server.address}")
private String serverAddress; @Value("${server.port}")
private String serverPort; @RequestMapping(value = "/")
public String index(Model model) {
// 展示最新二十条数据
model.addAttribute("files", fileService.listFilesByPage(0, 20));
return "index";
} /**
* 分页查询文件
*/
@GetMapping("files/{pageIndex}/{pageSize}")
@ResponseBody
public List<FileModel> listFilesByPage(@PathVariable int pageIndex, @PathVariable int pageSize) {
return fileService.listFilesByPage(pageIndex, pageSize);
} /**
* 获取文件片信息
*/
@GetMapping("files/{id}")
@ResponseBody
public ResponseEntity<Object> serveFile(@RequestParam("id") String id) throws UnsupportedEncodingException { Optional<FileModel> file = fileService.getFileById(id); if (file.isPresent()) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; fileName=" + new String(file.get().getName().getBytes("utf-8"),"ISO-8859-1"))
.header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
.header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + "").header("Connection", "close")
.body(file.get().getContent().getData());
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not fount");
} } /**
* 在线显示文件
*/
@GetMapping("/view")
@ResponseBody
public ResponseEntity<Object> serveFileOnline(@RequestParam("id") String id) { Optional<FileModel> file = fileService.getFileById(id); if (file.isPresent()) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "fileName=\"" + file.get().getName() + "\"")
.header(HttpHeaders.CONTENT_TYPE, file.get().getContentType())
.header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + "").header("Connection", "close")
.body(file.get().getContent().getData());
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not fount");
} } /**
* 上传
*/
@PostMapping("/")
public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { try {
FileModel f = new FileModel(file.getOriginalFilename(), file.getContentType(), file.getSize(),
new Binary(file.getBytes()));
f.setMd5(MD5Util.getMD5(file.getInputStream()));
fileService.saveFile(f);
System.out.println(f);
} catch (IOException | NoSuchAlgorithmException ex) {
ex.printStackTrace();
redirectAttributes.addFlashAttribute("message", "Your " + file.getOriginalFilename() + " is wrong!");
return "redirect:/";
} redirectAttributes.addFlashAttribute("message",
"You successfully uploaded " + file.getOriginalFilename() + "!"); return "redirect:/";
} /**
* 上传接口
*/
@PostMapping("/upload")
@ResponseBody
public ResponseEntity<String> handleFileUpload(@RequestParam("file") MultipartFile file) {
FileModel returnFile = null;
try {
FileModel f = new FileModel(file.getOriginalFilename(), file.getContentType(), file.getSize(),
new Binary(file.getBytes()));
f.setMd5(MD5Util.getMD5(file.getInputStream()));
returnFile = fileService.saveFile(f);
String path = "//" + serverAddress + ":" + serverPort + "/view/" + returnFile.getId();
return ResponseEntity.status(HttpStatus.OK).body(path); } catch (IOException | NoSuchAlgorithmException ex) {
ex.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());
} } /**
* 删除文件
*/
@GetMapping("/delete")
@ResponseBody
public ResponseEntity<String> deleteFile( @RequestParam("id") String id) { try {
fileService.removeFile(id);
return ResponseEntity.status(HttpStatus.OK).body("DELETE Success!");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
}

1.7、工具类

md5工具类:

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();
}
}

1.8、前端页面

前端页面index.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
<h1 style="text-align: center">文件服务</h1>
<br>
<div >
<a href="/">首页</a>
</div> <br><br>
<div th:if="${message}" style="margin-left: 10%">
<h2 th:text="${message}"/>
</div> <br>
<div >
<form method="POST" enctype="multipart/form-data" action="/">
<table>
<tr><td>上传文件:</td><td><input type="file" name="file" /></td></tr>
<tr><td></td><td><input type="submit" value="上传" /></td></tr>
</table> </form>
</div>
<br><br> <div style="margin-left: 5%">
<h3 style="text-align: center">文件列表</h3>
<table border="1">
<thead>
<tr style="background-color: beige">
<td>文件名</td>
<td>文件ID</td>
<td>contentType</td>
<td>文件大小</td>
<td>上传时间</td>
<td>md5</td>
<td>操作</td>
</tr>
</thead>
<tbody>
<tr th:if="${files.size()} eq 0">
<td colspan="3">没有文件信息!!</td>
</tr>
<tr th:each="file : ${files}">
<td><a th:href="'files/'+${file.id}" th:text="${file.name}" /></td>
<td th:text="${file.id}" ></td>
<td th:text="${file.contentType}" ></td>
<td th:text="${file.size}" ></td>
<td th:text="${file.uploadDate}" ></td>
<td th:text="${file.md5}" ></td>
<td><a target="_blank" th:href="@{/view(id=${file.id})}">预览</a>|<a th:href="@{/delete(id=${file.id})}">删除</a></td>
</tr>
</tbody>
</table>
</div> </body> <style>
body{
text-align: center;
} table{
margin: auto;
} </style>

1.9、运行效果

  • 上传文件:

  • 预览

  • 下载

  • 删除

在文件的操作过程中,可以通过可视化工具或shell来查看存储在MongoDB中的文件:

  • 可以看到,在fileModel集合中存储了我们上传的文件,文件的内容是以二进制的形式存储

2、MongoDB存储大文件

Spring Data MongoDB提供了GridFsOperations接口以及相应的实现GridFsTemplate,可以和GridFs交互。

这里在上一个工程的基础上进行改造。

2.1、依赖

和上一个工程相比,这里添加开源工具包hutool的依赖:

         <dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.5.1</version>
</dependency>

2.2、启动类

@SpringBootApplication
public class SpringbootFileGridfsApplication { public static void main(String[] args) {
SpringApplication.run(SpringbootFileGridfsApplication.class, args);
} //Tomcat large file upload connection reset
@Bean
public TomcatServletWebServerFactory tomcatEmbedded() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addConnectorCustomizers((TomcatConnectorCustomizer) connector -> {
if ((connector.getProtocolHandler() instanceof AbstractHttp11Protocol<
?>)) {
//-1 means unlimited
((AbstractHttp11Protocol<?>) connector.getProtocolHandler()).setMaxSwallowSize(-1);
}
});
return tomcat;
}
}

TomcatServletWebServerFactory() ⽅法主要是为了解决上传文件较大时出现连接重置的问题,这个异常后台是捕捉不到的:

2.3、配置

  • application.properties
# MongoDB 配置
# 连接uri
#spring.data.mongodb.uri=mongodb://test:test@localhost:27017/filetest
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=filetest
spring.data.mongodb.username=test
spring.data.mongodb.password=test # 文件上传限制
spring.servlet.multipart.max-file-size=1020MB
spring.servlet.multipart.max-request-size=1020MB
  • 配置类
/**
* @Author 三分恶
* @Date 2020/1/11
* @Description
*/
@Configuration
public class MongoConfig {
//获取配置文件中数据库信息
@Value("${spring.data.mongodb.database}")
String db; ////GridFSBucket用于打开下载流
@Bean
public GridFSBucket getGridFSBucket(MongoClient mongoClient){
MongoDatabase mongoDatabase = mongoClient.getDatabase(db);
GridFSBucket bucket = GridFSBuckets.create(mongoDatabase);
return bucket;
}
}

2.4、实体类

  • 文件实体类
/**
* @Author 三分恶
* @Date 2020/1/11
* @Description
*/
@Document
public class FileDocument {
@Id // 主键
private String id;
private String name; // 文件名称
private long size; // 文件大小
private Date uploadDate; // 上传时间
private String md5; // 文件MD5值
private byte[] content; // 文件内容
private String contentType; // 文件类型
private String suffix; // 文件后缀名
private String description; // 文件描述
private String gridfsId; // 大文件管理GridFS的ID /**
* 省略getter、setter、equales、hashCode、toString方法
*/
}
  • 接口结果实体类
**
* @Author 三分恶
* @Date 2020/1/11
* @Description 公用数据返回模型
*/
public class ResponseModel {
public static final String Success = "success";
public static final String Fail = "fail"; private String code = "fail";
private String message = "";
private String data; //私有构造函数,此类不允许手动实例化,需要调用getInstance()获取实例
private ResponseModel() {
} /**
* 返回默认的实例
* @return
*/
public static ResponseModel getInstance() {
ResponseModel model = new ResponseModel();
model.setCode(ResponseModel.Fail);
return model;
} /**
*省略getter/setter
*/ }

2.5、服务层

上一个实例里采用MongReposity来操作MongoDB,这里操作MongoDB采用MongoTemplate,操作GridFs采用GridFsTemplate。

/**
* @Author 三分恶
* @Date 2020/1/11
* @Description
*/
@Service
public class FileServiceImpl implements FileService { @Autowired
private MongoTemplate mongoTemplate; @Autowired
private GridFsTemplate gridFsTemplate; @Autowired
private GridFSBucket gridFSBucket; /**
* 保存文件
* @param file
* @return
*/
@Override
public FileDocument saveFile(FileDocument file) {
file = mongoTemplate.save(file);
return file;
} /**
* 上传文件到Mongodb的GridFs中
* @param in
* @param contentType
* @return
*/
@Override
public String uploadFileToGridFS(InputStream in , String contentType){
String gridfsId = IdUtil.simpleUUID();
//将文件存储进GridFS中
gridFsTemplate.store(in, gridfsId , contentType);
return gridfsId;
} /**
* 删除文件
* @param id
*/
@Override
public void removeFile(String id) {
//根据id查询文件
FileDocument fileDocument = mongoTemplate.findById(id , FileDocument.class );
if(fileDocument!=null){
//根据文件ID删除fs.files和fs.chunks中的记录
Query deleteFileQuery = new Query().addCriteria(Criteria.where("filename").is(fileDocument.getGridfsId()));
gridFsTemplate.delete(deleteFileQuery);
//删除集合fileDocment中的数据
Query deleteQuery=new Query(Criteria.where("id").is(id));
mongoTemplate.remove(deleteQuery,FileDocument.class);
}
} /**
* 根据id查看文件
* @param id
* @return
*/
@Override
public Optional<FileDocument> getFileById(String id){
FileDocument fileDocument = mongoTemplate.findById(id , FileDocument.class );
if(fileDocument != null){
Query gridQuery = new Query().addCriteria(Criteria.where("filename").is(fileDocument.getGridfsId()));
try {
//根据id查询文件
GridFSFile fsFile = gridFsTemplate.findOne(gridQuery);
//打开流下载对象
GridFSDownloadStream in = gridFSBucket.openDownloadStream(fsFile.getObjectId());
if(in.getGridFSFile().getLength() > 0){
//获取流对象
GridFsResource resource = new GridFsResource(fsFile, in);
//获取数据
fileDocument.setContent(IoUtil.readBytes(resource.getInputStream()));
return Optional.of(fileDocument);
}else {
fileDocument = null;
return Optional.empty();
}
}catch (IOException ex){
ex.printStackTrace();
}
}
return Optional.empty();
} /**
* 分页列出文件
* @param pageIndex
* @param pageSize
* @return
*/
@Override
public List<FileDocument> listFilesByPage(int pageIndex, int pageSize) {
Query query = new Query().with(Sort.by(Sort.Direction.DESC, "uploadDate"));
long skip = (pageIndex -1) * pageSize;
query.skip(skip);
query.limit(pageSize);
Field field = query.fields();
field.exclude("content");
List<FileDocument> files = mongoTemplate.find(query , FileDocument.class );
return files; }
}

2.6、控制层

控制层变动不大,主要是调用服务层方法的返回值和参数有变化:

@Controller
public class FileController {
@Autowired
private FileService fileService; @Value("${server.address}")
private String serverAddress; @Value("${server.port}")
private String serverPort; @RequestMapping(value = "/")
public String index(Model model) {
// 展示最新二十条数据
model.addAttribute("files", fileService.listFilesByPage(0, 20));
return "index";
} /**
* 分页查询文件
*/
@GetMapping("files/{pageIndex}/{pageSize}")
@ResponseBody
public List<FileDocument> listFilesByPage(@PathVariable int pageIndex, @PathVariable int pageSize) {
return fileService.listFilesByPage(pageIndex, pageSize);
} /**
* 获取文件片信息
*/
@GetMapping("files/{id}")
@ResponseBody
public ResponseEntity<Object> serveFile(@PathVariable String id) throws UnsupportedEncodingException { Optional<FileDocument> file = fileService.getFileById(id); if (file.isPresent()) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; fileName=" + new String(file.get().getName().getBytes("utf-8"),"ISO-8859-1"))
.header(HttpHeaders.CONTENT_TYPE, "application/octet-stream")
.header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + "").header("Connection", "close")
.body(file.get().getContent());
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not fount");
} } /**
* 在线显示文件
*/
@GetMapping("/view")
@ResponseBody
public ResponseEntity<Object> serveFileOnline(@RequestParam("id") String id) {
Optional<FileDocument> file = fileService.getFileById(id);
if (file.isPresent()) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "fileName=" + file.get().getName())
.header(HttpHeaders.CONTENT_TYPE, file.get().getContentType())
.header(HttpHeaders.CONTENT_LENGTH, file.get().getSize() + "").header("Connection", "close")
.header(HttpHeaders.CONTENT_LENGTH , file.get().getSize() + "")
.body(file.get().getContent());
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File was not found");
} } /**
* 上传
*/
@PostMapping("/")
public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) { try {
FileDocument fileDocument = new FileDocument();
fileDocument.setName(file.getOriginalFilename());
fileDocument.setSize(file.getSize());
fileDocument.setContentType(file.getContentType());
fileDocument.setUploadDate(new Date());
String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
fileDocument.setSuffix(suffix);
fileDocument.setMd5(MD5Util.getMD5(file.getInputStream()));
//将文件存入gridFs
String gridfsId = fileService.uploadFileToGridFS(file.getInputStream() , file.getContentType());
fileDocument.setGridfsId(gridfsId);
fileDocument = fileService.saveFile(fileDocument);
System.out.println(fileDocument);
} catch (IOException | NoSuchAlgorithmException ex) {
ex.printStackTrace();
redirectAttributes.addFlashAttribute("message", "Your " + file.getOriginalFilename() + " is wrong!");
return "redirect:/";
} redirectAttributes.addFlashAttribute("message",
"You successfully uploaded " + file.getOriginalFilename() + "!"); return "redirect:/";
} /**
* 上传接口
*/
@PostMapping("/upload")
@ResponseBody
public ResponseEntity<String> handleFileUpload(@RequestParam("file") MultipartFile file) {
FileDocument returnFile = null;
try {
FileDocument fileDocument = new FileDocument();
fileDocument.setName(file.getOriginalFilename());
fileDocument.setSize(file.getSize());
fileDocument.setContentType(file.getContentType());
fileDocument.setUploadDate(new Date());
String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
fileDocument.setSuffix(suffix);
fileDocument.setMd5(MD5Util.getMD5(file.getInputStream()));
//将文件存入gridFs
String gridfsId = fileService.uploadFileToGridFS(file.getInputStream() , file.getContentType());
fileDocument.setGridfsId(gridfsId);
returnFile = fileService.saveFile(fileDocument);
String path = "//" + serverAddress + ":" + serverPort + "/view/" + returnFile.getId();
return ResponseEntity.status(HttpStatus.OK).body(path); } catch (IOException | NoSuchAlgorithmException ex) {
ex.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());
} } /**
* 删除文件
*/
@GetMapping("/delete")
@ResponseBody
public ResponseModel deleteFile( @RequestParam("id") String id) {
ResponseModel model = ResponseModel.getInstance();
if(!StrUtil.isEmpty(id)){
fileService.removeFile(id);
model.setCode(ResponseModel.Success);
model.setMessage("删除成功");
}else {
model.setMessage("请传入文件id");
}
return model;
}
}
  • 前端页面没有变动。

2.7、运行效果

  • 上传文件

    这里我们选择一个比较大的mp4文件

  • 预览

    预览还存在问题,后台会报错:org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class edu.hpu.domain.ResponseModel] with preset Content-Type 'video/mp4'

    待解决

  • 下载

  • 删除

在上传和删除数据的过程中,可以通过可视化工具或shell来查看MongoDB中的数据

  • fileDocment中的数据:fileDocment是一个普通的集合,对应地以文档的形式存储了FileDocument实例

  • fs.files中的数据:文件的元数据

  • fs.chunks中的数据:file被切分成若干个chunks,存储了文件的二进制数据


本文为学习笔记类博客,学习资料来源见参考!

【1】:MongoDB GridFS

【2】:Mongodb的文件存储GridFs

【3】:MongoDB学习笔记(五) MongoDB文件存取操作

【4】:《MongoDB大数据权威处理指南》

【5】:java文件转二进制

【6】:Java将文件转为字节数组

【7】:java文件下载的几种方式

【8】:文件和byte数组之间相互转换

【9】:关于知名数据库MongoDB,有个功能你不可不知!

【10】:MongoDB 学习笔记(五):固定集合、GridFS文件系统与服务器端脚本

【11】:GridFS 基于 MongoDB 的分布式文件存储系统

【12】:SpringBoot Mongodb文件存储服务器

【13】:MongoDB文件服务器搭建

【14】:基于 MongoDB 及 Spring Boot 的文件服务器的实现

【15】:SpringBoot中使用GridFS

【16】:SpringBoot2.x集成mongoDB4.0实现音频文件的上传下载功能

【17】:10.18. GridFS Support

【18】:GridFS in Spring Data MongoDB

【19】:纯洁的微笑 《精通SpringBoot 42讲》

【20】:JAVA 应用 / hutool / hutool系列教材 (一)- 介绍 - 简介

SpringBoot学习笔记(十一:使用MongoDB存储文件 )的更多相关文章

  1. SpringBoot学习笔记(10):使用MongoDB来访问数据

    SpringBoot学习笔记(10):使用MongoDB来访问数据 快速开始 本指南将引导您完成使用Spring Data MongoDB构建应用程序的过程,该应用程序将数据存储在MongoDB(基于 ...

  2. SpringBoot学习笔记

    SpringBoot个人感觉比SpringMVC还要好用的一个框架,很多注解配置可以非常灵活的在代码中运用起来: springBoot学习笔记: .一.aop: 新建一个类HttpAspect,类上添 ...

  3. Springboot学习笔记(六)-配置化注入

    前言 前面写过一个Springboot学习笔记(一)-线程池的简化及使用,发现有个缺陷,打个比方,我这个线程池写在一个公用服务中,各项参数都定死了,现在有两个服务要调用它,一个服务的线程数通常很多,而 ...

  4. python3.4学习笔记(十一) 列表、数组实例

    python3.4学习笔记(十一) 列表.数组实例 #python列表,数组类型要相同,python不需要指定数据类型,可以把各种类型打包进去#python列表可以包含整数,浮点数,字符串,对象#创建 ...

  5. Go语言学习笔记十一: 切片(slice)

    Go语言学习笔记十一: 切片(slice) 切片这个概念我是从python语言中学到的,当时感觉这个东西真的比较好用.不像java语言写起来就比较繁琐.不过我觉得未来java语法也会支持的. 定义切片 ...

  6. SpringBoot学习笔记(14):使用SpringBootAdmin管理监控你的应用

    SpringBoot学习笔记(14):使用SpringBootAdmin管理监控你的应用 Spring Boot Admin是一个管理和监控Spring Boot应用程序的应用程序.本文参考文档: 官 ...

  7. SpringBoot学习笔记(3):静态资源处理

    SpringBoot学习笔记(3):静态资源处理 在web开发中,静态资源的访问是必不可少的,如:Html.图片.js.css 等资源的访问. Spring Boot 对静态资源访问提供了很好的支持, ...

  8. SpringBoot学习笔记(2):引入Spring Security

    SpringBoot学习笔记(2):用Spring Security来保护你的应用 快速开始 本指南将引导您完成使用受Spring Security保护的资源创建简单Web应用程序的过程. 参考资料: ...

  9. SpringBoot学习笔记(7):Druid使用心得

    SpringBoot学习笔记(7):Druid使用心得 快速开始 添加依赖 <dependency> <groupId>com.alibaba</groupId> ...

  10. SpringBoot学习笔记(4):与前端交互的日期格式

    SpringBoot学习笔记(4):与前端交互的日期格式 后端模型Date字段解析String 我们从前端传回来表单的数据,当涉及时间.日期等值时,后端的模型需将其转换为对应的Date类型等. 我们可 ...

随机推荐

  1. VMware Workstation 虚拟机的安装及kali Linux 的安装

    要用到虚拟机了,Windows平台的虚拟机现在主要用的是VMWare 和VirtuualBox了.VirtualBox作为一款免费的软件,用着还行.笔者这里主要介绍VMware的安装和使用 . 下载: ...

  2. [CSS]CSS Position 详解

    一. CSS position 属性介绍 CSS中position属性指定一个元素(静态的,相对的,绝对或固定)的定位方法的类型.有static,relative,absolute和fixed四种取值 ...

  3. 前端javascript知识(一)

    介绍一下 JS 的基本数据类型. Undefined.Null.Boolean.Number.String 介绍一下 JS 有哪些内置对象. Object 是 JavaScript 中所有对象的父对象 ...

  4. IIS6.0文件解析漏洞和短文件名漏洞复现

    一.IIS6.0文件解析漏洞 1.ASP一句话木马的准备 新建木马文件“muma.txt”,将“我asp是一句话木马:<%eval request("asp")%>”写 ...

  5. 必备技能一、webpack

    https://cloud.tencent.com/developer/section/1477376----->配置很重要 一.基本安装 mkdir webpack-demo &&am ...

  6. ZipArchive(解压文件)

    一.首先介绍minizip 的使用方法 ziparchive是基于开源代码”MiniZip”的zip压缩与解压的Objective-C 的Class,使用起来非常的简单方法:从http://code. ...

  7. 在Deepin系统上装Python 3.8遇到的那些坑

    - 作为一天时间在Deepin上都没装好Python的代表,我感觉有必要记录一下我自己的解决方法 坑1-- SSL/TLS 字样错误 "pip is configured wih locat ...

  8. 深入理解React key

    一 react 组件元素的 diff 算法 二 key 的理解 概述 react 中的key 属性,它是一个特殊的属性,它的出现不是给开发者用的(例如你为一个组件设置key之后不能获取组件的这个key ...

  9. MySQL记录操作(增删改)

    概览 MySQL数据操作: DML 在MySQL管理软件中,可以通过SQL语句中的DML语言来实现数据的操作,包括 使用INSERT实现数据的插入 UPDATE实现数据的更新 使用DELETE实现数据 ...

  10. 5G 将带给程序员哪些新机会呢?

    5G,第 5 代移动通信技术,华为在此领域远远领先同行,这也让它成了中美贸易战的最前线.我的第一份工作就在通信行业,当时电信标准都在欧美企业手里,国内企业主要是遵照标准研发软硬件设备,核心芯片靠进口. ...