This commit is contained in:
zpc 2024-11-08 16:05:21 +08:00
parent 56e3b1d901
commit 7f5b0651d4
16 changed files with 577 additions and 15 deletions

View File

@ -0,0 +1,52 @@
using CloudGaming.Api.Base;
using CloudGaming.Code.Cache;
using CloudGaming.Code.Game;
using CloudGaming.Code.MiddlewareExtend;
using CloudGaming.DtoModel.Game;
using CloudGaming.GameModel.Db.Db_Game;
using Microsoft.AspNetCore.Mvc;
namespace CloudGaming.Api.Controllers
{
/// <summary>
/// 游戏控制器
/// </summary>
public class GameController : CloudGamingControllerBase
{
public GameController(IServiceProvider _serviceProvider) : base(_serviceProvider)
{
}
/// <summary>
/// 游戏类型列表
/// </summary>
/// <returns></returns>
[HttpGet]
[RedisCache(2, 0, 0)]
public async Task<List<GameExtendedAttribute>> GetGameTypeListAsync()
{
GameBLL gamebll = new GameBLL(this.ServiceProvider);
return await gamebll.GetGameTypeListAsync();
}
/// <summary>
/// 根据游戏类型Id 获取游戏列表
/// </summary>
/// <param name="typeId"></param>
/// <returns></returns>
[HttpGet]
[RedisCache(2, 5, 0)]
public async Task<List<GameListDto>> GetGameListAsync([FromQuery] int typeId)
{
if (typeId == 0)
{
return new List<GameListDto>();
}
GameBLL gamebll = new GameBLL(this.ServiceProvider);
return await gamebll.GetGameListAsync(typeId);
}
}
}

View File

@ -1,5 +1,6 @@
using CloudGaming.Api.Base;
using CloudGaming.Code.Epg;
using CloudGaming.Code.MiddlewareExtend;
using CloudGaming.DtoModel.Epg;
using Microsoft.AspNetCore.JsonPatch.Internal;
@ -18,6 +19,7 @@ public class HomeController : CloudGamingControllerBase
/// </summary>
/// <returns></returns>
[HttpGet]
[RedisCache(1, 0, 0)]
public async Task<List<EpgCategoryDto>> GetHomeInfo()
{
EpgBLL epgBLL = new EpgBLL(ServiceProvider);

View File

@ -17,6 +17,7 @@ using CloudGaming.Code.AppExtend.JsonConverHelper;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Options;
using CloudGaming.GameModel.Db.Db_Ext;
using CloudGaming.Code.MiddlewareExtend;
var builder = WebApplication.CreateBuilder(args);
#region
// Add services to the container.
@ -37,6 +38,7 @@ builder.Services.AddSingleton(typeof(ILogger<ExceptionMiddleware>), serviceProvi
return loggerFactory.CreateLogger<ExceptionMiddleware>();
});
#endregion
builder.Services.AddMemoryCache();
builder.Services.AddHttpClient();
builder.Services.AddHttpContextAccessor(); //添加httpContext注入访问
#region
@ -168,6 +170,8 @@ app.MapControllers();
app.UseStaticFiles();//静态文件访问配置
//执行扩展中间件
app.UseMiddlewareAll();
//缓存中间件
app.UseCloudGamingMiddlewareAll();
#region
app.MapGet("/", () => "请求成功").WithName("默认请求");

View File

@ -3,6 +3,9 @@ using AgileConfig.Client;
using CloudGaming.Code.DataAccess.MultiTenantUtil;
using HuanMeng.DotNetCore.CacheHelper;
using HuanMeng.DotNetCore.Redis;
using StackExchange.Redis;
using System.Collections.Concurrent;
using System.Collections.Frozen;
@ -136,6 +139,15 @@ namespace CloudGaming.Code.AppExtend
tenantInfo.Name = appConfig.Name;
return tenantInfo;
}
/// <summary>
/// 获取redis缓存
/// </summary>
/// <param name="appConfig"></param>
/// <returns></returns>
public static IDatabase GetRedisDataBase(this AppConfig appConfig)
{
return RedisConnection.GetRedis(appConfig.RedisConnectionString);
}
/// <summary>
///

