Compare commits

..

41 Commits
3.2 ... 3.2.5

Author SHA1 Message Date
zhaojun
803b8cdf71 🔖 发布 3.2.5 版本 2022-05-16 21:41:59 +08:00
zhaojun
eca5f7e48b feature: 新增挂载 webdav client pom 依赖 2022-05-16 21:41:36 +08:00
zhaojun
b70c37f3f0 feature: 新增挂载 webdav 功能 2022-05-16 21:40:58 +08:00
zhaojun
5cb2844141 🐛 修复错误的 content_disposition 和 contentType 导致下载文件格式被浏览器识别错误 2022-05-16 20:50:49 +08:00
赵俊
4dd6cdb4b3 合并拉取请求 #348
fix: modify extract driveId regex in WebDavController
2022-05-03 21:16:40 +08:00
quericy
c6127c029f fix: modify extract driveId regex in WebDavController 2022-04-14 10:06:46 +08:00
zhaojun
e149039ecb 🔖 发布 3.2.3 版本 2022-04-13 00:03:49 +08:00
zhaojun
37688d83cf feature: 降低 webdav 日志级别 2022-04-13 00:03:28 +08:00
zhaojun
6692016642 feature: 新增 webdav 开关功能 2022-04-13 00:01:59 +08:00
zhaojun
3f41aeda9a fix(bug): 修复因字段名称修改升级后导致短链异常 bug 2022-04-12 23:38:49 +08:00
赵俊
de86d5c47d 合并拉取请求 #347
feat: support webdav
2022-04-11 09:56:11 +08:00
quericy
c89e072005 feat: support webdav
1, feat:config webDav and milton in configuration;
2, feat:add event handler in WebDavController;
3, feat:check auth by admin in systemConfig;
4, feat:download webDav file by redirect to site DirectLink;
2022-04-10 04:11:34 +08:00
zhaojun
b533b5e959 🔖 发布 3.2.2 版本 2022-04-02 18:50:50 +08:00
zhaojun
d35cf27d47 fix(bug): ftp 默认为被动模式,防止因端口不同,无法读取。 2022-04-02 18:50:26 +08:00
zhaojun
796c4c1fb0 页面更新 2022-04-02 18:49:26 +08:00
zhaojun
1033d6c1a9 fix(bug): 修复某些情况下直链无法正常使用的 BUG 2022-04-02 18:36:44 +08:00
zhaojun
e09c6b4e58 fix(bug): 修复又拍云文件中包含特殊字符时,调用 upyun 接口未进行 url encode 返回 400,影响又拍云单文件直链无法使用。 2022-04-02 18:26:32 +08:00
zhaojun
168b0b08f3 fix(bug): 修复本地存储可以通过特殊路径符获取任意目录的 BUG. 2022-04-02 18:24:49 +08:00
zhaojun
60a6a5348c fix(bug): 修复获取音频信息时,因文件链接 302, 导致无法正常获取音频文件信息的 BUG. 2022-04-02 18:24:00 +08:00
zhaojun
2ec8a5df1f fix(bug): 修复 sharepoint 未自动刷新 accessToken 和 refreshToken 的 bug 2022-02-02 20:50:13 +08:00
zhaojun
47b5f6ac12 🔖 发布 3.2.1 版本 2022-02-02 20:46:45 +08:00
zhaojun
c64c8465f2 fix(bug): 升级相关有漏洞的依赖版本为最新版 2022-02-02 20:46:08 +08:00
zhaojun
f636681dd8 fix(bug): 修复本地存储可通过特殊命令访问到非指定目录的文件 2022-02-02 20:45:05 +08:00
赵俊
eadd2434e0 新增 ZFILE 社区地址 2021-10-30 21:05:11 +08:00
赵俊
b84c0bff42 Merge remote-tracking branch 'origin/master' 2021-10-07 17:53:19 +08:00
赵俊
4cb5b84bfe 🐛 修复 S3 协议无法正常保存的 BUG 2021-10-07 17:52:45 +08:00
赵俊
0351b4401c Merge pull request #286 from pippen57/master
转义SQL关键字, 防止创建表时出现错误
2021-09-25 18:29:42 +08:00
pippen
48cb14be8a 转义SQL关键字, 防止创建表时出现错误 2021-09-25 12:48:31 +08:00
赵俊
eea2ff11f9 ✏️ 修改文档拼写错误 2021-09-21 11:22:00 +08:00
赵俊
325ec1a348 对于因前端 vue history 路由模式下, 直接访问路径时, 出现的 404 响应码, 转化为 200 响应码. (404 下会将请求转发到首页) 2021-09-20 17:06:27 +08:00
赵俊
93205266d3 更新文档 2021-09-20 16:35:30 +08:00
赵俊
6849a4347f 去除自定义 404 错误页面 2021-09-20 16:33:54 +08:00
赵俊
cb5c6a5945 🎨 提交前端页面 2021-09-20 16:33:31 +08:00
赵俊
5ed45c3bb3 🔒 修复因升级 springboot 版本导致的安全和兼容问题 2021-09-20 16:20:25 +08:00
赵俊
4442ec3165 修复因前端 vue history 路由模式下, 直接访问路径时, 请求 /admin/ 下的路由会被权限框架拦截并校验登陆状态, 导致静态页面报错问题. 2021-09-20 16:19:21 +08:00
赵俊
b268a24333 修复因前端 vue history 路由模式下, 直接访问路径时, 接口和路径名冲突问题 2021-09-20 16:17:59 +08:00
赵俊
84f9354d4e 对于因前端 vue history 路由模式下, 直接访问路径时, 出现的 404 响应码, 转化为 200 响应码. (404 下会将请求转发到首页) 2021-09-20 16:17:16 +08:00
赵俊
8dfc4f8004 跨域请求过滤器修改为自注解注册方式 2021-09-20 16:14:45 +08:00
赵俊
1158f5c2b9 🐛 修复 OneDrive 加速域名在直链下未生效的 BUG 2021-09-20 11:18:13 +08:00
赵俊
c5f0e15207 🐛 修复因升级 spring boot 后部分参数过时, 导致的第一次启动无法初始化参数的 BUG 2021-09-19 21:33:07 +08:00
赵俊
68a842ce75 🐛 修复页面路径和接口地址冲突的 BUG 2021-09-19 16:08:19 +08:00
44 changed files with 1075 additions and 286 deletions

View File

@@ -16,7 +16,7 @@ assignees: ''
请确认你已经做了下面这些事情,若 bug 还是未解决,那么请尽可详细地描述你的问题。
- 我已经安装了最新版的 ZFile
- 我已经阅读了 ZFile 的文档http://docs.zhaojun.im/zfile
- 我已经阅读了 ZFile 的文档https://docs.zfile.vip
- 我已经搜索了已有的 Issues 列表中有关的信息
- 我已经清理过浏览器缓存并重试
-->

View File

