Spring Boot利用AOP獲取使用者操作實現日誌記錄

malizhi發表於2018-12-19

環境:IDEA版本2017.3.1 x64, JDK1.8, SpringBoot2.1.1, Druid1.1.8, mybatis1.3.2,Security5.1.2,thymeleaf3.0.11

思路總結:首先在需要做日誌記錄的方法中新增一個自定義註解,再去實現一個日誌AOP類,AOP類把自定義註解設定為切點,所以當系統執行某一個新增了自定義註解的方法時,AOP會自動獲取該方法名稱以及使用者資訊實現日誌記錄。

編寫自定義註解

/**
 * 自定義註解類  定義controller方法的中文含義
 * @Target({METHOD,TYPE}) 表示這個註解可以用用在類/介面上,還可以用在方法上
 * @Retention(RetentionPolicy.RUNTIME) 表示這是一個執行時註解,即執行起來之後,才獲取註解中的相關資訊,而不像基本註解如@Override 那種不用執行,在編譯時eclipse就可以進行相關工作的編譯時註解。
 * @Inherited 表示這個註解可以被子類繼承
 * @Documented 表示當執行javadoc的時候,本註解會生成相關文件
 */

@Target({METHOD, TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Operation {
    String value() default "";
}
複製程式碼

日誌AOP類會用到獲取IP地址的工具類,也用到json工具類,實現工具類如下

實現必要工具類

獲取ip地址工具類
        /**
	 * 獲取使用者真實的ip地址
	 * @param request
	 * @return
	 */
public class IpAdrressUtil {
	
	public static String getIpAdrress(HttpServletRequest request) {
		 	String ip = null;

		    //X-Forwarded-For:Squid 服務代理
		    String ipAddresses = request.getHeader("X-Forwarded-For");
		    String unknown = "unknown";
		    if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
		        //Proxy-Client-IP:apache 服務代理
		        ipAddresses = request.getHeader("Proxy-Client-IP");
		    }

		    if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
		        //WL-Proxy-Client-IP:weblogic 服務代理
		        ipAddresses = request.getHeader("WL-Proxy-Client-IP");
		    }

		    if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
		        //HTTP_CLIENT_IP:有些代理伺服器
		        ipAddresses = request.getHeader("HTTP_CLIENT_IP");
		    }

		    if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
		        //X-Real-IP:nginx服務代理
		        ipAddresses = request.getHeader("X-Real-IP");
		    }

		    //有些網路通過多層代理,那麼獲取到的ip就會有多個,一般都是通過逗號(,)分割開來,並且第一個ip為客戶端的真實IP
		    if (ipAddresses != null && ipAddresses.length() != 0) {
		        ip = ipAddresses.split(",")[0];
		    }

		    //還是不能獲取到,最後再通過request.getRemoteAddr();獲取
		    if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
		        ip = request.getRemoteAddr();
		    }
		    return ip;
	  }
	
	
}

複製程式碼
實現json工具類
public class JacksonUtil {

    private final static ObjectMapper objectMapper = new ObjectMapper();

    private JacksonUtil() {

    }

    public static ObjectMapper getInstance() {
        return objectMapper;
    }

    /**
     * javaBean、列表陣列轉換為json字串
     */
    public static String obj2json(Object obj) throws Exception {
        return objectMapper.writeValueAsString(obj);
    }

    /**
     * json 轉JavaBean
     */

    public static <T> T json2pojo(String jsonString, Class<T> clazz) throws Exception {
        objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
        return objectMapper.readValue(jsonString, clazz);
    }

    /**
     * json字串轉換為map
     */
    public static <T> Map<String, Object> json2map(String jsonString) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return mapper.readValue(jsonString, Map.class);
    }
}
複製程式碼

實現日誌AOP類

/**
 * 系統日誌:切面處理類
 */
@Aspect
@Component
public class SysLogAspect {

    @Autowired
    private SysLogService sysLogService;

    //定義切點 @Pointcut
    //在註解的位置切入程式碼
    @Pointcut("@annotation(cn.springboot.util.Operation)")
    public void logPoinCut() {
    }

    //切面 配置通知
    @AfterReturning("logPoinCut()")
    public void saveSysLog(JoinPoint joinPoint) {
        //儲存日誌
        SysLog sysLog = new SysLog();

        //從切面織入點處通過反射機制獲取織入點處的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //獲取切入點所在的方法
        Method method = signature.getMethod();


        //獲取操作
        Operation operation = method.getAnnotation(Operation.class);
        if (operation != null) {
            String value = operation.value();
            sysLog.setOperation(value);//儲存獲取的操作
        }

        //獲取請求的類名
        String className = joinPoint.getTarget().getClass().getName();

        //獲取請求的方法名
        String methodName = method.getName();
        sysLog.setMethod(className + "." + methodName);

        //請求的引數
        Object[] args = joinPoint.getArgs();
        //將引數所在的陣列轉換成json
        String params = null;
        try {
            params = JacksonUtil.obj2json(args);
        } catch (Exception e) {
            e.printStackTrace();
        }
        sysLog.setParams(params);

        //請求的時間
        sysLog.setCreateDate(new Date());

        //獲取使用者名稱
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (!(authentication instanceof AnonymousAuthenticationToken)) {
            sysLog.setUsername(authentication.getName());
        }

        //獲取使用者ip地址
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                .getRequest();
        sysLog.setIp(IpAdrressUtil.getIpAdrress(request));

        //呼叫service儲存SysLog實體類到資料庫
        sysLogService.saveLog(sysLog);
    }

}
複製程式碼

最後,我是呼叫service儲存SysLog實體類到資料庫,你也可以直接輸出到控制檯,要儲存到資料庫,還要實現service類,mapper類和javeBean。 我就簡單貼個javaBean,其他類就不具體貼出了。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysLog implements Serializable {
    private Long id;

    private String username; //使用者名稱

    private String operation; //操作

    private String method; //方法名

    private String params; //引數

    private String ip; //ip地址

    private Date createDate; //操作時間
    //建立getter和setter方法
}
複製程式碼

javaBean中使用lombok的註解實現了get、set等方法。

以下是存進資料庫的資料,關於IP的問題是因為本地測試,部署在伺服器上就會有正常的IP地址了。

Spring Boot利用AOP獲取使用者操作實現日誌記錄

更多Spring Boot整合可瀏覽此部落格:malizhi.cn

相關文章