SpringBoot 使用Mongo的GridFs实现分布式文件存储操作

目录
  • 前言
  • GridFs介绍
    • 什么时候使用GridFs
    • GridFs的原理
  • 环境
  • 引入依赖和项目配置
  • 使用GridFsTemplate操作GridFs

前言

这段时间在公司实习,安排给我一个任务,让在系统里实现一个知识库的模块,产品说,就像百度网盘那样。。。我tm…,这不就是应了那句话,“这个需求很简单,怎么实现我不管”。

可是我google小能手怎么会认输呢,本来还说研究一下FastDFS啥的,但是因为我们项目用的Mongo作为数据库,了解到Mongo自带分布式文件系统GridFs,这简直天助我也。

GridFs介绍

什么时候使用GridFs

我们平时使用Mongo也可以直接把文件的二进制保存在Document中,就相当于mysql的blob格式,但是mongo限制Document最大为16MB,那我们超过16MB的文件可咋办呐,就可以利用GridFs来存储。

  • 在某些情况下,在MongoDB数据库中存储大文件可能比在系统级文件系统上更高效。
  • 如果文件系统限制目录中的文件数,则可以使用GridFS根据需要存储任意数量的文件。如果要从大型文件的各个部分访问信息而无需将整个文件加载到内存中,可以使用GridFS调用文件的各个部分,而无需将整个文件读入内存。
  • 如果要保持文件和元数据在多个系统和设施中自动同步和部署,可以使用GridFS。使用地理位置分散的副本集时,MongoDB可以自动将文件及其元数据分发到多个 mongod实例和工具中。

GridFs的原理

GridFS不是将文件存储在单个文档中,而是将文件分成多个部分或块,并将每个块存储为单独的文档。

GridFS使用两个集合来存储文件。一个集合存储文件块,另一个存储文件元数据。

默认情况下,每一个块的大小为255kB; 但最后一个块除外。最后一个块只有必要的大小。类似地,不大于块大小的文件只有最终块,只使用所需的空间和一些额外的元数据。

当查询GridFS文件时,驱动程序将根据需要重新组装块。可以对通过GridFS存储的文件执行范围查询。还可以从文件的任意部分访问信息,例如“跳过”到视频或音频文件的中间。

环境

  • Spring Boot 2.0.4
  • Maven 3.5
  • Java 1.8
  • MongoDB 4.0
  • Robo 1.3.1

引入依赖和项目配置

首先添加Mongo客户端的依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

然后编写配置文件

spring:
  application:
    name: demo
  data:
    mongodb:
      uri: mongodb://root:你的密码@localhost:27019
      authentication-database: admin
      database: 你的数据库

使用GridFsTemplate操作GridFs

GridFsTemplate是Spring提供的专门操作GridFs的客户端,提供了一系列开箱即用的方法

只要把它注入到我们的Conteoller中,就可以愉快的CRUD了,需要注意的是获取文件时要注入MongoDbFactory ,我们使用默认配置的话,直接注入就好。

import com.mongodb.Block;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSBuckets;
import com.mongodb.client.gridfs.model.GridFSFile;
import com.mongodb.client.gridfs.model.GridFSUploadOptions;
import com.mongodb.gridfs.GridFS;
import com.mongodb.gridfs.GridFSDBFile;
import org.bson.BsonValue;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;

@RestController
@RequestMapping("/files")
public class FileController {
	private Logger logger = LoggerFactory.getLogger(GridFsServiceImpl.class);
    //匹配文件ID的正则
    private static Pattern NUMBER_PATTERN = Pattern.compile("(?<==).*(?=})");
    @Autowired
    GridFsTemplate gridFsTemplate;
    @Autowired
    MongoDbFactory mongoDbFactory;

