Files
stat_center/service/crossSer/cross.go
T
2026-05-20 16:13:58 +08:00

569 lines
16 KiB
Go

package crossSer
import (
"context"
"fmt"
"sort"
"stat/api/proto"
"stat/common"
"stat/common/statutils"
"stat/common/tgbot"
"stat/common/utils"
"stat/models/botdailyhourstat"
"stat/statg"
"time"
"github.com/wcharczuk/go-chart/v2"
"github.com/wcharczuk/go-chart/v2/drawing"
"github.com/vicanso/go-charts/v2"
)
var Colors = []drawing.Color{
drawing.ColorFromHex("0780cf"),
drawing.ColorFromHex("765005"),
drawing.ColorFromHex("fa6d1d"),
drawing.ColorFromHex("0e2c82"),
drawing.ColorFromHex("b6b51f"),
drawing.ColorFromHex("da1f18"),
drawing.ColorFromHex("701866"),
drawing.ColorFromHex("f47a75"),
drawing.ColorFromHex("009db2"),
drawing.ColorFromHex("024b51"),
drawing.ColorFromHex("0780cf"),
drawing.ColorFromHex("765005"),
}
type CrossStatMethod = func(ctx context.Context, cmd *tgbot.Command) error
// 统计方法汇总
var CrossStatMethodMap = map[string]CrossStatMethod{
statutils.StatProjectRecharge: CrossStatRecharge,
statutils.StatProjectRechargeNew: CrossStatRechargeNew,
statutils.StatProjectRechargeOld: CrossStatRechargeOld,
statutils.StatProjectRechargeGold: CrossStatRechargeGold,
statutils.StatProjectRechargeVip: CrossStatRechargeVip,
statutils.StatProjectRegister: CrossStatRegister,
statutils.StatProjectVisit: CrossStatVisit,
statutils.StatProjectOrder: CrossStatOrder,
}
type CrossAppDownloadRes struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
Tip string `json:"tip"`
Data *proto.PageButtonClickResp `json:"data"`
Time time.Time `json:"time"`
}
type CrossStatRes struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
Tip string `json:"tip"`
Data *proto.BotDailyHourListResp `json:"data"`
Time time.Time `json:"time"`
}
type CrossMonthRes struct {
Code int32 `json:"code"`
Msg string `json:"msg"`
Tip string `json:"tip"`
Data *proto.BotMonthStatResp `json:"data"`
Time time.Time `json:"time"`
}
type StatItem struct {
Value int64
Stat *botdailyhourstat.BotHourStat
}
type FuncGetStatValue func(item *botdailyhourstat.BotHourStat) int64
type StatItemList []*StatItem
func (a StatItemList) Len() int { return len(a) }
func (a StatItemList) Less(i, j int) bool { return a[i].Value > a[j].Value }
func (a StatItemList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func CrossMonthStat(ctx context.Context, cmd *tgbot.Command) error {
appId, _ := cmd.GetFlagInt32Value("-app")
monthStr := cmd.GetFlagStringValue("-month")
month, err := time.ParseInLocation("2006-01-02", monthStr, time.Local)
if err != nil {
return tgbot.NewUserError(cmd, "解析日期出错, 格式:\n"+cmd.Help+"\n"+cmd.Desc)
}
getValue := func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.Recharge.Value()
}
statList := make(StatItemList, 0)
for _, addr := range statg.Conf.InnerServers {
req := &proto.BotMonthStatReq{Month: month}
if appId > 0 {
req.AppId = &appId
}
res := CrossMonthRes{}
err = InnerRequest(addr+"/api/stat/inner/botMonthStat", req, &res, true)
if err != nil {
return err
}
if res.Data == nil {
continue
}
for _, stat := range res.Data.List {
if statg.GlobalEndpoints.ExistsByAppId(stat.AppId) {
value := getValue(stat)
statList = append(statList, &StatItem{
Value: value,
Stat: stat,
})
}
}
}
// sort
sort.Sort(statList)
// 显示
y, m, _ := month.Date()
for _, item := range statList {
appName := statg.GlobalEndpoints.ExistsByAppId(item.Stat.AppId)
title := fmt.Sprintf("%d年%d月【%s】数据汇总", y, m, appName)
text := item.Stat.DisplaySummaryText(title)
if err = cmd.SendBack(text); err != nil {
return err
}
}
return nil
}
func CrossStatRecharge(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "充值排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.Recharge.Value() / 100
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("充值排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatRechargeNew(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "新用户充值排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.RechargeNew.Value() / 100
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("新用户充值排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatRechargeOld(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "老用户充值排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.RechargeOld.Value() / 100
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("老用户充值排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatRechargeGold(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "金币充值排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.RechargeGold.Value() / 100
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("金币充值排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatRechargeVip(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "会员充值排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.RechargeVip.Value() / 100
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("会员充值排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatRegister(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "注册排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.Register.Value()
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("注册排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatVisit(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "日活排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.Visit.Value()
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("日活排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func CrossStatOrder(ctx context.Context, cmd *tgbot.Command) error {
moveBackDays, _ := cmd.GetFlagInt64Value("-back")
if moveBackDays > 0 {
moveBackDays = moveBackDays * -1
}
// get stats
now := time.Now()
reportTime := utils.RecentMinute(now, 60)
if moveBackDays != 0 {
reportTime = utils.BeginWithTime(now.AddDate(0, 0, int(moveBackDays)+1))
}
title := GetDateDesc(0) + "订单排名:" + reportTime.Format("2006-01-02T15:04:05")
photo, _, err := GetRankPhoto(reportTime, title, func(item *botdailyhourstat.BotHourStat) int64 {
return item.TodayStat.Order.Count()
})
if err != nil {
return err
}
cmd.PhotoPath = photo
return cmd.SendBack(fmt.Sprintf("订单排名:%s\n", reportTime.Format("2006-01-02T15:04:05")))
}
func GetRankPhoto(reportTime time.Time, title string, getValue FuncGetStatValue) (string, []byte, error) {
charts.SetDefaultFont(statutils.Font)
statList := make(StatItemList, 0)
registerStatList := make(StatItemList, 0)
var total, registerTotal, visitTotal int64
for _, addr := range statg.Conf.InnerServers {
req := &proto.BotDailyHourListReq{
Page: common.Page{
PageNum: 1,
PageSize: 100,
},
SumDateStart: reportTime,
SumDateStop: reportTime,
}
res := CrossStatRes{}
err := InnerRequest(addr+"/api/stat/inner/botStatList", req, &res, true)
if err != nil {
return "", nil, err
}
if res.Data == nil {
continue
}
for _, stat := range res.Data.List {
if statg.GlobalEndpoints.GetBotRankEndpoints().ExistsByAppId(stat.AppId) {
value := getValue(stat)
total += value
statList = append(statList, &StatItem{
Value: value,
Stat: stat,
})
registerValue := stat.TodayStat.Register.Total.Value()
registerTotal += registerValue
visitTotal += stat.TodayStat.Visit.Total.Value()
registerStatList = append(registerStatList, &StatItem{
Value: registerValue,
Stat: stat,
})
}
}
}
// sort
sort.Sort(statList)
// draw chart
xNames := make([]string, 0)
for _, stat := range statList {
xNames = append(xNames, statg.GlobalEndpoints.GetAppShortNameById(stat.Stat.AppId))
}
sDatas := make([]charts.SeriesData, 0)
rDatas := make([]charts.SeriesData, 0)
for index, stat := range statList {
sDatas = append(sDatas, charts.SeriesData{
Value: float64(stat.Value),
Style: charts.Style{
FillColor: Colors[index%len(Colors)],
TextWrap: chart.TextWrapRune,
},
})
for _, registerStat := range registerStatList {
if stat.Stat.AppId == registerStat.Stat.AppId {
rDatas = append(rDatas, charts.SeriesData{
Value: float64(registerStat.Value),
Style: charts.Style{
FillColor: Colors[index%len(Colors)],
TextWrap: chart.TextWrapRune,
},
})
}
}
}
chartOpt := charts.ChartOption{
Width: statg.Conf.ChartsConfig.Width,
Height: statg.Conf.ChartsConfig.Height,
Type: charts.ChartOutputPNG,
Title: charts.TitleOption{
Text: title,
},
XAxis: charts.NewXAxisOption(xNames),
Legend: charts.LegendOption{
Data: []string{
fmt.Sprintf("应用:%d", len(xNames)),
fmt.Sprintf("充值:%d", total),
fmt.Sprintf("日活:%d", visitTotal),
fmt.Sprintf("注册:%d", registerTotal),
},
Icon: charts.IconRect,
Left: charts.PositionRight,
},
SeriesList: []charts.Series{
{
Type: charts.ChartTypeBar,
Data: sDatas,
Label: charts.SeriesLabel{
Show: true,
Position: charts.PositionBottom,
},
MarkLine: charts.NewMarkLine(
charts.SeriesMarkDataTypeAverage,
),
},
{
Type: charts.ChartTypeBar,
Data: rDatas,
Label: charts.SeriesLabel{
Show: true,
Position: charts.PositionBottom,
},
},
},
}
show := true
chartOpt.XAxis.Show = &show
d, err := charts.Render(chartOpt)
if err != nil {
return "", nil, err
}
buf, err := d.Bytes()
if err != nil {
return "", buf, err
}
photo := fmt.Sprintf("/tmp/%s.png", utils.UUID())
if err = statutils.WriteBytesToFile(buf, photo); err != nil {
return "", buf, err
}
return photo, buf, nil
}
func GetRankDetail(reportTime time.Time, title string, getValue FuncGetStatValue) (string, error) {
charts.SetDefaultFont(statutils.Font)
statList := make(StatItemList, 0)
registerStatList := make(StatItemList, 0)
var total, registerTotal int64
for _, addr := range statg.Conf.InnerServers {
req := &proto.BotDailyHourListReq{
Page: common.Page{
PageNum: 1,
PageSize: 100,
},
SumDateStart: reportTime,
SumDateStop: reportTime,
}
res := CrossStatRes{}
err := InnerRequest(addr+"/api/stat/inner/botStatList", req, &res, true)
if err != nil {
return "", err
}
if res.Data == nil {
continue
}
for _, stat := range res.Data.List {
if statg.GlobalEndpoints.GetBotRankEndpoints().ExistsByAppId(stat.AppId) {
value := getValue(stat)
total += value
statList = append(statList, &StatItem{
Value: value,
Stat: stat,
})
registerValue := stat.TodayStat.Register.Total.Value()
registerTotal += registerValue
registerStatList = append(registerStatList, &StatItem{
Value: registerValue,
Stat: stat,
})
}
}
}
// sort
sort.Sort(statList)
// draw chart
xNames := make([]string, 0)
for _, stat := range statList {
xNames = append(xNames, statg.GlobalEndpoints.GetAppShortNameById(stat.Stat.AppId))
}
sDatas := make([]charts.SeriesData, 0)
rDatas := make([]charts.SeriesData, 0)
for index, stat := range statList {
sDatas = append(sDatas, charts.SeriesData{
Value: float64(stat.Value),
Style: charts.Style{
FillColor: Colors[index%len(Colors)],
TextWrap: chart.TextWrapRune,
},
})
for _, registerStat := range registerStatList {
if stat.Stat.AppId == registerStat.Stat.AppId {
rDatas = append(rDatas, charts.SeriesData{
Value: float64(registerStat.Value),
Style: charts.Style{
FillColor: Colors[index%len(Colors)],
TextWrap: chart.TextWrapRune,
},
})
}
}
}
chartOpt := charts.ChartOption{
Width: statg.Conf.ChartsConfig.Width,
Height: statg.Conf.ChartsConfig.Height,
Type: charts.ChartOutputPNG,
Title: charts.TitleOption{
Text: title,
},
XAxis: charts.NewXAxisOption(xNames),
Legend: charts.LegendOption{
Data: []string{
fmt.Sprintf("应用:%d", len(xNames)),
fmt.Sprintf("充值:%d", total),
fmt.Sprintf("注册:%d", registerTotal),
},
Icon: charts.IconRect,
Left: charts.PositionRight,
},
SeriesList: []charts.Series{
{
Type: charts.ChartTypeBar,
Data: sDatas,
Label: charts.SeriesLabel{
Show: true,
Position: charts.PositionBottom,
},
MarkLine: charts.NewMarkLine(
charts.SeriesMarkDataTypeAverage,
),
},
{
Type: charts.ChartTypeBar,
Data: rDatas,
Label: charts.SeriesLabel{
Show: true,
Position: charts.PositionBottom,
},
},
},
}
show := true
chartOpt.XAxis.Show = &show
d, err := charts.Render(chartOpt)
if err != nil {
return "", err
}
buf, err := d.Bytes()
if err != nil {
return "", err
}
photo := fmt.Sprintf("/tmp/%s.png", utils.UUID())
if err = statutils.WriteBytesToFile(buf, photo); err != nil {
return "", err
}
return photo, nil
}