Compare commits

...

113 Commits
0.7.1 ... 2.4

Author SHA1 Message Date
zhaojun1998
3720dc6aa9 🔖 发布 2.4 版 2020-04-20 22:40:06 +08:00
zhaojun1998
3ada172be2 💄 更新页面 2020-04-20 22:39:42 +08:00
zhaojun1998
15f8fbb49b 🐛 修复阿里云腾讯云私有空间时, 密码文件无法加载的 BUG 2020-04-20 22:37:12 +08:00
zhaojun1998
547e688d38 🏗️ 增强系统校验 2020-04-20 21:58:32 +08:00
zhaojun1998
708eb33d0e 🐛 修复多盘中是否多个 OneDrive 导致无法正常加载的 BUG 2020-04-20 21:57:25 +08:00
zhaojun1998
e954b725b1 📝 更新文档 2020-04-19 15:26:04 +08:00
zhaojun1998
c89cb4e495 📝 更新文档 2020-04-19 15:24:36 +08:00
zhaojun1998
286e9775f6 🔧 更新配置文件, 升级后切换默认数据库路径 2020-04-19 14:35:53 +08:00
zhaojun1998
60513abe6a 💄 更新页面, 删除缓存管理模块 2020-04-19 14:35:21 +08:00
zhaojun1998
f7a8c9faa2 🔖 发布 2.3 版 2020-04-19 12:14:27 +08:00
zhaojun1998
55e0d32ef8 🏗️ 架构调整, 支持多个驱动器. 2020-04-19 12:13:12 +08:00
zhaojun1998
4c0bacba31 🔨 重构代码, 将每个存储策略的表单设置改为 Java 配置 2020-04-04 19:57:09 +08:00
zhaojun1998
61128f2677 🎨 移除无用代码 2020-04-04 15:58:33 +08:00
zhaojun1998
3866526b95 📝 补充注释 2020-04-04 15:57:22 +08:00
zhaojun1998
250955fac9 📝 更新文档 2020-04-04 09:50:06 +08:00
zhaojun1998
3249266cd1 🔖 发布 2.2 版 2020-03-08 11:48:48 +08:00
zhaojun1998
1a6235df73 🔨 重构代码, 去除无效引用。 2020-03-08 11:46:31 +08:00
zhaojun1998
430aee2b7f 🔨 重构代码, 去除无效引用。 2020-03-08 11:43:42 +08:00
zhaojun1998
de2f7e4b80 🔨 重构代码, 优化目录结构 2020-03-08 11:11:12 +08:00
zhaojun1998
6dfcc409ac 更新页面 2020-03-08 11:07:01 +08:00
zhaojun1998
78f795e1cb 🔖 发布 2.1 版 2020-03-07 18:39:33 +08:00
zhaojun1998
c69ee0f356 🐛 修复 S3 协议对象存储, 在私有空间下, 无法下载的 BUG. 2020-03-07 13:10:42 +08:00
zhaojun1998
39475de789 🐛 修复 S3 协议对象存储, 在私有空间下, 无法下载的 BUG. 2020-03-07 13:10:21 +08:00
zhaojun1998
bd71712765 🐛 修复无法刷新单个缓存的 BUG 2020-03-07 13:08:42 +08:00
赵俊
8698686a47 Update application.yml 2020-03-01 14:04:14 +08:00
zhaojun1998
762c67ee37 更新文档 2020-02-29 16:04:19 +08:00
zhaojun1998
7bf3a29c17 🔖 发布 2.0 版 2020-02-29 15:48:57 +08:00
zhaojun1998
6ee5002f0c 🔒 修复任意用户名均可登陆后台的安全问题. 2020-02-29 15:47:24 +08:00
zhaojun1998
fadc64add4 💄 更新页面, 修复滚动分页 BUG. 2020-02-29 15:46:56 +08:00
zhaojun1998
234f43846f 优化分页功能 2020-02-29 15:45:34 +08:00
zhaojun1998
67e42d9753 🐛 修复 OneDrive 获取文档区或文件夹密码时, 链接超时导致异常的 BUG. 2020-02-29 15:45:11 +08:00
zhaojun1998
04f94b4bf5 🏗️ 缓存架构调整, 增强稳定性 2020-02-29 15:43:56 +08:00
zhaojun1998
d29c498457 更新配置自描述文件 2020-02-27 23:01:38 +08:00
zhaojun1998
5aa45b44b2 🔖 发布 1.9 版 2020-02-26 21:18:55 +08:00
zhaojun1998
8273a645f2 📝 更新文档 2020-02-26 21:16:49 +08:00
zhaojun1998
46f292cc9b 🔧 更新配置文件, 增加是否开启缓存自动刷新控制 2020-02-26 21:15:45 +08:00
zhaojun1998
261d48059e 增强 FTP 功能, 提高稳定性 2020-02-26 21:14:26 +08:00
zhaojun1998
79f931c51b 增强 OneDrive 调用, 如调用失败, 尝试重新获取 token 后, 再次请求. 2020-02-26 21:14:07 +08:00
zhaojun1998
399e961a65 🏗️ 缓存架构调整, 改为自实现缓存, 移除第三方依赖. 2020-02-26 21:13:08 +08:00
zhaojun1998
3e61d7d146 增加 UFILE 支持 2020-02-26 21:09:42 +08:00
zhaojun1998
ace95d9071 💄 更新页面 2020-02-26 21:09:02 +08:00
zhaojun1998
60d2a2b986 添加 github issue template 2020-02-23 12:07:16 +08:00
zhaojun1998
69d5661e06 添加 github issue template 2020-02-23 12:06:28 +08:00
zhaojun1998
01d11dfc23 移除无用依赖 2020-02-22 15:23:39 +08:00
zhaojun1998
d35e3ecd93 🔖 发布 1.8 版 2020-02-22 15:21:39 +08:00
zhaojun1998
9e5a3e5385 💄 更新页面 2020-02-22 15:18:15 +08:00
zhaojun1998
b62163b4e8 ⬆️ 升级 hutool 依赖版本 2020-02-22 15:16:39 +08:00
zhaojun1998
595a00f067 🐛 修复 FTP 加载 BUG 2020-02-22 15:16:11 +08:00
zhaojun1998
a759d9fe44 增加系统监控及日志下载功能 2020-02-22 12:53:35 +08:00
zhaojun1998
7a24fd10e0 ✏️ 修复拼写错误 2020-02-22 09:35:51 +08:00
zhaojun1998
7e04a817d7 🔖 发布 1.7 版 2020-02-19 21:45:48 +08:00
zhaojun1998
791967f45e 💄 更新页面 2020-02-19 21:43:17 +08:00
zhaojun1998
d789436a16 ✏️ 修复拼写错误 2020-02-19 21:23:09 +08:00
zhaojun1998
96ab8ff7dd 🔖 发布 1.6 版 2020-02-18 21:10:25 +08:00
zhaojun1998
8809aca170 📝 更新页面 2020-02-18 21:08:53 +08:00
zhaojun1998
97106383b6 🐛 修复使用本地存储时, 文件名中包含 + 引发的无法下载的 BUG. 2020-02-18 20:10:20 +08:00
zhaojun1998
208da95234 🐛 修复不同操作系统, 可能出现的编码问题, 导致导入的数据库文件乱码. 2020-02-18 20:09:32 +08:00
zhaojun1998
d4c843f5f5 🐛 修复 OneDrive 文件夹中包含 + 号, 且文件夹中的内容大于 200 个时, 请求出错的 BUG. 2020-02-18 20:02:37 +08:00
zhaojun1998
d273fc9f12 📝 更新文档 2020-02-15 18:39:56 +08:00
zhaojun1998
0b4a38218c 🔖 发布 1.5 版 2020-02-15 18:25:41 +08:00
zhaojun1998
2f57c5b5cc 新增功能: 自定义表格大小, 显示操作按钮, 显示文档, 公告信息 2020-02-15 18:22:37 +08:00
zhaojun1998
610f68295f 📝 更新页面 2020-02-15 18:22:01 +08:00
zhaojun1998
39ced8eb84 📝 更新页面 2020-02-15 18:05:43 +08:00
zhaojun1998
368b7a1df2 优化排序方式为自然排序 2020-02-14 20:22:22 +08:00
zhaojun1998
8e2107cd46 📝 更新开发计划 2020-02-11 18:27:34 +08:00
zhaojun1998
fa32a33371 📝 更新页面 2020-02-09 21:49:27 +08:00
zhaojun1998
59116a9414 🔖 发布 1.4.1 版 2020-02-09 21:47:27 +08:00
zhaojun1998
b2c732a389 🐛 修复 URL 中包含特殊字符, 返回 400 错误的 BUG. 2020-02-09 21:35:25 +08:00
zhaojun1998
0e1ffef92b 🔖 发布 1.4 版 2020-02-09 18:06:50 +08:00
zhaojun1998
a5b19d3577 新增获取直链下载功能. 2020-02-09 18:06:42 +08:00
zhaojun1998
185c84dd79 新增获取直链下载功能. 2020-02-09 18:05:56 +08:00
zhaojun1998
d554dd298c 优化 OneDrive 相关代码结构. 2020-02-09 18:05:01 +08:00
zhaojun1998
aa6ecf0aaa 优化 OneDrive 相关代码结构. 2020-02-09 18:04:33 +08:00
zhaojun1998
83692718e3 📝 更新页面 2020-02-09 18:01:57 +08:00
zhaojun1998
030bd95941 🔖 发布 1.3 版 2020-02-08 17:47:37 +08:00
zhaojun1998
8722d11ac3 📝 更新页面 2020-02-08 17:46:33 +08:00
zhaojun1998
0131ff02c0 🐛 修复头部文件和密码文件, 在开启缓存的状态下, 仅第一次生效的 BUG. 2020-02-08 17:43:02 +08:00
zhaojun1998
2d115bf1c6 更改本地存储, 文件下载时, 文件不存在的响应状态码为 404. 2020-02-08 17:14:01 +08:00
zhaojun1998
946113216d 🔖 发布 1.2.1 版 2020-02-03 20:57:07 +08:00
zhaojun1998
77b05c6dac 🐛 修复 OneDrive 的 Token 某些情况下, 可能超出 2000 字符的 BUG 2020-02-03 20:52:49 +08:00
zhaojun1998
07c9fca210 🔖 发布 1.2 版 2020-02-01 21:54:32 +08:00
zhaojun1998
27cf61693a 更新前端页面, 修复搜索模式下无法进入文件夹和滚动加载的 BUG 2020-02-01 21:54:20 +08:00
zhaojun1998
37e1aa1fec 🐛 修复 OneDrive 文件夹包含特殊符号时, 获取下一页数据 URL 中重复进行了 URL 编码的 BUG. 2020-02-01 21:51:15 +08:00
zhaojun1998
31b54a3c05 调整 OneDrive Token 自动刷新时间为 15 分钟. 2020-02-01 21:50:08 +08:00
zhaojun1998
589f07c103 🔖 发布 1.1 版 2020-01-31 11:52:27 +08:00
zhaojun1998
fe8b60d873 调整日志级别 2020-01-31 11:52:21 +08:00
zhaojun1998
1734619eac 📝 更新页面 2020-01-31 11:50:55 +08:00
zhaojun1998
f5724dc9ab OneDrive 基础路径支持 2020-01-31 11:21:36 +08:00
zhaojun1998
f7bb147b71 📝 更新文档 2020-01-30 19:31:50 +08:00
zhaojun1998
47fc1bc2df 🔖 发布 1.0 版 2020-01-30 18:06:09 +08:00
zhaojun1998
45172f69ba 📝 更新文档 2020-01-30 18:05:56 +08:00
zhaojun1998
9566b138ff 📝 更新页面 2020-01-30 17:58:26 +08:00
zhaojun1998
e15b6c2242 更新配置文件, 增加 h2 console, 便与调试. 2020-01-30 16:52:20 +08:00
zhaojun1998
acc41511e0 S3 协议新增是否为私有空间支持 2020-01-30 16:51:05 +08:00
zhaojun1998
b882b87405 数据库文件初始化更新, 新增 S3 通用协议支持 2020-01-30 16:50:37 +08:00
zhaojun1998
518b5170ae 优化代码 2020-01-30 16:48:37 +08:00
zhaojun1998
8bfac6d9ac 新增 S3 协议通用支持 2020-01-30 16:47:58 +08:00
zhaojun1998
3ffdb4f1b2 清洁代码 2020-01-30 16:47:03 +08:00
zhaojun1998
47509450a0 📝 更新文档, 修改 Debian/Ubuntu 安装命令. 2020-01-29 18:47:38 +08:00
zhaojun1998
812fd18aac 🐛 修复 OneDrive 世纪互联版自动刷新 REFRESH_TOKEN 失败的 BUG 2020-01-29 16:24:00 +08:00
zhaojun1998
77a13cf8ad 📝 更新文档, 新增预览图片 2020-01-29 13:56:20 +08:00
zhaojun1998
4c914793b0 📝 更新文档 2020-01-29 13:43:19 +08:00
zhaojun1998
5698cfb2d3 🔖 发布 0.9 版 2020-01-29 13:41:42 +08:00
zhaojun1998
3cd5f8f9a7 调整显示顺序 2020-01-29 12:58:03 +08:00
zhaojun1998
76747771de OneDrive 世纪互联支持 2020-01-29 12:53:38 +08:00
zhaojun1998
cfacd39210 优化代码, 增强健壮性 2020-01-29 12:52:07 +08:00
zhaojun1998
90cd13f2c3 🔖 发布 0.8 版 2020-01-28 15:19:04 +08:00
zhaojun1998
018a68246e 💄 更新页面 2020-01-28 15:16:27 +08:00
zhaojun1998
b6a2e3ccb8 添加获取指定路径文件信息的 API 2020-01-28 14:55:06 +08:00
zhaojun1998
38b811f8e6 🐛 新增 '搜索包含加密文件' 支持. 2020-01-28 13:25:28 +08:00
zhaojun1998
6922fa2195 添加自定义 JS, CSS 支持 2020-01-28 13:14:23 +08:00
zhaojun1998
4bca6cf7a5 移除尾部说明文件支持 2020-01-28 13:13:52 +08:00
zhaojun1998
c3484426ab 🐛 修复 OneDrive 教育版和部分国际版无法正常获取文件的 BUG 2020-01-28 10:18:36 +08:00
170 changed files with 4727 additions and 2446 deletions

48
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,48 @@
---
name: BUG 反馈
about: 事情不像预期的那样工作吗?
title: ''
labels: 'bug'
assignees: ''
---
<!--
你好!感谢你正在考虑为 ZFile 提交一个 bug。请花一点点时间尽量详细地回答以下基础问题。
谢谢!
-->
<!--
请确认你已经做了下面这些事情,若 bug 还是未解决,那么请尽可详细地描述你的问题。
- 我已经安装了最新版的 ZFile
- 我已经阅读了 ZFile 的文档http://docs.zhaojun.im/zfile
- 我已经搜索了已有的 Issues 列表中有关的信息
- 我已经清理过浏览器缓存并重试
-->
## 我的环境
<!--
请登录到管理后台,点击左侧系统监控, 复制或截图此页内容.
-->
## 错误日志
<!--
请登录到管理后台,点击左侧系统监控, 点击右上角诊断日志下载, 然后上传到此 Issue 中.
-->
## 期望行为
<!--
你期望会发生什么?
-->
## 当前行为
<!--
描述 bug 细节,确认出现此问题的复现步骤,例如点击了哪里,发生了什么情况?
你可以粘贴截图或附件。
-->

View File

@@ -0,0 +1,34 @@
---
name: 功能建议
about: 想让我们为 ZFile 增加什么功能吗?
title: 'feat: '
labels: 'Feature Request'
assignees: ''
---
<!--
你好!感谢你愿意考虑希望 ZFile 增加某个新功能。请花一点点时间尽量详细地回答以下基础问题。
谢谢!
-->
## 概述
<!--
对这个新功能的一段描述
-->
## 动机
<!--
为什么你希望在 ZFile 中使用这个功能?
-->
## 详细解释
<!--
详细描述这个新功能。
如果这是一个小功能,你可以忽略这部分。
-->

14
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

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

164
API.md Normal file
View File

@@ -0,0 +1,164 @@
## API 标准
所有 API 均返回 `msg`, `code`, `data` 三个属性.
| code | 描述 |
| :---: | :------------: |
| 0 | 请求成功 |
| -1 | 请求失败 |
| -2 | 文件夹需要密码 |
`code == 0` 时, `data` 中为请求所需数据.
`code != 0` 时, 应当将 `msg` 中的属性作为参考值.
## 获取文件列表
### 请求 URL
`/api/list` `GET`
### 参数
| 参数名 | 描述 | 是否必填 | 参考值 |
| :------: | :--------: | :------: | :--------------------------: |
| path | 路径 | 是 | `/`, `/文件夹名称` |
| password | 文件夹密码 | 否 | 当文件夹需要密码时, |
| page | 页数 | 否 | 默认取第一页, 每页固定 30 条 |
### 响应
```json
{
"msg": "操作成功",
"code": 0,
"data": [
{
"name": "密码文件夹",
"time": "2020-01-28 13:17",
"size": 4096,
"type": "FOLDER",
"path": "/",
"url": null
},
{
"name": "新建 文本文档.txt",
"time": "2020-01-28 13:16",
"size": 3,
"type": "FILE",
"path": "/",
"url": "http://127.0.0.1:8080/file/新建 文本文档.txt"
}
]
}
```
## 搜索
### 请求 URL
`/api/search` `GET`
### 参数
| 参数名 | 描述 | 是否必填 | 参考值 |
| :----: | :----: | :------: | :--------------------------: |
| name | 搜索值 | 是 | 模糊匹配 |
| page | 页数 | 否 | 默认取第一页, 每页固定 30 条 |
### 响应
```json
{
"msg": "操作成功",
"code": 0,
"data": [
{
"name": "密码文件夹",
"time": "2020-01-28 13:17",
"size": 4096,
"type": "FOLDER",
"path": "/",
"url": null
},
{
"name": "新建 文本文档.txt",
"time": "2020-01-28 13:16",
"size": 3,
"type": "FILE",
"path": "/",
"url": "http://127.0.0.1:8080/file/新建 文本文档.txt"
}
]
}
```
## 获取单个文件信息
### 请求 URL
`/api/directlink` `GET`
### 参数
| 参数名 | 描述 | 是否必填 | 参考值 |
| :----: | :--------: | :------: | :------------------: |
| path | 文件全路径 | 是 | `/新建 文本文档.txt` |
### 响应
```json
{
"msg": "操作成功",
"code": 0,
"data": {
"name": "新建 文本文档.txt",
"time": "2020-01-28 13:16",
"size": 3,
"type": "FILE",
"path": "d:/test",
"url": "http://127.0.0.1:8080/file/新建 文本文档.txt"
}
}
```
## 获取系统配置
### 请求 URL
`/api/config` `GET`
### 参数
| 参数名 | 描述 | 是否必填 | 参考值 |
| :----: | :--------: | :------: | :-----------: |
| path | 文件夹名称 | 是 | `/文件夹名称` |
### 响应
```json
{
"msg": "操作成功",
"code": 0,
"data": {
"readme": null, # 文档文件名称
"viewConfig": {
"siteName": "站点名称", # 站点名称
"infoEnable": false, # 是否开启右侧信息框
"searchEnable": false, # 是否开启搜索
"searchIgnoreCase": true, # 搜索是否忽略大小写
"storageStrategy": "local", # 当前启用存储引擎
"username": "2", # 用户名
"domain": "http://127.0.0.1:8080", # 域名
"enableCache": false, # 是否开启缓存
"searchContainEncryptedFile": false, # 搜索是否包含加密文件夹
"customJs": "", # 自定义 js 片段
"customCss": "" # 自定义 css 片段
}
}
}
```

125
README.md
View File

