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

pull/2/head
Lyon 2021-03-08 15:57:05 +08:00
parent 5803258ed7
commit 6163cfb4c2
9 changed files with 96 additions and 18 deletions

View File

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

View File

@ -16,7 +16,7 @@ public interface SysRedisKeyConstants {
RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登陆用户的缓存", RedisKeyDefine LOGIN_USER = new RedisKeyDefine("登陆用户的缓存",
"login_user:%s", // 参数为 sessionId "login_user:%s", // 参数为 sessionId
STRING, LoginUser.class, Duration.ofMinutes(30)); STRING, LoginUser.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("验证码的缓存", RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("验证码的缓存",
"captcha_code:%s", // 参数为 uuid "captcha_code:%s", // 参数为 uuid

View File

@ -1,11 +1,13 @@
package cn.iocoder.dashboard.modules.system.dal.redis.auth; package cn.iocoder.dashboard.modules.system.dal.redis.auth;
import cn.iocoder.dashboard.framework.security.core.LoginUser; 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 cn.iocoder.dashboard.util.json.JsonUtils;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.time.Duration;
import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.LOGIN_USER; import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.LOGIN_USER;
@ -19,6 +21,8 @@ public class SysLoginUserRedisDAO {
@Resource @Resource
private StringRedisTemplate stringRedisTemplate; private StringRedisTemplate stringRedisTemplate;
@Resource
private SysUserSessionService sysUserSessionService;
public LoginUser get(String sessionId) { public LoginUser get(String sessionId) {
String redisKey = formatKey(sessionId); String redisKey = formatKey(sessionId);
@ -27,7 +31,8 @@ public class SysLoginUserRedisDAO {
public void set(String sessionId, LoginUser loginUser) { public void set(String sessionId, LoginUser loginUser) {
String redisKey = formatKey(sessionId); 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) { public void delete(String sessionId) {

View File

@ -23,7 +23,7 @@ public class SysUserSessionTimeoutJob implements JobHandler {
public String execute(String param) throws Exception { public String execute(String param) throws Exception {
log.info("[execute][执行任务:{}]", "移除超时的在线用户"); log.info("[execute][执行任务:{}]", "移除超时的在线用户");
long timeoutCount = sysUserSessionService.clearSessionTimeout(); long timeoutCount = sysUserSessionService.clearSessionTimeout();
log.info("[execute][执行任务:{}]", "移除超时的在线用户完成" + timeoutCount); log.info("[execute][执行任务:{}:{}]", "移除超时的在线用户完成", timeoutCount);
return null; return null;
} }

View File

@ -62,8 +62,8 @@ public interface SysUserSessionService {
/** /**
* 线 * 线
* @param *
* @return {@link Long} * @return {@link Long }
* @author Lyon * @author Lyon
* @date 2021/3/7 * @date 2021/3/7
**/ **/

View File

@ -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.dal.redis.auth.SysLoginUserRedisDAO;
import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService; import cn.iocoder.dashboard.modules.system.service.auth.SysUserSessionService;
import cn.iocoder.dashboard.modules.system.service.user.SysUserService; 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 org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.time.Duration;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; 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.collection.CollectionUtils.convertSet;
import static cn.iocoder.dashboard.util.date.DateUtils.addTime;
/** /**
* 线 Session Service * 线 Session Service
@ -53,7 +55,7 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
// 写入 DB 中 // 写入 DB 中
SysUserSessionDO userSession = SysUserSessionDO.builder().id(sessionId) SysUserSessionDO userSession = SysUserSessionDO.builder().id(sessionId)
.userId(loginUser.getId()).userIp(userIp).userAgent(userAgent) .userId(loginUser.getId()).userIp(userIp).userAgent(userAgent)
.sessionTimeout(DateUtils.addTime(LOGIN_USER.getTimeout())) .sessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())))
.build(); .build();
userSessionMapper.insert(userSession); userSessionMapper.insert(userSession);
// 返回 Session 编号 // 返回 Session 编号
@ -68,7 +70,7 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
// 更新 DB 中 // 更新 DB 中
SysUserSessionDO updateObj = SysUserSessionDO.builder().id(sessionId).build(); SysUserSessionDO updateObj = SysUserSessionDO.builder().id(sessionId).build();
updateObj.setUpdateTime(new Date()); updateObj.setUpdateTime(new Date());
updateObj.setSessionTimeout(DateUtils.addTime(LOGIN_USER.getTimeout())); updateObj.setSessionTimeout(addTime(Duration.ofMillis(getSessionTimeoutMillis())));
userSessionMapper.updateById(updateObj); userSessionMapper.updateById(updateObj);
} }
@ -106,15 +108,17 @@ public class SysUserSessionServiceImpl implements SysUserSessionService {
@Override @Override
public long clearSessionTimeout() { public long clearSessionTimeout() {
// 获取db里已经超时的用户列表 // 获取db里已经超时的用户列表
Long timeoutCount = 0L; List<SysUserSessionDO> sessionTimeoutDOS = userSessionMapper.selectListBySessionTimoutLt();
List<SysUserSessionDO> sessionTimeoutDOS = userSessionMapper.selectSessionTimeout(); List<String> timeoutIdList = sessionTimeoutDOS
for (SysUserSessionDO sessionDO : sessionTimeoutDOS) { .stream()
// 确认已经超时,移出在线用户列表 .filter(sessionDO -> loginUserRedisDAO.get(sessionDO.getId()) == null)
if (loginUserRedisDAO.get(sessionDO.getId()) == null) { .map(SysUserSessionDO::getId)
timeoutCount += userSessionMapper.deleteById(sessionDO.getId()); .collect(Collectors.toList());
} // 确认已经超时,按批次移出在线用户列表
if (CollUtil.isNotEmpty(timeoutIdList)) {
Lists.partition(timeoutIdList, 100).forEach(userSessionMapper::deleteBatchIds);
} }
return timeoutCount; return timeoutIdList.size();
} }
/** /**

View File

@ -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);
}
}

View File

@ -8,3 +8,4 @@ DELETE FROM "sys_role";
DELETE FROM "sys_role_menu"; DELETE FROM "sys_role_menu";
DELETE FROM "sys_menu"; DELETE FROM "sys_menu";
DELETE FROM "sys_dict_type"; DELETE FROM "sys_dict_type";
DELETE FROM "sys_user_session";

View File

@ -114,3 +114,17 @@ CREATE TABLE "sys_dict_type" (
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT '字典类型表'; ) 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';