14 KiB
14 KiB
邮箱变更
**本文引用的文件** - [user_handler.go](file://internal/handler/user_handler.go) - [routes.go](file://internal/handler/routes.go) - [auth.go](file://internal/middleware/auth.go) - [verification_service.go](file://internal/service/verification_service.go) - [user_service.go](file://internal/service/user_service.go) - [user_repository.go](file://internal/repository/user_repository.go) - [common.go](file://internal/types/common.go) - [response.go](file://internal/model/response.go) - [redis.go](file://pkg/redis/redis.go) - [email.go](file://pkg/email/email.go)目录
简介
本文档围绕“POST /api/v1/user/change-email”端点,系统化阐述邮箱变更的完整流程与技术实现细节。该接口要求用户提供新邮箱地址与验证码,后端通过验证服务核验Redis中存储的验证码,随后由用户服务层执行数据库更新,确保邮箱唯一性与安全性。文档还解释了请求体结构、验证码生成与存储机制、错误处理策略,并给出成功与失败场景的响应示例,强调该操作需要有效的JWT认证。
项目结构
- 路由与鉴权
- 路由在 v1 组下,用户相关接口均受JWT中间件保护。
- 邮箱变更接口位于 /api/v1/user/change-email,采用POST方法。
- 控制器层
- 用户控制器负责接收请求、参数校验、调用服务层、组装响应。
- 服务层
- 验证服务:生成、发送、校验验证码;验证码以特定键规则存入Redis。
- 用户服务:执行邮箱唯一性检查与数据库更新。
- 数据访问层
- 用户仓库封装GORM操作,提供按邮箱查询与字段更新能力。
- 基础设施
- Redis客户端:提供键值存取、过期控制、删除等操作。
- 邮件服务:根据类型发送不同主题的验证码邮件。
graph TB
Client["客户端"] --> Routes["路由: /api/v1/user/change-email"]
Routes --> AuthMW["JWT中间件"]
AuthMW --> Handler["用户处理器: ChangeEmail"]
Handler --> VerifySvc["验证服务: VerifyCode"]
Handler --> UserSvc["用户服务: ChangeUserEmail"]
VerifySvc --> Redis["Redis: 验证码存储"]
UserSvc --> Repo["用户仓库: UpdateUserFields"]
Repo --> DB["数据库: user 表"]
VerifySvc --> Email["邮件服务: SendChangeEmail"]
图表来源
- routes.go
- auth.go
- user_handler.go
- verification_service.go
- user_service.go
- user_repository.go
- redis.go
- email.go
章节来源
核心组件
- 请求体结构
- ChangeEmailRequest.new_email:新邮箱地址,必填且符合邮箱格式。
- ChangeEmailRequest.verification_code:验证码,必填且长度为6。
- 鉴权要求
- 所有用户相关接口均需携带有效的Bearer JWT。
- 验证码机制
- 验证码类型常量包含“change_email”,用于区分不同用途。
- 验证码长度为6,有效期10分钟,发送频率限制1分钟。
- 验证通过后,Redis中的验证码键会被删除。
- 邮箱唯一性校验
- 在更新邮箱前,查询数据库确认新邮箱未被其他用户占用;若被占用则拒绝。
- 错误处理
- 参数错误、未授权、验证码错误、邮箱已被使用、服务器内部错误等均有明确响应码与消息。
章节来源
架构总览
POST /api/v1/user/change-email 的端到端流程如下:
- 客户端携带JWT向 /api/v1/user/change-email 发起POST请求,请求体包含 new_email 与 verification_code。
- 路由匹配到用户组,JWT中间件校验通过后进入用户处理器。
- 处理器解析请求体并调用验证服务,验证Redis中对应类型的验证码是否匹配。
- 验证通过后,处理器调用用户服务更新数据库中的邮箱字段。
- 更新完成后,重新查询用户信息并返回成功响应。
sequenceDiagram
participant C as "客户端"
participant R as "路由"
participant M as "JWT中间件"
participant H as "用户处理器"
participant VS as "验证服务"
participant RS as "Redis"
participant US as "用户服务"
participant UR as "用户仓库"
participant DB as "数据库"
C->>R : "POST /api/v1/user/change-email"
R->>M : "鉴权"
M-->>R : "通过"
R->>H : "进入ChangeEmail"
H->>VS : "VerifyCode(email, code, type=change_email)"
VS->>RS : "Get(verification : code : change_email : new_email)"
RS-->>VS : "验证码"
VS-->>H : "验证通过"
H->>US : "ChangeUserEmail(userID, new_email)"
US->>UR : "FindUserByEmail(new_email)"
UR-->>US : "查询结果"
US->>UR : "UpdateUserFields(userID, {email : new_email})"
UR->>DB : "更新"
DB-->>UR : "成功"
UR-->>US : "成功"
US-->>H : "成功"
H-->>C : "返回用户信息"
图表来源
- routes.go
- auth.go
- user_handler.go
- verification_service.go
- redis.go
- user_service.go
- user_repository.go
详细组件分析
控制器:ChangeEmail 端点
- 功能职责
- 从上下文提取用户ID(JWT中间件注入)。
- 解析请求体为 ChangeEmailRequest。
- 调用验证服务核验验证码(类型为 change_email)。
- 调用用户服务更新邮箱。
- 重新查询用户信息并返回成功响应。
- 错误处理
- 缺少Authorization头或无效token:返回401。
- 请求体绑定失败:返回400。
- 验证码错误或过期:返回400。
- 邮箱已被占用:返回400。
- 用户不存在:返回404。
- 其他异常:返回500。
flowchart TD
Start(["进入ChangeEmail"]) --> GetCtx["获取user_id"]
GetCtx --> CheckAuth{"user_id存在?"}
CheckAuth -- 否 --> Resp401["返回401未授权"]
CheckAuth -- 是 --> BindReq["绑定ChangeEmailRequest"]
BindReq --> BindOK{"绑定成功?"}
BindOK -- 否 --> Resp400["返回400参数错误"]
BindOK -- 是 --> Verify["调用VerifyCode(type=change_email)"]
Verify --> VerifyOK{"验证通过?"}
VerifyOK -- 否 --> Resp400V["返回400验证码错误/过期"]
VerifyOK -- 是 --> Update["调用ChangeUserEmail"]
Update --> UpdateOK{"更新成功?"}
UpdateOK -- 否 --> Resp400E["返回400邮箱已被使用/其他错误"]
UpdateOK -- 是 --> Reload["重新查询用户信息"]
Reload --> Resp200["返回200成功"]
图表来源
章节来源
验证服务:验证码生成、发送与校验
- 生成与存储
- 生成6位数字验证码。
- 以键模式 verification:code:{type}:{email} 写入Redis,有效期10分钟。
- 发送频率限制键 verification:rate_limit:{type}:{email} 设置1分钟。
- 校验逻辑
- 读取Redis中的验证码并与请求一致进行比对。
- 校验通过后删除该验证码键。
- 邮件发送
- 根据类型选择不同主题与正文,发送更换邮箱验证码邮件。
flowchart TD
Gen["生成6位验证码"] --> Store["写入Redis: code键(10分钟)"]
Store --> Rate["设置rate_limit键(1分钟)"]
Rate --> Mail["发送邮件(ChangeEmail)"]
Mail --> Verify["VerifyCode: 读取code键"]
Verify --> Match{"是否匹配?"}
Match -- 否 --> Err["返回错误(验证码错误/过期)"]
Match -- 是 --> Del["删除code键"]
Del --> Ok["返回成功"]
图表来源
章节来源
用户服务:邮箱唯一性与更新
- 唯一性检查
- 查询新邮箱是否已被其他用户占用;若存在且ID不同则报错。
- 更新逻辑
- 使用UpdateUserFields更新邮箱字段。
flowchart TD
Check["FindUserByEmail(new_email)"] --> Found{"是否找到用户?"}
Found -- 是且ID!=userID --> Err["返回错误: 邮箱已被使用"]
Found -- 否或ID==userID --> Update["UpdateUserFields(userID, {email: new_email})"]
Update --> Done["返回成功"]
图表来源
章节来源
数据模型与响应
- 请求体
- ChangeEmailRequest.new_email:新邮箱地址,必填且符合邮箱格式。
- ChangeEmailRequest.verification_code:验证码,必填且长度为6。
- 成功响应
- 返回通用响应结构,包含用户信息(含更新后的邮箱)。
- 错误响应
- 400:请求参数错误、验证码错误/过期、邮箱已被使用。
- 401:未授权。
- 404:用户不存在。
- 500:服务器内部错误。
章节来源
依赖关系分析
- 控制器依赖
- 路由与JWT中间件:保证接口受保护。
- 验证服务:负责验证码核验。
- 用户服务:负责邮箱更新与唯一性检查。
- 服务层依赖
- Redis客户端:验证码存取与删除。
- 邮件服务:发送验证码邮件。
- 用户仓库:数据库读写。
- 数据库约束
- 用户表的邮箱字段具备唯一索引,配合服务层检查可避免并发冲突。
graph LR
Handler["用户处理器"] --> VerifySvc["验证服务"]
Handler --> UserSvc["用户服务"]
VerifySvc --> Redis["Redis客户端"]
VerifySvc --> Email["邮件服务"]
UserSvc --> Repo["用户仓库"]
Repo --> DB["数据库"]
图表来源
章节来源
性能与可靠性
- 验证码缓存
- Redis键过期时间10分钟,避免长期占用内存;rate_limit键1分钟,限制发送频率,降低滥用风险。
- 并发一致性
- 服务层在更新前进行邮箱唯一性检查,结合数据库唯一索引,有效防止并发写入导致的重复。
- 日志与可观测性
- 处理器与服务层在关键路径记录日志,便于定位问题。
- 错误传播
- 明确的错误码与消息,便于前端统一处理。
[本节为通用指导,不直接分析具体文件]
故障排查指南
- 401 未授权
- 检查请求头 Authorization 是否为 Bearer 令牌,且令牌有效。
- 400 请求参数错误
- 确认请求体包含 new_email 与 verification_code,且格式正确。
- 400 验证码错误/过期
- 检查Redis中 verification:code:change_email:{email} 是否存在且未过期;确认邮件是否送达。
- 400 邮箱已被使用
- 确认新邮箱未被其他用户占用;若被占用请更换邮箱。
- 404 用户不存在
- 检查用户是否被软删除或账户状态异常。
- 500 服务器内部错误
- 查看服务日志,关注数据库更新与Redis存取异常。
章节来源
结论
POST /api/v1/user/change-email 通过严格的JWT鉴权、Redis验证码校验与数据库唯一性检查,实现了安全可靠的邮箱变更流程。请求体简洁明确,错误处理清晰,适合在生产环境中稳定运行。建议在部署时确保Redis与邮件服务可用,并合理配置验证码有效期与发送频率限制,以兼顾用户体验与安全。