`spring-boot-data-mongodb` 动态创建 `bucketName`

时间:2021-09-06
本文章向大家介绍`spring-boot-data-mongodb` 动态创建 `bucketName`,主要包括`spring-boot-data-mongodb` 动态创建 `bucketName`使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

当项目使用MongoDB存储文件的时候,小于16M是可以直接存在集合里面的,如果大于就需要用到GridFs来存储了。

小于16M存储的集合是可以运行时动态创建的,但是大于不可以。GridFs默认的存储桶的名是fs来定义的。具体可参考GridFSBucketImpl实现类。这里对这个类不做过多介绍。这里主要介绍如何在程序运行时动态创建 bucketName

  1. 先看下spring-boot-data-mongodb提供的模板类GridFsTemplate中具体方法。这里主要分析存储和查询的方法,该类的代码如下,该类有很多store重载方法

    public class GridFsTemplate extends GridFsOperationsSupport implements GridFsOperations, ResourcePatternResolver {
    
        private final MongoDbFactory dbFactory;
    
        private final @Nullable
        String bucket;
    
    
        public GridFsTemplate(MongoDbFactory dbFactory, MongoConverter converter) {
            this(dbFactory, converter, null);
        }
    
    
        public GridFsTemplate(MongoDbFactory dbFactory, MongoConverter converter, @Nullable String bucket) {
    
            super(converter);
    
            Assert.notNull(dbFactory, "MongoDbFactory must not be null!");
    
            this.dbFactory = dbFactory;
            this.bucket = bucket;
        }
    
    
        public ObjectId store(InputStream content, String filename) {
            return store(content, filename, (Object) null);
        }
    
    
        @Override
        public ObjectId store(InputStream content, @Nullable Object metadata) {
            return store(content, null, metadata);
        }
    
    
        @Override
        public ObjectId store(InputStream content, @Nullable Document metadata) {
            return store(content, null, metadata);
        }
    
    
        public ObjectId store(InputStream content, @Nullable String filename, @Nullable String contentType) {
            return store(content, filename, contentType, (Object) null);
        }
    
    
        public ObjectId store(InputStream content, @Nullable String filename, @Nullable Object metadata) {
            return store(content, filename, null, metadata);
        }
    
    
        public ObjectId store(InputStream content, @Nullable String filename, @Nullable String contentType,
                              @Nullable Object metadata) {
            return store(content, filename, contentType, toDocument(metadata));
        }
    
    
        public ObjectId store(InputStream content, @Nullable String filename, @Nullable Document metadata) {
            return this.store(content, filename, null, metadata);
        }
    
        /**
         * 主要储存实现
         */
        public ObjectId store(InputStream content, @Nullable String filename, @Nullable String contentType,
                              @Nullable Document metadata) {
    
            Assert.notNull(content, "InputStream must not be null!");
            // 主要getGridFs 返回一个 GridFSBucket,这个接口里面抽象大量的CURD的方法。具体你们可以看看
            return getGridFs().uploadFromStream(filename, content, computeUploadOptionsFor(contentType, metadata));
        }
    
    
        public GridFSFindIterable find(Query query) {
    
            Assert.notNull(query, "Query must not be null!");
    
            Document queryObject = getMappedQuery(query.getQueryObject());
            Document sortObject = getMappedQuery(query.getSortObject());
    
            return getGridFs().find(queryObject).sort(sortObject);
        }
    
       
        public GridFSFile findOne(Query query) {
            return find(query).first();
        }
    
    
        public void delete(Query query) {
    
            for (GridFSFile gridFSFile : find(query)) {
                getGridFs().delete(gridFSFile.getId());
            }
        }
    
    
        public ClassLoader getClassLoader() {
            return dbFactory.getClass().getClassLoader();
        }
    
     
        public GridFsResource getResource(String location) {
    
            return Optional.ofNullable(findOne(query(whereFilename().is(location)))) 
                    .map(this::getResource) 
                    .orElseGet(() -> GridFsResource.absent(location));
        }
    
        public GridFsResource getResource(GridFSFile file) {
    
            Assert.notNull(file, "GridFSFile must not be null!");
    
            return new GridFsResource(file, getGridFs().openDownloadStream(file.getId()));
        }
    
        public GridFsResource[] getResources(String locationPattern) {
    
            if (!StringUtils.hasText(locationPattern)) {
                return new GridFsResource[0];
            }
    
            AntPath path = new AntPath(locationPattern);
    
            if (path.isPattern()) {
    
                GridFSFindIterable files = find(query(whereFilename().regex(path.toRegex())));
                List<GridFsResource> resources = new ArrayList<>();
    
                for (GridFSFile file : files) {
                    resources.add(getResource(file));
                }
    
                return resources.toArray(new GridFsResource[0]);
            }
    
            return new GridFsResource[]{getResource(locationPattern)};
        }
    
        /**
         * 这个方法是最重要的
         */
        private GridFSBucket getGridFs() {
    
            MongoDatabase db = dbFactory.getDb();
            /* 
            * 判断 bucket 是否为null,是创建默认,否则根据传入bucket的创建GridFSBucket对象,默认创建的GridFsTemplate是不传入bucket
            * 具体可以参考MongoDbFactoryDependentConfiguration该类的bean创建实现
            * 
            * */
            return bucket == null ? GridFSBuckets.create(db) : GridFSBuckets.create(db, bucket);
        }
    }
    
    

    可以看到GridFsTemplate核心是getGridFs()方法。该类里面的所有操作都是依赖该方法的。我们主要针对这个就可以实现运行时动态创建bucketName

  2. 我们自定义一个类继承 GridFsTemplate即可,代码如下

    /**
     * 自定义 {@link GridFsTemplate} 实现,用于实现动态创建 bucket
     *
     * @author: pinlin
     * @date: 2021/9/1 17:03
     */
    public class CustomGridFsTemplate extends GridFsTemplate {
    
        private final MongoDbFactory dbFactory;
    
        public CustomGridFsTemplate(MongoDbFactory dbFactory, MongoConverter converter) {
            super(dbFactory, converter);
            this.dbFactory = dbFactory;
        }
    
        public CustomGridFsTemplate(MongoDbFactory dbFactory, MongoConverter converter, String bucket) {
            super(dbFactory, converter, bucket);
            this.dbFactory = dbFactory;
        }
    
        /**
         * 校验符合平台预设
         *
         * @param bucketName MongoDB 桶的名字 具体参考 
         * @throws CustomSystemException 抛出自定义异常
         * @author zpl
         * @date 2021/9/2 14:21
         */
        public static void isExist(String bucketName) throws CustomSystemException {
            if (StrUtil.isBlank(bucketName)) {
                throw new CustomSystemException("bucketName 名字不能为空");
            }
            // GridFsBucketEnum该枚举是我定义MongoDB的枚举类,主要用于校验传入bucketName是否是平台预设的。安全着想
            if (Boolean.FALSE.equals(GridFsBucketEnum.isExclude(bucketName))) {
                throw new CustomSystemException("bucketName 名字不符合平台预设");
            }
        }
    
        /**
         * 存储文件
         *
         * @param content     输入文件流
         * @param filename    文件名
         * @param contentType 文件类型
         * @param bucketName  桶的名字 
         * @return {@link ObjectId}
         * @throws CustomSystemException 抛出非受检异常,外部注意捕获
         * @author zpl
         * @date 2021/9/2 14:27
         */
        public ObjectId store(InputStream content, String filename, String contentType, String bucketName) throws CustomSystemException {
            return getGridFs(bucketName).uploadFromStream(filename, content, computeUploadOptionsFor(contentType, toDocument(null)));
        }
    
        /**
         * 创建 {@link GridFSBucket} 对象
         *
         * @param bucketName MongoDB的桶的名字
         * @return {@link GridFSBucket}
         * @author zpl
         * @date 2021/9/2 14:05
         */
        private GridFSBucket getGridFs(String bucketName) {
            isExist(bucketName);
            MongoDatabase db = dbFactory.getDb();
            if (StrUtil.isNotBlank(bucketName)) {
                return GridFSBuckets.create(db, bucketName);
            }
            return GridFSBuckets.create(db);
        }
    
        /**
         * 查询多个 返回 {@link GridFSFindIterable} 对象
         *
         * @param query      查询对象 {@link Query}
         * @param bucketName MongoDB桶的名字
         * @return {@link GridFSFindIterable}
         * @author zpl
         * @date 2021/9/2 14:17
         */
        public GridFSFindIterable find(Query query, String bucketName) {
            Assert.notNull(query, "Query 不能为空!");
            Assert.notNull(bucketName, "bucketName 不能为空!");
    
            Document queryObject = getMappedQuery(query.getQueryObject());
            Document sortObject = getMappedQuery(query.getSortObject());
    
            return getGridFs(bucketName).find(queryObject).sort(sortObject);
        }
    
        /**
         * 查询单个
         *
         * @param query      查询对象 {@link Query}
         * @param bucketName MongoDB桶的名字
         * @return {@link GridFSFile}
         * @throws CustomSystemException 抛出非受检异常,外部注意捕获
         * @author zpl
         * @date 2021/9/2 14:18
         */
        public GridFSFile findOne(Query query, String bucketName) {
            return find(query, bucketName).first();
        }
    
        /**
         * 删除
         *
         * @param query      查询对象 {@link Query}
         * @param bucketName MongoDB桶的名字
         * @throws CustomSystemException 抛出非受检异常,外部注意捕获
         * @author zpl
         * @date 2021/9/2 14:18
         */
        public void delete(Query query, String bucketName) {
            for (GridFSFile fsFile : find(query, bucketName)) {
                getGridFs(bucketName).delete(fsFile.getId());
            }
        }
    
    }
    
    
  3. 把自定义的类装配到spring容器,方便管理以及使用,代码如下

    /**
     * {@link org.springframework.data.mongodb.gridfs.GridFsTemplate} 创建
     *
     * @author: pinlin
     * @date: 2021/8/26 16:41
     */
    @Configuration
    public class GridFsTemplateConfig {
    
        @Bean(name = "gridFsTemplate")
        public CustomGridFsTemplate gridFsTemplate(MongoDbFactory mongoDbFactory, MongoConverter converter) {
            return new CustomGridFsTemplate(mongoDbFactory, converter);
        }
    }
    
    

    说明

    这个装配,如果是微服务工程,可以创建一个mongodb工程,把所有的封装都抽象在这个工程里面。然后写一个注解,使用spring-boot@Import的注解导入GridFsTemplateConfig就可以实现插拔装配自定义类

  4. 使用自定义配置类下载,代码如下

    public class Test {
    
        @Autowired
        private CustomGridFsTemplate customGridFsTemplate;
    
        @Autowired
        private MongoDbFactory mongoDbFactory;
    
        public void download(@PathVariable("fileId") String fileId,
                             @PathVariable("bucketName") String bucketName,
                             HttpServletResponse response,
                             HttpServletRequest request) throws IOException {
            Query query = Query.query(Criteria.where("_id").is(fileId));
            // 查询根据传入的bucketName ,这个bucketName最后和后台预设的对比以及校验写,不然别人乱传,就会导致创建很多个bucket储存桶,不安全
            GridFSFile gridFSFile = customGridFsTemplate.findOne(query, bucketName);
            if (gridFSFile != null) {
                GridFSBucket bucket = GridFSBuckets.create(mongoDbFactory.getDb(), bucketName);
                GridFSDownloadStream gridFSDownloadStream = bucket.openDownloadStream(gridFSFile.getObjectId());
                GridFsResource gridFsResource = new GridFsResource(gridFSFile, gridFSDownloadStream);
    
                // 获取文件名
                String fileName = FileUtils.getFileName(request, gridFSFile);
    
                response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment;filename=\"%s\"", fileName));
                response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(gridFSFile.getLength()));
                response.setHeader(HttpHeaders.CONTENT_TYPE, "application/octet-stream;charset=ISO8859-1");
                response.setHeader(HttpHeaders.CONTENT_TYPE, gridFsResource.getContentType());
                IOUtils.copy(gridFsResource.getInputStream(), response.getOutputStream());
            }
        }
    }
    

最后

此写法是我在项目中分析得到的。目前已经应用到我的项目中,如果你们有更好的方案,麻烦留言互相学习。如果指导有误的地方,还请指出。谢谢。

原文地址:https://www.cnblogs.com/zengpinlin/p/15235704.html