2、在基类中实现用户操作日志记录处理

当我们对数据进行重要的更改和调整时,我们经常需要跟踪和记录用户操作日志。一般来说,重要的表记录的插入、修改、删除都需要记录。由于用户操作日志会带来一定的额外消耗,我们决定通过配置记录这些业务数据的重要调整。本文介绍了如何在基于 的开发框架中实现用户操作日志的配置设置,并根据配置信息自动实现用户操作日志。

1、用户操作日志记录的配置处理

如前所述,由于用户操作日志会带来一定的额外消耗,我们通过配置文档来决定对那些业务数据进行重要调整。

首先我们在系统中定义一个用户操作日志记录表和一个操作日志配置信息表,系统根据配置记录重要的修改和调整信息。

列表显示信息如下

有了这些信息记录,在操作基类函数中,我们可以通过判断实体类信息中的重要设置是否被插入、更新或删除来决定记录它们的操作日志信息。

下表记录了部分表的添加、修改、删除,以及一些重要的系统操作日志信息,如“密码重置”、“密码修改”、“用户过期设置”等操作日志。

2、在基类中实现用户操作日志处理

以上界面展示了如何配置,自动记录用户与某项业务相关的重要操作记录的界面。系统之所以能记录相关信息,是因为在基类函数中定义了相关逻辑。根据配置逻辑写入插入对象的详细信息、修改对象的变化率记录、删除对象的详细信息,并对一些重要的处理,如重置密码等进行自定义信息记录。

让我们看看这些操作在基类中是如何处理的。

比如我们删除记录时,有时会收到实体类的ID,有时会收到实体类,那么针对这些情况,我们会进行相应的日志处理,如下代码所示。

        /// 
        /// 删除指定ID的对象
        /// 
        /// 记录ID
        /// 
        public virtual async Task<bool> DeleteAsync(TEntity input)
        {
            await OnOperationLog(input, OperationLogTypeEnum.删除);
            return await EntityDb.DeleteAsync(input);
        }
        /// 
        /// 删除指定ID的对象
        /// 
        /// 记录ID
        /// 
        public virtual async Task<bool> DeleteAsync(TKey id)
        {
            await OnOperationLog(id, OperationLogTypeEnum.删除);
            return await EntityDb.DeleteByIdAsync(id);
        }

其中我们根据日志的操作定义一个枚举对象,如下图。

    /// 
    /// 操作日志的枚举类型
    /// 
    public enum OperationLogTypeEnum
    {
        增加,
        删除,
        修改
    }

对于被删除记录的Id,我们需要将其转换为对应的实体类,然后记录下来。

        /// 
        /// 统一处理实体类的日志记录
        /// 
        /// 实体对象Id
        /// 记录类型
        /// 
        protected override async Task OnOperationLog(TKey id, OperationLogTypeEnum logType)
        {
            var enableLog = await CheckOperationLogEnable(logType);
            if (enableLog)
            {
                var input = await this.EntityDb.GetByIdAsync(id);
                if (input != null)
                {
                    string note = JsonConvert.SerializeObject(input, Formatting.Indented);
                    await AddOperationLog(logType.ToString(), note);
                }
            }
            await Task.CompletedTask;//结束处理
        }

ble用于判断是否有指定操作类型的配置信息,如果有,记录操作日志。

我们根据实体类的全名来判断。如果有指定的操作设置,返回True,如下图。 (泛型约束的全称可以在基类中判断)

        /// 
        /// 判断指定的类型(增加、删除、修改)是否配置启用
        /// 
        /// 指定的类型(增加、删除、修改)
        /// 
        protected async Task<bool> CheckOperationLogEnable(OperationLogTypeEnum logType)
        {
            var result = false;
            string tableName = typeof(TEntity).FullName;//表名称或者实体类全名
            var settingInfo = await this._logService.GetOperationLogSetting(tableName);
            if (settingInfo != null)
            {
                if (logType == OperationLogTypeEnum.修改)
                {
                    result = settingInfo.UpdateLog > 0;
                }
                else if (logType == OperationLogTypeEnum.增加)
                {
                    result = settingInfo.InsertLog > 0;
                }
                else if (logType == OperationLogTypeEnum.删除)
                {
                    result = settingInfo.DeleteLog > 0;
                }
            }
            return result;
        }

对于插入记录,我们也可以同时判断和处理日志信息。

        /// 
        /// 创建对象
        /// 
        /// 实体对象
        /// 
        public virtual async Task<bool> InsertAsync(TEntity input)
        {
            SetIdForGuids(input);//如果Id为空,设置有序的GUID值
            await OnOperationLog(input, OperationLogTypeEnum.增加);//判断并记录日志
            return await EntityDb.InsertAsync(input);
        }

对于原始记录的更新,只需要接收更新前的对象,然后进行判断处理即可。

        /// 
        /// 更新对象
        /// 
        /// 实体对象
        /// 
        public virtual async Task<bool> UpdateAsync(TEntity input)
        {
            SetIdForGuids(input);//如果Id为空,设置有序的GUID值
            await OnOperationLog(input, OperationLogTypeEnum.修改);//判断并记录日志
            return await EntityDb.UpdateAsync(input);
        }

为了比较两者,我们需要提供一个重载的操作日志方法来记录信息。

