feat 若依登录接口

V0.5.x
jay 2023-06-02 18:11:42 +08:00
parent 7f1453b971
commit cdf77d22a0
17 changed files with 1207 additions and 0 deletions

View File

@ -0,0 +1,44 @@
package cc.iotkit.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
*
*
* @author Lion Li
*/
@Getter
@AllArgsConstructor
public enum LoginType {
/**
*
*/
PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"),
/**
*
*/
SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
/**
*
*/
EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"),
/**
*
*/
XCX("", "");
/**
*
*/
final String retryLimitExceed;
/**
*
*/
final String retryLimitCount;
}

View File

@ -0,0 +1,30 @@
package cc.iotkit.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
*
*
* @author ruoyi
*/
@Getter
@AllArgsConstructor
public enum UserStatus {
/**
*
*/
OK("0", "正常"),
/**
*
*/
DISABLE("1", "停用"),
/**
*
*/
DELETED("2", "删除");
private final String code;
private final String info;
}

View File

@ -0,0 +1,80 @@
package cc.iotkit.common.exception;
import cc.iotkit.common.utils.MessageUtils;
import cc.iotkit.common.utils.StringUtils;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
*
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class BaseException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
*
*/
private String module;
/**
*
*/
private String code;
/**
*
*/
private Object[] args;
/**
*
*/
private String defaultMessage;
public BaseException(String module, String code, Object[] args, String defaultMessage) {
this.module = module;
this.code = code;
this.args = args;
this.defaultMessage = defaultMessage;
}
public BaseException(String module, String code, Object[] args) {
this(module, code, args, null);
}
public BaseException(String module, String defaultMessage) {
this(module, null, null, defaultMessage);
}
public BaseException(String code, Object[] args) {
this(null, code, args, null);
}
public BaseException(String defaultMessage) {
this(null, null, null, defaultMessage);
}
@Override
public String getMessage() {
String message = null;
if (!StringUtils.isEmpty(code)) {
message = MessageUtils.message(code, args);
}
if (message == null) {
message = defaultMessage;
}
return message;
}
}

View File

@ -0,0 +1,44 @@
/*
* +----------------------------------------------------------------------
* | Copyright (c) 2021-2022 All rights reserved.
* +----------------------------------------------------------------------
* | Licensed
* +----------------------------------------------------------------------
* | Author: xw2sy@163.com
* +----------------------------------------------------------------------
*/
package cc.iotkit.common.exception.user;
import cc.iotkit.common.enums.ErrCode;
import cc.iotkit.common.exception.BaseException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserException extends BaseException {
/**
*
*/
private String module;
/**
*
*/
private Integer code;
/**
*
*/
private String message;
public UserException(String code, Object... args) {
super("user", code, args, null);
}
}

View File

@ -0,0 +1,35 @@
package cc.iotkit.common.undefined;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
*
*
* @author Lion Li
*/
@Data
public class EmailLoginBody {
/**
* ID
*/
@NotBlank(message = "{tenant.number.not.blank}")
private String tenantId;
/**
*
*/
@NotBlank(message = "{user.email.not.blank}")
@Email(message = "{user.email.not.valid}")
private String email;
/**
* code
*/
@NotBlank(message = "{email.code.not.blank}")
private String emailCode;
}

View File

@ -0,0 +1,33 @@
package cc.iotkit.common.undefined;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
*
*
* @author Lion Li
*/
@Data
public class SmsLoginBody {
/**
* ID
*/
@NotBlank(message = "{tenant.number.not.blank}")
private String tenantId;
/**
*
*/
@NotBlank(message = "{user.phonenumber.not.blank}")
private String phonenumber;
/**
* code
*/
@NotBlank(message = "{sms.code.not.blank}")
private String smsCode;
}

View File

@ -0,0 +1,26 @@
package cc.iotkit.common.undefined;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
*
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
public class XcxLoginUser extends LoginUser {
private static final long serialVersionUID = 1L;
/**
* openid
*/
private String openid;
}

View File

