AccumulateChildNodes 把子代理数据累加到上级代理时,匹配条件只比了 SumDate, 没有比 AppId。上级节点的 Stats 在同一 SumDate 下每个 app 各有一条,子代理 某个 app 的数据会被累加进上级的全部 app 条目,且命中后不 break,会继续累加 进后续所有匹配项。 触发条件: GetAllAgentStatDict 对 appId 是可选过滤,而 AgentChannelStatSearch 的 AppId 为 *int32 且无 binding:required。请求不传 appId 时 statDict 跨 全部 app,此时必然多加。影响 /api/stat/agent/channel/stat,是代理商侧看到的 数据。 同函数下方的 lostStat 分支本就带 AppId: childStat.AppId,说明 appId 本是 预期维度,匹配处属遗漏。 本次只加 appId 比较,未加 break。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
249 lines
8.3 KiB
Go
249 lines
8.3 KiB
Go
package agentSer
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"stat/api/proto"
|
|
"stat/models/channelmod"
|
|
"time"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
)
|
|
|
|
type AgentNode struct {
|
|
Ctx context.Context
|
|
ChannelInfo *channelmod.ChannelInfo
|
|
Stats ChannelAgentStatList
|
|
Parent *AgentNode
|
|
Children []*AgentNode
|
|
Accumulate bool //是否已累计子层
|
|
AddedChild bool //是否已添加子层
|
|
Deducted bool //是否已计算扣量
|
|
}
|
|
|
|
func NewAgentNode(ctx context.Context, channel *channelmod.ChannelInfo, parent *AgentNode, stats ...*AgentChannelStat) *AgentNode {
|
|
ret := &AgentNode{
|
|
Ctx: ctx,
|
|
ChannelInfo: channel,
|
|
Stats: make([]*AgentChannelStat, 0),
|
|
Parent: parent,
|
|
Children: make([]*AgentNode, 0),
|
|
Accumulate: false,
|
|
}
|
|
ret.Stats = append(ret.Stats, stats...)
|
|
return ret
|
|
}
|
|
|
|
func GetRootAgentNode(ctx context.Context, selfChannel *channelmod.ChannelInfo, reqChannel *string, statDict SourceAgentStatMap) (*AgentNode, error) {
|
|
var rootAgent *AgentNode
|
|
if reqChannel != nil && *reqChannel != selfChannel.Channel {
|
|
cInfo, err := channelmod.FindOneChannelInfoEx(ctx, bson.M{channelmod.FN.ChannelInfo.Channel: *reqChannel})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cInfo.ParentAgent != selfChannel.Channel {
|
|
return nil, fmt.Errorf("查找渠道失败,只能查看自己或自己的直接下级!")
|
|
}
|
|
stats := statDict[cInfo.Type][cInfo.Channel]
|
|
rootAgent = NewAgentNode(ctx, cInfo, nil, stats...)
|
|
} else {
|
|
stats := statDict[selfChannel.Type][selfChannel.Channel]
|
|
rootAgent = NewAgentNode(ctx, selfChannel, nil, stats...)
|
|
}
|
|
return rootAgent, nil
|
|
}
|
|
|
|
func nodeFind(node *AgentNode, find func(node *AgentNode) bool) *AgentNode {
|
|
var stack = []*AgentNode{node}
|
|
for len(stack) > 0 {
|
|
node = stack[len(stack)-1]
|
|
stack = stack[:len(stack)-1]
|
|
if node != nil {
|
|
found := find(node)
|
|
if found {
|
|
return node
|
|
}
|
|
stack = append(stack, node.Children...)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func traverse(node *AgentNode, accumulate func(node *AgentNode)) {
|
|
// first child
|
|
for _, child := range node.Children {
|
|
traverse(child, accumulate)
|
|
}
|
|
// last self
|
|
accumulate(node)
|
|
}
|
|
|
|
func (s *AgentNode) AddChildNodes(srcDict SourceAgentStatMap, channelMap ChannelMap) error {
|
|
findNotAddChildNodeFn := func(node *AgentNode) bool {
|
|
if !node.AddedChild {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
for found := nodeFind(s, findNotAddChildNodeFn); found != nil; found = nodeFind(s, findNotAddChildNodeFn) {
|
|
childChannels := channelMap.GetChildChannels(found.ChannelInfo.Channel)
|
|
for _, channel := range childChannels {
|
|
stats := srcDict[channel.Type][channel.Channel]
|
|
agent := NewAgentNode(s.Ctx, channel, found, stats...)
|
|
found.Children = append(found.Children, agent)
|
|
}
|
|
found.AddedChild = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *AgentNode) AccumulateChildNodes(ruleMap RuleMap) error {
|
|
accumulate := func(found *AgentNode) {
|
|
if found.Accumulate {
|
|
panic(errors.New("AccumulateChildNodes found node can not be accumulate"))
|
|
}
|
|
for _, childAgent := range found.Children {
|
|
if !childAgent.Accumulate {
|
|
panic(errors.New("AccumulateChildNodes child node can not be not accumulated"))
|
|
}
|
|
for _, childStat := range childAgent.Stats {
|
|
hit := false
|
|
for _, parentStat := range found.Stats {
|
|
if parentStat.AppId == childStat.AppId && parentStat.SumDate.Equal(childStat.SumDate) {
|
|
hit = true
|
|
AccumulateChildAgentChannelStatToParent(parentStat, childStat)
|
|
}
|
|
}
|
|
if !hit {
|
|
lostStat := &AgentChannelStat{
|
|
ChannelStat: channelmod.ChannelStat{
|
|
Channel: childAgent.ChannelInfo.ParentAgent,
|
|
SumDate: childStat.SumDate,
|
|
AppId: childStat.AppId,
|
|
Type: childStat.Type - 1,
|
|
Settlement: childStat.Settlement,
|
|
UserName: childStat.UserName,
|
|
ParentChannel: childStat.ParentChannel,
|
|
},
|
|
}
|
|
if rule := ruleMap.GetRule(childAgent.ChannelInfo.ParentAgent, childStat.AppId); rule != nil {
|
|
lostStat.CPSRate = rule.CPSRate
|
|
lostStat.CPAPrice = rule.CPAPrice
|
|
lostStat.Settlement = rule.Settlement
|
|
lostStat.DivideRate = rule.DivideRate
|
|
lostStat.AppType = rule.AppType
|
|
lostStat.PromoteMode = rule.PromoteMode
|
|
}
|
|
AccumulateChildAgentChannelStatToParent(lostStat, childStat)
|
|
found.Stats = append(found.Stats, lostStat)
|
|
log.Println(lostStat)
|
|
}
|
|
}
|
|
}
|
|
found.Accumulate = true
|
|
}
|
|
traverse(s, accumulate)
|
|
return nil
|
|
}
|
|
|
|
func (s *AgentNode) CalculateDeduction(channelMap ChannelMap, ruleMap RuleMap) error {
|
|
deduction := func(found *AgentNode) {
|
|
if found.Deducted {
|
|
panic(errors.New("CalculateDeduction found node, can not be deducted"))
|
|
}
|
|
for _, stat := range found.Stats {
|
|
pRule := GetMergedRule(channelMap, ruleMap, found.ChannelInfo.ParentAgent, stat.AppId)
|
|
sRule := ruleMap.GetRule(stat.Channel, stat.AppId)
|
|
stat.DeductedStat = DeductionChannelStat(stat, pRule, sRule)
|
|
}
|
|
found.Deducted = true
|
|
}
|
|
traverse(s, deduction)
|
|
return nil
|
|
}
|
|
|
|
func DeductionChannelStat(stat *AgentChannelStat, parentRule, selfRule *AgentChannelRule) *proto.AgentDeductedStat {
|
|
proxyDeductionTime, _ := time.ParseInLocation("2006-01-02", "2021-10-28", time.Local)
|
|
base := &proto.AgentDeductedStat{
|
|
Channel: stat.Channel,
|
|
AppId: stat.AppId,
|
|
SumDate: stat.SumDate,
|
|
DirectCount: stat.ValidDirectCount,
|
|
DirectCountChild: stat.ChildValidDirectCount,
|
|
DirectCountSelf: stat.ValidDirectCount - stat.ChildValidDirectCount,
|
|
Recharge: stat.ValidPayMoney,
|
|
RechargeChild: stat.ChildValidPayMoney,
|
|
RechargeSelf: stat.ValidPayMoney - stat.ChildValidPayMoney,
|
|
Settlement: stat.Settlement,
|
|
CPSRate: stat.CPSRate,
|
|
CPAPrice: stat.CPAPrice,
|
|
DivideRate: stat.DivideRate,
|
|
UpdatedAt: stat.UpdatedAt,
|
|
}
|
|
if selfRule == nil {
|
|
if base.DivideRate == 0 {
|
|
base.DivideRate = 100
|
|
}
|
|
return base
|
|
}
|
|
rule := MergedRule(parentRule, selfRule)
|
|
if GlobalAgentChannelSummaryMode == AgentChannelSummaryModeFollowRecord {
|
|
rule.CPSRate = stat.CPSRate
|
|
rule.CPAPrice = stat.CPAPrice
|
|
if base.Settlement == "" {
|
|
base.Settlement = rule.Settlement
|
|
}
|
|
} else {
|
|
base.CPSRate = rule.CPSRate
|
|
base.CPAPrice = rule.CPAPrice
|
|
base.Settlement = rule.Settlement
|
|
}
|
|
base.DivideRate = selfRule.DivideRate
|
|
if base.DivideRate == 0 {
|
|
base.DivideRate = 100
|
|
}
|
|
// 首先进行根代理扣量
|
|
if rule.ProxyDeduction > 0 && rule.ProxyDeduction < 100 && base.SumDate.After(proxyDeductionTime) {
|
|
base.DirectCount = base.DirectCount * rule.ProxyDeduction / 100
|
|
base.DirectCountChild = base.DirectCountChild * rule.ProxyDeduction / 100
|
|
base.Recharge = base.Recharge*rule.ProxyDeduction/100 - (base.Recharge*rule.ProxyDeduction/100)%1000
|
|
base.RechargeChild = base.RechargeChild*rule.ProxyDeduction/100 - (base.RechargeChild*rule.ProxyDeduction/100)%1000
|
|
}
|
|
// 按上级代理对该代理设置的分成比例进行扣量
|
|
if rule.DivideRateBP > 0 && rule.DivideRateBP < 10000 {
|
|
base.DirectCount = base.DirectCount * rule.DivideRateBP / 10000
|
|
base.DirectCountChild = base.DirectCountChild * rule.DivideRateBP / 10000
|
|
base.Recharge = base.Recharge * rule.DivideRateBP / 10000
|
|
base.RechargeChild = base.RechargeChild * rule.DivideRateBP / 10000
|
|
}
|
|
// 再计算收益
|
|
if rule.Settlement == channelmod.SettlementCPA {
|
|
base.Income = base.DirectCount * rule.CPAPrice
|
|
base.IncomeChild = base.DirectCountChild * rule.CPAPrice
|
|
} else if rule.Settlement == channelmod.SettlementCPS {
|
|
base.Income = (base.Recharge * rule.CPSRate) / 100
|
|
base.IncomeChild = (base.RechargeChild * rule.CPSRate) / 100
|
|
} else if rule.Settlement == channelmod.SettlementCPT {
|
|
base.Income = base.DirectCount * rule.CPAPrice
|
|
base.IncomeChild = base.DirectCountChild * rule.CPAPrice
|
|
}
|
|
// 统计子代理数据
|
|
base.IncomeSelf = base.Income - base.IncomeChild
|
|
base.DirectCountSelf = base.DirectCount - base.DirectCountChild
|
|
base.RechargeSelf = base.Recharge - base.RechargeChild
|
|
for _, child := range stat.ChildChannelStats {
|
|
base.ChildRebate += child.DeductedStat.Income
|
|
}
|
|
return base
|
|
}
|
|
|
|
func AccumulateChildAgentChannelStatToParent(parent, child *AgentChannelStat) {
|
|
channelmod.ChannelStatSummation(&parent.ChannelStat, &child.ChannelStat)
|
|
// 子渠道累计
|
|
parent.ChildValidPayMoney += child.VipPayMoney
|
|
parent.ChildValidDirectCount += child.NewDirectCount
|
|
parent.ChildChannelStats = append(parent.ChildChannelStats, child)
|
|
}
|