LikeShop 邀请裂变体系二开:一级二级邀请奖励与拉新防作弊机制

一、原生分销体系拆解

1.1 原生分销的核心数据模型

LikeShop 的分销体系以 ls_user 表的上下级关系字段为核心;每个用户通过 invite_user_id (或类似字段)记录其直接上级,三级分销的佣金计算依赖这条链路的逐级向上查找。

分销关系的绑定逻辑为:A 分享商品或邀请链接给 B,B 通过链接注册或首次绑定后,A 成为 B 的上级。如果 A 是 B 的上级,B 是 C 的上级,那么 B 是 C 的一级分销,A 是 C 的二级分销;C 购买商品时,B 获得一级分销佣金,A 获得二级分销佣金。

佣金金额的计算公式为:佣金计算金额 = 商品总金额 - 优惠券金额 - 用户折扣金额 -/+ 商品改价金额 - 运费金额;这个公式有几个关键约束:运费不计算佣金;使用优惠券或积分等非现金支付的部分不计算佣金。

原生系统支持三种分销员模式:人人分销(注册即成为分销会员)、申请分销(需要申请并审核通过)、指定分销(通过后台开通指定会员分销权);指定分销状态按会员等级指定,未指定的分销等级会员不允许参与分销。

1.2 佣金结算的三个必要条件

原生系统的佣金结算逻辑为:商品已确认收货 → 已过商品售后期 → 已过结算时间,且需要定时任务正常执行;未配置定时任务时,佣金不会自动结算;佣金提现至钱包余额时不扣手续费,其他提现方式均需要手续费。

1.3 原生体系的四个核心缺口

缺口一:奖励类单一。原生分销仅支持佣金(现金)一种奖励类型;虽然系统在推广层面可以配合优惠券和积分使用,但奖励类型和分销佣金没有打通——邀请人无法获得“积分+优惠券”的组合奖励,被邀请人也无法自动获得新人礼包。

缺口二:邀请注册无独立奖励。原生系统的佣金仅在“下级购买分销商品”时产生;如果下级只是注册了但未购买,邀请人不会获得任何奖励;这意味着“拉新”和“转化”两个环节是捆绑的,无法激励纯拉新行为。

缺口三:无防刷判定机制。原生系统没有对邀请关系的真实性做校验;羊毛党可以通过同一设备批量注册账号互相邀请、同一 IP 下多账号绑定关系、同一手机号换绑等方式刷取佣金,而系统层面没有任何拦截。

缺口四:邀请奖励的配置粒度粗。原生的佣金比例在分销商品级别设置,按分销等级(一级/二级)区分比例;但没有“邀请注册奖励”“邀请首单奖励”“邀请多人的阶梯奖励”等更细粒度的配置能力。

1.4 二开策略定位

遵循 LikeShop 的“扩展优先”原则,邀请裂变二开采用以下策略:

  1. 不修改原生分销的佣金计算和结算逻辑,新建独立的 InviteRewardService 作为邀请奖励的扩展层
  2. 新增邀请奖励配置表,支持现金/积分/优惠券三种奖励类型的独立配置
  3. 新增邀请关系验证表,记录每次邀请行为的设备指纹、IP、手机号等风控维度
  4. 防刷判定在邀请关系绑定定时执行,判定不通过的邀请关系不建立上下级绑定、从源头阻断
二、数据库扩展设计

2.1 邀请奖励配置表

这是邀请奖励体系的核心配置表,支持按“邀请注册”“邀请首单”两个节点分别配置奖励:

