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:
GridFSResource
par son identifiantGridFSResource
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?
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 ...
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();
}
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
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();
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