OSharp3.0框架解说系列(6.2):操作日志与数据日志

前言

  在《【开源】OSharp框架解说系列(6.1):日志系统设计》中,我们已经设计并实现了一个可扩展的日志系统,只要定义好输出端的Adapter,就可以以任意形式输出日志信息。

  在日志开发中,有些日志记录需求是常规需要的,比如操作日志,数据变更日志,系统异常日志等,我们希望把这些常规需求都集成到OSharp框架当中。有了内置的支持,在做开发的时候,只需要很简单的配置,就可以实现相关需求。

  关于三类日志,这里先简要描述一下:

  • 操作日志:粗略描述系统用户(如管理员、业务人员、会员等)对系统的业务操作,只需要说清楚“XXX用户在XXX时间做了XXX操作”
  • 数据日志:有时候,为了追溯用户的业务操作对系统产生的影响,需要记录数据变更细节,这就是数据日志
  • 系统日志:主要记录系统在运行过程中产生的与业务无关的常规或异常的日志信息,这些日志信息通常由系统维护人员或开发人员查看

日志记录准备

  在OSharp框架中,操作日志与数据日志的记录流程如下图所示:

  这里用文字简单描述一下操作日志与数据日志记录的实现思路:

  1. 定义了一个“功能信息记录”的实体,用于提取系统中各个功能点的基础信息(名称、MVC的Area-Controller-Action、功能访问类型(匿名访问-登录访问-特定角色访问)、是否启用功能日志,是否启用数据日志、功能URL等),并配置功能的行为
  2. 定义了一个“实体信息记录”的实体,用于提取系统中各个数据实体类型的基础信息(实体类型全名、实体名称、是否启用数据日志,实体属性信息集),并配置实体的行为
  3. 系统初始化的时候,通过反射加载的程序集,提取并构建各个功能点(主要是MVC的Controller-Action)的功能信息记录,更新到数据库中
  4. 系统初始化的时候,通过反射加载的程序集,提取并构建各个实体类型的实体信息记录,更新到数据库中
  5. 利用MVC框架的ActionFilter进行AOP拦截,定义一个专门用于操作日志记录的 OperateLogFilterAttribute ,重写 OnActionExecuted 方法进行操作日志的记录
  6. 操作日志与数据日志记录的详细流程如下:
    1. 在用户的业务操作执行到保存数据的时候(EF执行SaveChanges时),根据操作涉及的实体获取相应的实体信息记录,确定是否创建数据日志,不需创建则跳过
    2. 需要创建时,根据实体的状态(Added-Modified-Deleted),创建各个实体的新增-更新-删除的数据日志信息,并存储到临时缓存中
    3. 执行到 OperateLogFilterAttribute 的 OnActionExecuted 方法的时候,根据ActionExecutedContext 中提供的Area,Controller,Action等信息,查询出当前功能的功能信息记录,确定是否记录操作日志,不需记录则返回
    4. 需要根据功能信息记录,创建操作日志信息,并指定当前用户为日志操作人。
    5. 根据功能信息是否启用数据日志的配置,确定是否记录数据日志,需要记录时,从临时缓存中提取前面创建的数据日志,作为从数据配置到操作日志中
    6. 向系统外部保存操作日志信息,完成操作日志的记录

