140 lines
4.6 KiB
Markdown
140 lines
4.6 KiB
Markdown
# 找回密码邮件发送问题修复计划
|
||
|
||
## 问题描述
|
||
用户在找回密码页面输入邮箱后,页面显示发送成功,但实际未收到验证码邮件。
|
||
|
||
## 根因分析
|
||
|
||
### 当前代码流程
|
||
1. 用户输入邮箱 → 前端调用 `/api/forgot_password_send_code/`
|
||
2. 后端 `ForgotPasswordSendCodeAPIView` 生成验证码并保存到缓存
|
||
3. 调用 `submit_task(send_reset_password_email_task, to_email, code)` 发送邮件
|
||
4. 前端显示成功
|
||
|
||
### 发现的问题
|
||
|
||
#### 1. Celery Worker 可能未运行
|
||
- **问题**:`submit_task` 函数先尝试 `apply_async`(异步),如果成功则直接返回
|
||
- **风险**:如果 Redis 可达但无 worker 运行,任务会永远留在队列中不被执行
|
||
- **验证**:本地测试显示 `apply_async` 返回 PENDING,说明任务已入队但可能无 worker 消费
|
||
- **fallback 机制**:`submit_task` 只在 `apply_async` 抛异常或超时时才降级为同步 `apply`
|
||
|
||
#### 2. QQ Mail 发送限制
|
||
- **问题**:QQ Mail 对同一地址发送频率有限制
|
||
- **风险**:短时间内多次发送可能被 QQ Mail 服务器静默丢弃
|
||
- **特征**:SMTP 连接成功,但邮件未投递
|
||
|
||
#### 3. 邮件进入垃圾箱
|
||
- **问题**:验证码邮件可能被收件方归类为垃圾邮件
|
||
- **风险**:用户未检查垃圾邮件箱
|
||
|
||
## 修复方案
|
||
|
||
### 修复1:确保邮件同步发送(解决 Celery 不可用问题)
|
||
|
||
**文件**:`utils/safe_task.py`
|
||
|
||
**修改**:将 `submit_task` 改为**默认同步发送**,失败时降级为异步
|
||
|
||
**理由**:
|
||
- 验证码邮件需要立即发送,不能依赖 Celery worker 的可用性
|
||
- 同步发送确保邮件立即投递
|
||
- 如果同步失败,再尝试异步作为 fallback
|
||
|
||
```python
|
||
def submit_task(task, *args, **kwargs):
|
||
"""发送任务,优先同步执行,失败时降级为异步"""
|
||
try:
|
||
# 优先同步执行,确保立即发送
|
||
return task.apply(*args, **kwargs)
|
||
except Exception as exc:
|
||
logger.warning('sync send failed, fallback to async: %s', exc)
|
||
try:
|
||
return task.apply_async(args, kwargs, kwargs.get('_timeout', SUBMIT_TIMEOUT))
|
||
except Exception as exc2:
|
||
logger.error('async fallback also failed: %s', exc2)
|
||
return None
|
||
```
|
||
|
||
### 修复2:增加邮件发送状态反馈
|
||
|
||
**文件**:`user/views/user.py` → `ForgotPasswordSendCodeAPIView`
|
||
|
||
**修改**:
|
||
- 在 `submit_task` 调用后检查返回结果
|
||
- 如果同步发送失败,返回更明确的错误信息
|
||
- 添加日志记录发送状态
|
||
|
||
```python
|
||
result = submit_task(send_reset_password_email_task, to_email, code)
|
||
if result is None:
|
||
logger.warning(f'Reset password email failed to send: {to_email}')
|
||
# 不暴露内部错误给用户,但记录日志
|
||
```
|
||
|
||
### 修复3:前端增加提示
|
||
|
||
**文件**:`src/pages/ForgotPassword/ForgotPassword.tsx`
|
||
|
||
**修改**:
|
||
- 发送成功后提示用户检查垃圾邮件箱
|
||
- 如果发送失败,提示用户稍后重试
|
||
|
||
```tsx
|
||
if (res?.code === 10009) {
|
||
message.success(t('forgotPassword.successCodeSent'));
|
||
message.info(t('forgotPassword.checkSpam')); // 新增:提示检查垃圾邮件
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### 修复4:QQ Mail 发送频率优化(可选)
|
||
|
||
**文件**:`utils/safe_task.py`
|
||
|
||
**修改**:
|
||
- 添加发送间隔限制,避免被 QQ Mail 拦截
|
||
- 对同一邮箱,短时间内不重复发送
|
||
|
||
## 涉及文件
|
||
|
||
| 文件 | 修改类型 | 说明 |
|
||
|------|----------|------|
|
||
| `utils/safe_task.py` | 修改 | 改为同步优先发送 |
|
||
| `user/views/user.py` | 修改 | 增加发送状态日志 |
|
||
| `src/pages/ForgotPassword/ForgotPassword.tsx` | 修改 | 增加垃圾邮件提示 |
|
||
| `src/locales/zh.json` | 修改 | 添加提示文本 |
|
||
|
||
## 验证步骤
|
||
|
||
1. **本地测试**
|
||
- 启动 Django 开发服务器
|
||
- 访问找回密码页面
|
||
- 输入已注册邮箱
|
||
- 检查是否收到邮件
|
||
- 检查 Django 日志是否有发送记录
|
||
|
||
2. **Celery 未运行场景**
|
||
- 确保本地无 Celery worker 运行
|
||
- 测试邮件发送是否正常
|
||
|
||
3. **同一邮箱多次发送**
|
||
- 连续发送3次验证码
|
||
- 检查是否每次都能收到
|
||
- 检查是否有频率限制
|
||
|
||
4. **垃圾邮件检查**
|
||
- 检查 QQ Mail 垃圾邮件箱
|
||
- 确认邮件是否被归类为垃圾邮件
|
||
|
||
## 风险评估
|
||
- 同步发送会增加请求响应时间(约1-2秒),但验证码邮件场景下可接受
|
||
- 如果 SMTP 服务器响应慢,可能导致请求超时
|
||
- 需要监控邮件发送失败率
|
||
|
||
## 备选方案
|
||
如果同步发送影响性能,可以考虑:
|
||
1. 使用数据库记录发送状态
|
||
2. 前端轮询检查邮件是否发送成功
|
||
3. 提供"重新发送"按钮
|