# 发送验证码 **本文引用的文件** - [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)