mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-17 12:53:02 +00:00
Compare commits
62 Commits
0.1.8.fixe
...
dev-0.1.8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f30027dd13 | ||
|
|
8b6aad17f4 | ||
|
|
b77930adfb | ||
|
|
aff8f88076 | ||
|
|
4e6582e24c | ||
|
|
fa9acaccfd | ||
|
|
0414f85f12 | ||
|
|
527dd0eeb4 | ||
|
|
74ed7475c9 | ||
|
|
54dc3dba96 | ||
|
|
9980159090 | ||
|
|
0b193ebb00 | ||
|
|
f5fc9843b2 | ||
|
|
df1f67dd26 | ||
|
|
b069a5f576 | ||
|
|
7686763a03 | ||
|
|
635a6eac37 | ||
|
|
877edc535f | ||
|
|
01d59e3c1e | ||
|
|
fece2799e3 | ||
|
|
de9756ee86 | ||
|
|
51f047a51b | ||
|
|
04b66e82b7 | ||
|
|
df89253647 | ||
|
|
45dbca794e | ||
|
|
857bf28f99 | ||
|
|
e07ce15228 | ||
|
|
0637bcfd8e | ||
|
|
23db0563ac | ||
|
|
ccba71aa4e | ||
|
|
fee4bf2ad6 | ||
|
|
5052fea9ef | ||
|
|
e85215fca1 | ||
|
|
e42fe45329 | ||
|
|
4240815bd1 | ||
|
|
6f0c5305e2 | ||
|
|
757005cad8 | ||
|
|
81651ad97c | ||
|
|
f3763b6058 | ||
|
|
82478dc485 | ||
|
|
703fd05d43 | ||
|
|
ff868b6e2a | ||
|
|
051a74b37b | ||
|
|
a0a1085623 | ||
|
|
2612d3919c | ||
|
|
6f123a236f | ||
|
|
71e57e6a08 | ||
|
|
7cb18d8186 | ||
|
|
cdbf670ece | ||
|
|
e0dafee617 | ||
|
|
c37bce1563 | ||
|
|
0b3c77d644 | ||
|
|
2cf85caf86 | ||
|
|
594010ba88 | ||
|
|
d91460d2e2 | ||
|
|
89713e6ac9 | ||
|
|
17c9b2538c | ||
|
|
d337b003cb | ||
|
|
8f1485656b | ||
|
|
f0c4ec3031 | ||
|
|
458be84aca | ||
|
|
c7716aad34 |
15
.github/FUNDING.yml
vendored
Normal file
15
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
patreon: # Replace with a single Patreon username
|
||||||
|
open_collective: # Replace with a single Open Collective username
|
||||||
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
|
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||||
|
polar: # Replace with a single Polar username
|
||||||
|
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||||
|
thanks_dev: # Replace with a single thanks.dev username
|
||||||
|
custom: https://blog.qaiu.top/archives/da-shang-zhuan-yong # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
10
.github/workflows/maven.yml
vendored
10
.github/workflows/maven.yml
vendored
@@ -16,6 +16,14 @@ permissions:
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main" ]
|
branches: [ "main" ]
|
||||||
|
paths-ignore:
|
||||||
|
- 'bin/**'
|
||||||
|
- '.github/**'
|
||||||
|
- '.mvn/**'
|
||||||
|
- '.run/**'
|
||||||
|
- '.vscode/**'
|
||||||
|
- '*.txt'
|
||||||
|
- '*.md'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main" ]
|
branches: [ "main" ]
|
||||||
|
|
||||||
@@ -30,8 +38,6 @@ jobs:
|
|||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '18'
|
||||||
cache: 'yarn'
|
|
||||||
cache-dependency-path: 'web-front/yarn.lock'
|
|
||||||
|
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
|
|||||||
198
README.md
198
README.md
@@ -1,66 +1,86 @@
|
|||||||
云盘解析服务 (nfd云解析)
|
|
||||||
|
<div align=center><img src="https://bd2.qaiu.cn/blog/lanzou111.403f7881.png" height="160" width="160"></div>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml"><img src="https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml/badge.svg?style=flat"></a>
|
||||||
|
<a href="https://www.oracle.com/cn/java/technologies/downloads"><img src="https://img.shields.io/badge/jdk-%3E%3D17-blue"></a>
|
||||||
|
<a href="https://vertx-china.github.io"><img src="https://img.shields.io/badge/vert.x-4.5.6-blue?style=flat"></a>
|
||||||
|
<a href="https://raw.githubusercontent.com/qaiu/netdisk-fast-download/master/LICENSE"><img src="https://img.shields.io/github/license/qaiu/netdisk-fast-download?style=flat"></a>
|
||||||
|
<a href="https://github.com/qaiu/netdisk-fast-download/releases/"><img src="https://img.shields.io/github/v/release/qaiu/netdisk-fast-download?style=flat"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
# netdisk-fast-download 网盘分享链接云解析服务
|
||||||
|
|
||||||
|
netdisk-fast-download网盘直链云解析(nfd云解析)能把网盘分享下载链接转化为直链,支持多款云盘,已支持蓝奏云/蓝奏云优享/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘/Cloudreve等,支持加密分享。
|
||||||
|
|
||||||
预览地址 https://lz.qaiu.top
|
预览地址 https://lz.qaiu.top
|
||||||
|
预览地址2(可以解析onedrive) http://8.209.249.88:6402
|
||||||
main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/netdisk-fast-download/tree/main-jdk11)
|
main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/netdisk-fast-download/tree/main-jdk11)
|
||||||
**注意: 请不要过度依赖lz.qaiu.top预览地址服务,建议本地搭建或者云服务器自行搭建。
|
**0.1.8及以上版本json接口格式有调整 参考json返回数据格式示例**
|
||||||
解析次数过多IP会被部分网盘厂商限制,不推荐做公共解析。**
|
**小飞机解析有IP限制,多数云服务商的大陆IP会被拦截(可以自行配置代理),和本程序无关**
|
||||||
|
**注意: 请不要过度依赖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/latest)
|
|
||||||
|
|
||||||
## 项目介绍
|
|
||||||
网盘直链解析工具能把网盘分享下载链接转化为直链,已支持蓝奏云/蓝奏云优享/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘/Cloudreve等,支持加密分享。
|
|
||||||
**0.1.8及以上版本json接口格式有调整,尤其依赖lz.qaiu.top做下载服务的朋友们记得修改, 参考json返回数据格式示例**
|
|
||||||
|
|
||||||
|
|
||||||
*重要声明:本项目仅供学习参考;请不要将此项目用于任何商业用途,否则可能带来严重的后果。转发/分享该项目请注明来源*
|
|
||||||
|
|
||||||
## 网盘支持情况:
|
## 网盘支持情况:
|
||||||
> 20230905 奶牛云直链做了防盗链,需加入请求头:Referer: https://cowtransfer.com/
|
> 20230905 奶牛云直链做了防盗链,需加入请求头:Referer: https://cowtransfer.com/
|
||||||
> 20230824 123云盘解析大文件(>100MB)失效,需要登录
|
> 20230824 123云盘解析大文件(>100MB)失效,需要登录
|
||||||
> 20230722 UC网盘解析失效,需要登录
|
> 20230722 UC网盘解析失效,需要登录
|
||||||
|
|
||||||
`网盘名称(网盘标识):`
|
网盘名称-网盘标识:
|
||||||
|
|
||||||
- [蓝奏云 (lz)](https://pc.woozooo.com/)
|
|
||||||
- [蓝奏云优享 (iz)](https://www.ilanzou.com/)
|
|
||||||
- [奶牛快传 (cow)](https://cowtransfer.com/)
|
|
||||||
- [移动云云空间 (ec)](https://www.ecpan.cn/web)
|
|
||||||
- [小飞机网盘 (fj)](https://www.feijipan.com/)
|
|
||||||
- [亿方云 (fc)](https://www.fangcloud.com/)
|
|
||||||
- [123云盘 (ye)](https://www.123pan.com/)
|
|
||||||
- [文叔叔 (ws)](https://www.wenshushu.cn/)
|
|
||||||
- [联想乐云 (le)](https://lecloud.lenovo.com/)
|
|
||||||
- [QQ邮箱文件中转站 (qq)](https://mail.qq.com/)
|
|
||||||
- [超星网盘-开发中 (cx)](https://passport2.chaoxing.com/login?newversion=true&refer=https%3A%2F%2Fpan-yz.chaoxing.com%2F)
|
|
||||||
- [城通网盘(ct)](https://www.ctfile.com)
|
|
||||||
- [网易云音乐(mne)](https://music.163.com)
|
|
||||||
- [Cloudreve自建网盘(ce)](https://github.com/cloudreve/Cloudreve)
|
|
||||||
|
|
||||||
|
- [蓝奏云-lz](https://pc.woozooo.com/)
|
||||||
|
- [蓝奏云优享-iz](https://www.ilanzou.com/)
|
||||||
|
- [奶牛快传-cow](https://cowtransfer.com/)
|
||||||
|
- [移动云云空间-ec](https://www.ecpan.cn/web)
|
||||||
|
- [小飞机网盘-fj](https://www.feijipan.com/)
|
||||||
|
- [亿方云-fc](https://www.fangcloud.com/)
|
||||||
|
- [123云盘-ye](https://www.123pan.com/)
|
||||||
|
- ~[115网盘(失效)-p115](https://115.com/)~
|
||||||
|
- [118网盘(已停服)-p118](https://www.118pan.com/)
|
||||||
|
- [文叔叔-ws](https://www.wenshushu.cn/)
|
||||||
|
- [联想乐云-le](https://lecloud.lenovo.com/)
|
||||||
|
- [QQ邮箱文件中转站-qq](https://mail.qq.com/)
|
||||||
|
- [城通网盘-ct](https://www.ctfile.com)
|
||||||
|
- [网易云音乐分享链接-mnes](https://music.163.com)
|
||||||
|
- [酷狗音乐分享链接-mkgs](https://www.kugou.com)
|
||||||
|
- [酷我音乐分享链接-mkws](https://kuwo.cn)
|
||||||
|
- [QQ音乐分享链接-mqqs](https://y.qq.com)
|
||||||
|
- 咪咕音乐分享链接(开发中)
|
||||||
|
- [Cloudreve自建网盘-ce](https://github.com/cloudreve/Cloudreve)
|
||||||
|
- ~[微雨云存储-pvvy](https://www.vyuyun.com/)~
|
||||||
|
- [超星云盘(需要referer: https://pan-yz.chaoxing.com)-pcx](https://pan-yz.chaoxing.com)
|
||||||
|
- Google云盘-pgd
|
||||||
|
- Onedrive-pod
|
||||||
|
- Dropbox-pdp
|
||||||
|
- iCloud-pic
|
||||||
|
### 仅专属版提供
|
||||||
|
- [移动云盘-p139](https://yun.139.com/)
|
||||||
|
- [联通云盘-pwo](https://pan.wo.cn/)
|
||||||
|
- [天翼云盘-p189](https://cloud.189.cn/)
|
||||||
|
|
||||||
### API接口说明
|
### API接口说明
|
||||||
your_host指的是您的域名或者IP,实际使用时替换为实际域名或者IP,端口默认6400,可以使用nginx代理来做域名访问。
|
your_host指的是您的域名或者IP,实际使用时替换为实际域名或者IP,端口默认6400,可以使用nginx代理来做域名访问。
|
||||||
解析方式分为两种类型直接跳转下载文件和获取下载链接,
|
解析方式分为两种类型直接跳转下载文件和获取下载链接,
|
||||||
每一种都提供了两种接口形式: `通用接口parser?url=`和`网盘标志/分享key拼接的短地址(标志短链)`,所有规则参考示例。
|
每一种都提供了两种接口形式: `通用接口parser?url=`和`网盘标志/分享key拼接的短地址(标志短链)`,所有规则参考示例。
|
||||||
- 通用接口: `/parser?url=分享链接`,加密分享需要加上参数pwd=密码;
|
- 通用接口: `/parser?url=分享链接&pwd=密码` 没有分享密码去掉&pwd参数;
|
||||||
- 标志短链: `/网盘标识/分享key` 在分享Key后面加上@密码;
|
- 标志短链: `/d/网盘标识/分享key@密码` 没有分享密码去掉@密码;
|
||||||
- 直链JSON: `通用接口`和`标志短链`前加上`/json` 加密分享的密码规则同上;
|
- 直链JSON: `/json/网盘标识/分享key@密码`和`/json/parser?url=分享链接&pwd=密码`
|
||||||
- 网盘标识参考上面网盘支持情况
|
- 网盘标识参考上面网盘支持情况
|
||||||
- 当带有分享密码时需要加上密码参数(pwd)
|
- 当带有分享密码时需要加上密码参数(pwd)
|
||||||
- 移动云云空间,小飞机网盘的加密分享的密码可以忽略
|
- 移动云云空间,小飞机网盘的加密分享的密码可以忽略
|
||||||
- 移动云空间分享key取分享链接中的data参数,比如`&data=xxx`的参数就是xxx
|
- 移动云空间分享key取分享链接中的data参数,比如`&data=xxx`的参数就是xxx
|
||||||
|
|
||||||
API规则:
|
API规则:
|
||||||
|
> 建议使用UrlEncode编码分享链接
|
||||||
```
|
```
|
||||||
|
|
||||||
1. 解析并自动302跳转 :
|
1. 解析并自动302跳转
|
||||||
http://your_host/parser?url=分享链接&pwd=xxx
|
http://your_host/parser?url=分享链接&pwd=xxx
|
||||||
http://your_host/网盘标识/分享key@分享密码
|
或者 http://your_host/parser?url=UrlEncode(分享链接)&pwd=xxx
|
||||||
|
http://your_host/d/网盘标识/分享key@分享密码
|
||||||
2. 获取解析后的直链--JSON格式
|
2. 获取解析后的直链--JSON格式
|
||||||
http://your_host/json/parser?url=分享链接&pwd=xxx
|
http://your_host/json/parser?url=分享链接&pwd=xxx
|
||||||
http://your_host/json/网盘标识/分享key@分享密码
|
http://your_host/json/网盘标识/分享key@分享密码
|
||||||
|
|
||||||
```
|
```
|
||||||
json返回数据格式示例:
|
json返回数据格式示例:
|
||||||
`shareKey`: 全局分享key
|
`shareKey`: 全局分享key
|
||||||
@@ -83,6 +103,50 @@ json返回数据格式示例:
|
|||||||
"timestamp": 1726637151902
|
"timestamp": 1726637151902
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
2. 分享链接详情接口 /v2/linkInfo?url=分享链接
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"success": true,
|
||||||
|
"count": 0,
|
||||||
|
"data": {
|
||||||
|
"downLink": "https://lz.qaiu.top/d/fj/xx",
|
||||||
|
"apiLink": "https://lz.qaiu.top/json/fj/xx",
|
||||||
|
"cacheHitTotal": 5,
|
||||||
|
"parserTotal": 2,
|
||||||
|
"sumTotal": 7,
|
||||||
|
"shareLinkInfo": {
|
||||||
|
"shareKey": "xx",
|
||||||
|
"panName": "小飞机网盘",
|
||||||
|
"type": "fj",
|
||||||
|
"sharePassword": "",
|
||||||
|
"shareUrl": "https://share.feijipan.com/s/xx",
|
||||||
|
"standardUrl": "https://www.feijix.com/s/xx",
|
||||||
|
"otherParam": {
|
||||||
|
"UA": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0"
|
||||||
|
},
|
||||||
|
"cacheKey": "fj:xx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestamp": 1736489219402
|
||||||
|
}
|
||||||
|
```
|
||||||
|
3. 解析次数统计接口 /v2/statisticsInfo
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"success": true,
|
||||||
|
"count": 0,
|
||||||
|
"data": {
|
||||||
|
"parserTotal": 320508,
|
||||||
|
"cacheTotal": 5957910,
|
||||||
|
"total": 6278418
|
||||||
|
},
|
||||||
|
"timestamp": 1736489378770
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
IDEA HttpClient示例:
|
IDEA HttpClient示例:
|
||||||
|
|
||||||
@@ -156,6 +220,7 @@ mvn package
|
|||||||
|
|
||||||
### Docker 部署(Main分支)
|
### Docker 部署(Main分支)
|
||||||
|
|
||||||
|
#### 海外服务器Docker部署
|
||||||
```shell
|
```shell
|
||||||
# 创建目录
|
# 创建目录
|
||||||
mkdir -p netdisk-fast-download
|
mkdir -p netdisk-fast-download
|
||||||
@@ -178,7 +243,32 @@ docker run -d -it --name netdisk-fast-download -p 6401:6401 --restart unless-sto
|
|||||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --cleanup --run-once netdisk-fast-download
|
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --cleanup --run-once netdisk-fast-download
|
||||||
```
|
```
|
||||||
|
|
||||||
### [宝塔安装参考](https://blog.qaiu.top/archives/netdisk-fast-download-bao-ta-an-zhuang-jiao-cheng)
|
#### 国内Docker部署
|
||||||
|
```shell
|
||||||
|
# 创建目录
|
||||||
|
mkdir -p netdisk-fast-download
|
||||||
|
cd netdisk-fast-download
|
||||||
|
|
||||||
|
# 拉取镜像
|
||||||
|
docker pull ghcr.nju.edu.cn/qaiu/netdisk-fast-download:main
|
||||||
|
|
||||||
|
# 复制配置文件(或下载仓库web-service\src\main\resources)
|
||||||
|
docker create --name netdisk-fast-download ghcr.nju.edu.cn/qaiu/netdisk-fast-download:main
|
||||||
|
docker cp netdisk-fast-download:/app/resources ./resources
|
||||||
|
docker rm netdisk-fast-download
|
||||||
|
|
||||||
|
# 启动容器
|
||||||
|
docker run -d -it --name netdisk-fast-download -p 6401:6401 --restart unless-stopped -e TZ=Asia/Shanghai -v ./resources:/app/resources -v ./db:/app/db -v ./logs:/app/logs ghcr.nju.edu.cn/qaiu/netdisk-fast-download:main
|
||||||
|
|
||||||
|
# 反代6401端口
|
||||||
|
|
||||||
|
# 升级容器
|
||||||
|
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --cleanup --run-once netdisk-fast-download
|
||||||
|
```
|
||||||
|
|
||||||
|
### 宝塔部署指引 -> [点击进入宝塔部署教程](https://blog.qaiu.top/archives/netdisk-fast-download-bao-ta-an-zhuang-jiao-cheng)
|
||||||
|
|
||||||
|
### Linux命令行部署
|
||||||
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
|
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
|
||||||
```shell
|
```shell
|
||||||
cd ~
|
cd ~
|
||||||
@@ -220,17 +310,30 @@ resources目录下包含服务端配置文件 配置文件自带说明,具体
|
|||||||
app-dev.yml 可以配置解析服务相关信息, 包括端口,域名,缓存时长等
|
app-dev.yml 可以配置解析服务相关信息, 包括端口,域名,缓存时长等
|
||||||
server-proxy.yml 可以配置代理服务运行的相关信息, 包括前端反向代理端口,路径等
|
server-proxy.yml 可以配置代理服务运行的相关信息, 包括前端反向代理端口,路径等
|
||||||
|
|
||||||
|
### ip代理配置说明
|
||||||
|
有时候解析量很大,IP容易被ban,这时候可以使用其他服务器搭建nfd-proxy代理服务。
|
||||||
|
|
||||||
|
修改配置文件:
|
||||||
|
app-dev.yml
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
proxy:
|
||||||
|
- panTypes: pgd,pdb,pod # 网盘标识
|
||||||
|
type: http # 支持http/socks4/socks5
|
||||||
|
host: 127.0.0.1 # 代理IP
|
||||||
|
port: 7890 # 端口
|
||||||
|
username: # 用户名
|
||||||
|
password: # 密码
|
||||||
|
```
|
||||||
|
nfd-proxy搭建http代理服务器
|
||||||
|
参考https://github.com/nfd-parser/nfd-proxy
|
||||||
|
|
||||||
## 0.1.9 开发计划
|
## 0.1.9 开发计划
|
||||||
- 超星网盘解析 doing
|
|
||||||
- 带Referer头的js请求下载 doing
|
|
||||||
- 城通网盘解析 √
|
|
||||||
- 目录解析(专属版)
|
- 目录解析(专属版)
|
||||||
- 带cookie/token参数解析大文件(专属版)
|
- 带cookie/token参数解析大文件(专属版)
|
||||||
- docker
|
|
||||||
|
|
||||||
**技术栈:**
|
**技术栈:**
|
||||||
Jdk17+Vert.x4.4.1
|
Jdk17+Vert.x4
|
||||||
Core模块集成Vert.x实现类似spring的注解式路由API
|
Core模块集成Vert.x实现类似spring的注解式路由API
|
||||||
|
|
||||||
|
|
||||||
@@ -238,10 +341,17 @@ Core模块集成Vert.x实现类似spring的注解式路由API
|
|||||||
|
|
||||||
[](https://star-history.com/#qaiu/netdisk-fast-download&Date)
|
[](https://star-history.com/#qaiu/netdisk-fast-download&Date)
|
||||||
|
|
||||||
|
## **免责声明**
|
||||||
|
- 用户在使用本项目时,应自行承担风险,并确保其行为符合当地法律法规及网盘服务提供商的使用条款。
|
||||||
|
- 开发者不对用户因使用本项目而导致的任何后果负责,包括但不限于数据丢失、隐私泄露、账号封禁或其他任何形式的损害。
|
||||||
|
|
||||||
## 支持该项目
|
## 支持该项目
|
||||||
开源不易,用爱发电,本项目长期维护如果觉得有帮助, 可以请作者喝杯咖啡, 感谢支持
|
开源不易,用爱发电,本项目长期维护如果觉得有帮助, 可以请作者喝杯咖啡, 感谢支持
|
||||||
赞助88元以上, 可以优先体验专享版--大文件解析,目录解析
|
|
||||||
|
### 关于专属版
|
||||||
|
99元, 提供对小飞机,蓝奏优享大文件解析的支持, 提供天翼云盘,移动云盘,联调云盘的解析支持
|
||||||
|
199元, 包含部署服务和首页定制, 需提供宝塔环境
|
||||||
|
可以提供功能定制开发, 加v价格详谈: qaiu-cn
|
||||||

|

|
||||||
|
|
||||||
[手机端支付宝打赏跳转链接](https://qr.alipay.com/fkx01882dnoxxtjenhlxt53)
|
[手机端支付宝打赏跳转链接](https://qr.alipay.com/fkx01882dnoxxtjenhlxt53)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<name>netdisk-fast-download</name>
|
<name>netdisk-fast-download</name>
|
||||||
<description>netdisk fast download service</description>
|
<description>netdisk fast download service</description>
|
||||||
<executable>java</executable>
|
<executable>java</executable>
|
||||||
<arguments> -server -Xmx128m -jar ${jar} </arguments>
|
<arguments> -server -Xmx128m -Dfile.encoding=utf8 -jar ${jar} </arguments>
|
||||||
<logpath>${dd}\logs</logpath>
|
<logpath>${dd}\logs</logpath>
|
||||||
<log mode="roll-by-time">
|
<log mode="roll-by-time">
|
||||||
<pattern>yyyyMMdd</pattern>
|
<pattern>yyyyMMdd</pattern>
|
||||||
|
|||||||
@@ -59,6 +59,18 @@
|
|||||||
<artifactId>vertx-jdbc-client</artifactId>
|
<artifactId>vertx-jdbc-client</artifactId>
|
||||||
<version>${vertx.version}</version>
|
<version>${vertx.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<version>9.2.0</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- PG驱动-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>42.7.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ public @interface Constraint {
|
|||||||
boolean notNull() default false;
|
boolean notNull() default false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 唯一键约束 TODO 待实现
|
|
||||||
* @return 唯一键约束
|
* @return 唯一键约束
|
||||||
*/
|
*/
|
||||||
String uniqueKey() default "";
|
String uniqueKey() default "";
|
||||||
@@ -32,7 +31,7 @@ public @interface Constraint {
|
|||||||
*/
|
*/
|
||||||
String defaultValue() default "";
|
String defaultValue() default "";
|
||||||
/**
|
/**
|
||||||
* 默认值是否是函数
|
* 默认值是否是函数 like value=NOW()
|
||||||
* @return false 不是函数
|
* @return false 不是函数
|
||||||
*/
|
*/
|
||||||
boolean defaultValueIsFunction() default false;
|
boolean defaultValueIsFunction() default false;
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package cn.qaiu.db.ddl;
|
||||||
|
|
||||||
|
import cn.qaiu.db.pool.JDBCPoolInit;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class CreateDatabase {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(JDBCPoolInit.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析数据库URL,获取数据库名
|
||||||
|
* @param url 数据库URL
|
||||||
|
* @return 数据库名
|
||||||
|
*/
|
||||||
|
public static String getDatabaseName(String url) {
|
||||||
|
// 正则表达式匹配数据库名
|
||||||
|
String regex = "jdbc:mysql://[^/]+/(\\w+)(\\?.*)?";
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(url);
|
||||||
|
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Invalid database URL: " + url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用JDBC原生方法创建数据库
|
||||||
|
* @param url 数据库连接URL
|
||||||
|
* @param user 数据库用户名
|
||||||
|
* @param password 数据库密码
|
||||||
|
*/
|
||||||
|
public static void createDatabase(String url, String user, String password) {
|
||||||
|
String dbName = getDatabaseName(url);
|
||||||
|
LOGGER.info(">>>>>>>>>>> 创建数据库:'{}' <<<<<<<<<<<< ", dbName);
|
||||||
|
|
||||||
|
// 去掉数据库名,构建不带数据库名的URL
|
||||||
|
String baseUrl = url.substring(0, url.lastIndexOf("/") + 1) + "?characterEncoding=UTF-8&useUnicode=true";
|
||||||
|
|
||||||
|
try (Connection conn = DriverManager.getConnection(baseUrl, user, password);
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
// 创建数据库
|
||||||
|
stmt.executeUpdate("CREATE DATABASE IF NOT EXISTS " + dbName + " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||||
|
LOGGER.info(">>>>>>>>>>> 数据库'{}'创建成功 <<<<<<<<<<<<", dbName);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void createDatabase(JsonObject dbConfig) {
|
||||||
|
createDatabase(
|
||||||
|
dbConfig.getString("jdbcUrl"),
|
||||||
|
dbConfig.getString("username"),
|
||||||
|
dbConfig.getString("password")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,9 @@ import io.vertx.codegen.format.CamelCase;
|
|||||||
import io.vertx.codegen.format.Case;
|
import io.vertx.codegen.format.Case;
|
||||||
import io.vertx.codegen.format.LowerCamelCase;
|
import io.vertx.codegen.format.LowerCamelCase;
|
||||||
import io.vertx.codegen.format.SnakeCase;
|
import io.vertx.codegen.format.SnakeCase;
|
||||||
import io.vertx.jdbcclient.JDBCPool;
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.Promise;
|
||||||
|
import io.vertx.sqlclient.Pool;
|
||||||
import io.vertx.sqlclient.templates.annotations.Column;
|
import io.vertx.sqlclient.templates.annotations.Column;
|
||||||
import io.vertx.sqlclient.templates.annotations.RowMapped;
|
import io.vertx.sqlclient.templates.annotations.RowMapped;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -14,9 +16,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建表
|
* 创建表
|
||||||
@@ -24,154 +24,312 @@ import java.util.Set;
|
|||||||
* @author <a href="https://qaiu.top">QAIU</a>
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
*/
|
*/
|
||||||
public class CreateTable {
|
public class CreateTable {
|
||||||
public static Map<Class<?>, String> javaProperty2SqlColumnMap = new HashMap<>();
|
public static Map<Class<?>, String> javaProperty2SqlColumnMap = new HashMap<>() {{
|
||||||
|
// Java类型到SQL类型的映射
|
||||||
|
put(Integer.class, "INT");
|
||||||
|
put(Short.class, "SMALLINT");
|
||||||
|
put(Byte.class, "TINYINT");
|
||||||
|
put(Long.class, "BIGINT");
|
||||||
|
put(java.math.BigDecimal.class, "DECIMAL");
|
||||||
|
put(Double.class, "DOUBLE");
|
||||||
|
put(Float.class, "REAL");
|
||||||
|
put(Boolean.class, "BOOLEAN");
|
||||||
|
put(String.class, "VARCHAR");
|
||||||
|
put(Date.class, "TIMESTAMP");
|
||||||
|
put(java.time.LocalDateTime.class, "TIMESTAMP");
|
||||||
|
put(java.sql.Timestamp.class, "TIMESTAMP");
|
||||||
|
put(java.sql.Date.class, "DATE");
|
||||||
|
put(java.sql.Time.class, "TIME");
|
||||||
|
|
||||||
|
// 基本数据类型
|
||||||
|
put(int.class, "INT");
|
||||||
|
put(short.class, "SMALLINT");
|
||||||
|
put(byte.class, "TINYINT");
|
||||||
|
put(long.class, "BIGINT");
|
||||||
|
put(double.class, "DOUBLE");
|
||||||
|
put(float.class, "REAL");
|
||||||
|
put(boolean.class, "BOOLEAN");
|
||||||
|
}};
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(CreateTable.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(CreateTable.class);
|
||||||
|
public static String UNIQUE_PREFIX = "idx_";
|
||||||
static {
|
|
||||||
javaProperty2SqlColumnMap.put(Integer.class, "INT");
|
|
||||||
javaProperty2SqlColumnMap.put(Short.class, "SMALLINT");
|
|
||||||
javaProperty2SqlColumnMap.put(Byte.class, "TINYINT");
|
|
||||||
javaProperty2SqlColumnMap.put(Long.class, "BIGINT");
|
|
||||||
javaProperty2SqlColumnMap.put(java.math.BigDecimal.class, "DECIMAL");
|
|
||||||
javaProperty2SqlColumnMap.put(Double.class, "DOUBLE");
|
|
||||||
javaProperty2SqlColumnMap.put(Float.class, "REAL");
|
|
||||||
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");
|
|
||||||
|
|
||||||
javaProperty2SqlColumnMap.put(int.class, "INT");
|
|
||||||
javaProperty2SqlColumnMap.put(short.class, "SMALLINT");
|
|
||||||
javaProperty2SqlColumnMap.put(byte.class, "TINYINT");
|
|
||||||
javaProperty2SqlColumnMap.put(long.class, "BIGINT");
|
|
||||||
javaProperty2SqlColumnMap.put(double.class, "DOUBLE");
|
|
||||||
javaProperty2SqlColumnMap.put(float.class, "REAL");
|
|
||||||
javaProperty2SqlColumnMap.put(boolean.class, "BOOLEAN");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Case getCase(Class<?> clz) {
|
private static Case getCase(Class<?> clz) {
|
||||||
switch (clz.getName()) {
|
return switch (clz.getName()) {
|
||||||
case "io.vertx.codegen.format.CamelCase":
|
case "io.vertx.codegen.format.CamelCase" -> CamelCase.INSTANCE;
|
||||||
return CamelCase.INSTANCE;
|
case "io.vertx.codegen.format.SnakeCase" -> SnakeCase.INSTANCE;
|
||||||
case "io.vertx.codegen.format.SnakeCase":
|
case "io.vertx.codegen.format.LowerCamelCase" -> LowerCamelCase.INSTANCE;
|
||||||
return SnakeCase.INSTANCE;
|
default -> throw new UnsupportedOperationException();
|
||||||
case "io.vertx.codegen.format.LowerCamelCase":
|
};
|
||||||
return LowerCamelCase.INSTANCE;
|
}
|
||||||
default:
|
|
||||||
throw new UnsupportedOperationException();
|
public static List<String> getCreateTableSQL(Class<?> clz, JDBCType type) {
|
||||||
|
// 获取表名和主键
|
||||||
|
TableInfo tableInfo = extractTableInfo(clz, type);
|
||||||
|
|
||||||
|
// 构建表的SQL语句
|
||||||
|
List<String> sqlList = new ArrayList<>();
|
||||||
|
StringBuilder sb = new StringBuilder(50);
|
||||||
|
sb.append("CREATE TABLE IF NOT EXISTS ")
|
||||||
|
.append(tableInfo.quotationMarks).append(tableInfo.tableName).append(tableInfo.quotationMarks)
|
||||||
|
.append(" ( \r\n ");
|
||||||
|
|
||||||
|
// 处理字段并生成列定义
|
||||||
|
List<String> indexSQLs = new ArrayList<>();
|
||||||
|
processFields(clz, tableInfo, sb, indexSQLs);
|
||||||
|
|
||||||
|
// 去掉最后一个逗号并添加表尾部信息
|
||||||
|
String tableSQL = sb.substring(0, sb.lastIndexOf(",")) + tableInfo.endStr;
|
||||||
|
sqlList.add(tableSQL);
|
||||||
|
|
||||||
|
// 添加索引SQL
|
||||||
|
sqlList.addAll(indexSQLs);
|
||||||
|
|
||||||
|
return sqlList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 修改extractTableInfo方法,处理没有Table注解时默认使用id字段作为主键
|
||||||
|
private static TableInfo extractTableInfo(Class<?> clz, JDBCType type) {
|
||||||
|
String quotationMarks;
|
||||||
|
String endStr;
|
||||||
|
if (type == JDBCType.MySQL) {
|
||||||
|
quotationMarks = "`";
|
||||||
|
endStr = ")ENGINE=InnoDB DEFAULT CHARSET=utf8;";
|
||||||
|
} else {
|
||||||
|
quotationMarks = "\"";
|
||||||
|
endStr = ");";
|
||||||
|
}
|
||||||
|
|
||||||
|
String primaryKey = null;
|
||||||
|
String tableName = null;
|
||||||
|
Case caseFormat = SnakeCase.INSTANCE;
|
||||||
|
|
||||||
|
// 判断类上是否有RowMapped注解
|
||||||
|
if (clz.isAnnotationPresent(RowMapped.class)) {
|
||||||
|
RowMapped annotation = clz.getAnnotation(RowMapped.class);
|
||||||
|
caseFormat = getCase(annotation.formatter());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断类上是否有Table注解
|
||||||
|
if (clz.isAnnotationPresent(Table.class)) {
|
||||||
|
Table annotation = clz.getAnnotation(Table.class);
|
||||||
|
tableName = StringUtils.isNotEmpty(annotation.value())
|
||||||
|
? annotation.value()
|
||||||
|
: LowerCamelCase.INSTANCE.to(caseFormat, clz.getSimpleName());
|
||||||
|
primaryKey = annotation.keyFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果表名仍为null,使用类名转下划线命名作为表名
|
||||||
|
if (StringUtils.isEmpty(tableName)) {
|
||||||
|
tableName = LowerCamelCase.INSTANCE.to(SnakeCase.INSTANCE, clz.getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果主键为空,默认使用id字段作为主键
|
||||||
|
if (StringUtils.isEmpty(primaryKey)) {
|
||||||
|
try {
|
||||||
|
clz.getDeclaredField("id");
|
||||||
|
primaryKey = "id";
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
// 如果没有id字段,不设置主键
|
||||||
|
primaryKey = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TableInfo(tableName, quotationMarks, endStr, primaryKey, caseFormat, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改processFields方法,处理索引
|
||||||
|
private static void processFields(Class<?> clz, TableInfo tableInfo, StringBuilder sb, List<String> indexSQLs) {
|
||||||
|
Field[] fields = clz.getDeclaredFields();
|
||||||
|
for (Field field : fields) {
|
||||||
|
// 跳过无效字段
|
||||||
|
if (isIgnoredField(field)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取字段名和SQL类型
|
||||||
|
String column = LowerCamelCase.INSTANCE.to(tableInfo.caseFormat, field.getName());
|
||||||
|
String sqlType = javaProperty2SqlColumnMap.get(field.getType());
|
||||||
|
|
||||||
|
// 处理字段注解
|
||||||
|
column = processColumnAnnotation(field, column);
|
||||||
|
int[] decimalSize = {22, 2};
|
||||||
|
int varcharSize = 255;
|
||||||
|
if (field.isAnnotationPresent(Length.class)) {
|
||||||
|
Length length = field.getAnnotation(Length.class);
|
||||||
|
decimalSize = length.decimalSize();
|
||||||
|
varcharSize = length.varcharSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建列定义
|
||||||
|
sb.append(tableInfo.quotationMarks).append(column).append(tableInfo.quotationMarks)
|
||||||
|
.append(" ").append(sqlType);
|
||||||
|
appendTypeLength(sqlType, sb, decimalSize, varcharSize);
|
||||||
|
appendConstraints(field, sb, tableInfo);
|
||||||
|
appendPrimaryKey(tableInfo.primaryKey, column, sb);
|
||||||
|
|
||||||
|
// 添加索引
|
||||||
|
appendIndex(tableInfo, indexSQLs, field);
|
||||||
|
|
||||||
|
sb.append(",\n ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getCreateTableSQL(Class<?> clz, JDBCType type) {
|
// 判断是否忽略字段
|
||||||
String quotationMarks = type == JDBCType.H2DB ? "\"" : "`";
|
private static boolean isIgnoredField(Field field) {
|
||||||
String endStr = type == JDBCType.H2DB ? ");" : ")ENGINE=InnoDB DEFAULT CHARSET=utf8;";
|
return field.getName().equals("serialVersionUID")
|
||||||
// 判断类上是否有次注解
|
|| StringUtils.isEmpty(javaProperty2SqlColumnMap.get(field.getType()))
|
||||||
String primaryKey = null; // 主键
|
|| field.isAnnotationPresent(TableGenIgnore.class);
|
||||||
String tableName = null; // 表名
|
}
|
||||||
Case caseFormat = SnakeCase.INSTANCE;
|
|
||||||
if (clz.isAnnotationPresent(RowMapped.class)) {
|
|
||||||
RowMapped annotation = clz.getAnnotation(RowMapped.class);
|
|
||||||
Class<? extends Case> formatter = annotation.formatter();
|
|
||||||
caseFormat = getCase(formatter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clz.isAnnotationPresent(Table.class)) {
|
// 处理Column注解
|
||||||
// 获取类上的注解
|
private static String processColumnAnnotation(Field field, String column) {
|
||||||
Table annotation = clz.getAnnotation(Table.class);
|
if (field.isAnnotationPresent(Column.class)) {
|
||||||
// 输出注解上的类名
|
Column columnAnnotation = field.getAnnotation(Column.class);
|
||||||
String tableNameAnnotation = annotation.value();
|
if (StringUtils.isNotBlank(columnAnnotation.name())) {
|
||||||
if (StringUtils.isNotEmpty(tableNameAnnotation)) {
|
column = columnAnnotation.name();
|
||||||
tableName = tableNameAnnotation;
|
|
||||||
} else {
|
|
||||||
tableName = LowerCamelCase.INSTANCE.to(caseFormat, clz.getSimpleName());
|
|
||||||
}
|
}
|
||||||
primaryKey = annotation.keyFields();
|
|
||||||
}
|
}
|
||||||
Field[] fields = clz.getDeclaredFields();
|
return column;
|
||||||
String column;
|
}
|
||||||
int[] decimalSize = {22, 2};
|
|
||||||
int varcharSize = 255;
|
// 添加类型长度
|
||||||
StringBuilder sb = new StringBuilder(50);
|
private static void appendTypeLength(String sqlType, StringBuilder sb, int[] decimalSize, int varcharSize) {
|
||||||
sb.append("CREATE TABLE IF NOT EXISTS ").append(quotationMarks).append(tableName).append(quotationMarks).append(" ( \r\n ");
|
if ("DECIMAL".equals(sqlType)) {
|
||||||
boolean firstId = true;
|
sb.append("(").append(decimalSize[0]).append(",").append(decimalSize[1]).append(")");
|
||||||
for (Field f : fields) {
|
} else if ("VARCHAR".equals(sqlType)) {
|
||||||
Class<?> paramType = f.getType();
|
sb.append("(").append(varcharSize).append(")");
|
||||||
String sqlType = javaProperty2SqlColumnMap.get(paramType);
|
}
|
||||||
if (f.getName().equals("serialVersionUID") || StringUtils.isEmpty(sqlType) || f.isAnnotationPresent(TableGenIgnore.class)) {
|
}
|
||||||
continue;
|
|
||||||
|
// 添加约束
|
||||||
|
private static void appendConstraints(Field field, StringBuilder sb, TableInfo tableInfo) {
|
||||||
|
JDBCType type = tableInfo.dbType;
|
||||||
|
|
||||||
|
if (field.isAnnotationPresent(Constraint.class)) {
|
||||||
|
Constraint constraint = field.getAnnotation(Constraint.class);
|
||||||
|
if (constraint.notNull()) {
|
||||||
|
sb.append(" NOT NULL");
|
||||||
}
|
}
|
||||||
column = LowerCamelCase.INSTANCE.to(caseFormat, f.getName());
|
String apostrophe = constraint.defaultValueIsFunction() ? "" : "'";
|
||||||
if (f.isAnnotationPresent(Column.class)) {
|
if (StringUtils.isNotEmpty(constraint.defaultValue())) {
|
||||||
Column columnAnnotation = f.getAnnotation(Column.class);
|
sb.append(" DEFAULT ").append(apostrophe).append(constraint.defaultValue()).append(apostrophe);
|
||||||
//输出注解属性
|
|
||||||
if (StringUtils.isNotBlank(columnAnnotation.name())) {
|
|
||||||
column = columnAnnotation.name();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (f.isAnnotationPresent(Length.class)) {
|
if (constraint.autoIncrement()) {
|
||||||
Length fieldAnnotation = f.getAnnotation(Length.class);
|
if (type == JDBCType.PostgreSQL) {
|
||||||
decimalSize = fieldAnnotation.decimalSize();
|
// 需要移除字段类型(最后一个单词)
|
||||||
varcharSize = fieldAnnotation.varcharSize();
|
if (field.getType().equals(Integer.class)) {
|
||||||
}
|
sb.delete(sb.lastIndexOf(" "), sb.length());
|
||||||
sb.append(quotationMarks).append(column).append(quotationMarks);
|
sb.append(" SERIAL");
|
||||||
sb.append(" ").append(sqlType);
|
} else if (field.getType().equals(Long.class)) {
|
||||||
// 添加类型长度
|
sb.delete(sb.lastIndexOf(" "), sb.length());
|
||||||
if (sqlType.equals("DECIMAL")) {
|
sb.append(" BIGSERIAL");
|
||||||
sb.append("(").append(decimalSize[0]).append(",").append(decimalSize[1]).append(")");
|
}
|
||||||
}
|
} else if (field.getType().equals(Integer.class) || field.getType().equals(Long.class)) {
|
||||||
if (sqlType.equals("VARCHAR")) {
|
|
||||||
sb.append("(").append(varcharSize).append(")");
|
|
||||||
}
|
|
||||||
if (f.isAnnotationPresent(Constraint.class)) {
|
|
||||||
Constraint constraintAnnotation = f.getAnnotation(Constraint.class);
|
|
||||||
if (constraintAnnotation.notNull()) {
|
|
||||||
//非空约束
|
|
||||||
sb.append(" NOT NULL");
|
|
||||||
}
|
|
||||||
String apostrophe = constraintAnnotation.defaultValueIsFunction() ? "" : "'";
|
|
||||||
if (StringUtils.isNotEmpty(constraintAnnotation.defaultValue())) {
|
|
||||||
//默认值约束
|
|
||||||
sb.append(" DEFAULT ").append(apostrophe).append(constraintAnnotation.defaultValue()).append(apostrophe);
|
|
||||||
}
|
|
||||||
if (constraintAnnotation.autoIncrement() && paramType.equals(Integer.class) || paramType.equals(Long.class)) {
|
|
||||||
////自增
|
|
||||||
sb.append(" AUTO_INCREMENT");
|
sb.append(" AUTO_INCREMENT");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(primaryKey)) {
|
}
|
||||||
if (firstId) {//类型转换
|
}
|
||||||
sb.append(" PRIMARY KEY");
|
|
||||||
firstId = false;
|
// 添加主键
|
||||||
}
|
private static void appendPrimaryKey(String primaryKey, String column, StringBuilder sb) {
|
||||||
} else {
|
if (StringUtils.isEmpty(primaryKey)) {
|
||||||
if (primaryKey.equals(column.toLowerCase())) {
|
return;
|
||||||
sb.append(" PRIMARY KEY");
|
}
|
||||||
|
if (primaryKey.equalsIgnoreCase(column)) {
|
||||||
|
sb.append(" PRIMARY KEY");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendIndex(TableInfo tableInfo, List<String> indexSQLs, Field field) {
|
||||||
|
if (!field.isAnnotationPresent(Constraint.class)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Constraint constraint = field.getAnnotation(Constraint.class);
|
||||||
|
if (StringUtils.isEmpty(constraint.uniqueKey())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String indexName = UNIQUE_PREFIX + tableInfo.tableName + "_" + constraint.uniqueKey();
|
||||||
|
String columnName = field.getName();
|
||||||
|
|
||||||
|
// 检查是否已有相同索引名称的索引
|
||||||
|
Optional<String> existingIndex = indexSQLs.stream()
|
||||||
|
.filter(sql -> sql.contains(tableInfo.quotationMarks + indexName + tableInfo.quotationMarks))
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
if (existingIndex.isPresent()) {
|
||||||
|
// 如果存在相同索引名称,追加字段到索引定义中
|
||||||
|
String updatedIndex = existingIndex.get().replaceFirst(
|
||||||
|
"\\(([^)]+)\\)", // 匹配索引字段列表
|
||||||
|
"($1, " + tableInfo.quotationMarks + columnName + tableInfo.quotationMarks + ")"
|
||||||
|
);
|
||||||
|
indexSQLs.remove(existingIndex.get());
|
||||||
|
indexSQLs.add(updatedIndex);
|
||||||
|
} else {
|
||||||
|
// 如果不存在相同索引名称,创建新的索引
|
||||||
|
String indexSQL = String.format(
|
||||||
|
"CREATE UNIQUE INDEX %s %s%s%s ON %s%s%s (%s%s%s);",
|
||||||
|
tableInfo.dbType == JDBCType.MySQL ? "" : "IF NOT EXISTS",
|
||||||
|
tableInfo.quotationMarks, indexName, tableInfo.quotationMarks,
|
||||||
|
tableInfo.quotationMarks, tableInfo.tableName, tableInfo.quotationMarks,
|
||||||
|
tableInfo.quotationMarks, columnName, tableInfo.quotationMarks
|
||||||
|
);
|
||||||
|
indexSQLs.add(indexSQL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表信息类
|
||||||
|
private record TableInfo(
|
||||||
|
String tableName, // 表名
|
||||||
|
String quotationMarks, // 引号或反引号
|
||||||
|
String endStr, // 表尾部信息
|
||||||
|
String primaryKey, // 主键字段
|
||||||
|
Case caseFormat, // 命名格式
|
||||||
|
JDBCType dbType // 数据库类型
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Future<Void> createTable(Pool pool, JDBCType type) {
|
||||||
|
Promise<Void> promise = Promise.promise();
|
||||||
|
Set<Class<?>> tableClasses = ReflectionUtil.getReflections().getTypesAnnotatedWith(Table.class);
|
||||||
|
|
||||||
|
if (tableClasses.isEmpty()) {
|
||||||
|
LOGGER.warn("Table model class not found");
|
||||||
|
promise.complete();
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Future<Object>> futures = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Class<?> clazz : tableClasses) {
|
||||||
|
List<String> sqlList = getCreateTableSQL(clazz, type);
|
||||||
|
LOGGER.info("Class `{}` auto-generate table", clazz.getName());
|
||||||
|
|
||||||
|
for (String sql : sqlList) {
|
||||||
|
try {
|
||||||
|
pool.query(sql).execute().toCompletionStage().toCompletableFuture().join();
|
||||||
|
futures.add(Future.succeededFuture());
|
||||||
|
LOGGER.debug("Executed SQL:\n{}", sql);
|
||||||
|
} catch (Exception e) {
|
||||||
|
String message = e.getMessage();
|
||||||
|
if (message != null && message.contains("Duplicate key name")) {
|
||||||
|
LOGGER.warn("Ignoring duplicate key error: {}", message);
|
||||||
|
futures.add(Future.succeededFuture());
|
||||||
|
} else {
|
||||||
|
LOGGER.error("SQL Error: {}\nSQL: {}", message, sql);
|
||||||
|
futures.add(Future.failedFuture(e));
|
||||||
|
throw new RuntimeException(e); // Stop execution for other exceptions
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sb.append(",\n ");
|
|
||||||
}
|
}
|
||||||
String sql = sb.toString();
|
|
||||||
//去掉最后一个逗号
|
Future.all(futures).onSuccess(r -> promise.complete()).onFailure(promise::fail);
|
||||||
int lastIndex = sql.lastIndexOf(",");
|
return promise.future();
|
||||||
sql = sql.substring(0, lastIndex) + sql.substring(lastIndex + 1);
|
|
||||||
return sql.substring(0, sql.length() - 1) + endStr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void createTable(JDBCPool pool, JDBCType type) {
|
|
||||||
Set<Class<?>> tableClassList = ReflectionUtil.getReflections().getTypesAnnotatedWith(Table.class);
|
|
||||||
if (tableClassList.isEmpty()) LOGGER.info("Table model class not fount");
|
|
||||||
tableClassList.forEach(clazz -> {
|
|
||||||
String createTableSQL = getCreateTableSQL(clazz, type);
|
|
||||||
|
|
||||||
pool.query(createTableSQL).execute().onSuccess(
|
|
||||||
rs -> LOGGER.info("table auto generate:\n" + createTableSQL)
|
|
||||||
).onFailure(e -> {
|
|
||||||
LOGGER.error(e.getMessage() + " SQL: \n" + createTableSQL);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package cn.qaiu.db.pool;
|
package cn.qaiu.db.pool;
|
||||||
|
|
||||||
import cn.qaiu.db.ddl.CreateTable;
|
import cn.qaiu.db.ddl.CreateTable;
|
||||||
|
import cn.qaiu.db.ddl.CreateDatabase;
|
||||||
import cn.qaiu.vx.core.util.VertxHolder;
|
import cn.qaiu.vx.core.util.VertxHolder;
|
||||||
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.Vertx;
|
import io.vertx.core.Vertx;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.jdbcclient.JDBCPool;
|
import io.vertx.jdbcclient.JDBCPool;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -17,18 +20,29 @@ import org.slf4j.LoggerFactory;
|
|||||||
public class JDBCPoolInit {
|
public class JDBCPoolInit {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(JDBCPoolInit.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(JDBCPoolInit.class);
|
||||||
|
|
||||||
|
private static final String providerClass = io.vertx.ext.jdbc.spi.impl.HikariCPDataSourceProvider.class.getName();
|
||||||
|
|
||||||
private JDBCPool pool = null;
|
private JDBCPool pool = null;
|
||||||
JsonObject dbConfig;
|
JsonObject dbConfig;
|
||||||
Vertx vertx = VertxHolder.getVertxInstance();
|
Vertx vertx = VertxHolder.getVertxInstance();
|
||||||
String url;
|
String url;
|
||||||
|
|
||||||
private final JDBCType type;
|
private final JDBCType type;
|
||||||
|
|
||||||
private static JDBCPoolInit instance;
|
private static JDBCPoolInit instance;
|
||||||
|
|
||||||
|
public JDBCType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
public JDBCPoolInit(Builder builder) {
|
public JDBCPoolInit(Builder builder) {
|
||||||
this.dbConfig = builder.dbConfig;
|
this.dbConfig = builder.dbConfig;
|
||||||
this.url = builder.url;
|
this.url = builder.url;
|
||||||
this.type = builder.type;
|
this.type = JDBCType.getJDBCTypeByURL(builder.url);
|
||||||
|
if (StringUtils.isBlank(builder.dbConfig.getString("provider_class"))) {
|
||||||
|
builder.dbConfig.put("provider_class", providerClass);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
@@ -42,12 +56,10 @@ public class JDBCPoolInit {
|
|||||||
public static class Builder {
|
public static class Builder {
|
||||||
private JsonObject dbConfig;
|
private JsonObject dbConfig;
|
||||||
private String url;
|
private String url;
|
||||||
private JDBCType type;
|
|
||||||
|
|
||||||
public Builder config(JsonObject dbConfig) {
|
public Builder config(JsonObject dbConfig) {
|
||||||
this.dbConfig = dbConfig;
|
this.dbConfig = dbConfig;
|
||||||
this.url = dbConfig.getString("jdbcUrl");
|
this.url = dbConfig.getString("jdbcUrl");
|
||||||
this.type = JDBCUtil.getJDBCType(dbConfig.getString("driverClassName"));
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,24 +71,28 @@ public class JDBCPoolInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* init h2db<br>
|
* init h2db<br>
|
||||||
* 这个方法只允许调用一次
|
* 这个方法只允许调用一次
|
||||||
*/
|
*/
|
||||||
synchronized public void initPool() {
|
synchronized public Future<Void> initPool() {
|
||||||
if (pool != null) {
|
if (pool != null) {
|
||||||
LOGGER.error("pool 重复初始化");
|
LOGGER.error("pool 重复初始化");
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化数据库连接
|
// 初始化数据库连接
|
||||||
// 初始化连接池
|
// 初始化连接池
|
||||||
|
if (type == JDBCType.MySQL) {
|
||||||
|
CreateDatabase.createDatabase(dbConfig);
|
||||||
|
}
|
||||||
pool = JDBCPool.pool(vertx, dbConfig);
|
pool = JDBCPool.pool(vertx, dbConfig);
|
||||||
CreateTable.createTable(pool, type);
|
|
||||||
LOGGER.info("数据库连接初始化: URL=" + url);
|
LOGGER.info("数据库连接初始化: URL=" + url);
|
||||||
|
return CreateTable.createTable(pool, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取连接池
|
* 获取连接池
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,9 +1,35 @@
|
|||||||
package cn.qaiu.db.pool;
|
package cn.qaiu.db.pool;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="https://qaiu.top">QAIU</a>
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
* @date 2023/10/10 14:06
|
* @since 2023/10/10 14:06
|
||||||
*/
|
*/
|
||||||
public enum JDBCType {
|
public enum JDBCType {
|
||||||
MySQL, H2DB
|
// 添加驱动类型字段
|
||||||
|
MySQL("jdbc:mysql:"),
|
||||||
|
H2DB("jdbc:h2:"),
|
||||||
|
PostgreSQL("jdbc:postgresql:");
|
||||||
|
private final String urlPrefix; // JDBC URL 前缀
|
||||||
|
|
||||||
|
// 构造函数
|
||||||
|
JDBCType(String urlPrefix) {
|
||||||
|
this.urlPrefix = urlPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 JDBC URL 前缀
|
||||||
|
public String getUrlPrefix() {
|
||||||
|
return urlPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据 JDBC URL 获取 JDBC 类型
|
||||||
|
public static JDBCType getJDBCTypeByURL(String jdbcURL) {
|
||||||
|
for (JDBCType jdbcType : values()) {
|
||||||
|
if (StringUtils.startsWithIgnoreCase(jdbcURL, jdbcType.getUrlPrefix())) {
|
||||||
|
return jdbcType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new RuntimeException("不支持的SQL类型: " + jdbcURL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package cn.qaiu.db.pool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="https://qaiu.top">QAIU</a>
|
|
||||||
* @date 2023/10/10 14:05
|
|
||||||
*/
|
|
||||||
public class JDBCUtil {
|
|
||||||
public static JDBCType getJDBCType(String deviceName) {
|
|
||||||
switch (deviceName) {
|
|
||||||
case "com.mysql.cj.jdbc.Driver":
|
|
||||||
case "com.mysql.jdbc.Driver":
|
|
||||||
return JDBCType.MySQL;
|
|
||||||
case "org.h2.Driver":
|
|
||||||
return JDBCType.H2DB;
|
|
||||||
}
|
|
||||||
throw new RuntimeException("不支持的SQL驱动类型: " + deviceName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven.build.timestamp.format>yyMMdd_HHmm</maven.build.timestamp.format>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -175,8 +175,13 @@ public class RouterHandlerFactory implements BaseHttpApi {
|
|||||||
route.handler(ResponseTimeHandler.create());
|
route.handler(ResponseTimeHandler.create());
|
||||||
route.handler(ctx -> handlerMethod(instance, method, ctx)).failureHandler(ctx -> {
|
route.handler(ctx -> handlerMethod(instance, method, ctx)).failureHandler(ctx -> {
|
||||||
if (ctx.response().ended()) return;
|
if (ctx.response().ended()) return;
|
||||||
ctx.failure().printStackTrace();
|
// 超时处理器状态码503
|
||||||
doFireJsonResultResponse(ctx, JsonResult.error(ctx.failure().getMessage(), 500));
|
if (ctx.statusCode() == 503 || ctx.failure() == null) {
|
||||||
|
doFireJsonResultResponse(ctx, JsonResult.error("未知异常, 请联系管理员", 500));
|
||||||
|
} else {
|
||||||
|
ctx.failure().printStackTrace();
|
||||||
|
doFireJsonResultResponse(ctx, JsonResult.error(ctx.failure().getMessage(), 500));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else if (method.isAnnotationPresent(SockRouteMapper.class)) {
|
} else if (method.isAnnotationPresent(SockRouteMapper.class)) {
|
||||||
// websocket 基于sockJs
|
// websocket 基于sockJs
|
||||||
|
|||||||
@@ -30,12 +30,10 @@ public class JsonResult<T> implements Serializable {
|
|||||||
|
|
||||||
private int code = SUCCESS_CODE;//状态码
|
private int code = SUCCESS_CODE;//状态码
|
||||||
|
|
||||||
private String msg = SUCCESS_MESSAGE;//消息
|
private String msg = SUCCESS_MESSAGE; //消息
|
||||||
|
|
||||||
private boolean success = true; //是否成功
|
private boolean success = true; //是否成功
|
||||||
|
|
||||||
private int count;
|
|
||||||
|
|
||||||
private T data;
|
private T data;
|
||||||
|
|
||||||
private long timestamp = System.currentTimeMillis(); //时间戳
|
private long timestamp = System.currentTimeMillis(); //时间戳
|
||||||
@@ -54,20 +52,6 @@ public class JsonResult<T> implements Serializable {
|
|||||||
this.success = success;
|
this.success = success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JsonResult(int code, String msg, boolean success, T data, int count) {
|
|
||||||
this(code, msg, success, data);
|
|
||||||
this.count = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCount() {
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JsonResult<T> setCount(int count) {
|
|
||||||
this.count = count;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCode() {
|
public int getCode() {
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
@@ -136,20 +120,9 @@ public class JsonResult<T> implements Serializable {
|
|||||||
return new JsonResult<>(SUCCESS_CODE, msg, true, data);
|
return new JsonResult<>(SUCCESS_CODE, msg, true, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应成功消息和数据实体
|
|
||||||
public static <T> JsonResult<T> data(String msg, T data, int count) {
|
|
||||||
if (StringUtils.isEmpty(msg)) msg = SUCCESS_MESSAGE;
|
|
||||||
return new JsonResult<>(SUCCESS_CODE, msg, true, data, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 响应数据实体
|
// 响应数据实体
|
||||||
public static <T> JsonResult<T> data(T data) {
|
public static <T> JsonResult<T> data(T data) {
|
||||||
return new JsonResult<>(SUCCESS_CODE, SUCCESS_MESSAGE, true, data, 0);
|
return new JsonResult<>(SUCCESS_CODE, SUCCESS_MESSAGE, true, data);
|
||||||
}
|
|
||||||
|
|
||||||
// 响应数据实体
|
|
||||||
public static <T> JsonResult<T> data(T data, int count) {
|
|
||||||
return new JsonResult<>(SUCCESS_CODE, SUCCESS_MESSAGE, true, data, count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应成功消息
|
// 响应成功消息
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ public class CommonUtil {
|
|||||||
try {
|
try {
|
||||||
properties.load(CommonUtil.class.getClassLoader().getResourceAsStream("app.properties"));
|
properties.load(CommonUtil.class.getClassLoader().getResourceAsStream("app.properties"));
|
||||||
if (!properties.isEmpty()) {
|
if (!properties.isEmpty()) {
|
||||||
appVersion = properties.getProperty("app.version");
|
appVersion = properties.getProperty("app.version") + "build" + properties.getProperty("build");
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|||||||
@@ -26,10 +26,20 @@ public class ResponseUtil {
|
|||||||
.end(jsonObject.encode());
|
.end(jsonObject.encode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void fireJsonObjectResponse(HttpServerResponse ctx, JsonObject jsonObject) {
|
||||||
|
ctx.putHeader(CONTENT_TYPE, "application/json; charset=utf-8")
|
||||||
|
.setStatusCode(200)
|
||||||
|
.end(jsonObject.encode());
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> void fireJsonResultResponse(RoutingContext ctx, JsonResult<T> jsonResult) {
|
public static <T> void fireJsonResultResponse(RoutingContext ctx, JsonResult<T> jsonResult) {
|
||||||
fireJsonObjectResponse(ctx, jsonResult.toJsonObject());
|
fireJsonObjectResponse(ctx, jsonResult.toJsonObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> void fireJsonResultResponse(HttpServerResponse ctx, JsonResult<T> jsonResult) {
|
||||||
|
fireJsonObjectResponse(ctx, jsonResult.toJsonObject());
|
||||||
|
}
|
||||||
|
|
||||||
public static void fireTextResponse(RoutingContext ctx, String text) {
|
public static void fireTextResponse(RoutingContext ctx, String text) {
|
||||||
ctx.response().putHeader(CONTENT_TYPE, "text/html; charset=utf-8").end(text);
|
ctx.response().putHeader(CONTENT_TYPE, "text/html; charset=utf-8").end(text);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
app.version=${project.version}
|
app.version=${project.version}
|
||||||
|
build=${maven.build.timestamp}
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<configuration>
|
|
||||||
|
|
||||||
<!-- 日志自定义颜色 -->
|
|
||||||
<!-- https://logback.qos.ch/manual/layouts.html#coloring -->
|
|
||||||
|
|
||||||
<!--日志文件主目录:这里${user.home}为当前服务器用户主目录-->
|
|
||||||
<property name="LOG_HOME" value="logs"/>
|
|
||||||
|
|
||||||
<property name="LOGBACK_DEFAULT" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%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"/>
|
|
||||||
<root level="debug">
|
|
||||||
<appender-ref ref="STDOUT"/>
|
|
||||||
<appender-ref ref="FILE"/>
|
|
||||||
</root>
|
|
||||||
</configuration>
|
|
||||||
10
note.txt
10
note.txt
@@ -60,7 +60,15 @@ jwt鉴权用户
|
|||||||
文件信息: 文件/文件夹, 文件数量, 文件大小, 文件类型; 链接信息: 解析次数, 缓存次数等)
|
文件信息: 文件/文件夹, 文件数量, 文件大小, 文件类型; 链接信息: 解析次数, 缓存次数等)
|
||||||
|
|
||||||
微服务设计:
|
微服务设计:
|
||||||
|
TODO
|
||||||
|
|
||||||
|
后台管理:
|
||||||
|
菜单:
|
||||||
|
网盘管理: token配置, 启用/禁用
|
||||||
|
短链管理: 短链列表, 新增, 删除
|
||||||
|
解析统计: 下载次数统计, 下载流量统计, 详细解析列表
|
||||||
|
状态监视: 服务请求并发数; 来源IP列表: 拉黑, 限制次数; Nginx
|
||||||
|
系统配置: 管理员账户, 系统参数: 域名配置, 预览URL,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,52 +7,72 @@ public class FileInfo {
|
|||||||
/**
|
/**
|
||||||
* 文件名
|
* 文件名
|
||||||
*/
|
*/
|
||||||
String fileName;
|
private String fileName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件ID
|
* 文件ID
|
||||||
*/
|
*/
|
||||||
String fileId;
|
private String fileId;
|
||||||
|
|
||||||
|
private String fileIcon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件大小(byte)
|
* 文件大小(byte)
|
||||||
*/
|
*/
|
||||||
Long size;
|
private Long size;
|
||||||
|
|
||||||
|
private String sizeStr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MIME类型
|
* 类型
|
||||||
*/
|
*/
|
||||||
String fileMIME;
|
private String fileType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件路径
|
* 文件路径
|
||||||
*/
|
*/
|
||||||
String filePath;
|
private String filePath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建(上传)时间 yyyy-MM-dd HH:mm:ss格式
|
* 创建(上传)时间 yyyy-MM-dd HH:mm:ss格式
|
||||||
*/
|
*/
|
||||||
String createTime;
|
private String createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上次修改时间
|
||||||
|
*/
|
||||||
|
private String updateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建者
|
* 创建者
|
||||||
*/
|
*/
|
||||||
String createBy;
|
private String createBy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文件描述
|
* 文件描述
|
||||||
*/
|
*/
|
||||||
String description;
|
private String description;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载次数
|
* 下载次数
|
||||||
*/
|
*/
|
||||||
Integer downloadCount;
|
private Integer downloadCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网盘标识
|
||||||
|
*/
|
||||||
|
private String panType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nfd下载链接(可能获取不到)
|
||||||
|
* note: 不是下载直链
|
||||||
|
*/
|
||||||
|
private String parserUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扩展参数
|
* 扩展参数
|
||||||
*/
|
*/
|
||||||
Map<String, Object> extParameters;
|
private Map<String, Object> extParameters;
|
||||||
|
|
||||||
public String getFileName() {
|
public String getFileName() {
|
||||||
return fileName;
|
return fileName;
|
||||||
@@ -72,6 +92,15 @@ public class FileInfo {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getFileIcon() {
|
||||||
|
return fileIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setFileIcon(String fileIcon) {
|
||||||
|
this.fileIcon = fileIcon;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Long getSize() {
|
public Long getSize() {
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
@@ -81,12 +110,21 @@ public class FileInfo {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFileMIME() {
|
public String getSizeStr() {
|
||||||
return fileMIME;
|
return sizeStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileInfo setFileMIME(String fileMIME) {
|
public FileInfo setSizeStr(String sizeStr) {
|
||||||
this.fileMIME = fileMIME;
|
this.sizeStr = sizeStr;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFileType() {
|
||||||
|
return fileType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setFileType(String fileType) {
|
||||||
|
this.fileType = fileType;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +146,15 @@ public class FileInfo {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getUpdateTime() {
|
||||||
|
return updateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setUpdateTime(String updateTime) {
|
||||||
|
this.updateTime = updateTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public String getCreateBy() {
|
public String getCreateBy() {
|
||||||
return createBy;
|
return createBy;
|
||||||
}
|
}
|
||||||
@@ -135,6 +182,24 @@ public class FileInfo {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPanType() {
|
||||||
|
return panType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setPanType(String panType) {
|
||||||
|
this.panType = panType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getParserUrl() {
|
||||||
|
return parserUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileInfo setParserUrl(String parserUrl) {
|
||||||
|
this.parserUrl = parserUrl;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, Object> getExtParameters() {
|
public Map<String, Object> getExtParameters() {
|
||||||
return extParameters;
|
return extParameters;
|
||||||
}
|
}
|
||||||
@@ -143,4 +208,21 @@ public class FileInfo {
|
|||||||
this.extParameters = extParameters;
|
this.extParameters = extParameters;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "FileInfo{" +
|
||||||
|
"fileName='" + fileName + '\'' +
|
||||||
|
", fileId='" + fileId + '\'' +
|
||||||
|
", size=" + size +
|
||||||
|
", fileType='" + fileType + '\'' +
|
||||||
|
", filePath='" + filePath + '\'' +
|
||||||
|
", createTime='" + createTime + '\'' +
|
||||||
|
", updateTime='" + updateTime + '\'' +
|
||||||
|
", createBy='" + createBy + '\'' +
|
||||||
|
", description='" + description + '\'' +
|
||||||
|
", downloadCount=" + downloadCount +
|
||||||
|
", extParameters=" + extParameters +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ public class ShareLinkInfo {
|
|||||||
private String shareUrl; // 原始分享链接
|
private String shareUrl; // 原始分享链接
|
||||||
private String standardUrl; // 规范化的标准链接
|
private String standardUrl; // 规范化的标准链接
|
||||||
|
|
||||||
private Map<String, Object> otherParam; // 其他参数
|
/**
|
||||||
|
* 其他参数预定义
|
||||||
|
* auths: 认证相关 传入
|
||||||
|
* UA: 浏览器请求头 传入
|
||||||
|
* fileInfo: 解析成功的文件信息对象 传出
|
||||||
|
*/
|
||||||
|
private Map<String, Object> otherParam;
|
||||||
|
|
||||||
private ShareLinkInfo(Builder builder) {
|
private ShareLinkInfo(Builder builder) {
|
||||||
this.shareKey = builder.shareKey;
|
this.shareKey = builder.shareKey;
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package cn.qaiu.parser;//package cn.qaiu.lz.common.parser;
|
package cn.qaiu.parser;//package cn.qaiu.lz.common.parser;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.FileInfo;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.Promise;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface IPanTool {
|
public interface IPanTool {
|
||||||
Future<String> parse();
|
Future<String> parse();
|
||||||
@@ -8,4 +12,24 @@ public interface IPanTool {
|
|||||||
default String parseSync() {
|
default String parseSync() {
|
||||||
return parse().toCompletionStage().toCompletableFuture().join();
|
return parse().toCompletionStage().toCompletableFuture().join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析文件列表
|
||||||
|
* @return List
|
||||||
|
*/
|
||||||
|
default Future<List<FileInfo>> parseFileList() {
|
||||||
|
Promise<List<FileInfo>> promise = Promise.promise();
|
||||||
|
promise.complete();
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据文件ID获取下载链接
|
||||||
|
* @return url
|
||||||
|
*/
|
||||||
|
default Future<String> parseById() {
|
||||||
|
Promise<String> promise = Promise.promise();
|
||||||
|
promise.complete();
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import cn.qaiu.entity.ShareLinkInfo;
|
|||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.Handler;
|
import io.vertx.core.Handler;
|
||||||
import io.vertx.core.Promise;
|
import io.vertx.core.Promise;
|
||||||
import io.vertx.core.json.DecodeException;
|
import io.vertx.core.buffer.Buffer;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.core.net.ProxyOptions;
|
import io.vertx.core.net.ProxyOptions;
|
||||||
import io.vertx.core.net.ProxyType;
|
import io.vertx.core.net.ProxyType;
|
||||||
@@ -17,8 +17,14 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析器抽象类包含promise, HTTP Client, 默认失败方法等;
|
* 解析器抽象类包含promise, HTTP Client, 默认失败方法等;
|
||||||
@@ -92,6 +98,14 @@ public abstract class PanBase implements IPanTool {
|
|||||||
protected PanBase() {
|
protected PanBase() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String baseMsg() {
|
||||||
|
if (shareLinkInfo.getShareUrl() != null) {
|
||||||
|
return shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + ": url=" + shareLinkInfo.getShareUrl();
|
||||||
|
}
|
||||||
|
return shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + ": key=" + shareLinkInfo.getShareKey() +
|
||||||
|
";pwd=" + shareLinkInfo.getSharePassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 失败时生成异常消息
|
* 失败时生成异常消息
|
||||||
@@ -104,11 +118,11 @@ public abstract class PanBase implements IPanTool {
|
|||||||
try {
|
try {
|
||||||
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
|
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
|
||||||
log.error("解析异常: " + s, t.fillInStackTrace());
|
log.error("解析异常: " + s, t.fillInStackTrace());
|
||||||
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + ": 解析异常: " + s + " -> " + t);
|
promise.fail(baseMsg() + ": 解析异常: " + s + " -> " + t);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("ErrorMsg format fail. The parameter has been discarded", e);
|
log.error("ErrorMsg format fail. The parameter has been discarded", e);
|
||||||
log.error("解析异常: " + errorMsg, t.fillInStackTrace());
|
log.error("解析异常: " + errorMsg, t.fillInStackTrace());
|
||||||
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + ": 解析异常: " + errorMsg + " -> " + t);
|
promise.fail(baseMsg() + ": 解析异常: " + errorMsg + " -> " + t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,10 +135,10 @@ public abstract class PanBase implements IPanTool {
|
|||||||
protected void fail(String errorMsg, Object... args) {
|
protected void fail(String errorMsg, Object... args) {
|
||||||
try {
|
try {
|
||||||
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
|
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
|
||||||
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 解析异常: " + s);
|
promise.fail(baseMsg() + " - 解析异常: " + s);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("ErrorMsg format fail. The parameter has been discarded", e);
|
log.error("ErrorMsg format fail. The parameter has been discarded", e);
|
||||||
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 解析异常: " + errorMsg);
|
promise.fail(baseMsg() + " - 解析异常: " + errorMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +153,7 @@ public abstract class PanBase implements IPanTool {
|
|||||||
* @return Handler
|
* @return Handler
|
||||||
*/
|
*/
|
||||||
protected Handler<Throwable> handleFail(String errorMsg) {
|
protected Handler<Throwable> handleFail(String errorMsg) {
|
||||||
return t -> fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 请求异常 {}: -> {}", errorMsg, t.fillInStackTrace());
|
return t -> fail(baseMsg() + " - 请求异常 {}: -> {}", errorMsg, t.fillInStackTrace());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Handler<Throwable> handleFail() {
|
protected Handler<Throwable> handleFail() {
|
||||||
@@ -149,15 +163,59 @@ public abstract class PanBase implements IPanTool {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* bodyAsJsonObject的封装, 会自动处理异常
|
* bodyAsJsonObject的封装, 会自动处理异常
|
||||||
|
*
|
||||||
* @param res HttpResponse
|
* @param res HttpResponse
|
||||||
* @return JsonObject
|
* @return JsonObject
|
||||||
*/
|
*/
|
||||||
protected JsonObject asJson(HttpResponse<?> res) {
|
protected JsonObject asJson(HttpResponse<?> res) {
|
||||||
|
// 检查响应头中的Content-Encoding是否为gzip
|
||||||
|
String contentEncoding = res.getHeader("Content-Encoding");
|
||||||
try {
|
try {
|
||||||
return res.bodyAsJsonObject();
|
if ("gzip".equalsIgnoreCase(contentEncoding)) {
|
||||||
} catch (DecodeException e) {
|
// 如果是gzip压缩的响应体,解压
|
||||||
fail("解析失败: json格式异常: {}", res.bodyAsString());
|
return new JsonObject(decompressGzip((Buffer) res.body()));
|
||||||
throw new RuntimeException("解析失败: json格式异常");
|
} else {
|
||||||
|
return res.bodyAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
if ("gzip".equalsIgnoreCase(contentEncoding)) {
|
||||||
|
// 如果是gzip压缩的响应体,解压
|
||||||
|
try {
|
||||||
|
log.error(decompressGzip((Buffer) res.body()));
|
||||||
|
fail(decompressGzip((Buffer) res.body()));
|
||||||
|
throw new RuntimeException("响应不是JSON格式");
|
||||||
|
} catch (IOException ex) {
|
||||||
|
log.error("响应gzip解压失败");
|
||||||
|
fail("响应gzip解压失败: {}", ex.getMessage());
|
||||||
|
throw new RuntimeException("响应gzip解压失败", ex);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.error("解析失败: json格式异常: {}", res.bodyAsString());
|
||||||
|
fail("解析失败: json格式异常: {}", res.bodyAsString());
|
||||||
|
throw new RuntimeException("解析失败: json格式异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* body To text的封装, 会自动处理异常, 会自动解压gzip
|
||||||
|
* @param res HttpResponse
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
protected String asText(HttpResponse<?> res) {
|
||||||
|
// 检查响应头中的Content-Encoding是否为gzip
|
||||||
|
String contentEncoding = res.getHeader("Content-Encoding");
|
||||||
|
try {
|
||||||
|
if ("gzip".equalsIgnoreCase(contentEncoding)) {
|
||||||
|
// 如果是gzip压缩的响应体,解压
|
||||||
|
return decompressGzip((Buffer) res.body());
|
||||||
|
} else {
|
||||||
|
return res.bodyAsString();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail("解析失败: res格式异常");
|
||||||
|
throw new RuntimeException("解析失败: res格式异常");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,4 +249,35 @@ public abstract class PanBase implements IPanTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解压gzip数据
|
||||||
|
* @param compressedData compressedData
|
||||||
|
* @return String
|
||||||
|
* @throws IOException IOException
|
||||||
|
*/
|
||||||
|
private String decompressGzip(Buffer compressedData) throws IOException {
|
||||||
|
try (ByteArrayInputStream bais = new ByteArrayInputStream(compressedData.getBytes());
|
||||||
|
GZIPInputStream gzis = new GZIPInputStream(bais);
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(gzis,
|
||||||
|
StandardCharsets.UTF_8))) {
|
||||||
|
|
||||||
|
// 用于存储解压后的字符串
|
||||||
|
StringBuilder decompressedData = new StringBuilder();
|
||||||
|
|
||||||
|
// 逐行读取解压后的数据
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
decompressedData.append(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 此时decompressedData.toString()包含了解压后的字符串
|
||||||
|
return decompressedData.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getDomainName(){
|
||||||
|
return shareLinkInfo.getOtherParam().getOrDefault("domainName", "").toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public enum PanDomainTemplate {
|
|||||||
|
|
||||||
// 网盘定义
|
// 网盘定义
|
||||||
LZ("蓝奏云",
|
LZ("蓝奏云",
|
||||||
compile("https://(?:[a-zA-Z\\d-]+\\.)?lanzou[a-z]\\.com/(.+/)?(?<KEY>.+)"),
|
compile("https://(?:[a-zA-Z\\d-]+\\.)?((lanzou[a-z])|(lanzn))\\.com/(.+/)?(?<KEY>.+)"),
|
||||||
"https://lanzoux.com/{shareKey}",
|
"https://lanzoux.com/{shareKey}",
|
||||||
LzTool.class),
|
LzTool.class),
|
||||||
|
|
||||||
@@ -59,6 +59,12 @@ public enum PanDomainTemplate {
|
|||||||
"https://iwx.mail.qq.com/ftn/download/{shareKey}",
|
"https://iwx.mail.qq.com/ftn/download/{shareKey}",
|
||||||
"https://mail.qq.com",
|
"https://mail.qq.com",
|
||||||
QQTool.class),
|
QQTool.class),
|
||||||
|
// https://wx.mail.qq.com/s?k=uAG9JR42Rqgt010mFp
|
||||||
|
QQW("QQ邮箱云盘",
|
||||||
|
compile("https://i?wx\\.mail\\.qq\\.com/s\\?k=(?<KEY>.+)"),
|
||||||
|
"https://wx.mail.qq.com/s?k={shareKey}",
|
||||||
|
"https://mail.qq.com",
|
||||||
|
QQwTool.class),
|
||||||
// https://f.ws59.cn/f/或者https://www.wenshushu.cn/f/
|
// https://f.ws59.cn/f/或者https://www.wenshushu.cn/f/
|
||||||
WS("文叔叔",
|
WS("文叔叔",
|
||||||
compile("https://(f\\.ws(\\d{2})\\.cn|www\\.wenshushu\\.cn)/f/(?<KEY>.+)"),
|
compile("https://(f\\.ws(\\d{2})\\.cn|www\\.wenshushu\\.cn)/f/(?<KEY>.+)"),
|
||||||
@@ -81,7 +87,8 @@ public enum PanDomainTemplate {
|
|||||||
"https://cowtransfer.com/s/{shareKey}",
|
"https://cowtransfer.com/s/{shareKey}",
|
||||||
CowTool.class),
|
CowTool.class),
|
||||||
CT("城通网盘",
|
CT("城通网盘",
|
||||||
compile("https://(?:[a-zA-Z\\d-]+\\.)?(ctfile|545c|u062|ghpym|474b)\\.com/file/(?<KEY>.+)"),
|
compile("https://(?:[a-zA-Z\\d-]+\\.)?(ctfile|545c|u062|ghpym|474b)\\.com/f(ile)?/" +
|
||||||
|
"(?<KEY>[0-9a-zA-Z_-]+)(\\?p=(?<PWD>\\w+))?"),
|
||||||
"https://474b.com/file/{shareKey}",
|
"https://474b.com/file/{shareKey}",
|
||||||
CtTool.class),
|
CtTool.class),
|
||||||
// https://xxx.118pan.com/bxxx
|
// https://xxx.118pan.com/bxxx
|
||||||
@@ -96,6 +103,7 @@ public enum PanDomainTemplate {
|
|||||||
"https://www.vyuyun.com/s/{shareKey}?password={pwd}",
|
"https://www.vyuyun.com/s/{shareKey}?password={pwd}",
|
||||||
PvyyTool.class),
|
PvyyTool.class),
|
||||||
// https://1drv.ms/w/s!Alg0feQmCv2rnRFd60DQOmMa-Oh_?e=buaRtp
|
// https://1drv.ms/w/s!Alg0feQmCv2rnRFd60DQOmMa-Oh_?e=buaRtp
|
||||||
|
// https://1drv.ms/u/c/abfd0a26e47d3458/EdYACWvPq85Et797YmvL5LgBruUKoNxqIFATXhIv1PI2_Q?e=z4ffNJ
|
||||||
POD("OneDrive",
|
POD("OneDrive",
|
||||||
compile("https://1drv\\.ms/(?<KEY>.+)"),
|
compile("https://1drv\\.ms/(?<KEY>.+)"),
|
||||||
"https://1drv\\.ms/{shareKey}",
|
"https://1drv\\.ms/{shareKey}",
|
||||||
@@ -117,9 +125,20 @@ public enum PanDomainTemplate {
|
|||||||
"https://www.dropbox.com/scl/fi/{shareKey}/?rlkey={pwd}&dl=0",
|
"https://www.dropbox.com/scl/fi/{shareKey}/?rlkey={pwd}&dl=0",
|
||||||
PdbTool.class),
|
PdbTool.class),
|
||||||
P115("115网盘",
|
P115("115网盘",
|
||||||
compile("https://(115|anxia).com/s/(?<KEY>\\w+)(\\?password=(?<PWD>\\w+))?"),
|
compile("https://(115|anxia).com/s/(?<KEY>\\w+)(\\?password=(?<PWD>\\w+))?([&#].*)?"),
|
||||||
"https://115.com/s/{shareKey}?password={pwd}",
|
"https://115.com/s/{shareKey}?password={pwd}",
|
||||||
P115Tool.class),
|
P115Tool.class),
|
||||||
|
// 链接:https://www.yunpan.com/surl_yD7wz4VgU9v(提取码:fc70)
|
||||||
|
// P360("360云盘(需要referer头)",
|
||||||
|
// compile("https://www\\.yunpan\\.com/(?<KEY>\\w+)"),
|
||||||
|
// "https://www.yunpan.com/{shareKey}",
|
||||||
|
// P360Tool.class),
|
||||||
|
|
||||||
|
// https://pan-yz.cldisk.com/external/m/file/953658049102462976
|
||||||
|
Pcx("超星云盘(需要referer头)",
|
||||||
|
compile("https://pan-yz\\.cldisk\\.com/external/m/file/(?<KEY>\\w+)"),
|
||||||
|
"https://pan-yz.cldisk.com/external/m/file/{shareKey}",
|
||||||
|
PcxTool.class),
|
||||||
// =====================音乐类解析 分享链接标志->MxxS (单歌曲/普通音质)==========================
|
// =====================音乐类解析 分享链接标志->MxxS (单歌曲/普通音质)==========================
|
||||||
// http://163cn.tv/xxx
|
// http://163cn.tv/xxx
|
||||||
MNES("网易云音乐分享",
|
MNES("网易云音乐分享",
|
||||||
@@ -128,7 +147,7 @@ public enum PanDomainTemplate {
|
|||||||
MnesTool.class),
|
MnesTool.class),
|
||||||
// https://music.163.com/#/song?id=xxx
|
// https://music.163.com/#/song?id=xxx
|
||||||
MNE("网易云音乐歌曲详情",
|
MNE("网易云音乐歌曲详情",
|
||||||
compile("https://music\\.163\\.com/(#/)?song\\?id=(?<KEY>.+)"),
|
compile("https://(y.)?music\\.163\\.com/(#|m/)?song\\?id=(?<KEY>.+)(&.*)?"),
|
||||||
"https://music.163.com/#/song?id={shareKey}",
|
"https://music.163.com/#/song?id={shareKey}",
|
||||||
MnesTool.MneTool.class),
|
MnesTool.MneTool.class),
|
||||||
// https://c6.y.qq.com/base/fcgi-bin/u?__=xxx
|
// https://c6.y.qq.com/base/fcgi-bin/u?__=xxx
|
||||||
@@ -167,7 +186,7 @@ public enum PanDomainTemplate {
|
|||||||
MMGS("咪咕音乐分享",
|
MMGS("咪咕音乐分享",
|
||||||
compile("https://music\\.migu\\.cn/v3/music/song/(?<KEY>.+)(\\?.*)?"),
|
compile("https://music\\.migu\\.cn/v3/music/song/(?<KEY>.+)(\\?.*)?"),
|
||||||
"https://music.migu.cn/v3/music/song/{shareKey}",
|
"https://music.migu.cn/v3/music/song/{shareKey}",
|
||||||
MkwTool.class),
|
MmgTool.class),
|
||||||
// =====================私有盘解析==========================
|
// =====================私有盘解析==========================
|
||||||
|
|
||||||
// Cloudreve自定义域名解析, 解析器CeTool兜底策略, 即任意域名如果匹配不到对应的规则, 则由CeTool统一处理,
|
// Cloudreve自定义域名解析, 解析器CeTool兜底策略, 即任意域名如果匹配不到对应的规则, 则由CeTool统一处理,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package cn.qaiu.parser;
|
|||||||
import cn.qaiu.entity.ShareLinkInfo;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
|
||||||
import static cn.qaiu.parser.PanDomainTemplate.KEY;
|
import static cn.qaiu.parser.PanDomainTemplate.KEY;
|
||||||
@@ -20,9 +22,12 @@ public class ParserCreate {
|
|||||||
private final PanDomainTemplate panDomainTemplate;
|
private final PanDomainTemplate panDomainTemplate;
|
||||||
private final ShareLinkInfo shareLinkInfo;
|
private final ShareLinkInfo shareLinkInfo;
|
||||||
|
|
||||||
|
private String standardUrl;
|
||||||
|
|
||||||
public ParserCreate(PanDomainTemplate panDomainTemplate, ShareLinkInfo shareLinkInfo) {
|
public ParserCreate(PanDomainTemplate panDomainTemplate, ShareLinkInfo shareLinkInfo) {
|
||||||
this.panDomainTemplate = panDomainTemplate;
|
this.panDomainTemplate = panDomainTemplate;
|
||||||
this.shareLinkInfo = shareLinkInfo;
|
this.shareLinkInfo = shareLinkInfo;
|
||||||
|
this.standardUrl = panDomainTemplate.getStandardUrlTemplate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -38,17 +43,19 @@ public class ParserCreate {
|
|||||||
}
|
}
|
||||||
Matcher matcher = this.panDomainTemplate.getPattern().matcher(shareUrl);
|
Matcher matcher = this.panDomainTemplate.getPattern().matcher(shareUrl);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
String shareKey = matcher.group(KEY);
|
String k0 = matcher.group(KEY);
|
||||||
|
String shareKey = URLEncoder.encode(k0, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
// 返回规范化的标准链接
|
// 返回规范化的标准链接
|
||||||
String standardUrl = getStandardUrlTemplate()
|
standardUrl = getStandardUrlTemplate()
|
||||||
.replace("{shareKey}", shareKey);
|
.replace("{shareKey}", k0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String pwd = matcher.group(PWD);
|
String pwd = matcher.group(PWD);
|
||||||
if (StringUtils.isNotEmpty(pwd)) {
|
if (StringUtils.isNotEmpty(pwd)) {
|
||||||
shareLinkInfo.setSharePassword(pwd);
|
shareLinkInfo.setSharePassword(pwd);
|
||||||
}
|
}
|
||||||
standardUrl = standardUrl .replace("{pwd}", pwd);
|
standardUrl = standardUrl.replace("{pwd}", pwd);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
shareLinkInfo.setShareUrl(shareUrl);
|
shareLinkInfo.setShareUrl(shareUrl);
|
||||||
@@ -88,7 +95,11 @@ public class ParserCreate {
|
|||||||
shareLinkInfo.setShareUrl(standardUrl);
|
shareLinkInfo.setShareUrl(standardUrl);
|
||||||
} else {
|
} else {
|
||||||
shareLinkInfo.setShareKey(shareKey);
|
shareLinkInfo.setShareKey(shareKey);
|
||||||
shareLinkInfo.setStandardUrl(panDomainTemplate.getStandardUrlTemplate().replace("{shareKey}", shareKey));
|
standardUrl = standardUrl.replace("{shareKey}", shareKey);
|
||||||
|
shareLinkInfo.setStandardUrl(standardUrl);
|
||||||
|
}
|
||||||
|
if (StringUtils.isEmpty(shareLinkInfo.getShareUrl())) {
|
||||||
|
shareLinkInfo.setShareUrl(standardUrl);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -111,6 +122,11 @@ public class ParserCreate {
|
|||||||
public ParserCreate setShareLinkInfoPwd(String pwd) {
|
public ParserCreate setShareLinkInfoPwd(String pwd) {
|
||||||
if (pwd != null) {
|
if (pwd != null) {
|
||||||
shareLinkInfo.setSharePassword(pwd);
|
shareLinkInfo.setSharePassword(pwd);
|
||||||
|
standardUrl = standardUrl.replace("{pwd}", pwd);
|
||||||
|
shareLinkInfo.setStandardUrl(standardUrl);
|
||||||
|
if (shareLinkInfo.getShareUrl().contains("{pwd}")) {
|
||||||
|
shareLinkInfo.setShareUrl(standardUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,25 @@ import io.vertx.core.json.JsonObject;
|
|||||||
import io.vertx.ext.web.client.HttpRequest;
|
import io.vertx.ext.web.client.HttpRequest;
|
||||||
import io.vertx.uritemplate.UriTemplate;
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <a href="https://www.ctfile.com">诚通网盘</a>
|
* <a href="https://www.ctfile.com">诚通网盘</a>
|
||||||
*/
|
*/
|
||||||
public class CtTool extends PanBase {
|
public class CtTool extends PanBase {
|
||||||
private static final String API_URL_PREFIX = "https://webapi.ctfile.com";
|
private static final String API_URL_PREFIX = "https://webapi.ctfile.com";
|
||||||
|
|
||||||
private static final String API1 = API_URL_PREFIX + "/getfile.php?path=file" +
|
// https://webapi.ctfile.com/getfile.php?path=f&f=55050874-1246660795-6464f6&
|
||||||
|
// passcode=7548&token=30wiijxs1fzhb6brw0p9m6&r=0.5885881231735761&
|
||||||
|
// ref=&url=https%3A%2F%2F474b.com%2Ff%2F55050874-1246660795-6464f6%3Fp%3D7548
|
||||||
|
private static final String API1 = API_URL_PREFIX + "/getfile.php?path={path}" +
|
||||||
"&f={shareKey}&passcode={pwd}&token={token}&r={rand}&ref=";
|
"&f={shareKey}&passcode={pwd}&token={token}&r={rand}&ref=";
|
||||||
|
|
||||||
|
//https://webapi.ctfile.com/get_file_url.php?uid=55050874&fid=1246660795&folder_id=0&
|
||||||
|
// file_chk=054bc20461f5c63ff82015b9e69fb7fc&mb=1&token=30wiijxs1fzhb6brw0p9m6&app=0&
|
||||||
|
// acheck=1&verifycode=&rd=0.965929071503574
|
||||||
private static final String API2 = API_URL_PREFIX + "/get_file_url.php?" +
|
private static final String API2 = API_URL_PREFIX + "/get_file_url.php?" +
|
||||||
"uid={uid}&fid={fid}&folder_id=0&file_chk={file_chk}&mb=0&token={token}&app=0&acheck=0&verifycode=" +
|
"uid={uid}&fid={fid}&folder_id=0&file_chk={file_chk}&mb=0&token={token}&app=0&acheck=0&verifycode=" +
|
||||||
"&rd={rand}";
|
"&rd={rand}";
|
||||||
@@ -49,8 +59,13 @@ public class CtTool extends PanBase {
|
|||||||
String[] split = shareKey.split("-");
|
String[] split = shareKey.split("-");
|
||||||
String uid = split[0], fid = split[1];
|
String uid = split[0], fid = split[1];
|
||||||
String token = RandomStringGenerator.generateRandomString();
|
String token = RandomStringGenerator.generateRandomString();
|
||||||
|
// 获取url path
|
||||||
|
int i1 = shareLinkInfo.getShareUrl().indexOf("com/");
|
||||||
|
int i2 = shareLinkInfo.getShareUrl().lastIndexOf("/");
|
||||||
|
String path = shareLinkInfo.getShareUrl().substring(i1 + 4, i2);
|
||||||
|
|
||||||
HttpRequest<Buffer> bufferHttpRequest1 = clientSession.getAbs(UriTemplate.of(API1))
|
HttpRequest<Buffer> bufferHttpRequest1 = clientSession.getAbs(UriTemplate.of(API1))
|
||||||
|
.setTemplateParam("path", path)
|
||||||
.setTemplateParam("shareKey", shareKey)
|
.setTemplateParam("shareKey", shareKey)
|
||||||
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
||||||
.setTemplateParam("token", token)
|
.setTemplateParam("token", token)
|
||||||
@@ -79,10 +94,10 @@ public class CtTool extends PanBase {
|
|||||||
}
|
}
|
||||||
}).onFailure(handleFail(bufferHttpRequest1.queryParams().toString()));
|
}).onFailure(handleFail(bufferHttpRequest1.queryParams().toString()));
|
||||||
} else {
|
} else {
|
||||||
fail("解析失败, 可能分享已失效: json: {} 字段 {} 不存在", resJson, "file_chk");
|
fail("解析失败, file_chk找不到, 可能分享已失效或者分享密码不对: {}", fileJson);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fail("解析失败, 可能分享已失效: json: {} 字段 {} 不存在", resJson, "file");
|
fail("解析失败, 文件信息为空, 可能分享已失效");
|
||||||
}
|
}
|
||||||
}).onFailure(handleFail(bufferHttpRequest1.queryParams().toString()));
|
}).onFailure(handleFail(bufferHttpRequest1.queryParams().toString()));
|
||||||
return promise.future();
|
return promise.future();
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package cn.qaiu.parser.impl;
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.FileInfo;
|
||||||
import cn.qaiu.entity.ShareLinkInfo;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import cn.qaiu.parser.PanBase;
|
import cn.qaiu.parser.PanBase;
|
||||||
import cn.qaiu.util.AESUtils;
|
import cn.qaiu.util.AESUtils;
|
||||||
|
import cn.qaiu.util.FileSizeConverter;
|
||||||
import cn.qaiu.util.UUIDUtil;
|
import cn.qaiu.util.UUIDUtil;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.MultiMap;
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.Promise;
|
||||||
import io.vertx.core.buffer.Buffer;
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.json.JsonArray;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.ext.web.client.HttpRequest;
|
import io.vertx.ext.web.client.HttpRequest;
|
||||||
import io.vertx.uritemplate.UriTemplate;
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
@@ -16,6 +20,9 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.List;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,8 +49,19 @@ public class FjTool extends PanBase {
|
|||||||
"={uuid}&extra=2×tamp={ts}";
|
"={uuid}&extra=2×tamp={ts}";
|
||||||
// https://api.feijipan.com/ws/buy/vip/list?devType=6&devModel=Chrome&uuid=WQAl5yBy1naGudJEILBvE&extra=2×tamp=E2C53155F6D09417A27981561134CB73
|
// https://api.feijipan.com/ws/buy/vip/list?devType=6&devModel=Chrome&uuid=WQAl5yBy1naGudJEILBvE&extra=2×tamp=E2C53155F6D09417A27981561134CB73
|
||||||
|
|
||||||
|
// https://api.feijipan.com/ws/share/list?devType=6&devModel=Chrome&uuid=pwRWqwbk1J-KMTlRZowrn&extra=2×tamp=C5F8A68C53121AB21FA35BA3529E8758&shareId=fmAuOh3m&folderId=28986333&offset=1&limit=60
|
||||||
|
|
||||||
|
private static final String FILE_LIST_URL = API_URL_PREFIX + "/share/list?devType=6&devModel=Chrome&uuid" +
|
||||||
|
"={uuid}&extra=2×tamp={ts}&shareId={shareId}&folderId" +
|
||||||
|
"={folderId}&offset=1&limit=60";
|
||||||
|
|
||||||
private static final MultiMap header;
|
private static final MultiMap header;
|
||||||
|
|
||||||
|
|
||||||
|
long nowTs = System.currentTimeMillis();
|
||||||
|
String tsEncode = AESUtils.encrypt2Hex(Long.toString(nowTs));
|
||||||
|
String uuid = UUIDUtil.fjUuid(); // 也可以使用 UUID.randomUUID().toString()
|
||||||
|
|
||||||
static {
|
static {
|
||||||
header = MultiMap.caseInsensitiveMultiMap();
|
header = MultiMap.caseInsensitiveMultiMap();
|
||||||
header.set("Accept", "application/json, text/plain, */*");
|
header.set("Accept", "application/json, text/plain, */*");
|
||||||
@@ -71,13 +89,10 @@ public class FjTool extends PanBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
final String dataKey = shareLinkInfo.getShareKey();
|
|
||||||
|
|
||||||
// 240530 此处shareId又改为了原始的shareId
|
// 240530 此处shareId又改为了原始的shareId
|
||||||
String shareId = dataKey; // String.valueOf(AESUtils.idEncrypt(dataKey));
|
// String.valueOf(AESUtils.idEncrypt(dataKey));
|
||||||
long nowTs = System.currentTimeMillis();
|
final String shareId = shareLinkInfo.getShareKey();
|
||||||
String tsEncode = AESUtils.encrypt2Hex(Long.toString(nowTs));
|
|
||||||
String uuid = UUIDUtil.fjUuid(); // 也可以使用 UUID.randomUUID().toString()
|
|
||||||
|
|
||||||
// 24.5.12 飞机盘 规则修改 需要固定UUID先请求会员接口, 再请求后续接口
|
// 24.5.12 飞机盘 规则修改 需要固定UUID先请求会员接口, 再请求后续接口
|
||||||
client.postAbs(UriTemplate.of(VIP_REQUEST_URL))
|
client.postAbs(UriTemplate.of(VIP_REQUEST_URL))
|
||||||
@@ -85,8 +100,6 @@ public class FjTool extends PanBase {
|
|||||||
.setTemplateParam("ts", tsEncode)
|
.setTemplateParam("ts", tsEncode)
|
||||||
.send().onSuccess(r0 -> { // 忽略res
|
.send().onSuccess(r0 -> { // 忽略res
|
||||||
|
|
||||||
// long nowTs0 = System.currentTimeMillis();
|
|
||||||
String tsEncode0 = AESUtils.encrypt2Hex(Long.toString(nowTs));
|
|
||||||
// 第一次请求 获取文件信息
|
// 第一次请求 获取文件信息
|
||||||
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
|
// 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))
|
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL))
|
||||||
@@ -132,6 +145,13 @@ public class FjTool extends PanBase {
|
|||||||
}
|
}
|
||||||
// 文件Id
|
// 文件Id
|
||||||
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
|
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
|
||||||
|
// 如果是目录返回目录ID
|
||||||
|
JsonObject fileList = fileInfo.getJsonArray("fileList").getJsonObject(0);
|
||||||
|
if (fileList.getInteger("fileType") == 2) {
|
||||||
|
promise.complete(fileList.getInteger("folderId").toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String fileId = fileInfo.getString("fileIds");
|
String fileId = fileInfo.getString("fileIds");
|
||||||
String userId = fileInfo.getString("userId");
|
String userId = fileInfo.getString("userId");
|
||||||
// 其他参数
|
// 其他参数
|
||||||
@@ -148,7 +168,7 @@ public class FjTool extends PanBase {
|
|||||||
.setTemplateParam("uuid", uuid)
|
.setTemplateParam("uuid", uuid)
|
||||||
.setTemplateParam("ts", tsEncode2)
|
.setTemplateParam("ts", tsEncode2)
|
||||||
.setTemplateParam("auth", auth)
|
.setTemplateParam("auth", auth)
|
||||||
.setTemplateParam("dataKey", dataKey);
|
.setTemplateParam("dataKey", shareId);
|
||||||
System.out.println(httpRequest.toString());
|
System.out.println(httpRequest.toString());
|
||||||
httpRequest.send().onSuccess(res2 -> {
|
httpRequest.send().onSuccess(res2 -> {
|
||||||
MultiMap headers = res2.headers();
|
MultiMap headers = res2.headers();
|
||||||
@@ -164,4 +184,124 @@ public class FjTool extends PanBase {
|
|||||||
|
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<List<FileInfo>> parseFileList() {
|
||||||
|
Promise<List<FileInfo>> promise = Promise.promise();
|
||||||
|
|
||||||
|
String shareId = shareLinkInfo.getShareKey(); // String.valueOf(AESUtils.idEncrypt(dataKey));
|
||||||
|
parse().onSuccess(id -> {
|
||||||
|
// 拿到目录ID
|
||||||
|
client.postAbs(UriTemplate.of(FILE_LIST_URL))
|
||||||
|
.putHeaders(header)
|
||||||
|
.setTemplateParam("shareId", shareId)
|
||||||
|
.setTemplateParam("uuid", uuid)
|
||||||
|
.setTemplateParam("ts", tsEncode)
|
||||||
|
.setTemplateParam("folderId", id)
|
||||||
|
.send().onSuccess(res -> {
|
||||||
|
JsonObject jsonObject = asJson(res);
|
||||||
|
System.out.println(jsonObject.encodePrettily());
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"iconId" : 13,
|
||||||
|
"fileName" : "酷我音乐车机版 6.4.2.20.apk",
|
||||||
|
"fileSaves" : 52,
|
||||||
|
"fileStars" : 5.0,
|
||||||
|
"type" : 1,
|
||||||
|
"userId" : 1392902,
|
||||||
|
"fileComments" : 0,
|
||||||
|
"fileSize" : 68854,
|
||||||
|
"fileIcon" : "https://d.feijix.com/storage/files/icon/2024/06/08/7/8146637/6534494874910391.gz?t=67a5ea7c&rlimit=20&us=nMfuftjBN5&sign=f72be03007a301217f90dcc20333bd9a",
|
||||||
|
"updTime" : "2024-06-10 17:26:53",
|
||||||
|
"sortId" : 1487918143,
|
||||||
|
"name" : "酷我音乐车机版 6.4.2.20.apk",
|
||||||
|
"fileDownloads" : 109,
|
||||||
|
"fileUrl" : null,
|
||||||
|
"fileLikes" : 0,
|
||||||
|
"fileType" : 1,
|
||||||
|
"fileId" : 1487918143
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
JsonArray list = jsonObject.getJsonArray("list");
|
||||||
|
ArrayList<FileInfo> result = new ArrayList<>();
|
||||||
|
list.forEach(item->{
|
||||||
|
JsonObject fileJson = (JsonObject) item;
|
||||||
|
FileInfo fileInfo = new FileInfo();
|
||||||
|
|
||||||
|
// 映射已知字段fileInfo
|
||||||
|
String fileId = fileJson.getString("fileId");
|
||||||
|
String userId = fileJson.getString("userId");
|
||||||
|
|
||||||
|
// 其他参数
|
||||||
|
long nowTs2 = System.currentTimeMillis();
|
||||||
|
String tsEncode2 = AESUtils.encrypt2Hex(Long.toString(nowTs2));
|
||||||
|
String fidEncode = AESUtils.encrypt2Hex(fileId + "|" + userId);
|
||||||
|
String auth = AESUtils.encrypt2Hex(fileId + "|" + nowTs2);
|
||||||
|
|
||||||
|
// 回传用到的参数
|
||||||
|
//"fidEncode", paramJson.getString("fidEncode"))
|
||||||
|
//"uuid", paramJson.getString("uuid"))
|
||||||
|
//"ts", paramJson.getString("ts"))
|
||||||
|
//"auth", paramJson.getString("auth"))
|
||||||
|
//"shareId", paramJson.getString("shareId"))
|
||||||
|
JsonObject entries = JsonObject.of(
|
||||||
|
"fidEncode", fidEncode,
|
||||||
|
"uuid", uuid,
|
||||||
|
"ts", tsEncode2,
|
||||||
|
"auth", auth,
|
||||||
|
"shareId", shareId);
|
||||||
|
byte[] encode = Base64.getEncoder().encode(entries.encode().getBytes());
|
||||||
|
String param = new String(encode);
|
||||||
|
|
||||||
|
long fileSize = fileJson.getLong("fileSize") * 1024;
|
||||||
|
fileInfo.setFileName(fileJson.getString("fileName"))
|
||||||
|
.setFileId(fileJson.getString("fileId"))
|
||||||
|
.setCreateTime(fileJson.getString("createTime"))
|
||||||
|
.setFileType(fileJson.getString("fileType"))
|
||||||
|
.setSize(fileSize)
|
||||||
|
.setSizeStr(FileSizeConverter.convertToReadableSize(fileSize))
|
||||||
|
.setCreateBy(fileJson.getLong("userId").toString())
|
||||||
|
.setDownloadCount(fileJson.getInteger("fileDownloads"))
|
||||||
|
.setCreateTime(fileJson.getString("updTime"))
|
||||||
|
.setFileIcon(fileJson.getString("fileIcon"))
|
||||||
|
.setPanType(shareLinkInfo.getType())
|
||||||
|
.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s", getDomainName(),
|
||||||
|
shareLinkInfo.getType(), param));
|
||||||
|
result.add(fileInfo);
|
||||||
|
});
|
||||||
|
promise.complete(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<String> parseById() {
|
||||||
|
// 第二次请求
|
||||||
|
JsonObject paramJson = (JsonObject)shareLinkInfo.getOtherParam().get("paramJson");
|
||||||
|
|
||||||
|
// clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
||||||
|
// .putHeaders(header)
|
||||||
|
// .setTemplateParam("fidEncode", fidEncode)
|
||||||
|
// .setTemplateParam("uuid", uuid)
|
||||||
|
// .setTemplateParam("ts", tsEncode2)
|
||||||
|
// .setTemplateParam("auth", auth)
|
||||||
|
// .setTemplateParam("dataKey", shareId)
|
||||||
|
|
||||||
|
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
||||||
|
.setTemplateParam("fidEncode", paramJson.getString("fidEncode"))
|
||||||
|
.setTemplateParam("uuid", paramJson.getString("uuid"))
|
||||||
|
.setTemplateParam("ts", paramJson.getString("ts"))
|
||||||
|
.setTemplateParam("auth", paramJson.getString("auth"))
|
||||||
|
.setTemplateParam("dataKey", paramJson.getString("shareId"))
|
||||||
|
.putHeaders(header).send().onSuccess(res2 -> {
|
||||||
|
MultiMap headers = res2.headers();
|
||||||
|
if (!headers.contains("Location")) {
|
||||||
|
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res2.headers());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promise.complete(headers.get("Location"));
|
||||||
|
}).onFailure(handleFail(SECOND_REQUEST_URL));
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
package cn.qaiu.parser.impl;
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.FileInfo;
|
||||||
import cn.qaiu.entity.ShareLinkInfo;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import cn.qaiu.parser.PanBase;
|
import cn.qaiu.parser.PanBase;
|
||||||
import cn.qaiu.util.AESUtils;
|
import cn.qaiu.util.AESUtils;
|
||||||
|
import cn.qaiu.util.FileSizeConverter;
|
||||||
|
import cn.qaiu.util.UUIDUtil;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.MultiMap;
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.Promise;
|
||||||
|
import io.vertx.core.json.JsonArray;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.uritemplate.UriTemplate;
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 蓝奏云优享
|
* 蓝奏云优享
|
||||||
@@ -16,65 +21,207 @@ import java.util.UUID;
|
|||||||
*/
|
*/
|
||||||
public class IzTool extends PanBase {
|
public class IzTool extends PanBase {
|
||||||
|
|
||||||
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 API_URL_PREFIX = "https://api.ilanzou.com/unproved/";
|
||||||
|
|
||||||
private static final String FIRST_REQUEST_URL = API_URL_PREFIX +
|
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "recommend/list?devType=6&devModel=Chrome" +
|
||||||
"recommend/list?devModel=Chrome&extra=2&shareId={shareId}&type=0&offset=1&limit=60";
|
"&uuid={uuid}&extra=2×tamp={ts}&shareId={shareId}&type=0&offset=1&limit=60";
|
||||||
|
|
||||||
private static final String SECOND_REQUEST_URL = API_URL_PREFIX +
|
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "file/redirect?downloadId={fidEncode}&enable=1" +
|
||||||
"file/redirect?downloadId={fidEncode}&enable=1&devType=6&uuid={uuid}×tamp={ts}&auth={auth}&shareId={shareId}";
|
"&devType=6&uuid={uuid}×tamp={ts}&auth={auth}&shareId={dataKey}";
|
||||||
// downloadId=x&enable=1&devType=6&uuid=x×tamp=x&auth=x&shareId=lGFndCM
|
// downloadId=x&enable=1&devType=6&uuid=x×tamp=x&auth=x&shareId=lGFndCM
|
||||||
|
|
||||||
|
private static final String VIP_REQUEST_URL = API_URL_PREFIX + "/buy/vip/list?devType=6&devModel=Chrome&uuid" +
|
||||||
|
"={uuid}&extra=2×tamp={ts}";
|
||||||
|
|
||||||
|
private static final String FILE_LIST_URL = API_URL_PREFIX + "/share/list?devType=6&devModel=Chrome&uuid" +
|
||||||
|
"={uuid}&extra=2×tamp={ts}&shareId={shareId}&folderId" +
|
||||||
|
"={folderId}&offset=1&limit=60";
|
||||||
|
|
||||||
|
long nowTs = System.currentTimeMillis();
|
||||||
|
String tsEncode = AESUtils.encrypt2HexIz(Long.toString(nowTs));
|
||||||
|
String uuid = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
private static final MultiMap header;
|
||||||
|
|
||||||
|
static {
|
||||||
|
header = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
header.set("Accept", "application/json, text/plain, */*");
|
||||||
|
header.set("Accept-Encoding", "gzip, deflate, br, zstd");
|
||||||
|
header.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
|
||||||
|
header.set("Cache-Control", "no-cache");
|
||||||
|
header.set("Connection", "keep-alive");
|
||||||
|
header.set("Content-Length", "0");
|
||||||
|
header.set("DNT", "1");
|
||||||
|
header.set("Host", "api.ilanzou.com");
|
||||||
|
header.set("Origin", "https://www.ilanzou.com/");
|
||||||
|
header.set("Pragma", "no-cache");
|
||||||
|
header.set("Referer", "https://www.ilanzou.com/");
|
||||||
|
header.set("Sec-Fetch-Dest", "empty");
|
||||||
|
header.set("Sec-Fetch-Mode", "cors");
|
||||||
|
header.set("Sec-Fetch-Site", "cross-site");
|
||||||
|
header.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36");
|
||||||
|
header.set("sec-ch-ua", "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"");
|
||||||
|
header.set("sec-ch-ua-mobile", "?0");
|
||||||
|
header.set("sec-ch-ua-platform", "\"Windows\"");
|
||||||
|
}
|
||||||
|
|
||||||
public IzTool(ShareLinkInfo shareLinkInfo) {
|
public IzTool(ShareLinkInfo shareLinkInfo) {
|
||||||
super(shareLinkInfo);
|
super(shareLinkInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
String dataKey = shareLinkInfo.getShareKey();
|
String shareId = shareLinkInfo.getShareKey();
|
||||||
|
|
||||||
// 24.5.12 ilanzou改规则无需计算shareId
|
// 24.5.12 ilanzou改规则无需计算shareId
|
||||||
// String shareId = String.valueOf(AESUtils.idEncryptIz(dataKey));
|
// 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
|
// POST https://api.ilanzou.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", dataKey).send().onSuccess(res -> {
|
|
||||||
JsonObject resJson = asJson(res);
|
client.postAbs(UriTemplate.of(VIP_REQUEST_URL))
|
||||||
if (resJson.getInteger("code") != 200) {
|
.setTemplateParam("uuid", uuid)
|
||||||
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
|
.setTemplateParam("ts", tsEncode)
|
||||||
return;
|
.send().onSuccess(r0 -> { // 忽略res
|
||||||
}
|
// 第一次请求 获取文件信息
|
||||||
if (resJson.getJsonArray("list").size() == 0) {
|
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
|
||||||
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
client.postAbs(UriTemplate.of(
|
||||||
return;
|
shareLinkInfo.getSharePassword() == null ?
|
||||||
}
|
FIRST_REQUEST_URL : (FIRST_REQUEST_URL + "&code=" + shareLinkInfo.getSharePassword()))
|
||||||
// 文件Id
|
)
|
||||||
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
|
.putHeaders(header)
|
||||||
String fileId = fileInfo.getString("fileIds");
|
.setTemplateParam("shareId", shareId)
|
||||||
String userId = fileInfo.getString("userId");
|
.setTemplateParam("uuid", uuid)
|
||||||
// 其他参数
|
.setTemplateParam("ts", tsEncode)
|
||||||
long nowTs = System.currentTimeMillis();
|
.send().onSuccess(res -> {
|
||||||
String tsEncode = AESUtils.encrypt2HexIz(Long.toString(nowTs));
|
JsonObject resJson = asJson(res);
|
||||||
String uuid = UUID.randomUUID().toString();
|
if (resJson.getInteger("code") != 200) {
|
||||||
// String fidEncode = AESUtils.encrypt2HexIz(fileId + "|");
|
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
|
||||||
String fidEncode = AESUtils.encrypt2HexIz(fileId + "|" + userId);
|
return;
|
||||||
String auth = AESUtils.encrypt2HexIz(fileId + "|" + nowTs);
|
}
|
||||||
// 第二次请求
|
if (resJson.getJsonArray("list").size() == 0) {
|
||||||
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
|
||||||
.setTemplateParam("fidEncode", fidEncode)
|
return;
|
||||||
|
}
|
||||||
|
// 文件Id
|
||||||
|
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
|
||||||
|
|
||||||
|
// 如果是目录返回目录ID
|
||||||
|
JsonObject fileList = fileInfo.getJsonArray("fileList").getJsonObject(0);
|
||||||
|
if (fileList.getInteger("fileType") == 2) {
|
||||||
|
promise.complete(fileList.getInteger("folderId").toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileId = fileInfo.getString("fileIds");
|
||||||
|
String userId = fileInfo.getString("userId");
|
||||||
|
// 其他参数
|
||||||
|
// String fidEncode = AESUtils.encrypt2HexIz(fileId + "|");
|
||||||
|
String fidEncode = AESUtils.encrypt2HexIz(fileId + "|" + userId);
|
||||||
|
String auth = AESUtils.encrypt2HexIz(fileId + "|" + nowTs);
|
||||||
|
// 第二次请求
|
||||||
|
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
||||||
|
.setTemplateParam("fidEncode", fidEncode)
|
||||||
|
.setTemplateParam("uuid", uuid)
|
||||||
|
.setTemplateParam("ts", tsEncode)
|
||||||
|
.setTemplateParam("auth", auth)
|
||||||
|
.setTemplateParam("shareId", shareId)
|
||||||
|
.putHeaders(header).send().onSuccess(res2 -> {
|
||||||
|
MultiMap headers = res2.headers();
|
||||||
|
if (!headers.contains("Location")) {
|
||||||
|
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + headers);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promise.complete(headers.get("Location"));
|
||||||
|
}).onFailure(handleFail(SECOND_REQUEST_URL));
|
||||||
|
}).onFailure(handleFail(FIRST_REQUEST_URL));
|
||||||
|
});
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<List<FileInfo>> parseFileList() {
|
||||||
|
Promise<List<FileInfo>> promise = Promise.promise();
|
||||||
|
|
||||||
|
String shareId = shareLinkInfo.getShareKey(); // String.valueOf(AESUtils.idEncrypt(dataKey));
|
||||||
|
parse().onSuccess(id -> {
|
||||||
|
// 拿到目录ID
|
||||||
|
client.postAbs(UriTemplate.of(FILE_LIST_URL))
|
||||||
|
.putHeaders(header)
|
||||||
|
.setTemplateParam("shareId", shareId)
|
||||||
.setTemplateParam("uuid", uuid)
|
.setTemplateParam("uuid", uuid)
|
||||||
.setTemplateParam("ts", tsEncode)
|
.setTemplateParam("ts", tsEncode)
|
||||||
.setTemplateParam("auth", auth)
|
.setTemplateParam("folderId", id)
|
||||||
.setTemplateParam("shareId", dataKey).send().onSuccess(res2 -> {
|
.send().onSuccess(res -> {
|
||||||
MultiMap headers = res2.headers();
|
JsonObject jsonObject = asJson(res);
|
||||||
if (!headers.contains("Location")) {
|
System.out.println(jsonObject.encodePrettily());
|
||||||
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res.headers());
|
JsonArray list = jsonObject.getJsonArray("list");
|
||||||
return;
|
ArrayList<FileInfo> result = new ArrayList<>();
|
||||||
}
|
list.forEach(item->{
|
||||||
promise.complete(headers.get("Location"));
|
JsonObject fileJson = (JsonObject) item;
|
||||||
}).onFailure(handleFail(SECOND_REQUEST_URL));
|
FileInfo fileInfo = new FileInfo();
|
||||||
}).onFailure(handleFail(FIRST_REQUEST_URL));
|
|
||||||
|
|
||||||
|
// 映射已知字段
|
||||||
|
String fileId = fileJson.getString("fileId");
|
||||||
|
String userId = fileJson.getString("userId");
|
||||||
|
|
||||||
|
// 回传用到的参数
|
||||||
|
//"fidEncode", paramJson.getString("fidEncode"))
|
||||||
|
//"uuid", paramJson.getString("uuid"))
|
||||||
|
//"ts", paramJson.getString("ts"))
|
||||||
|
//"auth", paramJson.getString("auth"))
|
||||||
|
//"shareId", paramJson.getString("shareId"))
|
||||||
|
String fidEncode = AESUtils.encrypt2HexIz(fileId + "|" + userId);
|
||||||
|
String auth = AESUtils.encrypt2HexIz(fileId + "|" + nowTs);
|
||||||
|
JsonObject entries = JsonObject.of(
|
||||||
|
"fidEncode", fidEncode,
|
||||||
|
"uuid", uuid,
|
||||||
|
"ts", tsEncode,
|
||||||
|
"auth", auth,
|
||||||
|
"shareId", shareId);
|
||||||
|
byte[] encode = Base64.getEncoder().encode(entries.encode().getBytes());
|
||||||
|
String param = new String(encode);
|
||||||
|
|
||||||
|
|
||||||
|
long fileSize = fileJson.getLong("fileSize") * 1024;
|
||||||
|
fileInfo.setFileName(fileJson.getString("fileName"))
|
||||||
|
.setFileId(fileId)
|
||||||
|
.setCreateTime(fileJson.getString("createTime"))
|
||||||
|
.setFileType(fileJson.getString("fileType"))
|
||||||
|
.setSize(fileSize)
|
||||||
|
.setSizeStr(FileSizeConverter.convertToReadableSize(fileSize))
|
||||||
|
.setCreateBy(fileJson.getLong("userId").toString())
|
||||||
|
.setDownloadCount(fileJson.getInteger("fileDownloads"))
|
||||||
|
.setCreateTime(fileJson.getString("updTime"))
|
||||||
|
.setFileIcon(fileJson.getString("fileIcon"))
|
||||||
|
.setPanType(shareLinkInfo.getType())
|
||||||
|
.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s", getDomainName(),
|
||||||
|
shareLinkInfo.getType(), param));
|
||||||
|
result.add(fileInfo);
|
||||||
|
});
|
||||||
|
promise.complete(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<String> parseById() {
|
||||||
|
// 第二次请求
|
||||||
|
JsonObject paramJson = (JsonObject)shareLinkInfo.getOtherParam().get("paramJson");
|
||||||
|
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
||||||
|
.setTemplateParam("fidEncode", paramJson.getString("fidEncode"))
|
||||||
|
.setTemplateParam("uuid", paramJson.getString("uuid"))
|
||||||
|
.setTemplateParam("ts", paramJson.getString("ts"))
|
||||||
|
.setTemplateParam("auth", paramJson.getString("auth"))
|
||||||
|
.setTemplateParam("shareId", paramJson.getString("shareId"))
|
||||||
|
.putHeaders(header).send().onSuccess(res2 -> {
|
||||||
|
MultiMap headers = res2.headers();
|
||||||
|
if (!headers.contains("Location")) {
|
||||||
|
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res2.headers());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promise.complete(headers.get("Location"));
|
||||||
|
}).onFailure(handleFail(SECOND_REQUEST_URL));
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
package cn.qaiu.parser.impl;
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.FileInfo;
|
||||||
import cn.qaiu.entity.ShareLinkInfo;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import cn.qaiu.parser.PanBase;
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.util.CastUtil;
|
||||||
|
import cn.qaiu.util.FileSizeConverter;
|
||||||
|
import cn.qaiu.util.HeaderUtils;
|
||||||
import cn.qaiu.util.JsExecUtils;
|
import cn.qaiu.util.JsExecUtils;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.MultiMap;
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.Promise;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.ext.web.client.WebClient;
|
import io.vertx.ext.web.client.WebClient;
|
||||||
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||||
|
|
||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@@ -28,7 +35,6 @@ public class LzTool extends PanBase {
|
|||||||
super(shareLinkInfo);
|
super(shareLinkInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
String sUrl = shareLinkInfo.getStandardUrl();
|
String sUrl = shareLinkInfo.getStandardUrl();
|
||||||
String pwd = shareLinkInfo.getSharePassword();
|
String pwd = shareLinkInfo.getSharePassword();
|
||||||
@@ -41,23 +47,11 @@ public class LzTool extends PanBase {
|
|||||||
Matcher matcher = compile.matcher(html);
|
Matcher matcher = compile.matcher(html);
|
||||||
// 没有Iframe说明是加密分享, 匹配sign通过密码请求下载页面
|
// 没有Iframe说明是加密分享, 匹配sign通过密码请求下载页面
|
||||||
if (!matcher.find()) {
|
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 + "\"");
|
|
||||||
int i = jsText.indexOf("document.getElementById('rpt')");
|
|
||||||
if (i > 0) {
|
|
||||||
jsText = jsText.substring(0, i);
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
|
String jsText = getJsByPwd(pwd, html, "document.getElementById('rpt')");
|
||||||
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "down_p");
|
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "down_p");
|
||||||
getDownURL(sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
|
getDownURL(sUrl, client, scriptObjectMirror);
|
||||||
} catch (ScriptException | NoSuchMethodException e) {
|
} catch (Exception e) {
|
||||||
fail(e, "js引擎执行失败");
|
fail(e, "js引擎执行失败");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -75,7 +69,7 @@ public class LzTool extends PanBase {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, null);
|
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, null);
|
||||||
getDownURL(sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
|
getDownURL(sUrl, client, scriptObjectMirror);
|
||||||
} catch (ScriptException | NoSuchMethodException e) {
|
} catch (ScriptException | NoSuchMethodException e) {
|
||||||
fail(e, "js引擎执行失败");
|
fail(e, "js引擎执行失败");
|
||||||
}
|
}
|
||||||
@@ -85,6 +79,20 @@ public class LzTool extends PanBase {
|
|||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getJsByPwd(String pwd, String html, String subText) {
|
||||||
|
String jsText = getJsText(html);
|
||||||
|
|
||||||
|
if (jsText == null) {
|
||||||
|
throw new RuntimeException("js脚本匹配失败, 可能分享已失效");
|
||||||
|
}
|
||||||
|
jsText = jsText.replace("document.getElementById('pwd').value", "\"" + pwd + "\"");
|
||||||
|
int i = jsText.indexOf(subText);
|
||||||
|
if (i > 0) {
|
||||||
|
jsText = jsText.substring(0, i);
|
||||||
|
}
|
||||||
|
return jsText;
|
||||||
|
}
|
||||||
|
|
||||||
private String getJsText(String html) {
|
private String getJsText(String html) {
|
||||||
String jsTagStart = "<script type=\"text/javascript\">";
|
String jsTagStart = "<script type=\"text/javascript\">";
|
||||||
String jsTagEnd = "</script>";
|
String jsTagEnd = "</script>";
|
||||||
@@ -94,14 +102,60 @@ public class LzTool extends PanBase {
|
|||||||
}
|
}
|
||||||
int startPos = index + jsTagStart.length();
|
int startPos = index + jsTagStart.length();
|
||||||
int endPos = html.indexOf(jsTagEnd, startPos);
|
int endPos = html.indexOf(jsTagEnd, startPos);
|
||||||
return html.substring(startPos, endPos);
|
return html.substring(startPos, endPos).replaceAll("<!--.*-->", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getDownURL(String key, WebClient client, Map<String, ?> signMap) {
|
private void getDownURL(String key, WebClient client, Map<String, ?> obj) {
|
||||||
|
if (obj == null) {
|
||||||
|
fail("需要访问密码");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<?, ?> signMap = (Map<?, ?>)obj.get("data");
|
||||||
|
String url0 = obj.get("url").toString();
|
||||||
MultiMap map = MultiMap.caseInsensitiveMultiMap();
|
MultiMap map = MultiMap.caseInsensitiveMultiMap();
|
||||||
signMap.forEach((k, v) -> {
|
signMap.forEach((k, v) -> {
|
||||||
map.set(k, v.toString());
|
map.add((String) k, v.toString());
|
||||||
});
|
});
|
||||||
|
MultiMap headers = HeaderUtils.parseHeaders("""
|
||||||
|
Accept: application/json, text/javascript, */*
|
||||||
|
Accept-Encoding: gzip, deflate, br, zstd
|
||||||
|
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
|
||||||
|
Cache-Control: no-cache
|
||||||
|
Connection: keep-alive
|
||||||
|
Content-Type: application/x-www-form-urlencoded
|
||||||
|
Pragma: no-cache
|
||||||
|
Sec-Fetch-Dest: empty
|
||||||
|
Sec-Fetch-Mode: cors
|
||||||
|
Sec-Fetch-Site: same-origin
|
||||||
|
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0
|
||||||
|
X-Requested-With: XMLHttpRequest
|
||||||
|
sec-ch-ua: "Chromium";v="134", "Not:A-Brand";v="24", "Microsoft Edge";v="134"
|
||||||
|
sec-ch-ua-mobile: ?0
|
||||||
|
sec-ch-ua-platform: "Windows"
|
||||||
|
""");
|
||||||
|
|
||||||
|
headers.set("referer", key);
|
||||||
|
// action=downprocess&signs=%3Fctdf&websignkey=I5gl&sign=BWMGOF1sBTRWXwI9BjZdYVA7BDhfNAIyUG9UawJtUGMIPlAhACkCa1UyUTAAYFxvUj5XY1E7UGFXaFVq&websign=&kd=1&ves=1
|
||||||
|
String url = SHARE_URL_PREFIX + url0;
|
||||||
|
client.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
|
||||||
|
try {
|
||||||
|
JsonObject urlJson = asJson(res2);
|
||||||
|
if (urlJson.getInteger("zt") != 1) {
|
||||||
|
fail(urlJson.getString("inf"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String downUrl = urlJson.getString("dom") + "/file/" + urlJson.getString("url");
|
||||||
|
headers.remove("Referer");
|
||||||
|
client.getAbs(downUrl).putHeaders(headers).send()
|
||||||
|
.onSuccess(res3 -> promise.complete(res3.headers().get("Location")))
|
||||||
|
.onFailure(handleFail(downUrl));
|
||||||
|
} catch (Exception e) {
|
||||||
|
fail("解析异常");
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MultiMap getHeaders(String key) {
|
||||||
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
|
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
|
||||||
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
|
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
|
||||||
"like " +
|
"like " +
|
||||||
@@ -111,18 +165,73 @@ public class LzTool extends PanBase {
|
|||||||
headers.set("sec-ch-ua-platform", "Android");
|
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("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");
|
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
String url = SHARE_URL_PREFIX + "/ajaxm.php";
|
|
||||||
client.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
|
@Override
|
||||||
JsonObject urlJson = asJson(res2);
|
public Future<List<FileInfo>> parseFileList() {
|
||||||
if (urlJson.getInteger("zt") != 1) {
|
Promise<List<FileInfo>> promise = Promise.promise();
|
||||||
fail(urlJson.getString("inf"));
|
|
||||||
return;
|
String sUrl = shareLinkInfo.getShareUrl();
|
||||||
|
String pwd = shareLinkInfo.getSharePassword();
|
||||||
|
|
||||||
|
WebClient client = clientNoRedirects;
|
||||||
|
client.getAbs(sUrl).send().onSuccess(res -> {
|
||||||
|
String html = res.bodyAsString();
|
||||||
|
try {
|
||||||
|
String jsText = getJsByPwd(pwd, html, "var urls =window.location.href");
|
||||||
|
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "file");
|
||||||
|
Map<String, Object> data = CastUtil.cast(scriptObjectMirror.get("data"));
|
||||||
|
MultiMap map = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
data.forEach((k, v) -> map.set(k, v.toString()));
|
||||||
|
log.debug("解析参数: {}", map);
|
||||||
|
MultiMap headers = getHeaders(sUrl);
|
||||||
|
|
||||||
|
String url = SHARE_URL_PREFIX + "/filemoreajax.php?file=" + data.get("fid");
|
||||||
|
client.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
|
||||||
|
JsonObject fileListJson = asJson(res2);
|
||||||
|
if (fileListJson.getInteger("zt") != 1) {
|
||||||
|
promise.fail(baseMsg() + fileListJson.getString("info"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<FileInfo> list = new ArrayList<>();
|
||||||
|
fileListJson.getJsonArray("text").forEach(item -> {
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"icon": "apk",
|
||||||
|
"t": 0,
|
||||||
|
"id": "iULV2n4361c",
|
||||||
|
"name_all": "xx.apk",
|
||||||
|
"size": "49.8 M",
|
||||||
|
"time": "2021-03-19",
|
||||||
|
"duan": "in4361",
|
||||||
|
"p_ico": 0
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
JsonObject fileJson = (JsonObject) item;
|
||||||
|
FileInfo fileInfo = new FileInfo();
|
||||||
|
String size = fileJson.getString("size");
|
||||||
|
Long sizeNum = FileSizeConverter.convertToBytes(size);
|
||||||
|
String panType = shareLinkInfo.getType();
|
||||||
|
String id = fileJson.getString("id");
|
||||||
|
fileInfo.setFileName(fileJson.getString("name_all"))
|
||||||
|
.setFileId(id)
|
||||||
|
.setCreateTime(fileJson.getString("time"))
|
||||||
|
.setFileType(fileJson.getString("icon"))
|
||||||
|
.setSizeStr(fileJson.getString("size"))
|
||||||
|
.setSize(sizeNum)
|
||||||
|
.setPanType(panType)
|
||||||
|
.setParserUrl(getDomainName() + "/d/" + panType + "/" + id);
|
||||||
|
log.debug("文件信息: {}", fileInfo);
|
||||||
|
list.add(fileInfo);
|
||||||
|
});
|
||||||
|
promise.complete(list);
|
||||||
|
});
|
||||||
|
} catch (ScriptException | NoSuchMethodException e) {
|
||||||
|
promise.fail(e);
|
||||||
}
|
}
|
||||||
String downUrl = urlJson.getString("dom") + "/file/" + urlJson.getString("url");
|
});
|
||||||
client.getAbs(downUrl).putHeaders(headers).send()
|
return promise.future();
|
||||||
.onSuccess(res3 -> promise.complete(res3.headers().get("Location")))
|
|
||||||
.onFailure(handleFail(downUrl));
|
|
||||||
}).onFailure(handleFail(url));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import io.vertx.uritemplate.UriTemplate;
|
|||||||
* 需要请求API的UA和请求下载链接的UA保持一致,安卓Chrome需要访问电脑版才能下载
|
* 需要请求API的UA和请求下载链接的UA保持一致,安卓Chrome需要访问电脑版才能下载
|
||||||
*/
|
*/
|
||||||
public class P115Tool extends PanBase {
|
public class P115Tool extends PanBase {
|
||||||
private static final String API_URL_PREFIX = "https://anxia.com/webapi/";
|
private static final String API_URL_PREFIX = "https://115cdn.com/webapi/";
|
||||||
|
|
||||||
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "share/snap?share_code={dataKey}&offset=0" +
|
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "share/snap?share_code={dataKey}&offset=0" +
|
||||||
"&limit=20&receive_code={dataPwd}&cid=";
|
"&limit=20&receive_code={dataPwd}&cid=";
|
||||||
@@ -31,10 +31,10 @@ public class P115Tool extends PanBase {
|
|||||||
header.set("Connection", "keep-alive");
|
header.set("Connection", "keep-alive");
|
||||||
header.set("Content-Length", "0");
|
header.set("Content-Length", "0");
|
||||||
header.set("DNT", "1");
|
header.set("DNT", "1");
|
||||||
header.set("Host", "anxia.com");
|
header.set("Host", "115cdn.com");
|
||||||
header.set("Origin", "https://anxia.com");
|
header.set("Origin", "https://115cdn.com");
|
||||||
header.set("Pragma", "no-cache");
|
header.set("Pragma", "no-cache");
|
||||||
header.set("Referer", "https://anxia.com");
|
header.set("Referer", "https://115cdn.com");
|
||||||
header.set("Sec-Fetch-Dest", "empty");
|
header.set("Sec-Fetch-Dest", "empty");
|
||||||
header.set("Sec-Fetch-Mode", "cors");
|
header.set("Sec-Fetch-Mode", "cors");
|
||||||
header.set("Sec-Fetch-Site", "cross-site");
|
header.set("Sec-Fetch-Site", "cross-site");
|
||||||
@@ -75,8 +75,8 @@ public class P115Tool extends PanBase {
|
|||||||
.set("file_id", fileId))
|
.set("file_id", fileId))
|
||||||
.onSuccess(res2 -> {
|
.onSuccess(res2 -> {
|
||||||
JsonObject resJson2 = asJson(res2);
|
JsonObject resJson2 = asJson(res2);
|
||||||
if (!resJson.getBoolean("state")) {
|
if (!resJson2.getBoolean("state")) {
|
||||||
fail(FIRST_REQUEST_URL + " 解析错误: " + resJson);
|
fail(FIRST_REQUEST_URL + " 解析错误: " + resJson2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// data.url.url
|
// data.url.url
|
||||||
|
|||||||
109
parser/src/main/java/cn/qaiu/parser/impl/P360Tool.java
Normal file
109
parser/src/main/java/cn/qaiu/parser/impl/P360Tool.java
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.buffer.Buffer;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://yunpan.360.cn">360AI云盘</a>
|
||||||
|
* 360AI云盘解析
|
||||||
|
* 下载链接需要Referer: https://link.yunpan.com/
|
||||||
|
*/
|
||||||
|
public class P360Tool extends PanBase {
|
||||||
|
public P360Tool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
// https://www.yunpan.com/surl_yD7jK9d4W6D 获取302跳转地址
|
||||||
|
clientNoRedirects.getAbs(shareLinkInfo.getShareUrl()).send()
|
||||||
|
.onSuccess(res -> {
|
||||||
|
String location = res.getHeader("Location");
|
||||||
|
if (location != null) {
|
||||||
|
down302(location);
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(""));
|
||||||
|
|
||||||
|
|
||||||
|
return future();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void down302(String url) {
|
||||||
|
// 获取URL前缀 https://214004.link.yunpan.com/lk/surl_yD7ZU4WreR8 -> https://214004.link.yunpan.com/
|
||||||
|
String urlPrefix = url.substring(0, url.indexOf("/", 8));
|
||||||
|
|
||||||
|
clientSession.getAbs(url)
|
||||||
|
.send()
|
||||||
|
.onSuccess(res -> {
|
||||||
|
// find "nid": "17402043311959599"
|
||||||
|
Pattern compile = Pattern.compile("\"nid\": \"([^\"]+)\"");
|
||||||
|
Matcher matcher = compile.matcher(res.bodyAsString());
|
||||||
|
AtomicReference<String> nid = new AtomicReference<>();
|
||||||
|
if (matcher.find()) {
|
||||||
|
nid.set(matcher.group(1));
|
||||||
|
} else {
|
||||||
|
// 需要验证密码
|
||||||
|
/*
|
||||||
|
* POST https://4aec17.link.yunpan.com/share/verifyPassword
|
||||||
|
* Content-type: application/x-www-form-urlencoded UTF-8
|
||||||
|
* Referer: https://4aec17.link.yunpan.com/lk/surl_yD7jK9d4W6D
|
||||||
|
*
|
||||||
|
* shorturl=surl_yD7jK9d4W6D&linkpassword=d969
|
||||||
|
*/
|
||||||
|
clientSession.postAbs(urlPrefix + "/share/verifyPassword")
|
||||||
|
.putHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
.putHeader("Referer", urlPrefix)
|
||||||
|
.sendBuffer(Buffer.buffer("shorturl=" + shareLinkInfo.getShareKey() + "&linkpassword" +
|
||||||
|
"=" + shareLinkInfo.getSharePassword()))
|
||||||
|
.onSuccess(res2 -> {
|
||||||
|
JsonObject entries = asJson(res2);
|
||||||
|
if (entries.getInteger("errno") == 0) {
|
||||||
|
clientSession.getAbs(url)
|
||||||
|
.send()
|
||||||
|
.onSuccess(res3 -> {
|
||||||
|
Matcher matcher1 = compile.matcher(res3.bodyAsString());
|
||||||
|
if (matcher1.find()) {
|
||||||
|
nid.set(matcher1.group(1));
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
down(urlPrefix, nid.get());
|
||||||
|
}).onFailure(handleFail(""));
|
||||||
|
} else {
|
||||||
|
fail(entries.encode());
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(""));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
down(urlPrefix, nid.get());
|
||||||
|
}).onFailure(handleFail(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void down(String urlPrefix, String nid) {
|
||||||
|
clientSession.postAbs(urlPrefix + "/share/downloadfile")
|
||||||
|
.putHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
.putHeader("Referer", urlPrefix)
|
||||||
|
.sendBuffer(Buffer.buffer("shorturl=" + shareLinkInfo.getShareKey() + "&nid=" + nid))
|
||||||
|
.onSuccess(res2 -> {
|
||||||
|
JsonObject entries = asJson(res2);
|
||||||
|
String downloadurl = entries.getJsonObject("data").getString("downloadurl");
|
||||||
|
complete(downloadurl);
|
||||||
|
}).onFailure(handleFail(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static void main(String[] args) {
|
||||||
|
// String s = new P360Tool(ShareLinkInfo.newBuilder().shareUrl("https://www.yunpan.com/surl_yD7ZU4WreR8")
|
||||||
|
// .shareKey("surl_yD7ZU4WreR8")
|
||||||
|
// .build()).parseSync();
|
||||||
|
// System.out.println(s);
|
||||||
|
// }
|
||||||
|
}
|
||||||
47
parser/src/main/java/cn/qaiu/parser/impl/PcxTool.java
Normal file
47
parser/src/main/java/cn/qaiu/parser/impl/PcxTool.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="https://passport2.chaoxing.com">超星云盘</a>
|
||||||
|
*/
|
||||||
|
public class PcxTool extends PanBase {
|
||||||
|
|
||||||
|
public PcxTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
client.getAbs(shareLinkInfo.getShareUrl())
|
||||||
|
.send().onSuccess(res -> {
|
||||||
|
// 'download': 'https://d0.ananas.chaoxing.com/download/de08dcf546e4dd88a17bead86ff6338d?at_=1740211698795&ak_=d62a3acbd5ce43e1e8565b67990691e4&ad_=8c4ef22e980ee0dd9532ec3757ab19f8&fn=33.c'
|
||||||
|
String body = res.bodyAsString();
|
||||||
|
// 获取download
|
||||||
|
String str = "var fileinfo = {";
|
||||||
|
String fileInfo = res.bodyAsString().substring(res.bodyAsString().indexOf(str) + str.length() - 1
|
||||||
|
, res.bodyAsString().indexOf("};") + 1);
|
||||||
|
fileInfo = fileInfo.replace("'", "\"");
|
||||||
|
JsonObject jsonObject = new JsonObject(fileInfo);
|
||||||
|
String download = jsonObject.getString("download");
|
||||||
|
if (download.contains("fn=")) {
|
||||||
|
complete(download);
|
||||||
|
} else {
|
||||||
|
fail("获取下载链接失败: 不支持的文件类型: {}", jsonObject.getString("suffix"));
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail(shareLinkInfo.getShareUrl()));
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// public static void main(String[] args) {
|
||||||
|
// String s = new PcxTool(ShareLinkInfo.newBuilder().shareUrl("https://pan-yz.cldisk.com/external/m/file/953658049102462976")
|
||||||
|
// .shareKey("953658049102462976")
|
||||||
|
// .build()).parseSync();
|
||||||
|
// System.out.println(s);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@ package cn.qaiu.parser.impl;
|
|||||||
|
|
||||||
import cn.qaiu.entity.ShareLinkInfo;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import cn.qaiu.parser.PanBase;
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.util.HeaderUtils;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
import io.vertx.uritemplate.UriTemplate;
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,6 +14,24 @@ public class PvyyTool extends PanBase {
|
|||||||
private static final String API_URL_PREFIX1 = "https://www.vyuyun.com/apiv1/share/file/{key}?password={pwd}";
|
private static final String API_URL_PREFIX1 = "https://www.vyuyun.com/apiv1/share/file/{key}?password={pwd}";
|
||||||
private static final String API_URL_PREFIX2 = "https://www.vyuyun.com/apiv1/share/getShareDownUrl/{key}/{id}?password={pwd}";
|
private static final String API_URL_PREFIX2 = "https://www.vyuyun.com/apiv1/share/getShareDownUrl/{key}/{id}?password={pwd}";
|
||||||
|
|
||||||
|
|
||||||
|
private static final MultiMap header = HeaderUtils.parseHeaders("""
|
||||||
|
accept-language: zh-CN,zh;q=0.9,en;q=0.8
|
||||||
|
cache-control: no-cache
|
||||||
|
dnt: 1
|
||||||
|
origin: https://www.vyuyun.com
|
||||||
|
pragma: no-cache
|
||||||
|
priority: u=1, i
|
||||||
|
referer: https://www.vyuyun.com/
|
||||||
|
sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
|
||||||
|
sec-ch-ua-mobile: ?0
|
||||||
|
sec-ch-ua-platform: "Windows"
|
||||||
|
sec-fetch-dest: empty
|
||||||
|
sec-fetch-mode: cors
|
||||||
|
sec-fetch-site: same-site
|
||||||
|
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
|
||||||
|
""");
|
||||||
|
|
||||||
public PvyyTool(ShareLinkInfo shareLinkInfo) {
|
public PvyyTool(ShareLinkInfo shareLinkInfo) {
|
||||||
super(shareLinkInfo);
|
super(shareLinkInfo);
|
||||||
}
|
}
|
||||||
@@ -21,7 +41,7 @@ public class PvyyTool extends PanBase {
|
|||||||
client.getAbs(UriTemplate.of(API_URL_PREFIX1))
|
client.getAbs(UriTemplate.of(API_URL_PREFIX1))
|
||||||
.setTemplateParam("key", shareLinkInfo.getShareKey())
|
.setTemplateParam("key", shareLinkInfo.getShareKey())
|
||||||
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
||||||
.putHeader("referer", "https://www.vyuyun.com")
|
.putHeaders(header)
|
||||||
.send().onSuccess(res -> {
|
.send().onSuccess(res -> {
|
||||||
try {
|
try {
|
||||||
String id = asJson(res).getJsonObject("data").getJsonObject("data").getString("id");
|
String id = asJson(res).getJsonObject("data").getJsonObject("data").getString("id");
|
||||||
@@ -30,8 +50,7 @@ public class PvyyTool extends PanBase {
|
|||||||
.setTemplateParam("key", shareLinkInfo.getShareKey())
|
.setTemplateParam("key", shareLinkInfo.getShareKey())
|
||||||
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
|
||||||
.setTemplateParam("id", id)
|
.setTemplateParam("id", id)
|
||||||
.putHeader("referer", "https://www.vyuyun.com")
|
.putHeaders(header).send().onSuccess(res2 -> {
|
||||||
.send().onSuccess(res2 -> {
|
|
||||||
try {
|
try {
|
||||||
// data->downInfo->url
|
// data->downInfo->url
|
||||||
String url =
|
String url =
|
||||||
|
|||||||
61
parser/src/main/java/cn/qaiu/parser/impl/QQwTool.java
Normal file
61
parser/src/main/java/cn/qaiu/parser/impl/QQwTool.java
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class QQwTool extends QQTool {
|
||||||
|
|
||||||
|
public QQwTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<String> parse() {
|
||||||
|
client.getAbs(shareLinkInfo.getStandardUrl()).send().onSuccess(res -> {
|
||||||
|
String html = res.bodyAsString();
|
||||||
|
String url = extractVariables(html).get("url");
|
||||||
|
if (url != null) {
|
||||||
|
String url302 = url.replace("\\x26", "&");
|
||||||
|
promise.complete(url302);
|
||||||
|
|
||||||
|
/*
|
||||||
|
clientNoRedirects.getAbs(url302).send().onSuccess(res2 -> {
|
||||||
|
MultiMap headers = res2.headers();
|
||||||
|
if (headers.contains("Location")) {
|
||||||
|
promise.complete(headers.get("Location"));
|
||||||
|
} else {
|
||||||
|
fail("找不到重定向URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
}).onFailure(handleFail());
|
||||||
|
*/
|
||||||
|
} else {
|
||||||
|
fail("分享链接解析失败, 可能是链接失效");
|
||||||
|
}
|
||||||
|
}).onFailure(handleFail());
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Map<String, String> extractVariables(String jsCode) {
|
||||||
|
Map<String, String> variables = new HashMap<>();
|
||||||
|
|
||||||
|
// 正则表达式匹配 var 变量定义
|
||||||
|
String regex = "var\\s+(\\w+)\\s*=\\s*([\"']?)([^\"';\\s]+)\\2\n";
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(jsCode);
|
||||||
|
|
||||||
|
while (matcher.find()) {
|
||||||
|
String variableName = matcher.group(1); // 变量名
|
||||||
|
String variableValue = matcher.group(3); // 变量值
|
||||||
|
variables.put(variableName, variableValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return variables;
|
||||||
|
}
|
||||||
|
}
|
||||||
122
parser/src/main/java/cn/qaiu/parser/impl/UcTool.java
Normal file
122
parser/src/main/java/cn/qaiu/parser/impl/UcTool.java
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package cn.qaiu.parser.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.json.JsonArray;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UC网盘解析
|
||||||
|
*/
|
||||||
|
public class UcTool extends PanBase {
|
||||||
|
private static final String API_URL_PREFIX = "https://pc-api.uc.cn/1/clouddrive/";
|
||||||
|
|
||||||
|
public static final String SHARE_URL_PREFIX = "https://fast.uc.cn/s/";
|
||||||
|
|
||||||
|
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "share/sharepage/token?entry=ft&fr=pc&pr" +
|
||||||
|
"=UCBrowser";
|
||||||
|
|
||||||
|
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "transfer_share/detail?pwd_id={pwd_id}&passcode" +
|
||||||
|
"={passcode}&stoken={stoken}";
|
||||||
|
|
||||||
|
private static final String THIRD_REQUEST_URL = API_URL_PREFIX + "file/download?entry=ft&fr=pc&pr=UCBrowser";
|
||||||
|
|
||||||
|
public UcTool(ShareLinkInfo shareLinkInfo) {
|
||||||
|
super(shareLinkInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<String> parse() {
|
||||||
|
var dataKey = shareLinkInfo.getShareKey();
|
||||||
|
var passcode = shareLinkInfo.getSharePassword();
|
||||||
|
|
||||||
|
var jsonObject = JsonObject.of("share_for_transfer", true);
|
||||||
|
jsonObject.put("pwd_id", dataKey);
|
||||||
|
jsonObject.put("passcode", passcode);
|
||||||
|
// 第一次请求 获取文件信息
|
||||||
|
client.postAbs(FIRST_REQUEST_URL).sendJsonObject(jsonObject).onSuccess(res -> {
|
||||||
|
log.debug("第一阶段 {}", res.body());
|
||||||
|
var resJson = res.bodyAsJsonObject();
|
||||||
|
if (resJson.getInteger("code") != 0) {
|
||||||
|
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var stoken = resJson.getJsonObject("data").getString("stoken");
|
||||||
|
// 第二次请求
|
||||||
|
client.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
|
||||||
|
.setTemplateParam("pwd_id", dataKey)
|
||||||
|
.setTemplateParam("passcode", passcode)
|
||||||
|
.setTemplateParam("stoken", stoken)
|
||||||
|
.send().onSuccess(res2 -> {
|
||||||
|
log.debug("第二阶段 {}", res2.body());
|
||||||
|
JsonObject resJson2 = res2.bodyAsJsonObject();
|
||||||
|
if (resJson2.getInteger("code") != 0) {
|
||||||
|
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 文件信息
|
||||||
|
var info = resJson2.getJsonObject("data").getJsonArray("list").getJsonObject(0);
|
||||||
|
// 第二次请求
|
||||||
|
var bodyJson = JsonObject.of()
|
||||||
|
.put("fids", JsonArray.of(info.getString("fid")))
|
||||||
|
.put("pwd_id", dataKey)
|
||||||
|
.put("stoken", stoken)
|
||||||
|
.put("fids_token", JsonArray.of(info.getString("share_fid_token")));
|
||||||
|
client.postAbs(THIRD_REQUEST_URL).sendJsonObject(bodyJson)
|
||||||
|
.onSuccess(res3 -> {
|
||||||
|
log.debug("第三阶段 {}", res3.body());
|
||||||
|
var resJson3 = res3.bodyAsJsonObject();
|
||||||
|
if (resJson3.getInteger("code") != 0) {
|
||||||
|
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promise.complete(resJson3.getJsonArray("data").getJsonObject(0).getString("download_url"));
|
||||||
|
}).onFailure(handleFail(THIRD_REQUEST_URL));
|
||||||
|
|
||||||
|
}).onFailure(handleFail(SECOND_REQUEST_URL));
|
||||||
|
}
|
||||||
|
).onFailure(handleFail(FIRST_REQUEST_URL));
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
// https://dl-uf-zb.pds.uc.cn/l3PNAKfz/64623447/
|
||||||
|
// 646b0de6e9f13000c9b14ba182b805312795a82a/
|
||||||
|
// 646b0de6717e1bfa5bb44dd2a456f103c5177850?
|
||||||
|
// Expires=1737784900&OSSAccessKeyId=LTAI5tJJpWQEfrcKHnd1LqsZ&
|
||||||
|
// Signature=oBVV3anhv3tBKanHUcEIsktkB%2BM%3D&x-oss-traffic-limit=503316480
|
||||||
|
// &response-content-disposition=attachment%3B%20filename%3DC%2523%2520Shell%2520%2528C%2523%2520Offline%2520Compiler%2529_2.5.16.apks
|
||||||
|
// %3Bfilename%2A%3Dutf-8%27%27C%2523%2520Shell%2520%2528C%2523%2520Offline%2520Compiler%2529_2.5.16.apks
|
||||||
|
|
||||||
|
//eyJ4OmF1IjoiLSIsIng6dWQiOiI0LU4tNS0wLTYtTi0zLWZ0LTAtMi1OLU4iLCJ4OnNwIjoiMTAwIiwieDp0b2tlbiI6IjQtZjY0ZmMxMDFjZmQxZGVkNTRkMGM0NmMzYzliMzkyOWYtNS03LTE1MzYxMS1kYWNiMzY2NWJiYWE0ZjVlOWQzNzgwMGVjNjQwMzE2MC0wLTAtMC0wLTQ5YzUzNTE3OGIxOTY0YzhjYzUwYzRlMDk5MTZmYWRhIiwieDp0dGwiOiIxMDgwMCJ9
|
||||||
|
//eyJjYWxsYmFja0JvZHlUeXBlIjoiYXBwbGljYXRpb24vanNvbiIsImNhbGxiYWNrU3RhZ2UiOiJiZWZvcmUtZXhlY3V0ZSIsImNhbGxiYWNrRmFpbHVyZUFjdGlvbiI6Imlnbm9yZSIsImNhbGxiYWNrVXJsIjoiaHR0cHM6Ly9hdXRoLWNkbi51Yy5jbi9vdXRlci9vc3MvY2hlY2twbGF5IiwiY2FsbGJhY2tCb2R5Ijoie1wiaG9zdFwiOiR7aHR0cEhlYWRlci5ob3N0fSxcInNpemVcIjoke3NpemV9LFwicmFuZ2VcIjoke2h0dHBIZWFkZXIucmFuZ2V9LFwicmVmZXJlclwiOiR7aHR0cEhlYWRlci5yZWZlcmVyfSxcImNvb2tpZVwiOiR7aHR0cEhlYWRlci5jb29raWV9LFwibWV0aG9kXCI6JHtodHRwSGVhZGVyLm1ldGhvZH0sXCJpcFwiOiR7Y2xpZW50SXB9LFwicG9ydFwiOiR7Y2xpZW50UG9ydH0sXCJvYmplY3RcIjoke29iamVjdH0sXCJzcFwiOiR7eDpzcH0sXCJ1ZFwiOiR7eDp1ZH0sXCJ0b2tlblwiOiR7eDp0b2tlbn0sXCJhdVwiOiR7eDphdX0sXCJ0dGxcIjoke3g6dHRsfSxcImR0X3NwXCI6JHt4OmR0X3NwfSxcImhzcFwiOiR7eDpoc3B9LFwiY2xpZW50X3Rva2VuXCI6JHtxdWVyeVN0cmluZy5jbGllbnRfdG9rZW59fSJ9
|
||||||
|
//callback-var {"x:au":"-","x:ud":"4-N-5-0-6-N-3-ft-0-2-N-N","x:sp":"100","x:token":"4-f64fc101cfd1ded54d0c46c3c9b3929f-5-7-153611-dacb3665bbaa4f5e9d37800ec6403160-0-0-0-0-49c535178b1964c8cc50c4e09916fada","x:ttl":"10800"}
|
||||||
|
//callback {"callbackBodyType":"application/json","callbackStage":"before-execute","callbackFailureAction":"ignore","callbackUrl":"https://auth-cdn.uc.cn/outer/oss/checkplay","callbackBody":"{\"host\":${httpHeader.host},\"size\":${size},\"range\":${httpHeader.range},\"referer\":${httpHeader.referer},\"cookie\":${httpHeader.cookie},\"method\":${httpHeader.method},\"ip\":${clientIp},\"port\":${clientPort},\"object\":${object},\"sp\":${x:sp},\"ud\":${x:ud},\"token\":${x:token},\"au\":${x:au},\"ttl\":${x:ttl},\"dt_sp\":${x:dt_sp},\"hsp\":${x:hsp},\"client_token\":${queryString.client_token}}"}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// callback-var
|
||||||
|
{
|
||||||
|
"x:au": "-",
|
||||||
|
"x:ud": "4-N-5-0-6-N-3-ft-0-2-N-N",
|
||||||
|
"x:sp": "100",
|
||||||
|
"x:token": "4-f64fc101cfd1ded54d0c46c3c9b3929f-5-7-153611-dacb3665bbaa4f5e9d37800ec6403160-0-0-0-0-49c535178b1964c8cc50c4e09916fada",
|
||||||
|
"x:ttl": "10800"
|
||||||
|
}
|
||||||
|
|
||||||
|
// callback
|
||||||
|
{
|
||||||
|
"callbackBodyType": "application/json",
|
||||||
|
"callbackStage": "before-execute",
|
||||||
|
"callbackFailureAction": "ignore",
|
||||||
|
"callbackUrl": "https://auth-cdn.uc.cn/outer/oss/checkplay",
|
||||||
|
"callbackBody": "{\"host\":${httpHeader.host},\"size\":${size},\"range\":${httpHeader.range},\"referer\":${httpHeader.referer},\"cookie\":${httpHeader.cookie},\"method\":${httpHeader.method},\"ip\":${clientIp},\"port\":${clientPort},\"object\":${object},\"sp\":${x:sp},\"ud\":${x:ud},\"token\":${x:token},\"au\":${x:au},\"ttl\":${x:ttl},\"dt_sp\":${x:dt_sp},\"hsp\":${x:hsp},\"client_token\":${queryString.client_token}}"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
new UcTool(ShareLinkInfo.newBuilder().shareUrl("https://fast.uc.cn/s/33197dd53ace4").shareKey("33197dd53ace4").build()).parse().onSuccess(
|
||||||
|
System.out::println
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import cn.qaiu.parser.PanBase;
|
|||||||
import cn.qaiu.util.CommonUtils;
|
import cn.qaiu.util.CommonUtils;
|
||||||
import cn.qaiu.util.JsExecUtils;
|
import cn.qaiu.util.JsExecUtils;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.ext.web.client.WebClient;
|
import io.vertx.ext.web.client.WebClient;
|
||||||
import io.vertx.uritemplate.UriTemplate;
|
import io.vertx.uritemplate.UriTemplate;
|
||||||
@@ -17,6 +18,8 @@ import java.util.Map;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static cn.qaiu.util.RandomStringGenerator.gen36String;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 123网盘
|
* 123网盘
|
||||||
*/
|
*/
|
||||||
@@ -30,8 +33,27 @@ public class YeTool extends PanBase {
|
|||||||
"&shareKey={shareKey}&SharePwd={pwd}&ParentFileId=0&Page=1&event=homeListFile&operateType=1";
|
"&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}";
|
private static final String DOWNLOAD_API_URL = "https://www.123pan.com/a/api/share/download/info?{authK}={authV}";
|
||||||
|
|
||||||
|
private final MultiMap header = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
|
||||||
public YeTool(ShareLinkInfo shareLinkInfo) {
|
public YeTool(ShareLinkInfo shareLinkInfo) {
|
||||||
super(shareLinkInfo);
|
super(shareLinkInfo);
|
||||||
|
header.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
|
||||||
|
header.set("App-Version", "3");
|
||||||
|
header.set("Cache-Control", "no-cache");
|
||||||
|
header.set("Connection", "keep-alive");
|
||||||
|
header.set("DNT", "1");
|
||||||
|
header.set("Host", "www.123pan.com");
|
||||||
|
header.set("LoginUuid", gen36String());
|
||||||
|
header.set("Pragma", "no-cache");
|
||||||
|
header.set("Referer", shareLinkInfo.getStandardUrl());
|
||||||
|
header.set("Sec-Fetch-Dest", "empty");
|
||||||
|
header.set("Sec-Fetch-Mode", "cors");
|
||||||
|
header.set("Sec-Fetch-Site", "same-origin");
|
||||||
|
header.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0");
|
||||||
|
header.set("platform", "web");
|
||||||
|
header.set("sec-ch-ua", "\"Not)A;Brand\";v=\"99\", \"Microsoft Edge\";v=\"127\", \"Chromium\";v=\"127\"");
|
||||||
|
header.set("sec-ch-ua-mobile", "?0");
|
||||||
|
header.set("sec-ch-ua-platform", "Windows");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
|
|||||||
18
parser/src/main/java/cn/qaiu/util/CastUtil.java
Normal file
18
parser/src/main/java/cn/qaiu/util/CastUtil.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package cn.qaiu.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为任意类型 旨在消除泛型转换时的异常
|
||||||
|
*/
|
||||||
|
public interface CastUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 泛型转换
|
||||||
|
* @param object 要转换的object
|
||||||
|
* @param <T> T
|
||||||
|
* @return T
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static <T> T cast(Object object) {
|
||||||
|
return (T) object;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
parser/src/main/java/cn/qaiu/util/FileSizeConverter.java
Normal file
35
parser/src/main/java/cn/qaiu/util/FileSizeConverter.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package cn.qaiu.util;
|
||||||
|
|
||||||
|
public class FileSizeConverter {
|
||||||
|
|
||||||
|
public static long convertToBytes(String sizeStr) {
|
||||||
|
if (sizeStr == null || sizeStr.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Invalid file size string");
|
||||||
|
}
|
||||||
|
|
||||||
|
sizeStr = sizeStr.trim().toUpperCase();
|
||||||
|
char unit = sizeStr.charAt(sizeStr.length() - 1);
|
||||||
|
double size = Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 1));
|
||||||
|
|
||||||
|
return switch (unit) {
|
||||||
|
case 'B' -> (long) size;
|
||||||
|
case 'K' -> (long) (size * 1024);
|
||||||
|
case 'M' -> (long) (size * 1024 * 1024);
|
||||||
|
case 'G' -> (long) (size * 1024 * 1024 * 1024);
|
||||||
|
default -> throw new IllegalArgumentException("Unknown file size unit: " + unit);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String convertToReadableSize(long bytes) {
|
||||||
|
if (bytes < 1024) {
|
||||||
|
return bytes + " B";
|
||||||
|
} else if (bytes < 1024 * 1024) {
|
||||||
|
return String.format("%.1f K", bytes / 1024.0);
|
||||||
|
} else if (bytes < 1024 * 1024 * 1024) {
|
||||||
|
return String.format("%.1f M", bytes / (1024.0 * 1024));
|
||||||
|
} else {
|
||||||
|
return String.format("%.1f G", bytes / (1024.0 * 1024 * 1024));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
35
parser/src/main/java/cn/qaiu/util/HeaderUtils.java
Normal file
35
parser/src/main/java/cn/qaiu/util/HeaderUtils.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package cn.qaiu.util;
|
||||||
|
|
||||||
|
import io.vertx.core.MultiMap;
|
||||||
|
|
||||||
|
public class HeaderUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将请求头字符串转换为Vert.x的MultiMap对象
|
||||||
|
*
|
||||||
|
* @param headerString 请求头字符串
|
||||||
|
* @return MultiMap对象
|
||||||
|
*/
|
||||||
|
public static MultiMap parseHeaders(String headerString) {
|
||||||
|
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
|
||||||
|
|
||||||
|
if (headerString == null || headerString.isEmpty()) {
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按行分割字符串
|
||||||
|
String[] lines = headerString.split("\n");
|
||||||
|
|
||||||
|
for (String line : lines) {
|
||||||
|
// 按冒号分割键值对
|
||||||
|
String[] parts = line.split(":", 2);
|
||||||
|
if (parts.length == 2) {
|
||||||
|
String key = parts[0].trim();
|
||||||
|
String value = parts[1].trim();
|
||||||
|
headers.add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -124,7 +124,10 @@ public interface JsContent {
|
|||||||
},
|
},
|
||||||
ajax: function (obj) {
|
ajax: function (obj) {
|
||||||
signObj = obj
|
signObj = obj
|
||||||
}
|
},
|
||||||
|
val: function(a) {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -134,7 +137,6 @@ public interface JsContent {
|
|||||||
jQuery.fn.init.prototype = jQuery.fn;
|
jQuery.fn.init.prototype = jQuery.fn;
|
||||||
|
|
||||||
|
|
||||||
// 伪装jquery.ajax函数获取关键数据
|
|
||||||
$.ajax = function (obj) {
|
$.ajax = function (obj) {
|
||||||
signObj = obj
|
signObj = obj
|
||||||
}
|
}
|
||||||
@@ -142,11 +144,16 @@ public interface JsContent {
|
|||||||
var document = {
|
var document = {
|
||||||
getElementById: function (v) {
|
getElementById: function (v) {
|
||||||
return {
|
return {
|
||||||
value: 'v'
|
value: 'v',
|
||||||
|
style: {
|
||||||
|
display: ''
|
||||||
|
},
|
||||||
|
addEventListener: function() {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var window = {location: {}}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
String kwSignString = """
|
String kwSignString = """
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package cn.qaiu.util;
|
package cn.qaiu.util;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
public class RandomStringGenerator {
|
public class RandomStringGenerator {
|
||||||
private static final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
private static final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
@@ -19,4 +20,10 @@ public class RandomStringGenerator {
|
|||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String gen36String() {
|
||||||
|
String uuid = UUID.randomUUID().toString().toLowerCase();
|
||||||
|
// 移除短横线
|
||||||
|
return uuid.replace("-", "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
<logger name="io.netty" level="warn"/>
|
<logger name="io.netty" level="warn"/>
|
||||||
<logger name="io.vertx" level="info"/>
|
<logger name="io.vertx" level="info"/>
|
||||||
<logger name="com.zaxxer.hikari" level="info"/>
|
<logger name="com.zaxxer.hikari" level="info"/>
|
||||||
|
<logger name="cn.qaiu" level="debug"/>
|
||||||
<root level="info">
|
<root level="info">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
<!-- <appender-ref ref="FILE"/>-->
|
<!-- <appender-ref ref="FILE"/>-->
|
||||||
|
|||||||
1
web-front/.gitignore
vendored
1
web-front/.gitignore
vendored
@@ -24,3 +24,4 @@ pnpm-debug.log*
|
|||||||
/nfd-front.zip
|
/nfd-front.zip
|
||||||
/nfd-front
|
/nfd-front
|
||||||
/package-lock.json
|
/package-lock.json
|
||||||
|
/yarn.lock
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<img :height="150" src="../public/images/lanzou111.png" alt="lz"></img>
|
<img :height="150" src="../public/images/lanzou111.png" alt="lz"></img>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 style="text-align: center;">NFD网盘直链解析0.1.8_bate3(API演示)</h3>
|
<h3 style="text-align: center;">NFD网盘直链解析0.1.8_bate32</h3>
|
||||||
<div class="typo">
|
<div class="typo">
|
||||||
<p style="text-align: center;">
|
<p style="text-align: center;">
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
5843
web-front/yarn.lock
5843
web-front/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ package cn.qaiu.lz;
|
|||||||
import cn.qaiu.WebClientVertxInit;
|
import cn.qaiu.WebClientVertxInit;
|
||||||
import cn.qaiu.db.pool.JDBCPoolInit;
|
import cn.qaiu.db.pool.JDBCPoolInit;
|
||||||
import cn.qaiu.lz.common.cache.CacheConfigLoader;
|
import cn.qaiu.lz.common.cache.CacheConfigLoader;
|
||||||
|
import cn.qaiu.lz.common.interceptorImpl.RateLimiter;
|
||||||
import cn.qaiu.vx.core.Deploy;
|
import cn.qaiu.vx.core.Deploy;
|
||||||
import cn.qaiu.vx.core.util.ConfigConstant;
|
import cn.qaiu.vx.core.util.ConfigConstant;
|
||||||
import cn.qaiu.vx.core.util.VertxHolder;
|
import cn.qaiu.vx.core.util.VertxHolder;
|
||||||
@@ -11,6 +12,9 @@ import io.vertx.core.json.JsonArray;
|
|||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.core.json.jackson.DatabindCodec;
|
import io.vertx.core.json.jackson.DatabindCodec;
|
||||||
import io.vertx.core.shareddata.LocalMap;
|
import io.vertx.core.shareddata.LocalMap;
|
||||||
|
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import static cn.qaiu.vx.core.util.ConfigConstant.LOCAL;
|
import static cn.qaiu.vx.core.util.ConfigConstant.LOCAL;
|
||||||
|
|
||||||
@@ -36,32 +40,46 @@ public class AppMain {
|
|||||||
private static void exec(JsonObject jsonObject) {
|
private static void exec(JsonObject jsonObject) {
|
||||||
WebClientVertxInit.init(VertxHolder.getVertxInstance());
|
WebClientVertxInit.init(VertxHolder.getVertxInstance());
|
||||||
DatabindCodec.mapper().registerModule(new JavaTimeModule());
|
DatabindCodec.mapper().registerModule(new JavaTimeModule());
|
||||||
|
// 限流
|
||||||
|
if (jsonObject.containsKey("rateLimit")) {
|
||||||
|
JsonObject rateLimit = jsonObject.getJsonObject("rateLimit");
|
||||||
|
RateLimiter.init(rateLimit);
|
||||||
|
}
|
||||||
// 数据库
|
// 数据库
|
||||||
if (jsonObject.getJsonObject(ConfigConstant.SERVER).getBoolean("enableDatabase")) {
|
if (jsonObject.getJsonObject(ConfigConstant.SERVER).getBoolean("enableDatabase")) {
|
||||||
JDBCPoolInit.builder().config(jsonObject.getJsonObject("dataSource")).build().initPool();
|
JDBCPoolInit.builder().config(jsonObject.getJsonObject("dataSource"))
|
||||||
|
.build()
|
||||||
|
.initPool().onSuccess(PreparedStatement -> {
|
||||||
|
VertxHolder.getVertxInstance().setTimer(1000, id -> {
|
||||||
|
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS"));
|
||||||
|
System.out.println("数据库连接成功");
|
||||||
|
String addr = jsonObject.getJsonObject(ConfigConstant.SERVER).getString("domainName");
|
||||||
|
System.out.println("启动成功: \n本地服务地址: " + addr);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// 缓存
|
// 缓存
|
||||||
if (jsonObject.containsKey(ConfigConstant.CACHE)) {
|
if (jsonObject.containsKey(ConfigConstant.CACHE)) {
|
||||||
CacheConfigLoader.init(jsonObject.getJsonObject(ConfigConstant.CACHE));
|
CacheConfigLoader.init(jsonObject.getJsonObject(ConfigConstant.CACHE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LocalMap<Object, Object> localMap = VertxHolder.getVertxInstance().sharedData().getLocalMap(LOCAL);
|
||||||
// 代理
|
// 代理
|
||||||
if (jsonObject.containsKey(ConfigConstant.PROXY)) {
|
if (jsonObject.containsKey(ConfigConstant.PROXY)) {
|
||||||
LocalMap<Object, Object> localMap = VertxHolder.getVertxInstance().sharedData().getLocalMap(LOCAL);
|
|
||||||
JsonArray proxyJsonArray = jsonObject.getJsonArray(ConfigConstant.PROXY);
|
JsonArray proxyJsonArray = jsonObject.getJsonArray(ConfigConstant.PROXY);
|
||||||
if (proxyJsonArray != null) {
|
if (proxyJsonArray != null) {
|
||||||
|
JsonObject jsonObject1 = new JsonObject();
|
||||||
proxyJsonArray.forEach(proxyJson -> {
|
proxyJsonArray.forEach(proxyJson -> {
|
||||||
String panTypes = ((JsonObject)proxyJson).getString("panTypes");
|
String panTypes = ((JsonObject)proxyJson).getString("panTypes");
|
||||||
|
|
||||||
if (!panTypes.isEmpty()) {
|
if (!panTypes.isEmpty()) {
|
||||||
JsonObject jsonObject1 = new JsonObject();
|
|
||||||
for (String s : panTypes.split(",")) {
|
for (String s : panTypes.split(",")) {
|
||||||
jsonObject1.put(s, proxyJson);
|
jsonObject1.put(s, proxyJson);
|
||||||
}
|
}
|
||||||
localMap.put("proxy", jsonObject1);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
localMap.put("proxy", jsonObject1);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ public class CacheConfigLoader {
|
|||||||
TYPE = config.getString("type");
|
TYPE = config.getString("type");
|
||||||
Integer defaultDuration = config.getInteger("defaultDuration");
|
Integer defaultDuration = config.getInteger("defaultDuration");
|
||||||
DEFAULT_DURATION = defaultDuration == null ? 60 : defaultDuration;
|
DEFAULT_DURATION = defaultDuration == null ? 60 : defaultDuration;
|
||||||
config.getJsonObject("duration").getMap().forEach((k,v) -> {
|
JsonObject duration = config.getJsonObject("duration");
|
||||||
|
if (duration == null) return;
|
||||||
|
duration.getMap().forEach((k, v) -> {
|
||||||
if (v == null) {
|
if (v == null) {
|
||||||
CONFIGS.put(k, DEFAULT_DURATION);
|
CONFIGS.put(k, DEFAULT_DURATION);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package cn.qaiu.lz.common.cache;
|
package cn.qaiu.lz.common.cache;
|
||||||
|
|
||||||
import cn.qaiu.db.pool.JDBCPoolInit;
|
import cn.qaiu.db.pool.JDBCPoolInit;
|
||||||
|
import cn.qaiu.db.pool.JDBCType;
|
||||||
import cn.qaiu.lz.web.model.CacheLinkInfo;
|
import cn.qaiu.lz.web.model.CacheLinkInfo;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.Promise;
|
import io.vertx.core.Promise;
|
||||||
@@ -8,13 +9,17 @@ import io.vertx.core.json.JsonObject;
|
|||||||
import io.vertx.jdbcclient.JDBCPool;
|
import io.vertx.jdbcclient.JDBCPool;
|
||||||
import io.vertx.sqlclient.Row;
|
import io.vertx.sqlclient.Row;
|
||||||
import io.vertx.sqlclient.templates.SqlTemplate;
|
import io.vertx.sqlclient.templates.SqlTemplate;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class CacheManager {
|
public class CacheManager {
|
||||||
private final JDBCPool jdbcPool = JDBCPoolInit.instance().getPool();
|
private final JDBCPool jdbcPool = JDBCPoolInit.instance().getPool();
|
||||||
|
private final JDBCType jdbcType = JDBCPoolInit.instance().getType();
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(CacheManager.class);
|
||||||
|
|
||||||
public Future<CacheLinkInfo> get(String cacheKey) {
|
public Future<CacheLinkInfo> get(String cacheKey) {
|
||||||
String sql = "SELECT share_key as shareKey, direct_link as directLink, expiration FROM cache_link_info WHERE share_key = #{share_key}";
|
String sql = "SELECT share_key as shareKey, direct_link as directLink, expiration FROM cache_link_info WHERE share_key = #{share_key}";
|
||||||
@@ -33,16 +38,31 @@ public class CacheManager {
|
|||||||
cacheHit = new CacheLinkInfo(JsonObject.of("cacheHit", false, "shareKey", cacheKey));
|
cacheHit = new CacheLinkInfo(JsonObject.of("cacheHit", false, "shareKey", cacheKey));
|
||||||
}
|
}
|
||||||
promise.complete(cacheHit);
|
promise.complete(cacheHit);
|
||||||
}).onFailure(Throwable::printStackTrace);
|
}).onFailure(e->{
|
||||||
|
promise.fail(e);
|
||||||
|
LOGGER.error("cache get:", e);
|
||||||
|
});
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 插入或更新缓存数据
|
// 插入或更新缓存数据
|
||||||
public Future<Void> cacheShareLink(CacheLinkInfo cacheLinkInfo) {
|
public Future<Void> cacheShareLink(CacheLinkInfo cacheLinkInfo) {
|
||||||
String sql = "MERGE INTO cache_link_info (share_key, direct_link, expiration) " +
|
String sql;
|
||||||
"KEY (share_key) " +
|
if (jdbcType == JDBCType.MySQL) {
|
||||||
"VALUES (#{shareKey}, #{directLink}, #{expiration})";
|
sql = """
|
||||||
|
INSERT INTO cache_link_info (share_key, direct_link, expiration)
|
||||||
|
VALUES (#{shareKey}, #{directLink}, #{expiration})
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
direct_link = VALUES(direct_link),
|
||||||
|
expiration = VALUES(expiration);
|
||||||
|
""";
|
||||||
|
} else { // 运行H2
|
||||||
|
sql = "MERGE INTO cache_link_info (share_key, direct_link, expiration) " +
|
||||||
|
"KEY (share_key) " +
|
||||||
|
"VALUES (#{shareKey}, #{directLink}, #{expiration})";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 直接传递 CacheLinkInfo 实体类
|
// 直接传递 CacheLinkInfo 实体类
|
||||||
return SqlTemplate.forUpdate(jdbcPool, sql)
|
return SqlTemplate.forUpdate(jdbcPool, sql)
|
||||||
@@ -55,11 +75,23 @@ public class CacheManager {
|
|||||||
public Future<Integer> updateTotalByField(String shareKey, CacheTotalField field) {
|
public Future<Integer> updateTotalByField(String shareKey, CacheTotalField field) {
|
||||||
Promise<Integer> promise = Promise.promise();
|
Promise<Integer> promise = Promise.promise();
|
||||||
String fieldLower = field.name().toLowerCase();
|
String fieldLower = field.name().toLowerCase();
|
||||||
String sql = """
|
String sql;
|
||||||
|
if (jdbcType == JDBCType.MySQL) { // 假设你有一个标识当前数据库类型的布尔变量
|
||||||
|
sql = """
|
||||||
|
INSERT INTO `api_statistics_info` (`pan_type`, `share_key`, `{field}`, `update_ts`)
|
||||||
|
VALUES (#{panType}, #{shareKey}, #{total}, #{ts})
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
`pan_type` = VALUES(`pan_type`),
|
||||||
|
`{field}` = VALUES(`{field}`),
|
||||||
|
`update_ts` = VALUES(`update_ts`);
|
||||||
|
""".replace("{field}", fieldLower);
|
||||||
|
} else { // 运行H2
|
||||||
|
sql = """
|
||||||
MERGE INTO `api_statistics_info` (`pan_type`, `share_key`, `{field}`, `update_ts`)
|
MERGE INTO `api_statistics_info` (`pan_type`, `share_key`, `{field}`, `update_ts`)
|
||||||
KEY (`share_key`)
|
KEY (`share_key`)
|
||||||
VALUES (#{panType}, #{shareKey}, #{total}, #{ts})
|
VALUES (#{panType}, #{shareKey}, #{total}, #{ts})
|
||||||
""".replace("{field}", fieldLower);
|
""".replace("{field}", fieldLower);
|
||||||
|
}
|
||||||
|
|
||||||
getShareKeyTotal(shareKey, fieldLower).onSuccess(total -> {
|
getShareKeyTotal(shareKey, fieldLower).onSuccess(total -> {
|
||||||
Integer newTotal = (total == null ? 0 : total) + 1;
|
Integer newTotal = (total == null ? 0 : total) + 1;
|
||||||
@@ -71,7 +103,10 @@ public class CacheManager {
|
|||||||
put("ts", System.currentTimeMillis());
|
put("ts", System.currentTimeMillis());
|
||||||
}})
|
}})
|
||||||
.onSuccess(res -> promise.complete(res.rowCount()))
|
.onSuccess(res -> promise.complete(res.rowCount()))
|
||||||
.onFailure(Throwable::printStackTrace);
|
.onFailure(e->{
|
||||||
|
promise.fail(e);
|
||||||
|
LOGGER.error("updateTotalByField: ", e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
@@ -84,10 +119,12 @@ public class CacheManager {
|
|||||||
|
|
||||||
public Future<Integer> getShareKeyTotal(String shareKey, String name) {
|
public Future<Integer> getShareKeyTotal(String shareKey, String name) {
|
||||||
String sql = """
|
String sql = """
|
||||||
select `share_key`, sum({total_name}) sum_num
|
SELECT `share_key`, SUM({total_name}) AS sum_num
|
||||||
from `api_statistics_info`
|
FROM `api_statistics_info`
|
||||||
group by `share_key` having `share_key` = #{shareKey};
|
WHERE `share_key` = #{shareKey}
|
||||||
|
GROUP BY `share_key`;
|
||||||
""".replace("{total_name}", name);
|
""".replace("{total_name}", name);
|
||||||
|
|
||||||
Promise<Integer> promise = Promise.promise();
|
Promise<Integer> promise = Promise.promise();
|
||||||
Map<String, Object> paramMap = new HashMap<>();
|
Map<String, Object> paramMap = new HashMap<>();
|
||||||
paramMap.put("shareKey", shareKey);
|
paramMap.put("shareKey", shareKey);
|
||||||
@@ -98,16 +135,21 @@ public class CacheManager {
|
|||||||
Integer total = res.iterator().hasNext() ?
|
Integer total = res.iterator().hasNext() ?
|
||||||
res.iterator().next().getInteger("sum_num") : null;
|
res.iterator().next().getInteger("sum_num") : null;
|
||||||
promise.complete(total);
|
promise.complete(total);
|
||||||
|
}).onFailure(e->{
|
||||||
|
promise.fail(e);
|
||||||
|
LOGGER.error("getShareKeyTotal: ", e);
|
||||||
});
|
});
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Future<Map<String, Integer>> getShareKeyTotal(String shareKey) {
|
public Future<Map<String, Integer>> getShareKeyTotal(String shareKey) {
|
||||||
String sql = """
|
String sql = """
|
||||||
select `share_key`, sum(cache_hit_total) hit_total, sum(api_parser_total) parser_total,
|
SELECT `share_key`, SUM(cache_hit_total) AS hit_total, SUM(api_parser_total) AS parser_total
|
||||||
from `api_statistics_info`
|
FROM `api_statistics_info`
|
||||||
group by `share_key` having `share_key` = #{shareKey}
|
WHERE `share_key` = #{shareKey}
|
||||||
|
GROUP BY `share_key`;
|
||||||
""";
|
""";
|
||||||
|
|
||||||
Promise<Map<String, Integer>> promise = Promise.promise();
|
Promise<Map<String, Integer>> promise = Promise.promise();
|
||||||
Map<String, Object> paramMap = new HashMap<>();
|
Map<String, Object> paramMap = new HashMap<>();
|
||||||
paramMap.put("shareKey", shareKey);
|
paramMap.put("shareKey", shareKey);
|
||||||
@@ -115,16 +157,19 @@ public class CacheManager {
|
|||||||
.mapTo(Row::toJson)
|
.mapTo(Row::toJson)
|
||||||
.execute(paramMap)
|
.execute(paramMap)
|
||||||
.onSuccess(res -> {
|
.onSuccess(res -> {
|
||||||
if(res.iterator().hasNext()) {
|
if(res.iterator().hasNext()) {
|
||||||
JsonObject next = res.iterator().next();
|
JsonObject next = res.iterator().next();
|
||||||
Map<String, Integer> resp = new HashMap<>(){{
|
Map<String, Integer> resp = new HashMap<>(){{
|
||||||
put("hit_total" ,next.getInteger("hit_total"));
|
put("hit_total" ,next.getInteger("hit_total"));
|
||||||
put("parser_total" ,next.getInteger("parser_total"));
|
put("parser_total" ,next.getInteger("parser_total"));
|
||||||
}};
|
}};
|
||||||
promise.complete(resp);
|
promise.complete(resp);
|
||||||
} else {
|
} else {
|
||||||
promise.complete();
|
promise.complete();
|
||||||
}
|
}
|
||||||
|
}).onFailure(e->{
|
||||||
|
promise.fail(e);
|
||||||
|
LOGGER.error("getShareKeyTotal0: ", e);
|
||||||
});
|
});
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ package cn.qaiu.lz.common.interceptorImpl;
|
|||||||
|
|
||||||
import cn.qaiu.vx.core.annotaions.HandleSortFilter;
|
import cn.qaiu.vx.core.annotaions.HandleSortFilter;
|
||||||
import cn.qaiu.vx.core.interceptor.BeforeInterceptor;
|
import cn.qaiu.vx.core.interceptor.BeforeInterceptor;
|
||||||
|
import cn.qaiu.vx.core.util.ConfigConstant;
|
||||||
import cn.qaiu.vx.core.util.SharedDataUtil;
|
import cn.qaiu.vx.core.util.SharedDataUtil;
|
||||||
import io.vertx.core.json.JsonArray;
|
import io.vertx.core.json.JsonArray;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
import io.vertx.ext.web.RoutingContext;
|
import io.vertx.ext.web.RoutingContext;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import static cn.qaiu.vx.core.util.ConfigConstant.IGNORES_REG;
|
import static cn.qaiu.vx.core.util.ConfigConstant.IGNORES_REG;
|
||||||
|
import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 前置拦截器实现
|
* 前置拦截器实现
|
||||||
@@ -20,8 +23,43 @@ public class DefaultInterceptor implements BeforeInterceptor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(RoutingContext ctx) {
|
public void handle(RoutingContext ctx) {
|
||||||
// System.out.println("进入前置拦截器1->" + ctx.request().path());
|
// 读取配置 如果配置了限流 则进行限流
|
||||||
doNext(ctx);
|
if (!SharedDataUtil.getJsonConfig(ConfigConstant.GLOBAL_CONFIG).containsKey("rateLimit")) {
|
||||||
|
doNext(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonObject rateLimit = SharedDataUtil.getJsonConfig(ConfigConstant.GLOBAL_CONFIG)
|
||||||
|
.getJsonObject("rateLimit");
|
||||||
|
// # 限流配置
|
||||||
|
//rateLimit:
|
||||||
|
// # 是否启用限流
|
||||||
|
// enable: true
|
||||||
|
// # 限流的请求数
|
||||||
|
// limit: 1000
|
||||||
|
// # 限流的时间窗口(单位秒)
|
||||||
|
// timeWindow: 60
|
||||||
|
if (rateLimit.getBoolean("enable")) {
|
||||||
|
// 获取当前请求的路径
|
||||||
|
String path = ctx.request().path();
|
||||||
|
// 正则匹配路径
|
||||||
|
if (ignores.stream().anyMatch(ignore -> path.matches(ignore.toString()))) {
|
||||||
|
// 如果匹配到忽略的路径,则不进行限流
|
||||||
|
doNext(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
RateLimiter.checkRateLimit(ctx.request())
|
||||||
|
.onSuccess(v -> {
|
||||||
|
// 继续执行下一个拦截器
|
||||||
|
doNext(ctx);
|
||||||
|
})
|
||||||
|
.onFailure(t -> {
|
||||||
|
// 限流失败,返回错误响应
|
||||||
|
log.warn("Rate limit exceeded for path: {}", path);
|
||||||
|
ctx.response().putHeader(CONTENT_TYPE, "text/html; charset=utf-8")
|
||||||
|
.setStatusCode(429)
|
||||||
|
.end(t.getMessage());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package cn.qaiu.lz.common.interceptorImpl;
|
||||||
|
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.Promise;
|
||||||
|
import io.vertx.core.http.HttpServerRequest;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class RateLimiter {
|
||||||
|
|
||||||
|
private static final Map<String, RequestInfo> ipRequestMap = new ConcurrentHashMap<>();
|
||||||
|
private static int MAX_REQUESTS = 10; // 最大请求次数
|
||||||
|
private static long TIME_WINDOW = 60 * 1000; // 时间窗口(毫秒)
|
||||||
|
|
||||||
|
private static String PATH_REG; // 限流路径正则
|
||||||
|
|
||||||
|
public static void init(JsonObject rateLimitConfig) {
|
||||||
|
MAX_REQUESTS = rateLimitConfig.getInteger("limit", 10);
|
||||||
|
TIME_WINDOW = rateLimitConfig.getInteger("timeWindow", 60) * 1000L; // 转换为毫秒
|
||||||
|
PATH_REG = rateLimitConfig.getString("pathReg", "/.*");
|
||||||
|
log.info("RateLimiter initialized with max requests: {}, time window: {} ms, path regex: {}",
|
||||||
|
MAX_REQUESTS, TIME_WINDOW, PATH_REG);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public static Future<Void> checkRateLimit(HttpServerRequest request) {
|
||||||
|
Promise<Void> promise = Promise.promise();
|
||||||
|
if (!request.path().matches(PATH_REG)) {
|
||||||
|
// 如果请求路径不匹配正则,则不进行限流
|
||||||
|
promise.complete();
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
String ip = request.remoteAddress().host();
|
||||||
|
|
||||||
|
ipRequestMap.compute(ip, (key, requestInfo) -> {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
if (requestInfo == null || currentTime - requestInfo.timestamp > TIME_WINDOW) {
|
||||||
|
// 初始化或重置计数器
|
||||||
|
return new RequestInfo(1, currentTime);
|
||||||
|
} else {
|
||||||
|
// 增加计数器
|
||||||
|
requestInfo.count++;
|
||||||
|
return requestInfo;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
RequestInfo info = ipRequestMap.get(ip);
|
||||||
|
if (info.count > MAX_REQUESTS) {
|
||||||
|
// 超过限制
|
||||||
|
// 计算剩余时间
|
||||||
|
long remainingTime = TIME_WINDOW - (System.currentTimeMillis() - info.timestamp);
|
||||||
|
BigDecimal bigDecimal = BigDecimal.valueOf(remainingTime / 1000.0)
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
promise.fail("请求次数太多了,请" + bigDecimal + "秒后再试。");
|
||||||
|
} else {
|
||||||
|
// 未超过限制,继续处理
|
||||||
|
promise.complete();
|
||||||
|
}
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RequestInfo {
|
||||||
|
int count;
|
||||||
|
long timestamp;
|
||||||
|
|
||||||
|
RequestInfo(int count, long time) {
|
||||||
|
this.count = count;
|
||||||
|
this.timestamp = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package cn.qaiu.lz.web.controller;
|
package cn.qaiu.lz.web.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.qaiu.entity.FileInfo;
|
||||||
import cn.qaiu.entity.ShareLinkInfo;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import cn.qaiu.lz.common.cache.CacheManager;
|
import cn.qaiu.lz.common.cache.CacheManager;
|
||||||
import cn.qaiu.lz.common.util.URLParamUtil;
|
import cn.qaiu.lz.common.util.URLParamUtil;
|
||||||
@@ -15,17 +16,17 @@ import cn.qaiu.vx.core.annotaions.RouteHandler;
|
|||||||
import cn.qaiu.vx.core.annotaions.RouteMapping;
|
import cn.qaiu.vx.core.annotaions.RouteMapping;
|
||||||
import cn.qaiu.vx.core.enums.RouteMethod;
|
import cn.qaiu.vx.core.enums.RouteMethod;
|
||||||
import cn.qaiu.vx.core.util.AsyncServiceUtil;
|
import cn.qaiu.vx.core.util.AsyncServiceUtil;
|
||||||
|
import cn.qaiu.vx.core.util.ResponseUtil;
|
||||||
import cn.qaiu.vx.core.util.SharedDataUtil;
|
import cn.qaiu.vx.core.util.SharedDataUtil;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.Promise;
|
import io.vertx.core.Promise;
|
||||||
import io.vertx.core.http.HttpServerRequest;
|
import io.vertx.core.http.HttpServerRequest;
|
||||||
|
import io.vertx.core.http.HttpServerResponse;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RouteHandler(value = "/v2", order = 10)
|
@RouteHandler(value = "/v2", order = 10)
|
||||||
@@ -35,7 +36,7 @@ public class ParserApi {
|
|||||||
private final UserService userService = AsyncServiceUtil.getAsyncServiceInstance(UserService.class);
|
private final UserService userService = AsyncServiceUtil.getAsyncServiceInstance(UserService.class);
|
||||||
private final DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
|
private final DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
|
||||||
|
|
||||||
@RouteMapping(value = "/login", method = RouteMethod.POST)
|
@RouteMapping(value = "/login", method = RouteMethod.GET)
|
||||||
public Future<SysUser> login(SysUser user) {
|
public Future<SysUser> login(SysUser user) {
|
||||||
log.info("<------- login: {}", user.getUsername());
|
log.info("<------- login: {}", user.getUsername());
|
||||||
return userService.login(user);
|
return userService.login(user);
|
||||||
@@ -93,8 +94,50 @@ public class ParserApi {
|
|||||||
return Arrays.stream(PanDomainTemplate.values()).map(pan -> new TreeMap<String, String>() {{
|
return Arrays.stream(PanDomainTemplate.values()).map(pan -> new TreeMap<String, String>() {{
|
||||||
put("name", pan.getDisplayName());
|
put("name", pan.getDisplayName());
|
||||||
put("type", pan.name().toLowerCase());
|
put("type", pan.name().toLowerCase());
|
||||||
|
put("shareUrlFormat", pan.getStandardUrlTemplate());
|
||||||
put("url", pan.getPanDomain());
|
put("url", pan.getPanDomain());
|
||||||
}}).collect(Collectors.toList());
|
}}).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RouteMapping("/getFileList")
|
||||||
|
public Future<List<FileInfo>> getFileList(HttpServerRequest request, String pwd) {
|
||||||
|
String url = URLParamUtil.parserParams(request);
|
||||||
|
ParserCreate parserCreate = ParserCreate.fromShareUrl(url).setShareLinkInfoPwd(pwd);
|
||||||
|
String linkPrefix = SharedDataUtil.getJsonConfig("server").getString("domainName");
|
||||||
|
parserCreate.getShareLinkInfo().getOtherParam().put("domainName", linkPrefix);
|
||||||
|
return parserCreate.createTool().parseFileList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 目录解析下载文件
|
||||||
|
// @RouteMapping("/getFileDownUrl/:type/:param")
|
||||||
|
public Future<String> getFileDownUrl(String type, String param) {
|
||||||
|
ParserCreate parserCreate = ParserCreate.fromType(type).shareKey("-") // shareKey not null
|
||||||
|
.setShareLinkInfoPwd("-");
|
||||||
|
|
||||||
|
if (param.isEmpty()) {
|
||||||
|
Promise<String> promise = Promise.promise();
|
||||||
|
promise.fail("下载参数为空");
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
String paramStr = new String(Base64.getDecoder().decode(param));
|
||||||
|
ShareLinkInfo shareLinkInfo = parserCreate.getShareLinkInfo();
|
||||||
|
shareLinkInfo.getOtherParam().put("paramJson", new JsonObject(paramStr));
|
||||||
|
|
||||||
|
// domainName
|
||||||
|
String linkPrefix = SharedDataUtil.getJsonConfig("server").getString("domainName");
|
||||||
|
shareLinkInfo.getOtherParam().put("domainName", linkPrefix);
|
||||||
|
return parserCreate.createTool().parseById();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@RouteMapping("/redirectUrl/:type/:param")
|
||||||
|
public Future<Void> redirectUrl(HttpServerResponse response, String type, String param) {
|
||||||
|
Promise<Void> promise = Promise.promise();
|
||||||
|
|
||||||
|
getFileDownUrl(type, param)
|
||||||
|
.onSuccess(res -> ResponseUtil.redirect(response, res))
|
||||||
|
.onFailure(t -> promise.fail(t.fillInStackTrace()));
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package cn.qaiu.lz.web.controller;
|
||||||
|
|
||||||
|
import cn.qaiu.lz.web.service.ShoutService;
|
||||||
|
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 io.vertx.core.Future;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
import io.vertx.ext.web.RoutingContext;
|
||||||
|
|
||||||
|
@RouteHandler("/v2/shout")
|
||||||
|
public class ShoutController {
|
||||||
|
|
||||||
|
private final ShoutService shoutService = AsyncServiceUtil.getAsyncServiceInstance(ShoutService.class);
|
||||||
|
|
||||||
|
@RouteMapping(value = "/submit", method = RouteMethod.POST)
|
||||||
|
public Future<JsonObject> submitMessage(RoutingContext ctx) {
|
||||||
|
String content = ctx.body().asJsonObject().getString("content");
|
||||||
|
if (content == null || content.trim().isEmpty()) {
|
||||||
|
return Future.failedFuture("内容不能为空");
|
||||||
|
}
|
||||||
|
return shoutService.submitMessage(content, ctx.request().remoteAddress().host()).compose(code ->
|
||||||
|
Future.succeededFuture(JsonResult.data(code).toJsonObject()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@RouteMapping(value = "/retrieve", method = RouteMethod.GET)
|
||||||
|
public Future<JsonObject> retrieveMessage(RoutingContext ctx) {
|
||||||
|
String code = ctx.request().getParam("code");
|
||||||
|
if (code == null || code.length() != 6) {
|
||||||
|
return Future.failedFuture("提取码必须为6位数字");
|
||||||
|
}
|
||||||
|
return shoutService.retrieveMessage(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,13 +21,13 @@ public class ApiStatisticsInfo implements ToJson {
|
|||||||
/**
|
/**
|
||||||
* pan type 单独拿出来便于统计.
|
* pan type 单独拿出来便于统计.
|
||||||
*/
|
*/
|
||||||
@Length(varcharSize = 4)
|
@Length(varcharSize = 16)
|
||||||
private String panType;
|
private String panType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分享key type:key
|
* 分享key type:key
|
||||||
*/
|
*/
|
||||||
@Length(varcharSize = 4096)
|
@Length(varcharSize = 1024)
|
||||||
private String shareKey;
|
private String shareKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package cn.qaiu.lz.web.model;
|
|||||||
import cn.qaiu.db.ddl.Length;
|
import cn.qaiu.db.ddl.Length;
|
||||||
import cn.qaiu.db.ddl.Table;
|
import cn.qaiu.db.ddl.Table;
|
||||||
import cn.qaiu.db.ddl.TableGenIgnore;
|
import cn.qaiu.db.ddl.TableGenIgnore;
|
||||||
|
import cn.qaiu.entity.FileInfo;
|
||||||
import cn.qaiu.lz.common.ToJson;
|
import cn.qaiu.lz.common.ToJson;
|
||||||
import io.vertx.codegen.annotations.DataObject;
|
import io.vertx.codegen.annotations.DataObject;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
@@ -22,7 +23,7 @@ public class CacheLinkInfo implements ToJson {
|
|||||||
/**
|
/**
|
||||||
* 缓存key: type:ShareKey; e.g. lz:xxxx
|
* 缓存key: type:ShareKey; e.g. lz:xxxx
|
||||||
*/
|
*/
|
||||||
@Length(varcharSize = 4096)
|
@Length(varcharSize = 1024)
|
||||||
private String shareKey;
|
private String shareKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,6 +49,8 @@ public class CacheLinkInfo implements ToJson {
|
|||||||
*/
|
*/
|
||||||
private Long expiration;
|
private Long expiration;
|
||||||
|
|
||||||
|
private FileInfo fileInfo;
|
||||||
|
|
||||||
|
|
||||||
// 使用 JsonObject 构造
|
// 使用 JsonObject 构造
|
||||||
public CacheLinkInfo(JsonObject json) {
|
public CacheLinkInfo(JsonObject json) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public class LinkInfoResp {
|
|||||||
// 解析链接
|
// 解析链接
|
||||||
private String downLink;
|
private String downLink;
|
||||||
private String apiLink;
|
private String apiLink;
|
||||||
|
private String viewLink;
|
||||||
private Integer cacheHitTotal;
|
private Integer cacheHitTotal;
|
||||||
private Integer parserTotal;
|
private Integer parserTotal;
|
||||||
private Integer sumTotal;
|
private Integer sumTotal;
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package cn.qaiu.lz.web.model;
|
||||||
|
|
||||||
|
import cn.qaiu.db.ddl.Constraint;
|
||||||
|
import cn.qaiu.db.ddl.Length;
|
||||||
|
import cn.qaiu.db.ddl.Table;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隔空喊话消息
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Table("t_messages")
|
||||||
|
public class ShoutMessage {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Constraint(autoIncrement= true, notNull = true)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Length(varcharSize = 16)
|
||||||
|
@Constraint(notNull = true, uniqueKey = "uk_code")
|
||||||
|
private String code; // 6位提取码
|
||||||
|
|
||||||
|
@Length(varcharSize = 4096)
|
||||||
|
private String content; // 消息内容
|
||||||
|
|
||||||
|
@Length(varcharSize = 32)
|
||||||
|
private String ip; // 发送者IP
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
|
private Date createTime = new Date(); // 创建时间
|
||||||
|
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
|
private Date expireTime; // 过期时间
|
||||||
|
|
||||||
|
private Boolean isUsed = false; // 是否已使用
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package cn.qaiu.lz.web.service;
|
||||||
|
|
||||||
|
import cn.qaiu.vx.core.base.BaseAsyncService;
|
||||||
|
import io.vertx.codegen.annotations.ProxyGen;
|
||||||
|
import io.vertx.core.Future;
|
||||||
|
import io.vertx.core.json.JsonObject;
|
||||||
|
|
||||||
|
@ProxyGen
|
||||||
|
public interface ShoutService extends BaseAsyncService {
|
||||||
|
// 提交消息并返回提取码
|
||||||
|
Future<String> submitMessage(String content, String host);
|
||||||
|
|
||||||
|
// 通过提取码获取消息
|
||||||
|
Future<JsonObject> retrieveMessage(String code);
|
||||||
|
}
|
||||||
@@ -48,10 +48,11 @@ public class DbServiceImpl implements DbService {
|
|||||||
JDBCPool client = JDBCPoolInit.instance().getPool();
|
JDBCPool client = JDBCPoolInit.instance().getPool();
|
||||||
Promise<StatisticsInfo> promise = Promise.promise();
|
Promise<StatisticsInfo> promise = Promise.promise();
|
||||||
String sql = """
|
String sql = """
|
||||||
select sum(api_parser_total) parserTotal,sum("cache_hit_total") cacheTotal,
|
select sum(api_parser_total) as parserTotal, sum(cache_hit_total) as cacheTotal,
|
||||||
sum(api_parser_total) + sum("cache_hit_total") total
|
sum(api_parser_total) + sum(cache_hit_total) as total
|
||||||
from "api_statistics_info";
|
from api_statistics_info;
|
||||||
""";
|
""";
|
||||||
|
|
||||||
SqlTemplate.forQuery(client, sql).mapTo(StatisticsInfo.class).execute(new HashMap<>()).onSuccess(row -> {
|
SqlTemplate.forQuery(client, sql).mapTo(StatisticsInfo.class).execute(new HashMap<>()).onSuccess(row -> {
|
||||||
StatisticsInfo info;
|
StatisticsInfo info;
|
||||||
if ((info = row.iterator().next()) != null) {
|
if ((info = row.iterator().next()) != null) {
|
||||||
@@ -59,7 +60,10 @@ public class DbServiceImpl implements DbService {
|
|||||||
} else {
|
} else {
|
||||||
promise.fail("t_parser_log_info查询为空");
|
promise.fail("t_parser_log_info查询为空");
|
||||||
}
|
}
|
||||||
}).onFailure(promise::fail);
|
}).onFailure(e->{
|
||||||
|
log.error("getStatisticsInfo: ", e);
|
||||||
|
promise.fail(e);
|
||||||
|
});
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package cn.qaiu.lz.web.service.impl;
|
||||||
|
|
||||||
|
import cn.qaiu.db.pool.JDBCPoolInit;
|
||||||
|
import cn.qaiu.lz.web.service.ShoutService;
|
||||||
|
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.Tuple;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@SuppressWarnings("SqlResolve") // 这里是为了避免检查SQL语句的警告
|
||||||
|
public class ShoutServiceImpl implements ShoutService {
|
||||||
|
private static final int CODE_LENGTH = 6;
|
||||||
|
private static final int EXPIRE_HOURS = 24;
|
||||||
|
private final JDBCPool jdbcPool = JDBCPoolInit.instance().getPool();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<String> submitMessage(String content, String host) {
|
||||||
|
Promise<String> promise = Promise.promise();
|
||||||
|
String code = generateRandomCode();
|
||||||
|
// 判断一下当前code是否存在消息
|
||||||
|
LocalDateTime expireTime = LocalDateTime.now().plusHours(EXPIRE_HOURS);
|
||||||
|
|
||||||
|
String sql = "INSERT INTO t_messages (code, content, expire_time, ip) VALUES (?, ?, ?, ?)";
|
||||||
|
|
||||||
|
jdbcPool.preparedQuery(sql)
|
||||||
|
.execute(Tuple.of(code, content,
|
||||||
|
java.sql.Timestamp.from(expireTime.atZone(ZoneId.systemDefault()).toInstant()),
|
||||||
|
host))
|
||||||
|
.onSuccess(res -> {
|
||||||
|
log.info("Message submitted with code: {}", code);
|
||||||
|
promise.complete(code);
|
||||||
|
})
|
||||||
|
.onFailure(err -> {
|
||||||
|
log.error("Failed to submit message", err);
|
||||||
|
promise.fail(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<JsonObject> retrieveMessage(String code) {
|
||||||
|
Promise<JsonObject> promise = Promise.promise();
|
||||||
|
|
||||||
|
String sql = "SELECT content FROM t_messages WHERE code = ? AND expire_time > NOW()";
|
||||||
|
|
||||||
|
jdbcPool.preparedQuery(sql)
|
||||||
|
.execute(Tuple.of(code))
|
||||||
|
.onSuccess(rows -> {
|
||||||
|
if (rows.size() > 0) {
|
||||||
|
String content = rows.iterator().next().getString("content");
|
||||||
|
// 标记为已使用
|
||||||
|
markAsUsed(code);
|
||||||
|
promise.complete(JsonResult.data(content).toJsonObject());
|
||||||
|
} else {
|
||||||
|
promise.fail("无效的提取码或消息已过期");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onFailure(err -> {
|
||||||
|
log.error("Failed to retrieve message", err);
|
||||||
|
promise.fail(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markAsUsed(String code) {
|
||||||
|
String sql = "UPDATE t_messages SET is_used = TRUE WHERE code = ?";
|
||||||
|
jdbcPool.preparedQuery(sql).execute(Tuple.of(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateRandomCode() {
|
||||||
|
Random random = new Random();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < CODE_LENGTH; i++) {
|
||||||
|
sb.append(random.nextInt(10));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,11 +19,11 @@ public class UserServiceImpl implements UserService {
|
|||||||
@Override
|
@Override
|
||||||
public Future<SysUser> login(SysUser user) {
|
public Future<SysUser> login(SysUser user) {
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
TimeUnit.SECONDS.sleep(2);
|
// TimeUnit.SECONDS.sleep(2);
|
||||||
} catch (InterruptedException e) {
|
// } catch (InterruptedException e) {
|
||||||
throw new RuntimeException(e);
|
// throw new RuntimeException(e);
|
||||||
}
|
// }
|
||||||
return Future.succeededFuture(user);
|
return Future.succeededFuture(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ server:
|
|||||||
enableDatabase: true
|
enableDatabase: true
|
||||||
# 服务域名或者IP 生成二维码链接时需要
|
# 服务域名或者IP 生成二维码链接时需要
|
||||||
domainName: http://127.0.0.1:6401
|
domainName: http://127.0.0.1:6401
|
||||||
|
# domainName: https://lz.qaiu.top
|
||||||
|
|
||||||
# 反向代理服务器配置路径(不用加后缀)
|
# 反向代理服务器配置路径(不用加后缀)
|
||||||
proxyConf: server-proxy
|
proxyConf: server-proxy
|
||||||
@@ -25,19 +26,33 @@ custom:
|
|||||||
routeTimeOut: 15000
|
routeTimeOut: 15000
|
||||||
# 拦截器匹配规则
|
# 拦截器匹配规则
|
||||||
ignoresReg:
|
ignoresReg:
|
||||||
- /v2/statisticsInfo
|
# - /v2/statisticsInfo
|
||||||
- .*/test.*$
|
- .*/test.*$
|
||||||
|
|
||||||
# 参数注入的实体类包路径匹配正则 (防止同名类引发歧义)
|
# 参数注入的实体类包路径匹配正则 (防止同名类引发歧义)
|
||||||
entityPackagesReg:
|
entityPackagesReg:
|
||||||
- ^cn\.qaiu\.lz\.web\.model\..*
|
- ^cn\.qaiu\.lz\.web\.model\..*
|
||||||
|
|
||||||
|
# 限流配置
|
||||||
|
rateLimit:
|
||||||
|
# 是否启用限流
|
||||||
|
enable: true
|
||||||
|
# 限流的请求数
|
||||||
|
limit: 5
|
||||||
|
# 限流的时间窗口(单位秒)
|
||||||
|
timeWindow: 10
|
||||||
|
# 路径匹配规则
|
||||||
|
pathReg: ^/v2/.*
|
||||||
|
|
||||||
|
|
||||||
# 数据源配置
|
# 数据源配置
|
||||||
dataSource:
|
dataSource:
|
||||||
provider_class: io.vertx.ext.jdbc.spi.impl.HikariCPDataSourceProvider
|
# PGSQL配置示例
|
||||||
|
#jdbcUrl: jdbc:postgresql://localhost:5432/zzzmy
|
||||||
|
# MySQL配置示例
|
||||||
|
#jdbcUrl: jdbc:mysql://127.0.0.1:3306/nfddata?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false
|
||||||
|
# 内置数据源H2db配置
|
||||||
jdbcUrl: jdbc:h2:file:./db/nfdData;MODE=MySQL;DATABASE_TO_UPPER=FALSE
|
jdbcUrl: jdbc:h2:file:./db/nfdData;MODE=MySQL;DATABASE_TO_UPPER=FALSE
|
||||||
driverClassName: org.h2.Driver
|
|
||||||
username: root
|
username: root
|
||||||
password: '123456'
|
password: '123456'
|
||||||
|
|
||||||
@@ -49,21 +64,23 @@ cache:
|
|||||||
defaultDuration: 59
|
defaultDuration: 59
|
||||||
# 具体网盘的缓存配置,如果不加配置则不缓存,每次请求都会请求网盘API,格式:网盘标识: 时长
|
# 具体网盘的缓存配置,如果不加配置则不缓存,每次请求都会请求网盘API,格式:网盘标识: 时长
|
||||||
duration:
|
duration:
|
||||||
ce:
|
ce: 5
|
||||||
cow:
|
cow: 5
|
||||||
ec:
|
ec: 5
|
||||||
fc:
|
fc:
|
||||||
fj:
|
fj: 20
|
||||||
iz: 20
|
iz: 20
|
||||||
le: 2879
|
le: 2879
|
||||||
lz: 20
|
lz: 20
|
||||||
qq: 9999999
|
qq: 9999999
|
||||||
ws:
|
qqw: 30
|
||||||
ye:
|
ws: 10
|
||||||
|
ye: -1
|
||||||
mne: 30
|
mne: 30
|
||||||
mqq: 30
|
mqq: 30
|
||||||
mkg: 30
|
mkg: 30
|
||||||
p115: 5
|
p115: 30
|
||||||
|
ct: 30
|
||||||
|
|
||||||
# httpClient静态代理服务器配置(外网代理)
|
# httpClient静态代理服务器配置(外网代理)
|
||||||
proxy:
|
proxy:
|
||||||
|
|||||||
@@ -2,9 +2,18 @@
|
|||||||
|
|
||||||
### 直链 Referer 有效期似乎是永久 at=1717958244333 => 2024/6/10 2:37:24
|
### 直链 Referer 有效期似乎是永久 at=1717958244333 => 2024/6/10 2:37:24
|
||||||
https://d0.ananas.chaoxing.com/download/8e8c9baca640d24680d974331390a158?at_=1717958244333&ak_=783925f9ba6eb2d0c711977b777a13e0&ad_=58ffecd38be494bea68f0cda68b18c0a&fn=testgles.c
|
https://d0.ananas.chaoxing.com/download/8e8c9baca640d24680d974331390a158?at_=1717958244333&ak_=783925f9ba6eb2d0c711977b777a13e0&ad_=58ffecd38be494bea68f0cda68b18c0a&fn=testgles.c
|
||||||
Referer: https://pan-yz.chaoxing.com/external/m/file/1006748113111711744
|
Referer: https://pan-yz.chaoxing.com/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
|
https://d0.ananas.chaoxing.com/download/ecf45b6d85a7e9d9d5f5ef33978c6f74?at_=1731411454916&ak_=12108e79795e73c6df0a75f5ea60fe2c&ad_=b6c289da3c6f6734b2262e8577364213
|
||||||
|
Referer: https://pan-yz.chaoxing.com/external/m/file/1006748113111711744
|
||||||
|
|
||||||
|
###
|
||||||
|
https://d0.ananas.chaoxing.com/download/3fe3ba9f6f2d1e9926b924c8268ca67d?at_=1740211468281&ak_=cc853b465206569f98a257674f8595a4&ad_=b5c27a9d86c298abc11220290a7dfde9&fn=wx_camera_1705290447091.jpg
|
||||||
|
Referer: https://pan-yz.chaoxing.com/
|
||||||
|
|
||||||
|
###
|
||||||
|
https://d0.ananas.chaoxing.com/download/8664a0b97110b49ca9e3ad024544649e?at_=1740211468281&ak_=cc853b465206569f98a257674f8595a4&ad_=b5c27a9d86c298abc11220290a7dfde9&fn=jdk17-jre-win-amd64.zip
|
||||||
|
Referer: https://pan-yz.chaoxing.com/
|
||||||
|
|||||||
@@ -25,3 +25,4 @@ https://api.ilanzou.com/unproved/file/redirect?uuid=0&devType=6×tamp=453240
|
|||||||
https://www.ilanzou.com/s/zHkna1S
|
https://www.ilanzou.com/s/zHkna1S
|
||||||
|
|
||||||
### fileId: 145042258
|
### fileId: 145042258
|
||||||
|
https://api.ilanzou.com/unproved/recommend/list?devType=6&devModel=Chrome&uuid=AjiM-Wl782OuHyuvKiuaH&extra=2×tamp=311D0B438400DB5D98F1D2FF5A7A86F1&shareId=jQ9i3F0&type=0&offset=1&limit=60
|
||||||
|
|||||||
@@ -51,3 +51,111 @@ Cookie: __pugs=efc5f3f9c041af5dc62eea4481901cbbAAT912628i+uT/WMwOFWBjJ1TjbKGC1j6
|
|||||||
https://dl-uf-zb.pds.uc.cn/l3PNAKfz/64623447/646b0de6e9f13000c9b14ba182b805312795a82a/646b0de6717e1bfa5bb44dd2a456f103c5177850?Expires=1691489999&OSSAccessKeyId=LTAIyYfxTqY7YZsg&Signature=NbQLAEUCkvJxmSsRoIynZ%2BuPMvY%3D&x-oss-traffic-limit=503316480&response-content-disposition=attachment%3B%20filename%3DC%23%20Shell%20%28C%23%20Offline%20Compiler%29_2.5.16.apks&callback-var=eyJ4OmF1IjoiLSIsIng6c3AiOiIxOTkiLCJ4OnRva2VuIjoiMi0wNDBjYjFjMDNjNzU1YWY1NDc0NjkxNjNmOTYzYWY2NC0yLTctNjE0NDAtZGFjYjM2NjViYmFhNGY1ZTlkMzc4MDBlYzY0MDMxNjAtYTU2MGJiMmU1MzhlNzY0OTFkMDY1MjA2OGRiNmEzMzEiLCJ4OnR0bCI6IjEwODAwIn0%3D&callback=eyJjYWxsYmFja0JvZHlUeXBlIjoiYXBwbGljYXRpb24vanNvbiIsImNhbGxiYWNrU3RhZ2UiOiJiZWZvcmUtZXhlY3V0ZSIsImNhbGxiYWNrRmFpbHVyZUFjdGlvbiI6Imlnbm9yZSIsImNhbGxiYWNrVXJsIjoiaHR0cHM6Ly9hdXRoLWNkbi51Yy5jbi9vdXRlci9vc3MvY2hlY2twbGF5IiwiY2FsbGJhY2tCb2R5Ijoie1wiaG9zdFwiOiR7aHR0cEhlYWRlci5ob3N0fSxcInNpemVcIjoke3NpemV9LFwicmFuZ2VcIjoke2h0dHBIZWFkZXIucmFuZ2V9LFwicmVmZXJlclwiOiR7aHR0cEhlYWRlci5yZWZlcmVyfSxcImNvb2tpZVwiOiR7aHR0cEhlYWRlci5jb29raWV9LFwibWV0aG9kXCI6JHtodHRwSGVhZGVyLm1ldGhvZH0sXCJpcFwiOiR7Y2xpZW50SXB9LFwicG9ydFwiOiR7Y2xpZW50UG9ydH0sXCJvYmplY3RcIjoke29iamVjdH0sXCJzcFwiOiR7eDpzcH0sXCJ0b2tlblwiOiR7eDp0b2tlbn0sXCJhdVwiOiR7eDphdX0sXCJ0dGxcIjoke3g6dHRsfSxcImNsaWVudF90b2tlblwiOiR7cXVlcnlTdHJpbmcuY2xpZW50X3Rva2VufX0ifQ%3D%3D&ud=4-0-5-0-6-N-3-ft-0-2
|
https://dl-uf-zb.pds.uc.cn/l3PNAKfz/64623447/646b0de6e9f13000c9b14ba182b805312795a82a/646b0de6717e1bfa5bb44dd2a456f103c5177850?Expires=1691489999&OSSAccessKeyId=LTAIyYfxTqY7YZsg&Signature=NbQLAEUCkvJxmSsRoIynZ%2BuPMvY%3D&x-oss-traffic-limit=503316480&response-content-disposition=attachment%3B%20filename%3DC%23%20Shell%20%28C%23%20Offline%20Compiler%29_2.5.16.apks&callback-var=eyJ4OmF1IjoiLSIsIng6c3AiOiIxOTkiLCJ4OnRva2VuIjoiMi0wNDBjYjFjMDNjNzU1YWY1NDc0NjkxNjNmOTYzYWY2NC0yLTctNjE0NDAtZGFjYjM2NjViYmFhNGY1ZTlkMzc4MDBlYzY0MDMxNjAtYTU2MGJiMmU1MzhlNzY0OTFkMDY1MjA2OGRiNmEzMzEiLCJ4OnR0bCI6IjEwODAwIn0%3D&callback=eyJjYWxsYmFja0JvZHlUeXBlIjoiYXBwbGljYXRpb24vanNvbiIsImNhbGxiYWNrU3RhZ2UiOiJiZWZvcmUtZXhlY3V0ZSIsImNhbGxiYWNrRmFpbHVyZUFjdGlvbiI6Imlnbm9yZSIsImNhbGxiYWNrVXJsIjoiaHR0cHM6Ly9hdXRoLWNkbi51Yy5jbi9vdXRlci9vc3MvY2hlY2twbGF5IiwiY2FsbGJhY2tCb2R5Ijoie1wiaG9zdFwiOiR7aHR0cEhlYWRlci5ob3N0fSxcInNpemVcIjoke3NpemV9LFwicmFuZ2VcIjoke2h0dHBIZWFkZXIucmFuZ2V9LFwicmVmZXJlclwiOiR7aHR0cEhlYWRlci5yZWZlcmVyfSxcImNvb2tpZVwiOiR7aHR0cEhlYWRlci5jb29raWV9LFwibWV0aG9kXCI6JHtodHRwSGVhZGVyLm1ldGhvZH0sXCJpcFwiOiR7Y2xpZW50SXB9LFwicG9ydFwiOiR7Y2xpZW50UG9ydH0sXCJvYmplY3RcIjoke29iamVjdH0sXCJzcFwiOiR7eDpzcH0sXCJ0b2tlblwiOiR7eDp0b2tlbn0sXCJhdVwiOiR7eDphdX0sXCJ0dGxcIjoke3g6dHRsfSxcImNsaWVudF90b2tlblwiOiR7cXVlcnlTdHJpbmcuY2xpZW50X3Rva2VufX0ifQ%3D%3D&ud=4-0-5-0-6-N-3-ft-0-2
|
||||||
Cookie: __puus=dc48cb12577eb3df6fe84fdea250ad6fAAOF0LBv/M4HTtkYfuUNdcVLXHZl1x2mw8NQSdxo5abymS+irugphlPNv5kwQZkDI+pXaeOD22v/whNQT5AwUULF0q1nSNXmHqxr20AJjXlEhvbIZNgUfwmw8aOCyarrLi7o7w6w0Rod4DLCSeYGwlTF3P9jMcqCM+WqWHnxKY6i8gaXZkHLObatSHkwivB7Xpc=
|
Cookie: __puus=dc48cb12577eb3df6fe84fdea250ad6fAAOF0LBv/M4HTtkYfuUNdcVLXHZl1x2mw8NQSdxo5abymS+irugphlPNv5kwQZkDI+pXaeOD22v/whNQT5AwUULF0q1nSNXmHqxr20AJjXlEhvbIZNgUfwmw8aOCyarrLi7o7w6w0Rod4DLCSeYGwlTF3P9jMcqCM+WqWHnxKY6i8gaXZkHLObatSHkwivB7Xpc=
|
||||||
Referer: https://fast.uc.cn/
|
Referer: https://fast.uc.cn/
|
||||||
|
###
|
||||||
|
# curl 'https://dl-uf-zb.pds.uc.cn/l3PNAKfz/64623447/646b0de6e9f13000c9b14ba182b805312795a82a/646b0de6717e1bfa5bb44dd2a456f103c5177850?Expires=1737791001&OSSAccessKeyId=LTAI5tJJpWQEfrcKHnd1LqsZ&Signature=n7Q7BNK5Z1rFS0nDgQZxbBBpqqE%3D&x-oss-traffic-limit=503316480&response-content-disposition=attachment%3B%20filename%3DC%2523%2520Shell%2520%2528C%2523%2520Offline%2520Compiler%2529_2.5.16.apks%3Bfilename%2A%3Dutf-8%27%27C%2523%2520Shell%2520%2528C%2523%2520Offline%2520Compiler%2529_2.5.16.apks&callback-var=eyJ4OmF1IjoiLSIsIng6dWQiOiI0LTAtNS0wLTYtTi0zLWZ0LTAtMi0wLU4iLCJ4OnNwIjoiMTAwIiwieDp0b2tlbiI6IjQtYjZlMGU5ZWUyMWFkOWZlZGIzNjMwMGY4ZWYyMWU5MjUtMi03LTE1MzYxMS1kYWNiMzY2NWJiYWE0ZjVlOWQzNzgwMGVjNjQwMzE2MC0wLTAtMC0wLTYzYmFhOGEzZGFlZGNlYzA5MmNlNzhkMDgyMmFlMjQ4IiwieDp0dGwiOiIxMDgwMCJ9&callback=eyJjYWxsYmFja0JvZHlUeXBlIjoiYXBwbGljYXRpb24vanNvbiIsImNhbGxiYWNrU3RhZ2UiOiJiZWZvcmUtZXhlY3V0ZSIsImNhbGxiYWNrRmFpbHVyZUFjdGlvbiI6Imlnbm9yZSIsImNhbGxiYWNrVXJsIjoiaHR0cHM6Ly9hdXRoLWNkbi51Yy5jbi9vdXRlci9vc3MvY2hlY2twbGF5IiwiY2FsbGJhY2tCb2R5Ijoie1wiaG9zdFwiOiR7aHR0cEhlYWRlci5ob3N0fSxcInNpemVcIjoke3NpemV9LFwicmFuZ2VcIjoke2h0dHBIZWFkZXIucmFuZ2V9LFwicmVmZXJlclwiOiR7aHR0cEhlYWRlci5yZWZlcmVyfSxcImNvb2tpZVwiOiR7aHR0cEhlYWRlci5jb29raWV9LFwibWV0aG9kXCI6JHtodHRwSGVhZGVyLm1ldGhvZH0sXCJpcFwiOiR7Y2xpZW50SXB9LFwicG9ydFwiOiR7Y2xpZW50UG9ydH0sXCJvYmplY3RcIjoke29iamVjdH0sXCJzcFwiOiR7eDpzcH0sXCJ1ZFwiOiR7eDp1ZH0sXCJ0b2tlblwiOiR7eDp0b2tlbn0sXCJhdVwiOiR7eDphdX0sXCJ0dGxcIjoke3g6dHRsfSxcImR0X3NwXCI6JHt4OmR0X3NwfSxcImhzcFwiOiR7eDpoc3B9LFwiY2xpZW50X3Rva2VuXCI6JHtxdWVyeVN0cmluZy5jbGllbnRfdG9rZW59fSJ9&ud=4-0-5-0-6-N-3-ft-0-2-0-N'
|
||||||
|
# -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7'
|
||||||
|
# -H 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8'
|
||||||
|
# -H 'Cache-Control: no-cache'
|
||||||
|
# -H 'Connection: keep-alive'
|
||||||
|
# -H 'Cookie: _UP_28A_52_=519; _UP_A4A_11_=wb9c5167d67c42ba9444ac9f42b20a05; _UP_D_=pc; __sdid=AARkEnYbifr7X5obz/wd2gzbXPw1zviyFe4xBC3uVp7xQG20ziCSbbxMib8Fov3RPRk=; __pus=9db24ba75335a2deb2e99fc400ac59ffAAQ9T+W6s02ilSvzMeh5ionWLTKFfaa2QFvJNU1PoiCYQe0aunWWq5GyJCVzXrVuICNHxooijrybFKlkUoI7xCfP; __kp=aaaeba0e-8c8e-4837-869f-882283adb802; __kps=AASxYmDMULu4nzmEK/wFzK3I; __ktd=dvy3qySVr8aXEqUuxMJydA==; __uid=AASxYmDMULu4nzmEK/wFzK3I; tfstk=gJ-IibMk8WVC79FMKXHNG51AEQSSgQi2v86JnLEUeMIdVLdF_65rKL8_2QCwYaSKtg1R6Q4UY_SeVCOMsBJeU3TgVKvkYvjrKMvhqghqgmSWKpjooOET3fJteTj5pUMeYcJhqghZgmo2KpAxkBSuFgH1XT65pgUpwGpOsTX89TEKCdCG6_IR2QptCTfOpfAThTgCtpMB-WEBJUCvp1Z8V-XCJsnc1uEJf9NVMp302uK1dwtlqg1VZw8JnZxeQ0ZGYKTOX9O-DXQMJdt5o30zYpINOKb6SXZOQa1BWHQ_euphbFOyPBGQ-6Le1ZXOW8icQI59KHLsE5ppg6sdBNoxhdQJ7HbDYfECDEvFYUdtfkOA4YqVG7kLPR_02O1qCAaurBoEyuDPFR31JOXC7AM_9UbdIO1qCAaurwBGdtksCWLl.; __puus=7a473fc40c62988cd82a0e443bb2a1baAAR498zI4bjrVRD3mNor9LX8Upgw8asjZxzEjBkb4gLBZ0tfOM5jNElFhDUmiKxA9d/xwzIgTARhY8ySH0Yl8Ipm2njsKPbk3gkplByVAZ62t0TQCe0hEdprNbdImIFDM8RbnpQ5PLvMeiJMZFJyXsK9EhAWvCpp0mO7sHVVk1oxjYkuBM3jff1l6YLQaZfd3h8='
|
||||||
|
# -H 'DNT: 1'
|
||||||
|
# -H 'Pragma: no-cache'
|
||||||
|
# -H 'Referer: https://fast.uc.cn/'
|
||||||
|
# -H 'Sec-Fetch-Dest: iframe'
|
||||||
|
# -H 'Sec-Fetch-Mode: navigate'
|
||||||
|
# -H 'Sec-Fetch-Site: same-site'
|
||||||
|
# -H 'Sec-Fetch-User: ?1'
|
||||||
|
# -H 'Upgrade-Insecure-Requests: 1'
|
||||||
|
# -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36'
|
||||||
|
# -H 'sec-ch-ua: "Not A(Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"'
|
||||||
|
# -H 'sec-ch-ua-mobile: ?0'
|
||||||
|
# -H 'sec-ch-ua-platform: "Windows"'
|
||||||
|
GET https://dl-uf-zb.pds.uc.cn/l3PNAKfz/64623447/646b0de6e9f13000c9b14ba182b805312795a82a/646b0de6717e1bfa5bb44dd2a456f103c5177850?Expires=1737791001&OSSAccessKeyId=LTAI5tJJpWQEfrcKHnd1LqsZ&Signature=n7Q7BNK5Z1rFS0nDgQZxbBBpqqE%3D&x-oss-traffic-limit=503316480&response-content-disposition=attachment%3B%20filename%3DC%2523%2520Shell%2520%2528C%2523%2520Offline%2520Compiler%2529_2.5.16.apks%3Bfilename%2A%3Dutf-8%27%27C%2523%2520Shell%2520%2528C%2523%2520Offline%2520Compiler%2529_2.5.16.apks&callback-var=eyJ4OmF1IjoiLSIsIng6dWQiOiI0LTAtNS0wLTYtTi0zLWZ0LTAtMi0wLU4iLCJ4OnNwIjoiMTAwIiwieDp0b2tlbiI6IjQtYjZlMGU5ZWUyMWFkOWZlZGIzNjMwMGY4ZWYyMWU5MjUtMi03LTE1MzYxMS1kYWNiMzY2NWJiYWE0ZjVlOWQzNzgwMGVjNjQwMzE2MC0wLTAtMC0wLTYzYmFhOGEzZGFlZGNlYzA5MmNlNzhkMDgyMmFlMjQ4IiwieDp0dGwiOiIxMDgwMCJ9&callback=eyJjYWxsYmFja0JvZHlUeXBlIjoiYXBwbGljYXRpb24vanNvbiIsImNhbGxiYWNrU3RhZ2UiOiJiZWZvcmUtZXhlY3V0ZSIsImNhbGxiYWNrRmFpbHVyZUFjdGlvbiI6Imlnbm9yZSIsImNhbGxiYWNrVXJsIjoiaHR0cHM6Ly9hdXRoLWNkbi51Yy5jbi9vdXRlci9vc3MvY2hlY2twbGF5IiwiY2FsbGJhY2tCb2R5Ijoie1wiaG9zdFwiOiR7aHR0cEhlYWRlci5ob3N0fSxcInNpemVcIjoke3NpemV9LFwicmFuZ2VcIjoke2h0dHBIZWFkZXIucmFuZ2V9LFwicmVmZXJlclwiOiR7aHR0cEhlYWRlci5yZWZlcmVyfSxcImNvb2tpZVwiOiR7aHR0cEhlYWRlci5jb29raWV9LFwibWV0aG9kXCI6JHtodHRwSGVhZGVyLm1ldGhvZH0sXCJpcFwiOiR7Y2xpZW50SXB9LFwicG9ydFwiOiR7Y2xpZW50UG9ydH0sXCJvYmplY3RcIjoke29iamVjdH0sXCJzcFwiOiR7eDpzcH0sXCJ1ZFwiOiR7eDp1ZH0sXCJ0b2tlblwiOiR7eDp0b2tlbn0sXCJhdVwiOiR7eDphdX0sXCJ0dGxcIjoke3g6dHRsfSxcImR0X3NwXCI6JHt4OmR0X3NwfSxcImhzcFwiOiR7eDpoc3B9LFwiY2xpZW50X3Rva2VuXCI6JHtxdWVyeVN0cmluZy5jbGllbnRfdG9rZW59fSJ9&ud=4-0-5-0-6-N-3-ft-0-2-0-N
|
||||||
|
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
|
||||||
|
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
|
||||||
|
Cache-Control: no-cache
|
||||||
|
Connection: keep-alive
|
||||||
|
Cookie: _UP_28A_52_=519; _UP_A4A_11_=wb9c5167d67c42ba9444ac9f42b20a05; _UP_D_=pc; __sdid=AARkEnYbifr7X5obz/wd2gzbXPw1zviyFe4xBC3uVp7xQG20ziCSbbxMib8Fov3RPRk=; __pus=9db24ba75335a2deb2e99fc400ac59ffAAQ9T+W6s02ilSvzMeh5ionWLTKFfaa2QFvJNU1PoiCYQe0aunWWq5GyJCVzXrVuICNHxooijrybFKlkUoI7xCfP; __kp=aaaeba0e-8c8e-4837-869f-882283adb802; __kps=AASxYmDMULu4nzmEK/wFzK3I; __ktd=dvy3qySVr8aXEqUuxMJydA==; __uid=AASxYmDMULu4nzmEK/wFzK3I; tfstk=gJ-IibMk8WVC79FMKXHNG51AEQSSgQi2v86JnLEUeMIdVLdF_65rKL8_2QCwYaSKtg1R6Q4UY_SeVCOMsBJeU3TgVKvkYvjrKMvhqghqgmSWKpjooOET3fJteTj5pUMeYcJhqghZgmo2KpAxkBSuFgH1XT65pgUpwGpOsTX89TEKCdCG6_IR2QptCTfOpfAThTgCtpMB-WEBJUCvp1Z8V-XCJsnc1uEJf9NVMp302uK1dwtlqg1VZw8JnZxeQ0ZGYKTOX9O-DXQMJdt5o30zYpINOKb6SXZOQa1BWHQ_euphbFOyPBGQ-6Le1ZXOW8icQI59KHLsE5ppg6sdBNoxhdQJ7HbDYfECDEvFYUdtfkOA4YqVG7kLPR_02O1qCAaurBoEyuDPFR31JOXC7AM_9UbdIO1qCAaurwBGdtksCWLl.;
|
||||||
|
Pragma: no-cache
|
||||||
|
Referer: https://fast.uc.cn/
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
# curl 'https://fast-api.uc.cn/1/transfer/member?fr=pc&pr=UCBrowser&fetch_rights=ALL'
|
||||||
|
# -H 'accept: application/json, text/plain, */*'
|
||||||
|
# -H 'accept-language: zh-CN,zh;q=0.9'
|
||||||
|
# -H 'cache-control: no-cache'
|
||||||
|
# -H 'cookie: __pus=52e1ead14f116cf7c9e1f661e94cd253AAT2jmfclZedBkubcvIXwKTFo7XJmjaf4bHgaMR/vSLdC1iXn192H+d3SgVIFkiWWjUAlSInoboPn7Zg5KU1Cbis; __kp=ac118c62-20c1-4e12-aac1-d901f6a0e8d4; __kps=AASxYmDMULu4nzmEK/wFzK3I; __ktd=dvy3qySVr8aXEqUuxMJydA==; __uid=AASxYmDMULu4nzmEK/wFzK3I; XSRF-TOKEN=67412de4-5552-4f80-aca5-a4e23ac2621a; XSRF-TOKEN=67412de4-5552-4f80-aca5-a4e23ac2621a; __puus=b2c6f59b3607030fb60c1d486ca09126AAR498zI4bjrVRD3mNor9LX86VT5PSEpFI0gt/S1i/S8HStTB8LEW+gpFt7z2zsuZDcCgcxzWFAPKQXYedkkQ03+iTFqCGSScL0iSNCfE1+aqsf7fkE2ToXKz+zHxg3iVTYNaSnUCkbXk6QlMPaatHGJeHuh0RpS7lG75zREt1MArKS0wWYF5hCgYogvvp92tkQ='
|
||||||
|
# -H 'dnt: 1'
|
||||||
|
# -H 'origin: https://fast.uc.cn'
|
||||||
|
# -H 'pragma: no-cache'
|
||||||
|
# -H 'priority: u=1, i'
|
||||||
|
# -H 'referer: https://fast.uc.cn/'
|
||||||
|
# -H 'sec-ch-ua: "Not A(Brand";v="8", "Chromium";v="132", "Microsoft Edge";v="132"'
|
||||||
|
# -H 'sec-ch-ua-mobile: ?0'
|
||||||
|
# -H 'sec-ch-ua-platform: "Windows"'
|
||||||
|
# -H 'sec-fetch-dest: empty'
|
||||||
|
# -H 'sec-fetch-mode: cors'
|
||||||
|
# -H 'sec-fetch-site: same-site'
|
||||||
|
# -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0'
|
||||||
|
# -H 'x-biz-retry: 0'
|
||||||
|
GET https://fast-api.uc.cn/1/transfer/member?fr=pc&pr=UCBrowser&fetch_rights=ALL
|
||||||
|
accept: application/json, text/plain, */*
|
||||||
|
accept-language: zh-CN,zh;q=0.9
|
||||||
|
cache-control: no-cache
|
||||||
|
cookie: __pus=52e1ead14f116cf7c9e1f661e94cd253AAT2jmfclZedBkubcvIXwKTFo7XJmjaf4bHgaMR/vSLdC1iXn192H+d3SgVIFkiWWjUAlSInoboPn7Zg5KU1Cbis;
|
||||||
|
dnt: 1
|
||||||
|
###
|
||||||
|
# curl 'https://fast.uc.cn/api/info?fr=pc&pr=UCBrowser'
|
||||||
|
# -H 'accept: application/json, text/plain, */*'
|
||||||
|
# -H 'accept-language: zh-CN,zh;q=0.9'
|
||||||
|
# -H 'cache-control: no-cache'
|
||||||
|
# -H 'content-type: application/json'
|
||||||
|
# -H 'cookie: UDRIVE_TRANSFER_SESS=PjvNRi7KTc55QvE9cxLKQKTWRNH_9C8dTVuWdgOJm9eKVLxXZy7_bXk92ma8wNw9XIOlqhtVTZ8BXNkV4jbTQjYjTrG8Z0FqcE_PMPDsctcmnxujbLa1jnm7SpVB2i3i-fltzwwwoI9RhkkfLZVMW3_cw2llA6Yip847Pr-auhXC-ZJpNp3nzPLANTgn19lz; b-user-id=d3b7a19e-c576-0fb1-602a-e3cf7821d90e'
|
||||||
|
# -H 'dnt: 1'
|
||||||
|
# -H 'origin: https://fast.uc.cn'
|
||||||
|
# -H 'pragma: no-cache'
|
||||||
|
# -H 'priority: u=1, i'
|
||||||
|
# -H 'referer: https://fast.uc.cn/s/33197dd53ace4'
|
||||||
|
# -H 'sec-ch-ua: "Not A(Brand";v="8", "Chromium";v="132", "Microsoft Edge";v="132"'
|
||||||
|
# -H 'sec-ch-ua-mobile: ?0'
|
||||||
|
# -H 'sec-ch-ua-platform: "Windows"'
|
||||||
|
# -H 'sec-fetch-dest: empty'
|
||||||
|
# -H 'sec-fetch-mode: cors'
|
||||||
|
# -H 'sec-fetch-site: same-origin'
|
||||||
|
# -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0'
|
||||||
|
# -H 'x-biz-retry: 0'
|
||||||
|
# --data-raw '{"st":"st9c5631ad6j032w5rdr0978zxcl1qjt"}'
|
||||||
|
# 不限跨域
|
||||||
|
POST https://fast.uc.cn/api/info?fr=pc&pr=UCBrowser
|
||||||
|
accept: application/json, text/plain, */*
|
||||||
|
accept-language: zh-CN,zh;q=0.9
|
||||||
|
cache-control: no-cache
|
||||||
|
cookie: UDRIVE_TRANSFER_SESS=PjvNRi7KTc55QvE9cxLKQKTWRNH_9C8dTVuWdgOJm9eKVLxXZy7_bXk92ma8wNw9XIOlqhtVTZ8BXNkV4jbTQjYjTrG8Z0FqcE_PMPDsctcmnxujbLa1jnm7SpVB2i3i-fltzwwwoI9RhkkfLZVMW3_cw2llA6Yip847Pr-auhXC-ZJpNp3nzPLANTgn19lz; b-user-id=d3b7a19e-c576-0fb1-602a-e3cf7821d90e
|
||||||
|
dnt: 1
|
||||||
|
origin: https://fast.qqq.cn
|
||||||
|
pragma: no-cache
|
||||||
|
priority: u=1, i
|
||||||
|
referer: https://fast.qqq.cn/s/33197dd53ace4
|
||||||
|
sec-ch-ua: "Not A(Brand";v="8", "Chromium";v="132", "Microsoft Edge";v="132"
|
||||||
|
sec-ch-ua-mobile: ?0
|
||||||
|
sec-ch-ua-platform: "Windows"
|
||||||
|
sec-fetch-dest: empty
|
||||||
|
sec-fetch-mode: cors
|
||||||
|
sec-fetch-site: same-origin
|
||||||
|
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0
|
||||||
|
x-biz-retry: 0
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"st": "xx"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
wx -> st -> pus->puus
|
||||||
|
|
||||||
|
pus-> st + /info
|
||||||
|
https://fast-api.uc.cn/1/transfer/member puus
|
||||||
|
###
|
||||||
|
|||||||
@@ -101,6 +101,128 @@ referer: https://www.vyuyun.com
|
|||||||
https://down2.bilnn.top/uploads/11283/306bdccd233ffd2c.jar?filename=C400003mAan70zUy5O+%282%29.m4a&mime=audio%2Fx-m4a&verify=1729911697-SLZtEHXB4TM*ze1j31WMyNA**p743DY*GN2sajUx5w*2mM**
|
https://down2.bilnn.top/uploads/11283/306bdccd233ffd2c.jar?filename=C400003mAan70zUy5O+%282%29.m4a&mime=audio%2Fx-m4a&verify=1729911697-SLZtEHXB4TM*ze1j31WMyNA**p743DY*GN2sajUx5w*2mM**
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# @no-cookie-jar
|
||||||
|
# curl 'https://www.vyuyun.com/apiv1/share/getShareDownUrl/QPyZsb/gDZBTm?password=&downcode=Iv7YsjhM_6kzHijjtHyTLfPqlr6xwR5kqVNfMMAdWBEBcW1iNNIK-hDvrwLyzF4a'
|
||||||
|
# -H 'Accept: */*'
|
||||||
|
# -H 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8'
|
||||||
|
# -H 'Cache-Control: no-cache'
|
||||||
|
# -H 'Connection: keep-alive'
|
||||||
|
# -H 'Cookie: Hm_lvt_6e5421de7bb814e1dfc49bbc577c04d3=1734489414,1736133991,1736139184; HMACCOUNT=7C4EDB1414D4E56F; ginmin-session=MTczNjEzOTY5OHxEWDhFQVFMX2dBQUJFQUVRQUFBRV80QUFBQT09fE1kmd-TQNIU-AcLRbEhAMzpcoaTIqnDkU_z1Z2kW64A; Hm_lpvt_6e5421de7bb814e1dfc49bbc577c04d3=1736139719; __gads=ID=710230eb796d6baf:T=1736139187:RT=1736139729:S=ALNI_MauDIW3Tl0IYOZILi-6C0qMU328RA; __gpi=UID=00000fa7e525a50e:T=1736139187:RT=1736139729:S=ALNI_MYOvo89qg4zJq0s35CanMI3lx-zLw; __eoi=ID=df2245386092f112:T=1736139187:RT=1736139729:S=AA-AfjbzvOhiQPaERKQKlmEEC7Ds'
|
||||||
|
# -H 'DNT: 1'
|
||||||
|
# -H 'Pragma: no-cache'
|
||||||
|
# -H 'Referer: https://www.vyuyun.com/s/QPyZsb/file?password='
|
||||||
|
# -H 'Sec-Fetch-Dest: empty'
|
||||||
|
# -H 'Sec-Fetch-Mode: cors'
|
||||||
|
# -H 'Sec-Fetch-Site: same-origin'
|
||||||
|
# -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
|
||||||
|
# -H 'sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"'
|
||||||
|
# -H 'sec-ch-ua-mobile: ?0'
|
||||||
|
# -H 'sec-ch-ua-platform: "Windows"'
|
||||||
|
# -H 'token;'
|
||||||
|
GET https://www.vyuyun.com/apiv1/share/getShareDownUrl/QPyZsb/gDZBTm?password=&downcode=Iv7YsjhM_6kzHijjtHyTLfPqlr6xwR5kqVNfMMAdWBEBcW1iNNIK-hDvrwLyzF4a
|
||||||
|
Accept: */*
|
||||||
|
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
|
||||||
|
Cache-Control: no-cache
|
||||||
|
Connection: keep-alive
|
||||||
|
Cookie: Hm_lvt_6e5421de7bb814e1dfc49bbc577c04d3=1734489414,1736133991,1736139184; HMACCOUNT=7C4EDB1414D4E56F; ginmin-session=MTczNjEzOTY5OHxEWDhFQVFMX2dBQUJFQUVRQUFBRV80QUFBQT09fE1kmd-TQNIU-AcLRbEhAMzpcoaTIqnDkU_z1Z2kW64A; Hm_lpvt_6e5421de7bb814e1dfc49bbc577c04d3=1736139719; __gads=ID=710230eb796d6baf:T=1736139187:RT=1736139729:S=ALNI_MauDIW3Tl0IYOZILi-6C0qMU328RA; __gpi=UID=00000fa7e525a50e:T=1736139187:RT=1736139729:S=ALNI_MYOvo89qg4zJq0s35CanMI3lx-zLw; __eoi=ID=df2245386092f112:T=1736139187:RT=1736139729:S=AA-AfjbzvOhiQPaERKQKlmEEC7Ds
|
||||||
|
DNT: 1
|
||||||
|
Pragma: no-cache
|
||||||
|
Referer: https://www.vyuyun.com/s/QPyZsb/file?password=
|
||||||
|
Sec-Fetch-Dest: empty
|
||||||
|
Sec-Fetch-Mode: cors
|
||||||
|
Sec-Fetch-Site: same-origin
|
||||||
|
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
|
||||||
|
sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
|
||||||
|
sec-ch-ua-mobile: ?0
|
||||||
|
sec-ch-ua-platform: "Windows"
|
||||||
|
token:
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# curl 'https://www.vyuyun.com/apiv1/share/file/QPyZsb?password='
|
||||||
|
# -H 'Accept: application/json, text/plain, */*'
|
||||||
|
# -H 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8'
|
||||||
|
# -H 'Cache-Control: no-cache'
|
||||||
|
# -H 'Connection: keep-alive'
|
||||||
|
# -H 'Cookie: Hm_lvt_6e5421de7bb814e1dfc49bbc577c04d3=1734489414,1736133991; HMACCOUNT=845216228CFDAEF3; ginmin-session=MTczNjEzNzIwNXxEWDhFQVFMX2dBQUJFQUVRQUFBaV80QUFBUVp6ZEhKcGJtY01CUUFEWVdsa0JuTjBjbWx1Wnd3SEFBVXhNVGt3T0E9PXygMqAo7e0FsZpAm5tggrVEAmipmptNAe4RF-StdbItOA==; __gads=ID=02846a0224427b16:T=1729911643:RT=1736137206:S=ALNI_MYN-82qd45RW0rYq1JewWVxCmoZAQ; __gpi=UID=00000f553e085dd5:T=1729911643:RT=1736137206:S=ALNI_Mb2dIMzZrO14lDlqCBOShH29OEfng; __eoi=ID=3e810b8ce65b0a46:T=1729911643:RT=1736137206:S=AA-AfjbN5kpzrPK6QBBlTEmMlxXx; Hm_lpvt_6e5421de7bb814e1dfc49bbc577c04d3=1736137222'
|
||||||
|
# -H 'DNT: 1'
|
||||||
|
# -H 'Pragma: no-cache'
|
||||||
|
# -H 'Referer: https://www.vyuyun.com/s/QPyZsb/file?password='
|
||||||
|
# -H 'Sec-Fetch-Dest: empty'
|
||||||
|
# -H 'Sec-Fetch-Mode: cors'
|
||||||
|
# -H 'Sec-Fetch-Site: same-origin'
|
||||||
|
# -H 'Token;'
|
||||||
|
# -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
|
||||||
|
# -H 'X-Requested-With: XMLHttpRequest'
|
||||||
|
# -H 'sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"'
|
||||||
|
# -H 'sec-ch-ua-mobile: ?0'
|
||||||
|
# -H 'sec-ch-ua-platform: "Windows"'
|
||||||
|
GET https://www.vyuyun.com/apiv1/share/file/QPyZsb?password=
|
||||||
|
Accept: application/json, text/plain, */*
|
||||||
|
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
|
||||||
|
Cache-Control: no-cache
|
||||||
|
Connection: keep-alive
|
||||||
|
Cookie: Hm_lvt_6e5421de7bb814e1dfc49bbc577c04d3=1734489414,1736133991; HMACCOUNT=845216228CFDAEF3; ginmin-session=MTczNjEzNzIwNXxEWDhFQVFMX2dBQUJFQUVRQUFBaV80QUFBUVp6ZEhKcGJtY01CUUFEWVdsa0JuTjBjbWx1Wnd3SEFBVXhNVGt3T0E9PXygMqAo7e0FsZpAm5tggrVEAmipmptNAe4RF-StdbItOA==; __gads=ID=02846a0224427b16:T=1729911643:RT=1736137206:S=ALNI_MYN-82qd45RW0rYq1JewWVxCmoZAQ; __gpi=UID=00000f553e085dd5:T=1729911643:RT=1736137206:S=ALNI_Mb2dIMzZrO14lDlqCBOShH29OEfng; __eoi=ID=3e810b8ce65b0a46:T=1729911643:RT=1736137206:S=AA-AfjbN5kpzrPK6QBBlTEmMlxXx; Hm_lpvt_6e5421de7bb814e1dfc49bbc577c04d3=1736137222
|
||||||
|
DNT: 1
|
||||||
|
Pragma: no-cache
|
||||||
|
Referer: https://www.vyuyun.com/s/QPyZsb/file?password=
|
||||||
|
Sec-Fetch-Dest: empty
|
||||||
|
Sec-Fetch-Mode: cors
|
||||||
|
Sec-Fetch-Site: same-origin
|
||||||
|
Token:
|
||||||
|
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
|
||||||
|
X-Requested-With: XMLHttpRequest
|
||||||
|
sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
|
||||||
|
sec-ch-ua-mobile: ?0
|
||||||
|
sec-ch-ua-platform: "Windows"
|
||||||
|
|
||||||
|
###
|
||||||
|
# curl 'https://www.vyuyun.com/apiv1/share/getShareDownUrl/QPyZsb/gDZBTm?password=&downcode=Iv7YsjhM_6kzHijjtHyTLT2CySejYHyXEtW_2oI9kqCnevPaRjMpzFDaq4dT4z3R'
|
||||||
|
# -H 'Accept: */*'
|
||||||
|
# -H 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6'
|
||||||
|
# -H 'Cache-Control: no-cache'
|
||||||
|
# -H 'Connection: keep-alive'
|
||||||
|
# -H 'Cookie: noticeId=1; Hm_lvt_6e5421de7bb814e1dfc49bbc577c04d3=1736134006; HMACCOUNT=DD7A065B1B5C7BCF; ginmin-session=MTczNjEzNDExNXxEWDhFQVFMX2dBQUJFQUVRQUFELUFRal9nQUFEQm5OMGNtbHVad3dIQUFWMGIydGxiZ1p6ZEhKcGJtY01fNjhBXzZ4b1pVOVNMWGRwVVRWemVrTlFhRGxzTjNCRlNHOW5jMWRLU2pKVFluUlZOVWR2UlhkWGNEbElUMEZNYlV0RVMwRkJTeloxVlhaS1UycE5aMXBrTW1jMWJYVlRPVjlOWkVVMWNHMU1iMkZMTW1sR1NHcFhRbWhPWjAxb05FWmpWV2swVWpOU01GZE9aRk5UZUd0WVJFSmphMWw0WVZKVkxYRlhSalZ5UlhSSmRYaG9jbVJuTWpkbmRFWlBWRlpDV0ZkWGMzWjJaVFZtUjFwSWMwRnBlbXhhUVROVFowaEtZakJqTlc4cUJuTjBjbWx1Wnd3SUFBWnZjR1Z1YVdRR2MzUnlhVzVuREFJQUFBWnpkSEpwYm1jTUNnQUlZV1J0YVc1ZmFYTUdjM1J5YVc1bkRBTUFBVEE9fMMethsDCW-b_YQHRj_0KHUVB1AcmohDXI4L4en8_8Nd; Hm_lpvt_6e5421de7bb814e1dfc49bbc577c04d3=1736138831'
|
||||||
|
# -H 'DNT: 1'
|
||||||
|
# -H 'Pragma: no-cache'
|
||||||
|
# -H 'Referer: https://www.vyuyun.com/s/QPyZsb/file?password='
|
||||||
|
# -H 'Sec-Fetch-Dest: empty'
|
||||||
|
# -H 'Sec-Fetch-Mode: cors'
|
||||||
|
# -H 'Sec-Fetch-Site: same-origin'
|
||||||
|
# -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0'
|
||||||
|
# -H 'sec-ch-ua: "Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"'
|
||||||
|
# -H 'sec-ch-ua-mobile: ?0'
|
||||||
|
# -H 'sec-ch-ua-platform: "Windows"'
|
||||||
|
# -H 'token: heOR-wiQ5szCPh9l7pEHogsWJJ2SbtU5GoEwWp9HOALmKDKAAK6uUvJSjMgZd2g5muS9_MdE5pmLoaK2iFHjWBhNgMh4FcUi4R3R0WNdSSxkXDBckYxaRU-qWF5rEtIuxhrdg27gtFOTVBXWWsvve5fGZHsAizlZA3SgHJb0c5o*'
|
||||||
|
GET https://www.vyuyun.com/apiv1/share/getShareDownUrl/QPyZsb/gDZBTm?password=&downcode=Iv7YsjhM_6kzHijjtHyTLT2CySejYHyXEtW_2oI9kqCnevPaRjMpzFDaq4dT4z3R
|
||||||
|
Accept: */*
|
||||||
|
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
|
||||||
|
Cache-Control: no-cache
|
||||||
|
Connection: keep-alive
|
||||||
|
Cookie: noticeId=1; Hm_lvt_6e5421de7bb814e1dfc49bbc577c04d3=1736134006; HMACCOUNT=DD7A065B1B5C7BCF; ginmin-session=MTczNjEzNDExNXxEWDhFQVFMX2dBQUJFQUVRQUFELUFRal9nQUFEQm5OMGNtbHVad3dIQUFWMGIydGxiZ1p6ZEhKcGJtY01fNjhBXzZ4b1pVOVNMWGRwVVRWemVrTlFhRGxzTjNCRlNHOW5jMWRLU2pKVFluUlZOVWR2UlhkWGNEbElUMEZNYlV0RVMwRkJTeloxVlhaS1UycE5aMXBrTW1jMWJYVlRPVjlOWkVVMWNHMU1iMkZMTW1sR1NHcFhRbWhPWjAxb05FWmpWV2swVWpOU01GZE9aRk5UZUd0WVJFSmphMWw0WVZKVkxYRlhSalZ5UlhSSmRYaG9jbVJuTWpkbmRFWlBWRlpDV0ZkWGMzWjJaVFZtUjFwSWMwRnBlbXhhUVROVFowaEtZakJqTlc4cUJuTjBjbWx1Wnd3SUFBWnZjR1Z1YVdRR2MzUnlhVzVuREFJQUFBWnpkSEpwYm1jTUNnQUlZV1J0YVc1ZmFYTUdjM1J5YVc1bkRBTUFBVEE9fMMethsDCW-b_YQHRj_0KHUVB1AcmohDXI4L4en8_8Nd; Hm_lpvt_6e5421de7bb814e1dfc49bbc577c04d3=1736138831
|
||||||
|
DNT: 1
|
||||||
|
Pragma: no-cache
|
||||||
|
Referer: https://www.vyuyun.com/s/QPyZsb/file?password=
|
||||||
|
Sec-Fetch-Dest: empty
|
||||||
|
Sec-Fetch-Mode: cors
|
||||||
|
Sec-Fetch-Site: same-origin
|
||||||
|
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0
|
||||||
|
sec-ch-ua: "Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"
|
||||||
|
sec-ch-ua-mobile: ?0
|
||||||
|
sec-ch-ua-platform: "Windows"
|
||||||
|
token: heOR-wiQ5szCPh9l7pEHogsWJJ2SbtU5GoEwWp9HOALmKDKAAK6uUvJSjMgZd2g5muS9_MdE5pmLoaK2iFHjWBhNgMh4FcUi4R3R0WNdSSxkXDBckYxaRU-qWF5rEtIuxhrdg27gtFOTVBXWWsvve5fGZHsAizlZA3SgHJb0c5o*
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 118网盘 https://qaiu.118pan.com/b1228264
|
### 118网盘 https://qaiu.118pan.com/b1228264
|
||||||
#密码:qaiu
|
#密码:qaiu
|
||||||
https://qaiu.118pan.com/b1228264
|
https://qaiu.118pan.com/b1228264
|
||||||
|
|||||||
18
web-service/src/main/resources/http-tools/test2.http
Normal file
18
web-service/src/main/resources/http-tools/test2.http
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
###
|
||||||
|
POST http://127.0.0.1:6400/v2/shout/submit
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"content": "CREATE UNIQUE INDEX `idx_uk_code` ON `t_messages` (`code`);"
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
GET http://127.0.0.1:6400/v2/shout/retrieve?code=878696
|
||||||
|
|
||||||
|
###
|
||||||
|
响应:
|
||||||
|
{
|
||||||
|
"data": "这是一条秘密消息",
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success"
|
||||||
|
}
|
||||||
@@ -55,6 +55,7 @@
|
|||||||
<logger name="io.netty" level="warn"/>
|
<logger name="io.netty" level="warn"/>
|
||||||
<logger name="io.vertx" level="info"/>
|
<logger name="io.vertx" level="info"/>
|
||||||
<logger name="com.zaxxer.hikari" level="info"/>
|
<logger name="com.zaxxer.hikari" level="info"/>
|
||||||
|
<logger name="cn.qaiu" level="debug"/>
|
||||||
<root level="info">
|
<root level="info">
|
||||||
<appender-ref ref="STDOUT"/>
|
<appender-ref ref="STDOUT"/>
|
||||||
<!-- <appender-ref ref="FILE"/>-->
|
<!-- <appender-ref ref="FILE"/>-->
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package cn.qaiu.db.ddl;
|
||||||
|
|
||||||
|
import cn.qaiu.db.pool.JDBCType;
|
||||||
|
import io.vertx.sqlclient.templates.annotations.Column;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class CreateTableTest {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static class TestModel2 {
|
||||||
|
@Column(name = "id")
|
||||||
|
@Constraint(autoIncrement = true)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "name")
|
||||||
|
@Constraint(notNull = true, uniqueKey = "ne_unique")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "age")
|
||||||
|
private Integer age;
|
||||||
|
|
||||||
|
@Column(name = "email")
|
||||||
|
@Constraint(notNull = true, uniqueKey = "ne_unique")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Column(name = "created_at")
|
||||||
|
private java.util.Date createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getCreateTableSQL() {
|
||||||
|
// 测试
|
||||||
|
String sql = String.join("\n", CreateTable.getCreateTableSQL(TestModel2.class, JDBCType.H2DB));
|
||||||
|
System.out.println(sql);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void getCreateTableSQL2() {
|
||||||
|
// 测试
|
||||||
|
String sql = String.join("\n", CreateTable.getCreateTableSQL(TestModel2.class, JDBCType.MySQL));
|
||||||
|
System.out.println(sql);
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void getCreateTableSQL3() {
|
||||||
|
// 测试
|
||||||
|
String sql = String.join("\n", CreateTable.getCreateTableSQL(TestModel2.class, JDBCType.PostgreSQL));
|
||||||
|
System.out.println(sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user