Spring邮箱验证码

0. 步骤

  1. 开启邮箱的 POP3/SMTP 服务。
  2. 新建 springboot 项目。
  3. 导入依赖。
  4. 配置配置文件。
  5. 编写 controller 测试接口。
  6. ApiPost中测试
  7. 新建html网页测试

1. 开启邮箱的 POP3/SMTP 服务

本次使用QQ邮箱,其他邮箱开启方式相同:

image-20241123203753871

打开后生成授权码,会获得一个授权码,这个授权码很重要,后面会用到,生成后需要立刻保存,后面无法再次查询到

image-20241123212849269

2. 新建 springboot 项目

x项目结构:
image-20241123212937013

使用application.yml和 application.properties 的区别体现在使用时yml的层级结构会更加的清晰

3. 导入依赖

这里我导入两个依赖,分别是 spring-boot-starter-mail 和 hutool-all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 邮箱验证码依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- 一个很强大的工具库 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.19</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

4. 配置配置文件

这里使用层级更加清晰的yml结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
server:
port: 8080

spring:
#邮箱验证码配置
mail:
#smtp服务主机 qq邮箱则为smtp.qq.com; 163邮箱是smtp.163.com
host: smtp.qq.com
#服务协议
protocol: smtp
# 编码集
default-encoding: UTF-8
#发送邮件的账户
username: *****@qq.com
#授权码
password: *****

# 昵称
nickname: 康弟弟

test-connection: true
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true

5. controller 和 测试接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package com.kd_13.emailcode;

import cn.hutool.core.util.RandomUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/email")
public class EmailController {

@Autowired
private JavaMailSender mailSender;

@Value("${spring.mail.username}")
private String sender;

@Value("${spring.mail.nickname}")
private String nickname;

// 用于存储验证码和对应的邮箱,以及验证码的有效时间
private Map<String, CodeEntry> codeMap = new HashMap<>();

@GetMapping("/code")
public String getCode(@RequestParam("email") String email) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(nickname + '<' + sender + '>');
message.setTo(email);
message.setSubject("欢迎访问康弟弟的网站");

String code = RandomUtil.randomNumbers(6);
System.out.println("code"+code);
String content = "【验证码】您的验证码为:" + code + " 。 验证码五分钟内有效,逾期作废。"
+"------------------------------\n\n\n" +
"更多博客可访问:\n\n" +
"https://kd-13.cn/\n\n";;

message.setText(content);

mailSender.send(message);

// 存储验证码和邮箱,并设置有效时间为5分钟
codeMap.put(email, new CodeEntry(code, System.currentTimeMillis() + 5 * 60 * 1000));

return "发送成功!";
}

@PostMapping("/checkcode")
public String checkCode(@RequestParam("email") String email, @RequestParam("code") String code) {
CodeEntry entry = codeMap.get(email);
if (entry == null || !entry.getCode().equals(code)) {

return "验证码错误或已过期!";
}
if (System.currentTimeMillis() > entry.getExpiryTime()) {
codeMap.remove(email); // 移除过期的验证码
return "验证码已过期!";
}
codeMap.remove(email); // 验证成功后移除验证码
return "验证成功!";
}

// 内部类,用于存储验证码和过期时间
private static class CodeEntry {
private String code;
private long expiryTime;

public CodeEntry(String code, long expiryTime) {
this.code = code;
this.expiryTime = expiryTime;
}

public String getCode() {
return code;
}

public long getExpiryTime() {
return expiryTime;
}
}
}

6. 接口测试

使用ApiPost进行测试,测试端口如下:

1
http://localhost:8080/email/code?email=****@qq.com

image-20241123213633139

1
http://localhost:8080/email/checkcode?email =****@qq.com&code =****

image-20241123213706928

7. 网页测试

搭建一个简易的html页面对接口进行测试,还是放在同一个端口的应用下测试解决跨域问题,其中的jquery可以使用网络加载映入,但是网络慢的话网站的打开也会很慢,所以我这里直接下载下来了。

1
https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>邮箱验证码示例</title>
<script src="jquery.min.js"></script>
<script>
function startTimer(duration, display) {
var timer = duration, minutes, seconds;
var interval = setInterval(function () {
minutes = parseInt(timer / 60, 10);
seconds = parseInt(timer % 60, 10);

minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;

display.text(seconds);

if (--timer < 0) {
clearInterval(interval);
display.text('发送验证码');
display.prop('disabled', false); // 重新启用按钮
}
}, 1000);
}

function sendCode() {
var email = $('#email').val();
var sendButton = $('#sendButton');
sendButton.prop('disabled', true); // 禁用按钮
sendButton.text('发送中...'); // 更改按钮文本为“发送中...”

$.ajax({
url: '/email/code',
type: 'GET',
data: { email: email },
success: function(response) {
if (response === "发送成功!") {
alert("发送成功!")
sendButton.text('60'); // 设置初始倒计时文本
startTimer(60, sendButton); // 开始倒计时
} else {
sendButton.prop('disabled', false); // 启用按钮
sendButton.text('发送验证码'); // 恢复按钮文本
}
},
error: function(error) {
alert('发送验证码失败,请稍后再试。');
sendButton.prop('disabled', false); // 出错时重新启用按钮
sendButton.text('发送验证码'); // 恢复按钮文本
}
});
}

function checkCode() {
var email = $('#email').val();
var code = $('#code').val();
$.ajax({
url: '/email/checkcode',
type: 'POST',
data: { email: email, code: code },
success: function(response) {
alert(response);
},
error: function(error) {
alert('核对验证码失败,请稍后再试。');
}
});
}
</script>
</head>
<body>

<h2>邮箱验证码示例</h2>

<label for="email">邮箱地址:</label>
<input type="email" id="email" name="email" required>
<button id="sendButton" onclick="sendCode()">发送验证码</button>

<br><br>

<label for="code">验证码:</label>
<input type="text" id="code" name="code" required>
<button onclick="checkCode()">核对验证码</button>

</body>
</html>

image-20241123213849384

image-20241123213829840

8. 改进

可以将数据存储到Redis 中