hi-server/.trae/documents/绑定邮箱验证码流程梳理与修复方案.md
shanshanzhong 58107ed76f
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 7m56s
feat(handler): 添加绑定邀请码接口路由和处理逻辑
新增/public/user/bindInviteCodeHandler.go处理绑定邀请码请求
在routes.go中添加对应的POST路由/bind_invite_code
2025-11-20 05:56:18 -08:00

93 lines
3.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 修复目标
* 解决首次设备登录时在 `internal/logic/auth/deviceLoginLogic.go:99``deviceInfo` 赋值导致的空指针崩溃,确保接口稳定返回。
## 根因定位
* 设备不存在分支仅创建用户与设备记录,但未为局部变量 `deviceInfo` 赋值;随后在 `internal/logic/auth/deviceLoginLogic.go:99-100` 使用 `deviceInfo` 导致 `nil` 解引用。
* 参考位置:
* 赋值处:`internal/logic/auth/deviceLoginLogic.go:99-101`
* 设备存在分支赋值:`internal/logic/auth/deviceLoginLogic.go:88-95`
* 设备不存在分支未赋值:`internal/logic/auth/deviceLoginLogic.go:74-79`
* `UpdateDevice` 需要有效设备 `Id``internal/model/user/device.go:58-69`
## 修改方案
1. 在“设备不存在”分支注册完成后,立即通过标识重新查询设备,赋值给 `deviceInfo`
*`internal/logic/auth/deviceLoginLogic.go``if errors.Is(err, gorm.ErrRecordNotFound)` 分支中,`userInfo, err = l.registerUserAndDevice(req)` 之后追加:
* `deviceInfo, err = l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier)`
* 如果查询失败则返回数据库查询错误(与现有风格一致)。
2. 在更新设备 UA 前增加空指针保护,并不再忽略更新错误:
*`internal/logic/auth/deviceLoginLogic.go:99-101` 改为:
* 检查 `deviceInfo != nil`
* `deviceInfo.UserAgent = req.UserAgent`
* `if err := l.svcCtx.UserModel.UpdateDevice(l.ctx, deviceInfo); err != nil {` 记录错误并返回包装后的错误 `xerr.DatabaseUpdateError`
3. 可选优化(减少二次查询):
*`registerUserAndDevice(req)` 的返回值改为 `(*user.User, *user.Device, error)`,在注册时直接返回新建设备对象;调用点随之调整。若选择此方案,仍需在更新前做空指针保护。
## 代码示例方案1最小改动
```go
// internal/logic/auth/deviceLoginLogic.go
// 设备不存在分支注册后追加一次设备查询
userInfo, err = l.registerUserAndDevice(req)
if err != nil {
return nil, err
}
deviceInfo, err = l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier)
if err != nil {
l.Errorw("query device after register failed",
logger.Field("identifier", req.Identifier),
logger.Field("error", err.Error()),
)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query device after register failed: %v", err.Error())
}
// 更新 UA不忽略更新错误
if deviceInfo != nil {
deviceInfo.UserAgent = req.UserAgent
if err := l.svcCtx.UserModel.UpdateDevice(l.ctx, deviceInfo); err != nil {
l.Errorw("update device failed",
logger.Field("user_id", userInfo.Id),
logger.Field("identifier", req.Identifier),
logger.Field("error", err.Error()),
)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device failed: %v", err.Error())
}
}
```
## 测试用例与验证
* 用例1首次设备标识登录设备不存在应成功返回 Token日志包含注册与登录记录无 500。
* 用例2已存在设备标识登录设备存在应正常更新 UA 并返回 Token。
* 用例3模拟数据库异常时应返回一致的业务错误码不产生 `panic`
## 风险与回滚
* 改动限定在登录逻辑,属最小范围;若出现异常,回滚为当前版本即可。
* 不改变数据结构与外部接口行为,兼容现有客户端。
## 后续优化(可选)
* 统一 `UpdateDevice` 错误处理路径,避免 `_ = ...` 静默失败。
* 为“首次设备登录”场景补充集成测试,保证不再回归。