利用Aop记录Controller层日志信息

本文详细介绍了如何使用Spring AOP在Controller层记录日志,包括设置切点、环绕通知处理返回值问题及异常情况,同时探讨了获取真实IP地址的方法。遇到的问题如文件参数处理和返回值处理被巧妙解决。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一 起因

 

今天接到了公司的一个需求,利用aop记录Controller层的日志信息。当时2个反应,一个是啊,终于不是做crud了,第二个是,呀,我没做过,可以吗?不行也只能行了,于是马上找博文看,毕竟当初学的时候只记得有切入点,还有一些方法了

二 选择方案

估计你们看到这里就有点郁闷了吧,还有方案选择?我也被惊讶到了,话不多说,放大佬截图

是不是看的一脸懵逼,最后我还是采用了aop记录日志信息,别问,问就是网友说的话,可持续发展(我会告诉你其它的我不会吗,哼(傲娇)) 

这个我主要讲一下应用中的问题吧,因为原理解释很多博文中都有分享

第一是开启注解,第二是声明切面类(好像在水诶)

我看到有些博文还写了自定义注解,但是我这里没用到,就不说了。

/*
 * 只拦截RestController标记的方法
 */
@Pointcut("@within(org.springframework.web.bind.annotation.RestController) || @annotation(org.springframework.web.bind.annotation.RestController)")
public void requestServer() {
}

这里我是这样写的,然后大家也可以用那种表达式写,都行

   @Around("requestServer()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Object result = proceedingJoinPoint.proceed();
        EpmsLogInfo logInfo = new EpmsLogInfo();
        logInfo.setLogModule("Controller");
        logInfo.setLogDescription("查看Controller层日志记录");
        logInfo.setLogResult("Y");
        logInfo.setLogTime(DateTime.now());
        String ip = IpUtil.getRealIp(request);
        String url = request.getRequestURL().toString();
        String method = request.getMethod();
        String classMethod = String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName());
        Map<String, Object> params = getRequestParamsByProceedingJoinPoint(proceedingJoinPoint);
        String info="用户ip地址为"+ip+" " +
                "url路径为"+url+" " +
                "请求方法为"+method+" " +
                "接口路径为"+classMethod+" " +
                "入参为"+JSON.toJSONString(params);
        logInfo.setLogInfo(info);
        int insert = epmsLogInfoMapper.insert(logInfo);
        logger.info("切面类测试"+JSON.toJSONString(logInfo));
        return result;



    }

因为我是把日志存到表中的,所以大家重点看info里的就行,然后这里我自作聪明的把返回类型改为void,结果就是因为result不返回,后端得不到结束信息,迟迟不肯执行下一步,然后导致前端得不到数据,就挺尴尬了

然后还写了一个抛错注解方法,

然后讲一下ip地址吧,一开始写的是getRemoteip,后面有听说这个得不到真实地址(可能通过代理服务器查询)就找了个工具类写了进去,当然还没测试,没办法测试,行不行就不知道了,反正能得到我的地址,记录这个主要是防止dos攻击

三 总结

我看到一些大佬发的文章说是为了向测试甩锅写了个这样的日志,但是小公司肯定不是了,毕竟人家要求你全能的,更多的是为了方便查看信息。然后其它大佬说的一些方案我也会找时间去尝试,毕竟mq和elasticsearch学过之后就没怎么用了,都忘的差不多了

四 源代码

package com.chinamobile.cmss.cpms.base.common.aop;

import cn.hutool.core.date.DateTime;
import com.alibaba.fastjson.JSON;
import com.chinamobile.cmss.cpms.base.common.utils.util.IpUtil;
import com.chinamobile.cmss.cpms.fjepms.common.util.DateUtils;
import com.chinamobile.cmss.cpms.fjepms.virtualWorkFlow.mapper.EpmsLogInfoMapper;
import com.chinamobile.cmss_projcet_entity.entity.virtualWorkFlow.EpmsLogInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Modifier;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author huyuhui
 * @date 2022/7/20 17:42
 * @description
 */

@Component
@Aspect
@Slf4j
public class ControllerAspeect {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private EpmsLogInfoMapper epmsLogInfoMapper;

    /*
     * 只拦截RestController标记的方法
     */
    @Pointcut("@within(org.springframework.web.bind.annotation.RestController) || @annotation(org.springframework.web.bind.annotation.RestController)")
    public void requestServer() {
    }

