Spring Boot+Vue 前后端分离实战:微光聊天室项目(注册功能实现)


崧峻
原创
发布时间: 2026-01-08 14:58:29 | 阅读数 0收藏数 0评论数 0
封面
本篇文章基于 Spring Boot + Vue 前后端分离 项目「微光聊天室」,重点讲解用户注册功能的具体实现过程。文章将围绕注册业务展开,详细说明前端注册页面的表单设计与交互校验逻辑,以及前后端在注册流程中的数据传递与接口调用方式。在后端部分,将介绍注册接口的实现细节,包括参数校验、验证码校验、密码加密存储以及异常场景的处理思路,确保注册流程的安全性与稳定性。通过结合真实业务场景和完整示例代码,帮助读者深入理解注册功能在实际项目中的实现方式,为后续用户相关功能的开发提供可复用的实践经验,适合希望提升项目实战能力的初学者阅读。
1

前言

上一期我们做好了整个项目的基础架构搭建和登录鉴权功能如图所示,但是既然有了登录功能 那么我们必不可少的也是注册功能,现在我们来写注册功能,想要源码的直接去最后一步自取

2

创建注册实体

首先我们创建一个用于接收前端传输过来的实体类password因为我们需要加密所以这里就只判断是否为空即可 内容如下



package com.glow.login.dto;

import com.glow.login.constant.AuthRegex;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;

/**
* @author gjq
* @version 1.0
* @description: 用户注册请求DTO 用于接收前端提交的注册信息
* @date 2025/12/25 11:11
*/
@Data
public class EmailRegisterDTO {

/**
* 用户名
*/
@NotBlank(message = "用户名不能为空")
@Pattern(regexp = AuthRegex.USERNAME_REGEX, message = "用户名长度需在 4~20 位之间的数字或字母")
private String username;

/**
* 邮箱
*/
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;

/**
* 验证码
*/
@NotBlank(message = "验证码不能为空")
@Pattern(regexp = "\\d{6}", message = "请输入六位数的验证码")
private String code;

/**
* 密码
*/
@NotBlank(message = "密码不能为空")
private String password;

}


3

修改yml

如图所示新建文件夹填写一下内容


captcha:
# 验证码重新发送间隔时长
resend-interval-seconds: 60
# 验证码有效时长 单位s
expire-time: 300


package com.glow.captcha.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
* @author gjq
* @version 1.0
* @description: 邮件验证码相关配置类
* @date 2025/12/24 17:22
*/
@Data
@Component
@ConfigurationProperties(prefix = "glow.captcha")
public class CaptchaProperties {

/**
* 过期时间 单位秒 s
*/
private Long expireTime;

/**
* 重新发送的间隔时长
*/
private Long resendIntervalSeconds;
}


4

编写邮箱发送验证码

如图所示在验证码模块里面新建一个 EmailCaptchaService和他的实现 EmailCaptchaServiceImpl 然后内容如下 这里面我们编写了发送注册验证码和校验验证码的方法

package com.glow.captcha.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.glow.captcha.config.CaptchaProperties;
import com.glow.captcha.constant.CaptchaConstant;
import com.glow.captcha.domain.enums.EmailCaptchaEnum;
import com.glow.captcha.dto.CaptchaMailDTO;
import com.glow.captcha.service.EmailCaptchaService;
import com.glow.captcha.utils.EmailUtils;
import com.glow.captcha.utils.generator.CaptchaGenerator;
import com.glow.common.utils.RedisUtils;
import com.glow.common.utils.exception.BusinessException;
import com.glow.user.domain.UserInfo;
import com.glow.user.service.UserInfoService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.Objects;

