mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-06-10 15:37:28 +00:00
feat: 新功能与配置优化
- QQscTool: 支持多文件和目录解析,通过 GetFileList API 实现递归目录导航 - Home: 从粘贴文本中自动提取分享链接 - DirectoryTree: 目录浏览添加复制直链按钮 - domainName 改为可选,未配置时自动从请求地址推断 - 统一版本号管理,GitHub URL 构建时自动从 git remote origin 识别 - vue.config.js 添加前端构建配置,sync-version.js 构建时同步版本号
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
# 一款网盘分享链接云解析快速下载服务
|
# 一款网盘分享链接云解析快速下载服务
|
||||||
QQ交流群:1017480890
|
QQ交流群:1017480890
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml"><img src="https://img.shields.io/github/actions/workflow/status/qaiu/netdisk-fast-download/maven.yml?branch=v0.1.9b8a&style=flat"></a>
|
<a href="https://github.com/qaiu/netdisk-fast-download/actions/workflows/maven.yml"><img src="https://img.shields.io/github/actions/workflow/status/qaiu/netdisk-fast-download/maven.yml?branch=main&style=flat"></a>
|
||||||
<a href="https://www.oracle.com/cn/java/technologies/downloads"><img src="https://img.shields.io/badge/jdk-%3E%3D17-blue"></a>
|
<a href="https://www.oracle.com/cn/java/technologies/downloads"><img src="https://img.shields.io/badge/jdk-%3E%3D17-blue"></a>
|
||||||
<a href="https://vertx-china.github.io"><img src="https://img.shields.io/badge/vert.x-4.5.24-blue?style=flat"></a>
|
<a href="https://vertx-china.github.io"><img src="https://img.shields.io/badge/vert.x-4.5.27-blue?style=flat"></a>
|
||||||
<a href="https://raw.githubusercontent.com/qaiu/netdisk-fast-download/master/LICENSE"><img src="https://img.shields.io/github/license/qaiu/netdisk-fast-download?style=flat"></a>
|
<a href="https://raw.githubusercontent.com/qaiu/netdisk-fast-download/master/LICENSE"><img src="https://img.shields.io/github/license/qaiu/netdisk-fast-download?style=flat"></a>
|
||||||
<a href="https://github.com/qaiu/netdisk-fast-download/releases/"><img src="https://img.shields.io/github/v/release/qaiu/netdisk-fast-download?style=flat"></a>
|
<a href="https://github.com/qaiu/netdisk-fast-download/releases/"><img src="https://img.shields.io/github/v/release/qaiu/netdisk-fast-download?style=flat"></a>
|
||||||
<a href="https://atomgit.com/QAIU/netdisk-fast-download"><img src="https://atomgit.com/QAIU/netdisk-fast-download/star/badge.svg" alt="AtomGit"></a>
|
<a href="https://atomgit.com/QAIU/netdisk-fast-download"><img src="https://atomgit.com/QAIU/netdisk-fast-download/star/badge.svg" alt="AtomGit"></a>
|
||||||
@@ -419,7 +419,7 @@ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtow
|
|||||||
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
|
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
|
||||||
```shell
|
```shell
|
||||||
cd ~
|
cd ~
|
||||||
wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/v0.1.9b7/netdisk-fast-download-bin.zip
|
wget -O netdisk-fast-download.zip https://github.com/qaiu/netdisk-fast-download/releases/download/v3.0.2/netdisk-fast-download-bin.zip
|
||||||
unzip netdisk-fast-download-bin.zip
|
unzip netdisk-fast-download-bin.zip
|
||||||
cd netdisk-fast-download
|
cd netdisk-fast-download
|
||||||
bash service-install.sh
|
bash service-install.sh
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class CreateDatabase {
|
|||||||
stmt.executeUpdate("CREATE DATABASE IF NOT EXISTS " + dbName + " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
stmt.executeUpdate("CREATE DATABASE IF NOT EXISTS " + dbName + " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||||
LOGGER.info(">>>>>>>>>>> 数据库'{}'创建成功 <<<<<<<<<<<<", dbName);
|
LOGGER.info(">>>>>>>>>>> 数据库'{}'创建成功 <<<<<<<<<<<<", dbName);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
e.printStackTrace();
|
LOGGER.error("创建数据库失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,35 +24,39 @@ import java.util.*;
|
|||||||
* @author <a href="https://qaiu.top">QAIU</a>
|
* @author <a href="https://qaiu.top">QAIU</a>
|
||||||
*/
|
*/
|
||||||
public class CreateTable {
|
public class CreateTable {
|
||||||
public static Map<Class<?>, String> javaProperty2SqlColumnMap = new HashMap<>() {{
|
public static final Map<Class<?>, String> javaProperty2SqlColumnMap;
|
||||||
|
static {
|
||||||
|
Map<Class<?>, String> map = new HashMap<>();
|
||||||
// Java类型到SQL类型的映射
|
// Java类型到SQL类型的映射
|
||||||
put(Integer.class, "INT");
|
map.put(Integer.class, "INT");
|
||||||
put(Short.class, "SMALLINT");
|
map.put(Short.class, "SMALLINT");
|
||||||
put(Byte.class, "TINYINT");
|
map.put(Byte.class, "TINYINT");
|
||||||
put(Long.class, "BIGINT");
|
map.put(Long.class, "BIGINT");
|
||||||
put(java.math.BigDecimal.class, "DECIMAL");
|
map.put(java.math.BigDecimal.class, "DECIMAL");
|
||||||
put(Double.class, "DOUBLE");
|
map.put(Double.class, "DOUBLE");
|
||||||
put(Float.class, "REAL");
|
map.put(Float.class, "REAL");
|
||||||
put(Boolean.class, "BOOLEAN");
|
map.put(Boolean.class, "BOOLEAN");
|
||||||
put(String.class, "VARCHAR");
|
map.put(String.class, "VARCHAR");
|
||||||
put(Date.class, "TIMESTAMP");
|
map.put(Date.class, "TIMESTAMP");
|
||||||
put(java.time.LocalDateTime.class, "TIMESTAMP");
|
map.put(java.time.LocalDateTime.class, "TIMESTAMP");
|
||||||
put(java.sql.Timestamp.class, "TIMESTAMP");
|
map.put(java.sql.Timestamp.class, "TIMESTAMP");
|
||||||
put(java.sql.Date.class, "DATE");
|
map.put(java.sql.Date.class, "DATE");
|
||||||
put(java.sql.Time.class, "TIME");
|
map.put(java.sql.Time.class, "TIME");
|
||||||
|
|
||||||
// 基本数据类型
|
// 基本数据类型
|
||||||
put(int.class, "INT");
|
map.put(int.class, "INT");
|
||||||
put(short.class, "SMALLINT");
|
map.put(short.class, "SMALLINT");
|
||||||
put(byte.class, "TINYINT");
|
map.put(byte.class, "TINYINT");
|
||||||
put(long.class, "BIGINT");
|
map.put(long.class, "BIGINT");
|
||||||
put(double.class, "DOUBLE");
|
map.put(double.class, "DOUBLE");
|
||||||
put(float.class, "REAL");
|
map.put(float.class, "REAL");
|
||||||
put(boolean.class, "BOOLEAN");
|
map.put(boolean.class, "BOOLEAN");
|
||||||
}};
|
|
||||||
|
javaProperty2SqlColumnMap = Collections.unmodifiableMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(CreateTable.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(CreateTable.class);
|
||||||
public static String UNIQUE_PREFIX = "idx_";
|
public static final String UNIQUE_PREFIX = "idx_";
|
||||||
|
|
||||||
private static Case getCase(Class<?> clz) {
|
private static Case getCase(Class<?> clz) {
|
||||||
return switch (clz.getName()) {
|
return switch (clz.getName()) {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -62,7 +64,12 @@ public final class Deploy {
|
|||||||
path.append("-").append(args[0].replace("app-",""));
|
path.append("-").append(args[0].replace("app-",""));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取yml配置
|
// 读取yml配置,优先当前目录,其次 resources/ 子目录
|
||||||
|
String configFile = path + ".yml";
|
||||||
|
if (!Files.exists(Path.of(configFile)) && Files.exists(Path.of("resources", configFile))) {
|
||||||
|
path.insert(0, "resources/");
|
||||||
|
LOGGER.info("从 resources/ 目录加载配置: {}", path + ".yml");
|
||||||
|
}
|
||||||
ConfigUtil.readYamlConfig(path.toString(), tempVertx)
|
ConfigUtil.readYamlConfig(path.toString(), tempVertx)
|
||||||
.onSuccess(this::readConf)
|
.onSuccess(this::readConf)
|
||||||
.onFailure(err -> {
|
.onFailure(err -> {
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ public class CommonUtil {
|
|||||||
appVersion = properties.getProperty("app.version") + "build" + properties.getProperty("build");
|
appVersion = properties.getProperty("app.version") + "build" + properties.getProperty("build");
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
LOGGER.error("读取app.properties失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return appVersion;
|
return appVersion;
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import io.vertx.core.json.JsonObject;
|
|||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 异步读取配置工具类
|
* 异步读取配置工具类
|
||||||
@@ -62,12 +64,35 @@ public class ConfigUtil {
|
|||||||
// 异步获取配置
|
// 异步获取配置
|
||||||
// 成功直接完成 promise
|
// 成功直接完成 promise
|
||||||
retriever.getConfig()
|
retriever.getConfig()
|
||||||
.onSuccess(promise::complete)
|
.onSuccess(config -> {
|
||||||
.onFailure(err -> {
|
promise.complete(config);
|
||||||
// 配置读取失败,直接返回失败 Future
|
|
||||||
promise.fail(new RuntimeException(
|
|
||||||
"读取配置文件失败: " + path, err));
|
|
||||||
retriever.close();
|
retriever.close();
|
||||||
|
})
|
||||||
|
.onFailure(err -> {
|
||||||
|
retriever.close();
|
||||||
|
// 读取失败时,尝试从 resources/ 子目录读取(兼容 Docker 卷挂载场景)
|
||||||
|
String resourcesPath = "resources/" + path;
|
||||||
|
if (!path.startsWith("resources/") && Files.exists(Path.of(resourcesPath))) {
|
||||||
|
ConfigStoreOptions fallbackStore = new ConfigStoreOptions()
|
||||||
|
.setType("file")
|
||||||
|
.setFormat(format)
|
||||||
|
.setConfig(new JsonObject().put("path", resourcesPath));
|
||||||
|
ConfigRetriever fallbackRetriever = ConfigRetriever
|
||||||
|
.create(vertx, new ConfigRetrieverOptions().addStore(fallbackStore));
|
||||||
|
fallbackRetriever.getConfig()
|
||||||
|
.onSuccess(config -> {
|
||||||
|
promise.complete(config);
|
||||||
|
fallbackRetriever.close();
|
||||||
|
})
|
||||||
|
.onFailure(e2 -> {
|
||||||
|
promise.fail(new RuntimeException(
|
||||||
|
"读取配置文件失败: " + path + " (也尝试了 " + resourcesPath + ")", e2));
|
||||||
|
fallbackRetriever.close();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
promise.fail(new RuntimeException(
|
||||||
|
"读取配置文件失败: " + path, err));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return promise.future();
|
return promise.future();
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ import java.net.URL;
|
|||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import static cn.qaiu.vx.core.util.ConfigConstant.BASE_LOCATIONS;
|
import static cn.qaiu.vx.core.util.ConfigConstant.BASE_LOCATIONS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,6 +39,8 @@ import static cn.qaiu.vx.core.util.ConfigConstant.BASE_LOCATIONS;
|
|||||||
*/
|
*/
|
||||||
public final class ReflectionUtil {
|
public final class ReflectionUtil {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class);
|
||||||
|
|
||||||
// 缓存Reflections实例,避免重复扫描(每次扫描约35K+值,耗时1-3秒,占用大量内存)
|
// 缓存Reflections实例,避免重复扫描(每次扫描约35K+值,耗时1-3秒,占用大量内存)
|
||||||
private static final Map<String, Reflections> REFLECTIONS_CACHE = new java.util.concurrent.ConcurrentHashMap<>();
|
private static final Map<String, Reflections> REFLECTIONS_CACHE = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
|
||||||
@@ -128,7 +133,7 @@ public final class ReflectionUtil {
|
|||||||
parameterTypes[j - k]));
|
parameterTypes[j - k]));
|
||||||
}
|
}
|
||||||
} catch (NotFoundException e) {
|
} catch (NotFoundException e) {
|
||||||
e.printStackTrace();
|
LOGGER.error("获取方法参数失败", e);
|
||||||
}
|
}
|
||||||
return paramMap;
|
return paramMap;
|
||||||
}
|
}
|
||||||
@@ -183,7 +188,7 @@ public final class ReflectionUtil {
|
|||||||
try {
|
try {
|
||||||
return DateUtils.parseDate(value, fmt);
|
return DateUtils.parseDate(value, fmt);
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
e.printStackTrace();
|
LOGGER.error("日期解析失败: {}", value, e);
|
||||||
throw new RuntimeException("无法将格式化日期");
|
throw new RuntimeException("无法将格式化日期");
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -215,7 +220,7 @@ public final class ReflectionUtil {
|
|||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
LOGGER.error("数组类型转换失败: {}", value, e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ public class HttpProxyVerticle extends AbstractVerticle {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.onFailure(err -> {
|
.onFailure(err -> {
|
||||||
err.printStackTrace();
|
LOGGER.error("HTTP请求失败", err);
|
||||||
clientRequest.response().setStatusCode(502).end("Bad Gateway: Request failed");
|
clientRequest.response().setStatusCode(502).end("Bad Gateway: Request failed");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -222,7 +222,7 @@ public class HttpProxyVerticle extends AbstractVerticle {
|
|||||||
}
|
}
|
||||||
return port;
|
return port;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
LOGGER.error("提取端口失败: {}", urlString, e);
|
||||||
// 出现异常时返回 -1,表示提取失败
|
// 出现异常时返回 -1,表示提取失败
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,26 +4,26 @@ NFD 解析器模块:聚合各类网盘/分享页解析,统一输出文件列
|
|||||||
|
|
||||||
- 语言:Java 17
|
- 语言:Java 17
|
||||||
- 构建:Maven
|
- 构建:Maven
|
||||||
- 模块版本:10.1.17
|
- 模块版本:10.2.5
|
||||||
|
|
||||||
## 依赖(Maven Central)
|
## 依赖(Maven Central)
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.qaiu</groupId>
|
<groupId>cn.qaiu</groupId>
|
||||||
<artifactId>parser</artifactId>
|
<artifactId>parser</artifactId>
|
||||||
<version>10.1.17</version>
|
<version>10.2.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
- Gradle Groovy DSL:
|
- Gradle Groovy DSL:
|
||||||
```groovy
|
```groovy
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'cn.qaiu:parser:10.1.17'
|
implementation 'cn.qaiu:parser:10.2.5'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- Gradle Kotlin DSL:
|
- Gradle Kotlin DSL:
|
||||||
```kotlin
|
```kotlin
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("cn.qaiu:parser:10.1.17")
|
implementation("cn.qaiu:parser:10.2.5")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.qaiu</groupId>
|
<groupId>cn.qaiu</groupId>
|
||||||
<artifactId>parser</artifactId>
|
<artifactId>parser</artifactId>
|
||||||
<version>10.1.17</version>
|
<version>10.2.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.qaiu</groupId>
|
<groupId>cn.qaiu</groupId>
|
||||||
<artifactId>parser</artifactId>
|
<artifactId>parser</artifactId>
|
||||||
<version>10.1.17</version>
|
<version>10.2.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package cn.qaiu.parser.impl;
|
|||||||
import cn.qaiu.entity.FileInfo;
|
import cn.qaiu.entity.FileInfo;
|
||||||
import cn.qaiu.entity.ShareLinkInfo;
|
import cn.qaiu.entity.ShareLinkInfo;
|
||||||
import cn.qaiu.parser.PanBase;
|
import cn.qaiu.parser.PanBase;
|
||||||
|
import cn.qaiu.util.CommonUtils;
|
||||||
import cn.qaiu.util.HeaderUtils;
|
import cn.qaiu.util.HeaderUtils;
|
||||||
import io.vertx.core.Future;
|
import io.vertx.core.Future;
|
||||||
import io.vertx.core.MultiMap;
|
import io.vertx.core.MultiMap;
|
||||||
|
import io.vertx.core.Promise;
|
||||||
import io.vertx.core.json.JsonArray;
|
import io.vertx.core.json.JsonArray;
|
||||||
import io.vertx.core.json.JsonObject;
|
import io.vertx.core.json.JsonObject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -13,27 +15,33 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* QQ闪传 <br>
|
* QQ闪传 <br>
|
||||||
* 只能客户端上传 支持Android QQ 9.2.5, MACOS QQ 6.9.78,可生成分享链接,通过浏览器下载,支持超大文件,有效期默认7天(暂时没找到续期方法)。<br>
|
* 支持多文件、多级目录解析。通过 GetFileList API 获取文件列表,BatchDownload API 获取下载直链。<br>
|
||||||
|
* 有效期默认7天。
|
||||||
*/
|
*/
|
||||||
public class QQscTool extends PanBase {
|
public class QQscTool extends PanBase {
|
||||||
|
|
||||||
Logger LOG = LoggerFactory.getLogger(QQscTool.class);
|
Logger LOG = LoggerFactory.getLogger(QQscTool.class);
|
||||||
|
|
||||||
private static final String API_URL = "https://qfile.qq.com/http2rpc/gotrpc/noauth/trpc.qqntv2.richmedia.InnerProxy/BatchDownload";
|
private static final String BATCH_DOWNLOAD_API =
|
||||||
|
"https://qfile.qq.com/http2rpc/gotrpc/noauth/trpc.qqntv2.richmedia.InnerProxy/BatchDownload";
|
||||||
|
|
||||||
private static final MultiMap HEADERS = HeaderUtils.parseHeaders("""
|
private static final String GET_FILE_LIST_API =
|
||||||
|
"https://qfile.qq.com/http2rpc/gotrpc/noauth/trpc.file.FileFlashTrans/GetFileList";
|
||||||
|
|
||||||
|
private static final MultiMap BATCH_DOWNLOAD_HEADERS = HeaderUtils.parseHeaders("""
|
||||||
Accept-Encoding: gzip, deflate
|
Accept-Encoding: gzip, deflate
|
||||||
Accept-Language: zh-CN,zh;q=0.9
|
Accept-Language: zh-CN,zh;q=0.9
|
||||||
Connection: keep-alive
|
Connection: keep-alive
|
||||||
Cookie: uin=9000002; p_uin=9000002
|
Cookie: uin=9000002; p_uin=9000002
|
||||||
DNT: 1
|
DNT: 1
|
||||||
Origin: https://qfile.qq.com
|
Origin: https://qfile.qq.com
|
||||||
Referer: https://qfile.qq.com/q/Xolxtv5b4O
|
|
||||||
Sec-Fetch-Dest: empty
|
Sec-Fetch-Dest: empty
|
||||||
Sec-Fetch-Mode: cors
|
Sec-Fetch-Mode: cors
|
||||||
Sec-Fetch-Site: same-origin
|
Sec-Fetch-Site: same-origin
|
||||||
@@ -46,86 +54,257 @@ public class QQscTool extends PanBase {
|
|||||||
x-oidb: {"uint32_command":"0x9248", "uint32_service_type":"4"}
|
x-oidb: {"uint32_command":"0x9248", "uint32_service_type":"4"}
|
||||||
""");
|
""");
|
||||||
|
|
||||||
|
private static final MultiMap GET_FILE_LIST_HEADERS = HeaderUtils.parseHeaders("""
|
||||||
|
Accept-Encoding: gzip, deflate
|
||||||
|
Cookie: uin=9000002; p_uin=9000002
|
||||||
|
Origin: https://qfile.qq.com
|
||||||
|
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0
|
||||||
|
content-type: application/json
|
||||||
|
x-oidb: {"uint32_command":"0x93d4", "uint32_service_type":"1"}
|
||||||
|
""");
|
||||||
|
|
||||||
public QQscTool(ShareLinkInfo shareLinkInfo) {
|
public QQscTool(ShareLinkInfo shareLinkInfo) {
|
||||||
super(shareLinkInfo);
|
super(shareLinkInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Future<String> parse() {
|
public Future<String> parse() {
|
||||||
String jsonTemplate = """
|
|
||||||
{"req_head":{"agent":8},"download_info":[{"batch_id":"%s","scene":{"business_type":4,"app_type":22,"scene_type":5},"index_node":{"file_uuid":"%s"},"url_type":2,"download_scene":0}],"scene_type":103}
|
|
||||||
""";
|
|
||||||
|
|
||||||
client.getAbs(shareLinkInfo.getShareUrl()).send(result -> {
|
client.getAbs(shareLinkInfo.getShareUrl()).send(result -> {
|
||||||
if (result.succeeded()) {
|
if (result.failed()) {
|
||||||
String htmlJs = result.result().bodyAsString();
|
|
||||||
LOG.debug("获取到的HTML内容: {}", htmlJs);
|
|
||||||
String fileUUID = getFileUUID(htmlJs);
|
|
||||||
String fileName = extractFileNameFromTitle(htmlJs);
|
|
||||||
if (fileName != null) {
|
|
||||||
LOG.info("提取到的文件名: {}", fileName);
|
|
||||||
FileInfo fileInfo = new FileInfo();
|
|
||||||
fileInfo.setFileName(fileName);
|
|
||||||
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
|
|
||||||
} else {
|
|
||||||
LOG.warn("未能提取到文件名");
|
|
||||||
}
|
|
||||||
if (fileUUID != null) {
|
|
||||||
LOG.info("提取到的文件UUID: {}", fileUUID);
|
|
||||||
String formatted = jsonTemplate.formatted(fileUUID, fileUUID);
|
|
||||||
JsonObject entries = new JsonObject(formatted);
|
|
||||||
client.postAbs(API_URL)
|
|
||||||
.putHeaders(HEADERS)
|
|
||||||
.sendJsonObject(entries)
|
|
||||||
.onSuccess(result2 -> {
|
|
||||||
if (result2.statusCode() == 200) {
|
|
||||||
JsonObject body = asJson(result2);
|
|
||||||
LOG.debug("API响应内容: {}", body.encodePrettily());
|
|
||||||
// {
|
|
||||||
// "retcode": 0,
|
|
||||||
// "cost": 132,
|
|
||||||
// "message": "",
|
|
||||||
// "error": {
|
|
||||||
// "message": "",
|
|
||||||
// "code": 0
|
|
||||||
// },
|
|
||||||
// "data": {
|
|
||||||
// "download_rsp": [{
|
|
||||||
|
|
||||||
// 取 download_rsp
|
|
||||||
if (!body.containsKey("retcode") || body.getInteger("retcode") != 0) {
|
|
||||||
promise.fail("API请求失败,错误信息: " + body.encodePrettily());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JsonArray downloadRsp = body.getJsonObject("data").getJsonArray("download_rsp");
|
|
||||||
if (downloadRsp != null && !downloadRsp.isEmpty()) {
|
|
||||||
String url = downloadRsp.getJsonObject(0).getString("url");
|
|
||||||
if (fileName != null) {
|
|
||||||
url = url + "&filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
promise.complete(url);
|
|
||||||
} else {
|
|
||||||
promise.fail("API响应中缺少 download_rsp");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
promise.fail("API请求失败,状态码: " + result2.statusCode());
|
|
||||||
}
|
|
||||||
}).onFailure(e -> {
|
|
||||||
LOG.error("API请求异常", e);
|
|
||||||
promise.fail(e);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
LOG.error("未能提取到文件UUID");
|
|
||||||
promise.fail("未能提取到文件UUID");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOG.error("请求失败: {}", result.cause().getMessage());
|
LOG.error("请求失败: {}", result.cause().getMessage());
|
||||||
promise.fail(result.cause());
|
promise.fail(result.cause());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String html = result.result().bodyAsString();
|
||||||
|
String fileName = extractFileNameFromTitle(html);
|
||||||
|
if (fileName != null) {
|
||||||
|
FileInfo fileInfo = new FileInfo();
|
||||||
|
fileInfo.setFileName(fileName);
|
||||||
|
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
|
||||||
|
}
|
||||||
|
// 尝试用 GetFileList API 获取第一个文件的下载链接
|
||||||
|
String filesetId = extractFilesetId(html);
|
||||||
|
if (filesetId != null) {
|
||||||
|
fetchFileList(filesetId, "").onSuccess(fileList -> {
|
||||||
|
for (int i = 0; i < fileList.size(); i++) {
|
||||||
|
JsonObject file = fileList.getJsonObject(i);
|
||||||
|
if (!file.getBoolean("is_dir", false)) {
|
||||||
|
String physicalId = file.getJsonObject("physical").getString("id");
|
||||||
|
String name = file.getString("name");
|
||||||
|
downloadFile(physicalId, name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
promise.fail("未找到可下载的文件");
|
||||||
|
}).onFailure(e -> {
|
||||||
|
LOG.warn("GetFileList 失败,回退到旧解析方式: {}", e.getMessage());
|
||||||
|
parseLegacy(html, fileName);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
parseLegacy(html, fileName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<List<FileInfo>> parseFileList() {
|
||||||
|
Promise<List<FileInfo>> resultPromise = Promise.promise();
|
||||||
|
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
|
||||||
|
|
||||||
|
client.getAbs(shareLinkInfo.getShareUrl()).send(result -> {
|
||||||
|
if (result.failed()) {
|
||||||
|
resultPromise.fail(result.cause());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String html = result.result().bodyAsString();
|
||||||
|
String filesetId = extractFilesetId(html);
|
||||||
|
if (filesetId == null) {
|
||||||
|
resultPromise.fail("无法从页面提取 filesetId");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String parentId = dirId != null ? dirId : "";
|
||||||
|
fetchFileList(filesetId, parentId).onSuccess(fileList -> {
|
||||||
|
try {
|
||||||
|
List<FileInfo> list = new ArrayList<>();
|
||||||
|
String panType = shareLinkInfo.getType();
|
||||||
|
for (int i = 0; i < fileList.size(); i++) {
|
||||||
|
JsonObject file = fileList.getJsonObject(i);
|
||||||
|
FileInfo fileInfo = new FileInfo();
|
||||||
|
String name = file.getString("name");
|
||||||
|
String cliFileid = file.getString("cli_fileid");
|
||||||
|
boolean isDir = file.getBoolean("is_dir", false);
|
||||||
|
String sizeStr = file.getString("file_size");
|
||||||
|
|
||||||
|
fileInfo.setFileName(name)
|
||||||
|
.setFileId(cliFileid)
|
||||||
|
.setPanType(panType)
|
||||||
|
.setSizeStr(sizeStr);
|
||||||
|
|
||||||
|
if (isDir) {
|
||||||
|
fileInfo.setFileType("folder")
|
||||||
|
.setParserUrl(String.format("%s/v2/getFileList?url=%s&dirId=%s",
|
||||||
|
getDomainName(),
|
||||||
|
URLEncoder.encode(shareLinkInfo.getShareUrl(), StandardCharsets.UTF_8),
|
||||||
|
cliFileid));
|
||||||
|
} else {
|
||||||
|
String physicalId = file.getJsonObject("physical").getString("id");
|
||||||
|
JsonObject paramJson = new JsonObject()
|
||||||
|
.put("fileId", physicalId)
|
||||||
|
.put("fileName", name)
|
||||||
|
.put("cliFileid", cliFileid);
|
||||||
|
String param = CommonUtils.urlBase64Encode(paramJson.encode());
|
||||||
|
fileInfo.setFileType("file")
|
||||||
|
.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s",
|
||||||
|
getDomainName(), panType, param));
|
||||||
|
}
|
||||||
|
list.add(fileInfo);
|
||||||
|
}
|
||||||
|
resultPromise.complete(list);
|
||||||
|
} catch (Exception e) {
|
||||||
|
resultPromise.fail(e);
|
||||||
|
}
|
||||||
|
}).onFailure(resultPromise::fail);
|
||||||
|
});
|
||||||
|
return resultPromise.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Future<String> parseById() {
|
||||||
|
JsonObject paramJson = (JsonObject) shareLinkInfo.getOtherParam().get("paramJson");
|
||||||
|
String fileId = paramJson.getString("fileId");
|
||||||
|
String fileName = paramJson.getString("fileName");
|
||||||
|
|
||||||
|
Promise<String> p = Promise.promise();
|
||||||
|
callBatchDownload(fileId, fileName, p);
|
||||||
|
return p.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 内部方法 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用 BatchDownload API 获取单个文件的下载直链
|
||||||
|
*/
|
||||||
|
private void downloadFile(String physicalId, String fileName) {
|
||||||
|
callBatchDownload(physicalId, fileName, promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void callBatchDownload(String physicalId, String fileName, Promise<String> p) {
|
||||||
|
String body = """
|
||||||
|
{"req_head":{"agent":8},"download_info":[{"batch_id":"%s","scene":{"business_type":4,"app_type":22,"scene_type":5},"index_node":{"file_uuid":"%s"},"url_type":2,"download_scene":0}],"scene_type":103}
|
||||||
|
""".formatted(physicalId, physicalId);
|
||||||
|
|
||||||
|
client.postAbs(BATCH_DOWNLOAD_API)
|
||||||
|
.putHeaders(BATCH_DOWNLOAD_HEADERS)
|
||||||
|
.sendJsonObject(new JsonObject(body))
|
||||||
|
.onSuccess(resp -> {
|
||||||
|
if (resp.statusCode() != 200) {
|
||||||
|
p.fail("BatchDownload 请求失败,状态码: " + resp.statusCode());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonObject respBody = asJson(resp);
|
||||||
|
if (!respBody.containsKey("retcode") || respBody.getInteger("retcode") != 0) {
|
||||||
|
p.fail("BatchDownload 请求失败: " + respBody.encodePrettily());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonArray downloadRsp = respBody.getJsonObject("data").getJsonArray("download_rsp");
|
||||||
|
if (downloadRsp == null || downloadRsp.isEmpty()) {
|
||||||
|
p.fail("BatchDownload 响应中缺少 download_rsp");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String url = downloadRsp.getJsonObject(0).getString("url");
|
||||||
|
if (url != null && url.startsWith("&filename=")) {
|
||||||
|
p.fail("该文件已被和谐");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fileName != null) {
|
||||||
|
url = url + "&filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
p.complete(url);
|
||||||
|
})
|
||||||
|
.onFailure(e -> {
|
||||||
|
LOG.error("BatchDownload 请求异常", e);
|
||||||
|
p.fail(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用 GetFileList API 获取指定目录下的文件列表
|
||||||
|
*/
|
||||||
|
private Future<JsonArray> fetchFileList(String filesetId, String parentId) {
|
||||||
|
Promise<JsonArray> p = Promise.promise();
|
||||||
|
JsonObject body = new JsonObject()
|
||||||
|
.put("fileset_id", filesetId)
|
||||||
|
.put("req_infos", new JsonArray()
|
||||||
|
.add(new JsonObject()
|
||||||
|
.put("parent_id", parentId)
|
||||||
|
.put("req_depth", 1)
|
||||||
|
.put("count", 50)
|
||||||
|
.put("filter_condition", new JsonObject().put("file_category", 0))
|
||||||
|
.put("sort_conditions", new JsonArray()
|
||||||
|
.add(new JsonObject()
|
||||||
|
.put("sort_field", 0)
|
||||||
|
.put("sort_order", 0)))))
|
||||||
|
.put("support_folder_status", true);
|
||||||
|
|
||||||
|
MultiMap headers = GET_FILE_LIST_HEADERS.set("Referer", shareLinkInfo.getShareUrl());
|
||||||
|
|
||||||
|
client.postAbs(GET_FILE_LIST_API)
|
||||||
|
.putHeaders(headers)
|
||||||
|
.sendJsonObject(body)
|
||||||
|
.onSuccess(resp -> {
|
||||||
|
if (resp.statusCode() != 200) {
|
||||||
|
p.fail("GetFileList 请求失败,状态码: " + resp.statusCode());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonObject respBody = asJson(resp);
|
||||||
|
if (respBody.getInteger("retcode", -1) != 0) {
|
||||||
|
p.fail("GetFileList 请求失败: " + respBody.getString("message", "未知错误"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonArray fileLists = respBody.getJsonObject("data").getJsonArray("file_lists");
|
||||||
|
if (fileLists == null || fileLists.isEmpty()) {
|
||||||
|
p.fail("GetFileList 响应中缺少 file_lists");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JsonArray fileList = fileLists.getJsonObject(0).getJsonArray("file_list");
|
||||||
|
p.complete(fileList != null ? fileList : new JsonArray());
|
||||||
|
})
|
||||||
|
.onFailure(e -> {
|
||||||
|
LOG.error("GetFileList 请求异常", e);
|
||||||
|
p.fail(e);
|
||||||
|
});
|
||||||
|
return p.future();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 HTML 的 __NUXT_DATA__ 中提取 fileset_id
|
||||||
|
*/
|
||||||
|
String extractFilesetId(String html) {
|
||||||
|
// Nuxt __NUXT_DATA__ 中 fileset_id 出现在缓存 key 的嵌套 JSON 中
|
||||||
|
// 直接匹配 fileset_id 后面最近的 UUID(跳过转义引号、冒号等非hex字符)
|
||||||
|
Pattern pattern = Pattern.compile(
|
||||||
|
"fileset_id[^a-f0-9]*([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})");
|
||||||
|
Matcher matcher = pattern.matcher(html);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 旧版解析方式(兼容单文件链接,通过 HTML 字符串搜索提取 UUID)
|
||||||
|
*/
|
||||||
|
private void parseLegacy(String html, String fileName) {
|
||||||
|
String fileUUID = getFileUUID(html);
|
||||||
|
if (fileUUID == null) {
|
||||||
|
promise.fail("未能提取到文件UUID");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG.info("使用旧版解析,提取到的文件UUID: {}", fileUUID);
|
||||||
|
downloadFile(fileUUID, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
String getFileUUID(String htmlJs) {
|
String getFileUUID(String htmlJs) {
|
||||||
String keyword = "\"download_limit_status\"";
|
String keyword = "\"download_limit_status\"";
|
||||||
String marker = "},\"";
|
String marker = "},\"";
|
||||||
@@ -140,32 +319,23 @@ public class QQscTool extends PanBase {
|
|||||||
String extracted = htmlJs.substring(quoteStart, quoteEnd);
|
String extracted = htmlJs.substring(quoteStart, quoteEnd);
|
||||||
LOG.debug("提取结果: {}", extracted);
|
LOG.debug("提取结果: {}", extracted);
|
||||||
return extracted;
|
return extracted;
|
||||||
} else {
|
|
||||||
LOG.error("未找到结束引号: {}", marker);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
LOG.error("未找到标记: {} 在关键字: {} 之后", marker, keyword);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
LOG.error("未找到关键字: {}", keyword);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String extractFileNameFromTitle(String content) {
|
public static String extractFileNameFromTitle(String content) {
|
||||||
// 匹配<title>和</title>之间的内容
|
|
||||||
Pattern pattern = Pattern.compile("<title>(.*?)</title>");
|
Pattern pattern = Pattern.compile("<title>(.*?)</title>");
|
||||||
Matcher matcher = pattern.matcher(content);
|
Matcher matcher = pattern.matcher(content);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
String fullTitle = matcher.group(1);
|
String fullTitle = matcher.group(1);
|
||||||
// 按 "|" 分割,取前半部分
|
|
||||||
int sepIndex = fullTitle.indexOf("|");
|
int sepIndex = fullTitle.indexOf("|");
|
||||||
if (sepIndex != -1) {
|
if (sepIndex != -1) {
|
||||||
return fullTitle.substring(0, sepIndex);
|
return fullTitle.substring(0, sepIndex);
|
||||||
}
|
}
|
||||||
return fullTitle; // 如果没有分隔符,就返回全部
|
return fullTitle;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package cn.qaiu.util;
|
|||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -10,6 +13,8 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class URLUtil {
|
public class URLUtil {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(URLUtil.class);
|
||||||
|
|
||||||
private final Map<String, String> queryParams = new HashMap<>();
|
private final Map<String, String> queryParams = new HashMap<>();
|
||||||
|
|
||||||
// 构造函数,传入URL并解析参数
|
// 构造函数,传入URL并解析参数
|
||||||
@@ -31,7 +36,7 @@ public class URLUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
LOGGER.error("URL解析失败: {}", url, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<meta name="description"
|
<meta name="description"
|
||||||
content="Netdisk fast download 网盘直链解析工具">
|
content="Netdisk fast download 网盘直链解析工具">
|
||||||
<!-- Font Awesome 图标库 - 使用国内CDN -->
|
<!-- Font Awesome 图标库 - 使用国内CDN -->
|
||||||
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://s4.zstatic.net/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
<!-- 迅雷 JS-SDK -->
|
<!-- 迅雷 JS-SDK -->
|
||||||
<script src="//open.thunderurl.com/thunder-link.js"></script>
|
<script src="//open.thunderurl.com/thunder-link.js"></script>
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
23
web-front/scripts/sync-version.js
Normal file
23
web-front/scripts/sync-version.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const pomPath = path.resolve(__dirname, '../../pom.xml');
|
||||||
|
const pkgPath = path.resolve(__dirname, '../package.json');
|
||||||
|
|
||||||
|
const pomContent = fs.readFileSync(pomPath, 'utf-8');
|
||||||
|
const match = pomContent.match(/<revision>([^<]+)<\/revision>/);
|
||||||
|
if (!match) {
|
||||||
|
console.error('sync-version: <revision> not found in root pom.xml');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const version = match[1];
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
||||||
|
if (pkg.version === version) {
|
||||||
|
console.log(`sync-version: package.json already at ${version}`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg.version = version;
|
||||||
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
||||||
|
console.log(`sync-version: package.json ${pkg.version} -> ${version}`);
|
||||||
@@ -1,10 +1,34 @@
|
|||||||
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const { execSync } = require("child_process");
|
||||||
|
const webpack = require("webpack");
|
||||||
|
|
||||||
function resolve(dir) {
|
function resolve(dir) {
|
||||||
return path.join(__dirname, dir)
|
return path.join(__dirname, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从 git remote origin 自动识别 GitHub 仓库地址
|
||||||
|
function getGitHubRepoUrl() {
|
||||||
|
try {
|
||||||
|
const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf-8', cwd: path.resolve(__dirname, '..') }).trim();
|
||||||
|
const match = remoteUrl.match(/github\.com[:/]([^/]+\/[^/.]+?)(?:\.git)?$/);
|
||||||
|
if (match) return `https://github.com/${match[1]}`;
|
||||||
|
} catch (e) {}
|
||||||
|
return 'https://github.com/qaiu/netdisk-fast-download';
|
||||||
|
}
|
||||||
|
// 从根 pom.xml 读取项目版本号(单一版本来源)
|
||||||
|
function getProjectVersion() {
|
||||||
|
try {
|
||||||
|
const pomContent = require('fs').readFileSync(path.resolve(__dirname, '../pom.xml'), 'utf-8');
|
||||||
|
const match = pomContent.match(/<revision>([^<]+)<\/revision>/);
|
||||||
|
if (match) return match[1];
|
||||||
|
} catch (e) {}
|
||||||
|
return require('./package.json').version;
|
||||||
|
}
|
||||||
|
const PROJECT_VERSION = getProjectVersion();
|
||||||
|
|
||||||
|
const GITHUB_REPO_URL = getGitHubRepoUrl();
|
||||||
|
|
||||||
const CompressionPlugin = require('compression-webpack-plugin');
|
const CompressionPlugin = require('compression-webpack-plugin');
|
||||||
const FileManagerPlugin = require('filemanager-webpack-plugin');
|
const FileManagerPlugin = require('filemanager-webpack-plugin');
|
||||||
const MonacoEditorPlugin = require('monaco-editor-webpack-plugin');
|
const MonacoEditorPlugin = require('monaco-editor-webpack-plugin');
|
||||||
@@ -55,6 +79,10 @@ module.exports = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.VUE_APP_GITHUB_REPO_URL': JSON.stringify(GITHUB_REPO_URL),
|
||||||
|
'process.env.VUE_APP_VERSION': JSON.stringify(PROJECT_VERSION)
|
||||||
|
}),
|
||||||
new MonacoEditorPlugin({
|
new MonacoEditorPlugin({
|
||||||
languages: ['javascript', 'typescript', 'json'],
|
languages: ['javascript', 'typescript', 'json'],
|
||||||
features: ['coreCommands', 'find', 'format', 'suggest', 'quickCommand'],
|
features: ['coreCommands', 'find', 'format', 'suggest', 'quickCommand'],
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ public class ServerApi {
|
|||||||
key = keys[0];
|
key = keys[0];
|
||||||
pwd = keys[1];
|
pwd = keys[1];
|
||||||
}
|
}
|
||||||
return cacheService.getCachedByShareKeyAndPwd(type, key, pwd, JsonObject.of("UA",request.headers().get("user-agent")));
|
String origin = resolveOrigin(request);
|
||||||
|
return cacheService.getCachedByShareKeyAndPwd(type, key, pwd, JsonObject.of("UA",request.headers().get("user-agent"), "_requestOrigin", origin));
|
||||||
}
|
}
|
||||||
|
|
||||||
@RouteMapping(value = "/:type/:key", method = RouteMethod.GET)
|
@RouteMapping(value = "/:type/:key", method = RouteMethod.GET)
|
||||||
@@ -80,7 +81,8 @@ public class ServerApi {
|
|||||||
key = keys[0];
|
key = keys[0];
|
||||||
pwd = keys[1];
|
pwd = keys[1];
|
||||||
}
|
}
|
||||||
cacheService.getCachedByShareKeyAndPwd(type, key, pwd, JsonObject.of("UA",request.headers().get("user-agent")))
|
String origin = resolveOrigin(request);
|
||||||
|
cacheService.getCachedByShareKeyAndPwd(type, key, pwd, JsonObject.of("UA",request.headers().get("user-agent"), "_requestOrigin", origin))
|
||||||
.onSuccess(res -> ResponseUtil.redirect(
|
.onSuccess(res -> ResponseUtil.redirect(
|
||||||
response.putHeader("nfd-cache-hit", res.getCacheHit().toString())
|
response.putHeader("nfd-cache-hit", res.getCacheHit().toString())
|
||||||
.putHeader("nfd-cache-expires", res.getExpires()),
|
.putHeader("nfd-cache-expires", res.getExpires()),
|
||||||
@@ -89,6 +91,21 @@ public class ServerApi {
|
|||||||
return promise.future();
|
return promise.future();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析请求来源地址,支持反向代理
|
||||||
|
*/
|
||||||
|
private static String resolveOrigin(HttpServerRequest request) {
|
||||||
|
String forwardedHost = request.getHeader("X-Forwarded-Host");
|
||||||
|
if (forwardedHost != null && !forwardedHost.isBlank()) {
|
||||||
|
String proto = request.getHeader("X-Forwarded-Proto");
|
||||||
|
if (proto == null || proto.isBlank()) {
|
||||||
|
proto = request.scheme();
|
||||||
|
}
|
||||||
|
return proto + "://" + forwardedHost;
|
||||||
|
}
|
||||||
|
return request.scheme() + "://" + request.host();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建 otherParam,包含 UA 和解码后的认证参数
|
* 构建 otherParam,包含 UA 和解码后的认证参数
|
||||||
*
|
*
|
||||||
@@ -97,7 +114,7 @@ public class ServerApi {
|
|||||||
* @return JsonObject
|
* @return JsonObject
|
||||||
*/
|
*/
|
||||||
private JsonObject buildOtherParam(HttpServerRequest request, String auth) {
|
private JsonObject buildOtherParam(HttpServerRequest request, String auth) {
|
||||||
JsonObject otherParam = JsonObject.of("UA", request.headers().get("user-agent"));
|
JsonObject otherParam = JsonObject.of("UA", request.headers().get("user-agent"), "_requestOrigin", resolveOrigin(request));
|
||||||
|
|
||||||
// 解码认证参数
|
// 解码认证参数
|
||||||
if (auth != null && !auth.isEmpty()) {
|
if (auth != null && !auth.isEmpty()) {
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ server:
|
|||||||
contextPath: /
|
contextPath: /
|
||||||
# 使用数据库
|
# 使用数据库
|
||||||
enableDatabase: true
|
enableDatabase: true
|
||||||
# 服务域名或者IP 生成二维码链接时需要
|
# 服务域名或者IP 生成二维码链接时需要,不设置则自动从请求地址获取
|
||||||
domainName: http://127.0.0.1:6401
|
# domainName: http://127.0.0.1:6401
|
||||||
# 预览服务URL
|
# 预览服务URL
|
||||||
previewURL: https://nfd-parser.github.io/nfd-preview/preview.html?src=
|
previewURL: https://nfd-parser.github.io/nfd-preview/preview.html?src=
|
||||||
# auth参数加密密钥(16位AES密钥)
|
# auth参数加密密钥(16位AES密钥)
|
||||||
authEncryptKey: 'nfd_auth_key2026'
|
authEncryptKey: 'nfd_auth_key2026'
|
||||||
# domainName: https://lz.qaiu.top
|
|
||||||
|
|
||||||
# 反向代理服务器配置路径(不用加后缀)
|
# 反向代理服务器配置路径(不用加后缀)
|
||||||
proxyConf: server-proxy
|
proxyConf: server-proxy
|
||||||
|
|||||||
Reference in New Issue
Block a user