    @Around("requestServer()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Object result = proceedingJoinPoint.proceed();
        EpmsLogInfo logInfo = new EpmsLogInfo();
        logInfo.setLogModule("Controller");
        logInfo.setLogDescription("查看Controller层日志记录");
        logInfo.setLogResult("Y");
        logInfo.setLogTime(DateTime.now());
        String ip = IpUtil.getRealIp(request);
        String url = request.getRequestURL().toString();
        String method = request.getMethod();
        String classMethod = String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName());
        Map<String, Object> params = getRequestParamsByProceedingJoinPoint(proceedingJoinPoint);
        String info="用户ip地址为"+ip+" " +
                "url路径为"+url+" " +
                "请求方法为"+method+" " +
                "接口路径为"+classMethod+" " +
                "入参为"+JSON.toJSONString(params);
        logInfo.setLogInfo(info);
        int insert = epmsLogInfoMapper.insert(logInfo);
        logger.info("切面类测试"+JSON.toJSONString(logInfo));
        return result;



    }
    @AfterThrowing(pointcut = "requestServer()", throwing = "e")
    public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        EpmsLogInfo logInfo = new EpmsLogInfo();
        logInfo.setLogModule("Controller");
        logInfo.setLogDescription("查看Controller层日志记录");
        logInfo.setLogResult("N");
        logInfo.setLogTime(DateTime.now());
        String ip =IpUtil.getRealIp(request);
        String url = request.getRequestURL().toString();
        String method = request.getMethod();
        String classMethod = String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        Map<String, Object> params = getRequestParamsByJoinPoint(joinPoint);
        String info="用户ip地址为"+ip+" " +
                "url路径为"+url+" " +
                "请求方法为"+method+" " +
                "接口路径为"+classMethod+" " +
                "入参为"+JSON.toJSONString(params)+" " +
                "Exception:"+e;
        logInfo.setLogInfo(info);
        int insert = epmsLogInfoMapper.insert(logInfo);
        logger.info("切面类测试"+JSON.toJSONString(logInfo));

    }

    /**
     * 获取入参
     * @param proceedingJoinPoint
     *
     * @return
     * */
    private Map<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {
        //参数名
        String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = proceedingJoinPoint.getArgs();

        return buildRequestParam(paramNames, paramValues);
    }

    private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) {
        //参数名
        String[] paramNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();
        //参数值
        Object[] paramValues = joinPoint.getArgs();

        return buildRequestParam(paramNames, paramValues);
    }

    private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
        Map<String, Object> requestParams = new HashMap<>();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];

            //如果是文件对象
            if (value instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename();  //获取文件名
            }

            requestParams.put(paramNames[i], value);
        }

        return requestParams;
    }

}
package com.chinamobile.cmss.cpms.base.common.utils.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author huyuhui
 * @date 2022/7/20 20:39
 * @description
 */

@Slf4j
public class IpUtil {
    private static final String IP_PATTERN = "^(?:(?:[01]?\\d{1,2}|2[0-4]\\d|25[0-5])\\.){3}(?:[01]?\\d{1,2}|2[0-4]\\d|25[0-5])\\b";

    private static final String UNKNOWN = "unknown";

    private static final String LOOPBACK_ADDRESS = "127.0.0.1";

    private static final String UNKNOWN_ADDRESS = "0:0:0:0:0:0:0:1";

    /**
     * @Description: 获取请求中的ip地址:过了多级反向代理,获取的ip不是唯一的,二是包含中间代理层ip。
     *
     * @return 可能有多个,例如:192.168.1.110, 192.168.1.120
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = "127.0.0.1";
        StringBuffer buffer = new StringBuffer();
        if (request != null) {
            ip = request.getHeader("x-forwarded-for");
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }

            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;

    }

    /**
     *
     * @Description: 获取客户端请求中的真实的ip地址
     *
     * 获取客户端的IP地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的。
     * 但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实IP地址。而且,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,
     * 而是一串ip值,例如:192.168.1.110, 192.168.1.120, 192.168.1.130, 192.168.1.100。其中第一个192.168.1.110才是用户真实的ip
     */
    public static String getRealIp(HttpServletRequest request) {
        StringBuffer buffer = new StringBuffer();
        String ip = LOOPBACK_ADDRESS;
        if (request == null) {
            return ip;
        }

        ip = request.getHeader("x-forwarded-for");
        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();
            if (LOOPBACK_ADDRESS.equals(ip) || UNKNOWN_ADDRESS.equals(ip)) {
                //根据网卡取本机配置的IP
                try {
                    InetAddress inet = InetAddress.getLocalHost();
                    ip = inet.getHostAddress();
                } catch (UnknownHostException e) {
                    log.error("getRealIp occurs error, caused by: ", e);
                    buffer.append("getRealIp occurs error, caused by: "+e);
                }
            }
        }

        //"***.***.***.***".length() = 15
        int ipLength = 15;
        String separator = ",";
        if (ip != null && ip.length() > ipLength) {
            if (ip.indexOf(separator) > 0) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        return buffer.append(" ip:"+ip).toString();
    }

    public static String getServiceIp() {
        Enumeration<NetworkInterface> netInterfaces = null;
        String ipsStr = "";

        try {
            netInterfaces = NetworkInterface.getNetworkInterfaces();
            while (netInterfaces.hasMoreElements()) {
                NetworkInterface ni = netInterfaces.nextElement();
                Enumeration<InetAddress> ips = ni.getInetAddresses();
                Pattern pattern = Pattern.compile(IP_PATTERN);
                while (ips.hasMoreElements()) {
                    String ip = ips.nextElement().getHostAddress();
                    Matcher matcher = pattern.matcher(ip);
                    if (matcher.matches() && !"127.0.0.1".equals(ip)) {
                        ipsStr = ip;
                    }
                }
            }
        } catch (Exception e) {
            log.error("getServiceIp occurs error, caused by: ", e);
        }

        return ipsStr;
    }

}

五 Bug

后面修改了一些问题,其实主要是判断文件集合出错了,上面只判断了文件对象

 private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
        Map<String, Object> requestParams = new HashMap<>();
        for (int i = 0; i < paramNames.length; i++) {
            Object value = paramValues[i];
            //因为格式不一样,拦截会报错,目前采取放行方法
            if(value instanceof HttpServletRequest || value instanceof HttpServletResponse){
                continue;
            }
            if (value instanceof MultipartFile[]){
                MultipartFile[] files = (MultipartFile[]) value;
                StringBuilder builder = new StringBuilder();
                for (MultipartFile file:files
                     ) {
                    builder.append(file.getOriginalFilename()) ;
                }
                value=builder.toString();
            }else if (value instanceof MultipartFile ) {
                MultipartFile file = (MultipartFile) value;
                value = file.getOriginalFilename();  //获取文件名
            }

            requestParams.put(paramNames[i], value);
        }

        return requestParams;
    }

其它方面大同小异

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值