250 lines
8.4 KiB
C#
250 lines
8.4 KiB
C#
using COSXML;
|
|
using COSXML.Auth;
|
|
using COSXML.Model.Object;
|
|
using COSXML.Transfer;
|
|
using WorkCameraExport.Models;
|
|
|
|
namespace WorkCameraExport.Services
|
|
{
|
|
/// <summary>
|
|
/// COS 服务类 - 处理腾讯云 COS 图片上传
|
|
/// </summary>
|
|
public class CosService : IDisposable
|
|
{
|
|
private CosXml? _cosXml;
|
|
private CosTempCredentials? _credentials;
|
|
private bool _disposed;
|
|
|
|
/// <summary>
|
|
/// 初始化 COS 客户端
|
|
/// </summary>
|
|
public void Initialize(CosTempCredentials credentials)
|
|
{
|
|
_credentials = credentials;
|
|
|
|
var config = new CosXmlConfig.Builder()
|
|
.IsHttps(true)
|
|
.SetRegion(credentials.Region)
|
|
.SetDebugLog(false)
|
|
.Build();
|
|
|
|
var credentialProvider = new DefaultSessionQCloudCredentialProvider(
|
|
credentials.TmpSecretId,
|
|
credentials.TmpSecretKey,
|
|
credentials.ExpiredTime,
|
|
credentials.SessionToken);
|
|
|
|
_cosXml = new CosXmlServer(config, credentialProvider);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查是否已初始化
|
|
/// </summary>
|
|
public bool IsInitialized => _cosXml != null && _credentials != null;
|
|
|
|
/// <summary>
|
|
/// 上传图片到 COS
|
|
/// </summary>
|
|
/// <param name="localFilePath">本地文件路径</param>
|
|
/// <param name="cosKey">COS 对象键(路径)</param>
|
|
/// <param name="progress">进度回调</param>
|
|
/// <param name="cancellationToken">取消令牌</param>
|
|
/// <returns>上传结果</returns>
|
|
public async Task<(bool Success, string Message, string? CosUrl)> UploadFileAsync(
|
|
string localFilePath,
|
|
string cosKey,
|
|
Action<long, long>? progress = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (!IsInitialized || _cosXml == null || _credentials == null)
|
|
{
|
|
return (false, "COS 服务未初始化", null);
|
|
}
|
|
|
|
try
|
|
{
|
|
var transferConfig = new TransferConfig();
|
|
var transferManager = new TransferManager(_cosXml, transferConfig);
|
|
|
|
var uploadTask = new COSXMLUploadTask(_credentials.Bucket, cosKey);
|
|
uploadTask.SetSrcPath(localFilePath);
|
|
|
|
if (progress != null)
|
|
{
|
|
uploadTask.progressCallback = (completed, total) =>
|
|
{
|
|
progress(completed, total);
|
|
};
|
|
}
|
|
|
|
// 使用新的异步 API
|
|
var result = await transferManager.UploadAsync(uploadTask);
|
|
|
|
if (result.IsSuccessful())
|
|
{
|
|
var cosUrl = $"https://{_credentials.Bucket}.cos.{_credentials.Region}.myqcloud.com/{cosKey}";
|
|
return (true, "上传成功", cosUrl);
|
|
}
|
|
else
|
|
{
|
|
return (false, "上传失败", null);
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
return (false, "上传已取消", null);
|
|
}
|
|
catch (COSXML.CosException.CosClientException clientEx)
|
|
{
|
|
return (false, $"客户端异常: {clientEx.Message}", null);
|
|
}
|
|
catch (COSXML.CosException.CosServerException serverEx)
|
|
{
|
|
return (false, $"服务端异常: {serverEx.GetInfo()}", null);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return (false, $"上传异常: {ex.Message}", null);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 上传字节数组到 COS
|
|
/// </summary>
|
|
public async Task<(bool Success, string Message, string? CosUrl)> UploadBytesAsync(
|
|
byte[] data,
|
|
string cosKey,
|
|
Action<long, long>? progress = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (!IsInitialized || _cosXml == null || _credentials == null)
|
|
{
|
|
return (false, "COS 服务未初始化", null);
|
|
}
|
|
|
|
// 创建临时文件
|
|
var tempFile = Path.GetTempFileName();
|
|
try
|
|
{
|
|
await File.WriteAllBytesAsync(tempFile, data, cancellationToken);
|
|
return await UploadFileAsync(tempFile, cosKey, progress, cancellationToken);
|
|
}
|
|
finally
|
|
{
|
|
// 清理临时文件
|
|
try
|
|
{
|
|
if (File.Exists(tempFile))
|
|
{
|
|
File.Delete(tempFile);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// 忽略清理错误
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 批量上传图片
|
|
/// </summary>
|
|
/// <param name="uploadTasks">上传任务列表 (本地路径, COS键)</param>
|
|
/// <param name="maxConcurrency">最大并发数</param>
|
|
/// <param name="progress">进度回调 (已完成数, 总数)</param>
|
|
/// <param name="cancellationToken">取消令牌</param>
|
|
/// <returns>上传结果列表</returns>
|
|
public async Task<List<(string LocalPath, bool Success, string? CosUrl, string Message)>>
|
|
BatchUploadAsync(
|
|
List<(string LocalPath, string CosKey)> uploadTasks,
|
|
int maxConcurrency = 5,
|
|
Action<int, int>? progress = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var results = new List<(string LocalPath, bool Success, string? CosUrl, string Message)>();
|
|
var semaphore = new SemaphoreSlim(maxConcurrency);
|
|
var completed = 0;
|
|
var total = uploadTasks.Count;
|
|
|
|
var tasks = uploadTasks.Select(async task =>
|
|
{
|
|
await semaphore.WaitAsync(cancellationToken);
|
|
try
|
|
{
|
|
var result = await UploadFileAsync(task.LocalPath, task.CosKey,
|
|
cancellationToken: cancellationToken);
|
|
|
|
lock (results)
|
|
{
|
|
results.Add((task.LocalPath, result.Success, result.CosUrl, result.Message));
|
|
completed++;
|
|
progress?.Invoke(completed, total);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
semaphore.Release();
|
|
}
|
|
});
|
|
|
|
await Task.WhenAll(tasks);
|
|
return results;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 生成 COS 对象键(路径)
|
|
/// </summary>
|
|
/// <param name="recordTime">记录时间</param>
|
|
/// <param name="category">分类(当日照片/参与人员/工作内容/部门)</param>
|
|
/// <param name="subCategory">子分类</param>
|
|
/// <param name="fileName">文件名</param>
|
|
/// <returns>COS 对象键</returns>
|
|
public static string GenerateCosKey(DateTime recordTime, string category,
|
|
string subCategory, string fileName)
|
|
{
|
|
var yearMonth = recordTime.ToString("yyyyMM");
|
|
var date = recordTime.ToString("yyyyMMdd");
|
|
|
|
if (string.IsNullOrEmpty(subCategory))
|
|
{
|
|
return $"workfiles/{yearMonth}/{date}/{category}/{fileName}";
|
|
}
|
|
|
|
return $"workfiles/{yearMonth}/{date}/{category}/{subCategory}/{fileName}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 生成唯一文件名
|
|
/// </summary>
|
|
public static string GenerateFileName(string extension = ".jpg")
|
|
{
|
|
var timestamp = DateTime.Now.ToString("yyyyMMddHHmmssfff");
|
|
var random = new Random().Next(1000, 9999);
|
|
return $"{timestamp}_{random}{extension}";
|
|
}
|
|
|
|
#region IDisposable
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!_disposed)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_cosXml = null;
|
|
_credentials = null;
|
|
}
|
|
_disposed = true;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|