基於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
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...