    /**
     * 上传文件
     *
     * @param file 文件
     * @return 文件名和文件存储的fileId键值对的Map
     */
    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    public Map<String, ObjectId> upload(@RequestParam(value = "file") MultipartFile file) {
  if(pathId == null || fileType == null || pathId.equals("") || fileType.equals("")){
            logger.debug("上传文件出错");
            throw new RuntimeException("上传文件出错:pathId and fileType can not be null");
        }
        Map<String, String> map = new HashMap<>(1);
        String fileName = file.getOriginalFilename();
        //设置元数据
        DBObject metaData = new BasicDBObject();
        metaData.put("userId","1");
        metaData.put("fileExtension",FilenameUtils.getExtension(file.getOriginalFilename()));
        //存储文件的扩展名

        try {
            InputStream inputStream = file.getInputStream();
            ObjectId fileId = gridFsTemplate.store(inputStream, fileName,metaData);
            //这个getFileId是我自己封装的获取文件ID的方法
            map.put(file.getOriginalFilename(),getFileId(fileId.toString()));
        } catch (IOException e) {
            logger.debug("上传文件失败: "+file);
            throw new RuntimeException("上传文件失败:"+e.getMessage());
        }
        return map;
    }

    /**
     * 通过文件fileId下载文件
     *
     * @param fileId 文件fileId
     * @param response 文件输出流
     */
    @RequestMapping(value = "/downLoadByFileId")
    public void downLoadByFileId(@RequestParam(value = "fileId") ObjectId fileId, HttpServletResponse response) {
 GridFSFile gridFsFile = gridFsTemplate.findOne(new Query().addCriteria(Criteria.where("_id").is(fileId)));
        if (gridFsFile != null) {
            // mongo-java-driver3.x以上的版本就变成了这种方式获取
            GridFSBucket bucket = GridFSBuckets.create(mongoDbFactory.getDb());
            GridFSDownloadStream gridFsDownloadStream = bucket.openDownloadStream(gridFsFile.getObjectId());
            GridFsResource gridFsResource = new GridFsResource(gridFsFile,gridFsDownloadStream);
            String fileName = gridFsFile.getFilename().replace(",", "");
            //处理中文文件名乱码
            if (request.getHeader("User-Agent").toUpperCase().contains("MSIE") ||
                    request.getHeader("User-Agent").toUpperCase().contains("TRIDENT")
                    || request.getHeader("User-Agent").toUpperCase().contains("EDGE")) {
                fileName = java.net.URLEncoder.encode(fileName, "UTF-8");
            } else {
                //非IE浏览器的处理:
                fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
            }
            response.setHeader("Content-Disposition", "inline;filename=\"" + fileName + "\"");
            response.setContentType(application/octet-stream);
            IOUtils.copy(gridFsResource.getInputStream(), response.getOutputStream());
        }else {
            logger.info("文件不存在");
        }
    }

	/**
     * 根据文件名查询文件列表
     * @param fileName 文件名
     * @return
     */
    @RequestMapping("/search")
    public List<Map<String,Object>> search(String filePath, String fileName) {
		//这里返回的是一个List<Map<String,Object>>
		//因为GridFSFindIterable 迭代出来的GridFSFile不能直接返回
        GridFSFindIterable gridFSFiles = gridFsTemplate.find(new Query().addCriteria
                (Criteria.where("filename").regex("^.*"+fileName+".*$")));
        return getGridFSFiles(gridFSFiles);
    }

