271 lines
7.9 KiB
Go
271 lines
7.9 KiB
Go
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)
|
|
}
|