using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Aliyun.OSS;
using Aliyun.OSS.Common;
using UnityEngine;
namespace HK
{
[System.Serializable]
public class OSSConfig
{
// 阿里云AccessKeyId
public string accessKeyId = "LTAI5t9AcXJm2BXwzLTie8Wa";
// 阿里云AccessKeySecret
public string accessKeySecret = "RJhANJfNL7Xr8EmEZfMR12ED5nIp8c";
// OSS服务地址,如"oss-cn-beijing.aliyuncs.com
public string endpoint = "oss-cn-hongkong.aliyuncs.com";
// 存储空间名称
public string bucketName = "kiosk-assets-bundle";
// OSS上的资源根存储目录
public string ossRootDirectory = "Framework"; //
// OSS上的资源相对存储目录
public string ossRelativePath = ""; //
}
///
/// 阿里云OSS管理单例类
/// 负责OSS的初始化、文件上传和下载
///
public class AliyunOSSManager : IDisposable
{
// 单例实例
private static readonly Lazy _instance =
new Lazy(() => new AliyunOSSManager());
public static AliyunOSSManager Instance => _instance.Value;
// OSS客户端
private OssClient _ossClient;
private string _bucketName;
private string _baseOssPath; // OSS基础路径,如"GameRes/"
private bool _isInitialized = false;
// 私有构造函数防止外部实例化
private AliyunOSSManager()
{
}
///
/// 初始化OSS客户端
///
/// 阿里云
/// 是否初始化成功
public bool Initialize(OSSConfig config)
{
if (_isInitialized)
{
Debug.LogWarning("OSS客户端已初始化,无需重复初始化");
return true;
}
try
{
// 验证基础路径格式
if (!config.ossRootDirectory.EndsWith("/"))
config.ossRootDirectory += "/";
// 初始化客户端
_ossClient = new OssClient(config.endpoint, config.accessKeyId, config.accessKeySecret);
_bucketName = config.bucketName;
_baseOssPath = config.ossRootDirectory;
// 检查Bucket是否存在
if (!_ossClient.DoesBucketExist(config.bucketName))
{
Debug.LogWarning($"Bucket不存在,正在创建: {config.bucketName}");
_ossClient.CreateBucket(config.bucketName);
}
_isInitialized = true;
Debug.Log("OSS客户端初始化成功");
return true;
}
catch (OssException ex)
{
Debug.LogError($"OSS初始化失败: {ex.Message} 错误代码: {ex.ErrorCode}");
return false;
}
catch (Exception ex)
{
Debug.LogError($"初始化异常: {ex.Message}");
return false;
}
}
///
/// 上传单个文件到OSS
///
/// 本地文件路径
/// OSS上的相对路径(相对于baseOssPath)
/// 是否上传成功
public async Task UploadFileAsync(string localFilePath, string ossRelativePath)
{
if (!CheckInitialized()) return false;
if (!File.Exists(localFilePath))
{
Debug.LogError($"本地文件不存在: {localFilePath}");
return false;
}
try
{
// 构建完整的OSS路径
string fullOssPath = GetFullOssPath(ossRelativePath);
// 使用异步上传
await Task.Run(() =>
{
using (var fileStream = File.OpenRead(localFilePath))
{
_ossClient.PutObject(_bucketName, fullOssPath, fileStream);
}
});
Debug.Log($"文件上传成功: {fullOssPath}");
return true;
}
catch (OssException ex)
{
Debug.LogError($"文件上传失败: {ex.Message} 错误代码: {ex.ErrorCode}");
return false;
}
catch (Exception ex)
{
Debug.LogError($"上传异常: {ex.Message}");
return false;
}
}
///
/// 上传目录下所有文件到OSS
///
/// 本地目录
/// OSS上的目录(相对于baseOssPath)
/// 进度回调 (当前文件数, 总文件数)
/// 是否上传成功
public async Task UploadDirectoryAsync(string localDirectory, string ossDirectory,
Action progressCallback = null)
{
if (!CheckInitialized()) return false;
if (!Directory.Exists(localDirectory))
{
Debug.LogError($"本地目录不存在: {localDirectory}");
return false;
}
try
{
// 获取所有文件
string[] allFiles = Directory.GetFiles(localDirectory, "*.*", SearchOption.AllDirectories);
int totalCount = allFiles.Length;
int currentCount = 0;
foreach (string filePath in allFiles)
{
// 计算相对路径
string relativePath = Path.GetRelativePath(localDirectory, filePath);
// 转换为OSS路径格式(替换反斜杠为正斜杠)
relativePath = relativePath.Replace('\\', '/');
// 构建OSS目标路径
string ossTargetPath = string.IsNullOrEmpty(ossDirectory)
? relativePath
: $"{ossDirectory}/{relativePath}";
// 上传单个文件
bool success = await UploadFileAsync(filePath, ossTargetPath);
if (!success)
{
Debug.LogError($"目录上传中断,文件上传失败: {filePath}");
return false;
}
// 进度回调
currentCount++;
progressCallback?.Invoke(currentCount, totalCount);
}
Debug.Log($"目录上传完成,共上传 {totalCount} 个文件");
return true;
}
catch (Exception ex)
{
Debug.LogError($"目录上传异常: {ex.Message}");
return false;
}
}
public async Task UploadDirectoryAsync(string localDirectory, string ossDirectory, CancellationToken cancellationToken = default,
Action progressCallback = null)
{
if (!CheckInitialized()) return false;
if (!Directory.Exists(localDirectory))
{
Debug.LogError($"本地目录不存在: {localDirectory}");
return false;
}
try
{
// 检查是否已取消
cancellationToken.ThrowIfCancellationRequested();
// 获取所有文件
string[] allFiles = Directory.GetFiles(localDirectory, "*.*", SearchOption.AllDirectories);
int totalCount = allFiles.Length;
int currentCount = 0;
foreach (string filePath in allFiles)
{
// 每次循环开始前检查是否取消
cancellationToken.ThrowIfCancellationRequested();
// 计算相对路径
string relativePath = Path.GetRelativePath(localDirectory, filePath);
// 转换为OSS路径格式(替换反斜杠为正斜杠)
relativePath = relativePath.Replace('\\', '/');
// 构建OSS目标路径
string ossTargetPath = string.IsNullOrEmpty(ossDirectory)
? relativePath
: $"{ossDirectory}/{relativePath}";
// 上传单个文件(传入取消令牌)
bool success = await UploadFileAsync(filePath, ossTargetPath);
if (!success)
{
Debug.LogError($"目录上传中断,文件上传失败: {filePath}");
return false;
}
// 进度回调
currentCount++;
progressCallback?.Invoke(currentCount, totalCount);
}
Debug.Log($"目录上传完成,共上传 {totalCount} 个文件");
return true;
}
catch (OperationCanceledException)
{
Debug.Log("目录上传已被取消");
return false;
}
catch (Exception ex)
{
Debug.LogError($"目录上传异常: {ex.Message}");
return false;
}
}
///
/// 从OSS下载文件到本地(优化版)
///
/// OSS上的相对路径(相对于baseOssPath)
/// 本地保存路径
/// 是否下载成功
public async Task DownloadFileAsync(string ossRelativePath, string localSavePath)
{
if (!CheckInitialized()) return false;
// 临时文件路径(避免下载中断导致的文件损坏)
string tempSavePath = $"{localSavePath}.tmp";
// 构建完整的OSS路径
string fullOssPath = GetFullOssPath(ossRelativePath);
try
{
// 确保保存目录存在
string directory = Path.GetDirectoryName(localSavePath);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
// 异步下载并保存文件
bool downloadSuccess = await Task.Run(() =>
{
try
{
// 获取OSS对象
using (OssObject ossObject = _ossClient.GetObject(_bucketName, fullOssPath))
{
// 读取对象内容流并写入本地文件
using (Stream sourceStream = ossObject.Content)
{
using (FileStream targetStream = File.OpenWrite(tempSavePath))
{
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
{
targetStream.Write(buffer, 0, bytesRead);
}
}
}
}
// 下载完成后重命名临时文件(原子操作,避免文件损坏)
if (File.Exists(localSavePath))
File.Delete(localSavePath);
File.Move(tempSavePath, localSavePath);
return true;
}
catch (Exception ex)
{
Debug.LogError($"下载任务执行失败: {ex.Message}");
return false;
}
});
if (downloadSuccess)
{
Debug.Log($"文件下载成功: {localSavePath}");
return true;
}
else
{
Debug.LogError($"文件下载失败: {fullOssPath}");
return false;
}
}
catch (OssException ex)
{
// 处理OSS特定错误
if (ex.ErrorCode == "NoSuchKey")
Debug.LogError($"OSS文件不存在: {fullOssPath}");
else
Debug.LogError($"OSS错误: {ex.Message} 错误代码: {ex.ErrorCode}");
}
catch (Exception ex)
{
Debug.LogError($"下载异常: {ex.Message}");
}
finally
{
// 清理临时文件
if (File.Exists(tempSavePath))
File.Delete(tempSavePath);
}
return false;
}
///
/// 检查文件是否在OSS上存在
///
/// OSS相对路径
/// 是否存在
public bool ExistsOnOss(string ossRelativePath)
{
if (!CheckInitialized()) return false;
try
{
string fullOssPath = GetFullOssPath(ossRelativePath);
_ossClient.GetObjectMetadata(_bucketName, fullOssPath);
return true;
}
catch (OssException ex)
{
if (ex.ErrorCode == "NoSuchKey")
return false;
Debug.LogError($"检查文件存在性失败: {ex.Message}");
return false;
}
}
///
/// 构建完整的OSS路径
///
private string GetFullOssPath(string relativePath)
{
// 移除可能的前导斜杠,避免重复
if (relativePath.StartsWith("/"))
relativePath = relativePath.Substring(1);
return $"{_baseOssPath}{relativePath}";
}
///
/// 检查是否已初始化
///
private bool CheckInitialized()
{
if (!_isInitialized)
{
Debug.LogError("OSS客户端未初始化,请先调用Initialize方法");
return false;
}
return true;
}
///
/// 释放资源
///
public void Dispose()
{
// OSS客户端在.NET SDK中没有Dispose方法,此处主要用于清理自定义资源
_isInitialized = false;
Debug.Log("OSS管理器已释放资源");
}
}
}