diff --git a/.gitignore b/.gitignore
index 32c8707..6cfc6c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,7 @@ target/
sdkTest.log
app.yml
app-local.yml
+secret.yml
#some local files
diff --git a/Dockerfile b/Dockerfile
index 8d50655..be3c89b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,8 +10,13 @@ COPY ./web-service/target/netdisk-fast-download-bin.zip .
RUN unzip netdisk-fast-download-bin.zip && \
mv netdisk-fast-download/* ./ && \
rm netdisk-fast-download-bin.zip && \
- chmod +x run.sh
+ chmod +x run.sh && \
+ mkdir -p db logs
-EXPOSE 6400 6401
+COPY ./docker-entrypoint.sh /docker-entrypoint.sh
+RUN chmod +x /docker-entrypoint.sh
-ENTRYPOINT ["sh", "run.sh"]
+EXPOSE 6401
+
+RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
+ENTRYPOINT ["/docker-entrypoint.sh"]
diff --git a/README.md b/README.md
index 95648e3..578f069 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
# 一款网盘分享链接云解析快速下载服务
QQ交流群:1017480890
-
+
-
+
@@ -419,7 +419,7 @@ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtow
> 注意: netdisk-fast-download.service中的ExecStart的路径改为实际路径
```shell
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
cd netdisk-fast-download
bash service-install.sh
diff --git a/bin/run.sh b/bin/run.sh
index 06e7c53..04c111c 100644
--- a/bin/run.sh
+++ b/bin/run.sh
@@ -1,6 +1,5 @@
#!/bin/bash
# set -x
LAUNCH_JAR="netdisk-fast-download.jar"
-nohup java -Xmx512M -jar "$LAUNCH_JAR" "$@" >startup.log 2>&1 &
-tail -f startup.log
+exec java -Xmx${JVM_XMX:-512M} ${JVM_OPTS} -jar "$LAUNCH_JAR" "$@"
diff --git a/core-database/pom.xml b/core-database/pom.xml
index 398fd2d..0ae8493 100644
--- a/core-database/pom.xml
+++ b/core-database/pom.xml
@@ -65,7 +65,7 @@
org.postgresql
postgresql
- 42.7.3
+ 42.7.11
diff --git a/core-database/src/main/java/cn/qaiu/db/ddl/CreateDatabase.java b/core-database/src/main/java/cn/qaiu/db/ddl/CreateDatabase.java
index 6749c15..f740c0f 100644
--- a/core-database/src/main/java/cn/qaiu/db/ddl/CreateDatabase.java
+++ b/core-database/src/main/java/cn/qaiu/db/ddl/CreateDatabase.java
@@ -53,7 +53,7 @@ public class CreateDatabase {
stmt.executeUpdate("CREATE DATABASE IF NOT EXISTS " + dbName + " CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
LOGGER.info(">>>>>>>>>>> 数据库'{}'创建成功 <<<<<<<<<<<<", dbName);
} catch (SQLException e) {
- e.printStackTrace();
+ LOGGER.error("创建数据库失败", e);
}
}
diff --git a/core-database/src/main/java/cn/qaiu/db/ddl/CreateTable.java b/core-database/src/main/java/cn/qaiu/db/ddl/CreateTable.java
index 2228980..a547e32 100644
--- a/core-database/src/main/java/cn/qaiu/db/ddl/CreateTable.java
+++ b/core-database/src/main/java/cn/qaiu/db/ddl/CreateTable.java
@@ -24,35 +24,39 @@ import java.util.*;
* @author QAIU
*/
public class CreateTable {
- public static Map, String> javaProperty2SqlColumnMap = new HashMap<>() {{
+ public static final Map, String> javaProperty2SqlColumnMap;
+ static {
+ Map, String> map = new HashMap<>();
// Java类型到SQL类型的映射
- put(Integer.class, "INT");
- put(Short.class, "SMALLINT");
- put(Byte.class, "TINYINT");
- put(Long.class, "BIGINT");
- put(java.math.BigDecimal.class, "DECIMAL");
- put(Double.class, "DOUBLE");
- put(Float.class, "REAL");
- put(Boolean.class, "BOOLEAN");
- put(String.class, "VARCHAR");
- put(Date.class, "TIMESTAMP");
- put(java.time.LocalDateTime.class, "TIMESTAMP");
- put(java.sql.Timestamp.class, "TIMESTAMP");
- put(java.sql.Date.class, "DATE");
- put(java.sql.Time.class, "TIME");
+ map.put(Integer.class, "INT");
+ map.put(Short.class, "SMALLINT");
+ map.put(Byte.class, "TINYINT");
+ map.put(Long.class, "BIGINT");
+ map.put(java.math.BigDecimal.class, "DECIMAL");
+ map.put(Double.class, "DOUBLE");
+ map.put(Float.class, "REAL");
+ map.put(Boolean.class, "BOOLEAN");
+ map.put(String.class, "VARCHAR");
+ map.put(Date.class, "TIMESTAMP");
+ map.put(java.time.LocalDateTime.class, "TIMESTAMP");
+ map.put(java.sql.Timestamp.class, "TIMESTAMP");
+ map.put(java.sql.Date.class, "DATE");
+ map.put(java.sql.Time.class, "TIME");
// 基本数据类型
- put(int.class, "INT");
- put(short.class, "SMALLINT");
- put(byte.class, "TINYINT");
- put(long.class, "BIGINT");
- put(double.class, "DOUBLE");
- put(float.class, "REAL");
- put(boolean.class, "BOOLEAN");
- }};
+ map.put(int.class, "INT");
+ map.put(short.class, "SMALLINT");
+ map.put(byte.class, "TINYINT");
+ map.put(long.class, "BIGINT");
+ map.put(double.class, "DOUBLE");
+ map.put(float.class, "REAL");
+ map.put(boolean.class, "BOOLEAN");
+
+ javaProperty2SqlColumnMap = Collections.unmodifiableMap(map);
+ }
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) {
return switch (clz.getName()) {
diff --git a/core/src/main/java/cn/qaiu/vx/core/Deploy.java b/core/src/main/java/cn/qaiu/vx/core/Deploy.java
index 09c2f9f..5fb606f 100644
--- a/core/src/main/java/cn/qaiu/vx/core/Deploy.java
+++ b/core/src/main/java/cn/qaiu/vx/core/Deploy.java
@@ -16,6 +16,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.management.ManagementFactory;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Calendar;
import java.util.Date;
import java.util.UUID;
@@ -62,7 +64,12 @@ public final class Deploy {
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)
.onSuccess(this::readConf)
.onFailure(err -> {
diff --git a/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java b/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java
index aca7f7b..92bb224 100644
--- a/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java
+++ b/core/src/main/java/cn/qaiu/vx/core/handlerfactory/RouterHandlerFactory.java
@@ -127,8 +127,9 @@ public class RouterHandlerFactory implements BaseHttpApi {
// 错误请求处理
mainRouter.errorHandler(405, ctx -> doFireJsonResultResponse(ctx, JsonResult
.error("Method Not Allowed", 405)));
- mainRouter.errorHandler(404, ctx -> ctx.response().setStatusCode(404).setChunked(true)
- .end("Internal server error: 404 not found"));
+ mainRouter.errorHandler(404, ctx -> {
+ ctx.response().setStatusCode(404).end("404 not found");
+ });
return mainRouter;
}
@@ -179,8 +180,9 @@ public class RouterHandlerFactory implements BaseHttpApi {
if (ctx.statusCode() == 503 || ctx.failure() == null) {
doFireJsonResultResponse(ctx, JsonResult.error("未知异常, 请联系管理员"), 503);
} else {
- ctx.failure().printStackTrace();
- doFireJsonResultResponse(ctx, JsonResult.error(ctx.failure().getMessage()), 500);
+ LOGGER.error("路由处理失败", ctx.failure());
+ String msg = ctx.failure() != null ? ctx.failure().getMessage() : "未知异常";
+ doFireJsonResultResponse(ctx, JsonResult.error(msg), 500);
}
});
} else if (method.isAnnotationPresent(SockRouteMapper.class)) {
@@ -198,7 +200,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
try {
ReflectionUtil.invokeWithArguments(method, instance, sock);
} catch (Throwable e) {
- e.printStackTrace();
+ LOGGER.error("WebSocket处理异常", e);
}
});
if (url.endsWith("*")) {
@@ -322,7 +324,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
parameterValueList.put(k, entity);
}
} catch (ClassNotFoundException e) {
- e.printStackTrace();
+ LOGGER.error("实体类绑定异常: {}", typeName, e);
}
}
});
@@ -365,7 +367,7 @@ public class RouterHandlerFactory implements BaseHttpApi {
Object entity = ParamUtil.multiMapToEntity(queryParams, aClass);
parameterValueList.put(k, entity);
} catch (Exception e) {
- e.printStackTrace();
+ LOGGER.error("参数绑定异常: {}", v.getRight().getName(), e);
}
} else if (parameterValueList.get(k) == null
&& JsonObject.class.getName().equals(v.getRight().getName())) {
@@ -408,22 +410,19 @@ public class RouterHandlerFactory implements BaseHttpApi {
doFireJsonResultResponse(ctx, JsonResult.data(null));
}
- }).onFailure(e -> doFireJsonResultResponse(ctx, JsonResult.error(e.getMessage()), 500));
+ }).onFailure(e -> {
+ LOGGER.error("请求处理失败", e);
+ String msg = e.getMessage() != null ? e.getMessage() : "服务器内部错误";
+ doFireJsonResultResponse(ctx, JsonResult.error(msg), 500);
+ });
} else {
doFireJsonResultResponse(ctx, JsonResult.data(data));
}
}
} catch (Throwable e) {
- e.printStackTrace();
- String err = e.getMessage();
- if (e.getCause() != null) {
- if (e.getCause() instanceof InvocationTargetException) {
- err = ((InvocationTargetException) e.getCause()).getTargetException().getMessage();
- } else {
- err = e.getCause().getMessage();
- }
- }
- doFireJsonResultResponse(ctx, JsonResult.error(err), 500);
+ LOGGER.error("请求处理异常", e);
+ String msg = e.getMessage() != null ? e.getMessage() : "服务器内部错误";
+ doFireJsonResultResponse(ctx, JsonResult.error(msg), 500);
}
}
diff --git a/core/src/main/java/cn/qaiu/vx/core/util/CommonUtil.java b/core/src/main/java/cn/qaiu/vx/core/util/CommonUtil.java
index d9da4ff..3f77baa 100644
--- a/core/src/main/java/cn/qaiu/vx/core/util/CommonUtil.java
+++ b/core/src/main/java/cn/qaiu/vx/core/util/CommonUtil.java
@@ -153,7 +153,7 @@ public class CommonUtil {
appVersion = properties.getProperty("app.version") + "build" + properties.getProperty("build");
}
} catch (IOException e) {
- e.printStackTrace();
+ LOGGER.error("读取app.properties失败", e);
}
}
return appVersion;
diff --git a/core/src/main/java/cn/qaiu/vx/core/util/ConfigUtil.java b/core/src/main/java/cn/qaiu/vx/core/util/ConfigUtil.java
index 1d5cb50..4b15dc7 100644
--- a/core/src/main/java/cn/qaiu/vx/core/util/ConfigUtil.java
+++ b/core/src/main/java/cn/qaiu/vx/core/util/ConfigUtil.java
@@ -10,6 +10,8 @@ import io.vertx.core.json.JsonObject;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
/**
* 异步读取配置工具类
@@ -62,12 +64,35 @@ public class ConfigUtil {
// 异步获取配置
// 成功直接完成 promise
retriever.getConfig()
- .onSuccess(promise::complete)
- .onFailure(err -> {
- // 配置读取失败,直接返回失败 Future
- promise.fail(new RuntimeException(
- "读取配置文件失败: " + path, err));
+ .onSuccess(config -> {
+ promise.complete(config);
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();
diff --git a/core/src/main/java/cn/qaiu/vx/core/util/LocalConstant.java b/core/src/main/java/cn/qaiu/vx/core/util/LocalConstant.java
index 0a7fa8c..6111e9b 100644
--- a/core/src/main/java/cn/qaiu/vx/core/util/LocalConstant.java
+++ b/core/src/main/java/cn/qaiu/vx/core/util/LocalConstant.java
@@ -1,7 +1,7 @@
package cn.qaiu.vx.core.util;
-import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
/**
* vertx 上下文外的本地容器 为不在vertx线程的方法传递数据
@@ -10,11 +10,10 @@ import java.util.Map;
* @author QAIU
*/
public class LocalConstant {
- private static final Map LOCAL_CONST = new HashMap<>();
+ private static final Map LOCAL_CONST = new ConcurrentHashMap<>();
public static Map put(String k, Object v) {
- if (LOCAL_CONST.containsKey(k)) return LOCAL_CONST;
- LOCAL_CONST.put(k, v);
+ LOCAL_CONST.putIfAbsent(k, v);
return LOCAL_CONST;
}
diff --git a/core/src/main/java/cn/qaiu/vx/core/util/ReflectionUtil.java b/core/src/main/java/cn/qaiu/vx/core/util/ReflectionUtil.java
index 3d30eee..8cb1b50 100644
--- a/core/src/main/java/cn/qaiu/vx/core/util/ReflectionUtil.java
+++ b/core/src/main/java/cn/qaiu/vx/core/util/ReflectionUtil.java
@@ -25,6 +25,9 @@ import java.net.URL;
import java.text.ParseException;
import java.util.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
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 {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class);
+
// 缓存Reflections实例,避免重复扫描(每次扫描约35K+值,耗时1-3秒,占用大量内存)
private static final Map REFLECTIONS_CACHE = new java.util.concurrent.ConcurrentHashMap<>();
@@ -128,7 +133,7 @@ public final class ReflectionUtil {
parameterTypes[j - k]));
}
} catch (NotFoundException e) {
- e.printStackTrace();
+ LOGGER.error("获取方法参数失败", e);
}
return paramMap;
}
@@ -183,7 +188,7 @@ public final class ReflectionUtil {
try {
return DateUtils.parseDate(value, fmt);
} catch (ParseException e) {
- e.printStackTrace();
+ LOGGER.error("日期解析失败: {}", value, e);
throw new RuntimeException("无法将格式化日期");
}
default:
@@ -215,7 +220,7 @@ public final class ReflectionUtil {
}
return arr;
} catch (Exception e) {
- e.printStackTrace();
+ LOGGER.error("数组类型转换失败: {}", value, e);
}
return null;
}
diff --git a/core/src/main/java/cn/qaiu/vx/core/verticle/HttpProxyVerticle.java b/core/src/main/java/cn/qaiu/vx/core/verticle/HttpProxyVerticle.java
index aa713ea..c6fdfc3 100644
--- a/core/src/main/java/cn/qaiu/vx/core/verticle/HttpProxyVerticle.java
+++ b/core/src/main/java/cn/qaiu/vx/core/verticle/HttpProxyVerticle.java
@@ -196,7 +196,7 @@ public class HttpProxyVerticle extends AbstractVerticle {
);
})
.onFailure(err -> {
- err.printStackTrace();
+ LOGGER.error("HTTP请求失败", err);
clientRequest.response().setStatusCode(502).end("Bad Gateway: Request failed");
});
}
@@ -222,7 +222,7 @@ public class HttpProxyVerticle extends AbstractVerticle {
}
return port;
} catch (Exception e) {
- e.printStackTrace();
+ LOGGER.error("提取端口失败: {}", urlString, e);
// 出现异常时返回 -1,表示提取失败
return -1;
}
diff --git a/core/src/main/java/cn/qaiu/vx/core/verticle/RouterVerticle.java b/core/src/main/java/cn/qaiu/vx/core/verticle/RouterVerticle.java
index acc0c7e..b85a484 100644
--- a/core/src/main/java/cn/qaiu/vx/core/verticle/RouterVerticle.java
+++ b/core/src/main/java/cn/qaiu/vx/core/verticle/RouterVerticle.java
@@ -23,12 +23,11 @@ public class RouterVerticle extends AbstractVerticle {
private static final Logger LOGGER = LoggerFactory.getLogger(RouterVerticle.class);
private static final int port = SharedDataUtil.getValueForServerConfig("port");
- private static final Router router = new RouterHandlerFactory(
- SharedDataUtil.getJsonStringForServerConfig("contextPath")).createRouter();
private static final JsonObject globalConfig = SharedDataUtil.getJsonConfig("globalConfig");
private HttpServer server;
+ private Router router;
static {
LOGGER.info(JacksonConfig.class.getSimpleName() + " >> ");
@@ -61,6 +60,8 @@ public class RouterVerticle extends AbstractVerticle {
.setReuseAddress(true) // 允许地址重用
.setReusePort(true); // 允许端口重用
+ router = new RouterHandlerFactory(
+ SharedDataUtil.getJsonStringForServerConfig("contextPath")).createRouter();
server = vertx.createHttpServer(options);
server.requestHandler(router).webSocketHandler(s->{}).listen()
diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh
new file mode 100644
index 0000000..2bc18db
--- /dev/null
+++ b/docker-entrypoint.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+set -e
+
+# Fix permissions on volume-mounted directories (runs as root)
+chown -R appuser:appgroup /app/db /app/logs /app/resources 2>/dev/null || true
+
+# Run Java directly - entrypoint is PID 1, exec makes Java PID 1
+# Docker SIGTERM goes directly to Java, triggering ShutdownHook
+exec java -Xmx${JVM_XMX:-512M} ${JVM_OPTS} -Duser.timezone=${TZ:-Asia/Shanghai} -jar /app/netdisk-fast-download.jar
diff --git a/parser/README.md b/parser/README.md
index 9b3c606..ddae166 100644
--- a/parser/README.md
+++ b/parser/README.md
@@ -4,26 +4,26 @@ NFD 解析器模块:聚合各类网盘/分享页解析,统一输出文件列
- 语言:Java 17
- 构建:Maven
-- 模块版本:10.1.17
+- 模块版本:10.2.5
## 依赖(Maven Central)
```xml
cn.qaiu
parser
- 10.1.17
+ 10.2.5
```
- Gradle Groovy DSL:
```groovy
dependencies {
- implementation 'cn.qaiu:parser:10.1.17'
+ implementation 'cn.qaiu:parser:10.2.5'
}
```
- Gradle Kotlin DSL:
```kotlin
dependencies {
- implementation("cn.qaiu:parser:10.1.17")
+ implementation("cn.qaiu:parser:10.2.5")
}
```
diff --git a/parser/doc/CUSTOM_PARSER_GUIDE.md b/parser/doc/CUSTOM_PARSER_GUIDE.md
index 9628cb2..ede1900 100644
--- a/parser/doc/CUSTOM_PARSER_GUIDE.md
+++ b/parser/doc/CUSTOM_PARSER_GUIDE.md
@@ -28,7 +28,7 @@
cn.qaiu
parser
- 10.1.17
+ 10.2.5
```
diff --git a/parser/doc/CUSTOM_PARSER_QUICKSTART.md b/parser/doc/CUSTOM_PARSER_QUICKSTART.md
index 8067805..b17d297 100644
--- a/parser/doc/CUSTOM_PARSER_QUICKSTART.md
+++ b/parser/doc/CUSTOM_PARSER_QUICKSTART.md
@@ -11,7 +11,7 @@
cn.qaiu
parser
- 10.1.17
+ 10.2.5
```
diff --git a/parser/pom.xml b/parser/pom.xml
index b5791d1..2a55f4d 100644
--- a/parser/pom.xml
+++ b/parser/pom.xml
@@ -12,7 +12,7 @@
cn.qaiu
parser
- 10.2.5
+ ${parserVersion}
jar
cn.qaiu:parser
@@ -35,9 +35,9 @@
- scm:git:https://github.com/qaiu/netdisk-fast-download.git
- scm:git:ssh://git@github.com:qaiu/netdisk-fast-download.git
- https://github.com/qaiu/netdisk-fast-download
+ scm:git:https://github.com/${github.owner}/${github.repo}.git
+ scm:git:ssh://git@github.com:${github.owner}/${github.repo}.git
+ https://github.com/${github.owner}/${github.repo}
@@ -52,20 +52,19 @@
- 0.2.1
17
17
17
UTF-8
- 4.5.24
+ 4.5.27
0.10.2
1.18.38
2.0.16
3.18.0
2.18.6
- 1.5.19
+ 1.5.32
4.13.2
@@ -124,6 +123,41 @@
+
+
+ org.codehaus.gmavenplus
+ gmavenplus-plugin
+ 4.1.1
+
+
+ org.apache.groovy
+ groovy
+ 4.0.24
+
+
+
+
+ initialize
+ execute
+
+
+
+
+
+
+
+
+
org.apache.maven.plugins
diff --git a/parser/src/main/java/cn/qaiu/entity/ShareLinkInfo.java b/parser/src/main/java/cn/qaiu/entity/ShareLinkInfo.java
index 138c05a..abdac86 100644
--- a/parser/src/main/java/cn/qaiu/entity/ShareLinkInfo.java
+++ b/parser/src/main/java/cn/qaiu/entity/ShareLinkInfo.java
@@ -86,7 +86,10 @@ public class ShareLinkInfo {
// 将type和shareKey组合成一个字符串作为缓存key
String key = type + ":" + shareKey;
if (type.equals("p115")) {
- key += ("_" + otherParam.get("UA").toString().hashCode());
+ Object ua = otherParam != null ? otherParam.get("UA") : null;
+ if (ua != null) {
+ key += ("_" + ua.toString().hashCode());
+ }
}
return key;
}
diff --git a/parser/src/main/java/cn/qaiu/parser/ParserCreate.java b/parser/src/main/java/cn/qaiu/parser/ParserCreate.java
index 71f0e13..2b743f7 100644
--- a/parser/src/main/java/cn/qaiu/parser/ParserCreate.java
+++ b/parser/src/main/java/cn/qaiu/parser/ParserCreate.java
@@ -81,16 +81,16 @@ public class ParserCreate {
if (shareKey != null) {
shareLinkInfo.setShareKey(shareKey);
}
- } catch (Exception ignored) {}
-
+ } catch (IllegalStateException | IllegalArgumentException ignored) {}
+
// 提取密码
try {
String pwd = matcher.group("PWD");
if (StringUtils.isNotEmpty(pwd)) {
shareLinkInfo.setSharePassword(pwd);
}
- } catch (Exception ignored) {}
-
+ } catch (IllegalStateException | IllegalArgumentException ignored) {}
+
// 设置标准URL
if (customParserConfig.getStandardUrlTemplate() != null) {
String standardUrl = customParserConfig.getStandardUrlTemplate()
@@ -133,7 +133,7 @@ public class ParserCreate {
shareLinkInfo.setSharePassword(pwd);
}
standardUrl = standardUrl.replace("{pwd}", pwd);
- } catch (Exception ignored) {}
+ } catch (IllegalStateException | IllegalArgumentException ignored) {}
shareLinkInfo.setShareUrl(shareUrl);
shareLinkInfo.setShareKey(shareKey);
@@ -266,15 +266,15 @@ public class ParserCreate {
if (shareKey != null) {
shareLinkInfo.setShareKey(shareKey);
}
- } catch (Exception ignored) {}
-
+ } catch (IllegalStateException | IllegalArgumentException ignored) {}
+
try {
String password = matcher.group("PWD");
if (password != null) {
shareLinkInfo.setSharePassword(password);
}
- } catch (Exception ignored) {}
-
+ } catch (IllegalStateException | IllegalArgumentException ignored) {}
+
// 设置标准URL(如果有模板)
if (customConfig.getStandardUrlTemplate() != null) {
String standardUrl = customConfig.getStandardUrlTemplate()
diff --git a/parser/src/main/java/cn/qaiu/parser/customjs/JsHttpClient.java b/parser/src/main/java/cn/qaiu/parser/customjs/JsHttpClient.java
index 0442903..2ada6b0 100644
--- a/parser/src/main/java/cn/qaiu/parser/customjs/JsHttpClient.java
+++ b/parser/src/main/java/cn/qaiu/parser/customjs/JsHttpClient.java
@@ -534,8 +534,8 @@ public class JsHttpClient {
} else {
promise.fail(result.cause());
}
- }).onFailure(Throwable::printStackTrace);
-
+ }).onFailure(e -> log.error("HTTP请求失败", e));
+
// 等待响应完成(使用配置的超时时间)
HttpResponse response = promise.future().toCompletionStage()
.toCompletableFuture()
diff --git a/parser/src/main/java/cn/qaiu/parser/customjs/JsParserExecutor.java b/parser/src/main/java/cn/qaiu/parser/customjs/JsParserExecutor.java
index 69310b4..7226e0b 100644
--- a/parser/src/main/java/cn/qaiu/parser/customjs/JsParserExecutor.java
+++ b/parser/src/main/java/cn/qaiu/parser/customjs/JsParserExecutor.java
@@ -33,7 +33,7 @@ public class JsParserExecutor implements IPanTool, AutoCloseable {
private static final Logger log = LoggerFactory.getLogger(JsParserExecutor.class);
- private static WorkerExecutor EXECUTOR;
+ private static volatile WorkerExecutor EXECUTOR;
private static final Object EXECUTOR_LOCK = new Object();
private static String FETCH_RUNTIME_JS = null;
diff --git a/parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundExecutor.java b/parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundExecutor.java
index e1a31cc..19ae052 100644
--- a/parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundExecutor.java
+++ b/parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundExecutor.java
@@ -355,7 +355,7 @@ public class JsPlaygroundExecutor {
*/
public List getLogs() {
List logs = playgroundLogger.getLogs();
- System.out.println("[JsPlaygroundExecutor] 获取日志,数量: " + logs.size());
+ log.debug("获取日志,数量: {}", logs.size());
return logs;
}
diff --git a/parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundLogger.java b/parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundLogger.java
index a6b2acd..f442e64 100644
--- a/parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundLogger.java
+++ b/parser/src/main/java/cn/qaiu/parser/customjs/JsPlaygroundLogger.java
@@ -4,6 +4,9 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
/**
* 演练场日志收集器
* 收集JavaScript执行过程中的日志信息
@@ -12,8 +15,11 @@ import java.util.List;
* @author QAIU
*/
public class JsPlaygroundLogger {
-
+
+ private static final Logger log = LoggerFactory.getLogger(JsPlaygroundLogger.class);
+
// 使用线程安全的列表
+ private static final int MAX_LOG_SIZE = 1000;
private final List logs = Collections.synchronizedList(new ArrayList<>());
/**
@@ -59,6 +65,18 @@ public class JsPlaygroundLogger {
return obj.toString();
}
+ /**
+ * 添加日志条目,超过最大容量时移除最早的条目
+ */
+ private void addLog(LogEntry entry) {
+ synchronized (logs) {
+ if (logs.size() >= MAX_LOG_SIZE) {
+ logs.remove(0);
+ }
+ logs.add(entry);
+ }
+ }
+
/**
* 记录日志(内部方法)
* @param level 日志级别
@@ -67,8 +85,8 @@ public class JsPlaygroundLogger {
*/
private void log(String level, Object message, String source) {
String msg = toString(message);
- logs.add(new LogEntry(level, msg, source));
- System.out.println("[" + source + "PlaygroundLogger] " + level + ": " + msg);
+ addLog(new LogEntry(level, msg, source));
+ log.debug("[{}PlaygroundLogger] {}: {}", source, level, msg);
}
/**
@@ -111,8 +129,8 @@ public class JsPlaygroundLogger {
if (throwable != null) {
msg = msg + ": " + throwable.getMessage();
}
- logs.add(new LogEntry("ERROR", msg, "JS"));
- System.out.println("[JSPlaygroundLogger] ERROR: " + msg);
+ addLog(new LogEntry("ERROR", msg, "JS"));
+ log.debug("[JSPlaygroundLogger] ERROR: {}", msg);
}
// ===== 以下是供Java层调用的内部方法 =====
@@ -153,8 +171,8 @@ public class JsPlaygroundLogger {
if (throwable != null) {
msg = msg + ": " + throwable.getMessage();
}
- logs.add(new LogEntry("ERROR", msg, "JAVA"));
- System.out.println("[JAVAPlaygroundLogger] ERROR: " + msg);
+ addLog(new LogEntry("ERROR", msg, "JAVA"));
+ log.debug("[JAVAPlaygroundLogger] ERROR: {}", msg);
}
/**
diff --git a/parser/src/main/java/cn/qaiu/parser/customjs/JsScriptLoader.java b/parser/src/main/java/cn/qaiu/parser/customjs/JsScriptLoader.java
index d66b106..a3d665f 100644
--- a/parser/src/main/java/cn/qaiu/parser/customjs/JsScriptLoader.java
+++ b/parser/src/main/java/cn/qaiu/parser/customjs/JsScriptLoader.java
@@ -139,21 +139,20 @@ public class JsScriptLoader {
try {
String jarPath = jarUrl.getPath().substring(5, jarUrl.getPath().indexOf("!"));
- JarFile jarFile = new JarFile(jarPath);
-
- Enumeration entries = jarFile.entries();
- while (entries.hasMoreElements()) {
- JarEntry entry = entries.nextElement();
- String entryName = entry.getName();
-
- if (entryName.startsWith(RESOURCE_PATH + "/") &&
- entryName.endsWith(".js") &&
- !isExcludedFile(entryName.substring(entryName.lastIndexOf('/') + 1))) {
- resourceFiles.add(entryName);
+
+ try (JarFile jarFile = new JarFile(jarPath)) {
+ Enumeration entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ String entryName = entry.getName();
+
+ if (entryName.startsWith(RESOURCE_PATH + "/") &&
+ entryName.endsWith(".js") &&
+ !isExcludedFile(entryName.substring(entryName.lastIndexOf('/') + 1))) {
+ resourceFiles.add(entryName);
+ }
}
}
-
- jarFile.close();
} catch (Exception e) {
log.debug("解析JAR包资源文件失败", e);
}
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/FjTool.java b/parser/src/main/java/cn/qaiu/parser/impl/FjTool.java
index 74e6546..6b991b4 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/FjTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/FjTool.java
@@ -109,9 +109,9 @@ public class FjTool extends PanBase {
// String uuid = UUID.randomUUID().toString().toLowerCase(); // 也可以使用 UUID.randomUUID().toString()
- static String token = null;
- static String userId = null;
- public static boolean authFlag = true;
+ static volatile String token = null;
+ static volatile String userId = null;
+ public static volatile boolean authFlag = true;
public FjTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
@@ -289,12 +289,14 @@ public class FjTool extends PanBase {
JsonObject json = asJson(res2);
if (json.getInteger("code") == 200) {
token = json.getJsonObject("data").getString("appToken");
- header0.set("appToken", token);
- log.info("登录成功 token: {}", token);
+ MultiMap h0 = MultiMap.caseInsensitiveMultiMap();
+ h0.addAll(header0);
+ h0.set("appToken", token);
+ log.info("登录成功 token: {}...", token != null ? token.substring(0, Math.min(8, token.length())) : "null");
client.postAbs(UriTemplate.of(TOKEN_VERIFY_URL))
.setTemplateParam("uuid", uuid)
.setTemplateParam("ts", tsEncode2)
- .putHeaders(header0).send().onSuccess(res -> {
+ .putHeaders(h0).send().onSuccess(res -> {
if (asJson(res).getInteger("code") == 200) {
if (FjTool.userId == null) {
FjTool.userId = asJson(res).getJsonObject("map").getString("userId");
@@ -454,7 +456,10 @@ public class FjTool extends PanBase {
// 如果参数里的目录ID不为空,则直接解析目录
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
if (dirId != null && !dirId.isEmpty()) {
- uuid = shareLinkInfo.getOtherParam().get("uuid").toString();
+ Object uuidObj = shareLinkInfo.getOtherParam().get("uuid");
+ if (uuidObj != null) {
+ uuid = uuidObj.toString();
+ }
parserDir(dirId, shareId, promise0);
return promise0.future();
}
@@ -495,7 +500,7 @@ public class FjTool extends PanBase {
JsonArray list;
try {
JsonObject jsonObject = asJson(res);
- System.out.println(jsonObject.encodePrettily());
+ log.debug("目录列表: {}", jsonObject.encodePrettily());
list = jsonObject.getJsonArray("list");
} catch (Exception e) {
log.error("解析目录失败: {}", res.bodyAsString());
@@ -576,6 +581,10 @@ public class FjTool extends PanBase {
// 第二次请求
JsonObject paramJson = (JsonObject)shareLinkInfo.getOtherParam().get("paramJson");
+ if (paramJson == null) {
+ promise.fail("缺少 paramJson 参数");
+ return promise.future();
+ }
clientNoRedirects.getAbs(UriTemplate.of(SECOND_REQUEST_URL_VIP))
.setTemplateParam("fidEncode", paramJson.getString("fidEncode"))
.setTemplateParam("uuid", paramJson.getString("uuid"))
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/FsTool.java b/parser/src/main/java/cn/qaiu/parser/impl/FsTool.java
index 1f0c09b..ad687e2 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/FsTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/FsTool.java
@@ -389,6 +389,10 @@ public class FsTool extends PanBase {
try {
JsonObject paramJson = (JsonObject) shareLinkInfo.getOtherParam().get("paramJson");
+ if (paramJson == null) {
+ parsePromise.fail("缺少 paramJson 参数");
+ return parsePromise.future();
+ }
String shareUrl = paramJson.getString("shareUrl");
String objToken = paramJson.getString("objToken");
String tenant = extractTenant(shareUrl);
@@ -444,7 +448,7 @@ public class FsTool extends PanBase {
if (m1.find()) {
try {
return URLDecoder.decode(m1.group(1).trim(), StandardCharsets.UTF_8);
- } catch (Exception ignored) {
+ } catch (IllegalArgumentException ignored) {
}
}
@@ -453,7 +457,7 @@ public class FsTool extends PanBase {
if (m2.find()) {
try {
return URLDecoder.decode(m2.group(1).trim(), StandardCharsets.UTF_8);
- } catch (Exception ignored) {
+ } catch (IllegalArgumentException ignored) {
}
}
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/IzTool.java b/parser/src/main/java/cn/qaiu/parser/impl/IzTool.java
index d8c9ac8..c135845 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/IzTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/IzTool.java
@@ -89,8 +89,8 @@ public class IzTool extends PanBase {
String uuid = UUID.randomUUID().toString().toLowerCase(); // 也可以使用 UUID.randomUUID().toString()
- public static String token = null;
- public static boolean authFlag = true;
+ public static volatile String token = null;
+ public static volatile boolean authFlag = true;
public Future parse() {
@@ -101,8 +101,8 @@ public class IzTool extends PanBase {
// 检查并输出认证状态
if (shareLinkInfo.getOtherParam().containsKey("auths")) {
boolean isTempAuth = shareLinkInfo.getOtherParam().containsKey("__TEMP_AUTH_ADDED");
- log.info("文件解析检测到认证信息: isTempAuth={}, authFlag={}, token={}",
- isTempAuth, authFlag, token != null ? "已登录(" + token.substring(0, Math.min(10, token.length())) + "...)" : "未登录");
+ log.info("文件解析检测到认证信息: isTempAuth={}, authFlag={}, token={}",
+ isTempAuth, authFlag, token != null ? "已登录(" + token.substring(0, Math.min(8, token.length())) + "...)" : "未登录");
// 如果需要认证但还没有token,先执行登录
if ((isTempAuth || authFlag) && token == null) {
@@ -118,7 +118,7 @@ public class IzTool extends PanBase {
// 登录失败,继续使用免登录模式
});
} else if (token != null) {
- log.info("文件解析使用已有token: {}...", token.substring(0, Math.min(10, token.length())));
+ log.info("文件解析使用已有token: {}...", token.substring(0, Math.min(8, token.length())));
}
} else {
log.debug("文件解析无认证信息,使用免登录模式");
@@ -247,7 +247,7 @@ public class IzTool extends PanBase {
log.warn("登录失败: {}", failRes.getMessage());
fail(failRes.getMessage());
}).onSuccess(r-> {
- httpRequest.setTemplateParam("appToken", header.get("appToken"))
+ httpRequest.setTemplateParam("appToken", token)
.putHeaders(header);
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
});
@@ -263,12 +263,12 @@ public class IzTool extends PanBase {
log.warn("重新登录失败: {}", failRes.getMessage());
fail(failRes.getMessage());
}).onSuccess(r-> {
- httpRequest.setTemplateParam("appToken", header.get("appToken"))
+ httpRequest.setTemplateParam("appToken", token)
.putHeaders(header);
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
});
} else {
- httpRequest.setTemplateParam("appToken", header.get("appToken"))
+ httpRequest.setTemplateParam("appToken", token)
.putHeaders(header);
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
}
@@ -311,8 +311,7 @@ public class IzTool extends PanBase {
JsonObject json = asJson(res2);
if (json.getInteger("code") == 200) {
token = json.getJsonObject("data").getString("appToken");
- header.set("appToken", token);
- log.info("登录成功 token: {}", token);
+ log.info("登录成功 token: {}...", token != null ? token.substring(0, Math.min(8, token.length())) : "null");
promise1.complete();
} else {
// 检查是否为临时认证
@@ -463,7 +462,10 @@ public class IzTool extends PanBase {
// 如果参数里的目录ID不为空,则直接解析目录
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
if (dirId != null && !dirId.isEmpty()) {
- uuid = shareLinkInfo.getOtherParam().get("uuid").toString();
+ Object uuidObj = shareLinkInfo.getOtherParam().get("uuid");
+ if (uuidObj != null) {
+ uuid = uuidObj.toString();
+ }
parserDir(dirId, shareId, promise);
return promise.future();
}
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/IzToolWithAuth.java b/parser/src/main/java/cn/qaiu/parser/impl/IzToolWithAuth.java
index 459e8d7..074d506 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/IzToolWithAuth.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/IzToolWithAuth.java
@@ -88,8 +88,8 @@ public class IzToolWithAuth extends PanBase {
String uuid = UUID.randomUUID().toString().toLowerCase(); // 也可以使用 UUID.randomUUID().toString()
- public static String token = null;
- public static boolean authFlag = true;
+ public static volatile String token = null;
+ public static volatile boolean authFlag = true;
public Future parse() {
@@ -216,7 +216,7 @@ public class IzToolWithAuth extends PanBase {
log.warn("登录失败: {}", failRes.getMessage());
fail(failRes.getMessage());
}).onSuccess(r-> {
- httpRequest.setTemplateParam("appToken", header.get("appToken"))
+ httpRequest.setTemplateParam("appToken", token)
.putHeaders(header);
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
});
@@ -232,12 +232,12 @@ public class IzToolWithAuth extends PanBase {
log.warn("重新登录失败: {}", failRes.getMessage());
fail(failRes.getMessage());
}).onSuccess(r-> {
- httpRequest.setTemplateParam("appToken", header.get("appToken"))
+ httpRequest.setTemplateParam("appToken", token)
.putHeaders(header);
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
});
} else {
- httpRequest.setTemplateParam("appToken", header.get("appToken"))
+ httpRequest.setTemplateParam("appToken", token)
.putHeaders(header);
httpRequest.send().onSuccess(this::down).onFailure(handleFail("请求2"));
}
@@ -280,8 +280,7 @@ public class IzToolWithAuth extends PanBase {
JsonObject json = asJson(res2);
if (json.getInteger("code") == 200) {
token = json.getJsonObject("data").getString("appToken");
- header.set("appToken", token);
- log.info("登录成功 token: {}", token);
+ log.info("登录成功 token: {}...", token != null ? token.substring(0, Math.min(8, token.length())) : "null");
promise1.complete();
} else {
// 检查是否为临时认证
@@ -432,7 +431,8 @@ public class IzToolWithAuth extends PanBase {
// 如果参数里的目录ID不为空,则直接解析目录
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
if (dirId != null && !dirId.isEmpty()) {
- uuid = shareLinkInfo.getOtherParam().get("uuid").toString();
+ Object uuidObj = shareLinkInfo.getOtherParam().get("uuid");
+ uuid = uuidObj != null ? uuidObj.toString() : null;
parserDir(dirId, shareId, promise);
return promise.future();
}
@@ -480,7 +480,7 @@ public class IzToolWithAuth extends PanBase {
requestDirList(id, shareId, tsEncode, promise);
})
.onSuccess(r -> {
- log.info("目录解析登录成功,token={}, 使用 VIP 模式", token != null ? token.substring(0, 10) + "..." : "null");
+ log.info("目录解析登录成功,token={}, 使用 VIP 模式", token != null ? token.substring(0, Math.min(8, token.length())) + "..." : "null");
requestDirList(id, shareId, tsEncode, promise);
});
return;
@@ -627,7 +627,7 @@ public class IzToolWithAuth extends PanBase {
// 如果有 token,使用 VIP 接口
if (StringUtils.isNotBlank(appToken)) {
- log.debug("parseById 使用 VIP 接口, appToken={}", appToken.substring(0, Math.min(10, appToken.length())) + "...");
+ log.debug("parseById 使用 VIP 接口, appToken={}", appToken.substring(0, Math.min(8, appToken.length())) + "...");
webClientSession.getAbs(UriTemplate.of(SECOND_REQUEST_URL_VIP))
.putHeaders(header)
.setTemplateParam("fidEncode", paramJson.getString("fidEncode"))
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java b/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java
index c87bb96..4a2e2dc 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/LzTool.java
@@ -14,6 +14,7 @@ import io.vertx.ext.web.client.WebClientSession;
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
import javax.script.ScriptException;
+import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -107,7 +108,7 @@ public class LzTool extends PanBase {
try {
setFileInfo(html, shareLinkInfo);
} catch (Exception e) {
- e.printStackTrace();
+ log.error("文件信息解析异常", e);
}
// 匹配iframe
Pattern compile = Pattern.compile("src=\"(/fn\\?[a-zA-Z\\d_+/=]{16,})\"");
@@ -175,7 +176,7 @@ public class LzTool extends PanBase {
if (firstDot >= 0) {
domain = host.substring(firstDot); // e.g. ".lanzoum.com"
}
- } catch (Exception ignored) {}
+ } catch (MalformedURLException ignored) {}
// 创建一个 Cookie 并放入 CookieStore
DefaultCookie nettyCookie = new DefaultCookie("acw_sc__v2", acw_sc__v2);
nettyCookie.setDomain(domain);
@@ -217,7 +218,7 @@ public class LzTool extends PanBase {
return;
}
Map, ?> signMap = (Map, ?>)obj.get("data");
- String url0 = obj.get("url").toString();
+ String url0 = String.valueOf(obj.get("url"));
MultiMap map = MultiMap.caseInsensitiveMultiMap();
signMap.forEach((k, v) -> {
map.add((String) k, v.toString());
@@ -275,7 +276,7 @@ public class LzTool extends PanBase {
String h = du.getHost();
int dot = h.indexOf('.');
if (dot >= 0) downDomain = h.substring(dot);
- } catch (Exception ignored) {}
+ } catch (MalformedURLException ignored) {}
// 创建一个 Cookie 并放入 CookieStore
DefaultCookie nettyCookie = new DefaultCookie("acw_sc__v2", acw_sc__v2);
nettyCookie.setDomain(downDomain);
@@ -290,12 +291,12 @@ public class LzTool extends PanBase {
if (location0 == null) {
fail(downUrl + " -> 直链获取失败2, 可能分享已失效");
} else {
- setDateAndComplate(location0);
+ setDateAndComplete(location0);
}
}).onFailure(handleFail(downUrl));
return;
}
- setDateAndComplate(location);
+ setDateAndComplete(location);
})
.onFailure(handleFail(downUrl));
} catch (Exception e) {
@@ -304,7 +305,7 @@ public class LzTool extends PanBase {
}).onFailure(handleFail(url));
}
- private void setDateAndComplate(String location0) {
+ private void setDateAndComplete(String location0) {
// 分享时间 提取url中的时间戳格式:lanzoui.com/abc/abc/yyyy/mm/dd/
String regex = "(\\d{4}/\\d{1,2}/\\d{1,2})";
Matcher matcher = Pattern.compile(regex).matcher(location0);
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/MkgsTool.java b/parser/src/main/java/cn/qaiu/parser/impl/MkgsTool.java
index 1182c9f..cb01165 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/MkgsTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/MkgsTool.java
@@ -86,10 +86,10 @@ public class MkgsTool extends PanBase {
// 查找并输出 hash 字段的值
if (matcher.find()) {
String hashValue = matcher.group(1); // 获取第一个捕获组
- System.out.println(hashValue);
+ log.debug("hash: {}", hashValue);
client.getAbs(UriTemplate.of(API_URL)).setTemplateParam("hash", hashValue).send().onSuccess(res3 -> {
JsonObject jsonObject = asJson(res3);
- System.out.println(jsonObject.encodePrettily());
+ log.debug("API response: {}", jsonObject.encodePrettily());
if (jsonObject.containsKey("url")) {
promise.complete(jsonObject.getString("url"));
} else {
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/MkwTool.java b/parser/src/main/java/cn/qaiu/parser/impl/MkwTool.java
index df856dd..5686703 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/MkwTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/MkwTool.java
@@ -29,19 +29,19 @@ public class MkwTool extends PanBase {
clientSession.getAbs(shareUrl).send().onSuccess(result -> {
String cookie = result.headers().get("set-cookie");
- if (!cookie.isEmpty()) {
+ if (cookie != null && !cookie.isEmpty()) {
String regex = "([A-Za-z0-9_]+)=([A-Za-z0-9]+)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(cookie);
if (matcher.find()) {
- System.out.println(matcher.group(1));
- System.out.println(matcher.group(2));
+ log.debug("cookie key: {}", matcher.group(1));
+ log.debug("cookie value: {}", matcher.group(2));
var key = matcher.group(1);
var token = matcher.group(2);
String sign = JsExecUtils.getKwSign(token, key);
- System.out.println(sign);
+ log.debug("sign: {}", sign);
clientSession.getAbs(UriTemplate.of(API_URL)).setTemplateParam("mid", shareLinkInfo.getShareKey())
.putHeader("Secret", sign).send().onSuccess(res -> {
JsonObject json = asJson(res);
@@ -54,7 +54,7 @@ public class MkwTool extends PanBase {
}
} catch (Exception e) {
- e.printStackTrace();
+ log.error("解析失败", e);
fail("解析失败");
}
});
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/P115Tool.java b/parser/src/main/java/cn/qaiu/parser/impl/P115Tool.java
index a02c586..0e3a23f 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/P115Tool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/P115Tool.java
@@ -21,6 +21,8 @@ public class P115Tool extends PanBase {
private static final String SECOND_REQUEST_URL = API_URL_PREFIX + "share/skip_login_downurl";
+ private static final String DEFAULT_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
+
private static final MultiMap header;
static {
@@ -49,9 +51,11 @@ public class P115Tool extends PanBase {
public Future parse() {
// 第一次请求 获取文件信息
+ Object uaObj = shareLinkInfo.getOtherParam().get("UA");
+ String ua = uaObj != null ? uaObj.toString() : DEFAULT_UA;
client.getAbs(UriTemplate.of(FIRST_REQUEST_URL))
.putHeaders(header)
- .putHeader("User-Agent", shareLinkInfo.getOtherParam().get("UA").toString())
+ .putHeader("User-Agent", ua)
.setTemplateParam("dataKey", shareLinkInfo.getShareKey())
.setTemplateParam("dataPwd", shareLinkInfo.getSharePassword())
.send().onSuccess(res -> {
@@ -68,7 +72,7 @@ public class P115Tool extends PanBase {
// share_code={dataKey}&receive_code={dataPwd}&file_id={file_id}
client.postAbs(SECOND_REQUEST_URL)
.putHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
- .putHeader("User-Agent", shareLinkInfo.getOtherParam().get("UA").toString())
+ .putHeader("User-Agent", ua)
.sendForm(MultiMap.caseInsensitiveMultiMap()
.set("share_code", shareLinkInfo.getShareKey())
.set("receive_code", shareLinkInfo.getSharePassword())
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/PdbTool.java b/parser/src/main/java/cn/qaiu/parser/impl/PdbTool.java
index 9edac9d..1561b1c 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/PdbTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/PdbTool.java
@@ -85,7 +85,7 @@ public class PdbTool extends PanBase implements IPanTool {
})
.onFailure(handleFail());
} catch (Exception e) {
- e.printStackTrace();
+ log.error("URL编码异常", e);
}
})
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/PodTool.java b/parser/src/main/java/cn/qaiu/parser/impl/PodTool.java
index 042e1ec..c8d8c0f 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/PodTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/PodTool.java
@@ -129,7 +129,7 @@ public class PodTool extends PanBase {
if (urlMatcher.find()) {
String url = urlMatcher.group("url");
- System.out.println("URL: " + url);
+ log.debug("URL: {}", url);
return url;
}
throw new RuntimeException("URL匹配失败");
@@ -172,7 +172,7 @@ public class PodTool extends PanBase {
if (tokenMatcher.find()) {
String token = tokenMatcher.group(1);
- System.out.println("Token: " + token);
+ log.debug("Token: {}***", token.length() > 4 ? token.substring(0, 4) : "***");
return token;
}
throw new RuntimeException("token匹配失败");
@@ -198,8 +198,8 @@ public class PodTool extends PanBase {
// 发送请求并处理响应
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(response -> {
- System.out.println("Response Status Code: " + response.statusCode());
- System.out.println("Response Body: " + response.body());
+ log.debug("Response Status Code: {}", response.statusCode());
+ log.debug("Response Body: {}", response.body());
promise.complete(response.body());
return null;
});
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/QQTool.java b/parser/src/main/java/cn/qaiu/parser/impl/QQTool.java
index f5e8cd7..fe6022d 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/QQTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/QQTool.java
@@ -74,9 +74,9 @@ public class QQTool extends PanBase {
});
// 调试匹配的情况
- System.out.println("文件名称: " + filename);
- System.out.println("文件大小: " + filesize);
- System.out.println("文件直链: " + fileurl);
+ log.debug("文件名称: {}", filename);
+ log.debug("文件大小: {}", filesize);
+ log.debug("文件直链: {}", fileurl);
// 提交
promise.complete(fileurl.replace("\\x26", "&"));
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/QQscTool.java b/parser/src/main/java/cn/qaiu/parser/impl/QQscTool.java
index 856a46a..a92534d 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/QQscTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/QQscTool.java
@@ -3,9 +3,11 @@ package cn.qaiu.parser.impl;
import cn.qaiu.entity.FileInfo;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
+import cn.qaiu.util.CommonUtils;
import cn.qaiu.util.HeaderUtils;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
+import io.vertx.core.Promise;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import org.slf4j.Logger;
@@ -13,27 +15,33 @@ import org.slf4j.LoggerFactory;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* QQ闪传
- * 只能客户端上传 支持Android QQ 9.2.5, MACOS QQ 6.9.78,可生成分享链接,通过浏览器下载,支持超大文件,有效期默认7天(暂时没找到续期方法)。
+ * 支持多文件、多级目录解析。通过 GetFileList API 获取文件列表,BatchDownload API 获取下载直链。
+ * 有效期默认7天。
*/
public class QQscTool extends PanBase {
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-Language: zh-CN,zh;q=0.9
Connection: keep-alive
Cookie: uin=9000002; p_uin=9000002
DNT: 1
Origin: https://qfile.qq.com
- Referer: https://qfile.qq.com/q/Xolxtv5b4O
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
@@ -46,86 +54,257 @@ public class QQscTool extends PanBase {
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) {
super(shareLinkInfo);
}
+ @Override
public Future 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 -> {
- if (result.succeeded()) {
- 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 {
+ if (result.failed()) {
LOG.error("请求失败: {}", result.cause().getMessage());
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();
}
+ @Override
+ public Future> parseFileList() {
+ Promise> 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 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 parseById() {
+ JsonObject paramJson = (JsonObject) shareLinkInfo.getOtherParam().get("paramJson");
+ String fileId = paramJson.getString("fileId");
+ String fileName = paramJson.getString("fileName");
+
+ Promise 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 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 fetchFileList(String filesetId, String parentId) {
+ Promise 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 keyword = "\"download_limit_status\"";
String marker = "},\"";
@@ -140,32 +319,23 @@ public class QQscTool extends PanBase {
String extracted = htmlJs.substring(quoteStart, quoteEnd);
LOG.debug("提取结果: {}", extracted);
return extracted;
- } else {
- LOG.error("未找到结束引号: {}", marker);
}
- } else {
- LOG.error("未找到标记: {} 在关键字: {} 之后", marker, keyword);
}
- } else {
- LOG.error("未找到关键字: {}", keyword);
}
return null;
}
public static String extractFileNameFromTitle(String content) {
- // 匹配和之间的内容
Pattern pattern = Pattern.compile("(.*?)");
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
String fullTitle = matcher.group(1);
- // 按 "|" 分割,取前半部分
int sepIndex = fullTitle.indexOf("|");
if (sepIndex != -1) {
return fullTitle.substring(0, sepIndex);
}
- return fullTitle; // 如果没有分隔符,就返回全部
+ return fullTitle;
}
return null;
}
}
-
diff --git a/parser/src/main/java/cn/qaiu/parser/impl/WsTool.java b/parser/src/main/java/cn/qaiu/parser/impl/WsTool.java
index e10c9dd..d7b0f51 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/WsTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/WsTool.java
@@ -67,11 +67,7 @@ public class WsTool extends PanBase {
String filepid = asJson(res2).getJsonObject("data").getString("ufileid"); // 文件夹pid
String filebid = asJson(res2).getJsonObject("data").getString("boxid"); // 文件夹bid
- // 调试输出文件夹信息
- System.out.println("文件夹期限: " + filetime);
- System.out.println("文件夹大小: " + filesize);
- System.out.println("文件夹pid: " + filepid);
- System.out.println("文件夹bid: " + filebid);
+ log.debug("文件夹期限: {}, 大小: {}, pid: {}, bid: {}", filetime, filesize, filepid, filebid);
// 获取文件信息
httpClient.postAbs(SHARE_URL_API + "ufile/list").putHeaders(headers)
@@ -97,9 +93,7 @@ public class WsTool extends PanBase {
String filefid = asJson(res3).getJsonObject("data")
.getJsonArray("fileList").getJsonObject(0).getString("fid"); // 文件fid
- // 调试输出文件信息
- System.out.println("文件名称: " + filename);
- System.out.println("文件fid: " + filefid);
+ log.debug("文件名称: {}, fid: {}", filename, filefid);
// 检查文件是否失效
httpClient.postAbs(SHARE_URL_API + "dl/sign").putHeaders(headers)
@@ -114,8 +108,7 @@ public class WsTool extends PanBase {
// 获取直链
String fileurl = asJson(res4).getJsonObject("data").getString("url");
- // 调试输出文件直链
- System.out.println("文件直链: " + fileurl);
+ log.debug("文件直链: {}", fileurl);
if (!fileurl.equals("")) {
promise.complete(URLDecoder.decode(fileurl, StandardCharsets.UTF_8));
diff --git a/parser/src/main/java/cn/qaiu/util/AESUtils.java b/parser/src/main/java/cn/qaiu/util/AESUtils.java
index 2fc41c7..a3a42b0 100644
--- a/parser/src/main/java/cn/qaiu/util/AESUtils.java
+++ b/parser/src/main/java/cn/qaiu/util/AESUtils.java
@@ -14,7 +14,6 @@ import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.HexFormat;
-import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -299,7 +298,7 @@ public class AESUtils {
//length用户要求产生字符串的长度
public static String getRandomString(int length){
String str="abcdefghijklmnopqrstuvwxyz0123456789";
- Random random=new Random();
+ SecureRandom random=new SecureRandom();
StringBuilder sb=new StringBuilder();
for(int i=0;i getURLParams(String url) throws MalformedURLException {
URL fullUrl = new URL(url);
String query = fullUrl.getQuery();
+ if (query == null || query.isEmpty()) {
+ return new HashMap<>();
+ }
String[] params = query.split("&");
Map map = new HashMap<>();
for (String param : params) {
diff --git a/parser/src/main/java/cn/qaiu/util/IpExtractor.java b/parser/src/main/java/cn/qaiu/util/IpExtractor.java
index f8bc7ea..186a8cd 100644
--- a/parser/src/main/java/cn/qaiu/util/IpExtractor.java
+++ b/parser/src/main/java/cn/qaiu/util/IpExtractor.java
@@ -5,6 +5,8 @@ import io.vertx.core.Vertx;
import io.vertx.core.http.impl.headers.HeadersMultiMap;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
@@ -15,6 +17,8 @@ import java.util.ArrayList;
import java.util.List;
public class IpExtractor {
+ private static final Logger log = LoggerFactory.getLogger(IpExtractor.class);
+
public static void main(String[] args) throws InterruptedException {
@@ -42,9 +46,9 @@ public class IpExtractor {
WebClient client = WebClient.create(Vertx.vertx());
WebClientSession webClientSession = WebClientSession.create(client);
webClientSession.getAbs("https://ip.ihuan.me").putHeaders(headers).send().onSuccess(res->{
- System.out.println(res.toString());
+ log.debug("response: {}", res.toString());
webClientSession.getAbs("https://ip.ihuan.me").putHeaders(headers).send().onSuccess(res2->{
- System.out.println(res2.toString());
+ log.debug("response2: {}", res2.toString());
});
});
diff --git a/parser/src/main/java/cn/qaiu/util/ReqIpUtil.java b/parser/src/main/java/cn/qaiu/util/ReqIpUtil.java
index 84384ce..bfaab33 100644
--- a/parser/src/main/java/cn/qaiu/util/ReqIpUtil.java
+++ b/parser/src/main/java/cn/qaiu/util/ReqIpUtil.java
@@ -8,15 +8,19 @@ import io.vertx.core.http.impl.headers.HeadersMultiMap;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class ReqIpUtil {
- public static String BASE_URL = "https://ip.ihuan.me";
- public static String BASE_URL_TEMPLATE = BASE_URL + "/{path}";
+ private static final Logger log = LoggerFactory.getLogger(ReqIpUtil.class);
+
+ public static final String BASE_URL = "https://ip.ihuan.me";
+ public static final String BASE_URL_TEMPLATE = BASE_URL + "/{path}";
// GET https://ip.ihuan.me/mouse.do -> $("input[name='key']").val("30b4975b5547fed806bd2b9caa18485a");
- public static String PATH1 = "mouse.do";
+ public static final String PATH1 = "mouse.do";
- public static String PATH2 = "tqdl.html";
+ public static final String PATH2 = "tqdl.html";
// 创建请求头Map
static MultiMap headers = new HeadersMultiMap();
@@ -58,15 +62,15 @@ public class ReqIpUtil {
void next(AsyncResult> response) {
if (response.failed()) {
- response.cause().printStackTrace();
+ log.error("请求失败", response.cause());
} else {
HttpResponse res = response.result();
- System.out.println("Received response with status code " + res.statusCode());
- System.out.println("Body: " + res.body());
+ log.debug("Received response with status code {}", res.statusCode());
+ log.debug("Body: {}", res.body());
webClientSession.getAbs(BASE_URL_TEMPLATE).setTemplateParam("path", PATH1)
.putHeaders(headers) // 将请求头Map添加到请求中
.send(response2 -> {
- System.out.println(response2.result().bodyAsString());
+ log.debug("response2: {}", response2.result().bodyAsString());
});
}
diff --git a/parser/src/main/java/cn/qaiu/util/URLUtil.java b/parser/src/main/java/cn/qaiu/util/URLUtil.java
index 916a27f..a8b7bb1 100644
--- a/parser/src/main/java/cn/qaiu/util/URLUtil.java
+++ b/parser/src/main/java/cn/qaiu/util/URLUtil.java
@@ -2,6 +2,9 @@ package cn.qaiu.util;
import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
@@ -10,6 +13,8 @@ import java.util.Map;
public class URLUtil {
+ private static final Logger LOGGER = LoggerFactory.getLogger(URLUtil.class);
+
private final Map queryParams = new HashMap<>();
// 构造函数,传入URL并解析参数
@@ -31,7 +36,7 @@ public class URLUtil {
}
}
} catch (Exception e) {
- e.printStackTrace();
+ LOGGER.error("URL解析失败: {}", url, e);
}
}
diff --git a/parser/src/test/java/cn/qaiu/parser/BaiduPhotoParserTest.java b/parser/src/test/java/cn/qaiu/parser/BaiduPhotoParserTest.java
index ee6bde0..1618f77 100644
--- a/parser/src/test/java/cn/qaiu/parser/BaiduPhotoParserTest.java
+++ b/parser/src/test/java/cn/qaiu/parser/BaiduPhotoParserTest.java
@@ -8,6 +8,8 @@ import cn.qaiu.parser.customjs.JsParserExecutor;
import cn.qaiu.WebClientVertxInit;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import java.util.HashMap;
@@ -22,15 +24,26 @@ import java.util.Map;
*/
public class BaiduPhotoParserTest {
+ private Vertx vertx;
+
+ @Before
+ public void setUp() {
+ vertx = Vertx.vertx();
+ WebClientVertxInit.init(vertx);
+ }
+
+ @After
+ public void tearDown() {
+ if (vertx != null) {
+ vertx.close();
+ }
+ }
+
@Test
public void testBaiduPhotoParserRegistration() {
// 清理注册表
CustomParserRegistry.clear();
-
- // 初始化Vertx
- Vertx vertx = Vertx.vertx();
- WebClientVertxInit.init(vertx);
-
+
// 检查是否加载了百度相册解析器
CustomParserConfig config = CustomParserRegistry.get("baidu_photo");
assert config != null : "百度相册解析器未加载";
@@ -44,11 +57,7 @@ public class BaiduPhotoParserTest {
public void testBaiduPhotoFileShareExecution() {
// 清理注册表
CustomParserRegistry.clear();
-
- // 初始化Vertx
- Vertx vertx = Vertx.vertx();
- WebClientVertxInit.init(vertx);
-
+
try {
// 创建解析器 - 测试文件分享链接
IPanTool tool = ParserCreate.fromType("baidu_photo")
@@ -76,11 +85,7 @@ public class BaiduPhotoParserTest {
public void testBaiduPhotoFolderShareExecution() {
// 清理注册表
CustomParserRegistry.clear();
-
- // 初始化Vertx
- Vertx vertx = Vertx.vertx();
- WebClientVertxInit.init(vertx);
-
+
try {
// 创建解析器 - 测试文件夹分享链接
IPanTool tool = ParserCreate.fromType("baidu_photo")
@@ -108,11 +113,7 @@ public class BaiduPhotoParserTest {
public void testBaiduPhotoParserFileList() {
// 清理注册表
CustomParserRegistry.clear();
-
- // 初始化Vertx
- Vertx vertx = Vertx.vertx();
- WebClientVertxInit.init(vertx);
-
+
try {
IPanTool tool = ParserCreate.fromType("baidu_photo")
// 分享key PPgOEodBVE
@@ -166,11 +167,7 @@ public class BaiduPhotoParserTest {
public void testBaiduPhotoParserById() {
// 清理注册表
CustomParserRegistry.clear();
-
- // 初始化Vertx
- Vertx vertx = Vertx.vertx();
- WebClientVertxInit.init(vertx);
-
+
try {
// 创建ShareLinkInfo
Map otherParam = new HashMap<>();
diff --git a/parser/src/test/java/cn/qaiu/parser/JsParserTest.java b/parser/src/test/java/cn/qaiu/parser/JsParserTest.java
index 88f2a43..c738f90 100644
--- a/parser/src/test/java/cn/qaiu/parser/JsParserTest.java
+++ b/parser/src/test/java/cn/qaiu/parser/JsParserTest.java
@@ -7,6 +7,8 @@ import cn.qaiu.parser.custom.CustomParserRegistry;
import cn.qaiu.parser.customjs.JsParserExecutor;
import cn.qaiu.WebClientVertxInit;
import io.vertx.core.Vertx;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import java.util.HashMap;
@@ -21,15 +23,26 @@ import java.util.Map;
*/
public class JsParserTest {
+ private Vertx vertx;
+
+ @Before
+ public void setUp() {
+ vertx = Vertx.vertx();
+ WebClientVertxInit.init(vertx);
+ }
+
+ @After
+ public void tearDown() {
+ if (vertx != null) {
+ vertx.close();
+ }
+ }
+
@Test
public void testJsParserRegistration() {
// 清理注册表
CustomParserRegistry.clear();
-
- // 初始化Vertx
- Vertx vertx = Vertx.vertx();
- WebClientVertxInit.init(vertx);
-
+
// 检查是否加载了JavaScript解析器
CustomParserConfig config = CustomParserRegistry.get("demo_js");
assert config != null : "JavaScript解析器未加载";
@@ -43,11 +56,7 @@ public class JsParserTest {
public void testJsParserExecution() {
// 清理注册表
CustomParserRegistry.clear();
-
- // 初始化Vertx
- Vertx vertx = Vertx.vertx();
- WebClientVertxInit.init(vertx);
-
+
try {
// 创建解析器
IPanTool tool = ParserCreate.fromType("demo_js")
@@ -74,11 +83,7 @@ public class JsParserTest {
public void testJsParserFileList() {
// 清理注册表
CustomParserRegistry.clear();
-
- // 初始化Vertx
- Vertx vertx = Vertx.vertx();
- WebClientVertxInit.init(vertx);
-
+
try {
// 创建解析器
IPanTool tool = ParserCreate.fromType("demo_js")
@@ -114,11 +119,7 @@ public class JsParserTest {
public void testJsParserById() {
// 清理注册表
CustomParserRegistry.clear();
-
- // 初始化Vertx
- Vertx vertx = Vertx.vertx();
- WebClientVertxInit.init(vertx);
-
+
try {
// 创建ShareLinkInfo
Map otherParam = new HashMap<>();
diff --git a/parser/src/test/java/cn/qaiu/parser/customjs/JsFetchBridgeTest.java b/parser/src/test/java/cn/qaiu/parser/customjs/JsFetchBridgeTest.java
index 527a954..47392ff 100644
--- a/parser/src/test/java/cn/qaiu/parser/customjs/JsFetchBridgeTest.java
+++ b/parser/src/test/java/cn/qaiu/parser/customjs/JsFetchBridgeTest.java
@@ -7,6 +7,8 @@ import cn.qaiu.parser.ParserCreate;
import cn.qaiu.parser.custom.CustomParserConfig;
import cn.qaiu.parser.custom.CustomParserRegistry;
import io.vertx.core.Vertx;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -16,18 +18,29 @@ import org.slf4j.LoggerFactory;
* 测试fetch API和Promise polyfill功能
*/
public class JsFetchBridgeTest {
-
+
private static final Logger log = LoggerFactory.getLogger(JsFetchBridgeTest.class);
-
+
+ private Vertx vertx;
+
+ @Before
+ public void setUp() {
+ vertx = Vertx.vertx();
+ WebClientVertxInit.init(vertx);
+ }
+
+ @After
+ public void tearDown() {
+ if (vertx != null) {
+ vertx.close();
+ }
+ }
+
@Test
public void testFetchPolyfillLoaded() {
- // 初始化Vertx
- Vertx vertx = Vertx.vertx();
- WebClientVertxInit.init(vertx);
-
// 清理注册表
CustomParserRegistry.clear();
-
+
// 创建一个简单的解析器配置
String jsCode = """
// 测试Promise是否可用
@@ -83,13 +96,9 @@ public class JsFetchBridgeTest {
@Test
public void testPromiseBasicUsage() {
- // 初始化Vertx
- Vertx vertx = Vertx.vertx();
- WebClientVertxInit.init(vertx);
-
// 清理注册表
CustomParserRegistry.clear();
-
+
String jsCode = """
function parse(shareLinkInfo, http, logger) {
logger.info("测试Promise基本用法");
diff --git a/pom.xml b/pom.xml
index 221b307..4a00f07 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,16 +25,17 @@
${project.basedir}/web-service/target/package
-
- 4.5.24
+
+ 4.5.27
0.10.2
1.18.38
2.0.16
3.18.0
2.0.0
+ 10.2.5
2.18.6
- 1.5.18
+ 1.5.32
4.13.2
@@ -74,7 +75,7 @@
cn.qaiu
parser
- 10.2.5
+ ${parserVersion}
diff --git a/web-front/package.json b/web-front/package.json
index b73baba..5abfb36 100644
--- a/web-front/package.json
+++ b/web-front/package.json
@@ -5,15 +5,15 @@
"scripts": {
"serve": "vue-cli-service serve",
"dev": "vue-cli-service serve",
- "build": "vue-cli-service build && node scripts/compress-vs.js",
- "build:no-compress": "vue-cli-service build",
+ "build": "node scripts/sync-version.js && vue-cli-service build && node scripts/compress-vs.js",
+ "build:no-compress": "node scripts/sync-version.js && vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@monaco-editor/loader": "^1.4.0",
"@vueuse/core": "^11.2.0",
- "axios": "1.13.5",
+ "axios": "1.16.1",
"clipboard": "^2.0.11",
"core-js": "^3.8.3",
"crypto-js": "^4.2.0",
diff --git a/web-front/public/index.html b/web-front/public/index.html
index 35d7ba4..2d60851 100644
--- a/web-front/public/index.html
+++ b/web-front/public/index.html
@@ -10,7 +10,7 @@
-
+