机器人房间相关
This commit is contained in:
parent
3b2e059964
commit
7883bb8217
File diff suppressed because it is too large
Load Diff
@ -299,6 +299,8 @@ message AdminHumanRoomRobotConfig {
|
||||
int64 normal_gift_interval_max_ms = 23;
|
||||
int32 room_target_min_online = 24;
|
||||
int32 room_target_max_online = 25;
|
||||
// allowed_owner_ids 为空表示扫描所有真人房;非空时只扫描房主展示短号/内部 owner_user_id 命中的房间。
|
||||
repeated string allowed_owner_ids = 26;
|
||||
}
|
||||
|
||||
message AdminGetHumanRoomRobotConfigRequest {
|
||||
|
||||
@ -186,6 +186,7 @@ type HumanRoomRobotConfig struct {
|
||||
LuckyPauseMinMS int64
|
||||
LuckyPauseMaxMS int64
|
||||
MaxGiftSenders int64
|
||||
AllowedOwnerIDs []string
|
||||
UpdatedByAdminID uint64
|
||||
CreatedAtMS int64
|
||||
UpdatedAtMS int64
|
||||
@ -746,6 +747,7 @@ func humanRoomRobotConfigFromProto(input *roomv1.AdminHumanRoomRobotConfig) Huma
|
||||
LuckyPauseMinMS: input.GetLuckyPauseMinMs(),
|
||||
LuckyPauseMaxMS: input.GetLuckyPauseMaxMs(),
|
||||
MaxGiftSenders: input.GetMaxGiftSenders(),
|
||||
AllowedOwnerIDs: append([]string(nil), input.GetAllowedOwnerIds()...),
|
||||
UpdatedByAdminID: input.GetUpdatedByAdminId(),
|
||||
CreatedAtMS: input.GetCreatedAtMs(),
|
||||
UpdatedAtMS: input.GetUpdatedAtMs(),
|
||||
@ -776,6 +778,7 @@ func humanRoomRobotConfigToProto(input HumanRoomRobotConfig) *roomv1.AdminHumanR
|
||||
LuckyPauseMinMs: input.LuckyPauseMinMS,
|
||||
LuckyPauseMaxMs: input.LuckyPauseMaxMS,
|
||||
MaxGiftSenders: input.MaxGiftSenders,
|
||||
AllowedOwnerIds: append([]string(nil), input.AllowedOwnerIDs...),
|
||||
UpdatedByAdminId: input.UpdatedByAdminID,
|
||||
CreatedAtMs: input.CreatedAtMS,
|
||||
UpdatedAtMs: input.UpdatedAtMS,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package roomadmin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
@ -46,3 +47,24 @@ func TestNormalizeRoomConfigRejectsInvalidValues(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHumanRoomRobotAllowedOwnerIDsAcceptsCommaStringAndArray(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
body string
|
||||
want []string
|
||||
}{
|
||||
{name: "comma_string", body: `{"allowedOwnerIds":"1001, 1002,,1001"}`, want: []string{"1001", "1002"}},
|
||||
{name: "array", body: `{"allowedOwnerIds":["1003"," 1004 ","1003"]}`, want: []string{"1003", "1004"}},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var req updateHumanRoomRobotConfigRequest
|
||||
if err := json.Unmarshal([]byte(test.body), &req); err != nil {
|
||||
t.Fatalf("unmarshal human room robot config failed: %v", err)
|
||||
}
|
||||
if got := normalizeStringList([]string(req.AllowedOwnerIDs)); !reflect.DeepEqual(got, test.want) {
|
||||
t.Fatalf("allowed owner ids mismatch: got %+v want %+v", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,26 +139,27 @@ type updateRoomWhitelistRequest struct {
|
||||
}
|
||||
|
||||
type updateHumanRoomRobotConfigRequest struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
CandidateRoomMaxOnline int32 `json:"candidateRoomMaxOnline"`
|
||||
RoomFullStopOnline int32 `json:"roomFullStopOnline"`
|
||||
RoomTargetMinOnline int32 `json:"roomTargetMinOnline"`
|
||||
RoomTargetMaxOnline int32 `json:"roomTargetMaxOnline"`
|
||||
RobotStayMinMS int64 `json:"robotStayMinMs"`
|
||||
RobotStayMaxMS int64 `json:"robotStayMaxMs"`
|
||||
RobotReplaceMinMS int64 `json:"robotReplaceMinMs"`
|
||||
RobotReplaceMaxMS int64 `json:"robotReplaceMaxMs"`
|
||||
NormalGiftIntervalMS int64 `json:"normalGiftIntervalMs"`
|
||||
NormalGiftIntervalMinMS int64 `json:"normalGiftIntervalMinMs"`
|
||||
NormalGiftIntervalMaxMS int64 `json:"normalGiftIntervalMaxMs"`
|
||||
GiftIDs []string `json:"giftIds"`
|
||||
LuckyGiftIDs []string `json:"luckyGiftIds"`
|
||||
SuperLuckyGiftIDs []string `json:"superLuckyGiftIds"`
|
||||
LuckyComboMin int64 `json:"luckyComboMin"`
|
||||
LuckyComboMax int64 `json:"luckyComboMax"`
|
||||
LuckyPauseMinMS int64 `json:"luckyPauseMinMs"`
|
||||
LuckyPauseMaxMS int64 `json:"luckyPauseMaxMs"`
|
||||
MaxGiftSenders int64 `json:"maxGiftSenders"`
|
||||
Enabled bool `json:"enabled"`
|
||||
CandidateRoomMaxOnline int32 `json:"candidateRoomMaxOnline"`
|
||||
RoomFullStopOnline int32 `json:"roomFullStopOnline"`
|
||||
RoomTargetMinOnline int32 `json:"roomTargetMinOnline"`
|
||||
RoomTargetMaxOnline int32 `json:"roomTargetMaxOnline"`
|
||||
RobotStayMinMS int64 `json:"robotStayMinMs"`
|
||||
RobotStayMaxMS int64 `json:"robotStayMaxMs"`
|
||||
RobotReplaceMinMS int64 `json:"robotReplaceMinMs"`
|
||||
RobotReplaceMaxMS int64 `json:"robotReplaceMaxMs"`
|
||||
NormalGiftIntervalMS int64 `json:"normalGiftIntervalMs"`
|
||||
NormalGiftIntervalMinMS int64 `json:"normalGiftIntervalMinMs"`
|
||||
NormalGiftIntervalMaxMS int64 `json:"normalGiftIntervalMaxMs"`
|
||||
GiftIDs []string `json:"giftIds"`
|
||||
LuckyGiftIDs []string `json:"luckyGiftIds"`
|
||||
SuperLuckyGiftIDs []string `json:"superLuckyGiftIds"`
|
||||
LuckyComboMin int64 `json:"luckyComboMin"`
|
||||
LuckyComboMax int64 `json:"luckyComboMax"`
|
||||
LuckyPauseMinMS int64 `json:"luckyPauseMinMs"`
|
||||
LuckyPauseMaxMS int64 `json:"luckyPauseMaxMs"`
|
||||
MaxGiftSenders int64 `json:"maxGiftSenders"`
|
||||
AllowedOwnerIDs flexibleCommaStringList `json:"allowedOwnerIds"`
|
||||
}
|
||||
|
||||
type flexibleJSONInt64StringSlice []string
|
||||
@ -182,3 +183,40 @@ func (values *flexibleJSONInt64StringSlice) UnmarshalJSON(data []byte) error {
|
||||
*values = out
|
||||
return nil
|
||||
}
|
||||
|
||||
type flexibleCommaStringList []string
|
||||
|
||||
func (values *flexibleCommaStringList) UnmarshalJSON(data []byte) error {
|
||||
// 真人房机器人后台是人工输入框场景,兼容 "1001,1002" 和 ["1001","1002"] 两种提交,统一在 service 层去空去重。
|
||||
raw := strings.TrimSpace(string(data))
|
||||
if raw == "" || raw == "null" {
|
||||
*values = nil
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(raw, "\"") {
|
||||
var text string
|
||||
if err := json.Unmarshal(data, &text); err != nil {
|
||||
return err
|
||||
}
|
||||
*values = splitCommaStringList(text)
|
||||
return nil
|
||||
}
|
||||
var list []string
|
||||
if err := json.Unmarshal(data, &list); err != nil {
|
||||
return err
|
||||
}
|
||||
*values = splitCommaStringList(strings.Join(list, ","))
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitCommaStringList(raw string) []string {
|
||||
parts := strings.Split(raw, ",")
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part != "" {
|
||||
out = append(out, part)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@ -80,6 +80,7 @@ type HumanRoomRobotConfig struct {
|
||||
LuckyPauseMinMS int64 `json:"luckyPauseMinMs"`
|
||||
LuckyPauseMaxMS int64 `json:"luckyPauseMaxMs"`
|
||||
MaxGiftSenders int64 `json:"maxGiftSenders"`
|
||||
AllowedOwnerIDs []string `json:"allowedOwnerIds"`
|
||||
UpdatedByAdminID uint64 `json:"updatedByAdminId"`
|
||||
CreatedAtMS int64 `json:"createdAtMs"`
|
||||
UpdatedAtMS int64 `json:"updatedAtMs"`
|
||||
@ -213,6 +214,7 @@ func (s *Service) UpdateHumanRoomRobotConfig(ctx context.Context, req updateHuma
|
||||
LuckyPauseMinMS: req.LuckyPauseMinMS,
|
||||
LuckyPauseMaxMS: req.LuckyPauseMaxMS,
|
||||
MaxGiftSenders: req.MaxGiftSenders,
|
||||
AllowedOwnerIDs: normalizeStringList([]string(req.AllowedOwnerIDs)),
|
||||
}
|
||||
saved, err := s.roomClient.UpdateHumanRoomRobotConfig(ctx, roomclient.UpdateHumanRoomRobotConfigRequest{
|
||||
Config: config,
|
||||
@ -283,6 +285,7 @@ func (s *Service) humanRoomRobotConfigFromClient(_ context.Context, config roomc
|
||||
LuckyPauseMinMS: config.LuckyPauseMinMS,
|
||||
LuckyPauseMaxMS: config.LuckyPauseMaxMS,
|
||||
MaxGiftSenders: config.MaxGiftSenders,
|
||||
AllowedOwnerIDs: append([]string(nil), config.AllowedOwnerIDs...),
|
||||
UpdatedByAdminID: config.UpdatedByAdminID,
|
||||
CreatedAtMS: config.CreatedAtMS,
|
||||
UpdatedAtMS: config.UpdatedAtMS,
|
||||
|
||||
@ -304,6 +304,7 @@ CREATE TABLE IF NOT EXISTS room_human_robot_configs (
|
||||
lucky_pause_max_ms BIGINT NOT NULL DEFAULT 20000 COMMENT '幸运礼物连击后最大暂停毫秒',
|
||||
max_gift_senders BIGINT NOT NULL DEFAULT 1 COMMENT '同一时间最多送礼机器人数量',
|
||||
country_pools_json JSON NOT NULL COMMENT '国家机器人池配置',
|
||||
allowed_owner_ids_json JSON NOT NULL COMMENT '限定进入的房主展示短号/内部用户 ID,空数组表示所有真人房',
|
||||
updated_by_admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '最后更新管理员 ID',
|
||||
created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms',
|
||||
updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms',
|
||||
|
||||
@ -109,7 +109,8 @@ func (s *Service) scanHumanRoomRobotRuntimes(ctx context.Context) {
|
||||
if countryCode == "" || len(pool.RobotUserIDs) == 0 {
|
||||
continue
|
||||
}
|
||||
rooms, err := s.repository.ListHumanRobotCandidateRooms(ctx, config.AppCode, countryCode, config.CandidateRoomMaxOnline, config.RoomTargetMaxOnline, 50)
|
||||
// 限定房主 ID 只影响本轮候选房 SQL,不额外读取 Room Cell 或 presence;为空时保持原来的全房间扫描语义。
|
||||
rooms, err := s.repository.ListHumanRobotCandidateRooms(ctx, config.AppCode, countryCode, config.CandidateRoomMaxOnline, config.RoomTargetMaxOnline, 50, config.AllowedOwnerIDs)
|
||||
if err != nil {
|
||||
logx.Warn(ctx, "human_room_robot_candidate_rooms_failed", slog.String("country_code", countryCode), slog.String("error", err.Error()))
|
||||
continue
|
||||
@ -446,10 +447,28 @@ func (s *Service) sendHumanRoomRobotGiftBestEffort(ctx context.Context, config H
|
||||
RealRoomHeat: true,
|
||||
})
|
||||
if err != nil {
|
||||
reconcileHumanRoomRobotGiftPresence(activity, senderID, targetID, err)
|
||||
logx.Warn(ctx, "human_room_robot_send_gift_failed", slog.String("room_id", roomID), slog.String("kind", kind), slog.Int64("sender_user_id", senderID), slog.Int64("target_user_id", targetID), slog.String("gift_id", giftID), slog.String("error", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
func reconcileHumanRoomRobotGiftPresence(activity *robotRoomActivity, senderID int64, targetID int64, err error) {
|
||||
if activity == nil || err == nil || xerr.CodeOf(err) != xerr.NotFound {
|
||||
return
|
||||
}
|
||||
message := xerr.MessageOf(err)
|
||||
switch {
|
||||
case strings.Contains(message, "sender not in room"):
|
||||
// 送礼前不额外读 snapshot,避免把每次礼物尝试都放大成一次 Room Cell/恢复查询;
|
||||
// 直接复用 RobotSendGift 在 Room Cell 内的强一致校验结果,把已离房 sender 从运行时活跃集合移除。
|
||||
activity.markInactive(senderID)
|
||||
case strings.Contains(message, "target not in room"):
|
||||
// target 已经不在 Room Cell presence 时继续保留 active 会造成后续随机命中反复失败;
|
||||
// 这里只更新本地 human-room 运行时状态,补位仍由 fill/presence 循环按原有节奏执行。
|
||||
activity.markInactive(targetID)
|
||||
}
|
||||
}
|
||||
|
||||
func markInitialHumanRoomGiftSenders(activity *robotRoomActivity, initialActive []int64, maxSenders int) {
|
||||
if activity == nil || maxSenders <= 0 || len(initialActive) == 0 {
|
||||
return
|
||||
@ -667,6 +686,7 @@ func normalizeHumanRoomRobotConfig(appCode string, input *roomv1.AdminHumanRoomR
|
||||
LuckyPauseMinMS: input.GetLuckyPauseMinMs(),
|
||||
LuckyPauseMaxMS: input.GetLuckyPauseMaxMs(),
|
||||
MaxGiftSenders: input.GetMaxGiftSenders(),
|
||||
AllowedOwnerIDs: normalizeStringSet(input.GetAllowedOwnerIds()),
|
||||
UpdatedByAdminID: adminID,
|
||||
CreatedAtMS: input.GetCreatedAtMs(),
|
||||
UpdatedAtMS: nowMS,
|
||||
@ -783,6 +803,7 @@ func humanRoomRobotConfigToProto(config HumanRoomRobotConfig) *roomv1.AdminHuman
|
||||
LuckyPauseMinMs: config.LuckyPauseMinMS,
|
||||
LuckyPauseMaxMs: config.LuckyPauseMaxMS,
|
||||
MaxGiftSenders: config.MaxGiftSenders,
|
||||
AllowedOwnerIds: append([]string(nil), config.AllowedOwnerIDs...),
|
||||
UpdatedByAdminId: config.UpdatedByAdminID,
|
||||
CreatedAtMs: config.CreatedAtMS,
|
||||
UpdatedAtMs: config.UpdatedAtMS,
|
||||
|
||||
@ -4,6 +4,8 @@ import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hyapp/pkg/xerr"
|
||||
)
|
||||
|
||||
func TestHumanRoomRobotGiftSenderLimit(t *testing.T) {
|
||||
@ -21,6 +23,32 @@ func TestHumanRoomRobotGiftSenderLimit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHumanRoomRobotGiftPresenceDriftRemovesStaleUsers(t *testing.T) {
|
||||
activity := newRobotRoomActivity([]int64{101, 102, 103})
|
||||
for _, userID := range []int64{101, 102, 103} {
|
||||
activity.markActive(userID)
|
||||
}
|
||||
activity.markGiftSender(101)
|
||||
|
||||
reconcileHumanRoomRobotGiftPresence(activity, 101, 102, xerr.New(xerr.NotFound, "sender not in room"))
|
||||
if activity.isActive(101) || activity.isGiftSender(101) {
|
||||
t.Fatalf("stale sender must be removed from active and gift sender sets")
|
||||
}
|
||||
if !activity.isActive(102) {
|
||||
t.Fatalf("target must stay active when sender is stale")
|
||||
}
|
||||
|
||||
activity.markActive(101)
|
||||
activity.markGiftSender(101)
|
||||
reconcileHumanRoomRobotGiftPresence(activity, 101, 102, xerr.New(xerr.NotFound, "target not in room"))
|
||||
if !activity.isActive(101) || !activity.isGiftSender(101) {
|
||||
t.Fatalf("sender must stay active when target is stale")
|
||||
}
|
||||
if activity.isActive(102) {
|
||||
t.Fatalf("stale target must be removed from active set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHumanRoomRobotNormalGiftDelayRange(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
config := HumanRoomRobotConfig{NormalGiftIntervalMinMS: 1000, NormalGiftIntervalMaxMS: 20000}
|
||||
|
||||
@ -288,6 +288,7 @@ type HumanRoomRobotConfig struct {
|
||||
LuckyPauseMaxMS int64
|
||||
MaxGiftSenders int64
|
||||
CountryPools []HumanRoomRobotCountryPool
|
||||
AllowedOwnerIDs []string
|
||||
UpdatedByAdminID uint64
|
||||
CreatedAtMS int64
|
||||
UpdatedAtMS int64
|
||||
@ -696,7 +697,7 @@ type Repository interface {
|
||||
ListActiveRobotRooms(ctx context.Context) ([]RobotRoomConfig, error)
|
||||
GetHumanRoomRobotConfig(ctx context.Context) (HumanRoomRobotConfig, bool, error)
|
||||
UpsertHumanRoomRobotConfig(ctx context.Context, config HumanRoomRobotConfig) (HumanRoomRobotConfig, error)
|
||||
ListHumanRobotCandidateRooms(ctx context.Context, appCode string, countryCode string, maxOnline int32, fullStopOnline int32, limit int) ([]RoomListEntry, error)
|
||||
ListHumanRobotCandidateRooms(ctx context.Context, appCode string, countryCode string, maxOnline int32, fullStopOnline int32, limit int, allowedOwnerIDs []string) ([]RoomListEntry, error)
|
||||
}
|
||||
|
||||
// RoomPresenceSnapshot 是 repository 投影接口的稳定入参,避免存储层反向依赖 protobuf。
|
||||
|
||||
@ -357,6 +357,7 @@ func (r *Repository) Migrate(ctx context.Context) error {
|
||||
lucky_pause_max_ms BIGINT NOT NULL DEFAULT 20000,
|
||||
max_gift_senders BIGINT NOT NULL DEFAULT 1,
|
||||
country_pools_json JSON NOT NULL,
|
||||
allowed_owner_ids_json JSON NOT NULL,
|
||||
updated_by_admin_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
created_at_ms BIGINT NOT NULL,
|
||||
updated_at_ms BIGINT NOT NULL,
|
||||
@ -463,6 +464,7 @@ func (r *Repository) ensureHumanRobotConfigBehaviorSchema(ctx context.Context) e
|
||||
{name: "normal_gift_interval_max_ms", statement: `ALTER TABLE room_human_robot_configs ADD COLUMN normal_gift_interval_max_ms BIGINT NOT NULL DEFAULT 20000 AFTER normal_gift_interval_min_ms`},
|
||||
{name: "room_target_min_online", statement: `ALTER TABLE room_human_robot_configs ADD COLUMN room_target_min_online INT NOT NULL DEFAULT 8 AFTER room_full_stop_online`},
|
||||
{name: "room_target_max_online", statement: `ALTER TABLE room_human_robot_configs ADD COLUMN room_target_max_online INT NOT NULL DEFAULT 10 AFTER room_target_min_online`},
|
||||
{name: "allowed_owner_ids_json", statement: `ALTER TABLE room_human_robot_configs ADD COLUMN allowed_owner_ids_json JSON NULL AFTER country_pools_json`},
|
||||
}
|
||||
for _, column := range columns {
|
||||
exists, err := r.columnExists(ctx, "room_human_robot_configs", column.name)
|
||||
@ -1563,7 +1565,7 @@ func (r *Repository) GetHumanRoomRobotConfig(ctx context.Context) (roomservice.H
|
||||
normal_gift_interval_ms, normal_gift_interval_min_ms, normal_gift_interval_max_ms,
|
||||
gift_ids_json, lucky_gift_ids_json, super_lucky_gift_ids_json,
|
||||
lucky_combo_min, lucky_combo_max, lucky_pause_min_ms, lucky_pause_max_ms, max_gift_senders,
|
||||
country_pools_json, updated_by_admin_id, created_at_ms, updated_at_ms
|
||||
country_pools_json, COALESCE(allowed_owner_ids_json, JSON_ARRAY()), updated_by_admin_id, created_at_ms, updated_at_ms
|
||||
FROM room_human_robot_configs
|
||||
WHERE app_code = ?`,
|
||||
appcode.FromContext(ctx),
|
||||
@ -1585,7 +1587,7 @@ func (r *Repository) UpsertHumanRoomRobotConfig(ctx context.Context, config room
|
||||
config.AppCode = appCode
|
||||
config.CreatedAtMS = firstPositiveInt64(config.CreatedAtMS, nowMS)
|
||||
config.UpdatedAtMS = nowMS
|
||||
rawGifts, rawLucky, rawSuperLucky, rawPools, err := humanRoomRobotConfigJSON(config)
|
||||
rawGifts, rawLucky, rawSuperLucky, rawPools, rawAllowedOwners, err := humanRoomRobotConfigJSON(config)
|
||||
if err != nil {
|
||||
return roomservice.HumanRoomRobotConfig{}, err
|
||||
}
|
||||
@ -1596,8 +1598,8 @@ func (r *Repository) UpsertHumanRoomRobotConfig(ctx context.Context, config room
|
||||
normal_gift_interval_ms, normal_gift_interval_min_ms, normal_gift_interval_max_ms,
|
||||
gift_ids_json, lucky_gift_ids_json, super_lucky_gift_ids_json,
|
||||
lucky_combo_min, lucky_combo_max, lucky_pause_min_ms, lucky_pause_max_ms, max_gift_senders,
|
||||
country_pools_json, updated_by_admin_id, created_at_ms, updated_at_ms
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
country_pools_json, allowed_owner_ids_json, updated_by_admin_id, created_at_ms, updated_at_ms
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
enabled = VALUES(enabled),
|
||||
candidate_room_max_online = VALUES(candidate_room_max_online),
|
||||
@ -1620,6 +1622,7 @@ func (r *Repository) UpsertHumanRoomRobotConfig(ctx context.Context, config room
|
||||
lucky_pause_max_ms = VALUES(lucky_pause_max_ms),
|
||||
max_gift_senders = VALUES(max_gift_senders),
|
||||
country_pools_json = VALUES(country_pools_json),
|
||||
allowed_owner_ids_json = VALUES(allowed_owner_ids_json),
|
||||
updated_by_admin_id = VALUES(updated_by_admin_id),
|
||||
updated_at_ms = VALUES(updated_at_ms)`,
|
||||
config.AppCode,
|
||||
@ -1644,6 +1647,7 @@ func (r *Repository) UpsertHumanRoomRobotConfig(ctx context.Context, config room
|
||||
config.LuckyPauseMaxMS,
|
||||
config.MaxGiftSenders,
|
||||
string(rawPools),
|
||||
string(rawAllowedOwners),
|
||||
config.UpdatedByAdminID,
|
||||
config.CreatedAtMS,
|
||||
config.UpdatedAtMS,
|
||||
@ -1655,8 +1659,8 @@ func (r *Repository) UpsertHumanRoomRobotConfig(ctx context.Context, config room
|
||||
return saved, err
|
||||
}
|
||||
|
||||
// ListHumanRobotCandidateRooms 找出同国家低人数真人房;room_id 使用 robot_ 前缀的内部机器人房不参与。
|
||||
func (r *Repository) ListHumanRobotCandidateRooms(ctx context.Context, appCode string, countryCode string, maxOnline int32, fullStopOnline int32, limit int) ([]roomservice.RoomListEntry, error) {
|
||||
// ListHumanRobotCandidateRooms 找出同国家低人数真人房;allowedOwnerIDs 为空时保持全量扫描,非空时只命中房主短号或内部 owner_user_id。
|
||||
func (r *Repository) ListHumanRobotCandidateRooms(ctx context.Context, appCode string, countryCode string, maxOnline int32, fullStopOnline int32, limit int, allowedOwnerIDs []string) ([]roomservice.RoomListEntry, error) {
|
||||
appCode = normalizedRecordAppCode(ctx, appCode)
|
||||
countryCode = strings.ToUpper(strings.TrimSpace(countryCode))
|
||||
if maxOnline <= 0 {
|
||||
@ -1671,25 +1675,41 @@ func (r *Repository) ListHumanRobotCandidateRooms(ctx context.Context, appCode s
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 50
|
||||
}
|
||||
rows, err := r.db.QueryContext(ctx,
|
||||
`SELECT app_code, room_id, room_short_id, visible_region_id, owner_country_code,
|
||||
shortIDs, ownerUserIDs := splitHumanRobotAllowedOwnerIDs(allowedOwnerIDs)
|
||||
where := []string{
|
||||
"app_code = ?",
|
||||
"status = 'active'",
|
||||
"owner_country_code = ?",
|
||||
"online_count < ?",
|
||||
"online_count < ?",
|
||||
"room_id NOT LIKE 'robot\\_%'",
|
||||
}
|
||||
args := []any{appCode, countryCode, maxOnline, fullStopOnline}
|
||||
if len(shortIDs) > 0 {
|
||||
ownerWhere := make([]string, 0, 2)
|
||||
shortPlaceholders := strings.TrimRight(strings.Repeat("?,", len(shortIDs)), ",")
|
||||
ownerWhere = append(ownerWhere, "room_short_id IN ("+shortPlaceholders+")")
|
||||
for _, id := range shortIDs {
|
||||
args = append(args, id)
|
||||
}
|
||||
if len(ownerUserIDs) > 0 {
|
||||
userPlaceholders := strings.TrimRight(strings.Repeat("?,", len(ownerUserIDs)), ",")
|
||||
ownerWhere = append(ownerWhere, "owner_user_id IN ("+userPlaceholders+")")
|
||||
for _, id := range ownerUserIDs {
|
||||
args = append(args, id)
|
||||
}
|
||||
}
|
||||
where = append(where, "("+strings.Join(ownerWhere, " OR ")+")")
|
||||
}
|
||||
args = append(args, limit)
|
||||
query := `SELECT app_code, room_id, room_short_id, visible_region_id, owner_country_code,
|
||||
owner_user_id, title, cover_url, mode, status, locked, heat,
|
||||
online_count, seat_count, occupied_seat_count, sort_score, created_at_ms, updated_at_ms
|
||||
FROM room_list_entries
|
||||
WHERE app_code = ?
|
||||
AND status = 'active'
|
||||
AND owner_country_code = ?
|
||||
AND online_count < ?
|
||||
AND online_count < ?
|
||||
AND room_id NOT LIKE 'robot\_%'
|
||||
WHERE ` + strings.Join(where, "\n AND ") + `
|
||||
ORDER BY online_count ASC, updated_at_ms DESC, room_id DESC
|
||||
LIMIT ?`,
|
||||
appCode,
|
||||
countryCode,
|
||||
maxOnline,
|
||||
fullStopOnline,
|
||||
limit,
|
||||
)
|
||||
LIMIT ?`
|
||||
rows, err := r.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1735,6 +1755,7 @@ func scanHumanRoomRobotConfig(row humanRoomRobotConfigScanner) (roomservice.Huma
|
||||
var rawLucky string
|
||||
var rawSuperLucky string
|
||||
var rawPools string
|
||||
var rawAllowedOwners string
|
||||
if err := row.Scan(
|
||||
&config.AppCode,
|
||||
&enabled,
|
||||
@ -1758,6 +1779,7 @@ func scanHumanRoomRobotConfig(row humanRoomRobotConfigScanner) (roomservice.Huma
|
||||
&config.LuckyPauseMaxMS,
|
||||
&config.MaxGiftSenders,
|
||||
&rawPools,
|
||||
&rawAllowedOwners,
|
||||
&config.UpdatedByAdminID,
|
||||
&config.CreatedAtMS,
|
||||
&config.UpdatedAtMS,
|
||||
@ -1777,27 +1799,59 @@ func scanHumanRoomRobotConfig(row humanRoomRobotConfigScanner) (roomservice.Huma
|
||||
if err := json.Unmarshal([]byte(rawPools), &config.CountryPools); err != nil {
|
||||
return roomservice.HumanRoomRobotConfig{}, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(rawAllowedOwners), &config.AllowedOwnerIDs); err != nil {
|
||||
return roomservice.HumanRoomRobotConfig{}, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func humanRoomRobotConfigJSON(config roomservice.HumanRoomRobotConfig) ([]byte, []byte, []byte, []byte, error) {
|
||||
func humanRoomRobotConfigJSON(config roomservice.HumanRoomRobotConfig) ([]byte, []byte, []byte, []byte, []byte, error) {
|
||||
rawGifts, err := json.Marshal(config.GiftIDs)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
rawLucky, err := json.Marshal(config.LuckyGiftIDs)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
rawSuperLucky, err := json.Marshal(config.SuperLuckyGiftIDs)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
rawPools, err := json.Marshal(config.CountryPools)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
return rawGifts, rawLucky, rawSuperLucky, rawPools, nil
|
||||
rawAllowedOwners, err := json.Marshal(config.AllowedOwnerIDs)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
return rawGifts, rawLucky, rawSuperLucky, rawPools, rawAllowedOwners, nil
|
||||
}
|
||||
|
||||
func splitHumanRobotAllowedOwnerIDs(values []string) ([]string, []int64) {
|
||||
seenShortIDs := make(map[string]bool, len(values))
|
||||
seenUserIDs := make(map[int64]bool, len(values))
|
||||
shortIDs := make([]string, 0, len(values))
|
||||
userIDs := make([]int64, 0, len(values))
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || seenShortIDs[value] {
|
||||
continue
|
||||
}
|
||||
seenShortIDs[value] = true
|
||||
shortIDs = append(shortIDs, value)
|
||||
if strings.ContainsAny(value, ".eE") {
|
||||
continue
|
||||
}
|
||||
userID, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil || userID <= 0 || seenUserIDs[userID] {
|
||||
continue
|
||||
}
|
||||
seenUserIDs[userID] = true
|
||||
userIDs = append(userIDs, userID)
|
||||
}
|
||||
return shortIDs, userIDs
|
||||
}
|
||||
|
||||
type robotRoomScanner interface {
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSplitHumanRobotAllowedOwnerIDs(t *testing.T) {
|
||||
shortIDs, userIDs := splitHumanRobotAllowedOwnerIDs([]string{" 1001 ", "abc", "1001", "1e3", "-2", ""})
|
||||
if !reflect.DeepEqual(shortIDs, []string{"1001", "abc", "1e3", "-2"}) {
|
||||
t.Fatalf("short ids mismatch: got %+v", shortIDs)
|
||||
}
|
||||
if !reflect.DeepEqual(userIDs, []int64{1001}) {
|
||||
t.Fatalf("owner user ids mismatch: got %+v", userIDs)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user