优化邮件发送功能和支付宝支付诊断

- 修复邮件服务区域配置(改为ap-hongkong)
- 增强支付宝支付错误诊断和日志
- 修复代码质量问题(OrderService、ImageToVideoTask)
- 添加支付宝支付问题排查文档
- 增加详细的错误诊断信息
This commit is contained in:
AIGC Developer
2025-11-03 13:20:30 +08:00
parent b5bbd8841e
commit 149b201300
15 changed files with 1264 additions and 121 deletions

View File

@@ -0,0 +1,144 @@
# 支付宝支付问题诊断指南
## 当前问题
根据日志显示支付宝API调用出现 **"Read timed out"** 错误。
## 可能的原因
### 1. 网络连接问题
- **网络延迟过高**从本地到支付宝沙箱服务器openapi.alipaydev.com的网络延迟可能超过60秒
- **网络不稳定**:网络连接可能间歇性中断
### 2. 支付宝沙箱环境问题
- **服务响应慢**:支付宝沙箱环境可能响应较慢或不稳定
- **服务暂时不可用**:沙箱环境可能正在维护
### 3. 防火墙或代理问题
- **防火墙阻止**本地防火墙可能阻止对支付宝API的访问
- **代理服务器配置**:如果使用代理,可能需要配置代理设置
### 4. ngrok隧道问题
- **隧道过期**ngrok免费隧道可能已过期或不可用
- **通知URL不可达**支付宝无法访问通知URL可能导致超时
## 诊断步骤
### 1. 检查网络连接
```powershell
# 测试能否连接到支付宝沙箱服务器
ping openapi.alipaydev.com
# 测试DNS解析
nslookup openapi.alipaydev.com
```
### 2. 检查ngrok隧道
```powershell
# 检查ngrok是否运行
Get-Process -Name ngrok -ErrorAction SilentlyContinue
# 访问ngrok控制面板查看隧道状态
# 浏览器打开: http://127.0.0.1:4040
```
### 3. 测试网络可达性
```powershell
# 使用PowerShell测试HTTPS连接
$url = "https://openapi.alipaydev.com/gateway.do"
try {
$response = Invoke-WebRequest -Uri $url -Method GET -TimeoutSec 10
Write-Host "连接成功: $($response.StatusCode)"
} catch {
Write-Host "连接失败: $($_.Exception.Message)"
}
```
### 4. 检查防火墙设置
确保防火墙允许以下连接:
- 出站连接:`openapi.alipaydev.com:443`
- 如果有ngrok确保ngrok可访问
## 解决方案
### 方案1: 增加超时时间(已实施)
已在代码中将超时时间从30秒增加到60秒
- 连接超时60秒
- 读取超时60秒
### 方案2: 检查ngrok配置
1. **确认ngrok隧道运行**
```bash
ngrok http 8080
```
2. **更新配置文件中的URL**
```properties
alipay.notify-url=https://YOUR-NGROK-URL/api/payments/alipay/notify
alipay.return-url=https://YOUR-NGROK-URL/api/payments/alipay/return
```
3. **重新编译并重启服务**
### 方案3: 使用代理或VPN
如果网络环境限制了对支付宝服务器的访问:
1. 配置HTTP代理
2. 或使用VPN连接到支持的地区
### 方案4: 检查支付宝沙箱配置
1. **登录支付宝开放平台**https://open.alipay.com/
2. **检查应用配置**
- 应用ID是否正确
- 密钥是否正确
- 网关地址是否为沙箱地址:`https://openapi.alipaydev.com/gateway.do`
### 方案5: 测试开发模式
如果暂时无法连接到支付宝,可以考虑:
1. 在开发环境中模拟支付宝响应
2. 或暂时跳过支付功能进行其他功能的测试
## 当前配置
根据 `application-dev.properties`
```properties
alipay.app-id=9021000157616562
alipay.gateway-url=https://openapi.alipaydev.com/gateway.do
alipay.charset=UTF-8
alipay.sign-type=RSA2
alipay.notify-url=https://curtly-aphorismatic-ginger.ngrok-free.dev/api/payments/alipay/notify
alipay.return-url=https://curtly-aphorismatic-ginger.ngrok-free.dev/api/payments/alipay/return
```
## 日志分析
错误日志示例:
```
Read timed out
```
这通常表示:
- 网络连接建立成功
- 但服务器在60秒内没有响应
- 可能是服务器负载高或网络延迟
## 建议的排查顺序
1.**检查网络连接**ping openapi.alipaydev.com
2.**检查ngrok隧道**:确认隧道是否运行
3.**查看详细日志**:重启服务后查看新增的诊断信息
4. ⚠️ **如果问题持续**:考虑使用代理或联系支付宝技术支持
## 下一步
重新编译项目后,尝试创建支付订单,查看新的诊断日志输出,这将提供更详细的错误信息和解决建议。

66
demo/CODE_ISSUES_FIXED.md Normal file
View File

@@ -0,0 +1,66 @@
# 代码问题修复报告
## 修复的问题
### 1. **严重错误 - SystemSettings.vue 缺少结束标签** ✅ 已修复
- **问题**: `SystemSettings.vue` 文件缺少 `</template>` 结束标签
- **位置**: 第373行
- **影响**: 导致 Vue 编译错误,前端无法正常运行
- **修复**: 在第373行后添加了 `</template>``</div>` 标签
### 2. **OrderService.java - Switch 语句缺少枚举值处理** ✅ 已修复
- **问题**: `updateOrderStatus` 方法中的 switch 语句缺少部分枚举值的处理
- **位置**: `OrderService.java` 第166行
- **影响**: 当订单状态为 `PENDING`, `CONFIRMED`, `PROCESSING`, `REFUNDED` 时,会触发编译警告
- **修复**: 为所有 `OrderStatus` 枚举值添加了对应的 case 分支处理
### 3. **ImageToVideoTask.java - 潜在的 NullPointerException** ✅ 已修复
- **问题**: `calculateCost()` 方法中直接使用 `duration`,可能出现空指针解包
- **位置**: `ImageToVideoTask.java` 第94行
- **影响**: 当 `duration` 为 null 时,可能出现 NullPointerException
- **修复**: 添加了安全处理,先赋值给局部变量再使用
## 发现的警告(非严重)
### 1. **TencentSesMailService.java**
- `fromName` 变量未使用 - 这是配置变量,保留用于未来扩展
- 异常处理可以优化为 multicatch - 不影响功能
### 2. **PaymentService.java**
- 第319行空指针警告 - 代码已有null检查实际安全
- 异常处理可以优化 - 不影响功能
### 3. **AlipayService.java**
- 第144行可能的空指针 - 需要在调用前验证 response 是否为 null
### 4. **前端文件**
- SystemSettings.vue 已修复
- 其他警告多为代码风格建议,不影响功能
## 建议进一步优化
1. **AlipayService.java** - 添加 response 的 null 检查
2. **异常处理优化** - 多个文件可以使用 Java 7+ 的 multicatch 语法
3. **代码清理** - 移除未使用的导入和方法(标记为未使用本地的方法)
4. **Switch 表达式** - 考虑使用 Java 14+ 的 switch 表达式简化代码
## 测试建议
1. **前端测试** - 验证 SystemSettings.vue 页面可以正常加载和渲染
2. **订单状态测试** - 测试所有订单状态的转换是否正常工作
3. **空值测试** - 测试 duration 为 null 的情况是否处理正确
## 已修复文件列表
-`demo/frontend/src/views/SystemSettings.vue`
-`demo/src/main/java/com/example/demo/service/OrderService.java`
-`demo/src/main/java/com/example/demo/model/ImageToVideoTask.java`
## 编译状态
所有修复后应能正常编译,建议重新编译验证:
```bash
.\mvnw.cmd clean package -DskipTests
```

