package app import ( "context" "encoding/json" "fmt" "strconv" "strings" "hyapp/pkg/appcode" "hyapp/pkg/rocketmqx" "hyapp/pkg/usermq" "hyapp/pkg/xerr" "hyapp/services/activity-service/internal/config" messageservice "hyapp/services/activity-service/internal/service/message" ) const userOutboxEventAgencyHostRemoved = "AgencyHostRemoved" type agencyHostRemovedUserSnapshot struct { UserID int64 `json:"user_id"` DisplayUserID string `json:"display_user_id"` Username string `json:"username"` } type agencyHostRemovedEvent struct { AppCode string EventID string MembershipID int64 AgencyID int64 HostUserID int64 Operator agencyHostRemovedUserSnapshot RemovedAtMS int64 } func newAgencyHostRemovedNoticeUserOutboxConsumer(cfg config.Config, services *serviceBundle) (*rocketmqx.Consumer, error) { consumer, err := rocketmqx.NewConsumer(userOutboxAgencyHostRemovedNoticeConsumerConfig(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 { event, ok, err := agencyHostRemovedEventFromUserMessage(message.Body) if err != nil || !ok { return err } return createAgencyHostRemovedSystemNotice(appcode.WithContext(ctx, event.AppCode), services.message, event) }); err != nil { _ = consumer.Shutdown() return nil, err } return consumer, nil } func agencyHostRemovedEventFromUserMessage(body []byte) (agencyHostRemovedEvent, bool, error) { message, err := usermq.DecodeUserOutboxMessage(body) if err != nil { return agencyHostRemovedEvent{}, false, err } if message.EventType != userOutboxEventAgencyHostRemoved { return agencyHostRemovedEvent{}, false, nil } var payload struct { MembershipID int64 `json:"membership_id"` AgencyID int64 `json:"agency_id"` HostUserID int64 `json:"host_user_id"` Operator agencyHostRemovedUserSnapshot `json:"operator"` RemovedAtMS int64 `json:"removed_at_ms"` } if err := json.Unmarshal([]byte(message.PayloadJSON), &payload); err != nil { // 目标事件损坏必须交回 MQ 重试并告警;确认消息会永久丢失主播可见的移除事实。 return agencyHostRemovedEvent{}, true, err } payload.Operator.DisplayUserID = strings.TrimSpace(payload.Operator.DisplayUserID) payload.Operator.Username = strings.TrimSpace(payload.Operator.Username) if payload.MembershipID <= 0 || payload.AgencyID <= 0 || payload.HostUserID <= 0 || payload.Operator.UserID <= 0 || payload.Operator.DisplayUserID == "" || payload.RemovedAtMS <= 0 || message.AggregateType != "agency_membership" || message.AggregateID != payload.MembershipID { return agencyHostRemovedEvent{}, true, xerr.New(xerr.InvalidArgument, "agency host removed event is invalid") } return agencyHostRemovedEvent{ AppCode: appcode.Normalize(message.AppCode), EventID: message.EventID, MembershipID: payload.MembershipID, AgencyID: payload.AgencyID, HostUserID: payload.HostUserID, Operator: payload.Operator, RemovedAtMS: payload.RemovedAtMS, }, true, nil } func createAgencyHostRemovedSystemNotice(ctx context.Context, service *messageservice.Service, event agencyHostRemovedEvent) error { if service == nil { return xerr.New(xerr.Unavailable, "message service is not configured") } // 昵称为空时只展示短号,避免形成双空格;正常资料同时展示“当前靓号/短 ID + 昵称”。 operatorLabel := strings.Join(nonEmptyStrings(event.Operator.DisplayUserID, event.Operator.Username), " ") content := fmt.Sprintf("你已被 %s agency移除", operatorLabel) _, err := service.CreateSystemNotice(ctx, messageservice.NoticeCommand{ TargetUserID: event.HostUserID, Producer: "user-service", ProducerEventID: event.EventID, ProducerEventType: userOutboxEventAgencyHostRemoved, AggregateType: "agency_membership", AggregateID: strconv.FormatInt(event.MembershipID, 10), TemplateID: "agency_host_removed", TemplateVersion: "v1", Title: "Agency移除通知", Summary: content, Body: content, Priority: 50, SentAtMS: event.RemovedAtMS, Metadata: map[string]any{ "agency_id": event.AgencyID, "membership_id": event.MembershipID, "operator_user_id": event.Operator.UserID, "operator_display_user_id": event.Operator.DisplayUserID, "operator_username": event.Operator.Username, "removed_at_ms": event.RemovedAtMS, }, }) return err } func nonEmptyStrings(values ...string) []string { items := make([]string, 0, len(values)) for _, value := range values { if value = strings.TrimSpace(value); value != "" { items = append(items, value) } } return items }