diff --git a/src/CloudGaming/Api/CloudGaming.Api/Base/CloudGamingControllerBase.cs b/src/CloudGaming/Api/CloudGaming.Api/Base/CloudGamingControllerBase.cs
index 6e76bfc..f688d9c 100644
--- a/src/CloudGaming/Api/CloudGaming.Api/Base/CloudGamingControllerBase.cs
+++ b/src/CloudGaming/Api/CloudGaming.Api/Base/CloudGamingControllerBase.cs
@@ -20,6 +20,5 @@ namespace CloudGaming.Api.Base
/// 数据库使用
///
public IServiceProvider ServiceProvider { get; set; } = _serviceProvider;
-
}
}
diff --git a/src/CloudGaming/Api/CloudGaming.ExtApi/Base/CloudGamingControllerBase.cs b/src/CloudGaming/Api/CloudGaming.ExtApi/Base/CloudGamingControllerBase.cs
new file mode 100644
index 0000000..5c0a2ab
--- /dev/null
+++ b/src/CloudGaming/Api/CloudGaming.ExtApi/Base/CloudGamingControllerBase.cs
@@ -0,0 +1,18 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace CloudGaming.ExtApi.Base;
+
+///
+/// 基础父类
+///
+///
+[ApiController]
+[Route("api/[controller]/[action]")]
+
+public class CloudGamingControllerBase(IServiceProvider _serviceProvider) : ControllerBase
+{
+ ///
+ /// 数据库使用
+ ///
+ public IServiceProvider ServiceProvider { get; set; } = _serviceProvider;
+}
diff --git a/src/CloudGaming/Api/CloudGaming.ExtApi/CloudGaming.ExtApi.csproj b/src/CloudGaming/Api/CloudGaming.ExtApi/CloudGaming.ExtApi.csproj
index c2219de..81b2116 100644
--- a/src/CloudGaming/Api/CloudGaming.ExtApi/CloudGaming.ExtApi.csproj
+++ b/src/CloudGaming/Api/CloudGaming.ExtApi/CloudGaming.ExtApi.csproj
@@ -6,6 +6,7 @@
enable
Linux
..\..
+ True
diff --git a/src/CloudGaming/Api/CloudGaming.ExtApi/Controllers/MonitorController.cs b/src/CloudGaming/Api/CloudGaming.ExtApi/Controllers/MonitorController.cs
index 790d167..6df0653 100644
--- a/src/CloudGaming/Api/CloudGaming.ExtApi/Controllers/MonitorController.cs
+++ b/src/CloudGaming/Api/CloudGaming.ExtApi/Controllers/MonitorController.cs
@@ -1,11 +1,29 @@
+using CloudGaming.AppConfigModel;
+using CloudGaming.Code.Monitor;
+using CloudGaming.DtoModel.Other;
+using CloudGaming.ExtApi.Base;
+
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-namespace CloudGaming.ExtApi.Controllers
+namespace CloudGaming.ExtApi.Controllers;
+
+
+///
+/// 监控数据
+///
+public class MonitorController : CloudGamingControllerBase
{
- [Route("api/[controller]")]
- [ApiController]
- public class MonitorController : ControllerBase
+ public MonitorController(IServiceProvider _serviceProvider) : base(_serviceProvider)
{
}
+ ///
+ /// 获取监控数据
+ ///
+ ///
+ [HttpGet]
+ public AppBasicStatistics GetAppMonitorInfo()
+ {
+ return new MonitorBLL(ServiceProvider).GetAppMonitorInfo();
+ }
}
diff --git a/src/CloudGaming/Code/CloudGaming.Code/Account/AccountExtend.cs b/src/CloudGaming/Code/CloudGaming.Code/Account/AccountExtend.cs
index 17243ab..390d879 100644
--- a/src/CloudGaming/Code/CloudGaming.Code/Account/AccountExtend.cs
+++ b/src/CloudGaming/Code/CloudGaming.Code/Account/AccountExtend.cs
@@ -82,6 +82,11 @@ namespace CloudGaming.Code.Account
return 0;
}
+ ///
+ ///
+ ///
+ public static string UserInfoRedisKeyPrefix { get => "user:userInfo"; }
+
///
/// 用户缓存的key
///
@@ -89,7 +94,7 @@ namespace CloudGaming.Code.Account
///
public static string GetUserInfoRedisKey(int userId)
{
- return $"user:userInfo:{userId}";
+ return $"{UserInfoRedisKeyPrefix}:{userId}";
}
///
diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppJobBase.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppJobBase.cs
new file mode 100644
index 0000000..1b09940
--- /dev/null
+++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppJobBase.cs
@@ -0,0 +1,76 @@
+using CloudGaming.Code.Monitor;
+using CloudGaming.DtoModel.Other;
+
+using Quartz;
+
+using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CloudGaming.Code.AppExtend
+{
+
+ ///
+ /// 定时使用
+ ///
+ public abstract class AppJobBase : IJob
+ {
+ ///
+ ///
+ ///
+ protected readonly IServiceScopeFactory scopeFactory;
+ public AppJobBase(IServiceScopeFactory scopeFactory)
+ {
+ this.scopeFactory = scopeFactory;
+ }
+
+ public Task Execute(IJobExecutionContext context)
+ {
+ if (AppConfigurationExtend.AppConfigs != null && AppConfigurationExtend.AppConfigs.Count > 0)
+ {
+ var appConfigs = AppConfigurationExtend.AppConfigs.Where(it => it.Key != "default")
+ .Select(it => it.Value)
+ .ToList();
+
+ // 为每个项目启动一个独立的任务
+ foreach (var appConfig in appConfigs)
+ {
+
+
+ _ = Task.Run(() => AppConfigProcessLoop(appConfig));
+ }
+ }
+ return Task.CompletedTask;
+ }
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task AppConfigProcessLoop(AppConfig appConfig)
+ {
+ AppMonitorInfo appMonitorInfo = MonitorExtend.AppMonitorConfigs.GetOrAdd(appConfig.Identifier, new AppMonitorInfo());
+ using var scope = scopeFactory.CreateScope();
+ var scopedProvider = scope.ServiceProvider;
+ var app = scopedProvider.GetRequiredService();
+ appConfig.ToAppConfig(app);
+ CloudGamingBase cloudGamingBase = new CloudGamingBase(scopedProvider);
+ await AppConfigProcessLoop(appConfig, appMonitorInfo, scopedProvider, cloudGamingBase);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public abstract Task AppConfigProcessLoop(AppConfig appConfig, AppMonitorInfo appMonitorInfo, IServiceProvider serviceProvider, CloudGamingBase cloudGamingBase);
+
+ }
+}
diff --git a/src/CloudGaming/Code/CloudGaming.Code/Monitor/MonitorBLL.cs b/src/CloudGaming/Code/CloudGaming.Code/Monitor/MonitorBLL.cs
new file mode 100644
index 0000000..fc6cc6f
--- /dev/null
+++ b/src/CloudGaming/Code/CloudGaming.Code/Monitor/MonitorBLL.cs
@@ -0,0 +1,28 @@
+using CloudGaming.DtoModel.Other;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CloudGaming.Code.Monitor;
+
+///
+/// 监控类
+///
+public class MonitorBLL : CloudGamingBase
+{
+ public MonitorBLL(IServiceProvider serviceProvider) : base(serviceProvider)
+ {
+ }
+
+ ///
+ /// 获取监控数据
+ ///
+ ///
+ public AppBasicStatistics GetAppMonitorInfo()
+ {
+ return AppConfig.GetAppMonitorInfo().ToAppBasicStatistics();
+ }
+}
diff --git a/src/CloudGaming/Code/CloudGaming.Code/Monitor/MonitorExtend.cs b/src/CloudGaming/Code/CloudGaming.Code/Monitor/MonitorExtend.cs
index c33db25..9307d83 100644
--- a/src/CloudGaming/Code/CloudGaming.Code/Monitor/MonitorExtend.cs
+++ b/src/CloudGaming/Code/CloudGaming.Code/Monitor/MonitorExtend.cs
@@ -13,6 +13,8 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
+using static SKIT.FlurlHttpClient.Wechat.TenpayV3.ExtendedSDK.Global.Models.QueryPartnerRefundsResponse.Types;
+using HuanMeng.DotNetCore.QuartzExtend;
namespace CloudGaming.Code.Monitor;
@@ -27,27 +29,28 @@ public static class MonitorExtend
public static ConcurrentDictionary AppMonitorConfigs { get; set; } = new ConcurrentDictionary();
+ ///
+ /// 添加监控服务
+ ///
+ ///
+ ///
public static WebApplicationBuilder AddMonitorConfig(this WebApplicationBuilder builder)
{
- // 添加 Quartz 配置
- builder.Services.AddQuartz(q =>
- {
- // 注册作业和触发器
- var jobKey = new JobKey("MonitorProcessor");
- q.AddJob(opts => opts.WithIdentity(jobKey));
-
- q.AddTrigger(opts => opts
- .ForJob(jobKey)
- .WithIdentity("monitorTrigger")
- .StartNow()
- .WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever()));
- });
-
- // 添加 Quartz 托管服务
- builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
-
+ builder.Services.AddQuartzWithAttributes(typeof(MonitorExtend).Assembly);
return builder;
}
+
+ ///
+ /// 获取app监控数据
+ ///
+ ///
+ ///
+ public static AppMonitorInfo GetAppMonitorInfo(this AppConfig appConfig)
+ {
+ AppMonitorInfo appMonitorInfo = MonitorExtend.AppMonitorConfigs.GetOrAdd(appConfig.Identifier, new AppMonitorInfo());
+ return appMonitorInfo;
+ }
+
///
///
///
@@ -57,12 +60,12 @@ public static class MonitorExtend
{
return new AppBasicStatistics()
{
- CurrentOnlineUsers= appMonitorInfo.CurrentOnlineUsers,
+ CurrentOnlineUsers = appMonitorInfo.CurrentOnlineUsers,
CurrentPlayingUsers = appMonitorInfo.CurrentPlayingUsers,
CurrentQueuedUsers = appMonitorInfo.CurrentQueuedUsers,
TodayIntendedOrders = appMonitorInfo.TodayIntendedOrders,
TodayLoggedInUsers = appMonitorInfo.TodayLoggedInUsers,
- TodayPaidOrders= appMonitorInfo.TodayPaidOrders,
+ TodayPaidOrders = appMonitorInfo.TodayPaidOrders,
TodayRechargeAmount = appMonitorInfo.TodayRechargeAmount,
TodayRegisteredUsers = appMonitorInfo.TodayRegisteredUsers
};
diff --git a/src/CloudGaming/Code/CloudGaming.Code/Monitor/MonitorProcessor.cs b/src/CloudGaming/Code/CloudGaming.Code/Monitor/MonitorProcessor.cs
index af5c609..3a242cd 100644
--- a/src/CloudGaming/Code/CloudGaming.Code/Monitor/MonitorProcessor.cs
+++ b/src/CloudGaming/Code/CloudGaming.Code/Monitor/MonitorProcessor.cs
@@ -1,6 +1,9 @@
+using CloudGaming.Code.Account;
using CloudGaming.DtoModel.Other;
using CloudGaming.DtoModel.PlayGame;
+using HuanMeng.DotNetCore.QuartzExtend;
+
using Newtonsoft.Json;
using Quartz;
@@ -15,53 +18,22 @@ namespace CloudGaming.Code.Monitor;
///
-/// 定时监控
+/// 基础信息定时监控
///
-public class MonitorProcessor : IJob
+[QuartzTrigger("MonitorProcessor", "3/10 * * * * ?")]
+public class MonitorProcessor : AppJobBase
{
- private readonly IServiceProvider serviceProvider;
- private readonly IServiceScopeFactory scopeFactory;
- public MonitorProcessor(IServiceScopeFactory scopeFactory)
+ public MonitorProcessor(IServiceScopeFactory scopeFactory) : base(scopeFactory)
{
- this.scopeFactory = scopeFactory;
}
- public Task Execute(IJobExecutionContext context)
- {
- if (AppConfigurationExtend.AppConfigs != null && AppConfigurationExtend.AppConfigs.Count > 0)
- {
- var appConfigs = AppConfigurationExtend.AppConfigs.Where(it => it.Key != "default")
- .Select(it => it.Value)
- .ToList();
-
- // 为每个项目启动一个独立的任务
- foreach (var appConfig in appConfigs)
- {
-
- AppMonitorInfo appMonitorInfo = MonitorExtend.AppMonitorConfigs.GetOrAdd(appConfig.Identifier, new AppMonitorInfo());
- _ = Task.Run(() => ProcessAppConfigLoop(appConfig, appMonitorInfo));
- }
- }
-
- //throw new NotImplementedException();
- return Task.CompletedTask;
- }
///
/// 单个项目的处理循环
///
- public async Task ProcessAppConfigLoop(AppConfig appConfig, AppMonitorInfo appMonitorInfo)
+ public override async Task AppConfigProcessLoop(AppConfig appConfig, AppMonitorInfo appMonitorInfo, IServiceProvider serviceProvider, CloudGamingBase cloudGamingBase)
{
- var sw = Stopwatch.StartNew();
- using var scope = scopeFactory.CreateScope();
- var scopedProvider = scope.ServiceProvider;
int day = int.Parse(DateTime.Now.ToString("yyyyMMdd"));
var nowDay = DateOnly.FromDateTime(DateTime.Now);
-
-
- var app = scopedProvider.GetRequiredService();
- appConfig.ToAppConfig(app);
- CloudGamingBase cloudGamingBase = new CloudGamingBase(scopedProvider);
-
//今日登录人数
var todayLoggedInUsers = await cloudGamingBase.Dao.DaoExt.Context.T_User_LoginDay_Log.Where(it => it.CreateTimeDay == day).CountAsync();
//今日注册人数
@@ -71,39 +43,24 @@ public class MonitorProcessor : IJob
//今日支付订单
var todayPaidOrders = await cloudGamingBase.Dao.DaoUser.Context.T_User_Order.Where(it => it.PaymentDay == nowDay).CountAsync();
//今日支付金额
- var todayRechargeAmount =await cloudGamingBase.Dao.DaoUser.Context.T_User_Order.Where(it => it.PaymentDay == nowDay).SumAsync(it => (decimal?)it.TotalPrice);
+ var todayRechargeAmount = await cloudGamingBase.Dao.DaoUser.Context.T_User_Order.Where(it => it.PaymentDay == nowDay).SumAsync(it => (decimal?)it.TotalPrice);
// 查询游戏数据
var playGameStatusStatistics = appMonitorInfo.PlayGameStatusStatistics();
//排队数据
- int currentQueuedUsers = 0;
- if (playGameStatusStatistics.TryGetValue(PlayGameStatus.排队中, out var _currentQueuedUsers))
- {
- currentQueuedUsers += _currentQueuedUsers;
- }
+ int currentQueuedUsers = playGameStatusStatistics.GetValueOrDefault(PlayGameStatus.排队中);
//开始游戏
- int currentPlayingUsers = 0;
- if (playGameStatusStatistics.TryGetValue(PlayGameStatus.开始游戏, out var _currentPlayingUsers))
- {
- currentPlayingUsers += _currentPlayingUsers;
- }
- if (playGameStatusStatistics.TryGetValue(PlayGameStatus.游戏中, out var _currentPlayingUsers1))
- {
- currentPlayingUsers += _currentPlayingUsers1;
- }
- //appMonitorInfo.CurrentQueuedUsers=
- sw.Stop();
+ int currentPlayingUsers = playGameStatusStatistics.GetValueOrDefault(PlayGameStatus.开始游戏) + playGameStatusStatistics.GetValueOrDefault(PlayGameStatus.游戏中);
+ //查询在线人数
+ //ScanKeysCount user:userInfo:*
+ var currentOnlineUsers = await cloudGamingBase.RedisServerCache.ScanKeysCountAsync($"{AccountExtend.UserInfoRedisKeyPrefix}:*");
+
appMonitorInfo.TodayLoggedInUsers = todayLoggedInUsers;
appMonitorInfo.TodayRegisteredUsers = todayRegisteredUsers;
appMonitorInfo.TodayIntendedOrders = todayIntendedOrders;
appMonitorInfo.TodayPaidOrders = todayPaidOrders;
appMonitorInfo.TodayRechargeAmount = todayRechargeAmount ?? 0;
- appMonitorInfo.CurrentOnlineUsers = 0;
+ appMonitorInfo.CurrentOnlineUsers = currentOnlineUsers;
appMonitorInfo.CurrentQueuedUsers = currentQueuedUsers;
appMonitorInfo.CurrentPlayingUsers = currentPlayingUsers;
-
- var t = appMonitorInfo.ToAppBasicStatistics();
- Console.WriteLine(JsonConvert.SerializeObject(t));
- Console.WriteLine($"消耗{sw.Elapsed.TotalMilliseconds.ToString("0.###")}");
-
}
}
\ No newline at end of file
diff --git a/src/CloudGaming/Code/CloudGaming.Code/Monitor/UserMonitorProcessor.cs b/src/CloudGaming/Code/CloudGaming.Code/Monitor/UserMonitorProcessor.cs
new file mode 100644
index 0000000..291511b
--- /dev/null
+++ b/src/CloudGaming/Code/CloudGaming.Code/Monitor/UserMonitorProcessor.cs
@@ -0,0 +1,119 @@
+
+using CloudGaming.DtoModel.Other;
+
+using HuanMeng.DotNetCore.QuartzExtend;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CloudGaming.Code.Monitor;
+
+///
+/// 用户定时类
+///
+[QuartzTrigger("UserMonitorProcessor", "* 15 4 * * ?")]
+public class UserMonitorProcessor : AppJobBase
+{
+ public UserMonitorProcessor(IServiceScopeFactory scopeFactory) : base(scopeFactory)
+ {
+ }
+
+ public override async Task AppConfigProcessLoop(AppConfig appConfig, AppMonitorInfo appMonitorInfo, IServiceProvider serviceProvider, CloudGamingBase cloudGamingBase)
+ {
+ var dao = cloudGamingBase.Dao;
+ var now = DateTime.Now.AddDays(-1);
+ int day = int.Parse(now.ToString("yyyyMMdd"));
+ var nowDay = DateOnly.FromDateTime(now);
+
+ //今日登录人数
+ var todayLoggedInUsers = await cloudGamingBase.Dao.DaoExt.Context.T_User_LoginDay_Log.Where(it => it.CreateTimeDay == day)
+ .GroupBy(it => it.Channel ?? "")
+ .ToDictionaryAsync(it => it.Key, it => it.Count());
+ //今日注册人数
+ var todayRegisteredUsers = await cloudGamingBase.Dao.DaoExt.Context.T_User_LoginDay_Log.Where(it => it.CreateTimeDay == day && it.IsNew)
+ .GroupBy(it => it.Channel ?? "")
+ .ToDictionaryAsync(it => it.Key, it => it.Count());
+ //今日活跃人数
+ var todayActionUsers = await cloudGamingBase.Dao.DaoExt.Context.T_User_LoginDay_Log.Where(it => it.CreateTimeDay == day && it.LogInDay > 1)
+ .GroupBy(it => it.Channel ?? "")
+ .ToDictionaryAsync(it => it.Key, it => it.Count());
+ //数据库中的数据
+ var userStatistics = await dao.DaoExt.Context.T_Statistics_User.Where(it => it.LoginDay == day).ToDictionaryAsync(it => it.Channel ?? "");//.ToListAsync();
+ //登录
+ if (todayLoggedInUsers != null)
+ {
+ foreach (var item in todayLoggedInUsers)
+ {
+ if (!userStatistics.TryGetValue(item.Key, out var statisticsUser))
+ {
+ statisticsUser = new T_Statistics_User()
+ {
+ ActiveCount = 0,
+ Channel = item.Key,
+ CreatedAt = DateTime.Now,
+ LoginCount = 0,
+ RegistrCount = 0,
+ LoginDate = nowDay,
+ LoginDay = day,
+ UpdatedAt = DateTime.Now,
+ };
+ await dao.DaoExt.Context.T_Statistics_User.AddAsync(statisticsUser);
+ userStatistics.Add(item.Key, statisticsUser);
+ }
+ statisticsUser.LoginDay = item.Value;
+ }
+ }
+ //注册
+ if (todayRegisteredUsers != null)
+ {
+ foreach (var item in todayRegisteredUsers)
+ {
+ if (!userStatistics.TryGetValue(item.Key, out var statisticsUser))
+ {
+ statisticsUser = new T_Statistics_User()
+ {
+ ActiveCount = 0,
+ Channel = item.Key,
+ CreatedAt = DateTime.Now,
+ LoginCount = 0,
+ RegistrCount = 0,
+ LoginDate = nowDay,
+ LoginDay = day,
+ UpdatedAt = DateTime.Now,
+ };
+ await dao.DaoExt.Context.T_Statistics_User.AddAsync(statisticsUser);
+ userStatistics.Add(item.Key, statisticsUser);
+ }
+ statisticsUser.RegistrCount = item.Value;
+ }
+ }
+ //活跃
+ if (todayActionUsers != null)
+ {
+ foreach (var item in todayActionUsers)
+ {
+ if (!userStatistics.TryGetValue(item.Key, out var statisticsUser))
+ {
+ statisticsUser = new T_Statistics_User()
+ {
+ ActiveCount = 0,
+ Channel = item.Key,
+ CreatedAt = DateTime.Now,
+ LoginCount = 0,
+ RegistrCount = 0,
+ LoginDate = nowDay,
+ LoginDay = day,
+ UpdatedAt = DateTime.Now,
+ };
+ await dao.DaoExt.Context.T_Statistics_User.AddAsync(statisticsUser);
+ userStatistics.Add(item.Key, statisticsUser);
+ }
+ statisticsUser.ActiveCount = item.Value;
+ }
+ }
+ await dao.DaoExt.Context.SaveChangesAsync();
+ }
+}
diff --git a/src/CloudGaming/Code/CloudGaming.Code/Other/MonitorBLL.cs b/src/CloudGaming/Code/CloudGaming.Code/Other/MonitorBLL.cs
deleted file mode 100644
index 1582713..0000000
--- a/src/CloudGaming/Code/CloudGaming.Code/Other/MonitorBLL.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace CloudGaming.Code.Other
-{
- ///
- /// 监控类
- ///
- public class MonitorBLL : CloudGamingBase
- {
- public MonitorBLL(IServiceProvider serviceProvider) : base(serviceProvider)
- {
- }
- }
-}
diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs
index c9da070..40aedb5 100644
--- a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs
+++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs
@@ -59,6 +59,21 @@ public partial class CloudGamingCBTContext : DbContext
///
public virtual DbSet T_Sms_Log { get; set; }
+ ///
+ /// 游戏统计数据
+ ///
+ public virtual DbSet T_Statistics_Game { get; set; }
+
+ ///
+ /// 订单统计表
+ ///
+ public virtual DbSet T_Statistics_Order { get; set; }
+
+ ///
+ /// 用户统计表
+ ///
+ public virtual DbSet T_Statistics_User { get; set; }
+
///
/// 用户登录日志表,每天每个用户只有一条数据
///
@@ -185,6 +200,75 @@ public partial class CloudGamingCBTContext : DbContext
});
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(e => e.Id).HasName("_copy_1");
+
+ entity.ToTable(tb => tb.HasComment("游戏统计数据"));
+
+ entity.Property(e => e.Channel)
+ .HasMaxLength(10)
+ .HasComment("渠道号");
+ entity.Property(e => e.CreatedAt)
+ .HasComment("创建时间")
+ .HasColumnType("datetime");
+ entity.Property(e => e.LoginDate).HasComment("日期");
+ entity.Property(e => e.LoginDay).HasComment("天");
+ entity.Property(e => e.PlayGameCount).HasComment("用户玩游戏人数");
+ entity.Property(e => e.PlayGameTimeCount).HasComment("用户玩游戏时长");
+ entity.Property(e => e.StartGameCount).HasComment("启动游戏次数");
+ entity.Property(e => e.UpdatedAt)
+ .HasComment("修改时间")
+ .HasColumnType("datetime");
+
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(e => e.Id).HasName("_copy_3");
+
+ entity.ToTable(tb => tb.HasComment("订单统计表"));
+
+ entity.Property(e => e.Channel)
+ .HasMaxLength(10)
+ .HasComment("渠道号");
+ entity.Property(e => e.CreatedAt)
+ .HasComment("创建时间")
+ .HasColumnType("datetime");
+ entity.Property(e => e.IntendedOrderCount).HasComment("意向订单次数");
+ entity.Property(e => e.LoginDate).HasComment("日期");
+ entity.Property(e => e.LoginDay).HasComment("天");
+ entity.Property(e => e.PaidOrders).HasComment("支付订单次数");
+ entity.Property(e => e.RechargeAmount).HasComment("支付订单金额");
+ entity.Property(e => e.UpdatedAt)
+ .HasComment("修改时间")
+ .HasColumnType("datetime");
+
+ });
+
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(e => e.Id).HasName("PK__T_Statis__3214EC07FE2CF78F");
+
+ entity.ToTable(tb => tb.HasComment("用户统计表"));
+
+ entity.Property(e => e.ActiveCount).HasComment("用户活跃数");
+ entity.Property(e => e.Channel)
+ .HasMaxLength(10)
+ .HasComment("渠道号");
+ entity.Property(e => e.CreatedAt)
+ .HasComment("创建时间")
+ .HasColumnType("datetime");
+ entity.Property(e => e.LoginCount).HasComment("用户登录数");
+ entity.Property(e => e.LoginDate).HasComment("日期");
+ entity.Property(e => e.LoginDay).HasComment("天");
+ entity.Property(e => e.RegistrCount).HasComment("用户注册数");
+ entity.Property(e => e.UpdatedAt)
+ .HasComment("修改时间")
+ .HasColumnType("datetime");
+
+ });
+
modelBuilder.Entity(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__T_User_L__3214EC0786EBC26A");
diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_Statistics_Game.cs b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_Statistics_Game.cs
new file mode 100644
index 0000000..2bec20e
--- /dev/null
+++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_Statistics_Game.cs
@@ -0,0 +1,53 @@
+using System;
+
+namespace CloudGaming.GameModel.Db.Db_Ext;
+
+///
+/// 游戏统计数据
+///
+public partial class T_Statistics_Game
+{
+ public T_Statistics_Game() { }
+
+ public virtual int Id { get; set; }
+
+ ///
+ /// 日期
+ ///
+ public virtual DateOnly LoginDate { get; set; }
+
+ ///
+ /// 天
+ ///
+ public virtual int LoginDay { get; set; }
+
+ ///
+ /// 用户玩游戏时长
+ ///
+ public virtual int PlayGameTimeCount { get; set; }
+
+ ///
+ /// 用户玩游戏人数
+ ///
+ public virtual int PlayGameCount { get; set; }
+
+ ///
+ /// 启动游戏次数
+ ///
+ public virtual int StartGameCount { get; set; }
+
+ ///
+ /// 渠道号
+ ///
+ public virtual string Channel { get; set; } = null!;
+
+ ///
+ /// 创建时间
+ ///
+ public virtual DateTime CreatedAt { get; set; }
+
+ ///
+ /// 修改时间
+ ///
+ public virtual DateTime UpdatedAt { get; set; }
+}
diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_Statistics_Order.cs b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_Statistics_Order.cs
new file mode 100644
index 0000000..e3adf0a
--- /dev/null
+++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_Statistics_Order.cs
@@ -0,0 +1,53 @@
+using System;
+
+namespace CloudGaming.GameModel.Db.Db_Ext;
+
+///
+/// 订单统计表
+///
+public partial class T_Statistics_Order
+{
+ public T_Statistics_Order() { }
+
+ public virtual int Id { get; set; }
+
+ ///
+ /// 日期
+ ///
+ public virtual DateOnly LoginDate { get; set; }
+
+ ///
+ /// 天
+ ///
+ public virtual int LoginDay { get; set; }
+
+ ///
+ /// 意向订单次数
+ ///
+ public virtual int IntendedOrderCount { get; set; }
+
+ ///
+ /// 支付订单次数
+ ///
+ public virtual int PaidOrders { get; set; }
+
+ ///
+ /// 支付订单金额
+ ///
+ public virtual int RechargeAmount { get; set; }
+
+ ///
+ /// 渠道号
+ ///
+ public virtual string Channel { get; set; } = null!;
+
+ ///
+ /// 创建时间
+ ///
+ public virtual DateTime CreatedAt { get; set; }
+
+ ///
+ /// 修改时间
+ ///
+ public virtual DateTime UpdatedAt { get; set; }
+}
diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_Statistics_User.cs b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_Statistics_User.cs
new file mode 100644
index 0000000..b63adbe
--- /dev/null
+++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_Statistics_User.cs
@@ -0,0 +1,53 @@
+using System;
+
+namespace CloudGaming.GameModel.Db.Db_Ext;
+
+///
+/// 用户统计表
+///
+public partial class T_Statistics_User
+{
+ public T_Statistics_User() { }
+
+ public virtual int Id { get; set; }
+
+ ///
+ /// 日期
+ ///
+ public virtual DateOnly LoginDate { get; set; }
+
+ ///
+ /// 天
+ ///
+ public virtual int LoginDay { get; set; }
+
+ ///
+ /// 用户登录数
+ ///
+ public virtual int LoginCount { get; set; }
+
+ ///
+ /// 用户注册数
+ ///
+ public virtual int RegistrCount { get; set; }
+
+ ///
+ /// 用户活跃数
+ ///
+ public virtual int ActiveCount { get; set; }
+
+ ///
+ /// 渠道号
+ ///
+ public virtual string Channel { get; set; } = null!;
+
+ ///
+ /// 创建时间
+ ///
+ public virtual DateTime CreatedAt { get; set; }
+
+ ///
+ /// 修改时间
+ ///
+ public virtual DateTime UpdatedAt { get; set; }
+}
diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_User_LoginDay_Log.cs b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_User_LoginDay_Log.cs
index 6bc9a82..07fea84 100644
--- a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_User_LoginDay_Log.cs
+++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_User_LoginDay_Log.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
namespace CloudGaming.GameModel.Db.Db_Ext;
diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Game/CloudGamingGameContext.cs b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Game/CloudGamingGameContext.cs
index 25cbf26..d64c728 100644
--- a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Game/CloudGamingGameContext.cs
+++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Game/CloudGamingGameContext.cs
@@ -70,7 +70,7 @@ public partial class CloudGamingGameContext : DbContext
public virtual DbSet T_Game_UserShare { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- {// => optionsBuilder.UseSqlServer("Server=192.168.195.6;Database=CloudGamingGame;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;");
+ {// => optionsBuilder.UseSqlServer("Server=192.168.195.8;Database=CloudGamingGame;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/Db/efcore-gen.md b/src/CloudGaming/Model/CloudGaming.GameModel/Db/efcore-gen.md
index 1de538a..acea14c 100644
--- a/src/CloudGaming/Model/CloudGaming.GameModel/Db/efcore-gen.md
+++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/efcore-gen.md
@@ -5,7 +5,7 @@
内网
dotnet ef dbcontext scaffold "Server=192.168.1.17;Database=CloudGamingGame;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;" Microsoft.EntityFrameworkCore.SqlServer -o Db/Db_Game/ --use-database-names --no-pluralize --force
内网穿透
-dotnet ef dbcontext scaffold "Server=192.168.195.6;Database=CloudGamingGame;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;" Microsoft.EntityFrameworkCore.SqlServer -o Db/Db_Game/ --use-database-names --no-pluralize --force
+dotnet ef dbcontext scaffold "Server=192.168.195.8;Database=CloudGamingGame;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;" Microsoft.EntityFrameworkCore.SqlServer -o Db/Db_Game/ --use-database-names --no-pluralize --force
--Ext
dotnet ef dbcontext scaffold "Server=192.168.1.17;Database=CloudGamingCBT;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;" Microsoft.EntityFrameworkCore.SqlServer -o Db/Db_Ext/ --use-database-names --no-pluralize --force
内网穿透
diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/HuanMeng.DotNetCore.csproj b/src/CloudGaming/Utile/HuanMeng.DotNetCore/HuanMeng.DotNetCore.csproj
index e95534e..91484a0 100644
--- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/HuanMeng.DotNetCore.csproj
+++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/HuanMeng.DotNetCore.csproj
@@ -23,6 +23,9 @@
+
+
+
diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/QuartzExtend/QuartzExtensions.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/QuartzExtend/QuartzExtensions.cs
new file mode 100644
index 0000000..cdf318f
--- /dev/null
+++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/QuartzExtend/QuartzExtensions.cs
@@ -0,0 +1,68 @@
+using Microsoft.Extensions.DependencyInjection;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using Quartz;
+using Microsoft.IdentityModel.Tokens;
+namespace HuanMeng.DotNetCore.QuartzExtend;
+
+///
+/// 提供扩展方法,用于基于 QuartzTriggerAttribute 注册 Quartz 作业和触发器。
+///
+public static class QuartzExtensions
+{
+ ///
+ /// 扫描指定程序集中的所有带有 QuartzTriggerAttribute 的作业类,并将它们注册到 Quartz 调度器中。
+ ///
+ /// 依赖注入服务集合。
+ /// 要扫描的程序集。
+ /// 更新后的服务集合。
+ public static IServiceCollection AddQuartzWithAttributes(this IServiceCollection services, Assembly assembly)
+ {
+ services.AddQuartz(q =>
+ {
+ // 查找实现了 IJob 接口并带有 QuartzTriggerAttribute 特性的所有类
+ var jobTypes = assembly.GetTypes()
+ .Where(t => t.IsClass && !t.IsAbstract && typeof(IJob).IsAssignableFrom(t));
+
+ foreach (var jobType in jobTypes)
+ {
+ // 获取 QuartzTriggerAttribute 特性实例
+ var attr = jobType.GetCustomAttribute();
+ if (attr != null)
+ {
+ // 注册作业
+ var jobKey = new JobKey(jobType.Name); // 使用类名作为作业的唯一标识
+ q.AddJob(jobType, jobKey, opts => opts.WithIdentity(jobKey)); // 将作业注册到调度器
+
+ // 根据特性中定义的调度配置注册触发器
+ if (!string.IsNullOrEmpty(attr.CronExpression))
+ {
+ // 如果定义了 Cron 表达式,使用 Cron 调度
+ q.AddTrigger(opts => opts
+ .ForJob(jobKey)
+ .WithIdentity(attr.TriggerName)
+ .WithCronSchedule(attr.CronExpression));
+ }
+ else if (attr.IntervalInSeconds.HasValue && attr.IntervalInSeconds > 0)
+ {
+ // 如果定义了简单时间间隔,使用简单调度
+ q.AddTrigger(opts => opts
+ .ForJob(jobKey)
+ .WithIdentity(attr.TriggerName)
+ .StartNow()
+ .WithSimpleSchedule(x => x.WithIntervalInSeconds(attr.IntervalInSeconds.Value).RepeatForever()));
+ }
+ }
+ }
+ });
+
+ // 注册 Quartz 的托管服务
+ services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
+ return services;
+ }
+}
diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/QuartzExtend/QuartzTriggerAttribute.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/QuartzExtend/QuartzTriggerAttribute.cs
new file mode 100644
index 0000000..3516b9c
--- /dev/null
+++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/QuartzExtend/QuartzTriggerAttribute.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace HuanMeng.DotNetCore.QuartzExtend;
+
+///
+/// 自定义特性,用于标识 Quartz 作业的触发器配置。
+///
+[AttributeUsage(AttributeTargets.Class, Inherited = false)]
+public class QuartzTriggerAttribute : Attribute
+{
+ ///
+ /// 获取触发器的名称。
+ ///
+ public string TriggerName { get; }
+
+ ///
+ /// 获取触发器的时间间隔(秒)。
+ ///
+ public int? IntervalInSeconds { get; }
+
+ ///
+ /// 获取 Cron 表达式。
+ ///
+ public string CronExpression { get; }
+
+ ///
+ /// 使用 Cron 表达式初始化特性。
+ ///
+ /// 触发器名称。
+ /// Cron 表达式。
+ public QuartzTriggerAttribute(string triggerName, string cronExpression = "0/1 * * * * ?")
+ {
+ TriggerName = triggerName;
+ IntervalInSeconds = null;
+ CronExpression = cronExpression;
+ }
+
+ ///
+ /// 使用时间间隔初始化特性。
+ ///
+ /// 触发器名称。
+ /// 时间间隔(秒)。
+ public QuartzTriggerAttribute(string triggerName, int intervalInSeconds)
+ {
+ TriggerName = triggerName;
+ IntervalInSeconds = intervalInSeconds;
+ CronExpression = "";
+ }
+}
diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs
index 13ca5de..4ef6490 100644
--- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs
+++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs
@@ -50,7 +50,7 @@ namespace HuanMeng.DotNetCore.Redis
return database;
}
-
+
///
///
@@ -195,6 +195,7 @@ namespace HuanMeng.DotNetCore.Redis
///
public static async Task StringGetAsync(this IDatabase database, string key)
{
+
var value = await database.StringGetAsync(key);
// 检查值是否为空
if (!value.HasValue)
@@ -232,21 +233,80 @@ namespace HuanMeng.DotNetCore.Redis
///
/// 异步模糊查询key
///
- ///
- ///
- ///
- ///
+ /// Redis 服务器实例
+ /// 匹配的模式(支持通配符)
+ /// 每次扫描的页大小
+ /// 数据库编号,默认值为 -1 表示全部数据库
/// 匹配的键的字符串列表
public static async Task> ScanKeysAsync(this IServer server, string pattern, int pageSize = 100, int database = -1)
{
var matchingKeys = new List();
- await foreach (var key in server.KeysAsync(database: database, pattern: pattern, pageSize: pageSize))
+ try
{
- matchingKeys.Add(key.ToString());
+ await foreach (var key in server.KeysAsync(database: database, pattern: pattern, pageSize: pageSize))
+ {
+ matchingKeys.Add(key.ToString());
+ }
+ }
+ catch (Exception ex)
+ {
+ // 可以记录日志或者处理异常
+ Console.WriteLine($"Error while scanning keys: {ex.Message}");
}
return matchingKeys;
}
+
+
+ ///
+ /// 模糊查询所有匹配的键的数量
+ ///
+ /// Redis 服务器实例
+ /// 匹配的模式(支持通配符)
+ /// 匹配的键的数量
+ public static int ScanKeysCount(this IServer server, string pattern,int database = -1)
+ {
+ int count = 0;
+ long cursor = 0; // 游标,用于记录扫描进度
+
+ do
+ {
+ // 使用 Keys 方法进行分页查询
+ var keys = server.Keys(database: database, cursor: cursor, pattern: pattern, pageSize: 1000).ToArray();
+
+ // 累计匹配到的键的数量
+ count += keys.Length;
+
+ // 如果还有更多键,则获取新的游标;否则,循环终止
+ cursor = keys.Length == 1000 ? 1 : 0;
+
+ } while (cursor != 0);
+
+ return count;
+ }
+
+ ///
+ /// 异步模糊查询所有匹配的键的数量
+ ///
+ /// Redis 服务器实例
+ /// 匹配的模式(支持通配符)
+ /// 每次扫描的页大小
+ /// 数据库编号,默认为 -1 表示当前数据库
+ /// 匹配的键的数量
+ public static async Task ScanKeysCountAsync(this IServer server, string pattern, int pageSize = 1000, int database = -1)
+ {
+ int count = 0;
+
+ // 异步遍历 KeysAsync
+ await foreach (var key in server.KeysAsync(database: database, pattern: pattern, pageSize: pageSize))
+ {
+ count++;
+ }
+
+ return count;
+ }
+
+
}
}