在我們對數據進行重要修改調整的時候,往往需要跟蹤記錄好用戶操作日誌。一般來說,如對重要表記錄的插入、修改、刪除都需要記錄下來,由於用戶操作日誌會帶來一定的額外消耗,因此我們通過配置的方式來決定記錄那些業務數據的重要調整。本篇隨筆介紹如何在基於SqlSugar的開發框架中,實現對用戶操作日誌記錄的配置... ...
在我們對數據進行重要修改調整的時候,往往需要跟蹤記錄好用戶操作日誌。一般來說,如對重要表記錄的插入、修改、刪除都需要記錄下來,由於用戶操作日誌會帶來一定的額外消耗,因此我們通過配置的方式來決定記錄那些業務數據的重要調整。本篇隨筆介紹如何在基於SqlSugar的開發框架中,實現對用戶操作日誌記錄的配置設置,以及根據配置信息自動實現用戶操作日誌記錄。
1、用戶操作日誌記錄的配置處理
前面提到,由於用戶操作日誌會帶來一定的額外消耗,因此我們通過配置的方式來決定記錄那些業務數據的重要調整。
首先我們在系統中定義一個用戶操作日誌記錄表和一個操作日誌配置信息表,系統根據配置進行記錄重要的修改調整信息。
列表展示信息如下所示
有了這些信息記錄,我們可以在操作基類函數中,通過判斷SqlSugar實體類信息中的是否插入、更新、刪除的重要設置,可以決定記錄它們那些操作日誌信息。
下麵列表記錄了對一些表的增加、修改、刪除、以及一些重要的系統操作日誌信息,如“密碼重置”、“密碼修改”、“用戶過期設置”等操作日誌。
2、在基類中實現用戶操作日誌記錄處理
上面界面展示瞭如何通過配置,自動記錄用戶對某業務的相關重要操作記錄的界面。系統之所以能夠進行相關的信息記錄,是在基類函數中定義了相關的邏輯,根據配置邏輯,把插入對象的詳細信息、修改對象的變化比記錄、刪除對象的詳細信息進行寫入,以及對一些重要的處理,如重置密碼等,進行自定義的信息記錄的。
下麵我們來看看如何在基類中處理這些操作。
例如,我們在刪除記錄的時候,有時候接收的是實體類的ID,有時候接收的是實體類,那麼對於這些條件,我們相應的進行日誌處理,如下代碼所示。
/// <summary> /// 刪除指定ID的對象 /// </summary> /// <param name="id">記錄ID</param> /// <returns></returns> public virtual async Task<bool> DeleteAsync(TEntity input) { await OnOperationLog(input, OperationLogTypeEnum.刪除); return await EntityDb.DeleteAsync(input); } /// <summary> /// 刪除指定ID的對象 /// </summary> /// <param name="id">記錄ID</param> /// <returns></returns> public virtual async Task<bool> DeleteAsync(TKey id) { await OnOperationLog(id, OperationLogTypeEnum.刪除); return await EntityDb.DeleteByIdAsync(id); }
其中我們根據日誌的操作,定義一個枚舉的對象,如下所示。
/// <summary> /// 操作日誌的枚舉類型 /// </summary> public enum OperationLogTypeEnum { 增加, 刪除, 修改 }
對於刪除記錄的Id,我們需要把它轉換為對應的實體類,然後進行記錄的。
/// <summary> /// 統一處理實體類的日誌記錄 /// </summary> /// <param name="id">實體對象Id</param> /// <param name="logType">記錄類型</param> /// <returns></returns> 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;//結束處理 }
其中 CheckOperationLogEnable 就是用來判斷是否存在指定操作類型的配置信息的,如果存在,那麼就記錄操作日誌。
我們是根據實體類的全名進行判斷,如果存在指定的操作設置,就返回True,如下所示。(剛好基類中可以判斷泛型約束TEntity的全名)
/// <summary> /// 判斷指定的類型(增加、刪除、修改)是否配置啟用 /// </summary> /// <param name="logType">指定的類型(增加、刪除、修改)</param> /// <returns></returns> 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; }
對於插入記錄,我們也可以同時進行判斷並處理日誌信息。
/// <summary> /// 創建對象 /// </summary> /// <param name="input">實體對象</param> /// <returns></returns> public virtual async Task<bool> InsertAsync(TEntity input) { SetIdForGuids(input);//如果Id為空,設置有序的GUID值 await OnOperationLog(input, OperationLogTypeEnum.增加);//判斷並記錄日誌 return await EntityDb.InsertAsync(input); }
對於更新原有記錄,它也只需要接收更新前的對象,然後進行判斷處理即可。
/// <summary> /// 更新對象 /// </summary> /// <param name="input">實體對象</param> /// <returns></returns> public virtual async Task<bool> UpdateAsync(TEntity input) { SetIdForGuids(input);//如果Id為空,設置有序的GUID值 await OnOperationLog(input, OperationLogTypeEnum.修改);//判斷並記錄日誌 return await EntityDb.UpdateAsync(input); }
比較兩者,我們需要提供一個操作日誌方法重載用於記錄信息即可。
由於修改的信息,我們需要對比兩個不同記錄之間的差異信息,這樣我們才能友好的判斷那些信息變化了。也就是更新前後兩個實體對象之間的屬性差異信息,需要獲取出來。
/// <summary> /// 統一處理實體類的日誌記錄 /// </summary> /// <param name="input">實體對象</param> /// <param name="logType">記錄類型</param> /// <returns></returns> 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;//結束處理 }
而對於差異信息,我能定義一個擴展函數來處理他們的差異信息,如下所示。
/// <summary> /// 對象屬性的處理操作 /// </summary> public static class ObjectExtensions { /// <summary> /// 對比兩個屬性的差異信息 /// </summary> /// <typeparam name="T">對象類型</typeparam> /// <param name="val1">對象實例1</param> /// <param name="val2">對象實例2</param> /// <returns></returns> public static List<Variance> DetailedCompare<T>(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(); } /// <summary> /// 把兩個對象的差異信息轉換為JSON格式 /// </summary> /// <typeparam name="T">對象類型</typeparam> /// <param name="val1">對象實例1</param> /// <param name="val2">對象實例2</param> /// <returns></returns> public static string GetChangedNote<T>(this T oldVal, T newVal) { var specialList = new List<string> { "edittime", "createtime", "lastupdated" }; var list = DetailedCompare<T>(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的話,不清楚那個記錄變更了。
這樣我們就實現了增刪改的重要操作的記錄,並且由於是基類實現,我們只需要在系統中配置決定哪些業務類需要記錄即可自動實現重要日誌的記錄。
另外,我們在類別中還發現了其他一些不同類別的重要操作日誌,如重置密碼、修改密碼、用戶過期設置等,這些操作我們提供介面給這些處理調用即可。
/// <summary> /// 設置用戶的過期與否 /// </summary> /// <param name="userId">用戶ID</param> /// <param name="expired">是否禁用,true為禁用,否則為啟用</param> 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; }
其中 AddOperationLog 就是我們調用基類插入指定類型和日誌信息的記錄的,通過自定義類型和自定義日誌信息,可以讓我們彈性化的處理一些重要日誌記錄。
系列文章:
《基於SqlSugar的開發框架的循序漸進介紹(1)--框架基礎類的設計和使用》
《基於SqlSugar的開發框架循序漸進介紹(2)-- 基於中間表的查詢處理》
《基於SqlSugar的開發框架循序漸進介紹(3)-- 實現代碼生成工具Database2Sharp的整合開發》
《基於SqlSugar的開發框架循序漸進介紹(4)-- 在數據訪問基類中對GUID主鍵進行自動賦值處理 》
《基於SqlSugar的開發框架循序漸進介紹(5)-- 在服務層使用介面註入方式實現IOC控制反轉》
《基於SqlSugar的開發框架循序漸進介紹(6)-- 在基類介面中註入用戶身份信息介面 》
《基於SqlSugar的開發框架循序漸進介紹(7)-- 在文件上傳模塊中採用選項模式【Options】處理常規上傳和FTP文件上傳》
《基於SqlSugar的開發框架循序漸進介紹(8)-- 在基類函數封裝實現用戶操作日誌記錄 》
專註於代碼生成工具、.Net/.NetCore 框架架構及軟體開發,以及各種Vue.js的前端技術應用。著有Winform開發框架/混合式開發框架、微信開發框架、Bootstrap開發框架、ABP開發框架、SqlSugar開發框架等框架產品。轉載請註明出處:撰寫人:伍華聰 http://www.iqidi.com