hyapp-server/pkg/gamemq/messages.go
2026-06-12 01:34:39 +08:00

88 lines
3.0 KiB
Go

// Package gamemq defines RocketMQ payloads owned by game-service.
package gamemq
import (
"encoding/json"
"errors"
"strings"
)
const (
MessageTypeGameOutboxEvent = "game_outbox_event"
TagGameOutboxEvent = "game_outbox_event"
EventTypeGameOrderSettled = "GameOrderSettled"
EventTypeSelfGameMatchCreated = "SelfGameMatchCreated"
EventTypeSelfGameMatchReady = "SelfGameMatchReady"
EventTypeSelfGameMatchSettled = "SelfGameMatchSettled"
EventTypeSelfGameMatchCanceled = "SelfGameMatchCanceled"
EventTypeSelfGameMatchFailed = "SelfGameMatchFailed"
EventTypeSelfGamePoolAdjusted = "SelfGamePoolAdjusted"
)
// GameOutboxMessage is the MQ representation of one committed game_outbox fact.
type GameOutboxMessage struct {
MessageType string `json:"message_type"`
AppCode string `json:"app_code"`
EventID string `json:"event_id"`
EventType string `json:"event_type"`
OrderID string `json:"order_id"`
UserID int64 `json:"user_id"`
PlatformCode string `json:"platform_code"`
GameID string `json:"game_id"`
OpType string `json:"op_type"`
CoinAmount int64 `json:"coin_amount"`
PayloadJSON string `json:"payload_json"`
OccurredAtMS int64 `json:"occurred_at_ms"`
}
// EncodeGameOutboxMessage serializes a game fact for fanout through RocketMQ.
func EncodeGameOutboxMessage(message GameOutboxMessage) ([]byte, error) {
message.MessageType = MessageTypeGameOutboxEvent
if err := validateGameOutboxMessage(message); err != nil {
return nil, err
}
if strings.TrimSpace(message.PayloadJSON) == "" {
message.PayloadJSON = "{}"
}
return json.Marshal(message)
}
// DecodeGameOutboxMessage validates the MQ body and restores the game fact.
func DecodeGameOutboxMessage(body []byte) (GameOutboxMessage, error) {
var message GameOutboxMessage
if err := json.Unmarshal(body, &message); err != nil {
return GameOutboxMessage{}, err
}
if message.MessageType != MessageTypeGameOutboxEvent {
return GameOutboxMessage{}, errors.New("unexpected game outbox message_type")
}
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
}