1. 程式人生 > >spring-boot-route(十七)使用aop記錄操作日誌

spring-boot-route(十七)使用aop記錄操作日誌

在上一章內容中——[使用logback管理日誌](https://mp.weixin.qq.com/s/2AJSkcoUpXLXnkCV8AFfRw),我們詳細講述瞭如何將日誌生成檔案進行儲存。但是在實際開發中,使用檔案儲存日誌用來快速查詢問題並不是最方便的,一個優秀系統除了日誌檔案還需要將操作日誌進行持久化,來監控平臺的操作記錄。今天我們一起來學習一下如何通過apo來記錄日誌。 為了讓記錄日誌更加靈活,我們將使用自定義的註解來實現重要操作的日誌記錄功能。 ## 一 日誌記錄表 日誌記錄表主要包含幾個欄位,業務模組,操作型別,介面地址,處理狀態,錯誤資訊以及操作時間。資料庫設計如下: ```sql CREATE TABLE `sys_oper_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日誌主鍵', `title` varchar(50) CHARACTER SET utf8 DEFAULT '' COMMENT '模組標題', `business_type` int(2) DEFAULT '0' COMMENT '業務型別(0其它 1新增 2修改 3刪除)', `method` varchar(255) CHARACTER SET utf8 DEFAULT '' COMMENT '方法名稱', `status` int(1) DEFAULT '0' COMMENT '操作狀態(0正常 1異常)', `error_msg` varchar(2000) CHARACTER SET utf8 DEFAULT '' COMMENT '錯誤訊息', `oper_time` datetime DEFAULT NULL COMMENT '操作時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB CHARSET=utf8mb4 CHECKSUM=1 COMMENT='操作日誌記錄' ``` 對應的實體類如下: ```java @Data @NoArgsConstructor @AllArgsConstructor public class SysOperLog implements Serializable { private static final long serialVersionUID = 1L; /** 日誌主鍵 */ private Long id; /** 操作模組 */ private String title; /** 業務型別(0其它 1新增 2修改 3刪除) */ private Integer businessType; /** 請求方法 */ private String method; /** 錯誤訊息 */ private String errorMsg; private Integer status; /** 操作時間 */ private Date operTime; } ``` ## 二 自定義註解及處理 自定義註解包含兩個屬性,一個是業務模組`title`,另一個是操作型別`businessType`。 ```java @Target({ ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { /** * 模組 */ String title() default ""; /** * 功能 */ BusinessType businessType() default BusinessType.OTHER; } ``` 使用aop對自定義的註解進行處理 ```java @Aspect @Component @Slf4j public class LogAspect { @Autowired private AsyncLogService asyncLogService; // 配置織入點 @Pointcut("@annotation(com.javatrip.aop.annotation.Log)") public void logPointCut() {} /** * 處理完請求後執行 * * @param joinPoint 切點 */ @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult") public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) { handleLog(joinPoint, null, jsonResult); } /** * 攔截異常操作 * * @param joinPoint 切點 * @param e 異常 */ @AfterThrowing(value = "logPointCut()", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Exception e) { handleLog(joinPoint, e, null); } protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) { try { // 獲得註解 Log controllerLog = getAnnotationLog(joinPoint); if (controllerLog == null) { return; } SysOperLog operLog = new SysOperLog(); operLog.setStatus(0); if (e != null) { operLog.setStatus(1); operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000)); } // 設定方法名稱 String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); operLog.setMethod(className + "." + methodName + "()"); // 處理設定註解上的引數 getControllerMethodDescription(joinPoint, controllerLog, operLog); // 儲存資料庫 asyncLogService.saveSysLog(operLog); } catch (Exception exp) { log.error("==前置通知異常=="); log.error("日誌異常資訊 {}", exp); } } /** * 獲取註解中對方法的描述資訊 用於Controller層註解 * * @param log 日誌 * @param operLog 操作日誌 * @throws Exception */ public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) { // 設定action動作 operLog.setBusinessType(log.businessType().ordinal()); // 設定標題 operLog.setTitle(log.title()); } /** * 是否存在註解,如果存在就獲取 */ private Log getAnnotationLog(JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); if (method != null) { return method.getAnnotation(Log.class); } return null; } } ``` 操作型別的列舉類: ```java public enum BusinessType { /** * 其它 */ OTHER, /** * 新增 */ INSERT, /** * 修改 */ UPDATE, /** * 刪除 */ DELETE, } ``` 使用**非同步**方法將操作日誌存庫,為了方便我直接使用jdbcTemplate在service中進行存庫操作。 ```java @Service public class AsyncLogService { @Autowired private JdbcTemplate jdbcTemplate; /** * 儲存系統日誌記錄 */ @Async public void saveSysLog(SysOperLog log) { String sql = "INSERT INTO sys_oper_log(title,business_type,method,STATUS,error_msg,oper_time) VALUES(?,?,?,?,?,?)"; jdbcTemplate.update(sql,new Object[]{log.getTitle(),log.getBusinessType(),log.getMethod(),log.getStatus(),log.getErrorMsg(),new Date()}); } } ``` ## 三 編寫介面測試 將自定義註解寫在業務方法上,測試效果 ```java @RestController @RequestMapping("person") public class PersonController { @GetMapping("/{name}") @Log(title = "system",businessType = BusinessType.OTHER) public Person getPerson(@PathVariable("name") String name, @RequestParam int age){ return new Person(name,age); } @PostMapping("add") @Log(title = "system",businessType = BusinessType.INSERT) public int addPerson(@RequestBody Person person){ if(StringUtils.isEmpty(person)){ return -1; } return 1; } @PutMapping("update") @Log(title = "system",businessType = BusinessType.UPDATE) public int updatePerson(@RequestBody Person person){ if(StringUtils.isEmpty(person)){ return -1; } return 1; } @DeleteMapping("/{name}") @Log(title = "system",businessType = BusinessType.DELETE) public int deletePerson(@PathVariable(name = "name") String name){ if(StringUtils.isEmpty(name)){ return -1; } return 1; } } ``` 當然,還可以在資料庫中將請求引數和響應結果也進行儲存,這樣就能看出具體介面的操作記錄了。 此是spring-boot-route系列的第十七篇文章,這個系列的文章都比較簡單,主要目的就是為了幫助初次接觸Spring Boot 的同學有一個系統的認識。本文已收錄至我的[github](https://github.com/binzh303/spring-boot-route),歡迎各位小夥伴`star`! **github**:https://github.com/binzh303/spring-boot-route ## 點關注、不迷路 如果覺得文章不錯,歡迎**關注**、**點贊**、**收藏**,你們的支援是我創作的動力,感謝大家。 如果文章寫的有問題,請不要吝嗇,歡迎留言指出,我會及時核查修改。 如果你還想更加深入的瞭解我,可以微信搜尋「**Java旅途**」進行關注。回覆「**1024**」即可獲得學習視訊及精美電子書。每天7:30準時推送技術文章,讓你的上班路不在孤獨,而且每月還有送書活動,助你提升硬