自定义注解类,记录请求参数并入表
使用
在这里插入代码片
1.自定义注解类 Loggable
import com...enums.LogScopeEnum;
import com...enums.LogTypeEnum;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD}) //注解作用于方法级别
@Retention(RetentionPolicy.RUNTIME) //运行时起作用
public @interface Loggable {
/**
* 是否输出日志
*/
boolean loggable() default true;
/**
* 日志信息描述,可以记录该方法的作用等信息。
*/
String descp() default "";
/**
* 请求所属模块。
*/
String module() default "";
/**
* 日志类型,可能存在多种接口类型都需要记录日志,比如dubbo接口,web接口
*/
LogTypeEnum type() default LogTypeEnum.WEB;
/**
* 日志等级
*/
String level() default "INFO";
/**
* 日志输出范围,用于标记需要记录的日志信息范围,包含入参、返回值等。
* ALL-入参和出参, BEFORE-入参, AFTER-出参
*/
LogScopeEnum scope() default LogScopeEnum.ALL;
/**
* 入参输出范围,值为入参变量名,多个则逗号分割。不为空时,入参日志仅打印include中的变量
*/
String include() default "";
/**
* 是否存入数据库
*/
boolean db() default true;
/**
* 是否输出到控制台
*
* @return
*/
boolean console() default false;
}
用到的枚举类 LogScopeEnum和LogTypeEnum BaseConstants
/**
* 日志作用范围枚举
*/
public enum LogScopeEnum {
ALL, BEFORE, AFTER;
public boolean contains(LogScopeEnum scope) {
if (this == ALL) {
return true;
} else {
return this == scope;
}
}
@Override
public String toString() {
String str = "";
switch (this) {
case ALL:
break;
case BEFORE:
str = "REQUEST";
break;
case AFTER:
str = "RESPONSE";
break;
default:
break;
}
return str;
}
}
public enum BaseConstants {
YES("YES"), NO("NO");
private String label;
private BaseConstants(String s) {
this.label = s;
}
public static BaseConstants fromCode(String baseConstants) {
switch (baseConstants) {
case "YES":
return YES;
case "NO":
return NO;
default:
return NO;
}
}
}
重点是切面类 WebLogAspect
import com.alibaba.fastjson.JSONObject;
import com...interfaces.Loggable;
import com...common.LogMessage;
import com...BaseConstants;
import com...enums.LogScopeEnum;
import com...service.LogMessageService;
import com...utils.HttpUtils;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtMethod;
import javassist.bytecode.LocalVariableAttribute;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* 日志记录AOP实现
*/
@Aspect
@Component
public class WebLogAspect {
private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
@Autowired
private LogMessageService logMessageService;
@Autowired
private HttpUtils httpUtils;
// 开始时间
private long startTime = 0L;
// 结束时间
private long endTime = 0L;
/**
* Controller层切点
*/
// @Pointcut("execution(public * *(..))")
@Pointcut("execution(* *..controller..*.*(..))")
public void controllerAspect() {
}
/**
* 前置通知 用于拦截Controller层记录用户的操作
*
* @param joinPoint 切点
*/
@Before("controllerAspect()")
public void doBeforeInServiceLayer(JoinPoint joinPoint) {
}
/**
* 配置controller环绕通知,使用在方法aspect()上注册的切入点
*
* @param point 切点
* @return
* @throws Throwable
*/
@Around("controllerAspect()")
public Object doAround(ProceedingJoinPoint point) throws Throwable {
// 获取request
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = servletRequestAttributes.getRequest();
// 获取ip地址
String ipAddress = httpUtils.getIpAddress(request);
//目标方法实体
Method method = ((MethodSignature) point.getSignature()).getMethod();
boolean hasMethodLogAnno = method.isAnnotationPresent(Loggable.class);
//没加注解 直接执行返回结果
/*if (!hasMethodLogAnno) {
return point.proceed();
}*/
//日志打印外部开关
String logSwitch = BaseConstants.YES.toString();
//记录日志信息
LogMessage logMessage = new LogMessage();
//方法注解实体
Loggable methodLogAnno = method.getAnnotation(Loggable.class);
//处理入参日志
handleRequestLog(point, methodLogAnno, request, logMessage, logSwitch, ipAddress);
//执行目标方法内容,获取执行结果
Object result = point.proceed();
//处理接口响应日志
handleResponseLog(logSwitch, logMessage, methodLogAnno, result);
return result;
}
/**
* 处理入参日志
*
* @param point 切点
* @param methodLogAnnon 日志注解
* @param logMessage 日志信息记录实体
*/
private void handleRequestLog(ProceedingJoinPoint point, Loggable methodLogAnnon, HttpServletRequest request,
LogMessage logMessage, String logSwitch, String ipAddress) throws Exception {
String paramsText = "";
//参数列表
String includeParam = methodLogAnnon.include();
Map<String, Object> methodParamNames = getMethodParamNames(
point.getTarget().getClass(), point.getSignature().getName(), includeParam);
Map<String, Object> params = getArgsMap(
point, methodParamNames);
if (params != null) {
//序列化参数列表
paramsText = JSONObject.toJSONString(params);
}
//判断是否输出日志
if (methodLogAnnon.loggable()
&& methodLogAnnon.scope().contains(LogScopeEnum.BEFORE)
&& methodLogAnnon.console()
&& StringUtils.equals(logSwitch, BaseConstants.YES.toString())) {
//打印入参日志
logger.info("【{}】 【{}】 【{}】 接口入参成功!, 方法名称:【{}】, 所属模块:【{}】, 请求参数:【{}】",
request.getRequestURI(), methodLogAnnon.descp(), ipAddress,
point.getSignature().getName(), methodLogAnnon.module(), paramsText);
}
// 1.构造开始时间
startTime = System.currentTimeMillis();
logMessage.setBeginTime(new Date(startTime));
// 2.接口描述
logMessage.setDescription(methodLogAnnon.descp());
String systemId = "";
if ((null != point.getArgs()) && (point.getArgs().length > 0)) {
try {
Object[] args = point.getArgs();
String objStr = JSONObject.toJSONString(args[0]);
// 如果转换json异常,则为get请求,直接从获取systemId
JSONObject jsonObject = JSONObject.parseObject(objStr);
systemId = null == jsonObject.get("systemId") ? "" : jsonObject.get("systemId").toString();
} catch (Exception e) {
if (null != params && null != params.get("systemId")) {
systemId = params.get("systemId").toString();
}
}
}
// 3.构造用户名
logMessage.setUsername(systemId + ":" + ipAddress);
// 5.构造调用接口
logMessage.setQueryinterface(request.getRequestURI());
// 5.构造调用方法
logMessage.setMethod(point.getSignature().getName());
// 6.参数
logMessage.setParameter(paramsText);
// 7.请求所属模块
logMessage.setModule(methodLogAnnon.module().toString());
}
/**
* 处理响应日志
*
* @param logSwitch 外部日志开关,用于外部动态开启日志打印
* @param logMessage 日志记录信息实体
* @param methodLogAnnon 日志注解实体
* @param result 接口执行结果
*/
private void handleResponseLog(String logSwitch, LogMessage logMessage, Loggable methodLogAnnon, Object result) {
endTime = System.currentTimeMillis();
//结束时间
logMessage.setEndTime(new Date());
//消耗时间
logMessage.setSpendTime(endTime - startTime);
//是否输出日志
if (methodLogAnnon.loggable()
&& methodLogAnnon.scope().contains(LogScopeEnum.AFTER)) {
//判断是否入库
if (methodLogAnnon.db()) {
// 入库
try {
logMessageService.saveLogMessage(logMessage);
} catch (Exception e) {
e.printStackTrace();
}
}
//判断是否输出到控制台
if (methodLogAnnon.console()
&& StringUtils.equals(logSwitch, BaseConstants.YES.toString())) {
//输出到控制台
System.out.println(logMessage);
}
}
}
/**
* 获取方法入参变量名
*
* @param cls 触发的类
* @param methodName 触发的方法名
* @param include 需要打印的变量名
* @return
* @throws Exception
*/
private Map<String, Object> getMethodParamNames(Class cls,
String methodName, String include) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(cls));
CtMethod cm = pool.get(cls.getName()).getDeclaredMethod(methodName);
LocalVariableAttribute attr = (LocalVariableAttribute) cm
.getMethodInfo().getCodeAttribute()
.getAttribute(LocalVariableAttribute.tag);
if (attr == null) {
throw new Exception("attr is null");
} else {
Map<String, Object> paramNames = new HashMap<>();
int paramNamesLen = cm.getParameterTypes().length;
int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
if (StringUtils.isEmpty(include)) {
for (int i = 0; i < paramNamesLen; i++) {
paramNames.put(attr.variableName(i + pos), i);
}
} else { // 若include不为空
for (int i = 0; i < paramNamesLen; i++) {
String paramName = attr.variableName(i + pos);
if (include.indexOf(paramName) > -1) {
paramNames.put(paramName, i);
}
}
}
return paramNames;
}
}
/**
* 组装入参Map
*
* @param point 切点
* @param methodParamNames 参数名称集合
* @return
*/
private Map getArgsMap(ProceedingJoinPoint point,
Map<String, Object> methodParamNames) {
Object[] args = point.getArgs();
if (null == methodParamNames) {
return Collections.EMPTY_MAP;
}
for (Map.Entry<String, Object> entry : methodParamNames.entrySet()) {
int index = Integer.valueOf(String.valueOf(entry.getValue()));
if (args != null && args.length > 0) {
if (args[index] instanceof ServletRequest || args[index] instanceof ServletResponse || args[index] instanceof MultipartFile) {
continue;
} else {
Object arg = (null == args[index] ? "" : args[index]);
methodParamNames.put(entry.getKey(), arg);
}
}
}
return methodParamNames;
}
}
日志实体类LogMessage
import lombok.Data;
import java.util.Date;
@Data
public class LogMessage {
private static final long serialVersionUID = 1L;
// @Column(name = "id")
private Integer id; // id ,后续考虑uuid
// @Column(name = "username")
private String username; // 用户名
// @Column(name = "queryinterface")
private String queryinterface; // 请求接口
// @Column(name = "method")
private String method; // 请求方法
// @Column(name = "parameter")
private String parameter; // 请求参数
// @Column(name = "description") // 描述信息
private String description;
// @Column(name = "module") // 请求所属模块
private String module;
// @Column(name = "begin_time")
private Date beginTime; // 开始时间
// @Column(name = "end_time")
private Date endTime; // 结束时间
// @Column(name = "spend_time")
private Long spendTime; // 耗费时间
// @Column(name = "d_date")
private Date insertDate; // 操作时间 插入数据的日期
public LogMessage() {
this.insertDate = new Date();
}
public LogMessage(Integer id, String username, String queryinterface, String method, String parameter, String description, String module, Date beginTime, Date endTime, Long spendTime) {
this.id = id;
this.username = username;
this.queryinterface = queryinterface;
this.method = method;
this.parameter = parameter;
this.description = description;
this.module = module;
this.beginTime = beginTime;
this.endTime = endTime;
this.spendTime = spendTime;
this.insertDate = new Date();
}
}
记录日志的Service类 LogMessageService
import com...common.LogMessage;
import com...mapper.WebLogAspectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class LogMessageService {
@Autowired
private WebLogAspectMapper webLogAspectMapper;
/**
* 保存日志记录
*
* @param logMessage
* @return
*/
public void saveLogMessage(LogMessage logMessage) {
webLogAspectMapper.saveLogMessage(logMessage);
}
/**
* 保存新日志记录
*
* @param username 用户名
* @param queryinterface 接口
* @param method 调用的方法名
* @param parameter 参数
* @param description 描述
* @param module 接口所属模块
* @param startTime 开始时间 System.currentTimeMillis();
*/
public void saveNewLogMessage(String username,
String queryinterface,
String method,
String parameter,
String description,
String module,
Long startTime) {
Long endTime = System.currentTimeMillis();
LogMessage logMessage = new LogMessage(null, username,
queryinterface,
method,
parameter,
description,
module,
new Date(), new Date(endTime), endTime - startTime);
webLogAspectMapper.saveLogMessage(logMessage);
}
}
Mapper类
import com.htsc.thfx.performance.entity.common.LogMessage;
public interface WebLogAspectMapper {
void saveLogMessage(LogMessage logMessage);
}
获取请求ip
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
@Component
public class HttpUtils {
private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class);
/**
* 获取IP地址
*/
public String getIpAddress(HttpServletRequest request) {
String ipAddress = request.getHeader("X-Forwarded-For");
if (StringUtils.isEmpty(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if ("127.0.0.1".equals(ipAddress) || "0:0:0:0:0:0:0:1".equals(ipAddress)) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (Exception e) {
logger.error("get native ip by network card failed ==>, msg:{}", e.getMessage());
}
if (null != inet && inet.getHostAddress() != null) {
ipAddress = inet.getHostAddress();
}
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) {
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
return ipAddress;
}
}