Winform开发框架之权限管理系统改进的经验总结(4)-一行代码实现表操作日志记录

在前面介绍了几篇关于我的权限系统改进的一些经验总结,本篇继续这一系列主体,介绍如何一行代码实现重要表的操作日志记录。我们知道,在很多业务系统里面,数据是很敏感的,特别对于一些增加、修改、删除等关键的操作,如果能在框架层面的支持基础上,以最少的代码实现重要表的日志记录,那么是一件非常值得庆贺的事情,也能够为我们客户的数据提供重要的日志跟踪,甚至是数据恢复的参考。

1、数据访问层的对象继承关系

首先,为了减少重复代码的编写,合理的继承关系是必要的,我们需要在数据访问层上建立合理的继承关系,如下是我的Winform开发框架的继承关系。每个数据访问对象(如ItemDetail数据访问对象)都继承一个抽象基类AbstractBaseDAL和一个IBaseDAL基类接口,同时它也有自己特殊的业务接口,如IItemDetail,关系如下所示。

有了上面的继承关系,我们就可以把常规的数据库重要操作(增删改)放到一个高一级的层次上去解决这个问题,而不需要在每个数据访问层的业务类来实现。

2、操作日志记录事件的定义和使用

为了更好实现数据操作日志的记录,我们以事件方式来触发操作日志的记录,事件的具体记录实现,可以交给外部来记录处理。如果事件被外部赋值了,那么就可以在底层触发这个事件记录,记录事件的定义代码在抽象基类进行定义,如下所示。

    /// 定义一个记录操作日志的事件处理
    /// </summary>
    /// <param name="userId">操作的用户ID</param>
    /// <param name="tableName">操作表名称</param>
    /// <param name="operationType">操作类型:增加、修改、删除</param>
    /// <param name="note">操作的详细记录信息</param>
    /// <returns></returns>
    public delegate bool OperationLogEventHandler(string userId, string tableName, string operationType, string note, DbTransaction trans = null); 

    /// <summary>
    /// 数据访问层的超级基类,所有数据库的数据访问基类都继承自这个超级基类,包括Oracle、SqlServer、Sqlite、MySql、Access等
    /// </summary>
    public abstract class AbstractBaseDAL<T> where T : BaseEntity, new()
    {
        #region 构造函数

        protected string dbConfigName = ""; //数据库配置名称
        protected string parameterPrefix = "@";//数据库参数化访问的占位符
        protected string safeFieldFormat = "[{0}]";//防止和保留字、关键字同名的字段格式,如[value]
        protected string tableName;//需要初始化的对象表名
        protected string primaryKey;//数据库的主键字段名
        protected string sortField;//排序字段
        protected bool isDescending = true;//是否为降序
        protected string selectedFields = " * ";//选择的字段,默认为所有(*)
        public event OperationLogEventHandler OnOperationLog;//定义一个操作记录的事件处理

.....................

以上是抽象基类AbstractBaseDAL的部分代码,上面代码定义了一个操作记录的委托和事件对象来处理操作日志的记录,通过委托的定义,我们可以规定具体的事件接口定义,并在抽象基类的底层构造这些参数的数值,传递给外部的对象进行处理。

那么我们是如何在底层操作构造这些信息的呢?

其实就是在相应的重要操作接口函数上调用这个定义的事件。我们可以在抽象基类的插入、修改、删除等接口上调用事件进行处理即可,为了更好处理相关数据的构造逻辑,我们把调用OnOperationLog的事件封装到一个单独的函数里面进行处理,如下所示是底层更新操作的代码,通过增加一个OperationLogOfUpdate来实现数据日志的事件处理。

        /// <summary>
        /// 更新对象属性到数据库中
        /// </summary>
        /// <param name="obj">指定的对象</param>
        /// <param name="primaryKeyValue">主键的值</param>
        /// <param name="trans">事务对象</param>
        /// <returns>执行成功返回<c>true</c>,否则为<c>false</c>。</returns>
        public virtual bool Update(T obj, object primaryKeyValue, DbTransaction trans = null)
        {
            ArgumentValidation.CheckForNullReference(obj, "传入的对象obj为空");

            OperationLogOfUpdate(obj, primaryKeyValue, trans);//根据设置记录操作日志

            Hashtable hash = GetHashByEntity(obj);
            return Update(primaryKeyValue, hash, trans);
        }

然后我们在具体的事件处理封装函数OnOperationLog的里面添加处理逻辑即可,一般事件的标准处理为如下代码。

        /// <summary>
        /// 修改操作的日志记录
        /// </summary>
        /// <param name="id">记录ID</param>
        /// <param name="obj">数据对象</param>
        /// <param name="trans">事务对象</param>
        protected virtual void OperationLogOfUpdate(T obj, object id, DbTransaction trans = null)
        {
            if (OnOperationLog != null)
            {
                    ...............................//构造相关参数
                    OnOperationLog(userId, this.tableName, operationType, note, trans);
                }
            }
        }

