mirror of
				https://github.com/dromara/RuoYi-Vue-Plus.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	add 增加 邮箱验证码发送接口
add 增加 邮箱登陆接口
This commit is contained in:
		@@ -10,10 +10,12 @@ import com.ruoyi.common.constant.Constants;
 | 
			
		||||
import com.ruoyi.common.core.domain.R;
 | 
			
		||||
import com.ruoyi.common.enums.CaptchaType;
 | 
			
		||||
import com.ruoyi.common.utils.StringUtils;
 | 
			
		||||
import com.ruoyi.common.utils.email.MailUtils;
 | 
			
		||||
import com.ruoyi.common.utils.redis.RedisUtils;
 | 
			
		||||
import com.ruoyi.common.utils.reflect.ReflectUtils;
 | 
			
		||||
import com.ruoyi.common.utils.spring.SpringUtils;
 | 
			
		||||
import com.ruoyi.framework.config.properties.CaptchaProperties;
 | 
			
		||||
import com.ruoyi.framework.config.properties.MailProperties;
 | 
			
		||||
import com.ruoyi.sms.config.properties.SmsProperties;
 | 
			
		||||
import com.ruoyi.sms.core.SmsTemplate;
 | 
			
		||||
import com.ruoyi.sms.entity.SmsResult;
 | 
			
		||||
