SpringBoot整合邮箱验证码实现用户注册

唠嗑部分

今天我们来分享一下在系统开发过程中,如何使用验证码来验证用户并完成用户注册

首先来看一下成品界面展示

SpringBoot整合邮箱验证码实现用户注册_第1张图片

说一下以上注册功能的设计:

用户手动输入用户名(全数据库唯一)、密码、确认密码、邮箱地址(单个邮箱最多可注册3个用户)、正确的邮箱验证码,即可注册

先来展示一下

输入用户信息

SpringBoot整合邮箱验证码实现用户注册_第2张图片

收到邮箱验证码

SpringBoot整合邮箱验证码实现用户注册_第3张图片

注册成功

言归正传

使用验证码这种方式呢是比较常见的,我们在注册App的时候,会有手机验证码、邮箱的类似哈(发邮件是免费的),邮箱验证也能够保证其真实性,防止恶意用户非法注册

下面我们就来说一下,这一系列的实现思路

1、用户名唯一验证就省略了哈,不在此范围内

2、在用户填入信息时前端先做必传、格式验证。

3、邮箱验证通过后,点击发送验证码时,携带邮箱参数请求后端接口。

4、后端生成并发送验证码后,将验证码进行分布式存储,如存到redis,key为邮箱,value为验证码,失效时间设置3分钟。

5、用户在3分钟内收到验证码并填入注册表单,请求用户注册接口。

6、后端在收到注册请求后,首先验证参数的合法性,验证通过后,根据邮箱去redis查询验证码。

7、未查询到验证码,则说明验证码已经过期,返回验证码校验失败,查询到验证码后,与表单中的验证码进行比较,相同则继续注册逻辑,不同则返回验证码校验失败。

代码环节

1、Vue组件






2、api

// 发送验证码
export function sendEmailCode(data = {}) {
  return request({
    url: URL_PREFIX + '/main/user/sendEmailCode',
    method: 'post',
    data
  })
}
// 注册
export function register(data = {}) {
  return request({
    url: URL_PREFIX + '/main/user/register',
    method: 'post',
    data
  })
}

3、服务端发送验证码

@PostMapping("/user/sendEmailCode")
@ApiOperation("发送验证码处理器")
public BaseResult sendEmailCode(@RequestBody @Validated SendEmailCodeDTO dto, HttpServletRequest request){
    BaseResult result = BaseResult.ok();
    baseService.sendEmailCode(dto, request, result);
    return result;
}

实现类

@Override
public void sendEmailCode(SendEmailCodeDTO dto, HttpServletRequest request, BaseResult result) {
    long startTime = System.currentTimeMillis();
    try {
        // 验证码发送频率控制验证
        String string = redisUtil.getString(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE_TIME, dto.getEmail()));
        if (StringUtils.hasLength(string)) {
            result.setCode(CurrencyErrorEnum.OPERA_ERROR.getCode());
            result.setMsg(CurrencyErrorEnum.OPERA_ERROR.getMsg() + ",验证码发送过于频繁,请稍后再试");
        } else {
            // 生成6位数验证码
            String code = StringUtil.generatorCode(6);
            // 将验证码存入redis,有效期3分钟
            redisUtil.set(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE, dto.getEmail()), code, 3L, TimeUnit.MINUTES);
            // 调用mail发送邮件
            sendMailUtil.sendMail(dto.getEmail(), "邮箱验证码", sendMailUtil.buildCodeContent(code));
            // 验证码发送频率控制
            redisUtil.set(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE_TIME, dto.getEmail()), dto.getEmail(), 30L, TimeUnit.SECONDS);
        }
    } catch (Exception e) {
        log.error("邮箱验证码发送失败,{}", e);
        result.setCode(CurrencyErrorEnum.OPERA_ERROR.getCode());
        result.setMsg(CurrencyErrorEnum.OPERA_ERROR.getMsg());
    } finally {
        long endTime = System.currentTimeMillis();
        log.info("【{}】【邮箱验证码发送接口】【{}ms】 \n入参:{}\n出参:{}", "发送验证码", endTime - startTime, dto, result);
    }
}

4、服务端用户注册接口

@PostMapping("/user/register")
@ApiOperation("新用户注册处理器")
public BaseResult register(@RequestBody @Validated UserRegirsterDTO dto, HttpServletRequest request){
    BaseResult result = BaseResult.ok();
    userService.register(dto, request, result);
    return result;
}

实现类,非必要代码就省略了哈

@Override
@Transactional
public void register(UserRegirsterDTO dto, HttpServletRequest request, BaseResult result) {
    long startTime = System.currentTimeMillis();
    try {
        // 因为传输过程中对密码进行加密了,先解密在验证长度
        String password = RSAUtil.decrypt(dto.getPassword(), commonConfig.getRsaPrivateKey());
        if (password.length() > 16) {
            result.setCode(CurrencyErrorEnum.REQUEST_PARAMETER_ERROR.getCode());
            result.setMsg(CurrencyErrorEnum.REQUEST_PARAMETER_ERROR.getMsg() + ",用户密码最大16个字符");
            return;
        }
        //  去redis获取邮箱验证码
        String sysCode = redisUtil.getString(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE, dto.getEmail()));
        // 验证码存在且与参数传递过来的相等
        if (StringUtils.hasLength(sysCode) && sysCode.equals(dto.getCode())) {
            // 清楚这个验证码
            redisUtil.removeKey(redisUtil.getCacheKey(CachePrefixContent.PUBLIC_EMAIL_CODE, dto.getEmail()));
            // 邮箱绑定账号数验证 ...
            // 用户名唯一验证 ...
            // 用户注册
            User user = new User();
            BeanUtils.copyProperties(dto, user);
            user.setCreateTime(LocalDateTime.now());
            user.setUserId(IdUtil.simpleUUID());
            user.setPassword(passwordEncoder.encode(password));
            int insert = userMapper.insert(user);
            // 用户注册后的一系列权限分配,初始化...
        } else {
            // 未查询到验证码,返回验证码校验失败
            result.setCode(CurrencyErrorEnum.OPERA_ERROR.getCode());
            result.setMsg(CurrencyErrorEnum.OPERA_ERROR.getMsg() + ",验证码校验失败");
        }
    } catch (Exception e) {
        log.error("新用户注册失败,{}", e);
        result.setCode(CurrencyErrorEnum.OPERA_ERROR.getCode());
        result.setMsg(CurrencyErrorEnum.OPERA_ERROR.getMsg());
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    } finally {
        long endTime = System.currentTimeMillis();
        log.info("【{}】【新用户注册接口】【{}ms】 \n入参:{}\n出参:{}", "新增", endTime - startTime, JSON.toJSONString(dto), result);
    }
}
结语

今天这个案例就分享到这,总结一下

1、在发送请求时,作为前端需根据需求严格的对参数进行较验,无误后发送请求。

2、作为后端来讲,接口设计应极为严格,需自行参数验证,不能相信前端,因为很有可能,设计的接口会脱离浏览器被访问。

3、此案例代码较多,部分不相关的代码省略了,请周知。

4、制作不易,一键三连再走吧,您的支持永远是我最大的动力!

你可能感兴趣的:(Java全栈开发,SpringBoot,spring,boot,java,vue.js)