# 发送验证码
**本文引用的文件**
- [auth_handler.go](file://internal/handler/auth_handler.go)
- [routes.go](file://internal/handler/routes.go)
- [verification_service.go](file://internal/service/verification_service.go)
- [common.go](file://internal/types/common.go)
- [email.go](file://pkg/email/email.go)
- [manager.go](file://pkg/email/manager.go)
- [redis.go](file://pkg/redis/redis.go)
- [manager.go](file://pkg/redis/manager.go)
- [config.go](file://pkg/config/config.go)
- [main.go](file://cmd/server/main.go)
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件面向“发送验证码”API,围绕 /api/v1/auth/send-code 端点进行完整说明。内容包括:
- 接口概述与HTTP规范
- 请求体结构与字段约束(email、type)
- 响应格式与错误码
- 验证码生成、存储与过期策略(Redis)
- 邮件服务集成(pkg/email)
- 不同验证码类型(注册、重置密码、更换邮箱)的处理逻辑
- 实际请求与响应示例
- 安全注意事项(频率限制、邮箱格式)
## 项目结构
/api/v1/auth/send-code 属于认证模块,位于 Gin 路由分组 /api/v1/auth 下,由处理器负责接收请求、绑定参数、调用服务层发送验证码,并通过 Redis 与邮件服务完成验证码的持久化与发送。
```mermaid
graph TB
Client["客户端"] --> Router["Gin 路由
/api/v1/auth/send-code"]
Router --> Handler["处理器
SendVerificationCode"]
Handler --> Service["服务层
SendVerificationCode/VerifyCode"]
Service --> Redis["Redis 客户端
存储验证码/频率限制"]
Service --> Email["邮件服务
发送验证码邮件"]
Email --> SMTP["SMTP 服务器"]
```
图表来源
- [routes.go](file://internal/handler/routes.go#L16-L26)
- [auth_handler.go](file://internal/handler/auth_handler.go#L149-L192)
- [verification_service.go](file://internal/service/verification_service.go#L40-L118)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
- [email.go](file://pkg/email/email.go#L29-L105)
章节来源
- [routes.go](file://internal/handler/routes.go#L16-L26)
- [auth_handler.go](file://internal/handler/auth_handler.go#L149-L192)
## 核心组件
- 路由与处理器
- 路由注册:/api/v1/auth/send-code 绑定到 SendVerificationCode 处理器。
- 处理器职责:参数绑定、调用服务层发送验证码、记录日志、返回统一响应。
- 服务层
- 生成6位数字验证码;按 type 与 email 组合键存储至 Redis,设置过期时间;设置发送频率限制;调用邮件服务发送对应类型的邮件。
- Redis
- 提供 Set/Get/Del/Exists 等基础操作,封装过期时间控制与错误处理。
- 邮件服务
- 基于 SMTP 的邮件发送,支持 465(隐式 TLS)与 587(显式 TLS)端口;根据 type 选择不同主题与正文模板。
- 类型定义
- SendVerificationCodeRequest 包含 email 与 type 字段,type 限定为 register/reset_password/change_email。
章节来源
- [routes.go](file://internal/handler/routes.go#L16-L26)
- [auth_handler.go](file://internal/handler/auth_handler.go#L149-L192)
- [verification_service.go](file://internal/service/verification_service.go#L14-L24)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
- [email.go](file://pkg/email/email.go#L29-L105)
- [common.go](file://internal/types/common.go#L49-L54)
## 架构总览
发送验证码的端到端流程如下:
```mermaid
sequenceDiagram
participant C as "客户端"
participant H as "处理器
SendVerificationCode"
participant S as "服务层
SendVerificationCode/VerifyCode"
participant R as "Redis 客户端"
participant E as "邮件服务
SendVerificationCode"
participant M as "SMTP 服务器"
C->>H : POST /api/v1/auth/send-code
{email, type}
H->>H : 绑定请求体并校验
H->>S : SendVerificationCode(ctx, redis, email, type)
S->>R : Exists(rate_limit_key)
alt 已存在
S-->>H : 返回“发送过于频繁”
H-->>C : 400 错误
else 不存在
S->>S : 生成6位数字验证码
S->>R : Set(code_key, code, 10分钟)
S->>R : Set(rate_limit_key, 1, 1分钟)
S->>E : 根据type发送邮件
E->>M : 发送邮件
M-->>E : 成功/失败
alt 邮件发送失败
S->>R : Del(code_key)
S-->>H : 返回“发送邮件失败”
H-->>C : 400 错误
else 成功
S-->>H : 返回成功
H-->>C : 200 成功
end
end
```
图表来源
- [auth_handler.go](file://internal/handler/auth_handler.go#L149-L192)
- [verification_service.go](file://internal/service/verification_service.go#L40-L118)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
- [email.go](file://pkg/email/email.go#L29-L105)
## 详细组件分析
### HTTP 端点定义
- 方法:POST
- 路径:/api/v1/auth/send-code
- 功能:根据邮箱与类型发送验证码邮件,并在 Redis 中存储验证码及频率限制。
章节来源
- [routes.go](file://internal/handler/routes.go#L16-L26)
- [auth_handler.go](file://internal/handler/auth_handler.go#L149-L192)
### 请求体结构
- 字段
- email: 必填,需符合邮箱格式
- type: 必填,枚举值为 register/reset_password/change_email
- 参数绑定与校验
- 使用 Gin 的绑定与验证机制,确保 email 符合邮箱格式,type 在允许范围内。
章节来源
- [common.go](file://internal/types/common.go#L49-L54)
### 响应格式
- 成功
- 状态码:200
- 结构:统一响应体,包含 message 字段提示“验证码已发送,请查收邮件”
- 失败
- 状态码:400
- 结构:统一错误响应体,包含错误码与错误信息
章节来源
- [auth_handler.go](file://internal/handler/auth_handler.go#L149-L192)
### 错误码与错误场景
- 400 参数错误
- 请求体绑定失败或参数校验失败
- 400 发送过于频繁
- Redis 中存在频率限制键(1分钟内)
- 400 邮件发送失败
- 邮件服务未启用或 SMTP 发送异常
- 400 验证码已过期或不存在
- 验证码校验时 Redis 中无对应键
章节来源
- [auth_handler.go](file://internal/handler/auth_handler.go#L149-L192)
- [verification_service.go](file://internal/service/verification_service.go#L40-L118)
### 验证码生成与存储(Redis)
- 生成规则
- 6位数字验证码
- 存储键
- 验证码键:verification:code:{type}:{email}
- 过期时间:10分钟
- 频率限制
- 频率限制键:verification:rate_limit:{type}:{email}
- 过期时间:1分钟
- Redis 操作
- Set/SetEx:写入验证码与频率限制
- Get:读取验证码用于校验
- Del:验证成功后删除验证码键
- Exists:检查是否处于发送冷却
章节来源
- [verification_service.go](file://internal/service/verification_service.go#L14-L24)
- [verification_service.go](file://internal/service/verification_service.go#L40-L118)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
### 邮件服务集成(pkg/email)
- 初始化
- 通过全局初始化函数完成一次性初始化,随后通过 MustGetService 获取实例
- 发送逻辑
- 根据 type 选择不同主题与正文模板
- 支持 465(隐式 TLS)与 587(显式 TLS)两种端口模式
- 当邮件服务未启用时,返回“邮件服务未启用”的错误
- 主题与正文
- 注册:邮箱验证
- 重置密码:重置密码
- 更换邮箱:更换邮箱验证
- 默认:通用验证码
章节来源
- [email.go](file://pkg/email/email.go#L29-L105)
- [manager.go](file://pkg/email/manager.go#L1-L43)
- [main.go](file://cmd/server/main.go#L71-L74)
### 不同验证码类型的处理逻辑
- 注册(register)
- 邮件主题:邮箱验证
- 通常配合注册接口使用,注册时需提供验证码
- 重置密码(reset_password)
- 邮件主题:重置密码
- 与 /api/v1/auth/reset-password 配合使用
- 更换邮箱(change_email)
- 邮件主题:更换邮箱验证
- 与用户更换邮箱流程配合使用
- 默认(其他)
- 通用验证码主题
章节来源
- [verification_service.go](file://internal/service/verification_service.go#L106-L118)
- [email.go](file://pkg/email/email.go#L107-L139)
### 安全考虑
- 邮箱格式验证
- 请求体中 email 字段使用邮箱格式校验
- 发送频率限制
- Redis 中按 type+email 维度设置1分钟冷却,避免刷屏
- 验证码有效期
- Redis 中验证码设置10分钟过期,过期即失效
- 重复使用防护
- 验证码校验成功后立即删除键,防止二次使用
- 邮件服务开关
- 若未启用邮件服务,发送验证码会直接失败,避免泄露敏感信息
章节来源
- [common.go](file://internal/types/common.go#L49-L54)
- [verification_service.go](file://internal/service/verification_service.go#L40-L118)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
- [email.go](file://pkg/email/email.go#L29-L40)
## 依赖关系分析
- 处理器依赖服务层
- 服务层依赖 Redis 与邮件服务
- 邮件服务依赖配置与日志
- Redis 客户端依赖配置与日志
- 全局初始化顺序:配置 -> 日志 -> 数据库 -> JWT -> Redis -> 对象存储 -> 邮件服务
```mermaid
graph LR
H["处理器
auth_handler.go"] --> S["服务层
verification_service.go"]
S --> R["Redis 客户端
redis.go"]
S --> E["邮件服务
email.go"]
E --> Cfg["配置
config.go"]
E --> Lg["日志"]
R --> Cfg
R --> Lg
Main["服务启动
main.go"] --> E
Main --> R
```
图表来源
- [auth_handler.go](file://internal/handler/auth_handler.go#L149-L192)
- [verification_service.go](file://internal/service/verification_service.go#L40-L118)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
- [email.go](file://pkg/email/email.go#L29-L105)
- [config.go](file://pkg/config/config.go#L49-L107)
- [main.go](file://cmd/server/main.go#L27-L74)
章节来源
- [main.go](file://cmd/server/main.go#L27-L74)
## 性能考量
- Redis 操作均为 O(1),Set/Get/Del/Exists 均为常数时间复杂度
- 验证码长度固定为6位,生成与比较成本低
- 邮件发送为外部依赖,受网络与SMTP服务器性能影响
- 建议
- 合理设置 Redis 连接池大小(PoolSize)
- 控制邮件发送并发,避免瞬时高峰导致SMTP限流
- 对高频请求开启更严格的频率限制(如增加冷却时间)
[本节为通用性能建议,不直接分析具体文件]
## 故障排查指南
- 400 参数错误
- 检查请求体是否包含 email 与 type,且 type 是否在允许范围内
- 400 发送过于频繁
- 等待1分钟冷却时间,或检查 Redis 中 rate_limit 键是否仍存在
- 400 邮件发送失败
- 确认邮件服务已启用(EMAIL_ENABLED=true)
- 检查 SMTP 配置(主机、端口、用户名、密码、发件人名称)
- 查看日志中 SMTP 发送错误信息
- 400 验证码已过期或不存在
- 检查 Redis 中 code 键是否存在与过期时间
- 确认验证码是否已被验证成功后删除
章节来源
- [auth_handler.go](file://internal/handler/auth_handler.go#L149-L192)
- [verification_service.go](file://internal/service/verification_service.go#L40-L118)
- [email.go](file://pkg/email/email.go#L29-L105)
- [redis.go](file://pkg/redis/redis.go#L60-L83)
## 结论
/api/v1/auth/send-code 端点通过 Gin 处理器接收请求,调用服务层完成验证码生成、Redis 存储与频率限制、邮件发送等流程。其设计遵循最小暴露面原则:严格参数校验、冷却时间、过期时间与删除机制共同保障安全性与可用性。结合 pkg/email 与 pkg/redis 的稳定实现,整体具备良好的扩展性与可维护性。
[本节为总结性内容,不直接分析具体文件]
## 附录
### 请求与响应示例
- 请求示例
- 方法:POST
- 路径:/api/v1/auth/send-code
- 请求体:
- email: user@example.com
- type: register 或 reset_password 或 change_email
- 成功响应示例
- 状态码:200
- 响应体:包含 message 字段,提示“验证码已发送,请查收邮件”
- 失败响应示例
- 状态码:400
- 响应体:包含错误码与错误信息,如“发送过于频繁,请稍后再试”或“发送邮件失败”
章节来源
- [auth_handler.go](file://internal/handler/auth_handler.go#L149-L192)
- [common.go](file://internal/types/common.go#L49-L54)