2026-06-22 13:59:15 +08:00

650 lines
22 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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()
}