2

我正在尝试为用户可以登录、更改密码和电子邮件等的系统实施基本审计。

我要审计的函数都在业务层中,我想创建一个审计对象来存储调用函数的日期时间,包括结果。

我最近参加了一个会议,其中一个会议是关于精心设计的 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 对象并将其信息设置在其中从而解决提前返回问题真的很糟糕吗?我的印象是 finally 是用于清理而不是审计。

我的名字是大卫,我只是想成为一个更好的代码。谢谢。

4

2 回答 2

2

我不能说我已经使用过它,但这似乎是Aspect Oriented Programming的候选者。基本上,您可以在每个方法调用中以自动方式为日志/审计/等内容注入代码。

另外,制作 try/catch/finally 块并不理想,但我会计算成本/收益,看看是否值得。如果您可以合理地廉价地重构代码以便您不必使用它,那么就这样做。如果成本过高,我会尝试/最后。我认为很多人都陷入了“最佳解决方案”中,但时间/金钱总是受到限制,所以做“有意义”的事情。

于 2011-06-30T13:54:37.060 回答
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();
于 2011-06-30T13:47:39.770 回答