1. 概述
Spring WebFlux 是一個反應式 Web 框架,它提供了一個非阻塞事件循環,用於異步處理 I/O 操作。它還使用 <em >Mono</em> 和 <em >Flux</em> 反應式流發佈器,在訂閲時發出數據。
這種反應式方法有助於應用程序處理大量請求和數據,而無需分配大量的資源。
在本教程中,我們將學習如何使用 Spring WebFlux 通過分步指南將多個文件上傳到目錄。此外,我們還將將文件名映射到實體類,以便輕鬆檢索。
2. 項目設置
讓我們創建一個簡單的響應式 Spring Boot 項目,用於上傳多個文件到目錄。為了簡化起見,我們將使用項目根目錄來存儲文件。 在生產環境中,我們可以使用諸如 AWS S3、Azure Blob Storage、Oracle Cloud Infrastructure 存儲等文件系統。
2.1. Maven 依賴
首先,讓我們通過在 pom.xml 中添加 spring-boot-starter-webflux 依賴來啓動一個 Spring WebFlux 應用程序:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>3.3.2</version>
</dependency>
這提供了 Spring WebFlux 的核心 API 以及嵌入式 Netty 服務器,用於構建響應式 Web 應用程序。
此外,讓我們將 spring-boot-starter-data-r2dbc 和 H2 數據庫 依賴項添加到 pom.xml 文件中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
</dependency>
Spring WebFlux R2DBC 是一個反應式數據庫連接器,而 H2 數據庫是一個內存數據庫。
最後,我們將 R2DBC 原生驅動包 依賴項 添加到 pom.xml 中:
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-h2</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>此原生驅動程序是為 H2 數據庫實現的。
2.2. 實體、存儲庫和控制器
讓我們創建一個名為 FileRecord 的實體類:
class FileRecord {
@Id
private int id;
private List<String> filenames;
// standard getters, setters, constructor
}接下來,讓我們創建一個名為 FileRecordRepository 的倉庫:
@Repository
interface FileRecordRepository extends R2dbcRepository<FileRecord, Integer> {
}最後,讓我們創建一個控制器類:
@RestController
class FileRecordController {
}在後續部分,我們將為文件名及其擴展名映射到 fileName 字段。
3. 上傳文件到目錄
有時,我們可能在沒有將文件名映射到數據庫實體的情況下,將多個文件上傳到文件系統。 在這種情況下,稍後檢索這些文件可能會具有挑戰性。
以下是一個示例代碼,它將多個文件上傳到我們的根目錄,而無需將文件名映射到實體:
PostMapping("/upload-files")
Mono 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"));
}首先,我們創建一個名為 uploadFileWithoutEntity() 的方法,該方法接受 Flux 中 FilePart 對象的流。 然後,我們調用每個 FilePart 對象的 flatMap() 方法,以傳輸文件並返回一個 Mono。 這為每個文件傳輸操作創建了一個單獨的 Mono,並將 Mono 流扁平化為一個單一的 Mono。
讓我們通過 Postman 上傳多個文件來測試端點:
在上面的圖片中,我們上傳了三個文件到項目根目錄。 端點返回 OK 以顯示操作已成功完成。
值得注意的是,我們使用 onErrorResume() 方法來明確處理與文件上傳相關的錯誤。 在上傳失敗的情況下,端點返回錯誤消息。
但是,先前上傳的文件可能已在失敗之前成功傳輸。 在這種情況下,可能需要清理以在錯誤發生時刪除部分上傳的文件。 為了簡化,我們沒有涵蓋清理過程。
4. 將上傳文件映射到數據庫實體
此外,我們還可以將文件名映射到數據庫實體。這使我們能夠通過其 Id 檢索文件,從而具有靈活性。這在我們需要顯示圖像或執行進一步計算時非常有用。
4.1. 數據庫配置
首先,讓我們在資源文件夾中創建一個 schema.sql 文件,以定義數據庫表結構:
CREATE TABLE IF NOT EXISTS file_record (
id INT NOT NULL AUTO_INCREMENT,
filenames VARCHAR(255),
PRIMARY KEY (id)
);在這裏,我們創建一個文件記錄表,用於存儲上傳的文件名及其擴展名。接下來,讓我們編寫一個配置,用於在啓動時初始化模式。
@Bean
ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
initializer.setConnectionFactory(connectionFactory);
initializer.setDatabasePopulator(new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));
return initializer;
}另外,我們將在 application.properties</em/> 文件中定義數據庫 URL:
spring.r2dbc.url=r2dbc:h2:file:///./testdb
這裏,我們定義了 R2DBC URL,用於連接到 H2 數據庫。為了簡化,數據庫未設置密碼。
4.2. 服務層
首先,創建一個服務類並添加處理數據持久化的邏輯:
@Service
public class FileRecordService {
private FileRecordRepository fileRecordRepository;
public FileRecordService(FileRecordRepository fileRecordRepository) {
this.fileRecordRepository = fileRecordRepository;
}
public Mono<FileRecord> save(FileRecord fileRecord) {
return fileRecordRepository.save(fileRecord);
}
}在這裏,我們注入了 FileRecordRepository 接口到服務類,並定義了將文件名及其擴展名保存到數據庫的邏輯。
接下來,讓我們將 FileRecordService 類注入到控制器類中:
private FileRecordService fileRecordService;
public FileRecordController(FileRecordService fileRecordService) {
this.fileRecordService = fileRecordService;
}上述代碼使得數據持久化邏輯在控制器類中可用。
4.3. 上傳端點
最後,我們編寫一個端點,將多個文件上傳到根目錄,並將文件名和它們的擴展名映射到實體類:
@PostMapping("/upload-files-entity")
Mono 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));
}在這裏,我們創建一個端點,它會發出 Mono 對象。 它接受 Flux 中的 FilePart 並上傳每個文件。 隨後,它收集文件名和擴展名,並將它們映射到 FileRecord 實體。
讓我們使用 Postman 測試該端點:
在這裏,我們上傳了兩個名為 spring-config.xml 和 server_name.png 的文件到服務器。 POST 請求會發出一個 Mono 對象,顯示請求的詳細信息。
為了簡化起見,我們沒有對文件名、類型和大小進行驗證。
4.4. 通過 Id 檢索圖書
讓我們實現一個端點,用於檢索存儲的文件記錄,通過其 Id 以查看相關的文件名。
首先,讓我們將檢索文件記錄的邏輯(通過其 Id)添加到服務類中:
Mono findById(int id) {
return fileRecordRepository.findById(id);
}在這裏,我們調用 findById() 方法,在 bookRepository 上檢索指定 ID 的圖書。
接下來,讓我們編寫一個端點來檢索文件記錄:
@GetMapping("/files/{id}")
Mono geFilesById(@PathVariable("id") int id) {
return fileRecordService.findById(id)
.onErrorResume(error -> Mono.error(error));
}
此端點返回一個包含文件 Id 和文件名 的 Mono。
讓我們使用 Postman 演示一下此端點:
上面的圖片顯示了返回的文件信息。 API 響應可以返回圖像文件 URL。客户端可以使用這些 URL 來檢索和顯示圖像。可以通過將文件傳遞給處理服務來實施對圖像的額外處理和編輯。
5. 結論
在本文中,我們學習瞭如何使用 Spring WebFlux 將多個文件上傳到服務器文件系統。此外,我們還看到了如何將文件名稱和擴展名映射到數據庫實體,無論文件是否需要映射。
最後,我們還看到了一個上傳文件並將其名稱和擴展名持久化到數據庫的方法。