功能信息与实体信息

  记录各个功能点的功能信息接口定义如下:

 1     /// <summary>
 2     /// 功能接口,最小功能信息
 3     /// </summary>
 4     public interface IFunction
 5     {
 6         /// <summary>
 7         /// 获取或设置 功能名称
 8         /// </summary>
 9         string Name { get; set; }
10
11         /// <summary>
12         /// 获取或设置 区域名称
13         /// </summary>
14         string Area { get; set; }
15
16         /// <summary>
17         /// 获取或设置 控制器名称
18         /// </summary>
19         string Controller { get; set; }
20
21         /// <summary>
22         /// 获取或设置 功能名称
23         /// </summary>
24         string Action { get; set; }
25
26         /// <summary>
27         /// 获取或设置 功能类型
28         /// </summary>
29         FunctionType FunctionType { get; set; }
30
31         /// <summary>
32         /// 获取或设置 是否启用操作日志
33         /// </summary>
34         bool OperateLogEnabled { get; set; }
35
36         /// <summary>
37         /// 获取或设置 是否启用数据日志
38         /// </summary>
39         bool DataLogEnabled { get; set; }
40
41         /// <summary>
42         /// 获取或设置 是否锁定
43         /// </summary>
44         bool IsLocked { get; set; }
45
46         /// <summary>
47         /// 获取或设置 功能地址
48         /// </summary>
49         string Url { get; set; }
50     }

  记录各个数据实体类型的实体信息接口定义如下:

 1     /// <summary>
 2     /// 实体数据接口
 3     /// </summary>
 4     public interface IEntityInfo
 5     {
 6         /// <summary>
 7         /// 获取 实体数据类型名称
 8         /// </summary>
 9         string ClassName { get; }
10
11         /// <summary>
12         /// 获取 实体数据显示名称
13         /// </summary>
14         string Name { get; }
15
16         /// <summary>
17         /// 获取 是否启用数据日志
18         /// </summary>
19         bool DataLogEnabled { get; }
20
21         /// <summary>
22         /// 获取 实体属性信息字典
23         /// </summary>
24         IDictionary<string, string> PropertyNames { get; }
25     }

  OSharp框架中,已经派生了 Function 与 EntityInfo 两个实体类型,作为功能信息与实体信息的封装。

  功能信息与实体信息的初始化实现,主要定义在 FunctionHandlerBase<TFunction, TKey> 与 EntityInfoHandlerBase<TEntityInfo, TKey> 两个基础中,OSharp中已经派生了 public class DefaultFunctionHandler : FunctionHandlerBase<Function, Guid> 与 public class DefaultEntityInfoHandler : EntityInfoHandlerBase<EntityInfo, Guid> 作为系统初始化时,从程序集中提取并更新功能信息与数据信息的默认实现。

  由代码图,我们能很直观的看到实体与处理器之间的关系:

  关于这两个处理器的实现流程,不是本文的重点,将在后面讲解OSharp初始化实现时再详述,这里先略过。提取的数据展示如下:

  提取的功能信息:

  提取的实体数据信息:

