2026-06-09 14:36:46 +08:00
|
|
|
|
using Aliyun.Acs.Core;
|
|
|
|
|
|
using Aliyun.Acs.Core.Auth.Sts;
|
|
|
|
|
|
using Aliyun.Acs.Core.Profile;
|
|
|
|
|
|
using Aliyun.OSS;
|
|
|
|
|
|
using Aliyun.OSS.Common;
|
|
|
|
|
|
using CSRedis;
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
|
using QYZH.InteractiveMagazine.Common.Extensions;
|
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
|
using System.Web;
|
|
|
|
|
|
|
|
|
|
|
|
namespace QYZH.InteractiveMagazine.Infrastructure.OSS
|
|
|
|
|
|
{
|
|
|
|
|
|
public class OssService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly OssClient _ossClient;
|
|
|
|
|
|
private readonly OSSOptions _ossOption;
|
|
|
|
|
|
private readonly CSRedisClient _redis;
|
|
|
|
|
|
private readonly ILogger<OssService> _logger;
|
|
|
|
|
|
//private readonly IOssResourceRepository _ossResourceRepository;
|
|
|
|
|
|
public OssService(IOptionsMonitor<OSSOptions> options, CSRedisClient redis, ILogger<OssService> logger)
|
|
|
|
|
|
{
|
|
|
|
|
|
// _ossOption = configuration.GetSection("AliyunOSSConfigs").Get<OSSOptions>();
|
|
|
|
|
|
_ossOption = options.CurrentValue;
|
|
|
|
|
|
_ossClient = new OssClient($"oss-cn-{_ossOption.Region}.aliyuncs.com", _ossOption.AccessKeyID, _ossOption.AccessKeySecret);
|
|
|
|
|
|
_ossClient.SetRegion($"cn-{_ossOption.Region}");
|
|
|
|
|
|
_redis = redis;
|
|
|
|
|
|
_logger = logger;
|
|
|
|
|
|
//_ossResourceRepository = ossResourceRepository;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<OssCredentials> GetSTSTokenAsync(CancellationToken cancellationToken)
|
|
|
|
|
|
{
|
|
|
|
|
|
var stsJson = await _redis.GetAsync("STS-InteractiveMagazine-OssCredentials");
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(stsJson))
|
|
|
|
|
|
{
|
|
|
|
|
|
// ststoken
|
|
|
|
|
|
IClientProfile profile = DefaultProfile.GetProfile($"oss-cn-{_ossOption.Region}", _ossOption.AccessKeyID, _ossOption.AccessKeySecret);
|
|
|
|
|
|
DefaultAcsClient client = new DefaultAcsClient(profile);
|
|
|
|
|
|
|
|
|
|
|
|
var policy = new
|
|
|
|
|
|
{
|
|
|
|
|
|
Version = "1",
|
|
|
|
|
|
Statement = new List<dynamic>
|
|
|
|
|
|
{
|
|
|
|
|
|
new
|
|
|
|
|
|
{
|
|
|
|
|
|
Action = new List<string> { "oss:PutObject", "oss:GetObject","oss:DeleteObject" },
|
|
|
|
|
|
Resource = new List<string> { $"acs:oss:*:*:{_ossOption.BucketName}/temp/*" },
|
|
|
|
|
|
//Condition = new
|
|
|
|
|
|
//{
|
|
|
|
|
|
// NumericLessThanEquals = new Dictionary<string, object>()
|
|
|
|
|
|
// {
|
|
|
|
|
|
// {"oss:ContentLength",104857600 }
|
|
|
|
|
|
// }
|
|
|
|
|
|
//},
|
|
|
|
|
|
Effect = "Allow",
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var assumeRoleRequest = new AssumeRoleRequest();
|
|
|
|
|
|
assumeRoleRequest.Policy = JsonSerializer.Serialize(policy);
|
|
|
|
|
|
assumeRoleRequest.RoleArn = _ossOption.RoleArn;
|
|
|
|
|
|
assumeRoleRequest.RoleSessionName = "oss-services";//这里的名字随便写
|
|
|
|
|
|
assumeRoleRequest.DurationSeconds = 3600;//过期时间
|
|
|
|
|
|
var response = client.GetAcsResponse(assumeRoleRequest);
|
|
|
|
|
|
|
|
|
|
|
|
var expiration = DateTime.Parse(response.Credentials.Expiration).AddMinutes(5);
|
|
|
|
|
|
var result = new OssCredentials
|
|
|
|
|
|
{
|
|
|
|
|
|
AccessKeyId = response.Credentials.AccessKeyId,
|
|
|
|
|
|
AccessKeySecret = response.Credentials.AccessKeySecret,
|
|
|
|
|
|
SecurityToken = response.Credentials.SecurityToken,
|
|
|
|
|
|
ExpireTime = expiration.ToMillisecondsTimestamp(),
|
|
|
|
|
|
BucketName = _ossOption.BucketName,
|
|
|
|
|
|
Region = $"oss-cn-{_ossOption.Region}",
|
|
|
|
|
|
Domain = _ossOption.Domain
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
await _redis.SetAsync("STS-InteractiveMagazine-OssCredentials", result, expiration - DateTime.Now);
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
return JsonSerializer.Deserialize<OssCredentials>(stsJson);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<Dictionary<string, string>> GetSTSToken()
|
|
|
|
|
|
{
|
|
|
|
|
|
var stsJson = await _redis.GetAsync("QYZH.InteractiveMagazine.STS");
|
|
|
|
|
|
if (stsJson.IsNull())
|
|
|
|
|
|
{
|
|
|
|
|
|
// ststoken
|
|
|
|
|
|
IClientProfile profile = DefaultProfile.GetProfile($"oss-cn-{_ossOption.Region}", _ossOption.AccessKeyID, _ossOption.AccessKeySecret);
|
|
|
|
|
|
DefaultAcsClient client = new DefaultAcsClient(profile);
|
|
|
|
|
|
|
|
|
|
|
|
var policy = new
|
|
|
|
|
|
{
|
|
|
|
|
|
Version = "1",
|
|
|
|
|
|
Statement = new List<dynamic>
|
|
|
|
|
|
{
|
|
|
|
|
|
new
|
|
|
|
|
|
{
|
|
|
|
|
|
Action = new List<string> { "oss:PutObject", "oss:GetObject","oss:DeleteObject" },
|
|
|
|
|
|
Resource = new List<string> { $"acs:oss:*:*:{_ossOption.BucketName}/temp/*" },
|
|
|
|
|
|
//Condition = new
|
|
|
|
|
|
//{
|
|
|
|
|
|
// NumericLessThanEquals = new Dictionary<string, object>()
|
|
|
|
|
|
// {
|
|
|
|
|
|
// {"oss:ContentLength",104857600 }
|
|
|
|
|
|
// }
|
|
|
|
|
|
//},
|
|
|
|
|
|
Effect = "Allow",
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var request = new AssumeRoleRequest();
|
|
|
|
|
|
request.Policy = JsonSerializer.Serialize(policy);
|
|
|
|
|
|
request.RoleArn = _ossOption.RoleArn;
|
|
|
|
|
|
request.RoleSessionName = "oss-services";//这里的名字随便写
|
|
|
|
|
|
request.DurationSeconds = 3600;//过期时间
|
|
|
|
|
|
var response = client.GetAcsResponse(request);
|
|
|
|
|
|
|
|
|
|
|
|
var expiration = DateTime.Parse(response.Credentials.Expiration);
|
|
|
|
|
|
var result = new Dictionary<string, string>
|
|
|
|
|
|
{
|
|
|
|
|
|
{"AccessKeyId", response.Credentials.AccessKeyId},
|
|
|
|
|
|
{"AccessKeySecret",response.Credentials.AccessKeySecret },
|
|
|
|
|
|
{"SecurityToken",response.Credentials.SecurityToken },
|
|
|
|
|
|
{"Expiration", DateTime.Parse(response.Credentials.Expiration).ToString("yyyy-MM-dd HH:mm:ss") },
|
|
|
|
|
|
{"BucketName",_ossOption.BucketName },
|
|
|
|
|
|
{"Region",$"oss-cn-{_ossOption.Region }"},
|
|
|
|
|
|
{"Domain",_ossOption.Domain }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var seconds = (expiration - DateTime.Now).TotalSeconds - 300;
|
|
|
|
|
|
await _redis.SetAsync("QYZH.InteractiveMagazine.STS", result, TimeSpan.FromSeconds(seconds));
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
return JsonSerializer.Deserialize<Dictionary<string, string>>(stsJson);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 上传单个
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="key"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public string PutObject(string key, Stream content)
|
|
|
|
|
|
{
|
|
|
|
|
|
//try
|
|
|
|
|
|
//{
|
|
|
|
|
|
var result = _ossClient.PutObject(_ossOption.BucketName, key, content);
|
|
|
|
|
|
if (result.HttpStatusCode == System.Net.HttpStatusCode.OK)
|
|
|
|
|
|
{
|
|
|
|
|
|
return key;
|
|
|
|
|
|
}
|
|
|
|
|
|
else return null;
|
|
|
|
|
|
//}
|
|
|
|
|
|
//catch (Exception ex)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// return null;
|
|
|
|
|
|
//}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 删除单个
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="key"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public bool DeleteObject(string key)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
if (key.IsNull()) return false;
|
|
|
|
|
|
|
|
|
|
|
|
key = HttpUtility.UrlDecode(key);
|
|
|
|
|
|
//_ossResourceRepository.UpdateDiy.Set(s => s.Status, -1).Where(w => w.Path == key).ExecuteAffrows();
|
|
|
|
|
|
|
|
|
|
|
|
var result = _ossClient.DeleteObject(_ossOption.BucketName, key);
|
|
|
|
|
|
return result.HttpStatusCode == System.Net.HttpStatusCode.OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OssException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex.Message + ex.StackTrace);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 删除多个
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="keys"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public bool DeleteObjects(List<string>? keys)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (keys.IsNull()) return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 设置为详细模式,返回所有删除的文件列表。
|
|
|
|
|
|
var quietMode = false;
|
|
|
|
|
|
|
|
|
|
|
|
keys = keys.Select(it => it.RemoveDomain()).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
//_ossResourceRepository.UpdateDiy.Set(s => s.Status, -1).Where(w => keys.Contains(w.Path)).ExecuteAffrows();
|
|
|
|
|
|
|
|
|
|
|
|
var request = new DeleteObjectsRequest(_ossOption.BucketName, keys, quietMode);
|
|
|
|
|
|
// 删除多个文件。
|
|
|
|
|
|
var result = _ossClient.DeleteObjects(request);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//var del = _ossResourceRepository.Delete(w => keys.Contains(w.Path));
|
2026-06-11 17:01:24 +08:00
|
|
|
|
//BusinessException.ThrowIf(!del, "删除资源失败");
|
2026-06-09 14:36:46 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result.Keys.Count() == keys.Count;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OssException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex.Message + ex.StackTrace);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public List<string> ListObjects(string prefix)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var keys = new List<string>();
|
|
|
|
|
|
ObjectListing result = null;
|
|
|
|
|
|
string nextMarker = string.Empty;
|
|
|
|
|
|
do
|
|
|
|
|
|
{
|
|
|
|
|
|
var listObjectsRequest = new ListObjectsRequest(_ossOption.BucketName)
|
|
|
|
|
|
{
|
|
|
|
|
|
Marker = nextMarker,
|
|
|
|
|
|
MaxKeys = 100,
|
|
|
|
|
|
Prefix = prefix,
|
|
|
|
|
|
};
|
|
|
|
|
|
result = _ossClient.ListObjects(listObjectsRequest);
|
|
|
|
|
|
foreach (var summary in result.ObjectSummaries)
|
|
|
|
|
|
{
|
|
|
|
|
|
keys.Add(summary.Key);
|
|
|
|
|
|
}
|
|
|
|
|
|
nextMarker = result.NextMarker;
|
|
|
|
|
|
} while (result.IsTruncated);
|
|
|
|
|
|
return keys;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OssException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex.Message + ex.StackTrace);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 复制文件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="sourceObject"></param>
|
|
|
|
|
|
/// <param name="targetObject"></param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
public bool CopyObject(string sourceObject, string targetObject, long? id = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
//var metadata = new ObjectMetadata();
|
|
|
|
|
|
// 设置自定义元数据。自定义元数据为键值对(Key-Value)的形式,例如Key为mk1,Value为mv1。
|
|
|
|
|
|
//metadata.AddHeader("mk1", "mv1");
|
|
|
|
|
|
//metadata.AddHeader("mk2", "mv2");
|
|
|
|
|
|
sourceObject = sourceObject.RemoveDomain();
|
|
|
|
|
|
sourceObject = Uri.UnescapeDataString(sourceObject);
|
|
|
|
|
|
|
|
|
|
|
|
//var uow = _unitOfWorkManager.Begin();
|
|
|
|
|
|
//var data = _ossResourceRepository.Insert(new OssResource
|
|
|
|
|
|
//{
|
|
|
|
|
|
// MainId = id,
|
|
|
|
|
|
// SoucePath = sourceObject,
|
|
|
|
|
|
// Path = targetObject,
|
|
|
|
|
|
|
|
|
|
|
|
//});
|
2026-06-11 17:01:24 +08:00
|
|
|
|
//BusinessException.ThrowIf(data.IsNull(), "资源添加失败");
|
2026-06-09 14:36:46 +08:00
|
|
|
|
|
|
|
|
|
|
var req = new CopyObjectRequest(_ossOption.BucketName, sourceObject, _ossOption.BucketName, targetObject)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果NewObjectMetadata为null则为COPY模式(即拷贝源文件的元数据),非null则为REPLACE模式(覆盖源文件的元数据)。
|
|
|
|
|
|
NewObjectMetadata = null
|
|
|
|
|
|
};
|
|
|
|
|
|
var result = _ossClient.CopyObject(req);
|
|
|
|
|
|
return result.HttpStatusCode == System.Net.HttpStatusCode.OK;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (OssException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex.Message + ex.StackTrace);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|