## 前言 使用 ABP vNext(下文簡稱 ABP)時,通常都是從 cli 開始新建模板,從一個空項目開始。對已經存續的項目來說,現有的數據,特別是用戶等核心數據需要進行遷移。 老的項目,隨著規模越來越大,每次修改都需要更改非常多地方,最重要的是,共用資料庫使得維護起來需要小心翼翼。為了後續維護 ...
前言
使用 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),用原來的用戶名與密碼調用,得到以下結果,完成遷移。