Reconcile game metrics from source tables
This commit is contained in:
parent
bf06fe6a8f
commit
5e7167da4f
@ -84,6 +84,7 @@ func main() {
|
||||
projector := dashboard.NewProjector(cfg, repo, redisClient, metrics)
|
||||
repo.StartUserReconciler(ctx, logger, projector)
|
||||
repo.StartRechargeReconciler(ctx, logger, projector)
|
||||
repo.StartGameReconciler(ctx, logger)
|
||||
|
||||
if cfg.Mongo.Enabled {
|
||||
mongoClient, err := mongo.Connect(options.Client().ApplyURI(cfg.Mongo.URI))
|
||||
|
||||
@ -18,6 +18,7 @@ type Config struct {
|
||||
Dashboard DashboardConfig
|
||||
Recharge RechargeReconcileConfig
|
||||
Gift GiftReconcileConfig
|
||||
Game GameReconcileConfig
|
||||
User UserReconcileConfig
|
||||
Active ActiveReconcileConfig
|
||||
Health HealthConfig
|
||||
@ -87,6 +88,12 @@ type GiftReconcileConfig struct {
|
||||
BatchLimit int
|
||||
}
|
||||
|
||||
type GameReconcileConfig struct {
|
||||
Enabled bool
|
||||
Interval time.Duration
|
||||
CurrentDayLag time.Duration
|
||||
}
|
||||
|
||||
type UserReconcileConfig struct {
|
||||
Enabled bool
|
||||
Interval time.Duration
|
||||
@ -166,6 +173,11 @@ func Load() (Config, error) {
|
||||
CurrentDayLag: envDuration("DASHBOARD_GIFT_RECONCILE_CURRENT_DAY_LAG", 5*time.Minute),
|
||||
BatchLimit: envInt("DASHBOARD_GIFT_RECONCILE_BATCH_LIMIT", 50000),
|
||||
},
|
||||
Game: GameReconcileConfig{
|
||||
Enabled: envBool("DASHBOARD_GAME_RECONCILE_ENABLED", true),
|
||||
Interval: envDuration("DASHBOARD_GAME_RECONCILE_INTERVAL", 10*time.Minute),
|
||||
CurrentDayLag: envDuration("DASHBOARD_GAME_RECONCILE_CURRENT_DAY_LAG", 5*time.Minute),
|
||||
},
|
||||
User: UserReconcileConfig{
|
||||
Enabled: envBool("DASHBOARD_USER_RECONCILE_ENABLED", true),
|
||||
Interval: envDuration("DASHBOARD_USER_RECONCILE_INTERVAL", 10*time.Minute),
|
||||
|
||||
68
internal/storage/game_reconcile.go
Normal file
68
internal/storage/game_reconcile.go
Normal file
@ -0,0 +1,68 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"log/slog"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (r *MySQLRepository) StartGameReconciler(ctx context.Context, logger *slog.Logger) {
|
||||
if !r.cfg.Game.Enabled || r.cfg.Game.Interval <= 0 {
|
||||
return
|
||||
}
|
||||
run := func() {
|
||||
since := currentStatDayStart(r.cfg.Dashboard.StatTimezones, r.cfg.Game.CurrentDayLag)
|
||||
until := since.AddDate(0, 0, 1)
|
||||
if err := r.RebuildGameMetricsRange(ctx, since, until); err != nil {
|
||||
if r.metrics != nil {
|
||||
r.metrics.RecordError(err)
|
||||
}
|
||||
logger.Error("dashboard game reconcile failed", "since", since, "until", until, "error", err)
|
||||
return
|
||||
}
|
||||
logger.Info("dashboard game reconcile applied", "since", since, "until", until)
|
||||
}
|
||||
go func() {
|
||||
run()
|
||||
ticker := time.NewTicker(r.cfg.Game.Interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
run()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (r *MySQLRepository) RebuildGameMetricsRange(ctx context.Context, start, end time.Time) error {
|
||||
return r.WithDashboardLock(ctx, r.cfg.CDC.BackfillLockWait, func() error {
|
||||
return r.WithTx(ctx, func(tx *sql.Tx) error {
|
||||
for _, statTimezone := range r.cfg.Dashboard.StatTimezones {
|
||||
location, err := time.LoadLocation(statTimezone)
|
||||
if err != nil {
|
||||
location = time.UTC
|
||||
}
|
||||
for day := start.In(location); day.Before(end); day = day.AddDate(0, 0, 1) {
|
||||
dayStart := time.Date(day.Year(), day.Month(), day.Day(), 0, 0, 0, 0, location)
|
||||
dayEnd := dayStart.AddDate(0, 0, 1)
|
||||
for _, sysOrigin := range r.cfg.Dashboard.SysOrigins {
|
||||
if err := r.replaceGameMetricDay(ctx, tx, statTimezone, sysOrigin, dayStart, dayEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := r.rebuildAllPeriodFromDays(ctx, tx, statTimezone); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.rebuildAllGamePeriodFromDays(ctx, tx, statTimezone); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
458
internal/storage/game_sql.go
Normal file
458
internal/storage/game_sql.go
Normal file
@ -0,0 +1,458 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (r *MySQLRepository) replaceGameMetricDay(ctx context.Context, tx *sql.Tx,
|
||||
statTimezone string, sysOrigin string, dayStart, dayEnd time.Time) error {
|
||||
dayKey := dayStart.Format("2006-01-02")
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
UPDATE country_dashboard_period_metric
|
||||
SET game_total_flow = 0, game_user = 0, game_payout = 0, refreshed_at = NOW()
|
||||
WHERE period_type = 'DAY' AND period_key = ? AND stat_timezone = ? AND sys_origin = ?`,
|
||||
dayKey, statTimezone, sysOrigin); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
DELETE FROM country_dashboard_period_user_metric
|
||||
WHERE period_type = 'DAY' AND period_key = ? AND stat_timezone = ? AND sys_origin = ?
|
||||
AND metric_type = ?`, dayKey, statTimezone, sysOrigin, "GAME_USER"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
DELETE FROM country_dashboard_game_period_user_metric
|
||||
WHERE period_type = 'DAY' AND period_key = ? AND stat_timezone = ? AND sys_origin = ?`,
|
||||
dayKey, statTimezone, sysOrigin); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
DELETE FROM country_dashboard_game_period_metric
|
||||
WHERE period_type = 'DAY' AND period_key = ? AND stat_timezone = ? AND sys_origin = ?`,
|
||||
dayKey, statTimezone, sysOrigin); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.insertWalletGamePeriodAmounts(ctx, tx, statTimezone, sysOrigin, dayStart, dayEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.insertLuckyBoxGamePeriodAmounts(ctx, tx, statTimezone, sysOrigin, dayStart, dayEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.refreshOverviewGameAmounts(ctx, tx, statTimezone, sysOrigin, dayStart, dayEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.insertWalletGamePeriodUsers(ctx, tx, statTimezone, sysOrigin, dayStart, dayEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.insertLuckyBoxGamePeriodUsers(ctx, tx, statTimezone, sysOrigin, dayStart, dayEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.refreshOverviewGameUsers(ctx, tx, statTimezone, sysOrigin, dayStart, dayEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.refreshGameDimensionUsers(ctx, tx, statTimezone, sysOrigin, dayStart, dayEnd)
|
||||
}
|
||||
|
||||
func (r *MySQLRepository) insertWalletGamePeriodAmounts(ctx context.Context, tx *sql.Tx,
|
||||
statTimezone string, sysOrigin string, dayStart, dayEnd time.Time) error {
|
||||
sourceSQL, sourceArgs := r.walletGameSourceSQL(sysOrigin, dayStart, dayEnd)
|
||||
dayKey := dayStart.Format("2006-01-02")
|
||||
args := append([]interface{}{
|
||||
dayKey, statTimezone, dayKey, dayStart, dayEnd,
|
||||
r.cfg.Dashboard.UnknownCountryCode, r.cfg.Dashboard.UnknownCountryCode,
|
||||
}, sourceArgs...)
|
||||
query := `
|
||||
INSERT INTO country_dashboard_game_period_metric (
|
||||
period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date,
|
||||
sys_origin, country_code, country_name, game_provider, game_id, game_name,
|
||||
consume_amount, payout_amount, profit_amount, order_count, refreshed_at
|
||||
)
|
||||
SELECT 'DAY', ?, ?, ?, ?, ?,
|
||||
t.sys_origin,
|
||||
IFNULL(NULLIF(u.country_code, ''), ?) AS country_code,
|
||||
IFNULL(NULLIF(u.country_name, ''), IFNULL(NULLIF(u.country_code, ''), ?)) AS country_name,
|
||||
` + walletGameProviderExpr() + ` AS game_provider,
|
||||
` + walletGameIDExpr() + ` AS game_id,
|
||||
MAX(` + walletGameNameExpr() + `) AS game_name,
|
||||
IFNULL(SUM(CASE WHEN t.type = 1 THEN t.penny_amount ELSE 0 END) / 100.00, 0) AS consume_amount,
|
||||
IFNULL(SUM(CASE WHEN t.type = 0 THEN t.penny_amount ELSE 0 END) / 100.00, 0) AS payout_amount,
|
||||
IFNULL((SUM(CASE WHEN t.type = 1 THEN t.penny_amount ELSE 0 END)
|
||||
- SUM(CASE WHEN t.type = 0 THEN t.penny_amount ELSE 0 END)) / 100.00, 0) AS profit_amount,
|
||||
COUNT(1) AS order_count,
|
||||
NOW()
|
||||
FROM (` + sourceSQL + `) t
|
||||
INNER JOIN ` + quoteIdent(r.cfg.MySQL.Schema) + `.user_base_info u ON u.id = t.user_id
|
||||
` + r.walletGameJoinCatalogs() + `
|
||||
WHERE ` + walletGameEventWhere() + `
|
||||
AND (u.is_del = 0 OR u.is_del IS NULL)
|
||||
AND u.origin_sys = t.sys_origin
|
||||
GROUP BY country_code, country_name, game_provider, game_id
|
||||
ON DUPLICATE KEY UPDATE
|
||||
country_name = VALUES(country_name),
|
||||
game_name = VALUES(game_name),
|
||||
consume_amount = consume_amount + VALUES(consume_amount),
|
||||
payout_amount = payout_amount + VALUES(payout_amount),
|
||||
profit_amount = profit_amount + VALUES(profit_amount),
|
||||
order_count = order_count + VALUES(order_count),
|
||||
refreshed_at = NOW()`
|
||||
_, err := tx.ExecContext(ctx, query, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *MySQLRepository) insertLuckyBoxGamePeriodAmounts(ctx context.Context, tx *sql.Tx,
|
||||
statTimezone string, sysOrigin string, dayStart, dayEnd time.Time) error {
|
||||
dayKey := dayStart.Format("2006-01-02")
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO country_dashboard_game_period_metric (
|
||||
period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date,
|
||||
sys_origin, country_code, country_name, game_provider, game_id, game_name,
|
||||
consume_amount, payout_amount, profit_amount, order_count, refreshed_at
|
||||
)
|
||||
SELECT 'DAY', ?, ?, ?, ?, ?,
|
||||
t.sys_origin,
|
||||
IFNULL(NULLIF(u.country_code, ''), ?) AS country_code,
|
||||
IFNULL(NULLIF(u.country_name, ''), IFNULL(NULLIF(u.country_code, ''), ?)) AS country_name,
|
||||
'INTERNAL', 'LUCKY_BOX', 'Lucky Box',
|
||||
IFNULL(SUM(t.pay_amount), 0),
|
||||
IFNULL(SUM(t.gift_amount), 0),
|
||||
IFNULL(SUM(t.pay_amount), 0) - IFNULL(SUM(t.gift_amount), 0),
|
||||
COUNT(1),
|
||||
NOW()
|
||||
FROM `+quoteIdent(r.cfg.MySQL.Schema)+`.game_lucky_box_count t
|
||||
INNER JOIN `+quoteIdent(r.cfg.MySQL.Schema)+`.user_base_info u ON u.id = t.user_id
|
||||
WHERE t.create_time >= ? AND t.create_time < ?
|
||||
AND t.sys_origin = ?
|
||||
AND (u.is_del = 0 OR u.is_del IS NULL)
|
||||
AND u.origin_sys = t.sys_origin
|
||||
GROUP BY country_code, country_name
|
||||
ON DUPLICATE KEY UPDATE
|
||||
country_name = VALUES(country_name),
|
||||
game_name = VALUES(game_name),
|
||||
consume_amount = consume_amount + VALUES(consume_amount),
|
||||
payout_amount = payout_amount + VALUES(payout_amount),
|
||||
profit_amount = profit_amount + VALUES(profit_amount),
|
||||
order_count = order_count + VALUES(order_count),
|
||||
refreshed_at = NOW()`, dayKey, statTimezone, dayKey, dayStart, dayEnd,
|
||||
r.cfg.Dashboard.UnknownCountryCode, r.cfg.Dashboard.UnknownCountryCode,
|
||||
mysqlTime(dayStart), mysqlTime(dayEnd), sysOrigin)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *MySQLRepository) refreshOverviewGameAmounts(ctx context.Context, tx *sql.Tx,
|
||||
statTimezone string, sysOrigin string, dayStart, dayEnd time.Time) error {
|
||||
dayKey := dayStart.Format("2006-01-02")
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO country_dashboard_period_metric (
|
||||
period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date,
|
||||
sys_origin, country_code, country_name, game_total_flow, game_payout, refreshed_at
|
||||
)
|
||||
SELECT 'DAY', ?, ?, ?, ?, ?,
|
||||
sys_origin, country_code, MAX(country_name),
|
||||
IFNULL(SUM(consume_amount), 0), IFNULL(SUM(payout_amount), 0), NOW()
|
||||
FROM country_dashboard_game_period_metric
|
||||
WHERE period_type = 'DAY' AND period_key = ? AND stat_timezone = ? AND sys_origin = ?
|
||||
GROUP BY sys_origin, country_code
|
||||
ON DUPLICATE KEY UPDATE
|
||||
country_name = VALUES(country_name),
|
||||
game_total_flow = VALUES(game_total_flow),
|
||||
game_payout = VALUES(game_payout),
|
||||
refreshed_at = NOW()`, dayKey, statTimezone, dayKey, dayStart, dayEnd,
|
||||
dayKey, statTimezone, sysOrigin)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *MySQLRepository) insertWalletGamePeriodUsers(ctx context.Context, tx *sql.Tx,
|
||||
statTimezone string, sysOrigin string, dayStart, dayEnd time.Time) error {
|
||||
sourceSQL, sourceArgs := r.walletGameSourceSQL(sysOrigin, dayStart, dayEnd)
|
||||
dayKey := dayStart.Format("2006-01-02")
|
||||
args := append([]interface{}{
|
||||
dayKey, statTimezone, dayKey, dayStart, dayEnd,
|
||||
r.cfg.Dashboard.UnknownCountryCode, r.cfg.Dashboard.UnknownCountryCode,
|
||||
}, sourceArgs...)
|
||||
query := `
|
||||
INSERT IGNORE INTO country_dashboard_game_period_user_metric (
|
||||
period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date,
|
||||
sys_origin, country_code, country_name, game_provider, game_id, user_id
|
||||
)
|
||||
SELECT DISTINCT 'DAY', ?, ?, ?, ?, ?,
|
||||
t.sys_origin,
|
||||
IFNULL(NULLIF(u.country_code, ''), ?) AS country_code,
|
||||
IFNULL(NULLIF(u.country_name, ''), IFNULL(NULLIF(u.country_code, ''), ?)) AS country_name,
|
||||
` + walletGameProviderExpr() + ` AS game_provider,
|
||||
` + walletGameIDExpr() + ` AS game_id,
|
||||
t.user_id
|
||||
FROM (` + sourceSQL + `) t
|
||||
INNER JOIN ` + quoteIdent(r.cfg.MySQL.Schema) + `.user_base_info u ON u.id = t.user_id
|
||||
` + r.walletGameJoinCatalogs() + `
|
||||
WHERE ` + walletGameEventWhere() + `
|
||||
AND (u.is_del = 0 OR u.is_del IS NULL)
|
||||
AND u.origin_sys = t.sys_origin`
|
||||
_, err := tx.ExecContext(ctx, query, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *MySQLRepository) insertLuckyBoxGamePeriodUsers(ctx context.Context, tx *sql.Tx,
|
||||
statTimezone string, sysOrigin string, dayStart, dayEnd time.Time) error {
|
||||
dayKey := dayStart.Format("2006-01-02")
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
INSERT IGNORE INTO country_dashboard_game_period_user_metric (
|
||||
period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date,
|
||||
sys_origin, country_code, country_name, game_provider, game_id, user_id
|
||||
)
|
||||
SELECT DISTINCT 'DAY', ?, ?, ?, ?, ?,
|
||||
t.sys_origin,
|
||||
IFNULL(NULLIF(u.country_code, ''), ?) AS country_code,
|
||||
IFNULL(NULLIF(u.country_name, ''), IFNULL(NULLIF(u.country_code, ''), ?)) AS country_name,
|
||||
'INTERNAL', 'LUCKY_BOX', t.user_id
|
||||
FROM `+quoteIdent(r.cfg.MySQL.Schema)+`.game_lucky_box_count t
|
||||
INNER JOIN `+quoteIdent(r.cfg.MySQL.Schema)+`.user_base_info u ON u.id = t.user_id
|
||||
WHERE t.create_time >= ? AND t.create_time < ?
|
||||
AND t.sys_origin = ?
|
||||
AND (u.is_del = 0 OR u.is_del IS NULL)
|
||||
AND u.origin_sys = t.sys_origin`, dayKey, statTimezone, dayKey, dayStart, dayEnd,
|
||||
r.cfg.Dashboard.UnknownCountryCode, r.cfg.Dashboard.UnknownCountryCode,
|
||||
mysqlTime(dayStart), mysqlTime(dayEnd), sysOrigin)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *MySQLRepository) refreshOverviewGameUsers(ctx context.Context, tx *sql.Tx,
|
||||
statTimezone string, sysOrigin string, dayStart, dayEnd time.Time) error {
|
||||
dayKey := dayStart.Format("2006-01-02")
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
INSERT IGNORE INTO country_dashboard_period_user_metric (
|
||||
period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date,
|
||||
sys_origin, country_code, country_name, user_id, metric_type
|
||||
)
|
||||
SELECT DISTINCT period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date,
|
||||
sys_origin, country_code, country_name, user_id, ?
|
||||
FROM country_dashboard_game_period_user_metric
|
||||
WHERE period_type = 'DAY' AND period_key = ? AND stat_timezone = ? AND sys_origin = ?`,
|
||||
"GAME_USER", dayKey, statTimezone, sysOrigin); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO country_dashboard_period_metric (
|
||||
period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date,
|
||||
sys_origin, country_code, country_name, game_user, refreshed_at
|
||||
)
|
||||
SELECT 'DAY', ?, ?, ?, ?, ?, sys_origin, country_code, MAX(country_name), COUNT(1), NOW()
|
||||
FROM country_dashboard_period_user_metric
|
||||
WHERE period_type = 'DAY' AND period_key = ? AND stat_timezone = ? AND sys_origin = ? AND metric_type = ?
|
||||
GROUP BY sys_origin, country_code
|
||||
ON DUPLICATE KEY UPDATE
|
||||
country_name = VALUES(country_name),
|
||||
game_user = VALUES(game_user),
|
||||
refreshed_at = NOW()`, dayKey, statTimezone, dayKey, dayStart, dayEnd,
|
||||
dayKey, statTimezone, sysOrigin, "GAME_USER")
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *MySQLRepository) refreshGameDimensionUsers(ctx context.Context, tx *sql.Tx,
|
||||
statTimezone string, sysOrigin string, dayStart, dayEnd time.Time) error {
|
||||
dayKey := dayStart.Format("2006-01-02")
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO country_dashboard_game_period_metric (
|
||||
period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date,
|
||||
sys_origin, country_code, country_name, game_provider, game_id, game_name, user_count, refreshed_at
|
||||
)
|
||||
SELECT 'DAY', ?, ?, ?, ?, ?,
|
||||
u.sys_origin, u.country_code, MAX(u.country_name), u.game_provider, u.game_id,
|
||||
COALESCE(MAX(m.game_name), u.game_id), COUNT(1), NOW()
|
||||
FROM country_dashboard_game_period_user_metric u
|
||||
LEFT JOIN country_dashboard_game_period_metric m
|
||||
ON m.period_type = u.period_type AND m.period_key = u.period_key AND m.stat_timezone = u.stat_timezone
|
||||
AND m.sys_origin = u.sys_origin AND m.country_code = u.country_code
|
||||
AND m.game_provider = u.game_provider AND m.game_id = u.game_id
|
||||
WHERE u.period_type = 'DAY' AND u.period_key = ? AND u.stat_timezone = ? AND u.sys_origin = ?
|
||||
GROUP BY u.sys_origin, u.country_code, u.game_provider, u.game_id
|
||||
ON DUPLICATE KEY UPDATE
|
||||
country_name = VALUES(country_name),
|
||||
game_name = VALUES(game_name),
|
||||
user_count = VALUES(user_count),
|
||||
refreshed_at = NOW()`, dayKey, statTimezone, dayKey, dayStart, dayEnd,
|
||||
dayKey, statTimezone, sysOrigin)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *MySQLRepository) rebuildAllGamePeriodFromDays(ctx context.Context, tx *sql.Tx, statTimezone string) error {
|
||||
for _, sysOrigin := range r.cfg.Dashboard.SysOrigins {
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
DELETE FROM country_dashboard_game_period_user_metric
|
||||
WHERE period_type = 'ALL' AND period_key = 'ALL' AND stat_timezone = ? AND sys_origin = ?`,
|
||||
statTimezone, sysOrigin); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
INSERT IGNORE INTO country_dashboard_game_period_user_metric (
|
||||
period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date,
|
||||
sys_origin, country_code, country_name, game_provider, game_id, user_id
|
||||
)
|
||||
SELECT DISTINCT 'ALL', 'ALL', ?, '全部', NULL, NULL,
|
||||
sys_origin, country_code, country_name, game_provider, game_id, user_id
|
||||
FROM country_dashboard_game_period_user_metric
|
||||
WHERE period_type = 'DAY' AND stat_timezone = ? AND sys_origin = ?`,
|
||||
statTimezone, statTimezone, sysOrigin); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
DELETE FROM country_dashboard_game_period_metric
|
||||
WHERE period_type = 'ALL' AND period_key = 'ALL' AND stat_timezone = ? AND sys_origin = ?`,
|
||||
statTimezone, sysOrigin); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO country_dashboard_game_period_metric (
|
||||
period_type, period_key, stat_timezone, period_name, period_start_date, period_end_date,
|
||||
sys_origin, country_code, country_name, game_provider, game_id, game_name,
|
||||
consume_amount, payout_amount, profit_amount, user_count, order_count, refreshed_at
|
||||
)
|
||||
SELECT 'ALL', 'ALL', ?, '全部', NULL, NULL,
|
||||
d.sys_origin, d.country_code, MAX(d.country_name), d.game_provider, d.game_id, MAX(d.game_name),
|
||||
IFNULL(SUM(d.consume_amount), 0),
|
||||
IFNULL(SUM(d.payout_amount), 0),
|
||||
IFNULL(SUM(d.profit_amount), 0),
|
||||
IFNULL(MAX(u.user_count), 0),
|
||||
IFNULL(SUM(d.order_count), 0),
|
||||
NOW()
|
||||
FROM country_dashboard_game_period_metric d
|
||||
LEFT JOIN (
|
||||
SELECT country_code, game_provider, game_id, COUNT(1) AS user_count
|
||||
FROM country_dashboard_game_period_user_metric
|
||||
WHERE period_type = 'ALL' AND period_key = 'ALL' AND stat_timezone = ? AND sys_origin = ?
|
||||
GROUP BY country_code, game_provider, game_id
|
||||
) u ON u.country_code = d.country_code AND u.game_provider = d.game_provider AND u.game_id = d.game_id
|
||||
WHERE d.period_type = 'DAY' AND d.stat_timezone = ? AND d.sys_origin = ?
|
||||
GROUP BY d.sys_origin, d.country_code, d.game_provider, d.game_id`,
|
||||
statTimezone, statTimezone, sysOrigin, statTimezone, sysOrigin); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *MySQLRepository) walletGameSourceSQL(sysOrigin string, start, end time.Time) (string, []interface{}) {
|
||||
startMS := start.UnixMilli()
|
||||
endMS := end.UnixMilli()
|
||||
parts := make([]string, 0, 12)
|
||||
args := make([]interface{}, 0, 36)
|
||||
for i := 1; i <= 12; i++ {
|
||||
parts = append(parts, `SELECT user_id, sys_origin, type, penny_amount, event_type, event_id
|
||||
FROM `+quoteIdent(r.cfg.MySQL.WalletSchema)+`.`+quoteIdent("wallet_gold_asset_record_"+strconv.Itoa(i))+`
|
||||
WHERE create_time >= ? AND create_time < ? AND sys_origin = ?`)
|
||||
args = append(args, startMS, endMS, sysOrigin)
|
||||
}
|
||||
return strings.Join(parts, "\nUNION ALL\n"), args
|
||||
}
|
||||
|
||||
func (r *MySQLRepository) walletGameJoinCatalogs() string {
|
||||
schema := quoteIdent(r.cfg.MySQL.Schema)
|
||||
idExpr := walletGameIDExpr()
|
||||
return `
|
||||
LEFT JOIN ` + schema + `.baishun_order_idempotency bo
|
||||
ON bo.sys_origin = t.sys_origin COLLATE utf8mb4_0900_ai_ci
|
||||
AND bo.wallet_event_id = t.event_id COLLATE utf8mb4_0900_ai_ci
|
||||
LEFT JOIN ` + schema + `.game_open_callback_log go
|
||||
ON go.sys_origin = t.sys_origin COLLATE utf8mb4_0900_ai_ci
|
||||
AND t.event_id COLLATE utf8mb4_0900_ai_ci = CONCAT('GAME_OPEN:', go.order_id)
|
||||
LEFT JOIN ` + schema + `.baishun_game_catalog bc
|
||||
ON (bo.vendor_game_id IS NOT NULL OR t.event_type LIKE 'BAISHUN_GAME_%')
|
||||
AND bc.sys_origin = t.sys_origin COLLATE utf8mb4_0900_ai_ci
|
||||
AND bc.profile = 'PROD'
|
||||
AND bc.vendor_game_id = CAST(` + idExpr + ` AS UNSIGNED)
|
||||
LEFT JOIN ` + schema + `.lingxian_game_catalog lc
|
||||
ON (go.game_id IS NOT NULL OR t.event_type LIKE 'GAME_OPEN_%'
|
||||
OR t.event_type LIKE 'YOMI_GAME_%' OR t.event_type LIKE 'HKYS_GAME_%' OR t.event_type LIKE 'HOT_GAME_%')
|
||||
AND lc.sys_origin = t.sys_origin COLLATE utf8mb4_0900_ai_ci
|
||||
AND lc.profile = 'PROD'
|
||||
AND lc.vendor_game_id = ` + idExpr + ` COLLATE utf8mb4_0900_ai_ci
|
||||
LEFT JOIN ` + schema + `.sys_game_list_config sg
|
||||
ON sg.sys_origin = t.sys_origin COLLATE utf8mb4_0900_ai_ci
|
||||
AND sg.game_id = ` + idExpr + ` COLLATE utf8mb4_0900_ai_ci`
|
||||
}
|
||||
|
||||
func walletGameEventWhere() string {
|
||||
return `(
|
||||
t.event_type IN (
|
||||
'GAME_SLOT_MACHINE', 'GAME_BARBECUE', 'TURNTABLE_GAME', 'LUDO_GAME',
|
||||
'GAME_BURST_CRYSTAL', 'EGG', 'FRUIT_GAME', 'DOUBLE_LAYER_FRUIT',
|
||||
'DOUBLE_LAYER_BARBECUE', 'GAME_FRUIT_BET', 'GAME_FRUIT_AWARD',
|
||||
'GAME_BACK', 'GAME_WINNING', 'LUDO_VICTORY', 'LUDO_REFUND',
|
||||
'GAME_LUDO_REFUND', 'USD_GAME_WIN', 'USD_GAME_REFUND',
|
||||
'USD_GAME_REFUND_DRAW', 'TEEN_PATTI', 'BET_TEEN_PATTI',
|
||||
'LOTTERY_NUMBER_BET', 'LOTTERY_REWARD'
|
||||
)
|
||||
OR t.event_type LIKE 'BAISHUN_GAME%'
|
||||
OR t.event_type LIKE 'GAME_OPEN_%'
|
||||
OR t.event_type LIKE 'YOMI_GAME%'
|
||||
OR t.event_type LIKE 'HKYS_GAME%'
|
||||
OR t.event_type LIKE 'HOT_GAME%'
|
||||
)
|
||||
AND t.event_type NOT IN ('LUCKY_GIFT', 'LUCKY_GIFT_GOLD_REWARD', 'LUCKY_BOX_GAME', 'NEW_LUCKY_BOX_GAME', 'GAME_RAKE')
|
||||
AND t.event_type NOT LIKE 'GIVE_GIFT%'
|
||||
AND t.event_type NOT LIKE 'ACCEPT_GIFT%'`
|
||||
}
|
||||
|
||||
func walletGameProviderExpr() string {
|
||||
return `CASE
|
||||
WHEN bo.vendor_game_id IS NOT NULL THEN 'BAISHUN'
|
||||
WHEN go.game_id IS NOT NULL THEN 'GAME_OPEN'
|
||||
WHEN t.event_type LIKE 'BAISHUN_GAME%' THEN 'BAISHUN'
|
||||
WHEN t.event_type LIKE 'GAME_OPEN_%' THEN 'GAME_OPEN'
|
||||
WHEN t.event_type LIKE 'YOMI_GAME%' THEN 'YOMI'
|
||||
WHEN t.event_type LIKE 'HKYS_GAME%' THEN 'HKYS'
|
||||
WHEN t.event_type LIKE 'HOT_GAME%' THEN 'HOT'
|
||||
ELSE 'INTERNAL'
|
||||
END`
|
||||
}
|
||||
|
||||
func walletGameIDExpr() string {
|
||||
return `CASE
|
||||
WHEN bo.vendor_game_id IS NOT NULL THEN CAST(bo.vendor_game_id AS CHAR)
|
||||
WHEN go.game_id IS NOT NULL THEN go.game_id
|
||||
WHEN t.event_type LIKE 'BAISHUN_GAME_%' THEN COALESCE(NULLIF(SUBSTRING(t.event_type, 14), ''), t.event_type)
|
||||
WHEN t.event_type LIKE 'GAME_OPEN_%' THEN COALESCE(NULLIF(SUBSTRING(t.event_type, 11), ''), t.event_type)
|
||||
WHEN t.event_type LIKE 'YOMI_GAME_%' THEN COALESCE(NULLIF(SUBSTRING(t.event_type, 11), ''), t.event_type)
|
||||
WHEN t.event_type LIKE 'HKYS_GAME_%' THEN COALESCE(NULLIF(SUBSTRING(t.event_type, 11), ''), t.event_type)
|
||||
WHEN t.event_type LIKE 'HOT_GAME_%' THEN COALESCE(NULLIF(SUBSTRING(t.event_type, 10), ''), t.event_type)
|
||||
WHEN t.event_type IN ('FRUIT_GAME', 'DOUBLE_LAYER_FRUIT', 'GAME_FRUIT_BET', 'GAME_FRUIT_AWARD') THEN 'FRUIT_GAME'
|
||||
WHEN t.event_type IN ('LUDO_GAME', 'LUDO_VICTORY', 'LUDO_REFUND', 'GAME_LUDO_REFUND') THEN 'LUDO_GAME'
|
||||
WHEN t.event_type IN ('GAME_BARBECUE', 'DOUBLE_LAYER_BARBECUE') THEN 'BARBECUE_GAME'
|
||||
WHEN t.event_type IN ('GAME_SLOT_MACHINE') THEN 'SLOT_MACHINE'
|
||||
WHEN t.event_type IN ('TURNTABLE_GAME') THEN 'TURNTABLE_GAME'
|
||||
WHEN t.event_type IN ('GAME_BURST_CRYSTAL') THEN 'BURST_CRYSTAL'
|
||||
WHEN t.event_type IN ('TEEN_PATTI', 'BET_TEEN_PATTI') THEN 'TEEN_PATTI'
|
||||
WHEN t.event_type IN ('LOTTERY_NUMBER_BET', 'LOTTERY_REWARD') THEN 'LOTTERY_NUMBER'
|
||||
ELSE t.event_type
|
||||
END`
|
||||
}
|
||||
|
||||
func walletGameNameFallbackExpr() string {
|
||||
return `CASE
|
||||
WHEN t.event_type IN ('FRUIT_GAME', 'DOUBLE_LAYER_FRUIT', 'GAME_FRUIT_BET', 'GAME_FRUIT_AWARD') THEN 'Fruit Game'
|
||||
WHEN t.event_type IN ('LUDO_GAME', 'LUDO_VICTORY', 'LUDO_REFUND', 'GAME_LUDO_REFUND') THEN 'Ludo Game'
|
||||
WHEN t.event_type IN ('GAME_BARBECUE', 'DOUBLE_LAYER_BARBECUE') THEN 'Barbecue Game'
|
||||
WHEN t.event_type = 'GAME_SLOT_MACHINE' THEN 'Slot Machine'
|
||||
WHEN t.event_type = 'TURNTABLE_GAME' THEN 'Turntable Game'
|
||||
WHEN t.event_type = 'GAME_BURST_CRYSTAL' THEN 'Burst Crystal'
|
||||
WHEN t.event_type IN ('TEEN_PATTI', 'BET_TEEN_PATTI') THEN 'Teen Patti'
|
||||
WHEN t.event_type IN ('LOTTERY_NUMBER_BET', 'LOTTERY_REWARD') THEN 'Lottery Number'
|
||||
ELSE t.event_type
|
||||
END`
|
||||
}
|
||||
|
||||
func walletGameNameExpr() string {
|
||||
return `COALESCE(
|
||||
NULLIF(CONVERT(bc.name USING utf8mb4) COLLATE utf8mb4_unicode_ci, CONVERT('' USING utf8mb4) COLLATE utf8mb4_unicode_ci),
|
||||
NULLIF(CONVERT(lc.name USING utf8mb4) COLLATE utf8mb4_unicode_ci, CONVERT('' USING utf8mb4) COLLATE utf8mb4_unicode_ci),
|
||||
NULLIF(CONVERT(sg.name USING utf8mb4) COLLATE utf8mb4_unicode_ci, CONVERT('' USING utf8mb4) COLLATE utf8mb4_unicode_ci),
|
||||
CONVERT(` + walletGameNameFallbackExpr() + ` USING utf8mb4) COLLATE utf8mb4_unicode_ci
|
||||
)`
|
||||
}
|
||||
|
||||
func mysqlTime(value time.Time) string {
|
||||
return value.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user