Files
kra-new/internal/data/business/server_analysis.go
T

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
}