fix(auth): 修复设备首次登录时空指针崩溃问题
Some checks failed
Build docker and publish / build (20.15.1) (push) Failing after 1m21s
Some checks failed
Build docker and publish / build (20.15.1) (push) Failing after 1m21s
在设备不存在分支注册后立即查询设备信息并赋值,避免后续操作解引用空指针 同时增加设备信息空指针保护并完善错误处理
This commit is contained in:
parent
004acd03d2
commit
70561876d6
67
.trae/documents/修复设备首次登录空指针崩溃.md
Normal file
67
.trae/documents/修复设备首次登录空指针崩溃.md
Normal file
@ -0,0 +1,67 @@
|
||||
## 修复目标
|
||||
- 解决首次设备登录时在 `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` 错误处理路径,避免 `_ = ...` 静默失败。
|
||||
- 为“首次设备登录”场景补充集成测试,保证不再回归。
|
||||
@ -68,6 +68,7 @@ func (l *DeviceLoginLogic) DeviceLogin(req *types.DeviceLoginRequest) (resp *typ
|
||||
}()
|
||||
|
||||
// Check if device exists by identifier
|
||||
createdNewDevice := false
|
||||
deviceInfo, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
@ -76,6 +77,7 @@ func (l *DeviceLoginLogic) DeviceLogin(req *types.DeviceLoginRequest) (resp *typ
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
createdNewDevice = true
|
||||
} else {
|
||||
l.Errorw("query device failed",
|
||||
logger.Field("identifier", req.Identifier),
|
||||
@ -95,9 +97,28 @@ func (l *DeviceLoginLogic) DeviceLogin(req *types.DeviceLoginRequest) (resp *typ
|
||||
}
|
||||
}
|
||||
|
||||
// 根据 req 中的UA 更新UA
|
||||
deviceInfo.UserAgent = req.UserAgent
|
||||
_ = l.svcCtx.UserModel.UpdateDevice(l.ctx, deviceInfo)
|
||||
if createdNewDevice {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
// Generate session id
|
||||
sessionId := uuidx.NewUUID().String()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user