View File

@ -110,6 +110,15 @@ namespace CloudGaming.Code.AppExtend
}
}
/// <summary>
/// 获取key
/// </summary>
public string Key
{
get => $"{Language}_{Channel}_{Version}";
}
/// <summary>
/// 地区
/// </summary>

View File

@ -71,7 +71,7 @@ namespace CloudGaming.Code.Cache
#endregion
public GameEntityCache? gameEntityCache;
private GameEntityCache? gameEntityCache;
/// <summary>
/// 游戏缓存

View File

@ -27,7 +27,8 @@ namespace CloudGaming.Code.Cache.Special
public override string key => $"{appConfig.Identifier}:game:gameInfo";
public string locaKey => $"{appConfig.Identifier}:lock:gameInfo";
public string locaKey => $"lock:gameInfo";
public string RedisKey => $"cache:game:gameInfo";
public override List<GameInfo> GetDataList()
{
@ -71,10 +72,10 @@ namespace CloudGaming.Code.Cache.Special
return _dataList;
}
long hashLength = database.HashLength(key);
long hashLength = database.HashLength(RedisKey);
if (hashLength > 0)
{
var hashEntries = database.HashGetAll(key);
var hashEntries = database.HashGetAll(RedisKey);
var list = hashEntries
.Where(entry => !string.IsNullOrEmpty(entry.Value))
.Select(entry => JsonConvert.DeserializeObject<GameInfo>(entry.Value))
@ -94,7 +95,7 @@ namespace CloudGaming.Code.Cache.Special
var serializedGameInfos = tempDataList
.Select(info => new HashEntry($"gameInfo:{info.GameId}", JsonConvert.SerializeObject(info)))
.ToArray();
database.HashSet(key, serializedGameInfos);
database.HashSet(RedisKey, serializedGameInfos);
MemoryCacheHelper.SetCache(key, tempDataList, 60 * 60);
_dataList = tempDataList;

View File

@ -29,8 +29,7 @@ namespace CloudGaming.Code.DataAccess.MultiTenantUtil
/// <returns></returns>
public virtual async Task Invoke(HttpContext context,
IServiceProvider _serviceProvider,
AppConfig appConfig
)
AppConfig appConfig )
{
var host = context.Request.Host.Host;

View File

@ -47,8 +47,14 @@ namespace CloudGaming.Code.Epg
return null;
}
epgInfo.Title ??= gameInfo.GameName;
epgInfo.SubTitle ??= gameInfo.Title2;
if (string.IsNullOrEmpty(epgInfo.Title))
{
epgInfo.Title = gameInfo.GameName;
}
if (string.IsNullOrEmpty(epgInfo.SubTitle))
{
epgInfo.SubTitle = gameInfo.Title2;
}
if (epgInfo.ImageUrl == 0 && !string.IsNullOrEmpty(epgCfg.ImageResStyle))
{

View File

@ -0,0 +1,64 @@
using CloudGaming.Code.Cache;
using CloudGaming.DtoModel.Game;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CloudGaming.Code.Game
{
/// <summary>
/// 游戏逻辑类
/// </summary>
public class GameBLL : CloudGamingBase
{
public GameBLL(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public async Task<List<GameExtendedAttribute>> GetGameTypeListAsync()
{
var gameList = Cache.GameEntityCache?.DataList ?? new List<GameInfo>();
var gameTypes = await Task.Run(() =>
{
return CloudGamingCacheExtend.GetDataEntityCache<T_Game_Types>(this, it => it.IsOnline)?.DataList
.Where(it => gameList.Any(item => item.GameType?.Any(gameType => gameType.Id == it.TypeId) ?? false))
.OrderBy(it => it.OrderId)
.Select(it => new GameExtendedAttribute
{
Id = it.TypeId,
Name = it.TypeName,
OrderId = it.OrderId
})
.ToList();
});
return gameTypes;
}
/// <summary>
/// 根据游戏类型Id 获取游戏列表
/// </summary>
/// <param name="typeId"></param>
/// <returns></returns>
public async Task<List<GameListDto>> GetGameListAsync(int typeId)
{
var gameListDto = await Task.Run(() =>
{
var gameList = Cache.GameEntityCache?.DataList ?? new List<GameInfo>();
var x = gameList.Where(it => it.GameType?.Any(item => item.Id == typeId) ?? false).Select(it => new
GameListDto(it)).ToList();
return x;
});
return gameListDto;
}
}
}

View File

@ -0,0 +1,147 @@
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;
using System.Collections.Concurrent;
namespace CloudGaming.Code.MiddlewareExtend
{
/// <summary>
/// 内存缓存
/// </summary>
public class MemoryCacheMiddleware
{
private readonly RequestDelegate _next;
private readonly IMemoryCache _cache;
private static readonly ConcurrentDictionary<Endpoint, MemoryCacheAttribute> _attributeCache = new();
/// <summary>
///
/// </summary>
/// <param name="next"></param>
/// <param name="cache"></param>
public MemoryCacheMiddleware(RequestDelegate next, IMemoryCache cache)
{
_next = next;
_cache = cache;
}
public async Task InvokeAsync(HttpContext context,
IServiceProvider _serviceProvider,
AppConfig appConfig
)
{
// 检查当前请求是否需要缓存
var cacheAttribute = GetCacheAttribute(context);
if (cacheAttribute == null)
{
// 如果没有找到 MemoryCacheAttribute直接调用下一个中间件
await _next(context);
return;
}
// 生成缓存键(基于请求路径和查询字符串)
var cacheKey = GenerateCacheKey(context.Request, appConfig);
// 尝试从缓存中获取数据
if (_cache.TryGetValue(cacheKey, out string cachedResponse))
{
context.Response.StatusCode = StatusCodes.Status200OK;
// 如果缓存中有数据,则直接返回缓存的响应
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(cachedResponse);
return;
}
// 捕获原始响应流
var originalResponseStream = context.Response.Body;
try
{
using (var memoryStream = new MemoryStream())
{
context.Response.Body = memoryStream;
// 调用下一个中间件
await _next(context);
// 将响应流重新定位到起始位置
memoryStream.Position = 0;
// 读取响应内容
var responseBody = await new StreamReader(memoryStream).ReadToEndAsync();
// 将响应内容缓存
_cache.Set(cacheKey, responseBody, cacheAttribute.TimeSpan);
// 将内容写回原始响应流
memoryStream.Position = 0;
await memoryStream.CopyToAsync(originalResponseStream);
}
}
finally
{
// 恢复原始响应流
context.Response.Body = originalResponseStream;
}
}
private MemoryCacheAttribute GetCacheAttribute(HttpContext context)
{
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
if (endpoint == null) return null;
// 使用字典缓存以减少重复获取
if (_attributeCache.TryGetValue(endpoint, out var cachedAttribute))
{
return cachedAttribute;
}
// 获取并缓存 MemoryCacheAttribute
var attribute = endpoint.Metadata.GetMetadata<MemoryCacheAttribute>();
if (attribute != null)
{
_attributeCache.TryAdd(endpoint, attribute);
}
return attribute;
}
private string GenerateCacheKey(HttpRequest request, AppConfig appConfig)
{
var appRequestInfo = new AppRequestConfig(request);
// 基于请求路径和查询参数生成缓存键
var cacheKey = $"{appConfig.Identifier}:{request.Path.Value.Replace('/', '.').TrimStart('.')}:{(string.IsNullOrEmpty(request.QueryString.Value) ? "default" : request.QueryString)}:{appRequestInfo.Key}";
return cacheKey;
}
}
/// <summary>
/// 内存缓存特性类
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class MemoryCacheAttribute : Attribute
{
public TimeSpan TimeSpan { get; }
public MemoryCacheAttribute(int durationInSeconds)
{
TimeSpan = TimeSpan.FromSeconds(durationInSeconds);
}
public MemoryCacheAttribute(int hours, int minutes, int seconds)
{
TimeSpan = new TimeSpan(hours, minutes, seconds);
}
public MemoryCacheAttribute(int minutes, int seconds)
{
TimeSpan = new TimeSpan(0, minutes, seconds);
}
}
}

View File

@ -0,0 +1,45 @@
using HuanMeng.DotNetCore.MiddlewareExtend;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CloudGaming.Code.MiddlewareExtend
{
/// <summary>
///
/// </summary>
public static class MiddlewareExtends
{
/// <summary>
/// 加载全部中间件
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseCloudGamingMiddlewareAll(this IApplicationBuilder builder)
{
return builder
.UseCacheMiddleware()
.UseRedisCacheMiddleware()
;
}
/// <summary>
/// 缓存中间件
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseCacheMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<MemoryCacheMiddleware>();
}
public static IApplicationBuilder UseRedisCacheMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RedisCacheMiddleware>();
}
}
}

