Compare commits

...

111 Commits
4.0.6 ... 4.1.4

Author SHA1 Message Date
zhaojun
e082043f99 🔖 发布 4.1.4 版本 2023-03-05 17:03:38 +08:00
zhaojun
a55e8ae2ad 提交静态页面 2023-03-05 17:02:43 +08:00
zhaojun
188431b64d Merge remote-tracking branch 'origin/main' 2023-03-05 16:50:04 +08:00
赵俊
7f23dcb7c4 合并拉取请求 #507
fix-bug:本地存储上传文件没有关闭流
2023-03-05 16:49:35 +08:00
zhaojun
8dcb64a60d ✏️ 修改 issue 模板 2023-03-05 15:42:35 +08:00
zhaojun
d9c64ff369 ⬆️ 升级依赖版本 2023-03-05 15:38:51 +08:00
zhaojun
6963b1d593 ✏️ 修改文档,增加谷歌云和多吉云支持 2023-03-05 15:37:12 +08:00
zhaojun
0a61e1047d 捕获 ClientAbortException 异常,不进行异常输出 2023-03-05 15:34:34 +08:00
zhaojun
3f02cd9832 🐛 修复存储源别名修改后再修改回去提示占用的 BUG 2023-03-05 15:34:00 +08:00
zhaojun
3aa42c00fa 🐛 修复七牛对私有空间使用自定义域名后无法正常下载的 bug 2023-03-05 15:33:38 +08:00
zhaojun
77b2253ff6 优化本地存储检测安全性的代码 2023-03-05 15:33:29 +08:00
zhaojun
71a4fdfbaf 🐛 修复 GoogleDrive 快捷方式文件夹无法正常获取内容的 BUG 2023-03-05 15:33:14 +08:00
zhaojun
2ecd69dc51 🐛 修复自动设置 CORS 时,某些 S3 兼容性不同导致的 BUG(BackBlaze 不支持 * 和实际域名写到一起,不支持空值) 2023-03-05 15:32:45 +08:00
zhaojun
ebae9ba5c8 增加动态忽略参数不显示到前端功能 2023-03-05 15:31:59 +08:00
zhaojun
b2fd722443 为多吉云增加存储源参数 2023-03-05 15:31:46 +08:00
zhaojun
e2ce404e87 为了安全性,去除从服务器加载文本文件的功能 2023-03-05 15:31:18 +08:00
zhaojun
6cfbe7689e 更显眼的提示用户使用 Google Drive 时需要自建 API 2023-03-05 15:30:38 +08:00
zhaojun
b95c1d890b 更显眼的提示用户腾讯云使用 CDN 回源鉴权后需要关闭 ZFile 中私有空间开关。 2023-03-05 15:29:48 +08:00
zhaojun
461c77012a 增加动态忽略参数不显示到前端功能 2023-03-05 15:29:36 +08:00
zhaojun
9328e0ea9d 根据查询条件批量删除直链 2023-03-05 15:28:14 +08:00
zhaojun
aefa928a19 🐛 修复短链对应的存储源关闭后,存储源仍然可以访问的 bug 2023-03-05 15:27:31 +08:00
zhaojun
89c6c515f1 🐛 避免未控制并发生成短链导致生成多条的 BUG 2023-03-05 15:26:22 +08:00
zhaojun
dcadffa265 增加是否缓存数据库的开关 2023-03-05 15:24:27 +08:00
zhaojun
300e58e92c 限制单 IP 直链单位时间内下载次数 2023-03-05 15:23:47 +08:00
zhaojun
96b71e4f8d 增加站点首页 Logo 自定义功能,Logo 支持点击跳转第三方链接。
增加默认排序字段功能,增加分页加载更多功能。
2023-03-05 15:16:55 +08:00
zhaojun
456aabb893 新增多吉云支持 2023-03-05 15:12:26 +08:00
wlplove007
b4d2ca238f fix-bug:本地存储上传文件没有关闭流 2023-03-02 20:45:53 +08:00
zhaojun
2afb841fd9 🔖 发布 4.1.3 版本 2022-11-26 20:02:31 +08:00
zhaojun
2b09812153 💄 更新前端页面 2022-11-26 20:02:18 +08:00
zhaojun
972099a598 优化 logback 日志输出格式及存储方式 2022-11-26 18:14:56 +08:00
zhaojun
9335d78d60 兼容 3.x 版本获取令牌功能 2022-11-26 18:11:50 +08:00
zhaojun
f332cb929b 🐛 修复本地存储不支持上传大小为 0 的文件. 2022-11-26 18:11:26 +08:00
zhaojun
a190a2ec6e 🐛 校验本地存储路径合法性,防止恶意获取上级目录。 2022-11-26 18:11:18 +08:00
zhaojun
5bfa7037cb ✏️ 增加 SharePoint 网站提示文本 2022-11-26 18:10:26 +08:00
zhaojun
4a94c879b8 🐛 修复 referer 防盗链允许为空时,仍然去黑/白名单校验的 bug 2022-11-26 18:10:04 +08:00
zhaojun
38161f96e1 优化微软 OneDrive、SharePoint 相关存储的代码, 更好的输出日志和重构代码。 2022-11-26 18:00:16 +08:00
zhaojun
33c751ab33 🐛 修复密码文件夹不正确的情况下,也显示出了文件夹 readme 的 bug 2022-11-26 17:59:52 +08:00
zhaojun
d12cbd2383 🐛 修复本地存储不支持上传大小为 0 的文件. 2022-11-26 17:59:14 +08:00
zhaojun
5f84becf08 优化存储源初始化方式,增加 name 的注入,以便更全面的日志输出。 2022-11-26 17:58:50 +08:00
zhaojun
432fd89c0f 优化存储源初始化方式,增加 name 的注入,以便更全面的日志输出。 2022-11-26 17:57:26 +08:00
zhaojun
84c8adc9d2 优化存储源初始化方式,增加 name 的注入,以便更全面的日志输出。 2022-11-26 17:56:54 +08:00
zhaojun
ba2523ac8a 重构代码,优化方法名 2022-11-26 17:56:12 +08:00
zhaojun
dcdd25c01f 🐛 修复 referer 防盗链允许为空时,仍然去黑/白名单校验的 bug 2022-11-26 17:55:15 +08:00
zhaojun
278e320550 🐛 修复 Referer 防盗链不生效的 bug 2022-11-26 17:54:43 +08:00
zhaojun
1bfa66cc49 更新 onedrive 使用的 restTemplate 客户端,增加日志记录功能,并移除请求头中写 storageId 来获取 AccessToken 的功能。 2022-11-26 17:54:07 +08:00
zhaojun
20fb2b3baa 🐛 修复本地存储删除失败的 bug 2022-09-24 20:11:03 +08:00
zhaojun
e9a85a4e88 🔖 发布 4.1.2 版本 2022-09-20 18:30:40 +08:00
zhaojun
63edbc11d3 🐛 修复在 Linux 下开启后台登陆图片验证码时,出现异常提示的 BUG 2022-09-20 18:30:22 +08:00
zhaojun
c98c6af0f9 🐛 修复事务中读取到旧缓存的 bug 2022-09-20 18:30:03 +08:00
zhaojun
47f6066733 🔨 重构代码,将捐赠版已验证的功能合并 2022-09-20 18:12:01 +08:00
zhaojun
f4fb471da3 💄 更新前端页面 2022-09-20 18:08:19 +08:00
zhaojun
301b6bdf70 🐛 修复没有 origin 情况下,跨域失效的问题。 2022-09-09 14:36:22 +08:00
zhaojun
a98c4f4c48 📝 文档更新,增加 3d 文件预览图片 2022-08-29 15:32:44 +08:00
zhaojun
f4a625dac6 🔖 发布 4.1.1 版本 2022-08-29 14:56:39 +08:00
zhaojun
1c2f3de55c 更新文档 2022-08-29 14:56:20 +08:00
zhaojun
ed375768f5 Merge remote-tracking branch 'origin/main' 2022-08-29 14:55:07 +08:00
zhaojun
b0c4ed1fad 💄 更新前端页面 2022-08-29 14:54:59 +08:00
zhaojun
8d30ca7eee 🐛 修复通过 nginx 反向代理后, 未登录后台时请求, 重定向到反代前地址的 bug 2022-08-29 14:44:12 +08:00
zhaojun
43aba8e20e 修改系统设置 value 值字段类型为 text, 防止过长 2022-08-29 14:39:08 +08:00
zhaojun
a2fa8b3eeb 🐛 修复没有自动创建数据库目录的 bug 2022-08-29 14:38:47 +08:00
赵俊
0ef77ee11b Update README.md
增加在线预览 Office 文档截图
2022-08-27 09:09:03 +08:00
zhaojun
6608c0b456 Merge remote-tracking branch 'origin/main' 2022-08-26 18:19:04 +08:00
zhaojun
3ceb5c8c1b 🔖 发布 4.1.0 版本 2022-08-26 18:18:44 +08:00
zhaojun
7399c89a8e 批量删除文件和批量删除直链功能 2022-08-26 18:18:19 +08:00
zhaojun
477c9dbdd2 优化代码结构 2022-08-26 18:17:58 +08:00
zhaojun
e8117c7d3b 增加 OnlyOffice 集成 2022-08-26 18:16:53 +08:00
zhaojun
b29ff1e646 增加 Google Drive 支持 2022-08-26 18:16:00 +08:00
zhaojun
774a8e184a 优化 OneDrive/SharePoint 获取令牌相关代码 2022-08-26 18:14:25 +08:00
zhaojun
7da1405798 💄 更新前端页面 2022-08-26 18:12:07 +08:00
赵俊
564770ba3c Update README.md 2022-08-16 18:45:11 +08:00
zhaojun
ac07de4e73 Merge remote-tracking branch 'origin/main' 2022-08-15 17:11:18 +08:00
zhaojun
3933eba99a 🔖 发布 4.0.10版本 2022-08-15 17:10:03 +08:00
zhaojun
27e8f7961e 🔖 发布 4.0.9 版本 2022-08-15 17:08:57 +08:00
zhaojun
55843cbef6 💄 更新前端页面 2022-08-15 16:35:17 +08:00
zhaojun
ebbb33409f 优化 OneDrive/SharePoint 获取 token 体验,增加信息显示,并优化页面效果。 2022-08-15 13:29:35 +08:00
zhaojun
b39360791f 🐛 修复因缓存未清理导致兼容 readme.md 功能修改无效的 bug 2022-08-15 13:28:35 +08:00
zhaojun
9833378e25 🐛 修复 SharePoint 世纪互联自定义 api 失败的 bug 2022-08-15 13:28:10 +08:00
zhaojun
31df2d16e2 🔖 发布 4.0.9 版本 2022-08-11 21:09:06 +08:00
zhaojun
f8f07912a1 🔨 规范代码 2022-08-11 21:06:40 +08:00
zhaojun
edf43954c6 兼容捐赠版数据文件功能 2022-08-11 21:06:16 +08:00
zhaojun
5a816b1dfb 增加 readme.md 兼容模式 2022-08-11 21:05:25 +08:00
zhaojun
910406c33a 增加最大同时上传数限制 2022-08-11 21:05:18 +08:00
zhaojun
543f76ad1d 增加 readme.md 兼容模式 2022-08-11 21:04:45 +08:00
zhaojun
1cc2d874a1 完善 OneDrive SharePoint 自定义 api 体验 2022-08-11 21:03:11 +08:00
zhaojun
18de372bf9 🐛 修复批量删除直链或直链下载日志数量过多时, 无法正常删除的问题。 2022-08-11 21:02:07 +08:00
zhaojun
5748644814 🐛 修复批量删除直链或直链下载日志数量过多时, 无法正常删除的问题。 2022-08-11 21:02:02 +08:00
zhaojun
fb8060d316 💄 更新前端页面 2022-08-11 21:01:41 +08:00
zhaojun
a4005defe2 更新 github issue 模板 2022-08-05 16:02:34 +08:00
zhaojun
4c90d5bdda 🔖 发布 4.0.8 版本 2022-08-05 15:30:13 +08:00
zhaojun
fa46850bb4 💄 更新前端页面 2022-08-05 15:29:08 +08:00
zhaojun
a7551fce53 完善代码结构 2022-08-05 15:28:52 +08:00
zhaojun
0591669ec9 增加获取单个文件接口请求参数 2022-08-05 15:28:33 +08:00
zhaojun
6286a9e9aa ✏️ 优化提示信息 2022-08-05 15:28:24 +08:00
zhaojun
c67ee1e89d 增加获取单个文件接口 2022-08-05 15:27:58 +08:00
zhaojun
c75695c63a 🐛 修复因类调用自身方法导致事务启用失败的 bug 2022-08-05 15:27:38 +08:00
zhaojun
8f771c652d ✏️ 优化 minio 提示信息 2022-08-05 15:25:52 +08:00
zhaojun
d6e13d6115 🐛 修复获取 m3u8 直链后系统报错的 bug. 2022-08-05 15:25:09 +08:00
zhaojun
8460d17b07 🔨 移动无用代码 2022-08-05 15:24:46 +08:00
zhaojun
49806221b4 💡 增加代码注释 2022-08-05 15:23:36 +08:00
zhaojun
409af8409d 🔨 重构代码,修改包名 2022-08-05 15:22:36 +08:00
zhaojun
6647efeb03 📝 完善 OneDrive SharePoint 反代域名参数的描述信息 2022-08-02 10:25:22 +08:00
zhaojun
c8bd52e26a 统一 sftp、ftp、webdav 下载文件 contentType 为 application/octet-stream, 避免浏览器自动进行默认预览动作 2022-08-01 15:20:06 +08:00
zhaojun
27db6ed14a 🐛 修复本地存储下载时,错误的 contentType 类型导致无法正常下载的问题 2022-08-01 15:17:29 +08:00
zhaojun
b079d03753 🔖 发布 4.0.7 版本 2022-07-31 15:07:11 +08:00
zhaojun
edf8e114ad 💄 更新前端页面 2022-07-31 15:06:55 +08:00
zhaojun
67a84edd4d 优化 S3 协议自动配置跨域逻辑,改为不覆盖原有配置。且增加 GET 跨域,对于在线预览文本、视频场景提供跨域支持. 2022-07-30 20:01:41 +08:00
zhaojun
d798750ee6 优化 S3 协议自动配置跨域逻辑,改为不覆盖原有配置,修改描述. 2022-07-30 20:00:18 +08:00
zhaojun
fea1da86fc 🐛 修复 minio 设置跨域上传报错的 bug 2022-07-30 18:45:19 +08:00
zhaojun
96a0c90600 ⬆️ 更新 pom 依赖,去除无用依赖,升级有漏洞的依赖,划分相关的依赖 2022-07-29 16:50:08 +08:00
赵俊
a054e82740 Merge pull request #404 from wswm152/main
解除对过期内容的引用
2022-07-29 16:19:08 +08:00
无始无名
dc0e84e1e3 解除对过期内容的引用
部分内容在jdk8以上版本中已被去除
2022-07-29 15:05:34 +08:00
789 changed files with 14318 additions and 8217 deletions

View File

