基於SqlSugar的開發框架循序漸進介紹(8)-- 在基類函數封裝實現用戶操作日誌記錄

来源:https://www.cnblogs.com/wuhuacong/archive/2022/06/14/16371025.html
-Advertisement-
Play Games

在我們對數據進行重要修改調整的時候,往往需要跟蹤記錄好用戶操作日誌。一般來說,如對重要表記錄的插入、修改、刪除都需要記錄下來,由於用戶操作日誌會帶來一定的額外消耗,因此我們通過配置的方式來決定記錄那些業務數據的重要調整。本篇隨筆介紹如何在基於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 
    

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.前言 冬天很冷,買了一個鍋爐,需要迴圈泵的。簡單來說就是鍋爐水熱了之後迴圈泵自動開啟,然後將熱水輸送走,送到暖 氣,熱水抽走,涼水進入鍋爐,溫度降低,迴圈泵關閉,等待下一次水燒熱。因為需要取暖的房子距離燒鍋爐的地方比較遠,所以需要迴圈 泵,如果距離近的話水燒熱後利用熱水上流冷水迴流的原理會自動完 ...
  • 我們經常需要統計一個方法的耗時,一般我們會這樣做: public class Test { public static void main(String[] args) throws InterruptedException { long start = System.currentTimeMill ...
  • 我本地寫了一個rabbitmq fanout模式的demo。consumer啟動類和producer啟動類都放到了一個springboot程式里。本地調試通過。 突然有個疑問,springboot項目是怎麼來發現主啟動類的呢? 我們知道,預設使用maven打包時,是個普通的可供依賴的jar包,僅包含 ...
  • referer,正確寫法referrer,指的是網站的一種安全策略,在請求頭CSP(Content-Security-Policy),標簽或者是指定的html標簽里都可以設置它,它指的是上一個請求的來源記錄,比如你從a1通過鏈連,跳到a2,那在a2的請求頭裡,就會有a1的網址或者功能變數名稱,這個和refe ...
  • 項目中經常遇到CSV文件的讀寫需求,其中的難點主要是CSV文件的解析。本文會介紹CsvHelper、TextFieldParser、正則表達式三種解析CSV文件的方法,順帶也會介紹一下CSV文件的寫方法。 CSV文件標準 在介紹CSV文件的讀寫方法前,我們需要瞭解一下CSV文件的格式。 文件示例 一 ...
  • 這幾天在看 C++ 的 lambda 表達式,挺有意思,這個標準是在 C11標準 加進去的,也就是 2011 年,相比 C# 2007 還晚了個 4 年, Lambda 這東西非常好用,會上癮,今天我們簡單聊一聊。 一:語法定義 首先我們看下 C++ 語法定義格式: [capture] (param ...
  • 在Winform開發中有時候我們為了不影響主UI線程的處理,以前我們使用後臺線程BackgroundWorker來處理一些任務操作,不過隨著非同步處理提供的便利性,我們可以使用Async-Awati非同步任務處理替換原來的後臺線程BackgroundWorker處理方式,更加的簡潔明瞭。 ...
  • WPF(Windows Presentation Foundation)是微軟推出的基於Windows 的用戶界面框架,由 .NET Framework 3.0 開始引入,與WCF (Windows Communication Foundation)及 WF(Windows Workflow Fou... ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...