zoukankan      html  css  js  c++  java
  • SpringBoot学习笔记(十一:使用MongoDB存储文件 )


    @


    一、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系列教材 (一)- 介绍 - 简介

  • 相关阅读:
    第二部分 高数_9 优化
    第二部分 高数_8 泰勒公式、麦克劳林公式和线性化
    第二部分 高数_7 二元符合函数的求导法则
    第二部分 高数_6 高阶偏导数
    第二部分 高数_5 多元函数的导数
    第二部分 高数_4 函数的积分
    第二部分 高数_3 函数的微分
    第二部分 高数_2 导数
    第二部分 高数_1 极限
    第一部分 现代_4 特征值和特征向量
  • 原文地址:https://www.cnblogs.com/three-fighter/p/12641771.html
Copyright © 2011-2022 走看看