mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-16 12:23:03 +00:00
Compare commits
210 Commits
v0.1.9b9
...
0.1.7-rele
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
068efd0503 | ||
|
|
d6de9851df | ||
|
|
a9a048a9ce | ||
|
|
481e7b1240 | ||
|
|
e406e03dc2 | ||
|
|
acbf56adbc | ||
|
|
1cf82b8df9 | ||
|
|
a8405830f2 | ||
|
|
2673f23e64 | ||
|
|
cbaf6e9ac0 | ||
|
|
edb2d65638 | ||
|
|
f8f64fdcb1 | ||
|
|
ae86b85e51 | ||
|
|
c35bc22d02 | ||
|
|
a682d2d876 | ||
|
|
72f363d0e6 | ||
|
|
4b84ecd5fc | ||
|
|
632945d39a | ||
|
|
40bbd3f135 | ||
|
|
d822090c76 | ||
|
|
bd2ab30e7a | ||
|
|
f306170994 | ||
|
|
c8e12de768 | ||
|
|
d602c8a020 | ||
|
|
efe39eef8d | ||
|
|
d828c7ac8b | ||
|
|
37babc9a89 | ||
|
|
8be497a290 | ||
|
|
9ff1ea2ff2 | ||
|
|
fc64363b47 | ||
|
|
36ab2985b1 | ||
|
|
8ba95932ba | ||
|
|
b8aaa59437 | ||
|
|
1d6953d7c7 | ||
|
|
98c336ab84 | ||
|
|
d5e2b9cb3e | ||
|
|
7c4a243429 | ||
|
|
64e312f108 | ||
|
|
265d11e74c | ||
|
|
fcc0f71c23 | ||
|
|
4fd4bfb048 | ||
|
|
da07cfaaff | ||
|
|
7e31adcad9 | ||
|
|
378ae84816 | ||
|
|
a32abe8bfa | ||
|
|
f7ecf3e66c | ||
|
|
e9e503c685 | ||
|
|
85c697ea92 | ||
|
|
2018b20be5 | ||
|
|
e1c61fb51a | ||
|
|
cdeb63e225 | ||
|
|
7b6a79f83a | ||
|
|
dfd7e7a934 | ||
|
|
8deb932685 | ||
|
|
da063461be | ||
|
|
3234f643d5 | ||
|
|
332cc587a2 | ||
|
|
b30d01e154 | ||
|
|
79d8aa0868 | ||
|
|
a724d703ed | ||
|
|
18478b2a0b | ||
|
|
dde54ed9cc | ||
|
|
3c56a7183e | ||
|
|
38a3ae8a56 | ||
|
|
00b30331eb | ||
|
|
8397c24715 | ||
|
|
3ee113e81f | ||
|
|
cde641c8db | ||
|
|
30cd2f5850 | ||
|
|
71066ded98 | ||
|
|
8441954075 | ||
|
|
5e0568fa0e | ||
|
|
594e9af71d | ||
|
|
9bc7e9aa26 | ||
|
|
5805edf0a9 | ||
|
|
9614a36fc2 | ||
|
|
f45031c3f1 | ||
|
|
28e0542c8e | ||
|
|
b78a8300c6 | ||
|
|
ca06d5ae98 | ||
|
|
0c5e53b86e | ||
|
|
f45e6f3468 | ||
|
|
08ffec524e | ||
|
|
62dea67d69 | ||
|
|
8108a4dd3f | ||
|
|
56334fa4a2 | ||
|
|
093b337396 | ||
|
|
4bedf4486b | ||
|
|
b700493a6c | ||
|
|
44b1168bb5 | ||
|
|
a351364faf | ||
|
|
6bd075e17c | ||
|
|
1e77b20e44 | ||
|
|
0c2165dbf3 | ||
|
|
7595345e7b | ||
|
|
d13fe1fc21 | ||
|
|
f0269b1687 | ||
|
|
257147dd75 | ||
|
|
6fe824e705 | ||
|
|
f482982a25 | ||
|
|
e3ec81bd9b | ||
|
|
4771a5b5ce | ||
|
|
1292440f95 | ||
|
|
c2052ddbd8 | ||
|
|
f65dfacf4b | ||
|
|
27c2c1ab29 | ||
|
|
b4471dd9c3 | ||
|
|
69d8b645a4 | ||
|
|
67dc452819 | ||
|
|
60df94ce86 | ||
|
|
d6575d5b81 | ||
|
|
2ffa31f655 | ||
|
|
1ae2c89d4f | ||
|
|
2de5790b2a | ||
|
|
e5b892c9d1 | ||
|
|
1ab5059a41 | ||
|
|
1d10e7ab50 | ||
|
|
0477cc4815 | ||
|
|
9d3fce3dee | ||
|
|
e07d427f85 | ||
|
|
998c6e4627 | ||
|
|
e284dfca63 | ||
|
|
cccacde288 | ||
|
|
ee99599d4f | ||
|
|
574d5861f5 | ||
|
|
c3173b7f93 | ||
|
|
6dc647d0be | ||
|
|
ba5ad53529 | ||
|
|
3c5a0d9c9d | ||
|
|
bb1c296872 | ||
|
|
b36e3628bd | ||
|
|
837a265fd8 | ||
|
|
208c430875 | ||
|
|
639f8df534 | ||
|
|
4f5e23ebb6 | ||
|
|
d86c67314c | ||
|
|
d1ba790d05 | ||
|
|
63b1cad839 | ||
|
|
610c6c8d2a | ||
|
|
7f50ee5217 | ||
|
|
5883c7c174 | ||
|
|
0b73738e39 | ||
|
|
4e11b61fdb | ||
|
|
853246d363 | ||
|
|
e1d880318a | ||
|
|
533f6f8596 | ||
|
|
68b7c57384 | ||
|
|
089dc5713b | ||
|
|
2596e58943 | ||
|
|
7d8b39ad92 | ||
|
|
9419c7e0ca | ||
|
|
f492e3c031 | ||
|
|
8973a0e4b2 | ||
|
|
fff8cfe460 | ||
|
|
3b3dd0634f | ||
|
|
e2945b8f34 | ||
|
|
5343c68603 | ||
|
|
daadb6e9cc | ||
|
|
b8057135a9 | ||
|
|
e9ed8715af | ||
|
|
e941d76507 | ||
|
|
f2280c8e86 | ||
|
|
b189614d29 | ||
|
|
ea9d1067ed | ||
|
|
70fe0c23ae | ||
|
|
03c4c8c581 | ||
|
|
9a3baebec9 | ||
|
|
3574b58d21 | ||
|
|
74e95d0f12 | ||
|
|
a14db13485 | ||
|
|
37f5f2a30d | ||
|
|
e7ebb7c8c3 | ||
|
|
11decadb4c | ||
|
|
34c6d26581 | ||
|
|
2155c3e0c0 | ||
|
|
0bc23e76bf | ||
|
|
095040efce | ||
|
|
d98751b0e3 | ||
|
|
3e31e97c35 | ||
|
|
eebce94e88 | ||
|
|
2ca7a78920 | ||
|
|
2c243650f6 | ||
|
|
a257cd2f69 | ||
|
|
d0d5c96b9e | ||
|
|
00af483239 | ||
|
|
25534452ae | ||
|
|
804266d5af | ||
|
|
7fa2ea59a5 | ||
|
|
c9e714418e | ||
|
|
b3beb61337 | ||
|
|
2270a05631 | ||
|
|
5fb3d2a2d6 | ||
|
|
34f2670700 | ||
|
|
d4586b342e | ||
|
|
1651f1ea93 | ||
|
|
11a054dc2c | ||
|
|
fc233bcea3 | ||
|
|
e7e3888b13 | ||
|
|
91845cc08c | ||
|
|
94cde1e953 | ||
|
|
678866f654 | ||
|
|
f7ccc3c3e3 | ||
|
|
1799763f7f | ||
|
|
5ba38f4f1d | ||
|
|
07d8ed19b0 | ||
|
|
15b6468333 | ||
|
|
2457ded158 | ||
|
|
54a1a6b7f4 | ||
|
|
167149f410 | ||
|
|
2c7a17c3b0 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -38,3 +38,4 @@ gradlew
|
||||
gradlew.bat
|
||||
unused.txt
|
||||
/web-service/src/main/generated/
|
||||
/db
|
||||
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"java.compile.nullAnalysis.mode": "automatic"
|
||||
}
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 qaiu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
264
README.md
264
README.md
@@ -1,73 +1,239 @@
|
||||
# netdisk-fast-download
|
||||
# 网盘快速下载器--直链解析
|
||||
云盘解析服务 (nfd云解析)
|
||||
预览地址 https://lz.qaiu.top
|
||||
**注意: 请不要过度依赖lz.qaiu.top预览地址服务,建议本地搭建或者云服务器自行搭建。
|
||||
解析次数过多IP会被部分网盘厂商限制,不推荐做公共解析。**
|
||||
|
||||
[](https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml)
|
||||
[](https://www.oracle.com/cn/java/technologies/downloads/)
|
||||
[](https://vertx-china.github.io/)
|
||||
[](https://github.com/qaiu/netdisk-fast-download/releases/tag/0.1.6-releases)
|
||||
|
||||
## 项目介绍
|
||||
网盘直链解析工具能把网盘分享下载链接转化为直链,已支持蓝奏云/蓝奏云优享/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘/Cloudreve等,支持加密分享。
|
||||
|
||||
|
||||
*重要声明:本项目仅供学习参考;请不要将此项目用于任何商业用途,否则可能带来严重的后果。*
|
||||
|
||||
## 网盘支持情况:
|
||||
` 网盘名称(网盘标识): `
|
||||
- 蓝奏云 (lz)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [x] 直链解析
|
||||
- 奶牛快传 (cow)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [x] 直链解析
|
||||
- 移动云空间 (ec)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [x] 直链解析
|
||||
- UC网盘 (uc)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [x] 直链解析
|
||||
- 夸克网盘 (qk)
|
||||
- TODO
|
||||
> 20230905 奶牛云直链做了防盗链,需加入请求头:Referer: https://cowtransfer.com/
|
||||
> 20230824 123云盘解析大文件(>100MB)失效,需要登录
|
||||
> 20230722 UC网盘解析失效,需要登录
|
||||
|
||||
技术栈:
|
||||
Jdk17+Vert.x4.4.1+Jsoup
|
||||
Core模块集成Vert.x实现类spring的注解式路由API
|
||||
`网盘名称(网盘标识):`
|
||||
|
||||
- [蓝奏云 (lz)](https://pc.woozooo.com/)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [X] 直链解析
|
||||
- [蓝奏云优享 (iz)](https://www.ilanzou.com/)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [X] 直链解析
|
||||
- [奶牛快传 (cow)](https://cowtransfer.com/)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [X] 直链解析
|
||||
- [移动云空间 (ec)](https://www.ecpan.cn/web)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [X] 直链解析
|
||||
- [小飞机网盘 (fj)](https://www.feijipan.com/)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [X] 直链解析
|
||||
- [亿方云 (fc)](https://www.fangcloud.com/)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [X] 直链解析
|
||||
- [123云盘 (ye)](https://www.123pan.com/)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [X] 直链解析
|
||||
- [文叔叔 (ws)](https://www.wenshushu.cn/)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [X] 直链解析
|
||||
- [Cloudreve自建网盘 (ce)](https://github.com/cloudreve/Cloudreve)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [X] 直链解析
|
||||
- [QQ邮箱 (qq) 暂不可用-存在cookie问题](https://wx.mail.qq.com/)
|
||||
- [ ] 登录, 上传, 下载, 分享
|
||||
- [X] 直链解析(用户无法直接使用直链)
|
||||
- [夸克网盘 (qk) 寄了](https://pan.quark.cn/)
|
||||
- [UC网盘 (uc) 寄了](https://fast.uc.cn/)
|
||||
|
||||
**TODO:**
|
||||
- 登录接口, 文件上传/下载/分享后端接口
|
||||
- 短地址服务
|
||||
- 前端界面(建设中...)
|
||||
|
||||
### API接口说明
|
||||
your_host指的是您的域名或者IP,实际使用时替换为实际域名或者IP。
|
||||
解析方式分为两种类型直接跳转下载链接和获取下载链接(JSON),每一种都提供了两种接口形式parser和网盘标志/分享key拼接的短地址(标志短链),所有规则参考示例。
|
||||
- 通用接口: `/parser?url=分享链接`,加密分享需要加上参数pwd=密码;
|
||||
- 标志短链: `/网盘标识/分享key` 在分享Key后面加上@密码;
|
||||
- 直链JSON: `通用接口`和`标志短链`前加上`/json` 加密分享的密码规则同上;
|
||||
- 网盘标识参考上面网盘支持情况
|
||||
- 括号内是可选内容: 表示当带有分享密码时需要加上密码参数
|
||||
- 移动云空间,小飞机网盘的加密分享的密码可以忽略
|
||||
|
||||
规则示例:
|
||||
```
|
||||
|
||||
API接口
|
||||
```shell
|
||||
(括号内表示可选内容)
|
||||
1. 解析并自动302跳转 :
|
||||
http(s)://you_host/parser?url=分享链接
|
||||
http(s)://you_host/网盘标识/分享id(#分享密码)
|
||||
http://your_host/parser?url=分享链接(&pwd=xxx)
|
||||
http://your_host/网盘标识/分享key(@分享密码)
|
||||
2. 获取解析后的直链--JSON格式
|
||||
http(s)://you_host/json/网盘标识/分享id(#分享密码)
|
||||
http://your_host/json/parser?url=分享链接(&pwd=xxx)
|
||||
http://your_host/json/网盘标识/分享key(@分享密码)
|
||||
3. 需要特殊处理的网盘分享:
|
||||
1. 移动云空间(ec)使用parser?url= 解析时因为分享链接比较特殊(链接带有参数且含有#符号)所以要么对#进行转义%23要么直接去掉# 或者URL直接是主机名+'/'跟一个data参数
|
||||
比如 http://your_host/parser?url=https://www.ecpan.cn/web//yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
|
||||
http://your_host/parser?url=https://www.ecpan.cn/web/%23/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
|
||||
http://your_host/parser?url=https://www.ecpan.cn/&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
|
||||
|
||||
2. Cloudreve自建网盘解析规则:
|
||||
1. 标志短链: 根据网盘使用https和http选择 http://your_host/ce/https_网盘域名_s_wDz5TK 或 http://your_host/ce/http_网盘域名_s_wDz5TK
|
||||
网盘域名指的是Cloudreve搭建网盘的主域名比如pan.huang1111.cn,如果存在子路径需要将/替换为_,是否存在子路径看分享链接格式是否是://网盘域名/子路径/s/xxx,一般不存在子路径:网盘域名/s/xxx,
|
||||
比如: http://127.0.0.1:6400/ce/https_pan.huang1111.cn_s_wDz5TK
|
||||
2. parser接口 -> http://your_host/parser?url=分享链接(&pwd=xxx)
|
||||
比如: http://127.0.0.1:6400/parser?url=https://pan.huang1111.cn/s/wDz5TK
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
示例:
|
||||
json返回数据格式示例:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"success": true,
|
||||
"count": 0,
|
||||
"data": "https://下载链接",
|
||||
"timestamp": 1690733953927
|
||||
}
|
||||
```
|
||||
// 解析并重定向到直链
|
||||
###
|
||||
|
||||
IDEA HttpClient示例:
|
||||
|
||||
```
|
||||
# 解析并重定向到直链
|
||||
### 蓝奏云普通分享
|
||||
# @no-redirect
|
||||
GET http://127.0.0.1:6400/parser?url=https://lanzoux.com/ia2cntg
|
||||
###
|
||||
### 奶牛快传普通分享
|
||||
# @no-redirect
|
||||
GET http://127.0.0.1:6400/parser?url=https://cowtransfer.com/s/9a644fe3e3a748
|
||||
### 360亿方云加密分享
|
||||
# @no-redirect
|
||||
GET http://127.0.0.1:6400/parser?url=https://v2.fangcloud.com/sharing/e5079007dc31226096628870c7&pwd=QAIU
|
||||
|
||||
// Rest请求(只提供共享文件Id):
|
||||
###
|
||||
# Rest请求自动302跳转(只提供共享文件Id):
|
||||
### 蓝奏云普通分享
|
||||
# @no-redirect
|
||||
GET http://127.0.0.1:6400/lz/ia2cntg
|
||||
### 奶牛快传普通分享
|
||||
# @no-redirect
|
||||
GET http://127.0.0.1:6400/cow/9a644fe3e3a748
|
||||
### 360亿方云加密分享
|
||||
GET http://127.0.0.1:6400/json/fc/e5079007dc31226096628870c7@QAIU
|
||||
|
||||
// 解析返回json直链
|
||||
###
|
||||
GET http://127.0.0.1:6400/json/cow/9a644fe3e3a748
|
||||
###
|
||||
|
||||
# 解析返回json直链
|
||||
### 蓝奏云普通分享
|
||||
GET http://127.0.0.1:6400/json/lz/ia2cntg
|
||||
### 奶牛快传普通分享
|
||||
GET http://127.0.0.1:6400/json/cow/9a644fe3e3a748
|
||||
### 360亿方云加密分享
|
||||
GET http://127.0.0.1:6400/json/fc/e5079007dc31226096628870c7@QAIU
|
||||
|
||||
|
||||
```
|
||||
|
||||
TODO:
|
||||
解析蓝奏云加密链接
|
||||
|
||||
|
||||
# 网盘对比
|
||||
|
||||
| 网盘名称 | 可直接下载分享 | 加密分享 | 初始网盘空间 | 单文件大小限制 | 登录接口 |
|
||||
|-------|-------------|----------|----------|----------------|------|
|
||||
| 蓝奏云 | √ | √ | 不限空间 | 100M | TODO |
|
||||
| 奶牛快传 | √ | X | 10G | 不限大小 | TODO |
|
||||
| 移动云空间 | √ | √(密码可忽略) | 5G(个人) | 不限大小 | TODO |
|
||||
| UC网盘 | √ | √ | 10G | 不限大小 | TODO |
|
||||
| 夸克网盘 | √(>10M需要登录) | √ | 10G(20G) | 不限大小(>10M需要登录) | X |
|
||||
|
||||
| 网盘名称 | 可直接下载分享 | 加密分享 | 初始网盘空间 | 单文件大小限制 | 登录接口 |
|
||||
|------------|------------------------|----------|-----------|-----------------|------|
|
||||
| 蓝奏云 | √ | √ | 不限空间 | 100M | TODO |
|
||||
| 奶牛快传 | √ | X | 10G | 不限大小 | TODO |
|
||||
| 移动云空间 | √ | √(密码可忽略) | 5G(个人) | 不限大小 | TODO |
|
||||
| UC网盘 | 需要登录 | √ | 10G | 不限大小 | TODO |
|
||||
| 小飞机网盘 | √ | √(密码可忽略) | 10G | 不限大小 | TODO |
|
||||
| 360亿方云 | √(试用账号有时间限制企业版需要599续费) | √(密码可忽略) | 100G(须实名) | 不限大小 | TODO |
|
||||
| 123云盘 | √ | √ | 2T | 100G(>100M需要登录) | TODO |
|
||||
| 文叔叔(TODO) | √(注意有时间限制) | √ | 10G | 5GB | TODO |
|
||||
| 夸克网盘(TODO) | 需要登录 | √ | 10G | 不限大小 | TODO |
|
||||
|
||||
# 打包部署
|
||||
|
||||
## JDK下载(lz.qaiu.top提供直链云解析服务)
|
||||
- [阿里jdk17(Dragonwell17-windows-x86)](https://lz.qaiu.top/ec/e957acef36ce89e1053979672a90d219n)
|
||||
- [阿里jdk17(Dragonwell17-linux-x86)](https://lz.qaiu.top/ec/6ebc9f2e0bbd53b4c4d5b11013f40a80NHvcYU)
|
||||
- [阿里jdk17(Dragonwell17-linux-aarch64)](https://lz.qaiu.top/ec/d14c2d06296f61b52a876b525265e0f8tzxTc5)
|
||||
- [解析有效性测试-移动云空间-阿里jdk17-linux-x86](https://lz.qaiu.top/json/ec/6ebc9f2e0bbd53b4c4d5b11013f40a80NHvcYU)
|
||||
|
||||
## 开发和打包
|
||||
|
||||
```shell
|
||||
# 环境要求: Jdk17 + maven;
|
||||
mvn clean
|
||||
mvn package
|
||||
|
||||
```
|
||||
打包好的文件位于 web-service/target/netdisk-fast-download-bin.zip
|
||||
## Linux服务部署
|
||||
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
|
||||
```shell
|
||||
cd ~
|
||||
wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/0.1.7/netdisk-fast-download.zip
|
||||
unzip netdisk-fast-download-bin.zip
|
||||
cd netdisk-fast-download
|
||||
bash service-install.sh
|
||||
```
|
||||
服务相关命令:
|
||||
|
||||
查看服务状态
|
||||
`systemctl status netdisk-fast-download.service`
|
||||
|
||||
启动服务
|
||||
`systemctl start netdisk-fast-download.service`
|
||||
|
||||
重启服务
|
||||
`systemctl restart netdisk-fast-download.service`
|
||||
|
||||
停止服务
|
||||
`systemctl stop netdisk-fast-download.service`
|
||||
|
||||
开机启动服务
|
||||
`systemctl enable netdisk-fast-download.servic`
|
||||
|
||||
停止开机启动
|
||||
`systemctl disable netdisk-fast-download.servic`
|
||||
|
||||
## Windows服务部署
|
||||
1. 下载并解压releases版本netdisk-fast-download-bin.zip
|
||||
2. 进入netdisk-fast-download下的bin目录
|
||||
3. 使用管理员权限运行nfd-service-install.bat
|
||||
如果不想使用服务运行可以直接运行run.bat
|
||||
> 注意: 如果jdk环境变量的java版本不是17请修改nfd-service-template.xml中的java命令的路径改为实际路径
|
||||
|
||||
## 0.1.8 开发计划
|
||||
- Docker部署
|
||||
- 联想乐云解析支持
|
||||
- CLoudreve解析解析
|
||||
- 直链缓存
|
||||
- 日志优化
|
||||
|
||||
|
||||
**技术栈:**
|
||||
Jdk17+Vert.x4.4.1
|
||||
Core模块集成Vert.x实现类似spring的注解式路由API
|
||||
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#qaiu/netdisk-fast-download&Date)
|
||||
|
||||
|
||||
## 支持该项目
|
||||
本项目长期维护如果觉得有帮助, 可以请作者喝杯咖啡, 感谢支持
|
||||

|
||||
|
||||
[手机端支付宝打赏跳转链接](https://qr.alipay.com/fkx01882dnoxxtjenhlxt53)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ Wants=network-online.target
|
||||
[Service]
|
||||
Type=simple
|
||||
# User=USER
|
||||
ExecStart=/usr/bin/java -server -Xmx128m -jar /root/java/netdisk-fast-download/netdisk-fast-download-0.0.1.jar
|
||||
# 需要JDK17及以上版本 注意修改为自己的路径
|
||||
ExecStart=/root/java/jdk-17.0.2/bin/java -server -Xmx128m -jar /root/java/netdisk-fast-download/netdisk-fast-download.jar
|
||||
ExecStop=/bin/kill -s QUIT $MAINPID
|
||||
Restart=always
|
||||
StandOutput=syslog
|
||||
|
||||
27
bin/nfd-service-install.bat
Normal file
27
bin/nfd-service-install.bat
Normal file
@@ -0,0 +1,27 @@
|
||||
::
|
||||
:: generate service xml file
|
||||
::
|
||||
|
||||
@echo off
|
||||
pushd %~dp0
|
||||
set MY_DIR=%~dp0
|
||||
set MY_DIR=%MY_DIR:~0,-1%
|
||||
|
||||
for /f "delims=X" %%i in ('dir /b %MY_DIR%\netdisk-fast-download.jar') do (
|
||||
set LAUNCH_JAR=%MY_DIR%\%%i
|
||||
)
|
||||
|
||||
(for /f "delims=" %%a in (nfd-service-template.xml) do (
|
||||
set "str=%%a"
|
||||
setlocal enabledelayedexpansion
|
||||
set "str=!str:${dd}=%MY_DIR%!"
|
||||
set "str=!str:${jar}=%LAUNCH_JAR%!"
|
||||
echo,!str!
|
||||
endlocal
|
||||
))>"nfd-service.xml"
|
||||
|
||||
|
||||
sc delete netdisk-fast-download
|
||||
nfd-service install
|
||||
sc start netdisk-fast-download
|
||||
pause
|
||||
12
bin/nfd-service-template.xml
Normal file
12
bin/nfd-service-template.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<service>
|
||||
<id>netdisk-fast-download</id>
|
||||
<name>netdisk-fast-download</name>
|
||||
<description>netdisk fast download service</description>
|
||||
<executable>java</executable>
|
||||
<arguments> -server -Xmx128m -jar ${jar} </arguments>
|
||||
<logpath>${dd}\logs</logpath>
|
||||
<log mode="roll-by-time">
|
||||
<pattern>yyyyMMdd</pattern>
|
||||
</log>
|
||||
</service>
|
||||
BIN
bin/nfd-service.exe
Normal file
BIN
bin/nfd-service.exe
Normal file
Binary file not shown.
@@ -1,5 +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 set LAUNCH_JAR=%LIB_DIR%\%%i
|
||||
for /f "delims=X" %%i in ('dir /b %LIB_DIR%\netdisk-fast-download.jar') do (
|
||||
set LAUNCH_JAR=%LIB_DIR%%%i
|
||||
)
|
||||
|
||||
set "JAVA_HOME=D:\App\dragonwell-17.0.3.0.3+7-GA"
|
||||
"%JAVA_HOME%\bin\java.exe" -Xmx512M -Dfile.encoding=utf8 -jar %LAUNCH_JAR% %*
|
||||
|
||||
pause
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
# set -x
|
||||
LAUNCH_JAR="netdisk-fast-download-*.jar"
|
||||
LAUNCH_JAR="netdisk-fast-download.jar"
|
||||
nohup java -Xmx512M -jar "$LAUNCH_JAR" "$@" >startup.log 2>&1 &
|
||||
tail -f startup.log
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>netdisk-fast-download</artifactId>
|
||||
<groupId>cn.qaiu</groupId>
|
||||
<version>0.1.3</version>
|
||||
<version>0.1.7</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<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>
|
||||
@@ -30,7 +30,7 @@
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>2.1.214</version>
|
||||
<version>2.2.220</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/com.zaxxer/HikariCP -->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package cn.qaiu;
|
||||
|
||||
public class StartH2DatabaseServer {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello world!");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,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");
|
||||
@@ -157,8 +158,8 @@ public class CreateTable {
|
||||
return sql.substring(0, sql.length() - 1) + ");\r\n";
|
||||
}
|
||||
|
||||
public static void createTable(JDBCPool pool, String tableClassPath) {
|
||||
Set<Class<?>> tableClassList = ReflectionUtil.getReflections(tableClassPath).getTypesAnnotatedWith(Table.class);
|
||||
public static void createTable(JDBCPool pool) {
|
||||
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);
|
||||
|
||||
@@ -92,7 +92,7 @@ public class JDBCPoolInit {
|
||||
private void poolInitExecute(Promise<String> promise) {
|
||||
// 初始化连接池
|
||||
pool = JDBCPool.pool(vertx, dbConfig);
|
||||
CreateTable.createTable(pool, dbConfig.getString("tableClassPath"));
|
||||
CreateTable.createTable(pool);
|
||||
promise.complete("init jdbc pool success");
|
||||
|
||||
}
|
||||
|
||||
17
core/pom.xml
17
core/pom.xml
@@ -5,21 +5,14 @@
|
||||
<parent>
|
||||
<artifactId>netdisk-fast-download</artifactId>
|
||||
<groupId>cn.qaiu</groupId>
|
||||
<version>0.1.3</version>
|
||||
<version>0.1.7</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>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -39,7 +32,7 @@
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.4.6</version>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
@@ -90,6 +83,12 @@
|
||||
<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>
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package cn.qaiu.vx.core;
|
||||
|
||||
import cn.qaiu.vx.core.util.ConfigConstant;
|
||||
import cn.qaiu.vx.core.util.ConfigUtil;
|
||||
import cn.qaiu.vx.core.util.VertxHolder;
|
||||
import cn.qaiu.vx.core.verticle.ReverseProxyVerticle;
|
||||
import cn.qaiu.vx.core.verticle.RouterVerticle;
|
||||
import cn.qaiu.vx.core.verticle.ServiceVerticle;
|
||||
import io.vertx.core.*;
|
||||
import io.vertx.core.impl.launcher.commands.VersionCommand;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.core.shareddata.LocalMap;
|
||||
import org.slf4j.Logger;
|
||||
@@ -17,6 +17,8 @@ import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import static cn.qaiu.vx.core.util.ConfigConstant.*;
|
||||
|
||||
/**
|
||||
* vertx启动类 需要在主启动类完成回调
|
||||
* <br>Create date 2021-05-07 10:26:54
|
||||
@@ -42,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;
|
||||
@@ -63,7 +70,7 @@ public final class Deploy {
|
||||
var activeMode = conf.getString("active");
|
||||
if ("dev".equals(activeMode)) {
|
||||
LOGGER.info("---------------> development environment <--------------\n");
|
||||
System.setProperty("vertxweb.environment","dev");
|
||||
System.setProperty("vertxweb.environment", "dev");
|
||||
} else {
|
||||
LOGGER.info("---------------> Production environment <--------------\n");
|
||||
}
|
||||
@@ -80,7 +87,7 @@ public final class Deploy {
|
||||
var calendar = Calendar.getInstance();
|
||||
calendar.setTime(new Date());
|
||||
var year = calendar.get(Calendar.YEAR);
|
||||
var logoTemplete = """
|
||||
var logoTemplate = """
|
||||
|
||||
Web Server powered by:\s
|
||||
____ ____ _ _ _ \s
|
||||
@@ -93,9 +100,9 @@ public final class Deploy {
|
||||
|
||||
""";
|
||||
|
||||
System.out.printf(logoTemplete,
|
||||
System.out.printf(logoTemplate,
|
||||
conf.getString("version_app"),
|
||||
conf.getString("version_vertx"),
|
||||
VersionCommand.getVersion(),
|
||||
conf.getString("copyright"),
|
||||
year
|
||||
);
|
||||
@@ -107,22 +114,30 @@ public final class Deploy {
|
||||
private void deployVerticle() {
|
||||
tempVertx.close();
|
||||
LOGGER.info("配置读取成功");
|
||||
customConfig = globalConfig.getJsonObject(ConfigConstant.CUSTOM);
|
||||
customConfig = globalConfig.getJsonObject(CUSTOM);
|
||||
|
||||
var vertxOptions = new VertxOptions(globalConfig.getJsonObject(ConfigConstant.VERTX));
|
||||
JsonObject vertxConfig = globalConfig.getJsonObject(VERTX);
|
||||
Integer vertxConfigELPS = vertxConfig.getInteger(EVENT_LOOP_POOL_SIZE);
|
||||
var vertxOptions = vertxConfigELPS == 0 ?
|
||||
new VertxOptions() : new VertxOptions(vertxConfig);
|
||||
|
||||
LOGGER.info("vertxConfigEventLoopPoolSize: {}, eventLoopPoolSize: {}, workerPoolSize: {}", vertxConfigELPS,
|
||||
vertxOptions.getEventLoopPoolSize(),
|
||||
vertxOptions.getWorkerPoolSize());
|
||||
var vertx = Vertx.vertx(vertxOptions);
|
||||
VertxHolder.init(vertx);
|
||||
//配置保存在共享数据中
|
||||
var sharedData = vertx.sharedData();
|
||||
LocalMap<String, Object> localMap = sharedData.getLocalMap(ConfigConstant.LOCAL);
|
||||
localMap.put(ConfigConstant.GLOBAL_CONFIG, globalConfig);
|
||||
localMap.put(ConfigConstant.CUSTOM_CONFIG, customConfig);
|
||||
localMap.put(ConfigConstant.SERVER, globalConfig.getJsonObject(ConfigConstant.SERVER));
|
||||
LocalMap<String, Object> localMap = sharedData.getLocalMap(LOCAL);
|
||||
localMap.put(GLOBAL_CONFIG, globalConfig);
|
||||
localMap.put(CUSTOM_CONFIG, customConfig);
|
||||
localMap.put(SERVER, globalConfig.getJsonObject(SERVER));
|
||||
var future0 = vertx.createSharedWorkerExecutor("other-handle").executeBlocking(bch -> {
|
||||
handle.handle(globalConfig);
|
||||
bch.complete("other handle complete");
|
||||
});
|
||||
|
||||
// 部署 路由、异步service、反向代理 服务
|
||||
var future1 = vertx.deployVerticle(RouterVerticle.class, getWorkDeploymentOptions("Router"));
|
||||
var future2 = vertx.deployVerticle(ServiceVerticle.class, getWorkDeploymentOptions("Service"));
|
||||
var future3 = vertx.deployVerticle(ReverseProxyVerticle.class, getWorkDeploymentOptions("proxy"));
|
||||
@@ -160,7 +175,7 @@ public final class Deploy {
|
||||
* @return Deployment Options
|
||||
*/
|
||||
private DeploymentOptions getWorkDeploymentOptions(String name) {
|
||||
return getWorkDeploymentOptions(name, customConfig.getInteger(ConfigConstant.ASYNC_SERVICE_INSTANCES));
|
||||
return getWorkDeploymentOptions(name, customConfig.getInteger(ASYNC_SERVICE_INSTANCES));
|
||||
}
|
||||
|
||||
private DeploymentOptions getWorkDeploymentOptions(String name, int ins) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.qaiu.vx.core.annotaions;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Documented
|
||||
@Inherited
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface HandleSortFilter {
|
||||
/**
|
||||
* 注册顺序,数字越大越先注册<br>
|
||||
* 值<0时会过滤掉该处理器
|
||||
*/
|
||||
int value() default 0;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.qaiu.vx.core.annotaions;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 拦截器配置注解
|
||||
* 正则匹配拦截途径
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
*/
|
||||
@Documented
|
||||
@Inherited
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface InterceptorConfig {
|
||||
|
||||
String pattern() default "";
|
||||
|
||||
/**
|
||||
* 注册顺序,数字越大越先注册
|
||||
*/
|
||||
int order() default 0;
|
||||
}
|
||||
@@ -1,9 +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 static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.qaiu.vx.core.util.ResponseUtil.*;
|
||||
|
||||
/**
|
||||
* 统一响应处理
|
||||
@@ -13,22 +20,43 @@ import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
|
||||
*/
|
||||
public interface BaseHttpApi {
|
||||
|
||||
default void fireJsonResponse(RoutingContext ctx, JsonObject jsonResult) {
|
||||
ctx.response().putHeader(CONTENT_TYPE, "application/json; charset=utf-8")
|
||||
.setStatusCode(200)
|
||||
.end(jsonResult.encode());
|
||||
// 需要扫描注册的Router路径
|
||||
Reflections reflections = ReflectionUtil.getReflections();
|
||||
|
||||
default void doFireJsonObjectResponse(RoutingContext ctx, JsonObject jsonObject) {
|
||||
if (!ctx.response().ended()) {
|
||||
fireJsonObjectResponse(ctx, jsonObject);
|
||||
}
|
||||
handleAfterInterceptor(ctx, jsonObject);
|
||||
}
|
||||
|
||||
default <T> void fireJsonResponse(RoutingContext ctx, T jsonResult) {
|
||||
JsonObject jsonObject = JsonObject.mapFrom(jsonResult);
|
||||
fireJsonResponse(ctx, jsonObject);
|
||||
|
||||
default <T> void doFireJsonResultResponse(RoutingContext ctx, JsonResult<T> jsonResult) {
|
||||
if (!ctx.response().ended()) {
|
||||
fireJsonResultResponse(ctx, jsonResult);
|
||||
}
|
||||
handleAfterInterceptor(ctx, jsonResult.toJsonObject());
|
||||
}
|
||||
|
||||
default void fireTextResponse(RoutingContext ctx, String text) {
|
||||
ctx.response().putHeader("content-type", "text/html; charset=utf-8").end(text);
|
||||
|
||||
default Set<AfterInterceptor> getAfterInterceptor() {
|
||||
|
||||
Set<Class<? extends AfterInterceptor>> afterInterceptorClassSet =
|
||||
reflections.getSubTypesOf(AfterInterceptor.class);
|
||||
if (afterInterceptorClassSet == null) {
|
||||
return null;
|
||||
}
|
||||
return CommonUtil.sortClassSet(afterInterceptorClassSet);
|
||||
}
|
||||
|
||||
default void sendError(int statusCode, RoutingContext ctx) {
|
||||
ctx.response().setStatusCode(statusCode).end();
|
||||
default void handleAfterInterceptor(RoutingContext ctx, JsonObject jsonObject) {
|
||||
Set<AfterInterceptor> afterInterceptor = getAfterInterceptor();
|
||||
if (afterInterceptor != null) {
|
||||
afterInterceptor.forEach(ai -> ai.handle(ctx, jsonObject));
|
||||
}
|
||||
if (!ctx.response().ended()) {
|
||||
fireTextResponse(ctx, "handleAfterInterceptor: response not end");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@ 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.enums.MIMEType;
|
||||
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;
|
||||
@@ -19,14 +20,12 @@ 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.*;
|
||||
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;
|
||||
|
||||
@@ -37,6 +36,7 @@ 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.*;
|
||||
|
||||
/**
|
||||
@@ -56,15 +56,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 routerScanAddress, String gatewayPrefix) {
|
||||
Objects.requireNonNull(routerScanAddress, "The router package address scan is empty.");
|
||||
public RouterHandlerFactory(String gatewayPrefix) {
|
||||
Objects.requireNonNull(gatewayPrefix, "The gateway prefix is empty.");
|
||||
reflections = ReflectionUtil.getReflections(routerScanAddress);
|
||||
this.gatewayPrefix = gatewayPrefix;
|
||||
}
|
||||
|
||||
@@ -72,22 +68,41 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
||||
* 开始扫描并注册handler
|
||||
*/
|
||||
public Router createRouter() {
|
||||
Router router = Router.router(VertxHolder.getVertxInstance());
|
||||
router.route().handler(ctx -> {
|
||||
// 主路由
|
||||
Router mainRouter = Router.router(VertxHolder.getVertxInstance());
|
||||
|
||||
// 静态资源
|
||||
String path = SharedDataUtil.getJsonConfig("server")
|
||||
.getString("staticResourcePath");
|
||||
if (!StringUtils.isEmpty(path)) {
|
||||
// 静态资源
|
||||
mainRouter.route("/*").handler(StaticHandler
|
||||
.create(path)
|
||||
.setCachingEnabled(true)
|
||||
.setDefaultContentEncoding("UTF-8"));
|
||||
}
|
||||
|
||||
|
||||
mainRouter.route().handler(ctx -> {
|
||||
LOGGER.debug("The HTTP service request address information ===>path:{}, uri:{}, method:{}",
|
||||
ctx.request().path(), ctx.request().absoluteURI(), ctx.request().method());
|
||||
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, OPTIONS, PUT, DELETE, HEAD");
|
||||
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_HEADERS, "X-PINGOTHER, Origin,Content-Type, Accept, X-Requested-With, Dev, Authorization, Version, Token");
|
||||
ctx.response().headers().add(ACCESS_CONTROL_ALLOW_HEADERS, "X-PINGOTHER, Origin,Content-Type, Accept, " +
|
||||
"X-Requested-With, Dev, Authorization, Version, Token");
|
||||
ctx.response().headers().add(ACCESS_CONTROL_MAX_AGE, "1728000");
|
||||
ctx.next();
|
||||
});
|
||||
// 添加跨域的方法
|
||||
router.route().handler(CorsHandler.create().addRelativeOrigin(".*").allowCredentials(true).allowedMethods(httpMethods));
|
||||
mainRouter.route().handler(CorsHandler.create().addRelativeOrigin(".*").allowCredentials(true).allowedMethods(httpMethods));
|
||||
|
||||
// 配置文件上传路径
|
||||
router.route().handler(BodyHandler.create().setUploadsDirectory("uploads"));
|
||||
mainRouter.route().handler(BodyHandler.create().setUploadsDirectory("uploads"));
|
||||
|
||||
// 拦截器
|
||||
Set<Handler<RoutingContext>> interceptorSet = getInterceptorSet();
|
||||
Route route0 = mainRouter.route("/*");
|
||||
interceptorSet.forEach(route0::handler);
|
||||
|
||||
try {
|
||||
Set<Class<?>> handlers = reflections.getTypesAnnotatedWith(RouteHandler.class);
|
||||
@@ -101,7 +116,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
||||
for (Class<?> handler : sortedHandlers) {
|
||||
try {
|
||||
// 注册请求处理方法
|
||||
registerNewHandler(router, handler);
|
||||
registerNewHandler(mainRouter, handler);
|
||||
} catch (Throwable e) {
|
||||
LOGGER.error("Error register {}, Error details:", handler, e.getCause());
|
||||
|
||||
@@ -111,11 +126,12 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
||||
LOGGER.error("Manually Register Handler Fail, Error details:" + e.getMessage());
|
||||
}
|
||||
// 错误请求处理
|
||||
router.errorHandler(405, ctx -> fireJsonResponse(ctx, JsonResult
|
||||
mainRouter.errorHandler(405, ctx -> doFireJsonResultResponse(ctx, JsonResult
|
||||
.error("Method Not Allowed", 405)));
|
||||
router.errorHandler(404, ctx -> ctx.response().setStatusCode(404).setChunked(true)
|
||||
mainRouter.errorHandler(404, ctx -> ctx.response().setStatusCode(404).setChunked(true)
|
||||
.end("Internal server error: 404 not found"));
|
||||
return router;
|
||||
|
||||
return mainRouter;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,15 +155,13 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
||||
method -> method.isAnnotationPresent(SockRouteMapper.class)
|
||||
).toList());
|
||||
|
||||
// 拦截器
|
||||
Handler<RoutingContext> interceptor = getInterceptor();
|
||||
// 依次注册处理方法
|
||||
for (Method method : methodList) {
|
||||
if (method.isAnnotationPresent(RouteMapping.class)) {
|
||||
// 普通路由
|
||||
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);
|
||||
@@ -157,17 +171,18 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
||||
route.consumes(mineType);
|
||||
}
|
||||
|
||||
// 先执行拦截方法, 再进入业务请求
|
||||
route.handler(interceptor);
|
||||
// 设置默认超时
|
||||
route.handler(TimeoutHandler.create(SharedDataUtil.getCustomConfig().getInteger(ROUTE_TIME_OUT)));
|
||||
route.handler(ResponseTimeHandler.create());
|
||||
route.handler(ctx -> handlerMethod(instance, method, ctx)).failureHandler(ctx -> {
|
||||
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()
|
||||
@@ -194,13 +209,12 @@ 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 (mapperValue.startsWith("/:") || "/".equals(mapperValue)) {
|
||||
routeUrl = (methodName + mapperValue);
|
||||
if ("/".equals(mapperValue)) {
|
||||
routeUrl = mapperValue;
|
||||
} else if (mapperValue.startsWith("/")) {
|
||||
routeUrl = mapperValue.substring(1);
|
||||
} else {
|
||||
@@ -213,15 +227,10 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
||||
* 配置拦截
|
||||
*
|
||||
* @return Handler
|
||||
* @throws Throwable Throwable
|
||||
*/
|
||||
private Handler<RoutingContext> getInterceptor() throws Throwable {
|
||||
private Set<Handler<RoutingContext>> getInterceptorSet() {
|
||||
// 配置拦截
|
||||
Class<?> interceptorClass = Class.forName(SharedDataUtil.getValueForCustomConfig("interceptorClassPath"));
|
||||
Object handleInstance = ReflectionUtil.newWithNoParam(interceptorClass);
|
||||
Method doHandle = interceptorClass.getMethod("doHandle");
|
||||
// 反射调用
|
||||
return CastUtil.cast(ReflectionUtil.invoke(doHandle, handleInstance));
|
||||
return getBeforeInterceptor().stream().map(BeforeInterceptor::doHandle).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,7 +252,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 + "/";
|
||||
@@ -286,37 +295,12 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
||||
});
|
||||
}
|
||||
|
||||
final MultiMap queryParams = ctx.queryParams();
|
||||
if ("POST".equals(ctx.request().method().name())) {
|
||||
queryParams.addAll(ctx.request().params());
|
||||
}
|
||||
|
||||
JsonArray entityPackagesReg = SharedDataUtil.getJsonArrayForCustomConfig("entityPackagesReg");
|
||||
// 绑定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) -> {
|
||||
@@ -336,26 +320,66 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (ctx.body() != null) {
|
||||
queryParams.addAll(ParamUtil.paramsToMap(ctx.body().asString()));
|
||||
}
|
||||
|
||||
// 解析其他参数
|
||||
if ("POST".equals(ctx.request().method().name())) {
|
||||
queryParams.addAll(ctx.request().params());
|
||||
}
|
||||
// 绑定get或post请求头的请求参数
|
||||
methodParametersTemp.forEach((k, v) -> {
|
||||
if (ReflectionUtil.isBasicType(v.getRight())) {
|
||||
String fmt = getFmt(v.getLeft(), v.getRight());
|
||||
String value = queryParams.get(k);
|
||||
parameterValueList.put(k, ReflectionUtil.conversion(v.getRight(), value, fmt));
|
||||
} else if (RoutingContext.class.getName().equals(v.getRight().getName())) {
|
||||
parameterValueList.put(k, ctx);
|
||||
} else if (HttpServerRequest.class.getName().equals(v.getRight().getName())) {
|
||||
parameterValueList.put(k, ctx.request());
|
||||
} else if (HttpServerResponse.class.getName().equals(v.getRight().getName())) {
|
||||
parameterValueList.put(k, ctx.response());
|
||||
} else if (parameterValueList.get(k) == null
|
||||
&& CommonUtil.matchRegList(entityPackagesReg.getList(), v.getRight().getName())) {
|
||||
// 绑定实体类
|
||||
try {
|
||||
Class<?> aClass = Class.forName(v.getRight().getName());
|
||||
Object entity = ParamUtil.multiMapToEntity(queryParams, aClass);
|
||||
parameterValueList.put(k, entity);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
// 调用handle 获取响应对象
|
||||
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 JsonObject) {
|
||||
fireJsonResponse(ctx, res);
|
||||
} else if (res != null){
|
||||
fireJsonResponse(ctx, JsonResult.data(res));
|
||||
if (res instanceof JsonResult) {
|
||||
doFireJsonResultResponse(ctx, (JsonResult<?>) res);
|
||||
}
|
||||
}).onFailure(e -> fireJsonResponse(ctx, JsonResult.error(e.getMessage())));
|
||||
if (res instanceof JsonObject) {
|
||||
doFireJsonObjectResponse(ctx, ((JsonObject) res));
|
||||
} else if (res != null) {
|
||||
doFireJsonResultResponse(ctx, JsonResult.data(res));
|
||||
} else {
|
||||
handleAfterInterceptor(ctx, null);
|
||||
}
|
||||
|
||||
}).onFailure(e -> doFireJsonResultResponse(ctx, JsonResult.error(e.getMessage())));
|
||||
} else {
|
||||
ctx.response().headers().set(CONTENT_TYPE, MIMEType.TEXT_HTML.getValue());
|
||||
ctx.end(data.toString());
|
||||
doFireJsonResultResponse(ctx, JsonResult.data(data));
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
@@ -363,12 +387,12 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
||||
String err = e.getMessage();
|
||||
if (e.getCause() != null) {
|
||||
if (e.getCause() instanceof InvocationTargetException) {
|
||||
err = ((InvocationTargetException)e.getCause()).getTargetException().getMessage();
|
||||
err = ((InvocationTargetException) e.getCause()).getTargetException().getMessage();
|
||||
} else {
|
||||
err = e.getCause().getMessage();
|
||||
}
|
||||
}
|
||||
fireJsonResponse(ctx, JsonResult.error(err));
|
||||
doFireJsonResultResponse(ctx, JsonResult.error(err));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,4 +410,13 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
||||
}
|
||||
return fmt;
|
||||
}
|
||||
|
||||
private Set<BeforeInterceptor> getBeforeInterceptor() {
|
||||
Set<Class<? extends BeforeInterceptor>> interceptorClassSet =
|
||||
reflections.getSubTypesOf(BeforeInterceptor.class);
|
||||
if (interceptorClassSet == null) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
return CommonUtil.sortClassSet(interceptorClassSet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.qaiu.vx.core.interceptor;
|
||||
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
/**
|
||||
* 后置拦截器接口
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
*/
|
||||
public interface AfterInterceptor {
|
||||
|
||||
void handle(RoutingContext ctx, JsonObject responseData);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.qaiu.vx.core.interceptor;
|
||||
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
import static cn.qaiu.vx.core.util.ResponseUtil.sendError;
|
||||
|
||||
/**
|
||||
* 前置拦截器接口
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
*/
|
||||
public interface BeforeInterceptor extends Handler<RoutingContext> {
|
||||
String IS_NEXT = "RoutingContextIsNext";
|
||||
|
||||
default Handler<RoutingContext> doHandle() {
|
||||
|
||||
return ctx -> {
|
||||
ctx.put(IS_NEXT, false);
|
||||
BeforeInterceptor.this.handle(ctx);
|
||||
if (!(Boolean) ctx.get(IS_NEXT) && !ctx.response().ended()) {
|
||||
sendError(ctx, 403);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
default void doNext(RoutingContext context) {
|
||||
context.put(IS_NEXT, true);
|
||||
context.next();
|
||||
}
|
||||
|
||||
void handle(RoutingContext context);
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package cn.qaiu.vx.core.interceptor;
|
||||
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
/**
|
||||
* 拦截器接口
|
||||
* <br>Create date 2021-05-06 09:20:37
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
*/
|
||||
public interface Interceptor {
|
||||
|
||||
default Handler<RoutingContext> doHandle() {
|
||||
return this::handle;
|
||||
}
|
||||
|
||||
void handle(RoutingContext context);
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
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;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
@@ -13,6 +17,7 @@ import java.io.Serializable;
|
||||
*/
|
||||
public class JsonResult<T> implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final int SUCCESS_CODE = 200;
|
||||
@@ -157,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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -15,6 +16,8 @@ import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* CommonUtil
|
||||
@@ -70,10 +73,11 @@ public class CommonUtil {
|
||||
} catch (UnknownHostException e) {
|
||||
return false;
|
||||
}
|
||||
try(Socket ignored = new Socket(Address, port)) {
|
||||
//建立一个Socket连接
|
||||
try (Socket ignored = new Socket(Address, port)) {
|
||||
//建立一个Socket连接
|
||||
flag = true;
|
||||
} catch (IOException ignoredException) {}
|
||||
} catch (IOException ignoredException) {
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
@@ -126,4 +130,32 @@ public class CommonUtil {
|
||||
LocalConstant.put(configName, map);
|
||||
LOGGER.info("读取配置{}成功", configName);
|
||||
}
|
||||
|
||||
public static <T> Set<T> sortClassSet(Set<Class<? extends T>> set) {
|
||||
return set.stream().filter(c1 -> {
|
||||
HandleSortFilter s1 = c1.getAnnotation(HandleSortFilter.class);
|
||||
if (s1 != null) {
|
||||
return s1.value() > 0;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}).sorted((c1, c2) -> {
|
||||
HandleSortFilter s1 = c1.getAnnotation(HandleSortFilter.class);
|
||||
HandleSortFilter s2 = c2.getAnnotation(HandleSortFilter.class);
|
||||
int n1 = 0, n2 = 0;
|
||||
if (s1 != null) {
|
||||
n1 = s1.value();
|
||||
}
|
||||
if (s2 != null) {
|
||||
n2 = s2.value();
|
||||
}
|
||||
return n1 - n2;
|
||||
}).map(c -> {
|
||||
try {
|
||||
return ReflectionUtil.newWithNoParam(c);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,14 @@ package cn.qaiu.vx.core.util;
|
||||
public interface ConfigConstant {
|
||||
String CUSTOM = "custom";
|
||||
String VERTX = "vertx";
|
||||
String EVENT_LOOP_POOL_SIZE = "eventLoopPoolSize";
|
||||
String LOCAL = "local";
|
||||
String SERVER = "server";
|
||||
String GLOBAL_CONFIG = "globalConfig";
|
||||
String CUSTOM_CONFIG = "customConfig";
|
||||
String ASYNC_SERVICE_INSTANCES = "asyncServiceInstances";
|
||||
String IGNORES_REG="ignoresReg";
|
||||
String BASE_LOCATIONS="baseLocations";
|
||||
|
||||
String ROUTE_TIME_OUT="routeTimeOut";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import java.net.URL;
|
||||
import java.text.ParseException;
|
||||
import java.util.*;
|
||||
|
||||
import static cn.qaiu.vx.core.util.ConfigConstant.BASE_LOCATIONS;
|
||||
|
||||
/**
|
||||
* 基于org.reflection和javassist的反射工具包
|
||||
* 通过包扫描实现路由地址的注解映射
|
||||
@@ -33,6 +35,16 @@ import java.util.*;
|
||||
*/
|
||||
public final class ReflectionUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 以默认配置的基础包路径获取反射器
|
||||
*
|
||||
* @return Reflections object
|
||||
*/
|
||||
public static Reflections getReflections() {
|
||||
return getReflections(SharedDataUtil.getStringForCustomConfig(BASE_LOCATIONS));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取反射器
|
||||
*
|
||||
@@ -48,6 +60,7 @@ public final class ReflectionUtil {
|
||||
} else {
|
||||
packageAddressList = Collections.singletonList(packageAddress);
|
||||
}
|
||||
|
||||
return getReflections(packageAddressList);
|
||||
}
|
||||
|
||||
@@ -70,11 +83,12 @@ public final class ReflectionUtil {
|
||||
// 发现注解api层 没有继承父类时 这里反射一直有问题(Scanner SubTypesScanner was not configured)
|
||||
// 因此这里需要手动配置各种Scanner扫描器 -- https://blog.csdn.net/qq_29499107/article/details/106889781
|
||||
configurationBuilder.setScanners(
|
||||
new SubTypesScanner(false), //允许getAllTypes获取所有Object的子类, 不设置为false则 getAllTypes 会报错.默认为true.
|
||||
Scanners.SubTypes.filterResultsBy(s -> true), //允许getAllTypes获取所有Object的子类, 不设置为false则 getAllTypes
|
||||
// 会报错.默认为true.
|
||||
new MethodParameterNamesScanner(), //设置方法参数名称 扫描器,否则调用getConstructorParamNames 会报错
|
||||
new MethodAnnotationsScanner(), //设置方法注解 扫描器, 否则getConstructorsAnnotatedWith,getMethodsAnnotatedWith 会报错
|
||||
new MemberUsageScanner(), //设置 member 扫描器,否则 getMethodUsage 会报错, 不推荐使用,有可能会报错 Caused by: java.lang.ClassCastException: javassist.bytecode.InterfaceMethodrefInfo cannot be cast to javassist.bytecode.MethodrefInfo
|
||||
new TypeAnnotationsScanner() //设置类注解 扫描器 ,否则 getTypesAnnotatedWith 会报错
|
||||
Scanners.MethodsAnnotated, //设置方法注解 扫描器, 否则getConstructorsAnnotatedWith,getMethodsAnnotatedWith 会报错
|
||||
new MemberUsageScanner(), //设置 member 扫描器,否则 getMethodUsage 会报错
|
||||
Scanners.TypesAnnotated //设置类注解 扫描器 ,否则 getTypesAnnotatedWith 会报错
|
||||
);
|
||||
|
||||
configurationBuilder.filterInputsBy(filterBuilder);
|
||||
@@ -98,7 +112,8 @@ public final class ReflectionUtil {
|
||||
MethodInfo methodInfo = cm.getMethodInfo();
|
||||
CtClass[] parameterTypes = cm.getParameterTypes();
|
||||
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
|
||||
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
|
||||
LocalVariableAttribute attr =
|
||||
(LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
|
||||
|
||||
boolean flag = true;
|
||||
boolean flag2 = cm.getModifiers() - 1 != AccessFlag.STATIC;
|
||||
@@ -110,7 +125,8 @@ public final class ReflectionUtil {
|
||||
continue;
|
||||
}
|
||||
flag = false;
|
||||
paramMap.put(attr.variableName(j + (flag2 ? 1 : 0)), Pair.of(parameterAnnotations[j - k], parameterTypes[j - k]));
|
||||
paramMap.put(attr.variableName(j + (flag2 ? 1 : 0)), Pair.of(parameterAnnotations[j - k],
|
||||
parameterTypes[j - k]));
|
||||
}
|
||||
} catch (NotFoundException e) {
|
||||
e.printStackTrace();
|
||||
@@ -214,7 +230,8 @@ public final class ReflectionUtil {
|
||||
if (ctClass.isPrimitive() || "java.util.Date".equals(ctClass.getName())) {
|
||||
return true;
|
||||
}
|
||||
return ctClass.getName().matches("^java\\.lang\\.((Boolean)|(Character)|(Byte)|(Short)|(Integer)|(Long)|(Float)|(Double)|(String))$");
|
||||
return ctClass.getName().matches("^java\\.lang\\.((Boolean)|(Character)|(Byte)|(Short)|(Integer)|(Long)|" +
|
||||
"(Float)|(Double)|(String))$");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,7 +255,8 @@ public final class ReflectionUtil {
|
||||
* @throws InstantiationException InstantiationException
|
||||
* @throws IllegalAccessException IllegalAccessException
|
||||
*/
|
||||
public static <T> T newWithNoParam(Class<T> handler) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
|
||||
public static <T> T newWithNoParam(Class<T> handler) throws NoSuchMethodException, InvocationTargetException,
|
||||
InstantiationException, IllegalAccessException {
|
||||
return handler.getConstructor().newInstance();
|
||||
}
|
||||
|
||||
|
||||
40
core/src/main/java/cn/qaiu/vx/core/util/ResponseUtil.java
Normal file
40
core/src/main/java/cn/qaiu/vx/core/util/ResponseUtil.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package cn.qaiu.vx.core.util;
|
||||
|
||||
import cn.qaiu.vx.core.model.JsonResult;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.http.HttpHeaders;
|
||||
import io.vertx.core.http.HttpServerResponse;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
|
||||
|
||||
public class ResponseUtil {
|
||||
|
||||
public static void redirect(HttpServerResponse response, String url) {
|
||||
response.putHeader(HttpHeaders.LOCATION, url).setStatusCode(302).end();
|
||||
}
|
||||
|
||||
public static void redirect(HttpServerResponse response, String url, Promise<?> promise) {
|
||||
redirect(response, url);
|
||||
promise.complete();
|
||||
}
|
||||
|
||||
public static void fireJsonObjectResponse(RoutingContext ctx, JsonObject jsonObject) {
|
||||
ctx.response().putHeader(CONTENT_TYPE, "application/json; charset=utf-8")
|
||||
.setStatusCode(200)
|
||||
.end(jsonObject.encode());
|
||||
}
|
||||
|
||||
public static <T> void fireJsonResultResponse(RoutingContext ctx, JsonResult<T> jsonResult) {
|
||||
fireJsonObjectResponse(ctx, jsonResult.toJsonObject());
|
||||
}
|
||||
|
||||
public static void fireTextResponse(RoutingContext ctx, String text) {
|
||||
ctx.response().putHeader(CONTENT_TYPE, "text/html; charset=utf-8").end(text);
|
||||
}
|
||||
|
||||
public static void sendError(RoutingContext ctx, int statusCode) {
|
||||
ctx.response().setStatusCode(statusCode).end();
|
||||
}
|
||||
}
|
||||
@@ -32,11 +32,11 @@ public class SharedDataUtil {
|
||||
return (JsonObject) localMap.get(key);
|
||||
}
|
||||
|
||||
public static JsonObject getJsonObjectForCustomConfig(String key) {
|
||||
return getJsonConfig("customConfig").getJsonObject(key);
|
||||
public static JsonObject getCustomConfig() {
|
||||
return getJsonConfig("customConfig");
|
||||
}
|
||||
|
||||
public static String getJsonStringForCustomConfig(String key) {
|
||||
public static String getStringForCustomConfig(String key) {
|
||||
return getJsonConfig("customConfig").getString(key);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ public class RouterVerticle extends AbstractVerticle {
|
||||
|
||||
private static final int port = SharedDataUtil.getValueForServerConfig("port");
|
||||
private static final Router router = new RouterHandlerFactory(
|
||||
SharedDataUtil.getJsonStringForCustomConfig("routerLocations"),
|
||||
SharedDataUtil.getJsonStringForServerConfig("contextPath")).createRouter();
|
||||
|
||||
private static final JsonObject globalConfig = SharedDataUtil.getJsonConfig("globalConfig");
|
||||
|
||||
@@ -3,7 +3,6 @@ package cn.qaiu.vx.core.verticle;
|
||||
import cn.qaiu.vx.core.annotaions.Service;
|
||||
import cn.qaiu.vx.core.base.BaseAsyncService;
|
||||
import cn.qaiu.vx.core.util.ReflectionUtil;
|
||||
import cn.qaiu.vx.core.util.SharedDataUtil;
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.serviceproxy.ServiceBinder;
|
||||
@@ -27,8 +26,7 @@ public class ServiceVerticle extends AbstractVerticle {
|
||||
private static final Set<Class<?>> handlers;
|
||||
|
||||
static {
|
||||
String handlerLocations = SharedDataUtil.getJsonStringForCustomConfig("handlerLocations");
|
||||
Reflections reflections = ReflectionUtil.getReflections(handlerLocations);
|
||||
Reflections reflections = ReflectionUtil.getReflections();
|
||||
handlers = reflections.getTypesAnnotatedWith(Service.class);
|
||||
}
|
||||
|
||||
|
||||
68
parser/pom.xml
Normal file
68
parser/pom.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>netdisk-fast-download</artifactId>
|
||||
<groupId>cn.qaiu</groupId>
|
||||
<version>0.1.7</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>parser</artifactId>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!--logback日志实现-->
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-web-client</artifactId>
|
||||
<version>${vertx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.openjdk.nashorn/nashorn-core -->
|
||||
<dependency>
|
||||
<groupId>org.openjdk.nashorn</groupId>
|
||||
<artifactId>nashorn-core</artifactId>
|
||||
<version>15.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
24
parser/src/main/java/cn/qaiu/WebClientVertxInit.java
Normal file
24
parser/src/main/java/cn/qaiu/WebClientVertxInit.java
Normal 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;
|
||||
}
|
||||
}
|
||||
62
parser/src/main/java/cn/qaiu/parser/IPanTool.java
Normal file
62
parser/src/main/java/cn/qaiu/parser/IPanTool.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package cn.qaiu.parser;//package cn.qaiu.lz.common.parser;
|
||||
|
||||
import cn.qaiu.parser.impl.*;
|
||||
import io.vertx.core.Future;
|
||||
|
||||
public interface IPanTool {
|
||||
Future<String> parse();
|
||||
|
||||
static IPanTool typeMatching(String type, String key, String pwd) {
|
||||
return switch (type) {
|
||||
case "lz" -> new LzTool(key, pwd);
|
||||
case "cow" -> new CowTool(key, pwd);
|
||||
case "ec" -> new EcTool(key, pwd);
|
||||
case "fc" -> new FcTool(key, pwd);
|
||||
case "uc" -> new UcTool(key, pwd);
|
||||
case "ye" -> new YeTool(key, pwd);
|
||||
case "fj" -> new FjTool(key, pwd);
|
||||
case "qk" -> new QkTool(key, pwd);
|
||||
case "le" -> new LeTool(key, pwd);
|
||||
case "ws" -> new WsTool(key, pwd);
|
||||
case "qq" -> new QQTool(key, pwd);
|
||||
case "iz" -> new IzTool(key, pwd);
|
||||
case "ce" -> new CeTool(key, pwd);
|
||||
default -> {
|
||||
throw new UnsupportedOperationException("未知分享类型");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static IPanTool shareURLPrefixMatching(String url, String pwd) {
|
||||
|
||||
if (url.contains(CowTool.LINK_KEY)) {
|
||||
return new CowTool(url, pwd);
|
||||
} else if (url.startsWith(EcTool.SHARE_URL_PREFIX)) {
|
||||
return new EcTool(url, pwd);
|
||||
} else if (url.startsWith(FcTool.SHARE_URL_PREFIX0)) {
|
||||
return new FcTool(url, pwd);
|
||||
} else if (url.startsWith(UcTool.SHARE_URL_PREFIX)) {
|
||||
return new UcTool(url, pwd);
|
||||
} else if (url.startsWith(YeTool.SHARE_URL_PREFIX)) {
|
||||
return new YeTool(url, pwd);
|
||||
} else if (url.startsWith(FjTool.SHARE_URL_PREFIX) || url.startsWith(FjTool.SHARE_URL_PREFIX2)) {
|
||||
return new FjTool(url, pwd);
|
||||
} else if (url.startsWith(IzTool.SHARE_URL_PREFIX)) {
|
||||
return new IzTool(url, pwd);
|
||||
} else if (url.contains(LzTool.LINK_KEY)) {
|
||||
return new LzTool(url, pwd);
|
||||
} else if (url.startsWith(LeTool.SHARE_URL_PREFIX)) {
|
||||
return new LeTool(url, pwd);
|
||||
} else if (url.contains(WsTool.SHARE_URL_PREFIX) || url.contains(WsTool.SHARE_URL_PREFIX2)) {
|
||||
return new WsTool(url, pwd);
|
||||
} else if (url.contains(QQTool.SHARE_URL_PREFIX)) {
|
||||
return new QQTool(url, pwd);
|
||||
} else if (url.contains("/s/")) {
|
||||
// Cloudreve 网盘通用解析
|
||||
return new CeTool(url, pwd);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("未知分享类型");
|
||||
}
|
||||
|
||||
}
|
||||
113
parser/src/main/java/cn/qaiu/parser/PanBase.java
Normal file
113
parser/src/main/java/cn/qaiu/parser/PanBase.java
Normal file
@@ -0,0 +1,113 @@
|
||||
package cn.qaiu.parser;
|
||||
|
||||
import cn.qaiu.WebClientVertxInit;
|
||||
import cn.qaiu.util.CommonUtils;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
import io.vertx.ext.web.client.WebClientOptions;
|
||||
import io.vertx.ext.web.client.WebClientSession;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 解析器抽象类包含promise, HTTP Client, 默认失败方法等;
|
||||
* 新增网盘解析器需要继承该类.
|
||||
*/
|
||||
public abstract class PanBase {
|
||||
protected Logger log = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
protected Promise<String> promise = Promise.promise();
|
||||
|
||||
/**
|
||||
* Http client
|
||||
*/
|
||||
protected WebClient client = WebClient.create(WebClientVertxInit.get());
|
||||
|
||||
/**
|
||||
* Http client session (会话管理, 带cookie请求)
|
||||
*/
|
||||
protected WebClientSession clientSession = WebClientSession.create(client);
|
||||
|
||||
/**
|
||||
* Http client 不自动跳转
|
||||
*/
|
||||
protected WebClient clientNoRedirects = WebClient.create(WebClientVertxInit.get(),
|
||||
new WebClientOptions().setFollowRedirects(false));
|
||||
|
||||
/**
|
||||
* 分享key 可以是整个URL; 如果是URL实现该类时要
|
||||
* 使用{@link CommonUtils#adaptShortPaths(String urlPrefix, String key)}获取真实的分享key
|
||||
*/
|
||||
protected String key;
|
||||
|
||||
/**
|
||||
* 分享密码
|
||||
*/
|
||||
protected String pwd;
|
||||
|
||||
/**
|
||||
* 子类重写此构造方法不需要添加额外逻辑
|
||||
* 如:
|
||||
* <blockquote><pre>
|
||||
* public XxTool(String key, String pwd) {
|
||||
* super(key, pwd);
|
||||
* }
|
||||
* </pre></blockquote>
|
||||
*
|
||||
* @param key 分享key/url
|
||||
* @param pwd 分享密码
|
||||
*/
|
||||
protected PanBase(String key, String pwd) {
|
||||
this.key = key;
|
||||
this.pwd = pwd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败时生成异常消息
|
||||
*
|
||||
* @param t 异常实例
|
||||
* @param errorMsg 提示消息
|
||||
* @param args log参数变量
|
||||
*/
|
||||
protected void fail(Throwable t, String errorMsg, Object... args) {
|
||||
try {
|
||||
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
|
||||
log.error("解析异常: " + s, t.fillInStackTrace());
|
||||
promise.fail(this.getClass().getSimpleName() + ": 解析异常: " + s + " -> " + t);
|
||||
} catch (Exception e) {
|
||||
log.error("ErrorMsg format fail. The parameter has been discarded", e);
|
||||
log.error("解析异常: " + errorMsg, t.fillInStackTrace());
|
||||
promise.fail(this.getClass().getSimpleName() + ": 解析异常: " + errorMsg + " -> " + t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败时生成异常消息
|
||||
*
|
||||
* @param errorMsg 提示消息
|
||||
* @param args log参数变量
|
||||
*/
|
||||
protected void fail(String errorMsg, Object... args) {
|
||||
try {
|
||||
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
|
||||
log.error("解析异常: " + s);
|
||||
promise.fail(this.getClass().getSimpleName() + " - 解析异常: " + s);
|
||||
} catch (Exception e) {
|
||||
log.error("ErrorMsg format fail. The parameter has been discarded", e);
|
||||
log.error("解析异常: " + errorMsg);
|
||||
promise.fail(this.getClass().getSimpleName() + " - 解析异常: " + errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成失败Future的处理器
|
||||
*
|
||||
* @param errorMsg 提示消息
|
||||
* @return Handler
|
||||
*/
|
||||
protected Handler<Throwable> handleFail(String errorMsg) {
|
||||
return t -> fail(this.getClass().getSimpleName() + " - 请求异常 {}: -> {}", errorMsg, t.fillInStackTrace());
|
||||
}
|
||||
|
||||
}
|
||||
13
parser/src/main/java/cn/qaiu/parser/PanType.java
Normal file
13
parser/src/main/java/cn/qaiu/parser/PanType.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package cn.qaiu.parser;
|
||||
|
||||
/**
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
* @date 2023/6/13 4:26
|
||||
*/
|
||||
public enum PanType {
|
||||
LZ("lz"),
|
||||
COW("cow");
|
||||
|
||||
PanType(String type) {
|
||||
}
|
||||
}
|
||||
73
parser/src/main/java/cn/qaiu/parser/impl/CeTool.java
Normal file
73
parser/src/main/java/cn/qaiu/parser/impl/CeTool.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.buffer.Buffer;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.client.HttpRequest;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* <a href="https://github.com/cloudreve/Cloudreve">Cloudreve网盘解析</a> <br>
|
||||
* <a href="https://pan.xiaomuxi.cn">暮希云盘</a> <br>
|
||||
* <a href="https://pan.huang1111.cn">huang1111</a> <br>
|
||||
*/
|
||||
public class CeTool extends PanBase implements IPanTool {
|
||||
|
||||
private static final String DOWNLOAD_API_PATH = "/api/v3/share/download/";
|
||||
|
||||
// api/v3/share/info/g31PcQ?password=qaiu
|
||||
private static final String SHARE_API_PATH = "/api/v3/share/info/";
|
||||
|
||||
public CeTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
|
||||
public Future<String> parse() {
|
||||
// https://pan.huang1111.cn/s/wDz5TK
|
||||
// https://pan.huang1111.cn/s/y12bI6 -> https://pan.huang1111
|
||||
// .cn/api/v3/share/download/y12bI6?path=undefined%2Fundefined;
|
||||
// 类型解析 -> /ce/https_pan.huang1111.cn_s_wDz5TK
|
||||
// parser接口 -> /parser?url=https://pan.huang1111.cn/s/wDz5TK
|
||||
try {
|
||||
if (key.startsWith("https_") || key.startsWith("http_")) {
|
||||
key = key.replace("https_", "https://")
|
||||
.replace("http_", "http://")
|
||||
.replace("_", "/");
|
||||
}
|
||||
// 处理URL
|
||||
URL url = new URL(key);
|
||||
String path = url.getPath();
|
||||
String shareKey = path.substring(3);
|
||||
String downloadApiUrl = url.getProtocol() + "://" + url.getHost() + DOWNLOAD_API_PATH + shareKey + "?path" +
|
||||
"=undefined/undefined;";
|
||||
String shareApiUrl = url.getProtocol() + "://" + url.getHost() + SHARE_API_PATH + shareKey;
|
||||
|
||||
// 设置cookie
|
||||
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
|
||||
if (pwd != null) {
|
||||
httpRequest.addQueryParam("password", pwd);
|
||||
}
|
||||
// 获取下载链接
|
||||
httpRequest.send().onSuccess(res -> getDownURL(downloadApiUrl)).onFailure(handleFail(shareApiUrl));
|
||||
} catch (MalformedURLException e) {
|
||||
fail(e, "URL解析错误");
|
||||
}
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
private void getDownURL(String apiUrl) {
|
||||
clientSession.putAbs(apiUrl).send().onSuccess(res -> {
|
||||
JsonObject jsonObject = res.bodyAsJsonObject();
|
||||
System.out.println(jsonObject.encodePrettily());
|
||||
if (jsonObject.containsKey("code") && jsonObject.getInteger("code") == 0) {
|
||||
promise.complete(jsonObject.getString("data"));
|
||||
} else {
|
||||
fail("JSON解析失败: {}", jsonObject.encodePrettily());
|
||||
}
|
||||
}).onFailure(handleFail(apiUrl));
|
||||
}
|
||||
}
|
||||
67
parser/src/main/java/cn/qaiu/parser/impl/CowTool.java
Normal file
67
parser/src/main/java/cn/qaiu/parser/impl/CowTool.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import cn.qaiu.util.CommonUtils;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* 奶牛快传解析工具
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
* @date 2023/4/21 21:19
|
||||
*/
|
||||
public class CowTool extends PanBase implements IPanTool {
|
||||
|
||||
private static final String API_REQUEST_URL = "https://cowtransfer.com/core/api/transfer/share";
|
||||
public static final String SHARE_URL_PREFIX = "https://cowtransfer.com/s/";
|
||||
|
||||
public static final String LINK_KEY = "cowtransfer.com/s/";
|
||||
|
||||
public CowTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
|
||||
public Future<String> parse() {
|
||||
key = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
|
||||
String url = API_REQUEST_URL + "?uniqueUrl=" + key;
|
||||
client.getAbs(url).send().onSuccess(res -> {
|
||||
JsonObject resJson = res.bodyAsJsonObject();
|
||||
if ("success".equals(resJson.getString("message")) && resJson.containsKey("data")) {
|
||||
JsonObject dataJson = resJson.getJsonObject("data");
|
||||
String guid = dataJson.getString("guid");
|
||||
StringBuilder url2Build = new StringBuilder(API_REQUEST_URL + "/download?transferGuid=" + guid);
|
||||
if (dataJson.getBoolean("zipDownload")) {
|
||||
// &title=xxx
|
||||
JsonObject firstFolder = dataJson.getJsonObject("firstFolder");
|
||||
url2Build.append("&title=").append(firstFolder.getString("title"));
|
||||
} else {
|
||||
String fileId = dataJson.getJsonObject("firstFile").getString("id");
|
||||
url2Build.append("&fileId=").append(fileId);
|
||||
}
|
||||
String url2 = url2Build.toString();
|
||||
client.getAbs(url2).send().onSuccess(res2 -> {
|
||||
JsonObject res2Json = res2.bodyAsJsonObject();
|
||||
if ("success".equals(res2Json.getString("message")) && res2Json.containsKey("data")) {
|
||||
JsonObject data2 = res2Json.getJsonObject("data");
|
||||
String downloadUrl = data2.getString("downloadUrl");
|
||||
if (StringUtils.isNotEmpty(downloadUrl)) {
|
||||
log.info("cow parse success: {}", downloadUrl);
|
||||
promise.complete(downloadUrl);
|
||||
return;
|
||||
}
|
||||
fail("cow parse fail: {}; downloadUrl is empty", url2);
|
||||
return;
|
||||
}
|
||||
fail("cow parse fail: {}; json: {}", url2, res2Json);
|
||||
}).onFailure(handleFail(url2));
|
||||
return;
|
||||
}
|
||||
fail("cow parse fail: {}; json: {}", key, resJson);
|
||||
}).onFailure(handleFail(url));
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
}
|
||||
59
parser/src/main/java/cn/qaiu/parser/impl/EcTool.java
Normal file
59
parser/src/main/java/cn/qaiu/parser/impl/EcTool.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import cn.qaiu.util.CommonUtils;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.uritemplate.UriTemplate;
|
||||
|
||||
/**
|
||||
* 移动云空间解析
|
||||
*/
|
||||
public class EcTool extends PanBase implements IPanTool {
|
||||
private static final String FIRST_REQUEST_URL = "https://www.ecpan.cn/drive/fileextoverrid" +
|
||||
".do?chainUrlTemplate=https:%2F%2Fwww.ecpan" +
|
||||
".cn%2Fweb%2F%23%2FyunpanProxy%3Fpath%3D%252F%2523%252Fdrive%252Foutside&parentId=-1&data={dataKey}";
|
||||
|
||||
private static final String DOWNLOAD_REQUEST_URL = "https://www.ecpan.cn/drive/sharedownload.do";
|
||||
|
||||
public static final String SHARE_URL_PREFIX = "www.ecpan.cn/";
|
||||
|
||||
public EcTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
|
||||
public Future<String> parse() {
|
||||
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
|
||||
// 第一次请求 获取文件信息
|
||||
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("dataKey", dataKey).send().onSuccess(res -> {
|
||||
JsonObject jsonObject = res.bodyAsJsonObject();
|
||||
log.debug("ecPan get file info -> {}", jsonObject);
|
||||
JsonObject fileInfo = jsonObject
|
||||
.getJsonObject("var")
|
||||
.getJsonObject("chainFileInfo");
|
||||
if (fileInfo.containsKey("errMesg")) {
|
||||
fail("{} 解析失败:{} key = {}", FIRST_REQUEST_URL, fileInfo.getString("errMesg"), dataKey);
|
||||
return;
|
||||
}
|
||||
JsonObject cloudpFile = fileInfo.getJsonObject("cloudpFile");
|
||||
JsonArray fileIdList = JsonArray.of(cloudpFile);
|
||||
// 构造请求JSON {"extCodeFlag":0,"isIp":0}
|
||||
JsonObject requestBodyJson = JsonObject.of("extCodeFlag", 0, "isIp", 0);
|
||||
requestBodyJson.put("shareId", Integer.parseInt(fileInfo.getString("shareId"))); // 注意shareId
|
||||
// 数据类型
|
||||
requestBodyJson.put("groupId", cloudpFile.getString("groupId"));
|
||||
requestBodyJson.put("fileIdList", fileInfo.getJsonArray("cloudpFileList"));
|
||||
|
||||
// 第二次请求 获取下载链接
|
||||
client.postAbs(DOWNLOAD_REQUEST_URL).sendJsonObject(requestBodyJson).onSuccess(res2 -> {
|
||||
JsonObject jsonRes = res2.bodyAsJsonObject();
|
||||
log.debug("ecPan get download url -> {}", res2.body().toString());
|
||||
promise.complete(jsonRes.getJsonObject("var").getString("downloadUrl"));
|
||||
}).onFailure(handleFail(""));
|
||||
}
|
||||
).onFailure(handleFail(FIRST_REQUEST_URL));
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
103
parser/src/main/java/cn/qaiu/parser/impl/FcTool.java
Normal file
103
parser/src/main/java/cn/qaiu/parser/impl/FcTool.java
Normal file
@@ -0,0 +1,103 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import cn.qaiu.util.CommonUtils;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.buffer.Buffer;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.client.HttpResponse;
|
||||
import io.vertx.ext.web.client.WebClientSession;
|
||||
import io.vertx.uritemplate.UriTemplate;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 360亿方云
|
||||
*/
|
||||
public class FcTool extends PanBase implements IPanTool {
|
||||
|
||||
public static final String SHARE_URL_PREFIX0 = "https://v2.fangcloud.com/s";
|
||||
public static final String SHARE_URL_PREFIX = "https://v2.fangcloud.com/sharing/";
|
||||
public static final String SHARE_URL_PREFIX2 = "https://v2.fangcloud.cn/sharing/";
|
||||
private static final String DOWN_REQUEST_URL = "https://v2.fangcloud.cn/apps/files/download?file_id={fid}" +
|
||||
"&scenario=share&unique_name={uname}";
|
||||
|
||||
public FcTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
|
||||
public Future<String> parse() {
|
||||
String data = key.replace("share","sharing");
|
||||
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, data);
|
||||
WebClientSession sClient = WebClientSession.create(client);
|
||||
// 第一次请求 自动重定向
|
||||
sClient.getAbs(SHARE_URL_PREFIX + dataKey).send().onSuccess(res -> {
|
||||
|
||||
// 判断是否是加密分享
|
||||
if (StringUtils.isNotEmpty(pwd)) {
|
||||
// 获取requesttoken
|
||||
String html = res.bodyAsString();
|
||||
Pattern compile = Pattern.compile("name=\"requesttoken\"\\s+value=\"([a-zA-Z0-9_+=]+)\"");
|
||||
Matcher matcher = compile.matcher(html);
|
||||
if (!matcher.find()) {
|
||||
fail(SHARE_URL_PREFIX + " 未匹配到加密分享的密码输入页面的requesttoken");
|
||||
return;
|
||||
}
|
||||
String token = matcher.group(1);
|
||||
|
||||
sClient.postAbs(SHARE_URL_PREFIX2 + dataKey).sendForm(MultiMap.caseInsensitiveMultiMap()
|
||||
.set("requesttoken", token)
|
||||
.set("password", pwd)).onSuccess(res2 -> {
|
||||
if (res2.statusCode() == 302) {
|
||||
sClient.getAbs(res2.getHeader("Location")).send()
|
||||
.onSuccess(res3 -> getDownURL(dataKey, promise, res3, sClient))
|
||||
.onFailure(handleFail(res2.getHeader("Location")));
|
||||
return;
|
||||
}
|
||||
fail(SHARE_URL_PREFIX + " 密码跳转后获取重定向失败");
|
||||
}).onFailure(handleFail(SHARE_URL_PREFIX2));
|
||||
return;
|
||||
}
|
||||
getDownURL(dataKey, promise, res, sClient);
|
||||
}).onFailure(handleFail(SHARE_URL_PREFIX + dataKey));
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
private void getDownURL(String dataKey, Promise<String> promise, HttpResponse<Buffer> res,
|
||||
WebClientSession sClient) {
|
||||
// 从HTML中找到文件id
|
||||
String html = res.bodyAsString();
|
||||
Pattern compile = Pattern.compile("id=\"typed_id\"\\s+value=\"file_(\\d+)\"");
|
||||
Matcher matcher = compile.matcher(html);
|
||||
if (!matcher.find()) {
|
||||
fail(SHARE_URL_PREFIX + " 未匹配到文件id(typed_id)");
|
||||
return;
|
||||
}
|
||||
String fid = matcher.group(1);
|
||||
|
||||
// 创建一个不自动重定向的WebClientSession
|
||||
WebClientSession sClientNoRedirects = WebClientSession.create(clientNoRedirects, sClient.cookieStore());
|
||||
// 第二次请求
|
||||
sClientNoRedirects.getAbs(UriTemplate.of(DOWN_REQUEST_URL))
|
||||
.setTemplateParam("fid", fid)
|
||||
.setTemplateParam("unique_name", dataKey).send().onSuccess(res2 -> {
|
||||
JsonObject resJson;
|
||||
try {
|
||||
resJson = res2.bodyAsJsonObject();
|
||||
} catch (Exception e) {
|
||||
fail(e, DOWN_REQUEST_URL + " 第二次请求没有返回JSON, 可能下载受限");
|
||||
return;
|
||||
}
|
||||
if (!resJson.getBoolean("success")) {
|
||||
fail(DOWN_REQUEST_URL + " 第二次请求未得到正确相应: " + resJson);
|
||||
return;
|
||||
}
|
||||
promise.complete(resJson.getString("download_url"));
|
||||
}).onFailure(handleFail(DOWN_REQUEST_URL));
|
||||
}
|
||||
}
|
||||
91
parser/src/main/java/cn/qaiu/parser/impl/FjTool.java
Normal file
91
parser/src/main/java/cn/qaiu/parser/impl/FjTool.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import cn.qaiu.util.AESUtils;
|
||||
import cn.qaiu.util.CommonUtils;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
import io.vertx.uritemplate.UriTemplate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 小飞机网盘
|
||||
*
|
||||
* @version V016_230609
|
||||
*/
|
||||
public class FjTool extends PanBase implements IPanTool {
|
||||
|
||||
public static final String SHARE_URL_PREFIX = "https://www.feijix.com/s/";
|
||||
public static final String REFERER_URL = "https://share.feijipan.com/";
|
||||
public static final String SHARE_URL_PREFIX2 = REFERER_URL + "s/";
|
||||
private static final String API_URL_PREFIX = "https://api.feijipan.com/ws/";
|
||||
|
||||
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "recommend/list?devType=6&devModel=Chrome&extra" +
|
||||
"=2&shareId={shareId}&type=0&offset=1&limit=60";
|
||||
|
||||
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "file/redirect?downloadId={fidEncode}&enable=1" +
|
||||
"&devType=6&uuid={uuid}×tamp={ts}&auth={auth}";
|
||||
|
||||
public FjTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
|
||||
public Future<String> parse() {
|
||||
String dataKey;
|
||||
if (key.startsWith(SHARE_URL_PREFIX2)) {
|
||||
dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX2, key);
|
||||
} else {
|
||||
dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
|
||||
}
|
||||
|
||||
WebClient client = clientNoRedirects;
|
||||
String shareId = String.valueOf(AESUtils.idEncrypt(dataKey));
|
||||
|
||||
// 第一次请求 获取文件信息
|
||||
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
|
||||
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("shareId", shareId).send().onSuccess(res -> {
|
||||
JsonObject resJson = res.bodyAsJsonObject();
|
||||
if (resJson.getInteger("code") != 200) {
|
||||
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
|
||||
return;
|
||||
}
|
||||
if (resJson.getJsonArray("list").size() == 0) {
|
||||
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
||||
return;
|
||||
}
|
||||
// 文件Id
|
||||
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
|
||||
String fileId = fileInfo.getString("fileIds");
|
||||
String userId = fileInfo.getString("userId");
|
||||
// 其他参数
|
||||
long nowTs = System.currentTimeMillis();
|
||||
String tsEncode = AESUtils.encrypt2Hex(Long.toString(nowTs));
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
String fidEncode = AESUtils.encrypt2Hex(fileId + "|");
|
||||
String auth = AESUtils.encrypt2Hex(fileId + "|" + nowTs);
|
||||
|
||||
MultiMap headers0 = MultiMap.caseInsensitiveMultiMap();
|
||||
headers0.set("referer", REFERER_URL);
|
||||
// 第二次请求
|
||||
client.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
||||
.putHeaders(headers0)
|
||||
.setTemplateParam("fidEncode", fidEncode)
|
||||
.setTemplateParam("uuid", uuid)
|
||||
.setTemplateParam("ts", tsEncode)
|
||||
.setTemplateParam("auth", auth).send().onSuccess(res2 -> {
|
||||
MultiMap headers = res2.headers();
|
||||
if (!headers.contains("Location")) {
|
||||
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res.headers());
|
||||
return;
|
||||
}
|
||||
promise.complete(headers.get("Location"));
|
||||
}).onFailure(handleFail(SECOND_REQUEST_URL));
|
||||
}).onFailure(handleFail(FIRST_REQUEST_URL));
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
80
parser/src/main/java/cn/qaiu/parser/impl/IzTool.java
Normal file
80
parser/src/main/java/cn/qaiu/parser/impl/IzTool.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import cn.qaiu.util.AESUtils;
|
||||
import cn.qaiu.util.CommonUtils;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
import io.vertx.uritemplate.UriTemplate;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 蓝奏云优享
|
||||
*
|
||||
*/
|
||||
public class IzTool extends PanBase implements IPanTool {
|
||||
|
||||
public static final String SHARE_URL_PREFIX = "https://www.ilanzou.com/s/";
|
||||
private static final String API_URL_PREFIX = "https://api.ilanzou.com/unproved/";
|
||||
|
||||
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "recommend/list?devType=6&devModel=Chrome&extra" +
|
||||
"=2&shareId={shareId}&type=0&offset=1&limit=60";
|
||||
|
||||
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "file/redirect?downloadId={fidEncode}&enable=1" +
|
||||
"&devType=6&uuid={uuid}×tamp={ts}&auth={auth}";
|
||||
|
||||
public IzTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
|
||||
public Future<String> parse() {
|
||||
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
|
||||
|
||||
WebClient client = clientNoRedirects;
|
||||
String shareId = String.valueOf(AESUtils.idEncryptIz(dataKey));
|
||||
|
||||
// 第一次请求 获取文件信息
|
||||
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
|
||||
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("shareId", shareId).send().onSuccess(res -> {
|
||||
JsonObject resJson = res.bodyAsJsonObject();
|
||||
if (resJson.getInteger("code") != 200) {
|
||||
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
|
||||
return;
|
||||
}
|
||||
if (resJson.getJsonArray("list").size() == 0) {
|
||||
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
||||
return;
|
||||
}
|
||||
// 文件Id
|
||||
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
|
||||
String fileId = fileInfo.getString("fileIds");
|
||||
String userId = fileInfo.getString("userId");
|
||||
// 其他参数
|
||||
long nowTs = System.currentTimeMillis();
|
||||
String tsEncode = AESUtils.encrypt2HexIz(Long.toString(nowTs));
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
// String fidEncode = AESUtils.encrypt2HexIz(fileId + "|");
|
||||
String fidEncode = AESUtils.encrypt2HexIz(fileId + "|" + userId);
|
||||
String auth = AESUtils.encrypt2HexIz(fileId + "|" + nowTs);
|
||||
// 第二次请求
|
||||
client.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
||||
.setTemplateParam("fidEncode", fidEncode)
|
||||
.setTemplateParam("uuid", uuid)
|
||||
.setTemplateParam("ts", tsEncode)
|
||||
.setTemplateParam("auth", auth).send().onSuccess(res2 -> {
|
||||
MultiMap headers = res2.headers();
|
||||
if (!headers.contains("Location")) {
|
||||
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res.headers());
|
||||
return;
|
||||
}
|
||||
promise.complete(headers.get("Location"));
|
||||
}).onFailure(handleFail(SECOND_REQUEST_URL));
|
||||
}).onFailure(handleFail(FIRST_REQUEST_URL));
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
94
parser/src/main/java/cn/qaiu/parser/impl/LeTool.java
Normal file
94
parser/src/main/java/cn/qaiu/parser/impl/LeTool.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import cn.qaiu.util.CommonUtils;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* <a href="https://lecloud.lenovo.com/">联想乐云</a>
|
||||
*/
|
||||
public class LeTool extends PanBase implements IPanTool {
|
||||
|
||||
public static final String SHARE_URL_PREFIX = "https://lecloud.lenovo.com/share/";
|
||||
private static final String API_URL_PREFIX = "https://lecloud.lenovo.com/share/api/clouddiskapi/share/public/v1/";
|
||||
|
||||
public LeTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
|
||||
public Future<String> parse() {
|
||||
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
|
||||
// {"shareId":"xxx","password":"xxx","directoryId":"-1"}
|
||||
String apiUrl1 = API_URL_PREFIX + "shareInfo";
|
||||
client.postAbs(apiUrl1)
|
||||
.sendJsonObject(JsonObject.of("shareId", dataKey, "password", pwd, "directoryId", -1))
|
||||
.onSuccess(res -> {
|
||||
JsonObject resJson = res.bodyAsJsonObject();
|
||||
if (resJson.containsKey("result")) {
|
||||
if (resJson.getBoolean("result")) {
|
||||
JsonObject dataJson = resJson.getJsonObject("data");
|
||||
// 密码验证失败
|
||||
if (!dataJson.getBoolean("passwordVerified")) {
|
||||
fail("密码验证失败, 分享key: {}, 密码: {}", dataKey, pwd);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取文件信息
|
||||
JsonArray files = dataJson.getJsonArray("files");
|
||||
if (files == null || files.size() == 0) {
|
||||
fail("Result JSON数据异常: files字段不存在或jsonArray长度为空");
|
||||
return;
|
||||
}
|
||||
JsonObject fileInfoJson = files.getJsonObject(0);
|
||||
if (fileInfoJson != null) {
|
||||
// TODO 文件大小fileSize和文件名fileName
|
||||
Long fileId = fileInfoJson.getLong("fileId");
|
||||
// 根据文件ID获取跳转链接
|
||||
getDownURL(dataKey, fileId);
|
||||
}
|
||||
} else {
|
||||
fail("{}: {}", resJson.getString("errcode"), resJson.getString("errmsg"));
|
||||
}
|
||||
} else {
|
||||
fail("Result JSON数据异常: result字段不存在");
|
||||
}
|
||||
}).onFailure(handleFail(apiUrl1));
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
private void getDownURL(String key, Long fileId) {
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
JsonArray fileIds = JsonArray.of(fileId);
|
||||
String apiUrl2 = API_URL_PREFIX + "packageDownloadWithFileIds";
|
||||
// {"fileIds":[123],"shareId":"xxx","browserId":"uuid"}
|
||||
client.postAbs(apiUrl2)
|
||||
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", key, "browserId", uuid))
|
||||
.onSuccess(res -> {
|
||||
JsonObject resJson = res.bodyAsJsonObject();
|
||||
if (resJson.containsKey("result")) {
|
||||
if (resJson.getBoolean("result")) {
|
||||
JsonObject dataJson = resJson.getJsonObject("data");
|
||||
// 获取重定向链接跳转链接
|
||||
String downloadUrl = dataJson.getString("downloadUrl");
|
||||
if (downloadUrl == null) {
|
||||
fail("Result JSON数据异常: downloadUrl不存在");
|
||||
return;
|
||||
}
|
||||
// 获取重定向链接跳转链接
|
||||
clientNoRedirects.getAbs(downloadUrl).send()
|
||||
.onSuccess(res2 -> promise.complete(res2.headers().get("Location")))
|
||||
.onFailure(handleFail(downloadUrl));
|
||||
} else {
|
||||
fail("{}: {}", resJson.getString("errcode"), resJson.getString("errmsg"));
|
||||
}
|
||||
} else {
|
||||
fail("Result JSON数据异常: result字段不存在");
|
||||
}
|
||||
}).onFailure(handleFail(apiUrl2));
|
||||
}
|
||||
}
|
||||
125
parser/src/main/java/cn/qaiu/parser/impl/LzTool.java
Normal file
125
parser/src/main/java/cn/qaiu/parser/impl/LzTool.java
Normal file
@@ -0,0 +1,125 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import cn.qaiu.util.JsExecUtils;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||
|
||||
import javax.script.ScriptException;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 蓝奏云解析工具
|
||||
*
|
||||
* @author QAIU
|
||||
*/
|
||||
public class LzTool extends PanBase implements IPanTool {
|
||||
|
||||
public static final String SHARE_URL_PREFIX = "https://wwwa.lanzoux.com";
|
||||
|
||||
public static final String LINK_KEY = "lanzou";
|
||||
|
||||
public LzTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Future<String> parse() {
|
||||
String sUrl = key.startsWith("https://") ? key : SHARE_URL_PREFIX + "/" + key;
|
||||
|
||||
WebClient client = clientNoRedirects;
|
||||
client.getAbs(sUrl).send().onSuccess(res -> {
|
||||
String html = res.bodyAsString();
|
||||
// 匹配iframe
|
||||
Pattern compile = Pattern.compile("src=\"(/fn\\?[a-zA-Z\\d_+/=]{16,})\"");
|
||||
Matcher matcher = compile.matcher(html);
|
||||
// 没有Iframe说明是加密分享, 匹配sign通过密码请求下载页面
|
||||
if (!matcher.find()) {
|
||||
// 处理一下JS
|
||||
String jsText = getJsText(html);
|
||||
|
||||
if (jsText == null) {
|
||||
fail(SHARE_URL_PREFIX + " -> " + sUrl + ": js脚本匹配失败, 可能分享已失效");
|
||||
return;
|
||||
}
|
||||
|
||||
jsText = jsText.replace("document.getElementById('pwd').value", "\"" + pwd + "\"");
|
||||
jsText = jsText.substring(0, jsText.indexOf("document.getElementById('rpt')"));
|
||||
try {
|
||||
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "down_p");
|
||||
getDownURL(sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
|
||||
} catch (ScriptException | NoSuchMethodException e) {
|
||||
fail(e, "js引擎执行失败");
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
String iframePath = matcher.group(1);
|
||||
client.getAbs(SHARE_URL_PREFIX + iframePath).send().onSuccess(res2 -> {
|
||||
String html2 = res2.bodyAsString();
|
||||
|
||||
// 去TMD正则
|
||||
// Matcher matcher2 = Pattern.compile("'sign'\s*:\s*'(\\w+)'").matcher(html2);
|
||||
String jsText = getJsText(html2);
|
||||
if (jsText == null) {
|
||||
fail(SHARE_URL_PREFIX + iframePath + " -> " + sUrl + ": js脚本匹配失败, 可能分享已失效");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, null);
|
||||
getDownURL(sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
|
||||
} catch (ScriptException | NoSuchMethodException e) {
|
||||
fail(e, "js引擎执行失败");
|
||||
}
|
||||
}).onFailure(handleFail(SHARE_URL_PREFIX));
|
||||
}).onFailure(handleFail(sUrl));
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
private String getJsText(String html) {
|
||||
String jsTagStart = "<script type=\"text/javascript\">";
|
||||
String jsTagEnd = "</script>";
|
||||
int index = html.lastIndexOf(jsTagStart);
|
||||
if (index == -1) {
|
||||
return null;
|
||||
}
|
||||
int startPos = index + jsTagStart.length();
|
||||
int endPos = html.indexOf(jsTagEnd, startPos);
|
||||
return html.substring(startPos, endPos);
|
||||
}
|
||||
|
||||
private void getDownURL(String key, WebClient client, Map<String, ?> signMap) {
|
||||
MultiMap map = MultiMap.caseInsensitiveMultiMap();
|
||||
signMap.forEach((k, v) -> {
|
||||
map.set(k, v.toString());
|
||||
});
|
||||
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
|
||||
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
|
||||
"like " +
|
||||
"Gecko) Chrome/111.0.0.0 Mobile Safari/537.36";
|
||||
headers.set("User-Agent", userAgent2);
|
||||
headers.set("referer", key);
|
||||
headers.set("sec-ch-ua-platform", "Android");
|
||||
headers.set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
|
||||
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
|
||||
|
||||
String url = SHARE_URL_PREFIX + "/ajaxm.php";
|
||||
client.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
|
||||
JsonObject urlJson = res2.bodyAsJsonObject();
|
||||
if (urlJson.getInteger("zt") != 1) {
|
||||
fail(urlJson.getString("inf"));
|
||||
return;
|
||||
}
|
||||
String downUrl = urlJson.getString("dom") + "/file/" + urlJson.getString("url");
|
||||
client.getAbs(downUrl).putHeaders(headers).send()
|
||||
.onSuccess(res3 -> promise.complete(res3.headers().get("Location")))
|
||||
.onFailure(handleFail(downUrl));
|
||||
}).onFailure(handleFail(url));
|
||||
}
|
||||
}
|
||||
90
parser/src/main/java/cn/qaiu/parser/impl/QQTool.java
Normal file
90
parser/src/main/java/cn/qaiu/parser/impl/QQTool.java
Normal file
@@ -0,0 +1,90 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.util.StringUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
|
||||
/**
|
||||
* <a href="https://wx.mail.qq.com/">QQ邮箱</a>
|
||||
*/
|
||||
public class QQTool extends PanBase implements IPanTool {
|
||||
|
||||
public static final String SHARE_URL_PREFIX = "wx.mail.qq.com/ftn/download?";
|
||||
|
||||
public QQTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Future<String> parse() {
|
||||
|
||||
WebClient httpClient = this.client;
|
||||
|
||||
// 补全链接
|
||||
if (!this.key.startsWith("https://" + SHARE_URL_PREFIX)) {
|
||||
if (this.key.startsWith(SHARE_URL_PREFIX)) {
|
||||
this.key = "https://" + this.key;
|
||||
} else if (this.key.startsWith("func=")) {
|
||||
this.key = "https://" + SHARE_URL_PREFIX + this.key;
|
||||
} else {
|
||||
throw new UnsupportedOperationException("未知分享类型");
|
||||
}
|
||||
}
|
||||
|
||||
// 设置基础HTTP头部
|
||||
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
|
||||
"like " +
|
||||
"Gecko) Chrome/111.0.0.0 Mobile Safari/537.36";
|
||||
|
||||
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
|
||||
headers.set("User-Agent", userAgent2);
|
||||
headers.set("sec-ch-ua-platform", "Android");
|
||||
headers.set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
|
||||
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
|
||||
|
||||
// 获取下载中转站页面
|
||||
httpClient.getAbs(this.key).putHeaders(headers).send().onSuccess(res -> {
|
||||
if (res.statusCode() == 200) {
|
||||
String html = res.bodyAsString();
|
||||
|
||||
// 匹配文件信息
|
||||
String filename = StringUtils.StringCutNot(html, "var filename = \"", "\"");
|
||||
String filesize = StringUtils.StringCutNot(html, "var filesize = ", "\n");
|
||||
String fileurl = StringUtils.StringCutNot(html, "var url = \"", "\"");
|
||||
|
||||
if (filename != null && filesize != null && fileurl != null) {
|
||||
// 设置所需HTTP头部
|
||||
headers.set("Referer", "https://" + StringUtils.StringCutNot(this.key, "https://", "/") + "/");
|
||||
headers.set("Host", StringUtils.StringCutNot(fileurl, "https://", "/"));
|
||||
res.headers().forEach((k, v) -> {
|
||||
if (k.toLowerCase().equals("set-cookie")) {
|
||||
headers.set("Cookie", "mail5k=" + StringUtils.StringCutNot(v, "mail5k=", ";") + ";");
|
||||
}
|
||||
});
|
||||
|
||||
// 调试匹配的情况
|
||||
System.out.println("文件名称: " + filename);
|
||||
System.out.println("文件大小: " + filesize);
|
||||
System.out.println("文件直链: " + fileurl);
|
||||
|
||||
// 提交
|
||||
promise.complete(fileurl.replace("\\x26", "&"));
|
||||
} else {
|
||||
this.fail("匹配失败,可能是分享链接的方式已更新");
|
||||
}
|
||||
} else {
|
||||
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||
}
|
||||
}).onFailure(this.handleFail(this.key));
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
}
|
||||
46
parser/src/main/java/cn/qaiu/parser/impl/QkTool.java
Normal file
46
parser/src/main/java/cn/qaiu/parser/impl/QkTool.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import io.vertx.core.Future;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class QkTool extends PanBase implements IPanTool {
|
||||
|
||||
public QkTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
|
||||
public Future<String> parse() {
|
||||
promise.complete("https://lz.qaiu.top");
|
||||
IntStream.range(0, 1000).forEach(num -> {
|
||||
clientNoRedirects.getAbs(key).send()
|
||||
.onSuccess(res -> {
|
||||
String location = res.headers().get("Location");
|
||||
System.out.println(num + ":" + location);
|
||||
})
|
||||
.onFailure(handleFail("连接失败"));
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
new QkTool("https://pimapi.lenovomm.com/clouddiskapi/v1/shareRedirect?si=12298704&dk" +
|
||||
"=19ab590770399d4438ea885446e27186cc668cdfa559f5fcc063a1ecf78008e5&pk" +
|
||||
"=ef45aa4d25c1dcecb631b3394f51539d59cb35c6a40c3911df8ba431ba2a3244&pc=true&ot=ali&ob=sync-cloud-disk" +
|
||||
"&ok=649593714557087744.dex&fn=classes" +
|
||||
".dex&ds=8909208&dc=1&bi=asdddsad&ri=&ts=1701235051759&sn" +
|
||||
"=13dc33749bd9cc108009fa505b3ecca9f358d70874352858475956ba4240e4c3", "")
|
||||
.parse().onSuccess((res) -> {
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,20 @@
|
||||
package cn.qaiu.lz.common.util;
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.vx.core.util.VertxHolder;
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import cn.qaiu.util.CommonUtils;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
import io.vertx.uritemplate.UriTemplate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 移动云空间解析
|
||||
* UC网盘解析
|
||||
*/
|
||||
@Slf4j
|
||||
public class UcTool {
|
||||
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 FULL_URL_PREFIX = "https://fast.uc.cn/s/";
|
||||
public static final String SHARE_URL_PREFIX = "https://fast.uc.cn/s/";
|
||||
|
||||
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "share/sharepage/token?entry=ft&fr=pc&pr" +
|
||||
"=UCBrowser";
|
||||
@@ -26,14 +24,13 @@ public class UcTool {
|
||||
|
||||
private static final String THIRD_REQUEST_URL = API_URL_PREFIX + "file/download?entry=ft&fr=pc&pr=UCBrowser";
|
||||
|
||||
public static Future<String> parse(String data, String code) {
|
||||
if (!data.startsWith(FULL_URL_PREFIX)) {
|
||||
data = FULL_URL_PREFIX + data;
|
||||
}
|
||||
var passcode = (code == null) ? "" : code;
|
||||
var dataKey = data.substring(FULL_URL_PREFIX.length());
|
||||
Promise<String> promise = Promise.promise();
|
||||
var client = WebClient.create(VertxHolder.getVertxInstance());
|
||||
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);
|
||||
@@ -42,7 +39,7 @@ public class UcTool {
|
||||
log.debug("第一阶段 {}", res.body());
|
||||
var resJson = res.bodyAsJsonObject();
|
||||
if (resJson.getInteger("code") != 0) {
|
||||
promise.fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
|
||||
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
|
||||
return;
|
||||
}
|
||||
var stoken = resJson.getJsonObject("data").getString("stoken");
|
||||
@@ -55,7 +52,7 @@ public class UcTool {
|
||||
log.debug("第二阶段 {}", res2.body());
|
||||
JsonObject resJson2 = res2.bodyAsJsonObject();
|
||||
if (resJson2.getInteger("code") != 0) {
|
||||
promise.fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2);
|
||||
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2);
|
||||
return;
|
||||
}
|
||||
// 文件信息
|
||||
@@ -71,17 +68,15 @@ public class UcTool {
|
||||
log.debug("第三阶段 {}", res3.body());
|
||||
var resJson3 = res3.bodyAsJsonObject();
|
||||
if (resJson3.getInteger("code") != 0) {
|
||||
promise.fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2);
|
||||
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2);
|
||||
return;
|
||||
}
|
||||
promise.complete(resJson3.getJsonArray("data").getJsonObject(0).getString("download_url"));
|
||||
})
|
||||
.onFailure(t -> promise
|
||||
.fail(new RuntimeException("解析异常: ", t.fillInStackTrace())));
|
||||
}).onFailure(handleFail(THIRD_REQUEST_URL));
|
||||
|
||||
}).onFailure(t -> promise.fail(new RuntimeException("解析异常: ", t.fillInStackTrace())));
|
||||
}).onFailure(handleFail(SECOND_REQUEST_URL));
|
||||
}
|
||||
).onFailure(t -> promise.fail(new RuntimeException("解析异常: key = " + dataKey, t.fillInStackTrace())));
|
||||
).onFailure(handleFail(FIRST_REQUEST_URL));
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
183
parser/src/main/java/cn/qaiu/parser/impl/WsTool.java
Normal file
183
parser/src/main/java/cn/qaiu/parser/impl/WsTool.java
Normal file
@@ -0,0 +1,183 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.util.StringUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.json.DecodeException;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
|
||||
/**
|
||||
* <a href="https://www.wenshushu.cn/">文叔叔</a>
|
||||
*/
|
||||
public class WsTool extends PanBase implements IPanTool {
|
||||
|
||||
public static final String SHARE_URL_PREFIX = "www.wenshushu.cn/f/";
|
||||
public static final String SHARE_URL_PREFIX2 = "f.ws59.cn/f/";
|
||||
public static final String SHARE_URL_API = "https://www.wenshushu.cn/ap/";
|
||||
|
||||
public WsTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Future<String> parse() {
|
||||
|
||||
WebClient httpClient = this.client;
|
||||
|
||||
// 补全链接
|
||||
if (!this.key.startsWith("https://" + SHARE_URL_PREFIX) && !this.key.startsWith("https://" + SHARE_URL_PREFIX2)) {
|
||||
if (this.key.startsWith(SHARE_URL_PREFIX) || this.key.startsWith(SHARE_URL_PREFIX2)) {
|
||||
this.key = "https://" + this.key;
|
||||
} else if (this.key.matches("^[A-Za-z0-9]+$")) {
|
||||
this.key = "https://" + SHARE_URL_PREFIX + this.key;
|
||||
} else {
|
||||
throw new UnsupportedOperationException("未知分享类型");
|
||||
}
|
||||
}
|
||||
|
||||
// 设置基础HTTP头部
|
||||
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
|
||||
"like " +
|
||||
"Gecko) Chrome/111.0.0.0 Mobile Safari/537.36";
|
||||
|
||||
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
|
||||
headers.set("User-Agent", userAgent2);
|
||||
headers.set("sec-ch-ua-platform", "Android");
|
||||
headers.set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
|
||||
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
|
||||
|
||||
// 获取匿名登录token
|
||||
httpClient.postAbs(SHARE_URL_API + "login/anonymous").putHeaders(headers)
|
||||
.sendJsonObject(JsonObject.of("dev_info", "{}"))
|
||||
.onSuccess(res -> {
|
||||
|
||||
if (res.statusCode() == 200) {
|
||||
try {
|
||||
// 设置匿名登录token
|
||||
String token = res.bodyAsJsonObject().getJsonObject("data").getString("token");
|
||||
headers.set("X-Token", token);
|
||||
|
||||
// 获取文件夹信息
|
||||
httpClient.postAbs(SHARE_URL_API + "task/mgrtask").putHeaders(headers)
|
||||
.sendJsonObject(JsonObject.of(
|
||||
"tid", StringUtils.StringCutNot(key, this.key.startsWith(SHARE_URL_PREFIX) ? SHARE_URL_PREFIX : SHARE_URL_PREFIX2),
|
||||
"password", ""
|
||||
)).onSuccess(res2 -> {
|
||||
|
||||
if (res2.statusCode() == 200) {
|
||||
try {
|
||||
// 获取文件夹信息
|
||||
String filetime = res2.bodyAsJsonObject().getJsonObject("data").getString("expire"); // 文件夹剩余时间
|
||||
String filesize = res2.bodyAsJsonObject().getJsonObject("data").getString("file_size"); // 文件夹大小
|
||||
String filepid = res2.bodyAsJsonObject().getJsonObject("data").getString("ufileid"); // 文件夹pid
|
||||
String filebid = res2.bodyAsJsonObject().getJsonObject("data").getString("boxid"); // 文件夹bid
|
||||
|
||||
// 调试输出文件夹信息
|
||||
System.out.println("文件夹期限: " + filetime);
|
||||
System.out.println("文件夹大小: " + filesize);
|
||||
System.out.println("文件夹pid: " + filepid);
|
||||
System.out.println("文件夹bid: " + filebid);
|
||||
|
||||
// 获取文件信息
|
||||
httpClient.postAbs(SHARE_URL_API + "ufile/list").putHeaders(headers)
|
||||
.sendJsonObject(JsonObject.of(
|
||||
"start", 0,
|
||||
"sort", JsonObject.of(
|
||||
"name", "asc"
|
||||
),
|
||||
"bid", filebid,
|
||||
"pid", filepid,
|
||||
"type", 1,
|
||||
"options", JsonObject.of(
|
||||
"uploader", "true"
|
||||
),
|
||||
"size", 50
|
||||
)).onSuccess(res3 -> {
|
||||
|
||||
if (res3.statusCode() == 200) {
|
||||
try {
|
||||
// 获取文件信息
|
||||
String filename = res3.bodyAsJsonObject().getJsonObject("data")
|
||||
.getJsonArray("fileList").getJsonObject(0).getString("fname"); // 文件名称
|
||||
String filefid = res3.bodyAsJsonObject().getJsonObject("data")
|
||||
.getJsonArray("fileList").getJsonObject(0).getString("fid"); // 文件fid
|
||||
|
||||
// 调试输出文件信息
|
||||
System.out.println("文件名称: " + filename);
|
||||
System.out.println("文件fid: " + filefid);
|
||||
|
||||
// 检查文件是否失效
|
||||
httpClient.postAbs(SHARE_URL_API + "dl/sign").putHeaders(headers)
|
||||
.sendJsonObject(JsonObject.of(
|
||||
"consumeCode", 0,
|
||||
"type", 1,
|
||||
"ufileid", filefid
|
||||
)).onSuccess(res4 -> {
|
||||
|
||||
if (res4.statusCode() == 200) {
|
||||
try {
|
||||
// 获取直链
|
||||
String fileurl = res4.bodyAsJsonObject().getJsonObject("data").getString("url");
|
||||
|
||||
// 调试输出文件直链
|
||||
System.out.println("文件直链: " + fileurl);
|
||||
|
||||
if (!fileurl.equals(""))
|
||||
{
|
||||
try {
|
||||
promise.complete(URLDecoder.decode(fileurl, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
promise.complete(fileurl);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.fail("文件已失效");
|
||||
}
|
||||
|
||||
} catch (DecodeException | NullPointerException e) {
|
||||
this.fail("获取文件信息失败,可能是分享链接的方式已更新,或者对方的文件已失效");
|
||||
}
|
||||
} else {
|
||||
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} catch (DecodeException | NullPointerException e) {
|
||||
this.fail("获取文件信息失败,可能是分享链接的方式已更新");
|
||||
}
|
||||
} else {
|
||||
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
} catch (DecodeException | NullPointerException e) {
|
||||
this.fail("获取文件夹信息失败,可能是分享链接的方式已更新");
|
||||
}
|
||||
} else {
|
||||
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||
}
|
||||
|
||||
}).onFailure(this.handleFail(this.key));
|
||||
|
||||
} catch (DecodeException | NullPointerException e) {
|
||||
this.fail("token获取失败,可能是分享链接的方式已更新");
|
||||
}
|
||||
} else {
|
||||
this.fail("HTTP状态不正确,可能是分享链接的方式已更新");
|
||||
}
|
||||
|
||||
}).onFailure(this.handleFail(this.key));
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
162
parser/src/main/java/cn/qaiu/parser/impl/YeTool.java
Normal file
162
parser/src/main/java/cn/qaiu/parser/impl/YeTool.java
Normal file
@@ -0,0 +1,162 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.PanBase;
|
||||
import cn.qaiu.util.CommonUtils;
|
||||
import cn.qaiu.util.JsExecUtils;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
import io.vertx.uritemplate.UriTemplate;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 123网盘
|
||||
*/
|
||||
public class YeTool extends PanBase implements IPanTool {
|
||||
|
||||
public static final String SHARE_URL_PREFIX = "https://www.123pan.com/s/";
|
||||
public static final String FIRST_REQUEST_URL = SHARE_URL_PREFIX + "{key}.html";
|
||||
|
||||
private static final String GET_FILE_INFO_URL = "https://www.123pan.com/a/api/share/get?limit=100&next=1&orderBy" +
|
||||
"=file_name&orderDirection=asc" +
|
||||
"&shareKey={shareKey}&SharePwd={pwd}&ParentFileId=0&Page=1&event=homeListFile&operateType=1";
|
||||
private static final String DOWNLOAD_API_URL = "https://www.123pan.com/a/api/share/download/info?{authK}={authV}";
|
||||
|
||||
public YeTool(String key, String pwd) {
|
||||
super(key, pwd);
|
||||
}
|
||||
|
||||
public Future<String> parse() {
|
||||
|
||||
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
|
||||
|
||||
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("key", dataKey).send().onSuccess(res -> {
|
||||
|
||||
String html = res.bodyAsString();
|
||||
Pattern compile = Pattern.compile("window.g_initialProps\\s*=\\s*(.*);");
|
||||
Matcher matcher = compile.matcher(html);
|
||||
|
||||
if (!matcher.find()) {
|
||||
fail(html + "\n Ye: " + dataKey + " 正则匹配失败");
|
||||
return;
|
||||
}
|
||||
String fileInfoString = matcher.group(1);
|
||||
JsonObject fileInfoJson = new JsonObject(fileInfoString);
|
||||
JsonObject resJson = fileInfoJson.getJsonObject("res");
|
||||
JsonObject resListJson = fileInfoJson.getJsonObject("reslist");
|
||||
|
||||
if (resJson == null || resJson.getInteger("code") != 0) {
|
||||
fail(dataKey + " 解析到异常JSON: " + resJson);
|
||||
return;
|
||||
}
|
||||
String shareKey = resJson.getJsonObject("data").getString("ShareKey");
|
||||
if (resListJson == null || resListJson.getInteger("code") != 0) {
|
||||
// 加密分享
|
||||
if (StringUtils.isNotEmpty(pwd)) {
|
||||
client.getAbs(UriTemplate.of(GET_FILE_INFO_URL))
|
||||
.setTemplateParam("shareKey", shareKey)
|
||||
.setTemplateParam("pwd", pwd)
|
||||
// .setTemplateParam("authKey", AESUtils.getAuthKey("/a/api/share/get"))
|
||||
.putHeader("Platform", "web")
|
||||
.putHeader("App-Version", "3")
|
||||
.send().onSuccess(res2 -> {
|
||||
JsonObject infoJson = res2.bodyAsJsonObject();
|
||||
if (infoJson.getInteger("code") != 0) {
|
||||
fail("{} 状态码异常 {}", dataKey, infoJson);
|
||||
return;
|
||||
}
|
||||
JsonObject getFileInfoJson =
|
||||
infoJson.getJsonObject("data").getJsonArray("InfoList").getJsonObject(0);
|
||||
getFileInfoJson.put("ShareKey", shareKey);
|
||||
getDownUrl(client, getFileInfoJson);
|
||||
}).onFailure(this.handleFail(GET_FILE_INFO_URL));
|
||||
} else {
|
||||
fail("该分享[{}]需要密码",dataKey);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject reqBodyJson = resListJson.getJsonObject("data").getJsonArray("InfoList").getJsonObject(0);
|
||||
reqBodyJson.put("ShareKey", shareKey);
|
||||
getDownUrl(client, reqBodyJson);
|
||||
}).onFailure(this.handleFail(FIRST_REQUEST_URL));
|
||||
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
private void getDownUrl(WebClient client, JsonObject reqBodyJson) {
|
||||
log.info(reqBodyJson.encodePrettily());
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
// {"ShareKey":"iaKtVv-6OECd","FileID":2193732,"S3keyFlag":"1811834632-0","Size":4203111,
|
||||
// "Etag":"69c94adbc0b9190cf23c4e958d8c7c53"}
|
||||
jsonObject.put("ShareKey", reqBodyJson.getString("ShareKey"));
|
||||
jsonObject.put("FileID", reqBodyJson.getInteger("FileId"));
|
||||
jsonObject.put("S3keyFlag", reqBodyJson.getString("S3KeyFlag"));
|
||||
jsonObject.put("Size", reqBodyJson.getLong("Size"));
|
||||
jsonObject.put("Etag", reqBodyJson.getString("Etag"));
|
||||
|
||||
// 调用JS文件获取签名
|
||||
ScriptObjectMirror getSign;
|
||||
try {
|
||||
getSign = JsExecUtils.executeJs("getSign", "/a/api/share/download/info");
|
||||
} catch (Exception e) {
|
||||
fail(e, "JS函数执行异常");
|
||||
return;
|
||||
}
|
||||
log.info("ye getSign: {}={}", getSign.get("0").toString(), getSign.get("1").toString());
|
||||
|
||||
client.postAbs(UriTemplate.of(DOWNLOAD_API_URL))
|
||||
.setTemplateParam("authK", getSign.get("0").toString())
|
||||
.setTemplateParam("authV", getSign.get("1").toString())
|
||||
.putHeader("Platform", "web")
|
||||
.putHeader("App-Version", "3")
|
||||
.sendJsonObject(jsonObject).onSuccess(res2 -> {
|
||||
JsonObject downURLJson = res2.bodyAsJsonObject();
|
||||
|
||||
try {
|
||||
if (downURLJson.getInteger("code") != 0) {
|
||||
fail("Ye: downURLJson返回值异常->" + downURLJson);
|
||||
return;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
fail("Ye: downURLJson格式异常->" + downURLJson);
|
||||
return;
|
||||
}
|
||||
String downURL = downURLJson.getJsonObject("data").getString("DownloadURL");
|
||||
try {
|
||||
Map<String, String> urlParams = CommonUtils.getURLParams(downURL);
|
||||
String params = urlParams.get("params");
|
||||
byte[] decodeByte = Base64.getDecoder().decode(params);
|
||||
String downUrl2 = new String(decodeByte);
|
||||
|
||||
// 获取直链
|
||||
client.getAbs(downUrl2).send().onSuccess(res3 -> {
|
||||
JsonObject res3Json = res3.bodyAsJsonObject();
|
||||
try {
|
||||
if (res3Json.getInteger("code") != 0) {
|
||||
fail("Ye: downUrl2返回值异常->" + res3Json);
|
||||
return;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
fail("Ye: downUrl2格式异常->" + downURLJson);
|
||||
return;
|
||||
}
|
||||
|
||||
promise.complete(res3Json.getJsonObject("data").getString("redirect_url"));
|
||||
|
||||
}).onFailure(this.handleFail("获取直链失败"));
|
||||
|
||||
} catch (MalformedURLException e) {
|
||||
fail("urlParams解析异常" + e.getMessage());
|
||||
}
|
||||
}).onFailure(this.handleFail(DOWNLOAD_API_URL));
|
||||
}
|
||||
}
|
||||
331
parser/src/main/java/cn/qaiu/util/AESUtils.java
Normal file
331
parser/src/main/java/cn/qaiu/util/AESUtils.java
Normal file
@@ -0,0 +1,331 @@
|
||||
package cn.qaiu.util;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.crypto.*;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.HexFormat;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* AES加解密工具类
|
||||
*
|
||||
* @author qaiu
|
||||
**/
|
||||
public class AESUtils {
|
||||
|
||||
/**
|
||||
* AES密钥标识
|
||||
*/
|
||||
public static final String SIGN_AES = "AES";
|
||||
|
||||
/**
|
||||
* 密码器AES模式
|
||||
*/
|
||||
public static final String CIPHER_AES = "AES/ECB/PKCS5Padding";
|
||||
|
||||
public static final String CIPHER_AES2 = "YbQHZqK/PdQql2+7ATcPQHREAxt0Hn0Ob9v317QirZM=";
|
||||
|
||||
public static final String CIPHER_AES2_IZ = "1uQFS3sNeHd/bCrmrQpflXREAxt0Hn0Ob9v317QirZM=";
|
||||
|
||||
public static final String CIPHER_AES0;
|
||||
public static final String CIPHER_AES0_IZ;
|
||||
|
||||
/**
|
||||
* 秘钥长度
|
||||
*/
|
||||
public static final int KEY_LENGTH = 16;
|
||||
|
||||
/**
|
||||
* 密钥长度128
|
||||
*/
|
||||
public static final int KEY_SIZE_128_LENGTH = 128;
|
||||
|
||||
/**
|
||||
* 密钥长度192
|
||||
*/
|
||||
public static final int KEY_SIZE_192_LENGTH = 192;
|
||||
|
||||
/**
|
||||
* 密钥长度256
|
||||
*/
|
||||
public static final int KEY_SIZE_256_LENGTH = 256;
|
||||
|
||||
static {
|
||||
try {
|
||||
CIPHER_AES0 = decryptByBase64AES(CIPHER_AES2, CIPHER_AES);
|
||||
CIPHER_AES0_IZ = decryptByBase64AES(CIPHER_AES2_IZ, CIPHER_AES);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException | NoSuchAlgorithmException |
|
||||
InvalidKeyException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机生成密钥,请使用合适的长度128 192 256
|
||||
*/
|
||||
public static Key createKeyString(int keySize) throws NoSuchAlgorithmException {
|
||||
KeyGenerator keyGenerator = KeyGenerator.getInstance(SIGN_AES);
|
||||
keyGenerator.init(keySize);
|
||||
SecretKey secretKey = keyGenerator.generateKey();
|
||||
return new SecretKeySpec(secretKey.getEncoded(), SIGN_AES);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Key对象
|
||||
*/
|
||||
public static Key generateKey(String keyString) {
|
||||
if (keyString.length() > KEY_LENGTH) {
|
||||
keyString = keyString.substring(0, KEY_LENGTH);
|
||||
} else if (keyString.length() < KEY_LENGTH) {
|
||||
keyString = StringUtils.rightPad(keyString, 16, 'L');
|
||||
}
|
||||
return new SecretKeySpec(keyString.getBytes(), SIGN_AES);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES加密
|
||||
*
|
||||
* @param source 原文
|
||||
* @param keyString 秘钥
|
||||
* @return byte arrays
|
||||
*/
|
||||
public static byte[] encryptByAES(String source, String keyString) throws NoSuchPaddingException,
|
||||
NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
||||
Cipher cipher = Cipher.getInstance(CIPHER_AES);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, generateKey(keyString));
|
||||
return cipher.doFinal(source.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static byte[] encryptByAES(String source, Key key) throws NoSuchPaddingException,
|
||||
NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
||||
Cipher cipher = Cipher.getInstance(CIPHER_AES);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
return cipher.doFinal(source.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* AES加密Base64
|
||||
*
|
||||
* @param source 原文
|
||||
* @param keyString 秘钥
|
||||
* @return BASE64
|
||||
*/
|
||||
public static String encryptBase64ByAES(String source, String keyString) throws NoSuchPaddingException,
|
||||
NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
||||
byte[] encrypted = encryptByAES(source, keyString);
|
||||
return Base64.getEncoder().encodeToString(encrypted);
|
||||
}
|
||||
|
||||
public static String encryptBase64ByAES(String source, Key key) throws NoSuchPaddingException,
|
||||
NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
||||
byte[] encrypted = encryptByAES(source, key);
|
||||
return Base64.getEncoder().encodeToString(encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES加密Hex
|
||||
*
|
||||
* @param source 原文
|
||||
* @param keyString 秘钥
|
||||
*/
|
||||
public static String encryptHexByAES(String source, String keyString) throws NoSuchPaddingException,
|
||||
NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
||||
byte[] encrypted = encryptByAES(source, keyString);
|
||||
return HexFormat.of().formatHex(encrypted);
|
||||
}
|
||||
|
||||
public static String encryptHexByAES(String source, Key key) throws NoSuchPaddingException,
|
||||
NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
||||
byte[] encrypted = encryptByAES(source, key);
|
||||
return HexFormat.of().formatHex(encrypted);
|
||||
}
|
||||
|
||||
public static String encrypt2Hex(String source) {
|
||||
try {
|
||||
return encryptHexByAES(source, CIPHER_AES0);
|
||||
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException |
|
||||
BadPaddingException e) {
|
||||
throw new RuntimeException("加密失败: "+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static String encrypt2HexIz(String source) {
|
||||
try {
|
||||
return encryptHexByAES(source, CIPHER_AES0_IZ);
|
||||
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException |
|
||||
BadPaddingException e) {
|
||||
throw new RuntimeException("加密失败: "+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密
|
||||
*
|
||||
* @param encrypted 密文 byte
|
||||
* @param keyString 秘钥
|
||||
*/
|
||||
public static String decryptByAES(byte[] encrypted, String keyString) throws IllegalBlockSizeException,
|
||||
BadPaddingException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
|
||||
return decryptByAES(encrypted, generateKey(keyString));
|
||||
}
|
||||
|
||||
public static String decryptByAES(byte[] encrypted, Key key) throws IllegalBlockSizeException,
|
||||
BadPaddingException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
|
||||
Cipher cipher = Cipher.getInstance(CIPHER_AES);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key);
|
||||
byte[] decrypted = cipher.doFinal(encrypted);
|
||||
return new String(decrypted, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密
|
||||
*
|
||||
* @param encrypted 密文 Hex
|
||||
* @param keyString 秘钥
|
||||
*/
|
||||
public static String decryptByHexAES(String encrypted, String keyString) throws IllegalBlockSizeException,
|
||||
BadPaddingException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
|
||||
return decryptByAES(HexFormat.of().parseHex(encrypted), keyString);
|
||||
}
|
||||
|
||||
public static String decryptByHexAES(String encrypted, Key key) throws IllegalBlockSizeException,
|
||||
BadPaddingException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
|
||||
return decryptByAES(HexFormat.of().parseHex(encrypted), key);
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密
|
||||
*
|
||||
* @param encrypted 密文 Base64
|
||||
* @param keyString 秘钥
|
||||
*/
|
||||
public static String decryptByBase64AES(String encrypted, String keyString) throws IllegalBlockSizeException,
|
||||
BadPaddingException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
|
||||
return decryptByAES(Base64.getDecoder().decode(encrypted), keyString);
|
||||
}
|
||||
|
||||
public static String decryptByBase64AES(String encrypted, Key key) throws IllegalBlockSizeException,
|
||||
BadPaddingException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException {
|
||||
return decryptByAES(Base64.getDecoder().decode(encrypted), key);
|
||||
}
|
||||
|
||||
// ================================飞机盘Id解密========================================== //
|
||||
private static final char[] array = {
|
||||
'T', 'U', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'0', 'M', 'N', 'O', 'P', 'X', 'Y', 'Z', 'V', 'W',
|
||||
'Q', '1', '2', '3', '4', 'a', 'b', 'c', 'd', 'e',
|
||||
'5', '6', '7', '8', '9', 'v', 'w', 'x', 'y', 'z',
|
||||
'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
|
||||
'p', 'q', 'r', 's', 't', 'u', 'L', 'R', 'S', 'I',
|
||||
'J', 'K'};
|
||||
private static final char[] arrayIz =
|
||||
{'Y', 'y', '0', 'Z', 'z', 'N', 'n', 'M', 'I', '6', 'm', 'W', 'w', '1', 'X', 'x', 'L', 'l', 'K', '7', 'k',
|
||||
'i', 'U', 'u', '2', 'V', 'v', 'J', 'j', '8', 'G', 'g', 'F', 'S', 's', '3', 'T', 't', 'H', 'h',
|
||||
'f', 'E', 'e', 'D', 'Q', 'q', '4', 'R', 'r', '9', 'd', 'a', 'C', 'c', 'B', 'O', 'o', '5', 'P',
|
||||
'p', 'b', 'A'};
|
||||
private static int decodeChar(char c, char[] keys) {
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
if (c == keys[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// id解密
|
||||
public static int idEncrypt(String str) {
|
||||
return idEncrypt0(str, array, 2, 2);
|
||||
}
|
||||
|
||||
// ================================蓝奏优享版Id解密========================================== //
|
||||
public static int idEncryptIz(String str) {
|
||||
// idEncrypt(e) {
|
||||
// let t = 1
|
||||
// , n = 0;
|
||||
// if ("" != e && e.length > 4) {
|
||||
// let r;
|
||||
// e = e.substring(3, e.length - 1);
|
||||
// for (let v = 0; v < e.length; v++)
|
||||
// r = e.charAt(e.length - v - 1),
|
||||
// n += this.decodeChar(r) * t,
|
||||
// t *= 62
|
||||
// }
|
||||
// return n
|
||||
// },
|
||||
|
||||
return idEncrypt0(str, arrayIz, 3, 1);
|
||||
}
|
||||
|
||||
public static int idEncrypt0(String str, char[] keys, int x1, int x2) {
|
||||
// 倍数
|
||||
int multiple = 1;
|
||||
int result = 0;
|
||||
if (StringUtils.isNotEmpty(str) && str.length() > 4) {
|
||||
str = str.substring(x1, str.length() - x2);
|
||||
char c;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
c = str.charAt(str.length() - i - 1);
|
||||
result += decodeChar(c, keys) * multiple;
|
||||
multiple = multiple * 62;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ========================== musetransfer加密相关 ===========================
|
||||
|
||||
//length用户要求产生字符串的长度
|
||||
public static String getRandomString(int length){
|
||||
String str="abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
Random random=new Random();
|
||||
StringBuilder sb=new StringBuilder();
|
||||
for(int i=0;i<length;i++){
|
||||
int number=random.nextInt(36);
|
||||
sb.append(str.charAt(number));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getRandomString(){
|
||||
return getRandomString(10);
|
||||
}
|
||||
|
||||
|
||||
//=============================== 123pan加密相关 ===============================
|
||||
|
||||
public static String getMD5Str(String str) {
|
||||
byte[] digest;
|
||||
try {
|
||||
MessageDigest md5 = MessageDigest.getInstance("md5");
|
||||
digest = md5.digest(str.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
//16是表示转换为16进制数
|
||||
return new BigInteger(1, digest).toString(16);
|
||||
}
|
||||
|
||||
public static String getAuthKey(String _0x2207af) {
|
||||
String _0x467baa = "web";
|
||||
int _0x4965f1 = 3;
|
||||
|
||||
String _0x430930 = String.valueOf(Math.round(0x989680 * Math.random()));
|
||||
String _0x53928f = String.valueOf(new Date().getTime() / 0x3e8);
|
||||
String _0x49ec94 = getMD5Str(_0x53928f + "|" + _0x430930 + "|" + _0x2207af + "|" + _0x467baa + "|" + _0x4965f1
|
||||
+ "|8-8D$sL8gPjom7bk#cY");
|
||||
|
||||
return _0x53928f + "-" + _0x430930 + "-" + _0x49ec94;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.qaiu.lz.common.util;
|
||||
package cn.qaiu.util;
|
||||
|
||||
public class ArrayUtil {
|
||||
|
||||
47
parser/src/main/java/cn/qaiu/util/CommonUtils.java
Normal file
47
parser/src/main/java/cn/qaiu/util/CommonUtils.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package cn.qaiu.util;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CommonUtils {
|
||||
|
||||
/**
|
||||
* 获取分享key 比如: https://www.ilanzou.com/s/xxx -> xxx
|
||||
* @param urlPrefix 不包含key的URL前缀
|
||||
* @param url 完整URL
|
||||
* @return 分享key
|
||||
*/
|
||||
public static String adaptShortPaths(String urlPrefix, String url) {
|
||||
if (url.endsWith(".html")) {
|
||||
url = url.substring(0, url.length() - 5);
|
||||
}
|
||||
String prefix = "https://";
|
||||
if (!url.startsWith(urlPrefix) && url.startsWith(prefix)) {
|
||||
urlPrefix = urlPrefix.substring(prefix.length());
|
||||
return url.substring(url.indexOf(urlPrefix) + urlPrefix.length());
|
||||
} else if (!url.startsWith(urlPrefix)) {
|
||||
url = urlPrefix + url;
|
||||
}
|
||||
return url.substring(urlPrefix.length());
|
||||
}
|
||||
|
||||
public static Map<String, String> getURLParams(String url) throws MalformedURLException {
|
||||
URL fullUrl = new URL(url);
|
||||
String query = fullUrl.getQuery();
|
||||
String[] params = query.split("&");
|
||||
Map<String, String> map = new HashMap<>();
|
||||
for (String param : params) {
|
||||
if (!param.contains("=")) {
|
||||
throw new RuntimeException("解析URL异常: 匹配不到参数中的=");
|
||||
}
|
||||
int endIndex = param.indexOf('=');
|
||||
String key = param.substring(0, endIndex);
|
||||
String value = param.substring(endIndex + 1);
|
||||
map.put(key, value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
152
parser/src/main/java/cn/qaiu/util/JsContent.java
Normal file
152
parser/src/main/java/cn/qaiu/util/JsContent.java
Normal 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'
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
""";
|
||||
|
||||
}
|
||||
60
parser/src/main/java/cn/qaiu/util/JsExecUtils.java
Normal file
60
parser/src/main/java/cn/qaiu/util/JsExecUtils.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package cn.qaiu.util;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||
|
||||
import javax.script.Invocable;
|
||||
import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
|
||||
/**
|
||||
* 执行Js脚本
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
* @date 2023/7/29 17:35
|
||||
*/
|
||||
public class JsExecUtils {
|
||||
private static final Invocable inv;
|
||||
|
||||
// 初始化脚本引擎
|
||||
static {
|
||||
ScriptEngineManager engineManager = new ScriptEngineManager();
|
||||
ScriptEngine engine = engineManager.getEngineByName("JavaScript"); // 得到脚本引擎
|
||||
|
||||
try {
|
||||
engine.eval(JsContent.ye123);
|
||||
inv = (Invocable) engine;
|
||||
} catch (ScriptException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用js文件
|
||||
*/
|
||||
public static ScriptObjectMirror executeJs(String functionName, Object... args) throws ScriptException,
|
||||
NoSuchMethodException {
|
||||
//调用js中的函数
|
||||
return (ScriptObjectMirror) inv.invokeFunction(functionName, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用执行蓝奏云js文件
|
||||
*/
|
||||
public static ScriptObjectMirror executeDynamicJs(String jsText, String funName) throws ScriptException,
|
||||
NoSuchMethodException {
|
||||
ScriptEngineManager engineManager = new ScriptEngineManager();
|
||||
ScriptEngine engine = engineManager.getEngineByName("JavaScript"); // 得到脚本引擎
|
||||
engine.eval(JsContent.lz + "\n" + jsText);
|
||||
Invocable inv = (Invocable) engine;
|
||||
//调用js中的函数
|
||||
if (StringUtils.isNotEmpty(funName)) {
|
||||
inv.invokeFunction(funName);
|
||||
}
|
||||
|
||||
return (ScriptObjectMirror) engine.get("signObj");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
12
parser/src/main/java/cn/qaiu/util/PanExceptionUtils.java
Normal file
12
parser/src/main/java/cn/qaiu/util/PanExceptionUtils.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package cn.qaiu.util;
|
||||
|
||||
/**
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
* @date 2023/7/16 1:53
|
||||
*/
|
||||
public class PanExceptionUtils {
|
||||
|
||||
public static RuntimeException fillRunTimeException(String name, String dataKey, Throwable t) {
|
||||
return new RuntimeException(name + ": 请求异常: key = " + dataKey, t.fillInStackTrace());
|
||||
}
|
||||
}
|
||||
36
parser/src/main/java/cn/qaiu/util/StringUtils.java
Normal file
36
parser/src/main/java/cn/qaiu/util/StringUtils.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
46
parser/src/main/resources/js/lz.js
Normal file
46
parser/src/main/resources/js/lz.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 蓝奏云解析器js签名获取工具
|
||||
*/
|
||||
|
||||
var signObj;
|
||||
|
||||
|
||||
var $, jQuery;
|
||||
|
||||
$ = jQuery = function () {
|
||||
return new jQuery.fn.init();
|
||||
}
|
||||
|
||||
jQuery.fn = jQuery.prototype = {
|
||||
init: function () {
|
||||
return {
|
||||
focus: function (a) {
|
||||
|
||||
},
|
||||
keyup: function(a) {
|
||||
|
||||
},
|
||||
ajax: function (obj) {
|
||||
signObj = obj
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
jQuery.fn.init.prototype = jQuery.fn;
|
||||
|
||||
|
||||
// 伪装jquery.ajax函数获取关键数据
|
||||
$.ajax = function (obj) {
|
||||
signObj = obj
|
||||
}
|
||||
|
||||
var document = {
|
||||
getElementById: function (v) {
|
||||
return {
|
||||
value: 'v'
|
||||
}
|
||||
},
|
||||
}
|
||||
95
parser/src/main/resources/js/ye123.js
Normal file
95
parser/src/main/resources/js/ye123.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
https://statics.123pan.com/share-static/dist/umi.fb72555e.js
|
||||
eaefamemdead
|
||||
eaefameidldy
|
||||
_0x4f141a(1690439821|5790548|/b/api/share/download/info|web|3|1946841013) = 秘钥
|
||||
|
||||
_0x1e2592 1690439821 时间戳
|
||||
_0x48562f 5790548 随机码
|
||||
_0x1e37d5 /b/api/share/download/info
|
||||
_0x4e2d74 web
|
||||
_0x56f040 3
|
||||
_0x43bdc6 1946841013 加密时间HASH戳
|
||||
|
||||
>>>>
|
||||
_0x43bdc6=''['concat'](_0x1e2592, '-')['concat'](_0x48562f, '-')['concat'](_0x406c4e)
|
||||
加密时间HASH戳 = 时间戳-随机码-秘钥
|
||||
*/
|
||||
|
||||
function _0x1b5d95(_0x278d1a) {
|
||||
var _0x839b57,
|
||||
_0x4ed4dc = arguments['length'] > 0x2 && void 0x0 !== arguments[0x2] ? arguments[0x2] : 0x8;
|
||||
if (0x0 === arguments['length'])
|
||||
return null;
|
||||
'object' === typeof _0x278d1a ? _0x839b57 = _0x278d1a : (0xa === ('' + _0x278d1a)['length'] && (_0x278d1a = 0x3e8 * parseInt(_0x278d1a)),
|
||||
_0x839b57 = new Date(_0x278d1a));
|
||||
var _0xc5c54a = _0x278d1a + 0xea60 * new Date(_0x278d1a)['getTimezoneOffset']()
|
||||
, _0x3732dc = _0xc5c54a + 0x36ee80 * _0x4ed4dc;
|
||||
return _0x839b57 = new Date(_0x3732dc),
|
||||
{
|
||||
'y': _0x839b57['getFullYear'](),
|
||||
'm': _0x839b57['getMonth']() + 0x1 < 0xa ? '0' + (_0x839b57['getMonth']() + 0x1) : _0x839b57['getMonth']() + 0x1,
|
||||
'd': _0x839b57['getDate']() < 0xa ? '0' + _0x839b57['getDate']() : _0x839b57['getDate'](),
|
||||
'h': _0x839b57['getHours']() < 0xa ? '0' + _0x839b57['getHours']() : _0x839b57['getHours'](),
|
||||
'f': _0x839b57['getMinutes']() < 0xa ? '0' + _0x839b57['getMinutes']() : _0x839b57['getMinutes']()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function _0x4f141a(_0x4075b1) {
|
||||
|
||||
for (var _0x4eddcb = arguments['length'] > 0x1 && void 0x0 !== arguments[0x1] ? arguments[0x1] : 0xa,
|
||||
_0x2fc680 = function() {
|
||||
for (var _0x515c63, _0x361314 = [], _0x4cbdba = 0x0; _0x4cbdba < 0x100; _0x4cbdba++) {
|
||||
_0x515c63 = _0x4cbdba;
|
||||
for (var _0x460960 = 0x0; _0x460960 < 0x8; _0x460960++)
|
||||
_0x515c63 = 0x1 & _0x515c63 ? 0xedb88320 ^ _0x515c63 >>> 0x1 : _0x515c63 >>> 0x1;
|
||||
_0x361314[_0x4cbdba] = _0x515c63;
|
||||
}
|
||||
return _0x361314;
|
||||
},
|
||||
_0x4aed86 = _0x2fc680(),
|
||||
_0x5880f0 = _0x4075b1,
|
||||
_0x492393 = -0x1, _0x25d82c = 0x0;
|
||||
_0x25d82c < _0x5880f0['length'];
|
||||
_0x25d82c++)
|
||||
|
||||
_0x492393 = _0x492393 >>> 0x8 ^ _0x4aed86[0xff & (_0x492393 ^ _0x5880f0.charCodeAt(_0x25d82c))];
|
||||
return _0x492393 = (-0x1 ^ _0x492393) >>> 0x0,
|
||||
_0x492393.toString(_0x4eddcb);
|
||||
}
|
||||
|
||||
|
||||
function getSign(_0x1e37d5) {
|
||||
var _0x4e2d74 = 'web';
|
||||
var _0x56f040 = 3;
|
||||
var _0x1e2592 = Math.round((new Date().getTime() + 0x3c * new Date().getTimezoneOffset() * 0x3e8 + 28800000) / 0x3e8).toString();
|
||||
var key = 'a,d,e,f,g,h,l,m,y,i,j,n,o,p,k,q,r,s,t,u,b,c,v,w,s,z';
|
||||
var _0x48562f = Math['round'](0x989680 * Math['random']());
|
||||
|
||||
var _0x2f7dfc;
|
||||
var _0x35a889;
|
||||
var _0x36f983;
|
||||
var _0x3b043d;
|
||||
var _0x5bc73b;
|
||||
var _0x4b30b2;
|
||||
var _0x32399e;
|
||||
var _0x25d94e;
|
||||
var _0x373490;
|
||||
for (var _0x1c540f in (_0x2f7dfc = key.split(','),
|
||||
_0x35a889 = _0x1b5d95(_0x1e2592),
|
||||
_0x36f983 = _0x35a889['y'],
|
||||
_0x3b043d = _0x35a889['m'],
|
||||
_0x5bc73b = _0x35a889['d'],
|
||||
_0x4b30b2 = _0x35a889['h'],
|
||||
_0x32399e = _0x35a889['f'],
|
||||
_0x25d94e = [_0x36f983, _0x3b043d, _0x5bc73b, _0x4b30b2, _0x32399e].join(''),
|
||||
_0x373490 = [],
|
||||
_0x25d94e))
|
||||
_0x373490['push'](_0x2f7dfc[Number(_0x25d94e[_0x1c540f])]);
|
||||
var _0x43bdc6;
|
||||
var _0x406c4e;
|
||||
return _0x43bdc6 = _0x4f141a(_0x373490['join']('')),
|
||||
_0x406c4e = _0x4f141a(''['concat'](_0x1e2592, '|')['concat'](_0x48562f, '|')['concat'](_0x1e37d5, '|')['concat'](_0x4e2d74, '|')['concat'](_0x56f040, '|')['concat'](_0x43bdc6)),
|
||||
[_0x43bdc6, ''['concat'](_0x1e2592, '-')['concat'](_0x48562f, '-')['concat'](_0x406c4e)];
|
||||
}
|
||||
61
parser/src/main/resources/logback.xml
Normal file
61
parser/src/main/resources/logback.xml
Normal 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>
|
||||
92
parser/src/test/java/qaiu/web/test/TestAESUtil.java
Normal file
92
parser/src/test/java/qaiu/web/test/TestAESUtil.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package qaiu.web.test;
|
||||
|
||||
import cn.qaiu.util.AESUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HexFormat;
|
||||
|
||||
public class TestAESUtil {
|
||||
|
||||
// 1686215935703
|
||||
// B4C5B9833113ACA41F16AABADE17349C
|
||||
@Test
|
||||
public void decode() throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException,
|
||||
BadPaddingException, InvalidKeyException {
|
||||
String hex = AESUtils.encryptHexByAES("1686215935703", AESUtils.CIPHER_AES2);
|
||||
Assert.assertEquals("B4C5B9833113ACA41F16AABADE17349C", hex.toUpperCase());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encode() throws IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException,
|
||||
NoSuchAlgorithmException, InvalidKeyException {
|
||||
String source = AESUtils.decryptByHexAES("B4C5B9833113ACA41F16AABADE17349C", AESUtils.CIPHER_AES2);
|
||||
Assert.assertEquals("1686215935703", source);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toHex() {
|
||||
byte[] d234EF67A1s = HexFormat.of().parseHex("D234EF67A1");
|
||||
Assert.assertArrayEquals(new byte[]{(byte) 0xd2, (byte) 0x34, (byte) 0xef, (byte) 0x67, (byte) 0xa1},
|
||||
d234EF67A1s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void base64AES() throws NoSuchAlgorithmException {
|
||||
System.out.println(HexFormat.of().formatHex(AESUtils.createKeyString(AESUtils.KEY_SIZE_128_LENGTH).getEncoded()));
|
||||
System.out.println(HexFormat.of().formatHex(AESUtils.createKeyString(AESUtils.KEY_SIZE_192_LENGTH).getEncoded()));
|
||||
System.out.println(HexFormat.of().formatHex(AESUtils.createKeyString(AESUtils.KEY_SIZE_256_LENGTH).getEncoded()));
|
||||
|
||||
// TODO Base64-AES
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdDecode() {
|
||||
Assert.assertEquals(146731, AESUtils.idEncrypt("7jy0zlv"));
|
||||
}
|
||||
|
||||
// 蓝奏优享
|
||||
@Test
|
||||
public void testIzIdDecode() {
|
||||
Assert.assertEquals(26216, AESUtils.idEncryptIz("lGFndCM"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test00() throws IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException,
|
||||
NoSuchAlgorithmException, InvalidKeyException {
|
||||
System.out.println(AESUtils.decryptByBase64AES(AESUtils.CIPHER_AES2, AESUtils.CIPHER_AES));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTs() {
|
||||
System.out.println(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRandom() {
|
||||
System.out.println(AESUtils.getRandomString());
|
||||
System.out.println(AESUtils.getRandomString());
|
||||
System.out.println(AESUtils.getRandomString());
|
||||
System.out.println(AESUtils.getRandomString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyAuth(){
|
||||
System.out.println(AESUtils.getAuthKey("/a/api/share/download/info"));
|
||||
System.out.println(AESUtils.getAuthKey("/a/api/share/download/info"));
|
||||
System.out.println(AESUtils.getAuthKey("/b/api/share/get"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAES2() throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException,
|
||||
BadPaddingException, InvalidKeyException {
|
||||
System.out.println(AESUtils.encryptBase64ByAES("AAAAA", "123123"));
|
||||
System.out.println(AESUtils.encryptBase64ByAES("AAAAA", AESUtils.generateKey("123123")));
|
||||
}
|
||||
}
|
||||
24
parser/src/test/java/qaiu/web/test/TestRegex.java
Normal file
24
parser/src/test/java/qaiu/web/test/TestRegex.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.qaiu.web.test;
|
||||
package qaiu.web.test;
|
||||
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.Vertx;
|
||||
23
pom.xml
23
pom.xml
@@ -7,19 +7,31 @@
|
||||
<groupId>cn.qaiu</groupId>
|
||||
<artifactId>netdisk-fast-download</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>0.1.3</version>
|
||||
<version>0.1.7</version>
|
||||
|
||||
<modules>
|
||||
<module>core</module>
|
||||
<module>web-service</module>
|
||||
<module>core-database</module>
|
||||
<module>parser</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<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.12</lombok.version>
|
||||
<slf4j.version>2.0.5</slf4j.version>
|
||||
<commons-lang3.version>3.12.0</commons-lang3.version>
|
||||
<jackson.version>2.14.2</jackson.version>
|
||||
<logback.version>1.4.12</logback.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
@@ -104,14 +116,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>
|
||||
|
||||
25
web-front/.gitignore
vendored
Normal file
25
web-front/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
dist.zip
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
/nfd-front.zip
|
||||
/nfd-front
|
||||
26
web-front/README.md
Normal file
26
web-front/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# nfd-web
|
||||
当前页面修改自开源项目 https://github.com/HurryBy/CloudDiskAnalysis
|
||||
|
||||

|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
5
web-front/babel.config.js
Normal file
5
web-front/babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
19
web-front/jsconfig.json
Normal file
19
web-front/jsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
||||
13867
web-front/package-lock.json
generated
Normal file
13867
web-front/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
50
web-front/package.json
Normal file
50
web-front/package.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "nfd-web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
"core-js": "^3.8.3",
|
||||
"element-ui": "^2.15.12",
|
||||
"vue": "^2.6.14",
|
||||
"vue-clipboard2": "^0.3.3",
|
||||
"vue-json-viewer": "^2.2.22"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.16",
|
||||
"@babel/eslint-parser": "^7.12.16",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"vue-template-compiler": "^2.6.14",
|
||||
|
||||
"compression-webpack-plugin": "^6.1.1",
|
||||
"filemanager-webpack-plugin": "2.0.5"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "@babel/eslint-parser"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
BIN
web-front/public/avator.png
Normal file
BIN
web-front/public/avator.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
web-front/public/favicon.ico
Normal file
BIN
web-front/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
168
web-front/public/index.html
Normal file
168
web-front/public/index.html
Normal file
@@ -0,0 +1,168 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cn">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Netdisk fast download</title>
|
||||
<meta name="keywords"
|
||||
content="Netdisk fast download,网盘直链解析工具">
|
||||
<meta name="description"
|
||||
content="Netdisk fast download 网盘直链解析工具">
|
||||
<script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script>
|
||||
<script>LA.init({id:"K8zkCkZMgFA6ShZK",ck:"K8zkCkZMgFA6ShZK"})</script>
|
||||
|
||||
<style>
|
||||
.page-loading-wrap {
|
||||
padding: 120px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.la-timer,
|
||||
.la-timer>div {
|
||||
position: relative;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.la-timer {
|
||||
display: block;
|
||||
font-size: 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.la-timer.la-dark {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.la-timer>div {
|
||||
display: inline-block;
|
||||
float: none;
|
||||
background-color: currentColor;
|
||||
border: 0 solid currentColor;
|
||||
}
|
||||
|
||||
.la-timer {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.la-timer>div {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: transparent;
|
||||
border-width: 2px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.la-timer>div:before,
|
||||
.la-timer>div:after {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
left: 14px;
|
||||
display: block;
|
||||
width: 2px;
|
||||
margin-top: -1px;
|
||||
margin-left: -1px;
|
||||
content: "";
|
||||
background: currentColor;
|
||||
border-radius: 2px;
|
||||
-webkit-transform-origin: 1px 1px 0;
|
||||
-moz-transform-origin: 1px 1px 0;
|
||||
-ms-transform-origin: 1px 1px 0;
|
||||
-o-transform-origin: 1px 1px 0;
|
||||
transform-origin: 1px 1px 0;
|
||||
-webkit-animation: timer-loader 1250ms infinite linear;
|
||||
-moz-animation: timer-loader 1250ms infinite linear;
|
||||
-o-animation: timer-loader 1250ms infinite linear;
|
||||
animation: timer-loader 1250ms infinite linear;
|
||||
-webkit-animation-delay: -625ms;
|
||||
-moz-animation-delay: -625ms;
|
||||
-o-animation-delay: -625ms;
|
||||
animation-delay: -625ms;
|
||||
}
|
||||
|
||||
.la-timer>div:before {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.la-timer>div:after {
|
||||
height: 8px;
|
||||
-webkit-animation-duration: 15s;
|
||||
-moz-animation-duration: 15s;
|
||||
-o-animation-duration: 15s;
|
||||
animation-duration: 15s;
|
||||
-webkit-animation-delay: -7.5s;
|
||||
-moz-animation-delay: -7.5s;
|
||||
-o-animation-delay: -7.5s;
|
||||
animation-delay: -7.5s;
|
||||
}
|
||||
|
||||
@-webkit-keyframes timer-loader {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes timer-loader {
|
||||
0% {
|
||||
-moz-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-moz-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes timer-loader {
|
||||
0% {
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes timer-loader {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-moz-transform: rotate(0deg);
|
||||
-o-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-moz-transform: rotate(360deg);
|
||||
-o-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="page-loading-wrap">
|
||||
<div style="color: #4e9ff5" class="la-timer">
|
||||
<div></div>
|
||||
</div>
|
||||
<h4 style="margin-top:20px;font-size:14px;font-weight:400;color: #515a6e;">正在加载资源...</h4>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
269
web-front/src/App.vue
Normal file
269
web-front/src/App.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<el-row :gutter="20">
|
||||
<el-card class="box-card">
|
||||
<div class="demo-basic--circle">
|
||||
<div class="block" style="text-align: center;">
|
||||
<el-avatar :size="150" :src="avatar"></el-avatar>
|
||||
</div>
|
||||
</div>
|
||||
<h3 style="text-align: center;">NFD网盘直链解析(API演示)</h3>
|
||||
<div class="typo">
|
||||
<p><strong>项目GitHub </strong><a href="https://github.com/qaiu/netdisk-fast-download" target="_blank"
|
||||
rel="nofollow"><u>netdisk-fast-download</u></a></p>
|
||||
<p><strong>当前页面修改自开源项目</strong><a href="https://github.com/HurryBy/CloudDiskAnalysis"
|
||||
target="_blank"
|
||||
rel="nofollow"><u>CloudDiskAnalysis</u></a></p>
|
||||
<p><strong>目前支持 </strong>已支持蓝奏云/奶牛快传/移动云云空间/UC网盘(暂时失效)/小飞机盘/亿方云/123云盘</p>
|
||||
<p>
|
||||
<el-button><strong @click="getInfo">刷新API调用统计</strong></el-button>
|
||||
</p>
|
||||
<p>节点1: 成功:{{ node1Info.success }},失败:{{ node1Info.fail }},总数:{{ node1Info.total }}</p>
|
||||
<p>节点2: 成功:{{ node2Info.success }},失败:{{ node2Info.fail }},总数:{{ node2Info.total }}</p>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="main" v-loading="isLoading">
|
||||
<div class="grid-content">
|
||||
<el-input placeholder="请粘贴分享链接" v-model="link" id="url" lass="input-with-select">
|
||||
<strong slot="prepend">分享链接</strong>
|
||||
<el-button slot="append" @click="onSubmit">解析</el-button>
|
||||
</el-input>
|
||||
<el-input placeholder="请输入密码" v-model="password" id="url" lass="input-with-select"></el-input>
|
||||
<el-input v-show="respData.data" placeholder="解析地址" :value="getLink2" id="url" lass="input-with-select">
|
||||
<el-button slot="append" v-clipboard:copy="getLink2"
|
||||
v-clipboard:success="onCopy"
|
||||
v-clipboard:error="onError">点我复制
|
||||
</el-button>
|
||||
</el-input>
|
||||
</div>
|
||||
<div v-show="respData.code" style="margin-top: 10px">
|
||||
<strong>解析结果: </strong>
|
||||
<json-viewer
|
||||
:value="respData"
|
||||
:expand-depth=5
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
/>
|
||||
<a :href="downUrl" v-show="downUrl">点击下载</a>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
/*
|
||||
蓝奏云 (lz)
|
||||
登录, 上传, 下载, 分享
|
||||
直链解析
|
||||
奶牛快传 (cow)
|
||||
登录, 上传, 下载, 分享
|
||||
直链解析
|
||||
移动云空间 (ec)
|
||||
登录, 上传, 下载, 分享
|
||||
直链解析
|
||||
UC网盘 (uc)似乎已经失效,需要登录
|
||||
登录, 上传, 下载, 分享
|
||||
直链解析
|
||||
小飞机网盘 (fj)
|
||||
登录, 上传, 下载, 分享
|
||||
直链解析
|
||||
亿方云 (fc)
|
||||
登录, 上传, 下载, 分享
|
||||
直链解析
|
||||
123云盘 (ye)
|
||||
登录, 上传, 下载,, 分享
|
||||
*/
|
||||
export default {
|
||||
name: 'App',
|
||||
data() {
|
||||
return {
|
||||
link: "",
|
||||
password: "",
|
||||
isLoading: false,
|
||||
downUrl: null,
|
||||
avatar: "https://q2.qlogo.cn/headimg_dl?dst_uin=736226400&spec=640",
|
||||
select: "lz",
|
||||
respData: {},
|
||||
panList: [
|
||||
{
|
||||
name: "蓝奏云",
|
||||
value: 'lz'
|
||||
},
|
||||
{
|
||||
name: "奶牛快传",
|
||||
value: 'cow'
|
||||
},
|
||||
{
|
||||
name: "移动云空间",
|
||||
value: 'ec'
|
||||
},
|
||||
{
|
||||
name: "UC网盘",
|
||||
value: 'uc',
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
name: "小飞机网盘",
|
||||
value: 'fj'
|
||||
},
|
||||
{
|
||||
name: "360亿方云",
|
||||
value: 'fc'
|
||||
},
|
||||
{
|
||||
name: "123云盘",
|
||||
value: 'ye'
|
||||
},
|
||||
],
|
||||
getLink: '',
|
||||
getLink2: '',
|
||||
node1Info: {},
|
||||
node2Info: {},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
if (!this.link.startsWith("https://")) {
|
||||
this.$message.error("请输入有效链接!")
|
||||
return
|
||||
}
|
||||
this.isLoading = true
|
||||
this.downUrl = ''
|
||||
this.respData = {}
|
||||
this.getLink2 = `${location.protocol}//${location.host}/parser?url=${this.link}`
|
||||
// this.getLink = `${location.protocol}//${location.host}/api/json/parser?url=${this.link}`
|
||||
this.getLink = `${location.protocol}//${location.host}/json/parser?url=${this.link}`
|
||||
if (this.password) {
|
||||
this.getLink += `&pwd=${this.password}`
|
||||
}
|
||||
axios.get(this.getLink).then(
|
||||
response => {
|
||||
this.isLoading = false
|
||||
this.respData = response.data
|
||||
if (response.data.code === 200) {
|
||||
this.$message({
|
||||
message: response.data.msg,
|
||||
type: 'success'
|
||||
})
|
||||
this.downUrl = response.data.data
|
||||
} else {
|
||||
this.$message.error(response.data.msg)
|
||||
}
|
||||
},
|
||||
error => {
|
||||
this.isLoading = false
|
||||
this.$message.error(error.message)
|
||||
}
|
||||
)
|
||||
},
|
||||
onCopy() {
|
||||
this.$message.success('复制成功')
|
||||
},
|
||||
onError() {
|
||||
this.$message.error('复制失败')
|
||||
},
|
||||
getInfo() {
|
||||
// 初始化统计信息
|
||||
axios.get('/n1/statisticsInfo').then(
|
||||
response => {
|
||||
if (response.data.success) {
|
||||
this.node1Info = response.data.data
|
||||
}
|
||||
})
|
||||
axios.get('/n2/statisticsInfo').then(
|
||||
response => {
|
||||
if (response.data.success) {
|
||||
this.node2Info = response.data.data
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getInfo()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app {
|
||||
font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
margin: auto;
|
||||
padding: 1em;
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: rgba(0, 149, 255, .1);
|
||||
}
|
||||
|
||||
body:before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: .3;
|
||||
z-index: -1;
|
||||
content: "";
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
margin-top: 1em;
|
||||
border-radius: 4px;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.el-select .el-input {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.input-with-select .el-input-group__prepend {
|
||||
background-color: #fff;
|
||||
|
||||
}
|
||||
|
||||
.box-card {
|
||||
margin-top: 4em !important;
|
||||
margin-bottom: 4em !important;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.box-card {
|
||||
margin-top: 1em !important;
|
||||
margin-bottom: 1em !important;
|
||||
}
|
||||
}
|
||||
|
||||
.download h3 {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.download button {
|
||||
margin-right: 0.5em;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.typo {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.typo a {
|
||||
color: #2c3e50;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 10px;
|
||||
margin-bottom: .8em;
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, .12);
|
||||
}
|
||||
</style>
|
||||
19
web-front/src/main.js
Normal file
19
web-front/src/main.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import ElementUI from 'element-ui'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
import JsonViewer from 'vue-json-viewer'
|
||||
|
||||
// Import JsonViewer as a Vue.js plugin
|
||||
Vue.use(JsonViewer)
|
||||
|
||||
// or
|
||||
// components: {JsonViewer}
|
||||
|
||||
Vue.use(VueClipboard)
|
||||
Vue.config.productionTip = false
|
||||
Vue.use(ElementUI)
|
||||
new Vue({
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
57
web-front/vue.config.js
Normal file
57
web-front/vue.config.js
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
const path = require("path");
|
||||
|
||||
function resolve(dir) {
|
||||
return path.join(__dirname, dir)
|
||||
}
|
||||
const CompressionPlugin = require('compression-webpack-plugin');
|
||||
const FileManagerPlugin = require('filemanager-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
transpileDependencies: true,
|
||||
lintOnSave: false,
|
||||
outputDir: 'nfd-front',
|
||||
devServer: {
|
||||
host: '127.0.0.1',
|
||||
port: 6444,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:6400', // 请求本地
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/api': '/' //本身的接口地址没有 '/api' 这种通用前缀,所以要rewrite,如果本身有则去掉
|
||||
},
|
||||
ws: true
|
||||
},
|
||||
}
|
||||
},
|
||||
configureWebpack: {
|
||||
// provide the app's title in webpack's name field, so that
|
||||
// it can be accessed in index.html to inject the correct title.
|
||||
name: 'Netdisk fast download',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve('src')
|
||||
}
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new CompressionPlugin({
|
||||
test: /\.js$|\.html$|\.css/, // 匹配文件
|
||||
threshold: 10240 // 对超过10k文件压缩
|
||||
}),
|
||||
new FileManagerPlugin({ //初始化 filemanager-webpack-plugin 插件实例
|
||||
onEnd: {
|
||||
mkdir: ['./nfd-front'],
|
||||
delete: [ //首先需要删除项目根目录下的dist.zip
|
||||
'./nfd-front.zip',
|
||||
],
|
||||
archive: [ //然后我们选择dist文件夹将之打包成dist.zip并放在根目录
|
||||
{source: './nfd-front', destination: './nfd-front.zip'},
|
||||
]
|
||||
}
|
||||
})
|
||||
]
|
||||
},
|
||||
|
||||
}
|
||||
@@ -5,19 +5,17 @@
|
||||
<parent>
|
||||
<artifactId>netdisk-fast-download</artifactId>
|
||||
<groupId>cn.qaiu</groupId>
|
||||
<version>0.1.3</version>
|
||||
<version>0.1.7</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<version>0.1.3</version>
|
||||
<version>0.1.7</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>
|
||||
@@ -36,37 +34,35 @@
|
||||
<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>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.qaiu</groupId>
|
||||
<artifactId>core-database</artifactId>
|
||||
<version>0.1.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.qaiu</groupId>
|
||||
<artifactId>parser</artifactId>
|
||||
<version>0.1.7</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.15.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-web-client</artifactId>
|
||||
<version>4.4.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.qaiu</groupId>
|
||||
<artifactId>core-database</artifactId>
|
||||
<version>0.1.3</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -105,6 +101,8 @@
|
||||
<exclude>*.**</exclude>
|
||||
<exclude>*/*.xml</exclude>
|
||||
<exclude>conf/**</exclude>
|
||||
<exclude>js/**</exclude>
|
||||
<exclude>http-tools/**</exclude>
|
||||
</excludes>
|
||||
<archive>
|
||||
<manifest>
|
||||
@@ -138,8 +136,7 @@
|
||||
</goals>
|
||||
<configuration>
|
||||
<!--打包时排除的依赖作用域-->
|
||||
<excludeScope>test</excludeScope>
|
||||
<excludeScope>provided</excludeScope>
|
||||
<includeScope>runtime</includeScope>
|
||||
<outputDirectory>
|
||||
${packageDirectory}/lib/
|
||||
</outputDirectory>
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package cn.qaiu.lz;
|
||||
|
||||
import cn.qaiu.WebClientVertxInit;
|
||||
import cn.qaiu.db.pool.JDBCPoolInit;
|
||||
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;
|
||||
|
||||
|
||||
/**
|
||||
* 程序入口
|
||||
* <br>Create date 2021-05-08 13:00:01
|
||||
*
|
||||
* @author qiu
|
||||
* @author qaiu
|
||||
*/
|
||||
public class AppMain {
|
||||
|
||||
@@ -24,6 +28,8 @@ 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();
|
||||
}
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
package cn.qaiu.lz.common.interceptorImpl;
|
||||
|
||||
import cn.qaiu.vx.core.base.BaseHttpApi;
|
||||
import cn.qaiu.vx.core.interceptor.Interceptor;
|
||||
import cn.qaiu.vx.core.model.JsonResult;
|
||||
import cn.qaiu.vx.core.util.CommonUtil;
|
||||
import cn.qaiu.vx.core.annotaions.HandleSortFilter;
|
||||
import cn.qaiu.vx.core.interceptor.BeforeInterceptor;
|
||||
import cn.qaiu.vx.core.util.SharedDataUtil;
|
||||
import cn.qaiu.vx.core.util.VertxHolder;
|
||||
import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.shareddata.LocalMap;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import lombok.val;
|
||||
|
||||
import static cn.qaiu.vx.core.util.ConfigConstant.IGNORES_REG;
|
||||
|
||||
/**
|
||||
* 默认拦截器实现
|
||||
* 校验用户是否合法 <br>
|
||||
* TODO 暂时只做简单实现
|
||||
* 前置拦截器实现
|
||||
*/
|
||||
@Slf4j
|
||||
public class DefaultInterceptor implements Interceptor, BaseHttpApi {
|
||||
@HandleSortFilter(1)
|
||||
public class DefaultInterceptor implements BeforeInterceptor {
|
||||
|
||||
private final JsonArray ignores = SharedDataUtil.getJsonArrayForCustomConfig("ignoresReg");
|
||||
protected final JsonArray ignores = SharedDataUtil.getJsonArrayForCustomConfig(IGNORES_REG);
|
||||
|
||||
@Override
|
||||
public void handle(RoutingContext ctx) {
|
||||
ctx.next();
|
||||
System.out.println("进入前置拦截器1->" + ctx.request().path());
|
||||
doNext(ctx);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package cn.qaiu.lz.common.interceptorImpl;
|
||||
|
||||
import cn.qaiu.db.pool.JDBCPoolInit;
|
||||
import cn.qaiu.lz.common.model.ParserLogInfo;
|
||||
import cn.qaiu.vx.core.annotaions.HandleSortFilter;
|
||||
import cn.qaiu.vx.core.interceptor.AfterInterceptor;
|
||||
import cn.qaiu.vx.core.model.JsonResult;
|
||||
import cn.qaiu.vx.core.util.CommonUtil;
|
||||
import cn.qaiu.vx.core.util.SharedDataUtil;
|
||||
import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import io.vertx.jdbcclient.JDBCPool;
|
||||
import io.vertx.sqlclient.templates.SqlTemplate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static cn.qaiu.vx.core.util.ConfigConstant.IGNORES_REG;
|
||||
|
||||
|
||||
/**
|
||||
* 记录解析日志
|
||||
*/
|
||||
@Slf4j
|
||||
@HandleSortFilter(99)
|
||||
public class LogStatistics implements AfterInterceptor {
|
||||
|
||||
JDBCPool client = JDBCPoolInit.instance().getPool();
|
||||
private final JsonArray ignores = SharedDataUtil.getJsonArrayForCustomConfig(IGNORES_REG);
|
||||
@Override
|
||||
public void handle(RoutingContext ctx, JsonObject responseData) {
|
||||
|
||||
// 判断是否忽略
|
||||
if (CommonUtil.matchRegList(ignores.getList(), ctx.request().path())) {
|
||||
return;
|
||||
}
|
||||
|
||||
ParserLogInfo parserLogInfo = new ParserLogInfo();
|
||||
parserLogInfo.setPath(ctx.request().uri());
|
||||
if (responseData == null) {
|
||||
String location = ctx.response().headers().get("location");
|
||||
if (location != null) {
|
||||
parserLogInfo.setCode(200);
|
||||
parserLogInfo.setData(location);
|
||||
} else {
|
||||
log.error("location不存在且responseData为空, path={}", ctx.request().path());
|
||||
}
|
||||
insert(parserLogInfo);
|
||||
|
||||
} else if (responseData.containsKey("code")) {
|
||||
JsonResult<?> result = JsonResult.toJsonResult(responseData);
|
||||
parserLogInfo.setCode(result.getCode());
|
||||
parserLogInfo.setData(result.getCode() == 500 ? result.getMsg() : result.getData().toString());
|
||||
insert(parserLogInfo);
|
||||
} else {
|
||||
log.error("未知json日志: {}, path: {}", responseData.encode(), ctx.request().path());
|
||||
}
|
||||
}
|
||||
|
||||
void insert(ParserLogInfo info) {
|
||||
SqlTemplate
|
||||
.forUpdate(client, "INSERT INTO t_parser_log_info VALUES (#{id},#{logTime},#{path},#{code},#{data})")
|
||||
.mapFrom(ParserLogInfo.class)
|
||||
.execute(info)
|
||||
.onSuccess(res -> {
|
||||
log.info("inserted log: id={}, path={}, code={}", info.getId(), info.getPath(), info.getCode());
|
||||
}).onFailure(Throwable::printStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.qaiu.lz.common.model;
|
||||
|
||||
import cn.qaiu.lz.common.util.SnowflakeIdWorker;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
abstract public class BaseModel {
|
||||
public static final long serialVersionUID = 1L;
|
||||
|
||||
|
||||
private String id = String.valueOf(SnowflakeIdWorker.idWorker().nextId());
|
||||
|
||||
private String createBy;
|
||||
|
||||
private Date createTime;
|
||||
|
||||
private String updateBy;
|
||||
|
||||
private Date updateTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.qaiu.lz.common.model;
|
||||
|
||||
import io.vertx.core.MultiMap;
|
||||
|
||||
public class FileInfo extends BaseModel {
|
||||
|
||||
private String fileName;
|
||||
|
||||
private String fileType;
|
||||
|
||||
private Long fileSize;
|
||||
|
||||
private String download;
|
||||
|
||||
private MultiMap header;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.qaiu.lz.common.model;
|
||||
|
||||
import cn.qaiu.db.ddl.Length;
|
||||
import cn.qaiu.db.ddl.Table;
|
||||
import cn.qaiu.lz.common.util.SnowflakeIdWorker;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Table("t_parser_log_info")
|
||||
public class ParserLogInfo {
|
||||
String id = SnowflakeIdWorker.getStringId();
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS", timezone = "GMT+8")
|
||||
Date logTime = new Date();
|
||||
String path;
|
||||
Integer code;
|
||||
|
||||
@Length(varcharSize = 4096)
|
||||
String data;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package cn.qaiu.lz.common.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 获取连接
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
*/
|
||||
public enum ConnectUtil {
|
||||
|
||||
// 实现枚举单例
|
||||
INSTANCE;
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ConnectUtil.class);
|
||||
|
||||
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package cn.qaiu.lz.common.util;
|
||||
|
||||
import cn.qaiu.vx.core.util.CastUtil;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jsoup.Jsoup;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 奶牛快传解析工具
|
||||
*
|
||||
* @author <a href="https://qaiu.top">QAIU</a>
|
||||
* @date 2023/4/21 21:19
|
||||
*/
|
||||
@Slf4j
|
||||
public class CowTool {
|
||||
/*
|
||||
First request:
|
||||
{
|
||||
"code": "0000",
|
||||
"message": "success",
|
||||
"data": {
|
||||
"guid": "e4f41b51-b5da-4f60-9312-37aa10c0aad7",
|
||||
"firstFile": {
|
||||
"id": "23861191276513345",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Then request:
|
||||
{
|
||||
"code": "0000",
|
||||
"message": "success",
|
||||
"tn": "TN:DE0E092E8A464521983780FBA21D0CD3",
|
||||
"data": {
|
||||
"downloadUrl": "https://download.cowcs.com..."
|
||||
}
|
||||
}
|
||||
*/
|
||||
public static String parse(String fullUrl) throws Exception {
|
||||
var uniqueUrl = fullUrl.substring(fullUrl.lastIndexOf('/') + 1);
|
||||
var baseUrl = "https://cowtransfer.com/core/api/transfer/share";
|
||||
var result = Jsoup
|
||||
.connect(baseUrl + "?uniqueUrl=" + uniqueUrl).ignoreContentType(true)
|
||||
.get()
|
||||
.text();
|
||||
var objectMapper = new ObjectMapper();
|
||||
Map<String, Object> map = objectMapper.readValue(result, new TypeReference<>() {
|
||||
});
|
||||
|
||||
if ("success".equals(map.get("message")) && map.containsKey("data")) {
|
||||
Map<String, Object> data = CastUtil.cast(map.get("data"));
|
||||
var guid = data.get("guid").toString();
|
||||
Map<String, Object> firstFile = CastUtil.cast(data.get("firstFile"));
|
||||
var fileId = firstFile.get("id").toString();
|
||||
var result2 = Jsoup
|
||||
.connect(baseUrl + "/download?transferGuid=" + guid + "&fileId=" + fileId)
|
||||
.ignoreContentType(true)
|
||||
.get()
|
||||
.text();
|
||||
Map<String, Object> map2 = objectMapper.readValue(result2, new TypeReference<>() {});
|
||||
|
||||
if ("success".equals(map2.get("message")) && map2.containsKey("data")) {
|
||||
Map<String, Object> data2 = CastUtil.cast(map2.get("data"));
|
||||
var downloadUrl = data2.get("downloadUrl").toString();
|
||||
if (StringUtils.isNotEmpty(downloadUrl)) {
|
||||
log.info("cow parse success: {}", downloadUrl);
|
||||
return downloadUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
log.info("Cow parse field------------->end");
|
||||
throw new Exception("Cow解析失败");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package cn.qaiu.lz.common.util;
|
||||
|
||||
import cn.qaiu.vx.core.util.VertxHolder;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.json.JsonArray;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
import io.vertx.uritemplate.UriTemplate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 移动云空间解析
|
||||
*/
|
||||
@Slf4j
|
||||
public class EcTool {
|
||||
private static final String FULL_URL_PREFIX = "https://www.ecpan.cn/drive/fileextoverrid" +
|
||||
".do?chainUrlTemplate=https:%2F%2Fwww.ecpan" +
|
||||
".cn%2Fweb%2F%23%2FyunpanProxy%3Fpath%3D%252F%2523%252Fdrive%252Foutside&parentId=-1&data={dataKey}";
|
||||
|
||||
private static final String DOWNLOAD_REQUEST_URL = "https://www.ecpan.cn/drive/sharedownload.do";
|
||||
|
||||
public static final String EC_HOST = "www.ecpan.cn";
|
||||
|
||||
public static Future<String> parse(String dataKey) {
|
||||
Promise<String> promise = Promise.promise();
|
||||
WebClient client = WebClient.create(VertxHolder.getVertxInstance());
|
||||
// 第一次请求 获取文件信息
|
||||
client.getAbs(UriTemplate.of(FULL_URL_PREFIX)).setTemplateParam("dataKey", dataKey).send().onSuccess(res -> {
|
||||
JsonObject jsonObject = res.bodyAsJsonObject();
|
||||
log.debug("ecPan get file info -> {}", jsonObject);
|
||||
JsonObject fileInfo = jsonObject
|
||||
.getJsonObject("var")
|
||||
.getJsonObject("chainFileInfo");
|
||||
if (!fileInfo.containsKey("errMesg")) {
|
||||
JsonObject cloudpFile = fileInfo.getJsonObject("cloudpFile");
|
||||
JsonArray fileIdList = JsonArray.of(cloudpFile);
|
||||
// 构造请求JSON {"extCodeFlag":0,"isIp":0}
|
||||
JsonObject requestBodyJson = JsonObject.of("extCodeFlag", 0, "isIp", 0);
|
||||
requestBodyJson.put("shareId", Integer.parseInt(fileInfo.getString("shareId"))); // 注意shareId
|
||||
// 数据类型
|
||||
requestBodyJson.put("groupId", cloudpFile.getString("groupId"));
|
||||
requestBodyJson.put("fileIdList", fileInfo.getJsonArray("cloudpFileList"));
|
||||
|
||||
// 第二次请求 获取下载链接
|
||||
client.postAbs(DOWNLOAD_REQUEST_URL)
|
||||
.sendJsonObject(requestBodyJson).onSuccess(res2 -> {
|
||||
JsonObject jsonRes = res2.bodyAsJsonObject();
|
||||
log.debug("ecPan get download url -> {}", res2.body().toString());
|
||||
promise.complete(jsonRes.getJsonObject("var").getString("downloadUrl"));
|
||||
}).onFailure(t -> {
|
||||
promise.fail(new RuntimeException("解析异常: key = " + dataKey, t.fillInStackTrace()));
|
||||
});
|
||||
|
||||
} else {
|
||||
promise.fail(new RuntimeException(DOWNLOAD_REQUEST_URL + " 解析失败: "
|
||||
+ fileInfo.getString("errMesg")) + " key = " + dataKey);
|
||||
}
|
||||
}
|
||||
).onFailure(t -> {
|
||||
promise.fail(new RuntimeException("解析异常: key = " + dataKey, t.fillInStackTrace()));
|
||||
});
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package cn.qaiu.lz.common.util;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.jsoup.Jsoup;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 蓝奏云解析工具
|
||||
*
|
||||
* @author QAIU
|
||||
* @version 1.0 update 2021/5/16 10:39
|
||||
*/
|
||||
public class LzTool {
|
||||
|
||||
public static String parse(String fullUrl) throws Exception {
|
||||
var userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.3";
|
||||
var url = fullUrl.substring(0, fullUrl.lastIndexOf('/') + 1);
|
||||
var id = fullUrl.substring(fullUrl.lastIndexOf('/') + 1);
|
||||
Map<String, String> header = new HashMap<>();
|
||||
header.put("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
|
||||
header.put("referer", url);
|
||||
|
||||
/*
|
||||
// 部分链接需要设置安卓UA
|
||||
sec-ch-ua: "Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"
|
||||
sec-ch-ua-mobile: ?1
|
||||
sec-ch-ua-platform: "Android"
|
||||
*/
|
||||
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Mobile Safari/537.36";
|
||||
Map<String, String> header2 = new HashMap<>();
|
||||
header2.put("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
|
||||
header2.put("sec-ch-ua-mobile", "sec-ch-ua-mobile");
|
||||
header2.put("sec-ch-ua-platform", "Android");
|
||||
header2.put("referer", url);
|
||||
|
||||
//第一次请求,获取iframe的地址
|
||||
String result = Jsoup.connect(url + id)
|
||||
.userAgent(userAgent)
|
||||
.get()
|
||||
.select(".ifr2")
|
||||
.attr("src");
|
||||
|
||||
//第二次请求得到js里的json数据里的sign
|
||||
result = Jsoup.connect(url + result)
|
||||
.headers(header)
|
||||
.userAgent(userAgent)
|
||||
.get()
|
||||
.html();
|
||||
// System.out.println(result);
|
||||
// 'sign':'AWcGOFprUGFWX1BvBTVXawdrBDZTOAU_bV2FTZFU7W2sBJ1t4DW0FYFIyBmgDZVJgUjAFNV41UGQFNg_c_c' 改下正则TMD 最近上传竟然没_c_c
|
||||
Matcher matcher = Pattern.compile("'sign'\s*:\s*'([0-9a-zA-Z_]+)'").matcher(result);
|
||||
Map<String, String> params = new LinkedHashMap<>();
|
||||
if (matcher.find()) {
|
||||
String sn = matcher.group(1).replace("'", "");
|
||||
params.put("action", "downprocess");
|
||||
params.put("sign", sn);
|
||||
params.put("ves", "1");
|
||||
// System.out.println(sn);
|
||||
|
||||
} else {
|
||||
throw new IOException();
|
||||
}
|
||||
//第三次请求 通过参数发起post请求,返回json数据
|
||||
result = Jsoup
|
||||
.connect(url + "ajaxm.php")
|
||||
.headers(header)
|
||||
.userAgent(userAgent)
|
||||
.data(params)
|
||||
.post()
|
||||
.text()
|
||||
.replace("\\", "");
|
||||
//json转为map
|
||||
params = new ObjectMapper().readValue(result, new TypeReference<>() {});
|
||||
// System.out.println(params);
|
||||
//通过json的数据拼接出最终的URL发起第最终请求,并得到响应信息头
|
||||
url = params.get("dom") + "/file/" + params.get("url");
|
||||
var headers = Jsoup.connect(url)
|
||||
.ignoreContentType(true)
|
||||
.userAgent(userAgent2)
|
||||
.headers(header2)
|
||||
.followRedirects(false)
|
||||
.execute()
|
||||
.headers();
|
||||
//得到重定向的地址进行重定向
|
||||
url = headers.get("Location");
|
||||
return url;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.qaiu.vx.core.util;
|
||||
package cn.qaiu.lz.common.util;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
@@ -30,6 +30,8 @@ public class SnowflakeIdWorker {
|
||||
|
||||
// ==============================Fields===========================================
|
||||
|
||||
//开始时间截 (2021-01-01)
|
||||
private static final long EPOCH = 1609459200000L;
|
||||
/**
|
||||
* 机器id所占的位数
|
||||
*/
|
||||
@@ -94,7 +96,7 @@ public class SnowflakeIdWorker {
|
||||
*
|
||||
* @return SnowflakeId
|
||||
*/
|
||||
public synchronized long nextId() {
|
||||
public synchronized Long nextId() {
|
||||
long timestamp = timeGen();
|
||||
|
||||
//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
|
||||
@@ -133,9 +135,7 @@ public class SnowflakeIdWorker {
|
||||
//时间截向左移22位(5+5+12)
|
||||
long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
|
||||
|
||||
//开始时间截 (2021-01-01)
|
||||
long twepoch = 1609459200000L;
|
||||
return ((timestamp - twepoch) << timestampLeftShift) //
|
||||
return ((timestamp - EPOCH) << timestampLeftShift) //
|
||||
| (datacenterId << datacenterIdShift) //
|
||||
| (workerId << sequenceBits) //
|
||||
| sequence;
|
||||
@@ -214,30 +214,18 @@ 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);
|
||||
}
|
||||
return snowflakeIdWorkerCluster;
|
||||
}
|
||||
//==============================Test=============================================
|
||||
|
||||
/**
|
||||
* 测试
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
final SnowflakeIdWorker snowflakeIdWorkerCluster = idWorkerCluster(0, 1);
|
||||
final SnowflakeIdWorker idWorker = idWorker();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
long id = idWorker.nextId();
|
||||
System.out.println(Long.toBinaryString(id));
|
||||
System.out.println(id);
|
||||
System.out.println("------------");
|
||||
id = snowflakeIdWorkerCluster.nextId();
|
||||
System.out.println(Long.toBinaryString(id));
|
||||
System.out.println(id);
|
||||
System.out.println("------------\n");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
32
web-service/src/main/java/cn/qaiu/lz/web/http/ParserApi.java
Normal file
32
web-service/src/main/java/cn/qaiu/lz/web/http/ParserApi.java
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,18 @@
|
||||
package cn.qaiu.lz.web.http;
|
||||
|
||||
import cn.qaiu.lz.common.util.CowTool;
|
||||
import cn.qaiu.lz.common.util.EcTool;
|
||||
import cn.qaiu.lz.common.util.LzTool;
|
||||
import cn.qaiu.lz.common.util.UcTool;
|
||||
import cn.qaiu.lz.web.model.SysUser;
|
||||
import cn.qaiu.lz.web.service.UserService;
|
||||
import cn.qaiu.parser.IPanTool;
|
||||
import cn.qaiu.parser.impl.EcTool;
|
||||
import cn.qaiu.parser.impl.QQTool;
|
||||
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.model.JsonResult;
|
||||
import cn.qaiu.vx.core.util.AsyncServiceUtil;
|
||||
import cn.qaiu.vx.core.util.ResponseUtil;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Promise;
|
||||
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
|
||||
@@ -29,130 +23,60 @@ 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);
|
||||
}
|
||||
|
||||
@RouteMapping(value = "/test2", method = RouteMethod.GET)
|
||||
public JsonResult<String> test01() {
|
||||
return JsonResult.data("ok");
|
||||
}
|
||||
|
||||
@RouteMapping(value = "/parser", method = RouteMethod.GET)
|
||||
@RouteMapping(value = "/parser", method = RouteMethod.GET, order = 4)
|
||||
public Future<Void> parse(HttpServerResponse response, HttpServerRequest request, String url, String pwd) {
|
||||
Promise<Void> promise = Promise.promise();
|
||||
if (url.contains("lanzou")) {
|
||||
String urlDownload = null;
|
||||
try {
|
||||
urlDownload = LzTool.parse(url);
|
||||
log.info("url = {}", urlDownload);
|
||||
response.putHeader("location", urlDownload).setStatusCode(302).end();
|
||||
promise.complete();
|
||||
} catch (Exception e) {
|
||||
promise.fail(e);
|
||||
}
|
||||
} else if (url.contains("cowtransfer.com")) {
|
||||
String urlDownload = null;
|
||||
try {
|
||||
urlDownload = CowTool.parse(url);
|
||||
response.putHeader("location", urlDownload).setStatusCode(302).end();
|
||||
promise.complete();
|
||||
} catch (Exception e) {
|
||||
promise.fail(e);
|
||||
}
|
||||
|
||||
} else if (url.contains(EcTool.EC_HOST)) {
|
||||
Promise<Void> promise = Promise.promise();
|
||||
if (url.contains(EcTool.SHARE_URL_PREFIX)) {
|
||||
// 默认读取Url参数会被截断手动获取一下其他参数
|
||||
String data = request.getParam("data");
|
||||
EcTool.parse(data).onSuccess(resUrl -> {
|
||||
response.putHeader("location", resUrl).setStatusCode(302).end();
|
||||
promise.complete();
|
||||
}).onFailure(t -> {
|
||||
promise.fail(t.fillInStackTrace());
|
||||
});
|
||||
} else if (url.contains(UcTool.FULL_URL_PREFIX)) {
|
||||
UcTool.parse(url, pwd).onSuccess(resUrl -> {
|
||||
response.putHeader("location", resUrl).setStatusCode(302).end();
|
||||
promise.complete();
|
||||
}).onFailure(t -> {
|
||||
promise.fail(t.fillInStackTrace());
|
||||
});
|
||||
url = EcTool.SHARE_URL_PREFIX + request.getParam("data");
|
||||
}
|
||||
if (url.contains(QQTool.SHARE_URL_PREFIX)) {
|
||||
// 默认读取Url参数会被截断手动获取一下其他参数
|
||||
url = url + "&key=" + request.getParam("key") +
|
||||
"&code=" + request.getParam("code") + "&k=" + request.getParam("k") +
|
||||
"&fweb=" + request.getParam("fweb") + "&cl=" + request.getParam("cl");
|
||||
}
|
||||
IPanTool.shareURLPrefixMatching(url, pwd).parse()
|
||||
.onSuccess(resUrl -> ResponseUtil.redirect(response, resUrl, promise))
|
||||
.onFailure(t -> promise.fail(t.fillInStackTrace()));
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
@RouteMapping(value = "/lz/:id", method = RouteMethod.GET)
|
||||
public void lzParse(HttpServerResponse response, String id) throws Exception {
|
||||
var url = "https://wwsd.lanzoue.com/" + id;
|
||||
var urlDownload = LzTool.parse(url);
|
||||
log.info("url = {}", urlDownload);
|
||||
response.putHeader("location", urlDownload).setStatusCode(302).end();
|
||||
}
|
||||
|
||||
@RouteMapping(value = "/cow/:id", method = RouteMethod.GET)
|
||||
public void cowParse(HttpServerResponse response, String id) throws Exception {
|
||||
var url = "https://cowtransfer.com/s/" + id;
|
||||
var urlDownload = CowTool.parse(url);
|
||||
response.putHeader("location", urlDownload).setStatusCode(302).end();
|
||||
}
|
||||
|
||||
@RouteMapping(value = "/json/lz/:id", method = RouteMethod.GET)
|
||||
public JsonResult<String> lzParseJson(HttpServerResponse response, String id) throws Exception {
|
||||
var url = "https://wwsd.lanzoue.com/" + id;
|
||||
var urlDownload = LzTool.parse(url);
|
||||
log.info("url = {}", urlDownload);
|
||||
return JsonResult.data(urlDownload);
|
||||
}
|
||||
|
||||
@RouteMapping(value = "/json/cow/:id", method = RouteMethod.GET)
|
||||
public JsonResult<String> cowParseJson(HttpServerResponse response, String id) throws Exception {
|
||||
var url = "https://cowtransfer.com/s/" + id;
|
||||
return JsonResult.data(CowTool.parse(url));
|
||||
}
|
||||
|
||||
@RouteMapping(value = "/ec/:id", method = RouteMethod.GET)
|
||||
public void ecParse(HttpServerResponse response, String id) {
|
||||
EcTool.parse(id).onSuccess(resUrl -> {
|
||||
response.putHeader("location", resUrl).setStatusCode(302).end();
|
||||
}).onFailure(t -> {
|
||||
response.putHeader(CONTENT_TYPE, "text/html;charset=utf-8");
|
||||
response.end(t.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
@RouteMapping(value = "/json/ec/:id", method = RouteMethod.GET)
|
||||
public Future<String> ecParseJson(HttpServerResponse response, String id) {
|
||||
return EcTool.parse(id);
|
||||
}
|
||||
|
||||
@RouteMapping(value = "/uc/:id", method = RouteMethod.GET)
|
||||
public void ucParse(HttpServerResponse response, String id) {
|
||||
String code = "";
|
||||
if (id.contains("#")) {
|
||||
String[] ids = id.split("#");
|
||||
id = ids[0];
|
||||
code = ids[1];
|
||||
@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");
|
||||
}
|
||||
UcTool.parse(id, code).onSuccess(resUrl -> {
|
||||
response.putHeader("location", resUrl).setStatusCode(302).end();
|
||||
}).onFailure(t -> {
|
||||
response.putHeader(CONTENT_TYPE, "text/html;charset=utf-8");
|
||||
response.end(t.getMessage());
|
||||
});
|
||||
return IPanTool.shareURLPrefixMatching(url, pwd).parse();
|
||||
}
|
||||
|
||||
@RouteMapping(value = "/json/uc/:id", method = RouteMethod.GET)
|
||||
public Future<String> ucParseJson(String id) {
|
||||
@RouteMapping(value = "/json/:type/:key", method = RouteMethod.GET, order = 2)
|
||||
public Future<String> parseKeyJson(String type, String key) {
|
||||
String code = "";
|
||||
if (id.contains("#")) {
|
||||
String[] ids = id.split("#");
|
||||
id = ids[0];
|
||||
code = ids[1];
|
||||
if (key.contains("@")) {
|
||||
String[] keys = key.split("@");
|
||||
key = keys[0];
|
||||
code = keys[1];
|
||||
}
|
||||
return UcTool.parse(id, code);
|
||||
return IPanTool.typeMatching(type, key, code).parse();
|
||||
}
|
||||
|
||||
@RouteMapping(value = "/:type/:key", method = RouteMethod.GET, order = 1)
|
||||
public Future<Void> parseKey(HttpServerResponse response, String type, String key) {
|
||||
Promise<Void> promise = Promise.promise();
|
||||
String code = "";
|
||||
if (key.contains("@")) {
|
||||
String[] keys = key.split("@");
|
||||
key = keys[0];
|
||||
code = keys[1];
|
||||
}
|
||||
|
||||
IPanTool.typeMatching(type, key, code).parse()
|
||||
.onSuccess(resUrl -> ResponseUtil.redirect(response, resUrl, promise))
|
||||
.onFailure(t -> promise.fail(t.fillInStackTrace()));
|
||||
return promise.future();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
package cn.qaiu.lz.web.model;
|
||||
|
||||
public class CowUser {
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package cn.qaiu.lz.web.model;
|
||||
|
||||
public class LzUser {
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.qaiu.lz.web.model;
|
||||
|
||||
import cn.qaiu.lz.common.ToJson;
|
||||
import io.vertx.codegen.annotations.DataObject;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@DataObject
|
||||
public class StatisticsInfo implements ToJson {
|
||||
Integer fail;
|
||||
Integer success;
|
||||
Integer total;
|
||||
|
||||
|
||||
public StatisticsInfo(JsonObject jsonObject) {
|
||||
this.fail = jsonObject.getInteger("fail");
|
||||
this.success = jsonObject.getInteger("success");
|
||||
this.total = jsonObject.getInteger("total");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cn.qaiu.lz.web.service;
|
||||
|
||||
import cn.qaiu.lz.common.model.UserInfo;
|
||||
import cn.qaiu.lz.web.model.StatisticsInfo;
|
||||
import cn.qaiu.vx.core.base.BaseAsyncService;
|
||||
import io.vertx.codegen.annotations.ProxyGen;
|
||||
import io.vertx.core.Future;
|
||||
@@ -16,4 +17,6 @@ import io.vertx.core.json.JsonObject;
|
||||
public interface DbService extends BaseAsyncService {
|
||||
Future<JsonObject> sayOk(String data);
|
||||
Future<JsonObject> sayOk2(String data, UserInfo holder);
|
||||
|
||||
Future<StatisticsInfo> getStatisticsInfo();
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@ import io.vertx.core.Future;
|
||||
*/
|
||||
@ProxyGen
|
||||
public interface UserService extends BaseAsyncService {
|
||||
Future<String> login(SysUser user);
|
||||
Future<SysUser> login(SysUser user);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
package cn.qaiu.lz.web.service.impl;
|
||||
|
||||
import cn.qaiu.db.pool.JDBCPoolInit;
|
||||
import cn.qaiu.lz.common.model.UserInfo;
|
||||
import cn.qaiu.lz.web.service.DbService;
|
||||
import cn.qaiu.lz.web.model.StatisticsInfo;
|
||||
import cn.qaiu.vx.core.annotaions.Service;
|
||||
import cn.qaiu.vx.core.model.JsonResult;
|
||||
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.templates.SqlTemplate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* lz-web
|
||||
* <br>Create date 2021/7/12 17:26
|
||||
@@ -35,4 +42,25 @@ public class DbServiceImpl implements DbService {
|
||||
// log.info("--> {}", holder.toString());
|
||||
return Future.succeededFuture(JsonObject.mapFrom(JsonResult.data("Hi: " + data)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<StatisticsInfo> getStatisticsInfo() {
|
||||
JDBCPool client = JDBCPoolInit.instance().getPool();
|
||||
Promise<StatisticsInfo> promise = Promise.promise();
|
||||
String sql = """
|
||||
select COUNT(CASE "code" WHEN 500 THEN "code" END ) "fail",
|
||||
COUNT(CASE "code" WHEN 200 THEN "code" END ) "success",
|
||||
count(1) "total"
|
||||
from "t_parser_log_info"
|
||||
""";
|
||||
SqlTemplate.forQuery(client, sql).mapTo(StatisticsInfo.class).execute(new HashMap<>()).onSuccess(row -> {
|
||||
StatisticsInfo info;
|
||||
if ((info = row.iterator().next()) != null) {
|
||||
promise.complete(info);
|
||||
} else {
|
||||
promise.fail("t_parser_log_info查询为空");
|
||||
}
|
||||
}).onFailure(promise::fail);
|
||||
return promise.future();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import cn.qaiu.lz.web.service.UserService;
|
||||
import cn.qaiu.vx.core.annotaions.Service;
|
||||
import io.vertx.core.Future;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* lz-web
|
||||
* <br>Create date 2021/8/27 14:09
|
||||
@@ -15,8 +17,13 @@ import io.vertx.core.Future;
|
||||
public class UserServiceImpl implements UserService {
|
||||
|
||||
@Override
|
||||
public Future<String> login(SysUser user) {
|
||||
public Future<SysUser> login(SysUser user) {
|
||||
|
||||
return Future.succeededFuture("111");
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(2);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return Future.succeededFuture(user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,37 +5,37 @@ server:
|
||||
# 使用静态页面
|
||||
enableStaticHtmlService: false
|
||||
# 使用数据库
|
||||
enableDatabase: false
|
||||
enableDatabase: true
|
||||
staticResourcePath: webroot/
|
||||
# 反向代理服务器配置路径(不用加后缀)
|
||||
proxyConf: server-proxy
|
||||
|
||||
# vertx线程配置 事件循环线程配置为0: eventLoopPoolSize将会采用默认配置(CPU核心*2) workerPoolSize将会采用默认20
|
||||
vertx:
|
||||
eventLoopPoolSize: 8
|
||||
workerPoolSize: 20
|
||||
eventLoopPoolSize: 0
|
||||
workerPoolSize: 0
|
||||
|
||||
custom:
|
||||
asyncServiceInstances: 8
|
||||
routerLocations: cn.qaiu.lz.web.http
|
||||
interceptorClassPath: cn.qaiu.lz.common.interceptorImpl.DefaultInterceptor
|
||||
handlerLocations: cn.qaiu.lz.web.service
|
||||
# 异步服务线程数
|
||||
asyncServiceInstances: 4
|
||||
# server路由(controller层)所在包路径
|
||||
baseLocations: cn.qaiu.lz
|
||||
# 路由处理默认超时时间(毫秒)
|
||||
routeTimeOut: 15000
|
||||
# 拦截器匹配规则
|
||||
ignoresReg:
|
||||
- .*/login$
|
||||
- /v2/statisticsInfo
|
||||
- .*/test.*$
|
||||
|
||||
# 参数注入的实体类包路径匹配正则 (防止同名类引发歧义)
|
||||
entityPackagesReg:
|
||||
- ^cn\.qaiu\.lz\.web\.model\..*
|
||||
errorPage404: /index.html
|
||||
indexPage: /test2
|
||||
sharedLogin: true
|
||||
lzConfig:
|
||||
config: '111'
|
||||
|
||||
|
||||
# 数据源配置
|
||||
dataSource:
|
||||
provider_class: io.vertx.ext.jdbc.spi.impl.HikariCPDataSourceProvider
|
||||
jdbcUrl: jdbc:h2:tcp://127.0.0.1:9095/./db/myData;MODE=MySQL;DATABASE_TO_LOWER=FALSE
|
||||
jdbcUrl: jdbc:h2:tcp://127.0.0.1:9095/./db/myData;MODE=MySQL;DATABASE_TO_UPPER=FALSE
|
||||
driverClassName: org.h2.Driver
|
||||
username: root
|
||||
password: '123456'
|
||||
tableClassPath: cn.qaiu.lz.web.model
|
||||
|
||||
cowConfig:
|
||||
config: '111'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user