sql
CREATE TABLE `ls_invite_reward_config` (
 `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
 `config_name` varchar(50) NOT NULL COMMENT '配置名称',
 `reward_node` tinyint NOT NULL DEFAULT 1 COMMENT '奖励节点:1-邀请注册,2-邀请首单,3-邀请复购',
 `inviter_reward_type` tinyint NOT NULL DEFAULT 1 COMMENT '邀请人奖励类型:1-现金,2-积分,3-优惠券,4-组合',
 `inviter_cash_amount` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '邀请人现金奖励金额',
 `inviter_integral` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '邀请人积分奖励数量',
 `inviter_coupon_ids` varchar(255) NOT NULL DEFAULT '' COMMENT '邀请人优惠券ID,逗号分隔',
 `invitee_reward_type` tinyint NOT NULL DEFAULT 1 COMMENT '被邀请人奖励类型:1-现金,2-积分,3-优惠券,4-组合',
 `invitee_cash_amount` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '被邀请人现金奖励金额',
 `invitee_integral` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '被邀请人积分奖励数量',
 `invitee_coupon_ids` varchar(255) NOT NULL DEFAULT '' COMMENT '被邀请人优惠券ID,逗号分隔',
`first_order_min_amount` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '首单最低金额门槛,0为不限',
`max_reward_per_day` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '邀请人每日最多获得奖励次数,0为不限',
`max_reward_total` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '邀请人累计最多获得奖励次数,0为不限',
`is_enable` tinyint NOT NULL DEFAULT 1 COMMENT '是否启用',
`create_time` int UNSIGNED NOT NULL DEFAULT 0,
`update_time` int UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_reward_node` (`reward_node`),
KEY `idx_enable` (`is_enable`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邀请奖励配置表';

reward_node 的设计将奖励拆分为三个节点:注册奖励(拉新激励)、首单奖励(转化激励)、复购奖励(留存激励);每个节点可以独立配置邀请人和被邀请人的奖励类型和金额。

2.2 邀请关系验证表

记录每次邀请绑定时的风控维度数据:

sql
CREATE TABLE `ls_invite_verify_log` (
  `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
  `inviter_user_id` int UNSIGNED NOT NULL COMMENT '邀请人ID',
  `invitee_user_id` int UNSIGNED NOT NULL COMMENT '被邀请人ID',
  `invitee_mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '被邀请人手机号',
  `invitee_ip` varchar(45) NOT NULL DEFAULT '' COMMENT '被邀请人IP',
  `invitee_device` varchar(128) NOT NULL DEFAULT '' COMMENT '被邀请人设备指纹',
  `inviter_ip` varchar(45) NOT NULL DEFAULT '' COMMENT '邀请人IP',
  `inviter_device` varchar(128) NOT NULL DEFAULT '' COMMENT '邀请人设备指纹',
  `verify_result` tinyint NOT NULL DEFAULT 1 COMMENT '验证结果:1-通过,2-设备重复,3-IP重复,4-手机号重复,5-其他异常',
  `verify_remark` varchar(255) NOT NULL DEFAULT '' COMMENT '验证备注',
  `create_time` int UNSIGNED NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `idx_inviter` (`inviter_user_id`),
  KEY `idx_invitee` (`invitee_user_id`),
  KEY `idx_device` (`invitee_device`),
  KEY `idx_ip` (`invitee_ip`),
  KEY `idx_mobile` (`invitee_mobile`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邀请关系验证记录表';

2.3 邀请奖励发放记录表

sql
CREATE TABLE `ls_invite_reward_log` (
  `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
  `config_id` int UNSIGNED NOT NULL COMMENT '奖励配置ID',
  `inviter_user_id` int UNSIGNED NOT NULL COMMENT '邀请人ID',
  `invitee_user_id` int UNSIGNED NOT NULL COMMENT '被邀请人ID',
  `reward_node` tinyint NOT NULL DEFAULT 1 COMMENT '奖励节点:1-注册,2-首单,3-复购',
  `reward_type` tinyint NOT NULL DEFAULT 1 COMMENT '奖励类型:1-现金,2-积分,3-优惠券',
  `reward_value` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '奖励数量/金额',
  `coupon_id` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '优惠券ID(奖励类型为券时)',
  `order_id` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '关联订单ID(首单/复购奖励时)',
  `status` tinyint NOT NULL DEFAULT 1 COMMENT '状态:1-已发放,2-已失效',
  `create_time` int UNSIGNED NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `idx_inviter_node` (`inviter_user_id`, `reward_node`),
  KEY `idx_invitee` (`invitee_user_id`),
  KEY `idx_order` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邀请奖励发放记录表';

2.4 用户邀请统计表

用于快速查询用户的邀请数据,避免每次从关系聚合:

 sql
 CREATE TABLE `ls_user_invite_stat` (
   `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
   `user_id` int UNSIGNED NOT NULL COMMENT '用户ID',
   `total_invite_count` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '累计邀请注册人数',
   `total_first_order_count` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '下级首单人数',
   `today_invite_count` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '今日邀请人数',
   `today_reward_count` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '今日已获得奖励次数',
   `total_reward_cash` decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '累计现金奖励',
   `total_reward_integral` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '累计积分奖励',
   `last_invite_time` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '最后邀请时间',
   `update_time` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '统计更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户邀请统计表';
三、一级二级邀请奖励实现

3.1 奖励链路设计

邀请奖励的触发链路为:

被邀请人注册 → 绑定邀请关系 → 触发注册奖励 → 被邀请人首单支付 → 触发首单奖励 → 被邀请人复购 → 触发复购奖励。

其中,一级奖励发放给直接邀请人,二级奖励发放给邀请人的上级;二级奖励的配置是可选开启的,如果只需要一级奖励,将二级奖励配置金额设为 0 即可。

3.2 邀请关系绑定与注册奖励

用户注册时,通过分享链接携带的邀请人ID 或邀请码绑定上下级关系;绑定成功后,根据配置发放注册奖励:

 php
 namespace app\common\service;

class InviteRewardService
{
/**
 * 用户注册时绑定邀请关系并发放注册奖励
 */
public function handleRegister($newUserId, $inviterId, $registerData)
{
    if ($inviterId <= 0 || $inviterId == $newUserId) {
        return ['bound' => false, 'reason' => '邀请人无效'];
    }

    // 1. 防刷校验
    $verifyResult = $this->verifyInviteRelation($inviterId, $newUserId, $registerData);
    if (!$verifyResult['passed']) {
        // 记录验证失败日志
        InviteVerifyLogModel::create([
            'inviter_user_id' => $inviterId,
            'invitee_user_id' => $newUserId,
            'invitee_mobile'  => $registerData['mobile'] ?? '',
            'invitee_ip'      => $registerData['ip'] ?? '',
            'invitee_device'  => $registerData['device'] ?? '',
            'verify_result'   => $verifyResult['code'],
            'verify_remark'   => $verifyResult['reason'],
            'create_time'     => time(),
        ]);
        return ['bound' => false, 'reason' => $verifyResult['reason']];
    }

    // 2. 绑定上下级关系(写入 ls_user 的 invite_user_id)
    Db::startTrans();
    try {
        UserModel::where('id', $newUserId)->update([
            'invite_user_id' => $inviterId,
            'invite_time'    => time(),
        ]);

        // 记录验证通过日志
        InviteVerifyLogModel::create([
            'inviter_user_id' => $inviterId,
            'invitee_user_id' => $newUserId,
            'invitee_mobile'  => $registerData['mobile'] ?? '',
            'invitee_ip'      => $registerData['ip'] ?? '',
            'invitee_device'  => $registerData['device'] ?? '',
            'verify_result'   => 1,
            'create_time'     => time(),
        ]);

        Db::commit();
    } catch (\Exception $e) {
        Db::rollback();
        throw $e;
    }

    // 3. 发放注册奖励
    $this->grantReward($inviterId, $newUserId, 1, null);

    // 4. 更新邀请统计
    $this->updateInviteStat($inviterId, 'register');

    return ['bound' => true, 'reason' => ''];
}

/**
 * 发放邀请奖励
 * @param int   $inviterId  邀请人ID
 * @param int   $inviteeId  被邀请人ID
 * @param int   $rewardNode 奖励节点:1-注册,2-首单,3-复购
 * @param int   $orderId    关联订单ID
 */
private function grantReward($inviterId, $inviteeId, $rewardNode, $orderId = null)
{
    // 查询该节点的奖励配置
    $config = InviteRewardConfigModel::where('reward_node', $rewardNode)
        ->where('is_enable', 1)
        ->find();
    if (!$config) return;

    // 检查邀请人的每日/累计奖励上限
    if (!$this->checkRewardLimit($inviterId, $config)) {
        return;
    }

    Db::startTrans();
    try {
        // 发放邀请人奖励
        $this->grantToUser($inviterId, $config, 'inviter', $rewardNode, $orderId);

        // 发放被邀请人奖励(仅注册和首单节点)
        if (in_array($rewardNode, [1, 2])) {
            $this->grantToUser($inviteeId, $config, 'invitee', $rewardNode, $orderId);
        }

        // 更新奖励统计
        $this->updateRewardStat($inviterId, $config, $rewardNode);

        Db::commit();
    } catch (\Exception $e) {
        Db::rollback();
        Log::error("邀请奖励发放失败 inviter={$inviterId}, invitee={$inviteeId}, node={$rewardNode}: " . $e->getMessage());
    }
}

/**
 * 给指定用户发放奖励
 */
private function grantToUser($userId, $config, $role, $rewardNode, $orderId)
{
    $prefix = ($role == 'inviter') ? 'inviter_' : 'invitee_';

    // 1. 现金奖励
    $cashAmount = $config[$prefix . 'cash_amount'];
    if ($cashAmount > 0) {
        UserModel::where('id', $userId)->inc('user_money', $cashAmount)->update();

        AccountLogModel::create([
            'sn'            => generateSn(),
            'user_id'       => $userId,
            'change_object' => 1,
            'change_type'   => 邀请奖励变动类型,
            'action'        => 1,
            'change_amount' => $cashAmount,
            'left_amount'   => UserModel::where('id', $userId)->value('user_money'),
            'remark'        => '邀请奖励-现金',
            'create_time'   => time(),
        ]);

        InviteRewardLogModel::create([
            'config_id'       => $config['id'],
            'inviter_user_id' => $userId,
            'invitee_user_id' => 0,
            'reward_node'     => $rewardNode,
            'reward_type'     => 1,
            'reward_value'    => $cashAmount,
            'order_id'        => $orderId ?: 0,
            'status'          => 1,
            'create_time'     => time(),
        ]);
    }

    // 2. 积分奖励
    $integral = $config[$prefix . 'integral'];
    if ($integral > 0) {
        IntegralService::addIntegral($userId, $integral, '邀请奖励-积分', '');

        InviteRewardLogModel::create([
            'config_id'       => $config['id'],
            'inviter_user_id' => $userId,
            'reward_node'     => $rewardNode,
            'reward_type'     => 2,
            'reward_value'    => $integral,
            'create_time'     => time(),
        ]);
    }

    // 3. 优惠券奖励
    $couponIds = $config[$prefix . 'coupon_ids'];
    if (!empty($couponIds)) {
        foreach (explode(',', $couponIds) as $couponId) {
            CouponService::sendCoupon($userId, (int)$couponId, '邀请奖励');
        }

        InviteRewardLogModel::create([
            'config_id'       => $config['id'],
            'inviter_user_id' => $userId,
            'reward_node'     => $rewardNode,
            'reward_type'     => 3,
            'reward_value'    => count(explode(',', $couponIds)),
            'create_time'     => time(),
        ]);
      }
   }
}

3.3 首单奖励的触发

被邀请人的首单支付成功后,触发首单奖励;需要校验订单是否为被邀请人的首单,以及是否满足配置中的最低金额门槛:

 php
 /**
* 订单支付成功后检查是否触发首单邀请奖励
*/
public function handleOrderPay($orderId)
{
$order = OrderModel::find($orderId);
if (!$order) return;

$userId = $order['user_id'];

// 1. 获取用户的邀请人
$user = UserModel::field('id, invite_user_id')->find($userId);
if (!$user || $user['invite_user_id'] <= 0) return;

// 2. 校验是否为该用户的首单
$orderCount = OrderModel::where('user_id', $userId)
    ->where('pay_status', 1)
    ->where('id', '<>', $orderId)
    ->count();
if ($orderCount > 0) return; // 非首单

// 3. 查询首单奖励配置,校验金额门槛
$config = InviteRewardConfigModel::where('reward_node', 2)
    ->where('is_enable', 1)->find();
if (!$config) return;

if ($config['first_order_min_amount'] > 0 
    && $order['order_amount'] < $config['first_order_min_amount']) {
    return; // 未达到金额门槛
}

// 4. 幂等校验:该被邀请人是否已发放过首单奖励
$exists = InviteRewardLogModel::where('invitee_user_id', $userId)
    ->where('reward_node', 2)
    ->where('status', 1)
    ->count();
if ($exists > 0) return;

// 5. 发放首单奖励
$this->grantReward($user['invite_user_id'], $userId, 2, $orderId);

// 6. 更新统计
$this->updateInviteStat($user['invite_user_id'], 'first_order');
}

3.4 二级奖励的实现

二级奖励的核心是在发放一级奖励的同时,向上追溯一级,找到邀请人的上级,给其发放二级奖励;二级奖励的金额通常是给一级奖励的上级(即被邀请人的二级上级),而非给被邀请人的直接上级。

 php
 /**
* 发放二级邀请奖励
*/
private function grantSecondLevelReward($inviterId, $inviteeId, $rewardNode, $orderId = null)
{
// 查询邀请人的上级(即被邀请人的二级上级)
$inviter = UserModel::field('id, invite_user_id')->find($inviterId);
if (!$inviter || $inviter['invite_user_id'] <= 0) return;

$secondInviterId = $inviter['invite_user_id'];

// 校验二级奖励配置(独立于一级配置,金额为0则不发放)
$secondConfig = InviteRewardConfigModel::where('reward_node', $rewardNode)
    ->where('is_enable', 1)
    ->where('inviter_cash_amount', '>', 0)
    ->find();
if (!$secondConfig) return;

// 发放二级奖励给二级上级
$this->grantToUser($secondInviterId, $secondConfig, 'inviter', $rewardNode, $orderId);
}

二级奖励的配置可以在邀请奖励配置表中增加独立的二级奖励字段,或者新建一条 reward_node 相同,但 config_type = 2 的配置记录来区分;推荐后者,保持配置结构的简洁。

3.5 后台配置

在“分销 → 邀请奖励设置”中增加配置页面:

配置项类型说明
奖励节点单选邀请注册 / 邀请首单 / 邀请复购
邀请人奖励类型多选现金 / 积分 / 优惠券,可组合
邀请人现金金额金额输入
邀请人积分数量数字输入
邀请人优惠券多选从优惠券列表中选择
被邀请人奖励类型多选同邀请人
被邀请人奖励内容组合配置现金/积分/优惠券
首单最低金额金额输入仅首单节点有效
每日奖励上限数字输入0 为不限
累计奖励上限数字输入0 为不限
四、拉新防作弊机制实现

4.1 三层防刷校验

在邀请关系绑定时,执行三层防刷校验:同设备校验、同 IP 校验、同手机号校验。

php
/**
* 邀请关系防刷校验
*/
private function verifyInviteRelation($inviterId, $inviteeId, $registerData)
{
$inviteeIp     = $registerData['ip'] ?? '';
$inviteeDevice = $registerData['device'] ?? '';
$inviteeMobile = $registerData['mobile'] ?? '';

// 第一层:同设备校验
// 如果被邀请人的设备指纹与邀请人或其他已绑定用户相同,判定为无效邀请
if (!empty($inviteeDevice)) {
    $deviceConflict = InviteVerifyLogModel::where('invitee_device', $inviteeDevice)
        ->where('verify_result', 1)
        ->where('invitee_user_id', '<>', $inviteeId)
        ->count();
    if ($deviceConflict > 0) {
        return ['passed' => false, 'code' => 2, 'reason' => '该设备已注册过其他账号,邀请无效'];
    }

    // 检查邀请人自身是否使用同一设备
    $inviterDevice = InviteVerifyLogModel::where('inviter_user_id', $inviterId)
        ->where('verify_result', 1)
        ->value('invitee_device');
    if ($inviterDevice && $inviterDevice == $inviteeDevice) {
        return ['passed' => false, 'code' => 2, 'reason' => '邀请人与被邀请人使用同一设备'];
    }
}

// 第二层:同IP校验
if (!empty($inviteeIp)) {
    // 检查该IP下已注册的账号数
    $ipUserCount = InviteVerifyLogModel::where('invitee_ip', $inviteeIp)
        ->where('verify_result', 1)
        ->group('invitee_user_id')
        ->count();
    $ipLimit = ConfigModel::get('invite_ip_limit', 3);
    if ($ipUserCount >= $ipLimit) {
        return ['passed' => false, 'code' => 3, 'reason' => "该IP下已注册{$ipUserCount}个账号,超过限制"];
    }

    // 检查邀请人与被邀请人是否同一IP
    $inviterLog = InviteVerifyLogModel::where('inviter_user_id', $inviterId)
        ->where('verify_result', 1)
        ->order('create_time', 'desc')
        ->find();
    if ($inviterLog && $inviterLog['invitee_ip'] == $inviteeIp) {
        return ['passed' => false, 'code' => 3, 'reason' => '邀请人与被邀请人使用同一网络'];
    }
}

// 第三层:同手机号校验
if (!empty($inviteeMobile)) {
    // 检查该手机号是否已被其他账号使用过(包括邀请人和被邀请人)
    $mobileConflict = InviteVerifyLogModel::where('invitee_mobile', $inviteeMobile)
        ->where('verify_result', 1)
        ->where('invitee_user_id', '<>', $inviteeId)
        ->count();
    if ($mobileConflict > 0) {
        return ['passed' => false, 'code' => 4, 'reason' => '该手机号已注册过其他账号'];
    }

    // 检查邀请人的手机号是否与被邀请人相同
    $inviterMobile = UserModel::where('id', $inviterId)->value('mobile');
    if ($inviterMobile && $inviterMobile == $inviteeMobile) {
        return ['passed' => false, 'code' => 4, 'reason' => '邀请人与被邀请人使用同一手机号'];
    }
}

  return ['passed' => true, 'code' => 1, 'reason' => ''];
}

4.2 设备指纹采集

设备指纹的采集在前端完成,通过 device_id 或 openid + unionid 组合生成;后端接口在接收注册请求时,从请求头中读取 X-Device-Id 字段;如果前端未传递设备指纹,则降级使用 IP + User-Agent 的哈希值作为设备标识的替代。

设备指纹的生成建议包含以下维度:设备型号、操作系统版本、屏幕分辨率、浏览器/微信版本、设备唯一标识(如 IOS 的 IDFV、AndroidID);多个维度的组合哈希可显著提高设备识别的准确性。

4.3 奖励发放时的二次校验

邀请关系绑定时已经做了防刷校验,但奖励发放时仍需二次校验,防止在绑定后到发放前的窗口期内发生异常:

php
/**
* 奖励发放前的二次校验
*/
private function checkRewardLimit($inviterId, $config)
{
// 1. 每日奖励上限
if ($config['max_reward_per_day'] > 0) {
    $todayStart = strtotime('today');
    $todayCount = InviteRewardLogModel::where('inviter_user_id', $inviterId)
        ->where('create_time', '>=', $todayStart)
        ->where('status', 1)
        ->count();
    if ($todayCount >= $config['max_reward_per_day']) {
        return false;
    }
}

// 2. 累计奖励上限
if ($config['max_reward_total'] > 0) {
    $totalCount = InviteRewardLogModel::where('inviter_user_id', $inviterId)
        ->where('status', 1)
        ->count();
    if ($totalCount >= $config['max_reward_total']) {
        return false;
    }
}

// 3. 检查邀请人的账户状态
$inviter = UserModel::field('id, status, is_distributor')->find($inviterId);
if (!$inviter || $inviter['status'] != 1) {
    return false;
}

  return true;
}

4.4 异常数据的人工审核

对于触发防刷规则但被系统放行的边缘情况(如设备指纹缺失、IP 处于共享网络),系统将这些记录标记为“待审核”状态;运营人员在后台可以查看待审核的邀请关系,确认后手动放行或拒绝:

php
/**
* 人工审核邀请关系
*/
public function auditInviteRelation($verifyLogId, $adminId, $action, $remark = '')
{
$log = InviteVerifyLogModel::find($verifyLogId);
if (!$log) throw new Exception('记录不存在');

Db::startTrans();
try {
    if ($action == 'approve') {
        // 审核通过:绑定关系并发放奖励
        InviteVerifyLogModel::where('id', $verifyLogId)->update([
            'verify_result' => 1,
            'verify_remark' => $remark,
        ]);

        // 绑定上下级关系
        UserModel::where('id', $log['invitee_user_id'])->update([
            'invite_user_id' => $log['inviter_user_id'],
            'invite_time'    => time(),
        ]);

        // 发放奖励
        $this->grantReward($log['inviter_user_id'], $log['invitee_user_id'], 1, null);
    } else {
        // 审核拒绝:不绑定关系
        InviteVerifyLogModel::where('id', $verifyLogId)->update([
            'verify_result' => 5,
            'verify_remark' => $remark,
        ]);
    }

    Db::commit();
} catch (\Exception $e) {
    Db::rollback();
    throw $e;
  }
}

4.5 后台配置

在“分销 → 邀请防刷设置”中增加:

配置项类型默认值说明
同设备校验开关开启同设备注册的账号邀请无效
同IP校验开关开启同IP下多账号邀请限制
同IP账号上限数字输入3同IP下最多允许注册的账号数
同手机号校验开关开启同手机号换绑后邀请无效
异常数据人工审核开关开启边缘情况是否进入人工审核
五、完整开发步骤总结

5.1 数据库变更清单

操作表名说明
CREATEls_invite_reward_config邀请奖励配置表
CREATEls_invite_verify_log邀请关系验证记录表
CREATEls_invite_reward_log邀请奖励发放记录表
CREATEls_user_invite_stat用户邀请统计表

5.2 后端开发清单

模块文件/类工作内容
数据层InviteRewardConfigModel奖励配置 Model
数据层InviteVerifyLogModel验证记录 Model
数据层InviteRewardLogModel奖励发放 Model
数据层UserInviteStatModel邀请统计 Model
业务层InviteRewardService邀请关系绑定、奖励发放核心逻辑
业务层InviteVerifyService防刷校验三层逻辑
业务层InviteStatService邀请统计更新和查询
事件UserRegisterEvent用户注册后触发邀请绑定和注册奖励
事件OrderPaySuccessEvent首单支付后触发首单奖励
后台接口InviteRewardController奖励配置管理、人工审核、统计查询
定时任务Crontab.php每日重置邀请统计中的“今日”计数

5.3 后台前端开发清单

页面工作内容
分销 → 邀请奖励设置按节点配置奖励类型和金额,支持一级/二级独立配置
分销 → 邀请防刷设置设备/IP/手机号校验开关和阈值配置
分销 → 邀请记录邀请关系列表(含验证状态、奖励发放状态)
分销 → 人工审核待审核的邀请关系列表,支持通过/拒绝
分销 → 邀请统计邀请人数、转化率、奖励发放总额等数据看板

5.4 开发注意事项

防刷校验的优先级。设备指纹的校验优先级最高,因为同一设备批量注册是最常见的作弊手段;IP 校验次之,手机号校验作为第三道防线;三层校验全部通过后,邀请关系才被建立。

奖励发放的幂等性。同一对邀请关系在同一奖励节点只能发放一次奖励;在发放前必须查询 ls_invite_reward_log 是否已有记录,避免因重复触发导致多次发放。

二级奖励的合规性。二级奖励的层级不得超过两级;参考行业实践,分销层级通常控制在 3 级以内(一级分销、二级分销、三级分销),且需符合 《禁止传销条例》 的要求;本方案的二级奖励指“一级邀请人的上级”,实际层级为 2 级,合规性可控。

统计数据的实时性。 ls_user_invite_stat 表的“今日邀请人数”和“今日已获得奖励次数”需要每日凌晨重置;建议在定时任务中执行重置操作,并在每次邀请和奖励发放时实时更新。

兼容性。新增表均为独立表,不修改原生分销的 ls_user 表结构和佣金计算逻辑;邀请奖励的发放不影响原生分销佣金的计算和结算,两者是独立的激励体系;防刷校验仅作用于邀请奖励的绑定环节,不影响原生的分销关系绑定。

0
0
0
0
评论
未登录
暂无评论