为了简洁,该篇文章没有入库等操作,看到该文章的小可爱们 可以根据需求而定。
最近有一个需求 需要记录每个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='删除用户信息:用户名称@肖战肖战}
最后的最后,初次写文章,还请各位大佬们 多多指教!