SpringBoot+Vue实现邮箱登录注册找回密码(附接口文档)

您所在的位置:网站首页 email密码 SpringBoot+Vue实现邮箱登录注册找回密码(附接口文档)

SpringBoot+Vue实现邮箱登录注册找回密码(附接口文档)

2024-02-14 20:55| 来源: 网络整理| 查看: 265

🚀 注重版权,转载请注明原作者和原文链接 🥭 作者:Yuan-Programmer 🍎 个人博客:https://www.xiaoyuan-boke.com 正在持续完善中 🍉 进来的小伙伴点点赞呀

🚀 花了几个小时做了一个SpringBoot+Vue实现邮箱登录注册找回密码的demo项目,项目已在Gitee上开源,Gitee开源地址(有接口文档):https://gitee.com/yuandewei/Yuan-SpringBoot/tree/master

跟着我的脚本一步一步实现代码,学会了你也能自己写出来 (或者根据接口文档自己写后端)✨

⛄ 小袁有话说 今天的教程内容呢是实现邮箱注册登录账号,以及发送邮箱验证码校验验证码和找回密码等,效果图如下 文章底部有视频效果展示 在这里插入图片描述在这里插入图片描述 在这里插入图片描述

话不多说,开始今天的详细教程

数据表的设计,没啥问题,邮箱、密码、加密盐,这里只是演示邮箱功能,所以字段就没有设计太多,需要的就自己额外设计了

在这里插入图片描述

🧸 Redis安装启动

redis本次案例用于存储请求权限码和邮箱验证码

复制链接下载windows解压版 https://user.xiaoyuan-boke.com/Redis-x64-5.0.14.zip

下载好直接解压就好,打开cmd命令控制台,来到刚刚解压的位置(我这里是F盘下的redis文件夹)

输入指令启动 redis-server.exe redis.windows.conf,显示下面这样则成功启动,窗口不能关闭,关闭了redis也跟着关闭了

在这里插入图片描述

🎃 创建项目,配置文件

我这里呢是直接创建一个SpringBoot的项目,启动类加上组件扫描和映射文件扫描,同时创建基础结构目录,如图

项目如何创建这里就不一步一步教啦,相信之前看过几篇教学应该都会了

在这里插入图片描述

依赖 org.springframework.boot spring-boot-starter-parent 2.3.9.RELEASE 1.8 UTF-8 UTF-8 org.springframework.boot spring-boot-starter-web com.baomidou mybatis-plus-boot-starter 3.4.0 mysql mysql-connector-java runtime org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-data-redis com.alibaba fastjson 1.2.76 org.apache.commons commons-lang3 3.12.0 commons-codec commons-codec org.springframework.boot spring-boot-starter-mail 配置文件

application 这个也没啥问题吧,大家应该都能看懂

server: port: 8081 spring: # 数据源配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql:///user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8 username: xiaoyuan password: root redis: port: 6379 host: localhost # 时间格式转换 jackson: time-zone: GMT+8 date-format: yyyy-MM-dd HH:mm:ss profiles: # 引入application-email配置文件 include: email mybatis-plus: # mapper文件映射路径 mapper-locations: classpath*:mapper/*.xml configuration: # 打印SQL语句 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl logging: level: com.shyroke.mapper: debug

application-email.yml 这个是配置邮箱信息的

spring: mail: host: smtp.qq.com username: 自己的QQ邮箱 password: 授权码 protocol: smtp default-encoding: UTF-8 properties: mail: smtp: auth: true starttls: enable: true required: true ssl: enable: true

🎯 这里主要一点,要实现邮箱发送功能,得开启SMTP服务,打开自己的QQ邮箱,点击设置 -> 点击账户 -> 开启SMTP服务 -> 获取授权码,将授权码复制到上面的 application-email.yml 文件里

在这里插入图片描述

⚽ db

User 实体类,entity 包下

@Data @TableName("t_user") public class User { @TableId(value = "id", type = IdType.AUTO) private Integer id; private String email; private String password; private String salt; }

