211 lines
5.8 KiB
Go
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
|
|
}
|