Files
stat_center/service/channelSer/stat.go
T
rootandClaude Opus 5 4cdca06289 修复子渠道汇总到父渠道时未区分 appId 导致数据多加
ChannelStat 按 (channel, sumDate, appId) 三维存储,但子渠道累加到父渠道的
匹配条件只比了 ParentChannel 和 SumDate,漏了 AppId。

当父渠道在多个 app 下都有记录时(如 appId=[2,3,4] 查同一渠道码),一个子渠道
的数据会被同时累加进该 (channel, sumDate) 下所有 app 的父渠道行,选几个 app
就多加几倍。settlement 的三处子查询本身也没有 appId 约束,父列表虽锁定单个
appId,子列表却跨 app,同样会把其他 app 的子渠道数据算进结算金额。

5 处匹配条件统一补上 child.AppId == parent.AppId:
- stat.go:162          SearchChannelStat         (列表接口,多值 appId)
- settlement.go:200    ChannelSettlementCalc     (结算计算,影响金额)
- settlement.go:327    SearchChannelSettlementStat2 (原本父子共用同一 filter
                       而侥幸正确,补上以免后续改动破坏该前提)
- settlement.go:857    AddSettlementInfo2        (影响金额)
- settlement.go:1138   DetailSettlementInfo

本次只改匹配条件。settlement 三处 childFilter 缺少 appId 约束的冗余查询,
以及 SearchChannelStat 在无 start/end 时 resp.Total 硬编码为 1000000、
withChild 累加被 canSum 跳过的问题,均未处理。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-02 09:38:03 +08:00

322 lines
9.6 KiB
Go

