package integration import ( "context" "fmt" "sort" "strings" "time" "google.golang.org/grpc" robotv1 "hyapp.local/api/proto/robot/v1" userv1 "hyapp.local/api/proto/user/v1" "hyapp/pkg/appcode" ) const humanRobotPoolPageSize = int32(200) // HumanRoomRobotPool 是真人房间机器人运行时按国家分组后的账号池。 type HumanRoomRobotPool struct { CountryCode string RobotUserIDs []int64 } // HumanRoomRobotPoolProvider 屏蔽 robot-service 和 user-service 的组合查询细节。 type HumanRoomRobotPoolProvider interface { ListHumanRoomRobotPools(ctx context.Context, appCode string) ([]HumanRoomRobotPool, error) } type GRPCHumanRoomRobotPoolProvider struct { robots robotv1.GameRobotServiceClient users userv1.UserServiceClient } func NewGRPCHumanRoomRobotPoolProvider(robotConn grpc.ClientConnInterface, userConn grpc.ClientConnInterface) *GRPCHumanRoomRobotPoolProvider { return &GRPCHumanRoomRobotPoolProvider{ robots: robotv1.NewGameRobotServiceClient(robotConn), users: userv1.NewUserServiceClient(userConn), } } func (p *GRPCHumanRoomRobotPoolProvider) ListHumanRoomRobotPools(ctx context.Context, appCode string) ([]HumanRoomRobotPool, error) { if p == nil || p.robots == nil || p.users == nil { return nil, fmt.Errorf("human room robot pool provider is not configured") } app := appcode.Normalize(appCode) userIDs, err := p.listActiveRobotUserIDs(ctx, app) if err != nil { return nil, err } if len(userIDs) == 0 { return nil, nil } groups := make(map[string][]int64) for start := 0; start < len(userIDs); start += int(humanRobotPoolPageSize) { end := start + int(humanRobotPoolPageSize) if end > len(userIDs) { end = len(userIDs) } resp, err := p.users.BatchGetUsers(ctx, &userv1.BatchGetUsersRequest{ Meta: userRequestMeta(app, "room-human-robot-users"), UserIds: userIDs[start:end], }) if err != nil { return nil, err } for _, userID := range userIDs[start:end] { user := resp.GetUsers()[userID] countryCode := normalizePoolCountryCode(user.GetCountry()) if countryCode == "" { continue } groups[countryCode] = append(groups[countryCode], userID) } } pools := make([]HumanRoomRobotPool, 0, len(groups)) for countryCode, ids := range groups { pools = append(pools, HumanRoomRobotPool{CountryCode: countryCode, RobotUserIDs: ids}) } sort.Slice(pools, func(i, j int) bool { return pools[i].CountryCode < pools[j].CountryCode }) return pools, nil } func (p *GRPCHumanRoomRobotPoolProvider) listActiveRobotUserIDs(ctx context.Context, app string) ([]int64, error) { userIDs := make([]int64, 0, humanRobotPoolPageSize) cursor := "" for { resp, err := p.robots.ListGameRobots(ctx, &robotv1.ListGameRobotsRequest{ Meta: robotRequestMeta(app, "room-human-robot-pool"), Status: "active", PageSize: humanRobotPoolPageSize, Cursor: cursor, }) if err != nil { return nil, err } for _, robot := range resp.GetRobots() { if robot.GetUserId() > 0 { userIDs = append(userIDs, robot.GetUserId()) } } cursor = strings.TrimSpace(resp.GetNextCursor()) if cursor == "" { return uniquePositiveInt64(userIDs), nil } } } func robotRequestMeta(appCode string, requestID string) *robotv1.RequestMeta { return &robotv1.RequestMeta{ RequestId: requestID, Caller: "room-service", AppCode: appcode.Normalize(appCode), SentAtMs: time.Now().UTC().UnixMilli(), } } func userRequestMeta(appCode string, requestID string) *userv1.RequestMeta { return &userv1.RequestMeta{ RequestId: requestID, Caller: "room-service", AppCode: appcode.Normalize(appCode), SentAtMs: time.Now().UTC().UnixMilli(), } } func normalizePoolCountryCode(value string) string { return strings.ToUpper(strings.TrimSpace(value)) } func uniquePositiveInt64(values []int64) []int64 { seen := make(map[int64]bool, len(values)) out := make([]int64, 0, len(values)) for _, value := range values { if value <= 0 || seen[value] { continue } seen[value] = true out = append(out, value) } return out }