极简邮箱注册系统流程说明

项目概述

这是一个基于 Spring Boot 的邮箱验证注册系统,采用经典的三层架构模式,实现用户通过邮箱验证码进行安全注册的功能。

技术栈

  • 后端框架: Spring Boot 2.6.13
  • 数据库: MySQL 8.0
  • ORM 框架: MyBatis-Plus 3.5.3.1
  • 缓存: Redis
  • 邮件服务: Spring Boot Mail (QQ 邮箱 SMTP)
  • 工具库: Lombok、Apache Commons Lang3

项目结构

src/
├── main/
│   ├── java/com/example/registration/
│   │   ├── RegistrationApplication.java     # 启动类
│   │   ├── controller/
│   │   │   └── UserController.java          # 控制器层
│   │   ├── service/
│   │   │   ├── UserService.java             # 用户业务逻辑
│   │   │   └── EmailService.java            # 邮件服务
│   │   ├── entity/
│   │   │   ├── User.java                    # 用户实体
│   │   │   └── UserRegisterDTO.java         # 注册数据传输对象
│   │   └── mapper/
│   │       └── UserMapper.java              # 数据访问层
│   └── resources/
│       └── application.yml                  # 应用配置
└── test/                                    # 测试代码

数据库设计

用户表结构 (user)

CREATE TABLE user (
    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
    username VARCHAR(50) NOT NULL COMMENT '用户名',
    email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱地址',
    password VARCHAR(255) NOT NULL COMMENT '密码',
    is_verified BOOLEAN DEFAULT FALSE COMMENT '邮箱验证状态'
);

系统配置

application.yml 配置说明

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/registration?useSSL=false
    username: root
    password: your_password_here # ⚠️ 请修改为实际密码
    driver-class-name: com.mysql.cj.jdbc.Driver

  mail:
    host: smtp.qq.com
    username: ********** # ⚠️ 请修改为实际邮箱
    password: your_auth_code_here # ⚠️ 请修改为邮箱授权码
    port: 465
    properties:
      mail.smtp.auth: true
      mail.smtp.starttls.enable: true
      mail.smtp.ssl.enable: true

  redis:
    host: localhost
    port: 6379

mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml

配置说明:

  • 数据库配置: MySQL 连接信息,需要创建名为 registration 的数据库
  • 邮件配置: QQ 邮箱 SMTP 服务器配置,需要开启邮箱的 SMTP 服务并获取授权码
  • Redis 配置: 用于存储验证码的缓存服务
  • MyBatis-Plus 配置: Mapper 文件路径配置

核心流程详解

1. 发送验证码流程

接口信息

  • 请求路径: POST /api/user/send-code
  • 请求参数: email (邮箱地址)
  • 响应: "验证码已发送"

处理流程

sequenceDiagram
    participant Client as 客户端
    participant Controller as UserController
    participant Service as UserService
    participant DB as 数据库
    participant Redis as Redis缓存
    participant Email as EmailService
    participant SMTP as 邮件服务器

    Client->>Controller: POST /api/user/send-code
    Controller->>Service: sendVerificationCode(email)
    Service->>DB: 检查邮箱是否已注册
    alt 邮箱已存在
        Service-->>Controller: 抛出"邮箱已注册"异常
        Controller-->>Client: 返回错误信息
    else 邮箱未注册
        Service->>Service: 生成6位随机验证码
        Service->>Redis: 存储验证码(5分钟有效期)
        Service->>Email: sendVerificationCode(email, code)
        Email->>SMTP: 发送邮件
        Email-->>Service: 发送成功
        Service-->>Controller: 完成
        Controller-->>Client: "验证码已发送"
    end

核心代码逻辑

1. 控制器层代码 (UserController.java)

@PostMapping("/send-code")
public ResponseEntity<String> sendCode(@RequestParam String email) {
    userService.sendVerificationCode(email);
    return ResponseEntity.ok("验证码已发送");
}

2. 业务逻辑层代码 (UserService.java)

public void sendVerificationCode(String email) {
    // 检查邮箱是否已注册
    if (userMapper.selectOne(new LambdaQueryWrapper<User>()
            .eq(User::getEmail, email)) != null) {
        throw new RuntimeException("邮箱已注册");
    }

    // 生成6位随机验证码
    String code = RandomStringUtils.randomNumeric(6);

    // 存储到Redis,5分钟有效期
    redisTemplate.opsForValue().set(
        "email:code:" + email, code, 5, TimeUnit.MINUTES);

    // 发送邮件
    emailService.sendVerificationCode(email, code);
}

3. 邮件服务代码 (EmailService.java)

