226 lines
9.4 KiB
Go
226 lines
9.4 KiB
Go
package business
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"gorm.io/gorm"
|
|
"kra/internal/biz/business"
|
|
)
|
|
|
|
type workbenchRepo struct{ data Provider }
|
|
|
|
func NewWorkbenchRepo(data Provider) business.WorkbenchRepo { return &workbenchRepo{data: data} }
|
|
func workbenchAvailable(db *gorm.DB, scope business.AnalyticsScope, sources []string, kind business.WorkbenchRankKind) (bool, bool, error) {
|
|
empty, available, err := prepareAnalytics(db, scope, business.AnalyticsFilter{Sources: sources})
|
|
if err != nil || empty || !available {
|
|
return empty, available, err
|
|
}
|
|
tables := []string{}
|
|
if kind == "" || kind == business.WorkbenchPayHigh || kind == business.WorkbenchPayLow {
|
|
tables = append(tables, "tab_sw_report_day")
|
|
}
|
|
if kind == "" || kind == business.WorkbenchRegister {
|
|
tables = append(tables, "tab_sw_report_day_key")
|
|
}
|
|
available, err = businessTablesAvailable(db, tables...)
|
|
return false, available, err
|
|
}
|
|
func (r *workbenchRepo) Summary(ctx context.Context, scope business.AnalyticsScope, sources []string, w business.WorkbenchWindow) (*business.WorkbenchTotals, error) {
|
|
db := r.data.DB().WithContext(ctx)
|
|
out := &business.WorkbenchTotals{Available: true}
|
|
empty, available, err := workbenchAvailable(db, scope, sources, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out.Available = available
|
|
if empty || !available {
|
|
return out, nil
|
|
}
|
|
mode := 0
|
|
if scope.IDCardDedup {
|
|
mode = 1
|
|
}
|
|
date := func(value time.Time) string { return value.Format("2006-01-02") }
|
|
today, yesterday := date(w.Today), date(w.Yesterday)
|
|
monthStart, previousStart, previousEnd := date(w.MonthStart), date(w.PreviousMonthStart), date(w.PreviousMonthEnd)
|
|
dayFilter := business.AnalyticsFilter{Sources: sources, Begin: yesterday, End: today}
|
|
type datedCount struct {
|
|
StatDate string
|
|
Metric int
|
|
Count int64
|
|
}
|
|
type datedChannel struct {
|
|
StatDate string
|
|
SourceDB string `gorm:"column:source_db"`
|
|
ID *int64 `gorm:"column:entity_id"`
|
|
}
|
|
var counts []datedCount
|
|
var registrations, pays []datedChannel
|
|
var amounts struct{ TodayPay, YesterdayPay, MonthPay, PreviousMonthPay string }
|
|
var created struct{ TodayCount, YesterdayCount int64 }
|
|
group, queryContext := errgroup.WithContext(ctx)
|
|
group.SetLimit(2)
|
|
queryDB := db.WithContext(queryContext)
|
|
group.Go(func() error {
|
|
return storageError(analyticsQuery(queryDB, scope, dayFilter, "tab_sw_report_day_key", mode).
|
|
Select("CAST(r.stat_date AS CHAR) AS stat_date,r.metric,COUNT(DISTINCT " + portraitIdentity(queryDB, scope.IDCardDedup) + ") AS count").
|
|
Where("r.metric IN (1,2)").Group("r.stat_date,r.metric").Scan(&counts).Error)
|
|
})
|
|
group.Go(func() error {
|
|
f := business.AnalyticsFilter{Sources: sources, Begin: previousStart, End: today}
|
|
// 同时取得两日和两个自然月区间的金额;月初的昨日须额外纳入扫描范围。
|
|
return storageError(analyticsQuery(queryDB, scope, f, "tab_sw_report_day", 0).
|
|
Where("((r.stat_date BETWEEN ? AND ?) OR (r.stat_date BETWEEN ? AND ?) OR r.stat_date=?)", monthStart, today, previousStart, previousEnd, yesterday).
|
|
Select("COALESCE(SUM(CASE WHEN r.stat_date=? THEN r.total_pay ELSE 0 END),0) AS today_pay,"+
|
|
"COALESCE(SUM(CASE WHEN r.stat_date=? THEN r.total_pay ELSE 0 END),0) AS yesterday_pay,"+
|
|
"COALESCE(SUM(CASE WHEN r.stat_date BETWEEN ? AND ? THEN r.total_pay ELSE 0 END),0) AS month_pay,"+
|
|
"COALESCE(SUM(CASE WHEN r.stat_date BETWEEN ? AND ? THEN r.total_pay ELSE 0 END),0) AS previous_month_pay",
|
|
today, yesterday, monthStart, today, previousStart, previousEnd).Scan(&amounts).Error)
|
|
})
|
|
group.Go(func() error {
|
|
// COUNT(DISTINCT identity_key)>0 等价于存在非 NULL 的注册键;保留空字符串的旧语义。
|
|
return storageError(analyticsQuery(queryDB, scope, dayFilter, "tab_sw_report_day_key", mode).
|
|
Where("r.metric=1 AND r.identity_key IS NOT NULL").Distinct().
|
|
Select("CAST(r.stat_date AS CHAR) AS stat_date,r.source_db,r.promote_id AS entity_id").Scan(®istrations).Error)
|
|
})
|
|
group.Go(func() error {
|
|
return storageError(analyticsQuery(queryDB, scope, dayFilter, "tab_sw_report_day", 0).
|
|
Select("CAST(r.stat_date AS CHAR) AS stat_date,r.source_db,r.promote_id AS entity_id").
|
|
Group("r.stat_date,r.source_db,r.promote_id").Having("SUM(r.total_pay)>0").Scan(&pays).Error)
|
|
})
|
|
group.Go(func() error {
|
|
todayEnd := w.Today.AddDate(0, 0, 1).Unix() - 1
|
|
yesterdayEnd := w.Yesterday.AddDate(0, 0, 1).Unix() - 1
|
|
return storageError(analyticsBasePromotes(queryDB, scope, dayFilter, "p").
|
|
Where("p.create_time BETWEEN ? AND ?", w.Yesterday.Unix(), todayEnd).
|
|
Select("COALESCE(SUM(CASE WHEN p.create_time BETWEEN ? AND ? THEN 1 ELSE 0 END),0) AS today_count,"+
|
|
"COALESCE(SUM(CASE WHEN p.create_time BETWEEN ? AND ? THEN 1 ELSE 0 END),0) AS yesterday_count",
|
|
w.Today.Unix(), todayEnd, w.Yesterday.Unix(), yesterdayEnd).Scan(&created).Error)
|
|
})
|
|
if err := group.Wait(); err != nil {
|
|
return nil, err
|
|
}
|
|
days := map[string]*business.WorkbenchDay{today: &out.Today, yesterday: &out.Yesterday}
|
|
for _, row := range counts {
|
|
if day := days[row.StatDate]; day != nil {
|
|
putAnalyticsCount(&day.Counts, row.Metric, row.Count)
|
|
}
|
|
}
|
|
active := map[string]map[business.AnalyticsKey]bool{today: {}, yesterday: {}}
|
|
for _, row := range append(registrations, pays...) {
|
|
if keys := active[row.StatDate]; keys != nil && row.ID != nil {
|
|
keys[business.AnalyticsKey{SourceDB: row.SourceDB, ID: *row.ID}] = true
|
|
}
|
|
}
|
|
out.Today.ActiveChannels, out.Yesterday.ActiveChannels = int64(len(active[today])), int64(len(active[yesterday]))
|
|
out.Today.Pay, out.Yesterday.Pay = amounts.TodayPay, amounts.YesterdayPay
|
|
out.Today.NewChannels, out.Yesterday.NewChannels = created.TodayCount, created.YesterdayCount
|
|
out.MonthPay, out.PreviousMonthPay = amounts.MonthPay, amounts.PreviousMonthPay
|
|
|
|
return out, nil
|
|
}
|
|
|
|
type workbenchPromotePO struct {
|
|
ID int64
|
|
SourceDB string `gorm:"column:source_db"`
|
|
Account string
|
|
CreateTime, LastLoginTime *int64
|
|
}
|
|
|
|
func (r *workbenchRepo) Ranks(ctx context.Context, scope business.AnalyticsScope, f business.AnalyticsFilter, kind business.WorkbenchRankKind) (*business.WorkbenchRankData, error) {
|
|
db := r.data.DB().WithContext(ctx)
|
|
out := &business.WorkbenchRankData{Rows: []*business.WorkbenchRankRow{}, Available: true}
|
|
empty, available, err := workbenchAvailable(db, scope, f.Sources, kind)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out.Available = available
|
|
if empty || !available {
|
|
return out, nil
|
|
}
|
|
rows := map[business.AnalyticsKey]*business.WorkbenchRankRow{}
|
|
rowFor := func(source string, id int64) *business.WorkbenchRankRow {
|
|
key := business.AnalyticsKey{SourceDB: source, ID: id}
|
|
if rows[key] == nil {
|
|
rows[key] = &business.WorkbenchRankRow{AnalyticsKey: key, Value: "0"}
|
|
}
|
|
return rows[key]
|
|
}
|
|
if kind == business.WorkbenchPayLow || kind == business.WorkbenchUnlogin {
|
|
var channels []workbenchPromotePO
|
|
if err := analyticsBasePromotes(db, scope, f, "p").Select("p.id,p.source_db,p.create_time,p.last_login_time").Where("p.promote_level=1").Scan(&channels).Error; err != nil {
|
|
return nil, business.ErrStorage
|
|
}
|
|
for _, po := range channels {
|
|
row := rowFor(po.SourceDB, po.ID)
|
|
row.CreatedUnix, row.LastLoginUnix = po.CreateTime, po.LastLoginTime
|
|
}
|
|
}
|
|
if kind != business.WorkbenchUnlogin {
|
|
mode, table := 0, "tab_sw_report_day"
|
|
if kind == business.WorkbenchRegister {
|
|
table = "tab_sw_report_day_key"
|
|
if scope.IDCardDedup {
|
|
mode = 1
|
|
}
|
|
}
|
|
q := analyticsQuery(db, scope, f, table, mode)
|
|
id := "r.promote_id"
|
|
if kind == business.WorkbenchRegister {
|
|
q = q.Where("r.metric=1")
|
|
if scope.IncludeSub {
|
|
// 旧工作台的注册榜使用 INNER JOIN + CASE,区别于渠道统计的历史 LEFT JOIN 口径。
|
|
q = q.Joins("INNER JOIN tab_promote root_p ON root_p.source_db=r.source_db AND root_p.id=r.promote_id")
|
|
id = "CASE WHEN root_p.promote_level=1 THEN root_p.id WHEN root_p.top_promote_id>0 THEN root_p.top_promote_id ELSE root_p.id END"
|
|
}
|
|
} else {
|
|
q = analyticsPayLeaves(db, 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 = "CASE WHEN root_p.promote_level=1 THEN root_p.id WHEN root_p.top_promote_id>0 THEN root_p.top_promote_id ELSE COALESCE(root_p.id,r.promote_id) END"
|
|
}
|
|
value := "COALESCE(SUM(r.total_pay),0)"
|
|
if kind == business.WorkbenchRegister {
|
|
value = "COUNT(DISTINCT r.identity_key)"
|
|
}
|
|
var agg []analyticsAggPO
|
|
if err := q.Select("r.source_db," + id + " AS entity_id," + value + " AS amount").Group("r.source_db," + id).Scan(&agg).Error; err != nil {
|
|
return nil, business.ErrStorage
|
|
}
|
|
for _, po := range agg {
|
|
if po.ID != nil {
|
|
rowFor(po.SourceDB, *po.ID).Value = po.Amount
|
|
}
|
|
}
|
|
|
|
}
|
|
for _, row := range rows {
|
|
out.Rows = append(out.Rows, row)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (r *workbenchRepo) FillRankNames(ctx context.Context, rows []*business.WorkbenchRankRow) error {
|
|
if len(rows) == 0 {
|
|
return nil
|
|
}
|
|
keys := make([]business.AnalyticsKey, 0, len(rows))
|
|
for _, row := range rows {
|
|
keys = append(keys, row.AnalyticsKey)
|
|
}
|
|
var names []workbenchPromotePO
|
|
if err := r.data.DB().WithContext(ctx).Table("tab_promote").Select("id,source_db,account").Where("(source_db,id) IN ?", analyticsPairs(keys)).Scan(&names).Error; err != nil {
|
|
return business.ErrStorage
|
|
}
|
|
byKey := make(map[business.AnalyticsKey]string, len(names))
|
|
for _, po := range names {
|
|
byKey[business.AnalyticsKey{SourceDB: po.SourceDB, ID: po.ID}] = po.Account
|
|
}
|
|
for _, row := range rows {
|
|
row.Name = byKey[row.AnalyticsKey]
|
|
}
|
|
return nil
|
|
}
|