Compare commits

...

337 Commits
2.8 ... main

Author SHA1 Message Date
zhaojun
90101c173c 优化又拍云参数名称,避免歧义为 ak、sk 2025-04-13 17:21:54 +08:00
zhaojun
c590953197 增加移动端文件操作习惯功能 2025-04-13 17:21:21 +08:00
zhaojun
23772223bd 优化网页缓存设置 2025-04-13 17:20:47 +08:00
zhaojun
b88f413906 增加存储源元数据: "是否需要在上传文件前创建文件夹" 来优化上传流量,避免不必要的请求。 2025-04-13 17:19:48 +08:00
zhaojun
6d8bdf1df3 修改配置避免创建 issue 失败 2025-04-10 12:06:03 +08:00
zhaojun
0173357ec9 增加 bug report 配置,避免创建 issue 失败 2025-04-10 12:03:52 +08:00
zhaojun
b4ae4bcc90 修复用 OnlyOffice 预览过某个文件后,删除或重新上传本地修改过的文件时,仍然看到旧版本的 bug 2025-04-10 12:02:13 +08:00
zhaojun
f7679217ec 修复用 OnlyOffice 预览过某个文件后,删除或重新上传本地修改过的文件时,仍然看到旧版本的 bug 2025-04-08 22:41:32 +08:00
zhaojun
901d539332 修正 README 未居中的问题 2025-04-07 19:29:35 +08:00
zhaojun
1b3c284f4e 修改 README 描述、图片为最新版内容。 2025-04-07 19:07:30 +08:00
zhaojun
f5b6f8da4e 补充脚本文件 2025-04-03 22:54:42 +08:00
zhaojun
6da72845e4 补充 Dockerfile 文件 2025-04-03 22:40:10 +08:00
zhaojun
81b9ac5923 从捐赠版合并部分代码 2025-04-03 21:33:53 +08:00
zhao jun
8f8dc86365 Update README.md 2024-10-26 10:17:40 +08:00
zhao jun
f9d46edbcc Update README.md 2024-10-03 17:00:53 +08:00
zhao jun
e3991be3b7 📝 修改 issue 模板,使用自定义 issue helper 来实现 2023-12-10 04:40:40 +00:00
zhao jun
d42853abe2 Update README.md 2023-11-22 19:27:51 +08:00
zhao jun
e5c6a1aca4 Update README.md 2023-08-01 08:38:57 +08:00
zhao jun
b5d46912d2 📝 更新文档描述,增加赞助商 Logo 2023-07-10 20:38:21 +08:00
zhaojun
dfcbaf7158 👷 支持通过参数调整打包方式 2023-05-30 11:22:14 +08:00
zhaojun
17257dccda 🔖 升级依赖版本,发布 4.1.5 版本 2023-05-28 13:07:51 +08:00
zhaojun
1c1248306c 提交静态页面 2023-05-28 13:06:29 +08:00
zhaojun
d5873a7f97 🐛 修复类加载顺序不同,导致在未初始化数据库时就执行了 sql 脚本的 bug 2023-05-28 10:14:54 +08:00
zhaojun
7437dc8936 🙈 更新 .gitignore 文件 2023-05-27 18:43:42 +08:00
zhaojun
549a599a5b 修改 "是否默认记住密码" 功能值为 false 2023-05-27 18:42:18 +08:00
zhaojun
38effb0bb7 新增存储源复制功能 2023-05-27 16:54:02 +08:00
zhaojun
1e25c23b0b 🎨 优化代码 2023-05-27 16:53:44 +08:00
zhaojun
c870d32777 新增是否默认记住文件夹密码功能 2023-05-27 16:51:51 +08:00
zhaojun
b86e487a47 新增辅助测试 ant 表达式请求参数 2023-05-27 16:51:24 +08:00
zhaojun
330713509e 直链日志页返回值新增存储源 key 2023-05-27 16:51:01 +08:00
zhaojun
ecf85dfe9e 🐛 修复多吉云令牌无法自动刷新的 bug 2023-05-27 16:50:31 +08:00
zhaojun
ac3b4283a3 新增网站 favicon 网站地址自定义功能,返回的 html 是已经修改过的,不是等待页面加载完再修改。 2023-05-27 16:50:14 +08:00
zhaojun
141f9dee5e 修改直链代码处理方式,兼容性更好,并增加短链有效期功能 2023-05-27 16:47:23 +08:00
zhaojun
ce9f809ab5 新增辅助测试 ant 表达式和获取客户端 ip 的 Controller 2023-05-27 16:44:11 +08:00
zhaojun
b83af8dc5e 复制移动文件功能 2023-05-27 16:42:39 +08:00
zhaojun
ce0a7bd6ef 抽取代码到工具类 2023-05-27 16:26:22 +08:00
zhaojun
008425734c 下载日志增加下载类型功能 2023-05-27 16:25:53 +08:00
zhaojun
e078366395 ✏️ 修改拼写错误的方法名 2023-05-27 16:20:02 +08:00
zhaojun
69ec12ab99 🐛 修复直短链下载响应头问题,导致安卓手机下载 apk 时自动变 zip 的问题 2023-05-27 16:19:23 +08:00
zhaojun
8e93874c69 🐛 修复某些情况无限重定向的 bug 2023-05-27 16:18:54 +08:00
zhaojun
2a6f0f94cc ⬆️ 升级依赖版本,修复安全漏洞 2023-05-27 16:18:20 +08:00
zhaojun
d501d96ad6 🗃️ 去除无用 mybatis xml 片段 2023-05-27 16:13:14 +08:00
zhaojun
73569a63f8 🗃️ 数据库兼容性增强 2023-05-27 16:12:15 +08:00
zhaojun
71e6ba4d8b 🔊 增加 sqlite 数据库初始化日志,便于排查问题 2023-05-27 16:03:47 +08:00
赵俊
72a627be74 合并拉取请求 #526
fix: 修复目录穿越问题
2023-05-27 16:00:22 +08:00
赵俊
0344f687b6 合并拉取请求 #538
fix dogecloud 保存存储源异常
2023-05-27 15:59:35 +08:00
LinXin
1b0789cdd0 补充响应错误码文档地址. 2023-05-24 22:13:16 +08:00
liupengyu
7cf16754f8 fix dogecloud 保存存储源异常 2023-05-23 14:03:38 +08:00
lxh
6aefc107e7 fix: 修复目录穿越问题 2023-04-23 16:59:11 +08:00
zhaojun
e082043f99 🔖 发布 4.1.4 版本 2023-03-05 17:03:38 +08:00
zhaojun
a55e8ae2ad 提交静态页面 2023-03-05 17:02:43 +08:00
zhaojun
188431b64d Merge remote-tracking branch 'origin/main' 2023-03-05 16:50:04 +08:00
赵俊
7f23dcb7c4 合并拉取请求 #507
fix-bug:本地存储上传文件没有关闭流
2023-03-05 16:49:35 +08:00
zhaojun
8dcb64a60d ✏️ 修改 issue 模板 2023-03-05 15:42:35 +08:00
zhaojun
d9c64ff369 ⬆️ 升级依赖版本 2023-03-05 15:38:51 +08:00
zhaojun
6963b1d593 ✏️ 修改文档,增加谷歌云和多吉云支持 2023-03-05 15:37:12 +08:00
zhaojun
0a61e1047d 捕获 ClientAbortException 异常,不进行异常输出 2023-03-05 15:34:34 +08:00
zhaojun
3f02cd9832 🐛 修复存储源别名修改后再修改回去提示占用的 BUG 2023-03-05 15:34:00 +08:00
zhaojun
3aa42c00fa 🐛 修复七牛对私有空间使用自定义域名后无法正常下载的 bug 2023-03-05 15:33:38 +08:00
zhaojun
77b2253ff6 优化本地存储检测安全性的代码 2023-03-05 15:33:29 +08:00
zhaojun
71a4fdfbaf 🐛 修复 GoogleDrive 快捷方式文件夹无法正常获取内容的 BUG 2023-03-05 15:33:14 +08:00
zhaojun
2ecd69dc51 🐛 修复自动设置 CORS 时,某些 S3 兼容性不同导致的 BUG(BackBlaze 不支持 * 和实际域名写到一起,不支持空值) 2023-03-05 15:32:45 +08:00
zhaojun
ebae9ba5c8 增加动态忽略参数不显示到前端功能 2023-03-05 15:31:59 +08:00
zhaojun
b2fd722443 为多吉云增加存储源参数 2023-03-05 15:31:46 +08:00
zhaojun
e2ce404e87 为了安全性,去除从服务器加载文本文件的功能 2023-03-05 15:31:18 +08:00
zhaojun
6cfbe7689e 更显眼的提示用户使用 Google Drive 时需要自建 API 2023-03-05 15:30:38 +08:00
zhaojun
b95c1d890b 更显眼的提示用户腾讯云使用 CDN 回源鉴权后需要关闭 ZFile 中私有空间开关。 2023-03-05 15:29:48 +08:00
zhaojun
461c77012a 增加动态忽略参数不显示到前端功能 2023-03-05 15:29:36 +08:00
zhaojun
9328e0ea9d 根据查询条件批量删除直链 2023-03-05 15:28:14 +08:00
zhaojun
aefa928a19 🐛 修复短链对应的存储源关闭后,存储源仍然可以访问的 bug 2023-03-05 15:27:31 +08:00
zhaojun
89c6c515f1 🐛 避免未控制并发生成短链导致生成多条的 BUG 2023-03-05 15:26:22 +08:00
zhaojun
dcadffa265 增加是否缓存数据库的开关 2023-03-05 15:24:27 +08:00
zhaojun
300e58e92c 限制单 IP 直链单位时间内下载次数 2023-03-05 15:23:47 +08:00
zhaojun
96b71e4f8d 增加站点首页 Logo 自定义功能,Logo 支持点击跳转第三方链接。
增加默认排序字段功能,增加分页加载更多功能。
2023-03-05 15:16:55 +08:00
zhaojun
456aabb893 新增多吉云支持 2023-03-05 15:12:26 +08:00
wlplove007
b4d2ca238f fix-bug:本地存储上传文件没有关闭流 2023-03-02 20:45:53 +08:00
zhaojun
2afb841fd9 🔖 发布 4.1.3 版本 2022-11-26 20:02:31 +08:00
zhaojun
2b09812153 💄 更新前端页面 2022-11-26 20:02:18 +08:00
zhaojun
972099a598 优化 logback 日志输出格式及存储方式 2022-11-26 18:14:56 +08:00
zhaojun
9335d78d60 兼容 3.x 版本获取令牌功能 2022-11-26 18:11:50 +08:00
zhaojun
f332cb929b 🐛 修复本地存储不支持上传大小为 0 的文件. 2022-11-26 18:11:26 +08:00
zhaojun
a190a2ec6e 🐛 校验本地存储路径合法性,防止恶意获取上级目录。 2022-11-26 18:11:18 +08:00
zhaojun
5bfa7037cb ✏️ 增加 SharePoint 网站提示文本 2022-11-26 18:10:26 +08:00
zhaojun
4a94c879b8 🐛 修复 referer 防盗链允许为空时,仍然去黑/白名单校验的 bug 2022-11-26 18:10:04 +08:00
zhaojun
38161f96e1 优化微软 OneDrive、SharePoint 相关存储的代码, 更好的输出日志和重构代码。 2022-11-26 18:00:16 +08:00
zhaojun
33c751ab33 🐛 修复密码文件夹不正确的情况下,也显示出了文件夹 readme 的 bug 2022-11-26 17:59:52 +08:00
zhaojun
d12cbd2383 🐛 修复本地存储不支持上传大小为 0 的文件. 2022-11-26 17:59:14 +08:00
zhaojun
5f84becf08 优化存储源初始化方式,增加 name 的注入,以便更全面的日志输出。 2022-11-26 17:58:50 +08:00
zhaojun
432fd89c0f 优化存储源初始化方式,增加 name 的注入,以便更全面的日志输出。 2022-11-26 17:57:26 +08:00
zhaojun
84c8adc9d2 优化存储源初始化方式,增加 name 的注入,以便更全面的日志输出。 2022-11-26 17:56:54 +08:00
zhaojun
ba2523ac8a 重构代码,优化方法名 2022-11-26 17:56:12 +08:00
zhaojun
dcdd25c01f 🐛 修复 referer 防盗链允许为空时,仍然去黑/白名单校验的 bug 2022-11-26 17:55:15 +08:00
zhaojun
278e320550 🐛 修复 Referer 防盗链不生效的 bug 2022-11-26 17:54:43 +08:00
zhaojun
1bfa66cc49 更新 onedrive 使用的 restTemplate 客户端,增加日志记录功能,并移除请求头中写 storageId 来获取 AccessToken 的功能。 2022-11-26 17:54:07 +08:00
zhaojun
20fb2b3baa 🐛 修复本地存储删除失败的 bug 2022-09-24 20:11:03 +08:00
zhaojun
e9a85a4e88 🔖 发布 4.1.2 版本 2022-09-20 18:30:40 +08:00
zhaojun
63edbc11d3 🐛 修复在 Linux 下开启后台登陆图片验证码时,出现异常提示的 BUG 2022-09-20 18:30:22 +08:00
zhaojun
c98c6af0f9 🐛 修复事务中读取到旧缓存的 bug 2022-09-20 18:30:03 +08:00
zhaojun
47f6066733 🔨 重构代码,将捐赠版已验证的功能合并 2022-09-20 18:12:01 +08:00
zhaojun
f4fb471da3 💄 更新前端页面 2022-09-20 18:08:19 +08:00
zhaojun
301b6bdf70 🐛 修复没有 origin 情况下,跨域失效的问题。 2022-09-09 14:36:22 +08:00
zhaojun
a98c4f4c48 📝 文档更新,增加 3d 文件预览图片 2022-08-29 15:32:44 +08:00
zhaojun
f4a625dac6 🔖 发布 4.1.1 版本 2022-08-29 14:56:39 +08:00
zhaojun
1c2f3de55c 更新文档 2022-08-29 14:56:20 +08:00
zhaojun
ed375768f5 Merge remote-tracking branch 'origin/main' 2022-08-29 14:55:07 +08:00
zhaojun
b0c4ed1fad 💄 更新前端页面 2022-08-29 14:54:59 +08:00
zhaojun
8d30ca7eee 🐛 修复通过 nginx 反向代理后, 未登录后台时请求, 重定向到反代前地址的 bug 2022-08-29 14:44:12 +08:00
zhaojun
43aba8e20e 修改系统设置 value 值字段类型为 text, 防止过长 2022-08-29 14:39:08 +08:00
zhaojun
a2fa8b3eeb 🐛 修复没有自动创建数据库目录的 bug 2022-08-29 14:38:47 +08:00
赵俊
0ef77ee11b Update README.md
增加在线预览 Office 文档截图
2022-08-27 09:09:03 +08:00
zhaojun
6608c0b456 Merge remote-tracking branch 'origin/main' 2022-08-26 18:19:04 +08:00
zhaojun
3ceb5c8c1b 🔖 发布 4.1.0 版本 2022-08-26 18:18:44 +08:00
zhaojun
7399c89a8e 批量删除文件和批量删除直链功能 2022-08-26 18:18:19 +08:00
zhaojun
477c9dbdd2 优化代码结构 2022-08-26 18:17:58 +08:00
zhaojun
e8117c7d3b 增加 OnlyOffice 集成 2022-08-26 18:16:53 +08:00
zhaojun
b29ff1e646 增加 Google Drive 支持 2022-08-26 18:16:00 +08:00
zhaojun
774a8e184a 优化 OneDrive/SharePoint 获取令牌相关代码 2022-08-26 18:14:25 +08:00
zhaojun
7da1405798 💄 更新前端页面 2022-08-26 18:12:07 +08:00
赵俊
564770ba3c Update README.md 2022-08-16 18:45:11 +08:00
zhaojun
ac07de4e73 Merge remote-tracking branch 'origin/main' 2022-08-15 17:11:18 +08:00
zhaojun
3933eba99a 🔖 发布 4.0.10版本 2022-08-15 17:10:03 +08:00
zhaojun
27e8f7961e 🔖 发布 4.0.9 版本 2022-08-15 17:08:57 +08:00
zhaojun
55843cbef6 💄 更新前端页面 2022-08-15 16:35:17 +08:00
zhaojun
ebbb33409f 优化 OneDrive/SharePoint 获取 token 体验,增加信息显示,并优化页面效果。 2022-08-15 13:29:35 +08:00
zhaojun
b39360791f 🐛 修复因缓存未清理导致兼容 readme.md 功能修改无效的 bug 2022-08-15 13:28:35 +08:00
zhaojun
9833378e25 🐛 修复 SharePoint 世纪互联自定义 api 失败的 bug 2022-08-15 13:28:10 +08:00
zhaojun
31df2d16e2 🔖 发布 4.0.9 版本 2022-08-11 21:09:06 +08:00
zhaojun
f8f07912a1 🔨 规范代码 2022-08-11 21:06:40 +08:00
zhaojun
edf43954c6 兼容捐赠版数据文件功能 2022-08-11 21:06:16 +08:00
zhaojun
5a816b1dfb 增加 readme.md 兼容模式 2022-08-11 21:05:25 +08:00
zhaojun
910406c33a 增加最大同时上传数限制 2022-08-11 21:05:18 +08:00
zhaojun
543f76ad1d 增加 readme.md 兼容模式 2022-08-11 21:04:45 +08:00
zhaojun
1cc2d874a1 完善 OneDrive SharePoint 自定义 api 体验 2022-08-11 21:03:11 +08:00
zhaojun
18de372bf9 🐛 修复批量删除直链或直链下载日志数量过多时, 无法正常删除的问题。 2022-08-11 21:02:07 +08:00
zhaojun
5748644814 🐛 修复批量删除直链或直链下载日志数量过多时, 无法正常删除的问题。 2022-08-11 21:02:02 +08:00
zhaojun
fb8060d316 💄 更新前端页面 2022-08-11 21:01:41 +08:00
zhaojun
a4005defe2 更新 github issue 模板 2022-08-05 16:02:34 +08:00
zhaojun
4c90d5bdda 🔖 发布 4.0.8 版本 2022-08-05 15:30:13 +08:00
zhaojun
fa46850bb4 💄 更新前端页面 2022-08-05 15:29:08 +08:00
zhaojun
a7551fce53 完善代码结构 2022-08-05 15:28:52 +08:00
zhaojun
0591669ec9 增加获取单个文件接口请求参数 2022-08-05 15:28:33 +08:00
zhaojun
6286a9e9aa ✏️ 优化提示信息 2022-08-05 15:28:24 +08:00
zhaojun
c67ee1e89d 增加获取单个文件接口 2022-08-05 15:27:58 +08:00
zhaojun
c75695c63a 🐛 修复因类调用自身方法导致事务启用失败的 bug 2022-08-05 15:27:38 +08:00
zhaojun
8f771c652d ✏️ 优化 minio 提示信息 2022-08-05 15:25:52 +08:00
zhaojun
d6e13d6115 🐛 修复获取 m3u8 直链后系统报错的 bug. 2022-08-05 15:25:09 +08:00
zhaojun
8460d17b07 🔨 移动无用代码 2022-08-05 15:24:46 +08:00
zhaojun
49806221b4 💡 增加代码注释 2022-08-05 15:23:36 +08:00
zhaojun
409af8409d 🔨 重构代码,修改包名 2022-08-05 15:22:36 +08:00
zhaojun
6647efeb03 📝 完善 OneDrive SharePoint 反代域名参数的描述信息 2022-08-02 10:25:22 +08:00
zhaojun
c8bd52e26a 统一 sftp、ftp、webdav 下载文件 contentType 为 application/octet-stream, 避免浏览器自动进行默认预览动作 2022-08-01 15:20:06 +08:00
zhaojun
27db6ed14a 🐛 修复本地存储下载时,错误的 contentType 类型导致无法正常下载的问题 2022-08-01 15:17:29 +08:00
zhaojun
b079d03753 🔖 发布 4.0.7 版本 2022-07-31 15:07:11 +08:00
zhaojun
edf8e114ad 💄 更新前端页面 2022-07-31 15:06:55 +08:00
zhaojun
67a84edd4d 优化 S3 协议自动配置跨域逻辑,改为不覆盖原有配置。且增加 GET 跨域,对于在线预览文本、视频场景提供跨域支持. 2022-07-30 20:01:41 +08:00
zhaojun
d798750ee6 优化 S3 协议自动配置跨域逻辑,改为不覆盖原有配置,修改描述. 2022-07-30 20:00:18 +08:00
zhaojun
fea1da86fc 🐛 修复 minio 设置跨域上传报错的 bug 2022-07-30 18:45:19 +08:00
zhaojun
96a0c90600 ⬆️ 更新 pom 依赖,去除无用依赖,升级有漏洞的依赖,划分相关的依赖 2022-07-29 16:50:08 +08:00
赵俊
a054e82740 Merge pull request #404 from wswm152/main
解除对过期内容的引用
2022-07-29 16:19:08 +08:00
无始无名
dc0e84e1e3 解除对过期内容的引用
部分内容在jdk8以上版本中已被去除
2022-07-29 15:05:34 +08:00
zhaojun
ae31005959 🔖 发布 4.0.6 版本 2022-07-28 21:27:18 +08:00
zhaojun
0f167a304d 独立直链和短链功能 2022-07-28 21:24:38 +08:00
zhaojun
43d8143c74 💄 更新前端页面 2022-07-28 21:23:42 +08:00
zhaojun
b2fb1af99b 📝 修复本地存储文件路径描述,对于 docker 环境更容易理解。 2022-07-27 20:49:39 +08:00
zhaojun
a4f4d654a3 ✏️ 修改、完善判断隐藏文件夹方法的代码注释 2022-07-16 17:42:14 +08:00
zhaojun
04d9c24b43 📝 修改文档 docker-compose 错误部分 2022-07-15 22:52:13 +08:00
zhaojun
91a7092190 🔖 发布 4.0.5 版本 2022-07-15 20:50:35 +08:00
zhaojun
f82155f157 💄 更新前端页面 2022-07-15 20:45:29 +08:00
zhaojun
a99218dfa5 🐛 修复 4.0.4 版本更新导致的新增存储源错误的 bug, 其他版本不影响 2022-07-15 15:08:23 +08:00
zhaojun
3e9fe27890 🐛 修复本地存储,文件重命名,前后名称一致时,会无限创建子目录的 bug 2022-07-15 15:07:53 +08:00
zhaojun
7524f58928 💄 更新前端页面 2022-07-15 08:47:25 +08:00
zhaojun
6499f2d220 💄 更新前端页面 2022-07-15 08:47:11 +08:00
zhaojun
40b1f1bc2d 🔖 发布 4.0.4 版本 2022-07-14 20:28:58 +08:00
zhaojun
3bd859d705 ✏️ 删除无用代码 2022-07-14 15:03:59 +08:00
zhaojun
6156d85ba3 🐛 修复启动匿名操作失败的 BUG 2022-07-14 15:03:03 +08:00
zhaojun
e1df2f886b 🐛 修复 url 路径中带 . 访问 404 的 bug 2022-07-13 20:18:48 +08:00
zhaojun
cea42a57bb 🔖 发布 4.0.3 版本 2022-07-13 20:14:24 +08:00
zhaojun
c123d2a671 💄 更新前端页面 2022-07-13 20:13:12 +08:00
zhaojun
02e00b765b 💄 更新前端页面 2022-07-13 20:12:05 +08:00
zhaojun
1992c1f52d 📝 增加异常情况的提示信息 2022-07-13 20:11:50 +08:00
zhaojun
4eb9a721fc 🐛 修复本地存储、FTP、SFTP、WebDav 未启用私有空间,仍然校验私有签名的 bug 2022-07-13 20:10:08 +08:00
zhaojun
f1bac40af4 ✏️ 删除无用代码 2022-07-13 11:19:42 +08:00
zhaojun
0a7fb41e81 🐛 修复 readme 文档仅在首页显示的 bug. 2022-07-13 11:08:30 +08:00
zhaojun
4fd0b8d442 🐛 修复 s3 存储类型不开启私有空间时,文件路径或文件名包含中文无法直链下载的 bug 2022-07-13 11:08:05 +08:00
zhaojun
28150b0c1a 🐛 修复直链下载日志表中 ip 字段长度不足矣存储 ipv6 的 问题 2022-07-13 11:06:29 +08:00
zhaojun
a7813ff7bc 🔖 发布 4.0.2 版本 2022-07-12 19:52:56 +08:00
zhaojun
ae500ef9dc ⬇️ 移除无用依赖,减小项目体积 2022-07-12 19:52:40 +08:00
zhaojun
29d5b84df2 📝 更新文档, 更换图片预览的示例图 2022-07-12 19:49:11 +08:00
zhaojun
28882b198c 💄 更新前端页面 2022-07-12 19:48:42 +08:00
zhaojun
d624c886fe war 打包方式 ipv6 支持 2022-07-12 19:36:45 +08:00
zhaojun
e08654a59e Merge remote-tracking branch 'origin/main' 2022-07-12 19:34:53 +08:00
zhaojun
8fa6a5c9b8 💄 更新前端页面 2022-07-12 19:34:20 +08:00
zhaojun
7620a578fb 服务器代理上传支持特殊字符的文件名,如 # 2022-07-12 19:33:49 +08:00
赵俊
9c9bf93f00 Update README.md 2022-07-11 19:17:54 +08:00
赵俊
912eb694cb Update README.md 2022-07-11 19:11:08 +08:00
zhaojun
78d5ba90cf 💄 更新前端页面 2022-07-11 17:33:53 +08:00
zhaojun
056f9d4449 💄 更新前端页面 2022-07-11 17:33:37 +08:00
zhaojun
9a6020dcc8 增加文件点击习惯设置, 支持设置为单击还是双击 2022-07-11 17:29:15 +08:00
zhaojun
6b3025f379 fix(直链): 统一下载日志 storage_key 字段长度, 防止存储源长度过长后无法正常记录下载日志. 2022-07-11 14:57:30 +08:00
zhaojun
9dffaaee25 fix(异常处理): 存储源不存在时进行提示,修复后提示存储源不存在 2022-07-11 14:56:27 +08:00
zhaojun
bda654a012 fix: 修复管理员修改密码无效的 bug 2022-07-11 14:54:26 +08:00
zhaojun
52271b5117 fix: 修复目录文档某些情况无法匹配成功的 bug 2022-07-11 14:53:44 +08:00
zhaojun
223a6c1970 去除无用的存储类型枚举 2022-07-11 14:53:04 +08:00
zhaojun
60c272b714 修复 SharePoint 无法直链下载的 bug 2022-07-11 14:52:45 +08:00
zhaojun
3b6ed1c78d fix: 修改数据库和日志等文件默认路径为新版路径 2022-07-10 21:56:43 +08:00
zhaojun
5d888bb68f 🔖 发布 4.0.0 版本 2022-07-10 21:44:14 +08:00
zhaojun
082bb07213 fix: 修复 webdav 账号密码错误问题 2022-06-16 20:11:54 +08:00
zhaojun
803b8cdf71 🔖 发布 3.2.5 版本 2022-05-16 21:41:59 +08:00
zhaojun
eca5f7e48b feature: 新增挂载 webdav client pom 依赖 2022-05-16 21:41:36 +08:00
zhaojun
b70c37f3f0 feature: 新增挂载 webdav 功能 2022-05-16 21:40:58 +08:00
zhaojun
5cb2844141 🐛 修复错误的 content_disposition 和 contentType 导致下载文件格式被浏览器识别错误 2022-05-16 20:50:49 +08:00
赵俊
4dd6cdb4b3 合并拉取请求 #348
fix: modify extract driveId regex in WebDavController
2022-05-03 21:16:40 +08:00
quericy
c6127c029f fix: modify extract driveId regex in WebDavController 2022-04-14 10:06:46 +08:00
zhaojun
e149039ecb 🔖 发布 3.2.3 版本 2022-04-13 00:03:49 +08:00
zhaojun
37688d83cf feature: 降低 webdav 日志级别 2022-04-13 00:03:28 +08:00
zhaojun
6692016642 feature: 新增 webdav 开关功能 2022-04-13 00:01:59 +08:00
zhaojun
3f41aeda9a fix(bug): 修复因字段名称修改升级后导致短链异常 bug 2022-04-12 23:38:49 +08:00
赵俊
de86d5c47d 合并拉取请求 #347
feat: support webdav
2022-04-11 09:56:11 +08:00
quericy
c89e072005 feat: support webdav
1, feat:config webDav and milton in configuration;
2, feat:add event handler in WebDavController;
3, feat:check auth by admin in systemConfig;
4, feat:download webDav file by redirect to site DirectLink;
2022-04-10 04:11:34 +08:00
zhaojun
b533b5e959 🔖 发布 3.2.2 版本 2022-04-02 18:50:50 +08:00
zhaojun
d35cf27d47 fix(bug): ftp 默认为被动模式,防止因端口不同,无法读取。 2022-04-02 18:50:26 +08:00
zhaojun
796c4c1fb0 页面更新 2022-04-02 18:49:26 +08:00
zhaojun
1033d6c1a9 fix(bug): 修复某些情况下直链无法正常使用的 BUG 2022-04-02 18:36:44 +08:00
zhaojun
e09c6b4e58 fix(bug): 修复又拍云文件中包含特殊字符时,调用 upyun 接口未进行 url encode 返回 400,影响又拍云单文件直链无法使用。 2022-04-02 18:26:32 +08:00
zhaojun
168b0b08f3 fix(bug): 修复本地存储可以通过特殊路径符获取任意目录的 BUG. 2022-04-02 18:24:49 +08:00
zhaojun
60a6a5348c fix(bug): 修复获取音频信息时,因文件链接 302, 导致无法正常获取音频文件信息的 BUG. 2022-04-02 18:24:00 +08:00
zhaojun
2ec8a5df1f fix(bug): 修复 sharepoint 未自动刷新 accessToken 和 refreshToken 的 bug 2022-02-02 20:50:13 +08:00
zhaojun
47b5f6ac12 🔖 发布 3.2.1 版本 2022-02-02 20:46:45 +08:00
zhaojun
c64c8465f2 fix(bug): 升级相关有漏洞的依赖版本为最新版 2022-02-02 20:46:08 +08:00
zhaojun
f636681dd8 fix(bug): 修复本地存储可通过特殊命令访问到非指定目录的文件 2022-02-02 20:45:05 +08:00
赵俊
eadd2434e0 新增 ZFILE 社区地址 2021-10-30 21:05:11 +08:00
赵俊
b84c0bff42 Merge remote-tracking branch 'origin/master' 2021-10-07 17:53:19 +08:00
赵俊
4cb5b84bfe 🐛 修复 S3 协议无法正常保存的 BUG 2021-10-07 17:52:45 +08:00
赵俊
0351b4401c Merge pull request #286 from pippen57/master
转义SQL关键字, 防止创建表时出现错误
2021-09-25 18:29:42 +08:00
pippen
48cb14be8a 转义SQL关键字, 防止创建表时出现错误 2021-09-25 12:48:31 +08:00
赵俊
eea2ff11f9 ✏️ 修改文档拼写错误 2021-09-21 11:22:00 +08:00
赵俊
325ec1a348 对于因前端 vue history 路由模式下, 直接访问路径时, 出现的 404 响应码, 转化为 200 响应码. (404 下会将请求转发到首页) 2021-09-20 17:06:27 +08:00
赵俊
93205266d3 更新文档 2021-09-20 16:35:30 +08:00
赵俊
6849a4347f 去除自定义 404 错误页面 2021-09-20 16:33:54 +08:00
赵俊
cb5c6a5945 🎨 提交前端页面 2021-09-20 16:33:31 +08:00
赵俊
5ed45c3bb3 🔒 修复因升级 springboot 版本导致的安全和兼容问题 2021-09-20 16:20:25 +08:00
赵俊
4442ec3165 修复因前端 vue history 路由模式下, 直接访问路径时, 请求 /admin/ 下的路由会被权限框架拦截并校验登陆状态, 导致静态页面报错问题. 2021-09-20 16:19:21 +08:00
赵俊
b268a24333 修复因前端 vue history 路由模式下, 直接访问路径时, 接口和路径名冲突问题 2021-09-20 16:17:59 +08:00
赵俊
84f9354d4e 对于因前端 vue history 路由模式下, 直接访问路径时, 出现的 404 响应码, 转化为 200 响应码. (404 下会将请求转发到首页) 2021-09-20 16:17:16 +08:00
赵俊
8dfc4f8004 跨域请求过滤器修改为自注解注册方式 2021-09-20 16:14:45 +08:00
赵俊
1158f5c2b9 🐛 修复 OneDrive 加速域名在直链下未生效的 BUG 2021-09-20 11:18:13 +08:00
赵俊
c5f0e15207 🐛 修复因升级 spring boot 后部分参数过时, 导致的第一次启动无法初始化参数的 BUG 2021-09-19 21:33:07 +08:00
赵俊
68a842ce75 🐛 修复页面路径和接口地址冲突的 BUG 2021-09-19 16:08:19 +08:00
赵俊
3bd4f74dae 🔖 发布 3.2 版本 2021-09-19 15:37:40 +08:00
赵俊
4a0bdc3baf 💬 修改文档和启动日志描述,取消 /#/ 提示 2021-09-19 15:37:10 +08:00
赵俊
603c1b4654 🎨 提交前端页面 2021-09-19 15:36:19 +08:00
赵俊
1136d582df 增加异常处理器,细化异常提示 2021-09-19 14:10:09 +08:00
赵俊
187544fc06 当密码文件无法正常打开时,给予友好提示 2021-09-19 14:09:52 +08:00
赵俊
aa3cde8f59 minio 和 s3 存储引擎支持自定义 region 功能 2021-09-19 11:30:05 +08:00
赵俊
e29a702b6e ✏️ 拼写错误修改 2021-09-19 11:28:40 +08:00
赵俊
e4f663c9f0 🔒 新增自动检测驱动器已存储参数和驱动器支持所有参数比对功能, 防止驱动器新增参数后, 系统已存在的驱动器因 NPE 问题无法正常加载的问题. 2021-09-19 11:27:57 +08:00
赵俊
fb08ef6e78 🐛 如果请求的密码文件链接是 http 的, 且会自动跳转到 https 时, 密码文件无法正常加载的 BUG (原因是 restTemplate http -> https 不会自动重定向) 2021-09-19 10:01:24 +08:00
赵俊
10c465d159 ♻️ 修改 Spring Security 为 Sa-Token 框架. 2021-09-19 09:57:09 +08:00
赵俊
d15d1203b7 跨域配置 2021-09-19 09:56:12 +08:00
赵俊
de48ed3b61 兼容前端 history 模式, 对于 404 请求, 指向到 /index.html 页面 2021-09-19 09:54:23 +08:00
赵俊
d22e2e872a ⬆️ 因旧版本 Spring 的安全问题, 升级 Spring 为 2.5.4 版本, 兼容新版本 2021-09-19 09:53:19 +08:00
赵俊
7da1b454dc 🐛 修复非个人版的 OneDrive CF 加速域名无法正常使用的 BUG 2021-09-18 22:22:50 +08:00
赵俊
463f311dd3 增加本地文件下载/预览行为控制 2021-07-13 21:43:25 +08:00
赵俊
73b42cf654 🔖 发布 3.1 版本 2021-05-30 17:43:37 +08:00
赵俊
1fac59c4cd 🎨 更新静态页面 2021-05-30 17:41:46 +08:00
赵俊
6ed6b4a019 驱动器添加时,默认 ID 修改为 1 2021-05-30 17:40:48 +08:00
赵俊
7bd02437f0 数据库初始化参数增加默认值 2021-05-30 17:40:14 +08:00
赵俊
57aeb5771c 驱动器列表中返回是否已初始化信息 2021-05-30 17:39:40 +08:00
赵俊
695c03a530 Merge remote-tracking branch 'origin/master' 2021-05-30 16:24:58 +08:00
赵俊
6ebc403572 🔧 添加 ZFile Banner 文件 2021-05-30 16:24:28 +08:00
赵俊
7ff6fe43b5 新增自定义直链名称功能功能 2021-05-30 16:23:41 +08:00
赵俊
b34f141181 🔥 移除无用代码 2021-05-30 16:21:11 +08:00
赵俊
87dd7b58d1 🔥 移除无用代码 2021-05-30 16:19:11 +08:00
赵俊
2a949db5d2 m3u8 响应头兼容部分浏览器直接解析 m3u8 文件 2021-05-30 15:53:55 +08:00
赵俊
b889e91fb5 📝 更新文档,更新赞助信息位置 2021-05-14 21:15:54 +08:00
赵俊
b5c757f9f0 📝 更新文档,更新赞助信息位置 2021-04-21 21:04:02 +08:00
赵俊
a465f48b94 📝 更新文档,增加项目 LOGO,修改项目描述信息。 2021-04-21 20:23:54 +08:00
赵俊
797a0a0e4c 新增直链和短链是否显示功能 2021-04-21 20:21:47 +08:00
赵俊
e7ff159b6d 🐛 修复某些下载地址带密钥的存储策略,m3u8 视频无法正常播放的功能 2021-04-11 19:15:50 +08:00
赵俊
a9fbf54bb2 🔊 完善日志输出 2021-04-11 16:02:49 +08:00
赵俊
81f9e262f5 增加获取站点域名方法 2021-04-11 16:00:01 +08:00
赵俊
23bb3960ab 已关闭的驱动器不允许下载 2021-04-11 15:56:08 +08:00
赵俊
c4a17985a4 优化下载地址获取逻辑,直接列表时不直接调用 API 获取下载地址,访问单文件时再调用。 2021-04-11 15:49:33 +08:00
赵俊
75ddcd47f4 :zip: 优化存储器保存逻辑,防止新增加的字段无法正常保存的 BUG。 2021-04-10 20:21:19 +08:00
赵俊
2dd03ae490 🐛 修复 FTP 模式在 Linux 环境下无法正常读取的 BUG 2021-04-10 20:20:03 +08:00
赵俊
5b383c8741 🐛 日志文件无法正常下载功能修复 2021-04-10 20:19:26 +08:00
赵俊
73198d7852 优化本地文件下载功能, 支持断点续传和多线程下载 2021-04-10 18:06:10 +08:00
赵俊
fb0d9721aa OneDrive 和 SharePoint 反代功能 2021-04-10 15:38:16 +08:00
赵俊
b24c663fd6 更新 star 趋势链接 2021-03-27 12:01:37 +08:00
zhaojun1998
6e62cfc84d 📝 更新文档,修改赞助二维码,增加服务器赞助商链接 2021-03-27 11:52:59 +08:00
zhaojun1998
eee22e9dc9 📝 更新文档 2021-03-26 18:24:02 +08:00
zhaojun1998
5109c51ffc 🔖 发布 3.0 版 2021-03-10 21:43:14 +08:00
zhaojun1998
66d6d311ea 🎨 更新静态页面 2021-03-10 21:42:57 +08:00
zhaojun1998
ef7cbdcbd7 🐛 修复旧版本时,直链多次创建后,根据条件查询时数据不唯一的 BUG 2021-03-10 21:42:36 +08:00
zhaojun1998
63bcbebb4b OneDrive 回调地址增加旧版本兼容 2021-03-10 21:41:42 +08:00
zhaojun1998
50ce1bb6db 修改直链 Key 时检测是否重复功能 2021-03-10 21:41:14 +08:00
zhaojun1998
3c88659679 🎨 更新静态页面 2021-03-09 21:14:42 +08:00
zhaojun1998
d79a993eea 新增设置默认打开图片模式功能 2021-03-09 21:14:21 +08:00
zhaojun1998
afafb311b8 合并文件列表和参数信息接口 2021-03-09 21:13:48 +08:00
zhaojun1998
2e280e4931 新增批量删除直链功能,修改直链 Key 功能 2021-03-08 21:12:42 +08:00
zhaojun1998
ed6efac8b7 🐛 修复切换存储策略时,存储参数值匹配错误的 BUG 2021-03-05 23:00:15 +08:00
zhaojun1998
7409df85d7 🐛 修复切换存储器 ID 时,为级联修改或清理缓存、过滤器、短链的问题 2021-03-05 22:59:08 +08:00
zhaojun1998
4d42529c4d 短链生成逻辑优化, 同链接不重复生成 2021-03-05 22:24:28 +08:00
zhaojun1998
65224685c8 短链生成逻辑优化, 同链接不重复生成 2021-03-05 22:23:41 +08:00
zhaojun1998
080a84986e 💩 优化代码, 添加注释, 优化方法命名, 2021-02-24 18:42:17 +08:00
zhaojun1998
3f8beb2f0b debug 模式优化, 增加到系统参数接口中 2021-02-24 18:40:10 +08:00
zhaojun1998
410a87c426 debug 模式优化, 增加到系统参数接口中 2021-02-24 18:39:47 +08:00
zhaojun1998
c14b8343d2 文件解析异常提示信息优化 2021-02-08 17:58:37 +08:00
zhaojun1998
bea440f6c3 文件解析异常提示信息优化 2021-02-08 17:50:25 +08:00
zhaojun1998
6a5fe15121 密码错误提示信息优化 2021-02-08 17:46:29 +08:00
zhaojun1998
e920ab0ec0 🐛 修复因缓存功能导致密码文件失效的 BUG 2021-02-08 17:46:04 +08:00
zhaojun1998
537e3e0563 🐛 修复 S3 协议新增时的 BUG 2021-02-03 22:01:56 +08:00
赵俊
e208dc7c4c Merge pull request #169 from tianyanli/master
修复S3协议没有指定region问题
2021-02-03 21:56:40 +08:00
zhaojun1998
ed64910a53 🔇 无效的驱动器不输出异常信息,仅返回给页面错误消息 2021-02-03 21:06:38 +08:00
zhaojun1998
5b075c3505 完善 SharePoint 支持 2021-02-03 21:05:46 +08:00
zhaojun1998
a8e6d9af6a 🐛 修复某些存储引擎 API 中文件夹不返回时间和大小时,排序的 NPE 问题。 2021-02-03 21:04:28 +08:00
zhaojun1998
2b21d8a73c 完善 SharePoint 自助获取 SiteId 的逻辑 2021-02-03 21:03:28 +08:00
zhaojun1998
e30289d21b 🔖 发布 2.9.0 版 2021-02-01 23:15:29 +08:00
zhaojun1998
3b6e2be7fe 新增 debug 模式, 可访问数据库控制台和重置密码 2021-02-01 23:14:29 +08:00
zhaojun1998
43c12aa8a7 🎨 更新静态页面 2021-02-01 23:13:29 +08:00
zhaojun1998
c03a7710c0 🎨 更新静态页面 2021-02-01 23:13:18 +08:00
zhaojun1998
1833b23d84 新增 sharepoint 及自助获取 siteId 功能 2021-02-01 23:11:49 +08:00
zhaojun1998
f3e393972d Merge remote-tracking branch 'origin/master' 2021-01-23 23:33:21 +08:00
zhaojun1998
4f46c13369 完善异常处理机制,新增异常类 2021-01-23 13:48:04 +08:00
zhaojun1998
f181959218 支持 SharePoint 2021-01-23 13:46:31 +08:00
zhaojun1998
11effc0ae7 支持自定义 client_id 和 client_secret 2021-01-23 13:45:26 +08:00
zhaojun1998
c8397e17bf 💩 优化代码, 抽取公共方法 2021-01-23 13:44:33 +08:00
zhaojun1998
ed32b9f1d4 限制直链不允许下载密码文件 2021-01-23 13:42:53 +08:00
zhaojun1998
4e184936db 💩 优化代码, 更新注释格式, 去除无用 import 2021-01-23 13:42:14 +08:00
zhaojun1998
fe6aebfdee 自带直链功能 2021-01-23 13:40:16 +08:00
zhaojun1998
d65e1a442d 🔥 移除系统检测功能 2021-01-23 13:36:56 +08:00
zhaojun1998
34647793c8 支持修改驱动器 ID 2021-01-23 13:35:52 +08:00
zhaojun1998
e8c249b9ea 🔥 移除系统检测功能 2021-01-23 13:22:41 +08:00
yanli
d1e613dc10 Update S3ServiceImpl.java 2020-12-17 17:26:55 +08:00
赵俊
1adcfee96f Update README.md 2020-10-07 17:19:26 +08:00
zhaojun1998
75f5de6b9a 🔖 发布 2.8.1 版 2020-08-18 20:43:07 +08:00
zhaojun1998
499942ef70 🎨 更新静态页面 2020-08-18 20:43:02 +08:00
674 changed files with 29655 additions and 7405 deletions

