package roomadmin import ( "encoding/json" "fmt" "strconv" "strings" ) type listQuery struct { Page int PageSize int Keyword string OwnerUserID int64 OwnerUserKeyword string Status string RegionID int64 SortBy string SortDirection string } type roomPinListQuery struct { Keyword string Page int PageSize int PinType string RegionID int64 Status string } type robotRoomListQuery struct { Page int PageSize int Status string } type createRobotRoomRequest struct { OwnerRobotUserID flexibleJSONInt64 `json:"ownerRobotUserId"` CandidateRobotUserIDs flexibleJSONInt64Slice `json:"candidateRobotUserIds"` MinRobotCount int32 `json:"minRobotCount"` MaxRobotCount int32 `json:"maxRobotCount"` RoomName string `json:"roomName"` RoomAvatar string `json:"roomAvatar"` VisibleRegionID int64 `json:"visibleRegionId"` OwnerCountryCode string `json:"-"` SeatCount int32 `json:"seatCount"` GiftIDs []string `json:"giftIds"` LuckyGiftIDs []string `json:"luckyGiftIds"` NormalGiftIntervalMS int64 `json:"normalGiftIntervalMs"` LuckyComboMin int64 `json:"luckyComboMin"` LuckyComboMax int64 `json:"luckyComboMax"` LuckyPauseMinMS int64 `json:"luckyPauseMinMs"` LuckyPauseMaxMS int64 `json:"luckyPauseMaxMs"` RobotStayMinMS int64 `json:"robotStayMinMs"` RobotStayMaxMS int64 `json:"robotStayMaxMs"` RobotReplaceMinMS int64 `json:"robotReplaceMinMs"` RobotReplaceMaxMS int64 `json:"robotReplaceMaxMs"` MaxGiftSenders int64 `json:"maxGiftSenders"` } type flexibleJSONInt64 int64 type flexibleJSONInt64Slice []int64 func (v *flexibleJSONInt64) UnmarshalJSON(data []byte) error { // 机器人用户 ID 来自 int64,前端必须按字符串提交才能避开 JS 安全整数上限;这里同时兼容旧脚本传数字。 value, err := parseFlexibleJSONInt64(data) if err != nil { return err } *v = flexibleJSONInt64(value) return nil } func (values *flexibleJSONInt64Slice) UnmarshalJSON(data []byte) error { // JSON 数组逐项走同一套 int64 解析,避免候选机器人列表里混入浮点或科学计数法。 var raw []json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { return err } out := make([]int64, 0, len(raw)) for _, item := range raw { value, err := parseFlexibleJSONInt64(item) if err != nil { return err } out = append(out, value) } *values = out return nil } func parseFlexibleJSONInt64(data []byte) (int64, error) { raw := strings.TrimSpace(string(data)) if raw == "" || raw == "null" { return 0, nil } if strings.HasPrefix(raw, "\"") { var text string if err := json.Unmarshal(data, &text); err != nil { return 0, err } raw = strings.TrimSpace(text) } if raw == "" { return 0, nil } if strings.ContainsAny(raw, ".eE") { // ID 没有小数语义,拒绝 float/scientific notation,避免 18 位 ID 被中间层改写精度。 return 0, fmt.Errorf("integer id must not be decimal") } return strconv.ParseInt(raw, 10, 64) } type createRoomPinRequest struct { DurationDays int64 `json:"durationDays"` ExpiresAtMS int64 `json:"expiresAtMs"` PinType string `json:"pinType"` PinnedAtMS int64 `json:"pinnedAtMs"` RoomID string `json:"roomId"` Weight int64 `json:"weight"` } type updateRoomRequest struct { CoverURL *string `json:"coverUrl"` CloseReason *string `json:"closeReason"` Description *string `json:"description"` Mode *string `json:"mode"` Status *string `json:"status"` Title *string `json:"title"` VisibleRegionID *int64 `json:"visibleRegionId"` } type updateRoomConfigRequest struct { AllowedSeatCounts []int32 `json:"allowedSeatCounts"` DefaultSeatCount int32 `json:"defaultSeatCount"` } type updateRoomWhitelistRequest struct { UserIDs flexibleJSONInt64StringSlice `json:"userIds"` } 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"` AllowedOwnerIDs flexibleCommaStringList `json:"allowedOwnerIds"` CountryLimitEnabled bool `json:"countryLimitEnabled"` CountryRules []HumanRoomRobotCountryRule `json:"countryRules"` } type flexibleJSONInt64StringSlice []string func (values *flexibleJSONInt64StringSlice) UnmarshalJSON(data []byte) error { // 房间白名单 user_id 是 int64 标识,API 合约统一按字符串输出;这里兼容旧脚本传数字但拒绝小数。 var raw []json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { return err } out := make([]string, 0, len(raw)) for _, item := range raw { value, err := parseFlexibleJSONInt64(item) if err != nil { return err } if value > 0 { out = append(out, strconv.FormatInt(value, 10)) } } *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 }