Files
klp-oa/ruoyi-system/src/main/java/com/ruoyi/system/service/SysLoginService.java

296 lines
12 KiB
Java
Raw Normal View History

2021-12-21 10:15:12 +08:00
package com.ruoyi.system.service;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.constant.CacheConstants;
2021-12-21 10:15:12 +08:00
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.event.LogininforEvent;
import com.ruoyi.common.core.domain.dto.RoleDTO;
2021-12-21 10:15:12 +08:00
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.domain.model.XcxLoginUser;
import com.ruoyi.common.enums.DeviceType;
import com.ruoyi.common.enums.LoginType;
import com.ruoyi.common.enums.UserStatus;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.exception.user.UserException;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.mapper.SysUserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
2021-12-21 10:15:12 +08:00
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.List;
import java.util.function.Supplier;
2021-12-21 10:15:12 +08:00
/**
* 登录校验方法
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Slf4j
2021-12-21 10:15:12 +08:00
@Service
public class SysLoginService {
private final SysUserMapper userMapper;
private final ISysConfigService configService;
private final SysPermissionService permissionService;
2021-12-21 10:15:12 +08:00
@Value("${user.password.maxRetryCount}")
private Integer maxRetryCount;
@Value("${user.password.lockTime}")
private Integer lockTime;
2021-12-21 10:15:12 +08:00
/**
* 登录验证
*
* @param username 用户名
* @param password 密码
* @param code 验证码
* @param uuid 唯一标识
* @return 结果
*/
public String login(String username, String password, String code, String uuid) {
HttpServletRequest request = ServletUtils.getRequest();
boolean captchaEnabled = configService.selectCaptchaEnabled();
2021-12-21 10:15:12 +08:00
// 验证码开关
if (captchaEnabled) {
2021-12-21 10:15:12 +08:00
validateCaptcha(username, code, uuid, request);
}
SysUser user = loadUserByUsername(username);
checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword()));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = buildLoginUser(user);
2021-12-21 10:15:12 +08:00
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.PC);
recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getUserId(), username);
return StpUtil.getTokenValue();
2021-12-21 10:15:12 +08:00
}
public String smsLogin(String phonenumber, String smsCode) {
// 通过手机号查找用户
SysUser user = loadUserByPhonenumber(phonenumber);
checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = buildLoginUser(user);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getUserId(), user.getUserName());
return StpUtil.getTokenValue();
}
public String xcxLogin(String xcxCode) {
// xcxCode 为 小程序调用 wx.login 授权后获取
// todo 以下自行实现
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
String openid = "";
SysUser user = loadUserByOpenid(openid);
// 此处可根据登录用户的数据不同 自行创建 loginUser
XcxLoginUser loginUser = new XcxLoginUser();
loginUser.setUserId(user.getUserId());
loginUser.setUsername(user.getUserName());
loginUser.setUserType(user.getUserType());
loginUser.setOpenid(openid);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getUserId(), user.getUserName());
return StpUtil.getTokenValue();
}
/**
* 退出登录
*/
public void logout() {
try {
LoginUser loginUser = LoginHelper.getLoginUser();
StpUtil.logout();
recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
sync: 同步 RuoYi-Vue-Plus(v4.4.0) 更新 重大更新 [重大更新] 优化支持 oss 私有库功能(数据库字段改动) #cd9c3c3f [重大更新] 连接池由 druid 修改为 hikari 更新相关配置(原因可看文档) #1f42bd3d [重大更新] 移除 tlog(不支持UI界面 使用的人太少)建议使用 skywalking [重大更新] 增加 skywalking 集成 默认注释不开启(使用看文档) 依赖升级 update springboot 2.7.5 => 2.7.6 update springboot-admin 2.7.6 => 2.7.7 update satoken 1.31.0 => 1.33.0 update spring-doc 1.6.12 => 1.6.13 update easyexcel 3.1.1 => 3.1.3 update hutool 5.8.8 => 5.8.10 update redisson 3.17.7 => 3.18.0 update lock4j 2.2.2 => 2.2.3 update s3-adk 1.12.324 => 1.12.349 update mysql-docker 8.0.29 => 8.0.31 功能更新 update 优化 oss 云厂商增加 华为obs关键字 update 优化 冗余的三元表达式 update 优化 重置时取消部门选中 update 优化 新增返回警告消息提示 update 优化 hikari 参数顺序 最常用的放上面 删除无用 druid 监控页面 update 优化 p6spy 排除健康检查 sql 执行记录 update 优化 Dockerfile 创建目录命令 update 优化 将空‘catch’块形参重命名为‘ignored’ update 优化 使用本地缓存优化 excel 导出 数据量大字典转换慢问题 update 优化 字典转换实现去除字符串查找拼接优化效率 update 优化 减小腾讯短信引入jar包的体积 update 消除Vue3控制台出现的警告信息 update 忽略不必要的属性数据返回 update 替换 mysql-jdbc 最新坐标 新增功能 add 新增 junit5 单元测试案例 #6e8ef308 add 增加 sys_oss_config access_policy 桶权限类型字段 add 增加 4.3-4.4 更新 sql 文件 add 新增 字典数据映射注解 #da94e898 add 增加 RedisUtils 获取缓存Map的key列表 问题修复 fix 修复 上传png透明图片 生成头像透明部分变成黑色 fix 修复 sqlserver sql文件 重复主键数据问题 fix 修复 sqlserver 特定情况下报 ssl 证书问题 默认关闭 ssl 认证 fix 修复 table中更多按钮切换主题色未生效修复问题 fix 修复 菜单激活无法修改其填充颜色去除某些svg图标的fill="#bfbfbf"属性 fix 修复 使用缓冲流 导致上传异常问题 fix 修复 过滤器链使用IoUtil.read方法导致request流关闭 fix 修复 Log注解GET请求记录不到参数问题 fix 修复 某些特性的环境生成代码变乱码TXT文件问题 fix 修复 开启TopNav没有子菜单隐藏侧边栏 fix 修复 回显数据字典数组异常问题 移除功能 remove 移除过期 Anonymous 注解与其实现代码 remove 移除 tlog(不支持UI界面 使用的人太少) 建议使用 skywalking
2023-01-07 16:51:51 +08:00
} catch (NotLoginException ignored) {
}
}
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
private void recordLogininfor(String username, String status, String message) {
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
logininforEvent.setRequest(ServletUtils.getRequest());
SpringUtils.context().publishEvent(logininforEvent);
}
/**
* 校验短信验证码
*/
private boolean validateSmsCode(String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(smsCode);
}
2021-12-21 10:15:12 +08:00
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
*/
public void validateCaptcha(String username, String code, String uuid, HttpServletRequest request) {
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
2021-12-21 10:15:12 +08:00
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
2021-12-21 10:15:12 +08:00
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
2021-12-21 10:15:12 +08:00
throw new CaptchaException();
}
}
private SysUser loadUserByUsername(String username) {
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
.select(SysUser::getUserName, SysUser::getStatus)
.eq(SysUser::getUserName, username));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UserException("user.not.exists", username);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new UserException("user.blocked", username);
}
return userMapper.selectUserByUserName(username);
}
private SysUser loadUserByPhonenumber(String phonenumber) {
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
.select(SysUser::getPhonenumber, SysUser::getStatus)
.eq(SysUser::getPhonenumber, phonenumber));
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", phonenumber);
throw new UserException("user.not.exists", phonenumber);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", phonenumber);
throw new UserException("user.blocked", phonenumber);
}
return userMapper.selectUserByPhonenumber(phonenumber);
}
private SysUser loadUserByOpenid(String openid) {
// 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
// todo 自行实现 userService.selectUserByOpenid(openid);
SysUser user = new SysUser();
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", openid);
// todo 用户不存在 业务逻辑自行实现
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", openid);
// todo 用户已被停用 业务逻辑自行实现
}
return user;
}
/**
* 构建登录用户
*/
private LoginUser buildLoginUser(SysUser user) {
LoginUser loginUser = new LoginUser();
loginUser.setUserId(user.getUserId());
loginUser.setDeptId(user.getDeptId());
loginUser.setUsername(user.getUserName());
loginUser.setNickName(user.getNickName());
loginUser.setUserType(user.getUserType());
loginUser.setMenuPermission(permissionService.getMenuPermission(user));
loginUser.setRolePermission(permissionService.getRolePermission(user));
loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
loginUser.setRoles(roles);
return loginUser;
}
2021-12-21 10:15:12 +08:00
/**
* 记录登录信息
*
* @param userId 用户ID
*/
public void recordLoginInfo(Long userId, String username) {
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
sysUser.setLoginIp(ServletUtils.getClientIP());
sysUser.setLoginDate(DateUtils.getNowDate());
sysUser.setUpdateBy(username);
userMapper.updateById(sysUser);
2021-12-21 10:15:12 +08:00
}
/**
* 登录校验
*/
private void checkLogin(LoginType loginType, String username, Supplier<Boolean> supplier) {
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
String loginFail = Constants.LOGIN_FAIL;
// 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
Integer errorNumber = RedisUtils.getCacheObject(errorKey);
// 锁定时间内登录 则踢出
if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) {
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
}
if (supplier.get()) {
// 是否第一次
errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
// 达到规定错误次数 则锁定登录
if (errorNumber.equals(maxRetryCount)) {
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} else {
// 未达到规定错误次数 则递增
RedisUtils.setCacheObject(errorKey, errorNumber);
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
}
}
// 登录成功 清空错误次数
RedisUtils.deleteObject(errorKey);
}
2021-12-21 10:15:12 +08:00
}