Compare commits

...

2 Commits

Author SHA1 Message Date
q
69d5f269bd fix(LeTool): 修复联想乐云目录解析失败问题
- 添加统一的 HEADERS 定义,包含完整的浏览器请求头
- 修复 API_URL_PREFIX 路径(share -> mshare)
- 添加 getCleanShareId() 方法处理 URL 中的查询参数
- 所有请求统一使用 putHeaders(HEADERS)
- 增加调试日志输出
2026-01-23 13:24:11 +08:00
q
459c974cb8 联想乐云文件夹解析 2026-01-23 12:45:51 +08:00

View File

@@ -5,6 +5,7 @@ import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase; import cn.qaiu.parser.PanBase;
import cn.qaiu.util.FileSizeConverter; import cn.qaiu.util.FileSizeConverter;
import io.vertx.core.Future; import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Promise; import io.vertx.core.Promise;
import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonObject;
@@ -22,17 +23,48 @@ public class LeTool extends PanBase {
private static final String DEFAULT_FILE_TYPE = "file"; private static final String DEFAULT_FILE_TYPE = "file";
private static final int FILE_TYPE_DIRECTORY = 0; // 目录类型 private static final int FILE_TYPE_DIRECTORY = 0; // 目录类型
private static final MultiMap HEADERS;
static {
HEADERS = MultiMap.caseInsensitiveMultiMap();
HEADERS.set("Accept", "application/json, text/plain, */*");
HEADERS.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6");
HEADERS.set("Cache-Control", "no-cache");
HEADERS.set("Connection", "keep-alive");
HEADERS.set("Content-Type", "application/json");
HEADERS.set("DNT", "1");
HEADERS.set("Origin", "https://lecloud.lenovo.com");
HEADERS.set("Pragma", "no-cache");
HEADERS.set("Sec-Fetch-Dest", "empty");
HEADERS.set("Sec-Fetch-Mode", "cors");
HEADERS.set("Sec-Fetch-Site", "same-origin");
HEADERS.set("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1 Edg/143.0.0.0");
}
public LeTool(ShareLinkInfo shareLinkInfo) { public LeTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo); super(shareLinkInfo);
} }
/**
* 获取干净的 shareId去掉可能的查询参数
* URL 如 https://lecloud.lenovo.com/share/5eoN3RA5PLhQcH4zE?path=... 会导致 shareKey 包含查询参数
*/
private String getCleanShareId() {
String shareKey = shareLinkInfo.getShareKey();
if (shareKey != null && shareKey.contains("?")) {
return shareKey.split("\\?")[0];
}
return shareKey;
}
public Future<String> parse() { public Future<String> parse() {
final String dataKey = shareLinkInfo.getShareKey(); final String dataKey = getCleanShareId();
final String pwd = shareLinkInfo.getSharePassword(); final String pwd = shareLinkInfo.getSharePassword();
// {"shareId":"xxx","password":"xxx","directoryId":"-1"} // {"shareId":"xxx","password":"xxx","directoryId":"-1"}
String apiUrl1 = API_URL_PREFIX + "shareInfo"; String apiUrl1 = API_URL_PREFIX + "shareInfo";
client.postAbs(apiUrl1) client.postAbs(apiUrl1)
.sendJsonObject(JsonObject.of("shareId", dataKey, "password", pwd, "directoryId", "-1")) .putHeaders(HEADERS)
.sendJsonObject(JsonObject.of("shareId", dataKey, "password", pwd, "directoryId", "-1"))
.onSuccess(res -> { .onSuccess(res -> {
JsonObject resJson = asJson(res); JsonObject resJson = asJson(res);
if (resJson.containsKey("result")) { if (resJson.containsKey("result")) {
@@ -83,28 +115,17 @@ public class LeTool extends PanBase {
public Future<List<FileInfo>> parseFileList() { public Future<List<FileInfo>> parseFileList() {
Promise<List<FileInfo>> listPromise = Promise.promise(); Promise<List<FileInfo>> listPromise = Promise.promise();
String dataKey = shareLinkInfo.getShareKey(); String dataKey = getCleanShareId();
// 如果参数里的目录ID不为空则直接解析目录 // 如果参数里的目录ID不为空则直接解析目录
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId"); String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
if (dirId != null && !dirId.isEmpty()) { if (dirId == null || dirId.isEmpty()) {
parseDirectory(dirId, dataKey, listPromise); // 如果没有指定目录ID使用根目录ID "-1"
return listPromise.future(); dirId = "-1";
} }
// 先解析获取根目录ID // 直接请求shareInfo接口解析目录
parse().onSuccess(id -> { parseDirectory(dirId, dataKey, listPromise);
if (id != null && !id.isEmpty()) {
// 解析目录
parseDirectory(id, dataKey, listPromise);
} else {
listPromise.fail("解析目录ID失败");
}
}).onFailure(failRes -> {
log.error("解析目录失败: {}", failRes.getMessage());
listPromise.fail(failRes);
});
return listPromise.future(); return listPromise.future();
} }
@@ -112,16 +133,18 @@ public class LeTool extends PanBase {
* 解析目录下的文件列表 * 解析目录下的文件列表
*/ */
private void parseDirectory(String directoryId, String shareId, Promise<List<FileInfo>> promise) { private void parseDirectory(String directoryId, String shareId, Promise<List<FileInfo>> promise) {
log.debug("开始解析目录: directoryId={}, shareId={}", directoryId, shareId);
String pwd = shareLinkInfo.getSharePassword(); String pwd = shareLinkInfo.getSharePassword();
if (pwd == null) { if (pwd == null) {
pwd = ""; pwd = "";
} }
String apiUrl = API_URL_PREFIX + "shareInfo"; String apiUrl = API_URL_PREFIX + "shareInfo";
JsonObject requestBody = JsonObject.of("shareId", shareId, "password", pwd, "directoryId", directoryId);
log.info("解析目录请求: url={}, body={}", apiUrl, requestBody.encode());
client.postAbs(apiUrl) client.postAbs(apiUrl)
.sendJsonObject(JsonObject.of("shareId", shareId, "password", pwd, "directoryId", directoryId)) .putHeaders(HEADERS)
.sendJsonObject(requestBody)
.onSuccess(res -> { .onSuccess(res -> {
JsonObject resJson = asJson(res); JsonObject resJson = asJson(res);
@@ -179,21 +202,12 @@ public class LeTool extends PanBase {
fileInfo.setFileType("folder"); fileInfo.setFileType("folder");
fileInfo.setSize(0L); fileInfo.setSize(0L);
fileInfo.setSizeStr("0B"); fileInfo.setSizeStr("0B");
// 设置目录解析的URL - 注意fileId已经是URL编码的直接使用 // 设置目录解析的URL - 注意:从API返回的fileId已经是URL编码的直接使用
// 使用 URLEncoder 确保特殊字符被正确编码 // 不需要再次编码,避免双重编码导致解析失败
try { fileInfo.setParserUrl(String.format("%s/v2/getFileList?url=%s&dirId=%s",
String encodedFileId = java.net.URLEncoder.encode(fileId, "UTF-8"); getDomainName(),
fileInfo.setParserUrl(String.format("%s/v2/getFileList?url=%s&dirId=%s", shareLinkInfo.getShareUrl(),
getDomainName(), fileId));
shareLinkInfo.getShareUrl(),
encodedFileId));
} catch (Exception e) {
// 如果编码失败直接使用原始fileId
fileInfo.setParserUrl(String.format("%s/v2/getFileList?url=%s&dirId=%s",
getDomainName(),
shareLinkInfo.getShareUrl(),
fileId));
}
} else { } else {
// 文件类型 // 文件类型
fileInfo.setFileType(fileType != null ? String.valueOf(fileType) : DEFAULT_FILE_TYPE); fileInfo.setFileType(fileType != null ? String.valueOf(fileType) : DEFAULT_FILE_TYPE);
@@ -211,7 +225,11 @@ public class LeTool extends PanBase {
fileInfo.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s", fileInfo.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s",
getDomainName(), getDomainName(),
shareLinkInfo.getType(), shareLinkInfo.getType(),
paramBase64)); paramBase64))
.setPreviewUrl(String.format("%s/v2/viewUrl/%s/%s",
getDomainName(),
shareLinkInfo.getType(),
paramBase64));
} }
} catch (Exception e) { } catch (Exception e) {
@@ -250,7 +268,8 @@ public class LeTool extends PanBase {
String apiUrl = API_URL_PREFIX + "packageDownloadWithFileIds"; String apiUrl = API_URL_PREFIX + "packageDownloadWithFileIds";
client.postAbs(apiUrl) client.postAbs(apiUrl)
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", shareId, "browserId", uuid)) .putHeaders(HEADERS)
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", shareId, "browserId", uuid))
.onSuccess(res -> { .onSuccess(res -> {
JsonObject resJson = asJson(res); JsonObject resJson = asJson(res);
if (resJson.containsKey("result")) { if (resJson.containsKey("result")) {
@@ -280,7 +299,8 @@ public class LeTool extends PanBase {
String apiUrl2 = API_URL_PREFIX + "packageDownloadWithFileIds"; String apiUrl2 = API_URL_PREFIX + "packageDownloadWithFileIds";
// {"fileIds":[123],"shareId":"xxx","browserId":"uuid"} // {"fileIds":[123],"shareId":"xxx","browserId":"uuid"}
client.postAbs(apiUrl2) client.postAbs(apiUrl2)
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", key, "browserId", uuid)) .putHeaders(HEADERS)
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", key, "browserId", uuid))
.onSuccess(res -> { .onSuccess(res -> {
JsonObject resJson = asJson(res); JsonObject resJson = asJson(res);
if (resJson.containsKey("result")) { if (resJson.containsKey("result")) {
@@ -351,4 +371,4 @@ public class LeTool extends PanBase {
return fileInfo; return fileInfo;
} }
} }