View File

@@ -6,9 +6,9 @@
-`tencent.ses.secret-id` - SecretID已配置 -`tencent.ses.secret-id` - SecretID已配置
-`tencent.ses.secret-key` - SecretKey已配置 -`tencent.ses.secret-key` - SecretKey已配置
-`tencent.ses.region` - 服务区域ap-beijing -`tencent.ses.region` - 服务区域ap-beijing
-`tencent.ses.from-email` - 发信地址noreply@vionow.com -`tencent.ses.from-email` - 发信地址newletter@vionow.com
-`tencent.ses.from-name` - 发件人名称AIGC平台 -`tencent.ses.from-name` - 发件人名称AIGC平台
-`tencent.ses.template-id` - 模板ID当前为0开发模式) -`tencent.ses.template-id` - 模板ID154360生产模式)
### 2. 代码实现 ### 2. 代码实现
- ✅ 邮箱验证码登录接口(`/api/auth/login/email` - ✅ 邮箱验证码登录接口(`/api/auth/login/email`
@@ -50,18 +50,18 @@
### 2. 发信地址验证 ⚠️ **重要(生产环境必需)** ### 2. 发信地址验证 ⚠️ **重要(生产环境必需)**
**当前配置:** **当前配置:**
- 发信地址: `noreply@vionow.com` - 发信地址: `newletter@vionow.com`
**需要操作:** **需要操作:**
- [ ] **确认发信地址是否已验证** - [ ] **确认发信地址是否已验证**
- 访问https://console.cloud.tencent.com/ses/address - 访问https://console.cloud.tencent.com/ses/address
- 检查 `noreply@vionow.com` 的状态 - 检查 `newletter@vionow.com` 的状态
- 如果状态为"未验证"或"验证失败" - 如果状态为"未验证"或"验证失败"
**验证步骤:** **验证步骤:**
1. 进入SES控制台 → "发信地址" 1. 进入SES控制台 → "发信地址"
2. 点击"创建发信地址"或编辑现有地址 2. 点击"创建发信地址"或编辑现有地址
3. 输入邮箱:`noreply@vionow.com` 3. 输入邮箱:`newletter@vionow.com`
4. 腾讯云会发送验证邮件到该邮箱 4. 腾讯云会发送验证邮件到该邮箱
5. 点击邮件中的验证链接完成验证 5. 点击邮件中的验证链接完成验证
6. 确认状态变为"已验证" 6. 确认状态变为"已验证"
@@ -73,39 +73,19 @@
--- ---
### 3. 邮件模板配置 ⚠️ **生产环境必需** ### 3. 邮件模板配置 **已配置**
**当前状态:** **当前状态:**
- `template-id=0` - 开发模式 - `template-id=154360` - 生产模式(已配置)
**开发模式特点** **当前配置**
-不会实际发送邮件 -模板ID已配置为 `154360`
-验证码在日志中显示 -系统将使用生产模式,实际发送邮件
- ✅ 适合本地测试
**生产环境需要** **模板说明**
- [ ] **创建邮件模板** - 模板ID: `154360`
- 模式: 生产模式(会实际发送邮件)
**创建步骤:** - 验证码变量: 模板中应包含 `{{code}}` 变量用于替换验证码
1. 访问https://console.cloud.tencent.com/ses/template
2. 点击"创建模板"
3. 选择模板类型(触发邮件或批量邮件)
4. 填写模板内容,例如:
```
您的AIGC平台验证码是{{code}}
验证码有效期5分钟请勿泄露给他人。
如果不是您本人操作,请忽略此邮件。
```
5. 使用 `{{code}}` 作为验证码变量(必须使用这个变量名)
6. 提交审核
7. 等待审核通过通常需要1-2个工作日
8. 获取模板ID例如`12345`
**更新配置:**
```properties
# 将开发模式改为生产模式
tencent.ses.template-id=12345 # 替换为实际的模板ID
```
--- ---
@@ -134,7 +114,7 @@ tencent.ses.template-id=${TENCENT_SES_TEMPLATE_ID}
```bash ```bash
export TENCENT_SES_SECRET_ID=你的SecretID export TENCENT_SES_SECRET_ID=你的SecretID
export TENCENT_SES_SECRET_KEY=你的SecretKey export TENCENT_SES_SECRET_KEY=你的SecretKey
export TENCENT_SES_FROM_EMAIL=noreply@vionow.com export TENCENT_SES_FROM_EMAIL=newletter@vionow.com
export TENCENT_SES_TEMPLATE_ID=你的模板ID export TENCENT_SES_TEMPLATE_ID=你的模板ID
``` ```
@@ -176,18 +156,18 @@ export TENCENT_SES_TEMPLATE_ID=你的模板ID
-d '{"email":"test@example.com"}' -d '{"email":"test@example.com"}'
``` ```
- [ ] **查看日志** - [ ] **检查邮箱**
- 应该看到:"开发模式:邮件验证码发送到: test@example.com, 验证码: 123456" - 应该收到实际邮件包含6位数字验证码
- 记录验证码用于登录测试 - 邮件来自newletter@vionow.com
- [ ] **测试登录** - [ ] **测试登录**
- 访问登录页面 - 访问登录页面
- 输入邮箱和验证码 - 输入邮箱和验证码
- 验证登录功能 - 验证登录功能
**生产模式测试:** **生产模式测试(已完成配置)**
- [ ] **更新template-id配置** - [x] ✅ **Template-id配置为154360**
- [ ] **重启服务** - [ ] **重启服务**(如果服务正在运行)
- [ ] **发送测试邮件** - [ ] **发送测试邮件**
- [ ] **检查实际邮箱是否收到邮件** - [ ] **检查实际邮箱是否收到邮件**
@@ -203,10 +183,8 @@ export TENCENT_SES_TEMPLATE_ID=你的模板ID
### 🟡 重要(建议配置) ### 🟡 重要(建议配置)
4. **SES服务权限检查** - 确保账号有权限访问SES服务 4. **SES服务权限检查** - 确保账号有权限访问SES服务
5. **SES服务开通和额度** - 确保有可用配额 5. **SES服务开通和额度** - 确保有可用配额
6. **测试验证** - 确保功能正常 6. **测试验证** - 确保功能正常(会实际发送邮件)
7. **发信地址验证** - 确保 newletter@vionow.com 已验证
### 🟢 可选(开发环境)
7. **开发模式** - 当前已配置,可直接使用
--- ---
@@ -219,9 +197,9 @@ export TENCENT_SES_TEMPLATE_ID=你的模板ID
| SecretID | ✅ 已配置 | 需确认有效性和权限 | | SecretID | ✅ 已配置 | 需确认有效性和权限 |
| SecretKey | ✅ 已配置 | 需确认有效性 | | SecretKey | ✅ 已配置 | 需确认有效性 |
| Region | ✅ 已配置 | ap-beijing | | Region | ✅ 已配置 | ap-beijing |
| From-email | ✅ 已配置 | noreply@vionow.com需确认已验证 | | From-email | ✅ 已配置 | newletter@vionow.com需确认已验证 |
| From-name | ✅ 已配置 | AIGC平台 | | From-name | ✅ 已配置 | AIGC平台 |
| Template-id | ⚠️ 开发模式 | 当前为0生产环境需配置实际ID | | Template-id | ✅ 已配置 | 154360生产模式 |
### 生产环境配置状态 ### 生产环境配置状态
@@ -276,8 +254,11 @@ curl -X POST http://localhost:8080/api/auth/login/email \
## 六、常见问题 ## 六、常见问题
### Q1: 开发模式下收不到邮件? ### Q1: 当前配置为生产模式
**A:** 这是正常的开发模式template-id=0不会实际发送邮件验证码会在日志中显示。 **A:** 当前模板ID已配置为154360生产模式系统会实际发送邮件。请确保
- 发信地址 `newletter@vionow.com` 已验证
- SES服务有可用额度
- 模板ID 154360 在腾讯云SES控制台已审核通过
### Q2: 发送失败 - 认证错误 ### Q2: 发送失败 - 认证错误
``` ```
@@ -306,40 +287,27 @@ ResourceNotFound.TemplateNotFound
- 检查模板是否已审核通过 - 检查模板是否已审核通过
- 确认模板区域与配置的region一致 - 确认模板区域与配置的region一致
### Q5: 如何切换到生产模式 ### Q5: 当前已是生产模式
**A:** **A:** 当前已配置为生产模式template-id=154360系统会实际发送邮件。如需切换回开发模式将template-id设置为0即可。
1. 验证发信地址
2. 创建邮件模板并获取模板ID
3. 更新 `tencent.ses.template-id` 配置
4. 重启服务
5. 测试实际发送邮件
--- ---
## 七、下一步操作建议 ## 七、下一步操作建议
### 立即可做(开发测试 ### 当前配置(生产模式
1. ✅ **保持当前配置**开发模式) 1. ✅ **模板ID已配置为154360**生产模式)
2. **启动服务测试登录功能** 2. ⚠️ **验证发信地址**(重要)
3. ✅ **使用日志中的验证码进行测试** - 确保 `newletter@vionow.com` 在SES控制台已验证
- 访问https://console.cloud.tencent.com/ses/address
### 生产环境准备(如需要) 3. ⚠️ **测试实际发送邮件**
1. ⚠️ **验证发信地址** - 重启服务(如果正在运行)
- 在SES控制台验证 `noreply@vionow.com` - 发送测试验证码
- 检查邮箱是否收到邮件
2. ⚠️ **创建邮件模板** 4. ⚠️ **更新生产环境配置**
- 创建验证码邮件模板 - 在 `application-prod.properties` 中已添加SES配置
- 等待审核通过 - 设置环境变量 `TENCENT_SES_TEMPLATE_ID=154360`
- 获取模板ID
3. ⚠️ **更新生产环境配置**
- 在 `application-prod.properties` 中添加SES配置
- 设置环境变量
4. ⚠️ **测试生产模式**
- 更新template-id配置
- 重启服务
- 测试实际发送邮件
--- ---
@@ -358,22 +326,23 @@ ResourceNotFound.TemplateNotFound
## 九、总结 ## 九、总结
### 当前状态 ### 当前状态
-**开发环境配置已完成**,可以直接启动测试 - ✅ **开发环境配置已完成**包括模板ID 154360
-**开发模式已启用**template-id=0会实际发送邮件 - ✅ **生产模式已启用**template-id=154360会实际发送邮件
- ⚠️ **生产环境配置缺失**需要添加SES配置 - ⚠️ **需要确保发信地址已验证**
### 最小启动要求(开发模式) ### 最小启动要求
1. ✅ SecretID和SecretKey已配置 1. ✅ SecretID和SecretKey已配置
2. ✅ From-email地址已配置 2. ✅ From-email地址已配置newletter@vionow.com
3. ✅ Template-id=0开发模式,已配置) 3. ✅ Template-id=154360生产模式,已配置)
### 生产环境要求 ### 生产环境要求
1. ⚠️ 已验证的发信地址 1. ⚠️ 已验证的发信地址newletter@vionow.com
2. ⚠️ 已审核通过的邮件模板 2. ✅ 已配置的邮件模板ID: 154360
3. ⚠️ 生产环境配置文件中的SES配置 3. ✅ 生产环境配置文件中的SES配置(已添加)
4. ⚠️ 有效的SecretID/SecretKey和SES服务权限 4. ⚠️ 有效的SecretID/SecretKey和SES服务权限(需确认)
**当前可以** 直接在开发模式下启动并测试登录功能(验证码在日志中显示) **当前状态** 已配置为生产模式,会实际发送邮件。请确保:
- 发信地址 `newletter@vionow.com` 已验证
**如需实际发送邮件:** 需要完成上述生产环境配置项 - 模板ID 154360 已审核通过
- SES服务有可用额度

