Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90101c173c | ||
|
|
c590953197 | ||
|
|
23772223bd | ||
|
|
b88f413906 | ||
|
|
6d8bdf1df3 | ||
|
|
0173357ec9 | ||
|
|
b4ae4bcc90 | ||
|
|
f7679217ec | ||
|
|
901d539332 | ||
|
|
1b3c284f4e |
13
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: 'Blank Issue'
|
||||
description: 请使用 https://issue.zfile.vip 创建新的问题.
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**注意:**
|
||||
不要通过此页面创建问题, 请使用 https://issue.zfile.vip 创建新的问题.
|
||||
如果不是通过此链接创建的问题, 将会被直接关闭.
|
||||
- type: textarea
|
||||
id: add-a-description
|
||||
attributes:
|
||||
label: Add a description
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,4 +1,4 @@
|
||||
blank_issues_enabled: false
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 创建 Issue
|
||||
url: https://issue.zfile.vip/
|
||||
|
||||
94
README.md
@@ -1,61 +1,83 @@
|
||||
# ZFile
|
||||
<div align="center">
|
||||
<a href="https://zfile.vip" target="_blank" rel="noopener noreferrer">
|
||||
<img style="margin: auto; width: 100px; display: block" src="/img/logo-zfile.png" alt="ZFile" />
|
||||
</a>
|
||||
<p>ZFile 是一个适用于个人或小团队的在线网盘程序,可以将多种存储类型统一管理,再也不用登录各种网站管理文件,现在你只需要在 ZFile 中畅快使用!</p>
|
||||
<div>
|
||||
<img alt="last commit" src="https://shields.io/github/last-commit/zhaojun1998/zfile.svg?style=flat-square"/>
|
||||
<img alt="downloads" src="https://shields.io/github/downloads/zhaojun1998/zfile/total?style=flat-square"/>
|
||||
<img alt="release version" src="https://shields.io/github/v/release/zhaojun1998/zfile?style=flat-square"/>
|
||||
<img alt="commit activity" src="https://shields.io/github/commit-activity/y/zhaojun1998/zfile?style=flat-square"/>
|
||||
<img alt="open issues" src="https://shields.io/github/issues/zhaojun1998/zfile?style=flat-square"/>
|
||||
<img alt="closed issues" src="https://shields.io/github/issues-closed-raw/zhaojun1998/zfile?style=flat-square"/>
|
||||
<img alt="forks" src="https://shields.io/github/forks/zhaojun1998/zfile?style=flat-square"/>
|
||||
<img alt="stars" src="https://shields.io/github/stars/zhaojun1998/zfile?style=flat-square"/>
|
||||
<img alt="watchers" src="https://shields.io/github/watchers/zhaojun1998/zfile?style=flat-square"/>
|
||||
</div>
|
||||
<span>
|
||||
<a href="https://zfile.vip">官网</a>
|
||||
<span> | </span>
|
||||
<a href="https://docs.zfile.vip">文档</a>
|
||||
<span> | </span>
|
||||
<a href="https://demo.zfile.vip">预览地址</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
[](https://github.com/zfile-dev/zfile/blob/main/LICENSE)
|
||||
[](https://github.com/zfile-dev/zfile/releases)
|
||||
<img src="https://api.codacy.com/project/badge/Grade/70b793267f7941d58cbd93f50c9a8e0a"/>
|
||||
[](https://hub.docker.com/r/zhaojun1998/zfile)
|
||||
[](https://www.bt.cn/u/WYVNdM)
|
||||
## 系统特色
|
||||
|
||||
## ZFile 是什么?
|
||||
|
||||
ZFile 是一个适用于个人的在线网盘(列目录)程序,可以将你各个存储类型的存储源,统一到一个网页中查看、预览、维护,再也不用去登录各种各样的网页登录后管理文件,现在你只需要在 ZFile 中使用。你只需要填写存储源相关信息,其他的令牌刷新,授权都是尽量自动化的,且有完善的文档帮助你使用。
|
||||
|
||||
- 支持对接 S3、OneDrive、SharePoint、Google Drive、多吉云、又拍云、本地存储、FTP、SFTP 等存储源
|
||||
- 支持在线浏览图片、播放音视频,文本文件、Office、obj(3d)等文件类型。
|
||||
- Docker、Docker Compose 支持(amd64, arm64)。
|
||||
- 支持对文件生成直链、短链(可设过期时间)。
|
||||
- 响应式设计,支持手机、平板、电脑等多种设备访问。
|
||||
- 支持多用户功能,可分配给指定用户指定存储源或目录。
|
||||
- 支持在线浏览图片、播放音视频,文本文件、Office、Obj(3d)等文件类型。
|
||||
- 支持对接 S3、OneDrive、SharePoint、Google Drive、多吉云、又拍云、本地存储、FTP、SFTP 等存储源。
|
||||
- 支持常用快捷键,`Ctrl + A` 全选,`Ctrl + 左键` 多选,`Shift + 左键` 范围选择,`Esc` 取消全选等。
|
||||
- 支持限速下载(捐赠版)
|
||||
- 支持限制指定用户可查看、上传的文件类型(捐赠版)
|
||||
|
||||
## 快速开始
|
||||
|
||||
请参考部署文档: [https://docs.zfile.vip](https://docs.zfile.vip)
|
||||
一键脚本安装:
|
||||
|
||||
## 在线体验
|
||||
```bash
|
||||
curl -sSL https://docs.zfile.vip/install.sh -o install.sh && chmod +x install.sh && ./install.sh
|
||||
```
|
||||
|
||||
更多安装方式请参考 [安装文档](https://docs.zfile.vip/install/)
|
||||
|
||||
[https://demo.zfile.vip](https://demo.zfile.vip)
|
||||
|
||||
## 功能预览
|
||||
|
||||
### 文件列表
|
||||

|
||||

|
||||
### 画廊模式
|
||||

|
||||

|
||||
### 视频预览
|
||||

|
||||

|
||||
### 文本预览
|
||||

|
||||

|
||||
### 音频预览
|
||||

|
||||

|
||||
### PDF 预览
|
||||

|
||||

|
||||
### Office 预览
|
||||

|
||||

|
||||
### 3d 文件预览
|
||||

|
||||

|
||||
### 生成直链
|
||||

|
||||

|
||||
### 页面设置
|
||||

|
||||

|
||||
### 后台设置-登录
|
||||

|
||||

|
||||
### 后台设置-存储源列表
|
||||

|
||||
### 后台设置-存储源权限控制
|
||||

|
||||

|
||||
### 后台设置-添加存储源(本地存储)
|
||||

|
||||
### 后台设置-添加存储源(世纪互联)
|
||||

|
||||

|
||||
### 后台设置-用户管理
|
||||

|
||||
### 后台设置-显示设置
|
||||

|
||||
|
||||

|
||||
|
||||
## 支持作者
|
||||
|
||||
@@ -63,10 +85,6 @@ ZFile 是一个适用于个人的在线网盘(列目录)程序,可以将你各
|
||||
|
||||
<img src="https://cdn.jun6.net/2021/03/27/152704e91f13d.png" width="400" alt="赞助我">
|
||||
|
||||
## Status
|
||||
|
||||

|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#zfile-dev/zfile&Date)
|
||||
|
||||
BIN
img/file-list.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
img/gallery.png
Normal file
|
After Width: | Height: | Size: 563 KiB |
BIN
img/generate-link.jpeg
Normal file
|
After Width: | Height: | Size: 362 KiB |
BIN
img/login.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
img/logo-zfile.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
img/page-setting.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
img/preview-3d.png
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
img/preview-audio.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
img/preview-office.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
img/preview-pdf.png
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
img/preview-text.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
img/preview-video.png
Normal file
|
After Width: | Height: | Size: 790 KiB |
BIN
img/storage-edit-local.png
Normal file
|
After Width: | Height: | Size: 483 KiB |
BIN
img/storage-list.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
img/user-edit.png
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
img/view-setting.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
@@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||
import org.springframework.core.io.FileSystemResourceLoader;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
@@ -37,7 +38,7 @@ public class FrontIndexController {
|
||||
*/
|
||||
@RequestMapping(value = { "/"})
|
||||
@ResponseBody
|
||||
public String redirect() {
|
||||
public ResponseEntity<String> redirect() {
|
||||
// 读取 resources/static/index.html 文件修改 title 和 favicon 后返回
|
||||
ResourceLoader resourceLoader = new FileSystemResourceLoader();
|
||||
String[] staticLocations = webProperties.getResources().getStaticLocations();
|
||||
@@ -64,7 +65,7 @@ public class FrontIndexController {
|
||||
log.debug("读取 index.html 文件成功, 文件路径: {}", staticLocation);
|
||||
} catch (Exception e) {
|
||||
log.error("{} 资源存在但读取 index.html 文件失败.", staticLocation);
|
||||
return "static index.html read error";
|
||||
return ResponseEntity.status(500).body("static index.html read error");
|
||||
}
|
||||
|
||||
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
|
||||
@@ -81,11 +82,14 @@ public class FrontIndexController {
|
||||
content = content.replace("/favicon.svg", faviconUrl);
|
||||
}
|
||||
|
||||
return content;
|
||||
// 添加缓存控制头
|
||||
return ResponseEntity.ok()
|
||||
.header("Cache-Control", "max-age=600, must-revalidate, proxy-revalidate") .header("Pragma", "no-cache")
|
||||
.body(content);
|
||||
}
|
||||
}
|
||||
|
||||
return "static index.html not found";
|
||||
return ResponseEntity.status(404).body("static index.html not found");
|
||||
}
|
||||
|
||||
@RequestMapping(value = { "/guest"})
|
||||
|
||||
@@ -139,7 +139,7 @@ public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(value = NoResourceFoundException.class)
|
||||
@ResponseBody
|
||||
public String notFoundAccessException() {
|
||||
return frontIndexController.redirect();
|
||||
return frontIndexController.redirect().getBody();
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = MethodNotAllowedAccessException.class)
|
||||
|
||||
@@ -2,11 +2,15 @@ package im.zhaojun.zfile.core.util;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.CacheUtil;
|
||||
import cn.hutool.cache.impl.CacheObj;
|
||||
import im.zhaojun.zfile.module.onlyoffice.model.OnlyOfficeFile;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@@ -83,6 +87,45 @@ public class OnlyOfficeKeyCacheUtils {
|
||||
return onlyOfficeFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理缓存中的文件信息与 Key 的映射关系.(文件发生了变化, 需要重新生成 OnlyOffice 预览链接时调用)
|
||||
*
|
||||
* @param onlyOfficeFile
|
||||
* OnlyOffice 文件信息
|
||||
*/
|
||||
public static OnlyOfficeFile removeByFile(OnlyOfficeFile onlyOfficeFile) {
|
||||
String key = ONLY_OFFICE_FILE_KEY_MAP.get(onlyOfficeFile);
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
ONLY_OFFICE_FILE_KEY_MAP.remove(onlyOfficeFile);
|
||||
ONLY_OFFICE_KEY_FILE_MAP.remove(key);
|
||||
return onlyOfficeFile;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 清理缓存中的某个文件夹下所有文件信息与 Key 的映射关系.(文件发生了变化, 需要重新生成 OnlyOffice 预览链接时调用)
|
||||
*
|
||||
* @param onlyOfficeFile
|
||||
* OnlyOffice 文件信息
|
||||
*/
|
||||
public static List<OnlyOfficeFile> removeByFolder(OnlyOfficeFile onlyOfficeFile) {
|
||||
List<OnlyOfficeFile> caches = new ArrayList<>();
|
||||
Iterator<CacheObj<OnlyOfficeFile, String>> cacheObjIterator = ONLY_OFFICE_FILE_KEY_MAP.cacheObjIterator();
|
||||
while (cacheObjIterator.hasNext()) {
|
||||
CacheObj<OnlyOfficeFile, String> cacheObj = cacheObjIterator.next();
|
||||
OnlyOfficeFile cacheOnlyOfficeFile = cacheObj.getKey();
|
||||
if (cacheOnlyOfficeFile.getStorageKey().equals(onlyOfficeFile.getStorageKey())
|
||||
&& StringUtils.startWith(cacheOnlyOfficeFile.getPathAndName(), onlyOfficeFile.getPathAndName())) {
|
||||
ONLY_OFFICE_FILE_KEY_MAP.remove(cacheObj.getKey());
|
||||
ONLY_OFFICE_KEY_FILE_MAP.remove(cacheObj.getValue());
|
||||
caches.add(cacheOnlyOfficeFile);
|
||||
}
|
||||
}
|
||||
return caches;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件锁, 防止并发操作文件缓存时出现问题.
|
||||
*
|
||||
|
||||
@@ -141,6 +141,9 @@ public class SystemConfigDTO implements Serializable {
|
||||
@Schema(name = "默认文件点击习惯", example = "click")
|
||||
private FileClickModeEnum fileClickMode;
|
||||
|
||||
@Schema(name = "移动端默认文件点击习惯", example = "click")
|
||||
private FileClickModeEnum mobileFileClickMode;
|
||||
|
||||
@Schema(name = "授权码", example = "e619510f-cdcd-f657-6c5e-2d12e9a28ae5")
|
||||
private String authCode;
|
||||
|
||||
|
||||
@@ -58,6 +58,9 @@ public class UpdateViewSettingRequest {
|
||||
@Schema(name = "默认文件点击习惯", example = "click")
|
||||
private FileClickModeEnum fileClickMode;
|
||||
|
||||
@Schema(name = "移动端默认文件点击习惯", example = "click")
|
||||
private FileClickModeEnum mobileFileClickMode;
|
||||
|
||||
@Schema(name = "onlyOffice 在线预览地址", example = "http://office.zfile.vip")
|
||||
private String onlyOfficeUrl;
|
||||
|
||||
|
||||
@@ -95,6 +95,9 @@ public class FrontSiteConfigResult {
|
||||
@Schema(name = "默认文件点击习惯", example = "click")
|
||||
private FileClickModeEnum fileClickMode;
|
||||
|
||||
@Schema(name = "移动端默认文件点击习惯", example = "click")
|
||||
private FileClickModeEnum mobileFileClickMode;
|
||||
|
||||
@Schema(name = "最大同时上传文件数", example = "5")
|
||||
private Integer maxFileUploads;
|
||||
|
||||
|
||||
@@ -101,6 +101,9 @@ public class OnlyOfficeController {
|
||||
throw new BizException("文件不存在");
|
||||
}
|
||||
|
||||
String currentUserBasePath = fileService.getCurrentUserBasePath();
|
||||
fileItemRequest.setPath(StringUtils.concat(currentUserBasePath, fileItemRequest.getPath()));
|
||||
|
||||
boolean hasUploadPermission = userStorageSourceService.hasCurrentUserStorageOperatorPermission(storageId, FileOperatorTypeEnum.UPLOAD);
|
||||
return Pair.of(fileItem, hasUploadPermission);
|
||||
} catch (Exception e) {
|
||||
@@ -163,17 +166,17 @@ public class OnlyOfficeController {
|
||||
log.debug("OnlyOffice 回调信息: {}, {}", onlyOfficeCallback.getStatus(), onlyOfficeCallback);
|
||||
boolean useOnlyOfficeSecret = StrUtil.isNotBlank(systemConfigService.getSystemConfig().getOnlyOfficeSecret());
|
||||
if (useOnlyOfficeSecret) {
|
||||
|
||||
|
||||
if (StrUtil.isBlank(onlyOfficeCallback.getToken())) {
|
||||
log.error("OnlyOffice 回调 Token 为空: {}", onlyOfficeCallback);
|
||||
return CALLBACK_ERROR_MSG;
|
||||
}
|
||||
|
||||
|
||||
if (!JWTUtil.verify(onlyOfficeCallback.getToken(), StrUtil.bytes(systemConfigService.getSystemConfig().getOnlyOfficeSecret(), StandardCharsets.UTF_8))) {
|
||||
log.error("OnlyOffice 回调 Token 验证失败: {}", onlyOfficeCallback);
|
||||
return CALLBACK_ERROR_MSG;
|
||||
return CALLBACK_ERROR_MSG;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
// 文件发送了变化,清空缓存中该文件的 key 信息.
|
||||
if (SUPPORTED_STATUS.contains(onlyOfficeCallback.getStatus())) {
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
package im.zhaojun.zfile.module.storage.aspect;
|
||||
|
||||
import im.zhaojun.zfile.core.util.ZFileAuthUtil;
|
||||
import im.zhaojun.zfile.module.user.model.entity.User;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import im.zhaojun.zfile.core.exception.biz.StorageSourceIllegalOperationBizException;
|
||||
import im.zhaojun.zfile.core.util.CollectionUtils;
|
||||
import im.zhaojun.zfile.core.util.OnlyOfficeKeyCacheUtils;
|
||||
import im.zhaojun.zfile.core.util.StringUtils;
|
||||
import im.zhaojun.zfile.core.util.ZFileAuthUtil;
|
||||
import im.zhaojun.zfile.module.onlyoffice.model.OnlyOfficeFile;
|
||||
import im.zhaojun.zfile.module.storage.annotation.StoragePermissionCheck;
|
||||
import im.zhaojun.zfile.module.storage.model.enums.FileOperatorTypeEnum;
|
||||
import im.zhaojun.zfile.module.storage.service.StorageSourceService;
|
||||
import im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService;
|
||||
import im.zhaojun.zfile.module.user.model.entity.User;
|
||||
import im.zhaojun.zfile.module.user.model.entity.UserStorageSource;
|
||||
import im.zhaojun.zfile.module.user.service.UserStorageSourceService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.Signature;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
@@ -18,8 +23,9 @@ import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -85,7 +91,8 @@ public class FileOperatorCheckAspect {
|
||||
@Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.fileList(..)) || " +
|
||||
"execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.getFileItem(..))")
|
||||
public Object availableAround(ProceedingJoinPoint point) throws Throwable {
|
||||
return check(point, FileOperatorTypeEnum.AVAILABLE);
|
||||
checkPermission(point, FileOperatorTypeEnum.AVAILABLE);
|
||||
return point.proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +105,8 @@ public class FileOperatorCheckAspect {
|
||||
*/
|
||||
@Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.newFolder(..))")
|
||||
public Object newFolderAround(ProceedingJoinPoint point) throws Throwable {
|
||||
return check(point, FileOperatorTypeEnum.NEW_FOLDER);
|
||||
checkPermission(point, FileOperatorTypeEnum.NEW_FOLDER);
|
||||
return point.proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,7 +119,19 @@ public class FileOperatorCheckAspect {
|
||||
*/
|
||||
@Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.delete*(..))")
|
||||
public Object deleteAround(ProceedingJoinPoint point) throws Throwable {
|
||||
return check(point, FileOperatorTypeEnum.DELETE);
|
||||
checkPermission(point, FileOperatorTypeEnum.DELETE);
|
||||
|
||||
Object result = point.proceed();
|
||||
|
||||
boolean isFolder = point.getSignature().getName().equals("deleteFolder");
|
||||
AbstractBaseFileService<?> targetService = (AbstractBaseFileService<?>) point.getTarget();
|
||||
String path = (String) point.getArgs()[0];
|
||||
String name = (String) point.getArgs()[1];
|
||||
String currentUserBasePath = targetService.getCurrentUserBasePath();
|
||||
String fullPath = StringUtils.concat(currentUserBasePath, path, name);
|
||||
clearOnlyOfficeCache(fullPath, targetService.storageId, isFolder);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +145,26 @@ public class FileOperatorCheckAspect {
|
||||
@Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.getUploadUrl(..)) || " +
|
||||
"execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractProxyTransferService.uploadFile(..))")
|
||||
public Object uploadAround(ProceedingJoinPoint point) throws Throwable {
|
||||
return check(point, FileOperatorTypeEnum.UPLOAD);
|
||||
checkPermission(point, FileOperatorTypeEnum.UPLOAD);
|
||||
|
||||
Object[] args = point.getArgs();
|
||||
AbstractBaseFileService<?> targetService = (AbstractBaseFileService<?>) point.getTarget();
|
||||
String currentUserBasePath = targetService.getCurrentUserBasePath();
|
||||
|
||||
String fullPath;
|
||||
String methodName = point.getSignature().getName();
|
||||
if (Objects.equals(methodName, "getUploadUrl")) {
|
||||
fullPath = StringUtils.concat(currentUserBasePath, (String) args[0], (String) args[1]);
|
||||
} else if (Objects.equals(methodName, "uploadFile")) {
|
||||
fullPath = StringUtils.concat(currentUserBasePath, (String) args[0]);
|
||||
} else {
|
||||
throw new IllegalArgumentException("上传校验异常.");
|
||||
}
|
||||
|
||||
Object result = point.proceed();
|
||||
clearOnlyOfficeCache(fullPath, targetService.storageId, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,7 +177,22 @@ public class FileOperatorCheckAspect {
|
||||
*/
|
||||
@Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.rename*(..))")
|
||||
public Object renameAround(ProceedingJoinPoint point) throws Throwable {
|
||||
return check(point, FileOperatorTypeEnum.RENAME);
|
||||
checkPermission(point, FileOperatorTypeEnum.RENAME);
|
||||
|
||||
AbstractBaseFileService<?> targetService = (AbstractBaseFileService<?>) point.getTarget();
|
||||
String currentUserBasePath = targetService.getCurrentUserBasePath();
|
||||
|
||||
Object[] args = point.getArgs();
|
||||
String path = (String) args[0];
|
||||
String name = (String) args[1];
|
||||
String newName = (String) args[2];
|
||||
String sourceFullPath = StringUtils.concat(currentUserBasePath, path, name);
|
||||
String targetFullPath = StringUtils.concat(currentUserBasePath, path, newName);
|
||||
|
||||
Object result = point.proceed();
|
||||
clearOnlyOfficeCache(sourceFullPath, targetService.storageId, Objects.equals(point.getSignature().getName(), "renameFolder"));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,7 +205,17 @@ public class FileOperatorCheckAspect {
|
||||
*/
|
||||
@Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.move*(..))")
|
||||
public Object moveAround(ProceedingJoinPoint point) throws Throwable {
|
||||
return check(point, FileOperatorTypeEnum.MOVE);
|
||||
checkPermission(point, FileOperatorTypeEnum.MOVE);
|
||||
Object result = point.proceed();
|
||||
|
||||
AbstractBaseFileService<?> targetService = (AbstractBaseFileService<?>) point.getTarget();
|
||||
String path = (String) point.getArgs()[0];
|
||||
String name = (String) point.getArgs()[1];
|
||||
String currentUserBasePath = targetService.getCurrentUserBasePath();
|
||||
String fullPath = StringUtils.concat(currentUserBasePath, path, name);
|
||||
clearOnlyOfficeCache(fullPath, targetService.storageId, Objects.equals(point.getSignature().getName(), "moveFolder"));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,7 +228,8 @@ public class FileOperatorCheckAspect {
|
||||
*/
|
||||
@Around("execution(public * im.zhaojun.zfile.module.storage.service.base.AbstractBaseFileService.copy*(..))")
|
||||
public Object copyAround(ProceedingJoinPoint point) throws Throwable {
|
||||
return check(point, FileOperatorTypeEnum.COPY);
|
||||
checkPermission(point, FileOperatorTypeEnum.COPY);
|
||||
return point.proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,10 +240,8 @@ public class FileOperatorCheckAspect {
|
||||
*
|
||||
* @param fileOperatorType
|
||||
* 文件操作类型
|
||||
*
|
||||
* @return 方法运行结果
|
||||
*/
|
||||
private Object check(ProceedingJoinPoint point, FileOperatorTypeEnum fileOperatorType) throws Throwable {
|
||||
private void checkPermission(ProceedingJoinPoint point, FileOperatorTypeEnum fileOperatorType) {
|
||||
// 获取对应的存储源 service
|
||||
AbstractBaseFileService<?> targetService = (AbstractBaseFileService<?>) point.getTarget();
|
||||
Integer storageId = targetService.storageId;
|
||||
@@ -188,8 +251,6 @@ public class FileOperatorCheckAspect {
|
||||
if (BooleanUtils.isFalse(allowAccess)) {
|
||||
throw new StorageSourceIllegalOperationBizException(storageId, fileOperatorType);
|
||||
}
|
||||
|
||||
return point.proceed();
|
||||
}
|
||||
|
||||
|
||||
@@ -215,4 +276,32 @@ public class FileOperatorCheckAspect {
|
||||
return permissions.contains(fileOperatorType.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除 OnlyOffice 缓存
|
||||
*
|
||||
* @param fullPath
|
||||
* 文件全路径(包含用户路径)
|
||||
*
|
||||
* @param storageId
|
||||
* 存储源 ID
|
||||
*/
|
||||
private void clearOnlyOfficeCache(String fullPath, Integer storageId, boolean isFolder) {
|
||||
try {
|
||||
String storageKey = storageSourceService.findStorageKeyById(storageId);
|
||||
if (isFolder) {
|
||||
List<OnlyOfficeFile> caches = OnlyOfficeKeyCacheUtils.removeByFolder(new OnlyOfficeFile(storageKey, fullPath));
|
||||
if (CollectionUtils.isNotEmpty(caches)) {
|
||||
log.debug("删除/重命名文件夹时, 清除 OnlyOffice 缓存 {} 个", caches);
|
||||
}
|
||||
} else {
|
||||
OnlyOfficeFile onlyOfficeFile = OnlyOfficeKeyCacheUtils.removeByFile(new OnlyOfficeFile(storageKey, fullPath));
|
||||
if (onlyOfficeFile != null) {
|
||||
log.debug("删除/重命名文件时, 清除 OnlyOffice 缓存: {}", onlyOfficeFile);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("清除 OnlyOffice 缓存失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,11 @@ public class StorageSourceMetadata {
|
||||
*/
|
||||
private boolean supportDeleteNotEmptyFolder = true;
|
||||
|
||||
/**
|
||||
* 是否需要在上传文件前创建文件夹
|
||||
*/
|
||||
private boolean needCreateFolderBeforeUpload = true;
|
||||
|
||||
public enum UploadType {
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,10 +14,10 @@ public class UpYunParam extends OptionalProxyTransferParam {
|
||||
@StorageParamItem(name = "存储空间名称", order = 1)
|
||||
private String bucketName;
|
||||
|
||||
@StorageParamItem(name = "用户名", order = 2)
|
||||
@StorageParamItem(name = "操作员名称", order = 2)
|
||||
private String username;
|
||||
|
||||
@StorageParamItem(name = "密码", order = 3)
|
||||
@StorageParamItem(name = "操作员密码", order = 3)
|
||||
private String password;
|
||||
|
||||
@StorageParamItem(name = "下载域名", description = "填写您在又拍云绑定的域名.", required = false, order = 4)
|
||||
|
||||
@@ -582,6 +582,7 @@ public abstract class AbstractMicrosoftDriveService<P extends MicrosoftDrivePara
|
||||
} else {
|
||||
storageSourceMetadata.setUploadType(StorageSourceMetadata.UploadType.MICROSOFT);
|
||||
}
|
||||
storageSourceMetadata.setNeedCreateFolderBeforeUpload(false);
|
||||
return storageSourceMetadata;
|
||||
}
|
||||
|
||||
|
||||
@@ -359,6 +359,7 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
|
||||
storageSourceMetadata.setSupportMoveFolder(false);
|
||||
storageSourceMetadata.setSupportCopyFolder(false);
|
||||
storageSourceMetadata.setSupportDeleteNotEmptyFolder(false);
|
||||
storageSourceMetadata.setNeedCreateFolderBeforeUpload(false);
|
||||
return storageSourceMetadata;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import im.zhaojun.zfile.core.exception.biz.FilePathSecurityBizException;
|
||||
import im.zhaojun.zfile.core.exception.biz.InitializeStorageSourceBizException;
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
import im.zhaojun.zfile.core.exception.status.NotFoundAccessException;
|
||||
import im.zhaojun.zfile.core.io.EnsureContentLengthInputStreamResource;
|
||||
import im.zhaojun.zfile.core.io.ThrottledInputStream;
|
||||
import im.zhaojun.zfile.core.util.FileUtils;
|
||||
import im.zhaojun.zfile.core.util.StringUtils;
|
||||
import im.zhaojun.zfile.module.storage.model.bo.StorageSourceMetadata;
|
||||
@@ -23,7 +21,6 @@ import im.zhaojun.zfile.module.storage.service.base.AbstractProxyTransferService
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -32,7 +29,10 @@ import org.springframework.http.MediaTypeFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
@@ -324,6 +324,7 @@ public class LocalServiceImpl extends AbstractProxyTransferService<LocalParam> {
|
||||
public StorageSourceMetadata getStorageSourceMetadata() {
|
||||
StorageSourceMetadata storageSourceMetadata = new StorageSourceMetadata();
|
||||
storageSourceMetadata.setUploadType(StorageSourceMetadata.UploadType.PROXY);
|
||||
storageSourceMetadata.setNeedCreateFolderBeforeUpload(false);
|
||||
return storageSourceMetadata;
|
||||
}
|
||||
|
||||
|
||||
@@ -357,6 +357,7 @@ public class UpYunServiceImpl extends AbstractProxyTransferService<UpYunParam> {
|
||||
storageSourceMetadata.setSupportMoveFolder(false);
|
||||
storageSourceMetadata.setSupportCopyFolder(false);
|
||||
storageSourceMetadata.setSupportDeleteNotEmptyFolder(false);
|
||||
storageSourceMetadata.setNeedCreateFolderBeforeUpload(false);
|
||||
return storageSourceMetadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
INSERT INTO system_config (`name`, `title`, `value`)
|
||||
select 'mobileFileClickMode', '移动端默认文件点击模式', value
|
||||
from system_config
|
||||
where name = 'fileClickMode';
|
||||
@@ -0,0 +1,4 @@
|
||||
INSERT INTO system_config (`name`, `title`, `value`)
|
||||
select 'mobileFileClickMode', '移动端默认文件点击模式', value
|
||||
from system_config
|
||||
where name = 'fileClickMode';
|
||||