Commit 71b9104a13fddcd03d37e49c76b9e42f5c3db379

Authored by YunaiV
1 parent d784b113

增加短信验证码的功能

Showing 17 changed files with 255 additions and 16 deletions
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.http
@@ -6,3 +6,12 @@ Content-Type: application/json @@ -6,3 +6,12 @@ Content-Type: application/json
6 "mobile": "15601691300", 6 "mobile": "15601691300",
7 "password": "admin123" 7 "password": "admin123"
8 } 8 }
  9 +
  10 +### 请求 /send-sms-code 接口 => 成功
  11 +POST {{userServerUrl}}/send-sms-code
  12 +Content-Type: application/json
  13 +
  14 +{
  15 + "mobile": "15601691300",
  16 + "scene": 1
  17 +}
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.userserver.modules.system.controller.auth; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.userserver.modules.system.controller.auth;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.*; 4 import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.*;
5 import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService; 5 import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService;
  6 +import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
6 import io.swagger.annotations.Api; 7 import io.swagger.annotations.Api;
7 import io.swagger.annotations.ApiImplicitParam; 8 import io.swagger.annotations.ApiImplicitParam;
8 import io.swagger.annotations.ApiImplicitParams; 9 import io.swagger.annotations.ApiImplicitParams;
@@ -27,6 +28,8 @@ public class SysAuthController { @@ -27,6 +28,8 @@ public class SysAuthController {
27 28
28 @Resource 29 @Resource
29 private SysAuthService authService; 30 private SysAuthService authService;
  31 + @Resource
  32 + private SysSmsCodeService smsCodeService;
30 33
31 @PostMapping("/login") 34 @PostMapping("/login")
32 @ApiOperation("使用手机 + 密码登录") 35 @ApiOperation("使用手机 + 密码登录")
@@ -45,10 +48,8 @@ public class SysAuthController { @@ -45,10 +48,8 @@ public class SysAuthController {
45 @PostMapping("/send-sms-code") 48 @PostMapping("/send-sms-code")
46 @ApiOperation("发送手机验证码") 49 @ApiOperation("发送手机验证码")
47 public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid MbrAuthSendSmsReqVO reqVO) { 50 public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid MbrAuthSendSmsReqVO reqVO) {
48 -// passportManager.sendSmsCode(sendSmsCodeDTO, HttpUtil.getIp(request));  
49 -// // 返回成功  
50 -// return success(true);  
51 - return null; 51 + smsCodeService.sendSmsCode(reqVO.getMobile(), reqVO.getScene(), getClientIP());
  52 + return success(true);
52 } 53 }
53 54
54 @PostMapping("/reset-password") 55 @PostMapping("/reset-password")
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/vo/MbrAuthSendSmsReqVO.java
1 package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo; 1 package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
2 2
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
3 import cn.iocoder.yudao.framework.common.validation.Mobile; 4 import cn.iocoder.yudao.framework.common.validation.Mobile;
  5 +import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
4 import io.swagger.annotations.ApiModel; 6 import io.swagger.annotations.ApiModel;
5 import io.swagger.annotations.ApiModelProperty; 7 import io.swagger.annotations.ApiModelProperty;
6 import lombok.Data; 8 import lombok.Data;
@@ -19,6 +21,7 @@ public class MbrAuthSendSmsReqVO { @@ -19,6 +21,7 @@ public class MbrAuthSendSmsReqVO {
19 21
20 @ApiModelProperty(value = "发送场景", example = "1", notes = "对应 MbrSmsSceneEnum 枚举") 22 @ApiModelProperty(value = "发送场景", example = "1", notes = "对应 MbrSmsSceneEnum 枚举")
21 @NotNull(message = "发送场景不能为空") 23 @NotNull(message = "发送场景不能为空")
  24 + @InEnum(SysSmsSceneEnum.class)
22 private Integer scene; 25 private Integer scene;
23 26
24 } 27 }
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.userserver.modules.system.dal.dataobject;
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/dal/mysql/sms/MbrSmsCodeDO.java renamed to yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/sms/SysSmsCodeDO.java
1 -package cn.iocoder.yudao.userserver.modules.member.dal.mysql.sms; 1 +package cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import com.baomidou.mybatisplus.annotation.TableName; 4 import com.baomidou.mybatisplus.annotation.TableName;
5 -import lombok.Data;  
6 -import lombok.EqualsAndHashCode; 5 +import lombok.*;
7 import lombok.experimental.Accessors; 6 import lombok.experimental.Accessors;
8 7
9 import java.util.Date; 8 import java.util.Date;
@@ -15,11 +14,13 @@ import java.util.Date; @@ -15,11 +14,13 @@ import java.util.Date;
15 * 14 *
16 * @author 芋道源码 15 * @author 芋道源码
17 */ 16 */
18 -@TableName("mbr_sms_code") 17 +@TableName("sys_sms_code")
19 @Data 18 @Data
20 @EqualsAndHashCode(callSuper = true) 19 @EqualsAndHashCode(callSuper = true)
21 -@Accessors(chain = true)  
22 -public class MbrSmsCodeDO extends BaseDO { 20 +@Builder
  21 +@NoArgsConstructor
  22 +@AllArgsConstructor
  23 +public class SysSmsCodeDO extends BaseDO {
23 24
24 /** 25 /**
25 * 编号 26 * 编号
@@ -36,7 +37,7 @@ public class MbrSmsCodeDO extends BaseDO { @@ -36,7 +37,7 @@ public class MbrSmsCodeDO extends BaseDO {
36 /** 37 /**
37 * 发送场景 38 * 发送场景
38 * 39 *
39 - * 枚举 {@link MbrSmsCodeDO} 40 + * 枚举 {@link SysSmsCodeDO}
40 */ 41 */
41 private Integer scene; 42 private Integer scene;
42 /** 43 /**
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.userserver.modules.system.dal.mysql;
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/sms/SysSmsCodeMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.userserver.modules.system.dal.mysql.sms;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
  5 +import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO;
  6 +import org.apache.ibatis.annotations.Mapper;
  7 +
  8 +@Mapper
  9 +public interface SysSmsCodeMapper extends BaseMapperX<SysSmsCodeDO> {
  10 +
  11 + /**
  12 + * 获得手机号的最后一个手机验证码
  13 + *
  14 + * @param mobile 手机号
  15 + * @param scene 发送场景,选填
  16 + * @return 手机验证码
  17 + */
  18 + default SysSmsCodeDO selectLastByMobile(String mobile, Integer scene) {
  19 + return selectOne(new QueryWrapperX<SysSmsCodeDO>()
  20 + .eq("mobile", mobile)
  21 + .eqIfPresent("scene", scene)
  22 + .orderByDesc("id")
  23 + .last("LIMIT 1"));
  24 + }
  25 +
  26 +}
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/redis/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.userserver.modules.system.dal.redis;
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/SysErrorCodeConstants.java
@@ -14,4 +14,11 @@ public interface SysErrorCodeConstants { @@ -14,4 +14,11 @@ public interface SysErrorCodeConstants {
14 ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1005000001, "登录失败,账号被禁用"); 14 ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1005000001, "登录失败,账号被禁用");
15 ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1005000002, "登录失败"); // 登录失败的兜底,未知原因 15 ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1005000002, "登录失败"); // 登录失败的兜底,未知原因
16 16
  17 + // ========== SMS CODE 模块 1005001000 ==========
  18 + ErrorCode USER_SMS_CODE_NOT_FOUND = new ErrorCode(1005001000, "验证码不存在");
  19 + ErrorCode USER_SMS_CODE_EXPIRED = new ErrorCode(1005001001, "验证码已过期");
  20 + ErrorCode USER_SMS_CODE_USED = new ErrorCode(1005001002, "验证码已使用");
  21 + ErrorCode USER_SMS_CODE_NOT_CORRECT = new ErrorCode(1005001003, "验证码不正确");
  22 + ErrorCode USER_SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1005001004, "超过每日短信发送数量");
  23 + ErrorCode USER_SMS_CODE_SEND_TOO_FAST = new ErrorCode(1005001005, "短信发送过于频率");
17 } 24 }
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/enums/sms/MbrSmsSceneEnum.java renamed to yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/sms/SysSmsSceneEnum.java
1 -package cn.iocoder.yudao.userserver.modules.member.enums.sms; 1 +package cn.iocoder.yudao.userserver.modules.system.enums.sms;
2 2
3 import cn.iocoder.yudao.framework.common.core.IntArrayValuable; 3 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
4 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
@@ -13,13 +13,13 @@ import java.util.Arrays; @@ -13,13 +13,13 @@ import java.util.Arrays;
13 */ 13 */
14 @Getter 14 @Getter
15 @AllArgsConstructor 15 @AllArgsConstructor
16 -public enum MbrSmsSceneEnum implements IntArrayValuable { 16 +public enum SysSmsSceneEnum implements IntArrayValuable {
17 17
18 LOGIN_BY_SMS(1, "手机号登陆"), 18 LOGIN_BY_SMS(1, "手机号登陆"),
19 CHANGE_MOBILE_BY_SMS(2, "更换手机号"), 19 CHANGE_MOBILE_BY_SMS(2, "更换手机号"),
20 ; 20 ;
21 21
22 - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MbrSmsSceneEnum::getScene).toArray(); 22 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SysSmsSceneEnum::getScene).toArray();
23 23
24 private final Integer scene; 24 private final Integer scene;
25 private final String name; 25 private final String name;
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/package-info.java 0 → 100644
  1 +/**
  2 + * 属于 system 模块的 framework 封装
  3 + *
  4 + * @author 芋道源码
  5 + */
  6 +package cn.iocoder.yudao.userserver.modules.system.framework;
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/sms/SmsCodeConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.userserver.modules.system.framework.sms;
  2 +
  3 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  4 +import org.springframework.context.annotation.Configuration;
  5 +
  6 +@Configuration
  7 +@EnableConfigurationProperties(SmsCodeProperties.class)
  8 +public class SmsCodeConfiguration {
  9 +}
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/sms/SmsCodeProperties.java 0 → 100644
  1 +package cn.iocoder.yudao.userserver.modules.system.framework.sms;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.boot.context.properties.ConfigurationProperties;
  5 +import org.springframework.validation.annotation.Validated;
  6 +
  7 +import javax.validation.constraints.NotEmpty;
  8 +import javax.validation.constraints.NotNull;
  9 +import java.time.Duration;
  10 +import java.util.Collection;
  11 +
  12 +@ConfigurationProperties(prefix = "yudao.sms-code")
  13 +@Validated
  14 +@Data
  15 +public class SmsCodeProperties {
  16 +
  17 + /**
  18 + * 过期时间
  19 + */
  20 + @NotNull(message = "过期时间不能为空")
  21 + private Duration expireTimes;
  22 + /**
  23 + * 短信发送频率
  24 + */
  25 + @NotNull(message = "短信发送频率不能为空")
  26 + private Duration sendFrequency;
  27 + /**
  28 + * 每日发送最大数量
  29 + */
  30 + @NotNull(message = "每日发送最大数量不能为空")
  31 + private Integer sendMaximumQuantityPerDay;
  32 + /**
  33 + * 验证码最小值
  34 + */
  35 + @NotNull(message = "验证码最小值不能为空")
  36 + private Integer beginCode;
  37 + /**
  38 + * 验证码最大值
  39 + */
  40 + @NotNull(message = "验证码最大值不能为空")
  41 + private Integer endCode;
  42 +
  43 +}
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/sms/SysSmsCodeService.java 0 → 100644
  1 +package cn.iocoder.yudao.userserver.modules.system.service.sms;
  2 +
  3 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
  4 +import cn.iocoder.yudao.framework.common.validation.Mobile;
  5 +import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
  6 +
  7 +/**
  8 + * 短信验证码 Service 接口
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public interface SysSmsCodeService {
  13 +
  14 + /**
  15 + * 创建短信验证码,并进行发送
  16 + *
  17 + * @param mobile 手机号
  18 + * @param scene 发送场景 {@link SysSmsSceneEnum}
  19 + * @param createIp 发送 IP
  20 + */
  21 + void sendSmsCode(@Mobile String mobile, Integer scene, String createIp);
  22 +
  23 + /**
  24 + * 验证短信验证码,并进行使用
  25 + * 如果正确,则将验证码标记成已使用
  26 + * 如果错误,则抛出 {@link ServiceException} 异常
  27 + *
  28 + * @param mobile 手机号
  29 + * @param scene 发送场景
  30 + * @param code 验证码
  31 + * @param usedIp 使用 IP
  32 + */
  33 + void useSmsCode(@Mobile String mobile, Integer scene, String code, String usedIp);
  34 +
  35 +}
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/sms/impl/SysSmsCodeServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.userserver.modules.system.service.sms.impl;
  2 +
  3 +import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO;
  4 +import cn.iocoder.yudao.userserver.modules.system.dal.mysql.sms.SysSmsCodeMapper;
  5 +import cn.iocoder.yudao.userserver.modules.system.framework.sms.SmsCodeProperties;
  6 +import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
  7 +import org.springframework.stereotype.Service;
  8 +import org.springframework.validation.annotation.Validated;
  9 +
  10 +import javax.annotation.Resource;
  11 +import java.util.Date;
  12 +
  13 +import static cn.hutool.core.util.RandomUtil.randomInt;
  14 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  15 +import static cn.iocoder.yudao.userserver.modules.system.enums.SysErrorCodeConstants.*;
  16 +
  17 +/**
  18 + * 短信验证码 Service 实现类
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +@Service
  23 +@Validated
  24 +public class SysSmsCodeServiceImpl implements SysSmsCodeService {
  25 +
  26 + @Resource
  27 + private SmsCodeProperties smsCodeProperties;
  28 +
  29 + @Resource
  30 + private SysSmsCodeMapper smsCodeMapper;
  31 +
  32 + @Override
  33 + public void sendSmsCode(String mobile, Integer scene, String createIp) {
  34 + // 创建验证码
  35 + String code = this.createSmsCode(mobile, scene, createIp);
  36 + // 发送验证码
  37 + // TODO 芋艿:重要,发送短信验证码
  38 + }
  39 +
  40 + private String createSmsCode(String mobile, Integer scene, String ip) {
  41 + // 校验是否可以发送验证码,不用筛选场景
  42 + SysSmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null);
  43 + if (lastSmsCode != null) {
  44 + if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
  45 + throw exception(USER_SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
  46 + }
  47 + if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
  48 + < smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
  49 + throw exception(USER_SMS_CODE_SEND_TOO_FAST);
  50 + }
  51 + // TODO 芋艿:提升,每个 IP 每天可发送数量
  52 + // TODO 芋艿:提升,每个 IP 每小时可发送数量
  53 + }
  54 +
  55 + // 创建验证码记录
  56 + String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
  57 + SysSmsCodeDO newSmsCode = SysSmsCodeDO.builder().mobile(mobile).code(code)
  58 + .scene(scene).todayIndex(lastSmsCode != null ? lastSmsCode.getTodayIndex() + 1 : 1)
  59 + .createIp(ip).used(false).build();
  60 + smsCodeMapper.insert(newSmsCode);
  61 + return code;
  62 + }
  63 +
  64 + @Override
  65 + public void useSmsCode(String mobile, Integer scene, String code, String usedIp) {
  66 + // 校验验证码
  67 + SysSmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, scene);
  68 + if (lastSmsCode == null) { // 若验证码不存在,抛出异常
  69 + throw exception(USER_SMS_CODE_NOT_FOUND);
  70 + }
  71 + if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
  72 + >= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期
  73 + throw exception(USER_SMS_CODE_EXPIRED);
  74 + }
  75 + if (lastSmsCode.getUsed()) { // 验证码已使用
  76 + throw exception(USER_SMS_CODE_USED);
  77 + }
  78 + if (!lastSmsCode.getCode().equals(code)) {
  79 + throw exception(USER_SMS_CODE_NOT_CORRECT);
  80 + }
  81 +
  82 + // 使用验证码
  83 + smsCodeMapper.updateById(SysSmsCodeDO.builder().id(lastSmsCode.getId())
  84 + .used(true).usedTime(new Date()).usedIp(usedIp).build());
  85 + }
  86 +
  87 +}
yudao-user-server/src/main/resources/application.yaml
@@ -58,5 +58,13 @@ yudao: @@ -58,5 +58,13 @@ yudao:
58 db-schemas: ${spring.datasource.dynamic.datasource.master.name} 58 db-schemas: ${spring.datasource.dynamic.datasource.master.name}
59 error-code: # 错误码相关配置项 59 error-code: # 错误码相关配置项
60 constants-class-list: 60 constants-class-list:
  61 + - cn.iocoder.yudao.userserver.modules.member.enums.MbrErrorCodeConstants
  62 + - cn.iocoder.yudao.userserver.modules.system.enums.SysErrorCodeConstants
  63 + sms-code: # 短信验证码相关的配置项
  64 + expire-times: 10m
  65 + send-frequency: 1m
  66 + send-maximum-quantity-per-day: 10
  67 + begin-code: 9999 # 这里配置 9999 的原因是,测试方便。
  68 + end-code: 9999 # 这里配置 9999 的原因是,测试方便。
61 69
62 debug: false 70 debug: false
更新日志.md
@@ -10,13 +10,14 @@ @@ -10,13 +10,14 @@
10 ## [v1.1.1] 待定 10 ## [v1.1.1] 待定
11 11
12 * 支付 12 * 支付
  13 +* 用户前台的社交登陆
13 14
14 -## [v1.1.0] 待定 15 +## [v1.1.0] 进行中
15 16
16 * 新增管理后台的企业微信、钉钉等社交登录 17 * 新增管理后台的企业微信、钉钉等社交登录
17 * 新增用户前台(例如说,用户使用的小程序)的后端项目 `yudao-user-server` 18 * 新增用户前台(例如说,用户使用的小程序)的后端项目 `yudao-user-server`
18 * 新增公共服务 `yudao-core-service` 项目,通过 Jar 包的方式,提供 `yudao-user-server` 和 `yudao-admin-server` 的共享逻辑的复用。 19 * 新增公共服务 `yudao-core-service` 项目,通过 Jar 包的方式,提供 `yudao-user-server` 和 `yudao-admin-server` 的共享逻辑的复用。
19 -* 新增用户前台的手机登录、验证码登录、TODO 20 +* 新增用户前台的手机登录、验证码登录
20 * 修复管理后台的用户头像上传 404 的问题 21 * 修复管理后台的用户头像上传 404 的问题
21 22
22 ## [v1.0.0] 2021.05.03 23 ## [v1.0.0] 2021.05.03