@
目录
一, MongoDB 存储文件
1,MongoDB 存储小文件
2,MongoDB 存储大文件
2.1,GridFS 存储原理
2.2,GridFS 使用
2.2.1, 使用 shell 命令
2.2.2, 使用 API
二, SpringBoot 整合 MongoDB 存储文件
1,MongoDB 存储小文件
1.1, 添加依赖
1.2, 配置
1.3, 模型层
1.4, 持久层
1.5, 服务层
1.6, 控制层
1.7, 工具类
1.8, 前端页面
1.9, 运行效果
2,MongoDB 存储大文件
2.1, 依赖
2.2, 启动类
2.3, 配置
2.4, 实体类
2.5, 服务层
2.6, 控制层
2.7, 运行效果
一, 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#,Node.JS 等. 因此可以使用这些语言 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 https://www.runoob.com/mongodb/mongodb-gridfs.html
[2] :MongoDB 的文件存储 GridFs
[3] :MongoDB 学习笔记(五) MongoDB 文件存取操作
[4] :《MongoDB 大数据权威处理指南》
[5] :java 文件转二进制
[6] :Java 将文件转为字节数组
[7] :java 文件下载的几种方式 https://www.iteye.com/blog/jsx112-1008954
[8] : 文件和 byte 数组之间相互转换 https://www.jianshu.com/p/b8b8f1ded401
[9] : 关于知名数据库 MongoDB, 有个功能你不可不知! https://rdc.hundsun.com/portal/article/703.html
[10] :MongoDB 学习笔记(五): 固定集合, GridFS 文件系统与服务器端脚本
[11] :GridFS 基于 MongoDB 的分布式文件存储系统 https://juejin.im/entry/57fc27492e958a00559926d5
[12] :SpringBoot MongoDB 文件存储服务器
[13] :MongoDB 文件服务器搭建
[14] : 基于 MongoDB 及 Spring Boot 的文件服务器的实现 https://www.imooc.com/article/18443
[15] :SpringBoot 中使用 GridFS http://www.manongjc.com/article/30628.html
[16] :SpringBoot2.x 集成 mongoDB4.0 实现音频文件的上传下载功能
[17] :10.18. GridFS Support
[18] :GridFS in Spring Data MongoDB
[19] : 纯洁的微笑 《精通 SpringBoot 42 讲》
[20] :JAVA 应用 / hutool / hutool 系列教材 (一)- 介绍 - 简介 https://how2j.cn/k/hutool/hutool-brief/1930.html
来源: https://www.cnblogs.com/three-fighter/p/12641771.html