Spring AOP來自定義注解實現審計或日志記錄(完整代碼)


環境准備

JDK 1.8,Springboot 2.1.3.RELEASE,spring-boot-starter-aop.2.1.4.RELEASE.jar,aspectjrt.1.9.2.jar,aspectjweaver.1.9.2.jar,pom依賴如下:

<!-- 添加aspectj -->
<dependency>
	 <groupId>org.springframework.boot</groupId>
	 <artifactId>spring-boot-starter-aop</artifactId>
	 <version>2.1.4.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjrt</artifactId>
	<version>1.9.2</version>
</dependency>
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.9.2</version>
</dependency>

項目結構

自定義審計注解

package com.cjia.common.annotation;

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

@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.METHOD })
public @interface Audit {

	/**
	 * 模塊代碼
	 */
	int moudleCode() default -1;
	
	/**
	 * 擴展信息,用戶返回操作類型及參數
	 */
	Class<?> extension();
}

定義切面類

package com.cjia.common.aspect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.cjia.common.Moudle;
import com.cjia.common.Operate;
import com.cjia.common.annotation.Audit;
import com.cjia.common.reflect.BaseValueReturn;
import com.cjia.model.AuditMessage;
import com.cjia.model.ResponseContent;
import com.cjia.model.User;
import com.cjia.service.AuditService;
import com.cjia.service.UserService;

@Aspect
@Component
public class AuditAop {

	private static final Logger LOGGER = LoggerFactory.getLogger(AuditAop.class);
	@Autowired
	private AuditService auditService;
	@Autowired
	private UserService userService;
	// 操作發起者
	ThreadLocal<User> user = new ThreadLocal<>();
	// 操作應用
	ThreadLocal<Integer> appId = new ThreadLocal<>();
	// 功能模塊
	ThreadLocal<Integer> moudleCode = new ThreadLocal<>();
	// 操作類型
	ThreadLocal<Integer> operateType = new ThreadLocal<>();
	// IP地址
	ThreadLocal<String> ip = new ThreadLocal<>();
	// 操作時間點
	ThreadLocal<String> operateTime = new ThreadLocal<>();
	// 操作信息實體
	ThreadLocal<AuditMessage> msg = new ThreadLocal<>();
	// 對CIS,額外的菜單id信息extension
	ThreadLocal<String> extension = new ThreadLocal<>();

	// 聲明AOP切入點
	@Pointcut("@annotation(com.cjia.common.annotation.Audit)")
	public void audit() {
	}

	@Before("audit()")
	public void beforeExec(JoinPoint joinPoint) {
	}

	@After("audit()")
	public void afterExec(JoinPoint joinPoint) {
	}

	@Around("audit()")
	public Object aroundExec(ProceedingJoinPoint pjp) throws Throwable {
		try {
			HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
			extension.set(request.getParameter("extension"));
			operateTime.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
			ip.set(getIpAddr(request));
			appId.set(Integer.valueOf(request.getParameter("appId")));

			MethodSignature ms = (MethodSignature) pjp.getSignature();
			Method method = ms.getMethod();
			// 獲取注解的參數信息
			Audit auditAnno = method.getAnnotation(Audit.class);
			moudleCode.set(auditAnno.moudleCode());
			Class<?> external = auditAnno.extension();
			Constructor<?> cons = external.getConstructor(Integer.class, HttpServletRequest.class);
			BaseValueReturn bvr = (BaseValueReturn) cons.newInstance(moudleCode.get(), request);
			Map<String, Object> reqInfo = bvr.getRequestInfo();
			Integer operate_type = Integer.valueOf(reqInfo.get(BaseValueReturn.OPT) + "");
			operateType.set(operate_type);
			Object target = reqInfo.get(BaseValueReturn.TARGET);
			// 獲取當前登錄的用戶,需注意:登錄時沒有msgKey
			String msgKey = request.getParameter("msgKey");
			User loginUser = null;
			if (operate_type.equals(Operate.LOGIN)) {
				List<User> users = userService.selectByEmail(String.valueOf(target));
				if (users != null && !users.isEmpty()) {
					loginUser = users.get(0);
				}
			} else if (msgKey != null) {
				loginUser = userService.selectMsgFromRedisPurely(msgKey);
			}
			user.set(loginUser);

			AuditMessage am = new AuditMessage();
			// am.setUserId需判空,代表過期
			if (loginUser != null) {
				am.setUserId(loginUser.getId());
			} else {
				am.setUserId(-1);
			}
			am.setAppId(appId.get());
			am.setMoudleCode(moudleCode.get());
			am.setOperateType(operateType.get());
			am.setIp(ip.get());
			am.setOperateTime(operateTime.get());
			// TODO details=target
			String details = "";
			if (moudleCode.get() == Moudle.DATA && loginUser != null) {
				details = (String) target;
			} else {
				// TODO 其他模塊
			}
			am.setTarget(details);
			msg.set(am);
		} catch (Exception e) {
			LOGGER.error("Error occured while auditing, cause by: ", e);
		}
		Object rtn = pjp.proceed();
		return rtn;
	}

