文章目录

  • 一、前言
  • 二、创建SpringBoot工程项目
    • 2.1 JWT认证
    • 2.2 集成mybatis-plus实现用户的增删改查
    • 2.3 编写Email工具类实现邮件的发送
    • 2.4 验证码邮件发送与验证码后台验证
    • 2.5 前后端联调测试
  • 三、源码

一、前言

注册一个系统成为用户,一般会要求用户留下一个邮件地址作为联系方式,就象我们去银行开户时银行会让我们留个手机号码一样。为了证明注册的邮箱地址是本人的,系统会向邮箱发送一串验证码,用户收取该验证码后在注册页面上输入验证码连同其他信息发往后台进行验证。

二、创建SpringBoot工程项目

  • 该工程项目主要实现步骤如下:
  1. JWT认证
  2. 集成mybatis-plus实现用户的增删改查
  3. 编写Email工具类实现邮件的发送
  4. 验证码邮件发送与验证码后台验证
  5. 前端联调测试

2.1 JWT认证

  • 前后端分离目前已成为互联网项目开发的业界标准,其核心思想就是前端(APP、小程序、H5页面等)通过调用后端的API接口,提交及返回JSON数据进行交互。
  • 在前后端分离项目中,首先要解决的就是登录及授权的问题。微服务架构下,传统的session认证限制了应用的扩展能力,无状态的JWT认证方法应运而生,该认证机制特别适用于分布式站点的单点登录(SSO)场景
  • 关于SpringBoot实现JWT的具体细节,请参考本人博文:
    《SpringBoot整合SpringSecurity实现JWT认证》

2.2 集成mybatis-plus实现用户的增删改查

  1. 添加maven依赖
  <!-- mybatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.0</version></dependency>
  1. 在 application.yml 配置文件中添加 mysql 数据库的相关配置:
spring:datasource:druid:db-type: com.alibaba.druid.pool.DruidDataSourcedriverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpyurl: jdbc:log4jdbc:mysql://localhost:3306/startup_backend?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: root
  1. 编写用户表实体类:
/*** 用户表** @author zhuhuix* @date 2020-04-03*/
@ApiModel(value = "用户信息")
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_user")
public class SysUser implements Serializable {@TableId(value = "id", type = IdType.AUTO)private Long id;private String userName;@JsonIgnoreprivate String password;private String nickName;/*** 性别 0-未知 1-male,2-female*/private Integer gender;/*** 头像地址*/private String avatarUrl;private String country;private String province;private String city;@Emailprivate String email;private String phone;private String remarks;private Boolean enabled;private Timestamp lastPasswordResetTime;@Builder.Defaultprivate Timestamp createTime = Timestamp.valueOf(LocalDateTime.now());@Builder.Defaultprivate Timestamp updateTime = Timestamp.valueOf(LocalDateTime.now());}
  1. 新增Mapper类 SysUserMapper.java:
  • 直接继承 BaseMapper,这是 mybatis-plus 封装好的类,已经实现了基本的增删改查。
/*** 用户DAO接口** @author zhuhuix* @date 2021-07-19*/
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {}
  1. 编写用户增删改查服务接口与实现类:
/*** 用户增删改查服务接口** @author zhuhuix* @date 2020-04-03*/
public interface SysUserService {/*** 增加用户** @param user 待新增的用户* @return 增加成功的用户*/SysUser create(SysUser user);/*** 删除用户** @param user 待删除的用户* @return 删除成功的用户*/Result<SysUser> delete(SysUser user);/*** 修改用户** @param user 待修改的用户* @return 修改成功的用户*/Result<SysUser> update(SysUser user);/*** 根据userName查找用户** @param userName 用户帐号* @return 用户帐号对应的用户*/SysUser findByUserName(String userName);/*** 判断注册使用的邮箱是否存在** @param email 邮箱号* @return 是否找到*/boolean registerEmailExist(String email);
}
/*** 用户增删改查实现类** @author zhuhuix* @date 2020-04-03*/
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class SysUserServiceImpl implements SysUserService {private final SysUserMapper sysUserMapper;@Override@Transactional(rollbackFor = Exception.class)public SysUser create(SysUser user) {return sysUserMapper.insert(user) > 0 ? user : null;}@Override@Transactional(rollbackFor = Exception.class)public Result<SysUser> delete(SysUser user) {QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();queryWrapper.lambda().eq(SysUser::getUserName, user.getUserName());return sysUserMapper.delete(queryWrapper) > 0 ? new Result<SysUser>().ok(user) : new Result<SysUser>().error("删除用户失败");}@Override@Transactional(rollbackFor = Exception.class)public Result<SysUser> update(SysUser user) {return sysUserMapper.updateById(user) > 0 ? new Result<SysUser>().ok(user) : new Result<SysUser>().error("更新用户失败");}@Overridepublic SysUser findByUserName(String userName) {return sysUserMapper.selectOne(new QueryWrapper<SysUser>().lambda().eq(SysUser::getUserName, userName));}@Overridepublic boolean registerEmailExist(String email) {QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();queryWrapper.lambda().eq(SysUser::getEmail, email);return sysUserMapper.selectOne(queryWrapper) != null;}
}

2.3 编写Email工具类实现邮件的发送

  1. 定义一个邮件发送信息传输类
/*** 邮件信息* @author zhuhuix* @date 2021-07-19*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmailDto {/*** 发送邮箱列表*/@NotEmptyprivate List<String> tos;/*** 主题*/@NotBlankprivate String subject;/*** 内容*/@NotBlankprivate String content;
}
  1. 定义邮件发送服务接口及编写实现类