View File

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

13
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: 'Blank Issue'
description: 请使用 https://issue.zfile.vip 创建新的问题.
body:
- type: markdown
attributes:
value: |
**注意:**
不要通过此页面创建问题, 请使用 https://issue.zfile.vip 创建新的问题.
如果不是通过此链接创建的问题, 将会被直接关闭.
- type: textarea
id: add-a-description
attributes:
label: Add a description

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: 创建 Issue
url: https://issue.zfile.vip/
about: 未通过 https://issue.zfile.vip/ 创建的问题可能会被立即关闭。

View File

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

View File

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

17
.gitignore vendored
View File

@@ -1,11 +1,8 @@
HELP.md
target/
.mvn/wrapper/**
!**/src/main/**
**/src/test/**
mvnw
mvnw.cmd
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
@@ -21,6 +18,8 @@ mvnw.cmd
*.iws
*.iml
*.ipr
.fastRequest
.murphy.yml
### NetBeans ###
/nbproject/private/
@@ -29,6 +28,12 @@ mvnw.cmd
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
/.mvn/wrapper/
/mvnw
/mvnw.cmd
/result/

2
.package/script/log.sh Normal file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
tail -fn100 ~/.zfile-v4/logs/zfile.log

View File

@@ -0,0 +1,6 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
$DIR/stop.sh
$DIR/start.sh

22
.package/script/start.sh Normal file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
# 检测是否已启动
pid=`ps -ef | grep -n zfile | grep -v grep | grep -v launch | grep -v .sh | awk '{print $2}'`
if [ -n "${pid}" ]
then
echo "已运行在 pid${pid},无需重复启动!"
exit 0
fi
# 获取当前脚本所在路径
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
ZFILE_DIR=$(dirname "$DIR")
# 启动 zfile
nohup $ZFILE_DIR/zfile/zfile --spring.config.location=$ZFILE_DIR/application.properties --spring.web.resources.static-locations=file:$ZFILE_DIR/static/ >/dev/null 2>&1 &
echo '启动中...'
sleep 3s
# 输出 pid
pid=`ps -ef | grep -n zfile | grep -v grep | grep -v .sh | awk '{print $2}'`
echo "目前 PID 为: ${pid}"

12
.package/script/status.sh Normal file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
echo "------------------ 检测状态 START --------------"
pid=`ps -ef | grep -n zfile | grep -v grep | grep -v launch | grep -v .sh | awk '{print $2}'`
if [ -z "${pid}" ]
then
echo "未运行, 无需停止!"
else
echo "运行pid${pid}"
fi
echo "------------------ 检测状态 END --------------"

14
.package/script/stop.sh Normal file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
echo "------------------ 检测状态 START --------------"
pid=`ps -ef | grep -n zfile | grep -v grep | grep -v .sh | awk '{print $2}'`
if [ -z "${pid}" ]
then
echo "未运行, 无需停止!"
else
echo "运行pid${pid}"
kill -9 ${pid}
echo "已停止进程: ${pid}"
fi
echo "------------------ 检测状态 END --------------"

Binary file not shown.

View File

@@ -0,0 +1,7 @@
@echo off
if not exist %windir%\system32\cmd.exe (
"%CD%\zfile\zfile.exe"
) else (
cmd /k "%CD%\zfile\zfile.exe"
exit
)

181
API.md
View File

@@ -1,181 +0,0 @@
## API 标准
所有 API 均返回 `msg`, `code`, `data` 三个属性.
| code | 描述 |
| :---: | :------------: |
| 0 | 请求成功 |
| -1 | 请求失败 |
| -2 | 文件夹需要密码 |
`code == 0` 时, `data` 中为请求所需数据.
`code != 0` 时, 应当将 `msg` 中的内容作为参考值.
## 驱动器列表
### 请求 URL
`/api/drive/list` `GET`
### 响应
```json
{
"msg": "操作成功",
"code": 0,
"data": [
{
"id": 3, --- ID 是驱动器 ID, 用来唯一区分驱动器
"name": "演示 A 盘", --- 驱动器名称
"enableCache": true, --- 是否开启了缓存
"autoRefreshCache": false, --- 是否开启了缓存自动刷新
"type": { --- 存储源类型
"key": "upyun",
"description": "又拍云 USS"
},
"searchEnable": false, --- 是否开启搜索
"searchIgnoreCase": false, --- 搜索是否忽略大小写
"searchContainEncryptedFile": false --- 搜索是否包含加密文件夹
}
]
}
```
## 获取文件列表
### 请求 URL
`/api/list/{driveId}` `GET`
### URL 参数
| 参数名 | 描述 | 是否必填 | 参考值 |
| :-----: | :-------------------: | :------: | :------------------------------------: |
| driveId | 驱动器 ID | 是 | 参考 `获取驱动器列表` 接口返回的 id 值 |
### 请求参数
| 参数名 | 描述 | 是否必填 | 参考值 |
| :------: | :--------: | :------: | :--------------------------: |
| path | 路径 | 是 | `/`, `/文件夹名称` |
| password | 文件夹密码 | 否 | 当文件夹需要密码时, |
| page | 页数 | 否 | 默认取第一页, 每页固定 30 条 |
### 响应
```json
{
"msg": "操作成功",
"code": 0,
"data": [
{
"name": "密码文件夹",
"time": "2020-01-28 13:17",
"size": 4096,
"type": "FOLDER",
"path": "/",
"url": null
},
{
"name": "新建 文本文档.txt",
"time": "2020-01-28 13:16",
"size": 3,
"type": "FILE",
"path": "/",
"url": "http://127.0.0.1:8080/file/新建 文本文档.txt"
}
]
}
```
## 获取单个文件信息
### 请求 URL
`/api/directlink/{driveId}` `GET`
### URL 参数
| 参数名 | 描述 | 是否必填 | 参考值 |
| :-----: | :-------------------: | :------: | :------------------------------------: |
| driveId | 驱动器 ID | 是 | 参考 `获取驱动器列表` 接口返回的 id 值 |
### 参数
| 参数名 | 描述 | 是否必填 | 参考值 |
| :----: | :--------: | :------: | :------------------: |
| path | 文件全路径 | 是 | `/新建 文本文档.txt` |
### 响应
```json
{
"msg": "操作成功",
"code": 0,
"data": {
"name": "新建 文本文档.txt",
"time": "2020-01-28 13:16",
"size": 3,
"type": "FILE",
"path": "d:/test",
"url": "http://127.0.0.1:8080/file/新建 文本文档.txt"
}
}
```
## 获取系统配置
### 请求 URL
`/api/config/{driveId}` `GET`
### URL 参数
| 参数名 | 描述 | 是否必填 | 参考值 |
| :-----: | :-------------------: | :------: | :------------------------------------: |
| driveId | 驱动器 ID | 是 | 参考 `获取驱动器列表` 接口返回的 id 值 |
### 参数
| 参数名 | 描述 | 是否必填 | 参考值 |
| :----: | :--------: | :------: | :-----------: |
| path | 文件夹名称 | 是 | `/文件夹名称` |
### 响应
```json
{
"msg": "操作成功",
"code": 0,
"data": {
"siteName": "ZFile 演示站",
"searchEnable": false,
"username": "zhao",
"domain": "https://zfile.jun6.net",
"customJs": "",
"customCss": "",
"tableSize": "small",
"showOperator": true,
"showDocument": true,
"announcement": "本站是 ZFile 演示站,交流反馈群 180605017",
"showAnnouncement": true,
"layout": "full",
"readme": null
}
}
```

21
Dockerfile Normal file
View File

@@ -0,0 +1,21 @@
FROM debian:10-slim
ARG TARGETARCH
WORKDIR /root
EXPOSE 8080
RUN apt update -y && apt install --no-install-recommends fontconfig zstd -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY zfile-artifacts/zfile-linux-${TARGETARCH}/zfile/* /root/
COPY zfile-artifacts/zfile-linux-${TARGETARCH}/static/ /root/static/
COPY zfile-artifacts/zfile-linux-${TARGETARCH}/application.properties /root/
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
# 设置编码为 UTF-8
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
CMD if [ -f /root/zfile.zst ]; then zstd --no-progress -d /root/zfile.zst && rm -rf /root/zfile.zst && chmod +x /root/zfile && /root/zfile --spring.config.location=file:/root/application.properties; else chmod +x /root/zfile && /root/zfile --spring.config.location=file:/root/application.properties; fi

198
README.md
View File

@@ -1,144 +1,90 @@
# Z-File
![https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square](https://img.shields.io/badge/license-MIT-blue.svg?longCache=true&style=flat-square)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/70b793267f7941d58cbd93f50c9a8e0a)](https://www.codacy.com/manual/zhaojun1998/zfile?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=zhaojun1998/zfile&amp;utm_campaign=Badge_Grade)
![https://img.shields.io/badge/springboot-2.0.6-orange.svg?style=flat-square](https://img.shields.io/badge/springboot-2.0.6-yellow.svg?longCache=true&style=popout-square)
![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/zhaojun1998/zfile.svg?style=flat-square)
此项目是一个在线文件目录的程序, 支持各种对象存储和本地存储, 使用定位是个人放常用工具下载, 或做公共的文件库. 不会向多账户方向开发.
前端基于 [h5ai](https://larsjung.de/h5ai/) 的原有功能使用 Vue 重新开发了一遍. 后端采用 SpringBoot, 数据库采用内嵌数据库.
预览地址: [https://zfile.jun6.net](https://zfile.jun6.net)
文档地址: [http://docs.zhaojun.im/zfile](http://docs.zhaojun.im/zfile)
<div align="center">
<a href="https://zfile.vip" target="_blank" rel="noopener noreferrer">
<img style="margin: auto; width: 100px; display: block" src="/img/logo-zfile.png" alt="ZFile" />
</a>
<p>ZFile 是一个适用于个人或小团队的在线网盘程序,可以将多种存储类型统一管理,再也不用登录各种网站管理文件,现在你只需要在 ZFile 中畅快使用!</p>
<div>
<img alt="last commit" src="https://shields.io/github/last-commit/zhaojun1998/zfile.svg?style=flat-square"/>
<img alt="downloads" src="https://shields.io/github/downloads/zhaojun1998/zfile/total?style=flat-square"/>
<img alt="release version" src="https://shields.io/github/v/release/zhaojun1998/zfile?style=flat-square"/>
<img alt="commit activity" src="https://shields.io/github/commit-activity/y/zhaojun1998/zfile?style=flat-square"/>
<img alt="open issues" src="https://shields.io/github/issues/zhaojun1998/zfile?style=flat-square"/>
<img alt="closed issues" src="https://shields.io/github/issues-closed-raw/zhaojun1998/zfile?style=flat-square"/>
<img alt="forks" src="https://shields.io/github/forks/zhaojun1998/zfile?style=flat-square"/>
<img alt="stars" src="https://shields.io/github/stars/zhaojun1998/zfile?style=flat-square"/>
<img alt="watchers" src="https://shields.io/github/watchers/zhaojun1998/zfile?style=flat-square"/>
</div>
<span>
<a href="https://zfile.vip">官网</a>
<span> | </span>
<a href="https://docs.zfile.vip">文档</a>
<span> | </span>
<a href="https://demo.zfile.vip">预览地址</a>
</span>
</div>
## 系统特色
* 内存缓存 (免安装)
* 内存数据库 (免安装)
* 个性化配置
* 自定义目录的 readme 说明文件
* 自定义 JS, CSS
* 文件夹密码
* 支持在线浏览文本文件, 视频, 图片, 音乐. (支持 FLV 和 HLS)
* 文件/目录二维码
* 缓存动态开启, ~~缓存自动刷新 (v2.2 及以前版本支持)~~
* ~~全局搜索 (v2.2 及以前版本支持)~~
* 同时挂载多个存储策略
* 支持 阿里云 OSS, FTP, 华为云 OBS, 本地存储, MINIO, OneDrive 国际/家庭/个人版, OneDrive 世纪互联版, 七牛云 KODO, 腾讯云 COS, 又拍云 USS.
- Docker、Docker Compose 支持(amd64, arm64)。
- 支持对文件生成直链、短链(可设过期时间)。
- 响应式设计,支持手机、平板、电脑等多种设备访问。
- 支持多用户功能,可分配给指定用户指定存储源或目录。
- 支持在线浏览图片、播放音视频文本文件、Office、Obj3d等文件类型。
- 支持对接 S3、OneDrive、SharePoint、Google Drive、多吉云、又拍云、本地存储、FTP、SFTP 等存储源。
- 支持常用快捷键,`Ctrl + A` 全选,`Ctrl + 左键` 多选,`Shift + 左键` 范围选择,`Esc` 取消全选等。
- 支持限速下载(捐赠版)
- 支持限制指定用户可查看、上传的文件类型(捐赠版)
## 快速开始
安装依赖环境:
一键脚本安装:
```bash
# CentOS系统
yum install -y java-1.8.0-openjdk unzip
curl -sSL https://docs.zfile.vip/install.sh -o install.sh && chmod +x install.sh && ./install.sh
```
```bash
# Debian 9 / Ubuntu 14+
apt update
apt install -y openjdk-8-jre-headless unzip
```
```bash
# Debian 10 (Buster) 系统
apt update && apt install -y apt-transport-https software-properties-common ca-certificates dirmngr gnupg
wget -qO - https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public | apt-key add -
add-apt-repository --yes https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/
apt update && apt install -y adoptopenjdk-8-hotspot-jre
```
更多安装方式请参考 [安装文档](https://docs.zfile.vip/install/)
> 如为更新程序, 则请先执行 `~/zfile/bin/stop.sh && rm -rf ~/zfile` 清理旧程序. 首次安装请忽略此选项.
## 功能预览
下载项目:
```bash
cd ~
wget https://c.jun6.net/ZFILE/zfile-release.war
mkdir zfile && unzip zfile-release.war -d zfile && rm -rf zfile-release.war
chmod +x zfile/bin/*.sh
```
> 下载指定版本可以将 `zfile-release.war` 改为 `zfile-x.x.war`,如 `zfile-2.2.war`。
程序的目录结构为:
```
├── zfile
├── META-INF
├── WEB-INF
└── bin
├── start.sh # 启动脚本
└── stop.sh # 停止脚本
├── restart.sh # 重启脚本
```
启动项目:
```bash
~/zfile/bin/start.sh
```
篇幅有限, 更详细的安装教程及介绍请参考: [ZFile 文档](http://zhaojun.im/zfile-install)
访问地址:
用户前台: http://127.0.0.1:8080/#/main
初始安装: http://127.0.0.1:8080/#/install
管理后台: http://127.0.0.1:8080/#/admin
## 预览
![前台首页](https://cdn.jun6.net/2020/04/19/d590d2bde13bb.png)
![后台设置-驱动器设置](https://cdn.jun6.net/2020/04/19/d58fc2debcce8.png)
![后台设置-驱动器设置](https://cdn.jun6.net/2020/04/19/0f321e47fc18c.png)
![后台设置-显示设置](https://cdn.jun6.net/2020/04/19/6d7c300b89671.png)
## 常见问题
### 默认路径
默认 H2 数据库文件地址: `~/.zfile/db/`, `~` 表示用户目录
windows 为 `C:/Users/用户名/`
linux 为 `/home/用户名/`, root 用户为 `/root/`
> 2.3 及以后版本路径为 `~/.zfile-new/db/`
### 文档文件和加密文件
- 目录文档显示文件名为 `readme.md`
- 目录需要密码访问, 添加文件 `password.txt` (无法拦截此文件被下载, 但可以改名文件)
## 开发计划
- [x] API 支持 [点击查看文档](https://github.com/zhaojun1998/zfile/blob/master/API.md)
- [x] 更方便的部署方式
- [x] 布局优化 - 自定义操作按钮 (现为右键实现)
- [x] 后台优化 - 设置按照其功能进行分离
- [x] 体验优化 - 支持前后端分离部署
- [x] 体验优化 - 文本预览更换 vscode 同款编辑器 monaco editor
- [x] 新功能 - Docker 支持
- [x] 架构调整 - 支持多存储策略
- [ ] 新功能 - 后台支持上传、编辑、删除等操作
- [ ] 新功能 - WebDav 支持
- [ ] 新功能 - 离线下载 (aria2)
- [ ] 体验优化 - 忽略文件列表 (正则表达式)
- [ ] 体验优化 - 自定义支持预览的文件后缀 (正则表达式)
- [ ] 体验优化 - 一键安装脚本
### 文件列表
![文件列表](/img/file-list.png)
### 画廊模式
![图片预览](/img/gallery.png)
### 视频预览
![视频预览](/img/preview-video.png)
### 文本预览
![文本预览](/img/preview-text.png)
### 音频预览
![音频预览](/img/preview-audio.png)
### PDF 预览
![PDF 预览](/img/preview-pdf.png)
### Office 预览
![Office 预览](/img/preview-office.png)
### 3d 文件预览
![3d 文件预览](/img/preview-3d.png)
### 生成直链
![生成直链](/img/generate-link.jpeg)
### 页面设置
![页面设置](/img/page-setting.png)
### 后台设置-登录
![后台设置-登录](/img/login.png)
### 后台设置-存储源列表
![后台设置-存储源列表](/img/storage-list.png)
### 后台设置-添加存储源(本地存储)
![后台设置-添加存储源(本地存储)](/img/storage-edit-local.png)
### 后台设置-用户管理
![后台设置-存储源权限控制](/img/user-edit.png)
### 后台设置-显示设置
![后台设置-显示设置](/img/view-setting.png)
## 支持作者
如果本项目对你有帮助,请作者喝杯咖啡吧。
<img src="http://cdn.jun6.net/alipay.png" width="200" height="312">
<img src="http://cdn.jun6.net/wechat.png" width="222" height="300">
<img src="https://cdn.jun6.net/2021/03/27/152704e91f13d.png" width="400" alt="赞助我">
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=zfile-dev/zfile&type=Date)](https://star-history.com/#zfile-dev/zfile&Date)

BIN
img/file-list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
img/gallery.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 KiB

BIN
img/generate-link.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

BIN
img/login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
img/logo-zfile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
img/page-setting.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

BIN
img/preview-3d.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

BIN
img/preview-audio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
img/preview-office.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
img/preview-pdf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

BIN
img/preview-text.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

BIN
img/preview-video.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 KiB

BIN
img/storage-edit-local.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 KiB

BIN
img/storage-list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
img/user-edit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

BIN
img/view-setting.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

363
pom.xml
View File

@@ -1,29 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>im.zhaojun</groupId>
<artifactId>zfile</artifactId>
<version>4.2.0</version>
<name>zfile</name>
<packaging>jar</packaging>
<description>一个在线的文件浏览系统</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/>
<version>3.3.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>im.zhaojun</groupId>
<artifactId>zfile</artifactId>
<version>2.8</version>
<name>zfile</name>
<packaging>war</packaging>
<description>一个在线的文件浏览系统</description>
<properties>
<java.version>1.8</java.version>
<skipTests>true</skipTests>
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
<snakeyaml.version>2.0</snakeyaml.version>
<jackson-bom.version>2.14.1</jackson-bom.version>
<sqlite-jdbc.version>3.46.0.1</sqlite-jdbc.version>
<flyway.version>10.12.0</flyway.version>
<lombok.version>1.18.32</lombok.version>
</properties>
<dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.24.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- spring boot 官方相关 -->
<dependencies>
<!-- spring boot 官方相关-->
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>24.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@@ -32,71 +64,140 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 数据库驱动-->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 数据库相关 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>${flyway.version}</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
<version>${flyway.version}</version>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.3</version>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.6</version>
</dependency>
<!-- 存储策略相关 API, 对象存储、FTP、 Rest API-->
<dependency>
<groupId>com.upyun</groupId>
<artifactId>java-sdk</artifactId>
<version>4.1.3</version>
<version>4.2.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.awssdk/s3 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.11.699</version>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>7.12.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.8</version>
<groupId>com.github.mwiede</groupId>
<artifactId>jsch</artifactId>
<version>0.2.20</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
<groupId>com.github.lookfirst</groupId>
<artifactId>sardine</artifactId>
<version>5.12</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.35.2</version>
</dependency>
<!-- 登陆/权限相关 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.38.0</version>
</dependency>
<!-- 文档相关 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
<!-- 工具类 -->
<!-- <dependency>-->
<!-- <groupId>com.hierynomus</groupId>-->
<!-- <artifactId>sshj</artifactId>-->
<!-- <version>0.38.0</version>-->
<!-- </dependency>-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.28</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
<exclusions>
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.26.2</version>
<scope>compile</scope>
</dependency>
<!-- 其他工具类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@@ -104,15 +205,89 @@
</dependency>
<dependency>
<groupId>com.mpatric</groupId>
<artifactId>mp3agic</artifactId>
<version>0.9.1</version>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.29</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.2.0-jre</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>commons-chain</groupId>
<artifactId>commons-chain</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>dev.samstevens.totp</groupId>
<artifactId>totp</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>com.beust</groupId>
<artifactId>jcommander</artifactId>
<version>1.82</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20231013</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.61</version>
<artifactId>dns-cache-manipulator</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.6.3</version>
</dependency>
</dependencies>
@@ -124,25 +299,81 @@
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.uyoqu.framework</groupId>
<artifactId>maven-plugin-starter</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>bin</goal>
</goals>
</execution>
</executions>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<jvms>
<jvm>-Djava.security.egd=file:/dev/./urandom</jvm>
<jvm>-Dfile.encoding=utf-8</jvm>
</jvms>
<source>21</source>
<target>21</target>
<encoding>UTF-8</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<fallback>false</fallback>
<imageName>${project.name}</imageName>
<metadataRepository>
<enabled>true</enabled>
</metadataRepository>
<jvmArgs>
<jvmArg>--add-opens=java.base/java.net=ALL-UNNAMED</jvmArg>
<jvmArg>--add-opens=java.base/sun.net=ALL-UNNAMED</jvmArg>
</jvmArgs>
<buildArgs>
<arg>
-march=compatibility
-H:+AddAllCharsets
--features=im.zhaojun.zfile.aot.LambdaRegistrationFeature
--features=im.zhaojun.zfile.aot.BouncyCastleFeature
--features=im.zhaojun.zfile.aot.SQLiteNativeConfiguration
</arg>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -2,19 +2,22 @@ package im.zhaojun.zfile;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* @author zhaojun
*/
@EnableAsync
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
@ServletComponentScan(basePackages = {"im.zhaojun.zfile.core.filter", "im.zhaojun.zfile.module.storage.filter"})
@ComponentScan(basePackages = "im.zhaojun.zfile.*")
public class ZfileApplication {
public static void main(String[] args) {
SpringApplication.run(ZfileApplication.class, args);
}
}
}

