package business import ( "context" "golang.org/x/sync/errgroup" "gorm.io/gorm" "kra/internal/biz/business" ) type rankingRepo struct{ data Provider } func NewRankingRepo(data Provider) business.RankingRepo { return &rankingRepo{data: data} } // 通过一次关系查询展开各上榜商务的下级绑定,避免旧服务对每个商务查询下级。 func rankingBusinessBindings(db *gorm.DB, scope business.AnalyticsScope, sources []string) *gorm.DB { q := db.Table("sys_users target").Where("target.deleted_at IS NULL") if scope.Viewer.All { q = q.Where("target.level IN (1,2,3)") } else { q = q.Where("target.parent_id=?", scope.Viewer.SelfID) } prefix := "(COALESCE(NULLIF(target.id_path,''),'0') || ',' || target.id)" if db.Dialector.Name() == "mysql" { prefix = "CONCAT(COALESCE(NULLIF(target.id_path,''),'0'),',',target.id)" } like := prefix + " || ',%'" if db.Dialector.Name() == "mysql" { like = "CONCAT(" + prefix + ",',%')" } q = q.Joins("JOIN sys_users child ON child.deleted_at IS NULL AND (child.id=target.id OR (target.level<3 AND (child.id_path=" + prefix + " OR child.id_path LIKE (" + like + "))))").Joins("JOIN sys_user_bind_platform b ON b.user_id=child.id AND b.status='0'") if len(sources) > 0 { q = q.Where("b.source_db IN ?", sources) } return q } func rankingBusinessPromotes(db *gorm.DB, scope business.AnalyticsScope, sources []string) *gorm.DB { q := rankingBusinessBindings(db, scope, sources).Joins("JOIN tab_promote p ON p.source_db=b.source_db AND p.busier_id=b.platform_busier_id") if !scope.IncludeSub { q = q.Where("p.promote_level=1") } return q } func rankingReportQuery(db *gorm.DB, kind business.RankingKind, scope business.AnalyticsScope, f business.AnalyticsFilter, table string, mode int) *gorm.DB { if kind != business.BusinessRanking { return analyticsQuery(db, scope, f, table, mode) } all := business.AnalyticsScope{Viewer: business.UserScope{All: true}, IncludeSub: true} // 同一渠道会出现在多级商务的范围中,先去重并一次性展开可见渠道。 // 避免对每一条报表身份记录重复扫描整棵商务关系树。 visible := rankingBusinessPromotes(db, scope, f.Sources).Select("DISTINCT p.source_db,p.id") return analyticsQuery(db, all, f, table, mode).Joins("JOIN (?) visible_promotes ON visible_promotes.source_db=r.source_db AND visible_promotes.id=r.promote_id", visible) } func (r *rankingRepo) Collect(ctx context.Context, kind business.RankingKind, scope business.AnalyticsScope, f business.RankingRange) (*business.RankingData, error) { db := r.data.DB().WithContext(ctx) out := &business.RankingData{Current: map[business.AnalyticsKey]*business.RankingMetrics{}, Previous: map[business.AnalyticsKey]*business.RankingMetrics{}, Available: true} if kind == business.BusinessRanking { var found []struct{ ID uint } if err := rankingBusinessBindings(db, scope, f.Sources).Select("target.id").Limit(1).Scan(&found).Error; err != nil { return nil, business.ErrStorage } if len(found) == 0 { return out, nil } available, err := businessTableAvailable(db, "tab_promote") if err != nil { return nil, err } if !available { out.Available = false return out, nil } var pairs []struct { UserID uint Username string Level int SourceDB string `gorm:"column:source_db"` PromoteID int64 } if err := rankingBusinessPromotes(db, scope, f.Sources).Select("DISTINCT target.id AS user_id,target.username,target.level,p.source_db,p.id AS promote_id").Scan(&pairs).Error; err != nil { return nil, business.ErrStorage } businesses := map[uint]*business.RankingBusiness{} for _, po := range pairs { if businesses[po.UserID] == nil { businesses[po.UserID] = &business.RankingBusiness{ID: po.UserID, Name: po.Username, Level: po.Level} } target := businesses[po.UserID] target.Channels = append(target.Channels, business.AnalyticsKey{SourceDB: po.SourceDB, ID: po.PromoteID}) } for _, target := range businesses { out.Businesses = append(out.Businesses, target) } if len(out.Businesses) == 0 { return out, nil } } else { empty, available, err := prepareAnalytics(db, scope, f.AnalyticsFilter) if err != nil { return nil, err } out.Available = available if empty || !available { return out, nil } } for _, table := range []string{"tab_sw_report_day", "tab_sw_report_day_key"} { available, err := businessTableAvailable(db, table) if err != nil { return nil, err } if !available { out.Available = false return out, nil } } group, queryContext := errgroup.WithContext(ctx) group.SetLimit(2) queryDB := db.WithContext(queryContext) for _, period := range []struct { begin, end string target map[business.AnalyticsKey]*business.RankingMetrics }{{f.Begin, f.End, out.Current}, {f.PreviousBegin, f.PreviousEnd, out.Previous}} { group.Go(func() error { query := f.AnalyticsFilter query.Begin, query.End = period.begin, period.end rowFor := func(po analyticsAggPO) *business.RankingMetrics { key := business.AnalyticsKey{SourceDB: po.SourceDB, ID: *po.ID} if period.target[key] == nil { period.target[key] = &business.RankingMetrics{} } return period.target[key] } for _, pay := range []bool{true, false} { table, mode := "tab_sw_report_day", 0 if !pay { table = "tab_sw_report_day_key" if scope.IDCardDedup { mode = 1 } } q := rankingReportQuery(queryDB, kind, scope, query, table, mode) id := "r.promote_id" if kind == business.GameRanking { id = "r.game_id" } if kind == business.ChannelRanking { if pay { q = analyticsPayLeaves(queryDB, q) } q = q.Joins("LEFT JOIN tab_promote root_p ON root_p.source_db=r.source_db AND root_p.id=r.promote_id") id = "COALESCE(NULLIF(root_p.top_promote_id,0),r.promote_id)" } selects := "r.source_db," + id + " AS entity_id" group := "r.source_db," + id if pay { selects += ",COALESCE(SUM(r.total_pay),0) AS amount" } else { selects += ",r.metric,COUNT(DISTINCT r.identity_key) AS count" group += ",r.metric" } var rows []analyticsAggPO if err := q.Select(selects).Group(group).Scan(&rows).Error; err != nil { return business.ErrStorage } for _, po := range rows { if po.ID == nil || (pay && *po.ID < 0) { continue } row := rowFor(po) if pay { row.Pay = po.Amount } else { putAnalyticsCount(&row.Counts, po.Metric, po.Count) } } } return nil }) } if err := group.Wait(); err != nil { return nil, err } if kind != business.BusinessRanking && len(out.Current) > 0 { table, column := "tab_promote", "account" if kind == business.GameRanking { table, column = "tab_game", "game_name" } available, err := businessTableAvailable(db, table) if err != nil { return nil, err } if !available { out.Available = false return out, nil } keys := make([]business.AnalyticsKey, 0, len(out.Current)) for key := range out.Current { keys = append(keys, key) } for start := 0; start < len(keys); start += 300 { var rows []struct { ID int64 SourceDB string `gorm:"column:source_db"` Name string } if err := db.Table(table).Select("id,source_db,"+column+" AS name").Where("(source_db,id) IN ?", analyticsPairs(keys[start:min(start+300, len(keys))])).Scan(&rows).Error; err != nil { return nil, business.ErrStorage } for _, po := range rows { if row := out.Current[business.AnalyticsKey{SourceDB: po.SourceDB, ID: po.ID}]; row != nil { row.Name = po.Name } } } } return out, nil }