| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- <?php
- namespace App\Services\Agent;
- use App\Models\Agent\Agent;
- use App\Models\Agent\AgentDomain;
- use App\Models\Agent\AgentDomainPool;
- use Illuminate\Support\Facades\DB;
- /**
- * 代理域名池(总后台「代理域名管理」):
- * - 批量添加可用域名,按主机名去重;
- * - 列表显示使用情况:绑定到哪个代理、PC 还是 H5(按 host 和 agent_domains 匹配);
- * - 使用中的域名不能删除;停用的域名不能再绑定(已绑定的不受影响,需要先到代理列表解绑);
- * - 代理列表直接输入并绑定的域名会自动登记到域名池(AgentDomainService::save)。
- */
- class AgentDomainPoolService
- {
- private const MAX_BATCH = 100;
- public function __construct(private AgentDomainService $domains)
- {
- }
- public function paginate(array $params): array
- {
- $page = max(1, (int) ($params['page'] ?? 1));
- $limit = min(100, max(1, (int) ($params['limit'] ?? 20)));
- $query = AgentDomainPool::query();
- if (!empty($params['keyword'])) $query->where('domain', 'like', '%' . trim((string) $params['keyword']) . '%');
- if (isset($params['status']) && $params['status'] !== '') $query->where('status', (int) $params['status']);
- $bound = fn ($q) => $q->select(DB::raw(1))->from('agent_domains')->whereColumn('agent_domains.host', 'agent_domain_pool.host');
- if (($params['usage'] ?? '') === 'in_use') $query->whereExists($bound);
- if (($params['usage'] ?? '') === 'available') $query->whereNotExists($bound);
- if (!empty($params['agent_id']) || !empty($params['agent_username'])) {
- $query->whereIn('host', AgentDomain::query()->select('host')->whereIn('agent_id', function ($q) use ($params) {
- $q->select('id')->from('agents');
- if (!empty($params['agent_id'])) $q->where('id', (int) $params['agent_id']);
- if (!empty($params['agent_username'])) $q->where('username', 'like', '%' . trim((string) $params['agent_username']) . '%');
- }));
- }
- $total = (clone $query)->count();
- $rows = $query->orderByDesc('id')->forPage($page, $limit)->get();
- $bindings = $this->bindings($rows->pluck('host')->all());
- $list = $rows->map(fn (AgentDomainPool $row) => $this->present($row, $bindings[$row->host] ?? []))->all();
- return compact('total', 'page', 'limit', 'list') + ['summary' => $this->summary()];
- }
- /** 绑定代理时的下拉:启用且未被使用的域名。 */
- public function options(): array
- {
- $query = AgentDomainPool::query()->where('status', 1)
- ->whereNotExists(fn ($q) => $q->select(DB::raw(1))->from('agent_domains')->whereColumn('agent_domains.host', 'agent_domain_pool.host'));
- return $query->orderByDesc('id')->limit(500)->get(['id', 'domain', 'remark'])
- ->map(fn (AgentDomainPool $row) => ['id' => $row->id, 'domain' => $row->domain, 'remark' => $row->remark])->all();
- }
- /** 批量添加:每行一个域名;已存在或重复的跳过并说明原因。 */
- public function create(array $domains, string $remark, ?int $adminId): array
- {
- $domains = array_values(array_filter(array_map(fn ($d) => trim((string) $d), $domains), fn ($d) => $d !== ''));
- if ($domains === []) throw new \InvalidArgumentException('请输入域名');
- if (count($domains) > self::MAX_BATCH) throw new \InvalidArgumentException('一次最多添加 ' . self::MAX_BATCH . ' 个域名');
- $created = [];
- $skipped = [];
- DB::transaction(function () use ($domains, $remark, $adminId, &$created, &$skipped) {
- $seen = [];
- foreach ($domains as $domain) {
- try {
- $value = $this->domains->normalize($domain);
- } catch (\InvalidArgumentException $e) {
- $skipped[] = ['domain' => $domain, 'reason' => '域名格式错误'];
- continue;
- }
- $host = $this->domains->host($value);
- if (isset($seen[$host])) { $skipped[] = ['domain' => $domain, 'reason' => '本次输入重复']; continue; }
- $seen[$host] = true;
- if (AgentDomainPool::query()->where('host', $host)->lockForUpdate()->exists()) {
- $skipped[] = ['domain' => $domain, 'reason' => '域名已存在']; continue;
- }
- $created[] = AgentDomainPool::query()->create(['domain' => $value, 'host' => $host, 'type' => '',
- 'status' => 1, 'remark' => $remark, 'created_by' => $adminId])->id;
- }
- });
- return ['created' => count($created), 'skipped' => $skipped];
- }
- public function update(int $id, array $data): array
- {
- return DB::transaction(function () use ($id, $data) {
- $row = AgentDomainPool::query()->lockForUpdate()->findOrFail($id);
- if (array_key_exists('domain', $data) && $data['domain'] !== null && trim((string) $data['domain']) !== $row->domain) {
- $value = $this->domains->normalize((string) $data['domain']);
- $host = $this->domains->host($value);
- if ($host !== $row->host) {
- if ($this->inUse($row->host)) throw new \RuntimeException('域名使用中,不能修改为其他域名,请先到代理列表解绑');
- if (AgentDomainPool::query()->where('host', $host)->whereKeyNot($row->id)->exists()) throw new \RuntimeException('域名已存在');
- }
- $row->domain = $value;
- $row->host = $host;
- }
- foreach (['remark'] as $field) {
- if (array_key_exists($field, $data) && $data[$field] !== null) $row->{$field} = (string) $data[$field];
- }
- if (array_key_exists('status', $data) && $data['status'] !== null) $row->status = (int) $data['status'];
- $row->save();
- return $this->present($row, $this->bindings([$row->host])[$row->host] ?? []);
- });
- }
- public function delete(int $id): void
- {
- DB::transaction(function () use ($id) {
- $row = AgentDomainPool::query()->lockForUpdate()->findOrFail($id);
- if ($this->inUse($row->host)) throw new \RuntimeException('域名使用中,不能删除,请先到代理列表解绑');
- $row->delete();
- });
- }
- /** 代理列表绑定域名前调用:停用的不能绑定;没登记的自动登记。调用方已在事务内。 */
- public function register(string $domain, string $host, ?int $adminId): void
- {
- $row = AgentDomainPool::query()->where('host', $host)->lockForUpdate()->first();
- if ($row) {
- if ((int) $row->status !== 1) throw new \RuntimeException('该域名已停用,不能绑定');
- return;
- }
- AgentDomainPool::query()->create(['domain' => $domain, 'host' => $host, 'type' => '', 'status' => 1,
- 'remark' => '代理列表绑定时自动登记', 'created_by' => $adminId]);
- }
- private function inUse(string $host): bool
- {
- return AgentDomain::query()->where('host', $host)->exists();
- }
- /** host => [绑定信息](一个主机名只属于一个代理,数组里最多一项)。 */
- private function bindings(array $hosts): array
- {
- if ($hosts === []) return [];
- $agents = [];
- $result = [];
- $rows = AgentDomain::query()->whereIn('host', $hosts)->orderBy('id')->get();
- foreach (Agent::query()->whereIn('id', $rows->pluck('agent_id')->unique())->get(['id', 'username', 'real_name', 'type', 'status']) as $agent) {
- $agents[$agent->id] = $agent;
- }
- foreach ($rows as $row) {
- $agent = $agents[$row->agent_id] ?? null;
- $result[$row->host][] = [
- 'agent_id' => (int) $row->agent_id,
- 'agent_username' => $agent?->username ?? '',
- 'agent_real_name' => $agent?->real_name ?? '',
- 'agent_type' => $agent ? (int) $agent->type : null,
- 'agent_type_text' => $agent?->type_text ?? '',
- 'agent_status' => $agent ? (int) $agent->status : null,
- 'bound_domain' => $row->domain,
- 'bound_at' => $row->created_at ? (string) $row->created_at : null,
- ];
- }
- return $result;
- }
- private function present(AgentDomainPool $row, array $bindings): array
- {
- $inUse = $bindings !== [];
- return [
- 'id' => (int) $row->id, 'domain' => $row->domain, 'host' => $row->host,
- 'status' => (int) $row->status, 'status_text' => (int) $row->status === 1 ? '启用' : '停用',
- 'remark' => $row->remark, 'in_use' => $inUse, 'usage_text' => $inUse ? '使用中' : ((int) $row->status === 1 ? '可用' : '停用'),
- 'bindings' => $bindings, 'created_by' => $row->created_by,
- 'created_at' => $row->created_at ? (string) $row->created_at : null, 'updated_at' => $row->updated_at ? (string) $row->updated_at : null,
- ];
- }
- private function summary(): array
- {
- $bound = fn ($q) => $q->select(DB::raw(1))->from('agent_domains')->whereColumn('agent_domains.host', 'agent_domain_pool.host');
- $total = AgentDomainPool::query()->count();
- $inUse = AgentDomainPool::query()->whereExists($bound)->count();
- $disabled = AgentDomainPool::query()->where('status', 0)->whereNotExists($bound)->count();
- return ['total' => $total, 'in_use' => $inUse, 'available' => $total - $inUse - $disabled, 'disabled' => $disabled];
- }
- }
|