Compare commits

...

210 Commits

Author SHA1 Message Date
qaiu
068efd0503 - 升级vertx到4.5.6
- 添加Cloudreve
- 蓝奏云优享和小飞机规则修改
2024-04-09 12:13:26 +08:00
qaiu
d6de9851df Merge pull request #35 from qaiu/dependabot/npm_and_yarn/web-front/webpack-dev-middleware-5.3.4
Bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /web-front
2024-03-24 15:23:37 +08:00
qaiu
a9a048a9ce Merge pull request #34 from qaiu/dependabot/npm_and_yarn/web-front/follow-redirects-1.15.6
Bump follow-redirects from 1.15.2 to 1.15.6 in /web-front
2024-03-24 15:23:15 +08:00
dependabot[bot]
481e7b1240 Bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /web-front
Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.3 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-23 19:01:51 +00:00
dependabot[bot]
e406e03dc2 Bump follow-redirects from 1.15.2 to 1.15.6 in /web-front
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-16 14:15:40 +00:00
qaiu
acbf56adbc 更新 README.md 2024-01-29 00:35:23 +08:00
qaiu
1cf82b8df9 更新 README.md 2024-01-29 00:34:43 +08:00
qaiu
a8405830f2 更新 README.md 2024-01-29 00:23:34 +08:00
qaiu
2673f23e64 更新 README.md 2024-01-29 00:22:39 +08:00
qaiu
cbaf6e9ac0 .. 2024-01-28 15:29:41 +08:00
qaiu
edb2d65638 添加蓝奏云优享解析支持 2024-01-28 15:26:22 +08:00
qaiu
f8f64fdcb1 蓝奏云域名修改, shell分隔符调整 2024-01-27 10:53:52 +08:00
qaiu
ae86b85e51 Update README.md 2023-12-27 14:27:31 +08:00
qaiu
c35bc22d02 Merge pull request #28 from qaiu/dev-reference
Dev reference 添加文叔叔解析
2023-12-16 14:35:44 +08:00
qaiu
a682d2d876 加入Jansi 让windows cmd日志彩色输出 2023-12-15 10:14:00 +08:00
BLSKWOLF
72f363d0e6 更新 README.md 2023-12-11 23:56:10 +08:00
BLSKWOLF
4b84ecd5fc 添加文叔叔解析
1.修复QQ邮箱解析
2.添加文叔叔解析
2023-12-11 23:51:49 +08:00
BLSKWOLF
632945d39a 更新 README.md 2023-12-11 17:46:42 +08:00
BLSKWOLF
40bbd3f135 [未完成]添加QQ邮箱文件解析
添加QQ邮箱文件解析,暂时未解决cookie的问题
2023-12-11 17:36:39 +08:00
qaiu
d822090c76 Update README.md 2023-12-05 17:32:17 +08:00
qaiu
bd2ab30e7a Update README.md 2023-12-05 12:49:07 +08:00
qaiu
f306170994 Update README.md 2023-12-05 11:43:04 +08:00
qaiu
c8e12de768 Update README.md 2023-12-05 11:39:26 +08:00
qaiu
d602c8a020 Merge pull request #27 from qaiu/dependabot/maven/ch.qos.logback-logback-classic-1.4.12
Bump ch.qos.logback:logback-classic from 1.4.7 to 1.4.12
2023-12-05 09:48:52 +08:00
dependabot[bot]
efe39eef8d Bump ch.qos.logback:logback-classic from 1.4.7 to 1.4.12
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.4.7 to 1.4.12.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.4.7...v_1.4.12)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-05 01:47:03 +00:00
qaiu
d828c7ac8b Update README.md 2023-12-05 09:46:22 +08:00
qaiu
37babc9a89 1. 日志配置微调 2. 乐云验证 2023-11-29 14:44:42 +08:00
qaiu
8be497a290 vertx升级4.5.0 2023-11-28 17:00:11 +08:00
qaiu
9ff1ea2ff2 Merge remote-tracking branch 'origin/main' 2023-11-28 16:53:48 +08:00
qaiu
fc64363b47 1. 添加联想乐云直链解析 2. 优化代码逻辑 2023-11-28 16:51:51 +08:00
qaiu
36ab2985b1 Merge pull request #25 from qaiu/dependabot/npm_and_yarn/web-front/axios-1.6.0
Bump axios from 1.2.2 to 1.6.0 in /web-front
2023-11-10 10:03:31 +08:00
dependabot[bot]
8ba95932ba Bump axios from 1.2.2 to 1.6.0 in /web-front
Bumps [axios](https://github.com/axios/axios) from 1.2.2 to 1.6.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/1.2.2...v1.6.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-10 02:02:37 +00:00
qaiu
b8aaa59437 Merge pull request #22 from qaiu/dependabot/npm_and_yarn/web-front/babel/traverse-7.23.2
Bump @babel/traverse from 7.20.12 to 7.23.2 in /web-front
2023-11-10 10:02:09 +08:00
qaiu
1d6953d7c7 小飞机网盘分享新URL格式适配 2023-11-02 10:43:10 +08:00
dependabot[bot]
98c336ab84 Bump @babel/traverse from 7.20.12 to 7.23.2 in /web-front
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.20.12 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-18 17:31:32 +00:00
qaiu
d5e2b9cb3e 更新 README.md 2023-09-23 11:21:47 +08:00
qaiu
7c4a243429 Update README.md 2023-09-21 17:32:09 +08:00
qaiu
64e312f108 Update README.md 更新jdk下载网盘为移动云空间 2023-09-21 17:30:55 +08:00
qaiu
265d11e74c Update README.md 2023-09-17 09:54:39 +08:00
qaiu
fcc0f71c23 蓝奏云js引入问题修复 2023-09-17 09:48:23 +08:00
QAIU
4fd4bfb048 Merge remote-tracking branch 'origin/main' 2023-09-08 17:42:09 +08:00
QAIU
da07cfaaff update test 2023-09-08 17:41:30 +08:00
qaiu
7e31adcad9 Update README.md 2023-09-07 08:52:43 +08:00
qaiu
378ae84816 Update README.md 错别字 2023-09-06 03:08:17 +08:00
qaiu
a32abe8bfa Update README.md 2023-09-06 03:06:55 +08:00
qaiu
f7ecf3e66c Update README.md 2023-09-06 03:05:47 +08:00
QAIU
e9e503c685 解析用到的js文件改为String方式引入 2023-09-01 18:07:21 +08:00
qaiu
85c697ea92 Update README.md 2023-08-28 22:37:26 +08:00
QAIU
2018b20be5 去除web-service无用依赖 2023-08-28 17:58:00 +08:00
QAIU
e1c61fb51a 预览首页统计修复,123盘大文件长度取值错误问题 2023-08-28 17:54:39 +08:00
QAIU
cdeb63e225 aaa 2023-08-25 18:30:27 +08:00
QAIU
7b6a79f83a Merge remote-tracking branch 'origin/main' 2023-08-25 18:29:00 +08:00
QAIU
dfd7e7a934 版本更新至0.1.7,启用h2db,添加统计功能,框架优化 2023-08-25 18:28:45 +08:00
qaiu
8deb932685 错别字 2023-08-25 17:04:37 +08:00
QAIU
da063461be Merge remote-tracking branch 'origin/main' 2023-08-25 16:56:00 +08:00
QAIU
3234f643d5 版本更新至0.1.7,启用h2db,添加统计功能,框架优化 2023-08-25 16:55:38 +08:00
qaiu
332cc587a2 Update README.md 2023-08-25 10:04:44 +08:00
qaiu
b30d01e154 Update README.md 2023-08-25 10:04:27 +08:00
QAIU
79d8aa0868 蓝奏云规则微调, 框架优化(70%) 2023-08-23 14:55:08 +08:00
qaiu
a724d703ed Update LzTool.java 2023-08-21 12:27:39 +08:00
qaiu
18478b2a0b Update README.md 2023-08-20 10:36:39 +08:00
qaiu
dde54ed9cc Merge pull request #13 from qaiu/dev
蓝奏云规则修改, 奶牛功能增强
2023-08-20 10:35:02 +08:00
qaiu
3c56a7183e 修复蓝奏云pan解析错误的问题#12 2023-08-20 10:29:00 +08:00
QAIU
38a3ae8a56 Merge remote-tracking branch 'origin/dev' into dev 2023-08-19 17:29:04 +08:00
QAIU
00b30331eb 奶牛快传支持下载zip目录分享#11, core框架优化 2023-08-19 17:28:30 +08:00
qaiu
8397c24715 Update README.md 2023-08-13 00:58:12 +08:00
qaiu
3ee113e81f Update README.md 2023-08-13 00:55:20 +08:00
qaiu
cde641c8db Update README.md 2023-08-13 00:54:21 +08:00
QAIU
30cd2f5850 奶牛快传支持下载zip目录分享#11, core框架优化 2023-08-11 15:28:37 +08:00
QAIU
71066ded98 core框架优化 2023-08-10 14:54:45 +08:00
qaiu
8441954075 修复123pan解析错误的问题#9 2023-08-10 01:49:41 +08:00
qaiu
5e0568fa0e 代码结构优化;修复123pan解析错误的问题#9 2023-08-10 01:45:39 +08:00
qaiu
594e9af71d 代码结构优化;修复123pan解析错误的问题#9 2023-08-10 01:42:34 +08:00
qaiu
9bc7e9aa26 代码结构优化
修复123pan解析错误的问题#9
2023-08-10 01:38:20 +08:00
qaiu
5805edf0a9 Merge pull request #10 from qaiu/master
Master
2023-08-10 01:17:11 +08:00
qaiu
9614a36fc2 fixed 123pan /b/api/share/download/info->/a/api/share/download/info 2023-08-10 00:59:09 +08:00
QAIU
f45031c3f1 代码结构优化, 异常处理优化 2023-08-08 17:36:36 +08:00
QAIU
28e0542c8e 代码结构优化, 异常处理优化 2023-08-08 11:34:32 +08:00
QAIU
b78a8300c6 0 2023-08-04 17:49:30 +08:00
qaiu
ca06d5ae98 Update README.md 2023-08-02 17:53:04 +08:00
QAIU
0c5e53b86e 0 2023-08-01 14:42:32 +08:00
qaiu
f45e6f3468 前端页面优化 2023-07-31 22:43:23 +08:00
qaiu
08ffec524e 前端页面优化 2023-07-31 22:41:33 +08:00
QAIU
62dea67d69 0 2023-07-31 14:07:12 +08:00
QAIU
8108a4dd3f Merge remote-tracking branch 'origin/main' 2023-07-31 13:56:49 +08:00
QAIU
56334fa4a2 360亿方云API-URL变更 2023-07-31 13:56:35 +08:00
qaiu
093b337396 Update README.md 2023-07-31 12:06:59 +08:00
qaiu
4bedf4486b Update README.md 2023-07-31 12:03:34 +08:00
qaiu
b700493a6c Merge pull request #6 from qaiu/dependabot/npm_and_yarn/web-front/webpack-5.88.2
Bump webpack from 5.75.0 to 5.88.2 in /web-front
2023-07-31 11:42:09 +08:00
qaiu
44b1168bb5 Merge pull request #7 from qaiu/dependabot/npm_and_yarn/web-front/word-wrap-1.2.5
Bump word-wrap from 1.2.3 to 1.2.5 in /web-front
2023-07-31 11:41:57 +08:00
dependabot[bot]
a351364faf Bump webpack from 5.75.0 to 5.88.2 in /web-front
Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.88.2.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.75.0...v5.88.2)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-31 03:39:18 +00:00
QAIU
6bd075e17c lz.qaiu.top加入前端页面 2023-07-31 11:38:23 +08:00
qaiu
1e77b20e44 前端优化 2023-07-31 05:08:13 +08:00
dependabot[bot]
0c2165dbf3 Bump word-wrap from 1.2.3 to 1.2.5 in /web-front
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.5.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-30 19:22:56 +00:00
qaiu
7595345e7b Merge remote-tracking branch 'origin/main' 2023-07-31 03:22:02 +08:00
qaiu
d13fe1fc21 添加前端页面 2023-07-31 03:21:39 +08:00
qaiu
f0269b1687 Update README.md 2023-07-31 00:20:52 +08:00
qaiu
257147dd75 Update README.md 2023-07-30 16:29:27 +08:00
qaiu
6fe824e705 添加 通过JS文件获取123pan签名 2023-07-29 18:08:30 +08:00
QAIU
f482982a25 123网盘解析规则优化 2023-07-27 17:45:53 +08:00
QAIU
e3ec81bd9b 123网盘解析规则优化 2023-07-24 10:47:01 +08:00
QAIU
4771a5b5ce Merge remote-tracking branch 'origin/main' 2023-07-24 10:41:43 +08:00
QAIU
1292440f95 123网盘解析规则优化 2023-07-24 10:41:25 +08:00
qaiu
c2052ddbd8 Update README.md 2023-07-23 15:15:25 +08:00
qaiu
f65dfacf4b Update README.md 2023-07-23 15:13:23 +08:00
qaiu
27c2c1ab29 Update README.md 2023-07-23 15:12:58 +08:00
qaiu
b4471dd9c3 Update README.md
lz.qaiu.top测试123pan 下载Dragonwell JDK17
2023-07-22 15:33:10 +08:00
qaiu
69d8b645a4 Update README.md
lz.qaiu.top测试123pan 下载Dragonwell JDK17
2023-07-22 15:32:13 +08:00
QAIU
67dc452819 修改123网盘解析规则 2023-07-22 12:34:02 +08:00
QAIU
60df94ce86 细节优化 2023-07-21 10:29:21 +08:00
qaiu
d6575d5b81 Update README.md 2023-07-16 04:26:05 +08:00
qaiu
2ffa31f655 修改win服务模板 2023-07-16 03:45:54 +08:00
qaiu
1ae2c89d4f - 更新版本号: 0.1.5->0.1.6 2023-07-16 03:11:41 +08:00
qaiu
2de5790b2a - WebServer: PanTool优化异常处理
- Core: Vertx事件循环线程数调整
2023-07-16 02:47:39 +08:00
qaiu
e5b892c9d1 Merge pull request #5 from qaiu/dependabot/maven/com.h2database-h2-2.2.220
Bump h2 from 2.1.214 to 2.2.220
2023-07-10 06:05:17 +08:00
qaiu
1ab5059a41 Merge pull request #4 from qaiu/dependabot/maven/core-database/com.h2database-h2-2.2.220
Bump h2 from 2.1.214 to 2.2.220 in /core-database
2023-07-10 06:04:58 +08:00
dependabot[bot]
1d10e7ab50 Bump h2 from 2.1.214 to 2.2.220
Bumps [h2](https://github.com/h2database/h2database) from 2.1.214 to 2.2.220.
- [Release notes](https://github.com/h2database/h2database/releases)
- [Commits](https://github.com/h2database/h2database/compare/version-2.1.214...version-2.2.220)

---
updated-dependencies:
- dependency-name: com.h2database:h2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-07 22:01:48 +00:00
dependabot[bot]
0477cc4815 Bump h2 from 2.1.214 to 2.2.220 in /core-database
Bumps [h2](https://github.com/h2database/h2database) from 2.1.214 to 2.2.220.
- [Release notes](https://github.com/h2database/h2database/releases)
- [Commits](https://github.com/h2database/h2database/compare/version-2.1.214...version-2.2.220)

---
updated-dependencies:
- dependency-name: com.h2database:h2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-07 22:00:11 +00:00
QAIU
9d3fce3dee 解决一些 warning jdk14对可序列化对象添加@Serial 2023-06-21 16:49:01 +08:00
QAIU
e07d427f85 Merge remote-tracking branch 'origin/main' 2023-06-21 15:40:42 +08:00
QAIU
998c6e4627 musetransfer api test 2023-06-21 15:40:24 +08:00
qaiu
e284dfca63 Zzz 2023-06-21 02:53:58 +00:00
qaiu
cccacde288 Cow解析重复创建实例VertxHolder.getVertxInstance() 2023-06-21 02:53:24 +00:00
qaiu
ee99599d4f cow parse fail return 2023-06-21 02:50:19 +00:00
QAIU
574d5861f5 add mu pan 2023-06-14 17:47:20 +08:00
QAIU
c3173b7f93 Zzz 2023-06-14 16:14:37 +08:00
qaiu
6dc647d0be Update README.md 2023-06-14 15:48:21 +08:00
qaiu
ba5ad53529 Update README.md 2023-06-14 15:48:08 +08:00
qaiu
3c5a0d9c9d Update README.md 2023-06-14 15:40:08 +08:00
qaiu
bb1c296872 Create LICENSE 2023-06-14 15:23:59 +08:00
QAIU
b36e3628bd Zzz 2023-06-14 11:57:58 +08:00
QAIU
837a265fd8 Zzz 2023-06-14 11:56:33 +08:00
QAIU
208c430875 Zzz 2023-06-14 11:53:27 +08:00
QAIU
639f8df534 依赖打包优化 2023-06-14 11:26:33 +08:00
qaiu
4f5e23ebb6 修复Linux运行脚本 2023-06-14 10:30:16 +08:00
QAIU
d86c67314c Linux部署服务模板修改 2023-06-13 15:11:32 +08:00
qaiu
d1ba790d05 Update README.md 2023-06-13 13:58:26 +08:00
QAIU
63b1cad839 update 0.1.5 2023-06-13 13:25:22 +08:00
QAIU
610c6c8d2a update 0.1.5 2023-06-13 13:23:09 +08:00
QAIU
7f50ee5217 移动云空间需要注意的地方 2023-06-13 10:53:02 +08:00
qaiu
5883c7c174 Merge pull request #3 from qaiu/dev
Controller 层不支持方法重载: ServerApi
2023-06-13 10:30:36 +08:00
qaiu
0b73738e39 Controller 层不支持方法重载: ServerApi 2023-06-13 10:29:23 +08:00
qaiu
4e11b61fdb 接口重构 2023-06-13 08:22:11 +08:00
qaiu
853246d363 接口重构 2023-06-13 05:43:37 +08:00
QAIU
e1d880318a 测试相关 2023-06-12 16:41:06 +08:00
QAIU
533f6f8596 Merge remote-tracking branch 'origin/main' 2023-06-12 16:12:12 +08:00
QAIU
68b7c57384 Zzzz 2023-06-12 16:07:28 +08:00
QAIU
089dc5713b Zzzz 2023-06-12 16:07:28 +08:00
QAIU
2596e58943 重构蓝奏云解析代码, 加入蓝奏云加密分享解析 2023-06-12 10:39:33 +08:00
QAIU
7d8b39ad92 重构蓝奏云解析代码, 加入蓝奏云加密分享解析 2023-06-12 10:39:33 +08:00
QAIU
9419c7e0ca 蓝奏云密码解析测试 2023-06-10 17:45:25 +08:00
QAIU
f492e3c031 蓝奏云密码解析测试 2023-06-10 17:45:25 +08:00
QAIU
8973a0e4b2 蓝奏云密码解析测试 2023-06-10 17:32:25 +08:00
QAIU
fff8cfe460 蓝奏云密码解析测试 2023-06-10 17:32:25 +08:00
QAIU
3b3dd0634f change README.md 2023-06-10 16:41:25 +08:00
QAIU
e2945b8f34 change README.md 2023-06-10 16:41:25 +08:00
QAIU
5343c68603 change README.md 2023-06-10 16:35:33 +08:00
QAIU
daadb6e9cc change README.md 2023-06-10 16:35:33 +08:00
QAIU
b8057135a9 添加123云盘直链解析 2023-06-10 16:31:17 +08:00
QAIU
e9ed8715af 添加123云盘直链解析 2023-06-10 16:31:17 +08:00
QAIU
e941d76507 修改密码分隔符$->@ 2023-06-10 13:34:01 +08:00
QAIU
f2280c8e86 修改密码分隔符$->@ 2023-06-10 13:34:01 +08:00
QAIU
b189614d29 - 加入360亿方云直链解析
- 优化代码
2023-06-10 13:29:22 +08:00
QAIU
ea9d1067ed - 加入360亿方云直链解析
- 优化代码
2023-06-10 13:29:22 +08:00
QAIU
70fe0c23ae - 加入小飞机盘直链解析
- 优化代码
2023-06-09 15:43:21 +08:00
QAIU
03c4c8c581 - 加入小飞机盘直链解析
- 优化代码
2023-06-09 15:43:21 +08:00
QAIU
9a3baebec9 Merge remote-tracking branch 'origin/main' 2023-06-08 17:34:02 +08:00
QAIU
3574b58d21 Merge remote-tracking branch 'origin/main' 2023-06-08 17:34:02 +08:00
QAIU
74e95d0f12 其他网盘API 2023-06-08 17:33:42 +08:00
QAIU
a14db13485 其他网盘API 2023-06-08 17:33:42 +08:00
qaiu
37f5f2a30d add lzpan login api test 2023-05-29 01:14:40 +08:00
qaiu
e7ebb7c8c3 change logo show version 2023-05-29 00:29:50 +08:00
qaiu
11decadb4c Update service-install.sh 2023-05-25 18:14:55 +08:00
QAIU
34c6d26581 测试蓝奏云密码访问 2023-05-25 17:44:55 +08:00
QAIU
2155c3e0c0 修改安装脚本 2023-05-25 15:15:56 +08:00
QAIU
0bc23e76bf Merge remote-tracking branch 'origin/main' 2023-05-24 17:41:48 +08:00
qaiu
095040efce Merge pull request #2 from qaiu/qaiu-patch-1
Update README.md
2023-05-24 17:38:05 +08:00
qaiu
d98751b0e3 Update README.md 2023-05-24 17:31:00 +08:00
QAIU
3e31e97c35 1. change actions maven.yml 2023-05-24 17:28:05 +08:00
QAIU
eebce94e88 1. change actions maven.yml 2023-05-24 17:20:45 +08:00
QAIU
2ca7a78920 1. change actions maven.yml 2023-05-24 17:18:30 +08:00
qaiu
2c243650f6 Update maven.yml 2023-05-24 17:01:26 +08:00
qaiu
a257cd2f69 Create maven.yml 2023-05-24 16:57:24 +08:00
qaiu
d0d5c96b9e Update README.md 2023-05-24 16:24:51 +08:00
QAIU
00af483239 1. 项目重命名
2. 添加UC网盘解析
3. 添加移动云空间解析 #1
2023-05-24 16:20:06 +08:00
QAIU
25534452ae UCpan和移动云空间API测试 2023-05-23 17:34:11 +08:00
QAIU
804266d5af do cow login api 2023-05-15 17:42:29 +08:00
QAIU
7fa2ea59a5 do db 2023-05-12 17:30:10 +08:00
QAIU
c9e714418e 夸克Http API测试 2023-05-11 17:51:00 +08:00
qaiu
b3beb61337 清理自动生成的文件 2023-05-07 18:05:43 +08:00
qaiu
2270a05631 zz 2023-05-07 17:21:48 +08:00
qaiu
5fb3d2a2d6 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	lz-cow-api-web/src/main/java/cn/qaiu/lz/common/util/LzTool.java
#	lz-cow-api-web/src/main/resources/logback.xml
2023-05-07 17:19:35 +08:00
qaiu
34f2670700 zz 2023-05-07 17:18:43 +08:00
QAIU
d4586b342e do db 2023-05-06 17:38:56 +08:00
QAIU
1651f1ea93 do var 2023-04-25 17:51:39 +08:00
QAIU
11a054dc2c 日志配置 2023-04-25 09:45:48 +08:00
QAIU
fc233bcea3 日志配置 2023-04-25 09:43:45 +08:00
QAIU
e7e3888b13 蓝奏云API 域名改为wwsd.lanzoue.com 2023-04-25 09:41:10 +08:00
QAIU
91845cc08c 蓝奏云API 正则解析规则修改 2023-04-25 09:32:55 +08:00
qaiu
94cde1e953 细节优化
cow解析失败时返回异常
2023-04-25 08:44:30 +08:00
qaiu
678866f654 Update README.md 2023-04-22 13:19:21 +08:00
qaiu
f7ccc3c3e3 细节优化
cow解析失败时返回异常
2023-04-22 12:46:42 +08:00
qaiu
1799763f7f vert.x升级到4.4.1 2023-04-22 11:34:05 +08:00
qaiu
5ba38f4f1d 0.0.1 fixed done 2023-04-21 23:42:22 +08:00
qaiu
07d8ed19b0 0.0.1 done 2023-04-21 23:30:50 +08:00
QAIU
15b6468333 蓝奏云API BUG修复 2023-04-21 17:39:09 +08:00
qaiu
2457ded158 init 2023-04-21 01:24:31 +08:00
qaiu
54a1a6b7f4 init 2023-04-21 01:21:56 +08:00
qaiu
167149f410 忽略一些文件 2023-04-21 00:41:57 +08:00
qaiu
2c7a17c3b0 Create README.md 2023-04-20 17:43:55 +08:00
132 changed files with 19167 additions and 990 deletions

1
.gitignore vendored
View File

@@ -38,3 +38,4 @@ gradlew
gradlew.bat gradlew.bat
unused.txt unused.txt
/web-service/src/main/generated/ /web-service/src/main/generated/
/db

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"java.compile.nullAnalysis.mode": "automatic"
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 qaiu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

268
README.md
View File

@@ -1,73 +1,239 @@
# netdisk-fast-download 云盘解析服务 (nfd云解析)
# 网盘快速下载器--直链解析 预览地址 https://lz.qaiu.top
**注意: 请不要过度依赖lz.qaiu.top预览地址服务建议本地搭建或者云服务器自行搭建。
解析次数过多IP会被部分网盘厂商限制不推荐做公共解析。**
[![Java CI with Maven](https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml/badge.svg)](https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml) [![Java CI with Maven](https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml/badge.svg)](https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml)
## 网盘支持情况: [![jdk](https://img.shields.io/badge/jdk-%3E%3D17-blue)](https://www.oracle.com/cn/java/technologies/downloads/)
` 网盘名称(网盘标识): ` [![vert.x](https://img.shields.io/badge/vert.x-4.5.6-blue)](https://vertx-china.github.io/)
- 蓝奏云 (lz) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/qaiu/netdisk-fast-download)](https://github.com/qaiu/netdisk-fast-download/releases/tag/0.1.6-releases)
- [ ] 登录, 上传, 下载, 分享
- [x] 直链解析
- 奶牛快传 (cow)
- [ ] 登录, 上传, 下载, 分享
- [x] 直链解析
- 移动云空间 (ec)
- [ ] 登录, 上传, 下载, 分享
- [x] 直链解析
- UC网盘 (uc)
- [ ] 登录, 上传, 下载, 分享
- [x] 直链解析
- 夸克网盘 (qk)
- TODO
技术栈: ## 项目介绍
Jdk17+Vert.x4.4.1+Jsoup 网盘直链解析工具能把网盘分享下载链接转化为直链,已支持蓝奏云/蓝奏云优享/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘/Cloudreve等支持加密分享。
Core模块集成Vert.x实现类spring的注解式路由API
API接口
```shell *重要声明:本项目仅供学习参考;请不要将此项目用于任何商业用途,否则可能带来严重的后果。*
(括号内表示可选内容)
1. 解析并自动302跳转 : ## 网盘支持情况:
http(s)://you_host/parser?url=分享链接 > 20230905 奶牛云直链做了防盗链需加入请求头Referer: https://cowtransfer.com/
http(s)://you_host/网盘标识/分享id(#分享密码) > 20230824 123云盘解析大文件(>100MB)失效,需要登录
> 20230722 UC网盘解析失效需要登录
`网盘名称(网盘标识):`
- [蓝奏云 (lz)](https://pc.woozooo.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [蓝奏云优享 (iz)](https://www.ilanzou.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [奶牛快传 (cow)](https://cowtransfer.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [移动云空间 (ec)](https://www.ecpan.cn/web)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [小飞机网盘 (fj)](https://www.feijipan.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [亿方云 (fc)](https://www.fangcloud.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [123云盘 (ye)](https://www.123pan.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [文叔叔 (ws)](https://www.wenshushu.cn/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [Cloudreve自建网盘 (ce)](https://github.com/cloudreve/Cloudreve)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [QQ邮箱 (qq) 暂不可用-存在cookie问题](https://wx.mail.qq.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析(用户无法直接使用直链)
- [夸克网盘 (qk) 寄了](https://pan.quark.cn/)
- [UC网盘 (uc) 寄了](https://fast.uc.cn/)
**TODO:**
- 登录接口, 文件上传/下载/分享后端接口
- 短地址服务
- 前端界面(建设中...)
### API接口说明
your_host指的是您的域名或者IP实际使用时替换为实际域名或者IP。
解析方式分为两种类型直接跳转下载链接和获取下载链接(JSON),每一种都提供了两种接口形式parser和网盘标志/分享key拼接的短地址标志短链所有规则参考示例。
- 通用接口: `/parser?url=分享链接`加密分享需要加上参数pwd=密码;
- 标志短链: `/网盘标识/分享key` 在分享Key后面加上@密码;
- 直链JSON: `通用接口``标志短链`前加上`/json` 加密分享的密码规则同上;
- 网盘标识参考上面网盘支持情况
- 括号内是可选内容: 表示当带有分享密码时需要加上密码参数
- 移动云空间,小飞机网盘的加密分享的密码可以忽略
规则示例:
```
1. 解析并自动302跳转 :
http://your_host/parser?url=分享链接(&pwd=xxx)
http://your_host/网盘标识/分享key(@分享密码)
2. 获取解析后的直链--JSON格式 2. 获取解析后的直链--JSON格式
http(s)://you_host/json/网盘标识/分享id(#分享密码) http://your_host/json/parser?url=分享链接(&pwd=xxx)
http://your_host/json/网盘标识/分享key(@分享密码)
3. 需要特殊处理的网盘分享:
1. 移动云空间(ec)使用parser?url= 解析时因为分享链接比较特殊(链接带有参数且含有#符号)所以要么对#进行转义%23要么直接去掉# 或者URL直接是主机名+'/'跟一个data参数
比如 http://your_host/parser?url=https://www.ecpan.cn/web//yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
http://your_host/parser?url=https://www.ecpan.cn/web/%23/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
http://your_host/parser?url=https://www.ecpan.cn/&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
2. Cloudreve自建网盘解析规则:
1. 标志短链: 根据网盘使用https和http选择 http://your_host/ce/https_网盘域名_s_wDz5TK 或 http://your_host/ce/http_网盘域名_s_wDz5TK
网盘域名指的是Cloudreve搭建网盘的主域名比如pan.huang1111.cn如果存在子路径需要将/替换为_是否存在子路径看分享链接格式是否是//网盘域名/子路径/s/xxx一般不存在子路径网盘域名/s/xxx
比如: http://127.0.0.1:6400/ce/https_pan.huang1111.cn_s_wDz5TK
2. parser接口 -> http://your_host/parser?url=分享链接(&pwd=xxx)
比如: http://127.0.0.1:6400/parser?url=https://pan.huang1111.cn/s/wDz5TK
``` ```
json返回数据格式示例:
```json
示例: {
"code": 200,
"msg": "success",
"success": true,
"count": 0,
"data": "https://下载链接",
"timestamp": 1690733953927
}
``` ```
// 解析并重定向到直链
### IDEA HttpClient示例:
```
# 解析并重定向到直链
### 蓝奏云普通分享
# @no-redirect # @no-redirect
GET http://127.0.0.1:6400/parser?url=https://lanzoux.com/ia2cntg GET http://127.0.0.1:6400/parser?url=https://lanzoux.com/ia2cntg
### ### 奶牛快传普通分享
# @no-redirect # @no-redirect
GET http://127.0.0.1:6400/parser?url=https://cowtransfer.com/s/9a644fe3e3a748 GET http://127.0.0.1:6400/parser?url=https://cowtransfer.com/s/9a644fe3e3a748
### 360亿方云加密分享
# @no-redirect
GET http://127.0.0.1:6400/parser?url=https://v2.fangcloud.com/sharing/e5079007dc31226096628870c7&pwd=QAIU
// Rest请求(只提供共享文件Id): # Rest请求自动302跳转(只提供共享文件Id):
### ### 蓝奏云普通分享
# @no-redirect
GET http://127.0.0.1:6400/lz/ia2cntg
### 奶牛快传普通分享
# @no-redirect # @no-redirect
GET http://127.0.0.1:6400/cow/9a644fe3e3a748 GET http://127.0.0.1:6400/cow/9a644fe3e3a748
### 360亿方云加密分享
GET http://127.0.0.1:6400/json/fc/e5079007dc31226096628870c7@QAIU
// 解析返回json直链
### # 解析返回json直链
GET http://127.0.0.1:6400/json/cow/9a644fe3e3a748 ### 蓝奏云普通分享
###
GET http://127.0.0.1:6400/json/lz/ia2cntg GET http://127.0.0.1:6400/json/lz/ia2cntg
### 奶牛快传普通分享
GET http://127.0.0.1:6400/json/cow/9a644fe3e3a748
### 360亿方云加密分享
GET http://127.0.0.1:6400/json/fc/e5079007dc31226096628870c7@QAIU
``` ```
TODO:
解析蓝奏云加密链接
# 网盘对比 # 网盘对比
| 网盘名称 | 可直接下载分享 | 加密分享 | 初始网盘空间 | 单文件大小限制 | 登录接口 |
|-------|-------------|----------|----------|----------------|------| | 网盘名称 | 可直接下载分享 | 加密分享 | 初始网盘空间 | 单文件大小限制 | 登录接口 |
| 蓝奏云 | √ | √ | 不限空间 | 100M | TODO | |------------|------------------------|----------|-----------|-----------------|------|
| 奶牛快传 | √ | X | 10G | 不限大小 | TODO | | 蓝奏云 | √ | √ | 不限空间 | 100M | TODO |
| 移动云空间 | √ | √(密码可忽略) | 5G(个人) | 不限大小 | TODO | | 奶牛快传 | √ | X | 10G | 不限大小 | TODO |
| UC网盘 | √ | √ | 10G | 不限大小 | TODO | | 移动云空间 | √ | √(密码可忽略) | 5G(个人) | 不限大小 | TODO |
| 夸克网盘 | √(>10M需要登录) | √ | 10G(20G) | 不限大小(>10M需要登录) | X | | UC网盘 | 需要登录 | √ | 10G | 不限大小 | TODO |
| 小飞机网盘 | √ | √(密码可忽略) | 10G | 不限大小 | TODO |
| 360亿方云 | √(试用账号有时间限制企业版需要599续费) | √(密码可忽略) | 100G(须实名) | 不限大小 | TODO |
| 123云盘 | √ | √ | 2T | 100G>100M需要登录 | TODO |
| 文叔叔(TODO) | √(注意有时间限制) | √ | 10G | 5GB | TODO |
| 夸克网盘(TODO) | 需要登录 | √ | 10G | 不限大小 | TODO |
# 打包部署
## JDK下载lz.qaiu.top提供直链云解析服务
- [阿里jdk17(Dragonwell17-windows-x86)](https://lz.qaiu.top/ec/e957acef36ce89e1053979672a90d219n)
- [阿里jdk17(Dragonwell17-linux-x86)](https://lz.qaiu.top/ec/6ebc9f2e0bbd53b4c4d5b11013f40a80NHvcYU)
- [阿里jdk17(Dragonwell17-linux-aarch64)](https://lz.qaiu.top/ec/d14c2d06296f61b52a876b525265e0f8tzxTc5)
- [解析有效性测试-移动云空间-阿里jdk17-linux-x86](https://lz.qaiu.top/json/ec/6ebc9f2e0bbd53b4c4d5b11013f40a80NHvcYU)
## 开发和打包
```shell
# 环境要求: Jdk17 + maven;
mvn clean
mvn package
```
打包好的文件位于 web-service/target/netdisk-fast-download-bin.zip
## Linux服务部署
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
```shell
cd ~
wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/0.1.7/netdisk-fast-download.zip
unzip netdisk-fast-download-bin.zip
cd netdisk-fast-download
bash service-install.sh
```
服务相关命令:
查看服务状态
`systemctl status netdisk-fast-download.service`
启动服务
`systemctl start netdisk-fast-download.service`
重启服务
`systemctl restart netdisk-fast-download.service`
停止服务
`systemctl stop netdisk-fast-download.service`
开机启动服务
`systemctl enable netdisk-fast-download.servic`
停止开机启动
`systemctl disable netdisk-fast-download.servic`
## Windows服务部署
1. 下载并解压releases版本netdisk-fast-download-bin.zip
2. 进入netdisk-fast-download下的bin目录
3. 使用管理员权限运行nfd-service-install.bat
如果不想使用服务运行可以直接运行run.bat
> 注意: 如果jdk环境变量的java版本不是17请修改nfd-service-template.xml中的java命令的路径改为实际路径
## 0.1.8 开发计划
- Docker部署
- 联想乐云解析支持
- CLoudreve解析解析
- 直链缓存
- 日志优化
**技术栈:**
Jdk17+Vert.x4.4.1
Core模块集成Vert.x实现类似spring的注解式路由API
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=qaiu/netdisk-fast-download&type=Date)](https://star-history.com/#qaiu/netdisk-fast-download&Date)
## 支持该项目
本项目长期维护如果觉得有帮助, 可以请作者喝杯咖啡, 感谢支持
![image](https://github.com/qaiu/netdisk-fast-download/assets/29825328/54276aee-cc3f-4ebd-8973-2e15f6295819)
[手机端支付宝打赏跳转链接](https://qr.alipay.com/fkx01882dnoxxtjenhlxt53)

View File

@@ -7,7 +7,8 @@ Wants=network-online.target
[Service] [Service]
Type=simple Type=simple
# User=USER # User=USER
ExecStart=/usr/bin/java -server -Xmx128m -jar /root/java/netdisk-fast-download/netdisk-fast-download-0.0.1.jar # 需要JDK17及以上版本 注意修改为自己的路径
ExecStart=/root/java/jdk-17.0.2/bin/java -server -Xmx128m -jar /root/java/netdisk-fast-download/netdisk-fast-download.jar
ExecStop=/bin/kill -s QUIT $MAINPID ExecStop=/bin/kill -s QUIT $MAINPID
Restart=always Restart=always
StandOutput=syslog StandOutput=syslog

View File

@@ -0,0 +1,27 @@
::
:: generate service xml file
::
@echo off
pushd %~dp0
set MY_DIR=%~dp0
set MY_DIR=%MY_DIR:~0,-1%
for /f "delims=X" %%i in ('dir /b %MY_DIR%\netdisk-fast-download.jar') do (
set LAUNCH_JAR=%MY_DIR%\%%i
)
(for /f "delims=" %%a in (nfd-service-template.xml) do (
set "str=%%a"
setlocal enabledelayedexpansion
set "str=!str:${dd}=%MY_DIR%!"
set "str=!str:${jar}=%LAUNCH_JAR%!"
echo,!str!
endlocal
))>"nfd-service.xml"
sc delete netdisk-fast-download
nfd-service install
sc start netdisk-fast-download
pause

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<service>
<id>netdisk-fast-download</id>
<name>netdisk-fast-download</name>
<description>netdisk fast download service</description>
<executable>java</executable>
<arguments> -server -Xmx128m -jar ${jar} </arguments>
<logpath>${dd}\logs</logpath>
<log mode="roll-by-time">
<pattern>yyyyMMdd</pattern>
</log>
</service>

BIN
bin/nfd-service.exe Normal file

Binary file not shown.

View File

@@ -1,5 +1,11 @@
@echo off && @chcp 65001 > nul @echo off && @chcp 65001 > nul
pushd %~dp0 pushd %~dp0
set LIB_DIR=%~dp0 set LIB_DIR=%~dp0
for /f "delims=X" %%i in ('dir /b %LIB_DIR%\netdisk-fast-download-*.jar') do set LAUNCH_JAR=%LIB_DIR%\%%i for /f "delims=X" %%i in ('dir /b %LIB_DIR%\netdisk-fast-download.jar') do (
set LAUNCH_JAR=%LIB_DIR%%%i
)
set "JAVA_HOME=D:\App\dragonwell-17.0.3.0.3+7-GA"
"%JAVA_HOME%\bin\java.exe" -Xmx512M -Dfile.encoding=utf8 -jar %LAUNCH_JAR% %* "%JAVA_HOME%\bin\java.exe" -Xmx512M -Dfile.encoding=utf8 -jar %LAUNCH_JAR% %*
pause

View File

@@ -1,5 +1,6 @@
#!/bin/sh #!/bin/bash
# set -x # set -x
LAUNCH_JAR="netdisk-fast-download-*.jar" LAUNCH_JAR="netdisk-fast-download.jar"
nohup java -Xmx512M -jar "$LAUNCH_JAR" "$@" >startup.log 2>&1 & nohup java -Xmx512M -jar "$LAUNCH_JAR" "$@" >startup.log 2>&1 &
tail -f startup.log tail -f startup.log

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>netdisk-fast-download</artifactId> <artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId> <groupId>cn.qaiu</groupId>
<version>0.1.3</version> <version>0.1.7</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -16,7 +16,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<slf4j.version>2.0.5</slf4j.version> <slf4j.version>2.0.5</slf4j.version>
<commons-lang3.version>3.12.0</commons-lang3.version> <commons-lang3.version>3.12.0</commons-lang3.version>
<vertx.version>4.4.1</vertx.version> <vertx.version>4.5.6</vertx.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -30,7 +30,7 @@
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
<version>2.1.214</version> <version>2.2.220</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP --> <!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->

View File

@@ -1,7 +1,7 @@
package cn.qaiu; package cn.qaiu;
public class StartH2DatabaseServer { public class StartH2DatabaseServer {
public static void main(String[] args) {
System.out.println("Hello world!");
}
} }

View File

@@ -38,6 +38,7 @@ public class CreateTable {
javaProperty2SqlColumnMap.put(Boolean.class, "BOOLEAN"); javaProperty2SqlColumnMap.put(Boolean.class, "BOOLEAN");
javaProperty2SqlColumnMap.put(String.class, "VARCHAR"); javaProperty2SqlColumnMap.put(String.class, "VARCHAR");
javaProperty2SqlColumnMap.put(java.util.Date.class, "TIMESTAMP"); javaProperty2SqlColumnMap.put(java.util.Date.class, "TIMESTAMP");
javaProperty2SqlColumnMap.put(java.time.LocalDateTime.class, "TIMESTAMP");
javaProperty2SqlColumnMap.put(java.sql.Timestamp.class, "TIMESTAMP"); javaProperty2SqlColumnMap.put(java.sql.Timestamp.class, "TIMESTAMP");
javaProperty2SqlColumnMap.put(java.sql.Date.class, "DATE"); javaProperty2SqlColumnMap.put(java.sql.Date.class, "DATE");
javaProperty2SqlColumnMap.put(java.sql.Time.class, "TIME"); javaProperty2SqlColumnMap.put(java.sql.Time.class, "TIME");
@@ -157,8 +158,8 @@ public class CreateTable {
return sql.substring(0, sql.length() - 1) + ");\r\n"; return sql.substring(0, sql.length() - 1) + ");\r\n";
} }
public static void createTable(JDBCPool pool, String tableClassPath) { public static void createTable(JDBCPool pool) {
Set<Class<?>> tableClassList = ReflectionUtil.getReflections(tableClassPath).getTypesAnnotatedWith(Table.class); Set<Class<?>> tableClassList = ReflectionUtil.getReflections().getTypesAnnotatedWith(Table.class);
if (tableClassList.isEmpty()) LOGGER.info("Table model class not fount"); if (tableClassList.isEmpty()) LOGGER.info("Table model class not fount");
tableClassList.forEach(clazz -> { tableClassList.forEach(clazz -> {
String createTableSQL = getCreateTableSQL(clazz); String createTableSQL = getCreateTableSQL(clazz);

View File

@@ -92,7 +92,7 @@ public class JDBCPoolInit {
private void poolInitExecute(Promise<String> promise) { private void poolInitExecute(Promise<String> promise) {
// 初始化连接池 // 初始化连接池
pool = JDBCPool.pool(vertx, dbConfig); pool = JDBCPool.pool(vertx, dbConfig);
CreateTable.createTable(pool, dbConfig.getString("tableClassPath")); CreateTable.createTable(pool);
promise.complete("init jdbc pool success"); promise.complete("init jdbc pool success");
} }

View File

@@ -5,21 +5,14 @@
<parent> <parent>
<artifactId>netdisk-fast-download</artifactId> <artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId> <groupId>cn.qaiu</groupId>
<version>0.1.3</version> <version>0.1.7</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<version>1.0.8</version> <version>1.0.8</version>
<artifactId>core</artifactId> <artifactId>core</artifactId>
<properties> <properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<vertx.version>4.4.1</vertx.version>
<org.reflections.version>0.10.2</org.reflections.version>
<lombok.version>1.18.12</lombok.version>
<slf4j.version>2.0.5</slf4j.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<jackson.version>2.14.2</jackson.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -39,7 +32,7 @@
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
<version>1.4.6</version> <version>${logback.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
@@ -90,6 +83,12 @@
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version> <version>${jackson.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies> </dependencies>

View File

@@ -1,12 +1,12 @@
package cn.qaiu.vx.core; package cn.qaiu.vx.core;
import cn.qaiu.vx.core.util.ConfigConstant;
import cn.qaiu.vx.core.util.ConfigUtil; import cn.qaiu.vx.core.util.ConfigUtil;
import cn.qaiu.vx.core.util.VertxHolder; import cn.qaiu.vx.core.util.VertxHolder;
import cn.qaiu.vx.core.verticle.ReverseProxyVerticle; import cn.qaiu.vx.core.verticle.ReverseProxyVerticle;
import cn.qaiu.vx.core.verticle.RouterVerticle; import cn.qaiu.vx.core.verticle.RouterVerticle;
import cn.qaiu.vx.core.verticle.ServiceVerticle; import cn.qaiu.vx.core.verticle.ServiceVerticle;
import io.vertx.core.*; import io.vertx.core.*;
import io.vertx.core.impl.launcher.commands.VersionCommand;
import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonObject;
import io.vertx.core.shareddata.LocalMap; import io.vertx.core.shareddata.LocalMap;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -17,6 +17,8 @@ import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.LockSupport;
import static cn.qaiu.vx.core.util.ConfigConstant.*;
/** /**
* vertx启动类 需要在主启动类完成回调 * vertx启动类 需要在主启动类完成回调
* <br>Create date 2021-05-07 10:26:54 * <br>Create date 2021-05-07 10:26:54
@@ -42,6 +44,11 @@ public final class Deploy {
return INSTANCE; return INSTANCE;
} }
/**
*
* @param args 启动参数
* @param handle 启动完成后回调处理函数
*/
public void start(String[] args, Handler<JsonObject> handle) { public void start(String[] args, Handler<JsonObject> handle) {
this.mainThread = Thread.currentThread(); this.mainThread = Thread.currentThread();
this.handle = handle; this.handle = handle;
@@ -63,7 +70,7 @@ public final class Deploy {
var activeMode = conf.getString("active"); var activeMode = conf.getString("active");
if ("dev".equals(activeMode)) { if ("dev".equals(activeMode)) {
LOGGER.info("---------------> development environment <--------------\n"); LOGGER.info("---------------> development environment <--------------\n");
System.setProperty("vertxweb.environment","dev"); System.setProperty("vertxweb.environment", "dev");
} else { } else {
LOGGER.info("---------------> Production environment <--------------\n"); LOGGER.info("---------------> Production environment <--------------\n");
} }
@@ -80,7 +87,7 @@ public final class Deploy {
var calendar = Calendar.getInstance(); var calendar = Calendar.getInstance();
calendar.setTime(new Date()); calendar.setTime(new Date());
var year = calendar.get(Calendar.YEAR); var year = calendar.get(Calendar.YEAR);
var logoTemplete = """ var logoTemplate = """
Web Server powered by:\s Web Server powered by:\s
____ ____ _ _ _ \s ____ ____ _ _ _ \s
@@ -93,9 +100,9 @@ public final class Deploy {
"""; """;
System.out.printf(logoTemplete, System.out.printf(logoTemplate,
conf.getString("version_app"), conf.getString("version_app"),
conf.getString("version_vertx"), VersionCommand.getVersion(),
conf.getString("copyright"), conf.getString("copyright"),
year year
); );
@@ -107,22 +114,30 @@ public final class Deploy {
private void deployVerticle() { private void deployVerticle() {
tempVertx.close(); tempVertx.close();
LOGGER.info("配置读取成功"); LOGGER.info("配置读取成功");
customConfig = globalConfig.getJsonObject(ConfigConstant.CUSTOM); customConfig = globalConfig.getJsonObject(CUSTOM);
var vertxOptions = new VertxOptions(globalConfig.getJsonObject(ConfigConstant.VERTX)); JsonObject vertxConfig = globalConfig.getJsonObject(VERTX);
Integer vertxConfigELPS = vertxConfig.getInteger(EVENT_LOOP_POOL_SIZE);
var vertxOptions = vertxConfigELPS == 0 ?
new VertxOptions() : new VertxOptions(vertxConfig);
LOGGER.info("vertxConfigEventLoopPoolSize: {}, eventLoopPoolSize: {}, workerPoolSize: {}", vertxConfigELPS,
vertxOptions.getEventLoopPoolSize(),
vertxOptions.getWorkerPoolSize());
var vertx = Vertx.vertx(vertxOptions); var vertx = Vertx.vertx(vertxOptions);
VertxHolder.init(vertx); VertxHolder.init(vertx);
//配置保存在共享数据中 //配置保存在共享数据中
var sharedData = vertx.sharedData(); var sharedData = vertx.sharedData();
LocalMap<String, Object> localMap = sharedData.getLocalMap(ConfigConstant.LOCAL); LocalMap<String, Object> localMap = sharedData.getLocalMap(LOCAL);
localMap.put(ConfigConstant.GLOBAL_CONFIG, globalConfig); localMap.put(GLOBAL_CONFIG, globalConfig);
localMap.put(ConfigConstant.CUSTOM_CONFIG, customConfig); localMap.put(CUSTOM_CONFIG, customConfig);
localMap.put(ConfigConstant.SERVER, globalConfig.getJsonObject(ConfigConstant.SERVER)); localMap.put(SERVER, globalConfig.getJsonObject(SERVER));
var future0 = vertx.createSharedWorkerExecutor("other-handle").executeBlocking(bch -> { var future0 = vertx.createSharedWorkerExecutor("other-handle").executeBlocking(bch -> {
handle.handle(globalConfig); handle.handle(globalConfig);
bch.complete("other handle complete"); bch.complete("other handle complete");
}); });
// 部署 路由、异步service、反向代理 服务
var future1 = vertx.deployVerticle(RouterVerticle.class, getWorkDeploymentOptions("Router")); var future1 = vertx.deployVerticle(RouterVerticle.class, getWorkDeploymentOptions("Router"));
var future2 = vertx.deployVerticle(ServiceVerticle.class, getWorkDeploymentOptions("Service")); var future2 = vertx.deployVerticle(ServiceVerticle.class, getWorkDeploymentOptions("Service"));
var future3 = vertx.deployVerticle(ReverseProxyVerticle.class, getWorkDeploymentOptions("proxy")); var future3 = vertx.deployVerticle(ReverseProxyVerticle.class, getWorkDeploymentOptions("proxy"));
@@ -160,7 +175,7 @@ public final class Deploy {
* @return Deployment Options * @return Deployment Options
*/ */
private DeploymentOptions getWorkDeploymentOptions(String name) { private DeploymentOptions getWorkDeploymentOptions(String name) {
return getWorkDeploymentOptions(name, customConfig.getInteger(ConfigConstant.ASYNC_SERVICE_INSTANCES)); return getWorkDeploymentOptions(name, customConfig.getInteger(ASYNC_SERVICE_INSTANCES));
} }
private DeploymentOptions getWorkDeploymentOptions(String name, int ins) { private DeploymentOptions getWorkDeploymentOptions(String name, int ins) {

View File

@@ -0,0 +1,15 @@
package cn.qaiu.vx.core.annotaions;
import java.lang.annotation.*;
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HandleSortFilter {
/**
* 注册顺序,数字越大越先注册<br>
* 值<0时会过滤掉该处理器
*/
int value() default 0;
}

View File

@@ -0,0 +1,23 @@
package cn.qaiu.vx.core.annotaions;
import java.lang.annotation.*;
/**
* 拦截器配置注解
* 正则匹配拦截途径
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InterceptorConfig {
String pattern() default "";
/**
* 注册顺序,数字越大越先注册
*/
int order() default 0;
}

View File

@@ -1,9 +1,16 @@
package cn.qaiu.vx.core.base; package cn.qaiu.vx.core.base;
import cn.qaiu.vx.core.interceptor.AfterInterceptor;
import cn.qaiu.vx.core.model.JsonResult;
import cn.qaiu.vx.core.util.CommonUtil;
import cn.qaiu.vx.core.util.ReflectionUtil;
import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import org.reflections.Reflections;
import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE; import java.util.Set;
import static cn.qaiu.vx.core.util.ResponseUtil.*;
/** /**
* 统一响应处理 * 统一响应处理
@@ -13,22 +20,43 @@ import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
*/ */
public interface BaseHttpApi { public interface BaseHttpApi {
default void fireJsonResponse(RoutingContext ctx, JsonObject jsonResult) { // 需要扫描注册的Router路径
ctx.response().putHeader(CONTENT_TYPE, "application/json; charset=utf-8") Reflections reflections = ReflectionUtil.getReflections();
.setStatusCode(200)
.end(jsonResult.encode()); default void doFireJsonObjectResponse(RoutingContext ctx, JsonObject jsonObject) {
if (!ctx.response().ended()) {
fireJsonObjectResponse(ctx, jsonObject);
}
handleAfterInterceptor(ctx, jsonObject);
} }
default <T> void fireJsonResponse(RoutingContext ctx, T jsonResult) {
JsonObject jsonObject = JsonObject.mapFrom(jsonResult); default <T> void doFireJsonResultResponse(RoutingContext ctx, JsonResult<T> jsonResult) {
fireJsonResponse(ctx, jsonObject); if (!ctx.response().ended()) {
fireJsonResultResponse(ctx, jsonResult);
}
handleAfterInterceptor(ctx, jsonResult.toJsonObject());
} }
default void fireTextResponse(RoutingContext ctx, String text) {
ctx.response().putHeader("content-type", "text/html; charset=utf-8").end(text); default Set<AfterInterceptor> getAfterInterceptor() {
Set<Class<? extends AfterInterceptor>> afterInterceptorClassSet =
reflections.getSubTypesOf(AfterInterceptor.class);
if (afterInterceptorClassSet == null) {
return null;
}
return CommonUtil.sortClassSet(afterInterceptorClassSet);
} }
default void sendError(int statusCode, RoutingContext ctx) { default void handleAfterInterceptor(RoutingContext ctx, JsonObject jsonObject) {
ctx.response().setStatusCode(statusCode).end(); Set<AfterInterceptor> afterInterceptor = getAfterInterceptor();
if (afterInterceptor != null) {
afterInterceptor.forEach(ai -> ai.handle(ctx, jsonObject));
}
if (!ctx.response().ended()) {
fireTextResponse(ctx, "handleAfterInterceptor: response not end");
}
} }
} }

View File

@@ -5,9 +5,10 @@ import cn.qaiu.vx.core.annotaions.RouteHandler;
import cn.qaiu.vx.core.annotaions.RouteMapping; import cn.qaiu.vx.core.annotaions.RouteMapping;
import cn.qaiu.vx.core.annotaions.SockRouteMapper; import cn.qaiu.vx.core.annotaions.SockRouteMapper;
import cn.qaiu.vx.core.base.BaseHttpApi; import cn.qaiu.vx.core.base.BaseHttpApi;
import cn.qaiu.vx.core.enums.MIMEType; import cn.qaiu.vx.core.interceptor.BeforeInterceptor;
import cn.qaiu.vx.core.model.JsonResult; import cn.qaiu.vx.core.model.JsonResult;
import cn.qaiu.vx.core.util.*; import cn.qaiu.vx.core.util.*;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.vertx.core.Future; import io.vertx.core.Future;
import io.vertx.core.Handler; import io.vertx.core.Handler;
import io.vertx.core.MultiMap; import io.vertx.core.MultiMap;
@@ -19,14 +20,12 @@ import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Route; import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router; import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler; import io.vertx.ext.web.handler.*;
import io.vertx.ext.web.handler.CorsHandler;
import io.vertx.ext.web.handler.sockjs.SockJSHandler; import io.vertx.ext.web.handler.sockjs.SockJSHandler;
import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions; import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions;
import javassist.CtClass; import javassist.CtClass;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.reflections.Reflections;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -37,6 +36,7 @@ import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static cn.qaiu.vx.core.util.ConfigConstant.ROUTE_TIME_OUT;
import static io.vertx.core.http.HttpHeaders.*; import static io.vertx.core.http.HttpHeaders.*;
/** /**
@@ -56,15 +56,11 @@ public class RouterHandlerFactory implements BaseHttpApi {
add(HttpMethod.DELETE); add(HttpMethod.DELETE);
add(HttpMethod.HEAD); add(HttpMethod.HEAD);
}}; }};
// 需要扫描注册的Router路径
private static volatile Reflections reflections;
private final String gatewayPrefix; private final String gatewayPrefix;
public RouterHandlerFactory(String routerScanAddress, String gatewayPrefix) { public RouterHandlerFactory(String gatewayPrefix) {
Objects.requireNonNull(routerScanAddress, "The router package address scan is empty.");
Objects.requireNonNull(gatewayPrefix, "The gateway prefix is empty."); Objects.requireNonNull(gatewayPrefix, "The gateway prefix is empty.");
reflections = ReflectionUtil.getReflections(routerScanAddress);
this.gatewayPrefix = gatewayPrefix; this.gatewayPrefix = gatewayPrefix;
} }
@@ -72,22 +68,41 @@ public class RouterHandlerFactory implements BaseHttpApi {
* 开始扫描并注册handler * 开始扫描并注册handler
*/ */
public Router createRouter() { public Router createRouter() {
Router router = Router.router(VertxHolder.getVertxInstance()); // 主路由
router.route().handler(ctx -> { Router mainRouter = Router.router(VertxHolder.getVertxInstance());
// 静态资源
String path = SharedDataUtil.getJsonConfig("server")
.getString("staticResourcePath");
if (!StringUtils.isEmpty(path)) {
// 静态资源
mainRouter.route("/*").handler(StaticHandler
.create(path)
.setCachingEnabled(true)
.setDefaultContentEncoding("UTF-8"));
}
mainRouter.route().handler(ctx -> {
LOGGER.debug("The HTTP service request address information ===>path:{}, uri:{}, method:{}", LOGGER.debug("The HTTP service request address information ===>path:{}, uri:{}, method:{}",
ctx.request().path(), ctx.request().absoluteURI(), ctx.request().method()); ctx.request().path(), ctx.request().absoluteURI(), ctx.request().method());
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, "*"); ctx.response().headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, OPTIONS, PUT, DELETE, HEAD"); ctx.response().headers().add(ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, OPTIONS, PUT, DELETE, HEAD");
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_HEADERS, "X-PINGOTHER, Origin,Content-Type, Accept, X-Requested-With, Dev, Authorization, Version, Token"); ctx.response().headers().add(ACCESS_CONTROL_ALLOW_HEADERS, "X-PINGOTHER, Origin,Content-Type, Accept, " +
"X-Requested-With, Dev, Authorization, Version, Token");
ctx.response().headers().add(ACCESS_CONTROL_MAX_AGE, "1728000"); ctx.response().headers().add(ACCESS_CONTROL_MAX_AGE, "1728000");
ctx.next(); ctx.next();
}); });
// 添加跨域的方法 // 添加跨域的方法
router.route().handler(CorsHandler.create().addRelativeOrigin(".*").allowCredentials(true).allowedMethods(httpMethods)); mainRouter.route().handler(CorsHandler.create().addRelativeOrigin(".*").allowCredentials(true).allowedMethods(httpMethods));
// 配置文件上传路径 // 配置文件上传路径
router.route().handler(BodyHandler.create().setUploadsDirectory("uploads")); mainRouter.route().handler(BodyHandler.create().setUploadsDirectory("uploads"));
// 拦截器
Set<Handler<RoutingContext>> interceptorSet = getInterceptorSet();
Route route0 = mainRouter.route("/*");
interceptorSet.forEach(route0::handler);
try { try {
Set<Class<?>> handlers = reflections.getTypesAnnotatedWith(RouteHandler.class); Set<Class<?>> handlers = reflections.getTypesAnnotatedWith(RouteHandler.class);
@@ -101,7 +116,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
for (Class<?> handler : sortedHandlers) { for (Class<?> handler : sortedHandlers) {
try { try {
// 注册请求处理方法 // 注册请求处理方法
registerNewHandler(router, handler); registerNewHandler(mainRouter, handler);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Error register {}, Error details", handler, e.getCause()); LOGGER.error("Error register {}, Error details", handler, e.getCause());
@@ -111,11 +126,12 @@ public class RouterHandlerFactory implements BaseHttpApi {
LOGGER.error("Manually Register Handler Fail, Error details" + e.getMessage()); LOGGER.error("Manually Register Handler Fail, Error details" + e.getMessage());
} }
// 错误请求处理 // 错误请求处理
router.errorHandler(405, ctx -> fireJsonResponse(ctx, JsonResult mainRouter.errorHandler(405, ctx -> doFireJsonResultResponse(ctx, JsonResult
.error("Method Not Allowed", 405))); .error("Method Not Allowed", 405)));
router.errorHandler(404, ctx -> ctx.response().setStatusCode(404).setChunked(true) mainRouter.errorHandler(404, ctx -> ctx.response().setStatusCode(404).setChunked(true)
.end("Internal server error: 404 not found")); .end("Internal server error: 404 not found"));
return router;
return mainRouter;
} }
/** /**
@@ -139,15 +155,13 @@ public class RouterHandlerFactory implements BaseHttpApi {
method -> method.isAnnotationPresent(SockRouteMapper.class) method -> method.isAnnotationPresent(SockRouteMapper.class)
).toList()); ).toList());
// 拦截器
Handler<RoutingContext> interceptor = getInterceptor();
// 依次注册处理方法 // 依次注册处理方法
for (Method method : methodList) { for (Method method : methodList) {
if (method.isAnnotationPresent(RouteMapping.class)) { if (method.isAnnotationPresent(RouteMapping.class)) {
// 普通路由 // 普通路由
RouteMapping mapping = method.getAnnotation(RouteMapping.class); RouteMapping mapping = method.getAnnotation(RouteMapping.class);
HttpMethod routeMethod = HttpMethod.valueOf(mapping.method().name()); HttpMethod routeMethod = HttpMethod.valueOf(mapping.method().name());
String routeUrl = getRouteUrl(method.getName(), mapping.value()); String routeUrl = getRouteUrl(mapping.value());
String url = root.concat(routeUrl); String url = root.concat(routeUrl);
// 匹配方法 // 匹配方法
Route route = router.route(routeMethod, url); Route route = router.route(routeMethod, url);
@@ -157,17 +171,18 @@ public class RouterHandlerFactory implements BaseHttpApi {
route.consumes(mineType); route.consumes(mineType);
} }
// 先执行拦截方法, 再进入业务请求 // 设置默认超时
route.handler(interceptor); route.handler(TimeoutHandler.create(SharedDataUtil.getCustomConfig().getInteger(ROUTE_TIME_OUT)));
route.handler(ResponseTimeHandler.create());
route.handler(ctx -> handlerMethod(instance, method, ctx)).failureHandler(ctx -> { route.handler(ctx -> handlerMethod(instance, method, ctx)).failureHandler(ctx -> {
if (ctx.response().ended()) return; if (ctx.response().ended()) return;
ctx.failure().printStackTrace(); ctx.failure().printStackTrace();
fireJsonResponse(ctx, JsonResult.error(ctx.failure().getMessage(), 500)); doFireJsonResultResponse(ctx, JsonResult.error(ctx.failure().getMessage(), 500));
}); });
} else if (method.isAnnotationPresent(SockRouteMapper.class)) { } else if (method.isAnnotationPresent(SockRouteMapper.class)) {
// websocket 基于sockJs // websocket 基于sockJs
SockRouteMapper mapping = method.getAnnotation(SockRouteMapper.class); SockRouteMapper mapping = method.getAnnotation(SockRouteMapper.class);
String routeUrl = getRouteUrl(method.getName(), mapping.value()); String routeUrl = getRouteUrl(mapping.value());
String url = root.concat(routeUrl); String url = root.concat(routeUrl);
LOGGER.info("Register New Websocket Handler -> {}", url); LOGGER.info("Register New Websocket Handler -> {}", url);
SockJSHandlerOptions options = new SockJSHandlerOptions() SockJSHandlerOptions options = new SockJSHandlerOptions()
@@ -194,13 +209,12 @@ public class RouterHandlerFactory implements BaseHttpApi {
/** /**
* 获取并处理路由URL分隔符 * 获取并处理路由URL分隔符
* *
* @param methodName 路由method
* @return String * @return String
*/ */
private String getRouteUrl(String methodName, String mapperValue) { private String getRouteUrl(String mapperValue) {
String routeUrl; String routeUrl;
if (mapperValue.startsWith("/:") || "/".equals(mapperValue)) { if ("/".equals(mapperValue)) {
routeUrl = (methodName + mapperValue); routeUrl = mapperValue;
} else if (mapperValue.startsWith("/")) { } else if (mapperValue.startsWith("/")) {
routeUrl = mapperValue.substring(1); routeUrl = mapperValue.substring(1);
} else { } else {
@@ -213,15 +227,10 @@ public class RouterHandlerFactory implements BaseHttpApi {
* 配置拦截 * 配置拦截
* *
* @return Handler * @return Handler
* @throws Throwable Throwable
*/ */
private Handler<RoutingContext> getInterceptor() throws Throwable { private Set<Handler<RoutingContext>> getInterceptorSet() {
// 配置拦截 // 配置拦截
Class<?> interceptorClass = Class.forName(SharedDataUtil.getValueForCustomConfig("interceptorClassPath")); return getBeforeInterceptor().stream().map(BeforeInterceptor::doHandle).collect(Collectors.toSet());
Object handleInstance = ReflectionUtil.newWithNoParam(interceptorClass);
Method doHandle = interceptorClass.getMethod("doHandle");
// 反射调用
return CastUtil.cast(ReflectionUtil.invoke(doHandle, handleInstance));
} }
/** /**
@@ -243,7 +252,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
if (handler.isAnnotationPresent(RouteHandler.class)) { if (handler.isAnnotationPresent(RouteHandler.class)) {
RouteHandler routeHandler = handler.getAnnotation(RouteHandler.class); RouteHandler routeHandler = handler.getAnnotation(RouteHandler.class);
String value = routeHandler.value(); String value = routeHandler.value();
root += ("/".equals(value) ? "" : value); root += (value.startsWith("/") ? value.substring(1) : value);
} }
if (!root.endsWith("/")) { if (!root.endsWith("/")) {
root = root + "/"; root = root + "/";
@@ -286,37 +295,12 @@ public class RouterHandlerFactory implements BaseHttpApi {
}); });
} }
final MultiMap queryParams = ctx.queryParams();
if ("POST".equals(ctx.request().method().name())) {
queryParams.addAll(ctx.request().params());
}
JsonArray entityPackagesReg = SharedDataUtil.getJsonArrayForCustomConfig("entityPackagesReg"); JsonArray entityPackagesReg = SharedDataUtil.getJsonArrayForCustomConfig("entityPackagesReg");
// 绑定get或post请求头的请求参数
methodParametersTemp.forEach((k, v) -> { final MultiMap queryParams = ctx.queryParams();
if (ReflectionUtil.isBasicType(v.getRight())) {
String fmt = getFmt(v.getLeft(), v.getRight());
String value = queryParams.get(k);
parameterValueList.put(k, ReflectionUtil.conversion(v.getRight(), value, fmt));
} else if (RoutingContext.class.getName().equals(v.getRight().getName())) {
parameterValueList.put(k, ctx);
} else if (HttpServerRequest.class.getName().equals(v.getRight().getName())) {
parameterValueList.put(k, ctx.request());
} else if (HttpServerResponse.class.getName().equals(v.getRight().getName())) {
parameterValueList.put(k, ctx.response());
} else if (CommonUtil.matchRegList(entityPackagesReg.getList(), v.getRight().getName())) {
// 绑定实体类
try {
Class<?> aClass = Class.forName(v.getRight().getName());
Object entity = ParamUtil.multiMapToEntity(queryParams, aClass);
parameterValueList.put(k, entity);
} catch (Exception e) {
e.printStackTrace();
}
}
});
// 解析body-json参数 // 解析body-json参数
if ("application/json".equals(ctx.parsedHeaders().contentType().value()) && ctx.body().asJsonObject() != null) { if (HttpHeaderValues.APPLICATION_JSON.toString().equals(ctx.parsedHeaders().contentType().value())
&& ctx.body().asJsonObject() != null) {
JsonObject body = ctx.body().asJsonObject(); JsonObject body = ctx.body().asJsonObject();
if (body != null) { if (body != null) {
methodParametersTemp.forEach((k, v) -> { methodParametersTemp.forEach((k, v) -> {
@@ -336,26 +320,66 @@ public class RouterHandlerFactory implements BaseHttpApi {
} }
}); });
} }
} else if (ctx.body() != null) {
queryParams.addAll(ParamUtil.paramsToMap(ctx.body().asString()));
} }
// 解析其他参数
if ("POST".equals(ctx.request().method().name())) {
queryParams.addAll(ctx.request().params());
}
// 绑定get或post请求头的请求参数
methodParametersTemp.forEach((k, v) -> {
if (ReflectionUtil.isBasicType(v.getRight())) {
String fmt = getFmt(v.getLeft(), v.getRight());
String value = queryParams.get(k);
parameterValueList.put(k, ReflectionUtil.conversion(v.getRight(), value, fmt));
} else if (RoutingContext.class.getName().equals(v.getRight().getName())) {
parameterValueList.put(k, ctx);
} else if (HttpServerRequest.class.getName().equals(v.getRight().getName())) {
parameterValueList.put(k, ctx.request());
} else if (HttpServerResponse.class.getName().equals(v.getRight().getName())) {
parameterValueList.put(k, ctx.response());
} else if (parameterValueList.get(k) == null
&& CommonUtil.matchRegList(entityPackagesReg.getList(), v.getRight().getName())) {
// 绑定实体类
try {
Class<?> aClass = Class.forName(v.getRight().getName());
Object entity = ParamUtil.multiMapToEntity(queryParams, aClass);
parameterValueList.put(k, entity);
} catch (Exception e) {
e.printStackTrace();
}
}
});
// 调用handle 获取响应对象 // 调用handle 获取响应对象
Object[] parameterValueArray = parameterValueList.values().toArray(new Object[0]); Object[] parameterValueArray = parameterValueList.values().toArray(new Object[0]);
try { try {
// 反射调用 // 反射调用
Object data = ReflectionUtil.invokeWithArguments(method, instance, parameterValueArray); Object data = ReflectionUtil.invokeWithArguments(method, instance, parameterValueArray);
if (data != null) { if (data != null) {
if (data instanceof JsonResult) { if (data instanceof JsonResult) {
fireJsonResponse(ctx, data); doFireJsonResultResponse(ctx, (JsonResult<?>) data);
}
if (data instanceof JsonObject) {
doFireJsonObjectResponse(ctx, ((JsonObject) data));
} else if (data instanceof Future) { // 处理异步响应 } else if (data instanceof Future) { // 处理异步响应
((Future<?>) data).onSuccess(res -> { ((Future<?>) data).onSuccess(res -> {
if (res instanceof JsonObject) { if (res instanceof JsonResult) {
fireJsonResponse(ctx, res); doFireJsonResultResponse(ctx, (JsonResult<?>) res);
} else if (res != null){
fireJsonResponse(ctx, JsonResult.data(res));
} }
}).onFailure(e -> fireJsonResponse(ctx, JsonResult.error(e.getMessage()))); if (res instanceof JsonObject) {
doFireJsonObjectResponse(ctx, ((JsonObject) res));
} else if (res != null) {
doFireJsonResultResponse(ctx, JsonResult.data(res));
} else {
handleAfterInterceptor(ctx, null);
}
}).onFailure(e -> doFireJsonResultResponse(ctx, JsonResult.error(e.getMessage())));
} else { } else {
ctx.response().headers().set(CONTENT_TYPE, MIMEType.TEXT_HTML.getValue()); doFireJsonResultResponse(ctx, JsonResult.data(data));
ctx.end(data.toString());
} }
} }
} catch (Throwable e) { } catch (Throwable e) {
@@ -363,12 +387,12 @@ public class RouterHandlerFactory implements BaseHttpApi {
String err = e.getMessage(); String err = e.getMessage();
if (e.getCause() != null) { if (e.getCause() != null) {
if (e.getCause() instanceof InvocationTargetException) { if (e.getCause() instanceof InvocationTargetException) {
err = ((InvocationTargetException)e.getCause()).getTargetException().getMessage(); err = ((InvocationTargetException) e.getCause()).getTargetException().getMessage();
} else { } else {
err = e.getCause().getMessage(); err = e.getCause().getMessage();
} }
} }
fireJsonResponse(ctx, JsonResult.error(err)); doFireJsonResultResponse(ctx, JsonResult.error(err));
} }
} }
@@ -386,4 +410,13 @@ public class RouterHandlerFactory implements BaseHttpApi {
} }
return fmt; return fmt;
} }
private Set<BeforeInterceptor> getBeforeInterceptor() {
Set<Class<? extends BeforeInterceptor>> interceptorClassSet =
reflections.getSubTypesOf(BeforeInterceptor.class);
if (interceptorClassSet == null) {
return new HashSet<>();
}
return CommonUtil.sortClassSet(interceptorClassSet);
}
} }

View File

@@ -0,0 +1,15 @@
package cn.qaiu.vx.core.interceptor;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
/**
* 后置拦截器接口
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public interface AfterInterceptor {
void handle(RoutingContext ctx, JsonObject responseData);
}

View File

@@ -0,0 +1,34 @@
package cn.qaiu.vx.core.interceptor;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import static cn.qaiu.vx.core.util.ResponseUtil.sendError;
/**
* 前置拦截器接口
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public interface BeforeInterceptor extends Handler<RoutingContext> {
String IS_NEXT = "RoutingContextIsNext";
default Handler<RoutingContext> doHandle() {
return ctx -> {
ctx.put(IS_NEXT, false);
BeforeInterceptor.this.handle(ctx);
if (!(Boolean) ctx.get(IS_NEXT) && !ctx.response().ended()) {
sendError(ctx, 403);
}
};
}
default void doNext(RoutingContext context) {
context.put(IS_NEXT, true);
context.next();
}
void handle(RoutingContext context);
}

View File

@@ -1,19 +0,0 @@
package cn.qaiu.vx.core.interceptor;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
/**
* 拦截器接口
* <br>Create date 2021-05-06 09:20:37
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public interface Interceptor {
default Handler<RoutingContext> doHandle() {
return this::handle;
}
void handle(RoutingContext context);
}

View File

@@ -1,8 +1,12 @@
package cn.qaiu.vx.core.model; package cn.qaiu.vx.core.model;
import cn.qaiu.vx.core.util.CastUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.vertx.core.json.JsonObject;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
/** /**
@@ -13,6 +17,7 @@ import java.io.Serializable;
*/ */
public class JsonResult<T> implements Serializable { public class JsonResult<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final int SUCCESS_CODE = 200; private static final int SUCCESS_CODE = 200;
@@ -157,4 +162,16 @@ public class JsonResult<T> implements Serializable {
public static <T> JsonResult<T> success() { public static <T> JsonResult<T> success() {
return new JsonResult<>(SUCCESS_CODE, SUCCESS_MESSAGE, true, null); return new JsonResult<>(SUCCESS_CODE, SUCCESS_MESSAGE, true, null);
} }
// 转为json对象
public JsonObject toJsonObject() {
return JsonObject.mapFrom(this);
}
private static final ObjectMapper mapper = new ObjectMapper();
// 转为json对象
public static JsonResult<?> toJsonResult(JsonObject json) {
return CastUtil.cast(json.mapTo(JsonResult.class));
}
} }

View File

@@ -1,5 +1,6 @@
package cn.qaiu.vx.core.util; package cn.qaiu.vx.core.util;
import cn.qaiu.vx.core.annotaions.HandleSortFilter;
import io.vertx.core.buffer.Buffer; import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonObject;
import org.apache.commons.beanutils.ConvertUtils; import org.apache.commons.beanutils.ConvertUtils;
@@ -15,6 +16,8 @@ import java.net.URL;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/** /**
* CommonUtil * CommonUtil
@@ -70,10 +73,11 @@ public class CommonUtil {
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
return false; return false;
} }
try(Socket ignored = new Socket(Address, port)) { try (Socket ignored = new Socket(Address, port)) {
//建立一个Socket连接 //建立一个Socket连接
flag = true; flag = true;
} catch (IOException ignoredException) {} } catch (IOException ignoredException) {
}
return flag; return flag;
} }
@@ -126,4 +130,32 @@ public class CommonUtil {
LocalConstant.put(configName, map); LocalConstant.put(configName, map);
LOGGER.info("读取配置{}成功", configName); LOGGER.info("读取配置{}成功", configName);
} }
public static <T> Set<T> sortClassSet(Set<Class<? extends T>> set) {
return set.stream().filter(c1 -> {
HandleSortFilter s1 = c1.getAnnotation(HandleSortFilter.class);
if (s1 != null) {
return s1.value() > 0;
} else {
return true;
}
}).sorted((c1, c2) -> {
HandleSortFilter s1 = c1.getAnnotation(HandleSortFilter.class);
HandleSortFilter s2 = c2.getAnnotation(HandleSortFilter.class);
int n1 = 0, n2 = 0;
if (s1 != null) {
n1 = s1.value();
}
if (s2 != null) {
n2 = s2.value();
}
return n1 - n2;
}).map(c -> {
try {
return ReflectionUtil.newWithNoParam(c);
} catch (Exception e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toSet());
}
} }

View File

@@ -3,9 +3,14 @@ package cn.qaiu.vx.core.util;
public interface ConfigConstant { public interface ConfigConstant {
String CUSTOM = "custom"; String CUSTOM = "custom";
String VERTX = "vertx"; String VERTX = "vertx";
String EVENT_LOOP_POOL_SIZE = "eventLoopPoolSize";
String LOCAL = "local"; String LOCAL = "local";
String SERVER = "server"; String SERVER = "server";
String GLOBAL_CONFIG = "globalConfig"; String GLOBAL_CONFIG = "globalConfig";
String CUSTOM_CONFIG = "customConfig"; String CUSTOM_CONFIG = "customConfig";
String ASYNC_SERVICE_INSTANCES = "asyncServiceInstances"; String ASYNC_SERVICE_INSTANCES = "asyncServiceInstances";
String IGNORES_REG="ignoresReg";
String BASE_LOCATIONS="baseLocations";
String ROUTE_TIME_OUT="routeTimeOut";
} }

View File

@@ -27,12 +27,12 @@ public final class ParamUtil {
return map; return map;
} }
public static <T> T multiMapToEntity(MultiMap multiMap,Class<T> tClass) throws NoSuchMethodException { public static <T> T multiMapToEntity(MultiMap multiMap, Class<T> tClass) throws NoSuchMethodException {
Map<String,String> map = multiMapToMap(multiMap); Map<String, String> map = multiMapToMap(multiMap);
T obj = null; T obj = null;
try { try {
obj = tClass.getDeclaredConstructor().newInstance(); obj = tClass.getDeclaredConstructor().newInstance();
BeanUtils.populate(obj,map); BeanUtils.populate(obj, map);
} catch (InstantiationException | IllegalAccessException e) { } catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace(); e.printStackTrace();
LOGGER.error("实例化异常"); LOGGER.error("实例化异常");
@@ -42,4 +42,21 @@ public final class ParamUtil {
} }
return obj; return obj;
} }
public static MultiMap paramsToMap(String paramString) {
MultiMap entries = MultiMap.caseInsensitiveMultiMap();
if (paramString == null) return entries;
String[] params = paramString.split("&");
if (params.length == 0) return entries;
for (String param : params) {
String[] kv = param.split("=");
if (kv.length == 2) {
entries.set(kv[0], kv[1]);
} else {
entries.set(kv[0], "");
}
}
return entries;
}
} }

View File

@@ -24,6 +24,8 @@ import java.net.URL;
import java.text.ParseException; import java.text.ParseException;
import java.util.*; import java.util.*;
import static cn.qaiu.vx.core.util.ConfigConstant.BASE_LOCATIONS;
/** /**
* 基于org.reflection和javassist的反射工具包 * 基于org.reflection和javassist的反射工具包
* 通过包扫描实现路由地址的注解映射 * 通过包扫描实现路由地址的注解映射
@@ -33,6 +35,16 @@ import java.util.*;
*/ */
public final class ReflectionUtil { public final class ReflectionUtil {
/**
* 以默认配置的基础包路径获取反射器
*
* @return Reflections object
*/
public static Reflections getReflections() {
return getReflections(SharedDataUtil.getStringForCustomConfig(BASE_LOCATIONS));
}
/** /**
* 获取反射器 * 获取反射器
* *
@@ -48,6 +60,7 @@ public final class ReflectionUtil {
} else { } else {
packageAddressList = Collections.singletonList(packageAddress); packageAddressList = Collections.singletonList(packageAddress);
} }
return getReflections(packageAddressList); return getReflections(packageAddressList);
} }
@@ -70,11 +83,12 @@ public final class ReflectionUtil {
// 发现注解api层 没有继承父类时 这里反射一直有问题(Scanner SubTypesScanner was not configured) // 发现注解api层 没有继承父类时 这里反射一直有问题(Scanner SubTypesScanner was not configured)
// 因此这里需要手动配置各种Scanner扫描器 -- https://blog.csdn.net/qq_29499107/article/details/106889781 // 因此这里需要手动配置各种Scanner扫描器 -- https://blog.csdn.net/qq_29499107/article/details/106889781
configurationBuilder.setScanners( configurationBuilder.setScanners(
new SubTypesScanner(false), //允许getAllTypes获取所有Object的子类, 不设置为false则 getAllTypes 会报错.默认为true. Scanners.SubTypes.filterResultsBy(s -> true), //允许getAllTypes获取所有Object的子类, 不设置为false则 getAllTypes
// 会报错.默认为true.
new MethodParameterNamesScanner(), //设置方法参数名称 扫描器,否则调用getConstructorParamNames 会报错 new MethodParameterNamesScanner(), //设置方法参数名称 扫描器,否则调用getConstructorParamNames 会报错
new MethodAnnotationsScanner(), //设置方法注解 扫描器, 否则getConstructorsAnnotatedWith,getMethodsAnnotatedWith 会报错 Scanners.MethodsAnnotated, //设置方法注解 扫描器, 否则getConstructorsAnnotatedWith,getMethodsAnnotatedWith 会报错
new MemberUsageScanner(), //设置 member 扫描器,否则 getMethodUsage 会报错, 不推荐使用,有可能会报错 Caused by: java.lang.ClassCastException: javassist.bytecode.InterfaceMethodrefInfo cannot be cast to javassist.bytecode.MethodrefInfo new MemberUsageScanner(), //设置 member 扫描器,否则 getMethodUsage 会报错
new TypeAnnotationsScanner() //设置类注解 扫描器 ,否则 getTypesAnnotatedWith 会报错 Scanners.TypesAnnotated //设置类注解 扫描器 ,否则 getTypesAnnotatedWith 会报错
); );
configurationBuilder.filterInputsBy(filterBuilder); configurationBuilder.filterInputsBy(filterBuilder);
@@ -98,7 +112,8 @@ public final class ReflectionUtil {
MethodInfo methodInfo = cm.getMethodInfo(); MethodInfo methodInfo = cm.getMethodInfo();
CtClass[] parameterTypes = cm.getParameterTypes(); CtClass[] parameterTypes = cm.getParameterTypes();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); LocalVariableAttribute attr =
(LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
boolean flag = true; boolean flag = true;
boolean flag2 = cm.getModifiers() - 1 != AccessFlag.STATIC; boolean flag2 = cm.getModifiers() - 1 != AccessFlag.STATIC;
@@ -110,7 +125,8 @@ public final class ReflectionUtil {
continue; continue;
} }
flag = false; flag = false;
paramMap.put(attr.variableName(j + (flag2 ? 1 : 0)), Pair.of(parameterAnnotations[j - k], parameterTypes[j - k])); paramMap.put(attr.variableName(j + (flag2 ? 1 : 0)), Pair.of(parameterAnnotations[j - k],
parameterTypes[j - k]));
} }
} catch (NotFoundException e) { } catch (NotFoundException e) {
e.printStackTrace(); e.printStackTrace();
@@ -214,7 +230,8 @@ public final class ReflectionUtil {
if (ctClass.isPrimitive() || "java.util.Date".equals(ctClass.getName())) { if (ctClass.isPrimitive() || "java.util.Date".equals(ctClass.getName())) {
return true; return true;
} }
return ctClass.getName().matches("^java\\.lang\\.((Boolean)|(Character)|(Byte)|(Short)|(Integer)|(Long)|(Float)|(Double)|(String))$"); return ctClass.getName().matches("^java\\.lang\\.((Boolean)|(Character)|(Byte)|(Short)|(Integer)|(Long)|" +
"(Float)|(Double)|(String))$");
} }
/** /**
@@ -238,7 +255,8 @@ public final class ReflectionUtil {
* @throws InstantiationException InstantiationException * @throws InstantiationException InstantiationException
* @throws IllegalAccessException IllegalAccessException * @throws IllegalAccessException IllegalAccessException
*/ */
public static <T> T newWithNoParam(Class<T> handler) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { public static <T> T newWithNoParam(Class<T> handler) throws NoSuchMethodException, InvocationTargetException,
InstantiationException, IllegalAccessException {
return handler.getConstructor().newInstance(); return handler.getConstructor().newInstance();
} }

View File

@@ -0,0 +1,40 @@
package cn.qaiu.vx.core.util;
import cn.qaiu.vx.core.model.JsonResult;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
public class ResponseUtil {
public static void redirect(HttpServerResponse response, String url) {
response.putHeader(HttpHeaders.LOCATION, url).setStatusCode(302).end();
}
public static void redirect(HttpServerResponse response, String url, Promise<?> promise) {
redirect(response, url);
promise.complete();
}
public static void fireJsonObjectResponse(RoutingContext ctx, JsonObject jsonObject) {
ctx.response().putHeader(CONTENT_TYPE, "application/json; charset=utf-8")
.setStatusCode(200)
.end(jsonObject.encode());
}
public static <T> void fireJsonResultResponse(RoutingContext ctx, JsonResult<T> jsonResult) {
fireJsonObjectResponse(ctx, jsonResult.toJsonObject());
}
public static void fireTextResponse(RoutingContext ctx, String text) {
ctx.response().putHeader(CONTENT_TYPE, "text/html; charset=utf-8").end(text);
}
public static void sendError(RoutingContext ctx, int statusCode) {
ctx.response().setStatusCode(statusCode).end();
}
}

View File

@@ -32,11 +32,11 @@ public class SharedDataUtil {
return (JsonObject) localMap.get(key); return (JsonObject) localMap.get(key);
} }
public static JsonObject getJsonObjectForCustomConfig(String key) { public static JsonObject getCustomConfig() {
return getJsonConfig("customConfig").getJsonObject(key); return getJsonConfig("customConfig");
} }
public static String getJsonStringForCustomConfig(String key) { public static String getStringForCustomConfig(String key) {
return getJsonConfig("customConfig").getString(key); return getJsonConfig("customConfig").getString(key);
} }

View File

@@ -23,7 +23,6 @@ public class RouterVerticle extends AbstractVerticle {
private static final int port = SharedDataUtil.getValueForServerConfig("port"); private static final int port = SharedDataUtil.getValueForServerConfig("port");
private static final Router router = new RouterHandlerFactory( private static final Router router = new RouterHandlerFactory(
SharedDataUtil.getJsonStringForCustomConfig("routerLocations"),
SharedDataUtil.getJsonStringForServerConfig("contextPath")).createRouter(); SharedDataUtil.getJsonStringForServerConfig("contextPath")).createRouter();
private static final JsonObject globalConfig = SharedDataUtil.getJsonConfig("globalConfig"); private static final JsonObject globalConfig = SharedDataUtil.getJsonConfig("globalConfig");

View File

@@ -3,7 +3,6 @@ package cn.qaiu.vx.core.verticle;
import cn.qaiu.vx.core.annotaions.Service; import cn.qaiu.vx.core.annotaions.Service;
import cn.qaiu.vx.core.base.BaseAsyncService; import cn.qaiu.vx.core.base.BaseAsyncService;
import cn.qaiu.vx.core.util.ReflectionUtil; import cn.qaiu.vx.core.util.ReflectionUtil;
import cn.qaiu.vx.core.util.SharedDataUtil;
import io.vertx.core.AbstractVerticle; import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise; import io.vertx.core.Promise;
import io.vertx.serviceproxy.ServiceBinder; import io.vertx.serviceproxy.ServiceBinder;
@@ -27,8 +26,7 @@ public class ServiceVerticle extends AbstractVerticle {
private static final Set<Class<?>> handlers; private static final Set<Class<?>> handlers;
static { static {
String handlerLocations = SharedDataUtil.getJsonStringForCustomConfig("handlerLocations"); Reflections reflections = ReflectionUtil.getReflections();
Reflections reflections = ReflectionUtil.getReflections(handlerLocations);
handlers = reflections.getTypesAnnotatedWith(Service.class); handlers = reflections.getTypesAnnotatedWith(Service.class);
} }

68
parser/pom.xml Normal file
View File

@@ -0,0 +1,68 @@
<?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">
<parent>
<artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId>
<version>0.1.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>parser</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--logback日志实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjdk.nashorn/nashorn-core -->
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,24 @@
package cn.qaiu;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WebClientVertxInit {
private Vertx vertx = null;
private static final WebClientVertxInit INSTANCE = new WebClientVertxInit();
private static final Logger log = LoggerFactory.getLogger(WebClientVertxInit.class);
public static void init(Vertx vx) {
INSTANCE.vertx = vx;
}
public static Vertx get() {
if (INSTANCE.vertx == null) {
log.info("getVertx: Vertx实例不存在, 创建Vertx实例.");
INSTANCE.vertx = Vertx.vertx();
}
return INSTANCE.vertx;
}
}

View File

@@ -0,0 +1,62 @@
package cn.qaiu.parser;//package cn.qaiu.lz.common.parser;
import cn.qaiu.parser.impl.*;
import io.vertx.core.Future;
public interface IPanTool {
Future<String> parse();
static IPanTool typeMatching(String type, String key, String pwd) {
return switch (type) {
case "lz" -> new LzTool(key, pwd);
case "cow" -> new CowTool(key, pwd);
case "ec" -> new EcTool(key, pwd);
case "fc" -> new FcTool(key, pwd);
case "uc" -> new UcTool(key, pwd);
case "ye" -> new YeTool(key, pwd);
case "fj" -> new FjTool(key, pwd);
case "qk" -> new QkTool(key, pwd);
case "le" -> new LeTool(key, pwd);
case "ws" -> new WsTool(key, pwd);
case "qq" -> new QQTool(key, pwd);
case "iz" -> new IzTool(key, pwd);
case "ce" -> new CeTool(key, pwd);
default -> {
throw new UnsupportedOperationException("未知分享类型");
}
};
}
static IPanTool shareURLPrefixMatching(String url, String pwd) {
if (url.contains(CowTool.LINK_KEY)) {
return new CowTool(url, pwd);
} else if (url.startsWith(EcTool.SHARE_URL_PREFIX)) {
return new EcTool(url, pwd);
} else if (url.startsWith(FcTool.SHARE_URL_PREFIX0)) {
return new FcTool(url, pwd);
} else if (url.startsWith(UcTool.SHARE_URL_PREFIX)) {
return new UcTool(url, pwd);
} else if (url.startsWith(YeTool.SHARE_URL_PREFIX)) {
return new YeTool(url, pwd);
} else if (url.startsWith(FjTool.SHARE_URL_PREFIX) || url.startsWith(FjTool.SHARE_URL_PREFIX2)) {
return new FjTool(url, pwd);
} else if (url.startsWith(IzTool.SHARE_URL_PREFIX)) {
return new IzTool(url, pwd);
} else if (url.contains(LzTool.LINK_KEY)) {
return new LzTool(url, pwd);
} else if (url.startsWith(LeTool.SHARE_URL_PREFIX)) {
return new LeTool(url, pwd);
} else if (url.contains(WsTool.SHARE_URL_PREFIX) || url.contains(WsTool.SHARE_URL_PREFIX2)) {
return new WsTool(url, pwd);
} else if (url.contains(QQTool.SHARE_URL_PREFIX)) {
return new QQTool(url, pwd);
} else if (url.contains("/s/")) {
// Cloudreve 网盘通用解析
return new CeTool(url, pwd);
}
throw new UnsupportedOperationException("未知分享类型");
}
}

View File

@@ -0,0 +1,113 @@
package cn.qaiu.parser;
import cn.qaiu.WebClientVertxInit;
import cn.qaiu.util.CommonUtils;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.ext.web.client.WebClientSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 解析器抽象类包含promise, HTTP Client, 默认失败方法等;
* 新增网盘解析器需要继承该类.
*/
public abstract class PanBase {
protected Logger log = LoggerFactory.getLogger(this.getClass());
protected Promise<String> promise = Promise.promise();
/**
* Http client
*/
protected WebClient client = WebClient.create(WebClientVertxInit.get());
/**
* Http client session (会话管理, 带cookie请求)
*/
protected WebClientSession clientSession = WebClientSession.create(client);
/**
* Http client 不自动跳转
*/
protected WebClient clientNoRedirects = WebClient.create(WebClientVertxInit.get(),
new WebClientOptions().setFollowRedirects(false));
/**
* 分享key 可以是整个URL; 如果是URL实现该类时要
* 使用{@link CommonUtils#adaptShortPaths(String urlPrefix, String key)}获取真实的分享key
*/
protected String key;
/**
* 分享密码
*/
protected String pwd;
/**
* 子类重写此构造方法不需要添加额外逻辑
* 如:
* <blockquote><pre>
* public XxTool(String key, String pwd) {
* super(key, pwd);
* }
* </pre></blockquote>
*
* @param key 分享key/url
* @param pwd 分享密码
*/
protected PanBase(String key, String pwd) {
this.key = key;
this.pwd = pwd;
}
/**
* 失败时生成异常消息
*
* @param t 异常实例
* @param errorMsg 提示消息
* @param args log参数变量
*/
protected void fail(Throwable t, String errorMsg, Object... args) {
try {
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
log.error("解析异常: " + s, t.fillInStackTrace());
promise.fail(this.getClass().getSimpleName() + ": 解析异常: " + s + " -> " + t);
} catch (Exception e) {
log.error("ErrorMsg format fail. The parameter has been discarded", e);
log.error("解析异常: " + errorMsg, t.fillInStackTrace());
promise.fail(this.getClass().getSimpleName() + ": 解析异常: " + errorMsg + " -> " + t);
}
}
/**
* 失败时生成异常消息
*
* @param errorMsg 提示消息
* @param args log参数变量
*/
protected void fail(String errorMsg, Object... args) {
try {
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
log.error("解析异常: " + s);
promise.fail(this.getClass().getSimpleName() + " - 解析异常: " + s);
} catch (Exception e) {
log.error("ErrorMsg format fail. The parameter has been discarded", e);
log.error("解析异常: " + errorMsg);
promise.fail(this.getClass().getSimpleName() + " - 解析异常: " + errorMsg);
}
}
/**
* 生成失败Future的处理器
*
* @param errorMsg 提示消息
* @return Handler
*/
protected Handler<Throwable> handleFail(String errorMsg) {
return t -> fail(this.getClass().getSimpleName() + " - 请求异常 {}: -> {}", errorMsg, t.fillInStackTrace());
}
}

View File

@@ -0,0 +1,13 @@
package cn.qaiu.parser;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/6/13 4:26
*/
public enum PanType {
LZ("lz"),
COW("cow");
PanType(String type) {
}
}

View File

@@ -0,0 +1,73 @@
package cn.qaiu.parser.impl;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpRequest;
import java.net.MalformedURLException;
import java.net.URL;
/**
* <a href="https://github.com/cloudreve/Cloudreve">Cloudreve网盘解析</a> <br>
* <a href="https://pan.xiaomuxi.cn">暮希云盘</a> <br>
* <a href="https://pan.huang1111.cn">huang1111</a> <br>
*/
public class CeTool extends PanBase implements IPanTool {
private static final String DOWNLOAD_API_PATH = "/api/v3/share/download/";
// api/v3/share/info/g31PcQ?password=qaiu
private static final String SHARE_API_PATH = "/api/v3/share/info/";
public CeTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
// https://pan.huang1111.cn/s/wDz5TK
// https://pan.huang1111.cn/s/y12bI6 -> https://pan.huang1111
// .cn/api/v3/share/download/y12bI6?path=undefined%2Fundefined;
// 类型解析 -> /ce/https_pan.huang1111.cn_s_wDz5TK
// parser接口 -> /parser?url=https://pan.huang1111.cn/s/wDz5TK
try {
if (key.startsWith("https_") || key.startsWith("http_")) {
key = key.replace("https_", "https://")
.replace("http_", "http://")
.replace("_", "/");
}
// 处理URL
URL url = new URL(key);
String path = url.getPath();
String shareKey = path.substring(3);
String downloadApiUrl = url.getProtocol() + "://" + url.getHost() + DOWNLOAD_API_PATH + shareKey + "?path" +
"=undefined/undefined;";
String shareApiUrl = url.getProtocol() + "://" + url.getHost() + SHARE_API_PATH + shareKey;
// 设置cookie
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
if (pwd != null) {
httpRequest.addQueryParam("password", pwd);
}
// 获取下载链接
httpRequest.send().onSuccess(res -> getDownURL(downloadApiUrl)).onFailure(handleFail(shareApiUrl));
} catch (MalformedURLException e) {
fail(e, "URL解析错误");
}
return promise.future();
}
private void getDownURL(String apiUrl) {
clientSession.putAbs(apiUrl).send().onSuccess(res -> {
JsonObject jsonObject = res.bodyAsJsonObject();
System.out.println(jsonObject.encodePrettily());
if (jsonObject.containsKey("code") && jsonObject.getInteger("code") == 0) {
promise.complete(jsonObject.getString("data"));
} else {
fail("JSON解析失败: {}", jsonObject.encodePrettily());
}
}).onFailure(handleFail(apiUrl));
}
}

View File

@@ -0,0 +1,67 @@
package cn.qaiu.parser.impl;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.CommonUtils;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import org.apache.commons.lang3.StringUtils;
/**
* 奶牛快传解析工具
*
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/4/21 21:19
*/
public class CowTool extends PanBase implements IPanTool {
private static final String API_REQUEST_URL = "https://cowtransfer.com/core/api/transfer/share";
public static final String SHARE_URL_PREFIX = "https://cowtransfer.com/s/";
public static final String LINK_KEY = "cowtransfer.com/s/";
public CowTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
key = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
String url = API_REQUEST_URL + "?uniqueUrl=" + key;
client.getAbs(url).send().onSuccess(res -> {
JsonObject resJson = res.bodyAsJsonObject();
if ("success".equals(resJson.getString("message")) && resJson.containsKey("data")) {
JsonObject dataJson = resJson.getJsonObject("data");
String guid = dataJson.getString("guid");
StringBuilder url2Build = new StringBuilder(API_REQUEST_URL + "/download?transferGuid=" + guid);
if (dataJson.getBoolean("zipDownload")) {
// &title=xxx
JsonObject firstFolder = dataJson.getJsonObject("firstFolder");
url2Build.append("&title=").append(firstFolder.getString("title"));
} else {
String fileId = dataJson.getJsonObject("firstFile").getString("id");
url2Build.append("&fileId=").append(fileId);
}
String url2 = url2Build.toString();
client.getAbs(url2).send().onSuccess(res2 -> {
JsonObject res2Json = res2.bodyAsJsonObject();
if ("success".equals(res2Json.getString("message")) && res2Json.containsKey("data")) {
JsonObject data2 = res2Json.getJsonObject("data");
String downloadUrl = data2.getString("downloadUrl");
if (StringUtils.isNotEmpty(downloadUrl)) {
log.info("cow parse success: {}", downloadUrl);
promise.complete(downloadUrl);
return;
}
fail("cow parse fail: {}; downloadUrl is empty", url2);
return;
}
fail("cow parse fail: {}; json: {}", url2, res2Json);
}).onFailure(handleFail(url2));
return;
}
fail("cow parse fail: {}; json: {}", key, resJson);
}).onFailure(handleFail(url));
return promise.future();
}
}

View File

@@ -0,0 +1,59 @@
package cn.qaiu.parser.impl;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.CommonUtils;
import io.vertx.core.Future;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.uritemplate.UriTemplate;
/**
* 移动云空间解析
*/
public class EcTool extends PanBase implements IPanTool {
private static final String FIRST_REQUEST_URL = "https://www.ecpan.cn/drive/fileextoverrid" +
".do?chainUrlTemplate=https:%2F%2Fwww.ecpan" +
".cn%2Fweb%2F%23%2FyunpanProxy%3Fpath%3D%252F%2523%252Fdrive%252Foutside&parentId=-1&data={dataKey}";
private static final String DOWNLOAD_REQUEST_URL = "https://www.ecpan.cn/drive/sharedownload.do";
public static final String SHARE_URL_PREFIX = "www.ecpan.cn/";
public EcTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
// 第一次请求 获取文件信息
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("dataKey", dataKey).send().onSuccess(res -> {
JsonObject jsonObject = res.bodyAsJsonObject();
log.debug("ecPan get file info -> {}", jsonObject);
JsonObject fileInfo = jsonObject
.getJsonObject("var")
.getJsonObject("chainFileInfo");
if (fileInfo.containsKey("errMesg")) {
fail("{} 解析失败:{} key = {}", FIRST_REQUEST_URL, fileInfo.getString("errMesg"), dataKey);
return;
}
JsonObject cloudpFile = fileInfo.getJsonObject("cloudpFile");
JsonArray fileIdList = JsonArray.of(cloudpFile);
// 构造请求JSON {"extCodeFlag":0,"isIp":0}
JsonObject requestBodyJson = JsonObject.of("extCodeFlag", 0, "isIp", 0);
requestBodyJson.put("shareId", Integer.parseInt(fileInfo.getString("shareId"))); // 注意shareId
// 数据类型
requestBodyJson.put("groupId", cloudpFile.getString("groupId"));
requestBodyJson.put("fileIdList", fileInfo.getJsonArray("cloudpFileList"));
// 第二次请求 获取下载链接
client.postAbs(DOWNLOAD_REQUEST_URL).sendJsonObject(requestBodyJson).onSuccess(res2 -> {
JsonObject jsonRes = res2.bodyAsJsonObject();
log.debug("ecPan get download url -> {}", res2.body().toString());
promise.complete(jsonRes.getJsonObject("var").getString("downloadUrl"));
}).onFailure(handleFail(""));
}
).onFailure(handleFail(FIRST_REQUEST_URL));
return promise.future();
}
}

View File

@@ -0,0 +1,103 @@
package cn.qaiu.parser.impl;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.CommonUtils;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClientSession;
import io.vertx.uritemplate.UriTemplate;
import org.apache.commons.lang3.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 360亿方云
*/
public class FcTool extends PanBase implements IPanTool {
public static final String SHARE_URL_PREFIX0 = "https://v2.fangcloud.com/s";
public static final String SHARE_URL_PREFIX = "https://v2.fangcloud.com/sharing/";
public static final String SHARE_URL_PREFIX2 = "https://v2.fangcloud.cn/sharing/";
private static final String DOWN_REQUEST_URL = "https://v2.fangcloud.cn/apps/files/download?file_id={fid}" +
"&scenario=share&unique_name={uname}";
public FcTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
String data = key.replace("share","sharing");
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, data);
WebClientSession sClient = WebClientSession.create(client);
// 第一次请求 自动重定向
sClient.getAbs(SHARE_URL_PREFIX + dataKey).send().onSuccess(res -> {
// 判断是否是加密分享
if (StringUtils.isNotEmpty(pwd)) {
// 获取requesttoken
String html = res.bodyAsString();
Pattern compile = Pattern.compile("name=\"requesttoken\"\\s+value=\"([a-zA-Z0-9_+=]+)\"");
Matcher matcher = compile.matcher(html);
if (!matcher.find()) {
fail(SHARE_URL_PREFIX + " 未匹配到加密分享的密码输入页面的requesttoken");
return;
}
String token = matcher.group(1);
sClient.postAbs(SHARE_URL_PREFIX2 + dataKey).sendForm(MultiMap.caseInsensitiveMultiMap()
.set("requesttoken", token)
.set("password", pwd)).onSuccess(res2 -> {
if (res2.statusCode() == 302) {
sClient.getAbs(res2.getHeader("Location")).send()
.onSuccess(res3 -> getDownURL(dataKey, promise, res3, sClient))
.onFailure(handleFail(res2.getHeader("Location")));
return;
}
fail(SHARE_URL_PREFIX + " 密码跳转后获取重定向失败");
}).onFailure(handleFail(SHARE_URL_PREFIX2));
return;
}
getDownURL(dataKey, promise, res, sClient);
}).onFailure(handleFail(SHARE_URL_PREFIX + dataKey));
return promise.future();
}
private void getDownURL(String dataKey, Promise<String> promise, HttpResponse<Buffer> res,
WebClientSession sClient) {
// 从HTML中找到文件id
String html = res.bodyAsString();
Pattern compile = Pattern.compile("id=\"typed_id\"\\s+value=\"file_(\\d+)\"");
Matcher matcher = compile.matcher(html);
if (!matcher.find()) {
fail(SHARE_URL_PREFIX + " 未匹配到文件id(typed_id)");
return;
}
String fid = matcher.group(1);
// 创建一个不自动重定向的WebClientSession
WebClientSession sClientNoRedirects = WebClientSession.create(clientNoRedirects, sClient.cookieStore());
// 第二次请求
sClientNoRedirects.getAbs(UriTemplate.of(DOWN_REQUEST_URL))
.setTemplateParam("fid", fid)
.setTemplateParam("unique_name", dataKey).send().onSuccess(res2 -> {
JsonObject resJson;
try {
resJson = res2.bodyAsJsonObject();
} catch (Exception e) {
fail(e, DOWN_REQUEST_URL + " 第二次请求没有返回JSON, 可能下载受限");
return;
}
if (!resJson.getBoolean("success")) {
fail(DOWN_REQUEST_URL + " 第二次请求未得到正确相应: " + resJson);
return;
}
promise.complete(resJson.getString("download_url"));
}).onFailure(handleFail(DOWN_REQUEST_URL));
}
}

View File

@@ -0,0 +1,91 @@
package cn.qaiu.parser.impl;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.AESUtils;
import cn.qaiu.util.CommonUtils;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import io.vertx.uritemplate.UriTemplate;
import java.util.UUID;
/**
* 小飞机网盘
*
* @version V016_230609
*/
public class FjTool extends PanBase implements IPanTool {
public static final String SHARE_URL_PREFIX = "https://www.feijix.com/s/";
public static final String REFERER_URL = "https://share.feijipan.com/";
public static final String SHARE_URL_PREFIX2 = REFERER_URL + "s/";
private static final String API_URL_PREFIX = "https://api.feijipan.com/ws/";
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "recommend/list?devType=6&devModel=Chrome&extra" +
"=2&shareId={shareId}&type=0&offset=1&limit=60";
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "file/redirect?downloadId={fidEncode}&enable=1" +
"&devType=6&uuid={uuid}&timestamp={ts}&auth={auth}";
public FjTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
String dataKey;
if (key.startsWith(SHARE_URL_PREFIX2)) {
dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX2, key);
} else {
dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
}
WebClient client = clientNoRedirects;
String shareId = String.valueOf(AESUtils.idEncrypt(dataKey));
// 第一次请求 获取文件信息
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("shareId", shareId).send().onSuccess(res -> {
JsonObject resJson = res.bodyAsJsonObject();
if (resJson.getInteger("code") != 200) {
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
return;
}
if (resJson.getJsonArray("list").size() == 0) {
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
return;
}
// 文件Id
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
String fileId = fileInfo.getString("fileIds");
String userId = fileInfo.getString("userId");
// 其他参数
long nowTs = System.currentTimeMillis();
String tsEncode = AESUtils.encrypt2Hex(Long.toString(nowTs));
String uuid = UUID.randomUUID().toString();
String fidEncode = AESUtils.encrypt2Hex(fileId + "|");
String auth = AESUtils.encrypt2Hex(fileId + "|" + nowTs);
MultiMap headers0 = MultiMap.caseInsensitiveMultiMap();
headers0.set("referer", REFERER_URL);
// 第二次请求
client.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
.putHeaders(headers0)
.setTemplateParam("fidEncode", fidEncode)
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode)
.setTemplateParam("auth", auth).send().onSuccess(res2 -> {
MultiMap headers = res2.headers();
if (!headers.contains("Location")) {
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res.headers());
return;
}
promise.complete(headers.get("Location"));
}).onFailure(handleFail(SECOND_REQUEST_URL));
}).onFailure(handleFail(FIRST_REQUEST_URL));
return promise.future();
}
}

View File

@@ -0,0 +1,80 @@
package cn.qaiu.parser.impl;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.AESUtils;
import cn.qaiu.util.CommonUtils;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import io.vertx.uritemplate.UriTemplate;
import java.util.UUID;
/**
* 蓝奏云优享
*
*/
public class IzTool extends PanBase implements IPanTool {
public static final String SHARE_URL_PREFIX = "https://www.ilanzou.com/s/";
private static final String API_URL_PREFIX = "https://api.ilanzou.com/unproved/";
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "recommend/list?devType=6&devModel=Chrome&extra" +
"=2&shareId={shareId}&type=0&offset=1&limit=60";
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "file/redirect?downloadId={fidEncode}&enable=1" +
"&devType=6&uuid={uuid}&timestamp={ts}&auth={auth}";
public IzTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
WebClient client = clientNoRedirects;
String shareId = String.valueOf(AESUtils.idEncryptIz(dataKey));
// 第一次请求 获取文件信息
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("shareId", shareId).send().onSuccess(res -> {
JsonObject resJson = res.bodyAsJsonObject();
if (resJson.getInteger("code") != 200) {
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
return;
}
if (resJson.getJsonArray("list").size() == 0) {
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
return;
}
// 文件Id
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
String fileId = fileInfo.getString("fileIds");
String userId = fileInfo.getString("userId");
// 其他参数
long nowTs = System.currentTimeMillis();
String tsEncode = AESUtils.encrypt2HexIz(Long.toString(nowTs));
String uuid = UUID.randomUUID().toString();
// String fidEncode = AESUtils.encrypt2HexIz(fileId + "|");
String fidEncode = AESUtils.encrypt2HexIz(fileId + "|" + userId);
String auth = AESUtils.encrypt2HexIz(fileId + "|" + nowTs);
// 第二次请求
client.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
.setTemplateParam("fidEncode", fidEncode)
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode)
.setTemplateParam("auth", auth).send().onSuccess(res2 -> {
MultiMap headers = res2.headers();
if (!headers.contains("Location")) {
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res.headers());
return;
}
promise.complete(headers.get("Location"));
}).onFailure(handleFail(SECOND_REQUEST_URL));
}).onFailure(handleFail(FIRST_REQUEST_URL));
return promise.future();
}
}

View File

@@ -0,0 +1,94 @@
package cn.qaiu.parser.impl;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.CommonUtils;
import io.vertx.core.Future;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import java.util.UUID;
/**
* <a href="https://lecloud.lenovo.com/">联想乐云</a>
*/
public class LeTool extends PanBase implements IPanTool {
public static final String SHARE_URL_PREFIX = "https://lecloud.lenovo.com/share/";
private static final String API_URL_PREFIX = "https://lecloud.lenovo.com/share/api/clouddiskapi/share/public/v1/";
public LeTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
// {"shareId":"xxx","password":"xxx","directoryId":"-1"}
String apiUrl1 = API_URL_PREFIX + "shareInfo";
client.postAbs(apiUrl1)
.sendJsonObject(JsonObject.of("shareId", dataKey, "password", pwd, "directoryId", -1))
.onSuccess(res -> {
JsonObject resJson = res.bodyAsJsonObject();
if (resJson.containsKey("result")) {
if (resJson.getBoolean("result")) {
JsonObject dataJson = resJson.getJsonObject("data");
// 密码验证失败
if (!dataJson.getBoolean("passwordVerified")) {
fail("密码验证失败, 分享key: {}, 密码: {}", dataKey, pwd);
return;
}
// 获取文件信息
JsonArray files = dataJson.getJsonArray("files");
if (files == null || files.size() == 0) {
fail("Result JSON数据异常: files字段不存在或jsonArray长度为空");
return;
}
JsonObject fileInfoJson = files.getJsonObject(0);
if (fileInfoJson != null) {
// TODO 文件大小fileSize和文件名fileName
Long fileId = fileInfoJson.getLong("fileId");
// 根据文件ID获取跳转链接
getDownURL(dataKey, fileId);
}
} else {
fail("{}: {}", resJson.getString("errcode"), resJson.getString("errmsg"));
}
} else {
fail("Result JSON数据异常: result字段不存在");
}
}).onFailure(handleFail(apiUrl1));
return promise.future();
}
private void getDownURL(String key, Long fileId) {
String uuid = UUID.randomUUID().toString();
JsonArray fileIds = JsonArray.of(fileId);
String apiUrl2 = API_URL_PREFIX + "packageDownloadWithFileIds";
// {"fileIds":[123],"shareId":"xxx","browserId":"uuid"}
client.postAbs(apiUrl2)
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", key, "browserId", uuid))
.onSuccess(res -> {
JsonObject resJson = res.bodyAsJsonObject();
if (resJson.containsKey("result")) {
if (resJson.getBoolean("result")) {
JsonObject dataJson = resJson.getJsonObject("data");
// 获取重定向链接跳转链接
String downloadUrl = dataJson.getString("downloadUrl");
if (downloadUrl == null) {
fail("Result JSON数据异常: downloadUrl不存在");
return;
}
// 获取重定向链接跳转链接
clientNoRedirects.getAbs(downloadUrl).send()
.onSuccess(res2 -> promise.complete(res2.headers().get("Location")))
.onFailure(handleFail(downloadUrl));
} else {
fail("{}: {}", resJson.getString("errcode"), resJson.getString("errmsg"));
}
} else {
fail("Result JSON数据异常: result字段不存在");
}
}).onFailure(handleFail(apiUrl2));
}
}

View File

@@ -0,0 +1,125 @@
package cn.qaiu.parser.impl;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.JsExecUtils;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
import javax.script.ScriptException;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 蓝奏云解析工具
*
* @author QAIU
*/
public class LzTool extends PanBase implements IPanTool {
public static final String SHARE_URL_PREFIX = "https://wwwa.lanzoux.com";
public static final String LINK_KEY = "lanzou";
public LzTool(String key, String pwd) {
super(key, pwd);
}
@SuppressWarnings("unchecked")
public Future<String> parse() {
String sUrl = key.startsWith("https://") ? key : SHARE_URL_PREFIX + "/" + key;
WebClient client = clientNoRedirects;
client.getAbs(sUrl).send().onSuccess(res -> {
String html = res.bodyAsString();
// 匹配iframe
Pattern compile = Pattern.compile("src=\"(/fn\\?[a-zA-Z\\d_+/=]{16,})\"");
Matcher matcher = compile.matcher(html);
// 没有Iframe说明是加密分享, 匹配sign通过密码请求下载页面
if (!matcher.find()) {
// 处理一下JS
String jsText = getJsText(html);
if (jsText == null) {
fail(SHARE_URL_PREFIX + " -> " + sUrl + ": js脚本匹配失败, 可能分享已失效");
return;
}
jsText = jsText.replace("document.getElementById('pwd').value", "\"" + pwd + "\"");
jsText = jsText.substring(0, jsText.indexOf("document.getElementById('rpt')"));
try {
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "down_p");
getDownURL(sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
} catch (ScriptException | NoSuchMethodException e) {
fail(e, "js引擎执行失败");
return;
}
return;
}
String iframePath = matcher.group(1);
client.getAbs(SHARE_URL_PREFIX + iframePath).send().onSuccess(res2 -> {
String html2 = res2.bodyAsString();
// 去TMD正则
// Matcher matcher2 = Pattern.compile("'sign'\s*:\s*'(\\w+)'").matcher(html2);
String jsText = getJsText(html2);
if (jsText == null) {
fail(SHARE_URL_PREFIX + iframePath + " -> " + sUrl + ": js脚本匹配失败, 可能分享已失效");
return;
}
try {
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, null);
getDownURL(sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
} catch (ScriptException | NoSuchMethodException e) {
fail(e, "js引擎执行失败");
}
}).onFailure(handleFail(SHARE_URL_PREFIX));
}).onFailure(handleFail(sUrl));
return promise.future();
}
private String getJsText(String html) {
String jsTagStart = "<script type=\"text/javascript\">";
String jsTagEnd = "</script>";
int index = html.lastIndexOf(jsTagStart);
if (index == -1) {
return null;
}
int startPos = index + jsTagStart.length();
int endPos = html.indexOf(jsTagEnd, startPos);
return html.substring(startPos, endPos);
}
private void getDownURL(String key, WebClient client, Map<String, ?> signMap) {
MultiMap map = MultiMap.caseInsensitiveMultiMap();
signMap.forEach((k, v) -> {
map.set(k, v.toString());
});
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
"like " +
"Gecko) Chrome/111.0.0.0 Mobile Safari/537.36";
headers.set("User-Agent", userAgent2);
headers.set("referer", key);
headers.set("sec-ch-ua-platform", "Android");
headers.set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
String url = SHARE_URL_PREFIX + "/ajaxm.php";
client.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
JsonObject urlJson = res2.bodyAsJsonObject();
if (urlJson.getInteger("zt") != 1) {
fail(urlJson.getString("inf"));
return;
}
String downUrl = urlJson.getString("dom") + "/file/" + urlJson.getString("url");
client.getAbs(downUrl).putHeaders(headers).send()
.onSuccess(res3 -> promise.complete(res3.headers().get("Location")))
.onFailure(handleFail(downUrl));
}).onFailure(handleFail(url));
}
}

View File

@@ -0,0 +1,90 @@
package cn.qaiu.parser.impl;
import cn.qaiu.util.StringUtils;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.ext.web.client.WebClient;
/**
* <a href="https://wx.mail.qq.com/">QQ邮箱</a>
*/
public class QQTool extends PanBase implements IPanTool {
public static final String SHARE_URL_PREFIX = "wx.mail.qq.com/ftn/download?";
public QQTool(String key, String pwd) {
super(key, pwd);
}
@SuppressWarnings("unchecked")
public Future<String> parse() {
WebClient httpClient = this.client;
// 补全链接
if (!this.key.startsWith("https://" + SHARE_URL_PREFIX)) {
if (this.key.startsWith(SHARE_URL_PREFIX)) {
this.key = "https://" + this.key;
} else if (this.key.startsWith("func=")) {
this.key = "https://" + SHARE_URL_PREFIX + this.key;
} else {
throw new UnsupportedOperationException("未知分享类型");
}
}
// 设置基础HTTP头部
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
"like " +
"Gecko) Chrome/111.0.0.0 Mobile Safari/537.36";
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
headers.set("User-Agent", userAgent2);
headers.set("sec-ch-ua-platform", "Android");
headers.set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
// 获取下载中转站页面
httpClient.getAbs(this.key).putHeaders(headers).send().onSuccess(res -> {
if (res.statusCode() == 200) {
String html = res.bodyAsString();
// 匹配文件信息
String filename = StringUtils.StringCutNot(html, "var filename = \"", "\"");
String filesize = StringUtils.StringCutNot(html, "var filesize = ", "\n");
String fileurl = StringUtils.StringCutNot(html, "var url = \"", "\"");
if (filename != null && filesize != null && fileurl != null) {
// 设置所需HTTP头部
headers.set("Referer", "https://" + StringUtils.StringCutNot(this.key, "https://", "/") + "/");
headers.set("Host", StringUtils.StringCutNot(fileurl, "https://", "/"));
res.headers().forEach((k, v) -> {
if (k.toLowerCase().equals("set-cookie")) {
headers.set("Cookie", "mail5k=" + StringUtils.StringCutNot(v, "mail5k=", ";") + ";");
}
});
// 调试匹配的情况
System.out.println("文件名称: " + filename);
System.out.println("文件大小: " + filesize);
System.out.println("文件直链: " + fileurl);
// 提交
promise.complete(fileurl.replace("\\x26", "&"));
} else {
this.fail("匹配失败,可能是分享链接的方式已更新");
}
} else {
this.fail("HTTP状态不正确可能是分享链接的方式已更新");
}
}).onFailure(this.handleFail(this.key));
return promise.future();
}
}

View File

@@ -0,0 +1,46 @@
package cn.qaiu.parser.impl;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
public class QkTool extends PanBase implements IPanTool {
public QkTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
promise.complete("https://lz.qaiu.top");
IntStream.range(0, 1000).forEach(num -> {
clientNoRedirects.getAbs(key).send()
.onSuccess(res -> {
String location = res.headers().get("Location");
System.out.println(num + ":" + location);
})
.onFailure(handleFail("连接失败"));
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
return promise.future();
}
public static void main(String[] args) {
new QkTool("https://pimapi.lenovomm.com/clouddiskapi/v1/shareRedirect?si=12298704&dk" +
"=19ab590770399d4438ea885446e27186cc668cdfa559f5fcc063a1ecf78008e5&pk" +
"=ef45aa4d25c1dcecb631b3394f51539d59cb35c6a40c3911df8ba431ba2a3244&pc=true&ot=ali&ob=sync-cloud-disk" +
"&ok=649593714557087744.dex&fn=classes" +
".dex&ds=8909208&dc=1&bi=asdddsad&ri=&ts=1701235051759&sn" +
"=13dc33749bd9cc108009fa505b3ecca9f358d70874352858475956ba4240e4c3", "")
.parse().onSuccess((res) -> {
});
}
}

View File

@@ -1,22 +1,20 @@
package cn.qaiu.lz.common.util; package cn.qaiu.parser.impl;
import cn.qaiu.vx.core.util.VertxHolder; import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.CommonUtils;
import io.vertx.core.Future; import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import io.vertx.uritemplate.UriTemplate; import io.vertx.uritemplate.UriTemplate;
import lombok.extern.slf4j.Slf4j;
/** /**
* 移动云空间解析 * UC网盘解析
*/ */
@Slf4j public class UcTool extends PanBase implements IPanTool {
public class UcTool {
private static final String API_URL_PREFIX = "https://pc-api.uc.cn/1/clouddrive/"; private static final String API_URL_PREFIX = "https://pc-api.uc.cn/1/clouddrive/";
public static final String FULL_URL_PREFIX = "https://fast.uc.cn/s/"; public static final String SHARE_URL_PREFIX = "https://fast.uc.cn/s/";
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "share/sharepage/token?entry=ft&fr=pc&pr" + private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "share/sharepage/token?entry=ft&fr=pc&pr" +
"=UCBrowser"; "=UCBrowser";
@@ -26,14 +24,13 @@ public class UcTool {
private static final String THIRD_REQUEST_URL = API_URL_PREFIX + "file/download?entry=ft&fr=pc&pr=UCBrowser"; private static final String THIRD_REQUEST_URL = API_URL_PREFIX + "file/download?entry=ft&fr=pc&pr=UCBrowser";
public static Future<String> parse(String data, String code) { public UcTool(String key, String pwd) {
if (!data.startsWith(FULL_URL_PREFIX)) { super(key, pwd);
data = FULL_URL_PREFIX + data; }
}
var passcode = (code == null) ? "" : code; public Future<String> parse() {
var dataKey = data.substring(FULL_URL_PREFIX.length()); var dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
Promise<String> promise = Promise.promise(); var passcode = (pwd == null) ? "" : pwd;
var client = WebClient.create(VertxHolder.getVertxInstance());
var jsonObject = JsonObject.of("share_for_transfer", true); var jsonObject = JsonObject.of("share_for_transfer", true);
jsonObject.put("pwd_id", dataKey); jsonObject.put("pwd_id", dataKey);
jsonObject.put("passcode", passcode); jsonObject.put("passcode", passcode);
@@ -42,7 +39,7 @@ public class UcTool {
log.debug("第一阶段 {}", res.body()); log.debug("第一阶段 {}", res.body());
var resJson = res.bodyAsJsonObject(); var resJson = res.bodyAsJsonObject();
if (resJson.getInteger("code") != 0) { if (resJson.getInteger("code") != 0) {
promise.fail(FIRST_REQUEST_URL + " 返回异常: " + resJson); fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
return; return;
} }
var stoken = resJson.getJsonObject("data").getString("stoken"); var stoken = resJson.getJsonObject("data").getString("stoken");
@@ -55,7 +52,7 @@ public class UcTool {
log.debug("第二阶段 {}", res2.body()); log.debug("第二阶段 {}", res2.body());
JsonObject resJson2 = res2.bodyAsJsonObject(); JsonObject resJson2 = res2.bodyAsJsonObject();
if (resJson2.getInteger("code") != 0) { if (resJson2.getInteger("code") != 0) {
promise.fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2); fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2);
return; return;
} }
// 文件信息 // 文件信息
@@ -71,17 +68,15 @@ public class UcTool {
log.debug("第三阶段 {}", res3.body()); log.debug("第三阶段 {}", res3.body());
var resJson3 = res3.bodyAsJsonObject(); var resJson3 = res3.bodyAsJsonObject();
if (resJson3.getInteger("code") != 0) { if (resJson3.getInteger("code") != 0) {
promise.fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2); fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2);
return; return;
} }
promise.complete(resJson3.getJsonArray("data").getJsonObject(0).getString("download_url")); promise.complete(resJson3.getJsonArray("data").getJsonObject(0).getString("download_url"));
}) }).onFailure(handleFail(THIRD_REQUEST_URL));
.onFailure(t -> promise
.fail(new RuntimeException("解析异常: ", t.fillInStackTrace())));
}).onFailure(t -> promise.fail(new RuntimeException("解析异常: ", t.fillInStackTrace()))); }).onFailure(handleFail(SECOND_REQUEST_URL));
} }
).onFailure(t -> promise.fail(new RuntimeException("解析异常: key = " + dataKey, t.fillInStackTrace()))); ).onFailure(handleFail(FIRST_REQUEST_URL));
return promise.future(); return promise.future();
} }
} }

View File

@@ -0,0 +1,183 @@
package cn.qaiu.parser.impl;
import cn.qaiu.util.StringUtils;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.json.DecodeException;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
/**
* <a href="https://www.wenshushu.cn/">文叔叔</a>
*/
public class WsTool extends PanBase implements IPanTool {
public static final String SHARE_URL_PREFIX = "www.wenshushu.cn/f/";
public static final String SHARE_URL_PREFIX2 = "f.ws59.cn/f/";
public static final String SHARE_URL_API = "https://www.wenshushu.cn/ap/";
public WsTool(String key, String pwd) {
super(key, pwd);
}
@SuppressWarnings("unchecked")
public Future<String> parse() {
WebClient httpClient = this.client;
// 补全链接
if (!this.key.startsWith("https://" + SHARE_URL_PREFIX) && !this.key.startsWith("https://" + SHARE_URL_PREFIX2)) {
if (this.key.startsWith(SHARE_URL_PREFIX) || this.key.startsWith(SHARE_URL_PREFIX2)) {
this.key = "https://" + this.key;
} else if (this.key.matches("^[A-Za-z0-9]+$")) {
this.key = "https://" + SHARE_URL_PREFIX + this.key;
} else {
throw new UnsupportedOperationException("未知分享类型");
}
}
// 设置基础HTTP头部
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
"like " +
"Gecko) Chrome/111.0.0.0 Mobile Safari/537.36";
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
headers.set("User-Agent", userAgent2);
headers.set("sec-ch-ua-platform", "Android");
headers.set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
// 获取匿名登录token
httpClient.postAbs(SHARE_URL_API + "login/anonymous").putHeaders(headers)
.sendJsonObject(JsonObject.of("dev_info", "{}"))
.onSuccess(res -> {
if (res.statusCode() == 200) {
try {
// 设置匿名登录token
String token = res.bodyAsJsonObject().getJsonObject("data").getString("token");
headers.set("X-Token", token);
// 获取文件夹信息
httpClient.postAbs(SHARE_URL_API + "task/mgrtask").putHeaders(headers)
.sendJsonObject(JsonObject.of(
"tid", StringUtils.StringCutNot(key, this.key.startsWith(SHARE_URL_PREFIX) ? SHARE_URL_PREFIX : SHARE_URL_PREFIX2),
"password", ""
)).onSuccess(res2 -> {
if (res2.statusCode() == 200) {
try {
// 获取文件夹信息
String filetime = res2.bodyAsJsonObject().getJsonObject("data").getString("expire"); // 文件夹剩余时间
String filesize = res2.bodyAsJsonObject().getJsonObject("data").getString("file_size"); // 文件夹大小
String filepid = res2.bodyAsJsonObject().getJsonObject("data").getString("ufileid"); // 文件夹pid
String filebid = res2.bodyAsJsonObject().getJsonObject("data").getString("boxid"); // 文件夹bid
// 调试输出文件夹信息
System.out.println("文件夹期限: " + filetime);
System.out.println("文件夹大小: " + filesize);
System.out.println("文件夹pid: " + filepid);
System.out.println("文件夹bid: " + filebid);
// 获取文件信息
httpClient.postAbs(SHARE_URL_API + "ufile/list").putHeaders(headers)
.sendJsonObject(JsonObject.of(
"start", 0,
"sort", JsonObject.of(
"name", "asc"
),
"bid", filebid,
"pid", filepid,
"type", 1,
"options", JsonObject.of(
"uploader", "true"
),
"size", 50
)).onSuccess(res3 -> {
if (res3.statusCode() == 200) {
try {
// 获取文件信息
String filename = res3.bodyAsJsonObject().getJsonObject("data")
.getJsonArray("fileList").getJsonObject(0).getString("fname"); // 文件名称
String filefid = res3.bodyAsJsonObject().getJsonObject("data")
.getJsonArray("fileList").getJsonObject(0).getString("fid"); // 文件fid
// 调试输出文件信息
System.out.println("文件名称: " + filename);
System.out.println("文件fid: " + filefid);
// 检查文件是否失效
httpClient.postAbs(SHARE_URL_API + "dl/sign").putHeaders(headers)
.sendJsonObject(JsonObject.of(
"consumeCode", 0,
"type", 1,
"ufileid", filefid
)).onSuccess(res4 -> {
if (res4.statusCode() == 200) {
try {
// 获取直链
String fileurl = res4.bodyAsJsonObject().getJsonObject("data").getString("url");
// 调试输出文件直链
System.out.println("文件直链: " + fileurl);
if (!fileurl.equals(""))
{
try {
promise.complete(URLDecoder.decode(fileurl, "UTF-8"));
} catch (UnsupportedEncodingException e) {
promise.complete(fileurl);
}
}
else
{
this.fail("文件已失效");
}
} catch (DecodeException | NullPointerException e) {
this.fail("获取文件信息失败,可能是分享链接的方式已更新,或者对方的文件已失效");
}
} else {
this.fail("HTTP状态不正确可能是分享链接的方式已更新");
}
});
} catch (DecodeException | NullPointerException e) {
this.fail("获取文件信息失败,可能是分享链接的方式已更新");
}
} else {
this.fail("HTTP状态不正确可能是分享链接的方式已更新");
}
});
} catch (DecodeException | NullPointerException e) {
this.fail("获取文件夹信息失败,可能是分享链接的方式已更新");
}
} else {
this.fail("HTTP状态不正确可能是分享链接的方式已更新");
}
}).onFailure(this.handleFail(this.key));
} catch (DecodeException | NullPointerException e) {
this.fail("token获取失败可能是分享链接的方式已更新");
}
} else {
this.fail("HTTP状态不正确可能是分享链接的方式已更新");
}
}).onFailure(this.handleFail(this.key));
return promise.future();
}
}

View File

@@ -0,0 +1,162 @@
package cn.qaiu.parser.impl;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.CommonUtils;
import cn.qaiu.util.JsExecUtils;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import io.vertx.uritemplate.UriTemplate;
import org.apache.commons.lang3.StringUtils;
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
import java.net.MalformedURLException;
import java.util.Base64;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 123网盘
*/
public class YeTool extends PanBase implements IPanTool {
public static final String SHARE_URL_PREFIX = "https://www.123pan.com/s/";
public static final String FIRST_REQUEST_URL = SHARE_URL_PREFIX + "{key}.html";
private static final String GET_FILE_INFO_URL = "https://www.123pan.com/a/api/share/get?limit=100&next=1&orderBy" +
"=file_name&orderDirection=asc" +
"&shareKey={shareKey}&SharePwd={pwd}&ParentFileId=0&Page=1&event=homeListFile&operateType=1";
private static final String DOWNLOAD_API_URL = "https://www.123pan.com/a/api/share/download/info?{authK}={authV}";
public YeTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("key", dataKey).send().onSuccess(res -> {
String html = res.bodyAsString();
Pattern compile = Pattern.compile("window.g_initialProps\\s*=\\s*(.*);");
Matcher matcher = compile.matcher(html);
if (!matcher.find()) {
fail(html + "\n Ye: " + dataKey + " 正则匹配失败");
return;
}
String fileInfoString = matcher.group(1);
JsonObject fileInfoJson = new JsonObject(fileInfoString);
JsonObject resJson = fileInfoJson.getJsonObject("res");
JsonObject resListJson = fileInfoJson.getJsonObject("reslist");
if (resJson == null || resJson.getInteger("code") != 0) {
fail(dataKey + " 解析到异常JSON: " + resJson);
return;
}
String shareKey = resJson.getJsonObject("data").getString("ShareKey");
if (resListJson == null || resListJson.getInteger("code") != 0) {
// 加密分享
if (StringUtils.isNotEmpty(pwd)) {
client.getAbs(UriTemplate.of(GET_FILE_INFO_URL))
.setTemplateParam("shareKey", shareKey)
.setTemplateParam("pwd", pwd)
// .setTemplateParam("authKey", AESUtils.getAuthKey("/a/api/share/get"))
.putHeader("Platform", "web")
.putHeader("App-Version", "3")
.send().onSuccess(res2 -> {
JsonObject infoJson = res2.bodyAsJsonObject();
if (infoJson.getInteger("code") != 0) {
fail("{} 状态码异常 {}", dataKey, infoJson);
return;
}
JsonObject getFileInfoJson =
infoJson.getJsonObject("data").getJsonArray("InfoList").getJsonObject(0);
getFileInfoJson.put("ShareKey", shareKey);
getDownUrl(client, getFileInfoJson);
}).onFailure(this.handleFail(GET_FILE_INFO_URL));
} else {
fail("该分享[{}]需要密码",dataKey);
}
return;
}
JsonObject reqBodyJson = resListJson.getJsonObject("data").getJsonArray("InfoList").getJsonObject(0);
reqBodyJson.put("ShareKey", shareKey);
getDownUrl(client, reqBodyJson);
}).onFailure(this.handleFail(FIRST_REQUEST_URL));
return promise.future();
}
private void getDownUrl(WebClient client, JsonObject reqBodyJson) {
log.info(reqBodyJson.encodePrettily());
JsonObject jsonObject = new JsonObject();
// {"ShareKey":"iaKtVv-6OECd","FileID":2193732,"S3keyFlag":"1811834632-0","Size":4203111,
// "Etag":"69c94adbc0b9190cf23c4e958d8c7c53"}
jsonObject.put("ShareKey", reqBodyJson.getString("ShareKey"));
jsonObject.put("FileID", reqBodyJson.getInteger("FileId"));
jsonObject.put("S3keyFlag", reqBodyJson.getString("S3KeyFlag"));
jsonObject.put("Size", reqBodyJson.getLong("Size"));
jsonObject.put("Etag", reqBodyJson.getString("Etag"));
// 调用JS文件获取签名
ScriptObjectMirror getSign;
try {
getSign = JsExecUtils.executeJs("getSign", "/a/api/share/download/info");
} catch (Exception e) {
fail(e, "JS函数执行异常");
return;
}
log.info("ye getSign: {}={}", getSign.get("0").toString(), getSign.get("1").toString());
client.postAbs(UriTemplate.of(DOWNLOAD_API_URL))
.setTemplateParam("authK", getSign.get("0").toString())
.setTemplateParam("authV", getSign.get("1").toString())
.putHeader("Platform", "web")
.putHeader("App-Version", "3")
.sendJsonObject(jsonObject).onSuccess(res2 -> {
JsonObject downURLJson = res2.bodyAsJsonObject();
try {
if (downURLJson.getInteger("code") != 0) {
fail("Ye: downURLJson返回值异常->" + downURLJson);
return;
}
} catch (Exception ignored) {
fail("Ye: downURLJson格式异常->" + downURLJson);
return;
}
String downURL = downURLJson.getJsonObject("data").getString("DownloadURL");
try {
Map<String, String> urlParams = CommonUtils.getURLParams(downURL);
String params = urlParams.get("params");
byte[] decodeByte = Base64.getDecoder().decode(params);
String downUrl2 = new String(decodeByte);
// 获取直链
client.getAbs(downUrl2).send().onSuccess(res3 -> {
JsonObject res3Json = res3.bodyAsJsonObject();
try {
if (res3Json.getInteger("code") != 0) {
fail("Ye: downUrl2返回值异常->" + res3Json);
return;
}
} catch (Exception ignored) {
fail("Ye: downUrl2格式异常->" + downURLJson);
return;
}
promise.complete(res3Json.getJsonObject("data").getString("redirect_url"));
}).onFailure(this.handleFail("获取直链失败"));
} catch (MalformedURLException e) {
fail("urlParams解析异常" + e.getMessage());
}
}).onFailure(this.handleFail(DOWNLOAD_API_URL));
}
}

View File

@@ -0,0 +1,331 @@
package cn.qaiu.util;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Date;
import java.util.HexFormat;
import java.util.Random;
/**
* AES加解密工具类
*
* @author qaiu
**/
public class AESUtils {
/**
* AES密钥标识
*/
public static final String SIGN_AES = "AES";
/**
* 密码器AES模式
*/
public static final String CIPHER_AES = "AES/ECB/PKCS5Padding";
public static final String CIPHER_AES2 = "YbQHZqK/PdQql2+7ATcPQHREAxt0Hn0Ob9v317QirZM=";
public static final String CIPHER_AES2_IZ = "1uQFS3sNeHd/bCrmrQpflXREAxt0Hn0Ob9v317QirZM=";
public static final String CIPHER_AES0;
public static final String CIPHER_AES0_IZ;
/**
* 秘钥长度
*/
public static final int KEY_LENGTH = 16;
/**
* 密钥长度128
*/
public static final int KEY_SIZE_128_LENGTH = 128;
/**
* 密钥长度192
*/
public static final int KEY_SIZE_192_LENGTH = 192;
/**
* 密钥长度256
*/
public static final int KEY_SIZE_256_LENGTH = 256;
static {
try {
CIPHER_AES0 = decryptByBase64AES(CIPHER_AES2, CIPHER_AES);
CIPHER_AES0_IZ = decryptByBase64AES(CIPHER_AES2_IZ, CIPHER_AES);
} catch (IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException | NoSuchAlgorithmException |
InvalidKeyException e) {
throw new RuntimeException(e);
}
}
/**
* 随机生成密钥请使用合适的长度128 192 256
*/
public static Key createKeyString(int keySize) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance(SIGN_AES);
keyGenerator.init(keySize);
SecretKey secretKey = keyGenerator.generateKey();
return new SecretKeySpec(secretKey.getEncoded(), SIGN_AES);
}
/**
* 生成Key对象
*/
public static Key generateKey(String keyString) {
if (keyString.length() > KEY_LENGTH) {
keyString = keyString.substring(0, KEY_LENGTH);
} else if (keyString.length() < KEY_LENGTH) {
keyString = StringUtils.rightPad(keyString, 16, 'L');
}
return new SecretKeySpec(keyString.getBytes(), SIGN_AES);
}
/**
* AES加密
*
* @param source 原文
* @param keyString 秘钥
* @return byte arrays
*/
public static byte[] encryptByAES(String source, String keyString) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance(CIPHER_AES);
cipher.init(Cipher.ENCRYPT_MODE, generateKey(keyString));
return cipher.doFinal(source.getBytes(StandardCharsets.UTF_8));
}
public static byte[] encryptByAES(String source, Key key) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance(CIPHER_AES);
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(source.getBytes(StandardCharsets.UTF_8));
}
/**
* AES加密Base64
*
* @param source 原文
* @param keyString 秘钥
* @return BASE64
*/
public static String encryptBase64ByAES(String source, String keyString) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
byte[] encrypted = encryptByAES(source, keyString);
return Base64.getEncoder().encodeToString(encrypted);
}
public static String encryptBase64ByAES(String source, Key key) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
byte[] encrypted = encryptByAES(source, key);
return Base64.getEncoder().encodeToString(encrypted);
}
/**
* AES加密Hex
*
* @param source 原文
* @param keyString 秘钥
*/
public static String encryptHexByAES(String source, String keyString) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
byte[] encrypted = encryptByAES(source, keyString);
return HexFormat.of().formatHex(encrypted);
}
public static String encryptHexByAES(String source, Key key) throws NoSuchPaddingException,
NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
byte[] encrypted = encryptByAES(source, key);
return HexFormat.of().formatHex(encrypted);
}
public static String encrypt2Hex(String source) {
try {
return encryptHexByAES(source, CIPHER_AES0);
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException |
BadPaddingException e) {
throw new RuntimeException("加密失败: "+ e.getMessage());
}
}
public static String encrypt2HexIz(String source) {
try {
return encryptHexByAES(source, CIPHER_AES0_IZ);
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException |
BadPaddingException e) {
throw new RuntimeException("加密失败: "+ e.getMessage());
}
}
/**
* AES解密
*
* @param encrypted 密文 byte
* @param keyString 秘钥
*/
public static String decryptByAES(byte[] encrypted, String keyString) throws IllegalBlockSizeException,
BadPaddingException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
return decryptByAES(encrypted, generateKey(keyString));
}
public static String decryptByAES(byte[] encrypted, Key key) throws IllegalBlockSizeException,
BadPaddingException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
Cipher cipher = Cipher.getInstance(CIPHER_AES);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decrypted = cipher.doFinal(encrypted);
return new String(decrypted, StandardCharsets.UTF_8);
}
/**
* AES解密
*
* @param encrypted 密文 Hex
* @param keyString 秘钥
*/
public static String decryptByHexAES(String encrypted, String keyString) throws IllegalBlockSizeException,
BadPaddingException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
return decryptByAES(HexFormat.of().parseHex(encrypted), keyString);
}
public static String decryptByHexAES(String encrypted, Key key) throws IllegalBlockSizeException,
BadPaddingException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
return decryptByAES(HexFormat.of().parseHex(encrypted), key);
}
/**
* AES解密
*
* @param encrypted 密文 Base64
* @param keyString 秘钥
*/
public static String decryptByBase64AES(String encrypted, String keyString) throws IllegalBlockSizeException,
BadPaddingException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
return decryptByAES(Base64.getDecoder().decode(encrypted), keyString);
}
public static String decryptByBase64AES(String encrypted, Key key) throws IllegalBlockSizeException,
BadPaddingException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
return decryptByAES(Base64.getDecoder().decode(encrypted), key);
}
// ================================飞机盘Id解密========================================== //
private static final char[] array = {
'T', 'U', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'0', 'M', 'N', 'O', 'P', 'X', 'Y', 'Z', 'V', 'W',
'Q', '1', '2', '3', '4', 'a', 'b', 'c', 'd', 'e',
'5', '6', '7', '8', '9', 'v', 'w', 'x', 'y', 'z',
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'L', 'R', 'S', 'I',
'J', 'K'};
private static final char[] arrayIz =
{'Y', 'y', '0', 'Z', 'z', 'N', 'n', 'M', 'I', '6', 'm', 'W', 'w', '1', 'X', 'x', 'L', 'l', 'K', '7', 'k',
'i', 'U', 'u', '2', 'V', 'v', 'J', 'j', '8', 'G', 'g', 'F', 'S', 's', '3', 'T', 't', 'H', 'h',
'f', 'E', 'e', 'D', 'Q', 'q', '4', 'R', 'r', '9', 'd', 'a', 'C', 'c', 'B', 'O', 'o', '5', 'P',
'p', 'b', 'A'};
private static int decodeChar(char c, char[] keys) {
for (int i = 0; i < keys.length; i++) {
if (c == keys[i]) {
return i;
}
}
return -1;
}
// id解密
public static int idEncrypt(String str) {
return idEncrypt0(str, array, 2, 2);
}
// ================================蓝奏优享版Id解密========================================== //
public static int idEncryptIz(String str) {
// idEncrypt(e) {
// let t = 1
// , n = 0;
// if ("" != e && e.length > 4) {
// let r;
// e = e.substring(3, e.length - 1);
// for (let v = 0; v < e.length; v++)
// r = e.charAt(e.length - v - 1),
// n += this.decodeChar(r) * t,
// t *= 62
// }
// return n
// },
return idEncrypt0(str, arrayIz, 3, 1);
}
public static int idEncrypt0(String str, char[] keys, int x1, int x2) {
// 倍数
int multiple = 1;
int result = 0;
if (StringUtils.isNotEmpty(str) && str.length() > 4) {
str = str.substring(x1, str.length() - x2);
char c;
for (int i = 0; i < str.length(); i++) {
c = str.charAt(str.length() - i - 1);
result += decodeChar(c, keys) * multiple;
multiple = multiple * 62;
}
}
return result;
}
// ========================== musetransfer加密相关 ===========================
//length用户要求产生字符串的长度
public static String getRandomString(int length){
String str="abcdefghijklmnopqrstuvwxyz0123456789";
Random random=new Random();
StringBuilder sb=new StringBuilder();
for(int i=0;i<length;i++){
int number=random.nextInt(36);
sb.append(str.charAt(number));
}
return sb.toString();
}
public static String getRandomString(){
return getRandomString(10);
}
//=============================== 123pan加密相关 ===============================
public static String getMD5Str(String str) {
byte[] digest;
try {
MessageDigest md5 = MessageDigest.getInstance("md5");
digest = md5.digest(str.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
//16是表示转换为16进制数
return new BigInteger(1, digest).toString(16);
}
public static String getAuthKey(String _0x2207af) {
String _0x467baa = "web";
int _0x4965f1 = 3;
String _0x430930 = String.valueOf(Math.round(0x989680 * Math.random()));
String _0x53928f = String.valueOf(new Date().getTime() / 0x3e8);
String _0x49ec94 = getMD5Str(_0x53928f + "|" + _0x430930 + "|" + _0x2207af + "|" + _0x467baa + "|" + _0x4965f1
+ "|8-8D$sL8gPjom7bk#cY");
return _0x53928f + "-" + _0x430930 + "-" + _0x49ec94;
}
}

View File

@@ -1,4 +1,4 @@
package cn.qaiu.lz.common.util; package cn.qaiu.util;
public class ArrayUtil { public class ArrayUtil {

View File

@@ -0,0 +1,47 @@
package cn.qaiu.util;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class CommonUtils {
/**
* 获取分享key 比如: https://www.ilanzou.com/s/xxx -> xxx
* @param urlPrefix 不包含key的URL前缀
* @param url 完整URL
* @return 分享key
*/
public static String adaptShortPaths(String urlPrefix, String url) {
if (url.endsWith(".html")) {
url = url.substring(0, url.length() - 5);
}
String prefix = "https://";
if (!url.startsWith(urlPrefix) && url.startsWith(prefix)) {
urlPrefix = urlPrefix.substring(prefix.length());
return url.substring(url.indexOf(urlPrefix) + urlPrefix.length());
} else if (!url.startsWith(urlPrefix)) {
url = urlPrefix + url;
}
return url.substring(urlPrefix.length());
}
public static Map<String, String> getURLParams(String url) throws MalformedURLException {
URL fullUrl = new URL(url);
String query = fullUrl.getQuery();
String[] params = query.split("&");
Map<String, String> map = new HashMap<>();
for (String param : params) {
if (!param.contains("=")) {
throw new RuntimeException("解析URL异常: 匹配不到参数中的=");
}
int endIndex = param.indexOf('=');
String key = param.substring(0, endIndex);
String value = param.substring(endIndex + 1);
map.put(key, value);
}
return map;
}
}

View File

@@ -0,0 +1,152 @@
package cn.qaiu.util;
public interface JsContent {
String ye123 = """
/*
https://statics.123pan.com/share-static/dist/umi.fb72555e.js
eaefamemdead
eaefameidldy
_0x4f141a(1690439821|5790548|/b/api/share/download/info|web|3|1946841013) = 秘钥
_0x1e2592 1690439821 时间戳
_0x48562f 5790548 随机码
_0x1e37d5 /b/api/share/download/info
_0x4e2d74 web
_0x56f040 3
_0x43bdc6 1946841013 加密时间HASH戳
>>>>
_0x43bdc6=''['concat'](_0x1e2592, '-')['concat'](_0x48562f, '-')['concat'](_0x406c4e)
加密时间HASH戳 = 时间戳-随机码-秘钥
*/
function _0x1b5d95(_0x278d1a) {
var _0x839b57,
_0x4ed4dc = arguments['length'] > 0x2 && void 0x0 !== arguments[0x2] ? arguments[0x2] : 0x8;
if (0x0 === arguments['length'])
return null;
'object' === typeof _0x278d1a ? _0x839b57 = _0x278d1a : (0xa === ('' + _0x278d1a)['length'] && (_0x278d1a = 0x3e8 * parseInt(_0x278d1a)),
_0x839b57 = new Date(_0x278d1a));
var _0xc5c54a = _0x278d1a + 0xea60 * new Date(_0x278d1a)['getTimezoneOffset']()
, _0x3732dc = _0xc5c54a + 0x36ee80 * _0x4ed4dc;
return _0x839b57 = new Date(_0x3732dc),
{
'y': _0x839b57['getFullYear'](),
'm': _0x839b57['getMonth']() + 0x1 < 0xa ? '0' + (_0x839b57['getMonth']() + 0x1) : _0x839b57['getMonth']() + 0x1,
'd': _0x839b57['getDate']() < 0xa ? '0' + _0x839b57['getDate']() : _0x839b57['getDate'](),
'h': _0x839b57['getHours']() < 0xa ? '0' + _0x839b57['getHours']() : _0x839b57['getHours'](),
'f': _0x839b57['getMinutes']() < 0xa ? '0' + _0x839b57['getMinutes']() : _0x839b57['getMinutes']()
};
}
function _0x4f141a(_0x4075b1) {
for (var _0x4eddcb = arguments['length'] > 0x1 && void 0x0 !== arguments[0x1] ? arguments[0x1] : 0xa,
_0x2fc680 = function() {
for (var _0x515c63, _0x361314 = [], _0x4cbdba = 0x0; _0x4cbdba < 0x100; _0x4cbdba++) {
_0x515c63 = _0x4cbdba;
for (var _0x460960 = 0x0; _0x460960 < 0x8; _0x460960++)
_0x515c63 = 0x1 & _0x515c63 ? 0xedb88320 ^ _0x515c63 >>> 0x1 : _0x515c63 >>> 0x1;
_0x361314[_0x4cbdba] = _0x515c63;
}
return _0x361314;
},
_0x4aed86 = _0x2fc680(),
_0x5880f0 = _0x4075b1,
_0x492393 = -0x1, _0x25d82c = 0x0;
_0x25d82c < _0x5880f0['length'];
_0x25d82c++)
_0x492393 = _0x492393 >>> 0x8 ^ _0x4aed86[0xff & (_0x492393 ^ _0x5880f0.charCodeAt(_0x25d82c))];
return _0x492393 = (-0x1 ^ _0x492393) >>> 0x0,
_0x492393.toString(_0x4eddcb);
}
function getSign(_0x1e37d5) {
var _0x4e2d74 = 'web';
var _0x56f040 = 3;
var _0x1e2592 = Math.round((new Date().getTime() + 0x3c * new Date().getTimezoneOffset() * 0x3e8 + 28800000) / 0x3e8).toString();
var key = 'a,d,e,f,g,h,l,m,y,i,j,n,o,p,k,q,r,s,t,u,b,c,v,w,s,z';
var _0x48562f = Math['round'](0x989680 * Math['random']());
var _0x2f7dfc;
var _0x35a889;
var _0x36f983;
var _0x3b043d;
var _0x5bc73b;
var _0x4b30b2;
var _0x32399e;
var _0x25d94e;
var _0x373490;
for (var _0x1c540f in (_0x2f7dfc = key.split(','),
_0x35a889 = _0x1b5d95(_0x1e2592),
_0x36f983 = _0x35a889['y'],
_0x3b043d = _0x35a889['m'],
_0x5bc73b = _0x35a889['d'],
_0x4b30b2 = _0x35a889['h'],
_0x32399e = _0x35a889['f'],
_0x25d94e = [_0x36f983, _0x3b043d, _0x5bc73b, _0x4b30b2, _0x32399e].join(''),
_0x373490 = [],
_0x25d94e))
_0x373490['push'](_0x2f7dfc[Number(_0x25d94e[_0x1c540f])]);
var _0x43bdc6;
var _0x406c4e;
return _0x43bdc6 = _0x4f141a(_0x373490['join']('')),
_0x406c4e = _0x4f141a(''['concat'](_0x1e2592, '|')['concat'](_0x48562f, '|')['concat'](_0x1e37d5, '|')['concat'](_0x4e2d74, '|')['concat'](_0x56f040, '|')['concat'](_0x43bdc6)),
[_0x43bdc6, ''['concat'](_0x1e2592, '-')['concat'](_0x48562f, '-')['concat'](_0x406c4e)];
}
""";
String lz = """
/**
* 蓝奏云解析器js签名获取工具
*/
var signObj;
var $, jQuery;
$ = jQuery = function () {
return new jQuery.fn.init();
}
jQuery.fn = jQuery.prototype = {
init: function () {
return {
focus: function (a) {
},
keyup: function(a) {
},
ajax: function (obj) {
signObj = obj
}
}
},
}
jQuery.fn.init.prototype = jQuery.fn;
// 伪装jquery.ajax函数获取关键数据
$.ajax = function (obj) {
signObj = obj
}
var document = {
getElementById: function (v) {
return {
value: 'v'
}
},
}
""";
}

View File

@@ -0,0 +1,60 @@
package cn.qaiu.util;
import org.apache.commons.lang3.StringUtils;
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
/**
* 执行Js脚本
*
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/7/29 17:35
*/
public class JsExecUtils {
private static final Invocable inv;
// 初始化脚本引擎
static {
ScriptEngineManager engineManager = new ScriptEngineManager();
ScriptEngine engine = engineManager.getEngineByName("JavaScript"); // 得到脚本引擎
try {
engine.eval(JsContent.ye123);
inv = (Invocable) engine;
} catch (ScriptException e) {
throw new RuntimeException(e);
}
}
/**
* 调用js文件
*/
public static ScriptObjectMirror executeJs(String functionName, Object... args) throws ScriptException,
NoSuchMethodException {
//调用js中的函数
return (ScriptObjectMirror) inv.invokeFunction(functionName, args);
}
/**
* 调用执行蓝奏云js文件
*/
public static ScriptObjectMirror executeDynamicJs(String jsText, String funName) throws ScriptException,
NoSuchMethodException {
ScriptEngineManager engineManager = new ScriptEngineManager();
ScriptEngine engine = engineManager.getEngineByName("JavaScript"); // 得到脚本引擎
engine.eval(JsContent.lz + "\n" + jsText);
Invocable inv = (Invocable) engine;
//调用js中的函数
if (StringUtils.isNotEmpty(funName)) {
inv.invokeFunction(funName);
}
return (ScriptObjectMirror) engine.get("signObj");
}
}

View File

@@ -0,0 +1,12 @@
package cn.qaiu.util;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/7/16 1:53
*/
public class PanExceptionUtils {
public static RuntimeException fillRunTimeException(String name, String dataKey, Throwable t) {
return new RuntimeException(name + ": 请求异常: key = " + dataKey, t.fillInStackTrace());
}
}

View File

@@ -0,0 +1,36 @@
package cn.qaiu.util;
public class StringUtils {
// 非贪婪截断匹配
public static String StringCutNot(final String strtarget, final String strstart)
{
int startIdx = strtarget.indexOf(strstart);
if (startIdx != -1) {
startIdx += strstart.length();
return strtarget.substring(startIdx);
}
return null;
}
// 非贪婪截断匹配
public static String StringCutNot(final String strtarget, final String strstart, final String strend)
{
int startIdx = strtarget.indexOf(strstart);
int endIdx = -1;
if (startIdx != -1) {
startIdx += strstart.length();
endIdx = strtarget.indexOf(strend, startIdx);
if (endIdx != -1) {
return strtarget.substring(startIdx, endIdx);
}
}
return null;
}
}

View File

@@ -0,0 +1,46 @@
/**
* 蓝奏云解析器js签名获取工具
*/
var signObj;
var $, jQuery;
$ = jQuery = function () {
return new jQuery.fn.init();
}
jQuery.fn = jQuery.prototype = {
init: function () {
return {
focus: function (a) {
},
keyup: function(a) {
},
ajax: function (obj) {
signObj = obj
}
}
},
}
jQuery.fn.init.prototype = jQuery.fn;
// 伪装jquery.ajax函数获取关键数据
$.ajax = function (obj) {
signObj = obj
}
var document = {
getElementById: function (v) {
return {
value: 'v'
}
},
}

View File

@@ -0,0 +1,95 @@
/*
https://statics.123pan.com/share-static/dist/umi.fb72555e.js
eaefamemdead
eaefameidldy
_0x4f141a(1690439821|5790548|/b/api/share/download/info|web|3|1946841013) = 秘钥
_0x1e2592 1690439821 时间戳
_0x48562f 5790548 随机码
_0x1e37d5 /b/api/share/download/info
_0x4e2d74 web
_0x56f040 3
_0x43bdc6 1946841013 加密时间HASH戳
>>>>
_0x43bdc6=''['concat'](_0x1e2592, '-')['concat'](_0x48562f, '-')['concat'](_0x406c4e)
加密时间HASH戳 = 时间戳-随机码-秘钥
*/
function _0x1b5d95(_0x278d1a) {
var _0x839b57,
_0x4ed4dc = arguments['length'] > 0x2 && void 0x0 !== arguments[0x2] ? arguments[0x2] : 0x8;
if (0x0 === arguments['length'])
return null;
'object' === typeof _0x278d1a ? _0x839b57 = _0x278d1a : (0xa === ('' + _0x278d1a)['length'] && (_0x278d1a = 0x3e8 * parseInt(_0x278d1a)),
_0x839b57 = new Date(_0x278d1a));
var _0xc5c54a = _0x278d1a + 0xea60 * new Date(_0x278d1a)['getTimezoneOffset']()
, _0x3732dc = _0xc5c54a + 0x36ee80 * _0x4ed4dc;
return _0x839b57 = new Date(_0x3732dc),
{
'y': _0x839b57['getFullYear'](),
'm': _0x839b57['getMonth']() + 0x1 < 0xa ? '0' + (_0x839b57['getMonth']() + 0x1) : _0x839b57['getMonth']() + 0x1,
'd': _0x839b57['getDate']() < 0xa ? '0' + _0x839b57['getDate']() : _0x839b57['getDate'](),
'h': _0x839b57['getHours']() < 0xa ? '0' + _0x839b57['getHours']() : _0x839b57['getHours'](),
'f': _0x839b57['getMinutes']() < 0xa ? '0' + _0x839b57['getMinutes']() : _0x839b57['getMinutes']()
};
}
function _0x4f141a(_0x4075b1) {
for (var _0x4eddcb = arguments['length'] > 0x1 && void 0x0 !== arguments[0x1] ? arguments[0x1] : 0xa,
_0x2fc680 = function() {
for (var _0x515c63, _0x361314 = [], _0x4cbdba = 0x0; _0x4cbdba < 0x100; _0x4cbdba++) {
_0x515c63 = _0x4cbdba;
for (var _0x460960 = 0x0; _0x460960 < 0x8; _0x460960++)
_0x515c63 = 0x1 & _0x515c63 ? 0xedb88320 ^ _0x515c63 >>> 0x1 : _0x515c63 >>> 0x1;
_0x361314[_0x4cbdba] = _0x515c63;
}
return _0x361314;
},
_0x4aed86 = _0x2fc680(),
_0x5880f0 = _0x4075b1,
_0x492393 = -0x1, _0x25d82c = 0x0;
_0x25d82c < _0x5880f0['length'];
_0x25d82c++)
_0x492393 = _0x492393 >>> 0x8 ^ _0x4aed86[0xff & (_0x492393 ^ _0x5880f0.charCodeAt(_0x25d82c))];
return _0x492393 = (-0x1 ^ _0x492393) >>> 0x0,
_0x492393.toString(_0x4eddcb);
}
function getSign(_0x1e37d5) {
var _0x4e2d74 = 'web';
var _0x56f040 = 3;
var _0x1e2592 = Math.round((new Date().getTime() + 0x3c * new Date().getTimezoneOffset() * 0x3e8 + 28800000) / 0x3e8).toString();
var key = 'a,d,e,f,g,h,l,m,y,i,j,n,o,p,k,q,r,s,t,u,b,c,v,w,s,z';
var _0x48562f = Math['round'](0x989680 * Math['random']());
var _0x2f7dfc;
var _0x35a889;
var _0x36f983;
var _0x3b043d;
var _0x5bc73b;
var _0x4b30b2;
var _0x32399e;
var _0x25d94e;
var _0x373490;
for (var _0x1c540f in (_0x2f7dfc = key.split(','),
_0x35a889 = _0x1b5d95(_0x1e2592),
_0x36f983 = _0x35a889['y'],
_0x3b043d = _0x35a889['m'],
_0x5bc73b = _0x35a889['d'],
_0x4b30b2 = _0x35a889['h'],
_0x32399e = _0x35a889['f'],
_0x25d94e = [_0x36f983, _0x3b043d, _0x5bc73b, _0x4b30b2, _0x32399e].join(''),
_0x373490 = [],
_0x25d94e))
_0x373490['push'](_0x2f7dfc[Number(_0x25d94e[_0x1c540f])]);
var _0x43bdc6;
var _0x406c4e;
return _0x43bdc6 = _0x4f141a(_0x373490['join']('')),
_0x406c4e = _0x4f141a(''['concat'](_0x1e2592, '|')['concat'](_0x48562f, '|')['concat'](_0x1e37d5, '|')['concat'](_0x4e2d74, '|')['concat'](_0x56f040, '|')['concat'](_0x43bdc6)),
[_0x43bdc6, ''['concat'](_0x1e2592, '-')['concat'](_0x48562f, '-')['concat'](_0x406c4e)];
}

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
<!-- 日志自定义颜色 -->
<!-- https://logback.qos.ch/manual/layouts.html#coloring -->
<!--日志文件主目录:这里${user.home}为当前服务器用户主目录-->
<property name="LOG_HOME" value="logs"/>
<!--日志文件名称这里spring.application.name表示工程名称-->
<property name="LOGBACK_DEFAULT" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<property name="CUSTOMER_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %magenta([%t]) %highlight(%-5p) %yellow(${PID:-}) %cyan(%-40.40logger{39}) : %green(%m%n)"/>
<property name="CUSTOMER_PATTERN2" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) -> %magenta([%15.15thread]) %cyan(%-40.40logger{39}) : %msg%n"/>
<!--配置日志文件(File)-->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--设置策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件路径:这里%d{yyyyMMdd}表示按天分类日志-->
<FileNamePattern>${LOG_HOME}/%d{yyyyMMdd}/run.log</FileNamePattern>
<!--日志保留天数-->
<MaxHistory>15</MaxHistory>
</rollingPolicy>
<!--设置格式-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<!-- 或者使用默认配置 -->
<!--<pattern>${FILE_LOG_PATTERN}</pattern>-->
<charset>utf8</charset>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>100MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 将文件输出设置成异步输出 -->
<appender name="ASYNC-FILE" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>256</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="FILE"/>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符-->
<pattern>${CUSTOMER_PATTERN2}</pattern>
</encoder>
</appender>
<logger name="io.netty" level="warn"/>
<logger name="io.vertx" level="info"/>
<logger name="com.zaxxer.hikari" level="info"/>
<root level="info">
<appender-ref ref="STDOUT"/>
<!-- <appender-ref ref="FILE"/>-->
</root>
</configuration>

View File

@@ -0,0 +1,92 @@
package qaiu.web.test;
import cn.qaiu.util.AESUtils;
import org.junit.Assert;
import org.junit.Test;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
public class TestAESUtil {
// 1686215935703
// B4C5B9833113ACA41F16AABADE17349C
@Test
public void decode() throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException,
BadPaddingException, InvalidKeyException {
String hex = AESUtils.encryptHexByAES("1686215935703", AESUtils.CIPHER_AES2);
Assert.assertEquals("B4C5B9833113ACA41F16AABADE17349C", hex.toUpperCase());
}
@Test
public void encode() throws IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException,
NoSuchAlgorithmException, InvalidKeyException {
String source = AESUtils.decryptByHexAES("B4C5B9833113ACA41F16AABADE17349C", AESUtils.CIPHER_AES2);
Assert.assertEquals("1686215935703", source);
}
@Test
public void toHex() {
byte[] d234EF67A1s = HexFormat.of().parseHex("D234EF67A1");
Assert.assertArrayEquals(new byte[]{(byte) 0xd2, (byte) 0x34, (byte) 0xef, (byte) 0x67, (byte) 0xa1},
d234EF67A1s);
}
@Test
public void base64AES() throws NoSuchAlgorithmException {
System.out.println(HexFormat.of().formatHex(AESUtils.createKeyString(AESUtils.KEY_SIZE_128_LENGTH).getEncoded()));
System.out.println(HexFormat.of().formatHex(AESUtils.createKeyString(AESUtils.KEY_SIZE_192_LENGTH).getEncoded()));
System.out.println(HexFormat.of().formatHex(AESUtils.createKeyString(AESUtils.KEY_SIZE_256_LENGTH).getEncoded()));
// TODO Base64-AES
}
@Test
public void testIdDecode() {
Assert.assertEquals(146731, AESUtils.idEncrypt("7jy0zlv"));
}
// 蓝奏优享
@Test
public void testIzIdDecode() {
Assert.assertEquals(26216, AESUtils.idEncryptIz("lGFndCM"));
}
@Test
public void test00() throws IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException,
NoSuchAlgorithmException, InvalidKeyException {
System.out.println(AESUtils.decryptByBase64AES(AESUtils.CIPHER_AES2, AESUtils.CIPHER_AES));
}
@Test
public void testTs() {
System.out.println(System.currentTimeMillis());
}
@Test
public void testRandom() {
System.out.println(AESUtils.getRandomString());
System.out.println(AESUtils.getRandomString());
System.out.println(AESUtils.getRandomString());
System.out.println(AESUtils.getRandomString());
}
@Test
public void testKeyAuth(){
System.out.println(AESUtils.getAuthKey("/a/api/share/download/info"));
System.out.println(AESUtils.getAuthKey("/a/api/share/download/info"));
System.out.println(AESUtils.getAuthKey("/b/api/share/get"));
}
@Test
public void testAES2() throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException,
BadPaddingException, InvalidKeyException {
System.out.println(AESUtils.encryptBase64ByAES("AAAAA", "123123"));
System.out.println(AESUtils.encryptBase64ByAES("AAAAA", AESUtils.generateKey("123123")));
}
}

View File

@@ -0,0 +1,24 @@
package qaiu.web.test;
import org.junit.Test;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TestRegex {
@Test
public void regexYFC() {
String html = """
<input type="hidden" id="typed_id" value="file_559003251828">
<input type="hidden" id="share_link_token" value="9cbe4b73521ba4d65a8cd38a8c">
""";
Pattern compile = Pattern.compile("id=\"typed_id\"\\s+value=\"file_(\\d+)\"");
Matcher matcher = compile.matcher(html);
if (matcher.find()) {
System.out.println(matcher.group(0));
System.out.println(matcher.group(1));
}
}
}

View File

@@ -1,10 +1,9 @@
package cn.qaiu.web.test; package qaiu.web.test;
import io.vertx.core.MultiMap; import io.vertx.core.MultiMap;
import io.vertx.core.Vertx; import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient; import io.vertx.ext.web.client.WebClient;
import org.apache.commons.lang3.RegExUtils;
import org.junit.Test; import org.junit.Test;
import java.util.regex.Matcher; import java.util.regex.Matcher;

View File

@@ -1,4 +1,4 @@
package cn.qaiu.web.test; package qaiu.web.test;
import io.vertx.core.MultiMap; import io.vertx.core.MultiMap;
import io.vertx.core.Vertx; import io.vertx.core.Vertx;

23
pom.xml
View File

@@ -7,19 +7,31 @@
<groupId>cn.qaiu</groupId> <groupId>cn.qaiu</groupId>
<artifactId>netdisk-fast-download</artifactId> <artifactId>netdisk-fast-download</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>0.1.3</version> <version>0.1.7</version>
<modules> <modules>
<module>core</module> <module>core</module>
<module>web-service</module> <module>web-service</module>
<module>core-database</module> <module>core-database</module>
<module>parser</module>
</modules> </modules>
<properties> <properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<packageDirectory>${project.basedir}/web-service/target/package</packageDirectory> <packageDirectory>${project.basedir}/web-service/target/package</packageDirectory>
<slf4j.version>2.0.5</slf4j.version>
<vertx.version>4.5.6</vertx.version>
<org.reflections.version>0.10.2</org.reflections.version>
<lombok.version>1.18.12</lombok.version>
<slf4j.version>2.0.5</slf4j.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<jackson.version>2.14.2</jackson.version>
<logback.version>1.4.12</logback.version>
</properties> </properties>
<build> <build>
@@ -104,14 +116,7 @@
<!--<failOnError>false</failOnError>--> <!--<failOnError>false</failOnError>-->
<!--当配置true时,只清理filesets里的文件,构建目录中得文件不被清理.默认是flase.--> <!--当配置true时,只清理filesets里的文件,构建目录中得文件不被清理.默认是flase.-->
<excludeDefaultDirectories>false</excludeDefaultDirectories> <excludeDefaultDirectories>false</excludeDefaultDirectories>
<filesets>
<fileset>
<!--要清理的目录位置-->
<directory>${project.basedir}/logs</directory>
<!--是否跟随符号链接 (symbolic links)-->
<followSymlinks>false</followSymlinks>
</fileset>
</filesets>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>

25
web-front/.gitignore vendored Normal file
View File

@@ -0,0 +1,25 @@
.DS_Store
node_modules
/dist
dist.zip
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/nfd-front.zip
/nfd-front

26
web-front/README.md Normal file
View File

@@ -0,0 +1,26 @@
# nfd-web
当前页面修改自开源项目 https://github.com/HurryBy/CloudDiskAnalysis
![11115](https://bd2.qaiu.cn/blog/11115.png)
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

19
web-front/jsconfig.json Normal file
View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

13867
web-front/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

50
web-front/package.json Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "nfd-web",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^1.6.0",
"core-js": "^3.8.3",
"element-ui": "^2.15.12",
"vue": "^2.6.14",
"vue-clipboard2": "^0.3.3",
"vue-json-viewer": "^2.2.22"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-template-compiler": "^2.6.14",
"compression-webpack-plugin": "^6.1.1",
"filemanager-webpack-plugin": "2.0.5"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

BIN
web-front/public/avator.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

168
web-front/public/index.html Normal file
View File

@@ -0,0 +1,168 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Netdisk fast download</title>
<meta name="keywords"
content="Netdisk fast download,网盘直链解析工具">
<meta name="description"
content="Netdisk fast download 网盘直链解析工具">
<script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script>
<script>LA.init({id:"K8zkCkZMgFA6ShZK",ck:"K8zkCkZMgFA6ShZK"})</script>
<style>
.page-loading-wrap {
padding: 120px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.la-timer,
.la-timer>div {
position: relative;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.la-timer {
display: block;
font-size: 0;
color: #fff;
}
.la-timer.la-dark {
color: #333;
}
.la-timer>div {
display: inline-block;
float: none;
background-color: currentColor;
border: 0 solid currentColor;
}
.la-timer {
width: 32px;
height: 32px;
}
.la-timer>div {
width: 32px;
height: 32px;
background: transparent;
border-width: 2px;
border-radius: 100%;
}
.la-timer>div:before,
.la-timer>div:after {
position: absolute;
top: 14px;
left: 14px;
display: block;
width: 2px;
margin-top: -1px;
margin-left: -1px;
content: "";
background: currentColor;
border-radius: 2px;
-webkit-transform-origin: 1px 1px 0;
-moz-transform-origin: 1px 1px 0;
-ms-transform-origin: 1px 1px 0;
-o-transform-origin: 1px 1px 0;
transform-origin: 1px 1px 0;
-webkit-animation: timer-loader 1250ms infinite linear;
-moz-animation: timer-loader 1250ms infinite linear;
-o-animation: timer-loader 1250ms infinite linear;
animation: timer-loader 1250ms infinite linear;
-webkit-animation-delay: -625ms;
-moz-animation-delay: -625ms;
-o-animation-delay: -625ms;
animation-delay: -625ms;
}
.la-timer>div:before {
height: 12px;
}
.la-timer>div:after {
height: 8px;
-webkit-animation-duration: 15s;
-moz-animation-duration: 15s;
-o-animation-duration: 15s;
animation-duration: 15s;
-webkit-animation-delay: -7.5s;
-moz-animation-delay: -7.5s;
-o-animation-delay: -7.5s;
animation-delay: -7.5s;
}
@-webkit-keyframes timer-loader {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-moz-keyframes timer-loader {
0% {
-moz-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-moz-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-o-keyframes timer-loader {
0% {
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes timer-loader {
0% {
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-o-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-moz-transform: rotate(360deg);
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div id="app">
<div class="page-loading-wrap">
<div style="color: #4e9ff5" class="la-timer">
<div></div>
</div>
<h4 style="margin-top:20px;font-size:14px;font-weight:400;color: #515a6e;">正在加载资源...</h4>
</div>
</div>
</body>
</html>

269
web-front/src/App.vue Normal file
View File

@@ -0,0 +1,269 @@
<template>
<div id="app">
<el-row :gutter="20">
<el-card class="box-card">
<div class="demo-basic--circle">
<div class="block" style="text-align: center;">
<el-avatar :size="150" :src="avatar"></el-avatar>
</div>
</div>
<h3 style="text-align: center;">NFD网盘直链解析(API演示)</h3>
<div class="typo">
<p><strong>项目GitHub </strong><a href="https://github.com/qaiu/netdisk-fast-download" target="_blank"
rel="nofollow"><u>netdisk-fast-download</u></a></p>
<p><strong>当前页面修改自开源项目</strong><a href="https://github.com/HurryBy/CloudDiskAnalysis"
target="_blank"
rel="nofollow"><u>CloudDiskAnalysis</u></a></p>
<p><strong>目前支持 </strong>已支持蓝奏云/奶牛快传/移动云云空间/UC网盘(暂时失效)/小飞机盘/亿方云/123云盘</p>
<p>
<el-button><strong @click="getInfo">刷新API调用统计</strong></el-button>
</p>
<p>节点1: 成功:{{ node1Info.success }},失败:{{ node1Info.fail }},总数:{{ node1Info.total }}</p>
<p>节点2: 成功:{{ node2Info.success }},失败:{{ node2Info.fail }},总数:{{ node2Info.total }}</p>
</div>
<hr>
<div class="main" v-loading="isLoading">
<div class="grid-content">
<el-input placeholder="请粘贴分享链接" v-model="link" id="url" lass="input-with-select">
<strong slot="prepend">分享链接</strong>
<el-button slot="append" @click="onSubmit">解析</el-button>
</el-input>
<el-input placeholder="请输入密码" v-model="password" id="url" lass="input-with-select"></el-input>
<el-input v-show="respData.data" placeholder="解析地址" :value="getLink2" id="url" lass="input-with-select">
<el-button slot="append" v-clipboard:copy="getLink2"
v-clipboard:success="onCopy"
v-clipboard:error="onError">点我复制
</el-button>
</el-input>
</div>
<div v-show="respData.code" style="margin-top: 10px">
<strong>解析结果: </strong>
<json-viewer
:value="respData"
:expand-depth=5
copyable
boxed
sort
/>
<a :href="downUrl" v-show="downUrl">点击下载</a>
</div>
</div>
</el-card>
</el-row>
</div>
</template>
<script>
import axios from 'axios'
/*
蓝奏云 (lz)
登录, 上传, 下载, 分享
直链解析
奶牛快传 (cow)
登录, 上传, 下载, 分享
直链解析
移动云空间 (ec)
登录, 上传, 下载, 分享
直链解析
UC网盘 (uc)似乎已经失效,需要登录
登录, 上传, 下载, 分享
直链解析
小飞机网盘 (fj)
登录, 上传, 下载, 分享
直链解析
亿方云 (fc)
登录, 上传, 下载, 分享
直链解析
123云盘 (ye)
登录, 上传, 下载,, 分享
*/
export default {
name: 'App',
data() {
return {
link: "",
password: "",
isLoading: false,
downUrl: null,
avatar: "https://q2.qlogo.cn/headimg_dl?dst_uin=736226400&spec=640",
select: "lz",
respData: {},
panList: [
{
name: "蓝奏云",
value: 'lz'
},
{
name: "奶牛快传",
value: 'cow'
},
{
name: "移动云空间",
value: 'ec'
},
{
name: "UC网盘",
value: 'uc',
disabled: true
},
{
name: "小飞机网盘",
value: 'fj'
},
{
name: "360亿方云",
value: 'fc'
},
{
name: "123云盘",
value: 'ye'
},
],
getLink: '',
getLink2: '',
node1Info: {},
node2Info: {},
}
},
methods: {
onSubmit() {
if (!this.link.startsWith("https://")) {
this.$message.error("请输入有效链接!")
return
}
this.isLoading = true
this.downUrl = ''
this.respData = {}
this.getLink2 = `${location.protocol}//${location.host}/parser?url=${this.link}`
// this.getLink = `${location.protocol}//${location.host}/api/json/parser?url=${this.link}`
this.getLink = `${location.protocol}//${location.host}/json/parser?url=${this.link}`
if (this.password) {
this.getLink += `&pwd=${this.password}`
}
axios.get(this.getLink).then(
response => {
this.isLoading = false
this.respData = response.data
if (response.data.code === 200) {
this.$message({
message: response.data.msg,
type: 'success'
})
this.downUrl = response.data.data
} else {
this.$message.error(response.data.msg)
}
},
error => {
this.isLoading = false
this.$message.error(error.message)
}
)
},
onCopy() {
this.$message.success('复制成功')
},
onError() {
this.$message.error('复制失败')
},
getInfo() {
// 初始化统计信息
axios.get('/n1/statisticsInfo').then(
response => {
if (response.data.success) {
this.node1Info = response.data.data
}
})
axios.get('/n2/statisticsInfo').then(
response => {
if (response.data.success) {
this.node2Info = response.data.data
}
})
}
},
mounted() {
this.getInfo()
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
margin: auto;
padding: 1em;
max-width: 900px;
}
::selection {
background: rgba(0, 149, 255, .1);
}
body:before {
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: .3;
z-index: -1;
content: "";
position: fixed;
}
.grid-content {
margin-top: 1em;
border-radius: 4px;
min-height: 50px;
}
.el-select .el-input {
width: 130px;
}
.input-with-select .el-input-group__prepend {
background-color: #fff;
}
.box-card {
margin-top: 4em !important;
margin-bottom: 4em !important;
opacity: .8;
}
@media screen and (max-width: 700px) {
.box-card {
margin-top: 1em !important;
margin-bottom: 1em !important;
}
}
.download h3 {
margin-top: 2em;
}
.download button {
margin-right: 0.5em;
margin-left: 0.5em;
}
.typo {
text-align: left;
}
.typo a {
color: #2c3e50;
text-decoration: none;
}
hr {
height: 10px;
margin-bottom: .8em;
border: none;
border-bottom: 1px solid rgba(0, 0, 0, .12);
}
</style>

19
web-front/src/main.js Normal file
View File

@@ -0,0 +1,19 @@
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import VueClipboard from 'vue-clipboard2'
import JsonViewer from 'vue-json-viewer'
// Import JsonViewer as a Vue.js plugin
Vue.use(JsonViewer)
// or
// components: {JsonViewer}
Vue.use(VueClipboard)
Vue.config.productionTip = false
Vue.use(ElementUI)
new Vue({
render: h => h(App),
}).$mount('#app')

57
web-front/vue.config.js Normal file
View File

@@ -0,0 +1,57 @@
const path = require("path");
function resolve(dir) {
return path.join(__dirname, dir)
}
const CompressionPlugin = require('compression-webpack-plugin');
const FileManagerPlugin = require('filemanager-webpack-plugin')
module.exports = {
transpileDependencies: true,
lintOnSave: false,
outputDir: 'nfd-front',
devServer: {
host: '127.0.0.1',
port: 6444,
proxy: {
'/api': {
target: 'http://127.0.0.1:6400', // 请求本地
changeOrigin: true,
pathRewrite: {
'^/api': '/' //本身的接口地址没有 '/api' 这种通用前缀所以要rewrite如果本身有则去掉
},
ws: true
},
}
},
configureWebpack: {
// provide the app's title in webpack's name field, so that
// it can be accessed in index.html to inject the correct title.
name: 'Netdisk fast download',
resolve: {
alias: {
'@': resolve('src')
}
},
plugins: [
new CompressionPlugin({
test: /\.js$|\.html$|\.css/, // 匹配文件
threshold: 10240 // 对超过10k文件压缩
}),
new FileManagerPlugin({ //初始化 filemanager-webpack-plugin 插件实例
onEnd: {
mkdir: ['./nfd-front'],
delete: [ //首先需要删除项目根目录下的dist.zip
'./nfd-front.zip',
],
archive: [ //然后我们选择dist文件夹将之打包成dist.zip并放在根目录
{source: './nfd-front', destination: './nfd-front.zip'},
]
}
})
]
},
}

View File

@@ -5,19 +5,17 @@
<parent> <parent>
<artifactId>netdisk-fast-download</artifactId> <artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId> <groupId>cn.qaiu</groupId>
<version>0.1.3</version> <version>0.1.7</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<version>0.1.3</version> <version>0.1.7</version>
<artifactId>web-service</artifactId> <artifactId>web-service</artifactId>
<properties> <properties>
<packageDirectory>${project.basedir}/target/package</packageDirectory> <packageDirectory>${project.basedir}/target/package</packageDirectory>
<java.version>17</java.version> <java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<slf4j.version>2.0.5</slf4j.version> <finalName>netdisk-fast-download</finalName>
<vertx-jooq.version>6.1.0</vertx-jooq.version>
<finalName>netdisk-fast-download-${project.version}</finalName>
</properties> </properties>
<dependencies> <dependencies>
@@ -36,37 +34,35 @@
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
<version>1.4.6</version> <version>${logback.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.fusesource.jansi/jansi -->
<dependency>
<groupId>org.fusesource.jansi</groupId>
<artifactId>jansi</artifactId>
<version>1.17.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version> <version>${slf4j.version}</version>
</dependency> </dependency>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>core-database</artifactId>
<version>0.1.7</version>
</dependency>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>parser</artifactId>
<version>0.1.7</version>
</dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.13.2</version> <version>4.13.2</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.4</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
<version>4.4.1</version>
</dependency>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>core-database</artifactId>
<version>0.1.3</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -105,6 +101,8 @@
<exclude>*.**</exclude> <exclude>*.**</exclude>
<exclude>*/*.xml</exclude> <exclude>*/*.xml</exclude>
<exclude>conf/**</exclude> <exclude>conf/**</exclude>
<exclude>js/**</exclude>
<exclude>http-tools/**</exclude>
</excludes> </excludes>
<archive> <archive>
<manifest> <manifest>
@@ -138,8 +136,7 @@
</goals> </goals>
<configuration> <configuration>
<!--打包时排除的依赖作用域--> <!--打包时排除的依赖作用域-->
<excludeScope>test</excludeScope> <includeScope>runtime</includeScope>
<excludeScope>provided</excludeScope>
<outputDirectory> <outputDirectory>
${packageDirectory}/lib/ ${packageDirectory}/lib/
</outputDirectory> </outputDirectory>

View File

@@ -1,16 +1,20 @@
package cn.qaiu.lz; package cn.qaiu.lz;
import cn.qaiu.WebClientVertxInit;
import cn.qaiu.db.pool.JDBCPoolInit; import cn.qaiu.db.pool.JDBCPoolInit;
import cn.qaiu.vx.core.Deploy; import cn.qaiu.vx.core.Deploy;
import cn.qaiu.vx.core.util.ConfigConstant; import cn.qaiu.vx.core.util.ConfigConstant;
import cn.qaiu.vx.core.util.VertxHolder;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonObject;
import io.vertx.core.json.jackson.DatabindCodec;
/** /**
* 程序入口 * 程序入口
* <br>Create date 2021-05-08 13:00:01 * <br>Create date 2021-05-08 13:00:01
* *
* @author qiu * @author qaiu
*/ */
public class AppMain { public class AppMain {
@@ -24,6 +28,8 @@ public class AppMain {
* @param jsonObject 配置 * @param jsonObject 配置
*/ */
private static void exec(JsonObject jsonObject) { private static void exec(JsonObject jsonObject) {
WebClientVertxInit.init(VertxHolder.getVertxInstance());
DatabindCodec.mapper().registerModule(new JavaTimeModule());
if (jsonObject.getJsonObject(ConfigConstant.SERVER).getBoolean("enableDatabase")) { if (jsonObject.getJsonObject(ConfigConstant.SERVER).getBoolean("enableDatabase")) {
JDBCPoolInit.builder().config(jsonObject.getJsonObject("dataSource")).build().initPool(); JDBCPoolInit.builder().config(jsonObject.getJsonObject("dataSource")).build().initPool();
} }

View File

@@ -1,29 +1,27 @@
package cn.qaiu.lz.common.interceptorImpl; package cn.qaiu.lz.common.interceptorImpl;
import cn.qaiu.vx.core.base.BaseHttpApi; import cn.qaiu.vx.core.annotaions.HandleSortFilter;
import cn.qaiu.vx.core.interceptor.Interceptor; import cn.qaiu.vx.core.interceptor.BeforeInterceptor;
import cn.qaiu.vx.core.model.JsonResult;
import cn.qaiu.vx.core.util.CommonUtil;
import cn.qaiu.vx.core.util.SharedDataUtil; import cn.qaiu.vx.core.util.SharedDataUtil;
import cn.qaiu.vx.core.util.VertxHolder;
import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonArray;
import io.vertx.core.shareddata.LocalMap;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import lombok.val;
import static cn.qaiu.vx.core.util.ConfigConstant.IGNORES_REG;
/** /**
* 默认拦截器实现 * 前置拦截器实现
* 校验用户是否合法 <br>
* TODO 暂时只做简单实现
*/ */
@Slf4j @Slf4j
public class DefaultInterceptor implements Interceptor, BaseHttpApi { @HandleSortFilter(1)
public class DefaultInterceptor implements BeforeInterceptor {
private final JsonArray ignores = SharedDataUtil.getJsonArrayForCustomConfig("ignoresReg"); protected final JsonArray ignores = SharedDataUtil.getJsonArrayForCustomConfig(IGNORES_REG);
@Override @Override
public void handle(RoutingContext ctx) { public void handle(RoutingContext ctx) {
ctx.next(); System.out.println("进入前置拦截器1->" + ctx.request().path());
doNext(ctx);
} }
} }

View File

@@ -0,0 +1,68 @@
package cn.qaiu.lz.common.interceptorImpl;
import cn.qaiu.db.pool.JDBCPoolInit;
import cn.qaiu.lz.common.model.ParserLogInfo;
import cn.qaiu.vx.core.annotaions.HandleSortFilter;
import cn.qaiu.vx.core.interceptor.AfterInterceptor;
import cn.qaiu.vx.core.model.JsonResult;
import cn.qaiu.vx.core.util.CommonUtil;
import cn.qaiu.vx.core.util.SharedDataUtil;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import io.vertx.jdbcclient.JDBCPool;
import io.vertx.sqlclient.templates.SqlTemplate;
import lombok.extern.slf4j.Slf4j;
import static cn.qaiu.vx.core.util.ConfigConstant.IGNORES_REG;
/**
* 记录解析日志
*/
@Slf4j
@HandleSortFilter(99)
public class LogStatistics implements AfterInterceptor {
JDBCPool client = JDBCPoolInit.instance().getPool();
private final JsonArray ignores = SharedDataUtil.getJsonArrayForCustomConfig(IGNORES_REG);
@Override
public void handle(RoutingContext ctx, JsonObject responseData) {
// 判断是否忽略
if (CommonUtil.matchRegList(ignores.getList(), ctx.request().path())) {
return;
}
ParserLogInfo parserLogInfo = new ParserLogInfo();
parserLogInfo.setPath(ctx.request().uri());
if (responseData == null) {
String location = ctx.response().headers().get("location");
if (location != null) {
parserLogInfo.setCode(200);
parserLogInfo.setData(location);
} else {
log.error("location不存在且responseData为空, path={}", ctx.request().path());
}
insert(parserLogInfo);
} else if (responseData.containsKey("code")) {
JsonResult<?> result = JsonResult.toJsonResult(responseData);
parserLogInfo.setCode(result.getCode());
parserLogInfo.setData(result.getCode() == 500 ? result.getMsg() : result.getData().toString());
insert(parserLogInfo);
} else {
log.error("未知json日志: {}, path: {}", responseData.encode(), ctx.request().path());
}
}
void insert(ParserLogInfo info) {
SqlTemplate
.forUpdate(client, "INSERT INTO t_parser_log_info VALUES (#{id},#{logTime},#{path},#{code},#{data})")
.mapFrom(ParserLogInfo.class)
.execute(info)
.onSuccess(res -> {
log.info("inserted log: id={}, path={}, code={}", info.getId(), info.getPath(), info.getCode());
}).onFailure(Throwable::printStackTrace);
}
}

View File

@@ -0,0 +1,23 @@
package cn.qaiu.lz.common.model;
import cn.qaiu.lz.common.util.SnowflakeIdWorker;
import lombok.Data;
import java.util.Date;
@Data
abstract public class BaseModel {
public static final long serialVersionUID = 1L;
private String id = String.valueOf(SnowflakeIdWorker.idWorker().nextId());
private String createBy;
private Date createTime;
private String updateBy;
private Date updateTime;
}

View File

@@ -0,0 +1,16 @@
package cn.qaiu.lz.common.model;
import io.vertx.core.MultiMap;
public class FileInfo extends BaseModel {
private String fileName;
private String fileType;
private Long fileSize;
private String download;
private MultiMap header;
}

View File

@@ -0,0 +1,22 @@
package cn.qaiu.lz.common.model;
import cn.qaiu.db.ddl.Length;
import cn.qaiu.db.ddl.Table;
import cn.qaiu.lz.common.util.SnowflakeIdWorker;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
@Table("t_parser_log_info")
public class ParserLogInfo {
String id = SnowflakeIdWorker.getStringId();
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS", timezone = "GMT+8")
Date logTime = new Date();
String path;
Integer code;
@Length(varcharSize = 4096)
String data;
}

View File

@@ -1,19 +0,0 @@
package cn.qaiu.lz.common.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 获取连接
*
* @author <a href="https://qaiu.top">QAIU</a>
*/
public enum ConnectUtil {
// 实现枚举单例
INSTANCE;
private static final Logger LOGGER = LoggerFactory.getLogger(ConnectUtil.class);
}

View File

@@ -1,80 +0,0 @@
package cn.qaiu.lz.common.util;
import cn.qaiu.vx.core.util.CastUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import java.util.Map;
/**
* 奶牛快传解析工具
*
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/4/21 21:19
*/
@Slf4j
public class CowTool {
/*
First request:
{
"code": "0000",
"message": "success",
"data": {
"guid": "e4f41b51-b5da-4f60-9312-37aa10c0aad7",
"firstFile": {
"id": "23861191276513345",
}
}
}
Then request:
{
"code": "0000",
"message": "success",
"tn": "TN:DE0E092E8A464521983780FBA21D0CD3",
"data": {
"downloadUrl": "https://download.cowcs.com..."
}
}
*/
public static String parse(String fullUrl) throws Exception {
var uniqueUrl = fullUrl.substring(fullUrl.lastIndexOf('/') + 1);
var baseUrl = "https://cowtransfer.com/core/api/transfer/share";
var result = Jsoup
.connect(baseUrl + "?uniqueUrl=" + uniqueUrl).ignoreContentType(true)
.get()
.text();
var objectMapper = new ObjectMapper();
Map<String, Object> map = objectMapper.readValue(result, new TypeReference<>() {
});
if ("success".equals(map.get("message")) && map.containsKey("data")) {
Map<String, Object> data = CastUtil.cast(map.get("data"));
var guid = data.get("guid").toString();
Map<String, Object> firstFile = CastUtil.cast(data.get("firstFile"));
var fileId = firstFile.get("id").toString();
var result2 = Jsoup
.connect(baseUrl + "/download?transferGuid=" + guid + "&fileId=" + fileId)
.ignoreContentType(true)
.get()
.text();
Map<String, Object> map2 = objectMapper.readValue(result2, new TypeReference<>() {});
if ("success".equals(map2.get("message")) && map2.containsKey("data")) {
Map<String, Object> data2 = CastUtil.cast(map2.get("data"));
var downloadUrl = data2.get("downloadUrl").toString();
if (StringUtils.isNotEmpty(downloadUrl)) {
log.info("cow parse success: {}", downloadUrl);
return downloadUrl;
}
}
}
log.info("Cow parse field------------->end");
throw new Exception("Cow解析失败");
}
}

View File

@@ -1,65 +0,0 @@
package cn.qaiu.lz.common.util;
import cn.qaiu.vx.core.util.VertxHolder;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import io.vertx.uritemplate.UriTemplate;
import lombok.extern.slf4j.Slf4j;
/**
* 移动云空间解析
*/
@Slf4j
public class EcTool {
private static final String FULL_URL_PREFIX = "https://www.ecpan.cn/drive/fileextoverrid" +
".do?chainUrlTemplate=https:%2F%2Fwww.ecpan" +
".cn%2Fweb%2F%23%2FyunpanProxy%3Fpath%3D%252F%2523%252Fdrive%252Foutside&parentId=-1&data={dataKey}";
private static final String DOWNLOAD_REQUEST_URL = "https://www.ecpan.cn/drive/sharedownload.do";
public static final String EC_HOST = "www.ecpan.cn";
public static Future<String> parse(String dataKey) {
Promise<String> promise = Promise.promise();
WebClient client = WebClient.create(VertxHolder.getVertxInstance());
// 第一次请求 获取文件信息
client.getAbs(UriTemplate.of(FULL_URL_PREFIX)).setTemplateParam("dataKey", dataKey).send().onSuccess(res -> {
JsonObject jsonObject = res.bodyAsJsonObject();
log.debug("ecPan get file info -> {}", jsonObject);
JsonObject fileInfo = jsonObject
.getJsonObject("var")
.getJsonObject("chainFileInfo");
if (!fileInfo.containsKey("errMesg")) {
JsonObject cloudpFile = fileInfo.getJsonObject("cloudpFile");
JsonArray fileIdList = JsonArray.of(cloudpFile);
// 构造请求JSON {"extCodeFlag":0,"isIp":0}
JsonObject requestBodyJson = JsonObject.of("extCodeFlag", 0, "isIp", 0);
requestBodyJson.put("shareId", Integer.parseInt(fileInfo.getString("shareId"))); // 注意shareId
// 数据类型
requestBodyJson.put("groupId", cloudpFile.getString("groupId"));
requestBodyJson.put("fileIdList", fileInfo.getJsonArray("cloudpFileList"));
// 第二次请求 获取下载链接
client.postAbs(DOWNLOAD_REQUEST_URL)
.sendJsonObject(requestBodyJson).onSuccess(res2 -> {
JsonObject jsonRes = res2.bodyAsJsonObject();
log.debug("ecPan get download url -> {}", res2.body().toString());
promise.complete(jsonRes.getJsonObject("var").getString("downloadUrl"));
}).onFailure(t -> {
promise.fail(new RuntimeException("解析异常: key = " + dataKey, t.fillInStackTrace()));
});
} else {
promise.fail(new RuntimeException(DOWNLOAD_REQUEST_URL + " 解析失败: "
+ fileInfo.getString("errMesg")) + " key = " + dataKey);
}
}
).onFailure(t -> {
promise.fail(new RuntimeException("解析异常: key = " + dataKey, t.fillInStackTrace()));
});
return promise.future();
}
}

View File

@@ -1,95 +0,0 @@
package cn.qaiu.lz.common.util;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jsoup.Jsoup;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 蓝奏云解析工具
*
* @author QAIU
* @version 1.0 update 2021/5/16 10:39
*/
public class LzTool {
public static String parse(String fullUrl) throws Exception {
var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.3";
var url = fullUrl.substring(0, fullUrl.lastIndexOf('/') + 1);
var id = fullUrl.substring(fullUrl.lastIndexOf('/') + 1);
Map<String, String> header = new HashMap<>();
header.put("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
header.put("referer", url);
/*
// 部分链接需要设置安卓UA
sec-ch-ua: "Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"
sec-ch-ua-mobile: ?1
sec-ch-ua-platform: "Android"
*/
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36";
Map<String, String> header2 = new HashMap<>();
header2.put("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
header2.put("sec-ch-ua-mobile", "sec-ch-ua-mobile");
header2.put("sec-ch-ua-platform", "Android");
header2.put("referer", url);
//第一次请求获取iframe的地址
String result = Jsoup.connect(url + id)
.userAgent(userAgent)
.get()
.select(".ifr2")
.attr("src");
//第二次请求得到js里的json数据里的sign
result = Jsoup.connect(url + result)
.headers(header)
.userAgent(userAgent)
.get()
.html();
// System.out.println(result);
// 'sign':'AWcGOFprUGFWX1BvBTVXawdrBDZTOAU_bV2FTZFU7W2sBJ1t4DW0FYFIyBmgDZVJgUjAFNV41UGQFNg_c_c' 改下正则TMD 最近上传竟然没_c_c
Matcher matcher = Pattern.compile("'sign'\s*:\s*'([0-9a-zA-Z_]+)'").matcher(result);
Map<String, String> params = new LinkedHashMap<>();
if (matcher.find()) {
String sn = matcher.group(1).replace("'", "");
params.put("action", "downprocess");
params.put("sign", sn);
params.put("ves", "1");
// System.out.println(sn);
} else {
throw new IOException();
}
//第三次请求 通过参数发起post请求,返回json数据
result = Jsoup
.connect(url + "ajaxm.php")
.headers(header)
.userAgent(userAgent)
.data(params)
.post()
.text()
.replace("\\", "");
//json转为map
params = new ObjectMapper().readValue(result, new TypeReference<>() {});
// System.out.println(params);
//通过json的数据拼接出最终的URL发起第最终请求,并得到响应信息头
url = params.get("dom") + "/file/" + params.get("url");
var headers = Jsoup.connect(url)
.ignoreContentType(true)
.userAgent(userAgent2)
.headers(header2)
.followRedirects(false)
.execute()
.headers();
//得到重定向的地址进行重定向
url = headers.get("Location");
return url;
}
}

View File

@@ -1,4 +1,4 @@
package cn.qaiu.vx.core.util; package cn.qaiu.lz.common.util;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -30,6 +30,8 @@ public class SnowflakeIdWorker {
// ==============================Fields=========================================== // ==============================Fields===========================================
//开始时间截 (2021-01-01)
private static final long EPOCH = 1609459200000L;
/** /**
* 机器id所占的位数 * 机器id所占的位数
*/ */
@@ -94,7 +96,7 @@ public class SnowflakeIdWorker {
* *
* @return SnowflakeId * @return SnowflakeId
*/ */
public synchronized long nextId() { public synchronized Long nextId() {
long timestamp = timeGen(); long timestamp = timeGen();
//如果当前时间小于上一次ID生成的时间戳说明系统时钟回退过这个时候应当抛出异常 //如果当前时间小于上一次ID生成的时间戳说明系统时钟回退过这个时候应当抛出异常
@@ -133,9 +135,7 @@ public class SnowflakeIdWorker {
//时间截向左移22位(5+5+12) //时间截向左移22位(5+5+12)
long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
//开始时间截 (2021-01-01) return ((timestamp - EPOCH) << timestampLeftShift) //
long twepoch = 1609459200000L;
return ((timestamp - twepoch) << timestampLeftShift) //
| (datacenterId << datacenterIdShift) // | (datacenterId << datacenterIdShift) //
| (workerId << sequenceBits) // | (workerId << sequenceBits) //
| sequence; | sequence;
@@ -214,30 +214,18 @@ public class SnowflakeIdWorker {
return snowflakeIdWorker; return snowflakeIdWorker;
} }
synchronized public static Long getLongId() {
return snowflakeIdWorker.nextId();
}
synchronized public static String getStringId() {
return snowflakeIdWorker.nextId().toString();
}
synchronized public static SnowflakeIdWorker idWorkerCluster(long workerId, long datacenterId) { synchronized public static SnowflakeIdWorker idWorkerCluster(long workerId, long datacenterId) {
if (snowflakeIdWorkerCluster == null) { if (snowflakeIdWorkerCluster == null) {
snowflakeIdWorkerCluster = new SnowflakeIdWorker(workerId, datacenterId); snowflakeIdWorkerCluster = new SnowflakeIdWorker(workerId, datacenterId);
} }
return snowflakeIdWorkerCluster; return snowflakeIdWorkerCluster;
} }
//==============================Test=============================================
/**
* 测试
*/
public static void main(String[] args) {
final SnowflakeIdWorker snowflakeIdWorkerCluster = idWorkerCluster(0, 1);
final SnowflakeIdWorker idWorker = idWorker();
for (int i = 0; i < 100; i++) {
long id = idWorker.nextId();
System.out.println(Long.toBinaryString(id));
System.out.println(id);
System.out.println("------------");
id = snowflakeIdWorkerCluster.nextId();
System.out.println(Long.toBinaryString(id));
System.out.println(id);
System.out.println("------------\n");
}
}
} }

View File

@@ -0,0 +1,32 @@
package cn.qaiu.lz.web.http;
import cn.qaiu.lz.web.model.SysUser;
import cn.qaiu.lz.web.service.DbService;
import cn.qaiu.lz.web.service.UserService;
import cn.qaiu.lz.web.model.StatisticsInfo;
import cn.qaiu.vx.core.annotaions.RouteHandler;
import cn.qaiu.vx.core.annotaions.RouteMapping;
import cn.qaiu.vx.core.enums.RouteMethod;
import cn.qaiu.vx.core.util.AsyncServiceUtil;
import io.vertx.core.Future;
import lombok.extern.slf4j.Slf4j;
@RouteHandler(value = "/v2", order = 10)
@Slf4j
public class ParserApi {
private final UserService userService = AsyncServiceUtil.getAsyncServiceInstance(UserService.class);
private final DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
@RouteMapping(value = "/login", method = RouteMethod.POST)
public Future<SysUser> login(SysUser user) {
log.info("<------- login: {}", user.getUsername());
return userService.login(user);
}
@RouteMapping(value = "/statisticsInfo", method = RouteMethod.GET, order = 99)
public Future<StatisticsInfo> statisticsInfo() {
return dbService.getStatisticsInfo();
}
}

View File

@@ -1,24 +1,18 @@
package cn.qaiu.lz.web.http; package cn.qaiu.lz.web.http;
import cn.qaiu.lz.common.util.CowTool; import cn.qaiu.parser.IPanTool;
import cn.qaiu.lz.common.util.EcTool; import cn.qaiu.parser.impl.EcTool;
import cn.qaiu.lz.common.util.LzTool; import cn.qaiu.parser.impl.QQTool;
import cn.qaiu.lz.common.util.UcTool;
import cn.qaiu.lz.web.model.SysUser;
import cn.qaiu.lz.web.service.UserService;
import cn.qaiu.vx.core.annotaions.RouteHandler; import cn.qaiu.vx.core.annotaions.RouteHandler;
import cn.qaiu.vx.core.annotaions.RouteMapping; import cn.qaiu.vx.core.annotaions.RouteMapping;
import cn.qaiu.vx.core.enums.RouteMethod; import cn.qaiu.vx.core.enums.RouteMethod;
import cn.qaiu.vx.core.model.JsonResult; import cn.qaiu.vx.core.util.ResponseUtil;
import cn.qaiu.vx.core.util.AsyncServiceUtil;
import io.vertx.core.Future; import io.vertx.core.Future;
import io.vertx.core.Promise; import io.vertx.core.Promise;
import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse; import io.vertx.core.http.HttpServerResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
/** /**
* 服务API * 服务API
* <br>Create date 2021/4/28 9:15 * <br>Create date 2021/4/28 9:15
@@ -29,130 +23,60 @@ import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
@RouteHandler("/") @RouteHandler("/")
public class ServerApi { public class ServerApi {
private final UserService userService = AsyncServiceUtil.getAsyncServiceInstance(UserService.class); @RouteMapping(value = "/parser", method = RouteMethod.GET, order = 4)
@RouteMapping(value = "/login", method = RouteMethod.POST)
public Future<String> login(SysUser user) {
log.info("<------- login: {}", user.getUsername());
return userService.login(user);
}
@RouteMapping(value = "/test2", method = RouteMethod.GET)
public JsonResult<String> test01() {
return JsonResult.data("ok");
}
@RouteMapping(value = "/parser", method = RouteMethod.GET)
public Future<Void> parse(HttpServerResponse response, HttpServerRequest request, String url, String pwd) { public Future<Void> parse(HttpServerResponse response, HttpServerRequest request, String url, String pwd) {
Promise<Void> promise = Promise.promise();
if (url.contains("lanzou")) {
String urlDownload = null;
try {
urlDownload = LzTool.parse(url);
log.info("url = {}", urlDownload);
response.putHeader("location", urlDownload).setStatusCode(302).end();
promise.complete();
} catch (Exception e) {
promise.fail(e);
}
} else if (url.contains("cowtransfer.com")) {
String urlDownload = null;
try {
urlDownload = CowTool.parse(url);
response.putHeader("location", urlDownload).setStatusCode(302).end();
promise.complete();
} catch (Exception e) {
promise.fail(e);
}
} else if (url.contains(EcTool.EC_HOST)) { Promise<Void> promise = Promise.promise();
if (url.contains(EcTool.SHARE_URL_PREFIX)) {
// 默认读取Url参数会被截断手动获取一下其他参数 // 默认读取Url参数会被截断手动获取一下其他参数
String data = request.getParam("data"); url = EcTool.SHARE_URL_PREFIX + request.getParam("data");
EcTool.parse(data).onSuccess(resUrl -> {
response.putHeader("location", resUrl).setStatusCode(302).end();
promise.complete();
}).onFailure(t -> {
promise.fail(t.fillInStackTrace());
});
} else if (url.contains(UcTool.FULL_URL_PREFIX)) {
UcTool.parse(url, pwd).onSuccess(resUrl -> {
response.putHeader("location", resUrl).setStatusCode(302).end();
promise.complete();
}).onFailure(t -> {
promise.fail(t.fillInStackTrace());
});
} }
if (url.contains(QQTool.SHARE_URL_PREFIX)) {
// 默认读取Url参数会被截断手动获取一下其他参数
url = url + "&key=" + request.getParam("key") +
"&code=" + request.getParam("code") + "&k=" + request.getParam("k") +
"&fweb=" + request.getParam("fweb") + "&cl=" + request.getParam("cl");
}
IPanTool.shareURLPrefixMatching(url, pwd).parse()
.onSuccess(resUrl -> ResponseUtil.redirect(response, resUrl, promise))
.onFailure(t -> promise.fail(t.fillInStackTrace()));
return promise.future(); return promise.future();
} }
@RouteMapping(value = "/lz/:id", method = RouteMethod.GET) @RouteMapping(value = "/json/parser", method = RouteMethod.GET, order = 3)
public void lzParse(HttpServerResponse response, String id) throws Exception { public Future<String> parseJson(HttpServerRequest request, String url, String pwd) {
var url = "https://wwsd.lanzoue.com/" + id; if (url.contains(EcTool.SHARE_URL_PREFIX)) {
var urlDownload = LzTool.parse(url); // 默认读取Url参数会被截断手动获取一下其他参数
log.info("url = {}", urlDownload); url = EcTool.SHARE_URL_PREFIX + request.getParam("data");
response.putHeader("location", urlDownload).setStatusCode(302).end();
}
@RouteMapping(value = "/cow/:id", method = RouteMethod.GET)
public void cowParse(HttpServerResponse response, String id) throws Exception {
var url = "https://cowtransfer.com/s/" + id;
var urlDownload = CowTool.parse(url);
response.putHeader("location", urlDownload).setStatusCode(302).end();
}
@RouteMapping(value = "/json/lz/:id", method = RouteMethod.GET)
public JsonResult<String> lzParseJson(HttpServerResponse response, String id) throws Exception {
var url = "https://wwsd.lanzoue.com/" + id;
var urlDownload = LzTool.parse(url);
log.info("url = {}", urlDownload);
return JsonResult.data(urlDownload);
}
@RouteMapping(value = "/json/cow/:id", method = RouteMethod.GET)
public JsonResult<String> cowParseJson(HttpServerResponse response, String id) throws Exception {
var url = "https://cowtransfer.com/s/" + id;
return JsonResult.data(CowTool.parse(url));
}
@RouteMapping(value = "/ec/:id", method = RouteMethod.GET)
public void ecParse(HttpServerResponse response, String id) {
EcTool.parse(id).onSuccess(resUrl -> {
response.putHeader("location", resUrl).setStatusCode(302).end();
}).onFailure(t -> {
response.putHeader(CONTENT_TYPE, "text/html;charset=utf-8");
response.end(t.getMessage());
});
}
@RouteMapping(value = "/json/ec/:id", method = RouteMethod.GET)
public Future<String> ecParseJson(HttpServerResponse response, String id) {
return EcTool.parse(id);
}
@RouteMapping(value = "/uc/:id", method = RouteMethod.GET)
public void ucParse(HttpServerResponse response, String id) {
String code = "";
if (id.contains("#")) {
String[] ids = id.split("#");
id = ids[0];
code = ids[1];
} }
UcTool.parse(id, code).onSuccess(resUrl -> { return IPanTool.shareURLPrefixMatching(url, pwd).parse();
response.putHeader("location", resUrl).setStatusCode(302).end();
}).onFailure(t -> {
response.putHeader(CONTENT_TYPE, "text/html;charset=utf-8");
response.end(t.getMessage());
});
} }
@RouteMapping(value = "/json/uc/:id", method = RouteMethod.GET) @RouteMapping(value = "/json/:type/:key", method = RouteMethod.GET, order = 2)
public Future<String> ucParseJson(String id) { public Future<String> parseKeyJson(String type, String key) {
String code = ""; String code = "";
if (id.contains("#")) { if (key.contains("@")) {
String[] ids = id.split("#"); String[] keys = key.split("@");
id = ids[0]; key = keys[0];
code = ids[1]; code = keys[1];
} }
return UcTool.parse(id, code); return IPanTool.typeMatching(type, key, code).parse();
} }
@RouteMapping(value = "/:type/:key", method = RouteMethod.GET, order = 1)
public Future<Void> parseKey(HttpServerResponse response, String type, String key) {
Promise<Void> promise = Promise.promise();
String code = "";
if (key.contains("@")) {
String[] keys = key.split("@");
key = keys[0];
code = keys[1];
}
IPanTool.typeMatching(type, key, code).parse()
.onSuccess(resUrl -> ResponseUtil.redirect(response, resUrl, promise))
.onFailure(t -> promise.fail(t.fillInStackTrace()));
return promise.future();
}
} }

View File

@@ -1,4 +0,0 @@
package cn.qaiu.lz.web.model;
public class CowUser {
}

View File

@@ -1,4 +0,0 @@
package cn.qaiu.lz.web.model;
public class LzUser {
}

View File

@@ -0,0 +1,23 @@
package cn.qaiu.lz.web.model;
import cn.qaiu.lz.common.ToJson;
import io.vertx.codegen.annotations.DataObject;
import io.vertx.core.json.JsonObject;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@DataObject
public class StatisticsInfo implements ToJson {
Integer fail;
Integer success;
Integer total;
public StatisticsInfo(JsonObject jsonObject) {
this.fail = jsonObject.getInteger("fail");
this.success = jsonObject.getInteger("success");
this.total = jsonObject.getInteger("total");
}
}

View File

@@ -4,22 +4,20 @@ import cn.qaiu.db.ddl.Table;
import cn.qaiu.lz.common.ToJson; import cn.qaiu.lz.common.ToJson;
import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.DataObject;
import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonObject;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@Data @Data
@NoArgsConstructor
@AllArgsConstructor
@DataObject @DataObject
@NoArgsConstructor
@Table("t_user") @Table("t_user")
public class SysUser implements ToJson { public class SysUser implements ToJson {
private String id; private String id;
private String username; private String username;
private String password; private String password;
public SysUser(JsonObject json) { public SysUser(JsonObject json) {
this.id = json.getString("id");
this.username = json.getString("username"); this.username = json.getString("username");
this.password = json.getString("password"); this.password = json.getString("password");
} }

View File

@@ -1,6 +1,7 @@
package cn.qaiu.lz.web.service; package cn.qaiu.lz.web.service;
import cn.qaiu.lz.common.model.UserInfo; import cn.qaiu.lz.common.model.UserInfo;
import cn.qaiu.lz.web.model.StatisticsInfo;
import cn.qaiu.vx.core.base.BaseAsyncService; import cn.qaiu.vx.core.base.BaseAsyncService;
import io.vertx.codegen.annotations.ProxyGen; import io.vertx.codegen.annotations.ProxyGen;
import io.vertx.core.Future; import io.vertx.core.Future;
@@ -16,4 +17,6 @@ import io.vertx.core.json.JsonObject;
public interface DbService extends BaseAsyncService { public interface DbService extends BaseAsyncService {
Future<JsonObject> sayOk(String data); Future<JsonObject> sayOk(String data);
Future<JsonObject> sayOk2(String data, UserInfo holder); Future<JsonObject> sayOk2(String data, UserInfo holder);
Future<StatisticsInfo> getStatisticsInfo();
} }

View File

@@ -13,5 +13,5 @@ import io.vertx.core.Future;
*/ */
@ProxyGen @ProxyGen
public interface UserService extends BaseAsyncService { public interface UserService extends BaseAsyncService {
Future<String> login(SysUser user); Future<SysUser> login(SysUser user);
} }

View File

@@ -1,13 +1,20 @@
package cn.qaiu.lz.web.service.impl; package cn.qaiu.lz.web.service.impl;
import cn.qaiu.db.pool.JDBCPoolInit;
import cn.qaiu.lz.common.model.UserInfo; import cn.qaiu.lz.common.model.UserInfo;
import cn.qaiu.lz.web.service.DbService; import cn.qaiu.lz.web.service.DbService;
import cn.qaiu.lz.web.model.StatisticsInfo;
import cn.qaiu.vx.core.annotaions.Service; import cn.qaiu.vx.core.annotaions.Service;
import cn.qaiu.vx.core.model.JsonResult; import cn.qaiu.vx.core.model.JsonResult;
import io.vertx.core.Future; import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonObject;
import io.vertx.jdbcclient.JDBCPool;
import io.vertx.sqlclient.templates.SqlTemplate;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
/** /**
* lz-web * lz-web
* <br>Create date 2021/7/12 17:26 * <br>Create date 2021/7/12 17:26
@@ -35,4 +42,25 @@ public class DbServiceImpl implements DbService {
// log.info("--> {}", holder.toString()); // log.info("--> {}", holder.toString());
return Future.succeededFuture(JsonObject.mapFrom(JsonResult.data("Hi: " + data))); return Future.succeededFuture(JsonObject.mapFrom(JsonResult.data("Hi: " + data)));
} }
@Override
public Future<StatisticsInfo> getStatisticsInfo() {
JDBCPool client = JDBCPoolInit.instance().getPool();
Promise<StatisticsInfo> promise = Promise.promise();
String sql = """
select COUNT(CASE "code" WHEN 500 THEN "code" END ) "fail",
COUNT(CASE "code" WHEN 200 THEN "code" END ) "success",
count(1) "total"
from "t_parser_log_info"
""";
SqlTemplate.forQuery(client, sql).mapTo(StatisticsInfo.class).execute(new HashMap<>()).onSuccess(row -> {
StatisticsInfo info;
if ((info = row.iterator().next()) != null) {
promise.complete(info);
} else {
promise.fail("t_parser_log_info查询为空");
}
}).onFailure(promise::fail);
return promise.future();
}
} }

View File

@@ -5,6 +5,8 @@ import cn.qaiu.lz.web.service.UserService;
import cn.qaiu.vx.core.annotaions.Service; import cn.qaiu.vx.core.annotaions.Service;
import io.vertx.core.Future; import io.vertx.core.Future;
import java.util.concurrent.TimeUnit;
/** /**
* lz-web * lz-web
* <br>Create date 2021/8/27 14:09 * <br>Create date 2021/8/27 14:09
@@ -15,8 +17,13 @@ import io.vertx.core.Future;
public class UserServiceImpl implements UserService { public class UserServiceImpl implements UserService {
@Override @Override
public Future<String> login(SysUser user) { public Future<SysUser> login(SysUser user) {
return Future.succeededFuture("111"); try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return Future.succeededFuture(user);
} }
} }

View File

@@ -5,37 +5,37 @@ server:
# 使用静态页面 # 使用静态页面
enableStaticHtmlService: false enableStaticHtmlService: false
# 使用数据库 # 使用数据库
enableDatabase: false enableDatabase: true
staticResourcePath: webroot/ staticResourcePath: webroot/
# 反向代理服务器配置路径(不用加后缀) # 反向代理服务器配置路径(不用加后缀)
proxyConf: server-proxy proxyConf: server-proxy
# vertx线程配置 事件循环线程配置为0: eventLoopPoolSize将会采用默认配置(CPU核心*2) workerPoolSize将会采用默认20
vertx: vertx:
eventLoopPoolSize: 8 eventLoopPoolSize: 0
workerPoolSize: 20 workerPoolSize: 0
custom: custom:
asyncServiceInstances: 8 # 异步服务线程数
routerLocations: cn.qaiu.lz.web.http asyncServiceInstances: 4
interceptorClassPath: cn.qaiu.lz.common.interceptorImpl.DefaultInterceptor # server路由(controller层)所在包路径
handlerLocations: cn.qaiu.lz.web.service baseLocations: cn.qaiu.lz
# 路由处理默认超时时间(毫秒)
routeTimeOut: 15000
# 拦截器匹配规则
ignoresReg: ignoresReg:
- .*/login$ - /v2/statisticsInfo
- .*/test.*$ - .*/test.*$
# 参数注入的实体类包路径匹配正则 (防止同名类引发歧义)
entityPackagesReg: entityPackagesReg:
- ^cn\.qaiu\.lz\.web\.model\..* - ^cn\.qaiu\.lz\.web\.model\..*
errorPage404: /index.html
indexPage: /test2
sharedLogin: true
lzConfig:
config: '111'
# 数据源配置
dataSource: dataSource:
provider_class: io.vertx.ext.jdbc.spi.impl.HikariCPDataSourceProvider provider_class: io.vertx.ext.jdbc.spi.impl.HikariCPDataSourceProvider
jdbcUrl: jdbc:h2:tcp://127.0.0.1:9095/./db/myData;MODE=MySQL;DATABASE_TO_LOWER=FALSE jdbcUrl: jdbc:h2:tcp://127.0.0.1:9095/./db/myData;MODE=MySQL;DATABASE_TO_UPPER=FALSE
driverClassName: org.h2.Driver driverClassName: org.h2.Driver
username: root username: root
password: '123456' password: '123456'
tableClassPath: cn.qaiu.lz.web.model
cowConfig:
config: '111'

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