View File

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

View File

@@ -1,19 +0,0 @@
package im.zhaojun.zfile.cache;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author zhaojun
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DriveCacheKey {
private Integer driveId;
private String key;
}

View File

@@ -1,55 +0,0 @@
package im.zhaojun.zfile.cache;
import cn.hutool.cache.impl.CacheObj;
import cn.hutool.cache.impl.TimedCache;
import im.zhaojun.zfile.context.DriveContext;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import im.zhaojun.zfile.util.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
/**
* @author zhaojun
*/
@Slf4j
public class MyTimedCache<K, V> extends TimedCache<K, V> {
private DriveContext driveContext;
public MyTimedCache(long timeout) {
super(timeout);
}
public MyTimedCache(long timeout, Map<K, CacheObj<K, V>> map) {
super(timeout, map);
}
@Override
protected void onRemove(K key, V cachedObject) {
if (driveContext == null) {
driveContext = SpringContextHolder.getBean(DriveContext.class);
}
DriveCacheKey cacheKey = (DriveCacheKey) key;
AbstractBaseFileService baseFileService = driveContext.get(cacheKey.getDriveId());
if (log.isDebugEnabled()) {
log.debug("尝试刷新缓存: {}", cacheKey);
}
if (baseFileService == null) {
log.error("尝试刷新缓存: {}, 时出现异常, 驱动器已不存在", cacheKey);
return;
}
try {
baseFileService.fileList(cacheKey.getKey());
} catch (Exception e) {
log.error("尝试刷新缓存 {} 失败", cacheKey);
e.printStackTrace();
}
}
}

