日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2024/8/22 编程问答 34 如意码农
生活随笔 收集整理的這篇文章主要介紹了 【开源】OSharp3.0框架解说系列(6.2):操作日志与数据日志 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

OSharp是什么?

  OSharp是個(gè)快速開(kāi)發(fā)框架,但不是一個(gè)大而全的包羅萬(wàn)象的框架,嚴(yán)格的說(shuō),OSharp中什么都沒(méi)有實(shí)現(xiàn)。與其他大而全的框架最大的不同點(diǎn),就是OSharp只做抽象封裝,不做實(shí)現(xiàn)。依賴注入、ORM、對(duì)象映射、日志、緩存等等功能,都只定義了一套最基礎(chǔ)最通用的抽象封裝,提供了一套統(tǒng)一的API、約定與規(guī)則,并定義了部分執(zhí)行流程,主要是讓項(xiàng)目在一定的規(guī)范下進(jìn)行開(kāi)發(fā)。所有的功能實(shí)現(xiàn)端,都是通過(guò)現(xiàn)有的成熟的第三方組件來(lái)實(shí)現(xiàn)的,除了EntityFramework之外,所有的第三方實(shí)現(xiàn)都可以輕松的替換成另一種第三方實(shí)現(xiàn),OSharp框架正是要起隔離作用,保證這種變更不會(huì)對(duì)業(yè)務(wù)代碼造成影響,使用統(tǒng)一的API來(lái)進(jìn)行業(yè)務(wù)實(shí)現(xiàn),解除與第三方實(shí)現(xiàn)的耦合,保持業(yè)務(wù)代碼的規(guī)范與穩(wěn)定。

本文已同步到系列目錄:OSharp快速開(kāi)發(fā)框架解說(shuō)系列

前言

  在《【開(kāi)源】OSharp框架解說(shuō)系列(6.1):日志系統(tǒng)設(shè)計(jì)》中,我們已經(jīng)設(shè)計(jì)并實(shí)現(xiàn)了一個(gè)可擴(kuò)展的日志系統(tǒng),只要定義好輸出端的Adapter,就可以以任意形式輸出日志信息。

  在系統(tǒng)開(kāi)發(fā)中,有些日志記錄需求是常規(guī)需要的,比如操作日志,數(shù)據(jù)變更日志,系統(tǒng)異常日志等,我們希望把這些常規(guī)需求都集成到OSharp框架當(dāng)中。有了內(nèi)置的支持,在做開(kāi)發(fā)的時(shí)候,只需要很簡(jiǎn)單的配置,就可以實(shí)現(xiàn)相關(guān)需求。

  關(guān)于三類(lèi)日志,這里先簡(jiǎn)要描述一下:

  • 操作日志:粗略描述系統(tǒng)用戶(如管理員、業(yè)務(wù)人員、會(huì)員等)對(duì)系統(tǒng)的業(yè)務(wù)操作,只需要說(shuō)清楚“XXX用戶在XXX時(shí)間做了XXX操作”
  • 數(shù)據(jù)日志:有時(shí)候,為了追溯用戶的業(yè)務(wù)操作對(duì)系統(tǒng)產(chǎn)生的影響,需要記錄數(shù)據(jù)變更細(xì)節(jié),這就是數(shù)據(jù)日志
  • 系統(tǒng)日志:主要記錄系統(tǒng)在運(yùn)行過(guò)程中產(chǎn)生的與業(yè)務(wù)無(wú)關(guān)的常規(guī)或異常的日志信息,這些日志信息通常由系統(tǒng)維護(hù)人員或開(kāi)發(fā)人員查看

