- 使用Redis存储Token并进行校验
- 完善常量
This commit is contained in:
yulinling 2025-06-18 23:22:39 +08:00
parent 28bba7ed79
commit 8937ec945a
21 changed files with 541 additions and 116 deletions

View File

@ -147,6 +147,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -2,16 +2,11 @@ package asia.yulinling.workflow.config;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.io.Serializable;
@ -28,6 +23,9 @@ import java.io.Serializable;
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableCaching
public class RedisConfig {
/**
* 默认情况下的模板只能支持RedisTemplate<String, String>也就是只能存入字符串因此支持序列化
*/
@Bean
public RedisTemplate<String, Serializable> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
@ -36,14 +34,4 @@ public class RedisConfig {
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
// @Bean
// public CacheManager cacheManager(RedisConnectionFactory factory) {
// RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
// RedisCacheConfiguration redisCacheConfiguration = configuration.serializeKeysWith(
// RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())
// ).serializeValuesWith(
// RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build();
// }
}

View File

@ -20,6 +20,14 @@ import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* <p>
* Spring Security配置类
* </p>
*
* @author YLL
* @since 2025/6/17
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor

View File

@ -0,0 +1,60 @@
package asia.yulinling.workflow.constant;
/**
* <p>
* 常量池
* </p>
*
* @author YLL
* @since 2025/6/18
*/
public interface Const {
/**
* 启用
*/
Integer ENABLE = 1;
/**
* 禁用
*/
Integer DISABLE = 0;
/**
* 页面
*/
Integer PAGE = 1;
/**
* 按钮
*/
Integer BUTTON = 2;
/**
* JWT Redis 中保存的key前缀
*/
String REDIS_JWT_KEY_PREFIX = "security:jwt:";
/**
* 星号
*/
String SYMBOL_STAR = "*";
/**
* 邮箱符号
*/
String SYMBOL_EMAIL = "@";
/**
* 默认当前页码
*/
Integer DEFAULT_CURRENT_PAGE = 1;
/**
* 默认每页条数
*/
Integer DEFAULT_PAGE_SIZE = 10;
/**
* 匿名用户 用户名
*/
String ANONYMOUS_NAME = "匿名用户";
}

View File

@ -12,11 +12,83 @@ import lombok.Getter;
*/
@Getter
public enum Status {
/** 操作成功 */
OK(200, "success"),
/**
* 操作成功
*/
SUCCESS(200, "操作成功!"),
/** 未知异常 */
UNKNOWN_ERROR(500, "Unknown error");
UNKNOWN_ERROR(500, "未知异常"),
/**
* 退出成功
*/
LOGOUT(200, "退出成功!"),
/**
* 请先登录
*/
UNAUTHORIZED(401, "请先登录!"),
/**
* 暂无权限访问
*/
ACCESS_DENIED(403, "权限不足!"),
/**
* 请求不存在
*/
REQUEST_NOT_FOUND(404, "请求不存在!"),
/**
* 请求方式不支持
*/
HTTP_BAD_METHOD(405, "请求方式不支持!"),
/**
* 请求异常
*/
BAD_REQUEST(400, "请求异常!"),
/**
* 参数不匹配
*/
PARAM_NOT_MATCH(400, "参数不匹配!"),
/**
* 参数不能为空
*/
PARAM_NOT_NULL(400, "参数不能为空!"),
/**
* 当前用户已被锁定请联系管理员解锁
*/
USER_DISABLED(403, "当前用户已被锁定,请联系管理员解锁!"),
/**
* 用户名或密码错误
*/
USERNAME_PASSWORD_ERROR(5001, "用户名或密码错误!"),
/**
* token 已过期请重新登录
*/
TOKEN_EXPIRED(5002, "token 已过期,请重新登录!"),
/**
* token 解析失败请尝试重新登录
*/
TOKEN_PARSE_ERROR(5002, "token 解析失败,请尝试重新登录!"),
/**
* 当前用户已在别处登录请尝试更改密码或重新登录
*/
TOKEN_OUT_OF_CTRL(5003, "当前用户已在别处登录,请尝试更改密码或重新登录!"),
/**
* 无法手动踢出自己请尝试退出登录操作
*/
KICKOUT_SELF(5004, "无法手动踢出自己,请尝试退出登录操作!");
/** 状态码 */
private final Integer code;

View File

@ -1,7 +1,10 @@
package asia.yulinling.workflow.controller;
import asia.yulinling.workflow.dto.request.LoginRequest;
import asia.yulinling.workflow.dto.response.JWTAuthResponse;
import asia.yulinling.workflow.model.ApiResponse;
import asia.yulinling.workflow.service.AuthService;
import asia.yulinling.workflow.utils.ResponseUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
@ -25,9 +28,10 @@ public class AuthController {
private final AuthService authenticate;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
public ApiResponse<JWTAuthResponse> login(@RequestBody LoginRequest loginRequest) {
String token = authenticate.login(loginRequest);
return ResponseEntity.ok(token);
JWTAuthResponse jwtAuthResponse = new JWTAuthResponse(token);
return ApiResponse.ofSuccess(jwtAuthResponse);
}
}

View File

@ -1,7 +1,7 @@
package asia.yulinling.workflow.dto.request;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* <p>
* 登录请求类
@ -12,6 +12,20 @@ import lombok.Data;
*/
@Data
public class LoginRequest {
/**
* 用户名或者邮箱名
*/
@NotBlank(message = "用户名或者邮箱名不能为空")
private String username;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
private String password;
/**
* 记住我
*/
private Boolean rememberMe = false;
}

View File

@ -12,8 +12,15 @@ import lombok.Data;
*/
@Data
public class PageParam {
private int pageNum;
private int pageSize;
/**
* 当前页码
*/
private Integer pageNum;
/**
* 每条页数
*/
private Integer pageSize;
public PageParam() {
this.pageNum = 1;

View File

@ -18,4 +18,8 @@ import lombok.NoArgsConstructor;
public class JWTAuthResponse {
private String accessToken;
private String tokenType = "Bearer";
public JWTAuthResponse(String accessToken) {
this.accessToken = accessToken;
}
}

View File

@ -2,16 +2,17 @@ package asia.yulinling.workflow.dto.response;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 分页返回结果
* 通用分页返回结果
*
* @author YLL
* @since 2025/6/8
*/
@Data
public class PageResult<T> {
public class PageResult<T> implements Serializable {
private long total; // 总记录数
private int totalPage; // 总页数
private int currentPage; // 当前页码
@ -46,4 +47,19 @@ public class PageResult<T> {
this.from = (currentPage - 1) * pageSize + 1;
this.to = Math.min(currentPage * pageSize, (int) total);
}
// 构造方法可选传参
public PageResult(long total, List<T> list) {
this.total = total;
this.list = list;
// 自动计算
this.totalPage = (int) Math.ceil((double) total / pageSize);
this.hasNext = currentPage < totalPage;
this.hasPrev = currentPage > 1;
this.firstPage = currentPage == 1;
this.lastPage = currentPage == totalPage;
this.from = (currentPage - 1) * pageSize + 1;
this.to = Math.min(currentPage * pageSize, (int) total);
}
}

View File

@ -15,8 +15,9 @@ import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class BaseException extends RuntimeException {
private final Integer code;
private final String message;
private Integer code;
private String message;
private Object data;
public BaseException(Status status) {
super(status.getMessage());
@ -24,9 +25,19 @@ public class BaseException extends RuntimeException{
this.message = status.getMessage();
}
public BaseException(Status status, Object data) {
this(status);
this.data = data;
}
public BaseException(Integer code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BaseException(Integer code, String message, Object data) {
this(code, message);
this.data = data;
}
}

View File

@ -0,0 +1,31 @@
package asia.yulinling.workflow.exception;
import asia.yulinling.workflow.constant.Status;
import lombok.EqualsAndHashCode;
/**
* <p>
* Security异常
* </p>
*
* @author YLL
* @since 2025/6/18
*/
@EqualsAndHashCode(callSuper = true)
public class SecurityException extends BaseException {
public SecurityException(Status status) {
super(status);
}
public SecurityException(Status status, Object data) {
super(status, data);
}
public SecurityException(Integer code, String message) {
super(code, message);
}
public SecurityException(Integer code, String message, Object data) {
super(code, message, data);
}
}

View File

@ -1,4 +1,4 @@
package asia.yulinling.workflow.handler;
package asia.yulinling.workflow.exception.handler;
import asia.yulinling.workflow.exception.JsonException;
import asia.yulinling.workflow.exception.PageException;

View File

@ -67,7 +67,7 @@ public class ApiResponse<T> {
* @return ApiResponse
*/
public static <T> ApiResponse<T> ofSuccess(T data) {
return ofStatus(Status.OK, data);
return ofStatus(Status.SUCCESS, data);
}
/**
@ -77,7 +77,7 @@ public class ApiResponse<T> {
* @return ApiResponse
*/
public static ApiResponse<Void> ofMessage(String message) {
return of(Status.OK.getCode(), message, null);
return of(Status.SUCCESS.getCode(), message, null);
}
/**

View File

@ -1,7 +1,10 @@
package asia.yulinling.workflow.security;
import asia.yulinling.workflow.exception.SecurityException;
import asia.yulinling.workflow.constant.Status;
import asia.yulinling.workflow.model.vo.user.UserPrincipal;
import asia.yulinling.workflow.utils.JwtUtil;
import asia.yulinling.workflow.utils.ResponseUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@ -40,6 +43,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
String token = jwtUtil.getTokenFromRequest(request);
if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) {
try {
// 2. 解析token获取username
String username = jwtUtil.parseToken(token).getSubject();
@ -53,6 +57,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
} catch (SecurityException e) {
log.error("Security异常:{}", e.getMessage());
ResponseUtil.renderJson(response, e);
}
}
filterChain.doFilter(request, response);

View File

@ -1,5 +1,9 @@
package asia.yulinling.workflow.utils;
import asia.yulinling.workflow.constant.Const;
import asia.yulinling.workflow.constant.Status;
import asia.yulinling.workflow.exception.SecurityException;
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
@ -7,12 +11,14 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.Authentication;
import io.jsonwebtoken.*;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* <p>
@ -26,6 +32,7 @@ import java.util.Date;
@Component
@Slf4j
public class JwtUtil {
private final StringRedisTemplate stringRedisTemplate;
/**
* jwt 加密 key默认值kw.
*/
@ -52,26 +59,27 @@ public class JwtUtil {
* @return JWT
*/
public String generateToken(Authentication authentication, boolean isRememberMe) {
// 1. 构建签名密钥
Key key = key();
// 2. 当前时间
// 1. 当前时间
Date now = new Date();
// 3. 计算过期时间
// 2. 计算过期时间
long ttl = isRememberMe ? this.remember : this.ttl;
Date expiration = new Date(now.getTime() + ttl);
// 4. 构建 JWT
// 3. 构建 JWT
String username = authentication.getName();
JwtBuilder builder = Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiration)
.signWith(key);
.signWith(this.key());
// 5. 返回生成的 token 字符串
return builder.compact();
// 4. 生成token
String token = builder.compact();
// 5. 将token存入redis
stringRedisTemplate.opsForValue().set(Const.REDIS_JWT_KEY_PREFIX + username, token, ttl, TimeUnit.SECONDS);
// 6. 返回生成的token
return token;
}
/**
@ -82,15 +90,26 @@ public class JwtUtil {
*/
public Claims parseToken(String token) {
try {
// 1. 获取签名密钥
Key key = key();
// 2. 解析Token
return Jwts.parserBuilder()
.setSigningKey(key)
// 1. 解析Token
Claims claims = Jwts.parserBuilder()
.setSigningKey(this.key())
.build()
.parseClaimsJws(token)
.getBody();
// 2. 获取RedisKey
String redisKey = Const.REDIS_JWT_KEY_PREFIX + claims.getSubject();
// 3. 校验Token是否存在
Long expire = stringRedisTemplate.getExpire(redisKey, TimeUnit.SECONDS);
if (expire <= 0) {
throw new SecurityException(Status.TOKEN_EXPIRED);
}
// 4. 校验Token是否一致
String redisToken = stringRedisTemplate.opsForValue().get(redisKey);
if (!StrUtil.equals(redisToken, token)) {
throw new SecurityException(Status.TOKEN_OUT_OF_CTRL);
}
return claims;
} catch (ExpiredJwtException e) {
log.error("Token 已过期: {}", token, e);
throw new JwtException("Token 已过期", e);
@ -136,6 +155,7 @@ public class JwtUtil {
public void invalidateToken(HttpServletRequest request) {
String token = getTokenFromRequest(request);
String username = parseToken(token).getSubject();
stringRedisTemplate.delete(Const.REDIS_JWT_KEY_PREFIX + username);
}
/**

View File

@ -0,0 +1,83 @@
package asia.yulinling.workflow.utils;
import asia.yulinling.workflow.dto.response.PageResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* <p>
* Redis工具类
* </p>
*
* @author YLL
* @since 2025/6/18
*/
@RequiredArgsConstructor
@Component
@Slf4j
public class RedisUtil {
private final StringRedisTemplate stringRedisTemplate;
/**
* 分页获取指定格式key使用 scan 命令代替 keys 命令在大数据量的情况下可以提高查询效率
*
* @param patternKey key格式
* @param currentPage 当前页码
* @param pageSize 每页条数
* @return 分页获取指定格式key
*/
public PageResult<String> findKeyForPage(String patternKey, int currentPage, int pageSize) {
ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
RedisConnectionFactory redisConnectionFactory = stringRedisTemplate.getConnectionFactory();
RedisConnection redisConnection = redisConnectionFactory.getConnection();
Cursor<byte[]> cursor = redisConnection.scan(options);
List<String> result = new ArrayList<>();
long tmpIndex = 0;
int startIndex = (currentPage - 1) * pageSize;
int end = startIndex + pageSize;
while (cursor.hasNext()) {
String key = new String(cursor.next());
if (tmpIndex >= startIndex && tmpIndex < end) {
result.add(key);
}
tmpIndex++;
}
try {
cursor.close();
RedisConnectionUtils.releaseConnection(redisConnection, redisConnectionFactory);
} catch (Exception e) {
log.error("Redis连接关闭异常:{}", e.getMessage());
}
return new PageResult<>(tmpIndex, result);
}
/**
* 删除Redis中的key
*/
public void delete(String key) {
stringRedisTemplate.delete(key);
}
/**
* 批量删除Redis中key
*
* @param keys 键列表
*/
public void delete(Collection<String> keys) {
stringRedisTemplate.delete(keys);
}
}

View File

@ -0,0 +1,58 @@
package asia.yulinling.workflow.utils;
import asia.yulinling.workflow.constant.Status;
import asia.yulinling.workflow.exception.BaseException;
import asia.yulinling.workflow.model.ApiResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
/**
* <p>
* 通用工具类
* </p>
*
* @author YLL
* @since 2025/6/18
*/
@Slf4j
public class ResponseUtil {
/**
* response 写出 json
*
* @param response 响应
* @param status 状态
* @param data 返回数据
*/
public static void renderJson(HttpServletResponse response, Status status, Object data) {
try {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setContentType("application/json;charset=UTF-8");
response.setStatus(200);
response.getWriter().write(JSONUtil.toJsonStr(
new JSONObject(ApiResponse.ofStatus(status, data), false)
));
} catch (IOException e) {
log.error("Response写正常Json异常:{}", e.getMessage());
}
}
public static void renderJson(HttpServletResponse response, BaseException baseException) {
try {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setContentType("application/json;charset=UTF-8");
response.setStatus(200);
response.getWriter().write(JSONUtil.toJsonStr(
new JSONObject(ApiResponse.ofException(baseException), false)
));
} catch (IOException e) {
log.error("Response写异常Json异常:{}", e.getMessage());
}
}
}

View File

@ -1,56 +0,0 @@
package asia.yulinling.workflow;
import asia.yulinling.workflow.model.entity.User;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.io.Serializable;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
/**
* <p>
* Redis测试
* </p>
*
* @author YLL
* @since 2025/6/17
*/
@Slf4j
public class RedisTest extends WorkFlowMainTests {
@Autowired
private RedisTemplate<String, Serializable> redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void testRedisTemplate() throws InterruptedException {
// CountDownLatch latch = new CountDownLatch(1000);
// ExecutorService executorService = Executors.newFixedThreadPool(100);
//
// IntStream.range(0, 1000).forEach(i -> {
// executorService.execute(() -> {
// stringRedisTemplate.opsForValue().increment("count", 1);
// latch.countDown();
// });
// });
//
// latch.await(); // 等待所有线程执行完毕
// executorService.shutdown(); // 关闭线程池
//
// Long count = Long.valueOf(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("count")));
// log.info("count: {}", count);
//
// stringRedisTemplate.opsForValue().set("k1", "v1");
// String k1 = stringRedisTemplate.opsForValue().get("k1");
// log.info("k1: {}", k1);
}
}

View File

@ -3,6 +3,9 @@ package asia.yulinling.workflow.utils;
import io.jsonwebtoken.Claims;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.Mockito;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
@ -12,6 +15,7 @@ import org.springframework.security.core.userdetails.UserDetails;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
/**
* <p>
@ -23,10 +27,18 @@ import static org.junit.jupiter.api.Assertions.*;
*/
class JwtUtilTest {
private JwtUtil jwtUtil;
private StringRedisTemplate stringRedisTemplate;
private ValueOperations<String, String> valueOps;
@BeforeEach
void setUp() {
jwtUtil = new JwtUtil();
stringRedisTemplate = Mockito.mock(StringRedisTemplate.class);
valueOps = Mockito.mock(ValueOperations.class);
// 当调用 stringRedisTemplate.opsForValue() 时返回 valueOps
when(stringRedisTemplate.opsForValue()).thenReturn(valueOps);
jwtUtil = new JwtUtil(stringRedisTemplate);
}
@Test

View File

@ -0,0 +1,80 @@
package asia.yulinling.workflow.utils;
import asia.yulinling.workflow.WorkFlowMainTests;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* <p>
* Redis测试类
* </p>
*
* @author YLL
* @since 2025/6/18
*/
@Slf4j
public class RedisTest extends WorkFlowMainTests {
@Autowired
private RedisTemplate<String, Serializable> redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private RedisUtil redisUtil;
@Test
public void testRedisTemplate() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1000);
ExecutorService executorService = Executors.newFixedThreadPool(100);
IntStream.range(0, 1000).forEach(i -> {
executorService.execute(() -> {
stringRedisTemplate.opsForValue().increment("count", 1);
latch.countDown();
});
});
latch.await(); // 等待所有线程执行完毕
executorService.shutdown(); // 关闭线程池
Long count = Long.valueOf(Objects.requireNonNull(stringRedisTemplate.opsForValue().get("count")));
log.info("count: {}", count);
stringRedisTemplate.opsForValue().set("k1", "v1");
String k1 = stringRedisTemplate.opsForValue().get("k1");
log.info("k1: {}", k1);
}
@Test
public void testRedis() {
UserDetails user = User.withUsername("test")
.password("test")
.roles("USER")
.build();
Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
String token = jwtUtil.generateToken(authentication, false);
assertNotNull(token);
Claims claims = jwtUtil.parseToken(token);
assertEquals("test", claims.getSubject());
}
}