From 71b9104a13fddcd03d37e49c76b9e42f5c3db379 Mon Sep 17 00:00:00 2001
From: YunaiV <zhijiantianya@gmail.com>
Date: Sun, 10 Oct 2021 21:57:18 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=9F=AD=E4=BF=A1=E9=AA=8C?=
 =?UTF-8?q?=E8=AF=81=E7=A0=81=E7=9A=84=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../controller/auth/SysAuthController.http    |  9 ++
 .../controller/auth/SysAuthController.java    |  9 +-
 .../auth/vo/MbrAuthSendSmsReqVO.java          |  3 +
 .../system/dal/dataobject/package-info.java   |  1 +
 .../dal/dataobject/sms/SysSmsCodeDO.java}     | 15 ++--
 .../system/dal/mysql/package-info.java        |  1 +
 .../dal/mysql/sms/SysSmsCodeMapper.java       | 26 ++++++
 .../system/dal/redis/package-info.java        |  1 +
 .../system/enums/SysErrorCodeConstants.java   |  7 ++
 .../enums/sms/SysSmsSceneEnum.java}           |  6 +-
 .../system/framework/package-info.java        |  6 ++
 .../framework/sms/SmsCodeConfiguration.java   |  9 ++
 .../framework/sms/SmsCodeProperties.java      | 43 +++++++++
 .../system/service/sms/SysSmsCodeService.java | 35 ++++++++
 .../sms/impl/SysSmsCodeServiceImpl.java       | 87 +++++++++++++++++++
 .../src/main/resources/application.yaml       |  8 ++
 更新日志.md                                   |  5 +-
 17 files changed, 255 insertions(+), 16 deletions(-)
 create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/package-info.java
 rename yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/{member/dal/mysql/sms/MbrSmsCodeDO.java => system/dal/dataobject/sms/SysSmsCodeDO.java} (78%)
 create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/package-info.java
 create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/sms/SysSmsCodeMapper.java
 create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/redis/package-info.java
 rename yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/{member/enums/sms/MbrSmsSceneEnum.java => system/enums/sms/SysSmsSceneEnum.java} (77%)
 create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/package-info.java
 create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/sms/SmsCodeConfiguration.java
 create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/sms/SmsCodeProperties.java
 create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/sms/SysSmsCodeService.java
 create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/sms/impl/SysSmsCodeServiceImpl.java

diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.http b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.http
index 33c20cf2b..af42933fb 100644
--- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.http
+++ b/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
+}
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.java
index 89b6b65f5..18058bdd3 100644
--- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/SysAuthController.java
+++ b/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")
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/vo/MbrAuthSendSmsReqVO.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/vo/MbrAuthSendSmsReqVO.java
index afda3aa65..6ce41ca22 100644
--- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/vo/MbrAuthSendSmsReqVO.java
+++ b/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;
 
 }
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/package-info.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/package-info.java
new file mode 100644
index 000000000..0c99dcc95
--- /dev/null
+++ b/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;
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/dal/mysql/sms/MbrSmsCodeDO.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/sms/SysSmsCodeDO.java
similarity index 78%
rename from yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/dal/mysql/sms/MbrSmsCodeDO.java
rename to yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/sms/SysSmsCodeDO.java
index 775d34e2a..8cce25f42 100644
--- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/dal/mysql/sms/MbrSmsCodeDO.java
+++ b/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;
     /**
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/package-info.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/package-info.java
new file mode 100644
index 000000000..a1bdeadcd
--- /dev/null
+++ b/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;
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/sms/SysSmsCodeMapper.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/sms/SysSmsCodeMapper.java
new file mode 100644
index 000000000..6ef96ca1f
--- /dev/null
+++ b/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"));
+    }
+
+}
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/redis/package-info.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/redis/package-info.java
new file mode 100644
index 000000000..bb5083518
--- /dev/null
+++ b/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;
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/SysErrorCodeConstants.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/SysErrorCodeConstants.java
index b62525cf1..3abcab559 100644
--- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/SysErrorCodeConstants.java
+++ b/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, "短信发送过于频率");
 }
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/enums/sms/MbrSmsSceneEnum.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/sms/SysSmsSceneEnum.java
similarity index 77%
rename from yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/enums/sms/MbrSmsSceneEnum.java
rename to yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/sms/SysSmsSceneEnum.java
index 246e5df19..6f5ce3daa 100644
--- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/enums/sms/MbrSmsSceneEnum.java
+++ b/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;
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/package-info.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/package-info.java
new file mode 100644
index 000000000..cb0def51f
--- /dev/null
+++ b/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;
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/sms/SmsCodeConfiguration.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/sms/SmsCodeConfiguration.java
new file mode 100644
index 000000000..a4510ea47
--- /dev/null
+++ b/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 {
+}
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/sms/SmsCodeProperties.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/framework/sms/SmsCodeProperties.java
new file mode 100644
index 000000000..b600c28dc
--- /dev/null
+++ b/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;
+
+}
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/sms/SysSmsCodeService.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/sms/SysSmsCodeService.java
new file mode 100644
index 000000000..5ee81b67c
--- /dev/null
+++ b/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);
+
+}
diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/sms/impl/SysSmsCodeServiceImpl.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/sms/impl/SysSmsCodeServiceImpl.java
new file mode 100644
index 000000000..de81048b1
--- /dev/null
+++ b/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());
+    }
+
+}
diff --git a/yudao-user-server/src/main/resources/application.yaml b/yudao-user-server/src/main/resources/application.yaml
index cf9819408..4e1d1f279 100644
--- a/yudao-user-server/src/main/resources/application.yaml
+++ b/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
diff --git a/更新日志.md b/更新日志.md
index 9dd7c2ac3..bd525a068 100644
--- a/更新日志.md
+++ b/更新日志.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