	/**
	 * 帶參返回
	 */
	@AfterReturning(pointcut = "audit()", returning = "rc")
	public void doAfterReturning(ResponseContent rc) {
		try {
			// 返回碼SUCCESS才去審計記錄
			if (ResponseContent.RESPCODE_SUCCESS.equals(rc.getRespCode())) {
				if (null == msg.get() && extension.get() != null) {
					LOGGER.warn("Warning occured, because msg is null!");
				} else if (!(operateType.get() == Operate.FETCH_DATA
						&& (extension.get() == null || extension.get().isEmpty()))) {
					auditService.insert(msg.get());
				}
			}
		} catch (Exception e) {
			LOGGER.warn("Warning occured while afterReturning, cause by: ", e);
		}
	}

	/**
	 * 不帶參返回
	 */
	@AfterReturning(pointcut = "audit()")
	public void doAfterReturning(JoinPoint joinPoint) {
		LOGGER.debug("afterReturning without returnType..");
	}

	@AfterThrowing(pointcut = "audit()", throwing = "e")
	public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
		LOGGER.error("Error occured, cause by {}", e.getMessage());
	}

	private String getRemoteHost(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 ip.equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ip;
	}

	/** 
     * 獲取用戶真實IP地址,不使用request.getRemoteAddr()的原因是有可能用戶使用了代理軟件方式避免真實IP地址, 
     * 可是,如果通過了多級反向代理的話,X-Forwarded-For的值並不止一個,而是一串IP值 
     *  
     * @return ip
     */
    private String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for"); 
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {  
            // 多次反向代理后會有多個ip值,第一個ip才是真實ip
            if( ip.indexOf(",")!=-1 ){
                ip = ip.split(",")[0];
            }
        }  
        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.getHeader("X-Real-IP");  
        }  
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
            ip = request.getRemoteAddr();  
        } 
        return ip;  
    }

}

注意事項

第81行的HttpServletRequest request最好設置為局部變量,或ThreadLocal修飾的成員變量,而非普通成員變量,防止異步請求較多,導致有的request丟失參數的離奇問題。

定義返回值處理基類

package com.cjia.common.reflect;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

public abstract class BaseValueReturn {
	public static final String OPT = "operate";
	public static final String TARGET = "target";

	protected Integer moudleCode;
	protected HttpServletRequest request;

	public BaseValueReturn(Integer moudleCode, HttpServletRequest request) {
		super();
		this.moudleCode = moudleCode;
		this.request = request;
	}

	/**
	 * 返回操作動作operate和操作目標target
	 */
	public abstract Map<String, Object> getRequestInfo();
}

定義返回值處理子類

package com.cjia.common.reflect;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.cjia.common.Operate;

public class DataValueReturn extends BaseValueReturn {

	public DataValueReturn(Integer moudleCode, HttpServletRequest request) {
		super(moudleCode, request);
	}

	@Override
	public Map<String, Object> getRequestInfo() {
		Map<String, Object> info = new HashMap<>();
		info.put(OPT, Operate.FETCH_DATA);
		String menuId = request.getParameter("extension");
		info.put(TARGET, menuId);
		return info;
	}

}
package com.cjia.common.reflect;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.cjia.common.Operate;

public class LoginValueReturn extends BaseValueReturn {

	public LoginValueReturn(Integer moudleCode, HttpServletRequest request) {
		super(moudleCode, request);
	}

	@Override
	public Map<String, Object> getRequestInfo() {
		Map<String, Object> info = new HashMap<>();
		info.put(OPT, Operate.LOGIN);
		String email = request.getParameter("email");
		info.put(TARGET, email);
		return info;
	}

}

定義功能模塊類

package com.cjia.common;

import java.util.HashMap;
import java.util.Map;

public class Moudle {

	public static final int LOGIN = 1;
	public static final int DATA = 2;
	public static final int USER = 3;
	// TODO more moudles
	
