diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AliyunOssConfig.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AliyunOssConfig.cs new file mode 100644 index 0000000..db5c9bb --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AliyunOssConfig.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.Code.AppExtend +{ + public class AliyunOssConfig + { + /// + /// + /// + public string AccessKeyId { get; set; } + /// + /// 配置环境变量 + /// + public string AccessKeySecret { get; set; } + /// + /// 替换为Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。 + /// + public string EndPoint { get; set; } + /// + /// Bucket名称。 + /// + public string BucketName { get; set; } + + /// + /// 上传路径 + /// + public string UploadPath { get; set; } + + /// + /// 域名 + /// + public string? DomainName { get; set; } + + /// + /// 前缀 + /// + public string ImagePrefix + { + get + { + return this.DomainName + this.UploadPath; + } + } + } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs index 400800d..301b08c 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs @@ -48,6 +48,10 @@ namespace CloudGaming.Code.AppExtend /// public string Name { get; set; } + /// + /// 默认语言 + /// + public string DefaultLanguage { get; set; } /// /// 租户 /// @@ -57,6 +61,10 @@ namespace CloudGaming.Code.AppExtend /// //public PaymentModel? Payment { get; set; } + /// + /// oss阿里云配置 + /// + public AliyunOssConfig AliyunConfig { get; set; } /// diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs index b96dbd1..8886451 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs @@ -162,6 +162,8 @@ namespace CloudGaming.Code.AppExtend newAppConfig.GameConnectionString = appConfig.GameConnectionString; newAppConfig.ExtConnectionString = appConfig.ExtConnectionString; newAppConfig.PhoneConnectionString = appConfig.PhoneConnectionString; + newAppConfig.AliyunConfig= appConfig.AliyunConfig; + newAppConfig.DefaultLanguage = appConfig.DefaultLanguage; return newAppConfig; } diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppRequestConfig.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppRequestConfig.cs index 2394559..1a534da 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppRequestConfig.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppRequestConfig.cs @@ -100,9 +100,9 @@ namespace CloudGaming.Code.AppExtend { if (string.IsNullOrEmpty(language)) { - if (!httpRequest.Headers.TryGetValue("Version", out var _language)) + if (!httpRequest.Headers.TryGetValue("Language", out var _language)) { - _language = "1.0.0"; + _language = "zh"; } language = _language; } diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CustomResultFilter.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CustomResultFilter.cs index 4a6b25e..42fd786 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CustomResultFilter.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CustomResultFilter.cs @@ -1,3 +1,6 @@ +using CloudGaming.Code.DataAccess; +using CloudGaming.GameModel.Db.Db_Ext; + using HuanMeng.DotNetCore.AttributeExtend; using HuanMeng.DotNetCore.Base; using HuanMeng.DotNetCore.Utility; @@ -5,12 +8,14 @@ using HuanMeng.DotNetCore.Utility; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Identity.Client; using Swashbuckle.AspNetCore.SwaggerGen; using System; using System.Collections; using System.Collections.Specialized; +using System.Diagnostics; using System.Net; using System.Reflection; @@ -22,12 +27,13 @@ namespace CloudGaming.Code.AppExtend public class CustomResultFilter : IResultFilter { private readonly IHttpContextAccessor _httpContextAccessor; - + private readonly IServiceProvider _serviceProvider; private readonly AppConfig _appConfig; - public CustomResultFilter(IHttpContextAccessor httpContextAccessor, AppConfig appConfig) + public CustomResultFilter(IHttpContextAccessor httpContextAccessor, AppConfig appConfig, IServiceProvider serviceProvider) { _httpContextAccessor = httpContextAccessor; _appConfig = appConfig; + _serviceProvider = serviceProvider; } public void OnResultExecuting(ResultExecutingContext context) @@ -35,6 +41,10 @@ namespace CloudGaming.Code.AppExtend // 获取当前的 HttpContext var httpContext = context.HttpContext; var path = httpContext.Request.Path.Value ?? ""; + var apiPrefix = path.Replace('/', '.').TrimStart('.'); + var sw = Stopwatch.StartNew(); + //_appConfig. + CloudGamingBase cloudGamingBase = new CloudGamingBase(_serviceProvider); // 获取当前用户的信息 var user = httpContext.User.Identity.IsAuthenticated ? httpContext.User.Identity.Name : "Anonymous"; //Dictionary keyValuePairs = new Dictionary(); @@ -52,198 +62,17 @@ namespace CloudGaming.Code.AppExtend value = objectResult.Value; } - var dic = value.ToDictionaryOrList(); + var dic = value.ToDictionaryOrList(apiPrefix + , it => cloudGamingBase.Cache.ImageEntityCache[it] + ); objectResult.Value = dic; - + sw.Stop(); + context.HttpContext.Response.Headers.TryAdd("X-Request-Duration-Filter", sw.Elapsed.TotalMilliseconds.ToString()); } } - - private void FindImagesAttributes(object obj) - { - if (obj == null) return; - - // 使用反射获取对象的所有公共实例属性和字段 - var membersWithImagesAttribute = obj.GetType() - .GetMembers(BindingFlags.Public | BindingFlags.Instance) - .Where(m => m is PropertyInfo || m is FieldInfo); - - foreach (var member in membersWithImagesAttribute) - { - // 获取并输出带有 [Images] 特性的属性或字段 - var imagesAttribute = member.GetCustomAttribute(); - if (imagesAttribute != null) - { - Console.WriteLine($"带有 [Images] 特性的成员:{member.Name}"); - Console.WriteLine($"FieldName 属性值:{imagesAttribute.FieldName}"); - - object value = GetMemberValue(member, obj); - Console.WriteLine($"成员值:{value}"); - } - - // 获取成员值 - var memberValue = GetMemberValue(member, obj); - - // 如果成员是 IEnumerable(集合类型),递归处理每个元素 - if (memberValue is IEnumerable enumerableValue && memberValue.GetType() != typeof(string)) - { - foreach (var item in enumerableValue) - { - if (item != null && !IsSimpleType(item.GetType())) - { - FindImagesAttributes(item); - } - } - } - // 如果成员是非集合的复杂类型,递归检查其内部成员 - else if (memberValue != null && !IsSimpleType(memberValue.GetType())) - { - FindImagesAttributes(memberValue); - } - } - } - - private object GetMemberValue(MemberInfo member, object obj) - { - try - { - return member switch - { - PropertyInfo property when property.GetMethod?.IsStatic == false => property.GetValue(obj), - FieldInfo field when !field.IsStatic => field.GetValue(obj), - _ => null - }; - } - catch (TargetParameterCountException ex) - { - Console.WriteLine($"Error retrieving value for {member.Name}: {ex.Message}"); - return null; - } - } - - private bool IsSimpleType(Type type) - { - return type.IsPrimitive || type.IsEnum || type == typeof(string) || type == typeof(decimal); - } - - public void OnResultExecuted(ResultExecutedContext context) { // 可在执行完结果后处理其他逻辑 } - - // 递归处理对象的所有属性并打印路径 - private void ProcessObjectProperties(object obj, string user, string language, string path) - { - if (obj == null || IsAnonymousType(obj.GetType())) - return; - - var objType = obj.GetType(); - - // 如果对象是集合(数组或列表),递归处理每个元素 - if (obj is IEnumerable enumerable && !(obj is string)) - { - int index = 0; - foreach (var item in enumerable) - { - ProcessObjectProperties(item, user, language, $"{path}[{index}]"); - index++; - } - return; - } - - // 遍历对象的所有属性 - foreach (var property in objType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - // 递归构建属性路径, - var propertyPath = string.IsNullOrEmpty(path) ? property.Name : $"{path}.{property.Name}"; - - // 打印当前属性路径 - Console.WriteLine($"Processing path: {propertyPath}"); - - // 判断是否是只读属性(可读不可写) - if (property.CanRead && !property.CanWrite) - { - // 为只读属性创建可写副本(仅复杂类型处理) - var originalValue = property.GetValue(obj); - if (originalValue != null && !IsPrimitiveOrString(property.PropertyType)) - { - var newValue = CloneAndModifyObject(originalValue, user, language); - // 替换原始对象的只读属性 - ReplaceReadOnlyProperty(obj, property, newValue); - } - } - else if (property.CanRead && property.CanWrite) - { - var propertyValue = property.GetValue(obj); - - // 针对字符串属性进行自定义处理 - if (property.PropertyType == typeof(string)) - { - if (propertyValue is string strValue) - { - property.SetValue(obj, $"{strValue} (modified by {user} with lang {language})"); - } - } - else if (property.PropertyType.IsClass && property.PropertyType != typeof(string)) - { - // 递归处理复杂类型,传递完整的路径 - ProcessObjectProperties(propertyValue, user, language, propertyPath); - } - } - } - } - - // 克隆只读属性的对象,并进行必要的修改 - private object CloneAndModifyObject(object source, string user, string language) - { - var sourceType = source.GetType(); - - // 如果是简单类型或者 string,不克隆 - if (IsPrimitiveOrString(sourceType) || IsAnonymousType(sourceType)) - return source; - - var newInstance = Activator.CreateInstance(sourceType); // 创建新对象实例 - - foreach (var property in sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - if (property.CanRead && property.CanWrite) - { - var value = property.GetValue(source); - // 针对字符串属性进行修改 - if (property.PropertyType == typeof(string) && value is string strValue) - { - property.SetValue(newInstance, $"{strValue} (modified by {user} with lang {language})"); - } - else - { - property.SetValue(newInstance, value); - } - } - } - - return newInstance; - } - - // 替换只读属性的值 - private void ReplaceReadOnlyProperty(object obj, PropertyInfo property, object newValue) - { - var backingField = obj.GetType().GetField($"<{property.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); - if (backingField != null) - { - backingField.SetValue(obj, newValue); - } - } - - // 判断类型是否是基础类型或 string - private bool IsPrimitiveOrString(Type type) - { - return type.IsPrimitive || type == typeof(string) || type == typeof(decimal); - } - - // 判断是否为匿名类型 - private bool IsAnonymousType(Type type) - { - return type.Name.StartsWith("<>f__AnonymousType"); - } } } diff --git a/src/CloudGaming/Code/CloudGaming.Code/Cache/CloudGamingCache.cs b/src/CloudGaming/Code/CloudGaming.Code/Cache/CloudGamingCache.cs index ddc7b4d..1941333 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Cache/CloudGamingCache.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Cache/CloudGamingCache.cs @@ -99,18 +99,34 @@ namespace CloudGaming.Code.Cache } } + #region 图片缓存 + + /// + /// + /// + private ImageEntityCache imageEntityCache; + + /// + /// 图片缓存 + /// + public ImageEntityCache ImageEntityCache + { + get + { + if (imageEntityCache == null) + { + imageEntityCache = new ImageEntityCache(_gamingBase.Dao, _gamingBase.AppConfig, _gamingBase.RedisCache, _gamingBase.AppRequestInfo); + } + return imageEntityCache; + } + } + #endregion + + #region 首页缓存表 - //private CommonDataEntityCache? _gameList; - - ///// - ///// 菜单分类 - ///// - //public List GameList => GetCacheList(ref _gameList); - - #endregion } diff --git a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/ImageEntityCache.cs b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/ImageEntityCache.cs new file mode 100644 index 0000000..3e69260 --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/ImageEntityCache.cs @@ -0,0 +1,213 @@ +using AutoMapper; + +using CloudGaming.Code.AppExtend; +using CloudGaming.Code.DataAccess; +using CloudGaming.DtoModel.Game; + +using HuanMeng.DotNetCore.CacheHelper; +using HuanMeng.DotNetCore.CacheHelper.Contract; +using HuanMeng.DotNetCore.Redis; + +using Newtonsoft.Json; + +using StackExchange.Redis; + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using static System.Net.Mime.MediaTypeNames; + +namespace CloudGaming.Code.Cache.Special +{ + /// + /// 图片缓存表 + /// + /// + /// + /// + /// + /// + public class ImageEntityCache(DAO dao, AppConfig appConfig, IDatabase database, AppRequestConfig appRequestConfig) : ICacheClearData, ICacheReloadData + { + public static bool IsLoadImage { get; set; } = false; + private string key = $"{appConfig.Identifier}:App:Image"; + private string redisKey = $"App:Image"; + ConcurrentDictionary>? ImageData { get; set; } + + /// + /// + /// + /// + /// + public ConcurrentDictionary this[string language] + { + get + { + if (string.IsNullOrEmpty(language)) + { + language = appRequestConfig.Language; + } + if (ImageData != null && ImageData.TryGetValue(language, out var images)) + { + return images; + } + ImageData = MemoryCacheHelper.GetCache>>(key); + if (ImageData == null) + { + ImageData = new ConcurrentDictionary>(); + + } + if (!ImageData.TryGetValue(language, out images)) + { + ImageData.TryAdd(language, images = new ConcurrentDictionary()); + MemoryCacheHelper.SetCache(key, ImageData, 60 * 60 * 24); + } + + return images; + + + + } + } + + /// + /// + /// + /// + /// + /// + public string this[string language, int imageId] + { + get + { + if (imageId == 0) + { + return ""; + } + var _imageData = this[language]; + if (_imageData == null) + { + _imageData = new ConcurrentDictionary(); + } + if (!_imageData.TryGetValue(imageId, out var imageUrl)) + { + var imageValue = database.StringGet($"{redisKey}:{language}:{imageId}"); + if (imageValue.IsNullOrEmpty) + { + imageValue = database.StringGet($"{redisKey}:default:{imageId}"); + } + if (!IsLoadImage && imageValue.IsNullOrEmpty) + { + if (!database.KeyExists(redisKey)) + { + ImageData = LoadImage(dao, appConfig); + MemoryCacheHelper.SetCache(key, ImageData, 60 * 60 * 24); + IsLoadImage = true; + _imageData = ImageData[language]; + if (!_imageData.TryGetValue(imageId, out imageUrl)) + { + imageUrl = ""; + } + return imageUrl; + } + } + imageUrl = imageValue; + _imageData.TryAdd(imageId, imageUrl); + //if (!_imageData.TryGetValue(imageId, out imageUrl)) + //{ + // if (string.IsNullOrEmpty(imageUrl)) + // { + // imageUrl = ""; + // } + + //}//imageValue.ToString(); + + //MemoryCacheHelper.SetCache(key, ImageData, 60 * 60 * 24); + + } + return imageUrl; + } + } + + public ConcurrentDictionary> LoadImage(DAO dao, AppConfig appConfig) + { + // 初始化_data字典 + ConcurrentDictionary> _data = new ConcurrentDictionary>(); + + // 获取图像列表 + var imageList = dao.DaoExt.Context.T_App_Image + .Where(it => !string.IsNullOrEmpty(it.Url)) + .AsNoTracking() + .Select(it => new { it.ImageId, it.Language, it.Url }) + .ToList(); + + // 设置默认语言 + if (string.IsNullOrEmpty(appConfig.DefaultLanguage)) + { + appConfig.DefaultLanguage = "zh"; + } + + // 创建默认语言的图像字典 + var defaultImage = imageList + .Where(it => it.Language == appConfig.DefaultLanguage) + .GroupBy(it => it.ImageId) + .ToDictionary(group => group.Key, group => appConfig.AliyunConfig.ImagePrefix + group.Last().Url); + + // 遍历图像列表,填充_data字典 + foreach (var item in imageList) + { + // 尝试获取语言字典,如果不存在则创建 + if (!_data.TryGetValue(item.Language, out var languageImage)) + { + languageImage = new ConcurrentDictionary(); + _data[item.Language] = languageImage; + } + + // 设置图像URL + languageImage[item.ImageId] = appConfig.AliyunConfig.ImagePrefix + item.Url; + + // 如果默认图像字典中没有此ImageId,加入默认图像字典 + if (!defaultImage.ContainsKey(item.ImageId)) + { + defaultImage[item.ImageId] = appConfig.AliyunConfig.ImagePrefix + item.Url; + } + } + + // 更新默认语言的图像字典 + _data["default"] = new ConcurrentDictionary(defaultImage); + foreach (var item in _data.Keys) + { + foreach (var image in _data[item]) + { + string _redisKey = $"{redisKey}:{item}:{image.Key}"; + database.StringSet(_redisKey, image.Value, TimeSpan.FromDays(1)); + } + } + database.StringSet(redisKey, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), TimeSpan.FromDays(1)); + return _data; + } + + public string this[int imageId] + { + get + { + var imageUrl = this[appRequestConfig.Language, imageId]; + return imageUrl; + } + } + + public bool ClearData() + { + throw new NotImplementedException(); + } + + public void ReloadData() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/CloudGaming.DtoModel.csproj b/src/CloudGaming/Model/CloudGaming.DtoModel/CloudGaming.DtoModel.csproj index 2558b98..926a996 100644 --- a/src/CloudGaming/Model/CloudGaming.DtoModel/CloudGaming.DtoModel.csproj +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/CloudGaming.DtoModel.csproj @@ -17,4 +17,8 @@ + + + + diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/ObjectExtensions1.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/ObjectExtensions1.cs index 40c5e37..acce3cf 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/ObjectExtensions1.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/ObjectExtensions1.cs @@ -46,15 +46,15 @@ public static class ObjectExtensions /// 要转换的对象。 /// 属性路径的可选前缀。 /// 对象的字典或列表表示形式。 - public static object ToDictionaryOrList(this object obj, string prefix = "") + public static object ToDictionaryOrList(this object obj, string prefix = "", Func? imageFunc = null) { if (obj == null) return null; return obj switch { _ when IsPrimitiveType(obj) => obj, - IEnumerable enumerable => TransformCollection(enumerable, prefix), - _ => TransformObject(obj, prefix) + IEnumerable enumerable => TransformCollection(enumerable, prefix, imageFunc), + _ => TransformObject(obj, prefix, imageFunc) }; } @@ -64,13 +64,13 @@ public static class ObjectExtensions /// 要转换的集合。 /// 集合中每个属性路径的前缀。 /// 转换后的项列表。 - private static List TransformCollection(IEnumerable enumerable, string prefix = "") + private static List TransformCollection(IEnumerable enumerable, string prefix = "", Func? imageFunc = null) { var list = new List(enumerable is ICollection collection ? collection.Count : 10); int index = 0; foreach (var item in enumerable) { - list.Add(ToDictionaryOrList(item, $"{prefix}.[{index}]")); // 为集合中每个项添加路径 + list.Add(ToDictionaryOrList(item, $"{prefix}.[{index}]", imageFunc)); // 为集合中每个项添加路径 index++; } return list; @@ -82,7 +82,7 @@ public static class ObjectExtensions /// 要转换的对象。 /// 每个属性路径的前缀。 /// 包含属性名和属性值的字典。 - private static Dictionary TransformObject(object obj, string prefix = "") + private static Dictionary TransformObject(object obj, string prefix = "", Func? imageFunc = null) { if (obj == null) { @@ -95,7 +95,7 @@ public static class ObjectExtensions foreach (var accessor in accessors) { var propertyPath = $"{prefix}.{accessor.PropertyName}"; // 构建完整的属性路径 - + // 使用访问器获取属性值 var propertyValue = accessor.Getter(obj); @@ -110,8 +110,8 @@ public static class ObjectExtensions // 如果属性具有 ImagesAttribute,在其值前添加 "image" // 否则,如果是集合类型,则递归转换 keyValuePairs[accessor.PropertyName] = accessor.HasImagesAttribute - ? $"image{propertyValue}" - : IsCollectionType(propertyValue) ? ToDictionaryOrList(propertyValue, propertyPath) : propertyValue; + ? imageFunc?.Invoke((int)propertyValue) ?? "" + : IsCollectionType(propertyValue) ? ToDictionaryOrList(propertyValue, propertyPath, imageFunc) : propertyValue; } return keyValuePairs; @@ -135,7 +135,7 @@ public static class ObjectExtensions }).ToArray(); } - + private static Func CreatePropertyGetter(Type type, PropertyInfo property) {