From 64d05857885aeb9fae3c822cb5474dd8259dcd07 Mon Sep 17 00:00:00 2001 From: net909 Date: Sat, 22 Mar 2025 22:28:42 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AF=81=E4=B9=A6=E5=8F=AF=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E3=80=81=E6=96=B0=E5=A2=9E=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=BA=AC=E4=B8=9C=E4=BA=91DNS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 105 +++++++------ app/controller/Cert.php | 50 +++++++ app/lib/DnsHelper.php | 13 ++ app/lib/client/AliyunNew.php | 36 ++--- app/lib/client/Jdcloud.php | 190 ++++++++++++++++++++++++ app/lib/dns/jdcloud.php | 246 +++++++++++++++++++++++++++++++ app/service/CertTaskService.php | 4 +- app/view/cert/certorder.html | 44 +++++- app/view/cert/deploytask.html | 44 +++++- config/app.php | 2 +- public/static/images/jdcloud.ico | Bin 0 -> 16958 bytes 11 files changed, 670 insertions(+), 64 deletions(-) create mode 100644 app/lib/client/Jdcloud.php create mode 100644 app/lib/dns/jdcloud.php create mode 100644 public/static/images/jdcloud.ico diff --git a/README.md b/README.md index 0168d4c..495e604 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,31 @@ -## 聚合DNS管理系统 +# 彩虹聚合DNS管理系统 -聚合DNS管理系统可以实现在一个网站内管理多个平台的域名解析,目前已支持的域名平台有:阿里云、腾讯云、华为云、百度云、西部数码、火山引擎、DNSLA、CloudFlare、Namesilo +
-### 功能特性 +[![GitHub stars](https://img.shields.io/github/stars/netcccyun/dnsmgr?style=flat)](https://github.com/netcccyun/dnsmgr/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/netcccyun/dnsmgr?style=flat)](https://github.com/netcccyun/dnsmgr/forks) +[![Docker Pulls](https://img.shields.io/docker/pulls/netcccyun/dnsmgr?style=flat)](https://hub.docker.com/r/netcccyun/dnsmgr) +[![GitHub release (latest by date)](https://img.shields.io/github/v/release/netcccyun/dnsmgr)](https://github.com/netcccyun/dnsmgr/releases) +[![GitHub last commit](https://img.shields.io/github/last-commit/netcccyun/dnsmgr)](https://github.com/netcccyun/dnsmgr/commits/main) -- 多用户管理,可为每个用户可分配不同的域名解析权限 -- 提供API接口,可获取域名单独的登录链接,方便各种IDC系统对接 -- 容灾切换功能,支持ping、tcp、http(s)检测协议并自动暂停/修改域名解析,并支持邮件、微信公众号、TG群机器人通知 -- CF优选IP功能,支持获取最新的Cloudflare优选IP,并自动更新到解析记录 -- SSL证书申请与自动部署功能,支持从Let's Encrypt等渠道申请SSL证书,并自动部署到各种面板、云服务商、服务器等 +
-### 演示截图 +彩虹聚合DNS管理系统 是一款基于ThinkPHP开发的网站程序,可实现在单一网站内管理多个平台的域名解析,目前已支持的域名解析平台有:阿里云、腾讯云、华为云、百度云、西部数码、火山引擎、DNSLA、CloudFlare、Namesilo、PowerDNS -添加域名账户 +## 功能特性 -![](https://p0.meituan.net/csc/090508cdc7aaabd185ba9c76a8c099f9283946.png) +- 多用户管理,可为每个用户可分配不同的域名解析权限; +- 提供API接口,可获取域名单独的登录链接,方便各种IDC系统对接; +- 容灾切换功能,支持ping、tcp、http(s)检测协议并自动暂停/修改域名解析,并支持发送通知; +- CF优选IP功能,支持获取最新的Cloudflare优选IP,并自动更新到解析记录; +- SSL证书申请与自动部署功能,支持从Let's Encrypt等渠道申请SSL证书,并自动部署到各种面板、云服务商、服务器等; +- 支持邮件、微信公众号、Telegram、钉钉、飞书、企业微信等多种通知渠道。 -域名管理列表 +## 部署方式 -![](https://p0.meituan.net/csc/60bf3f607d40f30f152ad1f6ee3be098357839.png) +### 自部署 -域名DNS解析管理,支持解析批量操作 - -![](https://p0.meituan.net/csc/f99c599d4febced404c88672dd50d62c212895.png) - -用户管理添加用户,支持为用户开启API接口 - -![](https://p0.meituan.net/csc/d1bd90bedca9b6cbc5da40286bdb5cd5228438.png) - -CF优选IP功能,添加优选IP任务 - -![](https://p1.meituan.net/csc/da70c76753aee4bce044d16fadd56e5f217660.png) - -SSL证书申请功能 - -![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154857.png) - -![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154652.png?a) - -SSL证书自动部署功能 - -![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154702.png) - -![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154804.png) - -### 部署方法 +可以使用宝塔、Kangle等任意支持PHP-MySQL的环境部署 * 从[Release](https://github.com/netcccyun/dnsmgr/releases)页面下载安装包 @@ -64,6 +45,8 @@ SSL证书自动部署功能 * 访问首页登录控制面板 +* 后续更新方式:重新下载安装包上传覆盖即可 + ##### 伪静态规则 * Nginx @@ -89,7 +72,7 @@ location / { ``` -### Docker部署方法 +### Docker 部署 首先需要安装Docker,然后执行以下命令拉取镜像并启动(启动后监听8081端口): @@ -103,7 +86,7 @@ docker run --name dnsmgr -dit -p 8081:80 -v /var/dnsmgr:/app/www netcccyun/dnsmg docker restart dnsmgr ``` -### docker-compose部署方法 +### docker-compose 部署 ``` version: '3' @@ -144,6 +127,7 @@ networks: ``` 在运行之前请创建好目录 + ``` mkdir -p ./web mkdir -p ./mysql/conf @@ -156,6 +140,7 @@ sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ ``` 登陆mysql容器创建数据库 + ``` docker exec -it dnsmgr-mysql /bin/bash mysql -uroot -p123456 @@ -164,9 +149,45 @@ create database dnsmgr; 在install界面链接IP填写dnsmgr-mysql -### 作者信息 +## 演示截图 -消失的彩虹海(https://blog.cccyun.cn) +添加域名账户 + +![](https://p0.meituan.net/csc/090508cdc7aaabd185ba9c76a8c099f9283946.png) + +域名管理列表 + +![](https://p0.meituan.net/csc/60bf3f607d40f30f152ad1f6ee3be098357839.png) + +域名DNS解析管理,支持解析批量操作 + +![](https://p0.meituan.net/csc/f99c599d4febced404c88672dd50d62c212895.png) + +用户管理添加用户,支持为用户开启API接口 + +![](https://p0.meituan.net/csc/d1bd90bedca9b6cbc5da40286bdb5cd5228438.png) + +CF优选IP功能,添加优选IP任务 + +![](https://p1.meituan.net/csc/da70c76753aee4bce044d16fadd56e5f217660.png) + +SSL证书申请功能 + +![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154857.png) + +![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154652.png?a) + +SSL证书自动部署功能 + +![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154702.png) + +![](https://blog.cccyun.cn/content/uploadfile/202412/QQ%E6%88%AA%E5%9B%BE20241221154804.png) + +## 支持与反馈 + +🌐 作者信息:消失的彩虹海(https://blog.cccyun.cn) + +⭐ 如果您觉得本项目对您有帮助,欢迎给项目点个 Star ### 其他推荐 diff --git a/app/controller/Cert.php b/app/controller/Cert.php index 324f79c..fe4cb7f 100644 --- a/app/controller/Cert.php +++ b/app/controller/Cert.php @@ -435,6 +435,35 @@ class Cert extends BaseController $file = app()->getRuntimePath().'log/'.$processid.'.log'; if(!file_exists($file)) return json(['code' => -1, 'msg' => '日志文件不存在']); return json(['code' => 0, 'data' => file_get_contents($file), 'time'=>filemtime($file)]); + } elseif ($action == 'operation') { + $ids = input('post.ids'); + $success = 0; + foreach ($ids as $id) { + if (input('post.action') == 'delete') { + $dcount = DB::name('cert_deploy')->where('oid', $id)->count(); + if ($dcount > 0) continue; + try { + (new CertOrderService($id))->cancel(); + } catch (Exception $e) { + } + Db::name('cert_order')->where('id', $id)->delete(); + Db::name('cert_domain')->where('oid', $id)->delete(); + $success++; + } elseif (input('post.action') == 'reset') { + try { + $service = new CertOrderService($id); + $service->cancel(); + $service->reset(); + $success++; + } catch (Exception $e) { + } + } elseif (input('post.action') == 'open' || input('post.action') == 'close') { + $isauto = input('post.action') == 'open' ? 1 : 0; + Db::name('cert_order')->where('id', $id)->update(['isauto' => $isauto]); + $success++; + } + } + return json(['code' => 0, 'msg' => '成功操作' . $success . '个证书订单']); } return json(['code' => -3]); } @@ -645,6 +674,27 @@ class Cert extends BaseController $file = app()->getRuntimePath().'log/'.$processid.'.log'; if(!file_exists($file)) return json(['code' => -1, 'msg' => '日志文件不存在']); return json(['code' => 0, 'data' => file_get_contents($file), 'time'=>filemtime($file)]); + } elseif ($action == 'operation') { + $ids = input('post.ids'); + $success = 0; + foreach ($ids as $id) { + if (input('post.action') == 'delete') { + Db::name('cert_deploy')->where('id', $id)->delete(); + $success++; + } elseif (input('post.action') == 'reset') { + try { + $service = new CertDeployService($id); + $service->reset(); + $success++; + } catch (Exception $e) { + } + } elseif (input('post.action') == 'open' || input('post.action') == 'close') { + $active = input('post.action') == 'open' ? 1 : 0; + Db::name('cert_deploy')->where('id', $id)->update(['active' => $active]); + $success++; + } + } + return json(['code' => 0, 'msg' => '成功操作' . $success . '个任务']); } return json(['code' => -3]); } diff --git a/app/lib/DnsHelper.php b/app/lib/DnsHelper.php index 150d4c2..6e71ba9 100644 --- a/app/lib/DnsHelper.php +++ b/app/lib/DnsHelper.php @@ -85,6 +85,19 @@ class DnsHelper 'weight' => true, 'page' => false, ], + 'jdcloud' => [ + 'name' => '京东云', + 'config' => [ + 'ak' => 'AccessKeyId', + 'sk' => 'AccessKeySecret', + ], + 'remark' => 0, + 'status' => true, + 'redirect' => true, + 'log' => false, + 'weight' => true, + 'page' => false, + ], 'dnsla' => [ 'name' => 'DNSLA', 'config' => [ diff --git a/app/lib/client/AliyunNew.php b/app/lib/client/AliyunNew.php index c8632a8..eca2a89 100644 --- a/app/lib/client/AliyunNew.php +++ b/app/lib/client/AliyunNew.php @@ -34,13 +34,15 @@ class AliyunNew public function request($method, $action, $path = '/', $params = null) { if (!empty($params)) { - $params = array_filter($params, function ($a) { return $a !== null;}); + $params = array_filter($params, function ($a) { + return $a !== null; + }); } - if($method == 'GET' || $method == 'DELETE'){ + if ($method == 'GET' || $method == 'DELETE') { $query = $params; $body = ''; - }else{ + } else { $query = []; $body = !empty($params) ? json_encode($params) : ''; } @@ -59,13 +61,13 @@ class AliyunNew $authorization = $this->generateSign($method, $path, $query, $headers, $body); $headers['Authorization'] = $authorization; - $url = 'https://'.$this->Endpoint.$path; + $url = 'https://' . $this->Endpoint . $path; if (!empty($query)) { - $url .= '?'.http_build_query($query); + $url .= '?' . http_build_query($query); } $header = []; foreach ($headers as $key => $value) { - $header[] = $key.': '.$value; + $header[] = $key . ': ' . $value; } return $this->curl($method, $url, $body, $header); } @@ -80,17 +82,17 @@ class AliyunNew $canonicalQueryString = $this->getCanonicalQueryString($query); [$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers); $hashedRequestPayload = hash("sha256", $body); - $canonicalRequest = $httpRequestMethod."\n" - .$canonicalUri."\n" - .$canonicalQueryString."\n" - .$canonicalHeaders."\n" - .$signedHeaders."\n" - .$hashedRequestPayload; + $canonicalRequest = $httpRequestMethod . "\n" + . $canonicalUri . "\n" + . $canonicalQueryString . "\n" + . $canonicalHeaders . "\n" + . $signedHeaders . "\n" + . $hashedRequestPayload; // step 2: build string to sign $hashedCanonicalRequest = hash("sha256", $canonicalRequest); - $stringToSign = $algorithm."\n" - .$hashedCanonicalRequest; + $stringToSign = $algorithm . "\n" + . $hashedCanonicalRequest; // step 3: sign string $signature = hash_hmac("sha256", $stringToSign, $this->AccessKeySecret); @@ -125,7 +127,7 @@ class AliyunNew ksort($parameters); $canonicalQueryString = ''; foreach ($parameters as $key => $value) { - $canonicalQueryString .= '&' . $this->escape($key). '=' . $this->escape($value); + $canonicalQueryString .= '&' . $this->escape($key) . '=' . $this->escape($value); } return substr($canonicalQueryString, 1); } @@ -176,10 +178,10 @@ class AliyunNew if ($httpCode == 200) { return $arr; } elseif ($arr) { - if(strpos($arr['Message'], '.') > 0) $arr['Message'] = substr($arr['Message'], 0, strpos($arr['Message'], '.')+1); + if (strpos($arr['Message'], '.') > 0) $arr['Message'] = substr($arr['Message'], 0, strpos($arr['Message'], '.') + 1); throw new Exception($arr['Message']); } else { throw new Exception('返回数据解析失败'); } } -} \ No newline at end of file +} diff --git a/app/lib/client/Jdcloud.php b/app/lib/client/Jdcloud.php new file mode 100644 index 0000000..75fd7cf --- /dev/null +++ b/app/lib/client/Jdcloud.php @@ -0,0 +1,190 @@ +AccessKeyId = $AccessKeyId; + $this->AccessKeySecret = $AccessKeySecret; + $this->endpoint = $endpoint; + $this->service = $service; + $this->region = $region; + $this->proxy = $proxy; + } + + /** + * @param string $method 请求方法 + * @param string $path 请求路径 + * @param array $params 请求参数 + * @return array + * @throws Exception + */ + public function request($method, $path, $params = []) + { + if (!empty($params)) { + $params = array_filter($params, function ($a) { + return $a !== null; + }); + } + + if ($method == 'GET' || $method == 'DELETE') { + $query = $params; + $body = ''; + } else { + $query = []; + $body = !empty($params) ? json_encode($params) : ''; + } + + $date = gmdate("Ymd\THis\Z"); + $headers = [ + 'Host' => $this->endpoint, + 'x-jdcloud-algorithm' => self::$algorithm, + 'x-jdcloud-date' => $date, + 'x-jdcloud-nonce' => uniqid('php', true), + ]; + if ($body) { + $headers['Content-Type'] = 'application/json'; + } + + $authorization = $this->generateSign($method, $path, $query, $headers, $body, $date); + $headers['authorization'] = $authorization; + + $url = 'https://' . $this->endpoint . $path; + if (!empty($query)) { + $url .= '?' . http_build_query($query); + } + $header = []; + foreach ($headers as $key => $value) { + $header[] = $key . ': ' . $value; + } + return $this->curl($method, $url, $body, $header); + } + + private function generateSign($method, $path, $query, $headers, $body, $date) + { + // step 1: build canonical request string + $httpRequestMethod = $method; + $canonicalUri = $path; + $canonicalQueryString = $this->getCanonicalQueryString($query); + [$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers); + $hashedRequestPayload = hash("sha256", $body); + $canonicalRequest = $httpRequestMethod . "\n" + . $canonicalUri . "\n" + . $canonicalQueryString . "\n" + . $canonicalHeaders . "\n" + . $signedHeaders . "\n" + . $hashedRequestPayload; + + // step 2: build string to sign + $shortDate = substr($date, 0, 8); + $credentialScope = $shortDate . '/' . $this->region . '/' . $this->service . '/jdcloud2_request'; + $hashedCanonicalRequest = hash("sha256", $canonicalRequest); + $stringToSign = self::$algorithm . "\n" + . $date . "\n" + . $credentialScope . "\n" + . $hashedCanonicalRequest; + + // step 3: sign string + $kDate = hash_hmac("sha256", $shortDate, 'JDCLOUD2' . $this->AccessKeySecret, true); + $kRegion = hash_hmac("sha256", $this->region, $kDate, true); + $kService = hash_hmac("sha256", $this->service, $kRegion, true); + $kSigning = hash_hmac("sha256", "jdcloud2_request", $kService, true); + $signature = hash_hmac("sha256", $stringToSign, $kSigning); + + // step 4: build authorization + $credential = $this->AccessKeyId . '/' . $credentialScope; + $authorization = self::$algorithm . ' Credential=' . $credential . ", SignedHeaders=" . $signedHeaders . ", Signature=" . $signature; + + return $authorization; + } + + private function escape($str) + { + $search = ['+', '*', '%7E']; + $replace = ['%20', '%2A', '~']; + return str_replace($search, $replace, urlencode($str)); + } + + private function getCanonicalQueryString($parameters) + { + if (empty($parameters)) return ''; + ksort($parameters); + $canonicalQueryString = ''; + foreach ($parameters as $key => $value) { + $canonicalQueryString .= '&' . $this->escape($key) . '=' . $this->escape($value); + } + return substr($canonicalQueryString, 1); + } + + private function getCanonicalHeaders($oldheaders) + { + $headers = array(); + foreach ($oldheaders as $key => $value) { + $headers[strtolower($key)] = trim($value); + } + ksort($headers); + + $canonicalHeaders = ''; + $signedHeaders = ''; + foreach ($headers as $key => $value) { + $canonicalHeaders .= $key . ':' . $value . "\n"; + $signedHeaders .= $key . ';'; + } + $signedHeaders = substr($signedHeaders, 0, -1); + return [$canonicalHeaders, $signedHeaders]; + } + + private function curl($method, $url, $body, $header) + { + $ch = curl_init($url); + if ($this->proxy) { + curl_set_proxy($ch); + } + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + curl_setopt($ch, CURLOPT_HTTPHEADER, $header); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + if (!empty($body)) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } + $response = curl_exec($ch); + $errno = curl_errno($ch); + if ($errno) { + curl_close($ch); + throw new Exception('Curl error: ' . curl_error($ch)); + } + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + $arr = json_decode($response, true); + if ($httpCode == 200) { + if (isset($arr['result'])) { + return $arr['result']; + } + return $arr; + } else { + if (isset($arr['error']['message'])) { + throw new Exception($arr['error']['message']); + } else { + throw new Exception('返回数据解析失败(http_code=' . $httpCode . ')'); + } + } + } +} diff --git a/app/lib/dns/jdcloud.php b/app/lib/dns/jdcloud.php new file mode 100644 index 0000000..dfee9cc --- /dev/null +++ b/app/lib/dns/jdcloud.php @@ -0,0 +1,246 @@ +AccessKeyId = $config['ak']; + $this->AccessKeySecret = $config['sk']; + $proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false; + $this->client = new JdcloudClient($this->AccessKeyId, $this->AccessKeySecret, $this->endpoint, $this->service, $this->region, $proxy); + $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) + { + $query = ['pageNumber' => $PageNumber, 'pageSize' => $PageSize, 'domainName' => $KeyWord]; + $data = $this->send_request('GET', '/domain', $query); + if ($data) { + $list = []; + if (!empty($data['dataList'])) { + foreach ($data['dataList'] as $row) { + $list[] = [ + 'DomainId' => $row['id'], + 'Domain' => $row['domainName'], + 'RecordCount' => 0, + ]; + } + } + return ['total' => $data['totalCount'], 'list' => $list]; + } + return false; + } + + //获取解析记录列表 + public function getDomainRecords($PageNumber = 1, $PageSize = 20, $KeyWord = null, $SubDomain = null, $Value = null, $Type = null, $Line = null, $Status = null) + { + $query = ['pageNumber' => $PageNumber, 'pageSize' => $PageSize]; + if (!empty($SubDomain)) { + $query += ['search' => $SubDomain]; + } elseif (!empty($KeyWord)) { + $query += ['search' => $KeyWord]; + } + $data = $this->send_request('GET', '/domain/'.$this->domainid.'/ResourceRecord', $query); + if ($data) { + $list = []; + foreach ($data['dataList'] as $row) { + if ($row['type'] == 'SRV') { + $row['hostValue'] = $row['mxPriority'].' '.$row['weight'].' '.$row['port'].' '.$row['hostValue']; + } + $list[] = [ + 'RecordId' => $row['id'], + 'Domain' => $this->domain, + 'Name' => $row['hostRecord'], + 'Type' => $row['type'], + 'Value' => $row['hostValue'], + 'Line' => array_pop($row['viewValue']), + 'TTL' => $row['ttl'], + 'MX' => isset($row['mxPriority']) ? $row['mxPriority'] : null, + 'Status' => $row['resolvingStatus'] == '2' ? '1' : '0', + 'Weight' => $row['weight'], + 'Remark' => null, + 'UpdateTime' => date('Y-m-d H:i:s', $row['updateTime']), + ]; + } + return ['total' => $data['totalCount'], '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 = '0', $TTL = 600, $MX = 1, $Weight = null, $Remark = null) + { + $params = ['hostRecord' => $Name, 'type' => $this->convertType($Type), 'hostValue' => $Value, 'viewValue' => intval($Line), 'ttl' => intval($TTL)]; + if ($Type == 'MX') $params['mxPriority'] = intval($MX); + if (!isNullOrEmpty($Weight)) $params['weight'] = intval($Weight); + if ($Type == 'SRV') { + $values = explode(' ', $Value); + $params['mxPriority'] = intval($values[0]); + $params['weight'] = intval($values[1]); + $params['port'] = intval($values[2]); + $params['hostValue'] = $values[3]; + } + $data = $this->send_request('POST', '/domain/'.$this->domainid.'/ResourceRecord', ['req'=>$params]); + return is_array($data) ? $data['dataList']['id'] : false; + } + + //修改解析记录 + public function updateDomainRecord($RecordId, $Name, $Type, $Value, $Line = '0', $TTL = 600, $MX = 1, $Weight = null, $Remark = null) + { + $params = ['domainName'=>$this->domain, 'hostRecord' => $Name, 'type' => $this->convertType($Type), 'hostValue' => $Value, 'viewValue' => intval($Line), 'ttl' => intval($TTL)]; + if ($Type == 'MX') $params['mxPriority'] = intval($MX); + if (!isNullOrEmpty($Weight)) $params['weight'] = intval($Weight); + if ($Type == 'SRV') { + $values = explode(' ', $Value); + $params['mxPriority'] = intval($values[0]); + $params['weight'] = intval($values[1]); + $params['port'] = intval($values[2]); + $params['hostValue'] = $values[3]; + } + return $this->send_request('PUT', '/domain/'.$this->domainid.'/ResourceRecord/'.$RecordId, ['req'=>$params]); + } + + //修改解析记录备注 + public function updateDomainRecordRemark($RecordId, $Remark) + { + return false; + } + + //删除解析记录 + public function deleteDomainRecord($RecordId) + { + return $this->send_request('DELETE', '/domain/'.$this->domainid.'/ResourceRecord/'.$RecordId); + } + + //设置解析记录状态 + public function setDomainRecordStatus($RecordId, $Status) + { + $params = ['action' => $Status == '1' ? 'enable' : 'disable']; + $data = $this->send_request('PUT', '/domain/'.$this->domainid.'/ResourceRecord/'.$RecordId.'/status', $params); + return is_array($data); + } + + //获取解析记录操作日志 + public function getDomainRecordLog($PageNumber = 1, $PageSize = 20, $KeyWord = null, $StartDate = null, $endDate = null) + { + return false; + } + + //获取解析线路列表 + public function getRecordLine() + { + $domainInfo = $this->getDomainInfo(); + if (!$domainInfo) return false; + $packId = $domainInfo['packId']; + $data = $this->send_request('GET', '/domain/'.$this->domainid.'/viewTree', ['packId'=>$packId, 'viewId'=>'0']); + if ($data) { + $list = []; + $this->processLineList($list, $data['data'], null); + return $list; + } + return false; + } + + private function processLineList(&$list, $line_list, $parent) + { + foreach ($line_list as $row) { + if ($row['disabled']) continue; + if (!isset($list[$row['value']])) { + $list[$row['value']] = ['name' => $row['label'], 'parent' => $parent]; + if (!$row['leaf'] && $row['children']) { + $this->processLineList($list, $row['children'], $row['value']); + } + } + } + } + + //获取域名概览信息 + public function getDomainInfo() + { + if (!empty($this->domainInfo)) return $this->domainInfo; + $query = ['domainId' => intval($this->domainid)]; + $data = $this->send_request('GET', '/domain', $query); + if ($data && $data['dataList']) { + return $data['dataList'][0]; + } + return false; + } + + //获取域名最低TTL + public function getMinTTL() + { + return false; + } + + private function convertType($type) + { + $convert_dict = ['REDIRECT_URL' => 'EXPLICIT_URL', 'FORWARD_URL' => 'IMPLICIT_URL']; + if (array_key_exists($type, $convert_dict)) { + return $convert_dict[$type]; + } + return $type; + } + + private function send_request($method, $action, $params = []) + { + $path = '/'.$this->version.'/regions/'.$this->region.$action; + try{ + return $this->client->request($method, $path, $params); + }catch(Exception $e){ + $this->setError($e->getMessage()); + 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/service/CertTaskService.php b/app/service/CertTaskService.php index 93578e2..b360b3e 100644 --- a/app/service/CertTaskService.php +++ b/app/service/CertTaskService.php @@ -20,7 +20,7 @@ class CertTaskService private function execute_order() { $days = config_get('cert_renewdays', 7); - $list = Db::name('cert_order')->field('id,status,issend')->whereRaw('status NOT IN (3,4) AND (retrytime IS NULL OR retrytime date('Y-m-d H:i:s', time() + $days * 86400)])->select(); + $list = Db::name('cert_order')->field('id,status,issend')->whereRaw('isauto=1 AND status NOT IN (3,4) AND (retrytime IS NULL OR retrytime date('Y-m-d H:i:s', time() + $days * 86400)])->select(); //print_r($list);exit; $failcount = 0; foreach ($list as $row) { @@ -67,7 +67,7 @@ class CertTaskService } } - $list = Db::name('cert_deploy')->field('id,status,issend')->whereRaw('status IN (0,-1) AND (retrytime IS NULL OR retrytimeselect(); + $list = Db::name('cert_deploy')->field('id,status,issend')->whereRaw('active=1 AND status IN (0,-1) AND (retrytime IS NULL OR retrytimeselect(); //print_r($list);exit; $count = 0; foreach ($list as $row) { diff --git a/app/view/cert/certorder.html b/app/view/cert/certorder.html index 9e7bfde..c1bcb75 100644 --- a/app/view/cert/certorder.html +++ b/app/view/cert/certorder.html @@ -2,7 +2,7 @@ {block name="title"}SSL证书订单列表{/block} {block name="main"} @@ -35,6 +35,10 @@ pre.pre-log{height: 330px;overflow-y: auto;width: 100%;background-color: rgba(51 刷新 添加 + @@ -64,6 +68,10 @@ $(document).ready(function(){ classes: 'table table-striped table-hover table-bordered', uniqueId: 'id', columns: [ + { + field: '', + checkbox: true + }, { field: 'id', title: 'ID' @@ -285,5 +293,39 @@ function showLog(processid){ } }, 'json'); } +function operation(action){ + var rows = $("#listTable").bootstrapTable('getSelections'); + if(rows.length == 0){ + layer.msg('请选择要操作的任务'); + return; + } + var ids = []; + for(var i in rows){ + ids.push(rows[i].id); + } + if(action == 'delete'){ + if(!confirm('确定要删除所选自动部署任务吗?')) return; + }else if(action == 'reset'){ + if(!confirm('重置任务后,任务将变成待处理状态,是否确定重置?')) return; + } + + var ii = layer.load(2); + $.ajax({ + type : 'POST', + url : '/cert/deploy/operation', + data : {action: action, ids: ids}, + dataType : 'json', + success : function(data) { + layer.close(ii); + if(data.code == 0){ + layer.closeAll(); + layer.alert(data.msg, {icon: 1}); + searchRefresh(); + }else{ + layer.alert(data.msg, {icon: 2}); + } + } + }); +} {/block} \ No newline at end of file diff --git a/config/app.php b/config/app.php index 590c1bf..ec7e6f0 100644 --- a/config/app.php +++ b/config/app.php @@ -31,7 +31,7 @@ return [ 'show_error_msg' => true, 'exception_tmpl' => \think\facade\App::getAppPath() . 'view/exception.tpl', - 'version' => '1030', + 'version' => '1031', 'dbversion' => '1028' ]; diff --git a/public/static/images/jdcloud.ico b/public/static/images/jdcloud.ico new file mode 100644 index 0000000000000000000000000000000000000000..5f246e6e4d1dc8479280a92cefbb048fa18ab09d GIT binary patch literal 16958 zcmeI3d5m3C9LLY}wSCoMELGGp)S4DaQA>%L5Hz(kg0?}?(2CMnqK&kbc})ose^?_D zOCuqomDtihM0nQNRn@*dVrh_iXc>&TK3{#OJ z&p@7mJOge|xDzgbeKuG(*TTmDI220o z7<>e4!Opvy_DWDpb724GW{O+kb9BxBA4ofIR{1_Q*TEj~8G4VPzauP!uOaJPR!=#a z3tRQ}oV^G8!Eg<9gZ164?7_z)Fg7g$##`Zf(EHTvT-L6Nk9jaCZO+gbHpAfsu)g|A zDi3!-uV+Kw39t(KYA)+%IX({v-Dju|UETY8n;;GwkK~syPV*!6p4|%Wcwe_wGfoNJ zuiJ-C5$ZknSI}7iC&PF+2#y2QyxTzas9yZvp>rhYeSa#0zUtilFdHVoI5-3*!e#If ztbuy-PIvFuZ)30Z9`o@sV~6=Rp=j1KK*g``c1~CLftit&JdUm z+M8S-Zu?KhrbJ>M!`M6UIq3Pg`Mi8lo$XV;7DMJ|h3+VD*Om1p`8o`P?OQN;IeIwI;>(3Rs#@D%?e|r!5er4OP>?HXr&E;I)XI&4bp_ zp7Md5AJILjHa?E=Un4pX#^PwSAODXB=6IMst$Q1{m9x_Pqpx*y9#jLi)`x1s{-E4H z>*>Ab`e!Wm4#?N#u5Pn!{4DTdknfeCdTV30a%q-%u-hqOZ~Z6-dxOsU?s@kN-#_Cz zXIeW~>fAcP*KhP1AF64t-(R@?39k0~rK3G_c$PWPD}eSu*SFjLk}=g%?Qb7|_LlYM zFFOZ)gWx%EZEaii_i(G*jKuyl$G7UMt?98EcylB3;7|2Y&&tN^$_oA4!(Q-r%@6rz zOy>aQ%eA#_J+Db2yDWX!&Z_y{H68jtmBT)O`8v0#l8sip15{d+D~A!{&#oUW1t%GqxCi|Etb$2HoJi8-5u~W zd;(gJI&*#sI@d0RufdO5`^V-XzkVNjTZ8K9i=gl8zAt;N_eC{#Ki`M0=|2k6*45RF z*WF`6zC(TZ+8JW+-RzJ}y*~VH4u`<0pxmDh9k63PTaBZ4BBa$I^A_mMhI%!-?%%=sI#?yT5rd|8L$AJgxK?T zeO}mK_m*`Y;?^Fs7~DAfx~+P1Zq}Io4L#yJ1!DDTWAdRrvCn=--ITc@&MiTEU^C|p zKS%PRvkU*X$N%d?%)>!7yaN7x)w_NRo4Jtbx2ykhX1E7^C)Zg+_p<~(f*(LR(D_~G zym#O}xEOl