	private static final Map<Integer, String> moudleMap = new HashMap<>();
	static {
		moudleMap.put(Moudle.LOGIN, "登錄");
		moudleMap.put(Moudle.DATA, "業務數據");
		moudleMap.put(Moudle.USER, "用戶");
	}
	
	public static String getNameByCode(int code) {
		return moudleMap.get(code);
	}
}

定義操作類

package com.cjia.common;

import java.util.HashMap;
import java.util.Map;

public class Operate {

	public static final int LOGIN = 1;
	// 內部系統獲取數據
	public static final int FETCH_DATA = 2;
	public static final int INSERT = 3;
	public static final int DELETE = 4;
	public static final int UPDATE = 5;
	public static final int SEARCH = 6;
	// TODO more opts
	
	private static final Map<Integer, String> optMap = new HashMap<>();
	static {
		optMap.put(Operate.LOGIN, "登錄");
		optMap.put(Operate.FETCH_DATA, "獲取業務數據");
		optMap.put(Operate.INSERT, "新增");
		optMap.put(Operate.DELETE, "刪除");
		optMap.put(Operate.UPDATE, "修改");
		optMap.put(Operate.SEARCH, "查詢");
	}
	
	public static String getNameByCode(int code) {
		return optMap.get(code);
	}
}

定義審計信息實體類

package com.cjia.model;

public class AuditMessage {

	private int id;
	private int userId;
	private int appId;
	private int moudleCode;
	private int operateType;
	private String ip;
	private String operateTime;
	private String target;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public int getUserId() {
		return userId;
	}

	public void setUserId(int userId) {
		this.userId = userId;
	}

	public int getAppId() {
		return appId;
	}

	public void setAppId(int appId) {
		this.appId = appId;
	}

	public int getMoudleCode() {
		return moudleCode;
	}

	public void setMoudleCode(int moudleCode) {
		this.moudleCode = moudleCode;
	}

	public int getOperateType() {
		return operateType;
	}

	public void setOperateType(int operateType) {
		this.operateType = operateType;
	}

	public String getIp() {
		return ip;
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	public String getOperateTime() {
		return operateTime;
	}

	public void setOperateTime(String operateTime) {
		this.operateTime = operateTime;
	}

	public String getTarget() {
		return target;
	}

	public void setTarget(String target) {
		this.target = target;
	}

	@Override
	public String toString() {
		return "AuditMessage [id=" + id + ", userId=" + userId + ", appId=" + appId + ", moudleCode=" + moudleCode
				+ ", operateType=" + operateType + ", ip=" + ip + ", operateTime=" + operateTime + ", target=" + target
				+ "]";
	}

}

書寫mapper文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cjia.dao.AuditMessageMapper">

	<resultMap id="BaseResultMap" type="com.cjia.model.AuditMessage">
		<result column="id" jdbcType="INTEGER" property="id" />
		<result column="user_id" jdbcType="VARCHAR" property="userId" />
		<result column="app_id" jdbcType="VARCHAR" property="appId" />
		<result column="moudle_code" jdbcType="VARCHAR" property="moudleCode" />
		<result column="operate_type" jdbcType="INTEGER" property="operateType" />
		<result column="ip" jdbcType="VARCHAR" property="ip" />
		<result column="operate_time" jdbcType="INTEGER" property="operateTime" />
		<result column="target" jdbcType="VARCHAR" property="target" />
	</resultMap>

	<!-- 對應的插入字段的名字 -->
	<sql id="keys">
		<trim suffixOverrides=",">
			<if test="id!=null and id!=''">
				id,
			</if>
			<if test="userId!=null and userId!=''">
				user_id,
			</if>
			<if test="appId!=null and appId!=''">
				app_id,
			</if>
			<if test="moudleCode!=null and moudleCode!=''">
				moudle_code,
			</if>
			<if test="operateType!=null and operateType!=''">
				operate_type,
			</if>
			<if test="ip!=null and ip!=''">
				ip,
			</if>
			<if test="operateTime!=null and operateTime!=''">
				operate_time,
			</if>
			<if test="target!=null and target!=''">
				target,
			</if>
		</trim>
	</sql>

