Files
Uni-Lab-OS/unilabos/app/oss_upload.py
2025-12-01 12:23:07 +08:00

242 lines
8.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import argparse
import os
import time
from datetime import datetime
from pathlib import Path
from typing import Dict, Optional, Tuple, Union
import requests
from unilabos.app.web.client import http_client, HTTPClient
from unilabos.utils import logger
def _get_oss_token(
filename: str,
driver_name: str = "default",
exp_type: str = "default",
client: Optional[HTTPClient] = None,
) -> Tuple[bool, Dict]:
"""
获取OSS上传Token
Args:
filename: 文件名
driver_name: 驱动名称
exp_type: 实验类型
client: HTTPClient实例如果不提供则使用默认的http_client
Returns:
(成功标志, Token数据字典包含token/path/host/expires)
"""
# 使用提供的client或默认的http_client
if client is None:
client = http_client
# 构造scene参数: driver_name-exp_type
sub_path = f"{driver_name}-{exp_type}"
# 构造请求URL使用client的remote_addr已包含/api/v1/
url = f"{client.remote_addr}/applications/token"
params = {"sub_path": sub_path, "filename": filename, "scene": "job"}
try:
logger.info(f"[OSS] 请求预签名URL: sub_path={sub_path}, filename={filename}")
response = requests.get(url, params=params, headers={"Authorization": f"Lab {client.auth}"}, timeout=10)
if response.status_code == 200:
result = response.json()
if result.get("code") == 0:
data = result.get("data", {})
# 转换expires时间戳为可读格式
expires_timestamp = data.get("expires", 0)
expires_datetime = datetime.fromtimestamp(expires_timestamp)
expires_str = expires_datetime.strftime("%Y-%m-%d %H:%M:%S")
logger.info(f"[OSS] 获取预签名URL成功")
logger.info(f"[OSS] - URL: {data.get('url', 'N/A')}")
logger.info(f"[OSS] - Expires: {expires_str} (timestamp: {expires_timestamp})")
return True, data
logger.error(f"[OSS] 获取预签名URL失败: {response.status_code}, {response.text}")
return False, {}
except Exception as e:
logger.error(f"[OSS] 获取预签名URL异常: {str(e)}")
return False, {}
def _put_upload(file_path: str, upload_url: str) -> bool:
"""
使用预签名URL上传文件到OSS
Args:
file_path: 本地文件路径
upload_url: 完整的预签名上传URL
Returns:
是否成功
"""
try:
logger.info(f"[OSS] 开始上传文件: {file_path}")
with open(file_path, "rb") as f:
# 使用预签名URL上传不需要额外的认证header
response = requests.put(upload_url, data=f, timeout=300)
if response.status_code == 200:
logger.info(f"[OSS] 文件上传成功")
return True
logger.error(f"[OSS] 上传失败: {response.status_code}")
logger.error(f"[OSS] 响应内容: {response.text[:500] if response.text else '无响应内容'}")
return False
except Exception as e:
logger.error(f"[OSS] 上传异常: {str(e)}")
return False
def oss_upload(
file_path: Union[str, Path],
filename: Optional[str] = None,
driver_name: str = "default",
exp_type: str = "default",
max_retries: int = 3,
client: Optional[HTTPClient] = None,
) -> Dict:
"""
文件上传主函数,包含重试机制
Args:
file_path: 本地文件路径
filename: 文件名如果为None则使用file_path的文件名
driver_name: 驱动名称用于构造scene
exp_type: 实验类型用于构造scene
max_retries: 最大重试次数
client: HTTPClient实例如果不提供则使用默认的http_client
Returns:
Dict: {
"success": bool, # 是否上传成功
"original_path": str, # 原始文件路径
"oss_path": str # OSS路径成功时或空字符串失败时
}
"""
file_path = Path(file_path)
if filename is None:
filename = os.path.basename(file_path)
if not os.path.exists(file_path):
logger.error(f"[OSS] 文件不存在: {file_path}")
return {"success": False, "original_path": file_path, "oss_path": ""}
retry_count = 0
oss_path = ""
while retry_count < max_retries:
try:
# 步骤1获取预签名URL
token_success, token_data = _get_oss_token(
filename=filename, driver_name=driver_name, exp_type=exp_type, client=client
)
if not token_success:
logger.warning(f"[OSS] 获取预签名URL失败重试 {retry_count + 1}/{max_retries}")
retry_count += 1
time.sleep(1)
continue
# 获取预签名URL和OSS路径
upload_url = token_data.get("url")
oss_path = token_data.get("path", "")
if not upload_url:
logger.warning(f"[OSS] 无法获取上传URLAPI未返回url字段")
retry_count += 1
time.sleep(1)
continue
# 步骤2PUT上传文件
put_success = _put_upload(file_path, upload_url)
if not put_success:
logger.warning(f"[OSS] PUT上传失败重试 {retry_count + 1}/{max_retries}")
retry_count += 1
time.sleep(1)
continue
# 所有步骤都成功
logger.info(f"[OSS] 文件 {file_path} 上传成功")
return {"success": True, "original_path": file_path, "oss_path": oss_path}
except Exception as e:
logger.error(f"[OSS] 上传过程异常: {str(e)},重试 {retry_count + 1}/{max_retries}")
retry_count += 1
time.sleep(1)
logger.error(f"[OSS] 文件 {file_path} 上传失败,已达到最大重试次数 {max_retries}")
return {"success": False, "original_path": file_path, "oss_path": oss_path}
if __name__ == "__main__":
# python -m unilabos.app.oss_upload -f /path/to/your/file.txt --driver HPLC --type test
# python -m unilabos.app.oss_upload -f /path/to/your/file.txt --driver HPLC --type test \
# --ak xxx --sk yyy --remote-addr http://xxx/api/v1
# 命令行参数解析
parser = argparse.ArgumentParser(description="文件上传测试工具")
parser.add_argument("--file", "-f", type=str, required=True, help="要上传的本地文件路径")
parser.add_argument("--driver", "-d", type=str, default="default", help="驱动名称")
parser.add_argument("--type", "-t", type=str, default="default", help="实验类型")
parser.add_argument("--ak", type=str, help="Access Key如果提供则覆盖配置")
parser.add_argument("--sk", type=str, help="Secret Key如果提供则覆盖配置")
parser.add_argument("--remote-addr", type=str, help="远程服务器地址(包含/api/v1如果提供则覆盖配置")
args = parser.parse_args()
# 检查文件是否存在
if not os.path.exists(args.file):
logger.error(f"错误:文件 {args.file} 不存在")
exit(1)
# 如果提供了ak/sk/remote_addr创建临时HTTPClient
temp_client = None
if args.ak and args.sk:
import base64
auth = base64.b64encode(f"{args.ak}:{args.sk}".encode("utf-8")).decode("utf-8")
remote_addr = args.remote_addr if args.remote_addr else http_client.remote_addr
temp_client = HTTPClient(remote_addr=remote_addr, auth=auth)
logger.info(f"[配置] 使用自定义配置: remote_addr={remote_addr}")
elif args.remote_addr:
temp_client = HTTPClient(remote_addr=args.remote_addr, auth=http_client.auth)
logger.info(f"[配置] 使用自定义remote_addr: {args.remote_addr}")
else:
logger.info(f"[配置] 使用默认配置: remote_addr={http_client.remote_addr}")
logger.info("=" * 50)
logger.info(f"开始上传文件: {args.file}")
logger.info(f"驱动名称: {args.driver}")
logger.info(f"实验类型: {args.type}")
logger.info(f"Scene: {args.driver}-{args.type}")
logger.info("=" * 50)
# 执行上传
result = oss_upload(
file_path=args.file,
filename=None, # 使用默认文件名
driver_name=args.driver,
exp_type=args.type,
client=temp_client,
)
# 输出结果
if result["success"]:
logger.info(f"\n√ 文件上传成功!")
logger.info(f"原始路径: {result['original_path']}")
logger.info(f"OSS路径: {result['oss_path']}")
exit(0)
else:
logger.error(f"\n× 文件上传失败!")
logger.error(f"原始路径: {result['original_path']}")
exit(1)