Преглед изворни кода

增加短信验证码的功能

YunaiV пре 3 година
родитељ
комит
71b9104a13
17 измењених фајлова са 255 додато и 16 уклоњено
  1. 9 0
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.http
  2. 5 4
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.java
  3. 3 0
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/vo/MbrAuthSendSmsReqVO.java
  4. 1 0
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/package-info.java
  5. 8 7
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/sms/SysSmsCodeDO.java
  6. 1 0
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/package-info.java
  7. 26 0
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/sms/SysSmsCodeMapper.java
  8. 1 0
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/redis/package-info.java
  9. 7 0
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/SysErrorCodeConstants.java
  10. 3 3
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/sms/SysSmsSceneEnum.java
  11. 6 0
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/package-info.java
  12. 9 0
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/sms/SmsCodeConfiguration.java
  13. 43 0
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/sms/SmsCodeProperties.java
  14. 35 0
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/sms/SysSmsCodeService.java
  15. 87 0
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/sms/impl/SysSmsCodeServiceImpl.java
  16. 8 0
      yudao-user-server/src/main/resources/application.yaml
  17. 3 2
      更新日志.md

+ 9 - 0
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.http

@@ -6,3 +6,12 @@ Content-Type: application/json
   "mobile": "15601691300",
   "password": "admin123"
 }
+
+### 请求 /send-sms-code 接口 => 成功
+POST {{userServerUrl}}/send-sms-code
+Content-Type: application/json
+
+{
+  "mobile": "15601691300",
+  "scene": 1
+}

+ 5 - 4
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;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.*;
 import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService;
+import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiImplicitParams;
@@ -27,6 +28,8 @@ public class SysAuthController {
 
     @Resource
     private SysAuthService authService;
+    @Resource
+    private SysSmsCodeService smsCodeService;
 
     @PostMapping("/login")
     @ApiOperation("使用手机 + 密码登录")
@@ -45,10 +48,8 @@ public class SysAuthController {
     @PostMapping("/send-sms-code")
     @ApiOperation("发送手机验证码")
     public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid MbrAuthSendSmsReqVO reqVO) {
-//        passportManager.sendSmsCode(sendSmsCodeDTO, HttpUtil.getIp(request));
-//        // 返回成功
-//        return success(true);
-        return null;
+        smsCodeService.sendSmsCode(reqVO.getMobile(), reqVO.getScene(), getClientIP());
+        return success(true);
     }
 
     @PostMapping("/reset-password")

+ 3 - 0
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/vo/MbrAuthSendSmsReqVO.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo;
 
+import cn.iocoder.yudao.framework.common.validation.InEnum;
 import cn.iocoder.yudao.framework.common.validation.Mobile;
+import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
@@ -19,6 +21,7 @@ public class MbrAuthSendSmsReqVO {
 
     @ApiModelProperty(value = "发送场景", example = "1", notes = "对应 MbrSmsSceneEnum 枚举")
     @NotNull(message = "发送场景不能为空")
+    @InEnum(SysSmsSceneEnum.class)
     private Integer scene;
 
 }

+ 1 - 0
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.userserver.modules.system.dal.dataobject;

+ 8 - 7
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/dal/mysql/sms/MbrSmsCodeDO.java → yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/sms/SysSmsCodeDO.java

@@ -1,9 +1,8 @@
-package cn.iocoder.yudao.userserver.modules.member.dal.mysql.sms;
+package cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
+import lombok.*;
 import lombok.experimental.Accessors;
 
 import java.util.Date;
@@ -15,11 +14,13 @@ import java.util.Date;
  *
  * @author 芋道源码
  */
-@TableName("mbr_sms_code")
+@TableName("sys_sms_code")
 @Data
 @EqualsAndHashCode(callSuper = true)
-@Accessors(chain = true)
-public class MbrSmsCodeDO extends BaseDO {
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class SysSmsCodeDO extends BaseDO {
 
     /**
      * 编号
@@ -36,7 +37,7 @@ public class MbrSmsCodeDO extends BaseDO {
     /**
      * 发送场景
      *
-     * 枚举 {@link MbrSmsCodeDO}
+     * 枚举 {@link SysSmsCodeDO}
      */
     private Integer scene;
     /**

+ 1 - 0
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.userserver.modules.system.dal.mysql;

+ 26 - 0
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/sms/SysSmsCodeMapper.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.userserver.modules.system.dal.mysql.sms;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
+import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface SysSmsCodeMapper extends BaseMapperX<SysSmsCodeDO> {
+
+    /**
+     * 获得手机号的最后一个手机验证码
+     *
+     * @param mobile 手机号
+     * @param scene 发送场景,选填
+     * @return 手机验证码
+     */
+    default SysSmsCodeDO selectLastByMobile(String mobile, Integer scene) {
+        return selectOne(new QueryWrapperX<SysSmsCodeDO>()
+                .eq("mobile", mobile)
+                .eqIfPresent("scene", scene)
+                .orderByDesc("id")
+                .last("LIMIT 1"));
+    }
+
+}

+ 1 - 0
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/redis/package-info.java

@@ -0,0 +1 @@
+package cn.iocoder.yudao.userserver.modules.system.dal.redis;

+ 7 - 0
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/SysErrorCodeConstants.java

@@ -14,4 +14,11 @@ public interface SysErrorCodeConstants {
     ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1005000001, "登录失败,账号被禁用");
     ErrorCode AUTH_LOGIN_FAIL_UNKNOWN = new ErrorCode(1005000002, "登录失败"); // 登录失败的兜底,未知原因
 
+    // ========== SMS CODE 模块 1005001000 ==========
+    ErrorCode USER_SMS_CODE_NOT_FOUND = new ErrorCode(1005001000, "验证码不存在");
+    ErrorCode USER_SMS_CODE_EXPIRED = new ErrorCode(1005001001, "验证码已过期");
+    ErrorCode USER_SMS_CODE_USED = new ErrorCode(1005001002, "验证码已使用");
+    ErrorCode USER_SMS_CODE_NOT_CORRECT = new ErrorCode(1005001003, "验证码不正确");
+    ErrorCode USER_SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1005001004, "超过每日短信发送数量");
+    ErrorCode USER_SMS_CODE_SEND_TOO_FAST = new ErrorCode(1005001005, "短信发送过于频率");
 }