@@ -47,6 +49,7 @@ public class CaptchaController {
 | 
			
		||||
    private final CaptchaProperties captchaProperties;
 | 
			
		||||
    private final SmsProperties smsProperties;
 | 
			
		||||
    private final ISysConfigService configService;
 | 
			
		||||
    private final MailProperties mailProperties;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 短信验证码
 | 
			
		||||
@@ -54,8 +57,7 @@ public class CaptchaController {
 | 
			
		||||
     * @param phonenumber 用户手机号
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("/captchaSms")
 | 
			
		||||
    public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}")
 | 
			
		||||
                              String phonenumber) {
 | 
			
		||||
    public R<Void> smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
 | 
			
		||||
        if (!smsProperties.getEnabled()) {
 | 
			
		||||
            return R.fail("当前系统没有开启短信功能!");
 | 
			
		||||
        }
 | 
			
		||||
@@ -75,6 +77,28 @@ public class CaptchaController {
 | 
			
		||||
        return R.ok();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 邮箱验证码
 | 
			
		||||
     *
 | 
			
		||||
     * @param email 邮箱
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("/captchaEmail")
 | 
			
		||||
    public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
 | 
			
		||||
        if (!mailProperties.getEnabled()) {
 | 
			
		||||
            return R.fail("当前系统没有开启邮箱功能!");
 | 
			
		||||
        }
 | 
			
		||||
        String key = CacheConstants.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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 生成验证码
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import com.ruoyi.common.constant.Constants;
 | 
			
		||||
import com.ruoyi.common.core.domain.R;
 | 
			
		||||
import com.ruoyi.common.core.domain.entity.SysMenu;
 | 
			
		||||
import com.ruoyi.common.core.domain.entity.SysUser;
 | 
			
		||||
import com.ruoyi.common.core.domain.model.EmailLoginBody;
 | 
			
		||||
import com.ruoyi.common.core.domain.model.LoginBody;
 | 
			
		||||
import com.ruoyi.common.core.domain.model.LoginUser;
 | 
			
		||||
import com.ruoyi.common.core.domain.model.SmsLoginBody;
 | 
			
		||||
@@ -57,7 +58,7 @@ public class SysLoginController {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 短信登录(示例)
 | 
			
		||||
     * 短信登录
 | 
			
		||||
     *
 | 
			
		||||
     * @param smsLoginBody 登录信息
 | 
			
		||||
     * @return 结果
 | 
			
		||||
@@ -72,6 +73,21 @@ public class SysLoginController {
 | 
			
		||||
        return R.ok(ajax);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 邮件登录
 | 
			
		||||
     *
 | 
			
		||||
     * @param body 登录信息
 | 
			
		||||
     * @return 结果
 | 
			
		||||
     */
 | 
			
		||||
    @PostMapping("/emailLogin")
 | 
			
		||||
    public R<Map<String, Object>> emailLogin(@Validated @RequestBody EmailLoginBody body) {
 | 
			
		||||
        Map<String, Object> ajax = new HashMap<>();
 | 
			
		||||
        // 生成令牌
 | 
			
		||||
        String token = loginService.emailLogin(body.getEmail(), body.getEmailCode());
 | 
			
		||||
        ajax.put(Constants.TOKEN, token);
 | 
			
		||||
        return R.ok(ajax);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 小程序登录(示例)
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -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,4 +43,7 @@ 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不能为空
 | 
			
		||||
 
 | 
			
		||||
@@ -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,4 +43,7 @@ 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
 | 
			
		||||
 
 | 
			
		||||
@@ -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,4 +43,7 @@ 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不能为空
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
package com.ruoyi.common.core.domain.model;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import javax.validation.constraints.Email;
 | 
			
		||||
import javax.validation.constraints.NotBlank;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 短信登录对象
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class EmailLoginBody {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 邮箱
 | 
			
		||||
     */
 | 
			
		||||
    @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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -14,13 +14,13 @@ import javax.validation.constraints.NotBlank;
 | 
			
		||||
public class SmsLoginBody {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 用户名
 | 
			
		||||
     * 手机号
 | 
			
		||||
     */
 | 
			
		||||
    @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"),
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 小程序登录
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -76,6 +76,14 @@ public interface SysUserMapper extends BaseMapperPlus<SysUserMapper, SysUser, Sy
 | 
			
		||||
     */
 | 
			
		||||
    SysUser selectUserByPhonenumber(String phonenumber);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 通过邮箱查询用户
 | 
			
		||||
     *
 | 
			
		||||
     * @param email 邮箱
 | 
			
		||||
     * @return 用户对象信息
 | 
			
		||||
     */
 | 
			
		||||
    SysUser selectUserByEmail(String email);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 通过用户ID查询用户
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -98,6 +98,20 @@ public class SysLoginService {
 | 
			
		||||
        return StpUtil.getTokenValue();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String emailLogin(String email, String emailCode) {
 | 
			
		||||
        // 通过手机号查找用户
 | 
			
		||||
        SysUser user = loadUserByEmail(email);
 | 
			
		||||
 | 
			
		||||
        checkLogin(LoginType.EMAIL, user.getUserName(), () -> !validateEmailCode(email, emailCode));
 | 
			
		||||
        // 此处可根据登录用户的数据不同 自行创建 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 授权后获取
 | 
			
		||||
@@ -160,6 +174,18 @@ public class SysLoginService {
 | 
			
		||||
        return code.equals(smsCode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验邮箱验证码
 | 
			
		||||
     */
 | 
			
		||||
    private boolean validateEmailCode(String email, String emailCode) {
 | 
			
		||||
        String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + email);
 | 
			
		||||
        if (StringUtils.isBlank(code)) {
 | 
			
		||||
            recordLogininfor(email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
 | 
			
		||||
            throw new CaptchaExpireException();
 | 
			
		||||
        }
 | 
			
		||||
        return code.equals(emailCode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验验证码
 | 
			
		||||
     *
 | 
			
		||||
@@ -209,6 +235,20 @@ public class SysLoginService {
 | 
			
		||||
        return userMapper.selectUserByPhonenumber(phonenumber);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private SysUser loadUserByEmail(String email) {
 | 
			
		||||
        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
 | 
			
		||||
            .select(SysUser::getPhonenumber, SysUser::getStatus)
 | 
			
		||||
            .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);
 | 
			
		||||
        }
 | 
			
		||||
        return userMapper.selectUserByEmail(email);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private SysUser loadUserByOpenid(String openid) {
 | 
			
		||||
        // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
 | 
			
		||||
        // todo 自行实现 userService.selectUserByOpenid(openid);
 | 
			
		||||
 
 | 
			
		||||
@@ -128,6 +128,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="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