Compare commits

..

74 Commits

Author SHA1 Message Date
QAIU
f30027dd13 1. 蓝奏云域名适配 2025-05-28 17:01:50 +08:00
QAIU
8b6aad17f4 0 2025-05-08 18:18:52 +08:00
QAIU
b77930adfb remove yarn.lock 2025-05-08 18:18:15 +08:00
qaiu
aff8f88076 Update README.md 2025-04-23 14:41:52 +08:00
qaiu
4e6582e24c Update nfd-service-template.xml 2025-04-23 14:31:56 +08:00
qaiu
fa9acaccfd Update README.md 2025-04-07 13:29:43 +08:00
QAIU
0414f85f12 115域名变动,360pan暂不可用,ctfile域名变动 2025-04-03 10:57:28 +08:00
QAIU
527dd0eeb4 城通分享格式适配, 优化日志打印 2025-03-28 17:59:28 +08:00
QAIU
74ed7475c9 json异常时, 快速失败 2025-03-24 13:35:40 +08:00
qaiu
54dc3dba96 蓝奏云随机404 问题修复 2025-03-22 12:53:33 +08:00
qaiu
9980159090 更新 README.md 2025-03-17 19:24:38 +08:00
QAIU
0b193ebb00 Merge remote-tracking branch 'origin/main' 2025-03-14 14:03:47 +08:00
QAIU
f5fc9843b2 微信版QQ邮箱中转站解析优化(已改名为QQ邮箱云盘) 2025-03-14 14:03:29 +08:00
qaiu
df1f67dd26 Update README.md 2025-02-25 10:29:25 +08:00
qaiu
b069a5f576 Update README.md 2025-02-25 10:26:47 +08:00
qaiu
7686763a03 Update README.md 2025-02-25 10:25:35 +08:00
qaiu
635a6eac37 Update README.md 2025-02-25 10:02:23 +08:00
QAIU
877edc535f md 2025-02-24 17:39:23 +08:00
QAIU
01d59e3c1e add 超星盘,360盘 2025-02-22 16:55:00 +08:00
QAIU
fece2799e3 Merge remote-tracking branch 'origin/main' 2025-02-21 18:28:08 +08:00
QAIU
de9756ee86 优化细节 2025-02-21 18:27:52 +08:00
qaiu
51f047a51b 更新 README.md 2025-02-20 18:11:39 +08:00
qaiu
04b66e82b7 Update README.md 2025-02-20 18:06:58 +08:00
qaiu
df89253647 Update README.md 2025-02-20 17:47:00 +08:00
qaiu
45dbca794e 更新 README.md 2025-02-13 12:14:59 +08:00
qaiu
857bf28f99 Update README.md 2025-02-12 16:25:16 +08:00
QAIU
e07ce15228 新增文件列表解析接口/redirectUrl/:type/:param 2025-02-10 14:19:18 +08:00
QAIU
0637bcfd8e 新增文件列表解析接口/v2/getFileList?url= 2025-02-07 19:28:09 +08:00
QAIU
23db0563ac 新增文件列表解析接口/v2/getFileList?url= 2025-02-07 19:27:48 +08:00
QAIU
ccba71aa4e MySQL支持, 其他优化 2025-02-06 16:54:54 +08:00
QAIU
fee4bf2ad6 MySQL支持, 其他优化 2025-02-06 16:52:22 +08:00
QAIU
5052fea9ef MySQL支持, 其他优化 2025-02-06 16:52:06 +08:00
qaiu
e85215fca1 Update README.md 2025-02-06 15:51:46 +08:00
qaiu
e42fe45329 Update README.md 2025-02-06 15:50:26 +08:00
qaiu
4240815bd1 Create FUNDING.yml 2025-02-05 11:11:01 +08:00
qaiu
6f0c5305e2 更新 README.md 2025-01-26 16:26:50 +08:00
qaiu
757005cad8 更新 README.md 2025-01-26 16:09:45 +08:00
qaiu
81651ad97c 更新 README.md 2025-01-26 15:31:43 +08:00
qaiu
f3763b6058 优化内核, QQ邮箱微信账户分享,添加123请求header 2025-01-24 19:21:58 +08:00
qaiu
82478dc485 add: 网易云音乐云盘分享URL支持 2025-01-23 17:23:58 +08:00
qaiu
703fd05d43 Update README.md 2025-01-10 14:33:30 +08:00
qaiu
ff868b6e2a Update README.md 2025-01-10 14:21:00 +08:00
qaiu
051a74b37b Update README.md 2025-01-10 14:19:59 +08:00
qaiu
a0a1085623 Update README.md 2025-01-10 14:14:20 +08:00
qaiu
2612d3919c Update README.md 2025-01-10 14:12:55 +08:00
qaiu
6f123a236f Update README.md 2025-01-10 14:12:04 +08:00
QAIU
71e57e6a08 Merge remote-tracking branch 'origin/main' 2025-01-07 15:04:07 +08:00
QAIU
7cb18d8186 remove yarn.lock 2025-01-07 15:03:53 +08:00
qaiu
cdbf670ece Update maven.yml 2025-01-07 15:00:18 +08:00
QAIU
e0dafee617 remove yarn.lock 2025-01-07 14:48:16 +08:00
QAIU
c37bce1563 优化解析器链接识别 2025-01-07 13:19:40 +08:00
QAIU
0b3c77d644 115pan 2025-01-07 11:13:52 +08:00
QAIU
2cf85caf86 115pan分享识别优化 2025-01-06 18:01:23 +08:00
QAIU
594010ba88 代理服务配置优化 2025-01-04 17:38:33 +08:00
QAIU
d91460d2e2 修复蓝奏优享解析失败, gz压缩的json解析 2025-01-04 15:21:15 +08:00
QAIU
89713e6ac9 修复蓝奏优享解析失败 2025-01-04 14:21:32 +08:00
QAIU
17c9b2538c Merge remote-tracking branch 'origin/main' 2025-01-04 14:18:46 +08:00
QAIU
d337b003cb 常规测试 2024-12-30 18:11:53 +08:00
qaiu
8f1485656b Update README.md
add docker加速地址
2024-12-23 13:05:25 +08:00
qaiu
f0c4ec3031 Update maven.yml 2024-12-23 13:02:51 +08:00
qaiu
458be84aca Update maven.yml 2024-12-23 13:00:28 +08:00
qaiu
c7716aad34 Update README.md 2024-12-18 13:05:16 +08:00
QAIU
4a3e734408 1. onedrive
常规测试
2024-12-18 11:46:06 +08:00
qaiu
54cc212753 Merge pull request #78 from xrgzs/add-ghcr
增加 ghcr.io 容器构建
2024-12-17 16:24:03 +08:00
xrgzs
f4ae1eaa51 PR时不更新依赖图 2024-12-17 16:15:59 +08:00
xrgzs
d2537282c9 增加 ghcr.io 容器构建 2024-12-17 15:47:05 +08:00
QAIU
87527688c3 1. 代理配置 2024-12-17 15:21:59 +08:00
qaiu
2be0b6505a 更新 P115Tool.java
UA问题说明
2024-12-17 09:37:48 +08:00
QAIU
672f100c7c 1. 动态UA 2024-12-16 19:01:55 +08:00
QAIU
5af402c0c5 1. 动态UA 2024-12-16 18:52:02 +08:00
QAIU
693a4f0f63 1. P115网盘解析BUG
2. 完善onedrive支持
2024-12-16 15:54:06 +08:00
QAIU
f8d2426ff6 API 2024-12-16 13:19:15 +08:00
QAIU
973a9bedcd 添加115网盘支持(测试中)#75 2024-12-16 13:15:53 +08:00
QAIU
a583733400 修复小飞机解析失败 2024-12-16 12:31:34 +08:00
79 changed files with 3777 additions and 6402 deletions

15
.github/FUNDING.yml vendored Normal file
View 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']

View File

@@ -11,10 +11,19 @@ name: Java CI with Maven
# The API requires write permission on the repository to submit dependencies
permissions:
contents: write
packages: write
on:
push:
branches: [ "main" ]
paths-ignore:
- 'bin/**'
- '.github/**'
- '.mvn/**'
- '.run/**'
- '.vscode/**'
- '*.txt'
- '*.md'
pull_request:
branches: [ "main" ]
@@ -25,21 +34,54 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v4
with:
node-version: '18'
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- name: Build Frontend
run: cd web-front && yarn install && yarn run build
- name: Build with Maven
run: mvn -B package --file pom.xml
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
uses: advanced-security/maven-dependency-submission-action@v3
if: github.event_name != 'pull_request'
with:
ignore-maven-wrapper: true
# - uses: release-drafter/release-drafter@v5
# env:
# GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
path: web-service/target/netdisk-fast-download-bin.zip
- name: Login to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ github.sha }}
ghcr.io/${{ github.repository }}:main

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM eclipse-temurin:17-alpine
WORKDIR /app
COPY ./web-service/target/netdisk-fast-download-bin.zip .
RUN unzip netdisk-fast-download-bin.zip && \
mv netdisk-fast-download/* ./ && \
rm netdisk-fast-download-bin.zip && \
chmod +x run.sh
EXPOSE 6400 6401
ENTRYPOINT ["sh", "run.sh"]

223
README.md
View File

@@ -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
预览地址2(可以解析onedrive) http://8.209.249.88:6402
main分支依赖JDK17, 提供了JDK11分支[main-jdk11](https://github.com/qaiu/netdisk-fast-download/tree/main-jdk11)
**注意: 请不要过度依赖lz.qaiu.top预览地址服务建议本地搭建或者云服务器自行搭建。
解析次数过多IP会被部分网盘厂商限制不推荐做公共解析。**
[![Java CI with Maven](https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml/badge.svg)](https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml)
[![jdk](https://img.shields.io/badge/jdk-%3E%3D17-blue)](https://www.oracle.com/cn/java/technologies/downloads/)
[![vert.x](https://img.shields.io/badge/vert.x-4.5.6-blue)](https://vertx-china.github.io/)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/qaiu/netdisk-fast-download)](https://github.com/qaiu/netdisk-fast-download/releases/latest)
## 项目介绍
网盘直链解析工具能把网盘分享下载链接转化为直链,已支持蓝奏云/蓝奏云优享/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘/Cloudreve等支持加密分享。
**0.1.8及以上版本json接口格式有调整尤其依赖lz.qaiu.top做下载服务的朋友们记得修改 参考json返回数据格式示例**
*重要声明:本项目仅供学习参考;请不要将此项目用于任何商业用途,否则可能带来严重的后果。转发/分享该项目请注明来源*
**0.1.8及以上版本json接口格式有调整 参考json返回数据格式示例**
**小飞机解析有IP限制多数云服务商的大陆IP会被拦截可以自行配置代理和本程序无关**
**注意: 请不要过度依赖lz.qaiu.top预览地址服务建议本地搭建或者云服务器自行搭建。解析次数过多IP会被部分网盘厂商限制不推荐做公共解析。**
## 网盘支持情况:
> 20230905 奶牛云直链做了防盗链需加入请求头Referer: https://cowtransfer.com/
> 20230824 123云盘解析大文件(>100MB)失效,需要登录
> 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接口说明
your_host指的是您的域名或者IP实际使用时替换为实际域名或者IP端口默认6400可以使用nginx代理来做域名访问。
解析方式分为两种类型直接跳转下载文件和获取下载链接,
每一种都提供了两种接口形式: `通用接口parser?url=``网盘标志/分享key拼接的短地址标志短链`,所有规则参考示例。
- 通用接口: `/parser?url=分享链接`加密分享需要加上参数pwd=密码;
- 标志短链: `/网盘标识/分享key` 在分享Key后面加上@密码;
- 直链JSON: `通用接口``标志短链`前加上`/json` 加密分享的密码规则同上;
- 通用接口: `/parser?url=分享链接&pwd=密码` 没有分享密码去掉&pwd参数;
- 标志短链: `/d/网盘标识/分享key@密码` 没有分享密码去掉@密码;
- 直链JSON: `/json/网盘标识/分享key@密码``/json/parser?url=分享链接&pwd=密码`
- 网盘标识参考上面网盘支持情况
- 当带有分享密码时需要加上密码参数(pwd)
- 移动云云空间,小飞机网盘的加密分享的密码可以忽略
- 移动云空间分享key取分享链接中的data参数,比如`&data=xxx`的参数就是xxx
API规则:
> 建议使用UrlEncode编码分享链接
```
1. 解析并自动302跳转 :
1. 解析并自动302跳转
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格式
http://your_host/json/parser?url=分享链接&pwd=xxx
http://your_host/json/网盘标识/分享key@分享密码
```
json返回数据格式示例:
`shareKey`: 全局分享key
@@ -83,6 +103,50 @@ json返回数据格式示例:
"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示例:
@@ -153,7 +217,58 @@ mvn package
```
打包好的文件位于 web-service/target/netdisk-fast-download-bin.zip
## Linux服务部署
### [宝塔安装参考](https://blog.qaiu.top/archives/netdisk-fast-download-bao-ta-an-zhuang-jiao-cheng)
### Docker 部署Main分支
#### 海外服务器Docker部署
```shell
# 创建目录
mkdir -p netdisk-fast-download
cd netdisk-fast-download
# 拉取镜像
docker pull ghcr.io/qaiu/netdisk-fast-download:main
# 复制配置文件或下载仓库web-service\src\main\resources
docker create --name netdisk-fast-download ghcr.io/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.io/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
```
#### 国内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的路径改为实际路径
```shell
cd ~
@@ -195,17 +310,30 @@ resources目录下包含服务端配置文件 配置文件自带说明,具体
app-dev.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 开发计划
- 超星网盘解析 doing
- 带Referer头的js请求下载 doing
- 城通网盘解析 √
- 目录解析(专属版)
- 带cookie/token参数解析大文件(专属版)
- docker
**技术栈:**
Jdk17+Vert.x4.4.1
Jdk17+Vert.x4
Core模块集成Vert.x实现类似spring的注解式路由API
@@ -213,10 +341,17 @@ Core模块集成Vert.x实现类似spring的注解式路由API
[![Star History Chart](https://api.star-history.com/svg?repos=qaiu/netdisk-fast-download&type=Date)](https://star-history.com/#qaiu/netdisk-fast-download&Date)
## **免责声明**
- 用户在使用本项目时,应自行承担风险,并确保其行为符合当地法律法规及网盘服务提供商的使用条款。
- 开发者不对用户因使用本项目而导致的任何后果负责,包括但不限于数据丢失、隐私泄露、账号封禁或其他任何形式的损害。
## 支持该项目
开源不易,用爱发电,本项目长期维护如果觉得有帮助, 可以请作者喝杯咖啡, 感谢支持
赞助88元以上, 可以优先体验专享版--大文件解析,目录解析
### 关于专属版
99元, 提供对小飞机,蓝奏优享大文件解析的支持, 提供天翼云盘,移动云盘,联调云盘的解析支持
199元, 包含部署服务和首页定制, 需提供宝塔环境
可以提供功能定制开发, 加v价格详谈: qaiu-cn
![image](https://github.com/qaiu/netdisk-fast-download/assets/29825328/54276aee-cc3f-4ebd-8973-2e15f6295819)
[手机端支付宝打赏跳转链接](https://qr.alipay.com/fkx01882dnoxxtjenhlxt53)

View File

@@ -4,7 +4,7 @@
<name>netdisk-fast-download</name>
<description>netdisk fast download service</description>
<executable>java</executable>
<arguments> -server -Xmx128m -jar ${jar} </arguments>
<arguments> -server -Xmx128m -Dfile.encoding=utf8 -jar ${jar} </arguments>
<logpath>${dd}\logs</logpath>
<log mode="roll-by-time">
<pattern>yyyyMMdd</pattern>

View File

@@ -59,6 +59,18 @@
<artifactId>vertx-jdbc-client</artifactId>
<version>${vertx.version}</version>
</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>
</project>

View File

@@ -21,7 +21,6 @@ public @interface Constraint {
boolean notNull() default false;
/**
* 唯一键约束 TODO 待实现
* @return 唯一键约束
*/
String uniqueKey() default "";
@@ -32,7 +31,7 @@ public @interface Constraint {
*/
String defaultValue() default "";
/**
* 默认值是否是函数
* 默认值是否是函数 like value=NOW()
* @return false 不是函数
*/
boolean defaultValueIsFunction() default false;

View File

@@ -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")
);
}
}

