level !== Agent::LEVEL_ONE) { throw new \RuntimeException('二级代理不能继续新增下级代理'); } if ($parentId && $parentId !== (int) $scopeAgent->id) { throw new \RuntimeException('一级代理只能新增自己的直属二级代理'); } $parentId = (int) $scopeAgent->id; } elseif ($parentId) { throw new \RuntimeException('总后台只能新增一级代理'); } $parent = $parentId ? Agent::query()->lockForUpdate()->findOrFail($parentId) : null; if ($scopeAgent && $parent && (int) $parent->status !== Agent::STATUS_ENABLED) { throw new \RuntimeException('不能在已禁用代理下新增账号'); } if ($parent) { $this->assertRatesWithinParent($parent, $data); } $level = $parent ? ((int) $parent->level + 1) : Agent::LEVEL_ONE; if ($level > Agent::MAX_LEVEL) { throw new \RuntimeException('代理层级最多为二级'); } $agent = Agent::query()->create([ 'parent_id' => $parent?->id, 'path' => '/', 'level' => $level, 'username' => trim((string) $data['username']), 'password' => Hash::make((string) $data['password']), 'withdraw_password' => empty($data['withdraw_password']) ? '' : Hash::make((string) $data['withdraw_password']), 'real_name' => trim((string) ($data['real_name'] ?? '')), 'invitation_code' => $this->invitationCode(), 'deposit_fee_rate' => (string) ($data['deposit_fee_rate'] ?? 0), 'withdraw_fee_rate' => (string) ($data['withdraw_fee_rate'] ?? 0), 'flow_commission_rate' => (string) ($data['flow_commission_rate'] ?? 0), 'profit_commission_rate' => (string) ($data['profit_commission_rate'] ?? 0), 'status' => (int) ($data['status'] ?? Agent::STATUS_ENABLED), 'created_by' => $createdBy, ]); $agent->path = ($parent ? rtrim((string) $parent->path, '/') . '/' : '/') . $agent->id . '/'; $agent->save(); AgentCommissionRule::query()->create([ 'agent_id' => $agent->id, 'flow_rate' => $agent->flow_commission_rate, 'profit_rate' => $agent->profit_commission_rate, 'effective_from' => now()->toDateString(), 'status' => 1, 'created_by' => $createdBy, ]); return $agent->fresh(); }); } public function update(Agent $agent, array $data, ?Agent $scopeAgent = null): Agent { return DB::transaction(function () use ($agent, $data, $scopeAgent): Agent { $agent = Agent::query()->lockForUpdate()->findOrFail($agent->id); if ($scopeAgent) { $this->assertVisible($scopeAgent, (int) $agent->id); if ($agent->parent) { $this->assertRatesWithinParent($agent->parent, $data); } $this->assertRatesCoverChildren($agent, $data); } elseif (array_key_exists('parent_id', $data)) { $this->move($agent, $data['parent_id'] === null ? null : (int) $data['parent_id']); } if (array_key_exists('real_name', $data)) { $agent->real_name = trim((string) ($data['real_name'] ?? '')); } foreach (['username', 'status', 'deposit_fee_rate', 'withdraw_fee_rate', 'flow_commission_rate', 'profit_commission_rate'] as $field) { if (array_key_exists($field, $data) && $data[$field] !== null) { $agent->{$field} = $data[$field]; } } if (!empty($data['password'])) { $agent->password = Hash::make((string) $data['password']); } if (!empty($data['withdraw_password'])) { $agent->withdraw_password = Hash::make((string) $data['withdraw_password']); } $agent->save(); return $agent->fresh(); }); } public function assertVisible(Agent $root, int $targetId): void { if ((int) $root->id === $targetId) { return; } $visible = Agent::query()->whereKey($targetId)->where('path', 'like', rtrim((string) $root->path, '/') . '/%')->exists(); if (!$visible) { throw new \RuntimeException('无权访问该代理'); } } private function invitationCode(): string { do { $code = strtoupper(Str::random(10)); } while (Agent::query()->where('invitation_code', $code)->exists()); return $code; } private function assertRatesWithinParent(Agent $parent, array $data): void { foreach (['flow_commission_rate', 'profit_commission_rate'] as $field) { if (array_key_exists($field, $data) && bccomp((string) $data[$field], (string) $parent->{$field}, 4) > 0) { throw new \RuntimeException('下级代理佣金比例不能高于上级'); } } } private function assertRatesCoverChildren(Agent $agent, array $data): void { foreach (['flow_commission_rate', 'profit_commission_rate'] as $field) { if (!array_key_exists($field, $data)) { continue; } $maxChildRate = Agent::query()->where('parent_id', $agent->id)->max($field) ?? 0; if (bccomp((string) $data[$field], (string) $maxChildRate, 4) < 0) { throw new \RuntimeException('代理佣金比例不能低于现有下级比例'); } } } private function move(Agent $agent, ?int $parentId): void { if ($parentId === (int) $agent->id) { throw new \RuntimeException('上级代理不能是自己'); } $currentParentId = $agent->parent_id === null ? null : (int) $agent->parent_id; if ($parentId === $currentParentId) { return; } $parent = $parentId ? Agent::query()->lockForUpdate()->findOrFail($parentId) : null; if ($parent && str_starts_with((string) $parent->path, (string) $agent->path)) { throw new \RuntimeException('不能把代理移动到自己的下级'); } if ($parent && (int) $parent->level !== Agent::LEVEL_ONE) { throw new \RuntimeException('二级代理不能作为上级代理'); } if ($parent) { $this->assertRatesWithinParent($parent, [ 'flow_commission_rate' => $agent->flow_commission_rate, 'profit_commission_rate' => $agent->profit_commission_rate, ]); } $oldPath = (string) $agent->path; $oldLevel = (int) $agent->level; $newLevel = $parent ? ((int) $parent->level + 1) : 1; $newPath = ($parent ? rtrim((string) $parent->path, '/') . '/' : '/') . $agent->id . '/'; $descendants = Agent::query()->where('path', 'like', rtrim($oldPath, '/') . '/%')->lockForUpdate()->get(); $newMaxLevel = $descendants->max(fn(Agent $descendant) => (int) $descendant->level + $newLevel - $oldLevel ) ?? $newLevel; if ($newLevel > Agent::MAX_LEVEL || $newMaxLevel > Agent::MAX_LEVEL) { throw new \RuntimeException('移动后会产生三级代理,操作已拒绝'); } $agent->parent_id = $parent?->id; $agent->path = $newPath; $agent->level = $newLevel; foreach ($descendants as $descendant) { $descendant->path = $newPath . substr((string) $descendant->path, strlen($oldPath)); $descendant->level = max(1, (int) $descendant->level + $newLevel - $oldLevel); $descendant->save(); } } }