Compare commits

...

54 Commits

Author SHA1 Message Date
QAIU
da063461be Merge remote-tracking branch 'origin/main' 2023-08-25 16:56:00 +08:00
QAIU
3234f643d5 版本更新至0.1.7,启用h2db,添加统计功能,框架优化 2023-08-25 16:55:38 +08:00
qaiu
332cc587a2 Update README.md 2023-08-25 10:04:44 +08:00
qaiu
b30d01e154 Update README.md 2023-08-25 10:04:27 +08:00
QAIU
79d8aa0868 蓝奏云规则微调, 框架优化(70%) 2023-08-23 14:55:08 +08:00
qaiu
a724d703ed Update LzTool.java 2023-08-21 12:27:39 +08:00
qaiu
18478b2a0b Update README.md 2023-08-20 10:36:39 +08:00
qaiu
dde54ed9cc Merge pull request #13 from qaiu/dev
蓝奏云规则修改, 奶牛功能增强
2023-08-20 10:35:02 +08:00
qaiu
3c56a7183e 修复蓝奏云pan解析错误的问题#12 2023-08-20 10:29:00 +08:00
QAIU
38a3ae8a56 Merge remote-tracking branch 'origin/dev' into dev 2023-08-19 17:29:04 +08:00
QAIU
00b30331eb 奶牛快传支持下载zip目录分享#11, core框架优化 2023-08-19 17:28:30 +08:00
qaiu
8397c24715 Update README.md 2023-08-13 00:58:12 +08:00
qaiu
3ee113e81f Update README.md 2023-08-13 00:55:20 +08:00
qaiu
cde641c8db Update README.md 2023-08-13 00:54:21 +08:00
QAIU
30cd2f5850 奶牛快传支持下载zip目录分享#11, core框架优化 2023-08-11 15:28:37 +08:00
QAIU
71066ded98 core框架优化 2023-08-10 14:54:45 +08:00
qaiu
8441954075 修复123pan解析错误的问题#9 2023-08-10 01:49:41 +08:00
qaiu
5e0568fa0e 代码结构优化;修复123pan解析错误的问题#9 2023-08-10 01:45:39 +08:00
qaiu
594e9af71d 代码结构优化;修复123pan解析错误的问题#9 2023-08-10 01:42:34 +08:00
qaiu
9bc7e9aa26 代码结构优化
修复123pan解析错误的问题#9
2023-08-10 01:38:20 +08:00
qaiu
5805edf0a9 Merge pull request #10 from qaiu/master
Master
2023-08-10 01:17:11 +08:00
qaiu
9614a36fc2 fixed 123pan /b/api/share/download/info->/a/api/share/download/info 2023-08-10 00:59:09 +08:00
QAIU
f45031c3f1 代码结构优化, 异常处理优化 2023-08-08 17:36:36 +08:00
QAIU
28e0542c8e 代码结构优化, 异常处理优化 2023-08-08 11:34:32 +08:00
QAIU
b78a8300c6 0 2023-08-04 17:49:30 +08:00
qaiu
ca06d5ae98 Update README.md 2023-08-02 17:53:04 +08:00
QAIU
0c5e53b86e 0 2023-08-01 14:42:32 +08:00
qaiu
f45e6f3468 前端页面优化 2023-07-31 22:43:23 +08:00
qaiu
08ffec524e 前端页面优化 2023-07-31 22:41:33 +08:00
QAIU
62dea67d69 0 2023-07-31 14:07:12 +08:00
QAIU
8108a4dd3f Merge remote-tracking branch 'origin/main' 2023-07-31 13:56:49 +08:00
QAIU
56334fa4a2 360亿方云API-URL变更 2023-07-31 13:56:35 +08:00
qaiu
093b337396 Update README.md 2023-07-31 12:06:59 +08:00
qaiu
4bedf4486b Update README.md 2023-07-31 12:03:34 +08:00
qaiu
b700493a6c Merge pull request #6 from qaiu/dependabot/npm_and_yarn/web-front/webpack-5.88.2
Bump webpack from 5.75.0 to 5.88.2 in /web-front
2023-07-31 11:42:09 +08:00
qaiu
44b1168bb5 Merge pull request #7 from qaiu/dependabot/npm_and_yarn/web-front/word-wrap-1.2.5
Bump word-wrap from 1.2.3 to 1.2.5 in /web-front
2023-07-31 11:41:57 +08:00
dependabot[bot]
a351364faf Bump webpack from 5.75.0 to 5.88.2 in /web-front
Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.88.2.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.75.0...v5.88.2)

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-30 19:22:56 +00:00
qaiu
7595345e7b Merge remote-tracking branch 'origin/main' 2023-07-31 03:22:02 +08:00
qaiu
d13fe1fc21 添加前端页面 2023-07-31 03:21:39 +08:00
qaiu
f0269b1687 Update README.md 2023-07-31 00:20:52 +08:00
qaiu
257147dd75 Update README.md 2023-07-30 16:29:27 +08:00
qaiu
6fe824e705 添加 通过JS文件获取123pan签名 2023-07-29 18:08:30 +08:00
QAIU
f482982a25 123网盘解析规则优化 2023-07-27 17:45:53 +08:00
QAIU
e3ec81bd9b 123网盘解析规则优化 2023-07-24 10:47:01 +08:00
QAIU
4771a5b5ce Merge remote-tracking branch 'origin/main' 2023-07-24 10:41:43 +08:00
QAIU
1292440f95 123网盘解析规则优化 2023-07-24 10:41:25 +08:00
qaiu
c2052ddbd8 Update README.md 2023-07-23 15:15:25 +08:00
qaiu
f65dfacf4b Update README.md 2023-07-23 15:13:23 +08:00
qaiu
27c2c1ab29 Update README.md 2023-07-23 15:12:58 +08:00
qaiu
b4471dd9c3 Update README.md
lz.qaiu.top测试123pan 下载Dragonwell JDK17
2023-07-22 15:33:10 +08:00
qaiu
69d8b645a4 Update README.md
lz.qaiu.top测试123pan 下载Dragonwell JDK17
2023-07-22 15:32:13 +08:00
103 changed files with 16408 additions and 672 deletions

1
.gitignore vendored
View File

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

View File

