Browse Source

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

Lyon 4 years ago
parent
commit
6163cfb4c2

+ 1 - 1
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/auth/SysUserSessionMapper.java

@@ -20,7 +20,7 @@ public interface SysUserSessionMapper extends BaseMapperX<SysUserSessionDO> {
                 .likeIfPresent("user_ip", reqVO.getUserIp()));
     }
 
-    default List<SysUserSessionDO> selectSessionTimeout() {
+    default List<SysUserSessionDO> selectListBySessionTimoutLt() {
         return selectList(new QueryWrapperX<SysUserSessionDO>().lt("session_timeout",new Date()));
     }
 }

+ 1 - 1
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

+ 6 - 1
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) {

+ 1 - 1
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;
     }
 

+ 2 - 2
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
      **/

+ 16 - 12
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<SysUserSessionDO> sessionTimeoutDOS = userSessionMapper.selectSessionTimeout();
-        for (SysUserSessionDO sessionDO : sessionTimeoutDOS) {
-            // 确认已经超时,移出在线用户列表
-            if (loginUserRedisDAO.get(sessionDO.getId()) == null) {
-                timeoutCount += userSessionMapper.deleteById(sessionDO.getId());
-            }
+        List<SysUserSessionDO> sessionTimeoutDOS = userSessionMapper.selectListBySessionTimoutLt();
+        List<String> 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();
     }
 
     /**

+ 54 - 0
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 <pre>3月 8, 2021</pre>
+ */
+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<SysUserSessionDO> 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);
+    }
+
+} 

+ 1 - 0
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";

+ 14 - 0
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';