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

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