package jnpf.base.service.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import jnpf.base.LogSortEnum;
|
import jnpf.base.UserInfo;
|
import jnpf.base.entity.LogEntity;
|
import jnpf.base.entity.LoginSessionEntity;
|
import jnpf.base.mapper.LogMapper;
|
import jnpf.base.mapper.LoginSessionMapper;
|
import jnpf.base.model.logmodel.LoginSessionEventModel;
|
import jnpf.base.model.logmodel.LoginSessionEventResult;
|
import jnpf.base.model.logmodel.LoginSessionStatus;
|
import jnpf.base.model.logmodel.LogoutReason;
|
import jnpf.base.service.LoginSessionService;
|
import jnpf.base.service.SuperServiceImpl;
|
import jnpf.util.DateUtil;
|
import jnpf.util.RandomUtil;
|
import jnpf.util.RedisUtil;
|
import jnpf.util.StringUtil;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.util.Date;
|
import java.util.List;
|
|
@Service
|
@RequiredArgsConstructor
|
public class LoginSessionServiceImpl extends SuperServiceImpl<LoginSessionMapper, LoginSessionEntity> implements LoginSessionService {
|
|
private static final String TOKEN_MAPPING_PREFIX = "jnpf:login-session:token:";
|
private static final int SINGLE_LOGIN_MODE = 1;
|
private static final int CONCURRENT_LOGIN_MODE = 2;
|
|
private final LogMapper logMapper;
|
private final RedisUtil redisUtil;
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public LoginSessionEventResult record(LoginSessionEventModel event) {
|
if (event == null || StringUtil.isEmpty(event.getSessionId()) || StringUtil.isEmpty(event.getEventType())) {
|
return LoginSessionEventResult.fail(event == null ? null : event.getSessionId(), "登录会话事件参数不完整");
|
}
|
switch (event.getEventType()) {
|
case LoginSessionEventModel.OPEN:
|
return open(event);
|
case LoginSessionEventModel.CLOSE:
|
return close(event);
|
case LoginSessionEventModel.TOUCH:
|
return touch(event);
|
default:
|
return LoginSessionEventResult.fail(event.getSessionId(), "不支持的登录会话事件类型");
|
}
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public int closeExpired(Date beforeTime, int limit) {
|
QueryWrapper<LoginSessionEntity> query = new QueryWrapper<>();
|
query.lambda()
|
.eq(LoginSessionEntity::getStatus, LoginSessionStatus.OPEN)
|
.isNotNull(LoginSessionEntity::getExpireTime)
|
.lt(LoginSessionEntity::getExpireTime, beforeTime)
|
.orderByAsc(LoginSessionEntity::getExpireTime);
|
Page<LoginSessionEntity> page = new Page<>(1, Math.max(1, Math.min(limit, 500)), false);
|
List<LoginSessionEntity> sessions = this.baseMapper.selectPage(page, query).getRecords();
|
int closed = 0;
|
for (LoginSessionEntity session : sessions) {
|
if (redisUtil.exists(TOKEN_MAPPING_PREFIX + session.getId())) {
|
continue;
|
}
|
LoginSessionEventModel event = LoginSessionEventModel.builder()
|
.eventId(RandomUtil.uuId())
|
.transitionId(RandomUtil.uuId())
|
.eventType(LoginSessionEventModel.CLOSE)
|
.sessionId(session.getId())
|
.logoutReason(LogoutReason.TIMEOUT)
|
.description("登录超时")
|
.eventTime(session.getExpireTime())
|
.build();
|
String logId = closeExisting(session, event, LogoutReason.TIMEOUT, "登录超时", session.getExpireTime());
|
if (StringUtil.isNotEmpty(logId)) {
|
closed++;
|
}
|
}
|
return closed;
|
}
|
|
private LoginSessionEventResult open(LoginSessionEventModel event) {
|
LoginSessionEntity existing = this.baseMapper.selectById(event.getSessionId());
|
if (existing != null) {
|
return LoginSessionEventResult.success(existing.getId(), existing.getLoginLogId());
|
}
|
|
UserInfo userInfo = event.getUserInfo();
|
Date eventTime = eventTime(event);
|
int loginMode = resolveLoginMode(event.getLoginMode());
|
if (loginMode == SINGLE_LOGIN_MODE && userInfo != null && StringUtil.isNotEmpty(userInfo.getUserId())) {
|
QueryWrapper<LoginSessionEntity> query = new QueryWrapper<>();
|
query.lambda()
|
.eq(StringUtil.isNotEmpty(userInfo.getTenantId()), LoginSessionEntity::getTenantId, userInfo.getTenantId())
|
.isNull(StringUtil.isEmpty(userInfo.getTenantId()), LoginSessionEntity::getTenantId)
|
.eq(LoginSessionEntity::getUserId, userInfo.getUserId())
|
.eq(LoginSessionEntity::getStatus, LoginSessionStatus.OPEN);
|
query.last("FOR UPDATE");
|
List<LoginSessionEntity> openSessions = this.baseMapper.selectList(query);
|
for (LoginSessionEntity oldSession : openSessions) {
|
closeExisting(oldSession, event, LogoutReason.REPLACED, "登录顶替", eventTime);
|
}
|
}
|
|
String loginLogId = RandomUtil.uuId();
|
LogEntity loginLog = buildLog(event, loginLogId, 0, null, null, 2, "登录成功");
|
logMapper.insert(loginLog);
|
|
LoginSessionEntity session = new LoginSessionEntity();
|
session.setId(event.getSessionId());
|
fillUser(session, userInfo);
|
session.setLoginMode(loginMode);
|
session.setStatus(LoginSessionStatus.OPEN);
|
session.setLoginLogId(loginLogId);
|
session.setLoginTime(eventTime);
|
session.setLastActiveTime(eventTime);
|
session.setExpireTime(event.getExpireTime());
|
session.setSource(StringUtil.isEmpty(event.getSource()) ? "NEW_LOGIN" : event.getSource());
|
session.setVersion(0);
|
session.setCreatorTime(eventTime);
|
session.setCreatorUserId(userInfo == null ? null : userInfo.getUserId());
|
this.baseMapper.insert(session);
|
return LoginSessionEventResult.success(event.getSessionId(), loginLogId);
|
}
|
|
private LoginSessionEventResult close(LoginSessionEventModel event) {
|
LoginSessionEntity session = this.baseMapper.selectById(event.getSessionId());
|
if (session == null) {
|
session = createLegacySession(event);
|
}
|
if (LoginSessionStatus.CLOSED.equals(session.getStatus())) {
|
return LoginSessionEventResult.success(session.getId(), session.getLogoutLogId());
|
}
|
String reason = StringUtil.isEmpty(event.getLogoutReason()) ? LogoutReason.UNKNOWN_RECOVERY : event.getLogoutReason();
|
String logoutLogId = closeExisting(session, event, reason, event.getDescription(), eventTime(event));
|
return LoginSessionEventResult.success(event.getSessionId(), logoutLogId);
|
}
|
|
private LoginSessionEventResult touch(LoginSessionEventModel event) {
|
UpdateWrapper<LoginSessionEntity> update = new UpdateWrapper<>();
|
update.lambda()
|
.eq(LoginSessionEntity::getId, event.getSessionId())
|
.eq(LoginSessionEntity::getStatus, LoginSessionStatus.OPEN)
|
.set(LoginSessionEntity::getLastActiveTime, eventTime(event))
|
.set(event.getExpireTime() != null, LoginSessionEntity::getExpireTime, event.getExpireTime())
|
.setSql("f_version = f_version + 1");
|
int updated = this.baseMapper.update(null, update);
|
if (updated == 0 && this.baseMapper.selectById(event.getSessionId()) == null && event.getUserInfo() != null) {
|
createLegacySession(event);
|
}
|
return LoginSessionEventResult.success(event.getSessionId(), null);
|
}
|
|
private LoginSessionEntity createLegacySession(LoginSessionEventModel event) {
|
UserInfo userInfo = event.getUserInfo();
|
int loginMode = resolveLoginMode(event.getLoginMode());
|
Date loginTime = parseLoginTime(userInfo, eventTime(event));
|
Date expireTime = event.getExpireTime() != null
|
? event.getExpireTime()
|
: (userInfo == null ? null : userInfo.getOverdueTime());
|
String loginLogId = RandomUtil.uuId();
|
LoginSessionEventModel adopted = LoginSessionEventModel.builder()
|
.eventId(event.getEventId())
|
.transitionId(event.getTransitionId())
|
.eventType(LoginSessionEventModel.OPEN)
|
.sessionId(event.getSessionId())
|
.userInfo(userInfo)
|
.loginMode(loginMode)
|
.source("LEGACY_ADOPTED")
|
.eventTime(loginTime)
|
.expireTime(expireTime)
|
.ipAddress(event.getIpAddress())
|
.ipAddressName(event.getIpAddressName())
|
.browser(event.getBrowser())
|
.platform(event.getPlatform())
|
.requestUrl(event.getRequestUrl())
|
.requestMethod(event.getRequestMethod())
|
.build();
|
LogEntity loginLog = buildLog(adopted, loginLogId, 0, null, null, 2, "存量会话接管");
|
logMapper.insert(loginLog);
|
|
LoginSessionEntity session = new LoginSessionEntity();
|
session.setId(event.getSessionId());
|
fillUser(session, userInfo);
|
session.setLoginMode(loginMode);
|
session.setStatus(LoginSessionStatus.OPEN);
|
session.setLoginLogId(loginLogId);
|
session.setLoginTime(loginTime);
|
session.setLastActiveTime(loginTime);
|
session.setExpireTime(expireTime);
|
session.setSource("LEGACY_ADOPTED");
|
session.setVersion(0);
|
session.setCreatorTime(loginTime);
|
session.setCreatorUserId(userInfo == null ? null : userInfo.getUserId());
|
this.baseMapper.insert(session);
|
return session;
|
}
|
|
private int resolveLoginMode(Integer loginMode) {
|
if (loginMode == null) {
|
return SINGLE_LOGIN_MODE;
|
}
|
if (loginMode != SINGLE_LOGIN_MODE && loginMode != CONCURRENT_LOGIN_MODE) {
|
throw new IllegalArgumentException("不支持的登录模式: " + loginMode);
|
}
|
return loginMode;
|
}
|
|
private String closeExisting(LoginSessionEntity session, LoginSessionEventModel event, String reason,
|
String description, Date logoutTime) {
|
String logoutLogId = RandomUtil.uuId();
|
UpdateWrapper<LoginSessionEntity> update = new UpdateWrapper<>();
|
update.lambda()
|
.eq(LoginSessionEntity::getId, session.getId())
|
.eq(LoginSessionEntity::getStatus, LoginSessionStatus.OPEN)
|
.set(LoginSessionEntity::getStatus, LoginSessionStatus.CLOSED)
|
.set(LoginSessionEntity::getLogoutLogId, logoutLogId)
|
.set(LoginSessionEntity::getLogoutTime, logoutTime)
|
.set(LoginSessionEntity::getLogoutReason, reason)
|
.set(LoginSessionEntity::getLogoutDescription, description)
|
.set(LoginSessionEntity::getLastModifyTime, logoutTime)
|
.setSql("f_version = f_version + 1");
|
if (this.baseMapper.update(null, update) == 0) {
|
LoginSessionEntity closed = this.baseMapper.selectById(session.getId());
|
return closed == null ? null : closed.getLogoutLogId();
|
}
|
|
LoginSessionEventModel closeEvent = event;
|
if (event.getUserInfo() == null || !session.getId().equals(event.getSessionId())) {
|
LogEntity loginLog = logMapper.selectById(session.getLoginLogId());
|
UserInfo snapshot = new UserInfo();
|
snapshot.setUserId(session.getUserId());
|
snapshot.setUserName(session.getUserName());
|
snapshot.setTenantId(session.getTenantId());
|
snapshot.setLoginDevice(session.getDeviceType());
|
snapshot.setGrantType(session.getGrantType());
|
closeEvent = LoginSessionEventModel.builder()
|
.eventId(event.getEventId())
|
.transitionId(event.getTransitionId())
|
.eventType(event.getEventType())
|
.sessionId(session.getId())
|
.userInfo(snapshot)
|
.eventTime(logoutTime)
|
.ipAddress(loginLog == null ? null : loginLog.getIpAddress())
|
.ipAddressName(loginLog == null ? null : loginLog.getIpAddressName())
|
.browser(loginLog == null ? null : loginLog.getBrowser())
|
.platform(loginLog == null ? null : loginLog.getPlatForm())
|
.requestUrl(event.getRequestUrl())
|
.requestMethod(event.getRequestMethod())
|
.build();
|
}
|
String text = StringUtil.isEmpty(description) ? logoutDescription(reason) : description;
|
LogEntity logoutLog = buildLog(closeEvent, logoutLogId, 1, session.getLoginLogId(), reason, 1, text);
|
logMapper.insert(logoutLog);
|
return logoutLogId;
|
}
|
|
private LogEntity buildLog(LoginSessionEventModel event, String id, int loginType, String relatedLogId,
|
String reason, int eventOrder, String description) {
|
UserInfo userInfo = event.getUserInfo();
|
LogEntity log = new LogEntity();
|
log.setId(id);
|
log.setType(LogSortEnum.LOGIN.getCode());
|
log.setLoginType(loginType);
|
log.setLoginMark(1);
|
log.setSessionId(event.getSessionId());
|
log.setRelatedLogId(relatedLogId);
|
log.setLogoutReason(reason);
|
log.setDeviceType(userInfo == null ? null : userInfo.getLoginDevice());
|
log.setEventTime(eventTime(event));
|
log.setTransitionId(event.getTransitionId());
|
log.setEventOrder(eventOrder);
|
log.setUserId(userInfo == null ? null : userInfo.getUserId());
|
log.setUserName(userName(userInfo));
|
log.setIpAddress(event.getIpAddress());
|
log.setIpAddressName(event.getIpAddressName());
|
log.setBrowser(event.getBrowser());
|
log.setPlatForm(event.getPlatform());
|
log.setRequestUrl(event.getRequestUrl());
|
log.setRequestMethod(event.getRequestMethod());
|
log.setRequestDuration(0);
|
log.setDescription(description);
|
log.setCreatorTime(eventTime(event));
|
log.setCreatorUserId(userInfo == null ? null : userInfo.getUserId());
|
return log;
|
}
|
|
private void fillUser(LoginSessionEntity session, UserInfo userInfo) {
|
if (userInfo == null) {
|
return;
|
}
|
session.setTenantId(userInfo.getTenantId());
|
session.setUserId(userInfo.getUserId());
|
session.setUserName(userName(userInfo));
|
session.setDeviceType(userInfo.getLoginDevice());
|
session.setGrantType(userInfo.getGrantType());
|
}
|
|
private String userName(UserInfo userInfo) {
|
if (userInfo == null) {
|
return null;
|
}
|
if (StringUtil.isEmpty(userInfo.getUserAccount())) {
|
return userInfo.getUserName();
|
}
|
return StringUtil.isEmpty(userInfo.getUserName())
|
? userInfo.getUserAccount()
|
: userInfo.getUserName() + "/" + userInfo.getUserAccount();
|
}
|
|
private Date eventTime(LoginSessionEventModel event) {
|
return event.getEventTime() == null ? DateUtil.getNowDate() : event.getEventTime();
|
}
|
|
private Date parseLoginTime(UserInfo userInfo, Date fallback) {
|
if (userInfo == null || StringUtil.isEmpty(userInfo.getLoginTime())) {
|
return fallback;
|
}
|
try {
|
return DateUtil.stringToDate(userInfo.getLoginTime());
|
} catch (Exception ignored) {
|
return fallback;
|
}
|
}
|
|
private String logoutDescription(String reason) {
|
if (LogoutReason.MANUAL.equals(reason)) return "主动退出";
|
if (LogoutReason.REPLACED.equals(reason)) return "登录顶替";
|
if (LogoutReason.KICKOUT.equals(reason)) return "强制下线";
|
if (LogoutReason.TIMEOUT.equals(reason)) return "登录超时";
|
if (LogoutReason.RELOGIN_RECOVERY.equals(reason)) return "再次登录补偿";
|
return "异常退出补偿";
|
}
|
}
|