SpringBoot集成Milton(webdav)應(yīng)用實(shí)例(一)
有最奇崛的峰巒 成全過(guò)你我張狂
海上清輝與圓月 盛進(jìn)杯光
有最孤傲的雪山 靜聽(tīng)過(guò)你我誦章
世人驚羨的橋段 不過(guò)尋常
有最奇崛的峰巒 成全過(guò)你我張狂
海上清輝與圓月 盛進(jìn)杯光
有最殘破的書(shū)簡(jiǎn) 記載過(guò)光陰漫長(zhǎng)
無(wú)意拾過(guò)的片瓦 歷數(shù)寒涼
有最孤傲的雪山 靜聽(tīng)過(guò)你我誦章
世人驚羨的橋段 不過(guò)尋常
01
—
webdav簡(jiǎn)介
WebDAV (Web-based Distributed Authoring and Versioning) 一種基于 HTTP 1.1協(xié)議的通信協(xié)議。它擴(kuò)展了HTTP 1.1,在GET、POST、HEAD等幾個(gè)HTTP標(biāo)準(zhǔn)方法以外添加了一些新的方法,使應(yīng)用程序可對(duì)Web Server直接讀寫(xiě),并支持寫(xiě)文件鎖定(Locking)及解鎖(Unlock),還可以支持文件的版本控制。
簡(jiǎn)單來(lái)說(shuō),webdav是一種協(xié)議,可以通過(guò)這種協(xié)議,對(duì)儲(chǔ)存于云的文件直接進(jìn)行傳輸,和平時(shí)我們用的那個(gè)百度網(wǎng)盤(pán)類(lèi)似,可以在自己的電腦上,直接對(duì)云文件傳輸編輯。

02
—
Milton
Milton.io,是一個(gè)基于Java開(kāi)發(fā)的,支持webdav協(xié)議的開(kāi)源框架。詳情可見(jiàn)官網(wǎng)介紹:https://milton.io/
此外,還支持CalDav和CardDav
使用milton開(kāi)發(fā)webdav服務(wù),通??梢酝ㄟ^(guò)2種方式進(jìn)行,一種是注解的方式,這種方式簡(jiǎn)單明了。另外一種是基于milton原生的方式,即實(shí)現(xiàn)milton的原生類(lèi),實(shí)現(xiàn)一些接口方法,從而實(shí)現(xiàn)服務(wù),兩種方式各有各的好處,對(duì)于初學(xué)者來(lái)說(shuō),注解的方式比較友好一些。milton原生方式可能稍微復(fù)雜些,因?yàn)樾枰私鈓ilton每個(gè)資源類(lèi)的邏輯和用法

