Как получить двоичный поток с помощью 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
В этих типах есть немного беспорядка:
От 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()