View File

@@ -1,331 +0,0 @@
package im.zhaojun.zfile.cache;
import cn.hutool.cache.impl.CacheObj;
import cn.hutool.core.util.StrUtil;
import im.zhaojun.zfile.model.constant.ZFileConstant;
import im.zhaojun.zfile.model.dto.FileItemDTO;
import im.zhaojun.zfile.model.dto.SystemConfigDTO;
import im.zhaojun.zfile.model.entity.DriveConfig;
import im.zhaojun.zfile.repository.DriverConfigRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* ZFile 缓存类
*
* @author zhaojun
*/
@Component
@Slf4j
public class ZFileCache {
@Resource
private DriverConfigRepository driverConfigRepository;
/**
* 缓存过期时间
*/
@Value("${zfile.cache.timeout}")
private long timeout;
/**
* 缓存自动刷新间隔
*/
@Value("${zfile.cache.auto-refresh.interval}")
private long autoRefreshInterval;
/**
* 文件/文件对象缓存.
*
* ConcurrentMap<Integer, ConcurrentHashMap<String, List<FileItemDTO>>>
* ConcurrentMap<driveId, ConcurrentHashMap<key, value>>
*
* driveId: 驱动器 ID
* key: 文件夹路径
* value: 文件夹中内容
*/
private ConcurrentMap<Integer, MyTimedCache<DriveCacheKey, List<FileItemDTO>>> drivesCache = new ConcurrentHashMap<>();
/**
* 系统设置缓存
*/
private SystemConfigDTO systemConfigCache;
/**
* 写入缓存
*
* @param driveId
* 驱动器 ID
*
* @param key
* 文件夹路径
*
* @param value
* 文件夹中列表
*/
public synchronized void put(Integer driveId, String key, List<FileItemDTO> value) {
getCacheByDriveId(driveId).put(new DriveCacheKey(driveId, key), value);
}
/**
* 获取指定驱动器, 某个文件夹的名称
*
* @param driveId
* 驱动器 ID
*
* @param key
* 文件夹路径
*
* @return 驱动器中文件夹的内容
*/
public List<FileItemDTO> get(Integer driveId, String key) {
return getCacheByDriveId(driveId).get(new DriveCacheKey(driveId, key), false);
}
/**
* 清空指定驱动器的缓存.
*
* @param driveId
* 驱动器 ID
*/
public void clear(Integer driveId) {
if (log.isDebugEnabled()) {
log.debug("清空驱动器所有缓存, driveId: {}", driveId);
}
getCacheByDriveId(driveId).clear();
}
/**
* 获取指定驱动器中已缓存文件夹数量
*
* @param driveId
* 驱动器 ID
*
* @return 已缓存文件夹数量
*/
public int cacheCount(Integer driveId) {
return getCacheByDriveId(driveId).size();
}
/**
* 指定驱动器, 根据文件及文件名查找相关的文件
*
* @param driveId
* 驱动器 ID
*
* @param key
* 搜索键, 可匹配文件夹名称和文件名称.
*
* @return 搜索结果, 包含文件夹和文件.
*/
public List<FileItemDTO> find(Integer driveId, String key) {
List<FileItemDTO> result = new ArrayList<>();
DriveConfig driveConfig = driverConfigRepository.getOne(driveId);
boolean searchContainEncryptedFile = driveConfig.getSearchContainEncryptedFile();
boolean ignoreCase = driveConfig.getSearchIgnoreCase();
for (List<FileItemDTO> fileItemList : getCacheByDriveId(driveId)) {
// 过滤加密文件
if (!searchContainEncryptedFile && isEncryptedFolder(fileItemList)) {
continue;
}
for (FileItemDTO fileItemDTO : fileItemList) {
boolean testResult;
// 根据是否需要忽略大小写来匹配文件(夹)名
if (ignoreCase) {
testResult = StrUtil.containsIgnoreCase(fileItemDTO.getName(), key);
} else {
testResult = fileItemDTO.getName().contains(key);
}
if (testResult) {
result.add(fileItemDTO);
}
}
}
return result;
}
/**
* 获取所有缓存 key (文件夹名称)
*
* @return 所有缓存 key
*/
public Set<String> keySet(Integer driveId) {
Iterator<CacheObj<DriveCacheKey, List<FileItemDTO>>> cacheObjIterator = getCacheByDriveId(driveId).cacheObjIterator();
Set<String> keys = new HashSet<>();
while (cacheObjIterator.hasNext()) {
keys.add(cacheObjIterator.next().getKey().getKey());
}
return keys;
}
/**
* 从缓存中删除指定驱动器的某个路径的缓存
*
* @param driveId
* 驱动器 ID
*
* @param key
* 文件夹路径
*/
public void remove(Integer driveId, String key) {
getCacheByDriveId(driveId).remove(new DriveCacheKey(driveId, key));
}
/**
* 更新缓存中的系统设置
*
* @param systemConfigCache
* 系统设置
*/
public void updateConfig(SystemConfigDTO systemConfigCache) {
this.systemConfigCache = systemConfigCache;
}
/**
* 从缓存中获取系统设置
*
* @return 系统设置
*/
public SystemConfigDTO getConfig() {
return this.systemConfigCache;
}
/**
* 清空系统设置缓存
*/
public void removeConfig() {
this.systemConfigCache = null;
}
/**
* 判断此文件夹是否为加密文件夹 (包含)
*
* @param list
* 文件夹中的内容
*
* @return 返回此文件夹是否是加密的 ().
*/
private boolean isEncryptedFolder(List<FileItemDTO> list) {
// 遍历文件判断是否包含
for (FileItemDTO fileItemDTO : list) {
if (Objects.equals(ZFileConstant.PASSWORD_FILE_NAME, fileItemDTO.getName())) {
return true;
}
}
return false;
}
/**
* 获取指定驱动器对应的缓存
*
* @param driveId
* 驱动器 ID
*
* @return 驱动器对应的缓存
*/
private synchronized MyTimedCache<DriveCacheKey, List<FileItemDTO>> getCacheByDriveId(Integer driveId) {
MyTimedCache<DriveCacheKey, List<FileItemDTO>> driveCache = drivesCache.get(driveId);
if (driveCache == null) {
driveCache = new MyTimedCache<>(timeout * 1000);
drivesCache.put(driveId, driveCache);
startAutoCacheRefresh(driveId);
}
return driveCache;
}
/**
* 获取指定驱动器的缓存命中数
*
* @param driveId
* 驱动器 ID
*
* @return 缓存命中数
*/
public int getHitCount(Integer driveId) {
return getCacheByDriveId(driveId).getHitCount();
}
/**
* 获取指定驱动器的缓存未命中数
*
* @param driveId
* 驱动器 ID
*
* @return 缓存未命中数
*/
public int getMissCount(Integer driveId) {
return getCacheByDriveId(driveId).getMissCount();
}
/**
* 开启缓存自动刷新
*
* @param driveId
* 驱动器 ID
*/
public void startAutoCacheRefresh(Integer driveId) {
if (log.isDebugEnabled()) {
log.debug("开启缓存自动刷新 driveId: {}", driveId);
}
DriveConfig driveConfig = driverConfigRepository.findById(driveId).get();
Boolean autoRefreshCache = driveConfig.getAutoRefreshCache();
if (autoRefreshCache != null && autoRefreshCache) {
MyTimedCache<DriveCacheKey, List<FileItemDTO>> driveCache = drivesCache.get(driveId);
if (driveCache == null) {
driveCache = new MyTimedCache<>(timeout * 1000);
drivesCache.put(driveId, driveCache);
}
driveCache.schedulePrune(autoRefreshInterval * 1000);
}
}
/**
* 停止缓存自动刷新
*
* @param driveId
* 驱动器 ID
*/
public void stopAutoCacheRefresh(Integer driveId) {
if (log.isDebugEnabled()) {
log.debug("停止缓存自动刷新 driveId: {}", driveId);
}
MyTimedCache<DriveCacheKey, List<FileItemDTO>> driveCache = drivesCache.get(driveId);
if (driveCache != null) {
driveCache.cancelPruneSchedule();
}
}
}

View File

@@ -1,47 +0,0 @@
package im.zhaojun.zfile.config;
import im.zhaojun.zfile.model.constant.StorageConfigConstant;
import im.zhaojun.zfile.model.entity.StorageConfig;
import im.zhaojun.zfile.service.StorageConfigService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.LinkedList;
/**
* @author zhaojun
*/
@Configuration
public class OneDriveConfig {
@Resource
private StorageConfigService storageConfigService;
/**
* OneDrive 请求 RestTemplate, 会在请求头中添加 Bearer: Authorization {token} 信息, 用于 API 认证.
*/
@Bean
public RestTemplate oneDriveRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
ClientHttpRequestInterceptor interceptor = (httpRequest, bytes, clientHttpRequestExecution) -> {
HttpHeaders headers = httpRequest.getHeaders();
Integer driveId = Integer.valueOf(((LinkedList)headers.get("driveId")).get(0).toString());
StorageConfig accessTokenConfig =
storageConfigService.findByDriveIdAndKey(driveId, StorageConfigConstant.ACCESS_TOKEN_KEY);
String tokenValue = String.format("%s %s", "Bearer", accessTokenConfig.getValue());
httpRequest.getHeaders().add("Authorization", tokenValue);
return clientHttpRequestExecution.execute(httpRequest, bytes);
};
restTemplate.setInterceptors(Collections.singletonList(interceptor));
return restTemplate;
}
}

View File

@@ -1,34 +0,0 @@
package im.zhaojun.zfile.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
/**
* @author zhaojun
*/
@Configuration
public class ZFileConfiguration {
@Bean
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
restTemplate.setInterceptors(Collections.singletonList((request, body, execution) -> {
ClientHttpResponse response = execution.execute(request, body);
HttpHeaders headers = response.getHeaders();
headers.put("Content-Type", Collections.singletonList("application/text"));
return response;
}));
return restTemplate;
}
}

View File

@@ -1,131 +0,0 @@
package im.zhaojun.zfile.context;
import com.alibaba.fastjson.JSON;
import im.zhaojun.zfile.exception.InvalidDriveException;
import im.zhaojun.zfile.model.entity.DriveConfig;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.service.DriveConfigService;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import im.zhaojun.zfile.util.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* 每个驱动器对应一个 Service, 其中初始化好了与对象存储的连接信息.
* 此驱动器上下文环境用户缓存每个 Service, 避免重复创建连接.
* @author zhaojun
*/
@Component
@DependsOn("springContextHolder")
@Slf4j
public class DriveContext implements ApplicationContextAware {
/**
* Map<Integer, AbstractBaseFileService>
* Map<驱动器 ID, 驱动器连接 Service>
*/
private static Map<Integer, AbstractBaseFileService> drivesServiceMap = new ConcurrentHashMap<>();
@Resource
private DriveConfigService driveConfigService;
/**
* 项目启动时, 自动调用数据库已存储的所有驱动器进行初始化.
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
List<DriveConfig> list = driveConfigService.list();
for (DriveConfig driveConfig : list) {
try {
init(driveConfig.getId());
log.info("启动时初始化驱动器成功, 驱动器信息: {}", JSON.toJSONString(driveConfig));
} catch (Exception e) {
log.error("启动时初始化驱动器失败, 驱动器信息: {}", JSON.toJSONString(driveConfig), e);
}
}
}
/**
* 初始化指定驱动器的 Service, 添加到上下文环境中.
*
* @param driveId
* 驱动器 ID.
*/
public void init(Integer driveId) {
AbstractBaseFileService baseFileService = getBeanByDriveId(driveId);
if (baseFileService != null) {
if (log.isDebugEnabled()) {
log.debug("尝试初始化驱动器, driveId: {}", driveId);
}
baseFileService.init(driveId);
if (log.isDebugEnabled()) {
log.debug("初始化驱动器成功, driveId: {}", driveId);
}
drivesServiceMap.put(driveId, baseFileService);
}
}
/**
* 获取指定驱动器的 Service.
*
* @param driveId
* 驱动器 ID
*
* @return 驱动器对应的 Service
*/
public AbstractBaseFileService get(Integer driveId) {
AbstractBaseFileService abstractBaseFileService = drivesServiceMap.get(driveId);
if (abstractBaseFileService == null) {
throw new InvalidDriveException("此驱动器不存在或初始化失败, 请检查后台参数配置");
}
return abstractBaseFileService;
}
/**
* 销毁指定驱动器的 Service.
*
* @param driveId
* 驱动器 ID
*/
public void destroy(Integer driveId) {
if (log.isDebugEnabled()) {
log.debug("清理驱动器上下文对象, driveId: {}", driveId);
}
drivesServiceMap.remove(driveId);
}
/**
* 获取指定驱动器对应的 Service, 状态为未初始化
*
* @param driveId
* 驱动器 ID
*
* @return 驱动器对应未初始化的 Service
*/
private AbstractBaseFileService getBeanByDriveId(Integer driveId) {
StorageTypeEnum storageTypeEnum = driveConfigService.findStorageTypeById(driveId);
Map<String, AbstractBaseFileService> beansOfType = SpringContextHolder.getBeansOfType(AbstractBaseFileService.class);
for (AbstractBaseFileService value : beansOfType.values()) {
if (Objects.equals(value.getStorageTypeEnum(), storageTypeEnum)) {
return SpringContextHolder.getBean(value.getClass());
}
}
return null;
}
}

View File

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

View File

@@ -1,54 +0,0 @@
package im.zhaojun.zfile.controller.admin;
import im.zhaojun.zfile.model.dto.SystemConfigDTO;
import im.zhaojun.zfile.model.support.ResultBean;
import im.zhaojun.zfile.service.SystemConfigService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 管理后台接口
* @author zhaojun
*/
@RestController
@RequestMapping("/admin")
public class AdminController {
@Resource
private SystemConfigService systemConfigService;
/**
* 获取系统配置
*/
@GetMapping("/config")
public ResultBean getConfig() {
SystemConfigDTO systemConfigDTO = systemConfigService.getSystemConfig();
return ResultBean.success(systemConfigDTO);
}
/**
* 更新系统配置
*/
@PostMapping("/config")
public ResultBean updateConfig(SystemConfigDTO systemConfigDTO) {
systemConfigDTO.setId(1);
systemConfigService.updateSystemConfig(systemConfigDTO);
return ResultBean.success();
}
/**
* 修改管理员登陆密码
*/
@PostMapping("/update-pwd")
public ResultBean updatePwd(String username, String password) {
systemConfigService.updateUsernameAndPwd(username, password);
return ResultBean.success();
}
}

View File

@@ -1,72 +0,0 @@
package im.zhaojun.zfile.controller.admin;
import im.zhaojun.zfile.model.dto.CacheInfoDTO;
import im.zhaojun.zfile.model.support.ResultBean;
import im.zhaojun.zfile.service.DriveConfigService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 缓存 Controller
*
* @author zhaojun
*/
@RestController
@RequestMapping("/admin/cache")
public class CacheController {
@Resource
private DriveConfigService driveConfigService;
@PostMapping("/{driveId}/enable")
public ResultBean enableCache(@PathVariable("driveId") Integer driveId) {
driveConfigService.updateCacheStatus(driveId, true);
return ResultBean.success();
}
@PostMapping("/{driveId}/disable")
public ResultBean disableCache(@PathVariable("driveId") Integer driveId) {
driveConfigService.updateCacheStatus(driveId, false);
return ResultBean.success();
}
@GetMapping("/{driveId}/info")
public ResultBean cacheInfo(@PathVariable("driveId") Integer driveId) {
CacheInfoDTO cacheInfo = driveConfigService.findCacheInfo(driveId);
return ResultBean.success(cacheInfo);
}
@PostMapping("/{driveId}/refresh")
public ResultBean refreshCache(@PathVariable("driveId") Integer driveId, String key) throws Exception {
driveConfigService.refreshCache(driveId, key);
return ResultBean.success();
}
@PostMapping("/{driveId}/auto-refresh/start")
public ResultBean enableAutoRefresh(@PathVariable("driveId") Integer driveId) {
driveConfigService.startAutoCacheRefresh(driveId);
return ResultBean.success();
}
@PostMapping("/{driveId}/auto-refresh/stop")
public ResultBean disableAutoRefresh(@PathVariable("driveId") Integer driveId) {
driveConfigService.stopAutoCacheRefresh(driveId);
return ResultBean.success();
}
@PostMapping("/{driveId}/clear")
public ResultBean clearCache(@PathVariable("driveId") Integer driveId) {
driveConfigService.clearCache(driveId);
return ResultBean.success();
}
}

