Compare commits
76 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 |
24
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: BUG 反馈
|
||||
about: 事情不像预期的那样工作吗?
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
为了帮助我们更好的解决您的问题,请填写以下选项(不填写完整可能会被直接关闭 issue):
|
||||
|
||||
|
||||
- 是否已搜索其他 issue,没有人提过这个问题?:
|
||||
- 当前 ZFile 版本:
|
||||
- 是否尝试最新版是否已解决此问题:
|
||||
- 是否尝试重启 ZFile,且问题依旧存在?:
|
||||
- 是否已尝试清空浏览器缓存,且问题依旧存在?:
|
||||
- 操作系统(如 Windows、Mac、iOS、安卓):
|
||||
- 浏览器(如 Chrome、Firefox、Safari,X 浏览器):
|
||||
- 做什么操作提示的错误?:
|
||||
- 期望行为(应该是什么样的结果):
|
||||
- 当前行为(当前是什么样的结果):
|
||||
- 错误日志(可选):
|
||||
- 复现步骤(可选):
|
||||
- 您的额外信息(可选):
|
||||
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/ 创建的问题可能会被立即关闭。
|
||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,17 +0,0 @@
|
||||
---
|
||||
name: 功能建议
|
||||
about: 想让我们为 ZFile 增加什么功能吗?
|
||||
title: 'feat: '
|
||||
labels: 'Feature Request'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
为了帮助我们更好的解决您的问题,请填写以下选项(不填写完整可能会被直接关闭 issue):
|
||||
|
||||
- 是否已搜索其他 issue,没有人提过这个功能?:
|
||||
- 是否已尝试使用最新版本,且仍然没有此功能?:
|
||||
- 功能概述:
|
||||
- 功能动机:
|
||||
- 详细解释(可选):
|
||||
4
.gitignore
vendored
@@ -18,6 +18,8 @@ target/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
.fastRequest
|
||||
.murphy.yml
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
@@ -34,4 +36,4 @@ build/
|
||||
/.mvn/wrapper/
|
||||
/mvnw
|
||||
/mvnw.cmd
|
||||
/.script/
|
||||
/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
|
||||
)
|
||||
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
|
||||
101
README.md
@@ -1,66 +1,83 @@
|
||||
<p align="center">
|
||||
<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>
|
||||
|
||||

