feat(saas): implement marketplace and tenant onboarding

This commit is contained in:
2026-07-29 13:58:59 +08:00
parent 76606029e2
commit 6db200a2fc
145 changed files with 16862 additions and 94529 deletions

View File

@@ -1,5 +1,7 @@
using Tiku.Application;
using Tiku.Application.Tenancy;
using Tiku.Application.PlatformBilling;
using Tiku.Application.Security;
using Tiku.Infrastructure;
using Tiku.Worker;
using Tiku.Infrastructure.Messaging;
@@ -52,6 +54,10 @@ else if (builder.Environment.IsProduction())
}
builder.Services.AddOptions<DomainLifecycleOptions>()
.Bind(builder.Configuration.GetSection("TenantDomains"));
builder.Services.AddOptions<SaasSubscriptionLifecycleOptions>()
.Bind(builder.Configuration.GetSection("SaasSubscriptions"));
builder.Services.AddOptions<FeatureUsageReconciliationOptions>()
.Bind(builder.Configuration.GetSection("FeatureUsageReconciliation"));
builder.Services.AddHostedService<Worker>();
var host = builder.Build();

View File

@@ -1,5 +1,6 @@
using Microsoft.Extensions.Options;
using Tiku.Application.Jobs;
using Tiku.Application.PlatformBilling;
using Tiku.Application.Security;
using Tiku.Application.Tenancy;
using Tiku.Infrastructure.Messaging;
@@ -21,12 +22,16 @@ public class Worker(
try
{
var domainCount = await ProcessTenantDomainsAsync(stoppingToken);
var subscriptionCount = await ProcessSaasSubscriptionsAsync(stoppingToken);
var usageReconciliationCount = await ProcessFeatureUsageReconciliationAsync(stoppingToken);
var jobCount = await ProcessBackgroundJobsAsync(stoppingToken);
if (domainCount > 0 || jobCount > 0)
if (domainCount > 0 || subscriptionCount > 0 || usageReconciliationCount > 0 || jobCount > 0)
{
logger.LogInformation(
"Worker processed {DomainCount} pending tenant domains and {JobCount} background jobs.",
"Worker processed {DomainCount} pending tenant domains, {SubscriptionCount} SaaS subscriptions, {UsageReconciliationCount} tenant usage reconciliations, and {JobCount} background jobs.",
domainCount,
subscriptionCount,
usageReconciliationCount,
jobCount);
}
}
@@ -68,4 +73,24 @@ public class Worker(
includeImmediateJobs: !messagingOptions.IsConfigured,
cancellationToken: stoppingToken);
}
private async Task<int> ProcessSaasSubscriptionsAsync(CancellationToken stoppingToken)
{
await using var scope = scopeFactory.CreateAsyncScope();
scope.ServiceProvider.GetRequiredService<ITenantContextInitializer>()
.InitializeSystem(null, "SaaS subscription lifecycle discovery worker");
return await scope.ServiceProvider
.GetRequiredService<ISaasSubscriptionLifecycleService>()
.ProcessDueAsync(cancellationToken: stoppingToken);
}
private async Task<int> ProcessFeatureUsageReconciliationAsync(CancellationToken stoppingToken)
{
await using var scope = scopeFactory.CreateAsyncScope();
scope.ServiceProvider.GetRequiredService<ITenantContextInitializer>()
.InitializeSystem(null, "Tenant feature usage reconciliation discovery worker");
return await scope.ServiceProvider
.GetRequiredService<IFeatureUsageReconciliationService>()
.ProcessDueAsync(stoppingToken);
}
}

View File

@@ -17,6 +17,16 @@
"GatewayBaseUrl": null,
"GatewayApiKey": null
},
"SaasSubscriptions": {
"Enabled": true,
"BatchSize": 100,
"PastDueGraceDays": 7
},
"FeatureUsageReconciliation": {
"Enabled": true,
"BatchSize": 100,
"IntervalMinutes": 60
},
"Logging": {
"LogLevel": {
"Default": "Information",