feat: 实现邮箱验证码登录和腾讯云SES集成

- 实现邮箱验证码登录功能,支持自动注册新用户
- 修复验证码生成逻辑,确保前后端验证码一致
- 添加腾讯云SES webhook回调接口,支持6种邮件事件
- 配置ngrok内网穿透支持,允许外部访问
- 优化登录页面UI,采用全屏背景和居中布局
- 清理调试代码和未使用的导入
- 添加完整的配置文档和测试脚本
This commit is contained in:
AIGC Developer
2025-10-23 17:50:12 +08:00
parent 26d10a3322
commit a13ff70055
32 changed files with 1979 additions and 588 deletions

View File

@@ -40,7 +40,7 @@ public class SecurityConfig {
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态使用JWT
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/login", "/register", "/api/public/**", "/api/auth/**", "/css/**", "/js/**", "/h2-console/**").permitAll()
.requestMatchers("/login", "/register", "/api/public/**", "/api/auth/**", "/api/verification/**", "/api/email/**", "/api/tencent/**", "/css/**", "/js/**", "/h2-console/**").permitAll()
.requestMatchers("/api/orders/stats").permitAll() // 统计接口允许匿名访问
.requestMatchers("/api/orders/**").authenticated() // 订单接口需要认证
.requestMatchers("/api/payments/**").authenticated() // 支付接口需要认证
@@ -82,8 +82,19 @@ public class SecurityConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// 允许前端开发服务器
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000", "http://127.0.0.1:3000"));
// 允许前端开发服务器和ngrok域名
configuration.setAllowedOrigins(Arrays.asList(
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://localhost:5173",
"http://127.0.0.1:5173",
"https://*.ngrok.io",
"https://*.ngrok-free.app"
));
configuration.setAllowedOriginPatterns(Arrays.asList(
"https://*.ngrok.io",
"https://*.ngrok-free.app"
));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);

View File

@@ -5,6 +5,7 @@ import java.util.Locale;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
@@ -31,6 +32,8 @@ public class WebMvcConfig implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
// CORS配置已移至SecurityConfig避免冲突
}

View File

@@ -107,11 +107,40 @@ public class AuthApiController {
.body(createErrorResponse("验证码错误或已过期"));
}
// 查找用户
// 查找用户,如果不存在则自动注册
User user = userService.findByEmail(email);
if (user == null) {
return ResponseEntity.badRequest()
.body(createErrorResponse("用户不存在"));
// 自动注册新用户
try {
// 从邮箱生成用户名(去掉@符号及后面的部分)
String username = email.split("@")[0];
// 确保用户名唯一
String originalUsername = username;
int counter = 1;
while (userService.findByUsername(username) != null) {
username = originalUsername + counter;
counter++;
}
// 创建新用户
user = new User();
user.setUsername(username);
user.setEmail(email);
user.setPasswordHash(""); // 邮箱登录不需要密码
user.setRole("ROLE_USER"); // 默认为普通用户
user.setPoints(50); // 默认积分
user.setNickname(username); // 默认昵称为用户名
user.setIsActive(true);
// 保存用户
user = userService.save(user);
logger.info("自动注册新用户:{}", email);
} catch (Exception e) {
logger.error("自动注册用户失败:{}", email, e);
return ResponseEntity.badRequest()
.body(createErrorResponse("用户注册失败:" + e.getMessage()));
}
}
// 生成JWT Token

View File

@@ -18,7 +18,6 @@ import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

View File

@@ -0,0 +1,296 @@
package com.example.demo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 腾讯云SES Webhook控制器
* 用于接收SES服务的推送数据
*/
@RestController
@RequestMapping("/api/email")
public class SesWebhookController {
private static final Logger logger = LoggerFactory.getLogger(SesWebhookController.class);
/**
* 处理邮件发送状态回调
* 当邮件发送成功或失败时SES会推送状态信息
*/
@PostMapping("/send-status")
public ResponseEntity<Map<String, Object>> handleSendStatus(@RequestBody Map<String, Object> data) {
logger.info("收到邮件发送状态回调: {}", data);
try {
// 解析SES推送的数据
String messageId = (String) data.get("MessageId");
String status = (String) data.get("Status");
String email = (String) data.get("Email");
String timestamp = (String) data.get("Timestamp");
logger.info("邮件发送状态 - MessageId: {}, Status: {}, Email: {}, Timestamp: {}",
messageId, status, email, timestamp);
// 根据状态进行相应处理
switch (status) {
case "Send":
logger.info("邮件发送成功: {}", email);
// 可以更新数据库中的发送状态
break;
case "Reject":
logger.warn("邮件发送被拒绝: {}", email);
// 处理发送被拒绝的情况
break;
case "Bounce":
logger.warn("邮件发送失败(退信): {}", email);
// 处理退信情况
break;
default:
logger.info("邮件发送状态: {} - {}", status, email);
}
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "状态回调处理成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("处理邮件发送状态回调失败", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "处理失败: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/**
* 处理邮件投递状态回调
* 当邮件被投递到收件人邮箱时SES会推送投递状态
*/
@PostMapping("/delivery-status")
public ResponseEntity<Map<String, Object>> handleDeliveryStatus(@RequestBody Map<String, Object> data) {
logger.info("收到邮件投递状态回调: {}", data);
try {
String messageId = (String) data.get("MessageId");
String status = (String) data.get("Status");
String email = (String) data.get("Email");
String timestamp = (String) data.get("Timestamp");
logger.info("邮件投递状态 - MessageId: {}, Status: {}, Email: {}, Timestamp: {}",
messageId, status, email, timestamp);
if ("Delivery".equals(status)) {
logger.info("邮件投递成功: {}", email);
// 可以更新数据库中的投递状态
}
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "投递状态回调处理成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("处理邮件投递状态回调失败", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "处理失败: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/**
* 处理邮件退信回调
* 当邮件无法投递时SES会推送退信信息
*/
@PostMapping("/bounce")
public ResponseEntity<Map<String, Object>> handleBounce(@RequestBody Map<String, Object> data) {
logger.info("收到邮件退信回调: {}", data);
try {
String messageId = (String) data.get("MessageId");
String email = (String) data.get("Email");
String bounceType = (String) data.get("BounceType");
String bounceSubType = (String) data.get("BounceSubType");
String timestamp = (String) data.get("Timestamp");
logger.warn("邮件退信 - MessageId: {}, Email: {}, BounceType: {}, BounceSubType: {}, Timestamp: {}",
messageId, email, bounceType, bounceSubType, timestamp);
// 处理退信逻辑
// 1. 记录退信信息到数据库
// 2. 如果是硬退信,可以考虑从邮件列表中移除该邮箱
// 3. 如果是软退信,可以稍后重试
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "退信回调处理成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("处理邮件退信回调失败", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "处理失败: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/**
* 处理邮件投诉回调
* 当收件人投诉邮件为垃圾邮件时SES会推送投诉信息
*/
@PostMapping("/complaint")
public ResponseEntity<Map<String, Object>> handleComplaint(@RequestBody Map<String, Object> data) {
logger.info("收到邮件投诉回调: {}", data);
try {
String messageId = (String) data.get("MessageId");
String email = (String) data.get("Email");
String complaintType = (String) data.get("ComplaintType");
String timestamp = (String) data.get("Timestamp");
logger.warn("邮件投诉 - MessageId: {}, Email: {}, ComplaintType: {}, Timestamp: {}",
messageId, email, complaintType, timestamp);
// 处理投诉逻辑
// 1. 记录投诉信息到数据库
// 2. 考虑从邮件列表中移除该邮箱
// 3. 检查邮件内容是否合规
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "投诉回调处理成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("处理邮件投诉回调失败", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "处理失败: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/**
* 处理邮件打开事件回调
* 当收件人打开邮件时SES会推送打开事件
*/
@PostMapping("/open")
public ResponseEntity<Map<String, Object>> handleOpen(@RequestBody Map<String, Object> data) {
logger.info("收到邮件打开事件回调: {}", data);
try {
String messageId = (String) data.get("MessageId");
String email = (String) data.get("Email");
String timestamp = (String) data.get("Timestamp");
String userAgent = (String) data.get("UserAgent");
String ipAddress = (String) data.get("IpAddress");
logger.info("邮件打开事件 - MessageId: {}, Email: {}, Timestamp: {}, UserAgent: {}, IpAddress: {}",
messageId, email, timestamp, userAgent, ipAddress);
// 处理打开事件逻辑
// 1. 记录邮件打开统计
// 2. 更新用户活跃度
// 3. 分析用户行为
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "打开事件回调处理成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("处理邮件打开事件回调失败", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "处理失败: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/**
* 处理邮件点击事件回调
* 当收件人点击邮件中的链接时SES会推送点击事件
*/
@PostMapping("/click")
public ResponseEntity<Map<String, Object>> handleClick(@RequestBody Map<String, Object> data) {
logger.info("收到邮件点击事件回调: {}", data);
try {
String messageId = (String) data.get("MessageId");
String email = (String) data.get("Email");
String timestamp = (String) data.get("Timestamp");
String link = (String) data.get("Link");
String userAgent = (String) data.get("UserAgent");
String ipAddress = (String) data.get("IpAddress");
logger.info("邮件点击事件 - MessageId: {}, Email: {}, Timestamp: {}, Link: {}, UserAgent: {}, IpAddress: {}",
messageId, email, timestamp, link, userAgent, ipAddress);
// 处理点击事件逻辑
// 1. 记录链接点击统计
// 2. 分析用户兴趣
// 3. 更新用户行为数据
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "点击事件回调处理成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("处理邮件点击事件回调失败", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "处理失败: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/**
* 处理SES配置集事件回调
* 当配置集状态发生变化时SES会推送配置集事件
*/
@PostMapping("/configuration-set")
public ResponseEntity<Map<String, Object>> handleConfigurationSet(@RequestBody Map<String, Object> data) {
logger.info("收到SES配置集事件回调: {}", data);
try {
String eventType = (String) data.get("EventType");
String configurationSet = (String) data.get("ConfigurationSet");
String timestamp = (String) data.get("Timestamp");
logger.info("SES配置集事件 - EventType: {}, ConfigurationSet: {}, Timestamp: {}",
eventType, configurationSet, timestamp);
// 处理配置集事件逻辑
// 1. 更新配置集状态
// 2. 记录配置变更历史
// 3. 发送通知给管理员
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "配置集事件回调处理成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("处理SES配置集事件回调失败", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "处理失败: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
}

View File

@@ -0,0 +1,302 @@
package com.example.demo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* 腾讯云SES邮件推送回调控制器
* 用于接收腾讯云SES服务的邮件事件推送
*
* 支持的事件类型:
* - 递送成功 (delivery)
* - 腾讯云拒信 (reject)
* - ESP退信 (bounce)
* - 用户打开邮件 (open)
* - 点击链接 (click)
* - 退订 (unsubscribe)
*/
@RestController
@RequestMapping("/api/tencent/ses")
public class TencentSesWebhookController {
private static final Logger logger = LoggerFactory.getLogger(TencentSesWebhookController.class);
/**
* 腾讯云SES邮件事件回调接口
*
* 回调地址配置:
* - 账户级回调https://your-domain.com/api/tencent/ses/webhook
* - 发信地址级回调https://your-domain.com/api/tencent/ses/webhook
*
* 支持端口8080, 8081, 8082
*/
@PostMapping("/webhook")
public ResponseEntity<Map<String, Object>> handleSesWebhook(
@RequestBody Map<String, Object> payload,
@RequestHeader Map<String, String> headers) {
logger.info("收到腾讯云SES回调: {}", payload);
logger.info("请求头: {}", headers);
try {
// 验证签名(可选,建议在生产环境中启用)
if (!verifySignature(payload, headers)) {
logger.warn("签名验证失败");
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "签名验证失败");
return ResponseEntity.badRequest().body(response);
}
// 解析回调数据
String eventType = (String) payload.get("eventType");
String messageId = (String) payload.get("messageId");
String email = (String) payload.get("email");
String timestamp = (String) payload.get("timestamp");
logger.info("SES事件 - Type: {}, MessageId: {}, Email: {}, Timestamp: {}",
eventType, messageId, email, timestamp);
// 根据事件类型处理
switch (eventType) {
case "delivery":
handleDeliveryEvent(payload);
break;
case "reject":
handleRejectEvent(payload);
break;
case "bounce":
handleBounceEvent(payload);
break;
case "open":
handleOpenEvent(payload);
break;
case "click":
handleClickEvent(payload);
break;
case "unsubscribe":
handleUnsubscribeEvent(payload);
break;
default:
logger.warn("未知事件类型: {}", eventType);
handleUnknownEvent(payload);
}
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "回调处理成功");
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.ok(response);
} catch (Exception e) {
logger.error("处理SES回调失败", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "处理失败: " + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/**
* 处理邮件递送成功事件
*/
private void handleDeliveryEvent(Map<String, Object> payload) {
String messageId = (String) payload.get("messageId");
String email = (String) payload.get("email");
String timestamp = (String) payload.get("timestamp");
logger.info("邮件递送成功 - MessageId: {}, Email: {}, Timestamp: {}",
messageId, email, timestamp);
// 业务处理逻辑
// 1. 更新数据库中的邮件状态
// 2. 记录递送统计
// 3. 更新用户活跃度
// 4. 发送递送成功通知(如需要)
// TODO: 实现具体的业务逻辑
updateEmailDeliveryStatus(messageId, email, "delivered");
}
/**
* 处理腾讯云拒信事件
*/
private void handleRejectEvent(Map<String, Object> payload) {
String messageId = (String) payload.get("messageId");
String email = (String) payload.get("email");
String reason = (String) payload.get("reason");
String timestamp = (String) payload.get("timestamp");
logger.warn("腾讯云拒信 - MessageId: {}, Email: {}, Reason: {}, Timestamp: {}",
messageId, email, reason, timestamp);
// 业务处理逻辑
// 1. 记录拒信原因
// 2. 检查邮件内容合规性
// 3. 更新发送策略
// 4. 通知管理员
updateEmailDeliveryStatus(messageId, email, "rejected");
}
/**
* 处理ESP退信事件
*/
private void handleBounceEvent(Map<String, Object> payload) {
String messageId = (String) payload.get("messageId");
String email = (String) payload.get("email");
String bounceType = (String) payload.get("bounceType");
String bounceSubType = (String) payload.get("bounceSubType");
String timestamp = (String) payload.get("timestamp");
logger.warn("邮件退信 - MessageId: {}, Email: {}, BounceType: {}, BounceSubType: {}, Timestamp: {}",
messageId, email, bounceType, bounceSubType, timestamp);
// 业务处理逻辑
// 1. 区分硬退信和软退信
// 2. 硬退信:从邮件列表移除
// 3. 软退信:稍后重试
// 4. 更新邮箱有效性状态
if ("Permanent".equals(bounceType)) {
// 硬退信,移除邮箱
removeInvalidEmail(email);
} else {
// 软退信,标记重试
markEmailForRetry(messageId, email);
}
updateEmailDeliveryStatus(messageId, email, "bounced");
}
/**
* 处理邮件打开事件
*/
private void handleOpenEvent(Map<String, Object> payload) {
String messageId = (String) payload.get("messageId");
String email = (String) payload.get("email");
String timestamp = (String) payload.get("timestamp");
String userAgent = (String) payload.get("userAgent");
String ipAddress = (String) payload.get("ipAddress");
logger.info("邮件打开事件 - MessageId: {}, Email: {}, Timestamp: {}, UserAgent: {}, IpAddress: {}",
messageId, email, timestamp, userAgent, ipAddress);
// 业务处理逻辑
// 1. 记录邮件打开统计
// 2. 更新用户活跃度
// 3. 分析用户行为
// 4. 触发后续营销活动
recordEmailOpen(messageId, email, timestamp, userAgent, ipAddress);
}
/**
* 处理链接点击事件
*/
private void handleClickEvent(Map<String, Object> payload) {
String messageId = (String) payload.get("messageId");
String email = (String) payload.get("email");
String timestamp = (String) payload.get("timestamp");
String link = (String) payload.get("link");
String userAgent = (String) payload.get("userAgent");
String ipAddress = (String) payload.get("ipAddress");
logger.info("链接点击事件 - MessageId: {}, Email: {}, Timestamp: {}, Link: {}, UserAgent: {}, IpAddress: {}",
messageId, email, timestamp, link, userAgent, ipAddress);
// 业务处理逻辑
// 1. 记录链接点击统计
// 2. 分析用户兴趣
// 3. 更新用户行为数据
// 4. 触发转化跟踪
recordLinkClick(messageId, email, timestamp, link, userAgent, ipAddress);
}
/**
* 处理退订事件
*/
private void handleUnsubscribeEvent(Map<String, Object> payload) {
String messageId = (String) payload.get("messageId");
String email = (String) payload.get("email");
String timestamp = (String) payload.get("timestamp");
String unsubscribeType = (String) payload.get("unsubscribeType");
logger.info("用户退订 - MessageId: {}, Email: {}, Timestamp: {}, UnsubscribeType: {}",
messageId, email, timestamp, unsubscribeType);
// 业务处理逻辑
// 1. 立即停止向该邮箱发送邮件
// 2. 更新用户订阅状态
// 3. 记录退订原因
// 4. 发送退订确认邮件
unsubscribeUser(email, unsubscribeType);
}
/**
* 处理未知事件类型
*/
private void handleUnknownEvent(Map<String, Object> payload) {
logger.warn("收到未知事件类型: {}", payload);
// 记录到数据库或日志文件,供后续分析
}
/**
* 验证签名(简化版本)
* 生产环境中应实现完整的签名验证逻辑
*/
private boolean verifySignature(Map<String, Object> payload, Map<String, String> headers) {
// TODO: 实现腾讯云SES签名验证
// 1. 获取签名相关头部
// 2. 验证时间戳
// 3. 验证签名算法
// 4. 验证Token
String token = headers.get("X-Tencent-Token");
String timestamp = headers.get("X-Tencent-Timestamp");
String signature = headers.get("X-Tencent-Signature");
// 简化验证:检查必要头部是否存在
return token != null && timestamp != null && signature != null;
}
// ========== 业务方法实现 ==========
private void updateEmailDeliveryStatus(String messageId, String email, String status) {
// TODO: 更新数据库中的邮件状态
logger.info("更新邮件状态 - MessageId: {}, Email: {}, Status: {}", messageId, email, status);
}
private void removeInvalidEmail(String email) {
// TODO: 从邮件列表中移除无效邮箱
logger.info("移除无效邮箱: {}", email);
}
private void markEmailForRetry(String messageId, String email) {
// TODO: 标记邮件重试
logger.info("标记邮件重试 - MessageId: {}, Email: {}", messageId, email);
}
private void recordEmailOpen(String messageId, String email, String timestamp, String userAgent, String ipAddress) {
// TODO: 记录邮件打开统计
logger.info("记录邮件打开 - MessageId: {}, Email: {}, Timestamp: {}", messageId, email, timestamp);
}
private void recordLinkClick(String messageId, String email, String timestamp, String link, String userAgent, String ipAddress) {
// TODO: 记录链接点击统计
logger.info("记录链接点击 - MessageId: {}, Email: {}, Link: {}", messageId, email, link);
}
private void unsubscribeUser(String email, String unsubscribeType) {
// TODO: 处理用户退订
logger.info("处理用户退订 - Email: {}, Type: {}", email, unsubscribeType);
}
}

View File

@@ -13,7 +13,6 @@ import java.util.Map;
*/
@RestController
@RequestMapping("/api/verification")
@CrossOrigin(origins = "*")
public class VerificationCodeController {
@Autowired
@@ -97,4 +96,49 @@ public class VerificationCodeController {
return ResponseEntity.ok(response);
}
/**
* 开发模式:设置验证码(仅开发环境使用)
*/
@PostMapping("/email/dev-set")
public ResponseEntity<Map<String, Object>> setDevCode(@RequestBody Map<String, String> request) {
Map<String, Object> response = new HashMap<>();
// 仅开发环境允许
if (!"dev".equals(System.getProperty("spring.profiles.active")) &&
!"development".equals(System.getProperty("spring.profiles.active"))) {
response.put("success", false);
response.put("message", "此接口仅开发环境可用");
return ResponseEntity.badRequest().body(response);
}
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 {
// 直接设置验证码到内存存储
verificationCodeService.setVerificationCode(email, code);
response.put("success", true);
response.put("message", "开发模式验证码设置成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
response.put("success", false);
response.put("message", "设置验证码失败:" + e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
}

View File

@@ -16,7 +16,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.UUID;
@@ -95,9 +94,7 @@ public class AlipayService {
if (response.isSuccess()) {
logger.info("支付宝支付订单创建成功,订单号:{}", payment.getOrderId());
// 返回支付URL前端可以跳转到这个URL进行支付
String paymentUrl = gatewayUrl + "?" + response.getBody();
return paymentUrl;
return response.getBody();
} else {
logger.error("支付宝支付订单创建失败:{}", response.getMsg());
payment.setStatus(PaymentStatus.FAILED);

View File

@@ -12,7 +12,6 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
@@ -200,3 +199,4 @@ public class PayPalService {
}
}

View File

@@ -134,6 +134,22 @@ public class VerificationCodeService {
}
}
/**
* 开发模式:直接设置验证码(仅开发环境使用)
*/
public void setVerificationCode(String email, String code) {
String codeKey = "email_code:" + email;
verificationCodes.put(codeKey, code);
// 设置验证码过期时间
scheduler.schedule(() -> {
verificationCodes.remove(codeKey);
logger.info("开发模式验证码已过期,邮箱: {}", email);
}, CODE_EXPIRE_MINUTES, TimeUnit.MINUTES);
logger.info("开发模式验证码设置成功,邮箱: {}, 验证码: {}", email, code);
}
/**
* 发送邮件简化版本实际使用时需要配置正确的腾讯云SES API

View File

@@ -4,5 +4,5 @@ spring.thymeleaf.cache=false
spring.profiles.active=dev
# 服务器配置
server.address=api.yourdomain.com
server.address=localhost
server.port=8080