日志記錄準(zhǔn)備

  在OSharp框架中,操作日志與數(shù)據(jù)日志的記錄流程如下圖所示:

  這里用文字簡(jiǎn)單描述一下操作日志與數(shù)據(jù)日志記錄的實(shí)現(xiàn)思路:

  1. 定義了一個(gè)“功能信息記錄”的實(shí)體,用于提取系統(tǒng)中各個(gè)功能點(diǎn)的基礎(chǔ)信息(名稱(chēng)、MVC的Area-Controller-Action、功能訪問(wèn)類(lèi)型(匿名訪問(wèn)-登錄訪問(wèn)-特定角色訪問(wèn))、是否啟用功能日志,是否啟用數(shù)據(jù)日志、功能URL等),并配置功能的行為
  2. 定義了一個(gè)“實(shí)體信息記錄”的實(shí)體,用于提取系統(tǒng)中各個(gè)數(shù)據(jù)實(shí)體類(lèi)型的基礎(chǔ)信息(實(shí)體類(lèi)型全名、實(shí)體名稱(chēng)、是否啟用數(shù)據(jù)日志,實(shí)體屬性信息集),并配置實(shí)體的行為
  3. 系統(tǒng)初始化的時(shí)候,通過(guò)反射加載的程序集,提取并構(gòu)建各個(gè)功能點(diǎn)(主要是MVC的Controller-Action)的功能信息記錄,更新到數(shù)據(jù)庫(kù)中
  4. 系統(tǒng)初始化的時(shí)候,通過(guò)反射加載的程序集,提取并構(gòu)建各個(gè)實(shí)體類(lèi)型的實(shí)體信息記錄,更新到數(shù)據(jù)庫(kù)中
  5. 利用MVC框架的ActionFilter進(jìn)行AOP攔截,定義一個(gè)專(zhuān)門(mén)用于操作日志記錄的 OperateLogFilterAttribute ,重寫(xiě) OnActionExecuted 方法進(jìn)行操作日志的記錄
  6. 操作日志與數(shù)據(jù)日志記錄的詳細(xì)流程如下:
    1. 在用戶的業(yè)務(wù)操作執(zhí)行到保存數(shù)據(jù)的時(shí)候(EF執(zhí)行SaveChanges時(shí)),根據(jù)操作涉及的實(shí)體獲取相應(yīng)的實(shí)體信息記錄,確定是否創(chuàng)建數(shù)據(jù)日志,不需創(chuàng)建則跳過(guò)
    2. 需要?jiǎng)?chuàng)建時(shí),根據(jù)實(shí)體的狀態(tài)(Added-Modified-Deleted),創(chuàng)建各個(gè)實(shí)體的新增-更新-刪除的數(shù)據(jù)日志信息,并存儲(chǔ)到臨時(shí)緩存中
    3. 執(zhí)行到 OperateLogFilterAttribute 的 OnActionExecuted 方法的時(shí)候,根據(jù)ActionExecutedContext 中提供的Area,Controller,Action等信息,查詢出當(dāng)前功能的功能信息記錄,確定是否記錄操作日志,不需記錄則返回
    4. 需要根據(jù)功能信息記錄,創(chuàng)建操作日志信息,并指定當(dāng)前用戶為日志操作人。
    5. 根據(jù)功能信息是否啟用數(shù)據(jù)日志的配置,確定是否記錄數(shù)據(jù)日志,需要記錄時(shí),從臨時(shí)緩存中提取前面創(chuàng)建的數(shù)據(jù)日志,作為從數(shù)據(jù)配置到操作日志中
    6. 向系統(tǒng)外部保存操作日志信息,完成操作日志的記錄

功能信息與實(shí)體信息

  記錄各個(gè)功能點(diǎn)的功能信息接口定義如下:

     /// <summary>
/// 功能接口,最小功能信息
/// </summary>
public interface IFunction
{
/// <summary>
/// 獲取或設(shè)置 功能名稱(chēng)
/// </summary>
string Name { get; set; } /// <summary>
/// 獲取或設(shè)置 區(qū)域名稱(chēng)
/// </summary>
string Area { get; set; } /// <summary>
/// 獲取或設(shè)置 控制器名稱(chēng)
/// </summary>
string Controller { get; set; } /// <summary>
/// 獲取或設(shè)置 功能名稱(chēng)
/// </summary>
string Action { get; set; } /// <summary>
/// 獲取或設(shè)置 功能類(lèi)型
/// </summary>
FunctionType FunctionType { get; set; } /// <summary>
/// 獲取或設(shè)置 是否啟用操作日志
/// </summary>
bool OperateLogEnabled { get; set; } /// <summary>
/// 獲取或設(shè)置 是否啟用數(shù)據(jù)日志
/// </summary>
bool DataLogEnabled { get; set; } /// <summary>
/// 獲取或設(shè)置 是否鎖定
/// </summary>
bool IsLocked { get; set; } /// <summary>
/// 獲取或設(shè)置 功能地址
/// </summary>
string Url { get; set; }
}

  記錄各個(gè)數(shù)據(jù)實(shí)體類(lèi)型的實(shí)體信息接口定義如下:

     /// <summary>
