diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java index 795359c5b..f5a603a43 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CaptchaController.java @@ -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 smsCaptcha(@NotBlank(message = "{user.phonenumber.not.blank}") - String phonenumber) { + public R 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 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(); + } + /** * 生成验证码 */ diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java index 78c44e305..f982a5fac 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java @@ -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> emailLogin(@Validated @RequestBody EmailLoginBody body) { + Map ajax = new HashMap<>(); + // 生成令牌 + String token = loginService.emailLogin(body.getEmail(), body.getEmailCode()); + ajax.put(Constants.TOKEN, token); + return R.ok(ajax); + } + /** * 小程序登录(示例) * diff --git a/ruoyi-admin/src/main/resources/i18n/messages.properties b/ruoyi-admin/src/main/resources/i18n/messages.properties index 4ff55f41a..ffdd8f304 100644 --- a/ruoyi-admin/src/main/resources/i18n/messages.properties +++ b/ruoyi-admin/src/main/resources/i18n/messages.properties @@ -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不能为空 diff --git a/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties b/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties index c0faca93a..c1ca43916 100644 --- a/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties +++ b/ruoyi-admin/src/main/resources/i18n/messages_en_US.properties @@ -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 diff --git a/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties b/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties index 4ff55f41a..ffdd8f304 100644 --- a/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties +++ b/ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties @@ -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不能为空 diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/EmailLoginBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/EmailLoginBody.java new file mode 100644 index 000000000..b7bf81bdb --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/EmailLoginBody.java @@ -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; + +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java index ce774ac7e..b12e74c14 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/SmsLoginBody.java @@ -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; diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/LoginType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/LoginType.java index c91a4b94f..875e4762e 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/enums/LoginType.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/LoginType.java @@ -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"), + /** * 小程序登录 */ diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java index 9b585e1db..7147198da 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java @@ -76,6 +76,14 @@ public interface SysUserMapper extends BaseMapperPlus !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() + .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); diff --git a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml index 06a653efe..e73e715db 100644 --- a/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -128,6 +128,11 @@ where u.del_flag = '0' and u.phonenumber = #{phonenumber} + +