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