View File

@@ -1,132 +0,0 @@
package im.zhaojun.zfile.controller.admin;
import com.alibaba.fastjson.JSONObject;
import im.zhaojun.zfile.model.dto.DriveConfigDTO;
import im.zhaojun.zfile.model.entity.DriveConfig;
import im.zhaojun.zfile.model.entity.FilterConfig;
import im.zhaojun.zfile.model.support.ResultBean;
import im.zhaojun.zfile.service.DriveConfigService;
import im.zhaojun.zfile.service.FilterConfigService;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* 驱动器相关操作 Controller
* @author zhaojun
*/
@RestController
@RequestMapping("/admin")
public class DriveController {
@Resource
private DriveConfigService driveConfigService;
@Resource
private FilterConfigService filterConfigService;
/**
* 获取所有驱动器列表
*
* @return 驱动器列表
*/
@GetMapping("/drives")
public ResultBean driveList() {
List<DriveConfig> list = driveConfigService.list();
return ResultBean.success(list);
}
/**
* 获取指定驱动器基本信息及其参数
*
* @param id
* 驱动器 ID
*
* @return 驱动器基本信息
*/
@GetMapping("/drive/{id}")
public ResultBean driveItem(@PathVariable Integer id) {
DriveConfigDTO driveConfig = driveConfigService.findDriveConfigDTOById(id);
return ResultBean.success(driveConfig);
}
/**
* 保存驱动器设置
*/
@PostMapping("/drive")
public ResultBean saveDriveItem(@RequestBody DriveConfigDTO driveConfigDTO) {
driveConfigService.saveDriveConfigDTO(driveConfigDTO);
return ResultBean.success();
}
/**
* 删除驱动器设置
*
* @param id
* 驱动器 ID
*/
@DeleteMapping("/drive/{id}")
public ResultBean deleteDriveItem(@PathVariable Integer id) {
driveConfigService.deleteById(id);
return ResultBean.success();
}
/**
* 启用驱动器
*
* @param id
* 驱动器 ID
*/
@PostMapping("/drive/{id}/enable")
public ResultBean enable(@PathVariable("id") Integer id) {
DriveConfig driveConfig = driveConfigService.findById(id);
driveConfig.setEnable(true);
driveConfigService.saveOrUpdate(driveConfig);
return ResultBean.success();
}
/**
* 停止驱动器
*
* @param id
* 驱动器 ID
*/
@PostMapping("/drive/{id}/disable")
public ResultBean disable(@PathVariable("id") Integer id) {
DriveConfig driveConfig = driveConfigService.findById(id);
driveConfig.setEnable(false);
driveConfigService.saveOrUpdate(driveConfig);
return ResultBean.success();
}
@GetMapping("/drive/{id}/filters")
public ResultBean getFilters(@PathVariable("id") Integer id) {
return ResultBean.success(filterConfigService.findByDriveId(id));
}
@PostMapping("/drive/{id}/filters")
public ResultBean saveFilters(@RequestBody List<FilterConfig> filter, @PathVariable("id") Integer driveId) {
filterConfigService.batchSave(filter, driveId);
return ResultBean.success();
}
@PostMapping("/drive/drag")
public ResultBean saveDriveDrag(@RequestBody List<JSONObject> driveConfigs) {
driveConfigService.saveDriveDrag(driveConfigs);
return ResultBean.success();
}
}

View File

@@ -1,39 +0,0 @@
package im.zhaojun.zfile.controller.admin;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ZipUtil;
import im.zhaojun.zfile.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.util.Date;
/**
* 日志相关 Controller
* @author zhaojun
*/
@RestController
@RequestMapping("/admin")
@Slf4j
public class LogController {
/**
* 系统日志下载
*/
@GetMapping("/log")
public ResponseEntity<Object> downloadLog(HttpServletResponse response) {
if (log.isDebugEnabled()) {
log.debug("下载诊断日志");
}
String userHome = System.getProperty("user.home");
File fileZip = ZipUtil.zip(userHome + "/.zfile/logs");
String currentDate = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss");
return FileUtil.export(fileZip, "ZFile 诊断日志 - " + currentDate + ".zip");
}
}

View File

@@ -1,47 +0,0 @@
package im.zhaojun.zfile.controller.admin;
import im.zhaojun.zfile.context.StorageTypeContext;
import im.zhaojun.zfile.model.entity.StorageConfig;
import im.zhaojun.zfile.model.enums.StorageTypeEnum;
import im.zhaojun.zfile.model.support.ResultBean;
import im.zhaojun.zfile.service.base.AbstractBaseFileService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 系统元数据 Controller
* @author zhaojun
*/
@RestController
@RequestMapping("/admin")
public class MateDataController {
/**
* 返回支持的存储引擎.
*/
@GetMapping("/support-strategy")
public ResultBean supportStrategy() {
StorageTypeEnum[] values = StorageTypeEnum.values();
return ResultBean.successData(values);
}
/**
* 获取指定存储策略的表单域
*
* @param storageType
* 存储策略
*
* @return 所有表单域
*/
@GetMapping("/strategy-form")
public ResultBean getFormByStorageType(StorageTypeEnum storageType) {
AbstractBaseFileService storageTypeService = StorageTypeContext.getStorageTypeService(storageType);
List<StorageConfig> storageConfigList = storageTypeService.storageStrategyConfigList();
return ResultBean.success(storageConfigList);
}
}

View File

@@ -1,26 +0,0 @@
package im.zhaojun.zfile.controller.admin;
import im.zhaojun.zfile.model.support.ResultBean;
import im.zhaojun.zfile.model.support.SystemMonitorInfo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 系统监控 Controller
* @author zhaojun
*/
@RestController
@RequestMapping("/admin")
public class MonitorController {
/**
* 获取系统监控信息
*/
@GetMapping("monitor")
public ResultBean monitor() {
return ResultBean.success(new SystemMonitorInfo());
}
}

View File

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

View File

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

View File

@@ -1,45 +0,0 @@
package im.zhaojun.zfile.controller.home;
import im.zhaojun.zfile.model.support.ResultBean;
import im.zhaojun.zfile.util.AudioUtil;
import im.zhaojun.zfile.util.HttpUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 文件解析 Controller
* @author zhaojun
*/
@RestController
@RequestMapping("/common")
public class FileParseController {
/**
* 获取文件内容, 仅限用于 txt, md, ini 等普通文本文件.
*
* @param url
* 文件路径
*
* @return 文件内容
*/
@GetMapping("/content")
public ResultBean getContent(String url) {
return ResultBean.successData(HttpUtil.getTextContent(url));
}
/**
* 获取音频文件信息
*
* @param url
* 文件 URL
*
* @return 音频信息, 标题封面等信息
*/
@GetMapping("/audio-info")
public ResultBean getAudioInfo(String url) throws Exception {
return ResultBean.success(AudioUtil.getAudioInfo(url));
}
}

View File

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

View File

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

View File

@@ -1,43 +0,0 @@
package im.zhaojun.zfile.controller.onedrive;
import im.zhaojun.zfile.model.support.OneDriveToken;
import im.zhaojun.zfile.service.impl.OneDriveChinaServiceImpl;
import im.zhaojun.zfile.service.impl.OneDriveServiceImpl;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
/**
* @author zhaojun
*/
@Controller
@RequestMapping("/onedrive")
public class OneDriveCallbackController {
@Resource
private OneDriveServiceImpl oneDriveServiceImpl;
@Resource
private OneDriveChinaServiceImpl oneDriveChinaServiceImpl;
@GetMapping("/callback")
public String oneDriveCallback(String code, Model model) {
OneDriveToken oneDriveToken = oneDriveServiceImpl.getToken(code);
model.addAttribute("accessToken", oneDriveToken.getAccessToken());
model.addAttribute("refreshToken", oneDriveToken.getRefreshToken());
return "callback";
}
@GetMapping("/china-callback")
public String oneDriveChinaCallback(String code, Model model) {
OneDriveToken oneDriveToken = oneDriveChinaServiceImpl.getToken(code);
model.addAttribute("accessToken", oneDriveToken.getAccessToken());
model.addAttribute("refreshToken", oneDriveToken.getRefreshToken());
return "callback";
}
}

View File

@@ -0,0 +1,33 @@
package im.zhaojun.zfile.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* 接口限流注解
*
* @author zhaojun
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiLimit {
/**
* 持续时间
*/
int timeout();
/**
* 时间单位, 默认为秒
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 单位时间内允许访问的最大次数
*/
long maxCount();
}

View File

@@ -0,0 +1,17 @@
package im.zhaojun.zfile.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 演示系统禁用功能注解
*
* @author zhaojun
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DemoDisable {
}

View File

@@ -0,0 +1,74 @@
package im.zhaojun.zfile.core.aspect;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.extra.servlet.JakartaServletUtil;
import im.zhaojun.zfile.core.annotation.ApiLimit;
import im.zhaojun.zfile.core.exception.ErrorCode;
import im.zhaojun.zfile.core.exception.core.BizException;
import im.zhaojun.zfile.core.util.RequestHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* 接口限流切面, 通过注解 {@link ApiLimit} 进行限流.
*
* @author zhaojun
*/
@Aspect
@Component
public class ApiLimitAspect {
private final TimedCache<String, AtomicLong> apiLimitTimedCache = CacheUtil.newTimedCache(1000);
public static final String API_LIMIT_KEY_PREFIX = "api_limit_";
/**
* 定义一个切点(通过注解)
*/
@Pointcut("@annotation(im.zhaojun.zfile.core.annotation.ApiLimit)")
public void apiLimit() {
}
/**
* 在标记了 {@link ApiLimit} 注解的方法执行前进行限流校验.
*
* @param joinPoint 切点
*/
@Before("apiLimit()")
public void before(JoinPoint joinPoint) {
// 获取当前请求的方法上的注解中设置的值
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 反射获取当前被调用的方法
Method method = signature.getMethod();
// 获取方法中的注解
ApiLimit apiLimit = method.getDeclaredAnnotation(ApiLimit.class);
int timeout = apiLimit.timeout();
TimeUnit timeUnit = apiLimit.timeUnit();
long millis = timeUnit.toMillis(timeout);
long maxCount = apiLimit.maxCount();
// 获取请求相关信息
String ip = JakartaServletUtil.getClientIP(RequestHolder.getRequest());
// 限制访问次数
String key = API_LIMIT_KEY_PREFIX.concat(ip).concat(method.getName());
AtomicLong atomicLong = apiLimitTimedCache.get(key, false);
if (atomicLong == null) {
apiLimitTimedCache.put(key, new AtomicLong(1), millis);
} else {
if (atomicLong.incrementAndGet() > maxCount) {
throw new BizException(ErrorCode.BIZ_ACCESS_TOO_FREQUENT);
}
}
}
}

View File

@@ -0,0 +1,69 @@
package im.zhaojun.zfile.core.aspect;
import im.zhaojun.zfile.core.constant.MdcConstant;
import im.zhaojun.zfile.core.util.AjaxJson;
import org.slf4j.MDC;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* Controller 切面, 用于处理返回值统一封装.
*
* @author zhaojun
*/
@ControllerAdvice
public class CommonResultControllerAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType,
@NonNull Class<? extends HttpMessageConverter<?>> converterType) {
return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}
@Override
@NonNull
public final Object beforeBodyWrite(@Nullable Object body,
@NonNull MethodParameter returnType,
@NonNull MediaType contentType,
@NonNull Class<? extends HttpMessageConverter<?>> converterType,
@NonNull ServerHttpRequest request,
@NonNull ServerHttpResponse response) {
MappingJacksonValue container = getOrCreateContainer(body);
// The contain body will never be null
beforeBodyWriteInternal(container, contentType, returnType, request, response);
return container;
}
/**
* Wrap the body in a {@link MappingJacksonValue} value container (for providing
* additional serialization instructions) or simply cast it if already wrapped.
*/
private MappingJacksonValue getOrCreateContainer(Object body) {
return body instanceof MappingJacksonValue ? (MappingJacksonValue) body :
new MappingJacksonValue(body);
}
private void beforeBodyWriteInternal(MappingJacksonValue bodyContainer,
MediaType contentType,
MethodParameter returnType,
ServerHttpRequest request,
ServerHttpResponse response) {
// Get return body
Object returnBody = bodyContainer.getValue();
if (returnBody instanceof AjaxJson<?> baseResponse) {
// 将 MDC 中的 TraceId 设置到返回值中
baseResponse.setTraceId(MDC.get(MdcConstant.TRACE_ID));
}
}
}

View File

@@ -0,0 +1,45 @@
package im.zhaojun.zfile.core.aspect;
import im.zhaojun.zfile.core.annotation.DemoDisable;
import im.zhaojun.zfile.core.config.ZFileProperties;
import im.zhaojun.zfile.core.exception.ErrorCode;
import im.zhaojun.zfile.core.exception.core.BizException;
import jakarta.annotation.Resource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 通过注解 {@link DemoDisable} 限制演示系统不可操作的功能.
*
* @author zhaojun
*/
@Aspect
@Component
public class DemoDisableAspect {
@Resource
private ZFileProperties zFileProperties;
/**
* 定义一个切点(通过注解)
*/
@Pointcut("@annotation(im.zhaojun.zfile.core.annotation.DemoDisable)")
public void demoDisable() {
}
/**
* 在标记了 {@link DemoDisable} 注解的方法执行前进行限流校验.
*
* @param joinPoint 切点
*/
@Before("demoDisable()")
public void before(JoinPoint joinPoint) {
if (zFileProperties.isDemoSite()) {
throw new BizException(ErrorCode.DEMO_SITE_DISABLE_OPERATOR);
}
}
}

View File

@@ -0,0 +1,37 @@
package im.zhaojun.zfile.core.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* ZFile 配置类,将配置文件中的 zfile 配置项映射到该类中.
*
* @author zhaojun
*/
@Data
@EnableConfigurationProperties
@Component
@ConfigurationProperties(prefix = "zfile")
public class ZFileProperties {
private boolean debug;
private String version;
private boolean isDemoSite;
private OAuth2Properties onedrive = new OAuth2Properties();
private OAuth2Properties onedriveChina = new OAuth2Properties();
private OAuth2Properties gd = new OAuth2Properties();
@Data
public static class OAuth2Properties {
private String clientId;
private String clientSecret;
private String redirectUri;
private String scope;
}
}

View File

@@ -0,0 +1,123 @@
package im.zhaojun.zfile.core.config.datasource;
import cn.hutool.core.io.FileUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.zaxxer.hikari.HikariDataSource;
import im.zhaojun.zfile.core.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.flyway.FlywayProperties;
import org.springframework.core.PriorityOrdered;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.List;
/**
* 在 Spring 容器初始化时, 对数据源进行处理.
* <br/>
* 1. 针对 DataSource 进行处理,仅针对 sqlite
* <ul>
* <li>提前创建 sqlite 数据文件所在目录.</li>
* <li>检测到版本更新时(pom.xml -> project.version)自动备份原数据库.</li>
* </ul>
* <br/>
* 2. 针对 Flyway 进行处理,根据数据库类型, 配置不同的 Flyway Migration Location
* <ul>
* <li>SQLite 数据库使用 migration-sqlite 目录.</li>
* <li>MySQL 数据库使用 migration-mysql 目录.</li>
* </ul>
*
* @author zhaojun
*/
@Slf4j
@Component
public class DataSourceBeanPostProcessor implements BeanPostProcessor, PriorityOrdered {
public static final String ZFILE_VERSION_PROPERTIES = "zfile.version";
public static final String DRIVE_CLASS_NAME_PROPERTIES = "spring.datasource.driver-class-name";
public static final String DATA_SOURCE_BEAN_NAME = "dataSource";
public static final String SQLITE_DRIVE_CLASS_NAME = "org.sqlite.JDBC";
public static final String MYSQL_DRIVE_CLASS_NAME = "com.mysql.cj.jdbc.Driver";
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 如果更改了数据源类型这里要修改
if (bean instanceof HikariDataSource dataSource && DATA_SOURCE_BEAN_NAME.equals(beanName)) {
processSqliteDataSource(dataSource);
} else if (bean instanceof FlywayProperties flywayProperties) {
processFlywayLocations(flywayProperties);
}
return bean;
}
/**
* 如果是 sqlite 数据库, 提前创建数据库文件所在目录. <br/>
*
* 如果检测到版本更新, 自动备份原数据库文件.
*
* @param dataSource
* 数据源
*/
private void processSqliteDataSource(HikariDataSource dataSource) {
String driverClassName = dataSource.getDriverClassName();
String jdbcUrl = dataSource.getJdbcUrl();
if (StringUtils.equals(driverClassName, SQLITE_DRIVE_CLASS_NAME)) {
String path = jdbcUrl.replace("jdbc:sqlite:", "");
String folderPath = FileUtil.getAbsolutePath(new File(path).getParentFile());
log.info("SQLite 数据库文件所在目录: [{}]", folderPath);
File file = new File(folderPath);
if (!file.exists()) {
log.info("检测到 SQLite 数据库文件所在目录不存在, 已自动创建.");
if (!file.mkdirs()) {
log.error("SQLite 数据库文件创建失败.");
}
} else {
log.info("检测到 SQLite 数据库文件所在目录已存在, 无需自动创建.");
// 更新版本时, 先自动备份数据库文件
String version = SpringUtil.getProperty(ZFILE_VERSION_PROPERTIES);
if (StringUtils.isNotEmpty(version)) {
String backupPath = folderPath + "/zfile-update-" + version + "-backup.db";
if (!FileUtil.exist(path)) {
log.error("检测到 SQLite 数据库文件不存在, 一般为初始化状态,无需备份.");
return;
}
if (FileUtil.exist(backupPath)) {
log.info("检测到 SQLite 数据库备份文件 [{}] 已存在, 无需再次备份.", backupPath);
} else {
FileUtil.copy(path, backupPath, false);
log.info("自动备份 SQLite 数据库文件到: [{}]", backupPath);
}
}
}
}
}
/**
* 根据使用的不同数据库, 配置使用不同的 migration location
*
* @param flywayProperties
* flyway 配置项
*/
private void processFlywayLocations(FlywayProperties flywayProperties) {
String driveClassName = SpringUtil.getProperty(DRIVE_CLASS_NAME_PROPERTIES);
if (SQLITE_DRIVE_CLASS_NAME.equals(driveClassName)) {
flywayProperties.setLocations(List.of("classpath:db/migration-sqlite"));
} else if (MYSQL_DRIVE_CLASS_NAME.equals(driveClassName)) {
flywayProperties.setLocations(List.of("classpath:db/migration-mysql"));
}
}
@Override
public int getOrder() {
return Integer.MIN_VALUE;
}
}

View File

@@ -0,0 +1,73 @@
package im.zhaojun.zfile.core.config.docs;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.HeaderParameter;
import org.springdoc.core.customizers.OperationCustomizer;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Knife4j 参数配置,区分前台功能和管理员功能,并为管理员接口增加统一 token header 配置.
*
* @author zhaojun
*/
@Configuration
public class Knife4jConfiguration {
@Bean
public GroupedOpenApi groupedOpenApi() {
String groupName = "前台功能";
return GroupedOpenApi.builder()
.group(groupName)
.packagesToScan("im.zhaojun.zfile.module")
.pathsToExclude("/admin/**")
.build();
}
@Bean
public GroupedOpenApi groupedOpenApi2() {
String groupName = "管理员功能";
return GroupedOpenApi.builder()
.group(groupName)
.packagesToScan("im.zhaojun.zfile.module")
.pathsToMatch("/admin/**")
.addOperationCustomizer(globalOperationCustomizer())
.build();
}
public OperationCustomizer globalOperationCustomizer() {
return (operation, handlerMethod) -> {
operation.addParametersItem(new HeaderParameter()
.name("zfile-token")
.description("token")
.required(true)
.schema(new StringSchema()));
return operation;
};
}
@Bean
public OpenAPI customOpenAPI() {
Contact contact = new Contact();
contact.setName("zhaojun");
contact.setUrl("https://zfile.vip");
contact.setEmail("873019219@qq.com");
return new OpenAPI()
.info(new Info()
.title("ZFILE 文档")
.description("# 这是 ZFILE Restful 接口文档展示页面")
.termsOfService("https://www.zfile.vip")
.contact(contact)
.version("1.0")
.license(new License()
.name("Apache 2.0")
.url("http://doc.xiaominfo.com")));
}
}

View File

