SpringBoot集成Dufs通过WebDAV实现文件管理方式
投稿:jingxian
文章介绍了在SpringBoot应用中集成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 = "/"; @Bean public 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 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); } } /** * 下载文件实现(方案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() { @Override public String getDescription() { return "WebDAV resource [" + fullPath + "]"; } @Override public InputStream getInputStream() throws IOException { // 每次调用都获取新的流 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); } } /** * 创建目录 */ 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. 高级功能
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. 性能优化
连接池配置:
@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. 常见问题解决
5.1 InputStream 重复读取问题
使用 ByteArrayResource
或缓存文件内容解决:
public Resource downloadFile(String path) { return new ByteArrayResource(getFileBytes(path)) { @Override public 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 协议的完整集成。
这种方案具有以下优势:
- 轻量级:Dufs 服务器非常轻量
- 功能全面:支持标准 WebDAV 协议的所有操作
- 易于集成:Spring Boot 提供了良好的异步支持
- 性能良好:支持大文件流式传输
在实际项目中,可以根据需求进一步扩展功能,如添加文件预览、权限控制等。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。