Files
QYZH.InteractiveMagazine/QYZH.InteractiveMagazine.Infrastructure/OSS/OssService.cs
glz bdaa6a0dc8 refactor: 统一业务异常处理,标准化结果码和错误响应
1.  新增并完善ResultCode枚举,补充标准HTTP状态码对应的业务状态码
2.  重构BusinessException,新增基于ResultCode的构造函数和ThrowIf扩展方法
3.  替换所有硬编码的HTTP状态码为统一的ResultCode枚举
4.  优化全局异常中间件,根据业务状态码映射对应HTTP状态码并规范化JSON响应
5.  修复OssImageHelper和AutoDotCodeConsumer中的OSS文件处理逻辑
6.  新增用户答题快照实体类
7.  清理废弃的宠物模块迁移脚本
2026-06-29 16:34:26 +08:00

357 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 QYZH.InteractiveMagazine.Models.Common;
using QYZH.InteractiveMagazine.Models.Dto;
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="key"></param>
/// <returns></returns>
public bool DoesObjectExist(string key)
{
try
{
if (key.IsNull()) return false;
key = key.RemoveDomain();
key = HttpUtility.UrlDecode(key);
return _ossClient.DoesObjectExist(_ossOption.BucketName, key);
}
catch (OssException ex)
{
_logger.LogError(ex.Message + ex.StackTrace);
return false;
}
}
/// <summary>
/// 获取对象内容流
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public Stream? GetObjectStream(string key)
{
try
{
if (key.IsNull()) return null;
key = key.RemoveDomain();
key = HttpUtility.UrlDecode(key);
var ossObject = _ossClient.GetObject(_ossOption.BucketName, key);
return ossObject.Content;
}
catch (OssException ex)
{
_logger.LogError(ex.Message + ex.StackTrace);
return null;
}
}
/// <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));
//BusinessException.ThrowIf(!del, "删除资源失败");
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为mk1Value为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,
//});
//BusinessException.ThrowIf(data.IsNull(), "资源添加失败");
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;
}
}
}
}