Merge remote-tracking branch 'origin/dev_2.0.0' into dev_2.0.0

dev_2.0.0
xueqingkun 1 year ago
commit 78ad6808bf

@ -30,6 +30,15 @@
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- 其他依赖 --> <!-- 其他依赖 -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

@ -1,13 +1,17 @@
package com.supervision.config; package com.supervision.config;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import cn.hutool.jwt.JWT; import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil; import cn.hutool.jwt.JWTUtil;
import com.supervision.constant.UserTokenConstant;
import com.supervision.exception.BusinessException; import com.supervision.exception.BusinessException;
import com.supervision.util.SpringBeanUtil;
import com.supervision.util.TokenUtil; import com.supervision.util.TokenUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
@ -19,10 +23,16 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
public class JwtInterceptor implements HandlerInterceptor { public class JwtInterceptor implements HandlerInterceptor {
private final RedisTemplate<String, String> redisTemplate;
public JwtInterceptor(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override @Override
@ -43,11 +53,33 @@ public class JwtInterceptor implements HandlerInterceptor {
// 校验token是否过期,如果过期了,需要提示过期重新登录 // 校验token是否过期,如果过期了,需要提示过期重新登录
checkTokenExpire(jwt); checkTokenExpire(jwt);
// 校验是否重复登录 // 校验是否重复登录
//UserSingleLoginConfig.checkSingleLogin(jwt); //checkSingleLogin(jwt);
cacheAuth(jwt); cacheAuth(jwt);
return true; return true;
} }
public void checkSingleLogin(String userId, JWT currentJwt) {
if (Boolean.FALSE.equals(redisTemplate.hasKey(UserTokenConstant.TOKEN_CACHE + userId))) {
throw new BusinessException("用户已被踢下线或超时,请重新登录", 505);
}
String value = redisTemplate.opsForValue().get(UserTokenConstant.TOKEN_CACHE + userId);
long redisCacheTime = Long.parseLong(String.valueOf(value));
Object currentJwtIssueTimeObject = currentJwt.getPayload("issueTime");
long currentJwtIssueTime = Long.parseLong(String.valueOf(currentJwtIssueTimeObject));
if (redisCacheTime == currentJwtIssueTime) {
// 如果相等,说明这个token就是最新的,直接放行
return;
} else if (currentJwtIssueTime > redisCacheTime) {
// 如果当前请求时间,大于Redis缓存时间,说明重新登录了,这个时候要把最新的放到缓存中
redisTemplate.opsForValue().set(UserTokenConstant.TOKEN_CACHE + userId, String.valueOf(System.currentTimeMillis()), 1000 * 5L, TimeUnit.MILLISECONDS);
} else {
// 走到这里,说明redisCacheTime是最新的,说明当前用户请求了一个新的token,那么原来的用户就踢掉
throw new BusinessException("当前用户已在其他地方登录!", 505);
}
}
@Override @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception { @Nullable Exception ex) throws Exception {
@ -68,7 +100,6 @@ public class JwtInterceptor implements HandlerInterceptor {
} }
private void cacheAuth(JWT jwt) { private void cacheAuth(JWT jwt) {
try { try {
JSONObject claimsJson = jwt.getPayload().getClaimsJson(); JSONObject claimsJson = jwt.getPayload().getClaimsJson();

@ -1,50 +0,0 @@
package com.supervision.config;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.jwt.JWT;
import com.supervision.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class UserSingleLoginConfig {
/**
* ,5,5,
*/
private static final TimedCache<Object, JWT> singleLoginTokenCacheMap = CacheUtil.newTimedCache(1000 * 5 * 24);
static {
// 每秒钟扫描一次
singleLoginTokenCacheMap.schedulePrune(1000);
}
public static void loginOrRefreshUser(String id, JWT jwt) {
singleLoginTokenCacheMap.put(id, jwt);
}
public static void checkSingleLogin(JWT currentJwt) {
Object id = currentJwt.getPayload("id");
JWT singleLoginTokenCache = singleLoginTokenCacheMap.get(id);
if (ObjectUtil.isEmpty(singleLoginTokenCache)) {
throw new BusinessException("用户已被踢下线或超时,请重新登录", 505);
}
// 然后将当前的expireTime和singleLoginTokenCache进行比较
Object expireTime = singleLoginTokenCache.getPayload("expireTime");
long singleLoginTokenCacheExpireTime = Long.parseLong(String.valueOf(expireTime));
Object currentJwtExpireTimeObject = currentJwt.getPayload("expireTime");
long currentJwtExpireTime = Long.parseLong(String.valueOf(currentJwtExpireTimeObject));
if (singleLoginTokenCacheExpireTime == currentJwtExpireTime) {
// 如果相等,说明这个token就是最新的,直接放行
return;
} else if (currentJwtExpireTime > singleLoginTokenCacheExpireTime) {
// 如果当前的超时时间要大于缓存的,说明重新登录了,这个时候要把最新的放到缓存中
singleLoginTokenCacheMap.put(id, currentJwt);
} else {
// 走到这里,说明singleLoginTokenCache是最新的,说明当前用户请求了一个新的token,那么原来的用户就踢掉
throw new BusinessException("当前用户已在其他地方登录!", 505);
}
}
}

@ -5,8 +5,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ -18,10 +20,13 @@ import java.util.List;
@Configuration @Configuration
public class WebConfig implements WebMvcConfigurer { public class WebConfig implements WebMvcConfigurer {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
// 添加权限拦截器 // 添加权限拦截器
registry.addInterceptor(new JwtInterceptor()) registry.addInterceptor(new JwtInterceptor(redisTemplate))
.addPathPatterns("/**") .addPathPatterns("/**")
.excludePathPatterns(ignorePathPatterns()); .excludePathPatterns(ignorePathPatterns());
} }

@ -0,0 +1,6 @@
package com.supervision.constant;
public interface UserTokenConstant {
String TOKEN_CACHE = "USER:LOGIN:TOKEN:";
}

@ -13,6 +13,7 @@ public class TokenUtil {
JSONObject info = JSONUtil.parseObj(userInfo); JSONObject info = JSONUtil.parseObj(userInfo);
// 过期时间一天,同时这个字段也作为单点登录使用 // 过期时间一天,同时这个字段也作为单点登录使用
info.putOnce("expireTime",System.currentTimeMillis() + 1000 * 60 * 60 * 24); info.putOnce("expireTime",System.currentTimeMillis() + 1000 * 60 * 60 * 24);
info.putOnce("issueTime",System.currentTimeMillis());
return JWTUtil.createToken(info, signer); return JWTUtil.createToken(info, signer);
} }
} }

@ -46,6 +46,10 @@ spring:
log-slow-sql: true # 是否开启 慢SQL 记录默认false log-slow-sql: true # 是否开启 慢SQL 记录默认false
slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000单位毫秒 slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000单位毫秒
merge-sql: false # 合并多个连接池的监控数据默认false merge-sql: false # 合并多个连接池的监控数据默认false
redis:
host: 192.168.10.138
port: 6379
password: 123456
mybatis-plus: mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml mapper-locations: classpath*:mapper/**/*.xml

@ -47,6 +47,10 @@ spring:
log-slow-sql: true # 是否开启 慢SQL 记录默认false log-slow-sql: true # 是否开启 慢SQL 记录默认false
slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000单位毫秒 slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000单位毫秒
merge-sql: false # 合并多个连接池的监控数据默认false merge-sql: false # 合并多个连接池的监控数据默认false
redis:
host: 192.168.10.138
port: 6379
password: 123456
mybatis-plus: mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml mapper-locations: classpath*:mapper/**/*.xml

@ -47,6 +47,10 @@ spring:
log-slow-sql: true # 是否开启 慢SQL 记录默认false log-slow-sql: true # 是否开启 慢SQL 记录默认false
slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000单位毫秒 slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000单位毫秒
merge-sql: false # 合并多个连接池的监控数据默认false merge-sql: false # 合并多个连接池的监控数据默认false
redis:
host: 192.168.10.138
port: 6379
password: 123456
mybatis-plus: mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml mapper-locations: classpath*:mapper/**/*.xml

@ -2,9 +2,7 @@ package com.supervision.controller;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import cn.hutool.jwt.JWT; import com.supervision.constant.UserTokenConstant;
import com.supervision.config.UserSingleLoginConfig;
import com.supervision.domain.UserInfo;
import com.supervision.exception.BusinessException; import com.supervision.exception.BusinessException;
import com.supervision.model.User; import com.supervision.model.User;
import com.supervision.pojo.vo.LoginReqVO; import com.supervision.pojo.vo.LoginReqVO;
@ -14,9 +12,11 @@ import com.supervision.util.UserUtil;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Api(tags = "用户管理") @Api(tags = "用户管理")
@RestController @RestController
@ -26,6 +26,9 @@ public class UserController {
private final UserService userService; private final UserService userService;
private final RedisTemplate<String, String> redisTemplate;
@ApiOperation("登录") @ApiOperation("登录")
@PostMapping("login") @PostMapping("login")
public String login(@RequestBody LoginReqVO reqVO) { public String login(@RequestBody LoginReqVO reqVO) {
@ -40,16 +43,17 @@ public class UserController {
throw new BusinessException("密码错误"); throw new BusinessException("密码错误");
} }
String token = TokenUtil.creatToken(JSONUtil.toJsonStr(user.get())); String token = TokenUtil.creatToken(JSONUtil.toJsonStr(user.get()));
// 将用户的token保存起来 // 将用户的token保存起来,超时时间为5分钟
UserSingleLoginConfig.loginOrRefreshUser(user.get().getId(), JWT.create().parse(token)); redisTemplate.opsForValue().set(UserTokenConstant.TOKEN_CACHE + user.get().getId(), String.valueOf(System.currentTimeMillis()), 1000 * 5L, TimeUnit.MILLISECONDS);
return token; return token;
} }
@ApiOperation("token心跳") @ApiOperation("token心跳")
@PostMapping("keepaliveToken") @PostMapping("keepaliveToken")
public void keepaliveToken() { public void keepaliveToken() {
String token = UserUtil.getUserToken(); User user = UserUtil.getUser();
UserSingleLoginConfig.loginOrRefreshUser(UserUtil.getUser().getId(), JWT.create().parse(token)); // 每次心跳都设置为5分钟之后
redisTemplate.expire(UserTokenConstant.TOKEN_CACHE + user.getId(), 1000 * 5L, TimeUnit.MILLISECONDS);
} }

@ -46,6 +46,11 @@ spring:
log-slow-sql: true # 是否开启 慢SQL 记录默认false log-slow-sql: true # 是否开启 慢SQL 记录默认false
slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000单位毫秒 slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000单位毫秒
merge-sql: false # 合并多个连接池的监控数据默认false merge-sql: false # 合并多个连接池的监控数据默认false
redis:
host: 192.168.10.138
port: 6379
password: 123456
mybatis-plus: mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml mapper-locations: classpath*:mapper/**/*.xml

@ -47,6 +47,10 @@ spring:
log-slow-sql: true # 是否开启 慢SQL 记录默认false log-slow-sql: true # 是否开启 慢SQL 记录默认false
slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000单位毫秒 slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000单位毫秒
merge-sql: false # 合并多个连接池的监控数据默认false merge-sql: false # 合并多个连接池的监控数据默认false
redis:
host: 192.168.10.138
port: 6379
password: 123456
mybatis-plus: mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml mapper-locations: classpath*:mapper/**/*.xml

@ -47,6 +47,10 @@ spring:
log-slow-sql: true # 是否开启 慢SQL 记录默认false log-slow-sql: true # 是否开启 慢SQL 记录默认false
slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000单位毫秒 slow-sql-millis: 5000 # 慢 SQL 的标准,默认 3000单位毫秒
merge-sql: false # 合并多个连接池的监控数据默认false merge-sql: false # 合并多个连接池的监控数据默认false
redis:
host: 192.168.10.138
port: 6379
password: 123456
mybatis-plus: mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml mapper-locations: classpath*:mapper/**/*.xml

Loading…
Cancel
Save