操作日志与数据日志实体

  操作日志实体定义如下:

 1     /// <summary>
 2     /// 操作日志信息类
 3     /// </summary>
 4     [Description("系统-操作日志信息")]
 5     public class OperateLog : EntityBase<int>, ICreatedTime
 6     {
 7         /// <summary>
 8         /// 初始化一个<see cref="OperateLog"/>类型的新实例
 9         /// </summary>
10         public OperateLog()
11         {
12             DataLogs = new List<DataLog>();
13         }
14
15         /// <summary>
16         /// 获取或设置 执行的功能名称
17         /// </summary>
18         [StringLength(100)]
19         public string FunctionName { get; set; }
20
21         /// <summary>
22         /// 获取或设置 操作人信息
23         /// </summary>
24         public Operator Operator { get; set; }
25
26         /// <summary>
27         /// 获取设置 信息创建时间
28         /// </summary>
29         public DateTime CreatedTime { get; set; }
30
31         /// <summary>
32         /// 获取或设置 数据日志集合
33         /// </summary>
34         public virtual ICollection<DataLog> DataLogs { get; set; }
35     }

  数据日志实体定义如下:

 1     /// <summary>
 2     /// 数据日志信息类
 3     /// </summary>
 4     [Description("系统-数据日志信息")]
 5     public class DataLog : EntityBase<int>
 6     {
 7         /// <summary>
 8         /// 初始化一个<see cref="DataLog"/>类型的新实例
 9         /// </summary>
10         public DataLog()
11             : this(null, null, OperatingType.Query)
12         { }
13
14         /// <summary>
15         /// 初始化一个<see cref="DataLog"/>类型的新实例
16         /// </summary>
17         public DataLog(string entityName, string name, OperatingType operatingType)
18         {
19             EntityName = entityName;
20             Name = name;
21             OperateType = operatingType;
22             LogItems = new List<DataLogItem>();
23         }
24
25         /// <summary>
26         /// 获取或设置 类型名称
27         /// </summary>
28         [StringLength(500)]
29         [Display(Name = "类型名称")]
30         public string EntityName { get; set; }
31
32         /// <summary>
33         /// 获取或设置 实体名称
34         /// </summary>
35         [Display(Name = "实体名称")]
36         public string Name { get; set; }
37
38         /// <summary>
39         /// 获取或设置 数据编号
40         /// </summary>
41         [StringLength(150)]
42         [DisplayName("主键值")]
43         public string EntityKey { get; set; }
44
45         /// <summary>
46         /// 获取或设置 操作类型
47         /// </summary>
48         [Description("操作类型")]
49         public OperatingType OperateType { get; set; }
50
51         /// <summary>
52         /// 获取或设置 操作日志信息
53         /// </summary>
54         public virtual OperateLog OperateLog { get; set; }
55
56         /// <summary>
57         /// 获取或设置 操作明细
58         /// </summary>
59         public virtual ICollection<DataLogItem> LogItems { get; set; }
60     }

  数据日志操作变更明细项

 1     /// <summary>
 2     /// 实体操作日志明细
 3     /// </summary>
 4     [Description("系统-操作日志明细信息")]
 5     public class DataLogItem : EntityBase<Guid>
 6     {
 7         /// <summary>
 8         /// 初始化一个<see cref="DataLogItem"/>类型的新实例
 9         /// </summary>
10         public DataLogItem()
11             : this(null, null)
12         { }
13
14        /// <summary>
15         ///初始化一个<see cref="DataLogItem"/>类型的新实例
16        /// </summary>
17        /// <param name="originalValue">旧值</param>
18        /// <param name="newValue">新值</param>
19         public DataLogItem(string originalValue, string newValue)
20         {
21             Id = CombHelper.NewComb();
22             OriginalValue = originalValue;
23             NewValue = newValue;
24         }
25
26         /// <summary>
27         /// 获取或设置 字段
28         /// </summary>
29         public string Field { get; set; }
30
31         /// <summary>
32         /// 获取或设置 字段名称
33         /// </summary>
34         public string FieldName { get; set; }
35
36         /// <summary>
37         /// 获取或设置 旧值
38         /// </summary>
39         public string OriginalValue { get; set; }
40
41         /// <summary>
42         /// 获取或设置 新值
43         /// </summary>
44         public string NewValue { get; set; }
45
46         /// <summary>
47         /// 获取或设置 数据类型
48         /// </summary>
49         public string DataType { get; set; }
50
51         /// <summary>
52         /// 获取或设置 所属数据日志
53         /// </summary>
54         public virtual DataLog DataLog { get; set; }
55     }

  数据日志操作类型的枚举:

 1     /// <summary>
 2     /// 实体数据日志操作类型
 3     /// </summary>
 4     public enum OperatingType
 5     {
 6         /// <summary>
 7         /// 查询
 8         /// </summary>
 9         Query = 0,
10
11         /// <summary>
12         /// 新建
13         /// </summary>
14         Insert = 10,
15
16         /// <summary>
17         /// 更新
18         /// </summary>
19         Update = 20,
20
21         /// <summary>
22         /// 删除
23         /// </summary>
24         Delete = 30
25     }

  下图以较直观的方式显示操作日志与数据日志之间的关系:

数据日志的创建

  数据日志,主要记录业务操作过程中涉及到的各个数据实体的变更,而这里的变更,主要是实体的新增、更新、删除三种情况。

  在EntityFramework的数据操作中,实体经过业务处理之后,都是有状态跟踪的,即是 EntityState 枚举类型:

