Spring Boot 集成 Dufs 通過 WebDAV 實現文件管理

Spring Boot 集成 Dufs 通過 WebDAV 實現文件管理

在這里插入圖片描述

引言

在現代應用開發中,文件存儲和管理是一個常見需求。Dufs 是一個輕量級的文件服務器,支持 WebDAV 協議,可以方便地集成到 Spring Boot 應用中。本文將詳細介紹如何使用 WebDAV 協議在 Spring Boot 中集成 Dufs 文件服務器。

1. 準備工作

1.1 添加項目依賴

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. 核心實現

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 = "/";@Beanpublic Sardine sardine() {Sardine sardine = SardineFactory.begin();sardine.setCredentials(username, password);return sardine;}
}

2.2 服務層實現

@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);}}/*** 構建完整的 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;}/*** 下載文件實現(方案1)* ByteArrayResource(適合小文件)*/public Resource downloadFileByte(String path) {String fullPath = buildFullPath(path);try {InputStream is = sardine.get(fullPath);byte[] bytes = IOUtils.toByteArray(is); // 使用 Apache Commons IOreturn new ByteArrayResource(bytes) {@Overridepublic String getFilename() {return path.substring(path.lastIndexOf('/') + 1);}};} catch (IOException e) {throw new DufsWebDavException("Failed to download file: " + path, e);}}/*** 下載文件實現(方案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);}}};}/*** 下載文件實現(方案3)* 自定義可重復讀取的 Resource(推薦)*/public Resource downloadFile(String path) {String fullPath = buildFullPath(path);try {// 測試文件是否存在if (!sardine.exists(fullPath)) {throw new DufsWebDavException("File not found: " + path);}return new AbstractResource() {@Overridepublic String getDescription() {return "WebDAV resource [" + fullPath + "]";}@Overridepublic InputStream getInputStream() throws IOException {// 每次調用都獲取新的流return sardine.get(fullPath);}@Overridepublic 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);}}/*** 創建目錄*/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@AllArgsConstructorpublic 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();}@DeleteMappingpublic 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. 高級功能

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. 性能優化

  1. 連接池配置

    @Bean
    public Sardine sardine() {Sardine sardine = SardineFactory.begin(username, password);sardine.setConnectionTimeout(5000);sardine.setReadTimeout(10000);return sardine;
    }
    
  2. 流式下載大文件

    @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. 常見問題解決

5.1 InputStream 重復讀取問題

使用 ByteArrayResource 或緩存文件內容解決:

public Resource downloadFile(String path) {return new ByteArrayResource(getFileBytes(path)) {@Overridepublic String getFilename() {return path.substring(path.lastIndexOf('/') + 1);}};
}

5.2 異步方法返回錯誤

Java 8 兼容的失敗 Future 創建方式:

@Async
public CompletableFuture<String> asyncOperation() {try {// 業務邏輯return CompletableFuture.completedFuture("success");} catch (Exception e) {CompletableFuture<String> future = new CompletableFuture<>();future.completeExceptionally(e);return future;}
}

結語

通過本文的介紹,我們實現了 Spring Boot 應用與 Dufs 文件服務器通過 WebDAV 協議的完整集成。這種方案具有以下優勢:

  1. 輕量級:Dufs 服務器非常輕量
  2. 功能全面:支持標準 WebDAV 協議的所有操作
  3. 易于集成:Spring Boot 提供了良好的異步支持
  4. 性能良好:支持大文件流式傳輸

在實際項目中,可以根據需求進一步擴展功能,如添加文件預覽、權限控制等。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/89521.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/89521.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/89521.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Unity打包時編碼錯誤解決方案:NotSupportedException Encoding 437

問題描述 在Unity項目開發過程中&#xff0c;經常會遇到這樣的情況&#xff1a;項目在編輯器模式下運行完全正常&#xff0c;但是打包后運行時卻出現以下錯誤&#xff1a; NotSupportedException: Encoding 437 data could not be found. Make sure you have correct interna…

Spring Bean的生命周期與作用域詳解

一、Spring Bean的生命周期 Spring Bean的生命周期指的是Bean從創建到銷毀的整個過程。理解這個生命周期對于正確使用Spring框架至關重要&#xff0c;它可以幫助我們在適當的時機執行自定義邏輯。 1. 完整的Bean生命周期階段 Spring Bean的生命周期可以分為以下幾個主要階段…

如何將Excel表的內容轉化為json格式呢?

文章目錄 一、前言二、具體操作步驟 一、前言 先說一下我使用Excel表的內容轉為json的應用場景&#xff0c;我們是用來處理國際化的時候用到的。 二、具體操作步驟 第一步&#xff1a;選擇要轉化Excel表的內容&#xff08;必須是key&#xff0c;value形式的&#xff09; 第二…

內存堆棧管理(Linux)

以問題形式講解 1.每一個進程都有一個堆空間嗎&#xff1f;還是多個進程共用一個堆空間&#xff1f; 在操作系統中&#xff0c;??每個進程都有自己獨立的虛擬地址空間&#xff0c;其中包括自己獨占的堆空間。堆空間是進程私有的&#xff0c;不與其他進程共享。 進程之間的內…

ThreatLabz 2025 年人工智能安全報告

AI 應用趨勢&#xff1a;爆發式增長與風險并存 2024 年&#xff0c;全球企業的 AI/ML 工具使用量呈指數級增長。Zscaler 云平臺數據顯示&#xff0c;2024 年 2 月至 12 月期間&#xff0c;AI/ML 交易總量達 5365 億次&#xff0c;同比激增 3464.6%&#xff0c;涉及 800 多個應…

【Oracle學習筆記】7.存儲過程(Stored Procedure)

Oracle中的存儲過程是一組為了完成特定功能而預先編譯并存儲在數據庫中的SQL語句和PL/SQL代碼塊。它可以接受參數、執行操作&#xff08;如查詢、插入、更新、刪除數據等&#xff09;&#xff0c;并返回結果。以下從多個方面詳細講解&#xff1a; 1. 存儲過程的創建 創建存儲過…

tc工具-corrupt 比 delay/loss 更影響性能

1. netem corrupt 5% 的作用 功能說明 corrupt 5% 表示 隨機修改 5% 的數據包內容&#xff08;如翻轉比特位&#xff09;&#xff0c;模擬數據損壞。它本身不會直接丟棄或延遲數據包&#xff0c;而是讓接收端收到錯誤的數據&#xff08;可能觸發校驗和失敗、協議層重傳等&…

Flask YAML管理工具

項目概述 項目地址&#xff1a;https://github.com/KaiqiZing/Flask_Yaml_Demo 這是一個基于Flask開發的YAML文件管理工具&#xff0c;提供了完整的YAML文件查看、編輯、管理功能&#xff0c;具有現代化的Web界面和強大的編輯能力。 核心功能 1. 文件管理功能 目錄掃描&am…

Embedding模型微調實戰(ms-swift框架)

目錄 簡介 1. 創建虛擬環境 2 安裝ms-swift 3安裝其他依賴庫 4. 下載數據集 5.開始embedding模型訓練 6. 自定義數據格式和對應的Loss類型 &#xff08;1&#xff09; infoNCE損失 (2)余弦相似度損失 (3)對比學習損失 &#xff08;4).在線對比學習損失 &#…

從性能優化賽到社區Committer,走進趙宇捷在Apache Fory的成長之路

Apache Fory 是一個基于JIT和零拷貝的高性能多語言序列化框架&#xff0c;實現了高效緊湊的序列化協議&#xff0c;提供極致的性能、壓縮率和易用性。在多語言序列化框架技術領域取得了重大突破&#xff0c;推動序列化技術步入高性能易用新篇章&#xff01;這一切&#xff0c;都…

Python實例題:基于 Flask 的任務管理系統

目錄 Python實例題 題目 要求&#xff1a; 解題思路&#xff1a; 代碼實現&#xff1a; Python實例題 題目 基于 Flask 的任務管理系統 要求&#xff1a; 使用 Flask 框架構建一個任務管理系統&#xff0c;支持以下功能&#xff1a; 用戶認證&#xff08;注冊、登錄、…

利用GPU加速TensorFlow

一、寫在前面 我們已經依靠keras和TensorFlow給大家做了一些機器學習在圖像處理中的應用(影像組學學習手冊,基于深度學習的圖像分類任務)&#xff0c;此前的教程中我們沒有用GPU進行加速&#xff0c;但是相較于CPU而言&#xff0c;GPU是設計用于處理大規模并行計算任務的硬件&…

模型預測專題:強魯棒性DPCC

0 1 前言 在進行DPCC的學習過程中&#xff0c;于下面鏈接看到了一篇強魯棒性算法&#xff1b;感覺挺有意思的&#xff0c;學習一下。 永磁同步電機高性能控制算法&#xff08;12&#xff09;——基于預測電流誤差補償的強魯棒預測控制/參數辨識&有限集預測控制與連續集預…

修復opensuse 風滾草rabbitmq的Error: :plugins_dir_does_not_exist問題

https://wiki.archlinux.org/title/Talk:RabbitMQ 報錯 yqh192 /u/l/r/l/r/plugins> sudo rabbitmq-plugins enable rabbitmq_management Error: :plugins_dir_does_not_exist Arguments given:enable rabbitmq_managementUsagerabbitmq-plugins [--node <node>] [--…

前端做gis地圖有哪些庫

以下是前端開發GIS地圖常用的庫&#xff1a; Leaflet&#xff1a;輕量級、易于使用的開源JavaScript庫&#xff0c;具有豐富的地圖功能和插件生態系統&#xff0c;支持多種地圖數據源&#xff0c;適合初學者和專業開發者。其優勢在于簡潔性和易用性&#xff0c;代碼結構清晰&am…

賦能城市安全韌性|眾智鴻圖總裁扈震受邀出席智慧城市大會發表主題報告

——“眾智鴻圖作為城市基礎設施智能化綜合服務提供商&#xff0c;以地理信息科學、時空大數據、人工智能為核心能力&#xff0c;長期深耕于燃氣、供水、排水等城市基礎設施生命線領域及港口、園區等工業領域&#xff0c;致力于為城市穩定運行與高效發展提供堅實保障。” 2025年…

【大語言模型入門】—— 淺析LLM基座—Transformer原理

【大語言模型入門】—— 淺析LLM基座—Transformer原理 解密GPT核心技術&#xff1a;Transformer架構深度解析被反復強調的核心概念意味著什么&#xff1f; GPT預測機制解析&#xff1a;從Next Token Prediction到任務推理核心機制的本質案例驅動的機制解析解構策略&#xff1a…

Django打造智能Web機器人控制平臺

Django 實現 Web 機器人控制 以下是關于 Django 實現 Web 機器人控制管理的實例思路和關鍵代碼片段,涵蓋多個常見場景。由于篇幅限制,剩余的可通過類似模式擴展。 基礎機器人模型定義 # models.py from django.db import modelsclass Robot(models.Model):name = models.C…

周賽98補題

題目意思&#xff1a; 給定一個數字判斷加上自身任意因子數&#xff0c;是否能成為一個奇數。 思路&#xff1a; 我們想一個最簡單的判斷方法&#xff0c; 任意的數字的因子數都有1&#xff0c;故&#xff0c;最簡單的方法就是判斷奇偶。 奇數1成偶數&#xff0c;偶數1成奇…

【STM32】 STM32低功耗模式詳解:睡眠模式與喚醒機制【待測試】

本篇知識點基于F0講解 一、STM32三種低功耗模式參考表格 模式功耗喚醒時間保持狀態典型應用場景睡眠模式中等 (mA級)最短 (μs級)CPU停止&#xff0c;外設保持短暫待機&#xff0c;快速響應停止模式低 (μA級)中等 (ms級)RAM保持&#xff0c;時鐘停止長時間待機&#xff0c;電…