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