LeTool乐云目录解析

This commit is contained in:
qaiu
2026-01-23 03:26:35 +08:00
parent e17fb99de4
commit 2f22cb01eb
2 changed files with 267 additions and 5 deletions

View File

@@ -1,18 +1,26 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.FileInfo;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import cn.qaiu.util.FileSizeConverter;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
/**
* <a href="https://lecloud.lenovo.com/">联想乐云</a>
*/
public class LeTool extends PanBase {
private static final String API_URL_PREFIX = "https://lecloud.lenovo.com/share/api/clouddiskapi/share/public/v1/";
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; // 目录类型
public LeTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
@@ -24,7 +32,7 @@ public class LeTool extends PanBase {
// {"shareId":"xxx","password":"xxx","directoryId":"-1"}
String apiUrl1 = API_URL_PREFIX + "shareInfo";
client.postAbs(apiUrl1)
.sendJsonObject(JsonObject.of("shareId", dataKey, "password", pwd, "directoryId", -1))
.sendJsonObject(JsonObject.of("shareId", dataKey, "password", pwd, "directoryId", "-1"))
.onSuccess(res -> {
JsonObject resJson = asJson(res);
if (resJson.containsKey("result")) {
@@ -44,7 +52,19 @@ public class LeTool extends PanBase {
}
JsonObject fileInfoJson = files.getJsonObject(0);
if (fileInfoJson != null) {
// TODO 文件大小fileSize和文件名fileName
// Extract and populate FileInfo
FileInfo fileInfo = createFileInfo(fileInfoJson);
shareLinkInfo.getOtherParam().put("fileInfo", fileInfo);
// 判断是否为目录
Integer fileType = fileInfoJson.getInteger("fileType");
if (fileType != null && fileType == FILE_TYPE_DIRECTORY) {
// 如果是目录返回目录ID
String fileId = fileInfoJson.getString("fileId");
promise.complete(fileId);
return;
}
String fileId = fileInfoJson.getString("fileId");
// 根据文件ID获取跳转链接
getDownURL(dataKey, fileId);
@@ -59,6 +79,201 @@ public class LeTool extends PanBase {
return promise.future();
}
@Override
public Future<List<FileInfo>> parseFileList() {
Promise<List<FileInfo>> listPromise = Promise.promise();
String dataKey = shareLinkInfo.getShareKey();
// 如果参数里的目录ID不为空则直接解析目录
String dirId = (String) shareLinkInfo.getOtherParam().get("dirId");
if (dirId != null && !dirId.isEmpty()) {
parseDirectory(dirId, dataKey, listPromise);
return listPromise.future();
}
// 先解析获取根目录ID
parse().onSuccess(id -> {
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();
}
/**
* 解析目录下的文件列表
*/
private void parseDirectory(String directoryId, String shareId, Promise<List<FileInfo>> promise) {
log.debug("开始解析目录: directoryId={}, shareId={}", directoryId, shareId);
String pwd = shareLinkInfo.getSharePassword();
if (pwd == null) {
pwd = "";
}
String apiUrl = API_URL_PREFIX + "shareInfo";
client.postAbs(apiUrl)
.sendJsonObject(JsonObject.of("shareId", shareId, "password", pwd, "directoryId", directoryId))
.onSuccess(res -> {
JsonObject resJson = asJson(res);
if (!resJson.containsKey("result") || !resJson.getBoolean("result")) {
promise.fail("解析目录失败: " + resJson.encode());
return;
}
JsonObject dataJson = resJson.getJsonObject("data");
if (!dataJson.getBoolean("passwordVerified")) {
promise.fail("密码验证失败");
return;
}
JsonArray files = dataJson.getJsonArray("files");
if (files == null || files.isEmpty()) {
promise.complete(new ArrayList<>());
return;
}
List<FileInfo> fileList = new ArrayList<>();
for (int i = 0; i < files.size(); i++) {
JsonObject fileJson = files.getJsonObject(i);
FileInfo fileInfo = createFileInfoForList(fileJson, shareId);
fileList.add(fileInfo);
}
promise.complete(fileList);
})
.onFailure(err -> {
log.error("解析目录请求失败: {}", err.getMessage());
promise.fail(err);
});
}
/**
* 为文件列表创建 FileInfo 对象
*/
private FileInfo createFileInfoForList(JsonObject fileJson, String shareId) {
FileInfo fileInfo = new FileInfo();
try {
String fileId = fileJson.getString("fileId");
String fileName = fileJson.getString("fileName");
Long fileSize = fileJson.getLong("fileSize");
Integer fileType = fileJson.getInteger("fileType");
fileInfo.setFileId(fileId);
fileInfo.setFileName(fileName);
fileInfo.setPanType(shareLinkInfo.getType());
// 判断是否为目录
if (fileType != null && fileType == FILE_TYPE_DIRECTORY) {
// 目录类型
fileInfo.setFileType("folder");
fileInfo.setSize(0L);
fileInfo.setSizeStr("0B");
// 设置目录解析的URL - 注意fileId已经是URL编码的直接使用
// 使用 URLEncoder 确保特殊字符被正确编码
try {
String encodedFileId = java.net.URLEncoder.encode(fileId, "UTF-8");
fileInfo.setParserUrl(String.format("%s/v2/getFileList?url=%s&dirId=%s",
getDomainName(),
shareLinkInfo.getShareUrl(),
encodedFileId));
} catch (Exception e) {
// 如果编码失败直接使用原始fileId
fileInfo.setParserUrl(String.format("%s/v2/getFileList?url=%s&dirId=%s",
getDomainName(),
shareLinkInfo.getShareUrl(),
fileId));
}
} else {
// 文件类型
fileInfo.setFileType(fileType != null ? String.valueOf(fileType) : DEFAULT_FILE_TYPE);
fileInfo.setSize(fileSize);
fileInfo.setSizeStr(FileSizeConverter.convertToReadableSize(fileSize));
// 创建参数JSON并编码为Base64
JsonObject paramJson = JsonObject.of(
"shareId", shareId,
"fileId", fileId
);
String paramBase64 = Base64.getEncoder().encodeToString(paramJson.encode().getBytes());
// 设置解析URL和预览URL
fileInfo.setParserUrl(String.format("%s/v2/redirectUrl/%s/%s",
getDomainName(),
shareLinkInfo.getType(),
paramBase64));
}
} catch (Exception e) {
log.warn("创建文件信息失败: {}", e.getMessage());
}
return fileInfo;
}
@Override
public Future<String> parseById() {
Promise<String> parsePromise = Promise.promise();
try {
// 从参数中获取解析所需的信息
JsonObject paramJson = (JsonObject) shareLinkInfo.getOtherParam().get("paramJson");
String shareId = paramJson.getString("shareId");
String fileId = paramJson.getString("fileId");
// 调用获取下载链接
getDownURLForById(shareId, fileId, parsePromise);
} catch (Exception e) {
parsePromise.fail("解析参数失败: " + e.getMessage());
}
return parsePromise.future();
}
/**
* 根据文件ID获取下载URL (用于 parseById)
*/
private void getDownURLForById(String shareId, String fileId, Promise<String> promise) {
String uuid = UUID.randomUUID().toString();
JsonArray fileIds = JsonArray.of(fileId);
String apiUrl = API_URL_PREFIX + "packageDownloadWithFileIds";
client.postAbs(apiUrl)
.sendJsonObject(JsonObject.of("fileIds", fileIds, "shareId", shareId, "browserId", uuid))
.onSuccess(res -> {
JsonObject resJson = asJson(res);
if (resJson.containsKey("result")) {
if (resJson.getBoolean("result")) {
JsonObject dataJson = resJson.getJsonObject("data");
String downloadUrl = dataJson.getString("downloadUrl");
if (downloadUrl == null) {
promise.fail("Result JSON数据异常: downloadUrl不存在");
return;
}
// 获取重定向链接
clientNoRedirects.getAbs(downloadUrl).send()
.onSuccess(res2 -> promise.complete(res2.headers().get("Location")))
.onFailure(err -> promise.fail(err));
} else {
promise.fail(resJson.getString("errcode") + ": " + resJson.getString("errmsg"));
}
} else {
promise.fail("Result JSON数据异常: result字段不存在");
}
}).onFailure(err -> promise.fail(err));
}
private void getDownURL(String key, String fileId) {
String uuid = UUID.randomUUID().toString();
JsonArray fileIds = JsonArray.of(fileId);
@@ -89,4 +304,51 @@ public class LeTool extends PanBase {
}
}).onFailure(handleFail(apiUrl2));
}
/**
* Create FileInfo object from JSON response
* Uses exact field names from the API response without fallback checks
*/
private FileInfo createFileInfo(JsonObject fileInfoJson) {
FileInfo fileInfo = new FileInfo();
try {
// Set fileId
String fileId = fileInfoJson.getString("fileId");
if (fileId != null) {
fileInfo.setFileId(fileId);
}
// Set fileName
String fileName = fileInfoJson.getString("fileName");
if (fileName != null) {
fileInfo.setFileName(fileName);
}
// Set file size
Long fileSize = fileInfoJson.getLong("fileSize");
if (fileSize != null) {
fileInfo.setSize(fileSize);
// Convert to readable size string
fileInfo.setSizeStr(FileSizeConverter.convertToReadableSize(fileSize));
}
// Set fileType (API returns it as an integer)
Integer fileTypeInt = fileInfoJson.getInteger("fileType");
if (fileTypeInt != null) {
fileInfo.setFileType(String.valueOf(fileTypeInt));
} else {
// Default to generic file type if not available
fileInfo.setFileType(DEFAULT_FILE_TYPE);
}
// Set panType
fileInfo.setPanType(shareLinkInfo.getType());
} catch (Exception e) {
log.warn("Error extracting file info from JSON: {}", e.getMessage());
}
return fileInfo;
}
}

View File

@@ -400,7 +400,7 @@ export default {
const data = result.data
// 检查是否支持目录解析
const supportedPans = ["iz", "lz", "fj", "ye"]
const supportedPans = ["iz", "lz", "fj", "ye", "le"]
if (!supportedPans.includes(data.shareLinkInfo.type)) {
this.$message.error("当前网盘不支持目录解析")
return