View File

@ -0,0 +1,124 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace CloudGaming.Code.MiddlewareExtend
{
/// <summary>
/// Redis 缓存中间件
/// </summary>
public class RedisCacheMiddleware
{
private readonly RequestDelegate _next;
private static readonly ConcurrentDictionary<Endpoint, RedisCacheAttribute> _attributeCache = new();
public RedisCacheMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context,
IServiceProvider _serviceProvider,
AppConfig appConfig
)
{
// 检查当前请求是否需要缓存
var cacheAttribute = GetCacheAttribute(context);
if (cacheAttribute == null)
{
await _next(context);
return;
}
var _cache = appConfig.GetRedisDataBase();
// 生成缓存键(基于请求路径和查询字符串)
var cacheKey = GenerateCacheKey(context.Request, appConfig);
// 尝试从 Redis 缓存中获取数据
var cachedResponse = await _cache.StringGetAsync(cacheKey);
if (!cachedResponse.IsNullOrEmpty)
{
context.Response.StatusCode = StatusCodes.Status200OK;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(cachedResponse);
return;
}
// 捕获原始响应流
var originalResponseStream = context.Response.Body;
try
{
using (var memoryStream = new MemoryStream())
{
context.Response.Body = memoryStream;
// 调用下一个中间件
await _next(context);
// 将响应流重新定位到起始位置
memoryStream.Position = 0;
// 读取响应内容
var responseBody = await new StreamReader(memoryStream).ReadToEndAsync();
await _cache.StringSetAsync(cacheKey, responseBody, cacheAttribute.TimeSpan);
// 将内容写回原始响应流
memoryStream.Position = 0;
await memoryStream.CopyToAsync(originalResponseStream);
}
}
finally
{
context.Response.Body = originalResponseStream;
}
}
private RedisCacheAttribute GetCacheAttribute(HttpContext context)
{
var endpoint = context.Features.Get<IEndpointFeature>()?.Endpoint;
if (endpoint == null) return null;
if (_attributeCache.TryGetValue(endpoint, out var cachedAttribute))
{
return cachedAttribute;
}
var attribute = endpoint.Metadata.GetMetadata<RedisCacheAttribute>();
if (attribute != null)
{
_attributeCache.TryAdd(endpoint, attribute);
}
return attribute;
}
private string GenerateCacheKey(HttpRequest request, AppConfig appConfig)
{
var appRequestInfo = new AppRequestConfig(request);
var cacheKey = $"cache:api:{request.Path.Value.Replace('/', '.').TrimStart('.')}:{appRequestInfo.Key}:{(string.IsNullOrEmpty(request.QueryString.Value) ? "default" : request.QueryString)}";
return cacheKey;
}
}
/// <summary>
/// Redis 缓存特性类
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class RedisCacheAttribute : Attribute
{
public TimeSpan TimeSpan { get; }
public RedisCacheAttribute(int durationInSeconds) => TimeSpan = TimeSpan.FromSeconds(durationInSeconds);
public RedisCacheAttribute(int hours, int minutes, int seconds) => TimeSpan = new TimeSpan(hours, minutes, seconds);
public RedisCacheAttribute(int minutes, int seconds) => TimeSpan = new TimeSpan(0, minutes, seconds);
}
}

