mirror of
https://github.com/zfile-dev/zfile.git
synced 2025-04-19 05:34:52 +00:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4a625dac6 | ||
|
|
1c2f3de55c | ||
|
|
ed375768f5 | ||
|
|
b0c4ed1fad | ||
|
|
8d30ca7eee | ||
|
|
43aba8e20e | ||
|
|
a2fa8b3eeb | ||
|
|
0ef77ee11b | ||
|
|
6608c0b456 | ||
|
|
3ceb5c8c1b | ||
|
|
7399c89a8e | ||
|
|
477c9dbdd2 | ||
|
|
e8117c7d3b | ||
|
|
b29ff1e646 | ||
|
|
774a8e184a | ||
|
|
7da1405798 | ||
|
|
564770ba3c | ||
|
|
ac07de4e73 | ||
|
|
3933eba99a | ||
|
|
27e8f7961e | ||
|
|
55843cbef6 | ||
|
|
ebbb33409f | ||
|
|
b39360791f | ||
|
|
9833378e25 | ||
|
|
31df2d16e2 | ||
|
|
f8f07912a1 | ||
|
|
edf43954c6 | ||
|
|
5a816b1dfb | ||
|
|
910406c33a | ||
|
|
543f76ad1d | ||
|
|
1cc2d874a1 | ||
|
|
18de372bf9 | ||
|
|
5748644814 | ||
|
|
fb8060d316 | ||
|
|
a4005defe2 | ||
|
|
4c90d5bdda | ||
|
|
fa46850bb4 | ||
|
|
a7551fce53 | ||
|
|
0591669ec9 | ||
|
|
6286a9e9aa | ||
|
|
c67ee1e89d | ||
|
|
c75695c63a | ||
|
|
8f771c652d | ||
|
|
d6e13d6115 | ||
|
|
8460d17b07 | ||
|
|
49806221b4 | ||
|
|
409af8409d | ||
|
|
6647efeb03 | ||
|
|
c8bd52e26a | ||
|
|
27db6ed14a | ||
|
|
b079d03753 | ||
|
|
edf8e114ad | ||
|
|
67a84edd4d | ||
|
|
d798750ee6 | ||
|
|
fea1da86fc | ||
|
|
96a0c90600 | ||
|
|
a054e82740 | ||
|
|
dc0e84e1e3 | ||
|
|
ae31005959 | ||
|
|
0f167a304d | ||
|
|
43d8143c74 | ||
|
|
b2fb1af99b | ||
|
|
a4f4d654a3 | ||
|
|
04d9c24b43 |
52
.github/ISSUE_TEMPLATE/bug_report.md
vendored
52
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -6,43 +6,19 @@ labels: 'bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
<!--
|
||||
你好!感谢你正在考虑为 ZFile 提交一个 bug。请花一点点时间尽量详细地回答以下基础问题。
|
||||
为了帮助我们更好的解决您的问题,请填写以下选项(不填写完整可能会被直接关闭 issue):
|
||||
|
||||
谢谢!
|
||||
-->
|
||||
|
||||
<!--
|
||||
请确认你已经做了下面这些事情,若 bug 还是未解决,那么请尽可详细地描述你的问题。
|
||||
|
||||
- 我已经安装了最新版的 ZFile
|
||||
- 我已经阅读了 ZFile 的文档:https://docs.zfile.vip
|
||||
- 我已经搜索了已有的 Issues 列表中有关的信息
|
||||
- 我已经清理过浏览器缓存并重试
|
||||
-->
|
||||
|
||||
## 我的环境
|
||||
|
||||
<!--
|
||||
请登录到管理后台,点击左侧系统监控, 复制或截图此页内容.
|
||||
-->
|
||||
|
||||
## 错误日志
|
||||
|
||||
<!--
|
||||
请登录到管理后台,点击左侧系统监控, 点击右上角诊断日志下载, 然后上传到此 Issue 中.
|
||||
-->
|
||||
|
||||
## 期望行为
|
||||
|
||||
<!--
|
||||
你期望会发生什么?
|
||||
-->
|
||||
|
||||
## 当前行为
|
||||
|
||||
<!--
|
||||
描述 bug 细节,确认出现此问题的复现步骤,例如点击了哪里,发生了什么情况?
|
||||
|
||||
你可以粘贴截图或附件。
|
||||
-->
|
||||
- 是否已搜索其他 issue,没有人提过这个问题?:
|
||||
- 当前 ZFile 版本:
|
||||
- 是否尝试最新版是否已解决此问题:
|
||||
- 是否尝试重启 ZFile,且问题依旧存在?:
|
||||
- 是否已尝试清空浏览器缓存,且问题依旧存在?:
|
||||
- 操作系统(如 Windows、Mac、iOS、安卓):
|
||||
- 浏览器(如 Chrome、Firefox、Safari,X 浏览器):
|
||||
- 做什么操作提示的错误?:
|
||||
- 期望行为(应该是什么样的结果):
|
||||
- 当前行为(当前是什么样的结果):
|
||||
- 错误日志(可选):
|
||||
- 复现步骤(可选):
|
||||
- 您的额外信息(可选):
|
||||
29
.github/ISSUE_TEMPLATE/feature_request.md
vendored
29
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -7,28 +7,11 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
你好!感谢你愿意考虑希望 ZFile 增加某个新功能。请花一点点时间尽量详细地回答以下基础问题。
|
||||
|
||||
谢谢!
|
||||
-->
|
||||
为了帮助我们更好的解决您的问题,请填写以下选项(不填写完整可能会被直接关闭 issue):
|
||||
|
||||
## 概述
|
||||
|
||||
<!--
|
||||
对这个新功能的一段描述
|
||||
-->
|
||||
|
||||
## 动机
|
||||
|
||||
<!--
|
||||
为什么你希望在 ZFile 中使用这个功能?
|
||||
-->
|
||||
|
||||
## 详细解释
|
||||
|
||||
<!--
|
||||
详细描述这个新功能。
|
||||
|
||||
如果这是一个小功能,你可以忽略这部分。
|
||||
-->
|
||||
- 是否已搜索其他 issue,没有人提过这个功能?:
|
||||
- 是否已尝试使用最新版本,且仍然没有此功能?:
|
||||
- 功能概述:
|
||||
- 功能动机:
|
||||
- 详细解释(可选):
|
||||
14
.github/ISSUE_TEMPLATE/question.md
vendored
14
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,14 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: 对 ZFile 有任何问题吗?
|
||||
title: ''
|
||||
labels: 'question'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
如果你有任何问题也可以通过此渠道来向我们反馈。
|
||||
|
||||
谢谢!
|
||||
-->
|
||||
244
README.md
244
README.md
@@ -1,200 +1,64 @@
|
||||
<p align = "center">
|
||||
<img alt="ZFile" src="https://cdn.jun6.net/2021/04/21/69a89344e2a84.png" height="150px">
|
||||
<p align="center">
|
||||
<a href="https://zfile.vip" target="_blank" rel="noopener noreferrer">
|
||||
<img alt="ZFile" src="https://cdn.jun6.net/2021/04/21/69a89344e2a84.png" height="150px">
|
||||
</a>
|
||||
<br><br>
|
||||
基于 Java 的在线网盘程序,支持对接 S3、OneDrive、SharePoint、又拍云、本地存储、FTP、SFTP 等存储源,支持在线浏览图片、播放音视频,文本文件等文件类型。
|
||||
基于 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">
|
||||
<img src="https://api.codacy.com/project/badge/Grade/70b793267f7941d58cbd93f50c9a8e0a">
|
||||
<img src="https://img.shields.io/github/last-commit/zhaojun1998/zfile.svg?style=flat-square">
|
||||
<img src="https://img.shields.io/github/downloads/zhaojun1998/zfile/total?style=flat-square">
|
||||
<img src="https://img.shields.io/github/v/release/zhaojun1998/zfile?style=flat-square">
|
||||
<img src="https://img.shields.io/github/commit-activity/y/zhaojun1998/zfile?style=flat-square">
|
||||
<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">
|
||||
<img src="https://img.shields.io/github/issues-closed-raw/zhaojun1998/zfile?style=flat-square">
|
||||
<img src="https://img.shields.io/github/forks/zhaojun1998/zfile?style=flat-square">
|
||||
<img src="https://img.shields.io/github/stars/zhaojun1998/zfile?style=flat-square">
|
||||
<img src="https://img.shields.io/github/watchers/zhaojun1998/zfile?style=flat-square">
|
||||
<img src="https://img.shields.io/github/issues/zhaojun1998/zfile?style=flat-square" alt="issues">
|
||||
<img src="https://img.shields.io/github/issues-closed-raw/zhaojun1998/zfile?style=flat-square" alt="closed issues">
|
||||
<img src="https://img.shields.io/github/forks/zhaojun1998/zfile?style=flat-square" alt="forks">
|
||||
<img src="https://img.shields.io/github/stars/zhaojun1998/zfile?style=flat-square" alt="stars">
|
||||
<img src="https://img.shields.io/github/watchers/zhaojun1998/zfile?style=flat-square" alt="watchers">
|
||||
</p>
|
||||
|
||||
## 相关地址
|
||||
|
||||
预览地址: [https://zfile.vip](https://zfile.vip)
|
||||
|
||||
文档地址: [https://docs.zfile.vip](https://docs.zfile.vip)
|
||||
|
||||
社区地址: [https://bbs.zfile.vip](https://bbs.zfile.vip)
|
||||
|
||||
项目源码: [https://github.com/zhaojun1998/zfile](https://github.com/zhaojun1998/zfile)
|
||||
|
||||
前端源码: [https://github.com/zhaojun1998/zfile-vue](https://github.com/zhaojun1998/zfile-vue)
|
||||
|
||||
## 系统特色
|
||||
|
||||
* 支持文件操作:上传, 删除, 重命名, 新建文件夹. 后续还会支持移动和复制文件(详见下方**后续计划**).
|
||||
* 操作系统级的文件操作体验
|
||||
1. 支持拖拽上传和 Ctrl + V 粘贴上传文件和文件夹
|
||||
2. 支持 Ctrl + A 全选文件, 按 Esc 取消全选.
|
||||
3. 支持拖拽批量选择文件
|
||||
4. 支持按住 Shift 多选文件
|
||||
5. 支持多选文件后按 Delete 键删除文件.
|
||||
6. 按 Backspace 返回上级文件夹.
|
||||
* 全新的 UI 风格, 更简洁易用.
|
||||
* 支持给文件生成直链(短链,永久直链,二维码)
|
||||
* 视频播放器支持调用本地软件进行下载,如迅雷、Motrix. 支持调用本地播放器播放,更好的进行视频解码: PotPlayer, IINA, VLC, nPlayer, MXPlayer(Free/Pro)
|
||||
* 全新画廊模式, 支持按照瀑布流显示图片, 支持自定义 N 栏, 自定义每栏的间距
|
||||
* 支持给文件夹配置 markdown 文档, 并配置显示方式, 如顶部、底部、弹窗
|
||||
* 支持给文件夹设置密码
|
||||
* 支持隐藏文件或文件夹
|
||||
* 后台登录支持设置图片验证码和 2FA 身份认证,防止后台被暴力破解
|
||||
* 支持自定义文件格式后缀, 避免系统内置的不完善导致文件无法预览.
|
||||
* Docker 支持
|
||||
* 自定义 JS, CSS
|
||||
* 同时挂载多个存储策略
|
||||
* 支持 S3 协议, 阿里云 OSS, FTP, SFTP, 华为云 OBS, 本地存储, MINIO, OneDrive 国际/家庭/个人版/世纪互联版/SharePoint, , 七牛云 KODO, 腾讯云 COS, 又拍云 USS.
|
||||
|
||||
## 快速开始
|
||||
|
||||
<details>
|
||||
<summary>普通安装 (点击展开)</summary>
|
||||
安装依赖环境:
|
||||
请参考部署文档: [https://docs.zfile.vip](https://docs.zfile.vip)
|
||||
|
||||
```bash
|
||||
## 在线体验
|
||||
|
||||
# CentOS系统
|
||||
yum install -y java-1.8.0-openjdk unzip
|
||||
```
|
||||
[https://demo.zfile.vip](https://demo.zfile.vip)
|
||||
|
||||
```bash
|
||||
# Debian 9 / Ubuntu 14+
|
||||
apt update
|
||||
apt install -y openjdk-8-jre-headless unzip
|
||||
```
|
||||
## 功能预览
|
||||
|
||||
```bash
|
||||
# Debian 10 (Buster) 系统
|
||||
apt update && apt install -y apt-transport-https software-properties-common ca-certificates dirmngr gnupg
|
||||
wget -qO - https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public | apt-key add -
|
||||
add-apt-repository --yes https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/
|
||||
apt update && apt install -y adoptopenjdk-8-hotspot-jre
|
||||
```
|
||||
|
||||
|
||||
下载项目:
|
||||
|
||||
```bash
|
||||
export ZFILE_INSTALL_PATH=~/zfile
|
||||
mkdir -p $ZFILE_INSTALL_PATH && cd $ZFILE_INSTALL_PATH
|
||||
wget https://c.jun6.net/ZFILE/zfile-release.war
|
||||
unzip zfile-release.war && rm -rf zfile-release.war
|
||||
chmod +x $ZFILE_INSTALL_PATH/bin/*.sh
|
||||
```
|
||||
|
||||
启动项目:
|
||||
|
||||
```bash
|
||||
~/zfile/bin/start.sh
|
||||
```
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary>Docker (点击展开)</summary>
|
||||
|
||||
```bash
|
||||
docker run -d --name=zfile --restart=always \
|
||||
-p 8080:8080 \
|
||||
-v /root/zfile/db:/root/.zfile-v4/db \
|
||||
-v /root/zfile/logs:/root/.zfile-v4/logs \
|
||||
zhaojun1998/zfile
|
||||
```
|
||||
|
||||
> 如需映射配置文件则需要先在宿主机下载配置文件,然后映射到容器内:
|
||||
|
||||
|
||||
```bash
|
||||
# 下载到 application.properties 文件到 /root 目录下, 此目录可自行更改, 如:
|
||||
curl -o /root/application.properties https://c.jun6.net/ZFILE/application.properties
|
||||
|
||||
# 然后增加一个 -v 参数,将此源文件映射到容器内(如修改宿主机的 application.properties 为其他路径, 则下面命令也要一起修改),如:
|
||||
docker run -d --name=zfile --restart=always \
|
||||
-p 8080:8080 \
|
||||
-v /root/zfile/db:/root/.zfile-v4/db \
|
||||
-v /root/zfile/logs:/root/.zfile-v4/logs \
|
||||
-v /root/application.properties:/root/application.properties \
|
||||
zhaojun1998/zfile
|
||||
```
|
||||
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary>Docker Compose (点击展开)</summary>
|
||||
|
||||
```yml
|
||||
version: '3.3'
|
||||
services:
|
||||
zfile:
|
||||
container_name: zfile
|
||||
restart: always
|
||||
ports:
|
||||
- '8080:8080'
|
||||
volumes:
|
||||
- '/root/zfile/db:/root/.zfile-v4/db'
|
||||
- '/root/zfile/logs:/root/.zfile-v4/logs'
|
||||
image: zhaojun1998/zfile
|
||||
```
|
||||
|
||||
> 如需映射配置文件则需要先在宿主机下载配置文件,然后映射到容器内:
|
||||
|
||||
|
||||
下载到 application.properties 文件到 /root 目录下, 此目录可自行更改, 命令如:
|
||||
```bash
|
||||
curl -o /root/application.properties https://c.jun6.net/ZFILE/application.properties
|
||||
```
|
||||
|
||||
> 然后增加一个 -v 参数,将此源文件映射到容器内(如修改宿主机的 application.properties 为其他路径, 则下面命令也要一起修改), 如:
|
||||
|
||||
```yml
|
||||
version: '3.3'
|
||||
services:
|
||||
zfile:
|
||||
container_name: zfile
|
||||
restart: always
|
||||
ports:
|
||||
- '8080:8080'
|
||||
volumes:
|
||||
- '/root/zfile/db:/root/.zfile-v4/db'
|
||||
- '/root/zfile/logs:/root/.zfile-v4/logs'
|
||||
image: zhaojun1998/zfile
|
||||
```
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
||||
篇幅有限, 更详细的安装教程及介绍请参考: [ZFile 文档](https://docs.zfile.vip)
|
||||
|
||||
## 预览
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
### 文件列表
|
||||

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

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

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

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

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

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

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

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

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

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

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

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

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

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

|
||||
|
||||
|
||||
## 支持作者
|
||||
@@ -203,10 +67,10 @@ services:
|
||||
|
||||
<img src="https://cdn.jun6.net/2021/03/27/152704e91f13d.png" width="400" alt="赞助我">
|
||||
|
||||
## Stargazers over time
|
||||
## Status
|
||||
|
||||
[](https://starchart.cc/zhaojun1998/zfile.svg)
|
||||

|
||||
|
||||
## 开发工具赞助
|
||||
## Star History
|
||||
|
||||
<a href="https://www.jetbrains.com/?from=zfile"><img src="https://cdn.jun6.net/2021/04/21/26e410d60b0b0.png?1=1" width="100px"></a>
|
||||
[](https://star-history.com/#zfile-dev/zfile&Date)
|
||||
198
pom.xml
198
pom.xml
@@ -5,20 +5,19 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.6.6</version>
|
||||
<version>2.6.8</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>im.zhaojun</groupId>
|
||||
<artifactId>zfile</artifactId>
|
||||
<version>4.0.5</version>
|
||||
<version>4.1.1</version>
|
||||
<name>zfile</name>
|
||||
<packaging>jar</packaging>
|
||||
<packaging>war</packaging>
|
||||
<description>一个在线的文件浏览系统</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<log4j2.version>2.17.0</log4j2.version>
|
||||
<org.mapstruct.version>1.5.1.Final</org.mapstruct.version>
|
||||
</properties>
|
||||
|
||||
@@ -41,33 +40,40 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>3.5.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 数据库相关 -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.29</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类 -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.3</version>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
<version>7.15.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>3.5.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 存储策略相关 API, 对象存储、FTP、 Rest API-->
|
||||
|
||||
<!-- 存储策略相关 SDK、 工具类-->
|
||||
<dependency>
|
||||
<groupId>com.upyun</groupId>
|
||||
<artifactId>java-sdk</artifactId>
|
||||
@@ -76,13 +82,53 @@
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-s3</artifactId>
|
||||
<version>1.12.146</version>
|
||||
<version>1.12.261</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.qiniu</groupId>
|
||||
<artifactId>qiniu-java-sdk</artifactId>
|
||||
<version>7.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
<version>0.1.55</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.lookfirst</groupId>
|
||||
<artifactId>sardine</artifactId>
|
||||
<version>5.10</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 登陆/权限相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.30.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 文档相关 -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 工具类 -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.24</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
@@ -92,73 +138,20 @@
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>3.8.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 其他工具类 -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.24</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mpatric</groupId>
|
||||
<artifactId>mp3agic</artifactId>
|
||||
<version>0.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.83</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc -->
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
<version>7.15.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 第三方依赖-权限相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.30.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>30.1.1-jre</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
@@ -169,42 +162,31 @@
|
||||
<artifactId>commons-chain</artifactId>
|
||||
<version>1.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.jcraft</groupId>
|
||||
<artifactId>jsch</artifactId>
|
||||
<version>0.1.55</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.lookfirst</groupId>
|
||||
<artifactId>sardine</artifactId>
|
||||
<version>5.10</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>dev.samstevens.totp</groupId>
|
||||
<artifactId>totp-spring-boot-starter</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.beust</groupId>
|
||||
<artifactId>jcommander</artifactId>
|
||||
<version>1.82</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20200518</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpmime</artifactId>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.70</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -252,27 +234,27 @@
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<!--<plugin>-->
|
||||
<!-- <groupId>com.uyoqu.framework</groupId>-->
|
||||
<!-- <artifactId>maven-plugin-starter</artifactId>-->
|
||||
<!-- <version>1.0.0</version>-->
|
||||
<!-- <executions>-->
|
||||
<!-- <execution>-->
|
||||
<!-- <phase>package</phase>-->
|
||||
<!-- <goals>-->
|
||||
<!-- <goal>bin</goal>-->
|
||||
<!-- </goals>-->
|
||||
<!-- </execution>-->
|
||||
<!-- </executions>-->
|
||||
<!-- <configuration>-->
|
||||
<!-- <jvms>-->
|
||||
<!-- <jvm>-Djava.security.egd=file:/dev/./urandom</jvm>-->
|
||||
<!-- <jvm>-Dfile.encoding=utf-8</jvm>-->
|
||||
<!-- <jvm>-Djava.net.preferIPv4Stack=false</jvm>-->
|
||||
<!-- <jvm>-Djava.net.preferIPv4Addresses=true</jvm>-->
|
||||
<!-- </jvms>-->
|
||||
<!-- </configuration>-->
|
||||
<!--</plugin>-->
|
||||
<plugin>
|
||||
<groupId>com.uyoqu.framework</groupId>
|
||||
<artifactId>maven-plugin-starter</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>bin</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<jvms>
|
||||
<jvm>-Djava.security.egd=file:/dev/./urandom</jvm>
|
||||
<jvm>-Dfile.encoding=utf-8</jvm>
|
||||
<jvm>-Djava.net.preferIPv4Stack=false</jvm>
|
||||
<jvm>-Djava.net.preferIPv4Addresses=true</jvm>
|
||||
</jvms>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
package im.zhaojun.zfile;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
|
||||
@ServletComponentScan(basePackages = "im.zhaojun.zfile.*.filter")
|
||||
@@ -22,22 +17,5 @@ public class ZfileApplication {
|
||||
SpringApplication.run(ZfileApplication.class, args);
|
||||
}
|
||||
|
||||
@Value("${spring.datasource.driver-class-name}")
|
||||
private String datasourceDriveClassName;
|
||||
|
||||
@Value("${spring.datasource.url}")
|
||||
private String datasourceUrl;
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if (StrUtil.equals(datasourceDriveClassName, "org.sqlite.JDBC")) {
|
||||
String path = datasourceUrl.replace("jdbc:sqlite:", "");
|
||||
String folderPath = FileUtil.getParent(path, 1);
|
||||
if (!FileUtil.exist(folderPath)) {
|
||||
FileUtil.mkdir(folderPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package im.zhaojun.zfile.admin.annoation;
|
||||
package im.zhaojun.zfile.admin.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -1,4 +1,4 @@
|
||||
package im.zhaojun.zfile.admin.annoation;
|
||||
package im.zhaojun.zfile.admin.annotation;
|
||||
|
||||
import im.zhaojun.zfile.admin.model.enums.StorageParamTypeEnum;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package im.zhaojun.zfile.admin.annoation;
|
||||
package im.zhaojun.zfile.admin.annotation;
|
||||
|
||||
import im.zhaojun.zfile.admin.model.param.IStorageParam;
|
||||
import im.zhaojun.zfile.admin.annoation.model.StorageSourceParamDef;
|
||||
import im.zhaojun.zfile.admin.annotation.model.StorageSourceParamDef;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package im.zhaojun.zfile.admin.annoation;
|
||||
package im.zhaojun.zfile.admin.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.zfile.admin.annoation.model;
|
||||
package im.zhaojun.zfile.admin.annotation.model;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamSelectOption;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamSelectOption;
|
||||
import im.zhaojun.zfile.admin.model.enums.StorageParamTypeEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
@@ -1,9 +1,9 @@
|
||||
package im.zhaojun.zfile.admin.annoation.select.impl;
|
||||
package im.zhaojun.zfile.admin.annotation.select.impl;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamSelect;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamSelect;
|
||||
import im.zhaojun.zfile.admin.model.param.IStorageParam;
|
||||
import im.zhaojun.zfile.admin.annoation.model.StorageSourceParamDef;
|
||||
import im.zhaojun.zfile.admin.annotation.model.StorageSourceParamDef;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
@@ -10,6 +10,7 @@ import com.github.xiaoymin.knife4j.annotations.ApiSort;
|
||||
import im.zhaojun.zfile.admin.convert.DownloadLogConvert;
|
||||
import im.zhaojun.zfile.admin.model.entity.DownloadLog;
|
||||
import im.zhaojun.zfile.admin.model.entity.StorageSource;
|
||||
import im.zhaojun.zfile.admin.model.request.link.BatchDeleteRequest;
|
||||
import im.zhaojun.zfile.admin.model.request.link.QueryDownloadLogRequest;
|
||||
import im.zhaojun.zfile.admin.model.result.link.DownloadLogResult;
|
||||
import im.zhaojun.zfile.admin.service.DownloadLogService;
|
||||
@@ -23,12 +24,13 @@ import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -134,12 +136,13 @@ public class DownloadLogManagerController {
|
||||
|
||||
|
||||
@ApiOperationSupport(order = 3)
|
||||
@DeleteMapping("/delete/batch")
|
||||
@PostMapping("/delete/batch")
|
||||
@ResponseBody
|
||||
@ApiImplicitParam(paramType = "query", name = "ids", value = "直链 id", required = true)
|
||||
@ApiOperation(value = "批量删除直链")
|
||||
public AjaxJson<Void> batchDelete(@RequestParam("id[]") Integer[] ids) {
|
||||
downloadLogService.removeBatchByIds(Arrays.asList(ids));
|
||||
public AjaxJson<Void> batchDelete(@RequestBody BatchDeleteRequest batchDeleteRequest) {
|
||||
List<Integer> ids = batchDeleteRequest.getIds();
|
||||
downloadLogService.removeBatchByIds(ids);
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.github.xiaoymin.knife4j.annotations.ApiSort;
|
||||
import im.zhaojun.zfile.admin.convert.ShortLinkConvert;
|
||||
import im.zhaojun.zfile.admin.model.entity.ShortLink;
|
||||
import im.zhaojun.zfile.admin.model.entity.StorageSource;
|
||||
import im.zhaojun.zfile.admin.model.request.link.BatchDeleteRequest;
|
||||
import im.zhaojun.zfile.admin.model.result.link.ShortLinkResult;
|
||||
import im.zhaojun.zfile.admin.service.ShortLinkService;
|
||||
import im.zhaojun.zfile.admin.service.StorageSourceService;
|
||||
@@ -21,12 +22,13 @@ import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -129,12 +131,12 @@ public class ShortLinkManagerController {
|
||||
|
||||
|
||||
@ApiOperationSupport(order = 3)
|
||||
@DeleteMapping("/link/delete/batch")
|
||||
@PostMapping("/link/delete/batch")
|
||||
@ResponseBody
|
||||
@ApiImplicitParam(paramType = "query", name = "ids", value = "短链 id", required = true)
|
||||
@ApiOperation(value = "批量删除短链")
|
||||
public AjaxJson<Void> batchDelete(@RequestParam("id[]") Integer[] ids) {
|
||||
shortLinkService.removeBatchByIds(Arrays.asList(ids));
|
||||
public AjaxJson<Void> batchDelete(@RequestBody BatchDeleteRequest batchDeleteRequest) {
|
||||
shortLinkService.removeBatchByIds(batchDeleteRequest.getIds());
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiSort;
|
||||
import im.zhaojun.zfile.common.context.StorageSourceContext;
|
||||
import im.zhaojun.zfile.home.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.admin.annoation.model.StorageSourceParamDef;
|
||||
import im.zhaojun.zfile.admin.annotation.model.StorageSourceParamDef;
|
||||
import im.zhaojun.zfile.common.util.AjaxJson;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
|
||||
@@ -13,6 +13,7 @@ import im.zhaojun.zfile.home.model.dto.StorageSourceDTO;
|
||||
import im.zhaojun.zfile.home.model.request.UpdateStorageSortRequest;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -131,4 +132,17 @@ public class StorageSourceController {
|
||||
return AjaxJson.getSuccessData(exist);
|
||||
}
|
||||
|
||||
@ApiOperationSupport(order = 9)
|
||||
@ApiOperation(value = "修改 readme 兼容模式", notes = "修改 readme 兼容模式是否启用")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(paramType = "path", name = "storageId", value = "存储源 id", required = true),
|
||||
@ApiImplicitParam(paramType = "path", name = "status", value = "存储源兼容模式状态", required = true)
|
||||
})
|
||||
@PostMapping("/storage/{storageId}/compatibility_readme/{status}")
|
||||
public AjaxJson<Void> changeCompatibilityReadme(@PathVariable Integer storageId, @PathVariable Boolean status) {
|
||||
StorageSource storageSource = storageSourceService.findById(storageId);
|
||||
storageSource.setCompatibilityReadme(status);
|
||||
storageSourceService.updateById(storageSource);
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import org.mapstruct.Mapping;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 下载日志实体类转换器
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Component
|
||||
|
||||
@@ -8,6 +8,8 @@ import org.mapstruct.Mapping;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 直链实体类器
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Component
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package im.zhaojun.zfile.admin.model.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* OneDrive Token DTO
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
public class OAuth2Token {
|
||||
|
||||
private String clientId;
|
||||
|
||||
private String clientSecret;
|
||||
|
||||
private String redirectUri;
|
||||
|
||||
private String accessToken;
|
||||
|
||||
private String refreshToken;
|
||||
|
||||
private boolean success;
|
||||
|
||||
private String body;
|
||||
|
||||
public static OAuth2Token success(String clientId, String clientSecret, String redirectUri, String accessToken, String refreshToken, String body) {
|
||||
OAuth2Token token = new OAuth2Token();
|
||||
token.setClientId(clientId);
|
||||
token.setClientSecret(clientSecret);
|
||||
token.setRedirectUri(redirectUri);
|
||||
token.setSuccess(true);
|
||||
token.setBody(body);
|
||||
token.setAccessToken(accessToken);
|
||||
token.setRefreshToken(refreshToken);
|
||||
return token;
|
||||
}
|
||||
|
||||
public static OAuth2Token fail(String clientId, String clientSecret, String redirectUri, String body) {
|
||||
OAuth2Token token = new OAuth2Token();
|
||||
token.setClientId(clientId);
|
||||
token.setClientSecret(clientSecret);
|
||||
token.setRedirectUri(redirectUri);
|
||||
token.setSuccess(false);
|
||||
token.setBody(body);
|
||||
return token;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package im.zhaojun.zfile.admin.model.dto;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* OneDrive Token DTO
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
public class OneDriveToken {
|
||||
|
||||
@JSONField(name = "access_token")
|
||||
private String accessToken;
|
||||
|
||||
@JSONField(name = "refresh_token")
|
||||
private String refreshToken;
|
||||
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
package im.zhaojun.zfile.admin.model.entity;
|
||||
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import im.zhaojun.zfile.common.util.RequestHolder;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@@ -19,6 +24,7 @@ import java.util.Date;
|
||||
@Data
|
||||
@ApiModel(value="文件下载日志")
|
||||
@TableName(value = "`download_log`")
|
||||
@NoArgsConstructor
|
||||
public class DownloadLog implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -62,4 +68,16 @@ public class DownloadLog implements Serializable {
|
||||
@ApiModelProperty(value="访问 referer")
|
||||
private String referer;
|
||||
|
||||
|
||||
public DownloadLog(String path, String storageKey, String shortKey) {
|
||||
this.path = path;
|
||||
this.storageKey = storageKey;
|
||||
this.shortKey = shortKey;
|
||||
this.createTime = new Date();
|
||||
HttpServletRequest request = RequestHolder.getRequest();
|
||||
this.ip = ServletUtil.getClientIP(request);
|
||||
this.referer = request.getHeader(HttpHeaders.REFERER);
|
||||
this.userAgent = request.getHeader(HttpHeaders.USER_AGENT);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -97,6 +97,10 @@ public class StorageSource implements Serializable {
|
||||
@TableField(value = "default_switch_to_img_mode")
|
||||
@ApiModelProperty(value = "是否默认开启图片模式", example = "true")
|
||||
private Boolean defaultSwitchToImgMode;
|
||||
|
||||
@TableField(value = "compatibility_readme")
|
||||
@ApiModelProperty(value = "兼容 readme 模式", example = "true", notes = "兼容模式, 目录文档读取 readme.md 文件")
|
||||
private Boolean compatibilityReadme;
|
||||
|
||||
public boolean allowOperator() {
|
||||
// 允许文件操作,且允许匿名操作或者当前登录用户是管理员
|
||||
|
||||
@@ -42,12 +42,7 @@ public enum FileOperatorTypeEnum {
|
||||
/**
|
||||
* 移动文件&文件夹操作
|
||||
*/
|
||||
MOVE("移动", "move"),
|
||||
|
||||
/**
|
||||
* 搜索操作
|
||||
*/
|
||||
SEARCH("搜索", "search");
|
||||
MOVE("移动", "move");
|
||||
|
||||
private final String name;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annoation.select.impl.EncodingStorageParamSelect;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.select.impl.EncodingStorageParamSelect;
|
||||
import im.zhaojun.zfile.admin.model.enums.StorageParamTypeEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Google Drive 初始化参数
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
@ToString
|
||||
public class GoogleDriveParam extends ProxyTransferParam {
|
||||
|
||||
@StorageParamItem(name = "clientId", defaultValue = "${zfile.gd.clientId}", order = 1, description = "默认 API 仅用作示例,因审核原因,目前不可用,请自行申请 API", link = "https://docs.zfile.vip/advanced#google-drive-api", linkName = "自定义 API 文档")
|
||||
private String clientId;
|
||||
|
||||
@StorageParamItem(name = "SecretKey", defaultValue = "${zfile.gd.clientSecret}", order = 2)
|
||||
private String clientSecret;
|
||||
|
||||
@StorageParamItem(name = "回调地址", description = "这里要修改为自己的域名", defaultValue = "${zfile.gd.redirectUri}", order = 3)
|
||||
private String redirectUri;
|
||||
|
||||
@Setter
|
||||
@StorageParamItem(name = "访问令牌", link = "/gd/authorize", linkName = "前往获取令牌", order = 4)
|
||||
private String accessToken;
|
||||
|
||||
@Setter
|
||||
@StorageParamItem(name = "刷新令牌", order = 5)
|
||||
private String refreshToken;
|
||||
|
||||
@StorageParamItem(name = "网盘", order = 6, required = false)
|
||||
private String driveId;
|
||||
|
||||
@StorageParamItem(name = "基路径", defaultValue = "/", order = 7, description = "基路径表示读取的根文件夹,不填写表示允许读取所有。如: '/','/文件夹1'")
|
||||
private String basePath;
|
||||
|
||||
@StorageParamItem(name = "加速域名", required = false, description = "可使用 cf worker index 程序的链接,会使用 cf 中转下载,教程自行查询. 不填写则使用服务器中转下载.")
|
||||
private String domain;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
@@ -11,7 +11,7 @@ import lombok.Getter;
|
||||
@Getter
|
||||
public class LocalParam extends ProxyDownloadParam {
|
||||
|
||||
@StorageParamItem(name = "文件路径", description = "只支持绝对路径<br>Docker 部署需提前映射宿主机路径! " +
|
||||
@StorageParamItem(name = "文件路径", description = "只支持绝对路径<br>Docker 方式部署的话需提前映射宿主机路径! " +
|
||||
"(<a class='link' target='_blank' href='https://docs.docker.com/engine/reference/run/#volume-shared-filesystems'>配置文档</a>)")
|
||||
private String filePath;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
@@ -11,12 +11,14 @@ import lombok.Getter;
|
||||
@Getter
|
||||
public class MicrosoftDriveParam implements IStorageParam {
|
||||
|
||||
@StorageParamItem(name = "clientId", defaultValue = "${zfile.onedrive.clientId}", order = 1,
|
||||
description = "可自行更改,但修改后,下方获取访问令牌的地址不可用,需自行获取访问令牌和刷新令牌.")
|
||||
@StorageParamItem(name = "clientId", defaultValue = "${zfile.onedrive.clientId}", order = 1)
|
||||
private String clientId;
|
||||
|
||||
@StorageParamItem(name = "SecretKey", defaultValue = "${zfile.onedrive.clientSecret}", order = 2)
|
||||
private String clientSecret;
|
||||
|
||||
@StorageParamItem(name = "回调地址", description = "如使用自定义 api, 需将此处默认的域名修改为您的域名, 且需在 api 中配置为回调域名.", defaultValue = "${zfile.onedrive.redirectUri}", order = 3)
|
||||
private String redirectUri;
|
||||
|
||||
@StorageParamItem(name = "访问令牌", link = "/onedrive/authorize", linkName = "前往获取令牌", order = 3)
|
||||
private String accessToken;
|
||||
@@ -24,7 +26,7 @@ public class MicrosoftDriveParam implements IStorageParam {
|
||||
@StorageParamItem(name = "刷新令牌", order = 4)
|
||||
private String refreshToken;
|
||||
|
||||
@StorageParamItem(name = "反代域名", required = false, order = 7,
|
||||
@StorageParamItem(name = "反代域名", required = false, order = 7, description = "世纪互联版不建议启用,国际版启用后不一定比启用前快,这个要根据仔细网络情况决定.",
|
||||
link = "https://docs.zfile.vip/#/advanced?id=onedrive-cf", linkName = "配置文档")
|
||||
private String proxyDomain;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
@@ -11,10 +11,13 @@ import lombok.Getter;
|
||||
@Getter
|
||||
public class MinIOParam extends S3BaseParam {
|
||||
|
||||
@StorageParamItem(name = "地域")
|
||||
@StorageParamItem(name = "Bucket 域名 / CDN 加速域名", required = false, order = 5, description = "为 minio 的服务地址,非 web 访问地址,一般为 http://ip:9000")
|
||||
private String domain;
|
||||
|
||||
@StorageParamItem(name = "地域", defaultValue = "auto")
|
||||
private String region;
|
||||
|
||||
@StorageParamItem(name = "服务地址", description = "为 minio 的服务地址,非 web 访问地址,一般为 http://ip:9000")
|
||||
@StorageParamItem(name = "服务地址", order = 5, description = "为 minio 的服务地址,非 web 访问地址,一般为 http://ip:9000")
|
||||
private String endPoint;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
@@ -11,12 +11,15 @@ import lombok.Getter;
|
||||
@Getter
|
||||
public class OneDriveChinaParam extends OneDriveParam {
|
||||
|
||||
@StorageParamItem(name = "clientId", defaultValue = "${zfile.onedrive-china.clientId}",
|
||||
description = "可自行更改,但修改后,则下方获取访问令牌的地址不可用,需自行获取访问令牌和刷新令牌.", order = 1)
|
||||
@StorageParamItem(name = "clientId", defaultValue = "${zfile.onedrive-china.clientId}", order = 1)
|
||||
private String clientId;
|
||||
|
||||
@StorageParamItem(name = "SecretKey", defaultValue = "${zfile.onedrive-china.clientSecret}", order = 2)
|
||||
private String clientSecret;
|
||||
|
||||
@StorageParamItem(name = "回调地址", description = "如使用自定义 api, 需将此处默认的域名修改为您的域名, 且需在 api 中配置为回调域名.",
|
||||
defaultValue = "${zfile.onedrive-china.redirectUri}", order = 3)
|
||||
private String redirectUri;
|
||||
|
||||
@StorageParamItem(name = "访问令牌", link = "/onedrive/china-authorize", linkName = "前往获取令牌", order = 3)
|
||||
private String accessToken;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.model.enums.StorageParamTypeEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.model.enums.StorageParamTypeEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
@@ -36,7 +36,7 @@ public class S3BaseParam implements IStorageParam {
|
||||
@StorageParamItem(name = "下载签名有效期", required = false, defaultValue = "1800", description = "当为私有空间时, 用于下载签名的有效期, 单位为秒, 如不配置则默认为 1800 秒.")
|
||||
private Integer tokenTime;
|
||||
|
||||
@StorageParamItem(name = "是否自动配置 CORS 跨域设置", order = 100, type = StorageParamTypeEnum.SWITCH, defaultValue = "true", description = "如不配置跨域设置,可能会无法导致无法上传,或上传后看不到文件 <br> 此配置会覆盖之前的跨域配置,如您已经配置过,可忽略此选项")
|
||||
@StorageParamItem(name = "是否自动配置 CORS 跨域设置", order = 100, type = StorageParamTypeEnum.SWITCH, defaultValue = "true", description = "如不配置跨域设置,可能会无法导致无法上传,或上传后看不到文件")
|
||||
private boolean autoConfigCors;
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamSelectOption;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamSelectOption;
|
||||
import im.zhaojun.zfile.admin.model.enums.StorageParamTypeEnum;
|
||||
import lombok.Getter;
|
||||
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* SharePoint 世纪互联初始化参数
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Getter
|
||||
public class SharePointChinaParam extends SharePointParam {
|
||||
|
||||
@StorageParamItem(name = "clientId", defaultValue = "${zfile.onedrive-china.clientId}", order = 1,
|
||||
description = "可自行更改,但修改后,则下方获取访问令牌的地址不可用,需自行获取访问令牌和刷新令牌.")
|
||||
@StorageParamItem(name = "clientId", defaultValue = "${zfile.onedrive-china.clientId}", order = 1)
|
||||
private String clientId;
|
||||
|
||||
@StorageParamItem(name = "SecretKey", defaultValue = "${zfile.onedrive-china.clientSecret}", order = 2)
|
||||
private String clientSecret;
|
||||
|
||||
@StorageParamItem(name = "回调地址", description = "如使用自定义 api, 需将此处默认的域名修改为您的域名, 且需在 api 中配置为回调域名.",
|
||||
defaultValue = "${zfile.onedrive-china.redirectUri}", order = 3)
|
||||
private String redirectUri;
|
||||
|
||||
@StorageParamItem(name = "访问令牌", link = "/onedrive/china-authorize", linkName = "前往获取令牌", order = 3)
|
||||
private String accessToken;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
@@ -11,8 +11,7 @@ import lombok.Getter;
|
||||
@Getter
|
||||
public class SharePointParam extends MicrosoftDriveParam {
|
||||
|
||||
@StorageParamItem(name = "clientId", defaultValue = "${zfile.onedrive.clientId}", order = 1,
|
||||
description = "可自行更改,但修改后,下方获取访问令牌的地址不可用,需自行获取访问令牌和刷新令牌.")
|
||||
@StorageParamItem(name = "clientId", defaultValue = "${zfile.onedrive.clientId}", order = 1)
|
||||
private String clientId;
|
||||
|
||||
@StorageParamItem(name = "SecretKey", defaultValue = "${zfile.onedrive.clientSecret}", order = 2)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package im.zhaojun.zfile.admin.model.param;
|
||||
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,5 +65,8 @@ public class SaveStorageSourceRequest {
|
||||
|
||||
@ApiModelProperty(value = "是否默认开启图片模式", example = "true")
|
||||
private boolean defaultSwitchToImgMode;
|
||||
|
||||
@ApiModelProperty(value = "兼容 readme 模式", example = "true", notes = "兼容模式, 目录文档读取 readme.md 文件")
|
||||
private boolean compatibilityReadme;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package im.zhaojun.zfile.admin.model.request.gd;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(value="gd drive 列表请求类")
|
||||
public class GetGdDriveListRequest {
|
||||
|
||||
@NotBlank(message = "accessToken 不能为空")
|
||||
@ApiModelProperty(value = "accessToken", required = true, example = "v7LtfjIbnxLCTj0R3riwhyxcbv4KVH5HuPWHWrrewHMEwjJyUlYXV6D4m1MLJ2dP__GX_7CKCc-HudUetPXWS2wwbfkNs6ydLq3xrk1gHA7wcD_pmt6oNuRXw5mnFzfdLkH5wIG1suQp3p0eHJurzIaCgYKATASATASFQE65dr8hO725r41QtZc9RJVUg12cA0163")
|
||||
private String accessToken;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package im.zhaojun.zfile.admin.model.request.link;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
public class BatchDeleteRequest {
|
||||
|
||||
private List<Integer> ids;
|
||||
|
||||
}
|
||||
@@ -28,7 +28,6 @@ public class GetS3BucketListRequest {
|
||||
private String endPoint;
|
||||
|
||||
@ApiModelProperty(value = "Endpoint 接入点", required = true, example = "cn-beijing")
|
||||
// @NotBlank(message = "地域不能为空")
|
||||
private String region;
|
||||
|
||||
}
|
||||
@@ -31,5 +31,8 @@ public class UpdateSiteSettingRequest {
|
||||
|
||||
@ApiModelProperty(value = "备案号", example = "冀ICP备12345678号-1")
|
||||
private String icp;
|
||||
|
||||
@ApiModelProperty(value = "最大同时上传文件数", example = "5")
|
||||
private Integer maxFileUploads;
|
||||
|
||||
}
|
||||
@@ -53,4 +53,7 @@ public class UpdateViewSettingRequest {
|
||||
@ApiModelProperty(value = "默认文件点击习惯", example = "click")
|
||||
private FileClickModeEnum fileClickMode;
|
||||
|
||||
@ApiModelProperty(value = "onlyOffice 在线预览地址", example = "http://office.zfile.vip")
|
||||
private String onlyOfficeUrl;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package im.zhaojun.zfile.admin.model.result.gd;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* gd drive 基本信息结果类
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@ApiModel(value="gd drive 基本信息结果类")
|
||||
public class GdDriveInfoResult {
|
||||
|
||||
@ApiModelProperty(value = "drive id", example = "0AGrY0xF1D7PEUk9PVB")
|
||||
private String id;
|
||||
|
||||
@ApiModelProperty(value = "drive 名称", example = "zfile")
|
||||
private String name;
|
||||
|
||||
}
|
||||
@@ -31,7 +31,6 @@ public class StorageSourceAdminResult {
|
||||
@ApiModelProperty(value = "是否允许匿名进行文件操作", example = "true", notes = "是否允许匿名进行文件上传,编辑,删除等操作.")
|
||||
private Boolean enableFileAnnoOperator;
|
||||
|
||||
|
||||
@ApiModelProperty(value = "是否开启缓存", example = "true")
|
||||
private Boolean enableCache;
|
||||
|
||||
@@ -78,5 +77,9 @@ public class StorageSourceAdminResult {
|
||||
|
||||
@ApiModelProperty(value = "存储源刷新信息")
|
||||
private RefreshTokenCache.RefreshTokenInfo refreshTokenInfo;
|
||||
|
||||
|
||||
@ApiModelProperty(value = "兼容 readme 模式", example = "true", notes = "兼容模式, 目录文档读取 readme.md 文件")
|
||||
private Boolean compatibilityReadme;
|
||||
|
||||
}
|
||||
@@ -143,7 +143,7 @@ public class FilterConfigService extends ServiceImpl<FilterConfigMapper, FilterC
|
||||
|
||||
|
||||
/**
|
||||
* 指定存储源下的文件名称, 根据过滤表达式判断是否会显示, 如果符合任意一条表达式, 则返回 true, 反之则返回 false.
|
||||
* 指定存储源下的文件名称, 根据过滤表达式判断是否会显示, 如果符合任意一条表达式, 表示隐藏则返回 true, 反之表示不隐藏则返回 false.
|
||||
*
|
||||
* @param storageId
|
||||
* 存储源 ID
|
||||
@@ -151,7 +151,7 @@ public class FilterConfigService extends ServiceImpl<FilterConfigMapper, FilterC
|
||||
* @param fileName
|
||||
* 文件名
|
||||
*
|
||||
* @return 是否显示
|
||||
* @return 是否是隐藏文件夹
|
||||
*/
|
||||
public boolean filterResultIsHidden(Integer storageId, String fileName) {
|
||||
List<FilterConfig> filterConfigList = findByStorageId(storageId);
|
||||
|
||||
@@ -1,13 +1,32 @@
|
||||
package im.zhaojun.zfile.admin.service;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import im.zhaojun.zfile.admin.mapper.ShortLinkMapper;
|
||||
import im.zhaojun.zfile.admin.model.entity.DownloadLog;
|
||||
import im.zhaojun.zfile.admin.model.entity.ShortLink;
|
||||
import im.zhaojun.zfile.common.context.StorageSourceContext;
|
||||
import im.zhaojun.zfile.common.exception.InvalidStorageSourceException;
|
||||
import im.zhaojun.zfile.common.exception.file.operator.DownloadFileException;
|
||||
import im.zhaojun.zfile.common.util.HttpUtil;
|
||||
import im.zhaojun.zfile.common.util.RequestHolder;
|
||||
import im.zhaojun.zfile.home.model.dto.SystemConfigDTO;
|
||||
import im.zhaojun.zfile.home.service.base.AbstractBaseFileService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.util.EncodingUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -16,6 +35,7 @@ import java.util.Date;
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ShortLinkService extends ServiceImpl<ShortLinkMapper, ShortLink> implements IService<ShortLink> {
|
||||
|
||||
@Resource
|
||||
@@ -24,6 +44,15 @@ public class ShortLinkService extends ServiceImpl<ShortLinkMapper, ShortLink> im
|
||||
@Resource
|
||||
private StorageSourceService storageSourceService;
|
||||
|
||||
@Resource
|
||||
private StorageSourceContext storageSourceContext;
|
||||
|
||||
@Resource
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
@Resource
|
||||
private DownloadLogService downloadLogService;
|
||||
|
||||
/**
|
||||
* 根据短链接 key 查询短链接
|
||||
*
|
||||
@@ -113,4 +142,78 @@ public class ShortLinkService extends ServiceImpl<ShortLinkMapper, ShortLink> im
|
||||
return shortLink;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理指定存储源的下载请求
|
||||
*
|
||||
* @param storageKey
|
||||
* 存储源 key
|
||||
*
|
||||
* @param filePath
|
||||
* 文件路径
|
||||
*
|
||||
* @param shortKey
|
||||
* 短链接 key
|
||||
*
|
||||
* @throws IOException 可能抛出的 IO 异常
|
||||
*/
|
||||
public void handlerDownload(String storageKey, String filePath, String shortKey) throws IOException {
|
||||
HttpServletResponse response = RequestHolder.getResponse();
|
||||
|
||||
// 获取存储源 Service
|
||||
AbstractBaseFileService<?> fileService;
|
||||
try {
|
||||
fileService = storageSourceContext.getByKey(storageKey);
|
||||
} catch (InvalidStorageSourceException e) {
|
||||
log.error("无效的存储源,存储源 key {}, 文件路径 {}", storageKey, filePath);
|
||||
response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8");
|
||||
response.getWriter().write("无效的或初始化失败的存储源, 请联系管理员!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取文件下载链接
|
||||
String downloadUrl;
|
||||
try {
|
||||
downloadUrl = fileService.getDownloadUrl(filePath);
|
||||
} catch (DownloadFileException e) {
|
||||
log.error("获取文件下载链接异常 {}. 存储源 ID: {}, 文件路径: {}", e.getMessage(), e.getStorageId(), e.getPathAndName());
|
||||
response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8");
|
||||
response.getWriter().write("获取下载链接异常,请联系管理员!");
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断下载链接是否为空
|
||||
if (StrUtil.isEmpty(downloadUrl)) {
|
||||
log.error("获取到文件下载链接为空,存储源 key {}, 文件路径 {}", storageKey, filePath);
|
||||
response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8");
|
||||
response.getWriter().write("获取下载链接异常,请联系管理员![2]");
|
||||
}
|
||||
|
||||
// 记录下载日志.
|
||||
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
|
||||
Boolean recordDownloadLog = systemConfig.getRecordDownloadLog();
|
||||
if (BooleanUtil.isTrue(recordDownloadLog)) {
|
||||
DownloadLog downloadLog = new DownloadLog(filePath, storageKey, shortKey);
|
||||
downloadLogService.save(downloadLog);
|
||||
}
|
||||
|
||||
// 判断下载链接是否为 m3u8 格式, 如果是则返回 m3u8 内容.
|
||||
if (StrUtil.equalsIgnoreCase(FileUtil.extName(filePath), "m3u8")) {
|
||||
String textContent = HttpUtil.getTextContent(downloadUrl);
|
||||
response.setContentType("application/vnd.apple.mpegurl;charset=utf-8");
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
byte[] textContentBytes = EncodingUtils.getBytes(textContent, CharsetUtil.CHARSET_UTF_8.displayName());
|
||||
IoUtil.write(outputStream, true, textContentBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// 禁止直链被浏览器 302 缓存.
|
||||
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate, private");
|
||||
response.setHeader(HttpHeaders.PRAGMA, "no-cache");
|
||||
response.setHeader(HttpHeaders.EXPIRES, "0");
|
||||
|
||||
// 重定向到下载链接.
|
||||
response.sendRedirect(downloadUrl);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import im.zhaojun.zfile.admin.mapper.StorageSourceConfigMapper;
|
||||
import im.zhaojun.zfile.admin.model.entity.StorageSourceConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
@@ -73,6 +74,7 @@ public class StorageSourceConfigService extends ServiceImpl<StorageSourceConfigM
|
||||
* @param storageSourceConfigList
|
||||
* 存储源设置列表
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateStorageConfig(List<StorageSourceConfig> storageSourceConfigList) {
|
||||
super.updateBatchById(storageSourceConfigList);
|
||||
if (CollUtil.isNotEmpty(storageSourceConfigList)) {
|
||||
@@ -89,6 +91,7 @@ public class StorageSourceConfigService extends ServiceImpl<StorageSourceConfigM
|
||||
* @param id
|
||||
* 存储源 ID
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteByStorageId(Integer id) {
|
||||
storageSourceConfigMapper.deleteByStorageId(id);
|
||||
sourceConfigConfigMapCache.remove(id);
|
||||
|
||||
@@ -6,7 +6,7 @@ import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import im.zhaojun.zfile.admin.annoation.model.StorageSourceParamDef;
|
||||
import im.zhaojun.zfile.admin.annotation.model.StorageSourceParamDef;
|
||||
import im.zhaojun.zfile.admin.mapper.StorageSourceMapper;
|
||||
import im.zhaojun.zfile.admin.model.entity.StorageSource;
|
||||
import im.zhaojun.zfile.admin.model.entity.StorageSourceConfig;
|
||||
@@ -495,6 +495,7 @@ public class StorageSourceService extends ServiceImpl<StorageSourceMapper, Stora
|
||||
if (entity != null) {
|
||||
Integer id = entity.getId();
|
||||
storageIdMapCache.put(id, entity);
|
||||
storageKeyMapCache.put(entity.getKey(), entity);
|
||||
}
|
||||
return super.updateById(entity);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package im.zhaojun.zfile.common.cache;
|
||||
|
||||
import cn.hutool.cache.impl.CacheObj;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import im.zhaojun.zfile.admin.model.dto.StorageSourceCacheKey;
|
||||
import im.zhaojun.zfile.admin.model.entity.StorageSource;
|
||||
import im.zhaojun.zfile.admin.service.StorageSourceService;
|
||||
@@ -13,7 +12,6 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -113,45 +111,6 @@ public class ZFileCache {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定存储源中已缓存文件夹数量
|
||||
*
|
||||
* @param storageId
|
||||
* 存储源 ID
|
||||
*
|
||||
* @return 已缓存文件夹数量
|
||||
*/
|
||||
public int cacheCount(Integer storageId) {
|
||||
return getCacheByStorageId(storageId).size();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 指定存储源, 根据文件及文件名查找相关的文件
|
||||
*
|
||||
* @param storageId
|
||||
* 存储源 ID
|
||||
*
|
||||
* @param key
|
||||
* 搜索键, 可匹配文件夹名称和文件名称.
|
||||
*
|
||||
* @return 搜索结果, 包含文件夹和文件.
|
||||
*/
|
||||
public List<FileItemResult> find(Integer storageId, String key) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
|
||||
private boolean testMatch(String searchKey, boolean ignoreCase, FileItemResult fileItemResult) {
|
||||
// 根据是否需要忽略大小写来匹配文件(夹)名
|
||||
if (ignoreCase) {
|
||||
return StrUtil.containsIgnoreCase(fileItemResult.getName(), searchKey);
|
||||
} else {
|
||||
return StrUtil.contains(fileItemResult.getName(), searchKey);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取所有缓存 key (文件夹名称)
|
||||
|
||||
@@ -1 +1 @@
|
||||
package im.zhaojun.zfile.common.config;
|
||||
package im.zhaojun.zfile.common.config;
|
||||
@@ -1,11 +1,15 @@
|
||||
package im.zhaojun.zfile.common.config;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.SQLException;
|
||||
@@ -20,8 +24,27 @@ public class MyBatisPlusConfig {
|
||||
|
||||
@Resource
|
||||
private DataSource dataSource;
|
||||
|
||||
|
||||
|
||||
@Value("${spring.datasource.driver-class-name}")
|
||||
private String datasourceDriveClassName;
|
||||
|
||||
@Value("${spring.datasource.url}")
|
||||
private String datasourceUrl;
|
||||
|
||||
/**
|
||||
* 如果是 sqlite 数据库,自动创建数据库文件所在目录
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
if (StrUtil.equals(datasourceDriveClassName, "org.sqlite.JDBC")) {
|
||||
String path = datasourceUrl.replace("jdbc:sqlite:", "");
|
||||
String folderPath = FileUtil.getParent(path, 1);
|
||||
if (!FileUtil.exist(folderPath)) {
|
||||
FileUtil.mkdir(folderPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mybatis plus 分页插件配置
|
||||
*/
|
||||
|
||||
@@ -4,8 +4,8 @@ import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annoation.model.StorageSourceParamDef;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.model.StorageSourceParamDef;
|
||||
import im.zhaojun.zfile.admin.model.entity.StorageSource;
|
||||
import im.zhaojun.zfile.admin.model.entity.StorageSourceConfig;
|
||||
import im.zhaojun.zfile.admin.model.param.IStorageParam;
|
||||
@@ -37,6 +37,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
/**
|
||||
* 每个存储源对应一个 Service, 其中初始化好了与对象存储的配置信息.
|
||||
* 此存储源上下文环境用户缓存每个 Service, 避免重复初始化.
|
||||
*
|
||||
* 依赖 {@link FlywayDbInitializer} 初始化数据库后执行.
|
||||
*
|
||||
* @author zhaojun
|
||||
@@ -203,7 +204,11 @@ public class StorageSourceContext implements ApplicationContextAware {
|
||||
* @return 存储源对应的 Service
|
||||
*/
|
||||
public AbstractBaseFileService<?> getByKey(String key) {
|
||||
return get(storageSourceService.findIdByKey(key));
|
||||
Integer storageId = storageSourceService.findIdByKey(key);
|
||||
if (storageId == null) {
|
||||
return null;
|
||||
}
|
||||
return get(storageId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package im.zhaojun.zfile.common.controller.callback;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import im.zhaojun.zfile.admin.model.dto.OAuth2Token;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Api(tags = "Google Drive 认证回调模块")
|
||||
@Controller
|
||||
@Slf4j
|
||||
@RequestMapping(value = {"/gd"})
|
||||
public class GdCallbackController {
|
||||
|
||||
@Value("${zfile.gd.clientId}")
|
||||
private String clientId;
|
||||
|
||||
@Value("${zfile.gd.redirectUri}")
|
||||
private String redirectUri;
|
||||
|
||||
@Value("${zfile.gd.clientSecret}")
|
||||
private String clientSecret;
|
||||
|
||||
@Value("${zfile.gd.scope}")
|
||||
private String scope;
|
||||
|
||||
@GetMapping("/authorize")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@ApiOperation(value = "生成 OAuth2 登陆 URL", notes = "生成 OneDrive OAuth2 登陆 URL,用户国际版,家庭版等非世纪互联运营的 OneDrive.")
|
||||
public String authorize(String clientId, String clientSecret, String redirectUri) {
|
||||
log.info("gd 生成授权链接参数信息: clientId: {}, clientSecret: {}, redirectUri: {}", clientId, clientSecret, redirectUri);
|
||||
|
||||
if (StrUtil.isAllEmpty(clientId, clientSecret, redirectUri)) {
|
||||
clientId = this.clientId;
|
||||
redirectUri = this.redirectUri;
|
||||
clientSecret = this.clientSecret;
|
||||
}
|
||||
|
||||
String stateStr = "&state=" + Base64.encodeUrlSafe(StrUtil.join("::", clientId, clientSecret, redirectUri));
|
||||
|
||||
String authorizeUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=" + clientId
|
||||
+ "&response_type=code&redirect_uri=" + redirectUri
|
||||
+ "&scope=" + this.scope
|
||||
+ "&access_type=offline"
|
||||
+ stateStr;
|
||||
|
||||
log.info("gd 生成授权链接结果: {}", authorizeUrl);
|
||||
|
||||
return "redirect:" + authorizeUrl;
|
||||
}
|
||||
|
||||
@GetMapping("/callback")
|
||||
public String gdCallback(String code, String state, Model model) {
|
||||
log.info("gd 授权回调参数信息: code: {}, state: {}", code, state);
|
||||
|
||||
String clientId, clientSecret, redirectUri;
|
||||
|
||||
if (StrUtil.isEmpty(state)) {
|
||||
clientId = this.clientId;
|
||||
clientSecret = this.clientSecret;
|
||||
redirectUri = this.redirectUri;
|
||||
} else {
|
||||
String stateDecode = Base64.decodeStr(state);
|
||||
String[] stateArr = stateDecode.split("::");
|
||||
clientId = stateArr[0];
|
||||
clientSecret = stateArr[1];
|
||||
redirectUri = stateArr[2];
|
||||
}
|
||||
|
||||
final String uri = "https://accounts.google.com/o/oauth2/token";
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||
String clientCredentials = Base64.encodeUrlSafe(clientId + ":" + clientSecret);
|
||||
headers.add("Authorization", "Basic " + clientCredentials);
|
||||
MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
|
||||
requestBody.add("code", code);
|
||||
requestBody.add("grant_type", "authorization_code");
|
||||
requestBody.add("redirect_uri", redirectUri);
|
||||
requestBody.add("scope", scope);
|
||||
|
||||
HttpEntity<MultiValueMap<String, String>> formEntity = new HttpEntity<>(requestBody, headers);
|
||||
|
||||
NoRedirectClientHttpRequestFactory noRedirectClientHttpRequestFactory = new NoRedirectClientHttpRequestFactory();
|
||||
|
||||
ResponseEntity<String> response = new RestTemplate(noRedirectClientHttpRequestFactory).exchange(uri, HttpMethod.POST, formEntity, String.class);
|
||||
|
||||
String body = response.getBody();
|
||||
log.info("{} 根据授权回调 code 获取令牌结果:body: {}", this, body);
|
||||
|
||||
OAuth2Token oAuth2Token ;
|
||||
if (response.getStatusCode() != HttpStatus.OK) {
|
||||
oAuth2Token = OAuth2Token.fail(clientId, clientSecret, redirectUri, body);
|
||||
} else {
|
||||
JSONObject jsonBody = JSONObject.parseObject(body);
|
||||
String accessToken = jsonBody.getString("access_token");
|
||||
String refreshToken = jsonBody.getString("refresh_token");
|
||||
oAuth2Token =
|
||||
OAuth2Token.success(clientId, clientSecret, redirectUri, accessToken, refreshToken, body);
|
||||
}
|
||||
|
||||
model.addAttribute("oauth2Token", oAuth2Token);
|
||||
model.addAttribute("type", "Google Drive");
|
||||
return "callback";
|
||||
}
|
||||
|
||||
static class NoRedirectClientHttpRequestFactory extends
|
||||
SimpleClientHttpRequestFactory {
|
||||
|
||||
@Override
|
||||
protected void prepareConnection(HttpURLConnection connection,
|
||||
String httpMethod) throws IOException {
|
||||
super.prepareConnection(connection, httpMethod);
|
||||
connection.setInstanceFollowRedirects(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
package im.zhaojun.zfile.common.controller.callback;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import im.zhaojun.zfile.admin.model.dto.OneDriveToken;
|
||||
import im.zhaojun.zfile.admin.model.dto.OAuth2Token;
|
||||
import im.zhaojun.zfile.home.service.impl.OneDriveChinaServiceImpl;
|
||||
import im.zhaojun.zfile.home.service.impl.OneDriveServiceImpl;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
@@ -20,55 +23,124 @@ import javax.annotation.Resource;
|
||||
*/
|
||||
@Api(tags = "OneDrive 认证回调模块")
|
||||
@Controller
|
||||
@Slf4j
|
||||
@RequestMapping(value = {"/onedrive", "/onedirve"})
|
||||
public class OneDriveCallbackController {
|
||||
|
||||
|
||||
@Resource
|
||||
private OneDriveServiceImpl oneDriveServiceImpl;
|
||||
|
||||
|
||||
@Resource
|
||||
private OneDriveChinaServiceImpl oneDriveChinaServiceImpl;
|
||||
|
||||
|
||||
|
||||
|
||||
@GetMapping("/authorize")
|
||||
@ApiOperationSupport(order = 1)
|
||||
@ApiOperation(value = "生成 OAuth2 登陆 URL", notes = "生成 OneDrive OAuth2 登陆 URL,用户国际版,家庭版等非世纪互联运营的 OneDrive.")
|
||||
public String authorize() {
|
||||
return "redirect:https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + oneDriveServiceImpl.getClientId() +
|
||||
"&response_type=code&redirect_uri=" + oneDriveServiceImpl.getRedirectUri() +
|
||||
"&scope=" + oneDriveServiceImpl.getScope();
|
||||
public String authorize(String clientId, String clientSecret, String redirectUri) {
|
||||
log.info("onedrive 国际版生成授权链接参数信息: clientId: {}, clientSecret: {}, redirectUri: {}", clientId, clientSecret, redirectUri);
|
||||
|
||||
if (StrUtil.isAllEmpty(clientId, clientSecret, redirectUri)) {
|
||||
clientId = oneDriveServiceImpl.getClientId();
|
||||
redirectUri = oneDriveServiceImpl.getRedirectUri();
|
||||
clientSecret = oneDriveServiceImpl.getClientSecret();
|
||||
}
|
||||
|
||||
|
||||
String stateStr = "&state=" + Base64.encodeUrlSafe(StrUtil.join("::", clientId, clientSecret, redirectUri));
|
||||
|
||||
String authorizeUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=" + clientId
|
||||
+ "&response_type=code&redirect_uri=" + redirectUri
|
||||
+ "&scope=" + oneDriveServiceImpl.getScope()
|
||||
+ stateStr;
|
||||
|
||||
log.info("onedrive 国际版生成授权链接结果: {}", authorizeUrl);
|
||||
|
||||
return "redirect:" + authorizeUrl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@GetMapping("/callback")
|
||||
@ApiOperationSupport(order = 2)
|
||||
@ApiOperation(value = "OAuth2 回调地址", notes = "根据 OAuth2 协议,登录成功后,会返回给网站一个 code,用此 code 去换取 accessToken 和 refreshToken.(oneDrive 会回调此接口)")
|
||||
public String oneDriveCallback(String code, Model model) {
|
||||
OneDriveToken oneDriveToken = oneDriveServiceImpl.getToken(code);
|
||||
model.addAttribute("accessToken", oneDriveToken.getAccessToken());
|
||||
model.addAttribute("refreshToken", oneDriveToken.getRefreshToken());
|
||||
public String oneDriveCallback(String code, String state, Model model) {
|
||||
log.info("onedrive 国际版授权回调参数信息: code: {}, state: {}", code, state);
|
||||
|
||||
String clientId, clientSecret, redirectUri;
|
||||
|
||||
if (StrUtil.isEmpty(state)) {
|
||||
clientId = oneDriveServiceImpl.getClientId();
|
||||
clientSecret = oneDriveServiceImpl.getClientSecret();
|
||||
redirectUri = oneDriveServiceImpl.getRedirectUri();
|
||||
} else {
|
||||
String stateDecode = Base64.decodeStr(state);
|
||||
String[] stateArr = stateDecode.split("::");
|
||||
clientId = stateArr[0];
|
||||
clientSecret = stateArr[1];
|
||||
redirectUri = stateArr[2];
|
||||
}
|
||||
|
||||
OAuth2Token OAuth2Token = oneDriveServiceImpl.getToken(code, clientId, clientSecret, redirectUri);
|
||||
log.info("onedrive 国际版授权回调获取令牌结果: {}", OAuth2Token);
|
||||
|
||||
model.addAttribute("oauth2Token", OAuth2Token);
|
||||
model.addAttribute("type", "OneDrive 国际版");
|
||||
return "callback";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@GetMapping("/china-authorize")
|
||||
@ApiOperationSupport(order = 3)
|
||||
@ApiOperation(value = "生成 OAuth2 登陆 URL(世纪互联)", notes = "生成 OneDrive OAuth2 登陆 URL,用于世纪互联版本.")
|
||||
public String authorizeChina() {
|
||||
return "redirect:https://login.chinacloudapi.cn/common/oauth2/v2.0/authorize?client_id=" + oneDriveChinaServiceImpl.getClientId() +
|
||||
"&response_type=code&redirect_uri=" + oneDriveChinaServiceImpl.getRedirectUri() +
|
||||
"&scope=" + oneDriveChinaServiceImpl.getScope();
|
||||
public String authorizeChina(String clientId, String clientSecret, String redirectUri) {
|
||||
log.info("onedrive 世纪互联版生成授权链接参数信息: clientId: {}, clientSecret: {}, redirectUri: {}", clientId, clientSecret, redirectUri);
|
||||
|
||||
if (StrUtil.isAllEmpty(clientId, clientSecret, redirectUri)) {
|
||||
clientId = oneDriveChinaServiceImpl.getClientId();
|
||||
redirectUri = oneDriveChinaServiceImpl.getRedirectUri();
|
||||
clientSecret = oneDriveChinaServiceImpl.getClientSecret();
|
||||
}
|
||||
|
||||
String stateStr = "&state=" + Base64.encodeUrlSafe(StrUtil.join("::", clientId, clientSecret, redirectUri));
|
||||
|
||||
|
||||
String authorizeUrl = "https://login.chinacloudapi.cn/common/oauth2/v2.0/authorize?client_id=" + clientId
|
||||
+ "&response_type=code&redirect_uri=" + redirectUri
|
||||
+ "&scope=" + oneDriveChinaServiceImpl.getScope()
|
||||
+ stateStr;
|
||||
|
||||
log.info("onedrive 世纪互联版生成授权链接结果: {}", authorizeUrl);
|
||||
|
||||
return "redirect:" + authorizeUrl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@GetMapping("/china-callback")
|
||||
@ApiOperationSupport(order = 4)
|
||||
@ApiOperation(value = "OAuth2 回调地址(世纪互联)", notes = "根据 OAuth2 协议,登录成功后,会返回给网站一个 code,用此 code 去换取 accessToken 和 refreshToken.(oneDrive 会回调此接口)")
|
||||
public String oneDriveChinaCallback(String code, Model model) {
|
||||
OneDriveToken oneDriveToken = oneDriveChinaServiceImpl.getToken(code);
|
||||
model.addAttribute("accessToken", oneDriveToken.getAccessToken());
|
||||
model.addAttribute("refreshToken", oneDriveToken.getRefreshToken());
|
||||
public String oneDriveChinaCallback(String code, String state, Model model) {
|
||||
log.info("onedrive 世纪互联版授权回调参数信息: code: {}, state: {}", code, state);
|
||||
|
||||
String clientId, clientSecret, redirectUri;
|
||||
|
||||
if (StrUtil.isEmpty(state)) {
|
||||
clientId = oneDriveChinaServiceImpl.getClientId();
|
||||
clientSecret = oneDriveChinaServiceImpl.getClientSecret();
|
||||
redirectUri = oneDriveChinaServiceImpl.getRedirectUri();
|
||||
} else {
|
||||
String stateDecode = Base64.decodeStr(state);
|
||||
String[] stateArr = stateDecode.split("::");
|
||||
clientId = stateArr[0];
|
||||
clientSecret = stateArr[1];
|
||||
redirectUri = stateArr[2];
|
||||
}
|
||||
|
||||
OAuth2Token OAuth2Token = oneDriveChinaServiceImpl.getToken(code, clientId, clientSecret, redirectUri);
|
||||
log.info("onedrive 世纪互联版授权回调获取令牌结果: {}", OAuth2Token);
|
||||
|
||||
model.addAttribute("oauth2Token", OAuth2Token);
|
||||
model.addAttribute("type", "OneDrive 世纪互联");
|
||||
return "callback";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package im.zhaojun.zfile.common.controller.gd;
|
||||
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import im.zhaojun.zfile.admin.model.request.gd.GetGdDriveListRequest;
|
||||
import im.zhaojun.zfile.admin.model.result.gd.GdDriveInfoResult;
|
||||
import im.zhaojun.zfile.common.util.AjaxJson;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Api(tags = "gd 工具辅助模块")
|
||||
@Controller
|
||||
@RequestMapping("/gd")
|
||||
public class GDHelperController {
|
||||
|
||||
@PostMapping("/drives")
|
||||
@ResponseBody
|
||||
@ApiOperationSupport(order = 1)
|
||||
@ApiOperation(value = "获取 gd drives 列表")
|
||||
public AjaxJson<List<GdDriveInfoResult>> getDrives(@Valid @RequestBody GetGdDriveListRequest gdDriveListRequest) {
|
||||
List<GdDriveInfoResult> bucketNameList = new ArrayList<>();
|
||||
String accessToken = gdDriveListRequest.getAccessToken();
|
||||
|
||||
HttpRequest httpRequest = HttpUtil.createGet("https://www.googleapis.com/drive/v3/drives");
|
||||
httpRequest.header("Authorization", "Bearer " + accessToken);
|
||||
|
||||
HttpResponse httpResponse = httpRequest.execute();
|
||||
|
||||
String body = httpResponse.body();
|
||||
JSONObject jsonObject = JSON.parseObject(body);
|
||||
JSONArray drives = jsonObject.getJSONArray("drives");
|
||||
|
||||
for (int i = 0; i < drives.size(); i++) {
|
||||
JSONObject drive = drives.getJSONObject(i);
|
||||
String id = drive.getString("id");
|
||||
String name = drive.getString("name");
|
||||
bucketNameList.add(new GdDriveInfoResult(id, name));
|
||||
}
|
||||
|
||||
return AjaxJson.getSuccessData(bucketNameList);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -29,4 +29,9 @@ public class StorageSourceException extends RuntimeException {
|
||||
this.storageId = storageId;
|
||||
}
|
||||
|
||||
public StorageSourceException(Integer storageId, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.storageId = storageId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public class AccessTokenRefreshSchedule {
|
||||
private StorageSourceContext storageSourceContext;
|
||||
|
||||
/**
|
||||
* 项目启动 30 秒后, 每 15 分钟执行一次刷新 OneDrive Token 的定时任务.
|
||||
* 项目启动 30 秒后, 每 10 分钟执行一次刷新 OneDrive Token 的定时任务.
|
||||
*/
|
||||
@Scheduled(fixedRate = 1000 * 60 * 10, initialDelay = 1000 * 10)
|
||||
public void autoRefreshAccessToken() {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package im.zhaojun.zfile.common.util;
|
||||
|
||||
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
@@ -21,7 +20,7 @@ public class ClassUtils {
|
||||
*/
|
||||
public static Class<?> getClassFirstGenericsParam(Class<?> clazz) {
|
||||
Type genericSuperclass = clazz.getGenericSuperclass();
|
||||
Type actualTypeArgument = ((ParameterizedTypeImpl) genericSuperclass).getActualTypeArguments()[0];
|
||||
Type actualTypeArgument = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
|
||||
return (Class<?>) actualTypeArgument;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
|
||||
import cn.hutool.crypto.symmetric.SymmetricCrypto;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import im.zhaojun.zfile.admin.service.SystemConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -17,6 +18,7 @@ import java.util.List;
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Slf4j
|
||||
public class ProxyDownloadUrlUtils {
|
||||
|
||||
private static SystemConfigService systemConfigService;
|
||||
@@ -70,13 +72,17 @@ public class ProxyDownloadUrlUtils {
|
||||
String storageId = split.get(0);
|
||||
String pathAndName = split.get(1);
|
||||
String expiredSecond = split.get(2);
|
||||
|
||||
// 校验存储源 ID 和文件路径及是否过期.
|
||||
if (StrUtil.equals(storageId, Convert.toStr(expectedStorageId))
|
||||
&& StrUtil.equals(StringUtils.concat(pathAndName), StringUtils.concat(expectedPathAndName))
|
||||
&& new Date().getTime() < Convert.toLong(expiredSecond)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
log.debug("校验链接已过期或不匹配, storageId={}, pathAndName={}, expiredSecond={}, now:={}", storageId, pathAndName, expiredSecond, new Date().getTime());
|
||||
} catch (Exception e) {
|
||||
log.error("校验链接是否过期异常", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.MediaTypeFactory;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
@@ -14,7 +13,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
@@ -58,11 +56,8 @@ public class RequestHolder {
|
||||
HttpServletResponse response = RequestHolder.getResponse();
|
||||
String fileName = FileUtil.getName(path);
|
||||
|
||||
Optional<MediaType> mediaTypeOptional = MediaTypeFactory.getMediaType(path);
|
||||
MediaType mediaType = mediaTypeOptional.orElse(MediaType.APPLICATION_OCTET_STREAM);
|
||||
|
||||
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + StringUtils.encodeAllIgnoreSlashes(fileName));
|
||||
response.setContentType(mediaType.getType());
|
||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM.getType());
|
||||
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ public class StringUtils {
|
||||
*
|
||||
* @return 生成结果
|
||||
*/
|
||||
public static String generatorLink(String storageKey, String fullPath) {
|
||||
public static String generatorPathLink(String storageKey, String fullPath) {
|
||||
SystemConfigService systemConfigService = SpringUtil.getBean(SystemConfigService.class);
|
||||
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
|
||||
String domain = systemConfig.getDomain();
|
||||
|
||||
@@ -79,19 +79,6 @@ public class FileOperatorCheckAspect {
|
||||
return check(point, FileOperatorTypeEnum.RENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索功能权限校验
|
||||
*
|
||||
* @param point
|
||||
* 连接点
|
||||
*
|
||||
* @return 方法运行结果
|
||||
*/
|
||||
@Around("execution(public * im.zhaojun.zfile.home.service.base.AbstractBaseFileService.search(..))")
|
||||
public Object searchAround(ProceedingJoinPoint point) throws Throwable {
|
||||
return check(point, FileOperatorTypeEnum.SEARCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验是否有此文件操作的权限
|
||||
*
|
||||
|
||||
@@ -2,7 +2,7 @@ package im.zhaojun.zfile.home.aspect;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import im.zhaojun.zfile.admin.annoation.RefererCheck;
|
||||
import im.zhaojun.zfile.admin.annotation.RefererCheck;
|
||||
import im.zhaojun.zfile.admin.model.enums.RefererTypeEnum;
|
||||
import im.zhaojun.zfile.admin.service.SystemConfigService;
|
||||
import im.zhaojun.zfile.home.model.dto.SystemConfigDTO;
|
||||
@@ -22,7 +22,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* 校验 referer 防盗链.
|
||||
*
|
||||
* <p>
|
||||
* 校验所有标注了 {@link RefererCheck} 的注解
|
||||
*
|
||||
* @author zhaojun
|
||||
@@ -51,7 +51,7 @@ public class RefererCheckAspect {
|
||||
*
|
||||
* @return 方法运行结果
|
||||
*/
|
||||
@Around(value = "@annotation(im.zhaojun.zfile.admin.annoation.RefererCheck)")
|
||||
@Around(value = "@annotation(im.zhaojun.zfile.admin.annotation.RefererCheck)")
|
||||
public Object around(ProceedingJoinPoint point) throws Throwable {
|
||||
// 获取配置的 referer 类型
|
||||
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package im.zhaojun.zfile.home.chain.command;
|
||||
|
||||
import im.zhaojun.zfile.home.chain.FileContext;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import im.zhaojun.zfile.admin.service.FilterConfigService;
|
||||
import im.zhaojun.zfile.common.util.StringUtils;
|
||||
import im.zhaojun.zfile.home.chain.FileContext;
|
||||
import im.zhaojun.zfile.home.model.result.FileItemResult;
|
||||
import org.apache.commons.chain.Command;
|
||||
import org.apache.commons.chain.Context;
|
||||
@@ -38,8 +39,7 @@ public class FileHiddenCommand implements Command {
|
||||
FileContext fileContext = (FileContext) context;
|
||||
Integer storageId = fileContext.getStorageId();
|
||||
List<FileItemResult> fileItemList = fileContext.getFileItemList();
|
||||
|
||||
if (fileItemList == null) {
|
||||
if (CollUtil.isEmpty(fileItemList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,10 @@ public class FileSortCommand implements Command {
|
||||
List<FileItemResult> fileItemList = fileContext.getFileItemList();
|
||||
FileListRequest fileListRequest = fileContext.getFileListRequest();
|
||||
|
||||
if (fileListRequest.getOrderBy() == null || fileListRequest.getOrderDirection() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建副本, 防止排序和过滤对原数据产生影响
|
||||
List<FileItemResult> copyList = new ArrayList<>(fileItemList);
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
package im.zhaojun.zfile.home.controller;
|
||||
package im.zhaojun.zfile.home.controller;
|
||||
@@ -1,10 +1,11 @@
|
||||
package im.zhaojun.zfile.home.controller;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiSort;
|
||||
import im.zhaojun.zfile.common.context.StorageSourceContext;
|
||||
import im.zhaojun.zfile.home.model.request.operator.DeleteFileRequest;
|
||||
import im.zhaojun.zfile.home.model.request.operator.DeleteFolderRequest;
|
||||
import im.zhaojun.zfile.home.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.zfile.home.model.request.operator.BatchDeleteRequest;
|
||||
import im.zhaojun.zfile.home.model.request.operator.NewFolderRequest;
|
||||
import im.zhaojun.zfile.home.model.request.operator.RenameFileRequest;
|
||||
import im.zhaojun.zfile.home.model.request.operator.RenameFolderRequest;
|
||||
@@ -21,6 +22,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 文件操作相关接口, 如新建文件夹, 上传文件, 删除文件, 移动文件等.
|
||||
@@ -52,29 +54,33 @@ public class FileOperatorController {
|
||||
|
||||
|
||||
@ApiOperationSupport(order = 2)
|
||||
@ApiOperation(value = "删除文件")
|
||||
@PostMapping("/delete/file")
|
||||
public AjaxJson<?> deleteFile(@Valid @RequestBody DeleteFileRequest deleteFileRequest) {
|
||||
AbstractBaseFileService<?> fileService = storageSourceContext.getByKey(deleteFileRequest.getStorageKey());
|
||||
boolean flag = fileService.deleteFile(deleteFileRequest.getPath(), deleteFileRequest.getName());
|
||||
if (flag) {
|
||||
return AjaxJson.getSuccess("删除成功");
|
||||
} else {
|
||||
return AjaxJson.getError("删除失败");
|
||||
@ApiOperation(value = "批量删除文件/文件夹")
|
||||
@PostMapping("/delete/batch")
|
||||
public AjaxJson<?> deleteFile(@Valid @RequestBody BatchDeleteRequest batchDeleteRequest) {
|
||||
AbstractBaseFileService<?> fileService = storageSourceContext.getByKey(batchDeleteRequest.getStorageKey());
|
||||
List<BatchDeleteRequest.DeleteItem> deleteItems = batchDeleteRequest.getDeleteItems();
|
||||
|
||||
int deleteSuccessCount = 0, deleteFailCount = 0, totalCount = CollUtil.size(deleteItems);
|
||||
|
||||
for (BatchDeleteRequest.DeleteItem deleteItem : deleteItems) {
|
||||
boolean flag = false;
|
||||
if (deleteItem.getType() == FileTypeEnum.FILE) {
|
||||
flag = fileService.deleteFile(deleteItem.getPath(), deleteItem.getName());
|
||||
} else if (deleteItem.getType() == FileTypeEnum.FOLDER) {
|
||||
flag = fileService.deleteFile(deleteItem.getPath(), deleteItem.getName());
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
deleteSuccessCount++;
|
||||
} else {
|
||||
deleteFailCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ApiOperationSupport(order = 3)
|
||||
@ApiOperation(value = "删除文件夹")
|
||||
@PostMapping("/delete/folder")
|
||||
public AjaxJson<?> deleteFolder(@Valid @RequestBody DeleteFolderRequest deleteFolderRequest) {
|
||||
AbstractBaseFileService<?> fileService = storageSourceContext.getByKey(deleteFolderRequest.getStorageKey());
|
||||
boolean flag = fileService.deleteFolder(deleteFolderRequest.getPath(), deleteFolderRequest.getName());
|
||||
if (flag) {
|
||||
return AjaxJson.getSuccess("删除成功");
|
||||
|
||||
if (totalCount > 1) {
|
||||
return AjaxJson.getSuccess("批量删除 " + totalCount + " 个, 删除成功 " + deleteSuccessCount + " 个, 失败 " + deleteFailCount + " 个.");
|
||||
} else {
|
||||
return AjaxJson.getError("删除失败");
|
||||
return totalCount == deleteSuccessCount ? AjaxJson.getSuccess("删除成功") : AjaxJson.getError("删除失败");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.github.xiaoymin.knife4j.annotations.ApiSort;
|
||||
import com.github.xiaoymin.knife4j.annotations.DynamicParameter;
|
||||
import com.github.xiaoymin.knife4j.annotations.DynamicResponseParameters;
|
||||
import im.zhaojun.zfile.admin.model.entity.ShortLink;
|
||||
import im.zhaojun.zfile.admin.model.entity.StorageSource;
|
||||
import im.zhaojun.zfile.admin.service.ShortLinkService;
|
||||
import im.zhaojun.zfile.admin.service.StorageSourceService;
|
||||
import im.zhaojun.zfile.admin.service.SystemConfigService;
|
||||
@@ -13,18 +14,27 @@ import im.zhaojun.zfile.common.exception.IllegalDownloadLinkException;
|
||||
import im.zhaojun.zfile.common.util.AjaxJson;
|
||||
import im.zhaojun.zfile.common.util.StringUtils;
|
||||
import im.zhaojun.zfile.home.model.dto.SystemConfigDTO;
|
||||
import im.zhaojun.zfile.home.model.request.BatchGenerateLinkRequest;
|
||||
import im.zhaojun.zfile.home.model.result.BatchGenerateLinkResponse;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiImplicitParams;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 短链接口
|
||||
@@ -34,6 +44,7 @@ import javax.annotation.Resource;
|
||||
@Api(tags = "直短链模块")
|
||||
@ApiSort(5)
|
||||
@Controller
|
||||
@Slf4j
|
||||
public class ShortLinkController {
|
||||
|
||||
@Resource
|
||||
@@ -45,8 +56,7 @@ public class ShortLinkController {
|
||||
@Resource
|
||||
private StorageSourceService storageSourceService;
|
||||
|
||||
|
||||
@GetMapping("/api/short-link")
|
||||
@PostMapping("/api/short-link/batch/generate")
|
||||
@ResponseBody
|
||||
@ApiOperationSupport(order = 1)
|
||||
@ApiOperation(value = "生成短链", notes = "对指定存储源的某文件路径生成短链")
|
||||
@@ -59,7 +69,9 @@ public class ShortLinkController {
|
||||
@DynamicParameter(name = "code", value = "业务状态码,0 为正常,其他值均为异常,异常情况下见响应消息", example = "0"),
|
||||
@DynamicParameter(name = "data", value = "短链地址", example = "https://zfile.vip/s/btz4tu")
|
||||
})
|
||||
public AjaxJson<String> generatorShortLink(String storageKey, String path) {
|
||||
public AjaxJson<List<BatchGenerateLinkResponse>> generatorShortLink(@RequestBody @Valid BatchGenerateLinkRequest batchGenerateLinkRequest) {
|
||||
List<BatchGenerateLinkResponse> result = new ArrayList<>();
|
||||
|
||||
// 获取站点域名
|
||||
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
|
||||
|
||||
@@ -67,13 +79,16 @@ public class ShortLinkController {
|
||||
Boolean showShortLink = systemConfig.getShowShortLink();
|
||||
Boolean showPathLink = systemConfig.getShowPathLink();
|
||||
if ( BooleanUtil.isFalse(showShortLink) && BooleanUtil.isFalse(showPathLink)) {
|
||||
throw new IllegalDownloadLinkException("当前系统不允许使用短链和短链.");
|
||||
throw new IllegalDownloadLinkException("当前系统不允许使用直链和短链.");
|
||||
}
|
||||
|
||||
String domain = systemConfig.getDomain();
|
||||
|
||||
// 拼接直链地址.
|
||||
String storageKey = batchGenerateLinkRequest.getStorageKey();
|
||||
for (String path : batchGenerateLinkRequest.getPaths()) {
|
||||
// 拼接全路径地址.
|
||||
String fullPath = StringUtils.concat(path);
|
||||
|
||||
// 生成短链
|
||||
ShortLink shortLink = shortLinkService.findByStorageKeyAndUrl(storageKey, fullPath);
|
||||
// 如果没有短链,则生成短链
|
||||
if (shortLink == null) {
|
||||
@@ -82,7 +97,11 @@ public class ShortLinkController {
|
||||
}
|
||||
|
||||
String shortUrl = StringUtils.removeDuplicateSlashes(domain + "/s/" + shortLink.getShortKey());
|
||||
return AjaxJson.getSuccessData(shortUrl);
|
||||
String pathLink = StringUtils.generatorPathLink(storageKey, fullPath);
|
||||
|
||||
result.add(new BatchGenerateLinkResponse(shortUrl, pathLink));
|
||||
}
|
||||
return AjaxJson.getSuccessData(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -91,28 +110,28 @@ public class ShortLinkController {
|
||||
@ApiOperationSupport(order = 2)
|
||||
@ApiOperation(value = "跳转短链", notes = "根据短链 key 跳转(302 重定向)到对应的直链.")
|
||||
@ApiImplicitParam(paramType = "path", name = "key", value = "短链 key", required = true)
|
||||
public String parseShortKey(@PathVariable String key) {
|
||||
public String parseShortKey(@PathVariable String key) throws IOException {
|
||||
ShortLink shortLink = shortLinkService.findByKey(key);
|
||||
if (shortLink == null) {
|
||||
throw new RuntimeException("此直链不存在或已失效.");
|
||||
}
|
||||
|
||||
// 获取站点域名
|
||||
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
|
||||
String domain = systemConfig.getDomain();
|
||||
|
||||
// 是否允许生成短链.
|
||||
// 判断是否允许生成短链.
|
||||
Boolean showShortLink = systemConfig.getShowShortLink();
|
||||
if ( BooleanUtil.isFalse(showShortLink)) {
|
||||
throw new IllegalDownloadLinkException("当前系统不允许使用短链.");
|
||||
}
|
||||
|
||||
String directLinkPrefix = systemConfig.getDirectLinkPrefix();
|
||||
Integer storageId = shortLink.getStorageId();
|
||||
String storageKey = storageSourceService.findKeyById(storageId);
|
||||
String filePath = StringUtils.encodeAllIgnoreSlashes(shortLink.getUrl());
|
||||
StorageSource storageSource = storageSourceService.findById(storageId);
|
||||
String storageKey = storageSource.getKey();
|
||||
String filePath = shortLink.getUrl();
|
||||
|
||||
String url = StringUtils.concat(domain, directLinkPrefix, storageKey, filePath);
|
||||
return "redirect:" + url;
|
||||
shortLinkService.handlerDownload(storageKey, filePath, shortLink.getShortKey());
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +1,29 @@
|
||||
package im.zhaojun.zfile.home.controller;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||
import com.github.xiaoymin.knife4j.annotations.ApiSort;
|
||||
import im.zhaojun.zfile.admin.model.entity.ReadmeConfig;
|
||||
import im.zhaojun.zfile.admin.model.entity.StorageSource;
|
||||
import im.zhaojun.zfile.admin.model.enums.ReadmeDisplayModeEnum;
|
||||
import im.zhaojun.zfile.admin.model.param.IStorageParam;
|
||||
import im.zhaojun.zfile.admin.service.ReadmeConfigService;
|
||||
import im.zhaojun.zfile.admin.service.StorageSourceService;
|
||||
import im.zhaojun.zfile.admin.service.SystemConfigService;
|
||||
import im.zhaojun.zfile.common.config.ZFileProperties;
|
||||
import im.zhaojun.zfile.common.context.StorageSourceContext;
|
||||
import im.zhaojun.zfile.common.exception.InvalidStorageSourceException;
|
||||
import im.zhaojun.zfile.common.exception.NotExistFileException;
|
||||
import im.zhaojun.zfile.common.util.AjaxJson;
|
||||
import im.zhaojun.zfile.common.util.HttpUtil;
|
||||
import im.zhaojun.zfile.common.util.StringUtils;
|
||||
import im.zhaojun.zfile.home.convert.StorageSourceConvert;
|
||||
import im.zhaojun.zfile.home.model.dto.SystemConfigDTO;
|
||||
import im.zhaojun.zfile.home.model.request.FileListConfigRequest;
|
||||
import im.zhaojun.zfile.home.model.result.FileItemResult;
|
||||
import im.zhaojun.zfile.home.model.result.SiteConfigResult;
|
||||
import im.zhaojun.zfile.home.model.result.StorageSourceConfigResult;
|
||||
import im.zhaojun.zfile.home.service.base.AbstractBaseFileService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -57,6 +64,9 @@ public class SiteController {
|
||||
|
||||
@Resource
|
||||
private ReadmeConfigService readmeConfigService;
|
||||
|
||||
@Resource
|
||||
private StorageSourceContext storageSourceContext;
|
||||
|
||||
|
||||
@ApiOperationSupport(order = 1)
|
||||
@@ -93,18 +103,41 @@ public class SiteController {
|
||||
|
||||
// 根据存储源 key 获取存储源 id
|
||||
Integer storageId = storageSource.getId();
|
||||
|
||||
// 获取指定目录 readme 文件
|
||||
ReadmeConfig readmeByPath = readmeConfigService.findReadmeByPath(storageId, path);
|
||||
|
||||
if (ObjectUtil.isNotNull(readmeByPath)) {
|
||||
String readmeText = readmeByPath.getReadmeText();
|
||||
ReadmeDisplayModeEnum displayMode = readmeByPath.getDisplayMode();
|
||||
|
||||
storageSourceConfigResult.setReadmeText(readmeText);
|
||||
storageSourceConfigResult.setReadmeDisplayMode(displayMode);
|
||||
|
||||
|
||||
ReadmeConfig readmeByPath = new ReadmeConfig();
|
||||
readmeByPath.setStorageId(storageId);
|
||||
readmeByPath.setDisplayMode(ReadmeDisplayModeEnum.BOTTOM);
|
||||
if (BooleanUtil.isTrue(storageSource.getCompatibilityReadme())) {
|
||||
try {
|
||||
log.info("存储源 {} 兼容获取目录 {} 下的 readme.md", storageSource.getName(), path);
|
||||
AbstractBaseFileService<IStorageParam> abstractBaseFileService = storageSourceContext.get(storageId);
|
||||
String pathAndName = StringUtils.concat(path, "readme.md");
|
||||
FileItemResult fileItem = abstractBaseFileService.getFileItem(pathAndName);
|
||||
if (fileItem != null) {
|
||||
String url = fileItem.getUrl();
|
||||
String readmeText = HttpUtil.getTextContent(url);
|
||||
readmeByPath.setReadmeText(readmeText);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (e instanceof NotExistFileException) {
|
||||
log.error("存储源 {} 兼容获取目录 {} 下的 readme.md 文件失败", storageSource.getName(), path);
|
||||
} else {
|
||||
log.error("存储源 {} 兼容获取目录 {} 下的 readme.md 文件失败", storageSource.getName(), path, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 获取指定目录 readme 文件
|
||||
ReadmeConfig dbReadmeConfig = readmeConfigService.findReadmeByPath(storageId, path);
|
||||
if (dbReadmeConfig != null) {
|
||||
readmeByPath = dbReadmeConfig;
|
||||
}
|
||||
log.info("存储源 {} 规则模式获取目录 {} 下文档信息", storageSource.getName(), path);
|
||||
}
|
||||
|
||||
|
||||
storageSourceConfigResult.setReadmeDisplayMode(readmeByPath.getDisplayMode());
|
||||
storageSourceConfigResult.setReadmeText(readmeByPath.getReadmeText());
|
||||
|
||||
return AjaxJson.getSuccessData(storageSourceConfigResult);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
package im.zhaojun.zfile.home.filter;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import im.zhaojun.zfile.admin.model.entity.DownloadLog;
|
||||
import im.zhaojun.zfile.admin.model.entity.ShortLink;
|
||||
import im.zhaojun.zfile.admin.model.entity.StorageSource;
|
||||
import im.zhaojun.zfile.admin.service.DownloadLogService;
|
||||
@@ -18,14 +13,9 @@ import im.zhaojun.zfile.admin.service.StorageSourceService;
|
||||
import im.zhaojun.zfile.admin.service.SystemConfigService;
|
||||
import im.zhaojun.zfile.common.constant.ZFileConstant;
|
||||
import im.zhaojun.zfile.common.context.StorageSourceContext;
|
||||
import im.zhaojun.zfile.common.exception.InvalidStorageSourceException;
|
||||
import im.zhaojun.zfile.common.exception.file.operator.DownloadFileException;
|
||||
import im.zhaojun.zfile.common.util.HttpUtil;
|
||||
import im.zhaojun.zfile.common.util.StringUtils;
|
||||
import im.zhaojun.zfile.home.model.dto.SystemConfigDTO;
|
||||
import im.zhaojun.zfile.home.service.base.AbstractBaseFileService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.util.EncodingUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
@@ -37,8 +27,6 @@ import javax.servlet.annotation.WebFilter;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -109,7 +97,6 @@ public class DownloadLinkFilter implements Filter {
|
||||
// 获取系统配置的直链前缀
|
||||
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
|
||||
String directLinkPrefix = systemConfig.getDirectLinkPrefix();
|
||||
|
||||
if (StrUtil.equalsIgnoreCase(currentRequestPrefix, directLinkPrefix)) {
|
||||
|
||||
if (BooleanUtil.isFalse(systemConfig.getShowPathLink())) {
|
||||
@@ -129,27 +116,7 @@ public class DownloadLinkFilter implements Filter {
|
||||
httpServletResponse.sendRedirect(forbiddenUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
Boolean recordDownloadLog = systemConfig.getRecordDownloadLog();
|
||||
if (BooleanUtil.isTrue(recordDownloadLog)) {
|
||||
DownloadLog downloadLog = new DownloadLog();
|
||||
downloadLog.setPath(decodeFilePath);
|
||||
downloadLog.setStorageKey(currentStorageKey);
|
||||
downloadLog.setCreateTime(new Date());
|
||||
downloadLog.setIp(ServletUtil.getClientIP(httpServletRequest));
|
||||
downloadLog.setReferer(httpServletRequest.getHeader(HttpHeaders.REFERER));
|
||||
downloadLog.setUserAgent(httpServletRequest.getHeader(HttpHeaders.USER_AGENT));
|
||||
|
||||
ShortLink shortLink = shortLinkService.findByStorageIdAndUrl(storageId, decodeFilePath);
|
||||
// 如果没有短链,则生成短链
|
||||
if (shortLink == null) {
|
||||
shortLink = shortLinkService.generatorShortLink(storageId, decodeFilePath);
|
||||
}
|
||||
downloadLog.setShortKey(shortLink.getShortKey());
|
||||
|
||||
downloadLogService.save(downloadLog);
|
||||
}
|
||||
handleDownloadLink(httpServletResponse, currentStorageKey, decodeFilePath);
|
||||
handleDownloadLink(httpServletResponse, storageId, currentStorageKey, decodeFilePath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -170,7 +137,7 @@ public class DownloadLinkFilter implements Filter {
|
||||
* @param filePath
|
||||
* 文件路径
|
||||
*/
|
||||
private void handleDownloadLink(HttpServletResponse response, String storageKey, String filePath) throws IOException {
|
||||
private void handleDownloadLink(HttpServletResponse response, Integer storageId, String storageKey, String filePath) throws IOException {
|
||||
StorageSource storageSource = storageSourceService.findByStorageKey(storageKey);
|
||||
Boolean enable = storageSource.getEnable();
|
||||
if (!enable) {
|
||||
@@ -184,48 +151,13 @@ public class DownloadLinkFilter implements Filter {
|
||||
filePath = "/" + filePath;
|
||||
}
|
||||
|
||||
AbstractBaseFileService<?> fileService;
|
||||
try {
|
||||
fileService = storageSourceContext.getByKey(storageKey);
|
||||
} catch (InvalidStorageSourceException e) {
|
||||
log.error("无效的存储源,存储源 key {}, 文件路径 {}", storageKey, filePath);
|
||||
response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8");
|
||||
response.getWriter().write("无效的或初始化失败的存储源, 请联系管理员!");
|
||||
return;
|
||||
ShortLink shortLink = shortLinkService.findByStorageIdAndUrl(storageId, filePath);
|
||||
// 如果没有短链,则生成短链
|
||||
if (shortLink == null) {
|
||||
shortLink = shortLinkService.generatorShortLink(storageId, filePath);
|
||||
}
|
||||
|
||||
String downloadUrl;
|
||||
try {
|
||||
downloadUrl = fileService.getDownloadUrl(filePath);
|
||||
} catch (DownloadFileException e) {
|
||||
log.error("获取文件下载链接异常 {}. 存储源 ID: {}, 文件路径: {}", e.getMessage(), e.getStorageId(), e.getPathAndName());
|
||||
response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8");
|
||||
response.getWriter().write("获取下载链接异常,请联系管理员!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (StrUtil.isEmpty(downloadUrl)) {
|
||||
log.error("获取到文件下载链接为空,存储源 key {}, 文件路径 {}", storageKey, filePath);
|
||||
response.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain;charset=utf-8");
|
||||
response.getWriter().write("获取下载链接异常,请联系管理员![2]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (StrUtil.equalsIgnoreCase(FileUtil.extName(filePath), "m3u8")) {
|
||||
String textContent = HttpUtil.getTextContent(downloadUrl);
|
||||
response.setContentType("application/vnd.apple.mpegurl;charset=utf-8");
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
byte[] textContentBytes = EncodingUtils.getBytes(textContent, CharsetUtil.CHARSET_UTF_8.displayName());
|
||||
IoUtil.write(outputStream, true, textContentBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// 禁止直链被浏览器 302 缓存.
|
||||
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate, private");
|
||||
response.setHeader(HttpHeaders.PRAGMA, "no-cache");
|
||||
response.setHeader(HttpHeaders.EXPIRES, "0");
|
||||
|
||||
response.sendRedirect(downloadUrl);
|
||||
shortLinkService.handlerDownload(storageKey, filePath, shortLink.getShortKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -90,6 +90,9 @@ public class StorageSourceAllParam {
|
||||
|
||||
@ApiModelProperty(value = "clientSecret", example = "l:zI-_yrW75lV8M61K@z.I2K@B/On6Q1a")
|
||||
private String clientSecret;
|
||||
|
||||
@ApiModelProperty(value = "回调地址", example = "https://zfile.jun6.net/onedrive/callback")
|
||||
private String redirectUri;
|
||||
|
||||
@ApiModelProperty(value = "区域", example = "cn-beijing")
|
||||
private String region;
|
||||
@@ -102,5 +105,8 @@ public class StorageSourceAllParam {
|
||||
|
||||
@ApiModelProperty(value = "编码格式", example = "UTF-8")
|
||||
private String encoding;
|
||||
|
||||
|
||||
@ApiModelProperty(value = "存储源 ID", example = "0AGrY0xF1D7PEUk9PV2")
|
||||
private String driveId;
|
||||
|
||||
}
|
||||
@@ -62,5 +62,8 @@ public class StorageSourceDTO {
|
||||
|
||||
@ApiModelProperty(value = "是否默认开启图片模式", example = "true")
|
||||
private boolean defaultSwitchToImgMode;
|
||||
|
||||
@ApiModelProperty(value = "兼容 readme 模式", example = "true", notes = "兼容模式, 目录文档读取 readme.md 文件")
|
||||
private Boolean compatibilityReadme;
|
||||
|
||||
}
|
||||
@@ -119,5 +119,11 @@ public class SystemConfigDTO {
|
||||
|
||||
@ApiModelProperty(value = "默认文件点击习惯", example = "click")
|
||||
private FileClickModeEnum fileClickMode;
|
||||
|
||||
|
||||
@ApiModelProperty(value = "最大同时上传文件数", example = "5")
|
||||
private Integer maxFileUploads;
|
||||
|
||||
@ApiModelProperty(value = "onlyOffice 在线预览地址", example = "http://office.zfile.vip")
|
||||
private String onlyOfficeUrl;
|
||||
|
||||
}
|
||||
@@ -34,6 +34,7 @@ public enum StorageTypeEnum implements IEnum {
|
||||
ONE_DRIVE_CHINA("onedrive-china", "OneDrive 世纪互联"),
|
||||
SHAREPOINT_DRIVE("sharepoint", "SharePoint"),
|
||||
SHAREPOINT_DRIVE_CHINA("sharepoint-china", "SharePoint 世纪互联"),
|
||||
GOOGLE_DRIVE("google-drive", "Google Drive"),
|
||||
QINIU("qiniu", "七牛云 KODO");
|
||||
|
||||
private static final Map<String, StorageTypeEnum> ENUM_MAP = new HashMap<>();
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package im.zhaojun.zfile.home.model.request;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量生成直链请求类
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(description = "批量生成直链请求类")
|
||||
public class BatchGenerateLinkRequest {
|
||||
|
||||
@NotBlank(message = "存储源 key 不能为空")
|
||||
private String storageKey;
|
||||
|
||||
@NotEmpty(message = "生成的文件路径不能为空")
|
||||
private List<String> paths;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package im.zhaojun.zfile.home.model.request;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import im.zhaojun.zfile.common.util.StringUtils;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 获取指定文件信息的请求参数
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(description = "获取指定文件信息的请求类")
|
||||
public class FileItemRequest {
|
||||
|
||||
@ApiModelProperty(value = "存储源 key", required = true, example = "local")
|
||||
@NotBlank(message = "存储源 key 不能为空")
|
||||
private String storageKey;
|
||||
|
||||
@ApiModelProperty(value = "请求路径", example = "/")
|
||||
private String path;
|
||||
|
||||
@ApiModelProperty(value = "文件夹密码, 如果文件夹需要密码才能访问,则支持请求密码", example = "123456")
|
||||
private String password;
|
||||
|
||||
public void handleDefaultValue() {
|
||||
if (StrUtil.isEmpty(path)) {
|
||||
path = "/";
|
||||
}
|
||||
// 自动补全路径, 如 a 补全为 /a/
|
||||
path = StringUtils.concat(path);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package im.zhaojun.zfile.home.model.request;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 搜索存储源中文件请求参数
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(description = "搜索存储源中文件请求类")
|
||||
public class SearchStorageRequest {
|
||||
|
||||
@ApiModelProperty(value = "存储源 key", required = true, example = "local")
|
||||
@NotBlank(message = "存储源 key 不能为空")
|
||||
private String storageKey;
|
||||
|
||||
@ApiModelProperty(value = "搜索 key", required = true, example = "png")
|
||||
@NotBlank(message = "搜索 key 不能为空")
|
||||
private String searchVal;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package im.zhaojun.zfile.home.model.request.operator;
|
||||
|
||||
import im.zhaojun.zfile.home.model.enums.FileTypeEnum;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 删除文件夹请求参数
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(description = "删除文件夹请求类")
|
||||
public class BatchDeleteRequest {
|
||||
|
||||
@ApiModelProperty(value = "存储源 key", required = true, example = "local")
|
||||
@NotBlank(message = "存储源 key 不能为空")
|
||||
private String storageKey;
|
||||
|
||||
@ApiModelProperty(value = "删除的文件详情")
|
||||
@NotEmpty(message = "要删除的文件/文件夹不能为空")
|
||||
private List<DeleteItem> deleteItems;
|
||||
|
||||
@Data
|
||||
public static class DeleteItem {
|
||||
|
||||
private String name;
|
||||
|
||||
private String path;
|
||||
|
||||
private FileTypeEnum type;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package im.zhaojun.zfile.home.model.request.operator;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 删除文件请求参数
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(description = "删除文件请求类")
|
||||
public class DeleteFileRequest {
|
||||
|
||||
@ApiModelProperty(value = "存储源 key", required = true, example = "local")
|
||||
@NotBlank(message = "存储源 key 不能为空")
|
||||
private String storageKey;
|
||||
|
||||
@ApiModelProperty(value = "请求路径", example = "/", notes = "表示在哪个文件夹下删除文件")
|
||||
@NotBlank(message = "请求路径不能为空")
|
||||
private String path;
|
||||
|
||||
@ApiModelProperty(value = "删除的文件夹名称", example = "movie")
|
||||
@NotBlank(message = "删除的文件名称不能为空")
|
||||
private String name;
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package im.zhaojun.zfile.home.model.request.operator;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 删除文件夹请求参数
|
||||
*
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(description = "删除文件夹请求类")
|
||||
public class DeleteFolderRequest {
|
||||
|
||||
@ApiModelProperty(value = "存储源 key", required = true, example = "local")
|
||||
@NotBlank(message = "存储源 key 不能为空")
|
||||
private String storageKey;
|
||||
|
||||
@ApiModelProperty(value = "请求路径", example = "/", notes = "表示在哪个文件夹下删除文件夹")
|
||||
@NotBlank(message = "请求路径不能为空")
|
||||
private String path;
|
||||
|
||||
@ApiModelProperty(value = "删除的文件夹名称", example = "movie")
|
||||
@NotBlank(message = "删除的文件夹名称不能为空")
|
||||
private String name;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package im.zhaojun.zfile.home.model.result;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Data
|
||||
@ApiModel(description = "批量生成直链结果类")
|
||||
@AllArgsConstructor
|
||||
public class BatchGenerateLinkResponse {
|
||||
|
||||
private String shortLink;
|
||||
|
||||
private String pathLink;
|
||||
|
||||
}
|
||||
@@ -85,5 +85,11 @@ public class SiteConfigResult {
|
||||
|
||||
@ApiModelProperty(value = "默认文件点击习惯", example = "click")
|
||||
private FileClickModeEnum fileClickMode;
|
||||
|
||||
@ApiModelProperty(value = "最大同时上传文件数", example = "5")
|
||||
private Integer maxFileUploads;
|
||||
|
||||
@ApiModelProperty(value = "onlyOffice 在线预览地址", example = "http://office.zfile.vip")
|
||||
private String onlyOfficeUrl;
|
||||
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamSelect;
|
||||
import im.zhaojun.zfile.admin.annoation.StorageParamSelectOption;
|
||||
import im.zhaojun.zfile.admin.annoation.model.StorageSourceParamDef;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamItem;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamSelect;
|
||||
import im.zhaojun.zfile.admin.annotation.StorageParamSelectOption;
|
||||
import im.zhaojun.zfile.admin.annotation.model.StorageSourceParamDef;
|
||||
import im.zhaojun.zfile.admin.model.enums.StorageParamTypeEnum;
|
||||
import im.zhaojun.zfile.admin.model.param.IStorageParam;
|
||||
import im.zhaojun.zfile.admin.service.SystemConfigService;
|
||||
@@ -163,7 +163,7 @@ public abstract class AbstractBaseFileService<P extends IStorageParam> implement
|
||||
// 从实现类中通过反射获取 options
|
||||
Class<? extends StorageParamSelect> storageParamSelectClass = annotation.optionsClass();
|
||||
if (ObjectUtil.isNotEmpty(storageParamSelectClass)
|
||||
&& ObjectUtil.notEqual(storageParamSelectClass.getName(), "im.zhaojun.zfile.admin.annoation.StorageParamSelect")) {
|
||||
&& ObjectUtil.notEqual(storageParamSelectClass.getName(), "im.zhaojun.zfile.admin.annotation.StorageParamSelect")) {
|
||||
StorageParamSelect storageParamSelect = ReflectUtil.newInstance(storageParamSelectClass);
|
||||
List<StorageSourceParamDef.Options> storageParamSelectOptions = storageParamSelect.getOptions(annotation, param);
|
||||
optionsList.addAll(storageParamSelectOptions);
|
||||
@@ -195,17 +195,6 @@ public abstract class AbstractBaseFileService<P extends IStorageParam> implement
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 搜索文件
|
||||
*
|
||||
* @param name 文件名
|
||||
* @return 包含该文件名的所有文件或文件夹
|
||||
*/
|
||||
public synchronized List<FileItemResult> search(String name) {
|
||||
return zFileCache.find(storageId, name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取单个文件信息
|
||||
*
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.amazonaws.services.s3.model.S3ObjectSummary;
|
||||
import com.amazonaws.services.s3.model.SetBucketCrossOriginConfigurationRequest;
|
||||
import im.zhaojun.zfile.admin.exception.StorageSourceAutoConfigCorsException;
|
||||
import im.zhaojun.zfile.admin.model.param.S3BaseParam;
|
||||
import im.zhaojun.zfile.admin.service.SystemConfigService;
|
||||
import im.zhaojun.zfile.common.constant.ZFileConstant;
|
||||
import im.zhaojun.zfile.common.exception.file.operator.GetFileInfoException;
|
||||
import im.zhaojun.zfile.common.util.StringUtils;
|
||||
@@ -24,11 +25,14 @@ import im.zhaojun.zfile.home.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.zfile.home.model.result.FileItemResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -36,15 +40,18 @@ import java.util.List;
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends AbstractBaseFileService<P> {
|
||||
|
||||
|
||||
protected AmazonS3 s3Client;
|
||||
|
||||
|
||||
@Resource
|
||||
private SystemConfigService systemConfigService;
|
||||
|
||||
@Override
|
||||
public List<FileItemResult> fileList(String folderPath) {
|
||||
return s3FileList(folderPath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 默认 S3 获取对象下载链接的方法, 如果指定了域名, 则替换为自定义域名.
|
||||
* @return S3 对象访问地址
|
||||
@@ -53,33 +60,33 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
|
||||
public String getDownloadUrl(String pathAndName) {
|
||||
String bucketName = param.getBucketName();
|
||||
String domain = param.getDomain();
|
||||
|
||||
|
||||
String fullPath = StringUtils.concatTrimStartSlashes(param.getBasePath() + pathAndName);
|
||||
|
||||
|
||||
// 如果不是私有空间, 且指定了加速域名, 则直接返回下载地址.
|
||||
if (BooleanUtil.isFalse(param.isPrivate()) && StrUtil.isNotEmpty(domain)) {
|
||||
return StringUtils.concat(domain, StringUtils.encodeAllIgnoreSlashes(fullPath));
|
||||
}
|
||||
|
||||
|
||||
Integer tokenTime = param.getTokenTime();
|
||||
if (param.getTokenTime() == null || param.getTokenTime() < 1) {
|
||||
tokenTime = 1800;
|
||||
}
|
||||
|
||||
|
||||
Date expirationDate = new Date(System.currentTimeMillis() + tokenTime * 1000);
|
||||
|
||||
|
||||
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, fullPath, HttpMethod.GET);
|
||||
generatePresignedUrlRequest.setExpiration(expirationDate);
|
||||
URL url = s3Client.generatePresignedUrl(generatePresignedUrlRequest);
|
||||
|
||||
|
||||
String defaultUrl = url.toExternalForm();
|
||||
if (StrUtil.isNotEmpty(domain)) {
|
||||
defaultUrl = StringUtils.concat(domain, url.getFile());
|
||||
}
|
||||
return defaultUrl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取 S3 指定目录下的对象列表
|
||||
* @param path 路径
|
||||
@@ -91,7 +98,7 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
|
||||
String fullPath = StringUtils.trimStartSlashes(StringUtils.concat(param.getBasePath(), path, ZFileConstant.PATH_SEPARATOR));
|
||||
List<FileItemResult> fileItemList = new ArrayList<>();
|
||||
ObjectListing objectListing = s3Client.listObjects(new ListObjectsRequest(bucketName, fullPath, "", "/", 1000));
|
||||
|
||||
|
||||
for (S3ObjectSummary s : objectListing.getObjectSummaries()) {
|
||||
FileItemResult fileItemResult = new FileItemResult();
|
||||
if (s.getKey().equals(fullPath)) {
|
||||
@@ -102,13 +109,13 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
|
||||
fileItemResult.setTime(s.getLastModified());
|
||||
fileItemResult.setType(FileTypeEnum.FILE);
|
||||
fileItemResult.setPath(path);
|
||||
|
||||
|
||||
String fullPathAndName = StringUtils.concat(path, fileItemResult.getName());
|
||||
fileItemResult.setUrl(getDownloadUrl(fullPathAndName));
|
||||
|
||||
|
||||
fileItemList.add(fileItemResult);
|
||||
}
|
||||
|
||||
|
||||
for (String commonPrefix : objectListing.getCommonPrefixes()) {
|
||||
FileItemResult fileItemResult = new FileItemResult();
|
||||
fileItemResult.setName(commonPrefix.substring(fullPath.length(), commonPrefix.length() - 1));
|
||||
@@ -116,24 +123,24 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
|
||||
if (StrUtil.isEmpty(name) || StrUtil.equals(name, StringUtils.DELIMITER_STR)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
fileItemResult.setType(FileTypeEnum.FOLDER);
|
||||
fileItemResult.setPath(path);
|
||||
fileItemList.add(fileItemResult);
|
||||
}
|
||||
|
||||
|
||||
return fileItemList;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public FileItemResult getFileItem(String pathAndName) {
|
||||
try {
|
||||
String fileName = FileUtil.getName(pathAndName);
|
||||
String parentPath = StringUtils.getParentPath(pathAndName);
|
||||
|
||||
|
||||
String trimStartPath = StringUtils.concatTrimStartSlashes(param.getBasePath(), pathAndName);
|
||||
ObjectMetadata objectMetadata = s3Client.getObjectMetadata(param.getBucketName(), trimStartPath);
|
||||
|
||||
|
||||
FileItemResult fileItemResult = new FileItemResult();
|
||||
fileItemResult.setName(fileName);
|
||||
fileItemResult.setSize(objectMetadata.getInstanceLength());
|
||||
@@ -146,7 +153,7 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
|
||||
throw new GetFileInfoException(storageId, pathAndName, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean newFolder(String path, String name) {
|
||||
String bucketName = param.getBucketName();
|
||||
@@ -158,7 +165,7 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
|
||||
PutObjectResult putObjectResult = s3Client.putObject(putObjectRequest);
|
||||
return putObjectResult != null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean deleteFile(String path, String name) {
|
||||
String bucketName = param.getBucketName();
|
||||
@@ -172,7 +179,7 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean deleteFolder(String path, String name) {
|
||||
String bucketName = param.getBucketName();
|
||||
@@ -186,16 +193,16 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean renameFile(String path, String name, String newName) {
|
||||
String bucketName = param.getBucketName();
|
||||
String srcPath = StringUtils.concat(param.getBasePath(), path, name);
|
||||
srcPath = StringUtils.trimStartSlashes(srcPath);
|
||||
|
||||
|
||||
String distPath = StringUtils.concat(param.getBasePath(), path, newName);
|
||||
distPath = StringUtils.trimStartSlashes(distPath);
|
||||
|
||||
|
||||
try {
|
||||
s3Client.copyObject(bucketName, srcPath, bucketName, distPath);
|
||||
deleteFile(path, name);
|
||||
@@ -203,41 +210,62 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
|
||||
} catch (Exception e) {
|
||||
log.error("存储源 {} 重命名文件 {} 至 {} 失败", storageId, srcPath, distPath, e);
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean renameFolder(String path, String name, String newName) {
|
||||
throw new UnsupportedOperationException("不支持重命名文件夹");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getUploadUrl(String path, String name, Long size) {
|
||||
String bucketName = param.getBucketName();
|
||||
String uploadToPath = StringUtils.concat(param.getBasePath(), path, name);
|
||||
uploadToPath = StringUtils.trimStartSlashes(uploadToPath);
|
||||
|
||||
|
||||
GeneratePresignedUrlRequest req =
|
||||
new GeneratePresignedUrlRequest(bucketName, uploadToPath, HttpMethod.PUT);
|
||||
URL url = s3Client.generatePresignedUrl(req);
|
||||
|
||||
|
||||
return url.toExternalForm();
|
||||
}
|
||||
|
||||
|
||||
protected void setUploadCors() {
|
||||
if (param.isAutoConfigCors()) {
|
||||
try {
|
||||
BucketCrossOriginConfiguration bucketCrossOriginConfiguration = new BucketCrossOriginConfiguration();
|
||||
ArrayList<CORSRule> rules = new ArrayList<>();
|
||||
|
||||
// 获取历史的 CORS 规则
|
||||
BucketCrossOriginConfiguration bucketCrossOriginConfiguration = s3Client.getBucketCrossOriginConfiguration(param.getBucketName());
|
||||
if (bucketCrossOriginConfiguration == null) {
|
||||
bucketCrossOriginConfiguration = new BucketCrossOriginConfiguration();
|
||||
}
|
||||
List<CORSRule> corsRules = bucketCrossOriginConfiguration.getRules();
|
||||
if (corsRules == null) {
|
||||
corsRules = new ArrayList<>();
|
||||
}
|
||||
|
||||
|
||||
// 当前要添加的规则
|
||||
List<String> allowOrigins = Arrays.asList("*", systemConfigService.getDomain(), systemConfigService.getFrontDomain());
|
||||
|
||||
// 从历史规则中查找是否已经存在, 如果存在则不添加.
|
||||
boolean presentCorsRules = corsRules.stream().anyMatch(corsRule -> {
|
||||
List<String> origins = corsRule.getAllowedOrigins();
|
||||
return new HashSet<>(origins).containsAll(allowOrigins);
|
||||
});
|
||||
|
||||
if (presentCorsRules) {
|
||||
log.info("存储源 {} CORS 规则已经存在,不需要重复添加", storageId);
|
||||
return;
|
||||
}
|
||||
|
||||
CORSRule corsRule = new CORSRule();
|
||||
corsRule.setAllowedMethods(CORSRule.AllowedMethods.PUT);
|
||||
corsRule.setAllowedOrigins("*");
|
||||
|
||||
rules.add(corsRule);
|
||||
|
||||
bucketCrossOriginConfiguration.setRules(rules);
|
||||
corsRule.setAllowedMethods(CORSRule.AllowedMethods.PUT, CORSRule.AllowedMethods.GET);
|
||||
corsRule.setAllowedOrigins("*", systemConfigService.getDomain(), systemConfigService.getFrontDomain());
|
||||
corsRules.add(corsRule);
|
||||
bucketCrossOriginConfiguration.setRules(corsRules);
|
||||
|
||||
SetBucketCrossOriginConfigurationRequest setBucketCrossOriginConfigurationRequest =
|
||||
new SetBucketCrossOriginConfigurationRequest(param.getBucketName(), bucketCrossOriginConfiguration);
|
||||
s3Client.setBucketCrossOriginConfiguration(setBucketCrossOriginConfigurationRequest);
|
||||
@@ -246,5 +274,5 @@ public abstract class AbstractS3BaseFileService<P extends S3BaseParam> extends A
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import cn.hutool.http.HttpUtil;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import im.zhaojun.zfile.admin.constant.StorageConfigConstant;
|
||||
import im.zhaojun.zfile.admin.model.dto.OneDriveToken;
|
||||
import im.zhaojun.zfile.admin.model.dto.OAuth2Token;
|
||||
import im.zhaojun.zfile.admin.model.entity.StorageSourceConfig;
|
||||
import im.zhaojun.zfile.admin.model.param.MicrosoftDriveParam;
|
||||
import im.zhaojun.zfile.admin.service.StorageSourceConfigService;
|
||||
@@ -90,7 +90,7 @@ public abstract class MicrosoftDriveServiceBase<P extends MicrosoftDriveParam> e
|
||||
*
|
||||
* @return 刷新后的 Token
|
||||
*/
|
||||
public OneDriveToken getRefreshToken() {
|
||||
public OAuth2Token getRefreshToken() {
|
||||
StorageSourceConfig refreshStorageSourceConfig =
|
||||
storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.REFRESH_TOKEN_KEY);
|
||||
|
||||
@@ -99,16 +99,26 @@ public abstract class MicrosoftDriveServiceBase<P extends MicrosoftDriveParam> e
|
||||
"&client_secret=" + getClientSecret() +
|
||||
"&refresh_token=" + refreshStorageSourceConfig.getValue() +
|
||||
"&grant_type=refresh_token";
|
||||
|
||||
|
||||
log.info("{} 尝试刷新令牌, 参数信息为: {}", this, param);
|
||||
|
||||
String fullAuthenticateUrl = AUTHENTICATE_URL.replace("{authenticateEndPoint}", getAuthenticateEndPoint());
|
||||
HttpRequest post = HttpUtil.createPost(fullAuthenticateUrl);
|
||||
|
||||
post.body(param, "application/x-www-form-urlencoded");
|
||||
HttpResponse response = post.execute();
|
||||
String body = response.body();
|
||||
log.info("{} 尝试刷新令牌成功, 响应信息为: {}", this, body);
|
||||
|
||||
JSONObject jsonBody = JSONObject.parseObject(body);
|
||||
|
||||
if (response.getStatus() != HttpStatus.OK.value()) {
|
||||
throw new RuntimeException(response.body());
|
||||
return OAuth2Token.fail(getClientId(), getClientSecret(), getRedirectUri(), body);
|
||||
}
|
||||
return JSONObject.parseObject(response.body(), OneDriveToken.class);
|
||||
|
||||
String accessToken = jsonBody.getString("access_token");
|
||||
String refreshToken = jsonBody.getString("refresh_token");
|
||||
return OAuth2Token.success(getClientId(), getClientSecret(), getRedirectUri(), accessToken, refreshToken, body);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,10 +129,11 @@ public abstract class MicrosoftDriveServiceBase<P extends MicrosoftDriveParam> e
|
||||
*
|
||||
* @return 获取的 Token 信息.
|
||||
*/
|
||||
public OneDriveToken getToken(String code) {
|
||||
String param = "client_id=" + getClientId() +
|
||||
"&redirect_uri=" + getRedirectUri() +
|
||||
"&client_secret=" + getClientSecret() +
|
||||
public OAuth2Token getToken(String code, String clientId, String clientSecret, String redirectUri) {
|
||||
log.info("{} 根据授权回调 code 获取令牌:code: {}, clientId: {}, clientSecret: {}, redirectUri: {}", this, code, clientId, clientSecret, redirectUri);
|
||||
String param = "client_id=" + clientId +
|
||||
"&redirect_uri=" + redirectUri +
|
||||
"&client_secret=" + clientSecret +
|
||||
"&code=" + code +
|
||||
"&scope=" + getScope() +
|
||||
"&grant_type=authorization_code";
|
||||
@@ -132,7 +143,17 @@ public abstract class MicrosoftDriveServiceBase<P extends MicrosoftDriveParam> e
|
||||
|
||||
post.body(param, "application/x-www-form-urlencoded");
|
||||
HttpResponse response = post.execute();
|
||||
return JSONObject.parseObject(response.body(), OneDriveToken.class);
|
||||
String body = response.body();
|
||||
log.info("{} 根据授权回调 code 获取令牌结果:body: {}", this, body);
|
||||
JSONObject jsonBody = JSONObject.parseObject(body);
|
||||
|
||||
if (response.getStatus() != HttpStatus.OK.value()) {
|
||||
return OAuth2Token.fail(clientId, clientSecret, redirectUri, body);
|
||||
}
|
||||
|
||||
String accessToken = jsonBody.getString("access_token");
|
||||
String refreshToken = jsonBody.getString("refresh_token");
|
||||
return OAuth2Token.success(clientId, clientSecret, redirectUri, accessToken, refreshToken, body);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -362,10 +383,10 @@ public abstract class MicrosoftDriveServiceBase<P extends MicrosoftDriveParam> e
|
||||
@Override
|
||||
public void refreshAccessToken() {
|
||||
try {
|
||||
OneDriveToken refreshToken = getRefreshToken();
|
||||
OAuth2Token refreshToken = getRefreshToken();
|
||||
|
||||
if (refreshToken.getAccessToken() == null || refreshToken.getRefreshToken() == null) {
|
||||
return;
|
||||
throw new StorageSourceRefreshTokenException("获取或刷新 AccessToken 失败, 获取到的令牌为空, 相关诊断信息为: " + refreshToken, storageId);
|
||||
}
|
||||
|
||||
StorageSourceConfig accessTokenConfig =
|
||||
|
||||
@@ -15,7 +15,7 @@ public abstract class ProxyDownloadService<P extends ProxyDownloadParam> extends
|
||||
* 空实现.
|
||||
*/
|
||||
@Override
|
||||
public void uploadFile(String path, InputStream inputStream) {
|
||||
public void uploadFile(String pathAndName, InputStream inputStream) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -89,14 +89,14 @@ public abstract class ProxyTransferService<P extends ProxyTransferParam> extends
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
* @param path
|
||||
* 文件下载路径
|
||||
* @param pathAndName
|
||||
* 文件上传路径
|
||||
*
|
||||
* @param inputStream
|
||||
* 文件流
|
||||
*
|
||||
*/
|
||||
public abstract void uploadFile(String path, InputStream inputStream);
|
||||
public abstract void uploadFile(String pathAndName, InputStream inputStream);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,8 @@ import org.apache.commons.net.ftp.FTPFile;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -161,9 +163,15 @@ public class FtpServiceImpl extends ProxyTransferService<FtpParam> {
|
||||
HttpServletResponse response = RequestHolder.getResponse();
|
||||
try {
|
||||
pathAndName = StringUtils.concat(param.getBasePath(), pathAndName);
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
String fileName = FileUtil.getName(pathAndName);
|
||||
String folderName = FileUtil.getParent(pathAndName, 1);
|
||||
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
|
||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM.getType());
|
||||
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + StringUtils.encodeAllIgnoreSlashes(fileName));
|
||||
|
||||
|
||||
ftp.download(folderName, fileName, outputStream);
|
||||
} catch (Exception e) {
|
||||
throw new DownloadFileException(storageId, "下载文件失败", e);
|
||||
@@ -173,9 +181,9 @@ public class FtpServiceImpl extends ProxyTransferService<FtpParam> {
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized void uploadFile(String path, InputStream inputStream) {
|
||||
String fullPath = StringUtils.concat(param.getBasePath(), path);
|
||||
String fileName = FileUtil.getName(path);
|
||||
public synchronized void uploadFile(String pathAndName, InputStream inputStream) {
|
||||
String fullPath = StringUtils.concat(param.getBasePath(), pathAndName);
|
||||
String fileName = FileUtil.getName(pathAndName);
|
||||
String folderName = FileUtil.getParent(fullPath, 1);
|
||||
ftp.upload(folderName, fileName, inputStream);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,605 @@
|
||||
package im.zhaojun.zfile.home.service.impl;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import im.zhaojun.zfile.admin.constant.StorageConfigConstant;
|
||||
import im.zhaojun.zfile.admin.model.dto.OAuth2Token;
|
||||
import im.zhaojun.zfile.admin.model.entity.StorageSourceConfig;
|
||||
import im.zhaojun.zfile.admin.model.param.GoogleDriveParam;
|
||||
import im.zhaojun.zfile.admin.service.StorageSourceConfigService;
|
||||
import im.zhaojun.zfile.common.cache.RefreshTokenCache;
|
||||
import im.zhaojun.zfile.common.exception.StorageSourceRefreshTokenException;
|
||||
import im.zhaojun.zfile.common.exception.file.StorageSourceException;
|
||||
import im.zhaojun.zfile.common.util.RequestHolder;
|
||||
import im.zhaojun.zfile.common.util.StringUtils;
|
||||
import im.zhaojun.zfile.home.model.enums.FileTypeEnum;
|
||||
import im.zhaojun.zfile.home.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.home.model.result.FileItemResult;
|
||||
import im.zhaojun.zfile.home.service.base.ProxyTransferService;
|
||||
import im.zhaojun.zfile.home.service.base.RefreshTokenService;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.StatusLine;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.methods.RequestBuilder;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author zhaojun
|
||||
*/
|
||||
@Service
|
||||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||
@Slf4j
|
||||
public class GoogleDriveServiceImpl extends ProxyTransferService<GoogleDriveParam> implements RefreshTokenService {
|
||||
|
||||
/**
|
||||
* 文件类型:文件夹
|
||||
*/
|
||||
private static final String FOLDER_MIME_TYPE = "application/vnd.google-apps.folder";
|
||||
|
||||
/**
|
||||
* 文件类型:快捷方式
|
||||
*/
|
||||
private static final String SHORTCUT_MIME_TYPE = "application/vnd.google-apps.shortcut";
|
||||
|
||||
/**
|
||||
* 文件基础操作 API
|
||||
*/
|
||||
private static final String DRIVE_FILE_URL = "https://www.googleapis.com/drive/v3/files";
|
||||
|
||||
|
||||
/**
|
||||
* 文件上传操作 API
|
||||
*/
|
||||
private static final String DRIVE_FILE_UPLOAD_URL = "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart";
|
||||
|
||||
/**
|
||||
* 刷新 AccessToken URL
|
||||
*/
|
||||
private static final String REFRESH_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
||||
|
||||
@javax.annotation.Resource
|
||||
private StorageSourceConfigService storageSourceConfigService;
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
refreshAccessToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据路径获取文件/文件夹 id
|
||||
*
|
||||
* @param path
|
||||
* 路径
|
||||
*
|
||||
* @return 文件/文件夹 id
|
||||
*/
|
||||
private String getIdByPath(String path) {
|
||||
String fullPath = StringUtils.concat(param.getBasePath(), path);
|
||||
if (StrUtil.isEmpty(fullPath) || StrUtil.equals(fullPath, "/")) {
|
||||
return StrUtil.isEmpty(param.getDriveId()) ? "root" : param.getDriveId();
|
||||
}
|
||||
|
||||
List<String> pathList = StrUtil.splitTrim(fullPath, "/");
|
||||
|
||||
String driveId = "";
|
||||
for (String subPath : pathList) {
|
||||
String folderIdParam = new GoogleDriveAPIParam().getDriveIdByPathParam(subPath, driveId);
|
||||
HttpRequest httpRequest = HttpUtil.createGet(DRIVE_FILE_URL);
|
||||
httpRequest.header("Authorization", "Bearer " + param.getAccessToken());
|
||||
httpRequest.body(folderIdParam);
|
||||
|
||||
HttpResponse httpResponse = httpRequest.execute();
|
||||
|
||||
if (HttpStatus.valueOf(httpResponse.getStatus()).isError()) {
|
||||
log.info("根据文件夹路径获取文件夹id失败, storageId: {},folderPath: {}, httpResponse body: {}", storageId, path, httpResponse.body());
|
||||
throw new StorageSourceException(storageId, "文件目录不存在或请求异常[1]");
|
||||
}
|
||||
|
||||
String body = httpResponse.body();
|
||||
|
||||
JSONObject jsonObject = JSON.parseObject(body);
|
||||
JSONArray files = jsonObject.getJSONArray("files");
|
||||
|
||||
if (files.size() == 0) {
|
||||
throw new StorageSourceException(storageId, "文件目录不存在或请求异常[2]");
|
||||
}
|
||||
|
||||
driveId = files.getJSONObject(0).getString("id");
|
||||
}
|
||||
|
||||
return driveId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FileItemResult> fileList(String folderPath) throws Exception {
|
||||
List<FileItemResult> result = new ArrayList<>();
|
||||
|
||||
String folderId = getIdByPath(folderPath);
|
||||
String pageToken = "";
|
||||
do {
|
||||
String folderIdParam = new GoogleDriveAPIParam().getFileListParam(folderId, pageToken);
|
||||
HttpRequest httpRequest = HttpUtil.createGet(DRIVE_FILE_URL);
|
||||
httpRequest.header("Authorization", "Bearer " + param.getAccessToken());
|
||||
httpRequest.body(folderIdParam);
|
||||
|
||||
HttpResponse httpResponse = httpRequest.execute();
|
||||
|
||||
if (HttpStatus.valueOf(httpResponse.getStatus()).isError()) {
|
||||
log.info("根据文件夹路径获取文件夹列表失败, storageId: {},folderPath: {}, httpResponse body: {}", storageId, folderPath, httpResponse.body());
|
||||
throw new StorageSourceException(storageId, "文件目录不存在或请求异常[3]");
|
||||
}
|
||||
|
||||
String body = httpResponse.body();
|
||||
|
||||
JSONObject jsonObject = JSON.parseObject(body);
|
||||
pageToken = jsonObject.getString("nextPageToken");
|
||||
JSONArray files = jsonObject.getJSONArray("files");
|
||||
result.addAll(jsonArrayToFileList(files, folderPath));
|
||||
} while (StrUtil.isNotEmpty(pageToken));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileItemResult getFileItem(String pathAndName) {
|
||||
String fileId = getIdByPath(pathAndName);
|
||||
|
||||
String folderName = FileUtil.getParent(pathAndName, 1);
|
||||
|
||||
HttpRequest httpRequest = HttpUtil.createGet(DRIVE_FILE_URL + "/" + fileId);
|
||||
httpRequest.header("Authorization", "Bearer " + param.getAccessToken());
|
||||
httpRequest.body("fields=id,name,mimeType,shortcutDetails,size,modifiedTime");
|
||||
HttpResponse httpResponse = httpRequest.execute();
|
||||
|
||||
if (HttpStatus.valueOf(httpResponse.getStatus()).isError()) {
|
||||
log.info("根据文件路径获取文件详情失败, storageId: {},pathAndName: {}, httpResponse body: {}", storageId, pathAndName, httpResponse.body());
|
||||
throw new StorageSourceException(storageId, "文件不存在或请求异常");
|
||||
}
|
||||
|
||||
String body = httpResponse.body();
|
||||
JSONObject jsonObject = JSON.parseObject(body);
|
||||
return jsonObjectToFileItem(jsonObject, folderName);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean newFolder(String path, String name) {
|
||||
HttpResponse httpResponse = HttpRequest.post(DRIVE_FILE_URL)
|
||||
.header("Authorization", "Bearer " + param.getAccessToken())
|
||||
.body(new JSONObject()
|
||||
.fluentPut("name", name)
|
||||
.fluentPut("mimeType", FOLDER_MIME_TYPE)
|
||||
.fluentPut("parents", Collections.singletonList(getIdByPath(path)))
|
||||
.toJSONString())
|
||||
.execute();
|
||||
|
||||
if (HttpStatus.valueOf(httpResponse.getStatus()).isError()) {
|
||||
log.info("新建文件夹失败, storageId: {},path: {}, name: {}, httpResponse body: {}", storageId, path, name, httpResponse.body());
|
||||
throw new StorageSourceException(storageId, "新建文件夹失败");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteFile(String path, String name) {
|
||||
String pathAndName = StringUtils.concat(path, name);
|
||||
HttpResponse httpResponse = HttpRequest.delete(DRIVE_FILE_URL + "/" + getIdByPath(pathAndName))
|
||||
.header("Authorization", "Bearer " + param.getAccessToken())
|
||||
.execute();
|
||||
|
||||
if (HttpStatus.valueOf(httpResponse.getStatus()).isError()) {
|
||||
log.info("删除文件/文件夹失败, storageId: {},pathAndName: {}, httpResponse body: {}", storageId, pathAndName, httpResponse.body());
|
||||
throw new StorageSourceException(storageId, "删除失败");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteFolder(String path, String name) {
|
||||
return deleteFile(path, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renameFile(String path, String name, String newName) {
|
||||
String pathAndName = StringUtils.concat(path, name);
|
||||
String fileId = getIdByPath(pathAndName);
|
||||
|
||||
HttpResponse httpResponse = HttpRequest.patch(DRIVE_FILE_URL + "/" + fileId)
|
||||
.header("Authorization", "Bearer " + param.getAccessToken())
|
||||
.body(new JSONObject()
|
||||
.fluentPut("name", newName)
|
||||
.toJSONString())
|
||||
.execute();
|
||||
|
||||
if (HttpStatus.valueOf(httpResponse.getStatus()).isError()) {
|
||||
log.info("重命名文件/文件夹失败, storageId: {},path: {}, name: {}, httpResponse body: {}", storageId, path, name, httpResponse.body());
|
||||
throw new StorageSourceException(storageId, "重命名文件/文件夹失败");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renameFolder(String path, String name, String newName) {
|
||||
return renameFile(path, name, newName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadFile(String pathAndName, InputStream inputStream) {
|
||||
String boundary = IdUtil.fastSimpleUUID();
|
||||
String fileName = FileUtil.getName(pathAndName);
|
||||
String folderName = StringUtils.getParentPath(pathAndName);
|
||||
|
||||
String jsonString = new JSONObject()
|
||||
.fluentPut("name", fileName)
|
||||
.fluentPut("parents", Collections.singletonList(getIdByPath(folderName)))
|
||||
.toJSONString();
|
||||
HttpEntity entity = MultipartEntityBuilder.create()
|
||||
.setMimeSubtype("related")
|
||||
.setBoundary(boundary)
|
||||
.addTextBody(boundary, jsonString, ContentType.APPLICATION_JSON)
|
||||
.addBinaryBody(boundary, inputStream)
|
||||
.build();
|
||||
|
||||
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||
HttpUriRequest httpUriRequest = RequestBuilder.post(DRIVE_FILE_UPLOAD_URL)
|
||||
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + param.getAccessToken())
|
||||
.setEntity(entity)
|
||||
.build();
|
||||
|
||||
CloseableHttpResponse response = httpClient.execute(httpUriRequest);
|
||||
StatusLine statusLine = response.getStatusLine();
|
||||
if (HttpStatus.valueOf(statusLine.getStatusCode()).isError()) {
|
||||
HttpEntity responseEntity = response.getEntity();
|
||||
String responseBody = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
|
||||
log.error("上传文件失败, storageId: {},pathAndName: {}, httpResponse body: {}", storageId, pathAndName, responseBody);
|
||||
throw new StorageSourceException(storageId, "上传文件失败:" + responseBody);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("上传文件失败, storageId: {},pathAndName: {}, httpResponse body: {}", storageId, pathAndName, e);
|
||||
throw new StorageSourceException(storageId, "上传文件失败:" + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Resource> downloadToStream(String pathAndName) {
|
||||
String fileId = getIdByPath(pathAndName);
|
||||
|
||||
HttpServletRequest request = RequestHolder.getRequest();
|
||||
|
||||
HttpRequest httpRequest = HttpUtil.createGet(DRIVE_FILE_URL + "/" + fileId);
|
||||
httpRequest.header("Authorization", "Bearer " + param.getAccessToken());
|
||||
httpRequest.body("alt=media");
|
||||
httpRequest.header(HttpHeaders.RANGE, request.getHeader(HttpHeaders.RANGE));
|
||||
HttpResponse httpResponse = httpRequest.executeAsync();
|
||||
if (HttpStatus.valueOf(httpResponse.getStatus()).isError()) {
|
||||
log.info("GoogleDrive下载文件失败, storageId: {},pathAndName: {}", storageId, pathAndName);
|
||||
throw new StorageSourceException(storageId, "下载文件失败");
|
||||
}
|
||||
|
||||
try {
|
||||
HttpServletResponse response = RequestHolder.getResponse();
|
||||
response.setHeader(HttpHeaders.CONTENT_RANGE, httpResponse.header(HttpHeaders.CONTENT_RANGE));
|
||||
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
|
||||
response.setHeader(HttpHeaders.CONTENT_LENGTH, httpResponse.header(HttpHeaders.CONTENT_LENGTH));
|
||||
response.setContentType(httpResponse.header(HttpHeaders.CONTENT_TYPE));
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
httpResponse.writeBody(outputStream, true, null);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("下载文件失败:" + e.getMessage(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public StorageTypeEnum getStorageTypeEnum() {
|
||||
return StorageTypeEnum.GOOGLE_DRIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 RefreshToken 刷新 AccessToken, 返回刷新后的 Token.
|
||||
*
|
||||
* @return 刷新后的 Token
|
||||
*/
|
||||
public OAuth2Token getRefreshToken() {
|
||||
StorageSourceConfig refreshStorageSourceConfig =
|
||||
storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.REFRESH_TOKEN_KEY);
|
||||
|
||||
String paramStr = "client_id=" + param.getClientId() +
|
||||
"&client_secret=" + param.getClientSecret() +
|
||||
"&refresh_token=" + refreshStorageSourceConfig.getValue() +
|
||||
"&grant_type=refresh_token" +
|
||||
"&access_type=offline";
|
||||
|
||||
log.info("{} 尝试刷新令牌, 参数信息为: {}", this, param);
|
||||
|
||||
HttpRequest post = HttpUtil.createPost(REFRESH_TOKEN_URL);
|
||||
|
||||
post.body(paramStr);
|
||||
HttpResponse response = post.execute();
|
||||
String body = response.body();
|
||||
log.info("{} 尝试刷新令牌成功, 响应信息为: {}", this, body);
|
||||
|
||||
JSONObject jsonBody = JSONObject.parseObject(body);
|
||||
|
||||
if (response.getStatus() != HttpStatus.OK.value()) {
|
||||
return OAuth2Token.fail(param.getClientId(), param.getClientSecret(), param.getRedirectUri(), body);
|
||||
}
|
||||
|
||||
String accessToken = jsonBody.getString("access_token");
|
||||
return OAuth2Token.success(param.getClientId(), param.getClientSecret(), param.getRedirectUri(), accessToken, null, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新当前存储源 AccessToken
|
||||
*/
|
||||
@Override
|
||||
public void refreshAccessToken() {
|
||||
try {
|
||||
OAuth2Token refreshToken = getRefreshToken();
|
||||
|
||||
if (refreshToken.getAccessToken() == null) {
|
||||
throw new StorageSourceRefreshTokenException("获取 AccessToken 失败, 获取到的令牌为空, 相关诊断信息为: " + refreshToken, storageId);
|
||||
}
|
||||
|
||||
StorageSourceConfig accessTokenConfig =
|
||||
storageSourceConfigService.findByStorageIdAndName(storageId, StorageConfigConstant.ACCESS_TOKEN_KEY);
|
||||
accessTokenConfig.setValue(refreshToken.getAccessToken());
|
||||
|
||||
storageSourceConfigService.updateStorageConfig(Collections.singletonList(accessTokenConfig));
|
||||
RefreshTokenCache.putRefreshTokenInfo(storageId, RefreshTokenCache.RefreshTokenInfo.success());
|
||||
param.setAccessToken(refreshToken.getAccessToken());
|
||||
param.setRefreshToken(refreshToken.getRefreshToken());
|
||||
log.info("存储源 {} 刷新 AccessToken 成功", storageId);
|
||||
} catch (Exception e) {
|
||||
RefreshTokenCache.putRefreshTokenInfo(storageId, RefreshTokenCache.RefreshTokenInfo.fail(getStorageTypeEnum().getDescription() + " AccessToken 刷新失败: " + e.getMessage()));
|
||||
throw new StorageSourceRefreshTokenException("存储源 ID: [{}] 刷新 AccessToken 失败", e, storageId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 api 返回的 json array 为 zfile 文件对象列表
|
||||
*
|
||||
* @param jsonArray
|
||||
* api 返回文件 json array
|
||||
*
|
||||
* @param folderPath
|
||||
* 所属文件夹路径
|
||||
*
|
||||
* @return zfile 文件对象列表
|
||||
*/
|
||||
public List<FileItemResult> jsonArrayToFileList(JSONArray jsonArray, String folderPath) {
|
||||
ArrayList<FileItemResult> fileList = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < jsonArray.size(); i++) {
|
||||
fileList.add(jsonObjectToFileItem(jsonArray.getJSONObject(i), folderPath));
|
||||
}
|
||||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 转换 api 返回的 json object 为 zfile 文件对象
|
||||
*
|
||||
* @param jsonObject
|
||||
* api 返回文件 json object
|
||||
*
|
||||
* @param folderPath
|
||||
* 所属文件夹路径
|
||||
*
|
||||
* @return zfile 文件对象
|
||||
*/
|
||||
public FileItemResult jsonObjectToFileItem(JSONObject jsonObject, String folderPath) {
|
||||
FileItemResult fileItemResult = new FileItemResult();
|
||||
fileItemResult.setName(jsonObject.getString("name"));
|
||||
fileItemResult.setPath(folderPath);
|
||||
fileItemResult.setSize(jsonObject.getLong("size"));
|
||||
|
||||
String mimeType = jsonObject.getString("mimeType");
|
||||
if (ObjectUtil.equals(SHORTCUT_MIME_TYPE, mimeType)) {
|
||||
JSONObject shortcutDetails = jsonObject.getJSONObject("shortcutDetails");
|
||||
mimeType = shortcutDetails.getString("targetMimeType");
|
||||
}
|
||||
|
||||
if (StrUtil.equals(mimeType, FOLDER_MIME_TYPE)) {
|
||||
fileItemResult.setType(FileTypeEnum.FOLDER);
|
||||
} else {
|
||||
fileItemResult.setType(FileTypeEnum.FILE);
|
||||
fileItemResult.setUrl(getDownloadUrl(StringUtils.concat(folderPath, fileItemResult.getName())));
|
||||
}
|
||||
|
||||
fileItemResult.setTime(jsonObject.getDate("modifiedTime"));
|
||||
|
||||
if (fileItemResult.getSize() == null) {
|
||||
fileItemResult.setSize(-1L);
|
||||
}
|
||||
|
||||
return fileItemResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 请求参数类
|
||||
*/
|
||||
@Data
|
||||
class GoogleDriveAPIParam {
|
||||
|
||||
private final Integer DEFAULT_PAGE_SIZE = 1000;
|
||||
|
||||
// 存储源 id
|
||||
private String driveId;
|
||||
|
||||
// 是否返回共享驱动器或团队盘的内容
|
||||
private boolean includeItemsFromAllDrives;
|
||||
|
||||
// 查询适用的文件分组, 支持 'user', 'drive', 'allDrives'
|
||||
private String corpora;
|
||||
|
||||
// 请求的应用程序是否同时支持“我的云端硬盘”和共享云端硬盘
|
||||
private boolean supportsAllDrives;
|
||||
|
||||
// 请求的字段
|
||||
private String fields;
|
||||
|
||||
// 查询参数
|
||||
private String q;
|
||||
|
||||
// 每页多少条
|
||||
private Integer pageSize;
|
||||
|
||||
// 下页的页码
|
||||
private String pageToken;
|
||||
|
||||
/**
|
||||
* 根据路径获取 id 的 api 请求参数
|
||||
*
|
||||
* @param folderPath
|
||||
* 文件夹路径
|
||||
*/
|
||||
public String getDriveIdByPathParam(String folderPath, String parentId) {
|
||||
GoogleDriveAPIParam googleDriveAPIParam = getBasicParam();
|
||||
|
||||
String parentIdParam = "";
|
||||
|
||||
if (StrUtil.isNotEmpty(parentId)) {
|
||||
parentIdParam = "'" + parentId + "' in parents and ";
|
||||
}
|
||||
|
||||
googleDriveAPIParam.setFields("files(id)");
|
||||
googleDriveAPIParam.setQ(parentIdParam + " name = '" + folderPath + "' and trashed = false");
|
||||
|
||||
return googleDriveAPIParam.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据路径获取 id 的 api 请求参数
|
||||
*
|
||||
* @param folderId
|
||||
* google drive 文件夹 id
|
||||
*
|
||||
* @param pageToken
|
||||
* 分页 token
|
||||
*/
|
||||
public String getFileListParam(String folderId, String pageToken) {
|
||||
GoogleDriveAPIParam googleDriveAPIParam = getBasicParam();
|
||||
|
||||
googleDriveAPIParam.setFields("files(id,name,mimeType,shortcutDetails,size,modifiedTime),nextPageToken");
|
||||
googleDriveAPIParam.setQ("'" + folderId + "' in parents and trashed = false");
|
||||
googleDriveAPIParam.setPageToken(pageToken);
|
||||
googleDriveAPIParam.setPageSize(DEFAULT_PAGE_SIZE);
|
||||
return googleDriveAPIParam.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据关键字和路径搜索文件 api 请求参数
|
||||
*
|
||||
* @param folderId
|
||||
* 搜索的父文件夹 id
|
||||
*
|
||||
* @param pageToken
|
||||
* 分页 token
|
||||
*
|
||||
* @param keyword
|
||||
* 搜索关键字
|
||||
*/
|
||||
public String getSearchParam(String folderId, String pageToken, String keyword) {
|
||||
GoogleDriveAPIParam googleDriveAPIParam = getBasicParam();
|
||||
|
||||
String parentIdParam = "";
|
||||
if (StrUtil.isNotEmpty(folderId)) {
|
||||
parentIdParam = "'" + folderId + "' in parents and ";
|
||||
}
|
||||
|
||||
googleDriveAPIParam.setFields("files(id,name,mimeType,shortcutDetails,size,modifiedTime),nextPageToken");
|
||||
googleDriveAPIParam.setQ(parentIdParam + " name contains '" + keyword + "' and trashed = false");
|
||||
googleDriveAPIParam.setPageToken(pageToken);
|
||||
googleDriveAPIParam.setPageSize(DEFAULT_PAGE_SIZE);
|
||||
return googleDriveAPIParam.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断是否是团队盘,填充基础参数
|
||||
*/
|
||||
public GoogleDriveAPIParam getBasicParam() {
|
||||
GoogleDriveAPIParam googleDriveAPIParam = new GoogleDriveAPIParam();
|
||||
String driveId = param.getDriveId();
|
||||
|
||||
// 判断是否是团队盘,如果是,则需要添加团队盘的参数
|
||||
boolean isTeamDrive = StrUtil.isNotEmpty(driveId);
|
||||
|
||||
googleDriveAPIParam.setCorpora("user");
|
||||
if (isTeamDrive) {
|
||||
googleDriveAPIParam.setDriveId(driveId);
|
||||
googleDriveAPIParam.setIncludeItemsFromAllDrives(true);
|
||||
googleDriveAPIParam.setSupportsAllDrives(true);
|
||||
googleDriveAPIParam.setCorpora("drive");
|
||||
}
|
||||
|
||||
return googleDriveAPIParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求对象转 url param string
|
||||
*
|
||||
* @return url param string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
Field[] fields = ReflectUtil.getFields(this.getClass());
|
||||
|
||||
StringBuilder param = new StringBuilder();
|
||||
|
||||
for (Field field : fields) {
|
||||
if (StrUtil.startWith(field.getName(), "this")) {
|
||||
continue;
|
||||
}
|
||||
Object fieldValue = ReflectUtil.getFieldValue(this, field);
|
||||
|
||||
if (ObjectUtil.isNotEmpty(fieldValue) && ObjectUtil.notEqual(fieldValue, false)) {
|
||||
param.append(field.getName()).append("=").append(fieldValue).append("&");
|
||||
}
|
||||
}
|
||||
|
||||
param.deleteCharAt(param.length() - 1);
|
||||
return param.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,11 +185,12 @@ public class LocalServiceImpl extends ProxyTransferService<LocalParam> {
|
||||
|
||||
String fileName = file.getName();
|
||||
headers.setContentDispositionFormData("attachment", StringUtils.encodeAllIgnoreSlashes(fileName));
|
||||
|
||||
|
||||
return ResponseEntity
|
||||
.ok()
|
||||
.headers(headers)
|
||||
.contentLength(file.length())
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(new FileSystemResource(file));
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,6 @@ public class MinIOServiceImpl extends AbstractS3BaseFileService<MinIOParam> {
|
||||
.withPathStyleAccessEnabled(true)
|
||||
.withCredentials(new AWSStaticCredentialsProvider(credentials))
|
||||
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(param.getEndPoint(), "minio")).build();
|
||||
|
||||
setUploadCors();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package im.zhaojun.zfile.home.service.impl;
|
||||
|
||||
import im.zhaojun.zfile.home.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.admin.model.param.OneDriveChinaParam;
|
||||
import im.zhaojun.zfile.home.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.home.service.base.AbstractOneDriveServiceBase;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -43,22 +43,31 @@ public class OneDriveChinaServiceImpl extends AbstractOneDriveServiceBase<OneDri
|
||||
public String getAuthenticateEndPoint() {
|
||||
return "login.partner.microsoftonline.cn";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
if (param == null || param.getClientId() == null) {
|
||||
return clientId;
|
||||
}
|
||||
return param.getClientId();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
if (param == null || param.getRedirectUri() == null) {
|
||||
return redirectUri;
|
||||
}
|
||||
return param.getRedirectUri();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return clientSecret;
|
||||
if (param == null || param.getClientSecret() == null) {
|
||||
return clientSecret;
|
||||
}
|
||||
return param.getClientSecret();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getScope() {
|
||||
return scope;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package im.zhaojun.zfile.home.service.impl;
|
||||
|
||||
import im.zhaojun.zfile.home.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.admin.model.param.OneDriveParam;
|
||||
import im.zhaojun.zfile.home.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.home.service.base.AbstractOneDriveServiceBase;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -43,20 +43,29 @@ public class OneDriveServiceImpl extends AbstractOneDriveServiceBase<OneDrivePar
|
||||
public String getAuthenticateEndPoint() {
|
||||
return "login.microsoftonline.com";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
if (param == null || param.getClientId() == null) {
|
||||
return clientId;
|
||||
}
|
||||
return param.getClientId();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
if (param == null || param.getRedirectUri() == null) {
|
||||
return redirectUri;
|
||||
}
|
||||
return param.getRedirectUri();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return clientSecret;
|
||||
if (param == null || param.getClientSecret() == null) {
|
||||
return clientSecret;
|
||||
}
|
||||
return param.getClientSecret();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,6 +21,8 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -137,8 +139,15 @@ public class SftpServiceImpl extends ProxyTransferService<SftpParam> {
|
||||
|
||||
HttpServletResponse response = RequestHolder.getResponse();
|
||||
try {
|
||||
pathAndName = StringUtils.concat(param.getBasePath(), pathAndName);
|
||||
String fileName = FileUtil.getName(pathAndName);
|
||||
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
sftp.download(StringUtils.concat(param.getBasePath(), pathAndName), outputStream);
|
||||
|
||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM.getType());
|
||||
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + StringUtils.encodeAllIgnoreSlashes(fileName));
|
||||
|
||||
sftp.download(pathAndName, outputStream);
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("下载文件失败", e);
|
||||
@@ -147,9 +156,9 @@ public class SftpServiceImpl extends ProxyTransferService<SftpParam> {
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized void uploadFile(String path, InputStream inputStream) {
|
||||
String fullPath = StringUtils.concat(param.getBasePath(), path);
|
||||
String fileName = FileUtil.getName(path);
|
||||
public synchronized void uploadFile(String pathAndName, InputStream inputStream) {
|
||||
String fullPath = StringUtils.concat(param.getBasePath(), pathAndName);
|
||||
String fileName = FileUtil.getName(pathAndName);
|
||||
String folderName = FileUtil.getParent(fullPath, 1);
|
||||
sftp.upload(folderName, fileName, inputStream);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package im.zhaojun.zfile.home.service.impl;
|
||||
|
||||
import im.zhaojun.zfile.home.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.admin.model.param.SharePointChinaParam;
|
||||
import im.zhaojun.zfile.home.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.home.service.base.AbstractSharePointServiceBase;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -43,22 +43,31 @@ public class SharePointChinaServiceImpl extends AbstractSharePointServiceBase<Sh
|
||||
public String getAuthenticateEndPoint() {
|
||||
return "login.partner.microsoftonline.cn";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
if (param == null || param.getClientId() == null) {
|
||||
return clientId;
|
||||
}
|
||||
return param.getClientId();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
if (param == null || param.getRedirectUri() == null) {
|
||||
return redirectUri;
|
||||
}
|
||||
return param.getRedirectUri();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return clientSecret;
|
||||
if (param == null || param.getClientSecret() == null) {
|
||||
return clientSecret;
|
||||
}
|
||||
return param.getClientSecret();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getScope() {
|
||||
return scope;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package im.zhaojun.zfile.home.service.impl;
|
||||
|
||||
import im.zhaojun.zfile.home.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.admin.model.param.SharePointParam;
|
||||
import im.zhaojun.zfile.home.model.enums.StorageTypeEnum;
|
||||
import im.zhaojun.zfile.home.service.base.AbstractSharePointServiceBase;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -43,22 +43,31 @@ public class SharePointServiceImpl extends AbstractSharePointServiceBase<SharePo
|
||||
public String getAuthenticateEndPoint() {
|
||||
return "login.microsoftonline.com";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
if (param == null || param.getClientId() == null) {
|
||||
return clientId;
|
||||
}
|
||||
return param.getClientId();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getRedirectUri() {
|
||||
return redirectUri;
|
||||
if (param == null || param.getRedirectUri() == null) {
|
||||
return redirectUri;
|
||||
}
|
||||
return param.getRedirectUri();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getClientSecret() {
|
||||
return clientSecret;
|
||||
if (param == null || param.getClientSecret() == null) {
|
||||
return clientSecret;
|
||||
}
|
||||
return param.getClientSecret();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getScope() {
|
||||
return scope;
|
||||
|
||||
@@ -158,12 +158,13 @@ public class WebdavServiceImpl extends ProxyTransferService<WebdavParam> {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void uploadFile(String path, InputStream inputStream) {
|
||||
@Override
|
||||
public void uploadFile(String pathAndName, InputStream inputStream) {
|
||||
try {
|
||||
path = getRequestPath(path);
|
||||
sardine.put(path, inputStream);
|
||||
pathAndName = getRequestPath(pathAndName);
|
||||
sardine.put(pathAndName, inputStream);
|
||||
} catch (IOException e) {
|
||||
throw new FileUploadException(getStorageTypeEnum(), storageId, path, e);
|
||||
throw new FileUploadException(getStorageTypeEnum(), storageId, pathAndName, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
# onedrive config
|
||||
# onedrive config
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user