web-dev-qa-db-fra.com

Comment obtenir un flux binaire par un objet ObjectId GridFS avec Spring Data MongoDB

Je n'arrive pas à comprendre comment diffuser un fichier binaire à partir de GridFS avec spring-data-mongodb et sa GridFSTemplate alors que j'ai déjà la bonne ObjectId.

GridFSTemplate renvoie soit GridFSResource (getResource()), soit GridFSFile (findX()). 

Je peux obtenir la GridFSFile par ID: 

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

mais il n’existe aucun moyen évident d’obtenir une InputStream pour cette GridFSFile.

Seul GridFSResource me permet de mettre la main sur la InputStream correspondante avec InputStreamResource#getInputstream. Mais le seul moyen d’obtenir une GridFSResource est de sa filename.

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

D'une manière ou d'une autre, l'API GridFsTemplate implique que les noms de fichiers sont uniques, ce qu'ils ne sont pas. L'implémentation GridFsTemplate ne fait que renvoyer le premier élément.

Maintenant, j'utilise l'API native MongoDB et tout est à nouveau logique:

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

Il semble que je ne comprenne pas les concepts fondamentaux de l'abstraction Spring Data Mongo GridFS. Je m'attendrais (au moins) à ce que l'une des choses suivantes soit possible/vraie:

  • obtenir une GridFSResource par son identifiant
  • obtenir une GridFSResource ou InputStream pour une GridFsFile que j'ai déjà

Est-ce que je me trompe ou y at-il quelque chose d'étrange avec cet élément particulier de l'API Spring Data MongoDB?

10
atamanroman

Je suis tombé sur cela aussi. Et je suis en fait assez choqué que le GridFsTemplate ait été conçu comme ceci .... De toute façon, ma vilaine "solution" à cela: 

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

Remarque: vous devez injecter MongoDbFactory pour que cela fonctionne ...

9
Steffen Jacobs

Il y a un peu de gâchis dans ces types:

De Spring GridFsTemplate source :

public getResource(String location) {

    GridFSFile file = findOne(query(whereFilename().is(location)));
    return file != null ? new GridFsResource(file, getGridFs().openDownloadStream(location)) : null;
}

Il y a une solution laide:

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

j'ai découvert la solution à ce problème!

Enveloppez simplement le GridFSFile dans un GridFsResource! Ceci est conçu pour être instancié avec un fichier GridFS.

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


}

Le grand avantage de cela est que vous pouvez directement passer le GridFsResource à un ResponseEntity car le GridFsResource étend un InputStreamResource.

J'espère que cela t'aides!

Salutations Niklas

1
Niklas

Envelopper le GridFSFile dans un GridFsResource ou utiliser ceci

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

Avez-vous envisagé d'utiliser Spring Content pour Mongo comme élément de stockage de contenu dans votre solution?

En supposant que vous utilisiez Spring Boot ainsi que Spring Data Mongo, le résultat pourrait être le suivant:

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>

Mettez à jour votre entité Spring Data Mongo, avec les attributs suivants:

@ContentId
private String contentId;

@ContentLength 
private long contentLength = 0L;

@MimeType
private String mimeType;

Ajouter une interface de magasin:

@StoreRestResource(path="content")
public interface MongoContentStore extends ContentStore<YourEntity, String> {
}

C'est tout ce dont vous avez besoin. Lorsque votre application démarrera, Spring Content verra les dépendances sur les modules Spring Content Mongo/REST et injectera une implémentation du magasin MongonContenStore pour GridFs ainsi qu'une implémentation d'un contrôleur prenant en charge la fonctionnalité CRUD complète interface de magasin sous-jacente. Le noeud final REST sera disponible sous /content.

c'est à dire. 

curl -X PUT /content/{entityId} va créer ou mettre à jour l'image d'une entité

curl -X GET /content/{entityId} va chercher l'image de l'entité

curl -X DELETE /content/{entityId} supprimera l'image de l'entité

Il existe quelques guides de démarrage ici . Ils utilisent Spring Content pour le système de fichiers mais les modules sont interchangeables. Le guide de référence Mongo est ici . Et il y a un tutoriel vidéo ici .

HTH

0
Paul Warren