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>
</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>
<groupId>org.springframework.boot</groupId>

@ -1,13 +1,17 @@
package com.supervision.config;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import com.supervision.constant.UserTokenConstant;
import com.supervision.exception.BusinessException;
import com.supervision.util.SpringBeanUtil;
import com.supervision.util.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
@ -19,10 +23,16 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {
private final RedisTemplate<String, String> redisTemplate;
public JwtInterceptor(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
@ -43,11 +53,33 @@ public class JwtInterceptor implements HandlerInterceptor {
// 校验token是否过期,如果过期了,需要提示过期重新登录
checkTokenExpire(jwt);
// 校验是否重复登录
//UserSingleLoginConfig.checkSingleLogin(jwt);
//checkSingleLogin(jwt);
cacheAuth(jwt);
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
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
@ -68,7 +100,6 @@ public class JwtInterceptor implements HandlerInterceptor {
}
private void cacheAuth(JWT jwt) {
try {
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.datatype.jsr310.JavaTimeModule;
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.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ -18,10 +20,13 @@ import java.util.List;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加权限拦截器
registry.addInterceptor(new JwtInterceptor())
registry.addInterceptor(new JwtInterceptor(redisTemplate))
.addPathPatterns("/**")
.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);
// 过期时间一天,同时这个字段也作为单点登录使用
info.putOnce("expireTime",System.currentTimeMillis() + 1000 * 60 * 60 * 24);
info.putOnce("issueTime",System.currentTimeMillis());
return JWTUtil.createToken(info, signer);
}
}

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

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

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

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

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

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

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

Loading…
Cancel
Save