/**
* @author gjq
* @version 1.0
* @description: 邮箱验证码相关逻辑接口实现
* @date 2025/12/24 14:34
*/
@Service
@RequiredArgsConstructor
public class EmailCaptchaServiceImpl implements EmailCaptchaService {

private final RedisUtils redisUtils;

private final EmailUtils emailUtils;

private final CaptchaProperties captchaProperties;

private final UserInfoService userInfoService;

/**
* 发送注册时候的邮箱验证码
*
* @param email 邮箱号
*/
@Override
public void sendRegisterEmailCaptcha(String email) {
// 查询邮箱是否存在
LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(UserInfo::getEmail, email);
boolean exists = userInfoService.exists(lambdaQueryWrapper);
// 判断是否存在
if (exists) {
throw new BusinessException("该邮箱已被注册,请更换邮箱。");
}
// 获取剩余的过期时间
Long expire = redisUtils.getExpire(CaptchaConstant.REGISTER_EMAIL_CAPTCHA_KEY + email);
// 判断是否频繁发送
if (captchaProperties.getExpireTime() - expire < captchaProperties.getResendIntervalSeconds()) {
throw new BusinessException("请勿频繁发送验证码");
}
// 获取验证码
String code = CaptchaGenerator.generateCode();
// 声明参数实体
CaptchaMailDTO mailDTO = new CaptchaMailDTO(code, EmailCaptchaEnum.REGISTER.getDesc());
// 发送邮件
emailUtils.sendCaptchaThymeleafEmail(email, mailDTO, EmailCaptchaEnum.REGISTER.getSubject());
// 存储到redis中
redisUtils.set(CaptchaConstant.REGISTER_EMAIL_CAPTCHA_KEY + email, code, captchaProperties.getExpireTime());
}

/**
* 校验验证码是否正确
*
* @param email 邮箱
* @param code 验证码
* @param key redis key
*/
@Override
public void verifyEmailCaptcha(String email, String code, String key) {
// 获取验证码
String captcha = (String) redisUtils.get(key + email);
// 判断是否相等
if (Objects.equals(captcha, code)) {
// 删除redis中的值
redisUtils.del(key + email);
} else {
// 抛出异常
throw new BusinessException("验证码错误或已过期");
}
}

/**
* 发送更改邮箱时候的邮箱验证码
*
* @param email 邮箱号
*/
@Override
public void sendChangeEmailCaptcha(String email) {
// 查询邮箱是否存在
LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(UserInfo::getEmail, email);
boolean exists = userInfoService.exists(lambdaQueryWrapper);
// 判断是否存在
if (!exists) {
throw new BusinessException("该邮箱未被注册,请先前往注册");
}
// 获取剩余的过期时间
Long expire = redisUtils.getExpire(CaptchaConstant.CHANGE_EMAIL_CAPTCHA_KEY + email);
// 判断是否频繁发送
if (captchaProperties.getExpireTime() - expire < captchaProperties.getResendIntervalSeconds()) {
throw new BusinessException("请勿频繁发送验证码");
}
// 获取验证码
String code = CaptchaGenerator.generateCode();
// 声明参数实体
CaptchaMailDTO mailDTO = new CaptchaMailDTO(code, EmailCaptchaEnum.CHANGE_EMAIL.getDesc());
// 发送邮件
emailUtils.sendCaptchaThymeleafEmail(email, mailDTO, EmailCaptchaEnum.CHANGE_EMAIL.getSubject());
// 存储到redis中
redisUtils.set(CaptchaConstant.CHANGE_EMAIL_CAPTCHA_KEY + email, code, captchaProperties.getExpireTime());
}
}


5

编写注册service

  1. 我们先判断验证码是否正确 然后判断该用户名是否已经存在了 如果已经存在或者验证码不正确我们就抛出相关异常
  2. 然后我们把加密的密码进行rsa解密 再通过security加密 查询相关的ip 客户端信息等 存储到实体类中
  3. 添加到数据库
  4. 注册的service内容如下