@@ -0,0 +1,25 @@
package im.zhaojun.zfile.core.config.jackson;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
/**
* JSON String 反序列化器, 用于将 JSON 字符串反序列化为 JSON 对象.
*
* @author zhaojun
*/
public class JSONStringDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser p, DeserializationContext context) throws IOException {
JsonNode node = p.getCodec().readTree(p);
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(node);
}
}

View File

@@ -0,0 +1,22 @@
package im.zhaojun.zfile.core.config.jackson;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
/**
* JSON String 序列化器, 用于将 JSON 字符串序列化为 JSON 对象.
*
* @author zhaojun
*/
public class JSONStringSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeRawValue(value);
}
}

View File

@@ -0,0 +1,7 @@
package im.zhaojun.zfile.core.config.mybatis;
import java.util.Set;
public class CollectionIntegerTypeHandler extends CollectionTypeHandler<Set<Integer>> {
}

View File

@@ -0,0 +1,7 @@
package im.zhaojun.zfile.core.config.mybatis;
import java.util.Set;
public class CollectionStrTypeHandler extends CollectionTypeHandler<Set<String>> {
}

View File

@@ -0,0 +1,121 @@
package im.zhaojun.zfile.core.config.mybatis;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.springframework.core.ResolvableType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
/**
* 自定义 Set 类型处理器, 用于处理数据库 VARCHAR 类型字段和 Java Set 类型属性之间的转换.
* 支持字符串格式为: "[a, b, c]".
*
* @author zhaojun
*/
@MappedJdbcTypes(JdbcType.VARCHAR)
public abstract class CollectionTypeHandler<T> extends BaseTypeHandler<Object> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
if (parameter instanceof Collection collection) {
StringJoiner joiner = new StringJoiner(",");
for (Object o : collection) {
joiner.add(Convert.toStr(o));
}
ps.setString(i, joiner.toString());
} else {
ps.setString(i, Convert.toStr(parameter));
}
}
@Override
public Object getNullableResult(ResultSet rs, String columnName)
throws SQLException {
String str = rs.getString(columnName);
return convertToEntityAttribute(str);
}
@Override
public Object getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
String str = rs.getString(columnIndex);
return convertToEntityAttribute(str);
}
@Override
public Object getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
String str = cs.getString(columnIndex);
return convertToEntityAttribute(str);
}
private Class<?> collectionClazz;
private Type innerType;
/**
* 构造方法
*/
public CollectionTypeHandler() {
ResolvableType resolvableType = ResolvableType.forClass(getClass());
Type type = resolvableType.as(CollectionTypeHandler.class).getGeneric().getType();
if (type instanceof ParameterizedType parameterizedType) {
collectionClazz = (Class<?>) parameterizedType.getRawType();
// 获取实际类型参数(泛型参数,例如 List<String> 中的 String
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
// 使用这些信息做进一步操作
for (Type actualTypeArgument : actualTypeArguments) {
innerType = actualTypeArgument;
break;
}
}
}
private Object convertToEntityAttribute(String dbData) {
if (StrUtil.isEmpty(dbData)) {
if (List.class.isAssignableFrom(collectionClazz)) {
return Collections.emptyList();
} else if (Set.class.isAssignableFrom(collectionClazz)) {
return Collections.emptySet();
} else {
return null;
}
}
Collection collection;
if (List.class.isAssignableFrom(collectionClazz)) {
collection = new ArrayList<>();
} else if (Set.class.isAssignableFrom(collectionClazz)) {
collection = new HashSet<>();
} else {
return null;
}
String[] split = dbData.split(",");
for (String s : split) {
if (NumberUtil.isNumber(s)) {
collection.add(Convert.convert(Integer.class, s));
} else {
collection.add(s);
}
}
return collection;
}
}

View File

@@ -0,0 +1,33 @@
package im.zhaojun.zfile.core.config.mybatis;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.sql.SQLException;
/**
* mybatis-plus 配置类
*
* @author zhaojun
*/
@Configuration
public class MyBatisPlusConfig {
/**
* mybatis plus 分页插件配置
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(DataSource dataSource) throws SQLException {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
String databaseProductName = dataSource.getConnection().getMetaData().getDatabaseProductName();
DbType dbType = DbType.getDbType(databaseProductName);
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(dbType));
return interceptor;
}
}

View File

@@ -0,0 +1,41 @@
package im.zhaojun.zfile.core.config.mybatis;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* MyBatis 数据库 ID Provider, 用于判断当前数据库类型来执行不同的 SQL 语句. <br>
* 可在 xml 中使用 <code>&lt;if test="_databaseId = 'mysql'"&gt; </code> 来判断数据库类型. <br>
* 也可以在外层使用,如 <code>&lt;delete id="xxx" databaseId="sqlite"&gt;</code> 来判断数据库类型.
*
* @author zhaojun
*/
@Component
public class MyDatabaseIdProvider implements DatabaseIdProvider {
private static final String DATABASE_MYSQL = "MySQL";
private static final String DATABASE_SQLITE = "SQLite";
@Override
public String getDatabaseId(DataSource dataSource) throws SQLException {
Connection conn = dataSource.getConnection();
String dbName = conn.getMetaData().getDatabaseProductName();
String dbAlias = "";
switch (dbName) {
case DATABASE_MYSQL:
dbAlias = "mysql";
break;
case DATABASE_SQLITE:
dbAlias = "sqlite";
break;
default:
break;
}
return dbAlias;
}
}

View File

@@ -0,0 +1,29 @@
package im.zhaojun.zfile.core.config.mybatis;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* MyBatis Plus 自动填充配置类
* 用于自动填充 createTime 和 updateTime 字段
*
* @author zhaojun
*/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}

View File

@@ -0,0 +1,32 @@
package im.zhaojun.zfile.core.config.security;
import cn.dev33.satoken.session.SaSession;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
/**
* Jackson 定制版 SaSession忽略 timeout 等属性的序列化
*
* @author click33
* @since 1.34.0
*/
@JsonIgnoreProperties({"timeout"})
public class SaSessionForJacksonCustomized extends SaSession {
/**
*
*/
private static final long serialVersionUID = -7600983549653130681L;
public SaSessionForJacksonCustomized() {
super();
}
/**
* 构建一个Session对象
* @param id Session的id
*/
public SaSessionForJacksonCustomized(String id) {
super(id);
}
}

View File

@@ -0,0 +1,34 @@
package im.zhaojun.zfile.core.config.security;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* SaToken 权限配置, 配置管理员才能访问管理员功能.
*
* @author zhaojun
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/**
* 注册权限校验拦截器, 拦截所有 /admin/** 请求,但不包含 /admin 因为这个是登录页面.
*
* @param registry
* 拦截器注册器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor(handle -> {
SaRouter.match("/admin/**", () -> {
StpUtil.checkLogin();
StpUtil.checkRole("admin");
});
})).addPathPatterns("/**").excludePathPatterns("/admin");
}
}

View File

@@ -0,0 +1,305 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package im.zhaojun.zfile.core.config.security;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Sa-Token 持久层实现 [ Redis存储、Jackson序列化 ]
*
* @author click33
* @since 1.34.0
*/
@Component
@ConditionalOnProperty(name = "spring.data.redis.host")
public class SaTokenDaoRedisJackson implements SaTokenDao {
public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static final String DATE_PATTERN = "yyyy-MM-dd";
public static final String TIME_PATTERN = "HH:mm:ss";
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_PATTERN);
public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(TIME_PATTERN);
/**
* ObjectMapper 对象 (以 public 作用域暴露出此对象,方便开发者二次更改配置)
*
* <p> 例如:
* <pre>
* SaTokenDaoRedisJackson redisJackson = (SaTokenDaoRedisJackson) SaManager.getSaTokenDao();
* redisJackson.objectMapper.xxx = xxx;
* </pre>
* </p>
*/
public ObjectMapper objectMapper;
/**
* String 读写专用
*/
public StringRedisTemplate stringRedisTemplate;
/**
* Object 读写专用
*/
public RedisTemplate<String, Object> objectRedisTemplate;
/**
* 标记:是否已初始化成功
*/
public boolean isInit;
@Autowired
public void init(RedisConnectionFactory connectionFactory) {
// 如果已经初始化成功了,就立刻退出,不重复初始化
if(this.isInit) {
return;
}
// 指定相应的序列化方案
StringRedisSerializer keySerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer();
// 通过反射获取Mapper对象, 增加一些配置, 增强兼容性
try {
Field field = GenericJackson2JsonRedisSerializer.class.getDeclaredField("mapper");
field.setAccessible(true);
this.objectMapper = (ObjectMapper) field.get(valueSerializer);
// 配置[忽略未知字段]
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 配置[时间类型转换]
JavaTimeModule timeModule = new JavaTimeModule();
// LocalDateTime序列化与反序列化
timeModule.addSerializer(new LocalDateTimeSerializer(DATE_TIME_FORMATTER));
timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER));
// LocalDate序列化与反序列化
timeModule.addSerializer(new LocalDateSerializer(DATE_FORMATTER));
timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DATE_FORMATTER));
// LocalTime序列化与反序列化
timeModule.addSerializer(new LocalTimeSerializer(TIME_FORMATTER));
timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(TIME_FORMATTER));
this.objectMapper.registerModule(timeModule);
// 重写 SaSession 生成策略
SaStrategy.instance.createSession = (sessionId) -> new SaSessionForJacksonCustomized(sessionId);
} catch (Exception e) {
System.err.println(e.getMessage());
}
// 构建StringRedisTemplate
StringRedisTemplate stringTemplate = new StringRedisTemplate();
stringTemplate.setConnectionFactory(connectionFactory);
stringTemplate.afterPropertiesSet();
// 构建RedisTemplate
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(keySerializer);
template.setHashKeySerializer(keySerializer);
template.setValueSerializer(valueSerializer);
template.setHashValueSerializer(valueSerializer);
template.afterPropertiesSet();
// 开始初始化相关组件
this.stringRedisTemplate = stringTemplate;
this.objectRedisTemplate = template;
// 打上标记,表示已经初始化成功,后续无需再重新初始化
this.isInit = true;
}
/**
* 获取Value如无返空
*/
@Override
public String get(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
/**
* 写入Value并设定存活时间 (单位: 秒)
*/
@Override
public void set(String key, String value, long timeout) {
if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
// 判断是否为永不过期
if(timeout == SaTokenDao.NEVER_EXPIRE) {
stringRedisTemplate.opsForValue().set(key, value);
} else {
stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
}
/**
* 修修改指定key-value键值对 (过期时间不变)
*/
@Override
public void update(String key, String value) {
long expire = getTimeout(key);
// -2 = 无此键
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.set(key, value, expire);
}
/**
* 删除Value
*/
@Override
public void delete(String key) {
stringRedisTemplate.delete(key);
}
/**
* 获取Value的剩余存活时间 (单位: 秒)
*/
@Override
public long getTimeout(String key) {
return stringRedisTemplate.getExpire(key);
}
/**
* 修改Value的剩余存活时间 (单位: 秒)
*/
@Override
public void updateTimeout(String key, long timeout) {
// 判断是否想要设置为永久
if(timeout == SaTokenDao.NEVER_EXPIRE) {
long expire = getTimeout(key);
if(expire == SaTokenDao.NEVER_EXPIRE) {
// 如果其已经被设置为永久,则不作任何处理
} else {
// 如果尚未被设置为永久那么再次set一次
this.set(key, this.get(key), timeout);
}
return;
}
stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 获取Object如无返空
*/
@Override
public Object getObject(String key) {
return objectRedisTemplate.opsForValue().get(key);
}
/**
* 写入Object并设定存活时间 (单位: 秒)
*/
@Override
public void setObject(String key, Object object, long timeout) {
if(timeout == 0 || timeout <= SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
// 判断是否为永不过期
if(timeout == SaTokenDao.NEVER_EXPIRE) {
objectRedisTemplate.opsForValue().set(key, object);
} else {
objectRedisTemplate.opsForValue().set(key, object, timeout, TimeUnit.SECONDS);
}
}
/**
* 更新Object (过期时间不变)
*/
@Override
public void updateObject(String key, Object object) {
long expire = getObjectTimeout(key);
// -2 = 无此键
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) {
return;
}
this.setObject(key, object, expire);
}
/**
* 删除Object
*/
@Override
public void deleteObject(String key) {
objectRedisTemplate.delete(key);
}
/**
* 获取Object的剩余存活时间 (单位: 秒)
*/
@Override
public long getObjectTimeout(String key) {
return objectRedisTemplate.getExpire(key);
}
/**
* 修改Object的剩余存活时间 (单位: 秒)
*/
@Override
public void updateObjectTimeout(String key, long timeout) {
// 判断是否想要设置为永久
if(timeout == SaTokenDao.NEVER_EXPIRE) {
long expire = getObjectTimeout(key);
if(expire == SaTokenDao.NEVER_EXPIRE) {
// 如果其已经被设置为永久,则不作任何处理
} else {
// 如果尚未被设置为永久那么再次set一次
this.setObject(key, this.getObject(key), timeout);
}
return;
}
objectRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 搜索数据
*/
@Override
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
Set<String> keys = stringRedisTemplate.keys(prefix + "*" + keyword + "*");
List<String> list = new ArrayList<>(keys);
return SaFoxUtil.searchList(list, start, size, sortType);
}
}

View File

@@ -0,0 +1,44 @@
package im.zhaojun.zfile.core.config.security;
import cn.dev33.satoken.stp.StpInterface;
import cn.hutool.core.convert.Convert;
import im.zhaojun.zfile.module.user.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
/**
* 自定义权限加载接口实现类
*
* @author zhaojun
*/
@Component
public class StpInterfaceImpl implements StpInterface {
private static final List<String> ADMIN_ROLE_LIST = Collections.singletonList("admin");
public static final List<String> EMPTY_ROLE_LIST = Collections.emptyList();
@Resource
private UserService userService;
/**
* 返回一个账号所拥有的权限码集合,这里没用到这个功能,所以返回空集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
return Collections.emptyList();
}
/**
* 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
boolean isAdmin = userService.isAdmin(Convert.toInt(loginId));
return isAdmin ? ADMIN_ROLE_LIST : EMPTY_ROLE_LIST;
}
}

View File

@@ -0,0 +1,82 @@
package im.zhaojun.zfile.core.config.spring;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.jackson.JsonComponent;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* Jackson 枚举反序列化器, 用于将接收请求中的参数(一般为字符串)转换为枚举对象.
*
* @author zhaojun
*/
@Setter
@Slf4j
@JsonComponent
public class JacksonEnumDeserializer extends JsonDeserializer<Enum<?>> implements ContextualDeserializer {
private Class<?> clazz;
/**
* 反序列化操作
*
* @param jsonParser
* json 解析器
*
* @param ctx
* 反序列化上下文
*
* @return 反序列化后的枚举值
* @throws IOException 反序列化异常
*/
@Override
public Enum<?> deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
Class<?> enumType = clazz;
if (Objects.isNull(enumType) || !enumType.isEnum()) {
return null;
}
String text = jsonParser.getText();
Method method = StringToEnumConverterFactory.getMethod(clazz);
Enum<?>[] enumConstants = (Enum<?>[]) enumType.getEnumConstants();
// 将值与枚举对象对应并缓存
for (Enum<?> e : enumConstants) {
try {
if (Objects.equals(method.invoke(e).toString(), text)) {
return e;
}
} catch (IllegalAccessException | InvocationTargetException ex) {
log.error("获取枚举值错误!!! ", ex);
}
}
return null;
}
/**
* 为不同的枚举获取合适的解析器
*
* @param ctx
* 反序列化上下文
*
* @param property
* property
*/
@Override
public JsonDeserializer<Enum<?>> createContextual(DeserializationContext ctx, BeanProperty property) {
Class<?> rawCls = ctx.getContextualType().getRawClass();
JacksonEnumDeserializer converter = new JacksonEnumDeserializer();
converter.setClazz(rawCls);
return converter;
}
}

View File

@@ -0,0 +1,36 @@
package im.zhaojun.zfile.core.config.spring;
import im.zhaojun.zfile.core.config.security.SaTokenDaoRedisJackson;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.cache.support.NoOpCacheManager;
import org.springframework.cache.transaction.TransactionAwareCacheManagerProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Spring Cache 相关配置
*
* @author zhaojun
*/
@Configuration
@EnableCaching
public class SpringCacheConfig {
@Value("${zfile.dbCache.enable:true}")
private Boolean dbCacheEnable;
/**
* 使用 TransactionAwareCacheManagerProxy 装饰 ConcurrentMapCacheManager使其支持事务 (将 put、evict、clear 操作延迟到事务成功提交再执行.
*/
@Bean
@ConditionalOnMissingBean(SaTokenDaoRedisJackson.class)
public CacheManager cacheManager() {
return BooleanUtils.isNotTrue(dbCacheEnable) ? new NoOpCacheManager() : new TransactionAwareCacheManagerProxy(new ConcurrentMapCacheManager());
}
}

View File

@@ -0,0 +1,117 @@
package im.zhaojun.zfile.core.config.spring;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import im.zhaojun.zfile.core.exception.core.SystemException;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
* String 转枚举通用转换器工厂
*
* @author zhaojun
*/
@Slf4j
public class StringToEnumConverterFactory implements ConverterFactory<String, Enum<?>> {
/**
* 存储枚举类型的缓存
*/
private static final Map<Class<?>, Converter<String, ? extends Enum<?>>> CONVERTER_MAP = new ConcurrentHashMap<>();
/**
* 枚举类的获取枚举值方法缓存
*/
private static final Map<Class<?>, Method> TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap<>();
@Override
@SuppressWarnings("unchecked cast")
public <T extends Enum<?>> Converter<String, T> getConverter(Class<T> targetType) {
// 缓存转换器
Converter<String, T> converter = (Converter<String, T>) CONVERTER_MAP.get(targetType);
if (converter == null) {
converter = new StringToEnumConverter<>(targetType);
CONVERTER_MAP.put(targetType, converter);
}
return converter;
}
static class StringToEnumConverter<T extends Enum<?>> implements Converter<String, T> {
private final Map<String, T> enumMap = new ConcurrentHashMap<>();
StringToEnumConverter(Class<T> enumType) {
Method method = getMethod(enumType);
T[] enums = enumType.getEnumConstants();
// 将值与枚举对象对应并缓存
for (T e : enums) {
try {
enumMap.put(method.invoke(e).toString(), e);
} catch (IllegalAccessException | InvocationTargetException ex) {
log.error("获取枚举值错误!!! ", ex);
}
}
}
@Override
public T convert(@NotNull String source) {
// 获取
T t = enumMap.get(source);
if (t == null) {
throw new SystemException("该字符串找不到对应的枚举对象 字符串:" + source);
}
return t;
}
}
public static <T> Method getMethod(Class<T> enumType) {
Method method;
// 找到取值的方法
if (IEnum.class.isAssignableFrom(enumType)) {
try {
method = enumType.getMethod("getValue");
} catch (NoSuchMethodException e) {
throw new SystemException(String.format("类:%s 找不到 getValue方法",
enumType.getName()));
}
} else {
method = TABLE_METHOD_OF_ENUM_TYPES.computeIfAbsent(enumType, k -> {
Field field =
dealEnumType(enumType).orElseThrow(() -> new IllegalArgumentException(String.format(
"类:%s 找不到 EnumValue注解", enumType.getName())));
Class<?> fieldType = field.getType();
String fieldName = field.getName();
String methodName = StringUtils.concatCapitalize(boolean.class.equals(fieldType) ? "is" : "get", fieldName);
try {
return enumType.getDeclaredMethod(methodName);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
return null;
});
}
return method;
}
private static Optional<Field> dealEnumType(Class<?> clazz) {
return clazz.isEnum() ?
Arrays.stream(clazz.getDeclaredFields()).filter(field -> field.isAnnotationPresent(EnumValue.class)).findFirst() : Optional.empty();
}
}

View File

@@ -1,6 +1,6 @@
package im.zhaojun.zfile.config;
package im.zhaojun.zfile.core.config.spring;
import im.zhaojun.zfile.model.enums.StorageTypeEnumDeSerializerConvert;
import im.zhaojun.zfile.module.storage.model.enums.StorageTypeEnum;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
@@ -9,26 +9,35 @@ import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* ZFile Web 相关配置.
*
* @author zhaojun
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StorageTypeEnumDeSerializerConvert());
}
/**
* 支持 url 中传入 <>[\]^`{|} 这些特殊字符.
*/
@Bean
public ServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory webServerFactory = new TomcatServletWebServerFactory();
// 添加对 URL 中特殊符号的支持.
webServerFactory.addConnectorCustomizers(connector -> {
connector.setAttribute("relaxedPathChars", "<>[\\]^`{|}");
connector.setAttribute("relaxedQueryChars", "<>[\\]^`{|}");
connector.setProperty("relaxedPathChars", "<>[\\]^`{|}%[]");
connector.setProperty("relaxedQueryChars", "<>[\\]^`{|}%[]");
});
return webServerFactory;
}
/**
* 添加自定义枚举格式化器.
* @see StorageTypeEnum
*/
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new StringToEnumConverterFactory());
}
}

View File

