上篇我们实现了认证服务和网关服务,基本我们的基础服务已经完成了,接下来我们才需要做服务的数据迁移。
这里我们需要使用EF的CodeFirst模式。通过DotnetCli的命令去操作:

  1. dotnet ef migrations add init

修改项目

编辑我们每个服务的EfCore项目的项目文件,添加Microsoft.EntityFrameworkCore.Tools的依赖,也可以通过VS的nuget包管理器安装。只有添加了这个依赖,我们才能使用dotnet ef命令。
在项目文件中添加如下内容:

  1. <ItemGroup>
  2. <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.1">
  3. <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  4. </PackageReference>
  5. </ItemGroup>

只添加这个依赖还不行,若直接运行dotnet ef命令的话,会提示我们需要实现一个DbContextFactory类。
所以我们在每个服务的EFCore项目中都添加一个DbContextFactory类,类结构如下,每个服务对应修改一下名字即可

  1. using System.IO;
  2. using JetBrains.Annotations;
  3. using Microsoft.EntityFrameworkCore;
  4. using Microsoft.EntityFrameworkCore.Design;
  5. using Microsoft.Extensions.Configuration;
  6. using Volo.Abp;
  7. namespace FunShow.AdministrationService.EntityFrameworkCore.EntityFrameworkCore
  8. {
  9. /* This class is needed for EF Core console commands
  10. * (like Add-Migration and Update-Database commands)
  11. * */
  12. public class AdministrationServiceDbContextFactory : IDesignTimeDbContextFactory<AdministrationServiceDbContext>
  13. {
  14. private readonly string _connectionString;
  15. /* This constructor is used when you use EF Core tooling (e.g. Update-Database) */
  16. public AdministrationServiceDbContextFactory()
  17. {
  18. _connectionString = GetConnectionStringFromConfiguration();
  19. }
  20. /* This constructor is used by DbMigrator application */
  21. public AdministrationServiceDbContextFactory([NotNull] string connectionString)
  22. {
  23. Check.NotNullOrWhiteSpace(connectionString, nameof(connectionString));
  24. _connectionString = connectionString;
  25. }
  26. public AdministrationServiceDbContext CreateDbContext(string[] args)
  27. {
  28. AdministrationServiceEfCoreEntityExtensionMappings.Configure();
  29. var builder = new DbContextOptionsBuilder<AdministrationServiceDbContext>()
  30. .UseNpgsql(_connectionString, b =>
  31. {
  32. b.MigrationsHistoryTable("__AdministrationService_Migrations");
  33. });
  34. return new AdministrationServiceDbContext(builder.Options);
  35. }
  36. private static string GetConnectionStringFromConfiguration()
  37. {
  38. return BuildConfiguration()
  39. .GetConnectionString(AdministrationServiceDbProperties.ConnectionStringName);
  40. }
  41. private static IConfigurationRoot BuildConfiguration()
  42. {
  43. var builder = new ConfigurationBuilder()
  44. .SetBasePath(
  45. Path.Combine(
  46. Directory.GetCurrentDirectory(),
  47. $"..{Path.DirectorySeparatorChar}FunShow.AdministrationService.HttpApi.Host"
  48. )
  49. )
  50. .AddJsonFile("appsettings.json", optional: false);
  51. return builder.Build();
  52. }
  53. }
  54. }

然后我们就可以执行dotnet ef migrations add init生成数据迁移文件了。

实现DbMigrator迁移程序

