2026-05-21 11:52:06 +08:00

347 lines
14 KiB
Go

package luckygift
import (
"strings"
"time"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/integration/activityclient"
"hyapp-admin-server/internal/middleware"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/response"
activityv1 "hyapp.local/api/proto/activity/v1"
"github.com/gin-gonic/gin"
)
type Handler struct {
activity activityclient.Client
audit shared.OperationLogger
}
func New(activity activityclient.Client, audit shared.OperationLogger) *Handler {
return &Handler{activity: activity, audit: audit}
}
type configRequest struct {
PoolID string `json:"pool_id"`
Enabled bool `json:"enabled"`
GiftPrice int64 `json:"gift_price"`
TargetRTPPPM int64 `json:"target_rtp_ppm"`
PoolRatePPM int64 `json:"pool_rate_ppm"`
GlobalWindowDraws int64 `json:"global_window_draws"`
GiftWindowDraws int64 `json:"gift_window_draws"`
NoviceDrawLimit int64 `json:"novice_draw_limit"`
IntermediateDrawLimit int64 `json:"intermediate_draw_limit"`
HighMultiplier int64 `json:"high_multiplier"`
HighWaterPoolMultiple int64 `json:"high_water_pool_multiple"`
PlatformPoolWeightPPM int64 `json:"platform_pool_weight_ppm"`
RoomPoolWeightPPM int64 `json:"room_pool_weight_ppm"`
GiftPoolWeightPPM int64 `json:"gift_pool_weight_ppm"`
InitialPlatformPool int64 `json:"initial_platform_pool"`
InitialGiftPool int64 `json:"initial_gift_pool"`
InitialRoomPool int64 `json:"initial_room_pool"`
PlatformReserve int64 `json:"platform_reserve"`
GiftReserve int64 `json:"gift_reserve"`
RoomReserve int64 `json:"room_reserve"`
MaxSinglePayout int64 `json:"max_single_payout"`
UserHourlyPayoutCap int64 `json:"user_hourly_payout_cap"`
UserDailyPayoutCap int64 `json:"user_daily_payout_cap"`
DeviceDailyPayoutCap int64 `json:"device_daily_payout_cap"`
RoomHourlyPayoutCap int64 `json:"room_hourly_payout_cap"`
AnchorDailyPayoutCap int64 `json:"anchor_daily_payout_cap"`
RoomAtmosphereRatePPM int64 `json:"room_atmosphere_rate_ppm"`
RoomAtmosphereInitial int64 `json:"room_atmosphere_initial"`
RoomAtmosphereReserve int64 `json:"room_atmosphere_reserve"`
ActivityBudget int64 `json:"activity_budget"`
ActivityDailyLimit int64 `json:"activity_daily_limit"`
LargeTierEnabled bool `json:"large_tier_enabled"`
MultiplierPPMs []int64 `json:"multiplier_ppms"`
Tiers []tierDTO `json:"tiers"`
}
type configDTO struct {
configRequest
AppCode string `json:"app_code"`
GiftID string `json:"gift_id"`
RuleVersion int64 `json:"rule_version"`
UpdatedByAdminID int64 `json:"updated_by_admin_id"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
type tierDTO struct {
Pool string `json:"pool"`
TierID string `json:"tier_id"`
RewardCoins int64 `json:"reward_coins"`
MultiplierPPM int64 `json:"multiplier_ppm"`
Weight int64 `json:"weight"`
HighWaterOnly bool `json:"high_water_only"`
Enabled bool `json:"enabled"`
}
type drawDTO struct {
DrawID string `json:"draw_id"`
CommandID string `json:"command_id"`
PoolID string `json:"pool_id"`
GiftID string `json:"gift_id"`
RuleVersion int64 `json:"rule_version"`
ExperiencePool string `json:"experience_pool"`
SelectedTierID string `json:"selected_tier_id"`
BaseRewardCoins int64 `json:"base_reward_coins"`
RoomAtmosphereRewardCoins int64 `json:"room_atmosphere_reward_coins"`
ActivitySubsidyCoins int64 `json:"activity_subsidy_coins"`
EffectiveRewardCoins int64 `json:"effective_reward_coins"`
BudgetSourcesJSON string `json:"budget_sources_json"`
RewardStatus string `json:"reward_status"`
RTPWindowIndex int64 `json:"rtp_window_index"`
GiftRTPWindowIndex int64 `json:"gift_rtp_window_index"`
GlobalBaseRTPPPM int64 `json:"global_base_rtp_ppm"`
GiftBaseRTPPPM int64 `json:"gift_base_rtp_ppm"`
StageFeedback bool `json:"stage_feedback"`
HighMultiplier bool `json:"high_multiplier"`
CreatedAtMS int64 `json:"created_at_ms"`
}
func (h *Handler) GetLuckyGiftConfig(c *gin.Context) {
resp, err := h.activity.GetLuckyGiftConfig(c.Request.Context(), &activityv1.GetLuckyGiftConfigRequest{
Meta: h.meta(c),
PoolId: strings.TrimSpace(c.Query("pool_id")),
})
if err != nil {
response.ServerError(c, "获取幸运礼物配置失败")
return
}
response.OK(c, configFromProto(resp.GetConfig()))
}
func (h *Handler) UpsertLuckyGiftConfig(c *gin.Context) {
var req configRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "幸运礼物配置参数不正确")
return
}
if req.PoolID == "" {
req.PoolID = strings.TrimSpace(c.Query("pool_id"))
}
resp, err := h.activity.UpsertLuckyGiftConfig(c.Request.Context(), &activityv1.UpsertLuckyGiftConfigRequest{
Meta: h.meta(c),
Config: configToProto(req),
OperatorAdminId: int64(middleware.CurrentUserID(c)),
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
item := configFromProto(resp.GetConfig())
shared.OperationLogWithResourceID(c, h.audit, "upsert-lucky-gift-config", "lucky_gift_rules", item.PoolID, "success", "")
response.OK(c, item)
}
func (h *Handler) ListLuckyGiftConfigs(c *gin.Context) {
resp, err := h.activity.ListLuckyGiftConfigs(c.Request.Context(), &activityv1.ListLuckyGiftConfigsRequest{
Meta: h.meta(c),
})
if err != nil {
response.ServerError(c, "获取幸运礼物奖池列表失败")
return
}
items := make([]configDTO, 0, len(resp.GetConfigs()))
for _, config := range resp.GetConfigs() {
items = append(items, configFromProto(config))
}
response.OK(c, items)
}
func (h *Handler) ListLuckyGiftDraws(c *gin.Context) {
options := shared.ListOptions(c)
resp, err := h.activity.ListLuckyGiftDraws(c.Request.Context(), &activityv1.ListLuckyGiftDrawsRequest{
Meta: h.meta(c),
PoolId: strings.TrimSpace(c.Query("pool_id")),
GiftId: strings.TrimSpace(c.Query("gift_id")),
UserId: parseOptionalInt64(c.Query("user_id")),
RoomId: strings.TrimSpace(c.Query("room_id")),
Status: strings.TrimSpace(options.Status),
Page: int32(options.Page),
PageSize: int32(options.PageSize),
})
if err != nil {
response.ServerError(c, "获取幸运礼物抽奖记录失败")
return
}
items := make([]drawDTO, 0, len(resp.GetDraws()))
for _, draw := range resp.GetDraws() {
items = append(items, drawFromProto(draw))
}
response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}
func (h *Handler) meta(c *gin.Context) *activityv1.RequestMeta {
return &activityv1.RequestMeta{
RequestId: middleware.CurrentRequestID(c),
Caller: "admin-server",
AppCode: appctx.FromContext(c.Request.Context()),
SentAtMs: time.Now().UTC().UnixMilli(),
}
}
func configToProto(req configRequest) *activityv1.LuckyGiftConfig {
tiers := make([]*activityv1.LuckyGiftTier, 0, len(req.Tiers))
for _, tier := range req.Tiers {
tiers = append(tiers, &activityv1.LuckyGiftTier{
Pool: tier.Pool,
TierId: tier.TierID,
RewardCoins: tier.RewardCoins,
MultiplierPpm: tier.MultiplierPPM,
Weight: tier.Weight,
HighWaterOnly: tier.HighWaterOnly,
Enabled: tier.Enabled,
})
}
return &activityv1.LuckyGiftConfig{
PoolId: strings.TrimSpace(req.PoolID),
Enabled: req.Enabled,
GiftPrice: req.GiftPrice,
TargetRtpPpm: req.TargetRTPPPM,
PoolRatePpm: req.PoolRatePPM,
GlobalWindowDraws: req.GlobalWindowDraws,
GiftWindowDraws: req.GiftWindowDraws,
NoviceDrawLimit: req.NoviceDrawLimit,
IntermediateDrawLimit: req.IntermediateDrawLimit,
HighMultiplier: req.HighMultiplier,
HighWaterPoolMultiple: req.HighWaterPoolMultiple,
PlatformPoolWeightPpm: req.PlatformPoolWeightPPM,
RoomPoolWeightPpm: req.RoomPoolWeightPPM,
GiftPoolWeightPpm: req.GiftPoolWeightPPM,
InitialPlatformPool: req.InitialPlatformPool,
InitialGiftPool: req.InitialGiftPool,
InitialRoomPool: req.InitialRoomPool,
PlatformReserve: req.PlatformReserve,
GiftReserve: req.GiftReserve,
RoomReserve: req.RoomReserve,
MaxSinglePayout: req.MaxSinglePayout,
UserHourlyPayoutCap: req.UserHourlyPayoutCap,
UserDailyPayoutCap: req.UserDailyPayoutCap,
DeviceDailyPayoutCap: req.DeviceDailyPayoutCap,
RoomHourlyPayoutCap: req.RoomHourlyPayoutCap,
AnchorDailyPayoutCap: req.AnchorDailyPayoutCap,
RoomAtmosphereRatePpm: req.RoomAtmosphereRatePPM,
RoomAtmosphereInitial: req.RoomAtmosphereInitial,
RoomAtmosphereReserve: req.RoomAtmosphereReserve,
ActivityBudget: req.ActivityBudget,
ActivityDailyLimit: req.ActivityDailyLimit,
LargeTierEnabled: req.LargeTierEnabled,
MultiplierPpms: append([]int64(nil), req.MultiplierPPMs...),
Tiers: tiers,
}
}
func configFromProto(config *activityv1.LuckyGiftConfig) configDTO {
if config == nil {
return configDTO{}
}
tiers := make([]tierDTO, 0, len(config.GetTiers()))
for _, tier := range config.GetTiers() {
tiers = append(tiers, tierDTO{
Pool: tier.GetPool(),
TierID: tier.GetTierId(),
RewardCoins: tier.GetRewardCoins(),
MultiplierPPM: tier.GetMultiplierPpm(),
Weight: tier.GetWeight(),
HighWaterOnly: tier.GetHighWaterOnly(),
Enabled: tier.GetEnabled(),
})
}
poolID := strings.TrimSpace(config.GetPoolId())
if poolID == "" {
poolID = strings.TrimSpace(config.GetGiftId())
}
return configDTO{
AppCode: config.GetAppCode(),
GiftID: config.GetGiftId(),
RuleVersion: config.GetRuleVersion(),
UpdatedByAdminID: config.GetUpdatedByAdminId(),
CreatedAtMS: config.GetCreatedAtMs(),
UpdatedAtMS: config.GetUpdatedAtMs(),
configRequest: configRequest{
PoolID: poolID,
Enabled: config.GetEnabled(),
GiftPrice: config.GetGiftPrice(),
TargetRTPPPM: config.GetTargetRtpPpm(),
PoolRatePPM: config.GetPoolRatePpm(),
GlobalWindowDraws: config.GetGlobalWindowDraws(),
GiftWindowDraws: config.GetGiftWindowDraws(),
NoviceDrawLimit: config.GetNoviceDrawLimit(),
IntermediateDrawLimit: config.GetIntermediateDrawLimit(),
HighMultiplier: config.GetHighMultiplier(),
HighWaterPoolMultiple: config.GetHighWaterPoolMultiple(),
PlatformPoolWeightPPM: config.GetPlatformPoolWeightPpm(),
RoomPoolWeightPPM: config.GetRoomPoolWeightPpm(),
GiftPoolWeightPPM: config.GetGiftPoolWeightPpm(),
InitialPlatformPool: config.GetInitialPlatformPool(),
InitialGiftPool: config.GetInitialGiftPool(),
InitialRoomPool: config.GetInitialRoomPool(),
PlatformReserve: config.GetPlatformReserve(),
GiftReserve: config.GetGiftReserve(),
RoomReserve: config.GetRoomReserve(),
MaxSinglePayout: config.GetMaxSinglePayout(),
UserHourlyPayoutCap: config.GetUserHourlyPayoutCap(),
UserDailyPayoutCap: config.GetUserDailyPayoutCap(),
DeviceDailyPayoutCap: config.GetDeviceDailyPayoutCap(),
RoomHourlyPayoutCap: config.GetRoomHourlyPayoutCap(),
AnchorDailyPayoutCap: config.GetAnchorDailyPayoutCap(),
RoomAtmosphereRatePPM: config.GetRoomAtmosphereRatePpm(),
RoomAtmosphereInitial: config.GetRoomAtmosphereInitial(),
RoomAtmosphereReserve: config.GetRoomAtmosphereReserve(),
ActivityBudget: config.GetActivityBudget(),
ActivityDailyLimit: config.GetActivityDailyLimit(),
LargeTierEnabled: config.GetLargeTierEnabled(),
MultiplierPPMs: append([]int64(nil), config.GetMultiplierPpms()...),
Tiers: tiers,
},
}
}
func drawFromProto(draw *activityv1.LuckyGiftDrawResult) drawDTO {
if draw == nil {
return drawDTO{}
}
return drawDTO{
DrawID: draw.GetDrawId(),
CommandID: draw.GetCommandId(),
PoolID: draw.GetPoolId(),
GiftID: draw.GetGiftId(),
RuleVersion: draw.GetRuleVersion(),
ExperiencePool: draw.GetExperiencePool(),
SelectedTierID: draw.GetSelectedTierId(),
BaseRewardCoins: draw.GetBaseRewardCoins(),
RoomAtmosphereRewardCoins: draw.GetRoomAtmosphereRewardCoins(),
ActivitySubsidyCoins: draw.GetActivitySubsidyCoins(),
EffectiveRewardCoins: draw.GetEffectiveRewardCoins(),
BudgetSourcesJSON: draw.GetBudgetSourcesJson(),
RewardStatus: draw.GetRewardStatus(),
RTPWindowIndex: draw.GetRtpWindowIndex(),
GiftRTPWindowIndex: draw.GetGiftRtpWindowIndex(),
GlobalBaseRTPPPM: draw.GetGlobalBaseRtpPpm(),
GiftBaseRTPPPM: draw.GetGiftBaseRtpPpm(),
StageFeedback: draw.GetStageFeedback(),
HighMultiplier: draw.GetHighMultiplier(),
CreatedAtMS: draw.GetCreatedAtMs(),
}
}
func parseOptionalInt64(value string) int64 {
value = strings.TrimSpace(value)
if value == "" {
return 0
}
var result int64
for _, char := range value {
if char < '0' || char > '9' {
return 0
}
result = result*10 + int64(char-'0')
}
return result
}