我正在尝试为用户可以登录,更改密码和电子邮件等系统实施基本审核。
我想要审计的函数都在业务层中,我想创建一个Audit对象,该对象存储调用函数的日期时间,包括结果。
我最近参加了一个会议,其中一个会议是关于精心设计的Web应用程序,我正在努力实现一些想法。基本上我使用Enum返回函数的结果并使用switch语句来更新该层中的UI。这些函数使用早期返回,不会留下任何时间来创建,设置和保存审计。
我的问题是,其他人在审核业务功能时会采取什么方法,如果你有像我这样的功能,你会采取什么方法(如果你说我会听,我会听,但我会脾气暴躁)。
代码看起来有点像这样:
function Login(string username, string password)
{
User user = repo.getUser(username, password);
if (user.failLogic1) { return failLogic1Enum; }
if (user.failLogic2) { return failLogic2Enum; }
if (user.failLogic3) { return failLogic3Enum; }
if (user.failLogic4) { return failLogic4Enum; }
user.AddAudit(new (Audit(AuditTypeEnum LoginSuccess));
user.Save();
return successEnum;
}
我可以扩展if语句以在每个语句中创建一个新的审计但是函数开始变得混乱。我可以在switch语句的UI层进行审计,但这似乎不对。
将它全部用于最终的try catch并使用finally来创建Audit对象并在那里设置它的信息从而解决早期的返回问题真的很糟糕吗?我的印象是,最后是清理而不是审计。
我的名字是大卫,我只是想成为更好的代码。感谢。
答案 0 :(得分:2)
我不能说我已经使用过它,但这似乎是Aspect Oriented Programming的候选人。基本上,您可以在每个方法调用中以自动方式为日志/审计等等注入代码。
另外,做一个try / catch / finally块并不理想,但我会花一个成本/收益来看它是否值得。如果您可以合理地重构代码,以便不必使用它,那就这样做吧。如果成本过高,我会尝试/最终。我认为很多人都会陷入“最佳解决方案”,但时间/金钱总是受到约束,所以做“有道理”的事情。
答案 1 :(得分:1)
枚举的问题是它不是真正可扩展的。如果稍后添加新组件,则审计框架将无法处理新事件。
在我们使用EF的最新系统中,我们为实体命名空间中的审计事件创建了一个基本的POCO:
public class AuditEvent : EntityBase
{
public string Event { get; set; }
public virtual AppUser AppUser { get; set; }
public virtual AppUser AdminUser { get; set; }
public string Message{get;set;}
private DateTime _timestamp;
public DateTime Timestamp
{
get { return _timestamp == DateTime.MinValue ? DateTime.UtcNow : _timestamp; }
set { _timestamp = value; }
}
public virtual Company Company { get; set; }
// etc.
}
在我们的任务层中,我们实现了一个抽象基础AuditEventTask:
internal abstract class AuditEventTask<TEntity>
{
internal readonly AuditEvent AuditEvent;
internal AuditEventTask()
{
AuditEvent = InitializeAuditEvent();
}
internal void Add(UnitOfWork unitOfWork)
{
if (unitOfWork == null)
{
throw new ArgumentNullException(Resources.UnitOfWorkRequired_Message);
}
new AuditEventRepository(unitOfWork).Add(AuditEvent);
}
private AuditEvent InitializeAuditEvent()
{
return new AuditEvent {Event = SetEvent(), Timestamp = DateTime.UtcNow};
}
internal abstract void Log(UnitOfWork unitOfWork, TEntity entity, string appUserName, string adminUserName);
protected abstract string SetEvent();
}
必须实现Log以记录与事件关联的数据,并且实现SetEvent以强制派生任务隐式设置其事件的类型:
internal class EmailAuditEventTask : AuditEventTask<Email>
{
internal override void Log(UnitOfWork unitOfWork, Email email, string appUserName, string adminUserName)
{
AppUser appUser = new AppUserRepository(unitOfWork).Find(au => au.Email.Equals(appUserName, StringComparison.OrdinalIgnoreCase));
AuditEvent.AppUser = appUser;
AuditEvent.Company = appUser.Company;
AuditEvent.Message = email.EmailType;
Add(unitOfWork);
}
protected override string SetEvent()
{
return AuditEvent.SendEmail;
}
}
这里的打嗝是内部基本任务 - 基本任务可以公开,以便以后添加到任务名称空间可以使用它 - 但总的来说,我认为这会给你一个想法。
在实现方面,我们的其他任务决定何时应该进行日志记录,因此在您的情况下:
AuditEventTask task;
if (user.failLogic1) { task = new FailLogin1AuditEventTask(fail 1 params); }
if (user.failLogic2) { task = new FailLogin2AuditEventTask(fail 2 params); }
if (user.failLogic3) { task = new FailLogin3AuditEventTask(etc); }
if (user.failLogic4) { task = new FailLogin4AuditEventTask(etc); }
task.Log();
user.Save();