使用DbMigrator迁移程序可以一次性执行多个服务的迁移任务,当然我们也可以每个服务单独去执行dotnet ef database update这个命令,如果不嫌麻烦的话。
同时DbMigrator程序可以添加一些初始化数据的DataSeeder。
在前面我们DbMigrator只是创建了个项目,并没有实现功能,接下来我们就需要实现DbMigrator了。
第一步当然是修改项目文件添加我们的项目依赖,我们需要添加每个服务的EntityFrameworkCore和Application.Contracts项目,以及Shared.Hosting项目,当然最重要是需要Microsoft.EntityFrameworkCore.Tools,不然无法执行迁移命令。完整项目文件内容如下:

  1. <Project Sdk="Microsoft.NET.Sdk">
  2. <PropertyGroup>
  3. <OutputType>Exe</OutputType>
  4. <TargetFramework>net7.0</TargetFramework>
  5. <ImplicitUsings>enable</ImplicitUsings>
  6. <Nullable>enable</Nullable>
  7. </PropertyGroup>
  8. <ItemGroup>
  9. <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
  10. <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.1" />
  11. </ItemGroup>
  12. <ItemGroup>
  13. <ProjectReference Include="..\FunShow.Shared.Hosting\FunShow.Shared.Hosting.csproj" />
  14. <ProjectReference Include="..\..\services\administration\src\FunShow.AdministrationService.Application.Contracts\FunShow.AdministrationService.Application.Contracts.csproj" />
  15. <ProjectReference Include="..\..\services\administration\src\FunShow.AdministrationService.EntityFrameworkCore\FunShow.AdministrationService.EntityFrameworkCore.csproj" />
  16. <ProjectReference Include="..\..\services\identity\src\FunShow.IdentityService.EntityFrameworkCore\FunShow.IdentityService.EntityFrameworkCore.csproj" />
  17. <ProjectReference Include="..\..\services\identity\src\FunShow.IdentityService.Application.Contracts\FunShow.IdentityService.Application.Contracts.csproj" />
  18. <ProjectReference Include="..\..\services\logging\src\FunShow.LoggingService.EntityFrameworkCore\FunShow.LoggingService.EntityFrameworkCore.csproj" />
  19. <ProjectReference Include="..\..\services\logging\src\FunShow.LoggingService.Application.Contracts\FunShow.LoggingService.Application.Contracts.csproj" />
  20. </ItemGroup>
  21. <ItemGroup>
  22. <None Remove="appsettings.json" />
  23. <Content Include="appsettings.json">
  24. <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
  25. <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  26. </Content>
  27. <None Remove="appsettings.secrets.json" />
  28. <Content Include="appsettings.secrets.json">
  29. <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
  30. <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  31. </Content>
  32. </ItemGroup>
  33. </Project>

根据ABP的Console模板,我们需要添加一个HostedService文件和Module文件,当然也可以直接新建一个ABP的console模板来操作。

在module文件中添加DepensOn依赖

  1. [DependsOn(
  2. typeof(FunShowSharedHostingModule),
  3. typeof(IdentityServiceEntityFrameworkCoreModule),
  4. typeof(IdentityServiceApplicationContractsModule),
  5. typeof(LoggingServiceEntityFrameworkCoreModule),
  6. typeof(LoggingServiceApplicationContractsModule),
  7. typeof(AdministrationServiceEntityFrameworkCoreModule),
  8. typeof(AdministrationServiceApplicationContractsModule)
  9. )]
  10. public class FunShowDbMigratorModule : AbpModule
  11. {
  12. }

实现DbMigrationService