1     public enum EntityState
2     {
3         Detached = 1,
4         Unchanged = 2,
5         Added = 4,
6         Deleted = 8,
7         Modified = 16,
8     }

  我们要关心的状态,主要是Added、Deleted、Modified三个值,分别对应着新增、删除、更新三种状态,在EntityFramework执行到 SaveChanges 的时候,各个实体的状态已经确定。OSharp将在这个时机获取变更的实体并创建数据日志信息。

 1     /// <summary>
 2     /// 提交当前单元操作的更改
 3     /// </summary>
 4     /// <param name="validateOnSaveEnabled">提交保存时是否验证实体约束有效性。</param>
 5     /// <returns>操作影响的行数</returns>
 6     internal virtual int SaveChanges(bool validateOnSaveEnabled)
 7     {
 8         bool isReturn = Configuration.ValidateOnSaveEnabled != validateOnSaveEnabled;
 9         try
10         {
11             Configuration.ValidateOnSaveEnabled = validateOnSaveEnabled;
12             //记录实体操作日志
13             List<DataLog> logs = new List<DataLog>();
14             if (DataLoggingEnabled)
15             {
16                 logs = this.GetEntityDataLogs().ToList();
17             }
18             int count = base.SaveChanges();
19             if (count > 0 && DataLoggingEnabled)
20             {
21                 Logger.Info(logs, true);
22             }
23             TransactionEnabled = false;
24             return count;
25         }
26         catch (DbUpdateException e)
27         {
28             if (e.InnerException != null && e.InnerException.InnerException is SqlException)
29             {
30                 SqlException sqlEx = e.InnerException.InnerException as SqlException;
31                 string msg = DataHelper.GetSqlExceptionMessage(sqlEx.Number);
32                 throw new OSharpException("提交数据更新时发生异常:" + msg, sqlEx);
33             }
34             throw;
35         }
36         finally
37         {
38             if (isReturn)
39             {
40                 Configuration.ValidateOnSaveEnabled = !validateOnSaveEnabled;
41             }
42         }
43     }

  以上代码中, DataLoggingEnabled 属性 是当前上下文是否开启数据日志的总开关,当开启数据日志记录功能时,才进行数据日志的创建。

  创建数据日志的实现如下,主要是从对象管理器中筛选出指定状态的实体对象,再由实体类型全名获取相应实体的“实体信息记录”,确定是否执行数据日志的创建,然后创建数据日志信息:

 1     /// <summary>
 2     /// 获取数据上下文的变更日志信息
 3     /// </summary>
 4     public static IEnumerable<DataLog> GetEntityDataLogs(this DbContext dbContext)
 5     {
 6         ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
 7         ObjectStateManager manager = objectContext.ObjectStateManager;
 8
 9         IEnumerable<DataLog> logs = from entry in manager.GetObjectStateEntries(EntityState.Added).Where(entry => entry.Entity != null)
10             let entityInfo = OSharpContext.Current.EntityInfoHandler.GetEntityInfo(entry.Entity.GetType())
11             where entityInfo != null && entityInfo.DataLogEnabled
12             select GetAddedLog(entry, entityInfo);
13
14         logs = logs.Concat(from entry in manager.GetObjectStateEntries(EntityState.Modified).Where(entry => entry.Entity != null)
15             let entityInfo = OSharpContext.Current.EntityInfoHandler.GetEntityInfo(entry.Entity.GetType())
16             where entityInfo != null && entityInfo.DataLogEnabled
17             select GetModifiedLog(entry, entityInfo));
18
19         logs = logs.Concat(from entry in manager.GetObjectStateEntries(EntityState.Deleted).Where(entry => entry.Entity != null)
20             let entityInfo = OSharpContext.Current.EntityInfoHandler.GetEntityInfo(entry.Entity.GetType())
21             where entityInfo != null && entityInfo.DataLogEnabled
22             select GetDeletedLog(entry, entityInfo));
23
24         return logs;
25     }

  创建“新增”实体的数据日志:

 1     /// <summary>
 2     /// 获取添加数据的日志信息
 3     /// </summary>
 4     /// <param name="entry">实体状态跟踪信息</param>
 5     /// <param name="entityInfo">实体数据信息</param>
 6     /// <returns>新增数据日志信息</returns>
 7     private static DataLog GetAddedLog(ObjectStateEntry entry, IEntityInfo entityInfo)
 8     {
 9         DataLog log = new DataLog(entityInfo.ClassName, entityInfo.Name, OperatingType.Insert);
10         for (int i = 0; i < entry.CurrentValues.FieldCount; i++)
11         {
12             string name = entry.CurrentValues.GetName(i);
13             if (name == "Timestamp")
14             {
15                 continue;
16             }
17             object value = entry.CurrentValues.GetValue(i);
18             if (name == "Id")
19             {
20                 log.EntityKey = value.ToString();
21             }
22             Type fieldType = entry.CurrentValues.GetFieldType(i);
23             DataLogItem logItem = new DataLogItem()
24             {
25                 Field = name,
26                 FieldName = entityInfo.PropertyNames[name],
27                 NewValue = value == null ? null : value.ToString(),
28                 DataType = fieldType == null ? null : fieldType.Name
29             };
30             log.LogItems.Add(logItem);
31         }
32         return log;
33     }

  创建“更新”实体的数据日志:

 1     /// <summary>
 2     /// 获取修改数据的日志信息
 3     /// </summary>
 4     /// <param name="entry">实体状态跟踪信息</param>
 5     /// <param name="entityInfo">实体数据信息</param>
 6     /// <returns>修改数据日志信息</returns>
 7     private static DataLog GetModifiedLog(ObjectStateEntry entry, IEntityInfo entityInfo)
 8     {
 9         DataLog log = new DataLog(entityInfo.ClassName, entityInfo.Name, OperatingType.Update);
10         for (int i = 0; i < entry.CurrentValues.FieldCount; i++)
11         {
12             string name = entry.CurrentValues.GetName(i);
13             if (name == "Timestamp")
14             {
15                 continue;
16             }
17             object currentValue = entry.CurrentValues.GetValue(i);
18             object originalValue = entry.OriginalValues[name];
19             if (name == "Id")
20             {
21                 log.EntityKey = originalValue.ToString();
22             }
23             if (currentValue.Equals(originalValue))
24             {
25                 continue;
26             }
27             Type fieldType = entry.CurrentValues.GetFieldType(i);
28             DataLogItem logItem = new DataLogItem()
29             {
30                 Field = name,
31                 FieldName = entityInfo.PropertyNames[name],
32                 NewValue = currentValue == null ? null : currentValue.ToString(),
33                 OriginalValue = originalValue == null ? null : originalValue.ToString(),
34                 DataType = fieldType == null ? null : fieldType.Name
35             };
36             log.LogItems.Add(logItem);
37         }
38         return log;
39     }

  创建“删除”实体的数据日志:

 1     /// <summary>
 2     /// 获取删除数据的日志信息
 3     /// </summary>
 4     /// <param name="entry">实体状态跟踪信息</param>
 5     /// <param name="entityInfo">实体数据信息</param>
 6     /// <returns>删除数据日志信息</returns>
 7     private static DataLog GetDeletedLog(ObjectStateEntry entry, IEntityInfo entityInfo)
 8     {
 9         DataLog log = new DataLog(entityInfo.ClassName, entityInfo.Name, OperatingType.Delete);
10         for (int i = 0; i < entry.OriginalValues.FieldCount; i++)
11         {
12             string name = entry.OriginalValues.GetName(i);
13             if (name == "Timestamp")
14             {
15                 continue;
16             }
17             object originalValue = entry.OriginalValues[i];
18             if (name == "Id")
19             {
20                 log.EntityKey = originalValue.ToString();
21             }
22             Type fieldType = entry.OriginalValues.GetFieldType(i);
23             DataLogItem logItem = new DataLogItem()
24             {
25                 Field = name,
26                 FieldName = entityInfo.PropertyNames[name],
27                 OriginalValue = originalValue == null ? null : originalValue.ToString(),
28                 DataType = fieldType == null ? null : fieldType.Name
29             };
30             log.LogItems.Add(logItem);
31         }
32         return log;
33     }

