From 0d938f2dca9a2a058615d5acbca6616e14f876b7 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 15 Jun 2025 20:41:32 +0800 Subject: [PATCH] =?UTF-8?q?refactor=EF=BC=9A=E5=87=8F=E5=B0=91Alipan?= =?UTF-8?q?=E5=8F=8A115=E7=9A=84Api=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/modules/filemanager/storages/alipan.py | 106 ++++---------------- app/modules/filemanager/storages/u115.py | 108 ++++----------------- 2 files changed, 41 insertions(+), 173 deletions(-) diff --git a/app/modules/filemanager/storages/alipan.py b/app/modules/filemanager/storages/alipan.py index 3dd73c75..e85dd422 100644 --- a/app/modules/filemanager/storages/alipan.py +++ b/app/modules/filemanager/storages/alipan.py @@ -5,7 +5,7 @@ import secrets import threading import time from pathlib import Path -from typing import List, Dict, Optional, Tuple, Union +from typing import List, Optional, Tuple, Union import requests from tqdm import tqdm @@ -52,9 +52,6 @@ class AliPan(StorageBase, metaclass=Singleton): # 基础url base_url = "https://openapi.alipan.com" - # CID和路径缓存 - _id_cache: Dict[str, Tuple[str, str]] = {} - def __init__(self): super().__init__() self.session = requests.Session() @@ -279,61 +276,6 @@ class AliPan(StorageBase, metaclass=Singleton): return ret_data.get(result_key) return ret_data - def _path_to_id(self, drive_id: str, path: str) -> Tuple[str, str]: - """ - 路径转drive_id, file_id(带缓存机制) - """ - # 根目录 - if path == "/": - return drive_id, "root" - if len(path) > 1 and path.endswith("/"): - path = path[:-1] - # 检查缓存 - if path in self._id_cache: - return self._id_cache[path] - # 逐级查找缓存 - file_id = "root" - file_path = "/" - for p in Path(path).parents: - if str(p) in self._id_cache: - file_path = str(p) - file_id = self._id_cache[file_path] - break - # 计算相对路径 - rel_path = Path(path).relative_to(file_path) - for part in Path(rel_path).parts: - find_part = False - next_marker = None - while True: - resp = self._request_api( - "POST", - "/adrive/v1.0/openFile/list", - json={ - "drive_id": drive_id, - "limit": 100, - "marker": next_marker, - "parent_file_id": file_id, - } - ) - if not resp: - break - for item in resp.get("items", []): - if item["name"] == part: - file_id = item["file_id"] - find_part = True - break - if find_part: - break - if len(resp.get("items")) < 100: - break - if not find_part: - raise FileNotFoundError(f"【阿里云盘】{path} 不存在") - if file_id == "root": - raise FileNotFoundError(f"【阿里云盘】{path} 不存在") - # 缓存路径 - self._id_cache[path] = (drive_id, file_id) - return drive_id, file_id - def __get_fileitem(self, fileinfo: dict, parent: str = "/") -> schemas.FileItem: """ 获取文件信息 @@ -427,9 +369,6 @@ class AliPan(StorageBase, metaclass=Singleton): break next_marker = resp.get("next_marker") for item in resp.get("items", []): - # 更新缓存 - path = f"{fileitem.path}{item.get('name')}" - self._id_cache[path] = (drive_id, item.get("file_id")) items.append(self.__get_fileitem(item, parent=fileitem.path)) if len(resp.get("items")) < 100: break @@ -467,7 +406,6 @@ class AliPan(StorageBase, metaclass=Singleton): return None # 缓存新目录 new_path = Path(parent_item.path) / name - self._id_cache[str(new_path)] = (resp.get("drive_id"), resp.get("file_id")) return self._delay_get_item(new_path) @staticmethod @@ -837,15 +775,9 @@ class AliPan(StorageBase, metaclass=Singleton): if resp.get("code"): logger.warn(f"【阿里云盘】重命名失败: {resp.get('message')}") return False - if fileitem.path in self._id_cache: - del self._id_cache[fileitem.path] - for key in list(self._id_cache.keys()): - if key.startswith(fileitem.path): - del self._id_cache[key] - self._id_cache[str(Path(fileitem.path).parent / name)] = (resp.get("drive_id"), resp.get("file_id")) return True - def get_item(self, path: Path) -> Optional[schemas.FileItem]: + def get_item(self, path: Path, drive_id: str = None) -> Optional[schemas.FileItem]: """ 获取指定路径的文件/目录项 """ @@ -854,7 +786,7 @@ class AliPan(StorageBase, metaclass=Singleton): "POST", "/adrive/v1.0/openFile/get_by_path", json={ - "drive_id": self._default_drive_id, + "drive_id": drive_id or self._default_drive_id, "file_path": str(path) } ) @@ -910,9 +842,15 @@ class AliPan(StorageBase, metaclass=Singleton): def copy(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool: """ - 企业级复制实现(支持目录递归复制) + 复制文件到指定路径 + :param fileitem: 要复制的文件项 + :param path: 目标目录路径 + :param new_name: 新文件名 """ - dest_cid = self._path_to_id(fileitem.drive_id, str(path)) + dest_fileitem = self.get_item(path, drive_id=fileitem.drive_id) + if not dest_fileitem or dest_fileitem.type != "dir": + logger.warn(f"【阿里云盘】目标路径 {path} 不存在或不是目录!") + return False resp = self._request_api( "POST", "/adrive/v1.0/openFile/copy", @@ -920,7 +858,7 @@ class AliPan(StorageBase, metaclass=Singleton): "drive_id": fileitem.drive_id, "file_id": fileitem.fileid, "to_drive_id": fileitem.drive_id, - "to_parent_file_id": dest_cid + "to_parent_file_id": dest_fileitem.fileid, } ) if not resp: @@ -932,18 +870,20 @@ class AliPan(StorageBase, metaclass=Singleton): new_path = Path(path) / fileitem.name new_file = self._delay_get_item(new_path) self.rename(new_file, new_name) - # 更新缓存 - del self._id_cache[fileitem.path] - rename_new_path = Path(path) / new_name - self._id_cache[str(rename_new_path)] = (resp.get("drive_id"), resp.get("file_id")) return True def move(self, fileitem: schemas.FileItem, path: Path, new_name: str) -> bool: """ - 原子性移动操作实现 + 移动文件到指定路径 + :param fileitem: 要移动的文件项 + :param path: 目标目录路径 + :param new_name: 新文件名 """ src_fid = fileitem.fileid - target_id = self._path_to_id(fileitem.drive_id, str(path)) + target_fileitem = self.get_item(path, drive_id=fileitem.drive_id) + if not target_fileitem or target_fileitem.type != "dir": + logger.warn(f"【阿里云盘】目标路径 {path} 不存在或不是目录!") + return False resp = self._request_api( "POST", @@ -951,7 +891,7 @@ class AliPan(StorageBase, metaclass=Singleton): json={ "drive_id": fileitem.drive_id, "file_id": src_fid, - "to_parent_file_id": target_id, + "to_parent_file_id": target_fileitem.fileid, "new_name": new_name } ) @@ -960,10 +900,6 @@ class AliPan(StorageBase, metaclass=Singleton): if resp.get("code"): logger.warn(f"【阿里云盘】移动文件失败: {resp.get('message')}") return False - # 更新缓存 - del self._id_cache[fileitem.path] - rename_new_path = Path(path) / new_name - self._id_cache[str(rename_new_path)] = (resp.get("drive_id"), resp.get("file_id")) return True def link(self, fileitem: schemas.FileItem, target_file: Path) -> bool: diff --git a/app/modules/filemanager/storages/u115.py b/app/modules/filemanager/storages/u115.py index dafbd8b9..c85ec425 100644 --- a/app/modules/filemanager/storages/u115.py +++ b/app/modules/filemanager/storages/u115.py @@ -5,7 +5,7 @@ import secrets import threading import time from pathlib import Path -from typing import List, Dict, Optional, Tuple, Union +from typing import List, Optional, Tuple, Union import oss2 import requests @@ -51,9 +51,6 @@ class U115Pan(StorageBase, metaclass=Singleton): # 基础url base_url = "https://proapi.115.com" - # CID和路径缓存 - _id_cache: Dict[str, str] = {} - def __init__(self): super().__init__() self.session = requests.Session() @@ -238,58 +235,6 @@ class U115Pan(StorageBase, metaclass=Singleton): return ret_data.get(result_key) return ret_data - def _path_to_id(self, path: str) -> str: - """ - 路径转FID(带缓存机制) - """ - # 根目录 - if path == "/": - return '0' - if len(path) > 1 and path.endswith("/"): - path = path[:-1] - # 检查缓存 - if path in self._id_cache: - return self._id_cache[path] - # 逐级查找缓存 - current_id = 0 - parent_path = "/" - for p in Path(path).parents: - if str(p) in self._id_cache: - parent_path = str(p) - current_id = self._id_cache[parent_path] - break - # 计算相对路径 - rel_path = Path(path).relative_to(parent_path) - for part in Path(rel_path).parts: - offset = 0 - find_part = False - while True: - resp = self._request_api( - "GET", - "/open/ufile/files", - "data", - params={"cid": current_id, "limit": 1000, "offset": offset, "cur": True, "show_dir": 1} - ) - if not resp: - break - for item in resp: - if item["fn"] == part: - current_id = item["fid"] - find_part = True - break - if find_part: - break - if len(resp) < 1000: - break - offset += len(resp) - if not find_part: - raise FileNotFoundError(f"【115】{path} 不存在") - if not current_id: - raise FileNotFoundError(f"【115】{path} 不存在") - # 缓存路径 - self._id_cache[path] = str(current_id) - return str(current_id) - @staticmethod def _calc_sha1(filepath: Path, size: Optional[int] = None) -> str: """ @@ -335,7 +280,11 @@ class U115Pan(StorageBase, metaclass=Singleton): else: cid = fileitem.fileid if not cid: - cid = self._path_to_id(fileitem.path) + _fileitem = self.get_item(Path(fileitem.path)) + if not _fileitem: + logger.warn(f"【115】获取目录 {fileitem.path} 失败!") + return [] + cid = _fileitem.fileid items = [] offset = 0 @@ -354,8 +303,6 @@ class U115Pan(StorageBase, metaclass=Singleton): for item in resp: # 更新缓存 path = f"{fileitem.path}{item['fn']}" - self._id_cache[path] = str(item["fid"]) - file_path = path + ("/" if item["fc"] == "0" else "") items.append(schemas.FileItem( storage=self.schema.value, @@ -398,8 +345,6 @@ class U115Pan(StorageBase, metaclass=Singleton): return self.get_item(new_path) logger.warn(f"【115】创建目录失败: {resp.get('error')}") return None - # 缓存新目录 - self._id_cache[str(new_path)] = str(resp["data"]["file_id"]) return schemas.FileItem( storage=self.schema.value, fileid=str(resp["data"]["file_id"]), @@ -716,13 +661,6 @@ class U115Pan(StorageBase, metaclass=Singleton): if not resp: return False if resp["state"]: - if fileitem.path in self._id_cache: - del self._id_cache[fileitem.path] - for key in list(self._id_cache.keys()): - if key.startswith(fileitem.path): - del self._id_cache[key] - new_path = Path(fileitem.path).parent / name - self._id_cache[str(new_path)] = fileitem.fileid return True return False @@ -731,15 +669,12 @@ class U115Pan(StorageBase, metaclass=Singleton): 获取指定路径的文件/目录项 """ try: - file_id = self._path_to_id(str(path)) - if not file_id: - return None resp = self._request_api( - "GET", + "POST", "/open/folder/get_info", "data", - params={ - "file_id": int(file_id) + data={ + "path": str(path) } ) if not resp: @@ -753,7 +688,7 @@ class U115Pan(StorageBase, metaclass=Singleton): basename=Path(resp["file_name"]).stem, extension=Path(resp["file_name"]).suffix[1:] if resp["file_category"] == "1" else None, pickcode=resp["pick_code"], - size=StringUtils.num_filesize(resp['size']) if resp["file_category"] == "1" else None, + size=resp['size_byte'] if resp["file_category"] == "1" else None, modify_time=resp["utime"] ) except Exception as e: @@ -805,14 +740,17 @@ class U115Pan(StorageBase, metaclass=Singleton): 企业级复制实现(支持目录递归复制) """ src_fid = fileitem.fileid - dest_cid = self._path_to_id(str(path)) + dest_fileitem = self.get_item(path) + if not dest_fileitem or dest_fileitem.type != "dir": + logger.warn(f"【115】目标路径 {path} 不是一个有效的目录!") + return False resp = self._request_api( "POST", "/open/ufile/copy", data={ "file_id": int(src_fid), - "pid": int(dest_cid) + "pid": int(dest_fileitem.fileid), } ) if not resp: @@ -821,10 +759,6 @@ class U115Pan(StorageBase, metaclass=Singleton): new_path = Path(path) / fileitem.name new_item = self._delay_get_item(new_path) self.rename(new_item, new_name) - # 更新缓存 - del self._id_cache[fileitem.path] - rename_new_path = Path(path) / new_name - self._id_cache[str(rename_new_path)] = new_item.fileid return True return False @@ -833,14 +767,16 @@ class U115Pan(StorageBase, metaclass=Singleton): 原子性移动操作实现 """ src_fid = fileitem.fileid - dest_cid = self._path_to_id(str(path)) - + dest_fileitem = self.get_item(path) + if not dest_fileitem or dest_fileitem.type != "dir": + logger.warn(f"【115】目标路径 {path} 不是一个有效的目录!") + return False resp = self._request_api( "POST", "/open/ufile/move", data={ "file_ids": int(src_fid), - "to_cid": int(dest_cid) + "to_cid": int(dest_fileitem.fileid), } ) if not resp: @@ -849,10 +785,6 @@ class U115Pan(StorageBase, metaclass=Singleton): new_path = Path(path) / fileitem.name new_file = self._delay_get_item(new_path) self.rename(new_file, new_name) - # 更新缓存 - del self._id_cache[fileitem.path] - rename_new_path = Path(path) / new_name - self._id_cache[str(rename_new_path)] = src_fid return True return False