mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2026-01-13 01:44:12 +00:00
feat: 添加 WPS 云文档/WPS 云盘解析支持 (closes #133)
- 新增 PwpsTool 解析器,支持 WPS 云文档直链获取 - 调用 WPS API: https://www.kdocs.cn/api/office/file/{shareKey}/download - 前端添加 kdocs.cn 链接识别规则 - 前端预览功能优化:WPS 云文档直接使用原分享链接预览 - 后端预览接口特殊处理:判断 shareKey 以 pwps: 开头自动重定向 - 支持提取文件名和有效期信息 - 更新 README 文档,添加 WPS 云文档支持说明 Parser 模块设计: - 遵循开放封闭原则,易于扩展新网盘 - 只需实现 IPanTool 接口和注册枚举即可 - 支持自定义域名解析和责任链模式 技术特性: - 免登录获取下载直链 - 支持在线预览(利用 WPS 原生功能) - 文件大小限制:10M(免费版)/2G(会员版) - 初始空间:5G(免费版)
This commit is contained in:
156
parser/src/test/java/cn/qaiu/parser/impl/WpsPanTest.java
Normal file
156
parser/src/test/java/cn/qaiu/parser/impl/WpsPanTest.java
Normal file
@@ -0,0 +1,156 @@
|
||||
package cn.qaiu.parser.impl;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.qaiu.parser.ParserCreate;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* WPS 云文档解析测试
|
||||
*/
|
||||
public class WpsPanTest {
|
||||
|
||||
@Test
|
||||
public void testWpsDownload() throws InterruptedException {
|
||||
System.out.println("======= WPS 云文档解析测试 =======");
|
||||
|
||||
// 测试链接:reset_navicat_mac
|
||||
String wpsUrl = "https://www.kdocs.cn/l/ck0azivLlDi3";
|
||||
|
||||
System.out.println("测试链接: " + wpsUrl);
|
||||
System.out.println("文件名称: reset_navicat_mac");
|
||||
System.out.println();
|
||||
|
||||
// 使用 ParserCreate 方式创建解析器
|
||||
ParserCreate parserCreate = ParserCreate.fromShareUrl(wpsUrl);
|
||||
|
||||
System.out.println("解析器类型: " + parserCreate.getShareLinkInfo().getType());
|
||||
System.out.println("网盘名称: " + parserCreate.getShareLinkInfo().getPanName());
|
||||
System.out.println("分享Key: " + parserCreate.getShareLinkInfo().getShareKey());
|
||||
System.out.println("标准URL: " + parserCreate.getShareLinkInfo().getStandardUrl());
|
||||
System.out.println();
|
||||
|
||||
System.out.println("开始解析下载链接...");
|
||||
|
||||
// 创建工具并解析
|
||||
parserCreate.createTool()
|
||||
.parse()
|
||||
.onSuccess(downloadUrl -> {
|
||||
System.out.println("✓ 解析成功!");
|
||||
System.out.println("下载直链: " + downloadUrl);
|
||||
System.out.println();
|
||||
|
||||
// 解析文件信息
|
||||
JsonObject fileInfo = getFileInfo(downloadUrl);
|
||||
System.out.println("文件信息: " + fileInfo.encodePrettily());
|
||||
System.out.println();
|
||||
})
|
||||
.onFailure(error -> {
|
||||
System.err.println("✗ 解析失败!");
|
||||
System.err.println("错误信息: " + error.getMessage());
|
||||
error.printStackTrace();
|
||||
});
|
||||
|
||||
// 等待异步结果
|
||||
System.out.println("等待解析结果...");
|
||||
TimeUnit.SECONDS.sleep(10);
|
||||
|
||||
System.out.println("======= 测试结束 =======");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWpsWithShareKey() throws InterruptedException {
|
||||
System.out.println("======= WPS 云文档解析测试 (使用 shareKey) =======");
|
||||
|
||||
String shareKey = "ck0azivLlDi3";
|
||||
|
||||
System.out.println("分享Key: " + shareKey);
|
||||
System.out.println();
|
||||
|
||||
// 使用 fromType + shareKey 方式
|
||||
ParserCreate parserCreate = ParserCreate.fromType("pwps")
|
||||
.shareKey(shareKey);
|
||||
|
||||
System.out.println("解析器类型: " + parserCreate.getShareLinkInfo().getType());
|
||||
System.out.println("网盘名称: " + parserCreate.getShareLinkInfo().getPanName());
|
||||
System.out.println("标准URL: " + parserCreate.getShareLinkInfo().getStandardUrl());
|
||||
System.out.println();
|
||||
|
||||
System.out.println("开始解析下载链接...");
|
||||
|
||||
// 创建工具并解析
|
||||
parserCreate.createTool()
|
||||
.parse()
|
||||
.onSuccess(downloadUrl -> {
|
||||
System.out.println("✓ 解析成功!");
|
||||
System.out.println("下载直链: " + downloadUrl);
|
||||
System.out.println();
|
||||
|
||||
// 解析文件信息
|
||||
JsonObject fileInfo = getFileInfo(downloadUrl);
|
||||
System.out.println("文件信息: " + fileInfo.encodePrettily());
|
||||
System.out.println();
|
||||
})
|
||||
.onFailure(error -> {
|
||||
System.err.println("✗ 解析失败!");
|
||||
System.err.println("错误信息: " + error.getMessage());
|
||||
error.printStackTrace();
|
||||
});
|
||||
|
||||
// 等待异步结果
|
||||
System.out.println("等待解析结果...");
|
||||
TimeUnit.SECONDS.sleep(10);
|
||||
|
||||
System.out.println("======= 测试结束 =======");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 WPS 下载直链中提取文件信息
|
||||
* 示例链接: https://hwc-bj.ag.kdocs.cn/api/object/xxx/compatible?response-content-disposition=attachment%3Bfilename%2A%3Dutf-8%27%27reset_navicat_mac.sh&AccessKeyId=xxx&Expires=1760928746&Signature=xxx
|
||||
*
|
||||
* @param downloadUrl WPS 下载直链
|
||||
* @return JSON 格式的文件信息 {fileName: "reset_navicat_mac.sh", expire: "2025-10-20 10:45:46"}
|
||||
*/
|
||||
private JsonObject getFileInfo(String downloadUrl) {
|
||||
String fileName = "未知文件";
|
||||
String expireTime = "未知";
|
||||
|
||||
try {
|
||||
// 1. 提取文件名 - 从 response-content-disposition 参数中提取
|
||||
// 格式: attachment%3Bfilename%2A%3Dutf-8%27%27reset_navicat_mac.sh
|
||||
// 解码后: attachment;filename*=utf-8''reset_navicat_mac.sh
|
||||
Pattern fileNamePattern = Pattern.compile("filename[^=]*=(?:utf-8'')?([^&]+)");
|
||||
Matcher fileNameMatcher = fileNamePattern.matcher(URLDecoder.decode(downloadUrl, StandardCharsets.UTF_8));
|
||||
if (fileNameMatcher.find()) {
|
||||
fileName = fileNameMatcher.group(1);
|
||||
// 再次解码(可能被双重编码)
|
||||
fileName = URLDecoder.decode(fileName, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
// 2. 提取有效期 - 从 Expires 参数中提取 Unix timestamp
|
||||
Pattern expiresPattern = Pattern.compile("[?&]Expires=([0-9]+)");
|
||||
Matcher expiresMatcher = expiresPattern.matcher(downloadUrl);
|
||||
if (expiresMatcher.find()) {
|
||||
long timestamp = Long.parseLong(expiresMatcher.group(1));
|
||||
// 转换为日期格式 yyyy-MM-dd HH:mm:ss
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
expireTime = sdf.format(new Date(timestamp * 1000L)); // Unix timestamp 是秒,需要转毫秒
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("解析文件信息失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return JsonObject.of("fileName", fileName, "expire", expireTime);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user