软件编程
位置:首页>> 软件编程>> java编程>> Spring AOP如何自定义注解实现审计或日志记录(完整代码)

Spring AOP如何自定义注解实现审计或日志记录(完整代码)

作者:DayDayUp丶  发布时间:2022-03-28 02:19:34 

标签: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>

项目结构

Spring AOP如何自定义注解实现审计或日志记录(完整代码)

自定义审计注解

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);
           auditService.insert(msg.get());
       } catch (Exception e) {
           LOGGER.error("Error occured while auditing, cause by: ", e);
       } finally {
           // FATAL: remove threadLocal and set threadLocal = null
       }
       Object rtn = pjp.proceed();
       return rtn;
   }

/**
    * 带参返回
    */
   @AfterReturning(pointcut = "audit()", returning = "rc")
   public void doAfterReturning(ResponseContent rc) {
       LOGGER.debug("afterReturning with returnType..");
   }

/**
    * 不带参返回
    */
   @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/90490023

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com