diff --git a/parser/src/main/java/cn/qaiu/parser/impl/LeTool.java b/parser/src/main/java/cn/qaiu/parser/impl/LeTool.java
index 1ea5abf..432fcda 100644
--- a/parser/src/main/java/cn/qaiu/parser/impl/LeTool.java
+++ b/parser/src/main/java/cn/qaiu/parser/impl/LeTool.java
@@ -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;
/**
* 联想乐云
*/
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> parseFileList() {
+ Promise> 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> 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 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 parseById() {
+ Promise 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 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;
+ }
+}
\ No newline at end of file
diff --git a/web-front/src/views/Home.vue b/web-front/src/views/Home.vue
index 0613bf8..d408a82 100644
--- a/web-front/src/views/Home.vue
+++ b/web-front/src/views/Home.vue
@@ -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