需求
需求:將案例中增、刪、改相關接口的操作日志記錄到數據庫表中
? ? ● 就是當訪問部門管理和員工管理當中的增、刪、改相關功能接口時,需要詳細的操作日志,并保存在數據表中,便于后期數據追蹤。
操作日志信息包含:
? ? ● 操作人、操作時間、執行方法的全類名、執行方法名、方法運行時參數、返回值、方法執行時長
所記錄的日志信息包括當前接口的操作人是誰操作的,什么時間點操作的,以及訪問的是哪個類當中的哪個方法,在訪問這個方法的時候傳入進來的參數是什么,訪問這個方法最終拿到的返回值是什么,以及整個接口方法的運行時長是多長時間。
分析
問題1:項目當中增刪改相關的方法是不是有很多?
? ??●?很多
問題2:我們需要針對每一個功能接口方法進行修改,在每一個功能接口當中都來記錄這些操作日志嗎?
? ??●?這種做法比較繁瑣
以上兩個問題的解決方案:可以使用AOP解決(每一個增刪改功能接口中要實現的記錄操作日志的邏輯代碼是相同)。
可以把這部分記錄操作日志的通用的、重復性的邏輯代碼抽取出來定義在一個通知方法當中,我們通過AOP面向切面編程的方式,在不改動原始功能的基礎上來對原始的功能進行增強。目前我們所增強的功能就是來記錄操作日志,所以也可以使用AOP的技術來實現。使用AOP的技術來實現也是最為簡單,最為方便的。
問題3:既然要基于AOP面向切面編程的方式來完成的功能,那么我們要使用 AOP五種通知類型當中的哪種通知類型?
? ??●?答案:環繞通知
所記錄的操作日志當中包括:操作人、操作時間,訪問的是哪個類、哪個方法、方法運行時參數、方法的返回值、方法的運行時長。
方法返回值,是在原始方法執行后才能獲取到的。
方法的運行時長,需要原始方法運行之前記錄開始時間,原始方法運行之后記錄結束時間。通過計算獲得方法的執行耗時。
基于以上的分析我們確定要使用Around環繞通知。
問題4:最后一個問題,切入點表達式我們該怎么寫?
答案:使用annotation來描述表達式
要匹配業務接口當中所有的增刪改的方法,而增刪改方法在命名上沒有共同的前綴或后綴。此時如果使用execution切入點表達式也可以,但是會比較繁瑣。 當遇到增刪改的方法名沒有規律時,就可以使用 annotation切入點表達式
步驟
簡單分析了一下大概的實現思路后,接下來我們就要來完成案例了。案例的實現步驟其實就兩步:
? ??●?準備工作
? ? ? ? 1.引入AOP的起步依賴
? ? ? ? 2.導入數據庫表結構,并引入對應的實體類
? ??●?編碼實現
? ? ? ? 1.自定義注解@Log
? ? ? ? 2.定義切面類,完成記錄操作日志的邏輯
實現
準備工作
1.AOP起步依賴
<!--AOP起步依賴-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.導入數據庫表結構,并引入對應的實體類
數據表
-- 操作日志表
create table operate_log(id int unsigned primary key auto_increment comment 'ID',operate_user int unsigned comment '操作人',operate_time datetime comment '操作時間',class_name varchar(100) comment '操作的類名',method_name varchar(100) comment '操作的方法名',method_params varchar(1000) comment '方法參數',return_value varchar(2000) comment '返回值',cost_time bigint comment '方法執行耗時, 單位:ms'
) comment '操作日志表';
實體類
//操作日志實體類
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {private Integer id; //主鍵IDprivate Integer operateUser; //操作人IDprivate LocalDateTime operateTime; //操作時間private String className; //操作類名private String methodName; //操作方法名private String methodParams; //操作方法參數private String returnValue; //操作方法返回值private Long costTime; //操作耗時
}
Mapper接口
@Mapper
public interface OperateLogMapper {//插入日志數據@Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +"values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")public void insert(OperateLog log);}
編碼實現
? ? ● 自定義注解@Log
/*** 自定義Log注解*/
@Target({ElementType.METHOD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}
? ??●?修改業務實現類,在增刪改業務方法上添加@Log注解
@Slf4j
@Service
public class EmpServiceImpl implements EmpService {@Autowiredprivate EmpMapper empMapper;@Override@Logpublic void update(Emp emp) {emp.setUpdateTime(LocalDateTime.now()); //更新修改時間為當前時間empMapper.update(emp);}@Override@Logpublic void save(Emp emp) {//補全數據emp.setCreateTime(LocalDateTime.now());emp.setUpdateTime(LocalDateTime.now());//調用添加方法empMapper.insert(emp);}@Override@Logpublic void delete(List<Integer> ids) {empMapper.delete(ids);}//省略其他代碼...
}
以同樣的方式,修改EmpServiceImpl業務類
? ??●?定義切面類,完成記錄操作日志的邏輯
@Slf4j
@Component
@Aspect //切面類
public class LogAspect {@Autowiredprivate HttpServletRequest request;@Autowiredprivate OperateLogMapper operateLogMapper;@Around("@annotation(com.itheima.anno.Log)")public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {//操作人ID - 當前登錄員工ID//獲取請求頭中的jwt令牌, 解析令牌String jwt = request.getHeader("token");Claims claims = JwtUtils.parseJWT(jwt);Integer operateUser = (Integer) claims.get("id");//操作時間LocalDateTime operateTime = LocalDateTime.now();//操作類名String className = joinPoint.getTarget().getClass().getName();//操作方法名String methodName = joinPoint.getSignature().getName();//操作方法參數Object[] args = joinPoint.getArgs();String methodParams = Arrays.toString(args);long begin = System.currentTimeMillis();//調用原始目標方法運行Object result = joinPoint.proceed();long end = System.currentTimeMillis();//方法返回值String returnValue = JSONObject.toJSONString(result);//操作耗時Long costTime = end - begin;//記錄操作日志OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);operateLogMapper.insert(operateLog);log.info("AOP記錄操作日志: {}" , operateLog);return result;}}
代碼實現細節: 獲取request對象,從請求頭中獲取到jwt令牌,解析令牌獲取出當前用戶的id。
重啟SpringBoot服務,測試操作日志記錄功能:
? ??●?添加一個新的部門
? ? ● 數據表