package com.glow.login.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.glow.captcha.constant.CaptchaConstant;
import com.glow.captcha.service.EmailCaptchaService;
import com.glow.common.utils.RsaUtils;
import com.glow.common.utils.exception.BusinessException;
import com.glow.common.utils.ip.AddressUtils;
import com.glow.common.utils.ip.IpUtils;
import com.glow.common.utils.security.SecurityUtil;
import com.glow.login.config.AuthProperties;
import com.glow.login.dto.EmailRegisterDTO;
import com.glow.login.service.RegisterService;
import com.glow.user.domain.UserInfo;
import com.glow.user.mapper.UserInfoMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

/**
* @author gjq
* @version 1.0
* @description: 注册功能相关接口实现
* @date 2025/12/25 15:04
*/
@Service
@RequiredArgsConstructor
public class RegisterServiceImpl implements RegisterService {

private final EmailCaptchaService emailCaptchaService;

private final UserInfoMapper userInfoMapper;

private final AddressUtils addressUtils;

private final AuthProperties authProperties;

/**
* 注册用户根据邮箱
*
* @param emailRegisterDTO 邮箱注册dto
* @return 是否注册成功
*/
@Override
public Boolean registerUserByEmail(EmailRegisterDTO emailRegisterDTO) {
// 校验验证码是否正确,错误会抛异常
emailCaptchaService.verifyEmailCaptcha(emailRegisterDTO.getEmail(),
emailRegisterDTO.getCode(), CaptchaConstant.REGISTER_EMAIL_CAPTCHA_KEY);
// 查询当前账号名是否已经被注册过
LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(UserInfo::getUsername, emailRegisterDTO.getUsername());
boolean exists = userInfoMapper.exists(lambdaQueryWrapper);
// 判断是否被注册
if (exists) {
// 被注册了就抛出异常
throw new BusinessException("该账号已经被注册");
}
// 密码解密
String password = RsaUtils.decrypt(emailRegisterDTO.getPassword(), authProperties.getRsaPrivateKey());
// 设置用户信息
UserInfo userInfo = new UserInfo();
// 设置账号
userInfo.setUsername(emailRegisterDTO.getUsername());
// 设置邮箱
userInfo.setEmail(emailRegisterDTO.getEmail());
// 获取ip
String ip = IpUtils.getIpAddr();
// 设置ip
userInfo.setIp(ip);
// 获取位置
String address = addressUtils.getProvince(ip);
// 设置位置
userInfo.setLocation(address);
// 加密并设置密码
userInfo.setPassword(SecurityUtil.encryptPassword(password));
// 设置密码
userInfo.setNickname(emailRegisterDTO.getUsername());
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
// 设置创建时间
userInfo.setCreateTime(now);
// 设置修改时间
userInfo.setUpdateTime(now);
// 新增用户信息
int line = userInfoMapper.insert(userInfo);
return line > 0;
}
}


6

编写注册controller

  1. 如图所示新建一个RegisterController
  2. controller层有两个接口 一个是发送注册用的邮箱验证码 一个是用于接收注册实体进行注册的具体操作的内容如下,
package com.glow.controller.auth;

import com.glow.captcha.service.EmailCaptchaService;
import com.glow.common.domain.ApiResponse;
import com.glow.login.dto.EmailRegisterDTO;
import com.glow.login.service.RegisterService;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

/**
* @author gjq
* @version 1.0
* @description: 账号相关接口
* @date 2025/12/23 16:59
*/
@Valid
@RestController
@RequestMapping("/register/")
@RequiredArgsConstructor
public class RegisterController {

private final EmailCaptchaService emailCaptchaService;

private final RegisterService registerService;

/**
* 发送邮箱验证码
*
* @return 发送成功
*/
@PostMapping("sendEmailCaptcha")
public ApiResponse sendEmailCaptcha(@RequestParam("email") @Email String email) {
emailCaptchaService.sendRegisterEmailCaptcha(email);
return ApiResponse.success("验证码发送成功");
}

/**
* 根据邮箱注册账号
*
* @return 注册结果
*/
@PostMapping("registerAccountByEmail")
public ApiResponse registerAccountByEmail(@RequestBody @Valid EmailRegisterDTO registerDTO) {
// 注册账号
Boolean result = registerService.registerUserByEmail(registerDTO);
return result ? ApiResponse.success("注册成功") : ApiResponse.error("注册失败,请稍后重试");

}


}


