提交代码

This commit is contained in:
zpc 2024-11-08 03:19:32 +08:00
parent d762ed726d
commit 56e3b1d901
9 changed files with 329 additions and 208 deletions

View File

@ -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
{
/// <summary>
///
/// </summary>
public string AccessKeyId { get; set; }
/// <summary>
/// 配置环境变量
/// </summary>
public string AccessKeySecret { get; set; }
/// <summary>
/// 替换为Bucket所在地域对应的Endpoint。以华东1杭州为例Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
/// </summary>
public string EndPoint { get; set; }
/// <summary>
/// Bucket名称。
/// </summary>
public string BucketName { get; set; }
/// <summary>
/// 上传路径
/// </summary>
public string UploadPath { get; set; }
/// <summary>
/// 域名
/// </summary>
public string? DomainName { get; set; }
/// <summary>
/// 前缀
/// </summary>
public string ImagePrefix
{
get
{
return this.DomainName + this.UploadPath;
}
}
}
}

View File

@ -48,6 +48,10 @@ namespace CloudGaming.Code.AppExtend
/// </summary>
public string Name { get; set; }
/// <summary>
/// 默认语言
/// </summary>
public string DefaultLanguage { get; set; }
/// <summary>
/// 租户
/// </summary>
@ -57,6 +61,10 @@ namespace CloudGaming.Code.AppExtend
/// </summary>
//public PaymentModel? Payment { get; set; }
/// <summary>
/// oss阿里云配置
/// </summary>
public AliyunOssConfig AliyunConfig { get; set; }
/// <summary>

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<string, object> keyValuePairs = new Dictionary<string, object>();
@ -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<ImagesAttribute>();
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");
}
}
}

View File

@ -99,18 +99,34 @@ namespace CloudGaming.Code.Cache
}
}
#region
/// <summary>
///
/// </summary>
private ImageEntityCache imageEntityCache;
/// <summary>
/// 图片缓存
/// </summary>
public ImageEntityCache ImageEntityCache
{
get
{
if (imageEntityCache == null)
{
imageEntityCache = new ImageEntityCache(_gamingBase.Dao, _gamingBase.AppConfig, _gamingBase.RedisCache, _gamingBase.AppRequestInfo);
}
return imageEntityCache;
}
}
#endregion
#region
//private CommonDataEntityCache<T_Game_List>? _gameList;
///// <summary>
///// 菜单分类
///// </summary>
//public List<T_Game_List> GameList => GetCacheList(ref _gameList);
#endregion
}

View File

@ -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
{
/// <summary>
/// 图片缓存表
/// </summary>
/// <param name="dao"></param>
/// <param name="appConfig"></param>
/// <param name="database"></param>
/// <param name="mapper"></param>
/// <param name="appRequestConfig"></param>
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<string, ConcurrentDictionary<int, string>>? ImageData { get; set; }
/// <summary>
///
/// </summary>
/// <param name="language"></param>
/// <returns></returns>
public ConcurrentDictionary<int, string> 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<ConcurrentDictionary<string, ConcurrentDictionary<int, string>>>(key);
if (ImageData == null)
{
ImageData = new ConcurrentDictionary<string, ConcurrentDictionary<int, string>>();
}
if (!ImageData.TryGetValue(language, out images))
{
ImageData.TryAdd(language, images = new ConcurrentDictionary<int, string>());
MemoryCacheHelper.SetCache(key, ImageData, 60 * 60 * 24);
}
return images;
}
}
/// <summary>
///
/// </summary>
/// <param name="language"></param>
/// <param name="imageId"></param>
/// <returns></returns>
public string this[string language, int imageId]
{
get
{
if (imageId == 0)
{
return "";
}
var _imageData = this[language];
if (_imageData == null)
{
_imageData = new ConcurrentDictionary<int, string>();
}
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<string, ConcurrentDictionary<int, string>> LoadImage(DAO dao, AppConfig appConfig)
{
// 初始化_data字典
ConcurrentDictionary<string, ConcurrentDictionary<int, string>> _data = new ConcurrentDictionary<string, ConcurrentDictionary<int, string>>();
// 获取图像列表
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<int, string>();
_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<int, string>(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();
}
}
}

View File

@ -17,4 +17,8 @@
<ProjectReference Include="..\CloudGaming.Model\CloudGaming.Model.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Image\" />
</ItemGroup>
</Project>

View File

@ -46,15 +46,15 @@ public static class ObjectExtensions
/// <param name="obj">要转换的对象。</param>
/// <param name="prefix">属性路径的可选前缀。</param>
/// <returns>对象的字典或列表表示形式。</returns>
public static object ToDictionaryOrList(this object obj, string prefix = "")
public static object ToDictionaryOrList(this object obj, string prefix = "", Func<int, string>? 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
/// <param name="enumerable">要转换的集合。</param>
/// <param name="prefix">集合中每个属性路径的前缀。</param>
/// <returns>转换后的项列表。</returns>
private static List<object> TransformCollection(IEnumerable enumerable, string prefix = "")
private static List<object> TransformCollection(IEnumerable enumerable, string prefix = "", Func<int, string>? imageFunc = null)
{
var list = new List<object>(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
/// <param name="obj">要转换的对象。</param>
/// <param name="prefix">每个属性路径的前缀。</param>
/// <returns>包含属性名和属性值的字典。</returns>
private static Dictionary<string, object> TransformObject(object obj, string prefix = "")
private static Dictionary<string, object> TransformObject(object obj, string prefix = "", Func<int, string>? 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<object, object> CreatePropertyGetter(Type type, PropertyInfo property)
{