feat: 乐云 directDownload 接口支持 & 缓存配置补充完善

- 新增 directDownload (GET) 接口,比 packageDownloadWithFileIds 少一次请求
- 每次随机选择下载方式,失败自动 fallback 到另一种
- 统一所有下载方法的 Promise 参数传递
- 添加 HTTP 状态码日志便于调试
- 优化 app-dev.yml 缓存配置注释,补充所有缺失的网盘类型
This commit is contained in:
yukaidi
2026-06-05 22:46:25 +08:00
parent 0fd78defcb
commit bca4da4b6c
2 changed files with 157 additions and 34 deletions

View File

@@ -15,6 +15,7 @@ import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Random;
import java.util.UUID;
/**
@@ -24,6 +25,7 @@ public class LeTool extends PanBase {
private static final String API_URL_PREFIX = "https://lecloud.lenovo.com/mshare/api/clouddiskapi/share/public/v1/";
private static final String DEFAULT_FILE_TYPE = "file";
private static final int FILE_TYPE_DIRECTORY = 0; // 目录类型
private static final Random RANDOM = new Random();
private static final MultiMap HEADERS;
@@ -100,8 +102,8 @@ public class LeTool extends PanBase {
}
String fileId = fileInfoJson.getString("fileId");
// 根据文件ID获取跳转链接
getDownURL(dataKey, fileId);
// 根据文件ID获取跳转链接随机选择方式失败自动fallback
getDownURLWithFallback(dataKey, fileId);
}
} else {
fail("{}: {}", resJson.getString("errcode"), resJson.getString("errmsg"));
@@ -260,8 +262,8 @@ public class LeTool extends PanBase {
String shareId = paramJson.getString("shareId");
String fileId = paramJson.getString("fileId");
// 调用获取下载链接
getDownURLForById(shareId, fileId, parsePromise);
// 调用获取下载链接随机选择方式失败自动fallback
getDownURLWithFallbackForById(shareId, fileId, parsePromise);
} catch (Exception e) {
parsePromise.fail("解析参数失败: " + e.getMessage());
@@ -304,14 +306,22 @@ public class LeTool extends PanBase {
}).onFailure(err -> promise.fail(err));
}
private void getDownURL(String key, String fileId) {
/**
* 通过 packageDownloadWithFileIds 接口获取下载链接
* 需要两步:先获取 downloadUrl再请求 302 跳转
*
* @param shareId 分享ID
* @param fileId 文件ID
* @param promise 完成时会写入此 promise
*/
private void getDownURL(String shareId, String fileId, Promise<String> promise) {
String uuid = UUID.randomUUID().toString();
JsonArray fileIds = JsonArray.of(fileId);
String apiUrl2 = API_URL_PREFIX + "packageDownloadWithFileIds";
String apiUrl = API_URL_PREFIX + "packageDownloadWithFileIds";
// {"fileIds":[123],"shareId":"xxx","browserId":"uuid"}
client.postAbs(apiUrl2)
client.postAbs(apiUrl)
.putHeaders(HEADERS)
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", key, "browserId", uuid))
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", shareId, "browserId", uuid))
.onSuccess(res -> {
JsonObject resJson = asJson(res);
if (resJson.containsKey("result")) {
@@ -320,20 +330,107 @@ public class LeTool extends PanBase {
// 获取重定向链接跳转链接
String downloadUrl = dataJson.getString("downloadUrl");
if (downloadUrl == null) {
fail("Result JSON数据异常: downloadUrl不存在");
promise.fail("Result JSON数据异常: downloadUrl不存在");
return;
}
// 获取重定向链接跳转链接
clientNoRedirects.getAbs(downloadUrl).send()
.onSuccess(res2 -> promise.complete(res2.headers().get("Location")))
.onFailure(handleFail(downloadUrl));
.onFailure(err -> promise.fail(err));
} else {
fail("{}: {}", resJson.getString("errcode"), resJson.getString("errmsg"));
promise.fail(resJson.getString("errcode") + ": " + resJson.getString("errmsg"));
}
} else {
fail("Result JSON数据异常: result字段不存在");
promise.fail("Result JSON数据异常: result字段不存在");
}
}).onFailure(handleFail(apiUrl2));
}).onFailure(err -> promise.fail(err));
}
/**
* 通过 directDownload 接口获取下载链接
* 相比 packageDownloadWithFileIds 少一次请求直接返回302
*
* @param shareId 分享ID
* @param fileId 文件ID
* @param promise 完成时会写入此 promise
*/
private void getDownURLDirect(String shareId, String fileId, Promise<String> promise) {
String uuid = UUID.randomUUID().toString();
String apiUrl = API_URL_PREFIX + "directDownload"
+ "?shareId=" + shareId
+ "&fileId=" + fileId
+ "&browserId=" + uuid;
clientNoRedirects.getAbs(apiUrl)
.putHeaders(HEADERS)
.send()
.onSuccess(res -> {
String location = res.headers().get("Location");
if (location != null && !location.isEmpty()) {
promise.complete(location);
} else {
log.warn("directDownload 返回非302响应: shareId={}, fileId={}, statusCode={}", shareId, fileId, res.statusCode());
promise.fail("directDownload 未返回有效的 Location, statusCode=" + res.statusCode());
}
})
.onFailure(err -> {
log.warn("directDownload 请求失败: shareId={}, fileId={}, error={}", shareId, fileId, err.getMessage());
promise.fail(err);
});
}
/**
* 随机选择下载方式并带 fallback用于 parse
* 先随机选择 directDownload 或 packageDownloadWithFileIds失败则尝试另一个
*/
private void getDownURLWithFallback(String shareId, String fileId) {
boolean useDirect = RANDOM.nextBoolean();
log.info("乐云下载方式选择: shareId={}, fileId={}, method={}", shareId, fileId, useDirect ? "directDownload" : "packageDownloadWithFileIds");
Promise<String> fallbackPromise = Promise.promise();
fallbackPromise.future().onSuccess(url -> {
promise.complete(url);
}).onFailure(err -> {
log.warn("乐云第一种下载方式失败,尝试另一种: {}", err.getMessage());
if (useDirect) {
getDownURL(shareId, fileId, promise);
} else {
getDownURLDirect(shareId, fileId, promise);
}
});
if (useDirect) {
getDownURLDirect(shareId, fileId, fallbackPromise);
} else {
getDownURL(shareId, fileId, fallbackPromise);
}
}
/**
* 随机选择下载方式并带 fallback用于 parseById
* 先随机选择 directDownload 或 packageDownloadWithFileIds失败则尝试另一个
*/
private void getDownURLWithFallbackForById(String shareId, String fileId, Promise<String> promise) {
boolean useDirect = RANDOM.nextBoolean();
log.info("乐云下载方式选择(parseById): shareId={}, fileId={}, method={}", shareId, fileId, useDirect ? "directDownload" : "packageDownloadWithFileIds");
Promise<String> fallbackPromise = Promise.promise();
fallbackPromise.future().onSuccess(url -> {
promise.complete(url);
}).onFailure(err -> {
log.warn("乐云第一种下载方式失败,尝试另一种: {}", err.getMessage());
if (useDirect) {
getDownURLForById(shareId, fileId, promise);
} else {
getDownURLDirect(shareId, fileId, promise);
}
});
if (useDirect) {
getDownURLDirect(shareId, fileId, fallbackPromise);
} else {
getDownURLForById(shareId, fileId, fallbackPromise);
}
}
/**