Upload Multiple Files using WebFlux
This commit is contained in:
parent
797f1e1737
commit
9a1c2dcf79
@ -88,6 +88,20 @@
|
|||||||
<artifactId>mockwebserver</artifactId>
|
<artifactId>mockwebserver</artifactId>
|
||||||
<version>4.12.0</version> <!-- this can be removed when we migrate spring-boot-dependencies to the latest version -->
|
<version>4.12.0</version> <!-- this can be removed when we migrate spring-boot-dependencies to the latest version -->
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@ -96,6 +110,14 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>16</source>
|
||||||
|
<target>16</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</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
|
5
spring-5-webflux-2/src/main/resources/schema.sql
Normal file
5
spring-5-webflux-2/src/main/resources/schema.sql
Normal file
@ -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…
x
Reference in New Issue
Block a user