如何使用 Spring Data MongoDB 通过 GridFS ObjectId 获取二进制流
Posted
技术标签:
【中文标题】如何使用 Spring Data MongoDB 通过 GridFS ObjectId 获取二进制流【英文标题】:How to get a binary stream by GridFS ObjectId with Spring Data MongoDB 【发布时间】:2018-08-15 16:09:09 【问题描述】:当我已经拥有正确的 ObjectId
时,我无法弄清楚如何使用 spring-data-mongodb 及其 GridFSTemplate
从 GridFS 流式传输二进制文件。
GridFSTemplate 返回GridFSResource
(getResource()
) 或GridFSFile
(findX()
)。
我可以通过 ID 获得GridFSFile
:
// no way to get the InputStream?
GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(id)))
但没有明显的方法可以为该GridFSFile
获取InputStream
。
只有GridFSResource
允许我获取对应的InputStream
和InputStreamResource#getInputstream
。但获得GridFSResource
的唯一方法是通过filename
。
// no way to get GridFSResource by ID?
GridFSResource resource = gridFsTemplate.getResource("test.jpeg");
return resource.getInputStream();
不知何故,GridFsTemplate
API 暗示文件名是唯一的——它们不是。 GridFsTemplate
实现只返回第一个元素。
现在我正在使用本机 MongoDB API,一切都变得有意义了:
GridFS gridFs = new GridFs(mongo);
GridFSDBFile nativeFile = gridFs.find(blobId);
return nativeFile.getInputStream();
看起来我误解了 Spring Data Mongo GridFS 抽象背后的基本概念。我希望(至少)以下事情之一是可能的/真实的:
通过其 ID 获取GridFSResource
获取GridFSResource
或InputStream
以获得GridFsFile
我已经拥有了
我错了还是 Spring Data MongoDB API 的这个特定部分有什么奇怪的地方?
【问题讨论】:
【参考方案1】:我也偶然发现了这一点。事实上,GridFsTemplate 的设计是这样的,这让我感到非常震惊...... 无论如何,到目前为止,我对此的丑陋“解决方案”:
public GridFsResource download(String fileId)
GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
return new GridFsResource(file, getGridFs().openDownloadStream(file.getObjectId()));
private GridFSBucket getGridFs()
MongoDatabase db = mongoDbFactory.getDb();
return GridFSBuckets.create(db);
注意:您必须注入 MongoDbFactory 才能使其工作...
【讨论】:
所有互联网海上唯一真正的解决方案,非常非常非常非常非常非常非常非常感谢您 是的,我做了完全一样的......只是从spring项目中复制getGridFs函数并实现一个新的getResource。他们应该将其添加到原始代码中。 您如何使用此解决方案处理 mediaType?谢谢 很高兴我不是唯一一个认为 GridFS 的 Mongo 功能和 Spring Data 围绕它的抽象之间存在令人惊讶的阻抗不匹配的人。【参考方案2】:这些类型有点乱:
GridFSFile 是来自 MongoDB 驱动程序的类型 GridFsResource 是 Spring 的类型 ObjectId 是来自 BSON API 的类型来自 Spring GridFsTemplate source:
public getResource(String location)
GridFSFile file = findOne(query(whereFilename().is(location)));
return file != null ? new GridFsResource(file, getGridFs().openDownloadStream(location)) : null;
有一个丑陋的解决方案:
@Autowired
private GridFsTemplate template;
@Autowired
private GridFsOperations operations;
public InputStream loadResource(ObjectId id) throws IOException
GridFSFile file = template.findOne(query(where("_id").is(id)));
GridFsResource resource = template.getResource(file.getFilename());
GridFSFile file = operations.findOne(query(where("_id").is(id)));
GridFsResource resource = operations.getResource(file.getFilename());
return resource.getInputStream();
【讨论】:
我想您终于可以按名称检索文件了。如果您有 2 个同名文件,并且需要第 2 个,则 GridFSFile 文件是正确的 obj,但 GridFsResource 资源是按名称查找的。是哪个?【参考方案3】:我发现了解决这个问题的方法!
只需将 GridFSFile 包装在 GridFsResource 中!这是为使用 GridFSFile 实例化而设计的。
public GridFsResource getUploadedFileResource(String id)
var file = this.gridFsTemplate.findOne(new Query(Criteria.where("_id").is(id)));
return new GridFsResource(file);
@GetMapping("/userId/files/id")
public ResponseEntity<InputStreamResource> getUploadedFile(
@PathVariable Long userId,
@PathVariable String id
)
var user = userService
.getCurrentUser()
.orElseThrow(EntityNotFoundException::new);
var resource = userService.getUploadedFileResource(id);
try
return ResponseEntity
.ok()
.contentType(MediaType.parseMediaType(resource.getContentType()))
.contentLength(resource.contentLength())
.body(resource);
catch (IOException e)
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
这样做的最大优点是,您可以直接将 GridFsResource 传递给 ResponseEntity,因为 GridFsResource 扩展了 InputStreamResource。
希望这会有所帮助!
问候 尼克拉斯
【讨论】:
【参考方案4】:您是否考虑过使用 Spring Content for Mongo 作为您解决方案中的内容存储部分?
假设您使用的是 Spring Boot 以及 Spring Data Mongo,那么它可能如下所示:
pom.xml
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-mongo-boot-starter</artifactId>
<version>0.0.10</version>
</dependency>
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest-boot-starter</artifactId>
<version>0.0.10</version>
</dependency>
使用以下属性更新您的 Spring Data Mongo 实体:
@ContentId
private String contentId;
@ContentLength
private long contentLength = 0L;
@MimeType
private String mimeType;
添加商店界面:
@StoreRestResource(path="content")
public interface MongoContentStore extends ContentStore<YourEntity, String>
这就是你所需要的。当您的应用程序启动时,Spring Content 将看到 Spring Content Mongo/REST 模块的依赖关系,它将为 GridFs 注入 MongonContenStore
存储的实现以及支持完整 CRUD 功能并将这些操作映射下来的控制器的实现到底层商店界面。 REST 端点将在/content
下可用。
即
curl -X PUT /content/entityId
将创建或更新实体的图像
curl -X GET /content/entityId
将获取实体的图像
curl -X DELETE /content/entityId
将删除实体的图像
有一些入门指南here。他们将 Spring Content 用于文件系统,但模块是可互换的。 Mongo 参考指南是here。还有教程视频here。
HTH
【讨论】:
以上文章看起来不错。感谢分享。假设我正在使用上面的 api 上传 pdf、word、文本文件等,现在我想根据用户输入搜索文件内容。如果用户输入了一个文本,它存在于 3 个文件中,所以我想显示 3 个文件。对此有任何建议。 是的,Spring Content 有两个全文索引模块。一个用于 solr,一个用于 elasticsearch。如果您将这些包含在您的类路径中并配置到相关服务器的连接 bean,那么当您添加内容时,它将被发送以进行全文索引,然后可以进行搜索。 这里有一个入门指南:paulcwarren.github.io/spring-content/… Paul,我实施了您提到的解决方案,但是在搜索内容时出现此错误 ->org.elasticsearch.ElasticsearchStatusException: Elasticsearch exception [type=index_not_found_exception, reason=no such index [spring-content-fulltext- index]] 由于需要一些修改,我复制了 DefaultMongoStoreImpl 和 SearchableImpl 的代码,Elasticsearch 也在运行。请建议。 @Anand 这个错误通常意味着你运行的是不兼容的 elasticsearch 版本。 Spring Content 1.0.x 针对 6.8.7 进行了测试,Spring Content 1.1.x 目前针对 7.8.2 进行了测试,如果这有帮助吗?【参考方案5】:将 GridFSFile 包装在 GridFsResource 中或使用它
GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
GridFsResource resource = gridFsTemplate.getResource(file);
return resource.getInputStream();
【讨论】:
【参考方案6】:GridFsTemplate 的getResource(com.mongodb.client.gridfs.model.GridFSFile file) 函数返回GridFSFile 的GridFsResource。
GridFSFile gridfsFile= gridFsTemplate.findOne(new
Query(Criteria.where("filename").is(fileName)));
GridFsResource gridFSResource= gridFsTemplate.getResource(gridfsFile);
InputStream inputStream= gridFSResource.getInputStream();
如果上述方法在某些更高版本的 Spring boot 中不起作用,请使用以下方法:
GridFSFile gridfsFile= gridFsTemplate.findOne(new
Query(Criteria.where("filename").is(fileName)));
//or
GridFSFile gridfsFile =
gridFsOperations.findOne(Query.query(Criteria.where("filename").is(fileName)));
return ResponseEntity.ok()
.contentLength(gridFsdbFile.getLength())
.contentType(MediaType.valueOf("image/png"))
.body(gridFsOperations.getResource(gridFsdbFile));
【讨论】:
【参考方案7】:@RequestMapping(value = "/api ")
public class AttachmentController
private final GridFsOperations gridFsOperations;
@Autowired
public AttachmentController(GridFsOperations gridFsOperations)
this.gridFsOperations = gridFsOperations;
@GetMapping("/file/fileId")
public ResponseEntity<Resource> getFile(@PathVariable String fileId)
GridFSFile file =
gridFsOperations.findOne(Query.query(Criteria.where("_id").is(fileId)));
return ResponseEntity.ok()
.contentLength(file.getLength())
.body(gridFsOperations.getResource(file));
【讨论】:
【参考方案8】:我知道的老问题,但尝试在 2019 年使用 WebFlux 执行此操作,我必须执行以下操作
public Mono<GridFsResource> getImageFromDatabase(final String id)
return Mono.fromCallable(
() ->
this.gridFsTemplate.getResource(
Objects.requireNonNull(
this.gridFsTemplate.findOne(new Query(Criteria.where("_id").is(id))))
.getFilename()));
这会给你一个Mono
,它可以在控制器中返回。不过,我确信有更好的解决方案。
【讨论】:
【参考方案9】:Spring Data 2.1.0 向GridFsTemplate
添加了getResource()
的重载,它返回给定GridFsFile
的GridFsResource
。 GridFsResource
有一个获取InputStream
的方法。因此,如果您至少使用此版本的 Spring Data,则可以通过两次调用 GridFsTemplate
来获取 InputStream
:
GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(id)));
// In real code, make sure you perform any necessary null checks if the file doesn't exist
GridFsResource resource = gridFsTemplate.getResource(gridFsFile);
InputStream inputStream = resource.getInputStream();
【讨论】:
【参考方案10】:GridFSDBFile file = ...
ByteArrayOutputStream baos = new ByteArrayOutputStream();
file.writeTo(baos);
byte[] ba = baos.toByteArray()
【讨论】:
以上是关于如何使用 Spring Data MongoDB 通过 GridFS ObjectId 获取二进制流的主要内容,如果未能解决你的问题,请参考以下文章
MongoDB Aggregation - 如何使用 spring-data-mongodb 将查询表达式应用到匹配阶段?
如何利用spring data mongodb 进行多条件查询
如何使用 Spring Data MongoDB 通过 GridFS ObjectId 获取二进制流
Spring Data MongoDB:如何实现“实体关系”?