636 lines
28 KiB
Go
636 lines
28 KiB
Go
package grpc
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
|
||
luckygiftv1 "hyapp.local/api/proto/luckygift/v1"
|
||
"hyapp/pkg/appcode"
|
||
"hyapp/pkg/xerr"
|
||
domain "hyapp/services/lucky-gift-service/internal/domain/luckygift"
|
||
service "hyapp/services/lucky-gift-service/internal/service/luckygift"
|
||
)
|
||
|
||
// LuckyGiftServer 暴露房间送礼主链路调用的幸运礼物查询和抽奖入口。
|
||
type LuckyGiftServer struct {
|
||
luckygiftv1.UnimplementedLuckyGiftServiceServer
|
||
|
||
svc *service.Service
|
||
}
|
||
|
||
func NewLuckyGiftServer(svc *service.Service) *LuckyGiftServer {
|
||
return &LuckyGiftServer{svc: svc}
|
||
}
|
||
|
||
func (s *LuckyGiftServer) CheckLuckyGift(ctx context.Context, req *luckygiftv1.CheckLuckyGiftRequest) (*luckygiftv1.CheckLuckyGiftResponse, error) {
|
||
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||
result, err := s.svc.Check(ctx, domain.CheckCommand{
|
||
PoolID: req.GetPoolId(),
|
||
GiftID: req.GetGiftId(),
|
||
UserID: req.GetUserId(),
|
||
RoomID: req.GetRoomId(),
|
||
})
|
||
if err != nil {
|
||
return nil, xerr.ToGRPCError(err)
|
||
}
|
||
return &luckygiftv1.CheckLuckyGiftResponse{
|
||
Enabled: result.Enabled,
|
||
Reason: result.Reason,
|
||
PoolId: result.PoolID,
|
||
GiftId: result.GiftID,
|
||
GiftPrice: result.GiftPrice,
|
||
RuleVersion: result.RuleVersion,
|
||
StrategyVersion: result.StrategyVersion,
|
||
TargetRtpPpm: result.TargetRTPPPM,
|
||
ExperiencePool: result.ExperiencePool,
|
||
}, nil
|
||
}
|
||
|
||
func (s *LuckyGiftServer) ExecuteLuckyGiftDraw(ctx context.Context, req *luckygiftv1.ExecuteLuckyGiftDrawRequest) (*luckygiftv1.ExecuteLuckyGiftDrawResponse, error) {
|
||
meta := req.GetLuckyGift()
|
||
ctx = appcode.WithContext(ctx, meta.GetMeta().GetAppCode())
|
||
result, err := s.svc.Draw(ctx, luckyDrawCommandFromProto(meta))
|
||
if err != nil {
|
||
return nil, xerr.ToGRPCError(err)
|
||
}
|
||
return &luckygiftv1.ExecuteLuckyGiftDrawResponse{Result: luckyDrawResultToProto(result)}, nil
|
||
}
|
||
|
||
func (s *LuckyGiftServer) BatchExecuteLuckyGiftDraw(ctx context.Context, req *luckygiftv1.BatchExecuteLuckyGiftDrawRequest) (*luckygiftv1.BatchExecuteLuckyGiftDrawResponse, error) {
|
||
if len(req.GetLuckyGifts()) == 0 {
|
||
return nil, xerr.ToGRPCError(xerr.New(xerr.InvalidArgument, "lucky gift batch draw commands are required"))
|
||
}
|
||
appCode := strings.TrimSpace(req.GetLuckyGifts()[0].GetMeta().GetAppCode())
|
||
if appCode == "" {
|
||
return nil, xerr.ToGRPCError(xerr.New(xerr.InvalidArgument, "lucky gift batch app_code is required"))
|
||
}
|
||
ctx = appcode.WithContext(ctx, appCode)
|
||
cmds := make([]domain.DrawCommand, 0, len(req.GetLuckyGifts()))
|
||
for _, meta := range req.GetLuckyGifts() {
|
||
// 一个批次共享同一条房间送礼命令和同一个 app 账本;混入其他 app_code 会污染第一条 app 的 RTP 和单池账本。
|
||
if strings.TrimSpace(meta.GetMeta().GetAppCode()) != appCode {
|
||
return nil, xerr.ToGRPCError(xerr.New(xerr.InvalidArgument, "lucky gift batch app_code mismatch"))
|
||
}
|
||
cmds = append(cmds, luckyDrawCommandFromProto(meta))
|
||
}
|
||
results, err := s.svc.DrawBatch(ctx, cmds)
|
||
if err != nil {
|
||
return nil, xerr.ToGRPCError(err)
|
||
}
|
||
resp := &luckygiftv1.BatchExecuteLuckyGiftDrawResponse{Results: make([]*luckygiftv1.LuckyGiftDrawResult, 0, len(results))}
|
||
for _, result := range results {
|
||
resp.Results = append(resp.Results, luckyDrawResultToProto(result))
|
||
}
|
||
return resp, nil
|
||
}
|
||
|
||
func (s *LuckyGiftServer) ExecuteExternalGiftDraw(ctx context.Context, req *luckygiftv1.ExternalGiftDrawRequest) (*luckygiftv1.ExternalGiftDrawResponse, error) {
|
||
ctx = appcode.WithContext(ctx, req.GetAppCode())
|
||
result, err := s.svc.ExecuteExternalDraw(ctx, domain.ExternalDrawCommand{
|
||
AppCode: req.GetAppCode(),
|
||
ExternalUserID: req.GetExternalUserId(),
|
||
DeviceID: req.GetDeviceId(),
|
||
RequestID: req.GetRequestId(),
|
||
GiftCount: req.GetGiftCount(),
|
||
UnitAmount: req.GetUnitAmount(),
|
||
TotalAmount: req.GetTotalAmount(),
|
||
Currency: req.GetCurrency(),
|
||
PaidAtMS: req.GetPaidAtMs(),
|
||
MetadataJSON: req.GetMetadataJson(),
|
||
PoolID: req.GetPoolId(),
|
||
UserRegisteredAtMS: req.GetUserRegisteredAtMs(),
|
||
})
|
||
if err != nil {
|
||
return nil, xerr.ToGRPCError(err)
|
||
}
|
||
return externalDrawResultToProto(result), nil
|
||
}
|
||
|
||
func luckyDrawCommandFromProto(meta *luckygiftv1.LuckyGiftMeta) domain.DrawCommand {
|
||
return domain.DrawCommand{
|
||
CommandID: meta.GetCommandId(),
|
||
PoolID: meta.GetPoolId(),
|
||
UserID: meta.GetUserId(),
|
||
TargetUserID: meta.GetTargetUserId(),
|
||
DeviceID: meta.GetDeviceId(),
|
||
RoomID: meta.GetRoomId(),
|
||
AnchorID: meta.GetAnchorId(),
|
||
GiftID: meta.GetGiftId(),
|
||
GiftCount: meta.GetGiftCount(),
|
||
CoinSpent: meta.GetCoinSpent(),
|
||
PaidAtMS: meta.GetPaidAtMs(),
|
||
UserRegisteredAtMS: meta.GetUserRegisteredAtMs(),
|
||
VisibleRegionID: meta.GetVisibleRegionId(),
|
||
CountryID: meta.GetCountryId(),
|
||
SenderName: meta.GetSenderName(),
|
||
SenderAvatar: meta.GetSenderAvatar(),
|
||
SenderDisplayUserID: meta.GetSenderDisplayUserId(),
|
||
SenderPrettyDisplayUserID: meta.GetSenderPrettyDisplayUserId(),
|
||
// 充值分层、短时加权与主播风控只消费 wallet-service 已落账的事实,
|
||
// lucky-gift-service 不跨 owner 数据库反查并重新解释同一笔交易。
|
||
Recharge7DCoins: meta.GetRecharge_7DCoins(),
|
||
Recharge30DCoins: meta.GetRecharge_30DCoins(),
|
||
LastRechargedAtMS: meta.GetLastRechargedAtMs(),
|
||
GiftIncomeCoins: meta.GetGiftIncomeCoins(),
|
||
}
|
||
}
|
||
|
||
// AdminLuckyGiftServer 暴露后台幸运礼物规则配置和抽奖审计入口。
|
||
type AdminLuckyGiftServer struct {
|
||
luckygiftv1.UnimplementedAdminLuckyGiftServiceServer
|
||
|
||
svc *service.Service
|
||
}
|
||
|
||
func NewAdminLuckyGiftServer(svc *service.Service) *AdminLuckyGiftServer {
|
||
return &AdminLuckyGiftServer{svc: svc}
|
||
}
|
||
|
||
func (s *AdminLuckyGiftServer) GetLuckyGiftConfig(ctx context.Context, req *luckygiftv1.GetLuckyGiftConfigRequest) (*luckygiftv1.GetLuckyGiftConfigResponse, error) {
|
||
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||
config, err := s.svc.GetConfig(ctx, req.GetPoolId())
|
||
if err != nil {
|
||
return nil, xerr.ToGRPCError(err)
|
||
}
|
||
return &luckygiftv1.GetLuckyGiftConfigResponse{Config: luckyRuleConfigToProto(config)}, nil
|
||
}
|
||
|
||
func (s *AdminLuckyGiftServer) UpsertLuckyGiftConfig(ctx context.Context, req *luckygiftv1.UpsertLuckyGiftConfigRequest) (*luckygiftv1.UpsertLuckyGiftConfigResponse, error) {
|
||
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||
config := luckyRuleConfigFromProto(req.GetConfig())
|
||
config.CreatedByAdminID = req.GetOperatorAdminId()
|
||
updated, err := s.svc.UpsertConfig(ctx, config)
|
||
if err != nil {
|
||
return nil, xerr.ToGRPCError(err)
|
||
}
|
||
return &luckygiftv1.UpsertLuckyGiftConfigResponse{Config: luckyRuleConfigToProto(updated)}, nil
|
||
}
|
||
|
||
func (s *AdminLuckyGiftServer) ListLuckyGiftConfigs(ctx context.Context, req *luckygiftv1.ListLuckyGiftConfigsRequest) (*luckygiftv1.ListLuckyGiftConfigsResponse, error) {
|
||
rawAppCode := req.GetMeta().GetAppCode()
|
||
if rawAppCode != "" {
|
||
ctx = appcode.WithContext(ctx, rawAppCode)
|
||
}
|
||
configs, err := s.svc.ListConfigs(ctx, rawAppCode)
|
||
if err != nil {
|
||
return nil, xerr.ToGRPCError(err)
|
||
}
|
||
resp := &luckygiftv1.ListLuckyGiftConfigsResponse{Configs: make([]*luckygiftv1.LuckyGiftRuleConfig, 0, len(configs))}
|
||
for _, config := range configs {
|
||
resp.Configs = append(resp.Configs, luckyRuleConfigToProto(config))
|
||
}
|
||
return resp, nil
|
||
}
|
||
|
||
func (s *AdminLuckyGiftServer) ListLuckyGiftDraws(ctx context.Context, req *luckygiftv1.ListLuckyGiftDrawsRequest) (*luckygiftv1.ListLuckyGiftDrawsResponse, error) {
|
||
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||
draws, total, err := s.svc.ListDraws(ctx, domain.DrawQuery{
|
||
AppCode: req.GetMeta().GetAppCode(),
|
||
GiftID: req.GetGiftId(),
|
||
PoolID: req.GetPoolId(),
|
||
UserID: req.GetUserId(),
|
||
RoomID: req.GetRoomId(),
|
||
Status: req.GetStatus(),
|
||
Page: req.GetPage(),
|
||
PageSize: req.GetPageSize(),
|
||
ExternalUserID: req.GetExternalUserId(),
|
||
ExternalOnly: req.GetExternalOnly(),
|
||
})
|
||
if err != nil {
|
||
return nil, xerr.ToGRPCError(err)
|
||
}
|
||
resp := &luckygiftv1.ListLuckyGiftDrawsResponse{Draws: make([]*luckygiftv1.LuckyGiftDrawResult, 0, len(draws)), Total: total}
|
||
for _, draw := range draws {
|
||
resp.Draws = append(resp.Draws, luckyDrawResultToProto(draw))
|
||
}
|
||
return resp, nil
|
||
}
|
||
|
||
func (s *AdminLuckyGiftServer) GetLuckyGiftDrawSummary(ctx context.Context, req *luckygiftv1.GetLuckyGiftDrawSummaryRequest) (*luckygiftv1.GetLuckyGiftDrawSummaryResponse, error) {
|
||
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||
summary, err := s.svc.GetDrawSummary(ctx, domain.DrawQuery{
|
||
AppCode: req.GetMeta().GetAppCode(),
|
||
GiftID: req.GetGiftId(),
|
||
PoolID: req.GetPoolId(),
|
||
UserID: req.GetUserId(),
|
||
RoomID: req.GetRoomId(),
|
||
Status: req.GetStatus(),
|
||
ExternalUserID: req.GetExternalUserId(),
|
||
ExternalOnly: req.GetExternalOnly(),
|
||
})
|
||
if err != nil {
|
||
return nil, xerr.ToGRPCError(err)
|
||
}
|
||
return &luckygiftv1.GetLuckyGiftDrawSummaryResponse{Summary: luckyDrawSummaryToProto(summary)}, nil
|
||
}
|
||
|
||
func (s *AdminLuckyGiftServer) ListLuckyGiftUserProfiles(ctx context.Context, req *luckygiftv1.ListLuckyGiftUserProfilesRequest) (*luckygiftv1.ListLuckyGiftUserProfilesResponse, error) {
|
||
appCode := strings.TrimSpace(req.GetMeta().GetAppCode())
|
||
ctx = appcode.WithContext(ctx, appCode)
|
||
profiles, total, nextCursor, hasMore, snapshotAtMS, err := s.svc.ListUserProfiles(ctx, domain.UserProfileQuery{
|
||
AppCode: appCode,
|
||
PoolID: req.GetPoolId(),
|
||
IdentityType: req.GetIdentityType(),
|
||
InternalUserID: req.GetInternalUserId(),
|
||
ExternalUserID: req.GetExternalUserId(),
|
||
Stage: req.GetStage(),
|
||
Status: req.GetStatus(),
|
||
SortBy: req.GetSortBy(),
|
||
SortDirection: req.GetSortDirection(),
|
||
Cursor: req.GetCursor(),
|
||
Page: req.GetPage(),
|
||
PageSize: req.GetPageSize(),
|
||
})
|
||
if err != nil {
|
||
return nil, xerr.ToGRPCError(err)
|
||
}
|
||
resp := &luckygiftv1.ListLuckyGiftUserProfilesResponse{
|
||
Profiles: make([]*luckygiftv1.LuckyGiftUserProfile, 0, len(profiles)),
|
||
Total: total,
|
||
SnapshotAtMs: snapshotAtMS,
|
||
NextCursor: nextCursor,
|
||
HasMore: hasMore,
|
||
}
|
||
for _, profile := range profiles {
|
||
resp.Profiles = append(resp.Profiles, luckyUserProfileToProto(profile))
|
||
}
|
||
return resp, nil
|
||
}
|
||
|
||
func (s *AdminLuckyGiftServer) GetLuckyGiftUserProfile(ctx context.Context, req *luckygiftv1.GetLuckyGiftUserProfileRequest) (*luckygiftv1.GetLuckyGiftUserProfileResponse, error) {
|
||
appCode := strings.TrimSpace(req.GetMeta().GetAppCode())
|
||
ctx = appcode.WithContext(ctx, appCode)
|
||
detail, snapshotAtMS, err := s.svc.GetUserProfile(ctx, domain.UserProfileQuery{
|
||
AppCode: appCode,
|
||
PoolID: req.GetPoolId(),
|
||
IdentityType: req.GetIdentityType(),
|
||
InternalUserID: req.GetInternalUserId(),
|
||
ExternalUserID: req.GetExternalUserId(),
|
||
JackpotPage: req.GetJackpotPage(),
|
||
JackpotPageSize: req.GetJackpotPageSize(),
|
||
JackpotCursor: req.GetJackpotCursor(),
|
||
})
|
||
if err != nil {
|
||
return nil, xerr.ToGRPCError(err)
|
||
}
|
||
resp := &luckygiftv1.GetLuckyGiftUserProfileResponse{
|
||
Profile: luckyUserProfileToProto(detail.Profile),
|
||
MultiplierDistribution: make([]*luckygiftv1.LuckyGiftUserMultiplierStat, 0, len(detail.MultiplierDistribution)),
|
||
JackpotHits: make([]*luckygiftv1.LuckyGiftUserJackpotHit, 0, len(detail.JackpotHits)),
|
||
JackpotHitTotal: detail.JackpotHitTotal,
|
||
SnapshotAtMs: snapshotAtMS,
|
||
JackpotPage: detail.JackpotPage,
|
||
JackpotPageSize: detail.JackpotPageSize,
|
||
NextJackpotCursor: detail.NextJackpotCursor,
|
||
JackpotHasMore: detail.JackpotHasMore,
|
||
}
|
||
for _, stat := range detail.MultiplierDistribution {
|
||
resp.MultiplierDistribution = append(resp.MultiplierDistribution, &luckygiftv1.LuckyGiftUserMultiplierStat{
|
||
HitType: stat.HitType, MultiplierPpm: stat.MultiplierPPM, DrawCount: stat.DrawCount,
|
||
WagerCoins: stat.WagerCoins, PayoutCoins: stat.PayoutCoins,
|
||
})
|
||
}
|
||
for _, hit := range detail.JackpotHits {
|
||
item := &luckygiftv1.LuckyGiftUserJackpotHit{
|
||
DrawId: hit.DrawID, RequestId: hit.RequestID, RuleVersion: hit.RuleVersion, Stage: hit.Stage,
|
||
EventKey: hit.SourceType + ":" + hit.DrawID,
|
||
Mechanism: hit.Mechanism, ReasonCode: hit.ReasonCode, ReasonSummary: hit.ReasonSummary,
|
||
TierId: hit.TierID, MultiplierPpm: hit.MultiplierPPM, WagerCoins: hit.WagerCoins,
|
||
PayoutCoins: hit.PayoutCoins, OccurredAtMs: hit.OccurredAtMS,
|
||
Conditions: make([]*luckygiftv1.LuckyGiftUserJackpotCondition, 0, len(hit.Conditions)),
|
||
}
|
||
for _, condition := range hit.Conditions {
|
||
item.Conditions = append(item.Conditions, &luckygiftv1.LuckyGiftUserJackpotCondition{
|
||
Name: condition.Name, Numerator: condition.Numerator, Denominator: condition.Denominator,
|
||
RatioPpm: condition.RatioPPM, LimitPpm: condition.LimitPPM, FactorPpm: condition.FactorPPM,
|
||
Passed: condition.Passed, Reason: condition.Reason,
|
||
})
|
||
}
|
||
resp.JackpotHits = append(resp.JackpotHits, item)
|
||
}
|
||
return resp, nil
|
||
}
|
||
|
||
func (s *AdminLuckyGiftServer) ListLuckyGiftPoolBalances(ctx context.Context, req *luckygiftv1.ListLuckyGiftPoolBalancesRequest) (*luckygiftv1.ListLuckyGiftPoolBalancesResponse, error) {
|
||
appCode := req.GetMeta().GetAppCode()
|
||
if appCode != "" {
|
||
ctx = appcode.WithContext(ctx, appCode)
|
||
}
|
||
pools, err := s.svc.ListPoolBalances(ctx, domain.PoolBalanceQuery{
|
||
AppCode: appCode,
|
||
PoolID: req.GetPoolId(),
|
||
StrategyVersion: req.GetStrategyVersion(),
|
||
})
|
||
if err != nil {
|
||
return nil, xerr.ToGRPCError(err)
|
||
}
|
||
resp := &luckygiftv1.ListLuckyGiftPoolBalancesResponse{Pools: make([]*luckygiftv1.LuckyGiftPoolBalance, 0, len(pools))}
|
||
for _, pool := range pools {
|
||
resp.Pools = append(resp.Pools, luckyPoolBalanceToProto(pool))
|
||
}
|
||
return resp, nil
|
||
}
|
||
|
||
func (s *AdminLuckyGiftServer) AdjustLuckyGiftPoolBalance(ctx context.Context, req *luckygiftv1.AdjustLuckyGiftPoolBalanceRequest) (*luckygiftv1.AdjustLuckyGiftPoolBalanceResponse, error) {
|
||
if req == nil || strings.TrimSpace(req.GetMeta().GetAppCode()) == "" {
|
||
// 资金 RPC 必须 fail-closed;缺失 meta 不能像普通兼容查询一样静默落到默认 lalu 租户。
|
||
return nil, xerr.ToGRPCError(xerr.New(xerr.InvalidArgument, "lucky gift pool adjustment app_code is required"))
|
||
}
|
||
ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
|
||
result, err := s.svc.AdjustPoolBalance(ctx, domain.PoolAdjustmentCommand{
|
||
PoolID: req.GetPoolId(),
|
||
StrategyVersion: req.GetStrategyVersion(),
|
||
AdjustmentID: req.GetAdjustmentId(),
|
||
Direction: req.GetDirection(),
|
||
AmountCoins: req.GetAmountCoins(),
|
||
Reason: req.GetReason(),
|
||
OperatorAdminID: req.GetOperatorAdminId(),
|
||
RequestID: req.GetMeta().GetRequestId(),
|
||
})
|
||
if err != nil {
|
||
return nil, xerr.ToGRPCError(err)
|
||
}
|
||
return &luckygiftv1.AdjustLuckyGiftPoolBalanceResponse{
|
||
Adjustment: luckyPoolAdjustmentToProto(result.Adjustment),
|
||
Pool: luckyPoolBalanceToProto(result.Pool),
|
||
IdempotentReplay: result.IdempotentReplay,
|
||
}, nil
|
||
}
|
||
|
||
func luckyRuleConfigFromProto(config *luckygiftv1.LuckyGiftRuleConfig) domain.RuleConfig {
|
||
if config == nil {
|
||
return domain.RuleConfig{}
|
||
}
|
||
stages := make([]domain.RuleStage, 0, len(config.GetStages()))
|
||
for _, stage := range config.GetStages() {
|
||
tiers := make([]domain.RuleTier, 0, len(stage.GetTiers()))
|
||
for _, tier := range stage.GetTiers() {
|
||
tiers = append(tiers, domain.RuleTier{
|
||
Stage: tier.GetStage(),
|
||
TierID: tier.GetTierId(),
|
||
MultiplierPPM: tier.GetMultiplierPpm(),
|
||
BaseWeightPPM: tier.GetBaseWeightPpm(),
|
||
HighWaterOnly: tier.GetHighWaterOnly(),
|
||
Enabled: tier.GetEnabled(),
|
||
})
|
||
}
|
||
stages = append(stages, domain.RuleStage{
|
||
Stage: stage.GetStage(),
|
||
MinRecharge7DCoins: stage.GetMinRecharge_7DCoins(),
|
||
MinRecharge30DCoins: stage.GetMinRecharge_30DCoins(),
|
||
Tiers: tiers,
|
||
})
|
||
}
|
||
return domain.RuleConfig{
|
||
AppCode: config.GetAppCode(),
|
||
PoolID: config.GetPoolId(),
|
||
RuleVersion: config.GetRuleVersion(),
|
||
Enabled: config.GetEnabled(),
|
||
TargetRTPPPM: config.GetTargetRtpPpm(),
|
||
PoolRatePPM: config.GetPoolRatePpm(),
|
||
SettlementWindowWager: config.GetSettlementWindowWager(),
|
||
ControlBandPPM: config.GetControlBandPpm(),
|
||
GiftPriceReference: config.GetGiftPriceReference(),
|
||
NoviceMaxEquivalentDraws: config.GetNoviceMaxEquivalentDraws(),
|
||
NormalMaxEquivalentDraws: config.GetNormalMaxEquivalentDraws(),
|
||
EffectiveFromMS: config.GetEffectiveFromMs(),
|
||
CreatedByAdminID: config.GetCreatedByAdminId(),
|
||
CreatedAtMS: config.GetCreatedAtMs(),
|
||
StrategyVersion: config.GetStrategyVersion(),
|
||
ProfitRatePPM: config.GetProfitRatePpm(),
|
||
AnchorRatePPM: config.GetAnchorRatePpm(),
|
||
InitialPoolCoins: config.GetInitialPoolCoins(),
|
||
LossStreakGuarantee: config.GetLossStreakGuarantee(),
|
||
LowWatermarkCoins: config.GetLowWatermarkCoins(),
|
||
LowWaterNonzeroFactorPPM: config.GetLowWaterNonzeroFactorPpm(),
|
||
HighWatermarkCoins: config.GetHighWatermarkCoins(),
|
||
HighWaterNonzeroFactorPPM: config.GetHighWaterNonzeroFactorPpm(),
|
||
RechargeBoostWindowMS: config.GetRechargeBoostWindowMs(),
|
||
RechargeBoostFactorPPM: config.GetRechargeBoostFactorPpm(),
|
||
User24HourRTPThresholdPPM: config.GetUser_24HRtpThresholdPpm(),
|
||
User24HourOrdinaryWinFactorPPM: config.GetUser_24HOrdinaryWinFactorPpm(),
|
||
JackpotMultiplierPPMs: append([]int64(nil), config.GetJackpotMultiplierPpms()...),
|
||
JackpotGlobalRTPMaxPPM: config.GetJackpotGlobalRtpMaxPpm(),
|
||
JackpotUserRoundRTPMaxPPM: config.GetJackpotUserRoundRtpMaxPpm(),
|
||
JackpotUser48hRTPMaxPPM: config.GetJackpotUser_48HRtpMaxPpm(),
|
||
JackpotSpendThresholdCoins: config.GetJackpotSpendThresholdCoins(),
|
||
MaxJackpotHitsPerUserDay: config.GetMaxJackpotHitsPerUserDay(),
|
||
MaxSinglePayout: config.GetMaxSinglePayout(),
|
||
UserHourlyPayoutCap: config.GetUserHourlyPayoutCap(),
|
||
UserDailyPayoutCap: config.GetUserDailyPayoutCap(),
|
||
DeviceDailyPayoutCap: config.GetDeviceDailyPayoutCap(),
|
||
RoomHourlyPayoutCap: config.GetRoomHourlyPayoutCap(),
|
||
AnchorDailyPayoutCap: config.GetAnchorDailyPayoutCap(),
|
||
Stages: stages,
|
||
}
|
||
}
|
||
|
||
func luckyRuleConfigToProto(config domain.RuleConfig) *luckygiftv1.LuckyGiftRuleConfig {
|
||
roundRTPMaxPPM := config.JackpotUserRoundRTPMaxPPM
|
||
if roundRTPMaxPPM == 0 {
|
||
roundRTPMaxPPM = config.JackpotUserDayRTPMaxPPM
|
||
}
|
||
rolling48hRTPMaxPPM := config.JackpotUser48hRTPMaxPPM
|
||
if rolling48hRTPMaxPPM == 0 {
|
||
rolling48hRTPMaxPPM = config.JackpotUser72hRTPMaxPPM
|
||
}
|
||
stages := make([]*luckygiftv1.LuckyGiftRuleStage, 0, len(config.Stages))
|
||
for _, stage := range config.Stages {
|
||
tiers := make([]*luckygiftv1.LuckyGiftRuleTier, 0, len(stage.Tiers))
|
||
for _, tier := range stage.Tiers {
|
||
tiers = append(tiers, &luckygiftv1.LuckyGiftRuleTier{
|
||
Stage: tier.Stage,
|
||
TierId: tier.TierID,
|
||
MultiplierPpm: tier.MultiplierPPM,
|
||
BaseWeightPpm: tier.BaseWeightPPM,
|
||
HighWaterOnly: tier.HighWaterOnly,
|
||
Enabled: tier.Enabled,
|
||
})
|
||
}
|
||
stages = append(stages, &luckygiftv1.LuckyGiftRuleStage{
|
||
Stage: stage.Stage,
|
||
MinRecharge_7DCoins: stage.MinRecharge7DCoins,
|
||
MinRecharge_30DCoins: stage.MinRecharge30DCoins,
|
||
Tiers: tiers,
|
||
})
|
||
}
|
||
return &luckygiftv1.LuckyGiftRuleConfig{
|
||
AppCode: config.AppCode,
|
||
PoolId: config.PoolID,
|
||
RuleVersion: config.RuleVersion,
|
||
Enabled: config.Enabled,
|
||
TargetRtpPpm: config.TargetRTPPPM,
|
||
PoolRatePpm: config.PoolRatePPM,
|
||
SettlementWindowWager: config.SettlementWindowWager,
|
||
ControlBandPpm: config.ControlBandPPM,
|
||
GiftPriceReference: config.GiftPriceReference,
|
||
NoviceMaxEquivalentDraws: config.NoviceMaxEquivalentDraws,
|
||
NormalMaxEquivalentDraws: config.NormalMaxEquivalentDraws,
|
||
EffectiveFromMs: config.EffectiveFromMS,
|
||
CreatedByAdminId: config.CreatedByAdminID,
|
||
CreatedAtMs: config.CreatedAtMS,
|
||
StrategyVersion: config.StrategyVersion,
|
||
ProfitRatePpm: config.ProfitRatePPM,
|
||
AnchorRatePpm: config.AnchorRatePPM,
|
||
InitialPoolCoins: config.InitialPoolCoins,
|
||
LossStreakGuarantee: config.LossStreakGuarantee,
|
||
LowWatermarkCoins: config.LowWatermarkCoins,
|
||
LowWaterNonzeroFactorPpm: config.LowWaterNonzeroFactorPPM,
|
||
HighWatermarkCoins: config.HighWatermarkCoins,
|
||
HighWaterNonzeroFactorPpm: config.HighWaterNonzeroFactorPPM,
|
||
RechargeBoostWindowMs: config.RechargeBoostWindowMS,
|
||
RechargeBoostFactorPpm: config.RechargeBoostFactorPPM,
|
||
User_24HRtpThresholdPpm: config.User24HourRTPThresholdPPM,
|
||
User_24HOrdinaryWinFactorPpm: config.User24HourOrdinaryWinFactorPPM,
|
||
JackpotMultiplierPpms: append([]int64(nil), config.JackpotMultiplierPPMs...),
|
||
JackpotGlobalRtpMaxPpm: config.JackpotGlobalRTPMaxPPM,
|
||
JackpotUserRoundRtpMaxPpm: roundRTPMaxPPM,
|
||
JackpotUser_48HRtpMaxPpm: rolling48hRTPMaxPPM,
|
||
JackpotSpendThresholdCoins: config.JackpotSpendThresholdCoins,
|
||
MaxJackpotHitsPerUserDay: config.MaxJackpotHitsPerUserDay,
|
||
MaxSinglePayout: config.MaxSinglePayout,
|
||
UserHourlyPayoutCap: config.UserHourlyPayoutCap,
|
||
UserDailyPayoutCap: config.UserDailyPayoutCap,
|
||
DeviceDailyPayoutCap: config.DeviceDailyPayoutCap,
|
||
RoomHourlyPayoutCap: config.RoomHourlyPayoutCap,
|
||
AnchorDailyPayoutCap: config.AnchorDailyPayoutCap,
|
||
Stages: stages,
|
||
}
|
||
}
|
||
|
||
func luckyDrawResultToProto(result domain.DrawResult) *luckygiftv1.LuckyGiftDrawResult {
|
||
response := &luckygiftv1.LuckyGiftDrawResult{
|
||
DrawId: result.DrawID,
|
||
CommandId: result.CommandID,
|
||
AppCode: result.AppCode,
|
||
UserId: result.UserID,
|
||
ExternalUserId: result.ExternalUserID,
|
||
PoolId: result.PoolID,
|
||
GiftId: result.GiftID,
|
||
RuleVersion: result.RuleVersion,
|
||
ExperiencePool: result.ExperiencePool,
|
||
SelectedTierId: result.SelectedTierID,
|
||
MultiplierPpm: result.MultiplierPPM,
|
||
BaseRewardCoins: result.BaseRewardCoins,
|
||
EffectiveRewardCoins: result.EffectiveRewardCoins,
|
||
RewardStatus: result.RewardStatus,
|
||
RtpWindowIndex: result.RTPWindowIndex,
|
||
GiftRtpWindowIndex: result.GiftRTPWindowIndex,
|
||
GlobalBaseRtpPpm: result.GlobalBaseRTPPPM,
|
||
GiftBaseRtpPpm: result.GiftBaseRTPPPM,
|
||
StageFeedback: result.StageFeedback,
|
||
HighMultiplier: result.HighMultiplier,
|
||
CreatedAtMs: result.CreatedAtMS,
|
||
WalletTransactionId: result.WalletTransactionID,
|
||
CoinBalanceAfter: result.CoinBalanceAfter,
|
||
}
|
||
for _, hit := range result.Hits {
|
||
response.Hits = append(response.Hits, &luckygiftv1.LuckyGiftHit{
|
||
GiftIndex: hit.GiftIndex, DrawId: hit.DrawID, SelectedTierId: hit.SelectedTierID,
|
||
MultiplierPpm: hit.MultiplierPPM, RewardCoins: hit.RewardCoins,
|
||
})
|
||
}
|
||
return response
|
||
}
|
||
|
||
func externalDrawResultToProto(result domain.ExternalDrawResult) *luckygiftv1.ExternalGiftDrawResponse {
|
||
return &luckygiftv1.ExternalGiftDrawResponse{
|
||
DrawId: result.DrawID,
|
||
RequestId: result.RequestID,
|
||
AppCode: result.AppCode,
|
||
ExternalUserId: result.ExternalUserID,
|
||
GiftCount: result.GiftCount,
|
||
UnitAmount: result.UnitAmount,
|
||
TotalAmount: result.TotalAmount,
|
||
RewardAmount: result.RewardAmount,
|
||
MultiplierPpm: result.MultiplierPPM,
|
||
RewardStatus: result.RewardStatus,
|
||
RuleVersion: result.RuleVersion,
|
||
CreatedAtMs: result.CreatedAtMS,
|
||
}
|
||
}
|
||
|
||
func luckyDrawSummaryToProto(summary domain.DrawSummary) *luckygiftv1.LuckyGiftDrawSummary {
|
||
return &luckygiftv1.LuckyGiftDrawSummary{
|
||
PoolId: summary.PoolID,
|
||
TotalDraws: summary.TotalDraws,
|
||
UniqueUsers: summary.UniqueUsers,
|
||
UniqueRooms: summary.UniqueRooms,
|
||
TotalSpentCoins: summary.TotalSpentCoins,
|
||
TotalRewardCoins: summary.TotalRewardCoins,
|
||
BaseRewardCoins: summary.BaseRewardCoins,
|
||
ActualRtpPpm: summary.ActualRTPPPM,
|
||
PendingDraws: summary.PendingDraws,
|
||
GrantedDraws: summary.GrantedDraws,
|
||
FailedDraws: summary.FailedDraws,
|
||
}
|
||
}
|
||
|
||
func luckyPoolBalanceToProto(pool domain.PoolBalance) *luckygiftv1.LuckyGiftPoolBalance {
|
||
return &luckygiftv1.LuckyGiftPoolBalance{
|
||
AppCode: pool.AppCode,
|
||
PoolId: pool.PoolID,
|
||
StrategyVersion: pool.StrategyVersion,
|
||
Balance: pool.Balance,
|
||
ReserveFloor: pool.ReserveFloor,
|
||
AvailableBalance: pool.AvailableBalance,
|
||
TotalIn: pool.TotalIn,
|
||
TotalOut: pool.TotalOut,
|
||
ManualCreditTotal: pool.ManualCreditTotal,
|
||
ManualDebitTotal: pool.ManualDebitTotal,
|
||
Materialized: pool.Materialized,
|
||
UpdatedAtMs: pool.UpdatedAtMS,
|
||
}
|
||
}
|
||
|
||
func luckyUserProfileWindowToProto(window domain.UserProfileWindow) *luckygiftv1.LuckyGiftUserProfileWindow {
|
||
return &luckygiftv1.LuckyGiftUserProfileWindow{
|
||
Draws: window.Draws, WagerCoins: window.WagerCoins, PayoutCoins: window.PayoutCoins,
|
||
NetCoins: window.NetCoins, RtpPpm: window.RTPPPM, HasRtp: window.HasRTP,
|
||
}
|
||
}
|
||
|
||
func luckyUserProfileToProto(profile domain.UserProfile) *luckygiftv1.LuckyGiftUserProfile {
|
||
// StrategyUserID 可能是 external_user_id 的稳定哈希,只允许仓储层关联 lucky_user_states;
|
||
// transport 显式逐字段映射且没有该字段,避免未来通过反射或 JSON 误下发。
|
||
return &luckygiftv1.LuckyGiftUserProfile{
|
||
AppCode: profile.AppCode, PoolId: profile.PoolID, IdentityType: profile.IdentityType,
|
||
InternalUserId: profile.InternalUserID, ExternalUserId: profile.ExternalUserID,
|
||
RuleVersion: profile.RuleVersion, StrategyVersion: profile.StrategyVersion, Stage: profile.Stage,
|
||
PaidDraws: profile.PaidDraws, EquivalentDraws: profile.EquivalentDraws, LossStreak: profile.LossStreak,
|
||
PendingSpendJackpotTokens: profile.PendingSpendJackpotTokens,
|
||
GuaranteeDrawsRemaining: profile.GuaranteeDrawsRemaining,
|
||
DownweightActive: profile.DownweightActive, DownweightFactorPpm: profile.DownweightFactorPPM,
|
||
User_24HRtpThresholdPpm: profile.User24HourRTPThresholdPPM,
|
||
OneHour: luckyUserProfileWindowToProto(profile.OneHour),
|
||
TwentyFourHours: luckyUserProfileWindowToProto(profile.TwentyFourHour),
|
||
FortyEightHours: luckyUserProfileWindowToProto(profile.FortyEightHour),
|
||
Lifetime: luckyUserProfileWindowToProto(profile.Lifetime),
|
||
BaseRewardCoins: profile.BaseRewardCoins, OrdinaryWinCount: profile.OrdinaryWinCount,
|
||
HighMultiplierOrdinaryWinCount: profile.HighMultiplierOrdinaryWinCount,
|
||
RtpCompensationJackpotCount: profile.RTPCompensationJackpotCount,
|
||
CumulativeSpendJackpotCount: profile.CumulativeSpendJackpotCount,
|
||
GuaranteeHitCount: profile.GuaranteeHitCount, ZeroDrawCount: profile.ZeroDrawCount,
|
||
GrantedDrawCount: profile.GrantedDrawCount, PendingDrawCount: profile.PendingDrawCount,
|
||
FailedDrawCount: profile.FailedDrawCount, MaxMultiplierPpm: profile.MaxMultiplierPPM,
|
||
MaxRewardCoins: profile.MaxRewardCoins, FirstDrawAtMs: profile.FirstDrawAtMS,
|
||
LastDrawAtMs: profile.LastDrawAtMS, AggregatedAtMs: profile.AggregatedAtMS, DataLagMs: profile.DataLagMS,
|
||
}
|
||
}
|
||
|
||
func luckyPoolAdjustmentToProto(adjustment domain.PoolAdjustment) *luckygiftv1.LuckyGiftPoolAdjustment {
|
||
return &luckygiftv1.LuckyGiftPoolAdjustment{
|
||
AdjustmentId: adjustment.AdjustmentID,
|
||
AppCode: adjustment.AppCode,
|
||
PoolId: adjustment.PoolID,
|
||
StrategyVersion: adjustment.StrategyVersion,
|
||
Direction: adjustment.Direction,
|
||
AmountCoins: adjustment.AmountCoins,
|
||
Reason: adjustment.Reason,
|
||
OperatorAdminId: adjustment.OperatorAdminID,
|
||
BalanceBefore: adjustment.BalanceBefore,
|
||
BalanceAfter: adjustment.BalanceAfter,
|
||
CreatedAtMs: adjustment.CreatedAtMS,
|
||
}
|
||
}
|