邮箱验证码
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -148,6 +148,12 @@
|
|||||||
<artifactId>spring-boot-starter-mail</artifactId>
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Redis 依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -19,6 +19,14 @@ public enum ResultCode {
|
|||||||
FORBIDDEN(403, "无权限"),
|
FORBIDDEN(403, "无权限"),
|
||||||
/** 数据不存在 */
|
/** 数据不存在 */
|
||||||
NOT_FOUND(404, "数据不存在"),
|
NOT_FOUND(404, "数据不存在"),
|
||||||
|
/** 验证码已发送 */
|
||||||
|
CODE_SENT(200, "验证码已发送"),
|
||||||
|
/** 验证码错误 */
|
||||||
|
CODE_ERROR(400, "验证码错误"),
|
||||||
|
/** 验证码已过期 */
|
||||||
|
CODE_EXPIRED(400, "验证码已过期"),
|
||||||
|
/** 验证码发送失败 */
|
||||||
|
CODE_SEND_FAIL(500, "验证码发送失败"),
|
||||||
/** 系统异常 */
|
/** 系统异常 */
|
||||||
SYSTEM_ERROR(500, "系统异常"),
|
SYSTEM_ERROR(500, "系统异常"),
|
||||||
/** 操作失败 */
|
/** 操作失败 */
|
||||||
|
|||||||
@@ -149,3 +149,4 @@ public class SmsUtil {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.bao.dating.controller;
|
||||||
|
|
||||||
|
import com.bao.dating.common.Result;
|
||||||
|
import com.bao.dating.common.ResultCode;
|
||||||
|
import com.bao.dating.service.VerificationCodeService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码控制器
|
||||||
|
* @author KilLze
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/verification")
|
||||||
|
public class VerificationCodeController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VerificationCodeService verificationCodeService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮箱验证码
|
||||||
|
* @param email 邮箱地址
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/send-email-code")
|
||||||
|
public Result<String> sendEmailCode(@RequestParam String email) {
|
||||||
|
// 参数校验
|
||||||
|
if (!StringUtils.hasText(email)) {
|
||||||
|
return Result.error(ResultCode.PARAM_ERROR, "邮箱地址不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的邮箱格式校验
|
||||||
|
if (!isValidEmail(email)) {
|
||||||
|
return Result.error(ResultCode.PARAM_ERROR, "邮箱格式不正确");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送验证码
|
||||||
|
boolean success = verificationCodeService.sendEmailCode(email);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
return Result.success(ResultCode.CODE_SENT, "验证码已发送到您的邮箱,请查收");
|
||||||
|
} else {
|
||||||
|
return Result.error(ResultCode.CODE_SEND_FAIL, "验证码发送失败,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证邮箱验证码
|
||||||
|
* @param email 邮箱地址
|
||||||
|
* @param code 验证码
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/verify-email-code")
|
||||||
|
public Result<String> verifyEmailCode(@RequestParam String email, @RequestParam String code) {
|
||||||
|
// 参数校验
|
||||||
|
if (!StringUtils.hasText(email)) {
|
||||||
|
return Result.error(ResultCode.PARAM_ERROR, "邮箱地址不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(code)) {
|
||||||
|
return Result.error(ResultCode.PARAM_ERROR, "验证码不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证验证码
|
||||||
|
boolean success = verificationCodeService.verifyEmailCode(email, code);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
return Result.success(ResultCode.SUCCESS, "验证码验证成功");
|
||||||
|
} else {
|
||||||
|
return Result.error(ResultCode.CODE_ERROR, "验证码错误或已过期");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单的邮箱格式校验
|
||||||
|
* @param email 邮箱地址
|
||||||
|
* @return 是否有效
|
||||||
|
*/
|
||||||
|
private boolean isValidEmail(String email) {
|
||||||
|
if (email == null || email.trim().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 简单的邮箱格式校验:包含@和.
|
||||||
|
return email.contains("@") && email.contains(".") && email.length() > 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.bao.dating.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码服务接口
|
||||||
|
* @author KilLze
|
||||||
|
*/
|
||||||
|
public interface VerificationCodeService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮箱验证码
|
||||||
|
* @param email 邮箱地址
|
||||||
|
* @return 是否发送成功
|
||||||
|
*/
|
||||||
|
boolean sendEmailCode(String email);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证邮箱验证码
|
||||||
|
* @param email 邮箱地址
|
||||||
|
* @param code 验证码
|
||||||
|
* @return 是否验证成功
|
||||||
|
*/
|
||||||
|
boolean verifyEmailCode(String email, String code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成验证码
|
||||||
|
* @param length 验证码长度(默认6位)
|
||||||
|
* @return 验证码字符串
|
||||||
|
*/
|
||||||
|
String generateCode(int length);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package com.bao.dating.service.impl;
|
||||||
|
|
||||||
|
import com.bao.dating.service.VerificationCodeService;
|
||||||
|
import com.bao.dating.util.EmailUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码服务实现类
|
||||||
|
* @author KilLze
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class VerificationCodeServiceImpl implements VerificationCodeService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EmailUtil emailUtil;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StringRedisTemplate redisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis中验证码的key前缀
|
||||||
|
*/
|
||||||
|
private static final String CODE_KEY_PREFIX = "email:code:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码过期时间(分钟)
|
||||||
|
*/
|
||||||
|
private static final long CODE_EXPIRE_MINUTES = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码长度
|
||||||
|
*/
|
||||||
|
private static final int CODE_LENGTH = 6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮箱验证码
|
||||||
|
* @param email 邮箱地址
|
||||||
|
* @return 是否发送成功
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean sendEmailCode(String email) {
|
||||||
|
try {
|
||||||
|
// 生成验证码
|
||||||
|
String code = generateCode(CODE_LENGTH);
|
||||||
|
|
||||||
|
// 存储到Redis,设置过期时间
|
||||||
|
String key = CODE_KEY_PREFIX + email;
|
||||||
|
redisTemplate.opsForValue().set(key, code, CODE_EXPIRE_MINUTES, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
// 发送邮件
|
||||||
|
boolean sendResult = emailUtil.sendVerificationCode(email, code);
|
||||||
|
|
||||||
|
if (sendResult) {
|
||||||
|
log.info("邮箱验证码发送成功,邮箱:{},验证码:{}", email, code);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// 如果发送失败,删除Redis中的验证码
|
||||||
|
redisTemplate.delete(key);
|
||||||
|
log.error("邮箱验证码发送失败,邮箱:{}", email);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("发送邮箱验证码异常,邮箱:{},异常信息:{}", email, e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证邮箱验证码
|
||||||
|
* @param email 邮箱地址
|
||||||
|
* @param code 验证码
|
||||||
|
* @return 是否验证成功
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean verifyEmailCode(String email, String code) {
|
||||||
|
try {
|
||||||
|
String key = CODE_KEY_PREFIX + email;
|
||||||
|
String storedCode = redisTemplate.opsForValue().get(key);
|
||||||
|
|
||||||
|
if (storedCode == null) {
|
||||||
|
log.warn("验证码不存在或已过期,邮箱:{}", email);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storedCode.equals(code)) {
|
||||||
|
// 验证成功后,删除验证码(防止重复使用)
|
||||||
|
redisTemplate.delete(key);
|
||||||
|
log.info("邮箱验证码验证成功,邮箱:{}", email);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
log.warn("邮箱验证码错误,邮箱:{},输入的验证码:{},正确的验证码:{}", email, code, storedCode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("验证邮箱验证码异常,邮箱:{},异常信息:{}", email, e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成验证码
|
||||||
|
* @param length 验证码长度
|
||||||
|
* @return 验证码字符串
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String generateCode(int length) {
|
||||||
|
Random random = new Random();
|
||||||
|
StringBuilder code = new StringBuilder();
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
code.append(random.nextInt(10)); // 生成0-9的随机数字
|
||||||
|
}
|
||||||
|
return code.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
package com.bao.dating.util;
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MD5工具类
|
|
||||||
* 提供MD5加密功能
|
|
||||||
* @author KilLze
|
|
||||||
*/
|
|
||||||
public class MD5Util {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对字符串进行MD5加密
|
|
||||||
* @param input 待加密的字符串
|
|
||||||
* @return MD5加密后的32位小写字符串
|
|
||||||
*/
|
|
||||||
public static String encrypt(String input) {
|
|
||||||
if (input == null || input.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
|
||||||
byte[] digest = md.digest(input.getBytes());
|
|
||||||
return bytesToHex(digest);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new RuntimeException("MD5算法不可用", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对字符串进行MD5加密(带盐值)
|
|
||||||
* @param input 待加密的字符串
|
|
||||||
* @param salt 盐值
|
|
||||||
* @return MD5加密后的32位小写字符串
|
|
||||||
*/
|
|
||||||
public static String encryptWithSalt(String input, String salt) {
|
|
||||||
if (input == null || input.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (salt == null) {
|
|
||||||
salt = "";
|
|
||||||
}
|
|
||||||
return encrypt(input + salt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证字符串与MD5值是否匹配
|
|
||||||
* @param input 原始字符串
|
|
||||||
* @param md5Hash MD5哈希值
|
|
||||||
* @return 是否匹配
|
|
||||||
*/
|
|
||||||
public static boolean verify(String input, String md5Hash) {
|
|
||||||
if (input == null || md5Hash == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return encrypt(input).equalsIgnoreCase(md5Hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证字符串与MD5值是否匹配(带盐值)
|
|
||||||
* @param input 原始字符串
|
|
||||||
* @param salt 盐值
|
|
||||||
* @param md5Hash MD5哈希值
|
|
||||||
* @return 是否匹配
|
|
||||||
*/
|
|
||||||
public static boolean verifyWithSalt(String input, String salt, String md5Hash) {
|
|
||||||
if (input == null || md5Hash == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (salt == null) {
|
|
||||||
salt = "";
|
|
||||||
}
|
|
||||||
return encryptWithSalt(input, salt).equalsIgnoreCase(md5Hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将字节数组转换为十六进制字符串
|
|
||||||
* @param bytes 字节数组
|
|
||||||
* @return 十六进制字符串
|
|
||||||
*/
|
|
||||||
private static String bytesToHex(byte[] bytes) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (byte b : bytes) {
|
|
||||||
sb.append(String.format("%02x", b));
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package com.bao.dating.service;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码服务测试类
|
||||||
|
* @author KilLze
|
||||||
|
*/
|
||||||
|
@SpringBootTest
|
||||||
|
public class VerificationCodeServiceTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VerificationCodeService verificationCodeService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试发送邮箱验证码
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSendEmailCode() {
|
||||||
|
String email = "test@example.com"; // 请修改为实际邮箱地址
|
||||||
|
|
||||||
|
System.out.println("========== 发送邮箱验证码测试 ==========");
|
||||||
|
System.out.println("邮箱地址: " + email);
|
||||||
|
|
||||||
|
boolean result = verificationCodeService.sendEmailCode(email);
|
||||||
|
|
||||||
|
System.out.println("发送结果: " + (result ? "成功" : "失败"));
|
||||||
|
System.out.println("=====================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试验证邮箱验证码
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testVerifyEmailCode() {
|
||||||
|
String email = "test@example.com"; // 请修改为实际邮箱地址
|
||||||
|
String code = "123456"; // 请修改为实际收到的验证码
|
||||||
|
|
||||||
|
System.out.println("========== 验证邮箱验证码测试 ==========");
|
||||||
|
System.out.println("邮箱地址: " + email);
|
||||||
|
System.out.println("验证码: " + code);
|
||||||
|
|
||||||
|
boolean result = verificationCodeService.verifyEmailCode(email, code);
|
||||||
|
|
||||||
|
System.out.println("验证结果: " + (result ? "成功" : "失败"));
|
||||||
|
System.out.println("=====================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试完整流程:发送验证码 -> 验证验证码
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testCompleteFlow() {
|
||||||
|
String email = "test@example.com"; // 请修改为实际邮箱地址
|
||||||
|
|
||||||
|
System.out.println("========== 完整流程测试 ==========");
|
||||||
|
System.out.println("邮箱地址: " + email);
|
||||||
|
|
||||||
|
// 1. 发送验证码
|
||||||
|
System.out.println("\n1. 发送验证码...");
|
||||||
|
boolean sendResult = verificationCodeService.sendEmailCode(email);
|
||||||
|
System.out.println("发送结果: " + (sendResult ? "成功" : "失败"));
|
||||||
|
|
||||||
|
if (sendResult) {
|
||||||
|
// 2. 等待用户输入验证码(这里模拟,实际应该从控制台或API获取)
|
||||||
|
System.out.println("\n2. 请查看邮箱获取验证码,然后手动测试验证功能");
|
||||||
|
System.out.println(" 使用 testVerifyEmailCode() 方法进行验证");
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("\n=====================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试生成验证码
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGenerateCode() {
|
||||||
|
System.out.println("========== 生成验证码测试 ==========");
|
||||||
|
|
||||||
|
// 测试不同长度的验证码
|
||||||
|
for (int length = 4; length <= 8; length++) {
|
||||||
|
String code = verificationCodeService.generateCode(length);
|
||||||
|
System.out.println(length + "位验证码: " + code);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("=====================================");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -75,3 +75,4 @@ public class EmailAndSmsTest {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user