public void sendVerificationCode(String toEmail, String code) {
    SimpleMailMessage message = new SimpleMailMessage();
    message.setFrom(fromEmail);
    message.setTo(toEmail);
    message.setSubject("邮箱验证码");
    message.setText("您的验证码是: " + code + ",5分钟内有效");
    mailSender.send(message);
}

流程说明:

  1. 邮箱重复检查: 使用 MyBatis-Plus 的 LambdaQueryWrapper 查询数据库,确保邮箱未被注册
  2. 验证码生成: 使用 Apache Commons Lang3 的 RandomStringUtils 生成 6 位数字验证码
  3. 缓存存储: 将验证码存储到 Redis,键名格式为"email:code:邮箱地址",有效期 5 分钟
  4. 邮件发送: 通过 EmailService 发送包含验证码的邮件

2. 用户注册流程

接口信息

  • 请求路径: POST /api/user/register
  • 请求体: UserRegisterDTO 对象
    {
      "username": "用户名",
      "email": "邮箱地址",
      "password": "密码",
      "code": "验证码"
    }
    
  • 响应: "注册成功"

处理流程

sequenceDiagram
    participant Client as 客户端
    participant Controller as UserController
    participant Service as UserService
    participant Redis as Redis缓存
    participant DB as 数据库

    Client->>Controller: POST /api/user/register
    Controller->>Controller: 构造User对象
    Controller->>Service: register(user, code)
    Service->>Redis: 获取存储的验证码
    alt 验证码不存在或错误
        Service-->>Controller: 抛出"验证码错误"异常
        Controller-->>Client: 返回错误信息
    else 验证码正确
        Service->>DB: 插入用户数据
        Service->>Redis: 删除已使用的验证码
        Service-->>Controller: "注册成功"
        Controller-->>Client: "注册成功"
    end

核心代码逻辑

1. 控制器层代码 (UserController.java)

@PostMapping("/register")
public ResponseEntity<String> register(@RequestBody UserRegisterDTO dto) {
    User user = new User();
    user.setEmail(dto.getEmail());
    user.setUsername(dto.getUsername());
    user.setPassword(dto.getPassword()); // ⚠️ 实际项目中应该加密密码

    userService.register(user, dto.getCode());
    return ResponseEntity.ok("注册成功");
}

2. 业务逻辑层代码 (UserService.java)

public String register(User user, String inputCode) {
    // 获取Redis中存储的验证码
    String storedCode = redisTemplate.opsForValue()
        .get("email:code:" + user.getEmail());

    // 验证码校验
    if (storedCode == null || !storedCode.equals(inputCode)) {
        throw new RuntimeException("验证码错误");
    }

    // 保存用户到数据库
    userMapper.insert(user);

    // 清除已使用的验证码
    redisTemplate.delete("email:code:" + user.getEmail());

    return "注册成功";
}

3. 数据传输对象 (UserRegisterDTO.java)

@Data
public class UserRegisterDTO {
    private String username;
    private String email;
    private String password;
    private String code; // 验证码
}

4. 用户实体类 (User.java)

@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;

    private String username;
    private String email;
    private String password; // ⚠️ 建议加密存储

    @TableField("is_verified")
    private Boolean isVerified = false;
}

流程说明:

  1. DTO 转换: Controller 层将 UserRegisterDTO 转换为 User 实体对象
  2. 验证码校验: 从 Redis 中获取存储的验证码,与用户输入的验证码进行比较
  3. 数据持久化: 验证通过后,使用 MyBatis-Plus 的 insert 方法将用户信息保存到数据库
  4. 缓存清理: 注册成功后清除 Redis 中的验证码

关键技术实现

1. 邮件服务实现

完整的邮件服务类:

@Service
public class EmailService {
    @Autowired
    private JavaMailSender mailSender;

    @Value("${spring.mail.username}")
    private String fromEmail;

    public void sendVerificationCode(String toEmail, String code) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(fromEmail);
        message.setTo(toEmail);
        message.setSubject("邮箱验证码");
        message.setText("您的验证码是: " + code + ",5分钟内有效");
        mailSender.send(message);
    }
}

特点:

  • 使用 Spring Boot Mail Starter
  • 配置 QQ 邮箱 SMTP 服务器
  • 支持 SSL 加密传输
  • 简单文本邮件格式

2. 缓存机制

Redis 操作示例:

// 存储验证码
redisTemplate.opsForValue().set(
    "email:code:" + email, code, 5, TimeUnit.MINUTES);

// 获取验证码
String storedCode = redisTemplate.opsForValue()
    .get("email:code:" + email);

// 删除验证码
redisTemplate.delete("email:code:" + email);

特点:

  • 使用 Redis 存储临时验证码
  • 设置 5 分钟过期时间
  • 键名采用统一命名规范

3. 数据访问层

Mapper 接口:

