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.
|
// SysBadgeConfig stores badge metadata from the Java badge system.
|
||||||
type SysBadgeConfig struct {
|
type SysBadgeConfig struct {
|
||||||
ID int64 `gorm:"column:id;primaryKey"`
|
ID int64 `gorm:"column:id;primaryKey"`
|
||||||
Type string `gorm:"column:type;size:64"`
|
Type string `gorm:"column:type;size:64"`
|
||||||
DisplayType string `gorm:"column:display_type;size:16"`
|
DisplayType string `gorm:"column:display_type;size:16"`
|
||||||
BadgeLevel int `gorm:"column:badge_level"`
|
BadgeLevel int `gorm:"column:badge_level"`
|
||||||
Milestone int64 `gorm:"column:milestone"`
|
Milestone int64 `gorm:"column:milestone"`
|
||||||
BadgeName string `gorm:"column:badge_name;size:128"`
|
BadgeName string `gorm:"column:badge_name;size:128"`
|
||||||
BadgeKey string `gorm:"column:badge_key;size:128"`
|
BadgeKey string `gorm:"column:badge_key;size:128"`
|
||||||
Del bool `gorm:"column:is_del"`
|
Del bool `gorm:"column:is_del"`
|
||||||
CreateTime time.Time `gorm:"column:create_time"`
|
CreateTime time.Time `gorm:"column:create_time"`
|
||||||
UpdateTime time.Time `gorm:"column:update_time"`
|
UpdateTime time.Time `gorm:"column:update_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName returns the badge config table name.
|
// TableName returns the badge config table name.
|
||||||
|
|||||||
@ -279,6 +279,9 @@ func applyVIPGiftConfigToState(state *model.UserVipState, config model.VipLevelC
|
|||||||
state.BadgeResourceID = config.BadgeResourceID
|
state.BadgeResourceID = config.BadgeResourceID
|
||||||
state.BadgeName = config.BadgeName
|
state.BadgeName = config.BadgeName
|
||||||
state.BadgeURL = config.BadgeURL
|
state.BadgeURL = config.BadgeURL
|
||||||
|
state.ShortBadgeResourceID = config.ShortBadgeResourceID
|
||||||
|
state.ShortBadgeName = config.ShortBadgeName
|
||||||
|
state.ShortBadgeURL = config.ShortBadgeURL
|
||||||
state.AvatarFrameResourceID = config.AvatarFrameResourceID
|
state.AvatarFrameResourceID = config.AvatarFrameResourceID
|
||||||
state.AvatarFrameName = config.AvatarFrameName
|
state.AvatarFrameName = config.AvatarFrameName
|
||||||
state.AvatarFrameURL = config.AvatarFrameURL
|
state.AvatarFrameURL = config.AvatarFrameURL
|
||||||
@ -365,6 +368,7 @@ func (s *Service) alignVIPGiftResourceExpireAt(ctx context.Context, userID int64
|
|||||||
func vipGiftResourcesFromConfig(config model.VipLevelConfig) []vipGiftResource {
|
func vipGiftResourcesFromConfig(config model.VipLevelConfig) []vipGiftResource {
|
||||||
resources := []vipGiftResource{
|
resources := []vipGiftResource{
|
||||||
{resourceID: config.BadgeResourceID, propsType: propsTypeBadge, badge: true},
|
{resourceID: config.BadgeResourceID, propsType: propsTypeBadge, badge: true},
|
||||||
|
{resourceID: config.ShortBadgeResourceID, propsType: propsTypeBadge, badge: true},
|
||||||
{resourceID: config.AvatarFrameResourceID, propsType: propsTypeAvatarFrame},
|
{resourceID: config.AvatarFrameResourceID, propsType: propsTypeAvatarFrame},
|
||||||
{resourceID: config.EntryEffectResourceID, propsType: propsTypeRide},
|
{resourceID: config.EntryEffectResourceID, propsType: propsTypeRide},
|
||||||
{resourceID: config.ChatBubbleResourceID, propsType: propsTypeChatBubble},
|
{resourceID: config.ChatBubbleResourceID, propsType: propsTypeChatBubble},
|
||||||
|
|||||||
@ -78,7 +78,7 @@ func TestCurrentBadgesSplitLongAndShort(t *testing.T) {
|
|||||||
t.Fatalf("badges len = %d, want 3", len(resp.Badges))
|
t.Fatalf("badges len = %d, want 3", len(resp.Badges))
|
||||||
}
|
}
|
||||||
long := resp.LongBadges[0]
|
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)
|
t.Fatalf("long badge = %#v", long)
|
||||||
}
|
}
|
||||||
if long.Name != "VIP Badge" || long.URL != "https://cdn.example.com/101.png" {
|
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) {
|
func TestCurrentBadgesRejectsInvalidUser(t *testing.T) {
|
||||||
service := NewService(newUserBadgeTestDB(t))
|
service := NewService(newUserBadgeTestDB(t))
|
||||||
_, err := service.Current(context.Background(), common.AuthUser{SysOrigin: "LIKEI"})
|
_, 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) {
|
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()
|
t.Helper()
|
||||||
if err := db.Create(&model.SysBadgeConfig{
|
if err := db.Create(&model.SysBadgeConfig{
|
||||||
ID: id,
|
ID: id,
|
||||||
Type: sourceType,
|
Type: sourceType,
|
||||||
BadgeName: name,
|
DisplayType: displayType,
|
||||||
BadgeKey: key,
|
BadgeName: name,
|
||||||
BadgeLevel: level,
|
BadgeKey: key,
|
||||||
Milestone: id * 10,
|
BadgeLevel: level,
|
||||||
Del: false,
|
Milestone: id * 10,
|
||||||
|
Del: false,
|
||||||
}).Error; err != nil {
|
}).Error; err != nil {
|
||||||
t.Fatalf("create badge config %d: %v", id, err)
|
t.Fatalf("create badge config %d: %v", id, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,9 +16,11 @@ const (
|
|||||||
badgeDisplayTypeLong = "LONG"
|
badgeDisplayTypeLong = "LONG"
|
||||||
badgeDisplayTypeShort = "SHORT"
|
badgeDisplayTypeShort = "SHORT"
|
||||||
|
|
||||||
badgeSourceTypeVIP = "VIP"
|
badgeSourceTypeVIP = "VIP"
|
||||||
badgeSourceTypeActivity = "ACTIVITY"
|
badgeSourceTypeActivity = "ACTIVITY"
|
||||||
badgeSourceTypeAchievement = "ACHIEVEMENT"
|
badgeSourceTypeAchievement = "ACHIEVEMENT"
|
||||||
|
badgeSourceTypeHonorAdmin = "HONOR_ADMIN"
|
||||||
|
badgeSourceTypeHonorActivity = "HONOR_ACTIVITY"
|
||||||
|
|
||||||
expireTypePermanent = "PERMANENT"
|
expireTypePermanent = "PERMANENT"
|
||||||
statusActive = "ACTIVE"
|
statusActive = "ACTIVE"
|
||||||
@ -56,8 +58,7 @@ type CurrentBadgeListResponse struct {
|
|||||||
ShortBadges []CurrentBadgeItem `json:"shortBadges"`
|
ShortBadges []CurrentBadgeItem `json:"shortBadges"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurrentBadgeItem is one current badge. Type is LONG for VIP badges and SHORT
|
// CurrentBadgeItem is one current badge. Type is LONG or SHORT by configured display type.
|
||||||
// for activity or achievement badges.
|
|
||||||
type CurrentBadgeItem struct {
|
type CurrentBadgeItem struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
BadgeID string `json:"badgeId"`
|
BadgeID string `json:"badgeId"`
|
||||||
@ -144,3 +145,24 @@ func firstNonEmpty(values ...string) string {
|
|||||||
}
|
}
|
||||||
return ""
|
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