190 lines
5.2 KiB
PHP
190 lines
5.2 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace app\admin\model;
|
||
|
|
|
||
|
|
use think\Model;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 域名管理模型
|
||
|
|
*/
|
||
|
|
class Domain extends Model
|
||
|
|
{
|
||
|
|
/** @var string 中国域名后缀黑名单 */
|
||
|
|
public const CHINA_SUFFIX_BLACKLIST = [
|
||
|
|
'cn', 'com.cn', 'net.cn', 'org.cn', 'gov.cn', 'edu.cn', 'ac.cn', 'mil.cn',
|
||
|
|
];
|
||
|
|
|
||
|
|
protected $name = 'domain';
|
||
|
|
protected $autoWriteTimestamp = 'integer';
|
||
|
|
protected $createTime = 'createtime';
|
||
|
|
protected $updateTime = 'updatetime';
|
||
|
|
protected $deleteTime = false;
|
||
|
|
|
||
|
|
protected $append = [
|
||
|
|
'zone_status_text',
|
||
|
|
'ns_status_text',
|
||
|
|
'dns_status_text',
|
||
|
|
];
|
||
|
|
|
||
|
|
public function getZoneStatusList(): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'pending' => __('Zone pending'),
|
||
|
|
'active' => __('Zone active'),
|
||
|
|
'failed' => __('Zone failed'),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getNsStatusList(): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'pending' => __('NS pending'),
|
||
|
|
'verified' => __('NS verified'),
|
||
|
|
'failed' => __('NS failed'),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getDnsStatusList(): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'pending' => __('DNS pending'),
|
||
|
|
'created' => __('DNS created'),
|
||
|
|
'failed' => __('DNS failed'),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getZoneStatusTextAttr($value, $data): string
|
||
|
|
{
|
||
|
|
$value = $value ?: ($data['zone_status'] ?? '');
|
||
|
|
$list = $this->getZoneStatusList();
|
||
|
|
return $list[$value] ?? '';
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getNsStatusTextAttr($value, $data): string
|
||
|
|
{
|
||
|
|
$value = $value ?: ($data['ns_status'] ?? '');
|
||
|
|
$list = $this->getNsStatusList();
|
||
|
|
return $list[$value] ?? '';
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getDnsStatusTextAttr($value, $data): string
|
||
|
|
{
|
||
|
|
$value = $value ?: ($data['dns_status'] ?? '');
|
||
|
|
$list = $this->getDnsStatusList();
|
||
|
|
return $list[$value] ?? '';
|
||
|
|
}
|
||
|
|
|
||
|
|
public function setNameserversAttr($value): string
|
||
|
|
{
|
||
|
|
if (is_array($value)) {
|
||
|
|
return json_encode(array_values($value), JSON_UNESCAPED_UNICODE);
|
||
|
|
}
|
||
|
|
return (string)$value;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return array<int, string>
|
||
|
|
*/
|
||
|
|
public function getNameserversArray(): array
|
||
|
|
{
|
||
|
|
$value = $this->getAttr('nameservers');
|
||
|
|
if (is_array($value)) {
|
||
|
|
return $value;
|
||
|
|
}
|
||
|
|
if (is_string($value) && $value !== '') {
|
||
|
|
$decoded = json_decode($value, true);
|
||
|
|
return is_array($decoded) ? $decoded : [];
|
||
|
|
}
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function normalizeDomain(string $domain): string
|
||
|
|
{
|
||
|
|
return strtolower(trim($domain));
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function isValidRootDomain(string $domain): bool
|
||
|
|
{
|
||
|
|
$domain = self::normalizeDomain($domain);
|
||
|
|
if ($domain === '' || strlen($domain) > 253) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (strpos($domain, 'www.') === 0) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (!preg_match('/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i', $domain)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (self::isChinaSuffix($domain)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
$label = self::getDomainLabelWithoutSuffix($domain);
|
||
|
|
if ($label === '' || strpos($label, '.') !== false) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function isChinaSuffix(string $domain): bool
|
||
|
|
{
|
||
|
|
$domain = self::normalizeDomain($domain);
|
||
|
|
foreach (self::CHINA_SUFFIX_BLACKLIST as $suffix) {
|
||
|
|
if ($domain === $suffix || self::endsWith($domain, '.' . $suffix)) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function getMatchedSuffix(string $domain): string
|
||
|
|
{
|
||
|
|
$domain = self::normalizeDomain($domain);
|
||
|
|
$suffixes = array_merge(self::CHINA_SUFFIX_BLACKLIST, ['co.uk', 'com', 'net', 'org', 'io', 'co', 'me', 'info', 'biz']);
|
||
|
|
usort($suffixes, function ($a, $b) {
|
||
|
|
return strlen($b) - strlen($a);
|
||
|
|
});
|
||
|
|
foreach ($suffixes as $suffix) {
|
||
|
|
if ($domain === $suffix || self::endsWith($domain, '.' . $suffix)) {
|
||
|
|
return $suffix;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function getDomainLabelWithoutSuffix(string $domain): string
|
||
|
|
{
|
||
|
|
$domain = self::normalizeDomain($domain);
|
||
|
|
$suffix = self::getMatchedSuffix($domain);
|
||
|
|
if ($suffix === '') {
|
||
|
|
$pos = strrpos($domain, '.');
|
||
|
|
return $pos === false ? $domain : substr($domain, 0, $pos);
|
||
|
|
}
|
||
|
|
$suffixWithDot = '.' . $suffix;
|
||
|
|
if (self::endsWith($domain, $suffixWithDot)) {
|
||
|
|
return substr($domain, 0, -strlen($suffixWithDot));
|
||
|
|
}
|
||
|
|
return $domain;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function mapCloudflareZoneStatus(string $status): string
|
||
|
|
{
|
||
|
|
if ($status === 'active') {
|
||
|
|
return 'active';
|
||
|
|
}
|
||
|
|
if (in_array($status, ['pending', 'initializing'], true)) {
|
||
|
|
return 'pending';
|
||
|
|
}
|
||
|
|
return 'failed';
|
||
|
|
}
|
||
|
|
|
||
|
|
private static function endsWith(string $haystack, string $needle): bool
|
||
|
|
{
|
||
|
|
if ($needle === '') {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return substr($haystack, -strlen($needle)) === $needle;
|
||
|
|
}
|
||
|
|
}
|