DbMigrationService负责执行我们的数据迁移文件以及初始化种子数据。完整的内容如下:

  1. using FunShow.AdministrationService.EntityFrameworkCore;
  2. using FunShow.IdentityService;
  3. using FunShow.IdentityService.EntityFrameworkCore;
  4. using FunShow.LoggingService.EntityFrameworkCore;
  5. using Microsoft.EntityFrameworkCore;
  6. using Microsoft.Extensions.DependencyInjection;
  7. using Microsoft.Extensions.Logging;
  8. using Volo.Abp.Data;
  9. using Volo.Abp.DependencyInjection;
  10. using Volo.Abp.Domain.Repositories;
  11. using Volo.Abp.EntityFrameworkCore;
  12. using Volo.Abp.Identity;
  13. using Volo.Abp.MultiTenancy;
  14. using Volo.Abp.TenantManagement;
  15. using Volo.Abp.Uow;
  16. namespace FunShow.DbMigrator;
  17. public class FunShowDbMigrationService : ITransientDependency
  18. {
  19. private readonly ILogger<FunShowDbMigrationService> _logger;
  20. private readonly ITenantRepository _tenantRepository;
  21. private readonly IDataSeeder _dataSeeder;
  22. private readonly ICurrentTenant _currentTenant;
  23. private readonly IUnitOfWorkManager _unitOfWorkManager;
  24. public FunShowDbMigrationService(
  25. ILogger<FunShowDbMigrationService> logger,
  26. ITenantRepository tenantRepository,
  27. IDataSeeder dataSeeder,
  28. ICurrentTenant currentTenant,
  29. IUnitOfWorkManager unitOfWorkManager)
  30. {
  31. _logger = logger;
  32. _tenantRepository = tenantRepository;
  33. _dataSeeder = dataSeeder;
  34. _currentTenant = currentTenant;
  35. _unitOfWorkManager = unitOfWorkManager;
  36. }
  37. public async Task MigrateAsync(CancellationToken cancellationToken)
  38. {
  39. await MigrateHostAsync(cancellationToken);
  40. await MigrateTenantsAsync(cancellationToken);
  41. _logger.LogInformation("Migration completed!");
  42. }
  43. private async Task MigrateHostAsync(CancellationToken cancellationToken)
  44. {
  45. _logger.LogInformation("Migrating Host side...");
  46. await MigrateAllDatabasesAsync(null, cancellationToken);
  47. await SeedDataAsync();
  48. }
  49. private async Task MigrateTenantsAsync(CancellationToken cancellationToken)
  50. {
  51. _logger.LogInformation("Migrating tenants...");
  52. var tenants =
  53. await _tenantRepository.GetListAsync(includeDetails: true, cancellationToken: cancellationToken);
  54. var migratedDatabaseSchemas = new HashSet<string>();
  55. foreach (var tenant in tenants)
  56. {
  57. using (_currentTenant.Change(tenant.Id))
  58. {
  59. // Database schema migration
  60. var connectionString = tenant.FindDefaultConnectionString();
  61. if (!connectionString.IsNullOrWhiteSpace() && //tenant has a separate database
  62. !migratedDatabaseSchemas.Contains(connectionString)) //the database was not migrated yet
  63. {
  64. _logger.LogInformation($"Migrating tenant database: {tenant.Name} ({tenant.Id})");
  65. await MigrateAllDatabasesAsync(tenant.Id, cancellationToken);
  66. migratedDatabaseSchemas.AddIfNotContains(connectionString);
  67. }
  68. //Seed data
  69. _logger.LogInformation($"Seeding tenant data: {tenant.Name} ({tenant.Id})");
  70. await SeedDataAsync();
  71. }
  72. }
  73. }
  74. private async Task MigrateAllDatabasesAsync(
  75. Guid? tenantId,
  76. CancellationToken cancellationToken)
  77. {
  78. using (var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: false))
  79. {
  80. await MigrateDatabaseAsync<AdministrationServiceDbContext>(cancellationToken);
  81. await MigrateDatabaseAsync<IdentityServiceDbContext>(cancellationToken);
  82. await MigrateDatabaseAsync<LoggingServiceDbContext>(cancellationToken);
  83. await uow.CompleteAsync(cancellationToken);
  84. }
  85. _logger.LogInformation(
  86. $"All databases have been successfully migrated ({(tenantId.HasValue ? $"tenantId: {tenantId}" : "HOST")}).");
  87. }
  88. private async Task MigrateDatabaseAsync<TDbContext>(
  89. CancellationToken cancellationToken)
  90. where TDbContext : DbContext, IEfCoreDbContext
  91. {
  92. _logger.LogInformation($"Migrating {typeof(TDbContext).Name.RemovePostFix("DbContext")} database...");
  93. var dbContext = await _unitOfWorkManager.Current.ServiceProvider
  94. .GetRequiredService<IDbContextProvider<TDbContext>>()
  95. .GetDbContextAsync();
  96. await dbContext
  97. .Database
  98. .MigrateAsync(cancellationToken);
  99. }
  100. private async Task SeedDataAsync()
  101. {
  102. await _dataSeeder.SeedAsync(
  103. new DataSeedContext(_currentTenant.Id)
  104. .WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName,
  105. IdentityServiceDbProperties.DefaultAdminEmailAddress)
  106. .WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName,
  107. IdentityServiceDbProperties.DefaultAdminPassword)
  108. );
  109. }
  110. }

MigrateDatabaseAsync方法负责执行我们之前生成的迁移文件,SeedDataAsync则负责执行初始化我们项目中的种子数据。
后续添加更多的服务,我们只需要在MigrateAllDatabasesAsync中添加我们服务对应的DBContext文件即可。

