From e6672a51c518a362683e1c1e2973352464361545 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 22:59:33 +0000 Subject: [PATCH 1/2] Initial plan From c7e6d68fbd03fc9be50dab462e0ea87988dd34a3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Oct 2025 23:03:39 +0000 Subject: [PATCH 2/2] Update parser documentation with PanBase inheritance and WebClient flow Co-authored-by: qaiu <29825328+qaiu@users.noreply.github.com> --- parser/README.md | 17 ++- parser/doc/CUSTOM_PARSER_GUIDE.md | 168 +++++++++++++++++++++++------- 2 files changed, 142 insertions(+), 43 deletions(-) diff --git a/parser/README.md b/parser/README.md index 1f3b786..1fb1f87 100644 --- a/parser/README.md +++ b/parser/README.md @@ -60,11 +60,20 @@ mvn -pl parser test 本模块支持用户自定义解析器扩展。通过简单的配置和注册,你可以添加自己的网盘解析实现: ```java -// 1. 实现 IPanTool 接口 -public class MyPanTool implements IPanTool { - public MyPanTool(ShareLinkInfo info) { /* 必须提供此构造器 */ } +// 1. 继承 PanBase 抽象类(推荐) +public class MyPanTool extends PanBase { + public MyPanTool(ShareLinkInfo info) { + super(info); + } @Override - public Future parse() { /* 实现解析逻辑 */ } + public Future parse() { + // 使用 PanBase 提供的 HTTP 客户端 + client.getAbs("https://api.example.com") + .send() + .onSuccess(res -> complete(asJson(res).getString("url"))) + .onFailure(handleFail("请求失败")); + return future(); + } } // 2. 注册到系统 diff --git a/parser/doc/CUSTOM_PARSER_GUIDE.md b/parser/doc/CUSTOM_PARSER_GUIDE.md index 4b57eb0..b27416c 100644 --- a/parser/doc/CUSTOM_PARSER_GUIDE.md +++ b/parser/doc/CUSTOM_PARSER_GUIDE.md @@ -1,5 +1,7 @@ # 自定义解析器扩展指南 +> 最后更新:2025-10-17 + ## 概述 本模块支持用户自定义解析器扩展。用户在依赖本项目的 Maven 坐标后,可以实现自己的网盘解析器并注册到系统中使用。 @@ -27,49 +29,50 @@ ``` -### 步骤2: 实现 IPanTool 接口 +### 步骤2: 继承 PanBase 抽象类 -创建自己的解析工具类,必须实现 `IPanTool` 接口: +创建自己的解析工具类,**必须继承 `PanBase` 抽象类**(而不是直接实现 IPanTool 接口)。PanBase 提供了丰富的工具方法和 HTTP 客户端,简化解析器的开发。 ```java package com.example.parser; import cn.qaiu.entity.ShareLinkInfo; -import cn.qaiu.parser.IPanTool; +import cn.qaiu.parser.PanBase; import io.vertx.core.Future; -import io.vertx.core.Promise; /** * 自定义网盘解析器示例 */ -public class MyCustomPanTool implements IPanTool { - - private final ShareLinkInfo shareLinkInfo; +public class MyCustomPanTool extends PanBase { /** * 必须提供 ShareLinkInfo 单参构造器 */ public MyCustomPanTool(ShareLinkInfo shareLinkInfo) { - this.shareLinkInfo = shareLinkInfo; + super(shareLinkInfo); } @Override public Future parse() { - Promise promise = Promise.promise(); - - // 实现你的解析逻辑 + // 使用 PanBase 提供的 HTTP 客户端发起请求 String shareKey = shareLinkInfo.getShareKey(); String sharePassword = shareLinkInfo.getSharePassword(); - try { - // 调用你的网盘API,获取下载链接 - String downloadUrl = callYourPanApi(shareKey, sharePassword); - promise.complete(downloadUrl); - } catch (Exception e) { - promise.fail(e); - } + // 示例:使用 client 发起 GET 请求 + client.getAbs("https://your-pan-domain.com/api/share/" + shareKey) + .send() + .onSuccess(res -> { + // 使用 asJson 方法将响应转换为 JSON + var json = asJson(res); + String downloadUrl = json.getString("download_url"); + + // 使用 complete 方法完成 Promise + complete(downloadUrl); + }) + .onFailure(handleFail("请求下载链接失败")); - return promise.future(); + // 返回 Future + return future(); } /** @@ -78,7 +81,7 @@ public class MyCustomPanTool implements IPanTool { @Override public Future> parseFileList() { // 实现文件列表解析逻辑 - return IPanTool.super.parseFileList(); + return super.parseFileList(); } /** @@ -87,16 +90,96 @@ public class MyCustomPanTool implements IPanTool { @Override public Future parseById() { // 实现根据ID解析的逻辑 - return IPanTool.super.parseById(); - } - - private String callYourPanApi(String shareKey, String password) { - // 实现你的网盘API调用逻辑 - return "https://your-pan-domain.com/download/" + shareKey; + return super.parseById(); } } ``` +### PanBase 提供的核心方法 + +PanBase 为解析器开发提供了以下工具和方法: + +#### HTTP 客户端 +- **`client`**: 标准 WebClient 实例,支持自动重定向 +- **`clientSession`**: 带会话管理的 WebClient,自动处理 Cookie +- **`clientNoRedirects`**: 不自动重定向的 WebClient,用于需要手动处理重定向的场景 + +#### 响应处理 +- **`asJson(HttpResponse)`**: 将 HTTP 响应转换为 JsonObject,自动处理 gzip 压缩和异常 +- **`asText(HttpResponse)`**: 将 HTTP 响应转换为文本,自动处理 gzip 压缩 + +#### Promise 管理 +- **`complete(String)`**: 完成 Promise 并返回结果 +- **`future()`**: 获取 Promise 的 Future 对象 +- **`fail(String, Object...)`**: Promise 失败时记录错误信息 +- **`fail(Throwable, String, Object...)`**: Promise 失败时记录错误信息和异常 +- **`handleFail(String)`**: 生成失败处理器,用于请求的 onFailure 回调 + +#### 其他工具 +- **`nextParser()`**: 调用下一个解析器,用于通用域名解析转发 +- **`getDomainName()`**: 获取域名名称 +- **`shareLinkInfo`**: 分享链接信息对象,包含 shareKey、sharePassword 等 +- **`log`**: 日志记录器 + +### WebClient 请求流程 + +WebClient 是基于 Vert.x 的异步 HTTP 客户端,其请求流程如下: + +1. **初始化 Vert.x 实例** + ```java + Vertx vertx = Vertx.vertx(); + WebClientVertxInit.init(vertx); + ``` + +2. **创建解析器实例** + - 继承 PanBase 的解析器会自动获得配置好的 WebClient 实例 + +3. **发起异步请求** + ```java + client.getAbs("https://api.example.com/endpoint") + .putHeader("User-Agent", "MyParser/1.0") + .send() + .onSuccess(res -> { + // 处理成功响应 + JsonObject json = asJson(res); + complete(json.getString("url")); + }) + .onFailure(handleFail("请求失败")); + ``` + +4. **请求流程说明** + - **GET 请求**: 使用 `client.getAbs(url).send()` + - **POST 请求**: 使用 `client.postAbs(url).sendJson(body)` 或 `.sendForm(form)` + - **会话请求**: 使用 `clientSession` 自动管理 Cookie + - **禁用重定向**: 使用 `clientNoRedirects` 手动处理 302/301 + - **代理支持**: PanBase 构造器会自动处理 shareLinkInfo 中的代理配置 + +5. **响应处理** + ```java + .onSuccess(res -> { + // 检查状态码 + if (res.statusCode() != 200) { + fail("请求失败,状态码:" + res.statusCode()); + return; + } + + // 解析 JSON 响应 + JsonObject json = asJson(res); + + // 或解析文本响应 + String text = asText(res); + + // 完成 Promise + complete(result); + }) + .onFailure(handleFail("网络请求异常")); + ``` + +6. **错误处理** + - 使用 `fail()` 方法标记解析失败 + - 使用 `handleFail()` 生成统一的失败处理器 + - 所有异常会自动记录到日志 + ### 步骤3: 注册自定义解析器 在应用启动时注册你的解析器: @@ -173,10 +256,10 @@ public class Example { - 注册时会自动检查冲突 ### 2. 构造器要求 -自定义解析器类必须提供 `ShareLinkInfo` 单参构造器: +自定义解析器类必须提供 `ShareLinkInfo` 单参构造器,并调用父类构造器: ```java public MyCustomPanTool(ShareLinkInfo shareLinkInfo) { - this.shareLinkInfo = shareLinkInfo; + super(shareLinkInfo); } ``` @@ -230,8 +313,8 @@ import cn.qaiu.parser.CustomParserConfig; import cn.qaiu.parser.CustomParserRegistry; import cn.qaiu.parser.IPanTool; import cn.qaiu.parser.ParserCreate; +import cn.qaiu.parser.PanBase; import io.vertx.core.Future; -import io.vertx.core.Promise; public class CompleteExample { @@ -298,24 +381,31 @@ public class CompleteExample { } } - // 自定义解析器实现 - static class MyCustomPanTool implements IPanTool { - private final ShareLinkInfo shareLinkInfo; + // 自定义解析器实现(继承 PanBase) + static class MyCustomPanTool extends PanBase { public MyCustomPanTool(ShareLinkInfo shareLinkInfo) { - this.shareLinkInfo = shareLinkInfo; + super(shareLinkInfo); } @Override public Future parse() { - Promise promise = Promise.promise(); - - // 模拟解析逻辑 + // 使用 PanBase 提供的 HTTP 客户端 String shareKey = shareLinkInfo.getShareKey(); - String downloadUrl = "https://mypan.com/download/" + shareKey; - promise.complete(downloadUrl); - return promise.future(); + client.getAbs("https://mypan.com/api/share/" + shareKey) + .send() + .onSuccess(res -> { + // 使用 asJson 解析响应 + var json = asJson(res); + String downloadUrl = json.getString("download_url"); + + // 使用 complete 完成 Promise + complete(downloadUrl); + }) + .onFailure(handleFail("获取下载链接失败")); + + return future(); } } }