/*** 邮箱服务接口** @author zhuhuix* @date 2021-07-19*/
public interface EmailService {/*** 发送邮件* @param emailDto 邮箱列表*/void send(EmailDto emailDto);
}
/*** 邮箱发送接口实现类** @author zhuhuix* @date 2021-07-19*/
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class EmailServiceImpl implements EmailService {@Value("${spring.mail.email}")private String email;@Value("${spring.mail.host}")private String host;@Value("${spring.mail.port}")private String port;@Value("${spring.mail.username}")private String username;@Value("${spring.mail.password}")private String password;@Overridepublic void send(EmailDto emailDto) {// 读取邮箱配置if (email == null || host == null || port == null || username == null || password == null) {throw new RuntimeException("邮箱配置异常");}// 设置MailAccount account = new MailAccount();account.setHost(host);account.setPort(Integer.parseInt(port));// 设置发送人邮箱account.setFrom(username + "<" + email + ">");// 设置发送人名称account.setUser(username);// 设置发送授权码account.setPass(password);account.setAuth(true);// ssl方式发送account.setSslEnable(true);// 使用安全连接account.setStarttlsEnable(true);// 发送邮件try {int size = emailDto.getTos().size();Mail.create(account).setTos(emailDto.getTos().toArray(new String[size])).setTitle(emailDto.getSubject()).setContent(emailDto.getContent()).setHtml(true)//关闭session.setUseGlobalSession(false).send();} catch (Exception e) {throw new RuntimeException(e.getMessage());}}
}
  • 注意:该实现类中使用了hutool工具包的Mail与MailAccount,具体要查阅hutool相关API:

  • 另外,如果后台使用163或qq邮箱发送验证码时,需要对SMTP密码设置单独生成的授权码,
    – 腾讯官方的帮助文档:
  • http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=28&&no=1001256
    – 163邮处的授权码设置
  1. 在项目配置文件中配置好邮箱信息
# application.yml
server:port: 8000spring:mail:email: xxxx@163.comhost: smtp.163.comport: 465username: xxxx# 授权密码, 非邮箱密码,授权码是用于登录第三方邮件客户端的专用密码。password: xxxxxxxx
  1. 设计邮箱验证码模板
  • 为了让用户收到美观的邮箱验证码邮件,我们设计一个模板,该模板需要将后台动态生成的验证码传入,生成HTML内容后向用户邮箱进行发送。
    – 加入模板引擎依赖
<!--模板引擎--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency>

– 设置模板

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/><style>@page {margin: 0;}</style>
</head>
<body>
<div class="header"><div style="padding: 10px;padding-bottom: 0px;"><p style="margin-bottom: 10px;padding-bottom: 0px;">尊敬的用户,您好:</p><p style="text-indent: 2em; margin-bottom: 10px;">您正在申请邮箱验证,您的验证码为:</p><p class="code-text">${code}</p><div class="footer"></div></div>
</div>
</body>
</html><style lang="css">body {margin: 0px;padding: 0px;font: 100% SimSun, Microsoft YaHei, Times New Roman, Verdana, Arial, Helvetica, sans-serif;color: #000;}.header {height: auto;width: 820px;min-width: 820px;margin: 0 auto;margin-top: 20px;border: 1px solid #eee;}.code-text {text-align: center;font-family: Times New Roman;font-size: 22px;color: #C60024;padding: 20px 0px;margin-bottom: 10px;font-weight: bold;background: #ebebeb;}.footer {margin: 0 auto;z-index: 111;width: 800px;margin-top: 30px;border-top: 1px solid #DA251D;}
</style>

– 编写调用模板引擎发送邮件的方法

public void sendMailCode(String email) {// 获取发送邮箱验证码的HTML模板TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));Template template = engine.getTemplate("email-code.ftl");// 发送验证码emailService.send(new EmailDto(Collections.singletonList(email),"邮箱验证码", template.render(Dict.create().set("code", code))));}

2.4 验证码邮件发送与验证码后台验证

  • 接下来我们要编写两个后台接口:1、验证码邮件发送;2、用户注册:判断注册时用户填写的验证码是否有效。
/*** 登录授权服务接口** @author zhuhuix* @date 2020-04-07*/
public interface AuthService {/*** 向指定邮箱发送验证码** @param email 邮箱号*/void sendMailCode(String email);/*** 注册** @param authUserDto 认证用户请求信息* @return 是否成功*/boolean register(AuthUserDto authUserDto);}```java
/*** 认证用户** @author zhuhuix* @date 2020-04-03*/
@ApiModel(value = "授权用户信息")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthUserDto {@ApiModelProperty(value = "用户名")private String userName;@ApiModelProperty(value = "密码")private String password;@ApiModelProperty(value = "临时登录凭证")private String code;@ApiModelProperty(value = "邮箱")private String email ;}
  1. 验证码邮件发送的具体实现:需要将发送的验证码放入Redis缓存,并设置过期时间
/*** 授权登录接口实现类** @author zhuhuix* @date 2020-06-15*/
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class AuthServiceImpl implements AuthService {// 验证码放入redis缓存过期时间@Value("${code.expiration}")private Long expiration;private final RedisUtils redisUtils;private final EmailService emailService;private final SysUserService sysUserService;@Overridepublic void sendMailCode(String email) {// 查看注册邮箱是否存在if (sysUserService.registerEmailExist(email)) {throw new RuntimeException("注册邮箱已存在");}// 获取发送邮箱验证码的HTML模板TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));Template template = engine.getTemplate("email-code.ftl");// 从redis缓存中尝试获取验证码Object code = redisUtils.get(email);if (code == null) {// 如果在缓存中未获取到验证码,则产生6位随机数,放入缓存中code = RandomUtil.randomNumbers(6);if (!redisUtils.set(email, code, expiration)) {throw new RuntimeException("后台缓存服务异常");}}// 发送验证码emailService.send(new EmailDto(Collections.singletonList(email),"邮箱验证码", template.render(Dict.create().set("code", code))));}
}
  1. 用户注册:判断注册时用户填写的验证码是否有效。
  • 在用户注册过程中,我们需要把用户上传信息中的验证码与缓存中的验证码进行比对验证
/*** 授权登录接口实现类** @author zhuhuix* @date 2020-06-15*/
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class AuthServiceImpl implements AuthService {@Value("${rsa.private-key}")private String privateKey;private final RedisUtils redisUtils;private final EmailService emailService;private final PasswordEncoder passwordEncoder;private final SysUserService sysUserService;@Override@Transactional(rollbackFor = Exception.class)public boolean register(AuthUserDto authUserDto) {// 通过email获取redis中的codeObject value = redisUtils.get(authUserDto.getEmail());if (value == null || !value.toString().equals(authUserDto.getCode())) {throw new RuntimeException("无效验证码");} else {redisUtils.del(authUserDto.getEmail());}// 如果前端没有传入用户名,则以邮箱号作为用户名进行注册String userName = StringUtils.isEmpty(authUserDto.getUserName()) ? authUserDto.getEmail() : authUserDto.getUserName();if (userService.findByUserName(userName) != null) {throw new RuntimeException("用户名已存在");}// 创建用户SysUser sysUser = new SysUser();sysUser.setUserName(userName);try {sysUser.setPassword(passwordEncoder.encode(RsaUtils.decryptByPrivateKey(privateKey, authUserDto.getPassword())));} catch (Exception e) {throw new RuntimeException("注册密码异常");}sysUser.setEmail(authUserDto.getEmail());return sysUserService.create(sysUser) != null;}
}
  1. 编写API接口
/*** api登录授权** @author zhuhuix* @date 2020-03-30*/
@Slf4j
@RestController
@RequestMapping("/api/auth")
@Api(tags = "系统授权接口")
public class AuthController {private final AuthService authService;public AuthController(AuthService authService) {this.authService = authService;}@ApiOperation("发送邮箱验证码")@PostMapping(value = "/getEmailCode")public ResponseEntity<Object> getEmailCode(@RequestParam String email) {authService.sendMailCode(email);return new ResponseEntity<>(HttpStatus.OK);}@ApiOperation("注册")@PostMapping(value = "/register")public ResponseEntity<Object> register(@RequestBody AuthUserDto authUserDto) {return ResponseEntity.ok(authService.register(authUserDto));}}

2.5 前后端联调测试

  • 终于到了前后端联调的步骤了,我们先准备好前端页面,具体可参考前面的文章
    《手把手教你使用Vue搭建注册登录界面及前端源码》

  • 然后编写前端访问后端api的接口

import request from '@/utils/request'export function getEmailCode(email) {return request({url: '/api/auth/getEmailCode?email=' + email,method: 'post'})
}export function register(data) {return request({url: '/api/auth/register',method: 'post',data})
}
  • 接下来进行联调
  • 收取到的验证码邮件
  • 后台注册成功后的表信息

三、源码

  • 前端
    https://gitee.com/zhuhuix/startup-frontend
    https://github.com/zhuhuix/startup-frontend
  • 后端
    https://gitee.com/zhuhuix/startup-backend
    https://github.com/zhuhuix/startup-backend

手把手教你通过SpringBoot实现邮箱注册码验证相关推荐

  1. 手把手教你创建springBoot项目

    **# 手把手教你创建springBoot项目 千里之行 始于足下 简介 springboot是用来简化spring应用的初始搭建以及开发过程 使用特定的方式来进行配置(properties或yml文 ...

  2. 手把手教你搭建springboot程序

    spring-boot项目搭建 一.从官网搭建 1.进入spring官网,快速初始化一个项目 2.填写项目基本信息 3.项目结构分析 4.添加项目依赖 5.下载到本地 6.解压 7.idea,打开,使 ...

  3. hMailServer 使用教程 —— 手把手教你搭建自己的邮箱服务器

    前言 假设你已经拥有了一台具有公网ip的服务器,以及域名 hMailServer 介绍 hMailServer 适用于 Windows 操作系统,它除了提供邮箱系统需要的所有基础功能之外,还内置了一些 ...

  4. 手把手教你搭建SpringBoot+MySQL+Mybatis项目(采坑日记)

    项目地址: GitHUb:https://github.com/China-Ma/happyfoal Gitee:https://gitee.com/China-Ma/happyfoal 1. 创建项 ...

  5. 手把手教你IDEA+SpringBoot+MyBatis+MySql实现动态登录与注册

    Just Code It 一.搭建SpringBoot项目1.1.file --> new --> project--> Spring Initializr--> next-- ...

  6. 手把手教你用springboot配置多数据源

    1.文件结构: 2.pom: <project xmlns="http://maven.apache.org/POM/4.0.0"     xmlns:xsi="h ...

  7. 手把手教你Tomcat配置环境变量以及验证方法

    场景 现在要将Tomcat配置进环境变量. 实现 以win7为例,找到桌面计算机图表,右键属性. 或者打开计算机,找到系统属性. 然后找到高级系统设置 找到环境变量 点击系统变量下的新建 找到Tomc ...

  8. 【直播回顾】企业如何在Q1开启新征程?教你巧用企业邮箱,打造公司管理运营新面貌

    2021年是数字化转型元年,站在2022年Q1放眼未来,信息化进程将持续加速.企业把控数字化转型.布局办公软件矩阵是重中之重.而邮箱作为企业最基础的办公软件之一,在收发信功能之外,也承载着公司的安全. ...

  9. 手牵手教Docker部署Springboot+vue ,全过程十分详细,轻松完成项目部署(简单,高效,通用)

    手把手教Docker部署Springboot+vue ,详细全过程,轻松完成项目部署(简单,高效) 上线前准备 腾讯云的服务器,服务器安装好docker 和docker-compose 最好事先了解技 ...

最新文章

  1. Oracle CDC (Change Data Capture)更新数据捕获——概述
  2. 职场,18个细节决定成败[转载]
  3. arcgis建立拓扑分析(检验矢量图)
  4. php变量教学,PHP变量详解
  5. 年龄与疾病(信息学奥赛一本通-T1106)
  6. 用U盘安装一个Linux系统
  7. eclipse for php开发环境,eclipse for php 开发环境配置
  8. pyton 编写脚本检测两台主机之间的通信状态,异常邮件通知
  9. 虚拟机IP更换后 weblogic无法启动 java.net.BindException: 无法指定被请求的地址
  10. setImageResource和setImageDrawable和setImageBitMap区别
  11. 科研伦理与学术规范(笔记)
  12. MongoDB命令笔记
  13. c# 中文数字转阿拉伯数字
  14. k8s部署-31-k8s中如何进行资源隔离资源
  15. php中文符号转英文符号,php如何中英文符号替换?
  16. 什么是SoC(System-on-a-Chip)
  17. error C4996: 'scanf': This function or variable may be unsafe. Consider using scanf_s instead.
  18. 企业邮箱怎么选,大公司青睐的企业邮箱都在这里~
  19. 西南民族大学计算机考研分数线,2020西南民族大学考研复试分数线已公布
  20. 【翻译】★VERTEBRA-FOCUSED LANDMARK DETECTION FOR SCOLIOSIS ASSESSMENT

热门文章

  1. Java“中文”编程-java为什么可以使用中文标识符
  2. 巨详细,大电流线性电源(LDO)原理,看完你就明白了
  3. 【新知实验室TRTC】
  4. 如何提高项目交付效率
  5. 手游还能这么玩?电脑控制手机鼠标键盘大屏玩手游了解一下
  6. STM32使用外设热敏打印机进行打印
  7. 85.You want to configure and schedule offline database backups to run automatically. Which tool or u
  8. 重装 Macos sierra系统 U盘
  9. laravel Helpers文件 通用帮助函数 以及常用帮助方法
  10. [二分查找] [luoguP3500] [POI2010] TES-Intelligence Test