385 lines
15 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,
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(),
RequestID: req.GetRequestId(),
GiftCount: req.GetGiftCount(),
UnitAmount: req.GetUnitAmount(),
TotalAmount: req.GetTotalAmount(),
Currency: req.GetCurrency(),
PaidAtMS: req.GetPaidAtMs(),
MetadataJSON: req.GetMetadataJson(),
PoolID: req.GetPoolId(),
})
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(),
VisibleRegionID: meta.GetVisibleRegionId(),
CountryID: meta.GetCountryId(),
}
}
// 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) 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(),
})
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 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(),
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(),
Stages: stages,
}
}
func luckyRuleConfigToProto(config domain.RuleConfig) *luckygiftv1.LuckyGiftRuleConfig {
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,
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,
Stages: stages,
}
}
func luckyDrawResultToProto(result domain.DrawResult) *luckygiftv1.LuckyGiftDrawResult {
return &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,
}
}
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,
Balance: pool.Balance,
ReserveFloor: pool.ReserveFloor,
AvailableBalance: pool.AvailableBalance,
TotalIn: pool.TotalIn,
TotalOut: pool.TotalOut,
Materialized: pool.Materialized,
UpdatedAtMs: pool.UpdatedAtMS,
}
}