基於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
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...