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管理器已释放资源"); } } }