Compare commits
No commits in common. "74063d0908cb8c78b685da9cf03480fcda299ade" and "4926d5dfd2de93e93007849a9c3739edab589c6b" have entirely different histories.
74063d0908
...
4926d5dfd2
@ -81,11 +81,8 @@ type InviteConfig struct {
|
|||||||
|
|
||||||
// RechargeRewardConfig 保存充值奖励活动配置。
|
// RechargeRewardConfig 保存充值奖励活动配置。
|
||||||
type RechargeRewardConfig struct {
|
type RechargeRewardConfig struct {
|
||||||
DefaultSysOrigin string
|
DefaultSysOrigin string
|
||||||
Timezone string
|
Timezone string
|
||||||
StorageTimezone string
|
|
||||||
StartDate string
|
|
||||||
CoinSellerGoldPerUSD int64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaskCenterConfig 保存任务中心模块配置。
|
// TaskCenterConfig 保存任务中心模块配置。
|
||||||
@ -170,7 +167,6 @@ type VoiceRoomRocketConfig struct {
|
|||||||
InRoomRewardDueZSetKey string
|
InRoomRewardDueZSetKey string
|
||||||
RoomLockTTLSeconds int
|
RoomLockTTLSeconds int
|
||||||
BroadcastStreamKey string
|
BroadcastStreamKey string
|
||||||
BroadcastStreamMaxLen int64
|
|
||||||
InRoomRewardScanIntervalSeconds int
|
InRoomRewardScanIntervalSeconds int
|
||||||
InRoomRewardBatchSize int
|
InRoomRewardBatchSize int
|
||||||
InRoomRewardUserLimit int
|
InRoomRewardUserLimit int
|
||||||
@ -233,11 +229,10 @@ type GameOpenConfig struct {
|
|||||||
|
|
||||||
// LuckyGiftConfig 保存幸运礼物模块配置。
|
// LuckyGiftConfig 保存幸运礼物模块配置。
|
||||||
type LuckyGiftConfig struct {
|
type LuckyGiftConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
RewardStreamKey string
|
RewardStreamKey string
|
||||||
RewardStreamMaxLen int64
|
Providers []LuckyGiftProviderConfig
|
||||||
Providers []LuckyGiftProviderConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BinanceRechargeConfig 保存币安自动充值验证的运行配置。
|
// BinanceRechargeConfig 保存币安自动充值验证的运行配置。
|
||||||
@ -316,18 +311,6 @@ func Load() Config {
|
|||||||
[]string{"CHATAPP_RECHARGE_REWARD_TIMEZONE", "RECHARGE_REWARD_TIMEZONE", "CHATAPP_WEEK_STAR_TIMEZONE", "WEEK_STAR_TIMEZONE"},
|
[]string{"CHATAPP_RECHARGE_REWARD_TIMEZONE", "RECHARGE_REWARD_TIMEZONE", "CHATAPP_WEEK_STAR_TIMEZONE", "WEEK_STAR_TIMEZONE"},
|
||||||
"Asia/Riyadh",
|
"Asia/Riyadh",
|
||||||
),
|
),
|
||||||
StorageTimezone: getEnvAny(
|
|
||||||
[]string{"CHATAPP_RECHARGE_REWARD_STORAGE_TIMEZONE", "RECHARGE_REWARD_STORAGE_TIMEZONE"},
|
|
||||||
"Asia/Shanghai",
|
|
||||||
),
|
|
||||||
StartDate: getEnvAny(
|
|
||||||
[]string{"CHATAPP_RECHARGE_REWARD_START_DATE", "RECHARGE_REWARD_START_DATE"},
|
|
||||||
"2026-05-21",
|
|
||||||
),
|
|
||||||
CoinSellerGoldPerUSD: int64(getEnvIntAny(
|
|
||||||
[]string{"CHATAPP_RECHARGE_REWARD_COIN_SELLER_GOLD_PER_USD", "RECHARGE_REWARD_COIN_SELLER_GOLD_PER_USD"},
|
|
||||||
100000,
|
|
||||||
)),
|
|
||||||
},
|
},
|
||||||
TaskCenter: TaskCenterConfig{
|
TaskCenter: TaskCenterConfig{
|
||||||
DefaultSysOrigin: strings.ToUpper(getEnvAny(
|
DefaultSysOrigin: strings.ToUpper(getEnvAny(
|
||||||
@ -450,10 +433,6 @@ func Load() Config {
|
|||||||
[]string{"CHATAPP_VOICE_ROOM_ROCKET_BROADCAST_STREAM_KEY", "VOICE_ROOM_ROCKET_BROADCAST_STREAM_KEY"},
|
[]string{"CHATAPP_VOICE_ROOM_ROCKET_BROADCAST_STREAM_KEY", "VOICE_ROOM_ROCKET_BROADCAST_STREAM_KEY"},
|
||||||
"voice_room:rocket:broadcast",
|
"voice_room:rocket:broadcast",
|
||||||
),
|
),
|
||||||
BroadcastStreamMaxLen: int64(getEnvIntAny(
|
|
||||||
[]string{"CHATAPP_VOICE_ROOM_ROCKET_BROADCAST_STREAM_MAX_LEN", "VOICE_ROOM_ROCKET_BROADCAST_STREAM_MAX_LEN"},
|
|
||||||
10000,
|
|
||||||
)),
|
|
||||||
InRoomRewardScanIntervalSeconds: getEnvIntAny(
|
InRoomRewardScanIntervalSeconds: getEnvIntAny(
|
||||||
[]string{"CHATAPP_VOICE_ROOM_ROCKET_IN_ROOM_SCAN_INTERVAL_SECONDS", "VOICE_ROOM_ROCKET_IN_ROOM_SCAN_INTERVAL_SECONDS"},
|
[]string{"CHATAPP_VOICE_ROOM_ROCKET_IN_ROOM_SCAN_INTERVAL_SECONDS", "VOICE_ROOM_ROCKET_IN_ROOM_SCAN_INTERVAL_SECONDS"},
|
||||||
1,
|
1,
|
||||||
@ -536,11 +515,10 @@ func Load() Config {
|
|||||||
TestPassword: getEnvAny([]string{"CHATAPP_GAME_OPEN_TEST_PASSWORD", "GAME_OPEN_TEST_PASSWORD"}, "1234567"),
|
TestPassword: getEnvAny([]string{"CHATAPP_GAME_OPEN_TEST_PASSWORD", "GAME_OPEN_TEST_PASSWORD"}, "1234567"),
|
||||||
},
|
},
|
||||||
LuckyGift: LuckyGiftConfig{
|
LuckyGift: LuckyGiftConfig{
|
||||||
Enabled: getEnvBoolAny([]string{"CHATAPP_LUCKY_GIFT_ENABLED", "LUCKY_GIFT_ENABLED", "LUCKY_GIFT_GO_ENABLED"}, false),
|
Enabled: getEnvBoolAny([]string{"CHATAPP_LUCKY_GIFT_ENABLED", "LUCKY_GIFT_ENABLED", "LUCKY_GIFT_GO_ENABLED"}, false),
|
||||||
Timeout: time.Duration(getEnvIntAny([]string{"CHATAPP_LUCKY_GIFT_TIMEOUT_MILLIS", "LUCKY_GIFT_TIMEOUT_MILLIS", "LUCKY_GIFT_GO_TIMEOUT_MILLIS"}, 8000)) * time.Millisecond,
|
Timeout: time.Duration(getEnvIntAny([]string{"CHATAPP_LUCKY_GIFT_TIMEOUT_MILLIS", "LUCKY_GIFT_TIMEOUT_MILLIS", "LUCKY_GIFT_GO_TIMEOUT_MILLIS"}, 8000)) * time.Millisecond,
|
||||||
RewardStreamKey: getEnvAny([]string{"CHATAPP_LUCKY_GIFT_REWARD_STREAM_KEY", "LUCKY_GIFT_REWARD_STREAM_KEY"}, "lucky-gift:reward"),
|
RewardStreamKey: getEnvAny([]string{"CHATAPP_LUCKY_GIFT_REWARD_STREAM_KEY", "LUCKY_GIFT_REWARD_STREAM_KEY"}, "lucky-gift:reward"),
|
||||||
RewardStreamMaxLen: int64(getEnvIntAny([]string{"CHATAPP_LUCKY_GIFT_REWARD_STREAM_MAX_LEN", "LUCKY_GIFT_REWARD_STREAM_MAX_LEN"}, 100000)),
|
Providers: loadLuckyGiftConfigs(),
|
||||||
Providers: loadLuckyGiftConfigs(),
|
|
||||||
},
|
},
|
||||||
BinanceRecharge: BinanceRechargeConfig{
|
BinanceRecharge: BinanceRechargeConfig{
|
||||||
BaseURL: getEnvAny([]string{"CHATAPP_BINANCE_RECHARGE_BASE_URL", "BINANCE_RECHARGE_BASE_URL"}, "https://api.binance.com"),
|
BaseURL: getEnvAny([]string{"CHATAPP_BINANCE_RECHARGE_BASE_URL", "BINANCE_RECHARGE_BASE_URL"}, "https://api.binance.com"),
|
||||||
|
|||||||
@ -29,39 +29,3 @@ type RechargeRewardLevel struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (RechargeRewardLevel) TableName() string { return "recharge_reward_level" }
|
func (RechargeRewardLevel) TableName() string { return "recharge_reward_level" }
|
||||||
|
|
||||||
// RechargeRewardGrantRecord 记录某个充值周期内用户各档位的实际发放状态。
|
|
||||||
type RechargeRewardGrantRecord struct {
|
|
||||||
ID int64 `gorm:"column:id;primaryKey"`
|
|
||||||
ConfigID int64 `gorm:"column:config_id;index:idx_recharge_reward_grant_config_cycle,priority:1"`
|
|
||||||
LevelID int64 `gorm:"column:level_id"`
|
|
||||||
SysOrigin string `gorm:"column:sys_origin;size:32;uniqueIndex:uk_recharge_reward_grant_level,priority:1;index:idx_recharge_reward_grant_date,priority:1"`
|
|
||||||
CycleKey string `gorm:"column:cycle_key;size:32;uniqueIndex:uk_recharge_reward_grant_level,priority:2;index:idx_recharge_reward_grant_config_cycle,priority:2"`
|
|
||||||
RechargeDate int `gorm:"column:recharge_date;index:idx_recharge_reward_grant_user,priority:2;index:idx_recharge_reward_grant_date,priority:2"`
|
|
||||||
PeriodStartAt time.Time `gorm:"column:period_start_at"`
|
|
||||||
PeriodEndAt time.Time `gorm:"column:period_end_at"`
|
|
||||||
UserID int64 `gorm:"column:user_id;uniqueIndex:uk_recharge_reward_grant_level,priority:3;index:idx_recharge_reward_grant_user,priority:1"`
|
|
||||||
Account string `gorm:"column:account;size:64"`
|
|
||||||
UserAvatar string `gorm:"column:user_avatar;size:1024"`
|
|
||||||
UserNickname string `gorm:"column:user_nickname;size:255"`
|
|
||||||
CountryCode string `gorm:"column:country_code;size:32"`
|
|
||||||
CountryName string `gorm:"column:country_name;size:128"`
|
|
||||||
RechargeAmountCents int64 `gorm:"column:recharge_amount_cents"`
|
|
||||||
Level int `gorm:"column:level;uniqueIndex:uk_recharge_reward_grant_level,priority:4"`
|
|
||||||
RechargeThresholdCents int64 `gorm:"column:recharge_threshold_cents"`
|
|
||||||
RewardGold int64 `gorm:"column:reward_gold"`
|
|
||||||
RewardGroupID *int64 `gorm:"column:reward_group_id"`
|
|
||||||
RewardGroupName string `gorm:"column:reward_group_name;size:255"`
|
|
||||||
RewardGroupTrackID int64 `gorm:"column:reward_group_track_id"`
|
|
||||||
GoldEventID string `gorm:"column:gold_event_id;size:128;index:idx_recharge_reward_gold_event"`
|
|
||||||
Status string `gorm:"column:status;size:32;index:idx_recharge_reward_grant_config_cycle,priority:3;index:idx_recharge_reward_grant_date,priority:3"`
|
|
||||||
RewardGroupStatus string `gorm:"column:reward_group_status;size:32"`
|
|
||||||
RewardGoldStatus string `gorm:"column:reward_gold_status;size:32"`
|
|
||||||
RetryCount int `gorm:"column:retry_count"`
|
|
||||||
LastError string `gorm:"column:last_error;size:1024"`
|
|
||||||
SentAt *time.Time `gorm:"column:sent_at"`
|
|
||||||
CreateTime time.Time `gorm:"column:create_time"`
|
|
||||||
UpdateTime time.Time `gorm:"column:update_time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (RechargeRewardGrantRecord) TableName() string { return "recharge_reward_grant_record" }
|
|
||||||
|
|||||||
@ -78,8 +78,6 @@ func (s *LuckyGiftService) publishRewardEvent(
|
|||||||
|
|
||||||
_, err := s.repo.Redis.XAdd(ctx, &redis.XAddArgs{
|
_, err := s.repo.Redis.XAdd(ctx, &redis.XAddArgs{
|
||||||
Stream: streamKey,
|
Stream: streamKey,
|
||||||
MaxLen: s.cfg.LuckyGift.RewardStreamMaxLen,
|
|
||||||
Approx: s.cfg.LuckyGift.RewardStreamMaxLen > 0,
|
|
||||||
Values: map[string]any{
|
Values: map[string]any{
|
||||||
"eventId": event.EventID,
|
"eventId": event.EventID,
|
||||||
"businessId": event.BusinessID,
|
"businessId": event.BusinessID,
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"chatapp3-golang/internal/config"
|
"chatapp3-golang/internal/config"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/alicebob/miniredis/v2"
|
"github.com/alicebob/miniredis/v2"
|
||||||
@ -24,8 +23,7 @@ func TestPublishRewardEventWritesWinningResultsToStream(t *testing.T) {
|
|||||||
service := &LuckyGiftService{
|
service := &LuckyGiftService{
|
||||||
cfg: config.Config{
|
cfg: config.Config{
|
||||||
LuckyGift: config.LuckyGiftConfig{
|
LuckyGift: config.LuckyGiftConfig{
|
||||||
RewardStreamKey: "lucky-gift:reward",
|
RewardStreamKey: "lucky-gift:reward",
|
||||||
RewardStreamMaxLen: 2,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
repo: luckyGiftPorts{Redis: client},
|
repo: luckyGiftPorts{Redis: client},
|
||||||
@ -91,8 +89,7 @@ func TestPublishRewardEventSkipsZeroRewardTotal(t *testing.T) {
|
|||||||
service := &LuckyGiftService{
|
service := &LuckyGiftService{
|
||||||
cfg: config.Config{
|
cfg: config.Config{
|
||||||
LuckyGift: config.LuckyGiftConfig{
|
LuckyGift: config.LuckyGiftConfig{
|
||||||
RewardStreamKey: "lucky-gift:reward",
|
RewardStreamKey: "lucky-gift:reward",
|
||||||
RewardStreamMaxLen: 2,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
repo: luckyGiftPorts{Redis: client},
|
repo: luckyGiftPorts{Redis: client},
|
||||||
@ -110,48 +107,3 @@ func TestPublishRewardEventSkipsZeroRewardTotal(t *testing.T) {
|
|||||||
t.Fatalf("expected no stream records for zero reward total, got %d", len(streams))
|
t.Fatalf("expected no stream records for zero reward total, got %d", len(streams))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPublishRewardEventTrimsStreamByConfiguredMaxLen(t *testing.T) {
|
|
||||||
mr, err := miniredis.Run()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("miniredis run failed: %v", err)
|
|
||||||
}
|
|
||||||
defer mr.Close()
|
|
||||||
|
|
||||||
client := redis.NewClient(&redis.Options{Addr: mr.Addr()})
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
service := &LuckyGiftService{
|
|
||||||
cfg: config.Config{
|
|
||||||
LuckyGift: config.LuckyGiftConfig{
|
|
||||||
RewardStreamKey: "lucky-gift:reward",
|
|
||||||
RewardStreamMaxLen: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
repo: luckyGiftPorts{Redis: client},
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx := 1; idx <= 3; idx++ {
|
|
||||||
req := LuckyGiftDrawRequest{
|
|
||||||
BusinessID: "biz-" + strconv.Itoa(idx),
|
|
||||||
SysOrigin: "LIKEI",
|
|
||||||
RoomID: 2001,
|
|
||||||
SendUserID: 3001,
|
|
||||||
GiftID: 4001,
|
|
||||||
GiftPrice: 500,
|
|
||||||
GiftNum: 2,
|
|
||||||
}
|
|
||||||
results := []LuckyGiftDrawResult{{ID: "order-a", AcceptUserID: 9001, RewardNum: 88}}
|
|
||||||
if err := service.publishRewardEvent(context.Background(), req, results, 88); err != nil {
|
|
||||||
t.Fatalf("publishRewardEvent returned error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
count, err := client.XLen(context.Background(), "lucky-gift:reward").Result()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("xlen returned error: %v", err)
|
|
||||||
}
|
|
||||||
if count != 2 {
|
|
||||||
t.Fatalf("expected trimmed stream length 2, got %d", count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -2,33 +2,48 @@ package rechargereward
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var personalMonthlyRechargeTypes = []string{
|
||||||
|
"UNDEFINED",
|
||||||
|
"HUAWEI",
|
||||||
|
"GOOGLE",
|
||||||
|
"APPLE",
|
||||||
|
"PAYER_MAX",
|
||||||
|
"SHIPPING_AGENT",
|
||||||
|
"STRIPE",
|
||||||
|
"SALARY_EXCHANGE",
|
||||||
|
"PAY_PAL",
|
||||||
|
"CLIPSPAY",
|
||||||
|
"SELLER_AGENT",
|
||||||
|
}
|
||||||
|
|
||||||
type rechargeRewardClaimRecordRow struct {
|
type rechargeRewardClaimRecordRow struct {
|
||||||
UserID int64 `gorm:"column:user_id"`
|
UserID int64 `gorm:"column:user_id"`
|
||||||
RechargeAmountCents int64 `gorm:"column:recharge_amount_cents"`
|
RechargeAmount string `gorm:"column:recharge_amount"`
|
||||||
LastRechargeTime string `gorm:"column:last_recharge_time"`
|
LastRechargeTime string `gorm:"column:last_recharge_time"`
|
||||||
LastClaimTime string `gorm:"column:last_claim_time"`
|
LastClaimTime string `gorm:"column:last_claim_time"`
|
||||||
Account string `gorm:"column:account"`
|
Account string `gorm:"column:account"`
|
||||||
UserAvatar string `gorm:"column:user_avatar"`
|
UserAvatar string `gorm:"column:user_avatar"`
|
||||||
UserNickname string `gorm:"column:user_nickname"`
|
UserNickname string `gorm:"column:user_nickname"`
|
||||||
CountryCode string `gorm:"column:country_code"`
|
CountryCode string `gorm:"column:country_code"`
|
||||||
CountryName string `gorm:"column:country_name"`
|
CountryName string `gorm:"column:country_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type rechargeRewardClaimDetailRow struct {
|
type rechargeRewardClaimDetailRow struct {
|
||||||
UserID int64 `gorm:"column:user_id"`
|
UserID int64 `gorm:"column:user_id"`
|
||||||
ActivityID int64 `gorm:"column:activity_id"`
|
ActivityID int64 `gorm:"column:activity_id"`
|
||||||
Level int `gorm:"column:level"`
|
Level int `gorm:"column:level"`
|
||||||
ResourceGroupID *int64 `gorm:"column:resource_group_id"`
|
ResourceGroupID *int64 `gorm:"column:resource_group_id"`
|
||||||
RewardGroupName string `gorm:"column:reward_group_name"`
|
RuleDescription string `gorm:"column:rule_description"`
|
||||||
RechargeThresholdCents int64 `gorm:"column:recharge_threshold_cents"`
|
JSONData string `gorm:"column:json_data"`
|
||||||
RewardGold int64 `gorm:"column:reward_gold"`
|
ClaimTime string `gorm:"column:claim_time"`
|
||||||
ClaimTime string `gorm:"column:claim_time"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PageClaimRecords 分页返回真实领取过累计充值奖励的用户。
|
// PageClaimRecords 分页返回真实领取过累计充值奖励的用户。
|
||||||
@ -52,10 +67,12 @@ func (s *Service) PageClaimRecords(ctx context.Context, sysOrigin string, month
|
|||||||
var total int64
|
var total int64
|
||||||
countSQL := `
|
countSQL := `
|
||||||
SELECT COUNT(DISTINCT receive.user_id)
|
SELECT COUNT(DISTINCT receive.user_id)
|
||||||
FROM recharge_reward_grant_record AS receive
|
FROM user_activity_reward_receive_record AS receive
|
||||||
|
INNER JOIN props_activity_rule_config AS rule ON rule.id = receive.activity_id
|
||||||
WHERE receive.recharge_date = ?
|
WHERE receive.recharge_date = ?
|
||||||
AND receive.sys_origin = ?
|
AND rule.sys_origin = ?
|
||||||
AND receive.status = 'SUCCESS'
|
AND rule.activity_type = 'CUMULATIVE_RECHARGE'
|
||||||
|
AND COALESCE(rule.is_del, 0) = 0
|
||||||
AND (? = 0 OR receive.user_id = ?)`
|
AND (? = 0 OR receive.user_id = ?)`
|
||||||
if err := s.db.WithContext(ctx).Raw(
|
if err := s.db.WithContext(ctx).Raw(
|
||||||
countSQL,
|
countSQL,
|
||||||
@ -73,34 +90,40 @@ WHERE receive.recharge_date = ?
|
|||||||
listSQL := `
|
listSQL := `
|
||||||
SELECT
|
SELECT
|
||||||
claims.user_id,
|
claims.user_id,
|
||||||
claims.recharge_amount_cents,
|
COALESCE(recharge.recharge_amount, 0) AS recharge_amount,
|
||||||
claims.last_recharge_time,
|
COALESCE(recharge.last_recharge_time, '') AS last_recharge_time,
|
||||||
claims.last_claim_time,
|
claims.last_claim_time,
|
||||||
claims.account,
|
user.account,
|
||||||
claims.user_avatar,
|
user.user_avatar,
|
||||||
claims.user_nickname,
|
user.user_nickname,
|
||||||
claims.country_code,
|
user.country_code,
|
||||||
claims.country_name
|
user.country_name
|
||||||
FROM (
|
FROM (
|
||||||
SELECT
|
SELECT
|
||||||
receive.user_id,
|
receive.user_id,
|
||||||
MAX(receive.recharge_amount_cents) AS recharge_amount_cents,
|
MAX(receive.create_time) AS last_claim_time
|
||||||
COALESCE(MAX(receive.sent_at), MAX(receive.update_time), '') AS last_recharge_time,
|
FROM user_activity_reward_receive_record AS receive
|
||||||
COALESCE(MAX(receive.sent_at), MAX(receive.create_time), '') AS last_claim_time,
|
INNER JOIN props_activity_rule_config AS rule ON rule.id = receive.activity_id
|
||||||
MAX(receive.account) AS account,
|
|
||||||
MAX(receive.user_avatar) AS user_avatar,
|
|
||||||
MAX(receive.user_nickname) AS user_nickname,
|
|
||||||
MAX(receive.country_code) AS country_code,
|
|
||||||
MAX(receive.country_name) AS country_name
|
|
||||||
FROM recharge_reward_grant_record AS receive
|
|
||||||
WHERE receive.recharge_date = ?
|
WHERE receive.recharge_date = ?
|
||||||
AND receive.sys_origin = ?
|
AND rule.sys_origin = ?
|
||||||
AND receive.status = 'SUCCESS'
|
AND rule.activity_type = 'CUMULATIVE_RECHARGE'
|
||||||
|
AND COALESCE(rule.is_del, 0) = 0
|
||||||
AND (? = 0 OR receive.user_id = ?)
|
AND (? = 0 OR receive.user_id = ?)
|
||||||
GROUP BY receive.user_id
|
GROUP BY receive.user_id
|
||||||
ORDER BY COALESCE(MAX(receive.sent_at), MAX(receive.create_time)) DESC, receive.user_id ASC
|
ORDER BY MAX(receive.create_time) DESC, receive.user_id ASC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT ? OFFSET ?
|
||||||
) AS claims
|
) AS claims
|
||||||
|
INNER JOIN user_base_info AS user ON user.id = claims.user_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
recharge.user_id,
|
||||||
|
SUM(recharge.amount) AS recharge_amount,
|
||||||
|
MAX(recharge.update_time) AS last_recharge_time
|
||||||
|
FROM user_monthly_recharge_v2 AS recharge
|
||||||
|
WHERE recharge.recharge_date = ?
|
||||||
|
AND recharge.type IN ?
|
||||||
|
GROUP BY recharge.user_id
|
||||||
|
) AS recharge ON recharge.user_id = claims.user_id
|
||||||
ORDER BY claims.last_claim_time DESC, claims.user_id ASC`
|
ORDER BY claims.last_claim_time DESC, claims.user_id ASC`
|
||||||
if err := s.db.WithContext(ctx).Raw(
|
if err := s.db.WithContext(ctx).Raw(
|
||||||
listSQL,
|
listSQL,
|
||||||
@ -110,6 +133,8 @@ ORDER BY claims.last_claim_time DESC, claims.user_id ASC`
|
|||||||
userID,
|
userID,
|
||||||
limit,
|
limit,
|
||||||
(cursor-1)*limit,
|
(cursor-1)*limit,
|
||||||
|
rechargeDate,
|
||||||
|
personalMonthlyRechargeTypes,
|
||||||
).Scan(&rows).Error; err != nil {
|
).Scan(&rows).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -182,6 +207,7 @@ func rechargeRewardClaimRecordView(
|
|||||||
details []rechargeRewardClaimDetailRow,
|
details []rechargeRewardClaimDetailRow,
|
||||||
rewardItems map[int64][]RechargeRewardRewardItem,
|
rewardItems map[int64][]RechargeRewardRewardItem,
|
||||||
) RechargeRewardClaimRecordView {
|
) RechargeRewardClaimRecordView {
|
||||||
|
rechargeCents := parseIntegrationAmountCents(integrationAmount(row.RechargeAmount))
|
||||||
reached := claimDetailPayloads(details, rewardItems)
|
reached := claimDetailPayloads(details, rewardItems)
|
||||||
return RechargeRewardClaimRecordView{
|
return RechargeRewardClaimRecordView{
|
||||||
RecordKey: fmt.Sprintf("%d-%d", row.UserID, rechargeDate),
|
RecordKey: fmt.Sprintf("%d-%d", row.UserID, rechargeDate),
|
||||||
@ -193,8 +219,8 @@ func rechargeRewardClaimRecordView(
|
|||||||
CountryName: strings.TrimSpace(row.CountryName),
|
CountryName: strings.TrimSpace(row.CountryName),
|
||||||
RechargeMonth: month,
|
RechargeMonth: month,
|
||||||
RechargeDate: rechargeDate,
|
RechargeDate: rechargeDate,
|
||||||
RechargeAmount: formatAmountCents(row.RechargeAmountCents),
|
RechargeAmount: formatAmountCents(rechargeCents),
|
||||||
RechargeAmountCents: row.RechargeAmountCents,
|
RechargeAmountCents: rechargeCents,
|
||||||
ReachedLevels: reached,
|
ReachedLevels: reached,
|
||||||
ClaimedRewards: rechargeRewardClaimedRewards(reached),
|
ClaimedRewards: rechargeRewardClaimedRewards(reached),
|
||||||
LastRechargeTime: formatRechargeRewardRecordTime(row.LastRechargeTime),
|
LastRechargeTime: formatRechargeRewardRecordTime(row.LastRechargeTime),
|
||||||
@ -209,19 +235,20 @@ func (s *Service) loadClaimDetails(ctx context.Context, sysOrigin string, rechar
|
|||||||
detailSQL := `
|
detailSQL := `
|
||||||
SELECT
|
SELECT
|
||||||
receive.user_id,
|
receive.user_id,
|
||||||
receive.level_id AS activity_id,
|
receive.activity_id,
|
||||||
receive.level,
|
COALESCE(rule.sort, 0) AS level,
|
||||||
receive.reward_group_id AS resource_group_id,
|
rule.resource_group_id,
|
||||||
receive.reward_group_name,
|
rule.rule_description,
|
||||||
receive.recharge_threshold_cents,
|
rule.json_data,
|
||||||
receive.reward_gold,
|
receive.create_time AS claim_time
|
||||||
COALESCE(receive.sent_at, receive.create_time) AS claim_time
|
FROM user_activity_reward_receive_record AS receive
|
||||||
FROM recharge_reward_grant_record AS receive
|
INNER JOIN props_activity_rule_config AS rule ON rule.id = receive.activity_id
|
||||||
WHERE receive.recharge_date = ?
|
WHERE receive.recharge_date = ?
|
||||||
AND receive.sys_origin = ?
|
AND rule.sys_origin = ?
|
||||||
AND receive.status = 'SUCCESS'
|
AND rule.activity_type = 'CUMULATIVE_RECHARGE'
|
||||||
|
AND COALESCE(rule.is_del, 0) = 0
|
||||||
AND receive.user_id IN ?
|
AND receive.user_id IN ?
|
||||||
ORDER BY receive.user_id ASC, receive.level ASC, receive.create_time ASC`
|
ORDER BY receive.user_id ASC, COALESCE(rule.sort, 0) ASC, receive.create_time ASC`
|
||||||
if err := s.db.WithContext(ctx).Raw(detailSQL, rechargeDate, sysOrigin, userIDs).Scan(&rows).Error; err != nil {
|
if err := s.db.WithContext(ctx).Raw(detailSQL, rechargeDate, sysOrigin, userIDs).Scan(&rows).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -283,14 +310,14 @@ func claimDetailPayloads(
|
|||||||
if detail.ResourceGroupID != nil {
|
if detail.ResourceGroupID != nil {
|
||||||
items = rewardItems[*detail.ResourceGroupID]
|
items = rewardItems[*detail.ResourceGroupID]
|
||||||
}
|
}
|
||||||
|
thresholdCents := rechargeRewardRuleQuantityCents(detail.JSONData)
|
||||||
result = append(result, RechargeRewardLevelPayload{
|
result = append(result, RechargeRewardLevelPayload{
|
||||||
ID: detail.ActivityID,
|
ID: detail.ActivityID,
|
||||||
Level: level,
|
Level: level,
|
||||||
RechargeAmount: formatAmountCents(detail.RechargeThresholdCents),
|
RechargeAmount: formatAmountCents(thresholdCents),
|
||||||
RechargeAmountCents: detail.RechargeThresholdCents,
|
RechargeAmountCents: thresholdCents,
|
||||||
RewardGold: detail.RewardGold,
|
|
||||||
RewardGroupID: detail.ResourceGroupID,
|
RewardGroupID: detail.ResourceGroupID,
|
||||||
RewardGroupName: strings.TrimSpace(detail.RewardGroupName),
|
RewardGroupName: strings.TrimSpace(detail.RuleDescription),
|
||||||
RewardItems: items,
|
RewardItems: items,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Reached: true,
|
Reached: true,
|
||||||
@ -299,6 +326,33 @@ func claimDetailPayloads(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rechargeRewardRuleQuantityCents(raw string) int64 {
|
||||||
|
var payload map[string]any
|
||||||
|
if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &payload); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
value, ok := payload["quantity"]
|
||||||
|
if !ok {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
switch typed := value.(type) {
|
||||||
|
case float64:
|
||||||
|
return int64(math.Round(typed * 100))
|
||||||
|
case string:
|
||||||
|
cents, err := parseAmountCents(typed)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return cents
|
||||||
|
default:
|
||||||
|
cents, err := parseAmountCents(fmt.Sprint(typed))
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return cents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func reachedRechargeRewardLevels(
|
func reachedRechargeRewardLevels(
|
||||||
levels []rechargeRewardLevelSnapshot,
|
levels []rechargeRewardLevelSnapshot,
|
||||||
rewardItems map[int64][]RechargeRewardRewardItem,
|
rewardItems map[int64][]RechargeRewardRewardItem,
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"chatapp3-golang/internal/config"
|
"chatapp3-golang/internal/config"
|
||||||
"chatapp3-golang/internal/integration"
|
"chatapp3-golang/internal/integration"
|
||||||
@ -23,21 +22,14 @@ func newTestService(t *testing.T, java rechargeRewardJavaGateway) (*Service, *go
|
|||||||
if err := db.AutoMigrate(
|
if err := db.AutoMigrate(
|
||||||
&model.RechargeRewardConfig{},
|
&model.RechargeRewardConfig{},
|
||||||
&model.RechargeRewardLevel{},
|
&model.RechargeRewardLevel{},
|
||||||
&model.RechargeRewardGrantRecord{},
|
|
||||||
&model.UserBaseInfo{},
|
&model.UserBaseInfo{},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
t.Fatalf("auto migrate: %v", err)
|
t.Fatalf("auto migrate: %v", err)
|
||||||
}
|
}
|
||||||
if err := db.Exec(`ALTER TABLE user_base_info ADD COLUMN is_del INTEGER DEFAULT 0`).Error; err != nil {
|
|
||||||
t.Fatalf("add is_del: %v", err)
|
|
||||||
}
|
|
||||||
service := NewService(config.Config{
|
service := NewService(config.Config{
|
||||||
RechargeReward: config.RechargeRewardConfig{
|
RechargeReward: config.RechargeRewardConfig{
|
||||||
DefaultSysOrigin: "LIKEI",
|
DefaultSysOrigin: "LIKEI",
|
||||||
Timezone: "Asia/Riyadh",
|
Timezone: "Asia/Riyadh",
|
||||||
StorageTimezone: "Asia/Shanghai",
|
|
||||||
StartDate: "2020-01-01",
|
|
||||||
CoinSellerGoldPerUSD: 100000,
|
|
||||||
},
|
},
|
||||||
}, db, java)
|
}, db, java)
|
||||||
return service, db
|
return service, db
|
||||||
@ -115,6 +107,7 @@ func TestSaveConfigUpsertsBySysOrigin(t *testing.T) {
|
|||||||
func TestGetHomeUsesJavaRechargeAndMarksLevels(t *testing.T) {
|
func TestGetHomeUsesJavaRechargeAndMarksLevels(t *testing.T) {
|
||||||
groupID := int64(9001)
|
groupID := int64(9001)
|
||||||
java := rechargeRewardTestGateway{
|
java := rechargeRewardTestGateway{
|
||||||
|
monthly: integration.DecimalString("150.50"),
|
||||||
total: map[int64][]integration.UserTotalRecharge{
|
total: map[int64][]integration.UserTotalRecharge{
|
||||||
42: {
|
42: {
|
||||||
{UserID: integration.Int64Value(42), Type: "GOOGLE", Amount: integration.DecimalString("5000.00")},
|
{UserID: integration.Int64Value(42), Type: "GOOGLE", Amount: integration.DecimalString("5000.00")},
|
||||||
@ -137,37 +130,7 @@ func TestGetHomeUsesJavaRechargeAndMarksLevels(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
service, db := newTestService(t, java)
|
service, _ := newTestService(t, java)
|
||||||
createRechargeRewardSourceTables(t, db)
|
|
||||||
if err := db.Create(&model.UserBaseInfo{
|
|
||||||
ID: 42,
|
|
||||||
Account: "1000042",
|
|
||||||
UserNickname: "Nina",
|
|
||||||
OriginSys: "LIKEI",
|
|
||||||
CountryCode: "SA",
|
|
||||||
CountryName: "Saudi Arabia",
|
|
||||||
}).Error; err != nil {
|
|
||||||
t.Fatalf("create user: %v", err)
|
|
||||||
}
|
|
||||||
rechargeAt := rechargeRewardStorageTime(time.Now().In(service.location), service.storageLocation)
|
|
||||||
if err := db.Exec(`
|
|
||||||
INSERT INTO order_purchase_history (user_id, sys_origin, evn, is_trial_period, status, pay_platform, unit_price, create_time) VALUES
|
|
||||||
(42, 'LIKEI', 'PROD', 0, 'COMPLETE', 'GOOGLE', 120.50, ?)
|
|
||||||
`, rechargeAt).Error; err != nil {
|
|
||||||
t.Fatalf("insert google recharge: %v", err)
|
|
||||||
}
|
|
||||||
if err := db.Exec(`
|
|
||||||
INSERT INTO order_user_purchase_pay (user_id, sys_origin, evn, factory_code, pay_status, receipt_type, refund_status, compute_usd_amount, create_time, update_time) VALUES
|
|
||||||
(42, 'LIKEI', 'PROD', 'MIFA_PAY', 'SUCCESSFUL', 'PAYMENT', 'NONE', 29.00, ?, ?)
|
|
||||||
`, rechargeAt, rechargeAt).Error; err != nil {
|
|
||||||
t.Fatalf("insert mifapay recharge: %v", err)
|
|
||||||
}
|
|
||||||
if err := db.Exec(`
|
|
||||||
INSERT INTO likei_wallet.user_freight_balance_running_water (accept_user_id, sys_origin, origin, type, quantity, create_time) VALUES
|
|
||||||
(42, 'LIKEI', 'SHIPMENT', 1, 100000, ?)
|
|
||||||
`, rechargeAt).Error; err != nil {
|
|
||||||
t.Fatalf("insert seller recharge: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := service.SaveConfig(context.Background(), SaveRechargeRewardConfigRequest{
|
if _, err := service.SaveConfig(context.Background(), SaveRechargeRewardConfigRequest{
|
||||||
SysOrigin: "LIKEI",
|
SysOrigin: "LIKEI",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
@ -211,6 +174,39 @@ func TestPageClaimRecordsUsesActualReceiveRecords(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
if err := db.Exec(`
|
||||||
|
CREATE TABLE user_monthly_recharge_v2 (
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
amount DECIMAL(12,2) NOT NULL,
|
||||||
|
recharge_date INTEGER NOT NULL,
|
||||||
|
update_time DATETIME NOT NULL
|
||||||
|
)`).Error; err != nil {
|
||||||
|
t.Fatalf("create monthly recharge table: %v", err)
|
||||||
|
}
|
||||||
|
if err := db.Exec(`
|
||||||
|
CREATE TABLE user_activity_reward_receive_record (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
recharge_date INTEGER NOT NULL,
|
||||||
|
activity_id INTEGER NOT NULL,
|
||||||
|
create_time DATETIME NOT NULL
|
||||||
|
)`).Error; err != nil {
|
||||||
|
t.Fatalf("create receive record table: %v", err)
|
||||||
|
}
|
||||||
|
if err := db.Exec(`
|
||||||
|
CREATE TABLE props_activity_rule_config (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
sys_origin TEXT NOT NULL,
|
||||||
|
activity_type TEXT NOT NULL,
|
||||||
|
resource_group_id INTEGER NOT NULL,
|
||||||
|
json_data TEXT NOT NULL,
|
||||||
|
sort INTEGER NOT NULL,
|
||||||
|
rule_description TEXT NOT NULL,
|
||||||
|
is_del INTEGER NOT NULL
|
||||||
|
)`).Error; err != nil {
|
||||||
|
t.Fatalf("create activity rule table: %v", err)
|
||||||
|
}
|
||||||
if _, err := service.SaveConfig(context.Background(), SaveRechargeRewardConfigRequest{
|
if _, err := service.SaveConfig(context.Background(), SaveRechargeRewardConfigRequest{
|
||||||
SysOrigin: "LIKEI",
|
SysOrigin: "LIKEI",
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
@ -232,33 +228,25 @@ func TestPageClaimRecordsUsesActualReceiveRecords(t *testing.T) {
|
|||||||
}).Error; err != nil {
|
}).Error; err != nil {
|
||||||
t.Fatalf("create user: %v", err)
|
t.Fatalf("create user: %v", err)
|
||||||
}
|
}
|
||||||
if err := db.Create(&model.RechargeRewardGrantRecord{
|
if err := db.Exec(`
|
||||||
ID: 8001,
|
INSERT INTO user_monthly_recharge_v2 (user_id, type, amount, recharge_date, update_time) VALUES
|
||||||
ConfigID: 1001,
|
(42, 'GOOGLE', 150.50, 202605, '2026-05-07 10:00:00'),
|
||||||
LevelID: 7001,
|
(42, 'MIFA_PAY', 70.00, 202605, '2026-05-07 11:00:00'),
|
||||||
SysOrigin: "LIKEI",
|
(43, 'GOOGLE', 300.00, 202605, '2026-05-07 12:00:00')
|
||||||
CycleKey: "20260521",
|
`).Error; err != nil {
|
||||||
RechargeDate: 202605,
|
t.Fatalf("insert monthly recharge: %v", err)
|
||||||
PeriodStartAt: time.Date(2026, 5, 21, 0, 0, 0, 0, time.UTC),
|
}
|
||||||
PeriodEndAt: time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC),
|
if err := db.Exec(`
|
||||||
UserID: 42,
|
INSERT INTO props_activity_rule_config (id, sys_origin, activity_type, resource_group_id, json_data, sort, rule_description, is_del) VALUES
|
||||||
Account: "1000042",
|
(7001, 'LIKEI', 'CUMULATIVE_RECHARGE', 9001, '{"quantity":100}', 1, 'Gift Box', 0)
|
||||||
UserNickname: "Nina",
|
`).Error; err != nil {
|
||||||
CountryCode: "SA",
|
t.Fatalf("insert activity rule: %v", err)
|
||||||
CountryName: "Saudi Arabia",
|
}
|
||||||
RechargeAmountCents: 15050,
|
if err := db.Exec(`
|
||||||
Level: 1,
|
INSERT INTO user_activity_reward_receive_record (id, user_id, recharge_date, activity_id, create_time) VALUES
|
||||||
RechargeThresholdCents: 10000,
|
(8001, 42, 202605, 7001, '2026-05-07 10:30:00')
|
||||||
RewardGroupID: &groupID,
|
`).Error; err != nil {
|
||||||
RewardGroupName: "Gift Box",
|
t.Fatalf("insert receive record: %v", err)
|
||||||
Status: "SUCCESS",
|
|
||||||
RewardGroupStatus: "SUCCESS",
|
|
||||||
RewardGoldStatus: "SKIPPED",
|
|
||||||
SentAt: ptrTime(time.Date(2026, 5, 21, 10, 30, 0, 0, time.UTC)),
|
|
||||||
CreateTime: time.Date(2026, 5, 21, 10, 30, 0, 0, time.UTC),
|
|
||||||
UpdateTime: time.Date(2026, 5, 21, 10, 30, 0, 0, time.UTC),
|
|
||||||
}).Error; err != nil {
|
|
||||||
t.Fatalf("insert grant record: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := service.PageClaimRecords(context.Background(), "LIKEI", "2026-05", 0, 1, 20)
|
resp, err := service.PageClaimRecords(context.Background(), "LIKEI", "2026-05", 0, 1, 20)
|
||||||
@ -314,56 +302,6 @@ func ptrFlexibleInt64(value int64) flexibleOptionalInt64 {
|
|||||||
return flexibleOptionalInt64{value: &value}
|
return flexibleOptionalInt64{value: &value}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ptrTime(value time.Time) *time.Time {
|
|
||||||
return &value
|
|
||||||
}
|
|
||||||
|
|
||||||
func createRechargeRewardSourceTables(t *testing.T, db *gorm.DB) {
|
|
||||||
t.Helper()
|
|
||||||
if err := db.Exec(`ATTACH DATABASE ':memory:' AS likei_wallet`).Error; err != nil {
|
|
||||||
t.Fatalf("attach wallet schema: %v", err)
|
|
||||||
}
|
|
||||||
if err := db.Exec(`
|
|
||||||
CREATE TABLE order_purchase_history (
|
|
||||||
user_id INTEGER NOT NULL,
|
|
||||||
sys_origin TEXT NOT NULL,
|
|
||||||
evn TEXT NOT NULL,
|
|
||||||
is_trial_period INTEGER NOT NULL,
|
|
||||||
status TEXT NOT NULL,
|
|
||||||
pay_platform TEXT NOT NULL,
|
|
||||||
unit_price DECIMAL(12,2) NOT NULL,
|
|
||||||
create_time DATETIME NOT NULL
|
|
||||||
)`).Error; err != nil {
|
|
||||||
t.Fatalf("create order_purchase_history: %v", err)
|
|
||||||
}
|
|
||||||
if err := db.Exec(`
|
|
||||||
CREATE TABLE order_user_purchase_pay (
|
|
||||||
user_id INTEGER NOT NULL,
|
|
||||||
sys_origin TEXT NOT NULL,
|
|
||||||
evn TEXT NOT NULL,
|
|
||||||
factory_code TEXT NOT NULL,
|
|
||||||
pay_status TEXT NOT NULL,
|
|
||||||
receipt_type TEXT NOT NULL,
|
|
||||||
refund_status TEXT NOT NULL,
|
|
||||||
compute_usd_amount DECIMAL(12,2) NOT NULL,
|
|
||||||
create_time DATETIME NOT NULL,
|
|
||||||
update_time DATETIME NOT NULL
|
|
||||||
)`).Error; err != nil {
|
|
||||||
t.Fatalf("create order_user_purchase_pay: %v", err)
|
|
||||||
}
|
|
||||||
if err := db.Exec(`
|
|
||||||
CREATE TABLE likei_wallet.user_freight_balance_running_water (
|
|
||||||
accept_user_id INTEGER NOT NULL,
|
|
||||||
sys_origin TEXT NOT NULL,
|
|
||||||
origin TEXT NOT NULL,
|
|
||||||
type INTEGER NOT NULL,
|
|
||||||
quantity DECIMAL(20,2) NOT NULL,
|
|
||||||
create_time DATETIME NOT NULL
|
|
||||||
)`).Error; err != nil {
|
|
||||||
t.Fatalf("create wallet freight water: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type rechargeRewardTestGateway struct {
|
type rechargeRewardTestGateway struct {
|
||||||
monthly integration.DecimalString
|
monthly integration.DecimalString
|
||||||
total map[int64][]integration.UserTotalRecharge
|
total map[int64][]integration.UserTotalRecharge
|
||||||
|
|||||||
@ -26,15 +26,11 @@ func (s *Service) GetHome(ctx context.Context, user AuthUser) (*RechargeRewardHo
|
|||||||
location = resolveLocation(bundle.Config.Timezone, s.location)
|
location = resolveLocation(bundle.Config.Timezone, s.location)
|
||||||
}
|
}
|
||||||
now := time.Now().In(location)
|
now := time.Now().In(location)
|
||||||
period := s.displayPeriod(now, location)
|
periodStart, periodEnd := currentMonthBounds(now, location)
|
||||||
currentPeriod, periodActive := s.currentPeriod(now, location)
|
|
||||||
|
|
||||||
var currentRechargeCents int64
|
currentRechargeCents, err := s.loadCurrentRechargeCents(ctx, user.UserID)
|
||||||
if periodActive {
|
if err != nil {
|
||||||
currentRechargeCents, err = s.loadCurrentRechargeCents(ctx, sysOrigin, user.UserID, currentPeriod)
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
accumulatedRechargeCents, err := s.loadAccumulatedRechargeCents(ctx, user.UserID)
|
accumulatedRechargeCents, err := s.loadAccumulatedRechargeCents(ctx, user.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -47,9 +43,9 @@ func (s *Service) GetHome(ctx context.Context, user AuthUser) (*RechargeRewardHo
|
|||||||
Enabled: bundle.Config != nil && bundle.Config.Enabled,
|
Enabled: bundle.Config != nil && bundle.Config.Enabled,
|
||||||
SysOrigin: sysOrigin,
|
SysOrigin: sysOrigin,
|
||||||
Timezone: location.String(),
|
Timezone: location.String(),
|
||||||
PeriodStartAt: formatDateTime(period.StartAt),
|
PeriodStartAt: formatDateTime(periodStart),
|
||||||
PeriodEndAt: formatDateTime(period.EndAt),
|
PeriodEndAt: formatDateTime(periodEnd),
|
||||||
CountdownMillis: maxInt64(0, period.EndAt.UnixMilli()-now.UnixMilli()),
|
CountdownMillis: maxInt64(0, periodEnd.UnixMilli()-now.UnixMilli()),
|
||||||
UserID: user.UserID,
|
UserID: user.UserID,
|
||||||
CurrentRechargeAmount: formatAmountCents(currentRechargeCents),
|
CurrentRechargeAmount: formatAmountCents(currentRechargeCents),
|
||||||
CurrentRechargeAmountCents: currentRechargeCents,
|
CurrentRechargeAmountCents: currentRechargeCents,
|
||||||
@ -92,6 +88,14 @@ func (s *Service) GetHome(ctx context.Context, user AuthUser) (*RechargeRewardHo
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) loadCurrentRechargeCents(ctx context.Context, userID int64) (int64, error) {
|
||||||
|
amount, err := s.java.GetThisMonthTotalPersonalRecharge(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return parseIntegrationAmountCents(integrationAmount(amount)), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) loadAccumulatedRechargeCents(ctx context.Context, userID int64) (int64, error) {
|
func (s *Service) loadAccumulatedRechargeCents(ctx context.Context, userID int64) (int64, error) {
|
||||||
recharges, err := s.java.MapUserTotalRecharge(ctx, []int64{userID})
|
recharges, err := s.java.MapUserTotalRecharge(ctx, []int64{userID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -100,6 +104,12 @@ func (s *Service) loadAccumulatedRechargeCents(ctx context.Context, userID int64
|
|||||||
return totalRechargeCents(recharges[userID]), nil
|
return totalRechargeCents(recharges[userID]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func currentMonthBounds(now time.Time, location *time.Location) (time.Time, time.Time) {
|
||||||
|
local := now.In(location)
|
||||||
|
start := time.Date(local.Year(), local.Month(), 1, 0, 0, 0, 0, location)
|
||||||
|
return start, start.AddDate(0, 1, 0)
|
||||||
|
}
|
||||||
|
|
||||||
func resolveLocation(timezone string, fallback *time.Location) *time.Location {
|
func resolveLocation(timezone string, fallback *time.Location) *time.Location {
|
||||||
location, err := time.LoadLocation(normalizeRechargeRewardTimezone(timezone))
|
location, err := time.LoadLocation(normalizeRechargeRewardTimezone(timezone))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@ -1,75 +0,0 @@
|
|||||||
package rechargereward
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type rechargeRewardPeriod struct {
|
|
||||||
CycleKey string
|
|
||||||
RechargeDate int
|
|
||||||
StartAt time.Time
|
|
||||||
EndAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) currentPeriod(now time.Time, location *time.Location) (rechargeRewardPeriod, bool) {
|
|
||||||
startAt := rechargeRewardStartAt(s.cfg.RechargeReward.StartDate, location)
|
|
||||||
return currentRechargeRewardPeriod(now, startAt, location)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) displayPeriod(now time.Time, location *time.Location) rechargeRewardPeriod {
|
|
||||||
startAt := rechargeRewardStartAt(s.cfg.RechargeReward.StartDate, location)
|
|
||||||
period, ok := currentRechargeRewardPeriod(now, startAt, location)
|
|
||||||
if ok {
|
|
||||||
return period
|
|
||||||
}
|
|
||||||
monthStart := time.Date(startAt.Year(), startAt.Month(), 1, 0, 0, 0, 0, location)
|
|
||||||
return rechargeRewardPeriod{
|
|
||||||
CycleKey: startAt.Format("20060102"),
|
|
||||||
RechargeDate: startAt.Year()*100 + int(startAt.Month()),
|
|
||||||
StartAt: startAt,
|
|
||||||
EndAt: monthStart.AddDate(0, 1, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func currentRechargeRewardPeriod(now time.Time, startAt time.Time, location *time.Location) (rechargeRewardPeriod, bool) {
|
|
||||||
local := now.In(location)
|
|
||||||
start := startAt.In(location)
|
|
||||||
if local.Before(start) {
|
|
||||||
return rechargeRewardPeriod{}, false
|
|
||||||
}
|
|
||||||
monthStart := time.Date(local.Year(), local.Month(), 1, 0, 0, 0, 0, location)
|
|
||||||
periodStart := monthStart
|
|
||||||
if periodStart.Before(start) {
|
|
||||||
periodStart = start
|
|
||||||
}
|
|
||||||
periodEnd := monthStart.AddDate(0, 1, 0)
|
|
||||||
if !local.Before(periodEnd) {
|
|
||||||
return rechargeRewardPeriod{}, false
|
|
||||||
}
|
|
||||||
return rechargeRewardPeriod{
|
|
||||||
CycleKey: periodStart.Format("20060102"),
|
|
||||||
RechargeDate: periodStart.Year()*100 + int(periodStart.Month()),
|
|
||||||
StartAt: periodStart,
|
|
||||||
EndAt: periodEnd,
|
|
||||||
}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func rechargeRewardStartAt(raw string, location *time.Location) time.Time {
|
|
||||||
value := strings.TrimSpace(raw)
|
|
||||||
if value == "" {
|
|
||||||
value = "2026-05-21"
|
|
||||||
}
|
|
||||||
parsed, err := time.ParseInLocation("2006-01-02", value, location)
|
|
||||||
if err != nil {
|
|
||||||
parsed, _ = time.ParseInLocation("2006-01-02", "2026-05-21", location)
|
|
||||||
}
|
|
||||||
return time.Date(parsed.Year(), parsed.Month(), parsed.Day(), 0, 0, 0, 0, location)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rechargeRewardStorageTime(t time.Time, storageLocation *time.Location) string {
|
|
||||||
if storageLocation == nil {
|
|
||||||
storageLocation = t.Location()
|
|
||||||
}
|
|
||||||
return t.In(storageLocation).Format("2006-01-02 15:04:05")
|
|
||||||
}
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
package rechargereward
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var rechargeRewardOrderPlatforms = []string{
|
|
||||||
"GOOGLE",
|
|
||||||
"PAYER_MAX",
|
|
||||||
"AIRWALLEX",
|
|
||||||
"PAYNICORN",
|
|
||||||
"STRIPE",
|
|
||||||
"PAY_PAL",
|
|
||||||
"CLIPSPAY",
|
|
||||||
}
|
|
||||||
|
|
||||||
type rechargeRewardAmountRow struct {
|
|
||||||
RechargeAmountCents int64 `gorm:"column:recharge_amount_cents"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) loadCurrentRechargeCents(ctx context.Context, sysOrigin string, userID int64, period rechargeRewardPeriod) (int64, error) {
|
|
||||||
if userID <= 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
sysOrigin = strings.ToUpper(strings.TrimSpace(sysOrigin))
|
|
||||||
startAt := rechargeRewardStorageTime(period.StartAt, s.storageLocation)
|
|
||||||
endAt := rechargeRewardStorageTime(period.EndAt, s.storageLocation)
|
|
||||||
coinSellerGoldPerUSD := s.cfg.RechargeReward.CoinSellerGoldPerUSD
|
|
||||||
if coinSellerGoldPerUSD <= 0 {
|
|
||||||
coinSellerGoldPerUSD = 100000
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = `
|
|
||||||
SELECT CAST(IFNULL(SUM(source.amount_cents), 0) AS SIGNED) AS recharge_amount_cents
|
|
||||||
FROM (
|
|
||||||
SELECT CAST(ROUND(SUM(IFNULL(t.unit_price, 0)) * 100, 0) AS SIGNED) AS amount_cents
|
|
||||||
FROM order_purchase_history AS t
|
|
||||||
INNER JOIN user_base_info AS u ON u.id = t.user_id
|
|
||||||
WHERE t.evn = 'PROD'
|
|
||||||
AND t.is_trial_period = 0
|
|
||||||
AND t.status = 'COMPLETE'
|
|
||||||
AND t.pay_platform IN ?
|
|
||||||
AND (u.is_del = 0 OR u.is_del IS NULL)
|
|
||||||
AND UPPER(TRIM(u.origin_sys)) = ?
|
|
||||||
AND t.user_id = ?
|
|
||||||
AND t.create_time >= ?
|
|
||||||
AND t.create_time < ?
|
|
||||||
UNION ALL
|
|
||||||
SELECT CAST(ROUND(SUM(IFNULL(t.compute_usd_amount, 0)) * 100, 0) AS SIGNED) AS amount_cents
|
|
||||||
FROM order_user_purchase_pay AS t
|
|
||||||
INNER JOIN user_base_info AS u ON u.id = t.user_id
|
|
||||||
WHERE t.evn = 'PROD'
|
|
||||||
AND t.factory_code = 'MIFA_PAY'
|
|
||||||
AND t.pay_status = 'SUCCESSFUL'
|
|
||||||
AND t.receipt_type = 'PAYMENT'
|
|
||||||
AND t.refund_status = 'NONE'
|
|
||||||
AND (u.is_del = 0 OR u.is_del IS NULL)
|
|
||||||
AND UPPER(TRIM(u.origin_sys)) = ?
|
|
||||||
AND t.user_id = ?
|
|
||||||
AND IFNULL(t.update_time, t.create_time) >= ?
|
|
||||||
AND IFNULL(t.update_time, t.create_time) < ?
|
|
||||||
UNION ALL
|
|
||||||
SELECT CAST(ROUND(SUM(IFNULL(t.quantity, 0) * 100 / ?), 0) AS SIGNED) AS amount_cents
|
|
||||||
FROM likei_wallet.user_freight_balance_running_water AS t
|
|
||||||
INNER JOIN user_base_info AS u ON u.id = t.accept_user_id
|
|
||||||
WHERE t.origin = 'SHIPMENT'
|
|
||||||
AND t.type = 1
|
|
||||||
AND t.accept_user_id IS NOT NULL
|
|
||||||
AND IFNULL(t.quantity, 0) > 0
|
|
||||||
AND (u.is_del = 0 OR u.is_del IS NULL)
|
|
||||||
AND UPPER(TRIM(u.origin_sys)) = ?
|
|
||||||
AND t.accept_user_id = ?
|
|
||||||
AND t.create_time >= ?
|
|
||||||
AND t.create_time < ?
|
|
||||||
) AS source`
|
|
||||||
|
|
||||||
var row rechargeRewardAmountRow
|
|
||||||
if err := s.db.WithContext(ctx).Raw(
|
|
||||||
query,
|
|
||||||
rechargeRewardOrderPlatforms,
|
|
||||||
sysOrigin,
|
|
||||||
userID,
|
|
||||||
startAt,
|
|
||||||
endAt,
|
|
||||||
sysOrigin,
|
|
||||||
userID,
|
|
||||||
startAt,
|
|
||||||
endAt,
|
|
||||||
coinSellerGoldPerUSD,
|
|
||||||
sysOrigin,
|
|
||||||
userID,
|
|
||||||
startAt,
|
|
||||||
endAt,
|
|
||||||
).Scan(&row).Error; err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if row.RechargeAmountCents < 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
return row.RechargeAmountCents, nil
|
|
||||||
}
|
|
||||||
@ -41,11 +41,10 @@ type rechargeRewardJavaGateway interface {
|
|||||||
|
|
||||||
// Service 负责充值奖励后台配置和 H5 用户态数据。
|
// Service 负责充值奖励后台配置和 H5 用户态数据。
|
||||||
type Service struct {
|
type Service struct {
|
||||||
cfg config.Config
|
cfg config.Config
|
||||||
db rechargeRewardDB
|
db rechargeRewardDB
|
||||||
java rechargeRewardJavaGateway
|
java rechargeRewardJavaGateway
|
||||||
location *time.Location
|
location *time.Location
|
||||||
storageLocation *time.Location
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(cfg config.Config, db rechargeRewardDB, java rechargeRewardJavaGateway) *Service {
|
func NewService(cfg config.Config, db rechargeRewardDB, java rechargeRewardJavaGateway) *Service {
|
||||||
@ -57,15 +56,7 @@ func NewService(cfg config.Config, db rechargeRewardDB, java rechargeRewardJavaG
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
location = time.UTC
|
location = time.UTC
|
||||||
}
|
}
|
||||||
storageTimezone := strings.TrimSpace(cfg.RechargeReward.StorageTimezone)
|
return &Service{cfg: cfg, db: db, java: java, location: location}
|
||||||
if storageTimezone == "" {
|
|
||||||
storageTimezone = "Asia/Shanghai"
|
|
||||||
}
|
|
||||||
storageLocation, err := time.LoadLocation(storageTimezone)
|
|
||||||
if err != nil {
|
|
||||||
storageLocation = location
|
|
||||||
}
|
|
||||||
return &Service{cfg: cfg, db: db, java: java, location: location, storageLocation: storageLocation}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RechargeRewardLevelPayload 是前后台共用的充值奖励档位读模型。
|
// RechargeRewardLevelPayload 是前后台共用的充值奖励档位读模型。
|
||||||
|
|||||||
@ -816,29 +816,6 @@ func TestNotifyRewardRecordPublishesIMAndRedisStream(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPublishRewardMessageTrimsBroadcastStreamByConfiguredMaxLen(t *testing.T) {
|
|
||||||
service, _, rdb, mr := newTestServiceWithRedis(t, newFakeRocketGateway())
|
|
||||||
defer mr.Close()
|
|
||||||
service.cfg.VoiceRoomRocket.BroadcastStreamMaxLen = 2
|
|
||||||
|
|
||||||
for idx := 1; idx <= 3; idx++ {
|
|
||||||
service.publishRewardMessage(context.Background(), 9001, "launch-"+strconv.Itoa(idx), map[string]any{
|
|
||||||
"type": imTypeRewardPopup,
|
|
||||||
"data": map[string]any{
|
|
||||||
"roomId": "9001",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
count, err := rdb.XLen(context.Background(), "voice_room:rocket:broadcast").Result()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("xlen broadcast: %v", err)
|
|
||||||
}
|
|
||||||
if count != 2 {
|
|
||||||
t.Fatalf("broadcast stream count = %d, want 2", count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatusUpdatePublishesRegionIM(t *testing.T) {
|
func TestStatusUpdatePublishesRegionIM(t *testing.T) {
|
||||||
gateway := newFakeRocketGateway()
|
gateway := newFakeRocketGateway()
|
||||||
service, db, _, mr := newTestServiceWithRedis(t, gateway)
|
service, db, _, mr := newTestServiceWithRedis(t, gateway)
|
||||||
@ -1033,8 +1010,6 @@ func newTestServiceWithRedis(t *testing.T, gateway *fakeRocketGateway) (*Service
|
|||||||
service.userProfiles = gateway
|
service.userProfiles = gateway
|
||||||
service.wallet = gateway
|
service.wallet = gateway
|
||||||
service.rewardGateway = gateway
|
service.rewardGateway = gateway
|
||||||
service.cfg.VoiceRoomRocket.BroadcastStreamKey = "voice_room:rocket:broadcast"
|
|
||||||
service.cfg.VoiceRoomRocket.BroadcastStreamMaxLen = 100
|
|
||||||
service.cfg.VoiceRoomRocket.InRoomRewardUserLimit = 3
|
service.cfg.VoiceRoomRocket.InRoomRewardUserLimit = 3
|
||||||
service.cfg.VoiceRoomRocket.InRoomRewardDueZSetKey = "voice_room:rocket:in_room_due"
|
service.cfg.VoiceRoomRocket.InRoomRewardDueZSetKey = "voice_room:rocket:in_room_due"
|
||||||
return service, db, rdb, mr
|
return service, db, rdb, mr
|
||||||
|
|||||||
@ -13,22 +13,6 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Service) broadcastStreamArgs(values map[string]any) *redis.XAddArgs {
|
|
||||||
streamKey := strings.TrimSpace(s.cfg.VoiceRoomRocket.BroadcastStreamKey)
|
|
||||||
if streamKey == "" {
|
|
||||||
streamKey = "voice_room:rocket:broadcast"
|
|
||||||
}
|
|
||||||
args := &redis.XAddArgs{
|
|
||||||
Stream: streamKey,
|
|
||||||
Values: values,
|
|
||||||
}
|
|
||||||
if s.cfg.VoiceRoomRocket.BroadcastStreamMaxLen > 0 {
|
|
||||||
args.MaxLen = s.cfg.VoiceRoomRocket.BroadcastStreamMaxLen
|
|
||||||
args.Approx = true
|
|
||||||
}
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) notifyStatusUpdate(ctx context.Context, sysOrigin string, roomID int64, senderUserIDs ...int64) error {
|
func (s *Service) notifyStatusUpdate(ctx context.Context, sysOrigin string, roomID int64, senderUserIDs ...int64) error {
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
"roomId": strconv.FormatInt(roomID, 10),
|
"roomId": strconv.FormatInt(roomID, 10),
|
||||||
@ -51,11 +35,18 @@ func (s *Service) notifyStatusUpdate(ctx context.Context, sysOrigin string, room
|
|||||||
"roomId": strconv.FormatInt(roomID, 10),
|
"roomId": strconv.FormatInt(roomID, 10),
|
||||||
"data": data,
|
"data": data,
|
||||||
})
|
})
|
||||||
_ = s.repo.Redis.XAdd(ctx, s.broadcastStreamArgs(map[string]any{
|
streamKey := strings.TrimSpace(s.cfg.VoiceRoomRocket.BroadcastStreamKey)
|
||||||
"type": imTypeStatusUpdate,
|
if streamKey == "" {
|
||||||
"roomId": strconv.FormatInt(roomID, 10),
|
streamKey = "voice_room:rocket:broadcast"
|
||||||
"body": string(body),
|
}
|
||||||
})).Err()
|
_ = s.repo.Redis.XAdd(ctx, &redis.XAddArgs{
|
||||||
|
Stream: streamKey,
|
||||||
|
Values: map[string]any{
|
||||||
|
"type": imTypeStatusUpdate,
|
||||||
|
"roomId": strconv.FormatInt(roomID, 10),
|
||||||
|
"body": string(body),
|
||||||
|
},
|
||||||
|
}).Err()
|
||||||
}
|
}
|
||||||
_ = s.notifyRegionStatusUpdate(ctx, sysOrigin, senderUserID, data)
|
_ = s.notifyRegionStatusUpdate(ctx, sysOrigin, senderUserID, data)
|
||||||
if s.im == nil {
|
if s.im == nil {
|
||||||
@ -156,12 +147,19 @@ func (s *Service) notifyLaunch(ctx context.Context, launch model.VoiceRoomRocket
|
|||||||
}
|
}
|
||||||
if s.repo.Redis != nil {
|
if s.repo.Redis != nil {
|
||||||
payload, _ := json.Marshal(body)
|
payload, _ := json.Marshal(body)
|
||||||
_ = s.repo.Redis.XAdd(ctx, s.broadcastStreamArgs(map[string]any{
|
streamKey := strings.TrimSpace(s.cfg.VoiceRoomRocket.BroadcastStreamKey)
|
||||||
"type": imTypeLaunchBroadcast,
|
if streamKey == "" {
|
||||||
"roomId": strconv.FormatInt(launch.RoomID, 10),
|
streamKey = "voice_room:rocket:broadcast"
|
||||||
"launchNo": launch.LaunchNo,
|
}
|
||||||
"body": string(payload),
|
_ = s.repo.Redis.XAdd(ctx, &redis.XAddArgs{
|
||||||
})).Err()
|
Stream: streamKey,
|
||||||
|
Values: map[string]any{
|
||||||
|
"type": imTypeLaunchBroadcast,
|
||||||
|
"roomId": strconv.FormatInt(launch.RoomID, 10),
|
||||||
|
"launchNo": launch.LaunchNo,
|
||||||
|
"body": string(payload),
|
||||||
|
},
|
||||||
|
}).Err()
|
||||||
}
|
}
|
||||||
if s.im == nil {
|
if s.im == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -243,12 +241,19 @@ func (s *Service) publishRewardMessage(ctx context.Context, roomID int64, launch
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
payload, _ := json.Marshal(body)
|
payload, _ := json.Marshal(body)
|
||||||
_ = s.repo.Redis.XAdd(ctx, s.broadcastStreamArgs(map[string]any{
|
streamKey := strings.TrimSpace(s.cfg.VoiceRoomRocket.BroadcastStreamKey)
|
||||||
"type": body["type"],
|
if streamKey == "" {
|
||||||
"roomId": strconv.FormatInt(roomID, 10),
|
streamKey = "voice_room:rocket:broadcast"
|
||||||
"launchNo": launchNo,
|
}
|
||||||
"body": string(payload),
|
_ = s.repo.Redis.XAdd(ctx, &redis.XAddArgs{
|
||||||
})).Err()
|
Stream: streamKey,
|
||||||
|
Values: map[string]any{
|
||||||
|
"type": body["type"],
|
||||||
|
"roomId": strconv.FormatInt(roomID, 10),
|
||||||
|
"launchNo": launchNo,
|
||||||
|
"body": string(payload),
|
||||||
|
},
|
||||||
|
}).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) resolveRoomAccount(ctx context.Context, roomID int64) string {
|
func (s *Service) resolveRoomAccount(ctx context.Context, roomID int64) string {
|
||||||
|
|||||||
@ -1,38 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS `recharge_reward_grant_record` (
|
|
||||||
`id` bigint NOT NULL COMMENT '主键ID',
|
|
||||||
`config_id` bigint NOT NULL COMMENT '充值奖励主配置ID',
|
|
||||||
`level_id` bigint NOT NULL COMMENT '充值奖励档位ID',
|
|
||||||
`sys_origin` varchar(32) NOT NULL COMMENT '来源系统',
|
|
||||||
`cycle_key` varchar(32) NOT NULL COMMENT '充值周期标识,周期开始日yyyyMMdd',
|
|
||||||
`recharge_date` int NOT NULL COMMENT '充值年月yyyyMM,用于后台筛选',
|
|
||||||
`period_start_at` datetime(3) NOT NULL COMMENT '周期开始时间,活动时区墙上时间',
|
|
||||||
`period_end_at` datetime(3) NOT NULL COMMENT '周期结束时间,活动时区墙上时间',
|
|
||||||
`user_id` bigint NOT NULL COMMENT '用户ID',
|
|
||||||
`account` varchar(64) NOT NULL DEFAULT '' COMMENT '用户账号快照',
|
|
||||||
`user_avatar` varchar(1024) NOT NULL DEFAULT '' COMMENT '用户头像快照',
|
|
||||||
`user_nickname` varchar(255) NOT NULL DEFAULT '' COMMENT '用户昵称快照',
|
|
||||||
`country_code` varchar(32) NOT NULL DEFAULT '' COMMENT '国家编码快照',
|
|
||||||
`country_name` varchar(128) NOT NULL DEFAULT '' COMMENT '国家名称快照',
|
|
||||||
`recharge_amount_cents` bigint NOT NULL DEFAULT '0' COMMENT '发放时周期累计充值金额,单位为分',
|
|
||||||
`level` int NOT NULL COMMENT '奖励档位',
|
|
||||||
`recharge_threshold_cents` bigint NOT NULL DEFAULT '0' COMMENT '充值金额门槛,单位为分',
|
|
||||||
`reward_gold` bigint NOT NULL DEFAULT '0' COMMENT '金币奖励数量',
|
|
||||||
`reward_group_id` bigint DEFAULT NULL COMMENT '奖励组ID',
|
|
||||||
`reward_group_name` varchar(255) NOT NULL DEFAULT '' COMMENT '奖励组名称快照',
|
|
||||||
`reward_group_track_id` bigint NOT NULL DEFAULT '0' COMMENT '奖励组发放幂等追踪ID',
|
|
||||||
`gold_event_id` varchar(128) NOT NULL DEFAULT '' COMMENT '金币发放钱包事件ID',
|
|
||||||
`status` varchar(32) NOT NULL DEFAULT 'PENDING' COMMENT '整体发放状态:PENDING/SUCCESS/FAILED',
|
|
||||||
`reward_group_status` varchar(32) NOT NULL DEFAULT 'SKIPPED' COMMENT '奖励组发放状态:PENDING/SUCCESS/FAILED/SKIPPED',
|
|
||||||
`reward_gold_status` varchar(32) NOT NULL DEFAULT 'SKIPPED' COMMENT '金币发放状态:PENDING/SUCCESS/FAILED/SKIPPED',
|
|
||||||
`retry_count` int NOT NULL DEFAULT '0' COMMENT '重试次数',
|
|
||||||
`last_error` varchar(1024) NOT NULL DEFAULT '' COMMENT '最近一次失败原因',
|
|
||||||
`sent_at` datetime(3) DEFAULT NULL COMMENT '整体发放成功时间',
|
|
||||||
`create_time` datetime(3) DEFAULT NULL COMMENT '创建时间',
|
|
||||||
`update_time` datetime(3) DEFAULT NULL COMMENT '更新时间',
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uk_recharge_reward_grant_level` (`sys_origin`, `cycle_key`, `user_id`, `level`),
|
|
||||||
KEY `idx_recharge_reward_grant_config_cycle` (`config_id`, `cycle_key`, `status`),
|
|
||||||
KEY `idx_recharge_reward_grant_user` (`user_id`, `recharge_date`),
|
|
||||||
KEY `idx_recharge_reward_grant_date` (`sys_origin`, `recharge_date`, `status`),
|
|
||||||
KEY `idx_recharge_reward_gold_event` (`gold_event_id`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='充值奖励周期发放记录';
|
|
||||||
Loading…
x
Reference in New Issue
Block a user