Files
ycc-proxy-server/app/main/api/internal/service/agentService.go
2025-11-27 13:09:54 +08:00

642 lines
20 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 service
import (
"context"
"database/sql"
"strconv"
"time"
"ycc-server/app/main/api/internal/config"
"ycc-server/app/main/model"
"ycc-server/common/globalkey"
"ycc-server/pkg/lzkit/lzUtils"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
// AgentService 新代理系统服务
type AgentService struct {
config config.Config
OrderModel model.OrderModel
AgentModel model.AgentModel
AgentWalletModel model.AgentWalletModel
AgentRelationModel model.AgentRelationModel
AgentLinkModel model.AgentLinkModel
AgentOrderModel model.AgentOrderModel
AgentCommissionModel model.AgentCommissionModel
AgentRebateModel model.AgentRebateModel
AgentUpgradeModel model.AgentUpgradeModel
AgentWithdrawalModel model.AgentWithdrawalModel
AgentConfigModel model.AgentConfigModel
AgentProductConfigModel model.AgentProductConfigModel
AgentRealNameModel model.AgentRealNameModel
AgentWithdrawalTaxModel model.AgentWithdrawalTaxModel
}
// NewAgentService 创建新的代理服务
func NewAgentService(
c config.Config,
orderModel model.OrderModel,
agentModel model.AgentModel,
agentWalletModel model.AgentWalletModel,
agentRelationModel model.AgentRelationModel,
agentLinkModel model.AgentLinkModel,
agentOrderModel model.AgentOrderModel,
agentCommissionModel model.AgentCommissionModel,
agentRebateModel model.AgentRebateModel,
agentUpgradeModel model.AgentUpgradeModel,
agentWithdrawalModel model.AgentWithdrawalModel,
agentConfigModel model.AgentConfigModel,
agentProductConfigModel model.AgentProductConfigModel,
agentRealNameModel model.AgentRealNameModel,
agentWithdrawalTaxModel model.AgentWithdrawalTaxModel,
) *AgentService {
return &AgentService{
config: c,
OrderModel: orderModel,
AgentModel: agentModel,
AgentWalletModel: agentWalletModel,
AgentRelationModel: agentRelationModel,
AgentLinkModel: agentLinkModel,
AgentOrderModel: agentOrderModel,
AgentCommissionModel: agentCommissionModel,
AgentRebateModel: agentRebateModel,
AgentUpgradeModel: agentUpgradeModel,
AgentWithdrawalModel: agentWithdrawalModel,
AgentConfigModel: agentConfigModel,
AgentProductConfigModel: agentProductConfigModel,
AgentRealNameModel: agentRealNameModel,
AgentWithdrawalTaxModel: agentWithdrawalTaxModel,
}
}
// AgentProcess 处理代理订单(新系统)
// 根据新代理系统的收益分配规则处理订单
func (s *AgentService) AgentProcess(ctx context.Context, order *model.Order) error {
// 1. 检查是否是代理推广订单
agentOrder, err := s.AgentOrderModel.FindOneByOrderId(ctx, order.Id)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 不是代理订单,直接返回
return nil
}
return errors.Wrapf(err, "查询代理订单失败, orderId: %d", order.Id)
}
// 2. 检查订单是否已处理
if agentOrder.ProcessStatus == 1 {
logx.Infof("订单已处理, orderId: %d", order.Id)
return nil
}
// 3. 获取代理信息
agent, err := s.AgentModel.FindOne(ctx, agentOrder.AgentId)
if err != nil {
return errors.Wrapf(err, "查询代理信息失败, agentId: %d", agentOrder.AgentId)
}
// 4. 获取系统配置
basePrice, err := s.getConfigFloat(ctx, "base_price")
if err != nil {
return errors.Wrapf(err, "获取基础底价配置失败")
}
// 6. 使用事务处理订单
return s.AgentWalletModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
// 6.1 计算实际底价和代理收益
levelBonus := s.getLevelBonus(agent.Level)
actualBasePrice := basePrice + float64(levelBonus)
// 6.2 计算提价成本
priceThreshold, _ := s.getConfigFloat(ctx, "price_threshold")
priceFeeRate, _ := s.getConfigFloat(ctx, "price_fee_rate")
priceCost := s.calculatePriceCost(agentOrder.SetPrice, priceThreshold, priceFeeRate)
// 6.3 计算代理收益
agentProfit := agentOrder.SetPrice - actualBasePrice - priceCost
// 6.4 更新代理订单记录
agentOrder.ProcessStatus = 1
agentOrder.ProcessTime = lzUtils.TimeToNullTime(time.Now())
agentOrder.ProcessRemark = lzUtils.StringToNullString("处理成功")
if err := s.AgentOrderModel.UpdateWithVersion(transCtx, session, agentOrder); err != nil {
return errors.Wrapf(err, "更新代理订单失败")
}
// 6.5 发放代理佣金
if err := s.giveAgentCommission(transCtx, session, agentOrder.AgentId, order.Id, order.ProductId, agentProfit); err != nil {
return errors.Wrapf(err, "发放代理佣金失败")
}
// 6.6 分配等级加成返佣给上级链
if levelBonus > 0 {
if err := s.distributeLevelBonus(transCtx, session, agent, order.Id, order.ProductId, float64(levelBonus), levelBonus); err != nil {
return errors.Wrapf(err, "分配等级加成返佣失败")
}
}
return nil
})
}
// getLevelBonus 获取等级加成
func (s *AgentService) getLevelBonus(level int64) int64 {
switch level {
case 1: // 普通
return 6
case 2: // 黄金
return 3
case 3: // 钻石
return 0
default:
return 0
}
}
// calculatePriceCost 计算提价成本
func (s *AgentService) calculatePriceCost(setPrice, priceThreshold, priceFeeRate float64) float64 {
if setPrice <= priceThreshold {
return 0
}
return (setPrice - priceThreshold) * priceFeeRate
}
// giveAgentCommission 发放代理佣金
func (s *AgentService) giveAgentCommission(ctx context.Context, session sqlx.Session, agentId, orderId, productId int64, amount float64) error {
// 1. 创建佣金记录
commission := &model.AgentCommission{
AgentId: agentId,
OrderId: orderId,
ProductId: productId,
Amount: amount,
Status: 1, // 已发放
}
if _, err := s.AgentCommissionModel.Insert(ctx, session, commission); err != nil {
return errors.Wrapf(err, "创建佣金记录失败")
}
// 2. 更新钱包余额
wallet, err := s.AgentWalletModel.FindOneByAgentId(ctx, agentId)
if err != nil {
return errors.Wrapf(err, "查询钱包失败, agentId: %d", agentId)
}
wallet.Balance += amount
wallet.TotalEarnings += amount
if err := s.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
return errors.Wrapf(err, "更新钱包失败")
}
return nil
}
// distributeLevelBonus 分配等级加成返佣给上级链
func (s *AgentService) distributeLevelBonus(ctx context.Context, session sqlx.Session, agent *model.Agent, orderId, productId int64, levelBonus float64, levelBonusInt int64) error {
// 钻石代理等级加成为0无返佣分配
if agent.Level == 3 {
return nil
}
// 黄金代理等级加成3元全部给钻石上级
if agent.Level == 2 {
diamondParent, err := s.findDiamondParent(ctx, agent.Id)
if err != nil {
return errors.Wrapf(err, "查找钻石上级失败")
}
if diamondParent != nil {
return s.giveRebate(ctx, session, diamondParent.Id, agent.Id, orderId, productId, levelBonus, levelBonusInt, 2) // 2=钻石上级返佣
}
// 找不到钻石上级,返佣归平台(异常情况)
return nil
}
// 普通代理等级加成6元按规则分配给上级链
if agent.Level == 1 {
return s.distributeNormalAgentBonus(ctx, session, agent, orderId, productId, levelBonus, levelBonusInt)
}
return nil
}
// distributeNormalAgentBonus 普通代理的等级加成返佣分配6元
func (s *AgentService) distributeNormalAgentBonus(ctx context.Context, session sqlx.Session, agent *model.Agent, orderId, productId int64, amount float64, levelBonusInt int64) error {
// 1. 查找直接上级
parent, err := s.findDirectParent(ctx, agent.Id)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(err, "查找直接上级失败")
}
if parent == nil {
// 无上级,全部归平台
return nil
}
// 2. 给直接上级分配固定金额
var directParentAmount float64
switch parent.Level {
case 3: // 钻石
directParentAmount = 6
case 2: // 黄金
directParentAmount = 3
case 1: // 普通
directParentAmount = 2
default:
directParentAmount = 0
}
if directParentAmount > 0 {
if err := s.giveRebate(ctx, session, parent.Id, agent.Id, orderId, productId, directParentAmount, levelBonusInt, 1); err != nil {
return errors.Wrapf(err, "给直接上级返佣失败")
}
}
remaining := amount - directParentAmount
if remaining <= 0 {
return nil
}
// 3. 分配剩余金额
// 确定查找起点:直接上级是普通时从直接上级开始查找,否则从直接上级的上级开始查找
searchStart := parent
if parent.Level != 1 {
searchStartParent, err := s.findDirectParent(ctx, parent.Id)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(err, "查找上级的上级失败")
}
if searchStartParent != nil {
searchStart = searchStartParent
}
}
if searchStart != nil {
// 查找上级链中的钻石和黄金
diamondParent, _ := s.findDiamondParent(ctx, searchStart.Id)
goldParent, _ := s.findGoldParent(ctx, searchStart.Id)
// 按优先级分配剩余金额
if diamondParent != nil {
// 优先级1有钻石剩余金额全部给钻石
return s.giveRebate(ctx, session, diamondParent.Id, agent.Id, orderId, productId, remaining, levelBonusInt, 2)
} else if goldParent != nil {
// 优先级2只有黄金最多3元给黄金剩余归平台
goldAmount := remaining
if goldAmount > 3 {
goldAmount = 3
}
if err := s.giveRebate(ctx, session, goldParent.Id, agent.Id, orderId, productId, goldAmount, levelBonusInt, 3); err != nil {
return errors.Wrapf(err, "给黄金上级返佣失败")
}
// 剩余归平台(不需要记录)
}
// 优先级3都没有剩余金额归平台不需要记录
}
return nil
}
// giveRebate 发放返佣
func (s *AgentService) giveRebate(ctx context.Context, session sqlx.Session, agentId, sourceAgentId, orderId, productId int64, amount float64, levelBonus int64, rebateType int64) error {
// 1. 创建返佣记录
rebate := &model.AgentRebate{
AgentId: agentId,
SourceAgentId: sourceAgentId,
OrderId: orderId,
ProductId: productId,
RebateType: rebateType,
LevelBonus: float64(levelBonus), // 等级加成金额
RebateAmount: amount,
Status: 1, // 已发放
}
if _, err := s.AgentRebateModel.Insert(ctx, session, rebate); err != nil {
return errors.Wrapf(err, "创建返佣记录失败")
}
// 2. 更新钱包余额
wallet, err := s.AgentWalletModel.FindOneByAgentId(ctx, agentId)
if err != nil {
return errors.Wrapf(err, "查询钱包失败, agentId: %d", agentId)
}
wallet.Balance += amount
wallet.TotalEarnings += amount
if err := s.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
return errors.Wrapf(err, "更新钱包失败")
}
return nil
}
// FindDirectParent 查找直接上级(公开方法)
func (s *AgentService) FindDirectParent(ctx context.Context, agentId int64) (*model.Agent, error) {
return s.findDirectParent(ctx, agentId)
}
// findDirectParent 查找直接上级
func (s *AgentService) findDirectParent(ctx context.Context, agentId int64) (*model.Agent, error) {
// 查找关系类型为1直接关系的上级
builder := s.AgentRelationModel.SelectBuilder()
builder = builder.Where("child_id = ? AND relation_type = ? AND del_state = ?", agentId, 1, globalkey.DelStateNo)
relations, err := s.AgentRelationModel.FindAll(ctx, builder, "")
if err != nil {
return nil, err
}
if len(relations) == 0 {
return nil, model.ErrNotFound
}
// 返回第一个直接上级
return s.AgentModel.FindOne(ctx, relations[0].ParentId)
}
// findDiamondParent 向上查找钻石上级
func (s *AgentService) findDiamondParent(ctx context.Context, agentId int64) (*model.Agent, error) {
currentId := agentId
maxDepth := 100 // 防止无限循环
depth := 0
for depth < maxDepth {
parent, err := s.findDirectParent(ctx, currentId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, model.ErrNotFound
}
return nil, err
}
if parent.Level == 3 { // 钻石
return parent, nil
}
currentId = parent.Id
depth++
}
return nil, model.ErrNotFound
}
// findGoldParent 向上查找黄金上级
func (s *AgentService) findGoldParent(ctx context.Context, agentId int64) (*model.Agent, error) {
currentId := agentId
maxDepth := 100 // 防止无限循环
depth := 0
for depth < maxDepth {
parent, err := s.findDirectParent(ctx, currentId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return nil, model.ErrNotFound
}
return nil, err
}
if parent.Level == 2 { // 黄金
return parent, nil
}
currentId = parent.Id
depth++
}
return nil, model.ErrNotFound
}
// getConfigFloat 获取配置值(浮点数)
func (s *AgentService) getConfigFloat(ctx context.Context, configKey string) (float64, error) {
config, err := s.AgentConfigModel.FindOneByConfigKey(ctx, configKey)
if err != nil {
return 0, err
}
value, err := strconv.ParseFloat(config.ConfigValue, 64)
if err != nil {
return 0, errors.Wrapf(err, "解析配置值失败, key: %s, value: %s", configKey, config.ConfigValue)
}
return value, nil
}
// getConfigInt 获取配置值(整数)
func (s *AgentService) getConfigInt(ctx context.Context, configKey string) (int64, error) {
config, err := s.AgentConfigModel.FindOneByConfigKey(ctx, configKey)
if err != nil {
return 0, err
}
value, err := strconv.ParseInt(config.ConfigValue, 10, 64)
if err != nil {
return 0, errors.Wrapf(err, "解析配置值失败, key: %s, value: %s", configKey, config.ConfigValue)
}
return value, nil
}
// ProcessUpgrade 处理代理升级
func (s *AgentService) ProcessUpgrade(ctx context.Context, agentId, toLevel int64, upgradeType int64, upgradeFee, rebateAmount float64, orderNo string, operatorAgentId int64) error {
return s.AgentWalletModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
// 1. 获取代理信息
agent, err := s.AgentModel.FindOne(transCtx, agentId)
if err != nil {
return errors.Wrapf(err, "查询代理信息失败, agentId: %d", agentId)
}
// 2. 如果是自主付费升级,处理返佣
if upgradeType == 1 { // 自主付费
// 查找原直接上级
parent, err := s.findDirectParent(transCtx, agentId)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(err, "查找直接上级失败")
}
if parent != nil && rebateAmount > 0 {
// 返佣给原直接上级
if err := s.giveRebateForUpgrade(transCtx, session, parent.Id, agentId, rebateAmount); err != nil {
return errors.Wrapf(err, "返佣给上级失败")
}
}
}
// 3. 执行升级操作
agent.Level = toLevel
// 4. 检查是否需要脱离直接上级关系
needDetach, err := s.needDetachFromParent(transCtx, agent, toLevel)
if err != nil {
return errors.Wrapf(err, "检查是否需要脱离关系失败")
}
if needDetach {
// 脱离直接上级关系
if err := s.detachFromParent(transCtx, session, agentId); err != nil {
return errors.Wrapf(err, "脱离直接上级关系失败")
}
}
// 5. 如果升级为钻石,独立成新团队
if toLevel == 3 {
agent.TeamLeaderId = sql.NullInt64{Int64: agentId, Valid: true}
// 更新所有下级的团队首领
if err := s.updateChildrenTeamLeader(transCtx, session, agentId, agentId); err != nil {
return errors.Wrapf(err, "更新下级团队首领失败")
}
} else {
// 更新团队首领(查找上级链中的钻石代理)
teamLeaderId, err := s.findTeamLeaderId(transCtx, agentId)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(err, "查找团队首领失败")
}
if teamLeaderId > 0 {
agent.TeamLeaderId = sql.NullInt64{Int64: teamLeaderId, Valid: true}
}
}
// 6. 更新代理记录
if err := s.AgentModel.UpdateWithVersion(transCtx, session, agent); err != nil {
return errors.Wrapf(err, "更新代理记录失败")
}
// 7. 更新升级记录状态
// 这里需要先查询升级记录暂时先跳过在logic中处理
return nil
})
}
// needDetachFromParent 检查是否需要脱离直接上级关系
func (s *AgentService) needDetachFromParent(ctx context.Context, agent *model.Agent, newLevel int64) (bool, error) {
parent, err := s.findDirectParent(ctx, agent.Id)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return false, nil // 没有上级,不需要脱离
}
return false, err
}
// 规则1下级不能比上级等级高
if newLevel > parent.Level {
return true, nil
}
// 规则2同级不能作为上下级除了普通代理
if newLevel == parent.Level {
if newLevel == 2 || newLevel == 3 { // 黄金或钻石
return true, nil
}
}
// 规则3钻石 → 黄金禁止(特殊规则)
if newLevel == 2 && parent.Level == 3 {
return true, nil
}
return false, nil
}
// detachFromParent 脱离直接上级关系
func (s *AgentService) detachFromParent(ctx context.Context, session sqlx.Session, agentId int64) error {
// 查找直接关系
builder := s.AgentRelationModel.SelectBuilder().
Where("child_id = ? AND relation_type = ? AND del_state = ?", agentId, 1, globalkey.DelStateNo)
relations, err := s.AgentRelationModel.FindAll(ctx, builder, "")
if err != nil {
return err
}
if len(relations) == 0 {
return nil // 没有关系,不需要脱离
}
// 将直接关系标记为已脱离
relation := relations[0]
relation.RelationType = 2 // 已脱离
relation.DetachReason = lzUtils.StringToNullString("upgrade")
relation.DetachTime = lzUtils.TimeToNullTime(time.Now())
if err := s.AgentRelationModel.UpdateWithVersion(ctx, session, relation); err != nil {
return errors.Wrapf(err, "更新关系记录失败")
}
return nil
}
// updateChildrenTeamLeader 更新所有下级的团队首领
func (s *AgentService) updateChildrenTeamLeader(ctx context.Context, session sqlx.Session, agentId, teamLeaderId int64) error {
// 递归更新所有下级
var updateChildren func(int64) error
updateChildren = func(parentId int64) error {
// 查找直接下级
builder := s.AgentRelationModel.SelectBuilder().
Where("parent_id = ? AND relation_type = ? AND del_state = ?", parentId, 1, globalkey.DelStateNo)
relations, err := s.AgentRelationModel.FindAll(ctx, builder, "")
if err != nil {
return err
}
for _, relation := range relations {
child, err := s.AgentModel.FindOne(ctx, relation.ChildId)
if err != nil {
continue
}
child.TeamLeaderId = sql.NullInt64{Int64: teamLeaderId, Valid: true}
if err := s.AgentModel.UpdateWithVersion(ctx, session, child); err != nil {
return errors.Wrapf(err, "更新下级团队首领失败, childId: %d", child.Id)
}
// 递归更新下级的下级
if err := updateChildren(child.Id); err != nil {
return err
}
}
return nil
}
return updateChildren(agentId)
}
// findTeamLeaderId 查找团队首领ID钻石代理
func (s *AgentService) findTeamLeaderId(ctx context.Context, agentId int64) (int64, error) {
diamondParent, err := s.findDiamondParent(ctx, agentId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return 0, nil
}
return 0, err
}
return diamondParent.Id, nil
}
// giveRebateForUpgrade 发放升级返佣
func (s *AgentService) giveRebateForUpgrade(ctx context.Context, session sqlx.Session, parentAgentId, upgradeAgentId int64, amount float64) error {
// 更新钱包余额
wallet, err := s.AgentWalletModel.FindOneByAgentId(ctx, parentAgentId)
if err != nil {
return errors.Wrapf(err, "查询钱包失败, agentId: %d", parentAgentId)
}
wallet.Balance += amount
wallet.TotalEarnings += amount
if err := s.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
return errors.Wrapf(err, "更新钱包失败")
}
return nil
}
// GetUpgradeFee 获取升级费用
func (s *AgentService) GetUpgradeFee(fromLevel, toLevel int64) float64 {
if fromLevel == 1 && toLevel == 2 {
return 199 // 普通→黄金
} else if toLevel == 3 {
return 980 // 升级为钻石
}
return 0
}
// GetUpgradeRebate 获取升级返佣金额
func (s *AgentService) GetUpgradeRebate(fromLevel, toLevel int64) float64 {
if fromLevel == 1 && toLevel == 2 {
return 139 // 普通→黄金返佣
} else if toLevel == 3 {
return 680 // 升级为钻石返佣
}
return 0
}