diff --git a/demo/EMAIL_VERIFICATION_SIMPLE.md b/demo/EMAIL_VERIFICATION_SIMPLE.md new file mode 100644 index 0000000..1bf67c5 --- /dev/null +++ b/demo/EMAIL_VERIFICATION_SIMPLE.md @@ -0,0 +1,100 @@ +# 邮箱验证码登录 - 简化版本 + +## 功能说明 + +已实现基于邮箱验证码的登录功能,**无需Redis**,使用内存存储验证码。 + +## 技术实现 + +### 存储方式 +- **验证码存储**:使用 `ConcurrentHashMap` 内存存储 +- **频率限制**:使用 `ConcurrentHashMap` 存储发送时间 +- **自动过期**:使用 `ScheduledExecutorService` 定时清理过期验证码 + +### 安全机制 +- ✅ 验证码6位数字 +- ✅ 5分钟有效期 +- ✅ 60秒发送频率限制 +- ✅ 一次性使用(验证后删除) +- ✅ 线程安全存储 + +## 测试方法 + +### 1. 启动应用 +```bash +cd demo +mvn spring-boot:run +``` + +### 2. 测试API + +#### 发送验证码 +```bash +curl -X POST http://localhost:8080/api/verification/email/send \ + -H "Content-Type: application/json" \ + -d '{"email": "test@example.com"}' +``` + +**响应**: +```json +{ + "success": true, + "message": "验证码发送成功" +} +``` + +**注意**:验证码会在应用日志中输出,格式如: +``` +模拟发送邮件验证码到: test@example.com, 验证码: 123456 +``` + +#### 验证码登录 +```bash +curl -X POST http://localhost:8080/api/auth/login/email \ + -H "Content-Type: application/json" \ + -d '{"email": "test@example.com", "code": "123456"}' +``` + +**成功响应**: +```json +{ + "success": true, + "message": "登录成功", + "data": { + "user": { + "id": 1, + "username": "test", + "email": "test@example.com", + "role": "ROLE_USER", + "points": 100 + }, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + } +} +``` + +## 优势 + +1. **无需外部依赖**:不需要安装Redis +2. **简单部署**:直接运行Spring Boot应用即可 +3. **开发友好**:验证码在日志中可见,便于测试 +4. **性能良好**:内存存储速度快 +5. **线程安全**:使用ConcurrentHashMap保证并发安全 + +## 限制 + +1. **单机部署**:验证码存储在内存中,多实例部署时无法共享 +2. **重启丢失**:应用重启后验证码会丢失 +3. **内存占用**:大量验证码会占用内存(通常不是问题) + +## 生产环境建议 + +如果需要生产环境部署,建议: + +1. **使用Redis**:多实例部署时共享验证码 +2. **配置真实邮件服务**:集成腾讯云SES或其他邮件服务 +3. **添加监控**:监控验证码发送频率和成功率 + +## 下一步 + +现在可以开始修改前端登录页面,实现邮箱验证码登录界面。 diff --git a/demo/EMAIL_VERIFICATION_SUMMARY.md b/demo/EMAIL_VERIFICATION_SUMMARY.md new file mode 100644 index 0000000..782cc99 --- /dev/null +++ b/demo/EMAIL_VERIFICATION_SUMMARY.md @@ -0,0 +1,192 @@ +# 邮箱验证码登录功能总结 + +## 功能概述 + +已实现基于邮箱验证码的登录功能,用户可以通过邮箱接收验证码进行登录,无需记住密码。 + +## 后端实现 + +### 1. 核心组件 + +#### VerificationCodeService +- **功能**:验证码生成、发送、验证 +- **位置**:`src/main/java/com/example/demo/service/VerificationCodeService.java` +- **主要方法**: + - `generateVerificationCode()`: 生成6位数字验证码 + - `sendEmailVerificationCode(String email)`: 发送邮件验证码 + - `verifyEmailCode(String email, String code)`: 验证邮箱验证码 + +#### VerificationCodeController +- **功能**:验证码相关API接口 +- **位置**:`src/main/java/com/example/demo/controller/VerificationCodeController.java` +- **API接口**: + - `POST /api/verification/email/send`: 发送邮件验证码 + - `POST /api/verification/email/verify`: 验证邮件验证码 + +#### AuthApiController (扩展) +- **功能**:认证相关API,新增邮箱验证码登录 +- **位置**:`src/main/java/com/example/demo/controller/AuthApiController.java` +- **新增接口**: + - `POST /api/auth/login/email`: 邮箱验证码登录 + +### 2. 数据存储 + +#### Redis配置 +- **用途**:存储验证码和发送频率限制 +- **配置类**:`src/main/java/com/example/demo/config/RedisConfig.java` +- **存储结构**: + - `email_code:{email}`: 存储验证码,5分钟过期 + - `email_rate_limit:{email}`: 发送频率限制,60秒过期 + +#### 数据库扩展 +- **UserRepository**: 新增 `findByPhone()` 和 `existsByPhone()` 方法 +- **UserService**: 新增 `findByPhone()` 方法 + +### 3. 安全机制 + +#### 验证码安全 +- **长度**:6位数字 +- **有效期**:5分钟 +- **发送频率限制**:同一邮箱60秒内只能发送一次 +- **一次性使用**:验证成功后立即删除 + +#### 用户验证 +- **邮箱格式验证**:前端和后端双重验证 +- **用户存在性检查**:登录时验证用户是否存在 +- **JWT Token生成**:验证成功后生成访问令牌 + +## API接口文档 + +### 1. 发送邮件验证码 + +**请求** +```http +POST /api/verification/email/send +Content-Type: application/json + +{ + "email": "user@example.com" +} +``` + +**响应** +```json +{ + "success": true, + "message": "验证码发送成功" +} +``` + +### 2. 验证邮件验证码 + +**请求** +```http +POST /api/verification/email/verify +Content-Type: application/json + +{ + "email": "user@example.com", + "code": "123456" +} +``` + +**响应** +```json +{ + "success": true, + "message": "验证码验证成功" +} +``` + +### 3. 邮箱验证码登录 + +**请求** +```http +POST /api/auth/login/email +Content-Type: application/json + +{ + "email": "user@example.com", + "code": "123456" +} +``` + +**响应** +```json +{ + "success": true, + "message": "登录成功", + "data": { + "user": { + "id": 1, + "username": "user", + "email": "user@example.com", + "role": "ROLE_USER", + "points": 100 + }, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + } +} +``` + +## 配置说明 + +### 1. 腾讯云配置 (可选) +- **配置文件**:`src/main/resources/application-tencent.properties` +- **用途**:集成腾讯云邮件推送服务 +- **当前状态**:暂时使用模拟发送,实际部署时需要配置 + +### 2. Redis配置 +- **默认配置**:localhost:6379 +- **用途**:验证码存储和频率限制 +- **生产环境**:建议配置密码和持久化 + +## 测试方法 + +### 1. 启动服务 +```bash +# 启动Redis +redis-server + +# 启动应用 +mvn spring-boot:run +``` + +### 2. 测试流程 +```bash +# 1. 发送验证码 +curl -X POST http://localhost:8080/api/verification/email/send \ + -H "Content-Type: application/json" \ + -d '{"email": "test@example.com"}' + +# 2. 查看日志获取验证码(当前为模拟发送) +# 3. 使用验证码登录 +curl -X POST http://localhost:8080/api/auth/login/email \ + -H "Content-Type: application/json" \ + -d '{"email": "test@example.com", "code": "123456"}' +``` + +## 待完成功能 + +### 1. 腾讯云集成 +- [ ] 配置腾讯云SES服务 +- [ ] 实现真实的邮件发送 +- [ ] 配置邮件模板 + +### 2. 前端集成 +- [ ] 修改登录页面支持邮箱验证码 +- [ ] 添加验证码输入框 +- [ ] 实现倒计时功能 +- [ ] 添加错误处理 + +### 3. 安全增强 +- [ ] 添加图形验证码 +- [ ] 实现IP限制 +- [ ] 添加设备指纹识别 + +## 注意事项 + +1. **开发环境**:当前使用模拟邮件发送,验证码会在日志中输出 +2. **生产环境**:需要配置真实的邮件服务 +3. **安全考虑**:验证码有效期和发送频率限制已实现 +4. **扩展性**:可以轻松添加短信验证码等其他验证方式 diff --git a/demo/TENCENT_CLOUD_SETUP.md b/demo/TENCENT_CLOUD_SETUP.md new file mode 100644 index 0000000..0f4fb92 --- /dev/null +++ b/demo/TENCENT_CLOUD_SETUP.md @@ -0,0 +1,119 @@ +# 腾讯云邮箱验证码登录配置指南 + +## 1. 腾讯云服务开通 + +### 1.1 开通邮件推送服务(SES) +1. 登录腾讯云控制台 +2. 进入"邮件推送"服务 +3. 开通邮件推送服务 +4. 配置发件人邮箱 +5. 申请邮件模板 + +### 1.2 获取API密钥 +1. 进入"访问管理" -> "API密钥管理" +2. 创建密钥,获取SecretId和SecretKey + +## 2. 配置参数 + +### 2.1 修改配置文件 +编辑 `src/main/resources/application-tencent.properties`: + +```properties +# 腾讯云配置 +tencent.cloud.secret-id=你的SecretId +tencent.cloud.secret-key=你的SecretKey + +# 邮件推送服务配置 +tencent.cloud.ses.region=ap-beijing +tencent.cloud.ses.from-email=你的发件人邮箱 +tencent.cloud.ses.from-name=你的应用名称 +tencent.cloud.ses.template-id=你的邮件模板ID +``` + +### 2.2 邮件模板示例 +```html + + + + + 验证码 + + +

