237 lines
8.7 KiB
Go
237 lines
8.7 KiB
Go
package app
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"time"
|
||
|
||
"hyapp/pkg/appcode"
|
||
"hyapp/pkg/rocketmqx"
|
||
"hyapp/pkg/roommq"
|
||
"hyapp/pkg/usermq"
|
||
"hyapp/pkg/walletmq"
|
||
"hyapp/services/activity-service/internal/config"
|
||
broadcastservice "hyapp/services/activity-service/internal/service/broadcast"
|
||
)
|
||
|
||
func buildMQConsumers(cfg config.Config, services *serviceBundle) ([]*rocketmqx.Consumer, error) {
|
||
mqConsumers := make([]*rocketmqx.Consumer, 0, 4)
|
||
if cfg.RocketMQ.RoomOutbox.Enabled {
|
||
consumer, err := newRoomOutboxConsumer(cfg, services)
|
||
if err != nil {
|
||
shutdownConsumers(mqConsumers)
|
||
return nil, err
|
||
}
|
||
mqConsumers = append(mqConsumers, consumer)
|
||
}
|
||
if cfg.RocketMQ.UserOutbox.Enabled && !services.broadcastPublisherAvailable {
|
||
shutdownConsumers(mqConsumers)
|
||
return nil, errors.New("rocketmq user_outbox consumer requires tencent_im.enabled")
|
||
}
|
||
if cfg.RocketMQ.UserOutbox.Enabled {
|
||
consumer, err := newUserOutboxConsumer(cfg, services.broadcast)
|
||
if err != nil {
|
||
shutdownConsumers(mqConsumers)
|
||
return nil, err
|
||
}
|
||
mqConsumers = append(mqConsumers, consumer)
|
||
}
|
||
if cfg.FirstRechargeRewardWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled {
|
||
consumer, err := newFirstRechargeWalletConsumer(cfg, services)
|
||
if err != nil {
|
||
shutdownConsumers(mqConsumers)
|
||
return nil, err
|
||
}
|
||
mqConsumers = append(mqConsumers, consumer)
|
||
}
|
||
if cfg.CumulativeRechargeRewardWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled {
|
||
consumer, err := newCumulativeRechargeWalletConsumer(cfg, services)
|
||
if err != nil {
|
||
shutdownConsumers(mqConsumers)
|
||
return nil, err
|
||
}
|
||
mqConsumers = append(mqConsumers, consumer)
|
||
}
|
||
if cfg.RedPacketBroadcastWorker.Enabled && cfg.RocketMQ.WalletOutbox.Enabled {
|
||
consumer, err := newRedPacketWalletConsumer(cfg, services)
|
||
if err != nil {
|
||
shutdownConsumers(mqConsumers)
|
||
return nil, err
|
||
}
|
||
mqConsumers = append(mqConsumers, consumer)
|
||
}
|
||
return mqConsumers, nil
|
||
}
|
||
|
||
func newRoomOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
|
||
consumer, err := rocketmqx.NewConsumer(roomOutboxConsumerConfig(cfg.RocketMQ))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err := consumer.Subscribe(cfg.RocketMQ.RoomOutbox.Topic, roommq.TagRoomOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
|
||
envelope, _, err := roommq.DecodeRoomOutboxMessage(message.Body)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
// 一个 room outbox envelope 代表房间状态已经提交;下游模块各自写幂等消费表。
|
||
// 任何模块失败都返回错误给 MQ,让同一个 envelope 重试,避免“播报成功但活动漏计分”的半完成状态。
|
||
eventCtx := appcode.WithContext(ctx, envelope.GetAppCode())
|
||
if _, err := services.broadcast.HandleRoomEvent(eventCtx, envelope); err != nil {
|
||
return err
|
||
}
|
||
if _, err := services.growth.HandleRoomEvent(eventCtx, envelope); err != nil {
|
||
return err
|
||
}
|
||
if _, err := services.weeklyStar.HandleRoomEvent(eventCtx, envelope); err != nil {
|
||
return err
|
||
}
|
||
_, err = services.roomTurnoverReward.HandleRoomEvent(eventCtx, envelope)
|
||
return err
|
||
}); err != nil {
|
||
_ = consumer.Shutdown()
|
||
return nil, err
|
||
}
|
||
return consumer, nil
|
||
}
|
||
|
||
func newUserOutboxConsumer(cfg config.Config, broadcastSvc *broadcastservice.Service) (*rocketmqx.Consumer, error) {
|
||
consumer, err := rocketmqx.NewConsumer(userOutboxConsumerConfig(cfg.RocketMQ))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err := consumer.Subscribe(cfg.RocketMQ.UserOutbox.Topic, usermq.TagUserOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
|
||
outboxMessage, err := usermq.DecodeUserOutboxMessage(message.Body)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return consumeUserRegionChangedForBroadcast(ctx, broadcastSvc, outboxMessage)
|
||
}); err != nil {
|
||
_ = consumer.Shutdown()
|
||
return nil, err
|
||
}
|
||
return consumer, nil
|
||
}
|
||
|
||
func newFirstRechargeWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
|
||
consumer, err := rocketmqx.NewConsumer(walletOutboxConsumerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.FirstRechargeConsumerGroup))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
|
||
event, ok, err := firstRechargeEventFromWalletMessage(message.Body)
|
||
if err != nil || !ok {
|
||
return err
|
||
}
|
||
return services.firstRechargeReward.ConsumeWalletRechargeEvent(appcode.WithContext(ctx, event.AppCode), event)
|
||
}); err != nil {
|
||
_ = consumer.Shutdown()
|
||
return nil, err
|
||
}
|
||
return consumer, nil
|
||
}
|
||
|
||
func newCumulativeRechargeWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
|
||
// 累充奖励使用独立 consumer group,避免和首充奖励共享消费位点后互相影响重放和排查。
|
||
consumer, err := rocketmqx.NewConsumer(walletOutboxConsumerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.CumulativeRechargeConsumerGroup))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
|
||
event, ok, err := cumulativeRechargeEventFromWalletMessage(message.Body)
|
||
if err != nil || !ok {
|
||
return err
|
||
}
|
||
return services.cumulativeRecharge.ConsumeWalletRechargeEvent(appcode.WithContext(ctx, event.AppCode), event)
|
||
}); err != nil {
|
||
_ = consumer.Shutdown()
|
||
return nil, err
|
||
}
|
||
return consumer, nil
|
||
}
|
||
|
||
func newRedPacketWalletConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) {
|
||
consumer, err := rocketmqx.NewConsumer(walletOutboxConsumerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.RedPacketBroadcastConsumerGroup))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err := consumer.Subscribe(redPacketWalletOutboxTopic(cfg.RocketMQ.WalletOutbox), walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error {
|
||
event, ok, err := redPacketEventFromWalletMessage(message.Body)
|
||
if err != nil || !ok {
|
||
return err
|
||
}
|
||
return services.broadcast.ConsumeRedPacketWalletEvent(appcode.WithContext(ctx, event.AppCode), event)
|
||
}); err != nil {
|
||
_ = consumer.Shutdown()
|
||
return nil, err
|
||
}
|
||
return consumer, nil
|
||
}
|
||
|
||
func roomOutboxConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig {
|
||
return rocketMQConsumerConfig(cfg, cfg.RoomOutbox.ConsumerGroup, cfg.RoomOutbox.ConsumerMaxReconsumeTimes)
|
||
}
|
||
|
||
func userOutboxConsumerConfig(cfg config.RocketMQConfig) rocketmqx.ConsumerConfig {
|
||
return rocketMQConsumerConfig(cfg, cfg.UserOutbox.ConsumerGroup, cfg.UserOutbox.ConsumerMaxReconsumeTimes)
|
||
}
|
||
|
||
func walletOutboxConsumerConfig(cfg config.RocketMQConfig, group string) rocketmqx.ConsumerConfig {
|
||
return rocketMQConsumerConfig(cfg, group, cfg.WalletOutbox.ConsumerMaxReconsumeTimes)
|
||
}
|
||
|
||
func redPacketWalletOutboxTopic(cfg config.WalletOutboxMQConfig) string {
|
||
// 钱包实时 topic 显式配置后,红包 worker 只消费实时通道,避免继续被普通账务 topic 的高频消息拖慢。
|
||
if cfg.RealtimeTopic != "" {
|
||
return cfg.RealtimeTopic
|
||
}
|
||
return cfg.Topic
|
||
}
|
||
|
||
func rocketMQConsumerConfig(cfg config.RocketMQConfig, group string, maxReconsume int32) rocketmqx.ConsumerConfig {
|
||
return rocketmqx.ConsumerConfig{
|
||
EndpointConfig: rocketmqx.EndpointConfig{
|
||
NameServers: cfg.NameServers,
|
||
NameServerDomain: cfg.NameServerDomain,
|
||
AccessKey: cfg.AccessKey,
|
||
SecretKey: cfg.SecretKey,
|
||
SecurityToken: cfg.SecurityToken,
|
||
Namespace: cfg.Namespace,
|
||
VIPChannel: cfg.VIPChannel,
|
||
},
|
||
GroupName: group,
|
||
MaxReconsumeTimes: maxReconsume,
|
||
ConsumeRetryDelay: time.Second,
|
||
ConsumePullBatch: 32,
|
||
ConsumePullTimeout: 15 * time.Minute,
|
||
}
|
||
}
|
||
|
||
func shutdownConsumers(consumers []*rocketmqx.Consumer) {
|
||
for _, consumer := range consumers {
|
||
_ = consumer.Shutdown()
|
||
}
|
||
}
|
||
|
||
type userRegionChangedPayload struct {
|
||
UserID int64 `json:"user_id"`
|
||
OldRegionID int64 `json:"old_region_id"`
|
||
NewRegionID int64 `json:"new_region_id"`
|
||
}
|
||
|
||
func consumeUserRegionChangedForBroadcast(ctx context.Context, service *broadcastservice.Service, message usermq.UserOutboxMessage) error {
|
||
if message.EventType != "UserRegionChanged" {
|
||
return nil
|
||
}
|
||
var payload userRegionChangedPayload
|
||
if err := json.Unmarshal([]byte(message.PayloadJSON), &payload); err != nil {
|
||
return err
|
||
}
|
||
if payload.UserID <= 0 || payload.OldRegionID <= 0 || payload.OldRegionID == payload.NewRegionID {
|
||
return nil
|
||
}
|
||
// 区域广播群成员关系是当前读模型;用户切区后只移除旧群,下一次 UserSig 会按新 users.region_id 返回新群。
|
||
_, _, err := service.RemoveRegionBroadcastMember(appcode.WithContext(ctx, message.AppCode), payload.UserID, payload.OldRegionID)
|
||
return err
|
||
}
|