@@ -6,43 +6,20 @@ labels: 'bug'
assignees: ''
---
<!--
你好!感谢你正在考虑为 ZFile 提交一个 bug。请花一点点时间尽量详细地回答以下基础问题。
为了帮助我们更好的解决您的问题,请填写以下选项(不填写完整可能会被直接关闭 issue
谢谢!
-->
<!--
请确认你已经做了下面这些事情,若 bug 还是未解决,那么请尽可详细地描述你的问题。
- 我已经安装了最新版的 ZFile
- 我已经阅读了 ZFile 的文档https://docs.zfile.vip
- 我已经搜索了已有的 Issues 列表中有关的信息
- 我已经清理过浏览器缓存并重试
-->
## 我的环境
<!--
请登录到管理后台,点击左侧系统监控, 复制或截图此页内容.
-->
## 错误日志
<!--
请登录到管理后台,点击左侧系统监控, 点击右上角诊断日志下载, 然后上传到此 Issue 中.
-->
## 期望行为
<!--
你期望会发生什么?
-->
## 当前行为
<!--
描述 bug 细节,确认出现此问题的复现步骤,例如点击了哪里,发生了什么情况?
你可以粘贴截图或附件。
-->
- 是否已搜索其他 issue没有人提过这个问题
- 是否已查阅、搜索 [ZFile 文档](https://docs.zfile.vip),尤其是常见问题页,仍然未解决?:
- 当前 ZFile 版本:
- 是否尝试最新版是否已解决此问题:
- 是否尝试重启 ZFile且问题依旧存在
- 是否已尝试清空浏览器缓存,且问题依旧存在?:
- 操作系统(如 Windows、Mac、iOS、安卓
- 浏览器(如 Chrome、Firefox、SafariX 浏览器):
- 做什么操作提示的错误?:
- 期望行为(应该是什么样的结果):
- 当前行为(当前是什么样的结果):
- 错误日志(可选):
- 复现步骤(可选):
- 您的额外信息(可选):

View File

@@ -7,28 +7,11 @@ assignees: ''
---
<!--
你好!感谢你愿意考虑希望 ZFile 增加某个新功能。请花一点点时间尽量详细地回答以下基础问题。
谢谢!
-->
为了帮助我们更好的解决您的问题,请填写以下选项(不填写完整可能会被直接关闭 issue
## 概述
<!--
对这个新功能的一段描述
-->
## 动机
<!--
为什么你希望在 ZFile 中使用这个功能?
-->
## 详细解释
<!--
详细描述这个新功能。
如果这是一个小功能,你可以忽略这部分。
-->
- 是否已搜索其他 issue没有人提过这个功能
- 是否已尝试使用最新版本,且仍然没有此功能?:
- 功能概述:
- 功能动机:
- 详细解释(可选):

View File

@@ -1,14 +0,0 @@
---
name: Question
about: 对 ZFile 有任何问题吗?
title: ''
labels: 'question'
assignees: ''
---
<!--
如果你有任何问题也可以通过此渠道来向我们反馈。
谢谢!
-->

226
README.md
View File

@@ -1,7 +1,8 @@
<p style="text-align: center">
<img alt="ZFile" src="https://cdn.jun6.net/2021/04/21/69a89344e2a84.png" height="150px">
<br><br>
基于 Java 的在线网盘程序,支持对接 S3、OneDrive、SharePoint、又拍云、本地存储、FTP、SFTP 等存储源,支持在线浏览图片、播放音视频,文本文件等文件类型。
<p align="center">
![zfile](https://cdn.jun6.net/uPic/2022/09/04/zfile-header.png)
基于 Java 的在线网盘程序,支持对接 S3、OneDrive、SharePoint、Google Drive、多吉云、又拍云、本地存储、FTP、SFTP 等存储源支持在线浏览图片、播放音视频文本文件、Office、obj3d等文件类型。
<br><br>
<img src="https://img.shields.io/badge/license-MIT-blue.svg?longCache=true&style=flat-square" alt="license">
<img src="https://api.codacy.com/project/badge/Grade/70b793267f7941d58cbd93f50c9a8e0a" alt="codady">
@@ -17,185 +18,48 @@
<img src="https://img.shields.io/github/watchers/zhaojun1998/zfile?style=flat-square" alt="watchers">
</p>
## 相关地址
预览地址: [https://zfile.vip](https://zfile.vip)
文档地址: [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)
前端源码: [https://github.com/zhaojun1998/zfile-vue](https://github.com/zhaojun1998/zfile-vue)
## 系统特色
* 支持文件操作:上传, 删除, 重命名, 新建文件夹. 后续还会支持移动和复制文件(详见下方**后续计划**.
* 操作系统级的文件操作体验
1. 支持拖拽上传和 Ctrl + V 粘贴上传文件和文件夹
2. 支持 Ctrl + A 全选文件, 按 Esc 取消全选.
3. 支持拖拽批量选择文件
4. 支持按住 Shift 多选文件
5. 支持多选文件后按 Delete 键删除文件.
6. 按 Backspace 返回上级文件夹.
* 全新的 UI 风格, 更简洁易用.
* 支持给文件生成直链(短链,永久直链,二维码)
* 视频播放器支持调用本地软件进行下载如迅雷、Motrix. 支持调用本地播放器播放,更好的进行视频解码: PotPlayer IINA, VLC, nPlayer, MXPlayer(Free/Pro)
* 全新画廊模式, 支持按照瀑布流显示图片, 支持自定义 N 栏, 自定义每栏的间距
* 支持给文件夹配置 markdown 文档, 并配置显示方式, 如顶部、底部、弹窗
* 支持给文件夹设置密码
* 支持隐藏文件或文件夹
* 后台登录支持设置图片验证码和 2FA 身份认证,防止后台被暴力破解
* 支持自定义文件格式后缀, 避免系统内置的不完善导致文件无法预览.
* Docker 支持
* 自定义 JS, CSS
* 同时挂载多个存储策略
* 支持 S3 协议, 阿里云 OSS, FTP, SFTP, 华为云 OBS, 本地存储, MINIO, OneDrive 国际/家庭/个人版/世纪互联版/SharePoint, , 七牛云 KODO, 腾讯云 COS, 又拍云 USS.
## 快速开始
<details>
<summary>普通安装 (点击展开)</summary>
安装依赖环境:
请参考部署文档: [https://docs.zfile.vip](https://docs.zfile.vip)
```bash
## 在线体验
# CentOS系统
yum install -y java-1.8.0-openjdk unzip
```
[https://demo.zfile.vip](https://demo.zfile.vip)
```bash
# Debian 9 / Ubuntu 14+
apt update
apt install -y openjdk-8-jre-headless unzip
```
## 功能预览
```bash
# Debian 10 (Buster) 系统
apt update && apt install -y apt-transport-https software-properties-common ca-certificates dirmngr gnupg
wget -qO - https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public | apt-key add -
add-apt-repository --yes https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/
apt update && apt install -y adoptopenjdk-8-hotspot-jre
```
下载项目:
```bash
export ZFILE_INSTALL_PATH=~/zfile
mkdir -p $ZFILE_INSTALL_PATH && cd $ZFILE_INSTALL_PATH
wget https://c.jun6.net/ZFILE/zfile-release.war
unzip zfile-release.war && rm -rf zfile-release.war
chmod +x $ZFILE_INSTALL_PATH/bin/*.sh
```
启动项目:
```bash
~/zfile/bin/start.sh
```
</details>
---
<details>
<summary>Docker (点击展开)</summary>
```bash
docker run -d --name=zfile --restart=always \
-p 8080:8080 \
-v /root/zfile/db:/root/.zfile-v4/db \
-v /root/zfile/logs:/root/.zfile-v4/logs \
zhaojun1998/zfile
```
> 如需映射配置文件则需要先在宿主机下载配置文件,然后映射到容器内:
```bash
# 下载到 application.properties 文件到 /root 目录下, 此目录可自行更改, 如:
curl -o /root/application.properties https://c.jun6.net/ZFILE/application.properties
# 然后增加一个 -v 参数,将此源文件映射到容器内(如修改宿主机的 application.properties 为其他路径, 则下面命令也要一起修改),如:
docker run -d --name=zfile --restart=always \
-p 8080:8080 \
-v /root/zfile/db:/root/.zfile-v4/db \
-v /root/zfile/logs:/root/.zfile-v4/logs \
-v /root/application.properties:/root/application.properties \
zhaojun1998/zfile
```
</details>
---
<details>
<summary>Docker Compose (点击展开)</summary>
```yml
version: '3.3'
services:
zfile:
container_name: zfile
restart: always
ports:
- '8080:8080'
volumes:
- '/root/zfile/db:/root/.zfile-v4/db'
- '/root/zfile/logs:/root/.zfile-v4/logs'
image: zhaojun1998/zfile
```
> 如需映射配置文件则需要先在宿主机下载配置文件,然后映射到容器内:
下载到 application.properties 文件到 /root 目录下, 此目录可自行更改, 命令如:
```bash
curl -o /root/application.properties https://c.jun6.net/ZFILE/application.properties
```
> 然后增加一个 -v 参数,将此源文件映射到容器内(如修改宿主机的 application.properties 为其他路径, 则下面命令也要一起修改), 如:
```yml
version: '3.3'
services:
zfile:
container_name: zfile
restart: always
ports:
- '8080:8080'
volumes:
- '/root/zfile/db:/root/.zfile-v4/db'
- '/root/zfile/logs:/root/.zfile-v4/logs'
- '/root/application.properties:/root/application.properties'
image: zhaojun1998/zfile
```
</details>
---
篇幅有限, 更详细的安装教程及介绍请参考: [ZFile 文档](https://docs.zfile.vip)
## 预览
![前台首页](https://cdn.jun6.net/uPic/2022/07/11/eJU1B5.png)
![前台设置](https://cdn.jun6.net/uPic/2022/07/11/Y0fK7b.png)
![图片预览](https://cdn.jun6.net/uPic/2022/07/12/BP8wog.png)
![视频预览](https://cdn.jun6.net/uPic/2022/07/11/MsubMr.png)
![文本预览](https://cdn.jun6.net/2021/03/23/b00efdfb4892e.png)
![音频预览](https://cdn.jun6.net/uPic/2022/07/11/7U5IoK.png)
![管理登录](https://cdn.jun6.net/uPic/2022/07/11/U2XKcg.png)
![后台设置-站点设置](https://cdn.jun6.net/uPic/2022/07/11/9zsedD.png)
![后台设置-驱动器列表](https://cdn.jun6.net/uPic/2022/07/11/y2pFa1.png)
![后台设置-新增驱动器](https://cdn.jun6.net/uPic/2022/07/11/I1NWzF.png)
### 文件列表
![文件列表](https://cdn.jun6.net/uPic/2022/08/13/0urMn8.png)
### 画廊模式
![图片预览](https://cdn.jun6.net/uPic/2022/08/13/d2J9aE.png)
### 视频预览
![视频预览](https://cdn.jun6.net/uPic/2022/08/13/tBX00R.png)
### 文本预览
![文本预览](https://cdn.jun6.net/uPic/2022/08/13/7dDy4G.png)
### 音频预览
![音频预览](https://cdn.jun6.net/uPic/2022/08/13/N5bU1R.png)
### PDF 预览
![PDF 预览](https://cdn.jun6.net/uPic/2022/08/13/H327bV.png)
### Office 预览
![Office 预览](https://cdn.jun6.net/uPic/2022/08/27/RxeiqI.png)
### 3d 文件预览
![3d 文件预览](https://cdn.jun6.net/uPic/2022/08/29/8iszyh.png)
### 生成直链
![生成直链](https://cdn.jun6.net/uPic/2022/08/13/zCX3xT.jpg)
### 页面设置
![页面设置](https://cdn.jun6.net/uPic/2022/08/13/54nYv2.png)
### 后台设置-登录
![后台设置-登录](https://cdn.jun6.net/uPic/2022/08/13/J8P2Zf.png)
### 后台设置-存储源列表
![后台设置-存储源列表](https://cdn.jun6.net/uPic/2022/08/13/jymieO.png)
### 后台设置-存储源权限控制
![后台设置-存储源权限控制](https://cdn.jun6.net/uPic/2022/08/13/JgiwkH.jpg)
### 后台设置-添加存储源(本地存储)
![后台设置-添加存储源(本地存储)](https://cdn.jun6.net/uPic/2022/08/13/add-storage.png)
### 后台设置-添加存储源(世纪互联)
![后台设置-添加存储源(世纪互联)](https://cdn.jun6.net/uPic/2022/08/13/add-storage2.png)
### 后台设置-显示设置
![后台设置-显示设置](https://cdn.jun6.net/uPic/2022/08/13/view-setting.png)
## 支持作者
@@ -204,10 +68,10 @@ services:
<img src="https://cdn.jun6.net/2021/03/27/152704e91f13d.png" width="400" alt="赞助我">
## Stargazers over time
## Status
[![starcharts stargazers over time](https://starchart.cc/zhaojun1998/zfile.svg)](https://starchart.cc/zhaojun1998/zfile.svg)
![Alt](https://repobeats.axiom.co/api/embed/580333f83b91087e713f15497e6433c50e1da090.svg "Repobeats analytics image")
## 开发工具赞助
## Star History
<a href="https://www.jetbrains.com/?from=zfile"><img src="https://cdn.jun6.net/2021/04/21/26e410d60b0b0.png?1=1" width="100px"></a>
[![Star History Chart](https://api.star-history.com/svg?repos=zfile-dev/zfile&type=Date)](https://star-history.com/#zfile-dev/zfile&Date)

214
pom.xml
View File

@@ -5,21 +5,20 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<version>2.6.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>im.zhaojun</groupId>
<artifactId>zfile</artifactId>
<version>4.0.6</version>
<version>4.1.4</version>
<name>zfile</name>
<packaging>jar</packaging>
<packaging>war</packaging>
<description>一个在线的文件浏览系统</description>
<properties>
<java.version>1.8</java.version>
<log4j2.version>2.17.0</log4j2.version>
<org.mapstruct.version>1.5.1.Final</org.mapstruct.version>
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
</properties>
<dependencies>
@@ -41,47 +40,94 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 数据库相关 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
<version>8.0.31</version>
<scope>runtime</scope>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.3</version>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>7.15.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- 存储策略相关 API, 对象存储、FTP、 Rest API-->
<dependency>
<groupId>com.upyun</groupId>
<artifactId>java-sdk</artifactId>
<version>4.1.3</version>
<version>4.2.3</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.146</version>
<version>1.12.406</version>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.11.0</version>
<version>7.12.0</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<dependency>
<groupId>com.github.lookfirst</groupId>
<artifactId>sardine</artifactId>
<version>5.10</version>
</dependency>
<!-- 登陆/权限相关 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.33.0</version>
</dependency>
<!-- 文档相关 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.10</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
@@ -90,75 +136,22 @@
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.8.0</version>
<version>3.9.0</version>
</dependency>
<!-- 其他工具类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.mpatric</groupId>
<artifactId>mp3agic</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
<version>1.2.83_noneautotype</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>7.15.0</version>
</dependency>
<!-- 第三方依赖-权限相关 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.30.0</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
@@ -169,43 +162,35 @@
<artifactId>commons-chain</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<dependency>
<groupId>com.github.lookfirst</groupId>
<artifactId>sardine</artifactId>
<version>5.10</version>
</dependency>
<dependency>
<groupId>dev.samstevens.totp</groupId>
<artifactId>totp-spring-boot-starter</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>com.beust</groupId>
<artifactId>jcommander</artifactId>
<version>1.82</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20200518</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
</dependencies>
<build>
@@ -252,27 +237,28 @@
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
</plugin>
<!--<plugin>-->
<!-- <groupId>com.uyoqu.framework</groupId>-->
<!-- <artifactId>maven-plugin-starter</artifactId>-->
<!-- <version>1.0.0</version>-->
<!-- <executions>-->
<!-- <execution>-->
<!-- <phase>package</phase>-->
<!-- <goals>-->
<!-- <goal>bin</goal>-->
<!-- </goals>-->
<!-- </execution>-->
<!-- </executions>-->
<!-- <configuration>-->
<!-- <jvms>-->
<!-- <jvm>-Djava.security.egd=file:/dev/./urandom</jvm>-->
<!-- <jvm>-Dfile.encoding=utf-8</jvm>-->
<!-- <jvm>-Djava.net.preferIPv4Stack=false</jvm>-->
<!-- <jvm>-Djava.net.preferIPv4Addresses=true</jvm>-->
<!-- </jvms>-->
<!-- </configuration>-->
<!--</plugin>-->
<plugin>
<groupId>com.uyoqu.framework</groupId>
<artifactId>maven-plugin-starter</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>bin</goal>
</goals>
</execution>
</executions>
<configuration>
<jvms>
<jvm>-Djava.security.egd=file:/dev/./urandom</jvm>
<jvm>-Dfile.encoding=utf-8</jvm>
<jvm>-Djava.net.preferIPv4Stack=false</jvm>
<jvm>-Djava.net.preferIPv4Addresses=true</jvm>
<jvm>-Djava.awt.headless=true</jvm>
</jvms>
</configuration>
</plugin>
</plugins>

View File

@@ -1,43 +1,23 @@
package im.zhaojun.zfile;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import javax.annotation.PostConstruct;
/**
* @author zhaojun
*/
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
@ServletComponentScan(basePackages = "im.zhaojun.zfile.*.filter")
@ServletComponentScan(basePackages = {"im.zhaojun.zfile.core.filter", "im.zhaojun.zfile.module.storage.filter"})
@ComponentScan(basePackages = "im.zhaojun.zfile.*")
public class ZfileApplication {
public static void main(String[] args) {
SpringApplication.run(ZfileApplication.class, args);
}
@Value("${spring.datasource.driver-class-name}")
private String datasourceDriveClassName;
@Value("${spring.datasource.url}")
private String datasourceUrl;
@PostConstruct
public void init() {
if (StrUtil.equals(datasourceDriveClassName, "org.sqlite.JDBC")) {
String path = datasourceUrl.replace("jdbc:sqlite:", "");
String folderPath = FileUtil.getParent(path, 1);
if (!FileUtil.exist(folderPath)) {
FileUtil.mkdir(folderPath);
}
}
}
}

View File

@@ -1,108 +0,0 @@
package im.zhaojun.zfile.admin.controller;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import im.zhaojun.zfile.home.model.dto.CacheInfoDTO;
import im.zhaojun.zfile.admin.service.StorageSourceService;
import im.zhaojun.zfile.common.util.AjaxJson;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 存储源缓存维护接口
*
* @author zhaojun
*/
@RestController
@Api(tags = "存储源模块-缓存")
@ApiSort(5)
@RequestMapping("/admin/cache")
public class CacheController {
@Resource
private StorageSourceService storageSourceService;
@ApiOperationSupport(order = 1)
@ApiOperation(value = "启用存储源缓存", notes = "开启缓存后N 秒内重复请求相同文件夹,不会重复调用 API。" +
"参数 N 在配置文件中设置 {zfile.cache.timeout},默认为 1800 秒。")
@ApiImplicitParam(paramType = "path", name = "storageId", value = "存储源 id", required = true)
@PostMapping("/{storageId}/enable")
public AjaxJson<Void> enableCache(@PathVariable("storageId") Integer storageId) {
storageSourceService.updateCacheStatus(storageId, true);
return AjaxJson.getSuccess();
}
@ApiOperationSupport(order = 2)
@ApiOperation(value = "禁用存储源缓存")
@ApiImplicitParam(paramType = "path", name = "storageId", value = "存储源 id", required = true)
@PostMapping("/{storageId}/disable")
public AjaxJson<Void> disableCache(@PathVariable("storageId") Integer storageId) {
storageSourceService.updateCacheStatus(storageId, false);
return AjaxJson.getSuccess();
}
@ApiOperationSupport(order = 3)
@ApiOperation(value = "查看存储源缓存", notes = "可查看存储源缓存的所有目录路径")
@ApiImplicitParam(paramType = "path", name = "storageId", value = "存储源 id", required = true)
@GetMapping("/{storageId}/info")
public AjaxJson<CacheInfoDTO> cacheInfo(@PathVariable("storageId") Integer storageId) {
CacheInfoDTO cacheInfo = storageSourceService.findCacheInfo(storageId);
return AjaxJson.getSuccessData(cacheInfo);
}
@ApiOperationSupport(order = 4)
@ApiOperation(value = "刷新存储源缓存", notes = "刷新存储源缓存路径,系统会重新预热此路径的内容")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", name = "storageId", value = "存储源 id", required = true),
@ApiImplicitParam(paramType = "body", name = "key", value = "缓存 key", required = true)
})
@PostMapping("/{storageId}/refresh")
public AjaxJson<Void> refreshCache(@PathVariable("storageId") Integer storageId, String key) throws Exception {
storageSourceService.refreshCache(storageId, key);
return AjaxJson.getSuccess();
}
@ApiOperationSupport(order = 5)
@ApiOperation(value = "开启缓存自动刷新", notes = "开启后每隔 N 秒检测到期的缓存, 对于过期缓存尝试调用 API, 重新写入缓存." +
"参数 N 在配置文件中设置 {zfile.cache.auto-refresh-interval},默认为 5 秒。")
@ApiImplicitParam(paramType = "path", name = "storageId", value = "存储源 id", required = true)
@PostMapping("/{storageId}/auto-refresh/start")
public AjaxJson<Void> enableAutoRefresh(@PathVariable("storageId") Integer storageId) {
storageSourceService.startAutoCacheRefresh(storageId);
return AjaxJson.getSuccess();
}
@ApiOperationSupport(order = 5)
@ApiOperation(value = "关闭缓存自动刷新")
@ApiImplicitParam(paramType = "path", name = "storageId", value = "存储源 id", required = true)
@PostMapping("/{storageId}/auto-refresh/stop")
public AjaxJson<Void> disableAutoRefresh(@PathVariable("storageId") Integer storageId) {
storageSourceService.stopAutoCacheRefresh(storageId);
return AjaxJson.getSuccess();
}
@ApiOperationSupport(order = 6)
@ApiOperation(value = "清空缓存")
@ApiImplicitParam(paramType = "path", name = "storageId", value = "存储源 id", required = true)
@PostMapping("/{storageId}/clear")
public AjaxJson<Void> clearCache(@PathVariable("storageId") Integer storageId) {
storageSourceService.clearCache(storageId);
return AjaxJson.getSuccess();
}
}

View File

@@ -1,147 +0,0 @@
package im.zhaojun.zfile.admin.controller.link;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import im.zhaojun.zfile.admin.convert.DownloadLogConvert;
import im.zhaojun.zfile.admin.model.entity.DownloadLog;
import im.zhaojun.zfile.admin.model.entity.StorageSource;
import im.zhaojun.zfile.admin.model.request.link.QueryDownloadLogRequest;
import im.zhaojun.zfile.admin.model.result.link.DownloadLogResult;
import im.zhaojun.zfile.admin.service.DownloadLogService;
import im.zhaojun.zfile.admin.service.StorageSourceService;
import im.zhaojun.zfile.common.util.AjaxJson;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
/**
* 直链下载日志接口
*
* @author zhaojun
*/
@Api(tags = "直链日志管理")
@ApiSort(7)
@Controller
@RequestMapping("/admin/download/log")
public class DownloadLogManagerController {
@Resource
private StorageSourceService storageSourceService;
@Resource
private DownloadLogConvert downloadLogConvert;
@Resource
private DownloadLogService downloadLogService;
@ApiOperationSupport(order = 1)
@GetMapping("/list")
@ApiOperation(value = "直链下载日志")
@ResponseBody
@ApiImplicitParams({
@ApiImplicitParam(paramType = "query", name = "key", value = "直链 key"),
@ApiImplicitParam(paramType = "query", name = "page", value = "分页页数"),
@ApiImplicitParam(paramType = "query", name = "limit", value = "每页条数"),
@ApiImplicitParam(paramType = "query", name = "orderBy", defaultValue = "createDate", value = "排序字段"),
@ApiImplicitParam(paramType = "query", name = "orderDirection", defaultValue = "desc", value = "排序顺序")
})
public AjaxJson<?> list(QueryDownloadLogRequest queryDownloadLogRequest,
@RequestParam(required = false, defaultValue = "create_time") String orderBy,
@RequestParam(required = false, defaultValue = "desc") String orderDirection) {
Page<DownloadLog> pages = Page.of(queryDownloadLogRequest.getPage(), queryDownloadLogRequest.getLimit());
boolean asc = Objects.equals(orderDirection, "asc");
pages.addOrder(new OrderItem(orderBy, asc));
DownloadLog downloadLog = new DownloadLog();
QueryWrapper<DownloadLog> queryWrapper = new QueryWrapper<>(downloadLog);
if (StrUtil.isNotEmpty(queryDownloadLogRequest.getStorageKey())) {
queryWrapper.eq("storage_key", queryDownloadLogRequest.getStorageKey());
}
if (StrUtil.isNotEmpty(queryDownloadLogRequest.getPath())) {
queryWrapper.like("path", queryDownloadLogRequest.getPath());
}
if (StrUtil.isNotEmpty(queryDownloadLogRequest.getShortKey())) {
queryWrapper.like("short_key", queryDownloadLogRequest.getShortKey());
}
if (StrUtil.isNotEmpty(queryDownloadLogRequest.getIp())) {
queryWrapper.like("ip", queryDownloadLogRequest.getIp());
}
if (StrUtil.isNotEmpty(queryDownloadLogRequest.getReferer())) {
queryWrapper.like("referer", queryDownloadLogRequest.getReferer());
}
if (StrUtil.isNotEmpty(queryDownloadLogRequest.getUserAgent())) {
queryWrapper.like("user_agent", queryDownloadLogRequest.getUserAgent());
}
if (ObjectUtil.isNotEmpty(queryDownloadLogRequest.getDateFrom())) {
queryWrapper.ge("create_time", queryDownloadLogRequest.getDateFrom());
}
if (ObjectUtil.isNotEmpty(queryDownloadLogRequest.getDateTo())) {
queryWrapper.le("create_time", queryDownloadLogRequest.getDateFrom());
}
Page<DownloadLog> selectResult = downloadLogService.page(pages, queryWrapper);
long total = selectResult.getTotal();
List<DownloadLog> records = selectResult.getRecords();
Map<String, StorageSource> cache = new HashMap<>();
Stream<DownloadLogResult> shortLinkResultList = records.stream().map(model -> {
String storageKey = model.getStorageKey();
StorageSource storageSource;
if (cache.containsKey(storageKey)) {
storageSource = cache.get(storageKey);
} else {
storageSource = storageSourceService.findByStorageKey(storageKey);
cache.put(storageKey, storageSource);
}
return downloadLogConvert.entityToResultList(model, storageSource);
});
return AjaxJson.getPageData(total, shortLinkResultList);
}
@ApiOperationSupport(order = 2)
@DeleteMapping("/delete/{id}")
@ApiOperation(value = "删除直链")
@ApiImplicitParam(paramType = "path", name = "id", value = "直链 id", required = true)
@ResponseBody
public AjaxJson<Void> deleteById(@PathVariable Integer id) {
downloadLogService.removeById(id);
return AjaxJson.getSuccess();
}
@ApiOperationSupport(order = 3)
@DeleteMapping("/delete/batch")
@ResponseBody
@ApiImplicitParam(paramType = "query", name = "ids", value = "直链 id", required = true)
@ApiOperation(value = "批量删除直链")
public AjaxJson<Void> batchDelete(@RequestParam("id[]") Integer[] ids) {
downloadLogService.removeBatchByIds(Arrays.asList(ids));
return AjaxJson.getSuccess();
}
}

View File

@@ -1,141 +0,0 @@
package im.zhaojun.zfile.admin.controller.link;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import im.zhaojun.zfile.admin.convert.ShortLinkConvert;
import im.zhaojun.zfile.admin.model.entity.ShortLink;
import im.zhaojun.zfile.admin.model.entity.StorageSource;
import im.zhaojun.zfile.admin.model.result.link.ShortLinkResult;
import im.zhaojun.zfile.admin.service.ShortLinkService;
import im.zhaojun.zfile.admin.service.StorageSourceService;
import im.zhaojun.zfile.common.util.AjaxJson;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
/**
* 直链管理接口
*
* @author zhaojun
*/
@Api(tags = "直链管理")
@ApiSort(7)
@Controller
@RequestMapping("/admin")
public class ShortLinkManagerController {
@Resource
private ShortLinkService shortLinkService;
@Resource
private StorageSourceService storageSourceService;
@Resource
private ShortLinkConvert shortLinkConvert;
@ApiOperationSupport(order = 1)
@GetMapping("/link/list")
@ApiOperation(value = "搜索短链")
@ResponseBody
@ApiImplicitParams({
@ApiImplicitParam(paramType = "query", name = "key", value = "短链 key"),
@ApiImplicitParam(paramType = "query", name = "storageId", value = "存储源 ID"),
@ApiImplicitParam(paramType = "query", name = "url", value = "短链 url"),
@ApiImplicitParam(paramType = "query", name = "dateFrom", value = "短链创建时间从"),
@ApiImplicitParam(paramType = "query", name = "dateTo", value = "短链创建时间至"),
@ApiImplicitParam(paramType = "query", name = "page", value = "分页页数"),
@ApiImplicitParam(paramType = "query", name = "limit", value = "每页条数"),
@ApiImplicitParam(paramType = "query", name = "orderBy", defaultValue = "createDate", value = "排序字段"),
@ApiImplicitParam(paramType = "query", name = "orderDirection", defaultValue = "desc", value = "排序顺序")
})
public AjaxJson<?> list(String key, String storageId,
String url,
String dateFrom,
String dateTo,
Integer page,
Integer limit,
@RequestParam(required = false, defaultValue = "create_date") String orderBy,
@RequestParam(required = false, defaultValue = "desc") String orderDirection) {
Page<ShortLink> pages = Page.of(page, limit);
boolean asc = Objects.equals(orderDirection, "asc");
pages.addOrder(new OrderItem(orderBy, asc));
QueryWrapper<ShortLink> queryWrapper = new QueryWrapper<>(new ShortLink());
if (StrUtil.isNotEmpty(storageId)) {
queryWrapper.eq("storage_id", storageId);
}
if (StrUtil.isNotEmpty(key)) {
queryWrapper.like("short_key", key);
}
if (StrUtil.isNotEmpty(url)) {
queryWrapper.like("url", url);
}
if (StrUtil.isNotEmpty(dateFrom)) {
queryWrapper.ge("create_date", dateFrom);
}
if (StrUtil.isNotEmpty(dateTo)) {
queryWrapper.le("create_date", dateTo);
}
Page<ShortLink> selectResult = shortLinkService.page(pages, queryWrapper);
long total = selectResult.getTotal();
List<ShortLink> records = selectResult.getRecords();
Map<Integer, StorageSource> cache = new HashMap<>();
Stream<ShortLinkResult> shortLinkResultList = records.stream().map(shortLink -> {
Integer shortLinkStorageId = shortLink.getStorageId();
StorageSource storageSource;
if (cache.containsKey(shortLinkStorageId)) {
storageSource = cache.get(shortLinkStorageId);
} else {
storageSource = storageSourceService.findById(shortLinkStorageId);
cache.put(shortLinkStorageId, storageSource);
}
return shortLinkConvert.entityToResultList(shortLink, storageSource);
});
return AjaxJson.getPageData(total, shortLinkResultList);
}
@ApiOperationSupport(order = 2)
@DeleteMapping("/link/delete/{id}")
@ApiOperation(value = "删除短链")
@ApiImplicitParam(paramType = "path", name = "id", value = "短链 id", required = true)
@ResponseBody
public AjaxJson<Void> deleteById(@PathVariable Integer id) {
shortLinkService.removeById(id);
return AjaxJson.getSuccess();
}
@ApiOperationSupport(order = 3)
@DeleteMapping("/link/delete/batch")
@ResponseBody
@ApiImplicitParam(paramType = "query", name = "ids", value = "短链 id", required = true)
@ApiOperation(value = "批量删除短链")
public AjaxJson<Void> batchDelete(@RequestParam("id[]") Integer[] ids) {
shortLinkService.removeBatchByIds(Arrays.asList(ids));
return AjaxJson.getSuccess();
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,22 +0,0 @@
package im.zhaojun.zfile.admin.exception;
import lombok.Getter;
/**
* 禁止的文件操作异常
*
* @author zhaojun
*/
@Getter
public class ForbidFileOperationException extends RuntimeException {
private final Integer storageId;
private final String action;
public ForbidFileOperationException(Integer storageId, String action) {
this.storageId = storageId;
this.action = action;
}
}

View File

@@ -1,15 +0,0 @@
package im.zhaojun.zfile.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import im.zhaojun.zfile.admin.model.entity.DownloadLog;
import org.apache.ibatis.annotations.Mapper;
/**
* 下载日志 Mapper 接口
*
* @author zhaojun
*/
@Mapper
public interface DownloadLogMapper extends BaseMapper<DownloadLog> {
}

View File

@@ -1,20 +0,0 @@
package im.zhaojun.zfile.admin.model.dto;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
/**
* OneDrive Token DTO
*
* @author zhaojun
*/
@Data
public class OneDriveToken {
@JSONField(name = "access_token")
private String accessToken;
@JSONField(name = "refresh_token")
private String refreshToken;
}

View File

@@ -1,21 +0,0 @@
package im.zhaojun.zfile.admin.model.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 缓存对象,用户表示那个存储源的那个文件夹.
*
* @author zhaojun
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StorageSourceCacheKey {
private Integer storageId;
private String key;
}

View File

@@ -1,5 +0,0 @@
package im.zhaojun.zfile.admin.model.param;
public interface IStorageParam {
}

View File

@@ -1,20 +0,0 @@
package im.zhaojun.zfile.admin.model.param;
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
import lombok.Getter;
/**
* MinIO 初始化参数
*
* @author zhaojun
*/
@Getter
public class MinIOParam extends S3BaseParam {
@StorageParamItem(name = "地域")
private String region;
@StorageParamItem(name = "服务地址", description = "为 minio 的服务地址,非 web 访问地址,一般为 http://ip:9000")
private String endPoint;
}

View File

@@ -1,17 +0,0 @@
package im.zhaojun.zfile.admin.model.param;
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
import lombok.Getter;
/**
* 腾讯云初始化参数
*
* @author zhaojun
*/
@Getter
public class TencentParam extends S3BaseParam {
@StorageParamItem(key = "secretId", name = "SecretId", order = 1)
private String accessKey;
}

View File

@@ -1,56 +0,0 @@
package im.zhaojun.zfile.admin.model.verify;
import lombok.Data;
/**
* 用于表示校验结果的类
*
* @author zhaojun
*/
@Data
public class VerifyResult {
/**
* 是否成功
*/
private boolean passed;
/**
* 消息
*/
private String msg;
/**
* 代码
*/
private Integer code;
/**
* 表达式
*/
private String pattern;
public static VerifyResult success() {
VerifyResult verifyResult = new VerifyResult();
verifyResult.setPassed(true);
return verifyResult;
}
public static VerifyResult success(String pattern) {
VerifyResult verifyResult = new VerifyResult();
verifyResult.setPassed(true);
verifyResult.setPattern(pattern);
return verifyResult;
}
public static VerifyResult fail(String msg, Integer code) {
VerifyResult verifyResult = new VerifyResult();
verifyResult.setPassed(false);
verifyResult.setMsg(msg);
verifyResult.setCode(code);
return verifyResult;
}
}

View File

@@ -1,17 +0,0 @@
package im.zhaojun.zfile.admin.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import im.zhaojun.zfile.admin.mapper.DownloadLogMapper;
import im.zhaojun.zfile.admin.model.entity.DownloadLog;
import org.springframework.stereotype.Service;
/**
* 下载日志 Service
*
* @author zhaojun
*/
@Service
public class DownloadLogService extends ServiceImpl<DownloadLogMapper, DownloadLog> {
}

View File

@@ -1,219 +0,0 @@
package im.zhaojun.zfile.admin.service;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import im.zhaojun.zfile.admin.mapper.FilterConfigMapper;
import im.zhaojun.zfile.admin.model.entity.FilterConfig;
import im.zhaojun.zfile.common.exception.FileAccessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.nio.file.FileSystems;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 存储源过滤器 Service
*
* @author zhaojun
*/
@Slf4j
@Service
public class FilterConfigService extends ServiceImpl<FilterConfigMapper, FilterConfig> {
@Resource
private FilterConfigMapper filterConfigMapper;
/**
* 存储源 ID -> 过滤器列表(全部)缓存.
*/
private final Map<Integer, List<FilterConfig>> baseCache = new HashMap<>();
/**
* 存储源 ID -> 过滤器列表(禁止访问)缓存.
*/
private final Map<Integer, List<FilterConfig>> inaccessibleCache = new HashMap<>();
/**
* 存储源 ID -> 过滤器列表(禁止下载)缓存.
*/
private final Map<Integer, List<FilterConfig>> disableDownloadCache = new HashMap<>();
/**
* 根据存储源 ID 获取存储源配置列表
*
* @param storageId
* 存储源 ID
*
* @return 存储源过滤器配置列表
*/
public List<FilterConfig> findByStorageId(Integer storageId){
if (baseCache.get(storageId) != null) {
return baseCache.get(storageId);
} else {
List<FilterConfig> dbResult = filterConfigMapper.findByStorageId(storageId);
baseCache.put(storageId, dbResult);
return dbResult;
}
}
/**
* 批量保存存储源过滤器配置, 会先删除之前的所有配置(在事务中运行)
*
* @param storageId
* 存储源 ID
*
* @param filterConfigList
* 存储源过滤器配置列表
*/
@Transactional(rollbackFor = Exception.class)
public void batchSave(Integer storageId, List<FilterConfig> filterConfigList) {
filterConfigMapper.deleteByStorageId(storageId);
super.saveBatch(filterConfigList);
baseCache.put(storageId, filterConfigList);
}
/**
* 判断访问的路径是否是不允许访问的
*
* @param storageId
* 存储源 ID
*
* @param path
* 请求路径
*
* @throws FileAccessException 如果没权限访问此目录, 则抛出异常
*
*/
public void checkPathPermission(Integer storageId, String path) {
List<FilterConfig> filterConfigList = findByStorageIdAndInaccessible(storageId);
if (testPattern(filterConfigList, path)) {
throw new FileAccessException("您没有权限访问该路径");
}
}
/**
* 获取所有类型为禁止访问的过滤规则
*
* @param storageId
* 存储 ID
*
* @return 禁止访问的过滤规则列表
*/
public List<FilterConfig> findByStorageIdAndInaccessible(Integer storageId){
if (inaccessibleCache.get(storageId) != null) {
return inaccessibleCache.get(storageId);
} else {
List<FilterConfig> dbResult = filterConfigMapper.findByStorageIdAndInaccessible(storageId);
inaccessibleCache.put(storageId, dbResult);
return dbResult;
}
}
/**
* 获取所有类型为禁止下载的过滤规则
*
* @param storageId
* 存储 ID
*
* @return 禁止下载的过滤规则列表
*/
public List<FilterConfig> findByStorageIdAndDisableDownload(Integer storageId){
if (disableDownloadCache.get(storageId) != null) {
return disableDownloadCache.get(storageId);
} else {
List<FilterConfig> dbResult = filterConfigMapper.findByStorageIdAndDisableDownload(storageId);
disableDownloadCache.put(storageId, dbResult);
return dbResult;
}
}
/**
* 指定存储源下的文件名称, 根据过滤表达式判断是否会显示, 如果符合任意一条表达式, 表示隐藏则返回 true, 反之表示不隐藏则返回 false.
*
* @param storageId
* 存储源 ID
*
* @param fileName
* 文件名
*
* @return 是否是隐藏文件夹
*/
public boolean filterResultIsHidden(Integer storageId, String fileName) {
List<FilterConfig> filterConfigList = findByStorageId(storageId);
return testPattern(filterConfigList, fileName);
}
/**
* 指定存储源下的文件名称, 根据过滤表达式判断文件名和所在路径是否禁止下载, 如果符合任意一条表达式, 则返回 true, 反之则返回 false.
*
* @param storageId
* 存储源 ID
*
* @param fileName
* 文件名
*
* @return 是否显示
*/
public boolean filterResultIsDisableDownload(Integer storageId, String fileName) {
List<FilterConfig> filterConfigList = findByStorageIdAndDisableDownload(storageId);
String filePath = FileUtil.getParent(fileName, 1);
if (StrUtil.isEmpty(filePath)) {
return testPattern(filterConfigList, fileName);
} else {
return testPattern(filterConfigList, fileName) || testPattern(filterConfigList, filePath);
}
}
/**
* 根据规则表达式和测试字符串进行匹配,如测试字符串和其中一个规则匹配上,则返回 true反之返回 false。
*
* @param patternList
* 规则列表
*
* @param test
*
* 测试字符串
*
* @return 是否显示
*/
private boolean testPattern(List<FilterConfig> patternList, String test) {
for (FilterConfig filterConfig : patternList) {
String expression = filterConfig.getExpression();
if (StrUtil.isEmpty(expression)) {
return false;
}
try {
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + expression);
boolean match = pathMatcher.matches(Paths.get(test));
if (match) {
return true;
}
log.debug("regex: {}, name {}, contains: {}", expression, test, match);
} catch (Exception e) {
log.debug("regex: {}, name {}, parse error, skip expression", expression, test);
}
}
return false;
}
}

View File

@@ -1,159 +0,0 @@
package im.zhaojun.zfile.admin.service;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import im.zhaojun.zfile.admin.mapper.PasswordConfigMapper;
import im.zhaojun.zfile.admin.model.entity.PasswordConfig;
import im.zhaojun.zfile.admin.model.verify.VerifyResult;
import im.zhaojun.zfile.common.util.AjaxJson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.nio.file.FileSystems;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 存储源密码配置 Service
*
* @author zhaojun
*/
@Service
@Slf4j
public class PasswordConfigService extends ServiceImpl<PasswordConfigMapper, PasswordConfig> {
@Resource
private PasswordConfigMapper passwordConfigMapper;
/**
* 存储源 ID -> 密码规则列表(全部)缓存.
*/
private final Map<Integer, List<PasswordConfig>> baseCache = new HashMap<>();
/**
* 根据存储源 ID 查询密码规则列表
*
* @param storageId
* 存储源 ID
*
* @return 密码规则列表
*/
public List<PasswordConfig> findByStorageId(Integer storageId) {
if (baseCache.get(storageId) != null) {
return baseCache.get(storageId);
} else {
List<PasswordConfig> dbResult = passwordConfigMapper.findByStorageId(storageId);
baseCache.put(storageId, dbResult);
return dbResult;
}
}
/**
* 批量保存指定存储源 ID 的密码规则列表
*
* @param storageId
* 存储源 ID
*
* @param passwordConfigList
* 存储源类别
*/
@Transactional(rollbackFor = Exception.class)
public void batchSave(Integer storageId, List<PasswordConfig> passwordConfigList) {
passwordConfigMapper.deleteByStorageId(storageId);
super.saveBatch(passwordConfigList);
baseCache.put(storageId, passwordConfigList);
}
/**
* 校验密码
*
* @param storageId
* 存储源 ID
*
* @param path
* 请求路径
*
* @param inputPassword
* 用户输入的密码
*
* @return 是否校验通过
*/
public VerifyResult verifyPassword(Integer storageId, String path, String inputPassword) {
List<PasswordConfig> passwordConfigList = findByStorageId(storageId);
for (PasswordConfig passwordConfig : passwordConfigList) {
String expression = passwordConfig.getExpression();
if (StrUtil.isEmpty(expression)) {
continue;
}
try {
PathMatcher pathMatcher = FileSystems.getDefault()
.getPathMatcher("glob:" + expression);
// 判断当前请求路径是否和规则路径表达式匹配
boolean match = pathMatcher.matches(Paths.get(path));
// 如果匹配且输入了密码则校验
if (match) {
if (StrUtil.isEmpty(inputPassword)) {
return VerifyResult.fail("此文件夹需要密码.", AjaxJson.REQUIRED_PASSWORD);
}
String expectedPassword = passwordConfig.getPassword();
if (matchPassword(expectedPassword, inputPassword)) {
log.debug("匹配文件夹密码 path: {}, expression: {}, 用户输入: {}, 匹配成功", path, expression, inputPassword);
return VerifyResult.success(expression);
}
log.debug("匹配文件夹密码 path: {}, expression: {}, 用户输入: {}, 不匹配.", path, expression, inputPassword);
return VerifyResult.fail("此文件夹密码错误.", AjaxJson.INVALID_PASSWORD);
}
} catch (Exception e) {
log.warn("匹配文件夹密码 path: {}, expression: {}, 用户输入: {}, 解析错误, 跳过此规则.",
path, expression, inputPassword, e);
}
}
log.debug("校验文件夹密码 path: {}, 没有匹配的表达式, 不进行密码校验.", path);
return VerifyResult.success();
}
/**
* 校验两个密码是否相同, 忽略空白字符
*
* @param expectedPasswordContent
* 预期密码
*
* @param password
* 实际输入密码
*
* @return 是否匹配
*/
private boolean matchPassword(String expectedPasswordContent, String password) {
if (Objects.equals(expectedPasswordContent, password)) {
return true;
}
if (expectedPasswordContent == null) {
return false;
}
if (password == null) {
return false;
}
expectedPasswordContent = expectedPasswordContent.replace("\n", "").trim();
password = password.replace("\n", "").trim();
return Objects.equals(expectedPasswordContent, password);
}
}

View File

@@ -1,114 +0,0 @@
package im.zhaojun.zfile.admin.service;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import im.zhaojun.zfile.admin.mapper.ReadmeConfigMapper;
import im.zhaojun.zfile.admin.model.entity.ReadmeConfig;
import im.zhaojun.zfile.common.constant.ZFileConstant;
import im.zhaojun.zfile.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.nio.file.FileSystems;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.util.List;
/**
* 存储源 readme 配置 Service
*
* @author zhaojun
*/
@Slf4j
@Service
public class ReadmeConfigService extends ServiceImpl<ReadmeConfigMapper, ReadmeConfig> {
@Resource
private ReadmeConfigMapper readmeConfigMapper;
/**
* 根据存储源 ID 查询文档配置
*
* @param storageId
* 存储源ID
*
* @return 存储源文档配置列表
*/
public List<ReadmeConfig> findByStorageId(Integer storageId){
return readmeConfigMapper.findByStorageId(storageId);
}
/**
* 批量保存存储源 readme 配置, 会先删除之前的所有配置(在事务中运行)
*
* @param storageId
* 存储源 ID
*
* @param filterConfigList
* 存储源 readme 配置列表
*/
@Transactional(rollbackFor = Exception.class)
public void batchSave(Integer storageId, List<ReadmeConfig> filterConfigList) {
readmeConfigMapper.deleteByStorageId(storageId);
super.saveBatch(filterConfigList);
}
/**
* 根据存储源指定路径下的 readme 配置
*
* @param storageId
* 存储源ID
*
* @param path
* 文件夹路径
*
* @return 存储源 readme 配置列表
*/
public ReadmeConfig findReadmeByPath(Integer storageId, String path) {
List<ReadmeConfig> readmeConfigList = readmeConfigMapper.findByStorageId(storageId);
return getReadmeByTestPattern(readmeConfigList, path);
}
/**
* 根据规则表达式和测试字符串进行匹配,如测试字符串和其中一个规则匹配上,则返回 true反之返回 false。
*
* @param patternList
* 规则列表
*
* @param test
* 测试字符串
*
* @return 是否显示
*/
private ReadmeConfig getReadmeByTestPattern(List<ReadmeConfig> patternList, String test) {
test = StringUtils.concat(test, ZFileConstant.PATH_SEPARATOR);
for (ReadmeConfig filterConfig : patternList) {
String expression = filterConfig.getExpression();
if (StrUtil.isEmpty(expression)) {
continue;
}
try {
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + expression);
boolean match = pathMatcher.matches(Paths.get(test));
if (match) {
return filterConfig;
}
log.debug("regex: {}, name {}, contains: {}", expression, test, match);
} catch (Exception e) {
log.debug("regex: {}, name {}, parse error, skip expression", expression, test);
}
}
return null;
}
}

View File

@@ -1,116 +0,0 @@
package im.zhaojun.zfile.admin.service;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import im.zhaojun.zfile.admin.mapper.StorageSourceConfigMapper;
import im.zhaojun.zfile.admin.model.entity.StorageSourceConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 存储源拓展配置 Service
*
* @author zhaojun
*/
@Service
@Slf4j
public class StorageSourceConfigService extends ServiceImpl<StorageSourceConfigMapper, StorageSourceConfig> implements IService<StorageSourceConfig> {
@Resource
private StorageSourceConfigMapper storageSourceConfigMapper;
/**
* 存储源 ID -> 存储源参数列表对象缓存
*/
private final Map<Integer, List<StorageSourceConfig>> sourceConfigConfigMapCache = new HashMap<>();
/**
* 根据存储源 ID 查询存储源拓展配置, 并按照存储源 id 排序
*
* @param storageId
* 存储源 ID
*
* @return 存储源拓展配置列表
*/
public List<StorageSourceConfig> selectStorageConfigByStorageId(Integer storageId) {
if (sourceConfigConfigMapCache.containsKey(storageId)) {
return sourceConfigConfigMapCache.get(storageId);
} else {
List<StorageSourceConfig> dbResult = storageSourceConfigMapper.findByStorageIdOrderById(storageId);
sourceConfigConfigMapCache.put(storageId, dbResult);
return dbResult;
}
}
/**
* 获取指定存储源的指定参数名称
*
* @param storageId
* 存储源 id
*
* @param name
* 参数名
*
* @return 参数信息
*/
public StorageSourceConfig findByStorageIdAndName(Integer storageId, String name) {
return storageSourceConfigMapper.findByStorageIdAndName(storageId, name);
}
/**
* 批量更新存储源设置
*
* @param storageSourceConfigList
* 存储源设置列表
*/
public void updateStorageConfig(List<StorageSourceConfig> storageSourceConfigList) {
super.updateBatchById(storageSourceConfigList);
if (CollUtil.isNotEmpty(storageSourceConfigList)) {
StorageSourceConfig first = CollUtil.getFirst(storageSourceConfigList);
Integer storageId = first.getStorageId();
sourceConfigConfigMapCache.remove(storageId);
}
}
/**
* 根据存储源 id 删除所有设置
*
* @param id
* 存储源 ID
*/
public void deleteByStorageId(Integer id) {
storageSourceConfigMapper.deleteByStorageId(id);
sourceConfigConfigMapCache.remove(id);
}
/**
* 批量保存
*
* @param entityList
* 实体对象集合
*
* @return 是否保存成功
*/
@Override
public boolean saveBatch(Collection<StorageSourceConfig> entityList) {
if (CollUtil.isNotEmpty(entityList)) {
StorageSourceConfig storageSourceConfig = CollUtil.getFirst(entityList);
Integer storageId = storageSourceConfig.getStorageId();
sourceConfigConfigMapCache.put(storageId, new ArrayList<>(entityList));
}
return saveBatch(entityList, DEFAULT_BATCH_SIZE);
}
}

View File

@@ -1,502 +0,0 @@
package im.zhaojun.zfile.admin.service;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import im.zhaojun.zfile.admin.annoation.model.StorageSourceParamDef;
import im.zhaojun.zfile.admin.mapper.StorageSourceMapper;
import im.zhaojun.zfile.admin.model.entity.StorageSource;
import im.zhaojun.zfile.admin.model.entity.StorageSourceConfig;
import im.zhaojun.zfile.admin.model.param.IStorageParam;
import im.zhaojun.zfile.admin.model.request.SaveStorageSourceRequest;
import im.zhaojun.zfile.common.cache.ZFileCache;
import im.zhaojun.zfile.common.context.StorageSourceContext;
import im.zhaojun.zfile.common.exception.InitializeStorageSourceException;
import im.zhaojun.zfile.common.exception.InvalidStorageSourceException;
import im.zhaojun.zfile.home.model.dto.CacheInfoDTO;
import im.zhaojun.zfile.home.model.dto.StorageSourceAllParam;
import im.zhaojun.zfile.home.model.dto.StorageSourceDTO;
import im.zhaojun.zfile.home.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.home.model.request.UpdateStorageSortRequest;
import im.zhaojun.zfile.home.service.base.AbstractBaseFileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* 存储源基本信息 Service
*
* @author zhaojun
*/
@Slf4j
@Service
public class StorageSourceService extends ServiceImpl<StorageSourceMapper, StorageSource> implements IService<StorageSource> {
@Resource
private StorageSourceMapper storageSourceMapper;
@Resource
private StorageSourceConfigService storageSourceConfigService;
@Resource
private StorageSourceContext storageSourceContext;
@Resource
private ZFileCache zFileCache;
public static final Class<StorageSourceAllParam> STORAGE_SOURCE_ALL_PARAM_CLASS = StorageSourceAllParam.class;
/**
* 存储源 ID -> 存储源对象缓存
*/
private final Map<Integer, StorageSource> storageIdMapCache = new HashMap<>();
/**
* 存储源 KEY -> 存储源对象缓存
*/
private final Map<String, StorageSource> storageKeyMapCache = new HashMap<>();
/**
* 获取所有存储源列表
*
* @return 存储源列表
*/
public List<StorageSource> findAllOrderByOrderNum() {
return storageSourceMapper.findAllOrderByOrderNum();
}
/**
* 获取所有已启用的存储源列表,按照存储源的排序号排序
*
* @return 已启用的存储源列表
*/
public List<StorageSource> findListByEnableOrderByOrderNum() {
return storageSourceMapper.findListByEnableOrderByOrderNum();
}
/**
* 获取指定存储源设置
*
* @param id
* 存储源 ID
*
* @return 存储源设置
*/
public StorageSource findById(Integer id) {
if (storageIdMapCache.get(id) != null) {
return storageIdMapCache.get(id);
} else {
StorageSource dbResult = storageSourceMapper.selectById(id);
storageIdMapCache.put(id, dbResult);
return dbResult;
}
}
/**
* 获取指定存储源 DTO 对象, 此对象包含详细的参数设置.
*
* @param id
* 存储源 ID
*
* @return 存储源 DTO
*/
public StorageSourceDTO findStorageSourceDTOById(Integer id) {
StorageSource storageSource = findById(id);
Boolean defaultSwitchToImgMode = storageSource.getDefaultSwitchToImgMode();
if (defaultSwitchToImgMode == null) {
storageSource.setDefaultSwitchToImgMode(false);
}
StorageSourceDTO storageSourceDTO = new StorageSourceDTO();
BeanUtils.copyProperties(storageSource, storageSourceDTO);
List<StorageSourceConfig> storageSourceConfigList = storageSourceConfigService.selectStorageConfigByStorageId(storageSource.getId());
StorageSourceAllParam storageSourceAllParam = new StorageSourceAllParam();
for (StorageSourceConfig storageSourceConfig : storageSourceConfigList) {
String name = storageSourceConfig.getName();
String value = storageSourceConfig.getValue();
Field declaredField;
try {
declaredField = STORAGE_SOURCE_ALL_PARAM_CLASS.getDeclaredField(name);
declaredField.setAccessible(true);
Class<?> paramFieldType = declaredField.getType();
Object convertToValue = Convert.convert(paramFieldType, value);
declaredField.set(storageSourceAllParam, convertToValue);
} catch (NoSuchFieldException | IllegalAccessException e) {
log.error("通过反射, 将字段 {} 注入 StorageSourceDTO 时出现异常:", name, e);
}
}
storageSourceDTO.setStorageSourceAllParam(storageSourceAllParam);
return storageSourceDTO;
}
/**
* 根据 id 获取指定存储源的类型.
*
* @param id
* 存储源 ID
*
* @return 存储源对应的类型.
*/
public StorageTypeEnum findStorageTypeById(Integer id) {
return findById(id).getType();
}
/**
* 保存存储源基本信息及其对应的参数设置
*
* @param saveStorageSourceRequest
* 存储源 DTO 对象
*/
@Transactional(rollbackFor = Exception.class)
public void saveStorageSource(SaveStorageSourceRequest saveStorageSourceRequest) {
// 判断是新增还是修改
boolean updateFlag = saveStorageSourceRequest.getId() != null;
// 保存基本信息
StorageSource storageSource = new StorageSource();
StorageTypeEnum storageType = saveStorageSourceRequest.getType();
BeanUtils.copyProperties(saveStorageSourceRequest, storageSource);
if (storageSource.getId() == null) {
Integer nextId = selectNextId();
storageSource.setId(nextId);
}
// 获取通过 id 缓存的对象
StorageSource cacheStorageSource = storageIdMapCache.get(storageSource.getId());
// 如果缓存的对象和当前的对象 key 不同, 则说明修改了 key, 需要移除 key 缓存, 并重新写入.
if (ObjectUtil.isNotEmpty(cacheStorageSource) &&
!StrUtil.equals(cacheStorageSource.getKey(), storageSource.getKey())) {
storageKeyMapCache.remove(cacheStorageSource.getKey());
}
super.saveOrUpdate(storageSource);
if (StrUtil.isEmpty(storageSource.getKey()) && !StrUtil.equals(storageSource.getId().toString(), storageSource.getKey())) {
storageSource.setKey(Convert.toStr(storageSource.getId()));
baseMapper.updateById(storageSource);
}
storageKeyMapCache.put(storageSource.getKey(), storageSource);
StorageSourceAllParam storageSourceAllParam = saveStorageSourceRequest.getStorageSourceAllParam();
// 获取该存储源类型需要的参数列表
List<StorageSourceParamDef> storageSourceParamList = StorageSourceContext.getStorageSourceParamListByType(storageType);
List<StorageSourceConfig> storageSourceConfigList = new ArrayList<>();
storageSourceConfigService.deleteByStorageId(saveStorageSourceRequest.getId());
for (StorageSourceParamDef storageSourceParam : storageSourceParamList) {
String paramKey = storageSourceParam.getKey();
String paramName = storageSourceParam.getName();
StorageSourceConfig storageSourceConfig = new StorageSourceConfig();
storageSourceConfigList.add(storageSourceConfig);
Object fieldValue = ReflectUtil.getFieldValue(storageSourceAllParam, paramKey);
String fieldStrValue = Convert.toStr(fieldValue);
boolean paramRequired = storageSourceParam.isRequired();
String paramDefaultValue = storageSourceParam.getDefaultValue();
// 如果是必填的, 并且值为空, 则抛出异常
if (paramRequired && StrUtil.isEmpty(fieldStrValue)) {
throw new InitializeStorageSourceException("存储源参数配置错误: [" + paramName + "] 不能为空");
}
// 如果默认值不为空, 且输入值为空, 则使用默认值
if (StrUtil.isNotEmpty(paramDefaultValue) && StrUtil.isEmpty(fieldStrValue)) {
fieldStrValue = paramDefaultValue;
}
storageSourceConfig.setTitle(paramName);
storageSourceConfig.setName(paramKey);
storageSourceConfig.setValue(fieldStrValue);
storageSourceConfig.setType(storageType);
storageSourceConfig.setStorageId(storageSource.getId());
}
storageSourceConfigService.saveBatch(storageSourceConfigList);
storageSourceContext.init(storageSource.getId());
AbstractBaseFileService<IStorageParam> driveService = storageSourceContext.get(storageSource.getId());
if (driveService.getIsUnInitialized()) {
throw new InitializeStorageSourceException("初始化异常, 请检查配置是否正确.");
}
if (storageSource.getAutoRefreshCache()) {
startAutoCacheRefresh(storageSource.getId());
} else if (updateFlag) {
stopAutoCacheRefresh(storageSource.getId());
}
}
/**
* 查询存储源最大的 ID
*
* @return 存储源最大 ID
*/
public synchronized Integer selectNextId() {
Integer maxId = storageSourceMapper.selectMaxId();
if (maxId == null) {
return 1;
} else {
return maxId + 1;
}
}
/**
* 删除指定存储源设置, 会级联删除其参数设置
*
* @param id
* 存储源 ID
*/
@Transactional(rollbackFor = Exception.class)
public void deleteById(Integer id) {
if (log.isDebugEnabled()) {
log.debug("尝试删除存储源, storageId: {}", id);
}
StorageSource storageSource = findById(id);
storageSourceMapper.deleteById(id);
storageSourceConfigService.deleteByStorageId(id);
if (storageSource.getEnableCache()) {
zFileCache.stopAutoCacheRefresh(id);
zFileCache.clear(id);
}
String key = storageSource.getKey();
storageIdMapCache.remove(id);
storageKeyMapCache.remove(key);
storageSourceContext.destroy(id);
if (log.isDebugEnabled()) {
log.debug("尝试删除存储源成功, 已清理相关数据, storageId: {}", id);
}
}
/**
* 更新指定存储源的缓存启用状态
*
* @param storageId
* 存储源 ID
*
* @param cacheEnable
* 是否启用缓存
*/
public void updateCacheStatus(Integer storageId, Boolean cacheEnable) {
StorageSource storageSource = findById(storageId);
if (storageSource != null) {
storageSource.setEnableCache(cacheEnable);
super.saveOrUpdate(storageSource);
storageIdMapCache.put(storageId, storageSource);
}
}
/**
* 获取指定存储源的缓存信息
*
* @param storageId
* 存储源 ID
*
* @return 缓存信息
*/
public CacheInfoDTO findCacheInfo(Integer storageId) {
long hitCount = zFileCache.getHitCount(storageId);
long missCount = zFileCache.getMissCount(storageId);
Set<String> keys = zFileCache.keySet(storageId);
int cacheCount = keys.size();
return new CacheInfoDTO(cacheCount, hitCount, missCount, keys);
}
/**
* 刷新指定 key 的缓存:
* 1. 清空此 key 的缓存.
* 2. 重新调用方法写入缓存.
*
* @param storageId
* 存储源 ID
*
* @param key
* 缓存 key (文件夹名称)
*/
public void refreshCache(Integer storageId, String key) throws Exception {
if (log.isDebugEnabled()) {
log.debug("手动刷新缓存 storageId: {}, key: {}", storageId, key);
}
zFileCache.remove(storageId, key);
AbstractBaseFileService<?> baseFileService = storageSourceContext.get(storageId);
baseFileService.fileList(key);
}
/**
* 开启缓存自动刷新
*
* @param storageId
* 存储源 ID
*/
public void startAutoCacheRefresh(Integer storageId) {
StorageSource storageSource = findById(storageId);
storageSource.setAutoRefreshCache(true);
super.saveOrUpdate(storageSource);
storageIdMapCache.put(storageId, storageSource);
zFileCache.startAutoCacheRefresh(storageId);
}
/**
* 停止缓存自动刷新
*
* @param storageId
* 存储源 ID
*/
public void stopAutoCacheRefresh(Integer storageId) {
StorageSource storageSource = findById(storageId);
storageSource.setAutoRefreshCache(false);
super.saveOrUpdate(storageSource);
storageIdMapCache.put(storageId, storageSource);
zFileCache.stopAutoCacheRefresh(storageId);
}
/**
* 清理缓存
*
* @param storageId
* 存储源 ID
*/
public void clearCache(Integer storageId) {
zFileCache.clear(storageId);
}
/**
* 交换存储源排序
*
* @param updateStorageSortRequestList
* 更新排序的存储源 id 及排序值列表
*/
@Transactional(rollbackFor = Exception.class)
public void updateStorageSort(List<UpdateStorageSortRequest> updateStorageSortRequestList) {
for (int i = 0; i < updateStorageSortRequestList.size(); i++) {
UpdateStorageSortRequest item = updateStorageSortRequestList.get(i);
if (!Objects.equals(i, item.getOrderNum())) {
storageSourceMapper.updateSetOrderNumById(i, item.getId());
storageIdMapCache.clear();
}
}
}
/**
* 根据存储源 key 获取存储源
*
* @param storageKey
* 存储源 key
*
* @throws InvalidStorageSourceException 存储源不存在时, 抛出异常.
*
* @return 存储源信息
*/
public StorageSource findByStorageKey(String storageKey) {
if (storageKeyMapCache.containsKey(storageKey)) {
return storageKeyMapCache.get(storageKey);
} else {
StorageSource storageSource = storageSourceMapper.findByStorageKey(storageKey);
if (storageSource != null) {
storageKeyMapCache.put(storageKey, storageSource);
}
return storageSource;
}
}
/**
* 判断存储源 key 是否已存在 (不读取缓存)
*
* @param storageKey
* 存储源 key
*
* @return 是否已存在
*/
public boolean existByStorageKey(String storageKey) {
return storageSourceMapper.findIdByStorageKey(storageKey) != null;
}
/**
* 根据存储源 id 获取存储源 key
*
* @param id
* 存储源 id
*
* @return 存储源 key
*/
public String findKeyById(Integer id){
return findById(id).getKey();
}
/**
* 根据存储源 key 获取存储源
*
* @param storageKey
* 存储源 key
*
* @return 存储源信息
*/
public Integer findIdByKey(String storageKey) {
StorageSource storageSource = findByStorageKey(storageKey);
if (storageSource == null) {
return null;
} else {
return storageSource.getId();
}
}
@Override
public boolean updateById(StorageSource entity) {
if (entity != null) {
Integer id = entity.getId();
storageIdMapCache.put(id, entity);
}
return super.updateById(entity);
}
}

View File

@@ -1,61 +0,0 @@
package im.zhaojun.zfile.common.cache;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.extra.spring.SpringUtil;
import im.zhaojun.zfile.common.context.StorageSourceContext;
import im.zhaojun.zfile.admin.model.dto.StorageSourceCacheKey;
import im.zhaojun.zfile.home.service.base.AbstractBaseFileService;
import lombok.extern.slf4j.Slf4j;
/**
* 自定义缓存类, 实现缓存超时后自动刷新
*
* @author zhaojun
*/
@Slf4j
public class MyTimedCache<K, V> extends TimedCache<K, V> {
private StorageSourceContext storageSourceContext;
public MyTimedCache(long timeout) {
super(timeout);
}
/**
* 当缓存超时后自动刷新
*
* @param key
* 缓存 key
*
* @param cachedObject
* 缓存值
*/
@Override
protected void onRemove(K key, V cachedObject) {
if (storageSourceContext == null) {
storageSourceContext = SpringUtil.getBean(StorageSourceContext.class);
}
StorageSourceCacheKey cacheKey = (StorageSourceCacheKey) key;
AbstractBaseFileService<?> baseFileService = storageSourceContext.get(cacheKey.getStorageId());
if (log.isDebugEnabled()) {
log.debug("尝试刷新缓存: {}", cacheKey);
}
if (baseFileService == null) {
log.error("尝试刷新缓存: {}, 时出现异常, 存储源已不存在", cacheKey);
return;
}
try {
baseFileService.fileList(cacheKey.getKey());
} catch (Exception e) {
log.error("尝试刷新缓存 {} 失败", cacheKey);
e.printStackTrace();
}
}
}

View File

@@ -1,298 +0,0 @@
package im.zhaojun.zfile.common.cache;
import cn.hutool.cache.impl.CacheObj;
import cn.hutool.core.util.StrUtil;
import im.zhaojun.zfile.admin.model.dto.StorageSourceCacheKey;
import im.zhaojun.zfile.admin.model.entity.StorageSource;
import im.zhaojun.zfile.admin.service.StorageSourceService;
import im.zhaojun.zfile.common.context.StorageSourceContext;
import im.zhaojun.zfile.home.model.dto.SystemConfigDTO;
import im.zhaojun.zfile.home.model.result.FileItemResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* ZFile 缓存工具类
*
* @author zhaojun
*/
@Service("zFileCache")
@Slf4j
public class ZFileCache {
@Resource
private StorageSourceService storageSourceService;
@Resource
private StorageSourceContext storageSourceContext;
/**
* 缓存过期时间
*/
@Value("${zfile.cache.timeout}")
private long timeout;
/**
* 缓存自动刷新间隔
*/
@Value("${zfile.cache.auto-refresh.interval}")
private long autoRefreshInterval;
/**
* 文件/文件对象缓存.
*
* ConcurrentMap<Integer, ConcurrentHashMap<String, List<FileItemDTO>>>
* ConcurrentMap<storageId, ConcurrentHashMap<key, value>>
*
* storageId: 存储源 ID
* key: 文件夹路径
* value: 文件夹中内容
*/
private final ConcurrentMap<Integer, MyTimedCache<StorageSourceCacheKey, List<FileItemResult>>> storageSourcesCache = new ConcurrentHashMap<>();
/**
* 系统设置缓存
*/
private SystemConfigDTO systemConfigCache;
/**
* 写入缓存
*
* @param storageId
* 存储源 ID
*
* @param key
* 文件夹路径
*
* @param value
* 文件夹中列表
*/
public synchronized void put(Integer storageId, String key, List<FileItemResult> value) {
getCacheByStorageId(storageId).put(new StorageSourceCacheKey(storageId, key), value);
}
/**
* 获取指定存储源, 某个文件夹的名称
*
* @param storageId
* 存储源 ID
*
* @param key
* 文件夹路径
*
* @return 存储源中文件夹的内容
*/
public List<FileItemResult> get(Integer storageId, String key) {
return getCacheByStorageId(storageId).get(new StorageSourceCacheKey(storageId, key), false);
}
/**
* 清空指定存储源的缓存.
*
* @param storageId
* 存储源 ID
*/
public void clear(Integer storageId) {
if (log.isDebugEnabled()) {
log.debug("清空存储源所有缓存, storageId: {}", storageId);
}
getCacheByStorageId(storageId).clear();
}
/**
* 获取指定存储源中已缓存文件夹数量
*
* @param storageId
* 存储源 ID
*
* @return 已缓存文件夹数量
*/
public int cacheCount(Integer storageId) {
return getCacheByStorageId(storageId).size();
}
/**
* 指定存储源, 根据文件及文件名查找相关的文件
*
* @param storageId
* 存储源 ID
*
* @param key
* 搜索键, 可匹配文件夹名称和文件名称.
*
* @return 搜索结果, 包含文件夹和文件.
*/
public List<FileItemResult> find(Integer storageId, String key) {
return new ArrayList<>();
}
private boolean testMatch(String searchKey, boolean ignoreCase, FileItemResult fileItemResult) {
// 根据是否需要忽略大小写来匹配文件(夹)名
if (ignoreCase) {
return StrUtil.containsIgnoreCase(fileItemResult.getName(), searchKey);
} else {
return StrUtil.contains(fileItemResult.getName(), searchKey);
}
}
/**
* 获取所有缓存 key (文件夹名称)
*
* @return 所有缓存 key
*/
public Set<String> keySet(Integer storageId) {
Iterator<CacheObj<StorageSourceCacheKey, List<FileItemResult>>> cacheObjIterator = getCacheByStorageId(storageId).cacheObjIterator();
Set<String> keys = new HashSet<>();
while (cacheObjIterator.hasNext()) {
keys.add(cacheObjIterator.next().getKey().getKey());
}
return keys;
}
/**
* 从缓存中删除指定存储源的某个路径的缓存
*
* @param storageId
* 存储源 ID
*
* @param key
* 文件夹路径
*/
public void remove(Integer storageId, String key) {
getCacheByStorageId(storageId).remove(new StorageSourceCacheKey(storageId, key));
}
/**
* 更新缓存中的系统设置
*
* @param systemConfigCache
* 系统设置
*/
public void updateConfig(SystemConfigDTO systemConfigCache) {
this.systemConfigCache = systemConfigCache;
}
/**
* 从缓存中获取系统设置
*
* @return 系统设置
*/
public SystemConfigDTO getConfig() {
return this.systemConfigCache;
}
/**
* 清空系统设置缓存
*/
public void removeConfig() {
this.systemConfigCache = null;
}
/**
* 获取指定存储源对应的缓存
*
* @param storageId
* 存储源 ID
*
* @return 存储源对应的缓存
*/
private synchronized MyTimedCache<StorageSourceCacheKey, List<FileItemResult>> getCacheByStorageId(Integer storageId) {
MyTimedCache<StorageSourceCacheKey, List<FileItemResult>> driveCache = storageSourcesCache.get(storageId);
if (driveCache == null) {
driveCache = new MyTimedCache<>(timeout * 1000);
storageSourcesCache.put(storageId, driveCache);
startAutoCacheRefresh(storageId);
}
return driveCache;
}
/**
* 获取指定存储源的缓存命中数
*
* @param storageId
* 存储源 ID
*
* @return 缓存命中数
*/
public long getHitCount(Integer storageId) {
return getCacheByStorageId(storageId).getHitCount();
}
/**
* 获取指定存储源的缓存未命中数
*
* @param storageId
* 存储源 ID
*
* @return 缓存未命中数
*/
public long getMissCount(Integer storageId) {
return getCacheByStorageId(storageId).getMissCount();
}
/**
* 开启缓存自动刷新
*
* @param storageId
* 存储源 ID
*/
public void startAutoCacheRefresh(Integer storageId) {
if (log.isDebugEnabled()) {
log.debug("开启缓存自动刷新 storageId: {}", storageId);
}
StorageSource storageSource = storageSourceService.findById(storageId);
Boolean autoRefreshCache = storageSource.getAutoRefreshCache();
if (autoRefreshCache != null && autoRefreshCache) {
MyTimedCache<StorageSourceCacheKey, List<FileItemResult>> driveCache = storageSourcesCache.get(storageId);
if (driveCache == null) {
driveCache = new MyTimedCache<>(timeout * 1000);
storageSourcesCache.put(storageId, driveCache);
}
driveCache.schedulePrune(autoRefreshInterval * 1000);
}
}
/**
* 停止缓存自动刷新
*
* @param storageId
* 存储源 ID
*/
public void stopAutoCacheRefresh(Integer storageId) {
if (log.isDebugEnabled()) {
log.debug("停止缓存自动刷新 storageId: {}", storageId);
}
MyTimedCache<StorageSourceCacheKey, List<FileItemResult>> driveCache = storageSourcesCache.get(storageId);
if (driveCache != null) {
driveCache.cancelPruneSchedule();
}
}
}

View File

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

View File

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

View File

@@ -1,173 +0,0 @@
/*
* Copyright (c) 2011-2022, baomidou (jobob@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.zhaojun.zfile.common.config;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.invoker.Invoker;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
* 自定义枚举属性转换器
*
* @author hubin
* @since 2017-10-11
*/
public class MybatisEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
private static final Map<String, String> TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap<>();
private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
private final Class<E> enumClassType;
private final Class<?> propertyType;
private final Invoker getInvoker;
public MybatisEnumTypeHandler(Class<E> enumClassType) {
if (enumClassType == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.enumClassType = enumClassType;
MetaClass metaClass = MetaClass.forClass(enumClassType, REFLECTOR_FACTORY);
String name = "value";
if (!IEnum.class.isAssignableFrom(enumClassType)) {
name = findEnumValueFieldName(this.enumClassType).orElseThrow(() -> new IllegalArgumentException(String.format("Could not find @EnumValue in Class: %s.", this.enumClassType.getName())));
}
this.propertyType = ReflectionKit.resolvePrimitiveIfNecessary(metaClass.getGetterType(name));
this.getInvoker = metaClass.getGetInvoker(name);
}
/**
* 查找标记标记EnumValue字段
*
* @param clazz class
* @return EnumValue字段
* @since 3.3.1
*/
public static Optional<String> findEnumValueFieldName(Class<?> clazz) {
if (clazz != null && clazz.isEnum()) {
String className = clazz.getName();
return Optional.ofNullable(CollectionUtils.computeIfAbsent(TABLE_METHOD_OF_ENUM_TYPES, className, key -> {
Optional<Field> fieldOptional = findEnumValueAnnotationField(clazz);
return fieldOptional.map(Field::getName).orElse(null);
}));
}
return Optional.empty();
}
private static Optional<Field> findEnumValueAnnotationField(Class<?> clazz) {
return Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(EnumValue.class)).findFirst();
}
/**
* 判断是否为MP枚举处理
*
* @param clazz class
* @return 是否为MP枚举处理
* @since 3.3.1
*/
public static boolean isMpEnums(Class<?> clazz) {
return clazz != null && clazz.isEnum() && (IEnum.class.isAssignableFrom(clazz) || findEnumValueFieldName(clazz).isPresent());
}
@SuppressWarnings("Duplicates")
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType)
throws SQLException {
if (jdbcType == null) {
ps.setObject(i, this.getValue(parameter));
} else {
// see r3589
ps.setObject(i, this.getValue(parameter), jdbcType.TYPE_CODE);
}
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
Object value = rs.getObject(columnName);
if (null == value && rs.wasNull()) {
return null;
}
return this.valueOf(value);
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
Object value = rs.getObject(columnIndex, this.propertyType);
if (null == value && rs.wasNull()) {
return null;
}
return this.valueOf(value);
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Object value = cs.getObject(columnIndex, this.propertyType);
if (null == value && cs.wasNull()) {
return null;
}
return this.valueOf(value);
}
private E valueOf(Object value) {
E[] es = this.enumClassType.getEnumConstants();
return Arrays.stream(es).filter((e) -> equalsValue(value, getValue(e))).findAny().orElse(null);
}
/**
* 值比较
*
* @param sourceValue 数据库字段值
* @param targetValue 当前枚举属性值
* @return 是否匹配
* @since 3.3.0
*/
protected boolean equalsValue(Object sourceValue, Object targetValue) {
String sValue = StringUtils.toStringTrim(sourceValue);
String tValue = StringUtils.toStringTrim(targetValue);
if (sourceValue instanceof Number && targetValue instanceof Number
&& new BigDecimal(sValue).compareTo(new BigDecimal(tValue)) == 0) {
return true;
}
return Objects.equals(sValue, tValue);
}
private Object getValue(Object object) {
try {
return this.getInvoker.invoke(object, new Object[0]);
} catch (ReflectiveOperationException e) {
throw ExceptionUtils.mpe(e);
}
}
}

View File

@@ -1,79 +0,0 @@
package im.zhaojun.zfile.common.config;
import im.zhaojun.zfile.admin.constant.StorageConfigConstant;
import im.zhaojun.zfile.admin.model.entity.StorageSourceConfig;
import im.zhaojun.zfile.admin.service.StorageSourceConfigService;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
/**
* restTemplate 相关配置
*
* @author zhaojun
*/
@Configuration
public class RestTemplateConfig {
@Resource
private StorageSourceConfigService storageSourceConfigService;
/**
* OneDrive 请求 RestTemplate.
* 获取 header 中的 storageId 来判断到底是哪个存储源 ID, 在请求头中添加 Bearer: Authorization {token} 信息, 用于 API 认证.
*/
@Bean
public RestTemplate oneDriveRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory();
restTemplate.setRequestFactory(factory);
ClientHttpRequestInterceptor interceptor = (httpRequest, bytes, clientHttpRequestExecution) -> {
HttpHeaders headers = httpRequest.getHeaders();
Integer storageId = Integer.valueOf(((List)headers.get("storageId")).get(0).toString());
StorageSourceConfig accessTokenConfig =
storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.ACCESS_TOKEN_KEY);
String tokenValue = String.format("%s %s", "Bearer", accessTokenConfig.getValue());
httpRequest.getHeaders().add("Authorization", tokenValue);
return clientHttpRequestExecution.execute(httpRequest, bytes);
};
restTemplate.setInterceptors(Collections.singletonList(interceptor));
return restTemplate;
}
/**
* restTemplate 设置请求和响应字符集都为 UTF-8, 并设置响应头为 Content-Type: application/text;
*/
@Bean
public RestTemplate restTemplate(){
HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
HttpClient httpClient = HttpClientBuilder.create().build();
httpRequestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(httpRequestFactory);
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
restTemplate.setInterceptors(Collections.singletonList((request, body, execution) -> {
ClientHttpResponse response = execution.execute(request, body);
HttpHeaders headers = response.getHeaders();
headers.put("Content-Type", Collections.singletonList("application/text"));
return response;
}));
return restTemplate;
}
}

View File

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

View File

@@ -1,74 +0,0 @@
package im.zhaojun.zfile.common.controller.callback;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import im.zhaojun.zfile.admin.model.dto.OneDriveToken;
import im.zhaojun.zfile.home.service.impl.OneDriveChinaServiceImpl;
import im.zhaojun.zfile.home.service.impl.OneDriveServiceImpl;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
/**
* OneDrive 授权回调
*
* @author zhaojun
*/
@Api(tags = "OneDrive 认证回调模块")
@Controller
@RequestMapping(value = {"/onedrive", "/onedirve"})
public class OneDriveCallbackController {
@Resource
private OneDriveServiceImpl oneDriveServiceImpl;
@Resource
private OneDriveChinaServiceImpl oneDriveChinaServiceImpl;
@GetMapping("/authorize")
@ApiOperationSupport(order = 1)
@ApiOperation(value = "生成 OAuth2 登陆 URL", notes = "生成 OneDrive OAuth2 登陆 URL用户国际版家庭版等非世纪互联运营的 OneDrive.")
public String authorize() {
return "redirect:https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + oneDriveServiceImpl.getClientId() +
"&response_type=code&redirect_uri=" + oneDriveServiceImpl.getRedirectUri() +
"&scope=" + oneDriveServiceImpl.getScope();
}
@GetMapping("/callback")
@ApiOperationSupport(order = 2)
@ApiOperation(value = "OAuth2 回调地址", notes = "根据 OAuth2 协议,登录成功后,会返回给网站一个 code用此 code 去换取 accessToken 和 refreshToken.oneDrive 会回调此接口)")
public String oneDriveCallback(String code, Model model) {
OneDriveToken oneDriveToken = oneDriveServiceImpl.getToken(code);
model.addAttribute("accessToken", oneDriveToken.getAccessToken());
model.addAttribute("refreshToken", oneDriveToken.getRefreshToken());
return "callback";
}
@GetMapping("/china-authorize")
@ApiOperationSupport(order = 3)
@ApiOperation(value = "生成 OAuth2 登陆 URL(世纪互联)", notes = "生成 OneDrive OAuth2 登陆 URL用于世纪互联版本.")
public String authorizeChina() {
return "redirect:https://login.chinacloudapi.cn/common/oauth2/v2.0/authorize?client_id=" + oneDriveChinaServiceImpl.getClientId() +
"&response_type=code&redirect_uri=" + oneDriveChinaServiceImpl.getRedirectUri() +
"&scope=" + oneDriveChinaServiceImpl.getScope();
}
@GetMapping("/china-callback")
@ApiOperationSupport(order = 4)
@ApiOperation(value = "OAuth2 回调地址(世纪互联)", notes = "根据 OAuth2 协议,登录成功后,会返回给网站一个 code用此 code 去换取 accessToken 和 refreshToken.oneDrive 会回调此接口)")
public String oneDriveChinaCallback(String code, Model model) {
OneDriveToken oneDriveToken = oneDriveChinaServiceImpl.getToken(code);
model.addAttribute("accessToken", oneDriveToken.getAccessToken());
model.addAttribute("refreshToken", oneDriveToken.getRefreshToken());
return "callback";
}
}

View File

@@ -1,10 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 禁止服务器代理下载异常
*
* @author zhaojun
*/
public class DisableProxyDownloadException extends RuntimeException {
}

View File

@@ -1,31 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 文件权限异常
*
* @author zhaojun
*/
public class FileAccessException extends RuntimeException {
private static final long serialVersionUID = 1L;
public FileAccessException() {
}
public FileAccessException(String message) {
super(message);
}
public FileAccessException(String message, Throwable cause) {
super(message, cause);
}
public FileAccessException(Throwable cause) {
super(cause);
}
public FileAccessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,27 +0,0 @@
package im.zhaojun.zfile.common.exception;
import im.zhaojun.zfile.home.model.enums.StorageTypeEnum;
import lombok.Getter;
/**
* 文件上传异常
*
* @author zhaojun
*/
@Getter
public class FileUploadException extends RuntimeException {
private final StorageTypeEnum storageTypeEnum;
private final Integer storageId;
private final String path;
public FileUploadException(StorageTypeEnum storageTypeEnum, Integer storageId, String path, Throwable cause) {
super(cause);
this.storageTypeEnum = storageTypeEnum;
this.path = path;
this.storageId = storageId;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,21 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 非法使用下载链接异常.
*
* @author zhaojun
*/
public class IllegalDownloadLinkException extends RuntimeException {
public IllegalDownloadLinkException() {
super();
}
public IllegalDownloadLinkException(String message) {
super(message);
}
public IllegalDownloadLinkException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,30 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 存储源初始化异常
*
* @author zhaojun
*/
public class InitializeStorageSourceException extends RuntimeException {
private static final long serialVersionUID = -1920550904063819880L;
public InitializeStorageSourceException() {
}
public InitializeStorageSourceException(String message) {
super(message);
}
public InitializeStorageSourceException(String message, Throwable cause) {
super(message, cause);
}
public InitializeStorageSourceException(Throwable cause) {
super(cause);
}
public InitializeStorageSourceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,30 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 系统初始化异常
*
* @author zhaojun
*/
public class InstallSystemException extends RuntimeException {
public InstallSystemException() {
super();
}
public InstallSystemException(String message) {
super(message);
}
public InstallSystemException(String message, Throwable cause) {
super(message, cause);
}
public InstallSystemException(Throwable cause) {
super(cause);
}
protected InstallSystemException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,29 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 无效的直链异常
*
* @author zhaojun
*/
public class InvalidShortLinkException extends RuntimeException {
public InvalidShortLinkException() {
}
public InvalidShortLinkException(String message) {
super(message);
}
public InvalidShortLinkException(String message, Throwable cause) {
super(message, cause);
}
public InvalidShortLinkException(Throwable cause) {
super(cause);
}
public InvalidShortLinkException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,29 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 无效的存储源异常
*
* @author zhaojun
*/
public class InvalidStorageSourceException extends RuntimeException {
public InvalidStorageSourceException() {
}
public InvalidStorageSourceException(String message) {
super(message);
}
public InvalidStorageSourceException(String message, Throwable cause) {
super(message, cause);
}
public InvalidStorageSourceException(Throwable cause) {
super(cause);
}
public InvalidStorageSourceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,29 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 文件不允许下载异常
*
* @author zhaojun
*/
public class NotAllowedDownloadException extends RuntimeException {
public NotAllowedDownloadException() {
}
public NotAllowedDownloadException(String message) {
super(message);
}
public NotAllowedDownloadException(String message, Throwable cause) {
super(message, cause);
}
public NotAllowedDownloadException(Throwable cause) {
super(cause);
}
public NotAllowedDownloadException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,29 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 未启用的存储源异常
*
* @author zhaojun
*/
public class NotEnabledStorageSourceException extends RuntimeException {
public NotEnabledStorageSourceException() {
}
public NotEnabledStorageSourceException(String message) {
super(message);
}
public NotEnabledStorageSourceException(String message, Throwable cause) {
super(message, cause);
}
public NotEnabledStorageSourceException(Throwable cause) {
super(cause);
}
public NotEnabledStorageSourceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,30 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 不存在的文件异常
*
* @author zhaojun
*/
public class NotExistFileException extends RuntimeException {
public NotExistFileException() {
super();
}
public NotExistFileException(String message) {
super(message);
}
public NotExistFileException(String message, Throwable cause) {
super(message, cause);
}
public NotExistFileException(Throwable cause) {
super(cause);
}
protected NotExistFileException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,29 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 文件预览异常类
*
* @author zhaojun
*/
public class PreviewException extends RuntimeException {
public PreviewException() {
}
public PreviewException(String message) {
super(message);
}
public PreviewException(String message, Throwable cause) {
super(message, cause);
}
public PreviewException(Throwable cause) {
super(cause);
}
public PreviewException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,29 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 刷新缓存时出现的异常信息
*
* @author zhaojun
*/
public class RefreshCacheException extends RuntimeException {
public RefreshCacheException() {
}
public RefreshCacheException(String message) {
super(message);
}
public RefreshCacheException(String message, Throwable cause) {
super(message, cause);
}
public RefreshCacheException(Throwable cause) {
super(cause);
}
public RefreshCacheException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,30 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 文件解析异常
*
* @author zhaojun
*/
public class TextParseException extends RuntimeException {
public TextParseException() {
super();
}
public TextParseException(String message) {
super(message);
}
public TextParseException(String message, Throwable cause) {
super(message, cause);
}
public TextParseException(Throwable cause) {
super(cause);
}
protected TextParseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,29 +0,0 @@
package im.zhaojun.zfile.common.exception;
/**
* 不支持的操作异常
*
* @author zhaojun
*/
public class UnSupportedOperation extends RuntimeException {
public UnSupportedOperation() {
super();
}
public UnSupportedOperation(String message) {
super(message);
}
public UnSupportedOperation(String message, Throwable cause) {
super(message, cause);
}
public UnSupportedOperation(Throwable cause) {
super(cause);
}
protected UnSupportedOperation(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,32 +0,0 @@
package im.zhaojun.zfile.common.exception.file;
import lombok.Getter;
import lombok.Setter;
/**
* 存储源异常
*
* @author zhaojun
*/
@Getter
@Setter
public class StorageSourceException extends RuntimeException {
// 存储源 ID
private Integer storageId;
public StorageSourceException(Integer storageId) {
this.storageId = storageId;
}
public StorageSourceException(Integer storageId, String message) {
super(message);
this.storageId = storageId;
}
public StorageSourceException(Integer storageId, Throwable cause) {
super(cause);
this.storageId = storageId;
}
}

View File

@@ -1,24 +0,0 @@
package im.zhaojun.zfile.common.exception.file.operator;
import im.zhaojun.zfile.common.exception.file.StorageSourceException;
import lombok.Getter;
import lombok.Setter;
/**
* 文件下载异常
*
* @author zhaojun
*/
@Getter
@Setter
public class DownloadFileException extends StorageSourceException {
// 下载文件路径
private String pathAndName;
public DownloadFileException(Integer storageId, String pathAndName, Throwable cause) {
super(storageId, cause);
this.pathAndName = pathAndName;
}
}

View File

@@ -1,35 +0,0 @@
package im.zhaojun.zfile.common.exception.file.operator;
import im.zhaojun.zfile.common.exception.file.StorageSourceException;
import lombok.Getter;
import lombok.Setter;
/**
* 获取文件信息异常
*
* @author zhaojun
*/
@Getter
@Setter
public class GetFileInfoException extends StorageSourceException {
// 文件信息路径
private String pathAndName;
public GetFileInfoException(Integer storageId, String pathAndName) {
super(storageId);
this.pathAndName = pathAndName;
}
public GetFileInfoException(Integer storageId, String pathAndName, String message) {
super(storageId, message);
this.pathAndName = pathAndName;
}
public GetFileInfoException(Integer storageId, String pathAndName, Throwable cause) {
super(storageId, cause);
this.pathAndName = pathAndName;
}
}

View File

@@ -1,24 +0,0 @@
package im.zhaojun.zfile.common.exception.file.operator;
import im.zhaojun.zfile.common.exception.file.StorageSourceException;
import lombok.Getter;
import lombok.Setter;
/**
* 代理文件下载异常
*
* @author zhaojun
*/
@Getter
@Setter
public class ProxyDownloadFileException extends StorageSourceException {
// 下载文件路径
private String pathAndName;
public ProxyDownloadFileException(Integer storageId, String pathAndName, Throwable cause) {
super(storageId, cause);
this.pathAndName = pathAndName;
}
}

View File

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

View File

@@ -0,0 +1,67 @@
package im.zhaojun.zfile.core;
import im.zhaojun.zfile.core.util.AjaxJson;
import org.slf4j.MDC;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* @author zhaojun
*/
@ControllerAdvice
public class CommonResultControllerAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType,
@NonNull Class<? extends HttpMessageConverter<?>> converterType) {
return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}
@Override
@NonNull
public final Object beforeBodyWrite(@Nullable Object body,
@NonNull MethodParameter returnType,
@NonNull MediaType contentType,
@NonNull Class<? extends HttpMessageConverter<?>> converterType,
@NonNull ServerHttpRequest request,
@NonNull ServerHttpResponse response) {
MappingJacksonValue container = getOrCreateContainer(body);
// The contain body will never be null
beforeBodyWriteInternal(container, contentType, returnType, request, response);
return container;
}
/**
* Wrap the body in a {@link MappingJacksonValue} value container (for providing
* additional serialization instructions) or simply cast it if already wrapped.
*/
private MappingJacksonValue getOrCreateContainer(Object body) {
return body instanceof MappingJacksonValue ? (MappingJacksonValue) body :
new MappingJacksonValue(body);
}
private void beforeBodyWriteInternal(MappingJacksonValue bodyContainer,
MediaType contentType,
MethodParameter returnType,
ServerHttpRequest request,
ServerHttpResponse response) {
// Get return body
Object returnBody = bodyContainer.getValue();
if (returnBody instanceof AjaxJson) {
// If the return body is instance of BaseResponse, then just do nothing
AjaxJson<?> baseResponse = (AjaxJson<?>) returnBody;
baseResponse.setTraceId(MDC.get("traceId"));
}
}
}

View File

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

View File

@@ -1,4 +1,4 @@
package im.zhaojun.zfile.common.config;
package im.zhaojun.zfile.core.config;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;

View File

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

View File

@@ -1,11 +1,15 @@
package im.zhaojun.zfile.common.config;
package im.zhaojun.zfile.core.config;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.SQLException;
@@ -20,8 +24,27 @@ public class MyBatisPlusConfig {
@Resource
private DataSource dataSource;
@Value("${spring.datasource.driver-class-name}")
private String datasourceDriveClassName;
@Value("${spring.datasource.url}")
private String datasourceUrl;
/**
* 如果是 sqlite 数据库自动创建数据库文件所在目录
*/
@PostConstruct
public void init() {
if (StrUtil.equals(datasourceDriveClassName, "org.sqlite.JDBC")) {
String path = datasourceUrl.replace("jdbc:sqlite:", "");
String folderPath = FileUtil.getParent(path, 1);
if (!FileUtil.exist(folderPath)) {
FileUtil.mkdir(folderPath);
}
}
}
/**
* mybatis plus 分页插件配置
*/

View File

@@ -0,0 +1,27 @@
package im.zhaojun.zfile.core.config;
import im.zhaojun.zfile.core.httpclient.ZFileOkHttp3ClientHttpRequestFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* restTemplate 相关配置
*
* @author zhaojun
*/
@Configuration
public class RestTemplateConfig {
/**
* OneDrive 请求 RestTemplate.
* 获取 header 中的 storageId 来判断到底是哪个存储源 ID, 在请求头中添加 Bearer: Authorization {token} 信息, 用于 API 认证.
*/
@Bean
public RestTemplate oneDriveRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new ZFileOkHttp3ClientHttpRequestFactory());
return restTemplate;
}
}

View File

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

View File

@@ -0,0 +1,33 @@
package im.zhaojun.zfile.core.config;
import cn.hutool.core.util.BooleanUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.cache.support.NoOpCacheManager;
import org.springframework.cache.transaction.TransactionAwareCacheManagerProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Spring Cache 相关配置
*
* @author zhaojun
*/
@Configuration
@EnableCaching
public class SpringCacheConfig {
@Value("${zfile.dbCache.enable:true}")
private Boolean dbCacheEnable;
/**
* 使用 TransactionAwareCacheManagerProxy 装饰 ConcurrentMapCacheManager使其支持事务 (将 put、evict、clear 操作延迟到事务成功提交再执行.
*/
@Bean
public CacheManager cacheManager() {
return BooleanUtil.isFalse(dbCacheEnable) ? new NoOpCacheManager() : new TransactionAwareCacheManagerProxy(new ConcurrentMapCacheManager());
}
}

View File

@@ -1,4 +1,4 @@
package im.zhaojun.zfile.common.config;
package im.zhaojun.zfile.core.config;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.baomidou.mybatisplus.annotation.IEnum;

View File

@@ -1,5 +1,6 @@
package im.zhaojun.zfile.common.config;
package im.zhaojun.zfile.core.config;
import im.zhaojun.zfile.module.storage.model.enums.StorageTypeEnum;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
@@ -25,7 +26,7 @@ public class WebMvcConfig implements WebMvcConfigurer {
/**
* 添加自定义枚举格式化器.
* @see im.zhaojun.zfile.home.model.enums.StorageTypeEnum
* @see StorageTypeEnum
*/
@Override
public void addFormatters(FormatterRegistry registry) {
@@ -60,5 +61,8 @@ public class WebMvcConfig implements WebMvcConfigurer {
factory.setErrorPages(errorPages);
};
}
}

View File

@@ -1,4 +1,4 @@
package im.zhaojun.zfile.common.config;
package im.zhaojun.zfile.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -7,8 +7,6 @@ import org.springframework.stereotype.Component;
/**
* @author zhaojun
* @version V1.0
* @date 2022-4-26
*/
@Data
@EnableConfigurationProperties

View File

@@ -0,0 +1,16 @@
package im.zhaojun.zfile.core.constant;
/**
* Slf4j mdc 常量
*
* @author zhaojun
*/
public class MdcConstant {
public static final String TRACE_ID = "traceId";
public static final String IP = "ip";
public static final String USER = "user";
}

View File

@@ -1,4 +1,4 @@
package im.zhaojun.zfile.common.constant;
package im.zhaojun.zfile.core.constant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -16,37 +16,11 @@ public class ZFileConstant {
public static final String PATH_SEPARATOR = "/";
/**
* 系统产生的临时文件路径
*/
public static String TEMP_FILE_PATH = "/.zfile/temp/";
/**
* 最大支持文件大小为 ? MB 的音乐文件解析封面, 歌手等信息.
*/
public static Long AUDIO_MAX_FILE_SIZE_MB = 1L;
/**
* 最大支持文本文件大小为 ? KB 的文件内容.
*/
public static Long TEXT_MAX_FILE_SIZE_KB = 100L;
@Autowired(required = false)
public void setTmpFilePath(@Value("${zfile.temp.path}") String tmpFilePath) {
ZFileConstant.TEMP_FILE_PATH = tmpFilePath;
}
@Autowired(required = false)
public void setAudioMaxFileSizeMb(@Value("${zfile.preview.audio.maxFileSizeMb}") Long maxFileSizeMb) {
ZFileConstant.AUDIO_MAX_FILE_SIZE_MB = maxFileSizeMb;
}
@Autowired(required = false)
public void setTextMaxFileSizeMb(@Value("${zfile.preview.text.maxFileSizeKb}") Long maxFileSizeKb) {
ZFileConstant.TEXT_MAX_FILE_SIZE_KB = maxFileSizeKb;

View File

@@ -1,4 +1,4 @@
package im.zhaojun.zfile.common.controller.front;
package im.zhaojun.zfile.core.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

View File

@@ -1,9 +1,9 @@
package im.zhaojun.zfile.admin.controller;
package im.zhaojun.zfile.core.controller;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ZipUtil;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import im.zhaojun.zfile.common.util.FileResponseUtil;
import im.zhaojun.zfile.core.util.FileResponseUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;

View File

@@ -0,0 +1,14 @@
package im.zhaojun.zfile.core.exception;
/**
* 非法使用下载链接异常.
*
* @author zhaojun
*/
public class IllegalDownloadLinkException extends ZFileRuntimeException {
public IllegalDownloadLinkException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,14 @@
package im.zhaojun.zfile.core.exception;
/**
* 系统初始化异常
*
* @author zhaojun
*/
public class InstallSystemException extends ZFileRuntimeException {
public InstallSystemException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,14 @@
package im.zhaojun.zfile.core.exception;
/**
* 无效的直链异常
*
* @author zhaojun
*/
public class InvalidShortLinkException extends ZFileRuntimeException {
public InvalidShortLinkException(String message) {
super(message);
}
}

View File

@@ -1,11 +1,11 @@
package im.zhaojun.zfile.common.exception;
package im.zhaojun.zfile.core.exception;
/**
* 登陆验证码验证异常
*
* @author zhaojun
*/
public class LoginVerifyException extends RuntimeException {
public class LoginVerifyException extends ZFileRuntimeException {
public LoginVerifyException(String message) {
super(message);

View File

@@ -1,4 +1,4 @@
package im.zhaojun.zfile.common.exception;
package im.zhaojun.zfile.core.exception;
/**

View File

@@ -0,0 +1,14 @@
package im.zhaojun.zfile.core.exception;
/**
* 文件预览异常类
*
* @author zhaojun
*/
public class PreviewException extends ZFileRuntimeException {
public PreviewException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,42 @@
package im.zhaojun.zfile.core.exception;
import im.zhaojun.zfile.core.util.CodeMsg;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* Service 层异常
* 所有 message 均为系统日志打印输出, CodeMsg 中的消息才是返回给客户端的消息.
*
* @author zhaojun
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ServiceException extends RuntimeException {
private CodeMsg codeMsg;
public ServiceException(CodeMsg codeMsg) {
this.codeMsg = codeMsg;
}
public ServiceException(String message, CodeMsg codeMsg) {
super(message);
this.codeMsg = codeMsg;
}
public ServiceException(String message, Throwable cause, CodeMsg codeMsg) {
super(message, cause);
this.codeMsg = codeMsg;
}
public ServiceException(Throwable cause, CodeMsg codeMsg) {
super(cause);
this.codeMsg = codeMsg;
}
public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, CodeMsg codeMsg) {
super(message, cause, enableSuppression, writableStackTrace);
this.codeMsg = codeMsg;
}
}

View File

@@ -1,6 +1,6 @@
package im.zhaojun.zfile.admin.exception;
package im.zhaojun.zfile.core.exception;
import im.zhaojun.zfile.admin.model.param.IStorageParam;
import im.zhaojun.zfile.module.storage.model.param.IStorageParam;
import lombok.Getter;
/**

View File

@@ -0,0 +1,60 @@
package im.zhaojun.zfile.core.exception;
import im.zhaojun.zfile.core.util.CodeMsg;
import lombok.EqualsAndHashCode;
import lombok.Getter;
/**
* 存储源异常
*
* @author zhaojun
*/
@EqualsAndHashCode(callSuper = true)
@Getter
public class StorageSourceException extends ServiceException {
/**
* 是否使用异常消息进行接口返回,如果是则取异常的 message, 否则取 CodeMsg 中的 message
*/
private boolean responseExceptionMessage;
/**
* 存储源 ID
*/
private final Integer storageId;
public StorageSourceException(CodeMsg codeMsg, Integer storageId, String message) {
super(message, codeMsg);
this.storageId = storageId;
}
public StorageSourceException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) {
super(message, cause, codeMsg);
this.storageId = storageId;
}
/**
* 根据 responseExceptionMessage 判断使用异常消息进行接口返回,如果是则取异常的 message, 否则取 CodeMsg 中的 message
*
* @return 异常消息
*/
public String getResultMessage() {
return responseExceptionMessage ? super.getMessage() : super.getCodeMsg().getMsg();
}
/**
* 设置值是否使用异常消息进行接口返回
*
* @param responseExceptionMessage
* 是否使用异常消息进行接口返回
*
* @return 当前对象
*/
public StorageSourceException setResponseExceptionMessage(boolean responseExceptionMessage) {
this.responseExceptionMessage = responseExceptionMessage;
return this;
}
}

View File

@@ -1,11 +1,11 @@
package im.zhaojun.zfile.common.exception;
package im.zhaojun.zfile.core.exception;
/**
* 存储源不支持代理上传异常
*
* @author zhaojun
*/
public class StorageSourceNotSupportProxyUploadException extends RuntimeException {
public class StorageSourceNotSupportProxyUploadException extends ZFileRuntimeException {
public StorageSourceNotSupportProxyUploadException(String message) {
super(message);

View File

@@ -1,4 +1,4 @@
package im.zhaojun.zfile.common.exception;
package im.zhaojun.zfile.core.exception;
import lombok.Getter;
@@ -10,10 +10,6 @@ public class StorageSourceRefreshTokenException extends RuntimeException {
private final Integer storageId;
public StorageSourceRefreshTokenException(Integer storageId) {
this.storageId = storageId;
}
public StorageSourceRefreshTokenException(String message, Integer storageId) {
super(message);
this.storageId = storageId;

View File

@@ -0,0 +1,17 @@
package im.zhaojun.zfile.core.exception;
/**
* 文件解析异常
*
* @author zhaojun
*/
public class TextParseException extends ZFileRuntimeException {
public TextParseException(String message) {
super(message);
}
public TextParseException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,26 @@
package im.zhaojun.zfile.core.exception;
/**
* @author zhaojun
*/
public class ZFileRetryException extends RuntimeException {
public ZFileRetryException() {
}
public ZFileRetryException(String message) {
super(message);
}
public ZFileRetryException(String message, Throwable cause) {
super(message, cause);
}
public ZFileRetryException(Throwable cause) {
super(cause);
}
public ZFileRetryException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -0,0 +1,15 @@
package im.zhaojun.zfile.core.exception;
/**
* @author zhaojun
*/
public class ZFileRuntimeException extends RuntimeException {
public ZFileRuntimeException(String message) {
super(message);
}
public ZFileRuntimeException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,25 @@
package im.zhaojun.zfile.core.exception.file;
import im.zhaojun.zfile.core.exception.StorageSourceException;
import im.zhaojun.zfile.core.util.CodeMsg;
/**
* 无效的存储源异常
*
* @author zhaojun
*/
public class InvalidStorageSourceException extends StorageSourceException {
public InvalidStorageSourceException(String message) {
super(CodeMsg.STORAGE_SOURCE_NOT_FOUND, null, message);
}
public InvalidStorageSourceException(Integer storageId) {
super(CodeMsg.STORAGE_SOURCE_NOT_FOUND, storageId, CodeMsg.STORAGE_SOURCE_NOT_FOUND.getMsg());
}
public InvalidStorageSourceException(Integer storageId, String message) {
super(CodeMsg.STORAGE_SOURCE_NOT_FOUND, storageId, message);
}
}

View File

@@ -0,0 +1,21 @@
package im.zhaojun.zfile.core.exception.file.init;
import im.zhaojun.zfile.core.exception.StorageSourceException;
import im.zhaojun.zfile.core.util.CodeMsg;
/**
* 存储源初始化异常
*
* @author zhaojun
*/
public class InitializeStorageSourceException extends StorageSourceException {
public InitializeStorageSourceException(CodeMsg codeMsg, Integer storageId, String message) {
super(codeMsg, storageId, message);
}
public InitializeStorageSourceException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) {
super(codeMsg, storageId, message, cause);
}
}

View File

@@ -0,0 +1,24 @@
package im.zhaojun.zfile.core.exception.file.operator;
import im.zhaojun.zfile.core.exception.StorageSourceException;
import im.zhaojun.zfile.core.util.CodeMsg;
/**
* 禁止服务器代理下载异常
*
* @author zhaojun
*/
public class DisableProxyDownloadException extends StorageSourceException {
public DisableProxyDownloadException(CodeMsg codeMsg, Integer storageId) {
super(codeMsg, storageId, null);
}
public DisableProxyDownloadException(CodeMsg codeMsg, Integer storageId, String message) {
super(codeMsg, storageId, message);
}
public DisableProxyDownloadException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) {
super(codeMsg, storageId, message, cause);
}
}

View File

@@ -0,0 +1,18 @@
package im.zhaojun.zfile.core.exception.file.operator;
import im.zhaojun.zfile.core.exception.StorageSourceException;
import im.zhaojun.zfile.core.util.CodeMsg;
import lombok.Getter;
/**
* 存储源文件操作异常
* @author zhaojun
*/
@Getter
public class StorageSourceFileOperatorException extends StorageSourceException {
public StorageSourceFileOperatorException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) {
super(codeMsg, storageId, message, cause);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,12 @@
package im.zhaojun.zfile.core.exception.http;
/**
* Http 请求状态码异常 (返回状态码为 5xx 抛出此异常)
* @author zhaojun
*/
public class HttpResponseStatusErrorException extends RuntimeException {
public HttpResponseStatusErrorException(String message) {
super(message);
}
}

View File

@@ -1,5 +1,6 @@
package im.zhaojun.zfile.common.filter;
package im.zhaojun.zfile.core.filter;
import cn.hutool.core.util.ObjectUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.web.cors.CorsUtils;
@@ -25,8 +26,9 @@ public class CorsFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, httpServletRequest.getHeader(HttpHeaders.ORIGIN));
String header = httpServletRequest.getHeader(HttpHeaders.ORIGIN);
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, ObjectUtil.defaultIfNull(header, "*"));
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Origin, X-Requested-With, Content-Type, Accept, zfile-token, axios-request");
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS");
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "false");

View File

@@ -0,0 +1,41 @@
package im.zhaojun.zfile.core.filter;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.extra.servlet.ServletUtil;
import im.zhaojun.zfile.core.constant.MdcConstant;
import org.slf4j.MDC;
import javax.servlet.Filter;
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;
/**
* @author zhaojun
*/
@WebFilter(urlPatterns = "/*")
public class MDCFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
MDC.put(MdcConstant.TRACE_ID, IdUtil.fastUUID());
MDC.put(MdcConstant.IP, ServletUtil.getClientIP(httpServletRequest));
MDC.put(MdcConstant.USER, StpUtil.isLogin() ? StpUtil.getLoginIdAsString() : "anonymous");
try {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} finally {
MDC.clear();
}
}
}

View File

@@ -0,0 +1,22 @@
package im.zhaojun.zfile.core.httpclient;
import im.zhaojun.zfile.core.httpclient.logging.HttpLoggingInterceptor;
import okhttp3.OkHttpClient;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
/**
* 自建 OkHTTP3 客户端工厂类, 增加日志输出拦截器.
* @author zhaojun
*/
public class ZFileOkHttp3ClientHttpRequestFactory extends OkHttp3ClientHttpRequestFactory {
public ZFileOkHttp3ClientHttpRequestFactory() {
// 使用 OkHttp3 作为底层请求库, 并设置重试机制和日志拦截器
super(new OkHttpClient()
.newBuilder()
.addNetworkInterceptor(new HttpLoggingInterceptor(HttpLoggingInterceptor.Logger.DEBUG)
.setLevel(HttpLoggingInterceptor.Level.HEADERS))
.build());
}
}

View File

@@ -0,0 +1,340 @@
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.zhaojun.zfile.core.httpclient.logging;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Connection;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.internal.http.HttpHeaders;
import okhttp3.internal.platform.Platform;
import okio.Buffer;
import okio.BufferedSource;
import okio.GzipSource;
import java.io.EOFException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import static okhttp3.internal.platform.Platform.INFO;
/**
* 此类代码来源于 logging-interceptor.
* <br>
* An OkHttp interceptor which logs request and response information. Can be applied as an
* {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain
* OkHttpClient#networkInterceptors() network interceptor}. <p> The format of the logs created by
* this class should not be considered stable and may change slightly between releases. If you need
* a stable logging format, use your own interceptor.
* <br>
* @author zhaojun
*/
@Slf4j
public final class HttpLoggingInterceptor implements Interceptor {
private static final Charset UTF8 = StandardCharsets.UTF_8;
public enum Level {
/** No logs. */
NONE,
/**
* Logs request and response lines.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1 (3-byte body)
*
* <-- 200 OK (22ms, 6-byte body)
* }</pre>
*/
BASIC,
/**
* Logs request and response lines and their respective headers.
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <-- END HTTP
* }</pre>
*/
HEADERS,
/**
* Logs request and response lines and their respective headers and bodies (if present).
*
* <p>Example:
* <pre>{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
*
* Hi?
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
*
* Hello!
* <-- END HTTP
* }</pre>
*/
BODY
}
public interface Logger {
void log(String message);
/** A {@link Logger} defaults output appropriate for the current platform. */
Logger DEFAULT = message -> Platform.get().log(INFO, message, null);
Logger DEBUG = log::debug;
Logger TRACE = log::trace;
}
public HttpLoggingInterceptor() {
this(Logger.DEFAULT);
}
public HttpLoggingInterceptor(Logger logger) {
this.logger = logger;
}
private final Logger logger;
private volatile Set<String> headersToRedact = Collections.emptySet();
public void redactHeader(String name) {
Set<String> newHeadersToRedact = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
newHeadersToRedact.addAll(headersToRedact);
newHeadersToRedact.add(name);
headersToRedact = newHeadersToRedact;
}
private volatile Level level = Level.NONE;
/** Change the level at which this interceptor logs. */
public HttpLoggingInterceptor setLevel(Level level) {
if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead.");
this.level = level;
return this;
}
public Level getLevel() {
return level;
}
@Override public Response intercept(Chain chain) throws IOException {
Level level = this.level;
Request request = chain.request();
if (level == Level.NONE) {
return chain.proceed(request);
}
boolean logBody = level == Level.BODY;
boolean logHeaders = logBody || level == Level.HEADERS;
RequestBody requestBody = request.body();
boolean hasRequestBody = requestBody != null;
Connection connection = chain.connection();
String requestStartMessage = "--> "
+ request.method()
+ ' ' + request.url()
+ (connection != null ? " " + connection.protocol() : "");
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
}
logger.log(requestStartMessage);
if (logHeaders) {
if (hasRequestBody) {
// Request body headers are only present when installed as a network interceptor. Force
// them to be included (when available) so there values are known.
if (requestBody.contentType() != null) {
logger.log("Content-Type: " + requestBody.contentType());
}
if (requestBody.contentLength() != -1) {
logger.log("Content-Length: " + requestBody.contentLength());
}
}
Headers headers = request.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
String name = headers.name(i);
// Skip headers from the request body as they are explicitly logged above.
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
logHeader(headers, i);
}
}
if (!logBody || !hasRequestBody) {
logger.log("--> END " + request.method());
} else if (bodyHasUnknownEncoding(request.headers())) {
logger.log("--> END " + request.method() + " (encoded body omitted)");
} else if (requestBody.isDuplex()) {
logger.log("--> END " + request.method() + " (duplex request body omitted)");
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
logger.log("");
if (isPlaintext(buffer)) {
logger.log(buffer.readString(charset));
logger.log("--> END " + request.method()
+ " (" + requestBody.contentLength() + "-byte body)");
} else {
logger.log("--> END " + request.method() + " (binary "
+ requestBody.contentLength() + "-byte body omitted)");
}
}
}
long startNs = System.nanoTime();
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
logger.log("<-- HTTP FAILED: " + e);
throw e;
}
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
logger.log("<-- "
+ response.code()
+ (response.message().isEmpty() ? "" : ' ' + response.message())
+ ' ' + response.request().url()
+ " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')');
if (logHeaders) {
Headers headers = response.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
logHeader(headers, i);
}
if (!logBody || !HttpHeaders.hasBody(response)) {
logger.log("<-- END HTTP");
} else if (bodyHasUnknownEncoding(response.headers())) {
logger.log("<-- END HTTP (encoded body omitted)");
} else {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.getBuffer();
Long gzippedLength = null;
if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) {
gzippedLength = buffer.size();
try (GzipSource gzippedResponseBody = new GzipSource(buffer.clone())) {
buffer = new Buffer();
buffer.writeAll(gzippedResponseBody);
}
}
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
if (!isPlaintext(buffer)) {
logger.log("");
logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
return response;
}
if (contentLength != 0) {
logger.log("");
logger.log(buffer.clone().readString(charset));
}
if (gzippedLength != null) {
logger.log("<-- END HTTP (" + buffer.size() + "-byte, "
+ gzippedLength + "-gzipped-byte body)");
} else {
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
}
}
}
return response;
}
private void logHeader(Headers headers, int i) {
String value = headersToRedact.contains(headers.name(i)) ? "██" : headers.value(i);
logger.log(headers.name(i) + ": " + value);
}
/**
* Returns true if the body in question probably contains human readable text. Uses a small sample
* of code points to detect unicode control characters commonly used in binary file signatures.
*/
static boolean isPlaintext(Buffer buffer) {
try {
Buffer prefix = new Buffer();
long byteCount = buffer.size() < 64 ? buffer.size() : 64;
buffer.copyTo(prefix, 0, byteCount);
for (int i = 0; i < 16; i++) {
if (prefix.exhausted()) {
break;
}
int codePoint = prefix.readUtf8CodePoint();
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
return false;
}
}
return true;
} catch (EOFException e) {
return false; // Truncated UTF-8 sequence.
}
}
private static boolean bodyHasUnknownEncoding(Headers headers) {
String contentEncoding = headers.get("Content-Encoding");
return contentEncoding != null
&& !contentEncoding.equalsIgnoreCase("identity")
&& !contentEncoding.equalsIgnoreCase("gzip");
}
}

View File

@@ -0,0 +1,24 @@
package im.zhaojun.zfile.core.model.request;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* @author zhaojun
*/
@Data
public class PageQueryRequest {
@ApiModelProperty(value="分页页数")
private Integer page = 1;
@ApiModelProperty(value="每页条数")
private Integer limit = 10;
@ApiModelProperty(value="排序字段")
private String orderBy = "create_date";
@ApiModelProperty(value="排序顺序")
private String orderDirection = "desc";
}

View File

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

View File

@@ -1,7 +1,6 @@
package im.zhaojun.zfile.common.util;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
package im.zhaojun.zfile.core.util;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
@@ -21,7 +20,7 @@ public class ClassUtils {
*/
public static Class<?> getClassFirstGenericsParam(Class<?> clazz) {
Type genericSuperclass = clazz.getGenericSuperclass();
Type actualTypeArgument = ((ParameterizedTypeImpl) genericSuperclass).getActualTypeArguments()[0];
Type actualTypeArgument = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
return (Class<?>) actualTypeArgument;
}

View File

@@ -0,0 +1,76 @@
package im.zhaojun.zfile.core.util;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* @author zhaojun
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class CodeMsg {
/**
* 错误码
* <p>
* 均为 5 位数, 如 00000, 10100, 20105 等.
* <br>
* 第一位表示错误类型, 4 为用户请求输入错误, 5 为服务端处理错误, 6 为警告信息
* <br>
* 第二位到第三位为二级类型
* <br>
* 第四位到第五位为具体错误代码, 根据业务场景自行定义
* <p>
* 以上三种类型均不允许重复, 且都需保持递增.
*/
private String code;
/**
* 错误消息
*/
private String msg;
// 通用返回值
public static CodeMsg SUCCESS = new CodeMsg("00000", "success");
public static CodeMsg BAD_REQUEST = new CodeMsg("40000", "非法请求");
public static CodeMsg ERROR = new CodeMsg("50000", "服务端异常");
// -------------- 用户输入级错误 --------------
public static CodeMsg REQUIRED_PASSWORD = new CodeMsg("40100", "请输入密码");
public static CodeMsg PASSWORD_FAULT = new CodeMsg("40101", "密码输入错误");
public static CodeMsg STORAGE_SOURCE_NOT_FOUND = new CodeMsg("40102", "无效的或初始化失败的存储源");
public static CodeMsg STORAGE_SOURCE_FORBIDDEN = new CodeMsg("40103", "无权访问存储源");
public static CodeMsg STORAGE_SOURCE_FILE_FORBIDDEN = new CodeMsg("40104", "无权访问该目录");
public static CodeMsg STORAGE_SOURCE_ILLEGAL_OPERATION = new CodeMsg("40105", "非法操作");
// -------------- 服务端处理错误 --------------
// 初始化相关错误
public static CodeMsg STORAGE_SOURCE_INIT_FAIL = new CodeMsg("50100", "初始化存储源失败");
public static CodeMsg STORAGE_SOURCE_INIT_STORAGE_CONFIG_FAIL = new CodeMsg("50101", "初始化存储源参数失败");
public static CodeMsg STORAGE_SOURCE_INIT_STORAGE_PARAM_FIELD_FAIL = new CodeMsg("50102", "填充存储源字段失败");
// 文件操作相关错误
public static CodeMsg STORAGE_SOURCE_FILE_NEW_FOLDER_FAIL = new CodeMsg("50201", "新建文件夹失败");
public static CodeMsg STORAGE_SOURCE_FILE_DELETE_FAIL = new CodeMsg("50202", "删除失败");
public static CodeMsg STORAGE_SOURCE_FILE_RENAME_FAIL = new CodeMsg("50203", "重命名失败");
public static CodeMsg STORAGE_SOURCE_FILE_GET_UPLOAD_FAIL = new CodeMsg("50204", "获取上传链接失败");
public static CodeMsg STORAGE_SOURCE_FILE_PROXY_UPLOAD_FAIL = new CodeMsg("50205", "文件上传失败");
public static CodeMsg STORAGE_SOURCE_FILE_PROXY_DOWNLOAD_FAIL = new CodeMsg("50206", "文件下载失败");
public static CodeMsg STORAGE_SOURCE_FILE_GET_ITEM_FAIL = new CodeMsg("50207", "文件不存在或请求异常");
public static CodeMsg STORAGE_SOURCE_FILE_DISABLE_PROXY_DOWNLOAD = new CodeMsg("50208", "非法操作, 当前文件不支持此类下载方式");
}

View File

@@ -1,4 +1,4 @@
package im.zhaojun.zfile.common.util;
package im.zhaojun.zfile.core.util;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ClassUtil;

View File

@@ -1,8 +1,8 @@
package im.zhaojun.zfile.common.util;
package im.zhaojun.zfile.core.util;
import cn.hutool.core.comparator.CompareUtil;
import im.zhaojun.zfile.home.model.result.FileItemResult;
import im.zhaojun.zfile.home.model.enums.FileTypeEnum;
import im.zhaojun.zfile.module.storage.model.result.FileItemResult;
import im.zhaojun.zfile.module.storage.model.enums.FileTypeEnum;
import java.util.Comparator;

View File

@@ -1,4 +1,4 @@
package im.zhaojun.zfile.common.util;
package im.zhaojun.zfile.core.util;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
@@ -58,7 +58,6 @@ public class FileResponseUtil {
.contentLength(file.length())
.contentType(mediaType)
.body(new InputStreamResource(FileUtil.getInputStream(file)));
// .body(new FileSystemResource(file));
}
}

View File

@@ -1,9 +1,10 @@
package im.zhaojun.zfile.common.util;
package im.zhaojun.zfile.core.util;
import cn.hutool.core.io.FileUtil;
import im.zhaojun.zfile.common.constant.ZFileConstant;
import im.zhaojun.zfile.common.exception.PreviewException;
import im.zhaojun.zfile.common.exception.TextParseException;
import cn.hutool.core.util.StrUtil;
import im.zhaojun.zfile.core.constant.ZFileConstant;
import im.zhaojun.zfile.core.exception.PreviewException;
import im.zhaojun.zfile.core.exception.TextParseException;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
@@ -38,7 +39,7 @@ public class HttpUtil {
try {
result = cn.hutool.http.HttpUtil.get(url);
} catch (Exception e) {
throw new TextParseException("文件解析异常, 请求 url = " + url + ", 异常信息为 = " + e.getMessage());
throw new TextParseException(StrUtil.format("获取文件内容失败, URL: {}", url), e);
}
return result == null ? "" : result;

Some files were not shown because too many files have changed in this diff Show More