package identity import ( "context" "database/sql" "hyapp/pkg/appcode" "hyapp/pkg/xerr" userdomain "hyapp/services/user-service/internal/domain/user" "hyapp/services/user-service/internal/storage/mysql/shared" userstorage "hyapp/services/user-service/internal/storage/mysql/user" ) func (r *Repository) GetUserIdentity(ctx context.Context, userID int64) (userdomain.Identity, error) { // 直接读取 users 当前快照;是否懒过期由 service 层统一判断。 user, err := userstorage.New(r.db).GetUser(ctx, userID) if err != nil { return userdomain.Identity{}, err } if user.CurrentDisplayUserID == "" { // 理论上用户创建事务会保证短号存在,该分支保护脏数据。 return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDNotFound, "display_user_id not found") } return user.EffectiveIdentity(), nil } // ResolveDisplayUserID 只解析当前有效展示号。 // 查询前先按输入短号触发懒过期,保证过期靓号不继续解析,默认短号能恢复解析。 func (r *Repository) ResolveDisplayUserID(ctx context.Context, displayUserID string, nowMs int64) (userdomain.Identity, error) { // 解析前按输入短号触发懒过期,避免过期靓号继续被解析。 if err := r.expirePrettyByDisplayUserID(ctx, displayUserID, nowMs, "lazy_resolve"); err != nil { return userdomain.Identity{}, err } user, err := userstorage.QueryUser(ctx, r.db, "WHERE current_display_user_id = ?", displayUserID) if err == sql.ErrNoRows { // held 默认号和过期靓号都不会匹配 current_display_user_id。 return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDNotFound, "display_user_id not found") } if err != nil { return userdomain.Identity{}, err } return user.EffectiveIdentity(), nil } // ChangeDisplayUserID 修改默认短号;有 active 靓号时拒绝,避免过期恢复目标不清晰。 func (r *Repository) ChangeDisplayUserID(ctx context.Context, command userdomain.DisplayUserIDChangeCommand) (userdomain.Identity, error) { // 默认短号修改涉及 users、短号记录和变更日志,必须在一个事务内完成。 tx, err := r.db.BeginTx(ctx, nil) if err != nil { return userdomain.Identity{}, err } defer tx.Rollback() user, err := userstorage.QueryUser(ctx, tx, "WHERE user_id = ? FOR UPDATE", command.UserID) if err == sql.ErrNoRows { // FOR UPDATE 锁住用户行,避免并发修改默认短号或申请靓号。 return userdomain.Identity{}, xerr.New(xerr.NotFound, "user not found") } if err != nil { return userdomain.Identity{}, err } if user.DisplayUserIDExpired(command.ChangedAtMs) { // 修改前先恢复过期靓号,避免默认短号仍处于 held。 identity, err := r.expirePrettyInTx(ctx, tx, user, "", command.ChangedAtMs, command.RequestID) if err != nil { return userdomain.Identity{}, err } user = userdomain.User{ AppCode: user.AppCode, UserID: identity.UserID, DefaultDisplayUserID: identity.DefaultDisplayUserID, CurrentDisplayUserID: identity.DisplayUserID, CurrentDisplayUserIDKind: identity.DisplayUserIDKind, Status: user.Status, CreatedAtMs: user.CreatedAtMs, UpdatedAtMs: command.ChangedAtMs, } } if user.CurrentDisplayUserIDKind == userdomain.DisplayUserIDKindPretty { // active 靓号期间拒绝改默认短号,保证过期恢复目标稳定。 return userdomain.Identity{}, xerr.New(xerr.DisplayUserIDPrettyActive, "pretty display_user_id is active") } if user.DefaultDisplayUserID == command.NewDisplayUserID { // 修改为相同默认短号按幂等成功处理,不写日志。 return user.EffectiveIdentity(), nil } if err := r.assertDisplayUserIDAvailable(ctx, tx, command.NewDisplayUserID, command.UserID, true); err != nil { return userdomain.Identity{}, err } if command.CooldownMs > 0 { // 冷却期检查必须在事务内读取变更日志,避免并发绕过。 if err := r.assertChangeCooldown(ctx, tx, command); err != nil { return userdomain.Identity{}, err } } // 旧默认短号释放,新默认短号插入为 active。 _, err = tx.ExecContext(ctx, ` UPDATE user_display_user_ids SET status = ?, released_at_ms = ?, release_reason = ?, updated_at_ms = ? WHERE app_code = ? AND user_id = ? AND display_user_id = ? AND display_user_id_kind = ? AND status = ? `, string(userdomain.DisplayUserIDStatusReleased), command.ChangedAtMs, command.Reason, command.ChangedAtMs, appcode.Normalize(command.AppCode), command.UserID, user.DefaultDisplayUserID, string(userdomain.DisplayUserIDKindDefault), string(userdomain.DisplayUserIDStatusActive)) if err != nil { return userdomain.Identity{}, err } _, err = tx.ExecContext(ctx, ` INSERT INTO user_display_user_ids (app_code, display_user_id, user_id, display_user_id_kind, status, assigned_at_ms, activated_at_ms, created_at_ms, updated_at_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `, appcode.Normalize(command.AppCode), command.NewDisplayUserID, command.UserID, string(userdomain.DisplayUserIDKindDefault), string(userdomain.DisplayUserIDStatusActive), command.ChangedAtMs, command.ChangedAtMs, command.ChangedAtMs, command.ChangedAtMs) if err != nil { return userdomain.Identity{}, shared.MapDisplayDuplicateError(err) } _, err = tx.ExecContext(ctx, ` UPDATE users SET default_display_user_id = ?, current_display_user_id = ?, current_display_user_id_kind = ?, current_display_user_id_expires_at_ms = NULL, updated_at_ms = ? WHERE app_code = ? AND user_id = ? `, command.NewDisplayUserID, command.NewDisplayUserID, string(userdomain.DisplayUserIDKindDefault), command.ChangedAtMs, appcode.Normalize(command.AppCode), command.UserID) if err != nil { return userdomain.Identity{}, shared.MapDisplayDuplicateError(err) } if err := insertDisplayUserIDChangeLog(ctx, tx, displayUserIDLog{ AppCode: appcode.Normalize(command.AppCode), UserID: command.UserID, OldID: user.DefaultDisplayUserID, NewID: command.NewDisplayUserID, OldKind: userdomain.DisplayUserIDKindDefault, NewKind: userdomain.DisplayUserIDKindDefault, ChangeType: "default_change", Reason: command.Reason, OperatorID: command.OperatorUserID, RequestID: command.RequestID, CreatedAtMs: command.ChangedAtMs, }); err != nil { return userdomain.Identity{}, err } if err := tx.Commit(); err != nil { // commit 失败时调用方不能使用返回 identity。 return userdomain.Identity{}, err } return userdomain.Identity{ AppCode: appcode.Normalize(command.AppCode), UserID: command.UserID, DisplayUserID: command.NewDisplayUserID, DefaultDisplayUserID: command.NewDisplayUserID, DisplayUserIDKind: userdomain.DisplayUserIDKindDefault, Status: userdomain.DisplayUserIDStatusActive, }, nil } // ApplyPrettyDisplayUserID 创建临时靓号租约,并把默认短号置为 held。