/// 實(shí)體數(shù)據(jù)接口
/// </summary>
public interface IEntityInfo
{
/// <summary>
/// 獲取 實(shí)體數(shù)據(jù)類(lèi)型名稱(chēng)
/// </summary>
string ClassName { get; } /// <summary>
/// 獲取 實(shí)體數(shù)據(jù)顯示名稱(chēng)
/// </summary>
string Name { get; } /// <summary>
/// 獲取 是否啟用數(shù)據(jù)日志
/// </summary>
bool DataLogEnabled { get; } /// <summary>
/// 獲取 實(shí)體屬性信息字典
/// </summary>
IDictionary<string, string> PropertyNames { get; }
}

  OSharp框架中,已經(jīng)派生了 Function 與 EntityInfo 兩個(gè)實(shí)體類(lèi)型,作為功能信息與實(shí)體信息的封裝。

  功能信息與實(shí)體信息的初始化實(shí)現(xiàn),主要定義在 FunctionHandlerBase<TFunction, TKey> 與 EntityInfoHandlerBase<TEntityInfo, TKey> 兩個(gè)基礎(chǔ)中,OSharp中已經(jīng)派生了 public class DefaultFunctionHandler : FunctionHandlerBase<Function, Guid> 與 public class DefaultEntityInfoHandler : EntityInfoHandlerBase<EntityInfo, Guid> 作為系統(tǒng)初始化時(shí),從程序集中提取并更新功能信息與數(shù)據(jù)信息的默認(rèn)實(shí)現(xiàn)。

  由代碼圖,我們能很直觀的看到實(shí)體與處理器之間的關(guān)系:

  關(guān)于這兩個(gè)處理器的實(shí)現(xiàn)流程,不是本文的重點(diǎn),將在后面講解OSharp初始化實(shí)現(xiàn)時(shí)再詳述,這里先略過(guò)。提取的數(shù)據(jù)展示如下:

  提取的功能信息:

  提取的實(shí)體數(shù)據(jù)信息:

操作日志與數(shù)據(jù)日志實(shí)體

  操作日志實(shí)體定義如下:

     /// <summary>
/// 操作日志信息類(lèi)
/// </summary>
[Description("系統(tǒng)-操作日志信息")]
public class OperateLog : EntityBase<int>, ICreatedTime
{
/// <summary>
/// 初始化一個(gè)<see cref="OperateLog"/>類(lèi)型的新實(shí)例
/// </summary>
public OperateLog()
{
DataLogs = new List<DataLog>();
} /// <summary>
/// 獲取或設(shè)置 執(zhí)行的功能名稱(chēng)
/// </summary>
[StringLength()]
public string FunctionName { get; set; } /// <summary>
/// 獲取或設(shè)置 操作人信息
/// </summary>
public Operator Operator { get; set; } /// <summary>
/// 獲取設(shè)置 信息創(chuàng)建時(shí)間
/// </summary>
public DateTime CreatedTime { get; set; } /// <summary>
/// 獲取或設(shè)置 數(shù)據(jù)日志集合
/// </summary>
public virtual ICollection<DataLog> DataLogs { get; set; }
}

  數(shù)據(jù)日志實(shí)體定義如下:

     /// <summary>
