Compare commits

..

202 Commits

Author SHA1 Message Date
qaiu
603afed2f2 . 2024-09-19 05:34:34 +00:00
qaiu
c2a7c34496 . 2024-09-19 05:31:24 +00:00
qaiu
edd40f48ba 依赖更新 2024-09-19 05:26:31 +00:00
qaiu
cca3d6b8b9 web-front add yarn.lock 2024-09-19 05:22:55 +00:00
qaiu
f004512903 Update devcontainer.json 2024-09-19 09:37:01 +08:00
qaiu
6407bb6730 Merge pull request #47 from qaiu/dependabot/npm_and_yarn/web-front/webpack-5.94.0
Bump webpack from 5.88.2 to 5.94.0 in /web-front
2024-09-19 09:01:53 +08:00
qaiu
b914eeadec Merge pull request #51 from qaiu/dependabot/npm_and_yarn/web-front/multi-d66d039ac5
Bump serve-static and express in /web-front
2024-09-19 09:01:04 +08:00
qaiu
dcadc6783e Merge pull request #52 from qaiu/dependabot/npm_and_yarn/web-front/express-4.21.0
Bump express from 4.19.2 to 4.21.0 in /web-front
2024-09-19 09:00:27 +08:00
dependabot[bot]
bc9f43634f Bump express from 4.19.2 to 4.21.0 in /web-front
Bumps [express](https://github.com/expressjs/express) from 4.19.2 to 4.21.0.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-19 00:59:00 +00:00
dependabot[bot]
4778f0164c Bump serve-static and express in /web-front
Bumps [serve-static](https://github.com/expressjs/serve-static) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `serve-static` from 1.15.0 to 1.16.2
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/v1.16.2/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...v1.16.2)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

---
updated-dependencies:
- dependency-name: serve-static
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-19 00:58:59 +00:00
qaiu
9904754a07 Merge pull request #48 from qaiu/dependabot/npm_and_yarn/web-front/axios-1.7.4
Bump axios from 1.6.0 to 1.7.4 in /web-front
2024-09-19 08:57:43 +08:00
qaiu
1b79077c9e 更新 PanDomainTemplate.java 2024-09-19 06:20:12 +08:00
qaiu
c13afb05b3 示例下载链接修改 2024-09-19 05:43:54 +08:00
qaiu
03e320efb8 Create devcontainer.json 2024-09-18 17:40:52 +08:00
qaiu
7846332476 Update README.md 2024-09-18 16:58:27 +08:00
qaiu
2d5d3b86e0 Update README.md 2024-09-18 16:58:02 +08:00
qaiu
7c9ba890af 1. .. 2024-09-18 15:45:49 +08:00
qaiu
0d609daffa 1. 123后缀处理 2. 修复缓存时间戳格式问题 2024-09-18 15:41:56 +08:00
qaiu
c12e56d402 Update README.md 2024-09-18 13:34:53 +08:00
qaiu
c7b38c07d5 Update README.md 2024-09-18 13:34:26 +08:00
qaiu
dc51066cea 1. 缓存优化 2024-09-18 13:28:20 +08:00
qaiu
59d2fb3010 1. 缓存优化 2024-09-18 13:24:33 +08:00
qaiu
a0fe702c10 1. remove error update 2024-09-15 06:54:55 +08:00
qaiu
f886f7e366 1. 添加缓存
2. 优化解析架构
3. 优化核心模块
2024-09-15 06:53:11 +08:00
dependabot[bot]
1d475d88ed Bump axios from 1.6.0 to 1.7.4 in /web-front
Bumps [axios](https://github.com/axios/axios) from 1.6.0 to 1.7.4.
- [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/v1.6.0...v1.7.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-30 18:09:12 +00:00
dependabot[bot]
e64c901912 Bump webpack from 5.88.2 to 5.94.0 in /web-front
Bumps [webpack](https://github.com/webpack/webpack) from 5.88.2 to 5.94.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.88.2...v5.94.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-30 18:07:43 +00:00
qaiu
5fce02e623 Update README.md 2024-07-13 12:50:02 +08:00
qaiu
13997bc543 Update README.md 2024-06-19 10:04:04 +08:00
qaiu
3e05b0d6f9 Update README.md 2024-06-19 10:03:53 +08:00
qaiu
966417f867 Merge pull request #45 from qaiu/dependabot/npm_and_yarn/web-front/multi-d7cccafd4e
Bump braces and filemanager-webpack-plugin in /web-front
2024-06-14 12:14:05 +08:00
dependabot[bot]
601a0d1b91 Bump braces and filemanager-webpack-plugin in /web-front
Bumps [braces](https://github.com/micromatch/braces) to 3.0.3 and updates ancestor dependency [filemanager-webpack-plugin](https://github.com/gregnb/filemanager-webpack-plugin). These dependencies need to be updated together.


Updates `braces` from 3.0.2 to 3.0.3
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

Updates `filemanager-webpack-plugin` from 2.0.5 to 8.0.0
- [Release notes](https://github.com/gregnb/filemanager-webpack-plugin/releases)
- [Changelog](https://github.com/gregnb/filemanager-webpack-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gregnb/filemanager-webpack-plugin/compare/v2.0.5...v8.0.0)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
- dependency-name: filemanager-webpack-plugin
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 06:54:54 +00:00
QAIU
ae91ce773e maven-dependency-submission-action add ignore-maven-wrapper: true 2024-06-07 11:17:37 +08:00
QAIU
d428380f79 add maven wrapper build script 2024-06-07 09:33:57 +08:00
QAIU
b0b8b61688 支持mysql 2024-06-06 18:10:15 +08:00
QAIU
7663320a55 1. h2数据库文件优化, 取消h2server启动
2. 项目结构优化, pom版本统一管理
3. core的beanutils依赖升级为commons-beanutils2版本, 修复之前版本的安全风险.
4. 此版本打包部署需要替换之前所有依赖
2024-06-06 18:06:33 +08:00
qaiu
aaae7ab9a6 1. 小飞机规则修改, 稍后更新发行版. #43
2. 缓存策略将在近期添加. 这阵子忙的一地鸡毛. 不想活了
2024-05-30 21:52:13 +08:00
qaiu
db7aa2f442 代码优化 2024-05-15 08:35:28 +08:00
qaiu
d7349cbf05 小飞机 API调试 2024-05-13 14:10:12 +08:00
qaiu
3a5474c9d8 - 小飞机和蓝奏优享规则修改 #40
- 支持移动云云空间加密分享
2024-05-13 04:59:42 +08:00
qaiu
4e6ed0f936 更新 README.md 2024-04-17 00:38:30 +08:00
qaiu
11ae57856e Update README.md 2024-04-15 10:11:46 +08:00
qaiu
66e67f3e1d Merge pull request #37 from qaiu/dependabot/npm_and_yarn/web-front/express-4.19.2
Bump express from 4.18.2 to 4.19.2 in /web-front
2024-04-09 12:50:11 +08:00
qaiu
8913221b97 Update README.md 2024-04-09 12:49:33 +08:00
qaiu
4f4133b98c - README.md下载地址修改 2024-04-09 12:46:32 +08:00
qaiu
5768ba48e8 - 升级vertx到4.5.6
- 添加Cloudreve
- 蓝奏云优享和小飞机规则修改
2024-04-09 12:13:26 +08:00
dependabot[bot]
9780d09e28 Bump express from 4.18.2 to 4.19.2 in /web-front
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-29 06:23:01 +00:00
qaiu
8358d8d1f8 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
d2a7bf4417 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]
24c5495e75 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]
4158200ba5 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
2a75f9d34d 更新 README.md 2024-01-29 00:35:23 +08:00
qaiu
441e69e5f8 更新 README.md 2024-01-29 00:34:43 +08:00
qaiu
0a3d91ee52 更新 README.md 2024-01-29 00:23:34 +08:00
qaiu
00b3af1c4c 更新 README.md 2024-01-29 00:22:39 +08:00
qaiu
6d4e0fb56e .. 2024-01-28 15:29:41 +08:00
qaiu
0a296cc937 添加蓝奏云优享解析支持 2024-01-28 15:26:22 +08:00
qaiu
257063bae3 蓝奏云域名修改, shell分隔符调整 2024-01-27 10:53:52 +08:00
qaiu
e21325a5d0 Update README.md 2023-12-27 14:27:31 +08:00
qaiu
32284c6da0 Merge pull request #28 from qaiu/dev-reference
Dev reference 添加文叔叔解析
2023-12-16 14:35:44 +08:00
qaiu
56c68bcce6 加入Jansi 让windows cmd日志彩色输出 2023-12-15 10:14:00 +08:00
BLSKWOLF
2ef7efdc42 更新 README.md 2023-12-11 23:56:10 +08:00
BLSKWOLF
7f00413756 添加文叔叔解析
1.修复QQ邮箱解析
2.添加文叔叔解析
2023-12-11 23:51:49 +08:00
BLSKWOLF
d26696d3fa 更新 README.md 2023-12-11 17:46:42 +08:00
BLSKWOLF
595f301748 [未完成]添加QQ邮箱文件解析
添加QQ邮箱文件解析,暂时未解决cookie的问题
2023-12-11 17:36:39 +08:00
qaiu
c33b0aa12c Update README.md 2023-12-05 17:32:17 +08:00
qaiu
b21189b7d7 Update README.md 2023-12-05 12:49:07 +08:00
qaiu
618f43da8e Update README.md 2023-12-05 11:43:04 +08:00
qaiu
d28b54f694 Update README.md 2023-12-05 11:39:26 +08:00
qaiu
093d651f2f 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]
ccc05c13b2 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
1dfcbcc132 Update README.md 2023-12-05 09:46:22 +08:00
qaiu
e9770c8eb3 1. 日志配置微调 2. 乐云验证 2023-11-29 14:44:42 +08:00
qaiu
cb14be12e1 vertx升级4.5.0 2023-11-28 17:00:11 +08:00
qaiu
b0ca6aa4dd Merge remote-tracking branch 'origin/main' 2023-11-28 16:53:48 +08:00
qaiu
c958229960 1. 添加联想乐云直链解析 2. 优化代码逻辑 2023-11-28 16:51:51 +08:00
qaiu
8aa10f6f27 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]
db073beeb5 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
3d46bcb5c8 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
66e62aead0 小飞机网盘分享新URL格式适配 2023-11-02 10:43:10 +08:00
dependabot[bot]
497a96a1df 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
c6e0bed331 更新 README.md 2023-09-23 11:21:47 +08:00
qaiu
d678a5f4f0 Update README.md 2023-09-21 17:32:09 +08:00
qaiu
8fcf3d225d Update README.md 更新jdk下载网盘为移动云空间 2023-09-21 17:30:55 +08:00
qaiu
4858461946 Update README.md 2023-09-17 09:54:39 +08:00
qaiu
7afe52918b 蓝奏云js引入问题修复 2023-09-17 09:48:23 +08:00
QAIU
1a0064fc43 Merge remote-tracking branch 'origin/main' 2023-09-08 17:42:09 +08:00
QAIU
7216c84282 update test 2023-09-08 17:41:30 +08:00
qaiu
94a58e2174 Update README.md 2023-09-07 08:52:43 +08:00
qaiu
aa2fae868c Update README.md 错别字 2023-09-06 03:08:17 +08:00
qaiu
1b80f49079 Update README.md 2023-09-06 03:06:55 +08:00
qaiu
135632309d Update README.md 2023-09-06 03:05:47 +08:00
QAIU
4c366b7d84 解析用到的js文件改为String方式引入 2023-09-01 18:07:21 +08:00
qaiu
e3efdc83e6 Update README.md 2023-08-28 22:37:26 +08:00
QAIU
595575381b 去除web-service无用依赖 2023-08-28 17:58:00 +08:00
QAIU
5cd1ef8bbf 预览首页统计修复,123盘大文件长度取值错误问题 2023-08-28 17:54:39 +08:00
QAIU
1aeb838fd9 aaa 2023-08-25 18:30:27 +08:00
QAIU
ce414e80b0 Merge remote-tracking branch 'origin/main' 2023-08-25 18:29:00 +08:00
QAIU
c47c9bfc68 版本更新至0.1.7,启用h2db,添加统计功能,框架优化 2023-08-25 18:28:45 +08:00
qaiu
7b2c493cf7 错别字 2023-08-25 17:04:37 +08:00
QAIU
b38291b6b2 Merge remote-tracking branch 'origin/main' 2023-08-25 16:56:00 +08:00
QAIU
66de667fc3 版本更新至0.1.7,启用h2db,添加统计功能,框架优化 2023-08-25 16:55:38 +08:00
qaiu
a1e9be0133 Update README.md 2023-08-25 10:04:44 +08:00
qaiu
bf70a7c83d Update README.md 2023-08-25 10:04:27 +08:00
QAIU
6706380558 蓝奏云规则微调, 框架优化(70%) 2023-08-23 14:55:08 +08:00
qaiu
b0d150319d Update LzTool.java 2023-08-21 12:27:39 +08:00
qaiu
03185d0592 Update README.md 2023-08-20 10:36:39 +08:00
qaiu
46f092f2ed Merge pull request #13 from qaiu/dev
蓝奏云规则修改, 奶牛功能增强
2023-08-20 10:35:02 +08:00
qaiu
aea97caed1 修复蓝奏云pan解析错误的问题#12 2023-08-20 10:29:00 +08:00
QAIU
6d3335d086 Merge remote-tracking branch 'origin/dev' into dev 2023-08-19 17:29:04 +08:00
QAIU
945e5a74e6 奶牛快传支持下载zip目录分享#11, core框架优化 2023-08-19 17:28:30 +08:00
qaiu
1f38201555 Update README.md 2023-08-13 00:58:12 +08:00
qaiu
cfc5afc029 Update README.md 2023-08-13 00:55:20 +08:00
qaiu
0526569d64 Update README.md 2023-08-13 00:54:21 +08:00
QAIU
1c94f4ef8b 奶牛快传支持下载zip目录分享#11, core框架优化 2023-08-11 15:28:37 +08:00
QAIU
26aabf19db core框架优化 2023-08-10 14:54:45 +08:00
qaiu
2e14d8d345 修复123pan解析错误的问题#9 2023-08-10 01:49:41 +08:00
qaiu
09140d56a3 代码结构优化;修复123pan解析错误的问题#9 2023-08-10 01:45:39 +08:00
qaiu
1eec14831c 代码结构优化;修复123pan解析错误的问题#9 2023-08-10 01:42:34 +08:00
qaiu
aafbf05d34 代码结构优化
修复123pan解析错误的问题#9
2023-08-10 01:38:20 +08:00
qaiu
8ffd35f2f0 Merge pull request #10 from qaiu/master
Master
2023-08-10 01:17:11 +08:00
qaiu
6808381292 fixed 123pan /b/api/share/download/info->/a/api/share/download/info 2023-08-10 00:59:09 +08:00
QAIU
72cc68e9ad 代码结构优化, 异常处理优化 2023-08-08 17:36:36 +08:00
QAIU
7917870c6b 代码结构优化, 异常处理优化 2023-08-08 11:34:32 +08:00
QAIU
facf9b645e 0 2023-08-04 17:49:30 +08:00
qaiu
46b4c524f0 Update README.md 2023-08-02 17:53:04 +08:00
QAIU
804c6d96ec 0 2023-08-01 14:42:32 +08:00
qaiu
91c3b0c612 前端页面优化 2023-07-31 22:43:23 +08:00
qaiu
400952e33b 前端页面优化 2023-07-31 22:41:33 +08:00
QAIU
c39f748a51 0 2023-07-31 14:07:12 +08:00
QAIU
2464693288 Merge remote-tracking branch 'origin/main' 2023-07-31 13:56:49 +08:00
QAIU
cfce8c1aef 360亿方云API-URL变更 2023-07-31 13:56:35 +08:00
qaiu
258a7cb1cc Update README.md 2023-07-31 12:06:59 +08:00
qaiu
049cfa3676 Update README.md 2023-07-31 12:03:34 +08:00
qaiu
7591aa4336 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
2aef15c670 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]
23f29be4b3 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
daea6bea68 lz.qaiu.top加入前端页面 2023-07-31 11:38:23 +08:00
qaiu
a71028c665 前端优化 2023-07-31 05:08:13 +08:00
dependabot[bot]
afb997f516 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
940d73a132 Merge remote-tracking branch 'origin/main' 2023-07-31 03:22:02 +08:00
qaiu
214eaed56a 添加前端页面 2023-07-31 03:21:39 +08:00
qaiu
3dab818dcb Update README.md 2023-07-31 00:20:52 +08:00
qaiu
61db1af83d Update README.md 2023-07-30 16:29:27 +08:00
qaiu
7d1cb5b5c1 添加 通过JS文件获取123pan签名 2023-07-29 18:08:30 +08:00
QAIU
878f61d349 123网盘解析规则优化 2023-07-27 17:45:53 +08:00
QAIU
9a1c9d8ba2 123网盘解析规则优化 2023-07-24 10:47:01 +08:00
QAIU
ffd4846924 Merge remote-tracking branch 'origin/main' 2023-07-24 10:41:43 +08:00
QAIU
3e1f29c12e 123网盘解析规则优化 2023-07-24 10:41:25 +08:00
qaiu
ec9a6dfe6c Update README.md 2023-07-23 15:15:25 +08:00
qaiu
a72ebbb83c Update README.md 2023-07-23 15:13:23 +08:00
qaiu
a8a172c027 Update README.md 2023-07-23 15:12:58 +08:00
qaiu
bcd71a844c Update README.md
lz.qaiu.top测试123pan 下载Dragonwell JDK17
2023-07-22 15:33:10 +08:00
qaiu
3608eb0370 Update README.md
lz.qaiu.top测试123pan 下载Dragonwell JDK17
2023-07-22 15:32:13 +08:00
QAIU
dbef574cbc 修改123网盘解析规则 2023-07-22 12:34:02 +08:00
QAIU
e2273587a0 细节优化 2023-07-21 10:29:21 +08:00
qaiu
9f50299943 Update README.md 2023-07-16 04:26:05 +08:00
qaiu
a51097e1ba 修改win服务模板 2023-07-16 03:45:54 +08:00
qaiu
5628d57299 - 更新版本号: 0.1.5->0.1.6 2023-07-16 03:11:41 +08:00
qaiu
774a069c0e - WebServer: PanTool优化异常处理
- Core: Vertx事件循环线程数调整
2023-07-16 02:47:39 +08:00
qaiu
5756fde338 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
5d6e22405a 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]
28497e900a 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]
00723cc2e2 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
91eb62d308 解决一些 warning jdk14对可序列化对象添加@Serial 2023-06-21 16:49:01 +08:00
QAIU
c73e614ce7 Merge remote-tracking branch 'origin/main' 2023-06-21 15:40:42 +08:00
QAIU
2586092760 musetransfer api test 2023-06-21 15:40:24 +08:00
qaiu
0d5b771db8 Zzz 2023-06-21 02:53:58 +00:00
qaiu
67edd5b5d8 Cow解析重复创建实例VertxHolder.getVertxInstance() 2023-06-21 02:53:24 +00:00
qaiu
40eea9b5f4 cow parse fail return 2023-06-21 02:50:19 +00:00
QAIU
29a993e1fc add mu pan 2023-06-14 17:47:20 +08:00
QAIU
2c9f0d8e83 Zzz 2023-06-14 16:14:37 +08:00
qaiu
656a43e4b7 Update README.md 2023-06-14 15:48:21 +08:00
qaiu
f0e28e1c37 Update README.md 2023-06-14 15:48:08 +08:00
qaiu
7e5400d43c Update README.md 2023-06-14 15:40:08 +08:00
qaiu
c7c932daf4 Create LICENSE 2023-06-14 15:23:59 +08:00
QAIU
8ca0eca2ea Zzz 2023-06-14 11:57:58 +08:00
QAIU
bb925eba35 Zzz 2023-06-14 11:56:33 +08:00
QAIU
a105a7d864 Zzz 2023-06-14 11:53:27 +08:00
QAIU
60e4ac6094 依赖打包优化 2023-06-14 11:26:33 +08:00
qaiu
8d80ff845a 修复Linux运行脚本 2023-06-14 10:30:16 +08:00
QAIU
49c2bbe680 Linux部署服务模板修改 2023-06-13 15:11:32 +08:00
qaiu
3017e8d17c Update README.md 2023-06-13 13:58:26 +08:00
QAIU
f3ae2b79b8 update 0.1.5 2023-06-13 13:25:22 +08:00
QAIU
c629525029 update 0.1.5 2023-06-13 13:23:09 +08:00
QAIU
5d9b18ef41 移动云空间需要注意的地方 2023-06-13 10:53:02 +08:00
qaiu
bdee797f7e Merge pull request #3 from qaiu/dev
Controller 层不支持方法重载: ServerApi
2023-06-13 10:30:36 +08:00
qaiu
a052bcd841 Controller 层不支持方法重载: ServerApi 2023-06-13 10:29:23 +08:00
qaiu
616cc9ce6a 接口重构 2023-06-13 08:22:11 +08:00
qaiu
f124c83286 接口重构 2023-06-13 05:43:37 +08:00
QAIU
d11fcfb282 测试相关 2023-06-12 16:41:06 +08:00
QAIU
5ec1ec7789 Zzzz 2023-06-12 16:07:28 +08:00
QAIU
8b6075af37 重构蓝奏云解析代码, 加入蓝奏云加密分享解析 2023-06-12 10:39:33 +08:00
QAIU
5d23c57d0e 蓝奏云密码解析测试 2023-06-10 17:45:25 +08:00
QAIU
409163ddf5 蓝奏云密码解析测试 2023-06-10 17:32:25 +08:00
QAIU
994202587d change README.md 2023-06-10 16:41:25 +08:00
QAIU
c01d41f9ca change README.md 2023-06-10 16:35:33 +08:00
QAIU
c42667fe3b 添加123云盘直链解析 2023-06-10 16:31:17 +08:00
QAIU
c782d495ea 修改密码分隔符$->@ 2023-06-10 13:34:01 +08:00
QAIU
3f4f9fa76b - 加入360亿方云直链解析
- 优化代码
2023-06-10 13:29:22 +08:00
QAIU
635c639bf5 - 加入小飞机盘直链解析
- 优化代码
2023-06-09 15:43:21 +08:00
QAIU
a49b9254a4 Merge remote-tracking branch 'origin/main' 2023-06-08 17:34:02 +08:00
QAIU
0c91eea711 其他网盘API 2023-06-08 17:33:42 +08:00
122 changed files with 11533 additions and 3439 deletions

