• 用户微服务用户注册功能实现



    用户注册之前需要先给注册的手机号发送一条验证码,我们把验证码存储在Redis中。
    发送的时候我们先把验证码存储到Redis,然后用户发起注册的时候取出验证。
    在这里插入图片描述
    image.png

    发送验证码

    Redis配置如下:

    package com.zjq.users.config;
    
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    /**
     * redis配置类
     * @author zjq
     */
    @Configuration
    public class RedisTemplateConfiguration {
        /**
         * redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类
         * @param redisConnectionFactory
         * @return
         */
        @Bean
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(redisConnectionFactory);
    
            // 使用Jackson2JsonRedisSerialize 替换默认序列化
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
    
            // 设置key和value的序列化规则
            redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
            redisTemplate.setKeySerializer(new StringRedisSerializer());
    
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
    
            redisTemplate.afterPropertiesSet();
            return redisTemplate;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    新建一个service实现发送验证码功能:

    1. 根据手机号查询是否已生成验证码,已生成直接返回
    2. 没有生成则生成6位验证码
    3. 调用短信服务发送短信
    4. 发送成功,将code保存至Redis,失效时间60s

    代码实现如下:

    /**
     * 发送验证码业务逻辑层
     * @author zjq
     */
    @Service
    public class SendVerifyCodeService {
    
        @Resource
        private RedisTemplate<String, String> redisTemplate;
    
        /**
         * 发送验证码
         *
         * @param phone
         */
        public void send(String phone) {
            // 检查非空
            AssertUtil.isNotEmpty(phone, "手机号不能为空");
            // 根据手机号查询是否已生成验证码,已生成直接返回
            if (!checkCodeIsExpired(phone)) {
                return;
            }
            // 生成 6 位验证码
            String code = RandomUtil.randomNumbers(6);
            // 调用短信服务发送短信
            // 发送成功,将 code 保存至 Redis,失效时间 60s
            String key = RedisKeyConstant.verify_code.getKey() + phone;
            redisTemplate.opsForValue().set(key, code, 60, TimeUnit.SECONDS);
        }
    
        /**
         * 根据手机号查询是否已生成验证码
         *
         * @param phone
         * @return
         */
        private boolean checkCodeIsExpired(String phone) {
            String key = RedisKeyConstant.verify_code.getKey() + phone;
            String code = redisTemplate.opsForValue().get(key);
            return StrUtil.isBlank(code) ? true : false;
        }
    
        /**
         * 根据手机号获取验证码
         *
         * @param phone
         * @return
         */
        public String getCodeByPhone(String phone) {
            String key = RedisKeyConstant.verify_code.getKey() + phone;
            return redisTemplate.opsForValue().get(key);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    使用到的枚举类RedisKeyConstant

    @Getter
    public enum RedisKeyConstant {
    
        verify_code("verify_code:", "验证码");
    
        private String key;
        private String desc;
    
        RedisKeyConstant(String key, String desc) {
            this.key = key;
            this.desc = desc;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    发送验证码的控制层代码如下:

    /**
     * 发送验证码控制层
     * @author zjq
     */
    @RestController
    public class SendVerifyCodeController {
    
        @Resource
        private SendVerifyCodeService sendVerifyCodeService;
    
        @Resource
        private HttpServletRequest request;
    
        /**
         * 发送验证码
         *
         * @param phone
         * @return
         */
        @GetMapping("send")
        public ResultInfo send(String phone) {
            sendVerifyCodeService.send(phone);
            return ResultInfoUtil.buildSuccess("发送成功", request.getServletPath());
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    发送验证码接口,需要配置网关放放行发送验证码接口/users/send,配置如下:

    secure:
      ignore:
        urls: # 配置白名单路径
          - /actuator/**
          - /auth/oauth/**
          - /users/signin
          - /users/send
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    验证发送:
    在这里插入图片描述
    在Redis中也可以查看到该手机号发送的验证码信息:
    在这里插入图片描述
    接下来继续走用户注册流程…

    用户注册

    校验手机号是否已注册或者不是可用状态

    在mapper中新建一个通过手机号查询用户的方法:

        /**
         * 根据手机号查询用户信息
         * @param phone
         * @return
         */
        @Select("select id, username, phone, email, is_valid " +
                " from t_users where phone = #{phone}")
        Users selectByPhone(@Param("phone") String phone);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在service中创建校验手机号相关方法:

        /**
         * 校验手机号是否已注册
         */
        public void checkPhoneIsRegistered(String phone) {
            AssertUtil.isNotEmpty(phone, "手机号不能为空");
            Users diners = usersMapper.selectByPhone(phone);
            AssertUtil.isTrue(diners == null, "该手机号未注册");
            AssertUtil.isTrue(diners.getIsValid() == 0, "该用户已锁定,请先解锁");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    控制层创建相关接口,提供验证:

        /**
         * 校验手机号是否已注册
         *
         * @param phone
         * @return
         */
        @GetMapping("checkPhone")
        public ResultInfo checkPhone(String phone) {
            userService.checkPhoneIsRegistered(phone);
            return ResultInfoUtil.buildSuccess(request.getServletPath());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    网关同样需要配置放行该接口:

    secure:
      ignore:
        urls: # 配置白名单路径
          - /actuator/**
          - /auth/oauth/**
          - /users/signin
          - /users/send
          - /users/checkPhone
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    此时数据库信息如下:
    在这里插入图片描述

    测试验证:
    已存在的手机号:
    image.png
    不存在的手机号:
    image.png
    image.png
    这个异常显然不够友好,接下来我们定义全局异常配置。

    全局异常配置

    添加全局异常处理类,代码如下:

    /**
     * 全局异常处理类
     * @author zjq
     */
    @RestControllerAdvice 
    @Slf4j
    public class GlobalExceptionHandler {
    
        @Resource
        private HttpServletRequest request;
    
        @ExceptionHandler(ParameterException.class)
        public ResultInfo<Map<String, String>> handlerParameterException(ParameterException ex) {
            String path = request.getRequestURI();
            ResultInfo<Map<String, String>> resultInfo =
                    ResultInfoUtil.buildError(ex.getErrorCode(), ex.getMessage(), path);
            return resultInfo;
        }
    
        @ExceptionHandler(Exception.class)
        public ResultInfo<Map<String, String>> handlerException(Exception ex) {
            log.info("未知异常:{}", ex);
            String path = request.getRequestURI();
            ResultInfo<Map<String, String>> resultInfo =
                    ResultInfoUtil.buildError(path);
            return resultInfo;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    再次请求不存在的手机号,或者已经被锁定的手机号,返回如下:
    在这里插入图片描述

    image.png

    查看用户名是否已经注册

    在mapper中添加根据用户名查询用户:

        /**
         * 根据用户名查询用户信息
         * @param username
         * @return
         */
        @Select("select id, username, phone, email, is_valid " +
                " from t_users where username = #{username}")
        Users selectByUsername(@Param("username") String username);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    用户注册验证都通过后需要把新用户添加到数据库,mapper中添加用户新增信息:

        /**
         * 新增用户信息
         * @param userDTO
         * @return
         */
        @Insert("insert into " +
                " t_users (username, password, phone, roles, is_valid, create_date, update_date) " +
                " values (#{username}, #{password}, #{phone}, \"ROLE_USER\", 1, now(), now())")
        int saveUser(UserDTO userDTO);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    UserDTO内容如下:

    package com.imooc.commons.model.dto;
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Getter;
    import lombok.Setter;
    
    import java.io.Serializable;
    
    @Getter
    @Setter
    @ApiModel(description = "注册用户信息")
    public class DinersDTO implements Serializable {
    
        @ApiModelProperty("用户名")
        private String username;
        @ApiModelProperty("密码")
        private String password;
        @ApiModelProperty("手机号")
        private String phone;
        @ApiModelProperty("验证码")
        private String verifyCode;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    用户注册逻辑实现

    用户注册步骤如下:

    1. 参数非空校验
    2. 验证码一致性校验
    3. 验证用户名是否已注册
    4. 注册
    5. 密码加密
    6. 自动登录

    代码实现如下:

        /**
         * 用户注册
         *
         * @param userDTO
         * @param path
         * @return
         */
        public ResultInfo register(UserDTO userDTO, String path) {
            // 参数非空校验
            String username = userDTO.getUsername();
            AssertUtil.isNotEmpty(username, "请输入用户名");
            String password = userDTO.getPassword();
            AssertUtil.isNotEmpty(password, "请输入密码");
            String phone = userDTO.getPhone();
            AssertUtil.isNotEmpty(phone, "请输入手机号");
            String verifyCode = userDTO.getVerifyCode();
            AssertUtil.isNotEmpty(verifyCode, "请输入验证码");
            // 获取验证码
            String code = sendVerifyCodeService.getCodeByPhone(phone);
            // 验证是否过期
            AssertUtil.isNotEmpty(code, "验证码已过期,请重新发送");
            // 验证码一致性校验
            AssertUtil.isTrue(!userDTO.getVerifyCode().equals(code), "验证码不一致,请重新输入");
            // 验证用户名是否已注册
            Users users = usersMapper.selectByUsername(username.trim());
            AssertUtil.isTrue(users != null, "用户名已存在,请重新输入");
            // 注册
            // 密码加密
            userDTO.setPassword(DigestUtil.md5Hex(password.trim()));
            usersMapper.saveUser(userDTO);
            // 自动登录
            return signIn(username.trim(), password.trim(), path);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    控制层代码如下:

        /**
         * 注册
         *
         * @param userDTO
         * @return
         */
        @PostMapping("register")
        public ResultInfo register(@RequestBody UserDTO userDTO) {
            return userService.register(userDTO, request.getServletPath());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    验证

    新用户发起注册。
    校验手机号是否已注册:
    image.png
    发送验证码:
    在这里插入图片描述
    执行注册操作:
    可以看到验证码为 807596:
    image.png
    第一次故意等待验证码失效再执行,返回如下:
    在这里插入图片描述
    然后重新发送验证码:
    image.png
    再次输入错误验证码,返回如下:
    image.png
    输入正确的,返回了自动登录的token信息:
    在这里插入图片描述

    本文内容到此结束了,
    如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
    如有错误❌疑问💬欢迎各位指出。
    主页共饮一杯无的博客汇总👨‍💻

    保持热爱,奔赴下一场山海。🏃🏃🏃

  • 相关阅读:
    [Linux]线程同步
    第2章 JavaScript基本语法
    招聘| 嵌入式軟件(单片机)工程师
    猿创征文|基于SSM框架+java+maven+jsp的小项目
    【算法基础】:(二)希尔排序
    python+appium自动化测试如何控制App的启动和退出
    Chain-of-thought prompting(链式思考提示)
    Spring Mvc服务端验证&拦截器
    Maven进阶-继承与聚合
    为什么选择事件驱动的微服务架构?
  • 原文地址:https://blog.csdn.net/qq_35427589/article/details/127841874