ASP.NET 样板:什么是四眼原则的最佳解决方案
ASP.NET Boilerplate: What is the best solution for Four eyes principle
我想将 4 眼原则添加到 ASP.NET 样板框架。这意味着对角色、用户等的每项更改在应用到系统之前都需要(由另一位管理员)批准。我已经搜索了一段时间但没有答案。那么这个流程的最佳解决方案是什么?
我可以创建与 Abp 表(dbo.AbpUser_Temp
等)相同的表,并且所有更改都将存储在这些表中吗?有没有更好的解决办法?
示例:在应用程序中,Admin1 创建了一个名为 User1 的用户。但是这个用户在Admin2批准之前不能登录应用程序。
用于开发
- 分开的暂存和生产环境。在一个机器上进行开发、测试、审查,然后部署到生产机器上。简单、有效且与语言无关的建议。
自 ASP.NET 包含样板框架 Entity Framework。您还可以利用迁移。
- 完成开发工作并要求您 "update-database" 后,您的 SOP 应该是让管理员审查将要提交的(相对简单的)迁移。
希望对您有所帮助。
申请流程
可能有很多方法可以实际实现这一点,所以我将介绍一个简单的方法来让您的想法流畅,但请记住:您需要实现两人完整性的方式必须适合您的操作程序应该如何工作,而不是相反。开发不驱动业务运营,业务用例驱动开发。
扩展现有 Identity*
classes。示例:ApplicationUser
class(可能命名不同,但它派生自 IdentityUser
- 创建 2 个必须且只能由管理员'on'
设置的标志(布尔字段)
- 单个管理员只能打开 1 个标志。 (这意味着您还必须存储哪个管理员打开了哪个标志。)
- 标志可以存储在现有的
Abp*
table 中,或者您可以创建一个新的 table
- 添加逻辑,除非这两个标志都打开,否则不允许用户登录。
- 示例:
default IdentityUserRole
已识别并注册,但无法登录。一旦两个管理员都打开标志,将用户 IdentityUserRole
提升为允许登录的角色。
简单的工作流程
Example: In the application, Admin1 has created a user named User1. But this user cannot login to the application until he was approved by Admin2.
像这样的简单工作流程可以通过 属性 和方法适当地处理:
public class User : AbpUser<User>
{
public bool IsApproved { get; set; }
public void Approve(User approver)
{
if (approver.Id != CreatorUserId)
{
IsApproved = true;
}
}
}
复杂的工作流程
像 "every change" 这样的复杂工作流可以代替 _Temp
表来执行此操作:
public abstract class ChangeBase : Entity<long>, IExtendableObject
{
public string EntityTypeAssemblyQualifiedName { get; set; }
public string EntityIdJsonString { get; set; }
public long ProposerUserId { get; set; }
public long? ApproverUserId { get; set; }
public string ExtensionData { get; set; }
}
public class Change : ChangeBase
{
[NotMapped]
public Type EntityType => Type.GetType(EntityTypeAssemblyQualifiedName);
[NotMapped]
public object EntityId => JsonConvert.DeserializeObject(EntityIdJsonString, EntityHelper.GetPrimaryKeyType(EntityType));
[NotMapped]
public bool IsApproved => ApproverUserId.HasValue && ApproverUserId != ProposerUserId;
[NotMapped]
public IDictionary<string, string> ChangedPropertyValuePairs => JObject.Parse(ExtensionData).ToObject<Dictionary<string, string>>();
public Change(EntityIdentifier changedEntityIdentifier, long proposerUserId, IDictionary<string, string> changedPropertyValuePairs)
{
EntityTypeAssemblyQualifiedName = changedEntityIdentifier.Type.AssemblyQualifiedName;
EntityIdJsonString = changedEntityIdentifier.Id.ToJsonString();
ProposerUserId = proposerUserId;
ExtensionData = JObject.FromObject(changedPropertyValuePairs).ToString(Formatting.None);
}
public bool Approve(long approverUserId)
{
if (approverUserId != ProposerUserId)
{
ApproverUserId = approverUserId;
return true;
}
return false;
}
}
用法:
public class UserAppService // ...
{
private readonly IRepository<Change, long> _changeRepository;
public UserAppService(
IRepository<User, long> repository,
IRepository<Change, long> changeRepository) // : base(repository)
{
_changeRepository = changeRepository;
}
public void ChangeUserName(long userId, string newUserName)
{
// Validation, etc.
var changedPropertyValuePairs = new Dictionary<string, string> {
{ nameof(User.UserName), newUserName }
};
var change = new Change(
new EntityIdentifier(typeof(User), userId),
AbpSession.GetUserId(),
changedPropertyValuePairs
);
_changeRepository.Insert(change);
}
public void ApproveChange(long changeId)
{
// Validation, etc.
var change = _changeRepository.Get(changeId);
if (change.EntityType == typeof(User) && change.Approve(AbpSession.GetUserId()))
{
var user = Repository.Get((long)change.EntityId);
var changedPropertyValuePairs = change.ChangedPropertyValuePairs;
foreach (var changedProperty in changedPropertyValuePairs.Keys)
{
switch (changedProperty)
{
case nameof(User.UserName):
user.UserName = changedPropertyValuePairs[changedProperty];
break;
// ...
default:
break;
}
}
}
}
我想将 4 眼原则添加到 ASP.NET 样板框架。这意味着对角色、用户等的每项更改在应用到系统之前都需要(由另一位管理员)批准。我已经搜索了一段时间但没有答案。那么这个流程的最佳解决方案是什么?
我可以创建与 Abp 表(dbo.AbpUser_Temp
等)相同的表,并且所有更改都将存储在这些表中吗?有没有更好的解决办法?
示例:在应用程序中,Admin1 创建了一个名为 User1 的用户。但是这个用户在Admin2批准之前不能登录应用程序。
用于开发
- 分开的暂存和生产环境。在一个机器上进行开发、测试、审查,然后部署到生产机器上。简单、有效且与语言无关的建议。
自 ASP.NET 包含样板框架 Entity Framework。您还可以利用迁移。
- 完成开发工作并要求您 "update-database" 后,您的 SOP 应该是让管理员审查将要提交的(相对简单的)迁移。
希望对您有所帮助。
申请流程
可能有很多方法可以实际实现这一点,所以我将介绍一个简单的方法来让您的想法流畅,但请记住:您需要实现两人完整性的方式必须适合您的操作程序应该如何工作,而不是相反。开发不驱动业务运营,业务用例驱动开发。
扩展现有
Identity*
classes。示例:ApplicationUser
class(可能命名不同,但它派生自IdentityUser
- 创建 2 个必须且只能由管理员'on' 设置的标志(布尔字段)
- 单个管理员只能打开 1 个标志。 (这意味着您还必须存储哪个管理员打开了哪个标志。)
- 标志可以存储在现有的
Abp*
table 中,或者您可以创建一个新的 table - 添加逻辑,除非这两个标志都打开,否则不允许用户登录。
- 示例:
default IdentityUserRole
已识别并注册,但无法登录。一旦两个管理员都打开标志,将用户IdentityUserRole
提升为允许登录的角色。
简单的工作流程
Example: In the application, Admin1 has created a user named User1. But this user cannot login to the application until he was approved by Admin2.
像这样的简单工作流程可以通过 属性 和方法适当地处理:
public class User : AbpUser<User>
{
public bool IsApproved { get; set; }
public void Approve(User approver)
{
if (approver.Id != CreatorUserId)
{
IsApproved = true;
}
}
}
复杂的工作流程
像 "every change" 这样的复杂工作流可以代替 _Temp
表来执行此操作:
public abstract class ChangeBase : Entity<long>, IExtendableObject
{
public string EntityTypeAssemblyQualifiedName { get; set; }
public string EntityIdJsonString { get; set; }
public long ProposerUserId { get; set; }
public long? ApproverUserId { get; set; }
public string ExtensionData { get; set; }
}
public class Change : ChangeBase
{
[NotMapped]
public Type EntityType => Type.GetType(EntityTypeAssemblyQualifiedName);
[NotMapped]
public object EntityId => JsonConvert.DeserializeObject(EntityIdJsonString, EntityHelper.GetPrimaryKeyType(EntityType));
[NotMapped]
public bool IsApproved => ApproverUserId.HasValue && ApproverUserId != ProposerUserId;
[NotMapped]
public IDictionary<string, string> ChangedPropertyValuePairs => JObject.Parse(ExtensionData).ToObject<Dictionary<string, string>>();
public Change(EntityIdentifier changedEntityIdentifier, long proposerUserId, IDictionary<string, string> changedPropertyValuePairs)
{
EntityTypeAssemblyQualifiedName = changedEntityIdentifier.Type.AssemblyQualifiedName;
EntityIdJsonString = changedEntityIdentifier.Id.ToJsonString();
ProposerUserId = proposerUserId;
ExtensionData = JObject.FromObject(changedPropertyValuePairs).ToString(Formatting.None);
}
public bool Approve(long approverUserId)
{
if (approverUserId != ProposerUserId)
{
ApproverUserId = approverUserId;
return true;
}
return false;
}
}
用法:
public class UserAppService // ...
{
private readonly IRepository<Change, long> _changeRepository;
public UserAppService(
IRepository<User, long> repository,
IRepository<Change, long> changeRepository) // : base(repository)
{
_changeRepository = changeRepository;
}
public void ChangeUserName(long userId, string newUserName)
{
// Validation, etc.
var changedPropertyValuePairs = new Dictionary<string, string> {
{ nameof(User.UserName), newUserName }
};
var change = new Change(
new EntityIdentifier(typeof(User), userId),
AbpSession.GetUserId(),
changedPropertyValuePairs
);
_changeRepository.Insert(change);
}
public void ApproveChange(long changeId)
{
// Validation, etc.
var change = _changeRepository.Get(changeId);
if (change.EntityType == typeof(User) && change.Approve(AbpSession.GetUserId()))
{
var user = Repository.Get((long)change.EntityId);
var changedPropertyValuePairs = change.ChangedPropertyValuePairs;
foreach (var changedProperty in changedPropertyValuePairs.Keys)
{
switch (changedProperty)
{
case nameof(User.UserName):
user.UserName = changedPropertyValuePairs[changedProperty];
break;
// ...
default:
break;
}
}
}
}