add 增加 邮箱验证码发送接口

add 增加 邮箱登陆接口
This commit is contained in:
疯狂的狮子li 2023-03-30 10:19:04 +08:00
parent 0c1d6e111a
commit 6ed424f89e
11 changed files with 172 additions and 5 deletions

View File

@ -3,6 +3,7 @@ package com.ruoyi.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.collection.CollUtil;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.model.EmailLoginBody;
import com.ruoyi.common.core.domain.model.LoginBody;
import com.ruoyi.common.core.domain.model.RegisterBody;
import com.ruoyi.common.core.domain.model.SmsLoginBody;
@ -62,7 +63,7 @@ public class AuthController {
}
/**
* 短信登录(示例)
* 短信登录
*
* @param body 登录信息
* @return 结果
@ -76,6 +77,21 @@ public class AuthController {
return R.ok(loginVo);
}
/**
* 邮件登录
*
* @param body 登录信息
* @return 结果
*/
@PostMapping("/emailLogin")
public R<LoginVo> emailLogin(@Validated @RequestBody EmailLoginBody body) {
LoginVo loginVo = new LoginVo();
// 生成令牌
String token = loginService.emailLogin(body.getTenantId(), body.getEmail(), body.getEmailCode());
loginVo.setToken(token);
return R.ok(loginVo);
}
/**
* 小程序登录(示例)
*

View File

@ -11,6 +11,8 @@ import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.SpringUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.reflect.ReflectUtils;
import com.ruoyi.common.mail.config.properties.MailProperties;
import com.ruoyi.common.mail.utils.MailUtils;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.sms.config.properties.SmsProperties;
import com.ruoyi.common.sms.core.SmsTemplate;
@ -46,6 +48,7 @@ public class CaptchaController {
private final CaptchaProperties captchaProperties;
private final SmsProperties smsProperties;
private final MailProperties mailProperties;
/**
* 短信验证码
@ -53,8 +56,7 @@ public class CaptchaController {
* @param phonenumber 用户手机号
*/
@GetMapping("/sms/code")
public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}")
String phonenumber) {
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
if (!smsProperties.getEnabled()) {
return R.fail("当前系统没有开启短信功能!");
}
@ -74,6 +76,28 @@ public class CaptchaController {
return R.ok();
}
/**
* 邮箱验证码
*
* @param email 邮箱
*/
@GetMapping("/email/code")
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
if (!mailProperties.getEnabled()) {
return R.fail("当前系统没有开启邮箱功能!");
}
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());
return R.fail(e.getMessage());
}
return R.ok();
}
/**
* 生成验证码
*/

View File

