423 lines
15 KiB
C#
423 lines
15 KiB
C#
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 = ""; //
|
||
}
|
||
|
||
/// <summary>
|
||
/// 阿里云OSS管理单例类
|
||
/// 负责OSS的初始化、文件上传和下载
|
||
/// </summary>
|
||
public class AliyunOSSManager : IDisposable
|
||
{
|
||
// 单例实例
|
||
private static readonly Lazy<AliyunOSSManager> _instance =
|
||
new Lazy<AliyunOSSManager>(() => 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()
|
||
{
|
||
}
|
||
|
||
/// <summary>
|
||
/// 初始化OSS客户端
|
||
/// </summary>
|
||
/// <param name="config">阿里云</param>
|
||
/// <returns>是否初始化成功</returns>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上传单个文件到OSS
|
||
/// </summary>
|
||
/// <param name="localFilePath">本地文件路径</param>
|
||
/// <param name="ossRelativePath">OSS上的相对路径(相对于baseOssPath)</param>
|
||
/// <returns>是否上传成功</returns>
|
||
public async Task<bool> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上传目录下所有文件到OSS
|
||
/// </summary>
|
||
/// <param name="localDirectory">本地目录</param>
|
||
/// <param name="ossDirectory">OSS上的目录(相对于baseOssPath)</param>
|
||
/// <param name="progressCallback">进度回调 (当前文件数, 总文件数)</param>
|
||
/// <returns>是否上传成功</returns>
|
||
public async Task<bool> UploadDirectoryAsync(string localDirectory, string ossDirectory,
|
||
Action<int, int> 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<bool> UploadDirectoryAsync(string localDirectory, string ossDirectory, CancellationToken cancellationToken = default,
|
||
Action<int, int> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从OSS下载文件到本地(优化版)
|
||
/// </summary>
|
||
/// <param name="ossRelativePath">OSS上的相对路径(相对于baseOssPath)</param>
|
||
/// <param name="localSavePath">本地保存路径</param>
|
||
/// <returns>是否下载成功</returns>
|
||
public async Task<bool> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查文件是否在OSS上存在
|
||
/// </summary>
|
||
/// <param name="ossRelativePath">OSS相对路径</param>
|
||
/// <returns>是否存在</returns>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建完整的OSS路径
|
||
/// </summary>
|
||
private string GetFullOssPath(string relativePath)
|
||
{
|
||
// 移除可能的前导斜杠,避免重复
|
||
if (relativePath.StartsWith("/"))
|
||
relativePath = relativePath.Substring(1);
|
||
|
||
return $"{_baseOssPath}{relativePath}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查是否已初始化
|
||
/// </summary>
|
||
private bool CheckInitialized()
|
||
{
|
||
if (!_isInitialized)
|
||
{
|
||
Debug.LogError("OSS客户端未初始化,请先调用Initialize方法");
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放资源
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
// OSS客户端在.NET SDK中没有Dispose方法,此处主要用于清理自定义资源
|
||
_isInitialized = false;
|
||
Debug.Log("OSS管理器已释放资源");
|
||
}
|
||
}
|
||
} |