View File

@@ -6,7 +6,9 @@ import io.vertx.codegen.format.CamelCase;
import io.vertx.codegen.format.Case;
import io.vertx.codegen.format.LowerCamelCase;
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.RowMapped;
import org.apache.commons.lang3.StringUtils;
@@ -14,9 +16,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* 创建表
@@ -24,154 +24,312 @@ import java.util.Set;
* @author <a href="https://qaiu.top">QAIU</a>
*/
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);
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");
}
public static String UNIQUE_PREFIX = "idx_";
private static Case getCase(Class<?> clz) {
switch (clz.getName()) {
case "io.vertx.codegen.format.CamelCase":
return CamelCase.INSTANCE;
case "io.vertx.codegen.format.SnakeCase":
return SnakeCase.INSTANCE;
case "io.vertx.codegen.format.LowerCamelCase":
return LowerCamelCase.INSTANCE;
default:
throw new UnsupportedOperationException();
return switch (clz.getName()) {
case "io.vertx.codegen.format.CamelCase" -> CamelCase.INSTANCE;
case "io.vertx.codegen.format.SnakeCase" -> SnakeCase.INSTANCE;
case "io.vertx.codegen.format.LowerCamelCase" -> 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 ? "\"" : "`";
String endStr = type == JDBCType.H2DB ? ");" : ")ENGINE=InnoDB DEFAULT CHARSET=utf8;";
// 判断类上是否有次注解
String primaryKey = null; // 主键
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);
}
// 判断是否忽略字段
private static boolean isIgnoredField(Field field) {
return field.getName().equals("serialVersionUID")
|| StringUtils.isEmpty(javaProperty2SqlColumnMap.get(field.getType()))
|| field.isAnnotationPresent(TableGenIgnore.class);
}
if (clz.isAnnotationPresent(Table.class)) {
// 获取类上的注解
Table annotation = clz.getAnnotation(Table.class);
// 输出注解上的类名
String tableNameAnnotation = annotation.value();
if (StringUtils.isNotEmpty(tableNameAnnotation)) {
tableName = tableNameAnnotation;
} else {
tableName = LowerCamelCase.INSTANCE.to(caseFormat, clz.getSimpleName());
// 处理Column注解
private static String processColumnAnnotation(Field field, String column) {
if (field.isAnnotationPresent(Column.class)) {
Column columnAnnotation = field.getAnnotation(Column.class);
if (StringUtils.isNotBlank(columnAnnotation.name())) {
column = columnAnnotation.name();
}
primaryKey = annotation.keyFields();
}
Field[] fields = clz.getDeclaredFields();
String column;
int[] decimalSize = {22, 2};
int varcharSize = 255;
StringBuilder sb = new StringBuilder(50);
sb.append("CREATE TABLE IF NOT EXISTS ").append(quotationMarks).append(tableName).append(quotationMarks).append(" ( \r\n ");
boolean firstId = true;
for (Field f : fields) {
Class<?> paramType = f.getType();
String sqlType = javaProperty2SqlColumnMap.get(paramType);
if (f.getName().equals("serialVersionUID") || StringUtils.isEmpty(sqlType) || f.isAnnotationPresent(TableGenIgnore.class)) {
continue;
return column;
}
// 添加类型长度
private static void appendTypeLength(String sqlType, StringBuilder sb, int[] decimalSize, int varcharSize) {
if ("DECIMAL".equals(sqlType)) {
sb.append("(").append(decimalSize[0]).append(",").append(decimalSize[1]).append(")");
} else if ("VARCHAR".equals(sqlType)) {
sb.append("(").append(varcharSize).append(")");
}
}
// 添加约束
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());
if (f.isAnnotationPresent(Column.class)) {
Column columnAnnotation = f.getAnnotation(Column.class);
//输出注解属性
if (StringUtils.isNotBlank(columnAnnotation.name())) {
column = columnAnnotation.name();
}
String apostrophe = constraint.defaultValueIsFunction() ? "" : "'";
if (StringUtils.isNotEmpty(constraint.defaultValue())) {
sb.append(" DEFAULT ").append(apostrophe).append(constraint.defaultValue()).append(apostrophe);
}
if (f.isAnnotationPresent(Length.class)) {
Length fieldAnnotation = f.getAnnotation(Length.class);
decimalSize = fieldAnnotation.decimalSize();
varcharSize = fieldAnnotation.varcharSize();
}
sb.append(quotationMarks).append(column).append(quotationMarks);
sb.append(" ").append(sqlType);
// 添加类型长度
if (sqlType.equals("DECIMAL")) {
sb.append("(").append(decimalSize[0]).append(",").append(decimalSize[1]).append(")");
}
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)) {
////自增
if (constraint.autoIncrement()) {
if (type == JDBCType.PostgreSQL) {
// 需要移除字段类型(最后一个单词)
if (field.getType().equals(Integer.class)) {
sb.delete(sb.lastIndexOf(" "), sb.length());
sb.append(" SERIAL");
} else if (field.getType().equals(Long.class)) {
sb.delete(sb.lastIndexOf(" "), sb.length());
sb.append(" BIGSERIAL");
}
} else if (field.getType().equals(Integer.class) || field.getType().equals(Long.class)) {
sb.append(" AUTO_INCREMENT");
}
}
if (StringUtils.isEmpty(primaryKey)) {
if (firstId) {//类型转换
sb.append(" PRIMARY KEY");
firstId = false;
}
} else {
if (primaryKey.equals(column.toLowerCase())) {
sb.append(" PRIMARY KEY");
}
}
// 添加主键
private static void appendPrimaryKey(String primaryKey, String column, StringBuilder sb) {
if (StringUtils.isEmpty(primaryKey)) {
return;
}
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();
//去掉最后一个逗号
int lastIndex = sql.lastIndexOf(",");
sql = sql.substring(0, lastIndex) + sql.substring(lastIndex + 1);
return sql.substring(0, sql.length() - 1) + endStr;
Future.all(futures).onSuccess(r -> promise.complete()).onFailure(promise::fail);
return promise.future();
}
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);
});
});
}
}

View File

@@ -1,10 +1,13 @@
package cn.qaiu.db.pool;
import cn.qaiu.db.ddl.CreateTable;
import cn.qaiu.db.ddl.CreateDatabase;
import cn.qaiu.vx.core.util.VertxHolder;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.jdbcclient.JDBCPool;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -17,18 +20,29 @@ import org.slf4j.LoggerFactory;
public class JDBCPoolInit {
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;
JsonObject dbConfig;
Vertx vertx = VertxHolder.getVertxInstance();
String url;
private final JDBCType type;
private static JDBCPoolInit instance;
public JDBCType getType() {
return type;
}
public JDBCPoolInit(Builder builder) {
this.dbConfig = builder.dbConfig;
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() {
@@ -42,12 +56,10 @@ public class JDBCPoolInit {
public static class Builder {
private JsonObject dbConfig;
private String url;
private JDBCType type;
public Builder config(JsonObject dbConfig) {
this.dbConfig = dbConfig;
this.url = dbConfig.getString("jdbcUrl");
this.type = JDBCUtil.getJDBCType(dbConfig.getString("driverClassName"));
return this;
}
@@ -59,24 +71,28 @@ public class JDBCPoolInit {
}
}
/**
* init h2db<br>
* 这个方法只允许调用一次
*/
synchronized public void initPool() {
synchronized public Future<Void> initPool() {
if (pool != null) {
LOGGER.error("pool 重复初始化");
return;
return null;
}
// 初始化数据库连接
// 初始化连接池
if (type == JDBCType.MySQL) {
CreateDatabase.createDatabase(dbConfig);
}
pool = JDBCPool.pool(vertx, dbConfig);
CreateTable.createTable(pool, type);
LOGGER.info("数据库连接初始化: URL=" + url);
return CreateTable.createTable(pool, type);
}
/**
* 获取连接池
*

View File

@@ -1,9 +1,35 @@
package cn.qaiu.db.pool;
import org.apache.commons.lang3.StringUtils;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/10/10 14:06
* @since 2023/10/10 14:06
*/
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);
}
}

View File

@@ -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);
}
}

View File

@@ -12,6 +12,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.build.timestamp.format>yyMMdd_HHmm</maven.build.timestamp.format>
</properties>
<dependencies>

View File

