feat(新增): JWT错误异常处理,AOP切面日志

This commit is contained in:
Bunny 2024-06-01 16:47:19 +08:00
parent 065adbbe9e
commit 817d0d33b2
12 changed files with 350 additions and 81 deletions

View File

@ -1,27 +0,0 @@
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bunny</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>common-utils</artifactId>
<packaging>jar</packaging>
<name>common-utils</name>
<url>https://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>cn.bunny</groupId>
<artifactId>dao</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

View File

@ -14,7 +14,6 @@
<modules>
<module>service-utils</module>
<module>common-generator</module>
<module>common-utils</module>
</modules>
<dependencies>

View File

@ -2,10 +2,10 @@ package cn.bunny.common.service.interceptor;
import cn.bunny.common.service.context.BaseContext;
import cn.bunny.common.service.utils.JwtHelper;
import cn.bunny.common.service.utils.ResponseUtil;
import cn.bunny.pojo.result.Result;
import cn.bunny.pojo.result.ResultCodeEnum;
import cn.bunny.pojo.result.constant.RedisUserConstant;
import cn.bunny.vo.system.login.LoginVo;
import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@ -16,34 +16,53 @@ import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Map;
import static cn.bunny.common.service.utils.ResponseHandlerUtil.loginAuthHandler;
@Component
@Slf4j
public class UserTokenInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("UserTokenInterceptor===>设置拦截器");
// 获取token
String token = request.getHeader("token");
Long userId = JwtHelper.getUserId(token);
String username = JwtHelper.getUsername(token);
Object redisUserinfo = redisTemplate.opsForValue().get(RedisUserConstant.getUserLoginInfoPrefix(username));
// token为空时
if (token == null) {
return loginAuthHandler(response, ResultCodeEnum.LOGIN_AUTH);
}
// 当token过期
if (JwtHelper.isExpired(token)) {
return loginAuthHandler(response, ResultCodeEnum.AUTHENTICATION_EXPIRED);
}
// 将token转成实体类
Map<String, Object> tokenByMap = JwtHelper.getMapByToken(token);
LoginVo loginVo = JSONObject.parseObject(JSONObject.toJSONString(tokenByMap), LoginVo.class);
// 获取用户id和用户邮箱
Long userId = loginVo.getId();
String email = loginVo.getEmail();
String redisKey = RedisUserConstant.getUserLoginInfoPrefix(email);
Object redisUserinfo = redisTemplate.opsForValue().get(redisKey);
// 不是动态方法直接返回
if (!(handler instanceof HandlerMethod)) return true;
// 解析不到userId
if (userId == null) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.LOGIN_AUTH));
return false;
}
if (redisUserinfo == null) {
ResponseUtil.out(response, Result.error(ResultCodeEnum.LOGIN_AUTH));
return false;
// 解析不到userIdRedis中没有这个用户
if (userId == null || redisUserinfo == null) {
return loginAuthHandler(response, ResultCodeEnum.LOGIN_AUTH);
}
BaseContext.setUserId(userId);
BaseContext.setUsername(username);
BaseContext.setUsername(email);
return true;
}

View File