View File

@@ -0,0 +1,28 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/java
{
"name": "Java",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/java:0-17",
"features": {
"ghcr.io/devcontainers/features/java:1": {
"version": "none",
"installMaven": "true",
"installGradle": "false"
},
"ghcr.io/devcontainers-contrib/features/ant-sdkman:2": {}
}
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "java -version",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

@@ -37,6 +37,8 @@ jobs:
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
uses: advanced-security/maven-dependency-submission-action@v3
with:
ignore-maven-wrapper: true
# - uses: release-drafter/release-drafter@v5
# env:

1
.gitignore vendored
View File

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

117
.mvn/wrapper/MavenWrapperDownloader.java vendored Normal file
View File

@@ -0,0 +1,117 @@
/*
* Copyright 2007-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

BIN
.mvn/wrapper/maven-wrapper.jar vendored Normal file

Binary file not shown.

2
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

70
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,70 @@
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Current File",
"request": "launch",
"mainClass": "${file}"
},
{
"type": "java",
"name": "StringCase",
"request": "launch",
"mainClass": "cn.qaiu.vx.core.util.StringCase",
"projectName": "core"
},
{
"type": "java",
"name": "FCURLParser",
"request": "launch",
"mainClass": "cn.qaiu.parser.FCURLParser",
"projectName": "parser"
},
{
"type": "java",
"name": "QkTool",
"request": "launch",
"mainClass": "cn.qaiu.parser.impl.QkTool",
"projectName": "parser"
},
{
"type": "java",
"name": "WebClientExample",
"request": "launch",
"mainClass": "qaiu.web.test.WebClientExample",
"projectName": "parser"
},
{
"type": "java",
"name": "AppMain",
"request": "launch",
"mainClass": "cn.qaiu.lz.AppMain",
"projectName": "web-service"
},
{
"type": "java",
"name": "TestJs",
"request": "launch",
"mainClass": "cn.qaiu.web.test.TestJs",
"projectName": "web-service"
},
{
"type": "java",
"name": "TestOS",
"request": "launch",
"mainClass": "cn.qaiu.web.test.TestOS",
"projectName": "web-service"
},
{
"type": "java",
"name": "WebProxyExamples",
"request": "launch",
"mainClass": "cn.qaiu.web.test.WebProxyExamples",
"projectName": "web-service"
}
]
}

View File

@@ -1,3 +1,4 @@
{
"java.compile.nullAnalysis.mode": "automatic"
"java.compile.nullAnalysis.mode": "automatic",
"java.configuration.updateBuildConfiguration": "interactive"
}

173
README.md
View File

@@ -1,78 +1,65 @@
# netdisk-fast-download
云盘解析服务 (nfd云解析)
预览地址 https://lz.qaiu.top
main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/netdisk-fast-download/tree/main-jdk11)
**注意: 请不要过度依赖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)
[![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.4.1-blue)](https://vertx-china.github.io/)
[![vert.x](https://img.shields.io/badge/vert.x-4.5.6-blue)](https://vertx-china.github.io/)
[![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)
## 项目介绍
网盘直链解析工具能把网盘分享下载链接转化为直链,已支持蓝奏云/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘等支持密分享。
网盘直链解析工具能把网盘分享下载链接转化为直链,已支持蓝奏云/蓝奏云优享/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘/Cloudreve等,支持密分享。
## 应用场景:
1. 游戏:使用本站工具可自动生成高速直流下载器,用于游戏客户端自动化高速下载安装、更新等。
2. 下载:可根据网盘分享的地址解析为直链,方便放到自己网站上供用户下载,节约服务器带宽和磁盘。
3. 音视频:可解析为音视频直链,可直接在网页上播放,高清,高速,节约服务器带宽和磁盘。
*重要声明:本项目仅供学习参考;请不要将此项目用于任何商业用途,否则可能带来严重的后果。转发/分享该项目请注明来源*
## 网盘支持情况:
> 20230905 奶牛云直链做了防盗链需加入请求头Referer: https://cowtransfer.com/
> 20230824 123云盘解析大文件(>100MB)失效,需要登录
> 20230722 UC网盘解析失效需要登录
`网盘名称(网盘标识):`
- [蓝奏云 (lz)](https://pc.woozooo.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [蓝奏云优享 (iz)](https://www.ilanzou.com/)
- [奶牛快传 (cow)](https://cowtransfer.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [移动云空间 (ec)](https://www.ecpan.cn/web)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [UC网盘 (uc)似乎已经失效,需要登录](https://fast.uc.cn/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [移动云云空间 (ec)](https://www.ecpan.cn/web)
- [小飞机网盘 (fj)](https://www.feijipan.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [亿方云 (fc)](https://www.fangcloud.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [123云盘 (ye)](https://www.123pan.com/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [文叔叔 (ws) 开发中](https://www.wenshushu.cn/)
- [夸克网盘 (qk) 开发中](https://pan.quark.cn/)
- [文叔叔 (ws)](https://www.wenshushu.cn/)
- [联想乐云 (le)](https://lecloud.lenovo.com/)
- [QQ邮箱文件中转站 (qq)](https://mail.qq.com/)
- [超星网盘-开发中 (cx)](https://passport2.chaoxing.com/login?newversion=true&refer=https%3A%2F%2Fpan-yz.chaoxing.com%2F)
- [Cloudreve自建网盘--正在优化 (ce)](https://github.com/cloudreve/Cloudreve)
**TODO:**
- 登录接口, 文件上传/下载/分享后端接口
- 短地址服务
- 前端界面(建设中...)
**技术栈:**
Jdk17+Vert.x4.4.1
Core模块集成Vert.x实现类似spring的注解式路由API
API接口
### API接口说明
your_host指的是您的域名或者IP实际使用时替换为实际域名或者IP端口默认6400可以使用nginx代理来做域名访问。
解析方式分为两种类型直接跳转下载文件和获取下载链接,每一种都提供了两种接口形式parser和网盘标志/分享key拼接的短地址标志短链所有规则参考示例。
- 通用接口: `/parser?url=分享链接`加密分享需要加上参数pwd=密码;
- 标志短链: `/网盘标识/分享key` 在分享Key后面加上@密码;
- 直链JSON: `通用接口``标志短链`前加上`/json` 加密分享的密码规则同上;
- 网盘标识参考上面网盘支持情况
- 当带有分享密码时需要加上密码参数(pwd)
- 移动云云空间,小飞机网盘的加密分享的密码可以忽略
- 移动云空间分享key取分享链接中的data参数,比如`&data=xxx`的参数就是xxx
API规则:
```
网盘标识参考上面网盘支持情况, 括号内是可选内容: 表示当带有分享密码时需要加上密码参数
parser接口可以直接解析分享链接: 加密分享需要加上参数pwd=密码;
其他接口在分享Key后面加上@密码;
1. 解析并自动302跳转 :
http(s)://your_host/parser?url=分享链接(&pwd=xxx)
http(s)://your_host/网盘标识/分享key(@分享密码)
http://your_host/parser?url=分享链接&pwd=xxx
http://your_host/网盘标识/分享key@分享密码
2. 获取解析后的直链--JSON格式
http(s)://your_host/json/parser?url=分享链接(&pwd=xxx)
http(s)://your_host/json/网盘标识/分享key(@分享密码)
3. 特别注意的地方:
- 有些网盘的加密分享的密码可以忽略: 如移动云空间,小飞机网盘
- 移动云空间(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
http://your_host/json/parser?url=分享链接&pwd=xxx
http://your_host/json/网盘标识/分享key@分享密码
```
json返回数据格式示例:
```json
@@ -81,8 +68,14 @@ json返回数据格式示例:
"msg": "success",
"success": true,
"count": 0,
"data": "https://下载链接",
"timestamp": 1690733953927
"data": {
"shareKey": "lz:xxx",
"directLink": "下载直链",
"cacheHit": true,
"expires": "2024-09-18 01:48:02",
"expiration": 1726638482825
},
"timestamp": 1726637151902
}
```
@@ -125,25 +118,25 @@ GET http://127.0.0.1:6400/json/fc/e5079007dc31226096628870c7@QAIU
# 网盘对比
| 网盘名称 | 可直接下载分享 | 加密分享 | 初始网盘空间 | 单文件大小限制 | 登录接口 |
|------------|------------------------|----------|-----------|---------|------|
| 蓝奏云 | √ | √ | 不限空间 | 100M | TODO |
| 奶牛快传 | √ | X | 10G | 不限大小 | TODO |
| 移动云空间 | √ | √(密码可忽略) | 5G(个人) | 不限大小 | TODO |
| UC网盘 | 需要登录 | √ | 10G | 不限大小 | TODO |
| 小飞机网盘 | √ | √(密码可忽略) | 10G | 不限大小 | TODO |
| 360亿方云 | √(试用账号有时间限制企业版需要599续费) | √(密码可忽略) | 100G(须实名) | 不限大小 | TODO |
| 123云盘 | √ | √ | 2T | 100G | TODO |
| 文叔叔(TODO) | √(注意有时间限制) | √ | 10G | 5GB | TODO |
| 夸克网盘(TODO) | 需要登录 | √ | 10G | 不限大小 | TODO |
| 网盘名称 | 免登陆下载分享 | 加密分享 | 初始网盘空间 | 单文件大小限制 |
|-------------|---------|----------|-----------|-----------------|
| 蓝奏云 | √ | √ | 不限空间 | 100M |
| 奶牛快传 | √ | X | 10G | 不限大小 |
| 移动云空间(个人版) | √ | √(密码可忽略) | 5G(个人) | 不限大小 |
| 小飞机网盘 | √ | √(密码可忽略) | 10G | 不限大小 |
| 360亿方云 | √ | √(密码可忽略) | 100G(须实名) | 不限大小 |
| 123云盘 | √ | √ | 2T | 100G>100M需要登录 |
| 文叔叔 | √ | √ | 10G | 5GB |
| 夸克网盘 | x | √ | 10G | 不限大小 |
| UC网盘 | x | √ | 10G | 不限大小 |
# 打包部署
## JDK下载lz.qaiu.top提供直链云解析服务
- [阿里jdk17(Dragonwell17-windows-x86)](https://lz.qaiu.top/ye/iaKtVv-hbECd)
- [阿里jdk17(Dragonwell17-linux-x86)](https://lz.qaiu.top/ye/iaKtVv-AbECd)
- [阿里jdk17(Dragonwell17-linux-aarch64)](https://lz.qaiu.top/ye/iaKtVv-HbECd)
- [123云盘解析有效性测试-阿里jdk17(Dragonwell17-linux-aarch64)](https://lz.qaiu.top/json/ye/iaKtVv-HbECd)
- [阿里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)
## 开发和打包
@@ -153,42 +146,66 @@ mvn clean
mvn package
```
打包好的文件位于 web-service/target/netdisk-fast-download-x.x.x-bin.zip
打包好的文件位于 web-service/target/netdisk-fast-download-bin.zip
## Linux服务部署
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
```shell
cd ~
wget -O netdisk-fast-download-0.1.6-bin.zip https://github.com/qaiu/netdisk-fast-download/releases/download/0.1.6-releases/netdisk-fast-download-0.1.6-bin.zip
unzip netdisk-fast-download-*-bin.zip
cd netdisk-fast-download-*-bin
wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/0.1.8-release-fixed2/netdisk-fast-download-bin-fixed2.zip
unzip netdisk-fast-download-bin.zip
cd netdisk-fast-download
bash service-install.sh
```
服务相关命令:
1、查看服务状态
systemctl status netdisk-fast-download.service
2、控制服务
查看服务状态
`systemctl status netdisk-fast-download.service`
启动服务
systemctl start netdisk-fast-download.service
`systemctl start netdisk-fast-download.service`
重启服务
systemctl restart netdisk-fast-download.service
`systemctl restart netdisk-fast-download.service`
停止服务
systemctl stop netdisk-fast-download.service
`systemctl stop netdisk-fast-download.service`
开机启动服务
systemctl enable netdisk-fast-download.servic
`systemctl enable netdisk-fast-download.servic`
停止开机启动
systemctl disable netdisk-fast-download.servic
`systemctl disable netdisk-fast-download.servic`
## Windows服务部署
1. 下载并解压releases版本netdisk-fast-download-0.1.6-bin.zip
2. 进入netdisk-fast-download-0.1.6-bin目录
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命令的路径改为实际路径
## Docker部署
TODO
## 0.1.8 开发计划
- Docker部署
- 联想乐云解析 √
- 直链缓存 √
- 日志优化 √
**技术栈:**
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)

22
bin/build.bat Normal file
View File

@@ -0,0 +1,22 @@
@echo off
setlocal
rem 获取当前 Java 版本信息并搜索是否包含 "17."
java -version 2>&1 | find "17." >nul
rem 如果找不到 JDK 17.x则下载并安装
if errorlevel 1 (
echo JDK 17.x not found. Downloading and installing...
REM 这里添加下载和安装 JDK 的代码
rem 验证安装
java -version
echo JDK 17.x installation complete.
) else (
echo JDK 17.x is already installed.
)
endlocal
pause

View File

@@ -8,7 +8,7 @@ Wants=network-online.target
Type=simple
# User=USER
# 需要JDK17及以上版本 注意修改为自己的路径
ExecStart=/root/java/jdk-17.0.2/bin/java -server -Xmx128m -jar /root/java/netdisk-fast-download-0.1.6/netdisk-fast-download-0.1.6.jar
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
Restart=always
StandOutput=syslog

View File

@@ -7,7 +7,7 @@ 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 (
for /f "delims=X" %%i in ('dir /b %MY_DIR%\netdisk-fast-download.jar') do (
set LAUNCH_JAR=%MY_DIR%\%%i
)

View File

@@ -1,10 +1,11 @@
@echo off && @chcp 65001 > nul
pushd %~dp0
set LIB_DIR=%~dp0
for /f "delims=X" %%i in ('dir /b %LIB_DIR%\netdisk-fast-download-*.jar') do (
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% %*
pause

View File

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

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId>
<version>0.1.6</version>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -16,14 +16,13 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<slf4j.version>2.0.5</slf4j.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>
<dependencies>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>core</artifactId>
<version>1.0.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->

View File

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

View File

@@ -1,5 +1,6 @@
package cn.qaiu.db.ddl;
import cn.qaiu.db.pool.JDBCType;
import cn.qaiu.vx.core.util.ReflectionUtil;
import io.vertx.codegen.format.CamelCase;
import io.vertx.codegen.format.Case;
@@ -38,6 +39,7 @@ public class CreateTable {
javaProperty2SqlColumnMap.put(Boolean.class, "BOOLEAN");
javaProperty2SqlColumnMap.put(String.class, "VARCHAR");
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.Date.class, "DATE");
javaProperty2SqlColumnMap.put(java.sql.Time.class, "TIME");
@@ -64,7 +66,9 @@ public class CreateTable {
}
}
public static String getCreateTableSQL(Class<?> clz) {
public static String getCreateTableSQL(Class<?> clz, JDBCType type) {
String quotationMarks = type == JDBCType.H2DB ? "\"" : "`";
String endStr = type == JDBCType.H2DB ? ");" : ")ENGINE=InnoDB DEFAULT CHARSET=utf8;";
// 判断类上是否有次注解
String primaryKey = null; // 主键
String tableName = null; // 表名
@@ -92,7 +96,7 @@ public class CreateTable {
int[] decimalSize = {22, 2};
int varcharSize = 255;
StringBuilder sb = new StringBuilder(50);
sb.append("CREATE TABLE IF NOT EXISTS \"").append(tableName).append("\" ( \r\n ");
sb.append("CREATE TABLE IF NOT EXISTS ").append(quotationMarks).append(tableName).append(quotationMarks).append(" ( \r\n ");
boolean firstId = true;
for (Field f : fields) {
Class<?> paramType = f.getType();
@@ -113,7 +117,7 @@ public class CreateTable {
decimalSize = fieldAnnotation.decimalSize();
varcharSize = fieldAnnotation.varcharSize();
}
sb.append("\"").append(column).append("\"");
sb.append(quotationMarks).append(column).append(quotationMarks);
sb.append(" ").append(sqlType);
// 添加类型长度
if (sqlType.equals("DECIMAL")) {
@@ -154,17 +158,20 @@ public class CreateTable {
//去掉最后一个逗号
int lastIndex = sql.lastIndexOf(",");
sql = sql.substring(0, lastIndex) + sql.substring(lastIndex + 1);
return sql.substring(0, sql.length() - 1) + ");\r\n";
return sql.substring(0, sql.length() - 1) + endStr;
}
public static void createTable(JDBCPool pool) {
public static void createTable(JDBCPool pool, JDBCType type) {
Set<Class<?>> tableClassList = ReflectionUtil.getReflections().getTypesAnnotatedWith(Table.class);
if (tableClassList.isEmpty()) LOGGER.info("Table model class not fount");
tableClassList.forEach(clazz -> {
String createTableSQL = getCreateTableSQL(clazz);
String createTableSQL = getCreateTableSQL(clazz, type);
pool.query(createTableSQL).execute().onSuccess(
rs -> LOGGER.info("\n" + createTableSQL + "create table --> ok")
).onFailure(Throwable::printStackTrace);
rs -> LOGGER.info("table auto generate:\n" + createTableSQL)
).onFailure(e -> {
LOGGER.error(e.getMessage() + " SQL: \n" + createTableSQL);
});
});
}
}

View File

@@ -1,20 +1,13 @@
package cn.qaiu.db.pool;
import cn.qaiu.db.ddl.CreateTable;
import cn.qaiu.db.server.H2ServerHolder;
import cn.qaiu.vx.core.util.VertxHolder;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.jdbcclient.JDBCPool;
import org.h2.tools.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
/**
* 初始化JDBC
* <br>Create date 2021/8/10 12:04
@@ -28,12 +21,14 @@ public class JDBCPoolInit {
JsonObject dbConfig;
Vertx vertx = VertxHolder.getVertxInstance();
String url;
private JDBCType type;
private static JDBCPoolInit instance;
public JDBCPoolInit(Builder builder) {
this.dbConfig = builder.dbConfig;
this.url = builder.url;
this.type = builder.type;
}
public static Builder builder() {
@@ -47,10 +42,12 @@ public class JDBCPoolInit {
public static class Builder {
private JsonObject dbConfig;
private String url;
private JDBCType type;
public Builder config(JsonObject dbConfig) {
this.dbConfig = dbConfig;
this.url = dbConfig.getString("jdbcUrl");
this.type = JDBCUtil.getJDBCType(dbConfig.getString("driverClassName"));
return this;
}
@@ -73,67 +70,16 @@ public class JDBCPoolInit {
return;
}
// 异步启动H2服务
vertx.createSharedWorkerExecutor("h2-server", 1, Long.MAX_VALUE)
.executeBlocking(this::h2serverExecute)
.onSuccess(res->{
LOGGER.info(res);
// 初始化数据库连接
vertx.createSharedWorkerExecutor("sql-pool-init")
.executeBlocking(this::poolInitExecute)
.onSuccess(LOGGER::info)
.onFailure(Throwable::printStackTrace);
})
.onFailure(Throwable::printStackTrace);
}
private void poolInitExecute(Promise<String> promise) {
.executeBlocking(() -> {
// 初始化连接池
pool = JDBCPool.pool(vertx, dbConfig);
CreateTable.createTable(pool);
promise.complete("init jdbc pool success");
}
private void checkOrCreateDBFile() {
LOGGER.info("init sql start");
String[] path = url.split("\\./");
path[1] = path[1].split(";")[0];
path[1] += ".mv.db";
File file = new File(path[1]);
if (!file.exists()) {
if (!file.getParentFile().exists()) {
if (file.getParentFile().mkdirs()) {
LOGGER.info("mkdirs -> {}", file.getParentFile().getAbsolutePath());
}
}
try {
if (file.createNewFile()) {
LOGGER.info("create file -> {}", file.getAbsolutePath());
}
} catch (IOException e) {
LOGGER.error(e.getMessage());
throw new RuntimeException("file create failed");
}
}
}
private void h2serverExecute(Promise<String> promise) {
// 初始化H2db, 创建本地db文件
checkOrCreateDBFile();
try {
String url = dbConfig.getString("jdbcUrl");
String[] portStr = url.split(":");
String port = portStr[portStr.length - 1].split("[/\\\\]")[0];
LOGGER.info("H2server listen port to {}", port);
H2ServerHolder.init(Server.createTcpServer("-tcp", "-tcpAllowOthers", "-tcpPort", port).start());
promise.complete("Start h2Server success");
} catch (SQLException e) {
throw new RuntimeException("Start h2Server failed: " + e.getMessage());
}
CreateTable.createTable(pool, type);
return "数据库连接初始化: URL=" + url;
})
.onSuccess(LOGGER::info)
.onFailure(Throwable::printStackTrace);
}
/**

View File

@@ -0,0 +1,9 @@
package cn.qaiu.db.pool;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/10/10 14:06
*/
public enum JDBCType {
MySQL, H2DB
}

View File

@@ -0,0 +1,18 @@
package cn.qaiu.db.pool;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/10/10 14:05
*/
public class JDBCUtil {
public static JDBCType getJDBCType(String deviceName) {
switch (deviceName) {
case "com.mysql.cj.jdbc.Driver":
case "com.mysql.jdbc.Driver":
return JDBCType.MySQL;
case "org.h2.Driver":
return JDBCType.H2DB;
}
throw new RuntimeException("不支持的SQL驱动类型: " + deviceName);
}
}

View File

@@ -5,36 +5,15 @@
<parent>
<artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId>
<version>0.1.6</version>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<version>1.0.8</version>
<artifactId>core</artifactId>
<properties>
<java.version>17</java.version>
<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>
<logback.version>1.4.6</logback.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-dependencies</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--logback日志实现-->
<dependency>
@@ -81,16 +60,24 @@
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.melloware/commons-beanutils2 -->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
<groupId>com.melloware</groupId>
<artifactId>commons-beanutils2</artifactId>
<version>${commons-beanutils2.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>

View File

@@ -44,6 +44,11 @@ public final class Deploy {
return INSTANCE;
}
/**
*
* @param args 启动参数
* @param handle 启动完成后回调处理函数
*/
public void start(String[] args, Handler<JsonObject> handle) {
this.mainThread = Thread.currentThread();
this.handle = handle;

View File

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

View File

@@ -1,11 +1,14 @@
package cn.qaiu.vx.core.handlerfactory;
import cn.qaiu.vx.core.annotaions.*;
import cn.qaiu.vx.core.annotaions.DateFormat;
import cn.qaiu.vx.core.annotaions.RouteHandler;
import cn.qaiu.vx.core.annotaions.RouteMapping;
import cn.qaiu.vx.core.annotaions.SockRouteMapper;
import cn.qaiu.vx.core.base.BaseHttpApi;
import cn.qaiu.vx.core.interceptor.AfterInterceptor;
import cn.qaiu.vx.core.interceptor.BeforeInterceptor;
import cn.qaiu.vx.core.model.JsonResult;
import cn.qaiu.vx.core.util.*;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
@@ -17,28 +20,26 @@ import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.CorsHandler;
import io.vertx.ext.web.handler.StaticHandler;
import io.vertx.ext.web.handler.TimeoutHandler;
import io.vertx.ext.web.handler.*;
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions;
import javassist.CtClass;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
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 java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME;
/**
* 路由映射, 参数绑定
@@ -57,14 +58,11 @@ public class RouterHandlerFactory implements BaseHttpApi {
add(HttpMethod.DELETE);
add(HttpMethod.HEAD);
}};
// 需要扫描注册的Router路径
private static volatile Reflections reflections;
private final String gatewayPrefix;
public RouterHandlerFactory(String gatewayPrefix) {
Objects.requireNonNull(gatewayPrefix, "The gateway prefix is empty.");
reflections = ReflectionUtil.getReflections();
this.gatewayPrefix = gatewayPrefix;
}
@@ -91,6 +89,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
LOGGER.debug("The HTTP service request address information ===>path:{}, uri:{}, method:{}",
ctx.request().path(), ctx.request().absoluteURI(), ctx.request().method());
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
ctx.response().headers().add(DATE, LocalDateTime.now().format(ISO_LOCAL_DATE_TIME));
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");
@@ -130,7 +129,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
LOGGER.error("Manually Register Handler Fail, Error details" + e.getMessage());
}
// 错误请求处理
mainRouter.errorHandler(405, ctx -> fireJsonResponse(ctx, JsonResult
mainRouter.errorHandler(405, ctx -> doFireJsonResultResponse(ctx, JsonResult
.error("Method Not Allowed", 405)));
mainRouter.errorHandler(404, ctx -> ctx.response().setStatusCode(404).setChunked(true)
.end("Internal server error: 404 not found"));
@@ -165,7 +164,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
// 普通路由
RouteMapping mapping = method.getAnnotation(RouteMapping.class);
HttpMethod routeMethod = HttpMethod.valueOf(mapping.method().name());
String routeUrl = getRouteUrl(method.getName(), mapping.value());
String routeUrl = getRouteUrl(mapping.value());
String url = root.concat(routeUrl);
// 匹配方法
Route route = router.route(routeMethod, url);
@@ -177,15 +176,16 @@ public class RouterHandlerFactory implements BaseHttpApi {
// 设置默认超时
route.handler(TimeoutHandler.create(SharedDataUtil.getCustomConfig().getInteger(ROUTE_TIME_OUT)));
route.handler(ResponseTimeHandler.create());
route.handler(ctx -> handlerMethod(instance, method, ctx)).failureHandler(ctx -> {
if (ctx.response().ended()) return;
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)) {
// websocket 基于sockJs
SockRouteMapper mapping = method.getAnnotation(SockRouteMapper.class);
String routeUrl = getRouteUrl(method.getName(), mapping.value());
String routeUrl = getRouteUrl(mapping.value());
String url = root.concat(routeUrl);
LOGGER.info("Register New Websocket Handler -> {}", url);
SockJSHandlerOptions options = new SockJSHandlerOptions()
@@ -212,10 +212,9 @@ public class RouterHandlerFactory implements BaseHttpApi {
/**
* 获取并处理路由URL分隔符
*
* @param methodName 路由method
* @return String
*/
private String getRouteUrl(String methodName, String mapperValue) {
private String getRouteUrl(String mapperValue) {
String routeUrl;
if ("/".equals(mapperValue)) {
routeUrl = mapperValue;
@@ -231,7 +230,6 @@ public class RouterHandlerFactory implements BaseHttpApi {
* 配置拦截
*
* @return Handler
* @throws Throwable Throwable
*/
private Set<Handler<RoutingContext>> getInterceptorSet() {
// 配置拦截
@@ -257,7 +255,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
if (handler.isAnnotationPresent(RouteHandler.class)) {
RouteHandler routeHandler = handler.getAnnotation(RouteHandler.class);
String value = routeHandler.value();
root += ("/".equals(value) ? "" : value);
root += (value.startsWith("/") ? value.substring(1) : value);
}
if (!root.endsWith("/")) {
root = root + "/";
@@ -300,37 +298,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");
// 绑定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 (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();
}
}
});
final MultiMap queryParams = ctx.queryParams();
// 解析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();
if (body != null) {
methodParametersTemp.forEach((k, v) -> {
@@ -350,28 +323,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 获取响应对象
Object[] parameterValueArray = parameterValueList.values().toArray(new Object[0]);
try {
// 反射调用
Object data = ReflectionUtil.invokeWithArguments(method, instance, parameterValueArray);
if (data != null) {
if (data instanceof JsonResult) {
fireJsonResponse(ctx, data);
doFireJsonResultResponse(ctx, (JsonResult<?>) data);
}
if (data instanceof JsonObject) {
doFireJsonObjectResponse(ctx, ((JsonObject) data));
} else if (data instanceof Future) { // 处理异步响应
((Future<?>) data).onSuccess(res -> {
if (res instanceof JsonResult) {
doFireJsonResultResponse(ctx, (JsonResult<?>) res);
}
if (res instanceof JsonObject) {
fireJsonResponse(ctx, res);
doFireJsonObjectResponse(ctx, ((JsonObject) res));
} else if (res != null) {
fireJsonResponse(ctx, JsonResult.data(res));
doFireJsonResultResponse(ctx, JsonResult.data(res));
} else {
handleAfterInterceptor(ctx, null);
}
}).onFailure(e -> fireJsonResponse(ctx, JsonResult.error(e.getMessage())));
}).onFailure(e -> doFireJsonResultResponse(ctx, JsonResult.error(e.getMessage())));
} else {
fireTextResponse(ctx, data.toString());
doFireJsonResultResponse(ctx, JsonResult.data(data));
}
}
} catch (Throwable e) {
@@ -384,7 +395,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
err = e.getCause().getMessage();
}
}
fireJsonResponse(ctx, JsonResult.error(err));
doFireJsonResultResponse(ctx, JsonResult.error(err));
}
}
@@ -403,16 +414,6 @@ public class RouterHandlerFactory implements BaseHttpApi {
return fmt;
}
@Override
public Set<AfterInterceptor> getAfterInterceptor() {
Set<Class<? extends AfterInterceptor>> afterInterceptorClassSet =
reflections.getSubTypesOf(AfterInterceptor.class);
if (afterInterceptorClassSet == null) {
return null;
}
return CommonUtil.sortClassSet(afterInterceptorClassSet);
}
private Set<BeforeInterceptor> getBeforeInterceptor() {
Set<Class<? extends BeforeInterceptor>> interceptorClassSet =
reflections.getSubTypesOf(BeforeInterceptor.class);

View File

@@ -1,7 +1,7 @@
package cn.qaiu.vx.core.interceptor;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
/**
* 后置拦截器接口
@@ -10,6 +10,6 @@ import io.vertx.core.json.JsonObject;
*/
public interface AfterInterceptor {
void handle(HttpServerRequest request, JsonObject responseData);
void handle(RoutingContext ctx, JsonObject responseData);
}

View File

@@ -3,15 +3,30 @@ 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 {
public interface BeforeInterceptor extends Handler<RoutingContext> {
String IS_NEXT = "RoutingContextIsNext";
default Handler<RoutingContext> doHandle() {
return this::handle;
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,6 +1,9 @@
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 java.io.Serial;
@@ -159,4 +162,16 @@ public class JsonResult<T> implements Serializable {
public static <T> JsonResult<T> success() {
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

@@ -3,8 +3,8 @@ package cn.qaiu.vx.core.util;
import cn.qaiu.vx.core.annotaions.HandleSortFilter;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.beanutils2.ConvertUtils;
import org.apache.commons.beanutils2.Converter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@@ -6,6 +6,7 @@ public interface ConfigConstant {
String EVENT_LOOP_POOL_SIZE = "eventLoopPoolSize";
String LOCAL = "local";
String SERVER = "server";
String CACHE = "cache";
String GLOBAL_CONFIG = "globalConfig";
String CUSTOM_CONFIG = "customConfig";
String ASYNC_SERVICE_INSTANCES = "asyncServiceInstances";

View File

@@ -1,7 +1,7 @@
package cn.qaiu.vx.core.util;
import io.vertx.core.MultiMap;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils2.BeanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -27,12 +27,12 @@ public final class ParamUtil {
return map;
}
public static <T> T multiMapToEntity(MultiMap multiMap,Class<T> tClass) throws NoSuchMethodException {
Map<String,String> map = multiMapToMap(multiMap);
public static <T> T multiMapToEntity(MultiMap multiMap, Class<T> tClass) throws NoSuchMethodException {
Map<String, String> map = multiMapToMap(multiMap);
T obj = null;
try {
obj = tClass.getDeclaredConstructor().newInstance();
BeanUtils.populate(obj,map);
BeanUtils.populate(obj, map);
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
LOGGER.error("实例化异常");
@@ -42,4 +42,21 @@ public final class ParamUtil {
}
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

@@ -5,12 +5,13 @@ import javassist.bytecode.AccessFlag;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.commons.beanutils.ConversionException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.reflections.Reflections;
import org.reflections.scanners.*;
import org.reflections.scanners.MemberUsageScanner;
import org.reflections.scanners.MethodParameterNamesScanner;
import org.reflections.scanners.Scanners;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
@@ -185,10 +186,10 @@ public final class ReflectionUtil {
return DateUtils.parseDate(value, fmt);
} catch (ParseException e) {
e.printStackTrace();
throw new ConversionException("无法将格式化日期");
throw new RuntimeException("无法将格式化日期");
}
default:
throw new ConversionException("无法将String类型" + value + "转为[" + name + "]");
throw new RuntimeException("无法将String类型" + value + "转为[" + name + "]");
}
}
@@ -200,7 +201,7 @@ public final class ReflectionUtil {
* @return Array
*/
public static Object conversionArray(CtClass ctClass, String value) {
if (!isBasicTypeArray(ctClass)) throw new ConversionException("无法解析数组");
if (!isBasicTypeArray(ctClass)) throw new RuntimeException("无法解析数组");
String[] strArr = value.split(",");
List<Object> obj = new ArrayList<>();
Arrays.stream(strArr).forEach(v -> obj.add(conversion(ctClass, v, null)));

View File

@@ -1,8 +1,13 @@
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 {
@@ -14,4 +19,22 @@ public class ResponseUtil {
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();
}
}

310
mvnw vendored Normal file
View File

@@ -0,0 +1,310 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

182
mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,182 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

24
note.txt Normal file
View File

@@ -0,0 +1,24 @@
直链缓存设计
每个网盘对应的标准分享URL如下
蓝奏云 (lz) https://lanzoux.com/{shareKey}
蓝奏云优享 (iz) https://www.ilanzou.com/s/{shareKey}
奶牛快传 (cow) https://cowtransfer.com/s/{shareKey}
移动云云空间 (ec) https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={shareKey}&isShare=1
小飞机网盘 (fj) https://share.feijipan.com/s/{shareKey}
亿方云 (fc) https://v2.fangcloud.com/sharing/{shareKey}
123云盘 (ye) https://www.123pan.com/s/{shareKey}.html
文叔叔 (ws) https://f.ws59.cn/f/{shareKey}
联想乐云 (le) https://lecloud.lenovo.com/share/{shareKey}
私有化网盘需要自己的域名也就是origin地址.
Cloudreve自建网盘 (ce) {origin}/s/{shareKey}
分享URL -> 类型+key
类型+key -> 标准分享URL
缓存key -> 下载URL
分享链接 -> add 网盘类型 pwd origin(私有化) -> 直链
https://f.ws59.cn/f/e3peohu6192

67
parser/pom.xml Normal file
View File

@@ -0,0 +1,67 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId>
<version>${revision}</version>
</parent>
<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,114 @@
package cn.qaiu.entity;
public class ShareLinkInfo {
private String shareKey; // 分享键
private String type; // 分享类型
private String sharePassword; // 分享密码(如果存在)
private String shareUrl; // 原始分享链接
private String standardUrl; // 规范化的标准链接
private ShareLinkInfo(Builder builder) {
this.shareKey = builder.shareKey;
this.type = builder.type;
this.sharePassword = builder.sharePassword;
this.shareUrl = builder.shareUrl;
this.standardUrl = builder.standardUrl;
}
// Getter和Setter方法
public String getShareKey() {
return shareKey;
}
public void setShareKey(String shareKey) {
this.shareKey = shareKey;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getSharePassword() {
return sharePassword;
}
public void setSharePassword(String sharePassword) {
this.sharePassword = sharePassword;
}
public String getShareUrl() {
return shareUrl;
}
public void setShareUrl(String shareUrl) {
this.shareUrl = shareUrl;
}
public String getStandardUrl() {
return standardUrl;
}
public void setStandardUrl(String standardUrl) {
this.standardUrl = standardUrl;
}
// 静态方法创建建造者对象
public static ShareLinkInfo.Builder newBuilder() {
return new ShareLinkInfo.Builder();
}
// 建造者类
public static class Builder {
private String shareKey; // 分享键
private String type; // 分享类型 (网盘模板枚举的小写)
private String sharePassword = ""; // 分享密码(如果存在)
private String shareUrl; // 原始分享链接
private String standardUrl; // 规范化的标准链接
public Builder shareKey(String shareKey) {
this.shareKey = shareKey;
return this;
}
public Builder type(String type) {
this.type = type;
return this;
}
public Builder sharePassword(String sharePassword) {
this.sharePassword = sharePassword;
return this;
}
public Builder shareUrl(String shareUrl) {
this.shareUrl = shareUrl;
return this;
}
public Builder standardUrl(String standardUrl) {
this.standardUrl = standardUrl;
return this;
}
public ShareLinkInfo build() {
return new ShareLinkInfo(this);
}
}
@Override
public String toString() {
return "ShareLinkInfo{" +
"shareKey='" + shareKey + '\'' +
", type='" + type + '\'' +
", sharePassword='" + sharePassword + '\'' +
", shareUrl='" + shareUrl + '\'' +
", standardUrl='" + standardUrl + '\'' +
'}';
}
}

View File

@@ -0,0 +1,7 @@
package cn.qaiu.parser;//package cn.qaiu.lz.common.parser;
import io.vertx.core.Future;
public interface IPanTool {
Future<String> parse();
}

View File

@@ -1,31 +1,63 @@
package cn.qaiu.lz.common.parser;
package cn.qaiu.parser;
import cn.qaiu.vx.core.util.VertxHolder;
import cn.qaiu.WebClientVertxInit;
import cn.qaiu.entity.ShareLinkInfo;
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();
protected WebClient client = WebClient.create(VertxHolder.getVertxInstance());
protected WebClient clientNoRedirects = WebClient.create(VertxHolder.getVertxInstance(), OPTIONS);
private static final WebClientOptions OPTIONS = new WebClientOptions().setFollowRedirects(false);
/**
* Http client
*/
protected WebClient client = WebClient.create(WebClientVertxInit.get());
/**
* Http client session (会话管理, 带cookie请求)
*/
protected WebClientSession clientSession = WebClientSession.create(client);
protected String key;
protected String pwd;
/**
* Http client 不自动跳转
*/
protected WebClient clientNoRedirects = WebClient.create(WebClientVertxInit.get(),
new WebClientOptions().setFollowRedirects(false));
protected PanBase(String key, String pwd) {
this.key = key;
this.pwd = pwd;
protected ShareLinkInfo shareLinkInfo;
/**
* 子类重写此构造方法不需要添加额外逻辑
* :
* <blockquote><pre>
* public XxTool(String key, String pwd) {
* super(key, pwd);
* }
* </pre></blockquote>
*
*/
public PanBase(ShareLinkInfo shareLinkInfo) {
this.shareLinkInfo = shareLinkInfo;
}
/**
* 失败时生成异常消息
*
* @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);
@@ -38,6 +70,12 @@ public abstract class PanBase {
}
}
/**
* 失败时生成异常消息
*
* @param errorMsg 提示消息
* @param args log参数变量
*/
protected void fail(String errorMsg, Object... args) {
try {
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
@@ -50,6 +88,12 @@ public abstract class PanBase {
}
}
/**
* 生成失败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,116 @@
package cn.qaiu.parser;
import cn.qaiu.parser.impl.*;
/**
* 枚举类 PanDomainTemplate 定义了不同网盘服务的模板信息,包括:
* <ul>
* <li>displayName: 网盘服务的显示名称,用于用户界面展示。</li>
* <li>regexPattern: 用于匹配和解析分享链接的正则表达式。</li>
* <li>standardUrlTemplate: 网盘服务的标准URL模板用于规范化分享链接。</li>
* <li>toolClass: 网盘解析工具实现类。</li>
* </ul>
* 请注意增添网盘时保证正则表达式最后一个捕捉组能匹配到分享key
* @author <a href="https://qaiu.top">QAIU</a>
* at 2023/6/13 4:26
*/
public enum PanDomainTemplate {
// 网盘定义
LZ("蓝奏云",
"https://([a-z]+)?\\.?lanzou[a-z]\\.com/(.+/)?(.+)",
"https://lanzoux.com/{shareKey}",
LzTool.class),
// https://www.feijix.com/s/
// https://share.feijipan.com/s/
FJ("小飞机网盘",
"https://(share\\.feijipan\\.com|www\\.feijix\\.com)/s/(.+)",
"https://www.feijix.com/s/{shareKey}",
FjTool.class),
// https://lecloud.lenovo.com/share/
LE("联想乐云",
"https://lecloud?\\.lenovo\\.com/share/(.+)",
"https://lecloud.lenovo.com/share/{shareKey}",
LeTool.class),
// https://v2.fangcloud.com/s/
FC("亿方云",
"https://v2\\.fangcloud\\.(com|cn)/(s|sharing)/([^/]+)",
"https://v2.fangcloud.com/s/{shareKey}",
FcTool.class),
// https://www.ilanzou.com/s/
IZ("蓝奏云优享",
"https://www\\.ilanzou\\.com/s/(.+)",
"https://www.ilanzou.com/s/{shareKey}",
IzTool.class),
// https://wx.mail.qq.com/ftn/download?
QQ("QQ邮箱中转站",
"https://i?wx\\.mail\\.qq\\.com/ftn/download\\?(.+)",
"https://iwx.mail.qq.com/ftn/download/{shareKey}",
QQTool.class),
// https://f.ws59.cn/f/或者https://www.wenshushu.cn/f/
WS("文叔叔",
"https://(f\\.ws59\\.cn|www\\.wenshushu\\.cn)/f/(.+)",
"https://f.ws59.cn/f/{shareKey}",
WsTool.class),
// https://www.123pan.com/s/
YE("123网盘",
"https://www\\.123pan\\.com/s/(.+)",
"https://www.123pan.com/s/{shareKey}",
YeTool.class),
// https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={code}&isShare=1
EC("移动云空间",
"https://www\\.ecpan\\.cn/web(/%23|/#)?/yunpanProxy\\?path=.*&data=" +
"([^&]+)&isShare=1",
"https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={shareKey}&isShare=1",
EcTool.class),
// https://cowtransfer.com/s/
COW("奶牛快传",
"https://(.*)cowtransfer\\.com/s/(.+)",
"https://cowtransfer.com/s/{shareKey}",
CowTool.class),
// https://pan.huang1111.cn/s/
CE("huang1111",
"https://pan\\.huang1111\\.cn/s/(.+)",
"https://pan.huang1111.cn/s/{shareKey}",
CeTool.class);
// 网盘的显示名称,用于用户界面显示
private final String displayName;
// 用于匹配和解析分享链接的正则表达式保证最后一个捕捉组能匹配到分享key
private final String regexPattern;
// 网盘的标准链接模板,不含占位符,用于规范化分享链接
private final String standardUrlTemplate;
// 指向解析工具IPanTool实现类
private final Class<? extends IPanTool> toolClass;
PanDomainTemplate(String displayName, String regexPattern, String standardUrlTemplate,
Class<? extends IPanTool> toolClass) {
this.displayName = displayName;
this.regexPattern = regexPattern;
this.standardUrlTemplate = standardUrlTemplate;
this.toolClass = toolClass;
}
public String getDisplayName() {
return displayName;
}
public String getRegexPattern() {
return regexPattern;
}
public String getStandardUrlTemplate() {
return standardUrlTemplate;
}
public Class<? extends IPanTool> getToolClass() {
return toolClass;
}
}

View File

@@ -1,6 +1,4 @@
package cn.qaiu.lz.common.parser;
import cn.qaiu.lz.common.parser.impl.LzTool;
package cn.qaiu.parser;
/**
* @author <a href="https://qaiu.top">QAIU</a>

View File

@@ -0,0 +1,113 @@
package cn.qaiu.parser;
import cn.qaiu.entity.ShareLinkInfo;
import org.apache.commons.lang3.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 该类提供方法来解析和规范化不同来源的分享链接,确保它们可以转换为统一的标准链接格式。
* 通过这种方式,应用程序可以更容易地处理和识别不同网盘服务的分享链接。
*
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/9/15 14:10
*/
public class ParserCreate {
private final PanDomainTemplate panDomainTemplate;
private final ShareLinkInfo shareLinkInfo;
public ParserCreate(PanDomainTemplate panDomainTemplate, ShareLinkInfo shareLinkInfo) {
this.panDomainTemplate = panDomainTemplate;
this.shareLinkInfo = shareLinkInfo;
}
// 解析并规范化分享链接
public ParserCreate normalizeShareLink() {
if (shareLinkInfo == null) {
throw new IllegalArgumentException("ShareLinkInfo not init");
}
// 匹配并提取shareKey
String shareUrl = shareLinkInfo.getShareUrl();
if (StringUtils.isEmpty(shareUrl)) {
throw new IllegalArgumentException("ShareLinkInfo shareUrl is empty");
}
Pattern pattern = Pattern.compile(this.panDomainTemplate.getRegexPattern());
Matcher matcher = pattern.matcher(shareUrl);
if (matcher.find()) {
String shareKey = matcher.group(matcher.groupCount());
// 返回规范化的标准链接
String standardUrl = getStandardUrlTemplate().replace("{shareKey}", shareKey);
shareLinkInfo.setShareUrl(shareUrl);
shareLinkInfo.setShareKey(shareKey);
shareLinkInfo.setStandardUrl(standardUrl);
return this;
}
throw new IllegalArgumentException("Invalid share URL for " + this.panDomainTemplate.getDisplayName());
}
public IPanTool createTool() {
if (shareLinkInfo == null || StringUtils.isEmpty(shareLinkInfo.getType())) {
throw new IllegalArgumentException("ShareLinkInfo not init or type is empty");
}
if (StringUtils.isEmpty(shareLinkInfo.getShareKey())) {
this.normalizeShareLink();
}
try {
return this.panDomainTemplate.getToolClass()
.getDeclaredConstructor(ShareLinkInfo.class)
.newInstance(shareLinkInfo);
} catch (Exception e) {
throw new RuntimeException("无法创建工具实例: " + panDomainTemplate.getToolClass().getName(), e);
}
}
// set share key
public ParserCreate shareKey(String shareKey) {
shareLinkInfo.setShareKey(shareKey);
shareLinkInfo.setStandardUrl(panDomainTemplate.getStandardUrlTemplate().replace("{shareKey}", shareKey));
return this;
}
public String getStandardUrlTemplate() {
return this.panDomainTemplate.getStandardUrlTemplate();
}
public ShareLinkInfo getShareLinkInfo() {
return shareLinkInfo;
}
public ParserCreate setShareLinkInfoPwd(String pwd) {
shareLinkInfo.setSharePassword(pwd);
return this;
}
// 根据分享链接获取PanDomainTemplate实例
public synchronized static ParserCreate fromShareUrl(String shareUrl) {
for (PanDomainTemplate panDomainTemplate : PanDomainTemplate.values()) {
if (shareUrl.matches(panDomainTemplate.getRegexPattern())) {
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
.type(panDomainTemplate.name().toLowerCase())
.shareUrl(shareUrl).build();
ParserCreate parserCreate = new ParserCreate(panDomainTemplate, shareLinkInfo);
return parserCreate.normalizeShareLink();
}
}
throw new IllegalArgumentException("Unsupported share URL");
}
// 根据type获取枚举实例
public synchronized static ParserCreate fromType(String type) {
try {
PanDomainTemplate panDomainTemplate = Enum.valueOf(PanDomainTemplate.class, type.toUpperCase());
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
.type(type.toLowerCase()).build();
return new ParserCreate(panDomainTemplate, shareLinkInfo);
} catch (IllegalArgumentException ignore) {
// 如果没有找到对应的枚举实例,抛出异常
throw new IllegalArgumentException("No enum constant for type name: " + type);
}
}
}

View File

@@ -0,0 +1,77 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
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;
/**
* <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 = "https://pan.huang1111.cn/api/v3/share/download/";
// api/v3/share/info/g31PcQ?password=qaiu
private static final String SHARE_API_PATH = "https://pan.huang1111.cn/api/v3/share/info/";
public CeTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
String key = shareLinkInfo.getShareKey();
String pwd = shareLinkInfo.getSharePassword();
// 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;
var shareApiUrl = SHARE_API_PATH;
var downloadApiUrl = DOWNLOAD_API_PATH;
// 设置cookie
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
if (pwd != null) {
httpRequest.addQueryParam("password", pwd);
}
// 获取下载链接
httpRequest.send().onSuccess(res -> getDownURL(downloadApiUrl)).onFailure(handleFail(shareApiUrl));
} catch (Exception 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

@@ -1,8 +1,8 @@
package cn.qaiu.lz.common.parser.impl;
package cn.qaiu.parser.impl;
import cn.qaiu.lz.common.parser.IPanTool;
import cn.qaiu.lz.common.parser.PanBase;
import cn.qaiu.lz.common.util.CommonUtils;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import org.apache.commons.lang3.StringUtils;
@@ -16,16 +16,14 @@ import org.apache.commons.lang3.StringUtils;
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 CowTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
key = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
final String key = shareLinkInfo.getShareKey();
String url = API_REQUEST_URL + "?uniqueUrl=" + key;
client.getAbs(url).send().onSuccess(res -> {
JsonObject resJson = res.bodyAsJsonObject();

View File

@@ -1,8 +1,8 @@
package cn.qaiu.lz.common.parser.impl;
package cn.qaiu.parser.impl;
import cn.qaiu.lz.common.parser.IPanTool;
import cn.qaiu.lz.common.parser.PanBase;
import cn.qaiu.lz.common.util.CommonUtils;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
@@ -12,22 +12,28 @@ import io.vertx.uritemplate.UriTemplate;
* 移动云空间解析
*/
public class EcTool extends PanBase implements IPanTool {
// https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=4b3d786755688b85c6eb0c04b9124f4dalzdaJpXHx&isShare=1
private static final String FIRST_REQUEST_URL = "https://www.ecpan.cn/drive/fileextoverrid" +
".do?chainUrlTemplate=https:%2F%2Fwww.ecpan" +
".do?extractionCode={extractionCode}&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 EcTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
final String dataKey = shareLinkInfo.getShareKey();
final String pwd = shareLinkInfo.getSharePassword();
// 第一次请求 获取文件信息
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("dataKey", dataKey).send().onSuccess(res -> {
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL))
.setTemplateParam("dataKey", dataKey)
.setTemplateParam("extractionCode", pwd == null ? "" : pwd)
.send()
.onSuccess(res -> {
JsonObject jsonObject = res.bodyAsJsonObject();
log.debug("ecPan get file info -> {}", jsonObject);
JsonObject fileInfo = jsonObject
@@ -37,6 +43,11 @@ public class EcTool extends PanBase implements IPanTool {
fail("{} 解析失败:{} key = {}", FIRST_REQUEST_URL, fileInfo.getString("errMesg"), dataKey);
return;
}
if (!fileInfo.containsKey("cloudpFile")) {
fail("{} 解析失败:cloudpFile不存在 key = {}", FIRST_REQUEST_URL, dataKey);
return;
}
JsonObject cloudpFile = fileInfo.getJsonObject("cloudpFile");
JsonArray fileIdList = JsonArray.of(cloudpFile);
// 构造请求JSON {"extCodeFlag":0,"isIp":0}

View File

@@ -1,8 +1,8 @@
package cn.qaiu.lz.common.parser.impl;
package cn.qaiu.parser.impl;
import cn.qaiu.lz.common.parser.IPanTool;
import cn.qaiu.lz.common.parser.PanBase;
import cn.qaiu.lz.common.util.CommonUtils;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
@@ -21,19 +21,19 @@ import java.util.regex.Pattern;
*/
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 FcTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
String data = key.replace("share","sharing");
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, data);
final String dataKey = shareLinkInfo.getShareKey();
final String pwd = shareLinkInfo.getSharePassword();
WebClientSession sClient = WebClientSession.create(client);
// 第一次请求 自动重定向
sClient.getAbs(SHARE_URL_PREFIX + dataKey).send().onSuccess(res -> {

View File

@@ -0,0 +1,111 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.AESUtils;
import cn.qaiu.util.UUIDUtil;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpRequest;
import io.vertx.uritemplate.UriTemplate;
/**
* 小飞机网盘
*
* @version V016_230609
*/
public class FjTool extends PanBase implements IPanTool {
public static final String REFERER_URL = "https://share.feijipan.com/";
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" +
"&uuid={uuid}&extra=2&timestamp={ts}&shareId={shareId}&type=0&offset=1&limit=60";
/// recommend/list?devType=6&devModel=Chrome&uuid={uuid}&extra=2&timestamp={ts}&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}&shareId={dataKey}";
// https://api.feijipan.com/ws/file/redirect?downloadId={fidEncode}&enable=1&devType=6&uuid={uuid}&timestamp={ts}&auth={auth}&shareId={dataKey}
private static final String VIP_REQUEST_URL = API_URL_PREFIX + "/buy/vip/list?devType=6&devModel=Chrome&uuid" +
"={uuid}&extra=2&timestamp={ts}";
// https://api.feijipan.com/ws/buy/vip/list?devType=6&devModel=Chrome&uuid=WQAl5yBy1naGudJEILBvE&extra=2&timestamp=E2C53155F6D09417A27981561134CB73
public FjTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
final String dataKey = shareLinkInfo.getShareKey();
// 240530 此处shareId又改为了原始的shareId
String shareId = dataKey; // String.valueOf(AESUtils.idEncrypt(dataKey));
long nowTs = System.currentTimeMillis();
String tsEncode = AESUtils.encrypt2Hex(Long.toString(nowTs));
String uuid = UUIDUtil.fjUuid(); // 也可以使用 UUID.randomUUID().toString()
// 24.5.12 飞机盘 规则修改 需要固定UUID先请求会员接口, 再请求后续接口
client.postAbs(UriTemplate.of(VIP_REQUEST_URL))
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode)
.send().onSuccess(r0 -> { // 忽略res
// long nowTs0 = System.currentTimeMillis();
String tsEncode0 = AESUtils.encrypt2Hex(Long.toString(nowTs));
// 第一次请求 获取文件信息
// 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)
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode0)
.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 nowTs2 = System.currentTimeMillis();
String tsEncode2 = AESUtils.encrypt2Hex(Long.toString(nowTs2));
String fidEncode = AESUtils.encrypt2Hex(fileId + "|" + userId);
String auth = AESUtils.encrypt2Hex(fileId + "|" + nowTs2);
MultiMap headers0 = MultiMap.caseInsensitiveMultiMap();
headers0.set("referer", REFERER_URL);
// 第二次请求
HttpRequest<Buffer> httpRequest =
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
.putHeaders(headers0)
.setTemplateParam("fidEncode", fidEncode)
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode2)
.setTemplateParam("auth", auth)
.setTemplateParam("dataKey", dataKey);
System.out.println(httpRequest.toString());
httpRequest.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));
}).onFailure(handleFail(FIRST_REQUEST_URL));
return promise.future();
}
}

View File

@@ -1,28 +1,24 @@
package cn.qaiu.lz.common.parser.impl;
package cn.qaiu.parser.impl;
import cn.qaiu.lz.common.parser.IPanTool;
import cn.qaiu.lz.common.parser.PanBase;
import cn.qaiu.lz.common.util.AESUtils;
import cn.qaiu.lz.common.util.CommonUtils;
import cn.qaiu.vx.core.util.VertxHolder;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.AESUtils;
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.ext.web.client.WebClientOptions;
import io.vertx.uritemplate.UriTemplate;
import java.util.UUID;
/**
* 小飞机网盘
* 蓝奏云优享
*
* @version V016_230609
*/
public class FjTool extends PanBase implements IPanTool {
public class IzTool extends PanBase implements IPanTool {
public static final String SHARE_URL_PREFIX = "https://www.feijix.com/s/";
private static final String API_URL_PREFIX = "https://api.feijipan.com/ws/";
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";
@@ -30,20 +26,19 @@ public class FjTool extends PanBase implements IPanTool {
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 IzTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
String dataKey = shareLinkInfo.getShareKey();
WebClient client = WebClient.create(VertxHolder.getVertxInstance(),
new WebClientOptions().setFollowRedirects(false));
String shareId = String.valueOf(AESUtils.idEncrypt(dataKey));
// 24.5.12 ilanzou改规则无需计算shareId
// 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 -> {
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("shareId", dataKey).send().onSuccess(res -> {
JsonObject resJson = res.bodyAsJsonObject();
if (resJson.getInteger("code") != 200) {
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
@@ -54,15 +49,18 @@ public class FjTool extends PanBase implements IPanTool {
return;
}
// 文件Id
String fileId = resJson.getJsonArray("list").getJsonObject(0).getString("fileIds");
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 tsEncode = AESUtils.encrypt2HexIz(Long.toString(nowTs));
String uuid = UUID.randomUUID().toString();
String fidEncode = AESUtils.encrypt2Hex(fileId + "|");
String auth = AESUtils.encrypt2Hex(fileId + "|" + nowTs);
// String fidEncode = AESUtils.encrypt2HexIz(fileId + "|");
String fidEncode = AESUtils.encrypt2HexIz(fileId + "|" + userId);
String auth = AESUtils.encrypt2HexIz(fileId + "|" + nowTs);
// 第二次请求
client.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
.setTemplateParam("fidEncode", fidEncode)
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode)

View File

@@ -0,0 +1,93 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
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 {
private static final String API_URL_PREFIX = "https://lecloud.lenovo.com/share/api/clouddiskapi/share/public/v1/";
public LeTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
final String dataKey = shareLinkInfo.getShareKey();
final String pwd = shareLinkInfo.getSharePassword();
// {"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
String fileId = fileInfoJson.getString("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, String 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

@@ -1,15 +1,13 @@
package cn.qaiu.lz.common.parser.impl;
package cn.qaiu.parser.impl;
import cn.qaiu.lz.common.parser.IPanTool;
import cn.qaiu.lz.common.parser.PanBase;
import cn.qaiu.lz.common.util.JsExecUtils;
import cn.qaiu.vx.core.util.VertxHolder;
import cn.qaiu.entity.ShareLinkInfo;
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.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
import javax.script.ScriptException;
@@ -21,24 +19,22 @@ import java.util.regex.Pattern;
* 蓝奏云解析工具
*
* @author QAIU
* @version 1.0 update 2021/5/16 10:39
*/
public class LzTool extends PanBase implements IPanTool {
public static final String SHARE_URL_PREFIX = "https://wwwa.lanzoui.com";
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);
public LzTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
@SuppressWarnings("unchecked")
public Future<String> parse() {
String sUrl = key.startsWith("https://") ? key : SHARE_URL_PREFIX + "/" + key;
String sUrl = shareLinkInfo.getStandardUrl();
String pwd = shareLinkInfo.getSharePassword();
WebClient client = WebClient.create(VertxHolder.getVertxInstance(),
new WebClientOptions().setFollowRedirects(false));
WebClient client = clientNoRedirects;
client.getAbs(sUrl).send().onSuccess(res -> {
String html = res.bodyAsString();
// 匹配iframe
@@ -55,16 +51,18 @@ public class LzTool extends PanBase implements IPanTool {
}
jsText = jsText.replace("document.getElementById('pwd').value", "\"" + pwd + "\"");
jsText = jsText.substring(0, jsText.indexOf("document.getElementById('rpt')"));
int i = jsText.indexOf("document.getElementById('rpt')");
if (i > 0) {
jsText = jsText.substring(0, i);
}
try {
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "down_p");
getDownURL(promise, sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
getDownURL(sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
} catch (ScriptException | NoSuchMethodException e) {
fail(e, "js引擎执行失败");
return;
}
return;
}
} else {
// 没有密码
String iframePath = matcher.group(1);
client.getAbs(SHARE_URL_PREFIX + iframePath).send().onSuccess(res2 -> {
String html2 = res2.bodyAsString();
@@ -78,11 +76,12 @@ public class LzTool extends PanBase implements IPanTool {
}
try {
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, null);
getDownURL(promise, sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
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();
}
@@ -99,7 +98,7 @@ public class LzTool extends PanBase implements IPanTool {
return html.substring(startPos, endPos);
}
private void getDownURL(Promise<String> promise, String key, WebClient client, Map<String, ?> signMap) {
private void getDownURL(String key, WebClient client, Map<String, ?> signMap) {
MultiMap map = MultiMap.caseInsensitiveMultiMap();
signMap.forEach((k, v) -> {
map.set(k, v.toString());
@@ -115,9 +114,7 @@ public class LzTool extends PanBase implements IPanTool {
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
String url = SHARE_URL_PREFIX + "/ajaxm.php";
client.postAbs(url).putHeaders(headers).sendForm(MultiMap
.caseInsensitiveMultiMap()
.setAll(map)).onSuccess(res2 -> {
client.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
JsonObject urlJson = res2.bodyAsJsonObject();
if (urlJson.getInteger("zt") != 1) {
fail(urlJson.getString("inf"));

View File

@@ -0,0 +1,93 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.StringUtils;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
/**
* <a href="https://wx.mail.qq.com/">QQ邮箱</a>
*/
public class QQTool extends PanBase implements IPanTool {
public static final String REDIRECT_URL_TEMP = "https://iwx.mail.qq.com/ftn/download?func=4&key={key}&code={code}";
public QQTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
// QQ mail 直接替换为302链接 无需请求
QueryStringDecoder queryStringDecoder = new QueryStringDecoder(shareLinkInfo.getShareUrl(), StandardCharsets.UTF_8);
Map<String, List<String>> prms = queryStringDecoder.parameters();
if (prms.containsKey("key") && prms.containsKey("code") && prms.containsKey("func")) {
log.info(prms.get("func").get(0));
promise.complete(REDIRECT_URL_TEMP.replace("{key}",
prms.get("key").get(0)).replace("{code}", prms.get("code").get(0)));
} else {
fail("key 不合法");
}
// 通过请求URL获取文件信息和直链 暂时不需要
// getFileInfo(key);
return promise.future();
}
private void getFileInfo(String key) {
// 设置基础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");
// 获取下载中转站页面
client.getAbs(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(key, "https://", "/") + "/");
headers.set("Host", StringUtils.StringCutNot(fileurl, "https://", "/"));
res.headers().forEach((k, v) -> {
if (k.equalsIgnoreCase("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(key));
}
}

View File

@@ -0,0 +1,42 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
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(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
final String key = shareLinkInfo.getShareKey();
final String pwd = shareLinkInfo.getSharePassword();
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) {
}
}

View File

@@ -0,0 +1,165 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
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;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
/**
* <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_API = "https://www.wenshushu.cn/ap/";
public WsTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
WebClient httpClient = this.client;
final String key = shareLinkInfo.getShareKey();
final String pwd = shareLinkInfo.getSharePassword();
// 设置基础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", key,
"password", pwd
)).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("")) {
promise.complete(URLDecoder.decode(fileurl, StandardCharsets.UTF_8));
} 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(key));
} catch (DecodeException | NullPointerException e) {
this.fail("token获取失败可能是分享链接的方式已更新");
}
} else {
this.fail("HTTP状态不正确可能是分享链接的方式已更新");
}
}).onFailure(this.handleFail(key));
return promise.future();
}
}

View File

@@ -1,9 +1,10 @@
package cn.qaiu.lz.common.parser.impl;
package cn.qaiu.parser.impl;
import cn.qaiu.lz.common.parser.IPanTool;
import cn.qaiu.lz.common.parser.PanBase;
import cn.qaiu.lz.common.util.CommonUtils;
import cn.qaiu.lz.common.util.JsExecUtils;
import cn.qaiu.entity.ShareLinkInfo;
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;
@@ -30,13 +31,14 @@ public class YeTool extends PanBase implements IPanTool {
"&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 YeTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
final String dataKey = shareLinkInfo.getShareKey();
final String pwd = shareLinkInfo.getSharePassword();
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("key", dataKey).send().onSuccess(res -> {
@@ -100,7 +102,7 @@ public class YeTool extends PanBase implements IPanTool {
jsonObject.put("ShareKey", reqBodyJson.getString("ShareKey"));
jsonObject.put("FileID", reqBodyJson.getInteger("FileId"));
jsonObject.put("S3keyFlag", reqBodyJson.getString("S3KeyFlag"));
jsonObject.put("Size", reqBodyJson.getInteger("Size"));
jsonObject.put("Size", reqBodyJson.getLong("Size"));
jsonObject.put("Etag", reqBodyJson.getString("Etag"));
// 调用JS文件获取签名

View File

@@ -1,4 +1,4 @@
package cn.qaiu.lz.common.util;
package cn.qaiu.util;
import org.apache.commons.lang3.StringUtils;
@@ -34,7 +34,10 @@ public class AESUtils {
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;
/**
* 秘钥长度
@@ -59,6 +62,7 @@ public class AESUtils {
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);
@@ -154,6 +158,15 @@ public class AESUtils {
}
}
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解密
*
@@ -214,10 +227,14 @@ public class AESUtils {
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'L', 'R', 'S', 'I',
'J', 'K'};
private static int decodeChar(char c) {
for (int i = 0; i < array.length; i++) {
if (c == array[i]) {
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;
}
}
@@ -226,21 +243,47 @@ public class AESUtils {
// 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(2, str.length() - 2);
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) * multiple;
result += decodeChar(c, keys) * multiple;
multiple = multiple * 62;
}
}
return result;
}
// ========================== musetransfer加密相关 ===========================
//length用户要求产生字符串的长度

View File

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

View File

@@ -1,4 +1,4 @@
package cn.qaiu.lz.common.util;
package cn.qaiu.util;
import java.net.MalformedURLException;
import java.net.URL;
@@ -7,6 +7,12 @@ 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);

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

@@ -1,4 +1,4 @@
package cn.qaiu.lz.common.util;
package cn.qaiu.util;
import org.apache.commons.lang3.StringUtils;
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
@@ -7,12 +7,6 @@ import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 执行Js脚本
@@ -21,28 +15,17 @@ import java.nio.file.Paths;
* @date 2023/7/29 17:35
*/
public class JsExecUtils {
private static final String JS_PATH = "js/ye123.js";
private static final String LZ_JS_PATH = "js/lz.js";
private static final String RES_PATH;
private static final Invocable inv;
// 初始化脚本引擎
static {
ScriptEngineManager engineManager = new ScriptEngineManager();
ScriptEngine engine = engineManager.getEngineByName("JavaScript"); // 得到脚本引擎
//获取文件所在的相对路径
URL resource = JsExecUtils.class.getResource("/");
if (resource == null) {
throw new RuntimeException("js resource path is null");
}
RES_PATH = resource.getPath();
String reader = RES_PATH + JS_PATH;
try (FileReader fReader = new FileReader(reader)) {
engine.eval(fReader);
fReader.close();
try {
engine.eval(JsContent.ye123);
inv = (Invocable) engine;
} catch (IOException | ScriptException e) {
} catch (ScriptException e) {
throw new RuntimeException(e);
}
}
@@ -63,24 +46,12 @@ public class JsExecUtils {
NoSuchMethodException {
ScriptEngineManager engineManager = new ScriptEngineManager();
ScriptEngine engine = engineManager.getEngineByName("JavaScript"); // 得到脚本引擎
try {
//获取文件所在的相对路径
Path path;
try {
path = Paths.get(RES_PATH + LZ_JS_PATH);
} catch (RuntimeException ioe) {
path = Paths.get(RES_PATH.substring(1) + LZ_JS_PATH);
}
String jsContent = Files.readString(path) + "\n" + jsText;
engine.eval(jsContent);
engine.eval(JsContent.lz + "\n" + jsText);
Invocable inv = (Invocable) engine;
//调用js中的函数
if (StringUtils.isNotEmpty(funName)) {
inv.invokeFunction(funName);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return (ScriptObjectMirror) engine.get("signObj");
}

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,35 @@
package cn.qaiu.util;
import java.security.SecureRandom;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/5/13 4:10
*/
public class UUIDUtil {
public static String fjUuid() {
return generateRandomString(21);
}
public static String generateRandomString(int length) {
SecureRandom random = new SecureRandom();
byte[] randomBytes = new byte[length];
random.nextBytes(randomBytes);
StringBuilder sb = new StringBuilder();
for (byte b : randomBytes) {
int value = b & 0x3F; // 63 in hexadecimal
if (value < 36) {
sb.append(Integer.toString(value, 36));
} else if (value < 62) {
sb.append(Character.toUpperCase(Integer.toString(value - 26, 36).charAt(0)));
} else if (value > 62) {
sb.append("-");
} else { // value == 62 or 63
sb.append("_");
}
}
return sb.toString();
}
}

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,37 @@
package cn.qaiu.parser;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FCURLParser {// 定义前缀
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 SHARING_REGEX = "https://www\\.ecpan\\.cn/web(/%23|/#)?/yunpanProxy\\?path=.*&data=" +
"([^&]+)&isShare=1";
public static void main(String[] args) {
// 测试 URL
String[] urls = {
"https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=4b3d786755688b85c6eb0c04b9124f4dalzdaJpXHx&isShare=1",
"https://www.ecpan.cn/web/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=4b3d786755688b85c6eb0c04b9124f4dalzdaJpXHx&isShare=1",
"https://v2.fangcloud.cn/sharing/xyz789"
};
// 编译正则表达式
Pattern pattern = Pattern.compile(SHARING_REGEX);
for (String url : urls) {
Matcher matcher = pattern.matcher(url);
if (matcher.find()) {
System.out.println(matcher.groupCount());
String shareKey = matcher.group(matcher.groupCount()); // 捕捉组 3
System.out.println("Captured part: " + shareKey);
} else {
System.out.println("No match found.");
}
}
}
}

View File

@@ -0,0 +1,73 @@
package cn.qaiu.parser;
import cn.qaiu.entity.ShareLinkInfo;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/8/8 2:39
*/
public class PanDomainTemplateTest {
@Test
public void normalizeShareLink() {
// 准备测试数据
String testShareUrl = "https://test.lanzoux.com/s/someShareKey";
ParserCreate parserCreate = ParserCreate.fromShareUrl(testShareUrl); // 假设使用LZ网盘模板
// 调用normalizeShareLink方法
ShareLinkInfo result = parserCreate.getShareLinkInfo();
System.out.println(result);
// 断言结果是否符合预期
assertNotNull("Result should not be null", result);
assertEquals("Share key should match", "someShareKey", result.getShareKey());
assertEquals("Standard URL should be generated correctly", parserCreate.getStandardUrlTemplate().replace("{shareKey}", "someShareKey"), result.getStandardUrl());
// 可以添加更多的断言来验证其他字段
}
@Test
public void fromShareUrl() throws InterruptedException {
// 准备测试数据
String lzUrl = "https://wwn.lanzouy.com/ihLkw1gezutg";
String cowUrl = "https://cowtransfer.com/s/9a644fe3e3a748";
String ceUrl = "https://pan.huang1111.cn/s/g31PcQ";
String wsUrl = "https://f.ws59.cn/f/f25625rv6p6";
// ParserCreate.fromShareUrl(wsUrl).createTool()
// .parse().onSuccess(System.out::println);
// ParserCreate.fromShareUrl(lzUrl).createTool()
// .parse().onSuccess(System.out::println);
// ParserCreate.fromShareUrl(cowUrl).createTool()
// .parse().onSuccess(System.out::println);
ParserCreate.fromShareUrl(lzUrl).createTool()
.parse().onSuccess(System.out::println);
// ParserCreate.fromType("lz").shareKey("ihLkw1gezutg")
// .createTool().parse().onSuccess(System.out::println);
// ParserCreate.LZ.shareKey("ihLkw1gezutg")
// .createTool().parse().onSuccess(System.out::println);
// 调用fromShareUrl方法
// PanDomainTemplate resultTemplate = ParserCreate.fromShareUrl(testShareUrl);
// System.out.println(resultTemplate.normalizeShareLink(testShareUrl));
// System.out.println(resultTemplate.shareKey("xxx"));
// System.out.println(resultTemplate.createTool("xxx",null).parse()
// .onSuccess(System.out::println));
// System.out.println(resultTemplate.getDisplayName());
// System.out.println(resultTemplate.getStandardUrlTemplate());
// System.out.println(resultTemplate.getRegexPattern());
//
// // 断言结果是否符合预期
// assertNotNull("Result should not be null", resultTemplate);
// assertEquals("Should return the correct template", ParserCreate.LZ, resultTemplate);
// // 可以添加更多的断言来验证正则表达式匹配逻辑
// new Scanner(System.in).nextLine();
TimeUnit.SECONDS.sleep(5);
}
}

View File

@@ -1,6 +1,6 @@
package cn.qaiu.web.test;
package qaiu.web.test;
import cn.qaiu.lz.common.util.AESUtils;
import cn.qaiu.util.AESUtils;
import org.junit.Assert;
import org.junit.Test;
@@ -50,6 +50,12 @@ public class TestAESUtil {
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 {

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.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import org.apache.commons.lang3.RegExUtils;
import org.junit.Test;
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.Vertx;
@@ -17,7 +17,7 @@ public class WebClientExample {
WebClient client = WebClient.create(vertx);
MultipartForm form = new MultipartFormImpl()
.attribute("email", "736226400@qq.com")
.attribute("email", "")
.attribute("password", "");
client.postAbs("https://cowtransfer.com/api/user/emaillogin")

53
pom.xml
View File

@@ -7,22 +7,64 @@
<groupId>cn.qaiu</groupId>
<artifactId>netdisk-fast-download</artifactId>
<packaging>pom</packaging>
<version>0.1.6</version>
<version>${revision}</version>
<modules>
<module>core</module>
<module>web-service</module>
<module>core-database</module>
<module>parser</module>
</modules>
<properties>
<revision>0.1.8</revision>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<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.30</lombok.version>
<slf4j.version>2.0.5</slf4j.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<commons-beanutils2.version>2.0.0</commons-beanutils2.version>
<jackson.version>2.14.2</jackson.version>
<logback.version>1.4.12</logback.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-dependencies</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>core-database</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>parser</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
@@ -105,14 +147,7 @@
<!--<failOnError>false</failOnError>-->
<!--当配置true时,只清理filesets里的文件,构建目录中得文件不被清理.默认是flase.-->
<excludeDefaultDirectories>false</excludeDefaultDirectories>
<filesets>
<fileset>
<!--要清理的目录位置-->
<directory>${project.basedir}/logs</directory>
<!--是否跟随符号链接 (symbolic links)-->
<followSymlinks>false</followSymlinks>
</fileset>
</filesets>
</configuration>
</plugin>
</plugins>

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^1.2.2",
"axios": "^1.7.4",
"core-js": "^3.8.3",
"element-ui": "^2.15.12",
"vue": "^2.6.14",
@@ -26,7 +26,7 @@
"vue-template-compiler": "^2.6.14",
"compression-webpack-plugin": "^6.1.1",
"filemanager-webpack-plugin": "2.0.5"
"filemanager-webpack-plugin": "8.0.0"
},
"eslintConfig": {
"root": true,

View File

@@ -11,7 +11,8 @@
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>
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-9851170484804006"
crossorigin="anonymous"></script>
<style>
.page-loading-wrap {
padding: 120px;

View File

@@ -7,13 +7,17 @@
<el-avatar :size="150" :src="avatar"></el-avatar>
</div>
</div>
<h3 style="text-align: center;">NFD网盘直链解析(API演示)</h3>
<h3 style="text-align: center;">NFD网盘直链解析0.1.8_bate(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><strong>目前支持 </strong>蓝奏云/蓝奏云优享/小飞机盘/123云盘/奶牛快传/移动云云空间/亿方云/文叔叔/QQ邮箱文件中转站</p>
<p>已加入缓存机制, 如果遇到解析出的下载链接失效的情况请及时到项目GitHub反馈</p>
<p>
<el-button><strong @click="getInfo">刷新API调用统计</strong></el-button>
</p>
<p>节点1: 回源请求数:{{ node1Info.parserTotal }}, 缓存请求数:{{ node1Info.cacheTotal }}, 总数:{{ node1Info.total }}</p>
<!-- <p>节点2: 成功:{{ node2Info.success }},失败:{{ node2Info.fail }},总数:{{ node2Info.total }}</p>-->
</div>
<hr>
<div class="main" v-loading="isLoading">
@@ -26,7 +30,8 @@
<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>
v-clipboard:error="onError">点我复制
</el-button>
</el-input>
</div>
<div v-show="respData.code" style="margin-top: 10px">
@@ -113,7 +118,9 @@ export default {
},
],
getLink: '',
getLink2: ''
getLink2: '',
node1Info: {},
node2Info: {},
}
},
methods: {
@@ -140,10 +147,11 @@ export default {
message: response.data.msg,
type: 'success'
})
this.downUrl = response.data.data
this.downUrl = response.data.data.directLink
} else {
this.$message.error(response.data.msg)
}
this.getInfo()
},
error => {
this.isLoading = false
@@ -151,12 +159,30 @@ export default {
}
)
},
onCopy(){
onCopy() {
this.$message.success('复制成功')
},
onError(){
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>

View File

@@ -40,17 +40,17 @@ module.exports = {
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'},
]
}
})
// 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'},
// ]
// }
// })
]
},

6771
web-front/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -5,38 +5,48 @@
<parent>
<artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId>
<version>0.1.6</version>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<version>0.1.6</version>
<artifactId>web-service</artifactId>
<properties>
<packageDirectory>${project.basedir}/target/package</packageDirectory>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<slf4j.version>2.0.5</slf4j.version>
<vertx-jooq.version>6.1.0</vertx-jooq.version>
<finalName>netdisk-fast-download-${project.version}</finalName>
<finalName>netdisk-fast-download</finalName>
</properties>
<dependencies>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>core</artifactId>
<version>1.0.8</version>
</dependency>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>core-database</artifactId>
</dependency>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>parser</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!--logback日志实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<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>
<groupId>org.slf4j</groupId>
@@ -49,24 +59,6 @@
<version>4.13.2</version>
<scope>test</scope>
</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.6</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>
</dependencies>
<build>
@@ -105,6 +97,8 @@
<exclude>*.**</exclude>
<exclude>*/*.xml</exclude>
<exclude>conf/**</exclude>
<exclude>js/**</exclude>
<exclude>http-tools/**</exclude>
</excludes>
<archive>
<manifest>

View File

@@ -1,9 +1,14 @@
package cn.qaiu.lz;
import cn.qaiu.WebClientVertxInit;
import cn.qaiu.db.pool.JDBCPoolInit;
import cn.qaiu.lz.common.cache.CacheConfigLoader;
import cn.qaiu.vx.core.Deploy;
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.jackson.DatabindCodec;
/**
@@ -19,15 +24,20 @@ public class AppMain {
}
/**
* 初始化数据库
* 初始化数据库/缓存等
*
* @param jsonObject 配置
*/
private static void exec(JsonObject jsonObject) {
WebClientVertxInit.init(VertxHolder.getVertxInstance());
DatabindCodec.mapper().registerModule(new JavaTimeModule());
// 数据库
if (jsonObject.getJsonObject(ConfigConstant.SERVER).getBoolean("enableDatabase")) {
JDBCPoolInit.builder().config(jsonObject.getJsonObject("dataSource")).build().initPool();
}
// 缓存
if (jsonObject.containsKey(ConfigConstant.CACHE)) {
CacheConfigLoader.init(jsonObject.getJsonObject(ConfigConstant.CACHE));
}
}
}

View File

@@ -0,0 +1,37 @@
package cn.qaiu.lz.common.cache;
import cn.qaiu.parser.PanDomainTemplate;
import io.vertx.core.json.JsonObject;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/9/12 7:38
*/
public class CacheConfigLoader {
private static final Map<String, Integer> CONFIGS = new HashMap<>();
public static String TYPE;
public static Integer DEFAULT_DURATION;
public static void init(JsonObject config) {
TYPE = config.getString("type");
Integer defaultDuration = config.getInteger("defaultDuration");
DEFAULT_DURATION = defaultDuration == null ? 60 : defaultDuration;
config.getJsonObject("duration").getMap().forEach((k,v) -> {
if (v == null) {
CONFIGS.put(k, DEFAULT_DURATION);
} else {
CONFIGS.put(k, (Integer) v);
}
});
}
public static Integer getDuration(PanDomainTemplate pdt) {
return CONFIGS.get(pdt.name().toLowerCase());
}
public static Integer getDuration(String type) {
return CONFIGS.get(type.toLowerCase());
}
}

View File

@@ -0,0 +1,129 @@
package cn.qaiu.lz.common.cache;
import cn.qaiu.db.pool.JDBCPoolInit;
import cn.qaiu.lz.web.model.CacheLinkInfo;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.jdbcclient.JDBCPool;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.templates.SqlTemplate;
import java.util.HashMap;
import java.util.Map;
public class CacheManager {
private final JDBCPool jdbcPool = JDBCPoolInit.instance().getPool();
public Future<CacheLinkInfo> get(String cacheKey) {
String sql = "SELECT share_key as shareKey, direct_link as directLink, expiration FROM cache_link_info WHERE share_key = #{share_key}";
Map<String, Object> params = new HashMap<>();
params.put("share_key", cacheKey);
Promise<CacheLinkInfo> promise = Promise.promise();
SqlTemplate.forQuery(jdbcPool, sql)
.mapTo(CacheLinkInfo.class)
.execute(params)
.onSuccess(rows->{
CacheLinkInfo cacheHit;
if (rows.size() > 0) {
cacheHit = rows.iterator().next();
cacheHit.setCacheHit(true);
} else {
cacheHit = new CacheLinkInfo(JsonObject.of("cacheHit", false, "shareKey", cacheKey));
}
promise.complete(cacheHit);
}).onFailure(Throwable::printStackTrace);
return promise.future();
}
// 插入或更新缓存数据
public Future<Void> cacheShareLink(CacheLinkInfo cacheLinkInfo) {
String sql = "MERGE INTO cache_link_info (share_key, direct_link, expiration) " +
"KEY (share_key) " +
"VALUES (#{shareKey}, #{directLink}, #{expiration})";
// 直接传递 CacheLinkInfo 实体类
return SqlTemplate.forUpdate(jdbcPool, sql)
.mapFrom(CacheLinkInfo.class) // 将实体类映射为 Tuple 参数
.execute(cacheLinkInfo)
.mapEmpty();
}
// 统计网盘厂商API解析次数
public Future<Integer> updateTotalByCached(String shareKey) {
Promise<Integer> promise = Promise.promise();
String sql = """
MERGE INTO `api_statistics_info` (`pan_type`, `share_key`, `cache_hit_total`, `update_ts`)
KEY (`share_key`)
VALUES (#{panType}, #{shareKey}, #{total}, #{ts})
""";
getShareKeyTotal(shareKey, "cache_hit_total").onSuccess(total -> {
Integer newTotal = (total == null ? 0 : total) + 1;
SqlTemplate.forUpdate(jdbcPool, sql)
.execute(new HashMap<>() {{
put("panType", getShareType(shareKey));
put("shareKey", shareKey);
put("total", newTotal);
put("ts", System.currentTimeMillis());
}})
.onSuccess(res -> promise.complete(res.rowCount()))
.onFailure(Throwable::printStackTrace);
});
return promise.future();
}
private String getShareType(String fullShareKey) {
// 将type和shareKey组合成一个字符串作为缓存key
return fullShareKey.split(":")[0];
}
// 统计网盘厂商API解析次数
public Future<Integer> updateTotalByParser(String shareKey) {
Promise<Integer> promise = Promise.promise();
String sql = """
MERGE INTO `api_statistics_info` (`pan_type`, `share_key`, `api_parser_total`, `update_ts`)
KEY (`share_key`)
VALUES (#{panType}, #{shareKey}, #{total}, #{ts})
""";
getShareKeyTotal(shareKey, "api_parser_total").onSuccess(total -> {
Integer newTotal = (total == null ? 0 : total) + 1;
SqlTemplate.forUpdate(jdbcPool, sql)
.execute(new HashMap<>() {{
put("panType", getShareType(shareKey));
put("shareKey", shareKey);
put("total", newTotal);
put("ts", System.currentTimeMillis());
}})
.onSuccess(res -> promise.complete(res.rowCount()))
.onFailure(Throwable::printStackTrace);
});
return promise.future();
}
public Future<Integer> getShareKeyTotal(String shareKey, String name) {
String sql = """
select `share_key`, sum({total_name}) sum_num
from `api_statistics_info`
group by `share_key` having `share_key` = #{shareKey};
""".replace("{total_name}", name);
Promise<Integer> promise = Promise.promise();
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("shareKey", shareKey);
SqlTemplate.forQuery(jdbcPool, sql)
.mapTo(Row::toJson)
.execute(paramMap)
.onSuccess(res -> {
Integer total = res.iterator().hasNext() ?
res.iterator().next().getInteger("sum_num") : null;
promise.complete(total);
});
return promise.future();
}
}

View File

@@ -16,14 +16,12 @@ import static cn.qaiu.vx.core.util.ConfigConstant.IGNORES_REG;
@HandleSortFilter(1)
public class DefaultInterceptor implements BeforeInterceptor {
protected final JsonArray ignores = SharedDataUtil.getJsonArrayForCustomConfig(IGNORES_REG);
@Override
public void handle(RoutingContext ctx) {
System.out.println("进入前置拦截器->" + ctx.request().path());
ctx.next();
// System.out.println("进入前置拦截器1->" + ctx.request().path());
doNext(ctx);
}
}

View File

@@ -1,26 +1,53 @@
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.util.CommonUtil;
import cn.qaiu.vx.core.util.SharedDataUtil;
import io.vertx.core.http.HttpServerRequest;
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 {
protected final JsonArray ignores = SharedDataUtil.getJsonArrayForCustomConfig(IGNORES_REG);
JDBCPool client = JDBCPoolInit.instance().getPool();
private final JsonArray ignores = SharedDataUtil.getJsonArrayForCustomConfig(IGNORES_REG);
@Override
public void handle(HttpServerRequest request, JsonObject responseData) {
System.out.println("后置拦截-->" + responseData + " path:" + request.path());
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) return;
if (responseData.containsKey("code") && responseData.getInteger("code") == 500) {
log.error("code 500: {} {}", ctx.request().path(), responseData.getString("msg"));
}
}
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

@@ -1,5 +0,0 @@
package cn.qaiu.lz.common.model;
public class ParserInfo {
}

View File

@@ -0,0 +1,24 @@
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();
@Length(varcharSize = 4096)
String path;
Integer code;
@Length(varcharSize = 4096)
String data;
}

View File

@@ -1,46 +0,0 @@
package cn.qaiu.lz.common.parser;//package cn.qaiu.lz.common.parser;
import cn.qaiu.lz.common.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);
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)) {
return new FjTool(url, pwd);
} else if (url.contains(LzTool.LINK_KEY)) {
return new LzTool(url, pwd);
}
throw new UnsupportedOperationException("未知分享类型");
}
}

View File

@@ -1,20 +0,0 @@
package cn.qaiu.lz.common.parser.impl;
import cn.qaiu.lz.common.parser.IPanTool;
import cn.qaiu.lz.common.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.Promise;
public class QkTool extends PanBase implements IPanTool {
public QkTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
Promise<String> promise = Promise.promise();
promise.complete("https://lz.qaiu.top");
return promise.future();
}
}

View File

@@ -1,82 +0,0 @@
package cn.qaiu.lz.common.parser.impl;
import cn.qaiu.lz.common.parser.IPanTool;
import cn.qaiu.lz.common.parser.PanBase;
import cn.qaiu.lz.common.util.CommonUtils;
import io.vertx.core.Future;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.uritemplate.UriTemplate;
/**
* UC网盘解析
*/
public class UcTool extends PanBase implements IPanTool {
private static final String API_URL_PREFIX = "https://pc-api.uc.cn/1/clouddrive/";
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" +
"=UCBrowser";
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "transfer_share/detail?pwd_id={pwd_id}&passcode" +
"={passcode}&stoken={stoken}";
private static final String THIRD_REQUEST_URL = API_URL_PREFIX + "file/download?entry=ft&fr=pc&pr=UCBrowser";
public UcTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
var dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
var passcode = (pwd == null) ? "" : pwd;
var jsonObject = JsonObject.of("share_for_transfer", true);
jsonObject.put("pwd_id", dataKey);
jsonObject.put("passcode", passcode);
// 第一次请求 获取文件信息
client.postAbs(FIRST_REQUEST_URL).sendJsonObject(jsonObject).onSuccess(res -> {
log.debug("第一阶段 {}", res.body());
var resJson = res.bodyAsJsonObject();
if (resJson.getInteger("code") != 0) {
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
return;
}
var stoken = resJson.getJsonObject("data").getString("stoken");
// 第二次请求
client.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
.setTemplateParam("pwd_id", dataKey)
.setTemplateParam("passcode", passcode)
.setTemplateParam("stoken", stoken)
.send().onSuccess(res2 -> {
log.debug("第二阶段 {}", res2.body());
JsonObject resJson2 = res2.bodyAsJsonObject();
if (resJson2.getInteger("code") != 0) {
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2);
return;
}
// 文件信息
var info = resJson2.getJsonObject("data").getJsonArray("list").getJsonObject(0);
// 第二次请求
var bodyJson = JsonObject.of()
.put("fids", JsonArray.of(info.getString("fid")))
.put("pwd_id", dataKey)
.put("stoken", stoken)
.put("fids_token", JsonArray.of(info.getString("share_fid_token")));
client.postAbs(THIRD_REQUEST_URL).sendJsonObject(bodyJson)
.onSuccess(res3 -> {
log.debug("第三阶段 {}", res3.body());
var resJson3 = res3.bodyAsJsonObject();
if (resJson3.getInteger("code") != 0) {
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2);
return;
}
promise.complete(resJson3.getJsonArray("data").getJsonObject(0).getString("download_url"));
}).onFailure(handleFail(THIRD_REQUEST_URL));
}).onFailure(handleFail(SECOND_REQUEST_URL));
}
).onFailure(handleFail(FIRST_REQUEST_URL));
return promise.future();
}
}

View File

@@ -1,12 +0,0 @@
package cn.qaiu.lz.common.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

@@ -96,7 +96,7 @@ public class SnowflakeIdWorker {
*
* @return SnowflakeId
*/
public synchronized long nextId() {
public synchronized Long nextId() {
long timestamp = timeGen();
//如果当前时间小于上一次ID生成的时间戳说明系统时钟回退过这个时候应当抛出异常
@@ -214,6 +214,14 @@ public class 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) {
if (snowflakeIdWorkerCluster == null) {
snowflakeIdWorkerCluster = new SnowflakeIdWorker(workerId, datacenterId);

View File

@@ -0,0 +1,79 @@
package cn.qaiu.lz.common.util;
import io.vertx.core.MultiMap;
import io.vertx.core.http.HttpServerRequest;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
/**
* 处理URL截断问题拼接被截断的参数特殊处理pwd参数。
*
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/9/13
*/
public class URLParamUtil {
/**
* 解析并处理截断的URL参数
*
* @param request HttpServerRequest对象
* @return 完整的URL字符串
*/
public static String parserParams(HttpServerRequest request) {
String url = request.absoluteURI();
MultiMap params = request.params();
// 处理URL截断的情况例如: url='https://...&key=...&code=...'
if (params.contains("url")) {
String encodedUrl = params.get("url");
url = handleTruncatedUrl(encodedUrl, params);
if (url.endsWith(".html")) {
// 123云盘的后缀处理
url = url.replace(".html", "");
}
}
return url;
}
/**
* 处理被截断的URL拼接所有参数特殊处理pwd参数。
*
* @param encodedUrl 被截断的url参数
* @param params 请求的其他参数
* @return 重新拼接后的完整URL
*/
private static String handleTruncatedUrl(String encodedUrl, MultiMap params) {
// 对URL进行解码以便获取完整的URL
String decodedUrl = URLDecoder.decode(encodedUrl, StandardCharsets.UTF_8);
// 如果URL已经包含查询参数不需要额外拼接
if (params.contains("pwd")) {
if (params.size() == 2) {
return decodedUrl;
}
} else {
if (params.size() == 1) {
return decodedUrl;
}
}
// 拼接被截断的URL参数忽略pwd参数
StringBuilder urlBuilder = new StringBuilder(decodedUrl);
boolean firstParam = !decodedUrl.contains("?");
for (String paramName : params.names()) {
if (!paramName.equals("url") && !paramName.equals("pwd")) { // 忽略 "url" 和 "pwd" 参数
if (firstParam) {
urlBuilder.append("?");
firstParam = false;
} else {
urlBuilder.append("&");
}
urlBuilder.append(paramName).append("=").append(params.get(paramName));
}
}
return urlBuilder.toString();
}
}

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,9 +1,8 @@
package cn.qaiu.lz.web.http;
import cn.qaiu.lz.common.parser.IPanTool;
import cn.qaiu.lz.common.parser.impl.EcTool;
import cn.qaiu.lz.web.model.SysUser;
import cn.qaiu.lz.web.service.UserService;
import cn.qaiu.lz.common.util.URLParamUtil;
import cn.qaiu.lz.web.model.CacheLinkInfo;
import cn.qaiu.lz.web.service.CacheService;
import cn.qaiu.vx.core.annotaions.RouteHandler;
import cn.qaiu.vx.core.annotaions.RouteMapping;
import cn.qaiu.vx.core.enums.RouteMethod;
@@ -15,8 +14,6 @@ import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import lombok.extern.slf4j.Slf4j;
import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
/**
* 服务API
* <br>Create date 2021/4/28 9:15
@@ -27,65 +24,55 @@ import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
@RouteHandler("/")
public class ServerApi {
private final UserService userService = AsyncServiceUtil.getAsyncServiceInstance(UserService.class);
@RouteMapping(value = "/login", method = RouteMethod.POST)
public Future<String> login(SysUser user) {
log.info("<------- login: {}", user.getUsername());
return userService.login(user);
}
private final CacheService cacheService = AsyncServiceUtil.getAsyncServiceInstance(CacheService.class);
@RouteMapping(value = "/parser", method = RouteMethod.GET, order = 4)
public Future<Void> parse(HttpServerResponse response, HttpServerRequest request, String url, String pwd) {
public Future<Void> parse(HttpServerResponse response, HttpServerRequest request, String pwd) {
Promise<Void> promise = Promise.promise();
if (url.contains(EcTool.SHARE_URL_PREFIX)) {
// 默认读取Url参数会被截断手动获取一下其他参数
url = EcTool.SHARE_URL_PREFIX + request.getParam("data");
}
try {
IPanTool.shareURLPrefixMatching(url, pwd).parse().onSuccess(resUrl -> {
ResponseUtil.redirect(response, resUrl, promise);
}).onFailure(t -> promise.fail(t.fillInStackTrace()));
} catch (Exception e) {
promise.fail(e);
}
String url = URLParamUtil.parserParams(request);
cacheService.getCachedByShareUrlAndPwd(url, pwd)
.onSuccess(res -> ResponseUtil.redirect(
response.putHeader("nfd-cache-hit", res.getCacheHit().toString())
.putHeader("nfd-cache-expires", res.getExpires()),
res.getDirectLink(), promise))
.onFailure(t -> promise.fail(t.fillInStackTrace()));
return promise.future();
}
@RouteMapping(value = "/json/parser", method = RouteMethod.GET, order = 3)
public Future<String> parseJson(HttpServerRequest request, String url, String pwd) {
if (url.contains(EcTool.SHARE_URL_PREFIX)) {
// 默认读取Url参数会被截断手动获取一下其他参数
url = EcTool.SHARE_URL_PREFIX + request.getParam("data");
}
return IPanTool.shareURLPrefixMatching(url, pwd).parse();
}
@RouteMapping(value = "/:type/:key", method = RouteMethod.GET, order = 1)
public void parseKey(HttpServerResponse response, String type, String key) {
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)).onFailure(t -> {
response.putHeader(CONTENT_TYPE, "text/html;charset=utf-8");
response.end(t.getMessage());
});
public Future<CacheLinkInfo> parseJson(HttpServerRequest request, String pwd) {
String url = URLParamUtil.parserParams(request);
return cacheService.getCachedByShareUrlAndPwd(url, pwd);
}
@RouteMapping(value = "/json/:type/:key", method = RouteMethod.GET, order = 2)
public Future<String> parseKeyJson(String type, String key) {
String code = "";
public Future<CacheLinkInfo> parseKeyJson(String type, String key) {
String pwd = "";
if (key.contains("@")) {
String[] keys = key.split("@");
key = keys[0];
code = keys[1];
pwd = keys[1];
}
return IPanTool.typeMatching(type, key, code).parse();
return cacheService.getCachedByShareKeyAndPwd(type, key, pwd);
}
@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 pwd = "";
if (key.contains("@")) {
String[] keys = key.split("@");
key = keys[0];
pwd = keys[1];
}
cacheService.getCachedByShareKeyAndPwd(type, key, pwd)
.onSuccess(res -> ResponseUtil.redirect(
response.putHeader("nfd-cache-hit", res.getCacheHit().toString())
.putHeader("nfd-cache-expires", res.getExpires()),
res.getDirectLink(), promise))
.onFailure(t -> promise.fail(t.fillInStackTrace()));
return promise.future();
}
}

View File

@@ -0,0 +1,68 @@
package cn.qaiu.lz.web.model;
import cn.qaiu.db.ddl.Length;
import cn.qaiu.db.ddl.Table;
import cn.qaiu.lz.common.ToJson;
import io.vertx.codegen.annotations.DataObject;
import io.vertx.core.json.JsonObject;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/9/11 16:06
*/
@Table(value = "api_statistics_info", keyFields = "share_key")
@Data
@DataObject
@NoArgsConstructor
public class ApiStatisticsInfo implements ToJson {
/**
* pan type 单独拿出来便于统计.
*/
@Length(varcharSize = 4)
private String panType;
/**
* 分享key type:key
*/
@Length(varcharSize = 4096)
private String shareKey;
/**
* 命中缓存次数
*/
private Integer cacheHitTotal;
/**
* api解析次数
*/
private Integer apiParserTotal;
/**
* 更新时间戳
*/
private Long updateTs;
// 使用 JsonObject 构造
public ApiStatisticsInfo(JsonObject json) {
if (json.containsKey("panType")) {
this.setPanType(json.getString("panType"));
}
if (json.containsKey("shareKey")) {
this.setShareKey(json.getString("shareKey"));
}
if (json.containsKey("cacheHitTotal")) {
this.setCacheHitTotal(json.getInteger("cacheHitTotal"));
}
if (json.containsKey("apiParserTotal")) {
this.setApiParserTotal(json.getInteger("apiParserTotal"));
}
if (json.containsKey("updateTs")) {
this.setUpdateTs(json.getLong("updateTs"));
}
}
}

View File

@@ -0,0 +1,70 @@
package cn.qaiu.lz.web.model;
import cn.qaiu.db.ddl.Length;
import cn.qaiu.db.ddl.Table;
import cn.qaiu.db.ddl.TableGenIgnore;
import cn.qaiu.lz.common.ToJson;
import io.vertx.codegen.annotations.DataObject;
import io.vertx.core.json.JsonObject;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2024/9/11 16:06
*/
@Table(value = "cache_link_info", keyFields = "share_key")
@Data
@DataObject
@NoArgsConstructor
public class CacheLinkInfo implements ToJson {
/**
* 缓存key: type:ShareKey; e.g. lz:xxxx
*/
@Length(varcharSize = 4096)
private String shareKey;
/**
* 解析后的直链
*/
@Length(varcharSize = 4096)
private String directLink;
/**
* 是否命中缓存
*/
@TableGenIgnore
private Boolean cacheHit;
/**
* 到期时间 yyyy-MM-dd hh:mm:ss
*/
@TableGenIgnore
private String expires;
/**
* 有效期
*/
private Long expiration;
// 使用 JsonObject 构造
public CacheLinkInfo(JsonObject json) {
if (json.containsKey("shareKey")) {
this.setShareKey(json.getString("shareKey"));
}
if (json.containsKey("directLink")) {
this.setDirectLink(json.getString("directLink"));
}
if (json.containsKey("expires")) {
this.setExpires(json.getString("expires"));
}
if (json.containsKey("expiration")) {
this.setExpiration(json.getLong("expiration"));
}
this.setCacheHit(json.getBoolean("cacheHit", false));
}
}

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 parserTotal;
Integer cacheTotal;
Integer total;
public StatisticsInfo(JsonObject jsonObject) {
this.parserTotal = jsonObject.getInteger("parserTotal");
this.cacheTotal = jsonObject.getInteger("cacheTotal");
this.total = jsonObject.getInteger("total");
}
}

View File

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

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