一 起因
今天接到了公司的一个需求,利用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;
}
其它方面大同小异