记录接口的审计信息[改变前后数据]

为了简洁,该篇文章没有入库等操作,看到该文章的小可爱们 可以根据需求而定。

最近有一个需求 需要记录每个controller中接口的信息,需要记录模块名称、ip、实体类的前后值变化、当前登录人等信息。

于是 写了该文章,详细信息 可自动添加(如当前登录人信息、ip等)。希望可以帮助有同样困难的小伙伴们。

1、自定义一个注解(用来记录需要做审计处理的接口)

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

/**
 * 内部审计注解 
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SystemAuditStarter {
	
	String operation() default "";
	
	String router() default "";
	
	String note() default "";
}

2、定义一个实体类(存放用户的操作、接口的调用等信息) 

import java.io.Serializable;


/**
 * 系统审计实体类
 */
public class SystemAudit implements Serializable {

	private static final long serialVersionUID = 1L;
	private Integer 	id;				// ID
	private String 		operationTime;	// 操作时间
	private String      operation;		// 操作动作(增加|删除|修改|查询)
	private String 		routerName;		// 操作模块
	private String 		note;			// 操作内描述
	public static final String INSERT = "增加";
	public static final String DELETE = "删除";
	public static final String UPDATE = "更新";
	public static final String SEARCH = "查询";

	
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getOperationTime() {
		return operationTime;
	}
	public void setOperationTime(String operationTime) {
		this.operationTime = operationTime;
	}
	public String getOperation() {
		return operation;
	}
	public void setOperation(String operation) {
		this.operation = operation;
	}
	public String getRouterName() {
		return routerName;
	}
	public void setRouterName(String routerName) {
		this.routerName = routerName;
	}
	public String getNote() {
		return note;
	}
	public void setNote(String note) {
		this.note = note;
	}

	public static long getSerialVersionUID() {
		return serialVersionUID;
	}

	public static String getINSERT() {
		return INSERT;
	}

	public static String getDELETE() {
		return DELETE;
	}

	public static String getUPDATE() {
		return UPDATE;
	}

	public static String getSEARCH() {
		return SEARCH;
	}

	@Override
	public String toString() {
		return "SystemAudit{" +
				"operationTime=" + operationTime +
				", operation='" + operation + '\'' +
				", routerName='" + routerName + '\'' +
				", note='" + note + '}';
	}
}
3、 对接口的aop切分【主要记录接口的内容 并进行入库操作】
import com.bigdata.bigdata.entity.SystemAudit;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

@Aspect
@Component
public class SystemAuditRespect {


    private final Logger logger = LoggerFactory.getLogger(SystemAuditRespect.class);

    public static String  note;

    @Pointcut("@annotation(auditStarter)")
    public void logPointCut(SystemAuditStarter auditStarter) {
        logger.debug("审计使用");
    }

    //默认是成功状态  只修改备注信息
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static void alterAnnotationOn(Method method,String a) throws Exception {
        method.setAccessible(true);
        boolean methodHasAnno = method.isAnnotationPresent(SystemAuditStarter.class); // 是否指定类型的注释存在于此元素上
        if (methodHasAnno) {
            // 得到注解
            SystemAuditStarter methodAnno = method.getAnnotation(SystemAuditStarter.class);
            // 修改
            InvocationHandler h = Proxy.getInvocationHandler(methodAnno);
            // annotation注解的membervalues
            Field hField = h.getClass().getDeclaredField("memberValues");
            // 因为这个字段是 private final 修饰,所以要打开权限
            hField.setAccessible(true);
            // 获取 memberValues
            Map<String, Object> memberValues = (Map) hField.get(h);
            memberValues.put("note", note+":"+a);
        }
    }

    @After("logPointCut(auditLog)")
    public void doAfter(JoinPoint joinPoint, SystemAuditStarter auditLog) {
        try {
            //尝试获取动态修改的注解AuditLog
            auditLog = SystemAuditRespect.getControllerMethodAuditLog(joinPoint);
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            SystemAudit systemAudit = new SystemAudit();
            systemAudit.setNote(auditLog.note());
            systemAudit.setOperation(auditLog.operation());
            systemAudit.setRouterName(auditLog.router());
            SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String optionTime = simpleDateFormat.format(new Date());
            systemAudit.setOperationTime(optionTime);
            logger.info("插入数据库的信息:"+systemAudit.toString());
        } catch (Exception e1) {
        }
    }


    /**
     *
     * @Title: getControllerMethodAuditLog
     * @Description: 获取注解中对方法的AuditLog信息,可以获取到反射动态修改后的AuditLog
     * @param @param joinPoint
     * @param @return
     * @param @throws Exception 参数
     * @return AuditLog 返回类型
     * @throws
     */
    @SuppressWarnings("rawtypes")
    public static SystemAuditStarter getControllerMethodAuditLog(JoinPoint joinPoint) throws Exception {
        // 类名
        String targetName = joinPoint.getTarget().getClass().getName();
        // 方法名
        String methodName = joinPoint.getSignature().getName();
        // 参数
        Object[] arguments = joinPoint.getArgs();
        // 切点类
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        SystemAuditStarter auditLog = null;
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    auditLog = method.getAnnotation(SystemAuditStarter.class);
                    break;
                }
            }
        }
        return auditLog;
    }

    /**
     * before 记录之前
     * @param joinPoint
     */
    @SuppressWarnings("")
    @Before("@annotation(auditLog)")
    public void doBefore(JoinPoint joinPoint,SystemAuditStarter auditLog) {
        try {
            note=auditLog.note();
        } catch (Exception e1) {
        }
    }
}