/// 數(shù)據(jù)日志信息類(lèi)
/// </summary>
[Description("系統(tǒng)-數(shù)據(jù)日志信息")]
public class DataLog : EntityBase<int>
{
/// <summary>
/// 初始化一個(gè)<see cref="DataLog"/>類(lèi)型的新實(shí)例
/// </summary>
public DataLog()
: this(null, null, OperatingType.Query)
{ } /// <summary>
/// 初始化一個(gè)<see cref="DataLog"/>類(lèi)型的新實(shí)例
/// </summary>
public DataLog(string entityName, string name, OperatingType operatingType)
{
EntityName = entityName;
Name = name;
OperateType = operatingType;
LogItems = new List<DataLogItem>();
} /// <summary>
/// 獲取或設(shè)置 類(lèi)型名稱(chēng)
/// </summary>
[StringLength()]
[Display(Name = "類(lèi)型名稱(chēng)")]
public string EntityName { get; set; } /// <summary>
/// 獲取或設(shè)置 實(shí)體名稱(chēng)
/// </summary>
[Display(Name = "實(shí)體名稱(chēng)")]
public string Name { get; set; } /// <summary>
/// 獲取或設(shè)置 數(shù)據(jù)編號(hào)
/// </summary>
[StringLength()]
[DisplayName("主鍵值")]
public string EntityKey { get; set; } /// <summary>
/// 獲取或設(shè)置 操作類(lèi)型
/// </summary>
[Description("操作類(lèi)型")]
public OperatingType OperateType { get; set; } /// <summary>
/// 獲取或設(shè)置 操作日志信息
/// </summary>
public virtual OperateLog OperateLog { get; set; } /// <summary>
/// 獲取或設(shè)置 操作明細(xì)
/// </summary>
public virtual ICollection<DataLogItem> LogItems { get; set; }
}

  數(shù)據(jù)日志操作變更明細(xì)項(xiàng)

     /// <summary>
/// 實(shí)體操作日志明細(xì)
/// </summary>
[Description("系統(tǒng)-操作日志明細(xì)信息")]
public class DataLogItem : EntityBase<Guid>
{
/// <summary>
/// 初始化一個(gè)<see cref="DataLogItem"/>類(lèi)型的新實(shí)例
/// </summary>
public DataLogItem()
: this(null, null)
{ } /// <summary>
///初始化一個(gè)<see cref="DataLogItem"/>類(lèi)型的新實(shí)例
/// </summary>
/// <param name="originalValue">舊值</param>
/// <param name="newValue">新值</param>
public DataLogItem(string originalValue, string newValue)
{
Id = CombHelper.NewComb();
OriginalValue = originalValue;
NewValue = newValue;
} /// <summary>
/// 獲取或設(shè)置 字段
/// </summary>
public string Field { get; set; } /// <summary>
/// 獲取或設(shè)置 字段名稱(chēng)
/// </summary>
public string FieldName { get; set; } /// <summary>
/// 獲取或設(shè)置 舊值
/// </summary>
public string OriginalValue { get; set; } /// <summary>
/// 獲取或設(shè)置 新值
/// </summary>
public string NewValue { get; set; } /// <summary>
/// 獲取或設(shè)置 數(shù)據(jù)類(lèi)型
/// </summary>
public string DataType { get; set; } /// <summary>
/// 獲取或設(shè)置 所屬數(shù)據(jù)日志
/// </summary>
public virtual DataLog DataLog { get; set; }
}

  數(shù)據(jù)日志操作類(lèi)型的枚舉:

     /// <summary>
/// 實(shí)體數(shù)據(jù)日志操作類(lèi)型
/// </summary>
public enum OperatingType
{
/// <summary>
/// 查詢
/// </summary>
Query = , /// <summary>
/// 新建
/// </summary>
Insert = , /// <summary>
/// 更新
/// </summary>
Update = , /// <summary>
/// 刪除
/// </summary>
Delete =
}

  下圖以較直觀的方式顯示操作日志與數(shù)據(jù)日志之間的關(guān)系:

數(shù)據(jù)日志的創(chuàng)建

  數(shù)據(jù)日志,主要記錄業(yè)務(wù)操作過(guò)程中涉及到的各個(gè)數(shù)據(jù)實(shí)體的變更,而這里的變更,主要是實(shí)體的新增、更新、刪除三種情況。

  在EntityFramework的數(shù)據(jù)操作中,實(shí)體經(jīng)過(guò)業(yè)務(wù)處理之后,都是有狀態(tài)跟蹤的,即是 EntityState 枚舉類(lèi)型:

     public enum EntityState
{
Detached = ,
Unchanged = ,
Added = ,
Deleted = ,
Modified = ,
}

  我們要關(guān)心的狀態(tài),主要是Added、Deleted、Modified三個(gè)值,分別對(duì)應(yīng)著新增、刪除、更新三種狀態(tài),在EntityFramework執(zhí)行到 SaveChanges 的時(shí)候,各個(gè)實(shí)體的狀態(tài)已經(jīng)確定。OSharp將在這個(gè)時(shí)機(jī)獲取變更的實(shí)體并創(chuàng)建數(shù)據(jù)日志信息。

     /// <summary>
