mirror of
https://github.com/zfile-dev/zfile.git
synced 2025-04-19 05:34:52 +00:00
Compare commits
355 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17257dccda | ||
|
|
1c1248306c | ||
|
|
d5873a7f97 | ||
|
|
7437dc8936 | ||
|
|
549a599a5b | ||
|
|
38effb0bb7 | ||
|
|
1e25c23b0b | ||
|
|
c870d32777 | ||
|
|
b86e487a47 | ||
|
|
330713509e | ||
|
|
ecf85dfe9e | ||
|
|
ac3b4283a3 | ||
|
|
141f9dee5e | ||
|
|
ce9f809ab5 | ||
|
|
b83af8dc5e | ||
|
|
ce0a7bd6ef | ||
|
|
008425734c | ||
|
|
e078366395 | ||
|
|
69ec12ab99 | ||
|
|
8e93874c69 | ||
|
|
2a6f0f94cc | ||
|
|
d501d96ad6 | ||
|
|
73569a63f8 | ||
|
|
71e6ba4d8b | ||
|
|
72a627be74 | ||
|
|
0344f687b6 | ||
|
|
1b0789cdd0 | ||
|
|
7cf16754f8 | ||
|
|
6aefc107e7 | ||
|
|
e082043f99 | ||
|
|
a55e8ae2ad | ||
|
|
188431b64d | ||
|
|
7f23dcb7c4 | ||
|
|
8dcb64a60d | ||
|
|
d9c64ff369 | ||
|
|
6963b1d593 | ||
|
|
0a61e1047d | ||
|
|
3f02cd9832 | ||
|
|
3aa42c00fa | ||
|
|
77b2253ff6 | ||
|
|
71a4fdfbaf | ||
|
|
2ecd69dc51 | ||
|
|
ebae9ba5c8 | ||
|
|
b2fd722443 | ||
|
|
e2ce404e87 | ||
|
|
6cfbe7689e | ||
|
|
b95c1d890b | ||
|
|
461c77012a | ||
|
|
9328e0ea9d | ||
|
|
aefa928a19 | ||
|
|
89c6c515f1 | ||
|
|
dcadffa265 | ||
|
|
300e58e92c | ||
|
|
96b71e4f8d | ||
|
|
456aabb893 | ||
|
|
b4d2ca238f | ||
|
|
2afb841fd9 | ||
|
|
2b09812153 | ||
|
|
972099a598 | ||
|
|
9335d78d60 | ||
|
|
f332cb929b | ||
|
|
a190a2ec6e | ||
|
|
5bfa7037cb | ||
|
|
4a94c879b8 | ||
|
|
38161f96e1 | ||
|
|
33c751ab33 | ||
|
|
d12cbd2383 | ||
|
|
5f84becf08 | ||
|
|
432fd89c0f | ||
|
|
84c8adc9d2 | ||
|
|
ba2523ac8a | ||
|
|
dcdd25c01f | ||
|
|
278e320550 | ||
|
|
1bfa66cc49 | ||
|
|
20fb2b3baa | ||
|
|
e9a85a4e88 | ||
|
|
63edbc11d3 | ||
|
|
c98c6af0f9 | ||
|
|
47f6066733 | ||
|
|
f4fb471da3 | ||
|
|
301b6bdf70 | ||
|
|
a98c4f4c48 | ||
|
|
f4a625dac6 | ||
|
|
1c2f3de55c | ||
|
|
ed375768f5 | ||
|
|
b0c4ed1fad | ||
|
|
8d30ca7eee | ||
|
|
43aba8e20e | ||
|
|
a2fa8b3eeb | ||
|
|
0ef77ee11b | ||
|
|
6608c0b456 | ||
|
|
3ceb5c8c1b | ||
|
|
7399c89a8e | ||
|
|
477c9dbdd2 | ||
|
|
e8117c7d3b | ||
|
|
b29ff1e646 | ||
|
|
774a8e184a | ||
|
|
7da1405798 | ||
|
|
564770ba3c | ||
|
|
ac07de4e73 | ||
|
|
3933eba99a | ||
|
|
27e8f7961e | ||
|
|
55843cbef6 | ||
|
|
ebbb33409f | ||
|
|
b39360791f | ||
|
|
9833378e25 | ||
|
|
31df2d16e2 | ||
|
|
f8f07912a1 | ||
|
|
edf43954c6 | ||
|
|
5a816b1dfb | ||
|
|
910406c33a | ||
|
|
543f76ad1d | ||
|
|
1cc2d874a1 | ||
|
|
18de372bf9 | ||
|
|
5748644814 | ||
|
|
fb8060d316 | ||
|
|
a4005defe2 | ||
|
|
4c90d5bdda | ||
|
|
fa46850bb4 | ||
|
|
a7551fce53 | ||
|
|
0591669ec9 | ||
|
|
6286a9e9aa | ||
|
|
c67ee1e89d | ||
|
|
c75695c63a | ||
|
|
8f771c652d | ||
|
|
d6e13d6115 | ||
|
|
8460d17b07 | ||
|
|
49806221b4 | ||
|
|
409af8409d | ||
|
|
6647efeb03 | ||
|
|
c8bd52e26a | ||
|
|
27db6ed14a | ||
|
|
b079d03753 | ||
|
|
edf8e114ad | ||
|
|
67a84edd4d | ||
|
|
d798750ee6 | ||
|
|
fea1da86fc | ||
|
|
96a0c90600 | ||
|
|
a054e82740 | ||
|
|
dc0e84e1e3 | ||
|
|
ae31005959 | ||
|
|
0f167a304d | ||
|
|
43d8143c74 | ||
|
|
b2fb1af99b | ||
|
|
a4f4d654a3 | ||
|
|
04d9c24b43 | ||
|
|
91a7092190 | ||
|
|
f82155f157 | ||
|
|
a99218dfa5 | ||
|
|
3e9fe27890 | ||
|
|
7524f58928 | ||
|
|
6499f2d220 | ||
|
|
40b1f1bc2d | ||
|
|
3bd859d705 | ||
|
|
6156d85ba3 | ||
|
|
e1df2f886b | ||
|
|
cea42a57bb | ||
|
|
c123d2a671 | ||
|
|
02e00b765b | ||
|
|
1992c1f52d | ||
|
|
4eb9a721fc | ||
|
|
f1bac40af4 | ||
|
|
0a7fb41e81 | ||
|
|
4fd0b8d442 | ||
|
|
28150b0c1a | ||
|
|
a7813ff7bc | ||
|
|
ae500ef9dc | ||
|
|
29d5b84df2 | ||
|
|
28882b198c | ||
|
|
d624c886fe | ||
|
|
e08654a59e | ||
|
|
8fa6a5c9b8 | ||
|
|
7620a578fb | ||
|
|
9c9bf93f00 | ||
|
|
912eb694cb | ||
|
|
78d5ba90cf | ||
|
|
056f9d4449 | ||
|
|
9a6020dcc8 | ||
|
|
6b3025f379 | ||
|
|
9dffaaee25 | ||
|
|
bda654a012 | ||
|
|
52271b5117 | ||
|
|
223a6c1970 | ||
|
|
60c272b714 | ||
|
|
3b6ed1c78d | ||
|
|
5d888bb68f | ||
|
|
082bb07213 | ||
|
|
803b8cdf71 | ||
|
|
eca5f7e48b | ||
|
|
b70c37f3f0 | ||
|
|
5cb2844141 | ||
|
|
4dd6cdb4b3 | ||
|
|
c6127c029f | ||
|
|
e149039ecb | ||
|
|
37688d83cf | ||
|
|
6692016642 | ||
|
|
3f41aeda9a | ||
|
|
de86d5c47d | ||
|
|
c89e072005 | ||
|
|
b533b5e959 | ||
|
|
d35cf27d47 | ||
|
|
796c4c1fb0 | ||
|
|
1033d6c1a9 | ||
|
|
e09c6b4e58 | ||
|
|
168b0b08f3 | ||
|
|
60a6a5348c | ||
|
|
2ec8a5df1f | ||
|
|
47b5f6ac12 | ||
|
|
c64c8465f2 | ||
|
|
f636681dd8 | ||
|
|
eadd2434e0 | ||
|
|
b84c0bff42 | ||
|
|
4cb5b84bfe | ||
|
|
0351b4401c | ||
|
|
48cb14be8a | ||
|
|
eea2ff11f9 | ||
|
|
325ec1a348 | ||
|
|
93205266d3 | ||
|
|
6849a4347f | ||
|
|
cb5c6a5945 | ||
|
|
5ed45c3bb3 | ||
|
|
4442ec3165 | ||
|
|
b268a24333 | ||
|
|
84f9354d4e | ||
|
|
8dfc4f8004 | ||
|
|
1158f5c2b9 | ||
|
|
c5f0e15207 | ||
|
|
68a842ce75 | ||
|
|
3bd4f74dae | ||
|
|
4a0bdc3baf | ||
|
|
603c1b4654 | ||
|
|
1136d582df | ||
|
|
187544fc06 | ||
|
|
aa3cde8f59 | ||
|
|
e29a702b6e | ||
|
|
e4f663c9f0 | ||
|
|
fb08ef6e78 | ||
|
|
10c465d159 | ||
|
|
d15d1203b7 | ||
|
|
de48ed3b61 | ||
|
|
d22e2e872a | ||
|
|
7da1b454dc | ||
|
|
463f311dd3 | ||
|
|
73b42cf654 | ||
|
|
1fac59c4cd | ||
|
|
6ed6b4a019 | ||
|
|
7bd02437f0 | ||
|
|
57aeb5771c | ||
|
|
695c03a530 | ||
|
|
6ebc403572 | ||
|
|
7ff6fe43b5 | ||
|
|
b34f141181 | ||
|
|
87dd7b58d1 | ||
|
|
2a949db5d2 | ||
|
|
b889e91fb5 | ||
|
|
b5c757f9f0 | ||
|
|
a465f48b94 | ||
|
|
797a0a0e4c | ||
|
|
e7ff159b6d | ||
|
|
a9fbf54bb2 | ||
|
|
81f9e262f5 | ||
|
|
23bb3960ab | ||
|
|
c4a17985a4 | ||
|
|
75ddcd47f4 | ||
|
|
2dd03ae490 | ||
|
|
5b383c8741 | ||
|
|
73198d7852 | ||
|
|
fb0d9721aa | ||
|
|
b24c663fd6 | ||
|
|
6e62cfc84d | ||
|
|
eee22e9dc9 | ||
|
|
5109c51ffc | ||
|
|
66d6d311ea | ||
|
|
ef7cbdcbd7 | ||
|
|
63bcbebb4b | ||
|
|
50ce1bb6db | ||
|
|
3c88659679 | ||
|
|
d79a993eea | ||
|
|
afafb311b8 | ||
|
|
2e280e4931 | ||
|
|
ed6efac8b7 | ||
|
|
7409df85d7 | ||
|
|
4d42529c4d | ||
|
|
65224685c8 | ||
|
|
080a84986e | ||
|
|
3f8beb2f0b | ||
|
|
410a87c426 | ||
|
|
c14b8343d2 | ||
|
|
bea440f6c3 | ||
|
|
6a5fe15121 | ||
|
|
e920ab0ec0 | ||
|
|
537e3e0563 | ||
|
|
e208dc7c4c | ||
|
|
ed64910a53 | ||
|
|
5b075c3505 | ||
|
|
a8e6d9af6a | ||
|
|
2b21d8a73c | ||
|
|
e30289d21b | ||
|
|
3b6e2be7fe | ||
|
|
43c12aa8a7 | ||
|
|
c03a7710c0 | ||
|
|
1833b23d84 | ||
|
|
f3e393972d | ||
|
|
4f46c13369 | ||
|
|
f181959218 | ||
|
|
11effc0ae7 | ||
|
|
c8397e17bf | ||
|
|
ed32b9f1d4 | ||
|
|
4e184936db | ||
|
|
fe6aebfdee | ||
|
|
d65e1a442d | ||
|
|
34647793c8 | ||
|
|
e8c249b9ea | ||
|
|
d1e613dc10 | ||
|
|
1adcfee96f | ||
|
|
75f5de6b9a | ||
|
|
499942ef70 | ||
|
|
e11277ce26 | ||
|
|
5edd9e38a7 | ||
|
|
f4ffee706b | ||
|
|
bb65750278 | ||
|
|
e09167c0d0 | ||
|
|
ee6c04fa11 | ||
|
|
b31982b788 | ||
|
|
544a3d3eb2 | ||
|
|
1987bc97a9 | ||
|
|
7e878af06c | ||
|
|
766a047ee1 | ||
|
|
c1d29a46f5 | ||
|
|
08e39b3d15 | ||
|
|
e7790ac256 | ||
|
|
499f3e108c | ||
|
|
19144b653e | ||
|
|
17a87648fa | ||
|
|
ac4cef0980 | ||
|
|
71978f8003 | ||
|
|
0b3a67ec6e | ||
|
|
ee5fb54ebb | ||
|
|
4585f22817 | ||
|
|
b523453588 | ||
|
|
a8cc03c911 | ||
|
|
949c437653 | ||
|
|
84e9cce60f | ||
|
|
5720bd93ec | ||
|
|
bcae9713bc | ||
|
|
04e3023071 | ||
|
|
a09ef84629 | ||
|
|
1d29395191 | ||
|
|
3720dc6aa9 | ||
|
|
3ada172be2 | ||
|
|
15f8fbb49b | ||
|
|
547e688d38 | ||
|
|
708eb33d0e | ||
|
|
e954b725b1 | ||
|
|
c89cb4e495 |
53
.github/ISSUE_TEMPLATE/bug_report.md
vendored
53
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -6,43 +6,20 @@ labels: 'bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!--
|
||||
你好!感谢你正在考虑为 ZFile 提交一个 bug。请花一点点时间尽量详细地回答以下基础问题。
|
||||
为了帮助我们更好的解决您的问题,请填写以下选项(不填写完整可能会被直接关闭 issue):
|
||||
|
||||
谢谢!
|
||||
-->
|
||||
|
||||
<!--
|
||||
请确认你已经做了下面这些事情,若 bug 还是未解决,那么请尽可详细地描述你的问题。
|
||||
|
||||
- 我已经安装了最新版的 ZFile
|
||||
- 我已经阅读了 ZFile 的文档:http://docs.zhaojun.im/zfile
|
||||
- 我已经搜索了已有的 Issues 列表中有关的信息
|
||||
- 我已经清理过浏览器缓存并重试
|
||||
-->
|
||||
|
||||
## 我的环境
|
||||
|
||||
<!--
|
||||
请登录到管理后台,点击左侧系统监控, 复制或截图此页内容.
|
||||
-->
|
||||
|
||||
## 错误日志
|
||||
|
||||
<!--
|
||||
请登录到管理后台,点击左侧系统监控, 点击右上角诊断日志下载, 然后上传到此 Issue 中.
|
||||
-->
|
||||
|
||||
## 期望行为
|
||||
|
||||
<!--
|
||||
你期望会发生什么?
|
||||
-->
|
||||
|
||||
## 当前行为
|
||||
|
||||
<!--
|
||||
描述 bug 细节,确认出现此问题的复现步骤,例如点击了哪里,发生了什么情况?
|
||||
|
||||
你可以粘贴截图或附件。
|
||||
-->
|
||||
- 是否已搜索其他 issue,没有人提过这个问题?:
|
||||
- 是否已查阅、搜索 [ZFile 文档](https://docs.zfile.vip),尤其是常见问题页,仍然未解决?:
|
||||
- 当前 ZFile 版本:
|
||||
- 是否尝试最新版是否已解决此问题:
|
||||
- 是否尝试重启 ZFile,且问题依旧存在?:
|
||||
- 是否已尝试清空浏览器缓存,且问题依旧存在?:
|
||||
- 操作系统(如 Windows、Mac、iOS、安卓):
|
||||
- 浏览器(如 Chrome、Firefox、Safari,X 浏览器):
|
||||
- 做什么操作提示的错误?:
|
||||
- 期望行为(应该是什么样的结果):
|
||||
- 当前行为(当前是什么样的结果):
|
||||
- 错误日志(可选):
|
||||
- 复现步骤(可选):
|
||||
- 您的额外信息(可选):
|
||||
29
.github/ISSUE_TEMPLATE/feature_request.md
vendored
29
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -7,28 +7,11 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
你好!感谢你愿意考虑希望 ZFile 增加某个新功能。请花一点点时间尽量详细地回答以下基础问题。
|
||||
|
||||
谢谢!
|
||||
-->
|
||||
为了帮助我们更好的解决您的问题,请填写以下选项(不填写完整可能会被直接关闭 issue):
|
||||
|
||||
## 概述
|
||||
|
||||
<!--
|
||||
对这个新功能的一段描述
|
||||
-->
|
||||
|
||||
## 动机
|
||||
|
||||
<!--
|
||||
为什么你希望在 ZFile 中使用这个功能?
|
||||
-->
|
||||
|
||||
## 详细解释
|
||||
|
||||
<!--
|
||||
详细描述这个新功能。
|
||||
|
||||
如果这是一个小功能,你可以忽略这部分。
|
||||
-->
|
||||
- 是否已搜索其他 issue,没有人提过这个功能?:
|
||||
- 是否已尝试使用最新版本,且仍然没有此功能?:
|
||||
- 功能概述:
|
||||
- 功能动机:
|
||||
- 详细解释(可选):
|
||||
14
.github/ISSUE_TEMPLATE/question.md
vendored
14
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,14 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: 对 ZFile 有任何问题吗?
|
||||
title: ''
|
||||
labels: 'question'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
如果你有任何问题也可以通过此渠道来向我们反馈。
|
||||
|
||||
谢谢!
|
||||
-->
|
||||
19
.gitignore
vendored
19
.gitignore
vendored
@@ -1,11 +1,8 @@
|
||||
HELP.md
|
||||
target/
|
||||
.mvn/wrapper/**
|
||||
!**/src/main/**
|
||||
**/src/test/**
|
||||
|
||||
mvnw
|
||||
mvnw.cmd
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
@@ -21,6 +18,8 @@ mvnw.cmd
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
.fastRequest
|
||||
.murphy.yml
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
@@ -29,6 +28,14 @@ mvnw.cmd
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
/.mvn/wrapper/
|
||||
/mvnw
|
||||
/mvnw.cmd
|
||||
/result/
|
||||
/.package/**
|
||||
/.package**
|
||||
164
API.md
164
API.md
@@ -1,164 +0,0 @@
|
||||
## 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 片段
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
206
README.md
206
README.md
@@ -1,161 +1,77 @@
|
||||
# Z-File
|
||||
<p align="center">
|
||||
|
||||

|
||||
[](https://www.codacy.com/manual/zhaojun1998/zfile?utm_source=github.com&utm_medium=referral&utm_content=zhaojun1998/zfile&utm_campaign=Badge_Grade)
|
||||

|
||||

|
||||

|
||||
|
||||
此项目是一个在线文件目录的程序, 支持各种对象存储和本地存储, 使用定位是个人放常用工具下载, 或做公共的文件库. 不会向多账户方向开发.
|
||||
|
||||
前端基于 [h5ai](https://larsjung.de/h5ai/) 的原有功能使用 Vue 重新开发了一遍. 后端采用 SpringBoot, 数据库采用内嵌数据库.
|
||||
|
||||
预览地址: [https://zfile.jun6.net](https://zfile.jun6.net)
|
||||
|
||||
文档地址: [http://docs.zhaojun.im/zfile](http://docs.zhaojun.im/zfile)
|
||||
|
||||
## 系统特色
|
||||
|
||||
* 内存缓存 (免安装)
|
||||
* 内存数据库 (免安装)
|
||||
* 个性化配置
|
||||
* 自定义目录的 readme 说明文件
|
||||
* 自定义 JS, CSS
|
||||
* 文件夹密码
|
||||
* 支持在线浏览文本文件, 视频, 图片, 音乐. (支持 FLV 和 HLS)
|
||||
* 文件/目录二维码
|
||||
* 缓存动态开启, 缓存自动刷新
|
||||
* 全局搜索
|
||||
* 支持 阿里云 OSS, FTP, 华为云 OBS, 本地存储, MINIO, OneDrive 国际/家庭/个人版, OneDrive 世纪互联版, 七牛云 KODO, 腾讯云 COS, 又拍云 USS.
|
||||
基于 Java 的在线网盘程序,支持对接 S3、OneDrive、SharePoint、Google Drive、多吉云、又拍云、本地存储、FTP、SFTP 等存储源,支持在线浏览图片、播放音视频,文本文件、Office、obj(3d)等文件类型。
|
||||
<br><br>
|
||||
<img src="https://img.shields.io/badge/license-MIT-blue.svg?longCache=true&style=flat-square" alt="license">
|
||||
<img src="https://api.codacy.com/project/badge/Grade/70b793267f7941d58cbd93f50c9a8e0a" alt="codady">
|
||||
<img src="https://img.shields.io/github/last-commit/zhaojun1998/zfile.svg?style=flat-square" alt="last commit">
|
||||
<img src="https://img.shields.io/github/downloads/zhaojun1998/zfile/total?style=flat-square" alt="downloads">
|
||||
<img src="https://img.shields.io/github/v/release/zhaojun1998/zfile?style=flat-square" alt="release">
|
||||
<img src="https://img.shields.io/github/commit-activity/y/zhaojun1998/zfile?style=flat-square" alt="commit activity">
|
||||
<br>
|
||||
<img src="https://img.shields.io/github/issues/zhaojun1998/zfile?style=flat-square" alt="issues">
|
||||
<img src="https://img.shields.io/github/issues-closed-raw/zhaojun1998/zfile?style=flat-square" alt="closed issues">
|
||||
<img src="https://img.shields.io/github/forks/zhaojun1998/zfile?style=flat-square" alt="forks">
|
||||
<img src="https://img.shields.io/github/stars/zhaojun1998/zfile?style=flat-square" alt="stars">
|
||||
<img src="https://img.shields.io/github/watchers/zhaojun1998/zfile?style=flat-square" alt="watchers">
|
||||
</p>
|
||||
|
||||
## 快速开始
|
||||
|
||||
安装依赖环境:
|
||||
请参考部署文档: [https://docs.zfile.vip](https://docs.zfile.vip)
|
||||
|
||||
```bash
|
||||
# CentOS系统
|
||||
yum install -y java-1.8.0-openjdk unzip
|
||||
## 在线体验
|
||||
|
||||
# Debian 9 / Ubuntu 14+
|
||||
apt update
|
||||
apt install -y openjdk-8-jre-headless unzip
|
||||
[https://demo.zfile.vip](https://demo.zfile.vip)
|
||||
|
||||
# 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` 清理旧程序. 首次安装请忽略此选项.
|
||||
### 文件列表
|
||||

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

|
||||
|
||||
下载项目:
|
||||
|
||||
```bash
|
||||
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
|
||||
├── META-INF
|
||||
├── WEB-INF
|
||||
└── bin
|
||||
├── start.sh # 启动脚本
|
||||
└── stop.sh # 停止脚本
|
||||
├── restart.sh # 重启脚本
|
||||
```
|
||||
|
||||
启动项目:
|
||||
|
||||
```bash
|
||||
~/zfile/bin/start.sh
|
||||
```
|
||||
|
||||
篇幅有限, 更详细的安装教程请参考: [安装文档](http://zhaojun.im/zfile-install)
|
||||
|
||||
访问地址:
|
||||
|
||||
用户前台: http://127.0.0.1:8080/#/main
|
||||
|
||||
初始安装: http://127.0.0.1:8080/#/install
|
||||
|
||||
管理后台: 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/onedrive/callback&scope=offline_access%20User.Read%20Files.ReadWrite.All
|
||||
|
||||
|
||||
世纪互联版:
|
||||
|
||||
https://login.chinacloudapi.cn/common/oauth2/v2.0/authorize?client_id=4a72d927-1907-488d-9eb2-1b465c53c1c5&response_type=code&redirect_uri=https://zfile.jun6.net/onedrive/china-callback&scope=offline_access%20User.Read%20Files.ReadWrite.All
|
||||
|
||||
|
||||
然后分别填写至访问令牌和刷新令牌即可:
|
||||
|
||||

|
||||
|
||||
## 运行环境
|
||||
|
||||
* JDK: `1.8`
|
||||
* 数据库: `h2/mysql`
|
||||
|
||||
## 预览
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 常见问题
|
||||
|
||||
### 数据库
|
||||
|
||||
缓存默认支持 `h2` 和 `mysql`, 前者为嵌入式数据库, 无需安装, 但后者相对性能更好.
|
||||
|
||||
### 默认路径
|
||||
|
||||
默认 H2 数据库文件地址: `~/.zfile/db/`, `~` 表示用户目录
|
||||
|
||||
windows 为 `C:/Users/用户名/`
|
||||
|
||||
linux 为 `/home/用户名/`, root 用户为 `/root/`
|
||||
|
||||
### 文档文件和加密文件
|
||||
|
||||
- 目录文档显示文件名为 `readme.md`
|
||||
- 目录需要密码访问, 添加文件 `password.txt` (无法拦截此文件被下载, 但可以改名文件)
|
||||
|
||||
## 开发计划
|
||||
|
||||
- [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="https://cdn.jun6.net/2021/03/27/152704e91f13d.png" width="400" alt="赞助我">
|
||||
|
||||
<img src="http://cdn.jun6.net/alipay.png" width="200" height="312">
|
||||
<img src="http://cdn.jun6.net/wechat.png" width="222" height="300">
|
||||
## Status
|
||||
|
||||

|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#zfile-dev/zfile&Date)
|
||||
240
pom.xml
240
pom.xml
@@ -1,28 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>im.zhaojun</groupId>
|
||||
<artifactId>zfile</artifactId>
|
||||
<version>4.1.5</version>
|
||||
<name>zfile</name>
|
||||
<packaging>jar</packaging>
|
||||
<description>一个在线的文件浏览系统</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.6.RELEASE</version>
|
||||
<relativePath/>
|
||||
<version>2.7.12</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>im.zhaojun</groupId>
|
||||
<artifactId>zfile</artifactId>
|
||||
<version>2.3</version>
|
||||
<name>zfile</name>
|
||||
<packaging>war</packaging>
|
||||
<description>一个在线的文件浏览系统</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
|
||||
<snakeyaml.version>2.0</snakeyaml.version>
|
||||
<jackson-bom.version>2.14.1</jackson-bom.version>
|
||||
<sqlite-jdbc.version>3.41.2.2</sqlite-jdbc.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- spring boot 官方相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@@ -32,14 +34,6 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
@@ -49,72 +43,170 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据库驱动-->
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 数据库相关 -->
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<version>8.0.33</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
<version>7.15.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>3.5.3.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类 -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.1.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 存储策略相关 API, 对象存储、FTP、 Rest API-->
|
||||
<dependency>
|
||||
<groupId>com.upyun</groupId>
|
||||
<artifactId>java-sdk</artifactId>
|
||||
<version>4.1.3</version>
|
||||
<version>4.2.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<version>1.11.699</version>
|
||||
<version>1.12.470</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.qiniu</groupId>
|
||||
<artifactId>qiniu-java-sdk</artifactId>
|
||||
<version>7.12.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
<version>0.1.55</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.lookfirst</groupId>
|
||||
<artifactId>sardine</artifactId>
|
||||
<version>5.10</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 登陆/权限相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.34.0.temp1</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 文档相关 -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 工具类 -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.18</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.24</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.8</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>3.6</version>
|
||||
<version>3.9.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>2.0.29</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>30.1.1-jre</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-chain</groupId>
|
||||
<artifactId>commons-chain</artifactId>
|
||||
<version>1.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.samstevens.totp</groupId>
|
||||
<artifactId>totp-spring-boot-starter</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.beust</groupId>
|
||||
<artifactId>jcommander</artifactId>
|
||||
<version>1.82</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20230227</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpmime</artifactId>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.70</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 其他工具类 -->
|
||||
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mpatric</groupId>
|
||||
<artifactId>mp3agic</artifactId>
|
||||
<version>0.9.1</version>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
<version>1.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.61</version>
|
||||
<artifactId>dns-cache-manipulator</artifactId>
|
||||
<version>1.8.1</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -122,6 +214,44 @@
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${org.mapstruct.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.16</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok-mapstruct-binding</artifactId>
|
||||
<version>0.2.0</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.uyoqu.framework</groupId>
|
||||
@@ -139,10 +269,14 @@
|
||||
<jvms>
|
||||
<jvm>-Djava.security.egd=file:/dev/./urandom</jvm>
|
||||
<jvm>-Dfile.encoding=utf-8</jvm>
|
||||
<jvm>-Djava.net.preferIPv4Stack=false</jvm>
|
||||
<jvm>-Djava.net.preferIPv4Addresses=true</jvm>
|
||||
<jvm>-Djava.awt.headless=true</jvm>
|
||||
</jvms>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2022, baomidou (jobob@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.baomidou.mybatisplus.core.handlers;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import org.apache.ibatis.reflection.DefaultReflectorFactory;
|
||||
import org.apache.ibatis.reflection.MetaClass;
|
||||
import org.apache.ibatis.reflection.ReflectorFactory;
|
||||
import org.apache.ibatis.reflection.invoker.Invoker;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 自定义枚举属性转换器
|
||||
*
|
||||
* @author hubin
|
||||
* @since 2017-10-11
|
||||
*/
|
||||
public class MybatisEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
|
||||
|
||||
private static final Map<String, String> TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap<>();
|
||||
private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
|
||||
private final Class<E> enumClassType;
|
||||
private final Class<?> propertyType;
|
||||
private final Invoker getInvoker;
|
||||
|
||||
public MybatisEnumTypeHandler(Class<E> enumClassType) {
|
||||
if (enumClassType == null) {
|
||||
throw new IllegalArgumentException("Type argument cannot be null");
|
||||
}
|
||||
this.enumClassType = enumClassType;
|
||||
MetaClass metaClass = MetaClass.forClass(enumClassType, REFLECTOR_FACTORY);
|
||||
String name = "value";
|
||||
if (!IEnum.class.isAssignableFrom(enumClassType)) {
|
||||
name = findEnumValueFieldName(this.enumClassType).orElseThrow(() -> new IllegalArgumentException(String.format("Could not find @EnumValue in Class: %s.", this.enumClassType.getName())));
|
||||
}
|
||||
this.propertyType = ReflectionKit.resolvePrimitiveIfNecessary(metaClass.getGetterType(name));
|
||||
this.getInvoker = metaClass.getGetInvoker(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找标记标记EnumValue字段
|
||||
*
|
||||
* @param clazz class
|
||||
* @return EnumValue字段
|
||||
* @since 3.3.1
|
||||
*/
|
||||
public static Optional<String> findEnumValueFieldName(Class<?> clazz) {
|
||||
if (clazz != null && clazz.isEnum()) {
|
||||
String className = clazz.getName();
|
||||
return Optional.ofNullable(CollectionUtils.computeIfAbsent(TABLE_METHOD_OF_ENUM_TYPES, className, key -> {
|
||||
Optional<Field> fieldOptional = findEnumValueAnnotationField(clazz);
|
||||
return fieldOptional.map(Field::getName).orElse(null);
|
||||
}));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<Field> findEnumValueAnnotationField(Class<?> clazz) {
|
||||
return Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(EnumValue.class)).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为MP枚举处理
|
||||
*
|
||||
* @param clazz class
|
||||
* @return 是否为MP枚举处理
|
||||
* @since 3.3.1
|
||||
*/
|
||||
public static boolean isMpEnums(Class<?> clazz) {
|
||||
return clazz != null && clazz.isEnum() && (IEnum.class.isAssignableFrom(clazz) || findEnumValueFieldName(clazz).isPresent());
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType)
|
||||
throws SQLException {
|
||||
if (jdbcType == null) {
|
||||
ps.setObject(i, this.getValue(parameter));
|
||||
} else {
|
||||
// see r3589
|
||||
ps.setObject(i, this.getValue(parameter), jdbcType.TYPE_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
Object value = rs.getObject(columnName);
|
||||
if (null == value && rs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return this.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
Object value = rs.getObject(columnIndex, this.propertyType);
|
||||
if (null == value && rs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return this.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
Object value = cs.getObject(columnIndex, this.propertyType);
|
||||
if (null == value && cs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return this.valueOf(value);
|
||||
}
|
||||
|
||||
private E valueOf(Object value) {
|
||||
E[] es = this.enumClassType.getEnumConstants();
|
||||
return Arrays.stream(es).filter((e) -> equalsValue(value, getValue(e))).findAny().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 值比较
|
||||
*
|
||||
* @param sourceValue 数据库字段值
|
||||
* @param targetValue 当前枚举属性值
|
||||
* @return 是否匹配
|
||||
* @since 3.3.0
|
||||
*/
|
||||
protected boolean equalsValue(Object sourceValue, Object targetValue) {
|
||||
String sValue = StringUtils.toStringTrim(sourceValue);
|
||||
String tValue = StringUtils.toStringTrim(targetValue);
|
||||
if (sourceValue instanceof Number && targetValue instanceof Number
|
||||
&& new BigDecimal(sValue).compareTo(new BigDecimal(tValue)) == 0) {
|
||||
return true;
|
||||
}
|
||||
return Objects.equals(sValue, tValue);
|
||||
}
|
||||
|
||||
private Object getValue(Object object) {
|
||||
try {
|
||||
return this.getInvoker.invoke(object, new Object[0]);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw ExceptionUtils.mpe(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,22 @@ package im.zhaojun.zfile;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@EnableAsync
|
||||
@SpringBootApplication
|
||||
@EnableAspectJAutoProxy(exposeProxy = true)
|
||||
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
|
||||
@ServletComponentScan(basePackages = {"im.zhaojun.zfile.core.filter", "im.zhaojun.zfile.module.storage.filter"})
|
||||
@ComponentScan(basePackages = "im.zhaojun.zfile.*")
|
||||
public class ZfileApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ZfileApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
254
src/main/java/im/zhaojun/zfile/cache/ZFileCache.java
vendored
254
src/main/java/im/zhaojun/zfile/cache/ZFileCache.java
vendored
@@ -1,254 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package im.zhaojun.zfile.config;
|
||||
|
||||
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.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
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) -> {
|
||||
String host = httpRequest.getURI().getHost();
|
||||
StorageTypeEnum type;
|
||||
if (oneDriveChinaServiceImpl.getGraphEndPoint().contains(host)) {
|
||||
type = StorageTypeEnum.ONE_DRIVE_CHINA;
|
||||
} else if (oneDriveServiceImpl.getGraphEndPoint().contains(host)) {
|
||||
type = StorageTypeEnum.ONE_DRIVE;
|
||||
} else {
|
||||
return clientHttpRequestExecution.execute(httpRequest, bytes);
|
||||
}
|
||||
|
||||
StorageConfig accessTokenConfig =
|
||||
storageConfigService.selectByTypeAndKey(type, StorageConfigConstant.ACCESS_TOKEN_KEY);
|
||||
|
||||
String tokenValue = String.format("%s %s", "Bearer", accessTokenConfig.getValue());
|
||||
httpRequest.getHeaders().add("Authorization", tokenValue);
|
||||
return clientHttpRequestExecution.execute(httpRequest, bytes);
|
||||
};
|
||||
restTemplate.setInterceptors(Collections.singletonList(interceptor));
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
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
|
||||
*/
|
||||
@Configuration
|
||||
public class ZFileConfiguration {
|
||||
|
||||
@Bean
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package im.zhaojun.zfile.context;
|
||||
|
||||
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 StorageTypeContext implements ApplicationContextAware {
|
||||
|
||||
private static Map<String, AbstractBaseFileService> storageTypeEnumFileServiceMap;
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
|
||||
/**
|
||||
* 项目启动时执行
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext act) throws BeansException {
|
||||
applicationContext = act;
|
||||
|
||||
// 获取 Spring 容器中所有 FileService 类型的类
|
||||
storageTypeEnumFileServiceMap = act.getBeansOfType(AbstractBaseFileService.class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定存储类型 Service
|
||||
*/
|
||||
public static AbstractBaseFileService getStorageTypeService(StorageTypeEnum type) {
|
||||
AbstractBaseFileService result = null;
|
||||
for (AbstractBaseFileService fileService : storageTypeEnumFileServiceMap.values()) {
|
||||
if (fileService.getStorageTypeEnum() == type) {
|
||||
result = fileService;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
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)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package im.zhaojun.zfile.core;
|
||||
|
||||
import im.zhaojun.zfile.core.util.AjaxJson;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJacksonValue;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class CommonResultControllerAdvice implements ResponseBodyAdvice<Object> {
|
||||
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType,
|
||||
@NonNull Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public final Object beforeBodyWrite(@Nullable Object body,
|
||||
@NonNull MethodParameter returnType,
|
||||
@NonNull MediaType contentType,
|
||||
@NonNull Class<? extends HttpMessageConverter<?>> converterType,
|
||||
@NonNull ServerHttpRequest request,
|
||||
@NonNull ServerHttpResponse response) {
|
||||
MappingJacksonValue container = getOrCreateContainer(body);
|
||||
// The contain body will never be null
|
||||
beforeBodyWriteInternal(container, contentType, returnType, request, response);
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the body in a {@link MappingJacksonValue} value container (for providing
|
||||
* additional serialization instructions) or simply cast it if already wrapped.
|
||||
*/
|
||||
private MappingJacksonValue getOrCreateContainer(Object body) {
|
||||
return body instanceof MappingJacksonValue ? (MappingJacksonValue) body :
|
||||
new MappingJacksonValue(body);
|
||||
}
|
||||
|
||||
private void beforeBodyWriteInternal(MappingJacksonValue bodyContainer,
|
||||
MediaType contentType,
|
||||
MethodParameter returnType,
|
||||
ServerHttpRequest request,
|
||||
ServerHttpResponse response) {
|
||||
// Get return body
|
||||
Object returnBody = bodyContainer.getValue();
|
||||
|
||||
if (returnBody instanceof AjaxJson) {
|
||||
// If the return body is instance of BaseResponse, then just do nothing
|
||||
AjaxJson<?> baseResponse = (AjaxJson<?>) returnBody;
|
||||
baseResponse.setTraceId(MDC.get("traceId"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
@@ -0,0 +1,83 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.BeanProperty;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.jackson.JsonComponent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Jackson 枚举反序列化器
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
@Setter
|
||||
@JsonComponent
|
||||
public class JacksonEnumDeserializer extends JsonDeserializer<Enum<?>> implements ContextualDeserializer {
|
||||
|
||||
private Class<?> clazz;
|
||||
|
||||
|
||||
/**
|
||||
* 反序列化操作
|
||||
*
|
||||
* @param jsonParser
|
||||
* json 解析器
|
||||
*
|
||||
* @param ctx
|
||||
* 反序列化上下文
|
||||
*
|
||||
* @return 反序列化后的枚举值
|
||||
* @throws IOException 反序列化异常
|
||||
*/
|
||||
@Override
|
||||
public Enum<?> deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
|
||||
Class<?> enumType = clazz;
|
||||
if (Objects.isNull(enumType) || !enumType.isEnum()) {
|
||||
return null;
|
||||
}
|
||||
String text = jsonParser.getText();
|
||||
Method method = StringToEnumConverterFactory.getMethod(clazz);
|
||||
Enum<?>[] enumConstants = (Enum<?>[]) enumType.getEnumConstants();
|
||||
|
||||
// 将值与枚举对象对应并缓存
|
||||
for (Enum<?> e : enumConstants) {
|
||||
try {
|
||||
if (Objects.equals(method.invoke(e).toString(), text)) {
|
||||
return e;
|
||||
}
|
||||
} catch (IllegalAccessException | InvocationTargetException ex) {
|
||||
log.error("获取枚举值错误!!! ", ex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 为不同的枚举获取合适的解析器
|
||||
*
|
||||
* @param ctx
|
||||
* 反序列化上下文
|
||||
*
|
||||
* @param property
|
||||
* property
|
||||
*/
|
||||
@Override
|
||||
public JsonDeserializer<Enum<?>> createContextual(DeserializationContext ctx, BeanProperty property) {
|
||||
Class<?> rawCls = ctx.getContextualType().getRawClass();
|
||||
JacksonEnumDeserializer converter = new JacksonEnumDeserializer();
|
||||
converter.setClazz(rawCls);
|
||||
return converter;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
@@ -0,0 +1,66 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* mybatis-plus 配置类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class MyBatisPlusConfig {
|
||||
|
||||
@Resource
|
||||
private DataSource dataSource;
|
||||
|
||||
@Value("${spring.datasource.driver-class-name}")
|
||||
private String datasourceDriveClassName;
|
||||
|
||||
@Value("${spring.datasource.url}")
|
||||
private String datasourceUrl;
|
||||
|
||||
/**
|
||||
* 如果是 sqlite 数据库,自动创建数据库文件所在目录
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if (StrUtil.equals(datasourceDriveClassName, "org.sqlite.JDBC")) {
|
||||
String path = datasourceUrl.replace("jdbc:sqlite:", "");
|
||||
String folderPath = FileUtil.getParent(path, 1);
|
||||
log.info("SQLite 数据库文件所在目录: [{}]", folderPath);
|
||||
if (!FileUtil.exist(folderPath)) {
|
||||
FileUtil.mkdir(folderPath);
|
||||
log.info("检测到 SQLite 数据库文件所在目录不存在, 已自动创建.");
|
||||
} else {
|
||||
log.info("检测到 SQLite 数据库文件所在目录已存在, 无需自动创建.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mybatis plus 分页插件配置
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() throws SQLException {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
String databaseProductName = dataSource.getConnection().getMetaData().getDatabaseProductName();
|
||||
DbType dbType = DbType.getDbType(databaseProductName);
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(dbType));
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
|
||||
import org.apache.ibatis.mapping.DatabaseIdProvider;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
@Component
|
||||
public class MyDatabaseIdProvider implements DatabaseIdProvider {
|
||||
|
||||
private static final String DATABASE_MYSQL = "MySQL";
|
||||
private static final String DATABASE_SQLITE = "SQLite";
|
||||
|
||||
@Override
|
||||
public String getDatabaseId(DataSource dataSource) throws SQLException {
|
||||
Connection conn = dataSource.getConnection();
|
||||
String dbName = conn.getMetaData().getDatabaseProductName();
|
||||
String dbAlias = "";
|
||||
switch (dbName) {
|
||||
case DATABASE_MYSQL:
|
||||
dbAlias = "mysql";
|
||||
break;
|
||||
case DATABASE_SQLITE:
|
||||
dbAlias = "sqlite";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return dbAlias;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
|
||||
import im.zhaojun.zfile.core.httpclient.ZFileOkHttp3ClientHttpRequestFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* restTemplate 相关配置
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
/**
|
||||
* OneDrive 请求 RestTemplate.
|
||||
* 获取 header 中的 storageId 来判断到底是哪个存储源 ID, 在请求头中添加 Bearer: Authorization {token} 信息, 用于 API 认证.
|
||||
*/
|
||||
@Bean
|
||||
public RestTemplate oneDriveRestTemplate() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setRequestFactory(new ZFileOkHttp3ClientHttpRequestFactory());
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
@@ -0,0 +1,33 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
||||
import org.springframework.cache.support.NoOpCacheManager;
|
||||
import org.springframework.cache.transaction.TransactionAwareCacheManagerProxy;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Spring Cache 相关配置
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class SpringCacheConfig {
|
||||
|
||||
@Value("${zfile.dbCache.enable:true}")
|
||||
private Boolean dbCacheEnable;
|
||||
|
||||
/**
|
||||
* 使用 TransactionAwareCacheManagerProxy 装饰 ConcurrentMapCacheManager,使其支持事务 (将 put、evict、clear 操作延迟到事务成功提交再执行.)
|
||||
*/
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
return BooleanUtil.isFalse(dbCacheEnable) ? new NoOpCacheManager() : new TransactionAwareCacheManagerProxy(new ConcurrentMapCacheManager());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterFactory;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* String 转枚举通用转换器工厂
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
public class StringToEnumConverterFactory implements ConverterFactory<String, Enum<?>> {
|
||||
|
||||
/**
|
||||
* 存储枚举类型的缓存
|
||||
*/
|
||||
private static final Map<Class<?>, Converter<String, ? extends Enum<?>>> CONVERTER_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 枚举类的获取枚举值方法缓存
|
||||
*/
|
||||
private static final Map<Class<?>, Method> TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked cast")
|
||||
public <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) {
|
||||
// 缓存转换器
|
||||
Converter<String, T> converter = (Converter<String, T>) CONVERTER_MAP.get(targetType);
|
||||
if (converter == null) {
|
||||
converter = new StringToEnumConverter<>(targetType);
|
||||
CONVERTER_MAP.put(targetType, converter);
|
||||
}
|
||||
return converter;
|
||||
}
|
||||
|
||||
static class StringToEnumConverter<T extends Enum<?>> implements Converter<String, T> {
|
||||
|
||||
private final Map<String, T> enumMap = new ConcurrentHashMap<>();
|
||||
|
||||
StringToEnumConverter(Class<T> enumType) {
|
||||
Method method = getMethod(enumType);
|
||||
T[] enums = enumType.getEnumConstants();
|
||||
|
||||
// 将值与枚举对象对应并缓存
|
||||
for (T e : enums) {
|
||||
try {
|
||||
enumMap.put(method.invoke(e).toString(), e);
|
||||
} catch (IllegalAccessException | InvocationTargetException ex) {
|
||||
log.error("获取枚举值错误!!! ", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public T convert(@NotNull String source) {
|
||||
// 获取
|
||||
T t = enumMap.get(source);
|
||||
if (t == null) {
|
||||
throw new IllegalArgumentException("该字符串找不到对应的枚举对象 字符串:" + source);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static <T> Method getMethod(Class<T> enumType) {
|
||||
Method method;
|
||||
// 找到取值的方法
|
||||
if (IEnum.class.isAssignableFrom(enumType)) {
|
||||
try {
|
||||
method = enumType.getMethod("getValue");
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalArgumentException(String.format("类:%s 找不到 getValue方法",
|
||||
enumType.getName()));
|
||||
}
|
||||
} else {
|
||||
method = TABLE_METHOD_OF_ENUM_TYPES.computeIfAbsent(enumType, k -> {
|
||||
Field field =
|
||||
dealEnumType(enumType).orElseThrow(() -> new IllegalArgumentException(String.format(
|
||||
"类:%s 找不到 EnumValue注解", enumType.getName())));
|
||||
|
||||
Class<?> fieldType = field.getType();
|
||||
String fieldName = field.getName();
|
||||
String methodName = StringUtils.concatCapitalize(boolean.class.equals(fieldType) ? "is" : "get", fieldName);
|
||||
try {
|
||||
return enumType.getDeclaredMethod(methodName);
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
|
||||
private static Optional<Field> dealEnumType(Class<?> clazz) {
|
||||
return clazz.isEnum() ?
|
||||
Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(EnumValue.class)).findFirst() : Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
68
src/main/java/im/zhaojun/zfile/core/config/WebMvcConfig.java
Normal file
68
src/main/java/im/zhaojun/zfile/core/config/WebMvcConfig.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
|
||||
import im.zhaojun.zfile.module.storage.model.enums.StorageTypeEnum;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
|
||||
import org.springframework.boot.web.server.ErrorPage;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
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.http.HttpStatus;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* ZFile Web 相关配置.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
|
||||
/**
|
||||
* 添加自定义枚举格式化器.
|
||||
* @see StorageTypeEnum
|
||||
*/
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverterFactory(new StringToEnumConverterFactory());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 支持 url 中传入 <>[\]^`{|} 这些特殊字符.
|
||||
*/
|
||||
@Bean
|
||||
public ServletWebServerFactory webServerFactory() {
|
||||
TomcatServletWebServerFactory webServerFactory = new TomcatServletWebServerFactory();
|
||||
|
||||
// 添加对 URL 中特殊符号的支持.
|
||||
webServerFactory.addConnectorCustomizers(connector -> {
|
||||
connector.setProperty("relaxedPathChars", "<>[\\]^`{|}%[]");
|
||||
connector.setProperty("relaxedQueryChars", "<>[\\]^`{|}%[]");
|
||||
});
|
||||
return webServerFactory;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
|
||||
return factory -> {
|
||||
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/index.html");
|
||||
ErrorPage error200Page = new ErrorPage(HttpStatus.OK, "/index.html");
|
||||
Set<ErrorPage> errorPages = new HashSet<>();
|
||||
errorPages.add(error404Page);
|
||||
errorPages.add(error200Page);
|
||||
factory.setErrorPages(errorPages);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@EnableConfigurationProperties
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "zfile")
|
||||
public class ZFileProperties {
|
||||
|
||||
private boolean debug;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package im.zhaojun.zfile.core.constant;
|
||||
|
||||
/**
|
||||
* Slf4j mdc 常量
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class MdcConstant {
|
||||
|
||||
public static final String TRACE_ID = "traceId";
|
||||
|
||||
public static final String IP = "ip";
|
||||
|
||||
public static final String USER = "user";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package im.zhaojun.zfile.core.constant;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* ZFile 常量
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
public class ZFileConstant {
|
||||
|
||||
public static final Character PATH_SEPARATOR_CHAR = '/';
|
||||
|
||||
public static final String PATH_SEPARATOR = "/";
|
||||
|
||||
/**
|
||||
* 最大支持文本文件大小为 ? KB 的文件内容.
|
||||
*/
|
||||
public static Long TEXT_MAX_FILE_SIZE_KB = 100L;
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setTextMaxFileSizeMb(@Value("${zfile.preview.text.maxFileSizeKb}") Long maxFileSizeKb) {
|
||||
ZFileConstant.TEXT_MAX_FILE_SIZE_KB = maxFileSizeKb;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package im.zhaojun.zfile.core.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import im.zhaojun.zfile.module.config.model.dto.SystemConfigDTO;
|
||||
import im.zhaojun.zfile.module.config.service.SystemConfigService;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 处理前端首页 Controller
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Controller
|
||||
public class FrontIndexController {
|
||||
|
||||
@Resource
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
|
||||
/**
|
||||
* 所有未找到的页面都跳转到首页, 用户解决 vue history 直接访问 404 的问题
|
||||
* 同时, 读取 index.html 文件, 修改 title 和 favicon 后返回.
|
||||
*
|
||||
* @return 转发到 /index.html
|
||||
*/
|
||||
@RequestMapping(value = {"/**/{[path:[^\\.]*}", "/"})
|
||||
@ResponseBody
|
||||
public String redirect() throws IOException {
|
||||
// 读取 resources/static/index.html 文件修改 title 和 favicon 后返回
|
||||
ClassPathResource resource = new ClassPathResource("static/index.html");
|
||||
InputStream inputStream = resource.getInputStream();
|
||||
String content = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
|
||||
|
||||
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
|
||||
String siteName = systemConfig.getSiteName();
|
||||
if (StrUtil.isNotBlank(siteName)) {
|
||||
content = content.replace("<title>ZFile</title>", "<title>" + siteName + "</title>");
|
||||
}
|
||||
|
||||
String faviconUrl = systemConfig.getFaviconUrl();
|
||||
if (StrUtil.isNotBlank(faviconUrl)) {
|
||||
content = content.replace("/favicon.svg", faviconUrl);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package im.zhaojun.zfile.core.controller;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ZipUtil;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiSort;
|
||||
import im.zhaojun.zfile.core.util.FileResponseUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 获取系统日志接口
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Api(tags = "日志")
|
||||
@ApiSort(8)
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/admin")
|
||||
public class LogController {
|
||||
|
||||
@Value("${zfile.log.path}")
|
||||
private String zfileLogPath;
|
||||
|
||||
@GetMapping("/log/download")
|
||||
@ApiOperation(value = "下载系统日志")
|
||||
public ResponseEntity<Resource> downloadLog() {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("下载诊断日志");
|
||||
}
|
||||
|
||||
File fileZip = ZipUtil.zip(zfileLogPath);
|
||||
String currentDate = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss");
|
||||
return FileResponseUtil.exportSingleThread(fileZip, "ZFile 诊断日志 - " + currentDate + ".zip");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 非法使用下载链接异常.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class IllegalDownloadLinkException extends ZFileRuntimeException {
|
||||
|
||||
public IllegalDownloadLinkException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 系统初始化异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class InstallSystemException extends ZFileRuntimeException {
|
||||
|
||||
public InstallSystemException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 无效的直链异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class InvalidShortLinkException extends ZFileRuntimeException {
|
||||
|
||||
public InvalidShortLinkException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 登陆验证码验证异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class LoginVerifyException extends ZFileRuntimeException {
|
||||
|
||||
public LoginVerifyException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
|
||||
/**
|
||||
* 密码校验失败异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class PasswordVerifyException extends RuntimeException {
|
||||
|
||||
private final Integer code;
|
||||
|
||||
public PasswordVerifyException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 文件预览异常类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class PreviewException extends ZFileRuntimeException {
|
||||
|
||||
public PreviewException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
import im.zhaojun.zfile.core.util.CodeMsg;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Service 层异常
|
||||
* 所有 message 均为系统日志打印输出, CodeMsg 中的消息才是返回给客户端的消息.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ServiceException extends RuntimeException {
|
||||
|
||||
private CodeMsg codeMsg;
|
||||
|
||||
public ServiceException(CodeMsg codeMsg) {
|
||||
this.codeMsg = codeMsg;
|
||||
}
|
||||
|
||||
public ServiceException(String message, CodeMsg codeMsg) {
|
||||
super(message);
|
||||
this.codeMsg = codeMsg;
|
||||
}
|
||||
|
||||
public ServiceException(String message, Throwable cause, CodeMsg codeMsg) {
|
||||
super(message, cause);
|
||||
this.codeMsg = codeMsg;
|
||||
}
|
||||
|
||||
public ServiceException(Throwable cause, CodeMsg codeMsg) {
|
||||
super(cause);
|
||||
this.codeMsg = codeMsg;
|
||||
}
|
||||
|
||||
public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, CodeMsg codeMsg) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
this.codeMsg = codeMsg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
import im.zhaojun.zfile.module.storage.model.param.IStorageParam;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 存储源自动设置 cors 异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class StorageSourceAutoConfigCorsException extends RuntimeException {
|
||||
|
||||
private final IStorageParam iStorageParam;
|
||||
|
||||
public StorageSourceAutoConfigCorsException(String message, Throwable cause, IStorageParam iStorageParam) {
|
||||
super(message, cause);
|
||||
this.iStorageParam = iStorageParam;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
import im.zhaojun.zfile.core.util.CodeMsg;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 存储源异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public class StorageSourceException extends ServiceException {
|
||||
|
||||
/**
|
||||
* 是否使用异常消息进行接口返回,如果是则取异常的 message, 否则取 CodeMsg 中的 message
|
||||
*/
|
||||
private boolean responseExceptionMessage;
|
||||
|
||||
/**
|
||||
* 存储源 ID
|
||||
*/
|
||||
private final Integer storageId;
|
||||
|
||||
public StorageSourceException(CodeMsg codeMsg, Integer storageId, String message) {
|
||||
super(message, codeMsg);
|
||||
this.storageId = storageId;
|
||||
}
|
||||
|
||||
public StorageSourceException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) {
|
||||
super(message, cause, codeMsg);
|
||||
this.storageId = storageId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 responseExceptionMessage 判断使用异常消息进行接口返回,如果是则取异常的 message, 否则取 CodeMsg 中的 message
|
||||
*
|
||||
* @return 异常消息
|
||||
*/
|
||||
public String getResultMessage() {
|
||||
return responseExceptionMessage ? super.getMessage() : super.getCodeMsg().getMsg();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置值是否使用异常消息进行接口返回
|
||||
*
|
||||
* @param responseExceptionMessage
|
||||
* 是否使用异常消息进行接口返回
|
||||
*
|
||||
* @return 当前对象
|
||||
*/
|
||||
public StorageSourceException setResponseExceptionMessage(boolean responseExceptionMessage) {
|
||||
this.responseExceptionMessage = responseExceptionMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 存储源不支持代理上传异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class StorageSourceNotSupportProxyUploadException extends ZFileRuntimeException {
|
||||
|
||||
public StorageSourceNotSupportProxyUploadException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class StorageSourceRefreshTokenException extends RuntimeException {
|
||||
|
||||
private final Integer storageId;
|
||||
|
||||
public StorageSourceRefreshTokenException(String message, Integer storageId) {
|
||||
super(message);
|
||||
this.storageId = storageId;
|
||||
}
|
||||
|
||||
public StorageSourceRefreshTokenException(String message, Throwable cause, Integer storageId) {
|
||||
super(message, cause);
|
||||
this.storageId = storageId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 文件解析异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class TextParseException extends ZFileRuntimeException {
|
||||
|
||||
public TextParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TextParseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class ZFileRetryException extends RuntimeException {
|
||||
|
||||
public ZFileRetryException() {
|
||||
}
|
||||
|
||||
public ZFileRetryException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ZFileRetryException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ZFileRetryException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ZFileRetryException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class ZFileRuntimeException extends RuntimeException {
|
||||
|
||||
public ZFileRuntimeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ZFileRuntimeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package im.zhaojun.zfile.core.exception.file;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.StorageSourceException;
|
||||
import im.zhaojun.zfile.core.util.CodeMsg;
|
||||
|
||||
/**
|
||||
* 无效的存储源异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class InvalidStorageSourceException extends StorageSourceException {
|
||||
|
||||
public InvalidStorageSourceException(String message) {
|
||||
super(CodeMsg.STORAGE_SOURCE_NOT_FOUND, null, message);
|
||||
}
|
||||
|
||||
public InvalidStorageSourceException(Integer storageId) {
|
||||
super(CodeMsg.STORAGE_SOURCE_NOT_FOUND, storageId, CodeMsg.STORAGE_SOURCE_NOT_FOUND.getMsg());
|
||||
}
|
||||
|
||||
public InvalidStorageSourceException(Integer storageId, String message) {
|
||||
super(CodeMsg.STORAGE_SOURCE_NOT_FOUND, storageId, message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package im.zhaojun.zfile.core.exception.file.init;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.StorageSourceException;
|
||||
import im.zhaojun.zfile.core.util.CodeMsg;
|
||||
|
||||
/**
|
||||
* 存储源初始化异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class InitializeStorageSourceException extends StorageSourceException {
|
||||
|
||||
public InitializeStorageSourceException(CodeMsg codeMsg, Integer storageId, String message) {
|
||||
super(codeMsg, storageId, message);
|
||||
}
|
||||
|
||||
public InitializeStorageSourceException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) {
|
||||
super(codeMsg, storageId, message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package im.zhaojun.zfile.core.exception.file.operator;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.StorageSourceException;
|
||||
import im.zhaojun.zfile.core.util.CodeMsg;
|
||||
|
||||
/**
|
||||
* 禁止服务器代理下载异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class DisableProxyDownloadException extends StorageSourceException {
|
||||
|
||||
public DisableProxyDownloadException(CodeMsg codeMsg, Integer storageId) {
|
||||
super(codeMsg, storageId, null);
|
||||
}
|
||||
|
||||
public DisableProxyDownloadException(CodeMsg codeMsg, Integer storageId, String message) {
|
||||
super(codeMsg, storageId, message);
|
||||
}
|
||||
|
||||
public DisableProxyDownloadException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) {
|
||||
super(codeMsg, storageId, message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package im.zhaojun.zfile.core.exception.file.operator;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.StorageSourceException;
|
||||
import im.zhaojun.zfile.core.util.CodeMsg;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 存储源文件操作异常
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class StorageSourceFileOperatorException extends StorageSourceException {
|
||||
|
||||
public StorageSourceFileOperatorException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) {
|
||||
super(codeMsg, storageId, message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
|
||||
package im.zhaojun.zfile.core.exception.http;
|
||||
|
||||
/**
|
||||
* Http 请求状态码异常 (返回状态码为 5xx 抛出此异常)
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class HttpResponseStatusErrorException extends RuntimeException {
|
||||
|
||||
public HttpResponseStatusErrorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,42 @@
|
||||
package im.zhaojun.zfile.filter;
|
||||
package im.zhaojun.zfile.core.filter;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.web.cors.CorsUtils;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.annotation.WebFilter;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 开启跨域支持. 一般用于开发环境, 或前后端分离部署时开启.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class CorsFilter extends GenericFilterBean {
|
||||
@WebFilter(urlPatterns = "/*")
|
||||
public class CorsFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
|
||||
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, httpServletRequest.getHeader(HttpHeaders.ORIGIN));
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Origin, X-Requested-With, Content-Type, Accept");
|
||||
|
||||
String header = httpServletRequest.getHeader(HttpHeaders.ORIGIN);
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, ObjectUtil.defaultIfNull(header, "*"));
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Origin, X-Requested-With, Content-Type, Accept, zfile-token, axios-request");
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS");
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "false");
|
||||
httpServletResponse.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "600");
|
||||
|
||||
if (!CorsUtils.isPreFlightRequest(httpServletRequest)) {
|
||||
chain.doFilter(httpServletRequest, httpServletResponse);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
41
src/main/java/im/zhaojun/zfile/core/filter/MDCFilter.java
Normal file
41
src/main/java/im/zhaojun/zfile/core/filter/MDCFilter.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package im.zhaojun.zfile.core.filter;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import im.zhaojun.zfile.core.constant.MdcConstant;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.annotation.WebFilter;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@WebFilter(urlPatterns = "/*")
|
||||
public class MDCFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
|
||||
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
|
||||
|
||||
MDC.put(MdcConstant.TRACE_ID, IdUtil.fastUUID());
|
||||
MDC.put(MdcConstant.IP, ServletUtil.getClientIP(httpServletRequest));
|
||||
MDC.put(MdcConstant.USER, StpUtil.isLogin() ? StpUtil.getLoginIdAsString() : "anonymous");
|
||||
|
||||
try {
|
||||
filterChain.doFilter(httpServletRequest, httpServletResponse);
|
||||
} finally {
|
||||
MDC.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package im.zhaojun.zfile.core.httpclient;
|
||||
|
||||
import im.zhaojun.zfile.core.httpclient.logging.HttpLoggingInterceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
|
||||
|
||||
/**
|
||||
* 自建 OkHTTP3 客户端工厂类, 增加日志输出拦截器.
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class ZFileOkHttp3ClientHttpRequestFactory extends OkHttp3ClientHttpRequestFactory {
|
||||
|
||||
public ZFileOkHttp3ClientHttpRequestFactory() {
|
||||
// 使用 OkHttp3 作为底层请求库, 并设置重试机制和日志拦截器
|
||||
super(new OkHttpClient()
|
||||
.newBuilder()
|
||||
.addNetworkInterceptor(new HttpLoggingInterceptor(HttpLoggingInterceptor.Logger.DEBUG)
|
||||
.setLevel(HttpLoggingInterceptor.Level.HEADERS))
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.zhaojun.zfile.core.httpclient.logging;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.Connection;
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okhttp3.internal.http.HttpHeaders;
|
||||
import okhttp3.internal.platform.Platform;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
import okio.GzipSource;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static okhttp3.internal.platform.Platform.INFO;
|
||||
|
||||
/**
|
||||
* 此类代码来源于 logging-interceptor.
|
||||
* <br>
|
||||
* An OkHttp interceptor which logs request and response information. Can be applied as an
|
||||
* {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain
|
||||
* OkHttpClient#networkInterceptors() network interceptor}. <p> The format of the logs created by
|
||||
* this class should not be considered stable and may change slightly between releases. If you need
|
||||
* a stable logging format, use your own interceptor.
|
||||
* <br>
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
public final class HttpLoggingInterceptor implements Interceptor {
|
||||
|
||||
private static final Charset UTF8 = StandardCharsets.UTF_8;
|
||||
|
||||
public enum Level {
|
||||
/** No logs. */
|
||||
NONE,
|
||||
/**
|
||||
* Logs request and response lines.
|
||||
*
|
||||
* <p>Example:
|
||||
* <pre>{@code
|
||||
* --> POST /greeting http/1.1 (3-byte body)
|
||||
*
|
||||
* <-- 200 OK (22ms, 6-byte body)
|
||||
* }</pre>
|
||||
*/
|
||||
BASIC,
|
||||
/**
|
||||
* Logs request and response lines and their respective headers.
|
||||
*
|
||||
* <p>Example:
|
||||
* <pre>{@code
|
||||
* --> POST /greeting http/1.1
|
||||
* Host: example.com
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 3
|
||||
* --> END POST
|
||||
*
|
||||
* <-- 200 OK (22ms)
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 6
|
||||
* <-- END HTTP
|
||||
* }</pre>
|
||||
*/
|
||||
HEADERS,
|
||||
/**
|
||||
* Logs request and response lines and their respective headers and bodies (if present).
|
||||
*
|
||||
* <p>Example:
|
||||
* <pre>{@code
|
||||
* --> POST /greeting http/1.1
|
||||
* Host: example.com
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 3
|
||||
*
|
||||
* Hi?
|
||||
* --> END POST
|
||||
*
|
||||
* <-- 200 OK (22ms)
|
||||
* Content-Type: plain/text
|
||||
* Content-Length: 6
|
||||
*
|
||||
* Hello!
|
||||
* <-- END HTTP
|
||||
* }</pre>
|
||||
*/
|
||||
BODY
|
||||
}
|
||||
|
||||
public interface Logger {
|
||||
void log(String message);
|
||||
|
||||
/** A {@link Logger} defaults output appropriate for the current platform. */
|
||||
Logger DEFAULT = message -> Platform.get().log(message, INFO, null);
|
||||
|
||||
Logger DEBUG = log::debug;
|
||||
Logger TRACE = log::trace;
|
||||
|
||||
}
|
||||
|
||||
public HttpLoggingInterceptor() {
|
||||
this(Logger.DEFAULT);
|
||||
}
|
||||
|
||||
public HttpLoggingInterceptor(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
private volatile Set<String> headersToRedact = Collections.emptySet();
|
||||
|
||||
public void redactHeader(String name) {
|
||||
Set<String> newHeadersToRedact = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
|
||||
newHeadersToRedact.addAll(headersToRedact);
|
||||
newHeadersToRedact.add(name);
|
||||
headersToRedact = newHeadersToRedact;
|
||||
}
|
||||
|
||||
private volatile Level level = Level.NONE;
|
||||
|
||||
/** Change the level at which this interceptor logs. */
|
||||
public HttpLoggingInterceptor setLevel(Level level) {
|
||||
if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead.");
|
||||
this.level = level;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Level getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
@Override public Response intercept(Chain chain) throws IOException {
|
||||
Level level = this.level;
|
||||
|
||||
Request request = chain.request();
|
||||
if (level == Level.NONE) {
|
||||
return chain.proceed(request);
|
||||
}
|
||||
|
||||
boolean logBody = level == Level.BODY;
|
||||
boolean logHeaders = logBody || level == Level.HEADERS;
|
||||
|
||||
RequestBody requestBody = request.body();
|
||||
boolean hasRequestBody = requestBody != null;
|
||||
|
||||
Connection connection = chain.connection();
|
||||
String requestStartMessage = "--> "
|
||||
+ request.method()
|
||||
+ ' ' + request.url()
|
||||
+ (connection != null ? " " + connection.protocol() : "");
|
||||
if (!logHeaders && hasRequestBody) {
|
||||
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
|
||||
}
|
||||
logger.log(requestStartMessage);
|
||||
|
||||
if (logHeaders) {
|
||||
if (hasRequestBody) {
|
||||
// Request body headers are only present when installed as a network interceptor. Force
|
||||
// them to be included (when available) so there values are known.
|
||||
if (requestBody.contentType() != null) {
|
||||
logger.log("Content-Type: " + requestBody.contentType());
|
||||
}
|
||||
if (requestBody.contentLength() != -1) {
|
||||
logger.log("Content-Length: " + requestBody.contentLength());
|
||||
}
|
||||
}
|
||||
|
||||
Headers headers = request.headers();
|
||||
for (int i = 0, count = headers.size(); i < count; i++) {
|
||||
String name = headers.name(i);
|
||||
// Skip headers from the request body as they are explicitly logged above.
|
||||
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
|
||||
logHeader(headers, i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!logBody || !hasRequestBody) {
|
||||
logger.log("--> END " + request.method());
|
||||
} else if (bodyHasUnknownEncoding(request.headers())) {
|
||||
logger.log("--> END " + request.method() + " (encoded body omitted)");
|
||||
} else if (requestBody.isDuplex()) {
|
||||
logger.log("--> END " + request.method() + " (duplex request body omitted)");
|
||||
} else {
|
||||
Buffer buffer = new Buffer();
|
||||
requestBody.writeTo(buffer);
|
||||
|
||||
Charset charset = UTF8;
|
||||
MediaType contentType = requestBody.contentType();
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(UTF8);
|
||||
}
|
||||
|
||||
logger.log("");
|
||||
if (isPlaintext(buffer)) {
|
||||
logger.log(buffer.readString(charset));
|
||||
logger.log("--> END " + request.method()
|
||||
+ " (" + requestBody.contentLength() + "-byte body)");
|
||||
} else {
|
||||
logger.log("--> END " + request.method() + " (binary "
|
||||
+ requestBody.contentLength() + "-byte body omitted)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long startNs = System.nanoTime();
|
||||
Response response;
|
||||
try {
|
||||
response = chain.proceed(request);
|
||||
} catch (Exception e) {
|
||||
logger.log("<-- HTTP FAILED: " + e);
|
||||
throw e;
|
||||
}
|
||||
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
|
||||
|
||||
ResponseBody responseBody = response.body();
|
||||
long contentLength = responseBody.contentLength();
|
||||
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
|
||||
logger.log("<-- "
|
||||
+ response.code()
|
||||
+ (response.message().isEmpty() ? "" : ' ' + response.message())
|
||||
+ ' ' + response.request().url()
|
||||
+ " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')');
|
||||
|
||||
if (logHeaders) {
|
||||
Headers headers = response.headers();
|
||||
for (int i = 0, count = headers.size(); i < count; i++) {
|
||||
logHeader(headers, i);
|
||||
}
|
||||
|
||||
if (!logBody || !HttpHeaders.hasBody(response)) {
|
||||
logger.log("<-- END HTTP");
|
||||
} else if (bodyHasUnknownEncoding(response.headers())) {
|
||||
logger.log("<-- END HTTP (encoded body omitted)");
|
||||
} else {
|
||||
BufferedSource source = responseBody.source();
|
||||
source.request(Long.MAX_VALUE); // Buffer the entire body.
|
||||
Buffer buffer = source.getBuffer();
|
||||
|
||||
Long gzippedLength = null;
|
||||
if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) {
|
||||
gzippedLength = buffer.size();
|
||||
try (GzipSource gzippedResponseBody = new GzipSource(buffer.clone())) {
|
||||
buffer = new Buffer();
|
||||
buffer.writeAll(gzippedResponseBody);
|
||||
}
|
||||
}
|
||||
|
||||
Charset charset = UTF8;
|
||||
MediaType contentType = responseBody.contentType();
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(UTF8);
|
||||
}
|
||||
|
||||
if (!isPlaintext(buffer)) {
|
||||
logger.log("");
|
||||
logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
|
||||
return response;
|
||||
}
|
||||
|
||||
if (contentLength != 0) {
|
||||
logger.log("");
|
||||
logger.log(buffer.clone().readString(charset));
|
||||
}
|
||||
|
||||
if (gzippedLength != null) {
|
||||
logger.log("<-- END HTTP (" + buffer.size() + "-byte, "
|
||||
+ gzippedLength + "-gzipped-byte body)");
|
||||
} else {
|
||||
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void logHeader(Headers headers, int i) {
|
||||
String value = headersToRedact.contains(headers.name(i)) ? "██" : headers.value(i);
|
||||
logger.log(headers.name(i) + ": " + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the body in question probably contains human readable text. Uses a small sample
|
||||
* of code points to detect unicode control characters commonly used in binary file signatures.
|
||||
*/
|
||||
static boolean isPlaintext(Buffer buffer) {
|
||||
try {
|
||||
Buffer prefix = new Buffer();
|
||||
long byteCount = buffer.size() < 64 ? buffer.size() : 64;
|
||||
buffer.copyTo(prefix, 0, byteCount);
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (prefix.exhausted()) {
|
||||
break;
|
||||
}
|
||||
int codePoint = prefix.readUtf8CodePoint();
|
||||
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (EOFException e) {
|
||||
return false; // Truncated UTF-8 sequence.
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean bodyHasUnknownEncoding(Headers headers) {
|
||||
String contentEncoding = headers.get("Content-Encoding");
|
||||
return contentEncoding != null
|
||||
&& !contentEncoding.equalsIgnoreCase("identity")
|
||||
&& !contentEncoding.equalsIgnoreCase("gzip");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package im.zhaojun.zfile.core.model.request;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
public class PageQueryRequest {
|
||||
|
||||
@ApiModelProperty(value="分页页数")
|
||||
private Integer page = 1;
|
||||
|
||||
@ApiModelProperty(value="每页条数")
|
||||
private Integer limit = 10;
|
||||
|
||||
@ApiModelProperty(value="排序字段")
|
||||
private String orderBy = "create_date";
|
||||
|
||||
@ApiModelProperty(value="排序顺序")
|
||||
private String orderDirection = "desc";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package im.zhaojun.zfile.core.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Service
|
||||
public class DynamicControllerManager {
|
||||
|
||||
@Resource
|
||||
private RequestMappingHandlerMapping requestMappingHandlerMapping;
|
||||
|
||||
private RequestMappingInfo shortLinkMappingInfo;
|
||||
|
||||
private Object shortLinkHandler;
|
||||
|
||||
private Method shortLinkMethod;
|
||||
|
||||
public void initDirectLinkPrefixPath(String path, Object handler, Method method) {
|
||||
if (shortLinkMappingInfo != null) {
|
||||
throw new RuntimeException("请勿重复初始化 DirectLinkPrefixPath.");
|
||||
}
|
||||
shortLinkMappingInfo = RequestMappingInfo.paths(path + "/{storageKey}/**").build();
|
||||
shortLinkHandler = handler;
|
||||
shortLinkMethod = method;
|
||||
requestMappingHandlerMapping.registerMapping(shortLinkMappingInfo, handler, method);
|
||||
}
|
||||
|
||||
public void changeDirectLinkPrefixPath(String path) {
|
||||
if (shortLinkMappingInfo != null) {
|
||||
requestMappingHandlerMapping.unregisterMapping(shortLinkMappingInfo);
|
||||
}
|
||||
shortLinkMappingInfo = RequestMappingInfo.paths(path + "/**").build();
|
||||
requestMappingHandlerMapping.registerMapping(shortLinkMappingInfo, shortLinkHandler, shortLinkMethod);
|
||||
}
|
||||
|
||||
}
|
||||
1
src/main/java/im/zhaojun/zfile/core/util/AjaxJson.java
Normal file
1
src/main/java/im/zhaojun/zfile/core/util/AjaxJson.java
Normal file
@@ -0,0 +1 @@
|
||||
package im.zhaojun.zfile.core.util;
|
||||
27
src/main/java/im/zhaojun/zfile/core/util/ClassUtils.java
Normal file
27
src/main/java/im/zhaojun/zfile/core/util/ClassUtils.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package im.zhaojun.zfile.core.util;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* Class & 反射相关工具类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class ClassUtils {
|
||||
|
||||
/**
|
||||
* 获取指定类的泛型类型, 只获取第一个泛型类型
|
||||
*
|
||||
* @param clazz
|
||||
* 泛型类
|
||||
*
|
||||
* @return 泛型类型
|
||||
*/
|
||||
public static Class<?> getClassFirstGenericsParam(Class<?> clazz) {
|
||||
Type genericSuperclass = clazz.getGenericSuperclass();
|
||||
Type actualTypeArgument = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
|
||||
return (Class<?>) actualTypeArgument;
|
||||
}
|
||||
|
||||
}
|
||||
76
src/main/java/im/zhaojun/zfile/core/util/CodeMsg.java
Normal file
76
src/main/java/im/zhaojun/zfile/core/util/CodeMsg.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package im.zhaojun.zfile.core.util;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
public class CodeMsg {
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
* <p>
|
||||
* 均为 5 位数, 如 00000, 10100, 20105 等.
|
||||
* <br>
|
||||
* 第一位表示错误类型, 4 为用户请求输入错误, 5 为服务端处理错误, 6 为警告信息
|
||||
* <br>
|
||||
* 第二位到第三位为二级类型
|
||||
* <br>
|
||||
* 第四位到第五位为具体错误代码, 根据业务场景自行定义
|
||||
* <p>
|
||||
* 以上三种类型均不允许重复, 且都需保持递增.
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
*/
|
||||
private String msg;
|
||||
|
||||
// 通用返回值
|
||||
public static CodeMsg SUCCESS = new CodeMsg("00000", "success");
|
||||
public static CodeMsg BAD_REQUEST = new CodeMsg("40000", "非法请求");
|
||||
public static CodeMsg ERROR = new CodeMsg("50000", "服务端异常");
|
||||
|
||||
|
||||
// -------------- 用户输入级错误 --------------
|
||||
public static CodeMsg REQUIRED_PASSWORD = new CodeMsg("40100", "请输入密码");
|
||||
public static CodeMsg PASSWORD_FAULT = new CodeMsg("40101", "密码输入错误");
|
||||
|
||||
public static CodeMsg STORAGE_SOURCE_NOT_FOUND = new CodeMsg("40102", "无效的或初始化失败的存储源");
|
||||
public static CodeMsg STORAGE_SOURCE_FORBIDDEN = new CodeMsg("40103", "无权访问存储源");
|
||||
public static CodeMsg STORAGE_SOURCE_FILE_FORBIDDEN = new CodeMsg("40104", "无权访问该目录");
|
||||
public static CodeMsg STORAGE_SOURCE_ILLEGAL_OPERATION = new CodeMsg("40105", "非法操作");
|
||||
|
||||
|
||||
|
||||
// -------------- 服务端处理错误 --------------
|
||||
|
||||
// 初始化相关错误
|
||||
public static CodeMsg STORAGE_SOURCE_INIT_FAIL = new CodeMsg("50100", "初始化存储源失败");
|
||||
public static CodeMsg STORAGE_SOURCE_INIT_STORAGE_CONFIG_FAIL = new CodeMsg("50101", "初始化存储源参数失败");
|
||||
public static CodeMsg STORAGE_SOURCE_INIT_STORAGE_PARAM_FIELD_FAIL = new CodeMsg("50102", "填充存储源字段失败");
|
||||
|
||||
|
||||
// 文件操作相关错误
|
||||
public static CodeMsg STORAGE_SOURCE_FILE_NEW_FOLDER_FAIL = new CodeMsg("50201", "新建文件夹失败");
|
||||
public static CodeMsg STORAGE_SOURCE_FILE_DELETE_FAIL = new CodeMsg("50202", "删除失败");
|
||||
public static CodeMsg STORAGE_SOURCE_FILE_RENAME_FAIL = new CodeMsg("50203", "重命名失败");
|
||||
public static CodeMsg STORAGE_SOURCE_FILE_GET_UPLOAD_FAIL = new CodeMsg("50204", "获取上传链接失败");
|
||||
public static CodeMsg STORAGE_SOURCE_FILE_PROXY_UPLOAD_FAIL = new CodeMsg("50205", "文件上传失败");
|
||||
public static CodeMsg STORAGE_SOURCE_FILE_PROXY_DOWNLOAD_FAIL = new CodeMsg("50206", "文件下载失败");
|
||||
public static CodeMsg STORAGE_SOURCE_FILE_GET_ITEM_FAIL = new CodeMsg("50207", "文件不存在或请求异常");
|
||||
public static CodeMsg STORAGE_SOURCE_FILE_DISABLE_PROXY_DOWNLOAD = new CodeMsg("50208", "非法操作, 当前文件不支持此类下载方式");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package im.zhaojun.zfile.core.util;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* 枚举转换工具类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class EnumConvertUtils {
|
||||
|
||||
|
||||
/**
|
||||
* 根据枚举 class 和值获取对应的枚举对象
|
||||
*
|
||||
* @param clazz
|
||||
* 枚举类 Class
|
||||
*
|
||||
* @param value
|
||||
* 枚举值
|
||||
*
|
||||
* @return 枚举对象
|
||||
*/
|
||||
public static Enum<?> convertStrToEnum(Class<?> clazz, Object value) {
|
||||
if (!ClassUtil.isEnum(clazz)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Field[] fields = ReflectUtil.getFields(clazz);
|
||||
for (Field field : fields) {
|
||||
boolean jsonValuePresent = field.isAnnotationPresent(JsonValue.class);
|
||||
boolean enumValuePresent = field.isAnnotationPresent(EnumValue.class);
|
||||
|
||||
if (jsonValuePresent || enumValuePresent) {
|
||||
Object[] enumConstants = clazz.getEnumConstants();
|
||||
|
||||
for (Object enumObj : enumConstants) {
|
||||
if (ObjectUtil.equal(value, ReflectUtil.getFieldValue(enumObj, field))) {
|
||||
return (Enum<?>) enumObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 转换枚举对象为字符串, 如果枚举对象没有定义 JsonValue 注解, 则使用 EnumValue 注解的值
|
||||
*
|
||||
* @param enumObj
|
||||
* 枚举对象
|
||||
*
|
||||
* @return 字符串
|
||||
*/
|
||||
public static String convertEnumToStr(Object enumObj) {
|
||||
Class<?> clazz = enumObj.getClass();
|
||||
if (!ClassUtil.isEnum(clazz)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Field[] fields = ReflectUtil.getFields(clazz);
|
||||
for (Field field : fields) {
|
||||
boolean jsonValuePresent = field.isAnnotationPresent(JsonValue.class);
|
||||
boolean enumValuePresent = field.isAnnotationPresent(EnumValue.class);
|
||||
|
||||
if (jsonValuePresent || enumValuePresent) {
|
||||
return Convert.toStr(ReflectUtil.getFieldValue(enumObj, field));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package im.zhaojun.zfile.util;
|
||||
package im.zhaojun.zfile.core.util;
|
||||
|
||||
import im.zhaojun.zfile.model.dto.FileItemDTO;
|
||||
import im.zhaojun.zfile.model.enums.FileTypeEnum;
|
||||
import cn.hutool.core.comparator.CompareUtil;
|
||||
import im.zhaojun.zfile.module.storage.model.result.FileItemResult;
|
||||
import im.zhaojun.zfile.module.storage.model.enums.FileTypeEnum;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
@@ -15,21 +16,31 @@ import java.util.Comparator;
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class FileComparator implements Comparator<FileItemDTO> {
|
||||
public class FileComparator implements Comparator<FileItemResult> {
|
||||
|
||||
private String sortBy;
|
||||
private String order;
|
||||
|
||||
public FileComparator() {
|
||||
}
|
||||
private String order;
|
||||
|
||||
public FileComparator(String sortBy, String order) {
|
||||
this.sortBy = sortBy;
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 比较两个文件的大小
|
||||
*
|
||||
* @param o1
|
||||
* 第一个文件
|
||||
*
|
||||
* @param o2
|
||||
* 第二个文件
|
||||
*
|
||||
* @return 比较结果
|
||||
*/
|
||||
@Override
|
||||
public int compare(FileItemDTO o1, FileItemDTO o2) {
|
||||
public int compare(FileItemResult o1, FileItemResult o2) {
|
||||
if (sortBy == null) {
|
||||
sortBy = "name";
|
||||
}
|
||||
@@ -43,8 +54,8 @@ public class FileComparator implements Comparator<FileItemDTO> {
|
||||
if (o1Type.equals(o2Type)) {
|
||||
int result;
|
||||
switch (sortBy) {
|
||||
case "time": result = o1.getTime().compareTo(o2.getTime()); break;
|
||||
case "size": result = o1.getSize().compareTo(o2.getSize()); break;
|
||||
case "time": result = CompareUtil.compare(o1.getTime(), o2.getTime()); break;
|
||||
case "size": result = CompareUtil.compare(o1.getSize(), o2.getSize()); break;
|
||||
default: result = naturalOrderComparator.compare(o1.getName(), o2.getName()); break;
|
||||
}
|
||||
return "asc".equals(order) ? result : -result;
|
||||
@@ -56,4 +67,5 @@ public class FileComparator implements Comparator<FileItemDTO> {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package im.zhaojun.zfile.core.util;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 将文件输出对象
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
public class FileResponseUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 文件下载,单线程,不支持断点续传
|
||||
*
|
||||
* @param file
|
||||
* 文件对象
|
||||
*
|
||||
* @param fileName
|
||||
* 要保存为的文件名
|
||||
*
|
||||
* @return 文件下载对象
|
||||
*/
|
||||
public static ResponseEntity<Resource> exportSingleThread(File file, String fileName) {
|
||||
if (!file.exists()) {
|
||||
ByteArrayResource byteArrayResource = new ByteArrayResource("文件不存在或异常,请联系管理员.".getBytes(StandardCharsets.UTF_8));
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.body(byteArrayResource);
|
||||
}
|
||||
|
||||
MediaType mediaType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
if (StrUtil.isEmpty(fileName)) {
|
||||
fileName = file.getName();
|
||||
}
|
||||
|
||||
headers.setContentDispositionFormData("attachment", StringUtils.encodeAllIgnoreSlashes(fileName));
|
||||
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.headers(headers)
|
||||
.contentLength(file.length())
|
||||
.contentType(mediaType)
|
||||
.body(new InputStreamResource(FileUtil.getInputStream(file)));
|
||||
}
|
||||
|
||||
}
|
||||
71
src/main/java/im/zhaojun/zfile/core/util/HttpUtil.java
Normal file
71
src/main/java/im/zhaojun/zfile/core/util/HttpUtil.java
Normal file
@@ -0,0 +1,71 @@
|
||||
package im.zhaojun.zfile.core.util;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import im.zhaojun.zfile.core.constant.ZFileConstant;
|
||||
import im.zhaojun.zfile.core.exception.PreviewException;
|
||||
import im.zhaojun.zfile.core.exception.TextParseException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
|
||||
/**
|
||||
* 网络相关工具
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
public class HttpUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 获取 URL 对应的文件内容
|
||||
*
|
||||
* @param url
|
||||
* 文件 URL
|
||||
*
|
||||
* @return 文件内容
|
||||
*/
|
||||
public static String getTextContent(String url) {
|
||||
long maxFileSize = 1024 * ZFileConstant.TEXT_MAX_FILE_SIZE_KB;
|
||||
|
||||
if (getRemoteFileSize(url) > maxFileSize) {
|
||||
throw new PreviewException("预览文件超出大小, 最大支持 " + FileUtil.readableFileSize(maxFileSize));
|
||||
}
|
||||
|
||||
String result;
|
||||
try {
|
||||
result = cn.hutool.http.HttpUtil.get(url);
|
||||
} catch (Exception e) {
|
||||
throw new TextParseException(StrUtil.format("获取文件内容失败, URL: {}", url), e);
|
||||
}
|
||||
|
||||
return result == null ? "" : result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取远程文件大小
|
||||
*
|
||||
* @param url
|
||||
* 文件 URL
|
||||
*
|
||||
* @return 文件大小
|
||||
*/
|
||||
public static Long getRemoteFileSize(String url) {
|
||||
long size = 0;
|
||||
URL urlObject;
|
||||
try {
|
||||
urlObject = new URL(url);
|
||||
URLConnection conn = urlObject.openConnection();
|
||||
size = conn.getContentLength();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package im.zhaojun.zfile.util;
|
||||
package im.zhaojun.zfile.core.util;
|
||||
/*
|
||||
NaturalOrderComparator.java -- Perform 'natural order' comparisons of strings in Java.
|
||||
Copyright (C) 2003 by Pierre-Luc Paour <natorder@paour.com>
|
||||
@@ -26,6 +26,8 @@ package im.zhaojun.zfile.util;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* 类 windows 文件排序算法
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class NaturalOrderComparator implements Comparator<String> {
|
||||
@@ -147,4 +149,4 @@ public class NaturalOrderComparator implements Comparator<String> {
|
||||
return a.length() - b.length();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package im.zhaojun.zfile.core.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import im.zhaojun.zfile.core.constant.ZFileConstant;
|
||||
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 规则表达式工具类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class PatternMatcherUtils {
|
||||
|
||||
private static final Map<String, PathMatcher> PATH_MATCHER_MAP = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 兼容模式的 glob 表达式匹配.
|
||||
* 默认的 glob 表达式是不支持以下情况的:<br>
|
||||
* <ul>
|
||||
* <li>pattern: /a/**</li>
|
||||
* <li>test1: /a</li>
|
||||
* <li>test2: /a/</li>
|
||||
* <ul>
|
||||
* <p>test1 和 test 2 均无法匹配这种情况, 此方法兼容了这种情况, 即对 test 内容后拼接 "/xx", 使其可以匹配上 pattern.
|
||||
* <p><strong>注意:</strong>但此方法对包含文件名的情况无效, 仅支持 test 为 路径的情况.
|
||||
*
|
||||
* @param pattern
|
||||
* glob 规则表达式
|
||||
*
|
||||
* @param test
|
||||
* 匹配内容
|
||||
*
|
||||
* @return 是否匹配.
|
||||
*/
|
||||
public static boolean testCompatibilityGlobPattern(String pattern, String test) {
|
||||
// 如果规则表达式最开始没有 /, 则兼容在最前方加上 /.
|
||||
if (!StrUtil.startWith(pattern, ZFileConstant.PATH_SEPARATOR)) {
|
||||
pattern = ZFileConstant.PATH_SEPARATOR + pattern;
|
||||
}
|
||||
|
||||
// 兼容性处理.
|
||||
test = StringUtils.concat(test, ZFileConstant.PATH_SEPARATOR);
|
||||
if (StrUtil.endWith(pattern, "/**")) {
|
||||
test += "xxx";
|
||||
}
|
||||
return testGlobPattern(pattern, test);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 测试密码规则表达式和文件路径是否匹配
|
||||
*
|
||||
* @param pattern
|
||||
* glob 规则表达式
|
||||
*
|
||||
* @param test
|
||||
* 测试字符串
|
||||
*/
|
||||
private static boolean testGlobPattern(String pattern, String test) {
|
||||
// 从缓存取出 PathMatcher, 防止重复初始化
|
||||
PathMatcher pathMatcher = PATH_MATCHER_MAP.getOrDefault(pattern, FileSystems.getDefault().getPathMatcher("glob:" + pattern));
|
||||
PATH_MATCHER_MAP.put(pattern, pathMatcher);
|
||||
|
||||
return pathMatcher.matches(Paths.get(test)) || StrUtil.equals(pattern, test);
|
||||
}
|
||||
|
||||
}
|
||||
146
src/main/java/im/zhaojun/zfile/core/util/PlaceholderUtils.java
Normal file
146
src/main/java/im/zhaojun/zfile/core/util/PlaceholderUtils.java
Normal file
@@ -0,0 +1,146 @@
|
||||
package im.zhaojun.zfile.core.util;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 配置文件或模板中的占位符替换工具类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
public class PlaceholderUtils {
|
||||
|
||||
/**
|
||||
* Prefix for system property placeholders: "${"
|
||||
*/
|
||||
public static final String PLACEHOLDER_PREFIX = "${";
|
||||
/**
|
||||
* Suffix for system property placeholders: "}"
|
||||
*/
|
||||
public static final String PLACEHOLDER_SUFFIX = "}";
|
||||
|
||||
|
||||
/**
|
||||
* 解析占位符, 将指定的占位符替换为指定的值. 变量值从 Spring 环境中获取, 如没取到, 则默认为空.
|
||||
*
|
||||
* 必须在 Spring 环境下使用, 否则会抛出异常.
|
||||
*
|
||||
*
|
||||
* @param formatStr
|
||||
* 模板字符串
|
||||
*
|
||||
* @return 替换后的字符串
|
||||
*/
|
||||
public static String resolvePlaceholdersBySpringProperties(String formatStr) {
|
||||
String placeholderName = getFirstPlaceholderName(formatStr);
|
||||
if (StrUtil.isEmpty(placeholderName)) {
|
||||
return formatStr;
|
||||
}
|
||||
|
||||
String propertyValue = SpringUtil.getProperty(placeholderName);
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put(placeholderName, propertyValue);
|
||||
return resolvePlaceholders(formatStr, map);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析占位符, 将指定的占位符替换为指定的值.
|
||||
*
|
||||
* @param formatStr
|
||||
* 模板字符串
|
||||
*
|
||||
* @param parameter
|
||||
* 参数列表
|
||||
*
|
||||
* @return 替换后的字符串
|
||||
*/
|
||||
public static String resolvePlaceholders(String formatStr, Map<String, String> parameter) {
|
||||
if (parameter == null || parameter.isEmpty()) {
|
||||
return formatStr;
|
||||
}
|
||||
StringBuffer buf = new StringBuffer(formatStr);
|
||||
int startIndex = buf.indexOf(PLACEHOLDER_PREFIX);
|
||||
while (startIndex != -1) {
|
||||
int endIndex = buf.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length());
|
||||
if (endIndex != -1) {
|
||||
String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
|
||||
int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length();
|
||||
try {
|
||||
String propVal = parameter.get(placeholder);
|
||||
if (propVal != null) {
|
||||
buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal);
|
||||
nextIndex = startIndex + propVal.length();
|
||||
} else {
|
||||
log.warn("Could not resolve placeholder '{}' in [{}] ", placeholder, formatStr);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.error("Could not resolve placeholder '{}' in [{}]: ", placeholder, formatStr, ex);
|
||||
}
|
||||
startIndex = buf.indexOf(PLACEHOLDER_PREFIX, nextIndex);
|
||||
} else {
|
||||
startIndex = -1;
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取模板字符串第一个占位符的名称, 如 "我的名字是: ${name}, 我的年龄是: ${age}", 返回 "name".
|
||||
*
|
||||
* @param formatStr
|
||||
* 模板字符串
|
||||
*
|
||||
* @return 占位符名称
|
||||
*/
|
||||
public static String getFirstPlaceholderName(String formatStr) {
|
||||
List<String> list = getPlaceholderNames(formatStr);
|
||||
if (CollUtil.isNotEmpty(list)) {
|
||||
return list.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取模板字符串第一个占位符的名称, 如 "我的名字是: ${name}, 我的年龄是: ${age}", 返回 ["name", "age].
|
||||
*
|
||||
* @param formatStr
|
||||
* 模板字符串
|
||||
*
|
||||
* @return 占位符名称
|
||||
*/
|
||||
public static List<String> getPlaceholderNames(String formatStr) {
|
||||
if (StrUtil.isEmpty(formatStr)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> placeholderNameList = new ArrayList<>();
|
||||
|
||||
StringBuffer buf = new StringBuffer(formatStr);
|
||||
int startIndex = buf.indexOf(PLACEHOLDER_PREFIX);
|
||||
while (startIndex != -1) {
|
||||
int endIndex = buf.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length());
|
||||
if (endIndex != -1) {
|
||||
String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
|
||||
int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length();
|
||||
startIndex = buf.indexOf(PLACEHOLDER_PREFIX, nextIndex);
|
||||
placeholderNameList.add(placeholder);
|
||||
} else {
|
||||
startIndex = -1;
|
||||
}
|
||||
}
|
||||
return placeholderNameList;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package im.zhaojun.zfile.core.util;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.HexUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
import cn.hutool.crypto.symmetric.SymmetricCrypto;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import im.zhaojun.zfile.module.config.service.SystemConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 代理下载链接工具类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
public class ProxyDownloadUrlUtils {
|
||||
|
||||
private static SystemConfigService systemConfigService;
|
||||
|
||||
private static final String PROXY_DOWNLOAD_LINK_DELIMITER= ":";
|
||||
|
||||
|
||||
/**
|
||||
* 服务器代理下载 URL 有效期 (分钟).
|
||||
*/
|
||||
public static final Integer PROXY_DOWNLOAD_LINK_EFFECTIVE_SECOND = 1800;
|
||||
|
||||
public static String generatorSignature(Integer storageId, String pathAndName, Integer effectiveSecond) {
|
||||
if (systemConfigService == null) {
|
||||
systemConfigService = SpringUtil.getBean(SystemConfigService.class);
|
||||
}
|
||||
|
||||
// 如果有效时间为空, 则设置 30 分钟过期
|
||||
if (effectiveSecond == null || effectiveSecond < 1) {
|
||||
effectiveSecond = PROXY_DOWNLOAD_LINK_EFFECTIVE_SECOND;
|
||||
}
|
||||
|
||||
// 过期时间的秒数
|
||||
long second = DateUtil.offsetSecond(DateUtil.date(), effectiveSecond).getTime();
|
||||
String content = storageId + PROXY_DOWNLOAD_LINK_DELIMITER + pathAndName + PROXY_DOWNLOAD_LINK_DELIMITER + second;
|
||||
|
||||
String rsaHexKey = systemConfigService.getRsaHexKeyOrGenerate();
|
||||
byte[] key = HexUtil.decodeHex(rsaHexKey);
|
||||
//构建
|
||||
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
|
||||
|
||||
//加密
|
||||
return aes.encryptHex(content);
|
||||
}
|
||||
|
||||
|
||||
public static boolean validSignatureExpired(Integer expectedStorageId, String expectedPathAndName, String signature) {
|
||||
if (systemConfigService == null) {
|
||||
systemConfigService = SpringUtil.getBean(SystemConfigService.class);
|
||||
}
|
||||
|
||||
String rsaHexKey = systemConfigService.getRsaHexKeyOrGenerate();
|
||||
byte[] key = HexUtil.decodeHex(rsaHexKey);
|
||||
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES, key);
|
||||
|
||||
long currentTimeMillis = System.currentTimeMillis();
|
||||
|
||||
String storageId = null;
|
||||
String pathAndName = null;
|
||||
String expiredSecond = null;
|
||||
|
||||
try {
|
||||
//解密
|
||||
String decryptStr = aes.decryptStr(signature);
|
||||
List<String> split = StrUtil.split(decryptStr, PROXY_DOWNLOAD_LINK_DELIMITER);
|
||||
storageId = split.get(0);
|
||||
pathAndName = split.get(1);
|
||||
expiredSecond = split.get(2);
|
||||
|
||||
// 校验存储源 ID 和文件路径及是否过期.
|
||||
if (StrUtil.equals(storageId, Convert.toStr(expectedStorageId))
|
||||
&& StrUtil.equals(StringUtils.concat(pathAndName), StringUtils.concat(expectedPathAndName))
|
||||
&& currentTimeMillis < Convert.toLong(expiredSecond)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
log.warn("校验链接已过期或不匹配, signature: {}, storageId={}, pathAndName={}, expiredSecond={}, now:={}", signature, storageId, pathAndName, expiredSecond, currentTimeMillis);
|
||||
} catch (Exception e) {
|
||||
log.error("校验签名链接异常, signature: {}, storageId={}, pathAndName={}, expiredSecond={}, now:={}", signature, storageId, pathAndName, expiredSecond, currentTimeMillis);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
72
src/main/java/im/zhaojun/zfile/core/util/RequestHolder.java
Normal file
72
src/main/java/im/zhaojun/zfile/core/util/RequestHolder.java
Normal file
@@ -0,0 +1,72 @@
|
||||
package im.zhaojun.zfile.core.util;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 获取 Request 工具类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class RequestHolder {
|
||||
|
||||
/**
|
||||
* 获取 HttpServletRequest
|
||||
*
|
||||
* @return HttpServletRequest
|
||||
*/
|
||||
public static HttpServletRequest getRequest(){
|
||||
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 HttpServletResponse
|
||||
*
|
||||
* @return HttpServletResponse
|
||||
*/
|
||||
public static HttpServletResponse getResponse(){
|
||||
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 向 response 写入文件流.
|
||||
*
|
||||
* @param function
|
||||
* 文件输入流获取函数
|
||||
*
|
||||
* @param path
|
||||
* 文件路径
|
||||
*/
|
||||
public static void writeFile(Function<String, InputStream> function, String path){
|
||||
try (InputStream inputStream = function.apply(path)) {
|
||||
HttpServletResponse response = RequestHolder.getResponse();
|
||||
String fileName = FileUtil.getName(path);
|
||||
|
||||
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + StringUtils.encodeAllIgnoreSlashes(fileName));
|
||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
|
||||
IoUtil.copy(inputStream, outputStream);
|
||||
response.flushBuffer();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
51
src/main/java/im/zhaojun/zfile/core/util/SizeToStrUtils.java
Normal file
51
src/main/java/im/zhaojun/zfile/core/util/SizeToStrUtils.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package im.zhaojun.zfile.core.util;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
|
||||
/**
|
||||
* 文件大小或带宽大小转可读单位
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class SizeToStrUtils {
|
||||
|
||||
/**
|
||||
* 将文件大小转换为可读单位
|
||||
*
|
||||
* @param bytes
|
||||
* 字节数
|
||||
*
|
||||
* @return 文件大小可读单位
|
||||
*/
|
||||
public static String bytesToSize(long bytes) {
|
||||
if (bytes == 0) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
double k = 1024;
|
||||
String[] sizes = new String[]{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
|
||||
double i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return NumberUtil.round(bytes / Math.pow(k, i), 3) + " " + sizes[(int) i];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将带宽大小转换为可读单位
|
||||
*
|
||||
* @param bps
|
||||
* 字节数
|
||||
*
|
||||
* @return 带宽大小可读单位
|
||||
*/
|
||||
public static String bpsToSize(long bps) {
|
||||
if (bps == 0) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
double k = 1000;
|
||||
String[] sizes = new String[]{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
|
||||
double i = Math.floor(Math.log(bps) / Math.log(k));
|
||||
return NumberUtil.round(bps / Math.pow(k, i), 3) + " " + sizes[(int) i];
|
||||
}
|
||||
|
||||
}
|
||||
404
src/main/java/im/zhaojun/zfile/core/util/StringUtils.java
Normal file
404
src/main/java/im/zhaojun/zfile/core/util/StringUtils.java
Normal file
@@ -0,0 +1,404 @@
|
||||
package im.zhaojun.zfile.core.util;
|
||||
|
||||
import cn.hutool.core.net.URLEncodeUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import im.zhaojun.zfile.module.config.service.SystemConfigService;
|
||||
import im.zhaojun.zfile.core.constant.ZFileConstant;
|
||||
import im.zhaojun.zfile.module.config.model.dto.SystemConfigDTO;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 字符串相关工具类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class StringUtils {
|
||||
|
||||
public static final char DELIMITER = '/';
|
||||
|
||||
public static final String DELIMITER_STR = "/";
|
||||
|
||||
public static final String HTTP = "http";
|
||||
|
||||
public static final String HTTP_PROTOCOL = "http://";
|
||||
|
||||
public static final String HTTPS_PROTOCOL = "https://";
|
||||
|
||||
|
||||
/**
|
||||
* 移除 URL 中的前后的所有 '/'
|
||||
*
|
||||
* @param path
|
||||
* 路径
|
||||
*
|
||||
* @return 如 path = '/folder1/file1/', 返回 'folder1/file1'
|
||||
* 如 path = '///folder1/file1//', 返回 'folder1/file1'
|
||||
*/
|
||||
public static String trimSlashes(String path) {
|
||||
path = trimStartSlashes(path);
|
||||
path = trimEndSlashes(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 移除 URL 中的第一个 '/'
|
||||
*
|
||||
* @param path
|
||||
* 路径
|
||||
*
|
||||
* @return 如 path = '/folder1/file1', 返回 'folder1/file1'
|
||||
* 如 path = '/folder1/file1', 返回 'folder1/file1'
|
||||
*
|
||||
*/
|
||||
public static String trimStartSlashes(String path) {
|
||||
if (StrUtil.isEmpty(path)) {
|
||||
return path;
|
||||
}
|
||||
|
||||
while (path.startsWith(DELIMITER_STR)) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 移除 URL 中的最后一个 '/'
|
||||
*
|
||||
* @param path
|
||||
* 路径
|
||||
*
|
||||
* @return 如 path = '/folder1/file1/', 返回 '/folder1/file1'
|
||||
* 如 path = '/folder1/file1///', 返回 '/folder1/file1'
|
||||
*/
|
||||
public static String trimEndSlashes(String path) {
|
||||
if (StrUtil.isEmpty(path)) {
|
||||
return path;
|
||||
}
|
||||
|
||||
while (path.endsWith(DELIMITER_STR)) {
|
||||
path = path.substring(0, path.length() - 1);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 去除路径中所有重复的 '/'
|
||||
*
|
||||
* @param path
|
||||
* 路径
|
||||
*
|
||||
* @return 如 path = '/folder1//file1/', 返回 '/folder1/file1/'
|
||||
* 如 path = '/folder1////file1///', 返回 '/folder1/file1/'
|
||||
*/
|
||||
public static String removeDuplicateSlashes(String path) {
|
||||
if (StrUtil.isEmpty(path)) {
|
||||
return path;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// 是否包含 http 或 https 协议信息
|
||||
boolean containProtocol = StrUtil.containsAnyIgnoreCase(path, HTTP_PROTOCOL, HTTPS_PROTOCOL);
|
||||
|
||||
if (containProtocol) {
|
||||
path = trimStartSlashes(path);
|
||||
}
|
||||
|
||||
// 是否包含 http 协议信息
|
||||
boolean startWithHttpProtocol = StrUtil.startWithIgnoreCase(path, HTTP_PROTOCOL);
|
||||
// 是否包含 https 协议信息
|
||||
boolean startWithHttpsProtocol = StrUtil.startWithIgnoreCase(path, HTTPS_PROTOCOL);
|
||||
|
||||
if (startWithHttpProtocol) {
|
||||
sb.append(HTTP_PROTOCOL);
|
||||
} else if (startWithHttpsProtocol) {
|
||||
sb.append(HTTPS_PROTOCOL);
|
||||
}
|
||||
|
||||
for (int i = sb.length(); i < path.length() - 1; i++) {
|
||||
char current = path.charAt(i);
|
||||
char next = path.charAt(i + 1);
|
||||
if (!(current == DELIMITER && next == DELIMITER)) {
|
||||
sb.append(current);
|
||||
}
|
||||
}
|
||||
sb.append(path.charAt(path.length() - 1));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 去除路径中所有重复的 '/', 并且去除开头的 '/'
|
||||
*
|
||||
* @param path
|
||||
* 路径
|
||||
*
|
||||
* @return 如 path = '/folder1//file1/', 返回 'folder1/file1/'
|
||||
* 如 path = '///folder1////file1///', 返回 'folder1/file1/'
|
||||
*/
|
||||
public static String removeDuplicateSlashesAndTrimStart(String path) {
|
||||
path = removeDuplicateSlashes(path);
|
||||
path = trimStartSlashes(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 去除路径中所有重复的 '/', 并且去除结尾的 '/'
|
||||
*
|
||||
* @param path
|
||||
* 路径
|
||||
*
|
||||
* @return 如 path = '/folder1//file1/', 返回 '/folder1/file1'
|
||||
* 如 path = '///folder1////file1///', 返回 '/folder1/file1'
|
||||
*/
|
||||
public static String removeDuplicateSlashesAndTrimEnd(String path) {
|
||||
path = removeDuplicateSlashes(path);
|
||||
path = trimEndSlashes(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 拼接 URL,并去除重复的分隔符 '/',并去除开头的 '/', 但不会影响 http:// 和 https:// 这种头部.
|
||||
*
|
||||
* @param strs
|
||||
* 拼接的字符数组
|
||||
*
|
||||
* @return 拼接结果
|
||||
*/
|
||||
public static String concatTrimStartSlashes(String... strs) {
|
||||
return trimStartSlashes(concat(strs));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 拼接 URL,并去除重复的分隔符 '/',并去除结尾的 '/', 但不会影响 http:// 和 https:// 这种头部.
|
||||
*
|
||||
* @param strs
|
||||
* 拼接的字符数组
|
||||
*
|
||||
* @return 拼接结果
|
||||
*/
|
||||
public static String concatTrimEndSlashes(String... strs) {
|
||||
return trimEndSlashes(concat(strs));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 拼接 URL,并去除重复的分隔符 '/',并去除开头和结尾的 '/', 但不会影响 http:// 和 https:// 这种头部.
|
||||
*
|
||||
* @param strs
|
||||
* 拼接的字符数组
|
||||
*
|
||||
* @return 拼接结果
|
||||
*/
|
||||
public static String concatTrimSlashes(String... strs) {
|
||||
return trimSlashes(concat(strs));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 拼接 URL,并去除重复的分隔符 '/',但不会影响 http:// 和 https:// 这种头部.
|
||||
*
|
||||
* @param strs
|
||||
* 拼接的字符数组
|
||||
*
|
||||
* @return 拼接结果
|
||||
*/
|
||||
public static String concat(String... strs) {
|
||||
StringBuilder sb = new StringBuilder(DELIMITER_STR);
|
||||
for (int i = 0; i < strs.length; i++) {
|
||||
String str = strs[i];
|
||||
if (StrUtil.isEmpty(str)) {
|
||||
continue;
|
||||
}
|
||||
sb.append(str);
|
||||
if (i != strs.length - 1) {
|
||||
sb.append(DELIMITER);
|
||||
}
|
||||
}
|
||||
return removeDuplicateSlashes(sb.toString());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 拼接 URL,并去除重复的分隔符 '/',但不会影响 http:// 和 https:// 这种头部.
|
||||
*
|
||||
* @param encodeAllIgnoreSlashes
|
||||
* 是否 encode 编码 (忽略 /)
|
||||
*
|
||||
* @param strs
|
||||
* 拼接的字符数组
|
||||
*
|
||||
* @return 拼接结果
|
||||
*/
|
||||
public static String concat(boolean encodeAllIgnoreSlashes, String... strs) {
|
||||
StringBuilder sb = new StringBuilder(DELIMITER_STR);
|
||||
for (int i = 0; i < strs.length; i++) {
|
||||
String str = strs[i];
|
||||
if (StrUtil.isEmpty(str)) {
|
||||
continue;
|
||||
}
|
||||
sb.append(str);
|
||||
if (i != strs.length - 1) {
|
||||
sb.append(DELIMITER);
|
||||
}
|
||||
}
|
||||
if (encodeAllIgnoreSlashes) {
|
||||
return encodeAllIgnoreSlashes(removeDuplicateSlashes(sb.toString()));
|
||||
} else {
|
||||
return removeDuplicateSlashes(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 拼接文件直链生成 URL
|
||||
*
|
||||
* @param storageKey
|
||||
* 存储源 ID
|
||||
*
|
||||
* @param fullPath
|
||||
* 文件全路径
|
||||
*
|
||||
* @return 生成结果
|
||||
*/
|
||||
public static String generatorPathLink(String storageKey, String fullPath) {
|
||||
SystemConfigService systemConfigService = SpringUtil.getBean(SystemConfigService.class);
|
||||
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
|
||||
String domain = systemConfig.getDomain();
|
||||
String directLinkPrefix = systemConfig.getDirectLinkPrefix();
|
||||
return concat(domain, directLinkPrefix, storageKey, encodeAllIgnoreSlashes(fullPath));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 替换 URL 中的 Host 部分,如替换 http://a.com/1.txt 为 https://abc.com/1.txt
|
||||
*
|
||||
* @param originUrl
|
||||
* 原 URL
|
||||
*
|
||||
* @param replaceHost
|
||||
* 替换的 HOST
|
||||
*
|
||||
* @return 替换后的 URL
|
||||
*/
|
||||
public static String replaceHost(String originUrl, String replaceHost) {
|
||||
try {
|
||||
String path = new URL(originUrl).getFile();
|
||||
return concat(replaceHost, path);
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 编码 URL,默认使用 UTF-8 编码
|
||||
* URL 的 Fragment URLEncoder
|
||||
* 默认的编码器针对Fragment,定义如下:
|
||||
*
|
||||
* <pre>
|
||||
* fragment = *( pchar / "/" / "?" )
|
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
||||
* </pre>
|
||||
*
|
||||
* 具体见:https://datatracker.ietf.org/doc/html/rfc3986#section-3.5
|
||||
*
|
||||
* @param url
|
||||
* 被编码内容
|
||||
*
|
||||
* @return 编码后的字符
|
||||
*/
|
||||
public static String encode(String url) {
|
||||
return URLEncodeUtil.encodeFragment(url);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 编码全部字符
|
||||
*
|
||||
* @param str
|
||||
* 被编码内容
|
||||
*
|
||||
* @return 编码后的字符
|
||||
*/
|
||||
public static String encodeAllIgnoreSlashes(String str) {
|
||||
if (StrUtil.isEmpty(str)) {
|
||||
return str;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
int prevIndex = -1;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char c = str.charAt(i);
|
||||
if (c == ZFileConstant.PATH_SEPARATOR_CHAR) {
|
||||
if (prevIndex < i) {
|
||||
String substring = str.substring(prevIndex + 1, i);
|
||||
sb.append(URLEncodeUtil.encodeAll(substring));
|
||||
prevIndex = i;
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
|
||||
if (i == str.length() - 1 && prevIndex < i) {
|
||||
String substring = str.substring(prevIndex + 1, i + 1);
|
||||
sb.append(URLEncodeUtil.encodeAll(substring));
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解码 URL, 默认使用 UTF8 编码. 不会将 + 转为空格.
|
||||
*
|
||||
* @param url
|
||||
* 被解码内容
|
||||
*
|
||||
* @return 解码后的内容
|
||||
*/
|
||||
public static String decode(String url) {
|
||||
return URLUtil.decode(url, StandardCharsets.UTF_8, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取路径的上级目录, 如最后为 /, 则也会认为是一级目录
|
||||
*
|
||||
* @param path
|
||||
* 文件路径
|
||||
*
|
||||
* @return 父级目录
|
||||
*/
|
||||
public static String getParentPath(String path) {
|
||||
int toIndex = StrUtil.lastIndexOfIgnoreCase(path, ZFileConstant.PATH_SEPARATOR);
|
||||
if (toIndex <= 0) {
|
||||
return "/";
|
||||
} else {
|
||||
return StrUtil.sub(path, 0, toIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public static String removeAllLineBreaksAndTrim(String str) {
|
||||
String removeResult = StrUtil.removeAllLineBreaks(str);
|
||||
return StrUtil.trim(removeResult);
|
||||
}
|
||||
|
||||
}
|
||||
34
src/main/java/im/zhaojun/zfile/core/util/UrlUtils.java
Normal file
34
src/main/java/im/zhaojun/zfile/core/util/UrlUtils.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package im.zhaojun.zfile.core.util;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* url 相关工具类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class UrlUtils {
|
||||
|
||||
/**
|
||||
* 给 url 拼接参数
|
||||
*
|
||||
* @param url
|
||||
* 原始 URL
|
||||
*
|
||||
* @param name
|
||||
* 参数名称
|
||||
*
|
||||
* @param value
|
||||
* 参数值
|
||||
*
|
||||
* @return 拼接后的 URL
|
||||
*/
|
||||
public static String concatQueryParam(String url, String name, String value) {
|
||||
if (StrUtil.contains(url, "?")) {
|
||||
return url + "&" + name + "=" + value;
|
||||
} else {
|
||||
return url + "?" + name + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package im.zhaojun.zfile.core.validation;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* 字符串列表值校验注解
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Documented
|
||||
@Constraint(validatedBy = { StringListValueConstraintValidator.class })
|
||||
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RUNTIME)
|
||||
public @interface StringListValue {
|
||||
|
||||
String message() default "";
|
||||
|
||||
Class<?>[] groups() default { };
|
||||
|
||||
Class<? extends Payload>[] payload() default { };
|
||||
|
||||
String[] vals() default { };
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package im.zhaojun.zfile.core.validation;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import javax.validation.ConstraintValidator;
|
||||
import javax.validation.ConstraintValidatorContext;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 字符串列表值校验器
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class StringListValueConstraintValidator implements ConstraintValidator<StringListValue, String> {
|
||||
|
||||
private final Set<String> set = new HashSet<>();
|
||||
|
||||
/**
|
||||
* 初始化方法
|
||||
*
|
||||
* @param constraintAnnotation
|
||||
* 校验注解对象
|
||||
*/
|
||||
@Override
|
||||
public void initialize(StringListValue constraintAnnotation) {
|
||||
String[] vals = constraintAnnotation.vals();
|
||||
set.addAll(Arrays.asList(vals));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断是否校验成功
|
||||
*
|
||||
* @param value
|
||||
* 需要校验的值
|
||||
*
|
||||
* @param context
|
||||
* 校验上下文
|
||||
*
|
||||
* @return 是否校验成功
|
||||
*/
|
||||
@Override
|
||||
public boolean isValid(String value, ConstraintValidatorContext context) {
|
||||
if (StrUtil.isEmpty(value)) {
|
||||
return true;
|
||||
}
|
||||
return set.contains(value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package im.zhaojun.zfile.exception;
|
||||
|
||||
import im.zhaojun.zfile.model.dto.ResultBean;
|
||||
import org.apache.catalina.connector.ClientAbortException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
* @author zhaojun
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class GlobleExceptionHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobleExceptionHandler.class);
|
||||
|
||||
@ExceptionHandler(SearchDisableException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus(code= HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public ResultBean searchDisableExceptionHandler(SearchDisableException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(e.getMessage(), e);
|
||||
}
|
||||
return ResultBean.error(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public ResultBean searchDisableExceptionHandler(StorageStrategyUninitializedException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(e.getMessage(), e);
|
||||
}
|
||||
return ResultBean.error(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 不存在的文件异常
|
||||
*/
|
||||
@ExceptionHandler({NotExistFileException.class})
|
||||
@ResponseBody
|
||||
public ResultBean notExistFile(Exception ex) {
|
||||
return ResultBean.error("文件不存在");
|
||||
}
|
||||
|
||||
/**
|
||||
* 捕获 ClientAbortException 异常, 不做任何处理, 防止出现大量堆栈日志输出, 此异常不影响功能.
|
||||
*/
|
||||
@ExceptionHandler({HttpMediaTypeNotAcceptableException.class, ClientAbortException.class})
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public void clientAbortException(Exception ex) {
|
||||
// if (log.isDebugEnabled()) {
|
||||
// log.debug("出现了断开异常:", ex);
|
||||
// }
|
||||
}
|
||||
|
||||
@ExceptionHandler({PreviewException.class})
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public ResultBean previewException(PreviewException ex) {
|
||||
return ResultBean.error(ex.getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ExceptionHandler
|
||||
@ResponseBody
|
||||
@ResponseStatus(code= HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public ResultBean searchDisableExceptionHandler(Exception e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(e.getMessage(), e);
|
||||
}
|
||||
return ResultBean.error("系统异常, 请联系管理员");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package im.zhaojun.zfile.exception;
|
||||
|
||||
/**
|
||||
* 对象存储初始化异常
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class InitializeException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -1920550904063819880L;
|
||||
|
||||
public InitializeException() {
|
||||
}
|
||||
|
||||
public InitializeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public InitializeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public InitializeException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public InitializeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package im.zhaojun.zfile.exception;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class NotExistFileException extends RuntimeException {
|
||||
|
||||
public NotExistFileException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NotExistFileException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NotExistFileException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public NotExistFileException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
protected NotExistFileException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package im.zhaojun.zfile.exception;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class SearchDisableException extends RuntimeException {
|
||||
|
||||
public SearchDisableException() {
|
||||
}
|
||||
|
||||
public SearchDisableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public SearchDisableException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public SearchDisableException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public SearchDisableException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package im.zhaojun.zfile.exception;
|
||||
|
||||
/**
|
||||
* 存储策略未初始化异常
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class StorageStrategyUninitializedException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 5736940575583615661L;
|
||||
|
||||
public StorageStrategyUninitializedException() {
|
||||
}
|
||||
|
||||
public StorageStrategyUninitializedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public StorageStrategyUninitializedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public StorageStrategyUninitializedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public StorageStrategyUninitializedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package im.zhaojun.zfile.exception;
|
||||
|
||||
/**
|
||||
* 未知的存储类型异常
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class UnknownStorageTypeException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -4853756482605773655L;
|
||||
|
||||
public UnknownStorageTypeException() {
|
||||
}
|
||||
|
||||
public UnknownStorageTypeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UnknownStorageTypeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public UnknownStorageTypeException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public UnknownStorageTypeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package im.zhaojun.zfile.filter;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
public class MyCorsFilter {
|
||||
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
|
||||
final CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||
corsConfiguration.setAllowCredentials(true);
|
||||
corsConfiguration.addAllowedOrigin("*");
|
||||
corsConfiguration.addAllowedHeader("*");
|
||||
corsConfiguration.addAllowedMethod("*");
|
||||
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
|
||||
return new CorsFilter(urlBasedCorsConfigurationSource);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package im.zhaojun.zfile.model.constant;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class StorageConfigConstant {
|
||||
|
||||
public static final String BUCKET_NAME_KEY = "bucketName";
|
||||
|
||||
public static final String SECRET_ID_KEY = "secretId";
|
||||
|
||||
public static final String ACCESS_KEY = "accessKey";
|
||||
|
||||
public static final String SECRET_KEY = "secretKey";
|
||||
|
||||
public static final String ENDPOINT_KEY = "endPoint";
|
||||
|
||||
public static final String BASE_PATH = "basePath";
|
||||
|
||||
public static final String DOMAIN_KEY = "domain";
|
||||
|
||||
public static final String USERNAME_KEY = "username";
|
||||
|
||||
public static final String PASSWORD_KEY = "password";
|
||||
|
||||
public static final String HOST_KEY = "host";
|
||||
|
||||
public static final String PORT_KEY = "port";
|
||||
|
||||
public static final String FILE_PATH_KEY = "filePath";
|
||||
|
||||
public static final String ACCESS_TOKEN_KEY = "accessToken";
|
||||
|
||||
public static final String REFRESH_TOKEN_KEY = "refreshToken";
|
||||
|
||||
public static final String PATH_STYLE = "pathStyle";
|
||||
|
||||
public static final String IS_PRIVATE = "isPrivate";
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package im.zhaojun.zfile.model.constant;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class SystemConfigConstant {
|
||||
|
||||
public static final String SITE_NAME = "siteName";
|
||||
|
||||
public static final String SEARCH_ENABLE = "searchEnable";
|
||||
|
||||
public static final String SEARCH_IGNORE_CASE = "searchIgnoreCase";
|
||||
|
||||
public static final String ENABLE_CACHE = "enableCache";
|
||||
|
||||
public static final String STORAGE_STRATEGY = "storageStrategy";
|
||||
|
||||
public static final String USERNAME = "username";
|
||||
|
||||
public static final String PASSWORD = "password";
|
||||
|
||||
public static final String DOMAIN = "domain";
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package im.zhaojun.zfile.model.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
public class CacheConfigDTO {
|
||||
private Boolean enableCache;
|
||||
private Boolean cacheFinish;
|
||||
private Set<String> cacheKeys;
|
||||
private Integer cacheDirectoryCount;
|
||||
private Integer cacheFileCount;
|
||||
private Date lastCacheAutoRefreshDate;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
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;
|
||||
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package im.zhaojun.zfile.model.dto;
|
||||
|
||||
import im.zhaojun.zfile.model.enums.FileTypeEnum;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class FileItemDTO implements Serializable {
|
||||
|
||||
private String name;
|
||||
private Date time;
|
||||
private Long size;
|
||||
private FileTypeEnum type;
|
||||
private String path;
|
||||
private String url;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Date getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public void setTime(Date time) {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public Long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(Long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public FileTypeEnum getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(FileTypeEnum type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FileItemDTO{" +
|
||||
"name='" + name + '\'' +
|
||||
", time=" + time +
|
||||
", size=" + size +
|
||||
", type=" + type +
|
||||
", path='" + path + '\'' +
|
||||
", url='" + url + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package im.zhaojun.zfile.model.dto;
|
||||
|
||||
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class InstallModelDTO {
|
||||
private String siteName;
|
||||
private StorageTypeEnum storageStrategy;
|
||||
private String username;
|
||||
private String password;
|
||||
private String domain;
|
||||
private Map<String, String> storageStrategyConfig;
|
||||
|
||||
public String getSiteName() {
|
||||
return siteName;
|
||||
}
|
||||
|
||||
public void setSiteName(String siteName) {
|
||||
this.siteName = siteName;
|
||||
}
|
||||
|
||||
public StorageTypeEnum getStorageStrategy() {
|
||||
return storageStrategy;
|
||||
}
|
||||
|
||||
public void setStorageStrategy(StorageTypeEnum storageStrategy) {
|
||||
this.storageStrategy = storageStrategy;
|
||||
}
|
||||
|
||||
public Map<String, String> getStorageStrategyConfig() {
|
||||
return storageStrategyConfig;
|
||||
}
|
||||
|
||||
public void setStorageStrategyConfig(Map<String, String> storageStrategyConfig) {
|
||||
this.storageStrategyConfig = storageStrategyConfig;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
public void setDomain(String domain) {
|
||||
this.domain = domain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InstallModelDTO{" +
|
||||
"siteName='" + siteName + '\'' +
|
||||
", storageStrategy=" + storageStrategy +
|
||||
", username='" + username + '\'' +
|
||||
", password='" + password + '\'' +
|
||||
", domain='" + domain + '\'' +
|
||||
", storageStrategyConfig=" + storageStrategyConfig +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user