@ -0,0 +1,28 @@
package cc.iotkit.common.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
/**
* i18n
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MessageUtils {
private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class);
/**
* spring messageSource
*
* @param code
* @param args
* @return
*/
public static String message(String code, Object... args) {
return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
}
}

View File

@ -9,6 +9,7 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* sys_user
@ -17,6 +18,8 @@ import lombok.EqualsAndHashCode;
*/
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = SysUser.class, reverseConvertGenerate = false)
public class SysUserBo extends BaseDto {

View File

@ -0,0 +1,155 @@
package cc.iotkit.web.controller;
import cc.iotkit.common.exception.BizException;
import cc.iotkit.common.tenant.helper.TenantHelper;
import cc.iotkit.common.undefined.EmailLoginBody;
import cc.iotkit.common.undefined.LoginBody;
import cc.iotkit.common.undefined.RegisterBody;
import cc.iotkit.common.undefined.SmsLoginBody;
import cc.iotkit.common.utils.MapstructUtils;
import cc.iotkit.common.utils.StreamUtils;
import cc.iotkit.common.utils.StringUtils;
import cc.iotkit.system.dto.bo.SysTenantBo;
import cc.iotkit.system.dto.vo.SysTenantVo;
import cc.iotkit.system.service.ISysConfigService;
import cc.iotkit.system.service.ISysTenantService;
import cc.iotkit.web.domain.vo.LoginTenantVo;
import cc.iotkit.web.domain.vo.LoginVo;
import cc.iotkit.web.domain.vo.TenantListVo;
import cc.iotkit.web.service.SysLoginService;
import cc.iotkit.web.service.SysRegisterService;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.collection.CollUtil;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.net.URL;
import java.util.List;
/**
*
*
* @author Lion Li
*/
@SaIgnore
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/auth")
public class AuthController {
private final SysLoginService loginService;
private final SysRegisterService registerService;
private final ISysConfigService configService;
private final ISysTenantService tenantService;
/**
*
*
* @param body
* @return
*/
@PostMapping("/login")
public LoginVo login(@Validated @RequestBody LoginBody body) {
LoginVo loginVo = new LoginVo();
// 生成令牌
String token = loginService.login(
body.getTenantId(),
body.getUsername(), body.getPassword(),
body.getCode(), body.getUuid());
loginVo.setToken(token);
return loginVo;
}
/**
*
*
* @param body
* @return
*/
@PostMapping("/smsLogin")
public LoginVo smsLogin(@Validated @RequestBody SmsLoginBody body) {
LoginVo loginVo = new LoginVo();
// 生成令牌
String token = loginService.smsLogin(body.getTenantId(), body.getPhonenumber(), body.getSmsCode());
loginVo.setToken(token);
return loginVo;
}
/**
*
*
* @param body
* @return
*/
@PostMapping("/emailLogin")
public LoginVo emailLogin(@Validated @RequestBody EmailLoginBody body) {
LoginVo loginVo = new LoginVo();
// 生成令牌
String token = loginService.emailLogin(body.getTenantId(), body.getEmail(), body.getEmailCode());
loginVo.setToken(token);
return loginVo;
}
/**
* ()
*
* @param xcxCode code
* @return
*/
@PostMapping("/xcxLogin")
public LoginVo xcxLogin(@NotBlank(message = "{xcx.code.not.blank}") String xcxCode) {
LoginVo loginVo = new LoginVo();
// 生成令牌
String token = loginService.xcxLogin(xcxCode);
loginVo.setToken(token);
return loginVo;
}
/**
* 退
*/
@PostMapping("/logout")
public void logout() {
loginService.logout();
}
/**
*
*/
@PostMapping("/register")
public void register(@Validated @RequestBody RegisterBody user) {
if (!configService.selectRegisterEnabled(user.getTenantId())) {
throw new BizException("当前租户不允许注册");
}
registerService.register(user);
}
/**
*
*
* @return
*/
@GetMapping("/tenant/list")
public LoginTenantVo tenantList(HttpServletRequest request) throws Exception {
List<SysTenantVo> tenantList = tenantService.queryList(new SysTenantBo());
List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
// 获取域名
String host = new URL(request.getRequestURL().toString()).getHost();
// 根据域名进行筛选
List<TenantListVo> list = StreamUtils.filter(voList, vo -> StringUtils.equals(vo.getDomain(), host));
// 返回对象
LoginTenantVo vo = new LoginTenantVo();
vo.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
vo.setTenantEnabled(TenantHelper.isEnable());
return vo;
}
}

View File

@ -0,0 +1,132 @@
package cc.iotkit.web.controller;
import cc.iotkit.common.constant.Constants;
import cc.iotkit.common.constant.GlobalConstants;
import cc.iotkit.common.exception.BizException;
import cc.iotkit.common.redis.utils.RedisUtils;
import cc.iotkit.common.utils.ReflectUtils;
import cc.iotkit.common.utils.SpringUtils;
import cc.iotkit.common.utils.StringUtils;
import cc.iotkit.common.web.config.properties.CaptchaProperties;
import cc.iotkit.common.web.enums.CaptchaType;
import cc.iotkit.web.domain.vo.CaptchaVo;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
*
*
* @author Lion Li
*/
@SaIgnore
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
public class CaptchaController {
private final CaptchaProperties captchaProperties;
// private final SmsProperties smsProperties;
private final MailProperties mailProperties;
/**
*
*
* @param phonenumber
*/
// @GetMapping("/resource/sms/code")
// public void smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
// if (!smsProperties.getEnabled()) {
// throw new BizException("当前系统没有开启短信功能!");
// }
// String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
// String code = RandomUtil.randomNumbers(4);
// RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// // 验证码模板id 自行处理 (查数据库或写死均可)
// String templateId = "";
// Map<String, String> map = new HashMap<>(1);
// map.put("code", code);
// SmsTemplate smsTemplate = SpringUtils.getBean(SmsTemplate.class);
// SmsResult result = smsTemplate.send(phonenumber, templateId, map);
// if (!result.isSuccess()) {
// log.error("验证码短信发送异常 => {}", result);
// throw new RuntimeException("验证码短信发送异常");
// }
// return ;
// }
/**
*
*
* @param email
*/
// @GetMapping("/resource/email/code")
// public void emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
// if (!mailProperties.getEnabled()) {
// throw new BizException("当前系统没有开启邮件功能!"));
// }
// String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
// String code = RandomUtil.randomNumbers(4);
// RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// try {
// MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
// } catch (Exception e) {
// log.error("验证码短信发送异常 => {}", e.getMessage());
// throw new RuntimeException("验证码短信发送异常");
// }
//
// }
/**
*
*/
@GetMapping("/code")
public CaptchaVo getCode() {
CaptchaVo captchaVo = new CaptchaVo();
boolean captchaEnabled = captchaProperties.getEnable();
if (!captchaEnabled) {
captchaVo.setCaptchaEnabled(false);
return captchaVo;
}
// 保存验证码信息
String uuid = IdUtil.simpleUUID();
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
// 生成验证码
CaptchaType captchaType = captchaProperties.getType();
boolean isMath = CaptchaType.MATH == captchaType;
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
captcha.setGenerator(codeGenerator);
captcha.createCode();
String code = captcha.getCode();
if (isMath) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
code = exp.getValue(String.class);
}
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
captchaVo.setUuid(uuid);
captchaVo.setImg(captcha.getImageBase64());
return captchaVo;
}
}

