diff --git a/yudao-user-server/pom.xml b/yudao-user-server/pom.xml index e7b1cd82d..b4c2cf8a5 100644 --- a/yudao-user-server/pom.xml +++ b/yudao-user-server/pom.xml @@ -91,6 +91,13 @@ test + + + junit + junit + test + + diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/controller/user/SysUserProfileController.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/controller/user/SysUserProfileController.java index 870d33718..39c3d88f3 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/controller/user/SysUserProfileController.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/controller/user/SysUserProfileController.java @@ -5,6 +5,9 @@ import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService; +import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserUpdateMobileReqVO; +import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum; +import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; @@ -13,10 +16,12 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; +import javax.validation.Valid; import java.io.IOException; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.userserver.modules.member.enums.MbrErrorCodeConstants.FILE_IS_EMPTY; @@ -30,6 +35,9 @@ public class SysUserProfileController { @Resource private MbrUserService userService; + @Resource + private SysSmsCodeService smsCodeService; + @PutMapping("/update-nickname") @ApiOperation("修改用户昵称") @PreAuthenticated @@ -56,5 +64,17 @@ public class SysUserProfileController { return success(userService.getUserInfo(getLoginUserId())); } + + @PostMapping("/update-mobile") + @ApiOperation(value = "修改用户手机") + @PreAuthenticated + public CommonResult updateMobile(@RequestBody @Valid MbrUserUpdateMobileReqVO reqVO) { + // 校验验证码 + smsCodeService.useSmsCode(reqVO.getMobile(),SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene(), reqVO.getCode(),getClientIP()); + + userService.updateMobile(getLoginUserId(), reqVO); + return success(true); + } + } diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/controller/user/vo/MbrUserUpdateMobileReqVO.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/controller/user/vo/MbrUserUpdateMobileReqVO.java new file mode 100644 index 000000000..0e5499bc7 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/controller/user/vo/MbrUserUpdateMobileReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.userserver.modules.member.controller.user.vo; + +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.NotEmpty; +import javax.validation.constraints.Pattern; + +@ApiModel("修改手机 Request VO") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class MbrUserUpdateMobileReqVO { + + @ApiModelProperty(value = "手机验证码", required = true, example = "1024") + @NotEmpty(message = "手机验证码不能为空") + @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位") + @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") + private String code; + + + @ApiModelProperty(value = "手机号",required = true,example = "15823654487") + @NotBlank(message = "手机号不能为空") + @Length(min = 8, max = 11, message = "手机号码长度为 8-11 位") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式错误") + private String mobile; + +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/service/user/MbrUserService.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/service/user/MbrUserService.java index 6b6a36a8e..e45763dd3 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/service/user/MbrUserService.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/service/user/MbrUserService.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.userserver.modules.member.service.user; import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO; import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserInfoRespVO; import cn.iocoder.yudao.framework.common.validation.Mobile; +import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserUpdateMobileReqVO; import java.io.InputStream; @@ -69,4 +70,11 @@ public interface MbrUserService { */ MbrUserInfoRespVO getUserInfo(Long userId); + /** + * 修改手机 + * @param userId 用户id + * @param reqVO 请求实体 + */ + void updateMobile(Long userId, MbrUserUpdateMobileReqVO reqVO); + } diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/service/user/impl/MbrUserServiceImpl.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/service/user/impl/MbrUserServiceImpl.java index f340c5fe9..76b4501eb 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/service/user/impl/MbrUserServiceImpl.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/service/user/impl/MbrUserServiceImpl.java @@ -6,8 +6,10 @@ import cn.iocoder.yudao.coreservice.modules.infra.service.file.InfFileCoreServic import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO; import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserInfoRespVO; 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.dal.mysql.user.MbrUserMapper; import cn.iocoder.yudao.userserver.modules.member.service.user.MbrUserService; +import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService; import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; @@ -40,6 +42,9 @@ public class MbrUserServiceImpl implements MbrUserService { @Resource private PasswordEncoder passwordEncoder; + @Resource + private SysAuthService sysAuthService; + @Override public MbrUserDO getUserByMobile(String mobile) { return userMapper.selectByMobile(mobile); @@ -116,6 +121,17 @@ public class MbrUserServiceImpl implements MbrUserService { return userResp; } + @Override + public void updateMobile(Long userId, MbrUserUpdateMobileReqVO reqVO) { + // 检测用户是否存在 + MbrUserDO userDO = checkUserExists(userId); + // 检测手机与验证码是否匹配 + sysAuthService.checkIfMobileMatchCodeAndDeleteCode(userDO.getMobile(),reqVO.getCode()); + // 更新用户手机 + userDO.setMobile(reqVO.getMobile()); + userMapper.updateById(userDO); + } + @VisibleForTesting public MbrUserDO checkUserExists(Long id) { if (id == null) { 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 6d18d53be..a4a4efb87 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,7 +3,9 @@ package cn.iocoder.yudao.userserver.modules.system.controller.auth; import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.*; +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.sms.SysSmsCodeService; import com.alibaba.fastjson.JSON; @@ -56,16 +58,47 @@ public class SysAuthController { } @PostMapping("/send-sms-code") - @ApiOperation("发送手机验证码") + @ApiOperation(value = "发送手机验证码",notes = "不检测该手机号是否已被注册") public CommonResult sendSmsCode(@RequestBody @Valid SysAuthSendSmsReqVO reqVO) { smsCodeService.sendSmsCode(reqVO.getMobile(), reqVO.getScene(), getClientIP()); return success(true); } + @PostMapping("/send-sms-new-code") + @ApiOperation(value = "发送手机验证码",notes = "检测该手机号是否已被注册,用于修改手机时使用") + public CommonResult sendSmsNewCode(@RequestBody @Valid SysAuthSendSmsReqVO reqVO) { + smsCodeService.sendSmsNewCode(reqVO); + return success(true); + } + + @GetMapping("/send-sms-code-login") + @ApiOperation(value = "向已登录用户发送验证码",notes = "修改手机时验证原手机号使用") + public CommonResult sendSmsCodeLogin() { + smsCodeService.sendSmsCodeLogin(getLoginUserId()); + return success(true); + } + @PostMapping("/reset-password") @ApiOperation(value = "重置密码", notes = "用户忘记密码时使用") - public CommonResult resetPassword(@RequestBody @Valid MbrAuthResetPasswordReqVO reqVO) { - return null; + public CommonResult resetPassword(@RequestBody @Validated(MbrAuthResetPasswordReqVO.resetPasswordValidView.class) MbrAuthResetPasswordReqVO reqVO) { + authService.resetPassword(reqVO); + return success(true); + } + + @PostMapping("/update-password") + @ApiOperation(value = "修改用户密码",notes = "用户修改密码时使用") + @PreAuthenticated + public CommonResult updatePassword(@RequestBody @Validated(MbrAuthResetPasswordReqVO.updatePasswordValidView.class) MbrAuthResetPasswordReqVO reqVO) { + authService.updatePassword(getLoginUserId(), reqVO); + return success(true); + } + + @PostMapping("/check-sms-code") + @ApiOperation(value = "校验验证码是否正确") + @PreAuthenticated + public CommonResult checkSmsCode(@RequestBody @Valid SysAuthSmsLoginReqVO reqVO) { + smsCodeService.useSmsCode(reqVO.getMobile(),SysSmsSceneEnum.CHECK_CODE_BY_SMS.getScene(),reqVO.getCode(),getClientIP()); + return success(true); } // ========== 社交登录相关 ========== diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/vo/MbrAuthResetPasswordReqVO.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/vo/MbrAuthResetPasswordReqVO.java index 3b3556fdb..f092eaf48 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/vo/MbrAuthResetPasswordReqVO.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/controller/auth/vo/MbrAuthResetPasswordReqVO.java @@ -8,6 +8,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Pattern; @@ -18,15 +19,31 @@ import javax.validation.constraints.Pattern; @Builder public class MbrAuthResetPasswordReqVO { + /** + * 修改密码校验规则 + */ + public interface updatePasswordValidView { + } + + /** + * 忘记密码校验规则 + */ + public interface resetPasswordValidView { + } + + @ApiModelProperty(value = "用户旧密码", required = true, example = "123456") + @NotBlank(message = "旧密码不能为空",groups = updatePasswordValidView.class) + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String oldPassword; + @ApiModelProperty(value = "新密码", required = true, example = "buzhidao") - @NotEmpty(message = "新密码不能为空") + @NotEmpty(message = "新密码不能为空",groups = {updatePasswordValidView.class,resetPasswordValidView.class}) @Length(min = 4, max = 16, message = "密码长度为 4-16 位") private String password; @ApiModelProperty(value = "手机验证码", required = true, example = "1024") - @NotEmpty(message = "手机验证码不能为空") + @NotEmpty(message = "手机验证码不能为空",groups = resetPasswordValidView.class) @Length(min = 4, max = 6, message = "手机验证码长度为 4-6 位") @Pattern(regexp = "^[0-9]+$", message = "手机验证码必须都是数字") private String code; - } 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 f24be9cc6..e7c104afb 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 @@ -23,7 +23,10 @@ public interface SysErrorCodeConstants { 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, "短信发送过于频率"); + ErrorCode USER_SMS_CODE_IS_EXISTS = new ErrorCode(1005001006, "手机号已被使用"); // ========== 用户模块 1005002000 ========== ErrorCode USER_NOT_EXISTS = new ErrorCode(1005002001, "用户不存在"); + ErrorCode USER_CODE_FAILED = new ErrorCode(1005002002, "验证码不匹配"); + ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1005002003, "密码校验失败"); } diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/sms/SysSmsSceneEnum.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/sms/SysSmsSceneEnum.java index 6f5ce3daa..c2156d218 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/sms/SysSmsSceneEnum.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/enums/sms/SysSmsSceneEnum.java @@ -17,7 +17,9 @@ public enum SysSmsSceneEnum implements IntArrayValuable { LOGIN_BY_SMS(1, "手机号登陆"), CHANGE_MOBILE_BY_SMS(2, "更换手机号"), - ; + FORGET_MOBILE_BY_SMS(3, "忘记密码"), + CHECK_CODE_BY_SMS(4, "审核验证码"), + ; public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SysSmsSceneEnum::getScene).toArray(); diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/auth/SysAuthService.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/auth/SysAuthService.java index 628f95c80..d84d17558 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/auth/SysAuthService.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/auth/SysAuthService.java @@ -63,4 +63,23 @@ public interface SysAuthService extends SecurityAuthFrameworkService { */ void socialBind(Long userId, @Valid MbrAuthSocialBindReqVO reqVO); + /** + * 修改用户密码 + * @param userId 用户id + * @param userReqVO 用户请求实体类 + */ + void updatePassword(Long userId, MbrAuthResetPasswordReqVO userReqVO); + + /** + * 忘记密码 + * @param userReqVO 用户请求实体类 + */ + void resetPassword(MbrAuthResetPasswordReqVO userReqVO); + + /** + * 检测手机与验证码是否匹配 + * @param phone 手机号 + * @param code 验证码 + */ + void checkIfMobileMatchCodeAndDeleteCode(String phone,String code); } diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/auth/impl/SysAuthServiceImpl.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/auth/impl/SysAuthServiceImpl.java index 2394cd03a..bd8b57198 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/auth/impl/SysAuthServiceImpl.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/service/auth/impl/SysAuthServiceImpl.java @@ -15,15 +15,19 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; import cn.iocoder.yudao.framework.security.core.LoginUser; +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.system.controller.auth.vo.*; import cn.iocoder.yudao.userserver.modules.system.convert.auth.SysAuthConvert; 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.sms.SysSmsCodeService; +import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import me.zhyd.oauth.model.AuthUser; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; @@ -32,6 +36,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -40,6 +45,7 @@ import java.util.List; import java.util.Objects; 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.system.enums.SysErrorCodeConstants.*; /** @@ -65,6 +71,13 @@ public class SysAuthServiceImpl implements SysAuthService { private SysUserSessionCoreService userSessionCoreService; @Resource private SysSocialService socialService; + @Resource + private StringRedisTemplate stringRedisTemplate; + @Resource + private PasswordEncoder passwordEncoder; + @Resource + private MbrUserMapper userMapper; + private static final UserTypeEnum userTypeEnum = UserTypeEnum.MEMBER; @Override @@ -200,12 +213,12 @@ public class SysAuthServiceImpl implements SysAuthService { } reqDTO.setUsername(mobile); reqDTO.setUserAgent(ServletUtils.getUserAgent()); - reqDTO.setUserIp(ServletUtils.getClientIP()); + reqDTO.setUserIp(getClientIP()); reqDTO.setResult(loginResult.getResult()); loginLogCoreService.createLoginLog(reqDTO); // 更新最后登录时间 if (user != null && Objects.equals(SysLoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) { - userService.updateUserLogin(user.getId(), ServletUtils.getClientIP()); + userService.updateUserLogin(user.getId(), getClientIP()); } } @@ -266,6 +279,66 @@ public class SysAuthServiceImpl implements SysAuthService { this.createLogoutLog(loginUser.getId(), loginUser.getUsername()); } + @Override + public void updatePassword(Long userId, MbrAuthResetPasswordReqVO reqVO) { + // 检验旧密码 + MbrUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword()); + + // 更新用户密码 + userDO.setPassword(passwordEncoder.encode(reqVO.getPassword())); + userMapper.updateById(userDO); + } + + @Override + public void resetPassword(MbrAuthResetPasswordReqVO reqVO) { + // 根据验证码取出手机号,并查询用户 + String mobile = stringRedisTemplate.opsForValue().get(reqVO.getCode()); + MbrUserDO userDO = userMapper.selectByMobile(mobile); + if (userDO == null){ + throw exception(USER_NOT_EXISTS); + } + // TODO @芋艿 这一步没必要检验验证码与手机是否匹配,因为是根据验证码去redis中查找手机号,然后根据手机号查询用户 + // 也就是说 即便黑客以其他方式将验证码发送到自己手机上,最终还是会根据手机号查询用户然后进行重置密码的操作,不存在安全问题 + + // 校验验证码 + smsCodeService.useSmsCode(userDO.getMobile(), SysSmsSceneEnum.FORGET_MOBILE_BY_SMS.getScene(), reqVO.getCode(),getClientIP()); + + // 更新密码 + userDO.setPassword(passwordEncoder.encode(reqVO.getPassword())); + userMapper.updateById(userDO); + } + + @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); + } + + /** + * 校验旧密码 + * + * @param id 用户 id + * @param oldPassword 旧密码 + * @return MbrUserDO 用户实体 + */ + @VisibleForTesting + public MbrUserDO checkOldPassword(Long id, String oldPassword) { + MbrUserDO user = userMapper.selectById(id); + if (user == null) { + throw exception(USER_NOT_EXISTS); + } + // 参数:未加密密码,编码后的密码 + if (!passwordEncoder.matches(oldPassword,user.getPassword())) { + throw exception(USER_PASSWORD_FAILED); + } + return user; + } + private void createLogoutLog(Long userId, String username) { SysLoginLogCreateReqDTO reqDTO = new SysLoginLogCreateReqDTO(); reqDTO.setLogType(SysLoginLogTypeEnum.LOGOUT_SELF.getType()); @@ -274,7 +347,7 @@ public class SysAuthServiceImpl implements SysAuthService { reqDTO.setUserType(userTypeEnum.getValue()); reqDTO.setUsername(username); reqDTO.setUserAgent(ServletUtils.getUserAgent()); - reqDTO.setUserIp(ServletUtils.getClientIP()); + reqDTO.setUserIp(getClientIP()); reqDTO.setResult(SysLoginResultEnum.SUCCESS.getResult()); loginLogCoreService.createLoginLog(reqDTO); } 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 index 5ee81b67c..6e9c3c7b3 100644 --- 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 @@ -2,6 +2,7 @@ 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.controller.auth.vo.SysAuthSendSmsReqVO; import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum; /** @@ -20,6 +21,12 @@ public interface SysSmsCodeService { */ void sendSmsCode(@Mobile String mobile, Integer scene, String createIp); + /** + * 发送短信验证码,并检测手机号是否已被注册 + * @param reqVO 请求实体 + */ + void sendSmsNewCode(SysAuthSendSmsReqVO reqVO); + /** * 验证短信验证码,并进行使用 * 如果正确,则将验证码标记成已使用 @@ -32,4 +39,9 @@ public interface SysSmsCodeService { */ void useSmsCode(@Mobile String mobile, Integer scene, String code, String usedIp); + /** + * 根据用户id发送验证码 + * @param userId 用户id + */ + void sendSmsCodeLogin(Long userId); } 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 index 6ad132aa5..d9a3d68f1 100644 --- 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 @@ -1,20 +1,27 @@ package cn.iocoder.yudao.userserver.modules.system.service.sms.impl; import cn.hutool.core.map.MapUtil; +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.userserver.modules.member.service.user.MbrUserService; +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.mysql.sms.SysSmsCodeMapper; +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.framework.sms.SmsCodeProperties; 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.validation.annotation.Validated; import javax.annotation.Resource; import java.util.Date; +import java.util.concurrent.TimeUnit; 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.util.servlet.ServletUtils.getClientIP; import static cn.iocoder.yudao.userserver.modules.system.enums.SysErrorCodeConstants.*; /** @@ -26,15 +33,26 @@ import static cn.iocoder.yudao.userserver.modules.system.enums.SysErrorCodeConst @Validated public class SysSmsCodeServiceImpl implements SysSmsCodeService { + /** + * 验证码 + 手机 在redis中存储的有效时间,单位:分钟 + */ + private static final Long CODE_TIME = 10L; + @Resource private SmsCodeProperties smsCodeProperties; @Resource private SysSmsCodeMapper smsCodeMapper; + @Resource + private MbrUserService mbrUserService; + @Resource private SysSmsCoreService smsCoreService; + @Resource + private StringRedisTemplate stringRedisTemplate; + @Override public void sendSmsCode(String mobile, Integer scene, String createIp) { // 创建验证码 @@ -42,6 +60,21 @@ public class SysSmsCodeServiceImpl implements SysSmsCodeService { // 发送验证码 smsCoreService.sendSingleSmsToMember(mobile, null, SysSmsTemplateCodeConstants.USER_SMS_LOGIN, MapUtil.of("code", code)); + + // 存储手机号与验证码到redis,用于标记 + stringRedisTemplate.opsForValue().set(code,mobile,CODE_TIME, TimeUnit.MINUTES); + } + + @Override + public void sendSmsNewCode(SysAuthSendSmsReqVO reqVO) { + // 检测手机号是否已被使用 + MbrUserDO userByMobile = mbrUserService.getUserByMobile(reqVO.getMobile()); + if (userByMobile != null){ + throw exception(USER_SMS_CODE_IS_EXISTS); + } + + // 发送短信 + this.sendSmsCode(reqVO.getMobile(),reqVO.getScene(),getClientIP()); } private String createSmsCode(String mobile, Integer scene, String ip) { @@ -91,4 +124,14 @@ public class SysSmsCodeServiceImpl implements SysSmsCodeService { .used(true).usedTime(new Date()).usedIp(usedIp).build()); } + @Override + public void sendSmsCodeLogin(Long userId) { + MbrUserDO user = mbrUserService.getUser(userId); + if (user == null){ + throw exception(USER_NOT_EXISTS); + } + // 发送验证码 + this.sendSmsCode(user.getMobile(),SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene(), getClientIP()); + } + } diff --git a/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/BaseDbAndRedisUnitTest.java b/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/BaseDbAndRedisUnitTest.java new file mode 100644 index 000000000..2669ef49c --- /dev/null +++ b/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/BaseDbAndRedisUnitTest.java @@ -0,0 +1,48 @@ +package cn.iocoder.yudao.userserver; + +import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration; +import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration; +import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; +import cn.iocoder.yudao.userserver.config.RedisTestConfiguration; +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; +import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; +import org.redisson.spring.starter.RedissonAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; + +/** + * 依赖内存 DB + Redis 的单元测试 + * + * 相比 {@link BaseDbUnitTest} 来说,额外增加了内存 Redis + * + * @author 芋道源码 + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisUnitTest.Application.class) +@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件 +@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB +public class BaseDbAndRedisUnitTest { + + @Import({ + // DB 配置类 + YudaoDataSourceAutoConfiguration.class, // 自己的 DB 配置类 + DataSourceAutoConfiguration.class, // Spring DB 自动配置类 + DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类 + DruidDataSourceAutoConfigure.class, // Druid 自动配置类 + // MyBatis 配置类 + YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类 + MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类 + // Redis 配置类 + RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer + RedisAutoConfiguration.class, // Spring Redis 自动配置类 + YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类 + RedissonAutoConfiguration.class, // Redisson 自动高配置类 + }) + public static class Application { + } + +} diff --git a/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/config/RedisTestConfiguration.java b/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/config/RedisTestConfiguration.java new file mode 100644 index 000000000..7164efd87 --- /dev/null +++ b/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/config/RedisTestConfiguration.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.userserver.config; + +import com.github.fppt.jedismock.RedisServer; +import org.springframework.boot.autoconfigure.data.redis.RedisProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; + +import java.io.IOException; + +@Configuration(proxyBeanMethods = false) +@Lazy(false) // 禁止延迟加载 +@EnableConfigurationProperties(RedisProperties.class) +public class RedisTestConfiguration { + + /** + * 创建模拟的 Redis Server 服务器 + */ + @Bean + public RedisServer redisServer(RedisProperties properties) throws IOException { + RedisServer redisServer = new RedisServer(properties.getPort()); + // TODO 芋艿:一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样,就导致端口被占用,无法启动。。。 + try { + redisServer.start(); + } catch (Exception ignore) {} + return redisServer; + } + +} diff --git a/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/modules/member/controller/SysUserProfileControllerTest.java b/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/modules/member/controller/SysUserProfileControllerTest.java new file mode 100644 index 000000000..d8b8e7f71 --- /dev/null +++ b/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/modules/member/controller/SysUserProfileControllerTest.java @@ -0,0 +1,59 @@ +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 宋天 + */ +public class SysUserProfileControllerTest { + + private MockMvc mockMvc; + + @InjectMocks + private SysUserProfileController sysUserProfileController; + + @Mock + private MbrUserService userService; + + @Mock + private SysSmsCodeService smsCodeService; + + @Before + 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()); + + } + + +} diff --git a/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/modules/member/service/MbrUserServiceImplTest.java b/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/modules/member/service/MbrUserServiceImplTest.java index 3639c25db..dadb27f7d 100644 --- a/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/modules/member/service/MbrUserServiceImplTest.java +++ b/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/modules/member/service/MbrUserServiceImplTest.java @@ -2,28 +2,32 @@ package cn.iocoder.yudao.userserver.modules.member.service; import cn.iocoder.yudao.coreservice.modules.infra.service.file.InfFileCoreService; import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO; -import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; +import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; +import cn.iocoder.yudao.userserver.BaseDbAndRedisUnitTest; import cn.iocoder.yudao.userserver.BaseDbUnitTest; import cn.iocoder.yudao.userserver.modules.member.controller.user.vo.MbrUserInfoRespVO; +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.service.user.impl.MbrUserServiceImpl; +import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.SysAuthSendSmsReqVO; +import cn.iocoder.yudao.userserver.modules.system.enums.sms.SysSmsSceneEnum; +import cn.iocoder.yudao.userserver.modules.system.service.auth.impl.SysAuthServiceImpl; +import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService; +import cn.iocoder.yudao.userserver.modules.system.service.sms.impl.SysSmsCodeServiceImpl; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockMultipartFile; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.util.Assert; import javax.annotation.Resource; import java.io.*; import java.util.function.Consumer; -import static cn.hutool.core.util.RandomUtil.randomBytes; +import static cn.hutool.core.util.RandomUtil.*; import static org.junit.jupiter.api.Assertions.assertEquals; -import static cn.hutool.core.util.RandomUtil.randomEle; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static org.mockito.Mockito.*; @@ -32,21 +36,30 @@ import static org.mockito.Mockito.*; * * @author 宋天 */ -@Import(MbrUserServiceImpl.class) -public class MbrUserServiceImplTest extends BaseDbUnitTest { +@Import({MbrUserServiceImpl.class, YudaoRedisAutoConfiguration.class}) +public class MbrUserServiceImplTest extends BaseDbAndRedisUnitTest { @Resource private MbrUserServiceImpl mbrUserService; + @Resource + private StringRedisTemplate stringRedisTemplate; + @Resource private MbrUserMapper userMapper; + @MockBean + private SysAuthServiceImpl authService; + @MockBean private InfFileCoreService fileCoreService; @MockBean private PasswordEncoder passwordEncoder; + @MockBean + private SysSmsCodeService sysSmsCodeService; + @Test public void testUpdateNickName_success(){ // mock 数据 @@ -94,6 +107,37 @@ public class MbrUserServiceImplTest extends BaseDbUnitTest { assertEquals(avatar, str); } + @Test + public void updateMobile_success(){ + // mock数据 + String oldMobile = randomNumbers(11); + MbrUserDO userDO = randomMbrUserDO(); + userDO.setMobile(oldMobile); + userMapper.insert(userDO); + + // 验证旧手机 + sysSmsCodeService.sendSmsCodeLogin(userDO.getId()); + + // 验证旧手机验证码是否正确 + sysSmsCodeService.useSmsCode(oldMobile,SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene(),"123","1.1.1.1"); + + // 验证新手机 + SysAuthSendSmsReqVO smsReqVO = new SysAuthSendSmsReqVO(); + smsReqVO.setMobile(oldMobile); + smsReqVO.setScene(SysSmsSceneEnum.CHANGE_MOBILE_BY_SMS.getScene()); + sysSmsCodeService.sendSmsNewCode(smsReqVO); + + // 更新手机号 + String newMobile = randomNumbers(11); + String code = randomNumbers(4); + MbrUserUpdateMobileReqVO reqVO = new MbrUserUpdateMobileReqVO(); + reqVO.setMobile(newMobile); + reqVO.setCode(code); + mbrUserService.updateMobile(userDO.getId(),reqVO); + + assertEquals(mbrUserService.getUser(userDO.getId()).getMobile(),newMobile); + } + // ========== 随机对象 ========== @SafeVarargs diff --git a/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/modules/system/controller/SysAuthControllerTest.java b/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/modules/system/controller/SysAuthControllerTest.java new file mode 100644 index 000000000..599ebaab6 --- /dev/null +++ b/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/modules/system/controller/SysAuthControllerTest.java @@ -0,0 +1,75 @@ +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()); + + } + + + +} diff --git a/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/modules/system/service/SysAuthServiceTest.java b/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/modules/system/service/SysAuthServiceTest.java new file mode 100644 index 000000000..2c2d73c76 --- /dev/null +++ b/yudao-user-server/src/test/java/cn/iocoder/yudao/userserver/modules/system/service/SysAuthServiceTest.java @@ -0,0 +1,134 @@ +package cn.iocoder.yudao.userserver.modules.system.service; + +import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.coreservice.modules.member.dal.dataobject.user.MbrUserDO; +import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user.SysUserDO; +import cn.iocoder.yudao.coreservice.modules.system.service.auth.SysUserSessionCoreService; +import cn.iocoder.yudao.coreservice.modules.system.service.logger.SysLoginLogCoreService; +import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; +import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; +import cn.iocoder.yudao.userserver.BaseDbAndRedisUnitTest; +import cn.iocoder.yudao.userserver.BaseDbUnitTest; +import cn.iocoder.yudao.userserver.config.RedisTestConfiguration; +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.impl.MbrUserServiceImpl; +import cn.iocoder.yudao.userserver.modules.system.controller.auth.vo.MbrAuthResetPasswordReqVO; +import cn.iocoder.yudao.userserver.modules.system.service.auth.SysAuthService; +import cn.iocoder.yudao.userserver.modules.system.service.auth.impl.SysAuthServiceImpl; +import cn.iocoder.yudao.userserver.modules.system.service.sms.SysSmsCodeService; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.crypto.password.PasswordEncoder; + +import javax.annotation.Resource; + +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import static cn.hutool.core.util.RandomUtil.randomEle; +import static cn.hutool.core.util.RandomUtil.randomNumbers; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + + +/** + * {@link SysAuthService} 的单元测试类 + * + * @author 宋天 + */ +@Import({SysAuthServiceImpl.class, YudaoRedisAutoConfiguration.class}) +public class SysAuthServiceTest extends BaseDbAndRedisUnitTest { + + @MockBean + private AuthenticationManager authenticationManager; + @MockBean + private MbrUserService userService; + @MockBean + private SysSmsCodeService smsCodeService; + @MockBean + private SysLoginLogCoreService loginLogCoreService; + @MockBean + private SysUserSessionCoreService userSessionCoreService; + @MockBean + private SysSocialService socialService; + @Resource + private StringRedisTemplate stringRedisTemplate; + @MockBean + private PasswordEncoder passwordEncoder; + @Resource + private MbrUserMapper mbrUserMapper; + @Resource + private SysAuthServiceImpl authService; + + + @Test + public void testUpdatePassword_success(){ + // 准备参数 + MbrUserDO userDO = randomMbrUserDO(); + mbrUserMapper.insert(userDO); + + // 新密码 + String newPassword = randomString(); + + // 请求实体 + MbrAuthResetPasswordReqVO reqVO = new MbrAuthResetPasswordReqVO(); + reqVO.setOldPassword(userDO.getPassword()); + reqVO.setPassword(newPassword); + + // 测试桩 + // 这两个相等是为了返回ture这个结果 + when(passwordEncoder.matches(reqVO.getOldPassword(),reqVO.getOldPassword())).thenReturn(true); + when(passwordEncoder.encode(newPassword)).thenReturn(newPassword); + + // 更新用户密码 + authService.updatePassword(userDO.getId(),reqVO); + assertEquals(mbrUserMapper.selectById(userDO.getId()).getPassword(),newPassword); + } + + @Test + public void testResetPassword_success(){ + // 准备参数 + MbrUserDO userDO = randomMbrUserDO(); + mbrUserMapper.insert(userDO); + + // 随机密码 + String password = randomNumbers(11); + // 随机验证码 + String code = randomNumbers(4); + + MbrAuthResetPasswordReqVO reqVO = new MbrAuthResetPasswordReqVO(); + reqVO.setPassword(password); + reqVO.setCode(code); + + // 放入code+手机号 + stringRedisTemplate.opsForValue().set(code,userDO.getMobile(),10, TimeUnit.MINUTES); + + // mock + when(passwordEncoder.encode(password)).thenReturn(password); + + // 更新用户密码 + authService.resetPassword(reqVO); + assertEquals(mbrUserMapper.selectById(userDO.getId()).getPassword(),password); + } + + + // ========== 随机对象 ========== + + @SafeVarargs + private static MbrUserDO randomMbrUserDO(Consumer... consumers) { + Consumer consumer = (o) -> { + o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 + o.setPassword(randomString()); + }; + return randomPojo(MbrUserDO.class, ArrayUtils.append(consumer, consumers)); + } + + +}