移除超时的在线用户&单元测试
parent
5803258ed7
commit
6163cfb4c2
|
@ -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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
**/
|
**/
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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";
|
||||||
|
|
|
@ -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';
|
||||||
|
|
Loading…
Reference in New Issue