package resource import ( "crypto/sha256" "encoding/hex" "encoding/json" "hyapp/services/wallet-service/internal/domain/ledger" "sort" "strings" ) type Resource struct { AppCode string ResourceID int64 ResourceCode string ResourceType string Name string Status string Grantable bool GrantStrategy string WalletAssetType string WalletAssetAmount int64 PriceType string CoinPrice int64 // GiftPointAmount 是历史字段;GIFT_POINT 已下线,新资源和礼物配置只保留真实 COIN 价格。 GiftPointAmount int64 UsageScopes []string AssetURL string PreviewURL string AnimationURL string MetadataJSON string SortOrder int32 CreatedByUserID int64 UpdatedByUserID int64 CreatedAtMS int64 UpdatedAtMS int64 ManagerGrantEnabled bool } type ListResourcesQuery struct { AppCode string ResourceType string Status string Keyword string Page int32 PageSize int32 ActiveOnly bool ManagerGrantOnly bool } type ResourceCommand struct { AppCode string ResourceID int64 ResourceCode string ResourceType string Name string Status string Grantable bool GrantStrategy string WalletAssetType string WalletAssetAmount int64 PriceType string CoinPrice int64 // GiftPointAmount 是历史字段;保存资源时固定为 0,保留字段只兼容旧表结构。 GiftPointAmount int64 UsageScopes []string AssetURL string PreviewURL string AnimationURL string MetadataJSON string SortOrder int32 OperatorUserID int64 ManagerGrantEnabled bool } type StatusCommand struct { AppCode string ID int64 StringID string Status string OperatorUserID int64 } type BatchDeleteResourcesCommand struct { AppCode string ResourceIDs []int64 OperatorUserID int64 } // CanonicalResourceJSON is the immutable entitlement projection. Usage scopes are set-like owner data, // therefore sorting them prevents an order-only edit from creating a different grant semantic. func CanonicalResourceJSON(resource Resource) ([]byte, error) { canonical := resource canonical.UsageScopes = append([]string(nil), resource.UsageScopes...) sort.Strings(canonical.UsageScopes) return json.Marshal(canonical) } func ResourceContentHash(resource Resource) (string, error) { body, err := CanonicalResourceJSON(resource) if err != nil { return "", err } sum := sha256.Sum256(body) return hex.EncodeToString(sum[:]), nil } func NormalizeStatus(value string) string { value = strings.ToLower(strings.TrimSpace(value)) if value == "" { return StatusActive } return value } func NormalizeGrantSource(value string) string { value = strings.ToLower(strings.TrimSpace(value)) if value == "" { return GrantSourceAdmin } return value } func ValidResourceType(value string) bool { switch strings.ToLower(strings.TrimSpace(value)) { case TypeAvatarFrame, TypeProfileCard, TypeCoin, TypeVehicle, TypeChatBubble, TypeBadge, TypeFloatingScreen, TypeGift, TypeMicSeatIcon, TypeMicSeatAnimation, TypeEmojiPack, TypeVIPTrialCard: return true default: return false } } func NormalizeResourceType(value string) string { return strings.ToLower(strings.TrimSpace(value)) } func NormalizeGrantStrategy(value string) string { return strings.ToLower(strings.TrimSpace(value)) } func NormalizeWalletAssetType(value string) string { return strings.ToUpper(strings.TrimSpace(value)) } func IsWalletCredit(resource Resource) bool { return NormalizeGrantStrategy(resource.GrantStrategy) == GrantStrategyWalletCredit } func IsCoinResource(resource Resource) bool { return NormalizeResourceType(resource.ResourceType) == TypeCoin } func WalletAssetForCoin() string { return ledger.AssetCoin }