Compare commits
337 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90101c173c | ||
|
|
c590953197 | ||
|
|
23772223bd | ||
|
|
b88f413906 | ||
|
|
6d8bdf1df3 | ||
|
|
0173357ec9 | ||
|
|
b4ae4bcc90 | ||
|
|
f7679217ec | ||
|
|
901d539332 | ||
|
|
1b3c284f4e | ||
|
|
f5b6f8da4e | ||
|
|
6da72845e4 | ||
|
|
81b9ac5923 | ||
|
|
8f8dc86365 | ||
|
|
f9d46edbcc | ||
|
|
e3991be3b7 | ||
|
|
d42853abe2 | ||
|
|
e5c6a1aca4 | ||
|
|
b5d46912d2 | ||
|
|
dfcbaf7158 | ||
|
|
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 |
48
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,48 +0,0 @@
|
||||
---
|
||||
name: BUG 反馈
|
||||
about: 事情不像预期的那样工作吗?
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!--
|
||||
你好!感谢你正在考虑为 ZFile 提交一个 bug。请花一点点时间尽量详细地回答以下基础问题。
|
||||
|
||||
谢谢!
|
||||
-->
|
||||
|
||||
<!--
|
||||
请确认你已经做了下面这些事情,若 bug 还是未解决,那么请尽可详细地描述你的问题。
|
||||
|
||||
- 我已经安装了最新版的 ZFile
|
||||
- 我已经阅读了 ZFile 的文档:http://docs.zhaojun.im/zfile
|
||||
- 我已经搜索了已有的 Issues 列表中有关的信息
|
||||
- 我已经清理过浏览器缓存并重试
|
||||
-->
|
||||
|
||||
## 我的环境
|
||||
|
||||
<!--
|
||||
请登录到管理后台,点击左侧系统监控, 复制或截图此页内容.
|
||||
-->
|
||||
|
||||
## 错误日志
|
||||
|
||||
<!--
|
||||
请登录到管理后台,点击左侧系统监控, 点击右上角诊断日志下载, 然后上传到此 Issue 中.
|
||||
-->
|
||||
|
||||
## 期望行为
|
||||
|
||||
<!--
|
||||
你期望会发生什么?
|
||||
-->
|
||||
|
||||
## 当前行为
|
||||
|
||||
<!--
|
||||
描述 bug 细节,确认出现此问题的复现步骤,例如点击了哪里,发生了什么情况?
|
||||
|
||||
你可以粘贴截图或附件。
|
||||
-->
|
||||
13
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: 'Blank Issue'
|
||||
description: 请使用 https://issue.zfile.vip 创建新的问题.
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**注意:**
|
||||
不要通过此页面创建问题, 请使用 https://issue.zfile.vip 创建新的问题.
|
||||
如果不是通过此链接创建的问题, 将会被直接关闭.
|
||||
- type: textarea
|
||||
id: add-a-description
|
||||
attributes:
|
||||
label: Add a description
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 创建 Issue
|
||||
url: https://issue.zfile.vip/
|
||||
about: 未通过 https://issue.zfile.vip/ 创建的问题可能会被立即关闭。
|
||||
34
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: 功能建议
|
||||
about: 想让我们为 ZFile 增加什么功能吗?
|
||||
title: 'feat: '
|
||||
labels: 'Feature Request'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
你好!感谢你愿意考虑希望 ZFile 增加某个新功能。请花一点点时间尽量详细地回答以下基础问题。
|
||||
|
||||
谢谢!
|
||||
-->
|
||||
|
||||
## 概述
|
||||
|
||||
<!--
|
||||
对这个新功能的一段描述
|
||||
-->
|
||||
|
||||
## 动机
|
||||
|
||||
<!--
|
||||
为什么你希望在 ZFile 中使用这个功能?
|
||||
-->
|
||||
|
||||
## 详细解释
|
||||
|
||||
<!--
|
||||
详细描述这个新功能。
|
||||
|
||||
如果这是一个小功能,你可以忽略这部分。
|
||||
-->
|
||||
14
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,14 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: 对 ZFile 有任何问题吗?
|
||||
title: ''
|
||||
labels: 'question'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
如果你有任何问题也可以通过此渠道来向我们反馈。
|
||||
|
||||
谢谢!
|
||||
-->
|
||||
17
.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,12 @@ mvnw.cmd
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
/.mvn/wrapper/
|
||||
/mvnw
|
||||
/mvnw.cmd
|
||||
/result/
|
||||
2
.package/script/log.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
tail -fn100 ~/.zfile-v4/logs/zfile.log
|
||||
6
.package/script/restart.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
$DIR/stop.sh
|
||||
$DIR/start.sh
|
||||
22
.package/script/start.sh
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 检测是否已启动
|
||||
pid=`ps -ef | grep -n zfile | grep -v grep | grep -v launch | grep -v .sh | awk '{print $2}'`
|
||||
if [ -n "${pid}" ]
|
||||
then
|
||||
echo "已运行在 pid:${pid},无需重复启动!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 获取当前脚本所在路径
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
ZFILE_DIR=$(dirname "$DIR")
|
||||
|
||||
# 启动 zfile
|
||||
nohup $ZFILE_DIR/zfile/zfile --spring.config.location=$ZFILE_DIR/application.properties --spring.web.resources.static-locations=file:$ZFILE_DIR/static/ >/dev/null 2>&1 &
|
||||
echo '启动中...'
|
||||
sleep 3s
|
||||
|
||||
# 输出 pid
|
||||
pid=`ps -ef | grep -n zfile | grep -v grep | grep -v .sh | awk '{print $2}'`
|
||||
echo "目前 PID 为: ${pid}"
|
||||
12
.package/script/status.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "------------------ 检测状态 START --------------"
|
||||
pid=`ps -ef | grep -n zfile | grep -v grep | grep -v launch | grep -v .sh | awk '{print $2}'`
|
||||
if [ -z "${pid}" ]
|
||||
then
|
||||
echo "未运行, 无需停止!"
|
||||
else
|
||||
echo "运行pid:${pid}"
|
||||
fi
|
||||
|
||||
echo "------------------ 检测状态 END --------------"
|
||||
14
.package/script/stop.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "------------------ 检测状态 START --------------"
|
||||
pid=`ps -ef | grep -n zfile | grep -v grep | grep -v .sh | awk '{print $2}'`
|
||||
if [ -z "${pid}" ]
|
||||
then
|
||||
echo "未运行, 无需停止!"
|
||||
else
|
||||
echo "运行pid:${pid}"
|
||||
kill -9 ${pid}
|
||||
echo "已停止进程: ${pid}"
|
||||
fi
|
||||
|
||||
echo "------------------ 检测状态 END --------------"
|
||||
BIN
.package/script/vcruntime140_1.dll
Normal file
7
.package/script/双击我启动.bat
Normal file
@@ -0,0 +1,7 @@
|
||||
@echo off
|
||||
if not exist %windir%\system32\cmd.exe (
|
||||
"%CD%\zfile\zfile.exe"
|
||||
) else (
|
||||
cmd /k "%CD%\zfile\zfile.exe"
|
||||
exit
|
||||
)
|
||||
181
API.md
@@ -1,181 +0,0 @@
|
||||
## API 标准
|
||||
|
||||
所有 API 均返回 `msg`, `code`, `data` 三个属性.
|
||||
|
||||
| code | 描述 |
|
||||
| :---: | :------------: |
|
||||
| 0 | 请求成功 |
|
||||
| -1 | 请求失败 |
|
||||
| -2 | 文件夹需要密码 |
|
||||
|
||||
当 `code == 0` 时, `data` 中为请求所需数据.
|
||||
|
||||
当 `code != 0` 时, 应当将 `msg` 中的内容作为参考值.
|
||||
|
||||
|
||||
## 驱动器列表
|
||||
|
||||
### 请求 URL
|
||||
|
||||
`/api/drive/list` `GET`
|
||||
|
||||
### 响应
|
||||
|
||||
```json
|
||||
{
|
||||
"msg": "操作成功",
|
||||
"code": 0,
|
||||
"data": [
|
||||
{
|
||||
"id": 3, --- 此 ID 是驱动器 ID, 用来唯一区分驱动器
|
||||
"name": "演示 A 盘", --- 驱动器名称
|
||||
"enableCache": true, --- 是否开启了缓存
|
||||
"autoRefreshCache": false, --- 是否开启了缓存自动刷新
|
||||
"type": { --- 存储源类型
|
||||
"key": "upyun",
|
||||
"description": "又拍云 USS"
|
||||
},
|
||||
"searchEnable": false, --- 是否开启搜索
|
||||
"searchIgnoreCase": false, --- 搜索是否忽略大小写
|
||||
"searchContainEncryptedFile": false --- 搜索是否包含加密文件夹
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 获取文件列表
|
||||
|
||||
### 请求 URL
|
||||
|
||||
`/api/list/{driveId}` `GET`
|
||||
|
||||
|
||||
### URL 参数
|
||||
|
||||
|
||||
| 参数名 | 描述 | 是否必填 | 参考值 |
|
||||
| :-----: | :-------------------: | :------: | :------------------------------------: |
|
||||
| driveId | 驱动器 ID | 是 | 参考 `获取驱动器列表` 接口返回的 id 值 |
|
||||
|
||||
|
||||
### 请求参数
|
||||
|
||||
|
||||
| 参数名 | 描述 | 是否必填 | 参考值 |
|
||||
| :------: | :--------: | :------: | :--------------------------: |
|
||||
| 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/directlink/{driveId}` `GET`
|
||||
|
||||
|
||||
### URL 参数
|
||||
|
||||
| 参数名 | 描述 | 是否必填 | 参考值 |
|
||||
| :-----: | :-------------------: | :------: | :------------------------------------: |
|
||||
| driveId | 驱动器 ID | 是 | 参考 `获取驱动器列表` 接口返回的 id 值 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名 | 描述 | 是否必填 | 参考值 |
|
||||
| :----: | :--------: | :------: | :------------------: |
|
||||
| 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/{driveId}` `GET`
|
||||
|
||||
|
||||
### URL 参数
|
||||
|
||||
| 参数名 | 描述 | 是否必填 | 参考值 |
|
||||
| :-----: | :-------------------: | :------: | :------------------------------------: |
|
||||
| driveId | 驱动器 ID | 是 | 参考 `获取驱动器列表` 接口返回的 id 值 |
|
||||
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名 | 描述 | 是否必填 | 参考值 |
|
||||
| :----: | :--------: | :------: | :-----------: |
|
||||
| path | 文件夹名称 | 是 | `/文件夹名称` |
|
||||
|
||||
### 响应
|
||||
|
||||
```json
|
||||
{
|
||||
"msg": "操作成功",
|
||||
"code": 0,
|
||||
"data": {
|
||||
"siteName": "ZFile 演示站",
|
||||
"searchEnable": false,
|
||||
"username": "zhao",
|
||||
"domain": "https://zfile.jun6.net",
|
||||
"customJs": "",
|
||||
"customCss": "",
|
||||
"tableSize": "small",
|
||||
"showOperator": true,
|
||||
"showDocument": true,
|
||||
"announcement": "本站是 ZFile 演示站,交流反馈群 180605017",
|
||||
"showAnnouncement": true,
|
||||
"layout": "full",
|
||||
"readme": null
|
||||
}
|
||||
}
|
||||
```
|
||||
21
Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
FROM debian:10-slim
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
WORKDIR /root
|
||||
EXPOSE 8080
|
||||
|
||||
RUN apt update -y && apt install --no-install-recommends fontconfig zstd -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
COPY zfile-artifacts/zfile-linux-${TARGETARCH}/zfile/* /root/
|
||||
COPY zfile-artifacts/zfile-linux-${TARGETARCH}/static/ /root/static/
|
||||
COPY zfile-artifacts/zfile-linux-${TARGETARCH}/application.properties /root/
|
||||
|
||||
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||
RUN echo 'Asia/Shanghai' >/etc/timezone
|
||||
|
||||
# 设置编码为 UTF-8
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LC_ALL=C.UTF-8
|
||||
|
||||
CMD if [ -f /root/zfile.zst ]; then zstd --no-progress -d /root/zfile.zst && rm -rf /root/zfile.zst && chmod +x /root/zfile && /root/zfile --spring.config.location=file:/root/application.properties; else chmod +x /root/zfile && /root/zfile --spring.config.location=file:/root/application.properties; fi
|
||||
198
README.md
@@ -1,144 +1,90 @@
|
||||
# Z-File
|
||||
|
||||

|
||||
[](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)
|
||||
<div align="center">
|
||||
<a href="https://zfile.vip" target="_blank" rel="noopener noreferrer">
|
||||
<img style="margin: auto; width: 100px; display: block" src="/img/logo-zfile.png" alt="ZFile" />
|
||||
</a>
|
||||
<p>ZFile 是一个适用于个人或小团队的在线网盘程序,可以将多种存储类型统一管理,再也不用登录各种网站管理文件,现在你只需要在 ZFile 中畅快使用!</p>
|
||||
<div>
|
||||
<img alt="last commit" src="https://shields.io/github/last-commit/zhaojun1998/zfile.svg?style=flat-square"/>
|
||||
<img alt="downloads" src="https://shields.io/github/downloads/zhaojun1998/zfile/total?style=flat-square"/>
|
||||
<img alt="release version" src="https://shields.io/github/v/release/zhaojun1998/zfile?style=flat-square"/>
|
||||
<img alt="commit activity" src="https://shields.io/github/commit-activity/y/zhaojun1998/zfile?style=flat-square"/>
|
||||
<img alt="open issues" src="https://shields.io/github/issues/zhaojun1998/zfile?style=flat-square"/>
|
||||
<img alt="closed issues" src="https://shields.io/github/issues-closed-raw/zhaojun1998/zfile?style=flat-square"/>
|
||||
<img alt="forks" src="https://shields.io/github/forks/zhaojun1998/zfile?style=flat-square"/>
|
||||
<img alt="stars" src="https://shields.io/github/stars/zhaojun1998/zfile?style=flat-square"/>
|
||||
<img alt="watchers" src="https://shields.io/github/watchers/zhaojun1998/zfile?style=flat-square"/>
|
||||
</div>
|
||||
<span>
|
||||
<a href="https://zfile.vip">官网</a>
|
||||
<span> | </span>
|
||||
<a href="https://docs.zfile.vip">文档</a>
|
||||
<span> | </span>
|
||||
<a href="https://demo.zfile.vip">预览地址</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
## 系统特色
|
||||
|
||||
* 内存缓存 (免安装)
|
||||
* 内存数据库 (免安装)
|
||||
* 个性化配置
|
||||
* 自定义目录的 readme 说明文件
|
||||
* 自定义 JS, CSS
|
||||
* 文件夹密码
|
||||
* 支持在线浏览文本文件, 视频, 图片, 音乐. (支持 FLV 和 HLS)
|
||||
* 文件/目录二维码
|
||||
* 缓存动态开启, ~~缓存自动刷新 (v2.2 及以前版本支持)~~
|
||||
* ~~全局搜索 (v2.2 及以前版本支持)~~
|
||||
* 同时挂载多个存储策略
|
||||
* 支持 阿里云 OSS, FTP, 华为云 OBS, 本地存储, MINIO, OneDrive 国际/家庭/个人版, OneDrive 世纪互联版, 七牛云 KODO, 腾讯云 COS, 又拍云 USS.
|
||||
- Docker、Docker Compose 支持(amd64, arm64)。
|
||||
- 支持对文件生成直链、短链(可设过期时间)。
|
||||
- 响应式设计,支持手机、平板、电脑等多种设备访问。
|
||||
- 支持多用户功能,可分配给指定用户指定存储源或目录。
|
||||
- 支持在线浏览图片、播放音视频,文本文件、Office、Obj(3d)等文件类型。
|
||||
- 支持对接 S3、OneDrive、SharePoint、Google Drive、多吉云、又拍云、本地存储、FTP、SFTP 等存储源。
|
||||
- 支持常用快捷键,`Ctrl + A` 全选,`Ctrl + 左键` 多选,`Shift + 左键` 范围选择,`Esc` 取消全选等。
|
||||
- 支持限速下载(捐赠版)
|
||||
- 支持限制指定用户可查看、上传的文件类型(捐赠版)
|
||||
|
||||
## 快速开始
|
||||
|
||||
安装依赖环境:
|
||||
一键脚本安装:
|
||||
|
||||
```bash
|
||||
# CentOS系统
|
||||
yum install -y java-1.8.0-openjdk unzip
|
||||
curl -sSL https://docs.zfile.vip/install.sh -o install.sh && chmod +x install.sh && ./install.sh
|
||||
```
|
||||
|
||||
```bash
|
||||
# Debian 9 / Ubuntu 14+
|
||||
apt update
|
||||
apt install -y openjdk-8-jre-headless unzip
|
||||
```
|
||||
|
||||
```bash
|
||||
# Debian 10 (Buster) 系统
|
||||
apt update && apt install -y apt-transport-https software-properties-common ca-certificates dirmngr gnupg
|
||||
wget -qO - https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public | apt-key add -
|
||||
add-apt-repository --yes https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/
|
||||
apt update && apt install -y adoptopenjdk-8-hotspot-jre
|
||||
```
|
||||
更多安装方式请参考 [安装文档](https://docs.zfile.vip/install/)
|
||||
|
||||
|
||||
> 如为更新程序, 则请先执行 `~/zfile/bin/stop.sh && rm -rf ~/zfile` 清理旧程序. 首次安装请忽略此选项.
|
||||
## 功能预览
|
||||
|
||||
|
||||
下载项目:
|
||||
|
||||
```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-release.war` 改为 `zfile-x.x.war`,如 `zfile-2.2.war`。
|
||||
|
||||
程序的目录结构为:
|
||||
```
|
||||
├── zfile
|
||||
├── META-INF
|
||||
├── WEB-INF
|
||||
└── bin
|
||||
├── start.sh # 启动脚本
|
||||
└── stop.sh # 停止脚本
|
||||
├── restart.sh # 重启脚本
|
||||
```
|
||||
|
||||
启动项目:
|
||||
|
||||
```bash
|
||||
~/zfile/bin/start.sh
|
||||
```
|
||||
|
||||
篇幅有限, 更详细的安装教程及介绍请参考: [ZFile 文档](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
|
||||
|
||||
|
||||
## 预览
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 常见问题
|
||||
|
||||
|
||||
### 默认路径
|
||||
|
||||
默认 H2 数据库文件地址: `~/.zfile/db/`, `~` 表示用户目录
|
||||
|
||||
windows 为 `C:/Users/用户名/`
|
||||
|
||||
linux 为 `/home/用户名/`, root 用户为 `/root/`
|
||||
|
||||
> 2.3 及以后版本路径为 `~/.zfile-new/db/`
|
||||
|
||||
### 文档文件和加密文件
|
||||
|
||||
- 目录文档显示文件名为 `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)
|
||||
- [ ] 体验优化 - 忽略文件列表 (正则表达式)
|
||||
- [ ] 体验优化 - 自定义支持预览的文件后缀 (正则表达式)
|
||||
- [ ] 体验优化 - 一键安装脚本
|
||||
### 文件列表
|
||||

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

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

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

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

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

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

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

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

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

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

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

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

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

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

