Springboot 利用AOP編程實現切面日誌

前言

踏入Springboot這個坑,你就別想再跳出來。這個自動配置確實是非常地舒服,幫助我們減少了很多的工作。使得編寫業務代碼的時間佔比相對更大。那麼這裏就講一下面向切面的日誌收集。筆者使用lombok插件,這也是一款非常不錯的插件,需要在pom引入依賴。

導入依賴

	<!-- apo -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-aop</artifactId>
	</dependency>
	
	<!-- lombok-->
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<optional>true</optional>
	</dependency>

創建日誌對象

	@Data
	@NoArgsConstructor
	@Accessors(chain = true)
	public class SysOperLog {
	    /**
	     * 日誌主鍵
	     */
	    private Long id;
	
	    /**
	     * 接口名稱
	     */
	    private String title;
	
	    /**
	     * 操作名稱
	     */
	    private String operName;
	
	    /**
	     * 接口類名稱
	     */
	    private String className;
	
	    /**
	     * 操作人員
	     */
	    private String operUser;
	
	    /**
	     * 請求URL
	     */
	    private String operUrl;
	
	    /**
	     * 請求方式
	     */
	    private String requestMethod;
	
	    /**
	     * 主機地址
	     */
	    private String operIp;
	
	    /**
	     * 源端口
	     */
	    private Integer operPort;
	
	    /**
	     * 操作地點
	     */
	    private String operLocation;
	
	    /**
	     * 請求參數
	     */
	    private String operParam;
	
	    /**
	     * 返回參數
	     */
	    private String jsonResult;
	
	    /**
	     * 操作狀態(0正常 1異常)
	     */
	    private Boolean status;
	
	    /**
	     * 錯誤消息
	     */
	    private String errorMsg;
	
	    /**
	     * 操作時間
	     */
	    private Date operTime;
	}

具體實現代碼

@Aspect
@Component
public class LogAdvice {

    @Resource
    private SysOperLogService sysOperLogService;

    // 定義切面
    private static final String POINT_CUT = "execution(* cn.example.project.controller.*.*(..))";


    @Pointcut(POINT_CUT)
    public void controllerAspect() {
    }


    private void handler(JoinPoint joinPoint, Object result, Exception e) {
        // 獲得request
        HttpServletRequest request = ((ServletRequestAttributes) (Objects.requireNonNull(RequestContextHolder.getRequestAttributes()))).getRequest();

        // 獲得接口所屬模塊
        Class<?> cls = joinPoint.getSignature().getDeclaringType();
        Api annotation = AnnotationUtils.findAnnotation(cls, Api.class);
        String title = "遺失的接口標題";
        if (annotation != null) {
            title = annotation.tags()[0];
        }

        // 獲得接口名稱
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        String operName = method.getAnnotation(ApiOperation.class).value();

        // 獲得接口所在類
        String className = joinPoint.getSignature().getDeclaringTypeName();
        className = className.substring(className.lastIndexOf(".") + 1);

        // 獲得請求用戶
        String loginName = request.getHeader("token");
        loginName = Pattern.compile("^Bearer ").matcher(loginName).replaceFirst("");

        // 獲得請求url
        String requestURI = request.getRequestURI();

        // 獲得請求方法
        String requestMethod = request.getMethod();

        // 獲得請求IP
        String requestIp = IPUtils.getIpAddr(request);

        // 獲得請求參數: 連接在url後面的參數
        String params = getParamsString(joinPoint, request);


        // 獲得源端口
        int requestPort = request.getRemotePort();

        // 日誌請求是否正常
        boolean status = e == null;

        // 封裝操作日誌
        SysOperLog sysOperLog = new SysOperLog();
        sysOperLog
                .setTitle(title)
                .setOperName(operName)
                .setClassName(className)
                .setOperUser(loginName)
                .setOperUrl(requestURI)
                .setRequestMethod(requestMethod)
                .setOperIp(requestIp)
                .setOperPort(requestPort)
                .setOperLocation(IPUtils.getLocation(requestIp))
                .setOperParam(params)
                .setStatus(status)
                .setErrorMsg(status ? null : e.getMessage())
                .setOperTime(new Date());

        // 保存
        sysOperLogService.add(sysOperLog);
    }

    private String getParamsString(JoinPoint joinPoint, HttpServletRequest request) {
        StringBuilder params = new StringBuilder();

        // 獲得請求體參數
        if (Pattern.compile("post|put|patch", Pattern.CASE_INSENSITIVE).matcher(request.getMethod()).find()) {
            Object[] args = joinPoint.getArgs();
            for (Object o: args) {
                String string = JSON.toJSONString(o);
                params.append(string);
            }
        }

        // 獲得queryString
        if (StringUtils.isNotBlank(request.getQueryString())) {
            params.append(request.getQueryString());
        }
        return params.toString();
    }


    // 返回之後的處理
    @AfterReturning(value = "controllerAspect()", returning = "returnResult")
    public void afterReturning(JoinPoint joinPoint, Object returnResult) {
        handler(joinPoint, returnResult, null);
    }

    // 拋出異常的處理
    @AfterThrowing(value = "controllerAspect()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        handler(joinPoint, null, e);
    }

}

其中的參數獲得,是獲得url後面的和請求體的。如果使用restful接口,直接作爲url一部分的需要根據自己的業務去匹配。日誌也添加了接口描述,這個描述來源於swagger註解。

具體結果:
在這裏插入圖片描述
獲得的還有些信息,並未一一渲染到頁面,原因是頁面撐不下了啊。

注意: 其中的獲得ip歸屬地,需要自己去獲得,通過httpclient請求ip查詢接口。幾個好用的ip查詢接口見另一篇博客:分享2020 幾個好用的ip地址歸屬地查詢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章