Merge pull request #15641 from Michaelin007/master

Upload Multiple Files using WebFlux
This commit is contained in:
Maiklins 2024-01-16 22:05:48 +01:00 committed by GitHub
commit f9a0b4369e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 265 additions and 0 deletions

View File

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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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> {
}

View File

@ -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);
}
}

View File

@ -0,0 +1 @@
spring.r2dbc.url=r2dbc:h2:file:///./testdb

View File

@ -0,0 +1,5 @@
CREATE TABLE IF NOT EXISTS file_record (
id INT NOT NULL AUTO_INCREMENT,
filenames VARCHAR(255),
PRIMARY KEY (id)
);

View File

@ -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();
}
}