SpringBoot自定义日志注解实现方法日志记录和参数获取
- 前言
- 一、定义注解
- 1.注解
- 2.枚举类
- 二、AOP处理
- 1.配置织入点
- 2.处理注解方法
- 3.获取参数
- 4.完整切面处理代码
- 三、工具类
- 总结
前言
我们在日常业务操作中需要记录很多日志,可以在我们需要的方法中对日志进行保存操作,但是对业务代码入侵性大。使用切面针对控制类进行处理灵活度不高,因此我们可以使用自定义注解来针对方法进行日志记录
一、定义注解
1.注解
定义一个 @LogMethod
注解,作用于方法上
- title:记录标题
- operatorType:操作类别,这里使用了枚举类
- isSaveRequestData:默认为true,是否获取请求参数
/**
* 自定义方法日志记录注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogMethod {
/**
* 记录标题
*/
public String title() default "";
/**
* 操作类别
*/
public UserStatusCode operatorType() default UserStatusCode.OTHER;
/**
* 是否保存请求参数
*/
public boolean isSaveRequestData() default true;
}
2.枚举类
接口定义了两个方法,一个获取 code
编码,一个获取 desc
描述
public interface ResultStandard {
int getCode();
String getDesc();
}
自定义用户操作枚举类,可向数据库存储 描述信息
或者 编码值
public enum UserStatusCode implements ResultStandard {
OTHER("其他操作", 10),
USER_QUERY("用户查询", 11),
USER_EXPORT("用户导出", 12),
WEA_QUERY("天气查询", 13)
;
private String desc;
private int code;
private UserStatusCode(String desc, int code) {
this.desc = desc;
this.code = code;
}
public String getDesc() {
return desc;
}
public int getCode() {
return code;
}
}
二、AOP处理
1.配置织入点
这里使用了后置通知 AfterReturning
,在处理完方法后获取到结果数据然后进行处理
/**
* 方法操作日志记录处理
*/
@Aspect
@Component
public class LogMethodAspect {
private static final Logger log = LoggerFactory.getLogger(LogMethodAspect.class);
/**
* 配置织入点
*/
@Pointcut("@annotation(com.angel.annotation.LogMethod)")
public void logMethodPointCut() { }
/**
* 处理完请求后执行
*/
@AfterReturning(pointcut = "logMethodPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
handleLog(joinPoint, null, jsonResult);
}
/**
* 拦截异常操作
*/
@AfterThrowing(value = "logMethodPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
handleLog(joinPoint, e, null);
}
// 代码在下面...
}
2.处理注解方法
- 根据
JoinPoint
目标对象,利用反射获取方法名称,方法路径 - 使用
getAnnotationLog()
方法获取LogMethod
注解 - 使用 IP工具类和 Security 可以获取到用户数据和访问的IP
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
try {
// 获得注解
LogMethod controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null) {
return;
}
Map<String, Object> logMap = new HashMap<String, Object>();
// 获取当前的用户
// ...
// 获取请求IP,网上IP工具类多的ya批
// ...
// 获取返回参数
JSONObject j = JSON.parseObject(JSON.toJSONString(jsonResult));
String msg = j.getString("message");
String code = j.getString("status");
String methodResult = "{\"msg\":\""+msg+"\",\"code\":"+code+"}";
logMap.put("requestResult", methodResult);
// 获取请求URL
logMap.put("requestUrl", ServletUtils.getRequest().getRequestURI());
// 获取异常信息
if (e != null) {
logMap.put("requestError", StringUtils.substring(e.getMessage(), 0, 2000));
}
// 获取方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
String methodStr = className + "." + methodName + "()";
logMap.put("method", methodStr);
// 获取请求方式
logMap.put("requestMethod",ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, logMap);
// 保存至数据库操作
System.out.println(logMap);
} catch (Exception exp) {
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
/**
*获取存在的注解
*/
private LogMethod getAnnotationLog(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(LogMethod.class);
}
return null;
}
3.获取参数
- 由上一步获取到的注解类,获取到注解的参数以及是否保存请求参数
- 可以根据
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE
来得到REST
风格的参数
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*/
public void getControllerMethodDescription(JoinPoint joinPoint, LogMethod log, Map<String, Object> logMap) throws Exception {
// 操作类型
logMap.put("operatorType", log.operatorType().getDesc());
// 标题
logMap.put("title", log.title());
// 是否需要保存request,参数和值
if (log.isSaveRequestData()) {
// 获取参数的信息
if ("PUT".equals(logMap.get("requestMethod")) || "POST".equals(logMap.get("requestMethod"))) {
String params = argsArrayToString(joinPoint.getArgs());
logMap.put("requestParams", StringUtils.substring(params, 0, 2000));
} else {
Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
logMap.put("requestParams", StringUtils.substring(paramsMap.toString(), 0, 2000));
}
}
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
String params = "";
if (paramsArray != null && paramsArray.length > 0) {
for (int i = 0; i < paramsArray.length; i++) {
if (!isFilterObject(paramsArray[i])) {
Object jsonObj = JSON.toJSON(paramsArray[i]);
params += jsonObj.toString() + " ";
}
}
}
return params.trim();
}
4.完整切面处理代码
完整代码如下,需要用到 ServletUtils
工具类
package com.angel.aspect;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.angel.annotation.LogMethod;
import com.angel.util.ServletUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* 方法操作日志记录处理
*/
@Aspect
@Component
public class LogMethodAspect {
private static final Logger log = LoggerFactory.getLogger(LogMethodAspect.class);
/**
* 配置织入点
*/
@Pointcut("@annotation(com.angel.annotation.LogMethod)")
public void logMethodPointCut() { }
/**
* 处理完请求后执行
*/
@AfterReturning(pointcut = "logMethodPointCut()", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
handleLog(joinPoint, null, jsonResult);
}
/**
* 拦截异常操作
*/
@AfterThrowing(value = "logMethodPointCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
handleLog(joinPoint, e, null);
}
protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
try {
// 获得注解
LogMethod controllerLog = getAnnotationLog(joinPoint);
if (controllerLog == null) {
return;
}
Map<String, Object> logMap = new HashMap<String, Object>();
// 获取当前的用户
// ...
// 获取请求IP,网上IP工具类多的ya批
// ...
// 获取返回参数
JSONObject j = JSON.parseObject(JSON.toJSONString(jsonResult));
String msg = j.getString("message");
String code = j.getString("status");
String methodResult = "{\"msg\":\""+msg+"\",\"code\":"+code+"}";
logMap.put("requestResult", methodResult);
// 获取请求URL
logMap.put("requestUrl", ServletUtils.getRequest().getRequestURI());
// 获取异常信息
if (e != null) {
logMap.put("requestError", StringUtils.substring(e.getMessage(), 0, 2000));
}
// 获取方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
String methodStr = className + "." + methodName + "()";
logMap.put("method", methodStr);
// 获取请求方式
logMap.put("requestMethod",ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, logMap);
// 保存至数据库操作
System.out.println(logMap);
} catch (Exception exp) {
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*/
public void getControllerMethodDescription(JoinPoint joinPoint, LogMethod log, Map<String, Object> logMap) throws Exception {
// 操作类型
logMap.put("operatorType", log.operatorType().getDesc());
// 标题
logMap.put("title", log.title());
// 是否需要保存request,参数和值
if (log.isSaveRequestData()) {
// 获取参数的信息
if ("PUT".equals(logMap.get("requestMethod")) || "POST".equals(logMap.get("requestMethod"))) {
String params = argsArrayToString(joinPoint.getArgs());
logMap.put("requestParams", StringUtils.substring(params, 0, 2000));
} else {
Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
logMap.put("requestParams", StringUtils.substring(paramsMap.toString(), 0, 2000));
}
}
}
/**
* 获取存在的注解
*/
private LogMethod getAnnotationLog(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(LogMethod.class);
}
return null;
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray) {
String params = "";
if (paramsArray != null && paramsArray.length > 0) {
for (int i = 0; i < paramsArray.length; i++) {
if (!isFilterObject(paramsArray[i])) {
Object jsonObj = JSON.toJSON(paramsArray[i]);
params += jsonObj.toString() + " ";
}
}
}
return params.trim();
}
/**
* 判断是否需要过滤的对象。
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Iterator iter = collection.iterator(); iter.hasNext(); ) {
return iter.next() instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry entry = (Map.Entry) iter.next();
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
}
}
三、工具类
需要用到的Servlet工具 ServletUtils
类如下,也可以在任意请求处理方法内调用该类,例如
- getRequest:获取请求对象
- renderString:渲染给客户端数据
- getParameter:获取参数
package com.angel.util;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* 客户端工具类
*/
public class ServletUtils {
/**
* 获取String参数
*/
public static String getParameter(String name) {
return getRequest().getParameter(name);
}
/**
* 获取String参数
*/
public static String getParameter(String name, String defaultValue) {
Object value = getRequest().getParameter(name);
return value == null ? defaultValue : (String) value;
}
/**
* 获取Integer参数
*/
public static Integer getParameterToInt(String name) {
return getParameterToInt(name, null);
}
/**
* 获取Integer参数
*/
public static Integer getParameterToInt(String name, Integer defaultValue) {
Object value = getRequest().getParameter(name);
if (value == null) {
return defaultValue;
}
try {
return Integer.parseInt(value.toString());
} catch (Exception e) {
return defaultValue;
}
}
/**
* 获取request
*/
public static HttpServletRequest getRequest() {
return getRequestAttributes().getRequest();
}
/**
* 获取response
*/
public static HttpServletResponse getResponse() {
return getRequestAttributes().getResponse();
}
/**
* 获取session
*/
public static HttpSession getSession() {
return getRequest().getSession();
}
public static ServletRequestAttributes getRequestAttributes() {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
}
/**
* 将字符串渲染到客户端
*/
public static String renderString(HttpServletResponse response, String string) {
try {
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 是否是Ajax异步请求
*/
public static boolean isAjaxRequest(HttpServletRequest request) {
String accept = request.getHeader("accept");
if (accept != null && accept.indexOf("application/json") != -1) {
return true;
}
String xRequestedWith = request.getHeader("X-Requested-With");
if (xRequestedWith != null && xRequestedWith.indexOf("XMLHttpRequest") != -1) {
return true;
}
String uri = request.getRequestURI();
if (inStringIgnoreCase(uri, ".json", ".xml")) {
return true;
}
String ajax = request.getParameter("__ajax");
if (inStringIgnoreCase(ajax, "json", "xml")) {
return true;
}
return false;
}
/**
* 需要用到的转换方法,是否包含字符串
*/
public static boolean inStringIgnoreCase(String str, String... strs) {
if (str != null && strs != null) {
for (String s : strs) {
if (str.equalsIgnoreCase(s == null ? "" : s.trim())) {
return true;
}
}
}
return false;
}
}
总结
来发送个GET请求吧,使用@PathVariable获取参数,参数名为dayCount
@GetMapping("/dayinfo/{dayCount}")
@LogMethod(title = "获取未来天气预报", operatorType = UserStatusCode.WEA_QUERY)
@ApiOperation(value = "获取未来天气预报", notes = "获取未来天气预报", response = Result.class)
// 方法省略...
来看下 logMap 的内容
最后把Map对象换成大家自己的操作日志实体类,然后就可以对其进行对心所欲的 插入
操作了,咳咳…
当然要插入到数据库,有瘾的话Redis也可以。实体类在手,日志你有