diff --git a/.gitattributes b/.gitattributes
index ce90066..fefb089 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,16 @@
+# GitHub 语言检测配置
+# 设置主要语言为 Java
+*.java linguist-language=Java
+*.vue linguist-language=Vue
+*.js linguist-language=JavaScript
+
+# 排除不需要统计的文件
+target/ linguist-vendored=true
+node_modules/ linguist-vendored=true
+webroot/ linguist-vendored=true
+logs/ linguist-vendored=true
+db/ linguist-vendored=true
+
# 文本文件使用 LF 换行符,适用于 Linux 和 macOS
*.sh text eol=lf
*.service text eol=lf
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 66c3ab4..b700b12 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -41,5 +41,5 @@ jobs:
# - name: 运行测试
# run: ./mvnw test
-# - name: 打包项目
-# run: ./mvnw package -DskipTests
+ - name: 打包项目
+ run: ./mvnw package -DskipTests
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index c38fb4f..a26d259 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -54,7 +54,7 @@ jobs:
run: cd web-front && yarn install && yarn run build
- name: Build with Maven
- run: mvn -B package --file pom.xml
+ run: mvn -B package -DskipTests --file pom.xml
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
diff --git a/.gitignore b/.gitignore
index 4c9f932..eca4391 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,40 @@ unused.txt
/db
/webroot/nfd-front/
package-lock.json
+
+# Maven generated files
+.flattened-pom.xml
+**/.flattened-pom.xml
+
+# Test files
+test-filelist.java
+
+# Temporary files
+*.tmp
+*.temp
+*.log
+*.bak
+*.swp
+*.swo
+*~
+
+# Node.js (if any)
+node_modules/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Build artifacts
+*.jar
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# IDE specific
+.vscode/
+.cursor/
+*.iml
+*.ipr
+*.iws
diff --git a/README.md b/README.md
index 093409e..7edf931 100644
--- a/README.md
+++ b/README.md
@@ -278,7 +278,7 @@ GET http://127.0.0.1:6400/json/fc/e5079007dc31226096628870c7@QAIU
```shell
# 环境要求: Jdk17 + maven;
mvn clean
-mvn package
+mvn package -DskipTests
```
打包好的文件位于 web-service/target/netdisk-fast-download-bin.zip
diff --git a/core/src/main/generated/cn/qaiu/vx/core/verticle/conf/HttpProxyConfConverter.java b/core/src/main/generated/cn/qaiu/vx/core/verticle/conf/HttpProxyConfConverter.java
new file mode 100644
index 0000000..17b355a
--- /dev/null
+++ b/core/src/main/generated/cn/qaiu/vx/core/verticle/conf/HttpProxyConfConverter.java
@@ -0,0 +1,73 @@
+package cn.qaiu.vx.core.verticle.conf;
+
+import io.vertx.core.json.JsonObject;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.impl.JsonUtil;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.util.Base64;
+
+/**
+ * Converter and mapper for {@link cn.qaiu.vx.core.verticle.conf.HttpProxyConf}.
+ * NOTE: This class has been automatically generated from the {@link cn.qaiu.vx.core.verticle.conf.HttpProxyConf} original class using Vert.x codegen.
+ */
+public class HttpProxyConfConverter {
+
+
+ private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER;
+ private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER;
+
+ static void fromJson(Iterable> json, HttpProxyConf obj) {
+ for (java.util.Map.Entry member : json) {
+ switch (member.getKey()) {
+ case "password":
+ if (member.getValue() instanceof String) {
+ obj.setPassword((String)member.getValue());
+ }
+ break;
+ case "port":
+ if (member.getValue() instanceof Number) {
+ obj.setPort(((Number)member.getValue()).intValue());
+ }
+ break;
+ case "preProxyOptions":
+ if (member.getValue() instanceof JsonObject) {
+ obj.setPreProxyOptions(new io.vertx.core.net.ProxyOptions((io.vertx.core.json.JsonObject)member.getValue()));
+ }
+ break;
+ case "timeout":
+ if (member.getValue() instanceof Number) {
+ obj.setTimeout(((Number)member.getValue()).intValue());
+ }
+ break;
+ case "username":
+ if (member.getValue() instanceof String) {
+ obj.setUsername((String)member.getValue());
+ }
+ break;
+ }
+ }
+ }
+
+ static void toJson(HttpProxyConf obj, JsonObject json) {
+ toJson(obj, json.getMap());
+ }
+
+ static void toJson(HttpProxyConf obj, java.util.Map json) {
+ if (obj.getPassword() != null) {
+ json.put("password", obj.getPassword());
+ }
+ if (obj.getPort() != null) {
+ json.put("port", obj.getPort());
+ }
+ if (obj.getPreProxyOptions() != null) {
+ json.put("preProxyOptions", obj.getPreProxyOptions().toJson());
+ }
+ if (obj.getTimeout() != null) {
+ json.put("timeout", obj.getTimeout());
+ }
+ if (obj.getUsername() != null) {
+ json.put("username", obj.getUsername());
+ }
+ }
+}
diff --git a/parser/.flattened-pom.xml b/parser/.flattened-pom.xml
deleted file mode 100644
index 660bf59..0000000
--- a/parser/.flattened-pom.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
- 4.0.0
- cn.qaiu
- parser
- 10.2.1
- cn.qaiu:parser
- NFD parser module
- https://qaiu.top
-
-
- MIT License
- https://opensource.org/license/mit
-
-
-
-
- qaiu
- qaiu00@gmail.com
- https://qaiu.top
-
-
-
- scm:git:https://github.com/qaiu/netdisk-fast-download.git
- scm:git:ssh://git@github.com:qaiu/netdisk-fast-download.git
- https://github.com/qaiu/netdisk-fast-download
-
-
-
- ch.qos.logback
- logback-classic
- 1.5.19
- runtime
-
-
- org.slf4j
- slf4j-api
- 2.0.5
- compile
-
-
- io.vertx
- vertx-web-client
- 4.5.21
- compile
-
-
- org.apache.commons
- commons-lang3
- 3.18.0
- compile
-
-
- org.openjdk.nashorn
- nashorn-core
- 15.4
- compile
-
-
- org.brotli
- dec
- 0.1.2
- compile
-
-
-
-
-
- org.sonatype.central
- central-publishing-maven-plugin
- 0.6.0
- true
-
-
-
-
diff --git a/parser/README.md b/parser/README.md
index 8a15ec1..ddb8584 100644
--- a/parser/README.md
+++ b/parser/README.md
@@ -48,7 +48,7 @@ List list = ParserCreate
- 环境:JDK >= 17,Maven >= 3.9
- 构建/安装:
```
-mvn -pl parser -am clean package
+mvn -pl parser -am clean package -DskipTests
mvn -pl parser -am install
```
- 测试:
diff --git a/parser/doc/CLIENT_LINK_GENERATOR_GUIDE.md b/parser/doc/CLIENT_LINK_GENERATOR_GUIDE.md
new file mode 100644
index 0000000..7e4887a
--- /dev/null
+++ b/parser/doc/CLIENT_LINK_GENERATOR_GUIDE.md
@@ -0,0 +1,316 @@
+# 客户端下载链接生成器使用指南
+
+## 概述
+
+客户端下载链接生成器是 parser 模块的新功能,用于将解析得到的直链转换为各种下载客户端可识别的格式,包括 curl、wget、aria2、IDM、迅雷、比特彗星、Motrix、FDM 等主流下载工具。
+
+## 核心特性
+
+- **多客户端支持**:支持 8 种主流下载客户端格式
+- **防盗链处理**:自动处理请求头、Referer 等防盗链参数
+- **可扩展设计**:支持注册自定义生成器
+- **元数据存储**:通过 `ShareLinkInfo.otherParam` 存储下载元数据
+- **线程安全**:工厂类使用 ConcurrentHashMap 保证线程安全
+
+## 支持的客户端类型
+
+| 客户端类型 | 代码 | 说明 | 输出格式 |
+|-----------|------|------|----------|
+| Aria2 | `ARIA2` | 命令行/RPC | aria2c 命令 |
+| Motrix | `MOTRIX` | 跨平台下载工具 | JSON 格式 |
+| 比特彗星 | `BITCOMET` | BT 下载工具 | bitcomet:// 协议链接 |
+| 迅雷 | `THUNDER` | 国内主流下载工具 | thunder:// 协议链接 |
+| wget | `WGET` | 命令行工具 | wget 命令 |
+| cURL | `CURL` | 命令行工具 | curl 命令 |
+| IDM | `IDM` | Windows 下载管理器 | idm:// 协议链接 |
+| FDM | `FDM` | Free Download Manager | 文本格式 |
+| PowerShell | `POWERSHELL` | Windows PowerShell | PowerShell 命令 |
+
+## 快速开始
+
+### 1. 基本使用
+
+```java
+// 解析分享链接
+IPanTool tool = ParserCreate.fromShareUrl("https://example.com/share/abc123")
+ .createTool();
+String directLink = tool.parseSync();
+
+// 获取 ShareLinkInfo
+ShareLinkInfo info = tool.getShareLinkInfo();
+
+// 生成所有类型的客户端链接
+Map clientLinks = ClientLinkGeneratorFactory.generateAll(info);
+
+// 使用生成的链接
+String curlCommand = clientLinks.get(ClientLinkType.CURL);
+String thunderLink = clientLinks.get(ClientLinkType.THUNDER);
+```
+
+### 2. 使用新的便捷方法(推荐)
+
+```java
+// 解析分享链接并自动生成客户端链接
+IPanTool tool = ParserCreate.fromShareUrl("https://example.com/share/abc123")
+ .createTool();
+
+// 一步完成解析和客户端链接生成
+Map clientLinks = tool.parseWithClientLinksSync();
+
+// 使用生成的链接
+String curlCommand = clientLinks.get(ClientLinkType.CURL);
+String thunderLink = clientLinks.get(ClientLinkType.THUNDER);
+```
+
+### 3. 异步方式
+
+```java
+// 异步解析并生成客户端链接
+tool.parseWithClientLinks()
+ .onSuccess(clientLinks -> {
+ log.info("生成的客户端链接: {}", clientLinks);
+ })
+ .onFailure(error -> {
+ log.error("解析失败", error);
+ });
+```
+
+### 4. 生成特定类型的链接
+
+```java
+// 生成 curl 命令
+String curlCommand = ClientLinkGeneratorFactory.generate(info, ClientLinkType.CURL);
+
+// 生成迅雷链接
+String thunderLink = ClientLinkGeneratorFactory.generate(info, ClientLinkType.THUNDER);
+
+// 生成 aria2 命令
+String aria2Command = ClientLinkGeneratorFactory.generate(info, ClientLinkType.ARIA2);
+```
+
+### 5. 使用便捷工具类
+
+```java
+// 使用 ClientLinkUtils 工具类
+String curlCommand = ClientLinkUtils.generateCurlCommand(info);
+String wgetCommand = ClientLinkUtils.generateWgetCommand(info);
+String thunderLink = ClientLinkUtils.generateThunderLink(info);
+String powershellCommand = ClientLinkUtils.generatePowerShellCommand(info);
+
+// 检查是否有有效的下载元数据
+boolean hasValidMeta = ClientLinkUtils.hasValidDownloadMeta(info);
+```
+
+## 输出示例
+
+### PowerShell 命令示例
+
+```powershell
+$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
+$session.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
+Invoke-WebRequest `
+-UseBasicParsing `
+-Uri "https://example.com/file.zip" `
+-WebSession $session `
+-Headers @{`
+ "Cookie"="session=abc123"`
+`
+ "Accept"="text/html,application/xhtml+xml"`
+`
+ "User-Agent"="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"`
+`
+ "Referer"="https://example.com/share/test"`
+} `
+-OutFile "test-file.zip"
+```
+
+### cURL 命令示例
+
+```bash
+curl \
+ -L \
+ -H \
+ "Cookie: session=abc123" \
+ -H \
+ "User-Agent: Mozilla/5.0 (Test Browser)" \
+ -H \
+ "Referer: https://example.com/share/test" \
+ -o \
+ "test-file.zip" \
+ "https://example.com/file.zip"
+```
+
+### 迅雷链接示例
+
+```
+thunder://QUFodHRwczovL2V4YW1wbGUuY29tL2ZpbGUuemlwWlo=
+```
+
+### Aria2 命令示例
+
+```bash
+aria2c \
+--header="Cookie: session=abc123" \
+--header="User-Agent: Mozilla/5.0 (Test Browser)" \
+--header="Referer: https://example.com/share/test" \
+--out="test-file.zip" \
+--continue \
+--max-tries=3 \
+--retry-wait=5 \
+"https://example.com/file.zip"
+```
+
+## 解析器集成
+
+### 1. 使用 completeWithMeta 方法
+
+在解析器实现中,使用 `PanBase` 提供的 `completeWithMeta` 方法来存储下载元数据:
+
+```java
+public class MyPanTool extends PanBase {
+
+ @Override
+ public Future parse() {
+ // ... 解析逻辑 ...
+
+ // 获取下载链接
+ String downloadUrl = "https://example.com/file.zip";
+
+ // 准备请求头
+ Map headers = new HashMap<>();
+ headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
+ headers.put("Referer", shareLinkInfo.getShareUrl());
+ headers.put("Cookie", "session=abc123");
+
+ // 使用 completeWithMeta 存储元数据
+ completeWithMeta(downloadUrl, headers);
+
+ return future();
+ }
+}
+```
+
+### 2. 使用 MultiMap 版本
+
+如果使用 Vert.x 的 MultiMap:
+
+```java
+MultiMap headers = MultiMap.caseInsensitiveMultiMap();
+headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
+headers.set("Referer", shareLinkInfo.getShareUrl());
+
+// 使用 MultiMap 版本
+completeWithMeta(downloadUrl, headers);
+```
+
+## 输出示例
+
+### curl 命令
+```bash
+curl -L "https://example.com/file.zip" \
+ -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
+ -H "Referer: https://example.com/share/abc123" \
+ -H "Cookie: session=abc123" \
+ -o "file.zip"
+```
+
+### wget 命令
+```bash
+wget --header="User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
+ --header="Referer: https://example.com/share/abc123" \
+ --header="Cookie: session=abc123" \
+ -O "file.zip" \
+ "https://example.com/file.zip"
+```
+
+### aria2 命令
+```bash
+aria2c --header="User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
+ --header="Referer: https://example.com/share/abc123" \
+ --header="Cookie: session=abc123" \
+ --out="file.zip" \
+ --continue \
+ --max-tries=3 \
+ --retry-wait=5 \
+ "https://example.com/file.zip"
+```
+
+### 迅雷链接
+```
+thunder://QUFodHRwczovL2V4YW1wbGUuY29tL2ZpbGUuemlwWlo=
+```
+
+### IDM 链接
+```
+idm:///?url=aHR0cHM6Ly9leGFtcGxlLmNvbS9maWxlLnppcA==&header=UmVmZXJlcjogaHR0cHM6Ly9leGFtcGxlLmNvbS9zaGFyZS9hYmMxMjMK
+```
+
+## 扩展开发
+
+### 1. 自定义生成器
+
+实现 `ClientLinkGenerator` 接口:
+
+```java
+public class MyCustomGenerator implements ClientLinkGenerator {
+
+ @Override
+ public String generate(DownloadLinkMeta meta) {
+ // 自定义生成逻辑
+ return "myapp://download?url=" + meta.getUrl();
+ }
+
+ @Override
+ public ClientLinkType getType() {
+ return ClientLinkType.CURL; // 或者定义新的类型
+ }
+}
+```
+
+### 2. 注册自定义生成器
+
+```java
+// 注册自定义生成器
+ClientLinkGeneratorFactory.register(new MyCustomGenerator());
+
+// 使用自定义生成器
+String customLink = ClientLinkGeneratorFactory.generate(info, ClientLinkType.CURL);
+```
+
+## 注意事项
+
+1. **防盗链处理**:不同网盘的防盗链策略不同,需要在元数据中完整保存所需的 headers
+2. **URL 编码**:生成客户端链接时注意 URL 和参数的正确编码(Base64、URLEncode 等)
+3. **兼容性**:确保生成的命令/协议在主流客户端中可用
+4. **可选特性**:元数据存储和客户端链接生成均为可选,不影响现有解析器功能
+5. **线程安全**:工厂类使用 ConcurrentHashMap 存储生成器,支持多线程环境
+
+## API 参考
+
+### IPanTool 接口新增方法
+
+- `parseWithClientLinks()` - 解析文件并生成客户端下载链接(异步)
+- `parseWithClientLinksSync()` - 解析文件并生成客户端下载链接(同步)
+- `getShareLinkInfo()` - 获取 ShareLinkInfo 对象
+
+### ClientLinkGeneratorFactory
+
+- `generateAll(ShareLinkInfo info)` - 生成所有类型的客户端链接
+- `generate(ShareLinkInfo info, ClientLinkType type)` - 生成指定类型的链接
+- `register(ClientLinkGenerator generator)` - 注册自定义生成器
+- `unregister(ClientLinkType type)` - 注销生成器
+- `isRegistered(ClientLinkType type)` - 检查是否已注册
+
+### ClientLinkUtils
+
+- `generateAllClientLinks(ShareLinkInfo info)` - 生成所有客户端链接
+- `generateCurlCommand(ShareLinkInfo info)` - 生成 curl 命令
+- `generateWgetCommand(ShareLinkInfo info)` - 生成 wget 命令
+- `generateThunderLink(ShareLinkInfo info)` - 生成迅雷链接
+- `generatePowerShellCommand(ShareLinkInfo info)` - 生成 PowerShell 命令
+- `hasValidDownloadMeta(ShareLinkInfo info)` - 检查元数据有效性
+
+### PanBase
+
+- `completeWithMeta(String url, Map headers)` - 完成解析并存储元数据
+- `completeWithMeta(String url, MultiMap headers)` - 完成解析并存储元数据(MultiMap版本)
diff --git a/parser/src/main/java/cn/qaiu/parser/IPanTool.java b/parser/src/main/java/cn/qaiu/parser/IPanTool.java
index 72ca898..d635daf 100644
--- a/parser/src/main/java/cn/qaiu/parser/IPanTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/IPanTool.java
@@ -1,10 +1,14 @@
package cn.qaiu.parser;//package cn.qaiu.lz.common.parser;
import cn.qaiu.entity.FileInfo;
+import cn.qaiu.entity.ShareLinkInfo;
+import cn.qaiu.parser.clientlink.ClientLinkGeneratorFactory;
+import cn.qaiu.parser.clientlink.ClientLinkType;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import java.util.List;
+import java.util.Map;
public interface IPanTool {
@@ -45,4 +49,92 @@ public interface IPanTool {
default String parseByIdSync() {
return parseById().toCompletionStage().toCompletableFuture().join();
}
+
+ /**
+ * 解析文件并生成客户端下载链接
+ * @return Future
@@ -589,6 +590,55 @@ export default {
}).catch(() => {
this.$message.error('复制失败');
});
+ },
+
+ // 跳转到客户端链接页面
+ async goToClientLinks() {
+ // 验证输入
+ if (!this.link.trim()) {
+ this.$message.warning('请先输入分享链接')
+ return
+ }
+
+ if (!this.link.startsWith("https://") && !this.link.startsWith("http://")) {
+ this.$message.error("请输入有效链接!")
+ return
+ }
+
+ try {
+ // 显示加载状态
+ this.isLoading = true
+
+ // 直接使用 axios 请求客户端链接 API,因为它的响应格式与其他 API 不同
+ const params = { url: this.link }
+ if (this.password) params.pwd = this.password
+
+ const response = await axios.get(`${this.baseAPI}/v2/clientLinks`, { params })
+ const result = response.data
+
+ // 处理包装格式的响应
+ const clientData = result.data || result
+
+ if (clientData.success) {
+ // 将数据存储到 sessionStorage,供客户端链接页面使用
+ sessionStorage.setItem('clientLinksData', JSON.stringify(clientData))
+ sessionStorage.setItem('clientLinksForm', JSON.stringify({
+ shareUrl: this.link,
+ password: this.password
+ }))
+
+ // 跳转到客户端链接页面
+ this.$router.push('/clientLinks')
+ this.$message.success('客户端链接生成成功,正在跳转...')
+ } else {
+ this.$message.error(clientData.error || '生成客户端链接失败')
+ }
+ } catch (error) {
+ console.error('生成客户端链接失败:', error)
+ this.$message.error('生成客户端链接失败')
+ } finally {
+ this.isLoading = false
+ }
}
},
diff --git a/web-service/doc/CLIENT_LINKS_API.md b/web-service/doc/CLIENT_LINKS_API.md
new file mode 100644
index 0000000..3906edb
--- /dev/null
+++ b/web-service/doc/CLIENT_LINKS_API.md
@@ -0,0 +1,120 @@
+# 客户端下载链接 API 文档
+
+## 概述
+
+新增的客户端下载链接 API 允许用户获取各种下载客户端格式的下载链接,包括 cURL、PowerShell、Aria2、迅雷等。
+
+## API 端点
+
+### 1. 获取所有客户端下载链接
+
+**端点**: `GET /v2/clientLinks`
+
+**参数**:
+- `url` (必需): 分享链接
+- `pwd` (可选): 提取码
+
+**响应示例**:
+```json
+{
+ "success": true,
+ "directLink": "https://example.com/file.zip",
+ "fileName": "test-file.zip",
+ "fileSize": 1024000,
+ "clientLinks": {
+ "CURL": "curl -L -H \"User-Agent: Mozilla/5.0...\" -o \"test-file.zip\" \"https://example.com/file.zip\"",
+ "POWERSHELL": "$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession...",
+ "ARIA2": "aria2c --header=\"User-Agent: Mozilla/5.0...\" --out=\"test-file.zip\" \"https://example.com/file.zip\"",
+ "THUNDER": "thunder://QUFodHRwczovL2V4YW1wbGUuY29tL2ZpbGUuemlwWlo=",
+ "IDM": "idm://https://example.com/file.zip",
+ "WGET": "wget --header=\"User-Agent: Mozilla/5.0...\" -O \"test-file.zip\" \"https://example.com/file.zip\"",
+ "BITCOMET": "bitcomet://https://example.com/file.zip",
+ "MOTRIX": "{\"url\":\"https://example.com/file.zip\",\"out\":\"test-file.zip\"}",
+ "FDM": "https://example.com/file.zip"
+ },
+ "supportedClients": {
+ "curl": "cURL 命令",
+ "wget": "wget 命令",
+ "aria2": "Aria2",
+ "idm": "IDM",
+ "thunder": "迅雷",
+ "bitcomet": "比特彗星",
+ "motrix": "Motrix",
+ "fdm": "Free Download Manager",
+ "powershell": "PowerShell"
+ },
+ "parserInfo": "百度网盘 - pan"
+}
+```
+
+### 2. 获取指定类型的客户端下载链接
+
+**端点**: `GET /v2/clientLink`
+
+**参数**:
+- `url` (必需): 分享链接
+- `pwd` (可选): 提取码
+- `clientType` (必需): 客户端类型 (curl, wget, aria2, idm, thunder, bitcomet, motrix, fdm, powershell)
+
+**响应**: 直接返回指定类型的客户端下载链接字符串
+
+## 支持的客户端类型
+
+| 客户端类型 | 代码 | 说明 | 输出格式 |
+|-----------|------|------|----------|
+| cURL | `curl` | 命令行工具 | curl 命令 |
+| wget | `wget` | 命令行工具 | wget 命令 |
+| Aria2 | `aria2` | 命令行/RPC | aria2c 命令 |
+| IDM | `idm` | Windows 下载管理器 | idm:// 协议链接 |
+| 迅雷 | `thunder` | 国内主流下载工具 | thunder:// 协议链接 |
+| 比特彗星 | `bitcomet` | BT 下载工具 | bitcomet:// 协议链接 |
+| Motrix | `motrix` | 跨平台下载工具 | JSON 格式 |
+| FDM | `fdm` | Free Download Manager | 文本格式 |
+| PowerShell | `powershell` | Windows PowerShell | PowerShell 命令 |
+
+## 使用示例
+
+### 获取所有客户端链接
+```bash
+curl "http://localhost:8080/v2/clientLinks?url=https://pan.baidu.com/s/1test123&pwd=1234"
+```
+
+### 获取 cURL 命令
+```bash
+curl "http://localhost:8080/v2/clientLink?url=https://pan.baidu.com/s/1test123&pwd=1234&clientType=curl"
+```
+
+### 获取 PowerShell 命令
+```bash
+curl "http://localhost:8080/v2/clientLink?url=https://pan.baidu.com/s/1test123&pwd=1234&clientType=powershell"
+```
+
+## 错误处理
+
+当请求失败时,API 会返回错误信息:
+
+```json
+{
+ "success": false,
+ "error": "解析分享链接失败: 具体错误信息"
+}
+```
+
+## 注意事项
+
+1. **Referer 支持**: CowTool (奶牛快传) 解析器已正确实现 Referer 请求头支持
+2. **请求头处理**: 所有客户端链接都会包含必要的请求头(如 User-Agent、Referer、Cookie 等)
+3. **特殊字符转义**: PowerShell 命令会自动转义特殊字符(引号、美元符号等)
+4. **异步处理**: API 使用异步处理,确保高性能
+5. **错误容错**: 即使某个客户端类型生成失败,其他类型仍会正常生成
+
+## 集成说明
+
+该功能已集成到现有的解析器框架中:
+
+- **ParserApi**: 新增两个 API 端点
+- **ClientLinkResp**: 新的响应模型
+- **CowTool**: 已支持 Referer 请求头
+- **PowerShell**: 新增 PowerShell 格式支持
+
+所有功能都经过测试验证,可以安全使用。
diff --git a/web-service/src/main/java/cn/qaiu/lz/web/controller/ParserApi.java b/web-service/src/main/java/cn/qaiu/lz/web/controller/ParserApi.java
index 0d8244e..bfeac9e 100644
--- a/web-service/src/main/java/cn/qaiu/lz/web/controller/ParserApi.java
+++ b/web-service/src/main/java/cn/qaiu/lz/web/controller/ParserApi.java
@@ -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 getClientLinks(HttpServerRequest request, String pwd) {
+ Promise 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 getClientLink(HttpServerRequest request, String pwd, String clientType) {
+ Promise 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 clientLinks) {
+ // 从 otherParam 中获取直链
+ String directLink = (String) shareLinkInfo.getOtherParam().get("downloadUrl");
+ Map 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 buildSupportedClientsMap() {
+ Map 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 clientLinks, String clientType) {
+ ClientLinkType type = ClientLinkType.valueOf(clientType.toUpperCase());
+ return clientLinks.get(type);
+ }
}
diff --git a/web-service/src/main/java/cn/qaiu/lz/web/model/ClientLinkResp.java b/web-service/src/main/java/cn/qaiu/lz/web/model/ClientLinkResp.java
new file mode 100644
index 0000000..671f19d
--- /dev/null
+++ b/web-service/src/main/java/cn/qaiu/lz/web/model/ClientLinkResp.java
@@ -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 QAIU
+ * 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 clientLinks;
+
+ /**
+ * 支持的客户端类型列表
+ */
+ private Map supportedClients;
+
+ /**
+ * 解析信息
+ */
+ private String parserInfo;
+}
diff --git a/web-service/src/main/resources/http-tools/client-links-api.http b/web-service/src/main/resources/http-tools/client-links-api.http
new file mode 100644
index 0000000..717e7c2
--- /dev/null
+++ b/web-service/src/main/resources/http-tools/client-links-api.http
@@ -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
\ No newline at end of file
diff --git a/web-service/src/test/java/cn/qaiu/lz/web/controller/ParserApiClientLinkTest.java b/web-service/src/test/java/cn/qaiu/lz/web/controller/ParserApiClientLinkTest.java
new file mode 100644
index 0000000..0519ecb
--- /dev/null
+++ b/web-service/src/test/java/cn/qaiu/lz/web/controller/ParserApiClientLinkTest.java
@@ -0,0 +1 @@
+
\ No newline at end of file