通过思考大概清楚系统操作日志的设计,以下是其UML图:
通过上图,我们可以了解知道该UML主要由三个表组成,其中一个主表LogSetting和两个从表分别是LogOperation和LogSettingDetail。
那么怎么样才能通过这样的设计来现实我们的日志功能呢?
其实一开始我就觉得通过.net的反射功能可以很简单、很方便的实现这个功能,所以我就顺着一个思路来实现她;通过反射动态的获取Model实体的属性,然后再根据LogSettingDetail配置来匹配所要记录的字段信息。
先来主要的代码吧,发现将思想用文字表达出来还是较困难的,代码比较直接:
代码的实现
using System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using BLL.Sys; using System.Collections.Generic; using System.Collections.Specialized; using System.Text; using System.Reflection; /// <summary> /// LogManager 的摘要说明 /// </summary> public class LogManager<T> where T : new() { #region Constructor /// <summary> /// 日志管理构造函数 /// </summary> public LogManager() { tableName = typeof(T).Name; Model.Sys.LogSetting model = GetLogSetting(tableName); if (model != null) { businessName = model.BusinessName; logID = model.LogID; primaryKey = model.PrimaryKey; urlTemplate = model.UrlTemplate; deleteScriptTemplate = model.DeleteScriptTemplate; updateScriptTemplate = model.UpdateScriptTemplate; } else { throw new ArgumentNullException("日志设置为空!"); } } /// <summary> /// /// 日志管理构造函数 /// </summary> /// </summary> /// <param name="tableName">表名</param> /// <param name="businessName">业务名称</param> public LogManager(string tableName, string businessName) { this.tableName = tableName; this.businessName = businessName; Model.Sys.LogSetting model = GetLogSetting(tableName, businessName); if (model != null) { logID = model.LogID; primaryKey = model.PrimaryKey; urlTemplate = model.UrlTemplate; deleteScriptTemplate = model.DeleteScriptTemplate; updateScriptTemplate = model.UpdateScriptTemplate; } else { throw new ArgumentNullException("日志设置为空!"); } } #endregion #region Properties private int logID; private string tableName; private string businessName; private string primaryKey; private string urlTemplate; private string deleteScriptTemplate; private string updateScriptTemplate; /// <summary> /// 日志设置实体列表 /// </summary> public List<Model.Sys.LogSetting> LogSettingList { get { System.Web.Caching.Cache cache = HttpRuntime.Cache; List<Model.Sys.LogSetting> list = cache["LogSettingList"] as List<Model.Sys.LogSetting>; if (list != null && list.Count > 0) { return list; } else { LogSetting bll = new LogSetting(); list = bll.GetModelList(string.Empty); cache["LogSettingList"] = list; return list; } } set { System.Web.Caching.Cache cache = HttpRuntime.Cache; cache["LogSettingList"] = null; } } /// <summary> /// 日志设置明细 /// </summary> public List<Model.Sys.LogSettingDetail> LogSettingDetail { get { System.Web.Caching.Cache cache = HttpRuntime.Cache; List<Model.Sys.LogSettingDetail> list = cache["LogSettingDetail"] as List<Model.Sys.LogSettingDetail>; if (list != null && list.Count > 0) { return list; } else { LogSettingDetail bll = new LogSettingDetail(); list = bll.GetModelList(string.Empty); cache["LogSettingDetail"] = list; return list; } } set { System.Web.Caching.Cache cache = HttpRuntime.Cache; cache["LogSettingDetail"] = null; } } #endregion #region Method /// <summary> /// 通过logId获取日志设置明细 /// </summary> /// <param name="logId">日志设置编号</param> /// <returns></returns> private List<Model.Sys.LogSettingDetail> GetLogSettingDetails(int logId) { if (logId == 0) throw new ArgumentNullException("LogID为空"); List<Model.Sys.LogSettingDetail> list = new List<Model.Sys.LogSettingDetail>(); foreach (Model.Sys.LogSettingDetail var in LogSettingDetail) { if (var.LogID == logId) list.Add(var); } return list; } /// <summary> /// 通过tableName和businessName来获取日志设置对象 /// </summary> /// <param name="tableName"></param> /// <param name="businessName"></param> /// <returns></returns> private Model.Sys.LogSetting GetLogSetting(string tableName, string businessName) { foreach (Model.Sys.LogSetting var in LogSettingList) { if (var.TableName.Equals(tableName, StringComparison.InvariantCultureIgnoreCase) && var.BusinessName.Equals(businessName, StringComparison.InvariantCultureIgnoreCase)) return var; } return null; } private Model.Sys.LogSetting GetLogSetting(string tableName) { foreach (Model.Sys.LogSetting var in LogSettingList) { if (var.TableName.Equals(tableName, StringComparison.InvariantCultureIgnoreCase)) return var; } return null; } /// <summary> /// 比较两个实体,然后返回实体中每个属性值不同的内容 /// </summary> /// <param name="oldObj"></param> /// <param name="newObj"></param> /// <returns></returns> public string Compare(T oldObj, T newObj) { Type objTye = typeof(T); StringBuilder sbResult = new StringBuilder(); string tableHeader = "<table class="GridView" cellspacing="0" rules="all" border="1" id="gv" style="border-collapse:collapse;">"; tableHeader += "<tr><th scope="col">序号</th><th scope="col">字段</th><th scope="col">名称</th><th scope="col">旧值</th><th scope="col">新值</th></tr>"; string tableRow = "<tr class='{0}'><td>{1}</td><td>{2}</td><td>{3}</td><td>{4}</td><td>{5}</td></tr>"; List<Model.Sys.LogSettingDetail> list = GetLogSettingDetails(logID); int i = 1; foreach (Model.Sys.LogSettingDetail var in list) { PropertyInfo property = objTye.GetProperty(var.ColumnName); if (property != null && !property.IsSpecialName) { object o = property.GetValue(oldObj, null); object n = property.GetValue(newObj, null); if (!IsEqual(property.PropertyType, o, n)) { sbResult.AppendFormat(tableRow, i % 2 == 0 ? "odd" : "even", i, var.ColumnName, var.ColumnText, o, n); i++; } } } sbResult.Append("</table>"); #region Add Log Record if (i > 1) { Model.Sys.LogOperation operModel = new Model.Sys.LogOperation(); operModel.LogID = logID; operModel.OperationType = (int)OperationType.Update; operModel.Content = tableHeader + sbResult.ToString(); operModel.CreateTime = DateTime.Now; if (HttpContext.Current != null) operModel.CreateUser = HttpContext.Current.User.Identity.Name; if (!string.IsNullOrEmpty(primaryKey)) { PropertyInfo p = objTye.GetProperty(primaryKey); object o = p.GetValue(newObj, null); if (o != null) { operModel.PrimaryKeyValue = o.ToString(); if (urlTemplate.Contains("{0}")) operModel.Url = string.Format(urlTemplate, o.ToString()); } } LogOperation operBll = new LogOperation(); operBll.Add(operModel); } #endregion return tableHeader + sbResult.ToString(); } /// <summary> /// 删除实体操作,这里并不是真的删除该实体,而是将删除的操作记录在日志中 /// </summary> /// <param name="obj"></param> /// <returns></returns> public string Delete(T obj) { Type objTye = typeof(T); StringBuilder sbResult = new StringBuilder(); string tableHeader = "<table class="GridView" cellspacing="0" rules="all" border="1" id="gv" style="border-collapse:collapse;">"; tableHeader += "<tr><th scope="col">序号</th><th scope="col">字段</th><th scope="col">名称</th><th scope="col">值</th></tr>"; string tableRow = "<tr class='{0}'><td>{1}</td><td>{2}</td><td>{3}</td><td>{4}</td></tr>"; List<Model.Sys.LogSettingDetail> list = GetLogSettingDetails(logID); int i = 1; foreach (Model.Sys.LogSettingDetail var in list) { PropertyInfo property = objTye.GetProperty(var.ColumnName); if (property != null && !property.IsSpecialName) { object o = property.GetValue(obj, null); sbResult.AppendFormat(tableRow, i % 2 == 0 ? "odd" : "even", i, var.ColumnName, var.ColumnText, o); i++; } } sbResult.Append("</table>"); #region Add Log Record Model.Sys.LogOperation operModel = new Model.Sys.LogOperation(); operModel.LogID = logID; operModel.OperationType = (int)OperationType.Delete; operModel.Content = tableHeader + sbResult.ToString(); operModel.CreateTime = DateTime.Now; if (!string.IsNullOrEmpty(primaryKey)) { PropertyInfo p = objTye.GetProperty(primaryKey); object o = p.GetValue(obj, null); if (o != null) { operModel.PrimaryKeyValue = o.ToString(); if (urlTemplate.Contains("{0}")) operModel.Url = string.Format(urlTemplate, o.ToString()); } } if (HttpContext.Current != null) operModel.CreateUser = HttpContext.Current.User.Identity.Name; LogOperation operBll = new LogOperation(); operBll.Add(operModel); #endregion return string.Empty; } /// <summary> /// 添加实体,将添加的操作记录在日志中 /// </summary> /// <param name="obj"></param> /// <returns></returns> public string Add(T obj) { Type objTye = typeof(T); StringBuilder sbResult = new StringBuilder(); string tableHeader = "<table class="GridView" cellspacing="0" rules="all" border="1" id="gv" style="border-collapse:collapse;">"; tableHeader += "<tr><th scope="col">序号</th><th scope="col">字段</th><th scope="col">名称</th><th scope="col">值</th></tr>"; string tableRow = "<tr class='{0}'><td>{1}</td><td>{2}</td><td>{3}</td><td>{4}</td></tr>"; List<Model.Sys.LogSettingDetail> list = GetLogSettingDetails(logID); int i = 1; foreach (Model.Sys.LogSettingDetail var in list) { PropertyInfo property = objTye.GetProperty(var.ColumnName); if (property != null && !property.IsSpecialName) { object o = property.GetValue(obj, null); sbResult.AppendFormat(tableRow, i % 2 == 0 ? "odd" : "even", i, var.ColumnName, var.ColumnText, o); i++; } } sbResult.Append("</table>"); #region Add Log Record Model.Sys.LogOperation operModel = new Model.Sys.LogOperation(); operModel.LogID = logID; operModel.OperationType = (int)OperationType.Add; operModel.Content = tableHeader + sbResult.ToString(); operModel.CreateTime = DateTime.Now; if (!string.IsNullOrEmpty(primaryKey)) { PropertyInfo p = objTye.GetProperty(primaryKey); object o = p.GetValue(obj, null); if (o != null) { operModel.PrimaryKeyValue = o.ToString(); if (urlTemplate.Contains("{0}")) operModel.Url = string.Format(urlTemplate, o.ToString()); } } if (HttpContext.Current != null) operModel.CreateUser = HttpContext.Current.User.Identity.Name; LogOperation operBll = new LogOperation(); operBll.Add(operModel); #endregion return string.Empty; } /// <summary> /// 复制一个对象 /// </summary> /// <param name="obj"></param> /// <returns></returns> public T Clone(T obj) { Type objTye = typeof(T); T model = new T(); PropertyInfo[] properties = objTye.GetProperties(); foreach (PropertyInfo property in properties) { if(!property.IsSpecialName) { object o = property.GetValue(obj, null); property.SetValue(model, o, null); } } return model; } private bool IsEqual(Type dataType, object oldObj, object newObj) { if (oldObj == null && newObj == null) return true; if (dataType == typeof(int)) { return (int)oldObj == (int)newObj; } else if (dataType == typeof(decimal)) { return (decimal)oldObj == (decimal)newObj; } else if (dataType == typeof(double)) { return (double)oldObj == (double)newObj; } else if (dataType == typeof(Guid)) { return (Guid)oldObj == (Guid)newObj; } else if (dataType == typeof(DateTime)) { return (DateTime)oldObj == (DateTime)newObj; } else return oldObj.Equals(newObj); } #region Script Excute //public int DeleteBusRecode(string primaryKeyValue) //{ // if (string.IsNullOrEmpty(tableName)) // throw new ArgumentException("tableName为空"); // if(string.IsNullOrEmpty(primaryKey)) // throw new ArgumentException("primaryKey为空"); // if (string.IsNullOrEmpty(deleteScriptTemplate)) // throw new ArgumentException("deleteScriptTemplate为空"); // string strSql = string.Format(deleteScriptTemplate, primaryKeyValue); // Database db = DatabaseFactory.CreateDatabase(); // return 0; //} #endregion #endregion } public enum OperationType { Select = 0, Add = 1, Update = 2, Delete = 3 }
使用的场景
Model文件:
public class EmployeeModel { public int ID{get;set;} public string Name{get;set;} … }
下面介绍如何将系统操作日志集成到你的业务系统中
添加操作:
EmployeeBll bll = new EmployeeBll();
EmployeeModel model = new EmployeeModel();
/* model 实体经过漫长的 赋值 后… */
bll.Add(model); //添加实体
//添加系统操作记录
//日志 LogManager<EmployeeModel> log = new LogManager<EmployeeModel>();
log.Add(model);
更新操作:
EmployeeBll bll = new EmployeeBll();
EmployeeModel model = bll.GetModel(employeeID);
LogManager<EmployeeModel> log = new LogManager<EmployeeModel>();
EmployeeModel modelOld = log.Clone(model); //克隆EmployeeModel实体对象,这个主要是在系统操作日志记录时使用的
/* model 实体又经过漫长的 赋值 后… */
bll.Update(model); //更新实体
//将更新的内容写入系统操作日志中
log.Compare(modelOld, model); //原来的实体和赋值后的实体对比,并将更新的内容写入系统操作日志中
删除操作:
在GridView的RowDeleting事件中获取要删除的实体
EmployeeBll bll = new EmployeeBll();
EmployeeModel model = bll.GetModel(employeeID);
bll.Delete(employeeID);
LogManager<EmployeeModel> log = new LogManager<EmployeeModel>(); log.Delete(model); //实体的内容记录到日志中
总结:
大家可以看到代码还是比较粗糙的,有不少的重复的代码,下一节将会讨论如何进行系统操作日志管理。
另外如何大家有什么意见或想法请分享提出。
本节用到的知识点:
1、泛型
2、反射
3、缓存
优点:
1、使用和集成方便,代码量小;
2、大大提高工作效率,避免表爆炸;
缺点:
1、代码有待优化;
2、可扩展性较差