diff --git a/app/controller/Domain.php b/app/controller/Domain.php index 8abac0b..159d667 100644 --- a/app/controller/Domain.php +++ b/app/controller/Domain.php @@ -430,7 +430,7 @@ class Domain extends BaseController } $dnstype = Db::name('account')->where('id', $drow['aid'])->value('type'); - if ($dnstype == 'baidu' || $dnstype == 'namesilo') { + if (DnsHelper::$dns_config[$dnstype]['page']) { return json($domainRecords['list']); } @@ -487,7 +487,7 @@ class Domain extends BaseController $dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']); $recordid = $dns->addDomainRecord($name, $type, $value, $line, $ttl, $mx, $weight, $remark); if ($recordid) { - $this->add_log($drow['name'], '添加解析', $type . '记录 ' . $name . ' ' . $value . ' (线路:' . $line . ' TTL:' . $ttl . ')'); + $this->add_log($drow['name'], '添加解析', $name.' ['.$type.'] '.$value.' (线路:'.$line.' TTL:'.$ttl.')'); return json(['code' => 0, 'msg' => '添加解析记录成功!']); } else { return json(['code' => -1, 'msg' => '添加解析记录失败,' . $dns->getError()]); @@ -513,6 +513,9 @@ class Domain extends BaseController $mx = input('post.mx/d', 1); $remark = input('post.remark', null, 'trim'); + $recordinfo = input('post.recordinfo', null, 'trim'); + $recordinfo = json_decode($recordinfo, true); + if (empty($recordid) || empty($name) || empty($type) || empty($value)) { return json(['code' => -1, 'msg' => '参数不能为空']); } @@ -520,7 +523,11 @@ class Domain extends BaseController $dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']); $recordid = $dns->updateDomainRecord($recordid, $name, $type, $value, $line, $ttl, $mx, $weight, $remark); if ($recordid) { - $this->add_log($drow['name'], '修改解析', $type . '记录 ' . $name . ' ' . $value . ' (线路:' . $line . ' TTL:' . $ttl . ')'); + if ($recordinfo['Name'] != $name || $recordinfo['Type'] != $type || $recordinfo['Value'] != $value) { + $this->add_log($drow['name'], '修改解析', $recordinfo['Name'].' ['.$recordinfo['Type'].'] '.$recordinfo['Value'].' → '.$name.' ['.$type.'] '.$value.' (线路:'.$line.' TTL:'.$ttl.')'); + } elseif($recordinfo['Line'] != $line || $recordinfo['TTL'] != $ttl) { + $this->add_log($drow['name'], '修改解析', $name.' ['.$type.'] '.$value.' (线路:'.$line.' TTL:'.$ttl.')'); + } return json(['code' => 0, 'msg' => '修改解析记录成功!']); } else { return json(['code' => -1, 'msg' => '修改解析记录失败,' . $dns->getError()]); @@ -537,6 +544,8 @@ class Domain extends BaseController if (!checkPermission(0, $drow['name'])) return $this->alert('error', '无权限'); $recordid = input('post.recordid', null, 'trim'); + $recordinfo = input('post.recordinfo', null, 'trim'); + $recordinfo = json_decode($recordinfo, true); if (empty($recordid)) { return json(['code' => -1, 'msg' => '参数不能为空']); @@ -544,7 +553,11 @@ class Domain extends BaseController $dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']); if ($dns->deleteDomainRecord($recordid)) { - $this->add_log($drow['name'], '删除解析', '记录ID:' . $recordid); + if ($recordinfo) { + $this->add_log($drow['name'], '删除解析', $recordinfo['Name'].' ['.$recordinfo['Type'].'] '.$recordinfo['Value'].' (线路:'.$recordinfo['Line'].' TTL:'.$recordinfo['TTL'].')'); + } else { + $this->add_log($drow['name'], '删除解析', '记录ID:'.$recordid); + } return json(['code' => 0, 'msg' => '删除解析记录成功!']); } else { return json(['code' => -1, 'msg' => '删除解析记录失败,' . $dns->getError()]); @@ -562,6 +575,8 @@ class Domain extends BaseController $recordid = input('post.recordid', null, 'trim'); $status = input('post.status', null, 'trim'); + $recordinfo = input('post.recordinfo', null, 'trim'); + $recordinfo = json_decode($recordinfo, true); if (empty($recordid)) { return json(['code' => -1, 'msg' => '参数不能为空']); @@ -570,7 +585,11 @@ class Domain extends BaseController $dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']); if ($dns->setDomainRecordStatus($recordid, $status)) { $action = $status == '1' ? '启用解析' : '暂停解析'; - $this->add_log($drow['name'], $action, '记录ID:' . $recordid); + if ($recordinfo) { + $this->add_log($drow['name'], $action, $recordinfo['Name'].' ['.$recordinfo['Type'].'] '.$recordinfo['Value'].' (线路:'.$recordinfo['Line'].' TTL:'.$recordinfo['TTL'].')'); + } else { + $this->add_log($drow['name'], $action, '记录ID:'.$recordid); + } return json(['code' => 0, 'msg' => '操作成功!']); } else { return json(['code' => -1, 'msg' => '操作失败,' . $dns->getError()]); @@ -611,10 +630,11 @@ class Domain extends BaseController } if (!checkPermission(0, $drow['name'])) return $this->alert('error', '无权限'); - $recordids = input('post.recordids', null, 'trim'); $action = input('post.action', null, 'trim'); + $recordinfo = input('post.recordinfo', null, 'trim'); + $recordinfo = json_decode($recordinfo, true); - if (empty($recordids) || empty($action)) { + if (empty($recordinfo) || empty($action)) { return json(['code' => -1, 'msg' => '参数不能为空']); } @@ -622,25 +642,25 @@ class Domain extends BaseController $fail = 0; $dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']); if ($action == 'open') { - foreach ($recordids as $recordid) { - if ($dns->setDomainRecordStatus($recordid, '1')) { - $this->add_log($drow['name'], '启用解析', '记录ID:' . $recordid); + foreach ($recordinfo as $record) { + if ($dns->setDomainRecordStatus($record['RecordId'], '1')) { + $this->add_log($drow['name'], '启用解析', $record['Name'].' ['.$record['Type'].'] '.$record['Value'].' (线路:'.$record['Line'].' TTL:'.$record['TTL'].')'); $success++; } } $msg = '成功启用' . $success . '条解析记录'; } else if ($action == 'pause') { - foreach ($recordids as $recordid) { - if ($dns->setDomainRecordStatus($recordid, '0')) { - $this->add_log($drow['name'], '暂停解析', '记录ID:' . $recordid); + foreach ($recordinfo as $record) { + if ($dns->setDomainRecordStatus($record['RecordId'], '0')) { + $this->add_log($drow['name'], '暂停解析', $record['Name'].' ['.$record['Type'].'] '.$record['Value'].' (线路:'.$record['Line'].' TTL:'.$record['TTL'].')'); $success++; } } $msg = '成功暂停' . $success . '条解析记录'; } else if ($action == 'delete') { - foreach ($recordids as $recordid) { - if ($dns->deleteDomainRecord($recordid)) { - $this->add_log($drow['name'], '删除解析', '记录ID:' . $recordid); + foreach ($recordinfo as $record) { + if ($dns->deleteDomainRecord($record['RecordId'])) { + $this->add_log($drow['name'], '删除解析', $record['Name'].' ['.$record['Type'].'] '.$record['Value'].' (线路:'.$record['Line'].' TTL:'.$record['TTL'].')'); $success++; } } @@ -648,8 +668,8 @@ class Domain extends BaseController } else if ($action == 'remark') { $remark = input('post.remark', null, 'trim'); if (empty($remark)) $remark = null; - foreach ($recordids as $recordid) { - if ($dns->updateDomainRecordRemark($recordid, $remark)) { + foreach ($recordinfo as $record) { + if ($dns->updateDomainRecordRemark($record['RecordId'], $remark)) { $success++; } else { $fail++; @@ -686,9 +706,9 @@ class Domain extends BaseController $success = 0; $fail = 0; foreach ($recordinfo as $record) { - $recordid = $dns->updateDomainRecord($record['recordid'], $record['name'], $type, $value, $record['line'], $record['ttl'], $record['mx'], $record['weight'], $record['remark']); + $recordid = $dns->updateDomainRecord($record['RecordId'], $record['Name'], $type, $value, $record['Line'], $record['TTL'], $record['MX'], $record['Weight'], $record['Remark']); if ($recordid) { - $this->add_log($drow['name'], '修改解析', $type . '记录 ' . $record['name'] . ' ' . $value . ' (线路:' . $record['line'] . ' TTL:' . $record['ttl'] . ')'); + $this->add_log($drow['name'], '修改解析', $record['Name'].' ['.$record['Type'].'] '.$record['Value'].' → '.$record['Name'].' ['.$type.'] '.$value.' (线路:'.$record['Line'].' TTL:'.$record['TTL'].')'); $success++; } else { $fail++; @@ -707,9 +727,9 @@ class Domain extends BaseController $success = 0; $fail = 0; foreach ($recordinfo as $record) { - $recordid = $dns->updateDomainRecord($record['recordid'], $record['name'], $record['type'], $record['value'], $line, $record['ttl'], $record['mx'], $record['weight'], $record['remark']); + $recordid = $dns->updateDomainRecord($record['RecordId'], $record['Name'], $record['Type'], $record['Value'], $line, $record['TTL'], $record['MX'], $record['Weight'], $record['Remark']); if ($recordid) { - $this->add_log($drow['name'], '修改解析', $record['type'] . '记录 ' . $record['name'] . ' ' . $record['value'] . ' (线路:' . $line . ' TTL:' . $record['ttl'] . ')'); + $this->add_log($drow['name'], '修改解析', $record['Name'].' ['.$record['Type'].'] '.$record['Value'].' (线路:'.$line.' TTL:'.$record['TTL'].')'); $success++; } else { $fail++; @@ -752,7 +772,7 @@ class Domain extends BaseController $thistype = empty($type) ? getDnsType($arr[1]) : $type; $recordid = $dns->addDomainRecord($arr[0], $thistype, $arr[1], $line, $ttl, $mx); if ($recordid) { - $this->add_log($drow['name'], '添加解析', $thistype . '记录 ' . $arr[0] . ' ' . $arr[1] . ' (线路:' . $line . ' TTL:' . $ttl . ')'); + $this->add_log($drow['name'], '添加解析', $arr[0].' ['.$thistype.'] '.$arr[1].' (线路:'.$line.' TTL:'.$ttl.')'); $success++; } else { $fail++; diff --git a/app/lib/DeployHelper.php b/app/lib/DeployHelper.php index fdf892a..b170136 100644 --- a/app/lib/DeployHelper.php +++ b/app/lib/DeployHelper.php @@ -1249,12 +1249,18 @@ class DeployHelper ], 'taskinputs' => [ 'product' => [ - 'name' => '产品', - 'type' => 'hidden', + 'name' => '要部署的产品', + 'type' => 'select', + 'options' => [ + ['value'=>'cdn', 'label'=>'CDN加速'], + ['value'=>'icdn', 'label'=>'全站加速'], + ['value'=>'accessone', 'label'=>'边缘安全加速平台'], + ], 'value' => 'cdn', + 'required' => true, ], 'domain' => [ - 'name' => 'CDN域名', + 'name' => '绑定的域名', 'type' => 'input', 'placeholder' => '', 'required' => true, diff --git a/app/lib/DnsHelper.php b/app/lib/DnsHelper.php index 073f25d..150d4c2 100644 --- a/app/lib/DnsHelper.php +++ b/app/lib/DnsHelper.php @@ -18,6 +18,7 @@ class DnsHelper 'redirect' => true, //是否支持域名转发 'log' => true, //是否支持查看日志 'weight' => false, //是否支持权重 + 'page' => false, //是否客户端分页 ], 'dnspod' => [ 'name' => '腾讯云', @@ -30,6 +31,7 @@ class DnsHelper 'redirect' => true, 'log' => true, 'weight' => true, + 'page' => false, ], 'huawei' => [ 'name' => '华为云', @@ -42,6 +44,7 @@ class DnsHelper 'redirect' => false, 'log' => false, 'weight' => true, + 'page' => false, ], 'baidu' => [ 'name' => '百度云', @@ -54,6 +57,7 @@ class DnsHelper 'redirect' => false, 'log' => false, 'weight' => false, + 'page' => true, ], 'west' => [ 'name' => '西部数码', @@ -66,6 +70,7 @@ class DnsHelper 'redirect' => false, 'log' => false, 'weight' => false, + 'page' => false, ], 'huoshan' => [ 'name' => '火山引擎', @@ -78,6 +83,7 @@ class DnsHelper 'redirect' => false, 'log' => false, 'weight' => true, + 'page' => false, ], 'dnsla' => [ 'name' => 'DNSLA', @@ -90,6 +96,7 @@ class DnsHelper 'redirect' => true, 'log' => false, 'weight' => true, + 'page' => false, ], 'cloudflare' => [ 'name' => 'Cloudflare', @@ -102,6 +109,7 @@ class DnsHelper 'redirect' => false, 'log' => false, 'weight' => false, + 'page' => false, ], 'namesilo' => [ 'name' => 'NameSilo', @@ -114,6 +122,21 @@ class DnsHelper 'redirect' => false, 'log' => false, 'weight' => false, + 'page' => true, + ], + 'powerdns' => [ + 'name' => 'PowerDNS', + 'config' => [ + 'ak' => 'IP地址', + 'sk' => '端口', + 'ext' => 'API KEY', + ], + 'remark' => 2, + 'status' => true, + 'redirect' => false, + 'log' => false, + 'weight' => false, + 'page' => true, ], ]; diff --git a/app/lib/deploy/ctyun.php b/app/lib/deploy/ctyun.php index 31eaa0c..052afd8 100644 --- a/app/lib/deploy/ctyun.php +++ b/app/lib/deploy/ctyun.php @@ -12,20 +12,19 @@ class ctyun implements DeployInterface private $AccessKeyId; private $SecretAccessKey; private $proxy; - private $client; public function __construct($config) { $this->AccessKeyId = $config['AccessKeyId']; $this->SecretAccessKey = $config['SecretAccessKey']; $this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false; - $this->client = new CtyunClient($this->AccessKeyId, $this->SecretAccessKey, 'ctcdn-global.ctapi.ctyun.cn', $this->proxy); } public function check() { if (empty($this->AccessKeyId) || empty($this->SecretAccessKey)) throw new Exception('必填参数不能为空'); - $this->client->request('GET', '/v1/cert/query-cert-list'); + $client = new CtyunClient($this->AccessKeyId, $this->SecretAccessKey, 'ctcdn-global.ctapi.ctyun.cn', $this->proxy); + $client->request('GET', '/v1/cert/query-cert-list'); return true; } @@ -33,31 +32,42 @@ class ctyun implements DeployInterface { $certInfo = openssl_x509_parse($fullchain, true); if (!$certInfo) throw new Exception('证书解析失败'); - $cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t']; + $config['cert_name'] = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t']; + if ($config['product'] == 'cdn') { + $this->deploy_cdn($fullchain, $privatekey, $config); + } elseif ($config['product'] == 'icdn') { + $this->deploy_icdn($fullchain, $privatekey, $config); + } elseif ($config['product'] == 'accessone') { + $this->deploy_accessone($fullchain, $privatekey, $config); + } + } + private function deploy_cdn($fullchain, $privatekey, $config) + { + $client = new CtyunClient($this->AccessKeyId, $this->SecretAccessKey, 'ctcdn-global.ctapi.ctyun.cn', $this->proxy); $param = [ - 'name' => $cert_name, + 'name' => $config['cert_name'], 'key' => $privatekey, 'certs' => $fullchain, ]; try { - $this->client->request('POST', '/v1/cert/creat-cert', null, $param); + $client->request('POST', '/v1/cert/creat-cert', null, $param); } catch (Exception $e) { if (strpos($e->getMessage(), '已存在重名的证书') !== false) { - $this->log('已存在重名的证书 cert_name=' . $cert_name); + $this->log('已存在重名的证书 cert_name=' . $config['cert_name']); } else { throw new Exception('上传证书失败:' . $e->getMessage()); } } - $this->log('上传证书成功 cert_name=' . $cert_name); + $this->log('上传证书成功 cert_name=' . $config['cert_name']); $param = [ 'domain' => $config['domain'], 'https_status' => 'on', - 'cert_name' => $cert_name, + 'cert_name' => $config['cert_name'], ]; try { - $this->client->request('POST', '/v1/domain/update-domain', null, $param); + $client->request('POST', '/v1/domain/update-domain', null, $param); } catch (Exception $e) { if (strpos($e->getMessage(), '请求已提交,请勿重复操作!') === false) { throw new Exception($e->getMessage()); @@ -67,6 +77,99 @@ class ctyun implements DeployInterface $this->log('CDN域名 ' . $config['domain'] . ' 部署证书成功!'); } + private function deploy_icdn($fullchain, $privatekey, $config) + { + $client = new CtyunClient($this->AccessKeyId, $this->SecretAccessKey, 'icdn-global.ctapi.ctyun.cn', $this->proxy); + $param = [ + 'name' => $config['cert_name'], + 'key' => $privatekey, + 'certs' => $fullchain, + ]; + try { + $client->request('POST', '/v1/cert/creat-cert', null, $param); + } catch (Exception $e) { + if (strpos($e->getMessage(), '已存在重名的证书') !== false) { + $this->log('已存在重名的证书 cert_name=' . $config['cert_name']); + } else { + throw new Exception('上传证书失败:' . $e->getMessage()); + } + } + $this->log('上传证书成功 cert_name=' . $config['cert_name']); + + $param = [ + 'domain' => $config['domain'], + 'https_status' => 'on', + 'cert_name' => $config['cert_name'], + ]; + try { + $client->request('POST', '/v1/domain/update-domain', null, $param); + } catch (Exception $e) { + if (strpos($e->getMessage(), '请求已提交,请勿重复操作!') === false) { + throw new Exception($e->getMessage()); + } + } + + $this->log('CDN域名 ' . $config['domain'] . ' 部署证书成功!'); + } + + private function deploy_accessone($fullchain, $privatekey, $config) + { + $client = new CtyunClient($this->AccessKeyId, $this->SecretAccessKey, 'accessone-global.ctapi.ctyun.cn', $this->proxy); + $param = [ + 'name' => $config['cert_name'], + 'key' => $privatekey, + 'certs' => $fullchain, + ]; + try { + $client->request('POST', '/ctapi/v1/accessone/cert/create', null, $param); + } catch (Exception $e) { + if (strpos($e->getMessage(), '已存在重名的证书') !== false) { + $this->log('已存在重名的证书 cert_name=' . $config['cert_name']); + } else { + throw new Exception('上传证书失败:' . $e->getMessage()); + } + } + $this->log('上传证书成功 cert_name=' . $config['cert_name']); + + $param = [ + 'domain' => $config['domain'], + 'product_code' => '020', + ]; + try { + $result = $client->request('POST', '/ctapi/v1/accessone/domain/config', null, $param); + } catch (Exception $e) { + throw new Exception('查询域名配置失败:' . $e->getMessage()); + } + + if ($result['https_status'] == 'on' && $result['cert_name'] == $config['cert_name']) { + $this->log('边缘安全加速域名 ' . $config['domain'] . ' 证书已部署,无需重复操作!'); + return; + } + + $result['https_status'] = 'on'; + $result['cert_name'] = $config['cert_name']; + $exclude_keys = ['status', 'area_scope', 'cname', 'insert_date', 'status_date', 'record_status', 'record_num', 'customer_name', 'outlink_replace_filter', 'website_ipv6_access_mark', 'websocket_speed', 'dynamic_config', 'dynamic_ability']; + foreach ($result as $key => $value) { + if (in_array($key, $exclude_keys) || is_array($value) && empty($value)) { + unset($result[$key]); + } + } + if (isset($result['origin'])) { + foreach ($result['origin'] as &$origin) { + $origin['weight'] = strval($origin['weight']); + } + } + try { + $client->request('POST', '/ctapi/v1/accessone/domain/modify_config', null, $result); + } catch (Exception $e) { + if (strpos($e->getMessage(), '请求已提交,请勿重复操作!') === false) { + throw new Exception($e->getMessage()); + } + } + + $this->log('边缘安全加速域名 ' . $config['domain'] . ' 部署证书成功!'); + } + public function setLogger($func) { $this->logger = $func; diff --git a/app/lib/deploy/synology.php b/app/lib/deploy/synology.php index dfce58a..8a79426 100644 --- a/app/lib/deploy/synology.php +++ b/app/lib/deploy/synology.php @@ -47,7 +47,7 @@ class synology implements DeployInterface $result = json_decode($response['body'], true); if (isset($result['success']) && $result['success']) { $this->token = $result['data']; - } elseif(isset($result['error'])) { + } elseif (isset($result['error'])) { throw new Exception('登录失败:' . $result['error']); } else { throw new Exception('请求失败(httpCode=' . $response['code'] . ')'); @@ -58,6 +58,7 @@ class synology implements DeployInterface { $this->login(); $certInfo = openssl_x509_parse($fullchain, true); + $certInfo['validFrom_time_t']; if (!$certInfo) throw new Exception('证书解析失败'); $url = $this->url . '/webapi/entry.cgi'; @@ -72,20 +73,26 @@ class synology implements DeployInterface $result = json_decode($response['body'], true); if (isset($result['success']) && $result['success']) { $this->log('获取证书列表成功'); - } elseif(isset($result['error'])) { + } elseif (isset($result['error'])) { throw new Exception('获取证书列表失败:' . json_encode($result['error'])); } else { throw new Exception('获取证书列表失败(httpCode=' . $response['code'] . ')'); } $id = null; + $validFrom = 0; foreach ($result['data']['certificates'] as $certificate) { if ($certificate['subject']['common_name'] == $certInfo['subject']['CN'] || $certificate['desc'] == $config['desc']) { $id = $certificate['id']; + $validFrom = \DateTime::createFromFormat('M d H:i:s Y T', $certificate['valid_from'])->getTimestamp(); break; } } if ($id) { + if ($validFrom == $certInfo['validFrom_time_t']) { + $this->log('证书ID:' . $id . '已存在,无需更新'); + return; + } $this->import($fullchain, $privatekey, $config, $id); } else { $this->import($fullchain, $privatekey, $config); @@ -112,22 +119,22 @@ class synology implements DeployInterface 'id' => $id, 'desc' => $config['desc'], ]; - $response = curl_client($url . '?' . http_build_query($params), $post, null, null, null, $this->proxy); + $response = curl_client($url . '?' . http_build_query($params), $post, null, null, null, $this->proxy, null, 15); unlink($privatekey_file); unlink($fullchain_file); $result = json_decode($response['body'], true); if ($id) { if (isset($result['success']) && $result['success']) { - $this->log('证书ID:'.$id.'更新成功!'); - } elseif(isset($result['error'])) { - throw new Exception('证书ID:'.$id.'更新失败:' . json_encode($result['error'])); + $this->log('证书ID:' . $id . '更新成功!'); + } elseif (isset($result['error'])) { + throw new Exception('证书ID:' . $id . '更新失败:' . json_encode($result['error'])); } else { - throw new Exception('证书ID:'.$id.'更新失败(httpCode=' . $response['code'] . ')'); + throw new Exception('证书ID:' . $id . '更新失败(httpCode=' . $response['code'] . ')'); } } else { if (isset($result['success']) && $result['success']) { $this->log('证书上传成功!'); - } elseif(isset($result['error'])) { + } elseif (isset($result['error'])) { throw new Exception('证书上传失败:' . json_encode($result['error'])); } else { throw new Exception('证书上传失败(httpCode=' . $response['code'] . ')'); diff --git a/app/lib/dns/namesilo.php b/app/lib/dns/namesilo.php index 68c1746..bcecac7 100644 --- a/app/lib/dns/namesilo.php +++ b/app/lib/dns/namesilo.php @@ -79,22 +79,22 @@ class namesilo implements DnsInterface 'UpdateTime' => null, ]; } - if(!empty($SubDomain)){ + if(!isNullOrEmpty($SubDomain)){ $list = array_values(array_filter($list, function($v) use ($SubDomain){ return $v['Name'] == $SubDomain; })); }else{ - if(!empty($KeyWord)){ + if(!isNullOrEmpty($KeyWord)){ $list = array_values(array_filter($list, function($v) use ($KeyWord){ return strpos($v['Name'], $KeyWord) !== false || strpos($v['Value'], $KeyWord) !== false; })); } - if(!empty($Value)){ + if(!isNullOrEmpty($Value)){ $list = array_values(array_filter($list, function($v) use ($Value){ return $v['Value'] == $Value; })); } - if(!empty($Type)){ + if(!isNullOrEmpty($Type)){ $list = array_values(array_filter($list, function($v) use ($Type){ return $v['Type'] == $Type; })); @@ -118,7 +118,7 @@ class namesilo implements DnsInterface } //添加解析记录 - public function addDomainRecord($Name, $Type, $Value, $Line = '0', $TTL = 600, $MX = 1, $Weight = null, $Remark = null) + public function addDomainRecord($Name, $Type, $Value, $Line = 'default', $TTL = 600, $MX = 1, $Weight = null, $Remark = null) { $param = ['domain' => $this->domain, 'rrtype' => $Type, 'rrhost' => $Name, 'rrvalue' => $Value, 'rrttl' => $TTL]; if ($Type == 'MX') $param['rrdistance'] = intval($MX); @@ -127,7 +127,7 @@ class namesilo implements DnsInterface } //修改解析记录 - public function updateDomainRecord($RecordId, $Name, $Type, $Value, $Line = '0', $TTL = 600, $MX = 1, $Weight = null, $Remark = null) + public function updateDomainRecord($RecordId, $Name, $Type, $Value, $Line = 'default', $TTL = 600, $MX = 1, $Weight = null, $Remark = null) { $param = ['domain' => $this->domain, 'rrid' => $RecordId, 'rrtype' => $Type, 'rrhost' => $Name, 'rrvalue' => $Value, 'rrttl' => $TTL]; if ($Type == 'MX') $param['rrdistance'] = intval($MX); diff --git a/app/lib/dns/powerdns.php b/app/lib/dns/powerdns.php new file mode 100644 index 0000000..7764e83 --- /dev/null +++ b/app/lib/dns/powerdns.php @@ -0,0 +1,409 @@ +url = 'http://' . $config['ak'] . ':' . $config['sk'] . '/api/v1'; + $this->apikey = $config['ext']; + $this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false; + $this->domain = $config['domain']; + $this->domainid = $config['domainid']; + } + + public function getError() + { + return $this->error; + } + + public function check() + { + if ($this->getDomainList() !== false) { + return true; + } + return false; + } + + //获取域名列表 + public function getDomainList($KeyWord = null, $PageNumber = 1, $PageSize = 20) + { + $data = $this->send_reuqest('GET', '/servers/' . $this->server_id . '/zones'); + if ($data) { + $list = []; + foreach ($data as $row) { + $list[] = [ + 'DomainId' => $row['id'], + 'Domain' => rtrim($row['name'], '.'), + 'RecordCount' => 0, + ]; + } + return ['total' => count($list), 'list' => $list]; + } + return false; + } + + //获取解析记录列表 + public function getDomainRecords($PageNumber = 1, $PageSize = 20, $KeyWord = null, $SubDomain = null, $Value = null, $Type = null, $Line = null, $Status = null) + { + $data = $this->send_reuqest('GET', '/servers/' . $this->server_id . '/zones/' . $this->domainid); + if ($data) { + $list = []; + $rrset_id = 0; + foreach ($data['rrsets'] as &$row) { + $rrset_id++; + $name = $row['name'] == $this->domainid ? '@' : str_replace('.' . $this->domainid, '', $row['name']); + $row['host'] = $name; + $row['id'] = $rrset_id; + $record_id = 0; + foreach ($row['records'] as &$record) { + $record_id++; + $record['id'] = $record_id; + $remark = !empty($row['comments']) ? $row['comments'][0]['content'] : null; + $value = $record['content']; + if ($row['type'] == 'MX') list($record['mx'], $value) = explode(' ', $record['content']); + $list[] = [ + 'RecordId' => $rrset_id . '_' . $record_id, + 'Domain' => $this->domain, + 'Name' => $name, + 'Type' => $row['type'], + 'Value' => $value, + 'Line' => 'default', + 'TTL' => $row['ttl'], + 'MX' => isset($record['mx']) ? $record['mx'] : null, + 'Status' => $record['disabled'] ? '0' : '1', + 'Weight' => null, + 'Remark' => $remark, + 'UpdateTime' => null, + ]; + } + } + cache('powerdns_' . $this->domainid, $data['rrsets'], 86400); + if (!isNullOrEmpty($SubDomain)) { + $list = array_values(array_filter($list, function ($v) use ($SubDomain) { + return $v['Name'] == $SubDomain; + })); + } else { + if (!isNullOrEmpty($KeyWord)) { + $list = array_values(array_filter($list, function ($v) use ($KeyWord) { + return strpos($v['Name'], $KeyWord) !== false || strpos($v['Value'], $KeyWord) !== false; + })); + } + if (!isNullOrEmpty($Value)) { + $list = array_values(array_filter($list, function ($v) use ($Value) { + return $v['Value'] == $Value; + })); + } + if (!isNullOrEmpty($Type)) { + $list = array_values(array_filter($list, function ($v) use ($Type) { + return $v['Type'] == $Type; + })); + } + if (!isNullOrEmpty($Status)) { + $list = array_values(array_filter($list, function ($v) use ($Status) { + return $v['Status'] == $Status; + })); + } + } + return ['total' => count($list), 'list' => $list]; + } + return false; + } + + //获取子域名解析记录列表 + public function getSubDomainRecords($SubDomain, $PageNumber = 1, $PageSize = 20, $Type = null, $Line = null) + { + return $this->getDomainRecords($PageNumber, $PageSize, null, $SubDomain, null, $Type, $Line); + } + + //获取解析记录详细信息 + public function getDomainRecordInfo($RecordId) + { + return false; + } + + //添加解析记录 + public function addDomainRecord($Name, $Type, $Value, $Line = 'default', $TTL = 600, $MX = 1, $Weight = null, $Remark = null) + { + if ($Type == 'TXT' && substr($Value, 0, 1) != '"') $Value = '"' . $Value . '"'; + if ($Type == 'CNAME' && substr($Value, -1) != '.') $Value .= '.'; + if ($Type == 'MX') $Value = intval($MX) . ' ' . $Value; + $records = []; + $rrsets = cache('powerdns_' . $this->domainid); + if ($rrsets) { + $rrsets_filter = array_filter($rrsets, function ($row) use ($Name, $Type) { + return $row['host'] == $Name && $row['type'] == $Type; + }); + if (!empty($rrsets_filter)) { + $rrset = $rrsets_filter[array_key_first($rrsets_filter)]; + $records = $rrset['records']; + $records_filter = array_filter($records, function ($record) use ($Value) { + return $record['content'] == $Value; + }); + if (!empty($records_filter)) { + $this->setError('已存在相同记录'); + return false; + } + } + } + $records[] = ['content' => $Value, 'disabled' => false]; + return $this->rrset_replace($Name, $Type, $TTL, $records, $Remark); + } + + //修改解析记录 + public function updateDomainRecord($RecordId, $Name, $Type, $Value, $Line = 'default', $TTL = 600, $MX = 1, $Weight = null, $Remark = null) + { + if ($Type == 'TXT' && substr($Value, 0, 1) != '"') $Value = '"' . $Value . '"'; + if ($Type == 'CNAME' && substr($Value, -1) != '.') $Value .= '.'; + if ($Type == 'MX') $Value = intval($MX) . ' ' . $Value; + $rrsets = cache('powerdns_' . $this->domainid); + $add = false; + $res = false; + if ($rrsets) { + [$rrset_id, $record_id] = explode('_', $RecordId); + $exist = false; + foreach ($rrsets as &$rrset) { + if ($rrset['id'] == $rrset_id) { + $records = $rrset['records']; + $records_filter = array_filter($records, function ($record) use ($Value, $record_id) { + return $record['content'] == $Value && $record['id'] != $record_id; + }); + if (!empty($records_filter)) { + $this->setError('已存在相同记录'); + return false; + } + foreach ($records as $i => &$record) { + if ($record['id'] == $record_id) { + $exist = true; + if ($rrset['host'] == $Name && $rrset['type'] == $Type) { + $record['content'] = $Value; + } else { + unset($records[$i]); + $add = true; + } + break; + } + } + if (!$exist) break; + $records = array_values($records); + if (!empty($records)) { + $res = $this->rrset_replace($rrset['host'], $rrset['type'], $TTL, $records, $Remark); + } else { + $res = $this->rrset_delete($rrset['host'], $rrset['type']); + } + $rrset['records'] = $records; + break; + } + } + if (!$exist) { + $this->setError('记录不存在,请刷新页面重试'); + return false; + } + cache('powerdns_' . $this->domainid, $rrsets, 86400); + if ($res && $add) { + $res = $this->addDomainRecord($Name, $Type, $Value, $Line, $TTL, $MX, $Weight, $Remark); + } + return $res; + } else { + $records[] = ['content' => $Value, 'disabled' => false]; + return $this->addDomainRecord($Name, $Type, $Value, $Line, $TTL, $MX, $Weight, $Remark); + } + } + + //修改解析记录备注 + public function updateDomainRecordRemark($RecordId, $Remark) + { + return false; + } + + //删除解析记录 + public function deleteDomainRecord($RecordId) + { + $rrsets = cache('powerdns_' . $this->domainid); + if (!$rrsets) { + $this->setError('记录不存在,请刷新页面重试'); + return false; + } + [$rrset_id, $record_id] = explode('_', $RecordId); + $exist = false; + $res = false; + foreach ($rrsets as &$rrset) { + if ($rrset['id'] == $rrset_id) { + $records = $rrset['records']; + foreach ($records as $i => &$record) { + if ($record['id'] == $record_id) { + $exist = true; + unset($records[$i]); + break; + } + } + if (!$exist) break; + $records = array_values($records); + if (!empty($records)) { + $res = $this->rrset_replace($rrset['host'], $rrset['type'], $rrset['ttl'], $records); + } else { + $res = $this->rrset_delete($rrset['host'], $rrset['type']); + } + $rrset['records'] = $records; + break; + } + } + if (!$exist) { + $this->setError('记录不存在,请刷新页面重试'); + return false; + } + cache('powerdns_' . $this->domainid, $rrsets, 86400); + return $res; + } + + //设置解析记录状态 + public function setDomainRecordStatus($RecordId, $Status) + { + $rrsets = cache('powerdns_' . $this->domainid); + if (!$rrsets) { + $this->setError('记录不存在,请刷新页面重试'); + return false; + } + [$rrset_id, $record_id] = explode('_', $RecordId); + $exist = false; + $res = false; + foreach ($rrsets as &$rrset) { + if ($rrset['id'] == $rrset_id) { + $records = $rrset['records']; + foreach ($records as &$record) { + if ($record['id'] == $record_id) { + $exist = true; + $record['disabled'] = $Status == '0'; + break; + } + } + if (!$exist) break; + $res = $this->rrset_replace($rrset['host'], $rrset['type'], $rrset['ttl'], $records); + $rrset['records'] = $records; + break; + } + } + if (!$exist) { + $this->setError('记录不存在,请刷新页面重试'); + return false; + } + cache('powerdns_' . $this->domainid, $rrsets, 86400); + return $res; + } + + //获取解析记录操作日志 + public function getDomainRecordLog($PageNumber = 1, $PageSize = 20, $KeyWord = null, $StartDate = null, $endDate = null) + { + return false; + } + + //获取解析线路列表 + public function getRecordLine() + { + return ['default' => ['name' => '默认', 'parent' => null]]; + } + + //获取域名信息 + public function getDomainInfo() + { + return false; + } + + //获取域名最低TTL + public function getMinTTL() + { + return false; + } + + private function rrset_replace($host, $type, $ttl, $records, $remark = null) + { + $name = $host == '@' ? $this->domainid : $host . '.' . $this->domainid; + $rrset = [ + 'name' => $name, + 'type' => $type, + 'ttl' => intval($ttl), + 'changetype' => 'REPLACE', + 'records' => $records, + 'comments' => [], + ]; + if (!empty($remark)) { + $rrset['comments'] = [ + ['account' => '', 'content' => $remark] + ]; + } + $param = [ + 'rrsets' => [ + $rrset + ], + ]; + return $this->send_reuqest('PATCH', '/servers/' . $this->server_id . '/zones/' . $this->domainid, $param); + } + + private function rrset_delete($host, $type) + { + $name = $host == '@' ? $this->domainid : $host . '.' . $this->domainid; + $param = [ + 'rrsets' => [ + [ + 'name' => $name, + 'type' => $type, + 'changetype' => 'DELETE', + ] + ], + ]; + return $this->send_reuqest('PATCH', '/servers/' . $this->server_id . '/zones/' . $this->domainid, $param); + } + + private function send_reuqest($method, $path, $params = null) + { + $url = $this->url . $path; + $headers[] = 'X-API-Key: ' . $this->apikey; + $body = null; + if ($method == 'GET' || $method == 'DELETE') { + if ($params) { + $url .= '?' . http_build_query($params); + } + } else { + $body = json_encode($params); + $headers[] = 'Content-Type: application/json'; + } + try { + $response = curl_client($url, $body, null, null, $headers, $this->proxy, $method); + } catch (Exception $e) { + $this->setError($e->getMessage()); + return false; + } + + $arr = json_decode($response['body'], true); + if ($response['code'] < 400) { + return is_array($arr) ? $arr : true; + } elseif (isset($arr['error'])) { + $this->setError($arr['error']); + return false; + } elseif (isset($arr['errors'])) { + $this->setError(implode(',', $arr['errors'])); + return false; + } else { + $this->setError($response['body']); + return false; + } + } + + private function setError($message) + { + $this->error = $message; + //file_put_contents('logs.txt',date('H:i:s').' '.$message."\r\n", FILE_APPEND); + } +} diff --git a/app/view/domain/account.html b/app/view/domain/account.html index 0145563..757b25f 100644 --- a/app/view/domain/account.html +++ b/app/view/domain/account.html @@ -39,7 +39,7 @@
diff --git a/app/view/domain/record.html b/app/view/domain/record.html index 56d3bc8..ba93c0f 100644 --- a/app/view/domain/record.html +++ b/app/view/domain/record.html @@ -17,6 +17,7 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
+
@@ -210,9 +211,6 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
-
- -
刷新 收起 @@ -236,7 +234,7 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px; var recordLine = {$recordLine|json_encode|raw}; var dnsconfig = {$dnsconfig|json_encode|raw}; var defaultLine = recordLine[0].id; -var sidePagination = dnsconfig.type == 'baidu' || dnsconfig.type == 'namesilo' ? 'client' : 'server'; +var sidePagination = dnsconfig.page ? 'client' : 'server'; var showWeight = dnsconfig.weight; $(document).ready(function(){ updateToolbar(); @@ -423,6 +421,7 @@ function editframe(recordid){ $("#modal-title").html("修改记录"); $("#form-store input[name=action]").val("update"); $("#form-store input[name=recordid]").val(recordid); + $("#form-store input[name=recordinfo]").val(JSON.stringify(row)); $("#form-store input[name=name]").val(row.Name); $("#form-store select[name=type]").val(row.Type); $("#form-store select[name=type]").change(); @@ -470,11 +469,12 @@ function save(){ }); } function setStatus(recordid, status){ + var row = $("#listTable").bootstrapTable('getRowByUniqueId', recordid); var ii = layer.load(2); $.ajax({ type : 'POST', url : '/record/status/{$domainId}', - data : {recordid: recordid, status: status}, + data : {recordid: recordid, status: status, recordinfo: JSON.stringify(row)}, dataType : 'json', success : function(data) { layer.close(ii); @@ -489,6 +489,7 @@ function setStatus(recordid, status){ }); } function delItem(recordid) { + var row = $("#listTable").bootstrapTable('getRowByUniqueId', recordid); var confirmobj = layer.confirm('确定要删除此解析记录吗?', { btn: ['确定','取消'] }, function(){ @@ -496,7 +497,7 @@ function delItem(recordid) { $.ajax({ type : 'POST', url : '/record/delete/{$domainId}', - data : {recordid: recordid}, + data : {recordid: recordid, recordinfo: JSON.stringify(row)}, dataType : 'json', success : function(data) { layer.close(ii); @@ -555,32 +556,16 @@ function operation(action){ return; } if(action == 'edit'){ - var records = []; - $.each(rows, function(index, item){ - records.push({recordid:item.RecordId, name:item.Name, line:item.Line, mx:item.MX, ttl:item.TTL, weight:item.Weight, remark:item.Remark}); - }) - batch_edit(records) + batch_edit(rows) return; }else if(action == 'editline'){ - var records = []; - $.each(rows, function(index, item){ - records.push({recordid:item.RecordId, name:item.Name, type:item.Type, value:item.Value, mx:item.MX, ttl:item.TTL, weight:item.Weight, remark:item.Remark}); - }) - batch_edit_line(records) + batch_edit_line(rows) return; }else if(action == 'editremark'){ - var ids = []; - $.each(rows, function(index, item){ - ids.push(item.RecordId); - }) - batch_edit_remark(ids) + batch_edit_remark(rows) return; } - var ids = []; - $.each(rows, function(index, item){ - ids.push(item.RecordId); - }) var confirmobj = layer.confirm('确定要'+(action=='open'?'启用':(action=='pause'?'暂停':'删除'))+'所选记录吗?', { btn: ['确定','取消'] }, function(){ @@ -588,7 +573,7 @@ function operation(action){ $.ajax({ type : 'POST', url : '/record/batch/{$domainId}', - data : {action: action, recordids: ids}, + data : {action: action, recordinfo: JSON.stringify(rows)}, dataType : 'json', success : function(data) { layer.close(ii); @@ -645,10 +630,10 @@ function batch_save(){ } }); } -function batch_edit_line(records){ - $("#batch_num").text(records.length); +function batch_edit_line(rows){ + $("#batch_num").text(rows.length); $("#modal-store3").modal('show'); - $("#form-store3 input[name=recordinfo]").val(JSON.stringify(records)); + $("#form-store3 input[name=recordinfo]").val(JSON.stringify(rows)); initLine('', 'line_list3'); } function batch_save_line(){ @@ -675,7 +660,7 @@ function batch_save_line(){ } }); } -function batch_edit_remark(recordids) { +function batch_edit_remark(rows) { layer.open({ type: 1, area: ['350px'], @@ -689,7 +674,7 @@ function batch_edit_remark(recordids) { $.ajax({ type : 'POST', url : '/record/batch/{$domainId}', - data : {action:'remark', recordids:recordids, remark:remark}, + data : {action:'remark', recordinfo: JSON.stringify(rows), remark:remark}, dataType : 'json', success : function(data) { layer.close(ii); diff --git a/public/static/images/powerdns.ico b/public/static/images/powerdns.ico new file mode 100644 index 0000000..78887f3 Binary files /dev/null and b/public/static/images/powerdns.ico differ