SpringBoot集成Dufs通過(guò)WebDAV實(shí)現(xiàn)文件管理方式
引言
在現(xiàn)代應(yīng)用開(kāi)發(fā)中,文件存儲(chǔ)和管理是一個(gè)常見(jiàn)需求。
Dufs 是一個(gè)輕量級(jí)的文件服務(wù)器,支持 WebDAV 協(xié)議,可以方便地集成到 Spring Boot 應(yīng)用中。
本文將詳細(xì)介紹如何使用 WebDAV 協(xié)議在 Spring Boot 中集成 Dufs 文件服務(wù)器。
1. 準(zhǔn)備工作
1.1 添加項(xiàng)目依賴
在 pom.xml 中添加必要依賴:
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sardine WebDAV 客戶端 -->
<dependency>
<groupId>com.github.lookfirst</groupId>
<artifactId>sardine</artifactId>
<version>5.10</version>
</dependency>
</dependencies>
2. 核心實(shí)現(xiàn)
2.1 配置類
@Configuration
@ConfigurationProperties(prefix = "dufs.webdav")
@Data
public class DufsWebDavConfig {
private String url = "http://localhost:5000";
private String username = "admin";
private String password = "password";
private String basePath = "/";
@Bean
public Sardine sardine() {
Sardine sardine = SardineFactory.begin();
sardine.setCredentials(username, password);
return sardine;
}
}
2.2 服務(wù)層實(shí)現(xiàn)
@Service
@RequiredArgsConstructor
@Slf4j
public class DufsWebDavService {
private final Sardine sardine;
private final DufsWebDavConfig config;
/**
* 自定義 WebDAV 異常
*/
public static class DufsWebDavException extends RuntimeException {
public DufsWebDavException(String message) {
super(message);
}
public DufsWebDavException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* 構(gòu)建完整的 WebDAV 路徑
*/
private String buildFullPath(String path) {
return config.getUrl() + config.getBasePath() + (path.startsWith("/") ? path : "/" + path);
}
/**
* 上傳文件
*/
public String uploadFile(String path, InputStream inputStream) throws IOException {
String fullPath = buildFullPath(path);
sardine.put(fullPath, inputStream);
return fullPath;
}
/**
* 下載文件實(shí)現(xiàn)(方案1)
* ByteArrayResource(適合小文件)
*/
public Resource downloadFileByte(String path) {
String fullPath = buildFullPath(path);
try {
InputStream is = sardine.get(fullPath);
byte[] bytes = IOUtils.toByteArray(is); // 使用 Apache Commons IO
return new ByteArrayResource(bytes) {
@Override
public String getFilename() {
return path.substring(path.lastIndexOf('/') + 1);
}
};
} catch (IOException e) {
throw new DufsWebDavException("Failed to download file: " + path, e);
}
}
/**
* 下載文件實(shí)現(xiàn)(方案2)
* StreamingResponseBody(適合大文件)
*/
public StreamingResponseBody downloadFileStreaming(String path) {
String fullPath = buildFullPath(path);
return outputStream -> {
try (InputStream is = sardine.get(fullPath)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
};
}
/**
* 下載文件實(shí)現(xiàn)(方案3)
* 自定義可重復(fù)讀取的 Resource(推薦)
*/
public Resource downloadFile(String path) {
String fullPath = buildFullPath(path);
try {
// 測(cè)試文件是否存在
if (!sardine.exists(fullPath)) {
throw new DufsWebDavException("File not found: " + path);
}
return new AbstractResource() {
@Override
public String getDescription() {
return "WebDAV resource [" + fullPath + "]";
}
@Override
public InputStream getInputStream() throws IOException {
// 每次調(diào)用都獲取新的流
return sardine.get(fullPath);
}
@Override
public String getFilename() {
return path.substring(path.lastIndexOf('/') + 1);
}
};
} catch (IOException e) {
throw new DufsWebDavException("Failed to download file: " + path, e);
}
}
/**
* 列出目錄下的文件信息
*/
public List<WebDavFileInfo> listDirectory(String path) {
String fullPath = buildFullPath(path);
try {
List<DavResource> resources = sardine.list(fullPath);
return resources.stream().filter(res -> !res.getHref().toString().equals(fullPath + "/")).map(res -> new WebDavFileInfo(res.getHref().getPath(), res.isDirectory(), res.getContentLength(), res.getModified())).collect(Collectors.toList());
} catch (IOException e) {
throw new DufsWebDavException("Failed to list directory: " + path, e);
}
}
/**
* 創(chuàng)建目錄
*/
public void createDirectory(String path) {
String fullPath = buildFullPath(path);
try {
sardine.createDirectory(fullPath);
} catch (IOException e) {
throw new DufsWebDavException("Failed to create directory: " + path, e);
}
}
/**
* 刪除文件/目錄
*/
public void delete(String path) {
String fullPath = buildFullPath(path);
try {
sardine.delete(fullPath);
} catch (IOException e) {
throw new DufsWebDavException("Failed to delete: " + path, e);
}
}
/**
* 檢查文件/目錄是否存在
*/
public boolean exists(String path) {
String fullPath = buildFullPath(path);
try {
sardine.exists(fullPath);
return true;
} catch (IOException e) {
return false;
}
}
/**
* 鎖定文件
*/
public String lockFile(String path) {
String fullPath = buildFullPath(path);
try {
return sardine.lock(fullPath);
} catch (IOException e) {
throw new DufsWebDavException("Failed to lock file: " + path, e);
}
}
/**
* 解鎖文件
*/
public void unlockFile(String path, String lockToken) {
String fullPath = buildFullPath(path);
try {
sardine.unlock(fullPath, lockToken);
} catch (IOException e) {
throw new DufsWebDavException("Failed to unlock file: " + path, e);
}
}
/**
* 文件信息DTO
*/
@Data
@AllArgsConstructor
public static class WebDavFileInfo implements java.io.Serializable {
private String name;
private boolean directory;
private Long size;
private Date lastModified;
}
}
2.3 控制器層
@RestController
@RequestMapping("/api/webdav")
@RequiredArgsConstructor
public class WebDavController {
private final DufsWebDavService webDavService;
@PostMapping("/upload")
public ResponseEntity<?> uploadFile(
@RequestParam("file") MultipartFile file,
@RequestParam(value = "path", defaultValue = "") String path) {
try {
String filePath = path.isEmpty() ? file.getOriginalFilename()
: path + "/" + file.getOriginalFilename();
String uploadPath = webDavService.uploadFile(filePath, file.getInputStream());
return ResponseEntity.ok().body(uploadPath);
} catch (IOException e) {
throw new DufsWebDavService.DufsWebDavException("File upload failed", e);
}
}
@GetMapping("/download")
public ResponseEntity<Resource> downloadFile(@RequestParam String path) {
Resource resource = webDavService.downloadFileByte(path);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
@GetMapping("/downloadFileStreaming")
public ResponseEntity<StreamingResponseBody> downloadFileStreaming(@RequestParam String path) {
StreamingResponseBody responseBody = webDavService.downloadFileStreaming(path);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + path + "\"")
.body(responseBody);
}
@GetMapping("/list")
public ResponseEntity<List<DufsWebDavService.WebDavFileInfo>> listDirectory(
@RequestParam(required = false) String path) {
return ResponseEntity.ok(webDavService.listDirectory(path == null ? "" : path));
}
@PostMapping("/directory")
public ResponseEntity<?> createDirectory(@RequestParam String path) {
webDavService.createDirectory(path);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
@DeleteMapping
public ResponseEntity<?> delete(@RequestParam String path) {
webDavService.delete(path);
return ResponseEntity.noContent().build();
}
@GetMapping("/exists")
public ResponseEntity<Boolean> exists(@RequestParam String path) {
return ResponseEntity.ok(webDavService.exists(path));
}
3. 高級(jí)功能
3.1 大文件分塊上傳
public void chunkedUpload(String path, InputStream inputStream, long size) {
String fullPath = buildFullPath(path);
try {
sardine.enableChunkedUpload();
Map<String, String> headers = new HashMap<>();
headers.put("Content-Length", String.valueOf(size));
sardine.put(fullPath, inputStream, headers);
} catch (IOException e) {
throw new RuntimeException("Chunked upload failed", e);
}
}
3.2 異步操作
@Async
public CompletableFuture<String> asyncUpload(String path, MultipartFile file) {
try {
uploadFile(path, file.getInputStream());
return CompletableFuture.completedFuture("Upload success");
} catch (IOException e) {
CompletableFuture<String> future = new CompletableFuture<>();
future.completeExceptionally(e);
return future;
}
}
4. 性能優(yōu)化
連接池配置:
@Bean
public Sardine sardine() {
Sardine sardine = SardineFactory.begin(username, password);
sardine.setConnectionTimeout(5000);
sardine.setReadTimeout(10000);
return sardine;
}
流式下載大文件:
@GetMapping("/download-large")
public ResponseEntity<StreamingResponseBody> downloadLargeFile(@RequestParam String path) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + path + "\"")
.body(outputStream -> {
try (InputStream is = sardine.get(buildFullPath(path))) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
});
}
5. 常見(jiàn)問(wèn)題解決
5.1 InputStream 重復(fù)讀取問(wèn)題
使用 ByteArrayResource 或緩存文件內(nèi)容解決:
public Resource downloadFile(String path) {
return new ByteArrayResource(getFileBytes(path)) {
@Override
public String getFilename() {
return path.substring(path.lastIndexOf('/') + 1);
}
};
}
5.2 異步方法返回錯(cuò)誤
Java 8 兼容的失敗 Future 創(chuàng)建方式:
@Async
public CompletableFuture<String> asyncOperation() {
try {
// 業(yè)務(wù)邏輯
return CompletableFuture.completedFuture("success");
} catch (Exception e) {
CompletableFuture<String> future = new CompletableFuture<>();
future.completeExceptionally(e);
return future;
}
}
結(jié)語(yǔ)
通過(guò)本文的介紹,我們實(shí)現(xiàn)了 Spring Boot 應(yīng)用與 Dufs 文件服務(wù)器通過(guò) WebDAV 協(xié)議的完整集成。
這種方案具有以下優(yōu)勢(shì):
- 輕量級(jí):Dufs 服務(wù)器非常輕量
- 功能全面:支持標(biāo)準(zhǔn) WebDAV 協(xié)議的所有操作
- 易于集成:Spring Boot 提供了良好的異步支持
- 性能良好:支持大文件流式傳輸
在實(shí)際項(xiàng)目中,可以根據(jù)需求進(jìn)一步擴(kuò)展功能,如添加文件預(yù)覽、權(quán)限控制等。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java基于面向?qū)ο髮?shí)現(xiàn)一個(gè)戰(zhàn)士小游戲
這篇文章主要為大家詳細(xì)介紹了Java如何基于面向?qū)ο髮?shí)現(xiàn)一個(gè)戰(zhàn)士小游戲,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以動(dòng)手嘗試一下2022-07-07
mybatis執(zhí)行錯(cuò)誤但sql執(zhí)行正常問(wèn)題
這篇文章主要介紹了mybatis執(zhí)行錯(cuò)誤但sql執(zhí)行正常問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
基于java時(shí)區(qū)轉(zhuǎn)換夏令時(shí)的問(wèn)題及解決方法
下面小編就為大家分享一篇基于java時(shí)區(qū)轉(zhuǎn)換夏令時(shí)的問(wèn)題及解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-11-11
如何把第三方服務(wù)注冊(cè)到spring項(xiàng)目容器中
這篇文章主要為大家介紹了如何把第三方服務(wù)注冊(cè)到spring項(xiàng)目容器中,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
Spring?Cache?集成?Caffeine實(shí)現(xiàn)項(xiàng)目緩存的示例
本文主要介紹了Spring?Cache?集成?Caffeine實(shí)現(xiàn)項(xiàng)目緩存的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12

