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) }