@@ -0,0 +1,270 @@
|
||||
package advgroupser
|
||||
|
||||
import (
|
||||
"91porn-server/common"
|
||||
"91porn-server/models/commod"
|
||||
"91porn-server/models/s/dailyretentionmod"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type AdStatType int
|
||||
|
||||
const (
|
||||
AdStatTypeUserRetained AdStatType = 1 // 用户留存数
|
||||
AdStatTypeUserRates AdStatType = 2 // 用户留存率
|
||||
AdStatTypeAdClickAcc AdStatType = 3 // 广告点击累计
|
||||
AdStatTypePayTotalAcc AdStatType = 4 // 充值总额累计
|
||||
AdStatTypePayAndAdClickTotalAcc AdStatType = 5 // 充值总额+广告点击
|
||||
)
|
||||
|
||||
type QueryReq struct {
|
||||
StartDate *time.Time `json:"startDate" form:"startDate"` // 开始日期
|
||||
EndDate *time.Time `json:"endDate" form:"endDate"` // 结束日期
|
||||
commod.Page
|
||||
}
|
||||
|
||||
type AdGroupStatReq struct {
|
||||
Type AdStatType `json:"type" form:"type"` // 类型 1-用户留存数 2-用户留存率 3-广告点击累计 4-充值总额累计 5-充值总额+广告点击
|
||||
QueryReq
|
||||
}
|
||||
|
||||
type QueryRes struct {
|
||||
Total int64 `json:"total"` // 总数
|
||||
List []Item `json:"list"` // 列表
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"id"` // ID
|
||||
Date time.Time `json:"date" bson:"date"` // 日期
|
||||
GroupA []string `json:"groupA" bson:"groupA"` // A组数据(0-30天)
|
||||
GroupB []string `json:"groupB" bson:"groupB"` // B组数据(0-30天)
|
||||
GroupC []string `json:"groupC" bson:"groupC"` // C组数据(0-30天)
|
||||
}
|
||||
|
||||
func GetAdGroupStatList(ctx context.Context, req *AdGroupStatReq) (*QueryRes, error) {
|
||||
var err error
|
||||
var res *QueryRes
|
||||
switch req.Type {
|
||||
case AdStatTypeUserRetained:
|
||||
res, err = QueryUserRetained(ctx, &req.QueryReq)
|
||||
case AdStatTypeUserRates:
|
||||
res, err = QueryUserRates(ctx, &req.QueryReq)
|
||||
case AdStatTypeAdClickAcc:
|
||||
res, err = QueryTotalClickAcc(ctx, &req.QueryReq)
|
||||
case AdStatTypePayTotalAcc:
|
||||
res, err = QueryPayTotalAcc(ctx, &req.QueryReq)
|
||||
case AdStatTypePayAndAdClickTotalAcc:
|
||||
res, err = QueryTotalPayAndClickAcc(ctx, &req.QueryReq)
|
||||
default:
|
||||
return nil, errors.New("未知的类型")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//res, _ = GetAdGroupStatListMose(ctx, req) // 使用模拟数据替代真实查询
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type dataExtractor func(item *dailyretentionmod.DailyRetention) []string
|
||||
|
||||
// queryRetention 通用查询函数
|
||||
func queryRetention(ctx context.Context, req *QueryReq, extractor dataExtractor) (*QueryRes, error) {
|
||||
startDate := time.Time{}
|
||||
endDate := time.Now()
|
||||
if req.StartDate != nil {
|
||||
startDate = time.Date(req.StartDate.Year(), req.StartDate.Month(), req.StartDate.Day(), 0, 0, 0, 0, req.StartDate.Location())
|
||||
}
|
||||
if req.EndDate != nil {
|
||||
endDate = time.Date(req.EndDate.Year(), req.EndDate.Month(), req.EndDate.Day(), 0, 0, 0, 0, req.EndDate.Location())
|
||||
}
|
||||
|
||||
filter := bson.M{
|
||||
"date": bson.M{
|
||||
"$gte": startDate,
|
||||
"$lte": endDate,
|
||||
},
|
||||
}
|
||||
|
||||
// 查询总数(按日期去重)
|
||||
total, err := countDistinctDates(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("count failed: %w", err)
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
skip := int64((req.PageNumber - 1) * req.PageSize)
|
||||
limit := int64(req.PageSize)
|
||||
|
||||
// 先查询日期范围内的所有记录
|
||||
opts := options.Find().SetSort(bson.D{{Key: "date", Value: -1}})
|
||||
items, err := dailyretentionmod.QueryAllList(filter, opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("query failed: %w", err)
|
||||
}
|
||||
|
||||
// 按日期分组
|
||||
dateMap := make(map[time.Time]map[commod.AdGroup]*dailyretentionmod.DailyRetention)
|
||||
for _, item := range items {
|
||||
if dateMap[item.Date] == nil {
|
||||
dateMap[item.Date] = make(map[commod.AdGroup]*dailyretentionmod.DailyRetention)
|
||||
}
|
||||
dateMap[item.Date][item.AdGroup] = item
|
||||
}
|
||||
|
||||
// 收集所有日期并排序
|
||||
dates := make([]time.Time, 0, len(dateMap))
|
||||
for date := range dateMap {
|
||||
dates = append(dates, date)
|
||||
}
|
||||
sortDatesDesc(dates)
|
||||
|
||||
// 分页处理
|
||||
start := int(skip)
|
||||
end := start + int(limit)
|
||||
if start > len(dates) {
|
||||
start = len(dates)
|
||||
}
|
||||
if end > len(dates) {
|
||||
end = len(dates)
|
||||
}
|
||||
pagedDates := dates[start:end]
|
||||
|
||||
// 构建结果
|
||||
list := make([]Item, 0, len(pagedDates))
|
||||
for _, date := range pagedDates {
|
||||
groupMap := dateMap[date]
|
||||
item := Item{
|
||||
Date: common.NormalizeDate(date),
|
||||
GroupA: extractGroupData(groupMap[commod.AdGroupA], extractor),
|
||||
GroupB: extractGroupData(groupMap[commod.AdGroupB], extractor),
|
||||
GroupC: extractGroupData(groupMap[commod.AdGroupC], extractor),
|
||||
}
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
return &QueryRes{
|
||||
Total: total,
|
||||
List: list,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// countDistinctDates 统计日期数量
|
||||
func countDistinctDates(ctx context.Context, filter bson.M) (int64, error) {
|
||||
dates, err := dailyretentionmod.Distinct(filter, "date")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int64(len(dates)), nil
|
||||
}
|
||||
|
||||
// sortDatesDesc 日期降序排序
|
||||
func sortDatesDesc(dates []time.Time) {
|
||||
for i := 0; i < len(dates)-1; i++ {
|
||||
for j := i + 1; j < len(dates); j++ {
|
||||
if dates[i].Before(dates[j]) {
|
||||
dates[i], dates[j] = dates[j], dates[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extractGroupData 提取单个广告组的数据
|
||||
func extractGroupData(item *dailyretentionmod.DailyRetention, extractor dataExtractor) []string {
|
||||
if item == nil {
|
||||
// 返回 31 个空字符串
|
||||
result := make([]string, dailyretentionmod.MaxRetentionDays+1)
|
||||
for i := range result {
|
||||
result[i] = ""
|
||||
}
|
||||
return result
|
||||
}
|
||||
return extractor(item)
|
||||
}
|
||||
|
||||
// QueryUserRetained 查询用户留存人数
|
||||
func QueryUserRetained(ctx context.Context, req *QueryReq) (*QueryRes, error) {
|
||||
return queryRetention(ctx, req, func(item *dailyretentionmod.DailyRetention) []string {
|
||||
result := make([]string, dailyretentionmod.MaxRetentionDays+1)
|
||||
for i := 0; i <= dailyretentionmod.MaxRetentionDays; i++ {
|
||||
if item.UserRetained != nil {
|
||||
result[i] = fmt.Sprintf("%d", item.UserRetained[i])
|
||||
} else {
|
||||
result[i] = "0"
|
||||
}
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
// QueryUserRates 查询用户留存率
|
||||
func QueryUserRates(ctx context.Context, req *QueryReq) (*QueryRes, error) {
|
||||
return queryRetention(ctx, req, func(item *dailyretentionmod.DailyRetention) []string {
|
||||
result := make([]string, dailyretentionmod.MaxRetentionDays+1)
|
||||
for i := 0; i <= dailyretentionmod.MaxRetentionDays; i++ {
|
||||
if item.UserRates != nil {
|
||||
result[i] = formatRatePercentage(item.UserRates[i])
|
||||
} else {
|
||||
result[i] = "100%"
|
||||
}
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
// QueryPayTotalAcc 查询充值累计金额
|
||||
func QueryPayTotalAcc(ctx context.Context, req *QueryReq) (*QueryRes, error) {
|
||||
return queryRetention(ctx, req, func(item *dailyretentionmod.DailyRetention) []string {
|
||||
result := make([]string, dailyretentionmod.MaxRetentionDays+1)
|
||||
for i := 0; i <= dailyretentionmod.MaxRetentionDays; i++ {
|
||||
if item.PayTotalAcc != nil {
|
||||
result[i] = fmt.Sprintf("%d", item.PayTotalAcc[i]/100) // 金额以分为单位,转换为元
|
||||
} else {
|
||||
result[i] = "0"
|
||||
}
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
// QueryTotalClickAcc 查询点击累计次数
|
||||
func QueryTotalClickAcc(ctx context.Context, req *QueryReq) (*QueryRes, error) {
|
||||
return queryRetention(ctx, req, func(item *dailyretentionmod.DailyRetention) []string {
|
||||
result := make([]string, dailyretentionmod.MaxRetentionDays+1)
|
||||
for i := 0; i <= dailyretentionmod.MaxRetentionDays; i++ {
|
||||
if item.TotalClickAcc != nil {
|
||||
result[i] = fmt.Sprintf("%d", item.TotalClickAcc[i])
|
||||
} else {
|
||||
result[i] = "0"
|
||||
}
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
// QueryTotalPayAndClickAcc 查询点击累计次数
|
||||
func QueryTotalPayAndClickAcc(ctx context.Context, req *QueryReq) (*QueryRes, error) {
|
||||
return queryRetention(ctx, req, func(item *dailyretentionmod.DailyRetention) []string {
|
||||
result := make([]string, dailyretentionmod.MaxRetentionDays+1)
|
||||
for i := 0; i <= dailyretentionmod.MaxRetentionDays; i++ {
|
||||
if item.TotalClickAcc != nil {
|
||||
result[i] = fmt.Sprintf("%d", item.TotalClickAcc[i]+item.PayTotalAcc[i]/100)
|
||||
} else {
|
||||
result[i] = "0"
|
||||
}
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
func formatRatePercentage(rate float64) string {
|
||||
return fmt.Sprintf("%.2f%%", rate*100)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package advgroupser
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFormatRatePercentage(t *testing.T) {
|
||||
t.Run("formats decimal rate as percentage", func(t *testing.T) {
|
||||
got := formatRatePercentage(0.1234)
|
||||
want := "12.340%"
|
||||
if got != want {
|
||||
t.Fatalf("expected %s, got %s", want, got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("formats zero as zero percentage", func(t *testing.T) {
|
||||
got := formatRatePercentage(0)
|
||||
want := "0.000%"
|
||||
if got != want {
|
||||
t.Fatalf("expected %s, got %s", want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user