View File

@ -0,0 +1,25 @@
package cc.iotkit.web.domain.vo;
import lombok.Data;
/**
*
*
* @author Michelle.Chung
*/
@Data
public class CaptchaVo {
/**
*
*/
private Boolean captchaEnabled = true;
private String uuid;
/**
*
*/
private String img;
}

View File

@ -0,0 +1,25 @@
package cc.iotkit.web.domain.vo;
import lombok.Data;
import java.util.List;
/**
*
*
* @author Michelle.Chung
*/
@Data
public class LoginTenantVo {
/**
*
*/
private Boolean tenantEnabled;
/**
*
*/
private List<TenantListVo> voList;
}

View File

@ -0,0 +1,15 @@
package cc.iotkit.web.domain.vo;
import lombok.Data;
/**
*
*
* @author Michelle.Chung
*/
@Data
public class LoginVo {
private String token;
}

View File

@ -0,0 +1,23 @@
package cc.iotkit.web.domain.vo;
import cc.iotkit.system.dto.vo.SysTenantVo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
/**
*
*
* @author Lion Li
*/
@Data
@AutoMapper(target = SysTenantVo.class)
public class TenantListVo {
private String tenantId;
private String companyName;
private String domain;
}

View File

@ -0,0 +1,403 @@
package cc.iotkit.web.service;
import cc.iotkit.common.constant.Constants;
import cc.iotkit.common.constant.GlobalConstants;
import cc.iotkit.common.enums.DeviceType;
import cc.iotkit.common.enums.LoginType;
import cc.iotkit.common.enums.UserStatus;
import cc.iotkit.common.exception.BizException;
import cc.iotkit.common.exception.user.UserException;
import cc.iotkit.common.redis.utils.RedisUtils;
import cc.iotkit.common.satoken.utils.LoginHelper;
import cc.iotkit.common.tenant.helper.TenantHelper;
import cc.iotkit.common.undefined.LoginUser;
import cc.iotkit.common.undefined.RoleDTO;
import cc.iotkit.common.undefined.XcxLoginUser;
import cc.iotkit.common.utils.DateUtils;
import cc.iotkit.common.utils.MapstructUtils;
import cc.iotkit.common.utils.MessageUtils;
import cc.iotkit.common.utils.StringUtils;
import cc.iotkit.common.web.config.properties.CaptchaProperties;
import cc.iotkit.common.web.utils.ServletUtils;
import cc.iotkit.data.system.ISysUserData;
import cc.iotkit.model.system.SysUser;
import cc.iotkit.system.dto.vo.SysUserVo;
import cc.iotkit.system.service.ISysPermissionService;
import cc.iotkit.system.service.ISysTenantService;
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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.function.Supplier;
/**
*
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Slf4j
@Service
public class SysLoginService {
private final ISysUserData userData;
private final CaptchaProperties captchaProperties;
private final ISysPermissionService permissionService;
private final ISysTenantService tenantService;
@Value("${user.password.maxRetryCount}")
private Integer maxRetryCount;
@Value("${user.password.lockTime}")
private Integer lockTime;
/**
*
*
* @param username
* @param password
* @param code
* @param uuid
* @return
*/
public String login(String tenantId, String username, String password, String code, String uuid) {
boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(tenantId, username, code, uuid);
}
// 校验租户
checkTenant(tenantId);
SysUserVo user = loadUserByUsername(tenantId, username);
checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = buildLoginUser(user);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.PC);
recordLogininfor(loginUser.getTenantId(), username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getId());
return StpUtil.getTokenValue();
}
public String smsLogin(String tenantId, String phonenumber, String smsCode) {
// 校验租户
checkTenant(tenantId);
// 通过手机号查找用户
SysUserVo user = loadUserByPhonenumber(tenantId, phonenumber);
checkLogin(LoginType.SMS, tenantId, user.getUserName(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = buildLoginUser(user);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getId());
return StpUtil.getTokenValue();
}
public String emailLogin(String tenantId, String email, String emailCode) {
// 校验租户
checkTenant(tenantId);
// 通过手机号查找用户
SysUserVo user = loadUserByEmail(tenantId, email);
checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser
LoginUser loginUser = buildLoginUser(user);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.APP);
recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getId());
return StpUtil.getTokenValue();
}
public String xcxLogin(String xcxCode) {
// xcxCode 为 小程序调用 wx.login 授权后获取
// todo 以下自行实现
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
String openid = "";
SysUserVo user = loadUserByOpenid(openid);
// 校验租户
checkTenant(user.getTenantId());
// 此处可根据登录用户的数据不同 自行创建 loginUser
XcxLoginUser loginUser = new XcxLoginUser();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getId());
loginUser.setUsername(user.getUserName());
loginUser.setUserType(user.getUserType());
loginUser.setOpenid(openid);
// 生成token
LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
recordLogininfor(loginUser.getTenantId(), user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
recordLoginInfo(user.getId());
return StpUtil.getTokenValue();
}
/**
* 退
*/
public void logout() {
try {
LoginUser loginUser = LoginHelper.getLoginUser();
if (TenantHelper.isEnable() && LoginHelper.isSuperAdmin()) {
// 超级管理员 登出清除动态租户
TenantHelper.clearDynamic();
}
StpUtil.logout();
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
} catch (NotLoginException ignored) {
}
}
/**
*
*
* @param tenantId ID
* @param username
* @param status
* @param message
*/
private void recordLogininfor(String tenantId, String username, String status, String message) {
}
/**
*
*/
private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new BizException(MessageUtils.message("user.jcaptcha.expire"));
}
return code.equals(smsCode);
}
/**
*
*/
private boolean validateEmailCode(String tenantId, String email, String emailCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
if (StringUtils.isBlank(code)) {
recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new BizException("验证码过期");
}
return code.equals(emailCode);
}
/**
*
*
* @param username
* @param code
* @param uuid
*/
public void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new BizException("验证码过期");
}
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new BizException("验证码错误");
}
}
private SysUserVo loadUserByUsername(String tenantId, String username) {
SysUser query = new SysUser();
query.setUserName(username);
if(TenantHelper.isEnable()){
query.setTenantId(tenantId);
}
SysUser user = userData.findOneByCondition(query);
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);
}
if (TenantHelper.isEnable()) {
SysUser sysUser = userData.selectTenantUserByUserName(username, tenantId);
return MapstructUtils.convert(sysUser, SysUserVo.class);
}
SysUser sysUser = userData.selectUserByUserName(username);
return MapstructUtils.convert(sysUser, SysUserVo.class);
}
private SysUserVo loadUserByPhonenumber(String tenantId, String phonenumber) {
SysUser query = new SysUser();
query.setPhonenumber(phonenumber);
if(TenantHelper.isEnable()){
query.setTenantId(tenantId);
}
SysUser user = userData.findOneByCondition(query);
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);
}
if (TenantHelper.isEnable()) {
SysUser sysUser = userData.selectTenantUserByPhonenumber(phonenumber, tenantId);
return MapstructUtils.convert(sysUser, SysUserVo.class);
}
SysUser userFind = userData.selectByPhonenumber(phonenumber);
return MapstructUtils.convert(userFind, SysUserVo.class);
}
private SysUserVo loadUserByEmail(String tenantId, String email) {
SysUser query = new SysUser();
query.setEmail(email);
if(TenantHelper.isEnable()){
query.setTenantId(tenantId);
}
SysUser user = userData.findOneByCondition(query);
if (ObjectUtil.isNull(user)) {
log.info("登录用户:{} 不存在.", email);
throw new UserException("user.not.exists", email);
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", email);
throw new UserException("user.blocked", email);
}
if (TenantHelper.isEnable()) {
SysUser sysUser = userData.selectTenantUserByEmail(email, tenantId);
return MapstructUtils.convert(sysUser, SysUserVo.class);
}
SysUser sysUser = userData.selectUserByEmail(email);
return MapstructUtils.convert(sysUser, SysUserVo.class);
}
private SysUserVo loadUserByOpenid(String openid) {
// 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
// todo 自行实现 userService.selectUserByOpenid(openid);
SysUserVo user = new SysUserVo();
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(SysUserVo user) {
LoginUser loginUser = new LoginUser();
loginUser.setTenantId(user.getTenantId());
loginUser.setUserId(user.getId());
loginUser.setDeptId(user.getDeptId());
loginUser.setUsername(user.getUserName());
loginUser.setUserType(user.getUserType());
loginUser.setMenuPermission(permissionService.getMenuPermission(user.getId()));
loginUser.setRolePermission(permissionService.getRolePermission(user.getId()));
loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
loginUser.setRoles(roles);
return loginUser;
}
/**
*
*
* @param userId ID
*/
public void recordLoginInfo(Long userId) {
SysUser sysUser = new SysUser();
sysUser.setId(userId);
sysUser.setLoginIp(ServletUtils.getClientIP());
sysUser.setLoginDate(DateUtils.getNowDate());
sysUser.setUpdateBy(userId);
userData.save(sysUser);
}
/**
*
*/
private void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
String errorKey = GlobalConstants.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(tenantId, 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(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} else {
// 未达到规定错误次数 则递增
RedisUtils.setCacheObject(errorKey, errorNumber);
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
}
}
// 登录成功 清空错误次数
RedisUtils.deleteObject(errorKey);
}
private void checkTenant(String tenantId) {
// if (!TenantHelper.isEnable()) {
// return;
// }
// if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
// return;
// }
// SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
// if (ObjectUtil.isNull(tenant)) {
// log.info("登录租户:{} 不存在.", tenantId);
// throw new TenantException("tenant.not.exists");
// } else if (TenantStatus.DISABLE.getCode().equals(tenant.getStatus())) {
// log.info("登录租户:{} 已被停用.", tenantId);
// throw new TenantException("tenant.blocked");
// } else if (ObjectUtil.isNotNull(tenant.getExpireTime())
// && new Date().after(tenant.getExpireTime())) {
// log.info("登录租户:{} 已超过有效期.", tenantId);
// throw new TenantException("tenant.expired");
// }
}
}

