package user import ( "context" "strings" "hyapp/pkg/appcode" "hyapp/pkg/xerr" userdomain "hyapp/services/user-service/internal/domain/user" ) const ( defaultUserIDPageSize = 500 maxUserIDPageSize = 5000 ) // GetUser 查询单个用户主状态。 func (s *Service) GetUser(ctx context.Context, userID int64) (userdomain.User, error) { if userID <= 0 { // service 层统一做参数校验,transport 只负责协议适配。 return userdomain.User{}, xerr.New(xerr.InvalidArgument, "user_id is required") } if s.userRepository == nil { // repository 缺失代表应用装配不完整。 return userdomain.User{}, xerr.New(xerr.Unavailable, "user repository is not configured") } user, err := s.userRepository.GetUser(ctx, userID) if err != nil { return userdomain.User{}, err } // 查询用户时顺带执行靓号懒过期,返回给上游的是当前有效展示号。 return s.refreshExpiredUser(ctx, user, "") } // GetMyProfileStats 读取我的页顶部统计计数;统计事实必须由专门 read model 维护。 func (s *Service) GetMyProfileStats(ctx context.Context, userID int64) (userdomain.ProfileStats, error) { if userID <= 0 { return userdomain.ProfileStats{}, xerr.New(xerr.InvalidArgument, "user_id is required") } if s.userRepository == nil { return userdomain.ProfileStats{}, xerr.New(xerr.Unavailable, "user repository is not configured") } return s.userRepository.GetMyProfileStats(ctx, userID) } // BatchGetUsers 批量查询用户主状态。 func (s *Service) BatchGetUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.User, error) { if len(userIDs) == 0 { // 空批量请求返回空 map,避免调用方处理 nil。 return map[int64]userdomain.User{}, nil } for _, userID := range userIDs { if userID <= 0 { // 任一非法 ID 都让整个批量请求失败,避免部分成功掩盖输入错误。 return nil, xerr.New(xerr.InvalidArgument, "user_ids contains invalid value") } } if s.userRepository == nil { // 批量查询同样不能绕过 repository。 return nil, xerr.New(xerr.Unavailable, "user repository is not configured") } users, err := s.userRepository.BatchGetUsers(ctx, userIDs) if err != nil { return nil, err } for userID, user := range users { // 批量返回前逐个刷新过期靓号,保证身份投影一致。 refreshed, err := s.refreshExpiredUser(ctx, user, "") if err != nil { return nil, err } users[userID] = refreshed } return users, nil } // ListUserIDs returns active user IDs for low-frequency backend fanout tasks. func (s *Service) ListUserIDs(ctx context.Context, filter userdomain.UserIDPageFilter) ([]int64, int64, bool, error) { if s.userRepository == nil { return nil, 0, false, xerr.New(xerr.Unavailable, "user repository is not configured") } filter.TargetScope = strings.TrimSpace(filter.TargetScope) filter.Country = userdomain.NormalizeCountryCode(filter.Country) filter.PageSize = normalizeUserIDPageSize(filter.PageSize) if filter.CursorUserID < 0 { return nil, 0, false, xerr.New(xerr.InvalidArgument, "cursor_user_id is invalid") } switch filter.TargetScope { case userdomain.UserIDTargetAllActiveUsers: case userdomain.UserIDTargetRegion: if filter.RegionID <= 0 { return nil, 0, false, xerr.New(xerr.InvalidArgument, "region_id is required") } case userdomain.UserIDTargetCountry: if !userdomain.ValidCountryCode(filter.Country) { return nil, 0, false, xerr.New(xerr.InvalidArgument, "country is invalid") } default: return nil, 0, false, xerr.New(xerr.InvalidArgument, "target_scope is invalid") } userIDs, err := s.userRepository.ListUserIDs(ctx, filter) if err != nil { return nil, 0, false, err } nextCursor := filter.CursorUserID if len(userIDs) > 0 { nextCursor = userIDs[len(userIDs)-1] } return userIDs, nextCursor, len(userIDs) < filter.PageSize, nil } func normalizeUserIDPageSize(value int) int { if value <= 0 { return defaultUserIDPageSize } if value > maxUserIDPageSize { return maxUserIDPageSize } return value } // CreateUser 创建用户主记录,并同时分配 active display_user_id。 // 该方法供注册和三方首次登录后续接入,避免出现没有短号的用户。 func (s *Service) CreateUser(ctx context.Context) (userdomain.User, userdomain.Identity, error) { if s.userRepository == nil { // 没有持久化能力时不能创建用户。 return userdomain.User{}, userdomain.Identity{}, xerr.New(xerr.Unavailable, "user repository is not configured") } for attempt := 0; attempt < s.allocateMaxAttempts; attempt++ { // user_id 和 display_user_id 都在每次尝试中重新生成,降低冲突概率。 userID := s.idGenerator.NewInt64() nowMs := s.now().UnixMilli() displayUserID := s.displayUserIDAllocator.NextDisplayUserID(userID, attempt) if !userdomain.ValidDisplayUserID(displayUserID) { // allocator 理论上应生成合法短号;非法候选直接跳过本次尝试。 continue } createdUser := userdomain.User{ AppCode: appcode.FromContext(ctx), UserID: userID, DefaultDisplayUserID: displayUserID, CurrentDisplayUserID: displayUserID, CurrentDisplayUserIDKind: userdomain.DisplayUserIDKindDefault, Status: userdomain.StatusActive, CreatedAtMs: nowMs, UpdatedAtMs: nowMs, } identity := userdomain.Identity{ AppCode: appcode.FromContext(ctx), UserID: userID, DisplayUserID: displayUserID, DefaultDisplayUserID: displayUserID, DisplayUserIDKind: userdomain.DisplayUserIDKindDefault, Status: userdomain.DisplayUserIDStatusActive, } err := s.userRepository.CreateUserWithIdentity(ctx, createdUser, identity) if err == nil { // repository 事务成功后,用户和默认短号一定同时存在。 return createdUser, identity, nil } if !xerr.IsCode(err, xerr.DisplayUserIDExists) { // 非短号冲突错误不能重试,否则可能掩盖真实存储异常。 return userdomain.User{}, userdomain.Identity{}, err } } // 有限重试耗尽后返回稳定 reason,调用方可以提示稍后重试。 return userdomain.User{}, userdomain.Identity{}, xerr.New(xerr.DisplayUserIDAllocateFailed, "display_user_id allocation failed") }