Commit 6163cfb4c25a2a7a19f532200b4b271fe39cd2e8

Authored by Lyon
1 parent 5803258e

移除超时的在线用户&单元测试

src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/auth/SysUserSessionMapper.java
@@ -20,7 +20,7 @@ public interface SysUserSessionMapper extends BaseMapperX<SysUserSessionDO> { @@ -20,7 +20,7 @@ public interface SysUserSessionMapper extends BaseMapperX<SysUserSessionDO> {
20 .likeIfPresent("user_ip", reqVO.getUserIp())); 20 .likeIfPresent("user_ip", reqVO.getUserIp()));
21 } 21 }
22 22
23 - default List<SysUserSessionDO> selectSessionTimeout() { 23 + default List<SysUserSessionDO> selectListBySessionTimoutLt() {
24 return selectList(new QueryWrapperX<SysUserSessionDO>().lt("session_timeout",new Date())); 24 return selectList(new QueryWrapperX<SysUserSessionDO>().lt("session_timeout",new Date()));
25 } 25 }
26 } 26 }
src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/SysRedisKeyConstants.java
@@ -16,7 +16,7 @@ public interface SysRedisKeyConstants { @@ -16,7 +16,7 @@ public interface SysRedisKeyConstants {
16 16
17 RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登陆用户的缓存", 17 RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登陆用户的缓存",
18 "login_user:%s", // 参数为 sessionId 18 "login_user:%s", // 参数为 sessionId
19 - STRING, LoginUser.class, Duration.ofMinutes(30)); 19 + STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
20 20
21 RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("验证码的缓存", 21 RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("验证码的缓存",
22 "captcha_code:%s", // 参数为 uuid 22 "captcha_code:%s", // 参数为 uuid
src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/auth/SysLoginUserRedisDAO.java
1 package cn.iocoder.dashboard.modules.system.dal.redis.auth; 1 package cn.iocoder.dashboard.modules.system.dal.redis.auth;
2 2
3 import cn.iocoder.dashboard.framework.security.core.LoginUser; 3 import cn.iocoder.dashboard.framework.security.core.LoginUser;
  4 +import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService;
4 import cn.iocoder.dashboard.util.json.JsonUtils; 5 import cn.iocoder.dashboard.util.json.JsonUtils;
5 import org.springframework.data.redis.core.StringRedisTemplate; 6 import org.springframework.data.redis.core.StringRedisTemplate;
6 import org.springframework.stereotype.Repository; 7 import org.springframework.stereotype.Repository;
7 8
8 import javax.annotation.Resource; 9 import javax.annotation.Resource;
  10 +import java.time.Duration;
9 11
10 import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.LOGIN_USER; 12 import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.LOGIN_USER;
11 13
@@ -19,6 +21,8 @@ public class SysLoginUserRedisDAO { @@ -19,6 +21,8 @@ public class SysLoginUserRedisDAO {
19 21
20 @Resource 22 @Resource
21 private StringRedisTemplate stringRedisTemplate; 23 private StringRedisTemplate stringRedisTemplate;
  24 + @Resource
  25 + private SysUserSessionService sysUserSessionService;
22 26
23 public LoginUser get(String sessionId) { 27 public LoginUser get(String sessionId) {
24 String redisKey = formatKey(sessionId); 28 String redisKey = formatKey(sessionId);
@@ -27,7 +31,8 @@ public class SysLoginUserRedisDAO { @@ -27,7 +31,8 @@ public class SysLoginUserRedisDAO {
27 31
28 public void set(String sessionId, LoginUser loginUser) { 32 public void set(String sessionId, LoginUser loginUser) {
29 String redisKey = formatKey(sessionId); 33 String redisKey = formatKey(sessionId);
30 - stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser), LOGIN_USER.getTimeout()); 34 + stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser),
  35 + Duration.ofMillis(sysUserSessionService.getSessionTimeoutMillis()));
31 } 36 }
32 37
33 public void delete(String sessionId) { 38 public void delete(String sessionId) {
src/main/java/cn/iocoder/dashboard/modules/system/job/auth/SysUserSessionTimeoutJob.java
@@ -23,7 +23,7 @@ public class SysUserSessionTimeoutJob implements JobHandler { @@ -23,7 +23,7 @@ public class SysUserSessionTimeoutJob implements JobHandler {
23 public String execute(String param) throws Exception { 23 public String execute(String param) throws Exception {
24 log.info("[execute][执行任务:{}]", "移除超时的在线用户"); 24 log.info("[execute][执行任务:{}]", "移除超时的在线用户");
25 long timeoutCount = sysUserSessionService.clearSessionTimeout(); 25 long timeoutCount = sysUserSessionService.clearSessionTimeout();
26 - log.info("[execute][执行任务:{}]", "移除超时的在线用户完成" + timeoutCount); 26 + log.info("[execute][执行任务:{}:{}]", "移除超时的在线用户完成", timeoutCount);
27 return null; 27 return null;
28 } 28 }
29 29
src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionService.java
@@ -62,8 +62,8 @@ public interface SysUserSessionService { @@ -62,8 +62,8 @@ public interface SysUserSessionService {
62 62
63 /** 63 /**
64 * 移除超时的在线用户 64 * 移除超时的在线用户
65 - * @param  
66 - * @return {@link Long} 65 + *
  66 + * @return {@link Long } 移出的超时用户数量
67 * @author Lyon 67 * @author Lyon
68 * @date 2021/3/7 68 * @date 2021/3/7
69 **/ 69 **/
src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysUserSessionServiceImpl.java
@@ -13,16 +13,18 @@ import cn.iocoder.dashboard.modules.system.dal.mysql.auth.SysUserSessionMapper; @@ -13,16 +13,18 @@ import cn.iocoder.dashboard.modules.system.dal.mysql.auth.SysUserSessionMapper;
13 import cn.iocoder.dashboard.modules.system.dal.redis.auth.SysLoginUserRedisDAO; 13 import cn.iocoder.dashboard.modules.system.dal.redis.auth.SysLoginUserRedisDAO;
14 import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService; 14 import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService;
15 import cn.iocoder.dashboard.modules.system.service.user.SysUserService; 15 import cn.iocoder.dashboard.modules.system.service.user.SysUserService;
16 -import cn.iocoder.dashboard.util.date.DateUtils; 16 +import com.google.common.collect.Lists;
17 import org.springframework.stereotype.Service; 17 import org.springframework.stereotype.Service;
18 18
19 import javax.annotation.Resource; 19 import javax.annotation.Resource;
  20 +import java.time.Duration;
20 import java.util.Collection; 21 import java.util.Collection;
21 import java.util.Date; 22 import java.util.Date;
22 import java.util.List; 23 import java.util.List;
  24 +import java.util.stream.Collectors;
23 25
24 -import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.LOGIN_USER;  
25 import static cn.iocoder.dashboard.util.collection.CollectionUtils.convertSet; 26 import static cn.iocoder.dashboard.util.collection.CollectionUtils.convertSet;
  27 +import static cn.iocoder.dashboard.util.date.DateUtils.addTime;
26 28
27 /** 29 /**
28 * 在线用户 Session Service 实现类 30 * 在线用户 Session Service 实现类
@@ -53,7 +55,7 @@ public class SysUserSessionServiceImpl implements SysUserSessionService { @@ -53,7 +55,7 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
53 // 写入 DB 中 55 // 写入 DB 中
54 SysUserSessionDO userSession = SysUserSessionDO.builder().id(sessionId) 56 SysUserSessionDO userSession = SysUserSessionDO.builder().id(sessionId)
55 .userId(loginUser.getId()).userIp(userIp).userAgent(userAgent) 57 .userId(loginUser.getId()).userIp(userIp).userAgent(userAgent)
56 - .sessionTimeout(DateUtils.addTime(LOGIN_USER.getTimeout())) 58 + .sessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())))
57 .build(); 59 .build();
58 userSessionMapper.insert(userSession); 60 userSessionMapper.insert(userSession);
59 // 返回 Session 编号 61 // 返回 Session 编号
@@ -68,7 +70,7 @@ public class SysUserSessionServiceImpl implements SysUserSessionService { @@ -68,7 +70,7 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
68 // 更新 DB 中 70 // 更新 DB 中
69 SysUserSessionDO updateObj = SysUserSessionDO.builder().id(sessionId).build(); 71 SysUserSessionDO updateObj = SysUserSessionDO.builder().id(sessionId).build();
70 updateObj.setUpdateTime(new Date()); 72 updateObj.setUpdateTime(new Date());
71 - updateObj.setSessionTimeout(DateUtils.addTime(LOGIN_USER.getTimeout())); 73 + updateObj.setSessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())));
72 userSessionMapper.updateById(updateObj); 74 userSessionMapper.updateById(updateObj);
73 } 75 }
74 76
@@ -106,15 +108,17 @@ public class SysUserSessionServiceImpl implements SysUserSessionService { @@ -106,15 +108,17 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
106 @Override 108 @Override
107 public long clearSessionTimeout() { 109 public long clearSessionTimeout() {
108 // 获取db里已经超时的用户列表 110 // 获取db里已经超时的用户列表
109 - Long timeoutCount = 0L;  
110 - List<SysUserSessionDO> sessionTimeoutDOS = userSessionMapper.selectSessionTimeout();  
111 - for (SysUserSessionDO sessionDO : sessionTimeoutDOS) {  
112 - // 确认已经超时,移出在线用户列表  
113 - if (loginUserRedisDAO.get(sessionDO.getId()) == null) {  
114 - timeoutCount += userSessionMapper.deleteById(sessionDO.getId());  
115 - } 111 + List<SysUserSessionDO> sessionTimeoutDOS = userSessionMapper.selectListBySessionTimoutLt();
  112 + List<String> timeoutIdList = sessionTimeoutDOS
  113 + .stream()
  114 + .filter(sessionDO -> loginUserRedisDAO.get(sessionDO.getId()) == null)
  115 + .map(SysUserSessionDO::getId)
  116 + .collect(Collectors.toList());
  117 + // 确认已经超时,按批次移出在线用户列表
  118 + if (CollUtil.isNotEmpty(timeoutIdList)) {
  119 + Lists.partition(timeoutIdList, 100).forEach(userSessionMapper::deleteBatchIds);
116 } 120 }
117 - return timeoutCount; 121 + return timeoutIdList.size();
118 } 122 }
119 123
120 /** 124 /**
src/test/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.dashboard.modules.system.service.auth;
  2 +
  3 +import cn.hutool.core.date.DateUtil;
  4 +import cn.iocoder.dashboard.BaseSpringBootUnitTest;
  5 +import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX;
  6 +import cn.iocoder.dashboard.modules.system.dal.dataobject.auth.SysUserSessionDO;
  7 +import cn.iocoder.dashboard.modules.system.dal.mysql.auth.SysUserSessionMapper;
  8 +import cn.iocoder.dashboard.util.RandomUtils;
  9 +import org.junit.jupiter.api.Test;
  10 +
  11 +import javax.annotation.Resource;
  12 +import java.util.Date;
  13 +import java.util.List;
  14 +import java.util.stream.Collectors;
  15 +import java.util.stream.Stream;
  16 +
  17 +import static org.junit.jupiter.api.Assertions.assertEquals;
  18 +
  19 +/**
  20 + * SysUserSessionServiceImpl Tester.
  21 + *
  22 + * @author Lyon
  23 + * @version 1.0
  24 + * @since <pre>3月 8, 2021</pre>
  25 + */
  26 +public class SysUserSessionServiceImplTest extends BaseSpringBootUnitTest {
  27 +
  28 + @Resource
  29 + SysUserSessionService sysUserSessionService;
  30 + @Resource
  31 + SysUserSessionMapper sysUserSessionMapper;
  32 +
  33 + @Test
  34 + public void testClearSessionTimeout_success() throws Exception {
  35 + // 准备超时数据 120 条, 在线用户 1 条
  36 + int expectedTimeoutCount = 120, expectedTotal = 1;
  37 +
  38 + // 准备数据
  39 + List<SysUserSessionDO> prepareData = Stream
  40 + .iterate(0, i -> i)
  41 + .limit(expectedTimeoutCount)
  42 + .map(i -> RandomUtils.randomPojo(SysUserSessionDO.class, o -> o.setSessionTimeout(DateUtil.offsetSecond(new Date(), -1))))
  43 + .collect(Collectors.toList());
  44 + prepareData.add(RandomUtils.randomPojo(SysUserSessionDO.class, o -> o.setSessionTimeout(DateUtil.offsetMinute(new Date(), 30))));
  45 + prepareData.forEach(sysUserSessionMapper::insert);
  46 +
  47 + //清空超时数据
  48 + long actualTimeoutCount = sysUserSessionService.clearSessionTimeout();
  49 + assertEquals(expectedTimeoutCount, actualTimeoutCount);
  50 + Integer actualTotal = sysUserSessionMapper.selectCount(new QueryWrapperX<>());
  51 + assertEquals(expectedTotal, actualTotal);
  52 + }
  53 +
  54 +}
src/test/resources/sql/clean.sql
@@ -8,3 +8,4 @@ DELETE FROM &quot;sys_role&quot;; @@ -8,3 +8,4 @@ DELETE FROM &quot;sys_role&quot;;
8 DELETE FROM "sys_role_menu"; 8 DELETE FROM "sys_role_menu";
9 DELETE FROM "sys_menu"; 9 DELETE FROM "sys_menu";
10 DELETE FROM "sys_dict_type"; 10 DELETE FROM "sys_dict_type";
  11 +DELETE FROM "sys_user_session";
src/test/resources/sql/create_tables.sql
@@ -114,3 +114,17 @@ CREATE TABLE &quot;sys_dict_type&quot; ( @@ -114,3 +114,17 @@ CREATE TABLE &quot;sys_dict_type&quot; (
114 "deleted" bit NOT NULL DEFAULT FALSE, 114 "deleted" bit NOT NULL DEFAULT FALSE,
115 PRIMARY KEY ("id") 115 PRIMARY KEY ("id")
116 ) COMMENT '字典类型表'; 116 ) COMMENT '字典类型表';
  117 +
  118 +CREATE TABLE `sys_user_session` (
  119 + `id` varchar(32) NOT NULL,
  120 + `user_id` bigint DEFAULT NULL,
  121 + `user_ip` varchar(50) DEFAULT NULL,
  122 + `user_agent` varchar(512) DEFAULT NULL,
  123 + `session_timeout` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  124 + "create_by" varchar(64) DEFAULT '',
  125 + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  126 + `update_by` varchar(64) DEFAULT '' ,
  127 + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  128 + "deleted" bit NOT NULL DEFAULT FALSE,
  129 + PRIMARY KEY (`id`)
  130 +) COMMENT '用户在线 Session';