背景
小组项目要做一个邮箱找回密码的功能。 需要用到redis
大概流程如下:
- 第一步,用户填写邮箱,并点击“获取验证码”,浏览器发送请求,调用获取验证码接口。然后,服务端根据邮箱,生成验证码,发送验证码给这个邮箱,并将验证码和邮箱和有效期放到redis/内存中。
- 用户在邮箱中查到验证码后→填写到登录界面→点击登录→前端请求登录接口,携带邮箱和验证码参数。
- 服务器收到请求后,到redis中取到这个邮箱对应的信息→校验验证码是否正确,并验证是否过期,如果验证码正确且没有过期,则正常登录。
Redis
先介绍一下redis
Redis 是完全开源免费的,,是一个高性能的key-value非关系性数据库(NoSql)。
Redis 与其他 key - value 缓存产品有以下三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
Redis可以存储键与5种不同数据结构类型之间的映射,这5种数据结构类型分别为String(字符串)、List(列表)、Set(集合)、Hash(散列)和 Zset(有序集合)。
下载Redis
直接访问github网址:https://github.com/MSOpenTech...,
如下图所示:
我下载的是windows版本
启动Redis
redis.windows.conf为redis配置文件,相关参数可以在这里配置,如:端口等,我这里使用默认参数,暂不修改,默认端口为6379。双击redis-server.exe启动,则出现如下图所示,则启动成功。
SpringBoot中使用Redis
在项目的pom.xml中添加如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
application.yml 中配置如下
# redis 配置
redis:
# 地址
host: localhost
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
编写Redis操作工具类
我们对redis操作需要用到RedisTemplate
RedisTemplate 是简化 Redis 数据库访问代码的助手类,也是 Spring Data Redis 对 Redis 支持的中心类。
我们这里将RedisTemplate实例包装成一个工具类,便于对redis进行数据操作。
创建spring redis 工具类
@Component
public class RedisCache {
@Autowired
public RedisTemplate redisTemplate;
// redis序列化器 类型为string
@Autowired(required = false)
public void setRedisTemplate(RedisTemplate redisTemplate) {
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(stringSerializer);
this.redisTemplate = redisTemplate;
}
}
下面编写工具方法
1.设置缓存对象
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
比如说我们要将验证码存入redis,就可以这么做
// 验证码存入redis并设置过期时间
redisCache.setCacheObject("mail_code:" + username, verifyCode, 5, TimeUnit.MINUTES);
这里我们设置
key为 mail_code: + username,表示某个用户的邮箱验证码
value为 verifycode , 表示验证码
timeout为5, timeUnit为 MINUTES, 表示过期时间为5分钟。
2.获得缓存的基本对象。
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
比如使用如下: 根据key 获取 value
String verifyCode = redisCache.getCacheObject("mail_code: + usrname);
3.设置有效时间
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
比如我们在用户成功使用完该验证码后,想将该验证码设置为过期:
// 将验证码过期
redisCache.expire("mail_code: + usrname, 0, TimeUnit.SECONDS);
表示该键值对会在0秒后过期。
4.删除对象
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
除这几个常用的外,还编写了几个类,比如根据key,写入新的值,覆盖旧的值等,在此就不一一列出。
同时我们还可以编写,对Set,Hash,Map,List 类型的操作工具类,但是我没有用到,所以没有编写。
使用邮箱发送验证码
这里用到SMTP服务
SMTP
SMTP的全称是“Simple Mail Transfer Protocol”,即简单邮件传输协议。
什么是 SMTP?
SMTP代表简单邮件传输协议,它是电子邮件发送的行业标准协议。
使用 SMTP,你可以从邮件客户端(如 Microsoft Outlook)向接收电子邮件服务器发送、中继或转发邮件。发件人将使用SMTP 服务器来执行发送电子邮件的过程。
在考虑是使用 SMTP 还是 IMAP 时要记住的关键是 SMTP 是关于发送电子邮件的。因此,如果你希望在你的应用程序中启用电子邮件发送,那么你需要继续使用 SMTP over IMAP。
什么是 IMAP?
如果 SMTP 是关于发送的,那么 IMAP 是什么?
简而言之,IMAP(Internet 访问消息协议)是一种电子邮件协议,用于管理和检索来自接收服务器的电子邮件消息。
由于 IMAP 处理消息检索,因此你将无法使用 IMAP 协议发送电子邮件。相反,IMAP 将用于接收消息。
除了 IMAP,还有另一种用于接收电子邮件的协议 — 称为 POP3。感兴趣可以查一下
简单来说, SMTP用于发送, IMAP用于接收
由于只发送,所以我只用到SMTP。
发件邮箱要求
这里使用的是163邮箱,
1.设置POP3/SMTP/IMAP
2.开启服务
开启会让设置授权码,授权码要记牢!后面需要写在配置里
3.查看服务器地址
再往下翻有163 SMTP服务器地址 等下需要配置在application.yml中
代码实现
1.pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
2.application.yml配置
# 配置邮箱服务器,账号密码等
mail:
host: smtp.163.com
username: xxx@163.com
password: xxx
port:
default-encoding: utf-8
protocol: smtp
properties:
mail.smtp.auth: true
mail.smtp.starttls.enable: true
mail.smtp.starttls.required: true
mail.smtp.socketFactory.port: 465
mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
mail.smtp.socketFactory.fallback: false
3.获取验证码controller
@RestController
@RequestMapping("/mail")
public class MailController {
@Autowired
private MailService mailService;
/**
* 获取重置密码的验证码
*/
@GetMapping("/getCode")
public void getCode(@RequestParam String staffNumber, @RequestParam String mailAddress){
mailService.getCode(staffNumber,mailAddress);
}
}
4. ServiceImpl层
@Service
public class MailServiceImpl implements MailService {
@Autowired
private RedisCache redisCache;
@Autowired
private JavaMailSender mailSender;
@Autowired
private UserRepository userRepository;
@Value("${spring.mail.username}")
private String mailUserName;
@Value("${mail.code.overtime}")
private Integer overtime; // 过期时间
/**
* 获取重置密码的验证码
*
* @param username 用户账号
* @param mailAddress 用户邮箱
* @return
*/
@Override
public void getCode(String username, String mailAddress) {
// 非空校验
if (null == username || "".equals(username)) throw new EntityNotFoundException("账号不能为空!");
if (null == mailAddress || "".equals(mailAddress)) throw new EntityNotFoundException("邮箱不能为空!");
// 账号存在校验
User user = userRepository.findByUsernameAndDeletedIsFalse(username);
if (null == user) throw new EntityNotFoundException("账号不存在!");
if (!user.getPhone().equals(mailAddress)) throw new EntityNotFoundException("输入邮箱和预留邮箱不一致!");
verifyCode = String.valueOf(new Random().nextInt(899999) + 100000);//生成短信验证码
// 验证码存入redis并设置过期时间
redisCache.setCacheObject(Constants.MAIL_CODE_KEY + username, verifyCode, overtime, TimeUnit.MINUTES);
// 编写邮箱内容
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("<html><head><title></title></head><body>");
stringBuilder.append("您好<br/>");
stringBuilder.append("您的验证码是:").append(verifyCode).append("<br/>");
stringBuilder.append("您可以复制此验证码并返回至设备管理系统找回密码页面,以验证您的邮箱。<br/>");
stringBuilder.append("此验证码只能使用一次,在");
stringBuilder.append(overtime.toString());
stringBuilder.append("分钟内有效。验证成功则自动失效。<br/>");
stringBuilder.append("如果您没有进行上述操作,请忽略此邮件。");
MimeMessage mimeMessage = mailSender.createMimeMessage();
// 发件配置并发送邮件
try {
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
//这里只是设置username 并没有设置host和password,因为host和password在springboot启动创建JavaMailSender实例的时候已经读取了
mimeMessageHelper.setFrom(mailUserName);
mimeMessageHelper.setTo(mailAddress);
mimeMessage.setSubject("邮箱验证-重置密码");
mimeMessageHelper.setText(stringBuilder.toString(), true);
mailSender.send(mimeMessage);
} catch (MessagingException e) {
e.printStackTrace();
}
}
主要逻辑:
1.数据校验
主要校验前台传入数据非空,以及user账号是否存在,输入邮箱和预留邮箱是否一致等
// 非空校验
if (null == username || "".equals(username)) throw new EntityNotFoundException("账号不能为空!");
if (null == mailAddress || "".equals(mailAddress)) throw new EntityNotFoundException("邮箱不能为空!");
// 账号存在校验
User user = userRepository.findByUsernameAndDeletedIsFalse(username);
if (null == user) throw new EntityNotFoundException("账号不存在!");
if (!user.getPhone().equals(mailAddress)) throw new EntityNotFoundException("输入邮箱和预留邮箱不一致!");
2.验证码存入redis
首先查看该key,是否存在于redis缓存中。
verifyCode = String.valueOf(new Random().nextInt(899999) + 100000);//生成短信验证码
// 验证码存入redis并设置过期时间
redisCache.setCacheObject(Constants.MAIL_CODE_KEY + username, verifyCode, overtime, TimeUnit.MINUTES);
3.编写邮件内容
代码如上,此处省略
4.发送并配置邮件
这里用到了JavaMailSender来发送邮件。
最早期的时候会使用JavaMail相关api来写发送邮件的相关代码
spring推出了JavaMailSender简化了邮件发送的过程,
在之后springboot对此进行了封装就有了现在的spring-boot-starter-mail
// 发件配置并发送邮件
try {
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
//这里只是设置username 并没有设置host和password,因为host和password在springboot启动创建JavaMailSender实例的时候已经读取了
mimeMessageHelper.setFrom(mailUserName);
mimeMessageHelper.setTo(mailAddress);
mimeMessage.setSubject("邮箱验证-重置密码");
mimeMessageHelper.setText(stringBuilder.toString(), true);
mailSender.send(mimeMessage);
} catch (
MessagingException e) {
e.printStackTrace();
}
- 验证验证码正确性
从redis中获取该验证码,若从redis中获取值为null, 则验证码已经过期
String cacheCode = redisCache.getCacheObject(Constants.MAIL_CODE_KEY + num); // 获取缓存中该账号的验证码
if (cacheCode == null) {
throw new EntityNotFoundException("验证码已过期,请重新获取!");
}
// 验证码正确性校验
if (!cacheCode.equals(codes)) {
throw new EntityNotFoundException("验证码错误!");
}
参考
https://www.cnblogs.com/fonks...
https://www.cnblogs.com/equal...
https://juejin.cn/post/709110...
https://ost.51cto.com/posts/1262
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。