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 20 .likeIfPresent("user_ip", reqVO.getUserIp()));
21 21 }
22 22  
23   - default List<SysUserSessionDO> selectSessionTimeout() {
  23 + default List<SysUserSessionDO> selectListBySessionTimoutLt() {
24 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 16  
17 17 RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登陆用户的缓存",
18 18 "login_user:%s", // 参数为 sessionId
19   - STRING, LoginUser.class, Duration.ofMinutes(30));
  19 + STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
20 20  
21 21 RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("验证码的缓存",
22 22 "captcha_code:%s", // 参数为 uuid
... ...
src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/auth/SysLoginUserRedisDAO.java
1 1 package cn.iocoder.dashboard.modules.system.dal.redis.auth;
2 2  
3 3 import cn.iocoder.dashboard.framework.security.core.LoginUser;
  4 +import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService;
4 5 import cn.iocoder.dashboard.util.json.JsonUtils;
5 6 import org.springframework.data.redis.core.StringRedisTemplate;
6 7 import org.springframework.stereotype.Repository;
7 8  
8 9 import javax.annotation.Resource;
  10 +import java.time.Duration;
9 11  
10 12 import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.LOGIN_USER;
11 13  
... ... @@ -19,6 +21,8 @@ public class SysLoginUserRedisDAO {
19 21  
20 22 @Resource
21 23 private StringRedisTemplate stringRedisTemplate;
  24 + @Resource
  25 + private SysUserSessionService sysUserSessionService;
22 26  
23 27 public LoginUser get(String sessionId) {
24 28 String redisKey = formatKey(sessionId);
... ... @@ -27,7 +31,8 @@ public class SysLoginUserRedisDAO {
27 31  
28 32 public void set(String sessionId, LoginUser loginUser) {
29 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 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 23 public String execute(String param) throws Exception {
24 24 log.info("[execute][执行任务:{}]", "移除超时的在线用户");
25 25 long timeoutCount = sysUserSessionService.clearSessionTimeout();
26   - log.info("[execute][执行任务:{}]", "移除超时的在线用户完成" + timeoutCount);
  26 + log.info("[execute][执行任务:{}:{}]", "移除超时的在线用户完成", timeoutCount);
27 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 62  
63 63 /**
64 64 * 移除超时的在线用户
65   - * @param
66   - * @return {@link Long}
  65 + *
  66 + * @return {@link Long } 移出的超时用户数量
67 67 * @author Lyon
68 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 13 import cn.iocoder.dashboard.modules.system.dal.redis.auth.SysLoginUserRedisDAO;
14 14 import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService;
15 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 17 import org.springframework.stereotype.Service;
18 18  
19 19 import javax.annotation.Resource;
  20 +import java.time.Duration;
20 21 import java.util.Collection;
21 22 import java.util.Date;
22 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 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 30 * 在线用户 Session Service 实现类
... ... @@ -53,7 +55,7 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
53 55 // 写入 DB 中
54 56 SysUserSessionDO userSession = SysUserSessionDO.builder().id(sessionId)
55 57 .userId(loginUser.getId()).userIp(userIp).userAgent(userAgent)
56   - .sessionTimeout(DateUtils.addTime(LOGIN_USER.getTimeout()))
  58 + .sessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())))
57 59 .build();
58 60 userSessionMapper.insert(userSession);
59 61 // 返回 Session 编号
... ... @@ -68,7 +70,7 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
68 70 // 更新 DB 中
69 71 SysUserSessionDO updateObj = SysUserSessionDO.builder().id(sessionId).build();
70 72 updateObj.setUpdateTime(new Date());
71   - updateObj.setSessionTimeout(DateUtils.addTime(LOGIN_USER.getTimeout()));
  73 + updateObj.setSessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())));
72 74 userSessionMapper.updateById(updateObj);
73 75 }
74 76  
... ... @@ -106,15 +108,17 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
106 108 @Override
107 109 public long clearSessionTimeout() {
108 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 8 DELETE FROM "sys_role_menu";
9 9 DELETE FROM "sys_menu";
10 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 114 "deleted" bit NOT NULL DEFAULT FALSE,
115 115 PRIMARY KEY ("id")
116 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';
... ...