Merge pull request #15641 from Michaelin007/master
Upload Multiple Files using WebFlux
This commit is contained in:
commit
f9a0b4369e
|
@ -88,6 +88,20 @@
|
|||
<artifactId>mockwebserver</artifactId>
|
||||
<version>4.12.0</version> <!-- this can be removed when we migrate spring-boot-dependencies to the latest version -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -96,6 +110,14 @@
|
|||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>16</source>
|
||||
<target>16</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package com.baeldung.webflux.filerecord;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FileRecord {
|
||||
@Id
|
||||
private int id;
|
||||
|
||||
private List<String> filenames;
|
||||
|
||||
public FileRecord(List<String> filenames) {
|
||||
this.filenames = filenames;
|
||||
}
|
||||
|
||||
public FileRecord() {
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public List<String> getFilenames() {
|
||||
return filenames;
|
||||
}
|
||||
|
||||
public void setFilenames(List<String> filenames) {
|
||||
this.filenames = filenames;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.baeldung.webflux.filerecord;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer;
|
||||
import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator;
|
||||
|
||||
@SpringBootApplication
|
||||
public class FileRecordApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(FileRecordApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
|
||||
|
||||
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
|
||||
initializer.setConnectionFactory(connectionFactory);
|
||||
initializer.setDatabasePopulator(new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));
|
||||
|
||||
return initializer;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package com.baeldung.webflux.filerecord;
|
||||
|
||||
import org.springframework.http.codec.multipart.FilePart;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@RestController
|
||||
public class FileRecordController {
|
||||
|
||||
private final FileRecordService fileRecordService;
|
||||
|
||||
public FileRecordController(FileRecordService fileRecordService) {
|
||||
this.fileRecordService = fileRecordService;
|
||||
}
|
||||
|
||||
@PostMapping("/upload-files")
|
||||
public Mono<String> uploadFileWithoutEntity(@RequestPart("files") Flux<FilePart> filePartFlux) {
|
||||
return filePartFlux.flatMap(file -> file.transferTo(Paths.get(file.filename())))
|
||||
.then(Mono.just("OK"))
|
||||
.onErrorResume(error -> Mono.just("Error uploading files"));
|
||||
}
|
||||
|
||||
@PostMapping("/upload-files-entity")
|
||||
public Mono<FileRecord> uploadFileWithEntity(@RequestPart("files") Flux<FilePart> filePartFlux) {
|
||||
FileRecord fileRecord = new FileRecord();
|
||||
|
||||
return filePartFlux.flatMap(filePart -> filePart.transferTo(Paths.get(filePart.filename()))
|
||||
.then(Mono.just(filePart.filename())))
|
||||
.collectList()
|
||||
.flatMap(filenames -> {
|
||||
fileRecord.setFilenames(filenames);
|
||||
return fileRecordService.save(fileRecord);
|
||||
})
|
||||
.onErrorResume(error -> Mono.error(error));
|
||||
}
|
||||
|
||||
@GetMapping("/files/{id}")
|
||||
public Mono<FileRecord> geFilesById(@PathVariable("id") int id) {
|
||||
return fileRecordService.findById(id)
|
||||
.onErrorResume(error -> Mono.error(error));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.baeldung.webflux.filerecord;
|
||||
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface FileRecordRepository extends R2dbcRepository<FileRecord, Integer> {
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.baeldung.webflux.filerecord;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Service
|
||||
public class FileRecordService {
|
||||
|
||||
private FileRecordRepository fileRecordRepository;
|
||||
|
||||
public FileRecordService(FileRecordRepository fileRecordRepository) {
|
||||
this.fileRecordRepository = fileRecordRepository;
|
||||
}
|
||||
|
||||
public Mono<FileRecord> save(FileRecord fileRecord) {
|
||||
return fileRecordRepository.save(fileRecord);
|
||||
}
|
||||
|
||||
public Mono<FileRecord> findById(int id) {
|
||||
return fileRecordRepository.findById(id);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
spring.r2dbc.url=r2dbc:h2:file:///./testdb
|
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE IF NOT EXISTS file_record (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
filenames VARCHAR(255),
|
||||
PRIMARY KEY (id)
|
||||
);
|
|
@ -0,0 +1,98 @@
|
|||
package com.baeldung.webflux.filerecord;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@SpringBootTest(classes = FileRecordController.class)
|
||||
@ContextConfiguration(classes = { FileRecordController.class })
|
||||
@ComponentScan("com.baeldung.webflux.filerecord")
|
||||
@AutoConfigureWebTestClient
|
||||
class FileRecordControllerIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
@MockBean
|
||||
private FileRecordService fileRecordService;
|
||||
|
||||
@Test
|
||||
public void givenUploadFilesWithEntity_whenRequestIsValid_thenReturnCreated() throws Exception {
|
||||
|
||||
FileRecord fileRecord = new FileRecord();
|
||||
MockMultipartFile firstFile = new MockMultipartFile("file", "baeldungdata.txt", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes());
|
||||
MockMultipartFile secondFile = new MockMultipartFile("file", "baeldungdata.pdf", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes());
|
||||
List<MockMultipartFile> files = List.of(firstFile, secondFile);
|
||||
fileRecord.setFilenames(files.stream()
|
||||
.map(MockMultipartFile::getOriginalFilename)
|
||||
.toList());
|
||||
|
||||
Mono<FileRecord> fileRecordMono = Mono.just(fileRecord);
|
||||
when(fileRecordService.save(any(FileRecord.class))).thenReturn(fileRecordMono);
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/upload-files-entity")
|
||||
.body(Mono.just(fileRecord), FileRecord.class)
|
||||
.exchange()
|
||||
.expectBody(FileRecord.class);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenUploadFiles_whenRequestIsValid_thenReturnOk() throws Exception {
|
||||
|
||||
MockMultipartFile firstFile = new MockMultipartFile("file", "baeldungdata.txt", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes());
|
||||
MockMultipartFile secondFile = new MockMultipartFile("file", "baeldungdata.pdf", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes());
|
||||
LinkedMultiValueMap<String, MockMultipartFile> multipartData = new LinkedMultiValueMap<>();
|
||||
multipartData.add("file", firstFile);
|
||||
multipartData.add("file", secondFile);
|
||||
|
||||
webTestClient.post()
|
||||
.uri("/upload-files")
|
||||
.bodyValue(multipartData)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenUploadFilesWithEntity_whenRequestFileIsFindById_thenReturnOk() throws Exception {
|
||||
|
||||
FileRecord fileRecord = new FileRecord();
|
||||
MockMultipartFile firstFile = new MockMultipartFile("file", "baeldungdata.txt", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes());
|
||||
MockMultipartFile secondFile = new MockMultipartFile("file", "baeldungdata.pdf", MediaType.TEXT_PLAIN_VALUE, "Test file content".getBytes());
|
||||
List<MockMultipartFile> files = List.of(firstFile, secondFile);
|
||||
fileRecord.setId(1);
|
||||
fileRecord.setFilenames(files.stream()
|
||||
.map(MockMultipartFile::getOriginalFilename)
|
||||
.toList());
|
||||
|
||||
Mono<FileRecord> fileRecordMono = Mono.just(fileRecord);
|
||||
|
||||
when(fileRecordService.findById(1)).thenReturn(fileRecordMono);
|
||||
|
||||
webTestClient.get()
|
||||
.uri("/files/{id}", 1)
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue