From 6163cfb4c25a2a7a19f532200b4b271fe39cd2e8 Mon Sep 17 00:00:00 2001 From: Lyon <18423215834@163.com> Date: Mon, 8 Mar 2021 15:57:05 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E8=B6=85=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E5=9C=A8=E7=BA=BF=E7=94=A8=E6=88=B7&=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dal/mysql/auth/SysUserSessionMapper.java | 2 +- .../dal/redis/SysRedisKeyConstants.java | 2 +- .../dal/redis/auth/SysLoginUserRedisDAO.java | 7 ++- .../job/auth/SysUserSessionTimeoutJob.java | 2 +- .../service/auth/SysUserSessionService.java | 4 +- .../auth/impl/SysUserSessionServiceImpl.java | 28 +++++----- .../auth/SysUserSessionServiceImplTest.java | 54 +++++++++++++++++++ src/test/resources/sql/clean.sql | 1 + src/test/resources/sql/create_tables.sql | 14 +++++ 9 files changed, 96 insertions(+), 18 deletions(-) create mode 100644 src/test/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionServiceImplTest.java diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/auth/SysUserSessionMapper.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/auth/SysUserSessionMapper.java index 6739d9252..bb311312d 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/auth/SysUserSessionMapper.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/auth/SysUserSessionMapper.java @@ -20,7 +20,7 @@ public interface SysUserSessionMapper extends BaseMapperX { .likeIfPresent("user_ip", reqVO.getUserIp())); } - default List selectSessionTimeout() { + default List selectListBySessionTimoutLt() { return selectList(new QueryWrapperX().lt("session_timeout",new Date())); } } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/SysRedisKeyConstants.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/SysRedisKeyConstants.java index 5c6be6b33..070f65c90 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/SysRedisKeyConstants.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/SysRedisKeyConstants.java @@ -16,7 +16,7 @@ public interface SysRedisKeyConstants { RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登陆用户的缓存", "login_user:%s", // 参数为 sessionId - STRING, LoginUser.class, Duration.ofMinutes(30)); + STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("验证码的缓存", "captcha_code:%s", // 参数为 uuid diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/auth/SysLoginUserRedisDAO.java b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/auth/SysLoginUserRedisDAO.java index c2d7bfd6b..02489e81a 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/auth/SysLoginUserRedisDAO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/dal/redis/auth/SysLoginUserRedisDAO.java @@ -1,11 +1,13 @@ package cn.iocoder.dashboard.modules.system.dal.redis.auth; import cn.iocoder.dashboard.framework.security.core.LoginUser; +import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService; import cn.iocoder.dashboard.util.json.JsonUtils; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Repository; import javax.annotation.Resource; +import java.time.Duration; import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.LOGIN_USER; @@ -19,6 +21,8 @@ public class SysLoginUserRedisDAO { @Resource private StringRedisTemplate stringRedisTemplate; + @Resource + private SysUserSessionService sysUserSessionService; public LoginUser get(String sessionId) { String redisKey = formatKey(sessionId); @@ -27,7 +31,8 @@ public class SysLoginUserRedisDAO { public void set(String sessionId, LoginUser loginUser) { String redisKey = formatKey(sessionId); - stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser), LOGIN_USER.getTimeout()); + stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(loginUser), + Duration.ofMillis(sysUserSessionService.getSessionTimeoutMillis())); } public void delete(String sessionId) { diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/job/auth/SysUserSessionTimeoutJob.java b/src/main/java/cn/iocoder/dashboard/modules/system/job/auth/SysUserSessionTimeoutJob.java index 799fe115f..5c1aae405 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/job/auth/SysUserSessionTimeoutJob.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/job/auth/SysUserSessionTimeoutJob.java @@ -23,7 +23,7 @@ public class SysUserSessionTimeoutJob implements JobHandler { public String execute(String param) throws Exception { log.info("[execute][执行任务:{}]", "移除超时的在线用户"); long timeoutCount = sysUserSessionService.clearSessionTimeout(); - log.info("[execute][执行任务:{}]", "移除超时的在线用户完成" + timeoutCount); + log.info("[execute][执行任务:{}:{}]", "移除超时的在线用户完成", timeoutCount); return null; } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionService.java index 588c9f956..3f8869a0f 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionService.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionService.java @@ -62,8 +62,8 @@ public interface SysUserSessionService { /** * 移除超时的在线用户 - * @param - * @return {@link Long} + * + * @return {@link Long } 移出的超时用户数量 * @author Lyon * @date 2021/3/7 **/ diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysUserSessionServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysUserSessionServiceImpl.java index e1fb1fa2f..cc3e0aa06 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/auth/impl/SysUserSessionServiceImpl.java +++ b/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; import cn.iocoder.dashboard.modules.system.dal.redis.auth.SysLoginUserRedisDAO; import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService; import cn.iocoder.dashboard.modules.system.service.user.SysUserService; -import cn.iocoder.dashboard.util.date.DateUtils; +import com.google.common.collect.Lists; import org.springframework.stereotype.Service; import javax.annotation.Resource; +import java.time.Duration; import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.stream.Collectors; -import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.LOGIN_USER; import static cn.iocoder.dashboard.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.dashboard.util.date.DateUtils.addTime; /** * 在线用户 Session Service 实现类 @@ -53,7 +55,7 @@ public class SysUserSessionServiceImpl implements SysUserSessionService { // 写入 DB 中 SysUserSessionDO userSession = SysUserSessionDO.builder().id(sessionId) .userId(loginUser.getId()).userIp(userIp).userAgent(userAgent) - .sessionTimeout(DateUtils.addTime(LOGIN_USER.getTimeout())) + .sessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis()))) .build(); userSessionMapper.insert(userSession); // 返回 Session 编号 @@ -68,7 +70,7 @@ public class SysUserSessionServiceImpl implements SysUserSessionService { // 更新 DB 中 SysUserSessionDO updateObj = SysUserSessionDO.builder().id(sessionId).build(); updateObj.setUpdateTime(new Date()); - updateObj.setSessionTimeout(DateUtils.addTime(LOGIN_USER.getTimeout())); + updateObj.setSessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis()))); userSessionMapper.updateById(updateObj); } @@ -106,15 +108,17 @@ public class SysUserSessionServiceImpl implements SysUserSessionService { @Override public long clearSessionTimeout() { // 获取db里已经超时的用户列表 - Long timeoutCount = 0L; - List sessionTimeoutDOS = userSessionMapper.selectSessionTimeout(); - for (SysUserSessionDO sessionDO : sessionTimeoutDOS) { - // 确认已经超时,移出在线用户列表 - if (loginUserRedisDAO.get(sessionDO.getId()) == null) { - timeoutCount += userSessionMapper.deleteById(sessionDO.getId()); - } + List sessionTimeoutDOS = userSessionMapper.selectListBySessionTimoutLt(); + List timeoutIdList = sessionTimeoutDOS + .stream() + .filter(sessionDO -> loginUserRedisDAO.get(sessionDO.getId()) == null) + .map(SysUserSessionDO::getId) + .collect(Collectors.toList()); + // 确认已经超时,按批次移出在线用户列表 + if (CollUtil.isNotEmpty(timeoutIdList)) { + Lists.partition(timeoutIdList, 100).forEach(userSessionMapper::deleteBatchIds); } - return timeoutCount; + return timeoutIdList.size(); } /** diff --git a/src/test/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionServiceImplTest.java b/src/test/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionServiceImplTest.java new file mode 100644 index 000000000..45561da0b --- /dev/null +++ b/src/test/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionServiceImplTest.java @@ -0,0 +1,54 @@ +package cn.iocoder.dashboard.modules.system.service.auth; + +import cn.hutool.core.date.DateUtil; +import cn.iocoder.dashboard.BaseSpringBootUnitTest; +import cn.iocoder.dashboard.framework.mybatis.core.query.QueryWrapperX; +import cn.iocoder.dashboard.modules.system.dal.dataobject.auth.SysUserSessionDO; +import cn.iocoder.dashboard.modules.system.dal.mysql.auth.SysUserSessionMapper; +import cn.iocoder.dashboard.util.RandomUtils; +import org.junit.jupiter.api.Test; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * SysUserSessionServiceImpl Tester. + * + * @author Lyon + * @version 1.0 + * @since
3月 8, 2021
+ */ +public class SysUserSessionServiceImplTest extends BaseSpringBootUnitTest { + + @Resource + SysUserSessionService sysUserSessionService; + @Resource + SysUserSessionMapper sysUserSessionMapper; + + @Test + public void testClearSessionTimeout_success() throws Exception { + // 准备超时数据 120 条, 在线用户 1 条 + int expectedTimeoutCount = 120, expectedTotal = 1; + + // 准备数据 + List prepareData = Stream + .iterate(0, i -> i) + .limit(expectedTimeoutCount) + .map(i -> RandomUtils.randomPojo(SysUserSessionDO.class, o -> o.setSessionTimeout(DateUtil.offsetSecond(new Date(), -1)))) + .collect(Collectors.toList()); + prepareData.add(RandomUtils.randomPojo(SysUserSessionDO.class, o -> o.setSessionTimeout(DateUtil.offsetMinute(new Date(), 30)))); + prepareData.forEach(sysUserSessionMapper::insert); + + //清空超时数据 + long actualTimeoutCount = sysUserSessionService.clearSessionTimeout(); + assertEquals(expectedTimeoutCount, actualTimeoutCount); + Integer actualTotal = sysUserSessionMapper.selectCount(new QueryWrapperX<>()); + assertEquals(expectedTotal, actualTotal); + } + +} diff --git a/src/test/resources/sql/clean.sql b/src/test/resources/sql/clean.sql index ccc6cbb44..110625b54 100644 --- a/src/test/resources/sql/clean.sql +++ b/src/test/resources/sql/clean.sql @@ -8,3 +8,4 @@ DELETE FROM "sys_role"; DELETE FROM "sys_role_menu"; DELETE FROM "sys_menu"; DELETE FROM "sys_dict_type"; +DELETE FROM "sys_user_session"; diff --git a/src/test/resources/sql/create_tables.sql b/src/test/resources/sql/create_tables.sql index 29078224e..827d4923d 100644 --- a/src/test/resources/sql/create_tables.sql +++ b/src/test/resources/sql/create_tables.sql @@ -114,3 +114,17 @@ CREATE TABLE "sys_dict_type" ( "deleted" bit NOT NULL DEFAULT FALSE, PRIMARY KEY ("id") ) COMMENT '字典类型表'; + +CREATE TABLE `sys_user_session` ( + `id` varchar(32) NOT NULL, + `user_id` bigint DEFAULT NULL, + `user_ip` varchar(50) DEFAULT NULL, + `user_agent` varchar(512) DEFAULT NULL, + `session_timeout` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "create_by" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_by` varchar(64) DEFAULT '' , + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY (`id`) +) COMMENT '用户在线 Session';