自动结算

This commit is contained in:
hy001 2026-06-01 19:46:21 +08:00
parent c7c40fce77
commit ecb2fcba4e
4 changed files with 441 additions and 11 deletions

View File

@ -375,6 +375,48 @@ func TestSendVIPUsesVIPLevelConfig(t *testing.T) {
}
}
func TestVIPGiftCleanupExpiresOldVIPResourcesOnly(t *testing.T) {
service, _ := newManagerCenterTestService(t)
db := service.db.(*gorm.DB)
if err := db.AutoMigrate(&managerVIPCleanupPropsRow{}, &managerVIPCleanupBadgeRow{}); err != nil {
t.Fatalf("auto migrate cleanup tables: %v", err)
}
now := time.Date(2026, 5, 30, 10, 0, 0, 0, time.UTC)
future := now.AddDate(0, 0, 20)
userID := int64(2)
seedManagerVIPCleanupProps(t, db,
managerVIPCleanupPropsRow{ID: 1, UserID: userID, PropsID: 101, Type: propsTypeAvatarFrame, ExpireTime: future, UseProps: true, AllowGive: true},
managerVIPCleanupPropsRow{ID: 2, UserID: userID, PropsID: 201, Type: propsTypeAvatarFrame, ExpireTime: future, UseProps: true, AllowGive: true},
managerVIPCleanupPropsRow{ID: 3, UserID: userID, PropsID: 999, Type: propsTypeAvatarFrame, ExpireTime: future, UseProps: true, AllowGive: true},
managerVIPCleanupPropsRow{ID: 4, UserID: userID, PropsID: 103, Type: propsTypeDataCard, ExpireTime: future, UseProps: true, AllowGive: true},
)
seedManagerVIPCleanupBadges(t, db,
managerVIPCleanupBadgeRow{ID: 1, UserID: userID, BadgeID: 100, ExpireTime: future, UseProps: true},
managerVIPCleanupBadgeRow{ID: 2, UserID: userID, BadgeID: 200, ExpireTime: future, UseProps: true},
)
target := vipGiftResourcesFromConfig(model.VipLevelConfig{
BadgeResourceID: 200,
AvatarFrameResourceID: 201,
})
all := append(vipGiftResourcesFromConfig(model.VipLevelConfig{
BadgeResourceID: 100,
AvatarFrameResourceID: 101,
BackgroundCardResourceID: 103,
}), target...)
if err := service.cleanupSupersededVIPGiftResources(context.Background(), userID, target, all, now); err != nil {
t.Fatalf("cleanupSupersededVIPGiftResources() error = %v", err)
}
assertManagerVIPCleanupProps(t, db, 101, now, false, false)
assertManagerVIPCleanupProps(t, db, 103, now, false, false)
assertManagerVIPCleanupProps(t, db, 201, future, true, true)
assertManagerVIPCleanupProps(t, db, 999, future, true, true)
assertManagerVIPCleanupBadge(t, db, 100, now, false)
assertManagerVIPCleanupBadge(t, db, 200, future, true)
}
func TestSendVIPRejectsLevelAboveGiftLimit(t *testing.T) {
service, _ := newManagerCenterTestService(t)
db := service.db.(*gorm.DB)
@ -400,6 +442,77 @@ func TestSendVIPRejectsLevelAboveGiftLimit(t *testing.T) {
assertAppErrorCode(t, err, "vip_not_giftable")
}
type managerVIPCleanupPropsRow struct {
ID int64 `gorm:"column:id;primaryKey"`
UserID int64 `gorm:"column:user_id"`
PropsID int64 `gorm:"column:props_id"`
Type string `gorm:"column:type"`
ExpireTime time.Time `gorm:"column:expire_time"`
UseProps bool `gorm:"column:is_use_props"`
AllowGive bool `gorm:"column:allow_give"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (managerVIPCleanupPropsRow) TableName() string {
return "user_props_backpack"
}
type managerVIPCleanupBadgeRow struct {
ID int64 `gorm:"column:id;primaryKey"`
UserID int64 `gorm:"column:user_id"`
BadgeID int64 `gorm:"column:badge_id"`
ExpireType string `gorm:"column:expire_type"`
ExpireTime time.Time `gorm:"column:expire_time"`
UseProps bool `gorm:"column:is_use_props"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (managerVIPCleanupBadgeRow) TableName() string {
return "user_badge_backpack"
}
func seedManagerVIPCleanupProps(t *testing.T, db *gorm.DB, rows ...managerVIPCleanupPropsRow) {
t.Helper()
for _, row := range rows {
if err := db.Create(&row).Error; err != nil {
t.Fatalf("seed manager cleanup props: %v", err)
}
}
}
func seedManagerVIPCleanupBadges(t *testing.T, db *gorm.DB, rows ...managerVIPCleanupBadgeRow) {
t.Helper()
for _, row := range rows {
if err := db.Create(&row).Error; err != nil {
t.Fatalf("seed manager cleanup badge: %v", err)
}
}
}
func assertManagerVIPCleanupProps(t *testing.T, db *gorm.DB, propsID int64, expireTime time.Time, useProps bool, allowGive bool) {
t.Helper()
var row managerVIPCleanupPropsRow
if err := db.Where("props_id = ?", propsID).First(&row).Error; err != nil {
t.Fatalf("load manager cleanup props %d: %v", propsID, err)
}
if !row.ExpireTime.Equal(expireTime) || row.UseProps != useProps || row.AllowGive != allowGive {
t.Fatalf("props %d = expire:%s use:%v allow:%v, want expire:%s use:%v allow:%v",
propsID, row.ExpireTime, row.UseProps, row.AllowGive, expireTime, useProps, allowGive)
}
}
func assertManagerVIPCleanupBadge(t *testing.T, db *gorm.DB, badgeID int64, expireTime time.Time, useProps bool) {
t.Helper()
var row managerVIPCleanupBadgeRow
if err := db.Where("badge_id = ?", badgeID).First(&row).Error; err != nil {
t.Fatalf("load manager cleanup badge %d: %v", badgeID, err)
}
if !row.ExpireTime.Equal(expireTime) || row.UseProps != useProps {
t.Fatalf("badge %d = expire:%s use:%v, want expire:%s use:%v",
badgeID, row.ExpireTime, row.UseProps, expireTime, useProps)
}
}
func TestBanUserRejectsRechargedUser(t *testing.T) {
service, fake := newManagerCenterTestService(t)
db := service.db.(*gorm.DB)

View File

@ -171,6 +171,7 @@ func (s *Service) sendVIPGift(ctx context.Context, manager AuthUser, target user
CreateTime: now,
}
}
previousResources := vipGiftResourcesFromState(state)
applyVIPGiftConfigToState(state, config, startAt, expireAt, now)
if err := tx.Save(state).Error; err != nil {
return nil, serverError("vip_state_save_failed", err.Error())
@ -209,7 +210,7 @@ func (s *Service) sendVIPGift(ctx context.Context, manager AuthUser, target user
}
committed = true
if err := s.grantVIPGiftResources(ctx, target.ID, manager.UserID, config, expireAt, now); err != nil {
if err := s.grantVIPGiftResources(ctx, sysOrigin, target.ID, manager.UserID, config, expireAt, previousResources, now); err != nil {
return nil, serverError("vip_resource_grant_failed", err.Error())
}
@ -301,11 +302,8 @@ func applyVIPGiftConfigToState(state *model.UserVipState, config model.VipLevelC
}
}
func (s *Service) grantVIPGiftResources(ctx context.Context, userID int64, managerID int64, config model.VipLevelConfig, expireAt time.Time, now time.Time) error {
func (s *Service) grantVIPGiftResources(ctx context.Context, sysOrigin string, userID int64, managerID int64, config model.VipLevelConfig, expireAt time.Time, additionalOldResources []vipGiftResource, now time.Time) error {
resources := vipGiftResourcesFromConfig(config)
if len(resources) == 0 {
return nil
}
days := config.DurationDays
if days <= 0 {
days = defaultVIPGiftDays
@ -337,6 +335,14 @@ func (s *Service) grantVIPGiftResources(ctx context.Context, userID int64, manag
return err
}
}
allResources, err := s.vipGiftResourcesFromSysOrigin(ctx, sysOrigin)
if err != nil {
return err
}
allResources = append(allResources, additionalOldResources...)
if err := s.cleanupSupersededVIPGiftResources(ctx, userID, resources, allResources, now); err != nil {
return err
}
if err := s.java.RemoveUserProfileCacheAll(ctx, userID); err != nil {
return fmt.Errorf("remove vip user profile cache: %w", err)
}
@ -374,6 +380,7 @@ func vipGiftResourcesFromConfig(config model.VipLevelConfig) []vipGiftResource {
{resourceID: config.EntryEffectResourceID, propsType: propsTypeRide},
{resourceID: config.ChatBubbleResourceID, propsType: propsTypeChatBubble},
{resourceID: config.FloatPictureResourceID, propsType: propsTypeFloatPicture},
{resourceID: config.BackgroundCardResourceID, propsType: propsTypeDataCard},
}
out := make([]vipGiftResource, 0, len(resources))
for _, resource := range resources {
@ -384,6 +391,104 @@ func vipGiftResourcesFromConfig(config model.VipLevelConfig) []vipGiftResource {
return out
}
func vipGiftResourcesFromState(state *model.UserVipState) []vipGiftResource {
if state == nil {
return nil
}
return compactVIPGiftResources([]vipGiftResource{
{resourceID: state.BadgeResourceID, propsType: propsTypeBadge, badge: true},
{resourceID: state.ShortBadgeResourceID, propsType: propsTypeBadge, badge: true},
{resourceID: state.AvatarFrameResourceID, propsType: propsTypeAvatarFrame},
{resourceID: state.EntryEffectResourceID, propsType: propsTypeRide},
{resourceID: state.ChatBubbleResourceID, propsType: propsTypeChatBubble},
{resourceID: state.FloatPictureResourceID, propsType: propsTypeFloatPicture},
{resourceID: state.BackgroundCardResourceID, propsType: propsTypeDataCard},
})
}
func compactVIPGiftResources(resources []vipGiftResource) []vipGiftResource {
out := make([]vipGiftResource, 0, len(resources))
for _, resource := range resources {
if resource.resourceID > 0 {
out = append(out, resource)
}
}
return out
}
func (s *Service) vipGiftResourcesFromSysOrigin(ctx context.Context, sysOrigin string) ([]vipGiftResource, error) {
var configs []model.VipLevelConfig
if err := s.db.WithContext(ctx).
Where("sys_origin = ?", normalizeSysOrigin(sysOrigin)).
Find(&configs).Error; err != nil {
return nil, err
}
resources := make([]vipGiftResource, 0, len(configs)*7)
for _, config := range configs {
resources = append(resources, vipGiftResourcesFromConfig(config)...)
}
return resources, nil
}
func (s *Service) cleanupSupersededVIPGiftResources(ctx context.Context, userID int64, targetResources []vipGiftResource, allVIPResources []vipGiftResource, now time.Time) error {
targets := make(map[string]struct{}, len(targetResources))
for _, resource := range targetResources {
targets[vipGiftResourceKey(resource)] = struct{}{}
}
badgeIDs := make([]int64, 0)
propsIDsByType := map[string][]int64{}
seen := make(map[string]struct{}, len(allVIPResources))
for _, resource := range allVIPResources {
if resource.resourceID <= 0 {
continue
}
key := vipGiftResourceKey(resource)
if _, exists := targets[key]; exists {
continue
}
if _, exists := seen[key]; exists {
continue
}
seen[key] = struct{}{}
if resource.badge {
badgeIDs = append(badgeIDs, resource.resourceID)
continue
}
if strings.TrimSpace(resource.propsType) != "" {
propsIDsByType[resource.propsType] = append(propsIDsByType[resource.propsType], resource.resourceID)
}
}
if len(badgeIDs) > 0 {
if err := s.db.WithContext(ctx).Exec(
"UPDATE user_badge_backpack SET expire_type = ?, expire_time = ?, is_use_props = ?, update_time = ? WHERE user_id = ? AND badge_id IN ? AND expire_time > ?",
"TEMPORARY", now, false, now, userID, badgeIDs, now,
).Error; err != nil {
return err
}
}
for propsType, propsIDs := range propsIDsByType {
if len(propsIDs) == 0 {
continue
}
if err := s.db.WithContext(ctx).Exec(
"UPDATE user_props_backpack SET expire_time = ?, is_use_props = ?, allow_give = ?, update_time = ? WHERE user_id = ? AND props_id IN ? AND type = ? AND expire_time > ?",
now, false, false, now, userID, propsIDs, propsType, now,
).Error; err != nil {
return err
}
}
return nil
}
func vipGiftResourceKey(resource vipGiftResource) string {
if resource.badge {
return fmt.Sprintf("BADGE:%d", resource.resourceID)
}
return fmt.Sprintf("%s:%d", resource.propsType, resource.resourceID)
}
func (s *Service) managerApplicantName(ctx context.Context, managerID int64) string {
row, err := s.loadUserRowByID(ctx, managerID)
if err != nil {

View File

@ -170,7 +170,7 @@ func (s *Service) Purchase(ctx context.Context, user AuthUser, req PurchaseReque
if stateErr != nil {
return nil, stateErr
}
if err := s.ensureStateResourcesGranted(ctx, user.UserID, state, now); err != nil {
if err := s.ensureStateResourcesGranted(ctx, sysOrigin, user.UserID, state, now); err != nil {
return nil, NewAppError(http.StatusBadGateway, "vip_resource_grant_failed", err.Error())
}
resources, resourceErr := s.loadResourceLookup(ctx, appendResourceIDsFromState(nil, state))
@ -274,6 +274,7 @@ func (s *Service) Purchase(ctx context.Context, user AuthUser, req PurchaseReque
}
}
previousResources := vipGrantResourcesFromState(state)
applyPlanToState(state, plan, now)
order.Status = orderStatusSuccess
order.ErrorMessage = ""
@ -289,7 +290,7 @@ func (s *Service) Purchase(ctx context.Context, user AuthUser, req PurchaseReque
}
committed = true
if err := s.ensurePlanResourcesGranted(ctx, user.UserID, plan, now); err != nil {
if err := s.ensurePlanResourcesGranted(ctx, sysOrigin, user.UserID, plan, configs, previousResources, now); err != nil {
return nil, NewAppError(http.StatusBadGateway, "vip_resource_grant_failed", err.Error())
}
@ -536,15 +537,28 @@ func expireState(state *model.UserVipState, now time.Time) {
state.UpdateTime = now
}
func (s *Service) ensurePlanResourcesGranted(ctx context.Context, userID int64, plan purchasePlan, now time.Time) error {
return s.ensureVIPResourcesGranted(ctx, userID, vipGrantResourcesFromConfig(plan.TargetConfig), plan.ExpireAt, plan.DurationDays, now)
func (s *Service) ensurePlanResourcesGranted(ctx context.Context, sysOrigin string, userID int64, plan purchasePlan, configs map[int]model.VipLevelConfig, additionalOldResources []vipGrantResource, now time.Time) error {
targetResources := vipGrantResourcesFromConfig(plan.TargetConfig)
if err := s.ensureVIPResourcesGranted(ctx, userID, targetResources, plan.ExpireAt, plan.DurationDays, now); err != nil {
return err
}
allVIPResources := append(vipGrantResourcesFromConfigs(configs), additionalOldResources...)
return s.cleanupSupersededVIPResources(ctx, userID, targetResources, allVIPResources, now)
}
func (s *Service) ensureStateResourcesGranted(ctx context.Context, userID int64, state *model.UserVipState, now time.Time) error {
func (s *Service) ensureStateResourcesGranted(ctx context.Context, sysOrigin string, userID int64, state *model.UserVipState, now time.Time) error {
if !isActiveState(state, now) {
return nil
}
return s.ensureVIPResourcesGranted(ctx, userID, vipGrantResourcesFromState(state), state.ExpireAt, vipGrantDaysUntil(now, state.ExpireAt), now)
targetResources := vipGrantResourcesFromState(state)
if err := s.ensureVIPResourcesGranted(ctx, userID, targetResources, state.ExpireAt, vipGrantDaysUntil(now, state.ExpireAt), now); err != nil {
return err
}
configs, err := s.loadConfigMap(ctx, sysOrigin)
if err != nil {
return err
}
return s.cleanupSupersededVIPResources(ctx, userID, targetResources, vipGrantResourcesFromConfigs(configs), now)
}
func (s *Service) ensureVIPResourcesGranted(ctx context.Context, userID int64, resources []vipGrantResource, expireAt time.Time, days int, now time.Time) error {
@ -685,6 +699,17 @@ func vipGrantResourcesFromState(state *model.UserVipState) []vipGrantResource {
})
}
func vipGrantResourcesFromConfigs(configs map[int]model.VipLevelConfig) []vipGrantResource {
if len(configs) == 0 {
return nil
}
resources := make([]vipGrantResource, 0, len(configs)*7)
for _, config := range configs {
resources = append(resources, vipGrantResourcesFromConfig(config)...)
}
return compactVIPGrantResources(resources)
}
func compactVIPGrantResources(resources []vipGrantResource) []vipGrantResource {
compacted := make([]vipGrantResource, 0, len(resources))
for _, resource := range resources {
@ -695,6 +720,65 @@ func compactVIPGrantResources(resources []vipGrantResource) []vipGrantResource {
return compacted
}
func (s *Service) cleanupSupersededVIPResources(ctx context.Context, userID int64, targetResources []vipGrantResource, allVIPResources []vipGrantResource, now time.Time) error {
targets := make(map[string]struct{}, len(targetResources))
for _, resource := range targetResources {
targets[vipGrantResourceKey(resource)] = struct{}{}
}
badgeIDs := make([]int64, 0)
propsIDsByType := map[string][]int64{}
seen := make(map[string]struct{}, len(allVIPResources))
for _, resource := range allVIPResources {
if resource.ResourceID <= 0 {
continue
}
key := vipGrantResourceKey(resource)
if _, exists := targets[key]; exists {
continue
}
if _, exists := seen[key]; exists {
continue
}
seen[key] = struct{}{}
if resource.Badge {
badgeIDs = append(badgeIDs, resource.ResourceID)
continue
}
if strings.TrimSpace(resource.PropsType) != "" {
propsIDsByType[resource.PropsType] = append(propsIDsByType[resource.PropsType], resource.ResourceID)
}
}
if len(badgeIDs) > 0 {
if err := s.db.WithContext(ctx).Exec(
"UPDATE user_badge_backpack SET expire_type = ?, expire_time = ?, is_use_props = ?, update_time = ? WHERE user_id = ? AND badge_id IN ? AND expire_time > ?",
"TEMPORARY", now, false, now, userID, badgeIDs, now,
).Error; err != nil {
return err
}
}
for propsType, propsIDs := range propsIDsByType {
if len(propsIDs) == 0 {
continue
}
if err := s.db.WithContext(ctx).Exec(
"UPDATE user_props_backpack SET expire_time = ?, is_use_props = ?, allow_give = ?, update_time = ? WHERE user_id = ? AND props_id IN ? AND type = ? AND expire_time > ?",
now, false, false, now, userID, propsIDs, propsType, now,
).Error; err != nil {
return err
}
}
return nil
}
func vipGrantResourceKey(resource vipGrantResource) string {
if resource.Badge {
return fmt.Sprintf("BADGE:%d", resource.ResourceID)
}
return fmt.Sprintf("%s:%d", resource.PropsType, resource.ResourceID)
}
func vipGrantDaysUntil(now time.Time, expireAt time.Time) int {
if !expireAt.After(now) {
return 1

View File

@ -1,6 +1,7 @@
package vip
import (
"context"
"encoding/json"
"errors"
"net/http"
@ -9,6 +10,9 @@ import (
"time"
"chatapp3-golang/internal/model"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func TestBuildPurchasePlanNewPurchase(t *testing.T) {
@ -322,6 +326,47 @@ func TestVIPGrantResourcesFromConfigMapsBackpackTypes(t *testing.T) {
assertGrant(6, 16, vipPropsTypeDataCard, false)
}
func TestCleanupSupersededVIPResourcesExpiresOnlyOldVIPResources(t *testing.T) {
db := openVIPCleanupTestDB(t)
service := &Service{db: db}
now := time.Date(2026, 5, 30, 10, 0, 0, 0, time.UTC)
future := now.AddDate(0, 0, 20)
oldTime := now.AddDate(0, 0, -1)
userID := int64(1001)
seedVIPCleanupRows(t, db,
vipCleanupPropsRow{ID: 1, UserID: userID, PropsID: 101, Type: vipPropsTypeAvatarFrame, ExpireTime: future, UseProps: true, AllowGive: true, UpdateTime: oldTime},
vipCleanupPropsRow{ID: 2, UserID: userID, PropsID: 201, Type: vipPropsTypeAvatarFrame, ExpireTime: future, UseProps: true, AllowGive: true, UpdateTime: oldTime},
vipCleanupPropsRow{ID: 3, UserID: userID, PropsID: 999, Type: vipPropsTypeAvatarFrame, ExpireTime: future, UseProps: true, AllowGive: true, UpdateTime: oldTime},
vipCleanupPropsRow{ID: 4, UserID: userID, PropsID: 102, Type: vipPropsTypeRide, ExpireTime: future, UseProps: true, AllowGive: true, UpdateTime: oldTime},
)
seedVIPCleanupBadges(t, db,
vipCleanupBadgeRow{ID: 1, UserID: userID, BadgeID: 100, ExpireTime: future, UseProps: true, UpdateTime: oldTime},
vipCleanupBadgeRow{ID: 2, UserID: userID, BadgeID: 200, ExpireTime: future, UseProps: true, UpdateTime: oldTime},
)
target := vipGrantResourcesFromConfig(model.VipLevelConfig{
BadgeResourceID: 200,
AvatarFrameResourceID: 201,
})
all := append(vipGrantResourcesFromConfig(model.VipLevelConfig{
BadgeResourceID: 100,
AvatarFrameResourceID: 101,
EntryEffectResourceID: 102,
}), target...)
if err := service.cleanupSupersededVIPResources(context.Background(), userID, target, all, now); err != nil {
t.Fatalf("cleanupSupersededVIPResources() error = %v", err)
}
assertVIPCleanupProps(t, db, 101, now, false, false)
assertVIPCleanupProps(t, db, 102, now, false, false)
assertVIPCleanupProps(t, db, 201, future, true, true)
assertVIPCleanupProps(t, db, 999, future, true, true)
assertVIPCleanupBadge(t, db, 100, now, false)
assertVIPCleanupBadge(t, db, 200, future, true)
}
func TestVIPGrantDaysUntilCeilsPartialDay(t *testing.T) {
now := time.Date(2026, 4, 29, 10, 0, 0, 0, time.UTC)
expireAt := now.Add((24 * time.Hour) + time.Second)
@ -334,6 +379,89 @@ func TestVIPGrantDaysUntilCeilsPartialDay(t *testing.T) {
}
}
type vipCleanupPropsRow struct {
ID int64 `gorm:"column:id;primaryKey"`
UserID int64 `gorm:"column:user_id"`
PropsID int64 `gorm:"column:props_id"`
Type string `gorm:"column:type"`
ExpireTime time.Time `gorm:"column:expire_time"`
UseProps bool `gorm:"column:is_use_props"`
AllowGive bool `gorm:"column:allow_give"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (vipCleanupPropsRow) TableName() string {
return "user_props_backpack"
}
type vipCleanupBadgeRow struct {
ID int64 `gorm:"column:id;primaryKey"`
UserID int64 `gorm:"column:user_id"`
BadgeID int64 `gorm:"column:badge_id"`
ExpireType string `gorm:"column:expire_type"`
ExpireTime time.Time `gorm:"column:expire_time"`
UseProps bool `gorm:"column:is_use_props"`
UpdateTime time.Time `gorm:"column:update_time"`
}
func (vipCleanupBadgeRow) TableName() string {
return "user_badge_backpack"
}
func openVIPCleanupTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
if err != nil {
t.Fatalf("open sqlite: %v", err)
}
if err := db.AutoMigrate(&vipCleanupPropsRow{}, &vipCleanupBadgeRow{}); err != nil {
t.Fatalf("auto migrate cleanup tables: %v", err)
}
return db
}
func seedVIPCleanupRows(t *testing.T, db *gorm.DB, rows ...vipCleanupPropsRow) {
t.Helper()
for _, row := range rows {
if err := db.Create(&row).Error; err != nil {
t.Fatalf("seed props row: %v", err)
}
}
}
func seedVIPCleanupBadges(t *testing.T, db *gorm.DB, rows ...vipCleanupBadgeRow) {
t.Helper()
for _, row := range rows {
if err := db.Create(&row).Error; err != nil {
t.Fatalf("seed badge row: %v", err)
}
}
}
func assertVIPCleanupProps(t *testing.T, db *gorm.DB, propsID int64, expireTime time.Time, useProps bool, allowGive bool) {
t.Helper()
var row vipCleanupPropsRow
if err := db.Where("props_id = ?", propsID).First(&row).Error; err != nil {
t.Fatalf("load props %d: %v", propsID, err)
}
if !row.ExpireTime.Equal(expireTime) || row.UseProps != useProps || row.AllowGive != allowGive {
t.Fatalf("props %d = expire:%s use:%v allow:%v, want expire:%s use:%v allow:%v",
propsID, row.ExpireTime, row.UseProps, row.AllowGive, expireTime, useProps, allowGive)
}
}
func assertVIPCleanupBadge(t *testing.T, db *gorm.DB, badgeID int64, expireTime time.Time, useProps bool) {
t.Helper()
var row vipCleanupBadgeRow
if err := db.Where("badge_id = ?", badgeID).First(&row).Error; err != nil {
t.Fatalf("load badge %d: %v", badgeID, err)
}
if !row.ExpireTime.Equal(expireTime) || row.UseProps != useProps {
t.Fatalf("badge %d = expire:%s use:%v, want expire:%s use:%v",
badgeID, row.ExpireTime, row.UseProps, expireTime, useProps)
}
}
func vipTestConfigs() map[int]model.VipLevelConfig {
configs := make(map[int]model.VipLevelConfig, levelCount)
for level := 1; level <= levelCount; level++ {