Files
portal-api/app/Jobs/DnsManager/SyncDnsFromPlesk.php
baodev0andClaude Opus 5.5 c5115b52dd feat: nạp tiền thủ công mã 30 phút, gia hạn trừ số dư, đồng bộ domain từ Plesk
- Nạp tiền: mã thanh toán sống 30 phút (ManualDepositDraft), khôi phục/huỷ mã, lệnh dọn mã hết hạn; total_pending chỉ tính giao dịch đã nộp chứng từ, admin duyệt/từ chối/xoá giao dịch
- Gia hạn dịch vụ: phát hành invoice gia hạn trên order gốc và trừ số dư ngay
- Đồng bộ domain từ Plesk theo server, ServiceOrderRecorder tạo order/invoice cho dịch vụ admin dựng
- Danh sách dịch vụ trả field theo loại (service_plan, dns_records_count), đếm dịch vụ theo loại

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 23:07:51 +07:00

192 lines
6.4 KiB
PHP

<?php
namespace App\Jobs\DnsManager;
use App\Events\DnsSyncCompleted;
use App\Models\DnsDomain;
use App\Models\DnsServer;
use App\Repositories\DnsManager\Domain\DnsDomainRepositoryInterface;
use App\Repositories\DnsManager\Record\DnsRecordRepositoryInterface;
use App\Services\Plesk\PleskDomainService;
use App\Services\ServerService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class SyncDnsFromPlesk implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private const PLESK_RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'TXT', 'MX', 'NS', 'SRV'];
public function __construct(private readonly ?int $serverId = null)
{
}
public function handle(
DnsDomainRepositoryInterface $domainRepository,
DnsRecordRepositoryInterface $recordRepository,
ServerService $serverService,
PleskDomainService $pleskDomainService,
): void {
$synced = 0;
$imported = 0;
$failedDomains = [];
if ($this->serverId !== null) {
$server = DnsServer::find($this->serverId);
$pleskDomains = $server ? $pleskDomainService->listDomains($server) : false;
if ($pleskDomains === false) {
Log::error('DNS sync: không lấy được danh sách domain từ Plesk', [
'server_id' => $this->serverId,
'error' => $pleskDomainService->getLastError(),
]);
event(new DnsSyncCompleted(0, ['server #' . $this->serverId], 0));
return;
}
$imported = $this->importDomains($pleskDomains);
}
$domains = $domainRepository->model
->notCancelled()
->when(
$this->serverId !== null,
fn ($query) => $query->where('dns_manager_server_id', $this->serverId),
fn ($query) => $query->whereNotNull('dns_manager_server_id'),
)
->get();
foreach ($domains as $domain) {
if (!$serverService->addServerById($domain->dns_manager_server_id)) {
$failedDomains[] = $domain->domain_name;
continue;
}
$result = $serverService->listRecords($domain->domain_name);
if ($result['error']) {
$failedDomains[] = $domain->domain_name;
continue;
}
foreach ($result['data'] as $item) {
$type = strtoupper($item['type'] ?? '');
if (!in_array($type, self::PLESK_RECORD_TYPES, true) || empty($item['id'])) {
continue;
}
$recordRepository->model->updateOrCreate(
[
'dns_manager_domain_id' => $domain->id,
'plesk_id' => $item['id'],
],
$this->mapPleskRecord($item, $type, $domain->domain_name)
);
$synced++;
}
}
Log::info('DNS sync from Plesk finished', [
'server_id' => $this->serverId,
'imported' => $imported,
'synced' => $synced,
'failed_domains' => $failedDomains,
]);
event(new DnsSyncCompleted($synced, $failedDomains, $imported));
}
/**
* @param array<int, array<string, mixed>> $pleskDomains
*/
private function importDomains(array $pleskDomains): int
{
$imported = 0;
foreach ($pleskDomains as $item) {
$name = strtolower(trim((string) ($item['name'] ?? '')));
if ($name === '' || empty($item['id']) || !empty($item['base_domain_id'])) {
continue;
}
$domain = DnsDomain::whereRaw('LOWER(domain_name) = ?', [$name])->first();
if (!$domain) {
DnsDomain::create([
'domain_name' => $name,
'dns_manager_server_id' => $this->serverId,
'plesk_id' => $item['id'],
'is_created' => DnsDomain::STATUS_CREATED,
]);
$imported++;
continue;
}
$sameServer = in_array((int) $domain->dns_manager_server_id, [0, $this->serverId], true);
if ($domain->plesk_id || !$sameServer || (int) $domain->is_created === DnsDomain::STATUS_CANCELLED) {
continue;
}
$domain->update([
'dns_manager_server_id' => $this->serverId,
'plesk_id' => $item['id'],
'is_created' => (int) $domain->is_created === DnsDomain::STATUS_PENDING
? DnsDomain::STATUS_CREATED
: $domain->is_created,
]);
$imported++;
}
return $imported;
}
private function mapPleskRecord(array $item, string $type, string $domainName): array
{
$host = rtrim($item['host'] ?? '', '.');
$domainName = rtrim($domainName, '.');
$name = $host === $domainName
? '@'
: preg_replace('/\.' . preg_quote($domainName, '/') . '$/', '', $host);
$opt = trim((string) ($item['opt'] ?? ''));
$priority = null;
$weight = null;
$port = null;
$serviceName = null;
$protocol = null;
if ($type === 'SRV') {
[$priority, $weight, $port] = array_pad(preg_split('/\s+/', $opt), 3, null);
if (preg_match('/^_([^.]+)\._([^.]+)\./', $name, $m)) {
$serviceName = '_' . $m[1];
$protocol = '_' . $m[2];
}
} elseif ($type === 'MX') {
$priority = is_numeric($opt) ? $opt : null;
}
return [
'type' => $type,
'type_valid' => $type,
'name' => $name,
'value' => $item['value'] ?? '',
'ttl' => $item['ttl'] ?? 300,
'priority' => $priority,
'weight' => $weight,
'port' => $port,
'service_name' => $serviceName,
'protocol' => $protocol,
];
}
}