@@ -1,11 +1,24 @@
# netdisk-fast-download
云盘解析服务 (nfd云解析)
预览地址 https://lz.qaiu.top
[![Java CI with Maven](https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml/badge.svg)](https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml)
[![jdk](https://img.shields.io/badge/jdk-%3E%3D17-blue)](https://www.oracle.com/cn/java/technologies/downloads/)
[![vert.x](https://img.shields.io/badge/vert.x-4.4.1-blue)](https://vertx-china.github.io/)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/qaiu/netdisk-fast-download)](https://github.com/qaiu/netdisk-fast-download/releases/tag/0.1.6-releases)
## 项目介绍
网盘直链解析工具能把网盘分享下载链接转化为直链,已支持蓝奏云/奶牛快传/移动云云空间/小飞机盘/亿方云/123云盘等支持私密分享。
## 应用场景:
1. 游戏:使用本站工具可自动生成高速直流下载器,用于游戏客户端自动化高速下载安装、更新等。
2. 下载:可根据网盘分享的地址解析为直链,方便放到自己网站上供用户下载,节约服务器带宽和磁盘。
3. 音视频:可解析为音视频直链,可直接在网页上播放,高清,高速,节约服务器带宽和磁盘。
## 网盘支持情况:
> 20230722 UC网盘解析失效需要登录
> 20230824 123云盘解析大文件(>100MB)失效,需要登录
`网盘名称(网盘标识):`
- [蓝奏云 (lz)](https://pc.woozooo.com/)
@@ -17,7 +30,7 @@
- [移动云空间 (ec)](https://www.ecpan.cn/web)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [UC网盘 (uc)](https://fast.uc.cn/)
- [UC网盘 (uc)似乎已经失效,需要登录](https://fast.uc.cn/)
- [ ] 登录, 上传, 下载, 分享
- [X] 直链解析
- [小飞机网盘 (fj)](https://www.feijipan.com/)
@@ -35,7 +48,7 @@
**TODO:**
- 登录接口, 文件上传/下载/分享后端接口
- 短地址服务
- 前端界面(正则规划)
- 前端界面(建设中...)
**技术栈:**
Jdk17+Vert.x4.4.1
@@ -52,6 +65,7 @@ parser接口可以直接解析分享链接: 加密分享需要加上参数pwd=
http(s)://your_host/parser?url=分享链接(&pwd=xxx)
http(s)://your_host/网盘标识/分享key(@分享密码)
2. 获取解析后的直链--JSON格式
http(s)://your_host/json/parser?url=分享链接(&pwd=xxx)
http(s)://your_host/json/网盘标识/分享key(@分享密码)
3. 特别注意的地方:
- 有些网盘的加密分享的密码可以忽略: 如移动云空间,小飞机网盘
@@ -60,6 +74,17 @@ parser接口可以直接解析分享链接: 加密分享需要加上参数pwd=
http://your_host/parser?url=https://www.ecpan.cn/web/%23/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
http://your_host/parser?url=https://www.ecpan.cn/&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
```
json返回数据格式示例:
```json
{
"code": 200,
"msg": "success",
"success": true,
"count": 0,
"data": "https://下载链接",
"timestamp": 1690733953927
}
```
IDEA HttpClient示例:
@@ -105,7 +130,7 @@ GET http://127.0.0.1:6400/json/fc/e5079007dc31226096628870c7@QAIU
| 蓝奏云 | √ | √ | 不限空间 | 100M | TODO |
| 奶牛快传 | √ | X | 10G | 不限大小 | TODO |
| 移动云空间 | √ | √(密码可忽略) | 5G(个人) | 不限大小 | TODO |
| UC网盘 | | √ | 10G | 不限大小 | TODO |
| UC网盘 | 需要登录 | √ | 10G | 不限大小 | TODO |
| 小飞机网盘 | √ | √(密码可忽略) | 10G | 不限大小 | TODO |
| 360亿方云 | √(试用账号有时间限制企业版需要599续费) | √(密码可忽略) | 100G(须实名) | 不限大小 | TODO |
| 123云盘 | √ | √ | 2T | 100G | TODO |
@@ -114,6 +139,12 @@ GET http://127.0.0.1:6400/json/fc/e5079007dc31226096628870c7@QAIU
# 打包部署
## JDK下载lz.qaiu.top提供直链云解析服务
- [阿里jdk17(Dragonwell17-windows-x86)](https://lz.qaiu.top/ye/iaKtVv-hbECd)
- [阿里jdk17(Dragonwell17-linux-x86)](https://lz.qaiu.top/ye/iaKtVv-AbECd)
- [阿里jdk17(Dragonwell17-linux-aarch64)](https://lz.qaiu.top/ye/iaKtVv-HbECd)
- [123云盘解析有效性测试用-阿里jdk17(Dragonwell17-linux-aarch64)](https://lz.qaiu.top/json/ye/iaKtVv-HbECd)
## 开发和打包
```shell
@@ -127,9 +158,9 @@ mvn package
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
```shell
cd ~
wget -O netdisk-fast-download-0.1.6-bin.zip https://github.com/qaiu/netdisk-fast-download/releases/download/0.1.6-releases/netdisk-fast-download-0.1.6-bin.zip
unzip netdisk-fast-download-*-bin.zip
cd netdisk-fast-download-*-bin
wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/0.1.7/netdisk-fast-download.zip
unzip netdisk-fast-download.zip
cd netdisk-fast-download
bash service-install.sh
```
服务相关命令:
@@ -153,10 +184,11 @@ systemctl enable netdisk-fast-download.servic
systemctl disable netdisk-fast-download.servic
## Windows服务部署
1. 下载并解压releases版本netdisk-fast-download-0.1.6-bin.zip
2. 进入netdisk-fast-download-0.1.6-bin目录
1. 下载并解压releases版本netdisk-fast-download.zip
2. 进入netdisk-fast-download下的bin目录
3. 使用管理员权限运行nfd-service-install.bat
如果不想使用服务运行可以直接运行run.bat
> 注意: 如果jdk环境变量的java版本不是17请修改nfd-service-template.xml中的java命令的路径改为实际路径
## Docker部署
TODO

View File

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

View File

@@ -7,7 +7,7 @@ pushd %~dp0
set MY_DIR=%~dp0
set MY_DIR=%MY_DIR:~0,-1%
for /f "delims=X" %%i in ('dir /b %MY_DIR%\netdisk-fast-download-*.jar') do (
for /f "delims=X" %%i in ('dir /b %MY_DIR%\netdisk-fast-download.jar') do (
set LAUNCH_JAR=%MY_DIR%\%%i
)

View File

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

View File

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

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId>
<version>0.1.6</version>
<version>0.1.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

68
parser/pom.xml Normal file
View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId>
<version>0.1.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>parser</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--logback日志实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjdk.nashorn/nashorn-core -->
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,19 @@
package cn.qaiu;
import io.vertx.core.Vertx;
public class WebClientVertxInit {
private Vertx vertx = null;
private static final WebClientVertxInit INSTANCE = new WebClientVertxInit();
public static void init(Vertx vx) {
INSTANCE.vertx = vx;
}
public static Vertx get() {
if (INSTANCE.vertx == null) {
throw new IllegalArgumentException("VertxInit getVertx: vertx is null");
}
return INSTANCE.vertx;
}
}

View File

@@ -0,0 +1,46 @@
package cn.qaiu.parser;//package cn.qaiu.lz.common.parser;
import cn.qaiu.parser.impl.*;
import io.vertx.core.Future;
public interface IPanTool {
Future<String> parse();
static IPanTool typeMatching(String type, String key, String pwd) {
return switch (type) {
case "lz" -> new LzTool(key, pwd);
case "cow" -> new CowTool(key, pwd);
case "ec" -> new EcTool(key, pwd);
case "fc" -> new FcTool(key, pwd);
case "uc" -> new UcTool(key, pwd);
case "ye" -> new YeTool(key, pwd);
case "fj" -> new FjTool(key, pwd);
case "qk" -> new QkTool(key, pwd);
default -> {
throw new UnsupportedOperationException("未知分享类型");
}
};
}
static IPanTool shareURLPrefixMatching(String url, String pwd) {
if (url.contains(CowTool.LINK_KEY)) {
return new CowTool(url, pwd);
} else if (url.startsWith(EcTool.SHARE_URL_PREFIX)) {
return new EcTool(url, pwd);
} else if (url.startsWith(FcTool.SHARE_URL_PREFIX0)) {
return new FcTool(url, pwd);
} else if (url.startsWith(UcTool.SHARE_URL_PREFIX)) {
return new UcTool(url, pwd);
} else if (url.startsWith(YeTool.SHARE_URL_PREFIX)) {
return new YeTool(url, pwd);
} else if (url.startsWith(FjTool.SHARE_URL_PREFIX)) {
return new FjTool(url, pwd);
} else if (url.contains(LzTool.LINK_KEY)) {
return new LzTool(url, pwd);
}
throw new UnsupportedOperationException("未知分享类型");
}
}

View File

@@ -0,0 +1,57 @@
package cn.qaiu.parser;
import cn.qaiu.WebClientVertxInit;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class PanBase {
protected Logger log = LoggerFactory.getLogger(this.getClass());
protected Promise<String> promise = Promise.promise();
protected WebClient client = WebClient.create(WebClientVertxInit.get());
protected WebClient clientNoRedirects = WebClient.create(WebClientVertxInit.get(), OPTIONS);
private static final WebClientOptions OPTIONS = new WebClientOptions().setFollowRedirects(false);
protected String key;
protected String pwd;
protected PanBase(String key, String pwd) {
this.key = key;
this.pwd = pwd;
}
protected void fail(Throwable t, String errorMsg, Object... args) {
try {
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
log.error("解析异常: " + s, t.fillInStackTrace());
promise.fail(this.getClass().getSimpleName() + ": 解析异常: " + s + " -> " + t);
} catch (Exception e) {
log.error("ErrorMsg format fail. The parameter has been discarded", e);
log.error("解析异常: " + errorMsg, t.fillInStackTrace());
promise.fail(this.getClass().getSimpleName() + ": 解析异常: " + errorMsg + " -> " + t);
}
}
protected void fail(String errorMsg, Object... args) {
try {
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
log.error("解析异常: " + s);
promise.fail(this.getClass().getSimpleName() + " - 解析异常: " + s);
} catch (Exception e) {
log.error("ErrorMsg format fail. The parameter has been discarded", e);
log.error("解析异常: " + errorMsg);
promise.fail(this.getClass().getSimpleName() + " - 解析异常: " + errorMsg);
}
}
protected Handler<Throwable> handleFail(String errorMsg) {
return t -> fail(this.getClass().getSimpleName() + " - 请求异常 {}: -> {}", errorMsg, t.fillInStackTrace());
}
}

View File

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

View File

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

View File

@@ -1,22 +1,17 @@
package cn.qaiu.lz.common.parser.impl;
package cn.qaiu.parser.impl;
import cn.qaiu.lz.common.parser.IPanTool;
import cn.qaiu.lz.common.util.CommonUtils;
import cn.qaiu.lz.common.util.PanExceptionUtils;
import cn.qaiu.vx.core.util.VertxHolder;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.CommonUtils;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import io.vertx.uritemplate.UriTemplate;
import lombok.extern.slf4j.Slf4j;
/**
* 移动云空间解析
*/
@Slf4j
public class EcTool implements IPanTool {
public class EcTool extends PanBase implements IPanTool {
private static final String FIRST_REQUEST_URL = "https://www.ecpan.cn/drive/fileextoverrid" +
".do?chainUrlTemplate=https:%2F%2Fwww.ecpan" +
".cn%2Fweb%2F%23%2FyunpanProxy%3Fpath%3D%252F%2523%252Fdrive%252Foutside&parentId=-1&data={dataKey}";
@@ -25,10 +20,12 @@ public class EcTool implements IPanTool {
public static final String SHARE_URL_PREFIX = "www.ecpan.cn/";
public Future<String> parse(String data, String code) {
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, data);
Promise<String> promise = Promise.promise();
WebClient client = WebClient.create(VertxHolder.getVertxInstance());
public EcTool(String key, String pwd) {
super(key, pwd);
}
public Future<String> parse() {
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
// 第一次请求 获取文件信息
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("dataKey", dataKey).send().onSuccess(res -> {
JsonObject jsonObject = res.bodyAsJsonObject();
@@ -37,8 +34,7 @@ public class EcTool implements IPanTool {
.getJsonObject("var")
.getJsonObject("chainFileInfo");
if (fileInfo.containsKey("errMesg")) {
promise.fail(new RuntimeException(DOWNLOAD_REQUEST_URL + " 解析失败: "
+ fileInfo.getString("errMesg")) + " key = " + dataKey);
fail("{} 解析失败:{} key = {}", FIRST_REQUEST_URL, fileInfo.getString("errMesg"), dataKey);
return;
}
JsonObject cloudpFile = fileInfo.getJsonObject("cloudpFile");
@@ -55,9 +51,9 @@ public class EcTool implements IPanTool {
JsonObject jsonRes = res2.bodyAsJsonObject();
log.debug("ecPan get download url -> {}", res2.body().toString());
promise.complete(jsonRes.getJsonObject("var").getString("downloadUrl"));
}).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Ec", dataKey, t)));
}).onFailure(handleFail(""));
}
).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Ec", dataKey, t)));;
).onFailure(handleFail(FIRST_REQUEST_URL));
return promise.future();
}
}

View File

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

View File

@@ -1,16 +1,13 @@
package cn.qaiu.lz.common.parser.impl;
package cn.qaiu.parser.impl;
import cn.qaiu.lz.common.parser.IPanTool;
import cn.qaiu.lz.common.util.AESUtils;
import cn.qaiu.lz.common.util.CommonUtils;
import cn.qaiu.lz.common.util.PanExceptionUtils;
import cn.qaiu.vx.core.util.VertxHolder;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.AESUtils;
import cn.qaiu.util.CommonUtils;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.uritemplate.UriTemplate;
import java.util.UUID;
@@ -20,7 +17,7 @@ import java.util.UUID;
*
* @version V016_230609
*/
public class FjTool implements IPanTool {
public class FjTool extends PanBase implements IPanTool {
public static final String SHARE_URL_PREFIX = "https://www.feijix.com/s/";
private static final String API_URL_PREFIX = "https://api.feijipan.com/ws/";
@@ -31,12 +28,14 @@ public class FjTool implements IPanTool {
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "file/redirect?downloadId={fidEncode}&enable=1" +
"&devType=6&uuid={uuid}&timestamp={ts}&auth={auth}";
public Future<String> parse(String data, String code) {
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, data);
public FjTool(String key, String pwd) {
super(key, pwd);
}
Promise<String> promise = Promise.promise();
WebClient client = WebClient.create(VertxHolder.getVertxInstance(),
new WebClientOptions().setFollowRedirects(false));
public Future<String> parse() {
String dataKey = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, key);
WebClient client = clientNoRedirects;
String shareId = String.valueOf(AESUtils.idEncrypt(dataKey));
// 第一次请求 获取文件信息
@@ -44,11 +43,11 @@ public class FjTool implements IPanTool {
client.postAbs(UriTemplate.of(FIRST_REQUEST_URL)).setTemplateParam("shareId", shareId).send().onSuccess(res -> {
JsonObject resJson = res.bodyAsJsonObject();
if (resJson.getInteger("code") != 200) {
promise.fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
fail(FIRST_REQUEST_URL + " 返回异常: " + resJson);
return;
}
if (resJson.getJsonArray("list").size() == 0) {
promise.fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
fail(FIRST_REQUEST_URL + " 解析文件列表为空: " + resJson);
return;
}
// 文件Id
@@ -67,12 +66,12 @@ public class FjTool implements IPanTool {
.setTemplateParam("auth", auth).send().onSuccess(res2 -> {
MultiMap headers = res2.headers();
if (!headers.contains("Location")) {
promise.fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res.headers());
fail(SECOND_REQUEST_URL + " 未找到重定向URL: \n" + res.headers());
return;
}
promise.complete(headers.get("Location"));
}).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Fj", dataKey, t)));
}).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Fj", dataKey, t)));
}).onFailure(handleFail(SECOND_REQUEST_URL));
}).onFailure(handleFail(FIRST_REQUEST_URL));
return promise.future();
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package cn.qaiu.lz.common.util;
package cn.qaiu.util;
import org.apache.commons.lang3.StringUtils;
@@ -274,8 +274,7 @@ public class AESUtils {
return new BigInteger(1, digest).toString(16);
}
public static String getAuthKey() {
String _0x2207af = "/b/api/share/download/info";
public static String getAuthKey(String _0x2207af) {
String _0x467baa = "web";
int _0x4965f1 = 3;

View File

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

View File

@@ -1,4 +1,4 @@
package cn.qaiu.lz.common.util;
package cn.qaiu.util;
import java.net.MalformedURLException;
import java.net.URL;
@@ -8,12 +8,16 @@ import java.util.Map;
public class CommonUtils {
public static String adaptShortPaths(String urlPrefix, String url) {
if (!url.startsWith(urlPrefix)) {
url = urlPrefix + url;
}
if (url.endsWith(".html")) {
url = url.substring(0, url.length() - 5);
}
String prefix = "https://";
if (!url.startsWith(urlPrefix) && url.startsWith(prefix)) {
urlPrefix = urlPrefix.substring(prefix.length());
return url.substring(url.indexOf(urlPrefix) + urlPrefix.length());
} else if (!url.startsWith(urlPrefix)) {
url = urlPrefix + url;
}
return url.substring(urlPrefix.length());
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
package cn.qaiu.web.test;
package qaiu.web.test;
import cn.qaiu.lz.common.util.AESUtils;
import cn.qaiu.util.AESUtils;
import org.junit.Assert;
import org.junit.Test;
@@ -50,6 +50,12 @@ public class TestAESUtil {
Assert.assertEquals(146731, AESUtils.idEncrypt("7jy0zlv"));
}
@Test
public void test00() throws IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException,
NoSuchAlgorithmException, InvalidKeyException {
System.out.println(AESUtils.decryptByBase64AES(AESUtils.CIPHER_AES2, AESUtils.CIPHER_AES));
}
@Test
public void testTs() {
System.out.println(System.currentTimeMillis());
@@ -65,9 +71,9 @@ public class TestAESUtil {
@Test
public void testKeyAuth(){
System.out.println(AESUtils.getAuthKey());
System.out.println(AESUtils.getAuthKey());
System.out.println(AESUtils.getAuthKey());
System.out.println(AESUtils.getAuthKey("/a/api/share/download/info"));
System.out.println(AESUtils.getAuthKey("/a/api/share/download/info"));
System.out.println(AESUtils.getAuthKey("/b/api/share/get"));
}

View File

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

View File

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

View File

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

23
pom.xml
View File

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

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

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

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

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

View File

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

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

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

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

File diff suppressed because it is too large Load Diff

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

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

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

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

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

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

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

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

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

View File

@@ -5,19 +5,17 @@
<parent>
<artifactId>netdisk-fast-download</artifactId>
<groupId>cn.qaiu</groupId>
<version>0.1.6</version>
<version>0.1.7</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<version>0.1.6</version>
<version>0.1.7</version>
<artifactId>web-service</artifactId>
<properties>
<packageDirectory>${project.basedir}/target/package</packageDirectory>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<slf4j.version>2.0.5</slf4j.version>
<vertx-jooq.version>6.1.0</vertx-jooq.version>
<finalName>netdisk-fast-download-${project.version}</finalName>
<finalName>netdisk-fast-download</finalName>
</properties>
<dependencies>
@@ -36,13 +34,23 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.6</version>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>core-database</artifactId>
<version>0.1.7</version>
</dependency>
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>parser</artifactId>
<version>0.1.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@@ -52,16 +60,16 @@
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
<version>4.4.1</version>
<version>${vertx.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjdk.nashorn/nashorn-core -->
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>core-database</artifactId>
<version>0.1.6</version>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.4</version>
</dependency>
</dependencies>
<build>
@@ -100,6 +108,8 @@
<exclude>*.**</exclude>
<exclude>*/*.xml</exclude>
<exclude>conf/**</exclude>
<exclude>js/**</exclude>
<exclude>http-tools/**</exclude>
</excludes>
<archive>
<manifest>

View File

@@ -1,9 +1,13 @@
package cn.qaiu.lz;
import cn.qaiu.WebClientVertxInit;
import cn.qaiu.db.pool.JDBCPoolInit;
import cn.qaiu.vx.core.Deploy;
import cn.qaiu.vx.core.util.ConfigConstant;
import cn.qaiu.vx.core.util.VertxHolder;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import io.vertx.core.json.JsonObject;
import io.vertx.core.json.jackson.DatabindCodec;
/**
@@ -24,6 +28,8 @@ public class AppMain {
* @param jsonObject 配置
*/
private static void exec(JsonObject jsonObject) {
WebClientVertxInit.init(VertxHolder.getVertxInstance());
DatabindCodec.mapper().registerModule(new JavaTimeModule());
if (jsonObject.getJsonObject(ConfigConstant.SERVER).getBoolean("enableDatabase")) {
JDBCPoolInit.builder().config(jsonObject.getJsonObject("dataSource")).build().initPool();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,45 +0,0 @@
package cn.qaiu.lz.common.parser;//package cn.qaiu.lz.common.parser;
import cn.qaiu.lz.common.parser.impl.*;
import io.vertx.core.Future;
public interface IPanTool {
Future<String> parse(String data, String code);
static IPanTool typeMatching(String type) {
return switch (type) {
case "lz" -> new LzTool();
case "cow" -> new CowTool();
case "ec" -> new EcTool();
case "fc" -> new FcTool();
case "uc" -> new UcTool();
case "ye" -> new YeTool();
case "fj" -> new FjTool();
default -> {
throw new IllegalArgumentException("未知分享类型");
}
};
}
static IPanTool shareURLPrefixMatching(String url) {
if (url.startsWith(CowTool.SHARE_URL_PREFIX)) {
return new CowTool();
} else if (url.startsWith(EcTool.SHARE_URL_PREFIX)) {
return new EcTool();
} else if (url.startsWith(FcTool.SHARE_URL_PREFIX)) {
return new FcTool();
} else if (url.startsWith(UcTool.SHARE_URL_PREFIX)) {
return new UcTool();
} else if (url.startsWith(YeTool.SHARE_URL_PREFIX)) {
return new YeTool();
} else if (url.startsWith(FjTool.SHARE_URL_PREFIX)) {
return new FjTool();
} else if (url.contains("lanzou")) {
return new LzTool();
}
throw new IllegalArgumentException("未知分享类型");
}
}

View File

@@ -1,63 +0,0 @@
package cn.qaiu.lz.common.parser.impl;
import cn.qaiu.lz.common.parser.IPanTool;
import cn.qaiu.lz.common.util.CommonUtils;
import cn.qaiu.lz.common.util.PanExceptionUtils;
import cn.qaiu.vx.core.util.VertxHolder;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
/**
* 奶牛快传解析工具
*
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/4/21 21:19
*/
@Slf4j
public class CowTool implements IPanTool {
private static final String API_REQUEST_URL = "https://cowtransfer.com/core/api/transfer/share";
public static final String SHARE_URL_PREFIX = "https://cowtransfer.com/s/";
public Future<String> parse(String data, String code) {
Promise<String> promise = Promise.promise();
WebClient client = WebClient.create(VertxHolder.getVertxInstance());
String key = CommonUtils.adaptShortPaths(SHARE_URL_PREFIX, data);
client.getAbs(API_REQUEST_URL + "?uniqueUrl=" + key).send().onSuccess(res -> {
JsonObject resJson = res.bodyAsJsonObject();
if ("success".equals(resJson.getString("message")) && resJson.containsKey("data")) {
JsonObject dataJson = resJson.getJsonObject("data");
String guid = dataJson.getString("guid");
String fileId = dataJson.getJsonObject("firstFile").getString("id");
String url2 = API_REQUEST_URL + "/download?transferGuid=" + guid + "&fileId=" + fileId;
client.getAbs(url2).send().onSuccess(res2 -> {
JsonObject res2Json = res2.bodyAsJsonObject();
if ("success".equals(res2Json.getString("message")) && res2Json.containsKey("data")) {
JsonObject data2 = res2Json.getJsonObject("data");
String downloadUrl = data2.getString("downloadUrl");
if (StringUtils.isNotEmpty(downloadUrl)) {
log.info("cow parse success: {}", downloadUrl);
promise.complete(downloadUrl);
return;
}
log.error("cow parse fail: {}; downloadUrl is empty", url2);
promise.fail("cow parse fail: " + url2 + "; downloadUrl is empty");
return;
}
log.error("cow parse fail: {}; json: {}", url2, res2Json);
promise.fail("cow parse fail: " + url2 + "; json:" + res2Json);
});
return;
}
log.error("cow parse fail: {}; json: {}", key, resJson);
promise.fail("cow parse fail: " + key + "; json:" + resJson);
}).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Cow", key, t)));
return promise.future();
}
}

View File

@@ -1,91 +0,0 @@
package cn.qaiu.lz.common.parser.impl;
import cn.qaiu.lz.common.parser.IPanTool;
import cn.qaiu.lz.common.util.PanExceptionUtils;
import cn.qaiu.vx.core.util.VertxHolder;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 蓝奏云解析工具
*
* @author QAIU
* @version 1.0 update 2021/5/16 10:39
*/
public class LzTool implements IPanTool {
public static final String SHARE_URL_PREFIX = "https://wwwa.lanzoui.com";
public Future<String> parse(String data, String code) {
Promise<String> promise = Promise.promise();
String key = data.indexOf('/') > 0 ? data : SHARE_URL_PREFIX + "/" + data;
WebClient client = WebClient.create(VertxHolder.getVertxInstance(),
new WebClientOptions().setFollowRedirects(false));
client.getAbs(key).send().onSuccess(res -> {
String html = res.bodyAsString();
// 匹配iframe
Pattern compile = Pattern.compile("src=\"(/fn\\?[a-zA-Z\\d_+/=]{16,})\"");
Matcher matcher = compile.matcher(html);
if (!matcher.find()) {
// 没有Iframe说明是加密分享, 匹配sign通过密码请求下载页面
Pattern compile2 = Pattern.compile("sign=(\\w{16,})");
Matcher matcher2 = compile2.matcher(html);
if (!matcher2.find()) {
promise.fail(key + ": sign正则匹配失败, 可能分享已失效: " + html);
return;
}
String sign = matcher2.group(1);
getDownURL(promise, code, key, client, sign);
return;
}
String iframePath = matcher.group(1);
client.getAbs(SHARE_URL_PREFIX + iframePath).send().onSuccess(res2 -> {
String html2 = res2.bodyAsString();
System.out.println(html);
Matcher matcher2 = Pattern.compile("'sign'\s*:\s*'(\\w+)'").matcher(html2);
if (!matcher2.find()) {
promise.fail(SHARE_URL_PREFIX + iframePath + " -> " + key + ": sign正则匹配失败, 可能分享已失效: " + html2);
return;
}
String sign = matcher2.group(1);
getDownURL(promise, code, key, client, sign);
}).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Lz", key, t)));
}).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Lz", key, t)));
return promise.future();
}
private void getDownURL(Promise<String> promise, String code, String key, WebClient client, String sign) {
MultiMap headers = MultiMap.caseInsensitiveMultiMap();
var userAgent2 = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, " +
"like " +
"Gecko) Chrome/111.0.0.0 Mobile Safari/537.36";
headers.set("User-Agent", userAgent2);
headers.set("referer", key);
headers.set("sec-ch-ua-platform", "Android");
headers.set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
headers.set("sec-ch-ua-mobile", "sec-ch-ua-mobile");
client.postAbs(SHARE_URL_PREFIX + "/ajaxm.php").putHeaders(headers).sendForm(MultiMap
.caseInsensitiveMultiMap()
.set("action", "downprocess")
.set("sign", sign).set("p", code)).onSuccess(res2 -> {
JsonObject urlJson = res2.bodyAsJsonObject();
if (urlJson.getInteger("zt") != 1) {
promise.fail(urlJson.getString("inf"));
return;
}
String downUrl = urlJson.getString("dom") + "/file/" + urlJson.getString("url");
client.getAbs(downUrl).putHeaders(headers).send()
.onSuccess(res3 -> promise.complete(res3.headers().get("Location")))
.onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Lz", key, t)));
}).onFailure(t -> promise.fail(PanExceptionUtils.fillRunTimeException("Lz", key, t)));
}
}

View File

@@ -1,12 +0,0 @@
package cn.qaiu.lz.common.parser.impl;
import io.vertx.core.Future;
import io.vertx.core.Promise;
public class QkTool {
public static Future<String> parse(String data, String code) {
Promise<String> promise = Promise.promise();
return promise.future();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
# 要激活的配置: dev--连接本地数据库; prod连接线上数据库
active: dev
# 框架版本号 和主版本号
version_vertx: 4.4.1
version_app: 0.1.6
# 版本号
version_app: 0.1.7
# 公司名称 -> LOGO版权文字
copyright: QAIU

View File

@@ -46,15 +46,14 @@ Content-Disposition: form-data; name="email"
--WebAppBoundary
Content-Disposition: form-data; name="joinProToken"
--WebAppBoundary--
###
https://cowtransfer.com/core/api/transfer/share/download?transferGuid=eb9df924-7b29-496b-8147-96762fc81d28&title=spring-play
###
git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch web-service/src/main/resources/http-tools/test.http' --prune-empty --tag-name-filter cat -- --all
git push origin master --force
rmdir .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now
https://cowtransfer.com/core/api/transfer/share/download?transferGuid=
###
https://cowtransfer.com/core/api/transfer/share/download?transferGuid=eb9df924-7b29-496b-8147-96762fc81d28

View File

@@ -11,7 +11,7 @@
# offset: 1
# limit: 60
# @no-cookie-jar
POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=146731&type=0&offset=1&limit=60
POST https://api.feijipan.com/ws/recommend/list?devType=6&devModel=Chrome&extra=2&shareId=193186&type=0&offset=1&limit=60
# 654488C79332E7279F9A38367BD72D3B
# 4703B103BD6871F83441233393695EF3

View File

@@ -24,4 +24,13 @@ https://developer.lanzoug.com/file/?VDJbZVxtADFSWwY+U2YHa1FuU2tTYgBnBnUGZFNmWylS
###
https://developer.lanzoug.com/file/?VTNVa1tqAjMFDAM7BDEAbAE+U2tfbgZhBnVbOQUwVCYEb1IoAToCNwQhVnRXLlcyVWAEdl9uVzkEbFYxVm5VeVVlVSxbNwJ4BWADeAQyAGgBNVN4X3oGbQZxW3sFP1Q8BGhSYAEDAj8ENlY9VzJXZ1U3BDFfMldlBDFWaVY9VXJVM1VxWzsCZwVlA2QEYAAwAWtTYF8wBiIGcVstBWRUZwQ0UjcBbwJ5BGJWNVcsV2RVOgQuX2NXZgQ2VjJWMlVhVWdVNlswAm8FagMxBGAANgE6UzdfYwZgBjFbbAU6VGwEZVIyAWQCZQRkVjZXMFdmVTcENV8uVy8EalYgVixVIVUmVWdbdAI/BTcDaARhADEBblNvXzQGPQY5W3sFLVQ8BGlSYAE6AmsEY1Y2VztXY1U/BDFfM1dnBDRWZFYkVXpVc1VkW2oCIQVuA2QEZgA5AW5TZl8wBjYGMFtkBWFUcwRxUnUBKwJrBGNWNlc7V2NVPwQxXzJXZQQzVmdWLFUhVTxVcls7AmcFYgNnBH4AMwFoU2RfLgY1BjVbawV3VGIEPA==
### 20230819新规则
POST /ajaxm.php
# var wsk_sign = 'c20230818';
# var aihidcms = 'J5Nh';
# var iucjdsd = '';
# var ws_sign = 'c20230818';
# var sasign = 'AGZQbgk4UGEDCgo1ATEGOlY_aBTlSNVRiV2gEMlA1WmkBJ1R3AWFUMVY2VDsDZQE0Vj4CMgNsADdXZw_c_c';
# var ajaxdata = '?ctdf';
data : { 'action':'downprocess','signs':ajaxdata,'sign':sasign,'websign':iucjdsd,'websignkey':aihidcms,'ves':1 }

View File

@@ -10,20 +10,44 @@ content-type: application/json
{"pwd_id":"33197dd53ace4","passcode":"","share_for_transfer":true}
### UCpan 第二步 获取fid,share_fid_token GET传参pwd_id,passcode,stoken
https://pc-api.uc.cn/1/clouddrive/transfer_share/detail?pwd_id=33197dd53ace4&passcode=&stoken=oPz47hsgQXQdDYimsP4kBMi8aLv40X378IZOiBsnfLU%3D
https://pc-api.uc.cn/1/clouddrive/transfer_share/detail?pwd_id=33197dd53ace4&passcode=&stoken=4lXGIeQ6rdqjuOPW1MHJpqrQR3RWGFKSAAjpTibsR%2B8%3D
content-type: application/json
### UCpan 第二步获取下载链接 POST json传入fids(fid),pwd_id,stoken,fids_token(share_fid_token)
POST https://pc-api.uc.cn/1/clouddrive/file/download?entry=ft&fr=pc&pr=UCBrowser
content-type: application/json
#Cookie: __pus=7576a6d3a511ad7b4c5649a1d89c29ffAAQ06zBxHWghrwEbKRdqBrhXssuYiMIwLLVzi1f2K6qnSL95A79GIxXDEPlYS3NaPjDWOcWVuvbQ3HqTfvqRKr29
{
"fids": [
"54c3cd90ed3e45119bb96ed99a562d40"
],
"pwd_id": "33197dd53ace4",
"stoken": "oPz47hsgQXQdDYimsP4kBMi8aLv40X378IZOiBsnfLU=",
"stoken": "7P02U9tWJEkkMgbXaIJxAQyBiqcfPdkbDZ6XoiYsBiA=",
"fids_token": [
"ff9f5b5c94df9d08c8dd3b7948fc5e20"
"e8a52adcda41d9e218e732b5de549d2a"
]
}
###
# @no-redirect
https://dl-uf-zb.pds.uc.cn/l3PNAKfz/64623447/646b0de6e9f13000c9b14ba182b805312795a82a/646b0de6717e1bfa5bb44dd2a456f103c5177850?Expires=1691488145&OSSAccessKeyId=LTAIyYfxTqY7YZsg&Signature=MvUiIWszmMncqDrsLV%2BlL1HpuYw%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=eyJ4OmF1IjoiLSIsIng6c3AiOiIxOTkiLCJ4OnRva2VuIjoiMi1iYzBhNTgxNTUwOGE0NmQwYmE4YzUxYjAyZGEwNjA5Yi01LTctNjE0NDAtZGFjYjM2NjViYmFhNGY1ZTlkMzc4MDBlYzY0MDMxNjAtNGZhNTliY2RkNzhlYTE0MDg0Mjc5OGVlNDMxZWFlMDciLCJ4OnR0bCI6IjEwODAwIn0%3D&callback=eyJjYWxsYmFja0JvZHlUeXBlIjoiYXBwbGljYXRpb24vanNvbiIsImNhbGxiYWNrU3RhZ2UiOiJiZWZvcmUtZXhlY3V0ZSIsImNhbGxiYWNrRmFpbHVyZUFjdGlvbiI6Imlnbm9yZSIsImNhbGxiYWNrVXJsIjoiaHR0cHM6Ly9hdXRoLWNkbi51Yy5jbi9vdXRlci9vc3MvY2hlY2twbGF5IiwiY2FsbGJhY2tCb2R5Ijoie1wiaG9zdFwiOiR7aHR0cEhlYWRlci5ob3N0fSxcInNpemVcIjoke3NpemV9LFwicmFuZ2VcIjoke2h0dHBIZWFkZXIucmFuZ2V9LFwicmVmZXJlclwiOiR7aHR0cEhlYWRlci5yZWZlcmVyfSxcImNvb2tpZVwiOiR7aHR0cEhlYWRlci5jb29raWV9LFwibWV0aG9kXCI6JHtodHRwSGVhZGVyLm1ldGhvZH0sXCJpcFwiOiR7Y2xpZW50SXB9LFwicG9ydFwiOiR7Y2xpZW50UG9ydH0sXCJvYmplY3RcIjoke29iamVjdH0sXCJzcFwiOiR7eDpzcH0sXCJ0b2tlblwiOiR7eDp0b2tlbn0sXCJhdVwiOiR7eDphdX0sXCJ0dGxcIjoke3g6dHRsfSxcImNsaWVudF90b2tlblwiOiR7cXVlcnlTdHJpbmcuY2xpZW50X3Rva2VufX0ifQ%3D%3D&ud=4-N-5-0-6-N-3-ft-0-2
#Cookie: __pus=7576a6d3a511ad7b4c5649a1d89c29ffAAQ06zBxHWghrwEbKRdqBrhXssuYiMIwLLVzi1f2K6qnSL95A79GIxXDEPlYS3NaPjDWOcWVuvbQ3HqTfvqRKr29
###
https://dl-uf-zb.pds.uc.cn/l3PNAKfz/64623447/646b0de6e9f13000c9b14ba182b805312795a82a/646b0de6717e1bfa5bb44dd2a456f103c5177850?Expires=1691488387&OSSAccessKeyId=LTAIyYfxTqY7YZsg&Signature=WIy4UGCwd9eNdUnSexRVFUCFZcM%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
###
#@no-cookie-jar
https://dl-uf-zb.pds.uc.cn/l3PNAKfz/64623447/646b0de6e9f13000c9b14ba182b805312795a82a/646b0de6717e1bfa5bb44dd2a456f103c5177850?Expires=1691488539&OSSAccessKeyId=LTAIyYfxTqY7YZsg&Signature=rhD0OQTq2CuMUvp0ZphStKiyyw8%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=eyJ4OmF1IjoiLSIsIng6c3AiOiIxOTkiLCJ4OnRva2VuIjoiMi02YTU3OGNkNjYyMTQzZWI4ODFjZTE0ZGYyNjk5OTQ4OS01LTctNjE0NDAtZGFjYjM2NjViYmFhNGY1ZTlkMzc4MDBlYzY0MDMxNjAtYzlhYWI1YjA5ZDNhYTA5MWU4NTJjNTJlNGRjYWJkZDYiLCJ4OnR0bCI6IjEwODAwIn0%3D&callback=eyJjYWxsYmFja0JvZHlUeXBlIjoiYXBwbGljYXRpb24vanNvbiIsImNhbGxiYWNrU3RhZ2UiOiJiZWZvcmUtZXhlY3V0ZSIsImNhbGxiYWNrRmFpbHVyZUFjdGlvbiI6Imlnbm9yZSIsImNhbGxiYWNrVXJsIjoiaHR0cHM6Ly9hdXRoLWNkbi51Yy5jbi9vdXRlci9vc3MvY2hlY2twbGF5IiwiY2FsbGJhY2tCb2R5Ijoie1wiaG9zdFwiOiR7aHR0cEhlYWRlci5ob3N0fSxcInNpemVcIjoke3NpemV9LFwicmFuZ2VcIjoke2h0dHBIZWFkZXIucmFuZ2V9LFwicmVmZXJlclwiOiR7aHR0cEhlYWRlci5yZWZlcmVyfSxcImNvb2tpZVwiOiR7aHR0cEhlYWRlci5jb29raWV9LFwibWV0aG9kXCI6JHtodHRwSGVhZGVyLm1ldGhvZH0sXCJpcFwiOiR7Y2xpZW50SXB9LFwicG9ydFwiOiR7Y2xpZW50UG9ydH0sXCJvYmplY3RcIjoke29iamVjdH0sXCJzcFwiOiR7eDpzcH0sXCJ0b2tlblwiOiR7eDp0b2tlbn0sXCJhdVwiOiR7eDphdX0sXCJ0dGxcIjoke3g6dHRsfSxcImNsaWVudF90b2tlblwiOiR7cXVlcnlTdHJpbmcuY2xpZW50X3Rva2VufX0ifQ%3D%3D&ud=4-N-5-0-6-N-3-ft-0-2
Cookie: __pugs=efc5f3f9c041af5dc62eea4481901cbbAAT912628i+uT/WMwOFWBjJ1TjbKGC1j6cyGHSLVzwuPQP74d+rZXO4xJgdG93MC5DUDdgRJcg5y93waA/KPWDSeDY8LPgjB2Ha2tQ3sQ/MXMoRUi/LRdY3psAyC3YOlUDeFwLtkLAXABRgJhKTw6W0v
###
#@no-cookie-jar
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/

View File

@@ -37,9 +37,9 @@ Accept: application/json, text/plain, */*
### 获取下载文件URL
POST https://www.wenshushu.cn/ap/dl/sign
Accept: application/json, text/plain, */*
X-Token:wss:bekzdwsfwwf
X-Token:wss:br57uq0qg03
{"ufileid":"bekoe5n508z","consumeCode":0,"type":1}
{"ufileid":"br4t1rst6xb","consumeCode":0,"type":1}
###
# {
@@ -49,5 +49,14 @@ X-Token:wss:bekzdwsfwwf
# "url": "https://down.wss.show/zkrk4th/9/yj/9yjvzkrk4th?cdn_sign=1686211254-31-0-6edd2e228a030be23210158daeb2c4a1&exp=4800&response-content-disposition=attachment%3B%20filename%3D%22supervision.sql%22%3B%20filename%2A%3Dutf-8%27%27supervision.sql",
### 直接下载
https://down.wss.show/zkrk4th/9/yj/9yjvzkrk4th?cdn_sign=1686211254-31-0-6edd2e228a030be23210158daeb2c4a1&exp=4800&response-content-disposition=attachment%3B%20filename%3D%22supervision.sql%22%3B%20filename%2A%3Dutf-8%27%27supervision.sql
https://down.wss.show/zkrk4th/9/yj/9yjvzkrk4th?cdn_sign=1690012643-63-0-f7761ba9a500a0b300a3b5573693f9ce&exp=4800&response-content-disposition=attachment%3B%20filename%3D%22supervision.sql%22%3B%20filename%2A%3Dutf-8%27%27supervision.sql
### https://115.com/s/sw6tqra3zti?password=vc30#boost.files
https://webapi.115.com/share/downurl?user_id=101200613&share_code=sw6tqra3zti&file_id=2643992364552551890&receive_code=vc30
Cookie: UID=101200613_A1_1690425668; CID=29ac00cab116eeda63fce2296179b5ad; SEID=8f0385cd8a10f4bb8db7b064ff55ae8f53f8c7e7c6f61a3f6b1eee234d31f08bf00ac9dc68c6ff38005788e0b8444751a6b35a3f9654b07d3e4d7a67
###
https://cdnfhnfile.115.com/64722de2d26c69c339bd4c1322a0a7714717d15f/boost.files?t=1690427063&u=101200613&s=1048576&d=1894289998--0&c=0&f=3&k=53d82265b751466ee0581d1638327605&us=52428800&uc=10&v=1
###
https://webapi.115.com/share/snap?share_code=sw6tqra3zti&offset=0&limit=20&receive_code=vc30

View File

@@ -104,4 +104,18 @@ Platform:web
https://www.123pan.com/a/api/share/get?limit=100&next=1&orderBy=file_name&orderDirection=asc&shareKey=iaKtVv-6OECd&SharePwd=DcGe&ParentFileId=0&Page=1&event=homeListFile&operateType=1
###
POST https://www.123pan.com/b/api/share/download/info?1091802552=1690430466-4809035-157589386
App-Version:3
Platform:web
{"ShareKey":"iaKtVv-6OECd","FileID":2193732,"S3keyFlag":"1811834632-0","Size":4203111,"Etag":"69c94adbc0b9190cf23c4e958d8c7c53"}
###
POST https://www.123pan.com/b/api/share/statistics?1091802552=1690430466-4809035-157589386
App-Version:3
Platform:web
{"ShareKey":"iaKtVv-6OECd","behavior":1}
### eaefamemdead

View File

@@ -28,6 +28,10 @@ GET http://127.0.0.1:6400/cow/e4f41b51b5da4f
# @no-redirect
GET http://127.0.0.1:6400/parser?url=https://cowtransfer.com/s/9a644fe3e3a748
### 奶牛
# @no-redirect
GET http://127.0.0.1:6400/parser?url=https://goldrepo.cowtransfer.com/s/026a638795634b
### 移动云空间 https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
# @no-redirect
GET http://127.0.0.1:6400/parser?url=https://www.ecpan.cn/web//yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data=81027a5c99af5b11ca004966c945cce6W9Bf2&isShare=1
@@ -65,10 +69,10 @@ GET http://127.0.0.1:6400/fj/tIfhRqH
### 360亿方云
# @no-redirect
GET http://127.0.0.1:6400/parser?url=https://v2.fangcloud.com/sharing/e5079007dc31226096628870c7&pwd=QAIU
GET http://127.0.0.1:6400/parser?url=https://v2.fangcloud.com/sharing/fb54bdf03c66c04334fe3687a3
### 360亿方云
GET http://127.0.0.1:6400/json/fc/30646fefc8bf936a4766ab8a5e
GET http://127.0.0.1:6400/json/fc/fb54bdf03c66c04334fe3687a3
### 360亿方云
# @no-redirect
@@ -80,6 +84,10 @@ GET http://127.0.0.1:6400/fc/e5079007dc31226096628870c7@QAIU
# https://www.123pan.com/s/iaKtVv-ICECd.html
### 123
GET http://127.0.0.1:6400/json/ye/iaKtVv-ICECd
### 123
GET http://127.0.0.1:6400/json/ye/iaKtVv-6OECd@DcGe
### 123
GET https://lz.qaiu.top/json/ye/iaKtVv-6OECd@DcGe
### 123
# @no-redirect
@@ -90,4 +98,8 @@ GET http://127.0.0.1:6400/ye/iaKtVv-qOECd
GET http://127.0.0.1:6400/parser?url=https://www.123pan.com/s/iaKtVv-6OECd.html&pwd=DcGe
###
POST http://127.0.0.1:6400/login1
GET http://127.0.0.1:6400/v2/statisticsInfo
###
POST http://127.0.0.1:6400/v2/login?username=asd

View File

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

View File

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

View File

@@ -0,0 +1,15 @@
package cn.qaiu.vx.core.util;
import org.junit.Test;
import static org.junit.Assert.*;
public class ParamUtilTest {
@Test
public void paramsToMap() {
System.out.println(ParamUtil.paramsToMap(""));
System.out.println(ParamUtil.paramsToMap("a=asd&d=23"));
System.out.println(ParamUtil.paramsToMap("asdasd&dd"));
}
}

View File

@@ -0,0 +1,30 @@
package cn.qaiu.vx.core.util;
import cn.qaiu.lz.common.util.SnowflakeIdWorker;
import org.junit.Test;
public class SnowflakeIdWorkerTest {
@Test
public void idWorker() {
final SnowflakeIdWorker idWorker = SnowflakeIdWorker.idWorker();
for (int i = 0; i < 100; i++) {
long id = idWorker.nextId();
System.out.println(Long.toBinaryString(id));
System.out.println(id);
System.out.println("------------");
}
}
@Test
public void idWorkerCluster() {
final SnowflakeIdWorker snowflakeIdWorkerCluster = SnowflakeIdWorker.idWorkerCluster(0, 1);
for (int i = 0; i < 100; i++) {
long id = snowflakeIdWorkerCluster.nextId();
System.out.println(Long.toBinaryString(id));
System.out.println(id);
System.out.println("------------\n");
}
}
}

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