mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 10:18:42 +08:00 
			
		
		
		
	优化重置手机逻辑
This commit is contained in:
		| @@ -90,15 +90,6 @@ | |||||||
|             <artifactId>yudao-spring-boot-starter-test</artifactId> |             <artifactId>yudao-spring-boot-starter-test</artifactId> | ||||||
|             <scope>test</scope> |             <scope>test</scope> | ||||||
|         </dependency> |         </dependency> | ||||||
|  |  | ||||||
|         <!-- TODO @宋天:junit 已经在 yudao-spring-boot-starter-test 啦,不用在引入哈 --> |  | ||||||
|         <!--单元测试相关--> |  | ||||||
|         <dependency> |  | ||||||
|             <groupId>junit</groupId> |  | ||||||
|             <artifactId>junit</artifactId> |  | ||||||
|             <scope>test</scope> |  | ||||||
|         </dependency> |  | ||||||
|  |  | ||||||
|         <!-- 工具类相关 --> |         <!-- 工具类相关 --> | ||||||
|  |  | ||||||
|     </dependencies> |     </dependencies> | ||||||
|   | |||||||
| @@ -35,9 +35,6 @@ public class SysUserProfileController { | |||||||
|     @Resource |     @Resource | ||||||
|     private MbrUserService userService; |     private MbrUserService userService; | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private SysSmsCodeService smsCodeService; |  | ||||||
|  |  | ||||||
|     @PutMapping("/update-nickname") |     @PutMapping("/update-nickname") | ||||||
|     @ApiOperation("修改用户昵称") |     @ApiOperation("修改用户昵称") | ||||||
|     @PreAuthenticated |     @PreAuthenticated | ||||||
| @@ -68,10 +65,6 @@ public class SysUserProfileController { | |||||||
|     @ApiOperation(value = "修改用户手机") |     @ApiOperation(value = "修改用户手机") | ||||||
|     @PreAuthenticated |     @PreAuthenticated | ||||||
|     public CommonResult<Boolean> updateMobile(@RequestBody @Valid MbrUserUpdateMobileReqVO reqVO) { |     public CommonResult<Boolean> updateMobile(@RequestBody @Valid MbrUserUpdateMobileReqVO reqVO) { | ||||||
|         // 校验验证码 |  | ||||||
|         // TODO @宋天:统一到 userService.updateMobile 方法里 |  | ||||||
|         smsCodeService.useSmsCode(reqVO.getMobile(),SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene(), reqVO.getCode(),getClientIP()); |  | ||||||
|  |  | ||||||
|         userService.updateMobile(getLoginUserId(), reqVO); |         userService.updateMobile(getLoginUserId(), reqVO); | ||||||
|         return success(true); |         return success(true); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| package cn.iocoder.yudao.userserver.modules.member.controller.user.vo; | package cn.iocoder.yudao.userserver.modules.member.controller.user.vo; | ||||||
|  |  | ||||||
|  | import cn.iocoder.yudao.framework.common.validation.Mobile; | ||||||
| import io.swagger.annotations.ApiModel; | import io.swagger.annotations.ApiModel; | ||||||
| import io.swagger.annotations.ApiModelProperty; | import io.swagger.annotations.ApiModelProperty; | ||||||
| import lombok.AllArgsConstructor; | import lombok.AllArgsConstructor; | ||||||
| @@ -27,9 +28,21 @@ public class MbrUserUpdateMobileReqVO { | |||||||
|  |  | ||||||
|     @ApiModelProperty(value = "手机号",required = true,example = "15823654487") |     @ApiModelProperty(value = "手机号",required = true,example = "15823654487") | ||||||
|     @NotBlank(message = "手机号不能为空") |     @NotBlank(message = "手机号不能为空") | ||||||
|     // TODO @宋天:手机校验,直接使用 @Mobile 哈 |  | ||||||
|     @Length(min = 8, max = 11, message = "手机号码长度为 8-11 位") |     @Length(min = 8, max = 11, message = "手机号码长度为 8-11 位") | ||||||
|     @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式错误") |     @Mobile | ||||||
|     private String mobile; |     private String mobile; | ||||||
|  |  | ||||||
|  |     @ApiModelProperty(value = "原手机验证码", required = true, example = "1024") | ||||||
|  |     @NotEmpty(message = "原手机验证码不能为空") | ||||||
|  |     @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位") | ||||||
|  |     @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") | ||||||
|  |     private String oldCode; | ||||||
|  |  | ||||||
|  |     @ApiModelProperty(value = "原手机号",required = true,example = "15823654487") | ||||||
|  |     @NotBlank(message = "手机号不能为空") | ||||||
|  |     @Length(min = 8, max = 11, message = "手机号码长度为 8-11 位") | ||||||
|  |     @Mobile | ||||||
|  |     private String oldMobile; | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,7 +9,10 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |||||||
| import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserUpdateMobileReqVO; | import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserUpdateMobileReqVO; | ||||||
| import cn.iocoder.yudao.userserver.modules.member.dal.mysql.user.MbrUserMapper; | import cn.iocoder.yudao.userserver.modules.member.dal.mysql.user.MbrUserMapper; | ||||||
| import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService; | import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService; | ||||||
|  | import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO; | ||||||
|  | import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum; | ||||||
| import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService; | import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService; | ||||||
|  | import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService; | ||||||
| import com.google.common.annotations.VisibleForTesting; | import com.google.common.annotations.VisibleForTesting; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
| import org.springframework.security.crypto.password.PasswordEncoder; | import org.springframework.security.crypto.password.PasswordEncoder; | ||||||
| @@ -21,7 +24,10 @@ import java.io.InputStream; | |||||||
| import java.util.Date; | import java.util.Date; | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||||
|  | import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; | ||||||
| import static cn.iocoder.yudao.userserver.modules.member.enums.MbrErrorCodeConstants.USER_NOT_EXISTS; | import static cn.iocoder.yudao.userserver.modules.member.enums.MbrErrorCodeConstants.USER_NOT_EXISTS; | ||||||
|  | import static cn.iocoder.yudao.userserver.modules.system.enums.SysErrorCodeConstants.USER_SMS_CODE_IS_UNUSED; | ||||||
|  | import static cn.iocoder.yudao.userserver.modules.system.enums.SysErrorCodeConstants.USER_SMS_CODE_NOT_CORRECT; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * User Service 实现类 |  * User Service 实现类 | ||||||
| @@ -45,6 +51,10 @@ public class MbrUserServiceImpl implements MbrUserService { | |||||||
|     @Resource |     @Resource | ||||||
|     private SysAuthService sysAuthService; |     private SysAuthService sysAuthService; | ||||||
|  |  | ||||||
|  |     @Resource | ||||||
|  |     private SysSmsCodeService smsCodeService; | ||||||
|  |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public MbrUserDO getUserByMobile(String mobile) { |     public MbrUserDO getUserByMobile(String mobile) { | ||||||
|         return userMapper.selectByMobile(mobile); |         return userMapper.selectByMobile(mobile); | ||||||
| @@ -124,12 +134,21 @@ public class MbrUserServiceImpl implements MbrUserService { | |||||||
|     @Override |     @Override | ||||||
|     public void updateMobile(Long userId, MbrUserUpdateMobileReqVO reqVO) { |     public void updateMobile(Long userId, MbrUserUpdateMobileReqVO reqVO) { | ||||||
|         // 检测用户是否存在 |         // 检测用户是否存在 | ||||||
|         MbrUserDO userDO = checkUserExists(userId); |         checkUserExists(userId); | ||||||
|         // 检测手机与验证码是否匹配 |         // 校验验证码,并标记为已使用 | ||||||
|         // TODO @宋天:修改手机的时候。应该要校验,老手机 + 老手机 code;新手机 + 新手机 code |         smsCodeService.useSmsCode(reqVO.getMobile(), SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene(), reqVO.getCode(),getClientIP()); | ||||||
|         sysAuthService.checkIfMobileMatchCodeAndDeleteCode(userDO.getMobile(),reqVO.getCode()); |  | ||||||
|  |         // 检测新手机和旧手机的验证码是否在30分钟内 | ||||||
|  |         SysSmsCodeDO smsOldCodeDO = smsCodeService.checkCodeIsExpired(reqVO.getOldMobile(), reqVO.getOldCode(), SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene()); | ||||||
|  |         SysSmsCodeDO smsNewCodeDO = smsCodeService.checkCodeIsExpired(reqVO.getMobile(), reqVO.getCode(), SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene()); | ||||||
|  |  | ||||||
|  |         // 判断新旧code是否未被使用,如果是,抛出异常 | ||||||
|  |         if (Boolean.FALSE.equals(smsOldCodeDO.getUsed()) || Boolean.FALSE.equals(smsNewCodeDO.getUsed())){ | ||||||
|  |             throw exception(USER_SMS_CODE_IS_UNUSED); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // 更新用户手机 |         // 更新用户手机 | ||||||
|         // TODO @宋天:更新的时候,单独创建对象。直接全量更新,会可能导致属性覆盖。可以看看打印出来的 SQL 哈 |         MbrUserDO userDO = MbrUserDO.builder().build(); | ||||||
|         userDO.setMobile(reqVO.getMobile()); |         userDO.setMobile(reqVO.getMobile()); | ||||||
|         userMapper.updateById(userDO); |         userMapper.updateById(userDO); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -93,14 +93,6 @@ public class SysAuthController { | |||||||
|         return success(true); |         return success(true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @PostMapping("/check-sms-code") |  | ||||||
|     @ApiOperation(value = "校验验证码是否正确") |  | ||||||
|     @PreAuthenticated |  | ||||||
|     public CommonResult<Boolean> checkSmsCode(@RequestBody @Valid SysAuthSmsLoginReqVO reqVO) { |  | ||||||
|         // TODO @宋天:check 的时候,不应该使用 useSmsCode 哈,这样验证码就直接被使用了。另外,check 开头的方法,更多是校验的逻辑,不会有 update 数据的动作。这点,在方法命名上,也是要注意的 |  | ||||||
|         smsCodeService.useSmsCode(reqVO.getMobile(),SysSmsSceneEnum.CHECK_CODE_BY_SMS.getScene(),reqVO.getCode(),getClientIP()); |  | ||||||
|         return success(true); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // ========== 社交登录相关 ========== |     // ========== 社交登录相关 ========== | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo; | package cn.iocoder.yudao.userserver.modules.system.controller.auth.vo; | ||||||
|  |  | ||||||
|  | import cn.iocoder.yudao.framework.common.validation.Mobile; | ||||||
| import io.swagger.annotations.ApiModel; | import io.swagger.annotations.ApiModel; | ||||||
| import io.swagger.annotations.ApiModelProperty; | import io.swagger.annotations.ApiModelProperty; | ||||||
| import lombok.AllArgsConstructor; | import lombok.AllArgsConstructor; | ||||||
| @@ -8,6 +9,7 @@ import lombok.Data; | |||||||
| import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||||
| import org.hibernate.validator.constraints.Length; | import org.hibernate.validator.constraints.Length; | ||||||
|  |  | ||||||
|  | import javax.validation.constraints.NotBlank; | ||||||
| import javax.validation.constraints.NotEmpty; | import javax.validation.constraints.NotEmpty; | ||||||
| import javax.validation.constraints.Pattern; | import javax.validation.constraints.Pattern; | ||||||
|  |  | ||||||
| @@ -29,4 +31,8 @@ public class MbrAuthResetPasswordReqVO { | |||||||
|     @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") |     @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") | ||||||
|     private String code; |     private String code; | ||||||
|  |  | ||||||
|  |     @ApiModelProperty(value = "手机号",required = true,example = "15878962356") | ||||||
|  |     @NotBlank(message = "手机号不能为空") | ||||||
|  |     @Mobile | ||||||
|  |     private String mobile; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | 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.AllArgsConstructor; | ||||||
|  | import lombok.Builder; | ||||||
|  | import lombok.Data; | ||||||
|  | import lombok.NoArgsConstructor; | ||||||
|  | import org.hibernate.validator.constraints.Length; | ||||||
|  |  | ||||||
|  | import javax.validation.constraints.NotBlank; | ||||||
|  | import javax.validation.constraints.NotNull; | ||||||
|  | import javax.validation.constraints.Pattern; | ||||||
|  |  | ||||||
|  | @ApiModel("校验验证码 Request VO") | ||||||
|  | @Data | ||||||
|  | @NoArgsConstructor | ||||||
|  | @AllArgsConstructor | ||||||
|  | @Builder | ||||||
|  | public class SysAuthCheckCodeReqVO { | ||||||
|  |  | ||||||
|  |     @ApiModelProperty(value = "手机号", example = "15601691234") | ||||||
|  |     @NotBlank(message = "手机号不能为空") | ||||||
|  |     @Mobile | ||||||
|  |     private String mobile; | ||||||
|  |  | ||||||
|  |     @ApiModelProperty(value = "手机验证码", required = true, example = "1024") | ||||||
|  |     @NotBlank(message = "手机验证码不能为空") | ||||||
|  |     @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位") | ||||||
|  |     @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") | ||||||
|  |     private String code; | ||||||
|  |  | ||||||
|  |     @ApiModelProperty(value = "发送场景", example = "1", notes = "对应 MbrSmsSceneEnum 枚举") | ||||||
|  |     @NotNull(message = "发送场景不能为空") | ||||||
|  |     @InEnum(SysSmsSceneEnum.class) | ||||||
|  |     private Integer scene; | ||||||
|  | } | ||||||
| @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; | |||||||
| import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO; | import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO; | ||||||
| import org.apache.ibatis.annotations.Mapper; | import org.apache.ibatis.annotations.Mapper; | ||||||
|  |  | ||||||
|  |  | ||||||
| @Mapper | @Mapper | ||||||
| public interface SysSmsCodeMapper extends BaseMapperX<SysSmsCodeDO> { | public interface SysSmsCodeMapper extends BaseMapperX<SysSmsCodeDO> { | ||||||
|  |  | ||||||
| @@ -13,14 +14,15 @@ public interface SysSmsCodeMapper extends BaseMapperX<SysSmsCodeDO> { | |||||||
|      * |      * | ||||||
|      * @param mobile 手机号 |      * @param mobile 手机号 | ||||||
|      * @param scene 发送场景,选填 |      * @param scene 发送场景,选填 | ||||||
|  |      * @param code 验证码 选填 | ||||||
|      * @return 手机验证码 |      * @return 手机验证码 | ||||||
|      */ |      */ | ||||||
|     default SysSmsCodeDO selectLastByMobile(String mobile, Integer scene) { |     default SysSmsCodeDO selectLastByMobile(String mobile,String code,Integer scene) { | ||||||
|         return selectOne(new QueryWrapperX<SysSmsCodeDO>() |         return selectOne(new QueryWrapperX<SysSmsCodeDO>() | ||||||
|                 .eq("mobile", mobile) |                 .eq("mobile", mobile) | ||||||
|                 .eqIfPresent("scene", scene) |                 .eqIfPresent("scene", scene) | ||||||
|  |                 .eqIfPresent("code", code) | ||||||
|                 .orderByDesc("id") |                 .orderByDesc("id") | ||||||
|                 .last("LIMIT 1")); |                 .last("LIMIT 1")); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -24,9 +24,9 @@ public interface SysErrorCodeConstants { | |||||||
|     ErrorCode USER_SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1005001004, "超过每日短信发送数量"); |     ErrorCode USER_SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1005001004, "超过每日短信发送数量"); | ||||||
|     ErrorCode USER_SMS_CODE_SEND_TOO_FAST = new ErrorCode(1005001005, "短信发送过于频率"); |     ErrorCode USER_SMS_CODE_SEND_TOO_FAST = new ErrorCode(1005001005, "短信发送过于频率"); | ||||||
|     ErrorCode USER_SMS_CODE_IS_EXISTS = new ErrorCode(1005001006, "手机号已被使用"); |     ErrorCode USER_SMS_CODE_IS_EXISTS = new ErrorCode(1005001006, "手机号已被使用"); | ||||||
|  |     ErrorCode USER_SMS_CODE_IS_UNUSED = new ErrorCode(1005001006, "手机号未被使用"); | ||||||
|  |  | ||||||
|     // ========== 用户模块 1005002000 ========== |     // ========== 用户模块 1005002000 ========== | ||||||
|     ErrorCode USER_NOT_EXISTS = new ErrorCode(1005002001, "用户不存在"); |     ErrorCode USER_NOT_EXISTS = new ErrorCode(1005002001, "用户不存在"); | ||||||
|     ErrorCode USER_CODE_FAILED = new ErrorCode(1005002002, "验证码不匹配"); |  | ||||||
|     ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1005002003, "密码校验失败"); |     ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1005002003, "密码校验失败"); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -68,18 +68,11 @@ public interface SysAuthService extends SecurityAuthFrameworkService { | |||||||
|      * @param userId 用户id |      * @param userId 用户id | ||||||
|      * @param userReqVO 用户请求实体类 |      * @param userReqVO 用户请求实体类 | ||||||
|      */ |      */ | ||||||
|     void updatePassword(Long userId, @Valid MbrAuthUpdatePasswordReqVO userReqVO); |     void updatePassword(Long userId,MbrAuthUpdatePasswordReqVO userReqVO); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 忘记密码 |      * 忘记密码 | ||||||
|      * @param userReqVO 用户请求实体类 |      * @param userReqVO 用户请求实体类 | ||||||
|      */ |      */ | ||||||
|     void resetPassword(MbrAuthResetPasswordReqVO userReqVO); |     void resetPassword(MbrAuthResetPasswordReqVO userReqVO); | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 检测手机与验证码是否匹配 |  | ||||||
|      * @param phone 手机号 |  | ||||||
|      * @param code 验证码 |  | ||||||
|      */ |  | ||||||
|     void checkIfMobileMatchCodeAndDeleteCode(String phone,String code); |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -280,46 +280,30 @@ public class SysAuthServiceImpl implements SysAuthService { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void updatePassword(Long userId, @Valid MbrAuthUpdatePasswordReqVO reqVO) { |     public void updatePassword(Long userId,MbrAuthUpdatePasswordReqVO reqVO) { | ||||||
|         // 检验旧密码 |         // 检验旧密码 | ||||||
|         MbrUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword()); |         MbrUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword()); | ||||||
|  |  | ||||||
|         // 更新用户密码 |         // 更新用户密码 | ||||||
|         // TODO @宋天:不要更新整个对象哈 |         MbrUserDO mbrUserDO = MbrUserDO.builder().build(); | ||||||
|         userDO.setPassword(passwordEncoder.encode(reqVO.getPassword())); |         mbrUserDO.setId(userDO.getId()); | ||||||
|         userMapper.updateById(userDO); |         mbrUserDO.setPassword(passwordEncoder.encode(reqVO.getPassword())); | ||||||
|  |         userMapper.updateById(mbrUserDO); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void resetPassword(MbrAuthResetPasswordReqVO reqVO) { |     public void resetPassword(MbrAuthResetPasswordReqVO reqVO) { | ||||||
|         // 根据验证码取出手机号,并查询用户 |         // 检验用户是否存在 | ||||||
|         String mobile = stringRedisTemplate.opsForValue().get(reqVO.getCode()); |         MbrUserDO userDO = checkUserIfExists(reqVO.getMobile()); | ||||||
|         MbrUserDO userDO = userMapper.selectByMobile(mobile); |  | ||||||
|         if (userDO == null){ |  | ||||||
|             throw exception(USER_NOT_EXISTS); |  | ||||||
|         } |  | ||||||
|         // TODO @芋艿 这一步没必要检验验证码与手机是否匹配,因为是根据验证码去redis中查找手机号,然后根据手机号查询用户 |  | ||||||
|         //  也就是说 即便黑客以其他方式将验证码发送到自己手机上,最终还是会根据手机号查询用户然后进行重置密码的操作,不存在安全问题 |  | ||||||
|  |  | ||||||
|         // TODO @宋天:这块微信在讨论下哈~~~ |         // 使用验证码 | ||||||
|  |         smsCodeService.useSmsCode(reqVO.getMobile(),SysSmsSceneEnum.FORGET_MOBILE_BY_SMS.getScene(),reqVO.getCode(),getClientIP()); | ||||||
|         // 校验验证码 |  | ||||||
|         smsCodeService.useSmsCode(userDO.getMobile(), SysSmsSceneEnum.FORGET_MOBILE_BY_SMS.getScene(), reqVO.getCode(),getClientIP()); |  | ||||||
|  |  | ||||||
|         // 更新密码 |         // 更新密码 | ||||||
|         userDO.setPassword(passwordEncoder.encode(reqVO.getPassword())); |         MbrUserDO mbrUserDO = MbrUserDO.builder().build(); | ||||||
|         userMapper.updateById(userDO); |         mbrUserDO.setId(userDO.getId()); | ||||||
|     } |         mbrUserDO.setPassword(passwordEncoder.encode(reqVO.getPassword())); | ||||||
|  |         userMapper.updateById(mbrUserDO); | ||||||
|     @Override |  | ||||||
|     public void checkIfMobileMatchCodeAndDeleteCode(String phone, String code) { |  | ||||||
|         // 检验用户手机与验证码是否匹配 |  | ||||||
|         String mobile = stringRedisTemplate.opsForValue().get(code); |  | ||||||
|         if (!phone.equals(mobile)){ |  | ||||||
|             throw exception(USER_CODE_FAILED); |  | ||||||
|         } |  | ||||||
|         // 销毁redis中此验证码 |  | ||||||
|         stringRedisTemplate.delete(code); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -342,6 +326,15 @@ public class SysAuthServiceImpl implements SysAuthService { | |||||||
|         return user; |         return user; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public MbrUserDO checkUserIfExists(String mobile) { | ||||||
|  |         MbrUserDO user = userMapper.selectByMobile(mobile); | ||||||
|  |         if (user == null) { | ||||||
|  |             throw exception(USER_NOT_EXISTS); | ||||||
|  |         } | ||||||
|  |         return user; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     private void createLogoutLog(Long userId, String username) { |     private void createLogoutLog(Long userId, String username) { | ||||||
|         SysLoginLogCreateReqDTO reqDTO = new SysLoginLogCreateReqDTO(); |         SysLoginLogCreateReqDTO reqDTO = new SysLoginLogCreateReqDTO(); | ||||||
|         reqDTO.setLogType(SysLoginLogTypeEnum.LOGOUT_SELF.getType()); |         reqDTO.setLogType(SysLoginLogTypeEnum.LOGOUT_SELF.getType()); | ||||||
|   | |||||||
| @@ -2,7 +2,10 @@ package cn.iocoder.yudao.userserver.modules.system.service.sms; | |||||||
|  |  | ||||||
| import cn.iocoder.yudao.framework.common.exception.ServiceException; | import cn.iocoder.yudao.framework.common.exception.ServiceException; | ||||||
| import cn.iocoder.yudao.framework.common.validation.Mobile; | import cn.iocoder.yudao.framework.common.validation.Mobile; | ||||||
|  | import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthCheckCodeReqVO; | ||||||
| import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthSendSmsReqVO; | import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthSendSmsReqVO; | ||||||
|  | import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthSmsLoginReqVO; | ||||||
|  | import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO; | ||||||
| import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum; | import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -44,4 +47,14 @@ public interface SysSmsCodeService { | |||||||
|      * @param userId 用户id |      * @param userId 用户id | ||||||
|      */ |      */ | ||||||
|     void sendSmsCodeLogin(Long userId); |     void sendSmsCodeLogin(Long userId); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 检测手机验证码是否有效 | ||||||
|  |      * @param mobile 手机号 | ||||||
|  |      * @param code 验证码 | ||||||
|  |      * @param scene 发送场景 {@link SysSmsSceneEnum} | ||||||
|  |      * @return SysSmsCodeDO 手机验证码 | ||||||
|  |      */ | ||||||
|  |     SysSmsCodeDO checkCodeIsExpired(String mobile, String code, Integer scene); | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import cn.hutool.core.map.MapUtil; | |||||||
| import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO; | import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO; | ||||||
| import cn.iocoder.yudao.coreservice.modules.system.service.sms.SysSmsCoreService; | import cn.iocoder.yudao.coreservice.modules.system.service.sms.SysSmsCoreService; | ||||||
| import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService; | import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService; | ||||||
|  | import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthCheckCodeReqVO; | ||||||
| import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthSendSmsReqVO; | import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthSendSmsReqVO; | ||||||
| import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.sms.SysSmsCodeDO; | 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.dal.mysql.sms.SysSmsCodeMapper; | ||||||
| @@ -11,13 +12,11 @@ import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum; | |||||||
| import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsTemplateCodeConstants; | import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsTemplateCodeConstants; | ||||||
| import cn.iocoder.yudao.userserver.modules.system.framework.sms.SmsCodeProperties; | import cn.iocoder.yudao.userserver.modules.system.framework.sms.SmsCodeProperties; | ||||||
| import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService; | import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService; | ||||||
| import org.springframework.data.redis.core.StringRedisTemplate; |  | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
| import org.springframework.validation.annotation.Validated; | import org.springframework.validation.annotation.Validated; | ||||||
|  |  | ||||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||||
| import java.util.Date; | import java.util.Date; | ||||||
| import java.util.concurrent.TimeUnit; |  | ||||||
|  |  | ||||||
| import static cn.hutool.core.util.RandomUtil.randomInt; | import static cn.hutool.core.util.RandomUtil.randomInt; | ||||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||||
| @@ -33,11 +32,6 @@ import static cn.iocoder.yudao.userserver.modules.system.enums.SysErrorCodeConst | |||||||
| @Validated | @Validated | ||||||
| public class SysSmsCodeServiceImpl implements SysSmsCodeService { | public class SysSmsCodeServiceImpl implements SysSmsCodeService { | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 验证码 + 手机 在redis中存储的有效时间,单位:分钟 |  | ||||||
|      */ |  | ||||||
|     private static final Long CODE_TIME = 10L; |  | ||||||
|  |  | ||||||
|     @Resource |     @Resource | ||||||
|     private SmsCodeProperties smsCodeProperties; |     private SmsCodeProperties smsCodeProperties; | ||||||
|  |  | ||||||
| @@ -50,9 +44,6 @@ public class SysSmsCodeServiceImpl implements SysSmsCodeService { | |||||||
|     @Resource |     @Resource | ||||||
|     private SysSmsCoreService smsCoreService; |     private SysSmsCoreService smsCoreService; | ||||||
|  |  | ||||||
|     @Resource |  | ||||||
|     private StringRedisTemplate stringRedisTemplate; |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void sendSmsCode(String mobile, Integer scene, String createIp) { |     public void sendSmsCode(String mobile, Integer scene, String createIp) { | ||||||
|         // 创建验证码 |         // 创建验证码 | ||||||
| @@ -61,12 +52,6 @@ public class SysSmsCodeServiceImpl implements SysSmsCodeService { | |||||||
|         // TODO @宋天:这里可以拓展下 SysSmsSceneEnum,支持设置对应的短信模板编号(不同场景的短信文案是不同的)、是否要校验手机号已经注册。这样 Controller 就可以收口成一个接口了。相当于说,不同场景,不同策略 |         // TODO @宋天:这里可以拓展下 SysSmsSceneEnum,支持设置对应的短信模板编号(不同场景的短信文案是不同的)、是否要校验手机号已经注册。这样 Controller 就可以收口成一个接口了。相当于说,不同场景,不同策略 | ||||||
|         smsCoreService.sendSingleSmsToMember(mobile, null, SysSmsTemplateCodeConstants.USER_SMS_LOGIN, |         smsCoreService.sendSingleSmsToMember(mobile, null, SysSmsTemplateCodeConstants.USER_SMS_LOGIN, | ||||||
|                 MapUtil.of("code", code)); |                 MapUtil.of("code", code)); | ||||||
|  |  | ||||||
|         // 存储手机号与验证码到redis,用于标记 |  | ||||||
|         // TODO @宋天:SysSmsCodeDO 表应该足够,无需增加额外的 redis 存储哇 |  | ||||||
|         // TODO @宋天:Redis 相关的操作,不要散落到业务层,而是写一个它对应的 RedisDAO。这样,实现业务与技术的解耦 |  | ||||||
|         // TODO @宋天:直接使用 code 作为 key,会存在 2 个问题:1)code 可能会冲突,多个手机号之间;2)缺少前缀。例如说 sms_code_${code} |  | ||||||
|         stringRedisTemplate.opsForValue().set(code,mobile,CODE_TIME, TimeUnit.MINUTES); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -83,7 +68,7 @@ public class SysSmsCodeServiceImpl implements SysSmsCodeService { | |||||||
|  |  | ||||||
|     private String createSmsCode(String mobile, Integer scene, String ip) { |     private String createSmsCode(String mobile, Integer scene, String ip) { | ||||||
|         // 校验是否可以发送验证码,不用筛选场景 |         // 校验是否可以发送验证码,不用筛选场景 | ||||||
|         SysSmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null); |         SysSmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null,null); | ||||||
|         if (lastSmsCode != null) { |         if (lastSmsCode != null) { | ||||||
|             if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。 |             if (lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。 | ||||||
|                 throw exception(USER_SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY); |                 throw exception(USER_SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY); | ||||||
| @@ -108,7 +93,7 @@ public class SysSmsCodeServiceImpl implements SysSmsCodeService { | |||||||
|     @Override |     @Override | ||||||
|     public void useSmsCode(String mobile, Integer scene, String code, String usedIp) { |     public void useSmsCode(String mobile, Integer scene, String code, String usedIp) { | ||||||
|         // 校验验证码 |         // 校验验证码 | ||||||
|         SysSmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, scene); |         SysSmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null,scene); | ||||||
|         if (lastSmsCode == null) { // 若验证码不存在,抛出异常 |         if (lastSmsCode == null) { // 若验证码不存在,抛出异常 | ||||||
|             throw exception(USER_SMS_CODE_NOT_FOUND); |             throw exception(USER_SMS_CODE_NOT_FOUND); | ||||||
|         } |         } | ||||||
| @@ -138,4 +123,21 @@ public class SysSmsCodeServiceImpl implements SysSmsCodeService { | |||||||
|         this.sendSmsCode(user.getMobile(),SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene(), getClientIP()); |         this.sendSmsCode(user.getMobile(),SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene(), getClientIP()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public SysSmsCodeDO checkCodeIsExpired(String mobile, String code, Integer scene) { | ||||||
|  |         SysSmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, code, scene); | ||||||
|  |  | ||||||
|  |         // 检测验证码是否存在 | ||||||
|  |         if (lastSmsCode == null){ | ||||||
|  |             throw exception(USER_SMS_CODE_EXPIRED); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 检测验证码是否过期 | ||||||
|  |         if (System.currentTimeMillis() - lastSmsCode.getCreateTime().getTime() | ||||||
|  |                 >= smsCodeProperties.getExpireTimes().toMillis()) { | ||||||
|  |             throw exception(USER_SMS_CODE_EXPIRED); | ||||||
|  |         } | ||||||
|  |         return lastSmsCode; | ||||||
|  |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,60 +0,0 @@ | |||||||
| package cn.iocoder.yudao.userserver.modules.member.controller; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.userserver.modules.member.controller.user.SysUserProfileController; |  | ||||||
| import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService; |  | ||||||
| import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService; |  | ||||||
| import org.junit.Before; |  | ||||||
| import org.junit.Test; |  | ||||||
| import org.mockito.InjectMocks; |  | ||||||
| import org.mockito.Mock; |  | ||||||
| import org.mockito.MockitoAnnotations; |  | ||||||
| import org.springframework.http.MediaType; |  | ||||||
| import org.springframework.test.web.servlet.MockMvc; |  | ||||||
| import org.springframework.test.web.servlet.result.MockMvcResultHandlers; |  | ||||||
| import org.springframework.test.web.servlet.setup.MockMvcBuilders; |  | ||||||
|  |  | ||||||
|  |  | ||||||
| import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; |  | ||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * {@link SysUserProfileController} 的单元测试类 |  | ||||||
|  * |  | ||||||
|  * @author 宋天 |  | ||||||
|  */ |  | ||||||
| // TODO @宋天:controller 的单测可以不写哈,因为收益太低了。未来我们做 qa 自动化测试 |  | ||||||
| public class SysUserProfileControllerTest { |  | ||||||
|  |  | ||||||
|     private MockMvc mockMvc; |  | ||||||
|  |  | ||||||
|     @InjectMocks |  | ||||||
|     private SysUserProfileController sysUserProfileController; |  | ||||||
|  |  | ||||||
|     @Mock |  | ||||||
|     private MbrUserService userService; |  | ||||||
|  |  | ||||||
|     @Mock |  | ||||||
|     private SysSmsCodeService smsCodeService; |  | ||||||
|  |  | ||||||
|     @Before // TODO @宋天:使用 junit5 哈 |  | ||||||
|     public void setup() { |  | ||||||
|         // 初始化 |  | ||||||
|         MockitoAnnotations.openMocks(this); |  | ||||||
|  |  | ||||||
|         // 构建mvc环境 |  | ||||||
|         mockMvc = MockMvcBuilders.standaloneSetup(sysUserProfileController).build(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void testUpdateMobile_success() throws Exception { |  | ||||||
|         //模拟接口调用 |  | ||||||
|         this.mockMvc.perform(post("/system/user/profile/update-mobile") |  | ||||||
|                         .contentType(MediaType.APPLICATION_JSON_VALUE) |  | ||||||
|                         .content("{\"mobile\":\"15819844280\",\"code\":\"123456\"}}")) |  | ||||||
|                 .andExpect(status().isOk()) |  | ||||||
|                 .andDo(MockMvcResultHandlers.print()); |  | ||||||
| // TODO @宋天:方法的结尾,不用空行哈 |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,75 +0,0 @@ | |||||||
| package cn.iocoder.yudao.userserver.modules.system.controller; |  | ||||||
|  |  | ||||||
| import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService; |  | ||||||
| import cn.iocoder.yudao.userserver.modules.system.controller.auth.SysAuthController; |  | ||||||
| import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService; |  | ||||||
| import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService; |  | ||||||
| import org.junit.Before; |  | ||||||
| import org.junit.Test; |  | ||||||
| import org.mockito.InjectMocks; |  | ||||||
| import org.mockito.Mock; |  | ||||||
| import org.mockito.MockitoAnnotations; |  | ||||||
| import org.springframework.http.MediaType; |  | ||||||
| import org.springframework.test.web.servlet.MockMvc; |  | ||||||
| import org.springframework.test.web.servlet.result.MockMvcResultHandlers; |  | ||||||
| import org.springframework.test.web.servlet.setup.MockMvcBuilders; |  | ||||||
|  |  | ||||||
| import static org.springframework.http.HttpHeaders.AUTHORIZATION; |  | ||||||
| import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; |  | ||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; |  | ||||||
| import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * {@link SysAuthController} 的单元测试类 |  | ||||||
|  * |  | ||||||
|  * @author 宋天 |  | ||||||
|  */ |  | ||||||
| public class SysAuthControllerTest { |  | ||||||
|  |  | ||||||
|     private MockMvc mockMvc; |  | ||||||
|  |  | ||||||
|     @InjectMocks |  | ||||||
|     private SysAuthController sysAuthController; |  | ||||||
|  |  | ||||||
|     @Mock |  | ||||||
|     private SysAuthService authService; |  | ||||||
|     @Mock |  | ||||||
|     private SysSmsCodeService smsCodeService; |  | ||||||
|     @Mock |  | ||||||
|     private SysSocialService socialService; |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @Before |  | ||||||
|     public void setup() { |  | ||||||
|         // 初始化 |  | ||||||
|         MockitoAnnotations.openMocks(this); |  | ||||||
|  |  | ||||||
|         // 构建mvc环境 |  | ||||||
|         mockMvc = MockMvcBuilders.standaloneSetup(sysAuthController).build(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void testResetPassword_success() throws Exception { |  | ||||||
|         //模拟接口调用 |  | ||||||
|         this.mockMvc.perform(post("/reset-password") |  | ||||||
|                         .contentType(MediaType.APPLICATION_JSON) |  | ||||||
|                         .content("{\"password\":\"1123\",\"code\":\"123456\"}}")) |  | ||||||
|                 .andExpect(status().isOk()) |  | ||||||
|                 .andDo(MockMvcResultHandlers.print()); |  | ||||||
|  |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |  | ||||||
|     public void testUpdatePassword_success() throws Exception { |  | ||||||
|         //模拟接口调用 |  | ||||||
|         this.mockMvc.perform(post("/update-password") |  | ||||||
|                         .contentType(MediaType.APPLICATION_JSON) |  | ||||||
|                         .content("{\"password\":\"1123\",\"code\":\"123456\",\"oldPassword\":\"1123\"}}")) |  | ||||||
|                 .andExpect(status().isOk()) |  | ||||||
|                 .andDo(MockMvcResultHandlers.print()); |  | ||||||
|  |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } |  | ||||||
		Reference in New Issue
	
	Block a user
	 宋天
					宋天