feat: 新增客户端协议生成系统,支持8种主流下载工具

🚀 核心功能
- 新增完整的客户端下载链接生成器系统
- 支持ARIA2、Motrix、比特彗星、迅雷、wget、cURL、IDM、FDM、PowerShell等8种客户端
- 自动处理防盗链参数(User-Agent、Referer、Cookie等)
- 提供可扩展的生成器架构,支持自定义客户端

🔧 技术实现
- ClientLinkGeneratorFactory: 工厂模式管理生成器
- DownloadLinkMeta: 元数据存储下载信息
- ClientLinkUtils: 便捷工具类
- 线程安全的ConcurrentHashMap设计

🌐 前端集成
- 新增ClientLinks.vue界面,支持客户端链接展示
- Element Plus图标系统,混合图标显示
- 客户端检测逻辑优化,避免自动打开外部应用
- 移动端和PC端环境判断

📚 文档完善
- 完整的CLIENT_LINK_GENERATOR_GUIDE.md使用指南
- API文档和测试用例
- 输出示例和最佳实践

从单纯的网盘解析工具升级为完整的下载解决方案生态
This commit is contained in:
q
2025-10-24 09:25:57 +08:00
parent 231d5c3fb9
commit 42b721eabf
47 changed files with 3740 additions and 96 deletions

View File

@@ -6,11 +6,13 @@ import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.lz.common.cache.CacheManager;
import cn.qaiu.lz.common.util.URLParamUtil;
import cn.qaiu.lz.web.model.CacheLinkInfo;
import cn.qaiu.lz.web.model.ClientLinkResp;
import cn.qaiu.lz.web.model.LinkInfoResp;
import cn.qaiu.lz.web.model.StatisticsInfo;
import cn.qaiu.lz.web.service.DbService;
import cn.qaiu.parser.PanDomainTemplate;
import cn.qaiu.parser.ParserCreate;
import cn.qaiu.parser.clientlink.ClientLinkType;
import cn.qaiu.vx.core.annotaions.RouteHandler;
import cn.qaiu.vx.core.annotaions.RouteMapping;
import cn.qaiu.vx.core.enums.RouteMethod;
@@ -244,4 +246,151 @@ public class ParserApi {
.replace("-", "")
.replace(":", "");
}
/**
* 获取客户端下载链接
*
* @param request HTTP请求
* @param pwd 提取码
* @return 客户端下载链接响应
*/
@RouteMapping(value = "/clientLinks", method = RouteMethod.GET)
public Future<ClientLinkResp> getClientLinks(HttpServerRequest request, String pwd) {
Promise<ClientLinkResp> promise = Promise.promise();
try {
String shareUrl = URLParamUtil.parserParams(request);
ParserCreate parserCreate = ParserCreate.fromShareUrl(shareUrl).setShareLinkInfoPwd(pwd);
ShareLinkInfo shareLinkInfo = parserCreate.getShareLinkInfo();
// 使用默认方法解析并生成客户端链接
parserCreate.createTool().parseWithClientLinks()
.onSuccess(clientLinks -> {
try {
ClientLinkResp response = buildClientLinkResponse(shareLinkInfo, clientLinks);
promise.complete(response);
} catch (Exception e) {
log.error("处理客户端链接结果失败", e);
promise.fail(new RuntimeException("处理客户端链接结果失败: " + e.getMessage()));
}
})
.onFailure(error -> {
log.error("解析分享链接失败", error);
promise.fail(new RuntimeException("解析分享链接失败: " + error.getMessage()));
});
} catch (Exception e) {
log.error("解析请求参数失败", e);
promise.fail(new RuntimeException("解析请求参数失败: " + e.getMessage()));
}
return promise.future();
}
/**
* 获取指定类型的客户端下载链接
*
* @param request HTTP请求
* @param pwd 提取码
* @param clientType 客户端类型 (curl, wget, aria2, idm, thunder, bitcomet, motrix, fdm, powershell)
* @return 指定类型的客户端下载链接
*/
@RouteMapping(value = "/clientLink", method = RouteMethod.GET)
public Future<String> getClientLink(HttpServerRequest request, String pwd, String clientType) {
Promise<String> promise = Promise.promise();
try {
String shareUrl = URLParamUtil.parserParams(request);
ParserCreate parserCreate = ParserCreate.fromShareUrl(shareUrl).setShareLinkInfoPwd(pwd);
// 使用默认方法解析并生成客户端链接
parserCreate.createTool().parseWithClientLinks()
.onSuccess(clientLinks -> {
try {
String clientLink = extractClientLinkByType(clientLinks, clientType);
if (clientLink != null) {
promise.complete(clientLink);
} else {
promise.fail("无法生成 " + clientType + " 格式的下载链接");
}
} catch (IllegalArgumentException e) {
promise.fail("不支持的客户端类型: " + clientType);
} catch (Exception e) {
log.error("获取客户端链接失败", e);
promise.fail("获取客户端链接失败: " + e.getMessage());
}
})
.onFailure(error -> {
log.error("解析分享链接失败", error);
promise.fail("解析分享链接失败: " + error.getMessage());
});
} catch (Exception e) {
log.error("解析请求参数失败", e);
promise.fail("解析请求参数失败: " + e.getMessage());
}
return promise.future();
}
/**
* 构建客户端链接响应
*
* @param shareLinkInfo 分享链接信息
* @param clientLinks 客户端链接映射
* @return 客户端链接响应
*/
private ClientLinkResp buildClientLinkResponse(ShareLinkInfo shareLinkInfo, Map<ClientLinkType, String> clientLinks) {
// 从 otherParam 中获取直链
String directLink = (String) shareLinkInfo.getOtherParam().get("downloadUrl");
Map<String, String> supportedClients = buildSupportedClientsMap();
FileInfo fileInfo = extractFileInfo(shareLinkInfo);
return ClientLinkResp.builder()
.success(true)
.directLink(directLink)
.fileName(fileInfo != null ? fileInfo.getFileName() : null)
.fileSize(fileInfo != null ? fileInfo.getSize() : null)
.clientLinks(clientLinks)
.supportedClients(supportedClients)
.parserInfo(shareLinkInfo.getPanName() + " - " + shareLinkInfo.getType())
.build();
}
/**
* 构建支持的客户端类型映射
*
* @return 客户端类型映射
*/
private Map<String, String> buildSupportedClientsMap() {
Map<String, String> supportedClients = new HashMap<>();
for (ClientLinkType type : ClientLinkType.values()) {
supportedClients.put(type.getCode(), type.getDisplayName());
}
return supportedClients;
}
/**
* 从ShareLinkInfo中提取文件信息
*
* @param shareLinkInfo 分享链接信息
* @return 文件信息如果不存在则返回null
*/
private FileInfo extractFileInfo(ShareLinkInfo shareLinkInfo) {
Object fileInfo = shareLinkInfo.getOtherParam().get("fileInfo");
return fileInfo instanceof FileInfo ? (FileInfo) fileInfo : null;
}
/**
* 根据客户端类型提取对应的客户端链接
*
* @param clientLinks 客户端链接映射
* @param clientType 客户端类型
* @return 客户端链接如果不存在则返回null
* @throws IllegalArgumentException 如果客户端类型不支持
*/
private String extractClientLinkByType(Map<ClientLinkType, String> clientLinks, String clientType) {
ClientLinkType type = ClientLinkType.valueOf(clientType.toUpperCase());
return clientLinks.get(type);
}
}