7

编写前端请求API方法

如图所示在api/auth 下面新建一个 register.ts 然后编写接口方法 内容如下


import request from "@/utils/request.ts";
import type {RegisterDto} from "@/types/auth/register.ts";

/**
* 发送注册邮箱验证码
* @param email
*/
export function sendEmailRegisterCaptcha(email: string) {
return request({
url: '/register/sendEmailCaptcha',
method: 'post',
params: {email}
})
}

/**
* 根据邮箱注册账号
* @param registerDto 注册账号实体类
*/
export function registerAccountByEmail(registerDto: RegisterDto) {
return request({
url: '/register/registerAccountByEmail',
method: 'post',
data: registerDto
})
}


8

注册页面编写

如图所示在views下面新建一个文件夹叫register 然后在这个下面建立一个index.vue文件 当然你也可以直接叫 register.vue 根据你的个人习惯即可


页面内容如下


<script setup lang="ts">
import GlassInput from "@/components/glass/GlassInput.vue";
import GlassDivider from "@/components/glass/GlassDivider.vue";
import GlassBtn from "@/components/glass/GlassBtn.vue";
import {registerAccountByEmail, sendEmailRegisterCaptcha} from "@/api/auth/register.ts";
import type {RegisterDto} from "@/types/auth/register.ts";
import {reactive, ref} from "vue";
import {
captchaErrorIfInvalid,
emailErrorIfInvalid,
passwordErrorIfInvalid,
usernameErrorIfInvalid
} from "@/utils/validators/validate.ts";
import notification from "@/components/notification/notification.ts";
import {rsaEncryption} from "@/utils/rsaJsencrypt.ts";

// 注册表单实体
let registerForm: RegisterDto = reactive({username: "", email: "", code: "", password: ""})

// 输入的密码
const password = ref("")

// 输入的确认密码
const confirmPass = ref("")

// 错误提示
let errorMsg = reactive({username: "", email: "", code: "", password: "", confirmPass: ""})

/**
* 注册账号
*/
const registerAccount = () => {
// 校验格式是否正确
if (verifyRegisterForm()) {
// 注册表单密码设置
registerForm.password = rsaEncryption(password.value);
// 根据邮箱注册账号
registerAccountByEmail(registerForm).then(response => {
if (response.code == 200) {
notification.success({title: "注册成功!", message: '请前往登录'});
}
})
}
}

/**
* 校验注册表单的数据
*/
const verifyRegisterForm = (): boolean => {
// 设置邮箱异常提示
errorMsg.email = emailErrorIfInvalid(registerForm.email)
// 设置账号异常提示
errorMsg.username = usernameErrorIfInvalid(registerForm.username)
// 设置验证码异常提示
errorMsg.code = captchaErrorIfInvalid(registerForm.code)
// 设置密码异常提示
errorMsg.password = passwordErrorIfInvalid(password.value)
// 判断两次密码是否一致
if (password.value != confirmPass.value) {
errorMsg.confirmPass = "两次输入的密码不一致"
} else {
errorMsg.confirmPass = ""
}
// 判断是否有异常消息
return !(errorMsg.email || errorMsg.username || errorMsg.password || errorMsg.confirmPass || errorMsg.code);
}


// 验证码时间
const codeTime = ref(0);

// 计时器对象
let timer: undefined | number = undefined

// 开启验证码倒计时
const startCodeTimer = () => {
codeTime.value = 60
if (codeTime) clearInterval(timer)
timer = setInterval(() => {
codeTime.value--
if (codeTime.value <= 0 && codeTime) {
clearInterval(timer)
timer = undefined
}
}, 1000)
}


