添加定时器统计

This commit is contained in:
zpc 2024-12-02 12:34:32 +08:00
parent c6db4d9294
commit cfd1f964bf
22 changed files with 745 additions and 112 deletions

View File

@ -20,6 +20,5 @@ namespace CloudGaming.Api.Base
/// 数据库使用
/// </summary>
public IServiceProvider ServiceProvider { get; set; } = _serviceProvider;
}
}

View File

@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Mvc;
namespace CloudGaming.ExtApi.Base;
/// <summary>
/// 基础父类
/// </summary>
/// <param name="_serviceProvider"></param>
[ApiController]
[Route("api/[controller]/[action]")]
public class CloudGamingControllerBase(IServiceProvider _serviceProvider) : ControllerBase
{
/// <summary>
/// 数据库使用
/// </summary>
public IServiceProvider ServiceProvider { get; set; } = _serviceProvider;
}

View File

@ -6,6 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>

View File

@ -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;
/// <summary>
/// 监控数据
/// </summary>
public class MonitorController : CloudGamingControllerBase
{
[Route("api/[controller]")]
[ApiController]
public class MonitorController : ControllerBase
public MonitorController(IServiceProvider _serviceProvider) : base(_serviceProvider)
{
}
/// <summary>
/// 获取监控数据
/// </summary>
/// <returns></returns>
[HttpGet]
public AppBasicStatistics GetAppMonitorInfo()
{
return new MonitorBLL(ServiceProvider).GetAppMonitorInfo();
}
}

View File

@ -82,6 +82,11 @@ namespace CloudGaming.Code.Account
return 0;
}
/// <summary>
///
/// </summary>
public static string UserInfoRedisKeyPrefix { get => "user:userInfo"; }
/// <summary>
/// 用户缓存的key
/// </summary>
@ -89,7 +94,7 @@ namespace CloudGaming.Code.Account
/// <returns></returns>
public static string GetUserInfoRedisKey(int userId)
{
return $"user:userInfo:{userId}";
return $"{UserInfoRedisKeyPrefix}:{userId}";
}
/// <summary>

View File

@ -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
{
/// <summary>
/// 定时使用
/// </summary>
public abstract class AppJobBase : IJob
{
/// <summary>
///
/// </summary>
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;
}
/// <summary>
///
/// </summary>
/// <param name="appConfig"></param>
/// <returns></returns>
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>();
appConfig.ToAppConfig(app);
CloudGamingBase cloudGamingBase = new CloudGamingBase(scopedProvider);
await AppConfigProcessLoop(appConfig, appMonitorInfo, scopedProvider, cloudGamingBase);
}
/// <summary>
///
/// </summary>
/// <param name="appConfig"></param>
/// <param name="appMonitorInfo"></param>
/// <param name="serviceProvider"></param>
/// <param name="cloudGamingBase"></param>
/// <returns></returns>
public abstract Task AppConfigProcessLoop(AppConfig appConfig, AppMonitorInfo appMonitorInfo, IServiceProvider serviceProvider, CloudGamingBase cloudGamingBase);
}
}

View File

@ -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;
/// <summary>
/// 监控类
/// </summary>
public class MonitorBLL : CloudGamingBase
{
public MonitorBLL(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
/// <summary>
/// 获取监控数据
/// </summary>
/// <returns></returns>
public AppBasicStatistics GetAppMonitorInfo()
{
return AppConfig.GetAppMonitorInfo().ToAppBasicStatistics();
}
}

View File

@ -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<string, AppMonitorInfo> AppMonitorConfigs { get; set; } = new ConcurrentDictionary<string, AppMonitorInfo>();
/// <summary>
/// 添加监控服务
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static WebApplicationBuilder AddMonitorConfig(this WebApplicationBuilder builder)
{
// 添加 Quartz 配置
builder.Services.AddQuartz(q =>
{
// 注册作业和触发器
var jobKey = new JobKey("MonitorProcessor");
q.AddJob<MonitorProcessor>(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;
}
/// <summary>
/// 获取app监控数据
/// </summary>
/// <param name="appConfig"></param>
/// <returns></returns>
public static AppMonitorInfo GetAppMonitorInfo(this AppConfig appConfig)
{
AppMonitorInfo appMonitorInfo = MonitorExtend.AppMonitorConfigs.GetOrAdd(appConfig.Identifier, new AppMonitorInfo());
return appMonitorInfo;
}
/// <summary>
///
/// </summary>
@ -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
};

View File

@ -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;
/// <summary>
/// 定时监控
/// 基础信息定时监控
/// </summary>
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;
}
/// <summary>
/// 单个项目的处理循环
/// </summary>
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>();
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.###")}");
}
}

