前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用户微服务用户注册功能实现

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

作者头像
共饮一杯无
发布2022-11-28 18:48:31
1.3K0
发布2022-11-28 18:48:31
举报
文章被收录于专栏:Java升级打怪进阶之路

文章目录

用户注册之前需要先给注册的手机号发送一条验证码,我们把验证码存储在Redis中。 发送的时候我们先把验证码存储到Redis,然后用户发起注册的时候取出验证。

在这里插入图片描述
在这里插入图片描述
image.png
image.png

发送验证码

Redis配置如下:

代码语言:javascript
复制
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;
    }

}

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

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

代码实现如下:

代码语言:javascript
复制
/**
 * 发送验证码业务逻辑层
 * @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);
    }

}

使用到的枚举类RedisKeyConstant

代码语言:javascript
复制
@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;
    }

}

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

代码语言:javascript
复制
/**
 * 发送验证码控制层
 * @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());
    }

}

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

代码语言:javascript
复制
secure:
  ignore:
    urls: # 配置白名单路径
      - /actuator/**
      - /auth/oauth/**
      - /users/signin
      - /users/send

验证发送:

在这里插入图片描述
在这里插入图片描述

在Redis中也可以查看到该手机号发送的验证码信息:

在这里插入图片描述
在这里插入图片描述

接下来继续走用户注册流程…

用户注册

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

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

代码语言:javascript
复制
    /**
     * 根据手机号查询用户信息
     * @param phone
     * @return
     */
    @Select("select id, username, phone, email, is_valid " +
            " from t_users where phone = #{phone}")
    Users selectByPhone(@Param("phone") String phone);

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

代码语言:javascript
复制
    /**
     * 校验手机号是否已注册
     */
    public void checkPhoneIsRegistered(String phone) {
        AssertUtil.isNotEmpty(phone, "手机号不能为空");
        Users diners = usersMapper.selectByPhone(phone);
        AssertUtil.isTrue(diners == null, "该手机号未注册");
        AssertUtil.isTrue(diners.getIsValid() == 0, "该用户已锁定,请先解锁");
    }

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

代码语言:javascript
复制
    /**
     * 校验手机号是否已注册
     *
     * @param phone
     * @return
     */
    @GetMapping("checkPhone")
    public ResultInfo checkPhone(String phone) {
        userService.checkPhoneIsRegistered(phone);
        return ResultInfoUtil.buildSuccess(request.getServletPath());
    }

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

代码语言:javascript
复制
secure:
  ignore:
    urls: # 配置白名单路径
      - /actuator/**
      - /auth/oauth/**
      - /users/signin
      - /users/send
      - /users/checkPhone

此时数据库信息如下:

在这里插入图片描述
在这里插入图片描述

测试验证: 已存在的手机号:

image.png
image.png

不存在的手机号:

image.png
image.png
image.png
image.png

这个异常显然不够友好,接下来我们定义全局异常配置。

全局异常配置

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

代码语言:javascript
复制
/**
 * 全局异常处理类
 * @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;
    }

}

再次请求不存在的手机号,或者已经被锁定的手机号,返回如下:

在这里插入图片描述
在这里插入图片描述
image.png
image.png

查看用户名是否已经注册

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

代码语言:javascript
复制
    /**
     * 根据用户名查询用户信息
     * @param username
     * @return
     */
    @Select("select id, username, phone, email, is_valid " +
            " from t_users where username = #{username}")
    Users selectByUsername(@Param("username") String username);

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

代码语言:javascript
复制
    /**
     * 新增用户信息
     * @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);

UserDTO内容如下:

代码语言:javascript
复制
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. 自动登录

代码实现如下:

代码语言:javascript
复制
    /**
     * 用户注册
     *
     * @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);
    }

控制层代码如下:

代码语言:javascript
复制
    /**
     * 注册
     *
     * @param userDTO
     * @return
     */
    @PostMapping("register")
    public ResultInfo register(@RequestBody UserDTO userDTO) {
        return userService.register(userDTO, request.getServletPath());
    }

验证

新用户发起注册。 校验手机号是否已注册:

image.png
image.png

发送验证码:

在这里插入图片描述
在这里插入图片描述

执行注册操作: 可以看到验证码为 807596:

image.png
image.png

第一次故意等待验证码失效再执行,返回如下:

在这里插入图片描述
在这里插入图片描述

然后重新发送验证码:

image.png
image.png

再次输入错误验证码,返回如下:

image.png
image.png

输入正确的,返回了自动登录的token信息:

在这里插入图片描述
在这里插入图片描述

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 发送验证码
  • 用户注册
    • 校验手机号是否已注册或者不是可用状态
      • 全局异常配置
        • 查看用户名是否已经注册
          • 用户注册逻辑实现
            • 验证
            相关产品与服务
            验证码
            腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档