Files
netdisk-fast-download/parser/doc/CUSTOM_PARSER_GUIDE.md
2025-11-28 19:48:19 +08:00

16 KiB
Raw Permalink Blame History

自定义解析器扩展指南

最后更新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自动处理 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 实例

    Vertx vertx = Vertx.vertx();
    WebClientVertxInit.init(vertx);
    
  2. 创建解析器实例

    • 继承 PanBase 的解析器会自动获得配置好的 WebClient 实例
  3. 发起异步请求

    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. 响应处理

    .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: 注册自定义解析器

在应用启动时注册你的解析器:

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: 可以在解析器类中注入依赖,或使用单例模式管理外部服务连接。

相关文档

贡献

如果你实现了通用的网盘解析器,欢迎提交 PR 将其加入到内置解析器中!

许可

本模块遵循项目主LICENSE。