@ -1,4 +1,4 @@
package cn.bunny.common.utils;
package cn.bunny.common.service.utils;
/**
* 计算 kb mb gb

View File

@ -1,6 +1,7 @@
package cn.bunny.common.service.utils;
import io.jsonwebtoken.*;
import io.micrometer.common.lang.Nullable;
import org.springframework.util.StringUtils;
import java.util.Date;
@ -205,12 +206,17 @@ public class JwtHelper {
* @param token token
* @return map集合
*/
public static Map<String, Object> getTokenByMap(String token) {
if (!StringUtils.hasText(token)) return null;
Claims claims = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody();
public static Map<String, Object> getMapByToken(String token) {
try {
if (!StringUtils.hasText(token)) return null;
Claims claims = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody();
// body 值转为map
return new HashMap<>(claims);
// body 值转为map
return new HashMap<>(claims);
} catch (Exception exception) {
return null;
}
}
/**
@ -220,12 +226,17 @@ public class JwtHelper {
* @param signKey 秘钥
* @return map集合
*/
public static Map<String, Object> getTokenByMap(String token, String signKey) {
if (!StringUtils.hasText(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(signKey).parseClaimsJws(token);
Claims body = claimsJws.getBody();
// body 值转为map
return new HashMap<>(body);
public static Map<String, Object> getMapByToken(String token, String signKey) {
try {
if (!StringUtils.hasText(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(signKey).parseClaimsJws(token);
Claims body = claimsJws.getBody();
// body 值转为map
return new HashMap<>(body);
} catch (Exception exception) {
return null;
}
}
/**
@ -234,12 +245,22 @@ public class JwtHelper {
* @param token token
* @return 主题
*/
public static String getTokenBySubject(String token) {
if (!StringUtils.hasText(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims body = claimsJws.getBody();
public static String getSubjectByToken(String token) {
return getSubjectByTokenHandler(token, tokenSignKey);
}
return body.getSubject();
@Nullable
private static String getSubjectByTokenHandler(String token, String tokenSignKey) {
try {
if (!StringUtils.hasText(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims body = claimsJws.getBody();
return body.getSubject();
} catch (Exception exception) {
return null;
}
}
/**
@ -248,12 +269,8 @@ public class JwtHelper {
* @param token token
* @return 主题
*/
public static String getTokenBySubject(String token, String tokenSignKey) {
if (!StringUtils.hasText(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims body = claimsJws.getBody();
return body.getSubject();
public static String getSubjectByToken(String token, String tokenSignKey) {
return getSubjectByTokenHandler(token, tokenSignKey);
}
/**
@ -263,12 +280,16 @@ public class JwtHelper {
* @return 用户ID
*/
public static Long getUserId(String token) {
if (!StringUtils.hasText(token)) return null;
try {
if (!StringUtils.hasText(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return Long.valueOf(String.valueOf(claims.get("userId")));
return Long.valueOf(String.valueOf(claims.get("userId")));
} catch (Exception exception) {
return null;
}
}
/**
@ -278,11 +299,15 @@ public class JwtHelper {
* @return 用户名
*/
public static String getUsername(String token) {
if (!StringUtils.hasText(token)) return "";
try {
if (!StringUtils.hasText(token)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return (String) claims.get("userName");
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return (String) claims.get("userName");
} catch (Exception exception) {
return null;
}
}
/**
@ -292,10 +317,7 @@ public class JwtHelper {
* @return 是否过期
*/
public static boolean isExpired(String token) {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Date expiration = claimsJws.getBody().getExpiration();
return expiration != null && expiration.before(new Date());
return isExpiredUtil(token, tokenSignKey);
}
/**
@ -305,9 +327,24 @@ public class JwtHelper {
* @return 是否过期
*/
public static boolean isExpired(String token, String tokenSignKey) {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Date expiration = claimsJws.getBody().getExpiration();
return isExpiredUtil(token, tokenSignKey);
}
return expiration != null && expiration.before(new Date());
/**
* 判断是否过期
*
* @param token token
* @param tokenSignKey key值
* @return 是否过期
*/
private static boolean isExpiredUtil(String token, String tokenSignKey) {
try {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Date expiration = claimsJws.getBody().getExpiration();
return expiration != null && expiration.before(new Date());
} catch (Exception exception) {
return true;
}
}
}

View File

@ -0,0 +1,12 @@
package cn.bunny.common.service.utils;
import cn.bunny.pojo.result.Result;
import cn.bunny.pojo.result.ResultCodeEnum;
import jakarta.servlet.http.HttpServletResponse;
public class ResponseHandlerUtil {
public static boolean loginAuthHandler(HttpServletResponse response, ResultCodeEnum loginAuth) {
ResponseUtil.out(response, Result.error(loginAuth));
return false;
}
}

View File

@ -0,0 +1,75 @@
package cn.bunny.entity.system.log;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 系统日志表
* </p>
*
* @author Bunny
* @since 2024-05-31
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("system_log")
@ApiModel(value = "SystemLog对象", description = "系统日志表")
public class SystemLog implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
@ApiModelProperty("所在类路径")
private String classPath;
@ApiModelProperty("执行方法名称")
private String methodName;
@ApiModelProperty("入参内容")
private Object args;
@ApiModelProperty("返回参数")
private String result;
@ApiModelProperty("报错堆栈")
private String errorStack;
@ApiModelProperty("报错")
private String errorMessage;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("用户名")
private String nickname;
@ApiModelProperty("当前用户token")
private String token;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
@ApiModelProperty("更新用户")
private Long updateUser;
@ApiModelProperty("是否被删除")
private Boolean isDeleted;
}

View File

@ -0,0 +1,11 @@
package cn.bunny.service.aop.annotation;
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 SkipLog {
}

View File

@ -11,7 +11,7 @@ import org.springframework.stereotype.Component;
@Component
@Slf4j
public class AutoFillAspect {
@Pointcut("execution(* cn.bunny.service.*.*(..))")
@Pointcut("execution(* cn.bunny.service.web.service.impl..*(..))")
public void autoFillPointcut() {
}

View File

@ -0,0 +1,97 @@
package cn.bunny.service.aop.aspect;
import cn.bunny.common.service.utils.JwtHelper;
import cn.bunny.entity.system.log.SystemLog;
import cn.bunny.service.aop.annotation.SkipLog;
import cn.bunny.service.mapper.SystemLogMapper;
import cn.bunny.vo.system.login.LoginVo;
import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
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.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Arrays;
import java.util.Map;
@Aspect
@Component
@Slf4j
public class AutoLogAspect {
@Autowired
private SystemLogMapper systemLogMapper;
@Pointcut("execution(* cn.bunny.service.web.controller..*(..))")
public void point() {
}
@Around(value = "point()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
Object result;
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 是否有跳过注解如果有跳过注解就不执行当前操作
SkipLog annotation = signature.getMethod().getAnnotation(SkipLog.class);
// 目标方法所在类名路径
String classPath = joinPoint.getSignature().getDeclaringTypeName();
// 当前执行的方法名
String methodName = signature.getName();
// 入参内容
String args = Arrays.toString(joinPoint.getArgs());
// 获取用户token
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String token = request.getHeader("token");
// 初始化系统日志对象
SystemLog systemLog = new SystemLog();
// token转为实体对象
Map<String, Object> mapByToken = JwtHelper.getMapByToken(token);
LoginVo loginVo = JSONObject.parseObject(JSONObject.toJSONString(mapByToken), LoginVo.class);
try {
// 当为null时跳过执行
if (annotation != null) return joinPoint.proceed();
// TODO 将请求头token全部转成 map
systemLog.setClassPath(classPath);
systemLog.setMethodName(methodName);
if (args.equals("[null]")) {
systemLog.setArgs(null);
} else {
systemLog.setArgs(args);
}
systemLog.setToken(token);
systemLog.setNickname(loginVo.getNickName());
systemLog.setEmail(loginVo.getEmail());
systemLog.setUpdateUser(loginVo.getId());
// 目标对象连接点方法的执行
result = joinPoint.proceed();
systemLog.setResult(JSONObject.toJSONString(result));
} catch (Exception exception) {
String message = exception.getMessage();
StackTraceElement[] stackTrace = exception.getStackTrace();
// 如果报错设置报错的堆栈和消息放到数据库中
systemLog.setErrorStack(Arrays.toString(stackTrace));
systemLog.setErrorMessage(message);
// 插入日志数据到数据库
systemLogMapper.insert(systemLog);
throw exception;
}
// 插入日志数据到数据库
systemLogMapper.insert(systemLog);
return result;
}
}

View File

@ -0,0 +1,18 @@
package cn.bunny.service.mapper;
import cn.bunny.entity.system.log.SystemLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* 系统日志表 Mapper 接口
* </p>
*
* @author Bunny
* @since 2024-05-31
*/
@Mapper
public interface SystemLogMapper extends BaseMapper<SystemLog> {
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.bunny.service.mapper.SystemLogMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="cn.bunny.entity.system.log.SystemLog">
<id column="id" property="id"/>
<result column="class_path" property="classPath"/>
<result column="method_name" property="methodName"/>
<result column="args" property="args"/>
<result column="result" property="result"/>
<result column="error_stack" property="errorStack"/>
<result column="error_message" property="errorMessage"/>
<result column="email" property="email"/>
<result column="nickname" property="nickname"/>
<result column="token" property="token"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<result column="update_user" property="updateUser"/>
<result column="is_deleted" property="isDeleted"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, class_path, method_name, args, result, error_stack, error_message, email, nickname, token, create_time, update_time, update_user, is_deleted
</sql>
</mapper>