数据日志的传递

  前面我们已经完成了数据日志创建,但数据日志是由数据层的EntityFramework的SaveChanges方法创建的,而创建的数据日志,最终将传递到上层定义的 OperateLogFilterAttribute 中进行使用,这就需要我们通过一定的机制将数据日志往上传递。在这里,使用的是日志组件。

  OSharp中定义了一个数据日志缓存,专门用于接收数据层创建的数据日志信息:

 1     /// <summary>
 2     /// 数据日志缓存接口
 3     /// </summary>
 4     public interface IDataLogCache : IDependency
 5     {
 6         /// <summary>
 7         /// 获取 数据日志集合
 8         /// </summary>
 9         IEnumerable<DataLog> DataLogs { get; }
10
11         /// <summary>
12         /// 向缓存中添加数据日志信息
13         /// </summary>
14         /// <param name="dataLog">数据日志信息</param>
15         void AddDataLog(DataLog dataLog);
16     }

  在专用于数据日志记录的 DatabaseLog 的 Write 方法重写时,判断数据是否是 DataLog 类型,并存入 IDataLogCache 中,这里使用MVC的依赖注入功能获取IDataLogCache的实现,以保证其在同一Http请求中,获取的是同一实例:

 1     /// <summary>
 2     /// 获取日志输出处理委托实例
 3     /// </summary>
 4     /// <param name="level">日志输出级别</param>
 5     /// <param name="message">日志消息</param>
 6     /// <param name="exception">日志异常</param>
 7     /// <param name="isData">是否数据日志</param>
 8     protected override void Write(LogLevel level, object message, Exception exception, bool isData = false)
 9     {
10         if (!isData)
11         {
12             return;
13         }
14         IEnumerable<DataLog> dataLogs = message as IEnumerable<DataLog>;
15         if (dataLogs == null)
16         {
17             return;
18         }
19         IDataLogCache logCache = DependencyResolver.Current.GetService<IDataLogCache>();
20         foreach (DataLog dataLog in dataLogs)
21         {
22             logCache.AddDataLog(dataLog);
23         }
24     }