@Mapper
public interface UserMapper extends BaseMapper<User> {
    // 继承BaseMapper即拥有基础CRUD方法
}

查询示例:

// 查询邮箱是否存在
User existUser = userMapper.selectOne(
    new LambdaQueryWrapper<User>().eq(User::getEmail, email));

// 插入新用户
userMapper.insert(user);

特点:

  • 基于 MyBatis-Plus 框架
  • 继承 BaseMapper 获得基础 CRUD 功能
  • 使用 LambdaQueryWrapper 进行类型安全查询

4. 异常处理

当前实现:

// 业务异常抛出
if (storedCode == null || !storedCode.equals(inputCode)) {
    throw new RuntimeException("验证码错误");
}

if (userMapper.selectOne(new LambdaQueryWrapper<User>()
        .eq(User::getEmail, email)) != null) {
    throw new RuntimeException("邮箱已注册");
}

建议改进 - 全局异常处理器:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<String> handleRuntimeException(RuntimeException e) {
        return ResponseEntity.badRequest().body(e.getMessage());
    }
}

特点:

  • 统一使用 RuntimeException 抛出业务异常
  • 建议在 Controller 层配置全局异常处理器

安全考虑

1. 验证码安全

  • 验证码有效期限制(5 分钟)
  • 一次性使用机制
  • 随机生成,难以预测

2. 邮箱唯一性

  • 数据库层面设置邮箱唯一约束
  • 注册前进行重复性检查

3. 密码安全

  • 建议在实际项目中对密码进行加密存储
  • 可以集成 Spring Security 进行密码编码

部署要求

环境依赖

  • JDK 1.8+
  • MySQL 8.0+
  • Redis 6.0+
  • Maven 3.6+

数据库初始化

创建数据库和表:

-- 创建数据库
CREATE DATABASE registration CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 使用数据库
USE registration;

-- 创建用户表
CREATE TABLE user (
    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
    username VARCHAR(50) NOT NULL COMMENT '用户名',
    email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱地址',
    password VARCHAR(255) NOT NULL COMMENT '密码',
    is_verified BOOLEAN DEFAULT FALSE COMMENT '邮箱验证状态',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
);

配置项

  1. 数据库连接: 修改 application.yml 中的数据库连接信息

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/registration?useSSL=false
        username: your_db_username # ⚠️ 修改为实际用户名
        password: your_db_password # ⚠️ 修改为实际密码
    
  2. 邮件服务: 配置邮箱 SMTP 服务器和授权码

    spring:
      mail:
        username: ********** # ⚠️ 修改为实际邮箱
        password: your_auth_code_here # ⚠️ 修改为邮箱授权码
    
  3. Redis 连接: 设置 Redis 服务器地址和端口

    spring:
      redis:
        host: localhost
        port: 6379
        password: your_redis_password # ⚠️ 如有密码请配置
    

API 测试示例

1. 发送验证码 API 测试

请求示例:

curl -X POST "http://localhost:8080/api/user/send-code" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "email=test@example.com"  # ⚠️ 请使用真实邮箱进行测试

Postman 测试:

  • 方法: POST
  • URL: http://localhost:8080/api/user/send-code
  • 参数: email=test@example.com
  • 预期响应: 验证码已发送

2. 用户注册 API 测试

请求示例:

curl -X POST "http://localhost:8080/api/user/register" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "testuser",
    "email": "test@example.com",
    "password": "123456",
    "code": "123456"  # 请使用收到的实际验证码
  }'

Postman 测试:

  • 方法: POST
  • URL: http://localhost:8080/api/user/register
  • Body: JSON 格式
{
  "username": "testuser",
  "email": "test@example.com",
  "password": "123456",
  "code": "请输入收到的验证码"
}
  • 预期响应: 注册成功

3. 常见错误响应

邮箱已注册:

{
  "error": "邮箱已注册"
}

验证码错误:

{
  "error": "验证码错误"
}

验证码过期:

{
  "error": "验证码错误"
}

扩展建议

功能扩展

  1. 短信验证: 增加手机号验证方式
  2. 图形验证码: 防止机器人恶意注册
  3. 密码强度验证: 增加密码复杂度要求
  4. 用户状态管理: 增加用户激活/禁用功能

技术优化

  1. 全局异常处理: 统一处理业务异常
  2. 参数校验: 使用 Bean Validation 进行参数验证
  3. 日志记录: 增加详细的操作日志
  4. 接口文档: 集成 Swagger 生成 API 文档

⚠️ 重要提醒:隐私和安全

代码中的隐私信息处理

