AOP实现操作日志的记录功能

参考

https://blog.csdn.net/chenxihua1/article/details/82703745

需求描述

在开发某系统时,遇到了这样的一个需求:记录该系统用户的所有操作细节,只要鼠标点击了界面,对数据库进行了增删改查操作,就必修记录下来。而且这种记录,不是给软件维护者查阅的,是要给用户查阅的。

这么看来,就不能够直接记录函数(方法)的名称,必须要转化成用户看的懂的信息。

因为要添加到数据库中,并且几乎每个方法中都要记录,直接来做的话工作量太大,而且还是和日志相关,自然而然就能想到了利用AOP来实现。

实现过程

自定义注解

因为要自定义方法的名称,不能简单的输出原方法名,所以考虑到用自定义的注解来记录需要暴露给用户的名称。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited  //如果有子类,子类也可以获取到该类的注解信息
@Documented
public @interface Operation {
    String name();
}

使用的时候只要这么标注就可以了

@RestController
@RequestMapping("/front/task")
@Operation(name = "任务管理")
public class TaskController {


    @PostMapping("/getTaskByPage")
    @Operation(name = "任务查询")
    public RestResult getTaskListByPage(@RequestBody Map<String, Object> map){
       
        RestResult result = new RestResult();
    
        return result;
    }

    //任务执行
    @PostMapping("/taskExecute")
    @Operation(name = "任务执行")
    public RestResult doTaskAction(@Valid @RequestBody TaskDao taskDao) throws Exception {
        RestResult result = new RestResult();

        return result;
    }

}

AOP实现与注解解析

@Component
@Aspect
public class OperationAop {

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

    @Autowired
    OperationHistoryService operationHistoryService;

    @Pointcut("execution(* czams.front.controller.*.*(..))")
    public void method(){}

    @After("method()")
    public void after(JoinPoint joinPoint){
    }

    @AfterReturning("method()")
    public void afterReturning(JoinPoint joinPoint){
        //获取httpServlet对象
        HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();

        //获取本AOP管辖范围内的字节码对象
        //?表示 extends Object
        Class<?>clazz = joinPoint.getTarget().getClass();

        //获取类名
        String controllerName = clazz.getName();
        //如果该类标注了自定义的注解,则替换默认的名字
        if(clazz.isAnnotationPresent(Operation.class)){
            controllerName = clazz.getAnnotation(Operation.class).name();
        }

        //获取方法名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getName();
        //如果该方法标注了自定义的注解,则替换默认的名字
        if(method.isAnnotationPresent(Operation.class)){
            methodName = method.getDeclaredAnnotation(Operation.class).name();
        }

        //获取参数,由于本项目只通过json传参,所以取第一个就好
        Object[] args = joinPoint.getArgs();
        String argName = "没有条件";
        if(args != null && args.length>0){
            argName = args[0].toString();
        }

        //保存到数据库或者文件
        OperationHistoryDao operationHistoryDao = new OperationHistoryDao();
        operationHistoryDao.setOperModule(controllerName);
        operationHistoryDao.setOperAction(methodName);
        operationHistoryDao.setOperDesc(argName);
        operationHistoryDao.setOperId(getRemoteHost(request));

        System.out.println(operationHistoryDao.getOperAction());
        operationHistoryService.addOperationHistory(operationHistoryDao);
        logger.info("ip为: "+getRemoteHost(request)+ "用户执行了 "+controllerName +" 模块下的 "+ methodName + " 条件为 "+ argName);
    }

    private String getRemoteHost(HttpServletRequest request) {
        // 获取请求主机IP地址,如果通过代理进来,则透过防火墙获取真实IP地址
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            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.getRemoteAddr();
            }
        } else if (ip.length() > 15) {
            String[] ips = ip.split(",");
            for (String s : ips) {
                if (!("unknown".equalsIgnoreCase((String) s))) {
                    ip = s;
                    break;
                }
            }
        }
        return ip;
    }

}

部分业务相关的代码可以忽略,大体流程如上。

另外,有时候controller包下面某些类或者说某些方法并不需要进行日志记录,那么可以通过改变命名的方式来实现这样的功能。

例如可以规定Controller结尾的才需要记录日志:

 @Pointcut("execution(* czams.front.controller.*Controller.*(..))")
    public void method(){}
上一篇:Angular学习系列五:装饰器ViewChild


下一篇:D. AND, OR and square sum