    /**
     * 通过fileId删除文件
     * @param fileId 文件ID
     * @return 成功为true, 失败为false
     */
    @RequestMapping("/deleteFilesByObjectId")
    public boolean deleteFilesByObjectId(@RequestParam(value = "fileId") String fileId) {
         try {
            gridFsTemplate.delete(new Query().addCriteria(Criteria.where("_id").is(fileId)));
        }catch (Exception e){
            logger.debug("删除文件失败: fileId= "+fileId);
            throw new RuntimeException("删除文件失败:"+e.getMessage());
        }
    }
	/**
     * 根据GridFSFiles迭代器返回文件列表,因为不能直接返回,所以写了这个工具方法
     * @param gridFSFiles 从数据库取出的文件集合
     * @return List<Map<String,Object>>
     */
    private List<Map<String,Object>> getGridFSFiles(GridFSFindIterable gridFSFiles){
        List<Map<String,Object>> result = new ArrayList<>();
        for (GridFSFile file : gridFSFiles) {
            HashMap<String,Object> map = new HashMap<>(6);
            map.put("fileId",getFileId(file.getId().toString()));
            map.put("fileName",file.getFilename());
            map.put("fileExtension",file.getMetadata().get("fileExtension"));
            map.put("fileSize",file.getLength()/1024);
            map.put("uploadTime",file.getUploadDate());
            map.put("user",file.getMetadata().get("userId"));
            result.add(map);
        }
        return result;
    }
    /**
     * 因为从mongo中获取的文件Id是BsonObjectId {value=5d7068bbcfaf962be4c7273f}的样子
     * 需要字符串截取
     * @param bsonObjectId 数据库文件的BsonObjectId
     */
    private String getFileId(String bsonObjectId) {
        Matcher m = NUMBER_PATTERN.matcher(bsonObjectId);
        if(!m.find()){
            return bsonObjectId;
        }
        return m.group();
    }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 详解如何使用MongoDB+Springboot实现分布式ID的方法

    一.背景 如何实现分布式id,搜索相关的资料,一般会给出这几种方案: 使用数据库自增Id 使用reids的incr命令 使用UUID Twitter的snowflake算法 利用zookeeper生成唯一ID MongoDB的ObjectId 另外,在我通过爬取知乎用户id发现,知乎的用户id是32位的,初步断定知乎采用的是md5加密,然后全部转换成小写.至于如何爬取知乎用户信息,见我之前分享的文章.本文采取的技术方案采取的是mogoodb的objectId. 二.mongodb如何实现分布式I

  • MongoDB系列教程(八):GridFS存储详解

    GridFS简介 mongoDB的文档以BSON格式存储,支持二进制的数据类型,当我们把二进制格式的数据直接保存到mongoDB的文档中.但是当文件太大时,例如图片和视频等文件,每个文档的长度是有限的,于是mongoDb会提供了一种处理大文件的规范--GridFS. GridFS实现原理 在GridFS数据库中,默认使用fs.chunks 和fs.files来存储文件,其中fs.files集合存放文件的信息,fs.chunks存放文件的数据,一个fs.files集合中的一条记录内容如下,即一个f

  • Spring Boot中使用MongoDB数据库的方法

    MongoDB数据库简介 简介 MongoDB是一个高性能,开源,无模式的,基于分布式文件存储的文档型数据库,由C++语言编写,其名称来源取自"humongous",是一种开源的文档数据库──NoSql数据库的一种.NoSql,全称是 Not Only Sql,指的是非关系型的数据库. 特点 MongoDB数据库的特点是高性能.易部署.易使用,存储数据非常方便.主要功能特性有: * 面向集合存储,易存储对象类型的数据. * 模式自由. * 支持动态查询. * 支持完全索引,包含内部对象

  • SpringBoot 使用Mongo的GridFs实现分布式文件存储操作

    目录 前言 GridFs介绍 什么时候使用GridFs GridFs的原理 环境 引入依赖和项目配置 使用GridFsTemplate操作GridFs 前言 这段时间在公司实习,安排给我一个任务,让在系统里实现一个知识库的模块,产品说,就像百度网盘那样...我tm-,这不就是应了那句话,"这个需求很简单,怎么实现我不管". 可是我google小能手怎么会认输呢,本来还说研究一下FastDFS啥的,但是因为我们项目用的Mongo作为数据库,了解到Mongo自带分布式文件系统GridFs,

  • Android编程之SharedPreferences文件存储操作实例分析

    本文实例讲述了Android编程之SharedPreferences文件存储操作的方法.分享给大家供大家参考.具体分析如下: SharedPreferences类提供了一种简单的文件存储功能,像程序的配置文件可以通过它来实现. 源代码: package com.test.sharedpreferences; import android.app.Activity; import android.content.Context; import android.content.SharedPrefe

  • SpringBoot整合redis中的JSON序列化文件夹操作小结

