基於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 MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...