// 验证码加载状态
const captchaLoading = ref(false);

/**
* 发送验证码
*/
const sendCaptcha = () => {
// 获取校验邮箱的异常提示
let message = emailErrorIfInvalid(registerForm.email);
// 赋值提示
errorMsg.email = message
// 判断是否有错误提示
if (message) {
return false;
}
// 开启加载状态
captchaLoading.value = true
// 发送邮箱验证码
sendEmailRegisterCaptcha(registerForm.email).then(response => {
if (response.code == 200) {
startCodeTimer()
}
// 关闭加载状态
captchaLoading.value = false;
})
}

</script>

<template>
<div class="auth-box">
<div class="auth-form-container glass-card">
<div class="auth-header">
<img src="@/assets/images/logo.png" width="50" alt="logo">
<span class="auth-title">加入微光</span>
<span class="auth-slogan">开启温暖的社区之旅</span>
</div>

<div class="auth-form">
<div class="auth-form-item">
<label class="auth-form-label">用户名</label>
<div class="auth-input-box">
<GlassInput v-model="registerForm.username" placeholder="您的账号"/>
<div class="auth-form-error" v-show="errorMsg.username" v-text="errorMsg.username"/>
</div>
</div>
<div class="auth-form-item">
<label class="auth-form-label">邮箱地址</label>
<div class="auth-input-box">
<GlassInput :error-message="errorMsg.email" v-model="registerForm.email" placeholder="请输入您的邮箱"/>
<div class="auth-form-error" v-show="errorMsg.email" v-text="errorMsg.email"/>
</div>
</div>
<div class="auth-form-item">
<label class="auth-form-label">验证码</label>
<div class="auth-input-box">
<div class="auth-captcha-box">
<GlassInput :maxLength="6" v-model="registerForm.code" class="auth-captcha-input"
placeholder="六位数验证码"/>
<GlassBtn class="auth-captcha-btn" :disabled="captchaLoading ||codeTime > 0" @click="sendCaptcha">
<template v-if="captchaLoading">发送中</template>
<template v-else-if="codeTime === 0">发送验证码</template>
<template v-else> {{ `${codeTime}秒` }}</template>
</GlassBtn>
</div>
<div class="auth-form-error" v-show="errorMsg.code" v-text="errorMsg.code"/>
</div>
</div>
<div class="auth-form-item">
<label class="auth-form-label">密码</label>
<div class="auth-input-box">
<GlassInput :maxLength="20" v-model="password" placeholder="请输入6-20位密码" type="password"/>
<div class="auth-form-error" v-show="errorMsg.password" v-text="errorMsg.password"/>
</div>
</div>
<div class="auth-form-item">
<label class="auth-form-label">确认密码</label>
<div class="auth-input-box">
<GlassInput :maxLength="20" v-model="confirmPass" placeholder="再次输入密码" type="password"/>
<div class="auth-form-error" v-show="errorMsg.confirmPass" v-text="errorMsg.confirmPass"/>
</div>
</div>
<GlassBtn class="auth-submit-btn" @click="registerAccount">创建帐号</GlassBtn>
</div>
<GlassDivider class="auth-form-divider"/>
<div class="auth-form-footer">
已有账户?
<router-link class="auth-footer-toLogin" to="/login">立即登录</router-link>
</div>
</div>

</div>
</template>

<style lang="scss" scoped>
@use "@/assets/style/auth-form";


</style>


9

路由添加

然后把注册加入到路由和白名单中 如图所示

10

完成

演示效果如图

阅读记录0
点赞0
收藏0
禁止 本文未经作者允许授权,禁止转载
猜你喜欢
评论/提问(已发布 0 条)
评论 评论
收藏 收藏
分享 分享
pdf下载 下载
pdf下载 举报