feat(lucky-gift): isolate strategy pools and audit balance adjustments
This commit is contained in:
parent
0cdbd28d46
commit
eaeff155f8
@ -2273,15 +2273,18 @@ type LuckyGiftPoolBalance struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
|
AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
|
||||||
PoolId string `protobuf:"bytes,2,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"`
|
PoolId string `protobuf:"bytes,2,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"`
|
||||||
Balance int64 `protobuf:"varint,3,opt,name=balance,proto3" json:"balance,omitempty"`
|
Balance int64 `protobuf:"varint,3,opt,name=balance,proto3" json:"balance,omitempty"`
|
||||||
ReserveFloor int64 `protobuf:"varint,4,opt,name=reserve_floor,json=reserveFloor,proto3" json:"reserve_floor,omitempty"`
|
ReserveFloor int64 `protobuf:"varint,4,opt,name=reserve_floor,json=reserveFloor,proto3" json:"reserve_floor,omitempty"`
|
||||||
AvailableBalance int64 `protobuf:"varint,5,opt,name=available_balance,json=availableBalance,proto3" json:"available_balance,omitempty"`
|
AvailableBalance int64 `protobuf:"varint,5,opt,name=available_balance,json=availableBalance,proto3" json:"available_balance,omitempty"`
|
||||||
TotalIn int64 `protobuf:"varint,6,opt,name=total_in,json=totalIn,proto3" json:"total_in,omitempty"`
|
TotalIn int64 `protobuf:"varint,6,opt,name=total_in,json=totalIn,proto3" json:"total_in,omitempty"`
|
||||||
TotalOut int64 `protobuf:"varint,7,opt,name=total_out,json=totalOut,proto3" json:"total_out,omitempty"`
|
TotalOut int64 `protobuf:"varint,7,opt,name=total_out,json=totalOut,proto3" json:"total_out,omitempty"`
|
||||||
Materialized bool `protobuf:"varint,8,opt,name=materialized,proto3" json:"materialized,omitempty"`
|
Materialized bool `protobuf:"varint,8,opt,name=materialized,proto3" json:"materialized,omitempty"`
|
||||||
UpdatedAtMs int64 `protobuf:"varint,9,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"`
|
UpdatedAtMs int64 `protobuf:"varint,9,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"`
|
||||||
|
StrategyVersion string `protobuf:"bytes,10,opt,name=strategy_version,json=strategyVersion,proto3" json:"strategy_version,omitempty"`
|
||||||
|
ManualCreditTotal int64 `protobuf:"varint,11,opt,name=manual_credit_total,json=manualCreditTotal,proto3" json:"manual_credit_total,omitempty"`
|
||||||
|
ManualDebitTotal int64 `protobuf:"varint,12,opt,name=manual_debit_total,json=manualDebitTotal,proto3" json:"manual_debit_total,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *LuckyGiftPoolBalance) Reset() {
|
func (x *LuckyGiftPoolBalance) Reset() {
|
||||||
@ -2377,6 +2380,27 @@ func (x *LuckyGiftPoolBalance) GetUpdatedAtMs() int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolBalance) GetStrategyVersion() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.StrategyVersion
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolBalance) GetManualCreditTotal() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ManualCreditTotal
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolBalance) GetManualDebitTotal() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.ManualDebitTotal
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
type GetLuckyGiftDrawSummaryRequest struct {
|
type GetLuckyGiftDrawSummaryRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@ -2528,8 +2552,9 @@ type ListLuckyGiftPoolBalancesRequest struct {
|
|||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
|
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
|
||||||
PoolId string `protobuf:"bytes,2,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"`
|
PoolId string `protobuf:"bytes,2,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"`
|
||||||
|
StrategyVersion string `protobuf:"bytes,3,opt,name=strategy_version,json=strategyVersion,proto3" json:"strategy_version,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListLuckyGiftPoolBalancesRequest) Reset() {
|
func (x *ListLuckyGiftPoolBalancesRequest) Reset() {
|
||||||
@ -2576,6 +2601,13 @@ func (x *ListLuckyGiftPoolBalancesRequest) GetPoolId() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ListLuckyGiftPoolBalancesRequest) GetStrategyVersion() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.StrategyVersion
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type ListLuckyGiftPoolBalancesResponse struct {
|
type ListLuckyGiftPoolBalancesResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
@ -2700,6 +2732,295 @@ func (x *LuckyGiftHit) GetRewardCoins() int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 调账消息也只追加在文件尾,保持所有既有 protobuf message index 稳定。
|
||||||
|
// adjustment_id 是业务幂等键;RequestMeta.request_id 只做链路追踪,不能替代资金幂等语义。
|
||||||
|
type AdjustLuckyGiftPoolBalanceRequest struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
|
||||||
|
PoolId string `protobuf:"bytes,2,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"`
|
||||||
|
StrategyVersion string `protobuf:"bytes,3,opt,name=strategy_version,json=strategyVersion,proto3" json:"strategy_version,omitempty"`
|
||||||
|
AdjustmentId string `protobuf:"bytes,4,opt,name=adjustment_id,json=adjustmentId,proto3" json:"adjustment_id,omitempty"`
|
||||||
|
Direction string `protobuf:"bytes,5,opt,name=direction,proto3" json:"direction,omitempty"`
|
||||||
|
AmountCoins int64 `protobuf:"varint,6,opt,name=amount_coins,json=amountCoins,proto3" json:"amount_coins,omitempty"`
|
||||||
|
Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"`
|
||||||
|
OperatorAdminId int64 `protobuf:"varint,8,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceRequest) Reset() {
|
||||||
|
*x = AdjustLuckyGiftPoolBalanceRequest{}
|
||||||
|
mi := &file_proto_luckygift_v1_luckygift_proto_msgTypes[29]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*AdjustLuckyGiftPoolBalanceRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_luckygift_v1_luckygift_proto_msgTypes[29]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use AdjustLuckyGiftPoolBalanceRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*AdjustLuckyGiftPoolBalanceRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_luckygift_v1_luckygift_proto_rawDescGZIP(), []int{29}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceRequest) GetMeta() *RequestMeta {
|
||||||
|
if x != nil {
|
||||||
|
return x.Meta
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceRequest) GetPoolId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.PoolId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceRequest) GetStrategyVersion() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.StrategyVersion
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceRequest) GetAdjustmentId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.AdjustmentId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceRequest) GetDirection() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Direction
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceRequest) GetAmountCoins() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.AmountCoins
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceRequest) GetReason() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Reason
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceRequest) GetOperatorAdminId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.OperatorAdminId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type LuckyGiftPoolAdjustment struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
AdjustmentId string `protobuf:"bytes,1,opt,name=adjustment_id,json=adjustmentId,proto3" json:"adjustment_id,omitempty"`
|
||||||
|
AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"`
|
||||||
|
PoolId string `protobuf:"bytes,3,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"`
|
||||||
|
StrategyVersion string `protobuf:"bytes,4,opt,name=strategy_version,json=strategyVersion,proto3" json:"strategy_version,omitempty"`
|
||||||
|
Direction string `protobuf:"bytes,5,opt,name=direction,proto3" json:"direction,omitempty"`
|
||||||
|
AmountCoins int64 `protobuf:"varint,6,opt,name=amount_coins,json=amountCoins,proto3" json:"amount_coins,omitempty"`
|
||||||
|
Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"`
|
||||||
|
OperatorAdminId int64 `protobuf:"varint,8,opt,name=operator_admin_id,json=operatorAdminId,proto3" json:"operator_admin_id,omitempty"`
|
||||||
|
BalanceBefore int64 `protobuf:"varint,9,opt,name=balance_before,json=balanceBefore,proto3" json:"balance_before,omitempty"`
|
||||||
|
BalanceAfter int64 `protobuf:"varint,10,opt,name=balance_after,json=balanceAfter,proto3" json:"balance_after,omitempty"`
|
||||||
|
CreatedAtMs int64 `protobuf:"varint,11,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) Reset() {
|
||||||
|
*x = LuckyGiftPoolAdjustment{}
|
||||||
|
mi := &file_proto_luckygift_v1_luckygift_proto_msgTypes[30]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LuckyGiftPoolAdjustment) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_luckygift_v1_luckygift_proto_msgTypes[30]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use LuckyGiftPoolAdjustment.ProtoReflect.Descriptor instead.
|
||||||
|
func (*LuckyGiftPoolAdjustment) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_luckygift_v1_luckygift_proto_rawDescGZIP(), []int{30}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) GetAdjustmentId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.AdjustmentId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) GetAppCode() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.AppCode
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) GetPoolId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.PoolId
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) GetStrategyVersion() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.StrategyVersion
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) GetDirection() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Direction
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) GetAmountCoins() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.AmountCoins
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) GetReason() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Reason
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) GetOperatorAdminId() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.OperatorAdminId
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) GetBalanceBefore() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.BalanceBefore
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) GetBalanceAfter() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.BalanceAfter
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LuckyGiftPoolAdjustment) GetCreatedAtMs() int64 {
|
||||||
|
if x != nil {
|
||||||
|
return x.CreatedAtMs
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdjustLuckyGiftPoolBalanceResponse struct {
|
||||||
|
state protoimpl.MessageState
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
|
Adjustment *LuckyGiftPoolAdjustment `protobuf:"bytes,1,opt,name=adjustment,proto3" json:"adjustment,omitempty"`
|
||||||
|
Pool *LuckyGiftPoolBalance `protobuf:"bytes,2,opt,name=pool,proto3" json:"pool,omitempty"`
|
||||||
|
IdempotentReplay bool `protobuf:"varint,3,opt,name=idempotent_replay,json=idempotentReplay,proto3" json:"idempotent_replay,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceResponse) Reset() {
|
||||||
|
*x = AdjustLuckyGiftPoolBalanceResponse{}
|
||||||
|
mi := &file_proto_luckygift_v1_luckygift_proto_msgTypes[31]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*AdjustLuckyGiftPoolBalanceResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_proto_luckygift_v1_luckygift_proto_msgTypes[31]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use AdjustLuckyGiftPoolBalanceResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*AdjustLuckyGiftPoolBalanceResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_proto_luckygift_v1_luckygift_proto_rawDescGZIP(), []int{31}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceResponse) GetAdjustment() *LuckyGiftPoolAdjustment {
|
||||||
|
if x != nil {
|
||||||
|
return x.Adjustment
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceResponse) GetPool() *LuckyGiftPoolBalance {
|
||||||
|
if x != nil {
|
||||||
|
return x.Pool
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdjustLuckyGiftPoolBalanceResponse) GetIdempotentReplay() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IdempotentReplay
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var File_proto_luckygift_v1_luckygift_proto protoreflect.FileDescriptor
|
var File_proto_luckygift_v1_luckygift_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_proto_luckygift_v1_luckygift_proto_rawDesc = []byte{
|
var file_proto_luckygift_v1_luckygift_proto_rawDesc = []byte{
|
||||||
@ -3184,7 +3505,7 @@ var file_proto_luckygift_v1_luckygift_proto_rawDesc = []byte{
|
|||||||
0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x44, 0x72,
|
0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x64, 0x44, 0x72,
|
||||||
0x61, 0x77, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x64, 0x72,
|
0x61, 0x77, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x64, 0x72,
|
||||||
0x61, 0x77, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x65,
|
0x61, 0x77, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x65,
|
||||||
0x64, 0x44, 0x72, 0x61, 0x77, 0x73, 0x22, 0xb6, 0x02, 0x0a, 0x14, 0x4c, 0x75, 0x63, 0x6b, 0x79,
|
0x64, 0x44, 0x72, 0x61, 0x77, 0x73, 0x22, 0xbf, 0x03, 0x0a, 0x14, 0x4c, 0x75, 0x63, 0x6b, 0x79,
|
||||||
0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12,
|
0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12,
|
||||||
0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x19, 0x0a, 0x08, 0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f,
|
0x09, 0x52, 0x07, 0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f,
|
||||||
@ -3203,137 +3524,217 @@ var file_proto_luckygift_v1_luckygift_proto_rawDesc = []byte{
|
|||||||
0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6d,
|
0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6d,
|
||||||
0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x75,
|
0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x22, 0x0a, 0x0d, 0x75,
|
||||||
0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01,
|
0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x09, 0x20, 0x01,
|
||||||
0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x22,
|
0x28, 0x03, 0x52, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73, 0x12,
|
||||||
0xa0, 0x02, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74,
|
0x29, 0x0a, 0x10, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x5f, 0x76, 0x65, 0x72, 0x73,
|
||||||
0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
|
0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x74, 0x72, 0x61, 0x74,
|
||||||
0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
|
0x65, 0x67, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2e, 0x0a, 0x13, 0x6d, 0x61,
|
||||||
0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69,
|
0x6e, 0x75, 0x61, 0x6c, 0x5f, 0x63, 0x72, 0x65, 0x64, 0x69, 0x74, 0x5f, 0x74, 0x6f, 0x74, 0x61,
|
||||||
0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74,
|
0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x43,
|
||||||
0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f,
|
0x72, 0x65, 0x64, 0x69, 0x74, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61,
|
||||||
0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64,
|
0x6e, 0x75, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x62, 0x69, 0x74, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c,
|
||||||
0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
|
0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x44, 0x65,
|
||||||
0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f,
|
0x62, 0x69, 0x74, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x22, 0xa0, 0x02, 0x0a, 0x1e, 0x47, 0x65, 0x74,
|
||||||
0x6d, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d,
|
0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d,
|
||||||
0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01,
|
0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d,
|
||||||
0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f,
|
0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70,
|
||||||
0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f,
|
0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52,
|
||||||
0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61,
|
||||||
0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65,
|
0x12, 0x17, 0x0a, 0x07, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||||
0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a,
|
0x09, 0x52, 0x06, 0x67, 0x69, 0x66, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65,
|
||||||
0x0d, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x09,
|
0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72,
|
||||||
0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4f, 0x6e,
|
0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x6f, 0x6f, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20,
|
||||||
0x6c, 0x79, 0x22, 0x65, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69,
|
0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x6f, 0x6f, 0x6d, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73,
|
||||||
0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73,
|
0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79,
|
0x74, 0x75, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x06,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c,
|
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10,
|
||||||
0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b,
|
0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64,
|
||||||
0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79,
|
0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
|
||||||
0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x70, 0x0a, 0x20, 0x4c, 0x69, 0x73,
|
0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e,
|
||||||
0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61,
|
0x61, 0x6c, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x65,
|
||||||
0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a,
|
0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x65, 0x0a, 0x1f, 0x47,
|
||||||
0x04, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79,
|
0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53,
|
||||||
0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31,
|
0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42,
|
||||||
0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65,
|
0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
0x74, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
|
|
||||||
0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x22, 0x63, 0x0a, 0x21, 0x4c,
|
|
||||||
0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c,
|
|
||||||
0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
|
||||||
0x12, 0x3e, 0x0a, 0x05, 0x70, 0x6f, 0x6f, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
|
||||||
0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66,
|
0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66,
|
||||||
0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f,
|
0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72,
|
||||||
0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x05, 0x70, 0x6f, 0x6f, 0x6c, 0x73,
|
0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61,
|
||||||
0x22, 0xba, 0x01, 0x0a, 0x0c, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x48, 0x69,
|
0x72, 0x79, 0x22, 0x9b, 0x01, 0x0a, 0x20, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79,
|
||||||
0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18,
|
0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73,
|
||||||
0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78,
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x18,
|
||||||
0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,
|
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75,
|
||||||
0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6c,
|
0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20,
|
0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a, 0x07,
|
||||||
0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x54, 0x69, 0x65,
|
0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70,
|
||||||
0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65,
|
0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
|
||||||
0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x75, 0x6c,
|
0x79, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50, 0x70, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65,
|
0x0f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
|
||||||
0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03,
|
0x22, 0x63, 0x0a, 0x21, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66,
|
||||||
0x52, 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x32, 0xf7, 0x03,
|
0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73,
|
||||||
0x0a, 0x10, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69,
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x05, 0x70, 0x6f, 0x6f, 0x6c, 0x73, 0x18, 0x01,
|
||||||
0x63, 0x65, 0x12, 0x67, 0x0a, 0x0e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x75, 0x63, 0x6b, 0x79,
|
0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63,
|
||||||
0x47, 0x69, 0x66, 0x74, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63,
|
0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47,
|
||||||
0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c,
|
0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x05,
|
||||||
0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
0x70, 0x6f, 0x6f, 0x6c, 0x73, 0x22, 0xba, 0x01, 0x0a, 0x0c, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47,
|
||||||
0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66,
|
0x69, 0x66, 0x74, 0x48, 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x69, 0x66, 0x74, 0x5f, 0x69,
|
||||||
0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47,
|
0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x67, 0x69, 0x66, 0x74,
|
||||||
0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x14, 0x45,
|
0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x72, 0x61, 0x77, 0x5f, 0x69, 0x64,
|
||||||
0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44,
|
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x72, 0x61, 0x77, 0x49, 0x64, 0x12, 0x28,
|
||||||
0x72, 0x61, 0x77, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b,
|
0x0a, 0x10, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x65, 0x72, 0x5f,
|
||||||
0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65,
|
0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74,
|
||||||
0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71,
|
0x65, 0x64, 0x54, 0x69, 0x65, 0x72, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x75, 0x6c, 0x74,
|
||||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63,
|
0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x5f, 0x70, 0x70, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03,
|
||||||
0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74,
|
0x52, 0x0d, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x50, 0x70, 0x6d, 0x12,
|
||||||
0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65,
|
0x21, 0x0a, 0x0c, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18,
|
||||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x88, 0x01, 0x0a, 0x19, 0x42, 0x61, 0x74, 0x63, 0x68,
|
0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x77, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x69,
|
||||||
0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74,
|
0x6e, 0x73, 0x22, 0xc6, 0x02, 0x0a, 0x21, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x4c, 0x75, 0x63,
|
||||||
0x44, 0x72, 0x61, 0x77, 0x12, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63,
|
0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63,
|
||||||
0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x45,
|
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x04, 0x6d, 0x65, 0x74, 0x61,
|
||||||
0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44,
|
0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c,
|
||||||
0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x68, 0x79, 0x61,
|
0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75,
|
||||||
0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e,
|
0x65, 0x73, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x04, 0x6d, 0x65, 0x74, 0x61, 0x12, 0x17, 0x0a,
|
||||||
0x42, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b,
|
0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
||||||
0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65,
|
||||||
0x65, 0x12, 0x74, 0x0a, 0x17, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x45, 0x78, 0x74, 0x65,
|
0x67, 0x79, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x72, 0x6e, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x12, 0x2b, 0x2e, 0x68,
|
0x52, 0x0f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||||
|
0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f,
|
||||||
|
0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74,
|
||||||
|
0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
|
||||||
|
0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63,
|
||||||
|
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63,
|
||||||
|
0x6f, 0x69, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x61, 0x6d, 0x6f, 0x75,
|
||||||
|
0x6e, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f,
|
||||||
|
0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12,
|
||||||
|
0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69,
|
||||||
|
0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72,
|
||||||
|
0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64, 0x22, 0x92, 0x03, 0x0a, 0x17,
|
||||||
|
0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x41, 0x64, 0x6a,
|
||||||
|
0x75, 0x73, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x64, 0x6a, 0x75, 0x73,
|
||||||
|
0x74, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
|
||||||
|
0x61, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08,
|
||||||
|
0x61, 0x70, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
||||||
|
0x61, 0x70, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f,
|
||||||
|
0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x49, 0x64,
|
||||||
|
0x12, 0x29, 0x0a, 0x10, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x5f, 0x76, 0x65, 0x72,
|
||||||
|
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x74, 0x72, 0x61,
|
||||||
|
0x74, 0x65, 0x67, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x64,
|
||||||
|
0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
|
||||||
|
0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x6d, 0x6f,
|
||||||
|
0x75, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||||
|
0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x12, 0x16, 0x0a, 0x06,
|
||||||
|
0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65,
|
||||||
|
0x61, 0x73, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72,
|
||||||
|
0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52,
|
||||||
|
0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x49, 0x64,
|
||||||
|
0x12, 0x25, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x62, 0x65, 0x66, 0x6f,
|
||||||
|
0x72, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63,
|
||||||
|
0x65, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e,
|
||||||
|
0x63, 0x65, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c,
|
||||||
|
0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d,
|
||||||
|
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x0b, 0x20,
|
||||||
|
0x01, 0x28, 0x03, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x4d, 0x73,
|
||||||
|
0x22, 0xdc, 0x01, 0x0a, 0x22, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79,
|
||||||
|
0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52,
|
||||||
|
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0a, 0x61, 0x64, 0x6a, 0x75, 0x73,
|
||||||
|
0x74, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x68, 0x79,
|
||||||
|
0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31,
|
||||||
|
0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x41, 0x64,
|
||||||
|
0x6a, 0x75, 0x73, 0x74, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0a, 0x61, 0x64, 0x6a, 0x75, 0x73, 0x74,
|
||||||
|
0x6d, 0x65, 0x6e, 0x74, 0x12, 0x3c, 0x0a, 0x04, 0x70, 0x6f, 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01,
|
||||||
|
0x28, 0x0b, 0x32, 0x28, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79,
|
||||||
|
0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66,
|
||||||
|
0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x04, 0x70, 0x6f,
|
||||||
|
0x6f, 0x6c, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74,
|
||||||
|
0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x69,
|
||||||
|
0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x79, 0x32,
|
||||||
|
0xf7, 0x03, 0x0a, 0x10, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x53, 0x65, 0x72,
|
||||||
|
0x76, 0x69, 0x63, 0x65, 0x12, 0x67, 0x0a, 0x0e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x75, 0x63,
|
||||||
|
0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x12, 0x29, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c,
|
||||||
|
0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63,
|
||||||
|
0x6b, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
|
0x74, 0x1a, 0x2a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67,
|
||||||
|
0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x4c, 0x75, 0x63, 0x6b,
|
||||||
|
0x79, 0x47, 0x69, 0x66, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a,
|
||||||
|
0x14, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66,
|
||||||
|
0x74, 0x44, 0x72, 0x61, 0x77, 0x12, 0x2f, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75,
|
||||||
|
0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75,
|
||||||
|
0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52,
|
||||||
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c,
|
||||||
|
0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x65, 0x63,
|
||||||
|
0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77,
|
||||||
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x88, 0x01, 0x0a, 0x19, 0x42, 0x61, 0x74,
|
||||||
|
0x63, 0x68, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69,
|
||||||
|
0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x12, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c,
|
||||||
|
0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x74, 0x63,
|
||||||
|
0x68, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66,
|
||||||
|
0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x68,
|
||||||
0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76,
|
0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76,
|
||||||
0x31, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72,
|
0x31, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4c, 0x75,
|
||||||
0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79, 0x61, 0x70,
|
0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x45,
|
0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x17, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x45, 0x78,
|
||||||
0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x52,
|
0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x12, 0x2b,
|
||||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x8a, 0x06, 0x0a, 0x15, 0x41, 0x64, 0x6d, 0x69,
|
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74,
|
||||||
0x6e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
0x2e, 0x76, 0x31, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74,
|
||||||
0x65, 0x12, 0x73, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66,
|
0x44, 0x72, 0x61, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x68, 0x79,
|
||||||
0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
|
0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31,
|
||||||
0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74,
|
0x2e, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61,
|
||||||
0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
|
0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x98, 0x07, 0x0a, 0x15, 0x41, 0x64,
|
||||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c,
|
0x6d, 0x69, 0x6e, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x53, 0x65, 0x72, 0x76,
|
||||||
0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4c,
|
0x69, 0x63, 0x65, 0x12, 0x73, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47,
|
||||||
0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65,
|
0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70,
|
||||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7c, 0x0a, 0x15, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74,
|
0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47,
|
||||||
0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
|
0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66,
|
0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70,
|
||||||
0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79,
|
0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65,
|
||||||
0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
|
||||||
0x74, 0x1a, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67,
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7c, 0x0a, 0x15, 0x55, 0x70, 0x73, 0x65,
|
||||||
|
0x72, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
|
0x67, 0x12, 0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67,
|
||||||
0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x75, 0x63,
|
0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c, 0x75, 0x63,
|
||||||
0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70,
|
0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75,
|
||||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b,
|
0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b,
|
||||||
0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x2f, 0x2e, 0x68,
|
0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x4c,
|
||||||
0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76,
|
0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65,
|
||||||
0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43,
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x79, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75,
|
||||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e,
|
0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x2f,
|
||||||
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e,
|
0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74,
|
||||||
0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74,
|
0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66,
|
||||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||||
0x73, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74,
|
0x30, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66,
|
||||||
0x44, 0x72, 0x61, 0x77, 0x73, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75,
|
0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69,
|
||||||
0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c,
|
0x66, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x73, 0x52, 0x65, 0x71,
|
0x65, 0x12, 0x73, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69,
|
||||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63,
|
0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x73, 0x12, 0x2d, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
|
||||||
0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75,
|
|
||||||
0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x73, 0x52, 0x65, 0x73, 0x70,
|
|
||||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b,
|
|
||||||
0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79,
|
|
||||||
0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69,
|
|
||||||
0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69,
|
|
||||||
0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71,
|
|
||||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63,
|
|
||||||
0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63,
|
|
||||||
0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72,
|
|
||||||
0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x88, 0x01, 0x0a, 0x19, 0x4c, 0x69,
|
|
||||||
0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42,
|
|
||||||
0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e,
|
|
||||||
0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73,
|
0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73,
|
||||||
|
0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x73, 0x52,
|
||||||
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c,
|
||||||
|
0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74,
|
||||||
|
0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x73, 0x52, 0x65,
|
||||||
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4c, 0x75,
|
||||||
|
0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61,
|
||||||
|
0x72, 0x79, 0x12, 0x32, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79,
|
||||||
|
0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79,
|
||||||
|
0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52,
|
||||||
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c,
|
||||||
|
0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4c,
|
||||||
|
0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x44, 0x72, 0x61, 0x77, 0x53, 0x75, 0x6d, 0x6d,
|
||||||
|
0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x88, 0x01, 0x0a, 0x19,
|
||||||
|
0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f,
|
||||||
|
0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x34, 0x2e, 0x68, 0x79, 0x61, 0x70,
|
||||||
|
0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c,
|
||||||
|
0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c,
|
||||||
|
0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||||
|
0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66,
|
||||||
|
0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69,
|
||||||
|
0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65,
|
||||||
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8b, 0x01, 0x0a, 0x1a, 0x41, 0x64, 0x6a, 0x75, 0x73,
|
||||||
0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61,
|
0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61,
|
||||||
0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e,
|
0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x35, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75,
|
||||||
0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e,
|
0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x6a, 0x75, 0x73,
|
||||||
0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74,
|
0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66, 0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61,
|
||||||
0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
|
0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68,
|
||||||
|
0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x75, 0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2e, 0x76,
|
||||||
|
0x31, 0x2e, 0x41, 0x64, 0x6a, 0x75, 0x73, 0x74, 0x4c, 0x75, 0x63, 0x6b, 0x79, 0x47, 0x69, 0x66,
|
||||||
|
0x74, 0x50, 0x6f, 0x6f, 0x6c, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70,
|
||||||
0x6f, 0x6e, 0x73, 0x65, 0x42, 0x30, 0x5a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f,
|
0x6f, 0x6e, 0x73, 0x65, 0x42, 0x30, 0x5a, 0x2e, 0x68, 0x79, 0x61, 0x70, 0x70, 0x2e, 0x6c, 0x6f,
|
||||||
0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x75,
|
0x63, 0x61, 0x6c, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x75,
|
||||||
0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x6c, 0x75, 0x63, 0x6b, 0x79,
|
0x63, 0x6b, 0x79, 0x67, 0x69, 0x66, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x6c, 0x75, 0x63, 0x6b, 0x79,
|
||||||
@ -3352,37 +3753,40 @@ func file_proto_luckygift_v1_luckygift_proto_rawDescGZIP() []byte {
|
|||||||
return file_proto_luckygift_v1_luckygift_proto_rawDescData
|
return file_proto_luckygift_v1_luckygift_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_proto_luckygift_v1_luckygift_proto_msgTypes = make([]protoimpl.MessageInfo, 29)
|
var file_proto_luckygift_v1_luckygift_proto_msgTypes = make([]protoimpl.MessageInfo, 32)
|
||||||
var file_proto_luckygift_v1_luckygift_proto_goTypes = []any{
|
var file_proto_luckygift_v1_luckygift_proto_goTypes = []any{
|
||||||
(*RequestMeta)(nil), // 0: hyapp.luckygift.v1.RequestMeta
|
(*RequestMeta)(nil), // 0: hyapp.luckygift.v1.RequestMeta
|
||||||
(*LuckyGiftMeta)(nil), // 1: hyapp.luckygift.v1.LuckyGiftMeta
|
(*LuckyGiftMeta)(nil), // 1: hyapp.luckygift.v1.LuckyGiftMeta
|
||||||
(*LuckyGiftRuleTier)(nil), // 2: hyapp.luckygift.v1.LuckyGiftRuleTier
|
(*LuckyGiftRuleTier)(nil), // 2: hyapp.luckygift.v1.LuckyGiftRuleTier
|
||||||
(*LuckyGiftRuleStage)(nil), // 3: hyapp.luckygift.v1.LuckyGiftRuleStage
|
(*LuckyGiftRuleStage)(nil), // 3: hyapp.luckygift.v1.LuckyGiftRuleStage
|
||||||
(*LuckyGiftRuleConfig)(nil), // 4: hyapp.luckygift.v1.LuckyGiftRuleConfig
|
(*LuckyGiftRuleConfig)(nil), // 4: hyapp.luckygift.v1.LuckyGiftRuleConfig
|
||||||
(*CheckLuckyGiftRequest)(nil), // 5: hyapp.luckygift.v1.CheckLuckyGiftRequest
|
(*CheckLuckyGiftRequest)(nil), // 5: hyapp.luckygift.v1.CheckLuckyGiftRequest
|
||||||
(*CheckLuckyGiftResponse)(nil), // 6: hyapp.luckygift.v1.CheckLuckyGiftResponse
|
(*CheckLuckyGiftResponse)(nil), // 6: hyapp.luckygift.v1.CheckLuckyGiftResponse
|
||||||
(*LuckyGiftDrawResult)(nil), // 7: hyapp.luckygift.v1.LuckyGiftDrawResult
|
(*LuckyGiftDrawResult)(nil), // 7: hyapp.luckygift.v1.LuckyGiftDrawResult
|
||||||
(*ExecuteLuckyGiftDrawRequest)(nil), // 8: hyapp.luckygift.v1.ExecuteLuckyGiftDrawRequest
|
(*ExecuteLuckyGiftDrawRequest)(nil), // 8: hyapp.luckygift.v1.ExecuteLuckyGiftDrawRequest
|
||||||
(*ExecuteLuckyGiftDrawResponse)(nil), // 9: hyapp.luckygift.v1.ExecuteLuckyGiftDrawResponse
|
(*ExecuteLuckyGiftDrawResponse)(nil), // 9: hyapp.luckygift.v1.ExecuteLuckyGiftDrawResponse
|
||||||
(*BatchExecuteLuckyGiftDrawRequest)(nil), // 10: hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawRequest
|
(*BatchExecuteLuckyGiftDrawRequest)(nil), // 10: hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawRequest
|
||||||
(*BatchExecuteLuckyGiftDrawResponse)(nil), // 11: hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawResponse
|
(*BatchExecuteLuckyGiftDrawResponse)(nil), // 11: hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawResponse
|
||||||
(*ExternalGiftDrawRequest)(nil), // 12: hyapp.luckygift.v1.ExternalGiftDrawRequest
|
(*ExternalGiftDrawRequest)(nil), // 12: hyapp.luckygift.v1.ExternalGiftDrawRequest
|
||||||
(*ExternalGiftDrawResponse)(nil), // 13: hyapp.luckygift.v1.ExternalGiftDrawResponse
|
(*ExternalGiftDrawResponse)(nil), // 13: hyapp.luckygift.v1.ExternalGiftDrawResponse
|
||||||
(*GetLuckyGiftConfigRequest)(nil), // 14: hyapp.luckygift.v1.GetLuckyGiftConfigRequest
|
(*GetLuckyGiftConfigRequest)(nil), // 14: hyapp.luckygift.v1.GetLuckyGiftConfigRequest
|
||||||
(*GetLuckyGiftConfigResponse)(nil), // 15: hyapp.luckygift.v1.GetLuckyGiftConfigResponse
|
(*GetLuckyGiftConfigResponse)(nil), // 15: hyapp.luckygift.v1.GetLuckyGiftConfigResponse
|
||||||
(*UpsertLuckyGiftConfigRequest)(nil), // 16: hyapp.luckygift.v1.UpsertLuckyGiftConfigRequest
|
(*UpsertLuckyGiftConfigRequest)(nil), // 16: hyapp.luckygift.v1.UpsertLuckyGiftConfigRequest
|
||||||
(*UpsertLuckyGiftConfigResponse)(nil), // 17: hyapp.luckygift.v1.UpsertLuckyGiftConfigResponse
|
(*UpsertLuckyGiftConfigResponse)(nil), // 17: hyapp.luckygift.v1.UpsertLuckyGiftConfigResponse
|
||||||
(*ListLuckyGiftConfigsRequest)(nil), // 18: hyapp.luckygift.v1.ListLuckyGiftConfigsRequest
|
(*ListLuckyGiftConfigsRequest)(nil), // 18: hyapp.luckygift.v1.ListLuckyGiftConfigsRequest
|
||||||
(*ListLuckyGiftConfigsResponse)(nil), // 19: hyapp.luckygift.v1.ListLuckyGiftConfigsResponse
|
(*ListLuckyGiftConfigsResponse)(nil), // 19: hyapp.luckygift.v1.ListLuckyGiftConfigsResponse
|
||||||
(*ListLuckyGiftDrawsRequest)(nil), // 20: hyapp.luckygift.v1.ListLuckyGiftDrawsRequest
|
(*ListLuckyGiftDrawsRequest)(nil), // 20: hyapp.luckygift.v1.ListLuckyGiftDrawsRequest
|
||||||
(*ListLuckyGiftDrawsResponse)(nil), // 21: hyapp.luckygift.v1.ListLuckyGiftDrawsResponse
|
(*ListLuckyGiftDrawsResponse)(nil), // 21: hyapp.luckygift.v1.ListLuckyGiftDrawsResponse
|
||||||
(*LuckyGiftDrawSummary)(nil), // 22: hyapp.luckygift.v1.LuckyGiftDrawSummary
|
(*LuckyGiftDrawSummary)(nil), // 22: hyapp.luckygift.v1.LuckyGiftDrawSummary
|
||||||
(*LuckyGiftPoolBalance)(nil), // 23: hyapp.luckygift.v1.LuckyGiftPoolBalance
|
(*LuckyGiftPoolBalance)(nil), // 23: hyapp.luckygift.v1.LuckyGiftPoolBalance
|
||||||
(*GetLuckyGiftDrawSummaryRequest)(nil), // 24: hyapp.luckygift.v1.GetLuckyGiftDrawSummaryRequest
|
(*GetLuckyGiftDrawSummaryRequest)(nil), // 24: hyapp.luckygift.v1.GetLuckyGiftDrawSummaryRequest
|
||||||
(*GetLuckyGiftDrawSummaryResponse)(nil), // 25: hyapp.luckygift.v1.GetLuckyGiftDrawSummaryResponse
|
(*GetLuckyGiftDrawSummaryResponse)(nil), // 25: hyapp.luckygift.v1.GetLuckyGiftDrawSummaryResponse
|
||||||
(*ListLuckyGiftPoolBalancesRequest)(nil), // 26: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesRequest
|
(*ListLuckyGiftPoolBalancesRequest)(nil), // 26: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesRequest
|
||||||
(*ListLuckyGiftPoolBalancesResponse)(nil), // 27: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesResponse
|
(*ListLuckyGiftPoolBalancesResponse)(nil), // 27: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesResponse
|
||||||
(*LuckyGiftHit)(nil), // 28: hyapp.luckygift.v1.LuckyGiftHit
|
(*LuckyGiftHit)(nil), // 28: hyapp.luckygift.v1.LuckyGiftHit
|
||||||
|
(*AdjustLuckyGiftPoolBalanceRequest)(nil), // 29: hyapp.luckygift.v1.AdjustLuckyGiftPoolBalanceRequest
|
||||||
|
(*LuckyGiftPoolAdjustment)(nil), // 30: hyapp.luckygift.v1.LuckyGiftPoolAdjustment
|
||||||
|
(*AdjustLuckyGiftPoolBalanceResponse)(nil), // 31: hyapp.luckygift.v1.AdjustLuckyGiftPoolBalanceResponse
|
||||||
}
|
}
|
||||||
var file_proto_luckygift_v1_luckygift_proto_depIdxs = []int32{
|
var file_proto_luckygift_v1_luckygift_proto_depIdxs = []int32{
|
||||||
0, // 0: hyapp.luckygift.v1.LuckyGiftMeta.meta:type_name -> hyapp.luckygift.v1.RequestMeta
|
0, // 0: hyapp.luckygift.v1.LuckyGiftMeta.meta:type_name -> hyapp.luckygift.v1.RequestMeta
|
||||||
@ -3408,31 +3812,36 @@ var file_proto_luckygift_v1_luckygift_proto_depIdxs = []int32{
|
|||||||
22, // 20: hyapp.luckygift.v1.GetLuckyGiftDrawSummaryResponse.summary:type_name -> hyapp.luckygift.v1.LuckyGiftDrawSummary
|
22, // 20: hyapp.luckygift.v1.GetLuckyGiftDrawSummaryResponse.summary:type_name -> hyapp.luckygift.v1.LuckyGiftDrawSummary
|
||||||
0, // 21: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta
|
0, // 21: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta
|
||||||
23, // 22: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesResponse.pools:type_name -> hyapp.luckygift.v1.LuckyGiftPoolBalance
|
23, // 22: hyapp.luckygift.v1.ListLuckyGiftPoolBalancesResponse.pools:type_name -> hyapp.luckygift.v1.LuckyGiftPoolBalance
|
||||||
5, // 23: hyapp.luckygift.v1.LuckyGiftService.CheckLuckyGift:input_type -> hyapp.luckygift.v1.CheckLuckyGiftRequest
|
0, // 23: hyapp.luckygift.v1.AdjustLuckyGiftPoolBalanceRequest.meta:type_name -> hyapp.luckygift.v1.RequestMeta
|
||||||
8, // 24: hyapp.luckygift.v1.LuckyGiftService.ExecuteLuckyGiftDraw:input_type -> hyapp.luckygift.v1.ExecuteLuckyGiftDrawRequest
|
30, // 24: hyapp.luckygift.v1.AdjustLuckyGiftPoolBalanceResponse.adjustment:type_name -> hyapp.luckygift.v1.LuckyGiftPoolAdjustment
|
||||||
10, // 25: hyapp.luckygift.v1.LuckyGiftService.BatchExecuteLuckyGiftDraw:input_type -> hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawRequest
|
23, // 25: hyapp.luckygift.v1.AdjustLuckyGiftPoolBalanceResponse.pool:type_name -> hyapp.luckygift.v1.LuckyGiftPoolBalance
|
||||||
12, // 26: hyapp.luckygift.v1.LuckyGiftService.ExecuteExternalGiftDraw:input_type -> hyapp.luckygift.v1.ExternalGiftDrawRequest
|
5, // 26: hyapp.luckygift.v1.LuckyGiftService.CheckLuckyGift:input_type -> hyapp.luckygift.v1.CheckLuckyGiftRequest
|
||||||
14, // 27: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftConfig:input_type -> hyapp.luckygift.v1.GetLuckyGiftConfigRequest
|
8, // 27: hyapp.luckygift.v1.LuckyGiftService.ExecuteLuckyGiftDraw:input_type -> hyapp.luckygift.v1.ExecuteLuckyGiftDrawRequest
|
||||||
16, // 28: hyapp.luckygift.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:input_type -> hyapp.luckygift.v1.UpsertLuckyGiftConfigRequest
|
10, // 28: hyapp.luckygift.v1.LuckyGiftService.BatchExecuteLuckyGiftDraw:input_type -> hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawRequest
|
||||||
18, // 29: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:input_type -> hyapp.luckygift.v1.ListLuckyGiftConfigsRequest
|
12, // 29: hyapp.luckygift.v1.LuckyGiftService.ExecuteExternalGiftDraw:input_type -> hyapp.luckygift.v1.ExternalGiftDrawRequest
|
||||||
20, // 30: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftDraws:input_type -> hyapp.luckygift.v1.ListLuckyGiftDrawsRequest
|
14, // 30: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftConfig:input_type -> hyapp.luckygift.v1.GetLuckyGiftConfigRequest
|
||||||
24, // 31: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:input_type -> hyapp.luckygift.v1.GetLuckyGiftDrawSummaryRequest
|
16, // 31: hyapp.luckygift.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:input_type -> hyapp.luckygift.v1.UpsertLuckyGiftConfigRequest
|
||||||
26, // 32: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftPoolBalances:input_type -> hyapp.luckygift.v1.ListLuckyGiftPoolBalancesRequest
|
18, // 32: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:input_type -> hyapp.luckygift.v1.ListLuckyGiftConfigsRequest
|
||||||
6, // 33: hyapp.luckygift.v1.LuckyGiftService.CheckLuckyGift:output_type -> hyapp.luckygift.v1.CheckLuckyGiftResponse
|
20, // 33: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftDraws:input_type -> hyapp.luckygift.v1.ListLuckyGiftDrawsRequest
|
||||||
9, // 34: hyapp.luckygift.v1.LuckyGiftService.ExecuteLuckyGiftDraw:output_type -> hyapp.luckygift.v1.ExecuteLuckyGiftDrawResponse
|
24, // 34: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:input_type -> hyapp.luckygift.v1.GetLuckyGiftDrawSummaryRequest
|
||||||
11, // 35: hyapp.luckygift.v1.LuckyGiftService.BatchExecuteLuckyGiftDraw:output_type -> hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawResponse
|
26, // 35: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftPoolBalances:input_type -> hyapp.luckygift.v1.ListLuckyGiftPoolBalancesRequest
|
||||||
13, // 36: hyapp.luckygift.v1.LuckyGiftService.ExecuteExternalGiftDraw:output_type -> hyapp.luckygift.v1.ExternalGiftDrawResponse
|
29, // 36: hyapp.luckygift.v1.AdminLuckyGiftService.AdjustLuckyGiftPoolBalance:input_type -> hyapp.luckygift.v1.AdjustLuckyGiftPoolBalanceRequest
|
||||||
15, // 37: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftConfig:output_type -> hyapp.luckygift.v1.GetLuckyGiftConfigResponse
|
6, // 37: hyapp.luckygift.v1.LuckyGiftService.CheckLuckyGift:output_type -> hyapp.luckygift.v1.CheckLuckyGiftResponse
|
||||||
17, // 38: hyapp.luckygift.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:output_type -> hyapp.luckygift.v1.UpsertLuckyGiftConfigResponse
|
9, // 38: hyapp.luckygift.v1.LuckyGiftService.ExecuteLuckyGiftDraw:output_type -> hyapp.luckygift.v1.ExecuteLuckyGiftDrawResponse
|
||||||
19, // 39: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:output_type -> hyapp.luckygift.v1.ListLuckyGiftConfigsResponse
|
11, // 39: hyapp.luckygift.v1.LuckyGiftService.BatchExecuteLuckyGiftDraw:output_type -> hyapp.luckygift.v1.BatchExecuteLuckyGiftDrawResponse
|
||||||
21, // 40: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftDraws:output_type -> hyapp.luckygift.v1.ListLuckyGiftDrawsResponse
|
13, // 40: hyapp.luckygift.v1.LuckyGiftService.ExecuteExternalGiftDraw:output_type -> hyapp.luckygift.v1.ExternalGiftDrawResponse
|
||||||
25, // 41: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:output_type -> hyapp.luckygift.v1.GetLuckyGiftDrawSummaryResponse
|
15, // 41: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftConfig:output_type -> hyapp.luckygift.v1.GetLuckyGiftConfigResponse
|
||||||
27, // 42: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftPoolBalances:output_type -> hyapp.luckygift.v1.ListLuckyGiftPoolBalancesResponse
|
17, // 42: hyapp.luckygift.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:output_type -> hyapp.luckygift.v1.UpsertLuckyGiftConfigResponse
|
||||||
33, // [33:43] is the sub-list for method output_type
|
19, // 43: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:output_type -> hyapp.luckygift.v1.ListLuckyGiftConfigsResponse
|
||||||
23, // [23:33] is the sub-list for method input_type
|
21, // 44: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftDraws:output_type -> hyapp.luckygift.v1.ListLuckyGiftDrawsResponse
|
||||||
23, // [23:23] is the sub-list for extension type_name
|
25, // 45: hyapp.luckygift.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:output_type -> hyapp.luckygift.v1.GetLuckyGiftDrawSummaryResponse
|
||||||
23, // [23:23] is the sub-list for extension extendee
|
27, // 46: hyapp.luckygift.v1.AdminLuckyGiftService.ListLuckyGiftPoolBalances:output_type -> hyapp.luckygift.v1.ListLuckyGiftPoolBalancesResponse
|
||||||
0, // [0:23] is the sub-list for field type_name
|
31, // 47: hyapp.luckygift.v1.AdminLuckyGiftService.AdjustLuckyGiftPoolBalance:output_type -> hyapp.luckygift.v1.AdjustLuckyGiftPoolBalanceResponse
|
||||||
|
37, // [37:48] is the sub-list for method output_type
|
||||||
|
26, // [26:37] is the sub-list for method input_type
|
||||||
|
26, // [26:26] is the sub-list for extension type_name
|
||||||
|
26, // [26:26] is the sub-list for extension extendee
|
||||||
|
0, // [0:26] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_proto_luckygift_v1_luckygift_proto_init() }
|
func init() { file_proto_luckygift_v1_luckygift_proto_init() }
|
||||||
@ -3446,7 +3855,7 @@ func file_proto_luckygift_v1_luckygift_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: file_proto_luckygift_v1_luckygift_proto_rawDesc,
|
RawDescriptor: file_proto_luckygift_v1_luckygift_proto_rawDesc,
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 29,
|
NumMessages: 32,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 2,
|
NumServices: 2,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -269,6 +269,9 @@ message LuckyGiftPoolBalance {
|
|||||||
int64 total_out = 7;
|
int64 total_out = 7;
|
||||||
bool materialized = 8;
|
bool materialized = 8;
|
||||||
int64 updated_at_ms = 9;
|
int64 updated_at_ms = 9;
|
||||||
|
string strategy_version = 10;
|
||||||
|
int64 manual_credit_total = 11;
|
||||||
|
int64 manual_debit_total = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetLuckyGiftDrawSummaryRequest {
|
message GetLuckyGiftDrawSummaryRequest {
|
||||||
@ -289,6 +292,7 @@ message GetLuckyGiftDrawSummaryResponse {
|
|||||||
message ListLuckyGiftPoolBalancesRequest {
|
message ListLuckyGiftPoolBalancesRequest {
|
||||||
RequestMeta meta = 1;
|
RequestMeta meta = 1;
|
||||||
string pool_id = 2;
|
string pool_id = 2;
|
||||||
|
string strategy_version = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListLuckyGiftPoolBalancesResponse {
|
message ListLuckyGiftPoolBalancesResponse {
|
||||||
@ -309,6 +313,7 @@ service AdminLuckyGiftService {
|
|||||||
rpc ListLuckyGiftDraws(ListLuckyGiftDrawsRequest) returns (ListLuckyGiftDrawsResponse);
|
rpc ListLuckyGiftDraws(ListLuckyGiftDrawsRequest) returns (ListLuckyGiftDrawsResponse);
|
||||||
rpc GetLuckyGiftDrawSummary(GetLuckyGiftDrawSummaryRequest) returns (GetLuckyGiftDrawSummaryResponse);
|
rpc GetLuckyGiftDrawSummary(GetLuckyGiftDrawSummaryRequest) returns (GetLuckyGiftDrawSummaryResponse);
|
||||||
rpc ListLuckyGiftPoolBalances(ListLuckyGiftPoolBalancesRequest) returns (ListLuckyGiftPoolBalancesResponse);
|
rpc ListLuckyGiftPoolBalances(ListLuckyGiftPoolBalancesRequest) returns (ListLuckyGiftPoolBalancesResponse);
|
||||||
|
rpc AdjustLuckyGiftPoolBalance(AdjustLuckyGiftPoolBalanceRequest) returns (AdjustLuckyGiftPoolBalanceResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// LuckyGiftHit 只返回批量逐份开奖中实际中奖的精简位置,不复制全部审计明细。
|
// LuckyGiftHit 只返回批量逐份开奖中实际中奖的精简位置,不复制全部审计明细。
|
||||||
@ -320,3 +325,36 @@ message LuckyGiftHit {
|
|||||||
int64 multiplier_ppm = 4;
|
int64 multiplier_ppm = 4;
|
||||||
int64 reward_coins = 5;
|
int64 reward_coins = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 调账消息也只追加在文件尾,保持所有既有 protobuf message index 稳定。
|
||||||
|
// adjustment_id 是业务幂等键;RequestMeta.request_id 只做链路追踪,不能替代资金幂等语义。
|
||||||
|
message AdjustLuckyGiftPoolBalanceRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
string pool_id = 2;
|
||||||
|
string strategy_version = 3;
|
||||||
|
string adjustment_id = 4;
|
||||||
|
string direction = 5;
|
||||||
|
int64 amount_coins = 6;
|
||||||
|
string reason = 7;
|
||||||
|
int64 operator_admin_id = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LuckyGiftPoolAdjustment {
|
||||||
|
string adjustment_id = 1;
|
||||||
|
string app_code = 2;
|
||||||
|
string pool_id = 3;
|
||||||
|
string strategy_version = 4;
|
||||||
|
string direction = 5;
|
||||||
|
int64 amount_coins = 6;
|
||||||
|
string reason = 7;
|
||||||
|
int64 operator_admin_id = 8;
|
||||||
|
int64 balance_before = 9;
|
||||||
|
int64 balance_after = 10;
|
||||||
|
int64 created_at_ms = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AdjustLuckyGiftPoolBalanceResponse {
|
||||||
|
LuckyGiftPoolAdjustment adjustment = 1;
|
||||||
|
LuckyGiftPoolBalance pool = 2;
|
||||||
|
bool idempotent_replay = 3;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.6.2
|
// - protoc-gen-go-grpc v1.6.2
|
||||||
// - protoc v5.29.2
|
// - protoc v7.35.0
|
||||||
// source: proto/luckygift/v1/luckygift.proto
|
// source: proto/luckygift/v1/luckygift.proto
|
||||||
|
|
||||||
package luckygiftv1
|
package luckygiftv1
|
||||||
@ -235,12 +235,13 @@ var LuckyGiftService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AdminLuckyGiftService_GetLuckyGiftConfig_FullMethodName = "/hyapp.luckygift.v1.AdminLuckyGiftService/GetLuckyGiftConfig"
|
AdminLuckyGiftService_GetLuckyGiftConfig_FullMethodName = "/hyapp.luckygift.v1.AdminLuckyGiftService/GetLuckyGiftConfig"
|
||||||
AdminLuckyGiftService_UpsertLuckyGiftConfig_FullMethodName = "/hyapp.luckygift.v1.AdminLuckyGiftService/UpsertLuckyGiftConfig"
|
AdminLuckyGiftService_UpsertLuckyGiftConfig_FullMethodName = "/hyapp.luckygift.v1.AdminLuckyGiftService/UpsertLuckyGiftConfig"
|
||||||
AdminLuckyGiftService_ListLuckyGiftConfigs_FullMethodName = "/hyapp.luckygift.v1.AdminLuckyGiftService/ListLuckyGiftConfigs"
|
AdminLuckyGiftService_ListLuckyGiftConfigs_FullMethodName = "/hyapp.luckygift.v1.AdminLuckyGiftService/ListLuckyGiftConfigs"
|
||||||
AdminLuckyGiftService_ListLuckyGiftDraws_FullMethodName = "/hyapp.luckygift.v1.AdminLuckyGiftService/ListLuckyGiftDraws"
|
AdminLuckyGiftService_ListLuckyGiftDraws_FullMethodName = "/hyapp.luckygift.v1.AdminLuckyGiftService/ListLuckyGiftDraws"
|
||||||
AdminLuckyGiftService_GetLuckyGiftDrawSummary_FullMethodName = "/hyapp.luckygift.v1.AdminLuckyGiftService/GetLuckyGiftDrawSummary"
|
AdminLuckyGiftService_GetLuckyGiftDrawSummary_FullMethodName = "/hyapp.luckygift.v1.AdminLuckyGiftService/GetLuckyGiftDrawSummary"
|
||||||
AdminLuckyGiftService_ListLuckyGiftPoolBalances_FullMethodName = "/hyapp.luckygift.v1.AdminLuckyGiftService/ListLuckyGiftPoolBalances"
|
AdminLuckyGiftService_ListLuckyGiftPoolBalances_FullMethodName = "/hyapp.luckygift.v1.AdminLuckyGiftService/ListLuckyGiftPoolBalances"
|
||||||
|
AdminLuckyGiftService_AdjustLuckyGiftPoolBalance_FullMethodName = "/hyapp.luckygift.v1.AdminLuckyGiftService/AdjustLuckyGiftPoolBalance"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AdminLuckyGiftServiceClient is the client API for AdminLuckyGiftService service.
|
// AdminLuckyGiftServiceClient is the client API for AdminLuckyGiftService service.
|
||||||
@ -253,6 +254,7 @@ type AdminLuckyGiftServiceClient interface {
|
|||||||
ListLuckyGiftDraws(ctx context.Context, in *ListLuckyGiftDrawsRequest, opts ...grpc.CallOption) (*ListLuckyGiftDrawsResponse, error)
|
ListLuckyGiftDraws(ctx context.Context, in *ListLuckyGiftDrawsRequest, opts ...grpc.CallOption) (*ListLuckyGiftDrawsResponse, error)
|
||||||
GetLuckyGiftDrawSummary(ctx context.Context, in *GetLuckyGiftDrawSummaryRequest, opts ...grpc.CallOption) (*GetLuckyGiftDrawSummaryResponse, error)
|
GetLuckyGiftDrawSummary(ctx context.Context, in *GetLuckyGiftDrawSummaryRequest, opts ...grpc.CallOption) (*GetLuckyGiftDrawSummaryResponse, error)
|
||||||
ListLuckyGiftPoolBalances(ctx context.Context, in *ListLuckyGiftPoolBalancesRequest, opts ...grpc.CallOption) (*ListLuckyGiftPoolBalancesResponse, error)
|
ListLuckyGiftPoolBalances(ctx context.Context, in *ListLuckyGiftPoolBalancesRequest, opts ...grpc.CallOption) (*ListLuckyGiftPoolBalancesResponse, error)
|
||||||
|
AdjustLuckyGiftPoolBalance(ctx context.Context, in *AdjustLuckyGiftPoolBalanceRequest, opts ...grpc.CallOption) (*AdjustLuckyGiftPoolBalanceResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type adminLuckyGiftServiceClient struct {
|
type adminLuckyGiftServiceClient struct {
|
||||||
@ -323,6 +325,16 @@ func (c *adminLuckyGiftServiceClient) ListLuckyGiftPoolBalances(ctx context.Cont
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *adminLuckyGiftServiceClient) AdjustLuckyGiftPoolBalance(ctx context.Context, in *AdjustLuckyGiftPoolBalanceRequest, opts ...grpc.CallOption) (*AdjustLuckyGiftPoolBalanceResponse, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(AdjustLuckyGiftPoolBalanceResponse)
|
||||||
|
err := c.cc.Invoke(ctx, AdminLuckyGiftService_AdjustLuckyGiftPoolBalance_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// AdminLuckyGiftServiceServer is the server API for AdminLuckyGiftService service.
|
// AdminLuckyGiftServiceServer is the server API for AdminLuckyGiftService service.
|
||||||
// All implementations must embed UnimplementedAdminLuckyGiftServiceServer
|
// All implementations must embed UnimplementedAdminLuckyGiftServiceServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@ -333,6 +345,7 @@ type AdminLuckyGiftServiceServer interface {
|
|||||||
ListLuckyGiftDraws(context.Context, *ListLuckyGiftDrawsRequest) (*ListLuckyGiftDrawsResponse, error)
|
ListLuckyGiftDraws(context.Context, *ListLuckyGiftDrawsRequest) (*ListLuckyGiftDrawsResponse, error)
|
||||||
GetLuckyGiftDrawSummary(context.Context, *GetLuckyGiftDrawSummaryRequest) (*GetLuckyGiftDrawSummaryResponse, error)
|
GetLuckyGiftDrawSummary(context.Context, *GetLuckyGiftDrawSummaryRequest) (*GetLuckyGiftDrawSummaryResponse, error)
|
||||||
ListLuckyGiftPoolBalances(context.Context, *ListLuckyGiftPoolBalancesRequest) (*ListLuckyGiftPoolBalancesResponse, error)
|
ListLuckyGiftPoolBalances(context.Context, *ListLuckyGiftPoolBalancesRequest) (*ListLuckyGiftPoolBalancesResponse, error)
|
||||||
|
AdjustLuckyGiftPoolBalance(context.Context, *AdjustLuckyGiftPoolBalanceRequest) (*AdjustLuckyGiftPoolBalanceResponse, error)
|
||||||
mustEmbedUnimplementedAdminLuckyGiftServiceServer()
|
mustEmbedUnimplementedAdminLuckyGiftServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,6 +374,9 @@ func (UnimplementedAdminLuckyGiftServiceServer) GetLuckyGiftDrawSummary(context.
|
|||||||
func (UnimplementedAdminLuckyGiftServiceServer) ListLuckyGiftPoolBalances(context.Context, *ListLuckyGiftPoolBalancesRequest) (*ListLuckyGiftPoolBalancesResponse, error) {
|
func (UnimplementedAdminLuckyGiftServiceServer) ListLuckyGiftPoolBalances(context.Context, *ListLuckyGiftPoolBalancesRequest) (*ListLuckyGiftPoolBalancesResponse, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method ListLuckyGiftPoolBalances not implemented")
|
return nil, status.Error(codes.Unimplemented, "method ListLuckyGiftPoolBalances not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedAdminLuckyGiftServiceServer) AdjustLuckyGiftPoolBalance(context.Context, *AdjustLuckyGiftPoolBalanceRequest) (*AdjustLuckyGiftPoolBalanceResponse, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method AdjustLuckyGiftPoolBalance not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedAdminLuckyGiftServiceServer) mustEmbedUnimplementedAdminLuckyGiftServiceServer() {}
|
func (UnimplementedAdminLuckyGiftServiceServer) mustEmbedUnimplementedAdminLuckyGiftServiceServer() {}
|
||||||
func (UnimplementedAdminLuckyGiftServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedAdminLuckyGiftServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
@ -490,6 +506,24 @@ func _AdminLuckyGiftService_ListLuckyGiftPoolBalances_Handler(srv interface{}, c
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _AdminLuckyGiftService_AdjustLuckyGiftPoolBalance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(AdjustLuckyGiftPoolBalanceRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(AdminLuckyGiftServiceServer).AdjustLuckyGiftPoolBalance(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: AdminLuckyGiftService_AdjustLuckyGiftPoolBalance_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(AdminLuckyGiftServiceServer).AdjustLuckyGiftPoolBalance(ctx, req.(*AdjustLuckyGiftPoolBalanceRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
// AdminLuckyGiftService_ServiceDesc is the grpc.ServiceDesc for AdminLuckyGiftService service.
|
// AdminLuckyGiftService_ServiceDesc is the grpc.ServiceDesc for AdminLuckyGiftService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@ -521,6 +555,10 @@ var AdminLuckyGiftService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "ListLuckyGiftPoolBalances",
|
MethodName: "ListLuckyGiftPoolBalances",
|
||||||
Handler: _AdminLuckyGiftService_ListLuckyGiftPoolBalances_Handler,
|
Handler: _AdminLuckyGiftService_ListLuckyGiftPoolBalances_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "AdjustLuckyGiftPoolBalance",
|
||||||
|
Handler: _AdminLuckyGiftService_AdjustLuckyGiftPoolBalance_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "proto/luckygift/v1/luckygift.proto",
|
Metadata: "proto/luckygift/v1/luckygift.proto",
|
||||||
|
|||||||
@ -17,6 +17,7 @@ type Client interface {
|
|||||||
ListLuckyGiftDraws(ctx context.Context, req *luckygiftv1.ListLuckyGiftDrawsRequest) (*luckygiftv1.ListLuckyGiftDrawsResponse, error)
|
ListLuckyGiftDraws(ctx context.Context, req *luckygiftv1.ListLuckyGiftDrawsRequest) (*luckygiftv1.ListLuckyGiftDrawsResponse, error)
|
||||||
GetLuckyGiftDrawSummary(ctx context.Context, req *luckygiftv1.GetLuckyGiftDrawSummaryRequest) (*luckygiftv1.GetLuckyGiftDrawSummaryResponse, error)
|
GetLuckyGiftDrawSummary(ctx context.Context, req *luckygiftv1.GetLuckyGiftDrawSummaryRequest) (*luckygiftv1.GetLuckyGiftDrawSummaryResponse, error)
|
||||||
ListLuckyGiftPoolBalances(ctx context.Context, req *luckygiftv1.ListLuckyGiftPoolBalancesRequest) (*luckygiftv1.ListLuckyGiftPoolBalancesResponse, error)
|
ListLuckyGiftPoolBalances(ctx context.Context, req *luckygiftv1.ListLuckyGiftPoolBalancesRequest) (*luckygiftv1.ListLuckyGiftPoolBalancesResponse, error)
|
||||||
|
AdjustLuckyGiftPoolBalance(ctx context.Context, req *luckygiftv1.AdjustLuckyGiftPoolBalanceRequest) (*luckygiftv1.AdjustLuckyGiftPoolBalanceResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type GRPCClient struct {
|
type GRPCClient struct {
|
||||||
@ -50,3 +51,7 @@ func (c *GRPCClient) GetLuckyGiftDrawSummary(ctx context.Context, req *luckygift
|
|||||||
func (c *GRPCClient) ListLuckyGiftPoolBalances(ctx context.Context, req *luckygiftv1.ListLuckyGiftPoolBalancesRequest) (*luckygiftv1.ListLuckyGiftPoolBalancesResponse, error) {
|
func (c *GRPCClient) ListLuckyGiftPoolBalances(ctx context.Context, req *luckygiftv1.ListLuckyGiftPoolBalancesRequest) (*luckygiftv1.ListLuckyGiftPoolBalancesResponse, error) {
|
||||||
return c.client.ListLuckyGiftPoolBalances(ctx, req)
|
return c.client.ListLuckyGiftPoolBalances(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *GRPCClient) AdjustLuckyGiftPoolBalance(ctx context.Context, req *luckygiftv1.AdjustLuckyGiftPoolBalanceRequest) (*luckygiftv1.AdjustLuckyGiftPoolBalanceResponse, error) {
|
||||||
|
return c.client.AdjustLuckyGiftPoolBalance(ctx, req)
|
||||||
|
}
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import (
|
|||||||
luckygiftv1 "hyapp.local/api/proto/luckygift/v1"
|
luckygiftv1 "hyapp.local/api/proto/luckygift/v1"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
grpcstatus "google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
@ -128,15 +130,44 @@ type drawSummaryDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type poolBalanceDTO struct {
|
type poolBalanceDTO struct {
|
||||||
AppCode string `json:"app_code"`
|
AppCode string `json:"app_code"`
|
||||||
PoolID string `json:"pool_id"`
|
PoolID string `json:"pool_id"`
|
||||||
Balance int64 `json:"balance"`
|
StrategyVersion string `json:"strategy_version"`
|
||||||
ReserveFloor int64 `json:"reserve_floor"`
|
Balance int64 `json:"balance"`
|
||||||
AvailableBalance int64 `json:"available_balance"`
|
ReserveFloor int64 `json:"reserve_floor"`
|
||||||
TotalIn int64 `json:"total_in"`
|
AvailableBalance int64 `json:"available_balance"`
|
||||||
TotalOut int64 `json:"total_out"`
|
TotalIn int64 `json:"total_in"`
|
||||||
Materialized bool `json:"materialized"`
|
TotalOut int64 `json:"total_out"`
|
||||||
UpdatedAtMS int64 `json:"updated_at_ms"`
|
ManualCreditTotal int64 `json:"manual_credit_total"`
|
||||||
|
ManualDebitTotal int64 `json:"manual_debit_total"`
|
||||||
|
Materialized bool `json:"materialized"`
|
||||||
|
UpdatedAtMS int64 `json:"updated_at_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type poolAdjustmentRequest struct {
|
||||||
|
AdjustmentID string `json:"adjustment_id"`
|
||||||
|
AmountCoins int64 `json:"amount_coins"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type poolAdjustmentDTO struct {
|
||||||
|
AdjustmentID string `json:"adjustment_id"`
|
||||||
|
AppCode string `json:"app_code"`
|
||||||
|
PoolID string `json:"pool_id"`
|
||||||
|
StrategyVersion string `json:"strategy_version"`
|
||||||
|
Direction string `json:"direction"`
|
||||||
|
AmountCoins int64 `json:"amount_coins"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
OperatorAdminID int64 `json:"operator_admin_id"`
|
||||||
|
BalanceBefore int64 `json:"balance_before"`
|
||||||
|
BalanceAfter int64 `json:"balance_after"`
|
||||||
|
CreatedAtMS int64 `json:"created_at_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type poolAdjustmentResponseDTO struct {
|
||||||
|
Adjustment poolAdjustmentDTO `json:"adjustment"`
|
||||||
|
Pool poolBalanceDTO `json:"pool"`
|
||||||
|
IdempotentReplay bool `json:"idempotent_replay"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) GetLuckyGiftConfig(c *gin.Context) {
|
func (h *Handler) GetLuckyGiftConfig(c *gin.Context) {
|
||||||
@ -273,8 +304,9 @@ func (h *Handler) ListLuckyGiftPoolBalances(c *gin.Context) {
|
|||||||
ctx, cancel := h.luckyGiftContext(c)
|
ctx, cancel := h.luckyGiftContext(c)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
resp, err := h.luckyGift.ListLuckyGiftPoolBalances(ctx, &luckygiftv1.ListLuckyGiftPoolBalancesRequest{
|
resp, err := h.luckyGift.ListLuckyGiftPoolBalances(ctx, &luckygiftv1.ListLuckyGiftPoolBalancesRequest{
|
||||||
Meta: h.meta(c),
|
Meta: h.meta(c),
|
||||||
PoolId: strings.TrimSpace(c.Query("pool_id")),
|
PoolId: strings.TrimSpace(c.Query("pool_id")),
|
||||||
|
StrategyVersion: strings.ToLower(strings.TrimSpace(c.Query("strategy_version"))),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.ServerError(c, "获取幸运礼物奖池余额失败")
|
response.ServerError(c, "获取幸运礼物奖池余额失败")
|
||||||
@ -287,6 +319,75 @@ func (h *Handler) ListLuckyGiftPoolBalances(c *gin.Context) {
|
|||||||
response.OK(c, gin.H{"items": items, "total": len(items)})
|
response.OK(c, gin.H{"items": items, "total": len(items)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) CreditLuckyGiftPoolBalance(c *gin.Context) {
|
||||||
|
h.adjustLuckyGiftPoolBalance(c, "in")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) DebitLuckyGiftPoolBalance(c *gin.Context) {
|
||||||
|
h.adjustLuckyGiftPoolBalance(c, "out")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) adjustLuckyGiftPoolBalance(c *gin.Context, direction string) {
|
||||||
|
appCode := strings.ToLower(strings.TrimSpace(c.Query("app_code")))
|
||||||
|
poolID := strings.TrimSpace(c.Query("pool_id"))
|
||||||
|
strategyVersion := strings.ToLower(strings.TrimSpace(c.Query("strategy_version")))
|
||||||
|
if appCode == "" || poolID == "" || (strategyVersion != "fixed_v2" && strategyVersion != "dynamic_v3") {
|
||||||
|
response.BadRequest(c, "app_code, pool_id and strategy_version are required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req poolAdjustmentRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.BadRequest(c, "幸运礼物奖池调整参数不正确")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.AdjustmentID = strings.TrimSpace(req.AdjustmentID)
|
||||||
|
req.Reason = strings.TrimSpace(req.Reason)
|
||||||
|
if req.AdjustmentID == "" || req.AmountCoins <= 0 || req.Reason == "" {
|
||||||
|
response.BadRequest(c, "adjustment_id, positive amount_coins and reason are required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx, cancel := h.luckyGiftContext(c)
|
||||||
|
defer cancel()
|
||||||
|
resp, err := h.luckyGift.AdjustLuckyGiftPoolBalance(ctx, &luckygiftv1.AdjustLuckyGiftPoolBalanceRequest{
|
||||||
|
Meta: h.metaForApp(c, appCode),
|
||||||
|
PoolId: poolID,
|
||||||
|
StrategyVersion: strategyVersion,
|
||||||
|
AdjustmentId: req.AdjustmentID,
|
||||||
|
Direction: direction,
|
||||||
|
AmountCoins: req.AmountCoins,
|
||||||
|
Reason: req.Reason,
|
||||||
|
OperatorAdminId: int64(middleware.CurrentUserID(c)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
h.writePoolAdjustmentError(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item := poolAdjustmentResponseDTO{
|
||||||
|
Adjustment: poolAdjustmentFromProto(resp.GetAdjustment()),
|
||||||
|
Pool: poolBalanceFromProto(resp.GetPool()),
|
||||||
|
IdempotentReplay: resp.GetIdempotentReplay(),
|
||||||
|
}
|
||||||
|
shared.OperationLogWithResourceID(
|
||||||
|
c, h.audit, "adjust-lucky-gift-pool-"+direction, "lucky_pool_adjustments", item.Adjustment.AdjustmentID, "success",
|
||||||
|
fmt.Sprintf("app_code=%s pool_id=%s strategy_version=%s direction=%s amount_coins=%d reason=%s", appCode, poolID, strategyVersion, direction, req.AmountCoins, req.Reason),
|
||||||
|
)
|
||||||
|
response.OK(c, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) writePoolAdjustmentError(c *gin.Context, err error) {
|
||||||
|
status := grpcstatus.Convert(err)
|
||||||
|
switch status.Code() {
|
||||||
|
case codes.InvalidArgument:
|
||||||
|
response.BadRequest(c, status.Message())
|
||||||
|
case codes.NotFound:
|
||||||
|
response.NotFound(c, status.Message())
|
||||||
|
case codes.AlreadyExists, codes.FailedPrecondition, codes.Aborted:
|
||||||
|
response.Conflict(c, status.Message())
|
||||||
|
default:
|
||||||
|
response.ServerError(c, "调整幸运礼物奖池余额失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) meta(c *gin.Context) *luckygiftv1.RequestMeta {
|
func (h *Handler) meta(c *gin.Context) *luckygiftv1.RequestMeta {
|
||||||
return h.metaForApp(c, "")
|
return h.metaForApp(c, "")
|
||||||
}
|
}
|
||||||
@ -311,6 +412,12 @@ func (h *Handler) luckyGiftContext(c *gin.Context) (context.Context, context.Can
|
|||||||
}
|
}
|
||||||
|
|
||||||
func configToProto(req configRequest) *luckygiftv1.LuckyGiftRuleConfig {
|
func configToProto(req configRequest) *luckygiftv1.LuckyGiftRuleConfig {
|
||||||
|
strategyVersion := strings.ToLower(strings.TrimSpace(req.StrategyVersion))
|
||||||
|
initialPoolCoins := req.InitialPoolCoins
|
||||||
|
if strategyVersion == "dynamic_v3" {
|
||||||
|
// V3 启动资金已经迁移为独立人工 credit;旧页面残留的 1,000,000 不能在再次保存时回写不可变配置。
|
||||||
|
initialPoolCoins = 0
|
||||||
|
}
|
||||||
stages := make([]*luckygiftv1.LuckyGiftRuleStage, 0, len(req.Stages))
|
stages := make([]*luckygiftv1.LuckyGiftRuleStage, 0, len(req.Stages))
|
||||||
for _, stage := range req.Stages {
|
for _, stage := range req.Stages {
|
||||||
stageName := strings.TrimSpace(stage.Stage)
|
stageName := strings.TrimSpace(stage.Stage)
|
||||||
@ -339,7 +446,7 @@ func configToProto(req configRequest) *luckygiftv1.LuckyGiftRuleConfig {
|
|||||||
return &luckygiftv1.LuckyGiftRuleConfig{
|
return &luckygiftv1.LuckyGiftRuleConfig{
|
||||||
AppCode: strings.ToLower(strings.TrimSpace(req.AppCode)),
|
AppCode: strings.ToLower(strings.TrimSpace(req.AppCode)),
|
||||||
PoolId: strings.TrimSpace(req.PoolID),
|
PoolId: strings.TrimSpace(req.PoolID),
|
||||||
StrategyVersion: strings.ToLower(strings.TrimSpace(req.StrategyVersion)),
|
StrategyVersion: strategyVersion,
|
||||||
Enabled: req.Enabled,
|
Enabled: req.Enabled,
|
||||||
TargetRtpPpm: percentToPPM(req.TargetRTPPercent),
|
TargetRtpPpm: percentToPPM(req.TargetRTPPercent),
|
||||||
PoolRatePpm: percentToPPM(req.PoolRatePercent),
|
PoolRatePpm: percentToPPM(req.PoolRatePercent),
|
||||||
@ -351,7 +458,7 @@ func configToProto(req configRequest) *luckygiftv1.LuckyGiftRuleConfig {
|
|||||||
NoviceMaxEquivalentDraws: req.NoviceMaxEquivalentDraws,
|
NoviceMaxEquivalentDraws: req.NoviceMaxEquivalentDraws,
|
||||||
NormalMaxEquivalentDraws: req.NormalMaxEquivalentDraws,
|
NormalMaxEquivalentDraws: req.NormalMaxEquivalentDraws,
|
||||||
EffectiveFromMs: req.EffectiveFromMS,
|
EffectiveFromMs: req.EffectiveFromMS,
|
||||||
InitialPoolCoins: req.InitialPoolCoins,
|
InitialPoolCoins: initialPoolCoins,
|
||||||
LossStreakGuarantee: req.LossStreakGuarantee,
|
LossStreakGuarantee: req.LossStreakGuarantee,
|
||||||
LowWatermarkCoins: req.LowWatermarkCoins,
|
LowWatermarkCoins: req.LowWatermarkCoins,
|
||||||
LowWaterNonzeroFactorPpm: percentToPPM(req.LowWaterNonzeroFactorPercent),
|
LowWaterNonzeroFactorPpm: percentToPPM(req.LowWaterNonzeroFactorPercent),
|
||||||
@ -399,6 +506,12 @@ func configFromProto(config *luckygiftv1.LuckyGiftRuleConfig) configDTO {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
poolID := strings.TrimSpace(config.GetPoolId())
|
poolID := strings.TrimSpace(config.GetPoolId())
|
||||||
|
strategyVersion := strings.ToLower(strings.TrimSpace(config.GetStrategyVersion()))
|
||||||
|
initialPoolCoins := config.GetInitialPoolCoins()
|
||||||
|
if strategyVersion == "dynamic_v3" {
|
||||||
|
// 兼容读取迁移前 V3 快照时也展示 0,避免运营误以为规则字段仍会自动形成账本资金。
|
||||||
|
initialPoolCoins = 0
|
||||||
|
}
|
||||||
return configDTO{
|
return configDTO{
|
||||||
RuleVersion: config.GetRuleVersion(),
|
RuleVersion: config.GetRuleVersion(),
|
||||||
CreatedByAdminID: config.GetCreatedByAdminId(),
|
CreatedByAdminID: config.GetCreatedByAdminId(),
|
||||||
@ -406,7 +519,7 @@ func configFromProto(config *luckygiftv1.LuckyGiftRuleConfig) configDTO {
|
|||||||
configRequest: configRequest{
|
configRequest: configRequest{
|
||||||
AppCode: config.GetAppCode(),
|
AppCode: config.GetAppCode(),
|
||||||
PoolID: poolID,
|
PoolID: poolID,
|
||||||
StrategyVersion: config.GetStrategyVersion(),
|
StrategyVersion: strategyVersion,
|
||||||
Enabled: config.GetEnabled(),
|
Enabled: config.GetEnabled(),
|
||||||
TargetRTPPercent: ppmToPercent(config.GetTargetRtpPpm()),
|
TargetRTPPercent: ppmToPercent(config.GetTargetRtpPpm()),
|
||||||
PoolRatePercent: ppmToPercent(config.GetPoolRatePpm()),
|
PoolRatePercent: ppmToPercent(config.GetPoolRatePpm()),
|
||||||
@ -418,7 +531,7 @@ func configFromProto(config *luckygiftv1.LuckyGiftRuleConfig) configDTO {
|
|||||||
NoviceMaxEquivalentDraws: config.GetNoviceMaxEquivalentDraws(),
|
NoviceMaxEquivalentDraws: config.GetNoviceMaxEquivalentDraws(),
|
||||||
NormalMaxEquivalentDraws: config.GetNormalMaxEquivalentDraws(),
|
NormalMaxEquivalentDraws: config.GetNormalMaxEquivalentDraws(),
|
||||||
EffectiveFromMS: config.GetEffectiveFromMs(),
|
EffectiveFromMS: config.GetEffectiveFromMs(),
|
||||||
InitialPoolCoins: config.GetInitialPoolCoins(),
|
InitialPoolCoins: initialPoolCoins,
|
||||||
LossStreakGuarantee: config.GetLossStreakGuarantee(),
|
LossStreakGuarantee: config.GetLossStreakGuarantee(),
|
||||||
LowWatermarkCoins: config.GetLowWatermarkCoins(),
|
LowWatermarkCoins: config.GetLowWatermarkCoins(),
|
||||||
LowWaterNonzeroFactorPercent: ppmToPercent(config.GetLowWaterNonzeroFactorPpm()),
|
LowWaterNonzeroFactorPercent: ppmToPercent(config.GetLowWaterNonzeroFactorPpm()),
|
||||||
@ -551,15 +664,37 @@ func poolBalanceFromProto(pool *luckygiftv1.LuckyGiftPoolBalance) poolBalanceDTO
|
|||||||
return poolBalanceDTO{}
|
return poolBalanceDTO{}
|
||||||
}
|
}
|
||||||
return poolBalanceDTO{
|
return poolBalanceDTO{
|
||||||
AppCode: pool.GetAppCode(),
|
AppCode: pool.GetAppCode(),
|
||||||
PoolID: pool.GetPoolId(),
|
PoolID: pool.GetPoolId(),
|
||||||
Balance: pool.GetBalance(),
|
StrategyVersion: pool.GetStrategyVersion(),
|
||||||
ReserveFloor: pool.GetReserveFloor(),
|
Balance: pool.GetBalance(),
|
||||||
AvailableBalance: pool.GetAvailableBalance(),
|
ReserveFloor: pool.GetReserveFloor(),
|
||||||
TotalIn: pool.GetTotalIn(),
|
AvailableBalance: pool.GetAvailableBalance(),
|
||||||
TotalOut: pool.GetTotalOut(),
|
TotalIn: pool.GetTotalIn(),
|
||||||
Materialized: pool.GetMaterialized(),
|
TotalOut: pool.GetTotalOut(),
|
||||||
UpdatedAtMS: pool.GetUpdatedAtMs(),
|
ManualCreditTotal: pool.GetManualCreditTotal(),
|
||||||
|
ManualDebitTotal: pool.GetManualDebitTotal(),
|
||||||
|
Materialized: pool.GetMaterialized(),
|
||||||
|
UpdatedAtMS: pool.GetUpdatedAtMs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func poolAdjustmentFromProto(adjustment *luckygiftv1.LuckyGiftPoolAdjustment) poolAdjustmentDTO {
|
||||||
|
if adjustment == nil {
|
||||||
|
return poolAdjustmentDTO{}
|
||||||
|
}
|
||||||
|
return poolAdjustmentDTO{
|
||||||
|
AdjustmentID: adjustment.GetAdjustmentId(),
|
||||||
|
AppCode: adjustment.GetAppCode(),
|
||||||
|
PoolID: adjustment.GetPoolId(),
|
||||||
|
StrategyVersion: adjustment.GetStrategyVersion(),
|
||||||
|
Direction: adjustment.GetDirection(),
|
||||||
|
AmountCoins: adjustment.GetAmountCoins(),
|
||||||
|
Reason: adjustment.GetReason(),
|
||||||
|
OperatorAdminID: adjustment.GetOperatorAdminId(),
|
||||||
|
BalanceBefore: adjustment.GetBalanceBefore(),
|
||||||
|
BalanceAfter: adjustment.GetBalanceAfter(),
|
||||||
|
CreatedAtMS: adjustment.GetCreatedAtMs(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,35 @@
|
|||||||
package luckygift
|
package luckygift
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hyapp-admin-server/internal/integration/luckygiftadmin"
|
||||||
|
"hyapp-admin-server/internal/middleware"
|
||||||
luckygiftv1 "hyapp.local/api/proto/luckygift/v1"
|
luckygiftv1 "hyapp.local/api/proto/luckygift/v1"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type poolAdjustmentClientStub struct {
|
||||||
|
luckygiftadmin.Client
|
||||||
|
request *luckygiftv1.AdjustLuckyGiftPoolBalanceRequest
|
||||||
|
response *luckygiftv1.AdjustLuckyGiftPoolBalanceResponse
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *poolAdjustmentClientStub) AdjustLuckyGiftPoolBalance(_ context.Context, request *luckygiftv1.AdjustLuckyGiftPoolBalanceRequest) (*luckygiftv1.AdjustLuckyGiftPoolBalanceResponse, error) {
|
||||||
|
s.request = request
|
||||||
|
return s.response, s.err
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfigToProtoConvertsBusinessUnitsToPPM(t *testing.T) {
|
func TestConfigToProtoConvertsBusinessUnitsToPPM(t *testing.T) {
|
||||||
config := configToProto(configRequest{
|
config := configToProto(configRequest{
|
||||||
PoolID: " lucky_100 ",
|
PoolID: " lucky_100 ",
|
||||||
@ -62,6 +86,63 @@ func TestConfigToProtoConvertsBusinessUnitsToPPM(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCreditLuckyGiftPoolBalanceForwardsAuditedOwnerCommand(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
client := &poolAdjustmentClientStub{response: &luckygiftv1.AdjustLuckyGiftPoolBalanceResponse{
|
||||||
|
Adjustment: &luckygiftv1.LuckyGiftPoolAdjustment{
|
||||||
|
AdjustmentId: "adjustment-1", AppCode: "aslan", PoolId: "lucky", StrategyVersion: "dynamic_v3",
|
||||||
|
Direction: "in", AmountCoins: 100, Reason: "launch budget", OperatorAdminId: 7, BalanceBefore: 0, BalanceAfter: 100,
|
||||||
|
},
|
||||||
|
Pool: &luckygiftv1.LuckyGiftPoolBalance{
|
||||||
|
AppCode: "aslan", PoolId: "lucky", StrategyVersion: "dynamic_v3", Balance: 100,
|
||||||
|
ManualCreditTotal: 100, Materialized: true,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(recorder)
|
||||||
|
c.Request = httptest.NewRequest(http.MethodPost, "/admin/ops-center/lucky-gifts/pools/credit?app_code=aslan&pool_id=lucky&strategy_version=dynamic_v3", bytes.NewBufferString(`{"adjustment_id":"adjustment-1","amount_coins":100,"reason":"launch budget"}`))
|
||||||
|
c.Request.Header.Set("Content-Type", "application/json")
|
||||||
|
c.Set(middleware.ContextUserID, uint(7))
|
||||||
|
c.Set(middleware.ContextRequestID, "request-1")
|
||||||
|
New(client, time.Second, nil).CreditLuckyGiftPoolBalance(c)
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
if client.request == nil || client.request.GetDirection() != "in" || client.request.GetOperatorAdminId() != 7 ||
|
||||||
|
client.request.GetMeta().GetAppCode() != "aslan" || client.request.GetMeta().GetRequestId() != "request-1" {
|
||||||
|
t.Fatalf("owner request=%+v", client.request)
|
||||||
|
}
|
||||||
|
var envelope struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Data struct {
|
||||||
|
Pool struct {
|
||||||
|
StrategyVersion string `json:"strategy_version"`
|
||||||
|
ManualCreditTotal int64 `json:"manual_credit_total"`
|
||||||
|
} `json:"pool"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil {
|
||||||
|
t.Fatalf("decode response: %v", err)
|
||||||
|
}
|
||||||
|
if envelope.Code != 0 || envelope.Data.Pool.StrategyVersion != "dynamic_v3" || envelope.Data.Pool.ManualCreditTotal != 100 {
|
||||||
|
t.Fatalf("response=%s", recorder.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDebitLuckyGiftPoolBalanceMapsOwnerConflictToHTTP409(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
client := &poolAdjustmentClientStub{err: status.Error(codes.FailedPrecondition, "debit would cross reserve floor")}
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(recorder)
|
||||||
|
c.Request = httptest.NewRequest(http.MethodPost, "/admin/ops-center/lucky-gifts/pools/debit?app_code=aslan&pool_id=lucky&strategy_version=dynamic_v3", bytes.NewBufferString(`{"adjustment_id":"adjustment-2","amount_coins":101,"reason":"return budget"}`))
|
||||||
|
c.Request.Header.Set("Content-Type", "application/json")
|
||||||
|
c.Set(middleware.ContextUserID, uint(7))
|
||||||
|
New(client, time.Second, nil).DebitLuckyGiftPoolBalance(c)
|
||||||
|
if recorder.Code != http.StatusConflict {
|
||||||
|
t.Fatalf("status=%d want=409 body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfigToProtoGeneratesUniqueTierIDs(t *testing.T) {
|
func TestConfigToProtoGeneratesUniqueTierIDs(t *testing.T) {
|
||||||
config := configToProto(configRequest{
|
config := configToProto(configRequest{
|
||||||
PoolID: "lucky",
|
PoolID: "lucky",
|
||||||
@ -182,6 +263,9 @@ func TestDynamicConfigAdminMappingRoundTrip(t *testing.T) {
|
|||||||
if protoConfig.GetStrategyVersion() != "dynamic_v3" || protoConfig.GetProfitRatePpm() != 10_000 || protoConfig.GetAnchorRatePpm() != 10_000 {
|
if protoConfig.GetStrategyVersion() != "dynamic_v3" || protoConfig.GetProfitRatePpm() != 10_000 || protoConfig.GetAnchorRatePpm() != 10_000 {
|
||||||
t.Fatalf("strategy/fund split proto mismatch: %+v", protoConfig)
|
t.Fatalf("strategy/fund split proto mismatch: %+v", protoConfig)
|
||||||
}
|
}
|
||||||
|
if protoConfig.GetInitialPoolCoins() != 0 {
|
||||||
|
t.Fatalf("dynamic_v3 legacy initial pool was forwarded: %d", protoConfig.GetInitialPoolCoins())
|
||||||
|
}
|
||||||
if protoConfig.GetLowWaterNonzeroFactorPpm() != 700_000 || protoConfig.GetHighWaterNonzeroFactorPpm() != 1_300_000 || protoConfig.GetRechargeBoostFactorPpm() != 1_100_000 {
|
if protoConfig.GetLowWaterNonzeroFactorPpm() != 700_000 || protoConfig.GetHighWaterNonzeroFactorPpm() != 1_300_000 || protoConfig.GetRechargeBoostFactorPpm() != 1_100_000 {
|
||||||
t.Fatalf("dynamic factors lost precision in admin mapping: %+v", protoConfig)
|
t.Fatalf("dynamic factors lost precision in admin mapping: %+v", protoConfig)
|
||||||
}
|
}
|
||||||
@ -197,6 +281,9 @@ func TestDynamicConfigAdminMappingRoundTrip(t *testing.T) {
|
|||||||
if dto.StrategyVersion != "dynamic_v3" || dto.ProfitRatePercent != 1 || dto.AnchorRatePercent != 1 {
|
if dto.StrategyVersion != "dynamic_v3" || dto.ProfitRatePercent != 1 || dto.AnchorRatePercent != 1 {
|
||||||
t.Fatalf("strategy/fund split DTO mismatch: %+v", dto)
|
t.Fatalf("strategy/fund split DTO mismatch: %+v", dto)
|
||||||
}
|
}
|
||||||
|
if dto.InitialPoolCoins != 0 {
|
||||||
|
t.Fatalf("dynamic_v3 DTO exposed legacy initial pool: %d", dto.InitialPoolCoins)
|
||||||
|
}
|
||||||
if dto.LowWaterNonzeroFactorPercent != 70 || dto.HighWaterNonzeroFactorPercent != 130 || dto.RechargeBoostFactorPercent != 110 {
|
if dto.LowWaterNonzeroFactorPercent != 70 || dto.HighWaterNonzeroFactorPercent != 130 || dto.RechargeBoostFactorPercent != 110 {
|
||||||
t.Fatalf("dynamic factor DTO mismatch: %+v", dto)
|
t.Fatalf("dynamic factor DTO mismatch: %+v", dto)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,4 +25,6 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
|
|||||||
group.GET("/lucky-gifts/draws", middleware.RequirePermission("lucky-gift:view"), h.luckyGift.ListLuckyGiftDraws)
|
group.GET("/lucky-gifts/draws", middleware.RequirePermission("lucky-gift:view"), h.luckyGift.ListLuckyGiftDraws)
|
||||||
group.GET("/lucky-gifts/summary", middleware.RequirePermission("lucky-gift:view"), h.luckyGift.GetLuckyGiftDrawSummary)
|
group.GET("/lucky-gifts/summary", middleware.RequirePermission("lucky-gift:view"), h.luckyGift.GetLuckyGiftDrawSummary)
|
||||||
group.GET("/lucky-gifts/pools", middleware.RequirePermission("lucky-gift:view"), h.luckyGift.ListLuckyGiftPoolBalances)
|
group.GET("/lucky-gifts/pools", middleware.RequirePermission("lucky-gift:view"), h.luckyGift.ListLuckyGiftPoolBalances)
|
||||||
|
group.POST("/lucky-gifts/pools/credit", middleware.RequirePermission("lucky-gift:pool-credit"), h.luckyGift.CreditLuckyGiftPoolBalance)
|
||||||
|
group.POST("/lucky-gifts/pools/debit", middleware.RequirePermission("lucky-gift:pool-debit"), h.luckyGift.DebitLuckyGiftPoolBalance)
|
||||||
}
|
}
|
||||||
|
|||||||
87
server/admin/internal/modules/opscenter/routes_test.go
Normal file
87
server/admin/internal/modules/opscenter/routes_test.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package opscenter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hyapp-admin-server/internal/integration/luckygiftadmin"
|
||||||
|
"hyapp-admin-server/internal/middleware"
|
||||||
|
luckygiftmodule "hyapp-admin-server/internal/modules/luckygift"
|
||||||
|
luckygiftv1 "hyapp.local/api/proto/luckygift/v1"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type poolPermissionClient struct {
|
||||||
|
luckygiftadmin.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (poolPermissionClient) AdjustLuckyGiftPoolBalance(_ context.Context, req *luckygiftv1.AdjustLuckyGiftPoolBalanceRequest) (*luckygiftv1.AdjustLuckyGiftPoolBalanceResponse, error) {
|
||||||
|
return &luckygiftv1.AdjustLuckyGiftPoolBalanceResponse{
|
||||||
|
Adjustment: &luckygiftv1.LuckyGiftPoolAdjustment{
|
||||||
|
AdjustmentId: req.GetAdjustmentId(), AppCode: req.GetMeta().GetAppCode(), PoolId: req.GetPoolId(),
|
||||||
|
StrategyVersion: req.GetStrategyVersion(), Direction: req.GetDirection(), AmountCoins: req.GetAmountCoins(),
|
||||||
|
},
|
||||||
|
Pool: &luckygiftv1.LuckyGiftPoolBalance{
|
||||||
|
AppCode: req.GetMeta().GetAppCode(), PoolId: req.GetPoolId(), StrategyVersion: req.GetStrategyVersion(), Materialized: true,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterRoutesExposesStrategyPoolAdjustmentEndpoints(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
engine := gin.New()
|
||||||
|
RegisterRoutes(engine.Group("/api/v1"), New(nil, luckygiftmodule.New(poolPermissionClient{}, 0, nil)))
|
||||||
|
routes := make(map[string]struct{})
|
||||||
|
for _, route := range engine.Routes() {
|
||||||
|
routes[route.Method+" "+route.Path] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, expected := range []string{
|
||||||
|
"POST /api/v1/admin/ops-center/lucky-gifts/pools/credit",
|
||||||
|
"POST /api/v1/admin/ops-center/lucky-gifts/pools/debit",
|
||||||
|
} {
|
||||||
|
if _, ok := routes[expected]; !ok {
|
||||||
|
t.Fatalf("missing route %s; actual=%v", expected, routes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPoolAdjustmentRoutesRequireDirectionSpecificPermission(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
permission string
|
||||||
|
wantStatus int
|
||||||
|
}{
|
||||||
|
{name: "credit allowed", path: "credit", permission: "lucky-gift:pool-credit", wantStatus: http.StatusOK},
|
||||||
|
{name: "credit rejects debit permission", path: "credit", permission: "lucky-gift:pool-debit", wantStatus: http.StatusForbidden},
|
||||||
|
{name: "debit allowed", path: "debit", permission: "lucky-gift:pool-debit", wantStatus: http.StatusOK},
|
||||||
|
{name: "debit rejects view permission", path: "debit", permission: "lucky-gift:view", wantStatus: http.StatusForbidden},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
engine := gin.New()
|
||||||
|
engine.Use(func(c *gin.Context) {
|
||||||
|
c.Set(middleware.ContextPermissions, []string{tt.permission})
|
||||||
|
c.Set(middleware.ContextUserID, uint(7))
|
||||||
|
c.Next()
|
||||||
|
})
|
||||||
|
RegisterRoutes(engine.Group("/api/v1"), New(nil, luckygiftmodule.New(poolPermissionClient{}, 0, nil)))
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
request := httptest.NewRequest(
|
||||||
|
http.MethodPost,
|
||||||
|
"/api/v1/admin/ops-center/lucky-gifts/pools/"+tt.path+"?app_code=aslan&pool_id=lucky&strategy_version=dynamic_v3",
|
||||||
|
strings.NewReader(`{"adjustment_id":"route-adjustment","amount_coins":1,"reason":"permission test"}`),
|
||||||
|
)
|
||||||
|
request.Header.Set("Content-Type", "application/json")
|
||||||
|
engine.ServeHTTP(recorder, request)
|
||||||
|
if recorder.Code != tt.wantStatus {
|
||||||
|
t.Fatalf("status=%d want=%d body=%s", recorder.Code, tt.wantStatus, recorder.Body.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -129,7 +129,7 @@ var (
|
|||||||
"daily-task:view", "first-recharge-reward:view", "cumulative-recharge-reward:view",
|
"daily-task:view", "first-recharge-reward:view", "cumulative-recharge-reward:view",
|
||||||
"invite-activity-reward:view", "seven-day-checkin:view", "room-rocket:view", "room-turnover-reward:view",
|
"invite-activity-reward:view", "seven-day-checkin:view", "room-rocket:view", "room-turnover-reward:view",
|
||||||
"wheel:view", "weekly-star:view", "agency-opening:view", "user-leaderboard:view", "red-packet:view",
|
"wheel:view", "weekly-star:view", "agency-opening:view", "user-leaderboard:view", "red-packet:view",
|
||||||
"cp-config:view", "cp-weekly-rank:view", "vip-config:view", "achievement:view",
|
"cp-config:view", "cp-weekly-rank:view", "vip-config:view", "achievement:view", "lucky-gift:view",
|
||||||
}
|
}
|
||||||
activityManage = []string{
|
activityManage = []string{
|
||||||
"activity-template:create", "activity-template:update", "activity-template:publish", "activity-template:delete",
|
"activity-template:create", "activity-template:update", "activity-template:publish", "activity-template:delete",
|
||||||
@ -140,8 +140,10 @@ var (
|
|||||||
"wheel:update", "weekly-star:create", "weekly-star:update", "weekly-star:settle",
|
"wheel:update", "weekly-star:create", "weekly-star:update", "weekly-star:settle",
|
||||||
"agency-opening:create", "agency-opening:update", "red-packet:update",
|
"agency-opening:create", "agency-opening:update", "red-packet:update",
|
||||||
"cp-config:update", "cp-weekly-rank:update", "vip-config:update",
|
"cp-config:update", "cp-weekly-rank:update", "vip-config:update",
|
||||||
"achievement:create", "achievement:update",
|
"achievement:create", "achievement:update", "lucky-gift:update",
|
||||||
}
|
}
|
||||||
|
// 奖池人工增减属于资金高风险动作,只授予运营负责人;platform-admin 仍由全权限同步自动获得。
|
||||||
|
luckyGiftPoolManage = []string{"lucky-gift:pool-credit", "lucky-gift:pool-debit"}
|
||||||
|
|
||||||
gameRead = []string{
|
gameRead = []string{
|
||||||
"game-catalog:view", "self-game:view", "game-robot:view", "room-rps-config:view", "room-rps-order:view",
|
"game-catalog:view", "self-game:view", "game-robot:view", "room-rps-config:view", "room-rps-order:view",
|
||||||
@ -172,7 +174,7 @@ func defaultRolePermissionCodes(code string) []string {
|
|||||||
roomRead, roomManage,
|
roomRead, roomManage,
|
||||||
[]string{"app-config:view"}, appConfigManage,
|
[]string{"app-config:view"}, appConfigManage,
|
||||||
resourceRead, resourceManage,
|
resourceRead, resourceManage,
|
||||||
operationsRead, operationsLeadManage,
|
operationsRead, operationsLeadManage, luckyGiftPoolManage,
|
||||||
paymentRead, paymentBillExport, paymentBillRefresh, paymentOperationsManage,
|
paymentRead, paymentBillExport, paymentBillRefresh, paymentOperationsManage,
|
||||||
activityRead, activityManage,
|
activityRead, activityManage,
|
||||||
gameRead, gameManage,
|
gameRead, gameManage,
|
||||||
|
|||||||
@ -197,6 +197,8 @@ var defaultPermissions = []model.Permission{
|
|||||||
{Name: "七日签到更新", Code: "seven-day-checkin:update", Kind: "button"},
|
{Name: "七日签到更新", Code: "seven-day-checkin:update", Kind: "button"},
|
||||||
{Name: "幸运礼物查看", Code: "lucky-gift:view", Kind: "menu"},
|
{Name: "幸运礼物查看", Code: "lucky-gift:view", Kind: "menu"},
|
||||||
{Name: "幸运礼物更新", Code: "lucky-gift:update", Kind: "button"},
|
{Name: "幸运礼物更新", Code: "lucky-gift:update", Kind: "button"},
|
||||||
|
{Name: "幸运礼物奖池注资", Code: "lucky-gift:pool-credit", Kind: "button"},
|
||||||
|
{Name: "幸运礼物奖池扣资", Code: "lucky-gift:pool-debit", Kind: "button"},
|
||||||
{Name: "转盘抽奖查看", Code: "wheel:view", Kind: "menu"},
|
{Name: "转盘抽奖查看", Code: "wheel:view", Kind: "menu"},
|
||||||
{Name: "转盘抽奖更新", Code: "wheel:update", Kind: "button"},
|
{Name: "转盘抽奖更新", Code: "wheel:update", Kind: "button"},
|
||||||
{Name: "房间火箭查看", Code: "room-rocket:view", Kind: "menu"},
|
{Name: "房间火箭查看", Code: "room-rocket:view", Kind: "menu"},
|
||||||
|
|||||||
@ -153,6 +153,21 @@ func TestManagedDefaultRoleModuleBoundaries(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLuckyGiftPermissionsStayInManagedRoleSync(t *testing.T) {
|
||||||
|
assertRolePermissions(t, roleCodeOperationsLead,
|
||||||
|
[]string{"lucky-gift:view", "lucky-gift:update", "lucky-gift:pool-credit", "lucky-gift:pool-debit"}, nil,
|
||||||
|
)
|
||||||
|
assertRolePermissions(t, roleCodeOperationsSpecialist,
|
||||||
|
[]string{"lucky-gift:view"}, []string{"lucky-gift:update", "lucky-gift:pool-credit", "lucky-gift:pool-debit"},
|
||||||
|
)
|
||||||
|
assertRolePermissions(t, roleCodeProductLead,
|
||||||
|
[]string{"lucky-gift:view", "lucky-gift:update"}, []string{"lucky-gift:pool-credit", "lucky-gift:pool-debit"},
|
||||||
|
)
|
||||||
|
assertRolePermissions(t, roleCodeProductSpecialist,
|
||||||
|
[]string{"lucky-gift:view"}, []string{"lucky-gift:update", "lucky-gift:pool-credit", "lucky-gift:pool-debit"},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRolePermissionMigrationMatchesManagedMatrix(t *testing.T) {
|
func TestRolePermissionMigrationMatchesManagedMatrix(t *testing.T) {
|
||||||
content, err := os.ReadFile("../../migrations/093_role_permission_matrix.sql")
|
content, err := os.ReadFile("../../migrations/093_role_permission_matrix.sql")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -221,6 +236,22 @@ func TestActivityTemplatePermissionMigrationExtendsManagedRoles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLuckyGiftPoolAdjustmentPermissionMigrationRepairsManagedRoles(t *testing.T) {
|
||||||
|
content, err := os.ReadFile("../../migrations/098_lucky_gift_pool_adjust_permissions.sql")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read lucky gift pool permission migration: %v", err)
|
||||||
|
}
|
||||||
|
sqlText := string(content)
|
||||||
|
for _, token := range []string{
|
||||||
|
"'lucky-gift:view'", "'lucky-gift:update'", "'lucky-gift:pool-credit'", "'lucky-gift:pool-debit'",
|
||||||
|
"'platform-admin', 'ops-admin'", "'operations-specialist'", "'product-lead'", "'product-specialist'",
|
||||||
|
} {
|
||||||
|
if !strings.Contains(sqlText, token) {
|
||||||
|
t.Fatalf("lucky gift pool permission migration missing %s", token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func assertMigrationPermissionCodes(t *testing.T, roleCode string, sqlList string) {
|
func assertMigrationPermissionCodes(t *testing.T, roleCode string, sqlList string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
quotedCode := regexp.MustCompile(`'([a-z0-9:-]+)'`)
|
quotedCode := regexp.MustCompile(`'([a-z0-9:-]+)'`)
|
||||||
@ -236,6 +267,7 @@ func assertMigrationPermissionCodes(t *testing.T, roleCode string, sqlList strin
|
|||||||
"gift-record:view",
|
"gift-record:view",
|
||||||
"activity-template:view", "activity-template:create", "activity-template:update", "activity-template:publish",
|
"activity-template:view", "activity-template:create", "activity-template:update", "activity-template:publish",
|
||||||
"activity-template:delete", "activity-template:data", "activity-template:export", "activity-template:retry",
|
"activity-template:delete", "activity-template:data", "activity-template:export", "activity-template:retry",
|
||||||
|
"lucky-gift:view", "lucky-gift:update", "lucky-gift:pool-credit", "lucky-gift:pool-debit",
|
||||||
})
|
})
|
||||||
sort.Strings(actual)
|
sort.Strings(actual)
|
||||||
sort.Strings(expected)
|
sort.Strings(expected)
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
-- 奖池人工增减是资金高风险动作,使用独立按钮权限;迁移仅按唯一 code 连接小型 RBAC 元数据表,不扫描业务流水。
|
||||||
|
SET @now_ms = CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED);
|
||||||
|
|
||||||
|
INSERT INTO admin_permissions (name, code, kind, description, created_at_ms, updated_at_ms) VALUES
|
||||||
|
('幸运礼物奖池注资', 'lucky-gift:pool-credit', 'button', '允许对指定策略奖池执行带幂等审计的人工注资', @now_ms, @now_ms),
|
||||||
|
('幸运礼物奖池扣资', 'lucky-gift:pool-debit', 'button', '允许在安全水位之上执行带幂等审计的人工扣资', @now_ms, @now_ms)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
name = VALUES(name),
|
||||||
|
kind = VALUES(kind),
|
||||||
|
description = VALUES(description),
|
||||||
|
updated_at_ms = @now_ms;
|
||||||
|
|
||||||
|
-- 093 的岗位同步矩阵曾漏掉既有 lucky-gift:view/update;增量迁移按当前岗位边界补齐,避免下次同步再次丢失。
|
||||||
|
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
|
||||||
|
SELECT role.id, permission.id
|
||||||
|
FROM admin_roles role
|
||||||
|
JOIN admin_permissions permission
|
||||||
|
WHERE role.code IN ('platform-admin', 'ops-admin', 'operations-specialist', 'product-lead', 'product-specialist')
|
||||||
|
AND permission.code = 'lucky-gift:view';
|
||||||
|
|
||||||
|
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
|
||||||
|
SELECT role.id, permission.id
|
||||||
|
FROM admin_roles role
|
||||||
|
JOIN admin_permissions permission
|
||||||
|
WHERE role.code IN ('platform-admin', 'ops-admin', 'product-lead')
|
||||||
|
AND permission.code = 'lucky-gift:update';
|
||||||
|
|
||||||
|
-- 实际资金操作只授予超级管理员和运营负责人;查看/规则编辑权限不能隐式获得调账能力。
|
||||||
|
INSERT IGNORE INTO admin_role_permissions (role_id, permission_id)
|
||||||
|
SELECT role.id, permission.id
|
||||||
|
FROM admin_roles role
|
||||||
|
JOIN admin_permissions permission
|
||||||
|
WHERE role.code IN ('platform-admin', 'ops-admin')
|
||||||
|
AND permission.code IN ('lucky-gift:pool-credit', 'lucky-gift:pool-debit');
|
||||||
@ -159,6 +159,8 @@ CREATE TABLE IF NOT EXISTS lucky_pools (
|
|||||||
reserve_floor BIGINT NOT NULL DEFAULT 0 COMMENT '安全水位',
|
reserve_floor BIGINT NOT NULL DEFAULT 0 COMMENT '安全水位',
|
||||||
total_in BIGINT NOT NULL DEFAULT 0 COMMENT '累计入池',
|
total_in BIGINT NOT NULL DEFAULT 0 COMMENT '累计入池',
|
||||||
total_out BIGINT NOT NULL DEFAULT 0 COMMENT '累计基础返奖支出',
|
total_out BIGINT NOT NULL DEFAULT 0 COMMENT '累计基础返奖支出',
|
||||||
|
manual_credit_total BIGINT NOT NULL DEFAULT 0 COMMENT '累计人工注资金币,不计入业务 total_in',
|
||||||
|
manual_debit_total BIGINT NOT NULL DEFAULT 0 COMMENT '累计人工扣资金币,不计入业务 total_out',
|
||||||
profit_total BIGINT NOT NULL DEFAULT 0 COMMENT 'dynamic_v3 累计平台盈利拆账金币',
|
profit_total BIGINT NOT NULL DEFAULT 0 COMMENT 'dynamic_v3 累计平台盈利拆账金币',
|
||||||
anchor_income_total BIGINT NOT NULL DEFAULT 0 COMMENT 'dynamic_v3 累计主播收益拆账金币',
|
anchor_income_total BIGINT NOT NULL DEFAULT 0 COMMENT 'dynamic_v3 累计主播收益拆账金币',
|
||||||
initial_seed_total BIGINT NOT NULL DEFAULT 0 COMMENT 'dynamic_v3 累计初始注资金币,只在首次物化时增加',
|
initial_seed_total BIGINT NOT NULL DEFAULT 0 COMMENT 'dynamic_v3 累计初始注资金币,只在首次物化时增加',
|
||||||
@ -167,6 +169,28 @@ CREATE TABLE IF NOT EXISTS lucky_pools (
|
|||||||
PRIMARY KEY (app_code, scope_type, scope_id)
|
PRIMARY KEY (app_code, scope_type, scope_id)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物单一基础奖池';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物单一基础奖池';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS lucky_pool_adjustments (
|
||||||
|
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
|
||||||
|
adjustment_id VARCHAR(128) NOT NULL COMMENT '调用方资金幂等 ID',
|
||||||
|
pool_id VARCHAR(96) NOT NULL COMMENT '幸运礼物奖池 ID',
|
||||||
|
strategy_version VARCHAR(32) NOT NULL COMMENT 'fixed_v2/dynamic_v3',
|
||||||
|
scope_type VARCHAR(32) NOT NULL COMMENT '实际资金账本 scope_type',
|
||||||
|
direction VARCHAR(16) NOT NULL COMMENT 'in/out',
|
||||||
|
amount_coins BIGINT NOT NULL COMMENT '本次人工调整金币',
|
||||||
|
reason VARCHAR(512) NOT NULL COMMENT '管理员填写的调整原因',
|
||||||
|
operator_admin_id BIGINT NOT NULL COMMENT '操作管理员 ID',
|
||||||
|
request_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '链路 request_id,不作为幂等键',
|
||||||
|
insert_token VARCHAR(64) NOT NULL COMMENT '首次插入事务随机令牌,用于并发幂等判定',
|
||||||
|
balance_before BIGINT NOT NULL DEFAULT 0 COMMENT '首次应用前余额',
|
||||||
|
balance_after BIGINT NOT NULL DEFAULT 0 COMMENT '首次应用后余额',
|
||||||
|
pool_snapshot_json JSON NULL COMMENT '首次应用后的完整奖池快照,供幂等重放',
|
||||||
|
status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/applied',
|
||||||
|
created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms',
|
||||||
|
updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms',
|
||||||
|
PRIMARY KEY (app_code, adjustment_id),
|
||||||
|
KEY idx_lucky_pool_adjustments_pool (app_code, pool_id, strategy_version, created_at_ms)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物人工奖池调整事实';
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS lucky_risk_counters (
|
CREATE TABLE IF NOT EXISTS lucky_risk_counters (
|
||||||
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
|
app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
|
||||||
scope_type VARCHAR(32) NOT NULL COMMENT 'user/device/room/anchor',
|
scope_type VARCHAR(32) NOT NULL COMMENT 'user/device/room/anchor',
|
||||||
|
|||||||
@ -0,0 +1,61 @@
|
|||||||
|
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
USE hyapp_lucky_gift;
|
||||||
|
|
||||||
|
-- 每列独立探测,使迁移在“全新库、004 后旧库、曾执行一半的旧库”上都可重复运行。
|
||||||
|
-- 人工资金必须与正常送礼入池/返奖分账,不能污染 total_in/total_out 的 RTP 经营口径。
|
||||||
|
SET @ddl := IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'lucky_pools' AND COLUMN_NAME = 'manual_credit_total') = 0,
|
||||||
|
'ALTER TABLE lucky_pools ADD COLUMN manual_credit_total BIGINT NOT NULL DEFAULT 0 COMMENT ''累计人工注资金币,不计入业务 total_in'' AFTER total_out', 'SELECT 1');
|
||||||
|
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
SET @ddl := IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'lucky_pools' AND COLUMN_NAME = 'manual_debit_total') = 0,
|
||||||
|
'ALTER TABLE lucky_pools ADD COLUMN manual_debit_total BIGINT NOT NULL DEFAULT 0 COMMENT ''累计人工扣资金币,不计入业务 total_out'' AFTER manual_credit_total', 'SELECT 1');
|
||||||
|
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS lucky_pool_adjustments (
|
||||||
|
app_code VARCHAR(32) NOT NULL COMMENT '应用编码',
|
||||||
|
adjustment_id VARCHAR(128) NOT NULL COMMENT '调用方资金幂等 ID',
|
||||||
|
pool_id VARCHAR(96) NOT NULL COMMENT '幸运礼物奖池 ID',
|
||||||
|
strategy_version VARCHAR(32) NOT NULL COMMENT 'fixed_v2/dynamic_v3',
|
||||||
|
scope_type VARCHAR(32) NOT NULL COMMENT '实际资金账本 scope_type',
|
||||||
|
direction VARCHAR(16) NOT NULL COMMENT 'in/out',
|
||||||
|
amount_coins BIGINT NOT NULL COMMENT '本次人工调整金币',
|
||||||
|
reason VARCHAR(512) NOT NULL COMMENT '管理员填写的调整原因',
|
||||||
|
operator_admin_id BIGINT NOT NULL COMMENT '操作管理员 ID',
|
||||||
|
request_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '链路 request_id,不作为幂等键',
|
||||||
|
insert_token VARCHAR(64) NOT NULL COMMENT '首次插入事务随机令牌,用于并发幂等判定',
|
||||||
|
balance_before BIGINT NOT NULL DEFAULT 0 COMMENT '首次应用前余额',
|
||||||
|
balance_after BIGINT NOT NULL DEFAULT 0 COMMENT '首次应用后余额',
|
||||||
|
pool_snapshot_json JSON NULL COMMENT '首次应用后的完整奖池快照,供幂等重放',
|
||||||
|
status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/applied',
|
||||||
|
created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms',
|
||||||
|
updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms',
|
||||||
|
PRIMARY KEY (app_code, adjustment_id),
|
||||||
|
KEY idx_lucky_pool_adjustments_pool (app_code, pool_id, strategy_version, created_at_ms)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物人工奖池调整事实';
|
||||||
|
|
||||||
|
SET @ddl := IF((SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'lucky_pool_adjustments' AND COLUMN_NAME = 'insert_token') = 0,
|
||||||
|
'ALTER TABLE lucky_pool_adjustments ADD COLUMN insert_token VARCHAR(64) NOT NULL DEFAULT '''' COMMENT ''首次插入事务随机令牌,用于并发幂等判定'' AFTER request_id', 'SELECT 1');
|
||||||
|
PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
|
-- 只看每个 app+pool 当前最新不可变版本。若它已经切换到 dynamic_v3,就创建一口独立的零余额账本;
|
||||||
|
-- ON DUPLICATE 分支绝不更新余额、累计或时间,迁移重跑以及后续 V3 版本都不会重置真实资金。
|
||||||
|
INSERT INTO lucky_pools (
|
||||||
|
app_code, scope_type, scope_id, balance, reserve_floor, total_in, total_out,
|
||||||
|
manual_credit_total, manual_debit_total, profit_total, anchor_income_total, initial_seed_total,
|
||||||
|
created_at_ms, updated_at_ms
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
current_rule.app_code, 'pool_dynamic_v3', current_rule.pool_id, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, current_rule.created_at_ms, current_rule.created_at_ms
|
||||||
|
FROM lucky_gift_rule_versions current_rule
|
||||||
|
INNER JOIN (
|
||||||
|
SELECT app_code, pool_id, MAX(rule_version) AS rule_version
|
||||||
|
FROM lucky_gift_rule_versions
|
||||||
|
GROUP BY app_code, pool_id
|
||||||
|
) latest
|
||||||
|
ON latest.app_code = current_rule.app_code
|
||||||
|
AND latest.pool_id = current_rule.pool_id
|
||||||
|
AND latest.rule_version = current_rule.rule_version
|
||||||
|
WHERE current_rule.strategy_version = 'dynamic_v3'
|
||||||
|
ON DUPLICATE KEY UPDATE scope_id = lucky_pools.scope_id;
|
||||||
@ -23,6 +23,9 @@ const (
|
|||||||
StrategyFixedV2 = "fixed_v2"
|
StrategyFixedV2 = "fixed_v2"
|
||||||
StrategyDynamicV3 = "dynamic_v3"
|
StrategyDynamicV3 = "dynamic_v3"
|
||||||
|
|
||||||
|
PoolAdjustmentIn = "in"
|
||||||
|
PoolAdjustmentOut = "out"
|
||||||
|
|
||||||
EventTypeLuckyGiftDrawn = "LuckyGiftDrawn"
|
EventTypeLuckyGiftDrawn = "LuckyGiftDrawn"
|
||||||
EventTypeLuckyGiftRewardSettlement = "LuckyGiftRewardSettlement"
|
EventTypeLuckyGiftRewardSettlement = "LuckyGiftRewardSettlement"
|
||||||
EventTypeExternalLuckyGiftDrawn = "ExternalLuckyGiftDrawn"
|
EventTypeExternalLuckyGiftDrawn = "ExternalLuckyGiftDrawn"
|
||||||
@ -251,20 +254,58 @@ type DrawSummary struct {
|
|||||||
// PoolBalance 是后台看到的 app+pool_id 单池账本快照。
|
// PoolBalance 是后台看到的 app+pool_id 单池账本快照。
|
||||||
// materialized=false 表示该池还没有真实 lucky_pools 行,展示的是规则默认初始水位,不能当成已经入库的资金流水。
|
// materialized=false 表示该池还没有真实 lucky_pools 行,展示的是规则默认初始水位,不能当成已经入库的资金流水。
|
||||||
type PoolBalance struct {
|
type PoolBalance struct {
|
||||||
AppCode string
|
AppCode string
|
||||||
PoolID string
|
PoolID string
|
||||||
Balance int64
|
StrategyVersion string
|
||||||
ReserveFloor int64
|
Balance int64
|
||||||
AvailableBalance int64
|
ReserveFloor int64
|
||||||
TotalIn int64
|
AvailableBalance int64
|
||||||
TotalOut int64
|
TotalIn int64
|
||||||
Materialized bool
|
TotalOut int64
|
||||||
UpdatedAtMS int64
|
ManualCreditTotal int64
|
||||||
|
ManualDebitTotal int64
|
||||||
|
Materialized bool
|
||||||
|
UpdatedAtMS int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type PoolBalanceQuery struct {
|
type PoolBalanceQuery struct {
|
||||||
AppCode string
|
AppCode string
|
||||||
PoolID string
|
PoolID string
|
||||||
|
StrategyVersion string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PoolAdjustmentCommand 是人工注资/扣资的 owner 命令。AdjustmentID 由调用方生成并在 App 内唯一,
|
||||||
|
// 同一个 ID 的重试只能复用完全相同的资金参数,避免网络重试演变成重复记账。
|
||||||
|
type PoolAdjustmentCommand struct {
|
||||||
|
AppCode string
|
||||||
|
PoolID string
|
||||||
|
StrategyVersion string
|
||||||
|
AdjustmentID string
|
||||||
|
Direction string
|
||||||
|
AmountCoins int64
|
||||||
|
Reason string
|
||||||
|
OperatorAdminID int64
|
||||||
|
RequestID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PoolAdjustment struct {
|
||||||
|
AdjustmentID string
|
||||||
|
AppCode string
|
||||||
|
PoolID string
|
||||||
|
StrategyVersion string
|
||||||
|
Direction string
|
||||||
|
AmountCoins int64
|
||||||
|
Reason string
|
||||||
|
OperatorAdminID int64
|
||||||
|
BalanceBefore int64
|
||||||
|
BalanceAfter int64
|
||||||
|
CreatedAtMS int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type PoolAdjustmentResult struct {
|
||||||
|
Adjustment PoolAdjustment
|
||||||
|
Pool PoolBalance
|
||||||
|
IdempotentReplay bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminConfigQuery struct {
|
type AdminConfigQuery struct {
|
||||||
|
|||||||
@ -281,7 +281,7 @@ func DefaultLuckyGiftStrategyConfig() StrategyConfig {
|
|||||||
{ID: "1000x", MultiplierPPM: 1_000_000_000, BaseWeightPPM: 0, Jackpot: true, JackpotWeight: 1, Enabled: true},
|
{ID: "1000x", MultiplierPPM: 1_000_000_000, BaseWeightPPM: 0, Jackpot: true, JackpotWeight: 1, Enabled: true},
|
||||||
},
|
},
|
||||||
PublicPoolRatePPM: 980_000, ProfitPoolRatePPM: 10_000, AnchorReturnRatePPM: 10_000,
|
PublicPoolRatePPM: 980_000, ProfitPoolRatePPM: 10_000, AnchorReturnRatePPM: 10_000,
|
||||||
ColdStartPoolCoins: 1_000_000,
|
ColdStartPoolCoins: 0,
|
||||||
LowWaterThresholdCoins: 10_000_000, HighWaterThresholdCoins: 20_000_000,
|
LowWaterThresholdCoins: 10_000_000, HighWaterThresholdCoins: 20_000_000,
|
||||||
LowWaterFactorPPM: 700_000, HighWaterFactorPPM: 1_300_000,
|
LowWaterFactorPPM: 700_000, HighWaterFactorPPM: 1_300_000,
|
||||||
RechargeFactorPPM: 1_100_000, RechargeBoostStartMS: 0, RechargeBoostEndMS: 5 * 60 * 1000,
|
RechargeFactorPPM: 1_100_000, RechargeBoostStartMS: 0, RechargeBoostEndMS: 5 * 60 * 1000,
|
||||||
|
|||||||
@ -18,18 +18,19 @@ const (
|
|||||||
func DefaultRuleConfig(appCode, poolID string) domain.RuleConfig {
|
func DefaultRuleConfig(appCode, poolID string) domain.RuleConfig {
|
||||||
poolID = normalizePoolID(poolID)
|
poolID = normalizePoolID(poolID)
|
||||||
return domain.RuleConfig{
|
return domain.RuleConfig{
|
||||||
AppCode: appCode,
|
AppCode: appCode,
|
||||||
PoolID: poolID,
|
PoolID: poolID,
|
||||||
StrategyVersion: domain.StrategyDynamicV3,
|
StrategyVersion: domain.StrategyDynamicV3,
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
TargetRTPPPM: 980_000,
|
TargetRTPPPM: 980_000,
|
||||||
PoolRatePPM: 980_000,
|
PoolRatePPM: 980_000,
|
||||||
ProfitRatePPM: 10_000,
|
ProfitRatePPM: 10_000,
|
||||||
AnchorRatePPM: 10_000,
|
AnchorRatePPM: 10_000,
|
||||||
SettlementWindowWager: 1_000_000,
|
SettlementWindowWager: 1_000_000,
|
||||||
ControlBandPPM: 30_000,
|
ControlBandPPM: 30_000,
|
||||||
GiftPriceReference: 100,
|
GiftPriceReference: 100,
|
||||||
InitialPoolCoins: 1_000_000,
|
// dynamic_v3 的账本从 0 开始,任何启动资金都必须经过带管理员和原因的人工注资事实。
|
||||||
|
InitialPoolCoins: 0,
|
||||||
LossStreakGuarantee: 5,
|
LossStreakGuarantee: 5,
|
||||||
LowWatermarkCoins: 10_000_000,
|
LowWatermarkCoins: 10_000_000,
|
||||||
LowWaterNonzeroFactorPPM: 700_000,
|
LowWaterNonzeroFactorPPM: 700_000,
|
||||||
@ -177,8 +178,8 @@ func validateDynamicRuleConfig(config domain.RuleConfig, stages map[string]domai
|
|||||||
if config.ProfitRatePPM < 0 || config.AnchorRatePPM < 0 || config.PoolRatePPM+config.ProfitRatePPM+config.AnchorRatePPM != ppmScale {
|
if config.ProfitRatePPM < 0 || config.AnchorRatePPM < 0 || config.PoolRatePPM+config.ProfitRatePPM+config.AnchorRatePPM != ppmScale {
|
||||||
return xerr.New(xerr.InvalidArgument, "dynamic_v3 pool, profit and anchor rates must be non-negative and sum to 100%")
|
return xerr.New(xerr.InvalidArgument, "dynamic_v3 pool, profit and anchor rates must be non-negative and sum to 100%")
|
||||||
}
|
}
|
||||||
if config.InitialPoolCoins <= 0 {
|
if config.InitialPoolCoins != 0 {
|
||||||
return xerr.New(xerr.InvalidArgument, "dynamic_v3 initial pool coins must be positive")
|
return xerr.New(xerr.InvalidArgument, "dynamic_v3 initial pool coins must be zero; fund the pool through an audited credit adjustment")
|
||||||
}
|
}
|
||||||
if config.LossStreakGuarantee <= 0 {
|
if config.LossStreakGuarantee <= 0 {
|
||||||
return xerr.New(xerr.InvalidArgument, "dynamic_v3 loss streak guarantee must be positive")
|
return xerr.New(xerr.InvalidArgument, "dynamic_v3 loss streak guarantee must be positive")
|
||||||
|
|||||||
@ -15,7 +15,7 @@ func TestDefaultRuleConfigUsesSafeDynamicV3Draft(t *testing.T) {
|
|||||||
if config.TargetRTPPPM != 980_000 || config.PoolRatePPM != 980_000 || config.ProfitRatePPM != 10_000 || config.AnchorRatePPM != 10_000 {
|
if config.TargetRTPPPM != 980_000 || config.PoolRatePPM != 980_000 || config.ProfitRatePPM != 10_000 || config.AnchorRatePPM != 10_000 {
|
||||||
t.Fatalf("default RTP/fund split mismatch: %+v", config)
|
t.Fatalf("default RTP/fund split mismatch: %+v", config)
|
||||||
}
|
}
|
||||||
if config.ControlBandPPM != 30_000 || config.InitialPoolCoins != 1_000_000 || config.LossStreakGuarantee != 5 {
|
if config.ControlBandPPM != 30_000 || config.InitialPoolCoins != 0 || config.LossStreakGuarantee != 5 {
|
||||||
t.Fatalf("default control values mismatch: %+v", config)
|
t.Fatalf("default control values mismatch: %+v", config)
|
||||||
}
|
}
|
||||||
if config.LowWatermarkCoins != 10_000_000 || config.LowWaterNonzeroFactorPPM != 700_000 ||
|
if config.LowWatermarkCoins != 10_000_000 || config.LowWaterNonzeroFactorPPM != 700_000 ||
|
||||||
@ -53,6 +53,14 @@ func TestDefaultRuleConfigUsesSafeDynamicV3Draft(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateDynamicV3RejectsImplicitInitialFunding(t *testing.T) {
|
||||||
|
config := DefaultRuleConfig("lalu", "lucky")
|
||||||
|
config.InitialPoolCoins = 1_000_000
|
||||||
|
if err := validateRuleConfig(config); err == nil || !strings.Contains(err.Error(), "audited credit adjustment") {
|
||||||
|
t.Fatalf("implicit V3 seed error=%v, want audited credit guidance", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidateDynamicV3RequiresExplicitMonetaryRiskLimitsWhenEnabled(t *testing.T) {
|
func TestValidateDynamicV3RequiresExplicitMonetaryRiskLimitsWhenEnabled(t *testing.T) {
|
||||||
config := DefaultRuleConfig("lalu", "lucky")
|
config := DefaultRuleConfig("lalu", "lucky")
|
||||||
config.Enabled = true
|
config.Enabled = true
|
||||||
|
|||||||
@ -0,0 +1,71 @@
|
|||||||
|
package luckygift
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hyapp/pkg/appcode"
|
||||||
|
"hyapp/pkg/xerr"
|
||||||
|
domain "hyapp/services/lucky-gift-service/internal/domain/luckygift"
|
||||||
|
)
|
||||||
|
|
||||||
|
type poolAdjustmentRepositoryStub struct {
|
||||||
|
Repository
|
||||||
|
command domain.PoolAdjustmentCommand
|
||||||
|
nowMS int64
|
||||||
|
result domain.PoolAdjustmentResult
|
||||||
|
err error
|
||||||
|
calls int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *poolAdjustmentRepositoryStub) AdjustLuckyGiftPoolBalance(_ context.Context, command domain.PoolAdjustmentCommand, nowMS int64) (domain.PoolAdjustmentResult, error) {
|
||||||
|
s.calls++
|
||||||
|
s.command = command
|
||||||
|
s.nowMS = nowMS
|
||||||
|
return s.result, s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdjustPoolBalanceNormalizesAndForwardsOwnerCommand(t *testing.T) {
|
||||||
|
stub := &poolAdjustmentRepositoryStub{result: domain.PoolAdjustmentResult{Pool: domain.PoolBalance{Balance: 50}}}
|
||||||
|
service := New(stub)
|
||||||
|
fixedNow := time.Date(2026, 7, 15, 1, 2, 3, 0, time.UTC)
|
||||||
|
service.SetClock(func() time.Time { return fixedNow })
|
||||||
|
ctx := appcode.WithContext(context.Background(), " AsLaN ")
|
||||||
|
result, err := service.AdjustPoolBalance(ctx, domain.PoolAdjustmentCommand{
|
||||||
|
PoolID: " lucky ", StrategyVersion: " DYNAMIC_V3 ", AdjustmentID: " adjustment-1 ",
|
||||||
|
Direction: " IN ", AmountCoins: 50, Reason: " approved budget ", OperatorAdminID: 9,
|
||||||
|
RequestID: " request-1 ",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("adjust pool balance: %v", err)
|
||||||
|
}
|
||||||
|
if result.Pool.Balance != 50 || stub.calls != 1 {
|
||||||
|
t.Fatalf("result=%+v calls=%d", result, stub.calls)
|
||||||
|
}
|
||||||
|
if stub.command.AppCode != "aslan" || stub.command.PoolID != "lucky" || stub.command.StrategyVersion != domain.StrategyDynamicV3 ||
|
||||||
|
stub.command.Direction != domain.PoolAdjustmentIn || stub.command.Reason != "approved budget" || stub.nowMS != fixedNow.UnixMilli() {
|
||||||
|
t.Fatalf("normalized command=%+v now=%d", stub.command, stub.nowMS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdjustPoolBalanceRejectsInvalidMoneyCommandBeforeRepository(t *testing.T) {
|
||||||
|
stub := &poolAdjustmentRepositoryStub{}
|
||||||
|
service := New(stub)
|
||||||
|
tests := []domain.PoolAdjustmentCommand{
|
||||||
|
{StrategyVersion: domain.StrategyDynamicV3, AdjustmentID: "a", Direction: "in", AmountCoins: 1, Reason: "r", OperatorAdminID: 1},
|
||||||
|
{PoolID: "lucky", StrategyVersion: "v4", AdjustmentID: "a", Direction: "in", AmountCoins: 1, Reason: "r", OperatorAdminID: 1},
|
||||||
|
{PoolID: "lucky", StrategyVersion: domain.StrategyDynamicV3, Direction: "in", AmountCoins: 1, Reason: "r", OperatorAdminID: 1},
|
||||||
|
{PoolID: "lucky", StrategyVersion: domain.StrategyDynamicV3, AdjustmentID: "a", Direction: "sideways", AmountCoins: 1, Reason: "r", OperatorAdminID: 1},
|
||||||
|
{PoolID: "lucky", StrategyVersion: domain.StrategyDynamicV3, AdjustmentID: "a", Direction: "out", AmountCoins: 0, Reason: "r", OperatorAdminID: 1},
|
||||||
|
{PoolID: "lucky", StrategyVersion: domain.StrategyDynamicV3, AdjustmentID: "a", Direction: "out", AmountCoins: 1, OperatorAdminID: 1},
|
||||||
|
}
|
||||||
|
for index, command := range tests {
|
||||||
|
if _, err := service.AdjustPoolBalance(context.Background(), command); !xerr.IsCode(err, xerr.InvalidArgument) {
|
||||||
|
t.Fatalf("case %d error=%v, want invalid argument", index, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stub.calls != 0 {
|
||||||
|
t.Fatalf("repository calls=%d, want 0 for invalid commands", stub.calls)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"hyapp/pkg/appcode"
|
"hyapp/pkg/appcode"
|
||||||
"hyapp/pkg/giftlimits"
|
"hyapp/pkg/giftlimits"
|
||||||
@ -37,6 +38,7 @@ type Repository interface {
|
|||||||
ListLuckyGiftDraws(ctx context.Context, query domain.DrawQuery) ([]domain.DrawResult, int64, error)
|
ListLuckyGiftDraws(ctx context.Context, query domain.DrawQuery) ([]domain.DrawResult, int64, error)
|
||||||
GetLuckyGiftDrawSummary(ctx context.Context, query domain.DrawQuery) (domain.DrawSummary, error)
|
GetLuckyGiftDrawSummary(ctx context.Context, query domain.DrawQuery) (domain.DrawSummary, error)
|
||||||
ListLuckyGiftPoolBalances(ctx context.Context, query domain.PoolBalanceQuery) ([]domain.PoolBalance, error)
|
ListLuckyGiftPoolBalances(ctx context.Context, query domain.PoolBalanceQuery) ([]domain.PoolBalance, error)
|
||||||
|
AdjustLuckyGiftPoolBalance(ctx context.Context, command domain.PoolAdjustmentCommand, nowMS int64) (domain.PoolAdjustmentResult, error)
|
||||||
GetLuckyGiftDrawRewardState(ctx context.Context, appCode string, drawIDs []string) (domain.DrawRewardState, error)
|
GetLuckyGiftDrawRewardState(ctx context.Context, appCode string, drawIDs []string) (domain.DrawRewardState, error)
|
||||||
ClaimPendingLuckyGiftOutbox(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int) ([]domain.DrawOutbox, error)
|
ClaimPendingLuckyGiftOutbox(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int) ([]domain.DrawOutbox, error)
|
||||||
DeleteDeliveredLuckyGiftOutboxBefore(ctx context.Context, cutoffMS int64, limit int) (int64, error)
|
DeleteDeliveredLuckyGiftOutboxBefore(ctx context.Context, cutoffMS int64, limit int) (int64, error)
|
||||||
@ -780,9 +782,57 @@ func (s *Service) ListPoolBalances(ctx context.Context, query domain.PoolBalance
|
|||||||
if strings.TrimSpace(query.PoolID) != "" {
|
if strings.TrimSpace(query.PoolID) != "" {
|
||||||
query.PoolID = normalizePoolID(query.PoolID)
|
query.PoolID = normalizePoolID(query.PoolID)
|
||||||
}
|
}
|
||||||
|
query.StrategyVersion = strings.ToLower(strings.TrimSpace(query.StrategyVersion))
|
||||||
|
if query.StrategyVersion != "" && query.StrategyVersion != domain.StrategyFixedV2 && query.StrategyVersion != domain.StrategyDynamicV3 {
|
||||||
|
return nil, xerr.New(xerr.InvalidArgument, "unsupported lucky gift strategy version")
|
||||||
|
}
|
||||||
return s.repository.ListLuckyGiftPoolBalances(ctx, query)
|
return s.repository.ListLuckyGiftPoolBalances(ctx, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdjustPoolBalance 校验管理员资金命令并交给 owner 仓储执行。request_id 只记录链路,
|
||||||
|
// adjustment_id 才是可跨超时重试的业务幂等键。
|
||||||
|
func (s *Service) AdjustPoolBalance(ctx context.Context, command domain.PoolAdjustmentCommand) (domain.PoolAdjustmentResult, error) {
|
||||||
|
if err := s.requireRepository(); err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, err
|
||||||
|
}
|
||||||
|
command.AppCode = appcode.FromContext(ctx)
|
||||||
|
rawPoolID := strings.TrimSpace(command.PoolID)
|
||||||
|
if rawPoolID == "" {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.InvalidArgument, "lucky gift pool adjustment pool_id is required")
|
||||||
|
}
|
||||||
|
command.PoolID = normalizePoolID(rawPoolID)
|
||||||
|
command.StrategyVersion = strings.ToLower(strings.TrimSpace(command.StrategyVersion))
|
||||||
|
command.AdjustmentID = strings.TrimSpace(command.AdjustmentID)
|
||||||
|
command.Direction = strings.ToLower(strings.TrimSpace(command.Direction))
|
||||||
|
command.Reason = strings.TrimSpace(command.Reason)
|
||||||
|
command.RequestID = strings.TrimSpace(command.RequestID)
|
||||||
|
if utf8.RuneCountInString(command.AppCode) > 32 || utf8.RuneCountInString(command.PoolID) > 96 {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.InvalidArgument, "lucky gift pool adjustment app_code or pool_id is too long")
|
||||||
|
}
|
||||||
|
if command.StrategyVersion != domain.StrategyFixedV2 && command.StrategyVersion != domain.StrategyDynamicV3 {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.InvalidArgument, "lucky gift pool strategy_version must be fixed_v2 or dynamic_v3")
|
||||||
|
}
|
||||||
|
if command.AdjustmentID == "" || utf8.RuneCountInString(command.AdjustmentID) > 128 {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.InvalidArgument, "lucky gift pool adjustment_id is required and must not exceed 128 characters")
|
||||||
|
}
|
||||||
|
if command.Direction != domain.PoolAdjustmentIn && command.Direction != domain.PoolAdjustmentOut {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.InvalidArgument, "lucky gift pool adjustment direction must be in or out")
|
||||||
|
}
|
||||||
|
if command.AmountCoins <= 0 {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.InvalidArgument, "lucky gift pool adjustment amount_coins must be positive")
|
||||||
|
}
|
||||||
|
if command.Reason == "" || utf8.RuneCountInString(command.Reason) > 512 {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.InvalidArgument, "lucky gift pool adjustment reason is required and must not exceed 512 characters")
|
||||||
|
}
|
||||||
|
if command.OperatorAdminID <= 0 {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.InvalidArgument, "lucky gift pool adjustment operator_admin_id must be positive")
|
||||||
|
}
|
||||||
|
if utf8.RuneCountInString(command.RequestID) > 128 {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.InvalidArgument, "lucky gift pool adjustment request_id must not exceed 128 characters")
|
||||||
|
}
|
||||||
|
return s.repository.AdjustLuckyGiftPoolBalance(ctx, command, s.now().UTC().UnixMilli())
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) requireRepository() error {
|
func (s *Service) requireRepository() error {
|
||||||
if s == nil || s.repository == nil {
|
if s == nil || s.repository == nil {
|
||||||
return xerr.New(xerr.Unavailable, "lucky gift repository is not configured")
|
return xerr.New(xerr.Unavailable, "lucky gift repository is not configured")
|
||||||
|
|||||||
@ -83,7 +83,8 @@ func (r *Repository) executeDynamicLuckyGiftDrawBatch(ctx context.Context, tx *s
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
basePool, err := r.getOrCreateLuckyPool(ctx, tx, appCode, luckyBasePoolScopeType, luckyBasePoolScopeID(config), config.InitialPoolCoins, 0, nowMS)
|
// dynamic_v3 永远从独立的零余额账本启动;运营启动资金必须走人工 credit,不能由规则版本发布隐式注资。
|
||||||
|
basePool, err := r.getOrCreateLuckyPool(ctx, tx, appCode, luckyDynamicPoolScopeType, luckyBasePoolScopeID(config), 0, 0, nowMS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,6 +32,14 @@ func TestDynamicLuckyGiftMySQLBatchAndExternalPaths(t *testing.T) {
|
|||||||
if _, err := repo.PublishLuckyGiftRuleConfig(ctx, internalRule, now-1_000); err != nil {
|
if _, err := repo.PublishLuckyGiftRuleConfig(ctx, internalRule, now-1_000); err != nil {
|
||||||
t.Fatalf("publish internal dynamic rule: %v", err)
|
t.Fatalf("publish internal dynamic rule: %v", err)
|
||||||
}
|
}
|
||||||
|
// 同名 fixed_v2 旧账本模拟策略切换前历史水位;dynamic draw 必须只碰 pool_dynamic_v3。
|
||||||
|
if _, err := schema.DB.Exec(`
|
||||||
|
INSERT INTO lucky_pools (
|
||||||
|
app_code, scope_type, scope_id, balance, reserve_floor, total_in, total_out,
|
||||||
|
profit_total, anchor_income_total, initial_seed_total, created_at_ms, updated_at_ms
|
||||||
|
) VALUES ('lalu', 'pool', ?, 4321, 321, 4000, 100, 0, 0, 4321, ?, ?)`, internalRule.PoolID, now-2_000, now-2_000); err != nil {
|
||||||
|
t.Fatalf("seed fixed strategy ledger: %v", err)
|
||||||
|
}
|
||||||
checked, err := repo.CheckLuckyGift(ctx, domain.CheckCommand{PoolID: internalRule.PoolID, GiftID: "super-lucky", UserID: 1001, RoomID: "room-1"})
|
checked, err := repo.CheckLuckyGift(ctx, domain.CheckCommand{PoolID: internalRule.PoolID, GiftID: "super-lucky", UserID: 1001, RoomID: "room-1"})
|
||||||
if err != nil || checked.StrategyVersion != domain.StrategyDynamicV3 {
|
if err != nil || checked.StrategyVersion != domain.StrategyDynamicV3 {
|
||||||
t.Fatalf("check did not expose immutable strategy version: result=%+v err=%v", checked, err)
|
t.Fatalf("check did not expose immutable strategy version: result=%+v err=%v", checked, err)
|
||||||
@ -103,6 +111,13 @@ func TestDynamicLuckyGiftMySQLBatchAndExternalPaths(t *testing.T) {
|
|||||||
t.Fatalf("internal command lock count=%d err=%v", internalLockCount, err)
|
t.Fatalf("internal command lock count=%d err=%v", internalLockCount, err)
|
||||||
}
|
}
|
||||||
assertDynamicMySQLBatchFacts(t, schema.DB, "lalu", internalRule.PoolID, command.UserID, command.CommandID, 99, 100, 98, 1, 1)
|
assertDynamicMySQLBatchFacts(t, schema.DB, "lalu", internalRule.PoolID, command.UserID, command.CommandID, 99, 100, 98, 1, 1)
|
||||||
|
var fixedBalance, fixedTotalIn, fixedTotalOut int64
|
||||||
|
if err := schema.DB.QueryRow(`
|
||||||
|
SELECT balance, total_in, total_out FROM lucky_pools
|
||||||
|
WHERE app_code='lalu' AND scope_type='pool' AND scope_id=?`, internalRule.PoolID,
|
||||||
|
).Scan(&fixedBalance, &fixedTotalIn, &fixedTotalOut); err != nil || fixedBalance != 4321 || fixedTotalIn != 4000 || fixedTotalOut != 100 {
|
||||||
|
t.Fatalf("dynamic draw changed fixed ledger balance/in/out=%d/%d/%d err=%v", fixedBalance, fixedTotalIn, fixedTotalOut, err)
|
||||||
|
}
|
||||||
assertDynamicRTPWindowRollover(t, schema.DB, internalRule.PoolID)
|
assertDynamicRTPWindowRollover(t, schema.DB, internalRule.PoolID)
|
||||||
assertExactRolling72HourBoundary(t, repo, schema.DB, ctx, internalRule.PoolID, now)
|
assertExactRolling72HourBoundary(t, repo, schema.DB, ctx, internalRule.PoolID, now)
|
||||||
|
|
||||||
@ -301,8 +316,10 @@ func assertDynamicTokenSurvivesUTCDayBoundary(t *testing.T, repo *Repository, db
|
|||||||
t.Fatalf("earn milestone token: %v", err)
|
t.Fatalf("earn milestone token: %v", err)
|
||||||
}
|
}
|
||||||
assertPendingTokenCount(t, db, rule.PoolID, userID, 1)
|
assertPendingTokenCount(t, db, rule.PoolID, userID, 1)
|
||||||
if _, err := db.Exec(`UPDATE lucky_pools SET balance=0 WHERE app_code='lalu' AND scope_type='pool' AND scope_id=?`, rule.PoolID); err != nil {
|
if result, err := db.Exec(`UPDATE lucky_pools SET balance=0 WHERE app_code='lalu' AND scope_type=? AND scope_id=?`, luckyDynamicPoolScopeType, rule.PoolID); err != nil {
|
||||||
t.Fatalf("drain token test pool: %v", err)
|
t.Fatalf("drain token test pool: %v", err)
|
||||||
|
} else if rows, _ := result.RowsAffected(); rows != 1 {
|
||||||
|
t.Fatalf("drain token test pool affected=%d, want 1", rows)
|
||||||
}
|
}
|
||||||
dayTwoMS := time.UnixMilli(dayOneMS).UTC().Add(24 * time.Hour).UnixMilli()
|
dayTwoMS := time.UnixMilli(dayOneMS).UTC().Add(24 * time.Hour).UnixMilli()
|
||||||
retain := earn
|
retain := earn
|
||||||
@ -314,8 +331,10 @@ func assertDynamicTokenSurvivesUTCDayBoundary(t *testing.T, repo *Repository, db
|
|||||||
t.Fatalf("retain token across UTC day: %v", err)
|
t.Fatalf("retain token across UTC day: %v", err)
|
||||||
}
|
}
|
||||||
assertPendingTokenCount(t, db, rule.PoolID, userID, 1)
|
assertPendingTokenCount(t, db, rule.PoolID, userID, 1)
|
||||||
if _, err := db.Exec(`UPDATE lucky_pools SET balance=1000000 WHERE app_code='lalu' AND scope_type='pool' AND scope_id=?`, rule.PoolID); err != nil {
|
if result, err := db.Exec(`UPDATE lucky_pools SET balance=1000000 WHERE app_code='lalu' AND scope_type=? AND scope_id=?`, luckyDynamicPoolScopeType, rule.PoolID); err != nil {
|
||||||
t.Fatalf("refill token test pool: %v", err)
|
t.Fatalf("refill token test pool: %v", err)
|
||||||
|
} else if rows, _ := result.RowsAffected(); rows != 1 {
|
||||||
|
t.Fatalf("refill token test pool affected=%d, want 1", rows)
|
||||||
}
|
}
|
||||||
consume := retain
|
consume := retain
|
||||||
consume.CommandID = "token-consume-next-day"
|
consume.CommandID = "token-consume-next-day"
|
||||||
@ -466,7 +485,7 @@ func dynamicMySQLTestRule(poolID string) domain.RuleConfig {
|
|||||||
PoolID: poolID, StrategyVersion: domain.StrategyDynamicV3, Enabled: true,
|
PoolID: poolID, StrategyVersion: domain.StrategyDynamicV3, Enabled: true,
|
||||||
TargetRTPPPM: 980_000, PoolRatePPM: 980_000, ProfitRatePPM: 10_000, AnchorRatePPM: 10_000,
|
TargetRTPPPM: 980_000, PoolRatePPM: 980_000, ProfitRatePPM: 10_000, AnchorRatePPM: 10_000,
|
||||||
SettlementWindowWager: 1_000_000, ControlBandPPM: 30_000, GiftPriceReference: 1,
|
SettlementWindowWager: 1_000_000, ControlBandPPM: 30_000, GiftPriceReference: 1,
|
||||||
InitialPoolCoins: 1_000_000, LossStreakGuarantee: 5,
|
InitialPoolCoins: 0, LossStreakGuarantee: 5,
|
||||||
LowWatermarkCoins: 10_000_000, LowWaterNonzeroFactorPPM: 700_000,
|
LowWatermarkCoins: 10_000_000, LowWaterNonzeroFactorPPM: 700_000,
|
||||||
HighWatermarkCoins: 20_000_000, HighWaterNonzeroFactorPPM: 1_300_000,
|
HighWatermarkCoins: 20_000_000, HighWaterNonzeroFactorPPM: 1_300_000,
|
||||||
RechargeBoostWindowMS: 300_000, RechargeBoostFactorPPM: 1_100_000,
|
RechargeBoostWindowMS: 300_000, RechargeBoostFactorPPM: 1_100_000,
|
||||||
|
|||||||
@ -272,7 +272,7 @@ func (r *Repository) updateLuckyDynamicPoolNet(ctx context.Context, tx *sql.Tx,
|
|||||||
profit_total = profit_total + ?, anchor_income_total = anchor_income_total + ?, updated_at_ms = ?
|
profit_total = profit_total + ?, anchor_income_total = anchor_income_total + ?, updated_at_ms = ?
|
||||||
WHERE app_code = ? AND scope_type = ? AND scope_id = ?`,
|
WHERE app_code = ? AND scope_type = ? AND scope_id = ?`,
|
||||||
publicIn, payout, publicIn, payout, profitIn, anchorIncome, nowMS,
|
publicIn, payout, publicIn, payout, profitIn, anchorIncome, nowMS,
|
||||||
appCode, luckyBasePoolScopeType, scopeID,
|
appCode, luckyDynamicPoolScopeType, scopeID,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,9 @@ import (
|
|||||||
const (
|
const (
|
||||||
luckyPPMScale int64 = 1_000_000
|
luckyPPMScale int64 = 1_000_000
|
||||||
luckyBasePoolScopeType = "pool"
|
luckyBasePoolScopeType = "pool"
|
||||||
|
// dynamic_v3 与 fixed_v2 的资金算法和启动规则不同,必须使用独立主键空间;
|
||||||
|
// 任何兼容读取都不能把两种策略重新折叠到同一口 pool 账本。
|
||||||
|
luckyDynamicPoolScopeType = "pool_dynamic_v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type luckyRTPWindow struct {
|
type luckyRTPWindow struct {
|
||||||
@ -47,12 +50,14 @@ func (w luckyRTPWindow) withCurrentWager(spent int64) luckyRTPWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type luckyPool struct {
|
type luckyPool struct {
|
||||||
ScopeType string
|
ScopeType string
|
||||||
ScopeID string
|
ScopeID string
|
||||||
Balance int64
|
Balance int64
|
||||||
ReserveFloor int64
|
ReserveFloor int64
|
||||||
TotalIn int64
|
TotalIn int64
|
||||||
TotalOut int64
|
TotalOut int64
|
||||||
|
ManualCreditTotal int64
|
||||||
|
ManualDebitTotal int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p luckyPool) capacity() int64 {
|
func (p luckyPool) capacity() int64 {
|
||||||
@ -1938,15 +1943,29 @@ func (r *Repository) ListLuckyGiftPoolBalances(ctx context.Context, query domain
|
|||||||
if r == nil || r.db == nil {
|
if r == nil || r.db == nil {
|
||||||
return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
|
return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
|
||||||
}
|
}
|
||||||
appCode := appcode.Normalize(query.AppCode)
|
// 空 app_code 是后台“全部应用”筛选,不能经过 Normalize 变成默认 lalu;只有明确值才参与租户条件。
|
||||||
|
appCode := strings.ToLower(strings.TrimSpace(query.AppCode))
|
||||||
poolID := strings.TrimSpace(query.PoolID)
|
poolID := strings.TrimSpace(query.PoolID)
|
||||||
if poolID != "" {
|
if poolID != "" {
|
||||||
poolID = luckyPoolID(poolID)
|
poolID = luckyPoolID(poolID)
|
||||||
}
|
}
|
||||||
|
strategyVersion := strings.ToLower(strings.TrimSpace(query.StrategyVersion))
|
||||||
|
|
||||||
// 先读真实单池账本。这个查询不会 FOR UPDATE,也不会调用 getOrCreate,数据汇总刷新不能制造奖池行或改变余额。
|
// 先读两套真实账本。这个查询不会 FOR UPDATE,也不会调用 getOrCreate,后台列表不能制造奖池行或改变余额。
|
||||||
where := []string{"scope_type = ?"}
|
// strategy_version 由内部 scope_type 显式映射;固定与动态绝不能再按 app+pool 折叠覆盖。
|
||||||
args := []any{luckyBasePoolScopeType}
|
where := make([]string, 0, 3)
|
||||||
|
args := make([]any, 0, 4)
|
||||||
|
if strategyVersion != "" {
|
||||||
|
scopeType, err := luckyPoolScopeForStrategy(strategyVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
where = append(where, "scope_type = ?")
|
||||||
|
args = append(args, scopeType)
|
||||||
|
} else {
|
||||||
|
where = append(where, "scope_type IN (?, ?)")
|
||||||
|
args = append(args, luckyBasePoolScopeType, luckyDynamicPoolScopeType)
|
||||||
|
}
|
||||||
if appCode != "" {
|
if appCode != "" {
|
||||||
where = append(where, "app_code = ?")
|
where = append(where, "app_code = ?")
|
||||||
args = append(args, appCode)
|
args = append(args, appCode)
|
||||||
@ -1956,10 +1975,11 @@ func (r *Repository) ListLuckyGiftPoolBalances(ctx context.Context, query domain
|
|||||||
args = append(args, poolID)
|
args = append(args, poolID)
|
||||||
}
|
}
|
||||||
rows, err := r.db.QueryContext(ctx, `
|
rows, err := r.db.QueryContext(ctx, `
|
||||||
SELECT app_code, scope_id, balance, reserve_floor, total_in, total_out, updated_at_ms
|
SELECT app_code, scope_type, scope_id, balance, reserve_floor, total_in, total_out,
|
||||||
|
manual_credit_total, manual_debit_total, updated_at_ms
|
||||||
FROM lucky_pools
|
FROM lucky_pools
|
||||||
WHERE `+strings.Join(where, " AND ")+`
|
WHERE `+strings.Join(where, " AND ")+`
|
||||||
ORDER BY app_code ASC, scope_id ASC`, args...)
|
ORDER BY app_code ASC, scope_id ASC, scope_type ASC`, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1968,12 +1988,21 @@ func (r *Repository) ListLuckyGiftPoolBalances(ctx context.Context, query domain
|
|||||||
byKey := make(map[string]domain.PoolBalance)
|
byKey := make(map[string]domain.PoolBalance)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var item domain.PoolBalance
|
var item domain.PoolBalance
|
||||||
if err := rows.Scan(&item.AppCode, &item.PoolID, &item.Balance, &item.ReserveFloor, &item.TotalIn, &item.TotalOut, &item.UpdatedAtMS); err != nil {
|
var scopeType string
|
||||||
|
if err := rows.Scan(
|
||||||
|
&item.AppCode, &scopeType, &item.PoolID, &item.Balance, &item.ReserveFloor, &item.TotalIn, &item.TotalOut,
|
||||||
|
&item.ManualCreditTotal, &item.ManualDebitTotal, &item.UpdatedAtMS,
|
||||||
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
mappedStrategy, ok := luckyStrategyForPoolScope(scopeType)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
item.StrategyVersion = mappedStrategy
|
||||||
item.Materialized = true
|
item.Materialized = true
|
||||||
item.AvailableBalance = luckyAvailableBalance(item.Balance, item.ReserveFloor)
|
item.AvailableBalance = luckyAvailableBalance(item.Balance, item.ReserveFloor)
|
||||||
byKey[luckyPoolBalanceKey(item.AppCode, item.PoolID)] = item
|
byKey[luckyPoolBalanceKey(item.AppCode, item.PoolID, item.StrategyVersion)] = item
|
||||||
}
|
}
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -1988,7 +2017,14 @@ func (r *Repository) ListLuckyGiftPoolBalances(ctx context.Context, query domain
|
|||||||
if poolID != "" && luckyPoolID(config.PoolID) != poolID {
|
if poolID != "" && luckyPoolID(config.PoolID) != poolID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
key := luckyPoolBalanceKey(config.AppCode, config.PoolID)
|
configStrategy := strings.ToLower(strings.TrimSpace(config.StrategyVersion))
|
||||||
|
if configStrategy == "" {
|
||||||
|
configStrategy = domain.StrategyFixedV2
|
||||||
|
}
|
||||||
|
if strategyVersion != "" && configStrategy != strategyVersion {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := luckyPoolBalanceKey(config.AppCode, config.PoolID, configStrategy)
|
||||||
if _, exists := byKey[key]; exists {
|
if _, exists := byKey[key]; exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -1996,12 +2032,20 @@ func (r *Repository) ListLuckyGiftPoolBalances(ctx context.Context, query domain
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
initialBalance := runtimeConfig.InitialBasePool
|
||||||
|
reserveFloor := runtimeConfig.BasePoolReserve
|
||||||
|
if configStrategy == domain.StrategyDynamicV3 {
|
||||||
|
// V3 规则即使来自迁移前旧数据,也只能展示独立零余额冷启动;配置里的历史 initial_pool_coins 不再形成资金。
|
||||||
|
initialBalance = 0
|
||||||
|
reserveFloor = 0
|
||||||
|
}
|
||||||
byKey[key] = domain.PoolBalance{
|
byKey[key] = domain.PoolBalance{
|
||||||
AppCode: appcode.Normalize(config.AppCode),
|
AppCode: appcode.Normalize(config.AppCode),
|
||||||
PoolID: runtimeConfig.PoolID,
|
PoolID: runtimeConfig.PoolID,
|
||||||
Balance: runtimeConfig.InitialBasePool,
|
StrategyVersion: configStrategy,
|
||||||
ReserveFloor: runtimeConfig.BasePoolReserve,
|
Balance: initialBalance,
|
||||||
AvailableBalance: luckyAvailableBalance(runtimeConfig.InitialBasePool, runtimeConfig.BasePoolReserve),
|
ReserveFloor: reserveFloor,
|
||||||
|
AvailableBalance: luckyAvailableBalance(initialBalance, reserveFloor),
|
||||||
Materialized: false,
|
Materialized: false,
|
||||||
UpdatedAtMS: config.CreatedAtMS,
|
UpdatedAtMS: config.CreatedAtMS,
|
||||||
}
|
}
|
||||||
@ -2009,14 +2053,22 @@ func (r *Repository) ListLuckyGiftPoolBalances(ctx context.Context, query domain
|
|||||||
|
|
||||||
// 没有任何配置的新应用仍然需要在 ops-center 看到 default 池。这里用当前默认规则推导初始水位,但不写库。
|
// 没有任何配置的新应用仍然需要在 ops-center 看到 default 池。这里用当前默认规则推导初始水位,但不写库。
|
||||||
if appCode != "" && (poolID == "" || poolID == domain.DefaultPoolID) {
|
if appCode != "" && (poolID == "" || poolID == domain.DefaultPoolID) {
|
||||||
key := luckyPoolBalanceKey(appCode, domain.DefaultPoolID)
|
defaultStrategy := strategyVersion
|
||||||
|
if defaultStrategy == "" {
|
||||||
|
defaultStrategy = domain.StrategyDynamicV3
|
||||||
|
}
|
||||||
|
key := luckyPoolBalanceKey(appCode, domain.DefaultPoolID, defaultStrategy)
|
||||||
if _, exists := byKey[key]; !exists {
|
if _, exists := byKey[key]; !exists {
|
||||||
const defaultReferencePrice = int64(100)
|
var defaultBalance, defaultReserve int64
|
||||||
defaultBalance := defaultReferencePrice * (950 + 500 + 2_375)
|
if defaultStrategy == domain.StrategyFixedV2 {
|
||||||
defaultReserve := defaultReferencePrice * (100 + 10 + 100)
|
const defaultReferencePrice = int64(100)
|
||||||
|
defaultBalance = defaultReferencePrice * (950 + 500 + 2_375)
|
||||||
|
defaultReserve = defaultReferencePrice * (100 + 10 + 100)
|
||||||
|
}
|
||||||
byKey[key] = domain.PoolBalance{
|
byKey[key] = domain.PoolBalance{
|
||||||
AppCode: appCode,
|
AppCode: appCode,
|
||||||
PoolID: domain.DefaultPoolID,
|
PoolID: domain.DefaultPoolID,
|
||||||
|
StrategyVersion: defaultStrategy,
|
||||||
Balance: defaultBalance,
|
Balance: defaultBalance,
|
||||||
ReserveFloor: defaultReserve,
|
ReserveFloor: defaultReserve,
|
||||||
AvailableBalance: luckyAvailableBalance(defaultBalance, defaultReserve),
|
AvailableBalance: luckyAvailableBalance(defaultBalance, defaultReserve),
|
||||||
@ -2033,7 +2085,10 @@ func (r *Repository) ListLuckyGiftPoolBalances(ctx context.Context, query domain
|
|||||||
if items[i].AppCode != items[j].AppCode {
|
if items[i].AppCode != items[j].AppCode {
|
||||||
return items[i].AppCode < items[j].AppCode
|
return items[i].AppCode < items[j].AppCode
|
||||||
}
|
}
|
||||||
return items[i].PoolID < items[j].PoolID
|
if items[i].PoolID != items[j].PoolID {
|
||||||
|
return items[i].PoolID < items[j].PoolID
|
||||||
|
}
|
||||||
|
return items[i].StrategyVersion < items[j].StrategyVersion
|
||||||
})
|
})
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
@ -2077,8 +2132,8 @@ func luckyDrawSummaryCanUseStats(query domain.DrawQuery) bool {
|
|||||||
return query.UserID <= 0 && strings.TrimSpace(query.RoomID) == "" && strings.TrimSpace(query.Status) == ""
|
return query.UserID <= 0 && strings.TrimSpace(query.RoomID) == "" && strings.TrimSpace(query.Status) == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func luckyPoolBalanceKey(appCode string, poolID string) string {
|
func luckyPoolBalanceKey(appCode string, poolID string, strategyVersion string) string {
|
||||||
return appcode.Normalize(appCode) + "\x00" + luckyPoolID(poolID)
|
return appcode.Normalize(appCode) + "\x00" + luckyPoolID(poolID) + "\x00" + strings.ToLower(strings.TrimSpace(strategyVersion))
|
||||||
}
|
}
|
||||||
|
|
||||||
func luckyAvailableBalance(balance int64, reserveFloor int64) int64 {
|
func luckyAvailableBalance(balance int64, reserveFloor int64) int64 {
|
||||||
@ -2297,8 +2352,9 @@ func luckyRuntimeConfigFromRuleConfig(ruleConfig domain.RuleConfig) (domain.Conf
|
|||||||
config.StrategyVersion = domain.StrategyFixedV2
|
config.StrategyVersion = domain.StrategyFixedV2
|
||||||
}
|
}
|
||||||
if config.StrategyVersion == domain.StrategyDynamicV3 {
|
if config.StrategyVersion == domain.StrategyDynamicV3 {
|
||||||
// V3 的冷启动资金是绝对金币值且 P=W 可以支付到 0;旧版按礼物参考价推导的 reserve 不能继续套用。
|
// V3 只能由人工 credit 形成启动资金;旧配置残留的 initial_pool_coins 既不能注入账本,也不能进入策略冷启动状态。
|
||||||
config.InitialBasePool = config.InitialPoolCoins
|
config.InitialPoolCoins = 0
|
||||||
|
config.InitialBasePool = 0
|
||||||
config.BasePoolReserve = 0
|
config.BasePoolReserve = 0
|
||||||
}
|
}
|
||||||
config.MultiplierPPMs = luckyMultiplierPPMsFromTiers(config.Tiers, config.GiftPrice)
|
config.MultiplierPPMs = luckyMultiplierPPMsFromTiers(config.Tiers, config.GiftPrice)
|
||||||
@ -2509,12 +2565,14 @@ func (r *Repository) getOrCreateLuckyPool(ctx context.Context, tx *sql.Tx, appCo
|
|||||||
}
|
}
|
||||||
var pool luckyPool
|
var pool luckyPool
|
||||||
err := tx.QueryRowContext(ctx, `
|
err := tx.QueryRowContext(ctx, `
|
||||||
SELECT scope_type, scope_id, balance, reserve_floor, total_in, total_out
|
SELECT scope_type, scope_id, balance, reserve_floor, total_in, total_out,
|
||||||
|
manual_credit_total, manual_debit_total
|
||||||
FROM lucky_pools
|
FROM lucky_pools
|
||||||
WHERE app_code = ? AND scope_type = ? AND scope_id = ?
|
WHERE app_code = ? AND scope_type = ? AND scope_id = ?
|
||||||
FOR UPDATE`,
|
FOR UPDATE`,
|
||||||
appCode, scopeType, scopeID,
|
appCode, scopeType, scopeID,
|
||||||
).Scan(&pool.ScopeType, &pool.ScopeID, &pool.Balance, &pool.ReserveFloor, &pool.TotalIn, &pool.TotalOut)
|
).Scan(&pool.ScopeType, &pool.ScopeID, &pool.Balance, &pool.ReserveFloor, &pool.TotalIn, &pool.TotalOut,
|
||||||
|
&pool.ManualCreditTotal, &pool.ManualDebitTotal)
|
||||||
return pool, err
|
return pool, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,10 @@ func (r *Repository) PublishLuckyGiftRuleConfig(ctx context.Context, config doma
|
|||||||
// 升级前的发布请求没有 strategy_version;存储边界再次兜底为 fixed_v2,避免绕过 service 的调用写成动态规则。
|
// 升级前的发布请求没有 strategy_version;存储边界再次兜底为 fixed_v2,避免绕过 service 的调用写成动态规则。
|
||||||
config.StrategyVersion = domain.StrategyFixedV2
|
config.StrategyVersion = domain.StrategyFixedV2
|
||||||
}
|
}
|
||||||
|
if config.StrategyVersion == domain.StrategyDynamicV3 && config.InitialPoolCoins != 0 {
|
||||||
|
// 仓储边界再次守住零冷启动,即使内部调用绕过 service 校验,也不能发布一个会暗示自动注资的 V3 快照。
|
||||||
|
return domain.RuleConfig{}, xerr.New(xerr.InvalidArgument, "dynamic_v3 initial pool coins must be zero")
|
||||||
|
}
|
||||||
if config.JackpotMultiplierPPMs == nil {
|
if config.JackpotMultiplierPPMs == nil {
|
||||||
config.JackpotMultiplierPPMs = []int64{}
|
config.JackpotMultiplierPPMs = []int64{}
|
||||||
}
|
}
|
||||||
@ -121,6 +125,21 @@ func (r *Repository) PublishLuckyGiftRuleConfig(ctx context.Context, config doma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if config.StrategyVersion == domain.StrategyDynamicV3 {
|
||||||
|
// 发布首个 V3 版本只物化一口 0 余额账本;后续 V3 版本命中唯一键后必须保持余额、累计和时间不变。
|
||||||
|
// 这也保证 fixed_v2 -> dynamic_v3 切换不会继承旧 scope_type=pool 的历史资金。
|
||||||
|
if _, err := tx.ExecContext(ctx, `
|
||||||
|
INSERT INTO lucky_pools (
|
||||||
|
app_code, scope_type, scope_id, balance, reserve_floor, total_in, total_out,
|
||||||
|
manual_credit_total, manual_debit_total, profit_total, anchor_income_total, initial_seed_total,
|
||||||
|
created_at_ms, updated_at_ms
|
||||||
|
) VALUES (?, ?, ?, 0, 0, 0, 0, 0, 0, 0, 0, 0, ?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE scope_id = scope_id`,
|
||||||
|
config.AppCode, luckyDynamicPoolScopeType, config.PoolID, nowMS, nowMS,
|
||||||
|
); err != nil {
|
||||||
|
return domain.RuleConfig{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
return domain.RuleConfig{}, err
|
return domain.RuleConfig{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,7 +78,7 @@ func TestPublishLuckyGiftRuleConfigPersistsVersionsAndTiers(t *testing.T) {
|
|||||||
dynamic.StrategyVersion = domain.StrategyDynamicV3
|
dynamic.StrategyVersion = domain.StrategyDynamicV3
|
||||||
dynamic.ProfitRatePPM = 10_000
|
dynamic.ProfitRatePPM = 10_000
|
||||||
dynamic.AnchorRatePPM = 10_000
|
dynamic.AnchorRatePPM = 10_000
|
||||||
dynamic.InitialPoolCoins = 1_000_000
|
dynamic.InitialPoolCoins = 0
|
||||||
dynamic.LossStreakGuarantee = 5
|
dynamic.LossStreakGuarantee = 5
|
||||||
dynamic.LowWatermarkCoins = 10_000_000
|
dynamic.LowWatermarkCoins = 10_000_000
|
||||||
dynamic.LowWaterNonzeroFactorPPM = 700_000
|
dynamic.LowWaterNonzeroFactorPPM = 700_000
|
||||||
@ -112,7 +112,7 @@ func TestPublishLuckyGiftRuleConfigPersistsVersionsAndTiers(t *testing.T) {
|
|||||||
if err != nil || !exists {
|
if err != nil || !exists {
|
||||||
t.Fatalf("get dynamic rule: exists=%v err=%v", exists, err)
|
t.Fatalf("get dynamic rule: exists=%v err=%v", exists, err)
|
||||||
}
|
}
|
||||||
if loaded.StrategyVersion != domain.StrategyDynamicV3 || loaded.InitialPoolCoins != 1_000_000 || loaded.AnchorDailyPayoutCap != 6_000_000 {
|
if loaded.StrategyVersion != domain.StrategyDynamicV3 || loaded.InitialPoolCoins != 0 || loaded.AnchorDailyPayoutCap != 6_000_000 {
|
||||||
t.Fatalf("dynamic scalar fields were not persisted: %+v", loaded)
|
t.Fatalf("dynamic scalar fields were not persisted: %+v", loaded)
|
||||||
}
|
}
|
||||||
if len(loaded.JackpotMultiplierPPMs) != 3 || loaded.JackpotMultiplierPPMs[2] != 1_000_000_000 {
|
if len(loaded.JackpotMultiplierPPMs) != 3 || loaded.JackpotMultiplierPPMs[2] != 1_000_000_000 {
|
||||||
@ -121,6 +121,25 @@ func TestPublishLuckyGiftRuleConfigPersistsVersionsAndTiers(t *testing.T) {
|
|||||||
if len(loaded.Stages) != 1 || loaded.Stages[0].MinRecharge7DCoins != 1 || loaded.Stages[0].MinRecharge30DCoins != 2 {
|
if len(loaded.Stages) != 1 || loaded.Stages[0].MinRecharge7DCoins != 1 || loaded.Stages[0].MinRecharge30DCoins != 2 {
|
||||||
t.Fatalf("dynamic stage thresholds were not persisted: %+v", loaded.Stages)
|
t.Fatalf("dynamic stage thresholds were not persisted: %+v", loaded.Stages)
|
||||||
}
|
}
|
||||||
|
var dynamicBalance int64
|
||||||
|
if err := schema.DB.QueryRow(`
|
||||||
|
SELECT balance FROM lucky_pools
|
||||||
|
WHERE app_code='lalu' AND scope_type='pool_dynamic_v3' AND scope_id='lucky'`).Scan(&dynamicBalance); err != nil || dynamicBalance != 0 {
|
||||||
|
t.Fatalf("first dynamic publish balance=%d err=%v, want materialized zero ledger", dynamicBalance, err)
|
||||||
|
}
|
||||||
|
if _, err := schema.DB.Exec(`
|
||||||
|
UPDATE lucky_pools SET balance=777, manual_credit_total=777
|
||||||
|
WHERE app_code='lalu' AND scope_type='pool_dynamic_v3' AND scope_id='lucky'`); err != nil {
|
||||||
|
t.Fatalf("fund dynamic ledger before republish: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := repo.PublishLuckyGiftRuleConfig(ctx, dynamic, 1_700_000_300_000); err != nil {
|
||||||
|
t.Fatalf("publish later dynamic version: %v", err)
|
||||||
|
}
|
||||||
|
if err := schema.DB.QueryRow(`
|
||||||
|
SELECT balance FROM lucky_pools
|
||||||
|
WHERE app_code='lalu' AND scope_type='pool_dynamic_v3' AND scope_id='lucky'`).Scan(&dynamicBalance); err != nil || dynamicBalance != 777 {
|
||||||
|
t.Fatalf("later dynamic publish balance=%d err=%v, want existing 777 preserved", dynamicBalance, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDynamicStrategyMigrationIsIdempotent(t *testing.T) {
|
func TestDynamicStrategyMigrationIsIdempotent(t *testing.T) {
|
||||||
@ -185,3 +204,52 @@ func TestDynamicStrategyMigrationIsIdempotent(t *testing.T) {
|
|||||||
t.Fatalf("pre-dynamic row decoded as strategy=%q jackpot=%v, want fixed_v2 and empty jackpot", legacy.StrategyVersion, legacy.JackpotMultiplierPPMs)
|
t.Fatalf("pre-dynamic row decoded as strategy=%q jackpot=%v, want fixed_v2 and empty jackpot", legacy.StrategyVersion, legacy.JackpotMultiplierPPMs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLuckyPoolBalanceMigrationMaterializesLatestDynamicV3AtZeroWithoutReset(t *testing.T) {
|
||||||
|
caller := mysqlschema.CallerFile(t, 1)
|
||||||
|
schema := mysqlschema.New(t, mysqlschema.Config{
|
||||||
|
EnvVar: "LUCKY_GIFT_SERVICE_MYSQL_TEST_DSN",
|
||||||
|
InitDBPath: mysqlschema.InitDBPath(t, caller, "..", "..", "..", "deploy", "mysql", "initdb", "001_lucky_gift_service.sql"),
|
||||||
|
DatabasePrefix: "hy_lucky_gift",
|
||||||
|
})
|
||||||
|
repo := &Repository{db: schema.DB}
|
||||||
|
ctx := appcode.WithContext(context.Background(), "aslan")
|
||||||
|
rule := dynamicMySQLTestRule("migration-v3")
|
||||||
|
if _, err := repo.PublishLuckyGiftRuleConfig(ctx, rule, 1_700_001_000_000); err != nil {
|
||||||
|
t.Fatalf("publish dynamic config: %v", err)
|
||||||
|
}
|
||||||
|
// 模拟现网已经发布的旧 V3 快照:配置里仍残留 1,000,000,但 005 必须忽略它并创建零余额新 scope。
|
||||||
|
if _, err := schema.DB.Exec(`UPDATE lucky_gift_rule_versions SET initial_pool_coins=1000000 WHERE app_code='aslan' AND pool_id='migration-v3'`); err != nil {
|
||||||
|
t.Fatalf("seed legacy dynamic initial_pool_coins: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := schema.DB.Exec(`
|
||||||
|
INSERT INTO lucky_pools (
|
||||||
|
app_code, scope_type, scope_id, balance, reserve_floor, total_in, total_out,
|
||||||
|
profit_total, anchor_income_total, initial_seed_total, created_at_ms, updated_at_ms
|
||||||
|
) VALUES ('aslan', 'pool', 'migration-v3', 888, 88, 1000, 112, 0, 0, 888, 1, 2)`); err != nil {
|
||||||
|
t.Fatalf("seed legacy fixed scope: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := schema.DB.Exec(`DELETE FROM lucky_pools WHERE app_code='aslan' AND scope_type='pool_dynamic_v3' AND scope_id='migration-v3'`); err != nil {
|
||||||
|
t.Fatalf("simulate pre-005 database: %v", err)
|
||||||
|
}
|
||||||
|
migrationPath := mysqlschema.InitDBPath(t, caller, "..", "..", "..", "deploy", "mysql", "migrations", "005_lucky_pool_balance.sql")
|
||||||
|
runLuckyGiftSQLScript(t, schema.DB, migrationPath)
|
||||||
|
var balance int64
|
||||||
|
if err := schema.DB.QueryRow(`SELECT balance FROM lucky_pools WHERE app_code='aslan' AND scope_type='pool_dynamic_v3' AND scope_id='migration-v3'`).Scan(&balance); err != nil || balance != 0 {
|
||||||
|
t.Fatalf("005 materialized balance=%d err=%v, want 0", balance, err)
|
||||||
|
}
|
||||||
|
var legacyBalance int64
|
||||||
|
if err := schema.DB.QueryRow(`SELECT balance FROM lucky_pools WHERE app_code='aslan' AND scope_type='pool' AND scope_id='migration-v3'`).Scan(&legacyBalance); err != nil || legacyBalance != 888 {
|
||||||
|
t.Fatalf("005 changed legacy fixed scope balance=%d err=%v, want 888", legacyBalance, err)
|
||||||
|
}
|
||||||
|
if _, err := schema.DB.Exec(`UPDATE lucky_pools SET balance=321, manual_credit_total=321 WHERE app_code='aslan' AND scope_type='pool_dynamic_v3' AND scope_id='migration-v3'`); err != nil {
|
||||||
|
t.Fatalf("fund migrated pool: %v", err)
|
||||||
|
}
|
||||||
|
runLuckyGiftSQLScript(t, schema.DB, migrationPath)
|
||||||
|
if err := schema.DB.QueryRow(`SELECT balance FROM lucky_pools WHERE app_code='aslan' AND scope_type='pool_dynamic_v3' AND scope_id='migration-v3'`).Scan(&balance); err != nil || balance != 321 {
|
||||||
|
t.Fatalf("rerun 005 balance=%d err=%v, want existing 321 preserved", balance, err)
|
||||||
|
}
|
||||||
|
if err := schema.DB.QueryRow(`SELECT balance FROM lucky_pools WHERE app_code='aslan' AND scope_type='pool' AND scope_id='migration-v3'`).Scan(&legacyBalance); err != nil || legacyBalance != 888 {
|
||||||
|
t.Fatalf("rerun 005 changed legacy fixed scope balance=%d err=%v, want 888", legacyBalance, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,261 @@
|
|||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hyapp/pkg/appcode"
|
||||||
|
"hyapp/pkg/xerr"
|
||||||
|
domain "hyapp/services/lucky-gift-service/internal/domain/luckygift"
|
||||||
|
)
|
||||||
|
|
||||||
|
func luckyPoolScopeForStrategy(strategyVersion string) (string, error) {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(strategyVersion)) {
|
||||||
|
case domain.StrategyFixedV2:
|
||||||
|
return luckyBasePoolScopeType, nil
|
||||||
|
case domain.StrategyDynamicV3:
|
||||||
|
return luckyDynamicPoolScopeType, nil
|
||||||
|
default:
|
||||||
|
return "", xerr.New(xerr.InvalidArgument, "unsupported lucky gift strategy version")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func luckyStrategyForPoolScope(scopeType string) (string, bool) {
|
||||||
|
switch scopeType {
|
||||||
|
case luckyBasePoolScopeType:
|
||||||
|
return domain.StrategyFixedV2, true
|
||||||
|
case luckyDynamicPoolScopeType:
|
||||||
|
return domain.StrategyDynamicV3, true
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdjustLuckyGiftPoolBalance 是人工资金调整的唯一持久化入口。同一个事务先锁 adjustment_id,
|
||||||
|
// 再锁 app+strategy+pool 资金行;这样并发重试只能重放首次结果,不能在两个事务中重复增减余额。
|
||||||
|
func (r *Repository) AdjustLuckyGiftPoolBalance(ctx context.Context, command domain.PoolAdjustmentCommand, nowMS int64) (domain.PoolAdjustmentResult, error) {
|
||||||
|
if r == nil || r.db == nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
|
||||||
|
}
|
||||||
|
appCode := appcode.Normalize(command.AppCode)
|
||||||
|
poolID := luckyPoolID(command.PoolID)
|
||||||
|
scopeType, err := luckyPoolScopeForStrategy(command.StrategyVersion)
|
||||||
|
if err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := r.db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, err
|
||||||
|
}
|
||||||
|
defer func() { _ = tx.Rollback() }()
|
||||||
|
|
||||||
|
var tokenEntropy [16]byte
|
||||||
|
if _, err := rand.Read(tokenEntropy[:]); err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.Unavailable, "cannot generate lucky gift pool adjustment insert token")
|
||||||
|
}
|
||||||
|
insertToken := hex.EncodeToString(tokenEntropy[:])
|
||||||
|
// 唯一键的 ON DUPLICATE 分支只做 no-op;随后比较库内首次随机令牌来判断本事务是否首写,
|
||||||
|
// 因而既不依赖 clientFoundRows,也避免多个 duplicate INSERT 错误事务互相持锁形成死锁。
|
||||||
|
_, err = tx.ExecContext(ctx, `
|
||||||
|
INSERT INTO lucky_pool_adjustments (
|
||||||
|
app_code, adjustment_id, pool_id, strategy_version, scope_type, direction,
|
||||||
|
amount_coins, reason, operator_admin_id, request_id, insert_token,
|
||||||
|
balance_before, balance_after, pool_snapshot_json, status, created_at_ms, updated_at_ms
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, NULL, 'pending', ?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE adjustment_id = adjustment_id`,
|
||||||
|
appCode, command.AdjustmentID, poolID, command.StrategyVersion, scopeType, command.Direction,
|
||||||
|
command.AmountCoins, command.Reason, command.OperatorAdminID, command.RequestID, insertToken, nowMS, nowMS,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var stored domain.PoolAdjustment
|
||||||
|
var storedScopeType string
|
||||||
|
var storedRequestID string
|
||||||
|
var storedInsertToken string
|
||||||
|
var storedStatus string
|
||||||
|
var storedSnapshot sql.NullString
|
||||||
|
if err := tx.QueryRowContext(ctx, `
|
||||||
|
SELECT adjustment_id, app_code, pool_id, strategy_version, scope_type, direction,
|
||||||
|
amount_coins, reason, operator_admin_id, request_id, insert_token,
|
||||||
|
balance_before, balance_after, pool_snapshot_json, status, created_at_ms
|
||||||
|
FROM lucky_pool_adjustments
|
||||||
|
WHERE app_code = ? AND adjustment_id = ?
|
||||||
|
FOR UPDATE`, appCode, command.AdjustmentID,
|
||||||
|
).Scan(
|
||||||
|
&stored.AdjustmentID, &stored.AppCode, &stored.PoolID, &stored.StrategyVersion, &storedScopeType, &stored.Direction,
|
||||||
|
&stored.AmountCoins, &stored.Reason, &stored.OperatorAdminID, &storedRequestID, &storedInsertToken,
|
||||||
|
&stored.BalanceBefore, &stored.BalanceAfter, &storedSnapshot, &storedStatus, &stored.CreatedAtMS,
|
||||||
|
); err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, err
|
||||||
|
}
|
||||||
|
inserted := storedInsertToken == insertToken
|
||||||
|
|
||||||
|
// request_id 允许随网络重试变化;其余字段共同定义资金命令,任何不一致都属于幂等冲突。
|
||||||
|
if stored.PoolID != poolID || stored.StrategyVersion != command.StrategyVersion || storedScopeType != scopeType ||
|
||||||
|
stored.Direction != command.Direction || stored.AmountCoins != command.AmountCoins || stored.Reason != command.Reason ||
|
||||||
|
stored.OperatorAdminID != command.OperatorAdminID {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.IdempotencyConflict, "lucky gift pool adjustment payload does not match the existing adjustment_id")
|
||||||
|
}
|
||||||
|
if !inserted {
|
||||||
|
if storedStatus != "applied" || !storedSnapshot.Valid {
|
||||||
|
// 正常事务不会留下 pending 行;若人工修复或异常导入造成该状态,宁可阻断也不能猜测是否已经动账。
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.Conflict, "lucky gift pool adjustment is not in an applied state")
|
||||||
|
}
|
||||||
|
var snapshot domain.PoolBalance
|
||||||
|
if err := json.Unmarshal([]byte(storedSnapshot.String), &snapshot); err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.Conflict, "lucky gift pool adjustment snapshot is invalid")
|
||||||
|
}
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, err
|
||||||
|
}
|
||||||
|
return domain.PoolAdjustmentResult{Adjustment: stored, Pool: snapshot, IdempotentReplay: true}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pool, err := r.getOrCreateAdjustmentPool(ctx, tx, appCode, poolID, command.StrategyVersion, scopeType, nowMS)
|
||||||
|
if err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, err
|
||||||
|
}
|
||||||
|
stored.BalanceBefore = pool.Balance
|
||||||
|
switch command.Direction {
|
||||||
|
case domain.PoolAdjustmentIn:
|
||||||
|
if pool.Balance > math.MaxInt64-command.AmountCoins || pool.ManualCreditTotal > math.MaxInt64-command.AmountCoins {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.Conflict, "lucky gift pool credit would overflow the ledger")
|
||||||
|
}
|
||||||
|
pool.Balance += command.AmountCoins
|
||||||
|
pool.ManualCreditTotal += command.AmountCoins
|
||||||
|
case domain.PoolAdjustmentOut:
|
||||||
|
if pool.ManualDebitTotal > math.MaxInt64-command.AmountCoins || command.AmountCoins > pool.Balance || pool.Balance-command.AmountCoins < pool.ReserveFloor {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.Conflict, "lucky gift pool debit would cross the reserve floor")
|
||||||
|
}
|
||||||
|
pool.Balance -= command.AmountCoins
|
||||||
|
pool.ManualDebitTotal += command.AmountCoins
|
||||||
|
default:
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.InvalidArgument, "lucky gift pool adjustment direction is invalid")
|
||||||
|
}
|
||||||
|
stored.BalanceAfter = pool.Balance
|
||||||
|
|
||||||
|
// 手工资金只更新 balance 和独立累计列;业务 total_in/total_out 仍只表达送礼入池与实际返奖。
|
||||||
|
updateResult, err := tx.ExecContext(ctx, `
|
||||||
|
UPDATE lucky_pools
|
||||||
|
SET balance = ?, manual_credit_total = ?, manual_debit_total = ?, updated_at_ms = ?
|
||||||
|
WHERE app_code = ? AND scope_type = ? AND scope_id = ?`,
|
||||||
|
pool.Balance, pool.ManualCreditTotal, pool.ManualDebitTotal, nowMS, appCode, scopeType, poolID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, err
|
||||||
|
}
|
||||||
|
if rows, err := updateResult.RowsAffected(); err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, err
|
||||||
|
} else if rows != 1 {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.Conflict, "lucky gift pool changed during adjustment")
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot := domain.PoolBalance{
|
||||||
|
AppCode: appCode,
|
||||||
|
PoolID: poolID,
|
||||||
|
StrategyVersion: command.StrategyVersion,
|
||||||
|
Balance: pool.Balance,
|
||||||
|
ReserveFloor: pool.ReserveFloor,
|
||||||
|
AvailableBalance: luckyAvailableBalance(pool.Balance, pool.ReserveFloor),
|
||||||
|
TotalIn: pool.TotalIn,
|
||||||
|
TotalOut: pool.TotalOut,
|
||||||
|
ManualCreditTotal: pool.ManualCreditTotal,
|
||||||
|
ManualDebitTotal: pool.ManualDebitTotal,
|
||||||
|
Materialized: true,
|
||||||
|
UpdatedAtMS: nowMS,
|
||||||
|
}
|
||||||
|
snapshotJSON, err := json.Marshal(snapshot)
|
||||||
|
if err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, err
|
||||||
|
}
|
||||||
|
finalizeResult, err := tx.ExecContext(ctx, `
|
||||||
|
UPDATE lucky_pool_adjustments
|
||||||
|
SET balance_before = ?, balance_after = ?, pool_snapshot_json = ?, status = 'applied', updated_at_ms = ?
|
||||||
|
WHERE app_code = ? AND adjustment_id = ? AND status = 'pending'`,
|
||||||
|
stored.BalanceBefore, stored.BalanceAfter, snapshotJSON, nowMS, appCode, command.AdjustmentID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, err
|
||||||
|
}
|
||||||
|
if rows, err := finalizeResult.RowsAffected(); err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, err
|
||||||
|
} else if rows != 1 {
|
||||||
|
return domain.PoolAdjustmentResult{}, xerr.New(xerr.Conflict, "lucky gift pool adjustment could not be finalized")
|
||||||
|
}
|
||||||
|
if err := tx.Commit(); err != nil {
|
||||||
|
return domain.PoolAdjustmentResult{}, err
|
||||||
|
}
|
||||||
|
return domain.PoolAdjustmentResult{Adjustment: stored, Pool: snapshot}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) getOrCreateAdjustmentPool(ctx context.Context, tx *sql.Tx, appCode, poolID, strategyVersion, scopeType string, nowMS int64) (luckyPool, error) {
|
||||||
|
// 人工资金不能凭任意 pool_id 创建孤儿账本;指定策略至少要有一个已发布不可变版本。
|
||||||
|
var configured int
|
||||||
|
err := tx.QueryRowContext(ctx, `
|
||||||
|
SELECT 1
|
||||||
|
FROM lucky_gift_rule_versions
|
||||||
|
WHERE app_code = ? AND pool_id = ?
|
||||||
|
AND COALESCE(NULLIF(strategy_version, ''), 'fixed_v2') = ?
|
||||||
|
LIMIT 1`, appCode, poolID, strategyVersion,
|
||||||
|
).Scan(&configured)
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return luckyPool{}, xerr.New(xerr.NotFound, "lucky gift pool strategy is not configured")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return luckyPool{}, err
|
||||||
|
}
|
||||||
|
if strategyVersion == domain.StrategyDynamicV3 {
|
||||||
|
return r.getOrCreateLuckyPool(ctx, tx, appCode, scopeType, poolID, 0, 0, nowMS)
|
||||||
|
}
|
||||||
|
var existing luckyPool
|
||||||
|
err = tx.QueryRowContext(ctx, `
|
||||||
|
SELECT scope_type, scope_id, balance, reserve_floor, total_in, total_out,
|
||||||
|
manual_credit_total, manual_debit_total
|
||||||
|
FROM lucky_pools
|
||||||
|
WHERE app_code = ? AND scope_type = ? AND scope_id = ?
|
||||||
|
FOR UPDATE`, appCode, scopeType, poolID,
|
||||||
|
).Scan(
|
||||||
|
&existing.ScopeType, &existing.ScopeID, &existing.Balance, &existing.ReserveFloor, &existing.TotalIn, &existing.TotalOut,
|
||||||
|
&existing.ManualCreditTotal, &existing.ManualDebitTotal,
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
return existing, nil
|
||||||
|
}
|
||||||
|
if !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return luckyPool{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixed_v2 首次人工调整仍需保持旧算法的启动水位。只读取最新 fixed_v2 规则的参考价格,
|
||||||
|
// 不使用当前最新 dynamic_v3 配置,也不把旧固定账本从 0 重新解释。
|
||||||
|
var referencePrice int64
|
||||||
|
err = tx.QueryRowContext(ctx, `
|
||||||
|
SELECT gift_price_reference
|
||||||
|
FROM lucky_gift_rule_versions
|
||||||
|
WHERE app_code = ? AND pool_id = ?
|
||||||
|
AND COALESCE(NULLIF(strategy_version, ''), 'fixed_v2') = 'fixed_v2'
|
||||||
|
ORDER BY rule_version DESC
|
||||||
|
LIMIT 1`, appCode, poolID,
|
||||||
|
).Scan(&referencePrice)
|
||||||
|
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return luckyPool{}, err
|
||||||
|
}
|
||||||
|
if errors.Is(err, sql.ErrNoRows) || referencePrice <= 0 {
|
||||||
|
return luckyPool{}, xerr.New(xerr.NotFound, "fixed_v2 lucky gift pool is not configured")
|
||||||
|
}
|
||||||
|
if referencePrice > math.MaxInt64/(950+500+2_375) {
|
||||||
|
return luckyPool{}, xerr.New(xerr.Conflict, "fixed_v2 lucky gift pool initial balance would overflow")
|
||||||
|
}
|
||||||
|
return r.getOrCreateLuckyPool(
|
||||||
|
ctx, tx, appCode, scopeType, poolID,
|
||||||
|
referencePrice*(950+500+2_375), referencePrice*(100+10+100), nowMS,
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,163 @@
|
|||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"hyapp/internal/testutil/mysqlschema"
|
||||||
|
"hyapp/pkg/appcode"
|
||||||
|
"hyapp/pkg/xerr"
|
||||||
|
domain "hyapp/services/lucky-gift-service/internal/domain/luckygift"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdjustLuckyGiftPoolBalanceSeparatesStrategiesAndReplaysIdempotently(t *testing.T) {
|
||||||
|
caller := mysqlschema.CallerFile(t, 1)
|
||||||
|
schema := mysqlschema.New(t, mysqlschema.Config{
|
||||||
|
EnvVar: "LUCKY_GIFT_SERVICE_MYSQL_TEST_DSN",
|
||||||
|
InitDBPath: mysqlschema.InitDBPath(t, caller, "..", "..", "..", "deploy", "mysql", "initdb", "001_lucky_gift_service.sql"),
|
||||||
|
DatabasePrefix: "hy_lucky_gift",
|
||||||
|
})
|
||||||
|
repo := &Repository{db: schema.DB}
|
||||||
|
ctx := appcode.WithContext(context.Background(), "aslan")
|
||||||
|
|
||||||
|
fixed := dynamicMySQLTestRule("shared-pool")
|
||||||
|
fixed.StrategyVersion = domain.StrategyFixedV2
|
||||||
|
if _, err := repo.PublishLuckyGiftRuleConfig(ctx, fixed, 1_700_010_000_000); err != nil {
|
||||||
|
t.Fatalf("publish fixed config: %v", err)
|
||||||
|
}
|
||||||
|
dynamic := dynamicMySQLTestRule("shared-pool")
|
||||||
|
if _, err := repo.PublishLuckyGiftRuleConfig(ctx, dynamic, 1_700_010_000_100); err != nil {
|
||||||
|
t.Fatalf("publish dynamic config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fixedResult, err := repo.AdjustLuckyGiftPoolBalance(ctx, domain.PoolAdjustmentCommand{
|
||||||
|
AppCode: "aslan", PoolID: "shared-pool", StrategyVersion: domain.StrategyFixedV2,
|
||||||
|
AdjustmentID: "fixed-credit-1", Direction: domain.PoolAdjustmentIn, AmountCoins: 10,
|
||||||
|
Reason: "legacy reserve", OperatorAdminID: 7,
|
||||||
|
}, 1_700_010_001_000)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("credit fixed pool: %v", err)
|
||||||
|
}
|
||||||
|
if fixedResult.Pool.Balance != 3_835 || fixedResult.Pool.ReserveFloor != 210 {
|
||||||
|
t.Fatalf("fixed pool snapshot=%+v, want old 3825/210 seed plus 10", fixedResult.Pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
credit := domain.PoolAdjustmentCommand{
|
||||||
|
AppCode: "aslan", PoolID: "shared-pool", StrategyVersion: domain.StrategyDynamicV3,
|
||||||
|
AdjustmentID: "dynamic-credit-1", Direction: domain.PoolAdjustmentIn, AmountCoins: 100,
|
||||||
|
Reason: "approved launch budget", OperatorAdminID: 7, RequestID: "request-1",
|
||||||
|
}
|
||||||
|
first, err := repo.AdjustLuckyGiftPoolBalance(ctx, credit, 1_700_010_002_000)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("credit dynamic pool: %v", err)
|
||||||
|
}
|
||||||
|
if first.Adjustment.BalanceBefore != 0 || first.Adjustment.BalanceAfter != 100 || first.Pool.ManualCreditTotal != 100 || first.Pool.TotalIn != 0 {
|
||||||
|
t.Fatalf("dynamic credit result=%+v, want 0->100 isolated manual credit", first)
|
||||||
|
}
|
||||||
|
|
||||||
|
replayCommand := credit
|
||||||
|
replayCommand.RequestID = "request-retry"
|
||||||
|
replay, err := repo.AdjustLuckyGiftPoolBalance(ctx, replayCommand, 1_700_010_003_000)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("replay dynamic credit: %v", err)
|
||||||
|
}
|
||||||
|
if !replay.IdempotentReplay || replay.Adjustment.BalanceBefore != 0 || replay.Pool.Balance != 100 {
|
||||||
|
t.Fatalf("replay result=%+v, want original snapshot", replay)
|
||||||
|
}
|
||||||
|
mismatch := credit
|
||||||
|
mismatch.AmountCoins = 101
|
||||||
|
if _, err := repo.AdjustLuckyGiftPoolBalance(ctx, mismatch, 1_700_010_004_000); !xerr.IsCode(err, xerr.IdempotencyConflict) {
|
||||||
|
t.Fatalf("same ID with different payload error=%v, want idempotency conflict", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
debit := domain.PoolAdjustmentCommand{
|
||||||
|
AppCode: "aslan", PoolID: "shared-pool", StrategyVersion: domain.StrategyDynamicV3,
|
||||||
|
AdjustmentID: "dynamic-debit-1", Direction: domain.PoolAdjustmentOut, AmountCoins: 30,
|
||||||
|
Reason: "return unused budget", OperatorAdminID: 7,
|
||||||
|
}
|
||||||
|
debitResult, err := repo.AdjustLuckyGiftPoolBalance(ctx, debit, 1_700_010_005_000)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("debit dynamic pool: %v", err)
|
||||||
|
}
|
||||||
|
if debitResult.Pool.Balance != 70 || debitResult.Pool.ManualDebitTotal != 30 || debitResult.Pool.TotalOut != 0 {
|
||||||
|
t.Fatalf("dynamic debit result=%+v, want manual 30 without business total_out", debitResult)
|
||||||
|
}
|
||||||
|
overDebit := debit
|
||||||
|
overDebit.AdjustmentID = "dynamic-debit-over-reserve"
|
||||||
|
overDebit.AmountCoins = 71
|
||||||
|
if _, err := repo.AdjustLuckyGiftPoolBalance(ctx, overDebit, 1_700_010_006_000); !xerr.IsCode(err, xerr.Conflict) {
|
||||||
|
t.Fatalf("over-reserve debit error=%v, want conflict", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 八个并发重试共享一个 adjustment_id;唯一键和 FOR UPDATE 必须只让一次 10 金币入账。
|
||||||
|
concurrent := credit
|
||||||
|
concurrent.AdjustmentID = "dynamic-credit-concurrent"
|
||||||
|
concurrent.AmountCoins = 10
|
||||||
|
concurrent.Reason = "single concurrent grant"
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errorsByWorker := make([]error, 8)
|
||||||
|
for index := range errorsByWorker {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(worker int) {
|
||||||
|
defer wg.Done()
|
||||||
|
command := concurrent
|
||||||
|
command.RequestID = "concurrent-request"
|
||||||
|
_, errorsByWorker[worker] = repo.AdjustLuckyGiftPoolBalance(ctx, command, 1_700_010_007_000)
|
||||||
|
}(index)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
for index, workerErr := range errorsByWorker {
|
||||||
|
if workerErr != nil {
|
||||||
|
t.Fatalf("concurrent worker %d: %v", index, workerErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dynamicBalance, dynamicManualCredit, dynamicManualDebit, dynamicTotalIn, dynamicTotalOut int64
|
||||||
|
if err := schema.DB.QueryRow(`
|
||||||
|
SELECT balance, manual_credit_total, manual_debit_total, total_in, total_out
|
||||||
|
FROM lucky_pools WHERE app_code='aslan' AND scope_type='pool_dynamic_v3' AND scope_id='shared-pool'`,
|
||||||
|
).Scan(&dynamicBalance, &dynamicManualCredit, &dynamicManualDebit, &dynamicTotalIn, &dynamicTotalOut); err != nil {
|
||||||
|
t.Fatalf("query dynamic pool: %v", err)
|
||||||
|
}
|
||||||
|
if dynamicBalance != 80 || dynamicManualCredit != 110 || dynamicManualDebit != 30 || dynamicTotalIn != 0 || dynamicTotalOut != 0 {
|
||||||
|
t.Fatalf("dynamic ledger balance/credit/debit/in/out=%d/%d/%d/%d/%d, want 80/110/30/0/0", dynamicBalance, dynamicManualCredit, dynamicManualDebit, dynamicTotalIn, dynamicTotalOut)
|
||||||
|
}
|
||||||
|
var fixedBalance int64
|
||||||
|
if err := schema.DB.QueryRow(`
|
||||||
|
SELECT balance FROM lucky_pools WHERE app_code='aslan' AND scope_type='pool' AND scope_id='shared-pool'`,
|
||||||
|
).Scan(&fixedBalance); err != nil || fixedBalance != 3_835 {
|
||||||
|
t.Fatalf("fixed ledger balance=%d err=%v, want isolated 3835", fixedBalance, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := repo.ListLuckyGiftPoolBalances(ctx, domain.PoolBalanceQuery{AppCode: "aslan", PoolID: "shared-pool"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("list both strategy ledgers: %v", err)
|
||||||
|
}
|
||||||
|
if len(items) != 2 || items[0].StrategyVersion == items[1].StrategyVersion {
|
||||||
|
t.Fatalf("strategy-separated pool list=%+v, want fixed_v2 and dynamic_v3", items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdjustLuckyGiftPoolBalanceRejectsUnconfiguredStrategy(t *testing.T) {
|
||||||
|
caller := mysqlschema.CallerFile(t, 1)
|
||||||
|
schema := mysqlschema.New(t, mysqlschema.Config{
|
||||||
|
EnvVar: "LUCKY_GIFT_SERVICE_MYSQL_TEST_DSN",
|
||||||
|
InitDBPath: mysqlschema.InitDBPath(t, caller, "..", "..", "..", "deploy", "mysql", "initdb", "001_lucky_gift_service.sql"),
|
||||||
|
DatabasePrefix: "hy_lucky_gift",
|
||||||
|
})
|
||||||
|
repo := &Repository{db: schema.DB}
|
||||||
|
ctx := appcode.WithContext(context.Background(), "aslan")
|
||||||
|
_, err := repo.AdjustLuckyGiftPoolBalance(ctx, domain.PoolAdjustmentCommand{
|
||||||
|
AppCode: "aslan", PoolID: "not-configured", StrategyVersion: domain.StrategyDynamicV3,
|
||||||
|
AdjustmentID: "orphan-credit", Direction: domain.PoolAdjustmentIn, AmountCoins: 1,
|
||||||
|
Reason: "must fail", OperatorAdminID: 7,
|
||||||
|
}, 1_700_020_000_000)
|
||||||
|
if !xerr.IsCode(err, xerr.NotFound) {
|
||||||
|
t.Fatalf("unconfigured strategy error=%v, want not found", err)
|
||||||
|
}
|
||||||
|
var count int
|
||||||
|
if scanErr := schema.DB.QueryRow(`SELECT COUNT(*) FROM lucky_pools WHERE app_code='aslan' AND scope_id='not-configured'`).Scan(&count); scanErr != nil || count != 0 {
|
||||||
|
t.Fatalf("orphan pool count=%d err=%v, want 0", count, scanErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -228,8 +228,9 @@ func (s *AdminLuckyGiftServer) ListLuckyGiftPoolBalances(ctx context.Context, re
|
|||||||
ctx = appcode.WithContext(ctx, appCode)
|
ctx = appcode.WithContext(ctx, appCode)
|
||||||
}
|
}
|
||||||
pools, err := s.svc.ListPoolBalances(ctx, domain.PoolBalanceQuery{
|
pools, err := s.svc.ListPoolBalances(ctx, domain.PoolBalanceQuery{
|
||||||
AppCode: appCode,
|
AppCode: appCode,
|
||||||
PoolID: req.GetPoolId(),
|
PoolID: req.GetPoolId(),
|
||||||
|
StrategyVersion: req.GetStrategyVersion(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerr.ToGRPCError(err)
|
return nil, xerr.ToGRPCError(err)
|
||||||
@ -241,6 +242,32 @@ func (s *AdminLuckyGiftServer) ListLuckyGiftPoolBalances(ctx context.Context, re
|
|||||||
return resp, nil
|
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 {
|
func luckyRuleConfigFromProto(config *luckygiftv1.LuckyGiftRuleConfig) domain.RuleConfig {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return domain.RuleConfig{}
|
return domain.RuleConfig{}
|
||||||
@ -440,14 +467,33 @@ func luckyDrawSummaryToProto(summary domain.DrawSummary) *luckygiftv1.LuckyGiftD
|
|||||||
|
|
||||||
func luckyPoolBalanceToProto(pool domain.PoolBalance) *luckygiftv1.LuckyGiftPoolBalance {
|
func luckyPoolBalanceToProto(pool domain.PoolBalance) *luckygiftv1.LuckyGiftPoolBalance {
|
||||||
return &luckygiftv1.LuckyGiftPoolBalance{
|
return &luckygiftv1.LuckyGiftPoolBalance{
|
||||||
AppCode: pool.AppCode,
|
AppCode: pool.AppCode,
|
||||||
PoolId: pool.PoolID,
|
PoolId: pool.PoolID,
|
||||||
Balance: pool.Balance,
|
StrategyVersion: pool.StrategyVersion,
|
||||||
ReserveFloor: pool.ReserveFloor,
|
Balance: pool.Balance,
|
||||||
AvailableBalance: pool.AvailableBalance,
|
ReserveFloor: pool.ReserveFloor,
|
||||||
TotalIn: pool.TotalIn,
|
AvailableBalance: pool.AvailableBalance,
|
||||||
TotalOut: pool.TotalOut,
|
TotalIn: pool.TotalIn,
|
||||||
Materialized: pool.Materialized,
|
TotalOut: pool.TotalOut,
|
||||||
UpdatedAtMs: pool.UpdatedAtMS,
|
ManualCreditTotal: pool.ManualCreditTotal,
|
||||||
|
ManualDebitTotal: pool.ManualDebitTotal,
|
||||||
|
Materialized: pool.Materialized,
|
||||||
|
UpdatedAtMs: pool.UpdatedAtMS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,24 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
luckygiftv1 "hyapp.local/api/proto/luckygift/v1"
|
luckygiftv1 "hyapp.local/api/proto/luckygift/v1"
|
||||||
|
domain "hyapp/services/lucky-gift-service/internal/domain/luckygift"
|
||||||
|
service "hyapp/services/lucky-gift-service/internal/service/luckygift"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type poolAdjustmentGRPCRepository struct {
|
||||||
|
service.Repository
|
||||||
|
command domain.PoolAdjustmentCommand
|
||||||
|
result domain.PoolAdjustmentResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *poolAdjustmentGRPCRepository) AdjustLuckyGiftPoolBalance(_ context.Context, command domain.PoolAdjustmentCommand, _ int64) (domain.PoolAdjustmentResult, error) {
|
||||||
|
r.command = command
|
||||||
|
return r.result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestBatchExecuteLuckyGiftDrawRejectsMixedAppCodes(t *testing.T) {
|
func TestBatchExecuteLuckyGiftDrawRejectsMixedAppCodes(t *testing.T) {
|
||||||
server := NewLuckyGiftServer(nil)
|
server := NewLuckyGiftServer(nil)
|
||||||
_, err := server.BatchExecuteLuckyGiftDraw(context.Background(), &luckygiftv1.BatchExecuteLuckyGiftDrawRequest{
|
_, err := server.BatchExecuteLuckyGiftDraw(context.Background(), &luckygiftv1.BatchExecuteLuckyGiftDrawRequest{
|
||||||
@ -19,3 +35,42 @@ func TestBatchExecuteLuckyGiftDrawRejectsMixedAppCodes(t *testing.T) {
|
|||||||
t.Fatal("expected mixed app_code batch to be rejected")
|
t.Fatal("expected mixed app_code batch to be rejected")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdjustLuckyGiftPoolBalanceMapsExplicitStrategyAndSnapshot(t *testing.T) {
|
||||||
|
repository := &poolAdjustmentGRPCRepository{result: domain.PoolAdjustmentResult{
|
||||||
|
Adjustment: domain.PoolAdjustment{
|
||||||
|
AdjustmentID: "adjustment-1", AppCode: "aslan", PoolID: "lucky", StrategyVersion: domain.StrategyDynamicV3,
|
||||||
|
Direction: domain.PoolAdjustmentIn, AmountCoins: 100, BalanceBefore: 0, BalanceAfter: 100,
|
||||||
|
},
|
||||||
|
Pool: domain.PoolBalance{
|
||||||
|
AppCode: "aslan", PoolID: "lucky", StrategyVersion: domain.StrategyDynamicV3,
|
||||||
|
Balance: 100, ManualCreditTotal: 100, Materialized: true,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
server := NewAdminLuckyGiftServer(service.New(repository))
|
||||||
|
resp, err := server.AdjustLuckyGiftPoolBalance(context.Background(), &luckygiftv1.AdjustLuckyGiftPoolBalanceRequest{
|
||||||
|
Meta: &luckygiftv1.RequestMeta{AppCode: "aslan", RequestId: "request-1"},
|
||||||
|
PoolId: "lucky", StrategyVersion: domain.StrategyDynamicV3, AdjustmentId: "adjustment-1",
|
||||||
|
Direction: domain.PoolAdjustmentIn, AmountCoins: 100, Reason: "launch", OperatorAdminId: 7,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("adjust pool RPC: %v", err)
|
||||||
|
}
|
||||||
|
if repository.command.AppCode != "aslan" || repository.command.RequestID != "request-1" || repository.command.StrategyVersion != domain.StrategyDynamicV3 {
|
||||||
|
t.Fatalf("owner command=%+v", repository.command)
|
||||||
|
}
|
||||||
|
if resp.GetPool().GetStrategyVersion() != domain.StrategyDynamicV3 || resp.GetPool().GetManualCreditTotal() != 100 || resp.GetAdjustment().GetBalanceAfter() != 100 {
|
||||||
|
t.Fatalf("RPC response=%+v", resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdjustLuckyGiftPoolBalanceRejectsMissingAppCodeBeforeOwnerCall(t *testing.T) {
|
||||||
|
server := NewAdminLuckyGiftServer(nil)
|
||||||
|
_, err := server.AdjustLuckyGiftPoolBalance(context.Background(), &luckygiftv1.AdjustLuckyGiftPoolBalanceRequest{
|
||||||
|
Meta: &luckygiftv1.RequestMeta{}, PoolId: "lucky", StrategyVersion: domain.StrategyDynamicV3,
|
||||||
|
AdjustmentId: "adjustment-1", Direction: domain.PoolAdjustmentIn, AmountCoins: 1, Reason: "test", OperatorAdminId: 7,
|
||||||
|
})
|
||||||
|
if status.Code(err) != codes.InvalidArgument {
|
||||||
|
t.Fatalf("missing app_code error=%v, want InvalidArgument", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user