由于修改了信息,我们需要比较两条不同记录的差异信息,这样才能友好地判断信息发生了变化。即需要获取更新前后两个实体对象的属性差异信息。

        /// 
        /// 统一处理实体类的日志记录
        /// 
        /// 实体对象
        /// 记录类型
        /// 
        protected override async Task OnOperationLog(TEntity input, OperationLogTypeEnum logType)
        {
            var enableLog = await CheckOperationLogEnable(logType);
            if (enableLog && input != null)
            {
                if (logType == OperationLogTypeEnum.修改)
                {
                    var oldInput = await this.EntityDb.GetByIdAsync(input.Id);
                    //对于更新记录,需要判断更新前后两个对象的差异信息
                    var changeNote = oldInput.GetChangedNote(input); //计算差异的部分
                    if (!string.IsNullOrEmpty(changeNote))
                    {
                        await AddOperationLog(logType.ToString(), changeNote);
                    }
                }
                else
                {
                    //对于插入、删除的操作,只需要记录对象的信息
                    var note = JsonConvert.SerializeObject(input, Formatting.Indented);
                    await AddOperationLog(logType.ToString(), note);
                }
            }
            await Task.CompletedTask;//结束处理
        }

而对于差异信息,我可以定义一个扩展函数来处理它们的差异信息,如下所示。

    /// 
    /// 对象属性的处理操作
    /// 
    public static class ObjectExtensions
    {
        /// 
        /// 对比两个属性的差异信息
        /// 
        /// 对象类型
        /// 对象实例1
        /// 对象实例2
        /// 
        public static List DetailedCompare(this T val1, T val2)
        {
            var propertyInfo = val1.GetType().GetProperties();
            return propertyInfo.Select(f => new Variance
            {
                Property = f.Name,
                ValueA = (f.GetValue(val1, null)?.ToString()) ?? "", //确保不为null
                ValueB = (f.GetValue(val2, null)?.ToString()) ?? ""
            })
            .Where(v => !v.ValueA.Equals(v.ValueB)) //调用内置的Equals判断
            .ToList();
        }
        /// 
        /// 把两个对象的差异信息转换为JSON格式
        /// 
        /// 对象类型
        /// 对象实例1
        /// 对象实例2
        /// 
        public static string GetChangedNote(this T oldVal, T newVal)
        {
            var specialList = new List<string> { "edittime", "createtime", "lastupdated" };
            var list = DetailedCompare(oldVal, newVal);
            var newList = list.Select(s => new { Property = s.Property, OldValue = s.ValueA, NewValue = s.ValueB })
                             .Where(s=> !specialList.Contains(s.Property.ToLower())).ToList();//排除某些属性
            
            string note = null;
            if (newList?.Count > 0)
            {
                //增加一个ID属性记录显示
                var id = EntityHelper.GetEntityId(oldVal)?.ToString();
                newList.Add(new { Property = "Id", OldValue = id, NewValue = id });
                note = JsonConvert.SerializeObject(newList, Formatting.Indented);
            }
            return note;
        }
        public class Variance
        {
            public string Property { get; set; }
            public string ValueA { get; set; }
            public string ValueB { get; set; }
        }
    }

这样我们就可以通过LINQ生成两个对象的差异信息,可以用来记录变更操作的信息,最后可以得到类似如下界面差异信息的提示。

就是获取相似字符串的差异信息。

[
  {
    "Property": "PID",
    "OldValue": "-1",
    "NewValue": "0"
  },
  {
    "Property": "OfficePhone",
    "OldValue": "",
    "NewValue": "18620292076"
  },
  {
    "Property": "WorkAddr",
    "OldValue": "广州市白云区同和路**小区**号",
    "NewValue": "广州市白云区同和路330号君立公寓B栋1803房"
  },
  {
    "Property": "Id",
    "OldValue": "1",
    "NewValue": "1"
  }
]

最后一个属性Id被强制加入到变化列表中,因为如果没有记录Id,就不清楚是哪条记录发生了变化。

这样我们就可以记录增删改查的重要操作了,而且由于是基类实现,我们只需要配置系统确定需要记录哪些业务类就可以自动记录重要的日志。

此外,我们还在分类中发现了其他一些不同分类的重要操作日志,如重置密码、修改密码、用户过期设置等,对于这些操作,我们可以为这些处理调用提供接口。

        /// 
        /// 设置用户的过期与否
        /// 
        /// 用户ID
        /// 是否禁用,true为禁用,否则为启用
        public async Task<bool> SetExpire(int userId, bool expired)
        {
            bool result = false;
            var info = await this.GetAsync(userId);
            if (info != null)
            {
                info.IsExpire = expired;
                result = await this.UpdateAsync(info);
                if (result)
                {
                    //记录用户修改密码日志
                    string note = string.Format("{0} {1}了用户【{2}】的账号", this.CurrentApiUser.FullName, expired ? "禁用" : "启用", info.Name);
                    await base.AddOperationLog("用户过期设置", note);
                }
            }
            return result;
        }

我们在其中调用基类插入指定类型的记录和日志信息。通过自定义类型和自定义日志信息,我们可以灵活处理一些重要的日志记录。

系列文章:

《基于(1)–基础框架类的设计与使用的开发框架逐步介绍》

《基于开发框架的分步介绍(2)–基于中间表的查询处理》

《基于(3)–实现代码生成工具集成开发的开发框架分步介绍》

《基于(4)–数据访问基类中GUID主键自动赋值的开发框架分步介绍》

《基于(5)的开发框架分步介绍–使用接口注入在服务层实现IOC控制倒置》

《基于(6)– users into the base class ) 的开发框架分步介绍”

《基于(7)–使用文件上传模块[]中的选项模式处理常规上传和FTP文件上传)的开发框架逐步介绍”

《基于(8)的开发框架分步介绍–基类函数封装中用户操作日志的实现》

© 版权声明
THE END
喜欢就支持一下吧
点赞55 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片