diff --git a/QYZH.InteractiveMagazine.Common/Extensions/DateTimeExtension.cs b/QYZH.InteractiveMagazine.Common/Extensions/DateTimeExtension.cs
index 4973374..a378922 100644
--- a/QYZH.InteractiveMagazine.Common/Extensions/DateTimeExtension.cs
+++ b/QYZH.InteractiveMagazine.Common/Extensions/DateTimeExtension.cs
@@ -7,6 +7,10 @@ namespace QYZH.InteractiveMagazine.Common.Extensions
///
public static class DateTimeExtension
{
+ ///
+ /// 时间戳起始日期
+ ///
+ public static readonly DateTime TimestampStart = new(1970, 1, 1, 0, 0, 0, 0);
///
/// 转换为Unix时间戳(秒)
///
@@ -84,5 +88,15 @@ namespace QYZH.InteractiveMagazine.Common.Extensions
{
return dateTime.Date.AddDays(1).AddTicks(-1);
}
+
+ public static long ToTimestamp(this DateTime dateTime, bool milliseconds = false)
+ {
+ var timestamp = dateTime.ToUniversalTime() - TimestampStart;
+ return (long)(milliseconds ? timestamp.TotalMilliseconds : timestamp.TotalSeconds);
+ }
+ public static long ToMillisecondsTimestamp(this DateTime dateTime)
+ {
+ return ToTimestamp(dateTime, true);
+ }
}
}
diff --git a/QYZH.InteractiveMagazine.Common/Extensions/ObjectExtension.cs b/QYZH.InteractiveMagazine.Common/Extensions/ObjectExtension.cs
index d6b81a4..303b882 100644
--- a/QYZH.InteractiveMagazine.Common/Extensions/ObjectExtension.cs
+++ b/QYZH.InteractiveMagazine.Common/Extensions/ObjectExtension.cs
@@ -90,5 +90,10 @@ namespace QYZH.InteractiveMagazine.Common.Extensions
return dictionary;
}
+
+ public static bool IsNull(this IList? s)
+ {
+ return s == null || s?.Count() < 1;
+ }
}
}
diff --git a/QYZH.InteractiveMagazine.Common/Extensions/StringExtension.cs b/QYZH.InteractiveMagazine.Common/Extensions/StringExtension.cs
index 116cc88..985dbc0 100644
--- a/QYZH.InteractiveMagazine.Common/Extensions/StringExtension.cs
+++ b/QYZH.InteractiveMagazine.Common/Extensions/StringExtension.cs
@@ -146,5 +146,22 @@ namespace QYZH.InteractiveMagazine.Common.Extensions
return Regex.IsMatch(input, pattern);
}
+
+ ///
+ /// 判断字符串是否为Null、空
+ ///
+ ///
+ ///
+ public static bool IsNull(this string s)
+ {
+ return string.IsNullOrWhiteSpace(s);
+ }
+
+ public static string RemoveDomain(this string str)
+ {
+ if (str.IsNull()) return str;
+ // 匹配域名后的路径部分(包括第一个/)
+ return Regex.Replace(str, @"https?://[^/]+/([^""\s<>]*)", "$1");
+ }
}
}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacExtension.cs b/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacExtension.cs
index e0a86be..74c17ce 100644
--- a/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacExtension.cs
+++ b/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacExtension.cs
@@ -2,7 +2,6 @@
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
-using QYZH.InteractiveMagazine.Infrastructure.Cache;
using QYZH.InteractiveMagazine.Infrastructure.MessageQueue;
using QYZH.InteractiveMagazine.Models.Settings;
using RabbitMQ.Client;
@@ -27,21 +26,10 @@ namespace QYZH.InteractiveMagazine.Infrastructure.Autofacs
var assemblyNames = string.Join(".", source.Take(source.Length - 1));
containerBuilder.RegisterModule(new AutofacModuleRegister(assemblyNames));
- InitializeRedis(c.Configuration);
InitializeRabbitMQ(c.Configuration, containerBuilder);
});
}
- private static void InitializeRedis(IConfiguration configuration)
- {
- var redisSettings = configuration.GetSection("RedisSettings").Get();
- if (redisSettings != null && !string.IsNullOrWhiteSpace(redisSettings.ConnectionString))
- {
- var multiplexer = ConnectionMultiplexer.Connect(redisSettings.ConnectionString);
- RedisHelper.Connection = multiplexer;
- RedisHelper.SetKeyPrefix(redisSettings.InstanceName ?? string.Empty);
- }
- }
private static void InitializeRabbitMQ(IConfiguration configuration, ContainerBuilder containerBuilder)
{
diff --git a/QYZH.InteractiveMagazine.Infrastructure/Cache/RedisHelper.cs b/QYZH.InteractiveMagazine.Infrastructure/Cache/RedisHelper.cs
deleted file mode 100644
index 9fc9409..0000000
--- a/QYZH.InteractiveMagazine.Infrastructure/Cache/RedisHelper.cs
+++ /dev/null
@@ -1,119 +0,0 @@
-using StackExchange.Redis;
-
-namespace QYZH.InteractiveMagazine.Infrastructure.Cache;
-
-///
-/// Redis操作封装
-///
-public static class RedisHelper
-{
- private static string? _keyPrefix;
-
- ///
- /// Redis连接实例
- ///
- public static IConnectionMultiplexer? Connection { get; set; }
-
- ///
- /// 设置键前缀
- ///
- /// 前缀字符串
- public static void SetKeyPrefix(string prefix)
- {
- _keyPrefix = prefix;
- }
-
- ///
- /// 获取Redis数据库实例
- ///
- /// 数据库索引
- /// IDatabase实例
- public static IDatabase GetDatabase(int db = -1)
- {
- if (Connection == null || !Connection.IsConnected)
- {
- throw new InvalidOperationException("Redis连接未初始化或已断开");
- }
-
- return Connection.GetDatabase(db);
- }
-
- ///
- /// 设置字符串值
- ///
- /// 键
- /// 值
- /// 过期时间
- /// 是否成功
- public static async Task StringSetAsync(string key, string value, TimeSpan? expiry = null)
- {
- var db = GetDatabase();
- var prefixedKey = GetPrefixedKey(key);
-
- if (expiry.HasValue)
- {
- return await db.StringSetAsync(prefixedKey, value, (Expiration)expiry.Value);
- }
-
- return await db.StringSetAsync(prefixedKey, value);
- }
-
- ///
- /// 获取字符串值
- ///
- /// 键
- /// 值
- public static async Task StringGetAsync(string key)
- {
- var db = GetDatabase();
- var prefixedKey = GetPrefixedKey(key);
- return await db.StringGetAsync(prefixedKey);
- }
-
- ///
- /// 删除键
- ///
- /// 键
- /// 是否成功
- public static async Task KeyDeleteAsync(string key)
- {
- var db = GetDatabase();
- var prefixedKey = GetPrefixedKey(key);
- return await db.KeyDeleteAsync(prefixedKey);
- }
-
- ///
- /// 检查键是否存在
- ///
- /// 键
- /// 是否存在
- public static async Task KeyExistsAsync(string key)
- {
- var db = GetDatabase();
- var prefixedKey = GetPrefixedKey(key);
- return await db.KeyExistsAsync(prefixedKey);
- }
-
- ///
- /// 设置键的过期时间
- ///
- /// 键
- /// 过期时间
- /// 是否成功
- public static async Task KeyExpireAsync(string key, TimeSpan expiry)
- {
- var db = GetDatabase();
- var prefixedKey = GetPrefixedKey(key);
- return await db.KeyExpireAsync(prefixedKey, expiry);
- }
-
- ///
- /// 获取带前缀的键
- ///
- /// 原始键
- /// 带前缀的键
- private static string GetPrefixedKey(string key)
- {
- return string.IsNullOrEmpty(_keyPrefix) ? key : $"{_keyPrefix}:{key}";
- }
-}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/Middleware/JwtAutoRefreshMiddleware.cs b/QYZH.InteractiveMagazine.Infrastructure/Middleware/JwtAutoRefreshMiddleware.cs
index 6679d05..f3f3681 100644
--- a/QYZH.InteractiveMagazine.Infrastructure/Middleware/JwtAutoRefreshMiddleware.cs
+++ b/QYZH.InteractiveMagazine.Infrastructure/Middleware/JwtAutoRefreshMiddleware.cs
@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
-using QYZH.InteractiveMagazine.Infrastructure.Cache;
using QYZH.InteractiveMagazine.Models.Settings;
using System.IdentityModel.Tokens.Jwt;
@@ -63,7 +62,7 @@ public class JwtAutoRefreshMiddleware
var newToken = QYZH.InteractiveMagazine.Infrastructure.Auth.JwtHelper.GenerateToken(
long.Parse(userId), userName, jwtSettings);
- await RedisHelper.StringSetAsync(
+ await RedisHelper.SetAsync(
$"{TokenKeyPrefix}:{userId}",
newToken,
TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
diff --git a/QYZH.InteractiveMagazine.Infrastructure/OSS/OSSOptions.cs b/QYZH.InteractiveMagazine.Infrastructure/OSS/OSSOptions.cs
new file mode 100644
index 0000000..f5edadd
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Infrastructure/OSS/OSSOptions.cs
@@ -0,0 +1,62 @@
+
+namespace QYZH.InteractiveMagazine.Infrastructure.OSS
+{
+ ///
+ /// OSS配置
+ ///
+ public class OSSOptions
+ {
+ public const string Section = "AliyunOSSConfigs";
+
+ ///
+ /// ID
+ ///
+ public string AccessKeyID { get; set; }
+
+ ///
+ /// 密钥
+ ///
+ public string AccessKeySecret { get; set; }
+
+ ///
+ /// 地区
+ ///
+ public string Region { get; set; }
+
+ ///
+ /// 存储桶
+ ///
+ public string BucketName { get; set; }
+
+ ///
+ /// 视频点播储存桶
+ ///
+ public string VodBucketName { get; set; }
+
+ ///
+ /// 存储桶
+ ///
+ public string RoleArn { get; set; }
+
+ ///
+ /// 过期时间
+ ///
+ public int DurationSeconds { get; set; }
+
+ ///
+ /// 终结点
+ ///
+ public string Endpoint { get; set; }
+
+ ///
+ /// IMM转换项目名称
+ ///
+ public string ProjectName { get; set; }
+
+ ///
+ /// 访问域名
+ ///
+ public string Domain { get; set; }
+ }
+
+}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/OSS/OssCredentials.cs b/QYZH.InteractiveMagazine.Infrastructure/OSS/OssCredentials.cs
new file mode 100644
index 0000000..66d7d55
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Infrastructure/OSS/OssCredentials.cs
@@ -0,0 +1,19 @@
+namespace QYZH.InteractiveMagazine.Infrastructure.OSS
+{
+ public class OssCredentials
+ {
+ public string AccessKeyId { get; set; }
+
+ public string AccessKeySecret { get; set; }
+
+ public string SecurityToken { get; set; }
+
+ public long ExpireTime { get; set; } // 格式化为 "yyyy-MM-dd HH:mm:ss"
+
+ public string BucketName { get; set; }
+
+ public string Region { get; set; } // 格式如 "oss-cn-hangzhou"
+
+ public string Domain { get; set; }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/OSS/OssIMMService.cs b/QYZH.InteractiveMagazine.Infrastructure/OSS/OssIMMService.cs
new file mode 100644
index 0000000..079b501
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Infrastructure/OSS/OssIMMService.cs
@@ -0,0 +1,96 @@
+using Aliyun.Credentials.Models;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
+using System.Web;
+using Tea;
+
+namespace QYZH.InteractiveMagazine.Infrastructure.OSS
+{
+ public class OssIMMService
+ {
+ private readonly AlibabaCloud.SDK.Imm20200930.Client _client;
+ private readonly OSSOptions _oSSOptions;
+ private readonly IConfiguration _configuration;
+ public OssIMMService(IConfiguration configuration,IOptionsMonitor ossOptions)
+ {
+ this._configuration = configuration;
+ _oSSOptions = ossOptions.CurrentValue;
+ Config config = new Config()
+ {
+ Type = "access_key",
+ AccessKeyId = _oSSOptions.AccessKeyID,
+ AccessKeySecret = _oSSOptions.AccessKeySecret,
+ };
+
+ Aliyun.Credentials.Client credential = new Aliyun.Credentials.Client(config);
+ AlibabaCloud.OpenApiClient.Models.Config config1 = new AlibabaCloud.OpenApiClient.Models.Config
+ {
+ Credential = credential,
+ };
+ // Endpoint 请参考 https://api.aliyun.com/product/imm
+ config1.Endpoint = $"imm.cn-{_oSSOptions.Region}.aliyuncs.com";
+ _client = new AlibabaCloud.SDK.Imm20200930.Client(config1);
+ }
+
+
+ public string DocConvertPng(string source, string prefix)
+ {
+ var createOfficeConversionTaskRequest = new AlibabaCloud.SDK.Imm20200930.Models.CreateOfficeConversionTaskRequest
+ {
+ ProjectName = _oSSOptions.ProjectName,
+ TargetType = "png",
+ SourceURI = $"oss://{_oSSOptions.BucketName}/{HttpUtility.UrlDecode(source)}",
+ TargetURIPrefix = $"oss://{_oSSOptions.BucketName}/{prefix}",
+ //TargetURI = "oss://returnzero/output1/{dirname}/{barename}/{index}.{autoext}",//oss://examplebucket/outputprefix/
+ };
+ var runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions();
+ try
+ {
+ // 复制代码运行请自行打印 API 的返回值
+ var data = _client.CreateOfficeConversionTaskWithOptions(createOfficeConversionTaskRequest, runtime);
+ if (data?.StatusCode == 200)
+ {
+ return data.Body.TaskId;
+ }
+ return null;
+ }
+ catch (Exception _error)
+ {
+ return null;
+ }
+ }
+
+ public bool GetTask(string taskId)
+ {
+ AlibabaCloud.SDK.Imm20200930.Models.GetTaskRequest getTaskRequest = new()
+ {
+ ProjectName = _oSSOptions.ProjectName,
+ TaskType = "OfficeConversion",
+ TaskId = taskId,
+ };
+ var runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions();
+ try
+ {
+ var data = _client.GetTaskWithOptions(getTaskRequest, runtime);
+ if (data?.StatusCode == 200)
+ {
+ if (data.Body.Status == "Running") return GetTask(taskId);
+ if (data.Body.Status == "Succeeded") return true;
+
+ }
+ return false;
+
+ }
+
+ catch (Exception _error)
+ {
+ TeaException error = new TeaException(new Dictionary
+ {
+ { "message", _error.Message }
+ });
+ Console.WriteLine(error.Message);
+ return false;
+ }
+ }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/OSS/OssService.cs b/QYZH.InteractiveMagazine.Infrastructure/OSS/OssService.cs
new file mode 100644
index 0000000..e62f38f
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Infrastructure/OSS/OssService.cs
@@ -0,0 +1,307 @@
+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 _logger;
+ //private readonly IOssResourceRepository _ossResourceRepository;
+ public OssService(IOptionsMonitor options, CSRedisClient redis, ILogger logger)
+ {
+ // _ossOption = configuration.GetSection("AliyunOSSConfigs").Get();
+ _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 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
+ {
+ new
+ {
+ Action = new List { "oss:PutObject", "oss:GetObject","oss:DeleteObject" },
+ Resource = new List { $"acs:oss:*:*:{_ossOption.BucketName}/temp/*" },
+ //Condition = new
+ //{
+ // NumericLessThanEquals = new Dictionary()
+ // {
+ // {"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(stsJson);
+ }
+ }
+
+ public async Task> 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
+ {
+ new
+ {
+ Action = new List { "oss:PutObject", "oss:GetObject","oss:DeleteObject" },
+ Resource = new List { $"acs:oss:*:*:{_ossOption.BucketName}/temp/*" },
+ //Condition = new
+ //{
+ // NumericLessThanEquals = new Dictionary()
+ // {
+ // {"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
+ {
+ {"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>(stsJson);
+ }
+
+ ///
+ /// 上传单个
+ ///
+ ///
+ ///
+ 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;
+ //}
+ }
+
+ ///
+ /// 删除单个
+ ///
+ ///
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// 删除多个
+ ///
+ ///
+ ///
+ public bool DeleteObjects(List? 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));
+ //AppException.ThrowIf(!del, "删除资源失败");
+
+
+ return result.Keys.Count() == keys.Count;
+ }
+ catch (OssException ex)
+ {
+ _logger.LogError(ex.Message + ex.StackTrace);
+ return false;
+ }
+ }
+
+
+ public List ListObjects(string prefix)
+ {
+ try
+ {
+ var keys = new List();
+ 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;
+ }
+ }
+
+ ///
+ /// 复制文件
+ ///
+ ///
+ ///
+ ///
+ 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,
+
+ //});
+ //AppException.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;
+ }
+
+ }
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Infrastructure/QYZH.InteractiveMagazine.Infrastructure.csproj b/QYZH.InteractiveMagazine.Infrastructure/QYZH.InteractiveMagazine.Infrastructure.csproj
index 61c1cf7..d889c8e 100644
--- a/QYZH.InteractiveMagazine.Infrastructure/QYZH.InteractiveMagazine.Infrastructure.csproj
+++ b/QYZH.InteractiveMagazine.Infrastructure/QYZH.InteractiveMagazine.Infrastructure.csproj
@@ -7,8 +7,14 @@
+
+
+
+
+
+
diff --git a/QYZH.InteractiveMagazine.Infrastructure/Redis/RedisExtensions.cs b/QYZH.InteractiveMagazine.Infrastructure/Redis/RedisExtensions.cs
new file mode 100644
index 0000000..fec96d7
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Infrastructure/Redis/RedisExtensions.cs
@@ -0,0 +1,106 @@
+using CSRedis;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace QYZH.InteractiveMagazine.Infrastructure.Redis
+{
+ public static class RedisExtensions
+ {
+ public static TimeSpan expireSecond = TimeSpan.FromMinutes(20);
+
+ ///
+ /// AddCSRedisCacheExtension
+ ///
+ ///
+ ///
+ ///
+ public static IServiceCollection AddCSRedisCacheExtension(this IServiceCollection services, IConfiguration configuration)
+ {
+ if (services == null)
+ throw new ArgumentNullException("services");
+
+ if (configuration == null)
+ throw new ArgumentNullException("configuration");
+
+ var redisOptions = configuration.Get();
+ services.AddSingleton(redisOptions);
+ var csredis = new CSRedisClient(redisOptions.ConnectionString, redisOptions.Sentinels, redisOptions.ReadOnly);
+ RedisHelper.Initialization(csredis);
+ services.AddSingleton(csredis);//csredis注册为单例
+ //services.AddSingleton(new Microsoft.Extensions.Caching.Redis.CSRedisCache(RedisHelper.Instance));
+
+ return services;
+ }
+
+
+ ///
+ /// 获取缓存,如果换成不存在则执行方法生成缓存
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static T GetOrSet(this CSRedisClient redisCache, string key, Func factory, TimeSpan? expireTime = null) where T : class
+ {
+ var result = redisCache.Get(key);
+ if (factory != null && result == null)
+ {
+ result = factory();
+ if (expireTime == null || !expireTime.HasValue)
+ expireTime = expireSecond;
+
+ redisCache.Set(key, result, expireTime.Value);
+ }
+ return result;
+ }
+
+ ///
+ /// 获取缓存,如果缓存不存在则执行方法生成缓存
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static async Task GetOrSetAsync(this CSRedisClient redisCache, string key, Func factory, TimeSpan? expireTime = null)
+ {
+ var result = await redisCache.GetAsync(key);
+ if (result == null && factory != null)
+ {
+ result = factory();
+
+ if (expireTime == null || !expireTime.HasValue)
+ expireTime = expireSecond;
+
+ if (result != null)
+ await redisCache.SetAsync(key, result, expireTime.Value);
+ }
+ return result;
+ }
+ }
+
+
+
+ public class RedisCacheOption
+ {
+ ///
+ /// 连接字符串
+ ///
+ public string ConnectionString { get; set; }
+
+ ///
+ /// 哨兵设置
+ ///
+ public string[] Sentinels { get; set; }
+
+ ///
+ /// 是否只读
+ ///
+ public bool ReadOnly { get; set; } = false;
+
+ public int[] ExpireSecondRange { get; set; }
+
+ public int? ExpireSecond { get; set; } = 3600;
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Enum/ApiVersionEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/ApiVersionEnum.cs
index 9363da6..8af7eb3 100644
--- a/QYZH.InteractiveMagazine.Models/Enum/ApiVersionEnum.cs
+++ b/QYZH.InteractiveMagazine.Models/Enum/ApiVersionEnum.cs
@@ -21,6 +21,11 @@ namespace QYZH.InteractiveMagazine.Models.Enum
///
[Description("小程序管理")]
Wechat = 20,
+ ///
+ /// Wechat
+ ///
+ [Description("SDK")]
+ SDK = 30,
}
}
diff --git a/QYZH.InteractiveMagazine.Service/AdminAuthService.cs b/QYZH.InteractiveMagazine.Service/AdminAuthService.cs
index 0087406..ae575c0 100644
--- a/QYZH.InteractiveMagazine.Service/AdminAuthService.cs
+++ b/QYZH.InteractiveMagazine.Service/AdminAuthService.cs
@@ -2,7 +2,6 @@ using BCrypt.Net;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using QYZH.InteractiveMagazine.Infrastructure.Auth;
-using QYZH.InteractiveMagazine.Infrastructure.Cache;
using QYZH.InteractiveMagazine.IService;
using QYZH.InteractiveMagazine.Models.Common;
@@ -57,7 +56,7 @@ public class AdminAuthService(BaseRepository adminUserRepository, ICo
var token = JwtHelper.GenerateToken((long)adminUser.Id, adminUser.UserName, jwtSettings);
- await RedisHelper.StringSetAsync($"{TokenKeyPrefix}:{adminUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
+ await RedisHelper.SetAsync($"{TokenKeyPrefix}:{adminUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
logger.LogInformation("管理员登录成功,用户名: {UserName}, ID: {UserId}", input.UserName, adminUser.Id);
@@ -74,7 +73,7 @@ public class AdminAuthService(BaseRepository adminUserRepository, ICo
{
logger.LogInformation("管理员登出,ID: {UserId}", userId);
- await RedisHelper.KeyDeleteAsync($"{TokenKeyPrefix}:{userId}");
+ await RedisHelper.DelAsync($"{TokenKeyPrefix}:{userId}");
logger.LogInformation("管理员登出成功,ID: {UserId}", userId);
}
@@ -134,7 +133,7 @@ public class AdminAuthService(BaseRepository adminUserRepository, ICo
throw new BusinessException("修改密码失败", 500);
}
- await RedisHelper.KeyDeleteAsync($"{TokenKeyPrefix}:{userId}");
+ await RedisHelper.DelAsync($"{TokenKeyPrefix}:{userId}");
logger.LogInformation("管理员修改密码成功,ID: {UserId}", userId);
}
diff --git a/QYZH.InteractiveMagazine.Service/WeChatAuthService.cs b/QYZH.InteractiveMagazine.Service/WeChatAuthService.cs
index 4d8e197..4513a15 100644
--- a/QYZH.InteractiveMagazine.Service/WeChatAuthService.cs
+++ b/QYZH.InteractiveMagazine.Service/WeChatAuthService.cs
@@ -2,7 +2,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using QYZH.InteractiveMagazine.Common.Helpers;
using QYZH.InteractiveMagazine.Infrastructure.Auth;
-using QYZH.InteractiveMagazine.Infrastructure.Cache;
using QYZH.InteractiveMagazine.IService;
using QYZH.InteractiveMagazine.Models.Common;
using QYZH.InteractiveMagazine.Models.Entity;
@@ -220,8 +219,8 @@ public class WeChatAuthService(BaseRepository usersRepository, IConfigura
var token = JwtHelper.GenerateToken((long)refreshedUser.Id, refreshedUser.Name, jwtSettings);
// 清除旧用户 Token 缓存,写入新用户 Token 缓存
- await RedisHelper.KeyDeleteAsync($"{TokenKeyPrefix}:{currentUserId}");
- await RedisHelper.StringSetAsync($"{TokenKeyPrefix}:{refreshedUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
+ await RedisHelper.DelAsync($"{TokenKeyPrefix}:{currentUserId}");
+ await RedisHelper.SetAsync($"{TokenKeyPrefix}:{refreshedUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
return new WeChatSwitchUserOutput
{
@@ -259,7 +258,7 @@ public class WeChatAuthService(BaseRepository usersRepository, IConfigura
var token = JwtHelper.GenerateToken((long)primaryUser.Id, primaryUser.Name, jwtSettings);
// 缓存 Token 到 Redis
- await RedisHelper.StringSetAsync($"{TokenKeyPrefix}:{primaryUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
+ await RedisHelper.SetAsync($"{TokenKeyPrefix}:{primaryUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
var userOutputs = users.Select(MapUserToOutput).ToList();
@@ -277,7 +276,7 @@ public class WeChatAuthService(BaseRepository usersRepository, IConfigura
private async Task GetAccessTokenAsync(WeChatSettings settings)
{
// 先从 Redis 缓存获取
- var cachedToken = await RedisHelper.StringGetAsync(AccessTokenCacheKey);
+ var cachedToken = await RedisHelper.GetAsync(AccessTokenCacheKey);
if (!string.IsNullOrWhiteSpace(cachedToken))
{
return cachedToken;
@@ -296,7 +295,7 @@ public class WeChatAuthService(BaseRepository usersRepository, IConfigura
// 缓存 access_token,提前 5 分钟过期(微信默认 7200 秒)
var expiresIn = response.ExpiresIn > 300 ? response.ExpiresIn - 300 : response.ExpiresIn;
- await RedisHelper.StringSetAsync(AccessTokenCacheKey, response.AccessToken, TimeSpan.FromSeconds(expiresIn));
+ await RedisHelper.SetAsync(AccessTokenCacheKey, response.AccessToken, TimeSpan.FromSeconds(expiresIn));
logger.LogInformation("获取微信 access_token 成功,有效期: {ExpiresIn} 秒", expiresIn);
return response.AccessToken;
diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs
index 87421f0..734a48c 100644
--- a/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs
+++ b/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs
@@ -60,4 +60,19 @@ public abstract class BaseController : ControllerBase
{
return BaseResponse