Allow zero coin game outbox events

This commit is contained in:
zhx 2026-06-12 01:34:39 +08:00
parent d2dbc88e5f
commit 1b8241c3d2
2 changed files with 90 additions and 24 deletions

View File

@ -40,18 +40,8 @@ type GameOutboxMessage struct {
// EncodeGameOutboxMessage serializes a game fact for fanout through RocketMQ.
func EncodeGameOutboxMessage(message GameOutboxMessage) ([]byte, error) {
message.MessageType = MessageTypeGameOutboxEvent
if strings.TrimSpace(message.AppCode) == "" ||
strings.TrimSpace(message.EventID) == "" ||
strings.TrimSpace(message.EventType) == "" ||
strings.TrimSpace(message.GameID) == "" ||
message.OccurredAtMS <= 0 {
return nil, errors.New("game outbox message is incomplete")
}
if message.EventType == EventTypeGameOrderSettled && (strings.TrimSpace(message.OrderID) == "" ||
message.UserID <= 0 ||
strings.TrimSpace(message.OpType) == "" ||
message.CoinAmount <= 0) {
return nil, errors.New("game order outbox message is incomplete")
if err := validateGameOutboxMessage(message); err != nil {
return nil, err
}
if strings.TrimSpace(message.PayloadJSON) == "" {
message.PayloadJSON = "{}"
@ -68,21 +58,30 @@ func DecodeGameOutboxMessage(body []byte) (GameOutboxMessage, error) {
if message.MessageType != MessageTypeGameOutboxEvent {
return GameOutboxMessage{}, errors.New("unexpected game outbox message_type")
}
if strings.TrimSpace(message.AppCode) == "" ||
strings.TrimSpace(message.EventID) == "" ||
strings.TrimSpace(message.EventType) == "" ||
strings.TrimSpace(message.GameID) == "" ||
message.OccurredAtMS <= 0 {
return GameOutboxMessage{}, errors.New("game outbox message is incomplete")
}
if message.EventType == EventTypeGameOrderSettled && (strings.TrimSpace(message.OrderID) == "" ||
message.UserID <= 0 ||
strings.TrimSpace(message.OpType) == "" ||
message.CoinAmount <= 0) {
return GameOutboxMessage{}, errors.New("game order outbox message is incomplete")
if err := validateGameOutboxMessage(message); err != nil {
return GameOutboxMessage{}, err
}
if strings.TrimSpace(message.PayloadJSON) == "" {
message.PayloadJSON = "{}"
}
return message, nil
}
func validateGameOutboxMessage(message GameOutboxMessage) error {
if strings.TrimSpace(message.AppCode) == "" ||
strings.TrimSpace(message.EventID) == "" ||
strings.TrimSpace(message.EventType) == "" ||
strings.TrimSpace(message.GameID) == "" ||
message.OccurredAtMS <= 0 {
return errors.New("game outbox message is incomplete")
}
if message.EventType == EventTypeGameOrderSettled && (strings.TrimSpace(message.OrderID) == "" ||
message.UserID <= 0 ||
strings.TrimSpace(message.OpType) == "" ||
message.CoinAmount < 0) {
// 厂商可能发送 0 金额派奖或 0 金额结算确认;这类订单是已落库事实,必须允许 MQ 投递。
// 负数仍然不是合法订单金额,扣减方向必须由 op_type=debit 表达,不能把符号塞进金额字段。
return errors.New("game order outbox message is incomplete")
}
return nil
}

View File

@ -0,0 +1,67 @@
package gamemq
import "testing"
func TestEncodeGameOutboxMessageAllowsZeroCoinGameOrder(t *testing.T) {
message := GameOutboxMessage{
AppCode: "lalu",
EventID: "GameOrderSettled:gord_zero_credit",
EventType: EventTypeGameOrderSettled,
OrderID: "gord_zero_credit",
UserID: 42,
PlatformCode: "zeeone",
GameID: "1021",
OpType: "credit",
CoinAmount: 0,
OccurredAtMS: 1781199149295,
}
body, err := EncodeGameOutboxMessage(message)
if err != nil {
t.Fatalf("zero coin game order should be publishable: %v", err)
}
decoded, err := DecodeGameOutboxMessage(body)
if err != nil {
t.Fatalf("zero coin game order should be decodable: %v", err)
}
if decoded.CoinAmount != 0 || decoded.PayloadJSON != "{}" {
t.Fatalf("decoded zero coin order mismatch: %+v", decoded)
}
}
func TestEncodeGameOutboxMessageRejectsNegativeGameOrderAmount(t *testing.T) {
_, err := EncodeGameOutboxMessage(GameOutboxMessage{
AppCode: "lalu",
EventID: "GameOrderSettled:gord_negative",
EventType: EventTypeGameOrderSettled,
OrderID: "gord_negative",
UserID: 42,
PlatformCode: "zeeone",
GameID: "1021",
OpType: "credit",
CoinAmount: -1,
OccurredAtMS: 1781199149295,
})
if err == nil {
t.Fatalf("negative game order amount should be rejected")
}
}
func TestEncodeGameOutboxMessageAllowsSelfGameEventWithoutOrderFields(t *testing.T) {
body, err := EncodeGameOutboxMessage(GameOutboxMessage{
AppCode: "lalu",
EventID: "SelfGameMatchSettled:match_1",
EventType: EventTypeSelfGameMatchSettled,
UserID: 42,
PlatformCode: "self",
GameID: "dice",
PayloadJSON: `{"match_id":"match_1"}`,
OccurredAtMS: 1781199149295,
})
if err != nil {
t.Fatalf("self game fact should not require order fields: %v", err)
}
if _, err := DecodeGameOutboxMessage(body); err != nil {
t.Fatalf("self game fact should decode: %v", err)
}
}