+ 3 - 3
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/enums/sms/MbrSmsSceneEnum.java → yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/sms/SysSmsSceneEnum.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.userserver.modules.member.enums.sms;
+package cn.iocoder.yudao.userserver.modules.system.enums.sms;
 
 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
 import lombok.AllArgsConstructor;
@@ -13,13 +13,13 @@ import java.util.Arrays;
  */
 @Getter
 @AllArgsConstructor
-public enum MbrSmsSceneEnum implements IntArrayValuable {
+public enum SysSmsSceneEnum implements IntArrayValuable {
 
     LOGIN_BY_SMS(1, "手机号登陆"),
     CHANGE_MOBILE_BY_SMS(2, "更换手机号"),
             ;
 
-    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MbrSmsSceneEnum::getScene).toArray();
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SysSmsSceneEnum::getScene).toArray();
 
     private final Integer scene;
     private final String name;

+ 6 - 0
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * 属于 system 模块的 framework 封装
+ *
+ * @author 芋道源码
+ */
+package cn.iocoder.yudao.userserver.modules.system.framework;

+ 9 - 0
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/sms/SmsCodeConfiguration.java

@@ -0,0 +1,9 @@
+package cn.iocoder.yudao.userserver.modules.system.framework.sms;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableConfigurationProperties(SmsCodeProperties.class)
+public class SmsCodeConfiguration {
+}

+ 43 - 0
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/sms/SmsCodeProperties.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.userserver.modules.system.framework.sms;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.validation.annotation.Validated;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.time.Duration;
+import java.util.Collection;
+
+@ConfigurationProperties(prefix = "yudao.sms-code")
+@Validated
+@Data
+public class SmsCodeProperties {
+
+    /**
+     * 过期时间
+     */
+    @NotNull(message = "过期时间不能为空")
+    private Duration expireTimes;
+    /**
+     * 短信发送频率
+     */
+    @NotNull(message = "短信发送频率不能为空")
+    private Duration sendFrequency;
+    /**
+     * 每日发送最大数量
+     */
+    @NotNull(message = "每日发送最大数量不能为空")
+    private Integer sendMaximumQuantityPerDay;
+    /**
+     * 验证码最小值
+     */
+    @NotNull(message = "验证码最小值不能为空")
+    private Integer beginCode;
+    /**
+     * 验证码最大值
+     */
+    @NotNull(message = "验证码最大值不能为空")
+    private Integer endCode;
+
+}

+ 35 - 0
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/sms/SysSmsCodeService.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.userserver.modules.system.service.sms;
+
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.common.validation.Mobile;
+import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum;
+
+/**
+ * 短信验证码 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface SysSmsCodeService {
+
+    /**
+     * 创建短信验证码,并进行发送
+     *
+     * @param mobile 手机号
+     * @param scene 发送场景 {@link SysSmsSceneEnum}
+     * @param createIp 发送 IP
+     */
+    void sendSmsCode(@Mobile String mobile, Integer scene, String createIp);
+
+    /**
+     * 验证短信验证码,并进行使用
+     * 如果正确,则将验证码标记成已使用
+     * 如果错误,则抛出 {@link ServiceException} 异常
+     *
+     * @param mobile 手机号
+     * @param scene 发送场景
+     * @param code 验证码
+     * @param usedIp 使用 IP
+     */
+    void useSmsCode(@Mobile String mobile, Integer scene, String code, String usedIp);
+
+}

