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