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

唠嗑部分

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

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

image-20230605171952976

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

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

先来展示一下

输入用户信息

image-20230605172346155

收到邮箱验证码

image-20230605172320988

注册成功

言归正传

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

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

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

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

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

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

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

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

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

代码环节

1、Vue组件

<template>
  <div class="header-all">
      <!--...-->
    <el-dialog :visible.sync="loginFlag" width="500px" :close-on-click-modal="false" :show-close="true">
      <!--登录表单省略...-->
      <el-form ref="registerForm" v-show="!login" :model="registerInfo" :rules="registerRules" class="register-form"
               autocomplete="on"
               label-position="left">
        <div class="title-container">
          <h3 class="title" style="text-align: center;font-size: 20px;margin-bottom: 15px;">
            {{ sysInfo.sysTitle }}注册</h3>
        </div>

        <el-form-item prop="userName">
          <el-input
              prefix-icon="el-icon-user"
              ref="username"
              v-model="registerInfo.userName"
              placeholder="请输入用户名"
              name="userName"
              type="text"
              tabindex="1"
              autocomplete="on"
          />
        </el-form-item>

        <el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
          <el-form-item prop="password">
            <el-input
                show-password
                prefix-icon="el-icon-lock"
                ref="password"
                v-model="registerInfo.password"
                placeholder="请输入账户密码"
                name="password"
                tabindex="2"
                autocomplete="on"
                @blur="capsTooltip = false"
            />
            <span class="show-pwd">
          </span>
          </el-form-item>
        </el-tooltip>

        <el-tooltip v-model="capsTooltip" content="Caps lock is On" placement="right" manual>
          <el-form-item prop="password2">
            <el-input
                show-password
                prefix-icon="el-icon-lock"
                ref="password2"
                v-model="registerInfo.password2"
                placeholder="请确认密码"
                name="password2"
                tabindex="2"
                autocomplete="on"
                @blur="capsTooltip = false"
            />
            <span class="show-pwd">
          </span>
          </el-form-item>
        </el-tooltip>

        <el-form-item prop="email">
          <el-input
              prefix-icon="el-icon-message"
              ref="email"
              v-model="registerInfo.email"
              placeholder="请输入邮箱地址,每个邮箱最多可绑定3个账号"
              name="email"
              type="text"
              tabindex="1"
              autocomplete="on"
          />
        </el-form-item>

        <el-form-item prop="code">
          <el-row :gutter="20">
            <el-col :span="12">
              <el-input
                  ref="code"
                  type="text"
                  prefix-icon="el-icon-key"
                  v-model="registerInfo.code"
                  placeholder="请输入邮箱验证码"
                  name="code"
                  tabindex="2"
                  autocomplete="on"
                  @keyup.enter.native="registerHandle"
              />
            </el-col>
            <el-col :span="6" :offset="3">
              <el-button type="primary" size="small" :disabled="pause" @click="sendEmailCode">获取验证码 <span v-if="pause">{{ time }}</span>
              </el-button>
            </el-col>
          </el-row>
        </el-form-item>

        <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;"
                   @click.native.prevent="registerHandle">注册
        </el-button>

        <div style="text-align: right">
          <el-link type="primary" :underline="false" @click.native="nativeToLogin">已有账号,去登录>></el-link>
        </div>
      </el-form>
    </el-dialog>

    <el-dialog
        title="新用户提示"
        :visible.sync="dialogVisible"
        width="30%">
      <span>系统检测到您是新用户,请及时更新信息</span>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="toUpdateSelfInfo">去更新</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