操作日志的记录

  定义了一个 OperateLogFilterAttribute 的ActionFilter,专门用于拦截并记录操作日志。

 1     /// <summary>
 2     /// 操作日志记录过滤器
 3     /// </summary>
 4     public class OperateLogFilterAttribute : ActionFilterAttribute
 5     {
 6         /// <summary>
 7         /// 获取或设置 数据日志缓存
 8         /// </summary>
 9         public IDataLogCache DataLogCache { get; set; }
10
11         /// <summary>
12         /// 获取或设置 操作日志输出者
13         /// </summary>
14         public IOperateLogWriter OperateLogWriter { get; set; }
15
16         /// <summary>
17         /// Called after the action method executes.
18         /// </summary>
19         /// <param name="filterContext">The filter context.</param>
20         public override void OnActionExecuted(ActionExecutedContext filterContext)
21         {
22             string area = filterContext.GetAreaName();
23             string controller = filterContext.GetControllerName();
24             string action = filterContext.GetActionName();
25
26             IFunction function = OSharpContext.Current.FunctionHandler.GetFunction(area, controller, action);
27             if (function == null || !function.OperateLogEnabled)
28             {
29                 return;
30             }
31             Operator @operator = new Operator()
32             {
33                 Ip = filterContext.HttpContext.Request.GetIpAddress(),
34             };
35             if (filterContext.HttpContext.Request.IsAuthenticated)
36             {
37                 ClaimsIdentity identity = filterContext.HttpContext.User.Identity as ClaimsIdentity;
38                 if (identity != null)
39                 {
40                     @operator.UserId = identity.GetClaimValue(ClaimTypes.NameIdentifier);
41                     @operator.Name = identity.GetClaimValue(ClaimTypes.Name);
42                     @operator.NickName = identity.GetClaimValue(ClaimTypes.GivenName);
43                 }
44             }
45
46             OperateLog operateLog = new OperateLog()
47             {
48                 FunctionName = function.Name,
49                 Operator = @operator
50             };
51             if (function.DataLogEnabled)
52             {
53                 foreach (DataLog dataLog in DataLogCache.DataLogs)
54                 {
55                     operateLog.DataLogs.Add(dataLog);
56                 }
57             }
58             OperateLogWriter.Write(operateLog);
59         }
60     }

  最后,操作日志将由 IOperateLogWriter 进行输出,定义如下:

 1     /// <summary>
 2     /// 操作日志输出接口
 3     /// </summary>
 4     public interface IOperateLogWriter : IDependency
 5     {
 6         /// <summary>
 7         /// 输出操作日志
 8         /// </summary>
 9         /// <param name="operateLog">操作日志信息</param>
10         void Write(OperateLog operateLog);
11     }

  默认的,操作日志将被记录到数据库中:

 1     /// <summary>
 2     /// 操作日志数据库输出实现
 3     /// </summary>
 4     public class DatabaseOperateLogWriter : IOperateLogWriter
 5     {
 6         private readonly IRepository<OperateLog, int> _operateLogRepository;
 7
 8         /// <summary>
 9         /// 初始化一个<see cref="DatabaseOperateLogWriter"/>类型的新实例
10         /// </summary>
11         public DatabaseOperateLogWriter(IRepository<OperateLog, int> operateLogRepository)
12         {
13             _operateLogRepository = operateLogRepository;
14         }
15
16         /// <summary>
17         /// 输出操作日志
18         /// </summary>
19         /// <param name="operateLog">操作日志信息</param>
20         public void Write(OperateLog operateLog)
21         {
22             operateLog.CheckNotNull("operateLog" );
23             _operateLogRepository.Insert(operateLog);
24         }
25     }

