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 | ||||
							
								
								
									
										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