134 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package app
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"hyapp/pkg/appcode"
"hyapp/pkg/walletmq"
"hyapp/pkg/xerr"
messageservice "hyapp/services/activity-service/internal/service/message"
)
const vipDailyCoinRebateAvailableEventType = "VipDailyCoinRebateAvailable"
// vipRebateAvailableEvent 是 wallet outbox 的逐用户返现资格快照。
// activity-service 只把这个已提交事实投影为 inbox资格、金额、过期和最终入账仍由 wallet owner 判定。
type vipRebateAvailableEvent struct {
AppCode string
EventID string
RebateID string
UserID int64
TaskDay string
VIPLevel int32
VIPName string
CoinAmount int64
AvailableAtMS int64
ExpiresAtMS int64
ConfigVersion int64
CreatedAtMS int64
}
func vipRebateAvailableEventFromWalletMessage(body []byte) (vipRebateAvailableEvent, bool, error) {
message, err := walletmq.DecodeWalletOutboxMessage(body)
if err != nil {
return vipRebateAvailableEvent{}, false, err
}
if message.EventType != vipDailyCoinRebateAvailableEventType {
return vipRebateAvailableEvent{}, false, nil
}
var payload struct {
RebateID string `json:"rebate_id"`
UserID int64 `json:"user_id"`
TaskDay string `json:"task_day"`
VIPLevel int32 `json:"vip_level"`
VIPName string `json:"vip_name"`
CoinAmount int64 `json:"coin_amount"`
AvailableAtMS int64 `json:"available_at_ms"`
ExpiresAtMS int64 `json:"expires_at_ms"`
ConfigVersion int64 `json:"config_version"`
CreatedAtMS int64 `json:"created_at_ms"`
}
if err := json.Unmarshal([]byte(message.PayloadJSON), &payload); err != nil {
// 目标事件 payload 损坏必须让 MQ 重投并进入告警,不能确认后永久丢失用户领取入口。
return vipRebateAvailableEvent{}, false, err
}
payload.RebateID = strings.TrimSpace(payload.RebateID)
payload.TaskDay = strings.TrimSpace(payload.TaskDay)
payload.VIPName = strings.TrimSpace(payload.VIPName)
if payload.CreatedAtMS <= 0 {
payload.CreatedAtMS = message.OccurredAtMS
}
if payload.RebateID == "" || payload.UserID <= 0 || payload.UserID != message.UserID || !validUTCTaskDay(payload.TaskDay) || payload.VIPLevel <= 0 || payload.CoinAmount <= 0 || payload.AvailableAtMS <= 0 || payload.ExpiresAtMS <= payload.AvailableAtMS || payload.ConfigVersion <= 0 {
return vipRebateAvailableEvent{}, false, xerr.New(xerr.InvalidArgument, "VIP rebate wallet event is invalid")
}
return vipRebateAvailableEvent{
AppCode: appcode.Normalize(message.AppCode),
EventID: message.EventID,
RebateID: payload.RebateID,
UserID: payload.UserID,
TaskDay: payload.TaskDay,
VIPLevel: payload.VIPLevel,
VIPName: payload.VIPName,
CoinAmount: payload.CoinAmount,
AvailableAtMS: payload.AvailableAtMS,
ExpiresAtMS: payload.ExpiresAtMS,
ConfigVersion: payload.ConfigVersion,
CreatedAtMS: payload.CreatedAtMS,
}, true, nil
}
func createVIPRebateSystemNotice(ctx context.Context, service *messageservice.Service, event vipRebateAvailableEvent) error {
if service == nil {
return xerr.New(xerr.Unavailable, "message service is not configured")
}
actionParam, err := json.Marshal(map[string]any{
"rebate_id": event.RebateID,
"task_day": event.TaskDay,
"coin_amount": event.CoinAmount,
"expires_at_ms": event.ExpiresAtMS,
})
if err != nil {
return err
}
summary := fmt.Sprintf("尊敬的VIP用户平台今日返现%d金币请及时领取。", event.CoinAmount)
_, err = service.CreateSystemNotice(ctx, messageservice.NoticeCommand{
TargetUserID: event.UserID,
Producer: "wallet-service",
ProducerEventID: event.EventID,
ProducerEventType: vipDailyCoinRebateAvailableEventType,
AggregateType: "vip_daily_coin_rebate",
AggregateID: event.RebateID,
TemplateID: "vip_daily_coin_rebate_available",
TemplateVersion: "v1",
Title: "VIP金币返现",
Summary: summary,
Body: summary,
ActionType: "vip_coin_rebate_claim",
ActionParam: string(actionParam),
Priority: 50,
SentAtMS: event.AvailableAtMS,
ExpireAtMS: event.ExpiresAtMS,
Metadata: map[string]any{
"rebate_id": event.RebateID,
"task_day": event.TaskDay,
"vip_level": event.VIPLevel,
"vip_name": event.VIPName,
"coin_amount": event.CoinAmount,
"available_at_ms": event.AvailableAtMS,
"expires_at_ms": event.ExpiresAtMS,
"config_version": event.ConfigVersion,
"claim_status": "claimable",
},
})
return err
}
func validUTCTaskDay(value string) bool {
parsed, err := time.Parse("2006-01-02", value)
return err == nil && parsed.Location() == time.UTC && parsed.Format("2006-01-02") == value
}