@@ -11,51 +11,79 @@
预览地址: [https://zfile.jun6.net](https://zfile.jun6.net)
文档地址: [http://docs.zhaojun.im/zfile](http://docs.zhaojun.im/zfile)
## 系统特色
* 内存缓存 (免安装)
* 内存数据库 (免安装)
* 个性化配置
* 自定义目录的 header 和 footer 说明文件
* 自定义目录的 readme 说明文件
* 自定义 JS, CSS
* 文件夹密码
* 支持在线浏览文本文件, 视频, 图片, 音乐. (支持 FLV 和 HLS)
* 文件/目录二维码
* 缓存动态开启, 缓存自动刷新
* 全局搜索
* 缓存动态开启, ~~缓存自动刷新 (v2.2 及以前版本支持)~~
* ~~全局搜索 (v2.2 及以前版本支持)~~
* 同时挂载多个存储策略
* 支持 阿里云 OSS, FTP, 华为云 OBS, 本地存储, MINIO, OneDrive 国际/家庭/个人版, OneDrive 世纪互联版, 七牛云 KODO, 腾讯云 COS, 又拍云 USS.
## 快速开始
安装 JDK 1.8 :
安装依赖环境:
```bash
yum install -y java # 适用于 Centos 7.x
# CentOS系统
yum install -y java-1.8.0-openjdk unzip
```
```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
```
> 如为更新程序, 则请先执行 `~/zfile/bin/stop.sh && rm -rf ~/zfile` 清理旧程序. 首次安装请忽略此选项.
下载项目:
```bash
wget https://github.com/zhaojun1998/zfile/releases/download/0.7.1/zfile-0.7.1.jar
cd ~
wget https://c.jun6.net/ZFILE/zfile-release.war
mkdir zfile && unzip zfile-release.war -d zfile && rm -rf zfile-release.war
chmod +x zfile/bin/*.sh
```
> 下载指定版本可以将 `zfile-release.war` 改为 `zfile-x.x.war`,如 `zfile-2.2.war`。
程序的目录结构为:
```
├── zfile
├── META-INF
├── WEB-INF
└── bin
├── start.sh # 启动脚本
└── stop.sh # 停止脚本
├── restart.sh # 重启脚本
```
启动项目:
```bash
java -Djava.security.egd=file:/dev/./urandom -jar zfile-0.7.1.jar
## 高级启动
java -Djava.security.egd=file:/dev/./urandom -jar zfile-0.7.1.jar --server.port=18777
## 后台运行
nohup java -Djava.security.egd=file:/dev/./urandom -jar zfile-0.7.1.jar &
~/zfile/bin/start.sh
```
> 系统使用的是内置配置文件, 默认配置请参考: [application.yml](https://github.com/zhaojun1998/zfile/blob/master/src/main/resources/application.yml)
> **可下载此文件放置与 jar 包同目录, 此时会以外部配置文件为准, 推荐适用此方式!.**
> 所有参数都可在命令行启动时, 以类似 `--server.port=18777` 的方式强制执行, 此方式的优先级最高.
> *指定 `-Djava.security.egd=file:/dev/./urandom` 是为了防止在 Linux 环境中, 生成首次登陆生成 sessionId 取系统随机数过慢的问题.*
篇幅有限, 更详细的安装教程及介绍请参考: [ZFile 文档](http://zhaojun.im/zfile-install)
访问地址:
@@ -66,48 +94,51 @@ nohup java -Djava.security.egd=file:/dev/./urandom -jar zfile-0.7.1.jar &
管理后台: http://127.0.0.1:8080/#/admin
## OneDrive 使用教程.
## 预览
访问地址进行授权, 获取 accessToken 和 refreshToken: https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=09939809-c617-43c8-a220-a93c1513c5d4&response_type=code&redirect_uri=https://zfile.jun6.net/onedirve/callback&scope=offline_access%20User.Read%20Files.ReadWrite.All
然后分别填写至访问令牌和刷新令牌即可:
![http://cdn.jun6.net/2020-01-24_18-57-06.png](http://cdn.jun6.net/2020-01-24_18-57-06.png)
## 运行环境
* JDK: `1.8`
* 缓存: `caffeine`
* 数据库: `h2/mysql`
![前台首页](https://cdn.jun6.net/2020/04/19/d590d2bde13bb.png)
![后台设置-驱动器设置](https://cdn.jun6.net/2020/04/19/d58fc2debcce8.png)
![后台设置-驱动器设置](https://cdn.jun6.net/2020/04/19/0f321e47fc18c.png)
![后台设置-显示设置](https://cdn.jun6.net/2020/04/19/6d7c300b89671.png)
## 常见问题
### 数据库
缓存默认支持 `h2``mysql`, 前者为嵌入式数据库, 无需安装, 但后者相对性能更好.
### 默认路径
默认 H2 数据库文件地址: `~/.zfile/db/`, `~` 表示用户目录, windows 为 `C:/Users/用户名/`, linux 为 `/home/用户名/`, root 用户为 `/root/`
默认 H2 数据库文件地址: `~/.zfile/db/`, `~` 表示用户目录
### 头尾文件和加密文件
windows 为 `C:/Users/用户名/`
- 目录头部显示文件名为 `header.md`
- 目录底部显示文件名为 `footer.md`
linux 为 `/home/用户名/`, root 用户为 `/root/`
> 2.3 及以后版本路径为 `~/.zfile-new/db/`
### 文档文件和加密文件
- 目录文档显示文件名为 `readme.md`
- 目录需要密码访问, 添加文件 `password.txt` (无法拦截此文件被下载, 但可以改名文件)
## TODO
- 文本预览更换更好用的编辑器
- 后台支持上传、编辑、删除等操作
- API 支持
- 更方便的部署方式
## 开发计划
- [x] API 支持 [点击查看文档](https://github.com/zhaojun1998/zfile/blob/master/API.md)
- [x] 更方便的部署方式
- [x] 布局优化 - 自定义操作按钮 (现为右键实现)
- [x] 后台优化 - 设置按照其功能进行分离
- [x] 体验优化 - 支持前后端分离部署
- [x] 体验优化 - 文本预览更换 vscode 同款编辑器 monaco editor
- [x] 新功能 - Docker 支持
- [x] 架构调整 - 支持多存储策略
- [ ] 新功能 - 后台支持上传、编辑、删除等操作
- [ ] 新功能 - WebDav 支持
- [ ] 新功能 - 离线下载 (aria2)
- [ ] 体验优化 - 忽略文件列表 (正则表达式)
- [ ] 体验优化 - 自定义支持预览的文件后缀 (正则表达式)
- [ ] 体验优化 - 一键安装脚本
## 支持作者
如果本项目对你有帮助,请作者喝杯咖啡吧。
<img src="http://cdn.jun6.net/alipay.png" width="150" height="234">
<img src="http://cdn.jun6.net/alipay.png" width="200" height="312">
<img src="http://cdn.jun6.net/wechat.png" width="222" height="300">

48
pom.xml
View File

@@ -12,8 +12,9 @@
<groupId>im.zhaojun</groupId>
<artifactId>zfile</artifactId>
<version>0.7.1</version>
<version>2.4</version>
<name>zfile</name>
<packaging>war</packaging>
<description>一个在线的文件浏览系统</description>
<properties>
@@ -44,6 +45,10 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -66,7 +71,7 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.5.11</version>
<version>5.1.3</version>
</dependency>
<!-- 存储策略相关 API, 对象存储、FTP、 Rest API-->
@@ -105,23 +110,9 @@
</dependency>
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>2.5.14</version>
<exclusions>
<exclusion>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-redis</artifactId>
</exclusion>
<exclusion>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</exclusion>
<exclusion>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
</exclusion>
</exclusions>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.61</version>
</dependency>
</dependencies>
@@ -132,6 +123,25 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-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>
</jvms>
</configuration>
</plugin>
</plugins>
</build>

View File

@@ -1,15 +0,0 @@
package im.zhaojun.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 标记注解, 用于在调用前检查是否已存储策略
* @author zhaojun
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckStorageStrategyInit {
}

View File

@@ -1,31 +0,0 @@
package im.zhaojun.common.aspect;
import im.zhaojun.common.exception.StorageStrategyUninitializedException;
import im.zhaojun.common.service.AbstractFileService;
import im.zhaojun.common.service.SystemConfigService;
import im.zhaojun.common.util.SpringContextHolder;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* @author zhaojun
*/
@Aspect
@Component
public class StorageStrategyInitCheckAspect {
@Before("@annotation(im.zhaojun.common.annotation.CheckStorageStrategyInit)")
public void logStart() {
SystemConfigService systemConfigService = SpringContextHolder.getBean(SystemConfigService.class);
AbstractFileService currentFileService = systemConfigService.getCurrentFileService();
if (currentFileService == null) {
throw new StorageStrategyUninitializedException("存储策略尚未初始化, 请联系管理员!");
}
if (currentFileService.getIsUnInitialized()) {
throw new StorageStrategyUninitializedException("存储策略异常, 请联系管理员!");
}
}
}

View File

@@ -1,75 +0,0 @@
package im.zhaojun.common.config;
import im.zhaojun.common.model.StorageConfig;
import im.zhaojun.common.model.constant.StorageConfigConstant;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.service.AbstractFileService;
import im.zhaojun.common.service.StorageConfigService;
import im.zhaojun.common.service.SystemConfigService;
import im.zhaojun.onedrive.service.OneDriveService;
import im.zhaojun.onedrive.service.OneDriveServiceImpl;
import im.zhaojun.onedrive.service.OneDriveToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Arrays;
/**
* @author zhaojun
*/
@Configuration
@EnableScheduling
@Slf4j
public class GlobalScheduleTask {
@Resource
private StorageConfigService storageConfigService;
@Resource
private OneDriveService oneDriveService;
@Resource
private SystemConfigService systemConfigService;
/**
* 项目启动 30 秒后, 每半小时执行一次刷新 OneDrive Token 的定时任务.
*/
@Scheduled(fixedRate = 1000 * 60 * 30, initialDelay = 1000 * 30)
public void autoRefreshOneDriveToken() {
AbstractFileService currentFileService = systemConfigService.getCurrentFileService();
if (!(currentFileService instanceof OneDriveServiceImpl)) {
log.debug("当前启用存储类型, 不是 OneDrive, 跳过自动刷新 AccessToken");
return;
}
if (currentFileService.getIsUnInitialized()) {
log.debug("当前启用 OneDrive 未初始化成功, 跳过自动刷新 AccessToken");
return;
}
refreshOneDriveToken();
}
/**
* 调用刷新 OneDrive Token
*/
public void refreshOneDriveToken() {
OneDriveToken refreshToken = oneDriveService.getRefreshToken();
StorageConfig accessTokenConfig =
storageConfigService.selectByTypeAndKey(StorageTypeEnum.ONE_DRIVE, StorageConfigConstant.ACCESS_TOKEN_KEY);
StorageConfig refreshTokenConfig =
storageConfigService.selectByTypeAndKey(StorageTypeEnum.ONE_DRIVE, StorageConfigConstant.REFRESH_TOKEN_KEY);
accessTokenConfig.setValue(refreshToken.getAccessToken());
refreshTokenConfig.setValue(refreshToken.getRefreshToken());
storageConfigService.updateStorageConfig(Arrays.asList(accessTokenConfig, refreshTokenConfig));
log.info("刷新 OneDrive key 时间: " + LocalDateTime.now());
}
}

View File

@@ -1,18 +0,0 @@
package im.zhaojun.common.config;
import im.zhaojun.common.model.enums.StorageTypeEnumDeSerializerConvert;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author zhaojun
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StorageTypeEnumDeSerializerConvert());
}
}

View File

@@ -1,171 +0,0 @@
package im.zhaojun.common.controller;
import im.zhaojun.common.config.StorageTypeFactory;
import im.zhaojun.common.model.StorageConfig;
import im.zhaojun.common.model.dto.ResultBean;
import im.zhaojun.common.model.dto.StorageStrategyDTO;
import im.zhaojun.common.model.dto.SystemConfigDTO;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.service.AbstractFileService;
import im.zhaojun.common.service.FileAsyncCacheService;
import im.zhaojun.common.service.StorageConfigService;
import im.zhaojun.common.service.SystemConfigService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 后台管理
* @author zhaojun
*/
@RestController
@RequestMapping("/admin")
public class AdminController {
private static final Logger log = LoggerFactory.getLogger(AdminController.class);
@Resource
private StorageConfigService storageConfigService;
@Resource
private SystemConfigService systemConfigService;
@Resource
private FileAsyncCacheService fileAsyncCacheService;
/**
* 获取系统配置
*/
@GetMapping("/config")
public ResultBean getConfig() {
SystemConfigDTO systemConfigDTO = systemConfigService.getSystemConfig();
return ResultBean.success(systemConfigDTO);
}
/**
* 更新系统配置
*/
@PostMapping("/config")
public ResultBean updateConfig(SystemConfigDTO systemConfigDTO) throws Exception {
AbstractFileService currentFileService = systemConfigService.getCurrentFileService();
systemConfigDTO.setId(1);
systemConfigService.updateSystemConfig(systemConfigDTO);
StorageTypeEnum currentStorageStrategy = currentFileService.getStorageTypeEnum();
if (!Objects.equals(currentStorageStrategy, systemConfigDTO.getStorageStrategy())) {
log.info("已将存储策略由 {} 切换为 {}", currentStorageStrategy, systemConfigDTO.getStorageStrategy());
refreshStorageStrategy();
}
return ResultBean.success();
}
/**
* 修改管理员登陆密码
*/
@PostMapping("/update-pwd")
public ResultBean updatePwd(String username, String password) {
systemConfigService.updateUsernameAndPwd(username, password);
return ResultBean.success();
}
/**
* 获取指定存储策略的设置
* @param storageType 存储策略
* @return 所有设置
*/
@GetMapping("/strategy-form")
public ResultBean getFormByStorageType(StorageTypeEnum storageType) {
List<StorageConfig> storageConfigList = storageConfigService.selectStorageConfigByType(storageType);
return ResultBean.success(storageConfigList);
}
@GetMapping("/support-strategy")
public ResultBean supportStrategy() {
List<StorageStrategyDTO> result = new ArrayList<>();
StorageTypeEnum[] values = StorageTypeEnum.values();
for (StorageTypeEnum value : values) {
AbstractFileService storageTypeService = StorageTypeFactory.getStorageTypeService(value);
result.add(new StorageStrategyDTO(value.getKey(),
value.getDescription(),
storageTypeService.getIsInitialized()));
}
return ResultBean.successData(result);
}
/**
* 保存存储策略
* @param storageStrategyConfig 保存表单值
* @param storageStrategy 所属策略
* @return 操作结果
* @throws Exception 表单解析出错异常
*/
@PostMapping("/storage-strategy")
public ResultBean save(@RequestParam Map<String, String> storageStrategyConfig, StorageTypeEnum storageStrategy) throws Exception {
// 保存设置.
List<StorageConfig> storageConfigList = storageConfigService.selectStorageConfigByType(storageStrategy);
for (StorageConfig storageConfig : storageConfigList) {
String key = storageConfig.getKey();
String value = storageStrategyConfig.get(key);
storageConfig.setValue(value);
}
storageConfigService.updateStorageConfig(storageConfigList);
// 获取当前修改的存储策略 Service, 尝试调用初始化.
AbstractFileService updateStorageStrategyService = StorageTypeFactory.getStorageTypeService(storageStrategy);
updateStorageStrategyService.init();
// 如果修改的为当前启用的缓存, 则重新进行缓存.
StorageTypeEnum currentStorageStrategy = systemConfigService.getCurrentStorageStrategy();
if (Objects.equals(storageStrategy, currentStorageStrategy)) {
if (log.isDebugEnabled()) {
log.debug("检测到更新了当前启用的存储策略 {}, 已清理缓存.", currentStorageStrategy);
}
AbstractFileService fileService = systemConfigService.getCurrentFileService();
fileService.clearFileCache();
fileAsyncCacheService.cacheGlobalFile();
}
// 返回是否初始化成功.
if (updateStorageStrategyService.getIsInitialized()) {
return ResultBean.success();
} else {
return ResultBean.error("保存成功, 但尝试初始化异常, 请检查设置.");
}
}
/**
* 更新存储策略
*/
public void refreshStorageStrategy() throws Exception {
StorageTypeEnum storageStrategy = systemConfigService.getCurrentStorageStrategy();
refreshStorageStrategy(storageStrategy);
}
/**
* 更新存储策略
*/
public void refreshStorageStrategy(StorageTypeEnum storageStrategy) throws Exception {
if (storageStrategy == null) {
log.info("尚未配置存储策略.");
} else {
AbstractFileService fileService = systemConfigService.getCurrentFileService();
fileService.init();
fileService.clearFileCache();
log.info("切换至存储类型: {}", storageStrategy.getDescription());
fileAsyncCacheService.cacheGlobalFile();
}
}
}

View File

@@ -1,81 +0,0 @@
package im.zhaojun.common.controller;
import im.zhaojun.common.model.dto.CacheConfigDTO;
import im.zhaojun.common.model.dto.ResultBean;
import im.zhaojun.common.service.AbstractFileService;
import im.zhaojun.common.service.FileAsyncCacheService;
import im.zhaojun.common.service.FileCacheService;
import im.zhaojun.common.service.SystemConfigService;
import org.springframework.web.bind.annotation.GetMapping;
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;
import java.util.Set;
/**
* @author zhaojun
* @date 2020/1/11 14:03
*/
@RestController
@RequestMapping("/admin/cache")
public class CacheController {
@Resource
private SystemConfigService systemConfigService;
@Resource
private FileAsyncCacheService fileAsyncCacheService;
@Resource
private FileCacheService fileCacheService;
@PostMapping("/enable")
public ResultBean enableCache() throws Exception {
fileCacheService.enableCache();
return ResultBean.success();
}
@PostMapping("/disable")
public ResultBean disableCache() throws Exception {
fileCacheService.disableCache();
return ResultBean.success();
}
@GetMapping("/config")
public ResultBean cacheConfig() throws Exception {
AbstractFileService fileService = systemConfigService.getCurrentFileService();
Set<String> cacheKeys = fileService.getCacheKeys();
CacheConfigDTO cacheConfigDTO = new CacheConfigDTO();
cacheConfigDTO.setEnableCache(systemConfigService.getEnableCache());
cacheConfigDTO.setCacheFinish(fileAsyncCacheService.isCacheFinish());
cacheConfigDTO.setCacheKeys(cacheKeys);
cacheConfigDTO.setCacheDirectoryCount(fileAsyncCacheService.getCacheDirectoryCount());
cacheConfigDTO.setCacheFileCount(fileAsyncCacheService.getCacheFileCount());
return ResultBean.success(cacheConfigDTO);
}
@PostMapping("/refresh")
public ResultBean refreshCache(String key) throws Exception {
AbstractFileService fileService = systemConfigService.getCurrentFileService();
fileService.refreshCache(key);
return ResultBean.success();
}
@PostMapping("/clear")
public ResultBean clearCache(String key) throws Exception {
AbstractFileService fileService = systemConfigService.getCurrentFileService();
fileService.clearFileCache();
return ResultBean.success();
}
@PostMapping("/all")
public ResultBean cacheAll() throws Exception {
AbstractFileService fileService = systemConfigService.getCurrentFileService();
fileService.clearFileCache();
fileAsyncCacheService.cacheGlobalFile();
return ResultBean.success();
}
}

View File

@@ -1,66 +0,0 @@
package im.zhaojun.common.controller;
import im.zhaojun.common.model.dto.ResultBean;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.util.AudioHelper;
import im.zhaojun.common.util.HttpUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author zhaojun
* @date 2020/1/13 21:40
*/
@RestController
@RequestMapping("/common")
public class CommonController {
@GetMapping("/support-strategy")
public ResultBean supportStrategy() {
return ResultBean.successData(StorageTypeEnum.values());
}
/**
* 获取文件内容, 仅限用于, txt, md, ini 等普通文本文件.
* @param url 文件路径
* @return 文件内容
*/
@GetMapping("/content")
public ResultBean getContent(String url) {
return ResultBean.successData(HttpUtil.getTextContent(url));
}
/**
* 获取文件内容, 仅限用于, txt, md, ini 等普通文本文件.
* @param url 文件路径
* @return 文件内容
*/
@GetMapping("/content/origin")
public String getContentOrigin(String url) {
return HttpUtil.getTextContent(url);
}
/**
* 检测文件是否存在
* @param url 文件路径
* @return 是否存在
*/
@GetMapping("/content/exist")
public boolean checkFileExist(String url) {
return HttpUtil.checkUrlExist(url);
}
/**
* 获取音频文件信息
* @param url 文件 URL
* @return 音频信息, 标题封面等信息
*/
@GetMapping("/audio-info")
public ResultBean getAudioInfo(String url) throws Exception {
return ResultBean.success(AudioHelper.getAudioInfo(url));
}
}

View File

@@ -1,137 +0,0 @@
package im.zhaojun.common.controller;
import cn.hutool.core.util.URLUtil;
import im.zhaojun.common.annotation.CheckStorageStrategyInit;
import im.zhaojun.common.exception.SearchDisableException;
import im.zhaojun.common.model.constant.ZFileConstant;
import im.zhaojun.common.model.dto.FileItemDTO;
import im.zhaojun.common.model.dto.ResultBean;
import im.zhaojun.common.model.dto.SiteConfigDTO;
import im.zhaojun.common.model.dto.SystemConfigDTO;
import im.zhaojun.common.service.AbstractFileService;
import im.zhaojun.common.service.FileAsyncCacheService;
import im.zhaojun.common.service.SystemConfigService;
import im.zhaojun.common.service.SystemService;
import im.zhaojun.common.util.FileComparator;
import im.zhaojun.common.util.HttpUtil;
import im.zhaojun.common.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* 前台文件管理
* @author zhaojun
*/
@RequestMapping("/api")
@RestController
public class FileController {
@Resource
private SystemService systemService;
@Resource
private SystemConfigService systemConfigService;
@Resource
private FileAsyncCacheService fileAsyncCacheService;
/**
* 滚动加载每页条数.
*/
private static final Integer PAGE_SIZE = 30;
@CheckStorageStrategyInit
@GetMapping("/list")
public ResultBean list(@RequestParam(defaultValue = "/") String path,
@RequestParam(defaultValue = "name") String sortBy,
@RequestParam(defaultValue = "asc") String order,
@RequestParam(required = false) String password,
@RequestParam(defaultValue = "1") Integer page) throws Exception {
AbstractFileService fileService = systemConfigService.getCurrentFileService();
List<FileItemDTO> fileItemList = fileService.fileList(StringUtils.removeDuplicateSeparator("/" + path + "/"));
for (FileItemDTO fileItemDTO : fileItemList) {
if (ZFileConstant.PASSWORD_FILE_NAME.equals(fileItemDTO.getName())
&& !HttpUtil.getTextContent(fileItemDTO.getUrl()).equals(password)) {
if (password != null && !"".equals(password)) {
return ResultBean.error("密码错误.");
}
return ResultBean.error("此文件夹需要密码.", ResultBean.REQUIRED_PASSWORD);
}
}
List<FileItemDTO> sortedPagingData = getSortedPagingData(fileItemList, page);
return ResultBean.successData(sortedPagingData);
}
/**
* 获取系统配置信息和当前页的标题, 文件头, 文件尾信息
* @param path 路径
*/
@CheckStorageStrategyInit
@GetMapping("/config")
public ResultBean getConfig(String path) throws Exception {
SiteConfigDTO config = systemService.getConfig(StringUtils.removeDuplicateSeparator("/" + path + "/"));
config.setSystemConfigDTO(systemConfigService.getSystemConfig());
return ResultBean.successData(config);
}
@CheckStorageStrategyInit
@GetMapping("/search")
public ResultBean search(@RequestParam(value = "name", defaultValue = "/") String name,
@RequestParam(defaultValue = "name") String sortBy,
@RequestParam(defaultValue = "asc") String order,
@RequestParam(defaultValue = "1") Integer page) throws Exception {
AbstractFileService fileService = systemConfigService.getCurrentFileService();
SystemConfigDTO systemConfigDTO = systemConfigService.getSystemConfig();
if (!systemConfigDTO.getSearchEnable()) {
throw new SearchDisableException("搜索功能未开启");
}
if (!fileAsyncCacheService.isCacheFinish()) {
throw new SearchDisableException("搜索功能缓存预热中, 请稍后再试");
}
List<FileItemDTO> fileItemList = fileService.search(URLUtil.decode(name));
List<FileItemDTO> sortedPagingData = getSortedPagingData(fileItemList, page);
return ResultBean.successData(sortedPagingData);
}
/**
* 过滤文件列表, 不显示密码, 头部和尾部文件.
*/
private void filterFileList(List<FileItemDTO> fileItemList) {
if (fileItemList == null) {
return;
}
fileItemList.removeIf(fileItem -> ZFileConstant.PASSWORD_FILE_NAME.equals(fileItem.getName())
|| ZFileConstant.FOOTER_FILE_NAME.equals(fileItem.getName())
|| ZFileConstant.HEADER_FILE_NAME.equals(fileItem.getName()));
}
private List<FileItemDTO> getSortedPagingData(List<FileItemDTO> fileItemList, Integer page) {
// 排序, 先按照文件类型比较, 文件夹在前, 文件在后, 然后根据 sortBy 字段排序, 默认为升序;
fileItemList.sort(new FileComparator());
filterFileList(fileItemList);
int total = fileItemList.size();
int totalPage = (total + PAGE_SIZE - 1) / PAGE_SIZE;
if (page > totalPage) {
return new ArrayList<>();
}
int start = (page - 1) * PAGE_SIZE;
int end = page * PAGE_SIZE;
end = Math.min(end, total);
return new ArrayList<>(fileItemList.subList(start, end));
}
}

View File

@@ -1,83 +0,0 @@
package im.zhaojun.common.controller;
import cn.hutool.crypto.SecureUtil;
import im.zhaojun.common.model.StorageConfig;
import im.zhaojun.common.model.dto.InstallModelDTO;
import im.zhaojun.common.model.dto.ResultBean;
import im.zhaojun.common.model.dto.SystemConfigDTO;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.service.StorageConfigService;
import im.zhaojun.common.service.SystemConfigService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* 系统安装初始化
* @author zhaojun
*/
@RestController
public class InstallController {
@Resource
private SystemConfigService systemConfigService;
@Resource
private StorageConfigService storageConfigService;
@Resource
private AdminController adminController;
@GetMapping("/is-installed")
public ResultBean isInstall() {
if (systemConfigService.getCurrentStorageStrategy() == null) {
return ResultBean.success();
}
return ResultBean.error("请勿重复初始化");
}
@PostMapping("/install")
public ResultBean install(InstallModelDTO installModelDTO) throws Exception {
SystemConfigDTO systemConfigDTO = systemConfigService.getSystemConfig();
if (systemConfigDTO.getStorageStrategy() != null) {
return ResultBean.error("请勿重复初始化.");
}
systemConfigDTO.setSiteName(installModelDTO.getSiteName());
StorageTypeEnum storageTypeEnum = installModelDTO.getStorageStrategy();
systemConfigDTO.setStorageStrategy(storageTypeEnum);
systemConfigDTO.setUsername(installModelDTO.getUsername());
systemConfigDTO.setPassword(SecureUtil.md5(installModelDTO.getPassword()));
systemConfigDTO.setDomain(installModelDTO.getDomain());
systemConfigService.updateSystemConfig(systemConfigDTO);
Map<String, String> storageStrategyConfig = installModelDTO.getStorageStrategyConfig();
List<StorageConfig> storageConfigList = storageConfigService.selectStorageConfigByType(storageTypeEnum);
for (StorageConfig storageConfig : storageConfigList) {
String key = storageConfig.getKey();
String value = storageStrategyConfig.get(key);
storageConfig.setValue(value);
}
storageConfigService.updateStorageConfig(storageConfigList);
adminController.refreshStorageStrategy();
return ResultBean.success();
}
@GetMapping("/form")
public ResultBean getFormByStorageType(String storageType) {
StorageTypeEnum storageTypeEnum = StorageTypeEnum.getEnum(storageType);
List<StorageConfig> storageConfigList = storageConfigService.selectStorageConfigByType(storageTypeEnum);
storageConfigList.forEach(storageConfig -> storageConfig.setValue(null));
return ResultBean.success(storageConfigList);
}
}

View File

@@ -1,47 +0,0 @@
package im.zhaojun.common.model.constant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
/**
* @author zhaojun
*/
@Configuration
public class ZFileConstant {
public final static String USER_HOME = System.getProperty("user.home");
public static final String AUDIO_TMP_PATH = "/.zfile/tmp/audio/";
/**
* 页面头部文件
*/
public static String HEADER_FILE_NAME = "header.md";
/**
* 页面尾部文件
*/
public static String FOOTER_FILE_NAME = "footer.md";
/**
* 密码文件
*/
public static String PASSWORD_FILE_NAME = "password.txt";
@Autowired(required = false)
public void setHeaderFileName(@Value("${zfile.constant.header}") String headerFileName) {
ZFileConstant.HEADER_FILE_NAME = headerFileName;
}
@Autowired(required = false)
public void setFooterFileName(@Value("${zfile.constant.footer}") String footerFileName) {
ZFileConstant.FOOTER_FILE_NAME = footerFileName;
}
@Autowired(required = false)
public void setPasswordFileName(@Value("${zfile.constant.password}") String passwordFileName) {
ZFileConstant.PASSWORD_FILE_NAME = passwordFileName;
}
}

View File

@@ -1,53 +0,0 @@
package im.zhaojun.common.model.dto;
/**
* @author zhaojun
*/
public class AudioInfoDTO {
private String title;
private String artist;
private String cover;
private String src;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getArtist() {
return artist;
}
public void setArtist(String artist) {
this.artist = artist;
}
public String getCover() {
return cover;
}
public void setCover(String cover) {
this.cover = cover;
}
public String getSrc() {
return src;
}
public void setSrc(String src) {
this.src = src;
}
@Override
public String toString() {
return "AudioInfoDTO{" +
"title='" + title + '\'' +
", artist='" + artist + '\'' +
", cover='" + cover + '\'' +
", src='" + src + '\'' +
'}';
}
}

View File

@@ -1,53 +0,0 @@
package im.zhaojun.common.model.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
/**
* @author zhaojun
*/
public class SiteConfigDTO implements Serializable {
private static final long serialVersionUID = 8811196207046121740L;
private String header;
private String footer;
@JsonProperty("viewConfig")
private SystemConfigDTO systemConfigDTO;
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
public String getFooter() {
return footer;
}
public void setFooter(String footer) {
this.footer = footer;
}
public SystemConfigDTO getSystemConfigDTO() {
return systemConfigDTO;
}
public void setSystemConfigDTO(SystemConfigDTO systemConfigDTO) {
this.systemConfigDTO = systemConfigDTO;
}
@Override
public String toString() {
return "SiteConfigDTO{" +
"header='" + header + '\'' +
", footer='" + footer + '\'' +
", systemConfig=" + systemConfigDTO +
'}';
}
}

View File

@@ -1,31 +0,0 @@
package im.zhaojun.common.repository;
import im.zhaojun.common.model.StorageConfig;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author zhaojun
*/
@Repository
public interface StorageConfigRepository extends JpaRepository<StorageConfig, Integer> {
/**
* 根据存储类型找对应的配置信息
* @param type 存储类型
* @return 此类型所有的配置信息
*/
List<StorageConfig> findByTypeOrderById(StorageTypeEnum type);
/**
* 根据存储类型找到某个 KEY 的值
* @param type 存储类型
* @param key KEY
* @return KEY 对应的对象
*/
StorageConfig findByTypeAndKey(StorageTypeEnum type, String key);
}

View File

@@ -1,219 +0,0 @@
package im.zhaojun.common.service;
import cn.hutool.core.util.StrUtil;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.RefreshPolicy;
import com.alicp.jetcache.anno.CacheRefresh;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.alicp.jetcache.anno.CreateCache;
import im.zhaojun.common.model.dto.FileItemDTO;
import im.zhaojun.common.model.enums.FileTypeEnum;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Value;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author zhaojun
* @date 2019/12/28 19:27
*/
@Slf4j
public abstract class AbstractFileService extends FileCacheService implements FileService {
private static final String SYSTEM_CONFIG_CACHE_PREFIX = "zfile-cache:";
@Value("${zfile.cache.timeout}")
protected Long timeout;
protected boolean isInitialized = false;
@Resource
private SystemConfigService systemConfigService;
@Resource
private FileAsyncCacheService fileAsyncCacheService;
@CreateCache(name = SYSTEM_CONFIG_CACHE_PREFIX, cacheType = CacheType.LOCAL)
private Cache<String, List<FileItemDTO>> cache;
/***
* 获取指定路径下的文件及文件夹, 默认缓存 60 分钟,每隔 30 分钟刷新一次.
* @param path 文件路径
* @return 文件及文件夹列表
* @throws Exception 获取文件列表中出现的异常
*/
@Override
@Cached(name = SYSTEM_CONFIG_CACHE_PREFIX,
key = "args[0]",
cacheType = CacheType.LOCAL, localLimit = 100000, condition = "mvel{bean('systemConfigService').enableCache}")
@CacheRefresh(refresh = 30, timeUnit = TimeUnit.MINUTES)
public abstract List<FileItemDTO> fileList(String path) throws Exception;
/**
* 清理当前存储策略的缓存
* 1. 删除全部缓存
* 2. 关闭自动刷新
* 3. 重置缓存个数
* 4. 标记为当前处于未完成缓存状态
*/
public void clearFileCache() throws Exception {
Set<String> cacheKeys = getCacheKeys();
cache.removeAll(cacheKeys);
closeCacheAutoRefresh();
fileAsyncCacheService.resetCacheCount();
fileAsyncCacheService.setCacheFinish(false);
}
/**
* 初始化方法, 启动时自动调用实现类的此方法进行初始化.
*/
@PostConstruct
public abstract void init();
protected boolean testConnection() {
boolean flag = true;
try {
fileList("/");
} catch (Exception e) {
log.debug(getStorageTypeEnum().getDescription() + " 初始化异常", e);
flag = false;
}
return flag;
}
/**
* 获取是否初始化成功
* @return 初始化成功与否
*/
public boolean getIsUnInitialized() {
return !isInitialized;
}
/**
* 获取是否初始化成功
* @return 初始化成功与否
*/
public boolean getIsInitialized() {
return isInitialized;
}
/**
* 获取存储策略类型
* @return 存储策略类型枚举
*/
public abstract StorageTypeEnum getStorageTypeEnum();
/**
* 搜索文件
* @param name 文件名
* @return 包含该文件名的所有文件或文件夹
* @throws Exception 搜索过程出现的异常
*/
public List<FileItemDTO> search(String name) throws Exception {
List<FileItemDTO> result = new ArrayList<>();
boolean searchIgnoreCase = systemConfigService.getSearchIgnoreCase();
List<FileItemDTO> fileItemList = selectAllFileList();
for (FileItemDTO fileItemDTO : fileItemList) {
boolean testResult;
if (searchIgnoreCase) {
testResult = StrUtil.containsIgnoreCase(fileItemDTO.getName(), name);
} else {
testResult = fileItemDTO.getName().contains(name);
}
if (testResult) {
result.add(fileItemDTO);
}
}
return result;
}
/**
* 查询所有文件, 仅去缓存中查询.
* @return 所有文件
*/
public List<FileItemDTO> selectAllFileList() {
List<FileItemDTO> result = new ArrayList<>();
boolean enableCache = systemConfigService.getEnableCache();
if (!enableCache) {
log.debug("未开启缓存, 不支持查询所有文件.");
return null;
}
String path = "/";
List<FileItemDTO> fileItemList = cache.get(path);
fileItemList = fileItemList == null ? new ArrayList<>() : fileItemList;
ArrayDeque<FileItemDTO> queue = new ArrayDeque<>(fileItemList);
while (!queue.isEmpty()) {
FileItemDTO fileItemDTO = queue.pop();
result.add(fileItemDTO);
if (fileItemDTO.getType() == FileTypeEnum.FOLDER) {
String filePath = StringUtils.removeDuplicateSeparator("/" + fileItemDTO.getPath() + "/" + fileItemDTO.getName() + "/");
List<FileItemDTO> cacheList = cache.get(filePath);
if (cacheList != null) {
queue.addAll(cacheList);
}
}
}
return result;
}
/**
* 获取所有缓存的 Key, 仅当开启缓存, 且缓存完成时, 可获取.
* @return 所有缓存的 Key
* @throws Exception 可能出现的异常
*/
public Set<String> getCacheKeys() throws Exception {
if (systemConfigService.getEnableCache() && fileAsyncCacheService.isCacheFinish()) {
Set<String> collect = selectAllFileList().stream().map(fileItemDTO -> {
if (fileItemDTO.getType() == FileTypeEnum.FOLDER) {
return StringUtils.removeDuplicateSeparator("/" + fileItemDTO.getPath() + "/" + fileItemDTO.getName() + "/");
}
return null;
}).collect(Collectors.toSet());
collect.remove(null);
collect.add("/");
return collect;
} else {
return Collections.emptySet();
}
}
/**
* 刷新缓存
*/
public void refreshCache(String key) throws Exception {
cache.remove(key);
FileService currentFileService = (FileService) AopContext.currentProxy();
currentFileService.fileList(key);
}
public void closeCacheAutoRefresh() {
cache.config().setRefreshPolicy(null);
}
public void openCacheAutoRefresh() {
RefreshPolicy refreshPolicy = RefreshPolicy.newPolicy(30, TimeUnit.MINUTES);
cache.config().setRefreshPolicy(refreshPolicy);
}
}

View File

@@ -1,140 +0,0 @@
package im.zhaojun.common.service;
import cn.hutool.core.util.ObjectUtil;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CreateCache;
import im.zhaojun.common.config.StorageTypeFactory;
import im.zhaojun.common.model.dto.FileItemDTO;
import im.zhaojun.common.model.enums.FileTypeEnum;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayDeque;
import java.util.List;
/**
* @author zhaojun
*/
@Slf4j
@Service
public class FileAsyncCacheService {
public static final String CACHE_PROCESS_PREFIX = "zfile-process-cache:";
public static final String CACHE_FILE_COUNT_KEY = "file-count";
public static final String CACHE_DIRECTORY_COUNT_KEY = "directory-count";
@CreateCache(name = "SYSTEM_CONFIG_CACHE_PREFIX", cacheType = CacheType.LOCAL)
private Cache<String, Integer> cache;
private boolean cacheFinish;
@Resource
private SystemConfigService systemConfigService;
@Async
public void cacheGlobalFile() {
StorageTypeEnum storageStrategy = systemConfigService.getCurrentStorageStrategy();
if (storageStrategy == null) {
log.debug("尚未配置存储策略. 跳过启动缓存.");
return;
}
boolean enableCache = systemConfigService.getEnableCache();
if (!enableCache) {
log.debug("存储策略 {} 未启用缓存, 跳过缓存.", storageStrategy.getDescription());
return;
}
AbstractFileService fileService = StorageTypeFactory.getStorageTypeService(storageStrategy);
if (fileService.getIsUnInitialized()) {
log.debug("存储策略 {} 未初始化成功, 跳过缓存.", storageStrategy.getDescription());
return;
}
Integer cacheDirectoryCount = cache.get(CACHE_DIRECTORY_COUNT_KEY);
if (cacheDirectoryCount == null) {
cacheDirectoryCount = 0;
}
Integer cacheFileCount = cache.get(CACHE_FILE_COUNT_KEY);
if (cacheFileCount == null) {
cacheFileCount = 0;
}
log.info("缓存 {} 所有文件开始", storageStrategy.getDescription());
long startTime = System.currentTimeMillis();
try {
FileService currentFileService = systemConfigService.getCurrentFileService();
List<FileItemDTO> rootFileItems = currentFileService.fileList("/");
ArrayDeque<FileItemDTO> queue = new ArrayDeque<>(rootFileItems);
while (!queue.isEmpty()) {
FileItemDTO fileItemDTO = queue.pop();
if (FileTypeEnum.FOLDER.equals(fileItemDTO.getType())) {
cacheDirectoryCount++;
}
if (FileTypeEnum.FILE.equals(fileItemDTO.getType())) {
cacheFileCount++;
}
log.debug("已缓存 {} 个文件夹", cacheDirectoryCount);
cache.put(CACHE_DIRECTORY_COUNT_KEY, cacheDirectoryCount);
log.debug("已缓存 {} 个文件", cacheFileCount);
cache.put(CACHE_FILE_COUNT_KEY, cacheFileCount);
if (fileItemDTO.getType() == FileTypeEnum.FOLDER) {
String filePath = StringUtils.removeDuplicateSeparator("/" + fileItemDTO.getPath() + "/" + fileItemDTO.getName() + "/");
List<FileItemDTO> fileItems = currentFileService.fileList(filePath);
queue.addAll(fileItems);
}
}
cache.put(CACHE_DIRECTORY_COUNT_KEY, cacheDirectoryCount);
cache.put(CACHE_FILE_COUNT_KEY, cacheFileCount);
} catch (Exception e) {
log.error("缓存所有文件失败", e);
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
log.info("缓存 {} 所有文件结束, 用时: {} 秒, 文件夹共 {} 个, 文件共 {} 个",
storageStrategy.getDescription(),
( (endTime - startTime) / 1000 ), cacheDirectoryCount, cacheFileCount);
cacheFinish = true;
}
/**
* 清理缓存的文件/文件夹数量统计
*/
public void resetCacheCount() {
cache.remove(CACHE_DIRECTORY_COUNT_KEY);
cache.remove(CACHE_FILE_COUNT_KEY);
}
public Integer getCacheDirectoryCount() {
return ObjectUtil.defaultIfNull(cache.get(CACHE_DIRECTORY_COUNT_KEY), 0);
}
public Integer getCacheFileCount() {
return ObjectUtil.defaultIfNull(cache.get(CACHE_FILE_COUNT_KEY), 0);
}
public boolean isCacheFinish() {
return cacheFinish;
}
public void setCacheFinish(boolean cacheFinish) {
this.cacheFinish = cacheFinish;
}
}

View File

@@ -1,39 +0,0 @@
package im.zhaojun.common.service;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author zhaojun
* @date 2020/1/11 14:08
*/
@Service
public class FileCacheService {
@Resource
private SystemConfigService systemConfigService;
@Resource
@Lazy
private FileAsyncCacheService fileAsyncCacheService;
public void enableCache() {
systemConfigService.updateCacheEnableConfig(true);
AbstractFileService currentFileService = systemConfigService.getCurrentFileService();
currentFileService.openCacheAutoRefresh();
fileAsyncCacheService.cacheGlobalFile();
}
public void disableCache() throws Exception {
systemConfigService.updateCacheEnableConfig(false);
AbstractFileService currentFileService = systemConfigService.getCurrentFileService();
currentFileService.clearFileCache();
fileAsyncCacheService.resetCacheCount();
}
}

View File

@@ -1,158 +0,0 @@
package im.zhaojun.common.service;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.SecureUtil;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CreateCache;
import im.zhaojun.common.config.StorageTypeFactory;
import im.zhaojun.common.model.SystemConfig;
import im.zhaojun.common.model.constant.SystemConfigConstant;
import im.zhaojun.common.model.dto.SystemConfigDTO;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.repository.SystemConfigRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* @author zhaojun
*/
@Slf4j
@Service
public class SystemConfigService {
public static final String SYSTEM_CONFIG_CACHE_PREFIX = "zfile-config-cache:";
public static final String SYSTEM_CONFIG_CACHE_KEY = "1";
@CreateCache(name = SYSTEM_CONFIG_CACHE_PREFIX, cacheType = CacheType.LOCAL)
private Cache<String, Object> configCache;
@Resource
private SystemConfigRepository systemConfigRepository;
@Resource
private FileCacheService fileCacheService;
private Class<SystemConfigDTO> systemConfigDTOClass = SystemConfigDTO.class;
public SystemConfigDTO getSystemConfig() {
Object cache = configCache.get(SYSTEM_CONFIG_CACHE_KEY);
if (configCache.get(SYSTEM_CONFIG_CACHE_KEY) != null) {
return (SystemConfigDTO) cache;
}
SystemConfigDTO systemConfigDTO = new SystemConfigDTO();
List<SystemConfig> systemConfigList = systemConfigRepository.findAll();
for (SystemConfig systemConfig : systemConfigList) {
String key = systemConfig.getKey();
try {
Field field = systemConfigDTOClass.getDeclaredField(key);
if (field != null) {
field.setAccessible(true);
String strVal = systemConfig.getValue();
Object convertVal = Convert.convert(field.getType(), strVal);
field.set(systemConfigDTO, convertVal);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
if (log.isDebugEnabled()) {
log.debug("通过反射, 将字段 {" + key + "}注入 SystemConfigDTO 时出现异常:", e);
}
}
}
configCache.put(SYSTEM_CONFIG_CACHE_KEY, systemConfigDTO);
return systemConfigDTO;
}
public void updateSystemConfig(SystemConfigDTO systemConfigDTO) throws Exception {
List<SystemConfig> systemConfigList = new ArrayList<>();
Field[] fields = systemConfigDTOClass.getDeclaredFields();
for (Field field : fields) {
String key = field.getName();
SystemConfig systemConfig = systemConfigRepository.findByKey(key);
if (systemConfig != null) {
field.setAccessible(true);
Object val = field.get(systemConfigDTO);
if (val != null) {
systemConfig.setValue(val.toString());
systemConfigList.add(systemConfig);
}
}
}
boolean oldEnableCache = getEnableCache();
boolean curEnableCache = ObjectUtil.defaultIfNull(systemConfigDTO.getEnableCache(), false);
configCache.remove(SYSTEM_CONFIG_CACHE_KEY);
systemConfigRepository.saveAll(systemConfigList);
if (!oldEnableCache && curEnableCache) {
log.debug("检测到开启了缓存, 开启预热缓存");
fileCacheService.enableCache();
}
if (oldEnableCache && !curEnableCache) {
log.debug("检测到关闭了缓存, 正在清理缓存数据及关闭自动刷新");
fileCacheService.disableCache();
}
}
public void updateUsernameAndPwd(String username, String password) {
SystemConfig usernameConfig = systemConfigRepository.findByKey(SystemConfigConstant.USERNAME);
usernameConfig.setValue(username);
systemConfigRepository.save(usernameConfig);
String encryptionPassword = SecureUtil.md5(password);
SystemConfig systemConfig = systemConfigRepository.findByKey(SystemConfigConstant.PASSWORD);
systemConfig.setValue(encryptionPassword);
configCache.remove(SYSTEM_CONFIG_CACHE_KEY);
systemConfigRepository.save(systemConfig);
}
public void updateCacheEnableConfig(Boolean isEnable) {
SystemConfig enableConfig = systemConfigRepository.findByKey(SystemConfigConstant.ENABLE_CACHE);
enableConfig.setValue(isEnable.toString());
systemConfigRepository.save(enableConfig);
configCache.remove(SYSTEM_CONFIG_CACHE_KEY);
}
public AbstractFileService getCurrentFileService() {
StorageTypeEnum storageStrategy = getCurrentStorageStrategy();
return StorageTypeFactory.getStorageTypeService(storageStrategy);
}
public StorageTypeEnum getCurrentStorageStrategy() {
SystemConfigDTO systemConfigDTO = getSystemConfig();
return systemConfigDTO.getStorageStrategy();
}
public boolean getEnableCache() {
SystemConfigDTO systemConfigDTO = getSystemConfig();
return ObjectUtil.defaultIfNull(systemConfigDTO.getEnableCache(), false);
}
public boolean getSearchIgnoreCase() {
SystemConfigDTO systemConfigDTO = getSystemConfig();
return ObjectUtil.defaultIfNull(systemConfigDTO.getSearchIgnoreCase(), false);
}
}

View File

@@ -1,42 +0,0 @@
package im.zhaojun.common.service;
import im.zhaojun.common.model.constant.ZFileConstant;
import im.zhaojun.common.model.dto.FileItemDTO;
import im.zhaojun.common.model.dto.SiteConfigDTO;
import im.zhaojun.common.util.HttpUtil;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @author zhaojun
*/
@Service
public class SystemService {
@Resource
private SystemConfigService systemConfigService;
/**
* 构建指定路径下标题, 页头, 页尾
* @param path 路径
*/
public synchronized SiteConfigDTO getConfig(String path) throws Exception {
SiteConfigDTO siteConfigDTO = new SiteConfigDTO();
AbstractFileService fileService = systemConfigService.getCurrentFileService();
List<FileItemDTO> fileItemList = new ArrayList<>(fileService.fileList(path));
for (FileItemDTO fileItemDTO : fileItemList) {
if (ZFileConstant.FOOTER_FILE_NAME.equalsIgnoreCase(fileItemDTO.getName())) {
siteConfigDTO.setFooter(HttpUtil.getTextContent(fileItemDTO.getUrl()));
} else if (ZFileConstant.HEADER_FILE_NAME.equalsIgnoreCase(fileItemDTO.getName())) {
siteConfigDTO.setHeader(HttpUtil.getTextContent(fileItemDTO.getUrl()));
}
}
return siteConfigDTO;
}
}

View File

@@ -1,29 +0,0 @@
package im.zhaojun.common.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
/**
* @author zhaojun
*/
@Slf4j
public class HttpUtil {
public static String getTextContent(String url) {
RestTemplate restTemplate = SpringContextHolder.getBean("restTemplate");
String result = restTemplate.getForObject(url, String.class);
return result == null ? "" : result;
}
public static boolean checkUrlExist(String url) {
RestTemplate restTemplate = SpringContextHolder.getBean("restTemplate");
try {
restTemplate.headForHeaders(url);
return true;
} catch (RestClientException ignored) {
}
return false;
}
}

View File

@@ -1,93 +0,0 @@
package im.zhaojun.ftp.service;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.ftp.Ftp;
import im.zhaojun.common.model.StorageConfig;
import im.zhaojun.common.model.constant.StorageConfigConstant;
import im.zhaojun.common.model.dto.FileItemDTO;
import im.zhaojun.common.model.enums.FileTypeEnum;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.service.AbstractFileService;
import im.zhaojun.common.service.FileService;
import im.zhaojun.common.service.StorageConfigService;
import im.zhaojun.common.util.StringUtils;
import org.apache.commons.net.ftp.FTPFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* @author zhaojun
*/
@Service
public class FtpServiceImpl extends AbstractFileService implements FileService {
private static final Logger log = LoggerFactory.getLogger(FtpServiceImpl.class);
@Resource
private StorageConfigService storageConfigService;
private Ftp ftp;
private String domain;
@Override
public void init() {
try {
Map<String, StorageConfig> stringStorageConfigMap =
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.FTP);
String host = stringStorageConfigMap.get(StorageConfigConstant.HOST_KEY).getValue();
String port = stringStorageConfigMap.get(StorageConfigConstant.PORT_KEY).getValue();
String username = stringStorageConfigMap.get(StorageConfigConstant.USERNAME_KEY).getValue();
String password = stringStorageConfigMap.get(StorageConfigConstant.PASSWORD_KEY).getValue();
domain = stringStorageConfigMap.get(StorageConfigConstant.DOMAIN_KEY).getValue();
if (Objects.isNull(host) || Objects.isNull(port) || Objects.isNull(username) || Objects.isNull(password)) {
isInitialized = false;
} else {
ftp = new Ftp(host, Integer.parseInt(port), username, password);
isInitialized = testConnection();
}
} catch (Exception e) {
log.debug(getStorageTypeEnum().getDescription() + " 初始化异常, 已跳过");
}
}
@Override
public List<FileItemDTO> fileList(String path) {
FTPFile[] ftpFiles = ftp.lsFiles(path);
List<FileItemDTO> fileItemList = new ArrayList<>();
for (FTPFile ftpFile : ftpFiles) {
FileItemDTO fileItemDTO = new FileItemDTO();
fileItemDTO.setName(ftpFile.getName());
fileItemDTO.setSize(ftpFile.getSize());
fileItemDTO.setTime(ftpFile.getTimestamp().getTime());
fileItemDTO.setType(ftpFile.isDirectory() ? FileTypeEnum.FOLDER : FileTypeEnum.FILE);
fileItemDTO.setPath(path);
if (ftpFile.isFile()) {
fileItemDTO.setUrl(getDownloadUrl(StringUtils.concatUrl(path, fileItemDTO.getName())));
}
fileItemList.add(fileItemDTO);
}
return fileItemList;
}
@Override
public String getDownloadUrl(String path) {
return URLUtil.complateUrl(domain, path);
}
@Override
public StorageTypeEnum getStorageTypeEnum() {
return StorageTypeEnum.FTP;
}
}

View File

@@ -1,68 +0,0 @@
package im.zhaojun.local.controller;
import cn.hutool.core.util.URLUtil;
import im.zhaojun.common.exception.NotExistFileException;
import im.zhaojun.common.util.StringUtils;
import im.zhaojun.local.service.LocalServiceImpl;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.HandlerMapping;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.Date;
/**
* @author zhaojun
*/
@Controller
public class LocalController {
@Resource
private LocalServiceImpl localServiceImpl;
@GetMapping("/file/**")
@ResponseBody
public ResponseEntity<FileSystemResource> downAttachment(final HttpServletRequest request) {
String path = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String bestMatchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
AntPathMatcher apm = new AntPathMatcher();
String filePath = apm.extractPathWithinPattern(bestMatchPattern, path);
return export(new File(StringUtils.concatPath(localServiceImpl.getFilePath(), URLUtil.decode(filePath))));
}
private ResponseEntity<FileSystemResource> export(File file) {
if (!file.exists()) {
throw new NotExistFileException();
}
MediaType mediaType = MediaType.APPLICATION_OCTET_STREAM;
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.setContentDispositionFormData("attachment", URLUtil.encode(file.getName()));
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
headers.add("Last-Modified", new Date().toString());
headers.add("ETag", String.valueOf(System.currentTimeMillis()));
return ResponseEntity
.ok()
.headers(headers)
.contentLength(file.length())
.contentType(mediaType)
.body(new FileSystemResource(file));
}
}

View File

@@ -1,30 +0,0 @@
package im.zhaojun.onedrive.controller;
import im.zhaojun.onedrive.service.OneDriveService;
import im.zhaojun.onedrive.service.OneDriveToken;
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;
/**
* @author zhaojun
*/
@Controller
@RequestMapping("/onedirve")
public class OneDriveController {
@Resource
private OneDriveService oneDriveService;
@GetMapping("/callback")
public String onedriveCallback(String code, Model model) {
OneDriveToken oneDriveToken = oneDriveService.getToken(code);
model.addAttribute("accessToken", oneDriveToken.getAccessToken());
model.addAttribute("refreshToken", oneDriveToken.getRefreshToken());
return "callback";
}
}

View File

@@ -1,143 +0,0 @@
package im.zhaojun.onedrive.service;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import im.zhaojun.common.model.StorageConfig;
import im.zhaojun.common.model.constant.StorageConfigConstant;
import im.zhaojun.common.model.dto.FileItemDTO;
import im.zhaojun.common.model.enums.FileTypeEnum;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.repository.StorageConfigRepository;
import im.zhaojun.common.util.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @author zhaojun
* @date 2020/1/12 12:17
*/
@Service
public class OneDriveService {
private static final String DRIVER_INFO_URL = "https://graph.microsoft.com/v1.0/drive";
private static final String DRIVER_ROOT_URL = "https://graph.microsoft.com/v1.0/drive/root/children";
private static final String DRIVER_ITEMS_URL = "https://graph.microsoft.com/v1.0/drive/root:{path}:/children";
private static final String AUTHENTICATE_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
@Value("${zfile.onedirve.clientId}")
private String clientId;
@Value("${zfile.onedirve.redirectUri}")
private String redirectUri;
@Value("${zfile.onedirve.clientSecret}")
private String clientSecret;
@Value("${zfile.onedirve.scope}")
private String scope;
@Resource
private RestTemplate oneDriveRestTemplate;
@Resource
private StorageConfigRepository storageConfigRepository;
public OneDriveToken getToken(String code) {
String param = "client_id=" + clientId +
"&redirect_uri=" + redirectUri +
"&client_secret=" + clientSecret +
"&code=" + code +
"&scope=" + scope +
"&grant_type=authorization_code";
HttpRequest post = HttpUtil.createPost(AUTHENTICATE_URL);
post.body(param, "application/x-www-form-urlencoded");
HttpResponse response = post.execute();
return JSONObject.parseObject(response.body(), OneDriveToken.class);
}
public OneDriveToken getRefreshToken() {
StorageConfig refreshStorageConfig =
storageConfigRepository.findByTypeAndKey(StorageTypeEnum.ONE_DRIVE, StorageConfigConstant.REFRESH_TOKEN_KEY);
String param = "client_id=" + clientId +
"&redirect_uri=" + redirectUri +
"&client_secret=" + clientSecret +
"&refresh_token=" + refreshStorageConfig.getValue() +
"&grant_type=refresh_token";
HttpRequest post = HttpUtil.createPost(AUTHENTICATE_URL);
post.body(param, "application/x-www-form-urlencoded");
HttpResponse response = post.execute();
return JSONObject.parseObject(response.body(), OneDriveToken.class);
}
public String getUserInfo() {
return oneDriveRestTemplate.getForObject(DRIVER_INFO_URL, String.class);
}
public List<FileItemDTO> list(String path) {
List<FileItemDTO> result = new ArrayList<>();
String nextLink = null;
do {
String requestUrl;
if (nextLink != null) {
requestUrl = nextLink;
}else if ("/".equalsIgnoreCase(path)) {
requestUrl = DRIVER_ROOT_URL;
} else {
requestUrl = DRIVER_ITEMS_URL;
}
path = StringUtils.removeLastSeparator(path);
ResponseEntity<String> responseEntity = oneDriveRestTemplate.getForEntity(requestUrl, String.class, path);
String body = responseEntity.getBody();
JSONObject root = JSON.parseObject(body);
nextLink = root.getString("@odata.nextLink");
JSONArray fileList = root.getJSONArray("value");
for (int i = 0; i < fileList.size(); i++) {
FileItemDTO fileItemDTO = new FileItemDTO();
JSONObject fileItem = fileList.getJSONObject(i);
fileItemDTO.setName(fileItem.getString("name"));
fileItemDTO.setSize(fileItem.getLong("size"));
fileItemDTO.setTime(fileItem.getDate("lastModifiedDateTime"));
if (fileItem.containsKey("file")) {
fileItemDTO.setUrl(fileItem.getString("@microsoft.graph.downloadUrl"));
fileItemDTO.setType(FileTypeEnum.FILE);
} else {
fileItemDTO.setType(FileTypeEnum.FOLDER);
}
fileItemDTO.setPath(path);
result.add(fileItemDTO);
}
} while (nextLink != null);
return result;
}
}

View File

@@ -1,76 +0,0 @@
package im.zhaojun.onedrive.service;
import im.zhaojun.common.config.GlobalScheduleTask;
import im.zhaojun.common.model.StorageConfig;
import im.zhaojun.common.model.constant.StorageConfigConstant;
import im.zhaojun.common.model.dto.FileItemDTO;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.service.AbstractFileService;
import im.zhaojun.common.service.FileService;
import im.zhaojun.common.service.StorageConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* @author zhaojun
* @date 2020/1/12 13:53
*/
@Service
@Slf4j
public class OneDriveServiceImpl extends AbstractFileService implements FileService {
public static final String SYSTEM_ONEDRIVE_CACHE_PREFIX = "zfile-onedrive-cache:";
public static final String SYSTEM_ONEDRIVE_CACHE_ASSESS_TOKEN_KEY = "accessToken";
public static final String SYSTEM_ONEDRIVE_CACHE_REFRESH_TOKEN_KEY = "refreshToken";
@Resource
private GlobalScheduleTask globalScheduleTask;
@Resource
private StorageConfigService storageConfigService;
@Resource
private OneDriveService oneDriveService;
@Override
public void init() {
try {
Map<String, StorageConfig> stringStorageConfigMap =
storageConfigService.selectStorageConfigMapByKey(StorageTypeEnum.ONE_DRIVE);
String accessToken = stringStorageConfigMap.get(StorageConfigConstant.ACCESS_TOKEN_KEY).getValue();
String refreshToken = stringStorageConfigMap.get(StorageConfigConstant.REFRESH_TOKEN_KEY).getValue();
if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(refreshToken)) {
log.debug("初始化存储策略 [{}] 失败: 参数不完整", getStorageTypeEnum().getDescription());
isInitialized = false;
} else {
globalScheduleTask.refreshOneDriveToken();
isInitialized = testConnection();
}
} catch (Exception e) {
log.debug(getStorageTypeEnum().getDescription() + " 初始化异常, 已跳过");
}
}
@Override
public List<FileItemDTO> fileList(String path) {
return oneDriveService.list(path);
}
@Override
public String getDownloadUrl(String path) {
return null;
}
@Override
public StorageTypeEnum getStorageTypeEnum() {
return StorageTypeEnum.ONE_DRIVE;
}
}

View File

@@ -1,7 +1,5 @@
package im.zhaojun;
package im.zhaojun.zfile;
import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@@ -12,8 +10,6 @@ import org.springframework.scheduling.annotation.EnableAsync;
*/
@EnableAsync
@SpringBootApplication
@EnableMethodCache(basePackages = "im.zhaojun", proxyTargetClass = true)
@EnableCreateCacheAnnotation
@EnableAspectJAutoProxy(exposeProxy = true)
public class ZfileApplication {

View File

@@ -0,0 +1,65 @@
package im.zhaojun.zfile.aspect;
import im.zhaojun.zfile.cache.ZFileCache;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.entity.DriveConfig;
import im.zhaojun.zfile.service.DriveConfigService;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* @author zhaojun
* 缓存切面类, 用于访问文件夹时, 缓存文件列表内容.
*/
@Aspect
@Component
public class FileListCacheAop {
@Resource
private ZFileCache zFileCache;
@Resource
private DriveConfigService driveConfigService;
/**
* 缓存切面, 如果此驱动器开启了缓存, 则从缓存中取数据, 没有开启, 则直接调用方法.
*/
@Around(value = "execution(public * im.zhaojun.zfile.service.base.AbstractBaseFileService.fileList(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
List<FileItemDTO> result;
// 获取请求路径
Object[] args = point.getArgs();
String path = String.valueOf(args[0]);
// 获取当前驱动器
AbstractBaseFileService fileService = ((AbstractBaseFileService) point.getTarget());
Integer driveId = fileService.driveId;
// 判断驱动器是否开启了缓存
DriveConfig driveConfig = driveConfigService.findById(driveId);
boolean enableCache = driveConfig.getEnableCache();
if (enableCache) {
List<FileItemDTO> cacheFileList = zFileCache.get(driveId, path);
if (cacheFileList == null) {
result = (List<FileItemDTO>) point.proceed();
zFileCache.put(driveId, path, result);
} else {
result = cacheFileList;
}
} else {
result = (List<FileItemDTO>) point.proceed();
}
return result;
}
}

View File

@@ -0,0 +1,254 @@
package im.zhaojun.zfile.cache;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.CacheObj;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.util.StrUtil;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.dto.SystemConfigDTO;
import im.zhaojun.zfile.model.entity.DriveConfig;
import im.zhaojun.zfile.repository.DriverConfigRepository;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* ZFile 缓存类
*
* @author zhaojun
*/
@Component
public class ZFileCache {
@Value("${zfile.cache.timeout}")
private long timeout;
/**
* 缓存 map 对象.
*
* ConcurrentMap<Integer, ConcurrentHashMap<String, List<FileItemDTO>>>
* ConcurrentMap<driveId, ConcurrentHashMap<key, value>>
*
* driveId: 驱动器 ID
* key: 文件夹路径
* value: 文件夹中内容
*/
private ConcurrentMap<Integer, TimedCache<String, List<FileItemDTO>>> drivesCache = new ConcurrentHashMap<>();
/**
* 系统设置缓存
*/
private SystemConfigDTO systemConfigCache;
@Resource
private DriverConfigRepository driverConfigRepository;
/**
* 写入缓存
*
* @param driveId
* 驱动器 ID
*
* @param key
* 文件夹路径
*
* @param value
* 文件夹中列表
*/
public synchronized void put(Integer driveId, String key, List<FileItemDTO> value) {
getCacheByDriveId(driveId).put(key, value);
}
/**
* 获取指定驱动器, 某个文件夹的名称
*
* @param driveId
* 驱动器 ID
*
* @param key
* 文件夹路径
*
* @return 驱动器中文件夹的内容
*/
public List<FileItemDTO> get(Integer driveId, String key) {
return getCacheByDriveId(driveId).get(key, false);
}
/**
* 清空指定驱动器的缓存.
*
* @param driveId
* 驱动器 ID
*/
public void clear(Integer driveId) {
getCacheByDriveId(driveId).clear();
}
/**
* 获取指定驱动器中已缓存文件夹数量
*
* @param driveId
* 驱动器 ID
*
* @return 已缓存文件夹数量
*/
public int cacheCount(Integer driveId) {
return getCacheByDriveId(driveId).size();
}
/**
* 指定驱动器, 根据文件及文件名查找相关的文件
*
* @param driveId
* 驱动器 ID
*
* @param key
* 搜索键, 可匹配文件夹名称和文件名称.
*
* @return 搜索结果, 包含文件夹和文件.
*/
public List<FileItemDTO> find(Integer driveId, String key) {
List<FileItemDTO> result = new ArrayList<>();
DriveConfig driveConfig = driverConfigRepository.getOne(driveId);
boolean searchContainEncryptedFile = driveConfig.getSearchContainEncryptedFile();
boolean ignoreCase = driveConfig.getSearchIgnoreCase();
for (List<FileItemDTO> fileItemList : getCacheByDriveId(driveId)) {
// 过滤加密文件
if (!searchContainEncryptedFile && isEncryptedFolder(fileItemList)) {
continue;
}
for (FileItemDTO fileItemDTO : fileItemList) {
boolean testResult;
// 根据是否需要忽略大小写来匹配文件(夹)名
if (ignoreCase) {
testResult = StrUtil.containsIgnoreCase(fileItemDTO.getName(), key);
} else {
testResult = fileItemDTO.getName().contains(key);
}
if (testResult) {
result.add(fileItemDTO);
}
}
}
return result;
}
/**
* 获取所有缓存 key (文件夹名称)
*
* @return 所有缓存 key
*/
public Set<String> keySet(Integer driveId) {
Iterator<CacheObj<String, List<FileItemDTO>>> cacheObjIterator = getCacheByDriveId(driveId).cacheObjIterator();
Set<String> keys = new HashSet<>();
while (cacheObjIterator.hasNext()) {
keys.add(cacheObjIterator.next().getKey());
}
return keys;
}
/**
* 从缓存中删除指定存储器的某个路径的缓存
*
* @param driveId
* 驱动器 ID
*
* @param key
* 文件夹路径
*/
public void remove(Integer driveId, String key) {
getCacheByDriveId(driveId).remove(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 list
* 文件夹中的内容
*
* @return 返回此文件夹是否是加密的 ().
*/
private boolean isEncryptedFolder(List<FileItemDTO> list) {
// 遍历文件判断是否包含
for (FileItemDTO fileItemDTO : list) {
if (Objects.equals(ZFileConstant.PASSWORD_FILE_NAME, fileItemDTO.getName())) {
return true;
}
}
return false;
}
/**
* 获取指定驱动器对应的缓存
*
* @param driveId
* 驱动器 ID
*
* @return 驱动器对应的缓存
*/
private synchronized TimedCache<String, List<FileItemDTO>> getCacheByDriveId(Integer driveId) {
TimedCache<String, List<FileItemDTO>> driveCache = drivesCache.get(driveId);
if (driveCache == null) {
driveCache = CacheUtil.newTimedCache(timeout * 1000);
drivesCache.put(driveId, driveCache);
}
return driveCache;
}
}

View File

@@ -1,20 +1,23 @@
package im.zhaojun.onedrive.config;
package im.zhaojun.zfile.config;
import im.zhaojun.common.model.StorageConfig;
import im.zhaojun.common.model.constant.StorageConfigConstant;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.service.StorageConfigService;
import im.zhaojun.zfile.model.constant.StorageConfigConstant;
import im.zhaojun.zfile.model.entity.StorageConfig;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.service.StorageConfigService;
import im.zhaojun.zfile.service.impl.OneDriveChinaServiceImpl;
import im.zhaojun.zfile.service.impl.OneDriveServiceImpl;
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.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.LinkedList;
/**
* @author zhaojun
* @date 2020/1/18 17:13
*/
@Configuration
public class OneDriveConfig {
@@ -22,13 +25,27 @@ public class OneDriveConfig {
@Resource
private StorageConfigService storageConfigService;
@Resource
private OneDriveServiceImpl oneDriveServiceImpl;
@Resource
private OneDriveChinaServiceImpl oneDriveChinaServiceImpl;
/**
* OneDrive 请求 RestTemplate, 会在请求头中添加 Bearer: Authorization {token} 信息, 用于 API 认证.
*/
@Bean
public RestTemplate oneDriveRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
ClientHttpRequestInterceptor interceptor = (httpRequest, bytes, clientHttpRequestExecution) -> {
HttpHeaders headers = httpRequest.getHeaders();
Integer driveId = Integer.valueOf(((LinkedList)headers.get("driveId")).get(0).toString());
StorageConfig accessTokenConfig =
storageConfigService.selectByTypeAndKey(StorageTypeEnum.ONE_DRIVE, StorageConfigConstant.ACCESS_TOKEN_KEY);
storageConfigService.findByDriveIdAndKey(driveId, StorageConfigConstant.ACCESS_TOKEN_KEY);
String tokenValue = String.format("%s %s", "Bearer", accessTokenConfig.getValue());
httpRequest.getHeaders().add("Authorization", tokenValue);
return clientHttpRequestExecution.execute(httpRequest, bytes);
@@ -37,5 +54,4 @@ public class OneDriveConfig {
return restTemplate;
}
}
}

View File

@@ -0,0 +1,34 @@
package im.zhaojun.zfile.config;
import im.zhaojun.zfile.model.enums.StorageTypeEnumDeSerializerConvert;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author zhaojun
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StorageTypeEnumDeSerializerConvert());
}
@Bean
public ServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory webServerFactory = new TomcatServletWebServerFactory();
// 添加对 URL 中特殊符号的支持.
webServerFactory.addConnectorCustomizers(connector -> {
connector.setAttribute("relaxedPathChars", "<>[\\]^`{|}");
connector.setAttribute("relaxedQueryChars", "<>[\\]^`{|}");
});
return webServerFactory;
}
}

View File

@@ -1,11 +1,14 @@
package im.zhaojun.common.config;
package im.zhaojun.zfile.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
/**
* @author zhaojun
@@ -17,6 +20,14 @@ public class ZFileConfiguration {
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
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

@@ -0,0 +1,107 @@
package im.zhaojun.zfile.context;
import im.zhaojun.zfile.model.entity.DriveConfig;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.service.DriveConfigService;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import im.zhaojun.zfile.util.SpringContextHolder;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* 驱动器上下文环境
* @author zhaojun
*/
@Component
@DependsOn("springContextHolder")
public class DriveContext implements ApplicationContextAware {
private static Map<Integer, AbstractBaseFileService> drivesServiceMap = new ConcurrentHashMap<>();
private static Map<StorageTypeEnum, Class<AbstractBaseFileService>> storageTypeEnumClassMap = new ConcurrentHashMap<>();
@Resource
private DriveConfigService driveConfigService;
/**
* 初始化指定驱动器的 Service, 添加到上下文环境中.
*
* @param driveId
* 驱动器 ID.
*/
public void initDrive(Integer driveId) {
AbstractBaseFileService baseFileService = getBeanByDriveId(driveId);
if (baseFileService != null) {
baseFileService.init(driveId);
drivesServiceMap.put(driveId, baseFileService);
}
}
/**
* 获取指定驱动器的 Service.
*
* @param driveId
* 驱动器 ID
*
* @return 驱动器对应的 Service
*/
public AbstractBaseFileService getDriveService(Integer driveId) {
return drivesServiceMap.get(driveId);
}
/**
* 销毁指定驱动器的 Service.
*
* @param driveId
* 驱动器 ID
*/
public void destroyDrive(Integer driveId) {
drivesServiceMap.remove(driveId);
}
/**
* 获取指定驱动器对应的 Service, 状态为未初始化
*
* @param driveId
* 驱动器 ID
*
* @return 驱动器对应未初始化的 Service
*/
private AbstractBaseFileService getBeanByDriveId(Integer driveId) {
StorageTypeEnum storageTypeEnum = driveConfigService.findStorageTypeById(driveId);
Map<String, AbstractBaseFileService> beansOfType = SpringContextHolder.getBeansOfType(AbstractBaseFileService.class);
for (AbstractBaseFileService value : beansOfType.values()) {
if (Objects.equals(value.getStorageTypeEnum(), storageTypeEnum)) {
return SpringContextHolder.getBean(value.getClass());
}
}
return null;
}
/**
* 项目启动时, 自动调用所有驱动器进行初始化.
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
List<DriveConfig> list = driveConfigService.list();
for (DriveConfig driveConfig : list) {
initDrive(driveConfig.getId());
}
}
}

View File

@@ -1,24 +1,26 @@
package im.zhaojun.common.config;
package im.zhaojun.zfile.context;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.service.AbstractFileService;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 存储类型工厂类
* @author zhaojun
*/
@Component
public class StorageTypeFactory implements ApplicationContextAware {
public class StorageTypeContext implements ApplicationContextAware {
private static Map<String, AbstractFileService> storageTypeEnumFileServiceMap;
private static Map<String, AbstractBaseFileService> storageTypeEnumFileServiceMap;
private static ApplicationContext applicationContext;
/**
* 项目启动时执行
*/
@@ -27,15 +29,16 @@ public class StorageTypeFactory implements ApplicationContextAware {
applicationContext = act;
// 获取 Spring 容器中所有 FileService 类型的类
storageTypeEnumFileServiceMap = act.getBeansOfType(AbstractFileService.class);
storageTypeEnumFileServiceMap = act.getBeansOfType(AbstractBaseFileService.class);
}
/**
* 获取指定存储类型 Service
*/
public static AbstractFileService getStorageTypeService(StorageTypeEnum type) {
AbstractFileService result = null;
for (AbstractFileService fileService : storageTypeEnumFileServiceMap.values()) {
public static AbstractBaseFileService getStorageTypeService(StorageTypeEnum type) {
AbstractBaseFileService result = null;
for (AbstractBaseFileService fileService : storageTypeEnumFileServiceMap.values()) {
if (fileService.getStorageTypeEnum() == type) {
result = fileService;
break;
@@ -44,7 +47,9 @@ public class StorageTypeFactory implements ApplicationContextAware {
return result;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
}

View File

@@ -0,0 +1,58 @@
package im.zhaojun.zfile.controller;
import im.zhaojun.zfile.model.dto.ResultBean;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.util.AudioHelper;
import im.zhaojun.zfile.util.HttpUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 公共 Controller
* @author zhaojun
*/
@RestController
@RequestMapping("/common")
public class CommonController {
/**
* 返回系统支持的所有存储策略
*
* @return 存储策略
*/
@GetMapping("/support-strategy")
public ResultBean supportStrategy() {
return ResultBean.successData(StorageTypeEnum.values());
}
/**
* 获取文件内容, 仅限用于 txt, md, ini 等普通文本文件.
*
* @param url
* 文件路径
*
* @return 文件内容
*/
@GetMapping("/content")
public ResultBean getContent(String url) {
return ResultBean.successData(HttpUtil.getTextContent(url));
}
/**
* 获取音频文件信息
*
* @param url
* 文件 URL
*
* @return 音频信息, 标题封面等信息
*/
@GetMapping("/audio-info")
public ResultBean getAudioInfo(String url) throws Exception {
return ResultBean.success(AudioHelper.getAudioInfo(url));
}
}

View File

@@ -0,0 +1,220 @@
package im.zhaojun.zfile.controller;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.dto.ResultBean;
import im.zhaojun.zfile.model.dto.SystemFrontConfigDTO;
import im.zhaojun.zfile.model.support.FilePageModel;
import im.zhaojun.zfile.service.DriveConfigService;
import im.zhaojun.zfile.service.SystemConfigService;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import im.zhaojun.zfile.context.DriveContext;
import im.zhaojun.zfile.util.FileComparator;
import im.zhaojun.zfile.util.HttpUtil;
import im.zhaojun.zfile.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
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.RestController;
import org.springframework.web.client.HttpClientErrorException;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* 前台文件管理
* @author zhaojun
*/
@Slf4j
@RequestMapping("/api")
@RestController
public class FileController {
@Resource
private SystemConfigService systemConfigService;
@Resource
private DriveContext driveContext;
@Resource
private DriveConfigService driveConfigService;
/**
* 滚动加载每页条数.
*/
private static final Integer PAGE_SIZE = 30;
/**
* 获取所有驱动器
*
* @return 所有驱动器
*/
@GetMapping("/drive/list")
public ResultBean drives() {
return ResultBean.success(driveConfigService.list());
}
/**
* 获取某个驱动器下, 指定路径的数据, 每页固定 {@link #PAGE_SIZE} 条数据.
*
* @param driveId
* 驱动器 ID
*
* @param path
* 路径
*
* @param password
* 文件夹密码, 某些文件夹需要密码才能访问, 当不需要密码时, 此参数可以为空
*
* @param page
* 页数
*
* @return 当前路径下所有文件及文件夹
*/
@GetMapping("/list/{driveId}")
public ResultBean list(@PathVariable(name = "driveId") Integer driveId,
@RequestParam(defaultValue = "/") String path,
@RequestParam(required = false) String password,
@RequestParam(defaultValue = "1") Integer page) throws Exception {
AbstractBaseFileService fileService = driveContext.getDriveService(driveId);
List<FileItemDTO> fileItemList = fileService.fileList(StringUtils.removeDuplicateSeparator("/" + path + "/"));
for (FileItemDTO fileItemDTO : fileItemList) {
if (ZFileConstant.PASSWORD_FILE_NAME.equals(fileItemDTO.getName())) {
String expectedPasswordContent;
try {
expectedPasswordContent = HttpUtil.getTextContent(fileItemDTO.getUrl());
} catch (HttpClientErrorException httpClientErrorException) {
log.debug("尝试重新获取密码文件缓存中链接后仍失败", httpClientErrorException);
try {
String fullPath = StringUtils.removeDuplicateSeparator(fileItemDTO.getPath() + "/" + fileItemDTO.getName());
FileItemDTO fileItem = fileService.getFileItem(fullPath);
expectedPasswordContent = HttpUtil.getTextContent(fileItem.getUrl());
} catch (Exception e) {
log.debug("尝试重新获取密码文件链接后仍失败, 已暂时取消密码", e);
break;
}
}
if (Objects.equals(expectedPasswordContent, password)) {
break;
}
if (password != null && !"".equals(password)) {
return ResultBean.error("密码错误.", ResultBean.INVALID_PASSWORD);
}
return ResultBean.error("此文件夹需要密码.", ResultBean.REQUIRED_PASSWORD);
}
}
return ResultBean.successData(getSortedPagingData(fileItemList, page));
}
/**
* 获取系统配置信息和当前页的标题, 页面文档信息
*
* @param driveId
* 驱动器 ID
*
* @return 返回指定存储器的系统配置信息
*/
@GetMapping("/config/{driveId}")
public ResultBean getConfig(@PathVariable(name = "driveId") Integer driveId, String path) {
SystemFrontConfigDTO systemConfig = systemConfigService.getSystemFrontConfig(driveId);
AbstractBaseFileService fileService = driveContext.getDriveService(driveId);
String fullPath = StringUtils.removeDuplicateSeparator(path + "/" + ZFileConstant.README_FILE_NAME);
try {
FileItemDTO fileItem = fileService.getFileItem(fullPath);
String readme = HttpUtil.getTextContent(fileItem.getUrl());
systemConfig.setReadme(readme);
} catch (Exception e) {
// ignore
}
return ResultBean.successData(systemConfig);
}
@GetMapping("/search/{driveId}")
public ResultBean search(@RequestParam(value = "name", defaultValue = "/") String name,
@RequestParam(defaultValue = "name") String sortBy,
@RequestParam(defaultValue = "asc") String order,
@RequestParam(defaultValue = "1") Integer page,
@PathVariable("driveId") Integer driveId) {
return ResultBean.error("暂不支持搜索功能");
}
/**
* 过滤文件列表, 去除密码, 文档文件.
*
* @param fileItemList
* 文件列表
*/
private void filterFileList(List<FileItemDTO> fileItemList) {
if (fileItemList == null) {
return;
}
fileItemList.removeIf(fileItem -> ZFileConstant.PASSWORD_FILE_NAME.equals(fileItem.getName())
|| ZFileConstant.README_FILE_NAME.equals(fileItem.getName()));
}
/**
* 对传入的文件列表, 按照文件名进行排序, 然后取相应页数的文件
*
* @param fileItemList
* 文件列表
*
* @param page
* 要取的页数
*
* @return 排序及分页后的那段数据
*/
private FilePageModel getSortedPagingData(List<FileItemDTO> fileItemList, Integer page) {
ArrayList<FileItemDTO> copy = new ArrayList<>(Arrays.asList(new FileItemDTO[fileItemList.size()]));
Collections.copy(copy, fileItemList);
// 排序, 先按照文件类型比较, 文件夹在前, 文件在后, 然后根据 sortBy 字段排序, 默认为升序;
copy.sort(new FileComparator());
filterFileList(copy);
int total = copy.size();
int totalPage = (total + PAGE_SIZE - 1) / PAGE_SIZE;
if (page > totalPage) {
return new FilePageModel(totalPage, Collections.emptyList());
}
int start = (page - 1) * PAGE_SIZE;
int end = page * PAGE_SIZE;
end = Math.min(end, total);
return new FilePageModel(totalPage, copy.subList(start, end));
}
/**
* 获取指定路径下的文件信息内容
*
* @param driveId
* 驱动器 ID
*
* @param path
* 文件全路径
*
* @return 该文件的名称, 路径, 大小, 下载地址等信息.
*/
@GetMapping("/directlink/{driveId}")
public ResultBean directlink(@PathVariable(name = "driveId") Integer driveId, String path) {
AbstractBaseFileService fileService = driveContext.getDriveService(driveId);
return ResultBean.successData(fileService.getFileItem(path));
}
}

View File

@@ -0,0 +1,53 @@
package im.zhaojun.zfile.controller;
import cn.hutool.crypto.SecureUtil;
import im.zhaojun.zfile.controller.admin.AdminController;
import im.zhaojun.zfile.model.dto.ResultBean;
import im.zhaojun.zfile.model.dto.SystemConfigDTO;
import im.zhaojun.zfile.service.StorageConfigService;
import im.zhaojun.zfile.service.SystemConfigService;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 系统安装初始化
* @author zhaojun
*/
@RestController
public class InstallController {
@Resource
private SystemConfigService systemConfigService;
@Resource
private StorageConfigService storageConfigService;
@Resource
private AdminController adminController;
@GetMapping("/is-installed")
public ResultBean isInstall() {
if (!StringUtils.isEmpty(systemConfigService.getAdminUsername())) {
return ResultBean.error("请勿重复初始化");
}
return ResultBean.success();
}
@PostMapping("/install")
public ResultBean install(SystemConfigDTO systemConfigDTO) {
if (!StringUtils.isEmpty(systemConfigService.getAdminUsername())) {
return ResultBean.error("请勿重复初始化.");
}
systemConfigDTO.setPassword(SecureUtil.md5(systemConfigDTO.getPassword()));
systemConfigService.updateSystemConfig(systemConfigDTO);
return ResultBean.success();
}
}

View File

@@ -0,0 +1,49 @@
package im.zhaojun.zfile.controller;
import im.zhaojun.zfile.service.impl.LocalServiceImpl;
import im.zhaojun.zfile.context.DriveContext;
import im.zhaojun.zfile.util.FileUtil;
import im.zhaojun.zfile.util.StringUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.HandlerMapping;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
/**
* 本地存储 Controller
* @author zhaojun
*/
@Controller
public class LocalController {
@Resource
private DriveContext driveContext;
/**
* 本地存储下载指定文件
*
* @param driveId
* 驱动器 ID
*
* @return 文件
*/
@GetMapping("/file/{driveId}/**")
@ResponseBody
public ResponseEntity<Object> downAttachment(@PathVariable("driveId") Integer driveId, final HttpServletRequest request) {
String path = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String bestMatchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
AntPathMatcher apm = new AntPathMatcher();
String filePath = apm.extractPathWithinPattern(bestMatchPattern, path);
LocalServiceImpl localService = (LocalServiceImpl) driveContext.getDriveService(driveId);
return FileUtil.export(new File(StringUtils.concatPath(localService.getFilePath(), filePath)));
}
}

View File

@@ -0,0 +1,43 @@
package im.zhaojun.zfile.controller;
import im.zhaojun.zfile.model.support.OneDriveToken;
import im.zhaojun.zfile.service.impl.OneDriveChinaServiceImpl;
import im.zhaojun.zfile.service.impl.OneDriveServiceImpl;
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;
/**
* @author zhaojun
*/
@Controller
@RequestMapping("/onedrive")
public class OneDriveController {
@Resource
private OneDriveServiceImpl oneDriveServiceImpl;
@Resource
private OneDriveChinaServiceImpl oneDriveChinaServiceImpl;
@GetMapping("/callback")
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-callback")
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

@@ -0,0 +1,70 @@
package im.zhaojun.zfile.controller;
import cn.hutool.core.util.URLUtil;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.enums.FileTypeEnum;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import im.zhaojun.zfile.context.DriveContext;
import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.servlet.HandlerMapping;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
* @author Zhao Jun
*/
@Controller
public class PageController {
@Resource
private DriveContext driveContext;
/**
* 获取指定驱动器, 某个文件的直链, 然后重定向过去.
* @param driveId
* 驱动器 ID
*
* @return 重定向至文件直链
*/
@GetMapping("/directlink/{driveId}/**")
public String directlink(@PathVariable("driveId") Integer driveId, final HttpServletRequest request) {
String path = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String bestMatchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
AntPathMatcher apm = new AntPathMatcher();
String filePath = apm.extractPathWithinPattern(bestMatchPattern, path);
if (filePath.length() > 0 && filePath.charAt(0) != ZFileConstant.PATH_SEPARATOR_CHAR) {
filePath = "/" + filePath;
}
AbstractBaseFileService fileService = driveContext.getDriveService(driveId);
FileItemDTO fileItem = fileService.getFileItem(filePath);
String url = fileItem.getUrl();
int queryIndex = url.indexOf('?');
if (queryIndex != -1) {
String origin = url.substring(0, queryIndex);
String queryString = url.substring(queryIndex + 1);
url = URLUtil.encode(origin) + "?" + URLUtil.encode(queryString);
} else {
url = URLUtil.encode(url);
}
if (Objects.equals(fileItem.getType(), FileTypeEnum.FOLDER)) {
return "redirect:" + fileItem.getUrl();
} else {
return "redirect:" + url;
}
}
}

View File

@@ -0,0 +1,119 @@
package im.zhaojun.zfile.controller.admin;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ZipUtil;
import im.zhaojun.zfile.context.StorageTypeContext;
import im.zhaojun.zfile.model.dto.ResultBean;
import im.zhaojun.zfile.model.dto.StorageStrategyDTO;
import im.zhaojun.zfile.model.dto.SystemConfigDTO;
import im.zhaojun.zfile.model.entity.StorageConfig;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.model.support.SystemMonitorInfo;
import im.zhaojun.zfile.service.SystemConfigService;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import im.zhaojun.zfile.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
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;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 管理后台接口
* @author zhaojun
*/
@RestController
@RequestMapping("/admin")
@Slf4j
public class AdminController {
@Resource
private SystemConfigService systemConfigService;
/**
* 获取系统配置
*/
@GetMapping("/config")
public ResultBean getConfig() {
SystemConfigDTO systemConfigDTO = systemConfigService.getSystemConfig();
return ResultBean.success(systemConfigDTO);
}
/**
* 更新系统配置
*/
@PostMapping("/config")
public ResultBean updateConfig(SystemConfigDTO systemConfigDTO) throws Exception {
systemConfigDTO.setId(1);
systemConfigService.updateSystemConfig(systemConfigDTO);
return ResultBean.success();
}
/**
* 修改管理员登陆密码
*/
@PostMapping("/update-pwd")
public ResultBean updatePwd(String username, String password) {
systemConfigService.updateUsernameAndPwd(username, password);
return ResultBean.success();
}
/**
* 获取指定存储策略的表单域
*
* @param storageType
* 存储策略
*
* @return 所有表单域
*/
@GetMapping("/strategy-form")
public ResultBean getFormByStorageType(StorageTypeEnum storageType) {
AbstractBaseFileService storageTypeService = StorageTypeContext.getStorageTypeService(storageType);
List<StorageConfig> storageConfigList = storageTypeService.storageStrategyConfigList();
return ResultBean.success(storageConfigList);
}
/**
* 返回支持的存储引擎.
*/
@GetMapping("/support-strategy")
public ResultBean supportStrategy() {
List<StorageStrategyDTO> result = new ArrayList<>();
StorageTypeEnum[] values = StorageTypeEnum.values();
return ResultBean.successData(values);
}
/**
* 系统日志下载
*/
@GetMapping("/log")
public ResponseEntity<Object> downloadLog(HttpServletResponse response) {
String userHome = System.getProperty("user.home");
File fileZip = ZipUtil.zip(userHome + "/.zfile/logs");
String currentDate = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss");
return FileUtil.export(fileZip, "ZFile 诊断日志 - " + currentDate + ".zip");
}
/**
* 获取系统监控信息
*/
@GetMapping("monitor")
public ResultBean monitor() {
return ResultBean.success(new SystemMonitorInfo());
}
}

View File

@@ -0,0 +1,37 @@
package im.zhaojun.zfile.controller.admin;
import im.zhaojun.zfile.model.dto.ResultBean;
import im.zhaojun.zfile.service.DriveConfigService;
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;
/**
* 缓存 Controller
*
* @author zhaojun
*/
@RestController
@RequestMapping("/admin/cache")
public class CacheController {
@Resource
private DriveConfigService driveConfigService;
@PostMapping("/{driveId}/enable")
public ResultBean enableCache(@PathVariable("driveId") Integer driveId) {
driveConfigService.updateCacheStatus(driveId, true);
return ResultBean.success();
}
@PostMapping("/{driveId}/disable")
public ResultBean disableCache(@PathVariable("driveId") Integer driveId) {
driveConfigService.updateCacheStatus(driveId, false);
return ResultBean.success();
}
}

View File

@@ -0,0 +1,81 @@
package im.zhaojun.zfile.controller.admin;
import im.zhaojun.zfile.model.dto.DriveConfigDTO;
import im.zhaojun.zfile.model.dto.ResultBean;
import im.zhaojun.zfile.model.entity.DriveConfig;
import im.zhaojun.zfile.service.DriveConfigService;
import lombok.extern.slf4j.Slf4j;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* 驱动器 Controller
* @author zhaojun
*/
@RestController
@RequestMapping("/admin")
@Slf4j
public class DriveController {
@Resource
private DriveConfigService driveConfigService;
/**
* 获取所有驱动器列表
*
* @return 驱动器列表
*/
@GetMapping("drives")
public ResultBean driveList() {
List<DriveConfig> list = driveConfigService.list();
return ResultBean.success(list);
}
/**
* 获取指定驱动器基本信息及其参数
*
* @param id
* 驱动器 ID
*
* @return 驱动器基本信息信息
*/
@GetMapping("drive/{id}")
public ResultBean driveItem(@PathVariable Integer id) {
DriveConfigDTO driveConfig = driveConfigService.findDriveConfigDTOById(id);
return ResultBean.success(driveConfig);
}
/**
* 保存驱动器设置
*/
@PostMapping("drive")
public ResultBean saveDriveItem(@RequestBody DriveConfigDTO driveConfigDTO) {
driveConfigService.save(driveConfigDTO);
return ResultBean.success();
}
/**
* 删除驱动器设置
*
* @param id
* 驱动器 ID
*/
@DeleteMapping("drive/{id}")
public ResultBean deleteDriveItem(@PathVariable Integer id) {
driveConfigService.deleteById(id);
return ResultBean.success();
}
}

View File

@@ -1,6 +1,6 @@
package im.zhaojun.common.exception;
package im.zhaojun.zfile.exception;
import im.zhaojun.common.model.dto.ResultBean;
import im.zhaojun.zfile.model.dto.ResultBean;
import org.apache.catalina.connector.ClientAbortException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,9 +46,9 @@ public class GlobleExceptionHandler {
* 不存在的文件异常
*/
@ExceptionHandler({NotExistFileException.class})
@ResponseStatus(HttpStatus.NOT_FOUND)
public String notExistFile(Exception ex) {
return "error/404";
@ResponseBody
public ResultBean notExistFile(Exception ex) {
return ResultBean.error("文件不存在");
}
/**
@@ -63,6 +63,30 @@ public class GlobleExceptionHandler {
// }
}
/**
* 文件预览异常
*/
@ExceptionHandler({PreviewException.class})
@ResponseBody
@ResponseStatus
public ResultBean previewException(PreviewException ex) {
return ResultBean.error(ex.getMessage());
}
/**
* 初始化异常
*/
@ExceptionHandler({InitializeException.class})
@ResponseBody
@ResponseStatus
public ResultBean initializeException(InitializeException ex) {
return ResultBean.error(ex.getMessage());
}
@ExceptionHandler
@ResponseBody
@ResponseStatus(code= HttpStatus.INTERNAL_SERVER_ERROR)

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.exception;
package im.zhaojun.zfile.exception;
/**
* 对象存储初始化异常

View File

@@ -1,8 +1,7 @@
package im.zhaojun.common.exception;
package im.zhaojun.zfile.exception;
/**
* @author zhaojun
* @date 2020/1/20 22:15
*/
public class NotExistFileException extends RuntimeException {

View File

@@ -0,0 +1,28 @@
package im.zhaojun.zfile.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,4 +1,4 @@
package im.zhaojun.common.exception;
package im.zhaojun.zfile.exception;
/**
* @author zhaojun

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.exception;
package im.zhaojun.zfile.exception;
/**
* 存储策略未初始化异常

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.exception;
package im.zhaojun.zfile.exception;
/**
* 未知的存储类型异常

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.config;
package im.zhaojun.zfile.filter;
import org.springframework.http.HttpHeaders;
import org.springframework.web.cors.CorsUtils;
@@ -23,7 +23,6 @@ public class CorsFilter extends GenericFilterBean {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// Set customized header
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, httpServletRequest.getHeader(HttpHeaders.ORIGIN));
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Origin, X-Requested-With, Content-Type, Accept");
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS");
@@ -34,4 +33,5 @@ public class CorsFilter extends GenericFilterBean {
chain.doFilter(httpServletRequest, httpServletResponse);
}
}
}

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.security;
package im.zhaojun.zfile.filter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

View File

@@ -1,12 +1,11 @@
package im.zhaojun.common.model.constant;
package im.zhaojun.zfile.model.constant;
/**
* @author zhaojun
* @date 2019/12/28 18:47
*/
public class StorageConfigConstant {
public static final String BUCKET_NAME_KEY = "bucket-name";
public static final String BUCKET_NAME_KEY = "bucketName";
public static final String SECRET_ID_KEY = "secretId";
@@ -16,7 +15,7 @@ public class StorageConfigConstant {
public static final String ENDPOINT_KEY = "endPoint";
public static final String BASE_PATH = "base-path";
public static final String BASE_PATH = "basePath";
public static final String DOMAIN_KEY = "domain";
@@ -34,4 +33,8 @@ public class StorageConfigConstant {
public static final String REFRESH_TOKEN_KEY = "refreshToken";
public static final String PATH_STYLE = "pathStyle";
public static final String IS_PRIVATE = "isPrivate";
}

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.model.constant;
package im.zhaojun.zfile.model.constant;
/**
* @author zhaojun
@@ -7,8 +7,6 @@ public class SystemConfigConstant {
public static final String SITE_NAME = "siteName";
public static final String INFO_ENABLE = "infoEnable";
public static final String SEARCH_ENABLE = "searchEnable";
public static final String SEARCH_IGNORE_CASE = "searchIgnoreCase";

View File

@@ -0,0 +1,63 @@
package im.zhaojun.zfile.model.constant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
/**
* @author zhaojun
*/
@Configuration
public class ZFileConstant {
public final static String USER_HOME = System.getProperty("user.home");
public static final String AUDIO_TMP_PATH = "/.zfile/tmp/audio/";
public static final Character PATH_SEPARATOR_CHAR = '/';
public static final String PATH_SEPARATOR = "/";
/**
* 页面文档文件
*/
public static String README_FILE_NAME = "readme.md";
/**
* 密码文件
*/
public static String PASSWORD_FILE_NAME = "password.txt";
/**
* 最大支持文件大小为 ? 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 setHeaderFileName(@Value("${zfile.constant.readme}") String headerFileName) {
ZFileConstant.README_FILE_NAME = headerFileName;
}
@Autowired(required = false)
public void setPasswordFileName(@Value("${zfile.constant.password}") String passwordFileName) {
ZFileConstant.PASSWORD_FILE_NAME = passwordFileName;
}
@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

@@ -0,0 +1,27 @@
package im.zhaojun.zfile.model.dto;
import lombok.Data;
/**
* @author zhaojun
*/
@Data
public class AudioInfoDTO {
private String title;
private String artist;
private String cover;
private String src;
public static AudioInfoDTO buildDefaultAudioInfoDTO() {
AudioInfoDTO audioInfoDTO = new AudioInfoDTO();
audioInfoDTO.setTitle("未知歌曲");
audioInfoDTO.setArtist("未知");
audioInfoDTO.setCover("http://c.jun6.net/audio.png");
return audioInfoDTO;
}
}

View File

@@ -1,12 +1,12 @@
package im.zhaojun.common.model.dto;
package im.zhaojun.zfile.model.dto;
import lombok.Data;
import java.util.Date;
import java.util.Set;
/**
* @author zhaojun
* @date 2020/1/3 12:39
*/
@Data
public class CacheConfigDTO {
@@ -15,4 +15,5 @@ public class CacheConfigDTO {
private Set<String> cacheKeys;
private Integer cacheDirectoryCount;
private Integer cacheFileCount;
private Date lastCacheAutoRefreshDate;
}

View File

@@ -0,0 +1,33 @@
package im.zhaojun.zfile.model.dto;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.model.enums.StorageTypeEnumJsonDeSerializerConvert;
import lombok.Data;
/**
* @author zhaojun
*/
@Data
public class DriveConfigDTO {
private Integer id;
private String name;
@JsonDeserialize(using = StorageTypeEnumJsonDeSerializerConvert.class)
private StorageTypeEnum type;
private boolean enableCache;
private boolean autoRefreshCache;
private boolean searchEnable;
private boolean searchIgnoreCase;
private boolean searchContainEncryptedFile;
private StorageStrategyConfig storageStrategyConfig;
}

View File

@@ -1,6 +1,6 @@
package im.zhaojun.common.model.dto;
package im.zhaojun.zfile.model.dto;
import im.zhaojun.common.model.enums.FileTypeEnum;
import im.zhaojun.zfile.model.enums.FileTypeEnum;
import java.io.Serializable;
import java.util.Date;

View File

@@ -1,6 +1,6 @@
package im.zhaojun.common.model.dto;
package im.zhaojun.zfile.model.dto;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import java.util.Map;

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.model.dto;
package im.zhaojun.zfile.model.dto;
import java.io.Serializable;
@@ -15,6 +15,8 @@ public class ResultBean implements Serializable {
public static final int REQUIRED_PASSWORD = -2;
public static final int INVALID_PASSWORD = -3;
private String msg = "操作成功";
private int code = SUCCESS;
@@ -43,6 +45,10 @@ public class ResultBean implements Serializable {
return success("操作成功", data);
}
public static ResultBean successPage(Object data, Long total) {
return success("操作成功", data);
}
public static ResultBean success(Object data) {
return success("操作成功", data);
}

View File

@@ -0,0 +1,23 @@
// package im.zhaojun.zfile.model.dto;
//
// import com.fasterxml.jackson.annotation.JsonProperty;
// import lombok.Data;
// import lombok.ToString;
//
// import java.io.Serializable;
//
// /**
// * @author zhaojun
// */
// @Data
// @ToString
// public class SiteConfigDTO implements Serializable {
//
// private static final long serialVersionUID = 8811196207046121740L;
//
// private String readme;
//
// @JsonProperty("viewConfig")
// private SystemConfigDTO systemConfigDTO;
//
// }

View File

@@ -0,0 +1,43 @@
package im.zhaojun.zfile.model.dto;
import lombok.Data;
/**
* @author zhaojun
*/
@Data
public class StorageStrategyConfig {
private String endPoint;
private String pathStyle;
private Boolean isPrivate;
private String accessKey;
private String secretKey;
private String bucketName;
private String host;
private String port;
private String accessToken;
private String refreshToken;
private String secretId;
private String filePath;
private String username;
private String password;
private String domain;
private String basePath;
}

View File

@@ -1,5 +1,6 @@
package im.zhaojun.common.model.dto;
package im.zhaojun.zfile.model.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -16,6 +17,7 @@ public class StorageStrategyDTO {
private String description;
private boolean available;
@JsonProperty(defaultValue = "false")
private Boolean available;
}

View File

@@ -1,14 +1,15 @@
package im.zhaojun.common.model.dto;
package im.zhaojun.zfile.model.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.model.enums.StorageTypeEnumSerializerConvert;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.model.enums.StorageTypeEnumSerializerConvert;
import lombok.Data;
import lombok.ToString;
/**
* 系统设置传输类
*
* @author zhaojun
*/
@ToString
@@ -20,21 +21,29 @@ public class SystemConfigDTO {
private String siteName;
private Boolean infoEnable;
private Boolean searchEnable;
private Boolean searchIgnoreCase;
private String username;
@JsonSerialize(using = StorageTypeEnumSerializerConvert.class)
private StorageTypeEnum storageStrategy;
private String username;
@JsonIgnore
private String password;
private String domain;
private Boolean enableCache;
private String customJs;
private String customCss;
private String tableSize;
private Boolean showOperator;
private Boolean showDocument;
private String announcement;
private Boolean showAnnouncement;
private String layout;
}

View File

@@ -0,0 +1,47 @@
package im.zhaojun.zfile.model.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.ToString;
/**
* 系统设置传输类
* @author zhaojun
*/
@ToString
@Data
public class SystemFrontConfigDTO {
@JsonIgnore
private Integer id;
private String siteName;
private Boolean searchEnable;
// @JsonSerialize(using = StorageTypeEnumSerializerConvert.class)
// private StorageTypeEnum storageStrategy;
private String username;
private String domain;
private String customJs;
private String customCss;
private String tableSize;
private Boolean showOperator;
private Boolean showDocument;
private String announcement;
private Boolean showAnnouncement;
private String layout;
private String readme;
}

View File

@@ -0,0 +1,38 @@
package im.zhaojun.zfile.model.entity;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
* 驱动器
*
* @author zhaojun
*/
@Entity(name = "DRIVER_CONFIG")
@Data
public class DriveConfig {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private Boolean enableCache;
private Boolean autoRefreshCache;
private StorageTypeEnum type;
private Boolean searchEnable;
private Boolean searchIgnoreCase;
private Boolean searchContainEncryptedFile;
}

View File

@@ -1,6 +1,6 @@
package im.zhaojun.common.model;
package im.zhaojun.zfile.model.entity;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import lombok.Data;
import javax.persistence.Column;
@@ -27,9 +27,16 @@ public class StorageConfig {
private String title;
@Column(length = 2048)
@Column(length = 4000)
private String value;
private Integer driveId;
public StorageConfig(String key, String title) {
this.key = key;
this.title = title;
}
public Integer getId() {
return id;
}

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.model;
package im.zhaojun.zfile.model.entity;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.model.enums;
package im.zhaojun.zfile.model.enums;
/**
* @author zhaojun

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.model.enums;
package im.zhaojun.zfile.model.enums;
import com.fasterxml.jackson.annotation.JsonFormat;
@@ -14,15 +14,18 @@ public enum StorageTypeEnum {
/**
* 当前系统支持的所有存储策略
*/
ALIYUN("aliyun", "阿里云 OSS"),
FTP("ftp", "FTP"),
HUAWEI("huawei", "华为云 OBS"),
LOCAL("local", "本地存储"),
MINIO("minio", "MINIO"),
ONE_DRIVE("onedrive", "OneDrive"),
QINIU("qiniu", "七牛云 KODO"),
ALIYUN("aliyun", "阿里云 OSS"),
TENCENT("tencent", "腾讯云 COS"),
UPYUN("upyun", "又拍云 USS");
UPYUN("upyun", "又拍云 USS"),
FTP("ftp", "FTP"),
UFILE("ufile", "UFile"),
HUAWEI("huawei", "华为云 OBS"),
MINIO("minio", "MINIO"),
S3("s3", "S3通用协议"),
ONE_DRIVE("onedrive", "OneDrive"),
ONE_DRIVE_CHINA("onedrive-china", "OneDrive 世纪互联"),
QINIU("qiniu", "七牛云 KODO");
private String key;
private String description;

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.model.enums;
package im.zhaojun.zfile.model.enums;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.model.enums;
package im.zhaojun.zfile.model.enums;
import org.springframework.core.convert.converter.Converter;
import org.springframework.lang.NonNull;
@@ -12,4 +12,5 @@ public class StorageTypeEnumDeSerializerConvert implements Converter<String, Sto
public StorageTypeEnum convert(@NonNull String s) {
return StorageTypeEnum.getEnum(s);
}
}
}

View File

@@ -0,0 +1,18 @@
package im.zhaojun.zfile.model.enums;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
/**
* @author zhaojun
*/
public class StorageTypeEnumJsonDeSerializerConvert extends JsonDeserializer<StorageTypeEnum> {
@Override
public StorageTypeEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
return StorageTypeEnum.getEnum(jsonParser.getText());
}
}

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.model.enums;
package im.zhaojun.zfile.model.enums;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;

View File

@@ -0,0 +1,20 @@
package im.zhaojun.zfile.model.support;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
/**
* @author zhaojun
*/
@Data
@AllArgsConstructor
public class FilePageModel {
private int totalPage;
private List<FileItemDTO> fileList;
}

View File

@@ -0,0 +1,39 @@
package im.zhaojun.zfile.model.support;
import lombok.Data;
/**
* @author zhaojun
*/
@Data
public class Jvm {
/**
* 当前 JVM 占用的内存总数 (M)
*/
private double total;
/**
* JVM 最大可用内存总数 (M)
*/
private double max;
/**
* JVM 空闲内存 (M)
*/
private double free;
/**
* JDK 版本
*/
private String version;
public Jvm() {
Runtime runtime = Runtime.getRuntime();
this.total = runtime.totalMemory();
this.free = runtime.freeMemory();
this.max = runtime.maxMemory();
this.version = System.getProperty("java.version");
}
}

View File

@@ -0,0 +1,41 @@
package im.zhaojun.zfile.model.support;
import com.sun.management.OperatingSystemMXBean;
import lombok.Data;
import java.lang.management.ManagementFactory;
/**
* @author zhaojun
*/
@Data
public class Mem {
/**
* 内存总量
*/
private double total;
/**
* 已用内存
*/
private double used;
/**
* 剩余内存
*/
private double free;
public Mem() {
OperatingSystemMXBean osb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
// 总的物理内存+虚拟内存
long totalVirtualMemory = osb.getTotalSwapSpaceSize();
// 剩余的物理内存
long freePhysicalMemorySize = osb.getFreePhysicalMemorySize();
this.total = totalVirtualMemory;
this.free = freePhysicalMemorySize;
this.used = totalVirtualMemory - freePhysicalMemorySize;
}
}

View File

@@ -1,11 +1,10 @@
package im.zhaojun.onedrive.service;
package im.zhaojun.zfile.model.support;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
/**
* @author zhaojun
* @date 2020/1/18 17:28
*/
@Data
public class OneDriveToken {

View File

@@ -0,0 +1,58 @@
package im.zhaojun.zfile.model.support;
import cn.hutool.core.date.BetweenFormater;
import cn.hutool.core.date.DateUtil;
import lombok.Data;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.IOException;
import java.lang.management.ManagementFactory;
/**
* @author zhaojun
*/
@Data
public class Sys {
/**
* 项目路径
*/
private String projectDir;
/**
* 操作系统
*/
private String osName;
/**
* 系统架构
*/
private String osArch;
/**
* 系统版本
*/
private String osVersion;
/**
* 启动时间
*/
private String upTime;
public Sys() {
this.osName = System.getProperty("os.name");
this.osArch = System.getProperty("os.arch");
this.osVersion = System.getProperty("os.version");
Resource resource = new ClassPathResource("");
try {
this.projectDir = resource.getFile().getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
}
long uptime = ManagementFactory.getRuntimeMXBean().getUptime();
this.upTime = DateUtil.formatBetween(uptime, BetweenFormater.Level.SECOND);
}
}

View File

@@ -0,0 +1,34 @@
package im.zhaojun.zfile.model.support;
import lombok.Data;
import java.io.Serializable;
/**
* @author zhaojun
*/
@Data
public class SystemMonitorInfo implements Serializable {
/**
* 服务器基本信息
*/
private Sys sys;
/**
* JVM 信息
*/
private Jvm jvm;
/**
* 系统内存
*/
private Mem mem;
public SystemMonitorInfo() {
this.jvm = new Jvm();
this.sys = new Sys();
this.mem = new Mem();
}
}

View File

@@ -0,0 +1,26 @@
package im.zhaojun.zfile.repository;
import im.zhaojun.zfile.model.entity.DriveConfig;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author zhaojun
*/
@Repository
public interface DriverConfigRepository extends JpaRepository<DriveConfig, Integer> {
/**
* 根据存储策略类型获取所有驱动器
*
* @param type
* 存储类型
*
* @return 指定存储类型的存储器
*/
List<DriveConfig> findByType(StorageTypeEnum type);
}

View File

@@ -0,0 +1,71 @@
package im.zhaojun.zfile.repository;
import im.zhaojun.zfile.model.entity.StorageConfig;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author zhaojun
*/
@Repository
public interface StorageConfigRepository extends JpaRepository<StorageConfig, Integer> {
/**
* 根据存储类型找对应的配置信息
*
* @param type
* 存储类型
*
* @return 此类型所有的配置信息
*/
List<StorageConfig> findByTypeOrderById(StorageTypeEnum type);
/**
* 根据存储类型找对应的配置信息
*
* @param driveId
* 驱动器 ID
*
* @return 此驱动器所有的配置信息
*/
List<StorageConfig> findByDriveIdOrderById(Integer driveId);
/**
* 根据驱动器找到对应的配置信息
*
* @param driveId
* 驱动器 ID
*
* @return 此驱动器所有的配置信息
*/
List<StorageConfig> findByDriveId(Integer driveId);
/**
* 删除指定驱动器对应的配置信息
*
* @param driveId
* 驱动器 ID
*/
void deleteByDriveId(Integer driveId);
/**
* 查找某个驱动器的某个 KEY 的值
*
* @param driveId
* 驱动器
*
* @param key
* KEY 值
*
* @return KEY 对应的对象
*/
StorageConfig findByDriveIdAndKey(Integer driveId, String key);
}

View File

@@ -1,6 +1,6 @@
package im.zhaojun.common.repository;
package im.zhaojun.zfile.repository;
import im.zhaojun.common.model.SystemConfig;
import im.zhaojun.zfile.model.entity.SystemConfig;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@@ -12,8 +12,12 @@ public interface SystemConfigRepository extends JpaRepository<SystemConfig, Inte
/**
* 查找系统设置中, 某个设置项对应的值
* @param key 设置项
* @return 设置值
*
* @param key
* 设置项
*
* @return 设置值
*/
SystemConfig findByKey(String key);
}
}

View File

@@ -0,0 +1,72 @@
package im.zhaojun.zfile.schedule;
import im.zhaojun.zfile.model.entity.DriveConfig;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.service.DriveConfigService;
import im.zhaojun.zfile.service.base.AbstractOneDriveServiceBase;
import im.zhaojun.zfile.service.impl.OneDriveChinaServiceImpl;
import im.zhaojun.zfile.service.impl.OneDriveServiceImpl;
import im.zhaojun.zfile.context.DriveContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
/**
* 计划任务工具类
* @author zhaojun
*/
@Configuration
@EnableScheduling
@Slf4j
public class GlobalScheduleTask {
@Resource
private OneDriveServiceImpl oneDriveServiceImpl;
@Resource
private OneDriveChinaServiceImpl oneDriveChinaServiceImpl;
@Resource
private DriveConfigService driveConfigService;
@Resource
private DriveContext driveContext;
/**
* 项目启动 30 秒后, 每 15 分钟执行一次刷新 OneDrive Token 的定时任务.
*/
@Scheduled(fixedRate = 1000 * 60 * 10, initialDelay = 1000 * 30)
public void autoRefreshOneDriveToken() {
try {
log.debug("尝试调用 OneDrive 自动刷新 AccessToken 定时任务");
List<DriveConfig> driveConfigList = driveConfigService.findByType(StorageTypeEnum.ONE_DRIVE);
driveConfigList.addAll(driveConfigService.findByType(StorageTypeEnum.ONE_DRIVE_CHINA));
driveConfigList.forEach(driveConfig -> {
StorageTypeEnum storageType = driveConfig.getType();
String name = driveConfig.getName();
try {
AbstractOneDriveServiceBase driveService = (AbstractOneDriveServiceBase) driveContext.getDriveService(driveConfig.getId());
driveService.refreshOneDriveToken();
log.info("刷新驱动器 {}, {} key 时间: {}", name, storageType.getDescription(), LocalDateTime.now());
} catch (Exception e) {
log.debug("刷新驱动器 " + name + " Token 失败.", e);
}
});
} catch (Throwable e) {
log.debug("尝试调用 OneDrive 自动刷新 AccessToken 定时任务出现未知异常", e);
}
}
}

View File

@@ -1,4 +1,4 @@
package im.zhaojun.common.security;
package im.zhaojun.zfile.security;
import cn.hutool.crypto.SecureUtil;
import org.springframework.security.crypto.password.PasswordEncoder;

View File

@@ -1,7 +1,7 @@
package im.zhaojun.common.security;
package im.zhaojun.zfile.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import im.zhaojun.common.model.dto.ResultBean;
import im.zhaojun.zfile.model.dto.ResultBean;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.BadCredentialsException;
@@ -21,6 +21,7 @@ import java.io.PrintWriter;
/**
* 自定义 Security 配置类
*
* @author zhaojun
*/
@EnableWebSecurity
@@ -34,7 +35,7 @@ public class MySecurityConfig extends WebSecurityConfigurerAdapter {
http
// .authenticationProvider(authenticationProvider())
.exceptionHandling()
//未登录时进行json格式的提示很喜欢这种写法不用单独写一个又一个的类
// 未登录时进行 json 格式的提示.
.authenticationEntryPoint((request, response, authException) -> {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
@@ -48,8 +49,8 @@ public class MySecurityConfig extends WebSecurityConfigurerAdapter {
.antMatchers("/").permitAll()
.antMatchers("/admin/**").authenticated()
.and()
.formLogin() //使用自带的登录
//登录失败返回json
.formLogin() // 使用自带的登录
// 登录失败返回json
.failureHandler((request, response, ex) -> {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
@@ -64,7 +65,7 @@ public class MySecurityConfig extends WebSecurityConfigurerAdapter {
out.flush();
out.close();
})
//登录成功返回json
// 登录成功返回json
.successHandler((request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
@@ -74,7 +75,7 @@ public class MySecurityConfig extends WebSecurityConfigurerAdapter {
})
.and()
.exceptionHandling()
//没有权限返回json
// 没有权限返回json
.accessDeniedHandler((request, response, ex) -> {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
@@ -85,7 +86,7 @@ public class MySecurityConfig extends WebSecurityConfigurerAdapter {
})
.and()
.logout()
//退出成功返回json
// 退出成功返回 json
.logoutSuccessHandler((request, response, authentication) -> {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
@@ -98,6 +99,7 @@ public class MySecurityConfig extends WebSecurityConfigurerAdapter {
http.cors();
http.csrf().disable();
http.headers().frameOptions().sameOrigin();
}
@Bean
@@ -119,7 +121,7 @@ public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) {
//对于在header里面增加token等类似情况放行所有OPTIONS请求
// 对于在 header 里面增加 token 等类似情况放行所有 OPTIONS 请求
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
web.httpFirewall(allowUrlEncodedSlashHttpFirewall());
}

View File

@@ -1,7 +1,7 @@
package im.zhaojun.common.security;
package im.zhaojun.zfile.security;
import im.zhaojun.common.model.dto.SystemConfigDTO;
import im.zhaojun.common.service.SystemConfigService;
import im.zhaojun.zfile.model.dto.SystemConfigDTO;
import im.zhaojun.zfile.service.SystemConfigService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -9,6 +9,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.Objects;
/**
* @author zhaojun
@@ -24,6 +25,10 @@ public class MyUserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
if (!Objects.equals(systemConfig.getUsername(), username)) {
throw new UsernameNotFoundException("用户名不存在");
}
return new User(systemConfig.getUsername(), systemConfig.getPassword(), Collections.emptyList());
}
}

View File

@@ -0,0 +1,224 @@
package im.zhaojun.zfile.service;
import im.zhaojun.zfile.context.StorageTypeContext;
import im.zhaojun.zfile.exception.InitializeException;
import im.zhaojun.zfile.model.constant.StorageConfigConstant;
import im.zhaojun.zfile.model.dto.DriveConfigDTO;
import im.zhaojun.zfile.model.dto.StorageStrategyConfig;
import im.zhaojun.zfile.model.entity.DriveConfig;
import im.zhaojun.zfile.model.entity.StorageConfig;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.repository.DriverConfigRepository;
import im.zhaojun.zfile.repository.StorageConfigRepository;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import im.zhaojun.zfile.context.DriveContext;
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.List;
import java.util.Objects;
/**
* 驱动器 Service 类
* @author zhaojun
*/
@Slf4j
@Service
public class DriveConfigService {
@Resource
private DriverConfigRepository driverConfigRepository;
@Resource
private StorageConfigRepository storageConfigRepository;
@Resource
private DriveContext driveContext;
public static final Class<StorageStrategyConfig> STORAGE_STRATEGY_CONFIG_CLASS = StorageStrategyConfig.class;
/**
* 获取所有驱动器列表
*
* @return 驱动器列表
*/
public List<DriveConfig> list() {
return driverConfigRepository.findAll();
}
/**
* 获取指定驱动器设置
*
* @param id
* 驱动器 ID
*
* @return 驱动器设置
*/
public DriveConfig findById(Integer id) {
return driverConfigRepository.getOne(id);
}
/**
* 获取指定驱动器 DTO 对象, 此对象包含详细的参数设置.
*
* @param id
* 驱动器 ID
*
* @return 驱动器 DTO
*/
public DriveConfigDTO findDriveConfigDTOById(Integer id) {
DriveConfig driveConfig = driverConfigRepository.getOne(id);
DriveConfigDTO driveConfigDTO = new DriveConfigDTO();
List<StorageConfig> storageConfigList = storageConfigRepository.findByDriveId(driveConfig.getId());
BeanUtils.copyProperties(driveConfig, driveConfigDTO);
StorageStrategyConfig storageStrategyConfig = new StorageStrategyConfig();
for (StorageConfig storageConfig : storageConfigList) {
String key = storageConfig.getKey();
String value = storageConfig.getValue();
Field declaredField;
try {
declaredField = STORAGE_STRATEGY_CONFIG_CLASS.getDeclaredField(key);
declaredField.setAccessible(true);
if (Objects.equals(StorageConfigConstant.IS_PRIVATE, key)) {
declaredField.set(storageStrategyConfig, Boolean.valueOf(value));
} else {
declaredField.set(storageStrategyConfig, value);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
if (log.isDebugEnabled()) {
log.debug("通过反射, 将字段 {" + key + "}注入 DriveConfigDTO 时出现异常:", e);
}
}
}
driveConfigDTO.setStorageStrategyConfig(storageStrategyConfig);
return driveConfigDTO;
}
/**
* 获取指定驱动器的存储策略.
*
* @param id
* 驱动器 ID
*
* @return 驱动器对应的存储策略.
*/
public StorageTypeEnum findStorageTypeById(Integer id) {
// return findById(id).getType();
return driverConfigRepository.findById(id).get().getType();
}
/**
* 保存驱动器基本信息及其对应的参数设置
*
* @param driveConfigDTO 驱动器 DTO 对象
*/
@Transactional(rollbackFor = Exception.class)
public void save(DriveConfigDTO driveConfigDTO) {
// 判断是新增还是修改
boolean updateFlag = driveConfigDTO.getId() != null;
// 保存基本信息
DriveConfig driveConfig = new DriveConfig();
StorageTypeEnum storageType = driveConfigDTO.getType();
BeanUtils.copyProperties(driveConfigDTO, driveConfig);
driverConfigRepository.save(driveConfig);
// 保存存储策略设置.
StorageStrategyConfig storageStrategyConfig = driveConfigDTO.getStorageStrategyConfig();
AbstractBaseFileService storageTypeService = StorageTypeContext.getStorageTypeService(storageType);
List<StorageConfig> storageConfigList;
if (updateFlag) {
storageConfigList = storageConfigRepository.findByDriveId(driveConfigDTO.getId());
} else {
storageConfigList = storageTypeService.storageStrategyConfigList();
}
for (StorageConfig storageConfig : storageConfigList) {
String key = storageConfig.getKey();
try {
Field field = STORAGE_STRATEGY_CONFIG_CLASS.getDeclaredField(key);
field.setAccessible(true);
Object o = field.get(storageStrategyConfig);
String value = o == null ? null : o.toString();
storageConfig.setValue(value);
storageConfig.setType(storageType);
storageConfig.setDriveId(driveConfig.getId());
} catch (IllegalAccessException | NoSuchFieldException e) {
if (log.isDebugEnabled()) {
log.debug("通过反射, 从 StorageStrategyConfig 中获取字段 {" + key + "} 时出现异常:", e);
}
}
}
storageConfigRepository.saveAll(storageConfigList);
driveContext.initDrive(driveConfig.getId());
AbstractBaseFileService driveService = driveContext.getDriveService(driveConfig.getId());
if (driveService.getIsUnInitialized()) {
throw new InitializeException("初始化异常, 请检查配置是否正确.");
}
}
/**
* 删除指定驱动器设置, 会级联删除其参数设置
*
* @param id
* 驱动器 ID
*/
@Transactional(rollbackFor = Exception.class)
public void deleteById(Integer id) {
driverConfigRepository.deleteById(id);
storageConfigRepository.deleteByDriveId(id);
driveContext.destroyDrive(id);
}
/**
* 根据存储策略类型获取所有驱动器
*
* @param type
* 存储类型
*
* @return 指定存储类型的存储器
*/
public List<DriveConfig> findByType(StorageTypeEnum type) {
return driverConfigRepository.findByType(type);
}
/**
* 更新指定驱动器的缓存启用状态
*
* @param driveId
* 驱动器 ID
*
* @param cacheEnable
* 是否启用缓存
*/
public void updateCacheStatus(Integer driveId, Boolean cacheEnable) {
DriveConfig driveConfig = findById(driveId);
if (driveConfig != null) {
driveConfig.setEnableCache(cacheEnable);
driverConfigRepository.save(driveConfig);
}
}
}

View File

@@ -1,8 +1,8 @@
package im.zhaojun.common.service;
package im.zhaojun.zfile.service;
import im.zhaojun.common.model.StorageConfig;
import im.zhaojun.common.model.enums.StorageTypeEnum;
import im.zhaojun.common.repository.StorageConfigRepository;
import im.zhaojun.zfile.model.entity.StorageConfig;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.repository.StorageConfigRepository;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -19,13 +19,19 @@ public class StorageConfigService {
@Resource
private StorageConfigRepository storageConfigRepository;
public List<StorageConfig> selectStorageConfigByType(StorageTypeEnum storageTypeEnum) {
return storageConfigRepository.findByTypeOrderById(storageTypeEnum);
}
public StorageConfig selectByTypeAndKey(StorageTypeEnum storageType, String key) {
return storageConfigRepository.findByTypeAndKey(storageType, key);
public List<StorageConfig> selectStorageConfigByDriveId(Integer driveId) {
return storageConfigRepository.findByDriveIdOrderById(driveId);
}
public StorageConfig findByDriveIdAndKey(Integer driveId, String key) {
return storageConfigRepository.findByDriveIdAndKey(driveId, key);
}
@@ -38,8 +44,17 @@ public class StorageConfigService {
}
public Map<String, StorageConfig> selectStorageConfigMapByDriveId(Integer driveId) {
Map<String, StorageConfig> map = new HashMap<>(24);
for (StorageConfig storageConfig : selectStorageConfigByDriveId(driveId)) {
map.put(storageConfig.getKey(), storageConfig);
}
return map;
}
public void updateStorageConfig(List<StorageConfig> storageConfigList) {
storageConfigRepository.saveAll(storageConfigList);
}
}
}

View File

@@ -0,0 +1,166 @@
package im.zhaojun.zfile.service;
import cn.hutool.core.convert.Convert;
import cn.hutool.crypto.SecureUtil;
import im.zhaojun.zfile.cache.ZFileCache;
import im.zhaojun.zfile.model.constant.SystemConfigConstant;
import im.zhaojun.zfile.model.dto.SystemConfigDTO;
import im.zhaojun.zfile.model.dto.SystemFrontConfigDTO;
import im.zhaojun.zfile.model.entity.DriveConfig;
import im.zhaojun.zfile.model.entity.SystemConfig;
import im.zhaojun.zfile.repository.SystemConfigRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
/**
* @author zhaojun
*/
@Slf4j
@Service
public class SystemConfigService {
@Resource
private ZFileCache zFileCache;
@Resource
private SystemConfigRepository systemConfigRepository;
@Resource
private DriveConfigService driveConfigService;
private Class<SystemConfigDTO> systemConfigClazz = SystemConfigDTO.class;
/**
* 获取系统设置, 如果缓存中有, 则去缓存取, 没有则查询数据库并写入到缓存中.
*
* @return 系统设置
*/
public SystemConfigDTO getSystemConfig() {
SystemConfigDTO cacheConfig = zFileCache.getConfig();
if (cacheConfig != null) {
return cacheConfig;
}
SystemConfigDTO systemConfigDTO = new SystemConfigDTO();
List<SystemConfig> systemConfigList = systemConfigRepository.findAll();
for (SystemConfig systemConfig : systemConfigList) {
String key = systemConfig.getKey();
try {
Field field = systemConfigClazz.getDeclaredField(key);
field.setAccessible(true);
String strVal = systemConfig.getValue();
Object convertVal = Convert.convert(field.getType(), strVal);
field.set(systemConfigDTO, convertVal);
} catch (NoSuchFieldException | IllegalAccessException e) {
if (log.isDebugEnabled()) {
log.debug("通过反射, 将字段 {" + key + "}注入 SystemConfigDTO 时出现异常:", e);
}
}
}
zFileCache.updateConfig(systemConfigDTO);
return systemConfigDTO;
}
/**
* 更新系统设置, 并清空缓存中的内容.
*
* @param systemConfigDTO
* 系统
*
*/
public void updateSystemConfig(SystemConfigDTO systemConfigDTO) {
List<SystemConfig> systemConfigList = new ArrayList<>();
Field[] fields = systemConfigClazz.getDeclaredFields();
for (Field field : fields) {
String key = field.getName();
SystemConfig systemConfig = systemConfigRepository.findByKey(key);
if (systemConfig != null) {
field.setAccessible(true);
Object val = null;
try {
val = field.get(systemConfigDTO);
} catch (IllegalAccessException e) {
if (log.isDebugEnabled()) {
log.debug("通过反射, 从 SystemConfigDTO 获取字段 {" + key + "} 时出现异常:", e);
}
}
if (val != null) {
systemConfig.setValue(val.toString());
systemConfigList.add(systemConfig);
}
}
}
zFileCache.removeConfig();
systemConfigRepository.saveAll(systemConfigList);
}
/**
* 根据驱动器 ID, 获取对于前台页面的系统设置.
*
* @param driveId
* 驱动器 ID
*
* @return 前台系统设置
*/
public SystemFrontConfigDTO getSystemFrontConfig(Integer driveId) {
SystemConfigDTO systemConfig = getSystemConfig();
SystemFrontConfigDTO systemFrontConfigDTO = new SystemFrontConfigDTO();
BeanUtils.copyProperties(systemConfig, systemFrontConfigDTO);
DriveConfig driveConfig = driveConfigService.findById(driveId);
systemFrontConfigDTO.setSearchEnable(driveConfig.getSearchEnable());
return systemFrontConfigDTO;
}
/**
* 更新后台账号密码
*
* @param username
* 用户名
*
* @param password
* 密码
*/
public void updateUsernameAndPwd(String username, String password) {
SystemConfig usernameConfig = systemConfigRepository.findByKey(SystemConfigConstant.USERNAME);
usernameConfig.setValue(username);
systemConfigRepository.save(usernameConfig);
String encryptionPassword = SecureUtil.md5(password);
SystemConfig systemConfig = systemConfigRepository.findByKey(SystemConfigConstant.PASSWORD);
systemConfig.setValue(encryptionPassword);
zFileCache.removeConfig();
systemConfigRepository.save(systemConfig);
}
/**
* 获取管理员名称
*
* @return 管理员名称
*/
public String getAdminUsername() {
SystemConfigDTO systemConfigDTO = getSystemConfig();
return systemConfigDTO.getUsername();
}
}

View File

@@ -0,0 +1,69 @@
// package im.zhaojun.zfile.service;
//
// import im.zhaojun.zfile.model.constant.ZFileConstant;
// import im.zhaojun.zfile.model.dto.FileItemDTO;
// import im.zhaojun.zfile.model.dto.SiteConfigDTO;
// import im.zhaojun.zfile.model.enums.StorageTypeEnum;
// import im.zhaojun.zfile.service.base.AbstractBaseFileService;
// import im.zhaojun.zfile.context.DriveContext;
// import im.zhaojun.zfile.util.HttpUtil;
// import im.zhaojun.zfile.util.StringUtils;
// import lombok.extern.slf4j.Slf4j;
// import org.springframework.stereotype.Service;
// import org.springframework.web.client.HttpClientErrorException;
//
// import javax.annotation.Resource;
// import java.util.ArrayList;
// import java.util.List;
// import java.util.Objects;
//
// /**
// * @author zhaojun
// */
// @Slf4j
// @Service
// public class SystemService {
//
// @Resource
// private DriveContext driveContext;
//
// /**
// * 构建指定路径下标题, 页面文档信息
// * @param path 路径
// */
// public SiteConfigDTO getConfig(Integer driveId, String path) throws Exception {
//
// SiteConfigDTO siteConfigDTO = new SiteConfigDTO();
//
// AbstractBaseFileService fileService = driveContext.getDriveService(driveId);
//
// List<FileItemDTO> fileItemList;
//
// if (Objects.equals(fileService.getStorageTypeEnum(), StorageTypeEnum.FTP)) {
// fileItemList = new ArrayList<>();
// } else {
// fileItemList = fileService.fileList(path);
// }
//
// for (FileItemDTO fileItemDTO : fileItemList) {
// if (ZFileConstant.README_FILE_NAME.equalsIgnoreCase(fileItemDTO.getName())) {
// String textContent = null;
// try {
// textContent = HttpUtil.getTextContent(fileItemDTO.getUrl());
// } catch (HttpClientErrorException httpClientErrorException) {
// log.debug("尝试重新获取文档区缓存中链接后仍失败", httpClientErrorException);
// try {
// String fullPath = StringUtils.removeDuplicateSeparator(fileItemDTO.getPath() + "/" + fileItemDTO.getName());
// FileItemDTO fileItem = fileService.getFileItem(fullPath);
// textContent = HttpUtil.getTextContent(fileItem.getUrl());
// } catch (Exception e) {
// log.debug("尝试重新获取文档区链接后仍失败, 已置为空", e);
// }
// }
// siteConfigDTO.setReadme(textContent);
// }
// }
// return siteConfigDTO;
// }
//
// }

View File

@@ -0,0 +1,168 @@
package im.zhaojun.zfile.service.base;
import im.zhaojun.zfile.cache.ZFileCache;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.entity.StorageConfig;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.service.SystemConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Value;
import javax.annotation.Resource;
import java.util.List;
/**
* @author zhaojun
*/
@Slf4j
public abstract class AbstractBaseFileService implements BaseFileService {
/**
* 下载链接过期时间, 目前只在兼容 S3 协议的存储策略中使用到.
*/
@Value("${zfile.cache.timeout}")
protected Long timeout;
/**
* 是否初始化成功
*/
protected boolean isInitialized = false;
/**
* 基路径
*/
protected String basePath;
/**
* 驱动器 ID
*/
public Integer driveId;
@Resource
private SystemConfigService systemConfigService;
@Resource
private ZFileCache zFileCache;
/***
* 获取指定路径下的文件及文件夹, 默认缓存 60 分钟,每隔 30 分钟刷新一次.
*
* @param path
* 文件路径
*
* @return 文件及文件夹列表
*
* @throws Exception 获取文件列表中出现的异常
*/
@Override
public abstract List<FileItemDTO> fileList(String path) throws Exception;
/**
* 清理当前存储策略的缓存
*/
public void clearFileCache() {
zFileCache.clear(driveId);
}
/**
* 初始化方法, 启动时自动调用实现类的此方法进行初始化.
*/
public abstract void init(Integer driveId);
/**
* 测试是否连接成功, 会尝试取调用获取根路径的文件, 如果没有抛出异常, 则认为连接成功, 某些存储策略需要复写此方法.
*
* @return 连接结果
*/
protected boolean testConnection() {
boolean flag = true;
try {
fileList("/");
} catch (Exception e) {
log.debug(getStorageTypeEnum().getDescription() + " 初始化异常", e);
flag = false;
}
return flag;
}
/**
* 获取是否初始化成功
*
* @return 初始化成功与否
*/
public boolean getIsUnInitialized() {
return !isInitialized;
}
/**
* 获取是否初始化成功
*
* @return 初始化成功与否
*/
public boolean getIsInitialized() {
return isInitialized;
}
/**
* 获取当前实现类的存储策略类型
*
* @return 存储策略类型枚举对象
*/
public abstract StorageTypeEnum getStorageTypeEnum();
/**
* 获取初始化当前存储策略, 所需要的参数信息 (表单填写)
*
* @return 初始化所需的参数列表
*/
public abstract List<StorageConfig> storageStrategyConfigList();
/**
* 搜索文件
*
* @param name
* 文件名
*
* @return 包含该文件名的所有文件或文件夹
*/
public List<FileItemDTO> search(String name) {
return zFileCache.find(driveId, name);
}
/**
* 刷新指定 key 的缓存:
* 1. 清空此 key 的缓存.
* 2. 重新调用方法写入缓存.
*
* @param key
* 缓存 key (文件夹名称)
*/
public void refreshCache(String key) throws Exception {
zFileCache.remove(driveId, key);
BaseFileService currentFileService = (BaseFileService) AopContext.currentProxy();
currentFileService.fileList(key);
}
/**
* 获取单个文件信息
*
* @param path
* 文件路径
*
* @return 单个文件的内容.
*/
public abstract FileItemDTO getFileItem(String path);
}

View File

@@ -0,0 +1,247 @@
package im.zhaojun.zfile.service.base;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import im.zhaojun.zfile.model.constant.StorageConfigConstant;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.entity.StorageConfig;
import im.zhaojun.zfile.model.enums.FileTypeEnum;
import im.zhaojun.zfile.model.support.OneDriveToken;
import im.zhaojun.zfile.repository.StorageConfigRepository;
import im.zhaojun.zfile.service.StorageConfigService;
import im.zhaojun.zfile.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author Zhao Jun
*/
@Slf4j
public abstract class AbstractOneDriveServiceBase extends AbstractBaseFileService {
protected static final String DRIVER_INFO_URL = "https://{graphEndPoint}/v1.0/me/drives";
protected static final String DRIVER_ROOT_URL = "https://{graphEndPoint}/v1.0/me/drive/root/children";
protected static final String DRIVER_ITEMS_URL = "https://{graphEndPoint}/v1.0/me/drive/root:{path}:/children";
protected static final String DRIVER_ITEM_URL = "https://{graphEndPoint}/v1.0/me/drive/root:{path}";
protected static final String AUTHENTICATE_URL = "https://{authenticateEndPoint}/common/oauth2/v2.0/token";
private static final String ONE_DRIVE_FILE_FLAG = "file";
@Resource
@Lazy
private RestTemplate oneDriveRestTemplate;
@Resource
private StorageConfigRepository storageConfigRepository;
@Resource
private StorageConfigService storageConfigService;
/**
* 根据 RefreshToken 刷新 AccessToken, 返回刷新后的 Token.
*
* @return 刷新后的 Token
*/
public OneDriveToken getRefreshToken() {
StorageConfig refreshStorageConfig =
storageConfigRepository.findByDriveIdAndKey(driveId, StorageConfigConstant.REFRESH_TOKEN_KEY);
String param = "client_id=" + getClientId() +
"&redirect_uri=" + getRedirectUri() +
"&client_secret=" + getClientSecret() +
"&refresh_token=" + refreshStorageConfig.getValue() +
"&grant_type=refresh_token";
String fullAuthenticateUrl = AUTHENTICATE_URL.replace("{authenticateEndPoint}", getAuthenticateEndPoint());
HttpRequest post = HttpUtil.createPost(fullAuthenticateUrl);
post.body(param, "application/x-www-form-urlencoded");
HttpResponse response = post.execute();
return JSONObject.parseObject(response.body(), OneDriveToken.class);
}
/**
* OAuth2 协议中, 根据 code 换取 access_token 和 refresh_token.
*
* @param code
* 代码
*
* @return 获取的 Token 信息.
*/
public OneDriveToken getToken(String code) {
String param = "client_id=" + getClientId() +
"&redirect_uri=" + getRedirectUri() +
"&client_secret=" + getClientSecret() +
"&code=" + code +
"&scope=" + getScope() +
"&grant_type=authorization_code";
String fullAuthenticateUrl = AUTHENTICATE_URL.replace("{authenticateEndPoint}", getAuthenticateEndPoint());
HttpRequest post = HttpUtil.createPost(fullAuthenticateUrl);
post.body(param, "application/x-www-form-urlencoded");
HttpResponse response = post.execute();
return JSONObject.parseObject(response.body(), OneDriveToken.class);
}
@Override
public List<FileItemDTO> fileList(String path) {
path = StringUtils.removeFirstSeparator(path);
String fullPath = StringUtils.getFullPath(basePath, path);
List<FileItemDTO> result = new ArrayList<>();
String nextLink = null;
do {
String requestUrl;
if (nextLink != null) {
nextLink = nextLink.replace("+", "%2B");
requestUrl = URLUtil.decode(nextLink);
}else if (ZFileConstant.PATH_SEPARATOR.equalsIgnoreCase(fullPath) || "".equalsIgnoreCase(fullPath)) {
requestUrl = DRIVER_ROOT_URL;
} else {
requestUrl = DRIVER_ITEMS_URL;
}
fullPath = StringUtils.removeLastSeparator(fullPath);
JSONObject root;
HttpHeaders headers = new HttpHeaders();
headers.set("driveId", driveId.toString());
HttpEntity<Object> entity = new HttpEntity<>(headers);
try {
root = oneDriveRestTemplate.exchange(requestUrl, HttpMethod.GET, entity, JSONObject.class, getGraphEndPoint(), fullPath).getBody();
} catch (HttpClientErrorException e) {
log.debug("调用 OneDrive 时出现了网络异常: {} , 已尝试重新刷新 token 后再试.", e.getMessage());
refreshOneDriveToken();
root = oneDriveRestTemplate.exchange(requestUrl, HttpMethod.GET, entity, JSONObject.class, getGraphEndPoint(), fullPath).getBody();
}
if (root == null) {
return Collections.emptyList();
}
nextLink = root.getString("@odata.nextLink");
JSONArray fileList = root.getJSONArray("value");
for (int i = 0; i < fileList.size(); i++) {
FileItemDTO fileItemDTO = new FileItemDTO();
JSONObject fileItem = fileList.getJSONObject(i);
fileItemDTO.setName(fileItem.getString("name"));
fileItemDTO.setSize(fileItem.getLong("size"));
fileItemDTO.setTime(fileItem.getDate("lastModifiedDateTime"));
if (fileItem.containsKey("file")) {
fileItemDTO.setUrl(fileItem.getString("@microsoft.graph.downloadUrl"));
fileItemDTO.setType(FileTypeEnum.FILE);
} else {
fileItemDTO.setType(FileTypeEnum.FOLDER);
}
fileItemDTO.setPath(path);
result.add(fileItemDTO);
}
} while (nextLink != null);
return result;
}
@Override
public FileItemDTO getFileItem(String path) {
String fullPath = StringUtils.getFullPath(basePath, path);
String requestUrl;
HttpHeaders headers = new HttpHeaders();
headers.set("driveId", driveId.toString());
HttpEntity<Object> entity = new HttpEntity<>(headers);
JSONObject fileItem = oneDriveRestTemplate.exchange(DRIVER_ITEM_URL, HttpMethod.GET, entity, JSONObject.class, getGraphEndPoint(), fullPath).getBody();
if (fileItem == null) {
return null;
}
FileItemDTO fileItemDTO = new FileItemDTO();
fileItemDTO.setName(fileItem.getString("name"));
fileItemDTO.setSize(fileItem.getLong("size"));
fileItemDTO.setTime(fileItem.getDate("lastModifiedDateTime"));
if (fileItem.containsKey(ONE_DRIVE_FILE_FLAG)) {
fileItemDTO.setUrl(fileItem.getString("@microsoft.graph.downloadUrl"));
fileItemDTO.setType(FileTypeEnum.FILE);
} else {
fileItemDTO.setType(FileTypeEnum.FOLDER);
}
fileItemDTO.setPath(path);
return fileItemDTO;
}
public abstract String getGraphEndPoint();
public abstract String getAuthenticateEndPoint();
public abstract String getClientId();
public abstract String getRedirectUri();
public abstract String getClientSecret();
public abstract String getScope();
public void refreshOneDriveToken() {
OneDriveToken refreshToken = getRefreshToken();
if (refreshToken.getAccessToken() == null || refreshToken.getRefreshToken() == null) {
return;
}
StorageConfig accessTokenConfig =
storageConfigService.findByDriveIdAndKey(driveId, StorageConfigConstant.ACCESS_TOKEN_KEY);
StorageConfig refreshTokenConfig =
storageConfigService.findByDriveIdAndKey(driveId, StorageConfigConstant.REFRESH_TOKEN_KEY);
accessTokenConfig.setValue(refreshToken.getAccessToken());
refreshTokenConfig.setValue(refreshToken.getRefreshToken());
storageConfigService.updateStorageConfig(Arrays.asList(accessTokenConfig, refreshTokenConfig));
}
@Override
public List<StorageConfig> storageStrategyConfigList() {
return new ArrayList<StorageConfig>() {{
add(new StorageConfig("accessToken", "访问令牌"));
add(new StorageConfig("refreshToken", "刷新令牌"));
add(new StorageConfig("basePath", "基路径"));
}};
}
}

View File

@@ -1,14 +1,16 @@
package im.zhaojun.common.service;
package im.zhaojun.zfile.service.base;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.URLUtil;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ListObjectsRequest;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import im.zhaojun.common.model.dto.FileItemDTO;
import im.zhaojun.common.model.enums.FileTypeEnum;
import im.zhaojun.common.util.StringUtils;
import im.zhaojun.zfile.exception.NotExistFileException;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.enums.FileTypeEnum;
import im.zhaojun.zfile.service.StorageConfigService;
import im.zhaojun.zfile.util.StringUtils;
import javax.annotation.Resource;
import java.net.URL;
@@ -19,23 +21,22 @@ import java.util.Objects;
/**
* @author zhaojun
* @date 2019/12/26 22:26
*/
public abstract class AbstractS3FileService extends AbstractFileService {
public abstract class AbstractS3BaseFileService extends AbstractBaseFileService {
@Resource
protected StorageConfigService storageConfigService;
protected String path;
protected String basePath;
protected String bucketName;
protected String domain;
protected AmazonS3 s3Client;
protected boolean isPrivate;
@Override
public List<FileItemDTO> fileList(String path) {
this.path = path;
@@ -55,7 +56,7 @@ public abstract class AbstractS3FileService extends AbstractFileService {
*/
public List<FileItemDTO> s3FileList(String path) {
path = StringUtils.removeFirstSeparator(path);
String fullPath = StringUtils.removeFirstSeparator(getFullPath());
String fullPath = StringUtils.removeFirstSeparator(StringUtils.getFullPath(basePath, path));
List<FileItemDTO> fileItemList = new ArrayList<>();
ObjectListing objectListing = s3Client.listObjects(new ListObjectsRequest(bucketName, fullPath, "", "/", 1000));
@@ -92,8 +93,14 @@ public abstract class AbstractS3FileService extends AbstractFileService {
* @return S3 对象访问地址
*/
public String s3ObjectUrl(String path) {
basePath = basePath == null ? "" : basePath;
String fullPath = StringUtils.removeFirstSeparator(StringUtils.removeDuplicateSeparator(basePath + "/" + path));
// 如果不是私有空间, 且指定了加速域名, 则直接返回下载地址.
if (BooleanUtil.isFalse(isPrivate) && StringUtils.isNotNullOrEmpty(domain)) {
return StringUtils.concatPath(domain, fullPath);
}
Date expirationDate = new Date(System.currentTimeMillis() + timeout * 1000);
URL url = s3Client.generatePresignedUrl(bucketName, fullPath, expirationDate);
@@ -104,13 +111,23 @@ public abstract class AbstractS3FileService extends AbstractFileService {
return URLUtil.decode(defaultUrl);
}
/**
* 获取 basePath + path 的全路径地址.
* @return basePath + path 的全路径地址.
*/
public String getFullPath() {
String basePath = ObjectUtil.defaultIfNull(this.basePath, "");
String path = ObjectUtil.defaultIfNull(this.path, "");
return StringUtils.removeDuplicateSeparator(basePath + "/" + path);
@Override
public FileItemDTO getFileItem(String path) {
List<FileItemDTO> list;
try {
int end = path.lastIndexOf("/");
list = fileList(path.substring(0, end + 1));
} catch (Exception e) {
throw new NotExistFileException();
}
for (FileItemDTO fileItemDTO : list) {
String fullPath = StringUtils.concatUrl(fileItemDTO.getPath(), fileItemDTO.getName());
if (Objects.equals(fullPath, path)) {
return fileItemDTO;
}
}
throw new NotExistFileException();
}
}

View File

@@ -1,13 +1,13 @@
package im.zhaojun.common.service;
package im.zhaojun.zfile.service.base;
import im.zhaojun.common.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import java.util.List;
/**
* @author zhaojun
*/
public interface FileService {
public interface BaseFileService {
/***
* 获取指定路径下的文件及文件夹

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