View File

@ -0,0 +1,40 @@
using HuanMeng.DotNetCore.AttributeExtend;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CloudGaming.DtoModel.Game
{
public class GameInfoDto
{
/// <summary>
/// 游戏Id
/// </summary>
public virtual string GameId { get; set; } = null!;
/// <summary>
/// 游戏icon
/// </summary>
[Images("ImageIcon")]
public virtual int ImageIcon { get; set; }
/// <summary>
///
/// </summary>
[Images("GameBgImage")]
public virtual int GameBgImage { get; set; }
/// <summary>
/// 游戏标签
/// </summary>
public List<GameExtendedAttribute> GameTags { get; set; }
/// <summary>
/// 游戏列表
/// </summary>
public List<GameExtendedAttribute> GameType { get; set; }
}
}

View File

@ -0,0 +1,48 @@
using HuanMeng.DotNetCore.AttributeExtend;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CloudGaming.DtoModel.Game
{
/// <summary>
/// 游戏列表数据
/// </summary>
public class GameListDto
{
public GameListDto() { }
/// <summary>
///
/// </summary>
/// <param name="gameInfo"></param>
public GameListDto(GameInfo gameInfo)
{
if (gameInfo != null)
{
this.GameName = gameInfo.GameName;
this.GameId = gameInfo.GameId;
this.GameIconImage = gameInfo.GameImageId;
}
}
/// <summary>
/// 游戏Id
/// </summary>
public string GameId { get; set; }
/// <summary>
/// 游戏名称
/// </summary>
public string GameName { get; set; }
/// <summary>
/// 游戏icon
/// </summary>
[Images]
public int GameIconImage { get; set; }
}
}

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Reflection;
using HuanMeng.DotNetCore.AttributeExtend;
using System.Linq.Expressions;
using Microsoft.IdentityModel.Tokens;
namespace HuanMeng.DotNetCore.Utility;
@ -23,7 +24,7 @@ public static class ObjectExtensions
/// <summary>
/// 缓存每个属性是否具有 ImagesAttribute 特性。
/// </summary>
public static readonly ConcurrentDictionary<PropertyInfo, bool> _PropertyCache = new();
public static readonly ConcurrentDictionary<PropertyInfo, ImagesAttribute?> _PropertyCache = new();
/// <summary>
/// 判断对象是否为原始类型或字符串类型。
@ -102,8 +103,8 @@ public static class ObjectExtensions
// 如果属性是字符串,在其值前添加 "test"
if (propertyValue is string stringValue)
{
keyValuePairs[accessor.PropertyName] = $"test{stringValue}";
Console.WriteLine(propertyPath);
keyValuePairs[accessor.PropertyName] = stringValue;
//Console.WriteLine(propertyPath);
continue;
}
@ -130,8 +131,16 @@ public static class ObjectExtensions
// 创建用于访问属性值的委托
var getter = CreatePropertyGetter(type, property);
// 检查属性是否具有 ImagesAttribute并将结果存储在缓存中
bool hasImagesAttribute = _PropertyCache.GetOrAdd(property, p => p.GetCustomAttribute<ImagesAttribute>() != null);
return new PropertyAccessor(property.Name, getter, hasImagesAttribute);
var imagesAttribute = _PropertyCache.GetOrAdd(property, p => p.GetCustomAttribute<ImagesAttribute>());
if (imagesAttribute != null)
{
if (!string.IsNullOrEmpty(imagesAttribute.FieldName))
{
return new PropertyAccessor(imagesAttribute.FieldName, getter, true);
}
return new PropertyAccessor(property.Name, getter, true);
}
return new PropertyAccessor(property.Name, getter, false);
}).ToArray();
}