迁移现有用户数据到ABP vNext
前言
使用 ABP vNext(下文简称 ABP)时,通常都是从 cli 开始新建模板,从一个空项目开始。对已经存续的项目来说,现有的数据,特别是用户等核心数据需要进行迁移。
老的项目,随着规模越来越大,每次修改都需要更改非常多地方,最重要的是,共用数据库使得维护起来需要小心翼翼。为了后续维护方便,我们可以使用 ABP 进行拆分,并将一个个子功能拆成独立的解决方案,独立进行部署。
数据库基于 postgresql。
迁移数据库
老系统建立于 ASP. NET CORE 3.1 时代,使用的是 ASP. NET Identity 这个东西,而 ABP 的用户管理系统也是基于 ASP. NET Identity 的,因此理论上来说可以平滑迁移。
有关用户权限与角色的一共有三个表:
- AbpUserRoles:记录用户与角色的映射
- AbpUsers :用户表
- AbpRoles:角色表
实际上数据库表还是有一些区别,并不能直接进行平滑迁移,举几个例子:
- ABP 的 Id 字段为 uuid 类型,Identity 为 string 类型。
- ABP 多了一些与多租户管理相关的字段。
- ABP 多了一些的标识默认的字段,并且不可为 null。
- 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),用原来的用户名与密码调用,得到以下结果,完成迁移。