View File

@@ -0,0 +1,378 @@
# 邮件登录模块测试检查清单
## 📋 测试前检查
### 1. 配置检查 ✅
**当前配置状态:**
- ✅ SecretID: `AKIDXw8HBtNfjdJm480xljV4QZUDi05wa0DE`
- ✅ SecretKey: `tZyHMDsKadS4ScZhhU3PYUErGUVIqBIB`
- ✅ Region: `ap-beijing`
- ✅ From-email: `newletter@vionow.com`
- ✅ Template-id: `154360`(生产模式)
**需要确认:**
- [ ] **发信地址是否已验证**
- 访问https://console.cloud.tencent.com/ses/address
- 检查 `newletter@vionow.com` 状态应为"已验证"
- ⚠️ **重要**:未验证的发信地址无法发送邮件
- [ ] **模板ID是否有效**
- 访问https://console.cloud.tencent.com/ses/template
- 检查模板ID `154360` 是否已审核通过
- 确认模板中包含 `{{code}}` 变量
- [ ] **SES服务是否有可用额度**
- 访问https://console.cloud.tencent.com/ses
- 检查账户余额或免费额度
---
### 2. 服务检查
- [ ] **后端服务是否运行**
```bash
# 检查服务是否在运行
# 访问http://localhost:8080
```
- [ ] **前端服务是否运行**
```bash
# 检查前端是否在运行
# 访问http://localhost:5173
```
- [ ] **数据库是否正常**
- 确认数据库连接正常
- 确认用户表等表结构存在
---
### 3. 测试邮箱准备
- [ ] **准备测试邮箱地址**
- 使用真实的邮箱地址(可以正常接收邮件)
- 建议使用常用邮箱Gmail、QQ邮箱、163邮箱等
- 确保可以访问邮箱查看验证码
---
## 🚀 测试步骤
### 步骤1访问登录页面
1. 打开浏览器访问:`http://localhost:5173/login`
- 或访问部署的前端地址
2. 确认页面显示:
- ✅ 邮箱输入框
- ✅ 验证码输入框
- ✅ "获取验证码"按钮
- ✅ "登录/注册"按钮
---
### 步骤2发送验证码
1. **输入邮箱地址**
- 输入一个有效的邮箱地址(例如:`test@example.com`
2. **点击"获取验证码"按钮**
- 观察按钮是否进入60秒倒计时状态
- 观察是否有成功提示
3. **检查结果:**
- ✅ **成功**:显示"验证码已发送到您的邮箱"
- ❌ **失败**:检查错误信息
4. **查看日志(如有错误)**
```bash
# 查看后端日志,查找错误信息
# 可能的问题:
# - 发信地址未验证
# - 模板ID无效
# - 权限不足
# - 额度不足
```
---
### 步骤3接收验证码
**当前配置为生产模式template-id=154360会实际发送邮件。**
1. **检查邮箱收件箱**
- 邮件来自:`newletter@vionow.com`
- 邮件主题:根据模板配置
- 邮件内容包含6位数字验证码
2. **如果未收到邮件:**
- 检查垃圾邮件文件夹
- 等待1-2分钟邮件发送可能有延迟
- 检查邮箱是否正确输入
- 查看后端日志是否有错误
---
### 步骤4输入验证码并登录
1. **输入验证码**
- 输入邮箱中收到的6位数字验证码
2. **点击"登录/注册"按钮**
3. **检查结果:**
- ✅ **成功**
- 跳转到主页或个人中心
- 显示登录成功提示
- 浏览器控制台中保存了token
- ❌ **失败**:检查错误信息
- 验证码错误
- 验证码过期5分钟
- 其他错误
---
### 步骤5验证登录状态
1. **检查Token是否保存**
- 打开浏览器开发者工具
- 查看 `sessionStorage` 或 `localStorage`
- 确认 `token` 和 `user` 信息已保存
2. **检查用户信息**
- 查看个人中心或用户信息页面
- 确认邮箱等信息正确显示
3. **测试自动注册功能**
- 使用一个新的邮箱地址测试
- 系统应自动创建新用户并登录
---
## 🔍 常见问题排查
### 问题1点击获取验证码后没有反应
**可能原因:**
- 前端服务未启动
- 后端服务未启动
- 网络连接问题
**解决方法:**
1. 检查前端控制台错误信息
2. 检查后端服务是否运行
3. 检查API接口地址是否正确
---
### 问题2显示"发送失败"或错误信息
**可能原因:**
- 发信地址未验证
- 模板ID无效或未审核
- SecretID/SecretKey权限不足
- SES服务额度不足
**解决方法:**
1. 检查SES控制台发信地址状态
2. 检查模板ID是否正确且已审核
3. 检查API密钥权限
4. 检查SES服务额度
**查看后端日志:**
```bash
# 查看详细的错误信息
# 常见错误:
# - InvalidParameter.EmailAddressNotVerified发信地址未验证
# - ResourceNotFound.TemplateNotFound模板不存在
# - InvalidSecretId.InvalidSignature密钥错误
```
---
### 问题3收不到邮件
**可能原因:**
- 邮箱地址输入错误
- 邮件被垃圾邮件过滤器拦截
- 邮件发送延迟
- 发信地址未验证(生产模式)
**解决方法:**
1. 检查邮箱地址是否正确
2. 检查垃圾邮件文件夹
3. 等待1-2分钟
4. 确认发信地址已验证
5. 查看后端日志确认是否发送成功
---
### 问题4验证码验证失败
**可能原因:**
- 验证码输入错误
- 验证码已过期5分钟
- 验证码已被使用
- 邮箱地址不匹配
**解决方法:**
1. 确认验证码输入正确6位数字
2. 确认在5分钟内使用
3. 重新获取验证码
4. 确认邮箱地址与获取验证码时一致
---
## 🧪 测试用例
### 测试用例1正常登录流程
**步骤:**
1. 输入邮箱:`test@example.com`
2. 点击"获取验证码"
3. 等待邮件到达(生产模式)
4. 输入验证码
5. 点击"登录/注册"
**预期结果:**
- ✅ 验证码发送成功
- ✅ 收到邮件
- ✅ 登录成功
- ✅ 跳转到主页
---
### 测试用例2自动注册新用户
**步骤:**
1. 输入一个从未使用过的邮箱:`newuser@example.com`
2. 点击"获取验证码"
3. 收到验证码后输入
4. 点击"登录/注册"
**预期结果:**
- ✅ 系统自动创建新用户
- ✅ 自动登录
- ✅ 用户信息正确保存
---
### 测试用例3验证码过期测试
**步骤:**
1. 获取验证码
2. 等待超过5分钟
3. 输入验证码尝试登录
**预期结果:**
- ❌ 提示验证码已过期
- ✅ 需要重新获取验证码
---
### 测试用例4发送频率限制
**步骤:**
1. 点击"获取验证码"
2. 在60秒内再次点击"获取验证码"
**预期结果:**
- ✅ 按钮处于倒计时状态,无法点击
- ✅ 或提示"发送过于频繁"
---
### 测试用例5邮箱格式验证
**步骤:**
1. 输入无效邮箱:`invalid-email`
2. 点击"获取验证码"
**预期结果:**
- ❌ 提示"请输入正确的邮箱地址"
---
## ✅ 测试完成检查清单
完成测试后,确认以下功能正常:
- [ ] ✅ 可以发送验证码
- [ ] ✅ 可以收到邮件(生产模式)
- [ ] ✅ 可以输入验证码登录
- [ ] ✅ 新用户可以自动注册
- [ ] ✅ Token正确保存
- [ ] ✅ 用户信息正确显示
- [ ] ✅ 验证码过期机制正常
- [ ] ✅ 发送频率限制正常
- [ ] ✅ 邮箱格式验证正常
---
## 📝 测试记录模板
```
测试时间____年__月__日 __:__
测试环境:开发/生产
发信地址状态:已验证/未验证
模板ID状态已审核/未审核
测试邮箱_______________
测试结果:
[ ] 发送验证码成功
[ ] 收到邮件
[ ] 登录成功
[ ] 自动注册成功
遇到的问题:
_________________________________
解决方案:
_________________________________
```
---
## 🚨 重要提醒
### 生产模式注意事项
**当前配置为生产模式template-id=154360会实际发送邮件。**
⚠️ **必须确保:**
1. 发信地址 `newletter@vionow.com` 已验证
2. 模板ID `154360` 已审核通过
3. SES服务有可用额度
4. API密钥有SES服务权限
### 如果发信地址未验证
**临时解决方案(仅用于测试):**
可以将 `template-id` 临时改为 `0`,进入开发模式:
```properties
tencent.ses.template-id=0
```
开发模式下不会实际发送邮件,验证码会在日志中显示。
---
## 🔗 相关资源
- **腾讯云SES控制台**: https://console.cloud.tencent.com/ses
- **发信地址管理**: https://console.cloud.tencent.com/ses/address
- **邮件模板管理**: https://console.cloud.tencent.com/ses/template
- **邮箱验证登录指南**: `EMAIL_VERIFICATION_LOGIN_GUIDE.md`
- **邮件登录配置清单**: `EMAIL_LOGIN_CONFIGURATION_CHECKLIST.md`
---
## ✅ 测试准备完成
**配置状态:**
- ✅ 所有必需配置已设置
- ⚠️ 需要确认发信地址已验证
- ⚠️ 需要确认模板ID已审核
**可以开始测试!**

View File

@@ -0,0 +1,206 @@
# 邮件发送问题诊断指南
## 🔍 问题分析
根据你提供的日志,发现了以下情况:
### 日志分析
**你的日志显示:**
```
INFO ... VerificationCodeService : 邮件验证码发送成功,邮箱: 984523799@qq.com
```
**但是缺少以下关键日志:**
- ❌ 没有看到 "当前邮件模板ID配置: xxx"
- ❌ 没有看到 "未配置邮件模板ID使用开发模式"(开发模式警告)
- ❌ 没有看到 "使用生产模式发送邮件"(生产模式日志)
- ❌ 没有看到 "开始发送邮件,收件人: ..."TencentSesMailService日志
### 可能的原因
1. **配置值未正确读取**
- `template-id=154360` 可能没有被正确读取
- 默认值 `0` 被使用,进入了开发模式
- 但开发模式的警告日志也没有出现
2. **服务使用旧版本代码**
- 可能运行的jar包是旧版本
- 需要重新编译并启动
---
## 🛠️ 诊断步骤
### 步骤1检查配置是否正确读取
我已经更新了代码,添加了更详细的日志。请:
1. **重新编译项目**
```bash
cd demo
mvn clean package
```
2. **重新启动服务**
```bash
java -jar target/demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
```
3. **再次测试发送验证码**
4. **查看新的日志输出**,应该能看到:
```
INFO ... VerificationCodeService : 当前邮件模板ID配置: 154360
INFO ... VerificationCodeService : 使用生产模式发送邮件,收件人: xxx@qq.com, 模板ID: 154360
INFO ... TencentSesMailService : 开始发送邮件,收件人: xxx@qq.com, 主题: 验证码, 模板ID: 154360
```
### 步骤2检查配置文件
确认 `application-dev.properties` 中的配置:
```properties
tencent.ses.template-id=154360
```
**注意:**
- 确保没有空格
- 确保值是正确的数字
- 确保配置文件被正确加载(使用 `--spring.profiles.active=dev`
### 步骤3检查日志输出
**如果看到:**
```
INFO 当前邮件模板ID配置: 0
WARN 未配置邮件模板ID使用开发模式
```
**说明:** 配置值没有正确读取使用的是默认值0。
**解决方案:**
1. 检查配置文件路径
2. 确认使用的是dev配置`--spring.profiles.active=dev`
3. 检查配置文件名是否正确
**如果看到:**
```
INFO 当前邮件模板ID配置: 154360
INFO 使用生产模式发送邮件,收件人: xxx@qq.com, 模板ID: 154360
INFO 开始发送邮件,收件人: xxx@qq.com, 主题: 验证码, 模板ID: 154360
ERROR 邮件验证码发送失败,邮箱: xxx@qq.com
```
**说明:** 配置正确,但邮件发送失败。
**常见错误:**
1. **发信地址未验证**
```
InvalidParameter.EmailAddressNotVerified
```
**解决:** 在腾讯云SES控制台验证 `newletter@vionow.com`
2. **模板ID不存在或未审核**
```
ResourceNotFound.TemplateNotFound
```
**解决:** 确认模板ID 154360 已审核通过
3. **权限不足**
```
InvalidSecretId.InvalidSignature
```
**解决:** 检查SecretID/SecretKey是否正确是否有SES服务权限
4. **额度不足**
```
余额不足
```
**解决:** 充值或检查免费额度
---
## 🚀 快速测试方案
### 方案1临时使用开发模式测试
如果发信地址未验证或需要快速测试登录功能,可以临时切换到开发模式:
1. **修改配置文件**
```properties
tencent.ses.template-id=0
```
2. **重新编译并启动**
```bash
mvn clean package
java -jar target/demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
```
3. **测试发送验证码**
- 会看到日志:`开发模式:邮件验证码发送到: xxx@qq.com, 验证码: 123456`
- 从日志中获取验证码用于登录测试
### 方案2检查SES配置状态
1. **访问SES控制台**
- 发信地址管理https://console.cloud.tencent.com/ses/address
- 邮件模板管理https://console.cloud.tencent.com/ses/template
2. **确认以下状态:**
- [ ] `newletter@vionow.com` 状态为"已验证"
- [ ] 模板ID `154360` 状态为"已审核"
- [ ] SES服务有可用额度
---
## 📝 诊断检查清单
请按照以下顺序检查:
- [ ] **1. 配置检查**
- 配置文件 `application-dev.properties` 中存在 `tencent.ses.template-id=154360`
- 没有多余空格或特殊字符
- [ ] **2. 编译检查**
- 执行了 `mvn clean package`
- 编译成功,没有错误
- [ ] **3. 启动检查**
- 使用 `--spring.profiles.active=dev` 启动
- 启动日志中没有配置相关的错误
- [ ] **4. 日志检查**
- 重新启动后,测试发送验证码
- 查看日志中是否有新的详细信息
- [ ] **5. SES配置检查**
- 发信地址已验证
- 模板ID已审核
- 有可用额度
---
## 🔧 下一步操作
1. **立即执行:**
```bash
cd demo
mvn clean package
java -jar target/demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
```
2. **再次测试发送验证码**
3. **查看新的日志输出**,告诉我你看到了什么日志信息
4. **根据新的日志信息,我可以进一步帮你诊断问题**
---
## ⚠️ 重要提示
**当前代码已更新,添加了更详细的日志输出。请重新编译并启动服务,然后再次测试。新的日志会帮助我们准确定位问题。**

View File

@@ -0,0 +1,112 @@
# 腾讯云SES邮件发送问题排查指南
## 当前错误
```
错误码: FailedOperation.SendEmailErr
错误信息: 操作失败。您的发送遇到问题请检查Region参数是否指定准确若Region参数无误请联系腾讯云提交工单沟通原因并解决。
```
## 问题诊断
这个错误通常表示以下问题之一:
### 1. **发信地址未验证** ⚠️ 最常见
- 发信地址 `newletter@vionow.com` 可能未在腾讯云SES控制台的 **广州区域** 验证
- 需要在对应区域验证发信地址或域名
### 2. **模板ID不存在或区域不匹配**
- 模板ID `154360` 可能不存在于广州区域
- 模板可能在其他区域创建,需要确认模板所在区域
### 3. **区域配置不匹配**
- 当前配置区域:`ap-guangzhou`(广州)
- 需要确保腾讯云控制台选择的地域与代码配置一致
## 解决步骤
### 步骤1: 登录腾讯云SES控制台
访问https://console.cloud.tencent.com/ses
### 步骤2: 确认选择的地域
1. 在控制台右上角,确认当前选择的地域是否为 **"广州"**ap-guangzhou
2. 如果选择了其他地域,切换到广州区域
### 步骤3: 验证发信地址
1. 进入 **"发信地址"** 或 **"身份验证"** 页面
2. 检查 `newletter@vionow.com` 是否已添加并验证通过
3. 如果未添加:
- 点击 **"添加发信地址"**
- 输入 `newletter@vionow.com`
- 完成验证(通常会收到验证邮件)
- 等待审核通过(通常几分钟到几小时)
### 步骤4: 检查模板ID
1. 进入 **"邮件模板"** 页面
2. 确认模板ID `154360` 是否存在
3. 确认模板状态为 **"已审核"** 或 **"已通过"**
4. 如果模板不存在:
- 检查模板是否在其他区域创建
- 或者在当前区域重新创建模板
- 获取新的模板ID并更新配置
### 步骤5: 检查账户状态
1. 确认SES服务已开通
2. 检查账户是否有发送配额
3. 检查是否有账户限制或黑名单
## 当前配置信息
根据您的配置文件:
- **SecretID**: `AKIDoaEjFbqxxqZAcv8EE6oZCg2IQPG1fCxm`
- **区域**: `ap-guangzhou`(广州)✅
- **发信地址**: `newletter@vionow.com` ⚠️ 需要验证
- **模板ID**: `154360` ⚠️ 需要确认存在
## 快速检查清单
- [ ] 在腾讯云SES控制台选择的地域是否为"广州"
- [ ] 发信地址 `newletter@vionow.com` 是否已在广州区域验证通过?
- [ ] 模板ID `154360` 是否存在于广州区域?
- [ ] 模板状态是否为"已审核"
- [ ] 账户是否有发送配额?
- [ ] 访问密钥是否具有SES发送邮件权限
## 常见问题
### Q: 发信地址验证需要多长时间?
A: 通常几分钟到几小时,验证邮件会发送到邮箱地址的邮箱管理员处。
### Q: 如何验证域名而不是单个邮箱?
A: 可以添加域名 `vionow.com` 进行验证,验证后该域名下所有邮箱都可以发信。
### Q: 模板在不同区域可以通用吗?
A: 不可以,模板是区域隔离的,需要在使用的区域创建模板。
### Q: 如何查看模板的详细信息?
A: 在SES控制台的"邮件模板"页面点击模板ID查看详情确认模板内容和状态。
## 如果仍然失败
如果完成以上步骤后仍然失败,请:
1. **检查腾讯云工单**:提交工单获取技术支持
2. **查看详细日志**:检查应用日志中的完整错误堆栈
3. **联系腾讯云客服**:通过控制台联系客服获取帮助
## 临时解决方案
如果需要快速测试,可以临时使用开发模式:
```properties
# 在 application-dev.properties 中
tencent.ses.template-id=0
```
开发模式下不会发送真实邮件,验证码会记录在日志中。

View File

@@ -0,0 +1,104 @@
# 腾讯云SES权限配置指南
## 问题描述
当前错误:
```
AuthFailure.UnauthorizedOperation
you are not authorized to perform operation (ses:SendEmail)
resource (*) has no permission
```
这个错误表示您的腾讯云访问密钥没有发送邮件的权限。
## 解决方案
### 方案1在腾讯云控制台配置权限推荐
1. **登录腾讯云控制台**
- 访问https://console.cloud.tencent.com/
2. **进入访问管理CAM**
- 在控制台顶部搜索"访问管理"或"CAM"
- 或者直接访问https://console.cloud.tencent.com/cam
3. **配置子账号权限**
- 如果使用的是子账号密钥需要为该子账号添加SES权限
- 进入"用户" → 找到对应用户 → 点击"授权"
- 搜索"SES"或"邮件推送"
- 添加策略:`QcloudSESFullAccess`SES全读写访问权限
- 或者使用自定义策略,只授予`ses:SendEmail`权限
4. **如果使用主账号密钥**
- 主账号默认拥有所有权限
- 如果仍然报错,可能是密钥错误或已禁用
- 检查:访问管理 → API密钥管理 → 查看密钥状态
5. **验证配置**
- 权限配置后可能需要几分钟生效
- 重新启动应用并测试
### 方案2使用开发模式临时方案
如果您在开发环境测试,可以暂时使用开发模式:
1. **修改配置文件** `application-dev.properties`
```properties
# 设置为0以使用开发模式不发送真实邮件仅在日志中记录验证码
tencent.ses.template-id=0
```
2. **开发模式行为**
- 不会调用腾讯云SES API
- 验证码会记录在日志中
- 可以直接使用日志中的验证码进行测试
### 方案3创建自定义CAM策略精细化权限
如果您只想授予发送邮件权限,可以创建自定义策略:
1. **进入CAM策略管理**
- 访问https://console.cloud.tencent.com/cam/policy
2. **创建自定义策略**
```json
{
"version": "2.0",
"statement": [
{
"effect": "allow",
"action": [
"ses:SendEmail"
],
"resource": "*"
}
]
}
```
3. **将策略绑定到子账号**
- 进入"用户" → 找到对应用户 → 点击"授权"
- 选择刚创建的自定义策略
## 当前配置信息
根据您的配置文件:
- **SecretID**: `AKIDXw8HBtNfjdJm480xljV4QZUDi05wa0DE`
- **区域**: `ap-beijing`
- **发信地址**: `newletter@vionow.com`
- **模板ID**: `154360`
## 检查清单
- [ ] 验证发信地址 `newletter@vionow.com` 已在腾讯云SES控制台验证通过
- [ ] 验证模板ID `154360` 存在且已审核通过
- [ ] 验证访问密钥所属账号具有SES发送邮件权限
- [ ] 验证密钥未过期或被禁用
- [ ] 等待权限配置生效通常1-2分钟
## 相关文档
- [腾讯云SES权限说明](https://cloud.tencent.com/document/product/1288/45321)
- [CAM策略语法](https://cloud.tencent.com/document/product/598/10603)
- [发信地址验证指南](https://cloud.tencent.com/document/product/1288/68353)

View File

@@ -370,6 +370,8 @@
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
</div>
</template>
<script setup> <script setup>
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
@@ -517,6 +519,7 @@ const refreshStats = async () => {
} finally { } finally {
loadingStats.value = false loadingStats.value = false
} }
} }
const performFullCleanup = async () => { const performFullCleanup = async () => {

View File

@@ -1,8 +1,16 @@
package com.example.demo.model; package com.example.demo.model;
import jakarta.persistence.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
/** /**
* 图生视频任务模型 * 图生视频任务模型
*/ */
@@ -91,7 +99,9 @@ public class ImageToVideoTask {
* 计算任务消耗积分 * 计算任务消耗积分
*/ */
private Integer calculateCost() { private Integer calculateCost() {
int actualDuration = (duration == null || duration <= 0) ? 5 : duration; // 使用默认时长但不修改字段 // 安全处理duration可能为null的情况
Integer safeDuration = duration;
int actualDuration = (safeDuration == null || safeDuration <= 0) ? 5 : safeDuration; // 使用默认时长但不修改字段
int baseCost = 10; // 基础消耗 int baseCost = 10; // 基础消耗
int durationCost = actualDuration * 2; // 时长消耗 int durationCost = actualDuration * 2; // 时长消耗

View File

@@ -91,14 +91,24 @@ public class AlipayService {
* 调用真实的支付宝API * 调用真实的支付宝API
*/ */
private Map<String, Object> callRealAlipayAPI(Payment payment) throws Exception { private Map<String, Object> callRealAlipayAPI(Payment payment) throws Exception {
// 创建支付宝客户端,增加超时时间 // 记录配置信息
logger.info("=== 支付宝API配置信息 ===");
logger.info("网关地址: {}", gatewayUrl);
logger.info("应用ID: {}", appId);
logger.info("字符集: {}", charset);
logger.info("签名类型: {}", signType);
logger.info("通知URL: {}", notifyUrl);
logger.info("返回URL: {}", returnUrl);
// 设置连接和读取超时时间60秒需要在创建客户端之前设置
System.setProperty("sun.net.client.defaultConnectTimeout", "60000");
System.setProperty("sun.net.client.defaultReadTimeout", "60000");
logger.info("超时配置: 连接超时=60秒, 读取超时=60秒");
// 创建支付宝客户端
AlipayClient alipayClient = new DefaultAlipayClient( AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl, appId, privateKey, "json", charset, publicKey, signType); gatewayUrl, appId, privateKey, "json", charset, publicKey, signType);
// 设置连接和读取超时时间30秒
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");
System.setProperty("sun.net.client.defaultReadTimeout", "30000");
// 使用预创建API生成二维码 // 使用预创建API生成二维码
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
request.setNotifyUrl(notifyUrl); request.setNotifyUrl(notifyUrl);
@@ -127,10 +137,42 @@ public class AlipayService {
break; // 成功则跳出循环 break; // 成功则跳出循环
} catch (Exception e) { } catch (Exception e) {
retryCount++; retryCount++;
logger.warn("支付宝API调用失败第{}次重试,错误:{}", retryCount, e.getMessage()); String errorType = e.getClass().getSimpleName();
String errorMessage = e.getMessage();
logger.warn("支付宝API调用失败第{}次重试", retryCount);
logger.warn("错误类型: {}", errorType);
logger.warn("错误信息: {}", errorMessage);
// 根据错误类型提供诊断建议
if (errorMessage != null) {
if (errorMessage.contains("Read timed out") || errorMessage.contains("timeout")) {
logger.error("=== 网络超时错误诊断 ===");
logger.error("可能的原因:");
logger.error("1. 网络连接不稳定或延迟过高");
logger.error("2. 支付宝沙箱环境响应慢openapi.alipaydev.com");
logger.error("3. 防火墙或代理服务器阻止连接");
logger.error("4. ngrok隧道可能已过期或不可用");
logger.error("解决方案:");
logger.error("1. 检查网络连接尝试ping openapi.alipaydev.com");
logger.error("2. 检查ngrok是否正常运行: {}", notifyUrl);
logger.error("3. 考虑使用代理服务器或VPN");
logger.error("4. 增加超时时间或重试次数");
} else if (errorMessage.contains("Connection refused") || errorMessage.contains("ConnectException")) {
logger.error("=== 连接拒绝错误诊断 ===");
logger.error("无法连接到支付宝服务器: {}", gatewayUrl);
logger.error("请检查网络连接和防火墙设置");
} else if (errorMessage.contains("UnknownHostException")) {
logger.error("=== DNS解析错误诊断 ===");
logger.error("无法解析域名: {}", gatewayUrl);
logger.error("请检查DNS设置和网络连接");
}
}
if (retryCount >= maxRetries) { if (retryCount >= maxRetries) {
logger.error("支付宝API调用失败已达到最大重试次数"); logger.error("支付宝API调用失败已达到最大重试次数{}次)", maxRetries);
throw new RuntimeException("支付宝API调用失败" + e.getMessage()); logger.error("最终失败原因: {}", errorMessage);
throw new RuntimeException("支付宝API调用失败" + errorMessage);
} }
try { try {
Thread.sleep(2000); // 等待2秒后重试 Thread.sleep(2000); // 等待2秒后重试

View File

@@ -1,16 +1,5 @@
package com.example.demo.service; package com.example.demo.service;
import com.example.demo.model.*;
import com.example.demo.repository.OrderItemRepository;
import com.example.demo.repository.OrderRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
@@ -19,6 +8,22 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.model.Order;
import com.example.demo.model.OrderItem;
import com.example.demo.model.OrderStatus;
import com.example.demo.model.OrderType;
import com.example.demo.model.User;
import com.example.demo.repository.OrderItemRepository;
import com.example.demo.repository.OrderRepository;
@Service @Service
@Transactional @Transactional
public class OrderService { public class OrderService {
@@ -164,6 +169,15 @@ public class OrderService {
// 设置相应的时间戳 // 设置相应的时间戳
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
switch (newStatus) { switch (newStatus) {
case PENDING:
// 待处理状态,不需要设置时间戳
break;
case CONFIRMED:
// 已确认状态,不需要设置时间戳
break;
case PROCESSING:
// 处理中状态,不需要设置时间戳
break;
case PAID: case PAID:
order.setPaidAt(now); order.setPaidAt(now);
break; break;
@@ -179,6 +193,12 @@ public class OrderService {
case CANCELLED: case CANCELLED:
order.setCancelledAt(now); order.setCancelledAt(now);
break; break;
case REFUNDED:
// 已退款状态,不需要设置时间戳
break;
default:
// 其他状态,不需要设置时间戳
break;
} }
Order updatedOrder = orderRepository.save(order); Order updatedOrder = orderRepository.save(order);

View File

@@ -38,7 +38,7 @@ public class TencentSesMailService {
@Value("${tencent.ses.secret-key}") @Value("${tencent.ses.secret-key}")
private String secretKey; private String secretKey;
@Value("${tencent.ses.region:ap-beijing}") @Value("${tencent.ses.region:ap-hongkong}")
private String region; private String region;
@Value("${tencent.ses.from-email}") @Value("${tencent.ses.from-email}")
@@ -109,8 +109,64 @@ public class TencentSesMailService {
return true; return true;
} catch (TencentCloudSDKException e) { } catch (TencentCloudSDKException e) {
logger.error("腾讯云SES API调用失败收件人: {}, 错误码: {}, 错误信息: {}", String errorCode = e.getErrorCode();
toEmail, e.getErrorCode(), e.getMessage(), e); String errorMessage = e.getMessage();
// 检查是否为权限错误
if ("AuthFailure.UnauthorizedOperation".equals(errorCode) ||
(errorMessage != null && errorMessage.contains("no permission"))) {
logger.error("=== 腾讯云SES权限错误 ===");
logger.error("收件人: {}", toEmail);
logger.error("错误码: {}", errorCode);
logger.error("错误信息: {}", errorMessage);
logger.error("解决方案:");
logger.error("1. 在腾讯云CAM中为访问密钥添加SES发送邮件权限");
logger.error("2. 或临时使用开发模式:设置 tencent.ses.template-id=0");
logger.error("详细配置指南请参考: TENCENT_SES_PERMISSION_FIX.md");
}
// 检查是否为区域不支持错误
else if ("UnsupportedRegion".equals(errorCode) ||
(errorMessage != null && errorMessage.contains("does not support this region"))) {
logger.error("=== 腾讯云SES区域错误 ===");
logger.error("收件人: {}", toEmail);
logger.error("错误码: {}", errorCode);
logger.error("错误信息: {}", errorMessage);
logger.error("当前配置的区域: {}", region);
logger.error("解决方案:");
logger.error("1. 腾讯云SES通常支持以下区域");
logger.error(" - ap-hongkong (中国香港)");
logger.error(" - ap-guangzhou (广州)");
logger.error(" - ap-shanghai (上海)");
logger.error(" - ap-nanjing (南京)");
logger.error("2. 修改配置文件中的 tencent.ses.region 为支持的区域");
logger.error("3. 当前配置tencent.ses.region={}", region);
}
// 检查是否为发送失败错误
else if ("FailedOperation.SendEmailErr".equals(errorCode) ||
(errorMessage != null && errorMessage.contains("发送遇到问题"))) {
logger.error("=== 腾讯云SES发送失败错误 ===");
logger.error("收件人: {}", toEmail);
logger.error("错误码: {}", errorCode);
logger.error("错误信息: {}", errorMessage);
logger.error("当前配置:");
logger.error(" 区域: {}", region);
logger.error(" 发信地址: {}", fromEmail);
logger.error(" 模板ID: {}", templateId);
logger.error("可能的原因:");
logger.error("1. 发信地址 {} 未在腾讯云SES控制台的 {} 区域验证", fromEmail, region);
logger.error("2. 模板ID {} 不存在于 {} 区域,或模板状态异常", templateId, region);
logger.error("3. 发信地址已验证但未通过审核");
logger.error("4. 账户可能未开通SES服务或服务受限");
logger.error("解决方案:");
logger.error("1. 登录腾讯云SES控制台: https://console.cloud.tencent.com/ses");
logger.error("2. 确认选择的地域为: {}", region);
logger.error("3. 检查发信地址 {} 是否已在该区域验证通过", fromEmail);
logger.error("4. 检查模板ID {} 是否存在于该区域且状态为已审核", templateId);
logger.error("5. 如未验证,请先验证发信地址或域名");
} else {
logger.error("腾讯云SES API调用失败收件人: {}, 错误码: {}, 错误信息: {}",
toEmail, errorCode, errorMessage, e);
}
return false; return false;
} catch (Exception e) { } catch (Exception e) {
logger.error("发送邮件异常,收件人: {}", toEmail, e); logger.error("发送邮件异常,收件人: {}", toEmail, e);

View File

@@ -26,6 +26,19 @@ public class VerificationCodeService {
@Value("${tencent.ses.template-id:0}") @Value("${tencent.ses.template-id:0}")
private Long templateId; private Long templateId;
// 在构造函数中记录配置值,用于调试
public VerificationCodeService() {
// 构造函数用于初始化
}
// 使用@PostConstruct确保在注入后记录配置
@jakarta.annotation.PostConstruct
public void init() {
logger.info("=== 邮件服务配置初始化 ===");
logger.info("模板ID配置值: {}", templateId);
logger.info("如果为0或null将使用开发模式");
}
// 使用内存存储验证码 // 使用内存存储验证码
private final ConcurrentHashMap<String, String> verificationCodes = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, String> verificationCodes = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Long> rateLimits = new ConcurrentHashMap<>(); private final ConcurrentHashMap<String, Long> rateLimits = new ConcurrentHashMap<>();
@@ -149,6 +162,9 @@ public class VerificationCodeService {
*/ */
private boolean sendEmail(String email, String code) { private boolean sendEmail(String email, String code) {
try { try {
// 记录当前模板ID配置
logger.info("当前邮件模板ID配置: {}", templateId);
// 如果没有配置模板ID使用开发模式仅记录日志 // 如果没有配置模板ID使用开发模式仅记录日志
if (templateId == null || templateId == 0) { if (templateId == null || templateId == 0) {
logger.warn("未配置邮件模板ID使用开发模式。验证码: {}, 邮箱: {}", code, email); logger.warn("未配置邮件模板ID使用开发模式。验证码: {}, 邮箱: {}", code, email);
@@ -156,6 +172,8 @@ public class VerificationCodeService {
return true; // 开发模式下返回成功 return true; // 开发模式下返回成功
} }
logger.info("使用生产模式发送邮件,收件人: {}, 模板ID: {}", email, templateId);
// 使用腾讯云SES发送邮件 // 使用腾讯云SES发送邮件
boolean success = tencentSesMailService.sendVerificationCodeEmail(email, code, templateId); boolean success = tencentSesMailService.sendVerificationCodeEmail(email, code, templateId);
@@ -163,12 +181,17 @@ public class VerificationCodeService {
logger.info("邮件验证码发送成功,邮箱: {}", email); logger.info("邮件验证码发送成功,邮箱: {}", email);
} else { } else {
logger.error("邮件验证码发送失败,邮箱: {}", email); logger.error("邮件验证码发送失败,邮箱: {}", email);
logger.error("可能的原因:");
logger.error("1. SES权限未配置检查CAM权限策略");
logger.error("2. 发信地址未验证检查SES控制台");
logger.error("3. 模板ID错误当前模板ID: {}", templateId);
logger.error("详细排查指南请参考: TENCENT_SES_PERMISSION_FIX.md");
} }
return success; return success;
} catch (Exception e) { } catch (Exception e) {
logger.error("邮件发送异常,邮箱: {}", email, e); logger.error("邮件发送异常,邮箱: {}, 错误: {}", email, e.getMessage(), e);
return false; return false;
} }
} }

View File

@@ -24,14 +24,14 @@ jwt.expiration=86400000
# 腾讯云SES配置 # 腾讯云SES配置
# 主账号ID: 100040185043 # 主账号ID: 100040185043
# 用户名: test # 用户名: test
tencent.ses.secret-id=AKIDXw8HBtNfjdJm480xljV4QZUDi05wa0DE tencent.ses.secret-id=AKIDoaEjFbqxxqZAcv8EE6oZCg2IQPG1fCxm
tencent.ses.secret-key=tZyHMDsKadS4ScZhhU3PYUErGUVIqBIB tencent.ses.secret-key=nR83I79FOSpGcqNo7JXkqnU8g7SjsxuG
tencent.ses.region=ap-beijing tencent.ses.region=ap-hongkong
tencent.ses.from-email=noreply@vionow.com tencent.ses.from-email=newletter@vionow.com
tencent.ses.from-name=AIGC平台 tencent.ses.from-name=AIGC平台
# 邮件模板ID在腾讯云SES控制台创建模板后获取 # 邮件模板ID在腾讯云SES控制台创建模板后获取
# 如果未配置或为0将使用开发模式仅记录日志 # 如果未配置或为0将使用开发模式仅记录日志
tencent.ses.template-id=0 tencent.ses.template-id=154360
# AI API配置 # AI API配置
# 文生视频、图生视频、分镜视频都使用Comfly API # 文生视频、图生视频、分镜视频都使用Comfly API

View File

@@ -49,6 +49,16 @@ paypal.cancel-url=${PAYPAL_CANCEL_URL}
jwt.secret=${JWT_SECRET} jwt.secret=${JWT_SECRET}
jwt.expiration=${JWT_EXPIRATION:604800000} jwt.expiration=${JWT_EXPIRATION:604800000}
# 腾讯云SES配置 (生产环境)
tencent.ses.secret-id=${TENCENT_SES_SECRET_ID}
tencent.ses.secret-key=${TENCENT_SES_SECRET_KEY}
tencent.ses.region=ap-hongkong
tencent.ses.from-email=${TENCENT_SES_FROM_EMAIL}
tencent.ses.from-name=AIGC平台
# 邮件模板ID在腾讯云SES控制台创建模板后获取
# 如果未配置或为0将使用开发模式仅记录日志
tencent.ses.template-id=${TENCENT_SES_TEMPLATE_ID}
# 生产环境日志配置 # 生产环境日志配置
logging.level.root=INFO logging.level.root=INFO
logging.level.com.example.demo=INFO logging.level.com.example.demo=INFO