zoukankan      html  css  js  c++  java
  • 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(){}
  • 相关阅读:
    Best Time to Buy and Sell Stock II
    Subsets II
    Subsets I
    Combinations
    Permutation Sequence
    Next Permutation
    Anagrams
    Combination-Sum II
    Combination-Sum I
    Permutations II
  • 原文地址:https://www.cnblogs.com/phdeblog/p/13387187.html
Copyright © 2011-2022 走看看