Commit 6163cfb4c25a2a7a19f532200b4b271fe39cd2e8
1 parent
5803258e
移除超时的在线用户&单元测试
Showing
9 changed files
with
96 additions
and
18 deletions
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
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
src/test/resources/sql/create_tables.sql
| ... | ... | @@ -114,3 +114,17 @@ CREATE TABLE "sys_dict_type" ( |
| 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'; | ... | ... |