机器人房间相关

This commit is contained in:
zhx 2026-06-18 18:58:21 +08:00
parent 3b2e059964
commit 7883bb8217
12 changed files with 1890 additions and 1690 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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 {

View File

@ -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,

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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,

View File

@ -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',

View File

@ -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,

View File

@ -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}

View File

@ -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。

View File

@ -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 {

View File

@ -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)
}
}