目录
②根据前端的请求数据和Redis存储结构考虑,创建对应的对象传输类,关于什么是Dto类可以参考我上一篇文章:
场景:
老用户想通过邮箱获取验证码实现一键登录,或者新用户注册并登陆,前端发送获取验证码的请求-->后端生成验证码发送给目标邮箱,并存储于Redis缓存中->用户收到验证码-->填写验证码向后端发送登陆请求-->后端接收发送来的邮箱和验证码的JSON与Redis中获取的对比,响应给前端登陆成功与否
1.完成发送邮箱的功能
①引入email的依赖和Redis的相关依赖
②根据前端的请求数据和Redis存储结构考虑,创建对应的对象传输类,关于什么是Dto类可以参考我上一篇文章:
Dto类的使用以及Spring中对于前端请求处理的注意事项https://blog.csdn.net/qq_52692506/article/details/132025236
一、前端请求:
1.验证码请求和请求体
2. 登陆验证请求及其请求体数据
我们发现都带有他们都带有mail这个数据,同时登陆请求还需要验证码Code,而数据库对应的实体类中的成员变量不足以满足数据的接收,所以要创建Dto类完成接收
二、创建对应的Dto类:UserDto类:
这里其实可以选择继承User类,这样的话只需要多加一个code成员就行,能接收到前端发来的数据就行
③创建发送验证码的工具类 EmailCodeUtils
工具类内部要实现发送信息到目标邮箱+验证码生成方法
一、发送到目标邮箱方法:
private static void sendEmail(String recipientEmail, String subject, String content) {
String senderEmail = "你自己的邮箱"; // 发件人邮箱地址
String senderPassword = "你的授权码"; // 发件人邮箱密码或授权码
// 设置邮件服务器属性,我这里是网易邮箱,你们用其他邮箱的可以去搜搜对应的属性
Properties properties = new Properties();
properties.put("mail.transport.protocol", "smtp");// 连接协议
properties.put("mail.smtp.host", "smtp.163.com"); // 邮件服务器主机地址,根据你的邮箱配置填写
properties.put("mail.smtp.port", "465"); // 邮件服务器端口号,网易的SSL端口号,经过SSL加密
properties.put("mail.smtp.auth", "true"); // 启用身份验证
properties.put("mail.smtp.starttls.enable", "true"); // 启用TLS加密
properties.put("mail.smtp.ssl.enable", "true");// 设置是否使用ssl安全连接 ---一般都使用
properties.put("mail.debug", "true");// 设置是否显示debug信息 true 会在控制台显示相关信息
// 创建会话
Session session = Session.getInstance(properties, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(senderEmail, senderPassword);
}
});
try {
// 创建邮件对象
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(senderEmail));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipientEmail));
message.setSubject(subject);
message.setText(content);
// 发送邮件
Transport.send(message);
System.out.println("邮件已发送成功!");
} catch (MessagingException e) {
e.printStackTrace();
}
}
上述代码中,通过JavaMail的会话对象Session完成了与邮箱的身份校验,注意这里可能一些邮箱服务器不支持账号密码登录,所以要去邮箱网站获取授权码代替密码,来完成身份验证
以网易邮箱为例:
1.确保开通smtp服务
2. 添加授权码用于身份校验
二、生成验证码方法(可以随便写,主要是满足需求即可)
我这里就是生成一个随机的六位验证码
private static String generateVerificationCode() {
// 生成6位随机验证码
Random random = new Random();
StringBuilder verificationCode = new StringBuilder();
for (int i = 0; i < 6; i++) {
verificationCode.append(random.nextInt(10));
}
return verificationCode.toString();
}
2.完成Redis的配置
二、完成Redis的一些配置类
配置Redis的配置 ,包括访问端口,库号,超时时间,这里的host和port我是从application.yml获取的,也可以直接赋值给他们,其中host="localhost" port="6379"
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedisConfig {
private String host;
private String port;
private Integer database;
@Bean
public RedissonClient redisson(){
// 创建配置
Config config = new Config();
String redisAddress="redis://"+host+":"+port;
System.out.println(redisAddress);
//setdatebase是指redis库号
config.useSingleServer().setAddress(redisAddress).setDatabase(database).setTimeout(3000);
//创建实例
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
规定Redis的存储类型(泛型),键值对的形式,我们写入读取都是以键值对的形式(通过key找value),然后就是一些初始化配置
@Configuration
public class RedisTemplateConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate <String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(RedisSerializer.string());
return redisTemplate;
}
}
3.完成Redis存储和读取数据
我下面给了我服务层的发送验证码的代码,需要注意的就是通过@Resource注入RedisTemplate,存储 键值对数据(redisKey 和 UserDto) 到Redis中,后面我们就可以通过 redisKey找到我们的数据 UserDto类的userDto
完整代码如下:(这里我在写入缓存之后,用日志输出了一下,方便debug)
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private UserMapper userMapper;
@Override
public String sendMsg(String mail) {
//前端设置了非空,所以不需要判断非空其实,这里是为了postman测试用
if (!StringUtils.isNotEmpty(mail))
throw new CustomException("email必须非空");
String code = EmailCodeUtil.send(mail);
UserDto userDto = new UserDto(mail, code);
log.info("成功发出邮件,验证码应为:{}", code);
//用Redis存储
// 作为唯一标识
String redisKey = String.format("my:user:sendMessage:%s", mail);
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
// 写缓存
try {
valueOperations.set(redisKey, userDto, 300000, TimeUnit.MILLISECONDS);
UserDto sendMessage = (UserDto) valueOperations.get(redisKey);
log.info(sendMessage.toString());
return "成功发出验证码,请在五分钟内使用";
} catch (Exception e) {
log.error("redis set key error", e);
throw new CustomException("缓存失败!");
}
}
表现层对应代码:
4.登陆验证和发放jwt令牌
大致流程:从前端请求体中获取用户输入的邮箱和验证码 ——>从Redis中获取到存储的UserDto对象,拿出存的邮箱和验证码——>保证邮箱和验证码同时相等,才允许接下来的操作——>如果是新用户就加入到数据库表User中,老用户直接下发jwt令牌作为登陆凭证
服务层代码:
@Override
public String VerifyCode(UserDto userDto) {
//获取email
String mail = userDto.getMail();
String code = userDto.getCode();
//要传回去的token,初始为 ""
String token=null;
//从redsson获取验证码
String redisKey = String.format("my:user:sendMessage:%s", mail);
ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
log.info(redisKey);
//通过key获取之前存在redis中的UserSendMessage
UserDto sendedMessage = (UserDto) valueOperations.get(redisKey);
//如果这两个都符合redis存储的email和code,那就说明验证码接收正确,允许登录,发放token,并判断是否是新用户,是的话插入
if (sendedMessage.getMail().equals(mail) && sendedMessage.getCode().equals(code)) {
/*细节:如果表中已经有了这个邮箱说明是已存在的用户,就不用插入了*/
//1.先查询表中有无该邮箱
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.eq(User::getMail, mail);
Integer count = userMapper.selectCount(lqw);
//如果count=0,说明这是新用户,插入表中
if (count == 0) {
log.info("新用户,插入数据库");
User user = new User();
//设置邮箱值,因为在数据库里只有id和邮箱是非空约束,而id是雪花算法生成,所以直接插入就行
user.setMail(mail);
//插入user表
userMapper.insert(user);
}
//2.发放Token,老用户直接发放token
Map<String,Object> claims =new HashMap<>();
//获取id
Long id = userMapper.selectOne(lqw).getId();
claims.put("mail",mail);
claims.put("id",id);
token = JwtUtils.generateJwt(claims);
log.info("发放token:{}",token);
}
return token;
}
表现层代码为: (其实可以在服务层中通过校验邮箱和验证码,不符合直接抛异常给全局异常处理器,然后返回前端error的响应就行,这样就可以让表现层的代码更简洁)
总结:
这次邮箱发送验证码的开发,其实起因是因为短信服务用不了(大部分云服务厂商,都不对个人提供短信服务,好像是运营商的要求),所以我转而使用邮箱发送验证码(正好省钱) ,不过这次邮箱发送验证码让我收益良多,学会了JavaMail的基础使用,和初步认识了Redis,并且简单运用了Redis缓存来保存数据与读取数据