457 lines
18 KiB
Go
457 lines
18 KiB
Go
//go:build ignore
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"chatapp3-golang/internal/common"
|
|
"chatapp3-golang/internal/config"
|
|
"chatapp3-golang/internal/integration"
|
|
"chatapp3-golang/internal/model"
|
|
"chatapp3-golang/internal/service/voiceroomrocket"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
"gorm.io/driver/mysql"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type localGateway struct {
|
|
db *gorm.DB
|
|
goldEvents map[string]integration.GoldReceiptCommand
|
|
}
|
|
|
|
func (g localGateway) MapRoomProfiles(ctx context.Context, roomIDs []int64) (map[int64]integration.RoomProfile, error) {
|
|
result := make(map[int64]integration.RoomProfile, len(roomIDs))
|
|
for _, roomID := range roomIDs {
|
|
result[roomID] = integration.RoomProfile{
|
|
ID: integration.Int64Value(roomID),
|
|
RoomAccount: strconv.FormatInt(roomID, 10),
|
|
RoomName: "local-room-" + strconv.FormatInt(roomID, 10),
|
|
SysOrigin: "LIKEI",
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (g localGateway) GetUserProfile(ctx context.Context, userID int64) (integration.UserProfile, error) {
|
|
var user model.UserBaseInfo
|
|
if err := g.db.WithContext(ctx).Where("id = ?", userID).First(&user).Error; err != nil {
|
|
return integration.UserProfile{}, err
|
|
}
|
|
return integration.UserProfile{
|
|
ID: integration.Int64Value(user.ID),
|
|
Account: user.Account,
|
|
UserAvatar: user.UserAvatar,
|
|
UserNickname: user.UserNickname,
|
|
CountryCode: user.CountryCode,
|
|
CountryName: user.CountryName,
|
|
OriginSys: user.OriginSys,
|
|
}, nil
|
|
}
|
|
|
|
func (g localGateway) ExistsGoldEvent(ctx context.Context, eventID string) (bool, error) {
|
|
_, ok := g.goldEvents[eventID]
|
|
return ok, nil
|
|
}
|
|
|
|
func (g localGateway) ChangeGoldBalance(ctx context.Context, cmd integration.GoldReceiptCommand) error {
|
|
g.goldEvents[cmd.EventID] = cmd
|
|
return nil
|
|
}
|
|
|
|
func (g localGateway) GivePropsBackpack(ctx context.Context, req integration.GivePropsBackpackRequest) error {
|
|
return nil
|
|
}
|
|
|
|
func (g localGateway) GetNobleVIPAbility(ctx context.Context, sourceID int64) (integration.PropsNobleVIPAbility, error) {
|
|
return integration.PropsNobleVIPAbility{}, nil
|
|
}
|
|
|
|
func (g localGateway) SwitchUseProps(ctx context.Context, userID int64, propsID int64) error {
|
|
return nil
|
|
}
|
|
|
|
func (g localGateway) ActivateTemporaryBadge(ctx context.Context, userID int64, badgeID int64, days int) error {
|
|
return nil
|
|
}
|
|
|
|
func (g localGateway) RemoveUserProfileCacheAll(ctx context.Context, userID int64) error {
|
|
return nil
|
|
}
|
|
|
|
type sampleRoom struct {
|
|
RoomID int64
|
|
UserID int64
|
|
}
|
|
|
|
type verifySummary struct {
|
|
DSNHost string `json:"dsnHost"`
|
|
RedisAddr string `json:"redisAddr"`
|
|
RoomID int64 `json:"roomId,string"`
|
|
Top1UserID int64 `json:"top1UserId,string"`
|
|
IgniteUserID int64 `json:"igniteUserId,string"`
|
|
DayKey string `json:"dayKey"`
|
|
FirstAcceptedEnergy int64 `json:"firstAcceptedEnergy"`
|
|
SecondRawEnergy int64 `json:"secondRawEnergy"`
|
|
SecondAcceptedEnergy int64 `json:"secondAcceptedEnergy"`
|
|
LaunchNo string `json:"launchNo"`
|
|
StatusLevel int `json:"statusLevel"`
|
|
StatusEnergy int64 `json:"statusEnergy"`
|
|
KingRecords int `json:"kingRecords"`
|
|
Top1PopupBeforeAck int `json:"top1PopupBeforeAck"`
|
|
Top1PopupAfterAck int `json:"top1PopupAfterAck"`
|
|
IgnitePopupCount int `json:"ignitePopupCount"`
|
|
LeftUserID int64 `json:"leftUserId,string"`
|
|
LeftUserSnapshotCount int64 `json:"leftUserSnapshotCount"`
|
|
LeftUserRewardCount int64 `json:"leftUserRewardCount"`
|
|
RewardRecordCount int64 `json:"rewardRecordCount"`
|
|
SnapshotCount int64 `json:"snapshotCount"`
|
|
SelectedAudienceCount int64 `json:"selectedAudienceCount"`
|
|
GrantedRewardCount int64 `json:"grantedRewardCount"`
|
|
PendingRewardCount int64 `json:"pendingRewardCount"`
|
|
GoldGrantCount int `json:"goldGrantCount"`
|
|
BroadcastEventCount int64 `json:"broadcastEventCount"`
|
|
MQEnvelopeVerified bool `json:"mqEnvelopeVerified"`
|
|
DuplicateReason string `json:"duplicateReason"`
|
|
}
|
|
|
|
func main() {
|
|
ctx := context.Background()
|
|
dsn := env("CHATAPP_STORE_MYSQL_DSN", "root:123456@tcp(127.0.0.1:13306)/likei?charset=utf8mb4&parseTime=True&loc=Asia%2FRiyadh")
|
|
redisAddr := env("CHATAPP_STORE_REDIS_ADDR", "127.0.0.1:16379")
|
|
|
|
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
|
must(err, "connect mysql")
|
|
sqlDB, err := db.DB()
|
|
must(err, "unwrap mysql")
|
|
defer sqlDB.Close()
|
|
must(sqlDB.PingContext(ctx), "ping mysql")
|
|
|
|
rdb := redis.NewClient(&redis.Options{Addr: redisAddr})
|
|
defer rdb.Close()
|
|
must(rdb.Ping(ctx).Err(), "ping redis")
|
|
|
|
room := pickRoom(ctx, db)
|
|
otherUsers := pickUsers(ctx, db, room.UserID, 3)
|
|
if room.RoomID <= 0 || room.UserID <= 0 || len(otherUsers) < 3 {
|
|
log.Fatalf("missing real room/user sample: room=%+v otherUsers=%v", room, otherUsers)
|
|
}
|
|
igniteUserID := otherUsers[0]
|
|
|
|
cfg := config.Config{
|
|
VoiceRoomRocket: config.VoiceRoomRocketConfig{
|
|
DefaultSysOrigin: "LIKEI",
|
|
Timezone: "Asia/Riyadh",
|
|
InRoomRewardDueZSetKey: "voice_room:rocket:in_room_due",
|
|
BroadcastStreamKey: "voice_room:rocket:broadcast",
|
|
RoomLockTTLSeconds: 10,
|
|
},
|
|
}
|
|
gateway := localGateway{db: db, goldEvents: map[string]integration.GoldReceiptCommand{}}
|
|
service := voiceroomrocket.NewService(cfg, db, rdb, gateway)
|
|
user := common.AuthUser{UserID: room.UserID, SysOrigin: "LIKEI"}
|
|
igniteUser := common.AuthUser{UserID: igniteUserID, SysOrigin: "LIKEI"}
|
|
|
|
now := time.Now()
|
|
day := now.In(time.FixedZone("UTC+3", 3*60*60)).Format("20060102")
|
|
cleanup(ctx, db, rdb, room.RoomID, day)
|
|
seedConfig(ctx, service, db)
|
|
seedPresence(ctx, rdb, room.RoomID, append([]int64{room.UserID, igniteUserID}, otherUsers[1:]...))
|
|
leftUserID := otherUsers[2]
|
|
must(rdb.Del(ctx,
|
|
fmt.Sprintf("voice_room:user_room:LIKEI:%d", leftUserID),
|
|
fmt.Sprintf("voice_room:user_seen:LIKEI:%d", leftUserID),
|
|
).Err(), "simulate left user presence")
|
|
|
|
initial, err := service.GetStatus(ctx, user, room.RoomID)
|
|
must(err, "initial status")
|
|
assert(initial.CurrentLevel == 1 && initial.CurrentEnergy == 0 && initial.NeedEnergy == 100, "initial status mismatch")
|
|
|
|
firstPayload := giftPayload("LOCAL_VERIFY_TRACK_1", room.UserID, room.RoomID, 60, now)
|
|
first, err := service.ProcessGiftPayload(ctx, firstPayload)
|
|
must(err, "process first gift")
|
|
assert(first.Processed && first.AcceptedEnergy == 60 && !first.Launched, "first gift result mismatch")
|
|
|
|
mid, err := service.GetStatus(ctx, user, room.RoomID)
|
|
must(err, "mid status")
|
|
assert(mid.CurrentLevel == 1 && mid.CurrentEnergy == 60 && mid.DisplayPercent == 60, "mid status mismatch")
|
|
|
|
secondPayload := mqEnvelope(giftPayload("LOCAL_VERIFY_TRACK_2", igniteUserID, room.RoomID, 80, now.Add(time.Second)))
|
|
second, err := service.ProcessGiftPayload(ctx, secondPayload)
|
|
must(err, "process second gift")
|
|
assert(second.Processed && second.AcceptedEnergy == 40 && !second.Launched, "second gift result mismatch")
|
|
|
|
launched, err := service.ProcessDueLaunches(ctx, now.Add(11*time.Second), 10)
|
|
must(err, "process due launches")
|
|
assert(launched == 1, "due launch count mismatch")
|
|
var launch model.VoiceRoomRocketLaunch
|
|
must(db.WithContext(ctx).
|
|
Where("room_id = ? AND day_key = ?", room.RoomID, day).
|
|
Order("create_time DESC").
|
|
First(&launch).Error, "load launch")
|
|
assert(strings.TrimSpace(launch.LaunchNo) != "", "launch no missing")
|
|
|
|
finalStatus, err := service.GetStatus(ctx, user, room.RoomID)
|
|
must(err, "final status")
|
|
assert(finalStatus.CurrentLevel == 2 && finalStatus.CurrentEnergy == 0 && finalStatus.NeedEnergy == 200, "final status mismatch")
|
|
|
|
king, err := service.GetKingRanking(ctx, user, room.RoomID, 1, 1, 1, 100)
|
|
must(err, "king ranking")
|
|
assert(len(king.Records) >= 2, "king records missing")
|
|
assert(king.Records[0].UserID == room.UserID && king.Records[0].ScoreEnergy == 60, "top1 ranking mismatch")
|
|
assert(king.Records[1].UserID == igniteUserID && king.Records[1].ScoreEnergy == 40, "ignite ranking mismatch")
|
|
|
|
top1Popups, err := service.ListRewardPopups(ctx, user, room.RoomID)
|
|
must(err, "top1 popups")
|
|
assert(len(top1Popups.Records) >= 1, "top1 popup count mismatch")
|
|
ignitePopups, err := service.ListRewardPopups(ctx, igniteUser, room.RoomID)
|
|
must(err, "ignite popups")
|
|
assert(len(ignitePopups.Records) >= 1, "ignite popup count mismatch")
|
|
|
|
ackIDs := make([]voiceroomrocket.FlexibleInt64, 0, len(top1Popups.Records))
|
|
for _, record := range top1Popups.Records {
|
|
ackIDs = append(ackIDs, voiceroomrocket.FlexibleInt64(record.ID))
|
|
}
|
|
must(service.AckRewardPopups(ctx, user, voiceroomrocket.AckRewardPopupsRequest{
|
|
RewardRecordIDs: ackIDs,
|
|
}), "ack top1 popup")
|
|
top1AfterAck, err := service.ListRewardPopups(ctx, user, room.RoomID)
|
|
must(err, "top1 popups after ack")
|
|
assert(len(top1AfterAck.Records) == 0, "top1 popup ack mismatch")
|
|
|
|
rewardRecords, err := service.PageRewardRecords(ctx, user, room.RoomID, 1, 20)
|
|
must(err, "top1 reward records")
|
|
assert(len(rewardRecords.Records) >= 1, "top1 reward record count mismatch")
|
|
|
|
dueProcessed, err := service.ProcessDueInRoomRewards(ctx, now.Add(30*time.Second), 10)
|
|
must(err, "process due in-room rewards")
|
|
assert(dueProcessed == 1, "due in-room reward count mismatch")
|
|
|
|
granted, err := service.ProcessPendingRewards(ctx, 100)
|
|
must(err, "process pending rewards")
|
|
assert(granted >= 5, "granted reward count mismatch")
|
|
|
|
duplicate, err := service.ProcessGiftPayload(ctx, secondPayload)
|
|
must(err, "process duplicate gift")
|
|
assert(duplicate.Reason == "duplicate_event", "duplicate reason mismatch")
|
|
|
|
var rewardCount int64
|
|
must(db.WithContext(ctx).Model(&model.VoiceRoomRocketRewardRecord{}).
|
|
Where("room_id = ? AND day_key = ?", room.RoomID, day).
|
|
Count(&rewardCount).Error, "count rewards")
|
|
var snapshotCount int64
|
|
must(db.WithContext(ctx).Model(&model.VoiceRoomRocketAudienceSnapshot{}).
|
|
Where("room_id = ?", room.RoomID).
|
|
Count(&snapshotCount).Error, "count snapshots")
|
|
assert(snapshotCount == 3, "snapshot count mismatch")
|
|
var selectedCount int64
|
|
must(db.WithContext(ctx).Model(&model.VoiceRoomRocketAudienceSnapshot{}).
|
|
Where("room_id = ? AND selected = ?", room.RoomID, true).
|
|
Count(&selectedCount).Error, "count selected snapshots")
|
|
assert(selectedCount == 3, "selected snapshot count mismatch")
|
|
var leftSnapshotCount int64
|
|
must(db.WithContext(ctx).Model(&model.VoiceRoomRocketAudienceSnapshot{}).
|
|
Where("room_id = ? AND user_id = ?", room.RoomID, leftUserID).
|
|
Count(&leftSnapshotCount).Error, "count left user snapshots")
|
|
assert(leftSnapshotCount == 0, "left user snapshot mismatch")
|
|
var leftRewardCount int64
|
|
must(db.WithContext(ctx).Model(&model.VoiceRoomRocketRewardRecord{}).
|
|
Where("room_id = ? AND day_key = ? AND user_id = ?", room.RoomID, day, leftUserID).
|
|
Count(&leftRewardCount).Error, "count left user rewards")
|
|
assert(leftRewardCount == 0, "left user reward mismatch")
|
|
var successCount int64
|
|
must(db.WithContext(ctx).Model(&model.VoiceRoomRocketRewardRecord{}).
|
|
Where("room_id = ? AND day_key = ? AND grant_status = ?", room.RoomID, day, "SUCCESS").
|
|
Count(&successCount).Error, "count granted rewards")
|
|
var pendingCount int64
|
|
must(db.WithContext(ctx).Model(&model.VoiceRoomRocketRewardRecord{}).
|
|
Where("room_id = ? AND day_key = ? AND grant_status <> ?", room.RoomID, day, "SUCCESS").
|
|
Count(&pendingCount).Error, "count pending rewards")
|
|
broadcastCount, err := rdb.XLen(ctx, "voice_room:rocket:broadcast").Result()
|
|
must(err, "count broadcast stream")
|
|
|
|
summary := verifySummary{
|
|
DSNHost: "127.0.0.1:13306/likei",
|
|
RedisAddr: redisAddr,
|
|
RoomID: room.RoomID,
|
|
Top1UserID: room.UserID,
|
|
IgniteUserID: igniteUserID,
|
|
DayKey: day,
|
|
FirstAcceptedEnergy: first.AcceptedEnergy,
|
|
SecondRawEnergy: 80,
|
|
SecondAcceptedEnergy: second.AcceptedEnergy,
|
|
LaunchNo: launch.LaunchNo,
|
|
StatusLevel: finalStatus.CurrentLevel,
|
|
StatusEnergy: finalStatus.CurrentEnergy,
|
|
KingRecords: len(king.Records),
|
|
Top1PopupBeforeAck: len(top1Popups.Records),
|
|
Top1PopupAfterAck: len(top1AfterAck.Records),
|
|
IgnitePopupCount: len(ignitePopups.Records),
|
|
LeftUserID: leftUserID,
|
|
LeftUserSnapshotCount: leftSnapshotCount,
|
|
LeftUserRewardCount: leftRewardCount,
|
|
RewardRecordCount: rewardCount,
|
|
SnapshotCount: snapshotCount,
|
|
SelectedAudienceCount: selectedCount,
|
|
GrantedRewardCount: successCount,
|
|
PendingRewardCount: pendingCount,
|
|
GoldGrantCount: len(gateway.goldEvents),
|
|
BroadcastEventCount: broadcastCount,
|
|
MQEnvelopeVerified: true,
|
|
DuplicateReason: duplicate.Reason,
|
|
}
|
|
encoded, err := json.MarshalIndent(summary, "", " ")
|
|
must(err, "marshal summary")
|
|
fmt.Println(string(encoded))
|
|
}
|
|
|
|
func pickRoom(ctx context.Context, db *gorm.DB) sampleRoom {
|
|
var row sampleRoom
|
|
_ = db.WithContext(ctx).
|
|
Table("room_member").
|
|
Select("room_id, user_id").
|
|
Where("sys_origin = ? AND room_id > 0 AND user_id > 0", "LIKEI").
|
|
Order("update_time DESC").
|
|
Limit(1).
|
|
Scan(&row).Error
|
|
return row
|
|
}
|
|
|
|
func pickUsers(ctx context.Context, db *gorm.DB, excludeUserID int64, limit int) []int64 {
|
|
var users []model.UserBaseInfo
|
|
_ = db.WithContext(ctx).
|
|
Where("origin_sys = ? AND id <> ?", "LIKEI", excludeUserID).
|
|
Order("id DESC").
|
|
Limit(limit).
|
|
Find(&users).Error
|
|
result := make([]int64, 0, len(users))
|
|
for _, user := range users {
|
|
if user.ID > 0 {
|
|
result = append(result, user.ID)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func seedPresence(ctx context.Context, rdb *redis.Client, roomID int64, users []int64) {
|
|
key := fmt.Sprintf("voice_room:online:LIKEI:%d", roomID)
|
|
for _, userID := range users {
|
|
must(rdb.SAdd(ctx, key, userID).Err(), "seed online room")
|
|
must(rdb.Set(ctx, fmt.Sprintf("voice_room:user_room:LIKEI:%d", userID), strconv.FormatInt(roomID, 10), time.Minute).Err(), "seed user room")
|
|
must(rdb.Set(ctx, fmt.Sprintf("voice_room:user_seen:LIKEI:%d", userID), strconv.FormatInt(time.Now().UnixMilli(), 10), time.Minute).Err(), "seed user seen")
|
|
}
|
|
must(rdb.Expire(ctx, key, time.Minute).Err(), "expire online room")
|
|
}
|
|
|
|
func cleanup(ctx context.Context, db *gorm.DB, rdb *redis.Client, roomID int64, day string) {
|
|
tables := []string{
|
|
"voice_room_rocket_reward_record",
|
|
"voice_room_rocket_launch",
|
|
"voice_room_rocket_gift_event_log",
|
|
"voice_room_rocket_contribution",
|
|
"voice_room_rocket_level_round",
|
|
"voice_room_rocket_status",
|
|
}
|
|
for _, table := range tables {
|
|
must(db.WithContext(ctx).Exec("DELETE FROM "+table+" WHERE sys_origin = ? AND room_id = ? AND day_key = ?", "LIKEI", roomID, day).Error, "cleanup "+table)
|
|
}
|
|
must(db.WithContext(ctx).Exec("DELETE FROM voice_room_rocket_audience_snapshot WHERE sys_origin = ? AND room_id = ?", "LIKEI", roomID).Error, "cleanup voice_room_rocket_audience_snapshot")
|
|
must(db.WithContext(ctx).Exec("DELETE FROM voice_room_rocket_reward_config WHERE sys_origin = ? AND reward_name LIKE ?", "LIKEI", "Local Verify%").Error, "cleanup reward config")
|
|
for level := 1; level <= 6; level++ {
|
|
key := fmt.Sprintf("voice_room:rocket:rank:LIKEI:%d:%s:1:%d", roomID, day, level)
|
|
_ = rdb.Del(ctx, key).Err()
|
|
}
|
|
_ = rdb.Del(ctx, fmt.Sprintf("voice_room:rocket:lock:LIKEI:%d", roomID)).Err()
|
|
_ = rdb.Del(ctx, fmt.Sprintf("voice_room:online:LIKEI:%d", roomID)).Err()
|
|
_ = rdb.Del(ctx, "voice_room:rocket:in_room_due").Err()
|
|
_ = rdb.Del(ctx, "voice_room:rocket:broadcast").Err()
|
|
}
|
|
|
|
func seedConfig(ctx context.Context, service *voiceroomrocket.Service, db *gorm.DB) {
|
|
_, err := service.SaveAdminConfig(ctx, voiceroomrocket.SaveConfigRequest{
|
|
SysOrigin: "LIKEI",
|
|
Enabled: true,
|
|
Timezone: "Asia/Riyadh",
|
|
MaxLevel: 6,
|
|
RankingTopLimit: 100,
|
|
RewardRecordKeepDays: 35,
|
|
BroadcastDurationSeconds: 7,
|
|
InRoomRewardDelaySeconds: 1,
|
|
RuleText: json.RawMessage(`{"zh":"本地验证规则"}`),
|
|
})
|
|
must(err, "save config")
|
|
|
|
levels := make([]voiceroomrocket.LevelConfigPayload, 0, 6)
|
|
for i, need := range []int64{100, 200, 300, 400, 500, 600} {
|
|
levels = append(levels, voiceroomrocket.LevelConfigPayload{
|
|
Level: i + 1,
|
|
NeedEnergy: need,
|
|
ShakeThresholdPercent: "95.0000",
|
|
Enabled: true,
|
|
Sort: i + 1,
|
|
})
|
|
}
|
|
_, err = service.SaveLevelConfigs(ctx, voiceroomrocket.SaveLevelConfigsRequest{SysOrigin: "LIKEI", Levels: levels})
|
|
must(err, "save levels")
|
|
|
|
_, err = service.SaveRewardConfigs(ctx, voiceroomrocket.SaveRewardConfigsRequest{
|
|
SysOrigin: "LIKEI",
|
|
Rewards: []voiceroomrocket.RewardConfigPayload{
|
|
{Level: 1, RewardScene: "TOP1", RewardType: "GOLD", RewardName: "Local Verify Top1 Gold", RewardAmount: 10, Enabled: true, Sort: 1},
|
|
{Level: 1, RewardScene: "IGNITE", RewardType: "GOLD", RewardName: "Local Verify Ignite Gold", RewardAmount: 5, Enabled: true, Sort: 1},
|
|
{Level: 1, RewardScene: "IN_ROOM", RewardType: "GOLD", RewardName: "Local Verify In Room Gold", RewardAmount: 1, RewardCopies: 3, Enabled: true, Sort: 1},
|
|
},
|
|
})
|
|
must(err, "save rewards")
|
|
|
|
var count int64
|
|
must(db.WithContext(ctx).Model(&model.VoiceRoomRocketLevelConfig{}).Where("sys_origin = ? AND enabled = ?", "LIKEI", true).Count(&count).Error, "count levels")
|
|
assert(count >= 6, "level config missing")
|
|
}
|
|
|
|
func giftPayload(trackID string, sendUserID int64, roomID int64, gold int64, at time.Time) string {
|
|
return fmt.Sprintf(`{
|
|
"trackId": %q,
|
|
"sysOrigin": "LIKEI",
|
|
"sendUserId": %q,
|
|
"roomId": %q,
|
|
"quantity": 1,
|
|
"giftConfig": {"id": "90001", "giftCandy": %d, "type": "GOLD", "giftTab": "NORMAL"},
|
|
"createTime": %d
|
|
}`, trackID, strconv.FormatInt(sendUserID, 10), strconv.FormatInt(roomID, 10), gold, at.UnixMilli())
|
|
}
|
|
|
|
func mqEnvelope(body string) string {
|
|
encodedBody, err := json.Marshal(body)
|
|
must(err, "marshal mq body")
|
|
return fmt.Sprintf(`{"tag":"give_gift_v3","body":%s}`, string(encodedBody))
|
|
}
|
|
|
|
func env(key, fallback string) string {
|
|
value := strings.TrimSpace(os.Getenv(key))
|
|
if value == "" {
|
|
return fallback
|
|
}
|
|
return value
|
|
}
|
|
|
|
func must(err error, label string) {
|
|
if err != nil {
|
|
log.Fatalf("%s: %v", label, err)
|
|
}
|
|
}
|
|
|
|
func assert(ok bool, message string) {
|
|
if !ok {
|
|
log.Fatal(message)
|
|
}
|
|
}
|