<!--引入...-->
const defaultRegisterInfo = {
  userName: '',
  password: '',
  password2: '',
  email: '',
  code: ''
}
export default {
  <!--...-->
  data() {
    var checkUserName = (rule, value, callback) => {
      const pattern = /^[A-Za-z0-9-_]+$/;
      if (!value) {
        return callback(new Error('登录名不能为空'));
      }
      if (pattern.test(value) && value.length <= 16) {
        checkUserNameExist({userName: value}).then(res => {
          if (res.data) {
            callback(new Error('登录名已存在'));
          } else {
            callback();
          }
        })
      } else {
        callback(new Error('登录名须由数字、英文字母、-、下划线(不包含、)组成,不大于16位'));
      }
    };
    var validatePass = (rule, value, callback) => {
      if (value === '') {
        callback(new Error('请输入密码'));
      } else {
        if (this.registerInfo.password !== '') {
          this.$refs.registerForm.validateField('password2');
        }
        callback();
      }
    };
    var validatePass2 = (rule, value, callback) => {
      if (value === '') {
        callback(new Error('请再次输入密码'));
      } else if (value !== this.registerInfo.password) {
        callback(new Error('两次输入密码不一致!'));
      } else {
        callback();
      }
    };
    var validateEmail = (rule, value, callback) => {
      const pattern = /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/;
      if (value && value !== '') {
        if (pattern.test(value)) {
          checkEmailBindStatus({email: value}).then(res => {
            if (res.data) {
              callback();
            } else {
              callback(new Error('当前邮箱绑定用户已达到上限'));
            }
          })
        } else {
          callback(new Error('邮箱格式不正确'));
        }
      } else {
        callback(new Error('邮箱为必传项'));
      }
    };
    return {
      defaultUserImg: defaultUserImg,
      refresh: true,
      baseUrl: URL_PREFIX,
      codeUrl: URL_PREFIX + '/auth/getCaptcha',
      rules: {
        username: [{required: true, trigger: 'blur', message: '用户名为必填项'}],
        password: [{required: true, trigger: 'blur', message: '密码为必填项'}],
        code: [{required: true, trigger: 'blur', message: '验证码为必填项'}]
      },
      loading: false,
      registerInfo: {
        userName: '',
        password: '',
        password2: '',
        email: '',
        code: ''
      },
      time: 30,
      pause: false,
      registerRules: {
        userName: [{validator: checkUserName, trigger: 'blur'}],
        password: [{validator: validatePass, trigger: 'blur'}],
        password2: [{validator: validatePass2, trigger: 'blur'}],
        email: [{validator: validateEmail, trigger: 'blur'}],
        code: [{required: true, trigger: 'blur', message: '验证码为必填项'}]
      },
      dialogVisible: false
    }
  },
  methods: {
    // 用户注册
    registerHandle() {
      this.$refs.registerForm.validate(valid => {
        if (valid) {
          this.loading = true
          const registerInfo = {
              ...this.registerInfo,
              password: this.$encruption(this.registerInfo.password), 
              password2: this.$encruption(this.registerInfo.password2)
          }
          register(registerInfo).then(res => {
            this.$message.success('用户注册成功')
            this.loading = false
            this.registerInfo = defaultRegisterInfo
            this.$refs.registerForm.resetFields()
            this.nativeToLogin()
          })
        } else {
          console.log('error submit!!')
          return false
        }
        setTimeout(() => {
          this.loading = false
        }, 2000)
      })
    },
    // 发送验证码
    sendEmailCode() {
      this.$refs.registerForm.validateField('email', valid => {
        if (!valid) {
          if (this.pause) {
            this.$message.error('操作太频繁,请稍后再试')
          } else {
            sendEmailCode({email: this.registerInfo.email}).then(res => {
              this.$message.success('验证码发送成功,请注意查收!')
              this.pause = true
              const timer = setInterval(() => {
                if (this.time > 0) {
                  this.time = this.time - 1;
                }
                if (this.time <= 0) {
                  this.pause = false;
                  clearInterval(timer)
                  this.time = 30
                }
              }, 1000)
            })
          }
        }
      });
    }
  }
}
</script>

<style scoped lang="less">
   <!--css省略-->
</style>

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、制作不易,一键三连再走吧,您的支持永远是我最大的动力!

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要在Spring Boot中集成邮箱验证码功能,你可以按照以下步骤进行操作: 1. 在项目的pom.xml文件中添加mail模块的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> ``` 这样就可以使用Spring Boot提供的邮件功能了。\[1\] 2. 在配置文件(application.properties或application.yml)中填写相关的邮箱配置,例如使用163邮箱: ```properties # Mail spring.mail.host=smtp.163.com spring.mail.username=your-email@163.com spring.mail.password=your-password spring.mail.default-encoding=UTF-8 mail.from=your-email@163.com ``` 其中,`spring.mail.host`是SMTP服务器地址,`spring.mail.username`和`mail.from`是你的邮箱地址,`spring.mail.password`是你的邮箱授权码(不是邮箱密码)。你需要在相关邮箱设置中开启SMTP服务,并获取授权码。\[2\]\[3\] 3. 在你的代码中使用JavaMailSender发送邮件,可以通过注入`JavaMailSender`对象来实现: ```java @Autowired private JavaMailSender javaMailSender; public void sendVerificationCode(String email, String code) { SimpleMailMessage message = new SimpleMailMessage(); message.setTo(email); message.setSubject("验证码"); message.setText("您的验证码是:" + code); javaMailSender.send(message); } ``` 以上代码示例中,`sendVerificationCode`方法用于发送验证码邮件,其中`email`是收件人邮箱地址,`code`是验证码内容。你可以根据实际需求自定义邮件的主题和内容。 这样,你就可以在Spring Boot中集成邮箱验证码功能了。记得替换相关配置为你自己的邮箱信息。 #### 引用[.reference_title] - *1* *2* *3* [Spring Boot 整合163或者qq邮箱发送验证码](https://blog.csdn.net/hghjgkjn/article/details/125952509)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈小白.

感谢老板,祝老板今年发大财!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值