aop实现记录后端用户访问
记录:
访问ip, 请求链接, 请求参数, 请求头, 返回信息...
问题1:
如何实现aop切片的就是Controller中的请求方法?
监听xxxController下的所有方法?
担心Controller中有部分方法是普通方法,例如private String datePares(Date date)这种
使用过滤器?
此业务不应该用过滤器
使用拦截器?
要求参照前面aop的例子完成,所以先不考虑。
解决方案:
spring的切点定义允许通过注解定位,结合execution表达式完成切点。
@Pointcut(" @annotation(org.springframework.web.bind.annotation.PostMapping)" + " || @annotation(org.springframework.web.bind.annotation.PutMapping)" + " || @annotation(org.springframework.web.bind.annotation.DeleteMapping)" + " || @annotation(org.springframework.web.bind.annotation.RequestMapping)") private void requestAspect() { } @Pointcut("execution(* com.duoyu.home..* (..))") private void backendAspect() { } @Around("requestAspect() && backendAspect()") public Object run(ProceedingJoinPoint point) throws Throwable {
问题2:
处理方法中如何得到HttpServletRequest?
spring提供了RequestContextListener这个监听器,针对请求,完成了一份数据的拷贝。(我使用的是boot,应该是直接自动配置了)
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
问题3:
获得请求ip?请求方式?url?header头信息?请求参数?body体信息?
获取请求ip前面想直接用request.getRemoteHost()获得,纠结remoteHost()和remoteAddr()有什么区别,于是上网找了个获取的方法,顺便简单针对被代理了的ip的追踪
public static String getClientIp(HttpServletRequest request) { //反向代理了的话 String ip = request.getHeader("x-forwarded-for"); //多级反向代理处理 if (ip != null && ip.contains(",")) { for (String ipItem : ip.split(",")) { if (!ipItem.equals("unknown")) { ip = ipItem; break; } } } //如果x-forwarded-for获取不到,或者是unknown,依旧使用了反向代理 if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getHeader("WL-Proxy-Client-IP"); } //没有使用反向代理 if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; }
请求uri 和请求method很简单
请求header:
Map<String, String> headerMaps = new HashMap<String, String>(); Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String key = (String) headerNames.nextElement(); String value = request.getHeader(key); headerMaps.put(key, value); } String headers = JSONObject.toJSONString(headerMaps); visitRecordDo.setHeaders(headers);
因为http请求的参数可能在body体内,也可能不在body体内,所以针对请求参数使用2种方式获得:
1、 request.getParameterMaps() 然后遍历获取
2、 request.getInputStream()读取流信息获取body数据
响应信息,直接执行切点对象即可。
Object resultObj = point.proceed();
问题4:
问题4是一个大问题,前面编写完毕后,简单使用看上去没问题。但是在我某些post操作时候后台日志报错:流已被关闭。
为什么有的请求读取request的流不会被关闭有些被关闭呢?
我发现出现流被关闭的都是使用了@RequestBody的注解的方法上。@RequestBody会读取request的body体内容且转化为对应对象。且断点发现@RequestBody注入的对象里面有值。
也就是说,先进入的方法头,读取掉了request内容,然后再被aop切面,发现流关闭报错。
怎么解决这个问题?
拦截器是基于AOP实现的,好像只有过滤器能够优先执行。
网上解决方案:
过滤器拿到请求,读取请求信息存储到byte[]数组,然后返回一个包装的装载数组数据的request给下游。
如:https://blog.csdn.net/a704397849/article/details/97267572
我这里业务不应该使用过滤器,应该稍微变通了下我的需求,只要参数即可,那么我不区分其他参数和body体参数。使用切点的获取参数方法直接获取参数列表
String params = JSONObject.toJSONString(point.getArgs());
完