`spring-boot-data-mongodb` 動態建立 `bucketName`
阿新 • • 發佈:2021-09-06
當專案使用MongoDB
儲存檔案的時候,小於16M
是可以直接存在集合裡面的,如果大於就需要用到GridFs
來儲存了。
小於16M
儲存的集合是可以執行時動態建立的,但是大於不可以。GridFs
預設的儲存桶的名是fs
來定義的。具體可參考GridFSBucketImpl
實現類。這裡對這個類不做過多介紹。這裡主要介紹如何在程式執行時動態建立 bucketName
-
先看下
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
了 -
我們自定義一個類繼承
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()); } } }
-
把自定義的類裝配到
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
就可以實現插拔裝配自定義類 -
使用自定義配置類下載,程式碼如下
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()); } } }
最後
此寫法是我在專案中分析得到的。目前已經應用到我的專案中,如果你們有更好的方案,麻煩留言互相學習。如果指導有誤的地方,還請指出。謝謝。