? 哈嘍,屏幕前的每一位開發者朋友,你們好呀!??
當你點開這篇文章時,或許正對著 IDE 里閃爍的光標發呆,或許剛解決一個卡了三天的 bug,正端著咖啡松口氣 —— 不管此刻的你在經歷什么,都想先和你說聲:“辛苦了,同行者!” 👋?
作為一名摸爬滾打多年的開發工程師,我始終覺得,我們敲出的每一行代碼,不僅是業務邏輯的堆疊,更是無數個 “踩坑與爬坑” 的縮影。從第一次上線時的手忙腳亂,到如今能冷靜應對突發 bug;從對著文檔啃源碼的迷茫,到能給新人講清設計思路 —— 這些藏在鍵盤敲擊聲里的成長,太值得被好好梳理和分享了。 📝?
所以,這一系列文章里,不會有太多高深的架構理論,也不會羅列晦澀的技術文檔。我想聊的,是那些 “教科書里沒寫” 的實戰細節:?
比如上線前必做的 3 個自查動作(親測能減少 80% 的線上問題)🛡?;?
比如和產品經理 “友好溝通” 需求的 5 個小技巧(避免反復改需求到崩潰)🤝;?
比如如何用最少的時間,快速定位線上性能瓶頸(曾靠這招拯救過一次緊急故障)🚀;?
再比如那些看似 “浪費時間” 的重構,其實藏著怎樣的長期價值…… ???
當然,更想和大家聊聊 “技術之外” 的事:如何平衡加班與生活(畢竟身體是敲代碼的本錢)💪,如何在團隊中清晰表達自己的想法(別讓好方案被沉默埋沒)🗣?,甚至是 “35 歲焦慮” 來襲時,我是如何調整心態的…… 🌱?
如果你也和我一樣,相信 “經驗不是用來炫耀的資本,而是能幫同行少走彎路的路燈”,那不妨坐下來喝杯茶,一起在評論區聊聊:你最近遇到的最大挑戰是什么?有沒有哪個瞬間,讓你覺得 “啊,原來我真的成長了”? 💬?
畢竟,開發這條路從來不是孤軍奮戰。我們分享的每一個踩坑故事,都可能成為別人的指路牌;你留下的每一條評論,或許也會給我新的啟發。 🌟?
那么,準備好了嗎?讓我們開始這場 “代碼背后的成長對話” 吧!接下來的每一篇,都等你來拍磚、補充、共鳴 —— 因為最好的經驗,永遠在交流里生長。 🌱?
目錄
一.需求分析
二.實現邏輯(完全由后端實現)
1.自定義注解OperatorLog
2.在所有controller的方法上,添加該自定義注解
3.編寫切面類OperationLogAspect
三.效果展示
1.使用一下管理系統的某個功能
2.查看數據庫,肯定會多一條操作日志
四.特別聲明
一.需求分析
我要實現的效果是,我訪問管理系統的任何一個功能之后,都要往數據庫插入一條操作日志。
舉例:我在管理系統中,新增了一條合同記錄,此時就需要往數據庫插入一條操作日志,樣式如下
二.實現邏輯(完全由后端實現)
1.自定義注解OperatorLog
@Target(ElementType.METHOD)//表明這個注解只能加在方法上
@Retention(RetentionPolicy.RUNTIME)//表明這個注解在程序運行的時候還能被讀取到,而不是用完就丟)
public @interface OperationLog {String moduleName();//模塊名String operationType();//操作類型
}
2.在所有controller的方法上,添加該自定義注解
舉例:
3.編寫切面類OperationLogAspect
@Aspect
@Component
public class OperationLogAspect {//注入SysOperationLogMapper對象@Autowiredprivate SysOperationLogMapper sysOperationLogMapper;//攔截所有加了@OperationLog注解的方法,進行功能無侵入增強(環繞通知)@Around("@annotation(operationLog)")public Object around(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable {//ProceedingJoinPoint joinPoint:攔截的方法本身(我們要攔截controller層的方法)//OperationLog operationLog:在所攔截的方法上,添加的注解內容//獲取當前的HTTP請求對象HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();//1.獲取請求的controller方法的模塊名稱、操作類型(從注解中獲取)String moduleName = operationLog.moduleName();String operationTypeStr = operationLog.operationType();//2.獲取本次請求的請求方式(如GET、POST、PUT、DELETE)String requestMethod = request.getMethod();//3.獲取請求發起人的用戶名稱(直接從請求對象中獲取即可(別的服務放進去的))//3.1先獲取controller方法的參數實體類Object[] args = joinPoint.getArgs();Object entity = args[0];//必須默認controller的實體類參數放在第一個//3.2通過反射,拿到實體類中的用戶名稱(curUserName)Method getCurUserName = entity.getClass().getMethod("getCurUserName");String operator = (String)getCurUserName.invoke(entity);//4.獲取請求發起人的主機ipString hostIp = ClientIpUtil.getClientIpAddress(request);//5.獲取操作時間(就是當前的時間)Date operationTime = new Date();//6.先默認操作狀態為成功Integer operationStatus = 1;//7.獲取請求方法的全類名,如:com.hxb.xltj.controller.SysLogControllerString methodFullName = joinPoint.getTarget().getClass().getName();//System.out.println(methodFullName);//8.被增強的controller方法的返回結果(字符串類型)String methodResult = null;//9.請求路徑String requestUrl = request.getRequestURI();//10.請求參數String requestParam = JSON.toJSONString(entity);try{//放行被增強的方法Object result = joinPoint.proceed();//查看被增強方法是否執行成功if(result instanceof ResultBody){ResultBody resultBody = (ResultBody) result;//如果返回的結果響應碼不是“000000”,則說明請求失敗了,直接令該日志的狀態為0(失敗)operationStatus = !resultBody.getReturnCode().equals("000000")? 0:1;//如果是“導出”操作,就將返回體ResultBody的data屬性設為空(不給前端展示,太長了)if(operationTypeStr.equals("導出")){methodResult = "";}else{//給返回結果賦值methodResult = result.toString();}}//將結果返回出去,不要讓切面吞被增強方法的原有邏輯return result;}catch(Exception e){throw e;//繼續拋出異常,不影響原有錯誤處理}finally{//看看都獲取到了哪些日志信息//System.out.println(moduleName);//測試模塊//System.out.println(operationType);//查詢//System.out.println(requestMethod);//POST//System.out.println(hostIp);//127.0.0.1//System.out.println(operationTime);//Thu Aug 21 10:48:07 CST 2025//System.out.println(operationStatus);//1(代表成功)//將操作類型轉為整型,再存入數據庫Integer operationType = operationTypeStr.equals("新增")?1:operationTypeStr.equals("刪除")?2:operationTypeStr.equals("修改")?3:operationTypeStr.equals("查詢")?4:operationTypeStr.equals("授權")?5:operationTypeStr.equals("導出")?6:operationTypeStr.equals("導入")?7:operationTypeStr.equals("強退")?8:operationTypeStr.equals("生成代碼")?9:operationTypeStr.equals("清空數據")?10:null;//封裝系統操作日志記錄對象SysOperationLog sysOperationLog = new SysOperationLog(null, moduleName, operationType, requestMethod, operator, hostIp, operationStatus, operationTime,methodFullName,methodResult,requestUrl, requestParam,0,null,null);//將系統操作日志,存入數據庫sysOperationLogMapper.insert(sysOperationLog);}}
}
上述代碼,核心邏輯就是:獲取到【系統日志表】需要的所有字段,然后寫入數據庫即可。
三.效果展示
1.使用一下管理系統的某個功能
2.查看數據庫,肯定會多一條操作日志
四.特別聲明
1.如果你想讓管理系統的所有行為,都記錄到操作日志表中,你需要把后端所有的controller方法,都加上自定義注解@OperatorLog(因為只有這樣,才會被AOP切面進行代碼無侵入增強)
2.由于AOP切面類OperationLogAspect里面,有一些反射操作(通過controller方法的形參獲取操作信息),因此需要團隊伙伴在寫controller方法時,要遵循一定的約束(比如:controller方法必須通過Body接收參數,且Body參數必須放在形參列表的第一位)。
諸如此類,具體需要團隊伙伴遵循哪些約束,需要具體看需求和代碼邏輯,我們主要有這個意識就行,其他都好說。
以上就是通過SpringAOP切面編程,實現【記錄系統操作日志】功能的詳細流程,喜歡的話可以留個免費的關注呦~~~