Springboot整合Redis实现邮箱验证码

Springboot整合Redis实现邮箱验证码

开启邮箱服务

打开https://mail.qq.com/ 登录你自己的qq账号

选择账户

img

点击开启 STMP服务:

img

发送短信:

img

发送完,点击我已发送,然后得到密码:

img

Springboot配置邮箱

pom依赖

1<dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-mail</artifactId> 4</dependency>

yml配置

1spring: 2 mail: 3 # 配置 SMTP 服务器地址 4 host: smtp.qq.com 5 # 发送者邮箱 6 username: XXXXXXXX 7 # 配置密码,注意不是真正的密码,而是刚刚申请到的授权码 8 password: XXXXXXXX 9 # 端口号465或587 10 port: 587 11 # 默认的邮件编码为UTF-8 12 default-encoding: UTF-8

编写邮箱工具类

1import lombok.extern.slf4j.Slf4j; 2import org.springframework.beans.factory.annotation.Autowired; 3import org.springframework.beans.factory.annotation.Value; 4import org.springframework.mail.javamail.JavaMailSender; 5import org.springframework.mail.javamail.MimeMessageHelper; 6import org.springframework.stereotype.Component; 7 8import javax.mail.MessagingException; 9import javax.mail.internet.MimeMessage; 10import java.util.Date; 11 12@Component 13@Slf4j 14public class EmailUtils { 15 @Autowired 16 JavaMailSender javaMailSender; 17 18 @Value("${spring.mail.username}") 19 String username; 20 21 public void sendHtml(String title, String html, String to) { 22 MimeMessage mailMessage = javaMailSender.createMimeMessage(); 23 //需要借助Helper类 24 MimeMessageHelper helper = new MimeMessageHelper(mailMessage); 25 try { 26 helper.setFrom(username); // 必填 27 helper.setTo(to); // 必填 28// helper.setBcc("密送人"); // 选填 29 helper.setSubject(title); // 必填 30 helper.setSentDate(new Date());//发送时间 31 helper.setText(html, true); // 必填 第一个参数要发送的内容,第二个参数是不是Html格式。 32 javaMailSender.send(mailMessage); 33 } catch (MessagingException e) { 34 log.error("发送邮件失败", e); 35 } 36 } 37}

Springboot整合Redis

导入maven

1<dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-data-redis</artifactId> 4</dependency> 5<dependency> 6 <groupId>org.apache.commons</groupId> 7 <artifactId>commons-pool2</artifactId> 8</dependency>

配置Redis

1spring: 2 redis: 3 # Redis数据库索引(默认为0) 4 database: 1 5 # Redis服务器地址 6 host: XXX.XXX.XXX.XXX 7 # Redis服务器连接端口 8 port: 6379 9 # Redis服务器连接密码(默认为空) 10 # password: 11 # 连接超时时间 12 timeout: 10s 13 lettuce: 14 pool: 15 # 连接池最大连接数 16 max-active: 200 17 # 连接池最大阻塞等待时间(使用负值表示没有限制) 18 max-wait: -1ms 19 # 连接池中的最大空闲连接 20 max-idle: 10 21 # 连接池中的最小空闲连接 22 min-idle: 0

Redis配置防止序列化

1@Configuration 2public class RedisConfig { 3 4 @Bean 5 public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { 6 RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); 7 // 设置 key的序列化方式 防止默认的jdk序列化方式出现二进制码 看不懂 8 redisTemplate.setKeySerializer(new StringRedisSerializer()); 9 10 ObjectMapper objectMapper = new ObjectMapper(); 11 objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 12 objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance , 13 ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); 14 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 15 16 Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); 17 jackson2JsonRedisSerializer.setObjectMapper(objectMapper); 18 19 redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value的序列化类型 20 21 redisTemplate.setConnectionFactory(connectionFactory); 22 return redisTemplate; 23 } 24}

Redis工具类

1@SuppressWarnings(value = {"unchecked"}) 2@Component 3@Slf4j 4public class RedisUtils { 5 private static RedisTemplate<String, Object> staticRedisTemplate; 6 7 private final RedisTemplate<String, Object> redisTemplate; 8 9 public RedisUtils(RedisTemplate<String, Object> redisTemplate) { 10 this.redisTemplate = redisTemplate; 11 } 12 13 // Springboot启动成功之后会调用这个方法 14 @PostConstruct 15 public void initRedis() { 16 // 初始化设置 静态staticRedisTemplate对象,方便后续操作数据 17 staticRedisTemplate = redisTemplate; 18 } 19 20 /** 21 * 缓存基本的对象,Integer、String、实体类等 22 * 23 * @param key 缓存的键值 24 * @param value 缓存的值 25 */ 26 public static <T> void setCacheObject(final String key, final T value) { 27 staticRedisTemplate.opsForValue().set(key, value); 28 } 29 30 /** 31 * 缓存基本的对象,Integer、String、实体类等 32 * 33 * @param key 缓存的键值 34 * @param value 缓存的值 35 * @param timeout 时间 36 * @param timeUnit 时间颗粒度 37 */ 38 public static <T> void setCacheObject(final String key, final T value, final long timeout, final TimeUnit timeUnit) { 39 staticRedisTemplate.opsForValue().set(key, value, timeout, timeUnit); 40 } 41 42 /** 43 * 获得缓存的基本对象。 44 * 45 * @param key 缓存键值 46 * @return 缓存键值对应的数据 47 */ 48 public static <T> T getCacheObject(final String key) { 49 return (T) staticRedisTemplate.opsForValue().get(key); 50 } 51 52 /** 53 * 删除单个对象 54 * 55 * @param key 56 */ 57 public static boolean deleteObject(final String key) { 58 return Boolean.TRUE.equals(staticRedisTemplate.delete(key)); 59 } 60 61 /** 62 * 获取单个key的过期时间 63 * 64 * @param key 65 * @return 66 */ 67 public static Long getExpireTime(final String key) { 68 return staticRedisTemplate.getExpire(key); 69 } 70 71 /** 72 * 发送ping命令 73 * redis 返回pong 74 */ 75 public static void ping() { 76 String res = staticRedisTemplate.execute(RedisConnectionCommands::ping); 77 log.info("Redis ping ==== {}", res); 78 } 79}

验证码发送及过期实现

业务:涉及到邮箱验证码的功能:用户注册,密码重置

思路分析:首先通过hutool工具类随机生成验证码,通过邮箱发送工具发送。验证码过期使用Redis设计过期时间实现,唯一性则通过key值保证,键由 功能前缀+邮箱(邮箱设置索引保证唯一性)+业务功能 来组成。举个例子

String key = Constants.EMAIL_CODE + EmailCodeEnum.RESET_PASSWORD.getValue() + email;所涉及到的枚举类如下。

1@Getter 2public enum EmailCodeEnum { 3 REGISTER("REGISTER", "register:"), 4 RESET_PASSWORD("RESETPASSWORD", "resetPassword:"), 5 LOGIN("LOGIN", "login:"), 6 CHANGE_PASSWORD("CHANGEPASSWORD", "changePassword:"), 7 UNKNOWN("", ""); 8 9 private final String type; 10 private final String value; 11 12 EmailCodeEnum(String type, String value) { 13 this.type = type; 14 this.value = value; 15 } 16 17 public static String getValue(String type) { 18 EmailCodeEnum[] values = values(); 19 for (EmailCodeEnum codeEnum : values) { 20 if (type.equals(codeEnum.type)) { 21 return codeEnum.value; 22 } 23 } 24 return ""; 25 } 26 27 public static EmailCodeEnum getEnum(String type) { 28 EmailCodeEnum[] values = values(); 29 for (EmailCodeEnum codeEnum : values) { 30 if (type.equals(codeEnum.type)) { 31 return codeEnum; 32 } 33 } 34 return UNKNOWN; 35 } 36}
1public interface Constants { 2 3 // 用户名次前缀 4 String USER_NAME_PREFIX = "partner_"; 5 6 // 时间的规则 7 String DATE_RULE_YYYYMMDD = "yyyyMMdd"; 8 9 String EMAIL_CODE = "email.code."; 10 11}

发送验证码时,设置过期时间,并通过合理的key设计,保证唯一性

RedisUtils.setCacheObject(key, code, TIME_IN_MS5, TimeUnit.MILLISECONDS);Redis存储设置过期时间

1/** 2 * 邮箱发送验证码 3 * @param email 4 * @param type 5 */ 6 @Override 7 public void sendEmail(String email, String type) { 8 String emailPrefix = EmailCodeEnum.getValue(type); 9 if (StrUtil.isBlank(emailPrefix)) { 10 throw new ServiceException("不支持的邮箱验证类型"); 11 } 12 String key = Constants.EMAIL_CODE + emailPrefix + email; 13 Long expireTime = RedisUtils.getExpireTime(key); 14 // 限制超过1分钟才可以继续发送邮件,判断过期时间是否大于4分钟 15 if (expireTime != null && expireTime > 4 * 60) { 16 throw new ServiceException("发送邮箱验证过于频繁"); 17 } 18 19 Integer code = Integer.valueOf(RandomUtil.randomNumbers(6)); 20 log.info("本次验证的code是:{}", code); 21 String context = "<b>尊敬的用户:</b><br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;您好," + 22 "Odin交友网提醒您本次的验证码是:<b>{}</b>," + 23 "有效期5分钟。<br><br><br><b>Odin交友网</b>"; 24 String html = StrUtil.format(context, code); 25 if ("REGISTER".equals(type) ) { 26 User dbUser = getOne(new QueryWrapper<User>().eq("email", email)); 27 if (dbUser != null) { 28 throw new ServiceException("邮箱已经注册!"); 29 } 30 } 31 // 线程异步执行,防止网络阻塞 32 ThreadUtil.execAsync(() -> { 33 emailUtils.sendHtml("【Odin交友网】验证提醒", html, email); 34 }); 35 RedisUtils.setCacheObject(key, code, TIME_IN_MS5, TimeUnit.MILLISECONDS); 36 }

为了保证验证码不被重复使用,在验证通过之后直接删除Redis中的数据。

1private void validateEmail(String emailKey,String emailCode) { 2 // 从Redis中获取验证码 3 Integer code = RedisUtils.getCacheObject(emailKey); 4 if (code == null) { 5 throw new ServiceException("验证码失效"); 6 } 7 if (!emailCode.equals(code.toString())) { 8 throw new ServiceException("验证码错误"); 9 } 10 // 验证通过后立即删除 11 RedisUtils.deleteObject(emailKey); 12 }

完整业务代码如下

1@Service 2@Slf4j 3public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { 4 5 private static final long TIME_IN_MS5 = 5 * 60 * 1000; // 表示5分钟的毫秒数 6 7 @Resource 8 private EmailUtils emailUtils; 9 10 /** 11 * 用户登陆 12 * @param user 13 * @return 14 */ 15 @Override 16 public User login(UserRequest user) { 17 User dbUser = null; 18 try { 19 dbUser = getOne(new UpdateWrapper<User>().eq("username", user.getUsername()) 20 .or().eq("email", user.getUsername())); 21 } catch (Exception e) { 22 throw new RuntimeException("系统异常"); 23 } 24 if (dbUser == null) { 25 throw new ServiceException("未找到用户"); 26 } 27 if (!user.getPassword().equals(dbUser.getPassword())) { 28 throw new ServiceException("用户名或密码错误"); 29 } 30 return dbUser; 31 } 32 33 @Override 34 public User register(UserRequest user) { 35 String key = Constants.EMAIL_CODE + EmailCodeEnum.REGISTER.getValue() + user.getEmail(); 36 String emailCode = user.getEmailCode(); 37 validateEmail(key ,emailCode); 38 try { 39 User saveUser = new User(); 40 BeanUtils.copyProperties(user, saveUser); 41 return saveUser(saveUser); 42 } catch (Exception e) { 43 throw new RuntimeException("数据库异常",e); 44 } 45 } 46 47 /** 48 * 邮箱发送验证码 49 * @param email 50 * @param type 51 */ 52 @Override 53 public void sendEmail(String email, String type) { 54 String emailPrefix = EmailCodeEnum.getValue(type); 55 if (StrUtil.isBlank(emailPrefix)) { 56 throw new ServiceException("不支持的邮箱验证类型"); 57 } 58 String key = Constants.EMAIL_CODE + emailPrefix + email; 59 Long expireTime = RedisUtils.getExpireTime(key); 60 // 限制超过1分钟才可以继续发送邮件,判断过期时间是否大于4分钟 61 if (expireTime != null && expireTime > 4 * 60) { 62 throw new ServiceException("发送邮箱验证过于频繁"); 63 } 64 65 Integer code = Integer.valueOf(RandomUtil.randomNumbers(6)); 66 log.info("本次验证的code是:{}", code); 67 String context = "<b>尊敬的用户:</b><br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;您好," + 68 "Odin交友网提醒您本次的验证码是:<b>{}</b>," + 69 "有效期5分钟。<br><br><br><b>Odin交友网</b>"; 70 String html = StrUtil.format(context, code); 71 if ("REGISTER".equals(type) ) { 72 User dbUser = getOne(new QueryWrapper<User>().eq("email", email)); 73 if (dbUser != null) { 74 throw new ServiceException("邮箱已经注册!"); 75 } 76 } 77 // 线程异步执行,防止网络阻塞 78 ThreadUtil.execAsync(() -> { 79 emailUtils.sendHtml("【Odin交友网】验证提醒", html, email); 80 }); 81 RedisUtils.setCacheObject(key, code, TIME_IN_MS5, TimeUnit.MILLISECONDS); 82 } 83 84 /** 85 * 重置密码功能 86 * @param userRequest 87 * @return 88 */ 89 @Override 90 public String passwordReset(UserRequest userRequest) { 91 String email = userRequest.getEmail(); 92 String key = Constants.EMAIL_CODE + EmailCodeEnum.RESET_PASSWORD.getValue() + email; 93 User dbUser = getOne(new UpdateWrapper<User>().eq("email", email)); 94 if (dbUser == null) { 95 throw new ServiceException("未找到当前用户"); 96 } 97 validateEmail(key,userRequest.getEmailCode()); 98 String newPass = "admin"; 99 dbUser.setPassword(newPass); 100 try { 101 updateById(dbUser); // 设置到数据库 102 } catch (Exception e) { 103 throw new RuntimeException("重置失败", e); 104 } 105 return newPass; 106 } 107 108 /** 109 * 用户注册 110 * @param user 前端传入用户 111 * @return 注册用户信息 112 */ 113 private User saveUser(User user) { 114 User dbUser = getOne(new UpdateWrapper<User>().eq("username", user.getUsername())); 115 if (dbUser != null) { 116 throw new ServiceException("用户已存在"); 117 } 118 if (StrUtil.isBlank(user.getName())) { 119 String name = Constants.USER_NAME_PREFIX + DateUtil.format(new Date(), DATE_RULE_YYYYMMDD) 120 + RandomUtil.randomString(4); 121 user.setName(name); 122 } 123 if (StrUtil.isBlank(user.getPassword())) { 124 user.setPassword("admin"); // 设置默认密码 125 } 126 // 设置用户唯一uid 127 user.setUid(IdUtil.fastSimpleUUID()); 128 boolean save = save(user); 129 if (!save) { 130 throw new RuntimeException("注册失败"); 131 } 132 return user; 133 } 134 135 /** 136 * 邮箱验证码验证功能 137 * @param emailKey Redis中存储的key 138 * @param emailCode 前端传入的验证码 139 */ 140 private void validateEmail(String emailKey,String emailCode) { 141 // 从Redis中获取验证码 142 Integer code = RedisUtils.getCacheObject(emailKey); 143 if (code == null) { 144 throw new ServiceException("验证码失效"); 145 } 146 if (!emailCode.equals(code.toString())) { 147 throw new ServiceException("验证码错误"); 148 } 149 // 验证通过后立即删除 150 RedisUtils.deleteObject(emailKey); 151 } 152}
点赞
收藏

评论区

加载中...

相关推荐

python实现邮件发送

前言使用python的第三方库yagmail实现邮件发送的功能yagmail官网文档:第一步:申请一个邮箱作为发送邮箱此处以网易邮箱为例,因为使用python代码实现邮件的发送,需要开启邮箱的授权密码功能,用生成的授权密码作为发送邮件的密码,以下步骤为开启网易邮箱的授权密码功能。第二步:安装yagmail库languagepipinstally

java实现使用QQ邮箱发送验证码功能

首先当然是导入jar包了啊如果是maven项目可以进maven资源库进行搜索导入,在此附上地址:https://mvnrepository.com这是需要导入的jar包 commonsemail1.x.jar、mail.jar  activation.jar,其中activation.jar我并没有导入,但是还是发送成功了,但看网上有蛮多都说需

PDF文件转化成mobi格式,亲测kindle或者iReader可用!

点击连接,然后选择要转换的文件比如我的是MySQL电子书书,选择输入文件和输出文件的格式,转换,对了记得输入邮箱号码,转化完毕会发送连接到邮箱提供下载。或者,网络流畅的情况下不用输入邮箱号码,转化完毕会自动重定向到下载页面。

云函数手撸用户体系

使用云函数实现用户系统数据库为腾讯云TDSQL其它服务商云函数通用只需修改index.js返回参数即可主要有用户注册用户登陆邮箱发送验证码邮箱验证码校检邮箱绑定邮箱解绑邮箱验证码登陆生成token校验token其它功能可以在此基础上拓展纯手撸代码云函数环境为nodejs12.13由于我比较穷就不带大家使用短信服务了

SpringBoot入门 (十) 发送邮件

本文记录学习在SpringBoot中发送邮件。一邮件发送过程发送邮件是一个我们在项目中经常会用到的功能,如在用户注册时发送验证码,账户激活等都会用到。完整的一个邮件发送过程主要包含以下几个步骤:1发件人在用户邮件代理上写邮件内容及收件人的邮箱地址;2用户邮件代理根据发件人填写的邮件信息,生成一封符合邮件格式的邮件;

Spring Boot demo系列(七):邮件服务

2021.2.24更新1概述SpringBoot整合邮件服务,包括发送普通的文本邮件以及带附件的邮件。2邮箱选择这里选择的是QQ邮箱作为发送的邮箱,当然也可以选择其他的邮箱,只是具体的配置不一样。使用QQ邮箱的话,需要在个人设置中开启SMTP服务:!在这里插入