+ 87 - 0
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/sms/impl/SysSmsCodeServiceImpl.java

@@ -0,0 +1,87 @@
+package cn.iocoder.yudao.userserver.modules.system.service.sms.impl;
+
+import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO;
+import cn.iocoder.yudao.userserver.modules.system.dal.mysql.sms.SysSmsCodeMapper;
+import cn.iocoder.yudao.userserver.modules.system.framework.sms.SmsCodeProperties;
+import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Date;
+
+import static cn.hutool.core.util.RandomUtil.randomInt;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.userserver.modules.system.enums.SysErrorCodeConstants.*;
+
+/**
+ * 短信验证码 Service 实现类
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+public class SysSmsCodeServiceImpl implements SysSmsCodeService {
+
+    @Resource
+    private SmsCodeProperties smsCodeProperties;
+
+    @Resource
+    private SysSmsCodeMapper smsCodeMapper;
+
+    @Override
+    public void sendSmsCode(String mobile, Integer scene, String createIp) {
+        // 创建验证码
+        String code = this.createSmsCode(mobile, scene, createIp);
+        // 发送验证码
+        // TODO 芋艿:重要,发送短信验证码
+    }
+
+    private String createSmsCode(String mobile, Integer scene, String ip) {
+        // 校验是否可以发送验证码,不用筛选场景
+        SysSmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null);
+        if (lastSmsCode != null) {
+            if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。
+                throw exception(USER_SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);
+            }
+            if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
+                    < smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁
+                throw exception(USER_SMS_CODE_SEND_TOO_FAST);
+            }
+            // TODO 芋艿:提升,每个 IP 每天可发送数量
+            // TODO 芋艿:提升,每个 IP 每小时可发送数量
+        }
+
+        // 创建验证码记录
+        String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
+        SysSmsCodeDO newSmsCode = SysSmsCodeDO.builder().mobile(mobile).code(code)
+                .scene(scene).todayIndex(lastSmsCode != null ? lastSmsCode.getTodayIndex() + 1 : 1)
+                .createIp(ip).used(false).build();
+        smsCodeMapper.insert(newSmsCode);
+        return code;
+    }
+
+    @Override
+    public void useSmsCode(String mobile, Integer scene, String code, String usedIp) {
+        // 校验验证码
+        SysSmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, scene);
+        if (lastSmsCode == null) { // 若验证码不存在,抛出异常
+            throw exception(USER_SMS_CODE_NOT_FOUND);
+        }
+        if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime()
+                >= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期
+            throw exception(USER_SMS_CODE_EXPIRED);
+        }
+        if (lastSmsCode.getUsed()) { // 验证码已使用
+            throw exception(USER_SMS_CODE_USED);
+        }
+        if (!lastSmsCode.getCode().equals(code)) {
+            throw exception(USER_SMS_CODE_NOT_CORRECT);
+        }
+
+        // 使用验证码
+        smsCodeMapper.updateById(SysSmsCodeDO.builder().id(lastSmsCode.getId())
+                .used(true).usedTime(new Date()).usedIp(usedIp).build());
+    }
+
+}

+ 8 - 0
yudao-user-server/src/main/resources/application.yaml

@@ -58,5 +58,13 @@ yudao:
     db-schemas: ${spring.datasource.dynamic.datasource.master.name}
   error-code: # 错误码相关配置项
     constants-class-list:
+      - cn.iocoder.yudao.userserver.modules.member.enums.MbrErrorCodeConstants
+      - cn.iocoder.yudao.userserver.modules.system.enums.SysErrorCodeConstants
+  sms-code: # 短信验证码相关的配置项
+    expire-times: 10m
+    send-frequency: 1m
+    send-maximum-quantity-per-day: 10
+    begin-code: 9999 # 这里配置 9999 的原因是,测试方便。
+    end-code: 9999 # 这里配置 9999 的原因是,测试方便。
 
 debug: false

+ 3 - 2
更新日志.md

@@ -10,13 +10,14 @@
 ## [v1.1.1] 待定
 
 * 支付
+* 用户前台的社交登陆
 
-## [v1.1.0] 待定
+## [v1.1.0] 进行中
 
 * 新增管理后台的企业微信、钉钉等社交登录
 * 新增用户前台(例如说,用户使用的小程序)的后端项目 `yudao-user-server`
 * 新增公共服务 `yudao-core-service` 项目,通过 Jar 包的方式,提供 `yudao-user-server` 和 `yudao-admin-server` 的共享逻辑的复用。
-* 新增用户前台的手机登录、验证码登录、TODO 
+* 新增用户前台的手机登录、验证码登录 
 * 修复管理后台的用户头像上传 404 的问题
 
 ## [v1.0.0] 2021.05.03