记录接口的审计信息

tech2022-10-17  106

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

最近有一个需求 需要记录每个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)