本文档中所有带有 ⚠️ 标记的配置项都需要根据实际情况修改:

  1. 数据库密码: your_password_here → 请设置安全的数据库密码
  2. 邮箱地址: ********** → 请使用真实的邮箱地址
  3. 邮箱授权码: your_auth_code_here → 请使用邮箱的授权码(非登录密码)
  4. Redis 密码: 如果 Redis 设置了密码,请配置正确的密码

生产环境安全建议

  1. 密码加密: 示例代码中密码是明文存储,生产环境必须使用 BCrypt 等加密算法

    // 推荐使用 Spring Security 的 BCryptPasswordEncoder
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    
    // 注册时加密密码
    user.setPassword(passwordEncoder.encode(dto.getPassword()));
    
  2. 配置文件安全:

    • 不要将包含敏感信息的配置文件提交到公共代码仓库
    • 使用环境变量或配置中心管理敏感配置
    • 使用 Spring Profiles 区分不同环境配置
  3. 验证码安全增强:

    • 增加发送频率限制(如:1 分钟内只能发送一次)
    • 增加 IP 限制防止恶意攻击
    • 考虑添加图形验证码
  4. HTTPS: 生产环境必须使用 HTTPS 协议

QQ 邮箱授权码获取步骤

  1. 登录 QQ 邮箱网页版
  2. 点击设置 → 账户
  3. 找到"POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务"
  4. 开启"IMAP/SMTP 服务"
  5. 生成授权码(16 位字符)
  6. 将授权码配置到 application.ymlspring.mail.password

总结

该邮箱注册系统实现了完整的"发送验证码 → 验证注册"流程,采用了现代化的技术栈和标准的分层架构。系统具有良好的可扩展性和维护性,可以作为其他项目的基础模块进行复用和扩展。

通过 Redis 缓存机制确保了验证码的时效性,通过数据库约束保证了数据的一致性,通过邮件服务实现了用户身份的验证,整个流程设计合理,安全可靠。

注意: 本文档提供的是基础版本的实现,在实际生产环境中请务必加强安全性设计,包括但不限于密码加密、HTTPS 部署、频率限制等安全措施。

#邮箱注册#
全部评论

相关推荐

一共一个小时,面试难度以及自己的回答算是最近的面试压力比较大的,实习问了30分钟,中间穿插八股。1.redis数据结构2.redis持久化机制3.mysql索引底层4.聚簇索引与非聚簇索引5.索引优化6.索引失效7.mysql执行一条sql8.那么多索引mysql怎么选(不会)9.tcp与udp区别10.tcp为什么可靠11.消息队列作用12.kafka怎么保证消息有序性13.mcp是什么?14.skills是什么?15.jvm内存分配与回收过程(我讲了从创建对象到判断垃圾对象到垃圾回收我全说了一遍,是这个吗?)16.fullgc触发机制17.tcp的拥塞控制流程(不会了)18.分布式事务解决方案,说了2pc,3pc,tcc。算法是反转双向链表,没有按格式输出,但是面试官没让继续写了,面完以为挂了,结果晚上秒过,看看复试什么情况吧。今天百度打电话准备发offer了,业务跟在手子的差不多,很垂,并且说不分日常暑期,只看表现,会有转正机会,但是考虑再三还是拒绝了,百度实习薪资确实有点低,title也不如之前了,但是面试的二位业务老师我很喜欢,对我的评价也不错,希望之后能有机会共事。从三月份到现在一共面了六家,面试次数总共是8场,情况如下:脉脉二面(无答复,默认挂)百度二面已oc美团一面过,下周一二面shein一面过直接HR面游族一面过直接HR面腾讯一面过等待约二面滴滴明天一面面试通过率还是蛮高的,但是大部分都是日常,感觉对我现在的加成不大,大概率不会去,不知道暑期会是什么情况呢唉,希望能有面试吧,继续加油。字节被无hc直接取消了,现在还没人捞,有没有字节HR救救我
不管什么都不想跳动了:本人美团百度快手都待过,建议肯定是直接留快手多一点产出后转正or直接冲字节腾讯暑期吧。一是快手从福利到基建都吊打另外两家。美团现在这个业务比较惨,本来毛利就很低,亏损严重,今年很可能要优化人力降低成本,去了别说日常,就算暑期后面都很可能被优化。百度其实实习生权限挺高的,可以接触到一些含金量高的项目,但是现在的风评不如之前了,薪资也不高。二是转正概率和薪资是跟产出挂钩的,你都在手子已经积累产出了,去其他家日常实习产出都是从0开始,肯定不可能有你在手子转正可能性大啊,现在日常压根没必要去,而且我有两个师弟都是在快手日常转正的,不用太担心,安心留在手子一边多做一点产出然后一边冲字节腾讯暑期,字节腾讯今年实习岗位非常多的,不如好好把握这个,加油。
查看18道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务