操作日志显示

  如果一条操作日志中包含有数据日志,那么数据日志将以下级数据的方式展现在操作日志中:

nuget获取程序集及更新

  OSharp的相关类库已经发布到nuget上,并且会伴随着项目进度及时更新。

  直接通过VisualStudio自带的nuget插件搜索“osharp”关键字即可找到,欢迎试用。

获取示例

  OSharp 3.0 的使用示例,已提交到 github.com,将会随着框架的发展而及时更新,欢迎关注:

  示例源码地址:https://github.com/i66soft/osharp3-demo

  在Visual Studio 2013 中,可直接获取最新的源代码,获取方式如下,地址为:https://github.com/i66soft/osharp3-demo.git

  

系列导航

  1. 【开源】OSharp框架解说系列(1):总体设计
  2. 【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构
  3. 【开源】OSharp框架解说系列(2.2):EasyUI复杂布局及数据操作
  4. 【开源】OSharp框架解说系列(3):扩展方法
  5. 【开源】OSharp框架解说系列(4):架构分层及IoC
  6. 【开源】OSharp框架解说系列(5.1):EntityFramework数据层设计
  7. 【开源】OSharp框架解说系列(5.2):EntityFramework数据层实现
  8. 【开源】OSharp框架解说系列(6.1):日志系统设计
  9. OSharp3.0框架解说系列:新版本说明及新功能规划预览
  10. OSharp3.0框架解说系列(6.2):操作日志与数据日志
时间: 2024-11-06 08:45:53

OSharp3.0框架解说系列(6.2):操作日志与数据日志的相关文章

OSharp3.0框架解说系列:新版本说明及新功能规划预览

前言 时间过得真快,小半年又过去了. OSharp在github.com开源已经半年了,半年时间里,我们发现开源并没有给OSharp带来什么发展,关注的人不多,提交Bug的人更少,至于愿意参与到项目中来,给OSharp提交代码的人,0. 大环境如此,我也没什么可说的. 一个人的开源,开的不是源,是寂寞. 为了OSharp项目能继续发展下去,也为了团队的积极性(大家都懂的,如果只有你一个人在贡献,别人都只索取,你的热情坚持不了多久的),我们做了一个决定…… OSharp3.0不再开源 从OShar

【开源】OSharp3.3框架解说系列(7.1):初始化流程概述

本文已同步到系列目录:OSharp快速开发框架解说系列 框架初始化 相对于OSharp 3.0,3.3版本最大的更新,就是从框架级别定义了初始化流程,对初始化功能进行了抽象与封装,不依赖于第三方实现,第三方实现仅作为可替换的服务实现方案存在. 例如,依赖注入功能中,接口与其实现类的映射配置,对象容器的构建,对象的解析获取,都将通过框架定义的API来完成,而Autofac,仅作为这些功能的实现方存在,如果不想使用Autofac,则可以很方便的切换成别的IoC组件. 具体的初始化功能是怎样抽象与定义

【开源】OSharp3.3框架解说系列:开发计划与进度

OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依赖注入.ORM.对象映射.日志.缓存等等功能,都只定义了一套最基础最通用的抽象封装,提供了一套统一的API.约定与规则,并定义了部分执行流程,主要是让项目在一定的规范下进行开发.所有的功能实现端,都是通过现有的成熟的第三方组件来实现的,除了EntityFramework之外,所有的第三方实现都可以轻

【开源】OSharp框架解说系列(4):架构分层及IoC

〇.前言 前面构造了一个后台管理的界面布局,下面开始讲解整个项目的分层设计. 关于分层,网上已经存在相当多的讨论了,这也是一个程序员初学架构设计最先会碰到的问题. 该不该分层? 怎样分层? 层与层之间是否需要解耦?是否需要设计接口?接口是否是多余的? 看完OSharp的分层设计,我想,你应该多少能得到一些启示. 注:OSharp 开发框架的前身是<MVC实体架构设计>系列中讲到的那个架构示例,所以有很多知识点那个系列讲到了,就不会在这个系列再重复了,如果有什么觉得不太明白的可以参考<MV

【开源】OSharp框架解说系列(2.2):EasyUI复杂布局及数据操作

一.目录 一.目录 二.EasyUI复杂布局 三.EasyUI动态工具栏 四.EasyUI增删改操作 五.开源说明 系列导航 二.EasyUI复杂布局 接上篇,前面我们已经定义了一个 datagrid父视图 _DataGridLayout.cshtml,实现一个表格是相当的容易.但是,实际业务中,并非所有的数据列表并非只是单一的datagrid列表,还可能需要把datagrid与其他组件配合使用,比如角色信息是来源于各个组织机构的,就需要增加一个组织机构的分类,来更好的管理各种角色.最终效果图如

【开源】OSharp框架解说系列(1):总体设计

〇.前言 哈,距离前一个系列<MVC实用构架设计>的烂尾篇(2013年9月1日)已经跨了两个年头了,今天是2015年1月9日,日期已经相映,让我们开启新的航程吧. 前一个系列讲的主要是我对架构设计的理解以及怎样用好EntityFramework的一些想法,在技术细节上并没有太多的考究.不幸的是,不少同学把这个架构当作框架来用了,里边留的很多坑,坑苦了很多人,真是误人子弟,深表愧疚.于是重新整理代码,整理思路,鼓捣出了这个我们将要详解的开源框架:OSharp.这次,我们真的深入地说框架了,而不是

【开源】OSharp框架解说系列(3):扩展方法

〇.前言 扩展方法使你能够向现有类型“添加”方法,而无需创建新的派生类型.重新编译或以其他方式修改原始类型. 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用. 对于用 C# 和 Visual Basic 编写的客户端代码,调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异. 最常见的扩展方法是 LINQ 标准查询运算符,它将查询功能添加到现有的 System.Collections.IEnumerable 和 System.Collections.Generic.

【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构

〇.前言 要了解一个东西长什么样,至少得让我们能看到,才能提出针对性的见解.所以,为了言之有物,而不是凭空漫谈,我们先从UI说起,后台管理页面的UI我们将使用应用比较普遍的easyui框架. 以前在用easyui的时候,每个页面都得从0做起,或者不厌其烦地由以前的页面通过“复制-粘贴”的方式来修改,久页久之,就会造成页面庞大且难以维护.其实,前端的html,javascript代码与后端的代码是一样的,通过一定的组织,把重复的代码抽离出来,同样也通过达到很好的复用率.而MVC的天生的Layout

【开源】OSharp框架解说系列(6.1):日志系统设计

〇.前言 日志记录对于一个系统而言,重要性不言而喻.日志记录功能在系统开发阶段,往往容易被忽略.因为开发阶段,程序可以调试,可以反复的运行以查找问题.但在系统进入正常的运行维护阶段,特别是在进行审计统计的时候,追踪问题的时候,在追溯责任的时候,在系统出错的时候等等场景中,日志记录才会显示出它不可替代的作用.记录的日志,平时看似累赘,只有在需要的时候,才会拍大腿后悔当初为什么不把日志记录得详细些. 日志系统,是一个非常基础的系统,但由于需求的复杂性,各个场景需要的日志分类,来源,输出方式各有不同,