初始化种子数据

上面说了DbMigrationService可以负责执行初始化种子数据。
根据我们需要添加一个DataSeedContributor和DataSeeder类。
这里我们初始化一下OpenIddict的种子数据。

  1. using System.Threading.Tasks;
  2. using Volo.Abp.Data;
  3. using Volo.Abp.DependencyInjection;
  4. namespace FunShow.DbMigrator;
  5. public class OpenIddictDataSeedContributor : IDataSeedContributor, ITransientDependency
  6. {
  7. private readonly OpenIddictDataSeeder _openIddictDataSeeder;
  8. public OpenIddictDataSeedContributor(OpenIddictDataSeeder openIddictDataSeeder)
  9. {
  10. _openIddictDataSeeder = openIddictDataSeeder;
  11. }
  12. public async Task SeedAsync(DataSeedContext context)
  13. {
  14. await _openIddictDataSeeder.SeedAsync();
  15. }
  16. }

主要是继承并实现IDataSeedContributor接口,这个接口会在DbMigrationService中获取并执行SeedAsync方法。
OpenIddictDataSeeder执行的初始化数据太多,这里就不贴代码了。主要就是读取配置文件的Applications和Resources初始化写进数据库。

编辑appsettings.json文件

在配置文件中添加数据库连接字符串和OpenIddict配置

  1. {
  2. "ConnectionStrings": {
  3. "AdministrationService": "Host=localhost;Port=5432;User ID=postgres;password=myPassw0rd;Pooling=true;Database=FunShow_Administration;",
  4. "IdentityService": "Host=localhost;Port=5432;User ID=postgres;password=myPassw0rd;Pooling=true;Database=FunShow_Identity;",
  5. "LoggingService": "Host=localhost;Port=5432;User ID=postgres;password=myPassw0rd;Pooling=true;Database=FunShow_LoggingService;"
  6. },
  7. "OpenIddict": {
  8. "Applications": {
  9. "FunShow_Vue": {
  10. "RootUrl": "http://localhost:4200"
  11. },
  12. "WebGateway": {
  13. "RootUrl": "https://localhost:44325"
  14. }
  15. },
  16. "Resources": {
  17. "AccountService": {
  18. "RootUrl": "https://localhost:44322"
  19. },
  20. "IdentityService": {
  21. "RootUrl": "https://localhost:44388"
  22. },
  23. "AdministrationService": {
  24. "RootUrl": "https://localhost:44367"
  25. },
  26. "LoggingService": {
  27. "RootUrl": "https://localhost:45124"
  28. }
  29. }
  30. }
  31. }

到这我们的DbMigrator迁移程序也实现完了,后续添加新服务也只需要添加修改对应的地方,然后执行程序即可。
执行之后我们会生成3个数据库,里面也包含我们的种子数据。

到这我们基本完成了微服务的搭建。