我们知道,一般操作日志都会记录是谁进行操作的,然后把它写到日志里面,并把操作的内容可读化即可,那么在更新的时候,我们如何知道是谁操作的对象呢?因为我们没有传递具体的用户ID等标识的啊。

这个问题挺头痛,如果增加多一个参数,那么就得修改很多相关的调用逻辑,这个明显不太符合我们简约的风格,因此最好另寻其他方式来实现这个人员身份记录的问题。

我们知道,一般插入、更新操作,都是带一个操作对象的,这个操作对象是一个实体类,基类是BaseEntity,那么我们可以在它的身上定义多一个属性,这个属性不参数数据的保存,只是作为参数的传递和识别而已,实体类基类的代码如下所示。

    /// <summary>
    /// 框架实体类的基类
    /// </summary>
    [DataContract]
    public class BaseEntity
    {
        private string m_CurrentLoginUserId;

        /// <summary>
        /// 当前登录用户ID。该字段不保存到数据表中,只用于记录用户的操作日志。
        /// </summary>
        [DataMember]
        public string CurrentLoginUserId
        {
            get { return m_CurrentLoginUserId; }
            set { m_CurrentLoginUserId = value; }
        }
    }
}

有了这个信息,我们就可以在刚才的事件处理逻辑上进行获取用户的ID操作了。

string userId = obj.CurrentLoginUserId;

下一个问题是,如何把操作的信息可读化,我们知道,一般操作只是对部分字段进行修改,那么我们一般也不需要把所有的字段信息都弄出来显示,只需要显示那些修改的即可。

为了实现这个数据的差异化显示,我们需要在更新操作之前进行获取数据库的对象信息,然后和将要进行更新的对象进行对比,把差异的信息作为备注信息记录下来即可,具体逻辑如下所示。

        /// <summary>
        /// 修改操作的日志记录
        /// </summary>
        /// <param name="id">记录ID</param>
        /// <param name="obj">数据对象</param>
        /// <param name="trans">事务对象</param>
        protected virtual void OperationLogOfUpdate(T obj, object id, DbTransaction trans = null)
        {
            if (OnOperationLog != null)
            {
                string operationType = "修改";
                string userId = obj.CurrentLoginUserId;

                Hashtable recordField = GetHashByEntity(obj);
                Dictionary<string, string> dictColumnNameAlias = GetColumnNameAlias();

                T objInDb = FindByID(id, trans);
                if (objInDb != null)
                {
                    Hashtable dbrecordField = GetHashByEntity(objInDb);//把数据库里的实体对象数据转换为哈希表

                    StringBuilder sb = new StringBuilder();
                    foreach (string field in recordField.Keys)
                    {
                        string newValue = recordField[field].ToString();
                        string oldValue = dbrecordField[field].ToString();
                        if (newValue != oldValue)//只记录变化的内容
                        {
                            string columnAlias = "";
                            bool result = dictColumnNameAlias.TryGetValue(field, out columnAlias);
                            if (result && !string.IsNullOrEmpty(columnAlias))
                            {
                                columnAlias = string.Format("({0})", columnAlias);//字段中文名称前,增加一个括号显示,方便区分显示
                            }

                            sb.AppendLine(string.Format("{0}{1}:", field, columnAlias));
                            sb.AppendLine(string.Format("\t {0} -> {1}", dbrecordField[field], recordField[field]));
                            sb.AppendLine();
                        }
                    }
                    sb.AppendLine();
                    string note = sb.ToString();

                    OnOperationLog(userId, this.tableName, operationType, note, trans);
                }
            }
        }

上面是更新操作的日志记录处理,其他的插入、删除等操作类似这样的操作方式,再次不在赘述。

3、业务层对操作日志信息的处理

上面的代码只是实现了对底层操作的信息记录并传递给操作日志的记录事件,并没有知道上层是如何处理事件信息的记录的,这个问题留给上层去处理。

为了实现这个信息的记录,我们在权限系统里面增加一个单独的数据库表如T_ACL_OperationLog表用来专门记录这些信息的。

上层的处理逻辑是获取用户ID的登陆信息,包括用户名、用户IP地址、Mac地址信息等,这些信息一旦用户登陆到系统就会发生了,所以可以方便获取到,然后就是把这些信息作为一个数据库记录写入数据库表即可。

                    OperationLogInfo info = new OperationLogInfo();
                    info.TableName = tableName;
                    info.OperationType = operationType;
                    info.Note = note;
                    info.CreateTime = DateTime.Now;

                    if (!string.IsNullOrEmpty(userId))
                    {
                        UserInfo userInfo = BLLFactory<User>.Instance.FindByID(userId, trans);
                        if (userInfo != null)
                        {
                            info.User_ID = userId;
                            info.LoginName = userInfo.Name;
                            info.FullName = userInfo.FullName;
                            info.Company_ID = userInfo.Company_ID;
                            info.CompanyName = userInfo.CompanyName;
                            info.MacAddress = userInfo.CurrentMacAddress;
                            info.IPAddress = userInfo.CurrentLoginIP;
                        }
                    }

                    return BLLFactory<OperationLog>.Instance.Insert(info, trans);