View File

@@ -0,0 +1,62 @@
package cn.qaiu.lz.web.model;
import cn.qaiu.parser.clientlink.ClientLinkType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* 客户端下载链接响应模型
*
* @author <a href="https://qaiu.top">QAIU</a>
* Create at 2025/01/21
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ClientLinkResp {
/**
* 是否成功
*/
private boolean success;
/**
* 错误信息
*/
private String error;
/**
* 直链URL
*/
private String directLink;
/**
* 文件名
*/
private String fileName;
/**
* 文件大小
*/
private Long fileSize;
/**
* 所有客户端下载链接
*/
private Map<ClientLinkType, String> clientLinks;
/**
* 支持的客户端类型列表
*/
private Map<String, String> supportedClients;
/**
* 解析信息
*/
private String parserInfo;
}

View File

@@ -0,0 +1,51 @@
### 客户端下载链接 API 测试
### 环境变量
@host = http://localhost:6400
@testUrl = https://www.kdocs.cn/l/ck0azivLlDi3
@testPwd =
@cowUrl = https://cowtransfer.com/s/test123
@lanzouUrl = https://wwsd.lanzoue.com/iLany1e9bbbi
### 1. 获取所有客户端下载链接
GET {{host}}/v2/clientLinks?url={{lanzouUrl}}&pwd={{testPwd}}
### 2. 获取指定类型的客户端下载链接 - cURL
GET {{host}}/v2/clientLink?url={{testUrl}}&pwd={{testPwd}}&clientType=curl
### 3. 获取指定类型的客户端下载链接 - PowerShell
GET {{host}}/v2/clientLink?url={{testUrl}}&pwd={{testPwd}}&clientType=powershell
### 4. 获取指定类型的客户端下载链接 - Aria2
GET {{host}}/v2/clientLink?url={{testUrl}}&pwd={{testPwd}}&clientType=aria2
### 5. 获取指定类型的客户端下载链接 - 迅雷
GET {{host}}/v2/clientLink?url={{testUrl}}&pwd={{testPwd}}&clientType=thunder
### 6. 获取指定类型的客户端下载链接 - IDM
GET {{host}}/v2/clientLink?url={{testUrl}}&pwd={{testPwd}}&clientType=idm
### 7. 获取指定类型的客户端下载链接 - wget
GET {{host}}/v2/clientLink?url={{testUrl}}&pwd={{testPwd}}&clientType=wget
### 8. 获取指定类型的客户端下载链接 - 比特彗星
GET {{host}}/v2/clientLink?url={{testUrl}}&pwd={{testPwd}}&clientType=bitcomet
### 9. 获取指定类型的客户端下载链接 - Motrix
GET {{host}}/v2/clientLink?url={{testUrl}}&pwd={{testPwd}}&clientType=motrix
### 10. 获取指定类型的客户端下载链接 - FDM
GET {{host}}/v2/clientLink?url={{testUrl}}&pwd={{testPwd}}&clientType=fdm
### 11. 测试不支持的客户端类型
GET {{host}}/v2/clientLink?url={{testUrl}}&pwd={{testPwd}}&clientType=invalid
### 12. 测试奶牛快传(需要 Referer
GET {{host}}/v2/clientLinks?url={{cowUrl}}&pwd=
### 13. 测试空参数
GET {{host}}/v2/clientLinks
### 14. 测试无效URL
GET {{host}}/v2/clientLinks?url=invalid-url&pwd=1234