194 lines
7.9 KiB
Go
194 lines
7.9 KiB
Go
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"time"
|
|
|
|
"hyapp/pkg/appcode"
|
|
)
|
|
|
|
type OverviewQuery struct {
|
|
AppCode string
|
|
StartMS int64
|
|
EndMS int64
|
|
CountryID int64
|
|
}
|
|
|
|
type Overview struct {
|
|
AppCode string `json:"app_code"`
|
|
StartMS int64 `json:"start_ms"`
|
|
EndMS int64 `json:"end_ms"`
|
|
CountryID int64 `json:"country_id"`
|
|
NewUsers int64 `json:"new_users"`
|
|
ActiveUsers int64 `json:"active_users"`
|
|
PaidUsers int64 `json:"paid_users"`
|
|
RechargeUSDMinor int64 `json:"recharge_usd_minor"`
|
|
NewUserRechargeUSDMinor int64 `json:"new_user_recharge_usd_minor"`
|
|
CoinSellerRechargeUSDMinor int64 `json:"coin_seller_recharge_usd_minor"`
|
|
MifaPayRechargeUSDMinor int64 `json:"mifapay_recharge_usd_minor"`
|
|
GoogleRechargeUSDMinor int64 `json:"google_recharge_usd_minor"`
|
|
GiftCoinSpent int64 `json:"gift_coin_spent"`
|
|
LuckyGiftTurnover int64 `json:"lucky_gift_turnover"`
|
|
LuckyGiftPayers int64 `json:"lucky_gift_payers"`
|
|
GameTurnover int64 `json:"game_turnover"`
|
|
GamePayout int64 `json:"game_payout"`
|
|
GameRefund int64 `json:"game_refund"`
|
|
GamePlayers int64 `json:"game_players"`
|
|
GameProfit int64 `json:"game_profit"`
|
|
GameProfitRate float64 `json:"game_profit_rate"`
|
|
ARPUUSDMinor int64 `json:"arpu_usd_minor"`
|
|
ARPPUUSDMinor int64 `json:"arppu_usd_minor"`
|
|
UPValueUSDMinor int64 `json:"up_value_usd_minor"`
|
|
Retention Retention `json:"retention"`
|
|
GameRanking []GameRank `json:"game_ranking"`
|
|
}
|
|
|
|
type Retention struct {
|
|
RegisteredUsers int64 `json:"registered_users"`
|
|
Day1Users int64 `json:"day1_users"`
|
|
Day7Users int64 `json:"day7_users"`
|
|
Day30Users int64 `json:"day30_users"`
|
|
Day1Rate float64 `json:"day1_rate"`
|
|
Day7Rate float64 `json:"day7_rate"`
|
|
Day30Rate float64 `json:"day30_rate"`
|
|
}
|
|
|
|
type GameRank struct {
|
|
PlatformCode string `json:"platform_code"`
|
|
GameID string `json:"game_id"`
|
|
TurnoverCoin int64 `json:"turnover_coin"`
|
|
PayoutCoin int64 `json:"payout_coin"`
|
|
RefundCoin int64 `json:"refund_coin"`
|
|
PlayerCount int64 `json:"player_count"`
|
|
ProfitCoin int64 `json:"profit_coin"`
|
|
ProfitRate float64 `json:"profit_rate"`
|
|
}
|
|
|
|
func (r *Repository) QueryOverview(ctx context.Context, query OverviewQuery) (Overview, error) {
|
|
app := appcode.Normalize(query.AppCode)
|
|
if app == "" {
|
|
app = "lalu"
|
|
}
|
|
startDay := statDay(query.StartMS)
|
|
endDay := statDay(query.EndMS - 1)
|
|
args := []any{app, startDay, endDay}
|
|
countryFilter := ""
|
|
if query.CountryID > 0 {
|
|
countryFilter = " AND country_id = ?"
|
|
args = append(args, query.CountryID)
|
|
}
|
|
row := r.db.QueryRowContext(ctx, `
|
|
SELECT COALESCE(SUM(new_users),0), COALESCE(SUM(active_users),0), COALESCE(SUM(paid_users),0),
|
|
COALESCE(SUM(recharge_usd_minor),0), COALESCE(SUM(new_user_recharge_usd_minor),0),
|
|
COALESCE(SUM(coin_seller_recharge_usd_minor),0), COALESCE(SUM(mifapay_recharge_usd_minor),0),
|
|
COALESCE(SUM(google_recharge_usd_minor),0), COALESCE(SUM(gift_coin_spent),0),
|
|
COALESCE(SUM(lucky_gift_turnover),0), COALESCE(SUM(lucky_gift_payers),0),
|
|
COALESCE(SUM(game_turnover),0), COALESCE(SUM(game_payout),0), COALESCE(SUM(game_refund),0),
|
|
COALESCE(SUM(game_players),0)
|
|
FROM stat_app_day_country
|
|
WHERE app_code = ? AND stat_day BETWEEN ? AND ?`+countryFilter, args...)
|
|
var overview Overview
|
|
overview.AppCode, overview.StartMS, overview.EndMS, overview.CountryID = app, query.StartMS, query.EndMS, query.CountryID
|
|
if err := row.Scan(&overview.NewUsers, &overview.ActiveUsers, &overview.PaidUsers, &overview.RechargeUSDMinor, &overview.NewUserRechargeUSDMinor, &overview.CoinSellerRechargeUSDMinor, &overview.MifaPayRechargeUSDMinor, &overview.GoogleRechargeUSDMinor, &overview.GiftCoinSpent, &overview.LuckyGiftTurnover, &overview.LuckyGiftPayers, &overview.GameTurnover, &overview.GamePayout, &overview.GameRefund, &overview.GamePlayers); err != nil {
|
|
return Overview{}, err
|
|
}
|
|
overview.GameProfit = overview.GameTurnover - overview.GamePayout - overview.GameRefund
|
|
overview.GameProfitRate = ratio(overview.GameProfit, overview.GameTurnover)
|
|
overview.ARPUUSDMinor = div(overview.RechargeUSDMinor, overview.ActiveUsers)
|
|
overview.ARPPUUSDMinor = div(overview.RechargeUSDMinor, overview.PaidUsers)
|
|
overview.UPValueUSDMinor = overview.ARPPUUSDMinor
|
|
retention, err := r.queryRetention(ctx, app, startDay, endDay, query.CountryID)
|
|
if err != nil {
|
|
return Overview{}, err
|
|
}
|
|
overview.Retention = retention
|
|
ranking, err := r.queryGameRanking(ctx, app, startDay, endDay, query.CountryID)
|
|
if err != nil {
|
|
return Overview{}, err
|
|
}
|
|
overview.GameRanking = ranking
|
|
return overview, nil
|
|
}
|
|
|
|
func (r *Repository) queryRetention(ctx context.Context, app, startDay, endDay string, countryID int64) (Retention, error) {
|
|
filter := ""
|
|
args := []any{app, startDay, endDay}
|
|
if countryID > 0 {
|
|
filter = " AND r.country_id = ?"
|
|
args = append(args, countryID)
|
|
}
|
|
row := r.db.QueryRowContext(ctx, `
|
|
SELECT COUNT(*),
|
|
COALESCE(SUM(EXISTS(SELECT 1 FROM stat_user_day_activity a WHERE a.app_code=r.app_code AND a.user_id=r.user_id AND a.stat_day=DATE_ADD(r.registered_day, INTERVAL 1 DAY))),0),
|
|
COALESCE(SUM(EXISTS(SELECT 1 FROM stat_user_day_activity a WHERE a.app_code=r.app_code AND a.user_id=r.user_id AND a.stat_day=DATE_ADD(r.registered_day, INTERVAL 7 DAY))),0),
|
|
COALESCE(SUM(EXISTS(SELECT 1 FROM stat_user_day_activity a WHERE a.app_code=r.app_code AND a.user_id=r.user_id AND a.stat_day=DATE_ADD(r.registered_day, INTERVAL 30 DAY))),0)
|
|
FROM stat_user_registration r
|
|
WHERE r.app_code = ? AND r.registered_day BETWEEN ? AND ?`+filter, args...)
|
|
var out Retention
|
|
if err := row.Scan(&out.RegisteredUsers, &out.Day1Users, &out.Day7Users, &out.Day30Users); err != nil && err != sql.ErrNoRows {
|
|
return Retention{}, err
|
|
}
|
|
out.Day1Rate = ratio(out.Day1Users, out.RegisteredUsers)
|
|
out.Day7Rate = ratio(out.Day7Users, out.RegisteredUsers)
|
|
out.Day30Rate = ratio(out.Day30Users, out.RegisteredUsers)
|
|
return out, nil
|
|
}
|
|
|
|
func (r *Repository) queryGameRanking(ctx context.Context, app, startDay, endDay string, countryID int64) ([]GameRank, error) {
|
|
args := []any{app, startDay, endDay}
|
|
filter := ""
|
|
if countryID > 0 {
|
|
filter = " AND country_id = ?"
|
|
args = append(args, countryID)
|
|
}
|
|
rows, err := r.db.QueryContext(ctx, `
|
|
SELECT platform_code, game_id, COALESCE(SUM(turnover_coin),0), COALESCE(SUM(payout_coin),0),
|
|
COALESCE(SUM(refund_coin),0), COALESCE(SUM(player_count),0)
|
|
FROM stat_game_day_country
|
|
WHERE app_code = ? AND stat_day BETWEEN ? AND ?`+filter+`
|
|
GROUP BY platform_code, game_id
|
|
ORDER BY COALESCE(SUM(turnover_coin),0) DESC
|
|
LIMIT 50`, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
out := []GameRank{}
|
|
for rows.Next() {
|
|
var item GameRank
|
|
if err := rows.Scan(&item.PlatformCode, &item.GameID, &item.TurnoverCoin, &item.PayoutCoin, &item.RefundCoin, &item.PlayerCount); err != nil {
|
|
return nil, err
|
|
}
|
|
item.ProfitCoin = item.TurnoverCoin - item.PayoutCoin - item.RefundCoin
|
|
item.ProfitRate = ratio(item.ProfitCoin, item.TurnoverCoin)
|
|
out = append(out, item)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func normalizeRange(startMS, endMS int64) (int64, int64) {
|
|
if startMS <= 0 {
|
|
now := time.Now().UTC()
|
|
startMS = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).UnixMilli()
|
|
}
|
|
if endMS <= startMS {
|
|
endMS = startMS + 24*time.Hour.Milliseconds()
|
|
}
|
|
return startMS, endMS
|
|
}
|
|
|
|
func div(numerator, denominator int64) int64 {
|
|
if denominator <= 0 {
|
|
return 0
|
|
}
|
|
return numerator / denominator
|
|
}
|
|
|
|
func ratio(numerator, denominator int64) float64 {
|
|
if denominator <= 0 {
|
|
return 0
|
|
}
|
|
return float64(numerator) / float64(denominator)
|
|
}
|