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