一、数据库变更SQL

在现有表基础上新增必要字段和表。

-- 1. 用户表 eb_user 新增字段
ALTER TABLE `eb_user` 
ADD COLUMN `frozen_brokerage` decimal(12,2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '冻结佣金(仅用于复购)' AFTER `brokerage_price`,
ADD COLUMN `total_withdrawn` decimal(12,2) UNSIGNED NOT NULL DEFAULT '0.00' COMMENT '累计已提现金额' AFTER `frozen_brokerage`,
ADD COLUMN `is_service_point` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否为服务点' AFTER `total_withdrawn`,
ADD COLUMN `service_point_id` int(11) NOT NULL DEFAULT '0' COMMENT '所属服务点ID(0表示无服务点或公共)' AFTER `is_service_point`,
ADD COLUMN `service_point_time` int(11) NOT NULL DEFAULT '0' COMMENT '成为服务点时间' AFTER `service_point_id`,
ADD COLUMN `direct_referrals` int(11) NOT NULL DEFAULT '0' COMMENT '直接推荐会员数量(满足条件可升级服务点)' AFTER `service_point_time`,
ADD COLUMN `lock_orders_count` int(11) NOT NULL DEFAULT '0' COMMENT '购买锁单商品数量' AFTER `direct_referrals`;

-- 为性能添加索引
ALTER TABLE `eb_user` ADD INDEX `service_point_id`(`service_point_id`);
ALTER TABLE `eb_user` ADD INDEX `is_service_point`(`is_service_point`);

-- 2. 订单表 eb_store_order 新增字段
ALTER TABLE `eb_store_order` 
ADD COLUMN `is_auto_order` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否为自动滑降订单' AFTER `is_gift`,
ADD COLUMN `origin_order_id` int(11) NOT NULL DEFAULT '0' COMMENT '原始订单ID(滑降订单的来源订单)' AFTER `is_auto_order`,
ADD COLUMN `slide_source_order_id` int(11) NOT NULL DEFAULT '0' COMMENT '产生滑降的触发订单ID' AFTER `origin_order_id`;

-- 添加索引
ALTER TABLE `eb_store_order` ADD INDEX `is_auto_order`(`is_auto_order`);
ALTER TABLE `eb_store_order` ADD INDEX `origin_order_id`(`origin_order_id`);

-- 3. 新增服务点升级记录表(可选,用于审计)
CREATE TABLE IF NOT EXISTS `eb_service_point_upgrade_log` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `uid` int(11) UNSIGNED NOT NULL COMMENT '用户ID',
  `upgrade_type` tinyint(1) NOT NULL COMMENT '升级方式:1=直推10人,2=锁单10单,3=后台手动',
  `related_count` int(11) NOT NULL COMMENT '升级时的达标数量',
  `add_time` int(11) UNSIGNED NOT NULL,
  PRIMARY KEY (`id`),
  KEY `uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='服务点升级记录表';

二、新增服务类

1. 滑降队列服务 app/services/activity/slide/SlideQueueService.php

负责管理公共队列和服务点队列(基于Redis),实现订单入队、滑降触发、自动下单等核心逻辑。

<?php

namespace app\services\activity\slide;

use app\services\BaseServices;
use app\services\order\StoreOrderCreateServices;
use app\services\order\StoreOrderServices;
use app\services\order\StoreOrderTakeServices;
use app\services\user\UserServices;
use crmeb\exceptions\ApiException;
use crmeb\services\CacheService;
use think\facade\Db;
use think\facade\Log;

/**
 * 滑降公排队列服务
 */
class SlideQueueService extends BaseServices
{
    // Redis key 前缀
    const ORDER_QUEUE_PREFIX = 'slide:order_queue:';      // 报单队列
    const SLIDE_QUEUE_PREFIX = 'slide:slide_queue:';      // 待滑降队列
    const COMMON_QUEUE_KEY = 'common';                    // 公共队列标识

    /** @var StoreOrderServices 订单服务 */
    protected $orderServices;

    /** @var StoreOrderCreateServices 订单创建服务 */
    protected $orderCreateServices;

    /** @var UserServices 用户服务 */
    protected $userServices;

    /** @var array 商品配置(900元产品ID,可从后台配置) */
    protected $productIds;

    public function __construct()
    {
        $this->orderServices = app()->make(StoreOrderServices::class);
        $this->orderCreateServices = app()->make(StoreOrderCreateServices::class);
        $this->userServices = app()->make(UserServices::class);
        // 从系统配置读取900元产品ID,默认1和7
        $this->productIds = sys_config('slide_product_ids', '1,7');
        if (is_string($this->productIds)) {
            $this->productIds = array_map('intval', explode(',', $this->productIds));
        }
    }

    /**
     * 获取队列key
     * @param string $type order|slide
     * @param int|null $servicePointId 服务点ID,null表示公共队列
     * @return string
     */
    private function getQueueKey(string $type, ?int $servicePointId = null): string
    {
        $prefix = $type === 'order' ? self::ORDER_QUEUE_PREFIX : self::SLIDE_QUEUE_PREFIX;
        $identifier = is_null($servicePointId) ? self::COMMON_QUEUE_KEY : (string)$servicePointId;
        return $prefix . $identifier;
    }

    /**
     * 添加订单到报单队列(订单队列)
     * @param int $orderId 订单ID(必须是900元产品订单)
     * @param int|null $servicePointId 所属服务点ID,null表示公共
     */
    public function addToOrderQueue(int $orderId, ?int $servicePointId = null): void
    {
        $key = $this->getQueueKey('order', $servicePointId);
        // 使用有序集合,score为订单创建时间,保证先进先出
        $order = $this->orderServices->get($orderId, ['add_time']);
        $score = $order ? $order['add_time'] : time();
        CacheService::zAdd($key, $score, $orderId);
        // 每添加一单,尝试触发滑降
        $this->triggerSlide($servicePointId);
    }

    /**
     * 添加订单到待滑降队列
     * @param int $orderId 订单ID
     * @param int|null $servicePointId 所属服务点ID
     */
    public function addToSlideQueue(int $orderId, ?int $servicePointId = null): void
    {
        $key = $this->getQueueKey('slide', $servicePointId);
        $order = $this->orderServices->get($orderId, ['add_time']);
        $score = $order ? $order['add_time'] : time();
        CacheService::zAdd($key, $score, $orderId);
    }

    /**
     * 从待滑降队列中弹出一个订单(最早进入的)
     * @param int|null $servicePointId
     * @return int|null 订单ID,无则返回null
     */
    public function popFromSlideQueue(?int $servicePointId = null): ?int
    {
        $key = $this->getQueueKey('slide', $servicePointId);
        // 获取最小score的一个元素(即最早进入)
        $orders = CacheService::zRangeByScore($key, '-inf', '+inf', ['limit' => [0, 1]]);
        if (empty($orders)) {
            return null;
        }
        $orderId = (int)$orders[0];
        // 移除该元素
        CacheService::zRem($key, $orderId);
        return $orderId;
    }

    /**
     * 获取报单队列长度
     * @param int|null $servicePointId
     * @return int
     */
    public function getOrderQueueLength(?int $servicePointId = null): int
    {
        $key = $this->getQueueKey('order', $servicePointId);
        return CacheService::zCard($key);
    }

    /**
     * 触发滑降:检查报单队列长度是否为2的倍数,是则从待滑降队列取出一个订单生成滑降订单
     * @param int|null $servicePointId
     */
    public function triggerSlide(?int $servicePointId = null): void
    {
        $orderQueueLen = $this->getOrderQueueLength($servicePointId);
        // 每新增2单触发一次滑降(即订单队列长度%2 == 0 且 >0)
        if ($orderQueueLen % 2 === 0 && $orderQueueLen > 0) {
            $slideOrderId = $this->popFromSlideQueue($servicePointId);
            if ($slideOrderId) {
                $this->generateSlideOrder($slideOrderId, $servicePointId);
            }
        }
    }

    /**
     * 生成滑降订单(自动下单900元指定产品,自动收货)
     * @param int $sourceOrderId 触发滑降的源订单(即从待滑降队列弹出的订单)
     * @param int|null $servicePointId 所属服务点ID
     * @throws \Exception
     */
    public function generateSlideOrder(int $sourceOrderId, ?int $servicePointId = null): void
    {
        // 获取源订单信息
        $sourceOrder = $this->orderServices->get($sourceOrderId);
        if (!$sourceOrder) {
            Log::error("滑降失败:源订单{$sourceOrderId}不存在");
            return;
        }

        $buyerUid = $sourceOrder['uid'];           // 要自动下单的用户
        $productId = $this->getSlideProductId();   // 获取滑降产品ID(900元)

        // 检查该用户是否有足够冻结金额(或者直接用余额?需求是使用冻结金额复购)
        $user = $this->userServices->get($buyerUid, ['frozen_brokerage', 'brokerage_price']);
        if ($user['frozen_brokerage'] < 900) {
            Log::error("滑降失败:用户{$buyerUid}冻结佣金不足900,当前冻结:" . $user['frozen_brokerage']);
            // 若冻结不足,可以尝试从可提现佣金扣除,但需求明确是冻结金额
            return;
        }

        // 扣减冻结佣金(减少900)
        $newFrozen = bcsub($user['frozen_brokerage'], '900', 2);
        $this->userServices->update($buyerUid, ['frozen_brokerage' => $newFrozen]);

        // 生成自动订单
        // 需要调用订单创建逻辑,创建一笔购买指定产品ID的订单,支付方式为“冻结余额支付”,金额为0(因为冻结已扣?实际上冻结金额作为支付方式)
        // 简化:直接创建一个已支付、已收货的订单(因为我们扣了冻结佣金,相当于已支付)
        $orderData = $this->createAutoOrder($buyerUid, $productId, $sourceOrderId);
        if (!$orderData) {
            // 回滚冻结金额
            $this->userServices->update($buyerUid, ['frozen_brokerage' => $user['frozen_brokerage']]);
            Log::error("生成滑降订单失败,用户{$buyerUid}");
            return;
        }

        // 记录源订单已被滑降(可选)
        $this->orderServices->update($sourceOrderId, ['is_slided' => 1]);

        // 自动收货:直接调用收货逻辑(模拟确认收货)
        try {
            /** @var StoreOrderTakeServices $takeServices */
            $takeServices = app()->make(StoreOrderTakeServices::class);
            $takeServices->takeOrder($orderData['order_id'], $buyerUid);
        } catch (\Exception $e) {
            Log::error("滑降订单自动收货失败,订单号:" . $orderData['order_id'] . ",错误:" . $e->getMessage());
        }

        // 滑降订单本身也要加入待滑降队列(再次排队等待下一轮)
        $this->addToSlideQueue($orderData['id'], $servicePointId);
    }

    /**
     * 获取滑降产品ID(900元,从配置中取第一个)
     * @return int
     */
    private function getSlideProductId(): int
    {
        return $this->productIds[0] ?? 1;
    }

    /**
     * 创建自动订单(使用冻结佣金支付)
     * @param int $uid
     * @param int $productId
     * @param int $sourceOrderId
     * @return array|null 订单信息数组
     */
    private function createAutoOrder(int $uid, int $productId, int $sourceOrderId): ?array
    {
        // 这里需要构造购物车数据,然后调用正常下单流程,但支付金额设置为0(因为已从冻结扣款)
        // 由于业务复杂,我们使用简化方式:直接向订单表插入一条记录,并关联购物车信息(需伪造)
        // 实际开发应复用购物车和订单创建逻辑,这里仅做演示,请根据项目实际调整
        // 注意:需要确保分发佣金、服务费等流程正常触发,所以最好走完整订单创建流程。
        // 以下仅为占位逻辑,真实场景需要实现完整的免支付下单。
        // 建议在 `StoreOrderCreateServices` 中新增方法 `createAutoSlideOrder` 来实现。
        try {
            // 假设已有方法:$this->orderCreateServices->createAutoSlideOrder($uid, $productId, $sourceOrderId);
            // 这里模拟返回
            return [
                'id' => 99999,
                'order_id' => 'AS' . time() . rand(1000, 9999),
                'uid' => $uid,
                'pay_price' => 900,
                'paid' => 1,
                'status' => 2, // 直接已收货
            ];
        } catch (\Exception $e) {
            Log::error("自动订单创建失败:" . $e->getMessage());
            return null;
        }
    }
}

2. 服务点管理服务 app/services/user/ServicePointServices.php

负责服务点资格检查、绑定下级、升级等。

<?php

namespace app\services\user;

use app\services\BaseServices;
use app\dao\user\UserDao;
use crmeb\exceptions\ApiException;
use think\facade\Db;

class ServicePointServices extends BaseServices
{
    protected $dao;

    public function __construct(UserDao $dao)
    {
        $this->dao = $dao;
    }

    /**
     * 检查用户是否可升级为服务点
     * @param int $uid
     * @return bool
     */
    public function canUpgradeToServicePoint(int $uid): bool
    {
        $user = $this->dao->get($uid, ['direct_referrals', 'lock_orders_count', 'is_service_point']);
        if ($user['is_service_point']) {
            return false;
        }
        // 条件1:直接推荐满10人
        if ($user['direct_referrals'] >= 10) {
            return true;
        }
        // 条件2:购买锁单商品满10单(锁单商品ID需配置,假设为商品ID=1)
        $lockProductId = sys_config('lock_product_id', 1);
        /** @var StoreOrderServices $orderServices */
        $orderServices = app()->make(StoreOrderServices::class);
        $lockCount = $orderServices->count([
            'uid' => $uid,
            'paid' => 1,
            'is_del' => 0,
            'pid' => 0,
            'product_id' => $lockProductId,   // 注意:order表无product_id直接字段,需通过cart_info关联,此处简化
        ]);
        if ($lockCount >= 10) {
            return true;
        }
        return false;
    }

    /**
     * 执行升级为服务点
     * @param int $uid
     * @param int $upgradeType 1=直推升级 2=锁单升级 3=后台手动
     * @return bool
     */
    public function upgradeToServicePoint(int $uid, int $upgradeType = 1): bool
    {
        $user = $this->dao->get($uid);
        if ($user['is_service_point']) {
            return true;
        }
        $relatedCount = $upgradeType == 1 ? $user['direct_referrals'] : $user['lock_orders_count'];
        return Db::transaction(function () use ($uid, $upgradeType, $relatedCount, $user) {
            // 更新用户服务点状态
            $this->dao->update($uid, [
                'is_service_point' => 1,
                'service_point_id' => $uid,   // 服务点自身ID即为其团队ID
                'service_point_time' => time(),
            ]);
            // 记录升级日志
            $logModel = app()->make(\app\model\other\ServicePointUpgradeLog::class);
            $logModel->save([
                'uid' => $uid,
                'upgrade_type' => $upgradeType,
                'related_count' => $relatedCount,
                'add_time' => time(),
            ]);
            return true;
        });
    }

    /**
     * 获取用户所属服务点ID(如果是服务点自身,则返回自身;否则返回上级服务点)
     * 注意:升级服务点后,新推荐的下级才会被绑定到此服务点。此方法需在推荐时调用。
     * @param int $uid
     * @return int 服务点ID,0表示无服务点(公共队列)
     */
    public function getUserServicePointId(int $uid): int
    {
        $user = $this->dao->get($uid, ['service_point_id', 'spread_uid', 'is_service_point']);
        if ($user['service_point_id'] > 0) {
            return $user['service_point_id'];
        }
        // 如果用户本身不是服务点且没有绑定服务点,则尝试向上级查找(仅当上级是服务点且用户被绑定)
        // 注意:只有用户注册/推荐时,如果推荐人是服务点,才会设置service_point_id。因此此处直接返回0。
        return 0;
    }

    /**
     * 当新用户注册或被推荐时,绑定服务点(如果推荐人是服务点)
     * @param int $newUid 新用户ID
     * @param int $spreadUid 推荐人ID
     */
    public function bindServicePointOnRegister(int $newUid, int $spreadUid): void
    {
        $spreadUser = $this->dao->get($spreadUid, ['is_service_point', 'service_point_id', 'uid']);
        if ($spreadUser['is_service_point']) {
            // 推荐人是服务点,则新用户绑定到该服务点
            $servicePointId = $spreadUser['uid'];   // 服务点自身ID就是团队标识
            $this->dao->update($newUid, ['service_point_id' => $servicePointId]);
        } else if ($spreadUser['service_point_id'] > 0) {
            // 推荐人本身有上级服务点,则新用户继承该服务点
            $this->dao->update($newUid, ['service_point_id' => $spreadUser['service_point_id']]);
        }
        // 否则不绑定,service_point_id = 0
    }

    /**
     * 增加用户的直接推荐人数(当成功推荐新会员时调用)
     * @param int $uid
     */
    public function incDirectReferrals(int $uid): void
    {
        $this->dao->bcInc($uid, 'direct_referrals', 1);
        // 检测是否满足服务点升级条件
        if ($this->canUpgradeToServicePoint($uid)) {
            $this->upgradeToServicePoint($uid, 1);
        }
    }
}

3. 佣金及冻结逻辑扩展 app/services/user/UserBrokerageServices.php

增加收入类型,并实现“累计提现满900后佣金转为冻结金额”的逻辑。

在原有文件中添加方法:

// 在 UserBrokerageServices 类末尾添加

/**
 * 增加佣金(可提现或冻结)
 * @param int $uid
 * @param string $type
 * @param array $data
 * @param bool $isFrozen 是否直接存入冻结金额(用于提现超限后)
 * @return bool
 */
public function incomeWithFrozenCheck(int $uid, string $type, array $data, bool $isFrozen = false): bool
{
    $userServices = app()->make(UserServices::class);
    $user = $userServices->get($uid, ['total_withdrawn', 'brokerage_price', 'frozen_brokerage']);
    $amount = $data['number'];
    
    // 判断累计提现是否已达到900元(阈值可配置)
    $threshold = sys_config('frozen_threshold', 900);
    if (!$isFrozen && $user['total_withdrawn'] >= $threshold) {
        // 超过阈值,此笔佣金转入冻结金额
        $isFrozen = true;
    }
    
    if ($isFrozen) {
        // 增加冻结佣金
        $newFrozen = bcadd($user['frozen_brokerage'], $amount, 2);
        $userServices->update($uid, ['frozen_brokerage' => $newFrozen]);
        $data['mark'] = ($data['mark'] ?? '') . '(已转为冻结金额,仅用于复购)';
        $data['type'] .= '_frozen';
    } else {
        // 正常增加可提现佣金
        $newBrokerage = bcadd($user['brokerage_price'], $amount, 2);
        $userServices->update($uid, ['brokerage_price' => $newBrokerage]);
    }
    
    // 记录佣金明细(可使用原有income方法,但需调整balance)
    $balance = $isFrozen ? $newFrozen : $newBrokerage;
    return $this->income($type, $uid, $amount, $balance, $data['link_id'] ?? 0);
}

4. 收货后处理滑降逻辑

修改 StoreOrderTakeServicestakeOrder 方法,在成功收货后调用滑降处理。

修改文件:app/services/order/StoreOrderTakeServices.php

takeOrder 方法结尾处(return $order; 之前)添加:

// 订单收货成功后,处理滑降公排逻辑
$this->afterTakeOrderProcess($order);

新增私有方法:

/**
 * 收货后处理滑降公排逻辑
 * @param $order
 */
protected function afterTakeOrderProcess($order): void
{
    // 仅处理已支付、非自动订单、且是900元产品的订单
    if (!$order['paid'] || $order['is_auto_order'] == 1) {
        return;
    }
    // 检查商品是否为900元产品(商品ID在配置的产品ID列表中)
    $slideProductIds = sys_config('slide_product_ids', '1,7');
    $slideProductIds = array_map('intval', explode(',', $slideProductIds));
    // 需要获取订单中的商品ID,通过 cart_info
    /** @var StoreOrderCartInfoServices $cartInfoServices */
    $cartInfoServices = app()->make(StoreOrderCartInfoServices::class);
    $cartInfo = $cartInfoServices->getCartColunm(['oid' => $order['id']], 'cart_info');
    $isSlideProduct = false;
    foreach ($cartInfo as $cart) {
        $cartData = is_string($cart) ? json_decode($cart, true) : $cart;
        $productId = $cartData['product_id'] ?? 0;
        if (in_array($productId, $slideProductIds)) {
            $isSlideProduct = true;
            break;
        }
    }
    if (!$isSlideProduct) {
        return;
    }

    // 获取用户所属服务点ID
    /** @var ServicePointServices $servicePointServices */
    $servicePointServices = app()->make(ServicePointServices::class);
    $servicePointId = $servicePointServices->getUserServicePointId($order['uid']);

    // 检查上级累积可提现佣金是否达到900(上级的累计提现金额?需求原文:该会员的上级会员累积获得可提现金额是否满900)
    // 注意:累积获得可提现金额是指上级自注册以来累计获取的佣金(不扣除提现),可以用 user_brokerage 中 pm=1 且 type 为佣金类型的总和。
    $spreadUid = $order['spread_uid'];
    if ($spreadUid > 0) {
        /** @var UserBrokerageServices $brokerageServices */
        $brokerageServices = app()->make(UserBrokerageServices::class);
        // 获取上级累积获得佣金(可提现部分,不包含冻结)
        $totalEarned = $brokerageServices->getUserBrokerageSum($spreadUid, ['one_brokerage', 'two_brokerage', 'self_brokerage', 'staff_brokerage', 'agent_brokerage', 'division_brokerage']);
        $threshold = sys_config('slide_trigger_threshold', 900);
        if ($totalEarned >= $threshold) {
            // 满足条件,将该订单加入待滑降队列
            /** @var SlideQueueService $slideQueue */
            $slideQueue = app()->make(SlideQueueService::class);
            $slideQueue->addToSlideQueue($order['id'], $servicePointId);
            // 不加入报单队列
            return;
        }
    }
    // 不满足条件,加入报单队列
    /** @var SlideQueueService $slideQueue */
    $slideQueue = app()->make(SlideQueueService::class);
    $slideQueue->addToOrderQueue($order['id'], $servicePointId);
}

5. 订单创建时绑定服务点

修改推荐注册逻辑(通常在 UserServicesRegisterServices 中),当新用户注册时,调用服务点绑定。

在您系统的用户注册成功处添加:

/** @var ServicePointServices $spServices */
$spServices = app()->make(ServicePointServices::class);
$spServices->bindServicePointOnRegister($newUid, $spreadUid);
// 同时增加推荐人的直接推荐人数
if ($spreadUid) {
    $spServices->incDirectReferrals($spreadUid);
}

6. 提现时累计总额并触发冻结

修改 UserExtractServicescash 方法,在成功扣除佣金后,累计 total_withdrawn

在提现申请通过后(changeSuccess 方法中),更新用户的 total_withdrawn 字段:

// 在 changeSuccess 方法中,更新订单状态之前,更新用户累计提现金额
$userServices = app()->make(UserServices::class);
$user = $userServices->get($userExtract['uid'], ['total_withdrawn']);
$newTotal = bcadd($user['total_withdrawn'], $extractNumber, 2);
$userServices->update($userExtract['uid'], ['total_withdrawn' => $newTotal]);

7. 佣金发放时检查是否转为冻结

StoreOrderTakeServices 中的 backOrderBrokeragedivisionBrokerage 等方法中,原本调用 UserBrokerageServices::income 的地方,替换为 incomeWithFrozenCheck

例如 backOrderBrokerage 方法中:

// 原代码:
// $userBrokerageServices->income('get_brokerage', $one_spread_uid, [...])

// 替换为:
$userBrokerageServices->incomeWithFrozenCheck($one_spread_uid, 'get_brokerage', [
    'nickname' => $userInfo['nickname'],
    'pay_price' => $orderInfo['pay_price'],
    'number' => $brokeragePrice,
    'frozen_time' => $frozen_time,
    'link_id' => $orderInfo['id']
], false);

同理处理其他佣金类型(二级返佣、事业部返佣等)。

8. 后台配置项

在系统配置表中添加以下配置项,便于动态修改:

  • slide_product_ids:滑降产品ID,多个用逗号分隔,如“1,7”
  • lock_product_id:锁单产品ID,用于服务点资格
  • slide_trigger_threshold:触发滑降所需上级累计佣金阈值(元),默认900
  • frozen_threshold:佣金转为冻结的累计提现阈值(元),默认900

您可以通过后台配置管理页面添加这些配置。

三、使用说明

  1. 执行数据库SQL:新增字段和表。
  2. 部署新增服务类:将上述代码放入对应命名空间的目录。
  3. 修改原有文件:按照说明修改 StoreOrderTakeServicesUserBrokerageServicesUserExtractServices、用户注册逻辑。
  4. 配置Redis:确保系统已正确配置Redis,且 CacheService 支持Redis驱动(因为使用了zAdd等有序集合操作)。
  5. 添加后台配置项:在系统配置表中插入上述配置项,并设置默认值。
  6. 测试流程
    • 模拟用户注册、购买900元产品,收货后检查队列。
    • 模拟上级累计佣金达900后,新用户收货订单进入待滑降队列。
    • 模拟报单队列每增加2单,触发滑降,自动下单并扣减冻结佣金。
    • 测试提现超过900后,后续佣金自动转为冻结金额。

四、注意事项

  • 滑降订单生成涉及完整的订单创建流程,实际开发中需复用原有购物车和下单逻辑,避免重复代码。建议在 StoreOrderCreateServices 中新增 createAutoSlideOrder 方法,内部调用订单创建步骤,但跳过支付验证,直接从冻结佣金扣款。
  • 队列使用Redis有序集合保证先进先出,需确保 CacheService 支持相应方法(如 zAdd, zRangeByScore, zCard, zRem)。若原有 CacheService 不支持,可扩展或直接使用 Redis 门面。
  • 由于业务复杂,请充分测试所有边界情况,特别是决定是否从可提现佣金补足或放弃滑降并记录日志。根据您的需求,我将提供一个完整的技术实现方案。该方案基于ThinkPHP6框架,使用Redis队列实现自动滑降公排逻辑,并完善会员分销、服务点管理、佣金冻结与复购等业务功能。