feat: add MagicWalletCycleSeedService — one-time BackgroundService to seed ClubMembershipCycle for existing active memberships, controlled via appsettings SeedWorkers:MagicWalletCycleSeed:Enabled
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m36s
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m36s
This commit is contained in:
@@ -0,0 +1,126 @@
|
|||||||
|
using CMSMicroservice.Application.Common.Interfaces;
|
||||||
|
using CMSMicroservice.Domain.Entities.Club;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CMSMicroservice.Infrastructure.BackgroundServices;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// سرویس یکباره برای Seed کردن ClubMembershipCycle برای عضویتهای فعال موجود.
|
||||||
|
/// عضویتهایی که قبل از اضافه شدن سیستم Cycle ایجاد شدهاند رکورد ندارند.
|
||||||
|
/// این Worker در استارتاپ اجرا شده، برای آنها CycleNumber=1 میسازد و سپس متوقف میشود.
|
||||||
|
///
|
||||||
|
/// فعال/غیرفعال از appsettings:
|
||||||
|
/// "SeedWorkers": { "MagicWalletCycleSeed": { "Enabled": true } }
|
||||||
|
///
|
||||||
|
/// بعد از اجرای موفق، Enabled را false کنید.
|
||||||
|
/// </summary>
|
||||||
|
public class MagicWalletCycleSeedService : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
private readonly ILogger<MagicWalletCycleSeedService> _logger;
|
||||||
|
|
||||||
|
public MagicWalletCycleSeedService(
|
||||||
|
IServiceScopeFactory scopeFactory,
|
||||||
|
ILogger<MagicWalletCycleSeedService> logger)
|
||||||
|
{
|
||||||
|
_scopeFactory = scopeFactory;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
// صبر کوتاه برای اطمینان از آماده شدن دیتابیس
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken);
|
||||||
|
|
||||||
|
_logger.LogInformation("MagicWalletCycleSeedService started — scanning active memberships without cycles...");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
var context = scope.ServiceProvider.GetRequiredService<IApplicationDbContext>();
|
||||||
|
|
||||||
|
var seededCount = await SeedMissingCycles(context, stoppingToken);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"MagicWalletCycleSeedService completed — seeded {Count} ClubMembershipCycle records",
|
||||||
|
seededCount);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "MagicWalletCycleSeedService encountered an error");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"MagicWalletCycleSeedService finished — shutting down (one-time execution). " +
|
||||||
|
"Set SeedWorkers:MagicWalletCycleSeed:Enabled to false in appsettings.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> SeedMissingCycles(IApplicationDbContext context, CancellationToken ct)
|
||||||
|
{
|
||||||
|
// پیدا کردن عضویتهای فعالی که هنوز Cycle ندارند
|
||||||
|
var membershipIdsWithCycle = await context.ClubMembershipCycles
|
||||||
|
.Where(c => !c.IsDeleted)
|
||||||
|
.Select(c => c.ClubMembershipId)
|
||||||
|
.Distinct()
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
var membershipsWithoutCycle = await context.ClubMemberships
|
||||||
|
.Where(cm => !cm.IsDeleted && cm.IsActive && !membershipIdsWithCycle.Contains(cm.Id))
|
||||||
|
.Select(cm => new
|
||||||
|
{
|
||||||
|
cm.Id,
|
||||||
|
cm.UserId,
|
||||||
|
cm.ActivatedAt
|
||||||
|
})
|
||||||
|
.ToListAsync(ct);
|
||||||
|
|
||||||
|
if (membershipsWithoutCycle.Count == 0)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("No active memberships without cycles found — nothing to seed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Found {Count} active memberships without ClubMembershipCycle — seeding...",
|
||||||
|
membershipsWithoutCycle.Count);
|
||||||
|
|
||||||
|
var seeded = 0;
|
||||||
|
foreach (var cm in membershipsWithoutCycle)
|
||||||
|
{
|
||||||
|
if (ct.IsCancellationRequested) break;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cycle = new ClubMembershipCycle
|
||||||
|
{
|
||||||
|
UserId = cm.UserId,
|
||||||
|
ClubMembershipId = cm.Id,
|
||||||
|
CycleNumber = 1,
|
||||||
|
PackagePurchasedAt = cm.ActivatedAt ?? DateTime.UtcNow,
|
||||||
|
IsCurrentCycle = true,
|
||||||
|
PurchaseMethod = 0,
|
||||||
|
PackageAmount = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
context.ClubMembershipCycles.Add(cycle);
|
||||||
|
seeded++;
|
||||||
|
|
||||||
|
_logger.LogDebug(
|
||||||
|
"Seeded ClubMembershipCycle for UserId={UserId}, MembershipId={MembershipId}, ActivatedAt={ActivatedAt}",
|
||||||
|
cm.UserId, cm.Id, cm.ActivatedAt);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex,
|
||||||
|
"Failed to seed cycle for MembershipId={MembershipId}, UserId={UserId}",
|
||||||
|
cm.Id, cm.UserId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.SaveChangesAsync(ct);
|
||||||
|
|
||||||
|
return seeded;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -126,6 +126,10 @@ public static class ConfigureServices
|
|||||||
// One-time: Initialize inventory records for existing products
|
// One-time: Initialize inventory records for existing products
|
||||||
services.AddHostedService<InventoryInitializerService>();
|
services.AddHostedService<InventoryInitializerService>();
|
||||||
|
|
||||||
|
// One-time: Seed ClubMembershipCycle for existing active memberships
|
||||||
|
if (configuration.GetValue<bool>("SeedWorkers:MagicWalletCycleSeed:Enabled"))
|
||||||
|
services.AddHostedService<MagicWalletCycleSeedService>();
|
||||||
|
|
||||||
if (configuration.GetValue<bool>("UseInMemoryDatabase"))
|
if (configuration.GetValue<bool>("UseInMemoryDatabase"))
|
||||||
{
|
{
|
||||||
services.AddDbContext<ApplicationDbContext>(options =>
|
services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
|
|||||||
@@ -68,6 +68,11 @@
|
|||||||
"CronExpression": "5 0 * * 0"
|
"CronExpression": "5 0 * * 0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"SeedWorkers": {
|
||||||
|
"MagicWalletCycleSeed": {
|
||||||
|
"Enabled": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Kestrel": {
|
"Kestrel": {
|
||||||
"EndpointDefaults": {
|
"EndpointDefaults": {
|
||||||
|
|||||||
@@ -75,6 +75,11 @@
|
|||||||
"CronExpression": "5 0 * * 0"
|
"CronExpression": "5 0 * * 0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"SeedWorkers": {
|
||||||
|
"MagicWalletCycleSeed": {
|
||||||
|
"Enabled": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Authentication": {
|
"Authentication": {
|
||||||
"Authority": "https://ids.domain.com/",
|
"Authority": "https://ids.domain.com/",
|
||||||
|
|||||||
Reference in New Issue
Block a user