使用 WebFlux 上傳多個文件
1. 概述
Spring WebFlux 是一個反應式 Web 框架,它提供非阻塞事件循環來非同步處理 I/O 操作。此外,它還使用Mono
和Flux
反應式串流發佈器在訂閱時發出數據。
這種反應式方法可以幫助應用程式處理大量請求和數據,而無需分配大量資源。
在本教程中,我們將透過逐步指南學習如何使用 Spring WebFlux 將多個檔案上傳到目錄。此外,我們會將檔案名稱對應到實體類別以便於檢索。
2. 項目設定
讓我們建立一個簡單的反應式 Spring Boot 項目,將多個檔案上傳到一個目錄。為簡單起見,我們將使用專案的根目錄來儲存檔案。在生產中,我們可以使用AWS S3、Azure Blob儲存、Oracle雲端基礎設施儲存等檔案系統。
2.1. Maven 依賴項
首先,讓我們透過將[spring-boot-starter-webflux](https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-webflux)
依賴項新增至pom.xml
來引導 Spring WebFlux 應用程式:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>3.2.0</version>
</dependency>
這提供了核心 Spring WebFlux API 和嵌入式 Netty 伺服器來建立反應式 Web 應用程式。
另外,我們將[spring-boot-starter-data-r2dbc](https://mvnrepository.com/artifact/org.springframework.boot/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()
的方法,它接受FilePart
物件的Flux
。然後,我們在每個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
檔案中定義資料庫 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
端點。它接受FilePart
的Flux
並上傳每個檔案。接下來,它收集檔案名稱及其副檔名並將它們對應到FileRecord
實體。
讓我們用 Postman 測試端點:
在這裡,我們將兩個名為spring-config.xml
和server_name.png
的檔案上傳到伺服器。 POST 請求發出一個Mono
,顯示請求的詳細資訊。
為簡單起見,我們沒有驗證檔案名稱、類型和大小。
4.4.按Id
檢索圖書
讓我們實作一個端點來檢索儲存的檔案記錄 透過其Id
查看關聯的檔案名稱。
首先,我們將透過Id
檢索文件記錄的邏輯加入到服務類別:
Mono findById(int id) {
return fileRecordRepository.findById(id);
}
在這裡,我們呼叫bookRepository
上的findById()
以透過其 id 檢索商店 Book。
接下來,讓我們寫一個端點來檢索檔案記錄:
@GetMapping("/files/{id}")
Mono geFilesById(@PathVariable("id") int id) {
return fileRecordService.findById(id)
.onErrorResume(error -> Mono.error(error));
}
該端點傳回一個包含檔案Id
和檔案名稱的Mono
。
讓我們看看使用 Postman 運行的端點:
上圖顯示了返回文件資訊。圖像檔案 URL 可以在 API 回應中傳回。客戶端可以使用這些 URL 來檢索和顯示圖像。可以透過將文件傳遞給處理服務來實現圖像的附加處理和編輯。
5. 結論
在本文中,我們學習如何使用 Spring WebFlux 將多個檔案上傳到伺服器檔案系統。此外,我們還了解如何上傳文件,無論是否將文件名稱和副檔名對應到資料庫實體。
最後,我們看到了一種上傳檔案並將檔案名稱及其副檔名儲存到資料庫的方法。
與往常一樣,該範例的完整原始程式碼可在 GitHub 上取得。