package statminute import ( "context" "fmt" "math/rand" "stat/common/log" "stat/common/statutils" "stat/common/topicmessage" "stat/common/utils" "stat/models/accessmod" "stat/models/channelmod" "stat/models/downloadinfo" "stat/models/rechargegamemod" "stat/models/rechargemod" "stat/models/registermod" "stat/models/syncvip" "stat/models/versionMod" "stat/models/withdrawGameOrdermod" "strings" "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) const ( ChannelStat string = "visitInfo" // 渠道统计 ) func defaultTimeMap(defaultStatDate time.Time) map[string]time.Time { return map[string]time.Time{ ChannelStat: defaultStatDate, // 渠道统计 } } // 获取统计方法 type StatMethod = func(ctx context.Context, start time.Time, end time.Time, appIds []int32) error // 统计方法汇总 var statMethodMap = map[string]StatMethod{ ChannelStat: ChannelSettlementMethod, // 渠道统计 } func canUserDeduction(ruleInfo *channelmod.ChannelDeductionRule, index int, incomeRate float64, isDirect bool) bool { rate := int32(0) // 扣量比例 if ruleInfo.Settlement == channelmod.SettlementCPA || ruleInfo.Settlement == channelmod.SettlementCPS { //自动扣量 if ruleInfo.Settlement == channelmod.SettlementCPA && ruleInfo.AutoDeduction && incomeRate > 0.0 && ruleInfo.LessIncomeRate > incomeRate { rate = 80 } else { if index < 1 { // 一阶 rate = ruleInfo.First } else if index <= 3 { // 二阶 rate = ruleInfo.Second } else if index <= 6 { // 三阶 rate = ruleInfo.Third } else if index <= 10 { // 四阶 rate = ruleInfo.Fourth } else { rate = ruleInfo.Fifth } } if isDirect && int32(rand.Intn(100)) < rate { // rate扣量比例 return true } } return false } func deductionCountMethod(ctx context.Context, ruleInfo *channelmod.ChannelDeductionRule, settlement *channelmod.ChannelSettlement, registerList []*registermod.RegisterMod, incomeRate float64) error { deductionUserIds := make([]int64, 0) // 先计算扣量 for index, item := range registerList { item.IsDeduction = false if canUserDeduction(ruleInfo, index, incomeRate, item.IsDirect) { item.IsDeduction = true deductionUserIds = append(deductionUserIds, int64(item.UserId)) } } // 统计IP ipDict := make(map[string]int64) for _, item := range registerList { if item.IP != "" { ipDict[item.IP]++ } } // 1.统计这段时间内的注册用户 stat := &statutils.Stat{} uniqueStat := &statutils.Stat{} for _, item := range registerList { client := statutils.GetClientInfo(item.SysType, item.DevType, item.DistrictCode, item.IsDirect, item.IsDeduction, item.IsFormal) stat.Add(1, client) if _, exists := ipDict[item.IP]; exists { uniqueStat.Add(1, client) } } settlement.NewTotalIpCount += uniqueStat.Count() settlement.NewTotalCount += stat.Total.Count() settlement.NewDirectCount += stat.Total.Direct.Count() settlement.NewFissionCount += stat.Total.Fission.Count settlement.NewDeductionCount += stat.Total.Deduction.Count settlement.NewLightCount += stat.Light.Count() settlement.NewLightDirectCount += stat.Light.Total.Direct.Count() settlement.NewLightFissionCount += stat.Light.Total.Fission.Count settlement.NewLightDeductionCount += stat.Light.Total.Deduction.Count settlement.NewLightNaturalCount += stat.Light.Total.Direct.Natural.Count settlement.NewLightGuestCount += stat.Light.Guest.Count() settlement.NewLightGuestDirectCount += stat.Light.Guest.Direct.Count() settlement.NewLightGuestFissionCount += stat.Light.Guest.Fission.Count settlement.NewLightGuestDeductionCount += stat.Light.Guest.Deduction.Count settlement.NewLightFormalCount += stat.Light.Formal.Count() settlement.NewLightFormalDirectCount += stat.Light.Formal.Direct.Count() settlement.NewLightFormalFissionCount += stat.Light.Formal.Fission.Count settlement.NewLightFormalDeductionCount += stat.Light.Formal.Deduction.Count settlement.NewAndroidCount += stat.Android.Count() settlement.NewAndroidDirectCount += stat.Android.Direct.Count() settlement.NewAndroidFissionCount += stat.Android.Fission.Count settlement.NewAndroidDeductionCount += stat.Android.Deduction.Count settlement.NewIosCount += stat.Ios.Count() settlement.NewIosDirectCount += stat.Ios.Direct.Count() settlement.NewIosFissionCount += stat.Ios.Fission.Count settlement.NewIosDeductionCount += stat.Ios.Deduction.Count settlement.NewH5Count += stat.H5.Count() settlement.NewH5DirectCount += stat.H5.Total.Direct.Count() settlement.NewH5FissionCount += stat.H5.Total.Fission.Count settlement.NewH5DeductionCount += stat.H5.Total.Deduction.Count settlement.NewH5AndroidCount += stat.H5.Android.Count() settlement.NewH5IosCount += stat.H5.Ios.Count() settlement.NewH5PcCount += stat.H5.Pc.Count() settlement.NewH5FormalCount += stat.H5.Formal.Count() settlement.NewH5FormalDirectCount += stat.H5.Formal.Direct.Count() settlement.NewH5FormalFissionCount += stat.H5.Formal.Fission.Count settlement.NewH5FormalDeductionCount += stat.H5.Formal.Deduction.Count // 5 更新扣量用户 if len(deductionUserIds) > 0 { setdoc := bson.M{"$set": bson.M{"isDeduction": true}} filter := bson.M{ "appId": ruleInfo.AppId, "userId": bson.M{"$in": deductionUserIds}, } err := registermod.UpdateManyRegister(ctx, filter, setdoc) if err != nil { log.Error("NewCountMethod UpdateManyRegister ", log.E(err)) return err } } return nil } func channelChannelStatMethod(ctx context.Context, ruleInfo *channelmod.ChannelDeductionRule, settlement *channelmod.ChannelSettlement, visitList accessmod.VisitInfoList, start, end time.Time, appid int32) error { stat := &statutils.Stat{} for _, item := range visitList { client := statutils.GetClientInfo(item.SysType, item.DevType, item.DistrictCode, item.IsDirect, false, item.IsFormalUser) stat.Add(1, client) } settlement.TotalVisitCount = stat.Count() settlement.DirectVisitCount = stat.Total.Direct.Count() settlement.FissionVisitCount = stat.Total.Fission.Count settlement.AndroidVisitCount = stat.Android.Count() settlement.IosVisitCount = stat.Ios.Count() settlement.LightVisitCount = stat.Light.Count() settlement.LightVisitDirectCount = stat.Light.Total.Direct.Count() settlement.LightVisitFissionCount = stat.Light.Total.Fission.Count settlement.LightVisitDeductionCount = stat.Light.Total.Deduction.Count settlement.LightVisitNaturalCount = stat.Light.Total.Direct.Natural.Count settlement.H5VisitCount = stat.H5.Count() settlement.H5AndroidVisitCount = stat.H5.Android.Count() settlement.H5IosVisitCount = stat.H5.Ios.Count() settlement.H5PcVisitCount = stat.H5.Pc.Count() return nil } func channelVipMethod(ctx context.Context, ruleInfo *channelmod.ChannelDeductionRule, settlement *channelmod.ChannelSettlement, VipList syncvip.SyncVipList, start, end time.Time, appid int32) error { deductionVipOrderIds := make([]primitive.ObjectID, 0) for _, buyInfo := range VipList { // 统计渠道信息 if buyInfo.IsDirect && buyInfo.BuyVIPType == topicmessage.BuyVIPTypeMoney { if ruleInfo.Settlement == channelmod.SettlementCPS { if ruleInfo.TodayTime != utils.BeginWithTime(start) { ruleInfo.TodayDeduction = 0 ruleInfo.TodayTime = utils.BeginWithTime(start) } ruleInfo.TodayDeduction += buyInfo.Money if ruleInfo.CPSBegin < ruleInfo.TodayDeduction { if int32(rand.Intn(100)) < int32(ruleInfo.CPSDeductionRate) { deductionVipOrderIds = append(deductionVipOrderIds, buyInfo.ID) settlement.VipPayDeduction += buyInfo.Money } } } } // 统计信息 if buyInfo.IsDirect { if buyInfo.BuyVIPType == topicmessage.BuyVIPTypeMoney { settlement.VipPayMoney += buyInfo.Money } else { settlement.VipPayCoin += buyInfo.Amount } } } if len(deductionVipOrderIds) > 0 { settlement.VipPayMoney = settlement.VipPayMoney - settlement.VipPayDeduction setdoc := bson.M{"$set": bson.M{syncvip.FN.SyncVip.IsDeduction: true}} filter := bson.M{ syncvip.FN.SyncVip.ID: bson.M{"$in": deductionVipOrderIds}, syncvip.FN.SyncVip.AppId: ruleInfo.AppId, } err := syncvip.UpdateOneSyncVip(ctx, filter, setdoc) if err != nil { log.Error("channelRechargeCountMethod UpdateManyRecharge ", log.E(err)) return err } } return nil } func channelRechargeCountMethod(ctx context.Context, ruleInfo *channelmod.ChannelDeductionRule, settlement *channelmod.ChannelSettlement, rechargeList rechargemod.RechargeInfoList, start, end time.Time, appid int32) error { stat := &statutils.Stat{} statNew := statutils.Stat{} for _, item := range rechargeList { client := statutils.GetClientInfo(item.SysType, item.DevType, item.DistrictCode, item.IsDirect, false, item.IsFormalUser) stat.Add(item.Money, client) if item.RegisterTime.After(item.SuccessAt.AddDate(0, 0, -1)) { statNew.Add(item.Money, client) } } settlement.TotalAmount = stat.Value() settlement.DirectAmount = stat.Total.Direct.Value() settlement.DirectAmountCount = stat.Total.Direct.Count() settlement.FissionAmount = stat.Total.Fission.Value settlement.FissionAmountCount = stat.Total.Fission.Count settlement.NaturalAmount = stat.Total.Direct.Natural.Value settlement.NaturalAmountCount = stat.Total.Direct.Natural.Count settlement.DeductionAmount = stat.Total.Deduction.Value settlement.DeductionAmountCount = stat.Total.Deduction.Count settlement.AndroidAmount = stat.Android.Value() settlement.AndroidAmountCount = stat.Android.Count() settlement.IosAmount = stat.Ios.Value() settlement.IosAmountCount = stat.Ios.Count() settlement.H5Amount = stat.H5.Value() settlement.H5AmountCount = stat.H5.Count() settlement.LightAmount = stat.Light.Value() settlement.LightAmountCount = stat.Light.Count() settlement.LightDirectAmount = stat.Light.Total.Direct.Value() settlement.LightDirectAmountCount = stat.Light.Total.Direct.Count() settlement.LightFissionAmount = stat.Light.Total.Fission.Value settlement.LightFissionAmountCount = stat.Light.Total.Fission.Count settlement.LightDeductionAmount = stat.Light.Total.Deduction.Value settlement.LightDeductionAmountCount = stat.Light.Total.Deduction.Count settlement.LightNaturalAmount = stat.Light.Total.Direct.Natural.Value settlement.LightNaturalAmountCount = stat.Light.Total.Direct.Natural.Count settlement.NewRechargeAmount = statNew.Value() return nil } // GameRechargeMethod 游戏充值 func channelGameRechargeMethod(ctx context.Context, ruleInfo *channelmod.ChannelDeductionRule, rechageList rechargegamemod.GameRechargeInfoList, withdrawList []withdrawGameOrdermod.GameWithdrawOrder, settlement *channelmod.ChannelSettlement, start, end time.Time, appid int32) error { for _, withdrawInfo := range withdrawList { if ruleInfo.Channel == withdrawInfo.DistrictCode { settlement.GameWithdrawAmount += withdrawInfo.Money } } for _, rechageInfo := range rechageList { if ruleInfo.Channel == rechageInfo.DistrictCode { settlement.GameRechageAmount += rechageInfo.Money } } return nil } // channelLandPageMethod 落地页访问 func channelLandPageMethod(ctx context.Context, settlement *channelmod.ChannelSettlement, start, end time.Time, appid int32) error { itemList, err := downloadinfo.GetChannelDownloadPageListByTime(ctx, settlement.Channel, appid, start, end) if err != nil { return err } androidClick := int64(0) appstoreClick := int64(0) iosAtherClick := int64(0) lightClick := int64(0) for _, downloadInfo := range itemList { if downloadInfo.SysType == "android" { androidClick += 1 } else if strings.Contains(downloadInfo.SysType, "ios") || strings.Contains(downloadInfo.SysType, "light") { if downloadInfo.PackageType == versionMod.Shop { appstoreClick += 1 } else if downloadInfo.PackageType == versionMod.Enterprise { iosAtherClick += 1 } else if downloadInfo.PackageType == versionMod.Light { lightClick += 1 } } } settlement.LandPageVisitCount = int64(len(itemList)) settlement.LandpageAndroidClick = androidClick settlement.LandpageIosAppstoreClick = appstoreClick settlement.LandpageIosAthorClick = iosAtherClick settlement.LandpageIosH5Click = lightClick return nil } // ChannelSettlementMethod 渠道计算 func ChannelSettlementMethod(ctx context.Context, start time.Time, end time.Time, appIds []int32) error { for _, appid := range appIds { channelDeductionRules, err := channelmod.FindManyChannelDeductionRule(ctx, bson.M{channelmod.FN.ChannelDeductionRule.AppId: appid}) if err != nil { log.Error("ChannelSettlementMethod FindManyChannelDeductionRule", log.E(err)) return err } channnels := make([]string, 0) for _, channnelInfo := range channelDeductionRules { channnels = append(channnels, channnelInfo.Channel) } channelInfolist, err := channelmod.FindManyChannelInfo(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: bson.M{"$in": channnels}}) if err != nil { log.Error("ChannelSettlementMethod FindManyChannelInfo", log.E(err)) return err } registerList, err := registermod.RegisterUsersInfoByTime(ctx, appid, start, end) if err != nil { log.Error("NewCountMethod RegisterUsersInfoByDistrict ", log.E(err)) return err } registerFormalList, err := registermod.RegisterFormalsInfoByTime(ctx, appid, start, end) if err != nil { log.Error("NewCountMethod RegisterUsersInfoByDistrict ", log.E(err)) return err } vipfilter := bson.M{ syncvip.FN.SyncVip.AppId: appid, syncvip.FN.SyncVip.SuccessAt: bson.M{"$gte": start, "$lt": end}, } VipList, err := syncvip.FindManySyncVip(ctx, vipfilter) if err != nil { log.Error("ChannelChannelStatMethod FindManyVisitInfo ", log.E(err)) return err } visitFilter := bson.M{"appId": appid, "visitAt": bson.M{"$gte": start, "$lt": end}} visitList, _ := accessmod.FindManyVisitInfo(ctx, visitFilter) rechargeFilter := bson.M{"appId": appid, "successAt": bson.M{"$gte": start, "$lt": end}} rechargeList, _ := rechargemod.FindManyRechargeInfo(ctx, rechargeFilter) for _, ruleInfo := range channelDeductionRules { if ruleInfo.CreatedAt.After(start) { continue } //fmt.Println("channelDeductionRules", ruleInfo.Channel) settlement := &channelmod.ChannelSettlement{ SumDate: start, AppId: appid, AppType: ruleInfo.AppType, PromoteMode: ruleInfo.PromoteMode, Channel: ruleInfo.Channel, } for _, channelInfo := range channelInfolist { if channelInfo.Channel == ruleInfo.Channel { settlement.Type = channelInfo.Type settlement.ParentChannel = channelInfo.ParentChannel settlement.UserName = channelInfo.UserName } } if ruleInfo.SettlementTime == start { // 已经结算 continue } statFilter := bson.M{ channelmod.FN.ChannelStat.Channel: ruleInfo.Channel, channelmod.FN.ChannelStat.SumDate: utils.BeginWithTime(start), channelmod.FN.ChannelStat.AppId: appid, } channelStatInfo, err := channelmod.FindOneChannelStat(ctx, statFilter) if err != nil { log.Error("ChannelSettlementMethod CountChannelStats", log.E(err)) return err } // 统计新增情况 userDict := make(map[uint64]bool) channelRegisterList := make([]*registermod.RegisterMod, 0) for _, v := range registerFormalList { if v.IsFormal && statutils.IsClientH5OrLight(v.SysType, v.DevType) { if v.DistrictCode == ruleInfo.Channel { userDict[v.UserId] = true channelRegisterList = append(channelRegisterList, v) } } } for _, v := range registerList { if v.DistrictCode == ruleInfo.Channel { if _, exists := userDict[v.UserId]; !exists { userDict[v.UserId] = true channelRegisterList = append(channelRegisterList, v) } } } oldIncomeRate := float64(0) if channelStatInfo != nil { oldIncomeRate = channelStatInfo.IncomeRate } deductionCountMethod(ctx, ruleInfo, settlement, channelRegisterList, oldIncomeRate) // 统计访问量 channelAccessList := make(accessmod.VisitInfoList, 0) for _, v := range visitList { if v.DistrictCode == ruleInfo.Channel { channelAccessList = append(channelAccessList, v) } } channelChannelStatMethod(ctx, ruleInfo, settlement, channelAccessList, start, end, appid) // 统计充值数据 channelRechargeList := make(rechargemod.RechargeInfoList, 0) for _, v := range rechargeList { if v.DistrictCode == ruleInfo.Channel { channelRechargeList = append(channelRechargeList, v) } } channelRechargeCountMethod(ctx, ruleInfo, settlement, channelRechargeList, start, end, appid) // 统计VIP购买 channelVipList := make(syncvip.SyncVipList, 0) for _, v := range VipList { if v.DistrictCode == ruleInfo.Channel { channelVipList = append(channelVipList, v) } } channelVipMethod(ctx, ruleInfo, settlement, channelVipList, start, end, appid) channelLandPageMethod(ctx, settlement, start, end, appid) // 更新渠道信息 ruleFilter := bson.M{channelmod.FN.ChannelDeductionRule.ID: ruleInfo.ID} ruleSet := bson.M{"$set": bson.M{ channelmod.FN.ChannelDeductionRule.SettlementTime: start, channelmod.FN.ChannelDeductionRule.TodayTime: ruleInfo.TodayTime, channelmod.FN.ChannelDeductionRule.TodayDeduction: ruleInfo.TodayDeduction, }} err = channelmod.UpdateManyChannelDeductionRule(ctx, ruleFilter, ruleSet) if err != nil { log.Error("ChannelSettlementMethod UpdateManyChannelDeductionRule", log.E(err)) return err } settlement.CorrectValidData() //插入数据 err = channelmod.InsertOneChannelSettlement(ctx, settlement) if err != nil { log.Error("ChannelSettlementMethod InsertOneChannelSettlement", log.E(err)) return err } if channelStatInfo.Channel != ruleInfo.Channel { channelStatInfo.Channel = ruleInfo.Channel channelStatInfo.SumDate = utils.BeginWithTime(start) channelStatInfo.AppId = appid channelStatInfo.Type = settlement.Type channelStatInfo.UserName = settlement.UserName channelStatInfo.Settlement = ruleInfo.Settlement channelStatInfo.CPAPrice = ruleInfo.CPAPrice channelStatInfo.CPSRate = ruleInfo.CPSRate channelStatInfo.AppType = ruleInfo.AppType channelStatInfo.PromoteMode = ruleInfo.PromoteMode channelStatInfo.ParentChannel = settlement.ParentChannel channelStatInfo.IsPay = false // 数据赋值 settlement.AssignmentTo(channelStatInfo) // 插入记录 err = channelmod.InsertOneChannelStat(ctx, channelStatInfo) if err != nil { log.Error("ChannelSettlementMethod InsertOneChannelStat", log.E(err)) return err } } else { // 数据累加 settlement.AccumulateTo(channelStatInfo) // 更新记录 setdoc := bson.M{"$set": channelStatInfo} err = channelmod.UpdateOneChannelStat(ctx, statFilter, setdoc) if err != nil { log.Error("ChannelSettlementMethod UpdateOneChannelStat", log.E(err)) return err } } } } return nil } // 更新渠道数据 func ChannelSettlementUpdateByHour() error { ctx := context.Background() nowHour := utils.BeginOfHour() statFilter := bson.M{ channelmod.FN.ChannelStat.SumDate: utils.BeginWithTime(nowHour.Add(-30 * time.Minute)), // 确保:0点是获取上一天数据 "$or": bson.A{ bson.M{channelmod.FN.ChannelStat.NewDirectCount: bson.M{"$gt": 0}}, bson.M{channelmod.FN.ChannelStat.DirectAmount: bson.M{"$gt": 0}}, }, } statList, err := channelmod.FindManyChannelStat(ctx, statFilter) if err != nil { log.Error("ChannelSettlementUpdateByHour FindManyChannelStat", log.E(err)) return err } yesterdayFilter := bson.M{ channelmod.FN.ChannelStatYestoday.SumDate: nowHour.AddDate(0, 0, -1), } statYesterdayList, _ := channelmod.FindManyChannelStatYestoday(ctx, yesterdayFilter) for _, channelStatInfo := range statList { channelStatInfoYesterday := &channelmod.ChannelStatYesterday{ // base Channel: channelStatInfo.Channel, SumDate: nowHour, AppId: channelStatInfo.AppId, ParentChannel: channelStatInfo.ParentChannel, Settlement: channelStatInfo.Settlement, UpdatedAt: time.Now(), CreatedAt: time.Now(), // new NewCount: channelStatInfo.NewTotalCount, NewDirectCount: channelStatInfo.NewDirectCount, NewFissionCount: channelStatInfo.NewFissionCount, NewAndroidCount: channelStatInfo.NewAndroidCount, NewIosCount: channelStatInfo.NewIosCount, NewH5Count: channelStatInfo.NewH5Count, NewH5FormalCount: channelStatInfo.NewH5FormalCount, NewLightGuestCount: channelStatInfo.NewLightGuestCount, NewLightFormalCount: channelStatInfo.NewLightFormalCount, // recharge RechargeAmount: channelStatInfo.TotalAmount, DirectAmount: channelStatInfo.DirectAmount, FissionAmount: channelStatInfo.FissionAmount, AndroidRechargeAmount: channelStatInfo.AndroidAmount, IosRechargeAmount: channelStatInfo.IosAmount, LightRechargeAmount: channelStatInfo.LightAmount, H5RechargeAmount: channelStatInfo.H5Amount, // other VipPayCoin: channelStatInfo.VipPayCoin, VipPayMoney: channelStatInfo.VipPayMoney, VipPayDeduction: channelStatInfo.VipPayDeduction, } for _, yesterdayStatInfo := range statYesterdayList { // 获取昨天数据 if yesterdayStatInfo.Channel == channelStatInfo.Channel && yesterdayStatInfo.AppId == channelStatInfo.AppId { newYesterday := yesterdayStatInfo.NewCount newToday := channelStatInfo.NewTotalCount channelStatInfoYesterday.NewIncreaseCount = newToday - newYesterday if newYesterday != 0 { channelStatInfoYesterday.NewIncreaseRate = fmt.Sprintf("%.2f", float64(newToday-newYesterday)*100/float64(newYesterday)) + "%" } rechargeYesterday := yesterdayStatInfo.RechargeAmount rechargeToday := channelStatInfo.TotalAmount channelStatInfoYesterday.RechargeIncreaseAmount = rechargeToday - rechargeYesterday if rechargeYesterday != 0 { channelStatInfoYesterday.RechargeIncreaseRate = fmt.Sprintf("%.2f", float64(rechargeToday-rechargeYesterday)*100/float64(rechargeYesterday)) + "%" } } } err := channelmod.InsertOneChannelStatYestoday(ctx, channelStatInfoYesterday) if err != nil { log.Error("ChannelSettlementMethod InsertOneChannelStatYestoday", log.E(err)) return err } } return nil }