mirror of
				https://github.com/dromara/RuoYi-Vue-Plus.git
				synced 2025-11-04 08:13:44 +08:00 
			
		
		
		
	add 增加 邮箱验证码发送接口
add 增加 邮箱登陆接口
This commit is contained in:
		@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 小程序登录(示例)
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 生成验证码
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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=租户编号不能为空
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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=租户编号不能为空
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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"),
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 小程序登录
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -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查询用户
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -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}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user