feat: 迁移渠道列表新增和游戏累计分布
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
package business
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"kra/internal/biz/business"
|
||||
)
|
||||
|
||||
func (r *channelRepo) Metrics(ctx context.Context, roots, statKeys []business.ChannelKey, dedup bool) (*business.ChannelMetricData, error) {
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
metrics := &business.ChannelMetricData{Games: map[business.ChannelKey]int64{}, Players: map[business.ChannelKey]int64{}, Pay: map[business.ChannelKey]string{}}
|
||||
var err error
|
||||
metrics.GamesAvailable, err = businessTableAvailable(db, "tab_promote_apply")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metrics.PlayersAvailable, err = businessTableAvailable(db, "tab_user")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
metrics.PayAvailable, err = businessTableAvailable(db, "tab_spend")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
type countRow struct {
|
||||
SourceDB string `gorm:"column:source_db"`
|
||||
PromoteID uint64
|
||||
Count int64
|
||||
}
|
||||
if metrics.GamesAvailable {
|
||||
for start := 0; start < len(roots); start += 300 {
|
||||
var rows []countRow
|
||||
if err := db.Table("tab_promote_apply").Select("source_db,promote_id,COUNT(DISTINCT game_id) AS count").Where("status = 1 AND (source_db,promote_id) IN ?", channelPairs(roots[start:min(start+300, len(roots))])).Group("source_db,promote_id").Scan(&rows).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
for _, row := range rows {
|
||||
metrics.Games[business.ChannelKey{SourceDB: row.SourceDB, ID: row.PromoteID}] = row.Count
|
||||
}
|
||||
}
|
||||
}
|
||||
// 每批按平台/渠道一起查询,避免逐个渠道访问数据库,也避免参数数量无限增长。
|
||||
for start := 0; start < len(statKeys); start += 300 {
|
||||
pairs := channelPairs(statKeys[start:min(start+300, len(statKeys))])
|
||||
if metrics.PlayersAvailable {
|
||||
q := db.Table("tab_user").Where("(puid = 0 OR puid IS NULL) AND (source_db,promote_id) IN ?", pairs)
|
||||
if dedup {
|
||||
var players []struct {
|
||||
SourceDB string `gorm:"column:source_db"`
|
||||
PromoteID, UserID uint64
|
||||
IDCard string `gorm:"column:idcard"`
|
||||
}
|
||||
if err := q.Select("source_db,promote_id,id AS user_id,idcard").Scan(&players).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
for _, p := range players {
|
||||
metrics.PlayerCards = append(metrics.PlayerCards, business.ChannelPlayer{ChannelKey: business.ChannelKey{SourceDB: p.SourceDB, ID: p.PromoteID}, UserID: p.UserID, IDCard: p.IDCard})
|
||||
}
|
||||
} else {
|
||||
var rows []countRow
|
||||
if err := q.Select("source_db,promote_id,COUNT(*) AS count").Group("source_db,promote_id").Scan(&rows).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
for _, row := range rows {
|
||||
metrics.Players[business.ChannelKey{SourceDB: row.SourceDB, ID: row.PromoteID}] = row.Count
|
||||
}
|
||||
}
|
||||
}
|
||||
if metrics.PayAvailable {
|
||||
var rows []struct {
|
||||
SourceDB string `gorm:"column:source_db"`
|
||||
PromoteID uint64
|
||||
Amount string
|
||||
}
|
||||
if err := db.Table("tab_spend").Select("source_db,promote_id,COALESCE(SUM(pay_amount),0) AS amount").Where("pay_status = 1 AND (source_db,promote_id) IN ?", pairs).Group("source_db,promote_id").Scan(&rows).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
for _, row := range rows {
|
||||
metrics.Pay[business.ChannelKey{SourceDB: row.SourceDB, ID: row.PromoteID}] = row.Amount
|
||||
}
|
||||
}
|
||||
}
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
func channelGameTree(db *gorm.DB, scope business.ChannelGameScope) *gorm.DB {
|
||||
root := scope.Root
|
||||
q := db.Table("tab_promote p").Where("p.source_db = ?", root.SourceDB)
|
||||
if !scope.IncludeSub || root.Level == nil || *root.Level >= 3 {
|
||||
return q.Where("p.id = ?", root.ID)
|
||||
}
|
||||
if *root.Level == 2 {
|
||||
return q.Where("p.id = ? OR (p.parent_id = ? AND p.promote_level = 3)", root.ID, root.ID)
|
||||
}
|
||||
return q.Where("p.id = ? OR p.parent_id = ? OR p.top_promote_id = ?", root.ID, root.ID, root.ID)
|
||||
}
|
||||
func (r *channelRepo) Games(ctx context.Context, scope business.ChannelGameScope) ([]*business.ChannelGame, error) {
|
||||
db := r.data.DB().WithContext(ctx)
|
||||
available, err := businessTableAvailable(db, "tab_promote_apply")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !available {
|
||||
return nil, business.ErrChannelReportsUnavailable
|
||||
}
|
||||
ids := []uint64{}
|
||||
if err := db.Table("tab_promote_apply").Where("source_db = ? AND promote_id = ? AND status = 1 AND game_id IS NOT NULL", scope.Root.SourceDB, scope.Root.ID).Distinct("game_id").Order("game_id").Pluck("game_id", &ids).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
result := []*business.ChannelGame{}
|
||||
if len(ids) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
for _, table := range []string{"tab_game", "tab_sw_report_day", "tab_sw_report_day_key"} {
|
||||
available, err := businessTableAvailable(db, table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !available {
|
||||
return nil, business.ErrChannelReportsUnavailable
|
||||
}
|
||||
}
|
||||
mode := 0
|
||||
if scope.IDCardDedup {
|
||||
mode = 1
|
||||
}
|
||||
query := func(table string, dedup int) *gorm.DB {
|
||||
return db.Table(table+" r").Where("r.source_db = ? AND r.game_id > 0 AND r.dedup_mode = ?", scope.Root.SourceDB, dedup).Where("EXISTS (?)", channelGameTree(db, scope).Select("1").Where("p.id=r.promote_id AND p.source_db=r.source_db"))
|
||||
}
|
||||
// 累计起点按当前人数口径的日汇总最早日期决定,与原画像查询一致。
|
||||
var minimum struct{ FirstDate *string }
|
||||
if err := query("tab_sw_report_day", mode).Select("CAST(MIN(r.stat_date) AS CHAR) AS first_date").Scan(&minimum).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
begin := scope.EndDate
|
||||
if minimum.FirstDate != nil && *minimum.FirstDate != "" {
|
||||
begin = *minimum.FirstDate
|
||||
}
|
||||
byID := map[uint64]*business.ChannelGame{}
|
||||
for _, id := range ids {
|
||||
item := &business.ChannelGame{ID: id, PayAmount: "0.00"}
|
||||
byID[id] = item
|
||||
result = append(result, item)
|
||||
}
|
||||
for start := 0; start < len(ids); start += 300 {
|
||||
batch := ids[start:min(start+300, len(ids))]
|
||||
var names []struct {
|
||||
ID uint64
|
||||
GameName string
|
||||
}
|
||||
if err := db.Table("tab_game").Select("id,game_name").Where("source_db = ? AND id IN ?", scope.Root.SourceDB, batch).Scan(&names).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
for _, n := range names {
|
||||
byID[n.ID].Name = n.GameName
|
||||
}
|
||||
var counts []struct {
|
||||
GameID uint64
|
||||
Count int64
|
||||
}
|
||||
if err := query("tab_sw_report_day_key", mode).Select("r.game_id,COUNT(DISTINCT r.identity_key) AS count").Where("r.metric = 1 AND r.stat_date >= ? AND r.stat_date <= ? AND r.game_id IN ?", begin, scope.EndDate, batch).Group("r.game_id").Scan(&counts).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
for _, c := range counts {
|
||||
byID[c.GameID].RegisterCount = c.Count
|
||||
}
|
||||
var amounts []struct {
|
||||
GameID uint64
|
||||
Amount string
|
||||
}
|
||||
if err := query("tab_sw_report_day", 0).Select("r.game_id,COALESCE(SUM(r.total_pay),0) AS amount").Where("r.stat_date >= ? AND r.stat_date <= ? AND r.game_id IN ?", begin, scope.EndDate, batch).Group("r.game_id").Scan(&amounts).Error; err != nil {
|
||||
return nil, business.ErrStorage
|
||||
}
|
||||
for _, a := range amounts {
|
||||
byID[a.GameID].PayAmount = a.Amount
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user