650 lines
22 KiB
Go
650 lines
22 KiB
Go
// Package mysql 实现 room-service 的 MySQL 持久化边界。
|
||
package mysql
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"errors"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"hyapp/pkg/appcode"
|
||
"hyapp/pkg/xerr"
|
||
roomservice "hyapp/services/room-service/internal/room/service"
|
||
)
|
||
|
||
// ListRobotRooms 读取后台机器人房间列表;机器人房间配置归 room-service 所有,后台不直连房间库。
|
||
func (r *Repository) ListRobotRooms(ctx context.Context, query roomservice.RobotRoomListQuery) ([]roomservice.RobotRoomConfig, int64, error) {
|
||
appCode := normalizedRecordAppCode(ctx, query.AppCode)
|
||
page := query.Page
|
||
if page <= 0 {
|
||
page = 1
|
||
}
|
||
pageSize := query.PageSize
|
||
if pageSize <= 0 || pageSize > 100 {
|
||
pageSize = 50
|
||
}
|
||
status := strings.TrimSpace(query.Status)
|
||
args := []any{appCode}
|
||
where := `WHERE app_code = ?`
|
||
if status != "" {
|
||
where += ` AND status = ?`
|
||
args = append(args, status)
|
||
}
|
||
var total int64
|
||
if err := r.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM room_robot_rooms `+where, args...).Scan(&total); err != nil {
|
||
return nil, 0, err
|
||
}
|
||
args = append(args, pageSize, (page-1)*pageSize)
|
||
rows, err := r.db.QueryContext(ctx,
|
||
`SELECT app_code, room_id, room_short_id, title, cover_url, visible_region_id, status,
|
||
owner_robot_user_id, robot_user_ids_json, active_robot_count, seat_count, gift_ids_json, lucky_gift_ids_json,
|
||
normal_gift_interval_ms, lucky_combo_min, lucky_combo_max, lucky_pause_min_ms, lucky_pause_max_ms,
|
||
robot_stay_min_ms, robot_stay_max_ms, robot_replace_min_ms, robot_replace_max_ms, max_gift_senders,
|
||
created_by_admin_id, created_at_ms, updated_at_ms
|
||
FROM room_robot_rooms `+where+`
|
||
ORDER BY updated_at_ms DESC, room_id DESC
|
||
LIMIT ? OFFSET ?`,
|
||
args...,
|
||
)
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
defer rows.Close()
|
||
items, err := scanRobotRoomConfigs(rows)
|
||
return items, total, err
|
||
}
|
||
|
||
// GetRobotRoom 精确读取一个机器人房间配置。
|
||
func (r *Repository) GetRobotRoom(ctx context.Context, roomID string) (roomservice.RobotRoomConfig, bool, error) {
|
||
row := r.db.QueryRowContext(ctx,
|
||
`SELECT app_code, room_id, room_short_id, title, cover_url, visible_region_id, status,
|
||
owner_robot_user_id, robot_user_ids_json, active_robot_count, seat_count, gift_ids_json, lucky_gift_ids_json,
|
||
normal_gift_interval_ms, lucky_combo_min, lucky_combo_max, lucky_pause_min_ms, lucky_pause_max_ms,
|
||
robot_stay_min_ms, robot_stay_max_ms, robot_replace_min_ms, robot_replace_max_ms, max_gift_senders,
|
||
created_by_admin_id, created_at_ms, updated_at_ms
|
||
FROM room_robot_rooms
|
||
WHERE app_code = ? AND room_id = ?`,
|
||
appcode.FromContext(ctx),
|
||
strings.TrimSpace(roomID),
|
||
)
|
||
item, err := scanRobotRoomConfig(row)
|
||
if errors.Is(err, sql.ErrNoRows) {
|
||
return roomservice.RobotRoomConfig{}, false, nil
|
||
}
|
||
return item, err == nil, err
|
||
}
|
||
|
||
// CreateRobotRoomConfig 写入机器人房间配置,并用事务锁定候选账号避免两个 active 机器人房复用同一批账号。
|
||
func (r *Repository) CreateRobotRoomConfig(ctx context.Context, input roomservice.CreateRobotRoomConfigInput) (roomservice.RobotRoomConfig, error) {
|
||
config := input.Config
|
||
appCode := normalizedRecordAppCode(ctx, config.AppCode)
|
||
nowMS := input.NowMS
|
||
if nowMS <= 0 {
|
||
nowMS = time.Now().UTC().UnixMilli()
|
||
}
|
||
config.AppCode = appCode
|
||
config.CreatedAtMS = firstPositiveInt64(config.CreatedAtMS, nowMS)
|
||
config.UpdatedAtMS = firstPositiveInt64(config.UpdatedAtMS, nowMS)
|
||
if config.Status == "" {
|
||
config.Status = "active"
|
||
}
|
||
rawRobots, rawGifts, rawLuckyGifts, err := robotRoomJSON(config)
|
||
if err != nil {
|
||
return roomservice.RobotRoomConfig{}, err
|
||
}
|
||
tx, err := r.db.BeginTx(ctx, nil)
|
||
if err != nil {
|
||
return roomservice.RobotRoomConfig{}, err
|
||
}
|
||
defer func() { _ = tx.Rollback() }()
|
||
occupied, err := occupiedRobotUserIDsTx(ctx, tx, appCode, config.RobotUserIDs)
|
||
if err != nil {
|
||
return roomservice.RobotRoomConfig{}, err
|
||
}
|
||
if len(occupied) > 0 {
|
||
return roomservice.RobotRoomConfig{}, xerr.New(xerr.Conflict, "robot user already assigned to active robot room")
|
||
}
|
||
_, err = tx.ExecContext(ctx,
|
||
`INSERT INTO room_robot_rooms (
|
||
app_code, room_id, room_short_id, title, cover_url, visible_region_id, status,
|
||
owner_robot_user_id, robot_user_ids_json, active_robot_count, seat_count, gift_ids_json, lucky_gift_ids_json,
|
||
normal_gift_interval_ms, lucky_combo_min, lucky_combo_max, lucky_pause_min_ms, lucky_pause_max_ms,
|
||
robot_stay_min_ms, robot_stay_max_ms, robot_replace_min_ms, robot_replace_max_ms, max_gift_senders,
|
||
created_by_admin_id, created_at_ms, updated_at_ms
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
config.AppCode,
|
||
config.RoomID,
|
||
config.RoomShortID,
|
||
config.Title,
|
||
config.CoverURL,
|
||
config.VisibleRegionID,
|
||
config.Status,
|
||
config.OwnerRobotUserID,
|
||
string(rawRobots),
|
||
config.ActiveRobotCount,
|
||
config.SeatCount,
|
||
string(rawGifts),
|
||
string(rawLuckyGifts),
|
||
config.GiftRule.NormalGiftIntervalMS,
|
||
config.GiftRule.LuckyComboMin,
|
||
config.GiftRule.LuckyComboMax,
|
||
config.GiftRule.LuckyPauseMinMS,
|
||
config.GiftRule.LuckyPauseMaxMS,
|
||
config.GiftRule.RobotStayMinMS,
|
||
config.GiftRule.RobotStayMaxMS,
|
||
config.GiftRule.RobotReplaceMinMS,
|
||
config.GiftRule.RobotReplaceMaxMS,
|
||
config.GiftRule.MaxGiftSenders,
|
||
config.CreatedByAdminID,
|
||
config.CreatedAtMS,
|
||
config.UpdatedAtMS,
|
||
)
|
||
if err != nil {
|
||
return roomservice.RobotRoomConfig{}, err
|
||
}
|
||
if err := tx.Commit(); err != nil {
|
||
return roomservice.RobotRoomConfig{}, err
|
||
}
|
||
return config, nil
|
||
}
|
||
|
||
// UpdateRobotRoomStatus 只切换机器人房间运行态;停止后账号会从“已占用”集合释放。
|
||
func (r *Repository) UpdateRobotRoomStatus(ctx context.Context, roomID string, status string, nowMS int64) (roomservice.RobotRoomConfig, bool, error) {
|
||
status = strings.TrimSpace(status)
|
||
if nowMS <= 0 {
|
||
nowMS = time.Now().UTC().UnixMilli()
|
||
}
|
||
res, err := r.db.ExecContext(ctx,
|
||
`UPDATE room_robot_rooms
|
||
SET status = ?, updated_at_ms = ?
|
||
WHERE app_code = ? AND room_id = ?`,
|
||
status,
|
||
nowMS,
|
||
appcode.FromContext(ctx),
|
||
strings.TrimSpace(roomID),
|
||
)
|
||
if err != nil {
|
||
return roomservice.RobotRoomConfig{}, false, err
|
||
}
|
||
affected, _ := res.RowsAffected()
|
||
if affected == 0 {
|
||
return roomservice.RobotRoomConfig{}, false, nil
|
||
}
|
||
item, exists, err := r.GetRobotRoom(ctx, roomID)
|
||
return item, exists, err
|
||
}
|
||
|
||
// OccupiedRobotUserIDs 返回仍被 active 机器人房占用的机器人账号集合。
|
||
func (r *Repository) OccupiedRobotUserIDs(ctx context.Context, userIDs []int64) (map[int64]bool, error) {
|
||
return occupiedRobotUserIDsQuery(ctx, r.db, appcode.FromContext(ctx), userIDs, false)
|
||
}
|
||
|
||
// ListActiveRobotRooms 为 room-service 重启恢复运行时循环提供当前 active 配置。
|
||
func (r *Repository) ListActiveRobotRooms(ctx context.Context) ([]roomservice.RobotRoomConfig, error) {
|
||
rows, err := r.db.QueryContext(ctx,
|
||
`SELECT app_code, room_id, room_short_id, title, cover_url, visible_region_id, status,
|
||
owner_robot_user_id, robot_user_ids_json, active_robot_count, seat_count, gift_ids_json, lucky_gift_ids_json,
|
||
normal_gift_interval_ms, lucky_combo_min, lucky_combo_max, lucky_pause_min_ms, lucky_pause_max_ms,
|
||
robot_stay_min_ms, robot_stay_max_ms, robot_replace_min_ms, robot_replace_max_ms, max_gift_senders,
|
||
created_by_admin_id, created_at_ms, updated_at_ms
|
||
FROM room_robot_rooms
|
||
WHERE status = 'active'
|
||
ORDER BY app_code ASC, updated_at_ms DESC, room_id DESC`,
|
||
)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
return scanRobotRoomConfigs(rows)
|
||
}
|
||
|
||
// GetHumanRoomRobotConfig 读取真人房间机器人行为配置;没有配置时由 service 层补默认值。
|
||
func (r *Repository) GetHumanRoomRobotConfig(ctx context.Context) (roomservice.HumanRoomRobotConfig, bool, error) {
|
||
row := r.db.QueryRowContext(ctx,
|
||
`SELECT app_code, enabled, candidate_room_max_online, room_full_stop_online, room_target_min_online, room_target_max_online,
|
||
robot_stay_min_ms, robot_stay_max_ms, robot_replace_min_ms, robot_replace_max_ms,
|
||
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, COALESCE(allowed_owner_ids_json, JSON_ARRAY()),
|
||
country_limit_enabled, COALESCE(country_rules_json, JSON_ARRAY()),
|
||
updated_by_admin_id, created_at_ms, updated_at_ms
|
||
FROM room_human_robot_configs
|
||
WHERE app_code = ?`,
|
||
appcode.FromContext(ctx),
|
||
)
|
||
config, err := scanHumanRoomRobotConfig(row)
|
||
if errors.Is(err, sql.ErrNoRows) {
|
||
return roomservice.HumanRoomRobotConfig{}, false, nil
|
||
}
|
||
return config, err == nil, err
|
||
}
|
||
|
||
// UpsertHumanRoomRobotConfig 保存真人房间机器人行为配置;运行时扫描每轮读取最新配置。
|
||
func (r *Repository) UpsertHumanRoomRobotConfig(ctx context.Context, config roomservice.HumanRoomRobotConfig) (roomservice.HumanRoomRobotConfig, error) {
|
||
appCode := normalizedRecordAppCode(ctx, config.AppCode)
|
||
nowMS := config.UpdatedAtMS
|
||
if nowMS <= 0 {
|
||
nowMS = time.Now().UTC().UnixMilli()
|
||
}
|
||
config.AppCode = appCode
|
||
config.CreatedAtMS = firstPositiveInt64(config.CreatedAtMS, nowMS)
|
||
config.UpdatedAtMS = nowMS
|
||
rawGifts, rawLucky, rawSuperLucky, rawPools, rawAllowedOwners, rawCountryRules, err := humanRoomRobotConfigJSON(config)
|
||
if err != nil {
|
||
return roomservice.HumanRoomRobotConfig{}, err
|
||
}
|
||
_, err = r.db.ExecContext(ctx,
|
||
`INSERT INTO room_human_robot_configs (
|
||
app_code, enabled, candidate_room_max_online, room_full_stop_online, room_target_min_online, room_target_max_online,
|
||
robot_stay_min_ms, robot_stay_max_ms, robot_replace_min_ms, robot_replace_max_ms,
|
||
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, allowed_owner_ids_json, country_limit_enabled, country_rules_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),
|
||
room_full_stop_online = VALUES(room_full_stop_online),
|
||
room_target_min_online = VALUES(room_target_min_online),
|
||
room_target_max_online = VALUES(room_target_max_online),
|
||
robot_stay_min_ms = VALUES(robot_stay_min_ms),
|
||
robot_stay_max_ms = VALUES(robot_stay_max_ms),
|
||
robot_replace_min_ms = VALUES(robot_replace_min_ms),
|
||
robot_replace_max_ms = VALUES(robot_replace_max_ms),
|
||
normal_gift_interval_ms = VALUES(normal_gift_interval_ms),
|
||
normal_gift_interval_min_ms = VALUES(normal_gift_interval_min_ms),
|
||
normal_gift_interval_max_ms = VALUES(normal_gift_interval_max_ms),
|
||
gift_ids_json = VALUES(gift_ids_json),
|
||
lucky_gift_ids_json = VALUES(lucky_gift_ids_json),
|
||
super_lucky_gift_ids_json = VALUES(super_lucky_gift_ids_json),
|
||
lucky_combo_min = VALUES(lucky_combo_min),
|
||
lucky_combo_max = VALUES(lucky_combo_max),
|
||
lucky_pause_min_ms = VALUES(lucky_pause_min_ms),
|
||
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),
|
||
country_limit_enabled = VALUES(country_limit_enabled),
|
||
country_rules_json = VALUES(country_rules_json),
|
||
updated_by_admin_id = VALUES(updated_by_admin_id),
|
||
updated_at_ms = VALUES(updated_at_ms)`,
|
||
config.AppCode,
|
||
boolToInt(config.Enabled),
|
||
config.CandidateRoomMaxOnline,
|
||
config.RoomFullStopOnline,
|
||
config.RoomTargetMinOnline,
|
||
config.RoomTargetMaxOnline,
|
||
config.RobotStayMinMS,
|
||
config.RobotStayMaxMS,
|
||
config.RobotReplaceMinMS,
|
||
config.RobotReplaceMaxMS,
|
||
config.NormalGiftIntervalMS,
|
||
config.NormalGiftIntervalMinMS,
|
||
config.NormalGiftIntervalMaxMS,
|
||
string(rawGifts),
|
||
string(rawLucky),
|
||
string(rawSuperLucky),
|
||
config.LuckyComboMin,
|
||
config.LuckyComboMax,
|
||
config.LuckyPauseMinMS,
|
||
config.LuckyPauseMaxMS,
|
||
config.MaxGiftSenders,
|
||
string(rawPools),
|
||
string(rawAllowedOwners),
|
||
boolToInt(config.CountryLimitEnabled),
|
||
string(rawCountryRules),
|
||
config.UpdatedByAdminID,
|
||
config.CreatedAtMS,
|
||
config.UpdatedAtMS,
|
||
)
|
||
if err != nil {
|
||
return roomservice.HumanRoomRobotConfig{}, err
|
||
}
|
||
saved, _, err := r.GetHumanRoomRobotConfig(ctx)
|
||
return saved, err
|
||
}
|
||
|
||
// 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 {
|
||
maxOnline = 5
|
||
}
|
||
if fullStopOnline <= 0 {
|
||
fullStopOnline = 10
|
||
}
|
||
if maxOnline > fullStopOnline {
|
||
maxOnline = fullStopOnline
|
||
}
|
||
if limit <= 0 || limit > 100 {
|
||
limit = 50
|
||
}
|
||
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 ` + strings.Join(where, "\n AND ") + `
|
||
ORDER BY online_count ASC, updated_at_ms DESC, room_id DESC
|
||
LIMIT ?`
|
||
rows, err := r.db.QueryContext(ctx, query, args...)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
items := make([]roomservice.RoomListEntry, 0, limit)
|
||
for rows.Next() {
|
||
var entry roomservice.RoomListEntry
|
||
if err := rows.Scan(
|
||
&entry.AppCode,
|
||
&entry.RoomID,
|
||
&entry.RoomShortID,
|
||
&entry.VisibleRegionID,
|
||
&entry.OwnerCountryCode,
|
||
&entry.OwnerUserID,
|
||
&entry.Title,
|
||
&entry.CoverURL,
|
||
&entry.Mode,
|
||
&entry.Status,
|
||
&entry.Locked,
|
||
&entry.Heat,
|
||
&entry.OnlineCount,
|
||
&entry.SeatCount,
|
||
&entry.OccupiedSeatCount,
|
||
&entry.SortScore,
|
||
&entry.CreatedAtMS,
|
||
&entry.UpdatedAtMS,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, entry)
|
||
}
|
||
return items, rows.Err()
|
||
}
|
||
|
||
type humanRoomRobotConfigScanner interface {
|
||
Scan(dest ...any) error
|
||
}
|
||
|
||
func scanHumanRoomRobotConfig(row humanRoomRobotConfigScanner) (roomservice.HumanRoomRobotConfig, error) {
|
||
var config roomservice.HumanRoomRobotConfig
|
||
var enabled int
|
||
var rawGifts string
|
||
var rawLucky string
|
||
var rawSuperLucky string
|
||
var rawPools string
|
||
var rawAllowedOwners string
|
||
var countryLimitEnabled int
|
||
var rawCountryRules string
|
||
if err := row.Scan(
|
||
&config.AppCode,
|
||
&enabled,
|
||
&config.CandidateRoomMaxOnline,
|
||
&config.RoomFullStopOnline,
|
||
&config.RoomTargetMinOnline,
|
||
&config.RoomTargetMaxOnline,
|
||
&config.RobotStayMinMS,
|
||
&config.RobotStayMaxMS,
|
||
&config.RobotReplaceMinMS,
|
||
&config.RobotReplaceMaxMS,
|
||
&config.NormalGiftIntervalMS,
|
||
&config.NormalGiftIntervalMinMS,
|
||
&config.NormalGiftIntervalMaxMS,
|
||
&rawGifts,
|
||
&rawLucky,
|
||
&rawSuperLucky,
|
||
&config.LuckyComboMin,
|
||
&config.LuckyComboMax,
|
||
&config.LuckyPauseMinMS,
|
||
&config.LuckyPauseMaxMS,
|
||
&config.MaxGiftSenders,
|
||
&rawPools,
|
||
&rawAllowedOwners,
|
||
&countryLimitEnabled,
|
||
&rawCountryRules,
|
||
&config.UpdatedByAdminID,
|
||
&config.CreatedAtMS,
|
||
&config.UpdatedAtMS,
|
||
); err != nil {
|
||
return roomservice.HumanRoomRobotConfig{}, err
|
||
}
|
||
config.Enabled = enabled == 1
|
||
if err := json.Unmarshal([]byte(rawGifts), &config.GiftIDs); err != nil {
|
||
return roomservice.HumanRoomRobotConfig{}, err
|
||
}
|
||
if err := json.Unmarshal([]byte(rawLucky), &config.LuckyGiftIDs); err != nil {
|
||
return roomservice.HumanRoomRobotConfig{}, err
|
||
}
|
||
if err := json.Unmarshal([]byte(rawSuperLucky), &config.SuperLuckyGiftIDs); err != nil {
|
||
return roomservice.HumanRoomRobotConfig{}, err
|
||
}
|
||
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
|
||
}
|
||
config.CountryLimitEnabled = countryLimitEnabled == 1
|
||
if err := json.Unmarshal([]byte(rawCountryRules), &config.CountryRules); err != nil {
|
||
return roomservice.HumanRoomRobotConfig{}, err
|
||
}
|
||
return config, nil
|
||
}
|
||
|
||
func humanRoomRobotConfigJSON(config roomservice.HumanRoomRobotConfig) ([]byte, []byte, []byte, []byte, []byte, []byte, error) {
|
||
rawGifts, err := json.Marshal(config.GiftIDs)
|
||
if err != nil {
|
||
return nil, nil, nil, nil, nil, nil, err
|
||
}
|
||
rawLucky, err := json.Marshal(config.LuckyGiftIDs)
|
||
if err != nil {
|
||
return nil, nil, nil, nil, nil, nil, err
|
||
}
|
||
rawSuperLucky, err := json.Marshal(config.SuperLuckyGiftIDs)
|
||
if err != nil {
|
||
return nil, nil, nil, nil, nil, nil, err
|
||
}
|
||
rawPools, err := json.Marshal(config.CountryPools)
|
||
if err != nil {
|
||
return nil, nil, nil, nil, nil, nil, err
|
||
}
|
||
rawAllowedOwners, err := json.Marshal(config.AllowedOwnerIDs)
|
||
if err != nil {
|
||
return nil, nil, nil, nil, nil, nil, err
|
||
}
|
||
rawCountryRules, err := json.Marshal(config.CountryRules)
|
||
if err != nil {
|
||
return nil, nil, nil, nil, nil, nil, err
|
||
}
|
||
return rawGifts, rawLucky, rawSuperLucky, rawPools, rawAllowedOwners, rawCountryRules, 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 {
|
||
Scan(dest ...any) error
|
||
}
|
||
|
||
func scanRobotRoomConfigs(rows *sql.Rows) ([]roomservice.RobotRoomConfig, error) {
|
||
items := make([]roomservice.RobotRoomConfig, 0)
|
||
for rows.Next() {
|
||
item, err := scanRobotRoomConfig(rows)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, item)
|
||
}
|
||
return items, rows.Err()
|
||
}
|
||
|
||
func scanRobotRoomConfig(row robotRoomScanner) (roomservice.RobotRoomConfig, error) {
|
||
var item roomservice.RobotRoomConfig
|
||
var rawRobots string
|
||
var rawGifts string
|
||
var rawLuckyGifts string
|
||
if err := row.Scan(
|
||
&item.AppCode,
|
||
&item.RoomID,
|
||
&item.RoomShortID,
|
||
&item.Title,
|
||
&item.CoverURL,
|
||
&item.VisibleRegionID,
|
||
&item.Status,
|
||
&item.OwnerRobotUserID,
|
||
&rawRobots,
|
||
&item.ActiveRobotCount,
|
||
&item.SeatCount,
|
||
&rawGifts,
|
||
&rawLuckyGifts,
|
||
&item.GiftRule.NormalGiftIntervalMS,
|
||
&item.GiftRule.LuckyComboMin,
|
||
&item.GiftRule.LuckyComboMax,
|
||
&item.GiftRule.LuckyPauseMinMS,
|
||
&item.GiftRule.LuckyPauseMaxMS,
|
||
&item.GiftRule.RobotStayMinMS,
|
||
&item.GiftRule.RobotStayMaxMS,
|
||
&item.GiftRule.RobotReplaceMinMS,
|
||
&item.GiftRule.RobotReplaceMaxMS,
|
||
&item.GiftRule.MaxGiftSenders,
|
||
&item.CreatedByAdminID,
|
||
&item.CreatedAtMS,
|
||
&item.UpdatedAtMS,
|
||
); err != nil {
|
||
return roomservice.RobotRoomConfig{}, err
|
||
}
|
||
if err := json.Unmarshal([]byte(rawRobots), &item.RobotUserIDs); err != nil {
|
||
return roomservice.RobotRoomConfig{}, err
|
||
}
|
||
if err := json.Unmarshal([]byte(rawGifts), &item.GiftRule.GiftIDs); err != nil {
|
||
return roomservice.RobotRoomConfig{}, err
|
||
}
|
||
if err := json.Unmarshal([]byte(rawLuckyGifts), &item.GiftRule.LuckyGiftIDs); err != nil {
|
||
return roomservice.RobotRoomConfig{}, err
|
||
}
|
||
if item.ActiveRobotCount <= 0 {
|
||
item.ActiveRobotCount = int32(len(item.RobotUserIDs))
|
||
}
|
||
if item.SeatCount <= 0 {
|
||
item.SeatCount = 10
|
||
}
|
||
item.GiftRule = roomservice.WithRobotRoomGiftRuleStorageDefaults(item.GiftRule)
|
||
return item, nil
|
||
}
|
||
|
||
func robotRoomJSON(config roomservice.RobotRoomConfig) ([]byte, []byte, []byte, error) {
|
||
rawRobots, err := json.Marshal(config.RobotUserIDs)
|
||
if err != nil {
|
||
return nil, nil, nil, err
|
||
}
|
||
rawGifts, err := json.Marshal(config.GiftRule.GiftIDs)
|
||
if err != nil {
|
||
return nil, nil, nil, err
|
||
}
|
||
rawLuckyGifts, err := json.Marshal(config.GiftRule.LuckyGiftIDs)
|
||
if err != nil {
|
||
return nil, nil, nil, err
|
||
}
|
||
return rawRobots, rawGifts, rawLuckyGifts, nil
|
||
}
|
||
|
||
func occupiedRobotUserIDsTx(ctx context.Context, tx *sql.Tx, appCode string, userIDs []int64) (map[int64]bool, error) {
|
||
return occupiedRobotUserIDsQuery(ctx, tx, appCode, userIDs, true)
|
||
}
|
||
|
||
type dbQuerier interface {
|
||
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
|
||
}
|
||
|
||
func occupiedRobotUserIDsQuery(ctx context.Context, db dbQuerier, appCode string, userIDs []int64, lock bool) (map[int64]bool, error) {
|
||
ids := uniquePositiveInt64(userIDs)
|
||
occupied := make(map[int64]bool, len(ids))
|
||
if len(ids) == 0 {
|
||
return occupied, nil
|
||
}
|
||
query := `SELECT robot_user_ids_json FROM room_robot_rooms WHERE app_code = ? AND status = 'active'`
|
||
if lock {
|
||
query += ` FOR UPDATE`
|
||
}
|
||
rows, err := db.QueryContext(ctx, query, appcode.Normalize(appCode))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
wanted := make(map[int64]bool, len(ids))
|
||
for _, id := range ids {
|
||
wanted[id] = true
|
||
}
|
||
for rows.Next() {
|
||
var raw string
|
||
if err := rows.Scan(&raw); err != nil {
|
||
return nil, err
|
||
}
|
||
var roomRobotIDs []int64
|
||
if err := json.Unmarshal([]byte(raw), &roomRobotIDs); err != nil {
|
||
return nil, err
|
||
}
|
||
for _, id := range roomRobotIDs {
|
||
if wanted[id] {
|
||
occupied[id] = true
|
||
}
|
||
}
|
||
}
|
||
return occupied, rows.Err()
|
||
}
|