@@ -19,9 +19,11 @@
## 相关地址
预览地址: [https://zfile.jun6.net](https://zfile.jun6.net)
预览地址: [https://zfile.vip](https://zfile.vip)
文档地址: [http://docs.zhaojun.im/zfile](http://docs.zhaojun.im/zfile)
文档地址: [https://docs.zfile.vip](https://docs.zfile.vip)
社区地址: [https://bbs.zfile.vip](https://bbs.zfile.vip)
项目源码: [https://github.com/zhaojun1998/zfile](https://github.com/zhaojun1998/zfile)
@@ -69,14 +71,14 @@ apt update && apt install -y adoptopenjdk-8-hotspot-jre
> 如为更新程序, 则请先执行 `~/zfile/bin/stop.sh && rm -rf ~/zfile` 清理旧程序. 首次安装请忽略此选项.
下载项目:
```bash
cd ~
export ZFILE_INSTALL_PATH=~/zfile
mkdir -p $ZFILE_INSTALL_PATH && cd $ZFILE_INSTALL_PATH
wget https://c.jun6.net/ZFILE/zfile-release.war
mkdir zfile && unzip zfile-release.war -d zfile && rm -rf zfile-release.war
chmod +x zfile/bin/*.sh
unzip zfile-release.war && rm -rf zfile-release.war
chmod +x $ZFILE_INSTALL_PATH/bin/*.sh
```
> 下载指定版本可以将 `zfile-release.war` 改为 `zfile-x.x.war`,如 `zfile-2.2.war`。
@@ -98,7 +100,7 @@ chmod +x zfile/bin/*.sh
~/zfile/bin/start.sh
```
篇幅有限, 更详细的安装教程及介绍请参考: [ZFile 文档](http://docs.zhaojun.im/zfile)
篇幅有限, 更详细的安装教程及介绍请参考: [ZFile 文档](https://docs.zfile.vip)
访问地址:
@@ -120,29 +122,6 @@ chmod +x zfile/bin/*.sh
![后台设置-新增驱动器](https://cdn.jun6.net/2021/03/23/e70e04f8cc5b6.png)
![后台设置-站点设置](https://cdn.jun6.net/2021/03/23/fd946991bb6b9.png)
## 开发计划
- [x] API 支持 [点击查看文档](https://github.com/zhaojun1998/zfile/blob/master/API.md)
- [x] 更方便的部署方式
- [x] 布局优化 - 自定义操作按钮 (现为右键实现)
- [x] 后台优化 - 设置按照其功能进行分离
- [x] 体验优化 - 支持前后端分离部署
- [x] 体验优化 - 文本预览更换 vscode 同款编辑器 monaco editor
- [x] 架构调整 - 支持多存储策略
- [x] 体验优化 - 忽略文件列表 (正则表达式)
- [x] 新功能 - Docker 支持
- [x] 新功能 - 图片模式
- [x] 新功能 - 直链/短链管理
- [ ] ~~新功能 - 后台支持上传、编辑、删除等操作 (不再支持)~~
- [ ] 体验优化 - 自定义支持预览的文件后缀 (正则表达式)
- [ ] 体验优化 - 一键安装脚本
- [ ] 新功能 - 分享功能,支持分享密码,文件夹分享
- [ ] 新功能 - 直链支持 Referer 防盗链
- [ ] 体验优化 - 视频列表支持
- [ ] 新功能 - 单独页面打开文件预览
- [ ] 新功能 - 在线查看日志功能
- [ ] 部署优化 - Docker Compose 支持
## 支持作者
如果本项目对你有帮助,请作者喝杯咖啡吧。
@@ -153,10 +132,6 @@ chmod +x zfile/bin/*.sh
[![starcharts stargazers over time](https://starchart.cc/zhaojun1998/zfile.svg)](https://starchart.cc/zhaojun1998/zfile.svg)
## 服务器赞助
<a href="https://kuline.cn"><img src="https://cdn.jun6.net/2021/05/14/1f6a4f0ad09ce.png" width="100px"></a>
## 开发工具赞助
<a href="https://www.jetbrains.com/?from=zfile"><img src="https://cdn.jun6.net/2021/04/21/26e410d60b0b0.png?1=1" width="100px"></a>

35
pom.xml
View File

@@ -12,13 +12,14 @@
<groupId>im.zhaojun</groupId>
<artifactId>zfile</artifactId>
<version>3.2</version>
<version>3.2.5</version>
<name>zfile</name>
<packaging>war</packaging>
<description>一个在线的文件浏览系统</description>
<properties>
<java.version>1.8</java.version>
<log4j2.version>2.17.1</log4j2.version>
</properties>
<dependencies>
@@ -56,11 +57,13 @@
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
<version>1.4.197</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.27</version>
</dependency>
<!-- 工具类 -->
@@ -84,7 +87,6 @@
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.8</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
@@ -92,6 +94,31 @@
<version>3.6</version>
</dependency>
<!-- WebDav -->
<dependency>
<groupId>io.milton</groupId>
<artifactId>milton-server-ce</artifactId>
<version>3.1.1.413</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
<exclusion>
<artifactId>json</artifactId>
<groupId>org.json</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- webdav client -->
<dependency>
<groupId>com.github.lookfirst</groupId>
<artifactId>sardine</artifactId>
<version>5.10</version>
</dependency>
<!-- 其他工具类 -->
<dependency>
<groupId>org.projectlombok</groupId>
@@ -108,7 +135,7 @@
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.61</version>
<version>1.2.69</version>
</dependency>
@@ -149,4 +176,4 @@
</plugins>
</build>
</project>
</project>

View File

@@ -0,0 +1,36 @@
package im.zhaojun.zfile.config;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
/**
* 应用上下文配置
*
* @author me
* @date 2022/4/9
*/
@Configuration
public class ApplicationContextConfigure implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextConfigure.applicationContext = applicationContext;
}
/**
* bean名称获取对象
*/
public static Object getBean(String name) throws BeansException {
return applicationContext.getBean(name);
}
/**
* bean类型获取对象
*/
public static <T> T getBean(Class<T> clazz) throws BeansException {
return applicationContext.getBean(clazz);
}
}

View File

@@ -1 +1 @@
package im.zhaojun.zfile.config;
package im.zhaojun.zfile.config;

View File

@@ -42,8 +42,10 @@ public class WebMvcConfig implements WebMvcConfigurer {
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
return factory -> {
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/index.html");
ErrorPage error200Page = new ErrorPage(HttpStatus.OK, "/index.html");
Set<ErrorPage> errorPages = new HashSet<>();
errorPages.add(error404Page);
errorPages.add(error200Page);
factory.setErrorPages(errorPages);
};
}

View File

@@ -39,12 +39,4 @@ public class ZFileConfiguration {
return restTemplate;
}
@Bean
public FilterRegistrationBean filterRegist() {
FilterRegistrationBean frBean = new FilterRegistrationBean();
frBean.setFilter(new CorsFilter());
frBean.addUrlPatterns("/*");
return frBean;
}
}

View File

@@ -0,0 +1,60 @@
package im.zhaojun.zfile.config.webdav;
import im.zhaojun.zfile.config.webdav.adapter.WebDavUrlAdapterImpl;
import im.zhaojun.zfile.config.webdav.auth.SystemConfigSecurityManager;
import im.zhaojun.zfile.config.webdav.resolver.WebDavRedirectViewResolver;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.model.dto.SystemConfigDTO;
import im.zhaojun.zfile.service.SystemConfigService;
import io.milton.http.ResourceFactory;
import io.milton.http.SecurityManager;
import io.milton.http.annotated.AnnotationResourceFactory;
import io.milton.http.fs.NullSecurityManager;
import io.milton.servlet.DefaultMiltonConfigurator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* Milton(webDav)配置
*
* @author me
* @date 2022/4/9
*/
@Configuration
public class MiltonConfiguration extends DefaultMiltonConfigurator implements InitializingBean {
/**
* 安全管理器
*/
private static SecurityManager securityManager = new NullSecurityManager();
@Resource
private SystemConfigService systemConfigService;
/**
* 构建milton初始化配置
*/
@Override
protected void build() {
builder.setSecurityManager(securityManager);
builder.setContextPath(ZFileConstant.WEB_DAV_PREFIX);
builder.setUrlAdapter(new WebDavUrlAdapterImpl());
final ResourceFactory resourceFactory = builder.getResourceFactory();
if (resourceFactory instanceof AnnotationResourceFactory) {
((AnnotationResourceFactory) resourceFactory).setViewResolver(new WebDavRedirectViewResolver());
}
super.build();
}
/**
* 属性初始化完成后,更新安全管理器,使用系统配置鉴权
*/
@Override
public void afterPropertiesSet() throws Exception {
final SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
if (systemConfig != null) {
securityManager = new SystemConfigSecurityManager(systemConfig);
}
}
}

View File

@@ -0,0 +1,33 @@
package im.zhaojun.zfile.config.webdav;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import io.milton.http.annotated.AnnotationResourceFactory;
import io.milton.servlet.MiltonFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* WebDav配置
*
* @author me
* @date 2022/4/9
*/
@Configuration
public class WebDavConfiguration {
@Bean
public FilterRegistrationBean miltonFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new MiltonFilter());
registration.setName("miltonFilter");
registration.addUrlPatterns(ZFileConstant.WEB_DAV_PREFIX + "/*");
registration.addInitParameter("resource.factory.class", AnnotationResourceFactory.class.getName());
registration.addInitParameter("milton.configurator", MiltonConfiguration.class.getName());
registration.addInitParameter("controllerPackagesToScan", "im.zhaojun.zfile.controller.home");
registration.setOrder(1);
return registration;
}
}

View File

@@ -0,0 +1,41 @@
package im.zhaojun.zfile.config.webdav.adapter;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.util.RegexMatchUtils;
import io.milton.http.HttpManager;
import io.milton.http.Request;
import io.milton.http.UrlAdapter;
import java.util.regex.Matcher;
/**
* WebDav路径适配器实现
*
* @author me
* @date 2022/4/10
*/
public class WebDavUrlAdapterImpl implements UrlAdapter {
/**
* 获取url
* eg: domain.com/{webdavPrefix}/{driveId}/{folders}
*
* @param request 请求
* @return {@link String}
*/
@Override
public String getUrl(Request request) {
// 匹配url前缀和驱动器ID
Matcher matcher = RegexMatchUtils.match("^" + ZFileConstant.WEB_DAV_PREFIX + "/(\\d+)(.*)",
HttpManager.decodeUrl(request.getAbsolutePath()));
final String driveId = RegexMatchUtils.getIndexResult(matcher, 1);
if (driveId == null) {
return "";
}
// 获取摘除前缀和驱动器ID后的文件路径
final String realPath = RegexMatchUtils.getIndexResult(matcher, 2);
return realPath != null ? realPath : "";
}
}

View File

@@ -0,0 +1,130 @@
package im.zhaojun.zfile.config.webdav.auth;
import cn.hutool.core.map.MapUtil;
import cn.hutool.crypto.SecureUtil;
import im.zhaojun.zfile.model.dto.SystemConfigDTO;
import io.milton.http.Auth;
import io.milton.http.Request;
import io.milton.http.Request.Method;
import io.milton.http.http11.auth.DigestResponse;
import io.milton.resource.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
/**
* 基于当前系统配置的WebDav鉴权管理器
*
* @author me
* @date 2022/4/10
* @see io.milton.http.fs.SimpleSecurityManager
*/
public class SystemConfigSecurityManager implements io.milton.http.SecurityManager {
private static final Logger log = LoggerFactory.getLogger(SystemConfigSecurityManager.class);
private String realm = "SystemConfig";
private Map<String, String> nameAndPasswords;
/**
* 根据系统配置创建安全管理器
*
* @param systemConfig 系统配置DTO
*/
public SystemConfigSecurityManager(SystemConfigDTO systemConfig) {
if (systemConfig != null) {
this.nameAndPasswords = MapUtil.of(systemConfig.getUsername(), systemConfig.getPassword());
}
}
public Object getUserByName(String name) {
String actualPassword = nameAndPasswords.get(name);
if (actualPassword != null) {
return name;
}
return null;
}
/**
* 用户名+密码身份验证
*
* @param user 用户
* @param password 密码
* @return {@link Object}
*/
@Override
public Object authenticate(String user, String password) {
if (user.contains("@")) {
user = user.substring(0, user.indexOf("@"));
}
String actualPassword = nameAndPasswords.get(user);
if (actualPassword == null) {
log.debug("user not found: " + user);
return null;
} else {
//比对密码MD5摘要
return (actualPassword.equals(SecureUtil.md5(password))) ? user : null;
}
}
/**
* 请求摘要身份验证(不进行换算)
*
* @param digestRequest 消化的请求
* @return {@link Object}
*/
@Override
public Object authenticate(DigestResponse digestRequest) {
String serverResponse = nameAndPasswords.get(digestRequest.getUser());
String clientResponse = digestRequest.getResponseDigest();
//比对密码MD5摘要
if (serverResponse.equals(SecureUtil.md5(clientResponse))) {
return "ok";
} else {
return null;
}
}
@Override
public boolean authorise(Request request, Method method, Auth auth, Resource resource) {
if (auth == null) {
log.trace("authorise: declining because there is no auth object");
return false;
} else {
if (auth.getTag() == null) {
log.trace("authorise: declining because there is no auth.getTag() object");
return false;
} else {
log.trace("authorise: permitting because there is an authenticated user associated with this request");
return true;
}
}
}
@Override
public String getRealm(String host) {
return realm;
}
/**
* @param realm the realm to set
*/
public void setRealm(String realm) {
this.realm = realm;
}
public void setNameAndPasswords(Map<String, String> nameAndPasswords) {
this.nameAndPasswords = nameAndPasswords;
}
@Override
public boolean isDigestAllowed() {
// 关闭请求摘要换算client端请求时若换算为摘要则无法和系统设置中获取的密码MD5比对
return false;
}
}

View File

@@ -0,0 +1,62 @@
package im.zhaojun.zfile.config.webdav.resolver;
import cn.hutool.core.util.URLUtil;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.model.entity.webdav.WebDavFile;
import io.milton.common.View;
import io.milton.http.template.TemplateProcessor;
import io.milton.http.template.ViewResolver;
import io.milton.servlet.OutputStreamWrappingHttpServletResponse;
import io.milton.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
/**
* WebDav重定向视图处理器
* Get注解handler返回字符串时将使用本ViewResolver处理
*
* @author me
* @date 2022/4/9
*/
public class WebDavRedirectViewResolver implements ViewResolver {
@Override
public TemplateProcessor resolveView(View view) {
return new RedirectTemplateProcessor();
}
/**
* 重定向模板处理程序
*
* @author me
* @date 2022/04/10
*/
public static class RedirectTemplateProcessor implements TemplateProcessor {
@Override
public void execute(Map<String, Object> model, OutputStream out) {
try {
// 获取要下载的资源文件
final Object resource = model.get("resource");
if (!(resource instanceof WebDavFile)) {
throw new RuntimeException("couldn't get direct url.");
}
final WebDavFile file = (WebDavFile) resource;
// 构造文件直链的路径
final String redirectPath = String.format("/%s/%s%s", ZFileConstant.DIRECT_LINK_PREFIX, file.getDriveId(), file.getFullPath());
// 重定向到直链
HttpServletResponse resp = new OutputStreamWrappingHttpServletResponse(ServletResponse.getResponse(), out);
resp.setStatus(301);
resp.setHeader("Location", URLUtil.encode(redirectPath));
resp.setHeader("Connection", "close");
resp.flushBuffer();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}

View File

@@ -1 +1 @@
package im.zhaojun.zfile.controller.admin;
package im.zhaojun.zfile.controller.admin;

View File

@@ -0,0 +1,159 @@
package im.zhaojun.zfile.controller.home;
import com.alibaba.fastjson.JSON;
import im.zhaojun.zfile.config.ApplicationContextConfigure;
import im.zhaojun.zfile.context.DriveContext;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.entity.webdav.WebDavEntity;
import im.zhaojun.zfile.model.entity.webdav.WebDavFile;
import im.zhaojun.zfile.model.entity.webdav.WebDavFolder;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import im.zhaojun.zfile.util.RegexMatchUtils;
import io.milton.annotations.*;
import io.milton.http.HttpManager;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* WebDav控制器
*
* @author me
* @date 2022/4/9
*/
@Slf4j
@ResourceController
@ConditionalOnProperty(value = "webdav.enable", havingValue = "true")
public class WebDavController {
private static final Logger LOGGER = LoggerFactory.getLogger(WebDavController.class);
/**
* 获取根目录文件夹
*
* @return {@link WebDavFolder} WebDav文件夹
*/
@Root
public WebDavFolder getRootFolder() {
return new WebDavFolder(ZFileConstant.PATH_SEPARATOR, getDriveId());
}
/**
* 获取根目录子文件/文件夹(控制器)
*
* @param rootFolder 根文件夹
* @return {@link WebDavController} 根控制器
*/
@ChildrenOf
public WebDavController getChildren(WebDavController rootFolder) {
return this;
}
/**
* 获取子文件/文件夹
*
* @param parent 父文件夹
* @return {@link List}<{@link WebDavEntity}> WebDav实体
*/
@ChildrenOf
public List<WebDavEntity> getChildren(WebDavFolder parent) {
if (parent == null) {
return Collections.emptyList();
}
try {
// 获取驱动器文件服务
AbstractBaseFileService fileService = ApplicationContextConfigure.getBean(DriveContext.class).get(parent.getDriveId());
if (fileService == null) {
return Collections.emptyList();
}
// 获取文件列表
List<FileItemDTO> fileItemList = fileService.fileList(parent.getFullPath());
// 转换FileItemDTO为WebDavEntity
return WebDavEntity.convertFromFileItemDTO(fileItemList, parent);
} catch (Exception e) {
LOGGER.warn("get webDav children failed,parent:{},msg:{}", JSON.toJSONString(parent), e.getMessage(), e);
return Collections.emptyList();
}
}
/**
* 获取子文件内容
*
* @param webDavFile WebDav文件
* @return {@link String} ViewResolver模板名称
*/
@Get
public String getChild(WebDavFile webDavFile) {
return JSON.toJSONString(webDavFile);
}
/**
* 获取WebDav实体文件名
*/
@Name
public String getWebDavFile(WebDavEntity webDavEntity) {
return webDavEntity.getName();
}
/**
* 获取WebDav实体展示名称
*/
@DisplayName
public String getDisplayName(WebDavEntity webDavEntity) {
return webDavEntity.getName();
}
/**
* 获取WebDav实体唯一id
*/
@UniqueId
public String getUniqueId(WebDavEntity entity) {
return entity.getId().toString();
}
/**
* 获取WebDav实体修改日期
*/
@ModifiedDate
public Date getModifiedDate(WebDavEntity webDavEntity) {
return webDavEntity.getModifiedDate();
}
/**
* 获取WebDav实体创建日期
*/
@CreatedDate
public Date getCreatedDate(WebDavEntity webDavEntity) {
return webDavEntity.getCreatedDate();
}
/**
* 获取WebDav实体大小
*/
@ContentLength
public Long getContentLength(WebDavEntity entity) {
if (entity instanceof WebDavFile) {
return ((WebDavFile) entity).getSize();
}
// 性能考虑,文件夹暂不进行大小统计
return null;
}
/**
* 获取驱动器id
*
* @return {@link Integer}
*/
private Integer getDriveId() {
String requestUrl = HttpManager.decodeUrl(HttpManager.request().getAbsolutePath());
final String driveId = RegexMatchUtils.matchByIndex("^" + ZFileConstant.WEB_DAV_PREFIX + "/(\\d+)(.*)", requestUrl, 1);
return driveId != null ? Integer.valueOf(driveId) : null;
}
}

View File

@@ -30,7 +30,7 @@ public class InstallController {
}
@PostMapping("/install")
@PostMapping("/doInstall")
public ResultBean install(SystemConfigDTO systemConfigDTO) {
if (!StringUtils.isEmpty(systemConfigService.getAdminUsername())) {
return ResultBean.error("请勿重复初始化.");

View File

@@ -1,6 +1,8 @@
package im.zhaojun.zfile.filter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.filter.GenericFilterBean;
@@ -8,6 +10,7 @@ import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@@ -16,6 +19,9 @@ import java.io.IOException;
* 开启跨域支持. 一般用于开发环境, 或前后端分离部署时开启.
* @author zhaojun
*/
@Order(1)
@WebFilter(value = "/*")
@Component
public class CorsFilter extends GenericFilterBean {
@Override

View File

@@ -0,0 +1 @@
package im.zhaojun.zfile.filter;

View File

@@ -23,6 +23,12 @@ public class StorageConfigConstant {
public static final String PASSWORD_KEY = "password";
public static final String WEBDAV_USERNAME = "webdavUsername";
public static final String WEBDAV_PASSWORD = "webdavPassword";
public static final String WEBDAV_URL = "webdavUrl";
public static final String HOST_KEY = "host";
public static final String PORT_KEY = "port";

View File

@@ -21,6 +21,11 @@ public class ZFileConstant {
*/
public static String DIRECT_LINK_PREFIX = "directlink";
/**
* WebDav前缀
*/
public static String WEB_DAV_PREFIX = "/webdav";
/**
* 系统产生的临时文件路径
*/

View File

@@ -48,4 +48,12 @@ public class StorageStrategyConfig {
private String proxyDomain;
private String region;
private String webdavUsername;
private String webdavPassword;
private String webdavUrl;
}

View File

@@ -2,10 +2,7 @@ package im.zhaojun.zfile.model.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.*;
import java.util.Date;
@Entity(name = "SHORT_LINK")
@@ -22,4 +19,4 @@ public class ShortLinkConfig {
private Date createDate;
}
}

View File

@@ -0,0 +1,112 @@
package im.zhaojun.zfile.model.entity.webdav;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.enums.FileTypeEnum;
import im.zhaojun.zfile.util.StringUtils;
import lombok.Data;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
/**
* WebDav实体
*
* @author me
* @date 2022/4/9
*/
@Data
public class WebDavEntity {
/**
* 唯一ID
*/
private UUID id;
/**
* 驱动器ID
*/
private Integer driveId;
/**
* 名称
*/
private String name;
/**
* 创建时间
*/
private Date createdDate;
/**
* 修改时间
*/
private Date modifiedDate;
/**
* 是否是目录
*/
private boolean isDirectory;
/**
* 父文件夹
*/
private WebDavFolder parent;
public WebDavEntity() {
}
public WebDavEntity(String name, WebDavFolder parent) {
this.id = UUID.randomUUID();
this.name = name;
this.parent = parent;
this.createdDate = new Date();
this.modifiedDate = new Date();
this.isDirectory = true;
}
public WebDavEntity(UUID id, String name, Date createdDate, Date modifiedDate,
WebDavFolder parent) {
this.id = id;
this.name = name;
this.parent = parent;
this.createdDate = createdDate;
this.modifiedDate = modifiedDate;
this.isDirectory = true;
}
/**
* 获取全路径
*/
public String getFullPath() {
if (this.getParent() != null) {
final String parentFullPath = this.getParent().getFullPath();
return StringUtils.removeDuplicateSeparator(parentFullPath + ZFileConstant.PATH_SEPARATOR + this.getName());
} else {
return ZFileConstant.PATH_SEPARATOR;
}
}
public static List<WebDavEntity> convertFromFileItemDTO(List<FileItemDTO> fileItemList, WebDavFolder parent) {
List<WebDavEntity> result = new ArrayList<>();
if (fileItemList == null || fileItemList.size() == 0) {
return result;
}
for (FileItemDTO each : fileItemList) {
WebDavEntity entity = convertFromFileItemDTO(each, parent);
result.add(entity);
}
return result;
}
public static WebDavEntity convertFromFileItemDTO(FileItemDTO fileItemDTO, WebDavFolder parent) {
if (fileItemDTO == null) {
return null;
}
WebDavEntity entity;
if (fileItemDTO.getType() == FileTypeEnum.FOLDER) {
entity = new WebDavFolder(fileItemDTO.getName(), parent);
} else {
entity = new WebDavFile(fileItemDTO.getName(), fileItemDTO.getSize(), parent);
}
entity.setModifiedDate(fileItemDTO.getTime());
entity.setDriveId(parent.getDriveId());
return entity;
}
}

View File

@@ -0,0 +1,37 @@
package im.zhaojun.zfile.model.entity.webdav;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import java.util.UUID;
/**
* WebDav文件实体
*
* @author me
* @date 2022/4/9
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class WebDavFile extends WebDavEntity {
/**
* 大小
*/
private Long size;
/**
* 内容类型
*/
private String contentType;
public WebDavFile(String fileName, Long size, WebDavFolder parent) {
super(fileName, parent);
this.setSize(size);
this.setDirectory(false);
}
public WebDavFile(UUID id, String name, Date createdDate, Date modifiedDate, WebDavFolder parent) {
super(id, name, createdDate, modifiedDate, parent);
this.setDirectory(false);
}
}

View File

@@ -0,0 +1,41 @@
package im.zhaojun.zfile.model.entity.webdav;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import java.util.UUID;
/**
* WebDav文件夹实体
*
* @author me
* @date 2022/4/9
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class WebDavFolder extends WebDavEntity {
public WebDavFolder(String folderName, WebDavFolder parent) {
super(folderName, parent);
}
public WebDavFolder(String folderName, Integer driveId) {
super(folderName, null);
setDriveId(driveId);
}
public WebDavFolder(UUID id, String name, Date createdDate, Date modifiedDate, WebDavFolder parent) {
super(id, name, createdDate, modifiedDate, parent);
}
public WebDavFile addFile(String fileName, Long size) {
WebDavFile file = new WebDavFile(fileName, size, this);
file.setDirectory(false);
return file;
}
public WebDavFolder addFolder(String folderName) {
return new WebDavFolder(folderName, this);
}
}

View File

@@ -17,6 +17,7 @@ public enum StorageTypeEnum {
LOCAL("local", "本地存储"),
ALIYUN("aliyun", "阿里云 OSS"),
TENCENT("tencent", "腾讯云 COS"),
WebDAV("webdav", "WebDAV"),
UPYUN("upyun", "又拍云 USS"),
FTP("ftp", "FTP"),
UFILE("ufile", "UFile"),

View File

@@ -40,6 +40,9 @@ public class OneDriveTokenRefreshSchedule {
List<DriveConfig> driveConfigList = driveConfigService.findByType(StorageTypeEnum.ONE_DRIVE);
driveConfigList.addAll(driveConfigService.findByType(StorageTypeEnum.ONE_DRIVE_CHINA));
driveConfigList.addAll(driveConfigService.findByType(StorageTypeEnum.SHAREPOINT_DRIVE));
driveConfigList.addAll(driveConfigService.findByType(StorageTypeEnum.SHAREPOINT_DRIVE_CHINA));
driveConfigList.forEach(driveConfig -> {
try {

View File

@@ -216,7 +216,11 @@ public abstract class MicrosoftDriveServiceBase extends AbstractBaseFileService
fileItemDTO.setTime(fileItem.getDate("lastModifiedDateTime"));
if (fileItem.containsKey(ONE_DRIVE_FILE_FLAG)) {
fileItemDTO.setUrl(fileItem.getString("@microsoft.graph.downloadUrl"));
String originUrl = fileItem.getString("@microsoft.graph.downloadUrl");
if (StringUtils.isNotNullOrEmpty(proxyDomain)) {
originUrl = StringUtils.replaceHost(originUrl, proxyDomain);
}
fileItemDTO.setUrl(originUrl);
fileItemDTO.setType(FileTypeEnum.FILE);
} else {
fileItemDTO.setType(FileTypeEnum.FOLDER);

View File

@@ -2,6 +2,7 @@ package im.zhaojun.zfile.service.impl;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.ftp.Ftp;
import cn.hutool.extra.ftp.FtpMode;
import im.zhaojun.zfile.model.constant.StorageConfigConstant;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.entity.StorageConfig;
@@ -22,6 +23,7 @@ import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -66,6 +68,7 @@ public class FtpServiceImpl extends AbstractBaseFileService implements BaseFileS
} else {
ftp = new Ftp(host, Integer.parseInt(port), username, password, StandardCharsets.UTF_8);
ftp.getClient().type(FTP.BINARY_FILE_TYPE);
ftp.setMode(FtpMode.Passive);
testConnection();
isInitialized = true;
}
@@ -88,6 +91,10 @@ public class FtpServiceImpl extends AbstractBaseFileService implements BaseFileS
for (FTPFile ftpFile : ftpFiles) {
FileItemDTO fileItemDTO = new FileItemDTO();
// 跳过 ftp 的本目录和上级目录
if (Arrays.asList(".", "..").contains(ftpFile.getName())) {
continue;
}
fileItemDTO.setName(ftpFile.getName());
fileItemDTO.setSize(ftpFile.getSize());
fileItemDTO.setTime(ftpFile.getTimestamp().getTime());

View File

@@ -1,5 +1,6 @@
package im.zhaojun.zfile.service.impl;
import cn.hutool.core.util.StrUtil;
import im.zhaojun.zfile.exception.InitializeDriveException;
import im.zhaojun.zfile.exception.NotExistFileException;
import im.zhaojun.zfile.model.constant.StorageConfigConstant;
@@ -25,6 +26,7 @@ import javax.annotation.Resource;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -72,6 +74,9 @@ public class LocalServiceImpl extends AbstractBaseFileService implements BaseFil
@Override
public List<FileItemDTO> fileList(String path) throws FileNotFoundException {
if (StrUtil.startWith(path, "..") || StrUtil.startWith(path, "/..")) {
return Collections.emptyList();
}
List<FileItemDTO> fileItemList = new ArrayList<>();
String fullPath = StringUtils.removeDuplicateSeparator(filePath + path);

View File

@@ -22,6 +22,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@@ -121,7 +122,7 @@ public class UpYunServiceImpl extends AbstractBaseFileService implements BaseFil
int lastDelimiterIndex = path.lastIndexOf("/");
String name = path.substring(lastDelimiterIndex + 1);
Map<String, String> fileInfo = upYun.getFileInfo(StringUtils.removeDuplicateSeparator(basePath + ZFileConstant.PATH_SEPARATOR + path));
Map<String, String> fileInfo = upYun.getFileInfo(URLUtil.encode(StringUtils.removeDuplicateSeparator(basePath + ZFileConstant.PATH_SEPARATOR + path), StandardCharsets.UTF_8));
if (fileInfo == null) {
throw new NotExistFileException();

View File

@@ -0,0 +1,133 @@
package im.zhaojun.zfile.service.impl;
import cn.hutool.core.util.StrUtil;
import com.github.sardine.DavResource;
import com.github.sardine.Sardine;
import com.github.sardine.SardineFactory;
import im.zhaojun.zfile.exception.NotExistFileException;
import im.zhaojun.zfile.model.constant.StorageConfigConstant;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.entity.StorageConfig;
import im.zhaojun.zfile.model.enums.FileTypeEnum;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.service.StorageConfigService;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import im.zhaojun.zfile.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* @author zhaojun
*/
@Service
@Slf4j
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class WebdavServiceImpl extends AbstractBaseFileService {
private Sardine sardine;
private String url;
@Resource
private StorageConfigService storageConfigService;
@Override
public void init(Integer driveId) {
this.driveId = driveId;
Map<String, StorageConfig> stringStorageConfigMap =
storageConfigService.selectStorageConfigMapByDriveId(driveId);
this.mergeStrategyConfig(stringStorageConfigMap);
String username = stringStorageConfigMap.get(StorageConfigConstant.WEBDAV_USERNAME).getValue();
String password = stringStorageConfigMap.get(StorageConfigConstant.WEBDAV_PASSWORD).getValue();
url = stringStorageConfigMap.get(StorageConfigConstant.WEBDAV_URL).getValue();
if (Objects.isNull(url)) {
log.debug("初始化存储策略 [{}] 失败: 参数不完整", getStorageTypeEnum().getDescription());
isInitialized = false;
} else {
// 如果用户名和密码为空,则使用默认用户名和密码
if (StrUtil.isNotEmpty(username) && StrUtil.isNotEmpty(password)) {
sardine = SardineFactory.begin("admin", "YgSBFCbH");
} else {
sardine = SardineFactory.begin();
}
testConnection();
isInitialized = true;
}
}
@Override
public List<FileItemDTO> fileList(String path) throws Exception {
List<FileItemDTO> resultList = new ArrayList<>();
String requestPath = StringUtils.removeDuplicateSeparator(url + "/" + path);
List<DavResource> resources = sardine.list(requestPath);
Integer index = 0;
for (DavResource res : resources) {
// 如果不是根目录, 则跳过第一个, 因为第一个是当前目录
if (!StrUtil.equals(path, "/") && index++ == 0) {
continue;
}
FileItemDTO fileItemResult = new FileItemDTO();
fileItemResult.setName(res.getName());
fileItemResult.setTime(res.getModified());
fileItemResult.setSize(res.getContentLength());
fileItemResult.setType(res.isDirectory() ? FileTypeEnum.FOLDER : FileTypeEnum.FILE);
fileItemResult.setPath(path);
fileItemResult.setUrl(getDownloadUrl(path + res.getName()));
resultList.add(fileItemResult);
}
return resultList;
}
@Override
public String getDownloadUrl(String path) {
return StringUtils.concatPath(url, path);
}
@Override
public StorageTypeEnum getStorageTypeEnum() {
return StorageTypeEnum.WebDAV;
}
@Override
public List<StorageConfig> storageStrategyConfigList() {
return new ArrayList<StorageConfig>() {{
add(new StorageConfig("webdavUsername", "用户名"));
add(new StorageConfig("webdavPassword", "密码"));
add(new StorageConfig("webdavUrl", "WebDav 链接"));
}};
}
@Override
public FileItemDTO getFileItem(String path) {
List<FileItemDTO> list;
try {
int end = path.lastIndexOf("/");
list = fileList(path.substring(0, end + 1));
} catch (Exception e) {
throw new NotExistFileException();
}
for (FileItemDTO fileItemDTO : list) {
String fullPath = StringUtils.concatUrl(fileItemDTO.getPath(), fileItemDTO.getName());
if (Objects.equals(fullPath, path)) {
return fileItemDTO;
}
}
throw new NotExistFileException();
}
}

View File

@@ -1,10 +1,16 @@
package im.zhaojun.zfile.util;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.mpatric.mp3agic.ID3v1;
import com.mpatric.mp3agic.ID3v2;
import com.mpatric.mp3agic.InvalidDataException;
@@ -29,27 +35,36 @@ public class AudioUtil {
private static final Logger log = LoggerFactory.getLogger(AudioUtil.class);
public static AudioInfoDTO getAudioInfo(String url) throws Exception {
String query = new URL(URLUtil.decode(url)).getQuery();
AudioInfoDTO audioInfoDTO;
try {
String query = new URL(URLUtil.decode(url)).getQuery();
if (query != null) {
url = url.replace(query, URLUtil.encode(query));
if (query != null) {
url = url.replace(query, URLUtil.encode(query));
}
// 如果音乐文件大小超出 5M, 则不解析此音乐
if (im.zhaojun.zfile.util.HttpUtil.getRemoteFileSize(url)
> (1024 * 1024 * ZFileConstant.AUDIO_MAX_FILE_SIZE_MB)) {
return AudioInfoDTO.buildDefaultAudioInfoDTO();
}
String fullFilePath = StringUtils.removeDuplicateSeparator(ZFileConstant.TMP_FILE_PATH + ZFileConstant.PATH_SEPARATOR + UUID.fastUUID());
File file = new File(fullFilePath);
FileUtil.mkParentDirs(file);
final HttpResponse response = HttpRequest.get(url).setFollowRedirects(true).timeout(-1).executeAsync();
response.writeBody(file);
audioInfoDTO = parseAudioInfo(file);
audioInfoDTO.setSrc(url);
file.deleteOnExit();
return audioInfoDTO;
} catch (Exception e) {
log.error("获取音频文件信息失败.", e);
}
// 如果音乐文件大小超出 5M, 则不解析此音乐
if (im.zhaojun.zfile.util.HttpUtil.getRemoteFileSize(url)
> (1024 * 1024 * ZFileConstant.AUDIO_MAX_FILE_SIZE_MB)) {
return AudioInfoDTO.buildDefaultAudioInfoDTO();
}
String fullFilePath = StringUtils.removeDuplicateSeparator(ZFileConstant.TMP_FILE_PATH + ZFileConstant.PATH_SEPARATOR + UUID.fastUUID());
File file = new File(fullFilePath);
FileUtil.mkParentDirs(file);
HttpUtil.downloadFile(url, file);
AudioInfoDTO audioInfoDTO = parseAudioInfo(file);
audioInfoDTO.setSrc(url);
file.deleteOnExit();
return audioInfoDTO;
return AudioInfoDTO.buildDefaultAudioInfoDTO();
}
private static AudioInfoDTO parseAudioInfo(File file) throws IOException, UnsupportedTagException {
@@ -86,4 +101,4 @@ public class AudioUtil {
return audioInfoDTO;
}
}
}

View File

@@ -137,16 +137,18 @@ public class FileUtil {
long contentLength = endByte - startByte + 1;
//文件类型
String contentType = request.getServletContext().getMimeType(fileName);
if (Objects.equals(type, LocalFileResponseTypeConstant.DOWNLOAD) || StrUtil.isEmpty(contentType)) {
contentType = "attachment";
if (StrUtil.isEmpty(contentType)) {
contentType = "application/octet-stream";
}
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
response.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
// 这里文件名换你想要的inline 表示浏览器可以直接使用
// 参考资料https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentType + ";filename=" + URLUtil.encode(fileName));
if (Objects.equals(type, LocalFileResponseTypeConstant.DOWNLOAD) || StrUtil.isEmpty(contentType)) {
String contentDisposition = "attachment;filename=" + URLUtil.encode(fileName);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition);
}
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
// [要下载的开始位置]-[结束位置]/[文件总大小]
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + startByte + rangeSeparator + endByte + "/" + file.length());
@@ -193,4 +195,4 @@ public class FileUtil {
}
}
}
}
}

View File

@@ -0,0 +1,61 @@
package im.zhaojun.zfile.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 正则匹配工具类
*
* @author me
* @date 2022/4/9
*/
public class RegexMatchUtils {
/**
* 正则匹配分组序号值
*
* @param regex 正则表达式
* @param str 待匹配字符串
* @param index 分组序号,从1开始
* @return {@link String} 不存在/匹配失败返回null
*/
public static String matchByIndex(String regex, String str, Integer index) {
Matcher matcher = match(regex, str);
if (matcher == null) {
return null;
}
return getIndexResult(matcher, index);
}
/**
* 匹配字符串
*
* @param regex 正则表达式
* @param str 待匹配字符串
* @return {@link Matcher}
*/
public static Matcher match(String regex, String str) {
if (str == null || "".equals(str)) {
return null;
}
Matcher matcher = Pattern.compile(regex).matcher(str);
if (!matcher.lookingAt()) {
return null;
}
return matcher;
}
/**
* 获取指定分组序号的匹配结果
*
* @param matcher {@link Matcher}
* @param index 分组序号,从1开始
* @return {@link String} 不存在/匹配失败返回null
*/
public static String getIndexResult(Matcher matcher, Integer index) {
if (matcher == null || index == null || index < 0 || index > matcher.groupCount()) {
return null;
}
return matcher.group(index);
}
}

View File

@@ -97,6 +97,11 @@
"type": "java.lang.String",
"defaultValue": "directlink",
"description": "直链前缀名称, 默认为 directlink"
},
{
"name": "zfile.webdav",
"type": "java.lang.Boolean",
"description": "是否开启 webdav 文件管理."
}
]
}

View File

@@ -1,5 +1,6 @@
zfile:
debug: false
webdav: false
directLinkPrefix: directlink
log:
path: ${user.home}/.zfile/logs
@@ -35,7 +36,8 @@ server:
servlet:
context-path: ''
tomcat:
max-threads: 20
threads:
max: 20
compression:
enabled: true
@@ -47,13 +49,6 @@ spring:
path: /h2-console
enabled: ${zfile.debug}
datasource:
# 初始化数据导入
data: classpath*:db/data.sql
sql-script-encoding: utf-8
initialization-mode: always
continue-on-error: true
# h2 内存数据库 配置
driver-class-name: org.h2.Driver
url: jdbc:h2:${zfile.db.path}
@@ -75,5 +70,15 @@ spring:
hibernate:
format_sql: false
show-sql: false
generate-ddl: true
defer-datasource-initialization: true
profiles:
active: prod
active: prod
# 初始化数据导入
sql:
init:
continue-on-error: true
mode: always
data-locations: classpath*:db/data.sql
encoding: utf-8

View File

@@ -13,4 +13,5 @@ INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`, `value`) VALUES (17, 'showAnnoun
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`, `value`) VALUES (18, 'layout', '页面布局', 'full');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`, `value`) VALUES (19, 'showLinkBtn', '是否显示生成直链按钮', 'true');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`, `value`) VALUES (20, 'showShortLink', '是否显示短链', 'true');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`, `value`) VALUES (21, 'showPathLink', '是否显示路径直链', 'true');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`, `value`) VALUES (21, 'showPathLink', '是否显示路径直链', 'true');
alter table short_link change "key" key varchar(255) null;

