diff --git a/app/common.php b/app/common.php index 8d87b95..f654125 100644 --- a/app/common.php +++ b/app/common.php @@ -482,4 +482,23 @@ function convertDomainToUtf8($domain) { } else { return $domain; } -} \ No newline at end of file +} + +function getDomainDate($domain) +{ + try { + $whois = \Iodev\Whois\Factory::get()->createWhois(); + $info = $whois->loadDomainInfo($domain); + if ($info) { + if ($info->expirationDate > 0) { + return [date('Y-m-d H:i:s', $info->creationDate), date('Y-m-d H:i:s', $info->expirationDate)]; + } else { + throw new Exception('域名到期时间未知'); + } + } else { + throw new Exception('域名信息未找到'); + } + } catch (Exception $e) { + throw new Exception('查询域名whois失败: ' . $e->getMessage()); + } +} diff --git a/app/controller/Domain.php b/app/controller/Domain.php index 6eab4d5..a78a4cc 100644 --- a/app/controller/Domain.php +++ b/app/controller/Domain.php @@ -5,8 +5,9 @@ namespace app\controller; use app\BaseController; use think\facade\Db; use think\facade\View; -use think\facade\Request; +use think\facade\Cache; use app\lib\DnsHelper; +use app\service\ExpireNoticeService; use Exception; class Domain extends BaseController @@ -179,6 +180,7 @@ class Domain extends BaseController if (!checkPermission(1)) return json(['total' => 0, 'rows' => []]); $kw = input('post.kw', null, 'trim'); $type = input('post.type', null, 'trim'); + $status = input('post.status', null, 'trim'); $offset = input('post.offset/d', 0); $limit = input('post.limit/d', 10); @@ -192,6 +194,13 @@ class Domain extends BaseController if (request()->user['level'] == 1) { $select->where('is_hide', 0)->where('A.name', 'in', request()->user['permission']); } + if (!isNullOrEmpty($status)) { + if ($status == '2') { + $select->where('A.expiretime', '<=', date('Y-m-d H:i:s')); + } elseif ($status == '1') { + $select->where('A.expiretime', '<=', date('Y-m-d H:i:s', time() + 86400 * 30))->where('A.expiretime', '>', date('Y-m-d H:i:s')); + } + } $total = $select->count(); $rows = $select->fieldRaw('A.*,B.type,B.remark aremark')->order('A.id', 'desc')->limit($offset, $limit)->select(); @@ -240,11 +249,13 @@ class Domain extends BaseController if (!$row) return json(['code' => -1, 'msg' => '域名不存在']); $is_hide = input('post.is_hide/d'); $is_sso = input('post.is_sso/d'); + $is_notice = input('post.is_notice/d'); $remark = input('post.remark', null, 'trim'); if (empty($remark)) $remark = null; Db::name('domain')->where('id', $id)->update([ 'is_hide' => $is_hide, 'is_sso' => $is_sso, + 'is_notice' => $is_notice, 'remark' => $remark, ]); return json(['code' => 0, 'msg' => '修改域名配置成功!']); @@ -280,8 +291,15 @@ class Domain extends BaseController if (empty($ids)) return json(['code' => -1, 'msg' => '参数不能为空']); $remark = input('post.remark', null, 'trim'); if (empty($remark)) $remark = null; - Db::name('domain')->where('id', 'in', $ids)->update(['remark' => $remark]); - return json(['code' => 0, 'msg' => '成功修改' . count($ids) . '个域名!']); + $count = Db::name('domain')->where('id', 'in', $ids)->update(['remark' => $remark]); + return json(['code' => 0, 'msg' => '成功修改' . $count . '个域名!']); + } elseif ($act == 'batchsetnotice') { + if (!checkPermission(2)) return $this->alert('error', '无权限'); + $ids = input('post.ids'); + $is_notice = input('post.is_notice/d', 0); + if (empty($ids)) return json(['code' => -1, 'msg' => '参数不能为空']); + $count = Db::name('domain')->where('id', 'in', $ids)->update(['is_notice' => $is_notice]); + return json(['code' => 0, 'msg' => '成功修改' . $count . '个域名!']); } elseif ($act == 'batchdel') { if (!checkPermission(2)) return $this->alert('error', '无权限'); $ids = input('post.ids'); @@ -1029,4 +1047,33 @@ class Domain extends BaseController $domainRecords = $dns->getWeightSubDomains($page, $limit, $keyword); return json(['total' => $domainRecords['total'], 'rows' => $domainRecords['list']]); } + + public function expire_notice() + { + if (!checkPermission(2)) return $this->alert('error', '无权限'); + if ($this->request->isPost()) { + $params = input('post.'); + foreach ($params as $key => $value) { + if (empty($key)) { + continue; + } + config_set($key, $value); + Cache::delete('configs'); + } + return json(['code' => 0, 'msg' => 'succ']); + } + return View::fetch(); + } + + public function update_date() + { + $id = input('param.id/d'); + $drow = Db::name('domain')->where('id', $id)->find(); + if (!$drow) { + return json(['code' => -1, 'msg' => '域名不存在']); + } + if (!checkPermission(0, $drow['name'])) return json(['code' => -1, 'msg' => '无权限']); + $result = (new ExpireNoticeService())->updateDomainDate($id, $drow['name']); + return json($result); + } } diff --git a/app/lib/DeployHelper.php b/app/lib/DeployHelper.php index f07057a..4a09afc 100644 --- a/app/lib/DeployHelper.php +++ b/app/lib/DeployHelper.php @@ -890,7 +890,7 @@ class DeployHelper 'domain' => [ 'name' => '绑定的域名', 'type' => 'input', - 'placeholder' => '', + 'placeholder' => '多个域名可使用,分隔', 'show' => 'product==\'cdn\'', 'required' => true, ], @@ -1142,6 +1142,7 @@ class DeployHelper ['value'=>'cdn', 'label'=>'内容分发网络CDN'], ['value'=>'dcdn', 'label'=>'全站加速DCDN'], ['value'=>'clb', 'label'=>'负载均衡CLB'], + ['value'=>'alb', 'label'=>'应用型负载均衡ALB'], ['value'=>'tos', 'label'=>'对象存储TOS'], ['value'=>'live', 'label'=>'视频直播'], ['value'=>'imagex', 'label'=>'veImageX'], @@ -1160,14 +1161,14 @@ class DeployHelper 'name' => '绑定的域名', 'type' => 'input', 'placeholder' => '多个域名可使用,分隔', - 'show' => 'product!=\'clb\'', + 'show' => 'product!=\'clb\'&&product!=\'alb\'', 'required' => true, ], 'listener_id' => [ 'name' => '监听器ID', 'type' => 'input', 'placeholder' => '', - 'show' => 'product==\'clb\'', + 'show' => 'product==\'clb\'||product==\'alb\'', 'required' => true, ], ], diff --git a/app/lib/deploy/huawei.php b/app/lib/deploy/huawei.php index 0006248..b562177 100644 --- a/app/lib/deploy/huawei.php +++ b/app/lib/deploy/huawei.php @@ -58,8 +58,10 @@ class huawei implements DeployInterface ], ], ]; - $client->request('PUT', '/v1.1/cdn/configuration/domains/' . $config['domain'] . '/configs', null, $param); - $this->log('CDN域名 ' . $config['domain'] . ' 部署证书成功!'); + foreach (explode(',', $config['domain']) as $domain) { + $client->request('PUT', '/v1.1/cdn/configuration/domains/' . $domain . '/configs', null, $param); + $this->log('CDN域名 ' . $domain . ' 部署证书成功!'); + } } private function deploy_elb($fullchain, $privatekey, $config) diff --git a/app/lib/deploy/huoshan.php b/app/lib/deploy/huoshan.php index 5fba09b..107e64a 100644 --- a/app/lib/deploy/huoshan.php +++ b/app/lib/deploy/huoshan.php @@ -46,6 +46,8 @@ class huoshan implements DeployInterface $this->deploy_imagex($cert_id, $config); } elseif ($config['product'] == 'clb') { $this->deploy_clb($cert_id, $config); + } elseif ($config['product'] == 'alb') { + $this->deploy_alb($cert_id, $config); } } } @@ -167,6 +169,19 @@ class huoshan implements DeployInterface $this->log('CLB监听器 ' . $config['listener_id'] . ' 部署证书成功!'); } + private function deploy_alb($cert_id, $config) + { + if (empty($config['listener_id'])) throw new Exception('监听器ID不能为空'); + $client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'alb', '2020-04-01', 'cn-beijing', $this->proxy); + $param = [ + 'ListenerId' => $config['listener_id'], + 'CertificateSource' => 'cert_center', + 'CertCenterCertificateId' => $cert_id, + ]; + $client->request('GET', 'ModifyListenerAttributes', $param); + $this->log('ALB监听器 ' . $config['listener_id'] . ' 部署证书成功!'); + } + private function get_cert_id($fullchain, $privatekey) { $certInfo = openssl_x509_parse($fullchain, true); diff --git a/app/service/CertTaskService.php b/app/service/CertTaskService.php index af942db..77aeb6e 100644 --- a/app/service/CertTaskService.php +++ b/app/service/CertTaskService.php @@ -13,6 +13,7 @@ class CertTaskService { $this->execute_deploy(); $this->execute_order(); + (new ExpireNoticeService())->task(); config_set('certtask_time', date("Y-m-d H:i:s")); echo 'done'.PHP_EOL; } diff --git a/app/service/ExpireNoticeService.php b/app/service/ExpireNoticeService.php new file mode 100644 index 0000000..e5fd2e2 --- /dev/null +++ b/app/service/ExpireNoticeService.php @@ -0,0 +1,102 @@ +where('id', $id)->update(['regtime' => $regTime, 'expiretime' => $expireTime, 'checktime' => date('Y-m-d H:i:s'), 'checkstatus' => 1]); + return ['code' => 0, 'regTime' => $regTime, 'expireTime' => $expireTime, 'msg' => 'Success']; + } catch (Exception $e) { + Db::name('domain')->where('id', $id)->update(['checktime' => date('Y-m-d H:i:s'), 'checkstatus' => 2]); + return ['code' => -1, 'msg' => $e->getMessage()]; + } + } + + public function task() + { + $count = $this->refreshDomainList(); + if ($count > 0) return; + + $days = config_get('expire_noticedays'); + $max_day = 30; + if (!empty($days)) { + $days = explode(',', $days); + $days = array_map('intval', $days); + $max_day = max($days) + 1; + } + $count = $this->refreshExpiringDomainList($max_day); + if ($count > 0) return; + + if (!empty($days) && (config_get('expire_notice_mail') == '1' || config_get('expire_notice_wxtpl') == '1' || config_get('expire_notice_tgbot') == '1' || config_get('expire_notice_webhook') == '1') && date('H') >= 9) { + $this->noticeExpiringDomainList($max_day, $days); + } + } + + private function refreshDomainList() + { + $domainList = Db::name('domain')->field('id,name')->where('expiretime', null)->where('checkstatus', 0)->select(); + $count = 0; + foreach ($domainList as $domain) { + $res = $this->updateDomainDate($domain['id'], $domain['name']); + if ($res['code'] == 0) { + echo '域名: ' . $domain['name'] . ' 注册时间: ' . $res['regTime'] . ' 到期时间: ' . $res['expireTime'] . PHP_EOL; + } else { + echo '域名: ' . $domain['name'] . ' 更新失败,' . $res['msg'] . PHP_EOL; + } + $count++; + if ($count >= 5) break; + sleep(1); + } + return $count; + } + + private function refreshExpiringDomainList($max_day) + { + $domainList = Db::name('domain')->field('id,name')->whereRaw('expiretime>=(NOW() - INTERVAL 5 DAY) AND expiretime<=(NOW() + INTERVAL ' . $max_day . ' DAY) AND checktime<=(NOW() - INTERVAL 1 DAY)')->select(); + $count = 0; + foreach ($domainList as $domain) { + $res = $this->updateDomainDate($domain['id'], $domain['name']); + if ($res['code'] == 0) { + echo '域名: ' . $domain['name'] . ' 注册时间: ' . $res['regTime'] . ' 到期时间: ' . $res['expireTime'] . PHP_EOL; + } else { + echo '域名: ' . $domain['name'] . ' 更新失败,' . $res['msg'] . PHP_EOL; + } + $count++; + if ($count >= 5) break; + sleep(1); + } + return $count; + } + + private function noticeExpiringDomainList($max_day, $days) + { + $domainList = Db::name('domain')->field('id,name,expiretime')->whereRaw('expiretime>=NOW() AND expiretime<=(NOW() + INTERVAL ' . $max_day . ' DAY) AND is_notice=1 AND (noticetime IS NULL OR noticetime<=(NOW() - INTERVAL 20 HOUR))')->order('expiretime', 'asc')->select(); + $noticeList = []; + foreach ($domainList as $domain) { + $expireDay = intval((strtotime($domain['expiretime']) - time()) / 86400); + if (in_array($expireDay, $days)) { + $noticeList[$expireDay][] = ['id' => $domain['id'], 'name' => $domain['name'], 'expiretime' => $domain['expiretime']]; + } + } + if (!empty($noticeList)) { + foreach ($noticeList as $day => $list) { + $ids = array_column($list, 'id'); + Db::name('domain')->whereIn('id', $ids)->update(['noticetime' => date('Y-m-d H:i:s')]); + MsgNotice::expire_notice_send($day, $list); + echo '域名到期提醒: ' . $day . '天内到期的' . count($ids) . '个域名已发送' . PHP_EOL; + } + } + } +} diff --git a/app/sql/install.sql b/app/sql/install.sql index de1fd85..cf73ed8 100644 --- a/app/sql/install.sql +++ b/app/sql/install.sql @@ -5,7 +5,7 @@ CREATE TABLE `dnsmgr_config` ( PRIMARY KEY (`key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -INSERT INTO `dnsmgr_config` VALUES ('version', '1028'); +INSERT INTO `dnsmgr_config` VALUES ('version', '1033'); INSERT INTO `dnsmgr_config` VALUES ('notice_mail', '0'); INSERT INTO `dnsmgr_config` VALUES ('notice_wxtpl', '0'); INSERT INTO `dnsmgr_config` VALUES ('mail_smtp', 'smtp.qq.com'); @@ -35,6 +35,12 @@ CREATE TABLE `dnsmgr_domain` ( `is_sso` tinyint(1) NOT NULL DEFAULT '0', `recordcount` int(1) NOT NULL DEFAULT '0', `remark` varchar(100) DEFAULT NULL, + `is_notice` tinyint(1) NOT NULL DEFAULT '0', + `regtime` datetime DEFAULT NULL, + `expiretime` datetime DEFAULT NULL, + `checktime` datetime DEFAULT NULL, + `noticetime` datetime DEFAULT NULL, + `checkstatus` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/app/sql/update.sql b/app/sql/update.sql index a6e415c..3c9db10 100644 --- a/app/sql/update.sql +++ b/app/sql/update.sql @@ -155,4 +155,12 @@ ALTER TABLE `dnsmgr_account` ADD COLUMN `proxy` tinyint(1) NOT NULL DEFAULT '0'; ALTER TABLE `dnsmgr_dmtask` -ADD COLUMN `cdn` tinyint(1) NOT NULL DEFAULT 0; \ No newline at end of file +ADD COLUMN `cdn` tinyint(1) NOT NULL DEFAULT 0; + +ALTER TABLE `dnsmgr_domain` +ADD COLUMN `is_notice` tinyint(1) NOT NULL DEFAULT '0', +ADD COLUMN `regtime` datetime DEFAULT NULL, +ADD COLUMN `expiretime` datetime DEFAULT NULL, +ADD COLUMN `checktime` datetime DEFAULT NULL, +ADD COLUMN `noticetime` datetime DEFAULT NULL, +ADD COLUMN `checkstatus` tinyint(1) NOT NULL DEFAULT '0'; \ No newline at end of file diff --git a/app/utils/MsgNotice.php b/app/utils/MsgNotice.php index 491b541..4820924 100644 --- a/app/utils/MsgNotice.php +++ b/app/utils/MsgNotice.php @@ -133,6 +133,34 @@ class MsgNotice } } + public static function expire_notice_send($day, $list) + { + $mail_title = '您有'.count($list).'个域名即将在'.$day.'天后到期'; + $mail_content = '尊敬的用户,您好:您有'.count($list).'个域名即将在'.$day.'天后到期!
域名&到期时间:
'; + foreach ($list as $domain) { + $mail_content .= ''.$domain['name'].' - '.$domain['expiretime'].'
'; + } + $mail_content .= '
'.self::$sitename.'
'.date('Y-m-d H:i:s').''; + + if (config_get('expire_notice_mail') == 1 || config_get('expire_notice_mail') == 2) { + $mail_name = config_get('mail_recv') ? config_get('mail_recv') : config_get('mail_name'); + self::send_mail($mail_name, $mail_title, $mail_content); + } + if (config_get('expire_notice_wxtpl') == 1 || config_get('expire_notice_wxtpl') == 2) { + $content = str_replace(['
', '', ''], ["\n\n", '**', '**'], $mail_content); + self::send_wechat_tplmsg($mail_title, strip_tags($content)); + } + if (config_get('expire_notice_tgbot') == 1 || config_get('expire_notice_tgbot') == 2) { + $content = str_replace('
', "\n", $mail_content); + $content = "".$mail_title."\n".strip_tags($content); + self::send_telegram_bot($content); + } + if (config_get('expire_notice_webhook') == 1) { + $content = str_replace(['*', '
', '', ''], ['\*', "\n", '**', '**'], $mail_content); + self::send_webhook($mail_title, $content); + } + } + public static function send_mail($to, $sub, $msg) { $mail_type = config_get('mail_type'); diff --git a/app/view/common/layout.html b/app/view/common/layout.html index fcc2257..f1f5142 100644 --- a/app/view/common/layout.html +++ b/app/view/common/layout.html @@ -103,7 +103,7 @@ {if request()->user['type'] eq 'user'}
  • 后台首页
  • {/if} -
  • +
  • 域名管理
  • {if request()->user['level'] eq 2} diff --git a/app/view/dmonitor/taskform.html b/app/view/dmonitor/taskform.html index 123c637..5c28fa2 100644 --- a/app/view/dmonitor/taskform.html +++ b/app/view/dmonitor/taskform.html @@ -17,11 +17,16 @@
    -
    -
    +
    +
    + + . + +
    +
    diff --git a/app/view/domain/domain.html b/app/view/domain/domain.html index d3193c6..a18c35f 100644 --- a/app/view/domain/domain.html +++ b/app/view/domain/domain.html @@ -50,6 +50,18 @@