| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- <?php
- namespace App\Services;
- use App\Models\Agent\Agent;
- use App\Models\User;
- use App\Services\Sharing\ShareRelations;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Schema;
- /**
- * 后台会员列表:给没有上级(无合营代理、无邀请人)的会员补绑上级。
- * type=agent:agent_username 为代理账号(agents.username),写 users.agent_id;
- * type=member:superior_id 为上级会员的会员 ID(users.member_id),写 users.agent_user_code 并挂到分享关系树。
- * 上级只有两种:会员邀请码,或合营代理(绑定域名注册)。入股代理不发展会员,不能当作上级。
- * 已有合营代理、能对上的邀请人,或分享关系树里已有上级的会员不能改绑。
- */
- class MemberSuperiorService
- {
- public const TYPE_AGENT = 'agent';
- public const TYPE_MEMBER = 'member';
- public function bind(string $memberId, string $type, string $target): array
- {
- return DB::transaction(function () use ($memberId, $type, $target) {
- // 与注册、分享关系绑定同一把锁,先锁关系再锁会员,避免死锁和并发重复绑定
- ShareRelations::lock();
- $user = User::query()->where('member_id', $memberId)->lockForUpdate()->first();
- if (!$user) throw new \RuntimeException('会员不存在');
- if ((int) $user->from === 2) throw new \RuntimeException('试玩会员不能绑定上级');
- if ($this->hasSuperior($user)) throw new \RuntimeException('会员已有上级,不能修改');
- if ($type === self::TYPE_AGENT) {
- $agent = Agent::query()->where('username', $target)->lockForUpdate()->first();
- if (!$agent) throw new \RuntimeException('代理不存在');
- if (!$agent->isJoint()) throw new \RuntimeException('只能绑定合营代理');
- if ((int) $agent->status !== Agent::STATUS_ENABLED) throw new \RuntimeException('代理已禁用');
- $user->agent_id = $agent->id;
- $user->agent_user_code = null;
- $user->save();
- return ['member_id' => (string) $user->member_id, 'type' => $type, 'agent_id' => (int) $agent->id, 'account' => (string) $agent->username];
- }
- if ($type !== self::TYPE_MEMBER) throw new \RuntimeException('绑定类型错误');
- $parent = User::query()->where('member_id', $target)->first();
- if (!$parent) throw new \RuntimeException('上级会员不存在');
- if ((int) $parent->id === (int) $user->id) throw new \RuntimeException('不能绑定自己');
- if ((int) $parent->from === 2) throw new \RuntimeException('试玩会员不能作为上级');
- $parentCode = $this->code($parent);
- if ($parentCode === '') throw new \RuntimeException('上级会员没有邀请码');
- $ownCode = $this->code($user);
- if ($ownCode === '') throw new \RuntimeException('会员邀请码为空,请先人工核对');
- // 校验上级关系已初始化、不能绑定自己的下级(成环),并写入分享关系树
- (new ShareRelations())->bind((int) $user->id, (int) $parent->id, $ownCode);
- $user->agent_id = null;
- $user->agent_user_code = $parentCode;
- $user->save();
- return ['member_id' => (string) $user->member_id, 'type' => $type, 'superior_member_id' => (string) $parent->member_id,
- 'account' => (string) ($parent->account ?: $parent->first_name)];
- });
- }
- public function hasSuperior(User $user): bool
- {
- return self::hasJointAgent($user) || self::hasMemberSuperior($user);
- }
- public static function hasJointAgent(User $user): bool
- {
- return $user->agent_id !== null && Agent::query()->whereKey($user->agent_id)
- ->where('type', Agent::TYPE_JOINT)->exists();
- }
- public static function hasMemberSuperior(User $user): bool
- {
- return array_key_exists($user->id, self::memberParentIds([$user]));
- }
- /** 已固化的上级优先。历史邀请码匹配异常时记录空值,保留绑定限制但不展示上级。 */
- public static function memberParentIds(iterable $users): array
- {
- $userIds = $legacyCodes = [];
- foreach ($users as $user) {
- $userIds[] = $user->id;
- $code = trim((string) $user->agent_user_code);
- if ($code !== '' && $code !== '0') $legacyCodes[$user->id] = $code;
- }
- if ($userIds === []) return [];
- $fixedParents = Schema::hasTable('share_members')
- ? DB::table('share_members')->whereIn('user_id', $userIds)->whereNotNull('parent_user_id')
- ->pluck('parent_user_id', 'user_id')->all()
- : [];
- $legacyCodes = array_diff_key($legacyCodes, $fixedParents);
- if ($legacyCodes === []) return $fixedParents;
- $codes = array_unique(array_merge($legacyCodes, array_map('strtoupper', $legacyCodes)));
- $matches = User::query()->whereIn('user_code', $codes)->get(['id', 'user_code', 'from'])
- ->groupBy(fn (User $inviter) => strtoupper(trim((string) $inviter->user_code)));
- foreach ($legacyCodes as $userId => $code) {
- $candidates = $matches->get(strtoupper($code));
- if (!$candidates) continue;
- $inviter = $candidates->count() === 1 ? $candidates->first() : null;
- $fixedParents[$userId] = $inviter && (int) $inviter->id !== (int) $userId && (int) $inviter->from !== 2
- ? $inviter->id : null;
- }
- return $fixedParents;
- }
- /** 会员自己的邀请码:分享关系表固化的优先,其次 users.user_code。 */
- private function code(User $user): string
- {
- $code = trim((string) DB::table('share_members')->where('user_id', $user->id)->value('invitation_code'));
- if ($code === '' || $code === '0') $code = trim((string) ($user->user_code ?? ''));
- return $code === '0' ? '' : $code;
- }
- }
|