diff --git a/src/CloudGaming/Api/CloudGaming.ExtApi/Controllers/CacheController.cs b/src/CloudGaming/Api/CloudGaming.ExtApi/Controllers/CacheController.cs new file mode 100644 index 0000000..8e91404 --- /dev/null +++ b/src/CloudGaming/Api/CloudGaming.ExtApi/Controllers/CacheController.cs @@ -0,0 +1,44 @@ +using CloudGaming.Code.Config; +using CloudGaming.ExtApi.Base; + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +using System.Text; + +namespace CloudGaming.ExtApi.Controllers +{ + + /// + /// 缓存管理 + /// + public class CacheController : CloudGamingControllerBase + { + public CacheController(IServiceProvider _serviceProvider) : base(_serviceProvider) + { + } + + /// + /// 清除缓存 + /// + /// + [HttpGet] + public async Task ClearAllCacheData() + { + var outputStream = Response.Body; + + Response.ContentType = "text/plain"; // 设置响应内容类型 + Response.StatusCode = 200; // 设置状态码 + await foreach (var item in new AppConfigBLL(ServiceProvider).ClearAllCacheData()) + { + if (item == null) + { + continue; + } + await outputStream.WriteAsync(Encoding.UTF8.GetBytes(item)); + await outputStream.FlushAsync(); + } + return new EmptyResult(); + } + } +} diff --git a/src/CloudGaming/Api/CloudGaming.ExtApi/Controllers/MonitorController.cs b/src/CloudGaming/Api/CloudGaming.ExtApi/Controllers/MonitorController.cs index a656c6d..5c589e1 100644 --- a/src/CloudGaming/Api/CloudGaming.ExtApi/Controllers/MonitorController.cs +++ b/src/CloudGaming/Api/CloudGaming.ExtApi/Controllers/MonitorController.cs @@ -49,4 +49,5 @@ public class MonitorController : CloudGamingControllerBase return await new MonitorBLL(ServiceProvider).GetAppMonitorHourAsync(startTimeStamp, endTimeStamp); } + } diff --git a/src/CloudGaming/Api/CloudGaming.ExtApi/Program.cs b/src/CloudGaming/Api/CloudGaming.ExtApi/Program.cs index 6167435..16bb5b6 100644 --- a/src/CloudGaming/Api/CloudGaming.ExtApi/Program.cs +++ b/src/CloudGaming/Api/CloudGaming.ExtApi/Program.cs @@ -1,10 +1,14 @@ +using CloudGaming.AppConfigModel; using CloudGaming.Code.AppExtend; using CloudGaming.Code.DataAccess.MultiTenantUtil; using CloudGaming.Code.Extend; using CloudGaming.Code.Filter; using CloudGaming.Code.Game; using CloudGaming.Code.Monitor; +using CloudGaming.DtoModel; using CloudGaming.GameModel.Db.Db_Ext; +using CloudGaming.GameModel.Db.Db_Game; +using CloudGaming.Model.DbSqlServer.Db_User; using HuanMeng.DotNetCore.CustomExtension; using HuanMeng.DotNetCore.MiddlewareExtend; @@ -44,16 +48,10 @@ builder.Services.AddSingleton(typeof(ILogger), serviceProvi #endregion #region automap var mapperDomain = AppDomain.CurrentDomain.GetAssemblies().Where(it => it.FullName.Contains("HuanMeng") || it.FullName.Contains("CloudGaming.")).ToList(); -Type type = typeof(T_App_Config); -if (type != null) -{ - Assembly assembly = Assembly.GetAssembly(type); - if (!mapperDomain.Any(it => it.FullName == assembly.FullName)) - { - mapperDomain.Add(assembly); - } -} - +AddAssembly(mapperDomain, typeof(T_App_Config)); +AddAssembly(mapperDomain,typeof(AppConfig)); +AddAssembly(mapperDomain, typeof(AppConfigCache)); +AddAssembly(mapperDomain, typeof(T_User)); builder.Services.AddAutoMapper(mapperDomain); #endregion #region 添加跨域 @@ -140,3 +138,16 @@ app.UseAppRequest("ext"); #endregion #endregion app.Run(); + +void AddAssembly(List mapperDomain, Type type) +{ + + if (type != null) + { + Assembly assembly = Assembly.GetAssembly(type); + if (!mapperDomain.Any(it => it.FullName == assembly.FullName)) + { + mapperDomain.Add(assembly); + } + } +} \ No newline at end of file diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs index f4bcfe6..a092db9 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs @@ -246,6 +246,7 @@ namespace CloudGaming.Code.AppExtend newAppConfig.PrivacyAgreement = appConfig.PrivacyAgreement; newAppConfig.UserAgreement = appConfig.UserAgreement; newAppConfig.LanguageRequestUrl = appConfig.LanguageRequestUrl; + newAppConfig.CacheRequestUrls = appConfig.CacheRequestUrls; return newAppConfig; } diff --git a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/AppConfigEntityCache.cs b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/AppConfigEntityCache.cs index 55a7423..dfcbe18 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/AppConfigEntityCache.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/AppConfigEntityCache.cs @@ -25,13 +25,13 @@ namespace CloudGaming.Code.Cache.Special /// /// /// - public class AppConfigEntityCache(DAO dao, IDatabase database, IMapper mapper, AppConfig appConfig) : RedisDataEntityCache(database, 0) + public class AppConfigEntityCache(DAO dao, IDatabase database, IMapper mapper, AppConfig appConfig) : RedisDataEntityCache(database, 0, "系统配置") { public override string key => $"cache:appconfig:list"; public override List GetDataList() { - var list = dao.DaoExt.Context.T_App_Config.Where(it => it.ConfigType !=(int)AppConfigType.协议配置).ToList(); + var list = dao.DaoExt.Context.T_App_Config.Where(it => it.ConfigType != (int)AppConfigType.协议配置).ToList(); var appList = mapper.Map>(list); return appList; diff --git a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/GameEntityCache.cs b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/GameEntityCache.cs index 26f6dc3..f207d42 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/GameEntityCache.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/GameEntityCache.cs @@ -120,7 +120,7 @@ namespace CloudGaming.Code.Cache.Special .Select(entry => JsonConvert.DeserializeObject(entry.Value)) .ToList(); - MemoryCacheHelper.SetCache(key, list, 10); + MemoryCacheHelper.SetCache(key, list, 60 * 60 * 12); _dataList = list; return _dataList; } @@ -135,8 +135,8 @@ namespace CloudGaming.Code.Cache.Special .Select(info => new HashEntry($"gameInfo:{info.GameId}", JsonConvert.SerializeObject(info))) .ToArray(); database.HashSet(RedisKey, serializedGameInfos); - - MemoryCacheHelper.SetCache(key, tempDataList, 60 * 60); + database.StringSet($"time:{RedisKey.Replace(":", ".")}", $"刷新游戏缓存时间{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}"); + MemoryCacheHelper.SetCache(key, tempDataList, 60 * 60 * 12); _dataList = tempDataList; database.KeyDelete(locaKey); return _dataList; @@ -216,26 +216,20 @@ namespace CloudGaming.Code.Cache.Special public override bool ClearData() { - lock (GameEntityCacheLock) - { - database.KeyDelete(RedisKey); - MemoryCacheHelper.DelCache(key); - _dataList = null; - gameInfoDic = null; - } + database.KeyDelete(RedisKey); + MemoryCacheHelper.DelCache(key); + _dataList = null; + gameInfoDic = null; return true; } public override void ReloadData() { - lock (lockObj) - { - database.KeyDelete(RedisKey); - MemoryCacheHelper.DelCache(key); - _dataList = null; - gameInfoDic = null; - var x = DataList; - } + database.KeyDelete(RedisKey); + MemoryCacheHelper.DelCache(key); + _dataList = null; + gameInfoDic = null; + var x = DataList; } public bool ClearLocalData() diff --git a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/ImageEntityCache.cs b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/ImageEntityCache.cs index 68b6050..774b840 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/ImageEntityCache.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/ImageEntityCache.cs @@ -199,12 +199,86 @@ public class ImageEntityCache : ICacheClearData, ICacheReloadData, ICacheClearLo database.StringSet($"{redisKey}:language:{_language}", d); } database.StringSet($"{redisKey}:language", languages); - + database.StringSet($"time:{redisKey.Replace(":", ".")}", $"刷新图片缓存时间{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}"); #endregion return; } + public void LoadRedisDatabase() + { + + var data = new ConcurrentDictionary>(); + var images = 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 = images + .Where(it => it.Language == appConfig.DefaultLanguage) + .GroupBy(it => it.ImageId) + .ToDictionary(group => group.Key, group => appConfig.AliyunConfig.ImagePrefix + group.Last().Url); + if (defaultImage == null) + { + defaultImage = new Dictionary(); + } + foreach (var image in images) + { + if (string.IsNullOrEmpty(image.Url)) + { + continue; + } + var languageCache = data.GetOrAdd(image.Language, _ => new ConcurrentDictionary()); + var url = string.Empty; + if (image.Url.StartsWith("http://") || image.Url.StartsWith("https://")) + { + url = $"{image.Url}"; + } + else + { + url = $"{appConfig.AliyunConfig.ImagePrefix}{image.Url}"; + } + languageCache[image.ImageId] = url; + // 如果默认图像字典中没有此ImageId,加入默认图像字典 + if (!defaultImage.ContainsKey(image.ImageId)) + { + defaultImage[image.ImageId] = url; + } + } + #region 保存默认图片 + var defaultLanguageCache = new ConcurrentDictionary(defaultImage); + foreach (var kv in defaultLanguageCache) + { + database.StringSet($"{redisKey}:default:{kv.Key}", kv.Value); + } + //data.TryAdd("default", defaultLanguageCache); + #endregion + #region 保存其他图片 + foreach (var _language in languages) + { + var mKey = $"{appConfig.Identifier}:App:Image:{_language}"; + if (!data.TryGetValue(_language, out var d)) + { + d = defaultLanguageCache; + } + foreach (var kv in d) + { + database.StringSet($"{redisKey}:{_language}:{kv.Key}", kv.Value); + } + database.StringSet($"{redisKey}:language:{_language}", d); + } + database.StringSet($"{redisKey}:language", languages); + database.StringSet($"time:{redisKey.Replace(":", ".")}", $"刷新图片缓存时间{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}"); + #endregion + return; + } + public bool ClearData() { diff --git a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/ProductCacheEntityCache.cs b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/ProductCacheEntityCache.cs index 85de7c5..add362e 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/ProductCacheEntityCache.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/ProductCacheEntityCache.cs @@ -23,7 +23,7 @@ namespace CloudGaming.Code.Cache.Special; /// /// /// -public class ProductCacheEntityCache(DAO dao, IDatabase database, IMapper mapper) : RedisDataEntityCache(database, 60 * 60 * 12) +public class ProductCacheEntityCache(DAO dao, IDatabase database, IMapper mapper) : RedisDataEntityCache(database, 60 * 60 * 12,"产品") { public override string key => "cache:Product:list"; diff --git a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/RedemptionCodeEntityCache.cs b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/RedemptionCodeEntityCache.cs index d8f2310..83fd406 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/RedemptionCodeEntityCache.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/RedemptionCodeEntityCache.cs @@ -22,7 +22,7 @@ namespace CloudGaming.Code.Cache.Special; /// /// /// -public class RedemptionCodeEntityCache(DAO dao, IDatabase database, IMapper mapper) : RedisDataEntityCache(database,60 * 60 * 1) +public class RedemptionCodeEntityCache(DAO dao, IDatabase database, IMapper mapper) : RedisDataEntityCache(database,60 * 60 * 1, "兑换码") { public override List GetDataList() { diff --git a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/SevenDayEntityCache.cs b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/SevenDayEntityCache.cs index 11eaea9..c6fb224 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/SevenDayEntityCache.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/SevenDayEntityCache.cs @@ -22,7 +22,7 @@ namespace CloudGaming.Code.Cache.Special; /// /// /// -public class SevenDayEntityCache(DAO dao, IDatabase database, IMapper mapper) : RedisDataEntityCache(database, 60 * 60 * 24 * 7) +public class SevenDayEntityCache(DAO dao, IDatabase database, IMapper mapper) : RedisDataEntityCache(database, 60 * 60 * 24 * 7, "七天签到") { public override List GetDataList() { diff --git a/src/CloudGaming/Code/CloudGaming.Code/Config/AppConfigBLL.cs b/src/CloudGaming/Code/CloudGaming.Code/Config/AppConfigBLL.cs index bf135d7..e70b21d 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Config/AppConfigBLL.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Config/AppConfigBLL.cs @@ -1,12 +1,20 @@ +using Azure; + +using CloudGaming.AppConfigModel; +using CloudGaming.Code.AppExtend; using CloudGaming.Code.Cache; using CloudGaming.DtoModel; +using StackExchange.Redis; + using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using static SKIT.FlurlHttpClient.Wechat.TenpayV3.Models.CreateNewTaxControlFapiaoApplicationRequest.Types.Fapiao.Types; + namespace CloudGaming.Code.Config { /// @@ -68,5 +76,86 @@ namespace CloudGaming.Code.Config return true; } + /// + /// 清除缓存 + /// + /// + /// + /// 清除缓存 + /// + /// + public async IAsyncEnumerable ClearAllCacheData() + { + // 开始总耗时计时 + var swAll = Stopwatch.StartNew(); + + // 删除应用配置缓存 + var delAppConfig = Stopwatch.StartNew(); + var keysDeleted = (int)await RedisCache.KeysDeleteds("App:Config:*"); + delAppConfig.Stop(); + yield return $"删除app配置:{keysDeleted}, 耗时==》{delAppConfig.ElapsedMilliseconds.ToString("0.##")}毫秒"; + + // 删除API接口缓存 + var apiCache = Stopwatch.StartNew(); + var keysDeleted1 = (int)await RedisCache.KeysDeleteds("cache:api:*"); + apiCache.Stop(); + yield return $"删除api接口缓存:{keysDeleted1}, 耗时==》{apiCache.ElapsedMilliseconds.ToString("0.##")}毫秒"; + + // 删除图片缓存 + var imageCache = Stopwatch.StartNew(); + var imageDel = (int)await RedisCache.KeysDeleteds("cache:Image:*"); + imageCache.Stop(); + yield return $"删除图片缓存:{imageDel}, 耗时==》{imageCache.ElapsedMilliseconds.ToString("0.##")}毫秒"; + + // 重新加载缓存 + var reloadCache = Stopwatch.StartNew(); + Cache.AppImageCache.LoadRedisDatabase(); + yield return $"重新加载图片缓存"; + Cache.GameEntityCache.ReloadData(); + yield return $"重新加载游戏缓存"; + Cache.AppConfigCache.ReloadData(); + yield return $"重新加载系统配置缓存"; + Cache.ProductCacheEntityCache.ReloadData(); + yield return $"重新加载产品缓存"; + Cache.RedemptionCodeEntityCache.ReloadData(); + yield return $"重新加载兑换码缓存"; + Cache.SevenDayEntityCache.ReloadData(); + yield return $"重新加载七天签到缓存"; + reloadCache.Stop(); + yield return $"全部重新加载缓存耗时==》{reloadCache.ElapsedMilliseconds.ToString("0.##")}毫秒"; + + // 清除本地缓存 + var clearLocalCache = Stopwatch.StartNew(); + ClearCacheData(); + clearLocalCache.Stop(); + yield return $"清除本地缓存, 耗时==》{clearLocalCache.ElapsedMilliseconds.ToString("0.##")}毫秒"; + + // 清除网络缓存 + var clearNetworkCache = Stopwatch.StartNew(); + var urls = AppConfig.CacheRequestUrls ?? new List(); + foreach (var url in urls) + { + string resp = string.Empty; + try + { + var client = HttpClientFactory.CreateClient(); + var data = await client.GetAsync(url); + resp = $"清除网络缓存{url}===》{data}"; + } + catch (Exception ex) + { + resp = $"清除网络缓存{url}失败===》{ex.Message}"; + } + yield return resp; + } + clearNetworkCache.Stop(); + yield return $"清除网络缓存耗时==》{clearNetworkCache.ElapsedMilliseconds.ToString("0.##")}毫秒"; + + // 停止总耗时计时 + swAll.Stop(); + yield return $"清除结束, 总耗时==》{swAll.ElapsedMilliseconds.ToString("0.##")}毫秒"; + } + + } } diff --git a/src/CloudGaming/Model/CloudGaming.AppConfigModel/AppConfig.cs b/src/CloudGaming/Model/CloudGaming.AppConfigModel/AppConfig.cs index 98bed38..3db9293 100644 --- a/src/CloudGaming/Model/CloudGaming.AppConfigModel/AppConfig.cs +++ b/src/CloudGaming/Model/CloudGaming.AppConfigModel/AppConfig.cs @@ -89,6 +89,11 @@ namespace CloudGaming.AppConfigModel /// 多语言列表请求地址 /// public string LanguageRequestUrl { get; set; } + + /// + /// 清除缓存接口 + /// + public List CacheRequestUrls { get; set; } /// /// 获取数据库连接字符串 /// diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/CacheHelper/CommonDataEntityCache.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/CacheHelper/CommonDataEntityCache.cs index 4a87e45..d4f5da4 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/CacheHelper/CommonDataEntityCache.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/CacheHelper/CommonDataEntityCache.cs @@ -117,11 +117,14 @@ public abstract class RedisDataEntityCache : CommonDataEntityCache, ICache /// protected int cacheTime; - protected RedisDataEntityCache(IDatabase database,int cacheTime = 36000) : base(new object(), cacheTime) + private string name; + + protected RedisDataEntityCache(IDatabase database, int cacheTime = 36000, string name = "") : base(new object(), cacheTime) { this.lockObj = lockObj; this.cacheTime = cacheTime; this.database = database; + this.name = name; } public int index = 0; @@ -212,6 +215,8 @@ public abstract class RedisDataEntityCache : CommonDataEntityCache, ICache } _dataList = tempDataList; database.KeyDeleteAsync($"lock:{key}").Wait(); + database.StringSet($"time:{key.Replace(":", ".")}", $"刷新{name}缓存时间{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}"); + } } diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs index 22ab78e..54339b2 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs @@ -132,6 +132,29 @@ namespace HuanMeng.DotNetCore.Redis return database.StringSet(key, value, TimeSpan.FromSeconds(time), when: When.NotExists); } + /// + /// 删除key + /// + /// + /// + /// + public static async Task KeysDeleteds(this IDatabase database, string key) + { + + string luaScript = @" +local keys = redis.call('KEYS', ARGV[1]) +if next(keys) ~= nil then + return redis.call('DEL', unpack(keys)) +else + return 0 +end +"; + + var keysDeleted = (int)await (database.ScriptEvaluateAsync(luaScript, values: new RedisValue[] { key })); + return keysDeleted; + } + + /// /// 获取一个key的对象 ///