fix: split badges by display type
This commit is contained in:
parent
8ace95f78f
commit
c1a0049b1d
@ -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.
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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 ""
|
||||
}
|
||||
}
|
||||
|
||||
22
migrations/043_sys_badge_display_type.sql
Normal file
22
migrations/043_sys_badge_display_type.sql
Normal file
@ -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 = '';
|
||||
Loading…
x
Reference in New Issue
Block a user