package user import ( "context" "hyapp/pkg/appcode" "hyapp/pkg/idgen" "hyapp/pkg/xerr" userdomain "hyapp/services/user-service/internal/domain/user" ) // ApplyPrettyDisplayUserID 申请临时靓号并让它在租期内覆盖当前展示号。 func (s *Service) ApplyPrettyDisplayUserID(ctx context.Context, userID int64, prettyDisplayUserID string, leaseDurationMs int64, paymentReceiptID string, requestID string) (userdomain.Identity, string, error) { if userID <= 0 { // 靓号租约必须绑定明确用户。 return userdomain.Identity{}, "", xerr.New(xerr.InvalidArgument, "user_id is required") } if !userdomain.ValidDisplayUserID(prettyDisplayUserID) { // 靓号也复用 display_user_id 格式规则。 return userdomain.Identity{}, "", xerr.New(xerr.DisplayUserIDInvalid, "display_user_id format is invalid") } if leaseDurationMs <= 0 { // 没有正租期无法计算过期恢复时间。 return userdomain.Identity{}, "", xerr.New(xerr.InvalidArgument, "lease_duration_ms is required") } if paymentReceiptID == "" { // v1 要求先完成支付再申请靓号,避免 user-service 自己处理账务。 return userdomain.Identity{}, "", xerr.New(xerr.DisplayUserIDPaymentRequired, "payment receipt is required") } if s.identityRepository == nil { // 靓号涉及多表事务,必须由 identity repository 完成。 return userdomain.Identity{}, "", xerr.New(xerr.Unavailable, "identity repository is not configured") } // LeaseID 在服务层生成,repository 只负责原子保存和冲突判断。 nowMs := s.now().UnixMilli() return s.identityRepository.ApplyPrettyDisplayUserID(ctx, userdomain.PrettyDisplayUserIDCommand{ AppCode: appcode.FromContext(ctx), LeaseID: idgen.New("pdu_lease"), UserID: userID, PrettyDisplayID: prettyDisplayUserID, LeaseDurationMs: leaseDurationMs, PaymentReceiptID: paymentReceiptID, Source: "payment", RequestID: requestID, OperatorUserID: userID, NowMs: nowMs, }) } // ExpirePrettyDisplayUserID 主动触发靓号过期恢复;定时任务和懒过期应复用同一事务。 func (s *Service) ExpirePrettyDisplayUserID(ctx context.Context, userID int64, leaseID string, requestID string) (userdomain.Identity, error) { if userID <= 0 { // 过期恢复需要明确用户,lease_id 只是可选校验条件。 return userdomain.Identity{}, xerr.New(xerr.InvalidArgument, "user_id is required") } if s.identityRepository == nil { // 没有 identity repository 无法恢复默认短号。 return userdomain.Identity{}, xerr.New(xerr.Unavailable, "identity repository is not configured") } return s.identityRepository.ExpirePrettyDisplayUserID(ctx, userdomain.ExpirePrettyDisplayUserIDCommand{ AppCode: appcode.FromContext(ctx), UserID: userID, LeaseID: leaseID, RequestID: requestID, NowMs: s.now().UnixMilli(), }) } func (s *Service) refreshExpiredUser(ctx context.Context, user userdomain.User, requestID string) (userdomain.User, error) { nowMs := s.now().UnixMilli() if !user.DisplayUserIDExpired(nowMs) { // 当前展示号仍有效时直接返回用户快照。 return user, nil } if s.identityRepository == nil { // 发现过期但无法恢复时不能继续返回过期靓号。 return userdomain.User{}, xerr.New(xerr.Unavailable, "identity repository is not configured") } // 过期恢复必须走 identity repository 事务,同步更新 users 和短号记录。 if _, err := s.identityRepository.ExpirePrettyDisplayUserID(ctx, userdomain.ExpirePrettyDisplayUserIDCommand{ AppCode: appcode.FromContext(ctx), UserID: user.UserID, RequestID: requestID, NowMs: nowMs, }); err != nil { return userdomain.User{}, err } if s.userRepository == nil { // 恢复后需要重新读取 users 快照,确保返回的展示号来自持久化结果。 return userdomain.User{}, xerr.New(xerr.Unavailable, "user repository is not configured") } return s.userRepository.GetUser(ctx, user.UserID) }