MongoDB GridFS 存储文件

前言

在开发中,会碰到存储图片或者视频的问题,也就是大文件存储的问题,能想到的有三种解决方式:

  • 本地文件系统
  • 数据库,比如 MongoDB
  • 云存储,比如 Amazon S3

其他两种都比较好理解,现在想来看一下 MongoDB 是怎么来存储图片或者视频的。看到官网的文档(版本是 5.0)是如果文件大小 < 16MB,一个文档是可以存的下的,超过 16MB 的话需要用 GridFS

关于 GridFS

GridFS 是 MongoDB 中存储超过 16M 文件的存储方式和约定,实现的思路就是分而治之。GridFS 默认的块(chunk)大小为 256kB,用两个 collection(chunks) 去存文件,一个 collection(files) 来存储切分的 chunk,另外一个 collection 来存储文件的元数据。这两个 collection 都在同一个 bucket 名为 fs 下。

什么时候用 GridFS

在 MongoDB 中,虽然使用 GridFS 来存储超过 16MB 大小的文件,但是有些情况下,用 MongoDB 来存储文可能比本地文件系统来存储有效的多。官方文档给了下面三种情况:

  • 本地文件系统有文件或者目录的限制,GridFS 没有限制
  • 不需要拿整个文件加载到内存就可以取到文件的所需要的部分数据,GridFS 可以做到。
  • 文件自动同步,尤其在分布式的应用上。

怎么使用 GridFS

基于 spring boot 搭建的示例项目来展示一下:

  • 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 配置上传单个文件大小无限制
    spring.servlet.multipart.max-file-size=-1
    # 配置上传总大小无限制
    spring.servlet.multipart.max-request-size=-1

    # MongoDB 的配置
    spring.data.mongodb.uri=mongodb://root:root123@127.0.0.1:27017
    spring.data.mongodb.database=test
    spring.data.mongodb.gridfs.database=uploads
  • thymeleaf 模板页面代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    <html xmlns:th="https://www.thymeleaf.org">
    <body>

    <h1><font color="blue">上传文件到 MongoDB </font> </h1>


    <div th:if="${message}">
    <h2 th:text="${message}"/>
    </div>

    <div>
    <form method="POST" enctype="multipart/form-data" action="/gridFs/upload">
    <table>
    <tr>
    <td>File to upload:</td>
    <td><input type="file" name="file"/></td>
    </tr>
    <tr>
    <td></td>
    <td><input type="submit" value="Upload"/></td>
    </tr>
    </table>
    </form>
    <form method="POST" action="/gridFs/delete">
    <table>
    <tr>
    <td></td>
    <td><input type="submit" value="deleteAll"/></td>
    </tr>
    </table>
    </div>

    <div>
    <ul>
    <li th:each="file : ${files}">
    <a th:href="@{'http://127.0.0.1:8080/gridFs/' + ${file} + '/download'}" th:text="${file}"/>
    </li>
    </ul>
    </div>
    </body>
    </html>

  • Controller 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    @Controller
    @RequestMapping("/gridFs")
    public class GridFsController {

    private final GridFsService gridFsService;

    @Autowired
    public GridFsController(GridFsService gridFsService) {
    this.gridFsService = gridFsService;
    }

    // 主页
    @GetMapping("/")
    public String listUploadFiles(Model model) {
    model.addAttribute("files", gridFsService.loadAllFiles());
    return "gridFsForm";
    }

    // 上传接口
    @PostMapping("/upload")
    public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {
    final long start = System.currentTimeMillis();
    gridFsService.upload(file, "user" + new Random().nextInt(100));
    final long end = System.currentTimeMillis();

    final long period = end - start;
    final long t = period / 1000;

    redirectAttributes.addFlashAttribute("message",
    "You successfully uploaded " + file.getOriginalFilename() + "!" +
    " spent time : " + ( t < 1 ? period + "ms" : t + "s"));
    return "redirect:/gridFs/";
    }

    // 下载接口
    @GetMapping("/{filename}/download")
    @ResponseBody
    public ResponseEntity<Resource> serverFile(@PathVariable String filename) {
    Resource file = gridFsService.download(filename);
    return ResponseEntity.ok()
    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(file.getFilename(),
    StandardCharsets.UTF_8) + "\"")
    .body(file);
    }

    // 删除所有的文件接口
    @PostMapping("/delete")
    public String deleteAllFiles(RedirectAttributes redirectAttributes) {
    gridFsService.deleteAll();
    redirectAttributes.addFlashAttribute("message",
    "You successfully deleted all files !");
    return "redirect:/gridFs/";
    }

    }
  • Service 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    @Component
    public class GridFsServiceImpl implements GridFsService {

    @Autowired
    GridFsTemplate gridFsTemplate;

    @Override
    public void upload(MultipartFile file, String name) {
    final String fileName = file.getName();
    System.out.println(fileName);
    final BasicDBObject basicDBObject = new BasicDBObject();
    basicDBObject.put("user", name);
    ObjectId objectId = null;
    try {
    objectId = gridFsTemplate.store(file.getInputStream(),
    file.getOriginalFilename(),
    file.getContentType(),
    basicDBObject);
    } catch (IOException e) {
    e.printStackTrace();
    }
    System.out.println("object id:" + objectId.toString());
    }

    @Override
    public List<String> loadAllFiles() {
    List<GridFSFile> gridFSFiles = new ArrayList<>();
    gridFsTemplate.find(new Query()).into(gridFSFiles);
    final List<String> files = gridFSFiles.stream().map(GridFSFile::getFilename).collect(Collectors.toList());
    return files;
    }

    @Override
    public Resource download(String fileName) {
    final GridFsResource[] resources = gridFsTemplate.getResources(fileName + "*");
    return resources[0];
    }

    @Override
    public void deleteAll() {
    gridFsTemplate.delete(new Query());
    }
    }
  • 演示
    使用 GridFS 上传文件到 MongoDB

作者

操先森

发布于

2021-10-11

更新于

2021-10-11

许可协议

评论