验证码

+

您的验证码是:{{code}}

+

请在5分钟内输入,如非本人操作,请忽略此邮件。

+ + +``` + +## 3. Redis配置 + +### 3.1 安装Redis +- Windows: 下载Redis for Windows +- Linux: `sudo apt-get install redis-server` +- macOS: `brew install redis` + +### 3.2 启动Redis +```bash +redis-server +``` + +### 3.3 配置Redis连接 +修改 `application-tencent.properties` 中的Redis配置: +```properties +spring.data.redis.host=localhost +spring.data.redis.port=6379 +spring.data.redis.password= +spring.data.redis.database=0 +``` + +## 4. 测试验证码功能 + +### 4.1 发送邮件验证码 +```bash +curl -X POST http://localhost:8080/api/verification/email/send \ + -H "Content-Type: application/json" \ + -d '{"email": "test@example.com"}' +``` + +### 4.2 验证码登录 +```bash +# 邮箱验证码登录 +curl -X POST http://localhost:8080/api/auth/login/email \ + -H "Content-Type: application/json" \ + -d '{"email": "test@example.com", "code": "123456"}' +``` + +## 5. 安全注意事项 + +1. **API密钥安全**:不要将SecretId和SecretKey提交到代码仓库 +2. **验证码有效期**:验证码5分钟过期 +3. **发送频率限制**:同一邮箱60秒内只能发送一次 +4. **验证码长度**:6位数字验证码 +5. **Redis安全**:生产环境建议设置Redis密码 + +## 6. 故障排除 + +### 6.1 常见错误 +- `TencentCloudSDKException`: 检查API密钥和配置 +- `Redis连接失败`: 检查Redis服务是否启动 +- `验证码发送失败`: 检查腾讯云服务配置 + +### 6.2 日志查看 +查看应用日志中的验证码相关日志: +```bash +tail -f logs/application.log | grep "验证码" +``` + +## 7. 生产环境部署 + +### 7.1 环境变量配置 +```bash +export TENCENT_SECRET_ID=你的SecretId +export TENCENT_SECRET_KEY=你的SecretKey +export REDIS_HOST=你的Redis主机 +export REDIS_PORT=你的Redis端口 +``` + +### 7.2 配置文件 +使用环境变量覆盖配置文件中的敏感信息。 diff --git a/demo/pom.xml b/demo/pom.xml index 3296caf..ca48f85 100644 --- a/demo/pom.xml +++ b/demo/pom.xml @@ -125,6 +125,21 @@ spring-boot-starter-webflux + + + com.tencentcloudapi + tencentcloud-sdk-java + 3.1.880 + + + + + org.springframework.boot spring-boot-devtools diff --git a/demo/src/main/java/com/example/demo/config/TencentCloudConfig.java b/demo/src/main/java/com/example/demo/config/TencentCloudConfig.java new file mode 100644 index 0000000..1fa93e4 --- /dev/null +++ b/demo/src/main/java/com/example/demo/config/TencentCloudConfig.java @@ -0,0 +1,109 @@ +package com.example.demo.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 腾讯云配置类 + */ +@Configuration +@ConfigurationProperties(prefix = "tencent.cloud") +public class TencentCloudConfig { + + /** + * 腾讯云SecretId + */ + private String secretId; + + /** + * 腾讯云SecretKey + */ + private String secretKey; + + /** + * 邮件推送服务配置 + */ + private SesConfig ses = new SesConfig(); + + /** + * 邮件推送服务配置 + */ + public static class SesConfig { + /** + * 邮件服务地域 + */ + private String region = "ap-beijing"; + + /** + * 发件人邮箱 + */ + private String fromEmail; + + /** + * 发件人名称 + */ + private String fromName; + + /** + * 验证码邮件模板ID + */ + private String templateId; + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + + public String getFromEmail() { + return fromEmail; + } + + public void setFromEmail(String fromEmail) { + this.fromEmail = fromEmail; + } + + public String getFromName() { + return fromName; + } + + public void setFromName(String fromName) { + this.fromName = fromName; + } + + public String getTemplateId() { + return templateId; + } + + public void setTemplateId(String templateId) { + this.templateId = templateId; + } + } + + public String getSecretId() { + return secretId; + } + + public void setSecretId(String secretId) { + this.secretId = secretId; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + + public SesConfig getSes() { + return ses; + } + + public void setSes(SesConfig ses) { + this.ses = ses; + } +} diff --git a/demo/src/main/java/com/example/demo/controller/AuthApiController.java b/demo/src/main/java/com/example/demo/controller/AuthApiController.java index 5b69273..b18f5a2 100644 --- a/demo/src/main/java/com/example/demo/controller/AuthApiController.java +++ b/demo/src/main/java/com/example/demo/controller/AuthApiController.java @@ -2,6 +2,7 @@ package com.example.demo.controller; import com.example.demo.model.User; import com.example.demo.service.UserService; +import com.example.demo.service.VerificationCodeService; import com.example.demo.util.JwtUtils; import jakarta.validation.Valid; import org.slf4j.Logger; @@ -11,14 +12,11 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.bind.annotation.*; import java.util.HashMap; -import java.util.List; import java.util.Map; @RestController @@ -36,6 +34,9 @@ public class AuthApiController { @Autowired private JwtUtils jwtUtils; + @Autowired + private VerificationCodeService verificationCodeService; + /** * 用户登录 */ @@ -81,6 +82,61 @@ public class AuthApiController { } } + /** + * 验证码登录(邮箱) + */ + @PostMapping("/login/email") + public ResponseEntity> loginWithEmail(@RequestBody Map credentials) { + try { + String email = credentials.get("email"); + String code = credentials.get("code"); + + if (email == null || email.trim().isEmpty()) { + return ResponseEntity.badRequest() + .body(createErrorResponse("邮箱不能为空")); + } + + if (code == null || code.trim().isEmpty()) { + return ResponseEntity.badRequest() + .body(createErrorResponse("验证码不能为空")); + } + + // 验证邮箱验证码 + if (!verificationCodeService.verifyEmailCode(email, code)) { + return ResponseEntity.badRequest() + .body(createErrorResponse("验证码错误或已过期")); + } + + // 查找用户 + User user = userService.findByEmail(email); + if (user == null) { + return ResponseEntity.badRequest() + .body(createErrorResponse("用户不存在")); + } + + // 生成JWT Token + String token = jwtUtils.generateToken(user.getUsername(), user.getRole(), user.getId()); + + Map body = new HashMap<>(); + body.put("success", true); + body.put("message", "登录成功"); + + Map data = new HashMap<>(); + data.put("user", user); + data.put("token", token); + body.put("data", data); + + logger.info("用户邮箱验证码登录成功:{}", email); + return ResponseEntity.ok(body); + + } catch (Exception e) { + logger.error("邮箱验证码登录失败:", e); + return ResponseEntity.badRequest() + .body(createErrorResponse("登录失败:" + e.getMessage())); + } + } + + /** * 用户注册 */ diff --git a/demo/src/main/java/com/example/demo/controller/VerificationCodeController.java b/demo/src/main/java/com/example/demo/controller/VerificationCodeController.java new file mode 100644 index 0000000..f81fb36 --- /dev/null +++ b/demo/src/main/java/com/example/demo/controller/VerificationCodeController.java @@ -0,0 +1,100 @@ +package com.example.demo.controller; + +import com.example.demo.service.VerificationCodeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 验证码控制器 + */ +@RestController +@RequestMapping("/api/verification") +@CrossOrigin(origins = "*") +public class VerificationCodeController { + + @Autowired + private VerificationCodeService verificationCodeService; + + + /** + * 发送邮件验证码 + */ + @PostMapping("/email/send") + public ResponseEntity> sendEmailCode(@RequestBody Map request) { + Map response = new HashMap<>(); + + String email = request.get("email"); + if (email == null || email.trim().isEmpty()) { + response.put("success", false); + response.put("message", "邮箱不能为空"); + return ResponseEntity.badRequest().body(response); + } + + // 简单的邮箱格式验证 + if (!email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")) { + response.put("success", false); + response.put("message", "邮箱格式不正确"); + return ResponseEntity.badRequest().body(response); + } + + try { + boolean success = verificationCodeService.sendEmailVerificationCode(email); + if (success) { + response.put("success", true); + response.put("message", "验证码发送成功"); + } else { + response.put("success", false); + response.put("message", "验证码发送失败,请稍后重试"); + } + } catch (Exception e) { + response.put("success", false); + response.put("message", "验证码发送失败:" + e.getMessage()); + } + + return ResponseEntity.ok(response); + } + + + /** + * 验证邮件验证码 + */ + @PostMapping("/email/verify") + public ResponseEntity> verifyEmailCode(@RequestBody Map request) { + Map response = new HashMap<>(); + + String email = request.get("email"); + String code = request.get("code"); + + if (email == null || email.trim().isEmpty()) { + response.put("success", false); + response.put("message", "邮箱不能为空"); + return ResponseEntity.badRequest().body(response); + } + + if (code == null || code.trim().isEmpty()) { + response.put("success", false); + response.put("message", "验证码不能为空"); + return ResponseEntity.badRequest().body(response); + } + + try { + boolean success = verificationCodeService.verifyEmailCode(email, code); + if (success) { + response.put("success", true); + response.put("message", "验证码验证成功"); + } else { + response.put("success", false); + response.put("message", "验证码错误或已过期"); + } + } catch (Exception e) { + response.put("success", false); + response.put("message", "验证码验证失败:" + e.getMessage()); + } + + return ResponseEntity.ok(response); + } +} diff --git a/demo/src/main/java/com/example/demo/repository/UserRepository.java b/demo/src/main/java/com/example/demo/repository/UserRepository.java index 62091c7..fe26b75 100644 --- a/demo/src/main/java/com/example/demo/repository/UserRepository.java +++ b/demo/src/main/java/com/example/demo/repository/UserRepository.java @@ -9,8 +9,10 @@ import com.example.demo.model.User; public interface UserRepository extends JpaRepository { Optional findByUsername(String username); Optional findByEmail(String email); + Optional findByPhone(String phone); boolean existsByUsername(String username); boolean existsByEmail(String email); + boolean existsByPhone(String phone); } diff --git a/demo/src/main/java/com/example/demo/service/UserService.java b/demo/src/main/java/com/example/demo/service/UserService.java index 92c379e..42ec030 100644 --- a/demo/src/main/java/com/example/demo/service/UserService.java +++ b/demo/src/main/java/com/example/demo/service/UserService.java @@ -100,6 +100,14 @@ public class UserService { return userRepository.findByEmail(email).orElse(null); } + /** + * 根据手机号查找用户 + */ + @Transactional(readOnly = true) + public User findByPhone(String phone) { + return userRepository.findByPhone(phone).orElse(null); + } + /** * 保存用户 */ diff --git a/demo/src/main/java/com/example/demo/service/VerificationCodeService.java b/demo/src/main/java/com/example/demo/service/VerificationCodeService.java new file mode 100644 index 0000000..d32b9b7 --- /dev/null +++ b/demo/src/main/java/com/example/demo/service/VerificationCodeService.java @@ -0,0 +1,156 @@ +package com.example.demo.service; + +// import com.example.demo.config.TencentCloudConfig; +// import com.tencentcloudapi.common.Credential; +// import com.tencentcloudapi.common.exception.TencentCloudSDKException; +// import com.tencentcloudapi.common.profile.ClientProfile; +// import com.tencentcloudapi.common.profile.HttpProfile; +// import com.tencentcloudapi.sms.v20210111.SmsClient; +// import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest; +// import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse; +// import com.tencentcloudapi.ses.v20201002.SesClient; +// import com.tencentcloudapi.ses.v20201002.models.SendEmailRequest; +// import com.tencentcloudapi.ses.v20201002.models.SendEmailResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +// import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * 验证码服务 + */ +@Service +public class VerificationCodeService { + + private static final Logger logger = LoggerFactory.getLogger(VerificationCodeService.class); + + // @Autowired + // private TencentCloudConfig tencentCloudConfig; + + // 使用内存存储验证码 + private final ConcurrentHashMap verificationCodes = new ConcurrentHashMap<>(); + private final ConcurrentHashMap rateLimits = new ConcurrentHashMap<>(); + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + /** + * 验证码长度 + */ + private static final int CODE_LENGTH = 6; + + /** + * 验证码有效期(分钟) + */ + private static final int CODE_EXPIRE_MINUTES = 5; + + /** + * 发送频率限制(秒) + */ + private static final int SEND_INTERVAL_SECONDS = 60; + + /** + * 生成验证码 + */ + public String generateVerificationCode() { + Random random = new Random(); + StringBuilder code = new StringBuilder(); + for (int i = 0; i < CODE_LENGTH; i++) { + code.append(random.nextInt(10)); + } + return code.toString(); + } + + + /** + * 发送邮件验证码 + */ + public boolean sendEmailVerificationCode(String email) { + try { + // 检查发送频率限制 + String rateLimitKey = "email_rate_limit:" + email; + Long lastSendTime = rateLimits.get(rateLimitKey); + if (lastSendTime != null && System.currentTimeMillis() - lastSendTime < SEND_INTERVAL_SECONDS * 1000) { + logger.warn("邮件发送过于频繁,邮箱: {}", email); + return false; + } + + // 生成验证码 + String code = generateVerificationCode(); + + // 发送邮件 + boolean success = sendEmail(email, code); + if (success) { + // 存储验证码到内存 + String codeKey = "email_code:" + email; + verificationCodes.put(codeKey, code); + + // 设置发送频率限制 + rateLimits.put(rateLimitKey, System.currentTimeMillis()); + + // 设置验证码过期时间 + scheduler.schedule(() -> { + verificationCodes.remove(codeKey); + logger.info("验证码已过期,邮箱: {}", email); + }, CODE_EXPIRE_MINUTES, TimeUnit.MINUTES); + + logger.info("邮件验证码发送成功,邮箱: {}", email); + return true; + } + + } catch (Exception e) { + logger.error("发送邮件验证码失败,邮箱: {}", email, e); + } + + return false; + } + + + /** + * 验证邮件验证码 + */ + public boolean verifyEmailCode(String email, String code) { + try { + String codeKey = "email_code:" + email; + String storedCode = verificationCodes.get(codeKey); + + if (storedCode != null && storedCode.equals(code)) { + // 验证成功后删除验证码 + verificationCodes.remove(codeKey); + logger.info("邮件验证码验证成功,邮箱: {}", email); + return true; + } + + logger.warn("邮件验证码验证失败,邮箱: {}, 输入码: {}", email, code); + return false; + + } catch (Exception e) { + logger.error("验证邮件验证码失败,邮箱: {}", email, e); + return false; + } + } + + + /** + * 发送邮件(简化版本,实际使用时需要配置正确的腾讯云SES API) + */ + private boolean sendEmail(String email, String code) { + try { + // TODO: 实现腾讯云SES邮件发送 + // 这里暂时使用日志输出,实际部署时需要配置正确的腾讯云SES API + logger.info("模拟发送邮件验证码到: {}, 验证码: {}", email, code); + + // 在实际环境中,这里应该调用腾讯云SES API + // 由于腾讯云SES API配置较复杂,这里先返回true进行测试 + return true; + + } catch (Exception e) { + logger.error("邮件发送失败", e); + return false; + } + } +} diff --git a/redis-temp/redis.zip b/redis-temp/redis.zip new file mode 100644 index 0000000..7a5fee6 Binary files /dev/null and b/redis-temp/redis.zip differ