Compare commits

..

28 Commits
3.0 ... 3.1

Author SHA1 Message Date
赵俊
73b42cf654 🔖 发布 3.1 版本 2021-05-30 17:43:37 +08:00
赵俊
1fac59c4cd 🎨 更新静态页面 2021-05-30 17:41:46 +08:00
赵俊
6ed6b4a019 驱动器添加时,默认 ID 修改为 1 2021-05-30 17:40:48 +08:00
赵俊
7bd02437f0 数据库初始化参数增加默认值 2021-05-30 17:40:14 +08:00
赵俊
57aeb5771c 驱动器列表中返回是否已初始化信息 2021-05-30 17:39:40 +08:00
赵俊
695c03a530 Merge remote-tracking branch 'origin/master' 2021-05-30 16:24:58 +08:00
赵俊
6ebc403572 🔧 添加 ZFile Banner 文件 2021-05-30 16:24:28 +08:00
赵俊
7ff6fe43b5 新增自定义直链名称功能功能 2021-05-30 16:23:41 +08:00
赵俊
b34f141181 🔥 移除无用代码 2021-05-30 16:21:11 +08:00
赵俊
87dd7b58d1 🔥 移除无用代码 2021-05-30 16:19:11 +08:00
赵俊
2a949db5d2 m3u8 响应头兼容部分浏览器直接解析 m3u8 文件 2021-05-30 15:53:55 +08:00
赵俊
b889e91fb5 📝 更新文档,更新赞助信息位置 2021-05-14 21:15:54 +08:00
赵俊
b5c757f9f0 📝 更新文档,更新赞助信息位置 2021-04-21 21:04:02 +08:00
赵俊
a465f48b94 📝 更新文档,增加项目 LOGO,修改项目描述信息。 2021-04-21 20:23:54 +08:00
赵俊
797a0a0e4c 新增直链和短链是否显示功能 2021-04-21 20:21:47 +08:00
赵俊
e7ff159b6d 🐛 修复某些下载地址带密钥的存储策略,m3u8 视频无法正常播放的功能 2021-04-11 19:15:50 +08:00
赵俊
a9fbf54bb2 🔊 完善日志输出 2021-04-11 16:02:49 +08:00
赵俊
81f9e262f5 增加获取站点域名方法 2021-04-11 16:00:01 +08:00
赵俊
23bb3960ab 已关闭的驱动器不允许下载 2021-04-11 15:56:08 +08:00
赵俊
c4a17985a4 优化下载地址获取逻辑,直接列表时不直接调用 API 获取下载地址,访问单文件时再调用。 2021-04-11 15:49:33 +08:00
赵俊
75ddcd47f4 :zip: 优化存储器保存逻辑,防止新增加的字段无法正常保存的 BUG。 2021-04-10 20:21:19 +08:00
赵俊
2dd03ae490 🐛 修复 FTP 模式在 Linux 环境下无法正常读取的 BUG 2021-04-10 20:20:03 +08:00
赵俊
5b383c8741 🐛 日志文件无法正常下载功能修复 2021-04-10 20:19:26 +08:00
赵俊
73198d7852 优化本地文件下载功能, 支持断点续传和多线程下载 2021-04-10 18:06:10 +08:00
赵俊
fb0d9721aa OneDrive 和 SharePoint 反代功能 2021-04-10 15:38:16 +08:00
赵俊
b24c663fd6 更新 star 趋势链接 2021-03-27 12:01:37 +08:00
zhaojun1998
6e62cfc84d 📝 更新文档,修改赞助二维码,增加服务器赞助商链接 2021-03-27 11:52:59 +08:00
zhaojun1998
eee22e9dc9 📝 更新文档 2021-03-26 18:24:02 +08:00
40 changed files with 486 additions and 153 deletions

106
README.md
View File

