【EasyPan】发送邮箱验证码sendEmailCode

【EasyPan】项目常见问题解答(自用&持续更新中…)汇总版

一、数据库设计

CREATE TABLE `email_code` (
  `email` varchar(150) NOT NULL COMMENT '邮箱',
  `code` varchar(5) NOT NULL COMMENT '验证码',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `status` tinyint(1) DEFAULT NULL COMMENT '0:未使用 1:已使用',
  PRIMARY KEY (`email`,`code`) -- 复合主键
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邮箱验证码';

设计特点

  1. 复合主键(email+code)确保同一邮箱相同验证码只能存一条
  2. status字段标记验证码使用状态
  3. create_time记录验证码有效期起始时间

二、核心交互流程

用户 前端 后端 Session Redis SMTP服务器 数据库 输入邮箱+图形验证码 POST /sendEmailCode 校验图形验证码 获取邮件模板配置 发送验证码邮件 保存验证码记录 返回发送结果 用户 前端 后端 Session Redis SMTP服务器 数据库

三、关键代码解析

1. 控制器层

@RequestMapping("/sendEmailCode")
@GlobalInterceptor(checkParams = true, checkLogin = false)
public ResponseVO sendEmailCode(HttpSession session,
                               @VerifyParam(regex = VerifyRegexEnum.EMAIL) String email,
                               @VerifyParam String checkCode, 
                               @VerifyParam Integer type) {
    try {
        // 1. 校验图形验证码
        if (!checkCode.equalsIgnoreCase((String) session.getAttribute(Constants.CHECK_CODE_KEY_EMAIL))) {
            throw new BusinessException("图形验证码错误");
        }
        // 2. 发送邮件验证码
        emailCodeService.sendEmailCode(email, type);
        return getSuccessResponseVO(null);
    } finally {
        session.removeAttribute(Constants.CHECK_CODE_KEY_EMAIL); // 销毁图形验证码
    }
}

2. 业务逻辑层

@Transactional(rollbackFor = Exception.class)
public void sendEmailCode(String email, Integer type) {
    // 1. 注册场景校验邮箱唯一性
    if (type == Constants.ZERO) {
        if (userInfoMapper.selectByEmail(email) != null) {
            throw new BusinessException("邮箱已注册");
        }
    }
    
    // 2. 生成5位数字验证码
    String code = StringTools.getRandomNumber(Constants.LENGTH_5);
    
    // 3. 发送邮件
    sendMailCode(email, code);
    
    // 4. 失效旧验证码
    emailCodeMapper.disableEmailCode(email);
    
    // 5. 存储新验证码
    EmailCode emailCode = new EmailCode();
    emailCode.setEmail(email);
    emailCode.setCode(code);
    emailCode.setStatus(Constants.ZERO); // 未使用状态
    emailCode.setCreateTime(new Date());
    emailCodeMapper.insert(emailCode);
}

3. 邮件发送实现

private void sendMailCode(String toEmail, String code) {
    try {
        MimeMessage message = javaMailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true); // 支持多部分邮件
        
        // 从Redis获取邮件模板配置
        SysSettingsDto settings = redisComponent.getSysSettingsDto();
        // 设置邮件基本信息
        helper.setFrom(appConfig.getSendUserName());// 发件人
        helper.setTo(toEmail);// 收件人
        helper.setSubject(settings.getRegisterEmailTitle());// 主题
        helper.setText(String.format(settings.getRegisterEmailContent(), code)); // 内容
        helper.setSentDate(new Date());// 发送时间
        // 触发发送
        javaMailSender.send(message);
    } catch (Exception e) {
        logger.error("邮件发送失败", e);
        throw new BusinessException("邮件发送失败");
    }
}

四、安全防护机制

防护措施 实现方式
图形验证码校验 先校验图形验证码正确性后才允许发送邮件
验证码时效性 通过create_time字段后续可做有效期校验
旧验证码失效 发送新验证码前先将该邮箱所有旧验证码标记为失效
防重复注册 注册场景(type=0)下校验邮箱是否已存在
模板注入防护 使用String.format规范插入验证码,避免邮件内容拼接

/**
 * 邮箱验证码表结构
 */
CREATE TABLE `email_code` (
  `email` varchar(150) NOT NULL COMMENT '邮箱',
  `code` varchar(5) NOT NULL COMMENT '编号',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `status` tinyint(1) DEFAULT NULL COMMENT '0:未使用 1:已使用',
  PRIMARY KEY (`email`,`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邮箱验证码';

/**
 * 发送邮箱验证码接口
 */
@RequestMapping("/sendEmailCode")
@GlobalInterceptor(checkParams = true, checkLogin = false)
public ResponseVO sendEmailCode(HttpSession session,
                                @VerifyParam(required = true, regex = VerifyRegexEnum.EMAIL, max = 150) String email,
                                @VerifyParam(required = true) String checkCode,
                                @VerifyParam(required = true) Integer type) {
    try {
        if (!checkCode.equalsIgnoreCase((String) session.getAttribute(Constants.CHECK_CODE_KEY_EMAIL))) {
            throw new BusinessException("邮箱验证码不正确");
        }
        //发送邮箱验证码
        emailCodeService.sendEmailCode(email, type);
        return getSuccessResponseVO(null);
    } finally {
        session.removeAttribute(Constants.CHECK_CODE_KEY_EMAIL);
    }
}

/**
 * 发送邮箱验证码实现
 */
@Override
@Transactional(rollbackFor = Exception.class)
public void sendEmailCode(String email, Integer type) {
    if (type == Constants.ZERO) {
        UserInfo userInfo = userInfoMapper.selectByEmail(email);
        if (userInfo != null) {
            throw new BusinessException("邮箱已经存在");
        }
    }

    String code = StringTools.getRandomNumber(Constants.LENGTH_5);

    //发送邮箱验证码
    sendMailCode(email, code);

    //将该邮箱之前的验证码置为无效
    emailCodeMapper.disableEmailCode(email);

    EmailCode emailCode = new EmailCode();
    emailCode.setEmail(email);
    emailCode.setCode(code);
    emailCode.setCreateTime(new Date());
    emailCode.setStatus(Constants.ZERO);
    emailCodeMapper.insert(emailCode);
}

/**
 * 实际邮件发送方法
 */
private void sendMailCode(String toEmail, String code){
    try {
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setFrom(appConfig.getSendUserName());
        helper.setTo(toEmail);
        SysSettingsDto sysSettingsDto = redisComponent.getSysSettingsDto();
        helper.setSubject(sysSettingsDto.getRegisterEmailTitle());
        helper.setText(String.format(sysSettingsDto.getRegisterEmailContent(), code));
        helper.setSentDate(new Date());
        javaMailSender.send(mimeMessage);
    } catch (Exception e) {
        logger.error("邮件发送失败", e);
        throw new BusinessException("邮件发送失败");
    }
}

你可能感兴趣的:(EasyPan,java)