Compare commits
No commits in common. "8c53a6441662456369bcd85a4faa5286db091d88" and "dbd073b97fe31b4df7010c68ed820454505779d6" have entirely different histories.
8c53a64416
...
dbd073b97f
@ -23,7 +23,6 @@ import (
|
|||||||
"chatapp3-golang/internal/service/lingxian"
|
"chatapp3-golang/internal/service/lingxian"
|
||||||
"chatapp3-golang/internal/service/luckygift"
|
"chatapp3-golang/internal/service/luckygift"
|
||||||
"chatapp3-golang/internal/service/managercenter"
|
"chatapp3-golang/internal/service/managercenter"
|
||||||
"chatapp3-golang/internal/service/propsstore"
|
|
||||||
"chatapp3-golang/internal/service/rechargeagency"
|
"chatapp3-golang/internal/service/rechargeagency"
|
||||||
"chatapp3-golang/internal/service/rechargereward"
|
"chatapp3-golang/internal/service/rechargereward"
|
||||||
"chatapp3-golang/internal/service/regionimgroup"
|
"chatapp3-golang/internal/service/regionimgroup"
|
||||||
@ -75,7 +74,6 @@ func main() {
|
|||||||
roomTurnoverRewardService := roomturnoverreward.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
roomTurnoverRewardService := roomturnoverreward.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
||||||
hostCenterService := hostcenter.NewService(app.Config, &app.Gateways)
|
hostCenterService := hostcenter.NewService(app.Config, &app.Gateways)
|
||||||
managerCenterService := managercenter.NewService(app.Repository.DB, &app.Gateways)
|
managerCenterService := managercenter.NewService(app.Repository.DB, &app.Gateways)
|
||||||
propsStoreService := propsstore.NewService(app.Repository.DB, app.Repository.Redis)
|
|
||||||
|
|
||||||
baishunService.SetTaskEventReporter(taskCenterService)
|
baishunService.SetTaskEventReporter(taskCenterService)
|
||||||
gameOpenService.SetTaskEventReporter(taskCenterService)
|
gameOpenService.SetTaskEventReporter(taskCenterService)
|
||||||
@ -115,7 +113,6 @@ func main() {
|
|||||||
GameProviders: gameProviders,
|
GameProviders: gameProviders,
|
||||||
LuckyGift: luckyGiftService,
|
LuckyGift: luckyGiftService,
|
||||||
ManagerCenter: managerCenterService,
|
ManagerCenter: managerCenterService,
|
||||||
PropsStore: propsStoreService,
|
|
||||||
HostCenter: hostCenterService,
|
HostCenter: hostCenterService,
|
||||||
RechargeAgency: rechargeAgencyService,
|
RechargeAgency: rechargeAgencyService,
|
||||||
RechargeReward: rechargeRewardService,
|
RechargeReward: rechargeRewardService,
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// PropsCommodityStore stores one props store commodity row.
|
|
||||||
type PropsCommodityStore struct {
|
|
||||||
ID int64 `gorm:"column:id;primaryKey"`
|
|
||||||
SysOrigin string `gorm:"column:sys_origin;size:32;index:idx_props_store_source,priority:1"`
|
|
||||||
SourceID int64 `gorm:"column:source_id;index:idx_props_store_source,priority:2"`
|
|
||||||
PropsType string `gorm:"column:props_type;size:64"`
|
|
||||||
CurrencyTypes string `gorm:"column:currency_types;size:128"`
|
|
||||||
ValidDays string `gorm:"column:valid_days;size:128"`
|
|
||||||
Discount *float64 `gorm:"column:discount;type:decimal(10,4)"`
|
|
||||||
ShelfStatus *bool `gorm:"column:shelf_status"`
|
|
||||||
Sort int `gorm:"column:sort"`
|
|
||||||
Label string `gorm:"column:label;size:255"`
|
|
||||||
Del *bool `gorm:"column:is_del"`
|
|
||||||
CreateTime *time.Time `gorm:"column:create_time"`
|
|
||||||
UpdateTime *time.Time `gorm:"column:update_time"`
|
|
||||||
CreateUser *int64 `gorm:"column:create_user"`
|
|
||||||
UpdateUser *int64 `gorm:"column:update_user"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TableName returns the props store commodity table name.
|
|
||||||
func (PropsCommodityStore) TableName() string { return "props_commodity_store" }
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
package router
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/service/propsstore"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// registerPropsStoreRoutes registers admin props store routes.
|
|
||||||
func registerPropsStoreRoutes(engine *gin.Engine, javaClient authGateway, service *propsstore.Service) {
|
|
||||||
if service == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
adminGroup := engine.Group("/props/store")
|
|
||||||
adminGroup.Use(consoleAuthMiddleware(javaClient))
|
|
||||||
adminGroup.POST("/source-map", func(c *gin.Context) {
|
|
||||||
var req propsstore.MapBySourceIDsRequest
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
writeError(c, propsstore.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp, err := service.MapBySourceIDs(c.Request.Context(), req)
|
|
||||||
if err != nil {
|
|
||||||
writeError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeOK(c, resp)
|
|
||||||
})
|
|
||||||
adminGroup.POST("/add-or-update", func(c *gin.Context) {
|
|
||||||
var req propsstore.SaveCommodityRequest
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
|
||||||
writeError(c, propsstore.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp, err := service.SaveCommodity(c.Request.Context(), req)
|
|
||||||
if err != nil {
|
|
||||||
writeError(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeOK(c, resp)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
package router
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/config"
|
|
||||||
"chatapp3-golang/internal/integration"
|
|
||||||
"chatapp3-golang/internal/model"
|
|
||||||
"chatapp3-golang/internal/repo"
|
|
||||||
"chatapp3-golang/internal/service/propsstore"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPropsStoreAdminRoutesSaveAndMap(t *testing.T) {
|
|
||||||
gin.SetMode(gin.TestMode)
|
|
||||||
db := newRouterPropsStoreDB(t)
|
|
||||||
service := propsstore.NewService(db, nil)
|
|
||||||
engine := NewRouter(config.Config{}, &repo.Repository{}, propsStoreAuthStub{}, Services{
|
|
||||||
PropsStore: service,
|
|
||||||
})
|
|
||||||
|
|
||||||
saveBody := []byte(`{
|
|
||||||
"sysOrigin": "LIKEI",
|
|
||||||
"sourceId": "100",
|
|
||||||
"propsType": "AVATAR_FRAME",
|
|
||||||
"currencyTypes": "GOLD",
|
|
||||||
"validDays": "7",
|
|
||||||
"discount": 0,
|
|
||||||
"shelfStatus": true,
|
|
||||||
"sort": 0,
|
|
||||||
"label": ""
|
|
||||||
}`)
|
|
||||||
saveReq := httptest.NewRequest(http.MethodPost, "/props/store/add-or-update", bytes.NewReader(saveBody))
|
|
||||||
saveReq.Header.Set("Authorization", "Bearer test-token")
|
|
||||||
saveReq.Header.Set("Content-Type", "application/json")
|
|
||||||
saveResp := httptest.NewRecorder()
|
|
||||||
engine.ServeHTTP(saveResp, saveReq)
|
|
||||||
if saveResp.Code != http.StatusOK {
|
|
||||||
t.Fatalf("save status = %d, body = %s", saveResp.Code, saveResp.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
mapReq := httptest.NewRequest(http.MethodPost, "/props/store/source-map", bytes.NewReader([]byte(`{
|
|
||||||
"sysOrigin": "LIKEI",
|
|
||||||
"sourceIds": ["100"]
|
|
||||||
}`)))
|
|
||||||
mapReq.Header.Set("Authorization", "Bearer test-token")
|
|
||||||
mapReq.Header.Set("Content-Type", "application/json")
|
|
||||||
mapResp := httptest.NewRecorder()
|
|
||||||
engine.ServeHTTP(mapResp, mapReq)
|
|
||||||
if mapResp.Code != http.StatusOK {
|
|
||||||
t.Fatalf("map status = %d, body = %s", mapResp.Code, mapResp.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
var decoded struct {
|
|
||||||
ErrorCode int `json:"errorCode"`
|
|
||||||
Body map[string]propsstore.CommodityView `json:"body"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(mapResp.Body.Bytes(), &decoded); err != nil {
|
|
||||||
t.Fatalf("decode response: %v", err)
|
|
||||||
}
|
|
||||||
item, ok := decoded.Body["100"]
|
|
||||||
if decoded.ErrorCode != 0 || !ok || item.ShelfStatus == nil || !*item.ShelfStatus {
|
|
||||||
t.Fatalf("unexpected source-map body: %#v", decoded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type propsStoreAuthStub struct{}
|
|
||||||
|
|
||||||
func (propsStoreAuthStub) AuthenticateToken(context.Context, string) (integration.UserCredential, error) {
|
|
||||||
return integration.UserCredential{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (propsStoreAuthStub) AuthenticateConsoleToken(context.Context, string) (integration.ConsoleAccount, error) {
|
|
||||||
return integration.ConsoleAccount{LoginName: "admin"}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRouterPropsStoreDB(t *testing.T) *gorm.DB {
|
|
||||||
t.Helper()
|
|
||||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("open sqlite: %v", err)
|
|
||||||
}
|
|
||||||
if err := db.AutoMigrate(&model.PropsCommodityStore{}); err != nil {
|
|
||||||
t.Fatalf("auto migrate: %v", err)
|
|
||||||
}
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
@ -17,7 +17,6 @@ import (
|
|||||||
"chatapp3-golang/internal/service/lingxian"
|
"chatapp3-golang/internal/service/lingxian"
|
||||||
"chatapp3-golang/internal/service/luckygift"
|
"chatapp3-golang/internal/service/luckygift"
|
||||||
"chatapp3-golang/internal/service/managercenter"
|
"chatapp3-golang/internal/service/managercenter"
|
||||||
"chatapp3-golang/internal/service/propsstore"
|
|
||||||
"chatapp3-golang/internal/service/rechargeagency"
|
"chatapp3-golang/internal/service/rechargeagency"
|
||||||
"chatapp3-golang/internal/service/rechargereward"
|
"chatapp3-golang/internal/service/rechargereward"
|
||||||
"chatapp3-golang/internal/service/regionimgroup"
|
"chatapp3-golang/internal/service/regionimgroup"
|
||||||
@ -48,7 +47,6 @@ type Services struct {
|
|||||||
GameProviders *gameprovider.Registry
|
GameProviders *gameprovider.Registry
|
||||||
LuckyGift *luckygift.LuckyGiftService
|
LuckyGift *luckygift.LuckyGiftService
|
||||||
ManagerCenter *managercenter.Service
|
ManagerCenter *managercenter.Service
|
||||||
PropsStore *propsstore.Service
|
|
||||||
HostCenter *hostcenter.Service
|
HostCenter *hostcenter.Service
|
||||||
RechargeAgency *rechargeagency.Service
|
RechargeAgency *rechargeagency.Service
|
||||||
RechargeReward *rechargereward.Service
|
RechargeReward *rechargereward.Service
|
||||||
@ -102,7 +100,6 @@ func NewRouter(
|
|||||||
registerLingxianRoutes(engine, javaClient, services.Lingxian)
|
registerLingxianRoutes(engine, javaClient, services.Lingxian)
|
||||||
registerLuckyGiftRoutes(engine, cfg, services.LuckyGift)
|
registerLuckyGiftRoutes(engine, cfg, services.LuckyGift)
|
||||||
registerManagerCenterRoutes(engine, javaClient, services.ManagerCenter)
|
registerManagerCenterRoutes(engine, javaClient, services.ManagerCenter)
|
||||||
registerPropsStoreRoutes(engine, javaClient, services.PropsStore)
|
|
||||||
|
|
||||||
return engine
|
return engine
|
||||||
}
|
}
|
||||||
|
|||||||
@ -535,7 +535,7 @@ func (s *Service) loadGiftableNobleVIPAbility(ctx context.Context, propsID int64
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return propsNobleVIPAbilityRow{}, serverError("vip_ability_query_failed", err.Error())
|
return propsNobleVIPAbilityRow{}, serverError("vip_ability_query_failed", err.Error())
|
||||||
}
|
}
|
||||||
if row.VIPLevel > maxGiftableVIPLevel {
|
if row.VIPLevel > 3 {
|
||||||
return propsNobleVIPAbilityRow{}, badRequest("vip_not_giftable", "only noble VIP level 1-3 can be gifted")
|
return propsNobleVIPAbilityRow{}, badRequest("vip_not_giftable", "only noble VIP level 1-3 can be gifted")
|
||||||
}
|
}
|
||||||
return row, nil
|
return row, nil
|
||||||
|
|||||||
@ -135,7 +135,6 @@ func TestListPropsReturnsVIPLevelConfigs(t *testing.T) {
|
|||||||
model.VipLevelConfig{ID: 301, SysOrigin: "LIKEI", Level: 1, LevelCode: "VIP1", DisplayName: "VIP 1", Enabled: true, DurationDays: 30},
|
model.VipLevelConfig{ID: 301, SysOrigin: "LIKEI", Level: 1, LevelCode: "VIP1", DisplayName: "VIP 1", Enabled: true, DurationDays: 30},
|
||||||
model.VipLevelConfig{ID: 302, SysOrigin: "LIKEI", Level: 2, LevelCode: "VIP2", DisplayName: "VIP 2", Enabled: false, DurationDays: 30},
|
model.VipLevelConfig{ID: 302, SysOrigin: "LIKEI", Level: 2, LevelCode: "VIP2", DisplayName: "VIP 2", Enabled: false, DurationDays: 30},
|
||||||
model.VipLevelConfig{ID: 303, SysOrigin: "ATYOU", Level: 1, LevelCode: "VIP1", DisplayName: "ATYOU VIP 1", Enabled: true, DurationDays: 30},
|
model.VipLevelConfig{ID: 303, SysOrigin: "ATYOU", Level: 1, LevelCode: "VIP1", DisplayName: "ATYOU VIP 1", Enabled: true, DurationDays: 30},
|
||||||
model.VipLevelConfig{ID: 304, SysOrigin: "LIKEI", Level: 4, LevelCode: "VIP4", DisplayName: "VIP 4", Enabled: true, DurationDays: 30},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
resp, err := service.ListProps(context.Background(), AuthUser{UserID: 1, SysOrigin: "LIKEI"}, propsTypeNobleVIP)
|
resp, err := service.ListProps(context.Background(), AuthUser{UserID: 1, SysOrigin: "LIKEI"}, propsTypeNobleVIP)
|
||||||
@ -308,31 +307,6 @@ func TestSendVIPUsesVIPLevelConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSendVIPRejectsLevelAboveGiftLimit(t *testing.T) {
|
|
||||||
service, _ := newManagerCenterTestService(t)
|
|
||||||
db := service.db.(*gorm.DB)
|
|
||||||
seedManager(t, db, 1)
|
|
||||||
seedUsers(t, db,
|
|
||||||
userBaseInfoRow{ID: 1, Account: "1", UserNickname: "manager", OriginSys: "LIKEI"},
|
|
||||||
userBaseInfoRow{ID: 2, Account: "2", OriginSys: "LIKEI"},
|
|
||||||
)
|
|
||||||
seedVIPConfigs(t, db, model.VipLevelConfig{
|
|
||||||
ID: 304,
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
Level: 4,
|
|
||||||
LevelCode: "VIP4",
|
|
||||||
DisplayName: "VIP 4",
|
|
||||||
Enabled: true,
|
|
||||||
DurationDays: 30,
|
|
||||||
})
|
|
||||||
|
|
||||||
_, err := service.SendProps(context.Background(), AuthUser{UserID: 1, SysOrigin: "LIKEI"}, SendPropsRequest{
|
|
||||||
AcceptUserID: ID(2),
|
|
||||||
PropsID: ID(304),
|
|
||||||
})
|
|
||||||
assertAppErrorCode(t, err, "vip_not_giftable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBanUserRejectsRechargedUser(t *testing.T) {
|
func TestBanUserRejectsRechargedUser(t *testing.T) {
|
||||||
service, fake := newManagerCenterTestService(t)
|
service, fake := newManagerCenterTestService(t)
|
||||||
db := service.db.(*gorm.DB)
|
db := service.db.(*gorm.DB)
|
||||||
|
|||||||
@ -27,9 +27,8 @@ const (
|
|||||||
vipGiftOrigin = "VIP_PURCHASE"
|
vipGiftOrigin = "VIP_PURCHASE"
|
||||||
vipGiftOriginDesc = "VIP purchase"
|
vipGiftOriginDesc = "VIP purchase"
|
||||||
|
|
||||||
defaultGiftDays = 7
|
defaultGiftDays = 7
|
||||||
defaultVIPGiftDays = 30
|
defaultVIPGiftDays = 30
|
||||||
maxGiftableVIPLevel = 3
|
|
||||||
|
|
||||||
accountStatusNormal = "NORMAL"
|
accountStatusNormal = "NORMAL"
|
||||||
accountStatusFreeze = "FREEZE"
|
accountStatusFreeze = "FREEZE"
|
||||||
|
|||||||
@ -63,7 +63,6 @@ func (s *Service) listVIPProps(ctx context.Context, user AuthUser) (*PropsListRe
|
|||||||
var rows []model.VipLevelConfig
|
var rows []model.VipLevelConfig
|
||||||
if err := s.db.WithContext(ctx).
|
if err := s.db.WithContext(ctx).
|
||||||
Where("sys_origin = ? AND enabled = ?", sysOrigin, true).
|
Where("sys_origin = ? AND enabled = ?", sysOrigin, true).
|
||||||
Where("level BETWEEN ? AND ?", 1, maxGiftableVIPLevel).
|
|
||||||
Order("level ASC").
|
Order("level ASC").
|
||||||
Find(&rows).Error; err != nil {
|
Find(&rows).Error; err != nil {
|
||||||
return nil, serverError("vip_config_query_failed", err.Error())
|
return nil, serverError("vip_config_query_failed", err.Error())
|
||||||
@ -88,7 +87,7 @@ func (s *Service) sendVIPGift(ctx context.Context, manager AuthUser, target user
|
|||||||
if !config.Enabled {
|
if !config.Enabled {
|
||||||
return nil, badRequest("vip_level_disabled", "vip level is disabled")
|
return nil, badRequest("vip_level_disabled", "vip level is disabled")
|
||||||
}
|
}
|
||||||
if config.Level < 1 || config.Level > maxGiftableVIPLevel {
|
if config.Level < 1 || config.Level > 5 {
|
||||||
return nil, badRequest("vip_not_giftable", "vip level is invalid")
|
return nil, badRequest("vip_not_giftable", "vip level is invalid")
|
||||||
}
|
}
|
||||||
if config.DurationDays <= 0 {
|
if config.DurationDays <= 0 {
|
||||||
|
|||||||
@ -1,235 +0,0 @@
|
|||||||
package propsstore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/model"
|
|
||||||
"chatapp3-golang/internal/utils"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MapBySourceIDs returns active store commodities keyed by source id.
|
|
||||||
func (s *Service) MapBySourceIDs(ctx context.Context, req MapBySourceIDsRequest) (map[string]CommodityView, error) {
|
|
||||||
if s == nil || s.db == nil {
|
|
||||||
return nil, NewAppError(http.StatusServiceUnavailable, "props_store_unavailable", "props store service is unavailable")
|
|
||||||
}
|
|
||||||
sysOrigin := normalizeSysOrigin(req.SysOrigin)
|
|
||||||
if sysOrigin == "" {
|
|
||||||
return nil, NewAppError(http.StatusBadRequest, "sys_origin_required", "sysOrigin is required")
|
|
||||||
}
|
|
||||||
sourceIDs := uniquePositiveSourceIDs(req.SourceIDs)
|
|
||||||
if len(sourceIDs) == 0 {
|
|
||||||
return map[string]CommodityView{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var rows []model.PropsCommodityStore
|
|
||||||
if err := s.db.WithContext(ctx).
|
|
||||||
Model(&model.PropsCommodityStore{}).
|
|
||||||
Where("sys_origin = ? AND source_id IN ?", sysOrigin, sourceIDs).
|
|
||||||
Where("(is_del = ? OR is_del IS NULL)", false).
|
|
||||||
Order("update_time DESC").
|
|
||||||
Find(&rows).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make(map[string]CommodityView, len(rows))
|
|
||||||
for _, row := range rows {
|
|
||||||
key := strconv.FormatInt(row.SourceID, 10)
|
|
||||||
if _, exists := result[key]; exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result[key] = commodityViewFromModel(row)
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveCommodity creates or updates one store commodity by sysOrigin and sourceId.
|
|
||||||
func (s *Service) SaveCommodity(ctx context.Context, req SaveCommodityRequest) (*CommodityView, error) {
|
|
||||||
if s == nil || s.db == nil {
|
|
||||||
return nil, NewAppError(http.StatusServiceUnavailable, "props_store_unavailable", "props store service is unavailable")
|
|
||||||
}
|
|
||||||
if err := validateSaveCommodityRequest(req); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sysOrigin := normalizeSysOrigin(req.SysOrigin)
|
|
||||||
sourceID := req.SourceID.Int64()
|
|
||||||
propsType := strings.ToUpper(strings.TrimSpace(req.PropsType))
|
|
||||||
discount := 0.0
|
|
||||||
if req.Discount != nil {
|
|
||||||
discount = *req.Discount
|
|
||||||
}
|
|
||||||
del := false
|
|
||||||
now := s.now()
|
|
||||||
var savedID int64
|
|
||||||
var previousPropsType string
|
|
||||||
|
|
||||||
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
|
||||||
var existing model.PropsCommodityStore
|
|
||||||
findErr := tx.Where("sys_origin = ? AND source_id = ?", sysOrigin, sourceID).
|
|
||||||
Order("update_time DESC").
|
|
||||||
First(&existing).Error
|
|
||||||
if findErr != nil && !errors.Is(findErr, gorm.ErrRecordNotFound) {
|
|
||||||
return findErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if findErr == nil {
|
|
||||||
previousPropsType = existing.PropsType
|
|
||||||
savedID = existing.ID
|
|
||||||
updates := map[string]any{
|
|
||||||
"sys_origin": sysOrigin,
|
|
||||||
"source_id": sourceID,
|
|
||||||
"props_type": propsType,
|
|
||||||
"currency_types": strings.ToUpper(strings.TrimSpace(req.CurrencyTypes)),
|
|
||||||
"valid_days": strings.TrimSpace(req.ValidDays),
|
|
||||||
"discount": discount,
|
|
||||||
"shelf_status": *req.ShelfStatus,
|
|
||||||
"sort": req.Sort,
|
|
||||||
"label": strings.TrimSpace(req.Label),
|
|
||||||
"is_del": del,
|
|
||||||
"update_time": now,
|
|
||||||
}
|
|
||||||
return tx.Model(&model.PropsCommodityStore{}).
|
|
||||||
Where("id = ?", existing.ID).
|
|
||||||
Updates(updates).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
id, idErr := utils.NextID()
|
|
||||||
if idErr != nil {
|
|
||||||
return idErr
|
|
||||||
}
|
|
||||||
savedID = id
|
|
||||||
row := model.PropsCommodityStore{
|
|
||||||
ID: id,
|
|
||||||
SysOrigin: sysOrigin,
|
|
||||||
SourceID: sourceID,
|
|
||||||
PropsType: propsType,
|
|
||||||
CurrencyTypes: strings.ToUpper(strings.TrimSpace(req.CurrencyTypes)),
|
|
||||||
ValidDays: strings.TrimSpace(req.ValidDays),
|
|
||||||
Discount: &discount,
|
|
||||||
ShelfStatus: req.ShelfStatus,
|
|
||||||
Sort: req.Sort,
|
|
||||||
Label: strings.TrimSpace(req.Label),
|
|
||||||
Del: &del,
|
|
||||||
CreateTime: &now,
|
|
||||||
UpdateTime: &now,
|
|
||||||
}
|
|
||||||
return tx.Create(&row).Error
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s.evictStoreCache(ctx, sysOrigin, propsType)
|
|
||||||
if strings.TrimSpace(previousPropsType) != "" && !strings.EqualFold(previousPropsType, propsType) {
|
|
||||||
s.evictStoreCache(ctx, sysOrigin, previousPropsType)
|
|
||||||
}
|
|
||||||
|
|
||||||
var saved model.PropsCommodityStore
|
|
||||||
if err := s.db.WithContext(ctx).Where("id = ?", savedID).First(&saved).Error; err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
view := commodityViewFromModel(saved)
|
|
||||||
return &view, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateSaveCommodityRequest(req SaveCommodityRequest) error {
|
|
||||||
if normalizeSysOrigin(req.SysOrigin) == "" {
|
|
||||||
return NewAppError(http.StatusBadRequest, "sys_origin_required", "sysOrigin is required")
|
|
||||||
}
|
|
||||||
if req.SourceID.Int64() <= 0 {
|
|
||||||
return NewAppError(http.StatusBadRequest, "source_id_required", "sourceId is required")
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(req.PropsType) == "" {
|
|
||||||
return NewAppError(http.StatusBadRequest, "props_type_required", "propsType is required")
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(req.CurrencyTypes) == "" {
|
|
||||||
return NewAppError(http.StatusBadRequest, "currency_types_required", "currencyTypes is required")
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(req.ValidDays) == "" {
|
|
||||||
return NewAppError(http.StatusBadRequest, "valid_days_required", "validDays is required")
|
|
||||||
}
|
|
||||||
if req.ShelfStatus == nil {
|
|
||||||
return NewAppError(http.StatusBadRequest, "shelf_status_required", "shelfStatus is required")
|
|
||||||
}
|
|
||||||
if req.Discount != nil && (*req.Discount < 0 || *req.Discount > 1) {
|
|
||||||
return NewAppError(http.StatusBadRequest, "invalid_discount", "discount must be between 0 and 1")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) evictStoreCache(ctx context.Context, sysOrigin string, propsType string) {
|
|
||||||
if s == nil || s.cache == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
propsType = strings.ToUpper(strings.TrimSpace(propsType))
|
|
||||||
if sysOrigin == "" || propsType == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
keys := []string{
|
|
||||||
propsStoreCacheKey(sysOrigin, propsType, "GOLD"),
|
|
||||||
propsStoreCacheKey(sysOrigin, propsType, "DIAMOND"),
|
|
||||||
propsStoreCacheKey(sysOrigin, propsType, "FREE"),
|
|
||||||
}
|
|
||||||
_ = s.cache.Del(ctx, keys...).Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func propsStoreCacheKey(sysOrigin string, propsType string, currencyType string) string {
|
|
||||||
return fmt.Sprintf("%s::%s_%s_%s", propsStoreCacheName, sysOrigin, propsType, currencyType)
|
|
||||||
}
|
|
||||||
|
|
||||||
func commodityViewFromModel(row model.PropsCommodityStore) CommodityView {
|
|
||||||
view := CommodityView{
|
|
||||||
ID: strconv.FormatInt(row.ID, 10),
|
|
||||||
SysOrigin: row.SysOrigin,
|
|
||||||
SourceID: strconv.FormatInt(row.SourceID, 10),
|
|
||||||
PropsType: row.PropsType,
|
|
||||||
CurrencyTypes: row.CurrencyTypes,
|
|
||||||
ValidDays: row.ValidDays,
|
|
||||||
Discount: row.Discount,
|
|
||||||
ShelfStatus: row.ShelfStatus,
|
|
||||||
Sort: row.Sort,
|
|
||||||
Label: row.Label,
|
|
||||||
Del: row.Del,
|
|
||||||
}
|
|
||||||
if row.CreateTime != nil && !row.CreateTime.IsZero() {
|
|
||||||
view.CreateTime = row.CreateTime.Format(timeLayout)
|
|
||||||
}
|
|
||||||
if row.UpdateTime != nil && !row.UpdateTime.IsZero() {
|
|
||||||
view.UpdateTime = row.UpdateTime.Format(timeLayout)
|
|
||||||
}
|
|
||||||
if row.CreateUser != nil {
|
|
||||||
view.CreateUser = strconv.FormatInt(*row.CreateUser, 10)
|
|
||||||
}
|
|
||||||
if row.UpdateUser != nil {
|
|
||||||
view.UpdateUser = strconv.FormatInt(*row.UpdateUser, 10)
|
|
||||||
}
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeSysOrigin(sysOrigin string) string {
|
|
||||||
return strings.ToUpper(strings.TrimSpace(sysOrigin))
|
|
||||||
}
|
|
||||||
|
|
||||||
func uniquePositiveSourceIDs(values []flexibleInt64) []int64 {
|
|
||||||
seen := make(map[int64]struct{}, len(values))
|
|
||||||
result := make([]int64, 0, len(values))
|
|
||||||
for _, value := range values {
|
|
||||||
id := value.Int64()
|
|
||||||
if id <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, exists := seen[id]; exists {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[id] = struct{}{}
|
|
||||||
result = append(result, id)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
@ -1,165 +0,0 @@
|
|||||||
package propsstore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/model"
|
|
||||||
|
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMapBySourceIDsReturnsLatestActiveCommodity(t *testing.T) {
|
|
||||||
db := newPropsStoreTestDB(t)
|
|
||||||
now := time.Date(2026, 5, 18, 12, 0, 0, 0, time.UTC)
|
|
||||||
older := now.Add(-time.Hour)
|
|
||||||
delFalse := false
|
|
||||||
delTrue := true
|
|
||||||
shelfFalse := false
|
|
||||||
shelfTrue := true
|
|
||||||
discount := 0.0
|
|
||||||
|
|
||||||
mustCreatePropsStore(t, db, model.PropsCommodityStore{
|
|
||||||
ID: 1,
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
SourceID: 100,
|
|
||||||
PropsType: "AVATAR_FRAME",
|
|
||||||
CurrencyTypes: "FREE",
|
|
||||||
ValidDays: "1",
|
|
||||||
Discount: &discount,
|
|
||||||
ShelfStatus: &shelfFalse,
|
|
||||||
Sort: 1,
|
|
||||||
Del: nil,
|
|
||||||
UpdateTime: &older,
|
|
||||||
})
|
|
||||||
mustCreatePropsStore(t, db, model.PropsCommodityStore{
|
|
||||||
ID: 2,
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
SourceID: 100,
|
|
||||||
PropsType: "AVATAR_FRAME",
|
|
||||||
CurrencyTypes: "GOLD",
|
|
||||||
ValidDays: "7",
|
|
||||||
Discount: &discount,
|
|
||||||
ShelfStatus: &shelfTrue,
|
|
||||||
Sort: 2,
|
|
||||||
Del: &delFalse,
|
|
||||||
UpdateTime: &now,
|
|
||||||
})
|
|
||||||
mustCreatePropsStore(t, db, model.PropsCommodityStore{
|
|
||||||
ID: 3,
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
SourceID: 200,
|
|
||||||
PropsType: "AVATAR_FRAME",
|
|
||||||
CurrencyTypes: "GOLD",
|
|
||||||
ValidDays: "7",
|
|
||||||
Discount: &discount,
|
|
||||||
ShelfStatus: &shelfTrue,
|
|
||||||
Sort: 1,
|
|
||||||
Del: &delTrue,
|
|
||||||
UpdateTime: &now,
|
|
||||||
})
|
|
||||||
|
|
||||||
service := NewService(db, nil)
|
|
||||||
got, err := service.MapBySourceIDs(context.Background(), MapBySourceIDsRequest{
|
|
||||||
SysOrigin: "likei",
|
|
||||||
SourceIDs: []flexibleInt64{
|
|
||||||
flexibleInt64(100),
|
|
||||||
flexibleInt64(200),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("MapBySourceIDs() error = %v", err)
|
|
||||||
}
|
|
||||||
if len(got) != 1 {
|
|
||||||
t.Fatalf("MapBySourceIDs() len = %d, want 1", len(got))
|
|
||||||
}
|
|
||||||
item, ok := got["100"]
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("MapBySourceIDs() missing source 100: %#v", got)
|
|
||||||
}
|
|
||||||
if item.ID != "2" || item.CurrencyTypes != "GOLD" || item.ShelfStatus == nil || !*item.ShelfStatus {
|
|
||||||
t.Fatalf("MapBySourceIDs() item = %#v", item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSaveCommodityUpdatesExistingAndRestoresDelFalse(t *testing.T) {
|
|
||||||
db := newPropsStoreTestDB(t)
|
|
||||||
now := time.Date(2026, 5, 18, 12, 0, 0, 0, time.UTC)
|
|
||||||
delTrue := true
|
|
||||||
shelfFalse := false
|
|
||||||
discount := 0.0
|
|
||||||
mustCreatePropsStore(t, db, model.PropsCommodityStore{
|
|
||||||
ID: 1,
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
SourceID: 100,
|
|
||||||
PropsType: "AVATAR_FRAME",
|
|
||||||
CurrencyTypes: "FREE",
|
|
||||||
ValidDays: "1",
|
|
||||||
Discount: &discount,
|
|
||||||
ShelfStatus: &shelfFalse,
|
|
||||||
Sort: 1,
|
|
||||||
Label: "old",
|
|
||||||
Del: &delTrue,
|
|
||||||
UpdateTime: &now,
|
|
||||||
})
|
|
||||||
|
|
||||||
shelfTrue := true
|
|
||||||
service := NewService(db, nil)
|
|
||||||
service.now = func() time.Time { return now.Add(time.Hour) }
|
|
||||||
got, err := service.SaveCommodity(context.Background(), SaveCommodityRequest{
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
SourceID: flexibleInt64(100),
|
|
||||||
PropsType: "AVATAR_FRAME",
|
|
||||||
CurrencyTypes: "GOLD",
|
|
||||||
ValidDays: "7",
|
|
||||||
Discount: &discount,
|
|
||||||
ShelfStatus: &shelfTrue,
|
|
||||||
Sort: 0,
|
|
||||||
Label: "",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("SaveCommodity() error = %v", err)
|
|
||||||
}
|
|
||||||
if got.ID != "1" || got.CurrencyTypes != "GOLD" || got.Label != "" {
|
|
||||||
t.Fatalf("SaveCommodity() = %#v", got)
|
|
||||||
}
|
|
||||||
|
|
||||||
var count int64
|
|
||||||
if err := db.Model(&model.PropsCommodityStore{}).Count(&count).Error; err != nil {
|
|
||||||
t.Fatalf("Count() error = %v", err)
|
|
||||||
}
|
|
||||||
if count != 1 {
|
|
||||||
t.Fatalf("row count = %d, want 1", count)
|
|
||||||
}
|
|
||||||
var row model.PropsCommodityStore
|
|
||||||
if err := db.First(&row, "id = ?", int64(1)).Error; err != nil {
|
|
||||||
t.Fatalf("First() error = %v", err)
|
|
||||||
}
|
|
||||||
if row.Del == nil || *row.Del {
|
|
||||||
t.Fatalf("row.Del = %v, want false", row.Del)
|
|
||||||
}
|
|
||||||
if row.ShelfStatus == nil || !*row.ShelfStatus {
|
|
||||||
t.Fatalf("row.ShelfStatus = %v, want true", row.ShelfStatus)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPropsStoreTestDB(t *testing.T) *gorm.DB {
|
|
||||||
t.Helper()
|
|
||||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("open sqlite: %v", err)
|
|
||||||
}
|
|
||||||
if err := db.AutoMigrate(&model.PropsCommodityStore{}); err != nil {
|
|
||||||
t.Fatalf("auto migrate: %v", err)
|
|
||||||
}
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustCreatePropsStore(t *testing.T, db *gorm.DB, row model.PropsCommodityStore) {
|
|
||||||
t.Helper()
|
|
||||||
if err := db.Create(&row).Error; err != nil {
|
|
||||||
t.Fatalf("create props store: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,116 +0,0 @@
|
|||||||
package propsstore
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/common"
|
|
||||||
|
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
propsStoreCacheName = "PROPS_STORE"
|
|
||||||
timeLayout = "2006-01-02 15:04:05"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AppError = common.AppError
|
|
||||||
|
|
||||||
var NewAppError = common.NewAppError
|
|
||||||
|
|
||||||
type dbHandle interface {
|
|
||||||
WithContext(ctx context.Context) *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service handles admin props store commodity operations.
|
|
||||||
type Service struct {
|
|
||||||
db dbHandle
|
|
||||||
cache redis.Cmdable
|
|
||||||
now func() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewService creates a props store service.
|
|
||||||
func NewService(db dbHandle, cache redis.Cmdable) *Service {
|
|
||||||
return &Service{
|
|
||||||
db: db,
|
|
||||||
cache: cache,
|
|
||||||
now: time.Now,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MapBySourceIDsRequest requests store commodities by props source ids.
|
|
||||||
type MapBySourceIDsRequest struct {
|
|
||||||
SysOrigin string `json:"sysOrigin"`
|
|
||||||
SourceIDs []flexibleInt64 `json:"sourceIds"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveCommodityRequest creates or updates one store commodity.
|
|
||||||
type SaveCommodityRequest struct {
|
|
||||||
ID flexibleInt64 `json:"id"`
|
|
||||||
SysOrigin string `json:"sysOrigin"`
|
|
||||||
SourceID flexibleInt64 `json:"sourceId"`
|
|
||||||
PropsType string `json:"propsType"`
|
|
||||||
CurrencyTypes string `json:"currencyTypes"`
|
|
||||||
ValidDays string `json:"validDays"`
|
|
||||||
Discount *float64 `json:"discount"`
|
|
||||||
ShelfStatus *bool `json:"shelfStatus"`
|
|
||||||
Sort int `json:"sort"`
|
|
||||||
Label string `json:"label"`
|
|
||||||
Del *bool `json:"del"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommodityView is the admin-facing props store commodity DTO.
|
|
||||||
type CommodityView struct {
|
|
||||||
ID string `json:"id,omitempty"`
|
|
||||||
SysOrigin string `json:"sysOrigin"`
|
|
||||||
SourceID string `json:"sourceId"`
|
|
||||||
PropsType string `json:"propsType"`
|
|
||||||
CurrencyTypes string `json:"currencyTypes"`
|
|
||||||
ValidDays string `json:"validDays"`
|
|
||||||
Discount *float64 `json:"discount"`
|
|
||||||
ShelfStatus *bool `json:"shelfStatus"`
|
|
||||||
Sort int `json:"sort"`
|
|
||||||
Label string `json:"label"`
|
|
||||||
Del *bool `json:"del"`
|
|
||||||
CreateTime string `json:"createTime,omitempty"`
|
|
||||||
UpdateTime string `json:"updateTime,omitempty"`
|
|
||||||
CreateUser string `json:"createUser,omitempty"`
|
|
||||||
UpdateUser string `json:"updateUser,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type flexibleInt64 int64
|
|
||||||
|
|
||||||
func (v *flexibleInt64) UnmarshalJSON(data []byte) error {
|
|
||||||
raw := strings.TrimSpace(string(data))
|
|
||||||
switch raw {
|
|
||||||
case "", "null", `""`:
|
|
||||||
*v = 0
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(raw, `"`) && strings.HasSuffix(raw, `"`) {
|
|
||||||
unquoted, err := strconv.Unquote(raw)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
raw = strings.TrimSpace(unquoted)
|
|
||||||
}
|
|
||||||
parsed, err := strconv.ParseInt(raw, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid int64 value: %w", err)
|
|
||||||
}
|
|
||||||
*v = flexibleInt64(parsed)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v flexibleInt64) Int64() int64 {
|
|
||||||
return int64(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v flexibleInt64) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(strconv.FormatInt(int64(v), 10))
|
|
||||||
}
|
|
||||||
@ -7,13 +7,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type energyRuleConfig struct {
|
type energyRuleConfig struct {
|
||||||
GoldRatio float64 `json:"goldRatio"`
|
GoldRatio float64 `json:"goldRatio"`
|
||||||
AllowedGiftTypes []string `json:"allowedGiftTypes"`
|
AllowedGiftTypes []string `json:"allowedGiftTypes"`
|
||||||
AllowedGiftTabs []string `json:"allowedGiftTabs"`
|
AllowedGiftTabs []string `json:"allowedGiftTabs"`
|
||||||
BlockedGiftTypes []string `json:"blockedGiftTypes"`
|
BlockedGiftTypes []string `json:"blockedGiftTypes"`
|
||||||
BlockedGiftTabs []string `json:"blockedGiftTabs"`
|
BlockedGiftTabs []string `json:"blockedGiftTabs"`
|
||||||
GiftRules []giftEnergyRule `json:"giftRules"`
|
GiftRules []giftEnergyRule `json:"giftRules"`
|
||||||
GiftTabRules []giftTabEnergyRule `json:"giftTabRules"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type giftEnergyRule struct {
|
type giftEnergyRule struct {
|
||||||
@ -26,16 +25,6 @@ type giftEnergyRule struct {
|
|||||||
Ratio float64 `json:"ratio"`
|
Ratio float64 `json:"ratio"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type giftTabEnergyRule struct {
|
|
||||||
GiftTab string `json:"giftTab"`
|
|
||||||
GiftTabs []string `json:"giftTabs"`
|
|
||||||
Enabled *bool `json:"enabled"`
|
|
||||||
Allow *bool `json:"allow"`
|
|
||||||
Energy int64 `json:"energy"`
|
|
||||||
EnergyPerUnit int64 `json:"energyPerUnit"`
|
|
||||||
Ratio float64 `json:"ratio"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveGiftEnergy(event giftEvent, snapshot configSnapshot) (int64, string) {
|
func resolveGiftEnergy(event giftEvent, snapshot configSnapshot) (int64, string) {
|
||||||
rule, err := parseEnergyRule(snapshot.EnergyRuleJSON)
|
rule, err := parseEnergyRule(snapshot.EnergyRuleJSON)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -51,9 +40,6 @@ func resolveGiftEnergy(event giftEvent, snapshot configSnapshot) (int64, string)
|
|||||||
if !isAllowedByGiftTypeAndTab(event, rule) {
|
if !isAllowedByGiftTypeAndTab(event, rule) {
|
||||||
return 0, "unsupported_gift"
|
return 0, "unsupported_gift"
|
||||||
}
|
}
|
||||||
if custom, ok, reason := applyGiftTabRule(event, base, rule); ok || reason != "" {
|
|
||||||
return custom, reason
|
|
||||||
}
|
|
||||||
ratio := rule.GoldRatio
|
ratio := rule.GoldRatio
|
||||||
if ratio <= 0 {
|
if ratio <= 0 {
|
||||||
ratio = 1
|
ratio = 1
|
||||||
@ -112,41 +98,6 @@ func applyGiftIDRule(event giftEvent, base int64, rule energyRuleConfig) (int64,
|
|||||||
return 0, false, ""
|
return 0, false, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyGiftTabRule(event giftEvent, base int64, rule energyRuleConfig) (int64, bool, string) {
|
|
||||||
giftTab := normalizeGiftToken(event.GiftConfig.GiftTab)
|
|
||||||
if giftTab == "" || len(rule.GiftTabRules) == 0 {
|
|
||||||
return 0, false, ""
|
|
||||||
}
|
|
||||||
for _, item := range rule.GiftTabRules {
|
|
||||||
if !giftTabRuleMatches(item, giftTab) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if item.Enabled != nil && !*item.Enabled {
|
|
||||||
return 0, true, "gift_tab_rule_disabled"
|
|
||||||
}
|
|
||||||
if item.Allow != nil && !*item.Allow {
|
|
||||||
return 0, true, "gift_tab_rule_blocked"
|
|
||||||
}
|
|
||||||
quantity := normalizedGiftQuantity(event)
|
|
||||||
acceptUserSize := normalizedAcceptUserSize(event)
|
|
||||||
if item.Energy > 0 {
|
|
||||||
return item.Energy * quantity * acceptUserSize, true, ""
|
|
||||||
}
|
|
||||||
if item.EnergyPerUnit > 0 {
|
|
||||||
return item.EnergyPerUnit * quantity * acceptUserSize, true, ""
|
|
||||||
}
|
|
||||||
if item.Ratio > 0 {
|
|
||||||
energy := int64(math.Floor(float64(base) * item.Ratio))
|
|
||||||
if energy <= 0 {
|
|
||||||
return 0, true, "zero_energy"
|
|
||||||
}
|
|
||||||
return energy, true, ""
|
|
||||||
}
|
|
||||||
return base, true, ""
|
|
||||||
}
|
|
||||||
return 0, false, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func giftRuleMatches(rule giftEnergyRule, giftID int64) bool {
|
func giftRuleMatches(rule giftEnergyRule, giftID int64) bool {
|
||||||
if rule.GiftID.Int64() == giftID {
|
if rule.GiftID.Int64() == giftID {
|
||||||
return true
|
return true
|
||||||
@ -159,18 +110,6 @@ func giftRuleMatches(rule giftEnergyRule, giftID int64) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func giftTabRuleMatches(rule giftTabEnergyRule, giftTab string) bool {
|
|
||||||
if normalizeGiftToken(rule.GiftTab) == giftTab {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, tab := range rule.GiftTabs {
|
|
||||||
if normalizeGiftToken(tab) == giftTab {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAllowedByGiftTypeAndTab(event giftEvent, rule energyRuleConfig) bool {
|
func isAllowedByGiftTypeAndTab(event giftEvent, rule energyRuleConfig) bool {
|
||||||
giftType := normalizeGiftToken(event.GiftConfig.Type)
|
giftType := normalizeGiftToken(event.GiftConfig.Type)
|
||||||
giftTab := normalizeGiftToken(event.GiftConfig.GiftTab)
|
giftTab := normalizeGiftToken(event.GiftConfig.GiftTab)
|
||||||
|
|||||||
@ -263,53 +263,6 @@ func TestGiftEnergyRulesRejectUnsupportedAndAllowConfiguredGift(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGiftTabEnergyRuleDiscountsLuckyGiftOnly(t *testing.T) {
|
|
||||||
service, db := newTestService(t)
|
|
||||||
ctx := context.Background()
|
|
||||||
seedRocketConfig(t, db)
|
|
||||||
seedLevelConfigs(t, db, []int64{1000, 2000, 3000, 4000, 5000, 6000})
|
|
||||||
|
|
||||||
if err := db.Model(&model.VoiceRoomRocketConfig{}).
|
|
||||||
Where("sys_origin = ?", "LIKEI").
|
|
||||||
Update("energy_rule_json", `{"giftTabRules":[{"giftTab":"LUCKY_GIFT","ratio":0.1}]}`).Error; err != nil {
|
|
||||||
t.Fatalf("update energy rule: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
luckyPayload := `{
|
|
||||||
"trackId":"track-lucky-ratio",
|
|
||||||
"sysOrigin":"LIKEI",
|
|
||||||
"sendUserId":"1001",
|
|
||||||
"roomId":"9001",
|
|
||||||
"quantity":1,
|
|
||||||
"giftConfig":{"id":"11","giftCandy":100,"type":"GOLD","giftTab":"LUCKY_GIFT"},
|
|
||||||
"createTime":` + strconvFormatMillis(time.Now()) + `
|
|
||||||
}`
|
|
||||||
lucky, err := service.ProcessGiftPayload(ctx, luckyPayload)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ProcessGiftPayload(lucky) error = %v", err)
|
|
||||||
}
|
|
||||||
if !lucky.Processed || lucky.AcceptedEnergy != 10 {
|
|
||||||
t.Fatalf("lucky = %+v, want 10 energy", lucky)
|
|
||||||
}
|
|
||||||
|
|
||||||
ordinaryPayload := `{
|
|
||||||
"trackId":"track-ordinary-ratio",
|
|
||||||
"sysOrigin":"LIKEI",
|
|
||||||
"sendUserId":"1001",
|
|
||||||
"roomId":"9001",
|
|
||||||
"quantity":1,
|
|
||||||
"giftConfig":{"id":"12","giftCandy":100,"type":"GOLD","giftTab":"ORDINARY"},
|
|
||||||
"createTime":` + strconvFormatMillis(time.Now().Add(time.Second)) + `
|
|
||||||
}`
|
|
||||||
ordinary, err := service.ProcessGiftPayload(ctx, ordinaryPayload)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ProcessGiftPayload(ordinary) error = %v", err)
|
|
||||||
}
|
|
||||||
if !ordinary.Processed || ordinary.AcceptedEnergy != 100 {
|
|
||||||
t.Fatalf("ordinary = %+v, want 100 energy", ordinary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInRoomRewardWorkerSnapshotsSelectsAndGrants(t *testing.T) {
|
func TestInRoomRewardWorkerSnapshotsSelectsAndGrants(t *testing.T) {
|
||||||
gateway := newFakeRocketGateway()
|
gateway := newFakeRocketGateway()
|
||||||
service, db, rdb, mr := newTestServiceWithRedis(t, gateway)
|
service, db, rdb, mr := newTestServiceWithRedis(t, gateway)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user