4、自定义一个注解(记录字段的前后值变化) 用于在实体类中的字段

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

/**
 * 记录某个字段是否发生变化
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Inherited
public @interface FieldNameStarter {

    String value() default "";
}
5、用户实体类信息 

import com.bigdata.bigdata.util.audit.FieldNameStarter;

import java.io.Serializable;

public class User implements Serializable {

    private int id;

    @FieldNameStarter("名字")
    private String name;
    @FieldNameStarter("性别")
    private String sex;
    @FieldNameStarter("是否删除")
    private int isDelete; ///1否2是

    public String getName() {
        return name;
    }

    public int getIsDelete() {
        return isDelete;
    }

    public void setIsDelete(int isDelete) {
        this.isDelete = isDelete;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}
6、记录实体类的前后变化值

import org.springframework.stereotype.Component;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * 记录每个实体类的属性前后变化
 * <T>代表 实体类的名字
 * @param <T>  表示传入的两个实体类参数 是同一个实体类
 */
@Component
public class BeanChangeUtil<T> {

    public BeanChangeUtil() {
    }

    /**
     * 修改时  oleBean和newBean都不为null  且同为一种实体类,只是前后数据不同
     * 增加时  oleBean不为null,newBean为null
     * 改查时  oleBean和newBean都为null
     * @param oldBean  改变之前的实体类
     * @param newBean  改变之后的实体类
     * @param option  操作: 增删改查
     * @return
     */
    public String contrastObj(Object oldBean, Object newBean,String option) {
        String name="admin";

        // 创建字符串拼接对象
        StringBuilder str = new StringBuilder();
        String lists="";
        // 转换为传入的泛型T
        T pojo1 = (T) oldBean;
        T pojo2 = (T) newBean;
        if(option.equals("删除") || option.equals("查询")){
            lists=name;
        }
        if(option.equals("更新")){
            Class clazz =null;
            Field[] fields=null;
            if(pojo1!=null){
                // 通过反射获取类的Class对象
                clazz = pojo1.getClass();
                // 获取类型及字段属性
                fields= clazz.getDeclaredFields();
            }
            if(pojo2!=null){
                lists = jdk8OrAfter(fields, pojo1, pojo2, str, clazz);
            }
        }
        if(option.equals("增加")){
            StringBuffer stringBuffer=new StringBuffer();
            if(pojo1!=null){
                // 通过反射获取类的Class对象
                Class clazz = pojo1.getClass();
                // 获取类型及字段属性
                Field[] fields = clazz.getDeclaredFields();
                for(Field f:fields){
                    if(f.isAnnotationPresent(FieldNameStarter.class)){
                        try {
                            PropertyDescriptor pd = new PropertyDescriptor(f.getName(), clazz);
                            // 获取对应属性值
                            Method getMethod = pd.getReadMethod();
                            Object o1 = getMethod.invoke(pojo1);

                            if (o1!=null && o1.toString()!=null) {
                                if(f.getAnnotation(FieldNameStarter.class).value().equals("名字")){
                                    o1=o1.toString();
                                }
                                if(f.getAnnotation(FieldNameStarter.class).value().equals("性别")){
                                    o1=o1.toString();
                                }
                                if(f.getAnnotation(FieldNameStarter.class).value().equals("是否删除")){
                                    if(o1.toString().equals("1")){
                                        o1="否";
                                    }
                                    if(o1.toString().equals("2")){
                                        o1="是";
                                    }
                                }
                                boolean equals = o1.toString().equals("");
                                if(equals){
                                    o1="空值";
                                }else{
                                    o1=o1.toString();
                                }
                                stringBuffer.append(f.getAnnotation(FieldNameStarter.class).value()+"@"+o1+"、");
                            }
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }
                lists=stringBuffer.toString().substring(0,stringBuffer.toString().length() - 1);
            }
        }
        return lists;
    }

    public String jdk8OrAfter(Field[] fields, T pojo1, T pojo2, StringBuilder str, Class clazz){
        String substring="";

        for(Field f:fields){
            if(f.isAnnotationPresent(FieldNameStarter.class)){
                try {
                    PropertyDescriptor pd = new PropertyDescriptor(f.getName(), clazz);
                    // 获取对应属性值
                    Method getMethod = pd.getReadMethod();
                    Object o1 = getMethod.invoke(pojo1);
                    Object o2 = getMethod.invoke(pojo2);

                    if (o1 != null&&o2!=null) {
                        if (!o1.toString().equals(o2.toString())) {
                            if(f.getAnnotation(FieldNameStarter.class).value().equals("是否删除")){
                                if(o1.toString().equals("1")){
                                    o1="否";
                                }
                                if(o1.toString().equals("2")){
                                    o1="是";
                                }
                                if(o2.toString().equals("1")){
                                    o2="否";
                                }
                                if(o2.toString().equals("2")){
                                    o2="是";
                                }
                            }
                            boolean equals = o1.toString().equals("");
                            if(equals){
                                o1="空值";
                            }else{
                                o1=o1.toString();
                            }
                            str.append(f.getAnnotation(FieldNameStarter.class).value()+":"+o1+"->"+o2+"、");
                        }
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }

        if(str.toString()!=null && !str.toString().equals("")){
            substring = str.toString().substring(0,str.toString().length() - 1);
        }
        return substring;
    }

}
7、此时写一个controller 来验证增删改查的记录信息
import com.bigdata.bigdata.entity.SystemAudit;
import com.bigdata.bigdata.entity.User;
import com.bigdata.bigdata.util.audit.BeanChangeUtil;
import com.bigdata.bigdata.util.audit.SystemAuditRespect;
import com.bigdata.bigdata.util.audit.SystemAuditStarter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Method;

@RestController
public class UserController {

    private static final String ROUTERNAME = "用户管理";

    @Autowired
    private BeanChangeUtil<User> beanChangeUtil;

    @SystemAuditStarter(operation = SystemAudit.SEARCH,
            router = ROUTERNAME,
            note = "根据id查询用户信息")
    @GetMapping("/queryUserById")
    public User queryUserById(){

        User user=new User();
        user.setIsDelete(1);
        user.setName("肖战肖战");
        user.setSex("男");
        //审计开始
        String s = "用户名称@"+user.getName();
        Method method = null;
        try {
            method = UserController.class.getMethod("queryUserById");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        try {
            SystemAuditRespect.alterAnnotationOn(method, s);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //审计结束

        return user;
    }

    @SystemAuditStarter(operation = SystemAudit.INSERT,
            router = ROUTERNAME,
            note = "添加用户信息")
    @GetMapping("/insertUser")
    public int insertUser(){

        User user=new User();
        user.setIsDelete(1);
        user.setName("肖战肖战");
        user.setSex("");
        //审计开始
        String s = beanChangeUtil.contrastObj(user, null, SystemAudit.INSERT);
        Method method = null;
        try {
            method = UserController.class.getMethod("insertUser");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        try {
            SystemAuditRespect.alterAnnotationOn(method, s);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //审计结束

        return 1;
    }

    @SystemAuditStarter(operation = SystemAudit.UPDATE,
            router = ROUTERNAME,
            note = "修改用户信息")
    @GetMapping("/updateUser")
    public int updateUser(){

        User user=new User();
        user.setIsDelete(1);
        user.setName("肖战肖战");
        user.setSex("男");
        User user1=new User();
        user1.setIsDelete(2);
        user1.setName("王一博王一博");
        user1.setSex("男");
        //审计开始
        String s = beanChangeUtil.contrastObj(user, user1, SystemAudit.UPDATE);
        Method method = null;
        try {
            method = UserController.class.getMethod("updateUser");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        try {
            SystemAuditRespect.alterAnnotationOn(method, s);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //审计结束

        return 1;
    }

    @SystemAuditStarter(operation = SystemAudit.DELETE,
            router = ROUTERNAME,
            note = "删除用户信息")
    @GetMapping("/deleteUser")
    public int deleteUser(@RequestParam("id")int id){

        User user=new User();
        user.setIsDelete(1);
        user.setName("肖战肖战");
        user.setSex("男");

        //审计开始
        String s = "用户名称@"+user.getName();
        Method method = null;
        try {
            method = UserController.class.getMethod("deleteUser",int.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        try {
            SystemAuditRespect.alterAnnotationOn(method, s);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //审计结束
        return 1;
    }

}

在浏览器地址栏中访问该接口 (为了方便,访问接口全部使用的@GetMapping,并且没有入库等操作,写的测试数据,所以看到该栏的同行们 可根据情况进行修改)
则会在上面步骤三中有打印审计实体类的信息日志 可以在打印过程中 看到审计表的信息

查询接口(queryUserById)的结果图:

插入数据库的信息:SystemAudit{operationTime=2020-09-03 10:35:56, operation='查询', routerName='用户管理', note='根据id查询用户信息:用户名称@肖战肖战}

 新增接口(insertUser)的结果图:

插入数据库的信息:SystemAudit{operationTime=2020-09-03 10:37:14, operation='增加', routerName='用户管理', note='添加用户信息:名字@肖战肖战、性别@空值、是否删除@否}

 修改接口(updateUser)的结果图:

插入数据库的信息:SystemAudit{operationTime=2020-09-03 10:37:54, operation='更新', routerName='用户管理', note='修改用户信息:名字:肖战肖战->王一博王一博、是否删除:否->是}

 删除接口(deleteUser)的结果图:

插入数据库的信息:SystemAudit{operationTime=2020-09-03 10:38:30, operation='删除', routerName='用户管理', note='删除用户信息:用户名称@肖战肖战}

最后的最后,初次写文章,还请各位大佬们 多多指教!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值