@@ -0,0 +1,91 @@
package im.zhaojun.zfile.core.config.totp;
import dev.samstevens.totp.TotpInfo;
import dev.samstevens.totp.code.*;
import dev.samstevens.totp.qr.QrDataFactory;
import dev.samstevens.totp.qr.QrGenerator;
import dev.samstevens.totp.qr.ZxingPngQrGenerator;
import dev.samstevens.totp.recovery.RecoveryCodeGenerator;
import dev.samstevens.totp.secret.DefaultSecretGenerator;
import dev.samstevens.totp.secret.SecretGenerator;
import dev.samstevens.totp.time.SystemTimeProvider;
import dev.samstevens.totp.time.TimeProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass({TotpInfo.class})
@EnableConfigurationProperties({TotpProperties.class})
public class TotpAutoConfiguration {
private final TotpProperties props;
@Autowired
public TotpAutoConfiguration(TotpProperties props) {
this.props = props;
}
@Bean
@ConditionalOnMissingBean
public SecretGenerator secretGenerator() {
int length = this.props.getSecret().getLength();
return new DefaultSecretGenerator(length);
}
@Bean
@ConditionalOnMissingBean
public TimeProvider timeProvider() {
return new SystemTimeProvider();
}
@Bean
@ConditionalOnMissingBean
public HashingAlgorithm hashingAlgorithm() {
return HashingAlgorithm.SHA1;
}
@Bean
@ConditionalOnMissingBean
public QrDataFactory qrDataFactory(HashingAlgorithm hashingAlgorithm) {
return new QrDataFactory(hashingAlgorithm, this.getCodeLength(), this.getTimePeriod());
}
@Bean
@ConditionalOnMissingBean
public QrGenerator qrGenerator() {
return new ZxingPngQrGenerator();
}
@Bean
@ConditionalOnMissingBean
public CodeGenerator codeGenerator(HashingAlgorithm algorithm) {
return new DefaultCodeGenerator(algorithm, this.getCodeLength());
}
@Bean
@ConditionalOnMissingBean
public CodeVerifier codeVerifier(CodeGenerator codeGenerator, TimeProvider timeProvider) {
DefaultCodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider);
verifier.setTimePeriod(this.getTimePeriod());
verifier.setAllowedTimePeriodDiscrepancy(this.props.getTime().getDiscrepancy());
return verifier;
}
@Bean
@ConditionalOnMissingBean
public RecoveryCodeGenerator recoveryCodeGenerator() {
return new RecoveryCodeGenerator();
}
private int getCodeLength() {
return this.props.getCode().getLength();
}
private int getTimePeriod() {
return this.props.getTime().getPeriod();
}
}

View File

@@ -0,0 +1,85 @@
package im.zhaojun.zfile.core.config.totp;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(
prefix = "totp"
)
public class TotpProperties {
private static final int DEFAULT_SECRET_LENGTH = 32;
private static final int DEFAULT_CODE_LENGTH = 6;
private static final int DEFAULT_TIME_PERIOD = 30;
private static final int DEFAULT_TIME_DISCREPANCY = 1;
private final Secret secret = new Secret();
private final Code code = new Code();
private final Time time = new Time();
public TotpProperties() {
}
public Secret getSecret() {
return this.secret;
}
public Code getCode() {
return this.code;
}
public Time getTime() {
return this.time;
}
public static class Time {
private int period = 30;
private int discrepancy = 1;
public Time() {
}
public int getPeriod() {
return this.period;
}
public void setPeriod(int period) {
this.period = period;
}
public int getDiscrepancy() {
return this.discrepancy;
}
public void setDiscrepancy(int discrepancy) {
this.discrepancy = discrepancy;
}
}
public static class Code {
private int length = 6;
public Code() {
}
public int getLength() {
return this.length;
}
public void setLength(int length) {
this.length = length;
}
}
public static class Secret {
private int length = 32;
public Secret() {
}
public int getLength() {
return this.length;
}
public void setLength(int length) {
this.length = length;
}
}
}

View File

@@ -0,0 +1,16 @@
package im.zhaojun.zfile.core.constant;
/**
* Slf4j mdc 常量
*
* @author zhaojun
*/
public class MdcConstant {
public static final String TRACE_ID = "traceId";
public static final String IP = "ip";
public static final String USER = "user";
}

View File

@@ -0,0 +1,18 @@
package im.zhaojun.zfile.core.constant;
/**
* 规则表达式类型常量
*
* @author zhaojun
*/
public class RuleTypeConstant {
public static final String IP = "ip";
public static final String REGEX = "regex";
public static final String ANT_PATH = "antPath";
public static final String SPRING_SIMPLE = "springSimple";
}

View File

@@ -0,0 +1,25 @@
package im.zhaojun.zfile.core.constant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
/**
* ZFile 常量
*
* @author zhaojun
*/
@Configuration
public class ZFileConstant {
/**
* 最大支持文本文件大小为 ? KB 的文件内容.
*/
public static Long TEXT_MAX_FILE_SIZE_KB = 100L;
@Autowired(required = false)
public void setTextMaxFileSizeMb(@Value("${zfile.preview.text.maxFileSizeKb}") Long maxFileSizeKb) {
ZFileConstant.TEXT_MAX_FILE_SIZE_KB = maxFileSizeKb;
}
}

View File

@@ -0,0 +1,16 @@
package im.zhaojun.zfile.core.constant;
/**
* ZFile 自定义 HTTP 请求头常量
*
* @author zhaojun
*/
public class ZFileHttpHeaderConstant {
public static final String ZFILE_TOKEN = "Zfile-Token";
public static final String AXIOS_REQUEST = "Axios-Request";
public static final String AXIOS_FROM = "Axios-From";
}

View File

@@ -0,0 +1,102 @@
package im.zhaojun.zfile.core.controller;
import im.zhaojun.zfile.core.util.StringUtils;
import im.zhaojun.zfile.module.config.model.dto.SystemConfigDTO;
import im.zhaojun.zfile.module.config.service.SystemConfigService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.WebProperties;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.nio.charset.StandardCharsets;
/**
* 处理前端首页 Controller
*
* @author zhaojun
*/
@Slf4j
@Controller
public class FrontIndexController {
@Resource
private SystemConfigService systemConfigService;
@Resource
private WebProperties webProperties;
/**
* 所有未找到的页面都跳转到首页, 用户解决 vue history 直接访问 404 的问题
* 同时, 读取 index.html 文件, 修改 title 和 favicon 后返回.
*
* @return 转发到 /index.html
*/
@RequestMapping(value = { "/"})
@ResponseBody
public ResponseEntity<String> redirect() {
// 读取 resources/static/index.html 文件修改 title 和 favicon 后返回
ResourceLoader resourceLoader = new FileSystemResourceLoader();
String[] staticLocations = webProperties.getResources().getStaticLocations();
// 如果 staticLocations 里没有包含 file:static/, 则手动添加
boolean fileStaticExist = false;
for (String staticLocation : staticLocations) {
if (staticLocation.startsWith("file:")) {
fileStaticExist = true;
break;
}
}
if (!fileStaticExist) {
staticLocations = org.apache.commons.lang3.ArrayUtils.add(staticLocations, "file:static/");
}
for (String staticLocation : staticLocations) {
org.springframework.core.io.Resource resource = resourceLoader.getResource(staticLocation + "/index.html");
boolean exists = resource.exists();
if (exists) {
String content;
try {
content = resource.getContentAsString(StandardCharsets.UTF_8);
log.debug("读取 index.html 文件成功, 文件路径: {}", staticLocation);
} catch (Exception e) {
log.error("{} 资源存在但读取 index.html 文件失败.", staticLocation);
return ResponseEntity.status(500).body("static index.html read error");
}
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
// 替换为系统设置中的站点名称
String siteName = systemConfig.getSiteName();
if (StringUtils.isNotBlank(siteName)) {
content = content.replace("<title>ZFile</title>", "<title>" + siteName + "</title>");
}
// 替换为系统设置中的 favicon 地址
String faviconUrl = systemConfig.getFaviconUrl();
if (StringUtils.isNotBlank(faviconUrl)) {
content = content.replace("/favicon.svg", faviconUrl);
}
// 添加缓存控制头
return ResponseEntity.ok()
.header("Cache-Control", "max-age=600, must-revalidate, proxy-revalidate") .header("Pragma", "no-cache")
.body(content);
}
}
return ResponseEntity.status(404).body("static index.html not found");
}
@RequestMapping(value = { "/guest"})
@ResponseBody
public String guest() {
SystemConfigDTO systemConfig = systemConfigService.getSystemConfig();
return systemConfig.getGuestIndexHtml();
}
}

View File

@@ -0,0 +1,49 @@
package im.zhaojun.zfile.core.controller;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ZipUtil;
import com.github.xiaoymin.knife4j.annotations.ApiSort;
import im.zhaojun.zfile.core.annotation.DemoDisable;
import im.zhaojun.zfile.core.util.FileResponseUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.util.Date;
/**
* 获取系统日志接口
*
* @author zhaojun
*/
@Tag(name = "日志")
@ApiSort(8)
@Slf4j
@RestController
@RequestMapping("/admin")
public class LogController {
@Value("${zfile.log.path}")
private String zfileLogPath;
@GetMapping("/log/download")
@Operation(summary = "下载系统日志")
@DemoDisable
public ResponseEntity<Resource> downloadLog() {
if (log.isDebugEnabled()) {
log.debug("下载诊断日志");
}
File fileZip = ZipUtil.zip(zfileLogPath);
String currentDate = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss");
return FileResponseUtil.exportSingleThread(fileZip, "ZFile 诊断日志 - " + currentDate + ".zip");
}
}

View File

@@ -0,0 +1,135 @@
package im.zhaojun.zfile.core.exception;
import lombok.Getter;
/**
* 异常信息枚举类
*
* @author zhaojun
*/
@Getter
public enum ErrorCode {
/**
* 系统异常
*/
SYSTEM_ERROR("50000", "系统异常"),
INVALID_STORAGE_SOURCE("50001", "无效或初始化失败的存储源"),
DEMO_SITE_DISABLE_OPERATOR("50002", "演示站点不允许此操作"),
/**
* 业务异常 4xxxx.
* 第二位为 0 时,是系统初始化相关错误
* 第二位为 1 时,是前台(文件管理)错误
* 第二位为 2 时,是登录错误
* 第二位为 3 时,是管理员端错误
*/
BIZ_ERROR("40000", "操作失败"),
BIZ_NOT_FOUND("40400", "NOT FOUND"),
// 第二位为 0 时,是系统初始化相关错误
BIZ_SYSTEM_ALREADY_INIT("40001", "系统已初始化,请勿重复初始化"),
BIZ_SYSTEM_INIT_ERROR("40002", "系统初始化错误"),
// 第二位为 1 时,是前台(文件管理)错误
BIZ_BAD_REQUEST("41000", "请求参数异常"),
BIZ_UNSUPPORTED_PROXY_DOWNLOAD("41001", "该存储源不支持代理下载"),
BIZ_INVALID_SIGNATURE("41002", "签名无效或下载地址已过期"),
BIZ_PREVIEW_FILE_SIZE_EXCEED("41003", "预览文本文件大小超出系统限制"),
BIZ_FILE_NOT_EXIST("41004", "文件不存在"),
BIZ_ACCESS_TOO_FREQUENT("41005", "请求太频繁了,请稍后再试"),
BIZ_UPLOAD_FILE_NOT_EMPTY("41006", "上传文件不能为空"),
BIZ_UPLOAD_FILE_ERROR("41010", "上传文件失败"),
BIZ_UPLOAD_FILE_TIMEOUT_ERROR("41026", "上传文件超时"),
BIZ_EXPIRE_TIME_ILLEGAL("41007", "过期时间不合法"),
BIZ_DELETE_FILE_NOT_EMPTY("41008", "非空文件夹不允许删除"),
BIZ_FILE_PATH_ILLEGAL("41009", "文件名/路径存在安全隐患"),
BIZ_DIRECT_LINK_NOT_ALLOWED("41011", "当前系统不允许使用直链"),
BIZ_SHORT_LINK_NOT_ALLOWED("41012", "当前系统不允许使用短链"),
BIZ_SHORT_LINK_EXPIRED("41013", "短链已失效"),
BIZ_SHORT_LINK_NOT_FOUNT("41014", "短链不存在"),
BIZ_DIRECT_LINK_EXPIRED("41015", "直链已失效"),
BIZ_STORAGE_NOT_SUPPORT_OPERATION("41016", "该存储类型不支持此操作"),
BIZ_STORAGE_NOT_FOUND("41017", "存储源不存在"),
BIZ_STORAGE_SOURCE_ILLEGAL_OPERATION("41018", "非法或未授权的操作"),
BIZ_STORAGE_SOURCE_FILE_FORBIDDEN("41019", "文件目录无访问权限"),
BIZ_STORAGE_SOURCE_FOLDER_PASSWORD_REQUIRED("41020", "此文件夹需要密码"),
BIZ_STORAGE_SOURCE_FOLDER_PASSWORD_ERROR("41021", "密码错误"),
BIZ_INVALID_FILE_NAME("41022", "文件名不合法"),
BIZ_UNSUPPORTED_OPERATION("41023", "不支持的操作"),
BIZ_FTP_CLIENT_POOL_FULL("41024", "FTP 客户端连接池已满"),
BIZ_SFTP_CLIENT_POOL_FULL("41025", "SFTP 客户端连接池已满"),
BIZ_FOLDER_NOT_EXIST("41026", "文件夹不存在"),
BIZ_UPLOAD_FILE_TYPE_NOT_ALLOWED("41027", "不允许上传的文件"),
BIZ_RENAME_FILE_TYPE_NOT_ALLOWED("41028", "不允许重命名到该名称"),
// 第二位为 2 时,是登录错误
BIZ_UNAUTHORIZED("42000", "未登录或未授权"),
BIZ_LOGIN_ERROR("42001", "登录失败, 账号或密码错误"),
BIZ_VERIFY_CODE_ERROR("42002", "验证码错误或已失效"),
// 第二位为 3 时,是管理员端错误
BIZ_ADMIN_ERROR("43000", "操作失败"),
BIZ_USER_NOT_EXIST("43001", "用户不存在"),
BIZ_USER_EXIST("43002", "用户已存在"),
BIZ_PASSWORD_NOT_SAME("43003", "两次密码不一致"),
BIZ_OLD_PASSWORD_ERROR("43004", "旧密码不匹配"),
BIZ_DELETE_BUILT_IN_USER("43005", "不能删除内置用户"),
BIZ_UNSUPPORTED_STORAGE_TYPE("43006", "不支持的存储类型"),
BIZ_STORAGE_KEY_EXIST("43007", "存储源别名已存在"),
BIZ_AUTO_GET_SHARE_POINT_SITES_ERROR("43008", "自动获取 SharePoint 网站列表失败"),
BIZ_ORIGINS_NOT_EMPTY("43009", "请先在 \"站点设置\" 中配置站点域名"),
BIZ_2FA_CODE_ERROR("43010", "两步验证失败"),
BIZ_STORAGE_INIT_ERROR("43011", "存储源初始化失败"),
BIZ_RULE_EXIST("43012", "规则已存在"),
/**
* 通用的无权限异常
*/
NO_FORBIDDEN("30000", "没有权限"),
/**
* 授权校验异常
*/
PRO_AUTH_CODE_EMPTY("20000", "请先去后台 \"基本设置\" 填写 \"授权码\""),
PRO_CHECK_REFERER_EMPTY("20001", "Referer 无效请检查服务端设置20001"), // Referer 无效,请检查服务端设置
PRO_CHECK_TIME_NO_SYNC("20002", "授权校验失败, 服务器时间异常20002"), // 授权校验失败, 服务器时间异常.
PRO_AUTH_CODE_INVALID_ERROR("20003", "授权码无效, 请检查后台 \"站点设置\" 中的 \"授权码\" 20003"),
PRO_CHECK_UNKNOWN_ERROR("20004", "授权验证异常未知异常20098"),
PRO_MSG_ERROR("20005", null);
private String code;
private String message;
ErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
/**
* 设置错误码
*
* @param code 错误码
* @return 返回当前枚举
*/
public ErrorCode setCode(String code) {
this.code = code;
return this;
}
/**
* 设置错误信息
*
* @param message 错误信息
* @return 返回当前枚举
*/
public ErrorCode setMessage(String message) {
this.message = message;
return this;
}
}

View File