View File

@ -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;
/// <summary>
/// 用户定时类
/// </summary>
[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();
}
}

View File

@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CloudGaming.Code.Other
{
/// <summary>
/// 监控类
/// </summary>
public class MonitorBLL : CloudGamingBase
{
public MonitorBLL(IServiceProvider serviceProvider) : base(serviceProvider)
{
}
}
}

View File

@ -59,6 +59,21 @@ public partial class CloudGamingCBTContext : DbContext
/// </summary>
public virtual DbSet<T_Sms_Log> T_Sms_Log { get; set; }
/// <summary>
/// 游戏统计数据
/// </summary>
public virtual DbSet<T_Statistics_Game> T_Statistics_Game { get; set; }
/// <summary>
/// 订单统计表
/// </summary>
public virtual DbSet<T_Statistics_Order> T_Statistics_Order { get; set; }
/// <summary>
/// 用户统计表
/// </summary>
public virtual DbSet<T_Statistics_User> T_Statistics_User { get; set; }
/// <summary>
/// 用户登录日志表,每天每个用户只有一条数据
/// </summary>
@ -185,6 +200,75 @@ public partial class CloudGamingCBTContext : DbContext
});
modelBuilder.Entity<T_Statistics_Game>(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<T_Statistics_Order>(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<T_Statistics_User>(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<T_User_LoginDay_Log>(entity =>
{
entity.HasKey(e => e.Id).HasName("PK__T_User_L__3214EC0786EBC26A");

View File

@ -0,0 +1,53 @@
using System;
namespace CloudGaming.GameModel.Db.Db_Ext;
/// <summary>
/// 游戏统计数据
/// </summary>
public partial class T_Statistics_Game
{
public T_Statistics_Game() { }
public virtual int Id { get; set; }
/// <summary>
/// 日期
/// </summary>
public virtual DateOnly LoginDate { get; set; }
/// <summary>
/// 天
/// </summary>
public virtual int LoginDay { get; set; }
/// <summary>
/// 用户玩游戏时长
/// </summary>
public virtual int PlayGameTimeCount { get; set; }
/// <summary>
/// 用户玩游戏人数
/// </summary>
public virtual int PlayGameCount { get; set; }
/// <summary>
/// 启动游戏次数
/// </summary>
public virtual int StartGameCount { get; set; }
/// <summary>
/// 渠道号
/// </summary>
public virtual string Channel { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
public virtual DateTime CreatedAt { get; set; }
/// <summary>
/// 修改时间
/// </summary>
public virtual DateTime UpdatedAt { get; set; }
}

View File

@ -0,0 +1,53 @@
using System;
namespace CloudGaming.GameModel.Db.Db_Ext;
/// <summary>
/// 订单统计表
/// </summary>
public partial class T_Statistics_Order
{
public T_Statistics_Order() { }
public virtual int Id { get; set; }
/// <summary>
/// 日期
/// </summary>
public virtual DateOnly LoginDate { get; set; }
/// <summary>
/// 天
/// </summary>
public virtual int LoginDay { get; set; }
/// <summary>
/// 意向订单次数
/// </summary>
public virtual int IntendedOrderCount { get; set; }
/// <summary>
/// 支付订单次数
/// </summary>
public virtual int PaidOrders { get; set; }
/// <summary>
/// 支付订单金额
/// </summary>
public virtual int RechargeAmount { get; set; }
/// <summary>
/// 渠道号
/// </summary>
public virtual string Channel { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
public virtual DateTime CreatedAt { get; set; }
/// <summary>
/// 修改时间
/// </summary>
public virtual DateTime UpdatedAt { get; set; }
}

View File

@ -0,0 +1,53 @@
using System;
namespace CloudGaming.GameModel.Db.Db_Ext;
/// <summary>
/// 用户统计表
/// </summary>
public partial class T_Statistics_User
{
public T_Statistics_User() { }
public virtual int Id { get; set; }
/// <summary>
/// 日期
/// </summary>
public virtual DateOnly LoginDate { get; set; }
/// <summary>
/// 天
/// </summary>
public virtual int LoginDay { get; set; }
/// <summary>
/// 用户登录数
/// </summary>
public virtual int LoginCount { get; set; }
/// <summary>
/// 用户注册数
/// </summary>
public virtual int RegistrCount { get; set; }
/// <summary>
/// 用户活跃数
/// </summary>
public virtual int ActiveCount { get; set; }
/// <summary>
/// 渠道号
/// </summary>
public virtual string Channel { get; set; } = null!;
/// <summary>
/// 创建时间
/// </summary>
public virtual DateTime CreatedAt { get; set; }
/// <summary>
/// 修改时间
/// </summary>
public virtual DateTime UpdatedAt { get; set; }
}

View File

@ -1,4 +1,4 @@
using System;
using System;
namespace CloudGaming.GameModel.Db.Db_Ext;

View File

@ -70,7 +70,7 @@ public partial class CloudGamingGameContext : DbContext
public virtual DbSet<T_Game_UserShare> 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)
{

View File

@ -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
内网穿透

View File

@ -23,6 +23,9 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="StackExchange.Redis" Version="2.8.16" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.8.1" />
<PackageReference Include="Quartz" Version="3.13.1" />
<PackageReference Include="Quartz.AspNetCore" Version="3.13.1" />
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.13.1" />
</ItemGroup>
</Project>

View File

@ -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;
/// <summary>
/// 提供扩展方法,用于基于 QuartzTriggerAttribute 注册 Quartz 作业和触发器。
/// </summary>
public static class QuartzExtensions
{
/// <summary>
/// 扫描指定程序集中的所有带有 QuartzTriggerAttribute 的作业类,并将它们注册到 Quartz 调度器中。
/// </summary>
/// <param name="services">依赖注入服务集合。</param>
/// <param name="assembly">要扫描的程序集。</param>
/// <returns>更新后的服务集合。</returns>
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<QuartzTriggerAttribute>();
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;
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.QuartzExtend;
/// <summary>
/// 自定义特性,用于标识 Quartz 作业的触发器配置。
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class QuartzTriggerAttribute : Attribute
{
/// <summary>
/// 获取触发器的名称。
/// </summary>
public string TriggerName { get; }
/// <summary>
/// 获取触发器的时间间隔(秒)。
/// </summary>
public int? IntervalInSeconds { get; }
/// <summary>
/// 获取 Cron 表达式。
/// </summary>
public string CronExpression { get; }
/// <summary>
/// 使用 Cron 表达式初始化特性。
/// </summary>
/// <param name="triggerName">触发器名称。</param>
/// <param name="cronExpression">Cron 表达式。</param>
public QuartzTriggerAttribute(string triggerName, string cronExpression = "0/1 * * * * ?")
{
TriggerName = triggerName;
IntervalInSeconds = null;
CronExpression = cronExpression;
}
/// <summary>
/// 使用时间间隔初始化特性。
/// </summary>
/// <param name="triggerName">触发器名称。</param>
/// <param name="intervalInSeconds">时间间隔(秒)。</param>
public QuartzTriggerAttribute(string triggerName, int intervalInSeconds)
{
TriggerName = triggerName;
IntervalInSeconds = intervalInSeconds;
CronExpression = "";
}
}

View File

@ -50,7 +50,7 @@ namespace HuanMeng.DotNetCore.Redis
return database;
}
/// <summary>
///
@ -195,6 +195,7 @@ namespace HuanMeng.DotNetCore.Redis
/// <returns></returns>
public static async Task<T?> StringGetAsync<T>(this IDatabase database, string key)
{
var value = await database.StringGetAsync(key);
// 检查值是否为空
if (!value.HasValue)
@ -232,21 +233,80 @@ namespace HuanMeng.DotNetCore.Redis
/// <summary>
/// 异步模糊查询key
/// </summary>
/// <param name="server"></param>
/// <param name="pattern"></param>
/// <param name="pageSize"></param>
/// <param name="database"></param>
/// <param name="server">Redis 服务器实例</param>
/// <param name="pattern">匹配的模式(支持通配符)</param>
/// <param name="pageSize">每次扫描的页大小</param>
/// <param name="database">数据库编号,默认值为 -1 表示全部数据库</param>
/// <returns>匹配的键的字符串列表</returns>
public static async Task<List<string>> ScanKeysAsync(this IServer server, string pattern, int pageSize = 100, int database = -1)
{
var matchingKeys = new List<string>();
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;
}
/// <summary>
/// 模糊查询所有匹配的键的数量
/// </summary>
/// <param name="server">Redis 服务器实例</param>
/// <param name="pattern">匹配的模式(支持通配符)</param>
/// <returns>匹配的键的数量</returns>
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;
}
/// <summary>
/// 异步模糊查询所有匹配的键的数量
/// </summary>
/// <param name="server">Redis 服务器实例</param>
/// <param name="pattern">匹配的模式(支持通配符)</param>
/// <param name="pageSize">每次扫描的页大小</param>
/// <param name="database">数据库编号,默认为 -1 表示当前数据库</param>
/// <returns>匹配的键的数量</returns>
public static async Task<int> 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;
}
}
}