迁移现有用户数据到ABP vNext

发布时间 2023-06-17 21:26:42作者: 波多尔斯基

前言

使用 ABP vNext(下文简称 ABP)时,通常都是从 cli 开始新建模板,从一个空项目开始。对已经存续的项目来说,现有的数据,特别是用户等核心数据需要进行迁移。

老的项目,随着规模越来越大,每次修改都需要更改非常多地方,最重要的是,共用数据库使得维护起来需要小心翼翼。为了后续维护方便,我们可以使用 ABP 进行拆分,并将一个个子功能拆成独立的解决方案,独立进行部署。

数据库基于 postgresql。

迁移数据库

老系统建立于 ASP. NET CORE 3.1 时代,使用的是 ASP. NET Identity 这个东西,而 ABP 的用户管理系统也是基于 ASP. NET Identity 的,因此理论上来说可以平滑迁移。
有关用户权限与角色的一共有三个表:

  • AbpUserRoles:记录用户与角色的映射
  • AbpUsers :用户表
  • AbpRoles:角色表

实际上数据库表还是有一些区别,并不能直接进行平滑迁移,举几个例子:

  1. ABP 的 Id 字段为 uuid 类型,Identity 为 string 类型。
  2. ABP 多了一些与多租户管理相关的字段。
  3. ABP 多了一些的标识默认的字段,并且不可为 null。
  4. ABP 7.2中用户表的 Email 是不可为空列,而老版的 Identity 并不是(新版的也是)。

需要先对 User 和 Role 进行同步,然后再同步映射表。先将原来系统内的数据导出为 SQL,直接执行同步语句以同步 User 表:

INSERT INTO "public"."AbpUsers"("Id", "UserName", "NormalizedUserName", "Email", "NormalizedEmail", "EmailConfirmed", "PasswordHash", "SecurityStamp", "ConcurrencyStamp", "PhoneNumber", "PhoneNumberConfirmed", "TwoFactorEnabled", "LockoutEnd", "LockoutEnabled", "AccessFailedCount") VALUES ('a9700c52-448c-bc3a-277bc95c15cb', 'USRDEMO', 'USERDEMO', NULL, NULL, 'f', 'AQAAAAEAACcQHnDh6dl+2xH9ld+XTlqKWQZNaBzhOXIAEzdQ', 'XXOBEMERW572TSLVMBSX56XI7LF', '4dad1c39-7c7e-466c-02b5d75bb006', NULL, 'f', 'f', NULL, 't', 0);

肯定是不能正常通过的,提示 Email 不能为空。

处理 Email 字段

在 2019 年之前,Email 字段还不是必填项目,后来改成了的必填的项。但是这个习惯不是很符合国人的习惯,很多系统有个手机号也能注册。

我翻到了 github 上面的一个 issue,作者给出了以下解决方案:

  • 首先修改数据库表格定义,确保数据库能够接受 null 值。
modelBuilder.Entity<IdentityUser>(entity =>
{
        entity.Property(p => p.Email).IsRequired(false);
        entity.Property(p => p.NormalizedEmail).IsRequired(false);
});
  • 其次修改 IdentityUserStore,在处理用户时,不会对 null 值弹出异常。(找一下哪个引用了 AbpIdentityDomainModule,通常在领域模块)
    [Dependency(ReplaceServices = true)]
    public class MyIdentityUserStore: IdentityUserStore
    {
        public MyIdentityUserStore(IIdentityUserRepository userRepository, IIdentityRoleRepository roleRepository, IGuidGenerator guidGenerator, ILogger<IdentityRoleStore> logger, IdentityErrorDescriber describer = null) : base(userRepository, roleRepository, guidGenerator, logger, describer)
        {
        }

        /// <summary>
        /// Sets the <paramref name="email" /> address for a <paramref name="user" />.
        /// </summary>
        /// <param name="user">The user whose email should be set.</param>
        /// <param name="email">The email to set.</param>
        /// <param name="cancellationToken">The <see cref="T:System.Threading.CancellationToken" /> used to propagate notifications that the operation should be canceled.</param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public override Task SetEmailAsync(IdentityUser user, string email, CancellationToken cancellationToken = new CancellationToken())
        {
            cancellationToken.ThrowIfCancellationRequested();

            Check.NotNull(user, nameof(user));
            var t = typeof(IdentityUser);
            t.GetProperty(nameof(IdentityUser.Email))
                .SetValue(user, email, null);

            return Task.CompletedTask;
        }
    }

	public override void ConfigureServices(ServiceConfigurationContext context)
	{
	        ...
	       context.Services.Replace(ServiceDescriptor.Scoped<IdentityUserStore, MyIdentityUserStore>());
	}

处理其他字段

其他字段主要是一个默认值的问题,直接设置就可以了:

            entity.Property(p => p.IsActive).HasDefaultValue(true);
            entity.Property(p => p.CreationTime).HasDefaultValue(DateTime.Now);

处理完这个表之后的,执行 update-database,就可以正常执行 SQL 插入了。按照同样的方法处理 AbpRoles 表,最后同步 AbpUserRoles 就完成了。

其实我推荐另外一种方法:直接在数据库上设置默认值,然后导入,最后恢复原来的表结构,这样还不容易有副作用。

验证

启动 Auth 项目(如果是 Tired),用原来的用户名与密码调用,得到以下结果,完成迁移。

image