springboot-GridFS上传下载,小程序正常使用

1.MongoDB-GridFS介绍

1.1特点

官方文档

  • GridFS 是一种用于存储和检索超过 BSON 文档大小限制 16 MB 的文件的规范。
  • GridFS 默认将每个文件分割为255kB 的块,来进行存储
  • GridFS 用两个集合来存储一个文件,分别是fs.filesfs.chunksfiles用来存储文件元信息,chunks用来存储文件块

2.准备工作

2.1 MongoDB的starter

引入MongoDB所需要的依赖包

pom.xml

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

2.2 MongoDB连接配置

application.yml

spring:
  data:
    mongodb:
      uri: mongodb://localhost:27017/gridFS-test
  servlet:
    multipart:
      max-file-size: 10MB

3.GridFs文件上传

3.1 封装文件桶

在gridFS中有bucket的概念,整个文件的检索路径为database->bucket,在分布式系统中,如果需要文件共享的bucket所在的database和数据所在database不是同一个,就不能直接使用注入GridFsTemplate来存取文件,所以封装了拿到公共bucket的过程。

import com.mongodb.client.MongoDatabase;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSBuckets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;


import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 *  文件上传下载
 * @author WangKangSheng
 * @date 2022-06-05 18:07
 */
@RestController
@RequestMapping("/file")
public class FileController {

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 获得默认gridFS bucket,
     *  指定上传文件的库为 'def_gfs'
     *   这样做的好处是,多个系统使用不同的数据库连接,可以通过指定统一的数据库来创建GridFSBucket
     * @param mongoTemplate MongoTemplate
     * @return GridFSBucket
     */
    private GridFSBucket defaultGridFsBucket(MongoTemplate mongoTemplate){
        if (null == mongoTemplate){
            throw new NullPointerException("获取默认的GridFSBucket时mongoTemplate不能为空");
        }
        // 通过mongoTemplate来获取默认的数据库连接
        MongoDatabaseFactory mongoDatabaseFactory = mongoTemplate.getMongoDatabaseFactory();
        MongoDatabase db = mongoDatabaseFactory.getMongoDatabase("def_gfs");

        // 创建GridFSBucket 并指定文件系统使用的bucket
        return GridFSBuckets.create(db,"def_bucket");
    }
}

3.2文件上传接口

拿到GridFSBucket之后可以通过GridFSBucket#uploadFromStream()方法来上传文件到GridFS.

在这里插入图片描述

该方法有8个重载方法,这一次使用**ObjectId uploadFromStream(String filename, InputStream source);**来上传文件。

可以通过指定第三个参数GridFSUploadOptions来添加一些自定义的信息

import com.mongodb.client.MongoDatabase;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSBuckets;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

/**
 *  文件上传下载
 * @author WangKangSheng
 * @date 2022-06-05 18:07
 */
@RestController
@RequestMapping("/file")
public class FileController {

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 获得默认gridFS bucket,
     *  指定上传文件的库为 'def_gfs'
     *   这样做的好处是,多个系统使用不同的数据库连接,可以通过指定统一的数据库来创建GridFSBucket
     * @param mongoTemplate MongoTemplate
     * @return GridFSBucket
     */
    private GridFSBucket defaultGridFsBucket(MongoTemplate mongoTemplate){
        if (null == mongoTemplate){
            throw new NullPointerException("获取默认的GridFSBucket时mongoTemplate不能为空");
        }
        // 通过mongoTemplate来获取默认的数据库连接
        MongoDatabaseFactory mongoDatabaseFactory = mongoTemplate.getMongoDatabaseFactory();
        MongoDatabase db = mongoDatabaseFactory.getMongoDatabase("def_gfs");

        // 创建GridFSBucket 并指定文件系统使用的bucket
        return GridFSBuckets.create(db,"def_bucket");
    }