UserMapper 数据访问接口,mapper 包下,继承MyBatis-Plus的 BaseMapper,内部封装了单表的大部分操作

@Repository public interface UserMapper extends BaseMapper { } ⚾ constant静态变量

constant 包下新建 RedisConstant 类,

public interface RedisConstant { // Key String EMAIL = "EMAIL_"; // 邮箱缓存前缀 String EMAIL_REQUEST_VERIFY = "EMAIL_REQUEST_VERIFY_"; // 邮箱请求的权限码 // 缓存时间 int EXPIRE_TEN_SECOND = 10; // 10s int EXPIRE_ONE_MINUTE = 60; // 1分钟 int EXPIRE_FIVE_MINUTE = 5 * 60; // (五分钟) int EXPIRE_HALF_HOUR = 30 * 60; // 半小时(30分钟) int EXPIRE_ONE_DAY = 24 * 60 * 60; // (1天) }

新建 HttpStatusEnum 枚举类

@Getter public enum HttpStatusEnum { EMAIL_ERROR(4001, "邮箱格式不正确"), PARAM_ERROR(4002, "参数格式不正确"), CODE_ERROR(4002, "验证码不正确"), PASSWORD_ERROR(4003, "密码错误"), USER_NOT_EXIST(4004, "用户不存在"), EMAIL_ALREADY_EXIST(4005, "邮箱已被注册"), PASSWORD_INCONSISTENT(4006, "密码不一致"), PARAM_ILLEGAL(4007, "参数不合法"), INTERNAL_SERVER_ERROR(500, "服务器异常"), UNKNOWN_ERROR(66666, "未知异常, 联系管理员"), ILLEGAL_OPERATION(88888, "非法操作"); private final int code; private final String msg; HttpStatusEnum(int code, String msg) { this.code = code; this.msg = msg; } } 🥎vo对象 统一结果返回

vo 包下新建 R 统一返回类

@Data public class R { private Boolean success; private Integer code; private String message; private Map data = new HashMap(); // 把构造方法私有化 private R() {} // 成功静态方法 public static R ok() { R r = new R(); r.setSuccess(true); r.setCode(200); r.setMessage("成功"); return r; } // 失败静态方法 public static R error() { R r = new R(); r.setSuccess(false); r.setCode(20001); r.setMessage("失败"); return r; } // 失败静态方法 public static R error(HttpStatusEnum httpStatus) { R r = new R(); r.setSuccess(false); r.setCode(httpStatus.getCode()); r.setMessage(httpStatus.getMsg()); return r; } public R success(Boolean success){ this.setSuccess(success); return this; } public R message(String message){ this.setMessage(message); return this; } public R code(Integer code){ this.setCode(code); return this; } public R data(String key, Object value){ this.data.put(key, value); return this; } public R data(Map map){ this.setData(map); return this; } } 参数

新建param包,包下新建LoginParam类

@Getter public class LoginParam { private String email; // 邮箱 private String password; // 密码 private String passwordConfirm; // 确认密码 private String code; // 验证码 } 🏀utils工具类

utils 包下新建StringUtil类,编写自定义的一些字符串工具类(如邮箱校验,验证码生成)

public class StringUtil { /** * 邮箱校验 * * @param email 邮箱 * @return true or false */ public static boolean checkEmail(String email) { String check = "^([a-zA-Z]|[0-9])(\\w|\\-)+@[a-zA-Z0-9]+\\.([a-zA-Z]{2,4})$"; Pattern regex = Pattern.compile(check); Matcher matcher = regex.matcher(email); return matcher.matches(); } /** * 密码校验(长度 6-18,至少包含1个字母) * @param password * @return */ public static boolean checkPassword(String password) { String check = "(?=.*[a-zA-Z])[a-zA-Z0-9]{6,18}"; Pattern regex = Pattern.compile(check); Matcher matcher = regex.matcher(password); return matcher.matches(); } /** * 随机生成六位数字验证码 */ public static String randomSixCode() { return String.valueOf(new Random().nextInt(899999) + 100000); } /** * 随机生成加密盐(4位随机字母 + 4位固定特殊字符) */ public static String randomEncryptedSalt() { return RandomStringUtils.randomAlphabetic(4) + "#!$@"; } }

至此,基本完成了80%的工作,剩下的就是最重要的部分了,service业务层的设计,这部分我会详细介绍

🍊 邮箱业务 邮箱服务

service 包下创建 MailService 邮箱服务类,这里只列出了普通邮件和HTML邮件的代码,其他类型邮件网上也有的

javaMailSender Java内部封装好的邮箱发送类,只需要导入对应的依赖,直接注入即可

from 通过@Value注解读取 application-email 的username字段(也就是自己的邮箱)

message.setForm 中的三个参数,第一个是发件人(也就是自己),第二个是发件人昵称,也就是下面图片框出来的,第三个参数编码

在这里插入图片描述

@Component public class MailService { @Resource private JavaMailSender javaMailSender; @Value("${spring.mail.username}") private String from; /** * 发送简单的邮箱 * * @param to 收件人 * @param theme 标题 * @param content 正文内容 * @param cc 抄送 */ public void sendSimpleMail(String to, String theme, String content, String... cc) { // 创建邮件对象 SimpleMailMessage message = new SimpleMailMessage(); try { message.setFrom(String.valueOf(new InternetAddress(from, "小袁博客平台", "UTF-8"))); // 发件人 } catch (UnsupportedEncodingException e) { e.printStackTrace(); } message.setTo(to); // 收件人 message.setSubject(theme); // 标题 message.setText(content); // 内容 if (ArrayUtils.isNotEmpty(cc)) { message.setCc(cc); } // 发送 javaMailSender.send(message); } /** * 发送HTML邮件 * * @param to 收件人地址 * @param subject 邮件主题 * @param content 邮件内容 * @param cc 抄送地址 * @throws MessagingException 邮件发送异常 */ public void sendHtmlMail(String to, String subject, String content, String... cc) throws MessagingException { MimeMessage message = javaMailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(message, true); helper.setFrom(from); helper.setTo(to); helper.setSubject(subject); helper.setText(content, true); if (ArrayUtils.isNotEmpty(cc)) { helper.setCc(cc); } javaMailSender.send(message); } } 线程池配置

先在主目录创建 config 包,包下新建 ThreadPoolConfig 类,这是线程池的配置类,帮助统一管理线程,线程池的其他作用就不一一介绍啦,挺多的,可以B站找视频学习

@Configuration @EnableAsync // 开启线程池 public class ThreadPoolConfig { @Bean("taskExecutor") public Executor asyncServiceExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 设置核兴线程数 executor.setCorePoolSize(5); // 设置最大线程数 executor.setMaxPoolSize(20); // 设置队列大小 executor.setQueueCapacity(Integer.MAX_VALUE); // 设置线程活跃时间 60s executor.setKeepAliveSeconds(60); // 设置默认线程名称 executor.setThreadNamePrefix("小袁博客平台"); // 是否所有任务执行完毕后关闭线程池 executor.setWaitForTasksToCompleteOnShutdown(true); // 执行初始化 executor.initialize(); return executor; } } 线程服务

service 包下新建 threadService 线程服务类

发送邮箱是一件很耗时的操作,如果不开辟线程去执行在主线程执行的话,会等待很久导致请求超时,而且我们发送验证码也不需要等到邮件到了才返回给前端提示

所以我们开辟线程去执行邮箱发送操作,不占用主线程运行

@Component public class ThreadService { @Autowired private MailService mailService; /** * 发送邮箱 * @param to 收件人 * @param theme 主题 * @param content 内容 */ @Async("taskExecutor") public void sendSimpleMail(String to, String theme, String content) { mailService.sendSimpleMail(to, theme, content); } } 公共服务

新建 CommonService 公共服务接口类,注意是接口,这里讲解一下发送验证码的设计

在这里插入图片描述 使用权限码校验防止故意重复使用该接口,每次发送验证码请求前都先请求一个随机的权限码(有效时间越短越好,当然也不要太短,5 - 10s即可)

public interface CommonService { /** * 获取请求权限码 * @param emailJson 邮箱 * @return */ R getRequestPermissionCode(String emailJson); /** * 发送邮箱验证码 * @param loginParam (邮箱和权限码) * @return */ R sendEmailCode(LoginParam loginParam); }

新建 CommonServiceImpl 类实现 CommonService 接口

代码每行都写有注释,内容不难,参数校验 -> 邮箱校验 -> 权限码校验 -> 发送验证码

@Component public class CommonServiceImpl implements CommonService { @Autowired private RedisTemplate redisTemplate; @Autowired private ThreadService threadService; @Override public R getRequestPermissionCode(String emailJson) { // 非空校验 if (StringUtils.isBlank(emailJson)) return R.error(HttpStatusEnum.PARAM_ILLEGAL); // JSON转换,提取email的值 String email = JSON.parseObject(emailJson).getString("email").trim(); // 邮箱校验 if (!StringUtil.checkEmail(email)) { return R.error(HttpStatusEnum.EMAIL_ERROR); } // 随机生成权限码 String permissionCode = UUID.randomUUID().toString(); // 存入redis,缓存10s redisTemplate.opsForValue().set(RedisConstant.EMAIL_REQUEST_VERIFY + email, permissionCode, RedisConstant.EXPIRE_TEN_SECOND, TimeUnit.SECONDS); return R.ok().data("permissionCode", permissionCode); } @Override public R sendEmailCode(LoginParam loginParam) { if (loginParam == null) return R.error(HttpStatusEnum.PARAM_ILLEGAL); // 获取权限码和邮箱 String email = loginParam.getEmail(); String permissionCode = loginParam.getCode(); // 参数校验 if (StringUtils.isAnyBlank(email, permissionCode)) { return R.error(HttpStatusEnum.PARAM_ILLEGAL); }else if (!StringUtil.checkEmail(email)) { // 邮箱校验 return R.error(HttpStatusEnum.EMAIL_ERROR); }else { // 权限码比对 String rightCode = redisTemplate.opsForValue().get(RedisConstant.EMAIL_REQUEST_VERIFY + email); if (!permissionCode.equals(rightCode)) { // 不通过 return R.error(HttpStatusEnum.ILLEGAL_OPERATION); } } // 全部通过 // 随机生成6位数字验证码 String code = StringUtil.randomSixCode(); // 正文内容 String content = "亲爱的用户:\n" + "您此次的验证码为:\n\n" + code + "\n\n" + "此验证码5分钟内有效,请立即进行下一步操作。 如非你本人操作,请忽略此邮件。\n" + "感谢您的使用!"; // 发送验证码 threadService.sendSimpleMail(email, "您此次的验证码为:" + code, content); // 丢入缓存,设置5分钟过期 redisTemplate.opsForValue().set(RedisConstant.EMAIL + email, code, RedisConstant.EXPIRE_FIVE_MINUTE, TimeUnit.SECONDS); return R.ok(); } } 邮箱服务器接口

邮箱服务已经设计好了,接下来写邮箱服务的接口,然后前端运行测试一下,邮箱功能是否正常

🎯 接口路径如果不和我这个一样的话,前端api下的请求记得也要改路径

@RestController @RequestMapping("/common") @CrossOrigin public class CommonController { @Autowired private CommonService commonService; // 权限码请求接口 @PostMapping("code/request") public R getRequestPermissionCode(@RequestBody String emailJson) { return commonService.getRequestPermissionCode(emailJson); } // 邮箱验证码接口 @PostMapping("code/email") public R sendEmailCode(@RequestBody LoginParam loginParam) { return commonService.sendEmailCode(loginParam); } }

启动后端项目,启动前端项目,成功发送邮箱验证码,没有问题

在这里插入图片描述

在这里插入图片描述

🍋 登录注册业务

新建 UserService 接口类

public interface UserService extends IService { /** * 登录 * @param loginParam (邮箱和密码) * @return */ R login(LoginParam loginParam); /** * 注册 * @param loginParam (邮箱、密码、确认密码、验证码) * @return */ R register(LoginParam loginParam); /** * 找回密码 * @param loginParam (邮箱、密码、验证码) * @return */ R findPassword(LoginParam loginParam); }

新建 UserServiceImpl 类实现 UserService 接口

@Service @Transactional public class UserServiceImpl extends ServiceImpl implements UserService { @Override public R login(LoginParam loginParam) { return null; } @Override public R register(LoginParam loginParam) { return null; } @Override public R findPassword(LoginParam loginParam) { return null; } }

登录注册找回密码我一个一个来介绍

参数校验 -> 用户是否存在 -> 密码是否正确 -> 登录成功

@Override public R login(LoginParam loginParam) { if (loginParam == null) return R.error(HttpStatusEnum.PARAM_ILLEGAL); // 获取参数 String email = loginParam.getEmail(); String password = loginParam.getPassword(); if (StringUtils.isAnyBlank(email, password)) { // 非空 return R.error(HttpStatusEnum.PARAM_ILLEGAL); }else if (!StringUtil.checkEmail(email)) { // 邮箱格式校验 return R.error(HttpStatusEnum.EMAIL_ERROR); }else if (!StringUtil.checkPassword(password)) { // 密码格式 return R.error(HttpStatusEnum.PASSWORD_ERROR); } // 构件条件对象 select salt from user where email = #{email} limit 1 QueryWrapper wrapper = new QueryWrapper(); wrapper.select("salt"); wrapper.eq("email", email); wrapper.last("limit 1"); // 查询结果 User user = this.baseMapper.selectOne(wrapper); if (user == null) { // 用户不存在 return R.error(HttpStatusEnum.USER_NOT_EXIST); } // 获取加密盐 String salt = user.getSalt(); // 重新设置条件 select id from user where email = #{email} and password #{password} limit 1 wrapper.clear(); wrapper.select("id"); wrapper.eq("email", email); wrapper.eq("password", DigestUtils.md5Hex(password + salt)); wrapper.last("limit 1"); // 查询用户 user = this.baseMapper.selectOne(wrapper); return user == null ? R.error(HttpStatusEnum.PASSWORD_ERROR) : R.ok(); }

参数校验 -> 邮箱是否被注册 -> 验证码比对-> 删除redis验证码 -> 生成加密盐 -> 加密密码 -> 注册用户

@Override public R register(LoginParam loginParam) { if (loginParam == null) return R.error(HttpStatusEnum.PARAM_ILLEGAL); // 获取参数 String email = loginParam.getEmail(); String password = loginParam.getPassword(); String passwordConfirm = loginParam.getPasswordConfirm(); String code = loginParam.getCode(); if (StringUtils.isAnyBlank(email, password, passwordConfirm, code)) { // 非空 return R.error(HttpStatusEnum.PARAM_ILLEGAL); }else if (!StringUtil.checkEmail(email)) { // 邮箱格式校验 return R.error(HttpStatusEnum.EMAIL_ERROR); }else if (!password.equals(passwordConfirm)) { // 密码一致校验 return R.error(HttpStatusEnum.PASSWORD_INCONSISTENT); }else if (!StringUtil.checkPassword(password) || code.length() != 6) { // 密码格式和验证码长度校验 return R.error(HttpStatusEnum.PARAM_ILLEGAL); } // 构造查询条件对象 QueryWrapper wrapper = new QueryWrapper(); wrapper.select("id"); wrapper.eq("email", email); wrapper.last("limit 1"); // 查询用户,是否存在 if (this.baseMapper.selectOne(wrapper) != null) { return R.error(HttpStatusEnum.EMAIL_ALREADY_EXIST); } // 获取正确的验证码 String rightCode = redisTemplate.opsForValue().get(RedisConstant.EMAIL + email); if (!code.equals(rightCode)) { // 验证码比对 return R.error(HttpStatusEnum.CODE_ERROR); } // 删除验证码 redisTemplate.delete(RedisConstant.EMAIL + email); // 注册用户 User user = new User(); // 获取加密盐 String salt = StringUtil.randomEncryptedSalt(); // 邮箱 user.setEmail(email); // 密码加密(原明文密码 + 随机加密盐) md5加密 user.setPassword(DigestUtils.md5Hex(password + salt)); // 加密盐 user.setSalt(salt); // 插入数据 return this.baseMapper.insert(user) == 0 ? R.error(HttpStatusEnum.UNKNOWN_ERROR) : R.ok(); }

参数校验 -> 用户是否存在 -> 验证码校验 -> 删除redis验证码 -> 覆盖密码

@Override public R findPassword(LoginParam loginParam) { if (loginParam == null) return R.error(HttpStatusEnum.PARAM_ILLEGAL); // 获取参数 String email = loginParam.getEmail(); String password = loginParam.getPassword(); String code = loginParam.getCode(); if (StringUtils.isAnyBlank(email, password, code)) { // 非空 return R.error(HttpStatusEnum.PARAM_ILLEGAL); }else if (!StringUtil.checkEmail(email)) { // 邮箱格式校验 return R.error(HttpStatusEnum.EMAIL_ERROR); }else if (!StringUtil.checkPassword(password) || code.length() != 6) { // 密码格式和验证码长度校验 return R.error(HttpStatusEnum.PARAM_ILLEGAL); } // 构造查询条件对象 QueryWrapper wrapper = new QueryWrapper(); wrapper.select("id", "salt"); wrapper.eq("email", email); wrapper.last("limit 1"); // 查询用户,是否存在 User user = this.baseMapper.selectOne(wrapper); if (user == null) { return R.error(HttpStatusEnum.USER_NOT_EXIST); } // 获取正确的验证码 String rightCode = redisTemplate.opsForValue().get(RedisConstant.EMAIL + email); if (!code.equals(rightCode)) { // 验证码比对 return R.error(HttpStatusEnum.CODE_ERROR); } // 删除验证码 redisTemplate.delete(RedisConstant.EMAIL + email); // 修改密码 User user1 = new User(); user1.setId(user.getId()); user1.setPassword(DigestUtils.md5Hex(password + user.getSalt())); // 修改 return this.baseMapper.updateById(user1) == 0 ? R.error(HttpStatusEnum.UNKNOWN_ERROR) : R.ok(); } 接口

🎯 接口路径如果不和我这个一样的话,前端api下的请求记得也要改路径

@RestController @RequestMapping("/user") @CrossOrigin public class UserController { @Autowired private UserService userService; // 登录 @PostMapping("login") public R login(@RequestBody LoginParam loginParam) { return userService.login(loginParam); } // 注册 @PostMapping("register") public R register(@RequestBody LoginParam loginParam) { return userService.register(loginParam); } // 找回密码 @PostMapping("findPassword") public R findPassword(@RequestBody LoginParam loginParam) { return userService.findPassword(loginParam); } } 🥭 视频演示

最后看看视频演示的效果

SpringBoot+Vue实现邮箱登录注册找回密码

🌹 结束语

好了,整篇的教程呢到这也就结束,整篇教程即为原创一字一字手敲,也花了心思想怎么写怎么设计才能更好的直观简洁展示给大家,让大家能看懂

最后,关于教程还有什么不懂的可以评论区留言,我一定会回复的,或者有什么更好的建议和想法也可以在评论区留言,看到好的我会一一采纳,感谢大家的支持

再一次附上Gitee开源地址:https://gitee.com/yuandewei/Yuan-SpringBoot/tree/master 不用大伙翻上去复制了

都看到这里啦,点点赞呀 😋感谢阅读 😘


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3