Как получить двоичный поток с помощью GridFS ObjectId с помощью Spring Data MongoDB

Я не могу понять, как передавать двоичный файл из GridFS с помощью spring-data-mongodb и его GridFSTemplate когда у меня уже есть правильный ObjectId.

GridFSTemplate возвращает либо GridFSResource (getResource()), либо GridFSFile (findX()).

Я могу получить GridFSFile по ID:

// no way to get the InputStream?
GridFSFile file = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(id)))

но нет очевидного способа, как получить InputStream для этого GridFSFile.

Только 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 подразумевает, что имена файлов уникальны - а это не так. Реализация GridFsTemplate просто возвращает первый элемент.

Теперь я использую нативный API MongoDB, и все снова имеет смысл:

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

Похоже, я неправильно понимаю фундаментальные концепции, лежащие в основе абстракции Spring Data Mongo GridFS. Я ожидаю (по крайней мере) одну из следующих вещей, которые будут возможны/верны:

  • получить 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...

Ответ 2

В этих типах есть немного беспорядка:

  • GridFSFile является типом из драйвера MongoDB
  • GridFsResource является типом из Spring
  • ObjectId является типом из BSON API

От Spring GridFsTemplate источника:

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

Ответ 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 Контент для 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 Содержимое будет видеть зависимости от модулей Spring Content Mongo/REST, и оно будет внедрять реализацию хранилища MongonContenStore для GridFs, а также реализацию контроллера, который поддерживает полную функциональность CRUD и отображает эти операции на основной интерфейс хранилища. Конечная точка REST будет доступна под /content.

то есть.

curl -X PUT /content/{entityId} создаст или обновит изображение объекта

curl -X GET /content/{entityId} отобразит изображение объекта

curl -X DELETE /content/{entityId} удалит изображение объекта

Ниже приведены несколько руководств здесь. Они используют Spring Содержимое для файловой системы, но модули взаимозаменяемы. Справочник по Mongo здесь. И здесь есть учебное видео здесь.

НТН

Ответ 5

Оберните GridFSFile в GridFsResource или используйте это

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

Ответ 6

Функция getResource (файл com.mongodb.client.gridfs.model.GridFSFile) GridFsTemplate возвращает GridFsResource для GridFSFile.

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

Ответ 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

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