Files
ycc-proxy-server/app/main/api/internal/logic/agent/getconversionratelogic.go
2025-12-09 18:55:28 +08:00

493 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package agent
import (
"context"
"time"
"ycc-server/app/main/model"
"ycc-server/common/ctxdata"
"ycc-server/common/globalkey"
"ycc-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"ycc-server/app/main/api/internal/svc"
"ycc-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetConversionRateLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetConversionRateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetConversionRateLogic {
return &GetConversionRateLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetConversionRateLogic) GetConversionRate() (resp *types.ConversionRateResp, err error) {
userID, err := ctxdata.GetUidFromCtx(l.ctx)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败, %v", err)
}
// 1. 获取代理信息
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrMsg("您不是代理"), "")
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败, %v", err)
}
// 2. 统计我的转化率
myConversionRate := l.calculateConversionRate(agent.Id, nil)
// 3. 统计下级转化率(按时间段动态查询历史下级关系)
subordinateConversionRate := l.calculateSubordinateConversionRate(agent.Id)
return &types.ConversionRateResp{
MyConversionRate: myConversionRate,
SubordinateConversionRate: subordinateConversionRate,
}, nil
}
// calculateSubordinateConversionRate 计算下级转化率(考虑历史关系)
func (l *GetConversionRateLogic) calculateSubordinateConversionRate(parentAgentId string) types.ConversionRateData {
// 使用Asia/Shanghai时区与数据库保持一致
loc, _ := time.LoadLocation("Asia/Shanghai")
now := time.Now().In(loc)
// 日统计:今日、昨日、前日
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
todayEnd := todayStart.AddDate(0, 0, 1)
yesterdayStart := todayStart.AddDate(0, 0, -1)
yesterdayEnd := todayStart
dayBeforeStart := todayStart.AddDate(0, 0, -2)
dayBeforeEnd := yesterdayStart
daily := []types.PeriodConversionData{
l.calculateSubordinatePeriodConversion(parentAgentId, "今日", todayStart, todayEnd),
l.calculateSubordinatePeriodConversion(parentAgentId, "昨日", yesterdayStart, yesterdayEnd),
l.calculateSubordinatePeriodConversion(parentAgentId, "前日", dayBeforeStart, dayBeforeEnd),
}
// 周统计:本周、上周、上上周
weekdayOffset := int(now.Weekday())
if weekdayOffset == 0 {
weekdayOffset = 7 // 周日算作第7天
}
thisWeekStart := now.AddDate(0, 0, -(weekdayOffset - 1))
thisWeekStart = time.Date(thisWeekStart.Year(), thisWeekStart.Month(), thisWeekStart.Day(), 0, 0, 0, 0, loc)
thisWeekEnd := now
lastWeekStart := thisWeekStart.AddDate(0, 0, -7)
lastWeekEnd := thisWeekStart
lastTwoWeekStart := thisWeekStart.AddDate(0, 0, -14)
lastTwoWeekEnd := lastWeekStart
weekly := []types.PeriodConversionData{
l.calculateSubordinatePeriodConversion(parentAgentId, "本周", thisWeekStart, thisWeekEnd),
l.calculateSubordinatePeriodConversion(parentAgentId, "上周", lastWeekStart, lastWeekEnd),
l.calculateSubordinatePeriodConversion(parentAgentId, "上上周", lastTwoWeekStart, lastTwoWeekEnd),
}
// 月统计:本月、上月、前两月
thisMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, loc)
thisMonthEnd := now
lastMonthStart := thisMonthStart.AddDate(0, -1, 0)
lastMonthEnd := thisMonthStart
lastTwoMonthStart := thisMonthStart.AddDate(0, -2, 0)
lastTwoMonthEnd := lastMonthStart
monthly := []types.PeriodConversionData{
l.calculateSubordinatePeriodConversion(parentAgentId, "本月", thisMonthStart, thisMonthEnd),
l.calculateSubordinatePeriodConversion(parentAgentId, "上月", lastMonthStart, lastMonthEnd),
l.calculateSubordinatePeriodConversion(parentAgentId, "前两月", lastTwoMonthStart, lastTwoMonthEnd),
}
return types.ConversionRateData{
Daily: daily,
Weekly: weekly,
Monthly: monthly,
}
}
// calculateConversionRate 计算转化率
// agentId > 0 时统计该代理的转化率,否则统计 subordinateIds 列表的转化率
func (l *GetConversionRateLogic) calculateConversionRate(agentId string, subordinateIds []string) types.ConversionRateData {
// 使用Asia/Shanghai时区与数据库保持一致
loc, _ := time.LoadLocation("Asia/Shanghai")
now := time.Now().In(loc)
// 日统计:今日、昨日、前日
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
todayEnd := todayStart.AddDate(0, 0, 1)
yesterdayStart := todayStart.AddDate(0, 0, -1)
yesterdayEnd := todayStart
dayBeforeStart := todayStart.AddDate(0, 0, -2)
dayBeforeEnd := yesterdayStart
daily := []types.PeriodConversionData{
l.calculatePeriodConversion(agentId, subordinateIds, "今日", todayStart, todayEnd),
l.calculatePeriodConversion(agentId, subordinateIds, "昨日", yesterdayStart, yesterdayEnd),
l.calculatePeriodConversion(agentId, subordinateIds, "前日", dayBeforeStart, dayBeforeEnd),
}
// 周统计:本周、上周、上上周
// 本周本周一00:00:00 到现在
weekdayOffset := int(now.Weekday())
if weekdayOffset == 0 {
weekdayOffset = 7 // 周日算作第7天
}
// 计算本周一:当前日期减去(weekdayOffset-1)天
thisWeekStart := now.AddDate(0, 0, -(weekdayOffset - 1))
thisWeekStart = time.Date(thisWeekStart.Year(), thisWeekStart.Month(), thisWeekStart.Day(), 0, 0, 0, 0, loc)
thisWeekEnd := now
lastWeekStart := thisWeekStart.AddDate(0, 0, -7)
lastWeekEnd := thisWeekStart
lastTwoWeekStart := thisWeekStart.AddDate(0, 0, -14)
lastTwoWeekEnd := lastWeekStart
weekly := []types.PeriodConversionData{
l.calculatePeriodConversion(agentId, subordinateIds, "本周", thisWeekStart, thisWeekEnd),
l.calculatePeriodConversion(agentId, subordinateIds, "上周", lastWeekStart, lastWeekEnd),
l.calculatePeriodConversion(agentId, subordinateIds, "上上周", lastTwoWeekStart, lastTwoWeekEnd),
}
// 月统计:本月、上月、前两月
thisMonthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, loc)
thisMonthEnd := now
lastMonthStart := thisMonthStart.AddDate(0, -1, 0)
lastMonthEnd := thisMonthStart
lastTwoMonthStart := thisMonthStart.AddDate(0, -2, 0)
lastTwoMonthEnd := lastMonthStart
monthly := []types.PeriodConversionData{
l.calculatePeriodConversion(agentId, subordinateIds, "本月", thisMonthStart, thisMonthEnd),
l.calculatePeriodConversion(agentId, subordinateIds, "上月", lastMonthStart, lastMonthEnd),
l.calculatePeriodConversion(agentId, subordinateIds, "前两月", lastTwoMonthStart, lastTwoMonthEnd),
}
return types.ConversionRateData{
Daily: daily,
Weekly: weekly,
Monthly: monthly,
}
}
// calculatePeriodConversion 计算指定时间段的转化率数据
func (l *GetConversionRateLogic) calculatePeriodConversion(agentId string, subordinateIds []string, periodLabel string, startTime, endTime time.Time) types.PeriodConversionData {
// 构建 agent_order 查询条件
agentOrderBuilder := l.svcCtx.AgentOrderModel.SelectBuilder().
Where("del_state = ?", globalkey.DelStateNo).
Where("create_time >= ? AND create_time < ?", startTime, endTime)
if agentId != "" {
// 统计我的转化率
agentOrderBuilder = agentOrderBuilder.Where("agent_id = ?", agentId)
} else if len(subordinateIds) > 0 {
// 统计下级转化率
agentOrderBuilder = agentOrderBuilder.Where(squirrel.Eq{"agent_id": subordinateIds})
} else {
// 没有数据
l.Infof("calculatePeriodConversion: 没有代理ID或下级IDperiodLabel=%s", periodLabel)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
// 添加调试日志
if agentId == "" && len(subordinateIds) > 0 {
l.Infof("calculatePeriodConversion: 统计下级转化率periodLabel=%s, startTime=%v, endTime=%v, subordinateIds数量=%d",
periodLabel, startTime, endTime, len(subordinateIds))
}
// 查询所有代理订单
agentOrders, err := l.svcCtx.AgentOrderModel.FindAll(l.ctx, agentOrderBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
l.Errorf("查询代理订单失败: periodLabel=%s, err=%v", periodLabel, err)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
if len(agentOrders) == 0 {
if agentId == "" && len(subordinateIds) > 0 {
l.Infof("calculatePeriodConversion: 未找到代理订单periodLabel=%s, startTime=%v, endTime=%v",
periodLabel, startTime, endTime)
}
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
l.Infof("calculatePeriodConversion: 找到代理订单数量=%d, periodLabel=%s", len(agentOrders), periodLabel)
// 收集订单ID
orderIds := make([]string, 0, len(agentOrders))
for _, ao := range agentOrders {
orderIds = append(orderIds, ao.OrderId)
}
// 查询订单信息
orderBuilder := l.svcCtx.OrderModel.SelectBuilder().
Where(squirrel.Eq{"id": orderIds}).
Where("del_state = ?", globalkey.DelStateNo)
orders, err := l.svcCtx.OrderModel.FindAll(l.ctx, orderBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
l.Errorf("查询订单失败: %v", err)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
// 统计查询订单数、付费订单数、用户数和总金额
var totalAmount float64
paidOrderCount := 0
queryUserSet := make(map[string]bool)
paidUserSet := make(map[string]bool)
for _, order := range orders {
// 查询用户数(所有订单的用户,去重)
queryUserSet[order.UserId] = true
// 付费订单数和总金额(只统计已支付的订单)
if order.Status == "paid" {
paidOrderCount++
paidUserSet[order.UserId] = true
totalAmount += order.Amount
}
}
// 查询订单数 = 所有订单数量
queryOrderCount := len(orders)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: int64(queryOrderCount), // 订单数量(保持向后兼容)
PaidUsers: int64(paidOrderCount), // 订单数量(保持向后兼容)
TotalAmount: totalAmount,
QueryUserCount: int64(len(queryUserSet)), // 用户数量(新增)
PaidUserCount: int64(len(paidUserSet)), // 用户数量(新增)
}
}
// calculateSubordinatePeriodConversion 计算指定时间段内下级转化率
// 结合使用agent_rebate表和agent_order表
// 1. 查询量通过agent_order表统计所有查询包括未付费的
// 2. 付费量和金额通过agent_rebate表统计只有付费的订单才会产生返佣
func (l *GetConversionRateLogic) calculateSubordinatePeriodConversion(parentAgentId string, periodLabel string, startTime, endTime time.Time) types.PeriodConversionData {
// 1. 查询agent_rebate表获取所有曾经给当前用户产生返佣的source_agent_id这些代理在某个时间点是下级
// 不限制时间,获取所有历史返佣记录,用于确定哪些代理曾经是下级
rebateBuilder := l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", parentAgentId, globalkey.DelStateNo).
Where("source_agent_id != ?", parentAgentId)
allRebates, err := l.svcCtx.AgentRebateModel.FindAll(l.ctx, rebateBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
l.Errorf("查询返佣记录失败: periodLabel=%s, err=%v", periodLabel, err)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
// 收集所有曾经产生返佣的source_agent_id这些代理在某个时间点是下级
sourceAgentIdSet := make(map[string]bool)
paidOrderIdSet := make(map[string]bool) // 已付费的订单ID有返佣的订单
paidOrderIdToAmount := make(map[string]float64) // 已付费订单的金额
for _, rebate := range allRebates {
sourceAgentIdSet[rebate.SourceAgentId] = true
// 如果返佣记录的创建时间在时间段内,说明该订单在时间段内已付费
if rebate.CreateTime.After(startTime) || rebate.CreateTime.Equal(startTime) {
if rebate.CreateTime.Before(endTime) {
paidOrderIdSet[rebate.OrderId] = true
}
}
}
if len(sourceAgentIdSet) == 0 {
l.Infof("calculateSubordinatePeriodConversion: 未找到返佣记录periodLabel=%s, startTime=%v, endTime=%v",
periodLabel, startTime, endTime)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
sourceAgentIds := make([]string, 0, len(sourceAgentIdSet))
for agentId := range sourceAgentIdSet {
sourceAgentIds = append(sourceAgentIds, agentId)
}
l.Infof("calculateSubordinatePeriodConversion: periodLabel=%s, 曾经产生返佣的代理数量=%d", periodLabel, len(sourceAgentIds))
// 2. 查询agent_order表统计这些代理在时间段内的所有订单包括未付费的
agentOrderBuilder := l.svcCtx.AgentOrderModel.SelectBuilder().
Where("del_state = ?", globalkey.DelStateNo).
Where("create_time >= ? AND create_time < ?", startTime, endTime).
Where(squirrel.Eq{"agent_id": sourceAgentIds})
agentOrders, err := l.svcCtx.AgentOrderModel.FindAll(l.ctx, agentOrderBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
l.Errorf("查询代理订单失败: periodLabel=%s, err=%v", periodLabel, err)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
l.Infof("calculateSubordinatePeriodConversion: periodLabel=%s, 查询到代理订单数量=%d", periodLabel, len(agentOrders))
if len(agentOrders) == 0 {
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
// 3. 通过order_id去重获取所有订单ID用于查询订单详情
orderIdSet := make(map[string]bool)
orderIdToAgentOrder := make(map[string]*model.AgentOrder)
for _, ao := range agentOrders {
orderIdSet[ao.OrderId] = true
// 如果同一个订单有多个agent_order记录保留金额更大的
if existing, exists := orderIdToAgentOrder[ao.OrderId]; exists {
if ao.OrderAmount > existing.OrderAmount {
orderIdToAgentOrder[ao.OrderId] = ao
}
} else {
orderIdToAgentOrder[ao.OrderId] = ao
}
}
orderIds := make([]string, 0, len(orderIdSet))
for orderId := range orderIdSet {
orderIds = append(orderIds, orderId)
}
// 4. 查询订单信息
orderBuilder := l.svcCtx.OrderModel.SelectBuilder().
Where(squirrel.Eq{"id": orderIds}).
Where("del_state = ?", globalkey.DelStateNo)
orders, err := l.svcCtx.OrderModel.FindAll(l.ctx, orderBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
l.Errorf("查询订单失败: %v", err)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
// 5. 查询时间段内的返佣记录,获取已付费订单的金额
rebateBuilder = l.svcCtx.AgentRebateModel.SelectBuilder().
Where("agent_id = ? AND del_state = ?", parentAgentId, globalkey.DelStateNo).
Where("source_agent_id != ?", parentAgentId).
Where("create_time >= ? AND create_time < ?", startTime, endTime).
Where(squirrel.Eq{"order_id": orderIds})
rebates, err := l.svcCtx.AgentRebateModel.FindAll(l.ctx, rebateBuilder, "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
l.Errorf("查询返佣记录失败: %v", err)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: 0,
PaidUsers: 0,
TotalAmount: 0,
QueryUserCount: 0,
PaidUserCount: 0,
}
}
// 记录已付费订单的金额使用agent_order的order_amount
for _, rebate := range rebates {
if ao, exists := orderIdToAgentOrder[rebate.OrderId]; exists {
paidOrderIdToAmount[rebate.OrderId] = ao.OrderAmount
}
}
// 6. 统计查询订单数、付费订单数、用户数和总金额
var totalAmount float64
paidOrderCount := 0
queryUserSet := make(map[string]bool)
paidUserSet := make(map[string]bool)
for _, order := range orders {
// 查询用户数(所有订单的用户,去重)
queryUserSet[order.UserId] = true
// 付费订单数和总金额只统计已付费的订单即order_id在paidOrderIdToAmount中
if _, isPaid := paidOrderIdToAmount[order.Id]; isPaid {
paidOrderCount++
paidUserSet[order.UserId] = true
// 使用agent_order的order_amount用户实际支付金额
totalAmount += paidOrderIdToAmount[order.Id]
}
}
// 查询订单数 = 所有订单数量
queryOrderCount := len(orders)
l.Infof("calculateSubordinatePeriodConversion: periodLabel=%s, 查询订单数=%d, 付费订单数=%d, 查询用户数=%d, 付费用户数=%d, 总金额=%.2f",
periodLabel, queryOrderCount, paidOrderCount, len(queryUserSet), len(paidUserSet), totalAmount)
return types.PeriodConversionData{
PeriodLabel: periodLabel,
QueryUsers: int64(queryOrderCount), // 订单数量(保持向后兼容)
PaidUsers: int64(paidOrderCount), // 订单数量(保持向后兼容)
TotalAmount: totalAmount,
QueryUserCount: int64(len(queryUserSet)), // 用户数量(新增)
PaidUserCount: int64(len(paidUserSet)), // 用户数量(新增)
}
}