View File

@@ -76,6 +76,9 @@
true 表示当前logger的appender-ref和rootLogger的appender-ref都有效
-->
<!-- milton webdav -->
<logger name="io.milton" additivity="false" level="info"/>
<!-- jetCache logger -->
<logger name="com.alicp" additivity="false" level="debug"/>

View File

@@ -1 +1 @@
.zfile-admin-index[data-v-77a86734]{height:100%;overflow-y:hidden}.zfile-admin-top[data-v-77a86734]{background-color:#001529}.zfile-admin-top-logo[data-v-77a86734]{cursor:pointer;height:100%;line-height:61px;color:#fff;padding-right:20px}.zfile-admin-top-logo[data-v-77a86734]:hover{color:#1890ff}.zfile-admin-top-content[data-v-77a86734]{display:-webkit-box;display:-ms-flexbox;display:flex;margin:auto;max-width:1200px}.zfile-admin-content[data-v-77a86734]{background-color:#f0f2f5;height:100%;overflow-y:auto}.zfile-admin-content-view[data-v-77a86734]{margin:auto;max-width:1200px;margin-top:50px;margin-bottom:100px}.el-menu--horizontal>.el-menu-item.is-active[data-v-77a86734]{color:#1890ff!important}.el-menu--horizontal>.el-menu-item[data-v-77a86734],.el-menu.el-menu--horizontal[data-v-77a86734]{border:none}.zfile-admin-index-version-info[data-v-77a86734],.zfile-admin-index-version-info[data-v-77a86734] .el-link--inner{font-size:13px}.zfile-login[data-v-834c542c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:100%;height:100%}.zfile-login-title[data-v-834c542c]{text-align:center;vertical-align:text-bottom;font-size:30px;font-weight:600;color:red;background-image:linear-gradient(-20deg,#6e45e2,#88d3ce);-webkit-text-fill-color:transparent;-webkit-background-clip:text;line-height:80px}.zfile-login-title small[data-v-834c542c]{font-size:20px}.zfile-login-form[data-v-834c542c]{width:350px;padding:30px 35px 15px;background:#fff;border:1px solid #eaeaea;-webkit-box-shadow:0 0 25px #cac6c6;box-shadow:0 0 25px #cac6c6}.el-row[data-v-506ed09e]{overflow-y:auto}#siteForm[data-v-506ed09e]{margin-top:20px}#siteForm[data-v-506ed09e] .el-select{width:70%}.markdown-body[data-v-12b3dceb]{padding:unset}.el-drive-form-col[data-v-3a30d939]{padding-left:0!important}.zfile-site-id-input-site-type-select[data-v-3a30d939]{width:100px}.zfile-cache-statistics[data-v-4d6f6841]{margin-bottom:20px}.zfile-cache-table[data-v-4d6f6841]{width:100%;overflow-y:auto}.zfile-filter-delete-btn[data-v-20b2942a]{margin-left:10px;margin-top:5px}.zfile-filter-save-btn[data-v-20b2942a]{text-align:right;margin-top:15px}.el-row[data-v-32b498fc]{padding:20px}.el-form-item[data-v-32b498fc]{margin-right:50px}.card-title[data-v-32b498fc]{color:rgba(0,0,0,.45);font-size:14px}.card-content[data-v-32b498fc]{color:rgba(0,0,0,.85);font-size:25px;line-height:30px}.card-title-button[data-v-32b498fc]{float:right;padding:3px 0}.table-search-input[data-v-32b498fc]{width:300px;float:right}#filterForm .el-row[data-v-32b498fc]{padding:0}#cacheDialog[data-v-32b498fc] .el-dialog__body{padding:20px}.table-edit-icon[data-v-32b498fc]{margin-left:5px;color:#409eff;cursor:pointer}.zfile-admin-short-form[data-v-f6484e10] .el-form-item:first-child{margin-left:10px}.zfile-admin-short-form[data-v-f6484e10] .el-form-item:not(:first-child){margin-left:20px}.el-pagination[data-v-f6484e10]{margin-top:15px}.table-edit-icon[data-v-f6484e10]{margin-left:10px;cursor:pointer}.input-with-select .el-select[data-v-3ac09bd8]{width:100px}
.zfile-admin-index[data-v-77a86734]{height:100%;overflow-y:hidden}.zfile-admin-top[data-v-77a86734]{background-color:#001529}.zfile-admin-top-logo[data-v-77a86734]{cursor:pointer;height:100%;line-height:61px;color:#fff;padding-right:20px}.zfile-admin-top-logo[data-v-77a86734]:hover{color:#1890ff}.zfile-admin-top-content[data-v-77a86734]{display:-webkit-box;display:-ms-flexbox;display:flex;margin:auto;max-width:1200px}.zfile-admin-content[data-v-77a86734]{background-color:#f0f2f5;height:100%;overflow-y:auto}.zfile-admin-content-view[data-v-77a86734]{margin:auto;max-width:1200px;margin-top:50px;margin-bottom:100px}.el-menu--horizontal>.el-menu-item.is-active[data-v-77a86734]{color:#1890ff!important}.el-menu--horizontal>.el-menu-item[data-v-77a86734],.el-menu.el-menu--horizontal[data-v-77a86734]{border:none}.zfile-admin-index-version-info[data-v-77a86734],.zfile-admin-index-version-info[data-v-77a86734] .el-link--inner{font-size:13px}.zfile-login[data-v-732157c2]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:100%;height:100%}.zfile-login-title[data-v-732157c2]{text-align:center;vertical-align:text-bottom;font-size:30px;font-weight:600;color:red;background-image:linear-gradient(-20deg,#6e45e2,#88d3ce);-webkit-text-fill-color:transparent;-webkit-background-clip:text;line-height:80px}.zfile-login-title small[data-v-732157c2]{font-size:20px}.zfile-login-form[data-v-732157c2]{width:350px;padding:30px 35px 15px;background:#fff;border:1px solid #eaeaea;-webkit-box-shadow:0 0 25px #cac6c6;box-shadow:0 0 25px #cac6c6}.el-row[data-v-506ed09e]{overflow-y:auto}#siteForm[data-v-506ed09e]{margin-top:20px}#siteForm[data-v-506ed09e] .el-select{width:70%}.markdown-body[data-v-12b3dceb]{padding:unset}.el-drive-form-col[data-v-3a30d939]{padding-left:0!important}.zfile-site-id-input-site-type-select[data-v-3a30d939]{width:100px}.zfile-cache-statistics[data-v-4d6f6841]{margin-bottom:20px}.zfile-cache-table[data-v-4d6f6841]{width:100%;overflow-y:auto}.zfile-filter-delete-btn[data-v-20b2942a]{margin-left:10px;margin-top:5px}.zfile-filter-save-btn[data-v-20b2942a]{text-align:right;margin-top:15px}.el-row[data-v-32b498fc]{padding:20px}.el-form-item[data-v-32b498fc]{margin-right:50px}.card-title[data-v-32b498fc]{color:rgba(0,0,0,.45);font-size:14px}.card-content[data-v-32b498fc]{color:rgba(0,0,0,.85);font-size:25px;line-height:30px}.card-title-button[data-v-32b498fc]{float:right;padding:3px 0}.table-search-input[data-v-32b498fc]{width:300px;float:right}#filterForm .el-row[data-v-32b498fc]{padding:0}#cacheDialog[data-v-32b498fc] .el-dialog__body{padding:20px}.table-edit-icon[data-v-32b498fc]{margin-left:5px;color:#409eff;cursor:pointer}.zfile-admin-short-form[data-v-f6484e10] .el-form-item:first-child{margin-left:10px}.zfile-admin-short-form[data-v-f6484e10] .el-form-item:not(:first-child){margin-left:20px}.el-pagination[data-v-f6484e10]{margin-top:15px}.table-edit-icon[data-v-f6484e10]{margin-left:10px;cursor:pointer}.input-with-select .el-select[data-v-3ac09bd8]{width:100px}

View File

@@ -1 +1 @@
.zfile-install[data-v-89486d62]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:100%;height:100%}.zfile-install-form[data-v-89486d62]{width:450px;padding:30px 35px 15px;background:#fff;border:1px solid #eaeaea;-webkit-box-shadow:0 0 15px #cac6c6;box-shadow:0 0 15px #cac6c6}.zfile-install-title[data-v-89486d62]{text-align:center;vertical-align:text-bottom;font-size:30px;font-weight:600;color:red;background-image:linear-gradient(-20deg,#6e45e2,#88d3ce);-webkit-text-fill-color:transparent;-webkit-background-clip:text;line-height:80px}.zfile-install-enter[data-v-89486d62]{text-align:right;margin-bottom:0}.zfile-install-title small[data-v-89486d62]{font-size:20px}
.zfile-install[data-v-c4d9c6f8]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:100%;height:100%}.zfile-install-form[data-v-c4d9c6f8]{width:450px;padding:30px 35px 15px;background:#fff;border:1px solid #eaeaea;-webkit-box-shadow:0 0 15px #cac6c6;box-shadow:0 0 15px #cac6c6}.zfile-install-title[data-v-c4d9c6f8]{text-align:center;vertical-align:text-bottom;font-size:30px;font-weight:600;color:red;background-image:linear-gradient(-20deg,#6e45e2,#88d3ce);-webkit-text-fill-color:transparent;-webkit-background-clip:text;line-height:80px}.zfile-install-enter[data-v-c4d9c6f8]{text-align:right;margin-bottom:0}.zfile-install-title small[data-v-c4d9c6f8]{font-size:20px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,187 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>404 ERROR</title>
<style type="text/css">
body, div, h3, h4, li, ol {
margin: 0;
padding: 0
}
body {
font: 14px/1.5 'Microsoft YaHei', '微软雅黑', Helvetica, Sans-serif;
min-width: 1200px;
background: #f0f1f3;
}
:focus {
outline: 0
}
h3, h4, strong {
font-weight: 700
}
a {
color: #428bca;
text-decoration: none
}
a:hover {
text-decoration: underline
}
.error-page {
background: #f0f1f3;
padding: 80px 0 180px
}
.error-page-container {
position: relative;
z-index: 1
}
.error-page-main {
position: relative;
background: #f9f9f9;
margin: 0 auto;
width: 617px;
-ms-box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 50px 50px 70px
}
.error-page-main:before {
content: '';
display: block;
background: url(img/errorPageBorder.png?1427783409637);
height: 7px;
position: absolute;
top: -7px;
width: 100%;
left: 0
}
.error-page-main h3 {
font-size: 24px;
font-weight: 400;
border-bottom: 1px solid #d0d0d0
}
.error-page-main h3 strong {
font-size: 54px;
font-weight: 400;
margin-right: 20px
}
.error-page-main h4 {
font-size: 20px;
font-weight: 400;
color: #333
}
.error-page-actions {
font-size: 0;
z-index: 100
}
.error-page-actions div {
font-size: 14px;
display: inline-block;
padding: 30px 0 0 10px;
width: 50%;
-ms-box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
color: #838383
}
.error-page-actions ol {
list-style: decimal;
padding-left: 20px
}
.error-page-actions li {
line-height: 2.5em
}
.error-page-actions:before {
content: '';
display: block;
position: absolute;
z-index: -1;
bottom: 17px;
left: 50px;
width: 200px;
height: 10px;
-moz-box-shadow: 4px 5px 31px 11px #999;
-webkit-box-shadow: 4px 5px 31px 11px #999;
box-shadow: 4px 5px 31px 11px #999;
-moz-transform: rotate(-4deg);
-webkit-transform: rotate(-4deg);
-ms-transform: rotate(-4deg);
-o-transform: rotate(-4deg);
transform: rotate(-4deg)
}
.error-page-actions:after {
content: '';
display: block;
position: absolute;
z-index: -1;
bottom: 17px;
right: 50px;
width: 200px;
height: 10px;
-moz-box-shadow: 4px 5px 31px 11px #999;
-webkit-box-shadow: 4px 5px 31px 11px #999;
box-shadow: 4px 5px 31px 11px #999;
-moz-transform: rotate(4deg);
-webkit-transform: rotate(4deg);
-ms-transform: rotate(4deg);
-o-transform: rotate(4deg);
transform: rotate(4deg)
}
</style>
</head>
<body>
<div class="error-page">
<div class="error-page-container">
<div class="error-page-main">
<h3>
<strong>404</strong>很抱歉,您要访问的文件/页面不存在!
</h3>
<div class="error-page-actions">
<div>
<h4>可能原因:</h4>
<ol>
<li>网络信号差不稳定</li>
<li>找不到请求的页面</li>
<li>输入的网址不正确</li>
</ol>
</div>
<div>
<h4>可以尝试:</h4>
<ol>
<li><a href="#" onclick="backHomePage()">返回首页</a></li>
<li><a href="https://github.com/zhaojun1998/zfile/issues" target="_blank">留言反馈</a></li>
<li><a href="#">联系站长</a></li>
</ol>
</div>
</div>
</div>
</div>
</div>
<script>
function backHomePage() {
window.location.href = window.location.origin;
}
</script>
</body>
</html>