1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 手把手教你通过SpringBoot实现邮箱注册码验证

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

时间:2022-06-26 11:22:57

相关推荐

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

文章目录

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

一、前言

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

二、创建SpringBoot工程项目

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

2.1 JWT认证

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

《SpringBoot整合SpringSecurity实现JWT认证》

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

添加maven依赖

<!-- mybatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.0</version></dependency>

在 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

编写用户表实体类:

/*** 用户表** @author zhuhuix* @date -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());}

新增Mapper类 SysUserMapper.java: 直接继承 BaseMapper,这是 mybatis-plus 封装好的类,已经实现了基本的增删改查。

/*** 用户DAO接口** @author zhuhuix* @date -07-19*/@Mapperpublic interface SysUserMapper extends BaseMapper<SysUser> {}

编写用户增删改查服务接口与实现类:

/*** 用户增删改查服务接口** @author zhuhuix* @date -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 -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工具类实现邮件的发送

定义一个邮件发送信息传输类

/*** 邮件信息* @author zhuhuix* @date -07-19*/@Data@AllArgsConstructor@NoArgsConstructorpublic class EmailDto {/*** 发送邮箱列表*/@NotEmptyprivate List<String> tos;/*** 主题*/@NotBlankprivate String subject;/*** 内容*/@NotBlankprivate String content;}

定义邮件发送服务接口及编写实现类

/*** 邮箱服务接口** @author zhuhuix* @date -07-19*/public interface EmailService {/*** 发送邮件* @param emailDto 邮箱列表*/void send(EmailDto emailDto);}

/*** 邮箱发送接口实现类** @author zhuhuix* @date -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./cgi-bin/help?subtype=1&&id=28&&no=1001256

– 163邮处的授权码设置

在项目配置文件中配置好邮箱信息

# application.ymlserver:port: 8000spring:mail:email: xxxx@host: port: 465username: xxxx# 授权密码, 非邮箱密码,授权码是用于登录第三方邮件客户端的专用密码。password: xxxxxxxx

设计邮箱验证码模板 为了让用户收到美观的邮箱验证码邮件,我们设计一个模板,该模板需要将后台动态生成的验证码传入,生成HTML内容后向用户邮箱进行发送。

– 加入模板引擎依赖

<!--模板引擎--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency>

– 设置模板

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="/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 -04-07*/public interface AuthService {/*** 向指定邮箱发送验证码** @param email 邮箱号*/void sendMailCode(String email);/*** 注册** @param authUserDto 认证用户请求信息* @return 是否成功*/boolean register(AuthUserDto authUserDto);}```java/*** 认证用户** @author zhuhuix* @date -04-03*/@ApiModel(value = "授权用户信息")@Data@AllArgsConstructor@NoArgsConstructorpublic class AuthUserDto {@ApiModelProperty(value = "用户名")private String userName;@ApiModelProperty(value = "密码")private String password;@ApiModelProperty(value = "临时登录凭证")private String code;@ApiModelProperty(value = "邮箱")private String email ;}

验证码邮件发送的具体实现:需要将发送的验证码放入Redis缓存,并设置过期时间

/*** 授权登录接口实现类** @author zhuhuix* @date -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))));}}

用户注册:判断注册时用户填写的验证码是否有效。 在用户注册过程中,我们需要把用户上传信息中的验证码与缓存中的验证码进行比对验证

/*** 授权登录接口实现类** @author zhuhuix* @date -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;} }

编写API接口

/*** api登录授权** @author zhuhuix* @date -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})}

接下来进行联调

收取到的验证码邮件

后台注册成功后的表信息

三、源码

前端

/zhuhuix/startup-frontend

/zhuhuix/startup-frontend后端

/zhuhuix/startup-backend

/zhuhuix/startup-backend

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。