255 lines
13 KiB
C#
255 lines
13 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Npgsql;
|
|
using Tiku.Application.Assets;
|
|
using Tiku.Application.Auth;
|
|
using Tiku.Application.Backoffice;
|
|
using Tiku.Application.Catalog;
|
|
using Tiku.Application.Commerce;
|
|
using Tiku.Application.Content;
|
|
using Tiku.Application.Growth;
|
|
using Tiku.Application.Jobs;
|
|
using Tiku.Application.Learning;
|
|
using Tiku.Application.Notifications;
|
|
using Tiku.Application.QuestionBanks;
|
|
using Tiku.Application.Profile;
|
|
using Tiku.Application.Points;
|
|
using Tiku.Application.PlatformAdmin;
|
|
using Tiku.Application.PlatformBilling;
|
|
using Tiku.Application.Scoreline;
|
|
using Tiku.Application.Storage;
|
|
using Tiku.Application.StudyContent;
|
|
using Tiku.Application.TenantAdmin;
|
|
using Tiku.Application.Security;
|
|
using Tiku.Application.Tenancy;
|
|
using Tiku.Infrastructure.Assets;
|
|
using Tiku.Infrastructure.Auth;
|
|
using Tiku.Infrastructure.Backoffice;
|
|
using Tiku.Infrastructure.Catalog;
|
|
using Tiku.Infrastructure.Commerce;
|
|
using Tiku.Infrastructure.Content;
|
|
using Tiku.Infrastructure.Growth;
|
|
using Tiku.Infrastructure.Jobs;
|
|
using Tiku.Infrastructure.Learning;
|
|
using Tiku.Infrastructure.Notifications;
|
|
using Tiku.Infrastructure.Persistence;
|
|
using Tiku.Infrastructure.Profile;
|
|
using Tiku.Infrastructure.Points;
|
|
using Tiku.Infrastructure.PlatformAdmin;
|
|
using Tiku.Infrastructure.PlatformBilling;
|
|
using Tiku.Infrastructure.QuestionBanks;
|
|
using Tiku.Infrastructure.Scoreline;
|
|
using Tiku.Infrastructure.Security;
|
|
using Tiku.Infrastructure.Storage;
|
|
using Tiku.Infrastructure.StudyContent;
|
|
using Tiku.Infrastructure.TenantAdmin;
|
|
using Tiku.Infrastructure.Tenancy;
|
|
using Tiku.Domain.Identity;
|
|
using StackExchange.Redis;
|
|
using MassTransit;
|
|
using Tiku.Infrastructure.Messaging;
|
|
|
|
namespace Tiku.Infrastructure;
|
|
|
|
public static class DependencyInjection
|
|
{
|
|
public static IServiceCollection AddInfrastructure(
|
|
this IServiceCollection services,
|
|
string connectionString,
|
|
Action<DbContextOptionsBuilder>? configureDatabase = null)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
|
|
|
services.AddSingleton(_ => NpgsqlDataSource.Create(connectionString));
|
|
services.AddScoped<TenantIsolationSaveChangesInterceptor>();
|
|
services.AddDbContext<TikuDbContext>((serviceProvider, options) =>
|
|
{
|
|
var dataSource = serviceProvider.GetRequiredService<NpgsqlDataSource>();
|
|
options.UseNpgsql(dataSource, npgsql =>
|
|
npgsql.MigrationsAssembly(typeof(TikuDbContext).Assembly.FullName));
|
|
configureDatabase?.Invoke(options);
|
|
options.AddInterceptors(serviceProvider.GetRequiredService<TenantIsolationSaveChangesInterceptor>());
|
|
});
|
|
services.AddIdentityCore<User>(options =>
|
|
{
|
|
options.Password.RequiredLength = 8;
|
|
options.Password.RequireDigit = false;
|
|
options.Password.RequireLowercase = false;
|
|
options.Password.RequireUppercase = false;
|
|
options.Password.RequireNonAlphanumeric = false;
|
|
options.Lockout.MaxFailedAccessAttempts = 5;
|
|
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
|
|
options.User.RequireUniqueEmail = false;
|
|
})
|
|
.AddEntityFrameworkStores<TikuDbContext>()
|
|
.AddSignInManager()
|
|
.AddDefaultTokenProviders()
|
|
.AddPasswordValidator<LetterAndDigitPasswordValidator<User>>();
|
|
services.Configure<PasswordHasherOptions>(options => options.IterationCount = 210_000);
|
|
services.AddScoped<ITenantDirectory, TenantDirectory>();
|
|
services.AddSingleton<IRedisSecurityStore, NullRedisSecurityStore>();
|
|
services.AddScoped<ISecurityEventPublisher, NullSecurityEventPublisher>();
|
|
services.AddMemoryCache();
|
|
services.AddScoped<ITenantFrontendConfigService, TenantFrontendConfigService>();
|
|
services.AddScoped<ITenantExternalProviderConfigService, TenantExternalProviderConfigService>();
|
|
services.AddSingleton<ITenantRuntimeCacheInvalidator, TenantRuntimeCacheInvalidator>();
|
|
services.AddHttpClient<IDomainOwnershipVerifier, DnsDomainOwnershipVerifier>();
|
|
services.AddHttpClient<IDomainGatewayProvisioner, HttpDomainGatewayProvisioner>();
|
|
services.AddScoped<ITenantDomainLifecycleService, TenantDomainLifecycleService>();
|
|
services.AddOptions<DomainLifecycleOptions>();
|
|
services.AddSingleton<ITenantExecutionScope, TenantExecutionScope>();
|
|
services.AddSingleton<IJwtKeyRing, JwtKeyRing>();
|
|
services.AddScoped<ITokenService, TokenService>();
|
|
services.AddScoped<AuthSessionStore>();
|
|
services.AddScoped<IAuthSessionStore>(provider => provider.GetRequiredService<AuthSessionStore>());
|
|
services.AddScoped<ISmsProvider, AliyunSmsProvider>();
|
|
services.AddScoped<ISmsVerificationService, SmsVerificationService>();
|
|
services.AddScoped<IWechatOAuthClient, WechatOAuthClient>();
|
|
services.AddScoped<IAuthService, AuthService>();
|
|
services.AddScoped<IIdentityProvider, SelfHostedIdentityProvider>();
|
|
services.AddScoped<ICatalogQueryService, CatalogQueryService>();
|
|
services.AddScoped<ITaxonomyService, TaxonomyService>();
|
|
services.AddScoped<IContentNavigationQueryService, ContentNavigationQueryService>();
|
|
services.AddScoped<IContentManagementService, ContentManagementService>();
|
|
services.AddScoped<IDirectContentService, DirectContentService>();
|
|
services.AddScoped<IQuestionBankQueryService, QuestionBankQueryService>();
|
|
services.AddScoped<IPublicQuestionAccessPolicy, PublicQuestionAccessPolicy>();
|
|
services.AddScoped<IQuestionReferenceService, QuestionReferenceService>();
|
|
services.AddScoped<IProfileService, ProfileService>();
|
|
services.AddScoped<IScorelineQueryService, ScorelineQueryService>();
|
|
services.AddScoped<IStudyContentQueryService, StudyContentQueryService>();
|
|
services.AddScoped<IAssetQueryService, AssetQueryService>();
|
|
services.AddScoped<IAssetAccessService, AssetAccessService>();
|
|
services.AddScoped<IAssetManagementService, AssetManagementService>();
|
|
services.AddScoped<IVideoPlaybackService, VideoPlaybackService>();
|
|
services.AddScoped<ILearningActivityService, LearningActivityService>();
|
|
services.AddScoped<ITenantAdminDirectService, TenantAdminDirectService>();
|
|
services.AddScoped<IBackofficeService, BackofficeService>();
|
|
services.AddScoped<IPlatformAdminService, PlatformAdminService>();
|
|
services.AddScoped<ISaasCatalogAdminService, SaasCatalogAdminService>();
|
|
services.AddScoped<ITenantBillingService, TenantBillingService>();
|
|
services.AddScoped<IPlatformBillingAdminService, PlatformBillingAdminService>();
|
|
services.AddScoped<IPlatformBillingPaymentGateway, PlatformBillingPaymentGateway>();
|
|
services.AddScoped<IPlatformBillingNotificationService, PlatformBillingNotificationService>();
|
|
services.AddScoped<IPlatformBillingSettlementService, PlatformBillingSettlementService>();
|
|
services.AddScoped<ISaasSubscriptionLifecycleService, SaasSubscriptionLifecycleService>();
|
|
services.AddOptions<SaasSubscriptionLifecycleOptions>();
|
|
services.AddScoped<ITenantOnboardingService, TenantOnboardingService>();
|
|
services.AddScoped<ICurrentAccessContext, CurrentAccessContext>();
|
|
services.AddScoped<IFeatureAccessService, FeatureAccessService>();
|
|
services.AddScoped<IFeatureUsageReconciliationService, FeatureUsageReconciliationService>();
|
|
services.AddOptions<FeatureUsageReconciliationOptions>();
|
|
services.AddScoped<IOperationAuditService, OperationAuditService>();
|
|
services.AddSingleton<IBackgroundJobDispatcher, NullBackgroundJobDispatcher>();
|
|
services.AddScoped<IBackgroundJobService, BackgroundJobService>();
|
|
services.AddScoped<ICommerceService, CommerceService>();
|
|
services.AddScoped<ICommerceAdminService, CommerceAdminService>();
|
|
services.AddScoped<IPointService, PointService>();
|
|
services.AddScoped<IReferralQrcodeGenerator, ReferralQrcodeGenerator>();
|
|
services.AddScoped<IReferralService, ReferralService>();
|
|
services.AddScoped<ICrmService, CrmService>();
|
|
services.AddScoped<ICommissionService, CommissionService>();
|
|
services.AddScoped<INotificationProvider, InAppNotificationProvider>();
|
|
services.AddSingleton<ITenantSecretProtector, TenantSecretProtector>();
|
|
services.AddScoped<ITenantSecretService, TenantSecretService>();
|
|
services.AddScoped<IPaymentProviderConfigService, PaymentProviderConfigService>();
|
|
services.AddScoped<IPaymentProviderGateway, PaymentProviderGateway>();
|
|
services.AddScoped<IPaymentProvider, ManualPaymentProvider>();
|
|
services.AddScoped<IPaymentProvider, WechatPayProvider>();
|
|
services.AddScoped<IPaymentProvider, AlipayProvider>();
|
|
services.AddOptions<AliyunOssOptions>()
|
|
.Validate(
|
|
AliyunOssOptions.BeValid,
|
|
"Aliyun OSS presign default expiration must be positive.");
|
|
services.AddOptions<ObjectStorageOptions>()
|
|
.Validate(
|
|
ObjectStorageOptions.BeValid,
|
|
"Storage options must include a default provider, default bucket and positive max upload bytes.");
|
|
services.AddSingleton<IObjectStorageService, AliyunOssObjectStorageService>();
|
|
|
|
return services;
|
|
}
|
|
|
|
public static IServiceCollection AddRedisSecurity(
|
|
this IServiceCollection services,
|
|
string connectionString,
|
|
string environmentName)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
|
|
var options = ConfigurationOptions.Parse(connectionString);
|
|
options.AbortOnConnectFail = false;
|
|
options.ClientName = $"tiku-{environmentName.ToLowerInvariant()}";
|
|
services.AddSingleton<IConnectionMultiplexer>(_ => ConnectionMultiplexer.Connect(options));
|
|
services.AddSingleton(provider => new RedisSecurityStore(
|
|
provider.GetRequiredService<IConnectionMultiplexer>(),
|
|
environmentName,
|
|
provider.GetRequiredService<Microsoft.Extensions.Logging.ILogger<RedisSecurityStore>>()));
|
|
services.AddSingleton<IRedisSecurityStore>(provider => provider.GetRequiredService<RedisSecurityStore>());
|
|
services.AddStackExchangeRedisCache(cache => cache.ConfigurationOptions = options);
|
|
return services;
|
|
}
|
|
|
|
public static IServiceCollection AddReliableMessaging(
|
|
this IServiceCollection services,
|
|
MessagingOptions options)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(options);
|
|
if (!options.IsConfigured)
|
|
{
|
|
throw new ArgumentException("A valid RabbitMQ host URI is required.", nameof(options));
|
|
}
|
|
|
|
services.AddMassTransit(registration =>
|
|
{
|
|
registration.SetKebabCaseEndpointNameFormatter();
|
|
registration.ConfigureHealthCheckOptions(health =>
|
|
{
|
|
health.Name = "rabbitmq";
|
|
health.Tags.Add("ready");
|
|
});
|
|
registration.AddEntityFrameworkOutbox<TikuDbContext>(outbox =>
|
|
{
|
|
outbox.UsePostgres();
|
|
outbox.UseBusOutbox();
|
|
outbox.QueryDelay = TimeSpan.FromSeconds(1);
|
|
outbox.DuplicateDetectionWindow = TimeSpan.FromMinutes(30);
|
|
});
|
|
if (options.ConfigureConsumers)
|
|
{
|
|
registration.AddConsumer<SecurityStateChangedConsumer>(consumer =>
|
|
{
|
|
consumer.ConcurrentMessageLimit = 1;
|
|
consumer.UseMessageRetry(retry => retry.Intervals(
|
|
TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15)));
|
|
});
|
|
registration.AddConsumer<BackgroundJobRequestedConsumer>(consumer =>
|
|
{
|
|
consumer.ConcurrentMessageLimit = 1;
|
|
consumer.UseMessageRetry(retry => retry.Intervals(
|
|
TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(15)));
|
|
});
|
|
registration.AddConfigureEndpointsCallback((context, _, endpoint) =>
|
|
{
|
|
endpoint.PrefetchCount = 1;
|
|
endpoint.ConcurrentMessageLimit = 1;
|
|
endpoint.UseEntityFrameworkOutbox<TikuDbContext>(context);
|
|
});
|
|
}
|
|
|
|
registration.UsingRabbitMq((context, configurator) =>
|
|
{
|
|
configurator.Host(new Uri(options.Host), options.VirtualHost, host =>
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(options.Username)) host.Username(options.Username);
|
|
if (!string.IsNullOrWhiteSpace(options.Password)) host.Password(options.Password);
|
|
});
|
|
configurator.ConfigureEndpoints(context);
|
|
});
|
|
});
|
|
services.AddScoped<ISecurityEventPublisher, MassTransitSecurityEventPublisher>();
|
|
services.AddScoped<IBackgroundJobDispatcher, MassTransitBackgroundJobDispatcher>();
|
|
return services;
|
|
}
|
|
}
|