package channelSer
import (
"context"
"errors"
"runtime"
"stat/api/proto"
"stat/common/utils"
"stat/models/channelmod"
"time"
"github.com/jinzhu/now"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// SearchChannelStat 查询渠道扣量规则信息
func SearchChannelStat(ctx context.Context, req *proto.ChannelStatSearch) (*proto.ChannelStatSearchResp, error) {
resp := &proto.ChannelStatSearchResp{
List: make(channelmod.ChannelStatList, 0),
}
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
if req.Channel != nil && *req.Channel != "" {
if req.WithChild != nil && *req.WithChild {
filter = bson.M{
"$or": bson.A{
bson.M{channelmod.FN.ChannelStat.Channel: *req.Channel},
bson.M{channelmod.FN.ChannelStat.ParentChannel: *req.Channel},
},
}
} else {
filter[channelmod.FN.ChannelStat.Channel] = *req.Channel
}
} else if req.DayType != nil {
timeNow := time.Now()
now.WeekStartDay = time.Monday
Now := now.With(timeNow.In(time.Local))
todayBegin := utils.BeginWithTime(timeNow)
weekBegin := Now.BeginningOfWeek()
monthBegin := Now.BeginningOfMonth()
var timeBegin time.Time
if *req.DayType == 1 { // 今天内
timeBegin = todayBegin
} else if *req.DayType == 2 { // 昨天和今天
timeBegin = todayBegin.AddDate(0, 0, -1)
} else if *req.DayType == 3 { // 三天内
timeBegin = todayBegin.AddDate(0, 0, -2)
} else if *req.DayType == 4 { // 七天内
timeBegin = todayBegin.AddDate(0, 0, -6)
} else if *req.DayType == 5 { // 本周内
timeBegin = weekBegin
} else if *req.DayType == 6 { // 两周内
timeBegin = weekBegin.AddDate(0, 0, -7)
} else if *req.DayType == 7 { // 本月内
timeBegin = monthBegin
} else if *req.DayType == 8 { // 两月内
timeBegin = monthBegin.AddDate(0, -1, 0)
}
channelList, err := channelmod.FindManyChannelInfo(ctx, bson.M{
channelmod.FN.ChannelInfo.CreatedAt: bson.M{
"$gte": timeBegin,
"$lte": timeNow,
},
})
if err != nil {
return resp, err
}
channelIds := make([]string, 0)
for _, channelInfo := range channelList {
channelIds = append(channelIds, channelInfo.Channel)
}
if len(channelIds) > 0 {
if req.WithChild != nil && *req.WithChild {
filter = bson.M{
"$or": bson.A{
bson.M{channelmod.FN.ChannelStat.Channel: bson.M{"$in": channelIds}},
bson.M{channelmod.FN.ChannelStat.ParentChannel: bson.M{"$in": channelIds}},
},
}
} else {
filter[channelmod.FN.ChannelStat.Channel] = bson.M{"$in": channelIds}
}
} else {
return resp, nil
}
}
//
//排序
if req.RankType != nil {
if *req.RankType == 1 {
opt.SetSort(bson.D{{Key: channelmod.FN.ChannelStat.SumDate, Value: -1}, {Key: channelmod.FN.ChannelStat.DirectAmount, Value: -1}})
} else if *req.RankType == 2 {
opt.SetSort(bson.D{{Key: channelmod.FN.ChannelStat.SumDate, Value: -1}, {Key: channelmod.FN.ChannelStat.NewDirectCount, Value: -1}})
} else if *req.RankType == 3 {
opt.SetSort(bson.D{{Key: channelmod.FN.ChannelStat.SumDate, Value: -1}, {Key: channelmod.FN.ChannelStat.NewRechargeAmount, Value: -1}})
} else if *req.RankType == 4 {
opt.SetSort(bson.D{{Key: channelmod.FN.ChannelStat.SumDate, Value: -1}, {Key: channelmod.FN.ChannelStat.IncomeRate, Value: -1}})
}
} else {
opt.SetSort(bson.D{{Key: channelmod.FN.ChannelStat.SumDate, Value: -1}, {Key: channelmod.FN.ChannelStat.NewDirectCount, Value: -1}})
}
if req.UserName != nil && *req.UserName != "" {
filter[channelmod.FN.ChannelStat.UserName] = bson.M{"$regex": *req.UserName}
}
if req.AppId != nil {
filter[channelmod.FN.ChannelStat.AppId] = bson.M{"$in": *req.AppId}
}
if req.Start != nil && req.End != nil {
filter[channelmod.FN.ChannelStat.SumDate] = bson.M{"$gte": *req.Start, "$lte": *req.End}
}
if req.WithChild != nil && !*req.WithChild {
filter[channelmod.FN.ChannelStat.Type] = channelmod.ChannelTypeParent
}
if req.Settlement != nil {
filter[channelmod.FN.ChannelStat.Settlement] = *req.Settlement
}
if req.Detail != nil && *req.Detail {
if req.SumDate == nil {
return resp, errors.New("param error")
}
filter = bson.M{
"$or": bson.A{
bson.M{channelmod.FN.ChannelStat.Channel: *req.Channel},
bson.M{channelmod.FN.ChannelStat.ParentChannel: *req.Channel},
},
channelmod.FN.ChannelStat.SumDate: *req.SumDate,
}
}
ChannelStatList, err := channelmod.FindManyChannelStat(ctx, filter, opt)
if err != nil {
return resp, err
}
for _, v := range ChannelStatList {
v.CorrectValidData()
v.CalcIncomeRate()
}
resp.List = ChannelStatList
// 汇总
canSum := utils.M(filter).HasKey(channelmod.FN.ChannelStat.AppId) && utils.M(filter).HasKey(channelmod.FN.ChannelStat.SumDate)
if canSum {
var total int64
err = channelmod.FindManyChannelStatShard(ctx, filter, 10000, func(item *channelmod.ChannelStat) {
total++
channelmod.ChannelStatSummation(&resp.TotalInfo, item)
if req.WithChild != nil && *req.WithChild {
for _, v := range ChannelStatList {
if item.ParentChannel == v.Channel && item.AppId == v.AppId && v.SumDate.Equal(item.SumDate) {
channelmod.ChannelStatSummation(v, item)
}
}
}
})
if err != nil {
return resp, err
}
if total == 0 {
return resp, nil
}
if total > 10000 {
runtime.GC()
}
resp.Total = total
var ruleInfo *channelmod.ChannelDeductionRule
if req.Channel != nil && *req.Channel != "" {
ruleFilter := bson.M{
channelmod.FN.ChannelDeductionRule.Channel: req.Channel,
// channelmod.FN.ChannelDeductionRule.AppId: req.AppId,
}
ruleInfo, _ = channelmod.FindOneChannelDeductionRule(ctx, ruleFilter)
}
if ruleInfo != nil {
resp.TotalInfo.Channel = ruleInfo.Channel
resp.TotalInfo.Settlement = ruleInfo.Settlement
resp.TotalInfo.AppId = ruleInfo.AppId
resp.TotalInfo.CPAPrice = ruleInfo.CPAPrice
resp.TotalInfo.CPSRate = ruleInfo.CPSRate
resp.TotalInfo.AppType = ruleInfo.AppType
resp.TotalInfo.PromoteMode = ruleInfo.PromoteMode
resp.TotalInfo.CalcIncomeRate()
resp.Income, resp.Desc = channelmod.CalculateChannelIncome(&resp.TotalInfo)
}
} else {
resp.Total = 1000000
}
return resp, nil
}
// SearchChannelSettlement 查询渠道结算信息
func SearchChannelSettlement(ctx context.Context, req *proto.ChannelSettlementSearch) (
*proto.ChannelSettlementSearchResp, error) {
resp := &proto.ChannelSettlementSearchResp{}
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSort(bson.M{channelmod.FN.ChannelSettlement.CreatedAt: -1})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
if req.Channel != nil && *req.Channel != "" {
filter[channelmod.FN.ChannelSettlement.Channel] = *req.Channel
}
if req.AppId != nil {
filter[channelmod.FN.ChannelSettlement.AppId] = *req.AppId
}
if req.Start != nil && req.End != nil {
filter[channelmod.FN.ChannelSettlement.SumDate] = bson.M{"$gt": *req.Start, "$lt": *req.End}
}
if req.WithChild != nil && *req.WithChild {
filter[channelmod.FN.ChannelSettlement.Type] = channelmod.ChannelTypeParent
}
total, serr := channelmod.CountChannelSettlements(ctx, filter)
if serr != nil {
return nil, serr
}
if total == 0 {
return resp, nil
}
ChannelSettlementList, err := channelmod.FindManyChannelSettlement(ctx, filter, opt)
if serr != nil {
return nil, err
}
if req.WithChild != nil && *req.WithChild {
channels := make([]string, 0)
for _, v := range ChannelSettlementList {
channels = append(channels, v.Channel)
}
childTypes := channelmod.GetChildChannelTypes()
filter[channelmod.FN.ChannelSettlement.Type] = bson.M{"$in": childTypes}
filter[channelmod.FN.ChannelSettlement.ParentChannel] = bson.M{"$in": channels}
channelsettlementList, err := channelmod.FindManyChannelStat(ctx, filter)
if err != nil {
return nil, err
}
for _, channnelSettlementInfo := range channelsettlementList {
for _, v := range ChannelSettlementList {
if channnelSettlementInfo.ParentChannel == v.Channel {
channnelSettlementInfo.AccumulateTo(v)
}
}
}
}
resp.Total = total
resp.List = ChannelSettlementList
return resp, nil
}
// SearchChannelStatCompared 渠道同时段对比
func SearchChannelStatCompared(ctx context.Context, req *proto.SearchChannelStatComparedReq) (*proto.SearchChannelStatComparedResp, error) {
resp := &proto.SearchChannelStatComparedResp{}
filter := bson.M{}
opt := &options.FindOptions{}
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
opt.SetSort(
bson.D{
{Key: channelmod.FN.ChannelStatYestoday.SumDate, Value: -1},
{Key: channelmod.FN.ChannelStatYestoday.NewCount, Value: -1},
},
)
if req.Channel != nil && *req.Channel != "" {
if req.WithChild != nil && *req.WithChild {
filter = bson.M{
"$or": bson.A{
bson.M{channelmod.FN.ChannelStatYestoday.Channel: *req.Channel},
bson.M{channelmod.FN.ChannelStatYestoday.ParentChannel: *req.Channel},
},
}
} else {
filter[channelmod.FN.ChannelStatYestoday.Channel] = *req.Channel
}
}
if req.AppId != nil {
filter[channelmod.FN.ChannelStatYestoday.AppId] = *req.AppId
}
if req.Start != nil && req.End != nil {
filter[channelmod.FN.ChannelStatYestoday.SumDate] = bson.M{"$gte": *req.Start, "$lte": *req.End}
}
if req.Settlement != nil {
filter[channelmod.FN.ChannelStatYestoday.Settlement] = *req.Settlement
}
total, err := channelmod.CountChannelStatYestoday(ctx, filter)
if err != nil {
return nil, err
}
channelStatList, err := channelmod.FindManyChannelStatYestoday(ctx, filter, opt)
if err != nil {
return nil, err
}
resp.Total = total
resp.List = channelStatList
return resp, nil
}