package business import ( "context" "sort" "gorm.io/gorm" "kra/internal/biz/business" ) func whereMetricChannels(db *gorm.DB, keys []business.ChannelKey) *gorm.DB { bySource := map[string][]uint64{} for _, key := range keys { bySource[key.SourceDB] = append(bySource[key.SourceDB], key.ID) } sources := make([]string, 0, len(bySource)) for source := range bySource { sources = append(sources, source) } sort.Strings(sources) if len(sources) == 0 { return db.Where("1=0") } var condition *gorm.DB for _, source := range sources { part := db.Session(&gorm.Session{NewDB: true}).Where("source_db=? AND promote_id IN ?", source, bySource[source]) if condition == nil { condition = part } else { condition = condition.Or(part) } } return db.Where(condition) } 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 := whereMetricChannels(db, roots[start:min(start+300, len(roots))]).Table("tab_promote_apply").Select("source_db,promote_id,COUNT(DISTINCT game_id) AS count").Where("status = 1").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 { keys := statKeys[start:min(start+300, len(statKeys))] if metrics.PlayersAvailable { q := whereMetricChannels(db, keys).Table("tab_user").Where("puid = 0 OR puid IS NULL") 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 := whereMetricChannels(db, keys).Table("tab_spend").Select("source_db,promote_id,COALESCE(SUM(pay_amount),0) AS amount").Where("pay_status = 1").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("(r.source_db,r.promote_id) IN (?)", channelGameTree(db, scope).Select("p.source_db,p.id")) } // 累计起点按当前人数口径的日汇总最早日期决定,与原画像查询一致。 begin := scope.BeginDate if begin == "" { 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 }