使用AOP 記錄操作日誌

使用AOP 記錄操作日誌

  • 最近在做後臺管理系統,需要記錄下操作日誌到數據庫
  • 採用AOP 的方式來實現
  • springboot + aop + mybatis

表設計與實體類

  • 日誌表 sys_log
CREATE TABLE `sys_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日誌ID',
  `username` varchar(50) DEFAULT NULL COMMENT '操作用戶',
  `operation` text COMMENT '操作內容',
  `time` decimal(11,0) DEFAULT NULL COMMENT '耗時',
  `method` text COMMENT '操作方法',
  `params` text COMMENT '方法參數',
  `ip` varchar(64) DEFAULT NULL COMMENT '操作者IP',
  `create_time` datetime DEFAULT NULL COMMENT '創建時間',
  `location` varchar(50) DEFAULT NULL COMMENT '操作地點',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1842 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
  • 實體類
@Data
@Table(name = "sys_log")
public class SysLog implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 自增長id
     */
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String username;

    private String operation;

    private Long time;

    private String method;

    private String params;

    private String ip;

    private Date createTime;

    private String location;
}

AOP 實現

  • Log 註解
package com.xtardex.admin.cofig.data.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Caixiaowei
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String value() default "";
}
  • 定義切面 LogAspect
package com.xtardex.admin.cofig.data.aspectj;

import com.xtardex.admin.entity.SysLog;
import com.xtardex.admin.service.ISysLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * AOP 記錄用戶操作日誌
 *
 * @author Caixiaowei
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    @Autowired
    private ISysLogService logService;

    @Pointcut("@annotation(com.xtardex.admin.cofig.data.annotation.Log)")
    public void pointcut() {
        // do nothing
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Object result = null;
        long beginTime = System.currentTimeMillis();
        // 執行方法
        result = point.proceed();
        // 獲取 request
        HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
        // 設置 IP 地址
        String ip = IPUtil.getIpAddr(request);
        // 執行時長(毫秒)
        long time = System.currentTimeMillis() - beginTime;
       // 獲取登錄用戶信息, username
		String username = "";
        SysLog log = new SysLog();
        log.setUsername(username);
        log.setIp(ip);
        log.setTime(time);
        logService.saveLog(point, log);
        return result;
    }
}
  • 日誌業務層具體實現
/**
 * @DESC
 * @Author:Caixiaowei
 * @Date:2019/12/18
 * @Time:3:20 下午
 */
@Service
public class SysLogServiceImpl implements ISysLogService {

    @Autowired
    private SysLogMapper logMapper;
    @Autowired
    ObjectMapper objectMapper;
	
	/**
     * 保存日誌
     * @param joinPoint
     * @param log
     * @throws JsonProcessingException
     */
    @Override
    public void saveLog(ProceedingJoinPoint joinPoint, SysLog log) throws JsonProcessingException {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Log logAnnotation = method.getAnnotation(Log.class);
        if (logAnnotation != null) {
            // 註解上的描述
            log.setOperation(logAnnotation.value());
        }
        // 請求的類名
        String className = joinPoint.getTarget().getClass().getName();
        // 請求的方法名
        String methodName = signature.getName();
        log.setMethod(className + "." + methodName + "()");
        // 請求的方法參數值
        Object[] args = joinPoint.getArgs();
        // 請求的方法參數名稱
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        if (args != null && paramNames != null) {
            StringBuilder params = new StringBuilder();
            params = handleParams(params, args, Arrays.asList(paramNames));
            log.setParams(params.toString());
        }
        log.setCreateTime(new Date());
        // 通過ip 去過去地址
        log.setLocation("");
        // 保存系統日誌
        logMapper.saveIgnoreNull(log);
    }

    /**
     * 處理請求參數
     * @param params
     * @param args
     * @param paramNames
     * @return
     * @throws JsonProcessingException
     */
    private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) throws JsonProcessingException {
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof Map) {
                Set set = ((Map) args[i]).keySet();
                List<Object> list = new ArrayList<>();
                List<Object> paramList = new ArrayList<>();
                for (Object key : set) {
                    list.add(((Map) args[i]).get(key));
                    paramList.add(key);
                }
                return handleParams(params, list.toArray(), paramList);
            } else {
                if (args[i] instanceof Serializable) {
                    Class<?> aClass = args[i].getClass();
                    try {
                        aClass.getDeclaredMethod("toString", new Class[]{null});
                        // 如果不拋出 NoSuchMethodException 異常則存在 toString 方法 ,安全的 writeValueAsString ,否則 走 Object的 toString方法
                        params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i]));
                    } catch (NoSuchMethodException e) {
                        params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i].toString()));
                    }
                } else if (args[i] instanceof MultipartFile) {
                    MultipartFile file = (MultipartFile) args[i];
                    params.append(" ").append(paramNames.get(i)).append(": ").append(file.getName());
                } else {
                    params.append(" ").append(paramNames.get(i)).append(": ").append(args[i]);
                }
            }
        }
        return params;
    }
}

使用到的工具類

  • HttpContextUtil
package com.xtardex.admin.common.utils;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

public class HttpContextUtil {

	private HttpContextUtil(){

	}
	public static HttpServletRequest getHttpServletRequest() {
		return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
	}
}
  • IPUtil
package com.xtardex.admin.common.utils;

import javax.servlet.http.HttpServletRequest;

public class IPUtil {

	private static final String UNKNOWN = "unknown";

	protected IPUtil(){

	}

	/**
	 * 獲取 IP地址
	 * 使用 Nginx等反向代理軟件, 則不能通過 request.getRemoteAddr()獲取 IP地址
	 * 如果使用了多級反向代理的話,X-Forwarded-For的值並不止一個,而是一串IP地址,
	 * X-Forwarded-For中第一個非 unknown的有效IP字符串,則爲真實IP地址
	 */
	public static String getIpAddr(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		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.getRemoteAddr();
		}
		return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
	}

}

注意

  • 我採用的是fastmybatis, 所以mapper 實現與 mybatis 有點不同,入庫操作不一樣
發佈了27 篇原創文章 · 獲贊 4 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章