From 817d0d33b2e349260b19540a316324c966cf3120 Mon Sep 17 00:00:00 2001 From: Bunny <1319900154@qq.com> Date: Sat, 1 Jun 2024 16:47:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=96=B0=E5=A2=9E):=20JWT=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86=EF=BC=8CAOP?= =?UTF-8?q?=E5=88=87=E9=9D=A2=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/common-utils/pom.xml | 27 ----- common/pom.xml | 1 - .../interceptor/UserTokenInterceptor.java | 47 +++++--- .../bunny/common/service}/utils/FileUtil.java | 2 +- .../bunny/common/service/utils/JwtHelper.java | 111 ++++++++++++------ .../service/utils/ResponseHandlerUtil.java | 12 ++ .../cn/bunny/entity/system/log/SystemLog.java | 75 ++++++++++++ .../bunny/service/aop/annotation/SkipLog.java | 11 ++ .../service/aop/aspect/AutoFillAspect.java | 2 +- .../service/aop/aspect/AutoLogAspect.java | 97 +++++++++++++++ .../bunny/service/mapper/SystemLogMapper.java | 18 +++ .../main/resources/mapper/SystemLogMapper.xml | 28 +++++ 12 files changed, 350 insertions(+), 81 deletions(-) delete mode 100644 common/common-utils/pom.xml rename common/{common-utils/src/main/java/cn/bunny/common => service-utils/src/main/java/cn/bunny/common/service}/utils/FileUtil.java (95%) create mode 100644 common/service-utils/src/main/java/cn/bunny/common/service/utils/ResponseHandlerUtil.java create mode 100644 dao/src/main/java/cn/bunny/entity/system/log/SystemLog.java create mode 100644 service/src/main/java/cn/bunny/service/aop/annotation/SkipLog.java create mode 100644 service/src/main/java/cn/bunny/service/aop/aspect/AutoLogAspect.java create mode 100644 service/src/main/java/cn/bunny/service/mapper/SystemLogMapper.java create mode 100644 service/src/main/resources/mapper/SystemLogMapper.xml diff --git a/common/common-utils/pom.xml b/common/common-utils/pom.xml deleted file mode 100644 index 943745a..0000000 --- a/common/common-utils/pom.xml +++ /dev/null @@ -1,27 +0,0 @@ - - 4.0.0 - - cn.bunny - common - 0.0.1-SNAPSHOT - - - common-utils - jar - - common-utils - https://maven.apache.org - - - UTF-8 - - - - - cn.bunny - dao - 0.0.1-SNAPSHOT - - - diff --git a/common/pom.xml b/common/pom.xml index c04ed68..f82ae99 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -14,7 +14,6 @@ service-utils common-generator - common-utils diff --git a/common/service-utils/src/main/java/cn/bunny/common/service/interceptor/UserTokenInterceptor.java b/common/service-utils/src/main/java/cn/bunny/common/service/interceptor/UserTokenInterceptor.java index 80aa5d6..41e2092 100644 --- a/common/service-utils/src/main/java/cn/bunny/common/service/interceptor/UserTokenInterceptor.java +++ b/common/service-utils/src/main/java/cn/bunny/common/service/interceptor/UserTokenInterceptor.java @@ -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 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 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; + + // 解析不到userId,Redis中没有这个用户 + if (userId == null || redisUserinfo == null) { + return loginAuthHandler(response, ResultCodeEnum.LOGIN_AUTH); } BaseContext.setUserId(userId); - BaseContext.setUsername(username); + BaseContext.setUsername(email); return true; } diff --git a/common/common-utils/src/main/java/cn/bunny/common/utils/FileUtil.java b/common/service-utils/src/main/java/cn/bunny/common/service/utils/FileUtil.java similarity index 95% rename from common/common-utils/src/main/java/cn/bunny/common/utils/FileUtil.java rename to common/service-utils/src/main/java/cn/bunny/common/service/utils/FileUtil.java index 8b591a0..aa60bc2 100644 --- a/common/common-utils/src/main/java/cn/bunny/common/utils/FileUtil.java +++ b/common/service-utils/src/main/java/cn/bunny/common/service/utils/FileUtil.java @@ -1,4 +1,4 @@ -package cn.bunny.common.utils; +package cn.bunny.common.service.utils; /** * 计算 kb mb gb diff --git a/common/service-utils/src/main/java/cn/bunny/common/service/utils/JwtHelper.java b/common/service-utils/src/main/java/cn/bunny/common/service/utils/JwtHelper.java index 8fd618b..07394bf 100644 --- a/common/service-utils/src/main/java/cn/bunny/common/service/utils/JwtHelper.java +++ b/common/service-utils/src/main/java/cn/bunny/common/service/utils/JwtHelper.java @@ -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 getTokenByMap(String token) { - if (!StringUtils.hasText(token)) return null; - Claims claims = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody(); + public static Map 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 getTokenByMap(String token, String signKey) { - if (!StringUtils.hasText(token)) return null; - Jws claimsJws = Jwts.parser().setSigningKey(signKey).parseClaimsJws(token); - Claims body = claimsJws.getBody(); - // 将 body 值转为map - return new HashMap<>(body); + public static Map getMapByToken(String token, String signKey) { + try { + if (!StringUtils.hasText(token)) return null; + Jws 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 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 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 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 claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token); - Claims claims = claimsJws.getBody(); + Jws 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 claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token); - Claims claims = claimsJws.getBody(); - return (String) claims.get("userName"); + Jws 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 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 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 claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token); + Date expiration = claimsJws.getBody().getExpiration(); + + return expiration != null && expiration.before(new Date()); + } catch (Exception exception) { + return true; + } } } \ No newline at end of file diff --git a/common/service-utils/src/main/java/cn/bunny/common/service/utils/ResponseHandlerUtil.java b/common/service-utils/src/main/java/cn/bunny/common/service/utils/ResponseHandlerUtil.java new file mode 100644 index 0000000..624a91a --- /dev/null +++ b/common/service-utils/src/main/java/cn/bunny/common/service/utils/ResponseHandlerUtil.java @@ -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; + } +} \ No newline at end of file diff --git a/dao/src/main/java/cn/bunny/entity/system/log/SystemLog.java b/dao/src/main/java/cn/bunny/entity/system/log/SystemLog.java new file mode 100644 index 0000000..d1b587b --- /dev/null +++ b/dao/src/main/java/cn/bunny/entity/system/log/SystemLog.java @@ -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; + +/** + *

