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));
+ }
+
+
+}