# parser 开发文档

面向开发者的解析器实现说明：约定、数据映射、HTTP 调试与示例代码。

- 语言/构建：Java 17 / Maven
- 关键接口：cn.qaiu.parser.IPanTool（返回 Future<List<FileInfo>>），各站点位于 parser/src/main/java/cn/qaiu/parser/impl
- 数据模型：cn.qaiu.entity.FileInfo（统一对外文件项）
- JavaScript解析器：支持使用JavaScript编写自定义解析器，位于 parser/src/main/resources/custom-parsers/

---

## 0. 快速调用示例（最小可运行）
```java
import cn.qaiu.WebClientVertxInit;
import cn.qaiu.entity.FileInfo;
import cn.qaiu.parser.IPanTool;
import cn.qaiu.parser.ParserCreate;
import io.vertx.core.Vertx;
import java.util.List;

public class ParserQuickStart {
    public static void main(String[] args) {
        // 1) 初始化 Vert.x（parser 内部 WebClient 依赖它）
        Vertx vertx = Vertx.vertx();
        WebClientVertxInit.init(vertx);

        // 2) 从分享链接自动识别网盘类型并创建解析器
        String shareUrl = "https://www.ilanzou.com/s/xxxx"; // 替换为实际分享链接
        IPanTool tool = ParserCreate.fromShareUrl(shareUrl)
                // .setShareLinkInfoPwd("1234") // 如有提取码可设置
                .createTool();

        // 3) 使用同步方法获取文件列表（推荐）
        List<FileInfo> files = tool.parseFileListSync();
        for (FileInfo f : files) {
            System.out.printf("%s\t%s\t%s\n",
                f.getFileName(), f.getSizeStr(), f.getParserUrl());
        }

        // 4) 使用同步方法获取原始解析输出（不同盘实现差异较大，仅供调试）
        String raw = tool.parseSync();
        System.out.println("raw: " + (raw == null ? "null" : raw.substring(0, Math.min(raw.length(), 200)) + "..."));
        
        // 5) 使用同步方法根据文件ID获取下载链接（可选）
        if (!files.isEmpty()) {
            String fileId = files.get(0).getFileId();
            String downloadUrl = tool.parseByIdSync();
            System.out.println("文件下载链接: " + downloadUrl);
        }

        // 6) 生成 parser 短链 path（可用于上层路由聚合显示）
        String path = ParserCreate.fromShareUrl(shareUrl).genPathSuffix();
        System.out.println("path suffix: /" + path);

        vertx.close();
    }
}
```

等价用法：已知网盘类型 + shareKey 构造
```java
IPanTool tool = ParserCreate.fromType("lz") // 对应 PanDomainTemplate.LZ
        .shareKey("abcd12")                   // 必填：分享 key
        .setShareLinkInfoPwd("1234")         // 可选：提取码
        .createTool();
// 获取文件列表（使用同步方法）
List<FileInfo> files = tool.parseFileListSync();
```

要点：
- 必须先 WebClientVertxInit.init(Vertx)；若未显式初始化，内部将懒加载 Vertx.vertx()，建议显式注入以统一生命周期。
- 支持三种同步方法：
  - `parseSync()`: 解析单个文件下载链接
  - `parseFileListSync()`: 解析文件列表
  - `parseByIdSync()`: 根据文件ID获取下载链接
- 异步方法仍可用：parse()、parseFileList()、parseById() 返回 Future 对象
- 生成短链 path：ParserCreate.genPathSuffix()（用于页面/服务端聚合）。

## JavaScript解析器快速开始

除了Java解析器，还支持使用JavaScript编写自定义解析器：

### 1. 创建JavaScript解析器

在 `parser/src/main/resources/custom-parsers/` 目录下创建 `.js` 文件：

```javascript
// ==UserScript==
// @name         我的解析器
// @type         my_parser
// @displayName  我的网盘
// @description  使用JavaScript实现的网盘解析器
// @match        https?://example\.com/s/(?<KEY>\w+)
// @author       yourname
// @version      1.0.0
// ==/UserScript==

/**
 * 解析单个文件下载链接
 * @param {ShareLinkInfo} shareLinkInfo - 分享链接信息
 * @param {JsHttpClient} http - HTTP客户端
 * @param {JsLogger} logger - 日志对象
 * @returns {string} 下载链接
 */
function parse(shareLinkInfo, http, logger) {
    var url = shareLinkInfo.getShareUrl();
    var response = http.get(url);
    return response.body();
}
```

### 2. JavaScript解析器特性

- **重定向处理**：支持`getNoRedirect()`方法获取302重定向的真实链接
- **代理支持**：自动支持HTTP/SOCKS代理配置
- **类型提示**：提供完整的JSDoc类型定义
- **热加载**：修改后重启应用即可生效

### 3. 详细文档

- **[JavaScript解析器开发指南](JAVASCRIPT_PARSER_GUIDE.md)** - 完整的JavaScript解析器开发文档，包含API参考、示例代码和最佳实践
- **[自定义解析器扩展指南](CUSTOM_PARSER_GUIDE.md)** - Java自定义解析器扩展完整指南
- **[自定义解析器快速开始](CUSTOM_PARSER_QUICKSTART.md)** - 快速上手自定义解析器开发

---

## 1. 解析器约定
- 输入：目标分享/目录页或接口的上下文（通常在实现类构造或初始化时已注入必要参数，如 shareKey、cookie、headers）。
- 输出：Future<List<FileInfo>>（文件/目录混合列表，必要时区分 file/folder）。
- 错误：失败场景通过 Future 失败或返回空列表；日志由上层统一处理。
- 并发：尽量使用 Vert.x Web Client 异步请求；注意限流与重试策略由实现类自定。