/// 提交當(dāng)前單元操作的更改
/// </summary>
/// <param name="validateOnSaveEnabled">提交保存時(shí)是否驗(yàn)證實(shí)體約束有效性。</param>
/// <returns>操作影響的行數(shù)</returns>
internal virtual int SaveChanges(bool validateOnSaveEnabled)
{
bool isReturn = Configuration.ValidateOnSaveEnabled != validateOnSaveEnabled;
try
{
Configuration.ValidateOnSaveEnabled = validateOnSaveEnabled;
//記錄實(shí)體操作日志
13 List<DataLog> logs = new List<DataLog>();
14 if (DataLoggingEnabled)
15 {
16 logs = this.GetEntityDataLogs().ToList();
17 }
int count = base.SaveChanges();
19 if (count > 0 && DataLoggingEnabled)
20 {
21 Logger.Info(logs, true);
22 }
TransactionEnabled = false;
return count;
}
catch (DbUpdateException e)
{
if (e.InnerException != null && e.InnerException.InnerException is SqlException)
{
SqlException sqlEx = e.InnerException.InnerException as SqlException;
string msg = DataHelper.GetSqlExceptionMessage(sqlEx.Number);
throw new OSharpException("提交數(shù)據(jù)更新時(shí)發(fā)生異常:" + msg, sqlEx);
}
throw;
}
finally
{
if (isReturn)
{
Configuration.ValidateOnSaveEnabled = !validateOnSaveEnabled;
}
}
}

  以上代碼中, DataLoggingEnabled 屬性 是當(dāng)前上下文是否開(kāi)啟數(shù)據(jù)日志的總開(kāi)關(guān),當(dāng)開(kāi)啟數(shù)據(jù)日志記錄功能時(shí),才進(jìn)行數(shù)據(jù)日志的創(chuàng)建。

  創(chuàng)建數(shù)據(jù)日志的實(shí)現(xiàn)如下,主要是從對(duì)象管理器中篩選出指定狀態(tài)的實(shí)體對(duì)象,再由實(shí)體類(lèi)型全名獲取相應(yīng)實(shí)體的“實(shí)體信息記錄”,確定是否執(zhí)行數(shù)據(jù)日志的創(chuàng)建,然后創(chuàng)建數(shù)據(jù)日志信息:

     /// <summary>
/// 獲取數(shù)據(jù)上下文的變更日志信息
/// </summary>
public static IEnumerable<DataLog> GetEntityDataLogs(this DbContext dbContext)
{
ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
ObjectStateManager manager = objectContext.ObjectStateManager; IEnumerable<DataLog> logs = from entry in manager.GetObjectStateEntries(EntityState.Added).Where(entry => entry.Entity != null)
let entityInfo = OSharpContext.Current.EntityInfoHandler.GetEntityInfo(entry.Entity.GetType())
where entityInfo != null && entityInfo.DataLogEnabled
select GetAddedLog(entry, entityInfo); logs = logs.Concat(from entry in manager.GetObjectStateEntries(EntityState.Modified).Where(entry => entry.Entity != null)
let entityInfo = OSharpContext.Current.EntityInfoHandler.GetEntityInfo(entry.Entity.GetType())
where entityInfo != null && entityInfo.DataLogEnabled
select GetModifiedLog(entry, entityInfo)); logs = logs.Concat(from entry in manager.GetObjectStateEntries(EntityState.Deleted).Where(entry => entry.Entity != null)
let entityInfo = OSharpContext.Current.EntityInfoHandler.GetEntityInfo(entry.Entity.GetType())
where entityInfo != null && entityInfo.DataLogEnabled
select GetDeletedLog(entry, entityInfo)); return logs;
}

  創(chuàng)建“新增”實(shí)體的數(shù)據(jù)日志:

     /// <summary>
