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>
项目结构
自定义审计注解
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
投稿
猜你喜欢
- 本文实例讲述了java求最大公约数与最小公倍数的方法。分享给大家供大家参考,具体如下:Gongyueshu.java文件:package m
- 在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的。但是在通过了Ap
- 引言容器是Java基础类库中使用频率最高的一部分,Java集合包中提供了大量的容器类来帮组我们简化开发,我前面的文章中对Java集合包中的关
- 概述从今天开始, 小白我将带大家开启 Jave 数据结构 & 算法的新篇章.栈栈 (Stack) 是一种运算受限的线性表, 遵循先进
- 今天本文与大家分享如何得到数组中的最大值和最小值的实例。很适合Java初学者复习数组的基本用法与流程控制语句的使用。具体如下:这个程序主要是
- init和clinit区别①init和clinit方法执行时机不同init是对象构造器方法,也就是说在程序执行 new 一个对象调用该对象类
- 面试题1:说说你对消息队列的理解,消息队列为了解决什么问题?我们公司业务系统一开始体量较小,很多组件都是单机版就足够,后来随着用户量逐渐扩大
- 通常我们在看一些源码时,发现全是T、?,晕乎乎的:sob:。于是,把泛型掌握好十分重要!什么是泛型Java 泛型(generics)是 JD
- 最近在做项目的时候有用到对两个集合中的元素进行对比求其交集的情况,因为涉及到的数据量比较大,所以在进行求两个集合中元素交集的时候,就应该考虑
- 注解@Validated和BindingResult对入参非空校验在项目当中少不了入参校验,服务器和浏览器互不信任,不能因为前端加入参判断了
- java.lang.ArrayStoreException 分析这个demo来说明怎样排查一个spring boot 1应用升级到sprin
- 目录实现效果实现方式实现步骤Blend绘制Path绘制Path绘制直线绘制曲线改变曲线形状移除Path上的线段移除Path上的点Path添加
- FPS是什么?FPS (每秒传输帧数(Frames Per Second))【摘自百度百科】FPS是图像领域中的定义,是指画面每秒传输帧数,
- Windows程序设计中的MDI(Multiple Document Interface)官方解释就是所谓的多文档界面,与此对应就有单文档界
- 背景笔者所在项目组在搭建一个全新项目的时候选用了SpringBoot3.x,项目中应用了很多SpringBoot2.x时代相关的第三方组件例
- 一、什么是 javabean ?在jsp页面中,包含html代码、css代码、java代码、以及业务逻辑处理代码等。javabean的作用就
- /** * Gets the number of cores available in this device, across all pr
- 一、开发环境:1、windows 7 企业版2、IDEA 143、JDK 1.84、Maven 3.5.25、MariaDB6、SQLYog
- 在android开发中,一说起线程的使用,很多人马上想到new Thread(){...}.start()这种方式。这样使用当然可以,但是多
- MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效