@@ -175,8 +175,13 @@ public class RouterHandlerFactory implements BaseHttpApi {
route.handler(ResponseTimeHandler.create());
route.handler(ctx -> handlerMethod(instance, method, ctx)).failureHandler(ctx -> {
if (ctx.response().ended()) return;
ctx.failure().printStackTrace();
doFireJsonResultResponse(ctx, JsonResult.error(ctx.failure().getMessage(), 500));
// 超时处理器状态码503
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)) {
// websocket 基于sockJs

View File

@@ -30,12 +30,10 @@ public class JsonResult<T> implements Serializable {
private int code = SUCCESS_CODE;//状态码
private String msg = SUCCESS_MESSAGE;//消息
private String msg = SUCCESS_MESSAGE; //消息
private boolean success = true; //是否成功
private int count;
private T data;
private long timestamp = System.currentTimeMillis(); //时间戳
@@ -54,20 +52,6 @@ public class JsonResult<T> implements Serializable {
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() {
return code;
}
@@ -136,20 +120,9 @@ public class JsonResult<T> implements Serializable {
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) {
return new JsonResult<>(SUCCESS_CODE, SUCCESS_MESSAGE, true, data, 0);
}
// 响应数据实体
public static <T> JsonResult<T> data(T data, int count) {
return new JsonResult<>(SUCCESS_CODE, SUCCESS_MESSAGE, true, data, count);
return new JsonResult<>(SUCCESS_CODE, SUCCESS_MESSAGE, true, data);
}
// 响应成功消息

View File

@@ -149,7 +149,7 @@ public class CommonUtil {
try {
properties.load(CommonUtil.class.getClassLoader().getResourceAsStream("app.properties"));
if (!properties.isEmpty()) {
appVersion = properties.getProperty("app.version");
appVersion = properties.getProperty("app.version") + "build" + properties.getProperty("build");
}
} catch (IOException e) {
e.printStackTrace();

View File

@@ -26,10 +26,20 @@ public class ResponseUtil {
.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) {
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) {
ctx.response().putHeader(CONTENT_TYPE, "text/html; charset=utf-8").end(text);
}

View File

@@ -1 +1,2 @@
app.version=${project.version}
build=${maven.build.timestamp}

View File

@@ -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>

View File

@@ -20,11 +20,11 @@ Cloudreve自建网盘 (ce) {origin}/s/{shareKey}
缓存key -> 下载URL
分享链接 -> add 网盘类型 pwd origin(私有化) -> 直链
https://f.ws59.cn/f/e3peohu6192
开源版 TODO
1. 缓存优化, 配置自动重载
2. 缓存删除接口(后台功能)
3. JS脚本引擎 自定义解析
专属版 功能设计
@@ -60,7 +60,15 @@ jwt鉴权用户
文件信息: 文件/文件夹, 文件数量, 文件大小, 文件类型; 链接信息: 解析次数, 缓存次数等)
微服务设计:
TODO
后台管理:
菜单:
网盘管理: token配置, 启用/禁用
短链管理: 短链列表, 新增, 删除
解析统计: 下载次数统计, 下载流量统计, 详细解析列表
状态监视: 服务请求并发数; 来源IP列表: 拉黑, 限制次数; Nginx
系统配置: 管理员账户, 系统参数: 域名配置, 预览URL,

View File

@@ -7,52 +7,72 @@ public class FileInfo {
/**
* 文件名
*/
String fileName;
private String fileName;
/**
* 文件ID
*/
String fileId;
private String fileId;
private String fileIcon;
/**
* 文件大小(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格式
*/
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() {
return fileName;
@@ -72,6 +92,15 @@ public class FileInfo {
return this;
}
public String getFileIcon() {
return fileIcon;
}
public FileInfo setFileIcon(String fileIcon) {
this.fileIcon = fileIcon;
return this;
}
public Long getSize() {
return size;
}
@@ -81,12 +110,21 @@ public class FileInfo {
return this;
}
public String getFileMIME() {
return fileMIME;
public String getSizeStr() {
return sizeStr;
}
public FileInfo setFileMIME(String fileMIME) {
this.fileMIME = fileMIME;
public FileInfo setSizeStr(String sizeStr) {
this.sizeStr = sizeStr;
return this;
}
public String getFileType() {
return fileType;
}
public FileInfo setFileType(String fileType) {
this.fileType = fileType;
return this;
}
@@ -108,6 +146,15 @@ public class FileInfo {
return this;
}
public String getUpdateTime() {
return updateTime;
}
public FileInfo setUpdateTime(String updateTime) {
this.updateTime = updateTime;
return this;
}
public String getCreateBy() {
return createBy;
}
@@ -135,6 +182,24 @@ public class FileInfo {
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() {
return extParameters;
}
@@ -143,4 +208,21 @@ public class FileInfo {
this.extParameters = extParameters;
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 +
'}';
}
}

View File

@@ -13,7 +13,13 @@ public class ShareLinkInfo {
private String shareUrl; // 原始分享链接
private String standardUrl; // 规范化的标准链接
private Map<String, Object> otherParam; // 其他参数
/**
* 其他参数预定义
* auths: 认证相关 传入
* UA: 浏览器请求头 传入
* fileInfo: 解析成功的文件信息对象 传出
*/
private Map<String, Object> otherParam;
private ShareLinkInfo(Builder builder) {
this.shareKey = builder.shareKey;
@@ -77,7 +83,11 @@ public class ShareLinkInfo {
public String getCacheKey() {
// 将type和shareKey组合成一个字符串作为缓存key
return type + ":" + shareKey;
String key = type + ":" + shareKey;
if (type.equals("p115")) {
key += ("_" + otherParam.get("UA").toString().hashCode());
}
return key;
}
public ShareLinkInfo setOtherParam(Map<String, Object> otherParam) {

View File

@@ -1,6 +1,10 @@
package cn.qaiu.parser;//package cn.qaiu.lz.common.parser;
import cn.qaiu.entity.FileInfo;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import java.util.List;
public interface IPanTool {
Future<String> parse();
@@ -8,4 +12,24 @@ public interface IPanTool {
default String parseSync() {
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();
}
}

View File

@@ -5,12 +5,10 @@ import cn.qaiu.entity.ShareLinkInfo;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.json.DecodeException;
import io.vertx.core.json.Json;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.ProxyOptions;
import io.vertx.core.net.ProxyType;
import io.vertx.core.net.impl.VertxHandler;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
@@ -19,9 +17,14 @@ import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
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.Iterator;
import java.util.Map;
import java.util.zip.GZIPInputStream;
/**
* 解析器抽象类包含promise, HTTP Client, 默认失败方法等;
@@ -39,7 +42,7 @@ public abstract class PanBase implements IPanTool {
* Http client
*/
protected WebClient client = WebClient.create(WebClientVertxInit.get(),
new WebClientOptions().setUserAgentEnabled(false));
new WebClientOptions());
/**
* Http client session (会话管理, 带cookie请求)
@@ -77,7 +80,7 @@ public abstract class PanBase implements IPanTool {
proxyOptions.setUsername(proxy.getString("username"));
}
if (StringUtils.isNotEmpty(proxy.getString("password"))) {
proxyOptions.setUsername(proxy.getString("password"));
proxyOptions.setPassword(proxy.getString("password"));
}
this.client = WebClient.create(WebClientVertxInit.get(),
new WebClientOptions()
@@ -95,6 +98,14 @@ public abstract class PanBase implements IPanTool {
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();
}
/**
* 失败时生成异常消息
@@ -107,11 +118,11 @@ public abstract class PanBase implements IPanTool {
try {
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
log.error("解析异常: " + s, t.fillInStackTrace());
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + ": 解析异常: " + s + " -> " + t);
promise.fail(baseMsg() + ": 解析异常: " + s + " -> " + t);
} catch (Exception e) {
log.error("ErrorMsg format fail. The parameter has been discarded", e);
log.error("解析异常: " + errorMsg, t.fillInStackTrace());
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + ": 解析异常: " + errorMsg + " -> " + t);
promise.fail(baseMsg() + ": 解析异常: " + errorMsg + " -> " + t);
}
}
@@ -124,10 +135,10 @@ public abstract class PanBase implements IPanTool {
protected void fail(String errorMsg, Object... args) {
try {
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 解析异常: " + s);
promise.fail(baseMsg() + " - 解析异常: " + s);
} catch (Exception e) {
log.error("ErrorMsg format fail. The parameter has been discarded", e);
promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 解析异常: " + errorMsg);
promise.fail(baseMsg() + " - 解析异常: " + errorMsg);
}
}
@@ -142,7 +153,7 @@ public abstract class PanBase implements IPanTool {
* @return Handler
*/
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() {
@@ -152,15 +163,59 @@ public abstract class PanBase implements IPanTool {
/**
* bodyAsJsonObject的封装, 会自动处理异常
*
* @param res HttpResponse
* @return JsonObject
*/
protected JsonObject asJson(HttpResponse<?> res) {
// 检查响应头中的Content-Encoding是否为gzip
String contentEncoding = res.getHeader("Content-Encoding");
try {
return res.bodyAsJsonObject();
} catch (DecodeException e) {
fail("解析失败: json格式异常: {}", res.bodyAsString());
throw new RuntimeException("解析失败: json格式异常");
if ("gzip".equalsIgnoreCase(contentEncoding)) {
// 如果是gzip压缩的响应体解压
return new JsonObject(decompressGzip((Buffer) res.body()));
} 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格式异常");
}
}
@@ -194,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();
}
}

View File

@@ -25,7 +25,7 @@ public enum PanDomainTemplate {
// 网盘定义
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}",
LzTool.class),
@@ -59,6 +59,12 @@ public enum PanDomainTemplate {
"https://iwx.mail.qq.com/ftn/download/{shareKey}",
"https://mail.qq.com",
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/
WS("文叔叔",
compile("https://(f\\.ws(\\d{2})\\.cn|www\\.wenshushu\\.cn)/f/(?<KEY>.+)"),
@@ -81,7 +87,8 @@ public enum PanDomainTemplate {
"https://cowtransfer.com/s/{shareKey}",
CowTool.class),
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}",
CtTool.class),
// https://xxx.118pan.com/bxxx
@@ -90,13 +97,16 @@ public enum PanDomainTemplate {
"https://qaiu.118pan.com/b{shareKey}",
P118Tool.class),
// https://www.vyuyun.com/s/QMa6ie?password=I4KG7H
// https://www.vyuyun.com/s/QMa6ie/file?password=I4KG7H
PVYY("微雨云存储",
compile("https://www\\.vyuyun\\.com/s/(?<KEY>[a-zA-Z\\d-]+)(\\?password=.*)?"),
"https://www.vyuyun.com/s/{shareKey}",
compile("https://www\\.vyuyun\\.com/s/(?<KEY>[a-zA-Z\\d-]+)(/file)?(\\?password=(?<PWD>\\w+))?"),
"https://www.vyuyun.com/s/{shareKey}?password={pwd}",
PvyyTool.class),
// https://1drv.ms/w/s!Alg0feQmCv2rnRFd60DQOmMa-Oh_?e=buaRtp
// https://1drv.ms/u/c/abfd0a26e47d3458/EdYACWvPq85Et797YmvL5LgBruUKoNxqIFATXhIv1PI2_Q?e=z4ffNJ
POD("OneDrive",
compile("https://1drv\\.ms/[uw]/s!(?<KEY>.+)"),
compile("https://1drv\\.ms/(?<KEY>.+)"),
"https://1drv\\.ms/{shareKey}",
"https://onedrive.live.com/",
PodTool.class),
// 404网盘 https://drive.google.com/file/d/xxx/view?usp=sharing
@@ -114,7 +124,21 @@ public enum PanDomainTemplate {
compile("https://www.dropbox.com/scl/fi/(?<KEY>\\w+)/.+?rlkey=(?<PWD>\\w+).*"),
"https://www.dropbox.com/scl/fi/{shareKey}/?rlkey={pwd}&dl=0",
PdbTool.class),
P115("115网盘",
compile("https://(115|anxia).com/s/(?<KEY>\\w+)(\\?password=(?<PWD>\\w+))?([&#].*)?"),
"https://115.com/s/{shareKey}?password={pwd}",
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 (单歌曲/普通音质)==========================
// http://163cn.tv/xxx
MNES("网易云音乐分享",
@@ -123,7 +147,7 @@ public enum PanDomainTemplate {
MnesTool.class),
// https://music.163.com/#/song?id=xxx
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}",
MnesTool.MneTool.class),
// https://c6.y.qq.com/base/fcgi-bin/u?__=xxx
@@ -162,7 +186,7 @@ public enum PanDomainTemplate {
MMGS("咪咕音乐分享",
compile("https://music\\.migu\\.cn/v3/music/song/(?<KEY>.+)(\\?.*)?"),
"https://music.migu.cn/v3/music/song/{shareKey}",
MkwTool.class),
MmgTool.class),
// =====================私有盘解析==========================
// Cloudreve自定义域名解析, 解析器CeTool兜底策略, 即任意域名如果匹配不到对应的规则, 则由CeTool统一处理,

View File

@@ -3,6 +3,8 @@ package cn.qaiu.parser;
import cn.qaiu.entity.ShareLinkInfo;
import org.apache.commons.lang3.StringUtils;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import static cn.qaiu.parser.PanDomainTemplate.KEY;
@@ -20,9 +22,12 @@ public class ParserCreate {
private final PanDomainTemplate panDomainTemplate;
private final ShareLinkInfo shareLinkInfo;
private String standardUrl;
public ParserCreate(PanDomainTemplate panDomainTemplate, ShareLinkInfo shareLinkInfo) {
this.panDomainTemplate = panDomainTemplate;
this.shareLinkInfo = shareLinkInfo;
this.standardUrl = panDomainTemplate.getStandardUrlTemplate();
}
@@ -38,15 +43,19 @@ public class ParserCreate {
}
Matcher matcher = this.panDomainTemplate.getPattern().matcher(shareUrl);
if (matcher.find()) {
String shareKey = matcher.group(KEY);
String k0 = matcher.group(KEY);
String shareKey = URLEncoder.encode(k0, StandardCharsets.UTF_8);
// 返回规范化的标准链接
String standardUrl = getStandardUrlTemplate()
.replace("{shareKey}", shareKey);
standardUrl = getStandardUrlTemplate()
.replace("{shareKey}", k0);
try {
String pwd = matcher.group(PWD);
standardUrl = standardUrl .replace("{pwd}", pwd);
if (StringUtils.isNotEmpty(pwd)) {
shareLinkInfo.setSharePassword(pwd);
}
standardUrl = standardUrl.replace("{pwd}", pwd);
} catch (Exception ignored) {}
shareLinkInfo.setShareUrl(shareUrl);
@@ -86,7 +95,11 @@ public class ParserCreate {
shareLinkInfo.setShareUrl(standardUrl);
} else {
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;
}
@@ -107,7 +120,14 @@ public class ParserCreate {
}
public ParserCreate setShareLinkInfoPwd(String pwd) {
shareLinkInfo.setSharePassword(pwd);
if (pwd != null) {
shareLinkInfo.setSharePassword(pwd);
standardUrl = standardUrl.replace("{pwd}", pwd);
shareLinkInfo.setStandardUrl(standardUrl);
if (shareLinkInfo.getShareUrl().contains("{pwd}")) {
shareLinkInfo.setShareUrl(standardUrl);
}
}
return this;
}

View File

@@ -9,15 +9,25 @@ import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpRequest;
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>
*/
public class CtTool extends PanBase {
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=";
//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?" +
"uid={uid}&fid={fid}&folder_id=0&file_chk={file_chk}&mb=0&token={token}&app=0&acheck=0&verifycode=" +
"&rd={rand}";
@@ -49,8 +59,13 @@ public class CtTool extends PanBase {
String[] split = shareKey.split("-");
String uid = split[0], fid = split[1];
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))
.setTemplateParam("path", path)
.setTemplateParam("shareKey", shareKey)
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
.setTemplateParam("token", token)
@@ -79,10 +94,10 @@ public class CtTool extends PanBase {
}
}).onFailure(handleFail(bufferHttpRequest1.queryParams().toString()));
} else {
fail("解析失败, 可能分享已失效: json: {} 字段 {} 不存在", resJson, "file_chk");
fail("解析失败, file_chk找不到, 可能分享已失效或者分享密码不对: {}", fileJson);
}
} else {
fail("解析失败, 可能分享已失效: json: {} 字段 {} 不存在", resJson, "file");
fail("解析失败, 文件信息为空, 可能分享已失效");
}
}).onFailure(handleFail(bufferHttpRequest1.queryParams().toString()));
return promise.future();

View File

@@ -1,6 +1,6 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.json.JsonArray;

View File

@@ -1,16 +1,30 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.entity.FileInfo;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.AESUtils;
import cn.qaiu.util.FileSizeConverter;
import cn.qaiu.util.UUIDUtil;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpRequest;
import io.vertx.uritemplate.UriTemplate;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.zip.GZIPInputStream;
/**
* 小飞机网盘
*
@@ -22,8 +36,9 @@ public class FjTool extends PanBase {
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "recommend/list?devType=6&devModel=Chrome" +
"&uuid={uuid}&extra=2&timestamp={ts}&shareId={shareId}&type=0&offset=1&limit=60";
/// recommend/list?devType=6&devModel=Chrome&uuid={uuid}&extra=2&timestamp={ts}&shareId={shareId}&type=0&offset=1
// &limit=60
/// recommend/list?devType=6&devModel=Chrome&uuid={uuid}&extra=2&timestamp={ts}&shareId={shareId}&type=0&offset=1&limit=60
// recommend/list?devType=6&devModel=Chrome&uuid={uuid}&extra=2&timestamp={ts}&shareId=JoUTkZYj&type=0&offset=1&limit=60
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "file/redirect?downloadId={fidEncode}&enable=1" +
"&devType=6&uuid={uuid}&timestamp={ts}&auth={auth}&shareId={dataKey}";
@@ -34,18 +49,50 @@ public class FjTool extends PanBase {
"={uuid}&extra=2&timestamp={ts}";
// https://api.feijipan.com/ws/buy/vip/list?devType=6&devModel=Chrome&uuid=WQAl5yBy1naGudJEILBvE&extra=2&timestamp=E2C53155F6D09417A27981561134CB73
// https://api.feijipan.com/ws/share/list?devType=6&devModel=Chrome&uuid=pwRWqwbk1J-KMTlRZowrn&extra=2&timestamp=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&timestamp={ts}&shareId={shareId}&folderId" +
"={folderId}&offset=1&limit=60";
private static final MultiMap header;
long nowTs = System.currentTimeMillis();
String tsEncode = AESUtils.encrypt2Hex(Long.toString(nowTs));
String uuid = UUIDUtil.fjUuid(); // 也可以使用 UUID.randomUUID().toString()
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.feijipan.com");
header.set("Origin", "https://www.feijix.com");
header.set("Pragma", "no-cache");
header.set("Referer", "https://www.feijix.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 FjTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
final String dataKey = shareLinkInfo.getShareKey();
// 240530 此处shareId又改为了原始的shareId
String shareId = dataKey; // String.valueOf(AESUtils.idEncrypt(dataKey));
long nowTs = System.currentTimeMillis();
String tsEncode = AESUtils.encrypt2Hex(Long.toString(nowTs));
String uuid = UUIDUtil.fjUuid(); // 也可以使用 UUID.randomUUID().toString()
// String.valueOf(AESUtils.idEncrypt(dataKey));
final String shareId = shareLinkInfo.getShareKey();
// 24.5.12 飞机盘 规则修改 需要固定UUID先请求会员接口, 再请求后续接口
client.postAbs(UriTemplate.of(VIP_REQUEST_URL))
@@ -53,16 +100,41 @@ public class FjTool extends PanBase {
.setTemplateParam("ts", tsEncode)
.send().onSuccess(r0 -> { // 忽略res
// long nowTs0 = System.currentTimeMillis();
String tsEncode0 = AESUtils.encrypt2Hex(Long.toString(nowTs));
// 第一次请求 获取文件信息
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL))
.putHeaders(header)
.setTemplateParam("shareId", shareId)
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode0)
.setTemplateParam("ts", tsEncode)
.send().onSuccess(res -> {
JsonObject resJson = asJson(res);
// 处理GZ压缩
// 使用GZIPInputStream来解压数据
String decompressedString;
try (ByteArrayInputStream bais = new ByteArrayInputStream(res.body().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()包含了解压后的字符串
decompressedString = decompressedData.toString();
} catch (IOException e) {
// 处理可能的IO异常
fail(FIRST_REQUEST_URL + " 响应异常");
return;
}
// 处理GZ压缩结束
JsonObject resJson = new JsonObject(decompressedString);
if (resJson.getInteger("code") != 200) {
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
return;
@@ -73,6 +145,13 @@ public class FjTool extends PanBase {
}
// 文件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");
// 其他参数
@@ -81,17 +160,15 @@ public class FjTool extends PanBase {
String fidEncode = AESUtils.encrypt2Hex(fileId + "|" + userId);
String auth = AESUtils.encrypt2Hex(fileId + "|" + nowTs2);
MultiMap headers0 = MultiMap.caseInsensitiveMultiMap();
headers0.set("referer", REFERER_URL);
// 第二次请求
HttpRequest<Buffer> httpRequest =
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
.putHeaders(headers0)
.putHeaders(header)
.setTemplateParam("fidEncode", fidEncode)
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode2)
.setTemplateParam("auth", auth)
.setTemplateParam("dataKey", dataKey);
.setTemplateParam("dataKey", shareId);
System.out.println(httpRequest.toString());
httpRequest.send().onSuccess(res2 -> {
MultiMap headers = res2.headers();
@@ -107,4 +184,124 @@ public class FjTool extends PanBase {
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();
}
}

View File

@@ -1,14 +1,19 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.FileInfo;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.AESUtils;
import cn.qaiu.util.FileSizeConverter;
import cn.qaiu.util.UUIDUtil;
import io.vertx.core.Future;
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.uritemplate.UriTemplate;
import java.util.UUID;
import java.util.*;
/**
* 蓝奏云优享
@@ -16,65 +21,207 @@ import java.util.UUID;
*/
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 FIRST_REQUEST_URL = API_URL_PREFIX +
"recommend/list?devModel=Chrome&extra=2&shareId={shareId}&type=0&offset=1&limit=60";
private static final String FIRST_REQUEST_URL = API_URL_PREFIX + "recommend/list?devType=6&devModel=Chrome" +
"&uuid={uuid}&extra=2&timestamp={ts}&shareId={shareId}&type=0&offset=1&limit=60";
private static final String SECOND_REQUEST_URL = API_URL_PREFIX +
"file/redirect?downloadId={fidEncode}&enable=1&devType=6&uuid={uuid}&timestamp={ts}&auth={auth}&shareId={shareId}";
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "file/redirect?downloadId={fidEncode}&enable=1" +
"&devType=6&uuid={uuid}&timestamp={ts}&auth={auth}&shareId={dataKey}";
// downloadId=x&enable=1&devType=6&uuid=x&timestamp=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&timestamp={ts}";
private static final String FILE_LIST_URL = API_URL_PREFIX + "/share/list?devType=6&devModel=Chrome&uuid" +
"={uuid}&extra=2&timestamp={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) {
super(shareLinkInfo);
}
public Future<String> parse() {
String dataKey = shareLinkInfo.getShareKey();
String shareId = shareLinkInfo.getShareKey();
// 24.5.12 ilanzou改规则无需计算shareId
// String shareId = String.valueOf(AESUtils.idEncryptIz(dataKey));
// 第一次请求 获取文件信息
// POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("shareId", dataKey).send().onSuccess(res -> {
JsonObject resJson = asJson(res);
if (resJson.getInteger("code") != 200) {
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
return;
}
if (resJson.getJsonArray("list").size() == 0) {
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
return;
}
// 文件Id
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
String fileId = fileInfo.getString("fileIds");
String userId = fileInfo.getString("userId");
// 其他参数
long nowTs = System.currentTimeMillis();
String tsEncode = AESUtils.encrypt2HexIz(Long.toString(nowTs));
String uuid = UUID.randomUUID().toString();
// String fidEncode = AESUtils.encrypt2HexIz(fileId + "|");
String fidEncode = AESUtils.encrypt2HexIz(fileId + "|" + userId);
String auth = AESUtils.encrypt2HexIz(fileId + "|" + nowTs);
// 第二次请求
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL))
.setTemplateParam("fidEncode", fidEncode)
// 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(VIP_REQUEST_URL))
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode)
.send().onSuccess(r0 -> { // 忽略res
// 第一次请求 获取文件信息
// 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(
shareLinkInfo.getSharePassword() == null ?
FIRST_REQUEST_URL : (FIRST_REQUEST_URL + "&code=" + shareLinkInfo.getSharePassword()))
)
.putHeaders(header)
.setTemplateParam("shareId", shareId)
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode)
.send().onSuccess(res -> {
JsonObject resJson = asJson(res);
if (resJson.getInteger("code") != 200) {
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
return;
}
if (resJson.getJsonArray("list").size() == 0) {
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
return;
}
// 文件Id
JsonObject fileInfo = resJson.getJsonArray("list").getJsonObject(0);
// 如果是目录返回目录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("ts", tsEncode)
.setTemplateParam("auth", auth)
.setTemplateParam("shareId", dataKey).send().onSuccess(res2 -> {
MultiMap headers = res2.headers();
if (!headers.contains("Location")) {
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res.headers());
return;
}
promise.complete(headers.get("Location"));
}).onFailure(handleFail(SECOND_REQUEST_URL));
}).onFailure(handleFail(FIRST_REQUEST_URL));
.setTemplateParam("folderId", id)
.send().onSuccess(res -> {
JsonObject jsonObject = asJson(res);
System.out.println(jsonObject.encodePrettily());
JsonArray list = jsonObject.getJsonArray("list");
ArrayList<FileInfo> result = new ArrayList<>();
list.forEach(item->{
JsonObject fileJson = (JsonObject) item;
FileInfo fileInfo = new FileInfo();
// 映射已知字段
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();
}
}

View File

@@ -1,15 +1,22 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.entity.FileInfo;
import cn.qaiu.entity.ShareLinkInfo;
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 io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
import javax.script.ScriptException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -28,7 +35,6 @@ public class LzTool extends PanBase {
super(shareLinkInfo);
}
@SuppressWarnings("unchecked")
public Future<String> parse() {
String sUrl = shareLinkInfo.getStandardUrl();
String pwd = shareLinkInfo.getSharePassword();
@@ -41,23 +47,11 @@ public class LzTool extends PanBase {
Matcher matcher = compile.matcher(html);
// 没有Iframe说明是加密分享, 匹配sign通过密码请求下载页面
if (!matcher.find()) {
// 处理一下JS
String jsText = getJsText(html);
if (jsText == null) {
fail(SHARE_URL_PREFIX + " -> " + sUrl + ": js脚本匹配失败, 可能分享已失效");
return;
}
jsText = jsText.replace("document.getElementById('pwd').value", "\"" + pwd + "\"");
int i = jsText.indexOf("document.getElementById('rpt')");
if (i > 0) {
jsText = jsText.substring(0, i);
}
try {
String jsText = getJsByPwd(pwd, html, "document.getElementById('rpt')");
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, "down_p");
getDownURL(sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
} catch (ScriptException | NoSuchMethodException e) {
getDownURL(sUrl, client, scriptObjectMirror);
} catch (Exception e) {
fail(e, "js引擎执行失败");
}
} else {
@@ -75,7 +69,7 @@ public class LzTool extends PanBase {
}
try {
ScriptObjectMirror scriptObjectMirror = JsExecUtils.executeDynamicJs(jsText, null);
getDownURL(sUrl, client, (Map<String, String>) scriptObjectMirror.get("data"));
getDownURL(sUrl, client, scriptObjectMirror);
} catch (ScriptException | NoSuchMethodException e) {
fail(e, "js引擎执行失败");
}
@@ -85,6 +79,20 @@ public class LzTool extends PanBase {
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) {
String jsTagStart = "<script type=\"text/javascript\">";
String jsTagEnd = "</script>";
@@ -94,14 +102,60 @@ public class LzTool extends PanBase {
}
int startPos = index + jsTagStart.length();
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();
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();
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
"like " +
@@ -111,18 +165,73 @@ public class LzTool extends PanBase {
headers.set("sec-ch-ua-platform", "Android");
headers.set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
return headers;
}
String url = SHARE_URL_PREFIX + "/ajaxm.php";
client.postAbs(url).putHeaders(headers).sendForm(map).onSuccess(res2 -> {
JsonObject urlJson = asJson(res2);
if (urlJson.getInteger("zt") != 1) {
fail(urlJson.getString("inf"));
return;
@Override
public Future<List<FileInfo>> parseFileList() {
Promise<List<FileInfo>> promise = Promise.promise();
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()
.onSuccess(res3 -> promise.complete(res3.headers().get("Location")))
.onFailure(handleFail(downUrl));
}).onFailure(handleFail(url));
});
return promise.future();
}
}

View File

@@ -0,0 +1,90 @@
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;
/**
* 115网盘
*
* 需要请求API的UA和请求下载链接的UA保持一致安卓Chrome需要访问电脑版才能下载
*/
public class P115Tool extends PanBase {
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" +
"&limit=20&receive_code={dataPwd}&cid=";
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "share/skip_login_downurl";
private static final MultiMap header;
static {
header = MultiMap.caseInsensitiveMultiMap();
header.set("Accept", "application/json, text/plain, */*");
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", "115cdn.com");
header.set("Origin", "https://115cdn.com");
header.set("Pragma", "no-cache");
header.set("Referer", "https://115cdn.com");
header.set("Sec-Fetch-Dest", "empty");
header.set("Sec-Fetch-Mode", "cors");
header.set("Sec-Fetch-Site", "cross-site");
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 P115Tool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
// 第一次请求 获取文件信息
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL))
.putHeaders(header)
.putHeader("User-Agent", shareLinkInfo.getOtherParam().get("UA").toString())
.setTemplateParam("dataKey", shareLinkInfo.getShareKey())
.setTemplateParam("dataPwd", shareLinkInfo.getSharePassword())
.send().onSuccess(res -> {
JsonObject resJson = asJson(res);
if (!resJson.getBoolean("state")) {
fail(FIRST_REQUEST_URL + " 解析错误: " + resJson);
return;
}
// 文件Id: data.list[0].fid
JsonObject fileInfo = resJson.getJsonObject("data").getJsonArray("list").getJsonObject(0);
String fileId = fileInfo.getString("fid");
// 第二次请求
// share_code={dataKey}&receive_code={dataPwd}&file_id={file_id}
client.postAbs(SECOND_REQUEST_URL)
.putHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
.putHeader("User-Agent", shareLinkInfo.getOtherParam().get("UA").toString())
.sendForm(MultiMap.caseInsensitiveMultiMap()
.set("share_code", shareLinkInfo.getShareKey())
.set("receive_code", shareLinkInfo.getSharePassword())
.set("file_id", fileId))
.onSuccess(res2 -> {
JsonObject resJson2 = asJson(res2);
if (!resJson2.getBoolean("state")) {
fail(FIRST_REQUEST_URL + " 解析错误: " + resJson2);
return;
}
// data.url.url
promise.complete(resJson2.getJsonObject("data").getJsonObject("url").getString("url"));
}).onFailure(handleFail(SECOND_REQUEST_URL));
}).onFailure(handleFail(FIRST_REQUEST_URL));
return promise.future();
}
}

View 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);
// }
}

View 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);
// }
}

View File

@@ -1,10 +1,20 @@
package cn.qaiu.parser.impl;
import cn.qaiu.WebClientVertxInit;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.WorkerExecutor;
import io.vertx.core.json.JsonObject;
import io.vertx.uritemplate.UriTemplate;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -21,47 +31,201 @@ public class PodTool extends PanBase {
* -> @content.downloadUrl
*/
private static final String api = "https://api.onedrive.com/v1.0/drives/{cid}/items/{cid20}?authkey={authkey}";
// https://onedrive.live.com/redir?resid=ABFD0A26E47D3458!4699&e=OggA4s&migratedtospo=true&redeem=aHR0cHM6Ly8xZHJ2Lm1zL3UvcyFBbGcwZmVRbUN2MnJwRnZ1NDQ0aGc1eVZxRGNLP2U9T2dnQTRz
private static final String API_TEMPLATE = "https://onedrive.live.com/embed" +
"?id={resid}&resid={resid1}" +
"&cid={cid}" +
"&redeem={redeem}" +
"&migratedtospo=true&embed=1";
private static final String TOKEN_API = "https://api-badgerp.svc.ms/v1.0/token";
private static final Pattern redirectUrlRegex =
Pattern.compile("=(?<cid>.+)!(?<cid2>.+)&authkey=(?<authkey>.+)&e=(?<e>.+)");
Pattern.compile("resid=(?<cid1>[^!]+)!(?<cid2>[^&]+).+&redeem=(?<redeem>.+).*");
public PodTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
clientNoRedirects.getAbs(shareLinkInfo.getShareUrl()).send().onSuccess(r0 -> {
/*
* POST https://api-badgerp.svc.ms/v1.0/token
* Content-Type: application/json
*
* {
* "appid": "00000000-0000-0000-0000-0000481710a4"
* }
*/
// https://my.microsoftpersonalcontent.com/_api/v2.0/shares/u!aHR0cHM6Ly8xZHJ2Lm1zL3UvcyFBbGcwZmVRbUN2MnJwRnZ1NDQ0aGc1eVZxRGNLP2U9T2dnQTRz/driveitem?%24select=*%2Cocr%2CwebDavUrl
// https://onedrive.live.com/embed?id=ABFD0A26E47D3458!4698&resid=ABFD0A26E47D3458!4698&cid=abfd0a26e47d3458&redeem=aHR0cHM6Ly8xZHJ2Lm1zL3UvYy9hYmZkMGEyNmU0N2QzNDU4L0lRUllOSDNrSmdyOUlJQ3JXaElBQUFBQUFTWGlubWZ2WmNxYUQyMXJUQjIxVmg4&migratedtospo=true&embed=1
clientNoRedirects.getAbs(shareLinkInfo.getShareUrl() == null ? shareLinkInfo.getStandardUrl() :
shareLinkInfo.getShareUrl()).send().onSuccess(r0 -> {
String location = r0.getHeader("Location");
Matcher matcher = redirectUrlRegex.matcher(location);
if (matcher.find()) {
var cid= matcher.group("cid");
var cid2= matcher.group("cid2");
var authkey= matcher.group("authkey");
client.getAbs(UriTemplate.of(api))
.setTemplateParam("cid", cid)
.setTemplateParam("cid20", cid + "!" + cid2)
.setTemplateParam("authkey", authkey).send()
.onSuccess(res -> {
JsonObject jsonObject = asJson(res);
// System.out.println(jsonObject);
complete(jsonObject.getString("@content.downloadUrl"));
})
.onFailure(handleFail());
if (!matcher.find()) {
fail("Location格式错误");
return;
}
String redeem = matcher.group("redeem");
String cid1 = matcher.group("cid1");
String cid2 = cid1 + "!" + matcher.group("cid2");
clientNoRedirects.getAbs(UriTemplate.of(API_TEMPLATE))
.setTemplateParam("resid", cid2)
.setTemplateParam("resid1", cid2)
.setTemplateParam("cid", cid1.toLowerCase())
.setTemplateParam("redeem", redeem)
.send()
.onSuccess(r1 -> {
String auth =
r1.cookies().stream().filter(c -> c.startsWith("BadgerAuth=")).findFirst().orElse("");
if (auth.isEmpty()) {
fail("Error BadgerAuth not fount");
return;
}
String token = auth.split(";")[0].split("=")[1];
try {
String url = matcherUrl(r1.bodyAsString());
sendHttpRequest(url, token).onSuccess(body -> {
Matcher matcher1 =
Pattern.compile("\"downloadUrl\":\"(?<url>https?://[^\s\"]+)").matcher(body);
if (matcher1.find()) {
complete(matcher1.group("url"));
} else {
fail();
}
}).onFailure(handleFail());
} catch (Exception ignored) {
sendHttpRequest2(token, redeem).onSuccess(res -> {
try {
complete(new JsonObject(res).getString("@content.downloadUrl"));
} catch (Exception ignored1) {
fail();
}
}).onFailure(handleFail());
}
}).onFailure(handleFail());
}).onFailure(handleFail());
return promise.future();
}
private String matcherUrl(String html) {
// 正则表达式来匹配 URL
String urlRegex = "'action'.+(?<url>https://.+)'\\)";
Pattern urlPattern = Pattern.compile(urlRegex);
Matcher urlMatcher = urlPattern.matcher(html);
if (urlMatcher.find()) {
String url = urlMatcher.group("url");
System.out.println("URL: " + url);
return url;
}
throw new RuntimeException("URL匹配失败");
}
private String matcherToken(String html) {
// 正则表达式来匹配 inputElem.value 中的 Token
String tokenRegex = "inputElem\\.value\\s*=\\s*'([^']+)'";
Pattern tokenPattern = Pattern.compile(tokenRegex);
Matcher tokenMatcher = tokenPattern.matcher(html);
if (tokenMatcher.find()) {
String token = tokenMatcher.group(1);
System.out.println("Token: " + token);
return token;
}
throw new RuntimeException("token匹配失败");
}
public Future<String> sendHttpRequest2(String token, String redeem) {
Promise<String> promise = Promise.promise();
// 构造 HttpClient
HttpClient client = HttpClient.newHttpClient();
// 构造请求的 URI 和头部信息
// https://onedrive.live.com/redir?cid=abfd0a26e47d3458&resid=ABFD0A26E47D3458!4465&ithint=file%2cxlsx&e=Ao2uSU&migratedtospo=true&redeem=aHR0cHM6Ly8xZHJ2Lm1zL3gvYy9hYmZkMGEyNmU0N2QzNDU4L0VWZzBmZVFtQ3YwZ2dLdHhFUUFBQUFBQlRQRWVDMTZfZk1EYk5FTjhEdTRta1E_ZT1BbzJ1U1U
String url = ("https://my.microsoftpersonalcontent.com/_api/v2.0/shares/u!%s/driveItem?$select=content" +
".downloadUrl").formatted(redeem);
String authorizationHeader = "Badger " + token;
// 构建请求
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", authorizationHeader)
.build();
// 发送请求并处理响应
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(response -> {
System.out.println("Response Status Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
promise.complete(response.body());
return null;
});
return promise.future();
}
//https://onedrive.live.com/redir?resid=ABFD0A26E47D3458!4699&e=OggA4s&migratedtospo=true&redeem=aHR0cHM6Ly8xZHJ2Lm1zL3UvcyFBbGcwZmVRbUN2MnJwRnZ1NDQ0aGc1eVZxRGNLP2U9T2dnQTRz
// public static void main(String[] args) {
// Matcher matcher = redirectUrlRegex.matcher("https://onedrive.live.com/redir?resid=ABFD0A26E47D3458!4698" +
// "&authkey=!ACpvXghP5xhG_cg&e=hV98W1");
// if (matcher.find()) {
// System.out.println(matcher.group("cid"));
// System.out.println(matcher.group("cid2"));
// System.out.println(matcher.group("authkey"));
// }
public Future<String> sendHttpRequest(String url, String token) {
// 创建一个 WorkerExecutor 用于异步执行阻塞的 HTTP 请求
WorkerExecutor executor = WebClientVertxInit.get().createSharedWorkerExecutor("http-client-worker");
// }
Promise<String> promise = Promise.promise();
executor.executeBlocking(() -> {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = null;
try {
// 构造请求
request = HttpRequest.newBuilder()
.uri(new URI(url))
.header("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")
.header("accept-language", "zh-CN,zh;q=0.9")
.header("cache-control", "no-cache")
.header("content-type", "application/x-www-form-urlencoded")
.header("dnt", "1")
.header("origin", "https://onedrive.live.com")
.header("pragma", "no-cache")
.header("priority", "u=0, i")
.header("referer", "https://onedrive.live.com/")
.header("sec-ch-ua", "\"Chromium\";v=\"130\", \"Google Chrome\";v=\"130\", " +
"\"Not?A_Brand\";v=\"99\"")
.header("sec-ch-ua-mobile", "?0")
.header("sec-ch-ua-platform", "\"Windows\"")
.header("sec-fetch-dest", "iframe")
.header("sec-fetch-mode", "navigate")
.header("sec-fetch-site", "cross-site")
.header("upgrade-insecure-requests", "1")
.header("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537" +
".36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36")
.POST(HttpRequest.BodyPublishers.ofString("badger_token=" + token))
.build();
// 发起请求并获取响应
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// 返回响应体
promise.complete(response.body());
return null;
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new RuntimeException(e);
}
});
return promise.future();
}
}

View File

@@ -2,7 +2,9 @@ package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.HeaderUtils;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
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_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) {
super(shareLinkInfo);
}
@@ -21,7 +41,7 @@ public class PvyyTool extends PanBase {
client.getAbs(UriTemplate.of(API_URL_PREFIX1))
.setTemplateParam("key", shareLinkInfo.getShareKey())
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
.putHeader("referer", "https://www.vyuyun.com")
.putHeaders(header)
.send().onSuccess(res -> {
try {
String id = asJson(res).getJsonObject("data").getJsonObject("data").getString("id");
@@ -30,8 +50,7 @@ public class PvyyTool extends PanBase {
.setTemplateParam("key", shareLinkInfo.getShareKey())
.setTemplateParam("pwd", shareLinkInfo.getSharePassword())
.setTemplateParam("id", id)
.putHeader("referer", "https://www.vyuyun.com")
.send().onSuccess(res2 -> {
.putHeaders(header).send().onSuccess(res2 -> {
try {
// data->downInfo->url
String url =

View 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;
}
}

View 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
);
}
}

View File

@@ -5,6 +5,7 @@ import cn.qaiu.parser.PanBase;
import cn.qaiu.util.CommonUtils;
import cn.qaiu.util.JsExecUtils;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import io.vertx.uritemplate.UriTemplate;
@@ -17,6 +18,8 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static cn.qaiu.util.RandomStringGenerator.gen36String;
/**
* 123网盘
*/
@@ -30,8 +33,27 @@ public class YeTool extends PanBase {
"&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 final MultiMap header = MultiMap.caseInsensitiveMultiMap();
public YeTool(ShareLinkInfo 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() {

View 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;
}
}

View 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));
}
}
}

View 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;
}
}

View File

@@ -124,7 +124,10 @@ public interface JsContent {
},
ajax: function (obj) {
signObj = obj
}
},
val: function(a) {
},
}
},
@@ -134,7 +137,6 @@ public interface JsContent {
jQuery.fn.init.prototype = jQuery.fn;
// 伪装jquery.ajax函数获取关键数据
$.ajax = function (obj) {
signObj = obj
}
@@ -142,11 +144,16 @@ public interface JsContent {
var document = {
getElementById: function (v) {
return {
value: 'v'
value: 'v',
style: {
display: ''
},
addEventListener: function() {}
}
},
}
var window = {location: {}}
""";
String kwSignString = """

View File

@@ -1,6 +1,7 @@
package cn.qaiu.util;
import java.security.SecureRandom;
import java.util.UUID;
public class RandomStringGenerator {
private static final String CHARACTERS = "abcdefghijklmnopqrstuvwxyz0123456789";
@@ -19,4 +20,10 @@ public class RandomStringGenerator {
return sb.toString();
}
public static String gen36String() {
String uuid = UUID.randomUUID().toString().toLowerCase();
// 移除短横线
return uuid.replace("-", "");
}
}

View File

@@ -54,6 +54,7 @@
<logger name="io.netty" level="warn"/>
<logger name="io.vertx" level="info"/>
<logger name="com.zaxxer.hikari" level="info"/>
<logger name="cn.qaiu" level="debug"/>
<root level="info">
<appender-ref ref="STDOUT"/>
<!-- <appender-ref ref="FILE"/>-->

View File

@@ -0,0 +1,28 @@
package cn.qaiu.parser;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.impl.PodTool;
public class ParserUrlOut {
//https://onedrive.live.com/redir?resid=ABFD0A26E47D3458!4699&e=OggA4s&migratedtospo=true&redeem=aHR0cHM6Ly8xZHJ2Lm1zL3UvcyFBbGcwZmVRbUN2MnJwRnZ1NDQ0aGc1eVZxRGNLP2U9T2dnQTRz
public static void main(String[] args) {
// Matcher matcher = redirectUrlRegex.matcher("https://onedrive.live.com/redir?resid=ABFD0A26E47D3458!4698" +
// "&authkey=!ACpvXghP5xhG_cg&e=hV98W1");
// if (matcher.find()) {
// System.out.println(matcher.group("cid"));
// System.out.println(matcher.group("cid2"));
// System.out.println(matcher.group("authkey"));
// }
// appid 5cbed6ac-a083-4e14-b191-b4ba07653de2 5cbed6ac-a083-4e14-b191-b4ba07653de2
// https://my.microsoftpersonalcontent.com/personal/abfd0a26e47d3458/_layouts/15/embed.aspx?UniqueId=e47d3458-0a26-20fd-80ab-5b1200000000&Translate=false&ApiVersion=2.0
// https://my.microsoftpersonalcontent.com/personal/abfd0a26e47d3458/_layouts/15/embed.aspx?UniqueId=6b0900d6-abcf-44ce-b7bf-7b626bcbe4b8&Translate=false&ApiVersion=2.0
// https://1drv.ms/u/s!Alg0feQmCv2rpFvu444hg5yVqDcK?e=OggA4s
// https://1drv.ms/u/c/abfd0a26e47d3458/EVg0feQmCv0ggKtbEgAAAAABqGv8K6HmOwLRsvokyV5fUg?e=iqoRc0
// https://1drv.ms/u/c/abfd0a26e47d3458/EVg0feQmCv0ggKtaEgAAAAAB-lF1qjkfv5OqdrT9VSMDMw
new PodTool(ShareLinkInfo.newBuilder().shareUrl("https://1drv.ms/u/c/abfd0a26e47d3458/EdYACWvPq85Et797YmvL5LgBruUKoNxqIFATXhIv1PI2_Q")
.build())
.parse().onSuccess(System.out::println);
}
}

View File

@@ -24,3 +24,4 @@ pnpm-debug.log*
/nfd-front.zip
/nfd-front
/package-lock.json
/yarn.lock

View File

@@ -10,7 +10,7 @@
<img :height="150" src="../public/images/lanzou111.png" alt="lz"></img>
</div>
</div>
<h3 style="text-align: center;">NFD网盘直链解析0.1.8_bate2(API演示)</h3>
<h3 style="text-align: center;">NFD网盘直链解析0.1.8_bate32</h3>
<div class="typo">
<p style="text-align: center;">
<span>
@@ -66,7 +66,7 @@
<p style="text-align: center">
<el-button style="margin-left: 40px;margin-bottom: 10px" @click="onSubmit">解析测试</el-button>
<el-button style="margin-left: 20px;margin-bottom: 10px" @click="genMd">生成Markdown链接</el-button>
<el-button style="margin-left: 20px" @click="generateQRCode">生成二维码</el-button>
<el-button style="margin-left: 20px" @click="generateQRCode">扫码下载</el-button>
<el-button style="margin-left: 20px" @click="getTj">链接信息统计</el-button>
</p>
</div>

View File

@@ -298,8 +298,14 @@
host: /118pan\.com/,
name: '118网盘'
},
pan115: {
// https://115.com/s/swhyiia3wzi?password=h374
reg: /https:\/\/(115|anxia)\.com\/s\/.+/,
host: /115pan\.com/,
name: '115网盘'
},
onedrive: {
reg: /https:\/\/1drv\.ms\/[uw]\/s!.+/,
reg: /https:\/\/1drv\.ms\/.+/,
host: /1drv\.ms/,
name: 'OneDrive'
},

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ package cn.qaiu.lz;
import cn.qaiu.WebClientVertxInit;
import cn.qaiu.db.pool.JDBCPoolInit;
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.util.ConfigConstant;
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.jackson.DatabindCodec;
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;
@@ -36,31 +40,46 @@ public class AppMain {
private static void exec(JsonObject jsonObject) {
WebClientVertxInit.init(VertxHolder.getVertxInstance());
DatabindCodec.mapper().registerModule(new JavaTimeModule());
// 限流
if (jsonObject.containsKey("rateLimit")) {
JsonObject rateLimit = jsonObject.getJsonObject("rateLimit");
RateLimiter.init(rateLimit);
}
// 数据库
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)) {
CacheConfigLoader.init(jsonObject.getJsonObject(ConfigConstant.CACHE));
}
LocalMap<Object, Object> localMap = VertxHolder.getVertxInstance().sharedData().getLocalMap(LOCAL);
// 代理
if (jsonObject.containsKey(ConfigConstant.PROXY)) {
LocalMap<Object, Object> localMap = VertxHolder.getVertxInstance().sharedData().getLocalMap(LOCAL);
JsonArray proxyJsonArray = jsonObject.getJsonArray(ConfigConstant.PROXY);
if (proxyJsonArray != null) {
JsonObject jsonObject1 = new JsonObject();
proxyJsonArray.forEach(proxyJson -> {
String panTypes = ((JsonObject)proxyJson).getString("panTypes");
proxyJsonArray.forEach(proxyJson -> {
String panTypes = ((JsonObject)proxyJson).getString("panTypes");
if (!panTypes.isEmpty()) {
JsonObject jsonObject1 = new JsonObject();
for (String s : panTypes.split(",")) {
jsonObject1.put(s, proxyJson);
if (!panTypes.isEmpty()) {
for (String s : panTypes.split(",")) {
jsonObject1.put(s, proxyJson);
}
}
localMap.put("proxy", jsonObject1);
}
});
});
localMap.put("proxy", jsonObject1);
}
}
}
}

View File

@@ -19,7 +19,9 @@ public class CacheConfigLoader {
TYPE = config.getString("type");
Integer defaultDuration = config.getInteger("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) {
CONFIGS.put(k, DEFAULT_DURATION);
} else {

View File

@@ -1,6 +1,7 @@
package cn.qaiu.lz.common.cache;
import cn.qaiu.db.pool.JDBCPoolInit;
import cn.qaiu.db.pool.JDBCType;
import cn.qaiu.lz.web.model.CacheLinkInfo;
import io.vertx.core.Future;
import io.vertx.core.Promise;
@@ -8,13 +9,17 @@ import io.vertx.core.json.JsonObject;
import io.vertx.jdbcclient.JDBCPool;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.templates.SqlTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class CacheManager {
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) {
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));
}
promise.complete(cacheHit);
}).onFailure(Throwable::printStackTrace);
}).onFailure(e->{
promise.fail(e);
LOGGER.error("cache get:", e);
});
return promise.future();
}
// 插入或更新缓存数据
public Future<Void> cacheShareLink(CacheLinkInfo cacheLinkInfo) {
String sql = "MERGE INTO cache_link_info (share_key, direct_link, expiration) " +
"KEY (share_key) " +
"VALUES (#{shareKey}, #{directLink}, #{expiration})";
String sql;
if (jdbcType == JDBCType.MySQL) {
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 实体类
return SqlTemplate.forUpdate(jdbcPool, sql)
@@ -55,11 +75,23 @@ public class CacheManager {
public Future<Integer> updateTotalByField(String shareKey, CacheTotalField field) {
Promise<Integer> promise = Promise.promise();
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`)
KEY (`share_key`)
VALUES (#{panType}, #{shareKey}, #{total}, #{ts})
""".replace("{field}", fieldLower);
}
getShareKeyTotal(shareKey, fieldLower).onSuccess(total -> {
Integer newTotal = (total == null ? 0 : total) + 1;
@@ -71,7 +103,10 @@ public class CacheManager {
put("ts", System.currentTimeMillis());
}})
.onSuccess(res -> promise.complete(res.rowCount()))
.onFailure(Throwable::printStackTrace);
.onFailure(e->{
promise.fail(e);
LOGGER.error("updateTotalByField: ", e);
});
});
return promise.future();
}
@@ -84,10 +119,12 @@ public class CacheManager {
public Future<Integer> getShareKeyTotal(String shareKey, String name) {
String sql = """
select `share_key`, sum({total_name}) sum_num
from `api_statistics_info`
group by `share_key` having `share_key` = #{shareKey};
SELECT `share_key`, SUM({total_name}) AS sum_num
FROM `api_statistics_info`
WHERE `share_key` = #{shareKey}
GROUP BY `share_key`;
""".replace("{total_name}", name);
Promise<Integer> promise = Promise.promise();
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("shareKey", shareKey);
@@ -98,16 +135,21 @@ public class CacheManager {
Integer total = res.iterator().hasNext() ?
res.iterator().next().getInteger("sum_num") : null;
promise.complete(total);
}).onFailure(e->{
promise.fail(e);
LOGGER.error("getShareKeyTotal: ", e);
});
return promise.future();
}
public Future<Map<String, Integer>> getShareKeyTotal(String shareKey) {
String sql = """
select `share_key`, sum(cache_hit_total) hit_total, sum(api_parser_total) parser_total,
from `api_statistics_info`
group by `share_key` having `share_key` = #{shareKey}
SELECT `share_key`, SUM(cache_hit_total) AS hit_total, SUM(api_parser_total) AS parser_total
FROM `api_statistics_info`
WHERE `share_key` = #{shareKey}
GROUP BY `share_key`;
""";
Promise<Map<String, Integer>> promise = Promise.promise();
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("shareKey", shareKey);
@@ -115,16 +157,19 @@ public class CacheManager {
.mapTo(Row::toJson)
.execute(paramMap)
.onSuccess(res -> {
if(res.iterator().hasNext()) {
JsonObject next = res.iterator().next();
Map<String, Integer> resp = new HashMap<>(){{
put("hit_total" ,next.getInteger("hit_total"));
put("parser_total" ,next.getInteger("parser_total"));
}};
promise.complete(resp);
if(res.iterator().hasNext()) {
JsonObject next = res.iterator().next();
Map<String, Integer> resp = new HashMap<>(){{
put("hit_total" ,next.getInteger("hit_total"));
put("parser_total" ,next.getInteger("parser_total"));
}};
promise.complete(resp);
} else {
promise.complete();
}
promise.complete();
}
}).onFailure(e->{
promise.fail(e);
LOGGER.error("getShareKeyTotal0: ", e);
});
return promise.future();
}

View File

@@ -2,12 +2,15 @@ package cn.qaiu.lz.common.interceptorImpl;
import cn.qaiu.vx.core.annotaions.HandleSortFilter;
import cn.qaiu.vx.core.interceptor.BeforeInterceptor;
import cn.qaiu.vx.core.util.ConfigConstant;
import cn.qaiu.vx.core.util.SharedDataUtil;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import lombok.extern.slf4j.Slf4j;
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
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());
});
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -1,6 +1,7 @@
package cn.qaiu.lz.web.controller;
import cn.qaiu.entity.FileInfo;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.lz.common.cache.CacheManager;
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.enums.RouteMethod;
import cn.qaiu.vx.core.util.AsyncServiceUtil;
import cn.qaiu.vx.core.util.ResponseUtil;
import cn.qaiu.vx.core.util.SharedDataUtil;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.*;
import java.util.stream.Collectors;
@RouteHandler(value = "/v2", order = 10)
@@ -35,7 +36,7 @@ public class ParserApi {
private final UserService userService = AsyncServiceUtil.getAsyncServiceInstance(UserService.class);
private final DbService dbService = AsyncServiceUtil.getAsyncServiceInstance(DbService.class);
@RouteMapping(value = "/login", method = RouteMethod.POST)
@RouteMapping(value = "/login", method = RouteMethod.GET)
public Future<SysUser> login(SysUser user) {
log.info("<------- login: {}", user.getUsername());
return userService.login(user);
@@ -59,6 +60,7 @@ public class ParserApi {
.apiLink(getDownLink(parserCreate, true))
.shareLinkInfo(shareLinkInfo).build();
// 解析次数统计
shareLinkInfo.getOtherParam().put("UA",request.headers().get("user-agent"));
cacheManager.getShareKeyTotal(shareLinkInfo.getCacheKey()).onSuccess(res -> {
if (res != null) {
build.setCacheHitTotal(res.get("hit_total") == null ? 0: res.get("hit_total"));
@@ -92,8 +94,50 @@ public class ParserApi {
return Arrays.stream(PanDomainTemplate.values()).map(pan -> new TreeMap<String, String>() {{
put("name", pan.getDisplayName());
put("type", pan.name().toLowerCase());
put("shareUrlFormat", pan.getStandardUrlTemplate());
put("url", pan.getPanDomain());
}}).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();
}
}

View File

@@ -12,6 +12,8 @@ import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import lombok.extern.slf4j.Slf4j;
/**
@@ -27,11 +29,11 @@ public class ServerApi {
private final CacheService cacheService = AsyncServiceUtil.getAsyncServiceInstance(CacheService.class);
@RouteMapping(value = "/parser", method = RouteMethod.GET, order = 1)
public Future<Void> parse(HttpServerResponse response, HttpServerRequest request, String pwd) {
public Future<Void> parse(HttpServerResponse response, HttpServerRequest request, RoutingContext rcx, String pwd) {
Promise<Void> promise = Promise.promise();
String url = URLParamUtil.parserParams(request);
cacheService.getCachedByShareUrlAndPwd(url, pwd)
cacheService.getCachedByShareUrlAndPwd(url, pwd, JsonObject.of("UA",request.headers().get("user-agent")))
.onSuccess(res -> ResponseUtil.redirect(
response.putHeader("nfd-cache-hit", res.getCacheHit().toString())
.putHeader("nfd-cache-expires", res.getExpires()),
@@ -43,22 +45,22 @@ public class ServerApi {
@RouteMapping(value = "/json/parser", method = RouteMethod.GET, order = 1)
public Future<CacheLinkInfo> parseJson(HttpServerRequest request, String pwd) {
String url = URLParamUtil.parserParams(request);
return cacheService.getCachedByShareUrlAndPwd(url, pwd);
return cacheService.getCachedByShareUrlAndPwd(url, pwd, JsonObject.of("UA",request.headers().get("user-agent")));
}
@RouteMapping(value = "/json/:type/:key", method = RouteMethod.GET)
public Future<CacheLinkInfo> parseKeyJson(String type, String key) {
public Future<CacheLinkInfo> parseKeyJson(HttpServerRequest request, String type, String key) {
String pwd = "";
if (key.contains("@")) {
String[] keys = key.split("@");
key = keys[0];
pwd = keys[1];
}
return cacheService.getCachedByShareKeyAndPwd(type, key, pwd);
return cacheService.getCachedByShareKeyAndPwd(type, key, pwd, JsonObject.of("UA",request.headers().get("user-agent")));
}
@RouteMapping(value = "/:type/:key", method = RouteMethod.GET)
public Future<Void> parseKey(HttpServerResponse response, String type, String key) {
public Future<Void> parseKey(HttpServerResponse response, HttpServerRequest request, String type, String key) {
Promise<Void> promise = Promise.promise();
String pwd = "";
if (key.contains("@")) {
@@ -66,7 +68,7 @@ public class ServerApi {
key = keys[0];
pwd = keys[1];
}
cacheService.getCachedByShareKeyAndPwd(type, key, pwd)
cacheService.getCachedByShareKeyAndPwd(type, key, pwd, JsonObject.of("UA",request.headers().get("user-agent")))
.onSuccess(res -> ResponseUtil.redirect(
response.putHeader("nfd-cache-hit", res.getCacheHit().toString())
.putHeader("nfd-cache-expires", res.getExpires()),

View File

@@ -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);
}
}

View File

@@ -21,13 +21,13 @@ public class ApiStatisticsInfo implements ToJson {
/**
* pan type 单独拿出来便于统计.
*/
@Length(varcharSize = 4)
@Length(varcharSize = 16)
private String panType;
/**
* 分享key type:key
*/
@Length(varcharSize = 4096)
@Length(varcharSize = 1024)
private String shareKey;
/**

View File

@@ -3,6 +3,7 @@ package cn.qaiu.lz.web.model;
import cn.qaiu.db.ddl.Length;
import cn.qaiu.db.ddl.Table;
import cn.qaiu.db.ddl.TableGenIgnore;
import cn.qaiu.entity.FileInfo;
import cn.qaiu.lz.common.ToJson;
import io.vertx.codegen.annotations.DataObject;
import io.vertx.core.json.JsonObject;
@@ -22,7 +23,7 @@ public class CacheLinkInfo implements ToJson {
/**
* 缓存key: type:ShareKey; e.g. lz:xxxx
*/
@Length(varcharSize = 4096)
@Length(varcharSize = 1024)
private String shareKey;
/**
@@ -48,6 +49,8 @@ public class CacheLinkInfo implements ToJson {
*/
private Long expiration;
private FileInfo fileInfo;
// 使用 JsonObject 构造
public CacheLinkInfo(JsonObject json) {

View File

@@ -10,6 +10,7 @@ public class LinkInfoResp {
// 解析链接
private String downLink;
private String apiLink;
private String viewLink;
private Integer cacheHitTotal;
private Integer parserTotal;
private Integer sumTotal;

View File

@@ -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; // 是否已使用
}

View File

@@ -4,6 +4,7 @@ import cn.qaiu.lz.web.model.CacheLinkInfo;
import cn.qaiu.vx.core.base.BaseAsyncService;
import io.vertx.codegen.annotations.ProxyGen;
import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
/**
* @author <a href="https://qaiu.top">QAIU</a>
@@ -12,7 +13,7 @@ import io.vertx.core.Future;
@ProxyGen
public interface CacheService extends BaseAsyncService {
Future<CacheLinkInfo> getCachedByShareKeyAndPwd(String type, String shareKey, String pwd);
Future<CacheLinkInfo> getCachedByShareKeyAndPwd(String type, String shareKey, String pwd, JsonObject otherParam);
Future<CacheLinkInfo> getCachedByShareUrlAndPwd(String shareUrl, String pwd);
Future<CacheLinkInfo> getCachedByShareUrlAndPwd(String shareUrl, String pwd, JsonObject otherParam);
}

View File

@@ -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);
}

View File

@@ -76,14 +76,16 @@ public class CacheServiceImpl implements CacheService {
}
@Override
public Future<CacheLinkInfo> getCachedByShareKeyAndPwd(String type, String shareKey, String pwd) {
public Future<CacheLinkInfo> getCachedByShareKeyAndPwd(String type, String shareKey, String pwd, JsonObject otherParam) {
ParserCreate parserCreate = ParserCreate.fromType(type).shareKey(shareKey).setShareLinkInfoPwd(pwd);
parserCreate.getShareLinkInfo().getOtherParam().putAll(otherParam.getMap());
return getAndSaveCachedShareLink(parserCreate);
}
@Override
public Future<CacheLinkInfo> getCachedByShareUrlAndPwd(String shareUrl, String pwd) {
public Future<CacheLinkInfo> getCachedByShareUrlAndPwd(String shareUrl, String pwd, JsonObject otherParam) {
ParserCreate parserCreate = ParserCreate.fromShareUrl(shareUrl).setShareLinkInfoPwd(pwd);
parserCreate.getShareLinkInfo().getOtherParam().putAll(otherParam.getMap());
return getAndSaveCachedShareLink(parserCreate);
}
}

View File

@@ -48,10 +48,11 @@ public class DbServiceImpl implements DbService {
JDBCPool client = JDBCPoolInit.instance().getPool();
Promise<StatisticsInfo> promise = Promise.promise();
String sql = """
select sum(api_parser_total) parserTotal,sum("cache_hit_total") cacheTotal,
sum(api_parser_total) + sum("cache_hit_total") total
from "api_statistics_info";
select sum(api_parser_total) as parserTotal, sum(cache_hit_total) as cacheTotal,
sum(api_parser_total) + sum(cache_hit_total) as total
from api_statistics_info;
""";
SqlTemplate.forQuery(client, sql).mapTo(StatisticsInfo.class).execute(new HashMap<>()).onSuccess(row -> {
StatisticsInfo info;
if ((info = row.iterator().next()) != null) {
@@ -59,7 +60,10 @@ public class DbServiceImpl implements DbService {
} else {
promise.fail("t_parser_log_info查询为空");
}
}).onFailure(promise::fail);
}).onFailure(e->{
log.error("getStatisticsInfo: ", e);
promise.fail(e);
});
return promise.future();
}
}

View File

@@ -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();
}
}

View File

@@ -19,11 +19,11 @@ public class UserServiceImpl implements UserService {
@Override
public Future<SysUser> login(SysUser user) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
return Future.succeededFuture(user);
}
}

View File

@@ -6,6 +6,7 @@ server:
enableDatabase: true
# 服务域名或者IP 生成二维码链接时需要
domainName: http://127.0.0.1:6401
# domainName: https://lz.qaiu.top
# 反向代理服务器配置路径(不用加后缀)
proxyConf: server-proxy
@@ -25,19 +26,33 @@ custom:
routeTimeOut: 15000
# 拦截器匹配规则
ignoresReg:
- /v2/statisticsInfo
# - /v2/statisticsInfo
- .*/test.*$
# 参数注入的实体类包路径匹配正则 (防止同名类引发歧义)
entityPackagesReg:
- ^cn\.qaiu\.lz\.web\.model\..*
# 限流配置
rateLimit:
# 是否启用限流
enable: true
# 限流的请求数
limit: 5
# 限流的时间窗口(单位秒)
timeWindow: 10
# 路径匹配规则
pathReg: ^/v2/.*
# 数据源配置
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
driverClassName: org.h2.Driver
username: root
password: '123456'
@@ -49,29 +64,32 @@ cache:
defaultDuration: 59
# 具体网盘的缓存配置如果不加配置则不缓存每次请求都会请求网盘API格式网盘标识: 时长
duration:
ce:
cow:
ec:
ce: 5
cow: 5
ec: 5
fc:
fj: 30
fj: 20
iz: 20
le: 2879
lz: 20
qq: 9999999
ws:
ye:
qqw: 30
ws: 10
ye: -1
mne: 30
mqq: 30
mkg: 30
p115: 30
ct: 30
# httpClient静态代理服务器配置(外网代理)
proxy:
- panTypes: pgd,pdb
type: http # 支持http/socks4/socks5
host: 127.0.0.1
port: 7890
username:
password:
# - panTypes: pgd,pdb,pod
# type: http # 支持http/socks4/socks5
# host: 127.0.0.1
# port: 7890
# username:
# password:
# 代理池配置

View File

@@ -2,9 +2,18 @@
### 直链 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
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/

View File

@@ -3,8 +3,203 @@
### ecpan(移动云空间)
https://www.ecpan.cn/drive/fileextoverrid.do?chainUrlTemplate=https:%2F%2Fwww.ecpan.cn%2Fweb%2F%23%2FyunpanProxy%3Fpath%3D%252F%2523%252Fdrive%252Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&parentId=-1
#
{
"code": "S_OK",
"errorCode": null,
"summary": "1",
"var": {
"resUrl": "/resource_drive",
"driveUrl": "/drive",
"chainFileInfo": {
"chainPower": 1010,
"navigations": [],
"cloudpFile": {
"tableName": "cloudp_file_52",
"appFileId": "2d06ad61f91c11ed86b3b026287b20b1",
"dfsFileId": "3080817de83747698ff5747e25f97e50",
"fileName": "fonts.zip",
"filePath": "个人盘",
"fileLevel": 0,
"fileCount": 0,
"fileType": 2,
"fileSort": 99,
"fileSize": 19115063,
"uploadSize": 19115063,
"parentId": "c27b53c4f91b11ed86b3b026287b20b1",
"usn": 11364252,
"userName": "157****1073",
"userId": "",
"corpId": 13260232,
"createDate": 1684813492000,
"modifyDate": 1684813492000,
"status": 1,
"version": 0,
"comeFrom": 21,
"isShare": 0,
"folderType": 0,
"groupDesc": null,
"groupId": "c27b53c4f91b11ed86b3b026287b20b1",
"groupName": null,
"permission": null,
"userInfo": null,
"diskType": 1,
"historyKey": null,
"oldAppFileId": null,
"fileRealPath": null,
"fileExt": null,
"oldFileName": null,
"key": null,
"isUpperCase": null,
"isSubDir": null,
"isViewFolder": null,
"uploadTimeType": null,
"startUploadTime": null,
"endUploadTime": null,
"sizeOperator": null,
"orderField": null,
"orderBy": null,
"oldFilePath": null,
"statusStr": null,
"rootUsn": 11364252,
"createdByUsn": null,
"attentionCount": 0,
"fileMd5": "86c1ff32437ca0ccaca24f7ea197e34a",
"fileLabel": null,
"fileDesc": null,
"fileExtends": "<fileExtends><shareRoot></shareRoot><isLock>0</isLock><isEncrypt>0</isEncrypt></fileExtends>",
"deviceId": null,
"fileNamePinyin": "fonts.zip",
"modifyUser": "157****1073",
"fileExFileds": {
"shareRoot": "",
"isLock": 0,
"isEncrypt": 0
},
"security": 0,
"createdDate": null,
"modifiedDate1": null,
"modifiedDate2": null,
"createdDate1": null,
"createdDate2": null,
"deleteUsn": null,
"opType": 0,
"toShares": null,
"firstLevel": null
},
"shareId": 2404783,
"jsonFileList": [
{
"appFileId": "2d06ad61f91c11ed86b3b026287b20b1",
"corpId": 13260232,
"createdByUsn": 0,
"dfsFileId": "3080817de83747698ff5747e25f97e50",
"diskType": 1,
"downloadUrl": "",
"fileMd5": "86c1ff32437ca0ccaca24f7ea197e34a",
"fileName": "fonts.zip",
"fileSize": 19115063,
"fileSort": 99,
"fileType": 2,
"groupId": "c27b53c4f91b11ed86b3b026287b20b1",
"parentId": "c27b53c4f91b11ed86b3b026287b20b1",
"rootUsn": 11364252,
"status": 1,
"usn": 11364252,
"version": 0
}
],
"watermarkStatus": 2,
"cloudpFileList": [
{
"tableName": "cloudp_file_52",
"appFileId": "2d06ad61f91c11ed86b3b026287b20b1",
"dfsFileId": "3080817de83747698ff5747e25f97e50",
"fileName": "fonts.zip",
"filePath": "个人盘",
"fileLevel": 0,
"fileCount": 0,
"fileType": 2,
"fileSort": 99,
"fileSize": 19115063,
"uploadSize": 19115063,
"parentId": "c27b53c4f91b11ed86b3b026287b20b1",
"usn": 11364252,
"userName": "157****1073",
"userId": "",
"corpId": 13260232,
"createDate": 1684813492000,
"modifyDate": 1684813492000,
"status": 1,
"version": 0,
"comeFrom": 21,
"isShare": 0,
"folderType": 0,
"groupDesc": null,
"groupId": "c27b53c4f91b11ed86b3b026287b20b1",
"groupName": null,
"permission": null,
"userInfo": null,
"diskType": 1,
"historyKey": null,
"oldAppFileId": null,
"fileRealPath": null,
"fileExt": null,
"oldFileName": null,
"key": null,
"isUpperCase": null,
"isSubDir": null,
"isViewFolder": null,
"uploadTimeType": null,
"startUploadTime": null,
"endUploadTime": null,
"sizeOperator": null,
"orderField": null,
"orderBy": null,
"oldFilePath": null,
"statusStr": null,
"rootUsn": 11364252,
"createdByUsn": null,
"attentionCount": 0,
"fileMd5": "86c1ff32437ca0ccaca24f7ea197e34a",
"fileLabel": null,
"fileDesc": null,
"fileExtends": "<fileExtends><shareRoot></shareRoot><isLock>0</isLock><isEncrypt>0</isEncrypt></fileExtends>",
"deviceId": null,
"fileNamePinyin": "fonts.zip",
"modifyUser": "157****1073",
"fileExFileds": {
"shareRoot": "",
"isLock": 0,
"isEncrypt": 0
},
"security": 0,
"createdDate": null,
"modifiedDate1": null,
"modifiedDate2": null,
"createdDate1": null,
"createdDate2": null,
"deleteUsn": null,
"opType": 0,
"toShares": null,
"firstLevel": null
}
],
"mobilePhone": "157****1073",
"domain": "https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside",
"shareTime": 1684813544000,
"expireDate": "2122-06-22 23:59:59",
"extCodeFlag": 0,
"shareUser": "157****1073"
},
"logo": "logo.png"
},
"rememberMe": null
}
###
# @no-cookie-jar
POST https://www.ecpan.cn/drive/sharedownload.do
Content-Type: application/json
@@ -86,3 +281,267 @@ Content-Type: application/json
"firstLevel": null
}]
}
###
# curl 'https://www.ecpan.cn/drive/sharedownload.do'
# -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 'Content-Type: application/json'
# -H 'Cookie: _rum_trackingId=m4t7vbvr3dspgdptfs823jei; _rum_sessionId=m4t7vbvr2qax1j1ez8g3ei3ry4ngh2dd'
# -H 'DNT: 1'
# -H 'Origin: https://www.ecpan.cn'
# -H 'Pinpoint-TraceID: 193d7666ad316af8d90bff63127016d0'
# -H 'Pinpoint-TransactionID: 112_b36bec39c167f5f5^1734485633747^0'
# -H 'Pinpoint-pAgentID: 112_b36bec39c167f5f5'
# -H 'Pinpoint-pSpanID: 193d7666ad316a43'
# -H 'Pragma: no-cache'
# -H 'Referer: https://www.ecpan.cn/web/yunpan/'
# -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"'
# --data-raw '{"extCodeFlag":0,"isIp":0,"shareId":2854164,"groupId":"c27b53c4f91b11ed86b3b026287b20b1","fileIdList":[{"tableName":"cloudp_file_52","appFileId":"f786f877eeb811ee88d4bc16950460f6","dfsFileId":"231f235d4380434ab9b57bc069d08630","fileName":"Pydroid 3_7.2_arm64_q_cn.apk","filePath":"个人盘\\pydroid7","fileLevel":1,"fileCount":0,"fileType":2,"fileSort":99,"fileSize":324728098,"uploadSize":324728098,"parentId":"964abd0f861e11eeb992bc16950460f6","usn":11364252,"userName":"157****1073","userId":"","corpId":13260232,"createDate":1711818868000,"modifyDate":1711818868000,"status":1,"version":0,"comeFrom":21,"isShare":0,"folderType":0,"groupDesc":null,"groupId":"c27b53c4f91b11ed86b3b026287b20b1","groupName":null,"permission":null,"userInfo":null,"diskType":1,"historyKey":null,"oldAppFileId":null,"fileRealPath":null,"fileExt":null,"oldFileName":null,"key":null,"isUpperCase":null,"isSubDir":null,"isViewFolder":null,"uploadTimeType":null,"startUploadTime":null,"endUploadTime":null,"sizeOperator":null,"orderField":null,"orderBy":null,"oldFilePath":null,"statusStr":null,"rootUsn":11364252,"createdByUsn":null,"attentionCount":0,"fileMd5":"c5ed01a124030ce376c9f4753e015544","fileLabel":null,"fileDesc":null,"fileExtends":"<fileExtends><shareRoot></shareRoot><isLock>0</isLock><isEncrypt>0</isEncrypt></fileExtends>","deviceId":null,"fileNamePinyin":"Pydroid 3_7.2_arm64_q_cn.apk","modifyUser":"157****1073","fileExFileds":{"shareRoot":"","isLock":0,"isEncrypt":0},"security":0,"createdDate":null,"modifiedDate1":null,"modifiedDate2":null,"createdDate1":null,"createdDate2":null,"deleteUsn":null,"opType":0,"toShares":null,"firstLevel":null,"downloadUrl":null}]}'
POST https://www.ecpan.cn/drive/sharedownload.do
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: _rum_trackingId=m4t7vbvr3dspgdptfs823jei; _rum_sessionId=m4t7vbvr2qax1j1ez8g3ei3ry4ngh2dd
DNT: 1
Origin: https://www.ecpan.cn
Pinpoint-TraceID: 193d7666ad316af8d90bff63127016d0
Pinpoint-TransactionID: 112_b36bec39c167f5f5^1734485633747^0
Pinpoint-pAgentID: 112_b36bec39c167f5f5
Pinpoint-pSpanID: 193d7666ad316a43
Pragma: no-cache
Referer: https://www.ecpan.cn/web/yunpan/
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"
Content-Type: application/json
{
"extCodeFlag": 0,
"isIp": 0,
"shareId": 2854164,
"groupId": "c27b53c4f91b11ed86b3b026287b20b1",
"fileIdList": [
{
"tableName": "cloudp_file_52",
"appFileId": "f786f877eeb811ee88d4bc16950460f6",
"dfsFileId": "231f235d4380434ab9b57bc069d08630",
"fileName": "Pydroid 3_7.2_arm64_q_cn.apk",
"filePath": "个人盘pydroid7",
"fileLevel": 1,
"fileCount": 0,
"fileType": 2,
"fileSort": 99,
"fileSize": 324728098,
"uploadSize": 324728098,
"parentId": "964abd0f861e11eeb992bc16950460f6",
"usn": 11364252,
"userName": "157****1073",
"userId": "",
"corpId": 13260232,
"createDate": 1711818868000,
"modifyDate": 1711818868000,
"status": 1,
"version": 0,
"comeFrom": 21,
"isShare": 0,
"folderType": 0,
"groupDesc": null,
"groupId": "c27b53c4f91b11ed86b3b026287b20b1",
"groupName": null,
"permission": null,
"userInfo": null,
"diskType": 1,
"historyKey": null,
"oldAppFileId": null,
"fileRealPath": null,
"fileExt": null,
"oldFileName": null,
"key": null,
"isUpperCase": null,
"isSubDir": null,
"isViewFolder": null,
"uploadTimeType": null,
"startUploadTime": null,
"endUploadTime": null,
"sizeOperator": null,
"orderField": null,
"orderBy": null,
"oldFilePath": null,
"statusStr": null,
"rootUsn": 11364252,
"createdByUsn": null,
"attentionCount": 0,
"fileMd5": "c5ed01a124030ce376c9f4753e015544",
"fileLabel": null,
"fileDesc": null,
"fileExtends": "<fileExtends><shareRoot></shareRoot><isLock>0</isLock><isEncrypt>0</isEncrypt></fileExtends>",
"deviceId": null,
"fileNamePinyin": "Pydroid 3_7.2_arm64_q_cn.apk",
"modifyUser": "157****1073",
"fileExFileds": {
"shareRoot": "",
"isLock": 0,
"isEncrypt": 0
},
"security": 0,
"createdDate": null,
"modifiedDate1": null,
"modifiedDate2": null,
"createdDate1": null,
"createdDate2": null,
"deleteUsn": null,
"opType": 0,
"toShares": null,
"firstLevel": null,
"downloadUrl": null
}
]
}
###
# curl 'https://www.ecpan.cn/drive/sharedownload.do'
# -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 'Content-Type: application/json'
# -H 'Cookie: _rum_trackingId=m4t7vbvr3dspgdptfs823jei; _rum_sessionId=m4t7vbvr2qax1j1ez8g3ei3ry4ngh2dd'
# -H 'DNT: 1'
# -H 'Origin: https://www.ecpan.cn'
# -H 'Pinpoint-TraceID: 193d7666ad316af8d90bff63127016d0'
# -H 'Pinpoint-TransactionID: 112_b36bec39c167f5f5^1734485633747^0'
# -H 'Pinpoint-pAgentID: 112_b36bec39c167f5f5'
# -H 'Pinpoint-pSpanID: 193d7666ad316a43'
# -H 'Pragma: no-cache'
# -H 'Referer: https://www.ecpan.cn/web/yunpan/'
# -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"'
# --data-raw '{"extCodeFlag":0,"isIp":0,"shareId":2854164,"groupId":"c27b53c4f91b11ed86b3b026287b20b1","fileIdList":[{"tableName":"cloudp_file_52","appFileId":"f786f877eeb811ee88d4bc16950460f6","dfsFileId":"231f235d4380434ab9b57bc069d08630","fileName":"Pydroid 3_7.2_arm64_q_cn.apk","filePath":"个人盘\\pydroid7","fileLevel":1,"fileCount":0,"fileType":2,"fileSort":99,"fileSize":324728098,"uploadSize":324728098,"parentId":"964abd0f861e11eeb992bc16950460f6","usn":11364252,"userName":"157****1073","userId":"","corpId":13260232,"createDate":1711818868000,"modifyDate":1711818868000,"status":1,"version":0,"comeFrom":21,"isShare":0,"folderType":0,"groupDesc":null,"groupId":"c27b53c4f91b11ed86b3b026287b20b1","groupName":null,"permission":null,"userInfo":null,"diskType":1,"historyKey":null,"oldAppFileId":null,"fileRealPath":null,"fileExt":null,"oldFileName":null,"key":null,"isUpperCase":null,"isSubDir":null,"isViewFolder":null,"uploadTimeType":null,"startUploadTime":null,"endUploadTime":null,"sizeOperator":null,"orderField":null,"orderBy":null,"oldFilePath":null,"statusStr":null,"rootUsn":11364252,"createdByUsn":null,"attentionCount":0,"fileMd5":"c5ed01a124030ce376c9f4753e015544","fileLabel":null,"fileDesc":null,"fileExtends":"<fileExtends><shareRoot></shareRoot><isLock>0</isLock><isEncrypt>0</isEncrypt></fileExtends>","deviceId":null,"fileNamePinyin":"Pydroid 3_7.2_arm64_q_cn.apk","modifyUser":"157****1073","fileExFileds":{"shareRoot":"","isLock":0,"isEncrypt":0},"security":0,"createdDate":null,"modifiedDate1":null,"modifiedDate2":null,"createdDate1":null,"createdDate2":null,"deleteUsn":null,"opType":0,"toShares":null,"firstLevel":null,"downloadUrl":null}]}'
POST https://www.ecpan.cn/drive/sharedownload.do
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: _rum_trackingId=m4t7vbvr3dspgdptfs823jei; _rum_sessionId=m4t7vbvr2qax1j1ez8g3ei3ry4ngh2dd
DNT: 1
Origin: https://www.ecpan.cn
Pinpoint-TraceID: 193d7666ad316af8d90bff63127016d0
Pinpoint-TransactionID: 112_b36bec39c167f5f5^1734485633747^0
Pinpoint-pAgentID: 112_b36bec39c167f5f5
Pinpoint-pSpanID: 193d7666ad316a43
Pragma: no-cache
Referer: https://www.ecpan.cn/web/yunpan/
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"
Content-Type: application/json
{
"extCodeFlag": 0,
"isIp": 0,
"shareId": 2854164,
"groupId": "c27b53c4f91b11ed86b3b026287b20b1",
"fileIdList": [
{
"tableName": "cloudp_file_52",
"appFileId": "f786f877eeb811ee88d4bc16950460f6",
"dfsFileId": "231f235d4380434ab9b57bc069d08630",
"fileName": "Pydroid 3_7.2_arm64_q_cn.apk",
"filePath": "个人盘pydroid7",
"fileLevel": 1,
"fileCount": 0,
"fileType": 2,
"fileSort": 99,
"fileSize": 324728098,
"uploadSize": 324728098,
"parentId": "964abd0f861e11eeb992bc16950460f6",
"usn": 11364252,
"userName": "157****1073",
"userId": "",
"corpId": 13260232,
"createDate": 1711818868000,
"modifyDate": 1711818868000,
"status": 1,
"version": 0,
"comeFrom": 21,
"isShare": 0,
"folderType": 0,
"groupDesc": null,
"groupId": "c27b53c4f91b11ed86b3b026287b20b1",
"groupName": null,
"permission": null,
"userInfo": null,
"diskType": 1,
"historyKey": null,
"oldAppFileId": null,
"fileRealPath": null,
"fileExt": null,
"oldFileName": null,
"key": null,
"isUpperCase": null,
"isSubDir": null,
"isViewFolder": null,
"uploadTimeType": null,
"startUploadTime": null,
"endUploadTime": null,
"sizeOperator": null,
"orderField": null,
"orderBy": null,
"oldFilePath": null,
"statusStr": null,
"rootUsn": 11364252,
"createdByUsn": null,
"attentionCount": 0,
"fileMd5": "c5ed01a124030ce376c9f4753e015544",
"fileLabel": null,
"fileDesc": null,
"fileExtends": "<fileExtends><shareRoot></shareRoot><isLock>0</isLock><isEncrypt>0</isEncrypt></fileExtends>",
"deviceId": null,
"fileNamePinyin": "Pydroid 3_7.2_arm64_q_cn.apk",
"modifyUser": "157****1073",
"fileExFileds": {
"shareRoot": "",
"isLock": 0,
"isEncrypt": 0
},
"security": 0,
"createdDate": null,
"modifiedDate1": null,
"modifiedDate2": null,
"createdDate1": null,
"createdDate2": null,
"deleteUsn": null,
"opType": 0,
"toShares": null,
"firstLevel": null,
"downloadUrl": null
}
]
}
###

View File

@@ -45,3 +45,101 @@ requesttoken=se2GpxdjP9zU4rajy1ro3vZ8x0nYE64KdTzgOUtG&password=QAIU
# @no-cookie-jar
https://v2.fangcloud.cn/share/e5079007dc31226096628870c7
Cookie:XSRF-TOKEN=eyJpdiI6ImVpdW9XOFwvazVka0xRQ2JUQ1p4c1dRPT0iLCJ2YWx1ZSI6Im1NcTJuQW8xdlF4MEg1XC9NZERZUnE4TUgxNm9UYWhoV3pKMlhKcngwakMxR2VHeTFZZ0EzSlBcL244YXlRTWtKZ3NOd1JxYTl4ZktsdXdcL0t5dVlTMXhnPT0iLCJtYWMiOiJhMjZlODE3NDFlMTRkMjg1MzVhMjRjOGVmYTgzYjk1NGM1NmY1N2NmZjljZmIzMmRjZGFmOTVkNDhhMWYwMDRhIn0%3D; fc_session=eyJpdiI6IlppZW1UQVwvSHRWSDhVZWZwampkNHZRPT0iLCJ2YWx1ZSI6IjliWlBUZ0VPMlhoTXlUN3hXU25qYjlIdnRQRmtqbFdBR2cyaW1RVDdBN0s5OFRwcGFURjZ3TTZxZnQ4VVRGS2hNbWdPUFhrU0tlUTg1ZTJLZVZFS0FnPT0iLCJtYWMiOiJjMTM0YjI5ZTEwYjQwMGU0MGNlN2UyZmM4MWM2NzAyZmZhYjg5NWNlMjc3ODRhMmIzMzhmODg5ZTZlNzcyMGM2In0%3D;
###
# curl 'https://anxia.com/webapi/share/skip_login_downurl'
# -H 'Accept: application/json, text/javascript, */*; q=0.01'
# -H 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8'
# -H 'Cache-Control: no-cache'
# -H 'Connection: keep-alive'
# -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8'
# -H 'Cookie: acw_tc=2f6a1fdb17343237335965711ee089dd42f90058dbad48c86a96931b7098b0'
# -H 'DNT: 1'
# -H 'Origin: https://anxia.com'
# -H 'Pragma: no-cache'
# -H 'Referer: https://anxia.com/s/swhyiia3wzi?password=h374'
# -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 '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"'
# --data-raw 'share_code=swhyiia3wzi&receive_code=h374&file_id=2532636713782843636'
POST https://anxia.com/webapi/share/skip_login_downurl
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
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
share_code=swhyiia3wzi&receive_code=h374&file_id=2532636713782843636
###
# @no-cookie-jar
# curl 'https://anxia.com/webapi/share/snap?share_code=swhyiia3wzi&offset=0&limit=20&receive_code=h374&cid='
# -H 'Accept: application/json, text/javascript, */*; q=0.01'
# -H 'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8'
# -H 'Cache-Control: no-cache'
# -H 'Connection: keep-alive'
# -H 'Cookie: acw_tc=2f6a1fdb17343237335965711ee089dd42f90058dbad48c86a96931b7098b0'
# -H 'DNT: 1'
# -H 'Pragma: no-cache'
# -H 'Referer: https://anxia.com/s/swhyiia3wzi?password=h374'
# -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 '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://anxia.com/webapi/share/snap?share_code=swhyiia3wzi&offset=0&limit=20&receive_code=h374&cid=
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: no-cache
Connection: keep-alive
DNT: 1
Pragma: no-cache
Referer: https://anxia.com/s/swhyiia3wzi?password=h374
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
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://cdnfhnfile.115.com/63a79fdca011d11b46d5fda83f6fdf7fe5141ec7/%E6%95%B0%E5%AD%A612%E6%9C%8819%E6%97%A5-12%E6%9C%8823%E6%97%A5%E7%BA%BF%E4%B8%8A%E8%B5%84%E6%BA%90.zip?t=1734331803&u=ip976768882&s=512000&d=976768882--0&c=1&f=1&k=89f2b6ec6839999b3e7edc2b6ac85066&us=512000&uc=0&v=1'
# -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 'DNT: 1'
# -H 'Pragma: no-cache'
# -H 'Referer: https://anxia.com/'
# -H 'Sec-Fetch-Dest: document'
# -H 'Sec-Fetch-Mode: navigate'
# -H 'Sec-Fetch-Site: cross-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/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"'
# @no-cookie-jar
# @no-redirect
GET https://cdnfhnfile.115.com/63a79fdca011d11b46d5fda83f6fdf7fe5141ec7/%E6%95%B0%E5%AD%A612%E6%9C%8819%E6%97%A5-12%E6%9C%8823%E6%97%A5%E7%BA%BF%E4%B8%8A%E8%B5%84%E6%BA%90.zip?t=1734331803&u=ip976768882&s=512000&d=976768882--0&c=1&f=1&k=89f2b6ec6839999b3e7edc2b6ac85066&us=512000&uc=0&v=1
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
###
# @no-cookie-jar
# @no-redirect
GET https://cdnfhnfile.115.com/63a79fdca011d11b46d5fda83f6fdf7fe5141ec7/%E6%95%B0%E5%AD%A612%E6%9C%8819%E6%97%A5-12%E6%9C%8823%E6%97%A5%E7%BA%BF%E4%B8%8A%E8%B5%84%E6%BA%90.zip?t=1734332086&u=ip976768882&s=512000&d=976768882--0&c=1&f=1&k=28e2b7be1a91ff0715dd92c6a9ec592f&us=512000&uc=0&v=1
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
###
https://cdnfhnfile.115.com/63a79fdca011d11b46d5fda83f6fdf7fe5141ec7/%E6%95%B0%E5%AD%A612%E6%9C%8819%E6%97%A5-12%E6%9C%8823%E6%97%A5%E7%BA%BF%E4%B8%8A%E8%B5%84%E6%BA%90.zip?t=1734336235&u=ip976768882&s=512000&d=976768882--0&c=1&f=1&k=89c7796e0906aca3eecccbd545775653&us=512000&uc=0&v=1
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

View File

@@ -66,3 +66,27 @@ Referer:https://www.feijipan.com/
POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&uuid=WQAl5yBy1naGudJEILBvE&extra=2&timestamp=A8ECC3C7C50191ACEB9CB8444FD37624&shareId=nMtCOXL&type=0&offset=1&limit=60
###
POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&uuid=23e9pOiMqb2Nc164OwF7X&extra=2&timestamp=1E5EE05EE24146C0EA994200A7EC2D82&shareId=JoUTkZYj&type=0&offset=1&limit=60
###
POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&uuid=23e9pOiMqb2Nc164OwF7X&extra=2&timestamp=F09BD416CC5AB6FFA52B8CA1FA6C38B7&shareId=JoUTkZYj&type=0&offset=1&limit=60
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 0
DNT: 1
Host: api.feijipan.com
Origin: https://www.feijix.com
Pragma: no-cache
Referer: https://www.feijix.com/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-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
appToken: undefined
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"

View File

@@ -25,3 +25,4 @@ https://api.ilanzou.com/unproved/file/redirect?uuid=0&devType=6&timestamp=453240
https://www.ilanzou.com/s/zHkna1S
### fileId: 145042258
https://api.ilanzou.com/unproved/recommend/list?devType=6&devModel=Chrome&uuid=AjiM-Wl782OuHyuvKiuaH&extra=2&timestamp=311D0B438400DB5D98F1D2FF5A7A86F1&shareId=jQ9i3F0&type=0&offset=1&limit=60

View File

@@ -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
Cookie: __puus=dc48cb12577eb3df6fe84fdea250ad6fAAOF0LBv/M4HTtkYfuUNdcVLXHZl1x2mw8NQSdxo5abymS+irugphlPNv5kwQZkDI+pXaeOD22v/whNQT5AwUULF0q1nSNXmHqxr20AJjXlEhvbIZNgUfwmw8aOCyarrLi7o7w6w0Rod4DLCSeYGwlTF3P9jMcqCM+WqWHnxKY6i8gaXZkHLObatSHkwivB7Xpc=
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
###

View File

@@ -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**
###
# @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
#密码qaiu
https://qaiu.118pan.com/b1228264

View 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"
}

View File

@@ -55,6 +55,7 @@
<logger name="io.netty" level="warn"/>
<logger name="io.vertx" level="info"/>
<logger name="com.zaxxer.hikari" level="info"/>
<logger name="cn.qaiu" level="debug"/>
<root level="info">
<appender-ref ref="STDOUT"/>
<!-- <appender-ref ref="FILE"/>-->

View 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);
}
}