    /**
     * 上传文件到gfs
     * @param file MultipartFile 文件
     * @return String
     * @throws IOException 读取输入流时的异常
     */
    @PostMapping("/upload")
    public String upload(MultipartFile file) throws IOException {

        // 拿到gfs的bucket
        GridFSBucket gridFsBucket = defaultGridFsBucket(this.mongoTemplate);

        // 读取文件名
        String filename = file.getOriginalFilename();

        // 上传文件到gfs  并得到返回的object id
        ObjectId objectId = gridFsBucket.uploadFromStream(filename, file.getInputStream());

        return "文件上传成功!文件名为:[%s] 文件id为:[%s]".formatted(filename,objectId.toHexString());
    }
    
}

3.3postman测试上传

3.3.1 上传文件

在这里插入图片描述

3.3.2 查看gridFS

文件已经上传到我们指定的database和bucket中了

在这里插入图片描述

文件信息

在这里插入图片描述

3.3.3查看集合信息

使用命令可以看到**files**集合中的文件信息

在这里插入图片描述

查看**chunks**集合的内容

集合中存储着文件内容和对应的文件id
在这里插入图片描述

4.下载文件

4.1 文件下载接口

通过**GridFSBucket#downloadToStream()**直接将文件写出到输出流

也可以使用**GridFSBucket#openDownloadStream()**来打开输出流,自己选择处理流

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 org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;

/**
 *  文件上传下载
 * @author WangKangSheng
 * @date 2022-06-05 18:07
 */
@RestController
@RequestMapping("/file")
public class FileController {

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 获得默认gridFS bucket,
     *  指定上传文件的库为 'def_gfs'
     *   这样做的好处是,多个系统使用不同的数据库连接,可以通过指定统一的数据库来创建GridFSBucket
     * @param mongoTemplate MongoTemplate
     * @return GridFSBucket
     */
    private GridFSBucket defaultGridFsBucket(MongoTemplate mongoTemplate){
        if (null == mongoTemplate){
            throw new NullPointerException("获取默认的GridFSBucket时mongoTemplate不能为空");
        }
        // 通过mongoTemplate来获取默认的数据库连接
        MongoDatabaseFactory mongoDatabaseFactory = mongoTemplate.getMongoDatabaseFactory();
        MongoDatabase db = mongoDatabaseFactory.getMongoDatabase("def_gfs");

        // 创建GridFSBucket 并指定文件系统使用的bucket
        return GridFSBuckets.create(db,"def_bucket");
    }

    /**
     * 下载、预览文件
     * @param fileId String 文件的objectId
     * @param response HttpServletResponse
     */
    @RequestMapping("/download/{fid}")
    public void download(@PathVariable("fid") String fileId, HttpServletResponse response) throws IOException {
        if (null == fileId || fileId.isEmpty() || !ObjectId.isValid(fileId)){
            response.setStatus(HttpStatus.NOT_FOUND.value());
            response.getWriter().println("File id is empty or not object id.");
            return;
        }

        // 拿到bucket
        GridFSBucket gridFsBucket = defaultGridFsBucket(this.mongoTemplate);

        ObjectId objectId = new ObjectId(fileId);
        // 查询文件
        GridFSFile file = gridFsBucket.find(new Document("_id", objectId)).first();

        // 没有查询到文件
        if (null == file){
            response.setStatus(HttpStatus.NOT_FOUND.value());
            response.getWriter().println("File not found.");
            return;
        }


        response.setContentType("application/octet-stream");
        // 下载文件能正常显示中文
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getFilename(), "UTF-8"));

        // 写出到response
        gridFsBucket.downloadToStream(objectId,response.getOutputStream());

    }

}

4.2 文件下载测试

浏览器根据返回可以正常打开文件保存窗口

文件可以正常打开

在这里插入图片描述

4.3 视频预览出现问题

当前的文件下载和文件预览没有任何问题,但是却在小程序的视频预览中出现问题,以下是在开发者工具中进行调试,没有发生问题,
在这里插入图片描述

但是却在手机端预览的时候频繁报错,而且视频打不开。以下分别是手机端的预览效果和后台的控制台报错。

在这里插入图片描述