03
—
Milton整合Spring
這里采用注解的方式,將通過(guò)一個(gè)實(shí)際的案例,一步一步來(lái)搭建一個(gè)屬于自己的webdav服務(wù)。
首先引入項(xiàng)目依賴(lài),milton目前高版本是到4了,由于對(duì)新版本的不是很熟悉,所以這里使用比較熟悉的版本2,項(xiàng)目后續(xù)會(huì)通過(guò)讀取數(shù)據(jù)庫(kù)的文件存儲(chǔ)目錄結(jié)構(gòu),所以引入數(shù)據(jù)庫(kù)支持,目前因?yàn)橛袀€(gè)文件管理系統(tǒng),目錄結(jié)構(gòu)存儲(chǔ)在數(shù)據(jù)庫(kù),文件在文件存儲(chǔ)服務(wù)器上的。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lgli</groupId>
<artifactId>webdav-server</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<milton.version>2.8.0.3</milton.version>
<springboot.version>2.5.11</springboot.version>
<mybatis.plus>3.5.3.1</mybatis.plus>
<mysql.connection>8.0.32</mysql.connection>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${springboot.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connection}</version>
</dependency>
<dependency>
<groupId>io.milton</groupId>
<artifactId>milton-server-ce</artifactId>
<version>${milton.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
</dependencies>
<build>
<!--保證配置文件和xml通過(guò)編譯-->
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.yaml</include>
<include>**/*.properties</include>
<include>**/banner.txt</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
????</build>
</project>
milton的大致工作原理是,對(duì)特定的webdav協(xié)議的http請(qǐng)求,有一個(gè)自己的處理方法,即io.milton.http.HttpManager#process方法,所以,首先需要定義一個(gè)攔截器類(lèi),把所需要milton處理的http請(qǐng)求,給予milton來(lái)處理。
這里定義一個(gè)SpringMiltonFilter攔截器,繼承org.springframework.web.filter.GenericFilterBean類(lèi),并重寫(xiě)doFilter方法,

這里的io.milton.http.HttpManager#process就是milton處理webdav請(qǐng)求的方法,這個(gè)HttpManager是來(lái)自于一個(gè)io.milton.config.HttpManagerBuilder通過(guò) io.milton.config.HttpManagerBuilder #buildHttpManager方法所創(chuàng)建出來(lái)的,所以要獲取HttpManager,首先要獲取到HttpManagerBuilder
HttpManagerBuilder的獲取,可以通過(guò)new一個(gè)實(shí)例,同時(shí)設(shè)定一些必要的參數(shù),比如這里所用的是注解的方式,那么 需要指定milton的Resource包掃描和一個(gè)ResourceFactory(這倆是必須的) ,還有一些需要或者不需要的權(quán)限認(rèn)證的設(shè)置,首先這里先不考慮權(quán)限,所以權(quán)限全部設(shè)置為false,

這里使用注解的方式,需要的ResourceFactory是一個(gè)io.milton.http.annotated.AnnotationResourceFactory
即:

在構(gòu)建ResourceFactory的時(shí)候,指定的安全管理器,先設(shè)置為null,就是圖例顯示的io.milton.http.fs.NullSecurityManager
最后,在指定milton的包掃描路徑中(io.milton.config.HttpManagerBuilder#setControllerPackagesToScan),創(chuàng)建一個(gè)自己的Resource管理器,同時(shí)標(biāo)注注解 ResourceController

寫(xiě)一個(gè)方法,標(biāo)注注解Root,表示根文件夾,這里表示當(dāng)webdav請(qǐng)求服務(wù)的時(shí)候,找到的根文件夾。
這個(gè)時(shí)候還需要從根文件夾中獲取文件或者文件夾,需要另外寫(xiě)其他的方法,來(lái)表示獲取根文件夾中的文件/文件夾,或者從文件夾中獲取下一級(jí)文件夾(直到遞歸到最后一級(jí)文件夾),這時(shí)候只需要寫(xiě)一個(gè)方法就好了,標(biāo)注注解ChildrenOf

方法傳入了一個(gè)實(shí)體類(lèi),這里可以發(fā)現(xiàn),這個(gè)實(shí)體類(lèi)和Root返回的根文件夾是同一個(gè)類(lèi),然后,如果實(shí)體類(lèi)的ID為空,視為獲取根文件夾下的文件/文件夾,這里返回了2個(gè)文件夾和1個(gè)txt文件:

獲取根文件的文件/文件夾都設(shè)置了有ID,于是后續(xù)調(diào)用這個(gè)方法的時(shí)候,會(huì)根據(jù)ID來(lái)獲取指定文件夾下的文件/文件夾
那么基于此,一個(gè)簡(jiǎn)單的webdav服務(wù)就實(shí)現(xiàn)了,看下效果:

這里把本次涉及到的核心代碼片段貼在下面:
package com.lgli.webdav.config;
import io.milton.config.HttpManagerBuilder;
import io.milton.http.HttpManager;
import io.milton.http.Request;
import io.milton.http.ResourceFactory;
import io.milton.http.Response;
import io.milton.http.annotated.AnnotationResourceFactory;
import io.milton.http.fs.NullSecurityManager;
import io.milton.http.http11.DefaultHttp11ResponseHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
/**
* springboot整合milton過(guò)濾器
*
* @author lgli
*/
public class SpringMiltonFilter extends GenericFilterBean {
/**
* milton配置類(lèi)
*/
private MiltonProperties miltonProperties;
/**
* milton Http管理構(gòu)建器
* 主要用于生成HttpManager用于處理webdav請(qǐng)求
*/
private HttpManagerBuilder httpManagerBuilder;
/**
* milton Http處理webdav
*/
private HttpManager httpManager;
public void setMiltonProperties(MiltonProperties miltonProperties) {
this.miltonProperties = miltonProperties;
}
public void setHttpManagerBuilder(HttpManagerBuilder httpManagerBuilder) {
this.httpManagerBuilder = httpManagerBuilder;
}
HttpManagerBuilder httpManagerBuilder(@Autowired ResourceFactory resourceFactory
, @Autowired MiltonProperties mp) {
HttpManagerBuilder builder = new HttpManagerBuilder();
builder.setResourceFactory(resourceFactory);
builder.setBuffering(DefaultHttp11ResponseHandler.BUFFERING.whenNeeded);
builder.setEnableCompression(false);
builder.setEnableBasicAuth(false);
builder.setEnableCookieAuth(false);
builder.setControllerPackagesToScan(mp.getController());
builder.setUrlAdapter((re) -> {
String s = HttpManager.decodeUrl(re.getAbsolutePath());
List<String> httpUrl = miltonProperties.getHttpUrl();
for (String s1 : httpUrl) {
boolean isSatisfy = s.contains(s1);
if (isSatisfy) {
s = s.replace(s1, "");
}
}
if( s.contains( "/DavWWWRoot")) {
s = s.replace( "/DavWWWRoot", "");
}
return s;
});
return builder;
}
ResourceFactory getResourceFactory() {
AnnotationResourceFactory factory = new AnnotationResourceFactory();
factory.setSecurityManager(new NullSecurityManager());
return factory;
}
public void destroy() {
super.destroy();
Optional.of(httpManager).ifPresent(HttpManager::shutdown);
}
protected void initFilterBean() throws ServletException {
super.initFilterBean();
Optional.of(httpManagerBuilder).ifPresent(hmb -> httpManager = hmb.buildHttpManager());
}
public void doFilter(ServletRequest servletRequest
, ServletResponse servletResponse
, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String requestURI = request.getRequestURI();
//判斷是否需要milton處理
boolean present = miltonProperties.getHttpUrl()
.stream()
.anyMatch(requestURI::startsWith);
if (!present) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
Request miltonReq = new io.milton.servlet
.ServletRequest(request, getServletContext());
Response miltonRes = new io.milton.servlet
.ServletResponse((HttpServletResponse) servletResponse);
httpManager.process(miltonReq, miltonRes);
}
}
package com.lgli.webdav.controller;
import cn.hutool.core.util.StrUtil;
import com.lgli.webdav.entity.FileEntity;
import com.lgli.webdav.entity.FolderEntity;
import com.lgli.webdav.entity.ResourceEntity;
import io.milton.annotations.*;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* ResourceController
*
* @author lgli
*/
public class ResourceManageController {
public FolderEntity getRoot() {
FolderEntity folderEntity = new FolderEntity();
folderEntity.setName("root");
return folderEntity;
}
public List<ResourceEntity> getList(FolderEntity folder) {
List<ResourceEntity> list = new ArrayList<>();
if (StrUtil.isEmpty(folder.getId())) {
//根文件夾
FolderEntity folderEntity0 = new FolderEntity();
folderEntity0.setId("0");
folderEntity0.setName("文件夾0");
list.add(folderEntity0);
FolderEntity folderEntity1 = new FolderEntity();
folderEntity1.setId("1");
folderEntity1.setName("文件夾1");
list.add(folderEntity1);
FileEntity fileEntity0 = new FileEntity();
fileEntity0.setId("2");
fileEntity0.setName("文件1.txt");
list.add(fileEntity0);
} else if ("0".equals(folder.getId())) {
FolderEntity folderEntity0 = new FolderEntity();
folderEntity0.setId("0-1");
folderEntity0.setName("文件夾0-1");
list.add(folderEntity0);
} else if ("1".equals(folder.getId())) {
FolderEntity folderEntity0 = new FolderEntity();
folderEntity0.setId("1-1");
folderEntity0.setName("文件夾1-1");
list.add(folderEntity0);
} else if ("1-1".equals(folder.getId())) {
FolderEntity folderEntity0 = new FolderEntity();
folderEntity0.setId("1-1-1");
folderEntity0.setName("文件夾1-1-1");
list.add(folderEntity0);
}
return list;
????}
}
后續(xù)將對(duì) demo每個(gè)類(lèi)和Resource類(lèi)做一個(gè)詳細(xì)的介紹,同時(shí)還會(huì)添加權(quán)限認(rèn)證, 請(qǐng)持續(xù)關(guān)注,
如果你也感興趣,麻煩多多支持。謝謝!
如有錯(cuò)誤,煩請(qǐng)指正。
點(diǎn)擊公眾號(hào)獲取更多