+ * 系统日志表 + *

+ * + * @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; +} diff --git a/service/src/main/java/cn/bunny/service/aop/annotation/SkipLog.java b/service/src/main/java/cn/bunny/service/aop/annotation/SkipLog.java new file mode 100644 index 0000000..1340c44 --- /dev/null +++ b/service/src/main/java/cn/bunny/service/aop/annotation/SkipLog.java @@ -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 { +} diff --git a/service/src/main/java/cn/bunny/service/aop/aspect/AutoFillAspect.java b/service/src/main/java/cn/bunny/service/aop/aspect/AutoFillAspect.java index 7d2fc45..423102c 100644 --- a/service/src/main/java/cn/bunny/service/aop/aspect/AutoFillAspect.java +++ b/service/src/main/java/cn/bunny/service/aop/aspect/AutoFillAspect.java @@ -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() { } diff --git a/service/src/main/java/cn/bunny/service/aop/aspect/AutoLogAspect.java b/service/src/main/java/cn/bunny/service/aop/aspect/AutoLogAspect.java new file mode 100644 index 0000000..a6f07a4 --- /dev/null +++ b/service/src/main/java/cn/bunny/service/aop/aspect/AutoLogAspect.java @@ -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 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; + } +} diff --git a/service/src/main/java/cn/bunny/service/mapper/SystemLogMapper.java b/service/src/main/java/cn/bunny/service/mapper/SystemLogMapper.java new file mode 100644 index 0000000..2741252 --- /dev/null +++ b/service/src/main/java/cn/bunny/service/mapper/SystemLogMapper.java @@ -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; + +/** + *

+ * 系统日志表 Mapper 接口 + *

+ * + * @author Bunny + * @since 2024-05-31 + */ +@Mapper +public interface SystemLogMapper extends BaseMapper { + +} diff --git a/service/src/main/resources/mapper/SystemLogMapper.xml b/service/src/main/resources/mapper/SystemLogMapper.xml new file mode 100644 index 0000000..c7c0feb --- /dev/null +++ b/service/src/main/resources/mapper/SystemLogMapper.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + id, class_path, method_name, args, result, error_stack, error_message, email, nickname, token, create_time, update_time, update_user, is_deleted + + +