@@ -0,0 +1,403 @@
package im.zhaojun.zfile.core.exception;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotRoleException;
import im.zhaojun.zfile.core.controller.FrontIndexController;
import im.zhaojun.zfile.core.exception.biz.*;
import im.zhaojun.zfile.core.exception.core.BizException;
import im.zhaojun.zfile.core.exception.core.ErrorPageBizException;
import im.zhaojun.zfile.core.exception.core.SystemException;
import im.zhaojun.zfile.core.exception.status.*;
import im.zhaojun.zfile.core.exception.system.UploadFileFailSystemException;
import im.zhaojun.zfile.core.exception.system.ZFileAuthorizationSystemException;
import im.zhaojun.zfile.core.util.AjaxJson;
import im.zhaojun.zfile.core.util.RequestHolder;
import im.zhaojun.zfile.core.util.StringUtils;
import im.zhaojun.zfile.module.config.service.SystemConfigService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.resource.NoResourceFoundException;
import org.sqlite.SQLiteException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* 全局异常处理
*
* @author zhaojun
*/
@ControllerAdvice
@Slf4j
@Order(1)
public class GlobalExceptionHandler {
private static final ThreadLocal<String> exceptionMessage = new ThreadLocal<>();
@Resource
private SystemConfigService systemConfigService;
@Resource
private FrontIndexController frontIndexController;
private static final int MAX_FIND_CAUSE_EXCEPTION_DEPTH = 10;
// ---------------------- status exception start ----------------------
@ExceptionHandler(value = UnauthorizedAccessException.class)
@ResponseBody
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public AjaxJson<?> unauthorizedAccessException() {
if (RequestHolder.isAxiosRequest()) {
return AjaxJson.getUnauthorizedResult();
}
try {
String unauthorizedUrl = systemConfigService.getUnauthorizedUrl();
RequestHolder.getResponse().sendRedirect(unauthorizedUrl);
} catch (IOException ex) {
return AjaxJson.getUnauthorizedResult();
}
return null;
}
@ExceptionHandler(value = {
NotRoleException.class
})
@ResponseBody
@ResponseStatus(HttpStatus.FORBIDDEN)
public AjaxJson<?> forbiddenAccessException() {
if (RequestHolder.isAxiosRequest()) {
return AjaxJson.getForbiddenResult();
}
try {
String forbiddenUrl = systemConfigService.getForbiddenUrl();
RequestHolder.getResponse().sendRedirect(forbiddenUrl);
} catch (IOException ex) {
return AjaxJson.getForbiddenResult();
}
return null;
}
@ExceptionHandler(value = ForbiddenAccessException.class)
@ResponseBody
@ResponseStatus(HttpStatus.FORBIDDEN)
public AjaxJson<?> forbiddenAccessException(ForbiddenAccessException e) {
if (RequestHolder.isAxiosRequest()) {
return AjaxJson.getError(e.getCode(), e.getMessage());
}
try {
String forbiddenUrl = systemConfigService.getForbiddenUrl(e.getCode(), e.getMessage());
RequestHolder.getResponse().sendRedirect(forbiddenUrl);
} catch (IOException ex) {
return AjaxJson.getError(e.getCode(), e.getMessage());
}
return null;
}
@ExceptionHandler(value = NotFoundAccessException.class)
@ResponseBody
@ResponseStatus(HttpStatus.NOT_FOUND)
public AjaxJson<?> notFoundAccessException(NotFoundAccessException e) {
if (RequestHolder.isAxiosRequest()) {
return AjaxJson.getError(e.getCode(), e.getMessage());
}
try {
String notFoundUrl = systemConfigService.getNotFoundUrl(e.getCode(), e.getMessage());
RequestHolder.getResponse().sendRedirect(notFoundUrl);
} catch (IOException ex) {
return AjaxJson.getError(e.getCode(), e.getMessage());
}
return null;
}
/**
* 所有未找到的页面都跳转到首页, 用户解决 vue history 直接访问 404 的问题
* 同时, 读取 index.html 文件, 修改 title 和 favicon 后返回.
*
* @return 转发到 /index.html
*/
@ExceptionHandler(value = NoResourceFoundException.class)
@ResponseBody
public String notFoundAccessException() {
return frontIndexController.redirect().getBody();
}
@ExceptionHandler(value = MethodNotAllowedAccessException.class)
@ResponseBody
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
public AjaxJson<String> methodNotAllowedAccessException(MethodNotAllowedAccessException e) {
return new AjaxJson<>(e.getCode(), e.getMessage());
}
@ExceptionHandler(value = BadRequestAccessException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public AjaxJson<String> badRequestAccessException(BadRequestAccessException e) {
return new AjaxJson<>(e.getCode(), e.getMessage());
}
// ---------------------- status exception end ----------------------
// ---------------------- biz exception start ----------------------
@ExceptionHandler(value = APIHttpRequestBizException.class)
@ResponseBody
@ResponseStatus
public AjaxJson<String> apiHttpRequestBizException(APIHttpRequestBizException e) {
log.warn("请求第三方 API 异常, 请求地址: {}, 响应码: {}, 响应体: {}", e.getUrl(), e.getResponseCode(), e.getResponseBody());
return new AjaxJson<>(e.getCode(), e.getMessage());
}
@ExceptionHandler(value = FilePathSecurityBizException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public AjaxJson<String> filePathSecurityBizException(FilePathSecurityBizException e) {
log.warn("获取文件路径存在安全风险, 文件路径: {}", e.getPath());
return new AjaxJson<>(e.getCode(), e.getMessage());
}
@ExceptionHandler(value = GetPreviewTextContentBizException.class)
@ResponseBody
@ResponseStatus
public AjaxJson<String> getPreviewTextContentBizException(GetPreviewTextContentBizException e) {
log.warn("获取预览文件内容失败, 文件 url: {}", e.getUrl(), e);
return new AjaxJson<>(e.getCode(), "预览文件内容失败, 请联系管理员.");
}
@ExceptionHandler(value = InitializeStorageSourceBizException.class)
@ResponseBody
@ResponseStatus
public AjaxJson<String> initializeStorageSourceBizException(InitializeStorageSourceBizException e) {
log.error("存储源初始化失败, 存储源 ID: {}.", e.getStorageId(), e);
return new AjaxJson<>(e.getCode(), "存储源初始化失败:" + e.getMessage());
}
@ExceptionHandler(value = StorageSourceFileForbiddenAccessBizException.class)
@ResponseBody
@ResponseStatus
public AjaxJson<String> storageSourceFileForbiddenAccessBizException(StorageSourceFileForbiddenAccessBizException e) {
log.warn("尝试访问不被授权的文件/目录, 存储源 ID: {}: 目录: {}", e.getStorageId(), e.getPath());
return new AjaxJson<>(e.getCode(), e.getMessage());
}
@ExceptionHandler(value = StorageSourceIllegalOperationBizException.class)
@ResponseBody
@ResponseStatus
public AjaxJson<String> storageSourceIllegalOperationBizException(StorageSourceIllegalOperationBizException e) {
log.warn("存储源非法或未授权的操作, 存储源 ID: {}, 操作类型: {}", e.getStorageId(), e.getAction());
return new AjaxJson<>(e.getCode(), e.getMessage());
}
@ExceptionHandler(value = CorsBizException.class)
@ResponseBody
@ResponseStatus
public AjaxJson<String> corsBizException(CorsBizException e) {
log.warn("跨域异常:", e);
return new AjaxJson<>(e.getCode(), e.getMessage());
}
@ExceptionHandler(value = ErrorPageBizException.class)
@ResponseBody
@ResponseStatus
public AjaxJson<?> errorPageBizException(ErrorPageBizException e) {
if (RequestHolder.isAxiosRequest()) {
return AjaxJson.getError(e.getCode(), e.getMessage());
}
try {
String errorPageUrl = systemConfigService.getErrorPageUrl(e.getCode(), e.getMessage());
RequestHolder.getResponse().sendRedirect(errorPageUrl);
} catch (IOException ex) {
return AjaxJson.getError(e.getCode(), e.getMessage());
}
return null;
}
@ExceptionHandler(value = BizException.class)
@ResponseBody
@ResponseStatus
public AjaxJson<String> bizException(BizException e) {
return new AjaxJson<>(e.getCode(), e.getMessage());
}
// ---------------------- biz exception end ----------------------
// ---------------------- system exception end ----------------------
@ExceptionHandler(value = UploadFileFailSystemException.class)
@ResponseBody
@ResponseStatus
public AjaxJson<String> uploadFileFailSystemException(UploadFileFailSystemException e) {
log.warn("上传文件失败, 存储类型: {}, 上传路径: {}, 输入流可用字节数: {}, 响应码: {}, 响应体: {}",
e.getStorageTypeEnum(), e.getUploadPath(), e.getInputStreamAvailable(), e.getResponseCode(), e.getResponseBody());
return new AjaxJson<>(e.getCode(), e.getMessage());
}
@ExceptionHandler(value = ZFileAuthorizationSystemException.class)
@ResponseBody
@ResponseStatus
public AjaxJson<?> zfileAuthorizationSystemException(ZFileAuthorizationSystemException e) {
return new AjaxJson<>(e.getCode(), e.getMessage());
}
@ExceptionHandler(value = SystemException.class)
@ResponseBody
@ResponseStatus
public AjaxJson<?> systemException(SystemException e) {
return new AjaxJson<>(e.getCode(), e.getMessage());
}
// ---------------------- system exception end ----------------------
// ---------------------- common exception end ----------------------
@ExceptionHandler(value = {MethodArgumentNotValidException.class, BindException.class})
@ResponseBody
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
public AjaxJson<Map<String, String>> handleValidException(Exception e) {
BindingResult bindingResult = null;
if (e instanceof MethodArgumentNotValidException) {
bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
} else if (e instanceof BindException) {
bindingResult = ((BindException) e).getBindingResult();
}
Map<String, String> errorMap = new HashMap<>(16);
Optional.ofNullable(bindingResult)
.map(BindingResult::getFieldErrors)
.ifPresent(fieldErrors -> {
for (FieldError fieldError : fieldErrors) {
errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
}
});
return new AjaxJson<>(ErrorCode.BIZ_BAD_REQUEST.getCode(), ErrorCode.BIZ_BAD_REQUEST.getMessage(), errorMap);
}
@ExceptionHandler({FileNotFoundException.class})
@ResponseBody
@ResponseStatus(HttpStatus.NOT_FOUND)
public AjaxJson<Void> fileNotFound() {
return AjaxJson.getError("文件不存在");
}
/**
* 登录异常拦截器
*/
@ExceptionHandler(NotLoginException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ResponseBody
public AjaxJson<?> handlerNotLoginException(NotLoginException e) {
if (RequestHolder.isAxiosRequest()) {
return AjaxJson.getUnauthorizedResult();
}
try {
String domain = systemConfigService.getRealFrontDomain();
if (StringUtils.isBlank(domain)) {
domain = "";
}
String loginUrl = StringUtils.concat(domain, "/login");
RequestHolder.getResponse().sendRedirect(loginUrl);
} catch (IOException ex) {
return AjaxJson.getUnauthorizedResult();
}
return null;
}
@ExceptionHandler
@ResponseBody
@ResponseStatus
public AjaxJson<?> extraExceptionHandler(Exception e) {
ExceptionType exceptionType = getExceptionType(e);
if (exceptionType == ExceptionType.IGNORE_PRINT_STACK_TRACE_EXCEPTION) {
log.warn(e.getMessage());
} else if (exceptionType == ExceptionType.OTHER) {
log.error(e.getMessage(), e);
} else if (exceptionType == ExceptionType.SPECIFY_MESSAGE_EXCEPTION) {
if (exceptionMessage.get() != null) {
String message = exceptionMessage.get();
log.error("发生异常: {}", message,e );
exceptionMessage.remove();
return AjaxJson.getError(message);
}
}
if (e.getClass() == Exception.class) {
return AjaxJson.getError("系统异常, 请联系管理员");
} else {
return AjaxJson.getError(e.getMessage());
}
}
private static ExceptionType getExceptionType(Exception e) {
int findCauseCount = 0;
do {
if (e instanceof BizException) {
return ExceptionType.IGNORE_PRINT_STACK_TRACE_EXCEPTION;
} else if (e instanceof ClientAbortException) {
return ExceptionType.IGNORE_EXCEPTION;
} else if (e instanceof SQLiteException && e.getMessage().contains("database is locked")) {
exceptionMessage.set("数据库繁忙,请稍后再试");
return ExceptionType.SPECIFY_MESSAGE_EXCEPTION;
}
e = (Exception) e.getCause();
findCauseCount++;
} while (e != null && findCauseCount < MAX_FIND_CAUSE_EXCEPTION_DEPTH);
return ExceptionType.OTHER;
}
enum ExceptionType {
/**
* 忽略打印异常信息和堆栈信息
*/
IGNORE_EXCEPTION,
/**
* 仅打印异常信息, 不打印堆栈信息
*/
IGNORE_PRINT_STACK_TRACE_EXCEPTION,
/**
* 不打印堆栈信息,但指定异常信息
*/
SPECIFY_MESSAGE_EXCEPTION,
/**
* 其他异常, 打印异常信息和堆栈信息
*/
OTHER;
}
}

View File

@@ -0,0 +1,31 @@
package im.zhaojun.zfile.core.exception.biz;
import im.zhaojun.zfile.core.exception.ErrorCode;
import im.zhaojun.zfile.core.exception.core.BizException;
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
import lombok.Getter;
/**
* 请求第三方 API 时如果返回非 2xx 状态码, 则抛出此异常. 需记录请求地址, 响应状态码, 响应内容.
* <p/>
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#apiHttpRequestBizException(APIHttpRequestBizException)}
*
* @author zhaojun
*/
@Getter
public class APIHttpRequestBizException extends BizException {
private final String url;
private final int responseCode;
private final String responseBody;
public APIHttpRequestBizException(ErrorCode errorCode, String url, int responseCode, String responseBody) {
super(errorCode);
this.url = url;
this.responseCode = responseCode;
this.responseBody = responseBody;
}
}

View File

@@ -0,0 +1,21 @@
package im.zhaojun.zfile.core.exception.biz;
import im.zhaojun.zfile.core.exception.core.BizException;
import lombok.Getter;
/**
* @author zhaojun
*/
@Getter
public class CorsBizException extends BizException {
public CorsBizException(String message, Throwable cause) {
super(message, cause);
}
@Override
public boolean printExceptionStackTrace() {
return true;
}
}

View File

@@ -0,0 +1,26 @@
package im.zhaojun.zfile.core.exception.biz;
import im.zhaojun.zfile.core.exception.ErrorCode;
import im.zhaojun.zfile.core.exception.core.BizException;
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
import lombok.Getter;
/**
* 文件路径安全异常, 表示文件路径不合法,如包含了 "./" 或 "../" 等字符来尝试访问非法目录.
* <p/>
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#filePathSecurityBizException(FilePathSecurityBizException)}
*
* @author zhaojun
*/
@Getter
public class FilePathSecurityBizException extends BizException {
private final String path;
public FilePathSecurityBizException(String path) {
super(ErrorCode.BIZ_FILE_PATH_ILLEGAL);
this.path = path;
}
}

View File

@@ -0,0 +1,27 @@
package im.zhaojun.zfile.core.exception.biz;
import im.zhaojun.zfile.core.exception.core.BizException;
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
import lombok.Getter;
/**
* 获取预览文件内容异常, 可能是目标连接无法访问/文件不存在等原因.
* <p/>
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#getPreviewTextContentBizException(GetPreviewTextContentBizException)}
*
* @author zhaojun
*/
@Getter
public class GetPreviewTextContentBizException extends BizException {
/**
* 获取预览文件的 URL
*/
private final String url;
public GetPreviewTextContentBizException(String url, Throwable cause) {
super(cause);
this.url = url;
}
}

View File

@@ -0,0 +1,29 @@
package im.zhaojun.zfile.core.exception.biz;
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
import im.zhaojun.zfile.core.exception.core.BizException;
import lombok.Getter;
/**
* 初始化存储源时失败产生的异常
* <p/>
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#initializeStorageSourceBizException(InitializeStorageSourceBizException)}
*
* @author zhaojun
*/
@Getter
public class InitializeStorageSourceBizException extends BizException {
private final Integer storageId;
public InitializeStorageSourceBizException(String message, Integer storageId) {
super(message);
this.storageId = storageId;
}
public InitializeStorageSourceBizException(String code, String message, Integer storageId, Throwable cause) {
super(code, message, cause);
this.storageId = storageId;
}
}

View File

@@ -0,0 +1,31 @@
package im.zhaojun.zfile.core.exception.biz;
import im.zhaojun.zfile.core.exception.ErrorCode;
import im.zhaojun.zfile.core.exception.core.BizException;
import lombok.Getter;
/**
* 不存在或初始化失败的存储源异常。
*
* @author zhaojun
*/
@Getter
public class InvalidStorageSourceBizException extends BizException {
private final Integer storageId;
private final String storageKey;
public InvalidStorageSourceBizException(String storageKey) {
super(ErrorCode.INVALID_STORAGE_SOURCE);
this.storageKey = storageKey;
this.storageId = null;
}
public InvalidStorageSourceBizException(Integer storageId) {
super(ErrorCode.INVALID_STORAGE_SOURCE);
this.storageId = storageId;
this.storageKey = null;
}
}

View File

@@ -0,0 +1,27 @@
package im.zhaojun.zfile.core.exception.biz;
import im.zhaojun.zfile.core.exception.ErrorCode;
import im.zhaojun.zfile.core.exception.core.BizException;
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
import lombok.Getter;
/**
* 访问了禁止访问的存储源文件/目录时抛出此异常.
* <p/>
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#storageSourceFileForbiddenAccessBizException(StorageSourceFileForbiddenAccessBizException)}
*
* @author zhaojun
*/
@Getter
public class StorageSourceFileForbiddenAccessBizException extends BizException {
private final Integer storageId;
private final String path;
public StorageSourceFileForbiddenAccessBizException(Integer storageId, String path) {
super(ErrorCode.BIZ_STORAGE_SOURCE_FILE_FORBIDDEN);
this.storageId = storageId;
this.path = path;
}
}

View File

@@ -0,0 +1,28 @@
package im.zhaojun.zfile.core.exception.biz;
import im.zhaojun.zfile.core.exception.ErrorCode;
import im.zhaojun.zfile.core.exception.core.BizException;
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
import im.zhaojun.zfile.module.storage.model.enums.FileOperatorTypeEnum;
import lombok.Getter;
/**
* 对存储源进行非法(未授权)的操作产生的异常
* <p/>
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#storageSourceIllegalOperationBizException(StorageSourceIllegalOperationBizException)}
*
* @author zhaojun
*/
@Getter
public class StorageSourceIllegalOperationBizException extends BizException {
private final Integer storageId;
private final FileOperatorTypeEnum action;
public StorageSourceIllegalOperationBizException(Integer storageId, FileOperatorTypeEnum action) {
super(ErrorCode.BIZ_STORAGE_SOURCE_ILLEGAL_OPERATION);
this.storageId = storageId;
this.action = action;
}
}

View File

@@ -0,0 +1,101 @@
package im.zhaojun.zfile.core.exception.core;
import im.zhaojun.zfile.core.exception.ErrorCode;
import lombok.Getter;
/**
* 业务异常,该类异常用户可自行处理,无需记录日志,属于正常业务流程中的异常. 如: 用户名密码错误, 未登录等.
*
* @author zhaojun
*/
@Getter
public class BizException extends RuntimeException {
private static final long serialVersionUID = 8312907182931723379L;
/**
* 错误码
*/
private String code;
/**
* 是否打印堆栈信息,业务异常默认不打印堆栈信息,如果需要打印堆栈信息,可以通过子类覆盖该方法修改返回值为 true.
*/
public boolean printExceptionStackTrace() {
return false;
}
/**
* 构造一个没有错误信息的 <code>SystemException</code>
*/
public BizException() {
super();
}
/**
* 使用指定的 Throwable 和 Throwable.toString() 作为异常信息来构造 SystemException
*
* @param cause 错误原因, 通过 Throwable.getCause() 方法可以获取传入的 cause信息
*/
public BizException(Throwable cause) {
super(cause);
}
/**
* 使用错误信息 message 构造 SystemException
*
* @param message 错误信息
*/
public BizException(String message) {
super(message);
}
/**
* 使用错误码和错误信息构造 SystemException
*
* @param code 错误码
* @param message 错误信息
*/
public BizException(String code, String message) {
super(message);
this.code = code;
}
/**
* 使用错误信息和 Throwable 构造 SystemException
*
* @param message 错误信息
* @param cause 错误原因
*/
public BizException(String message, Throwable cause) {
super(message, cause);
}
/**
* @param code 错误码
* @param message 错误信息
* @param cause 错误原因
*/
public BizException(String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
/**
* @param errorCode ErrorCode
*/
public BizException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
/**
* @param errorCode ErrorCode
* @param cause 错误原因
*/
public BizException(ErrorCode errorCode, Throwable cause) {
super(errorCode.getMessage(), cause);
this.code = errorCode.getCode();
}
}

View File

@@ -0,0 +1,103 @@
package im.zhaojun.zfile.core.exception.core;
import im.zhaojun.zfile.core.exception.ErrorCode;
import lombok.Getter;
/**
* 业务异常,该类异常用户可自行处理,无需记录日志,属于正常业务流程中的异常. 如: 用户名密码错误, 未登录等.<br>
* 使用该类的异常,当该异常被抛出时,会跳转到 500 错误页面(错误码和错误消息可被 {@link #code} 和 {@link #getMessage()} 覆盖),而不是返回 JSON 数据.<br>
* 一般使用该异常得请求不会是 AJAX 请求,而是直接在浏览器中访问的页面请求.
*
* @author zhaojun
*/
@Getter
public class ErrorPageBizException extends RuntimeException {
private static final long serialVersionUID = 8312907182931723379L;
/**
* 错误码
*/
private String code;
/**
* 是否打印堆栈信息,业务异常默认不打印堆栈信息,如果需要打印堆栈信息,可以通过子类覆盖该方法修改返回值为 true.
*/
public boolean printExceptionStackTrace() {
return false;
}
/**
* 构造一个没有错误信息的 <code>SystemException</code>
*/
public ErrorPageBizException() {
super();
}
/**
* 使用指定的 Throwable 和 Throwable.toString() 作为异常信息来构造 SystemException
*
* @param cause 错误原因, 通过 Throwable.getCause() 方法可以获取传入的 cause信息
*/
public ErrorPageBizException(Throwable cause) {
super(cause);
}
/**
* 使用错误信息 message 构造 SystemException
*
* @param message 错误信息
*/
public ErrorPageBizException(String message) {
super(message);
}
/**
* 使用错误码和错误信息构造 SystemException
*
* @param code 错误码
* @param message 错误信息
*/
public ErrorPageBizException(String code, String message) {
super(message);
this.code = code;
}
/**
* 使用错误信息和 Throwable 构造 SystemException
*
* @param message 错误信息
* @param cause 错误原因
*/
public ErrorPageBizException(String message, Throwable cause) {
super(message, cause);
}
/**
* @param code 错误码
* @param message 错误信息
* @param cause 错误原因
*/
public ErrorPageBizException(String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
/**
* @param errorCode ErrorCode
*/
public ErrorPageBizException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
/**
* @param errorCode ErrorCode
* @param cause 错误原因
*/
public ErrorPageBizException(ErrorCode errorCode, Throwable cause) {
super(errorCode.getMessage(), cause);
this.code = errorCode.getCode();
}
}

View File

@@ -0,0 +1,94 @@
package im.zhaojun.zfile.core.exception.core;
import im.zhaojun.zfile.core.exception.ErrorCode;
import lombok.Getter;
/**
* 系统异常, 该类异常用户无法处理,需要记录日志, 属于系统异常. 如: 网络异常, 服务器异常等.
*
* @author zhaojun
*/
@Getter
public class SystemException extends RuntimeException {
private static final long serialVersionUID = 8312907182931723379L;
/**
* 错误码
*/
private String code;
/**
* 构造一个没有错误信息的 <code>SystemException</code>
*/
public SystemException() {
super();
}
/**
* 使用指定的 Throwable 和 Throwable.toString() 作为异常信息来构造 SystemException
*
* @param cause 错误原因, 通过 Throwable.getCause() 方法可以获取传入的 cause信息
*/
public SystemException(Throwable cause) {
super(cause);
}
/**
* 使用错误信息 message 构造 SystemException
*
* @param message 错误信息
*/
public SystemException(String message) {
super(message);
}
/**
* 使用错误码和错误信息构造 SystemException
*
* @param code 错误码
* @param message 错误信息
*/
public SystemException(String code, String message) {
super(message);
this.code = code;
}
/**
* 使用错误信息和 Throwable 构造 SystemException
*
* @param message 错误信息
* @param cause 错误原因
*/
public SystemException(String message, Throwable cause) {
super(message, cause);
}
/**
* @param code 错误码
* @param message 错误信息
* @param cause 错误原因
*/
public SystemException(String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
/**
* @param errorCode ErrorCode
*/
public SystemException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
/**
* @param errorCode ErrorCode
* @param cause 错误原因
*/
public SystemException(ErrorCode errorCode, Throwable cause) {
super(errorCode.getMessage(), cause);
this.code = errorCode.getCode();
}
}

View File

@@ -0,0 +1,18 @@
package im.zhaojun.zfile.core.exception.status;
import im.zhaojun.zfile.core.exception.GlobalExceptionHandler;
import im.zhaojun.zfile.core.exception.core.BizException;
/**
* 错误请求异常, 表示请求参数有误或者服务器无法理解, 一般返回 400 状态码
* <p/>
* 需要全局异常处理器捕获此异常, 并记录日志. {@link GlobalExceptionHandler#badRequestAccessException(BadRequestAccessException)}
*
* @author zhaojun
*/
public class BadRequestAccessException extends BizException {
public BadRequestAccessException(String message) {
super(message);
}
}

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