/// 獲取添加數(shù)據(jù)的日志信息
/// </summary>
/// <param name="entry">實(shí)體狀態(tài)跟蹤信息</param>
/// <param name="entityInfo">實(shí)體數(shù)據(jù)信息</param>
/// <returns>新增數(shù)據(jù)日志信息</returns>
private static DataLog GetAddedLog(ObjectStateEntry entry, IEntityInfo entityInfo)
{
DataLog log = new DataLog(entityInfo.ClassName, entityInfo.Name, OperatingType.Insert);
for (int i = ; i < entry.CurrentValues.FieldCount; i++)
{
string name = entry.CurrentValues.GetName(i);
if (name == "Timestamp")
{
continue;
}
object value = entry.CurrentValues.GetValue(i);
if (name == "Id")
{
log.EntityKey = value.ToString();
}
Type fieldType = entry.CurrentValues.GetFieldType(i);
DataLogItem logItem = new DataLogItem()
{
Field = name,
FieldName = entityInfo.PropertyNames[name],
NewValue = value == null ? null : value.ToString(),
DataType = fieldType == null ? null : fieldType.Name
};
log.LogItems.Add(logItem);
}
return log;
}

  創(chuàng)建“更新”實(shí)體的數(shù)據(jù)日志:

     /// <summary>
/// 獲取修改數(shù)據(jù)的日志信息
/// </summary>
/// <param name="entry">實(shí)體狀態(tài)跟蹤信息</param>
/// <param name="entityInfo">實(shí)體數(shù)據(jù)信息</param>
/// <returns>修改數(shù)據(jù)日志信息</returns>
private static DataLog GetModifiedLog(ObjectStateEntry entry, IEntityInfo entityInfo)
{
DataLog log = new DataLog(entityInfo.ClassName, entityInfo.Name, OperatingType.Update);
for (int i = ; i < entry.CurrentValues.FieldCount; i++)
{
string name = entry.CurrentValues.GetName(i);
if (name == "Timestamp")
{
continue;
}
object currentValue = entry.CurrentValues.GetValue(i);
object originalValue = entry.OriginalValues[name];
if (name == "Id")
{
log.EntityKey = originalValue.ToString();
}
if (currentValue.Equals(originalValue))
{
continue;
}
Type fieldType = entry.CurrentValues.GetFieldType(i);
DataLogItem logItem = new DataLogItem()
{
Field = name,
FieldName = entityInfo.PropertyNames[name],
NewValue = currentValue == null ? null : currentValue.ToString(),
OriginalValue = originalValue == null ? null : originalValue.ToString(),
DataType = fieldType == null ? null : fieldType.Name
};
log.LogItems.Add(logItem);
}
return log;
}

  創(chuàng)建“刪除”實(shí)體的數(shù)據(jù)日志:

     /// <summary>
/// 獲取刪除數(shù)據(jù)的日志信息
/// </summary>
/// <param name="entry">實(shí)體狀態(tài)跟蹤信息</param>
/// <param name="entityInfo">實(shí)體數(shù)據(jù)信息</param>
/// <returns>刪除數(shù)據(jù)日志信息</returns>
private static DataLog GetDeletedLog(ObjectStateEntry entry, IEntityInfo entityInfo)
{
DataLog log = new DataLog(entityInfo.ClassName, entityInfo.Name, OperatingType.Delete);
for (int i = ; i < entry.OriginalValues.FieldCount; i++)
{
string name = entry.OriginalValues.GetName(i);
if (name == "Timestamp")
{
continue;
}
object originalValue = entry.OriginalValues[i];
if (name == "Id")
{
log.EntityKey = originalValue.ToString();
}
Type fieldType = entry.OriginalValues.GetFieldType(i);
DataLogItem logItem = new DataLogItem()
{
Field = name,
FieldName = entityInfo.PropertyNames[name],
OriginalValue = originalValue == null ? null : originalValue.ToString(),
DataType = fieldType == null ? null : fieldType.Name
};
log.LogItems.Add(logItem);
}
return log;
}