@ -112,6 +112,23 @@ public class SysLoginService {
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.getUserId());
return StpUtil.getTokenValue();
}
public String xcxLogin(String xcxCode) {
// xcxCode 小程序调用 wx.login 授权后获取
@ -184,6 +201,18 @@ public class SysLoginService {
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 CaptchaExpireException();
}
return code.equals(emailCode);
}
/**
* 校验验证码
*
@ -241,6 +270,24 @@ public class SysLoginService {
return userMapper.selectUserByPhonenumber(phonenumber);
}
private SysUserVo loadUserByEmail(String tenantId, String email) {
SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
.select(SysUser::getPhonenumber, SysUser::getStatus)
.eq(TenantHelper.isEnable(), SysUser::getTenantId, tenantId)
.eq(SysUser::getEmail, email));
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()) {
return userMapper.selectTenantUserByEmail(email, tenantId);
}
return userMapper.selectUserByEmail(email);
}
private SysUserVo loadUserByOpenid(String openid) {
// 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
// todo 自行实现 userService.selectUserByOpenid(openid);

View File

@ -18,6 +18,7 @@ user.password.not.blank=用户密码不能为空
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
user.password.not.valid=* 5-50个字符
user.email.not.valid=邮箱格式错误
user.email.not.blank=邮箱不能为空
user.phonenumber.not.blank=用户手机号不能为空
user.mobile.phone.number.not.valid=手机号格式错误
user.login.success=登录成功
@ -42,6 +43,9 @@ rate.limiter.message=访问过于频繁,请稍候再试
sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
email.code.not.blank=邮箱验证码不能为空
email.code.retry.limit.count=邮箱验证码输入错误{0}次
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空
##租户
tenant.number.not.blank=租户编号不能为空

View File

@ -18,6 +18,7 @@ user.password.not.blank=Password cannot be empty
user.password.length.valid=Password length must be between {min} and {max} characters
user.password.not.valid=* 5-50 characters
user.email.not.valid=Mailbox format error
user.email.not.blank=Mailbox cannot be blank
user.phonenumber.not.blank=Phone number cannot be blank
user.mobile.phone.number.not.valid=Phone number format error
user.login.success=Login successful
@ -42,6 +43,9 @@ rate.limiter.message=Visit too frequently, please try again later
sms.code.not.blank=Sms code cannot be blank
sms.code.retry.limit.count=Sms code input error {0} times
sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes
email.code.not.blank=Email code cannot be blank
email.code.retry.limit.count=Email code input error {0} times
email.code.retry.limit.exceed=Email code input error {0} times, account locked for {1} minutes
xcx.code.not.blank=Mini program code cannot be blank
##租户
tenant.number.not.blank=Tenant number cannot be blank

View File

@ -18,6 +18,7 @@ user.password.not.blank=用户密码不能为空
user.password.length.valid=用户密码长度必须在{min}到{max}个字符之间
user.password.not.valid=* 5-50个字符
user.email.not.valid=邮箱格式错误
user.email.not.blank=邮箱不能为空
user.phonenumber.not.blank=用户手机号不能为空
user.mobile.phone.number.not.valid=手机号格式错误
user.login.success=登录成功
@ -42,6 +43,9 @@ rate.limiter.message=访问过于频繁,请稍候再试
sms.code.not.blank=短信验证码不能为空
sms.code.retry.limit.count=短信验证码输入错误{0}次
sms.code.retry.limit.exceed=短信验证码输入错误{0}次,帐户锁定{1}分钟
email.code.not.blank=邮箱验证码不能为空
email.code.retry.limit.count=邮箱验证码输入错误{0}次
email.code.retry.limit.exceed=邮箱验证码输入错误{0}次,帐户锁定{1}分钟
xcx.code.not.blank=小程序code不能为空
##租户
tenant.number.not.blank=租户编号不能为空

View File

@ -0,0 +1,35 @@
package com.ruoyi.common.core.domain.model;
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

@ -20,13 +20,13 @@ public class SmsLoginBody {
private String tenantId;
/**
* 用户名
* 手机号
*/
@NotBlank(message = "{user.phonenumber.not.blank}")
private String phonenumber;
/**
* 用户密码
* 短信code
*/
@NotBlank(message = "{sms.code.not.blank}")
private String smsCode;

View File

@ -22,6 +22,11 @@ public enum LoginType {
*/
SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
/**
* 邮箱登录
*/
EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"),
/**
* 小程序登录
*/

View File

@ -78,6 +78,14 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
*/
SysUserVo selectUserByPhonenumber(String phonenumber);
/**
* 通过邮箱查询用户
*
* @param email 邮箱
* @return 用户对象信息
*/
SysUserVo selectUserByEmail(String email);
/**
* 通过用户名查询用户(不走租户插件)
*
@ -98,6 +106,16 @@ public interface SysUserMapper extends BaseMapperPlus<SysUser, SysUserVo> {
@InterceptorIgnore(tenantLine = "true")
SysUserVo selectTenantUserByPhonenumber(String phonenumber, String tenantId);
/**
* 通过邮箱查询用户(不走租户插件)
*
* @param email 邮箱
* @param tenantId 租户id
* @return 用户对象信息
*/
@InterceptorIgnore(tenantLine = "true")
SysUserVo selectTenantUserByEmail(String email, String tenantId);
/**
* 通过用户ID查询用户
*

View File

@ -102,6 +102,11 @@
where u.del_flag = '0' and u.phonenumber = #{phonenumber}
</select>
<select id="selectUserByEmail" parameterType="String" resultMap="SysUserResult">
<include refid="selectUserVo"/>
where u.del_flag = '0' and u.email = #{email}
</select>
<select id="selectTenantUserByUserName" parameterType="String" resultMap="SysUserResult">
<include refid="selectUserVo"/>
where u.del_flag = '0' and u.user_name = #{userName} and u.tenant_id = #{tenantId}
@ -112,6 +117,11 @@
where u.del_flag = '0' and u.phonenumber = #{phonenumber} and u.tenant_id = #{tenantId}
</select>
<select id="selectTenantUserByEmail" parameterType="String" resultMap="SysUserResult">
<include refid="selectUserVo"/>
where u.del_flag = '0' and u.email = #{email} and u.tenant_id = #{tenantId}
</select>
<select id="selectUserById" parameterType="Long" resultMap="SysUserResult">
<include refid="selectUserVo"/>
where u.del_flag = '0' and u.user_id = #{userId}