ABP微服务系列学习-搭建自己的微服务结构(四)的更多相关文章

  1. springcloud微服务架构搭建

    SpringCloud微服务框架搭建 一.微服务架构 1.1什么是分布式 不同模块部署在不同服务器上 作用:分布式解决网站高并发带来问题 1.2什么是集群 多台服务器部署相同应用构成一个集群 作用:通 ...

  2. SprngCloud微服务框架搭建(一)

    参照来源 :https://blog.csdn.net/forezp/article/details/70148833 1.简介 目前来说,SpringCloud是比较完整的微服务解决方案框架.不像其 ...

  3. Spring Cloud 微服务中搭建 OAuth2.0 认证授权服务

    在使用 Spring Cloud 体系来构建微服务的过程中,用户请求是通过网关(ZUUL 或 Spring APIGateway)以 HTTP 协议来传输信息,API 网关将自己注册为 Eureka ...

  4. 【spring colud】spring cloud微服务项目搭建【spring boot2.0】

    spring cloud微服务项目搭建 =================================== 示例版本: 1.spring boot 2.0版本 2.开发工具 IntellJ IDE ...

  5. 简单Spring Cloud 微服务框架搭建

    微服务是现在比较流行的技术,对于程序猿而言,了解并搭建一个基本的微服务框架是很有必要滴. 微服务包含的内容非常多,一般小伙伴们可以根据自己的需求不断添加各种组件.框架. 一般情况下,基本的微服务框架包 ...

  6. java架构之路-(微服务专题)初步认识微服务与nacos初步搭建

    历史演变: 以前我们都是一个war包,包含了很多很多的代码,反正我开始工作的时候做的就是这样的项目,一个金融系统,代码具体多少行记不清楚了,内部功能超多,但是实际能用到的不多,代码冗余超大,每次部署大 ...

  7. 十一、Docker搭建部署SpringCloud微服务项目Demo

    环境介绍 技术选型:SpringCloud&SpringCloud Alibaba&Docker 微服务模块划分: 员工模块:ems-employees 部门模块:ems-depart ...

  8. SpringCloudAlibaba 微服务讲解(二)微服务环境搭建

    微服务环境搭建 我们这次是使用的电商项目的商品.订单.用户为案例进行讲解 2.1 案例准备 2.1.1 技术选型 maven :3.3.9 数据库:mysql 持久层:SpringData JPA S ...

  9. (3)go-micro微服务项目搭建

    目录 一 微服务项目介绍 二 go-micro安装 1.拉取micro镜像 2.生成项目目录 三 项目搭建 使用DDD模式开发项目: 四 最后 一 微服务项目介绍 账户功能是每一个系统都绕不开的一部分 ...

  10. springCloud 微服务框架搭建入门(很简单的一个案例不喜勿扰)

    Spring cloud 实现服务注册及发现 服务注册与发现对于微服务系统来说非常重要.有了服务发现与注册,你就不需要整天改服务调用的配置文件了,你只需要使用服务的标识符,就可以访问到服务. clou ...

随机推荐

  1. Windows下使用VSCode搭建IDA Python脚本开发环境

    由于本人是VSCode的重度沉迷用户,需要写代码时总会想起这个软件,因此选择在VSCode中搭建IDA Python的开发环境 本文适用的环境如下: 1.操作系统 windows 2.Python3 ...

  2. 一篇文章教你实战Docker容器数据卷

    在上一篇中,咱们对Docker中的容器数据卷做了介绍.已经知道了容器数据卷是什么?能干什么用.那么本篇咱们就来实战容器数据卷,Docker容器数据卷案例主要做以下三个案例 1:宿主机(也就是Docke ...

  3. jQuery类库

    jQuery介绍 1. jQuery是一个轻量级的.兼容多浏览器的JavaScript库. 2. jQuery使用户能够更方便地处理HTML Document.Events.实现动画效果.方便地进行A ...

  4. openresty package path

    openresty lua_package_path 是整个openresty最基础的功能,不理解 path就无法做项目,更无法写框架. 先看下文档lua_package_path https://g ...

  5. selenium 输入文本时报InvalidElementStateException: Message: invalid element state

    问题: 当定位输入框时,定位到div标签,如:css->[class="delay el-input"],进行输入操作报invalid element state,显示元素状 ...

  6. 基于Unet+opencv实现天空对象的分割、替换和美化

           传统图像处理算法进行"天空分割"存在精度问题且调参复杂,无法很好地应对云雾.阴霾等情况:本篇文章分享的"基于Unet+opencv实现天空对象的分割.替换和 ...

  7. Go语言与其他高级语言的区别

    概述: go语言与其他语言相比,go语言的关键字非常少,只有25个,c语言有37个,c++有84个,python有33个,java有53个. 差异1:go语言不允许隐式转换,别名和原有类型也不能进行隐 ...

  8. Redis-02 Redis 类型

    Redis List 命令 说明 例子 LPush 在 List 头插入一个或多个元素 LPush mylist hello RPush 在 List 尾插入一个或多个元素 RPush mylist ...

  9. [深度学习]Keras利用VGG进行迁移学习模板

    # -*- coding: UTF-8 -*- import keras from keras import Model from keras.applications import VGG16 fr ...

  10. visualstudio2017 community版本,有点失去信心了,同样两行代码,外观看不出任何区别,但是一个报错

    不多废话,先上代码 注意查看函数fputs_FILE,该函数的两行代码fopen_s是同样的,但事实上: 第一条fopen_s执行起来会报错,但是第二条就不会!!! /* 练习:获取用户键盘输入,写入 ...