數(shù)據(jù)日志的傳遞

  前面我們已經(jīng)完成了數(shù)據(jù)日志創(chuàng)建,但數(shù)據(jù)日志是由數(shù)據(jù)層的EntityFramework的SaveChanges方法創(chuàng)建的,而創(chuàng)建的數(shù)據(jù)日志,最終將傳遞到上層定義的 OperateLogFilterAttribute 中進(jìn)行使用,這就需要我們通過(guò)一定的機(jī)制將數(shù)據(jù)日志往上傳遞。在這里,使用的是日志組件。

  OSharp中定義了一個(gè)數(shù)據(jù)日志緩存,專(zhuān)門(mén)用于接收數(shù)據(jù)層創(chuàng)建的數(shù)據(jù)日志信息:

     /// <summary>
/// 數(shù)據(jù)日志緩存接口
/// </summary>
public interface IDataLogCache : IDependency
{
/// <summary>
/// 獲取 數(shù)據(jù)日志集合
/// </summary>
IEnumerable<DataLog> DataLogs { get; } /// <summary>
/// 向緩存中添加數(shù)據(jù)日志信息
/// </summary>
/// <param name="dataLog">數(shù)據(jù)日志信息</param>
void AddDataLog(DataLog dataLog);
}

  在專(zhuān)用于數(shù)據(jù)日志記錄的 DatabaseLog 的 Write 方法重寫(xiě)時(shí),判斷數(shù)據(jù)是否是 DataLog 類(lèi)型,并存入 IDataLogCache 中,這里使用MVC的依賴注入功能獲取IDataLogCache的實(shí)現(xiàn),以保證其在同一Http請(qǐng)求中,獲取的是同一實(shí)例:

     /// <summary>
/// 獲取日志輸出處理委托實(shí)例
/// </summary>
/// <param name="level">日志輸出級(jí)別</param>
/// <param name="message">日志消息</param>
/// <param name="exception">日志異常</param>
/// <param name="isData">是否數(shù)據(jù)日志</param>
protected override void Write(LogLevel level, object message, Exception exception, bool isData = false)
{
if (!isData)
{
return;
}
IEnumerable<DataLog> dataLogs = message as IEnumerable<DataLog>;
if (dataLogs == null)
{
return;
}
IDataLogCache logCache = DependencyResolver.Current.GetService<IDataLogCache>();
foreach (DataLog dataLog in dataLogs)
{
logCache.AddDataLog(dataLog);
}
}

操作日志的記錄

  定義了一個(gè) OperateLogFilterAttribute 的ActionFilter,專(zhuān)門(mén)用于攔截并記錄操作日志。

     /// <summary>
/// 操作日志記錄過(guò)濾器
/// </summary>
public class OperateLogFilterAttribute : ActionFilterAttribute
{
/// <summary>
/// 獲取或設(shè)置 數(shù)據(jù)日志緩存
/// </summary>
public IDataLogCache DataLogCache { get; set; } /// <summary>
/// 獲取或設(shè)置 操作日志輸出者
/// </summary>
public IOperateLogWriter OperateLogWriter { get; set; } /// <summary>
/// Called after the action method executes.
/// </summary>
/// <param name="filterContext">The filter context.</param>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
string area = filterContext.GetAreaName();
string controller = filterContext.GetControllerName();
string action = filterContext.GetActionName(); IFunction function = OSharpContext.Current.FunctionHandler.GetFunction(area, controller, action);
if (function == null || !function.OperateLogEnabled)
{
return;
}
Operator @operator = new Operator()
{
Ip = filterContext.HttpContext.Request.GetIpAddress(),
};
if (filterContext.HttpContext.Request.IsAuthenticated)
{
ClaimsIdentity identity = filterContext.HttpContext.User.Identity as ClaimsIdentity;
if (identity != null)
{
@operator.UserId = identity.GetClaimValue(ClaimTypes.NameIdentifier);
@operator.Name = identity.GetClaimValue(ClaimTypes.Name);
@operator.NickName = identity.GetClaimValue(ClaimTypes.GivenName);
}
} OperateLog operateLog = new OperateLog()
{
FunctionName = function.Name,
Operator = @operator
};
if (function.DataLogEnabled)
{
foreach (DataLog dataLog in DataLogCache.DataLogs)
{
operateLog.DataLogs.Add(dataLog);
}
}
OperateLogWriter.Write(operateLog);
}
}

  最后,操作日志將由 IOperateLogWriter 進(jìn)行輸出,定義如下:

     /// <summary>
