文章目录

    • 发送验证码
    • 用户注册
      • 校验手机号是否已注册或者不是可用状态
      • 全局异常配置
      • 查看用户名是否已经注册
      • 用户注册逻辑实现
      • 验证

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

发送验证码

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 */@Configurationpublic class RedisTemplateConfiguration {/** * redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类 * @param redisConnectionFactory * @return */@Beanpublic 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

代码实现如下:

/** * 发送验证码业务逻辑层 * @author zjq */@Servicepublic class SendVerifyCodeService {@Resourceprivate 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,失效时间 60sString 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

@Getterpublic enum RedisKeyConstant {verify_code("verify_code:", "验证码");private String key;private String desc;RedisKeyConstant(String key, String desc) {this.key = key;this.desc = desc;}}

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

/** * 发送验证码控制层 * @author zjq */@RestControllerpublic class SendVerifyCodeController {@Resourceprivate SendVerifyCodeService sendVerifyCodeService;@Resourceprivate HttpServletRequest request;/** * 发送验证码 * * @param phone * @return */@GetMapping("send")public ResultInfo send(String phone) {sendVerifyCodeService.send(phone);return ResultInfoUtil.buildSuccess("发送成功", request.getServletPath());}}

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

secure:ignore:urls: # 配置白名单路径- /actuator/**- /auth/oauth/**- /users/signin- /users/send

验证发送:

在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);

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

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

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

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

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

secure:ignore:urls: # 配置白名单路径- /actuator/**- /auth/oauth/**- /users/signin- /users/send- /users/checkPhone

此时数据库信息如下:

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

不存在的手机号:


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

全局异常配置

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

/** * 全局异常处理类 * @author zjq */@RestControllerAdvice @Slf4jpublic class GlobalExceptionHandler {@Resourceprivate 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;}}

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

查看用户名是否已经注册

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

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

用户注册验证都通过后需要把新用户添加到数据库,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);

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. 自动登录

代码实现如下:

/** * 用户注册 * * @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);}

控制层代码如下:

/** * 注册 * * @param userDTO * @return */@PostMapping("register")public ResultInfo register(@RequestBody UserDTO userDTO) {return userService.register(userDTO, request.getServletPath());}

验证

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

发送验证码:

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

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

然后重新发送验证码:

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

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

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

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