mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-01 02:38:43 +08:00 
			
		
		
		
	增加短信验证码的功能
This commit is contained in:
		| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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") | ||||
|   | ||||
| @@ -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; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1 @@ | ||||
| package cn.iocoder.yudao.userserver.modules.system.dal.dataobject; | ||||
| @@ -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; | ||||
|     /** | ||||
| @@ -0,0 +1 @@ | ||||
| package cn.iocoder.yudao.userserver.modules.system.dal.mysql; | ||||
| @@ -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")); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1 @@ | ||||
| package cn.iocoder.yudao.userserver.modules.system.dal.redis; | ||||
| @@ -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, "短信发送过于频率"); | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
| @@ -0,0 +1,6 @@ | ||||
| /** | ||||
|  * 属于 system 模块的 framework 封装 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| package cn.iocoder.yudao.userserver.modules.system.framework; | ||||
| @@ -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 { | ||||
| } | ||||
| @@ -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; | ||||
|  | ||||
| } | ||||
| @@ -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); | ||||
|  | ||||
| } | ||||
| @@ -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()); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										5
									
								
								更新日志.md
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								更新日志.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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV