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

211 lines
5.8 KiB
Go

package business
import (
"context"
"math/big"
"sort"
"strings"
"time"
"github.com/go-kratos/kratos/v3/errors"
)
var (
ErrServerAnalysisGame = errors.BadRequest("SW_SERVER_ANALYSIS_GAME", "请选择游戏")
ErrServerOptionsUnavailable = errors.BadRequest("SW_SERVER_OPTIONS_UNAVAILABLE", "游戏或区服数据尚未接入")
)
type ServerAnalysisKey struct {
SourceDB string
GameID int64
ServerNum string
}
type ServerAnalysisFilter struct {
AnalyticsFilter
ServerNum string
}
type ServerAnalysisRow struct {
ServerAnalysisKey
GameName, ServerName string
OpenTime *int64
Counts PortraitCounts
Pay, ARPU string
}
type ServerAnalysisData struct {
Rows []*ServerAnalysisRow
Available bool
}
type ServerAnalysisPage struct {
ServerAnalysisData
Total int64
Summary *ServerAnalysisRow
Begin, End string
}
type ServerOption struct {
Number, Name string
OpenTime *int64
}
type ServerAnalysisRepo interface {
Aggregate(context.Context, AnalyticsScope, ServerAnalysisFilter) (*ServerAnalysisData, error)
Servers(context.Context, UserScope, []string, []string, string, int) ([]*ServerOption, error)
}
type ServerAnalysisUsecase struct {
repo ServerAnalysisRepo
accounts *AccountUsecase
preferences *UserPreferenceUsecase
now func() time.Time
}
func NewServerAnalysisUsecase(repo ServerAnalysisRepo, accounts *AccountUsecase, preferences *UserPreferenceUsecase) *ServerAnalysisUsecase {
return &ServerAnalysisUsecase{repo: repo, accounts: accounts, preferences: preferences, now: time.Now}
}
func (uc *ServerAnalysisUsecase) Servers(ctx context.Context, sources, games []string, keyword string, limit int) ([]*ServerOption, error) {
actor, account, err := uc.accounts.actor(ctx)
if err != nil {
return nil, err
}
viewer := portraitUserScope(account)
viewer.All = actor.AuthorityID == 888
if len(games) == 0 {
return nil, ErrServerAnalysisGame
}
if limit <= 0 {
limit = 50
}
if limit > 100 {
limit = 100000
}
return uc.repo.Servers(ctx, viewer, sources, games, strings.TrimSpace(keyword), limit)
}
func (uc *ServerAnalysisUsecase) List(ctx context.Context, f ServerAnalysisFilter, page, size int) (*ServerAnalysisPage, error) {
actor, account, err := uc.accounts.actor(ctx)
if err != nil {
return nil, err
}
if len(f.GameNames) == 0 {
return nil, ErrServerAnalysisGame
}
pref, err := uc.preferences.Current(ctx)
if err != nil {
return nil, err
}
f.StartDate, f.EndDate = reportDateDefaults(f.StartDate, f.EndDate, 30, uc.now())
if f.EndDate.Before(*f.StartDate) {
return nil, ErrAnalyticsDateOrder
}
f.Begin, f.End = f.StartDate.Format("2006-01-02"), f.EndDate.Format("2006-01-02")
f.ServerNum = strings.TrimSpace(f.ServerNum)
viewer := portraitUserScope(account)
viewer.All = actor.AuthorityID == 888
data, err := uc.repo.Aggregate(ctx, AnalyticsScope{Viewer: viewer, IncludeSub: pref.IncludeSubPromote, IDCardDedup: pref.IDCardDedup}, f)
if err != nil {
return nil, err
}
out := &ServerAnalysisPage{ServerAnalysisData: ServerAnalysisData{Rows: []*ServerAnalysisRow{}, Available: data.Available}, Begin: f.Begin, End: f.End}
if !data.Available {
return out, nil
}
rows := make([]*ServerAnalysisRow, 0, len(data.Rows))
summary := &ServerAnalysisRow{ServerName: "合计"}
sumPay := new(big.Rat)
for _, row := range data.Rows {
if strings.TrimSpace(row.ServerNum) == "" {
continue
}
row.Counts = analyticsCap(row.Counts)
pay, err := portraitNumber(row.Pay)
if err != nil {
return nil, err
}
row.Pay = portraitDecimal(pay, 2)
pay, _ = portraitNumber(row.Pay)
// 未指定区服时忽略全零行;指定区服仍只返回报表中已有的分组。
if f.ServerNum == "" && row.Counts.Register == 0 && row.Counts.Active == 0 && row.Counts.Pay == 0 && pay.Sign() == 0 {
continue
}
row.ARPU = analyticsRatio(pay, row.Counts.Active, false)
summary.Counts = analyticsAdd(summary.Counts, row.Counts)
sumPay.Add(sumPay, pay)
rows = append(rows, row)
}
summary.Pay = portraitDecimal(sumPay, 2)
summary.ARPU = analyticsRatio(sumPay, summary.Counts.Active, false)
out.Summary, out.Total = summary, int64(len(rows))
if err := sortServerAnalysis(rows, f.SortField, f.Ascending); err != nil {
return nil, err
}
if page < 1 {
page = 1
}
if size <= 0 || size > 500 {
size = 500
}
if page > len(rows)/size+1 {
return out, nil
}
from := (page - 1) * size
if from < len(rows) {
out.Rows = rows[from:min(from+size, len(rows))]
}
return out, nil
}
func sortServerAnalysis(rows []*ServerAnalysisRow, field string, ascending bool) error {
if field == "" {
sort.SliceStable(rows, func(i, j int) bool {
a, b := rows[i], rows[j]
if a.SourceDB != b.SourceDB {
return a.SourceDB < b.SourceDB
}
if a.GameID != b.GameID {
return a.GameID < b.GameID
}
return a.ServerNum < b.ServerNum
})
return nil
}
values := map[*ServerAnalysisRow]*big.Rat{}
for _, row := range rows {
var n *big.Rat
var err error
switch field {
case "openTime":
v := int64(0)
if row.OpenTime != nil {
v = *row.OpenTime
}
n = big.NewRat(v, 1)
case "newRegisterCount":
n = big.NewRat(row.Counts.Register, 1)
case "activeCount":
n = big.NewRat(row.Counts.Active, 1)
case "payUserCount":
n = big.NewRat(row.Counts.Pay, 1)
case "arpu":
n, err = portraitNumber(row.ARPU)
default:
n, err = portraitNumber(row.Pay)
}
if err != nil {
return err
}
values[row] = n
}
sort.SliceStable(rows, func(i, j int) bool {
a, b := rows[i], rows[j]
if c := values[a].Cmp(values[b]); c != 0 {
if ascending {
return c < 0
}
return c > 0
}
if a.ServerNum != b.ServerNum {
return strings.TrimSpace(a.ServerNum) < strings.TrimSpace(b.ServerNum)
}
if a.SourceDB != b.SourceDB {
return a.SourceDB < b.SourceDB
}
return a.GameID < b.GameID
})
return nil
}