View File

@ -0,0 +1,106 @@
package cc.iotkit.web.service;
import cc.iotkit.common.constant.Constants;
import cc.iotkit.common.constant.GlobalConstants;
import cc.iotkit.common.enums.UserType;
import cc.iotkit.common.exception.BizException;
import cc.iotkit.common.exception.user.UserException;
import cc.iotkit.common.redis.utils.RedisUtils;
import cc.iotkit.common.undefined.RegisterBody;
import cc.iotkit.common.utils.MessageUtils;
import cc.iotkit.common.utils.StringUtils;
import cc.iotkit.common.web.config.properties.CaptchaProperties;
import cc.iotkit.system.dto.bo.SysUserBo;
import cc.iotkit.system.service.ISysUserService;
import cn.dev33.satoken.secure.BCrypt;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
*
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Service
public class SysRegisterService {
private final ISysUserService userService;
private final CaptchaProperties captchaProperties;
/**
*
*/
public void register(RegisterBody registerBody) {
String tenantId = registerBody.getTenantId();
String username = registerBody.getUsername();
String password = registerBody.getPassword();
// 校验用户类型是否存在
String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
}
SysUserBo sysUser = new SysUserBo();
sysUser.setUserName(username);
sysUser.setNickName(username);
sysUser.setPassword(BCrypt.hashpw(password));
sysUser.setUserType(userType);
if (!userService.checkUserNameUnique(sysUser)) {
throw new UserException("user.register.save.error", username);
}
boolean regFlag = userService.registerUser(sysUser, tenantId);
if (!regFlag) {
throw new UserException("user.register.error");
}
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
}
/**
*
*
* @param username
* @param code
* @param uuid
*/
public void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire"));
throw new BizException("验证码过期");
}
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error"));
throw new BizException("验证码错误");
// throw new CaptchaException();
}
}
/**
*
*
* @param tenantId ID
* @param username
* @param status
* @param message
* @return
*/
private void recordLogininfor(String tenantId, String username, String status, String message) {
// LogininforEvent logininforEvent = new LogininforEvent();
// logininforEvent.setTenantId(tenantId);
// logininforEvent.setUsername(username);
// logininforEvent.setStatus(status);
// logininforEvent.setMessage(message);
// logininforEvent.setRequest(ServletUtils.getRequest());
// SpringUtils.context().publishEvent(logininforEvent);
}
}