如何使用 Spring 数据 MongoDB 通过 GridFS ObjectId 获取二进制流

How to get a binary stream by GridFS ObjectId with Spring Data MongoDB

当我已经拥有正确的 ObjectId.

GridFSTemplate returns 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能让我得到对应的InputStreamInputStreamResource#getInputstream。但是获得 GridFSResource 的唯一方法是通过它的 filename.

// no way to get GridFSResource by ID?
GridFSResource resource = gridFsTemplate.getResource("test.jpeg");
return resource.getInputStream();

不知何故 GridFsTemplate API 暗示文件名是唯一的——但事实并非如此。 GridFsTemplate 实现只是 returns 第一个元素。

现在我正在使用本机 MongoDB API 一切又变得有意义了:

GridFS gridFs = new GridFs(mongo);
GridFSDBFile nativeFile = gridFs.find(blobId);
return nativeFile.getInputStream();

看来我误解了 Spring 数据 Mongo GridFS 抽象背后的基本概念。我希望(至少)以下内容之一是 possible/true:

我是不是错了,或者 Spring 数据 MongoDB API 的这个特定部分有什么奇怪的?

GridFSDBFile file = ... 
ByteArrayOutputStream baos = new ByteArrayOutputStream();
file.writeTo(baos);
byte[] ba = baos.toByteArray()

这些类型有点乱:

来自 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();
}

我也偶然发现了这个。我真的很震惊 GridFsTemplate 的设计是这样的...... 无论如何,到目前为止,我的丑陋 "solution":

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 Content 用于 Mongo 作为您解决方案的内容存储部分?

假设您正在使用 Spring 引导以及 Spring 数据 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 数据 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. They use Spring Content for the filesystem but the modules are interchangeable. The Mongo reference guide is here. And there is a tutorial video here

HTH

我找到了解决这个问题的方法!

只需将 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。

希望对您有所帮助!

您好 尼克拉斯

将 GridFSFile 包装在 GridFsResource 中或使用此

GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileId)));
GridFsResource resource = gridFsTemplate.getResource(file);
return resource.getInputStream();

GridFsTemplate 的 getResource(com.mongodb.client.gridfs.model.GridFSFile file) 函数 returns GridFSFile 的 GridFsResource。

GridFSFile gridfsFile= gridFsTemplate.findOne(new 
Query(Criteria.where("filename").is(fileName)));
GridFsResource gridFSResource= gridFsTemplate.getResource(gridfsFile);
InputStream inputStream= gridFSResource.getInputStream();

如果上面的方法在某些更高版本的 Spring 引导中不起作用,请使用以下命令:

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));
@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));
}

我知道是个老问题,但在 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,它可以在控制器中返回。不过,我确定有更好的解决方案。

Spring Data 2.1.0 添加了 getResource()GridFsTemplate 的重载,对于给定的 GridFsFile returns GridFsResourceGridFsResource 有一个获取 InputStream 的方法。因此,如果您至少使用此版本的 Spring 数据,则可以通过两次调用 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();