FileInfo 关键字段（节选）：
- fileId：唯一标识
- fileName：展示名（建议带扩展名，如 basename）
- fileType：如 "file"/"folder" 或 mime（实现自定，保持一致即可）
- size（Long, 字节）/ sizeStr（原文字符串）
- createTime / updateTime：格式 yyyy-MM-dd HH:mm:ss（如源为时间戳或 yyyy-MM-dd 需转）
- parserUrl：非直连下载的中间链接或协议占位（如 BilPan://）
- filePath / previewUrl / extParameters：按需补充

工具类：
- FileSizeConverter：字符串容量转字节、字节转可读容量

---

## 2. 文件列表解析规范

### 通用解析原则

1. **数据结构识别**：根据网盘API响应结构确定文件列表的路径
2. **字段映射**：将网盘特定字段映射到统一的`FileInfo`对象
3. **类型区分**：正确识别文件和文件夹类型
4. **数据转换**：处理时间格式、文件大小等数据格式转换

### FileInfo字段映射指南

| FileInfo字段 | 说明 | 映射建议 |
|-------------|------|----------|
| `fileName` | 文件名 | 优先使用文件名字段，无则使用标题字段 |
| `fileId` | 文件ID | 使用网盘提供的唯一标识符 |
| `fileType` | 文件类型 | "file"或"folder" |
| `size` | 文件大小(字节) | 转换为字节数，文件夹可为0 |
| `sizeStr` | 文件大小(可读) | 保持网盘原始格式或转换 |
| `createTime` | 创建时间 | 统一时间格式 |
| `updateTime` | 更新时间 | 统一时间格式 |
| `parserUrl` | 下载链接 | 网盘提供的下载URL |
| `previewUrl` | 预览链接 | 可选，网盘提供的预览URL |

### 常见数据转换

- **文件大小**：使用`FileSizeConverter`进行字符串与字节数转换
- **时间格式**：统一转换为标准时间格式
- **文件类型**：根据网盘API判断文件/文件夹类型

### 解析注意事项

- **数据验证**：检查必要字段是否存在，避免空指针异常
- **格式兼容**：处理不同网盘的数据格式差异
- **错误处理**：转换失败时提供合理的默认值
- **扩展字段**：额外信息可存储在`extParameters`中

### 解析示例

```java
// 通用解析模式示例
JsonObject root = response.json(); // 获取API响应
JsonArray fileList = root.getJsonArray("files"); // 根据实际API调整路径
List<FileInfo> result = new ArrayList<>();

for (JsonObject item : fileList) {
    FileInfo fileInfo = new FileInfo();
    
    // 基本字段映射
    fileInfo.setFileName(item.getString("name"));
    fileInfo.setFileId(item.getString("id"));
    fileInfo.setFileType(item.getString("type").equals("file") ? "file" : "folder");
    
    // 文件大小处理
    String sizeStr = item.getString("size");
    if (sizeStr != null) {
        fileInfo.setSizeStr(sizeStr);
        try {
            fileInfo.setSize(FileSizeConverter.convertToBytes(sizeStr));
        } catch (Exception e) {
            // 转换失败时保持sizeStr，size为0
        }
    }
    
    // 时间处理
    fileInfo.setCreateTime(formatTime(item.getString("createTime")));
    fileInfo.setUpdateTime(formatTime(item.getString("updateTime")));
    
    // 下载链接
    fileInfo.setParserUrl(item.getString("downloadUrl"));
    
    result.add(fileInfo);
}
```

### JavaScript解析器示例

```javascript
function parseFileList(shareLinkInfo, http, logger) {
    var response = http.get(shareLinkInfo.getShareUrl());
    var data = response.json();
    
    var fileList = [];
    var files = data.files || data.data || data.items; // 根据实际API调整
    
    for (var i = 0; i < files.length; i++) {
        var file = files[i];
        var fileInfo = {
            fileName: file.name || file.title,
            fileId: file.id,
            fileType: file.type === "file" ? "file" : "folder",
            size: file.size || 0,
            sizeStr: file.sizeStr || formatSize(file.size),
            createTime: file.createTime,
            updateTime: file.updateTime,
            parserUrl: file.downloadUrl || file.url
        };
        
        fileList.push(fileInfo);
    }
    
    return fileList;
}
```

---

## 3. 开发流程建议
- 新增站点：在 impl 下新增 Tool，实现 IPanTool，复用 PanBase/模板类；补充单测。
- 字段不全：尽量回填 sizeStr/createTime 等便于前端展示；不可用字段置空。
- 单测：放置于 parser/src/test/java，尽量添加 1-2 个 happy path + 1 个边界用例。

## 4. 常见问题
- 容量解析失败：保留 sizeStr，并忽略 size；避免抛出异常影响整体列表。
- 协议占位下载链接：统一放至 parserUrl，直链转换由下载阶段处理。
- 鉴权：Cookie/Token 过期问题由上层刷新或外部注入处理；解析器保持无状态最佳。

---

## 5. 参考
- FileInfo：parser/src/main/java/cn/qaiu/entity/FileInfo.java
- IPanTool：parser/src/main/java/cn/qaiu/parser/IPanTool.java
- FileSizeConverter：parser/src/main/java/cn/qaiu/util/FileSizeConverter.java