@@ -1,32 +1,47 @@
# Z-File
<p align = "center">
<img alt="ZFile" src="https://cdn.jun6.net/2021/04/21/69a89344e2a84.png" height="150px">
<br><br>
基于 Java 的在线网盘程序,支持对接 S3、OneDrive、SharePoint、又拍云、本地存储、FTP 等存储源,支持在线浏览图片、播放音视频,文本文件等文件类型。
<br><br>
<img src="https://img.shields.io/badge/license-MIT-blue.svg?longCache=true&style=flat-square">
<img src="https://api.codacy.com/project/badge/Grade/70b793267f7941d58cbd93f50c9a8e0a">
<img src="https://img.shields.io/github/last-commit/zhaojun1998/zfile.svg?style=flat-square">
<img src="https://img.shields.io/github/downloads/zhaojun1998/zfile/total?style=flat-square">
<img src="https://img.shields.io/github/v/release/zhaojun1998/zfile?style=flat-square">
<img src="https://img.shields.io/github/commit-activity/y/zhaojun1998/zfile?style=flat-square">
<br>
<img src="https://img.shields.io/github/issues/zhaojun1998/zfile?style=flat-square">
<img src="https://img.shields.io/github/issues-closed-raw/zhaojun1998/zfile?style=flat-square">
<img src="https://img.shields.io/github/forks/zhaojun1998/zfile?style=flat-square">
<img src="https://img.shields.io/github/stars/zhaojun1998/zfile?style=flat-square">
<img src="https://img.shields.io/github/watchers/zhaojun1998/zfile?style=flat-square">
</p>
![https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square](https://img.shields.io/badge/license-MIT-blue.svg?longCache=true&style=flat-square)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/70b793267f7941d58cbd93f50c9a8e0a)](https://www.codacy.com/manual/zhaojun1998/zfile?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=zhaojun1998/zfile&amp;utm_campaign=Badge_Grade)
![https://img.shields.io/badge/springboot-2.0.6-orange.svg?style=flat-square](https://img.shields.io/badge/springboot-2.0.6-yellow.svg?longCache=true&style=popout-square)
![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/zhaojun1998/zfile.svg?style=flat-square)
此项目是一个在线文件目录的程序, 支持各种对象存储和本地存储, 使用定位是个人放常用工具下载, 或做公共的文件库. 不会向多账户方向开发.
前端基于 [h5ai](https://larsjung.de/h5ai/) 的原有功能使用 Vue 重新开发了一遍. 后端采用 SpringBoot, 数据库采用内嵌数据库.
## 相关地址
预览地址: [https://zfile.jun6.net](https://zfile.jun6.net)
文档地址: [http://docs.zhaojun.im/zfile](http://docs.zhaojun.im/zfile)
项目源码: [https://github.com/zhaojun1998/zfile](https://github.com/zhaojun1998/zfile)
前端源码: [https://github.com/zhaojun1998/zfile-vue](https://github.com/zhaojun1998/zfile-vue)
## 系统特色
* 内存缓存 (免安装)
* 内存数据库 (免安装)
* 个性化配置
* 自定义目录的 readme 说明文件
* 自定义 JS, CSS
* 文件夹密码
* 目录 README 说明
* 文件直链(短链,永久直链,二维码)
* 支持在线浏览文本文件, 视频, 图片, 音乐. (支持 FLV 和 HLS)
* 文件/目录二维码
* 图片模式
* Docker 支持
* 隐藏指定文件夹(通配符支持)
* 自定义 JS, CSS
* 自定义目录 README 说明文件和密码文件名称
* 同时挂载多个存储策略
* 缓存动态开启, ~~缓存自动刷新 (v2.2 及以前版本支持)~~
* ~~全局搜索 (v2.2 及以前版本支持)~~
* 同时挂载多个存储策略
* 支持 阿里云 OSS, FTP, 华为云 OBS, 本地存储, MINIO, OneDrive 国际/家庭/个人版, OneDrive 世纪互联版, 七牛云 KODO, 腾讯云 COS, 又拍云 USS.
* 支持 S3 协议, 阿里云 OSS, FTP, 华为云 OBS, 本地存储, MINIO, OneDrive 国际/家庭/个人版/世纪互联版/SharePoint, , 七牛云 KODO, 腾讯云 COS, 又拍云 USS.
## 快速开始
@@ -96,28 +111,14 @@ chmod +x zfile/bin/*.sh
## 预览
![前台首页](https://cdn.jun6.net/2020/04/19/d590d2bde13bb.png)
![后台设置-驱动器设置](https://cdn.jun6.net/2020/04/19/d58fc2debcce8.png)
![后台设置-驱动器设置](https://cdn.jun6.net/2020/04/19/0f321e47fc18c.png)
![后台设置-显示设置](https://cdn.jun6.net/2020/04/19/6d7c300b89671.png)
## 常见问题
### 默认路径
默认 H2 数据库文件地址: `~/.zfile/db/`, `~` 表示用户目录
windows 为 `C:/Users/用户名/`
linux 为 `/home/用户名/`, root 用户为 `/root/`
> 2.3 及以后版本路径为 `~/.zfile-new/db/`
### 文档文件和加密文件
- 目录文档显示文件名为 `readme.md`
- 目录需要密码访问, 添加文件 `password.txt` (无法拦截此文件被下载, 但可以改名文件)
![前台首页](https://cdn.jun6.net/2021/03/23/c1f4631ee2de4.png)
![图片预览](https://cdn.jun6.net/2021/03/23/713741d43b939.png)
![视频预览](https://cdn.jun6.net/2021/03/23/9c724383bb506.png)
![文本预览](https://cdn.jun6.net/2021/03/23/b00efdfb4892e.png)
![音频预览](https://cdn.jun6.net/2021/03/23/d15b14378d3f0.png)
![后台设置-驱动器列表](https://cdn.jun6.net/2021/03/23/b4f76f20ea73a.png)
![后台设置-新增驱动器](https://cdn.jun6.net/2021/03/23/e70e04f8cc5b6.png)
![后台设置-站点设置](https://cdn.jun6.net/2021/03/23/fd946991bb6b9.png)
## 开发计划
@@ -127,16 +128,35 @@ linux 为 `/home/用户名/`, root 用户为 `/root/`
- [x] 后台优化 - 设置按照其功能进行分离
- [x] 体验优化 - 支持前后端分离部署
- [x] 体验优化 - 文本预览更换 vscode 同款编辑器 monaco editor
- [x] 新功能 - Docker 支持
- [x] 架构调整 - 支持多存储策略
- [x] 体验优化 - 忽略文件列表 (正则表达式)
- [ ] 新功能 - 后台支持上传、编辑、删除等操作
- [x] 新功能 - Docker 支持
- [x] 新功能 - 图片模式
- [x] 新功能 - 直链/短链管理
- [ ] ~~新功能 - 后台支持上传、编辑、删除等操作 (不再支持)~~
- [ ] 体验优化 - 自定义支持预览的文件后缀 (正则表达式)
- [ ] 体验优化 - 一键安装脚本
- [ ] 新功能 - 分享功能,支持分享密码,文件夹分享
- [ ] 新功能 - 直链支持 Referer 防盗链
- [ ] 体验优化 - 视频列表支持
- [ ] 新功能 - 单独页面打开文件预览
- [ ] 新功能 - 在线查看日志功能
- [ ] 部署优化 - Docker Compose 支持
## 支持作者
如果本项目对你有帮助,请作者喝杯咖啡吧。
<img src="http://cdn.jun6.net/alipay.png" width="200" height="312">
<img src="http://cdn.jun6.net/wechat.png" width="222" height="300">
<img src="https://cdn.jun6.net/2021/03/27/152704e91f13d.png" width="400" alt="赞助我">
## Stargazers over time
[![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>

View File

@@ -12,7 +12,7 @@
<groupId>im.zhaojun</groupId>
<artifactId>zfile</artifactId>
<version>3.0</version>
<version>3.1</version>
<name>zfile</name>
<packaging>war</packaging>
<description>一个在线的文件浏览系统</description>

View File

@@ -9,7 +9,6 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.Date;
@@ -26,14 +25,14 @@ public class LogController {
* 系统日志下载
*/
@GetMapping("/log")
public ResponseEntity<Object> downloadLog(HttpServletResponse response) {
public ResponseEntity<Object> downloadLog() {
if (log.isDebugEnabled()) {
log.debug("下载诊断日志");
}
String userHome = System.getProperty("user.home");
File fileZip = ZipUtil.zip(userHome + "/.zfile/logs");
String currentDate = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss");
return FileUtil.export(fileZip, "ZFile 诊断日志 - " + currentDate + ".zip");
return FileUtil.exportSingleThread(fileZip, "ZFile 诊断日志 - " + currentDate + ".zip");
}
}

View File

@@ -1,13 +1,17 @@
package im.zhaojun.zfile.controller.home;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import im.zhaojun.zfile.context.DriveContext;
import im.zhaojun.zfile.exception.NotAllowedDownloadException;
import im.zhaojun.zfile.exception.NotEnabledDriveException;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.entity.DriveConfig;
import im.zhaojun.zfile.model.enums.FileTypeEnum;
import im.zhaojun.zfile.service.DriveConfigService;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import im.zhaojun.zfile.util.HttpUtil;
import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.GetMapping;
@@ -16,6 +20,9 @@ import org.springframework.web.servlet.HandlerMapping;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Objects;
/**
@@ -28,6 +35,9 @@ public class DirectLinkController {
@Resource
private DriveContext driveContext;
@Resource
private DriveConfigService driveConfigService;
/**
* 获取指定驱动器, 某个文件的直链, 然后重定向过去.
* @param driveId
@@ -35,9 +45,16 @@ public class DirectLinkController {
*
* @return 重定向至文件直链
*/
@GetMapping("/directlink/{driveId}/**")
@GetMapping("/${zfile.directLinkPrefix}/{driveId}/**")
public String directlink(@PathVariable("driveId") Integer driveId,
final HttpServletRequest request) {
final HttpServletRequest request,
final HttpServletResponse response) throws IOException {
DriveConfig driveConfig = driveConfigService.findById(driveId);
Boolean enable = driveConfig.getEnable();
if (!enable) {
throw new NotEnabledDriveException();
}
String path = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String bestMatchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
@@ -48,15 +65,21 @@ public class DirectLinkController {
filePath = "/" + filePath;
}
if (Objects.equals(FileUtil.getName(filePath), ZFileConstant.PASSWORD_FILE_NAME)) {
throw new NotAllowedDownloadException("不允许下载此文件");
}
AbstractBaseFileService fileService = driveContext.get(driveId);
FileItemDTO fileItem = fileService.getFileItem(filePath);
String url = fileItem.getUrl();
if (StrUtil.equalsIgnoreCase(FileUtil.extName(fileItem.getName()), "m3u8")) {
String textContent = HttpUtil.getTextContent(url);
response.setContentType("application/vnd.apple.mpegurl;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(textContent);
out.flush();
out.close();
return null;
}
int queryIndex = url.indexOf('?');
if (queryIndex != -1) {

View File

@@ -3,9 +3,10 @@ package im.zhaojun.zfile.controller.home;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import im.zhaojun.zfile.context.DriveContext;
import im.zhaojun.zfile.exception.NotExistFileException;
import im.zhaojun.zfile.exception.NotEnabledDriveException;
import im.zhaojun.zfile.exception.PasswordVerifyException;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.model.dto.DriveListDTO;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.dto.FileListDTO;
import im.zhaojun.zfile.model.dto.SystemFrontConfigDTO;
@@ -67,7 +68,10 @@ public class FileController {
*/
@GetMapping("/drive/list")
public ResultBean drives() {
return ResultBean.success(driveConfigService.listOnlyEnable());
List<DriveConfig> driveList = driveConfigService.listOnlyEnable();
boolean isInstall = systemConfigService.getIsInstall();
DriveListDTO driveListDTO = new DriveListDTO(driveList, isInstall);
return ResultBean.success(driveListDTO);
}
/**
@@ -113,8 +117,14 @@ public class FileController {
// 开始获取参数信息
SystemFrontConfigDTO systemConfig = systemConfigService.getSystemFrontConfig(driveId);
DriveConfig driveConfig = driveConfigService.findById(driveId);
Boolean enable = driveConfig.getEnable();
if (!enable) {
throw new NotEnabledDriveException();
}
systemConfig.setDebugMode(debug);
systemConfig.setDefaultSwitchToImgMode(driveConfig.getDefaultSwitchToImgMode());
systemConfig.setDirectLinkPrefix(ZFileConstant.DIRECT_LINK_PREFIX);
// 如果不是 FTP 模式,则尝试获取当前文件夹中的 README 文件,有则读取,没有则停止
if (!Objects.equals(driveConfig.getType(), StorageTypeEnum.FTP)) {
@@ -131,24 +141,6 @@ public class FileController {
}
/**
* 获取指定路径下的文件信息内容
*
* @param driveId
* 驱动器 ID
*
* @param path
* 文件全路径
*
* @return 该文件的名称, 路径, 大小, 下载地址等信息.
*/
@GetMapping("/directlink/{driveId}")
public ResultBean directlink(@PathVariable(name = "driveId") Integer driveId, String path) {
AbstractBaseFileService fileService = driveContext.get(driveId);
return ResultBean.successData(fileService.getFileItem(path));
}
/**
* 校验密码
* @param fileItemList

View File

@@ -5,7 +5,6 @@ import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.service.impl.LocalServiceImpl;
import im.zhaojun.zfile.util.FileUtil;
import im.zhaojun.zfile.util.StringUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.GetMapping;
@@ -15,6 +14,7 @@ import org.springframework.web.servlet.HandlerMapping;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
/**
@@ -32,18 +32,17 @@ public class LocalController {
*
* @param driveId
* 驱动器 ID
*
* @return 文件
*/
@GetMapping("/file/{driveId}/**")
@ResponseBody
public ResponseEntity<Object> downAttachment(@PathVariable("driveId") Integer driveId, final HttpServletRequest request) {
public void downAttachment(@PathVariable("driveId") Integer driveId, final HttpServletRequest request, final HttpServletResponse response) {
String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String bestMatchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
AntPathMatcher apm = new AntPathMatcher();
String filePath = apm.extractPathWithinPattern(bestMatchPattern, path);
LocalServiceImpl localService = (LocalServiceImpl) driveContext.get(driveId);
return FileUtil.export(new File(StringUtils.removeDuplicateSeparator(localService.getFilePath() + ZFileConstant.PATH_SEPARATOR + filePath)));
File file = new File(StringUtils.removeDuplicateSeparator(localService.getFilePath() + ZFileConstant.PATH_SEPARATOR + filePath));
FileUtil.export(request, response, file);
}
}

View File

@@ -2,6 +2,7 @@ package im.zhaojun.zfile.controller.home;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.URLUtil;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.model.dto.SystemConfigDTO;
import im.zhaojun.zfile.model.entity.ShortLinkConfig;
import im.zhaojun.zfile.model.support.ResultBean;
@@ -36,7 +37,7 @@ public class ShortLinkController {
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
String domain = systemConfig.getDomain();
// 拼接直链地址.
String fullPath = StringUtils.removeDuplicateSeparator("/directlink/" + driveId + path);
String fullPath = StringUtils.concatUrl(StringUtils.DELIMITER_STR, ZFileConstant.DIRECT_LINK_PREFIX, driveId, path);
ShortLinkConfig shortLinkConfig = shortLinkConfigService.findByUrl(fullPath);
if (shortLinkConfig == null) {

View File

@@ -19,12 +19,22 @@ public class GlobleExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobleExceptionHandler.class);
/**
* 不存在的文件异常
*/
@ExceptionHandler({NotEnabledDriveException.class})
@ResponseBody
public ResultBean notEnabledDrive() {
return ResultBean.error("驱动器已关闭");
}
/**
* 不存在的文件异常
*/
@ExceptionHandler({NotExistFileException.class})
@ResponseBody
public ResultBean notExistFile(Exception ex) {
public ResultBean notExistFile() {
return ResultBean.error("文件不存在");
}
@@ -35,7 +45,7 @@ public class GlobleExceptionHandler {
@ExceptionHandler({HttpMediaTypeNotAcceptableException.class, ClientAbortException.class})
@ResponseBody
@ResponseStatus
public void clientAbortException(Exception ex) {
public void clientAbortException() {
// if (log.isDebugEnabled()) {
// log.debug("出现了断开异常:", ex);
// }

View File

@@ -0,0 +1,27 @@
package im.zhaojun.zfile.exception;
/**
* 未启用的驱动器异常
*/
public class NotEnabledDriveException extends RuntimeException {
public NotEnabledDriveException() {
}
public NotEnabledDriveException(String message) {
super(message);
}
public NotEnabledDriveException(String message, Throwable cause) {
super(message, cause);
}
public NotEnabledDriveException(Throwable cause) {
super(cause);
}
public NotEnabledDriveException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -41,4 +41,6 @@ public class StorageConfigConstant {
public static final String IS_PRIVATE = "isPrivate";
public static final String PROXY_DOMAIN = "proxyDomain";
}

View File

@@ -16,6 +16,10 @@ public class ZFileConstant {
public static final String PATH_SEPARATOR = "/";
/**
* 直链前缀名称
*/
public static String DIRECT_LINK_PREFIX = "directlink";
/**
* 系统产生的临时文件路径
@@ -68,5 +72,9 @@ public class ZFileConstant {
ZFileConstant.TEXT_MAX_FILE_SIZE_KB = maxFileSizeKb;
}
@Autowired(required = false)
public void setDirectLinkPrefix(@Value("${zfile.directLinkPrefix}") String directLinkPrefix) {
ZFileConstant.DIRECT_LINK_PREFIX = directLinkPrefix;
}
}

View File

@@ -0,0 +1,21 @@
package im.zhaojun.zfile.model.dto;
import im.zhaojun.zfile.model.entity.DriveConfig;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
/**
* @author Zhao Jun
* 2021/5/26 15:17
*/
@Data
@AllArgsConstructor
public class DriveListDTO {
private List<DriveConfig> driveList;
private Boolean isInstall;
}

View File

@@ -46,4 +46,6 @@ public class StorageStrategyConfig {
private String siteType;
private String proxyDomain;
}

View File

@@ -46,4 +46,10 @@ public class SystemConfigDTO {
private Boolean showAnnouncement;
private String layout;
private Boolean showLinkBtn;
private Boolean showShortLink;
private Boolean showPathLink;
}

View File

@@ -45,4 +45,12 @@ public class SystemFrontConfigDTO {
private Boolean defaultSwitchToImgMode;
private Boolean showLinkBtn;
private Boolean showShortLink;
private Boolean showPathLink;
private String directLinkPrefix;
}

View File

@@ -1,12 +1,12 @@
package im.zhaojun.zfile.service;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONObject;
import im.zhaojun.zfile.cache.ZFileCache;
import im.zhaojun.zfile.context.DriveContext;
import im.zhaojun.zfile.context.StorageTypeContext;
import im.zhaojun.zfile.exception.InitializeDriveException;
import im.zhaojun.zfile.model.constant.StorageConfigConstant;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.model.dto.CacheInfoDTO;
import im.zhaojun.zfile.model.dto.DriveConfigDTO;
import im.zhaojun.zfile.model.dto.StorageStrategyConfig;
@@ -18,6 +18,7 @@ import im.zhaojun.zfile.repository.FilterConfigRepository;
import im.zhaojun.zfile.repository.ShortLinkConfigRepository;
import im.zhaojun.zfile.repository.StorageConfigRepository;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import im.zhaojun.zfile.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Example;
@@ -192,22 +193,8 @@ public class DriveConfigService {
AbstractBaseFileService storageTypeService = StorageTypeContext.getStorageTypeService(storageType);
List<StorageConfig> storageConfigList;
if (updateFlag) {
storageConfigList = storageConfigRepository.findByDriveId(driveConfigDTO.getId());
// 如果从数据库获取到的数据不为空, 则校验数据是否和当前存储类型一直, 如不一直则进行矫正.
if (CollectionUtil.isNotEmpty(storageConfigList)) {
StorageConfig storageConfig = storageConfigList.get(0);
StorageTypeEnum type = storageConfig.getType();
if (!Objects.equals(type, storageType)) {
storageConfigRepository.deleteByDriveId(driveConfigDTO.getId());
storageConfigList = storageTypeService.storageStrategyConfigList();
}
}
} else {
storageConfigList = storageTypeService.storageStrategyConfigList();
}
List<StorageConfig> storageConfigList = storageTypeService.storageStrategyConfigList();
storageConfigRepository.deleteByDriveId(driveConfigDTO.getId());
for (StorageConfig storageConfig : storageConfigList) {
String key = storageConfig.getKey();
@@ -251,10 +238,10 @@ public class DriveConfigService {
public Integer selectNextId() {
Integer maxId = driverConfigRepository.selectMaxId();
if (maxId == null) {
maxId = 1;
return 1;
} else {
return maxId + 1;
}
return maxId + 1;
}
@@ -273,7 +260,12 @@ public class DriveConfigService {
driverConfigRepository.updateId(updateId, newId);
storageConfigRepository.updateDriveId(updateId, newId);
filterConfigRepository.updateDriveId(updateId, newId);
shortLinkConfigRepository.updateUrlDriveId("/directlink/" + updateId, "/directlink/" + newId);
String updateSubPath = StringUtils.concatUrl(StringUtils.DELIMITER_STR, ZFileConstant.DIRECT_LINK_PREFIX, String.valueOf(updateId));
String newSubPath = StringUtils.concatUrl(StringUtils.DELIMITER_STR, ZFileConstant.DIRECT_LINK_PREFIX, String.valueOf(newId));
shortLinkConfigRepository.updateUrlDriveId(updateSubPath, newSubPath);
driveContext.updateDriveId(updateId, newId);
}

View File

@@ -1,6 +1,7 @@
package im.zhaojun.zfile.service;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import im.zhaojun.zfile.cache.ZFileCache;
import im.zhaojun.zfile.exception.InvalidDriveException;
@@ -163,4 +164,24 @@ public class SystemConfigService {
return systemConfigDTO.getUsername();
}
/**
* 获取站点域名
*
* @return 站点域名
*/
public String getDomain() {
SystemConfigDTO systemConfigDTO = getSystemConfig();
return systemConfigDTO.getDomain();
}
/**
* 获取是否已安装初始化
*
* @return 是否已安装初始化
*/
public boolean getIsInstall() {
SystemConfigDTO systemConfigDTO = getSystemConfig();
return StrUtil.isNotEmpty(systemConfigDTO.getUsername());
}
}

View File

@@ -29,6 +29,7 @@ public abstract class AbstractOneDriveServiceBase extends MicrosoftDriveServiceB
add(new StorageConfig("accessToken", "访问令牌"));
add(new StorageConfig("refreshToken", "刷新令牌"));
add(new StorageConfig("basePath", "基路径"));
add(new StorageConfig("proxyDomain", "反代域名"));
}};
}
}

View File

@@ -73,7 +73,11 @@ public abstract class AbstractS3BaseFileService extends AbstractBaseFileService
fileItemDTO.setTime(s.getLastModified());
fileItemDTO.setType(FileTypeEnum.FILE);
fileItemDTO.setPath(path);
fileItemDTO.setUrl(getDownloadUrl(StringUtils.concatUrl(path, fileItemDTO.getName())));
String fullPathAndName = StringUtils.concatUrl(path, fileItemDTO.getName());
String directlink = StringUtils.generatorLink(driveId, fullPathAndName);
fileItemDTO.setUrl(directlink);
fileItemList.add(fileItemDTO);
}
@@ -129,6 +133,7 @@ public abstract class AbstractS3BaseFileService extends AbstractBaseFileService
for (FileItemDTO fileItemDTO : list) {
String fullPath = StringUtils.concatUrl(fileItemDTO.getPath(), fileItemDTO.getName());
if (Objects.equals(fullPath, path)) {
fileItemDTO.setUrl(getDownloadUrl(path));
return fileItemDTO;
}
}

View File

@@ -24,6 +24,7 @@ public abstract class AbstractSharePointServiceBase extends MicrosoftDriveServic
return new ArrayList<StorageConfig>() {{
add(new StorageConfig("accessToken", "访问令牌"));
add(new StorageConfig("refreshToken", "刷新令牌"));
add(new StorageConfig("proxyDomain", "反代域名"));
add(new StorageConfig("basePath", "基路径"));
add(new StorageConfig("siteName", "站点名称"));
add(new StorageConfig("siteId", "SiteId"));

View File

@@ -32,12 +32,6 @@ import java.util.List;
@Slf4j
public abstract class MicrosoftDriveServiceBase extends AbstractBaseFileService {
/**
* https://graph.microsoft.com/v1.0/drives/6e4cf6d2f7e15197/root:%2FData%2F%E5%B8%A6%E6%9C%89%E7%AC%A6%E5%8F%B7%E6%96%87%E4%BB%B6%E5%A4%B9%E6%B5%8B%E8%AF%95%2F%25100+%2520:/children
* https://graph.microsoft.com/v1.0/me/drive/root:%2FData%2F%E5%B8%A6%E6%9C%89%E7%AC%A6%E5%8F%B7%E6%96%87%E4%BB%B6%E5%A4%B9%E6%B5%8B%E8%AF%95%2F%25100+%2520:/children
*/
/**
* 获取根文件 API URI
*/
@@ -63,6 +57,7 @@ public abstract class MicrosoftDriveServiceBase extends AbstractBaseFileService
*/
private static final String ONE_DRIVE_FILE_FLAG = "file";
protected String proxyDomain;
@Resource
@Lazy
@@ -152,7 +147,7 @@ public abstract class MicrosoftDriveServiceBase extends AbstractBaseFileService
try {
root = oneDriveRestTemplate.exchange(requestUrl, HttpMethod.GET, entity, JSONObject.class, getGraphEndPoint(), getType(), fullPath).getBody();
} catch (HttpClientErrorException e) {
log.debug("调用 OneDrive 时出现了网络异常, 已尝试重新刷新 token 后再试.");
log.debug("调用 OneDrive 时出现了网络异常, 响应信息: {}, 已尝试重新刷新 token 后再试.", e.getResponseBodyAsString());
refreshOneDriveToken();
root = oneDriveRestTemplate.exchange(requestUrl, HttpMethod.GET, entity, JSONObject.class, getGraphEndPoint(), getType(), fullPath).getBody();
}
@@ -174,7 +169,11 @@ public abstract class MicrosoftDriveServiceBase extends AbstractBaseFileService
fileItemDTO.setTime(fileItem.getDate("lastModifiedDateTime"));
if (fileItem.containsKey("file")) {
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);
@@ -202,7 +201,7 @@ public abstract class MicrosoftDriveServiceBase extends AbstractBaseFileService
try {
fileItem = oneDriveRestTemplate.exchange(DRIVER_ITEM_URL, HttpMethod.GET, entity, JSONObject.class, getGraphEndPoint(), getType(), fullPath).getBody();
} catch (HttpClientErrorException e) {
log.debug("调用 OneDrive 时出现了网络异常, 已尝试重新刷新 token 后再试.");
log.debug("调用 OneDrive 时出现了网络异常, 响应信息: {}, 已尝试重新刷新 token 后再试.", e.getResponseBodyAsString());
refreshOneDriveToken();
fileItem = oneDriveRestTemplate.exchange(DRIVER_ITEM_URL, HttpMethod.GET, entity, JSONObject.class, getGraphEndPoint(), getType(), fullPath).getBody();
}

View File

@@ -13,7 +13,6 @@ import im.zhaojun.zfile.service.base.BaseFileService;
import im.zhaojun.zfile.util.StringUtils;
import lombok.SneakyThrows;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClientConfig;
import org.apache.commons.net.ftp.FTPFile;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
@@ -65,7 +64,6 @@ public class FtpServiceImpl extends AbstractBaseFileService implements BaseFileS
isInitialized = false;
} else {
ftp = new Ftp(host, Integer.parseInt(port), username, password, StandardCharsets.UTF_8);
ftp.getClient().configure(new FTPClientConfig(FTPClientConfig.SYST_UNIX));
ftp.getClient().type(FTP.BINARY_FILE_TYPE);
testConnection();
isInitialized = true;
@@ -83,7 +81,6 @@ public class FtpServiceImpl extends AbstractBaseFileService implements BaseFileS
ftpFiles = ftp.getClient().listFiles(fullPath);
} catch (Exception e) {
e.printStackTrace();
// ignore
}
List<FileItemDTO> fileItemList = new ArrayList<>();

View File

@@ -47,6 +47,11 @@ public class OneDriveChinaServiceImpl extends AbstractOneDriveServiceBase implem
String accessToken = stringStorageConfigMap.get(StorageConfigConstant.ACCESS_TOKEN_KEY).getValue();
String refreshToken = stringStorageConfigMap.get(StorageConfigConstant.REFRESH_TOKEN_KEY).getValue();
super.basePath = stringStorageConfigMap.get(StorageConfigConstant.BASE_PATH).getValue();
StorageConfig proxyDomainStorageConfig = stringStorageConfigMap.get(StorageConfigConstant.PROXY_DOMAIN);
if (proxyDomainStorageConfig != null) {
super.proxyDomain = proxyDomainStorageConfig.getValue();
}
if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(refreshToken)) {
log.debug("初始化存储策略 [{}] 失败: 参数不完整", getStorageTypeEnum().getDescription());

View File

@@ -47,6 +47,10 @@ public class OneDriveServiceImpl extends AbstractOneDriveServiceBase implements
String accessToken = stringStorageConfigMap.get(StorageConfigConstant.ACCESS_TOKEN_KEY).getValue();
String refreshToken = stringStorageConfigMap.get(StorageConfigConstant.REFRESH_TOKEN_KEY).getValue();
super.basePath = stringStorageConfigMap.get(StorageConfigConstant.BASE_PATH).getValue();
StorageConfig proxyDomainStorageConfig = stringStorageConfigMap.get(StorageConfigConstant.PROXY_DOMAIN);
if (proxyDomainStorageConfig != null) {
super.proxyDomain = proxyDomainStorageConfig.getValue();
}
if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(refreshToken)) {
log.debug("初始化存储策略 [{}] 失败: 参数不完整", getStorageTypeEnum().getDescription());

View File

@@ -48,6 +48,10 @@ public class SharePointChinaServiceImpl extends AbstractSharePointServiceBase im
String refreshToken = stringStorageConfigMap.get(StorageConfigConstant.REFRESH_TOKEN_KEY).getValue();
super.siteId = stringStorageConfigMap.get(StorageConfigConstant.SHAREPOINT_SITE_ID).getValue();
super.basePath = stringStorageConfigMap.get(StorageConfigConstant.BASE_PATH).getValue();
StorageConfig proxyDomainStorageConfig = stringStorageConfigMap.get(StorageConfigConstant.PROXY_DOMAIN);
if (proxyDomainStorageConfig != null) {
super.proxyDomain = proxyDomainStorageConfig.getValue();
}
if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(refreshToken)) {
log.debug("初始化存储策略 [{}] 失败: 参数不完整", getStorageTypeEnum().getDescription());

View File

@@ -48,6 +48,10 @@ public class SharePointServiceImpl extends AbstractSharePointServiceBase impleme
String refreshToken = stringStorageConfigMap.get(StorageConfigConstant.REFRESH_TOKEN_KEY).getValue();
super.siteId = stringStorageConfigMap.get(StorageConfigConstant.SHAREPOINT_SITE_ID).getValue();
super.basePath = stringStorageConfigMap.get(StorageConfigConstant.BASE_PATH).getValue();
StorageConfig proxyDomainStorageConfig = stringStorageConfigMap.get(StorageConfigConstant.PROXY_DOMAIN);
if (proxyDomainStorageConfig != null) {
super.proxyDomain = proxyDomainStorageConfig.getValue();
}
if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(refreshToken)) {
log.debug("初始化存储策略 [{}] 失败: 参数不完整", getStorageTypeEnum().getDescription());

View File

@@ -1,21 +1,35 @@
package im.zhaojun.zfile.util;
import cn.hutool.core.util.URLUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Date;
/**
* @author zhaojun
*/
@Slf4j
public class FileUtil {
public static ResponseEntity<Object> export(File file, String fileName) {
/**
* 文件下载,单线程,直接传
* @param file 文件对象
* @param fileName 要保存为的文件名
* @return 文件下载对象
*/
public static ResponseEntity<Object> exportSingleThread(File file, String fileName) {
if (!file.exists()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("404 FILE NOT FOUND");
}
@@ -23,7 +37,7 @@ public class FileUtil {
MediaType mediaType = MediaType.APPLICATION_OCTET_STREAM;
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
if (StringUtils.isNullOrEmpty(fileName)) {
fileName = file.getName();
@@ -31,10 +45,10 @@ public class FileUtil {
headers.setContentDispositionFormData("attachment", URLUtil.encode(fileName));
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
headers.add("Last-Modified", new Date().toString());
headers.add("ETag", String.valueOf(System.currentTimeMillis()));
headers.add(HttpHeaders.PRAGMA, "no-cache");
headers.add(HttpHeaders.EXPIRES, "0");
headers.add(HttpHeaders.LAST_MODIFIED, new Date().toString());
headers.add(HttpHeaders.ETAG, String.valueOf(System.currentTimeMillis()));
return ResponseEntity
.ok()
.headers(headers)
@@ -43,26 +57,133 @@ public class FileUtil {
.body(new FileSystemResource(file));
}
public static ResponseEntity<Object> export(File file) {
/**
* 返回文件给 response支持断点续传和多线程下载
* @param request 请求对象
* @param response 响应对象
* @param file 下载的文件
*/
public static void export(HttpServletRequest request, HttpServletResponse response, File file) {
export(request, response, file, file.getName());
}
/**
* 返回文件给 response支持断点续传和多线程下载
* @param request 请求对象
* @param response 响应对象
* @param file 下载的文件
* @param fileName 下载的文件名,为空则默认读取文件名称
*/
public static void export(HttpServletRequest request, HttpServletResponse response, File file, String fileName) {
if (!file.exists()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("404 FILE NOT FOUND");
try {
response.getWriter().write("404 FILE NOT FOUND");
} catch (IOException e) {
e.printStackTrace();
}
}
MediaType mediaType = MediaType.APPLICATION_OCTET_STREAM;
if (StringUtils.isNullOrEmpty(fileName)) {
//文件名
fileName = file.getName();
}
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.setContentDispositionFormData("attachment", URLUtil.encode(file.getName()));
String range = request.getHeader(HttpHeaders.RANGE);
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
headers.add("Last-Modified", new Date().toString());
headers.add("ETag", String.valueOf(System.currentTimeMillis()));
return ResponseEntity
.ok()
.headers(headers)
.contentLength(file.length())
.contentType(mediaType)
.body(new FileSystemResource(file));
String rangeSeparator = "-";
// 开始下载位置
long startByte = 0;
// 结束下载位置
long endByte = file.length() - 1;
// 如果是断点续传
if (range != null && range.contains("bytes=") && range.contains(rangeSeparator)) {
// 设置响应状态码为 206
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
range = range.substring(range.lastIndexOf("=") + 1).trim();
String[] ranges = range.split(rangeSeparator);
try {
// 判断 range 的类型
if (ranges.length == 1) {
// 类型一bytes=-2343
if (range.startsWith(rangeSeparator)) {
endByte = Long.parseLong(ranges[0]);
}
// 类型二bytes=2343-
else if (range.endsWith(rangeSeparator)) {
startByte = Long.parseLong(ranges[0]);
}
}
// 类型三bytes=22-2343
else if (ranges.length == 2) {
startByte = Long.parseLong(ranges[0]);
endByte = Long.parseLong(ranges[1]);
}
} catch (NumberFormatException e) {
// 传参不规范,则直接返回所有内容
startByte = 0;
endByte = file.length() - 1;
}
} else {
// 没有 ranges 即全部一次性传输,需要用 200 状态码,这一行应该可以省掉,因为默认返回是 200 状态码
response.setStatus(HttpServletResponse.SC_OK);
}
//要下载的长度endByte 为总长度 -1这时候要加回去
long contentLength = endByte - startByte + 1;
//文件类型
String contentType = request.getServletContext().getMimeType(fileName);
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, "inline;filename=" + URLUtil.encode(fileName));
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
// [要下载的开始位置]-[结束位置]/[文件总大小]
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + startByte + rangeSeparator + endByte + "/" + file.length());
BufferedOutputStream outputStream;
RandomAccessFile randomAccessFile = null;
//已传送数据大小
long transmitted = 0;
try {
randomAccessFile = new RandomAccessFile(file, "r");
outputStream = new BufferedOutputStream(response.getOutputStream());
byte[] buff = new byte[4096];
int len = 0;
randomAccessFile.seek(startByte);
while ((transmitted + len) <= contentLength && (len = randomAccessFile.read(buff)) != -1) {
outputStream.write(buff, 0, len);
transmitted += len;
// 本地测试, 防止下载速度过快
// Thread.sleep(1);
}
// 处理不足 buff.length 部分
if (transmitted < contentLength) {
len = randomAccessFile.read(buff, 0, (int) (contentLength - transmitted));
outputStream.write(buff, 0, len);
transmitted += len;
}
outputStream.flush();
response.flushBuffer();
randomAccessFile.close();
// log.trace("下载完毕: {}-{}, 已传输 {}", startByte, endByte, transmitted);
} catch (ClientAbortException e) {
// ignore 用户停止下载
// log.trace("用户停止下载: {}-{}, 已传输 {}", startByte, endByte, transmitted);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (randomAccessFile != null) {
randomAccessFile.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

View File

@@ -37,7 +37,7 @@ public class HttpUtil {
try {
result = restTemplate.getForObject(url, String.class);
} catch (Exception e) {
throw new TextParseException("文件解析异常");
throw new TextParseException("文件解析异常, 请求 url = " + url + ", 异常信息为 = " + e.getMessage());
}
return result == null ? "" : result;

View File

@@ -1,7 +1,9 @@
package im.zhaojun.zfile.util;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.URLUtil;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.service.SystemConfigService;
/**
* @author zhaojun
@@ -11,6 +13,8 @@ public class StringUtils {
public static final char DELIMITER = '/';
public static final String DELIMITER_STR = "/";
public static final String HTTP_PROTOCAL = "http://";
public static final String HTTPS_PROTOCAL = "https://";
@@ -101,4 +105,44 @@ public class StringUtils {
path = ObjectUtil.defaultIfNull(path, "");
return StringUtils.removeDuplicateSeparator(basePath + ZFileConstant.PATH_SEPARATOR + path);
}
/**
* 替换 URL 中的 Host 部分,如替换 http://a.com/1.txt 为 https://abc.com/1.txt
* @param originUrl
* 原 URL
* @param replaceHost
* 替换的 HOST
* @return 替换后的 URL
*/
public static String replaceHost(String originUrl, String replaceHost) {
return concatPath(replaceHost, URLUtil.getPath(originUrl));
}
/**
* 拼接 URL并去除重复的分隔符 '/',但不会影响 http:// 和 https:// 这种头部
* @param strs 拼接的字符数组
* @return 拼接结果
*/
public static String concatUrl(String... strs) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < strs.length; i++) {
sb.append(strs[i]);
if (i != strs.length - 1) {
sb.append(DELIMITER);
}
}
return removeDuplicateSeparator(sb.toString());
}
/**
* 拼接文件直链生成 URL
* @param driveId 驱动器 ID
* @param fullPath 文件全路径
* @return 生成结果
*/
public static String generatorLink(Integer driveId, String fullPath) {
SystemConfigService systemConfigService = SpringContextHolder.getBean(SystemConfigService.class);
String domain = systemConfigService.getDomain();
return concatUrl(domain, ZFileConstant.DIRECT_LINK_PREFIX, String.valueOf(driveId), fullPath);
}
}

View File

@@ -91,6 +91,12 @@
"name": "zfile.debug",
"type": "java.lang.Boolean",
"description": "是否开启 debug 模式."
},
{
"name": "zfile.directLinkPrefix",
"type": "java.lang.String",
"defaultValue": "directlink",
"description": "直链前缀名称, 默认为 directlink"
}
]
}

View File

@@ -1,5 +1,6 @@
zfile:
debug: false
directLinkPrefix: directlink
log:
path: ${user.home}/.zfile/logs
db:

View File

@@ -0,0 +1,7 @@
________ ________ ___ ___ _______
|\_____ \|\ _____\\ \|\ \ |\ ___ \
\|___/ /\ \ \__/\ \ \ \ \ \ \ ___
/ / /\ \ __\\ \ \ \ \ \ \ ___\
/ /_/__\ \ \_| \ \ \ \ \____\ \ _____
|\________\ \__\ \ \__\ \_______\ \_______\
\|_______|\|__| \|__|\|_______|\|_______|

View File

@@ -5,9 +5,12 @@ INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`) VALUES (7, 'password', '管理
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`) VALUES (8, 'domain', '站点域名');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`) VALUES (11, 'customCss', '自定义 CSS');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`) VALUES (12, 'customJs', '自定义 JS (可用于统计代码)');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`) VALUES (13, 'tableSize', '表格大小');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`) VALUES (14, 'showOperator', '是否显示操作按钮');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`) VALUES (15, 'showDocument', '是否显示文档');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`, `value`) VALUES (13, 'tableSize', '表格大小', 'mini');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`, `value`) VALUES (14, 'showOperator', '是否显示操作按钮', 'true');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`, `value`) VALUES (15, 'showDocument', '是否显示文档', 'true');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`) VALUES (16, 'announcement', '网站公告');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`) VALUES (17, 'showAnnouncement', '是否显示网站公告');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`) VALUES (18, 'layout', '页面布局');
INSERT INTO SYSTEM_CONFIG (`ID`, `k`, `REMARK`, `value`) VALUES (17, 'showAnnouncement', '是否显示网站公告', 'true');
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');

View File

@@ -1 +1 @@
.zfile-admin-index[data-v-158be3bf]{height:100%;overflow-y:hidden}.zfile-admin-top[data-v-158be3bf]{background-color:#001529}.zfile-admin-top-logo[data-v-158be3bf]{cursor:pointer;height:100%;line-height:61px;color:#fff;padding-right:20px}.zfile-admin-top-logo[data-v-158be3bf]:hover{color:#1890ff}.zfile-admin-top-content[data-v-158be3bf]{display:-webkit-box;display:-ms-flexbox;display:flex;margin:auto;max-width:1200px}.zfile-admin-content[data-v-158be3bf]{background-color:#f0f2f5;height:100%;overflow-y:auto}.zfile-admin-content-view[data-v-158be3bf]{margin:auto;max-width:1200px;margin-top:50px;margin-bottom:100px}.el-menu--horizontal>.el-menu-item.is-active[data-v-158be3bf]{color:#1890ff!important}.el-menu--horizontal>.el-menu-item[data-v-158be3bf],.el-menu.el-menu--horizontal[data-v-158be3bf]{border:none}.zfile-admin-index-version-info[data-v-158be3bf],.zfile-admin-index-version-info[data-v-158be3bf] .el-link--inner{font-size:13px}.zfile-login[data-v-085cced0]{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-085cced0]{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-085cced0]{font-size:20px}.zfile-login-form[data-v-085cced0]{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-875b6660]{overflow-y:auto}#siteForm[data-v-875b6660]{margin-top:20px}#siteForm[data-v-875b6660] .el-select{width:70%}.markdown-body[data-v-fad21930]{padding:unset}.el-drive-form-col[data-v-6669110c]{padding-left:0!important}.zfile-site-id-input-site-type-select[data-v-6669110c]{width:100px}.zfile-cache-statistics[data-v-f4e53df0]{margin-bottom:20px}.zfile-cache-table[data-v-f4e53df0]{width:100%;overflow-y:auto}.zfile-filter-delete-btn[data-v-e3077b30]{margin-left:10px;margin-top:5px}.zfile-filter-save-btn[data-v-e3077b30]{text-align:right;margin-top:15px}.el-row[data-v-4162da7e]{padding:20px}.el-form-item[data-v-4162da7e]{margin-right:50px}.card-title[data-v-4162da7e]{color:rgba(0,0,0,.45);font-size:14px}.card-content[data-v-4162da7e]{color:rgba(0,0,0,.85);font-size:25px;line-height:30px}.card-title-button[data-v-4162da7e]{float:right;padding:3px 0}.table-search-input[data-v-4162da7e]{width:300px;float:right}#filterForm .el-row[data-v-4162da7e]{padding:0}#cacheDialog[data-v-4162da7e] .el-dialog__body{padding:20px}.table-edit-icon[data-v-4162da7e]{margin-left:5px;color:#409eff;cursor:pointer}.zfile-admin-short-form[data-v-1513b0ea] .el-form-item:first-child{margin-left:10px}.zfile-admin-short-form[data-v-1513b0ea] .el-form-item:not(:first-child){margin-left:20px}.el-pagination[data-v-1513b0ea]{margin-top:15px}.table-edit-icon[data-v-1513b0ea]{margin-left:10px;cursor:pointer}.input-with-select .el-select[data-v-3ac09bd8]{width:100px}
.zfile-admin-index[data-v-158be3bf]{height:100%;overflow-y:hidden}.zfile-admin-top[data-v-158be3bf]{background-color:#001529}.zfile-admin-top-logo[data-v-158be3bf]{cursor:pointer;height:100%;line-height:61px;color:#fff;padding-right:20px}.zfile-admin-top-logo[data-v-158be3bf]:hover{color:#1890ff}.zfile-admin-top-content[data-v-158be3bf]{display:-webkit-box;display:-ms-flexbox;display:flex;margin:auto;max-width:1200px}.zfile-admin-content[data-v-158be3bf]{background-color:#f0f2f5;height:100%;overflow-y:auto}.zfile-admin-content-view[data-v-158be3bf]{margin:auto;max-width:1200px;margin-top:50px;margin-bottom:100px}.el-menu--horizontal>.el-menu-item.is-active[data-v-158be3bf]{color:#1890ff!important}.el-menu--horizontal>.el-menu-item[data-v-158be3bf],.el-menu.el-menu--horizontal[data-v-158be3bf]{border:none}.zfile-admin-index-version-info[data-v-158be3bf],.zfile-admin-index-version-info[data-v-158be3bf] .el-link--inner{font-size:13px}.zfile-login[data-v-085cced0]{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-085cced0]{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-085cced0]{font-size:20px}.zfile-login-form[data-v-085cced0]{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-875b6660]{overflow-y:auto}#siteForm[data-v-875b6660]{margin-top:20px}#siteForm[data-v-875b6660] .el-select{width:70%}.markdown-body[data-v-fad21930]{padding:unset}.el-drive-form-col[data-v-da51cbbe]{padding-left:0!important}.zfile-site-id-input-site-type-select[data-v-da51cbbe]{width:100px}.zfile-cache-statistics[data-v-f4e53df0]{margin-bottom:20px}.zfile-cache-table[data-v-f4e53df0]{width:100%;overflow-y:auto}.zfile-filter-delete-btn[data-v-e3077b30]{margin-left:10px;margin-top:5px}.zfile-filter-save-btn[data-v-e3077b30]{text-align:right;margin-top:15px}.el-row[data-v-6b6f5036]{padding:20px}.el-form-item[data-v-6b6f5036]{margin-right:50px}.card-title[data-v-6b6f5036]{color:rgba(0,0,0,.45);font-size:14px}.card-content[data-v-6b6f5036]{color:rgba(0,0,0,.85);font-size:25px;line-height:30px}.card-title-button[data-v-6b6f5036]{float:right;padding:3px 0}.table-search-input[data-v-6b6f5036]{width:300px;float:right}#filterForm .el-row[data-v-6b6f5036]{padding:0}#cacheDialog[data-v-6b6f5036] .el-dialog__body{padding:20px}.table-edit-icon[data-v-6b6f5036]{margin-left:5px;color:#409eff;cursor:pointer}.zfile-admin-short-form[data-v-1513b0ea] .el-form-item:first-child{margin-left:10px}.zfile-admin-short-form[data-v-1513b0ea] .el-form-item:not(:first-child){margin-left:20px}.el-pagination[data-v-1513b0ea]{margin-top:15px}.table-edit-icon[data-v-1513b0ea]{margin-left:10px;cursor:pointer}.input-with-select .el-select[data-v-3ac09bd8]{width:100px}

View File

@@ -1 +1 @@
.zfile-install[data-v-3e291e0d]{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-3e291e0d]{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-3e291e0d]{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-3e291e0d]{text-align:right;margin-bottom:0}.zfile-install-title small[data-v-3e291e0d]{font-size:20px}
.zfile-install[data-v-66213dd3]{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-66213dd3]{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-66213dd3]{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-66213dd3]{text-align:right;margin-bottom:0}.zfile-install-title small[data-v-66213dd3]{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

File diff suppressed because one or more lines are too long