From 0133c6e60cac65339f1d0b3d4144d7e44aba5af6 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 21 Jun 2024 08:08:23 +0800 Subject: [PATCH] add upload api --- app/api/endpoints/aliyun.py | 16 ++--- app/api/endpoints/u115.py | 8 +-- app/helper/aliyun.py | 124 ++++++++++++++++++++++++++---------- app/helper/u115.py | 80 +++++++++++++++++------ requirements.txt | 3 +- 5 files changed, 162 insertions(+), 69 deletions(-) diff --git a/app/api/endpoints/aliyun.py b/app/api/endpoints/aliyun.py index e9529ab6..1177145d 100644 --- a/app/api/endpoints/aliyun.py +++ b/app/api/endpoints/aliyun.py @@ -47,10 +47,8 @@ def userinfo(_: schemas.TokenPayload = Depends(verify_token)) -> Any: 查询用户信息 """ aliyunhelper = AliyunHelper() - # 浏览一次文件确定token正确性 - aliyunhelper.list_files() # 查询用户信息返回 - info = aliyunhelper.get_user_info() + info = aliyunhelper.user_info() if info: return schemas.Response(success=True, data=info) return schemas.Response(success=False) @@ -76,7 +74,7 @@ def list_aliyun(fileitem: schemas.FileItem, if sort == "time": sort = "updated_at" if fileitem.type == "file": - fileinfo = AliyunHelper().get_file_detail(fileitem.fileid) + fileinfo = AliyunHelper().detail(fileitem.fileid) if fileinfo: return [schemas.FileItem( fileid=fileinfo.get("file_id"), @@ -91,7 +89,7 @@ def list_aliyun(fileitem: schemas.FileItem, drive_id=fileinfo.get("drive_id"), )] return [] - items = AliyunHelper().list_files(drive_id=fileitem.drive_id, parent_file_id=fileitem.fileid, order_by=sort) + items = AliyunHelper().list(drive_id=fileitem.drive_id, parent_file_id=fileitem.fileid, order_by=sort) if not items: return [] return [schemas.FileItem( @@ -131,7 +129,7 @@ def delete_aliyun(fileitem: schemas.FileItem, """ if not fileitem.fileid: return schemas.Response(success=False) - result = AliyunHelper().delete_file(fileitem.fileid) + result = AliyunHelper().delete(fileitem.fileid) if result: return schemas.Response(success=True) return schemas.Response(success=False) @@ -145,7 +143,7 @@ def download_aliyun(fileid: str, """ if not fileid: return schemas.Response(success=False) - url = AliyunHelper().get_download_url(fileid) + url = AliyunHelper().download(fileid) if url: # 重定向 return Response(status_code=302, headers={"Location": url}) @@ -162,7 +160,7 @@ def rename_aliyun(fileitem: schemas.FileItem, """ if not fileitem.fileid or not new_name: return schemas.Response(success=False) - result = AliyunHelper().rename_file(fileitem.fileid, new_name) + result = AliyunHelper().rename(fileitem.fileid, new_name) if result: if recursive: transferchain = TransferChain() @@ -214,7 +212,7 @@ def image_aliyun(fileid: str, _: schemas.TokenPayload = Depends(verify_uri_token """ if not fileid: return schemas.Response(success=False) - url = AliyunHelper().get_download_url(fileid) + url = AliyunHelper().download_url(fileid) if url: # 重定向 return Response(status_code=302, headers={"Location": url}) diff --git a/app/api/endpoints/u115.py b/app/api/endpoints/u115.py index 51bf0d64..167ca3fb 100644 --- a/app/api/endpoints/u115.py +++ b/app/api/endpoints/u115.py @@ -46,7 +46,7 @@ def storage(_: schemas.TokenPayload = Depends(verify_token)) -> Any: """ 查询存储空间信息 """ - storage_info = U115Helper().get_storage() + storage_info = U115Helper().storage() if storage_info: return schemas.Response(success=True, data={ "total": storage_info[0], @@ -87,7 +87,7 @@ def list_115(fileitem: schemas.FileItem, extension=suffix, pickcode=fileitem.pickcode )] - items = U115Helper().list_files(parent_file_id=fileid) + items = U115Helper().list(parent_file_id=fileid) if not items: return [] file_list = [schemas.FileItem( @@ -131,7 +131,7 @@ def delete_115(fileitem: schemas.FileItem, """ if not fileitem.fileid: return schemas.Response(success=False) - result = U115Helper().delete_file(fileitem.fileid) + result = U115Helper().delete(fileitem.fileid) if result: return schemas.Response(success=True) return schemas.Response(success=False) @@ -164,7 +164,7 @@ def rename_115(fileitem: schemas.FileItem, """ if not fileitem.fileid or not new_name: return schemas.Response(success=False) - result = U115Helper().rename_file(fileitem.fileid, new_name) + result = U115Helper().rename(fileitem.fileid, new_name) if result: if recursive: transferchain = TransferChain() diff --git a/app/helper/aliyun.py b/app/helper/aliyun.py index 40c5c99b..b3be63ae 100644 --- a/app/helper/aliyun.py +++ b/app/helper/aliyun.py @@ -2,6 +2,7 @@ import base64 import json import time import uuid +from pathlib import Path from typing import Optional, Tuple, List from requests import Response @@ -50,7 +51,11 @@ class AliyunHelper: # 获取下载链接 download_url = "https://api.aliyundrive.com/v2/file/get_download_url" # 移动文件 - move_file_url = "https://api.aliyundrive.com/v3/file/move" + move_file_url = "https://api.aliyundrive.com/v2/file/move" + # 创建文件 + create_file_url = "https://api.aliyundrive.com/adrive/v2/file/create" + # 上传文件完成 + upload_file_complete_url = "https://api.aliyundrive.com/v2/file/complete" def __init__(self): self.systemconfig = SystemConfigOper() @@ -71,32 +76,32 @@ class AliyunHelper: if action: if code == "DeviceSessionSignatureInvalid": logger.warn("设备已失效,正在重新建立会话...") - self.create_session(self.get_headers(self.auth_params)) + self.__create_session(self.get_headers(self.__auth_params)) if code == "UserDeviceOffline": logger.warn("设备已离线,尝试重新登录,如仍报错请检查阿里云盘绑定设备数量是否超限!") - self.create_session(self.get_headers(self.auth_params)) + self.__create_session(self.get_headers(self.__auth_params)) if code == "AccessTokenInvalid": logger.warn("访问令牌已失效,正在刷新令牌...") - self.__update_accesstoken(self.auth_params, self.auth_params.get("refreshToken")) + self.__update_accesstoken(self.__auth_params, self.__auth_params.get("refreshToken")) else: logger.info(f"Aliyun {apiname}成功") @property - def auth_params(self): + def __auth_params(self): """ 获取阿里云盘认证参数并初始化参数格式 """ return self.systemconfig.get(SystemConfigKey.UserAliyunParams) or {} - def update_params(self, params: dict): + def __update_params(self, params: dict): """ 设置阿里云盘认证参数 """ - current_params = self.auth_params + current_params = self.__auth_params current_params.update(params) self.systemconfig.set(SystemConfigKey.UserAliyunParams, current_params) - def clear_params(self): + def __clear_params(self): """ 清除阿里云盘认证参数 """ @@ -173,8 +178,8 @@ class AliyunHelper: "defaultDriveId": pds_login_result.get('defaultDriveId'), "updateTime": time.time(), }) - self.update_params(data) - self.get_user_info() + self.__update_params(data) + self.user_info() except Exception as e: return {}, f"bizExt 解码失败:{str(e)}" return data, "" @@ -198,9 +203,9 @@ class AliyunHelper: code = data.get("code") if code in ["RefreshTokenExpired", "InvalidParameter.RefreshToken"]: logger.warn("刷新令牌已过期,请重新登录!") - self.clear_params() + self.__clear_params() return False - self.update_params({ + self.__update_params({ "accessToken": data.get('access_token'), "expiresIn": data.get('expires_in'), "updateTime": time.time() @@ -211,10 +216,11 @@ class AliyunHelper: self.__handle_error(res, "更新令牌", action=False) return False - def create_session(self, headers: dict): + def __create_session(self, headers: dict): """ 创建会话 """ + def __os_name(): """ 获取操作系统名称 @@ -233,11 +239,12 @@ class AliyunHelper: }) self.__handle_error(res, "创建会话", action=False) - def get_access_params(self) -> Optional[dict]: + @property + def __access_params(self) -> Optional[dict]: """ 获取阿里云盘访问参数,如果超时则更新后返回 """ - params = self.auth_params + params = self.__auth_params if not params: logger.warn("阿里云盘访问令牌不存在,请先扫码登录!") return None @@ -246,7 +253,7 @@ class AliyunHelper: refresh_token = params.get("refreshToken") if not expires_in or not update_time or not refresh_token: logger.warn("阿里云盘访问令牌参数错误,请重新扫码登录!") - self.clear_params() + self.__clear_params() return None # 是否需要更新设备信息 update_device = False @@ -262,11 +269,11 @@ class AliyunHelper: if not x_device_id: x_device_id = uuid.uuid4().hex params['x_device_id'] = x_device_id - self.update_params({"x_device_id": x_device_id}) + self.__update_params({"x_device_id": x_device_id}) update_device = True # 更新设备信息重新创建会话 if update_device: - self.create_session(self.get_headers(params)) + self.__create_session(self.get_headers(params)) return params def get_headers(self, params: dict): @@ -286,18 +293,18 @@ class AliyunHelper: "x-signature": self._X_SIGNATURE } - def get_user_info(self) -> dict: + def user_info(self) -> dict: """ 获取用户信息(drive_id等) """ - params = self.get_access_params() + params = self.__access_params if not params: return {} headers = self.get_headers(params) res = RequestUtils(headers=headers, timeout=10).post_res(self.user_info_url) if res: result = res.json() - self.update_params({ + self.__update_params({ "resourceDriveId": result.get("resource_drive_id"), "backDriveId": result.get("backup_drive_id") }) @@ -306,8 +313,8 @@ class AliyunHelper: self.__handle_error(res, "获取用户信息") return {} - def list_files(self, drive_id: str = None, parent_file_id: str = 'root', list_type: str = None, - limit: int = 100, order_by: str = 'updated_at') -> List[dict]: + def list(self, drive_id: str = None, parent_file_id: str = 'root', list_type: str = None, + limit: int = 100, order_by: str = 'updated_at') -> List[dict]: """ 浏览文件 limit 返回文件数量,默认 50,最大 100 @@ -315,7 +322,7 @@ class AliyunHelper: parent_file_id 根目录为root type all | file | folder """ - params = self.get_access_params() + params = self.__access_params if not params: return [] # 请求头 @@ -379,7 +386,7 @@ class AliyunHelper: """ 创建目录 """ - params = self.get_access_params() + params = self.__access_params if not params: return None headers = self.get_headers(params) @@ -414,11 +421,11 @@ class AliyunHelper: self.__handle_error(res, "创建目录") return None - def delete_file(self, file_id: str) -> bool: + def delete(self, file_id: str) -> bool: """ 删除文件 """ - params = self.get_access_params() + params = self.__access_params if not params: return False headers = self.get_headers(params) @@ -432,11 +439,11 @@ class AliyunHelper: self.__handle_error(res, "删除文件") return False - def get_file_detail(self, file_id: str) -> Optional[dict]: + def detail(self, file_id: str) -> Optional[dict]: """ 获取文件详情 """ - params = self.get_access_params() + params = self.__access_params if not params: return None headers = self.get_headers(params) @@ -450,11 +457,11 @@ class AliyunHelper: self.__handle_error(res, "获取文件详情") return None - def rename_file(self, file_id: str, name: str) -> bool: + def rename(self, file_id: str, name: str) -> bool: """ 重命名文件 """ - params = self.get_access_params() + params = self.__access_params if not params: return False headers = self.get_headers(params) @@ -470,11 +477,11 @@ class AliyunHelper: self.__handle_error(res, "重命名文件") return False - def get_download_url(self, file_id: str) -> Optional[str]: + def download(self, file_id: str) -> Optional[str]: """ 获取下载链接 """ - params = self.get_access_params() + params = self.__access_params if not params: return None headers = self.get_headers(params) @@ -492,7 +499,7 @@ class AliyunHelper: """ 移动文件 """ - params = self.get_access_params() + params = self.__access_params if not params: return False headers = self.get_headers(params) @@ -507,3 +514,52 @@ class AliyunHelper: else: self.__handle_error(res, "移动文件") return False + + def upload(self, parent_file_id: str, name: str, filepath: Path) -> Optional[dict]: + """ + 上传文件,并标记完成 + """ + params = self.__access_params + if not params: + return None + headers = self.get_headers(params) + res = RequestUtils(headers=headers, timeout=10).post_res(self.create_file_url, json={ + "drive_id": params.get("resourceDriveId"), + "parent_file_id": parent_file_id, + "name": name, + "type": "file", + "check_name_mode": "refuse" + }) + if not res: + self.__handle_error(res, "创建文件") + return None + # 获取上传参数 + result = res.json() + drive_id = result.get("drive_id") + file_id = result.get("file_id") + upload_id = result.get("upload_id") + part_info_list = result.get("part_info_list") + if part_info_list: + # 上传地址 + upload_url = part_info_list[0].get("upload_url") + # 上传文件 + res = RequestUtils(headers=headers).put_res(upload_url, data=filepath.read_bytes()) + if not res: + self.__handle_error(res, "上传文件") + return None + # 标记文件上传完毕 + res = RequestUtils(headers=headers, timeout=10).post_res(self.upload_file_complete_url, json={ + "drive_id": drive_id, + "file_id": file_id, + "upload_id": upload_id + }) + if not res: + self.__handle_error(res, "标记上传状态") + return None + return { + "drive_id": drive_id, + "file_id": file_id + } + else: + logger.warn("上传文件失败:无法获取上传地址!") + return None diff --git a/app/helper/u115.py b/app/helper/u115.py index dd3e55a1..cb9febb5 100644 --- a/app/helper/u115.py +++ b/app/helper/u115.py @@ -1,6 +1,7 @@ import base64 from typing import Optional, Tuple, Generator +import oss2 import py115 from py115 import Cloud from py115.types import LoginTarget, QrcodeSession, QrcodeStatus, Credential, File, DownloadTicket @@ -26,7 +27,7 @@ class U115Helper(metaclass=Singleton): """ 初始化Cloud """ - credential = self.credential + credential = self.__credential if not credential: logger.warn("115未登录,请先登录!") return False @@ -35,12 +36,12 @@ class U115Helper(metaclass=Singleton): self.cloud = py115.connect(credential) except Exception as err: logger.error(f"115连接失败,请重新扫码登录:{str(err)}") - self.clear_credential() + self.__clear_credential() return False return True @property - def credential(self) -> Optional[Credential]: + def __credential(self) -> Optional[Credential]: """ 获取已保存的115认证参数 """ @@ -49,13 +50,13 @@ class U115Helper(metaclass=Singleton): return None return Credential.from_dict(cookie_dict) - def save_credentail(self, credential: Credential): + def __save_credentail(self, credential: Credential): """ 设置115认证参数 """ self.systemconfig.set(SystemConfigKey.User115Params, credential.to_dict()) - def clear_credential(self): + def __clear_credential(self): """ 清除115认证参数 """ @@ -91,7 +92,7 @@ class U115Helper(metaclass=Singleton): status = self.cloud.qrcode_poll(self._session) if status == QrcodeStatus.Done: # 确认完成,保存认证信息 - self.save_credentail(self.cloud.export_credentail()) + self.__save_credentail(self.cloud.export_credentail()) result = { "status": 1, "tip": "登录成功!" @@ -123,7 +124,19 @@ class U115Helper(metaclass=Singleton): except Exception as e: return {}, f"115登录确认失败:{str(e)}" - def list_files(self, parent_file_id: str = '0') -> Optional[Generator[File, None, None]]: + def storage(self) -> Optional[Tuple[int, int]]: + """ + 获取存储空间 + """ + if not self.__init_cloud(): + return None + try: + return self.cloud.storage().space() + except Exception as e: + logger.error(f"获取115存储空间失败:{str(e)}") + return None + + def list(self, parent_file_id: str = '0') -> Optional[Generator[File, None, None]]: """ 浏览文件 """ @@ -147,7 +160,7 @@ class U115Helper(metaclass=Singleton): logger.error(f"创建115目录失败:{str(e)}") return None - def delete_file(self, file_id: str) -> bool: + def delete(self, file_id: str) -> bool: """ 删除文件 """ @@ -160,7 +173,7 @@ class U115Helper(metaclass=Singleton): logger.error(f"删除115文件失败:{str(e)}") return False - def rename_file(self, file_id: str, name: str) -> bool: + def rename(self, file_id: str, name: str) -> bool: """ 重命名文件 """ @@ -185,18 +198,6 @@ class U115Helper(metaclass=Singleton): logger.error(f"115下载失败:{str(e)}") return None - def get_storage(self) -> Optional[Tuple[int, int]]: - """ - 获取存储空间 - """ - if not self.__init_cloud(): - return None - try: - return self.cloud.storage().space() - except Exception as e: - logger.error(f"获取115存储空间失败:{str(e)}") - return None - def move(self, file_id: str, target_id: str) -> bool: """ 移动文件 @@ -209,3 +210,40 @@ class U115Helper(metaclass=Singleton): except Exception as e: logger.error(f"移动115文件失败:{str(e)}") return False + + def upload(self, file_path: str, parent_file_id: str) -> Optional[dict]: + """ + 上传文件 + """ + if not self.__init_cloud(): + return None + try: + ticket = self.cloud.storage().request_upload(dir_id=parent_file_id, file_path=file_path) + if ticket is None: + logger.warn(f"115请求上传出错") + return None + elif ticket.is_done: + logger.warn(f"115请求上传失败:文件已存在") + return {} + else: + auth = oss2.StsAuth(**ticket.oss_token) + bucket = oss2.Bucket( + auth=auth, + endpoint=ticket.oss_endpoint, + bucket_name=ticket.bucket_name, + ) + por = bucket.put_object_from_file( + key=ticket.object_key, + filename=file_path, + headers=ticket.headers, + ) + result = por.resp.response.json() + if result: + logger.info(f"115上传文件成功:{result}") + return result + else: + logger.warn(f"115上传文件失败:{por.resp.response.text}") + return None + except Exception as e: + logger.error(f"上传115文件失败:{str(e)}") + return None diff --git a/requirements.txt b/requirements.txt index c7c321d8..7b1edaf8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -58,4 +58,5 @@ pystray~=0.19.5 pyotp~=2.9.0 Pinyin2Hanzi~=0.1.1 pywebpush~=2.0.0 - py115~=0.0.4 \ No newline at end of file +py115~=0.0.4 +oss2~=2.18.6 \ No newline at end of file