From c1a0049b1d3c4cba7240ea452b62ca32b6fad6fa Mon Sep 17 00:00:00 2001 From: hy001 Date: Sat, 16 May 2026 02:32:16 +0800 Subject: [PATCH] fix: split badges by display type --- internal/model/badge_models.go | 20 ++++---- internal/service/managercenter/vip_gift.go | 4 ++ internal/service/userbadge/current_test.go | 60 +++++++++++++++++++--- internal/service/userbadge/types.go | 32 ++++++++++-- migrations/043_sys_badge_display_type.sql | 22 ++++++++ 5 files changed, 115 insertions(+), 23 deletions(-) create mode 100644 migrations/043_sys_badge_display_type.sql diff --git a/internal/model/badge_models.go b/internal/model/badge_models.go index 49e26ea..c0c51d3 100644 --- a/internal/model/badge_models.go +++ b/internal/model/badge_models.go @@ -19,16 +19,16 @@ func (UserBadgeBackpack) TableName() string { return "user_badge_backpack" } // SysBadgeConfig stores badge metadata from the Java badge system. type SysBadgeConfig struct { - ID int64 `gorm:"column:id;primaryKey"` - Type string `gorm:"column:type;size:64"` - DisplayType string `gorm:"column:display_type;size:16"` - BadgeLevel int `gorm:"column:badge_level"` - Milestone int64 `gorm:"column:milestone"` - BadgeName string `gorm:"column:badge_name;size:128"` - BadgeKey string `gorm:"column:badge_key;size:128"` - Del bool `gorm:"column:is_del"` - CreateTime time.Time `gorm:"column:create_time"` - UpdateTime time.Time `gorm:"column:update_time"` + ID int64 `gorm:"column:id;primaryKey"` + Type string `gorm:"column:type;size:64"` + DisplayType string `gorm:"column:display_type;size:16"` + BadgeLevel int `gorm:"column:badge_level"` + Milestone int64 `gorm:"column:milestone"` + BadgeName string `gorm:"column:badge_name;size:128"` + BadgeKey string `gorm:"column:badge_key;size:128"` + Del bool `gorm:"column:is_del"` + CreateTime time.Time `gorm:"column:create_time"` + UpdateTime time.Time `gorm:"column:update_time"` } // TableName returns the badge config table name. diff --git a/internal/service/managercenter/vip_gift.go b/internal/service/managercenter/vip_gift.go index 07a42a0..bd100a7 100644 --- a/internal/service/managercenter/vip_gift.go +++ b/internal/service/managercenter/vip_gift.go @@ -279,6 +279,9 @@ func applyVIPGiftConfigToState(state *model.UserVipState, config model.VipLevelC state.BadgeResourceID = config.BadgeResourceID state.BadgeName = config.BadgeName state.BadgeURL = config.BadgeURL + state.ShortBadgeResourceID = config.ShortBadgeResourceID + state.ShortBadgeName = config.ShortBadgeName + state.ShortBadgeURL = config.ShortBadgeURL state.AvatarFrameResourceID = config.AvatarFrameResourceID state.AvatarFrameName = config.AvatarFrameName state.AvatarFrameURL = config.AvatarFrameURL @@ -365,6 +368,7 @@ func (s *Service) alignVIPGiftResourceExpireAt(ctx context.Context, userID int64 func vipGiftResourcesFromConfig(config model.VipLevelConfig) []vipGiftResource { resources := []vipGiftResource{ {resourceID: config.BadgeResourceID, propsType: propsTypeBadge, badge: true}, + {resourceID: config.ShortBadgeResourceID, propsType: propsTypeBadge, badge: true}, {resourceID: config.AvatarFrameResourceID, propsType: propsTypeAvatarFrame}, {resourceID: config.EntryEffectResourceID, propsType: propsTypeRide}, {resourceID: config.ChatBubbleResourceID, propsType: propsTypeChatBubble}, diff --git a/internal/service/userbadge/current_test.go b/internal/service/userbadge/current_test.go index f449ab4..eacf4d0 100644 --- a/internal/service/userbadge/current_test.go +++ b/internal/service/userbadge/current_test.go @@ -78,7 +78,7 @@ func TestCurrentBadgesSplitLongAndShort(t *testing.T) { t.Fatalf("badges len = %d, want 3", len(resp.Badges)) } long := resp.LongBadges[0] - if long.Type != badgeDisplayTypeLong || long.SourceType != badgeSourceTypeVIP || long.BadgeID != "101" || long.LevelCode != "VIP2" { + if long.Type != badgeDisplayTypeLong || long.SourceType != badgeSourceTypeActivity || long.BadgeID != "101" || long.LevelCode != "VIP2" { t.Fatalf("long badge = %#v", long) } if long.Name != "VIP Badge" || long.URL != "https://cdn.example.com/101.png" { @@ -92,6 +92,44 @@ func TestCurrentBadgesSplitLongAndShort(t *testing.T) { } } +func TestCurrentBadgesUsesConfiguredDisplayType(t *testing.T) { + db := newUserBadgeTestDB(t) + now := time.Date(2026, 5, 15, 12, 0, 0, 0, time.UTC) + userID := int64(42) + seedBadgeConfigWithDisplay(t, db, 301, badgeSourceTypeActivity, badgeDisplayTypeLong, "Activity Long", "ACTIVITY_LONG", 1) + seedBadgeConfigWithDisplay(t, db, 302, badgeSourceTypeVIP, badgeDisplayTypeShort, "VIP Short", "VIP_SHORT", 1) + for _, badgeID := range []int64{301, 302} { + if err := db.Create(&model.SysBadgePictureConfig{ + ID: badgeID + 1000, + BadgeConfigID: badgeID, + SysOrigin: "LIKEI", + SelectURL: "https://cdn.example.com/" + formatID(badgeID) + ".png", + }).Error; err != nil { + t.Fatalf("create picture %d: %v", badgeID, err) + } + } + backpacks := []model.UserBadgeBackpack{ + {ID: 4001, UserID: userID, BadgeID: 301, ExpireType: "TEMPORARY", ExpireTime: now.Add(2 * time.Hour), UseProps: true, UpdateTime: now.Add(time.Minute)}, + {ID: 4002, UserID: userID, BadgeID: 302, ExpireType: "TEMPORARY", ExpireTime: now.Add(2 * time.Hour), UseProps: true, UpdateTime: now}, + } + if err := db.Create(&backpacks).Error; err != nil { + t.Fatalf("create backpacks: %v", err) + } + + service := NewService(db) + service.now = func() time.Time { return now } + resp, err := service.Current(context.Background(), common.AuthUser{UserID: userID, SysOrigin: "LIKEI"}) + if err != nil { + t.Fatalf("Current() error = %v", err) + } + if len(resp.LongBadges) != 1 || resp.LongBadges[0].BadgeID != "301" || resp.LongBadges[0].Type != badgeDisplayTypeLong { + t.Fatalf("long badges = %#v, want configured activity long badge", resp.LongBadges) + } + if len(resp.ShortBadges) != 1 || resp.ShortBadges[0].BadgeID != "302" || resp.ShortBadges[0].Type != badgeDisplayTypeShort { + t.Fatalf("short badges = %#v, want configured vip short badge", resp.ShortBadges) + } +} + func TestCurrentBadgesRejectsInvalidUser(t *testing.T) { service := NewService(newUserBadgeTestDB(t)) _, err := service.Current(context.Background(), common.AuthUser{SysOrigin: "LIKEI"}) @@ -173,15 +211,21 @@ func newUserBadgeTestDB(t *testing.T) *gorm.DB { } func seedBadgeConfig(t *testing.T, db *gorm.DB, id int64, sourceType string, name string, key string, level int) { + t.Helper() + seedBadgeConfigWithDisplay(t, db, id, sourceType, "", name, key, level) +} + +func seedBadgeConfigWithDisplay(t *testing.T, db *gorm.DB, id int64, sourceType string, displayType string, name string, key string, level int) { t.Helper() if err := db.Create(&model.SysBadgeConfig{ - ID: id, - Type: sourceType, - BadgeName: name, - BadgeKey: key, - BadgeLevel: level, - Milestone: id * 10, - Del: false, + ID: id, + Type: sourceType, + DisplayType: displayType, + BadgeName: name, + BadgeKey: key, + BadgeLevel: level, + Milestone: id * 10, + Del: false, }).Error; err != nil { t.Fatalf("create badge config %d: %v", id, err) } diff --git a/internal/service/userbadge/types.go b/internal/service/userbadge/types.go index d239316..bc2857b 100644 --- a/internal/service/userbadge/types.go +++ b/internal/service/userbadge/types.go @@ -16,9 +16,11 @@ const ( badgeDisplayTypeLong = "LONG" badgeDisplayTypeShort = "SHORT" - badgeSourceTypeVIP = "VIP" - badgeSourceTypeActivity = "ACTIVITY" - badgeSourceTypeAchievement = "ACHIEVEMENT" + badgeSourceTypeVIP = "VIP" + badgeSourceTypeActivity = "ACTIVITY" + badgeSourceTypeAchievement = "ACHIEVEMENT" + badgeSourceTypeHonorAdmin = "HONOR_ADMIN" + badgeSourceTypeHonorActivity = "HONOR_ACTIVITY" expireTypePermanent = "PERMANENT" statusActive = "ACTIVE" @@ -56,8 +58,7 @@ type CurrentBadgeListResponse struct { ShortBadges []CurrentBadgeItem `json:"shortBadges"` } -// CurrentBadgeItem is one current badge. Type is LONG for VIP badges and SHORT -// for activity or achievement badges. +// CurrentBadgeItem is one current badge. Type is LONG or SHORT by configured display type. type CurrentBadgeItem struct { ID string `json:"id"` BadgeID string `json:"badgeId"` @@ -144,3 +145,24 @@ func firstNonEmpty(values ...string) string { } return "" } + +func normalizeBadgeDisplayType(displayType string, sourceType string, fallback string) string { + normalized := strings.ToUpper(strings.TrimSpace(displayType)) + if normalized == badgeDisplayTypeLong || normalized == badgeDisplayTypeShort { + return normalized + } + + normalizedFallback := strings.ToUpper(strings.TrimSpace(fallback)) + if normalizedFallback == badgeDisplayTypeLong || normalizedFallback == badgeDisplayTypeShort { + return normalizedFallback + } + + switch strings.ToUpper(strings.TrimSpace(sourceType)) { + case badgeSourceTypeVIP: + return badgeDisplayTypeLong + case badgeSourceTypeActivity, badgeSourceTypeAchievement, badgeSourceTypeHonorAdmin, badgeSourceTypeHonorActivity: + return badgeDisplayTypeShort + default: + return "" + } +} diff --git a/migrations/043_sys_badge_display_type.sql b/migrations/043_sys_badge_display_type.sql new file mode 100644 index 0000000..846fcfd --- /dev/null +++ b/migrations/043_sys_badge_display_type.sql @@ -0,0 +1,22 @@ +-- Store whether a badge should render as a long badge or a short badge in App payloads. + +SET @column_exists := ( + SELECT COUNT(*) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'sys_badge_config' + AND COLUMN_NAME = 'display_type' +); + +SET @ddl := IF( + @column_exists = 0, + 'ALTER TABLE sys_badge_config ADD COLUMN display_type varchar(16) NOT NULL DEFAULT '''' COMMENT ''展示类型: LONG长徽章 SHORT短徽章'' AFTER type', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +UPDATE sys_badge_config +SET display_type = CASE WHEN type = 'VIP' THEN 'LONG' ELSE 'SHORT' END +WHERE display_type IS NULL OR display_type = '';