345 lines
14 KiB
Go
345 lines
14 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
driverMysql "github.com/go-sql-driver/mysql"
|
|
)
|
|
|
|
func (r *MySQLRepository) EnsureSchema(ctx context.Context) error {
|
|
if err := execAll(ctx, r.db, []string{
|
|
`CREATE TABLE IF NOT EXISTS dashboard_cdc_checkpoint (
|
|
service_name varchar(64) NOT NULL,
|
|
binlog_file varchar(255) NOT NULL,
|
|
binlog_pos int unsigned NOT NULL,
|
|
updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (service_name)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
|
|
`CREATE TABLE IF NOT EXISTS dashboard_cdc_event_dedup (
|
|
event_key varchar(255) NOT NULL,
|
|
source varchar(128) NOT NULL,
|
|
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (event_key),
|
|
KEY idx_dashboard_cdc_event_dedup_created (created_at)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
|
|
`CREATE TABLE IF NOT EXISTS dashboard_cdc_user_country (
|
|
user_id bigint NOT NULL,
|
|
sys_origin varchar(50) NOT NULL,
|
|
country_code varchar(30) NOT NULL,
|
|
country_name varchar(100) NOT NULL,
|
|
is_deleted tinyint(1) NOT NULL DEFAULT 0,
|
|
user_created_at datetime NULL,
|
|
user_updated_at datetime NULL,
|
|
updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (user_id),
|
|
KEY idx_dashboard_cdc_user_country_origin_country (sys_origin, country_code),
|
|
KEY idx_dashboard_cdc_user_country_origin_created (sys_origin, user_created_at, country_code)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
|
|
createDailyMetricTable,
|
|
createDailyUserMetricTable,
|
|
createPeriodMetricTable,
|
|
createPeriodUserMetricTable,
|
|
createGamePeriodMetricTable,
|
|
createGamePeriodUserMetricTable,
|
|
createGameEventFactTable,
|
|
createRechargeDetailTable,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
return r.ensureMetricColumns(ctx)
|
|
}
|
|
|
|
const createDailyMetricTable = `CREATE TABLE IF NOT EXISTS country_dashboard_daily_metric (
|
|
id bigint NOT NULL AUTO_INCREMENT,
|
|
stat_date date NOT NULL,
|
|
date_number int NOT NULL,
|
|
sys_origin varchar(50) NOT NULL,
|
|
country_code varchar(64) NOT NULL,
|
|
country_name varchar(128) NOT NULL,
|
|
country_new_user bigint NOT NULL DEFAULT 0,
|
|
new_user_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
official_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
mifapay_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
google_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
dealer_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
user_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
new_dealer_user_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
salary_exchange decimal(20,2) NOT NULL DEFAULT 0,
|
|
salary_transfer decimal(20,2) NOT NULL DEFAULT 0,
|
|
gift_consume decimal(20,2) NOT NULL DEFAULT 0,
|
|
lucky_gift_total_flow decimal(20,2) NOT NULL DEFAULT 0,
|
|
lucky_gift_user bigint NOT NULL DEFAULT 0,
|
|
lucky_gift_payout decimal(20,2) NOT NULL DEFAULT 0,
|
|
game_total_flow decimal(20,2) NOT NULL DEFAULT 0,
|
|
daily_recharge_user bigint NOT NULL DEFAULT 0,
|
|
game_user bigint NOT NULL DEFAULT 0,
|
|
game_payout decimal(20,2) NOT NULL DEFAULT 0,
|
|
refreshed_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_daily_origin_country (stat_date, sys_origin, country_code),
|
|
KEY idx_daily_origin_date (sys_origin, stat_date)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
|
|
|
|
const createDailyUserMetricTable = `CREATE TABLE IF NOT EXISTS country_dashboard_daily_user_metric (
|
|
id bigint NOT NULL AUTO_INCREMENT,
|
|
stat_date date NOT NULL,
|
|
sys_origin varchar(50) NOT NULL,
|
|
country_code varchar(64) NOT NULL,
|
|
country_name varchar(128) NOT NULL,
|
|
user_id bigint NOT NULL,
|
|
metric_type varchar(32) NOT NULL,
|
|
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_daily_user_metric (stat_date, sys_origin, country_code, user_id, metric_type),
|
|
KEY idx_daily_user_metric (sys_origin, user_id, metric_type)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
|
|
|
|
const createPeriodMetricTable = `CREATE TABLE IF NOT EXISTS country_dashboard_period_metric (
|
|
id bigint NOT NULL AUTO_INCREMENT,
|
|
period_type varchar(16) NOT NULL,
|
|
period_key varchar(32) NOT NULL,
|
|
stat_timezone varchar(64) NOT NULL DEFAULT 'Asia/Riyadh',
|
|
period_name varchar(64) NOT NULL,
|
|
period_start_date date NULL,
|
|
period_end_date date NULL,
|
|
sys_origin varchar(50) NOT NULL,
|
|
country_code varchar(64) NOT NULL,
|
|
country_name varchar(128) NOT NULL,
|
|
country_new_user bigint NOT NULL DEFAULT 0,
|
|
new_user_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
official_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
mifapay_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
google_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
dealer_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
user_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
new_dealer_user_recharge decimal(20,2) NOT NULL DEFAULT 0,
|
|
salary_exchange decimal(20,2) NOT NULL DEFAULT 0,
|
|
salary_transfer decimal(20,2) NOT NULL DEFAULT 0,
|
|
gift_consume decimal(20,2) NOT NULL DEFAULT 0,
|
|
lucky_gift_total_flow decimal(20,2) NOT NULL DEFAULT 0,
|
|
lucky_gift_user bigint NOT NULL DEFAULT 0,
|
|
lucky_gift_payout decimal(20,2) NOT NULL DEFAULT 0,
|
|
game_total_flow decimal(20,2) NOT NULL DEFAULT 0,
|
|
daily_recharge_user bigint NOT NULL DEFAULT 0,
|
|
game_user bigint NOT NULL DEFAULT 0,
|
|
game_payout decimal(20,2) NOT NULL DEFAULT 0,
|
|
refreshed_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_period_origin_country (period_type, period_key, stat_timezone, sys_origin, country_code),
|
|
KEY idx_origin_period (sys_origin, stat_timezone, period_type, period_key)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
|
|
|
|
func (r *MySQLRepository) ensureMetricColumns(ctx context.Context) error {
|
|
if err := r.addColumnIfMissing(ctx, "country_dashboard_daily_metric", "salary_transfer",
|
|
"ALTER TABLE country_dashboard_daily_metric ADD COLUMN salary_transfer decimal(20,2) NOT NULL DEFAULT 0 AFTER salary_exchange"); err != nil {
|
|
return err
|
|
}
|
|
if err := r.addColumnIfMissing(ctx, "country_dashboard_period_metric", "salary_transfer",
|
|
"ALTER TABLE country_dashboard_period_metric ADD COLUMN salary_transfer decimal(20,2) NOT NULL DEFAULT 0 AFTER salary_exchange"); err != nil {
|
|
return err
|
|
}
|
|
if err := r.addColumnIfMissing(ctx, "country_dashboard_daily_metric", "user_recharge",
|
|
"ALTER TABLE country_dashboard_daily_metric ADD COLUMN user_recharge decimal(20,2) NOT NULL DEFAULT 0 AFTER dealer_recharge"); err != nil {
|
|
return err
|
|
}
|
|
if err := r.addColumnIfMissing(ctx, "country_dashboard_period_metric", "user_recharge",
|
|
"ALTER TABLE country_dashboard_period_metric ADD COLUMN user_recharge decimal(20,2) NOT NULL DEFAULT 0 AFTER dealer_recharge"); err != nil {
|
|
return err
|
|
}
|
|
if err := r.addColumnIfMissing(ctx, "country_dashboard_daily_metric", "new_dealer_user_recharge",
|
|
"ALTER TABLE country_dashboard_daily_metric ADD COLUMN new_dealer_user_recharge decimal(20,2) NOT NULL DEFAULT 0 AFTER user_recharge"); err != nil {
|
|
return err
|
|
}
|
|
if err := r.addColumnIfMissing(ctx, "country_dashboard_period_metric", "new_dealer_user_recharge",
|
|
"ALTER TABLE country_dashboard_period_metric ADD COLUMN new_dealer_user_recharge decimal(20,2) NOT NULL DEFAULT 0 AFTER user_recharge"); err != nil {
|
|
return err
|
|
}
|
|
if err := r.addColumnIfMissing(ctx, "country_dashboard_daily_metric", "daily_recharge_user",
|
|
"ALTER TABLE country_dashboard_daily_metric ADD COLUMN daily_recharge_user bigint NOT NULL DEFAULT 0 AFTER game_total_flow"); err != nil {
|
|
return err
|
|
}
|
|
if err := r.addColumnIfMissing(ctx, "country_dashboard_period_metric", "daily_recharge_user",
|
|
"ALTER TABLE country_dashboard_period_metric ADD COLUMN daily_recharge_user bigint NOT NULL DEFAULT 0 AFTER game_total_flow"); err != nil {
|
|
return err
|
|
}
|
|
return r.addIndexIfMissing(ctx, "dashboard_cdc_user_country", "idx_dashboard_cdc_user_country_origin_created",
|
|
"ALTER TABLE dashboard_cdc_user_country ADD INDEX idx_dashboard_cdc_user_country_origin_created (sys_origin, user_created_at, country_code)")
|
|
}
|
|
|
|
func (r *MySQLRepository) addColumnIfMissing(ctx context.Context, table string, column string, ddl string) error {
|
|
var exists int
|
|
err := r.db.QueryRowContext(ctx, `
|
|
SELECT COUNT(1)
|
|
FROM information_schema.columns
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = ?
|
|
AND column_name = ?
|
|
`, table, column).Scan(&exists)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if exists > 0 {
|
|
return nil
|
|
}
|
|
if _, err := r.db.ExecContext(ctx, ddl); err != nil && !isDuplicateColumnError(err) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *MySQLRepository) addIndexIfMissing(ctx context.Context, table string, indexName string, ddl string) error {
|
|
var exists int
|
|
err := r.db.QueryRowContext(ctx, `
|
|
SELECT COUNT(1)
|
|
FROM information_schema.statistics
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = ?
|
|
AND index_name = ?
|
|
`, table, indexName).Scan(&exists)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if exists > 0 {
|
|
return nil
|
|
}
|
|
if _, err := r.db.ExecContext(ctx, ddl); err != nil && !isDuplicateKeyError(err) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isDuplicateColumnError(err error) bool {
|
|
var mysqlErr *driverMysql.MySQLError
|
|
if err == nil {
|
|
return false
|
|
}
|
|
if errors.As(err, &mysqlErr) {
|
|
return mysqlErr.Number == 1060
|
|
}
|
|
return fmt.Sprint(err) == "Error 1060 (42S21): Duplicate column name 'salary_transfer'"
|
|
}
|
|
|
|
func isDuplicateKeyError(err error) bool {
|
|
var mysqlErr *driverMysql.MySQLError
|
|
if err == nil {
|
|
return false
|
|
}
|
|
if errors.As(err, &mysqlErr) {
|
|
return mysqlErr.Number == 1061
|
|
}
|
|
return false
|
|
}
|
|
|
|
const createPeriodUserMetricTable = `CREATE TABLE IF NOT EXISTS country_dashboard_period_user_metric (
|
|
id bigint NOT NULL AUTO_INCREMENT,
|
|
period_type varchar(16) NOT NULL,
|
|
period_key varchar(32) NOT NULL,
|
|
stat_timezone varchar(64) NOT NULL DEFAULT 'Asia/Riyadh',
|
|
period_name varchar(64) NOT NULL,
|
|
period_start_date date NULL,
|
|
period_end_date date NULL,
|
|
sys_origin varchar(50) NOT NULL,
|
|
country_code varchar(64) NOT NULL,
|
|
country_name varchar(128) NOT NULL,
|
|
user_id bigint NOT NULL,
|
|
metric_type varchar(32) NOT NULL,
|
|
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_period_user_metric (period_type, period_key, stat_timezone, sys_origin, country_code, user_id, metric_type),
|
|
KEY idx_origin_user_metric (sys_origin, stat_timezone, user_id, metric_type)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
|
|
|
|
const createGamePeriodMetricTable = `CREATE TABLE IF NOT EXISTS country_dashboard_game_period_metric (
|
|
id bigint NOT NULL AUTO_INCREMENT,
|
|
period_type varchar(16) NOT NULL,
|
|
period_key varchar(32) NOT NULL,
|
|
stat_timezone varchar(64) NOT NULL DEFAULT 'Asia/Riyadh',
|
|
period_name varchar(64) NOT NULL,
|
|
period_start_date date NULL,
|
|
period_end_date date NULL,
|
|
sys_origin varchar(50) NOT NULL,
|
|
country_code varchar(64) NOT NULL,
|
|
country_name varchar(128) NOT NULL,
|
|
game_provider varchar(32) NOT NULL,
|
|
game_id varchar(128) NOT NULL,
|
|
game_name varchar(256) NOT NULL,
|
|
consume_amount decimal(20,2) NOT NULL DEFAULT 0,
|
|
payout_amount decimal(20,2) NOT NULL DEFAULT 0,
|
|
profit_amount decimal(20,2) NOT NULL DEFAULT 0,
|
|
user_count bigint NOT NULL DEFAULT 0,
|
|
order_count bigint NOT NULL DEFAULT 0,
|
|
refreshed_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_game_period (sys_origin, stat_timezone, period_type, period_key, country_code, game_provider, game_id),
|
|
KEY idx_game_period_game (sys_origin, stat_timezone, period_type, period_key, game_provider, game_id, country_code)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
|
|
|
|
const createGamePeriodUserMetricTable = `CREATE TABLE IF NOT EXISTS country_dashboard_game_period_user_metric (
|
|
id bigint NOT NULL AUTO_INCREMENT,
|
|
period_type varchar(16) NOT NULL,
|
|
period_key varchar(32) NOT NULL,
|
|
stat_timezone varchar(64) NOT NULL DEFAULT 'Asia/Riyadh',
|
|
period_name varchar(64) NOT NULL,
|
|
period_start_date date NULL,
|
|
period_end_date date NULL,
|
|
sys_origin varchar(50) NOT NULL,
|
|
country_code varchar(64) NOT NULL,
|
|
country_name varchar(128) NOT NULL,
|
|
game_provider varchar(32) NOT NULL,
|
|
game_id varchar(128) NOT NULL,
|
|
user_id bigint NOT NULL,
|
|
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_game_period_user (period_type, period_key, stat_timezone, sys_origin, country_code, game_provider, game_id, user_id),
|
|
KEY idx_game_user (sys_origin, stat_timezone, user_id)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
|
|
|
|
const createGameEventFactTable = `CREATE TABLE IF NOT EXISTS dashboard_game_event_fact (
|
|
fact_key varchar(255) NOT NULL,
|
|
source_schema varchar(64) NOT NULL,
|
|
source_table varchar(128) NOT NULL,
|
|
source_pk varchar(128) NOT NULL,
|
|
source_action varchar(16) NOT NULL,
|
|
sys_origin varchar(50) NOT NULL,
|
|
stat_date date NOT NULL,
|
|
event_time datetime NOT NULL,
|
|
user_id bigint NOT NULL,
|
|
country_code varchar(64) NOT NULL,
|
|
country_name varchar(128) NOT NULL,
|
|
game_provider varchar(32) NOT NULL,
|
|
game_id varchar(128) NOT NULL,
|
|
game_name varchar(256) NOT NULL,
|
|
consume_amount decimal(20,2) NOT NULL DEFAULT 0,
|
|
payout_amount decimal(20,2) NOT NULL DEFAULT 0,
|
|
profit_amount decimal(20,2) NOT NULL DEFAULT 0,
|
|
order_count bigint NOT NULL DEFAULT 0,
|
|
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (fact_key),
|
|
KEY idx_game_fact_date_origin_country (stat_date, sys_origin, country_code),
|
|
KEY idx_game_fact_date_game (stat_date, sys_origin, game_provider, game_id),
|
|
KEY idx_game_fact_user (stat_date, sys_origin, user_id)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
|
|
|
|
const createRechargeDetailTable = `CREATE TABLE IF NOT EXISTS country_dashboard_recharge_detail (
|
|
id bigint NOT NULL AUTO_INCREMENT,
|
|
source_type varchar(32) NOT NULL,
|
|
record_id varchar(128) NOT NULL,
|
|
source_channel varchar(64) NOT NULL DEFAULT '',
|
|
sys_origin varchar(50) NOT NULL,
|
|
user_id bigint NOT NULL,
|
|
country_code varchar(64) NOT NULL DEFAULT 'UNKNOWN',
|
|
country_name varchar(128) NOT NULL DEFAULT 'UNKNOWN',
|
|
amount decimal(20,2) NOT NULL DEFAULT 0,
|
|
coin_quantity decimal(20,2) NULL,
|
|
remark varchar(512) NOT NULL DEFAULT '',
|
|
create_time datetime NOT NULL,
|
|
update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (id),
|
|
UNIQUE KEY uk_recharge_source_record (source_type, record_id),
|
|
KEY idx_recharge_query (sys_origin, create_time, country_code),
|
|
KEY idx_recharge_user (user_id, create_time)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`
|