1. 程式人生 > 實用技巧 >AOP實現操作日誌的記錄功能

AOP實現操作日誌的記錄功能

參考

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(){}