出现这个错误的原因是微信小程序默认用分段流读取的方式来加载视频,而我们当前的接口是一次性返回的
在这里插入图片描述

4.4 改造文件下载

使用**ResourceHttpRequestHandler来处理文件下载,重写内部的protected Resource getResource(HttpServletRequest request)方法,自定义返回GridFsResource**来处理下载

4.4.1 GridFsResourceHttpRequestHandler
import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.model.GridFSFile;
import org.springframework.core.io.Resource;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.ResourceRegionHttpMessageConverter;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @author WangKangSheng
 * @date 2022-06-05 20:05
 */
public class GridFsResourceHttpRequestHandler extends ResourceHttpRequestHandler {

    private final GridFSFile file;
    private final GridFSDownloadStream stream;

    public GridFsResourceHttpRequestHandler(GridFSFile file, GridFSDownloadStream stream) {
        this.file = file;
        this.stream = stream;
        // 这两行代码必须有 否则会报 Not initialized 错误
        this.setResourceRegionHttpMessageConverter(new ResourceRegionHttpMessageConverter());
        this.setResourceHttpMessageConverter(new ResourceHttpMessageConverter());
    }

    @Override protected Resource getResource(HttpServletRequest request) throws IOException {
        return new GridFsResource(file,stream);
    }

}
4.4.2 修改后的文件下载接口
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSBuckets;
import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.model.GridFSFile;
import com.yjntc.demo.gfs.config.GridFsResourceHttpRequestHandler;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 *  文件上传下载
 * @author WangKangSheng
 * @date 2022-06-05 18:07
 */
@RestController
@RequestMapping("/file")
public class FileController {

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 获得默认gridFS bucket,
     *  指定上传文件的库为 'def_gfs'
     *   这样做的好处是,多个系统使用不同的数据库连接,可以通过指定统一的数据库来创建GridFSBucket
     * @param mongoTemplate MongoTemplate
     * @return GridFSBucket
     */
    private GridFSBucket defaultGridFsBucket(MongoTemplate mongoTemplate){
        if (null == mongoTemplate){
            throw new NullPointerException("获取默认的GridFSBucket时mongoTemplate不能为空");
        }
        // 通过mongoTemplate来获取默认的数据库连接
        MongoDatabaseFactory mongoDatabaseFactory = mongoTemplate.getMongoDatabaseFactory();
        MongoDatabase db = mongoDatabaseFactory.getMongoDatabase("def_gfs");

        // 创建GridFSBucket 并指定文件系统使用的bucket
        return GridFSBuckets.create(db,"def_bucket");
    }

    /**
     * 下载、预览文件
     * @param fileId String 文件的objectId
     * @param response HttpServletResponse
     */
    @RequestMapping("/download/{fid}")
    public void download(@PathVariable("fid") String fileId, HttpServletResponse response, HttpServletRequest request) throws IOException, ServletException {
        if (null == fileId || fileId.isEmpty() || !ObjectId.isValid(fileId)){
            response.setStatus(HttpStatus.NOT_FOUND.value());
            response.getWriter().println("File id is empty or not object id.");
            return;
        }

        // 拿到bucket
        GridFSBucket gridFsBucket = defaultGridFsBucket(this.mongoTemplate);

        ObjectId objectId = new ObjectId(fileId);
        // 查询文件
        GridFSFile file = gridFsBucket.find(new Document("_id", objectId)).first();

        // 没有查询到文件
        if (null == file){
            response.setStatus(HttpStatus.NOT_FOUND.value());
            response.getWriter().println("File not found.");
            return;
        }

        GridFSDownloadStream downloadStream = gridFsBucket.openDownloadStream(objectId);

        // 使用自定义的ResourceHttpRequestHandler
        GridFsResourceHttpRequestHandler gridFsResourceHttpRequestHandler = new GridFsResourceHttpRequestHandler(file, downloadStream);
        gridFsResourceHttpRequestHandler.handleRequest(request,response);

    }

}

修改后的接口在小程序,浏览器等都能正常使用了

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不想写代码~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值