1.拦截器识别请求头token,token在登录时已经存入账号信息
2.利用token访问方法时,可以利用token获取访问者的身份信息等
3.在需要记录日志的方法上标记@Log 使此方法记录并入库
上代码
maven:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
要用到的两个实体类:
/**
* 模拟用户信息
*/
@Data
public class UserInfo {
String username;
Integer age;
}
//==========================
/**
* 模拟存储的日志 实体类
*/
@Data
public class SysLog extends BaseEntity{
//操作人id
private String operatorId;
//请求类型,0=ajax,1=普通请求
private int type;
//请求动作
private String action;
//请求主机
private String host;
//请求路径
private String uri;
//请求方式
private String httpMethod;
//请求类的方法
private String classMethod;
//请求参数
private String params;
}
1.首先自定义注解
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 Log {
String value() default ""; //默认值为空串,否则必须传入一个值
}
2.自定义切面,凡方法上有@Log的都进入此切面
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kdmins.annotation.Log;
import com.kdmins.pojo.SysLog;
import com.kdmins.pojo.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;
@Aspect
@Component
@Slf4j
public class LogAspect {
@Autowired
private HttpServletRequest request;
@Autowired
private ObjectMapper objectMapper;
/**
* 自定义切点表达式,检测到@Log 注解的进入aop切面
*/
@Pointcut("@annotation(com.kdmins.annotation.Log)")
public void pointcut() {
}
/**
* desc: 后置通知, @Before在Controller层操作前拦截(自己选择)
*
* @param joinPoint 切入点
*/
@After("pointcut()")
public void before(JoinPoint joinPoint) {
log.info("=========注解已生效==========");
try {
handleLog(joinPoint);
} catch (Exception e) {
log.error("[LogAspect] doBefore error = {}", e);
}
}
/**
* desc: 通过反射获取日志信息,存入数据库
*
* @param joinPoint 切点
* @throws Exception
*/
private void handleLog(final JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
//获取注解
Log logInfo = method.getAnnotation(Log.class);
if (logInfo == null) {
return;
}
String method1 = request.getMethod();
System.out.println("请求类型为" + method1);
SysLog sysLog = setSysLogInfo(joinPoint, method, logInfo);
System.out.println(sysLog);
//todo 日志入库
}
/**
* desc: 初始化日志信息,存入数据库
*
* @return SysLog
*/
private SysLog setSysLogInfo(JoinPoint joinPoint, Method method, Log logInfo ) {
//拦截器已经存入,直接request获取
UserInfo userInfo = (UserInfo) request.getAttribute("userInfo");
// 操作数据库日志表
SysLog sysLog = new SysLog();
//获取当前登陆人,获取操作人id,此处不写获取当前登录人的逻辑,直接写死operatorId=1
sysLog.setOperatorId(userInfo.getUsername());
// 请求信息
// sysLog.setType(AjaxUtils.isAjax(request) ? 0 : 1);
sysLog.setAction(logInfo.value());
sysLog.setHost(request.getRemoteHost());
sysLog.setUri(request.getRequestURI().toString());
sysLog.setHttpMethod(request.getMethod());
String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
sysLog.setClassMethod(classMethod);
// 请求的方法参数值
Object[] args = joinPoint.getArgs();
// 请求的方法参数名称
LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = discoverer.getParameterNames(method);
if (args != null && paramNames != null) {
StringBuilder params = new StringBuilder();
try {
params = handleParams(params, args, Arrays.asList(paramNames));
} catch (JsonProcessingException e) {
log.error("[LogAspect] setSysLogInfo handleParams error = {}", e);
}
sysLog.setParams(params.toString());
}
return sysLog;
}
/**
* desc: 处理请求参数
*
* @param params
* @param args
* @param paramNames
* @return
* @throws JsonProcessingException
*/
private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) throws JsonProcessingException {
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Map) {
Set set = ((Map) args[i]).keySet();
List list = new ArrayList();
List paramList = new ArrayList<>();
for (Object key : set) {
list.add(((Map) args[i]).get(key));
paramList.add(key);
}
return handleParams(params, list.toArray(), paramList);
} else {
if (args[i] instanceof Serializable) {
Class<?> aClass = args[i].getClass();
try {
aClass.getDeclaredMethod("toString", new Class[]{null});
// 如果不抛出NoSuchMethodException 异常则存在 toString 方法 ,安全的writeValueAsString ,否则 走 Object的 toString方法
params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i]));
} catch (NoSuchMethodException e) {
params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i].toString()));
}
} else if (args[i] instanceof MultipartFile) {
MultipartFile file = (MultipartFile) args[i];
params.append(" ").append(paramNames.get(i)).append(": ").append(file.getName());
} else {
params.append(" ").append(paramNames.get(i)).append(": ").append(args[i]);
}
}
}
return params;
}
}
3.实现拦截器
import com.kdmins.pojo.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @version 1.0
* @author: lsy
* @create: 2021-05-31 15:04
**/
@Component
@Slf4j
public class TokenInterceptor extends HandlerInterceptorAdapter {
/**
* 预处理回调方法,实现处理器的预处理(如检查登陆),第三个参数为响应的处理器,自定义Controller
* 返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("进入预拦截方法");
String token = request.getHeader("token");
log.info("获取到的 token 值 :{}", token);
//todo token中存放的账号信息等 (模拟从token中获取)
UserInfo userInfo = new UserInfo();
userInfo.setUsername("admin");
userInfo.setAge(11);
// 设置 request attribute 在Controller可以直接 用 @RequestAttribute
request.setAttribute("userInfo", userInfo);
//request.setAttribute("auditLog", null);
//token 校验成功后 可继续校验权限相关,数据库或redis都可以,此类交给spring了,可直接注入
if ("token".equals(token)) {
return true;
}
//todo 此处可以抛出异常,然后用全局异常包裹后返回响应,也可以直接返回 (建议)
response.setCharacterEncoding("utf-8");
response.getWriter().write("token校验失败");
return false;
}
/**
* 后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中
*
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//此方法必须是preHandle方法放行后才会执行
log.info("方法处理完毕,开始清理request");
// 移除request attribute
request.removeAttribute("currentAccount");
request.removeAttribute("auditLog");
request.removeAttribute("responseBody");
//清理资源
super.afterCompletion(request, response, handler, ex);
}
}
4.过滤器,并开启拦截器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 过滤器并开启token拦截器
*
* @version 1.0
* @author: lsy
* @create: 2021-05-31 15:25
**/
@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration registration = registry.addInterceptor(tokenInterceptor);
registration.addPathPatterns("/**");
registration.excludePathPatterns(
"/login", //登录方法
"/captcha",//验证码方法
"/logout" //登出方法
);
}
}
开始测试: 我用的是Postman
参数和请求头token
注解生效,日志已入库