/// 操作日志輸出接口
/// </summary>
public interface IOperateLogWriter : IDependency
{
/// <summary>
/// 輸出操作日志
/// </summary>
/// <param name="operateLog">操作日志信息</param>
void Write(OperateLog operateLog);
}

  默認(rèn)的,操作日志將被記錄到數(shù)據(jù)庫(kù)中:

     /// <summary>
/// 操作日志數(shù)據(jù)庫(kù)輸出實(shí)現(xiàn)
/// </summary>
public class DatabaseOperateLogWriter : IOperateLogWriter
{
private readonly IRepository<OperateLog, int> _operateLogRepository; /// <summary>
/// 初始化一個(gè)<see cref="DatabaseOperateLogWriter"/>類(lèi)型的新實(shí)例
/// </summary>
public DatabaseOperateLogWriter(IRepository<OperateLog, int> operateLogRepository)
{
_operateLogRepository = operateLogRepository;
} /// <summary>
/// 輸出操作日志
/// </summary>
/// <param name="operateLog">操作日志信息</param>
public void Write(OperateLog operateLog)
{
operateLog.CheckNotNull("operateLog" );
_operateLogRepository.Insert(operateLog);
}
}

操作日志顯示

  如果一條操作日志中包含有數(shù)據(jù)日志,那么數(shù)據(jù)日志將以下級(jí)數(shù)據(jù)的方式展現(xiàn)在操作日志中:

開(kāi)源說(shuō)明

github.com

  OSharp項(xiàng)目已在github.com上開(kāi)源,地址為:https://github.com/i66soft/osharp,歡迎閱讀代碼,歡迎 Watch(關(guān)注),歡迎 Star(推薦),如果您認(rèn)同 OSharp 項(xiàng)目的設(shè)計(jì)思想,歡迎參與 OSharp 項(xiàng)目的開(kāi)發(fā)。

  在Visual Studio 2013中,可直接獲取 OSharp 的最新源代碼,獲取方式如下,地址為:https://github.com/i66soft/osharp.git

  

開(kāi)源項(xiàng)目參與方式

  很多童鞋想?yún)⑴c開(kāi)源項(xiàng)目,為項(xiàng)目做貢獻(xiàn),但又不知道如何做,這里我簡(jiǎn)單說(shuō)下參與OSharp的步驟吧:

  1. 在 https://github.com/i66soft/osharp 右上角 Fork 一下項(xiàng)目源碼,在你的賬戶下會(huì)有一份代碼的副本
  2. 使用VisualStudio Clone 你賬戶下的代碼到本地,更改代碼,再提交,就完成代碼的更改了
  3. 如果覺(jué)得有并入 i66soft 主干的價(jià)值,可以向主干提交 pull request申請(qǐng),如果我審核通過(guò),就可以合并到主干了,這就形成了一次開(kāi)源代碼的貢獻(xiàn)了
  4. 如果我沒(méi)有接受合并,你也可以在你的賬戶上按你的風(fēng)格去發(fā)展osharp
  5. 我也會(huì)經(jīng)常瀏覽各個(gè)Fork版本對(duì)項(xiàng)目的更改,如果覺(jué)得有價(jià)值,也會(huì)主動(dòng)合并到主干代碼中,也能形成一次對(duì)開(kāi)源的貢獻(xiàn)
  6. 為保證提交的質(zhì)量,也便于對(duì)代碼的合并,每次更改與提交應(yīng)該只做一件事,只提交必要的更改

nuget

  OSharp的相關(guān)類(lèi)庫(kù)已經(jīng)發(fā)布到nuget上,歡迎試用,直接在nuget上搜索 “osharp” 關(guān)鍵字即可找到

系列導(dǎo)航

本文已同步到系列目錄:OSharp快速開(kāi)發(fā)框架解說(shuō)系列

總結(jié)

以上是生活随笔為你收集整理的【开源】OSharp3.0框架解说系列(6.2):操作日志与数据日志的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。