feat: 迁移商务工作台指标和仪表盘榜单
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
package business
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-kratos/kratos/v3/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrWorkbenchSource = errors.BadRequest("SW_WORKBENCH_SOURCE", "请选择所属平台后查看数据")
|
||||
ErrWorkbenchPeriod = errors.BadRequest("SW_WORKBENCH_PERIOD", "时间维度无效,应为 today/yesterday/last7/last30")
|
||||
ErrWorkbenchUnavailable = errors.BadRequest("SW_WORKBENCH_UNAVAILABLE", "工作台所需业务数据尚未接入")
|
||||
)
|
||||
|
||||
type WorkbenchRankKind string
|
||||
|
||||
const (
|
||||
WorkbenchRegister WorkbenchRankKind = "register"
|
||||
WorkbenchPayHigh WorkbenchRankKind = "payHigh"
|
||||
WorkbenchPayLow WorkbenchRankKind = "payLow"
|
||||
WorkbenchUnlogin WorkbenchRankKind = "unlogin"
|
||||
)
|
||||
|
||||
type WorkbenchWindow struct{ Today, Yesterday, MonthStart, PreviousMonthStart, PreviousMonthEnd time.Time }
|
||||
type WorkbenchDay struct {
|
||||
Counts PortraitCounts
|
||||
Pay string
|
||||
ActiveChannels, NewChannels int64
|
||||
}
|
||||
type WorkbenchTotals struct {
|
||||
Today, Yesterday WorkbenchDay
|
||||
MonthPay, PreviousMonthPay string
|
||||
Available bool
|
||||
}
|
||||
type WorkbenchCard struct {
|
||||
Value, CompareValue, ChangeRate *string
|
||||
CompareLabel string
|
||||
}
|
||||
type WorkbenchSummary struct {
|
||||
TodayRegister, TodayActive, TodayPay, MonthPay, ActivePromote, NewPromote *WorkbenchCard
|
||||
Available bool
|
||||
}
|
||||
type WorkbenchRankRow struct {
|
||||
AnalyticsKey
|
||||
Rank int
|
||||
Name, Value string
|
||||
CreatedUnix, LastLoginUnix *int64
|
||||
}
|
||||
type WorkbenchRankData struct {
|
||||
Rows []*WorkbenchRankRow
|
||||
Available bool
|
||||
}
|
||||
type WorkbenchRepo interface {
|
||||
Summary(context.Context, AnalyticsScope, []string, WorkbenchWindow) (*WorkbenchTotals, error)
|
||||
Ranks(context.Context, AnalyticsScope, AnalyticsFilter, WorkbenchRankKind) (*WorkbenchRankData, error)
|
||||
}
|
||||
type WorkbenchUsecase struct {
|
||||
repo WorkbenchRepo
|
||||
accounts *AccountUsecase
|
||||
preferences *UserPreferenceUsecase
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
func NewWorkbenchUsecase(repo WorkbenchRepo, accounts *AccountUsecase, preferences *UserPreferenceUsecase) *WorkbenchUsecase {
|
||||
return &WorkbenchUsecase{repo: repo, accounts: accounts, preferences: preferences, now: time.Now}
|
||||
}
|
||||
func (uc *WorkbenchUsecase) scope(ctx context.Context, sources []string) (AnalyticsScope, error) {
|
||||
actor, account, err := uc.accounts.actor(ctx)
|
||||
if err != nil {
|
||||
return AnalyticsScope{}, err
|
||||
}
|
||||
if len(sources) == 0 {
|
||||
return AnalyticsScope{}, ErrWorkbenchSource
|
||||
}
|
||||
pref, err := uc.preferences.Current(ctx)
|
||||
if err != nil {
|
||||
return AnalyticsScope{}, err
|
||||
}
|
||||
viewer := portraitUserScope(account)
|
||||
viewer.All = actor.AuthorityID == 888
|
||||
return AnalyticsScope{Viewer: viewer, IncludeSub: pref.IncludeSubPromote, IDCardDedup: pref.IDCardDedup}, nil
|
||||
}
|
||||
func workbenchWindow(now time.Time) WorkbenchWindow {
|
||||
now = now.In(portraitLocation)
|
||||
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, portraitLocation)
|
||||
month := time.Date(today.Year(), today.Month(), 1, 0, 0, 0, 0, portraitLocation)
|
||||
previous := month.AddDate(0, -1, 0)
|
||||
lastDay := month.AddDate(0, 0, -1).Day()
|
||||
previousEnd := previous.AddDate(0, 0, min(today.Day(), lastDay)-1)
|
||||
return WorkbenchWindow{Today: today, Yesterday: today.AddDate(0, 0, -1), MonthStart: month, PreviousMonthStart: previous, PreviousMonthEnd: previousEnd}
|
||||
}
|
||||
func workbenchCard(current, previous, label string, available bool) (*WorkbenchCard, error) {
|
||||
card := &WorkbenchCard{CompareLabel: label}
|
||||
if !available {
|
||||
return card, nil
|
||||
}
|
||||
cur, err := portraitNumber(current)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prev, err := portraitNumber(previous)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
card.Value, card.CompareValue = stringPointer(portraitDecimal(cur, 2)), stringPointer(portraitDecimal(prev, 2))
|
||||
cur, _ = portraitNumber(*card.Value)
|
||||
prev, _ = portraitNumber(*card.CompareValue)
|
||||
if prev.Sign() != 0 {
|
||||
diff := new(big.Rat).Sub(cur, prev)
|
||||
diff.Mul(diff, big.NewRat(100, 1))
|
||||
diff.Quo(diff, prev)
|
||||
card.ChangeRate = stringPointer(portraitDecimal(diff, 1))
|
||||
}
|
||||
return card, nil
|
||||
}
|
||||
func (uc *WorkbenchUsecase) Summary(ctx context.Context, sources []string) (*WorkbenchSummary, error) {
|
||||
scope, err := uc.scope(ctx, sources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := uc.repo.Summary(ctx, scope, sources, workbenchWindow(uc.now()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data.Today.Counts, data.Yesterday.Counts = analyticsCap(data.Today.Counts), analyticsCap(data.Yesterday.Counts)
|
||||
integer := func(n int64) string { return big.NewInt(n).String() }
|
||||
out := &WorkbenchSummary{Available: data.Available}
|
||||
for _, item := range []struct {
|
||||
field **WorkbenchCard
|
||||
current, previous, label string
|
||||
}{
|
||||
{&out.TodayRegister, integer(data.Today.Counts.Register), integer(data.Yesterday.Counts.Register), "环比昨日"},
|
||||
{&out.TodayActive, integer(data.Today.Counts.Active), integer(data.Yesterday.Counts.Active), "环比昨日"},
|
||||
{&out.TodayPay, data.Today.Pay, data.Yesterday.Pay, "环比昨日"},
|
||||
{&out.MonthPay, data.MonthPay, data.PreviousMonthPay, "环比上月"},
|
||||
{&out.ActivePromote, integer(data.Today.ActiveChannels), integer(data.Yesterday.ActiveChannels), "环比昨日"},
|
||||
{&out.NewPromote, integer(data.Today.NewChannels), integer(data.Yesterday.NewChannels), "环比昨日"},
|
||||
} {
|
||||
card, err := workbenchCard(item.current, item.previous, item.label, data.Available)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*item.field = card
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
func (uc *WorkbenchUsecase) Ranks(ctx context.Context, sources []string, period string, kind WorkbenchRankKind) ([]*WorkbenchRankRow, error) {
|
||||
scope, err := uc.scope(ctx, sources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if kind != WorkbenchRegister && kind != WorkbenchPayHigh && kind != WorkbenchPayLow && kind != WorkbenchUnlogin {
|
||||
return nil, ErrAnalyticsInput
|
||||
}
|
||||
period = strings.ToLower(strings.TrimSpace(period))
|
||||
if period == "" {
|
||||
period = "today"
|
||||
if kind == WorkbenchPayLow {
|
||||
period = "last30"
|
||||
}
|
||||
}
|
||||
w := workbenchWindow(uc.now())
|
||||
begin, end := w.Today, w.Today
|
||||
if kind != WorkbenchUnlogin {
|
||||
switch period {
|
||||
case "today":
|
||||
case "yesterday":
|
||||
begin, end = w.Yesterday, w.Yesterday
|
||||
case "last7":
|
||||
begin = end.AddDate(0, 0, -6)
|
||||
case "last30":
|
||||
begin = end.AddDate(0, 0, -29)
|
||||
default:
|
||||
return nil, ErrWorkbenchPeriod
|
||||
}
|
||||
}
|
||||
data, err := uc.repo.Ranks(ctx, scope, AnalyticsFilter{Sources: sources, Begin: begin.Format("2006-01-02"), End: end.Format("2006-01-02")}, kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !data.Available {
|
||||
return nil, ErrWorkbenchUnavailable
|
||||
}
|
||||
rows := make([]*WorkbenchRankRow, 0, len(data.Rows))
|
||||
values := map[*WorkbenchRankRow]*big.Rat{}
|
||||
for _, row := range data.Rows {
|
||||
if kind == WorkbenchUnlogin {
|
||||
row.Value = big.NewInt(workbenchUnloginDays(row, w.Today)).String()
|
||||
}
|
||||
n, err := portraitNumber(row.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if kind == WorkbenchRegister && n.Cmp(big.NewRat(2147483647, 1)) > 0 {
|
||||
n = big.NewRat(2147483647, 1)
|
||||
}
|
||||
if (kind == WorkbenchRegister || kind == WorkbenchPayHigh) && n.Sign() <= 0 {
|
||||
continue
|
||||
}
|
||||
values[row] = n
|
||||
rows = append(rows, row)
|
||||
}
|
||||
sort.SliceStable(rows, func(i, j int) bool {
|
||||
a, b := rows[i], rows[j]
|
||||
if c := values[a].Cmp(values[b]); c != 0 {
|
||||
if kind == WorkbenchPayLow {
|
||||
return c < 0
|
||||
}
|
||||
return c > 0
|
||||
}
|
||||
if a.ID != b.ID {
|
||||
return a.ID < b.ID
|
||||
}
|
||||
return a.SourceDB < b.SourceDB
|
||||
})
|
||||
rows = rows[:min(10, len(rows))]
|
||||
for i, row := range rows {
|
||||
row.Rank = i + 1
|
||||
row.Name = (&LedgerNames{PromoteID: &row.ID, PromoteAccount: row.Name}).DisplayPromote()
|
||||
if kind != WorkbenchUnlogin {
|
||||
row.Value = portraitDecimal(values[row], 2)
|
||||
}
|
||||
}
|
||||
return rows, nil
|
||||
}
|
||||
func workbenchUnloginDays(row *WorkbenchRankRow, today time.Time) int64 {
|
||||
stamp := row.LastLoginUnix
|
||||
if stamp == nil || *stamp <= 0 {
|
||||
stamp = row.CreatedUnix
|
||||
}
|
||||
if stamp == nil || *stamp <= 0 {
|
||||
return 99999
|
||||
}
|
||||
date := time.Unix(*stamp, 0).In(portraitLocation)
|
||||
date = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, portraitLocation)
|
||||
if date.After(today) {
|
||||
return 0
|
||||
}
|
||||
return min(int64(today.Sub(date).Hours()/24), 2147483647)
|
||||
}
|
||||
Reference in New Issue
Block a user