在日常業(yè)務(wù)需求開發(fā)中,經(jīng)常有對關(guān)鍵業(yè)務(wù)功能做操作日志記錄,即某用戶在某一時間操作某功能,操作前后的數(shù)據(jù)記錄。尤其是在按業(yè)務(wù)功能模塊拆分成多個project時,就會面臨記錄操作日志與業(yè)務(wù)邏輯之間解耦、記錄操作日志更加簡單、操作前后業(yè)務(wù)數(shù)據(jù)(字段)對比等問題。
接下來我們將介紹一種易于理解、簡單接入操作日志的方法,同時提供一個通用的接口,方便前端開發(fā)者進(jìn)行頁面展示。
設(shè)計并實現(xiàn)一種通用的數(shù)據(jù)變更追蹤和動態(tài)展示(對象描述)實踐方案,該解決方案允許開發(fā)人員通過簡單的注解來標(biāo)記需要追蹤的業(yè)務(wù)數(shù)據(jù)。該系統(tǒng)能夠自動捕獲所有標(biāo)記數(shù)據(jù)的添加、修改等操作,并詳細(xì)記錄數(shù)據(jù)變更的每一步。同時,提供的動態(tài)展示接口,可以清晰、直觀地展示數(shù)據(jù)的變更明細(xì),讓數(shù)據(jù)的前后差異一目了然。具體包括:
在應(yīng)用層面,可以在業(yè)務(wù)代碼中添加日志,記錄下關(guān)鍵操作和數(shù)據(jù)的變更以及字段描述。例如,可以在每個數(shù)據(jù)庫操作前后添加日志,記錄下操作名稱、時間、影響的數(shù)據(jù)等信息。這種方案需要改變業(yè)務(wù)代碼(散彈式),增加編碼的復(fù)雜性,麻煩且不夠通用。
Canal 是阿里巴巴開源的一款基于 MySQL 二進(jìn)制日志(binlog)的增量數(shù)據(jù)訂閱和消費組件。其主要功能是提供對 MySQL 數(shù)據(jù)庫變更的實時監(jiān)聽,包括表結(jié)構(gòu)或數(shù)據(jù)變動等。Canal 記錄數(shù)據(jù)變更時,它捕獲的是數(shù)據(jù)庫層面上的原始變更事件,即 insert、update 和 delete 操作。這些事件是基于單個數(shù)據(jù)庫事務(wù)的,Canal 本身并不處理關(guān)聯(lián)數(shù)據(jù)的變更。當(dāng)一個業(yè)務(wù)模型關(guān)聯(lián)了多個表的數(shù)據(jù)時,訂閱 binlog,需要根據(jù)業(yè)務(wù)的實際情況對獲取的 binlog 數(shù)據(jù)進(jìn)行正確組裝和解析,包括正確的將多個表的變更合并到一個業(yè)務(wù)對象中。
這種方案的優(yōu)點是和業(yè)務(wù)邏輯完全解耦。缺點也很明顯,缺點是只能對數(shù)據(jù)庫的更改做記錄。
觸發(fā)器會增加數(shù)據(jù)庫的復(fù)雜性和維護(hù)成本,如果處理不當(dāng),觸發(fā)器可能導(dǎo)致性能問題,不考慮。
面向切面編程(Aspect-Oriented Programming,AOP)是一種編程范式,主要用于將那些與業(yè)務(wù)邏輯無關(guān),但在多個地方都需要使用的公共操作(如日志記錄、安全檢查、事務(wù)處理等)分離出來,降低系統(tǒng)的耦合度。AOP的運行需要動態(tài)生成代理類和織入切面,本實現(xiàn)方案一部分基于AOP實現(xiàn)。
本實現(xiàn)方案基于Annotation+AOP+SpringEL+Swagger實現(xiàn),AOP用于攔截配置自定義注解的Controller方法執(zhí)行前后記錄操作前后的數(shù)據(jù)。鑒于多數(shù)業(yè)務(wù)系統(tǒng)中,每一個業(yè)務(wù)模塊都有根據(jù)ID查詢明細(xì)數(shù)據(jù),由此在記錄數(shù)據(jù)時回調(diào)此業(yè)務(wù)的getById方法獲取明細(xì)數(shù)據(jù)。SpringEL默認(rèn)為調(diào)用業(yè)務(wù)模塊的Service的getById方法,如果是uuid等其他條件獲取數(shù)據(jù)時可以自定義配置。Swagger用于獲取模型定義,在引入此類庫的項目啟動時,會掃描帶有@ApiModel注解的模型并緩存,當(dāng)模型發(fā)生變更時,找到相對應(yīng)的模型定義一同存儲。為這種不確定的實體字段存儲,本方案采用MongoDB作為數(shù)據(jù)庫。
圖片
圖片
說明:初始化請求Context,并解析request相關(guān)信息。
定義操作類型與注解,操作類型為便于搜索,注解用于標(biāo)記需要追蹤變更的 Controller 方法。
圖片
圖片
說明:
圖片
說明:業(yè)務(wù)模塊Service實現(xiàn)此接口,這樣就能獲取到業(yè)務(wù)實體Model。通過Spring EL表達(dá)式調(diào)用業(yè)務(wù)基礎(chǔ) Service 的 getById或listByIds等方法,分別獲取方法執(zhí)行前后的數(shù)據(jù)快照。將兩次數(shù)據(jù)快照進(jìn)行對比,然后獲取相對應(yīng)的Model描述,并生成詳細(xì)的變更記錄存儲到MongoDB中。為不污染原業(yè)務(wù)Service代碼,建議適配Tracker接口并注入原Service來實現(xiàn)getById、listByIds方法。雖增加了實現(xiàn)類,但對于 項目中一個模塊的業(yè)務(wù)確是一勞永逸。
圖片
說明:當(dāng)注解中l(wèi)ogin為true時,回調(diào)此接口,獲取當(dāng)前登錄用戶信息,如為false時,回調(diào)getAnonymousUser()方法。
圖片
圖片
說明:
圖片
圖片
說明:因預(yù)期目標(biāo)想做到通用,業(yè)務(wù)字段的變更不確定,故而采用NoSQL數(shù)據(jù)庫存儲,本實現(xiàn)方案內(nèi)置以MongoDB為默認(rèn)存儲數(shù)據(jù)庫,僅對埋點的接口追蹤日志存儲,業(yè)務(wù)方可自定義日志處理器。如業(yè)務(wù)方自定義實現(xiàn)存儲,實現(xiàn)AccessLogPersist接口并且配置為Bean即可,默認(rèn)存儲即失效。
為兼容不同業(yè)務(wù)Model不同的字段定義,接口返回變更前后的數(shù)據(jù)同時,將字段描述同時返回,以用戶修改為例定義接口如下:
圖片
圖片
說明:
圖片
本節(jié)將以一個簡單的用戶(User)和任務(wù)(Task)的修改操作為例,介紹如何使用本方案實現(xiàn)數(shù)據(jù)變更追蹤和動態(tài)展示功能。
圖片
圖片
說明:項目中實現(xiàn)權(quán)限,當(dāng)為登錄接口時,自動回調(diào)此方法將LoginUser設(shè)置到AccessLog.user對象中。
圖片
圖片
圖片
說明:任務(wù)模塊Service同理
圖片
分別修改User和Task id為1的User和Task,分別提交修改數(shù)據(jù)如下:
{ "age": 32, "id": 1, "username": "姜強"}{ "contractName": "jturbo", "id": 1, "name": "任務(wù)名稱2"}
說明:returncode為0說明業(yè)務(wù)接口返回成功。
由上面兩條日志,根據(jù)接口規(guī)則定義,前端查看變更明細(xì)時展示如下:
用戶模塊時:
圖片
任務(wù)模塊時:
圖片
前端可忽略業(yè)務(wù),根據(jù)接口規(guī)則動態(tài)渲染,也可使用JSONDiff,高亮差異。
通過以上實踐,我們可以實現(xiàn)對業(yè)務(wù)系統(tǒng)中數(shù)據(jù)變更的追蹤和記錄,通過通用的接口規(guī)則,可以動態(tài)地展示不同業(yè)務(wù)數(shù)據(jù)模型變更過程。這種方案的核心是將數(shù)據(jù)變更的追蹤和記錄與業(yè)務(wù)邏輯分離,使其成為一個通用的、可復(fù)用的服務(wù)。通過注解和 AOP技術(shù),我們可以輕松地在業(yè)務(wù)系統(tǒng)中引入這種服務(wù),而無需修改原有的業(yè)務(wù)代碼。不僅可以提高代碼的可維護(hù)性,而且記錄用戶的操作日志也更加優(yōu)雅。
雖然當(dāng)前已經(jīng)能夠滿足大部分需求,但在未來,還計劃增加功能:
在業(yè)務(wù)場景允許的情況下增加一鍵回退功能。
對于此方案大家有更好的建議,歡迎留言討論。
作者簡介
姜強強
經(jīng)銷商技術(shù)部-商業(yè)資源團(tuán)隊
016年加入汽車之家,目前主要負(fù)責(zé)經(jīng)銷商事業(yè)部內(nèi)創(chuàng)新商業(yè)項目的研發(fā)工作,熱衷于業(yè)內(nèi)新技術(shù)的探索與實踐。
本文鏈接:http://www.www897cc.com/showinfo-26-57400-0.html記錄業(yè)務(wù)系統(tǒng)操作日志方案實踐
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: Elasticsearch 8.X 小技巧:使用存儲腳本優(yōu)化數(shù)據(jù)索引與轉(zhuǎn)換過程
下一篇: Go語言常見錯誤—意外的變量隱藏