AOP實現操作日誌的記錄功能
阿新 • • 發佈:2020-07-27
參考
https://blog.csdn.net/chenxihua1/article/details/82703745
需求描述
在開發某系統時,遇到了這樣的一個需求:記錄該系統使用者的所有操作細節,只要滑鼠點選了介面,對資料庫進行了增刪改查操作,就必修記錄下來。而且這種記錄,不是給軟體維護者查閱的,是要給使用者查閱的。
這麼看來,就不能夠直接記錄函式(方法)的名稱,必須要轉化成使用者看的懂的資訊。
因為要新增到資料庫中,並且幾乎每個方法中都要記錄,直接來做的話工作量太大,而且還是和日誌相關,自然而然就能想到了利用AOP來實現。
實現過程
自定義註解
因為要自定義方法的名稱,不能簡單的輸出原方法名,所以考慮到用自定義的註解來記錄需要暴露給使用者的名稱。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited //如果有子類,子類也可以獲取到該類的註解資訊
@Documented
public @interface Operation {
String name();
}
使用的時候只要這麼標註就可以了
@RestController
@RequestMapping("/front/task")
@Operation(name = "任務管理")
public class TaskController {
@PostMapping( "/getTaskByPage")
@Operation(name = "任務查詢")
public RestResult getTaskListByPage(@RequestBody Map<String, Object> map){
RestResult result = new RestResult();
return result;
}
//任務執行
@PostMapping("/taskExecute")
@Operation(name = "任務執行")
public RestResult doTaskAction(@Valid @RequestBody TaskDao taskDao) throws Exception {
RestResult result = new RestResult();
return result;
}
}
AOP實現與註解解析
@Component
@Aspect
public class OperationAop {
private static final Logger logger = LoggerFactory.getLogger(OperationAop.class);
@Autowired
OperationHistoryService operationHistoryService;
@Pointcut("execution(* czams.front.controller.*.*(..))")
public void method(){}
@After("method()")
public void after(JoinPoint joinPoint){
}
@AfterReturning("method()")
public void afterReturning(JoinPoint joinPoint){
//獲取httpServlet物件
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
//獲取本AOP管轄範圍內的位元組碼物件
//?表示 extends Object
Class<?>clazz = joinPoint.getTarget().getClass();
//獲取類名
String controllerName = clazz.getName();
//如果該類標註了自定義的註解,則替換預設的名字
if(clazz.isAnnotationPresent(Operation.class)){
controllerName = clazz.getAnnotation(Operation.class).name();
}
//獲取方法名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String methodName = method.getName();
//如果該方法標註了自定義的註解,則替換預設的名字
if(method.isAnnotationPresent(Operation.class)){
methodName = method.getDeclaredAnnotation(Operation.class).name();
}
//獲取引數,由於本專案只通過json傳參,所以取第一個就好
Object[] args = joinPoint.getArgs();
String argName = "沒有條件";
if(args != null && args.length>0){
argName = args[0].toString();
}
//儲存到資料庫或者檔案
OperationHistoryDao operationHistoryDao = new OperationHistoryDao();
operationHistoryDao.setOperModule(controllerName);
operationHistoryDao.setOperAction(methodName);
operationHistoryDao.setOperDesc(argName);
operationHistoryDao.setOperId(getRemoteHost(request));
System.out.println(operationHistoryDao.getOperAction());
operationHistoryService.addOperationHistory(operationHistoryDao);
logger.info("ip為: "+getRemoteHost(request)+ "使用者執行了 "+controllerName +" 模組下的 "+ methodName + " 條件為 "+ argName);
}
private String getRemoteHost(HttpServletRequest request) {
// 獲取請求主機IP地址,如果通過代理進來,則透過防火牆獲取真實IP地址
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
} else if (ip.length() > 15) {
String[] ips = ip.split(",");
for (String s : ips) {
if (!("unknown".equalsIgnoreCase((String) s))) {
ip = s;
break;
}
}
}
return ip;
}
}
部分業務相關的程式碼可以忽略,大體流程如上。
另外,有時候controller包下面某些類或者說某些方法並不需要進行日誌記錄,那麼可以通過改變命名的方式來實現這樣的功能。
例如可以規定Controller結尾的才需要記錄日誌:
@Pointcut("execution(* czams.front.controller.*Controller.*(..))")
public void method(){}