	<!-- 對應的插入字段的值 -->
	<sql id="values">
		<trim suffixOverrides=",">
			<if test="id!=null and id!=''">
				#{id},
			</if>
			<if test="userId!=null and userId!=''">
				#{userId},
			</if>
			<if test="appId!=null and appId!=''">
				#{appId},
			</if>
			<if test="moudleCode!=null and moudleCode!=''">
				#{moudleCode},
			</if>
			<if test="operateType!=null and operateType!=''">
				#{operateType},
			</if>
			<if test="ip!=null and ip!=''">
				#{ip},
			</if>
			<if test="operateTime!=null and operateTime!=''">
				#{operateTime},
			</if>
			<if test="target!=null and target!=''">
				#{target},
			</if>
		</trim>
	</sql>
	<insert id="insert" parameterType="com.cjia.model.AuditMessage">
		insert into audit_message(
		<include refid="keys" />
		)
		values(
		<include refid="values" />
		)
	</insert>

	<select id="selectAll" resultMap="BaseResultMap">
		select * from audit_message
	</select>

</mapper>

開啟AOP攔截

package com.cjia;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@MapperScan("com.cjia.dao")
@EnableAspectJAutoProxy(exposeProxy=true)
public class DataserviceApplication {

	public static void main(String[] args) {
		SpringApplication.run(DataserviceApplication.class, args);
	}

}

或在配置文件中:

<!-- 開啟AOP攔截 -->
<aop:aspectj-autoproxy proxy-target-class="true" /> 
<mvc:annotation-driven />
<!-- 定義Spring描述Bean的范圍,使切面類AuditAop可以被掃描到  -->
<context:component-scan base-package="**.common" >
	 <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

注解配置

package com.cjia.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSON;
import com.cjia.common.InnerApp;
import com.cjia.common.Moudle;
import com.cjia.common.ResConstance.UserGroup;
import com.cjia.common.annotation.Audit;
import com.cjia.common.reflect.LoginValueReturn;
import com.cjia.model.Menu;
import com.cjia.model.ResponseContent;
import com.cjia.model.User;
import com.cjia.service.MenuService;
import com.cjia.service.UserService;
import com.cjia.utils.DigestUtil;

/**
 * 處理內部系統登錄信息驗證
 */
@Controller
public class LoginController {

	private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);

	@Autowired
	private UserService userService;

	@Autowired
	private MenuService menuService;

	/**
	 * 處理登錄頁表單提交信息
	 */
	@ResponseBody
	@PostMapping("/doLogin")
	@Audit(moudleCode = Moudle.LOGIN, extension = LoginValueReturn.class)
	public ResponseContent doLogin(HttpServletRequest req) throws Exception {
		String email = req.getParameter("email");
		String password = req.getParameter("password");
		int appId = Integer.valueOf(req.getParameter("appId"));
		// 獲取到的這么多user(同一賬戶)只是role或門店不同,密碼等其他都一致
		List<User> users = new ArrayList<>();
		// 對SRP報表系統不允許管家登錄
		if (appId == InnerApp.CIS) {
			users = userService.selectByEmail4CIS(email);
		} else {
			users = userService.selectByEmail(email);
		}
		ResponseContent responseContent = new ResponseContent();
		if (users != null && !users.isEmpty()
				&& users.get(0).getPassword().equals(DigestUtil.threeHash(password, users.get(0).getSecurityKey()))) {
			User user = users.get(0);
			LOGGER.debug("登陸驗證成功{}", user);
			// 設置roleIdLst和branchMap
			List<Integer> roleIdLst = users.stream().map(User::getRoleId).collect(Collectors.toList());
			Map<String, String> branchMap = new HashMap<>();
			Map<Integer, List<Menu>> menuMap = getAllMenus(users);
			for (User u : users) {
				branchMap.put(u.getBranchId() + "", u.getBranchName());
			}
			user.setRoleIdLst(roleIdLst);
			user.setBranchMap(branchMap);
			// 給user設置菜單列表
			user.setMenuMap(menuMap);
			// 登陸成功后,需要看此用戶的信息是否存在於redis,存在則刷新過期時間,不存在則插入記錄
			String msgKey = userService.insertIntoRedis(user);
			responseContent.setRespCode(ResponseContent.RESPCODE_SUCCESS);
			responseContent.setMessage("登陸成功");
			responseContent.setData(msgKey);
		} else {
			LOGGER.debug("登陸驗證失敗");
			responseContent.setRespCode(ResponseContent.RESPCODE_FAILURE);
			responseContent.setMessage("登陸失敗,請檢查用戶賬號密碼是否正確");
		}
		String jsonString = JSON.toJSONString(responseContent);
		LOGGER.debug("登錄返回信息:{}", jsonString);
		return responseContent;
	}
}

擴展資料可參閱https://blog.csdn.net/songzehao/article/details/80171646


以上。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM