|
@@ -5,8 +5,10 @@ import cn.hutool.core.util.StrUtil;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.baomidou.mybatisplus.spring.service.impl.ServiceImpl;
|
|
import com.baomidou.mybatisplus.spring.service.impl.ServiceImpl;
|
|
|
|
|
+import com.zsjz.ai.common.auth.LoginActivityService;
|
|
|
import com.zsjz.ai.common.datasource.CaseDataSourceRegistry;
|
|
import com.zsjz.ai.common.datasource.CaseDataSourceRegistry;
|
|
|
import com.zsjz.ai.common.cache.CaseDataCache;
|
|
import com.zsjz.ai.common.cache.CaseDataCache;
|
|
|
|
|
+import com.zsjz.ai.common.exception.AccountInUseException;
|
|
|
import com.zsjz.ai.common.exception.ServerException;
|
|
import com.zsjz.ai.common.exception.ServerException;
|
|
|
import com.zsjz.ai.common.model.plat.dto.ChangePwdDTO;
|
|
import com.zsjz.ai.common.model.plat.dto.ChangePwdDTO;
|
|
|
import com.zsjz.ai.common.model.plat.dto.LoginDTO;
|
|
import com.zsjz.ai.common.model.plat.dto.LoginDTO;
|
|
@@ -49,12 +51,16 @@ public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> {
|
|
|
|
|
|
|
|
private final CaseDataSourceRegistry caseDataSourceRegistry;
|
|
private final CaseDataSourceRegistry caseDataSourceRegistry;
|
|
|
|
|
|
|
|
|
|
+ private final LoginActivityService loginActivityService;
|
|
|
|
|
+
|
|
|
public SysUserService(PasswordEncoder passwordEncoder,
|
|
public SysUserService(PasswordEncoder passwordEncoder,
|
|
|
CaseInfoMapper caseInfoMapper,
|
|
CaseInfoMapper caseInfoMapper,
|
|
|
- CaseDataSourceRegistry caseDataSourceRegistry) {
|
|
|
|
|
|
|
+ CaseDataSourceRegistry caseDataSourceRegistry,
|
|
|
|
|
+ LoginActivityService loginActivityService) {
|
|
|
this.passwordEncoder = passwordEncoder;
|
|
this.passwordEncoder = passwordEncoder;
|
|
|
this.caseInfoMapper = caseInfoMapper;
|
|
this.caseInfoMapper = caseInfoMapper;
|
|
|
this.caseDataSourceRegistry = caseDataSourceRegistry;
|
|
this.caseDataSourceRegistry = caseDataSourceRegistry;
|
|
|
|
|
+ this.loginActivityService = loginActivityService;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
// ------------------------------------------------------------------
|
|
@@ -64,6 +70,12 @@ public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> {
|
|
|
/**
|
|
/**
|
|
|
* 登录。
|
|
* 登录。
|
|
|
*
|
|
*
|
|
|
|
|
+ * <p><b>账号占用确认</b>:登录前先判断该账号是否正在使用(存在有效会话且半小时内活跃过,
|
|
|
|
|
+ * 见 {@link LoginActivityService})。命中时抛 {@link AccountInUseException},
|
|
|
|
|
+ * 前端拿到 {@code code=40901} 弹「是否确定登录」;用户确认后带 {@code force=true}
|
|
|
|
|
+ * 重新提交,这里先踢掉旧会话(会话监听会同步释放旧会话打开的案件)再登录。
|
|
|
|
|
+ * 不在使用则直接登录,不打扰用户。
|
|
|
|
|
+ *
|
|
|
* <p>生产默认 {@code sa-token.is-concurrent=false}:本次登录会把该账号此前的 token 顶下线
|
|
* <p>生产默认 {@code sa-token.is-concurrent=false}:本次登录会把该账号此前的 token 顶下线
|
|
|
* (旧 token 下次请求抛 {@code NotLoginException(BE_REPLACED)})。
|
|
* (旧 token 下次请求抛 {@code NotLoginException(BE_REPLACED)})。
|
|
|
* 联调时可用 {@code SA_TOKEN_CONCURRENT=true} 临时开启多端。
|
|
* 联调时可用 {@code SA_TOKEN_CONCURRENT=true} 临时开启多端。
|
|
@@ -82,14 +94,26 @@ public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> {
|
|
|
if (!FLAG_ON.equals(user.getStatus())) {
|
|
if (!FLAG_ON.equals(user.getStatus())) {
|
|
|
throw badRequest("账号已被停用,请联系管理员");
|
|
throw badRequest("账号已被停用,请联系管理员");
|
|
|
}
|
|
}
|
|
|
|
|
+ boolean force = Boolean.TRUE.equals(dto.getForce());
|
|
|
|
|
+ // 密码校验通过后才判断占用:避免用登录接口探测「某个账号是否在线」
|
|
|
|
|
+ if (!force && loginActivityService.isInUse(user.getId())) {
|
|
|
|
|
+ throw new AccountInUseException("该账号正在其他设备使用");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (force) {
|
|
|
|
|
+ // 确认接管:显式踢掉该账号所有在线会话。不依赖 is-concurrent 配置,
|
|
|
|
|
+ // 保证「确定登录」在任何配置下都能让旧会话下线(会话监听会释放旧案件)。
|
|
|
|
|
+ StpUtil.kickout(user.getId());
|
|
|
|
|
+ }
|
|
|
StpUtil.login(user.getId());
|
|
StpUtil.login(user.getId());
|
|
|
|
|
+ // 登录本身算一次活跃:半小时内重复登录会触发下一次确认
|
|
|
|
|
+ loginActivityService.touch(user.getId());
|
|
|
|
|
|
|
|
SysUser touch = new SysUser();
|
|
SysUser touch = new SysUser();
|
|
|
touch.setId(user.getId());
|
|
touch.setId(user.getId());
|
|
|
touch.setLastLoginTime(LocalDateTime.now());
|
|
touch.setLastLoginTime(LocalDateTime.now());
|
|
|
updateById(touch);
|
|
updateById(touch);
|
|
|
|
|
|
|
|
- log.info("用户登录成功: id={}, username={}", user.getId(), user.getUsername());
|
|
|
|
|
|
|
+ log.info("用户登录成功: id={}, username={}, force={}", user.getId(), user.getUsername(), force);
|
|
|
return toLoginVO(user, StpUtil.getTokenValue());
|
|
return toLoginVO(user, StpUtil.getTokenValue());
|
|
|
}
|
|
}
|
|
|
|
|
|