因为这些记录的操作都是一样的,为了更方便,我们把这些逻辑封装在一个静态的方法里面,然后所有需要记录操作日志的,在业务对象里面增加一行代码,就可以轻松实现日志记录了,具体代码如下所示。

    /// <summary>
    /// 部门机构信息
    /// </summary>
    public class OU : BaseBLL<OUInfo>
    {
        private IOU ouDal;

        /// <summary>
        /// 构造函数
        /// </summary>
        public OU() : base()
        {
            base.Init(this.GetType().FullName, System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);
            baseDal.OnOperationLog += new OperationLogEventHandler(WHC.Security.BLL.OperationLog.OnOperationLog);//如果需要记录操作日志,则实现这个事件

            this.ouDal = baseDal as IOU;
        }

这样数据访问类baseDal一旦事件初始化,那么就会在底层进行触发,然后交给事件的处理逻辑(上层操作)进行处理了。

为了更好控制用户的增加、修改、删除的相关事件,我们可以通过一个配置表进行登记处理,然后根据配置表的参数来决定记录那些信息,这些就是细化的问题了。

4、我的Winform开发框架的权限系统模块里面对于操作日志的支持

在我的Winform开发框架里面,权限系统是其中的一个基础部分,因此也根据上面的逻辑实现了对操作日志的参数配置和记录显示,方便对业务系统所有表的操作记录进行跟踪和处理。

通过一行代码就能实现业务表的日志记录,对我们开发新的业务模块,效率可以提高很多,同时也能给客户提供更好的数据支持服务。通过在权限系统模块里面配置参数和显示操作日志记录,能够给业务开发提供基础性的开发框架支持。

下面是我的Winform开发框架的权限系统模块的一些功能 截图,供参考学习。

双击打开记录的明细,可以看到操作记录的明细显示。

参数配置界面如下所示。

以上显示只是基于权限系统进行日志的记录,当然整个业务系统框架都可以提供上面的记录操作,因为它们所有的数据访问基类都是继承自同一个抽象对象基类的。把这个模块集成在权限系统里面,和登陆日志一样,是供基础性的记录和查阅的,和业务不太相关。 

最后附上Winform开发框架的一个功能总结图形,Winform开发框架的主要功能概览如下图所示。

本文转自博客园伍华聪的博客,原文链接:Winform开发框架之权限管理系统改进的经验总结(4)-一行代码实现表操作日志记录,如需转载请自行联系原博主。

时间: 2024-10-03 23:39:33

Winform开发框架之权限管理系统改进的经验总结(4)-一行代码实现表操作日志记录的相关文章

Winform开发框架之权限管理系统改进的经验总结(4)--用户分级管理

在实际的系统应用环境中,用户的分级管理一般也是比较常见的功能,小的业务系统可以不需要,但是一般涉及到集团.分子公司.或者是事业单位里面的各个处室或者某某局的人员管理,这些分级管理就显得比较必要,否则单靠管理员来处理账号的事情,是比较麻烦一点的.分级管理就是让不同层次.不同机构的人员实现一定的自治管理,如分公司的人员有专门的管理员,各地区的处室或者某某局实现内部人员的创建.调整.角色分配等工作.本篇随笔主要介绍在我的权限系统中如何实现人员的分级管理的,给大家提供相应的思路和样例参考. 1.用户分级

Winform开发框架之权限管理系统改进的经验总结(2)-用户选择界面的设计

在上篇总结随笔<Winform开发框架之权限管理系统改进的经验总结(1)-TreeListLookupEdit控件的使用>介绍了权限管理模块的用户管理部分,其中主要介绍了其中的用户所属公司.所属部门.直属经理(人员列表)的几级数据级联的展示,通过引入TreeListLookupEdit控件,能增强用户的体验效果.本篇继续介绍权限系统模块中的一些闪光点,介绍组织机构管理里面选择用户的界面设计和实现,用户选择在很多场合会用到,如组织机构的用户选择,角色里面的用户选择,或者流程里面的用户选择等用途.

Winform开发框架之权限管理系统改进的经验总结(1)-TreeListLookupEdit控件的使用

最近一直在做一些技术性的研究和框架改进工作,博客也落下好几天没有更新了,也该是时候静下心来,总结这段时间的一些技术改进的经验了.和上一阶段的CRM系统开发和技术研究一样,我都喜欢在一个项目或者模块完成后,做一些相关的总结性工作,记录下前一阶段的技术脚印,希望给自己留下一个脚印快照,同时给读者了解自己的技术动向外,也有所收获.本随笔主要介绍在下拉列表中展示一个列表,以便实现数据结构的良好展示,并能快速选定所需的节点,这个就是TreeListLookupEdit控件的使用. 1.界面效果展示 首先我

Winform开发框架之权限管理系统改进的经验总结(3)-系统登录黑白名单的实现

在一般的权限系统里面,可能经常会看到系统的黑名单或者白名单的拦截功能.在一般权限系统里面,常见的黑名单就是禁止用户在某些IP上登录系统,白名单就是允许用户只在某些IP上登录系统.本随笔主要介绍在我的权限系统里面,如何实现这个黑白名单的功能,以及介绍在其中应用到的IP对比操作,IP段判断等操作代码. 1.黑白名单的配置 要完成黑名单的拦截和白名单的放行,我们需要进行名单的配置操作,我们把相关的配置放到列表里面进行展示,可以添加多个黑名单或者白名单,如下界面所示. 可以单击新建按钮进行添加一条记录,

Winform开发框架之权限管理系统改进的经验总结(4)

一行代码实现表操作日志记录 在前面介绍了几篇关于我的权限系统改进的一些经验总结,本篇继续这一系列主体,介绍如何一行代 码实现重要表的操作日志记录.我们知道,在很多业务系统里面,数据是很敏感的,特别对于一些增加 .修改.删除等关键的操作,如果能在框架层面的支持基础上,以最少的代码实现重要表的日志记录, 那么是一件非常值得庆贺的事情,也能够为我们客户的数据提供重要的日志跟踪,甚至是数据恢复的参 考. 1.数据访问层的对象继承关系 首先,为了减少重复代码的编写,合理的继承关系是必要的,我们需要在数据访

Winform开发框架之权限管理系统改进的经验总结(1)TreeListLoo

最近一直在做一些技术性的研究和框架改进工作,博客也落下好几天没有更新了,也该是时候静下心 来,总结这段时间的一些技术改进的经验了.和上一阶段的CRM系统开发和技术研究一样,我都喜欢在一 个项目或者模块完成后,做一些相关的总结性工作,记录下前一阶段的技术脚印,希望给自己留下一个 脚印快照,同时给读者了解自己的技术动向外,也有所收获.本随笔主要介绍在下拉列表中展示一个列 表,以便实现数据结构的良好展示,并能快速选定所需的节点,这个就是TreeListLookupEdit控件的使 用. 1.界面效果展

Winform开发框架之权限管理系统改进的经验总结(5) 用户分级管理

在实际的系统应用环境中,用户的分级管理一般也是比较常见的功能,小的业务系统可以不需要,但 是一般涉及到集团.分子公司.或者是事业单位里面的各个处室或者某某局的人员管理,这些分级管理 就显得比较必要,否则单靠管理员来处理账号的事情,是比较麻烦一点的.分级管理就是让不同层次. 不同机构的人员实现一定的自治管理,如分公司的人员有专门的管理员,各地区的处室或者某某局实现 内部人员的创建.调整.角色分配等工作.本篇随笔主要介绍在我的权限系统中如何实现人员的分级管 理的,给大家提供相应的思路和样例参考. 1

Winform开发框架之权限管理系统改进经验总结(2)用户选择界面的设计

在上篇总结随笔<Winform开发框架之权限管理系统改进的经验总结(1)-TreeListLookupEdit控件 的使用>介绍了权限管理模块的用户管理部分,其中主要介绍了其中的用户所属公司.所属部门.直属 经理(人员列表)的几级数据级联的展示,通过引入TreeListLookupEdit控件,能增强用户的体验效果 .本篇继续介绍权限系统模块中的一些闪光点,介绍组织机构管理里面选择用户的界面设计和实现,用 户选择在很多场合会用到,如组织机构的用户选择,角色里面的用户选择,或者流程里面的用户选择

Winform开发框架之权限管理系统改进经验总结(3)系统登录黑白名单的实现

在一般的权限系统里面,可能经常会看到系统的黑名单或者白名单的拦截功能.在一般权限系统里面 ,常见的黑名单就是禁止用户在某些IP上登录系统,白名单就是允许用户只在某些IP上登录系统.本随 笔主要介绍在我的权限系统里面,如何实现这个黑白名单的功能,以及介绍在其中应用到的IP对比操作 ,IP段判断等操作代码. 1.黑白名单的配置 要完成黑名单的拦截和白名单的放行,我们需要进行名单的配置操作,我们把相关的配置放到列表里 面进行展示,可以添加多个黑名单或者白名单,如下界面所示. 开发框架之权限管理系统改进