    目录 前言 快速配置 JSON序列化 jackson序列化 Fastjson序列化 分析参考对比 更多问题参考 redis数据库操作 前言 最近在开发项目,用到了redis作为缓存,来提高系统访问速度和缓解系统压力,提高用户响应和访问速度,这里遇到几个问题做一下总结和整理 快速配置 SpringBoot整合redis有专门的场景启动器整合起来还是非常方便的 <dependency> <groupId>org.springframework.boot</groupId>

  • SpringBoot中使用Session共享实现分布式部署的示例代码

    前言:我们知道,在单体项目中,我们将用户信息存在 session 中,那么在该 session 过期之前,我们都可以从 session 中获取到用户信息,通过登录拦截,进行操作 但是分布式部署的时候,我们请求的服务器可能不是同一台服务器,那么我们就必须要面对 session 共享的问题,下面介绍的是在 SpringBoot 实现 session 共享的方式 一.创建项目 创建 SpringBoot 项目,选择 Maven 依赖 最终 pom.xml 文件如下: <!-- redis的依赖 -->

  • SpringBoot整合Redis正确的实现分布式锁的示例代码

    前言 最近在做分块上传的业务,使用到了Redis来维护上传过程中的分块编号. 每上传完成一个分块就获取一下文件的分块集合,加入新上传的编号,手动接口测试下是没有问题的,前端通过并发上传调用就出现问题了,并发的get再set,就会存在覆盖写现象,导致最后的分块数据不对,不能触发分块合并请求. 遇到并发二话不说先上锁,针对执行代码块加了一个JVM锁之后问题就解决了. 仔细一想还是不太对,项目是分布式部署的,做了负载均衡,一个节点的代码被锁住了,请求轮询到其他节点还是可以进行覆盖写,并没有解决到问题啊

  • SpringBoot MongoDB与MongoDB GridFS基本使用

    目录 MongoDB的基本使用 添加依赖 配置application.yml 配置启动类 配置日志 创建User文档对象 创建UserRepository 执行测试 GridFS的基本使用 GridFS概述 存放文件 读取文件 删除文件 MongoDB的基本使用 添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-

  • SpringBoot集成redis与session实现分布式单点登录

    目录 单点登录 SSO(Single Sign On) 什么是单点登录? 实现方式 开发技术 单点登录实现流程 实现案例 看效果 前言: 由于考虑到cookie的安全性问题,就有了下面这个版本的sso 单点登录 SSO(Single Sign On) 什么是单点登录? 单点登录的英文名叫做:Single Sign On(简称SSO),指在同一帐号平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的系统.简而言之,多个系统,统一登陆. 我们可以这样理解,在一个服务模块登录后,其他模块无

  • springboot整合vue实现上传下载文件

    springboot整合vue实现上传下载文件,供大家参考,具体内容如下 环境 springboot 1.5.x 完整代码下载:springboot整合vue实现上传下载 1.上传下载文件api文件 设置上传路径,如例子: private final static String rootPath = System.getProperty("user.home")+File.separator+fileDir+File.separator; api接口: 下载url示例:http://l

  • FastDFS分布式文件系统环境搭建及安装过程解析

    FastDFS:分布式文件系统 它对文件进行管理,功能包括:文件存储.文件同步.文件访问(文件上传.文件下载)等,解决了大容量存储和负载均衡的问题. 特别适合以文件为载体的在线服务,如相册网站.视频网站等等. FastDFS为互联网量身定制,充分考虑了冗余备份.负载均衡.线性扩容等机制,并注重高可用.高性能等指标, 使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传.下载等服务. FastDFS服务端有两个角色: 跟踪器(tracker)和存储节点(storage). 跟踪器主要

  • Springboot 项目读取Resources目录下的文件(推荐)

    需求描述:企业开发过程中,经常需要将一些静态文本数据放到Resources目录下,项目启动时或者程序运行中,需要读取这些文件. 读取Resources目录下文件的方法 /** * @Description: 读取resources 目录下的文件 * @Author: ljj * @CreateDate: 2020/11/3 17:20 * @UpdateUser: * @UpdateDate: * @UpdateReakem * @param filePath * @Return: java.l

随机推荐