在上一篇文章【ABP框架实践】从零构建微服务解决方案 - 准备工作 中,我们创建了运行微服务解决方案的基本环境,这篇文章我们将创建一些共享项目,用于存放一些公共的配置和模块。
共享项目,顾名思义就是用于其他应用程序、服务以及微服务中的模块和配置,以避免代码重复,并能够集中基本配置。在eShopOnAbp微服务解决方案中有五个共享项目,因此我们这里也创建五个共享项目,如:
MyCompanyName.MyProjectName.Shared.Hosting
MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore
MyCompanyName.MyProjectName.Shared.Hosting.Gateways
MyCompanyName.MyProjectName.Shared.Hosting.Microservices
MyCompanyName.MyProjectName.Shared.Localization
不用在意MyCompanyName.MyProjectName这样的名称,后面会将其替换为自己的项目名称。
创建解决方案和项目 首先我们进入到上篇文章创建的目录下,然后打开命令行窗口。
本文中的命令行窗口均为PowerShell,如果你使用的是cmd,请自行替换命令。 右击在终端中打开或者使用Shift + 右键选择在此处打开PowerShell窗口。
我们使用dotnet new命令来创建解决方案和项目。首先创建一个解决方案,然后再创建五个共享项目的类库。
1 2 3 4 5 6 dotnet new solution -n MyCompanyName.MyProjectName dotnet new classlib -n MyCompanyName.MyProjectName.Shared.Hosting -o shared/MyCompanyName.MyProjectName.Shared.Hosting --no-restore dotnet new classlib -n MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore -o shared/MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore --no-restore dotnet new classlib -n MyCompanyName.MyProjectName.Shared.Hosting.Gateways -o shared/MyCompanyName.MyProjectName.Shared.Hosting.Gateways --no-restore dotnet new classlib -n MyCompanyName.MyProjectName.Shared.Hosting.Microservices -o shared/MyCompanyName.MyProjectName.Shared.Hosting.Microservices --no-restore dotnet new classlib -n MyCompanyName.MyProjectName.Shared.Localization -o shared/MyCompanyName.MyProjectName.Shared.Localization --no-restore
-n参数用于指定项目名称。 -o参数用于指定项目输出路径。 –no-restore参数用于避免在创建项目时自动还原依赖项,因为我们还没有添加任何依赖项。
删除默认生成的Class1.cs文件。
1 2 3 4 5 rm shared/MyCompanyName.MyProjectName.Shared.Hosting/Class1.csrm shared/MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore/Class1.csrm shared/MyCompanyName.MyProjectName.Shared.Hosting.Gateways/Class1.csrm shared/MyCompanyName.MyProjectName.Shared.Hosting.Microservices/Class1.csrm shared/MyCompanyName.MyProjectName.Shared.Localization/Class1.cs
然后使用dotnet sln命令将共享项目添加到解决方案。
1 2 3 4 5 dotnet sln MyCompanyName.MyProjectName.sln add shared/MyCompanyName.MyProjectName.Shared.Hosting/MyCompanyName.MyProjectName.Shared.Hosting.csproj -s shared dotnet sln MyCompanyName.MyProjectName.sln add shared/MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore/MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore.csproj -s shared dotnet sln MyCompanyName.MyProjectName.sln add shared/MyCompanyName.MyProjectName.Shared.Hosting.Gateways/MyCompanyName.MyProjectName.Shared.Hosting.Gateways.csproj -s shared dotnet sln MyCompanyName.MyProjectName.sln add shared/MyCompanyName.MyProjectName.Shared.Hosting.Microservices/MyCompanyName.MyProjectName.Shared.Hosting.Microservices.csproj -s shared dotnet sln MyCompanyName.MyProjectName.sln add shared/MyCompanyName.MyProjectName.Shared.Localization/MyCompanyName.MyProjectName.Shared.Localization.csproj -s shared
-s参数用于指定项目所属的解决方案文件夹。
在解决方案所在目录创建common.props文件,添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <Project > <PropertyGroup > <LangVersion > latest</LangVersion > <Version > 1.0.0</Version > <NoWarn > $(NoWarn);CS1591</NoWarn > <AbpProjectType > ms</AbpProjectType > </PropertyGroup > <Target Name ="NoWarnOnRazorViewImportedTypeConflicts" BeforeTargets ="RazorCoreCompile" > <PropertyGroup > <NoWarn > $(NoWarn);0436</NoWarn > </PropertyGroup > </Target > <ItemGroup > <Content Remove ="$(UserProfile)\.nuget\packages\*\*\contentFiles\any\*\*.abppkg*.json" /> </ItemGroup > </Project >
向之前创建的五个项目添加common.props文件。依次打开各项目的.csproj文件,在<Project>节点下添加以下内容即可:
1 <Import Project ="..\..\common.props" />
Localization Shared.Localization 项目包含虚拟文件系统和解决方案范围内的本地化配置。它也是公共Web应用程序使用的本地化资源。如果您计划开发单体UI作为公共Web应用程序,那么将使用此本地化资源。
首先添加项目依赖,进入 Shared.Localization 项目所在目录,执行下面的命令:
1 2 dotnet add package Microsoft.Extensions.FileProviders.Embedded dotnet add package Volo.Abp.Validation
创建本地化资源类Localization/MyProjectNameResource.cs,添加以下内容:
1 2 3 4 5 6 7 8 9 using Volo.Abp.Localization;namespace MyCompanyName.MyProjectName.Localization ;[LocalizationResourceName("MyProjectName" ) ] public class MyProjectNameResource { }
在项目中添加本地化文件,如下所示:
Localization/MyProjectName/en.json
Localization/MyProjectName/zh-Hans.json
1 2 3 4 5 6 7 { "culture" : "en" , "texts" : { "Menu:Home" : "Home" , "Login" : "Login" } }
1 2 3 4 5 6 7 { "culture" : "zh-Hans" , "texts" : { "Menu:Home" : "首页" , "Login" : "登录" } }
将本地化资源文件作为嵌入式资源添加到项目中,在<Project>节点里添加下面内容:
1 2 3 4 <ItemGroup > <EmbeddedResource Include ="Localization\MyProjectName\*.json" /> <Content Remove ="Localization\MyProjectName\*.json" /> </ItemGroup >
在项目根目录创建MyProjectNameSharedLocalizationModule.cs文件,添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 using MyCompanyName.MyProjectName.Localization;using Volo.Abp.Localization;using Volo.Abp.Modularity;using Volo.Abp.Validation;using Volo.Abp.Validation.Localization;using Volo.Abp.VirtualFileSystem;namespace MyCompanyName.MyProjectName ;[DependsOn( typeof(AbpValidationModule) ) ]public class MyProjectNameSharedLocalizationModule : AbpModule { public override void ConfigureServices (ServiceConfigurationContext context ) { Configure<AbpVirtualFileSystemOptions>(options => { options.FileSets.AddEmbedded<MyProjectNameSharedLocalizationModule>(); }); Configure<AbpLocalizationOptions>(options => { options.Resources .Add<MyProjectNameResource>("en" ) .AddBaseTypes( typeof (AbpValidationResource) ).AddVirtualJson("/Localization/MyProjectName" ); options.DefaultResourceType = typeof (MyProjectNameResource); }); } }
Hosting Shared.Hosting 项目包含在 SharedHostingModule 服务配置中的数据库配置,这些配置会在其他模块中使用。
首先添加项目依赖,进入 Shared.Hosting 项目所在目录,执行下面的命令:
1 2 3 4 5 6 dotnet add package Serilog.Extensions.Logging dotnet add package Serilog.Sinks.Async dotnet add package Serilog.Sinks.File dotnet add package Serilog.Sinks.Console dotnet add package Volo.Abp.Autofac dotnet add package Volo.Abp.Data
在 ABP 中,每个带有数据库连接的模块都有其自己的连接字符串,以便在需要时可以迁移到多个数据库。这是通过连接字符串管理实现的。由于基础设施微服务(如 AdministrationService、IdentityService 和 SaasService)使用一个或多个模块来执行,因此每个模块数据库配置必须显式添加到相关数据库中,以便模块可以连接到它的数据库。
为了避免在微服务连接字符串配置中显式添加所有模块连接字符串,相关连接字符串被映射到单个连接字符串中。
因此我们在项目根目录创建MyProjectNameSharedHostingModule.cs文件,并添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 using Volo.Abp.Autofac;using Volo.Abp.Data;using Volo.Abp.Modularity;namespace MyCompanyName.MyProjectName.Shared.Hosting ;[DependsOn( typeof(AbpAutofacModule), typeof(AbpDataModule) ) ]public class MyProjectNameSharedHostingModule : AbpModule { public override void ConfigureServices (ServiceConfigurationContext context ) { ConfigureDatabaseConnections(); } private void ConfigureDatabaseConnections () { Configure<AbpDbConnectionOptions>(options => { options.Databases.Configure("AdministrationService" , database => { database.MappedConnections.Add("AbpAuditLogging" ); database.MappedConnections.Add("AbpPermissionManagement" ); database.MappedConnections.Add("AbpSettingManagement" ); database.MappedConnections.Add("AbpFeatureManagement" ); database.MappedConnections.Add("AbpBlobStoring" ); }); options.Databases.Configure("IdentityService" , database => { database.MappedConnections.Add("AbpIdentity" ); database.MappedConnections.Add("OpenIddict" ); }); }); } }
Hosting AspNetCore Shared.Hosting.AspNetCore 项目是 AuthServer、Web 和 PublicWeb 等应用的基础依赖, 需要依赖下列模块:
SharedHostingModule
AbpAspNetCoreSerilogModule
AbpSwashbuckleModule
因此我们进入 Shared.Hosting.AspNetCore 项目所在目录,执行下面的命令:
1 2 3 4 5 dotnet add package Serilog.AspNetCore dotnet add package Volo.Abp.AspNetCore.Serilog dotnet add package Volo.Abp.Swashbuckle dotnet add reference ../MyCompanyName.MyProjectName.Shared.Hosting/MyCompanyName.MyProjectName.Shared.Hosting.csproj
在项目根目录创建MyProjectNameSharedHostingAspNetCoreModule.cs文件,并添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using Volo.Abp.AspNetCore.Serilog;using Volo.Abp.Modularity;using Volo.Abp.Swashbuckle;namespace MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore ;[DependsOn( typeof(MyProjectNameSharedHostingModule), typeof(AbpSwashbuckleModule), typeof(AbpAspNetCoreSerilogModule) ) ]public class MyProjectNameSharedHostingAspNetCoreModule : AbpModule { public override void ConfigureServices (ServiceConfigurationContext context ) { } }
创建SerilogConfigurationHelper.cs,添加Serilog的基础配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 using Serilog;using Serilog.Events;namespace MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore ;public static class SerilogConfigurationHelper { public static void Configure (string applicationName ) { Log.Logger = new LoggerConfiguration() #if DEBUG .MinimumLevel.Debug() #else .MinimumLevel.Information() #endif .MinimumLevel.Override("Microsoft" , LogEventLevel.Information) .MinimumLevel.Override("Microsoft.EntityFrameworkCore" , LogEventLevel.Warning) .Enrich.FromLogContext() .Enrich.WithProperty("Application" , $"{applicationName} " ) .WriteTo.Async(c => c.File("Logs/logs.txt" )) .WriteTo.Async(c => c.Console()) .CreateLogger(); } }
创建SwaggerConfigurationHelper.cs,添加Swagger的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 using System.Collections.Generic;using Microsoft.Extensions.DependencyInjection;using Microsoft.OpenApi.Models;using Volo.Abp.Modularity;namespace MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore ;public static class SwaggerConfigurationHelper { public static void Configure ( ServiceConfigurationContext context, string apiTitle ) { context.Services.AddAbpSwaggerGen(options => { options.SwaggerDoc("v1" , new OpenApiInfo { Title = apiTitle, Version = "v1" }); options.DocInclusionPredicate((docName, description) => true ); options.CustomSchemaIds(type => type.FullName); }); } public static void ConfigureWithAuth ( ServiceConfigurationContext context, string authority, Dictionary<string , string > scopes, string apiTitle, string apiVersion = "v1" , string apiName = "v1" ) { context.Services.AddAbpSwaggerGenWithOAuth( authority: authority, scopes: scopes, options => { options.SwaggerDoc(apiName, new OpenApiInfo { Title = apiTitle, Version = apiVersion }); options.DocInclusionPredicate((docName, description) => true ); options.CustomSchemaIds(type => type.FullName); }); } }
创建ApplicationBuilderHelper.cs,用于简化WebApplication的创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 using System.Threading.Tasks;using Microsoft.AspNetCore.Builder;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;using Serilog;using Volo.Abp.Modularity;namespace MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore ;public static class ApplicationBuilderHelper { public static async Task <WebApplication > BuildApplicationAsync <TStartupModule >(string [] args ) where TStartupModule : IAbpModule { var builder = WebApplication.CreateBuilder(args); builder.Host .AddAppSettingsSecretsJson() .UseAutofac() .UseSerilog(); await builder.AddApplicationAsync<TStartupModule>(); return builder.Build(); } }
创建MyProjectNameBrandingProvider.cs,替换掉默认的BrandingProvider
1 2 3 4 5 6 7 8 9 10 using Volo.Abp.DependencyInjection;using Volo.Abp.Ui.Branding;namespace MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore ;[Dependency(ReplaceServices = true) ] public class MyProjectNameBrandingProvider : DefaultBrandingProvider { public override string AppName => "MyProjectName" ; }
创建MyProjectNameConstants.cs,添加匿名用户ClaimName
1 2 3 4 5 6 namespace MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore ;public static class MyProjectNameConstants { public const string AnonymousUserClaimName = "anonymous_id" ; }
Hosting Gateways Shard.Hosting.Gatways 是WebGateway和PublicWebGateway等网关的基础依赖。 该模块依赖于:
SharedHostingAspNetCoreModule 用于Serilog配置
AbpAspNetCoreMvcUiMultiTenancyModule 用于http头中的tenantId重定向
Yarp.ReverseProxy 用于反向代理
因此我们进入 Shared.Hosting.Gatways 项目所在目录,执行下面的命令:
1 2 3 dotnet add package Yarp.ReverseProxy dotnet add reference ../MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore/MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore.csproj
在项目根目录创建MyProjectNameSharedHostingGatewaysModule.cs文件,并添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 using Microsoft.Extensions.DependencyInjection;using MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore;using Volo.Abp.Modularity;using Volo.Abp.Swashbuckle;namespace MyCompanyName.MyProjectName.Shared.Hosting.Gateways ;[DependsOn( typeof(MyProjectNameSharedHostingAspNetCoreModule), typeof(AbpSwashbuckleModule) ) ]public class MyProjectNameSharedHostingGatewaysModule : AbpModule { public override void ConfigureServices (ServiceConfigurationContext context ) { var configuration = context.Services.GetConfiguration(); context.Services.AddReverseProxy() .LoadFromConfig(configuration.GetSection("ReverseProxy" )); } }
创建YarpSwaggerUIBuilderExtensions.cs,添加SwaggerUI的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 using Microsoft.AspNetCore.Builder;using Microsoft.Extensions.Configuration;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Logging;using Volo.Abp;using Yarp.ReverseProxy.Configuration;namespace MyCompanyName.MyProjectName.Shared.Hosting.Gateways ;public static class YarpSwaggerUIBuilderExtensions { public static IApplicationBuilder UseSwaggerUIWithYarp (this IApplicationBuilder app, ApplicationInitializationContext context ) { app.UseSwagger(); app.UseSwaggerUI(options => { var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>(); var logger = context.ServiceProvider.GetRequiredService<ILogger<ApplicationInitializationContext>>(); var proxyConfigProvider = context.ServiceProvider.GetRequiredService<IProxyConfigProvider>(); var yarpConfig = proxyConfigProvider.GetConfig(); var routedClusters = yarpConfig.Clusters .SelectMany(t => t.Destinations, (clusterId, destination) => new { clusterId.ClusterId, destination.Value }); var groupedClusters = routedClusters .GroupBy(q => q.Value.Address) .Select(t => t.First()) .Distinct() .ToList(); foreach (var clusterGroup in groupedClusters) { var routeConfig = yarpConfig.Routes.FirstOrDefault(q => q.ClusterId == clusterGroup.ClusterId); if (routeConfig == null ) { logger.LogWarning($"Swagger UI: Couldn't find route configuration for {clusterGroup.ClusterId} ..." ); continue ; } options.SwaggerEndpoint($"{clusterGroup.Value.Address} /swagger/v1/swagger.json" , $"{routeConfig.RouteId} API" ); options.OAuthClientId(configuration["AuthServer:SwaggerClientId" ]); options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret" ]); } }); return app; } }
创建GatewayHostBuilderExtensions.cs,用于向IHostBuilder添加Yarp配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 using Microsoft.Extensions.Configuration;namespace Microsoft.Extensions.Hosting ;public static class AbpHostingHostBuilderExtensions { public const string AppYarpJsonPath = "yarp.json" ; public static IHostBuilder AddYarpJson ( this IHostBuilder hostBuilder, bool optional = true , bool reloadOnChange = true , string path = AppYarpJsonPath ) { return hostBuilder.ConfigureAppConfiguration((_, builder) => { builder.AddJsonFile( path: AppYarpJsonPath, optional: optional, reloadOnChange: reloadOnChange ) .AddEnvironmentVariables(); }); } }
Hosting Microservices Shared.Hosting.Microservices 项目是AdministrationService、IdentityService和SaasService等微服务的基础托管依赖。 该模块依赖于SharedHostingModule,并包含以下内容:
AbpDistributedCacheOptions、AbpMultiTenancyCacheOptions和Redis的配置
具有基本的JwtBearer身份验证配置的JwtBearerConfigurationHelper
DbMigrations文件夹包含用于自动迁移的PendingMigrationsCheckerBase和DatabaseMigrationEventHandlerBase(本文暂不涉及)
后续还会依赖于AdministrationService和SaasService的EntityFrameworkCore层,用于租户和语言的自动迁移。
首先我们进入 Shared.Hosting.Microservices 项目所在目录,执行下面的命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 dotnet add package Microsoft.AspNetCore.DataProtection.StackExchangeRedis dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer dotnet add package DistributedLock.Redis dotnet add package Volo.Abp.AspNetCore.MultiTenancy dotnet add package Volo.Abp.EventBus.RabbitMQ dotnet add package Volo.Abp.BackgroundJobs.RabbitMQ dotnet add package Volo.Abp.Caching.StackExchangeRedis dotnet add package Volo.Abp.MongoDB dotnet add package Volo.Abp.DistributedLocking dotnet add package Volo.Abp.EntityFrameworkCore dotnet add reference ../MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore/MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore.csproj
在项目根目录创建MyProjectNameSharedHostingMicroservicesModule.cs文件,并添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 using Medallion.Threading;using Medallion.Threading.Redis;using Microsoft.AspNetCore.DataProtection;using Microsoft.Extensions.DependencyInjection;using MyCompanyName.MyProjectName.Shared.Hosting.AspNetCore;using StackExchange.Redis;using Volo.Abp.AspNetCore.MultiTenancy;using Volo.Abp.BackgroundJobs.RabbitMQ;using Volo.Abp.Caching;using Volo.Abp.Caching.StackExchangeRedis;using Volo.Abp.DistributedLocking;using Volo.Abp.EntityFrameworkCore;using Volo.Abp.EventBus.RabbitMq;using Volo.Abp.Modularity;using Volo.Abp.MultiTenancy;namespace MyCompanyName.MyProjectName.Shared.Hosting.Microservices ;[DependsOn( typeof(MyProjectNameSharedHostingAspNetCoreModule), typeof(AbpBackgroundJobsRabbitMqModule), typeof(AbpAspNetCoreMultiTenancyModule), typeof(AbpDistributedLockingModule), typeof(AbpEventBusRabbitMqModule), typeof(AbpCachingStackExchangeRedisModule), typeof(AbpEntityFrameworkCoreModule) ) ]public class MyProjectNameSharedHostingMicroservicesModule : AbpModule { public override void ConfigureServices (ServiceConfigurationContext context ) { var configuration = context.Services.GetConfiguration(); var hostingEnvironment = context.Services.GetHostingEnvironment(); Configure<AbpMultiTenancyOptions>(options => { options.IsEnabled = true ; }); Configure<AbpDistributedCacheOptions>(options => { options.KeyPrefix = "MyProjectName:" ; }); var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration" ]); context.Services .AddDataProtection() .SetApplicationName("MyProjectName" ) .PersistKeysToStackExchangeRedis(redis, "MyProjectName-Protection-Keys" ); context.Services.AddSingleton<IDistributedLockProvider>(_ => new RedisDistributedSynchronizationProvider(redis.GetDatabase())); } }
创建JwtBearerConfigurationHelper.cs,添加JwtBearer的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 using System;using Microsoft.AspNetCore.Authentication.JwtBearer;using Microsoft.Extensions.DependencyInjection;using Volo.Abp.Modularity;namespace MyCompanyName.MyProjectName.Shared.Hosting.Microservices ;public static class JwtBearerConfigurationHelper { public static void Configure ( ServiceConfigurationContext context, string audience ) { var configuration = context.Services.GetConfiguration(); context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority" ]; options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata" ]); options.Audience = audience; }); } }
创建DbMigrations/PendingMigrationsCheckerBase.cs,用于检查是否有待迁移的数据库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 using System;using System.Threading.Tasks;using Microsoft.Extensions.Logging;using Volo.Abp;using Volo.Abp.DependencyInjection;namespace MyCompanyName.MyProjectName.Shared.Hosting.Microservices.DbMigrations ;public abstract class PendingMigrationsCheckerBase : ITransientDependency { private readonly ILogger<PendingMigrationsCheckerBase> _logger; protected PendingMigrationsCheckerBase (ILoggerFactory loggerFactory ) { _logger = loggerFactory.CreateLogger<PendingMigrationsCheckerBase>(); } public async Task TryAsync (Func<Task> task, int retryCount = 3 ) { try { await task(); } catch (Exception ex) { retryCount--; if (retryCount <= 0 ) { throw ; } _logger.LogWarning($"{ex.GetType().Name} has been thrown. The operation will be tried {retryCount} times more. Exception:\n{ex.Message} " ); await Task.Delay(RandomHelper.GetRandom(5000 , 15000 )); await TryAsync(task, retryCount); } } }
创建DbMigrations/EfCore/PendingEfCoreMigrationsChecker.cs,用于检查是否有待迁移的EntityFrameworkCore数据库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 using System;using System.Linq;using System.Threading.Tasks;using Microsoft.EntityFrameworkCore;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Logging;using Volo.Abp.DistributedLocking;using Volo.Abp.EntityFrameworkCore;using Volo.Abp.EventBus.Distributed;using Volo.Abp.MultiTenancy;using Volo.Abp.Uow;namespace MyCompanyName.MyProjectName.Shared.Hosting.Microservices.DbMigrations.EfCore ;public abstract class PendingEfCoreMigrationsChecker <TDbContext > : PendingMigrationsCheckerBase where TDbContext : DbContext , IEfCoreDbContext { protected IUnitOfWorkManager UnitOfWorkManager { get ; } protected IServiceProvider ServiceProvider { get ; } protected ICurrentTenant CurrentTenant { get ; } protected IDistributedEventBus DistributedEventBus { get ; } protected IAbpDistributedLock DistributedLock { get ; } protected ILogger<PendingEfCoreMigrationsChecker<TDbContext>> Logger { get ; } protected string DatabaseName { get ; } protected PendingEfCoreMigrationsChecker ( ILoggerFactory loggerFactory, IUnitOfWorkManager unitOfWorkManager, IServiceProvider serviceProvider, ICurrentTenant currentTenant, IDistributedEventBus distributedEventBus, IAbpDistributedLock abpDistributedLock, string databaseName ):base (loggerFactory ) { UnitOfWorkManager = unitOfWorkManager; ServiceProvider = serviceProvider; CurrentTenant = currentTenant; DistributedEventBus = distributedEventBus; DistributedLock = abpDistributedLock; DatabaseName = databaseName; Logger = loggerFactory.CreateLogger<PendingEfCoreMigrationsChecker<TDbContext>>(); } public virtual async Task CheckAndApplyDatabaseMigrationsAsync () { await TryAsync(LockAndApplyDatabaseMigrationsAsync); } protected virtual async Task LockAndApplyDatabaseMigrationsAsync () { await using (var handle = await DistributedLock.TryAcquireAsync("Migration_" + DatabaseName)) { Logger.LogInformation($"Lock is acquired for db migration and seeding on database named: {DatabaseName} ..." ); if (handle is null ) { Logger.LogInformation($"Handle is null because of the locking for : {DatabaseName} " ); return ; } using (CurrentTenant.Change(null )) { using (var uow = UnitOfWorkManager.Begin(requiresNew: true , isTransactional: false )) { var dbContext = await ServiceProvider .GetRequiredService<IDbContextProvider<TDbContext>>() .GetDbContextAsync(); var pendingMigrations = await dbContext .Database .GetPendingMigrationsAsync(); if (pendingMigrations.Any()) { await dbContext.Database.MigrateAsync(); } await uow.CompleteAsync(); } } Logger.LogInformation($"Lock is released for db migration and seeding on database named: {DatabaseName} ..." ); } } }
创建DbMigrations/MongoDb/PendingMongoDbMigrationsChecker.cs,用于检查是否有待迁移的MongoDb数据库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 using System;using System.Threading.Tasks;using MyCompanyName.MyProjectName.Shared.Hosting.Microservices.DbMigrations.EfCore;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Logging;using MongoDB.Driver;using Serilog;using Volo.Abp.Data;using Volo.Abp.DistributedLocking;using Volo.Abp.MongoDB;using Volo.Abp.MultiTenancy;using Volo.Abp.Uow;namespace MyCompanyName.MyProjectName.Shared.Hosting.Microservices.DbMigrations.MongoDb ;public class PendingMongoDbMigrationsChecker <TDbContext > : PendingMigrationsCheckerBase where TDbContext : AbpMongoDbContext { protected IUnitOfWorkManager UnitOfWorkManager { get ; } protected IServiceProvider ServiceProvider { get ; } protected ICurrentTenant CurrentTenant { get ; } protected IDataSeeder DataSeeder { get ; } protected IAbpDistributedLock DistributedLockProvider { get ; } protected string DatabaseName { get ; } protected ILogger<PendingMongoDbMigrationsChecker<TDbContext>> Logger { get ; } protected PendingMongoDbMigrationsChecker ( ILoggerFactory loggerFactory, IUnitOfWorkManager unitOfWorkManager, IServiceProvider serviceProvider, ICurrentTenant currentTenant, IDataSeeder dataSeeder, IAbpDistributedLock distributedLockProvider, string databaseName ) : base (loggerFactory ) { UnitOfWorkManager = unitOfWorkManager; ServiceProvider = serviceProvider; CurrentTenant = currentTenant; DataSeeder = dataSeeder; DistributedLockProvider = distributedLockProvider; DatabaseName = databaseName; Logger = loggerFactory.CreateLogger<PendingMongoDbMigrationsChecker<TDbContext>>(); } public virtual async Task CheckAndApplyDatabaseMigrationsAsync () { await TryAsync(async () => { using (CurrentTenant.Change(null )) { using (var uow = UnitOfWorkManager.Begin(requiresNew: true , isTransactional: false )) { await MigrateDatabaseSchemaAsync(); await DataSeeder.SeedAsync(); await uow.CompleteAsync(); } } }); } protected virtual async Task<bool > MigrateDatabaseSchemaAsync () { var result = false ; await using (var handle = await DistributedLockProvider.TryAcquireAsync("Migration_" + DatabaseName)) { using (var uow = UnitOfWorkManager.Begin(requiresNew: true , isTransactional: false )) { Logger.LogInformation($"Lock is acquired for db migration and seeding on database named: {DatabaseName} ..." ); if (handle is null ) { Logger.LogInformation($"Handle is null because of the locking for : {DatabaseName} " ); return false ; } async Task<bool > MigrateDatabaseSchemaWithDbContextAsync () { var dbContexts = ServiceProvider.GetServices<IAbpMongoDbContext>(); var connectionStringResolver = ServiceProvider.GetRequiredService<IConnectionStringResolver>(); foreach (var dbContext in dbContexts) { var connectionString = await connectionStringResolver.ResolveAsync( ConnectionStringNameAttribute.GetConnStringName(dbContext.GetType())); if (connectionString.IsNullOrWhiteSpace()) { continue ; } var mongoUrl = new MongoUrl(connectionString); var databaseName = mongoUrl.DatabaseName; var client = new MongoClient(mongoUrl); if (databaseName.IsNullOrWhiteSpace()) { databaseName = ConnectionStringNameAttribute.GetConnStringName(dbContext.GetType()); } (dbContext as AbpMongoDbContext)?.InitializeCollections(client.GetDatabase(databaseName)); } return true ; } result = await MigrateDatabaseSchemaWithDbContextAsync(); await uow.CompleteAsync(); } return result; } } }
参考资料