|
||||
|
||||
## 支持作者
|
||||
|
||||
如果本项目对你有帮助,请作者喝杯咖啡吧。
|
||||
|
||||
<img src="http://cdn.jun6.net/alipay.png" width="200" height="312">
|
||||
<img src="http://cdn.jun6.net/wechat.png" width="222" height="300">
|
||||
<img src="https://cdn.jun6.net/2021/03/27/152704e91f13d.png" width="400" alt="赞助我">
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#zfile-dev/zfile&Date)
|
||||
|
||||
BIN
img/file-list.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
img/gallery.png
Normal file
|
After Width: | Height: | Size: 563 KiB |
BIN
img/generate-link.jpeg
Normal file
|
After Width: | Height: | Size: 362 KiB |
BIN
img/login.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
img/logo-zfile.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
img/page-setting.png
Normal file
|
After Width: | Height: | Size: 166 KiB |
BIN
img/preview-3d.png
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
img/preview-audio.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
img/preview-office.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
img/preview-pdf.png
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
img/preview-text.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
BIN
img/preview-video.png
Normal file
|
After Width: | Height: | Size: 790 KiB |
BIN
img/storage-edit-local.png
Normal file
|
After Width: | Height: | Size: 483 KiB |
BIN
img/storage-list.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
img/user-edit.png
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
img/view-setting.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
363
pom.xml
@@ -1,29 +1,61 @@
|
||||
<?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.2.0</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>3.3.2</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>im.zhaojun</groupId>
|
||||
<artifactId>zfile</artifactId>
|
||||
<version>2.8</version>
|
||||
<name>zfile</name>
|
||||
<packaging>war</packaging>
|
||||
<description>一个在线的文件浏览系统</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<skipTests>true</skipTests>
|
||||
|
||||
<java.version>21</java.version>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
|
||||
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
||||
<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.46.0.1</sqlite-jdbc.version>
|
||||
<flyway.version>10.12.0</flyway.version>
|
||||
|
||||
<lombok.version>1.18.32</lombok.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>bom</artifactId>
|
||||
<version>2.24.3</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<!-- spring boot 官方相关 -->
|
||||
<dependencies>
|
||||
<!-- spring boot 官方相关-->
|
||||
<dependency>
|
||||
<groupId>org.graalvm.sdk</groupId>
|
||||
<artifactId>graal-sdk</artifactId>
|
||||
<version>24.1.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
@@ -32,71 +64,140 @@
|
||||
<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>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<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>
|
||||
<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>${flyway.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-mysql</artifactId>
|
||||
<version>${flyway.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类 -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.1.3</version>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>3.5.6</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>
|
||||
<!-- https://mvnrepository.com/artifact/software.amazon.awssdk/s3 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<version>1.11.699</version>
|
||||
<groupId>com.qiniu</groupId>
|
||||
<artifactId>qiniu-java-sdk</artifactId>
|
||||
<version>7.12.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.8</version>
|
||||
<groupId>com.github.mwiede</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
<version>0.2.20</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>3.6</version>
|
||||
<groupId>com.github.lookfirst</groupId>
|
||||
<artifactId>sardine</artifactId>
|
||||
<version>5.12</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.api-client</groupId>
|
||||
<artifactId>google-api-client</artifactId>
|
||||
<version>1.35.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 登陆/权限相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
<version>1.38.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 文档相关 -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
<version>4.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>com.hierynomus</groupId>-->
|
||||
<!-- <artifactId>sshj</artifactId>-->
|
||||
<!-- <version>0.38.0</version>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.28</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<version>5.2.5</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.26.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 其他工具类 -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
@@ -104,15 +205,89 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mpatric</groupId>
|
||||
<artifactId>mp3agic</artifactId>
|
||||
<version>0.9.1</version>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>3.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</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>33.2.0-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</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>20231013</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpmime</artifactId>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
</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>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.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.oshi</groupId>
|
||||
<artifactId>oshi-core</artifactId>
|
||||
<version>6.6.3</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
@@ -124,25 +299,81 @@
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.uyoqu.framework</groupId>
|
||||
<artifactId>maven-plugin-starter</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>bin</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<jvms>
|
||||
<jvm>-Djava.security.egd=file:/dev/./urandom</jvm>
|
||||
<jvm>-Dfile.encoding=utf-8</jvm>
|
||||
</jvms>
|
||||
<source>21</source>
|
||||
<target>21</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.32</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>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>native</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.graalvm.buildtools</groupId>
|
||||
<artifactId>native-maven-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-native</id>
|
||||
<goals>
|
||||
<goal>compile-no-fork</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<fallback>false</fallback>
|
||||
<imageName>${project.name}</imageName>
|
||||
<metadataRepository>
|
||||
<enabled>true</enabled>
|
||||
</metadataRepository>
|
||||
<jvmArgs>
|
||||
<jvmArg>--add-opens=java.base/java.net=ALL-UNNAMED</jvmArg>
|
||||
<jvmArg>--add-opens=java.base/sun.net=ALL-UNNAMED</jvmArg>
|
||||
</jvmArgs>
|
||||
<buildArgs>
|
||||
<arg>
|
||||
-march=compatibility
|
||||
-H:+AddAllCharsets
|
||||
--features=im.zhaojun.zfile.aot.LambdaRegistrationFeature
|
||||
--features=im.zhaojun.zfile.aot.BouncyCastleFeature
|
||||
--features=im.zhaojun.zfile.aot.SQLiteNativeConfiguration
|
||||
</arg>
|
||||
</buildArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
</project>
|
||||
@@ -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 FileListCacheAspect {
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package im.zhaojun.zfile.cache;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class DriveCacheKey {
|
||||
|
||||
private Integer driveId;
|
||||
|
||||
private String key;
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package im.zhaojun.zfile.cache;
|
||||
|
||||
import cn.hutool.cache.impl.CacheObj;
|
||||
import cn.hutool.cache.impl.TimedCache;
|
||||
import im.zhaojun.zfile.context.DriveContext;
|
||||
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
|
||||
import im.zhaojun.zfile.util.SpringContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
public class MyTimedCache<K, V> extends TimedCache<K, V> {
|
||||
|
||||
private DriveContext driveContext;
|
||||
|
||||
public MyTimedCache(long timeout) {
|
||||
super(timeout);
|
||||
}
|
||||
|
||||
public MyTimedCache(long timeout, Map<K, CacheObj<K, V>> map) {
|
||||
super(timeout, map);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove(K key, V cachedObject) {
|
||||
if (driveContext == null) {
|
||||
driveContext = SpringContextHolder.getBean(DriveContext.class);
|
||||
}
|
||||
|
||||
DriveCacheKey cacheKey = (DriveCacheKey) key;
|
||||
AbstractBaseFileService baseFileService = driveContext.get(cacheKey.getDriveId());
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("尝试刷新缓存: {}", cacheKey);
|
||||
}
|
||||
|
||||
if (baseFileService == null) {
|
||||
log.error("尝试刷新缓存: {}, 时出现异常, 驱动器已不存在", cacheKey);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
baseFileService.fileList(cacheKey.getKey());
|
||||
} catch (Exception e) {
|
||||
log.error("尝试刷新缓存 {} 失败", cacheKey);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
331
src/main/java/im/zhaojun/zfile/cache/ZFileCache.java
vendored
@@ -1,331 +0,0 @@
|
||||
package im.zhaojun.zfile.cache;
|
||||
|
||||
import cn.hutool.cache.impl.CacheObj;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
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
|
||||
@Slf4j
|
||||
public class ZFileCache {
|
||||
|
||||
@Resource
|
||||
private DriverConfigRepository driverConfigRepository;
|
||||
|
||||
/**
|
||||
* 缓存过期时间
|
||||
*/
|
||||
@Value("${zfile.cache.timeout}")
|
||||
private long timeout;
|
||||
|
||||
/**
|
||||
* 缓存自动刷新间隔
|
||||
*/
|
||||
@Value("${zfile.cache.auto-refresh.interval}")
|
||||
private long autoRefreshInterval;
|
||||
|
||||
/**
|
||||
* 文件/文件对象缓存.
|
||||
*
|
||||
* ConcurrentMap<Integer, ConcurrentHashMap<String, List<FileItemDTO>>>
|
||||
* ConcurrentMap<driveId, ConcurrentHashMap<key, value>>
|
||||
*
|
||||
* driveId: 驱动器 ID
|
||||
* key: 文件夹路径
|
||||
* value: 文件夹中内容
|
||||
*/
|
||||
private ConcurrentMap<Integer, MyTimedCache<DriveCacheKey, List<FileItemDTO>>> drivesCache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 系统设置缓存
|
||||
*/
|
||||
private SystemConfigDTO systemConfigCache;
|
||||
|
||||
|
||||
/**
|
||||
* 写入缓存
|
||||
*
|
||||
* @param driveId
|
||||
* 驱动器 ID
|
||||
*
|
||||
* @param key
|
||||
* 文件夹路径
|
||||
*
|
||||
* @param value
|
||||
* 文件夹中列表
|
||||
*/
|
||||
public synchronized void put(Integer driveId, String key, List<FileItemDTO> value) {
|
||||
getCacheByDriveId(driveId).put(new DriveCacheKey(driveId, key), value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定驱动器, 某个文件夹的名称
|
||||
*
|
||||
* @param driveId
|
||||
* 驱动器 ID
|
||||
*
|
||||
* @param key
|
||||
* 文件夹路径
|
||||
*
|
||||
* @return 驱动器中文件夹的内容
|
||||
*/
|
||||
public List<FileItemDTO> get(Integer driveId, String key) {
|
||||
return getCacheByDriveId(driveId).get(new DriveCacheKey(driveId, key), false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 清空指定驱动器的缓存.
|
||||
*
|
||||
* @param driveId
|
||||
* 驱动器 ID
|
||||
*/
|
||||
public void clear(Integer driveId) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("清空驱动器所有缓存, driveId: {}", 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<DriveCacheKey, List<FileItemDTO>>> cacheObjIterator = getCacheByDriveId(driveId).cacheObjIterator();
|
||||
Set<String> keys = new HashSet<>();
|
||||
while (cacheObjIterator.hasNext()) {
|
||||
keys.add(cacheObjIterator.next().getKey().getKey());
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从缓存中删除指定驱动器的某个路径的缓存
|
||||
*
|
||||
* @param driveId
|
||||
* 驱动器 ID
|
||||
*
|
||||
* @param key
|
||||
* 文件夹路径
|
||||
*/
|
||||
public void remove(Integer driveId, String key) {
|
||||
getCacheByDriveId(driveId).remove(new DriveCacheKey(driveId, 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 MyTimedCache<DriveCacheKey, List<FileItemDTO>> getCacheByDriveId(Integer driveId) {
|
||||
MyTimedCache<DriveCacheKey, List<FileItemDTO>> driveCache = drivesCache.get(driveId);
|
||||
if (driveCache == null) {
|
||||
driveCache = new MyTimedCache<>(timeout * 1000);
|
||||
drivesCache.put(driveId, driveCache);
|
||||
startAutoCacheRefresh(driveId);
|
||||
}
|
||||
return driveCache;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定驱动器的缓存命中数
|
||||
*
|
||||
* @param driveId
|
||||
* 驱动器 ID
|
||||
*
|
||||
* @return 缓存命中数
|
||||
*/
|
||||
public int getHitCount(Integer driveId) {
|
||||
return getCacheByDriveId(driveId).getHitCount();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定驱动器的缓存未命中数
|
||||
*
|
||||
* @param driveId
|
||||
* 驱动器 ID
|
||||
*
|
||||
* @return 缓存未命中数
|
||||
*/
|
||||
public int getMissCount(Integer driveId) {
|
||||
return getCacheByDriveId(driveId).getMissCount();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 开启缓存自动刷新
|
||||
*
|
||||
* @param driveId
|
||||
* 驱动器 ID
|
||||
*/
|
||||
public void startAutoCacheRefresh(Integer driveId) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("开启缓存自动刷新 driveId: {}", driveId);
|
||||
}
|
||||
DriveConfig driveConfig = driverConfigRepository.findById(driveId).get();
|
||||
Boolean autoRefreshCache = driveConfig.getAutoRefreshCache();
|
||||
if (autoRefreshCache != null && autoRefreshCache) {
|
||||
MyTimedCache<DriveCacheKey, List<FileItemDTO>> driveCache = drivesCache.get(driveId);
|
||||
if (driveCache == null) {
|
||||
driveCache = new MyTimedCache<>(timeout * 1000);
|
||||
drivesCache.put(driveId, driveCache);
|
||||
}
|
||||
driveCache.schedulePrune(autoRefreshInterval * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 停止缓存自动刷新
|
||||
*
|
||||
* @param driveId
|
||||
* 驱动器 ID
|
||||
*/
|
||||
public void stopAutoCacheRefresh(Integer driveId) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("停止缓存自动刷新 driveId: {}", driveId);
|
||||
}
|
||||
MyTimedCache<DriveCacheKey, List<FileItemDTO>> driveCache = drivesCache.get(driveId);
|
||||
if (driveCache != null) {
|
||||
driveCache.cancelPruneSchedule();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +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.service.StorageConfigService;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
public class OneDriveConfig {
|
||||
|
||||
@Resource
|
||||
private StorageConfigService storageConfigService;
|
||||
|
||||
/**
|
||||
* OneDrive 请求 RestTemplate, 会在请求头中添加 Bearer: Authorization {token} 信息, 用于 API 认证.
|
||||
*/
|
||||
@Bean
|
||||
public RestTemplate oneDriveRestTemplate() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
ClientHttpRequestInterceptor interceptor = (httpRequest, bytes, clientHttpRequestExecution) -> {
|
||||
HttpHeaders headers = httpRequest.getHeaders();
|
||||
Integer driveId = Integer.valueOf(((LinkedList)headers.get("driveId")).get(0).toString());
|
||||
|
||||
StorageConfig accessTokenConfig =
|
||||
storageConfigService.findByDriveIdAndKey(driveId, StorageConfigConstant.ACCESS_TOKEN_KEY);
|
||||
|
||||
String tokenValue = String.format("%s %s", "Bearer", accessTokenConfig.getValue());
|
||||
httpRequest.getHeaders().add("Authorization", tokenValue);
|
||||
return clientHttpRequestExecution.execute(httpRequest, bytes);
|
||||
};
|
||||
restTemplate.setInterceptors(Collections.singletonList(interceptor));
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,131 +0,0 @@
|
||||
package im.zhaojun.zfile.context;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import im.zhaojun.zfile.exception.InvalidDriveException;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 每个驱动器对应一个 Service, 其中初始化好了与对象存储的连接信息.
|
||||
* 此驱动器上下文环境用户缓存每个 Service, 避免重复创建连接.
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Component
|
||||
@DependsOn("springContextHolder")
|
||||
@Slf4j
|
||||
public class DriveContext implements ApplicationContextAware {
|
||||
|
||||
/**
|
||||
* Map<Integer, AbstractBaseFileService>
|
||||
* Map<驱动器 ID, 驱动器连接 Service>
|
||||
*/
|
||||
private static Map<Integer, AbstractBaseFileService> drivesServiceMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Resource
|
||||
private DriveConfigService driveConfigService;
|
||||
|
||||
|
||||
/**
|
||||
* 项目启动时, 自动调用数据库已存储的所有驱动器进行初始化.
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
List<DriveConfig> list = driveConfigService.list();
|
||||
for (DriveConfig driveConfig : list) {
|
||||
try {
|
||||
init(driveConfig.getId());
|
||||
log.info("启动时初始化驱动器成功, 驱动器信息: {}", JSON.toJSONString(driveConfig));
|
||||
} catch (Exception e) {
|
||||
log.error("启动时初始化驱动器失败, 驱动器信息: {}", JSON.toJSONString(driveConfig), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 初始化指定驱动器的 Service, 添加到上下文环境中.
|
||||
*
|
||||
* @param driveId
|
||||
* 驱动器 ID.
|
||||
*/
|
||||
public void init(Integer driveId) {
|
||||
AbstractBaseFileService baseFileService = getBeanByDriveId(driveId);
|
||||
if (baseFileService != null) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("尝试初始化驱动器, driveId: {}", driveId);
|
||||
}
|
||||
baseFileService.init(driveId);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("初始化驱动器成功, driveId: {}", driveId);
|
||||
}
|
||||
drivesServiceMap.put(driveId, baseFileService);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定驱动器的 Service.
|
||||
*
|
||||
* @param driveId
|
||||
* 驱动器 ID
|
||||
*
|
||||
* @return 驱动器对应的 Service
|
||||
*/
|
||||
public AbstractBaseFileService get(Integer driveId) {
|
||||
AbstractBaseFileService abstractBaseFileService = drivesServiceMap.get(driveId);
|
||||
if (abstractBaseFileService == null) {
|
||||
throw new InvalidDriveException("此驱动器不存在或初始化失败, 请检查后台参数配置");
|
||||
}
|
||||
return abstractBaseFileService;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 销毁指定驱动器的 Service.
|
||||
*
|
||||
* @param driveId
|
||||
* 驱动器 ID
|
||||
*/
|
||||
public void destroy(Integer driveId) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("清理驱动器上下文对象, driveId: {}", 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +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,54 +0,0 @@
|
||||
package im.zhaojun.zfile.controller.admin;
|
||||
|
||||
import im.zhaojun.zfile.model.dto.SystemConfigDTO;
|
||||
import im.zhaojun.zfile.model.support.ResultBean;
|
||||
import im.zhaojun.zfile.service.SystemConfigService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 管理后台接口
|
||||
* @author zhaojun
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/admin")
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package im.zhaojun.zfile.controller.admin;
|
||||
|
||||
import im.zhaojun.zfile.model.dto.CacheInfoDTO;
|
||||
import im.zhaojun.zfile.model.support.ResultBean;
|
||||
import im.zhaojun.zfile.service.DriveConfigService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 缓存 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();
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/{driveId}/info")
|
||||
public ResultBean cacheInfo(@PathVariable("driveId") Integer driveId) {
|
||||
CacheInfoDTO cacheInfo = driveConfigService.findCacheInfo(driveId);
|
||||
return ResultBean.success(cacheInfo);
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/{driveId}/refresh")
|
||||
public ResultBean refreshCache(@PathVariable("driveId") Integer driveId, String key) throws Exception {
|
||||
driveConfigService.refreshCache(driveId, key);
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
@PostMapping("/{driveId}/auto-refresh/start")
|
||||
public ResultBean enableAutoRefresh(@PathVariable("driveId") Integer driveId) {
|
||||
driveConfigService.startAutoCacheRefresh(driveId);
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/{driveId}/auto-refresh/stop")
|
||||
public ResultBean disableAutoRefresh(@PathVariable("driveId") Integer driveId) {
|
||||
driveConfigService.stopAutoCacheRefresh(driveId);
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
@PostMapping("/{driveId}/clear")
|
||||
public ResultBean clearCache(@PathVariable("driveId") Integer driveId) {
|
||||
driveConfigService.clearCache(driveId);
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
package im.zhaojun.zfile.controller.admin;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import im.zhaojun.zfile.model.dto.DriveConfigDTO;
|
||||
import im.zhaojun.zfile.model.entity.DriveConfig;
|
||||
import im.zhaojun.zfile.model.entity.FilterConfig;
|
||||
import im.zhaojun.zfile.model.support.ResultBean;
|
||||
import im.zhaojun.zfile.service.DriveConfigService;
|
||||
import im.zhaojun.zfile.service.FilterConfigService;
|
||||
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")
|
||||
public class DriveController {
|
||||
|
||||
@Resource
|
||||
private DriveConfigService driveConfigService;
|
||||
|
||||
@Resource
|
||||
private FilterConfigService filterConfigService;
|
||||
|
||||
/**
|
||||
* 获取所有驱动器列表
|
||||
*
|
||||
* @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.saveDriveConfigDTO(driveConfigDTO);
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除驱动器设置
|
||||
*
|
||||
* @param id
|
||||
* 驱动器 ID
|
||||
*/
|
||||
@DeleteMapping("/drive/{id}")
|
||||
public ResultBean deleteDriveItem(@PathVariable Integer id) {
|
||||
driveConfigService.deleteById(id);
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 启用驱动器
|
||||
*
|
||||
* @param id
|
||||
* 驱动器 ID
|
||||
*/
|
||||
@PostMapping("/drive/{id}/enable")
|
||||
public ResultBean enable(@PathVariable("id") Integer id) {
|
||||
DriveConfig driveConfig = driveConfigService.findById(id);
|
||||
driveConfig.setEnable(true);
|
||||
driveConfigService.saveOrUpdate(driveConfig);
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 停止驱动器
|
||||
*
|
||||
* @param id
|
||||
* 驱动器 ID
|
||||
*/
|
||||
@PostMapping("/drive/{id}/disable")
|
||||
public ResultBean disable(@PathVariable("id") Integer id) {
|
||||
DriveConfig driveConfig = driveConfigService.findById(id);
|
||||
driveConfig.setEnable(false);
|
||||
driveConfigService.saveOrUpdate(driveConfig);
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/drive/{id}/filters")
|
||||
public ResultBean getFilters(@PathVariable("id") Integer id) {
|
||||
return ResultBean.success(filterConfigService.findByDriveId(id));
|
||||
}
|
||||
|
||||
@PostMapping("/drive/{id}/filters")
|
||||
public ResultBean saveFilters(@RequestBody List<FilterConfig> filter, @PathVariable("id") Integer driveId) {
|
||||
filterConfigService.batchSave(filter, driveId);
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
@PostMapping("/drive/drag")
|
||||
public ResultBean saveDriveDrag(@RequestBody List<JSONObject> driveConfigs) {
|
||||
driveConfigService.saveDriveDrag(driveConfigs);
|
||||
return ResultBean.success();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package im.zhaojun.zfile.controller.admin;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ZipUtil;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 日志相关 Controller
|
||||
* @author zhaojun
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/admin")
|
||||
@Slf4j
|
||||
public class LogController {
|
||||
|
||||
/**
|
||||
* 系统日志下载
|
||||
*/
|
||||
@GetMapping("/log")
|
||||
public ResponseEntity<Object> downloadLog(HttpServletResponse response) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("下载诊断日志");
|
||||
}
|
||||
String userHome = System.getProperty("user.home");
|
||||
File fileZip = ZipUtil.zip(userHome + "/.zfile/logs");
|
||||
String currentDate = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss");
|
||||
return FileUtil.export(fileZip, "ZFile 诊断日志 - " + currentDate + ".zip");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package im.zhaojun.zfile.controller.admin;
|
||||
|
||||
import im.zhaojun.zfile.context.StorageTypeContext;
|
||||
import im.zhaojun.zfile.model.entity.StorageConfig;
|
||||
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.model.support.ResultBean;
|
||||
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 系统元数据 Controller
|
||||
* @author zhaojun
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/admin")
|
||||
public class MateDataController {
|
||||
|
||||
/**
|
||||
* 返回支持的存储引擎.
|
||||
*/
|
||||
@GetMapping("/support-strategy")
|
||||
public ResultBean supportStrategy() {
|
||||
StorageTypeEnum[] values = StorageTypeEnum.values();
|
||||
return ResultBean.successData(values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定存储策略的表单域
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package im.zhaojun.zfile.controller.admin;
|
||||
|
||||
import im.zhaojun.zfile.model.support.ResultBean;
|
||||
import im.zhaojun.zfile.model.support.SystemMonitorInfo;
|
||||
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("/admin")
|
||||
public class MonitorController {
|
||||
|
||||
|
||||
/**
|
||||
* 获取系统监控信息
|
||||
*/
|
||||
@GetMapping("monitor")
|
||||
public ResultBean monitor() {
|
||||
return ResultBean.success(new SystemMonitorInfo());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package im.zhaojun.zfile.controller.home;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import im.zhaojun.zfile.context.DriveContext;
|
||||
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 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 DirectLinkController {
|
||||
|
||||
@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.get(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,243 +0,0 @@
|
||||
package im.zhaojun.zfile.controller.home;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import im.zhaojun.zfile.context.DriveContext;
|
||||
import im.zhaojun.zfile.exception.NotExistFileException;
|
||||
import im.zhaojun.zfile.model.constant.ZFileConstant;
|
||||
import im.zhaojun.zfile.model.dto.FileItemDTO;
|
||||
import im.zhaojun.zfile.model.dto.SystemFrontConfigDTO;
|
||||
import im.zhaojun.zfile.model.entity.DriveConfig;
|
||||
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.model.support.FilePageModel;
|
||||
import im.zhaojun.zfile.model.support.ResultBean;
|
||||
import im.zhaojun.zfile.service.DriveConfigService;
|
||||
import im.zhaojun.zfile.service.FilterConfigService;
|
||||
import im.zhaojun.zfile.service.SystemConfigService;
|
||||
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
|
||||
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;
|
||||
|
||||
@Resource
|
||||
private FilterConfigService filterConfigService;
|
||||
|
||||
/**
|
||||
* 滚动加载每页条数.
|
||||
*/
|
||||
private static final Integer PAGE_SIZE = 30;
|
||||
|
||||
|
||||
/**
|
||||
* 获取所有已启用的驱动器
|
||||
*
|
||||
* @return 所有已启用驱动器
|
||||
*/
|
||||
@GetMapping("/drive/list")
|
||||
public ResultBean drives() {
|
||||
return ResultBean.success(driveConfigService.listOnlyEnable());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某个驱动器下, 指定路径的数据, 每页固定 {@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.get(driveId);
|
||||
List<FileItemDTO> fileItemList =
|
||||
fileService.fileList(StringUtils.removeDuplicateSeparator(ZFileConstant.PATH_SEPARATOR + path + ZFileConstant.PATH_SEPARATOR));
|
||||
|
||||
for (FileItemDTO fileItemDTO : fileItemList) {
|
||||
if (ZFileConstant.PASSWORD_FILE_NAME.equals(fileItemDTO.getName())) {
|
||||
String expectedPasswordContent;
|
||||
try {
|
||||
expectedPasswordContent = HttpUtil.getTextContent(fileItemDTO.getUrl());
|
||||
} catch (HttpClientErrorException httpClientErrorException) {
|
||||
log.error("尝试重新获取密码文件缓存中链接后仍失败, driveId: {}, path: {}, inputPassword: {}, passwordFile:{} ",
|
||||
driveId, path, password, JSON.toJSONString(fileItemDTO), httpClientErrorException);
|
||||
try {
|
||||
String fullPath = StringUtils.removeDuplicateSeparator(fileItemDTO.getPath() + ZFileConstant.PATH_SEPARATOR + fileItemDTO.getName());
|
||||
FileItemDTO fileItem = fileService.getFileItem(fullPath);
|
||||
expectedPasswordContent = HttpUtil.getTextContent(fileItem.getUrl());
|
||||
} catch (Exception e) {
|
||||
log.error("尝试重新获取密码文件链接后仍失败, 已暂时取消密码", 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤掉表达式中不存在的数据.
|
||||
fileItemList.removeIf(next -> filterConfigService.filterResultIsHidden(driveId, StringUtils.concatUrl(next.getPath(), next.getName())));
|
||||
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.get(driveId);
|
||||
DriveConfig driveConfig = driveConfigService.findById(driveId);
|
||||
String fullPath = StringUtils.removeDuplicateSeparator(path + ZFileConstant.PATH_SEPARATOR + ZFileConstant.README_FILE_NAME);
|
||||
FileItemDTO fileItem = null;
|
||||
try {
|
||||
fileItem = fileService.getFileItem(fullPath);
|
||||
|
||||
if (!Objects.equals(driveConfig.getType(), StorageTypeEnum.FTP)) {
|
||||
String readme = HttpUtil.getTextContent(fileItem.getUrl());
|
||||
systemConfig.setReadme(readme);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof NotExistFileException) {
|
||||
log.debug("不存在 README 文件, 已跳过, fullPath: {}, fileItem: {}", fullPath, JSON.toJSONString(fileItem));
|
||||
} else {
|
||||
log.error("获取 README 文件异常, fullPath: {}, fileItem: {}", fullPath, JSON.toJSONString(fileItem), e);
|
||||
}
|
||||
}
|
||||
|
||||
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.get(driveId);
|
||||
return ResultBean.successData(fileService.getFileItem(path));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package im.zhaojun.zfile.controller.home;
|
||||
|
||||
import im.zhaojun.zfile.model.support.ResultBean;
|
||||
import im.zhaojun.zfile.util.AudioUtil;
|
||||
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 FileParseController {
|
||||
|
||||
/**
|
||||
* 获取文件内容, 仅限用于 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(AudioUtil.getAudioInfo(url));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package im.zhaojun.zfile.controller.home;
|
||||
|
||||
import im.zhaojun.zfile.context.DriveContext;
|
||||
import im.zhaojun.zfile.model.constant.ZFileConstant;
|
||||
import im.zhaojun.zfile.service.impl.LocalServiceImpl;
|
||||
import im.zhaojun.zfile.util.FileUtil;
|
||||
import im.zhaojun.zfile.util.StringUtils;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
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.get(driveId);
|
||||
return FileUtil.export(new File(StringUtils.removeDuplicateSeparator(localService.getFilePath() + ZFileConstant.PATH_SEPARATOR + filePath)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package im.zhaojun.zfile.controller.install;
|
||||
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import im.zhaojun.zfile.model.dto.SystemConfigDTO;
|
||||
import im.zhaojun.zfile.model.support.ResultBean;
|
||||
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;
|
||||
|
||||
@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,43 +0,0 @@
|
||||
package im.zhaojun.zfile.controller.onedrive;
|
||||
|
||||
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 OneDriveCallbackController {
|
||||
|
||||
@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";
|
||||
}
|
||||
|
||||
}
|
||||
33
src/main/java/im/zhaojun/zfile/core/annotation/ApiLimit.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package im.zhaojun.zfile.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 接口限流注解
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ApiLimit {
|
||||
|
||||
/**
|
||||
* 持续时间
|
||||
*/
|
||||
int timeout();
|
||||
|
||||
/**
|
||||
* 时间单位, 默认为秒
|
||||
*/
|
||||
TimeUnit timeUnit() default TimeUnit.SECONDS;
|
||||
|
||||
/**
|
||||
* 单位时间内允许访问的最大次数
|
||||
*/
|
||||
long maxCount();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package im.zhaojun.zfile.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 演示系统禁用功能注解
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DemoDisable {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package im.zhaojun.zfile.core.aspect;
|
||||
|
||||
import cn.hutool.cache.CacheUtil;
|
||||
import cn.hutool.cache.impl.TimedCache;
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import im.zhaojun.zfile.core.annotation.ApiLimit;
|
||||
import im.zhaojun.zfile.core.exception.ErrorCode;
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
import im.zhaojun.zfile.core.util.RequestHolder;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* 接口限流切面, 通过注解 {@link ApiLimit} 进行限流.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class ApiLimitAspect {
|
||||
|
||||
private final TimedCache<String, AtomicLong> apiLimitTimedCache = CacheUtil.newTimedCache(1000);
|
||||
|
||||
public static final String API_LIMIT_KEY_PREFIX = "api_limit_";
|
||||
|
||||
/**
|
||||
* 定义一个切点(通过注解)
|
||||
*/
|
||||
@Pointcut("@annotation(im.zhaojun.zfile.core.annotation.ApiLimit)")
|
||||
public void apiLimit() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在标记了 {@link ApiLimit} 注解的方法执行前进行限流校验.
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
*/
|
||||
@Before("apiLimit()")
|
||||
public void before(JoinPoint joinPoint) {
|
||||
// 获取当前请求的方法上的注解中设置的值
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
// 反射获取当前被调用的方法
|
||||
Method method = signature.getMethod();
|
||||
// 获取方法中的注解
|
||||
ApiLimit apiLimit = method.getDeclaredAnnotation(ApiLimit.class);
|
||||
int timeout = apiLimit.timeout();
|
||||
TimeUnit timeUnit = apiLimit.timeUnit();
|
||||
long millis = timeUnit.toMillis(timeout);
|
||||
long maxCount = apiLimit.maxCount();
|
||||
|
||||
// 获取请求相关信息
|
||||
String ip = JakartaServletUtil.getClientIP(RequestHolder.getRequest());
|
||||
|
||||
// 限制访问次数
|
||||
String key = API_LIMIT_KEY_PREFIX.concat(ip).concat(method.getName());
|
||||
AtomicLong atomicLong = apiLimitTimedCache.get(key, false);
|
||||
if (atomicLong == null) {
|
||||
apiLimitTimedCache.put(key, new AtomicLong(1), millis);
|
||||
} else {
|
||||
if (atomicLong.incrementAndGet() > maxCount) {
|
||||
throw new BizException(ErrorCode.BIZ_ACCESS_TOO_FREQUENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package im.zhaojun.zfile.core.aspect;
|
||||
|
||||
import im.zhaojun.zfile.core.constant.MdcConstant;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Controller 切面, 用于处理返回值统一封装.
|
||||
*
|
||||
* @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<?> baseResponse) {
|
||||
// 将 MDC 中的 TraceId 设置到返回值中
|
||||
baseResponse.setTraceId(MDC.get(MdcConstant.TRACE_ID));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package im.zhaojun.zfile.core.aspect;
|
||||
|
||||
import im.zhaojun.zfile.core.annotation.DemoDisable;
|
||||
import im.zhaojun.zfile.core.config.ZFileProperties;
|
||||
import im.zhaojun.zfile.core.exception.ErrorCode;
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 通过注解 {@link DemoDisable} 限制演示系统不可操作的功能.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class DemoDisableAspect {
|
||||
|
||||
@Resource
|
||||
private ZFileProperties zFileProperties;
|
||||
|
||||
/**
|
||||
* 定义一个切点(通过注解)
|
||||
*/
|
||||
@Pointcut("@annotation(im.zhaojun.zfile.core.annotation.DemoDisable)")
|
||||
public void demoDisable() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在标记了 {@link DemoDisable} 注解的方法执行前进行限流校验.
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
*/
|
||||
@Before("demoDisable()")
|
||||
public void before(JoinPoint joinPoint) {
|
||||
if (zFileProperties.isDemoSite()) {
|
||||
throw new BizException(ErrorCode.DEMO_SITE_DISABLE_OPERATOR);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* ZFile 配置类,将配置文件中的 zfile 配置项映射到该类中.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@EnableConfigurationProperties
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "zfile")
|
||||
public class ZFileProperties {
|
||||
|
||||
private boolean debug;
|
||||
|
||||
private String version;
|
||||
|
||||
private boolean isDemoSite;
|
||||
|
||||
private OAuth2Properties onedrive = new OAuth2Properties();
|
||||
private OAuth2Properties onedriveChina = new OAuth2Properties();
|
||||
private OAuth2Properties gd = new OAuth2Properties();
|
||||
|
||||
@Data
|
||||
public static class OAuth2Properties {
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private String redirectUri;
|
||||
private String scope;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package im.zhaojun.zfile.core.config.datasource;
|
||||
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import im.zhaojun.zfile.core.util.StringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.autoconfigure.flyway.FlywayProperties;
|
||||
import org.springframework.core.PriorityOrdered;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 在 Spring 容器初始化时, 对数据源进行处理.
|
||||
* <br/>
|
||||
* 1. 针对 DataSource 进行处理,仅针对 sqlite:
|
||||
* <ul>
|
||||
* <li>提前创建 sqlite 数据文件所在目录.</li>
|
||||
* <li>检测到版本更新时(pom.xml -> project.version)自动备份原数据库.</li>
|
||||
* </ul>
|
||||
* <br/>
|
||||
* 2. 针对 Flyway 进行处理,根据数据库类型, 配置不同的 Flyway Migration Location:
|
||||
* <ul>
|
||||
* <li>SQLite 数据库使用 migration-sqlite 目录.</li>
|
||||
* <li>MySQL 数据库使用 migration-mysql 目录.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class DataSourceBeanPostProcessor implements BeanPostProcessor, PriorityOrdered {
|
||||
|
||||
public static final String ZFILE_VERSION_PROPERTIES = "zfile.version";
|
||||
|
||||
public static final String DRIVE_CLASS_NAME_PROPERTIES = "spring.datasource.driver-class-name";
|
||||
|
||||
public static final String DATA_SOURCE_BEAN_NAME = "dataSource";
|
||||
|
||||
public static final String SQLITE_DRIVE_CLASS_NAME = "org.sqlite.JDBC";
|
||||
|
||||
public static final String MYSQL_DRIVE_CLASS_NAME = "com.mysql.cj.jdbc.Driver";
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
// 如果更改了数据源类型这里要修改
|
||||
if (bean instanceof HikariDataSource dataSource && DATA_SOURCE_BEAN_NAME.equals(beanName)) {
|
||||
processSqliteDataSource(dataSource);
|
||||
} else if (bean instanceof FlywayProperties flywayProperties) {
|
||||
processFlywayLocations(flywayProperties);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果是 sqlite 数据库, 提前创建数据库文件所在目录. <br/>
|
||||
*
|
||||
* 如果检测到版本更新, 自动备份原数据库文件.
|
||||
*
|
||||
* @param dataSource
|
||||
* 数据源
|
||||
*/
|
||||
private void processSqliteDataSource(HikariDataSource dataSource) {
|
||||
String driverClassName = dataSource.getDriverClassName();
|
||||
String jdbcUrl = dataSource.getJdbcUrl();
|
||||
if (StringUtils.equals(driverClassName, SQLITE_DRIVE_CLASS_NAME)) {
|
||||
String path = jdbcUrl.replace("jdbc:sqlite:", "");
|
||||
String folderPath = FileUtil.getAbsolutePath(new File(path).getParentFile());
|
||||
log.info("SQLite 数据库文件所在目录: [{}]", folderPath);
|
||||
File file = new File(folderPath);
|
||||
if (!file.exists()) {
|
||||
log.info("检测到 SQLite 数据库文件所在目录不存在, 已自动创建.");
|
||||
if (!file.mkdirs()) {
|
||||
log.error("SQLite 数据库文件创建失败.");
|
||||
}
|
||||
} else {
|
||||
log.info("检测到 SQLite 数据库文件所在目录已存在, 无需自动创建.");
|
||||
|
||||
// 更新版本时, 先自动备份数据库文件
|
||||
String version = SpringUtil.getProperty(ZFILE_VERSION_PROPERTIES);
|
||||
if (StringUtils.isNotEmpty(version)) {
|
||||
String backupPath = folderPath + "/zfile-update-" + version + "-backup.db";
|
||||
if (!FileUtil.exist(path)) {
|
||||
log.error("检测到 SQLite 数据库文件不存在, 一般为初始化状态,无需备份.");
|
||||
return;
|
||||
}
|
||||
if (FileUtil.exist(backupPath)) {
|
||||
log.info("检测到 SQLite 数据库备份文件 [{}] 已存在, 无需再次备份.", backupPath);
|
||||
} else {
|
||||
FileUtil.copy(path, backupPath, false);
|
||||
log.info("自动备份 SQLite 数据库文件到: [{}]", backupPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据使用的不同数据库, 配置使用不同的 migration location
|
||||
*
|
||||
* @param flywayProperties
|
||||
* flyway 配置项
|
||||
*/
|
||||
private void processFlywayLocations(FlywayProperties flywayProperties) {
|
||||
String driveClassName = SpringUtil.getProperty(DRIVE_CLASS_NAME_PROPERTIES);
|
||||
if (SQLITE_DRIVE_CLASS_NAME.equals(driveClassName)) {
|
||||
flywayProperties.setLocations(List.of("classpath:db/migration-sqlite"));
|
||||
} else if (MYSQL_DRIVE_CLASS_NAME.equals(driveClassName)) {
|
||||
flywayProperties.setLocations(List.of("classpath:db/migration-mysql"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package im.zhaojun.zfile.core.config.docs;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.media.StringSchema;
|
||||
import io.swagger.v3.oas.models.parameters.HeaderParameter;
|
||||
import org.springdoc.core.customizers.OperationCustomizer;
|
||||
import org.springdoc.core.models.GroupedOpenApi;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Knife4j 参数配置,区分前台功能和管理员功能,并为管理员接口增加统一 token header 配置.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
public class Knife4jConfiguration {
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi groupedOpenApi() {
|
||||
String groupName = "前台功能";
|
||||
return GroupedOpenApi.builder()
|
||||
.group(groupName)
|
||||
.packagesToScan("im.zhaojun.zfile.module")
|
||||
.pathsToExclude("/admin/**")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi groupedOpenApi2() {
|
||||
String groupName = "管理员功能";
|
||||
return GroupedOpenApi.builder()
|
||||
.group(groupName)
|
||||
.packagesToScan("im.zhaojun.zfile.module")
|
||||
.pathsToMatch("/admin/**")
|
||||
.addOperationCustomizer(globalOperationCustomizer())
|
||||
.build();
|
||||
}
|
||||
|
||||
public OperationCustomizer globalOperationCustomizer() {
|
||||
return (operation, handlerMethod) -> {
|
||||
operation.addParametersItem(new HeaderParameter()
|
||||
.name("zfile-token")
|
||||
.description("token")
|
||||
.required(true)
|
||||
.schema(new StringSchema()));
|
||||
return operation;
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
Contact contact = new Contact();
|
||||
contact.setName("zhaojun");
|
||||
contact.setUrl("https://zfile.vip");
|
||||
contact.setEmail("873019219@qq.com");
|
||||
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("ZFILE 文档")
|
||||
.description("# 这是 ZFILE Restful 接口文档展示页面")
|
||||
.termsOfService("https://www.zfile.vip")
|
||||
.contact(contact)
|
||||
.version("1.0")
|
||||
.license(new License()
|
||||
.name("Apache 2.0")
|
||||
.url("http://doc.xiaominfo.com")));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package im.zhaojun.zfile.core.config.jackson;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* JSON String 反序列化器, 用于将 JSON 字符串反序列化为 JSON 对象.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class JSONStringDeserializer extends JsonDeserializer<String> {
|
||||
|
||||
@Override
|
||||
public String deserialize(JsonParser p, DeserializationContext context) throws IOException {
|
||||
JsonNode node = p.getCodec().readTree(p);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
return mapper.writeValueAsString(node);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package im.zhaojun.zfile.core.config.jackson;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* JSON String 序列化器, 用于将 JSON 字符串序列化为 JSON 对象.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class JSONStringSerializer extends JsonSerializer<String> {
|
||||
|
||||
@Override
|
||||
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
gen.writeRawValue(value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package im.zhaojun.zfile.core.config.mybatis;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class CollectionIntegerTypeHandler extends CollectionTypeHandler<Set<Integer>> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package im.zhaojun.zfile.core.config.mybatis;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class CollectionStrTypeHandler extends CollectionTypeHandler<Set<String>> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package im.zhaojun.zfile.core.config.mybatis;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.MappedJdbcTypes;
|
||||
import org.apache.ibatis.type.MappedTypes;
|
||||
import org.springframework.core.ResolvableType;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 自定义 Set 类型处理器, 用于处理数据库 VARCHAR 类型字段和 Java Set 类型属性之间的转换.
|
||||
* 支持字符串格式为: "[a, b, c]".
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@MappedJdbcTypes(JdbcType.VARCHAR)
|
||||
public abstract class CollectionTypeHandler<T> extends BaseTypeHandler<Object> {
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
|
||||
throws SQLException {
|
||||
if (parameter instanceof Collection collection) {
|
||||
StringJoiner joiner = new StringJoiner(",");
|
||||
for (Object o : collection) {
|
||||
joiner.add(Convert.toStr(o));
|
||||
}
|
||||
ps.setString(i, joiner.toString());
|
||||
} else {
|
||||
ps.setString(i, Convert.toStr(parameter));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(ResultSet rs, String columnName)
|
||||
throws SQLException {
|
||||
String str = rs.getString(columnName);
|
||||
return convertToEntityAttribute(str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(ResultSet rs, int columnIndex)
|
||||
throws SQLException {
|
||||
String str = rs.getString(columnIndex);
|
||||
return convertToEntityAttribute(str);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getNullableResult(CallableStatement cs, int columnIndex)
|
||||
throws SQLException {
|
||||
String str = cs.getString(columnIndex);
|
||||
return convertToEntityAttribute(str);
|
||||
}
|
||||
|
||||
private Class<?> collectionClazz;
|
||||
|
||||
private Type innerType;
|
||||
|
||||
/**
|
||||
* 构造方法
|
||||
*/
|
||||
public CollectionTypeHandler() {
|
||||
ResolvableType resolvableType = ResolvableType.forClass(getClass());
|
||||
Type type = resolvableType.as(CollectionTypeHandler.class).getGeneric().getType();
|
||||
|
||||
if (type instanceof ParameterizedType parameterizedType) {
|
||||
collectionClazz = (Class<?>) parameterizedType.getRawType();
|
||||
// 获取实际类型参数(泛型参数,例如 List<String> 中的 String)
|
||||
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
|
||||
|
||||
// 使用这些信息做进一步操作
|
||||
for (Type actualTypeArgument : actualTypeArguments) {
|
||||
innerType = actualTypeArgument;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object convertToEntityAttribute(String dbData) {
|
||||
if (StrUtil.isEmpty(dbData)) {
|
||||
if (List.class.isAssignableFrom(collectionClazz)) {
|
||||
return Collections.emptyList();
|
||||
} else if (Set.class.isAssignableFrom(collectionClazz)) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Collection collection;
|
||||
|
||||
if (List.class.isAssignableFrom(collectionClazz)) {
|
||||
collection = new ArrayList<>();
|
||||
} else if (Set.class.isAssignableFrom(collectionClazz)) {
|
||||
collection = new HashSet<>();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] split = dbData.split(",");
|
||||
for (String s : split) {
|
||||
if (NumberUtil.isNumber(s)) {
|
||||
collection.add(Convert.convert(Integer.class, s));
|
||||
} else {
|
||||
collection.add(s);
|
||||
}
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package im.zhaojun.zfile.core.config.mybatis;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* mybatis-plus 配置类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
public class MyBatisPlusConfig {
|
||||
|
||||
/**
|
||||
* mybatis plus 分页插件配置
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor(DataSource dataSource) 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,41 @@
|
||||
package im.zhaojun.zfile.core.config.mybatis;
|
||||
|
||||
import org.apache.ibatis.mapping.DatabaseIdProvider;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* MyBatis 数据库 ID Provider, 用于判断当前数据库类型来执行不同的 SQL 语句. <br>
|
||||
* 可在 xml 中使用 <code><if test="_databaseId = 'mysql'"> </code> 来判断数据库类型. <br>
|
||||
* 也可以在外层使用,如 <code><delete id="xxx" databaseId="sqlite"></code> 来判断数据库类型.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@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,29 @@
|
||||
package im.zhaojun.zfile.core.config.mybatis;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* MyBatis Plus 自动填充配置类
|
||||
* 用于自动填充 createTime 和 updateTime 字段
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MyMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package im.zhaojun.zfile.core.config.security;
|
||||
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
/**
|
||||
* Jackson 定制版 SaSession,忽略 timeout 等属性的序列化
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.34.0
|
||||
*/
|
||||
@JsonIgnoreProperties({"timeout"})
|
||||
public class SaSessionForJacksonCustomized extends SaSession {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -7600983549653130681L;
|
||||
|
||||
public SaSessionForJacksonCustomized() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建一个Session对象
|
||||
* @param id Session的id
|
||||
*/
|
||||
public SaSessionForJacksonCustomized(String id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package im.zhaojun.zfile.core.config.security;
|
||||
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* SaToken 权限配置, 配置管理员才能访问管理员功能.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 注册权限校验拦截器, 拦截所有 /admin/** 请求,但不包含 /admin 因为这个是登录页面.
|
||||
*
|
||||
* @param registry
|
||||
* 拦截器注册器
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new SaInterceptor(handle -> {
|
||||
SaRouter.match("/admin/**", () -> {
|
||||
StpUtil.checkLogin();
|
||||
StpUtil.checkRole("admin");
|
||||
});
|
||||
})).addPathPatterns("/**").excludePathPatterns("/admin");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
//
|
||||
// Source code recreated from a .class file by IntelliJ IDEA
|
||||
// (powered by FernFlower decompiler)
|
||||
//
|
||||
|
||||
package im.zhaojun.zfile.core.config.security;
|
||||
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Sa-Token 持久层实现 [ Redis存储、Jackson序列化 ]
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.34.0
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "spring.data.redis.host")
|
||||
public class SaTokenDaoRedisJackson implements SaTokenDao {
|
||||
|
||||
public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
|
||||
public static final String DATE_PATTERN = "yyyy-MM-dd";
|
||||
public static final String TIME_PATTERN = "HH:mm:ss";
|
||||
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);
|
||||
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_PATTERN);
|
||||
public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(TIME_PATTERN);
|
||||
|
||||
/**
|
||||
* ObjectMapper 对象 (以 public 作用域暴露出此对象,方便开发者二次更改配置)
|
||||
*
|
||||
* <p> 例如:
|
||||
* <pre>
|
||||
* SaTokenDaoRedisJackson redisJackson = (SaTokenDaoRedisJackson) SaManager.getSaTokenDao();
|
||||
* redisJackson.objectMapper.xxx = xxx;
|
||||
* </pre>
|
||||
* </p>
|
||||
*/
|
||||
public ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* String 读写专用
|
||||
*/
|
||||
public StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
/**
|
||||
* Object 读写专用
|
||||
*/
|
||||
public RedisTemplate<String, Object> objectRedisTemplate;
|
||||
|
||||
/**
|
||||
* 标记:是否已初始化成功
|
||||
*/
|
||||
public boolean isInit;
|
||||
|
||||
@Autowired
|
||||
public void init(RedisConnectionFactory connectionFactory) {
|
||||
// 如果已经初始化成功了,就立刻退出,不重复初始化
|
||||
if(this.isInit) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 指定相应的序列化方案
|
||||
StringRedisSerializer keySerializer = new StringRedisSerializer();
|
||||
GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer();
|
||||
|
||||
// 通过反射获取Mapper对象, 增加一些配置, 增强兼容性
|
||||
try {
|
||||
Field field = GenericJackson2JsonRedisSerializer.class.getDeclaredField("mapper");
|
||||
field.setAccessible(true);
|
||||
this.objectMapper = (ObjectMapper) field.get(valueSerializer);
|
||||
|
||||
// 配置[忽略未知字段]
|
||||
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
// 配置[时间类型转换]
|
||||
JavaTimeModule timeModule = new JavaTimeModule();
|
||||
|
||||
// LocalDateTime序列化与反序列化
|
||||
timeModule.addSerializer(new LocalDateTimeSerializer(DATE_TIME_FORMATTER));
|
||||
timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER));
|
||||
|
||||
// LocalDate序列化与反序列化
|
||||
timeModule.addSerializer(new LocalDateSerializer(DATE_FORMATTER));
|
||||
timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DATE_FORMATTER));
|
||||
|
||||
// LocalTime序列化与反序列化
|
||||
timeModule.addSerializer(new LocalTimeSerializer(TIME_FORMATTER));
|
||||
timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(TIME_FORMATTER));
|
||||
|
||||
this.objectMapper.registerModule(timeModule);
|
||||
|
||||
// 重写 SaSession 生成策略
|
||||
SaStrategy.instance.createSession = (sessionId) -> new SaSessionForJacksonCustomized(sessionId);
|
||||
} catch (Exception e) {
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
// 构建StringRedisTemplate
|
||||
StringRedisTemplate stringTemplate = new StringRedisTemplate();
|
||||
stringTemplate.setConnectionFactory(connectionFactory);
|
||||
stringTemplate.afterPropertiesSet();
|
||||
|
||||
// 构建RedisTemplate
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
template.setKeySerializer(keySerializer);
|
||||
template.setHashKeySerializer(keySerializer);
|
||||
template.setValueSerializer(valueSerializer);
|
||||
template.setHashValueSerializer(valueSerializer);
|
||||
template.afterPropertiesSet();
|
||||
|
||||
// 开始初始化相关组件
|
||||
this.stringRedisTemplate = stringTemplate;
|
||||
this.objectRedisTemplate = template;
|
||||
|
||||
// 打上标记,表示已经初始化成功,后续无需再重新初始化
|
||||
this.isInit = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取Value,如无返空
|
||||
*/
|
||||
@Override
|
||||
public String get(String key) {
|
||||
return stringRedisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入Value,并设定存活时间 (单位: 秒)
|
||||
*/
|
||||
@Override
|
||||
public void set(String key, String value, long timeout) {
|
||||
if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
// 判断是否为永不过期
|
||||
if(timeout == SaTokenDao.NEVER_EXPIRE) {
|
||||
stringRedisTemplate.opsForValue().set(key, value);
|
||||
} else {
|
||||
stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修修改指定key-value键值对 (过期时间不变)
|
||||
*/
|
||||
@Override
|
||||
public void update(String key, String value) {
|
||||
long expire = getTimeout(key);
|
||||
// -2 = 无此键
|
||||
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
this.set(key, value, expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Value
|
||||
*/
|
||||
@Override
|
||||
public void delete(String key) {
|
||||
stringRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Value的剩余存活时间 (单位: 秒)
|
||||
*/
|
||||
@Override
|
||||
public long getTimeout(String key) {
|
||||
return stringRedisTemplate.getExpire(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改Value的剩余存活时间 (单位: 秒)
|
||||
*/
|
||||
@Override
|
||||
public void updateTimeout(String key, long timeout) {
|
||||
// 判断是否想要设置为永久
|
||||
if(timeout == SaTokenDao.NEVER_EXPIRE) {
|
||||
long expire = getTimeout(key);
|
||||
if(expire == SaTokenDao.NEVER_EXPIRE) {
|
||||
// 如果其已经被设置为永久,则不作任何处理
|
||||
} else {
|
||||
// 如果尚未被设置为永久,那么再次set一次
|
||||
this.set(key, this.get(key), timeout);
|
||||
}
|
||||
return;
|
||||
}
|
||||
stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取Object,如无返空
|
||||
*/
|
||||
@Override
|
||||
public Object getObject(String key) {
|
||||
return objectRedisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入Object,并设定存活时间 (单位: 秒)
|
||||
*/
|
||||
@Override
|
||||
public void setObject(String key, Object object, long timeout) {
|
||||
if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
// 判断是否为永不过期
|
||||
if(timeout == SaTokenDao.NEVER_EXPIRE) {
|
||||
objectRedisTemplate.opsForValue().set(key, object);
|
||||
} else {
|
||||
objectRedisTemplate.opsForValue().set(key, object, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新Object (过期时间不变)
|
||||
*/
|
||||
@Override
|
||||
public void updateObject(String key, Object object) {
|
||||
long expire = getObjectTimeout(key);
|
||||
// -2 = 无此键
|
||||
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
this.setObject(key, object, expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除Object
|
||||
*/
|
||||
@Override
|
||||
public void deleteObject(String key) {
|
||||
objectRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Object的剩余存活时间 (单位: 秒)
|
||||
*/
|
||||
@Override
|
||||
public long getObjectTimeout(String key) {
|
||||
return objectRedisTemplate.getExpire(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改Object的剩余存活时间 (单位: 秒)
|
||||
*/
|
||||
@Override
|
||||
public void updateObjectTimeout(String key, long timeout) {
|
||||
// 判断是否想要设置为永久
|
||||
if(timeout == SaTokenDao.NEVER_EXPIRE) {
|
||||
long expire = getObjectTimeout(key);
|
||||
if(expire == SaTokenDao.NEVER_EXPIRE) {
|
||||
// 如果其已经被设置为永久,则不作任何处理
|
||||
} else {
|
||||
// 如果尚未被设置为永久,那么再次set一次
|
||||
this.setObject(key, this.getObject(key), timeout);
|
||||
}
|
||||
return;
|
||||
}
|
||||
objectRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 搜索数据
|
||||
*/
|
||||
@Override
|
||||
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
|
||||
Set<String> keys = stringRedisTemplate.keys(prefix + "*" + keyword + "*");
|
||||
List<String> list = new ArrayList<>(keys);
|
||||
return SaFoxUtil.searchList(list, start, size, sortType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package im.zhaojun.zfile.core.config.security;
|
||||
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import im.zhaojun.zfile.module.user.service.UserService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 自定义权限加载接口实现类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Component
|
||||
public class StpInterfaceImpl implements StpInterface {
|
||||
|
||||
private static final List<String> ADMIN_ROLE_LIST = Collections.singletonList("admin");
|
||||
|
||||
public static final List<String> EMPTY_ROLE_LIST = Collections.emptyList();
|
||||
|
||||
@Resource
|
||||
private UserService userService;
|
||||
|
||||
/**
|
||||
* 返回一个账号所拥有的权限码集合,这里没用到这个功能,所以返回空集合
|
||||
*/
|
||||
@Override
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
|
||||
*/
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
boolean isAdmin = userService.isAdmin(Convert.toInt(loginId));
|
||||
return isAdmin ? ADMIN_ROLE_LIST : EMPTY_ROLE_LIST;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package im.zhaojun.zfile.core.config.spring;
|
||||
|
||||
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
|
||||
*/
|
||||
@Setter
|
||||
@Slf4j
|
||||
@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,36 @@
|
||||
package im.zhaojun.zfile.core.config.spring;
|
||||
|
||||
import im.zhaojun.zfile.core.config.security.SaTokenDaoRedisJackson;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
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
|
||||
@ConditionalOnMissingBean(SaTokenDaoRedisJackson.class)
|
||||
public CacheManager cacheManager() {
|
||||
return BooleanUtils.isNotTrue(dbCacheEnable) ? new NoOpCacheManager() : new TransactionAwareCacheManagerProxy(new ConcurrentMapCacheManager());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package im.zhaojun.zfile.core.config.spring;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import im.zhaojun.zfile.core.exception.core.SystemException;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterFactory;
|
||||
|
||||
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 SystemException("该字符串找不到对应的枚举对象 字符串:" + 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 SystemException(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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.zfile.config;
|
||||
package im.zhaojun.zfile.core.config.spring;
|
||||
|
||||
import im.zhaojun.zfile.model.enums.StorageTypeEnumDeSerializerConvert;
|
||||
import im.zhaojun.zfile.module.storage.model.enums.StorageTypeEnum;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -9,26 +9,35 @@ import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* ZFile Web 相关配置.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverter(new StorageTypeEnumDeSerializerConvert());
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持 url 中传入 <>[\]^`{|} 这些特殊字符.
|
||||
*/
|
||||
@Bean
|
||||
public ServletWebServerFactory webServerFactory() {
|
||||
TomcatServletWebServerFactory webServerFactory = new TomcatServletWebServerFactory();
|
||||
|
||||
// 添加对 URL 中特殊符号的支持.
|
||||
webServerFactory.addConnectorCustomizers(connector -> {
|
||||
connector.setAttribute("relaxedPathChars", "<>[\\]^`{|}");
|
||||
connector.setAttribute("relaxedQueryChars", "<>[\\]^`{|}");
|
||||
connector.setProperty("relaxedPathChars", "<>[\\]^`{|}%[]");
|
||||
connector.setProperty("relaxedQueryChars", "<>[\\]^`{|}%[]");
|
||||
});
|
||||
return webServerFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义枚举格式化器.
|
||||
* @see StorageTypeEnum
|
||||
*/
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverterFactory(new StringToEnumConverterFactory());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package im.zhaojun.zfile.core.config.totp;
|
||||
|
||||
import dev.samstevens.totp.TotpInfo;
|
||||
import dev.samstevens.totp.code.*;
|
||||
import dev.samstevens.totp.qr.QrDataFactory;
|
||||
import dev.samstevens.totp.qr.QrGenerator;
|
||||
import dev.samstevens.totp.qr.ZxingPngQrGenerator;
|
||||
import dev.samstevens.totp.recovery.RecoveryCodeGenerator;
|
||||
import dev.samstevens.totp.secret.DefaultSecretGenerator;
|
||||
import dev.samstevens.totp.secret.SecretGenerator;
|
||||
import dev.samstevens.totp.time.SystemTimeProvider;
|
||||
import dev.samstevens.totp.time.TimeProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass({TotpInfo.class})
|
||||
@EnableConfigurationProperties({TotpProperties.class})
|
||||
public class TotpAutoConfiguration {
|
||||
|
||||
private final TotpProperties props;
|
||||
|
||||
@Autowired
|
||||
public TotpAutoConfiguration(TotpProperties props) {
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public SecretGenerator secretGenerator() {
|
||||
int length = this.props.getSecret().getLength();
|
||||
return new DefaultSecretGenerator(length);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TimeProvider timeProvider() {
|
||||
return new SystemTimeProvider();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public HashingAlgorithm hashingAlgorithm() {
|
||||
return HashingAlgorithm.SHA1;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public QrDataFactory qrDataFactory(HashingAlgorithm hashingAlgorithm) {
|
||||
return new QrDataFactory(hashingAlgorithm, this.getCodeLength(), this.getTimePeriod());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public QrGenerator qrGenerator() {
|
||||
return new ZxingPngQrGenerator();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CodeGenerator codeGenerator(HashingAlgorithm algorithm) {
|
||||
return new DefaultCodeGenerator(algorithm, this.getCodeLength());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public CodeVerifier codeVerifier(CodeGenerator codeGenerator, TimeProvider timeProvider) {
|
||||
DefaultCodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider);
|
||||
verifier.setTimePeriod(this.getTimePeriod());
|
||||
verifier.setAllowedTimePeriodDiscrepancy(this.props.getTime().getDiscrepancy());
|
||||
return verifier;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RecoveryCodeGenerator recoveryCodeGenerator() {
|
||||
return new RecoveryCodeGenerator();
|
||||
}
|
||||
|
||||
private int getCodeLength() {
|
||||
return this.props.getCode().getLength();
|
||||
}
|
||||
|
||||
private int getTimePeriod() {
|
||||
return this.props.getTime().getPeriod();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package im.zhaojun.zfile.core.config.totp;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@ConfigurationProperties(
|
||||
prefix = "totp"
|
||||
)
|
||||
public class TotpProperties {
|
||||
private static final int DEFAULT_SECRET_LENGTH = 32;
|
||||
private static final int DEFAULT_CODE_LENGTH = 6;
|
||||
private static final int DEFAULT_TIME_PERIOD = 30;
|
||||
private static final int DEFAULT_TIME_DISCREPANCY = 1;
|
||||
private final Secret secret = new Secret();
|
||||
private final Code code = new Code();
|
||||
private final Time time = new Time();
|
||||
|
||||
public TotpProperties() {
|
||||
}
|
||||
|
||||
public Secret getSecret() {
|
||||
return this.secret;
|
||||
}
|
||||
|
||||
public Code getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public Time getTime() {
|
||||
return this.time;
|
||||
}
|
||||
|
||||
public static class Time {
|
||||
private int period = 30;
|
||||
private int discrepancy = 1;
|
||||
|
||||
public Time() {
|
||||
}
|
||||
|
||||
public int getPeriod() {
|
||||
return this.period;
|
||||
}
|
||||
|
||||
public void setPeriod(int period) {
|
||||
this.period = period;
|
||||
}
|
||||
|
||||
public int getDiscrepancy() {
|
||||
return this.discrepancy;
|
||||
}
|
||||
|
||||
public void setDiscrepancy(int discrepancy) {
|
||||
this.discrepancy = discrepancy;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Code {
|
||||
private int length = 6;
|
||||
|
||||
public Code() {
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
public void setLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Secret {
|
||||
private int length = 32;
|
||||
|
||||
public Secret() {
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
public void setLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,18 @@
|
||||
package im.zhaojun.zfile.core.constant;
|
||||
|
||||
/**
|
||||
* 规则表达式类型常量
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class RuleTypeConstant {
|
||||
|
||||
public static final String IP = "ip";
|
||||
|
||||
public static final String REGEX = "regex";
|
||||
|
||||
public static final String ANT_PATH = "antPath";
|
||||
|
||||
public static final String SPRING_SIMPLE = "springSimple";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
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 {
|
||||
|
||||
/**
|
||||
* 最大支持文本文件大小为 ? 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,16 @@
|
||||
package im.zhaojun.zfile.core.constant;
|
||||
|
||||
/**
|
||||
* ZFile 自定义 HTTP 请求头常量
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class ZFileHttpHeaderConstant {
|
||||
|
||||
public static final String ZFILE_TOKEN = "Zfile-Token";
|
||||
|
||||
public static final String AXIOS_REQUEST = "Axios-Request";
|
||||
|
||||
public static final String AXIOS_FROM = "Axios-From";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package im.zhaojun.zfile.core.controller;
|
||||
|
||||
import im.zhaojun.zfile.core.util.StringUtils;
|
||||
import im.zhaojun.zfile.module.config.model.dto.SystemConfigDTO;
|
||||
import im.zhaojun.zfile.module.config.service.SystemConfigService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||
import org.springframework.core.io.FileSystemResourceLoader;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 处理前端首页 Controller
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
@Controller
|
||||
public class FrontIndexController {
|
||||
|
||||
@Resource
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
@Resource
|
||||
private WebProperties webProperties;
|
||||
|
||||
/**
|
||||
* 所有未找到的页面都跳转到首页, 用户解决 vue history 直接访问 404 的问题
|
||||
* 同时, 读取 index.html 文件, 修改 title 和 favicon 后返回.
|
||||
*
|
||||
* @return 转发到 /index.html
|
||||
*/
|
||||
@RequestMapping(value = { "/"})
|
||||
@ResponseBody
|
||||
public ResponseEntity<String> redirect() {
|
||||
// 读取 resources/static/index.html 文件修改 title 和 favicon 后返回
|
||||
ResourceLoader resourceLoader = new FileSystemResourceLoader();
|
||||
String[] staticLocations = webProperties.getResources().getStaticLocations();
|
||||
|
||||
// 如果 staticLocations 里没有包含 file:static/, 则手动添加
|
||||
boolean fileStaticExist = false;
|
||||
for (String staticLocation : staticLocations) {
|
||||
if (staticLocation.startsWith("file:")) {
|
||||
fileStaticExist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!fileStaticExist) {
|
||||
staticLocations = org.apache.commons.lang3.ArrayUtils.add(staticLocations, "file:static/");
|
||||
}
|
||||
|
||||
for (String staticLocation : staticLocations) {
|
||||
org.springframework.core.io.Resource resource = resourceLoader.getResource(staticLocation + "/index.html");
|
||||
boolean exists = resource.exists();
|
||||
if (exists) {
|
||||
String content;
|
||||
try {
|
||||
content = resource.getContentAsString(StandardCharsets.UTF_8);
|
||||
log.debug("读取 index.html 文件成功, 文件路径: {}", staticLocation);
|
||||
} catch (Exception e) {
|
||||
log.error("{} 资源存在但读取 index.html 文件失败.", staticLocation);
|
||||
return ResponseEntity.status(500).body("static index.html read error");
|
||||
}
|
||||
|
||||
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
|
||||
|
||||
// 替换为系统设置中的站点名称
|
||||
String siteName = systemConfig.getSiteName();
|
||||
if (StringUtils.isNotBlank(siteName)) {
|
||||
content = content.replace("<title>ZFile</title>", "<title>" + siteName + "</title>");
|
||||
}
|
||||
|
||||
// 替换为系统设置中的 favicon 地址
|
||||
String faviconUrl = systemConfig.getFaviconUrl();
|
||||
if (StringUtils.isNotBlank(faviconUrl)) {
|
||||
content = content.replace("/favicon.svg", faviconUrl);
|
||||
}
|
||||
|
||||
// 添加缓存控制头
|
||||
return ResponseEntity.ok()
|
||||
.header("Cache-Control", "max-age=600, must-revalidate, proxy-revalidate") .header("Pragma", "no-cache")
|
||||
.body(content);
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseEntity.status(404).body("static index.html not found");
|
||||
}
|
||||
|
||||
@RequestMapping(value = { "/guest"})
|
||||
@ResponseBody
|
||||
public String guest() {
|
||||
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
|
||||
return systemConfig.getGuestIndexHtml();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
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.annotation.DemoDisable;
|
||||
import im.zhaojun.zfile.core.util.FileResponseUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
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
|
||||
*/
|
||||
@Tag(name = "日志")
|
||||
@ApiSort(8)
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/admin")
|
||||
public class LogController {
|
||||
|
||||
@Value("${zfile.log.path}")
|
||||
private String zfileLogPath;
|
||||
|
||||
@GetMapping("/log/download")
|
||||
@Operation(summary = "下载系统日志")
|
||||
@DemoDisable
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
||||
135
src/main/java/im/zhaojun/zfile/core/exception/ErrorCode.java
Normal file
@@ -0,0 +1,135 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 异常信息枚举类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public enum ErrorCode {
|
||||
|
||||
/**
|
||||
* 系统异常
|
||||
*/
|
||||
SYSTEM_ERROR("50000", "系统异常"),
|
||||
INVALID_STORAGE_SOURCE("50001", "无效或初始化失败的存储源"),
|
||||
DEMO_SITE_DISABLE_OPERATOR("50002", "演示站点不允许此操作"),
|
||||
|
||||
/**
|
||||
* 业务异常 4xxxx.
|
||||
* 第二位为 0 时,是系统初始化相关错误
|
||||
* 第二位为 1 时,是前台(文件管理)错误
|
||||
* 第二位为 2 时,是登录错误
|
||||
* 第二位为 3 时,是管理员端错误
|
||||
*/
|
||||
BIZ_ERROR("40000", "操作失败"),
|
||||
BIZ_NOT_FOUND("40400", "NOT FOUND"),
|
||||
|
||||
// 第二位为 0 时,是系统初始化相关错误
|
||||
BIZ_SYSTEM_ALREADY_INIT("40001", "系统已初始化,请勿重复初始化"),
|
||||
BIZ_SYSTEM_INIT_ERROR("40002", "系统初始化错误"),
|
||||
|
||||
// 第二位为 1 时,是前台(文件管理)错误
|
||||
BIZ_BAD_REQUEST("41000", "请求参数异常"),
|
||||
BIZ_UNSUPPORTED_PROXY_DOWNLOAD("41001", "该存储源不支持代理下载"),
|
||||
BIZ_INVALID_SIGNATURE("41002", "签名无效或下载地址已过期"),
|
||||
BIZ_PREVIEW_FILE_SIZE_EXCEED("41003", "预览文本文件大小超出系统限制"),
|
||||
BIZ_FILE_NOT_EXIST("41004", "文件不存在"),
|
||||
BIZ_ACCESS_TOO_FREQUENT("41005", "请求太频繁了,请稍后再试"),
|
||||
BIZ_UPLOAD_FILE_NOT_EMPTY("41006", "上传文件不能为空"),
|
||||
BIZ_UPLOAD_FILE_ERROR("41010", "上传文件失败"),
|
||||
BIZ_UPLOAD_FILE_TIMEOUT_ERROR("41026", "上传文件超时"),
|
||||
BIZ_EXPIRE_TIME_ILLEGAL("41007", "过期时间不合法"),
|
||||
BIZ_DELETE_FILE_NOT_EMPTY("41008", "非空文件夹不允许删除"),
|
||||
BIZ_FILE_PATH_ILLEGAL("41009", "文件名/路径存在安全隐患"),
|
||||
BIZ_DIRECT_LINK_NOT_ALLOWED("41011", "当前系统不允许使用直链"),
|
||||
BIZ_SHORT_LINK_NOT_ALLOWED("41012", "当前系统不允许使用短链"),
|
||||
BIZ_SHORT_LINK_EXPIRED("41013", "短链已失效"),
|
||||
BIZ_SHORT_LINK_NOT_FOUNT("41014", "短链不存在"),
|
||||
BIZ_DIRECT_LINK_EXPIRED("41015", "直链已失效"),
|
||||
BIZ_STORAGE_NOT_SUPPORT_OPERATION("41016", "该存储类型不支持此操作"),
|
||||
BIZ_STORAGE_NOT_FOUND("41017", "存储源不存在"),
|
||||
BIZ_STORAGE_SOURCE_ILLEGAL_OPERATION("41018", "非法或未授权的操作"),
|
||||
BIZ_STORAGE_SOURCE_FILE_FORBIDDEN("41019", "文件目录无访问权限"),
|
||||
BIZ_STORAGE_SOURCE_FOLDER_PASSWORD_REQUIRED("41020", "此文件夹需要密码"),
|
||||
BIZ_STORAGE_SOURCE_FOLDER_PASSWORD_ERROR("41021", "密码错误"),
|
||||
BIZ_INVALID_FILE_NAME("41022", "文件名不合法"),
|
||||
BIZ_UNSUPPORTED_OPERATION("41023", "不支持的操作"),
|
||||
BIZ_FTP_CLIENT_POOL_FULL("41024", "FTP 客户端连接池已满"),
|
||||
BIZ_SFTP_CLIENT_POOL_FULL("41025", "SFTP 客户端连接池已满"),
|
||||
BIZ_FOLDER_NOT_EXIST("41026", "文件夹不存在"),
|
||||
BIZ_UPLOAD_FILE_TYPE_NOT_ALLOWED("41027", "不允许上传的文件"),
|
||||
BIZ_RENAME_FILE_TYPE_NOT_ALLOWED("41028", "不允许重命名到该名称"),
|
||||
|
||||
// 第二位为 2 时,是登录错误
|
||||
BIZ_UNAUTHORIZED("42000", "未登录或未授权"),
|
||||
BIZ_LOGIN_ERROR("42001", "登录失败, 账号或密码错误"),
|
||||
BIZ_VERIFY_CODE_ERROR("42002", "验证码错误或已失效"),
|
||||
|
||||
// 第二位为 3 时,是管理员端错误
|
||||
BIZ_ADMIN_ERROR("43000", "操作失败"),
|
||||
BIZ_USER_NOT_EXIST("43001", "用户不存在"),
|
||||
BIZ_USER_EXIST("43002", "用户已存在"),
|
||||
BIZ_PASSWORD_NOT_SAME("43003", "两次密码不一致"),
|
||||
BIZ_OLD_PASSWORD_ERROR("43004", "旧密码不匹配"),
|
||||
BIZ_DELETE_BUILT_IN_USER("43005", "不能删除内置用户"),
|
||||
BIZ_UNSUPPORTED_STORAGE_TYPE("43006", "不支持的存储类型"),
|
||||
BIZ_STORAGE_KEY_EXIST("43007", "存储源别名已存在"),
|
||||
BIZ_AUTO_GET_SHARE_POINT_SITES_ERROR("43008", "自动获取 SharePoint 网站列表失败"),
|
||||
BIZ_ORIGINS_NOT_EMPTY("43009", "请先在 \"站点设置\" 中配置站点域名"),
|
||||
BIZ_2FA_CODE_ERROR("43010", "两步验证失败"),
|
||||
BIZ_STORAGE_INIT_ERROR("43011", "存储源初始化失败"),
|
||||
BIZ_RULE_EXIST("43012", "规则已存在"),
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 通用的无权限异常
|
||||
*/
|
||||
NO_FORBIDDEN("30000", "没有权限"),
|
||||
|
||||
|
||||
/**
|
||||
* 授权校验异常
|
||||
*/
|
||||
PRO_AUTH_CODE_EMPTY("20000", "请先去后台 \"基本设置\" 填写 \"授权码\""),
|
||||
PRO_CHECK_REFERER_EMPTY("20001", "Referer 无效,请检查服务端设置,20001"), // Referer 无效,请检查服务端设置
|
||||
PRO_CHECK_TIME_NO_SYNC("20002", "授权校验失败, 服务器时间异常,20002"), // 授权校验失败, 服务器时间异常.
|
||||
PRO_AUTH_CODE_INVALID_ERROR("20003", "授权码无效, 请检查后台 \"站点设置\" 中的 \"授权码\" 20003"),
|
||||
PRO_CHECK_UNKNOWN_ERROR("20004", "授权验证异常,未知异常,20098"),
|
||||
PRO_MSG_ERROR("20005", null);
|
||||
|
||||
private String code;
|
||||
|
||||
private String message;
|
||||
|
||||
ErrorCode(String code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误码
|
||||
*
|
||||
* @param code 错误码
|
||||
* @return 返回当前枚举
|
||||
*/
|
||||
public ErrorCode setCode(String code) {
|
||||
this.code = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误信息
|
||||
*
|
||||
* @param message 错误信息
|
||||
* @return 返回当前枚举
|
||||
*/
|
||||
public ErrorCode setMessage(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.NotRoleException;
|
||||
import im.zhaojun.zfile.core.controller.FrontIndexController;
|
||||
import im.zhaojun.zfile.core.exception.biz.*;
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
import im.zhaojun.zfile.core.exception.core.ErrorPageBizException;
|
||||
import im.zhaojun.zfile.core.exception.core.SystemException;
|
||||
import im.zhaojun.zfile.core.exception.status.*;
|
||||
import im.zhaojun.zfile.core.exception.system.UploadFileFailSystemException;
|
||||
import im.zhaojun.zfile.core.exception.system.ZFileAuthorizationSystemException;
|
||||
import im.zhaojun.zfile.core.util.AjaxJson;
|
||||
import im.zhaojun.zfile.core.util.RequestHolder;
|
||||
import im.zhaojun.zfile.core.util.StringUtils;
|
||||
import im.zhaojun.zfile.module.config.service.SystemConfigService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.catalina.connector.ClientAbortException;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
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;
|
||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||
import org.sqlite.SQLiteException;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@ControllerAdvice
|
||||
@Slf4j
|
||||
@Order(1)
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final ThreadLocal<String> exceptionMessage = new ThreadLocal<>();
|
||||
|
||||
@Resource
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
@Resource
|
||||
private FrontIndexController frontIndexController;
|
||||
|
||||
|
||||
private static final int MAX_FIND_CAUSE_EXCEPTION_DEPTH = 10;
|
||||
|
||||
// ---------------------- status exception start ----------------------
|
||||
|
||||
@ExceptionHandler(value = UnauthorizedAccessException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||
public AjaxJson<?> unauthorizedAccessException() {
|
||||
if (RequestHolder.isAxiosRequest()) {
|
||||
return AjaxJson.getUnauthorizedResult();
|
||||
}
|
||||
try {
|
||||
String unauthorizedUrl = systemConfigService.getUnauthorizedUrl();
|
||||
RequestHolder.getResponse().sendRedirect(unauthorizedUrl);
|
||||
} catch (IOException ex) {
|
||||
return AjaxJson.getUnauthorizedResult();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = {
|
||||
NotRoleException.class
|
||||
})
|
||||
@ResponseBody
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
public AjaxJson<?> forbiddenAccessException() {
|
||||
if (RequestHolder.isAxiosRequest()) {
|
||||
return AjaxJson.getForbiddenResult();
|
||||
}
|
||||
try {
|
||||
String forbiddenUrl = systemConfigService.getForbiddenUrl();
|
||||
RequestHolder.getResponse().sendRedirect(forbiddenUrl);
|
||||
} catch (IOException ex) {
|
||||
return AjaxJson.getForbiddenResult();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = ForbiddenAccessException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
||||
public AjaxJson<?> forbiddenAccessException(ForbiddenAccessException e) {
|
||||
if (RequestHolder.isAxiosRequest()) {
|
||||
return AjaxJson.getError(e.getCode(), e.getMessage());
|
||||
}
|
||||
try {
|
||||
String forbiddenUrl = systemConfigService.getForbiddenUrl(e.getCode(), e.getMessage());
|
||||
RequestHolder.getResponse().sendRedirect(forbiddenUrl);
|
||||
} catch (IOException ex) {
|
||||
return AjaxJson.getError(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = NotFoundAccessException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public AjaxJson<?> notFoundAccessException(NotFoundAccessException e) {
|
||||
if (RequestHolder.isAxiosRequest()) {
|
||||
return AjaxJson.getError(e.getCode(), e.getMessage());
|
||||
}
|
||||
try {
|
||||
String notFoundUrl = systemConfigService.getNotFoundUrl(e.getCode(), e.getMessage());
|
||||
RequestHolder.getResponse().sendRedirect(notFoundUrl);
|
||||
} catch (IOException ex) {
|
||||
return AjaxJson.getError(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 所有未找到的页面都跳转到首页, 用户解决 vue history 直接访问 404 的问题
|
||||
* 同时, 读取 index.html 文件, 修改 title 和 favicon 后返回.
|
||||
*
|
||||
* @return 转发到 /index.html
|
||||
*/
|
||||
@ExceptionHandler(value = NoResourceFoundException.class)
|
||||
@ResponseBody
|
||||
public String notFoundAccessException() {
|
||||
return frontIndexController.redirect().getBody();
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = MethodNotAllowedAccessException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
|
||||
public AjaxJson<String> methodNotAllowedAccessException(MethodNotAllowedAccessException e) {
|
||||
return new AjaxJson<>(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = BadRequestAccessException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public AjaxJson<String> badRequestAccessException(BadRequestAccessException e) {
|
||||
return new AjaxJson<>(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
// ---------------------- status exception end ----------------------
|
||||
|
||||
|
||||
|
||||
|
||||
// ---------------------- biz exception start ----------------------
|
||||
|
||||
@ExceptionHandler(value = APIHttpRequestBizException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public AjaxJson<String> apiHttpRequestBizException(APIHttpRequestBizException e) {
|
||||
log.warn("请求第三方 API 异常, 请求地址: {}, 响应码: {}, 响应体: {}", e.getUrl(), e.getResponseCode(), e.getResponseBody());
|
||||
return new AjaxJson<>(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = FilePathSecurityBizException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public AjaxJson<String> filePathSecurityBizException(FilePathSecurityBizException e) {
|
||||
log.warn("获取文件路径存在安全风险, 文件路径: {}", e.getPath());
|
||||
return new AjaxJson<>(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = GetPreviewTextContentBizException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public AjaxJson<String> getPreviewTextContentBizException(GetPreviewTextContentBizException e) {
|
||||
log.warn("获取预览文件内容失败, 文件 url: {}", e.getUrl(), e);
|
||||
return new AjaxJson<>(e.getCode(), "预览文件内容失败, 请联系管理员.");
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = InitializeStorageSourceBizException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public AjaxJson<String> initializeStorageSourceBizException(InitializeStorageSourceBizException e) {
|
||||
log.error("存储源初始化失败, 存储源 ID: {}.", e.getStorageId(), e);
|
||||
return new AjaxJson<>(e.getCode(), "存储源初始化失败:" + e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = StorageSourceFileForbiddenAccessBizException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public AjaxJson<String> storageSourceFileForbiddenAccessBizException(StorageSourceFileForbiddenAccessBizException e) {
|
||||
log.warn("尝试访问不被授权的文件/目录, 存储源 ID: {}: 目录: {}", e.getStorageId(), e.getPath());
|
||||
return new AjaxJson<>(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = StorageSourceIllegalOperationBizException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public AjaxJson<String> storageSourceIllegalOperationBizException(StorageSourceIllegalOperationBizException e) {
|
||||
log.warn("存储源非法或未授权的操作, 存储源 ID: {}, 操作类型: {}", e.getStorageId(), e.getAction());
|
||||
return new AjaxJson<>(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = CorsBizException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public AjaxJson<String> corsBizException(CorsBizException e) {
|
||||
log.warn("跨域异常:", e);
|
||||
return new AjaxJson<>(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = ErrorPageBizException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public AjaxJson<?> errorPageBizException(ErrorPageBizException e) {
|
||||
if (RequestHolder.isAxiosRequest()) {
|
||||
return AjaxJson.getError(e.getCode(), e.getMessage());
|
||||
}
|
||||
try {
|
||||
String errorPageUrl = systemConfigService.getErrorPageUrl(e.getCode(), e.getMessage());
|
||||
RequestHolder.getResponse().sendRedirect(errorPageUrl);
|
||||
} catch (IOException ex) {
|
||||
return AjaxJson.getError(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ExceptionHandler(value = BizException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public AjaxJson<String> bizException(BizException e) {
|
||||
return new AjaxJson<>(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
// ---------------------- biz exception end ----------------------
|
||||
|
||||
|
||||
// ---------------------- system exception end ----------------------
|
||||
|
||||
@ExceptionHandler(value = UploadFileFailSystemException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public AjaxJson<String> uploadFileFailSystemException(UploadFileFailSystemException e) {
|
||||
log.warn("上传文件失败, 存储类型: {}, 上传路径: {}, 输入流可用字节数: {}, 响应码: {}, 响应体: {}",
|
||||
e.getStorageTypeEnum(), e.getUploadPath(), e.getInputStreamAvailable(), e.getResponseCode(), e.getResponseBody());
|
||||
return new AjaxJson<>(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = ZFileAuthorizationSystemException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public AjaxJson<?> zfileAuthorizationSystemException(ZFileAuthorizationSystemException e) {
|
||||
return new AjaxJson<>(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = SystemException.class)
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public AjaxJson<?> systemException(SystemException e) {
|
||||
return new AjaxJson<>(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ---------------------- system exception end ----------------------
|
||||
|
||||
|
||||
|
||||
// ---------------------- common exception end ----------------------
|
||||
|
||||
@ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class})
|
||||
@ResponseBody
|
||||
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
|
||||
public AjaxJson<Map<String, String>> handleValidException(Exception e) {
|
||||
BindingResult bindingResult = null;
|
||||
if (e instanceof MethodArgumentNotValidException) {
|
||||
bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
|
||||
} else if (e instanceof BindException) {
|
||||
bindingResult = ((BindException) e).getBindingResult();
|
||||
}
|
||||
Map<String, String> errorMap = new HashMap<>(16);
|
||||
|
||||
Optional.ofNullable(bindingResult)
|
||||
.map(BindingResult::getFieldErrors)
|
||||
.ifPresent(fieldErrors -> {
|
||||
for (FieldError fieldError : fieldErrors) {
|
||||
errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
|
||||
}
|
||||
});
|
||||
return new AjaxJson<>(ErrorCode.BIZ_BAD_REQUEST.getCode(), ErrorCode.BIZ_BAD_REQUEST.getMessage(), errorMap);
|
||||
|
||||
}
|
||||
|
||||
@ExceptionHandler({FileNotFoundException.class})
|
||||
@ResponseBody
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public AjaxJson<Void> fileNotFound() {
|
||||
return AjaxJson.getError("文件不存在");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 登录异常拦截器
|
||||
*/
|
||||
@ExceptionHandler(NotLoginException.class)
|
||||
@ResponseStatus(HttpStatus.UNAUTHORIZED)
|
||||
@ResponseBody
|
||||
public AjaxJson<?> handlerNotLoginException(NotLoginException e) {
|
||||
if (RequestHolder.isAxiosRequest()) {
|
||||
return AjaxJson.getUnauthorizedResult();
|
||||
}
|
||||
try {
|
||||
String domain = systemConfigService.getRealFrontDomain();
|
||||
if (StringUtils.isBlank(domain)) {
|
||||
domain = "";
|
||||
}
|
||||
String loginUrl = StringUtils.concat(domain, "/login");
|
||||
RequestHolder.getResponse().sendRedirect(loginUrl);
|
||||
} catch (IOException ex) {
|
||||
return AjaxJson.getUnauthorizedResult();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ExceptionHandler
|
||||
@ResponseBody
|
||||
@ResponseStatus
|
||||
public AjaxJson<?> extraExceptionHandler(Exception e) {
|
||||
ExceptionType exceptionType = getExceptionType(e);
|
||||
if (exceptionType == ExceptionType.IGNORE_PRINT_STACK_TRACE_EXCEPTION) {
|
||||
log.warn(e.getMessage());
|
||||
} else if (exceptionType == ExceptionType.OTHER) {
|
||||
log.error(e.getMessage(), e);
|
||||
} else if (exceptionType == ExceptionType.SPECIFY_MESSAGE_EXCEPTION) {
|
||||
if (exceptionMessage.get() != null) {
|
||||
String message = exceptionMessage.get();
|
||||
log.error("发生异常: {}", message,e );
|
||||
exceptionMessage.remove();
|
||||
return AjaxJson.getError(message);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.getClass() == Exception.class) {
|
||||
return AjaxJson.getError("系统异常, 请联系管理员");
|
||||
} else {
|
||||
return AjaxJson.getError(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static ExceptionType getExceptionType(Exception e) {
|
||||
int findCauseCount = 0;
|
||||
do {
|
||||
if (e instanceof BizException) {
|
||||
return ExceptionType.IGNORE_PRINT_STACK_TRACE_EXCEPTION;
|
||||
} else if (e instanceof ClientAbortException) {
|
||||
return ExceptionType.IGNORE_EXCEPTION;
|
||||
} else if (e instanceof SQLiteException && e.getMessage().contains("database is locked")) {
|
||||
exceptionMessage.set("数据库繁忙,请稍后再试");
|
||||
return ExceptionType.SPECIFY_MESSAGE_EXCEPTION;
|
||||
}
|
||||
e = (Exception) e.getCause();
|
||||
findCauseCount++;
|
||||
} while (e != null && findCauseCount < MAX_FIND_CAUSE_EXCEPTION_DEPTH);
|
||||
|
||||
return ExceptionType.OTHER;
|
||||
}
|
||||
|
||||
enum ExceptionType {
|
||||
/**
|
||||
* 忽略打印异常信息和堆栈信息
|
||||
*/
|
||||
IGNORE_EXCEPTION,
|
||||
|
||||
/**
|
||||
* 仅打印异常信息, 不打印堆栈信息
|
||||
*/
|
||||
IGNORE_PRINT_STACK_TRACE_EXCEPTION,
|
||||
|
||||
/**
|
||||
* 不打印堆栈信息,但指定异常信息
|
||||
*/
|
||||
SPECIFY_MESSAGE_EXCEPTION,
|
||||
|
||||
/**
|
||||
* 其他异常, 打印异常信息和堆栈信息
|
||||
*/
|
||||
OTHER;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package im.zhaojun.zfile.core.exception.biz;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.ErrorCode;
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 请求第三方 API 时如果返回非 2xx 状态码, 则抛出此异常. 需记录请求地址, 响应状态码, 响应内容.
|
||||
* <p/>
|
||||
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#apiHttpRequestBizException(APIHttpRequestBizException)}
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class APIHttpRequestBizException extends BizException {
|
||||
|
||||
private final String url;
|
||||
|
||||
private final int responseCode;
|
||||
|
||||
private final String responseBody;
|
||||
|
||||
public APIHttpRequestBizException(ErrorCode errorCode, String url, int responseCode, String responseBody) {
|
||||
super(errorCode);
|
||||
this.url = url;
|
||||
this.responseCode = responseCode;
|
||||
this.responseBody = responseBody;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package im.zhaojun.zfile.core.exception.biz;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class CorsBizException extends BizException {
|
||||
|
||||
public CorsBizException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean printExceptionStackTrace() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package im.zhaojun.zfile.core.exception.biz;
|
||||
|
||||
|
||||
import im.zhaojun.zfile.core.exception.ErrorCode;
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 文件路径安全异常, 表示文件路径不合法,如包含了 "./" 或 "../" 等字符来尝试访问非法目录.
|
||||
* <p/>
|
||||
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#filePathSecurityBizException(FilePathSecurityBizException)}
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class FilePathSecurityBizException extends BizException {
|
||||
|
||||
private final String path;
|
||||
|
||||
public FilePathSecurityBizException(String path) {
|
||||
super(ErrorCode.BIZ_FILE_PATH_ILLEGAL);
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package im.zhaojun.zfile.core.exception.biz;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 获取预览文件内容异常, 可能是目标连接无法访问/文件不存在等原因.
|
||||
* <p/>
|
||||
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#getPreviewTextContentBizException(GetPreviewTextContentBizException)}
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class GetPreviewTextContentBizException extends BizException {
|
||||
|
||||
/**
|
||||
* 获取预览文件的 URL
|
||||
*/
|
||||
private final String url;
|
||||
|
||||
public GetPreviewTextContentBizException(String url, Throwable cause) {
|
||||
super(cause);
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package im.zhaojun.zfile.core.exception.biz;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 初始化存储源时失败产生的异常
|
||||
* <p/>
|
||||
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#initializeStorageSourceBizException(InitializeStorageSourceBizException)}
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class InitializeStorageSourceBizException extends BizException {
|
||||
|
||||
private final Integer storageId;
|
||||
|
||||
public InitializeStorageSourceBizException(String message, Integer storageId) {
|
||||
super(message);
|
||||
this.storageId = storageId;
|
||||
}
|
||||
|
||||
public InitializeStorageSourceBizException(String code, String message, Integer storageId, Throwable cause) {
|
||||
super(code, message, cause);
|
||||
this.storageId = storageId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package im.zhaojun.zfile.core.exception.biz;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.ErrorCode;
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 不存在或初始化失败的存储源异常。
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class InvalidStorageSourceBizException extends BizException {
|
||||
|
||||
private final Integer storageId;
|
||||
|
||||
private final String storageKey;
|
||||
|
||||
public InvalidStorageSourceBizException(String storageKey) {
|
||||
super(ErrorCode.INVALID_STORAGE_SOURCE);
|
||||
this.storageKey = storageKey;
|
||||
this.storageId = null;
|
||||
}
|
||||
|
||||
public InvalidStorageSourceBizException(Integer storageId) {
|
||||
super(ErrorCode.INVALID_STORAGE_SOURCE);
|
||||
this.storageId = storageId;
|
||||
this.storageKey = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package im.zhaojun.zfile.core.exception.biz;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.ErrorCode;
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 访问了禁止访问的存储源文件/目录时抛出此异常.
|
||||
* <p/>
|
||||
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#storageSourceFileForbiddenAccessBizException(StorageSourceFileForbiddenAccessBizException)}
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class StorageSourceFileForbiddenAccessBizException extends BizException {
|
||||
|
||||
private final Integer storageId;
|
||||
|
||||
private final String path;
|
||||
|
||||
public StorageSourceFileForbiddenAccessBizException(Integer storageId, String path) {
|
||||
super(ErrorCode.BIZ_STORAGE_SOURCE_FILE_FORBIDDEN);
|
||||
this.storageId = storageId;
|
||||
this.path = path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package im.zhaojun.zfile.core.exception.biz;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.ErrorCode;
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
|
||||
import im.zhaojun.zfile.module.storage.model.enums.FileOperatorTypeEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 对存储源进行非法(未授权)的操作产生的异常
|
||||
* <p/>
|
||||
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#storageSourceIllegalOperationBizException(StorageSourceIllegalOperationBizException)}
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class StorageSourceIllegalOperationBizException extends BizException {
|
||||
|
||||
private final Integer storageId;
|
||||
|
||||
private final FileOperatorTypeEnum action;
|
||||
|
||||
public StorageSourceIllegalOperationBizException(Integer storageId, FileOperatorTypeEnum action) {
|
||||
super(ErrorCode.BIZ_STORAGE_SOURCE_ILLEGAL_OPERATION);
|
||||
this.storageId = storageId;
|
||||
this.action = action;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package im.zhaojun.zfile.core.exception.core;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.ErrorCode;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 业务异常,该类异常用户可自行处理,无需记录日志,属于正常业务流程中的异常. 如: 用户名密码错误, 未登录等.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class BizException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 8312907182931723379L;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 是否打印堆栈信息,业务异常默认不打印堆栈信息,如果需要打印堆栈信息,可以通过子类覆盖该方法修改返回值为 true.
|
||||
*/
|
||||
public boolean printExceptionStackTrace() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个没有错误信息的 <code>SystemException</code>
|
||||
*/
|
||||
public BizException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 Throwable 和 Throwable.toString() 作为异常信息来构造 SystemException
|
||||
*
|
||||
* @param cause 错误原因, 通过 Throwable.getCause() 方法可以获取传入的 cause信息
|
||||
*/
|
||||
public BizException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用错误信息 message 构造 SystemException
|
||||
*
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public BizException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用错误码和错误信息构造 SystemException
|
||||
*
|
||||
* @param code 错误码
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public BizException(String code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用错误信息和 Throwable 构造 SystemException
|
||||
*
|
||||
* @param message 错误信息
|
||||
* @param cause 错误原因
|
||||
*/
|
||||
public BizException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param code 错误码
|
||||
* @param message 错误信息
|
||||
* @param cause 错误原因
|
||||
*/
|
||||
public BizException(String code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorCode ErrorCode
|
||||
*/
|
||||
public BizException(ErrorCode errorCode) {
|
||||
super(errorCode.getMessage());
|
||||
this.code = errorCode.getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorCode ErrorCode
|
||||
* @param cause 错误原因
|
||||
*/
|
||||
public BizException(ErrorCode errorCode, Throwable cause) {
|
||||
super(errorCode.getMessage(), cause);
|
||||
this.code = errorCode.getCode();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package im.zhaojun.zfile.core.exception.core;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.ErrorCode;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 业务异常,该类异常用户可自行处理,无需记录日志,属于正常业务流程中的异常. 如: 用户名密码错误, 未登录等.<br>
|
||||
* 使用该类的异常,当该异常被抛出时,会跳转到 500 错误页面(错误码和错误消息可被 {@link #code} 和 {@link #getMessage()} 覆盖),而不是返回 JSON 数据.<br>
|
||||
* 一般使用该异常得请求不会是 AJAX 请求,而是直接在浏览器中访问的页面请求.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class ErrorPageBizException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 8312907182931723379L;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 是否打印堆栈信息,业务异常默认不打印堆栈信息,如果需要打印堆栈信息,可以通过子类覆盖该方法修改返回值为 true.
|
||||
*/
|
||||
public boolean printExceptionStackTrace() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个没有错误信息的 <code>SystemException</code>
|
||||
*/
|
||||
public ErrorPageBizException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 Throwable 和 Throwable.toString() 作为异常信息来构造 SystemException
|
||||
*
|
||||
* @param cause 错误原因, 通过 Throwable.getCause() 方法可以获取传入的 cause信息
|
||||
*/
|
||||
public ErrorPageBizException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用错误信息 message 构造 SystemException
|
||||
*
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public ErrorPageBizException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用错误码和错误信息构造 SystemException
|
||||
*
|
||||
* @param code 错误码
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public ErrorPageBizException(String code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用错误信息和 Throwable 构造 SystemException
|
||||
*
|
||||
* @param message 错误信息
|
||||
* @param cause 错误原因
|
||||
*/
|
||||
public ErrorPageBizException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param code 错误码
|
||||
* @param message 错误信息
|
||||
* @param cause 错误原因
|
||||
*/
|
||||
public ErrorPageBizException(String code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorCode ErrorCode
|
||||
*/
|
||||
public ErrorPageBizException(ErrorCode errorCode) {
|
||||
super(errorCode.getMessage());
|
||||
this.code = errorCode.getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorCode ErrorCode
|
||||
* @param cause 错误原因
|
||||
*/
|
||||
public ErrorPageBizException(ErrorCode errorCode, Throwable cause) {
|
||||
super(errorCode.getMessage(), cause);
|
||||
this.code = errorCode.getCode();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package im.zhaojun.zfile.core.exception.core;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.ErrorCode;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 系统异常, 该类异常用户无法处理,需要记录日志, 属于系统异常. 如: 网络异常, 服务器异常等.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class SystemException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 8312907182931723379L;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 构造一个没有错误信息的 <code>SystemException</code>
|
||||
*/
|
||||
public SystemException() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的 Throwable 和 Throwable.toString() 作为异常信息来构造 SystemException
|
||||
*
|
||||
* @param cause 错误原因, 通过 Throwable.getCause() 方法可以获取传入的 cause信息
|
||||
*/
|
||||
public SystemException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用错误信息 message 构造 SystemException
|
||||
*
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public SystemException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用错误码和错误信息构造 SystemException
|
||||
*
|
||||
* @param code 错误码
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public SystemException(String code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用错误信息和 Throwable 构造 SystemException
|
||||
*
|
||||
* @param message 错误信息
|
||||
* @param cause 错误原因
|
||||
*/
|
||||
public SystemException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param code 错误码
|
||||
* @param message 错误信息
|
||||
* @param cause 错误原因
|
||||
*/
|
||||
public SystemException(String code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorCode ErrorCode
|
||||
*/
|
||||
public SystemException(ErrorCode errorCode) {
|
||||
super(errorCode.getMessage());
|
||||
this.code = errorCode.getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorCode ErrorCode
|
||||
* @param cause 错误原因
|
||||
*/
|
||||
public SystemException(ErrorCode errorCode, Throwable cause) {
|
||||
super(errorCode.getMessage(), cause);
|
||||
this.code = errorCode.getCode();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package im.zhaojun.zfile.core.exception.status;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
|
||||
import im.zhaojun.zfile.core.exception.core.BizException;
|
||||
|
||||
/**
|
||||
* 错误请求异常, 表示请求参数有误或者服务器无法理解, 一般返回 400 状态码
|
||||
* <p/>
|
||||
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#badRequestAccessException(BadRequestAccessException)}
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class BadRequestAccessException extends BizException {
|
||||
|
||||
public BadRequestAccessException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||