16 KiB
自定义解析器扩展指南
最后更新:2025-10-17
概述
本模块支持用户自定义解析器扩展。用户在依赖本项目的 Maven 坐标后,可以实现自己的网盘解析器并注册到系统中使用。
提示:除了Java自定义解析器,本项目还支持使用JavaScript编写解析器,无需编译即可使用。
查看 JavaScript解析器开发指南 了解更多。
核心组件
1. CustomParserConfig
自定义解析器配置类,用于描述自定义解析器的元信息。
2. CustomParserRegistry
自定义解析器注册中心,用于管理所有已注册的自定义解析器。
3. ParserCreate
解析器工厂类,已增强支持自定义解析器的创建。
使用步骤
步骤1: 添加 Maven 依赖
<dependency>
<groupId>cn.qaiu</groupId>
<artifactId>parser</artifactId>
<version>10.1.17</version>
</dependency>
步骤2: 继承 PanBase 抽象类
创建自己的解析工具类,必须继承 PanBase 抽象类(而不是直接实现 IPanTool 接口)。PanBase 提供了丰富的工具方法和 HTTP 客户端,简化解析器的开发。
package com.example.parser;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
/**
* 自定义网盘解析器示例
*/
public class MyCustomPanTool extends PanBase {
/**
* 必须提供 ShareLinkInfo 单参构造器
*/
public MyCustomPanTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
@Override
public Future<String> parse() {
// 使用 PanBase 提供的 HTTP 客户端发起请求
String shareKey = shareLinkInfo.getShareKey();
String sharePassword = shareLinkInfo.getSharePassword();
// 示例:使用 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("请求下载链接失败"));
// 返回 Future
return future();
}
/**
* 如果需要解析文件列表,可以重写此方法
*/
@Override
public Future<List<FileInfo>> parseFileList() {
// 实现文件列表解析逻辑
return super.parseFileList();
}
/**
* 如果需要根据文件ID获取下载链接,可以重写此方法
*/
@Override
public Future<String> parseById() {
// 实现根据ID解析的逻辑
return super.parseById();
}
}
PanBase 提供的核心方法
PanBase 为解析器开发提供了以下工具和方法:
HTTP 客户端
client: 标准 WebClient 实例,支持自动重定向clientSession: 带会话管理的 WebClient,自动处理 CookieclientNoRedirects: 不自动重定向的 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 客户端,其请求流程如下:
-
初始化 Vert.x 实例
Vertx vertx = Vertx.vertx(); WebClientVertxInit.init(vertx); -
创建解析器实例
- 继承 PanBase 的解析器会自动获得配置好的 WebClient 实例
-
发起异步请求
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("请求失败")); -
请求流程说明
- GET 请求: 使用
client.getAbs(url).send() - POST 请求: 使用
client.postAbs(url).sendJson(body)或.sendForm(form) - 会话请求: 使用
clientSession自动管理 Cookie - 禁用重定向: 使用
clientNoRedirects手动处理 302/301 - 代理支持: PanBase 构造器会自动处理 shareLinkInfo 中的代理配置
- GET 请求: 使用
-
响应处理
.onSuccess(res -> { // 检查状态码 if (res.statusCode() != 200) { fail("请求失败,状态码:" + res.statusCode()); return; } // 解析 JSON 响应 JsonObject json = asJson(res); // 或解析文本响应 String text = asText(res); // 完成 Promise complete(result); }) .onFailure(handleFail("网络请求异常")); -
错误处理
- 使用
fail()方法标记解析失败 - 使用
handleFail()生成统一的失败处理器 - 所有异常会自动记录到日志
- 使用
步骤3: 注册自定义解析器
在应用启动时注册你的解析器:
import cn.qaiu.parser.custom.CustomParserConfig;
import cn.qaiu.parser.custom.CustomParserRegistry;
import com.example.parser.MyCustomPanTool;
public class Application {
public static void main(String[] args) {
// 注册自定义解析器
registerCustomParsers();
// 启动你的应用...
}
private static void registerCustomParsers() {
// 创建自定义解析器配置
CustomParserConfig config = CustomParserConfig.builder()
.type("mypan") // 类型标识(必填,唯一,建议小写)
.displayName("我的网盘") // 显示名称(必填)
.toolClass(MyCustomPanTool.class) // 解析工具类(必填)
.standardUrlTemplate("https://mypan.com/s/{shareKey}") // URL模板(可选)
.panDomain("https://mypan.com") // 网盘域名(可选)
.build();
// 注册到系统
CustomParserRegistry.register(config);
System.out.println("自定义解析器注册成功!");
}
}
步骤4: 使用自定义解析器
重要:自定义解析器只能通过 fromType 方法创建,不支持从分享链接自动识别。
import cn.qaiu.parser.ParserCreate;
import cn.qaiu.parser.IPanTool;
public class Example {
public static void main(String[] args) {
// 方式1: 使用 fromType 创建(推荐)
IPanTool tool = ParserCreate.fromType("mypan") // 使用注册时的type
.shareKey("abc123") // 设置分享键
.setShareLinkInfoPwd("1234") // 设置密码(可选)
.createTool(); // 创建工具实例
// 方式1: 使用同步方法解析(推荐)
String downloadUrl = tool.parseSync();
System.out.println("下载链接: " + downloadUrl);
// 方式2: 使用同步方法解析文件列表
List<FileInfo> files = tool.parseFileListSync();
System.out.println("文件列表: " + files.size() + " 个文件");
// 方式3: 使用同步方法根据文件ID获取下载链接
if (!files.isEmpty()) {
String fileId = files.get(0).getFileId();
String fileDownloadUrl = tool.parseByIdSync();
System.out.println("文件下载链接: " + fileDownloadUrl);
}
// 方式4: 异步解析(仍支持)
tool.parse().onSuccess(url -> {
System.out.println("异步获取下载链接: " + url);
}).onFailure(err -> {
System.err.println("解析失败: " + err.getMessage());
});
}
}
同步方法支持
解析器现在支持三种同步方法,简化了使用方式:
1. parseSync()
解析单个文件的下载链接:
String downloadUrl = tool.parseSync();
2. parseFileListSync()
解析文件列表(目录):
List<FileInfo> files = tool.parseFileListSync();
for (FileInfo file : files) {
System.out.println("文件: " + file.getFileName());
}
3. parseByIdSync()
根据文件ID获取下载链接:
String fileDownloadUrl = tool.parseByIdSync();
同步方法优势
- 简化使用: 无需处理 Future 和回调
- 异常处理: 同步方法会抛出异常,便于错误处理
- 代码简洁: 减少异步代码的复杂性
异步方法仍可用
原有的异步方法仍然支持:
parse(): 返回Future<String>parseFileList(): 返回Future<List<FileInfo>>parseById(): 返回Future<String>
注意事项
1. 类型标识规范
- 类型标识(type)必须唯一
- 建议使用小写英文字母
- 不能与内置解析器类型冲突
- 注册时会自动检查冲突
2. 构造器要求
自定义解析器类必须提供 ShareLinkInfo 单参构造器,并调用父类构造器:
public MyCustomPanTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
3. 创建方式限制
- ✅ 支持: 通过
ParserCreate.fromType("type")创建 - ❌ 不支持: 通过
ParserCreate.fromShareUrl(url)自动识别
这是因为自定义解析器没有正则表达式模式来匹配分享链接。
4. 线程安全
CustomParserRegistry 使用 ConcurrentHashMap 实现,支持多线程安全的注册和查询。
API 参考
CustomParserConfig.Builder
| 方法 | 说明 | 必填 |
|---|---|---|
type(String) |
设置类型标识,必须唯一 | 是 |
displayName(String) |
设置显示名称 | 是 |
toolClass(Class) |
设置解析工具类 | 是 |
standardUrlTemplate(String) |
设置标准URL模板 | 否 |
panDomain(String) |
设置网盘域名 | 否 |
build() |
构建配置对象 | - |
CustomParserRegistry
| 方法 | 说明 |
|---|---|
register(CustomParserConfig) |
注册自定义解析器 |
unregister(String type) |
注销指定类型的解析器 |
get(String type) |
获取指定类型的解析器配置 |
contains(String type) |
检查是否已注册 |
clear() |
清空所有自定义解析器 |
size() |
获取已注册数量 |
getAll() |
获取所有已注册配置 |
ParserCreate 扩展方法
| 方法 | 说明 |
|---|---|
isCustomParser() |
判断是否为自定义解析器 |
getCustomParserConfig() |
获取自定义解析器配置 |
getPanDomainTemplate() |
获取内置解析器模板 |
完整示例
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.custom.CustomParserConfig;
import cn.qaiu.parser.custom.CustomParserRegistry;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.ParserCreate;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
public class CompleteExample {
public static void main(String[] args) {
// 1. 注册自定义解析器
registerParser();
// 2. 使用自定义解析器
useParser();
// 3. 查询注册状态
checkRegistry();
// 4. 注销解析器(可选)
// CustomParserRegistry.unregister("mypan");
}
private static void registerParser() {
CustomParserConfig config = CustomParserConfig.builder()
.type("mypan")
.displayName("我的网盘")
.toolClass(MyCustomPanTool.class)
.standardUrlTemplate("https://mypan.com/s/{shareKey}")
.panDomain("https://mypan.com")
.build();
try {
CustomParserRegistry.register(config);
System.out.println("✓ 解析器注册成功");
} catch (IllegalArgumentException e) {
System.err.println("✗ 注册失败: " + e.getMessage());
}
}
private static void useParser() {
try {
ParserCreate parser = ParserCreate.fromType("mypan")
.shareKey("abc123")
.setShareLinkInfoPwd("1234");
// 检查是否为自定义解析器
if (parser.isCustomParser()) {
System.out.println("✓ 这是一个自定义解析器");
System.out.println(" 配置: " + parser.getCustomParserConfig());
}
// 创建工具并解析
IPanTool tool = parser.createTool();
// 使用同步方法解析
String url = tool.parseSync();
System.out.println("✓ 下载链接: " + url);
// 解析文件列表
List<FileInfo> files = tool.parseFileListSync();
System.out.println("✓ 文件列表: " + files.size() + " 个文件");
// 根据文件ID获取下载链接
if (!files.isEmpty()) {
String fileDownloadUrl = tool.parseByIdSync();
System.out.println("✓ 文件下载链接: " + fileDownloadUrl);
}
} catch (Exception e) {
System.err.println("✗ 解析失败: " + e.getMessage());
}
}
private static void checkRegistry() {
System.out.println("\n已注册的自定义解析器:");
System.out.println(" 数量: " + CustomParserRegistry.size());
if (CustomParserRegistry.contains("mypan")) {
CustomParserConfig config = CustomParserRegistry.get("mypan");
System.out.println(" - " + config.getType() + ": " + config.getDisplayName());
}
}
// 自定义解析器实现(继承 PanBase)
static class MyCustomPanTool extends PanBase {
public MyCustomPanTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
@Override
public Future<String> parse() {
// 使用 PanBase 提供的 HTTP 客户端
String shareKey = shareLinkInfo.getShareKey();
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();
}
}
}
常见问题
Q1: 如何更新已注册的解析器?
A: 需要先注销再重新注册:
CustomParserRegistry.unregister("mypan");
CustomParserRegistry.register(newConfig);
Q2: 注册时抛出"类型标识已被注册"异常?
A: 该类型已被使用,请更换其他类型标识或先注销已有的。
Q3: 注册时抛出"与内置解析器冲突"异常?
A: 你使用的类型标识与系统内置的解析器类型冲突,请查看 PanDomainTemplate 枚举了解所有内置类型。
Q4: 可以从分享链接自动识别我的自定义解析器吗?
A: 不可以。自定义解析器只能通过 fromType 方法创建。如果需要从链接识别,建议提交 PR 将解析器添加到 PanDomainTemplate 枚举中。
Q5: 解析器需要依赖外部服务怎么办?
A: 可以在解析器类中注入依赖,或使用单例模式管理外部服务连接。
相关文档
- JavaScript解析器开发指南 - 使用JavaScript编写解析器,无需编译
- 自定义解析器快速开始 - 快速上手指南
- 解析器开发文档 - 解析器开发约定和规范
贡献
如果你实现了通用的网盘解析器,欢迎提交 PR 将其加入到内置解析器中!
许可
本模块遵循项目主LICENSE。