Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+270
View File
@@ -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)
}