参考
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(){}