|
||||
## 系统特色
|
||||
|
||||
基于 Java 的在线网盘程序,支持对接 S3、OneDrive、SharePoint、又拍云、本地存储、FTP、SFTP 等存储源,支持在线浏览图片、播放音视频,文本文件、Office、obj(3d)等文件类型。
|
||||
<br><br>
|
||||
<img src="https://img.shields.io/badge/license-MIT-blue.svg?longCache=true&style=flat-square" alt="license">
|
||||
<img src="https://api.codacy.com/project/badge/Grade/70b793267f7941d58cbd93f50c9a8e0a" alt="codady">
|
||||
<img src="https://img.shields.io/github/last-commit/zhaojun1998/zfile.svg?style=flat-square" alt="last commit">
|
||||
<img src="https://img.shields.io/github/downloads/zhaojun1998/zfile/total?style=flat-square" alt="downloads">
|
||||
<img src="https://img.shields.io/github/v/release/zhaojun1998/zfile?style=flat-square" alt="release">
|
||||
<img src="https://img.shields.io/github/commit-activity/y/zhaojun1998/zfile?style=flat-square" alt="commit activity">
|
||||
<br>
|
||||
<img src="https://img.shields.io/github/issues/zhaojun1998/zfile?style=flat-square" alt="issues">
|
||||
<img src="https://img.shields.io/github/issues-closed-raw/zhaojun1998/zfile?style=flat-square" alt="closed issues">
|
||||
<img src="https://img.shields.io/github/forks/zhaojun1998/zfile?style=flat-square" alt="forks">
|
||||
<img src="https://img.shields.io/github/stars/zhaojun1998/zfile?style=flat-square" alt="stars">
|
||||
<img src="https://img.shields.io/github/watchers/zhaojun1998/zfile?style=flat-square" alt="watchers">
|
||||
</p>
|
||||
- Docker、Docker Compose 支持(amd64, arm64)。
|
||||
- 支持对文件生成直链、短链(可设过期时间)。
|
||||
- 响应式设计,支持手机、平板、电脑等多种设备访问。
|
||||
- 支持多用户功能,可分配给指定用户指定存储源或目录。
|
||||
- 支持在线浏览图片、播放音视频,文本文件、Office、Obj(3d)等文件类型。
|
||||
- 支持对接 S3、OneDrive、SharePoint、Google Drive、多吉云、又拍云、本地存储、FTP、SFTP 等存储源。
|
||||
- 支持常用快捷键,`Ctrl + A` 全选,`Ctrl + 左键` 多选,`Shift + 左键` 范围选择,`Esc` 取消全选等。
|
||||
- 支持限速下载(捐赠版)
|
||||
- 支持限制指定用户可查看、上传的文件类型(捐赠版)
|
||||
|
||||
## 快速开始
|
||||
|
||||
请参考部署文档: [https://docs.zfile.vip](https://docs.zfile.vip)
|
||||
一键脚本安装:
|
||||
|
||||
## 在线体验
|
||||
```bash
|
||||
curl -sSL https://docs.zfile.vip/install.sh -o install.sh && chmod +x install.sh && ./install.sh
|
||||
```
|
||||
|
||||
更多安装方式请参考 [安装文档](https://docs.zfile.vip/install/)
|
||||
|
||||
[https://demo.zfile.vip](https://demo.zfile.vip)
|
||||
|
||||
## 功能预览
|
||||
|
||||
### 文件列表
|
||||

|
||||

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

|
||||

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

|
||||

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

|
||||

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

|
||||

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

|
||||

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

|
||||

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

|
||||

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

|
||||

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

|
||||

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

|
||||

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

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

|
||||

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

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

|
||||

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

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

|
||||
|
||||

|
||||
|
||||
## 支持作者
|
||||
|
||||
@@ -68,10 +85,6 @@
|
||||
|
||||
<img src="https://cdn.jun6.net/2021/03/27/152704e91f13d.png" width="400" alt="赞助我">
|
||||
|
||||
## Status
|
||||
|
||||

|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#zfile-dev/zfile&Date)
|
||||
[](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 |
275
pom.xml
@@ -2,27 +2,60 @@
|
||||
<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.6.8</version>
|
||||
<version>3.3.2</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>im.zhaojun</groupId>
|
||||
<artifactId>zfile</artifactId>
|
||||
<version>4.1.3</version>
|
||||
<name>zfile</name>
|
||||
<packaging>war</packaging>
|
||||
<description>一个在线的文件浏览系统</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<org.mapstruct.version>1.5.1.Final</org.mapstruct.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>
|
||||
|
||||
<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>
|
||||
|
||||
<dependencies>
|
||||
<!-- spring boot 官方相关 -->
|
||||
<!-- 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>
|
||||
@@ -31,11 +64,16 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</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>
|
||||
@@ -49,12 +87,10 @@
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 数据库相关 -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.29</version>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -64,93 +100,132 @@
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
<version>7.15.0</version>
|
||||
<version>${flyway.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-mysql</artifactId>
|
||||
<version>${flyway.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>3.5.2</version>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>3.5.6</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 存储策略相关 SDK、 工具类-->
|
||||
<!-- 存储策略相关 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>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<version>1.12.261</version>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.qiniu</groupId>
|
||||
<artifactId>qiniu-java-sdk</artifactId>
|
||||
<version>7.11.0</version>
|
||||
<version>7.12.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<groupId>com.github.mwiede</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
<version>0.1.55</version>
|
||||
<version>0.2.20</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.lookfirst</groupId>
|
||||
<artifactId>sardine</artifactId>
|
||||
<version>5.10</version>
|
||||
<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-boot-starter</artifactId>
|
||||
<version>1.30.0</version>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
<version>1.38.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 文档相关 -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
<version>3.0.3</version>
|
||||
<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.3</version>
|
||||
<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>
|
||||
<version>1.18.24</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<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</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.83</version>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>2.0.29</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>30.1.1-jre</version>
|
||||
<version>33.2.0-jre</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
@@ -164,7 +239,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.samstevens.totp</groupId>
|
||||
<artifactId>totp-spring-boot-starter</artifactId>
|
||||
<artifactId>totp</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -175,13 +250,17 @@
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20200518</version>
|
||||
<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>
|
||||
@@ -191,6 +270,26 @@
|
||||
<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>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>
|
||||
|
||||
<build>
|
||||
@@ -198,21 +297,13 @@
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<source>21</source>
|
||||
<target>21</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
@@ -223,7 +314,7 @@
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.16</version>
|
||||
<version>1.18.32</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
@@ -237,30 +328,52 @@
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.uyoqu.framework</groupId>
|
||||
<artifactId>maven-plugin-starter</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>bin</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<jvms>
|
||||
<jvm>-Djava.security.egd=file:/dev/./urandom</jvm>
|
||||
<jvm>-Dfile.encoding=utf-8</jvm>
|
||||
<jvm>-Djava.net.preferIPv4Stack=false</jvm>
|
||||
<jvm>-Djava.net.preferIPv4Addresses=true</jvm>
|
||||
<jvm>-Djava.awt.headless=true</jvm>
|
||||
</jvms>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
|
||||
</build>
|
||||
|
||||
|
||||
<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>
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2011-2022, baomidou (jobob@qq.com).
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.baomidou.mybatisplus.core.handlers;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.EnumValue;
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
|
||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||
import org.apache.ibatis.reflection.DefaultReflectorFactory;
|
||||
import org.apache.ibatis.reflection.MetaClass;
|
||||
import org.apache.ibatis.reflection.ReflectorFactory;
|
||||
import org.apache.ibatis.reflection.invoker.Invoker;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 自定义枚举属性转换器
|
||||
*
|
||||
* @author hubin
|
||||
* @since 2017-10-11
|
||||
*/
|
||||
public class MybatisEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
|
||||
|
||||
private static final Map<String, String> TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap<>();
|
||||
private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
|
||||
private final Class<E> enumClassType;
|
||||
private final Class<?> propertyType;
|
||||
private final Invoker getInvoker;
|
||||
|
||||
public MybatisEnumTypeHandler(Class<E> enumClassType) {
|
||||
if (enumClassType == null) {
|
||||
throw new IllegalArgumentException("Type argument cannot be null");
|
||||
}
|
||||
this.enumClassType = enumClassType;
|
||||
MetaClass metaClass = MetaClass.forClass(enumClassType, REFLECTOR_FACTORY);
|
||||
String name = "value";
|
||||
if (!IEnum.class.isAssignableFrom(enumClassType)) {
|
||||
name = findEnumValueFieldName(this.enumClassType).orElseThrow(() -> new IllegalArgumentException(String.format("Could not find @EnumValue in Class: %s.", this.enumClassType.getName())));
|
||||
}
|
||||
this.propertyType = ReflectionKit.resolvePrimitiveIfNecessary(metaClass.getGetterType(name));
|
||||
this.getInvoker = metaClass.getGetInvoker(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找标记标记EnumValue字段
|
||||
*
|
||||
* @param clazz class
|
||||
* @return EnumValue字段
|
||||
* @since 3.3.1
|
||||
*/
|
||||
public static Optional<String> findEnumValueFieldName(Class<?> clazz) {
|
||||
if (clazz != null && clazz.isEnum()) {
|
||||
String className = clazz.getName();
|
||||
return Optional.ofNullable(CollectionUtils.computeIfAbsent(TABLE_METHOD_OF_ENUM_TYPES, className, key -> {
|
||||
Optional<Field> fieldOptional = findEnumValueAnnotationField(clazz);
|
||||
return fieldOptional.map(Field::getName).orElse(null);
|
||||
}));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<Field> findEnumValueAnnotationField(Class<?> clazz) {
|
||||
return Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(EnumValue.class)).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为MP枚举处理
|
||||
*
|
||||
* @param clazz class
|
||||
* @return 是否为MP枚举处理
|
||||
* @since 3.3.1
|
||||
*/
|
||||
public static boolean isMpEnums(Class<?> clazz) {
|
||||
return clazz != null && clazz.isEnum() && (IEnum.class.isAssignableFrom(clazz) || findEnumValueFieldName(clazz).isPresent());
|
||||
}
|
||||
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType)
|
||||
throws SQLException {
|
||||
if (jdbcType == null) {
|
||||
ps.setObject(i, this.getValue(parameter));
|
||||
} else {
|
||||
// see r3589
|
||||
ps.setObject(i, this.getValue(parameter), jdbcType.TYPE_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
Object value = rs.getObject(columnName);
|
||||
if (null == value && rs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return this.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
Object value = rs.getObject(columnIndex, this.propertyType);
|
||||
if (null == value && rs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return this.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
Object value = cs.getObject(columnIndex, this.propertyType);
|
||||
if (null == value && cs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return this.valueOf(value);
|
||||
}
|
||||
|
||||
private E valueOf(Object value) {
|
||||
E[] es = this.enumClassType.getEnumConstants();
|
||||
return Arrays.stream(es).filter((e) -> equalsValue(value, getValue(e))).findAny().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 值比较
|
||||
*
|
||||
* @param sourceValue 数据库字段值
|
||||
* @param targetValue 当前枚举属性值
|
||||
* @return 是否匹配
|
||||
* @since 3.3.0
|
||||
*/
|
||||
protected boolean equalsValue(Object sourceValue, Object targetValue) {
|
||||
String sValue = StringUtils.toStringTrim(sourceValue);
|
||||
String tValue = StringUtils.toStringTrim(targetValue);
|
||||
if (sourceValue instanceof Number && targetValue instanceof Number
|
||||
&& new BigDecimal(sValue).compareTo(new BigDecimal(tValue)) == 0) {
|
||||
return true;
|
||||
}
|
||||
return Objects.equals(sValue, tValue);
|
||||
}
|
||||
|
||||
private Object getValue(Object object) {
|
||||
try {
|
||||
return this.getInvoker.invoke(object, new Object[0]);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw ExceptionUtils.mpe(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package im.zhaojun.zfile.core;
|
||||
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;
|
||||
@@ -15,6 +16,8 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||
|
||||
/**
|
||||
* Controller 切面, 用于处理返回值统一封装.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@ControllerAdvice
|
||||
@@ -57,10 +60,9 @@ public class CommonResultControllerAdvice implements ResponseBodyAdvice<Object>
|
||||
// Get return body
|
||||
Object returnBody = bodyContainer.getValue();
|
||||
|
||||
if (returnBody instanceof AjaxJson) {
|
||||
// If the return body is instance of BaseResponse, then just do nothing
|
||||
AjaxJson<?> baseResponse = (AjaxJson<?>) returnBody;
|
||||
baseResponse.setTraceId(MDC.get("traceId"));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
@@ -1 +0,0 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
@@ -1,60 +0,0 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* mybatis-plus 配置类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
public class MyBatisPlusConfig {
|
||||
|
||||
@Resource
|
||||
private DataSource dataSource;
|
||||
|
||||
@Value("${spring.datasource.driver-class-name}")
|
||||
private String datasourceDriveClassName;
|
||||
|
||||
@Value("${spring.datasource.url}")
|
||||
private String datasourceUrl;
|
||||
|
||||
/**
|
||||
* 如果是 sqlite 数据库,自动创建数据库文件所在目录
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if (StrUtil.equals(datasourceDriveClassName, "org.sqlite.JDBC")) {
|
||||
String path = datasourceUrl.replace("jdbc:sqlite:", "");
|
||||
String folderPath = FileUtil.getParent(path, 1);
|
||||
if (!FileUtil.exist(folderPath)) {
|
||||
FileUtil.mkdir(folderPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mybatis plus 分页插件配置
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() throws SQLException {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
String databaseProductName = dataSource.getConnection().getMetaData().getDatabaseProductName();
|
||||
DbType dbType = DbType.getDbType(databaseProductName);
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(dbType));
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
|
||||
import im.zhaojun.zfile.core.httpclient.ZFileOkHttp3ClientHttpRequestFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* restTemplate 相关配置
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
/**
|
||||
* OneDrive 请求 RestTemplate.
|
||||
* 获取 header 中的 storageId 来判断到底是哪个存储源 ID, 在请求头中添加 Bearer: Authorization {token} 信息, 用于 API 认证.
|
||||
*/
|
||||
@Bean
|
||||
public RestTemplate oneDriveRestTemplate() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setRequestFactory(new ZFileOkHttp3ClientHttpRequestFactory());
|
||||
return restTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
@@ -6,6 +6,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* ZFile 配置类,将配置文件中的 zfile 配置项映射到该类中.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@@ -16,4 +18,20 @@ 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
package im.zhaojun.zfile.core.config.spring;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.BeanProperty;
|
||||
@@ -15,18 +15,17 @@ import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Jackson 枚举反序列化器
|
||||
* Jackson 枚举反序列化器, 用于将接收请求中的参数(一般为字符串)转换为枚举对象.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
@Setter
|
||||
@Slf4j
|
||||
@JsonComponent
|
||||
public class JacksonEnumDeserializer extends JsonDeserializer<Enum<?>> implements ContextualDeserializer {
|
||||
|
||||
private Class<?> clazz;
|
||||
|
||||
|
||||
/**
|
||||
* 反序列化操作
|
||||
*
|
||||
@@ -1,8 +1,13 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
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;
|
||||
@@ -15,13 +20,17 @@ import org.springframework.context.annotation.Configuration;
|
||||
@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 new TransactionAwareCacheManagerProxy(new ConcurrentMapCacheManager());
|
||||
return BooleanUtils.isNotTrue(dbCacheEnable) ? new NoOpCacheManager() : new TransactionAwareCacheManagerProxy(new ConcurrentMapCacheManager());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
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 javax.validation.constraints.NotNull;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -70,7 +71,7 @@ public class StringToEnumConverterFactory implements ConverterFactory<String, En
|
||||
// 获取
|
||||
T t = enumMap.get(source);
|
||||
if (t == null) {
|
||||
throw new IllegalArgumentException("该字符串找不到对应的枚举对象 字符串:" + source);
|
||||
throw new SystemException("该字符串找不到对应的枚举对象 字符串:" + source);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
@@ -84,7 +85,7 @@ public class StringToEnumConverterFactory implements ConverterFactory<String, En
|
||||
try {
|
||||
method = enumType.getMethod("getValue");
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new IllegalArgumentException(String.format("类:%s 找不到 getValue方法",
|
||||
throw new SystemException(String.format("类:%s 找不到 getValue方法",
|
||||
enumType.getName()));
|
||||
}
|
||||
} else {
|
||||
@@ -1,20 +1,13 @@
|
||||
package im.zhaojun.zfile.core.config;
|
||||
package im.zhaojun.zfile.core.config.spring;
|
||||
|
||||
import im.zhaojun.zfile.module.storage.model.enums.StorageTypeEnum;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
|
||||
import org.springframework.boot.web.server.ErrorPage;
|
||||
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* ZFile Web 相关配置.
|
||||
*
|
||||
@@ -23,17 +16,6 @@ import java.util.Set;
|
||||
@Configuration
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
|
||||
|
||||
/**
|
||||
* 添加自定义枚举格式化器.
|
||||
* @see StorageTypeEnum
|
||||
*/
|
||||
@Override
|
||||
public void addFormatters(FormatterRegistry registry) {
|
||||
registry.addConverterFactory(new StringToEnumConverterFactory());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 支持 url 中传入 <>[\]^`{|} 这些特殊字符.
|
||||
*/
|
||||
@@ -49,20 +31,13 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
||||
return webServerFactory;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
|
||||
return factory -> {
|
||||
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/index.html");
|
||||
ErrorPage error200Page = new ErrorPage(HttpStatus.OK, "/index.html");
|
||||
Set<ErrorPage> errorPages = new HashSet<>();
|
||||
errorPages.add(error404Page);
|
||||
errorPages.add(error200Page);
|
||||
factory.setErrorPages(errorPages);
|
||||
};
|
||||
/**
|
||||
* 添加自定义枚举格式化器.
|
||||
* @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,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";
|
||||
|
||||
}
|
||||
@@ -12,10 +12,6 @@ import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
public class ZFileConstant {
|
||||
|
||||
public static final Character PATH_SEPARATOR_CHAR = '/';
|
||||
|
||||
public static final String PATH_SEPARATOR = "/";
|
||||
|
||||
/**
|
||||
* 最大支持文本文件大小为 ? KB 的文件内容.
|
||||
*/
|
||||
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
@@ -1,25 +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 = "/**/{[path:[^\\.]*}")
|
||||
public String redirect() {
|
||||
// Forward to home page so that route is preserved.
|
||||
return "forward:/";
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,9 +3,10 @@ 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.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
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;
|
||||
@@ -22,7 +23,7 @@ import java.util.Date;
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Api(tags = "日志")
|
||||
@Tag(name = "日志")
|
||||
@ApiSort(8)
|
||||
@Slf4j
|
||||
@RestController
|
||||
@@ -33,7 +34,8 @@ public class LogController {
|
||||
private String zfileLogPath;
|
||||
|
||||
@GetMapping("/log/download")
|
||||
@ApiOperation(value = "下载系统日志")
|
||||
@Operation(summary = "下载系统日志")
|
||||
@DemoDisable
|
||||
public ResponseEntity<Resource> downloadLog() {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("下载诊断日志");
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 非法使用下载链接异常.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class IllegalDownloadLinkException extends ZFileRuntimeException {
|
||||
|
||||
public IllegalDownloadLinkException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 系统初始化异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class InstallSystemException extends ZFileRuntimeException {
|
||||
|
||||
public InstallSystemException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 无效的直链异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class InvalidShortLinkException extends ZFileRuntimeException {
|
||||
|
||||
public InvalidShortLinkException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 登陆验证码验证异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class LoginVerifyException extends ZFileRuntimeException {
|
||||
|
||||
public LoginVerifyException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
|
||||
/**
|
||||
* 密码校验失败异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class PasswordVerifyException extends RuntimeException {
|
||||
|
||||
private final Integer code;
|
||||
|
||||
public PasswordVerifyException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 文件预览异常类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class PreviewException extends ZFileRuntimeException {
|
||||
|
||||
public PreviewException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
import im.zhaojun.zfile.core.util.CodeMsg;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Service 层异常
|
||||
* 所有 message 均为系统日志打印输出, CodeMsg 中的消息才是返回给客户端的消息.
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ServiceException extends RuntimeException {
|
||||
|
||||
private CodeMsg codeMsg;
|
||||
|
||||
public ServiceException(CodeMsg codeMsg) {
|
||||
this.codeMsg = codeMsg;
|
||||
}
|
||||
|
||||
public ServiceException(String message, CodeMsg codeMsg) {
|
||||
super(message);
|
||||
this.codeMsg = codeMsg;
|
||||
}
|
||||
|
||||
public ServiceException(String message, Throwable cause, CodeMsg codeMsg) {
|
||||
super(message, cause);
|
||||
this.codeMsg = codeMsg;
|
||||
}
|
||||
|
||||
public ServiceException(Throwable cause, CodeMsg codeMsg) {
|
||||
super(cause);
|
||||
this.codeMsg = codeMsg;
|
||||
}
|
||||
|
||||
public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, CodeMsg codeMsg) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
this.codeMsg = codeMsg;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
import im.zhaojun.zfile.module.storage.model.param.IStorageParam;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 存储源自动设置 cors 异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class StorageSourceAutoConfigCorsException extends RuntimeException {
|
||||
|
||||
private final IStorageParam iStorageParam;
|
||||
|
||||
public StorageSourceAutoConfigCorsException(String message, Throwable cause, IStorageParam iStorageParam) {
|
||||
super(message, cause);
|
||||
this.iStorageParam = iStorageParam;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
import im.zhaojun.zfile.core.util.CodeMsg;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 存储源异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Getter
|
||||
public class StorageSourceException extends ServiceException {
|
||||
|
||||
/**
|
||||
* 是否使用异常消息进行接口返回,如果是则取异常的 message, 否则取 CodeMsg 中的 message
|
||||
*/
|
||||
private boolean responseExceptionMessage;
|
||||
|
||||
/**
|
||||
* 存储源 ID
|
||||
*/
|
||||
private final Integer storageId;
|
||||
|
||||
public StorageSourceException(CodeMsg codeMsg, Integer storageId, String message) {
|
||||
super(message, codeMsg);
|
||||
this.storageId = storageId;
|
||||
}
|
||||
|
||||
public StorageSourceException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) {
|
||||
super(message, cause, codeMsg);
|
||||
this.storageId = storageId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 responseExceptionMessage 判断使用异常消息进行接口返回,如果是则取异常的 message, 否则取 CodeMsg 中的 message
|
||||
*
|
||||
* @return 异常消息
|
||||
*/
|
||||
public String getResultMessage() {
|
||||
return responseExceptionMessage ? super.getMessage() : super.getCodeMsg().getMsg();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置值是否使用异常消息进行接口返回
|
||||
*
|
||||
* @param responseExceptionMessage
|
||||
* 是否使用异常消息进行接口返回
|
||||
*
|
||||
* @return 当前对象
|
||||
*/
|
||||
public StorageSourceException setResponseExceptionMessage(boolean responseExceptionMessage) {
|
||||
this.responseExceptionMessage = responseExceptionMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 存储源不支持代理上传异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class StorageSourceNotSupportProxyUploadException extends ZFileRuntimeException {
|
||||
|
||||
public StorageSourceNotSupportProxyUploadException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class StorageSourceRefreshTokenException extends RuntimeException {
|
||||
|
||||
private final Integer storageId;
|
||||
|
||||
public StorageSourceRefreshTokenException(String message, Integer storageId) {
|
||||
super(message);
|
||||
this.storageId = storageId;
|
||||
}
|
||||
|
||||
public StorageSourceRefreshTokenException(String message, Throwable cause, Integer storageId) {
|
||||
super(message, cause);
|
||||
this.storageId = storageId;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* 文件解析异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class TextParseException extends ZFileRuntimeException {
|
||||
|
||||
public TextParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TextParseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class ZFileRetryException extends RuntimeException {
|
||||
|
||||
public ZFileRetryException() {
|
||||
}
|
||||
|
||||
public ZFileRetryException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ZFileRetryException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public ZFileRetryException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ZFileRetryException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class ZFileRuntimeException extends RuntimeException {
|
||||
|
||||
public ZFileRuntimeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ZFileRuntimeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception.file;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.StorageSourceException;
|
||||
import im.zhaojun.zfile.core.util.CodeMsg;
|
||||
|
||||
/**
|
||||
* 无效的存储源异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class InvalidStorageSourceException extends StorageSourceException {
|
||||
|
||||
public InvalidStorageSourceException(String message) {
|
||||
super(CodeMsg.STORAGE_SOURCE_NOT_FOUND, null, message);
|
||||
}
|
||||
|
||||
public InvalidStorageSourceException(Integer storageId) {
|
||||
super(CodeMsg.STORAGE_SOURCE_NOT_FOUND, storageId, CodeMsg.STORAGE_SOURCE_NOT_FOUND.getMsg());
|
||||
}
|
||||
|
||||
public InvalidStorageSourceException(Integer storageId, String message) {
|
||||
super(CodeMsg.STORAGE_SOURCE_NOT_FOUND, storageId, message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception.file.init;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.StorageSourceException;
|
||||
import im.zhaojun.zfile.core.util.CodeMsg;
|
||||
|
||||
/**
|
||||
* 存储源初始化异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class InitializeStorageSourceException extends StorageSourceException {
|
||||
|
||||
public InitializeStorageSourceException(CodeMsg codeMsg, Integer storageId, String message) {
|
||||
super(codeMsg, storageId, message);
|
||||
}
|
||||
|
||||
public InitializeStorageSourceException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) {
|
||||
super(codeMsg, storageId, message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception.file.operator;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.StorageSourceException;
|
||||
import im.zhaojun.zfile.core.util.CodeMsg;
|
||||
|
||||
/**
|
||||
* 禁止服务器代理下载异常
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
public class DisableProxyDownloadException extends StorageSourceException {
|
||||
|
||||
public DisableProxyDownloadException(CodeMsg codeMsg, Integer storageId) {
|
||||
super(codeMsg, storageId, null);
|
||||
}
|
||||
|
||||
public DisableProxyDownloadException(CodeMsg codeMsg, Integer storageId, String message) {
|
||||
super(codeMsg, storageId, message);
|
||||
}
|
||||
|
||||
public DisableProxyDownloadException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) {
|
||||
super(codeMsg, storageId, message, cause);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package im.zhaojun.zfile.core.exception.file.operator;
|
||||
|
||||
import im.zhaojun.zfile.core.exception.StorageSourceException;
|
||||
import im.zhaojun.zfile.core.util.CodeMsg;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 存储源文件操作异常
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class StorageSourceFileOperatorException extends StorageSourceException {
|
||||
|
||||
public StorageSourceFileOperatorException(CodeMsg codeMsg, Integer storageId, String message, Throwable cause) {
|
||||
super(codeMsg, storageId, message, cause);
|
||||
}
|
||||
|
||||
}
|
||||