179 lines
6.8 KiB
Go
179 lines
6.8 KiB
Go
package business
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"gorm.io/gorm"
|
|
"kra/internal/biz/business"
|
|
)
|
|
|
|
type serverAnalysisRepo struct{ data Provider }
|
|
|
|
func NewServerAnalysisRepo(data Provider) business.ServerAnalysisRepo {
|
|
return &serverAnalysisRepo{data: data}
|
|
}
|
|
|
|
func serverReportQuery(db *gorm.DB, scope business.AnalyticsScope, f business.ServerAnalysisFilter, table string, mode int) *gorm.DB {
|
|
q := ledgerScopeQuery(db, business.LedgerScope{Viewer: scope.Viewer, IncludeSub: scope.IncludeSub, Unrestricted: scope.Viewer.All}, business.LedgerFilter{Sources: f.Sources}, table)
|
|
q = q.Where("r.stat_date >= ? AND r.stat_date <= ? AND r.dedup_mode=? AND r.server_num != ''", f.Begin, f.End, mode).Where("r.game_name IN ?", f.GameNames)
|
|
if f.ServerNum != "" {
|
|
q = q.Where("r.server_num=?", f.ServerNum)
|
|
}
|
|
return q
|
|
}
|
|
|
|
type serverAnalysisPO struct {
|
|
SourceDB string `gorm:"column:source_db"`
|
|
GameID int64
|
|
ServerNum, GameName, ServerName string
|
|
StartTime *int64
|
|
Metric int
|
|
Count int64
|
|
Amount string
|
|
}
|
|
|
|
func (r *serverAnalysisRepo) Aggregate(ctx context.Context, scope business.AnalyticsScope, f business.ServerAnalysisFilter) (*business.ServerAnalysisData, error) {
|
|
db := r.data.DB().WithContext(ctx)
|
|
out := &business.ServerAnalysisData{Rows: []*business.ServerAnalysisRow{}, Available: true}
|
|
empty, available, err := prepareAnalytics(db, scope, business.AnalyticsFilter{Sources: f.Sources})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out.Available = available
|
|
if empty || !available {
|
|
return out, nil
|
|
}
|
|
for _, table := range []string{"tab_sw_report_server_day", "tab_sw_report_server_day_key", "tab_game_server", "tab_game"} {
|
|
available, err := businessTableAvailable(db, table)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !available {
|
|
out.Available = false
|
|
return out, nil
|
|
}
|
|
}
|
|
rows := map[business.ServerAnalysisKey]*business.ServerAnalysisRow{}
|
|
rowFor := func(po serverAnalysisPO) *business.ServerAnalysisRow {
|
|
key := business.ServerAnalysisKey{SourceDB: po.SourceDB, GameID: po.GameID, ServerNum: po.ServerNum}
|
|
if rows[key] == nil {
|
|
rows[key] = &business.ServerAnalysisRow{ServerAnalysisKey: key, Pay: "0"}
|
|
}
|
|
return rows[key]
|
|
}
|
|
mode := 0
|
|
if scope.IDCardDedup {
|
|
mode = 1
|
|
}
|
|
var counts []serverAnalysisPO
|
|
if err := serverReportQuery(db, scope, f, "tab_sw_report_server_day_key", mode).Select("r.source_db,r.game_id,r.server_num,r.metric,COUNT(DISTINCT r.identity_key) AS count").Group("r.source_db,r.game_id,r.server_num,r.metric").Scan(&counts).Error; err != nil {
|
|
return nil, business.ErrStorage
|
|
}
|
|
for _, po := range counts {
|
|
if strings.TrimSpace(po.ServerNum) != "" {
|
|
putAnalyticsCount(&rowFor(po).Counts, po.Metric, po.Count)
|
|
}
|
|
}
|
|
var pays []serverAnalysisPO
|
|
if err := serverReportQuery(db, scope, f, "tab_sw_report_server_day", 0).Select("r.source_db,r.game_id,r.server_num,MAX(r.game_name) AS game_name,COALESCE(SUM(r.total_pay),0) AS amount").Group("r.source_db,r.game_id,r.server_num").Scan(&pays).Error; err != nil {
|
|
return nil, business.ErrStorage
|
|
}
|
|
for _, po := range pays {
|
|
if strings.TrimSpace(po.ServerNum) == "" {
|
|
continue
|
|
}
|
|
row := rowFor(po)
|
|
row.Pay = po.Amount
|
|
row.GameName = strings.TrimSpace(po.GameName)
|
|
}
|
|
if err := fillServerAnalysisNames(db, rows); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, row := range rows {
|
|
out.Rows = append(out.Rows, row)
|
|
}
|
|
return out, nil
|
|
}
|
|
func fillServerAnalysisNames(db *gorm.DB, rows map[business.ServerAnalysisKey]*business.ServerAnalysisRow) error {
|
|
keys := make([][]any, 0, len(rows))
|
|
missingGames := map[business.AnalyticsKey]bool{}
|
|
for key, row := range rows {
|
|
keys = append(keys, []any{key.SourceDB, key.GameID, key.ServerNum})
|
|
if row.GameName == "" {
|
|
missingGames[business.AnalyticsKey{SourceDB: key.SourceDB, ID: key.GameID}] = true
|
|
}
|
|
}
|
|
// 三元组限定资料查询,避免同号游戏或同名区服跨平台串联。
|
|
for start := 0; start < len(keys); start += 250 {
|
|
var meta []serverAnalysisPO
|
|
if err := db.Table("tab_game_server").Select("source_db,game_id,server_num,server_name,start_time").Where("(source_db,game_id,server_num) IN ?", keys[start:min(start+250, len(keys))]).Order("id").Scan(&meta).Error; err != nil {
|
|
return business.ErrStorage
|
|
}
|
|
seen := map[business.ServerAnalysisKey]bool{}
|
|
for _, po := range meta {
|
|
key := business.ServerAnalysisKey{SourceDB: po.SourceDB, GameID: po.GameID, ServerNum: po.ServerNum}
|
|
if row := rows[key]; row != nil && !seen[key] {
|
|
row.ServerName, row.OpenTime = po.ServerName, po.StartTime
|
|
seen[key] = true
|
|
}
|
|
}
|
|
}
|
|
pairs := make([]business.AnalyticsKey, 0, len(missingGames))
|
|
for key := range missingGames {
|
|
pairs = append(pairs, key)
|
|
}
|
|
names := map[business.AnalyticsKey]string{}
|
|
for start := 0; start < len(pairs); start += 300 {
|
|
var meta []struct {
|
|
SourceDB string `gorm:"column:source_db"`
|
|
ID int64
|
|
GameName string
|
|
}
|
|
if err := db.Table("tab_game").Select("source_db,id,game_name").Where("(source_db,id) IN ?", analyticsPairs(pairs[start:min(start+300, len(pairs))])).Scan(&meta).Error; err != nil {
|
|
return business.ErrStorage
|
|
}
|
|
for _, po := range meta {
|
|
names[business.AnalyticsKey{SourceDB: po.SourceDB, ID: po.ID}] = po.GameName
|
|
}
|
|
}
|
|
for _, row := range rows {
|
|
if row.GameName == "" {
|
|
row.GameName = names[business.AnalyticsKey{SourceDB: row.SourceDB, ID: row.GameID}]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func (r *serverAnalysisRepo) Servers(ctx context.Context, sources, games []string, keyword string, limit int) ([]*business.ServerOption, error) {
|
|
db := r.data.DB().WithContext(ctx)
|
|
for _, table := range []string{"tab_game", "tab_game_server"} {
|
|
available, err := businessTableAvailable(db, table)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !available {
|
|
return nil, business.ErrServerOptionsUnavailable
|
|
}
|
|
}
|
|
// MySQL 5.7 不会将相关 EXISTS 展开为半连接,导致扫描全部区服。
|
|
// 多列 IN 让优化器先按游戏名索引定位,再使用区服的 game_id 索引,
|
|
// 同时保留平台匹配和重复游戏资料不扩增结果的语义。
|
|
gamesQuery := db.Table("tab_game g").Select("g.source_db,g.id").Where("g.game_name IN ?", games)
|
|
q := db.Table("tab_game_server s").Where("(s.source_db,s.game_id) IN (?)", gamesQuery)
|
|
if len(sources) > 0 {
|
|
q = q.Where("s.source_db IN ?", sources)
|
|
}
|
|
if keyword != "" {
|
|
q = q.Where("s.server_name LIKE ?", "%"+keyword+"%")
|
|
}
|
|
var rows []serverAnalysisPO
|
|
if err := q.Select("s.server_num,s.server_name,s.start_time").Order("s.server_num,s.source_db,s.game_id,s.id").Limit(limit).Scan(&rows).Error; err != nil {
|
|
return nil, business.ErrStorage
|
|
}
|
|
out := make([]*business.ServerOption, 0, len(rows))
|
|
for _, row := range rows {
|
|
out = append(out, &business.ServerOption{Number: row.ServerNum, Name: row.ServerName, OpenTime: row.StartTime})
|
|
}
|
|
return out, nil
|
|
}
|