diff --git a/app/common.php b/app/common.php index eec1643..f334612 100644 --- a/app/common.php +++ b/app/common.php @@ -7,6 +7,7 @@ function get_curl($url, $post = 0, $referer = 0, $cookie = 0, $header = 0, $ua = { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $httpheader[] = "Accept: */*"; diff --git a/app/controller/System.php b/app/controller/System.php index 9411a14..f5d126e 100644 --- a/app/controller/System.php +++ b/app/controller/System.php @@ -76,6 +76,20 @@ class System extends BaseController } } + public function webhooktest() + { + if (!checkPermission(2)) return $this->alert('error', '无权限'); + $webhook_url = config_get('webhook_url'); + if (empty($webhook_url)) return json(['code' => -1, 'msg' => '请先保存设置']); + $content = "这是一封测试消息!\n来自:" . $this->request->root(true); + $result = \app\utils\MsgNotice::send_webhook('消息发送测试', $content); + if ($result === true) { + return json(['code' => 0, 'msg' => '消息发送成功!']); + } else { + return json(['code' => -1, 'msg' => '消息发送失败!' . $result]); + } + } + public function proxytest() { if (!checkPermission(2)) return $this->alert('error', '无权限'); diff --git a/app/lib/DeployHelper.php b/app/lib/DeployHelper.php index 72e4cd2..b81c697 100644 --- a/app/lib/DeployHelper.php +++ b/app/lib/DeployHelper.php @@ -342,6 +342,63 @@ class DeployHelper ], 'taskinputs' => [], ], + 'mwpanel' => [ + 'name' => 'mdserver-web', + 'class' => 1, + 'icon' => 'mwpanel.ico', + 'note' => null, + 'tasknote' => '', + 'inputs' => [ + 'url' => [ + 'name' => '面板地址', + 'type' => 'input', + 'placeholder' => 'MW面板地址', + 'note' => '填写规则如:http://192.168.1.100:8888 ,不要带其他后缀', + 'required' => true, + ], + 'appid' => [ + 'name' => '应用ID', + 'type' => 'input', + 'placeholder' => 'MW面板设置->API接口', + 'required' => true, + ], + 'appsecret' => [ + 'name' => '应用密钥', + 'type' => 'input', + 'placeholder' => '面板设置->API接口', + 'required' => true, + ], + 'proxy' => [ + 'name' => '使用代理服务器', + 'type' => 'radio', + 'options' => [ + '0' => '否', + '1' => '是', + ], + 'value' => '0' + ], + ], + 'taskinputs' => [ + 'type' => [ + 'name' => '部署类型', + 'type' => 'radio', + 'options' => [ + '0' => 'MW面板站点的证书', + '1' => 'MW面板本身的证书', + ], + 'value' => '0', + 'required' => true, + ], + 'sites' => [ + 'name' => '网站名称列表', + 'type' => 'textarea', + 'placeholder' => '填写要部署证书的网站名称,每行一个', + 'note' => '网站名称,即为网站创建时绑定的第一个域名', + 'show' => 'type==0', + 'required' => true, + ], + ], + ], 'aliyun' => [ 'name' => '阿里云', 'class' => 2, diff --git a/app/lib/deploy/lecdn.php b/app/lib/deploy/lecdn.php index 2e64c99..83b545b 100644 --- a/app/lib/deploy/lecdn.php +++ b/app/lib/deploy/lecdn.php @@ -62,8 +62,8 @@ class lecdn implements DeployInterface 'password' => $this->password, ]; $result = $this->request($path, $params); - if (isset($result['access_token'])) { - $this->accessToken = $result['access_token']; + if (isset($result['token'])) { + $this->accessToken = $result['token']; } else { throw new Exception('登录成功,获取access_token失败'); } @@ -83,7 +83,7 @@ class lecdn implements DeployInterface } $response = curl_client($url, $body, null, null, $headers, $this->proxy, $method); $result = json_decode($response['body'], true); - if (isset($result['code']) && $result['code'] == 0) { + if (isset($result['code']) && $result['code'] == 200) { return isset($result['data']) ? $result['data'] : null; } elseif (isset($result['message'])) { throw new Exception($result['message']); diff --git a/app/lib/deploy/mwpanel.php b/app/lib/deploy/mwpanel.php new file mode 100644 index 0000000..f3fdc6a --- /dev/null +++ b/app/lib/deploy/mwpanel.php @@ -0,0 +1,127 @@ +url = rtrim($config['url'], '/'); + $this->appid = $config['appid']; + $this->appsecret = $config['appsecret']; + $this->proxy = $config['proxy'] == 1; + } + + public function check() + { + if (empty($this->url) || empty($this->appid) || empty($this->appsecret)) throw new Exception('请填写面板地址和接口密钥'); + + $path = '/task/count'; + $response = $this->request($path); + $result = json_decode($response, true); + if (isset($result['status']) && $result['status'] == true) { + return true; + } else { + throw new Exception(isset($result['msg']) ? $result['msg'] : '面板地址无法连接'); + } + } + + public function deploy($fullchain, $privatekey, $config, &$info) + { + if ($config['type'] == '1') { + $this->deployPanel($fullchain, $privatekey); + $this->log("面板证书部署成功"); + return; + } + $sites = explode("\n", $config['sites']); + $success = 0; + $errmsg = null; + foreach ($sites as $site) { + $siteName = trim($site); + if (empty($siteName)) continue; + try { + $this->deploySite($siteName, $fullchain, $privatekey); + $this->log("网站 {$siteName} 证书部署成功"); + $success++; + } catch (Exception $e) { + $errmsg = $e->getMessage(); + $this->log("网站 {$siteName} 证书部署失败:" . $errmsg); + } + } + if ($success == 0) { + throw new Exception($errmsg ? $errmsg : '要部署的网站不存在'); + } + } + + private function deployPanel($fullchain, $privatekey) + { + $path = '/setting/save_panel_ssl'; + $data = [ + 'privateKey' => $privatekey, + 'certPem' => $fullchain, + 'choose' => 'local', + ]; + $response = $this->request($path, $data); + $result = json_decode($response, true); + if (isset($result['status']) && $result['status']) { + return true; + } elseif (isset($result['msg'])) { + throw new Exception($result['msg']); + } else { + throw new Exception($response ? $response : '返回数据解析失败'); + } + } + + private function deploySite($siteName, $fullchain, $privatekey) + { + $path = '/site/set_ssl'; + $data = [ + 'type' => '1', + 'siteName' => $siteName, + 'key' => $privatekey, + 'csr' => $fullchain, + ]; + $response = $this->request($path, $data); + $result = json_decode($response, true); + if (isset($result['status']) && $result['status']) { + return true; + } elseif (isset($result['msg'])) { + throw new Exception($result['msg']); + } else { + throw new Exception($response ? $response : '返回数据解析失败'); + } + } + + public function setLogger($func) + { + $this->logger = $func; + } + + private function log($txt) + { + if ($this->logger) { + call_user_func($this->logger, $txt); + } + } + + private function request($path, $params = null) + { + $url = $this->url . $path; + + $headers = [ + 'app-id: '.$this->appid, + 'app-secret: '.$this->appsecret, + ]; + $response = curl_client($url, $params ? http_build_query($params) : null, null, null, $headers, $this->proxy); + return $response['body']; + } +} diff --git a/app/service/CertTaskService.php b/app/service/CertTaskService.php index c953ce8..64f38ce 100644 --- a/app/service/CertTaskService.php +++ b/app/service/CertTaskService.php @@ -31,7 +31,7 @@ class CertTaskService $retcode = $service->process(); if ($retcode == 3) { echo 'ID:'.$row['id'].' 证书已签发成功!'.PHP_EOL; - if($row['issend'] == 0) MsgNotice::cert_send($row['id'], true); + if($row['issend'] == 0) MsgNotice::cert_order_send($row['id'], true); } elseif ($retcode == 1) { echo 'ID:'.$row['id'].' 添加DNS记录成功!'.PHP_EOL; } @@ -41,7 +41,7 @@ class CertTaskService if ($e->getCode() == 102) { break; } elseif ($e->getCode() == 103) { - if($row['issend'] == 0) MsgNotice::cert_send($row['id'], false); + if($row['issend'] == 0) MsgNotice::cert_order_send($row['id'], false); } else { $failcount++; } @@ -74,14 +74,14 @@ class CertTaskService $service = new CertDeployService($row['id']); $service->process(); echo 'ID:'.$row['id'].' 部署任务执行成功!'.PHP_EOL; - if($row['issend'] == 0) MsgNotice::deploy_send($row['id'], true); + if($row['issend'] == 0) MsgNotice::cert_deploy_send($row['id'], true); $count++; } catch (Exception $e) { echo 'ID:'.$row['id'].' '.$e->getMessage().PHP_EOL; if ($e->getCode() == 102) { break; } elseif ($e->getCode() == 103) { - if($row['issend'] == 0) MsgNotice::deploy_send($row['id'], false); + if($row['issend'] == 0) MsgNotice::cert_deploy_send($row['id'], false); } else { $count++; } diff --git a/app/utils/MsgNotice.php b/app/utils/MsgNotice.php index 5fb8e6c..9c1fd66 100644 --- a/app/utils/MsgNotice.php +++ b/app/utils/MsgNotice.php @@ -13,7 +13,7 @@ class MsgNotice { if ($action == 1) { $mail_title = 'DNS容灾切换-发生告警通知'; - $mail_content = '尊敬的系统管理员,您好:
您的域名 '.$task['domain'].''.$task['main_value'].' 记录发生了异常'; + $mail_content = '尊敬的用户,您好:
您的域名 '.$task['domain'].''.$task['main_value'].' 记录发生了异常'; if ($task['type'] == 2) { $mail_content .= ',已自动切换为备用解析记录 '.$task['backup_value'].' '; } elseif ($task['type'] == 1) { @@ -22,11 +22,11 @@ class MsgNotice $mail_content .= ',请及时处理'; } if (!empty($result['errmsg'])) { - $mail_content .= '。
异常信息:'.$result['errmsg']; + $mail_content .= '。
异常信息:'.$result['errmsg'].''; } } else { $mail_title = 'DNS容灾切换-恢复正常通知'; - $mail_content = '尊敬的系统管理员,您好:
您的域名 '.$task['domain'].''.$task['main_value'].' 记录已恢复正常'; + $mail_content = '尊敬的用户,您好:
您的域名 '.$task['domain'].''.$task['main_value'].' 记录已恢复正常'; if ($task['type'] == 2) { $mail_content .= ',已自动切换回当前解析记录'; } elseif ($task['type'] == 1) { @@ -41,7 +41,7 @@ class MsgNotice if (!empty($task['remark'])) { $mail_content .= '
备注:'.$task['remark']; } - $mail_content .= '
'.self::$sitename.'
'.date('Y-m-d H:i:s'); + $mail_content .= '
'.self::$sitename.'
'.date('Y-m-d H:i:s').''; if (config_get('notice_mail') == 1) { $mail_name = config_get('mail_recv') ? config_get('mail_recv') : config_get('mail_name'); @@ -49,16 +49,20 @@ class MsgNotice } if (config_get('notice_wxtpl') == 1) { $content = str_replace(['
', '', ''], ["\n\n", '**', '**'], $mail_content); - self::send_wechat_tplmsg($mail_title, $content); + self::send_wechat_tplmsg($mail_title, strip_tags($content)); } if (config_get('notice_tgbot') == 1) { $content = str_replace('
', "\n", $mail_content); - $content = "".$mail_title."\n".$content; + $content = "".$mail_title."\n".strip_tags($content); self::send_telegram_bot($content); } + if (config_get('notice_webhook') == 1) { + $content = str_replace(['
', '', ''], ["\n", '**', '**'], $mail_content); + self::send_webhook($mail_title, $content); + } } - public static function cert_send($id, $result) + public static function cert_order_send($id, $result) { $row = Db::name('cert_order')->field('id,aid,issuetime,expiretime,issuer,status,error')->where('id', $id)->find(); if (!$row) return; @@ -71,7 +75,7 @@ class MsgNotice } else { $mail_title = $domainList[0] . '域名SSL证书签发成功通知'; } - $mail_content = '尊敬的用户,您好:您的SSL证书已签发成功!
证书账户:'.CertHelper::$cert_config[$type]['name'].'('.$row['aid'].')
证书域名:'.implode('、', $domainList).'
签发时间:'.$row['issuetime'].'
到期时间:'.$row['expiretime'].'
颁发机构:'.$row['issuer']; + $mail_content = '尊敬的用户,您好:您的SSL证书已签发成功!
证书账户: '.CertHelper::$cert_config[$type]['name'].'('.$row['aid'].')
证书域名: '.implode('、', $domainList).'
签发时间: '.$row['issuetime'].'
到期时间: '.$row['expiretime'].'
颁发机构: '.$row['issuer']; } else { $status_arr = [0 => '失败', -1 => '购买证书失败', -2 => '创建订单失败', -3 => '添加DNS失败', -4 => '验证DNS失败', -5 => '验证订单失败', -6 => '订单验证未通过', -7 => '签发证书失败']; if(count($domainList) > 1){ @@ -79,27 +83,15 @@ class MsgNotice }else{ $mail_title = $domainList[0].'域名SSL证书'.$status_arr[$row['status']].'通知'; } - $mail_content = '尊敬的用户,您好:您的SSL证书'.$status_arr[$row['status']].'!
证书账户:'.CertHelper::$cert_config[$type]['name'].'('.$row['aid'].')
证书域名:'.implode('、', $domainList).'
失败时间:'.date('Y-m-d H:i:s').'
失败原因:'.$row['error']; + $mail_content = '尊敬的用户,您好:您的SSL证书'.$status_arr[$row['status']].'!
证书账户: '.CertHelper::$cert_config[$type]['name'].'('.$row['aid'].')
证书域名: '.implode('、', $domainList).'
失败时间: '.date('Y-m-d H:i:s').'
失败原因: '.$row['error'].''; } - $mail_content .= '
'.self::$sitename.'
'.date('Y-m-d H:i:s'); + $mail_content .= '
'.self::$sitename.'
'.date('Y-m-d H:i:s').''; - if (config_get('cert_notice_mail') == 1 || config_get('cert_notice_mail') == 2 && !$result) { - $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('cert_notice_wxtpl') == 1 || config_get('cert_notice_wxtpl') == 2 && !$result) { - $content = str_replace(['
', '', ''], ["\n\n", '**', '**'], $mail_content); - self::send_wechat_tplmsg($mail_title, $content); - } - if (config_get('cert_notice_tgbot') == 1 || config_get('cert_notice_tgbot') == 2 && !$result) { - $content = str_replace('
', "\n", $mail_content); - $content = "" . $mail_title . "\n" . $content; - self::send_telegram_bot($content); - } + self::cert_send($mail_title, $mail_content, $result); Db::name('cert_order')->where('id', $id)->update(['issend' => 1]); } - public static function deploy_send($id, $result) + public static function cert_deploy_send($id, $result) { $row = Db::name('cert_deploy')->field('id,aid,oid,remark,status,error')->where('id', $id)->find(); if (!$row) return; @@ -108,28 +100,37 @@ class MsgNotice $typename = DeployHelper::$deploy_config[$account['type']]['name']; $mail_title = $typename; if(!empty($row['remark'])) $mail_title .= '('.$row['remark'].')'; - $mail_title .= '证书部署'.($result?'成功':'失败').'通知'; + $mail_title .= 'SSL证书部署'.($result?'成功':'失败').'通知'; if ($result) { - $mail_content = '尊敬的用户,您好:您的SSL证书已成功部署到'.$typename.'!
自动部署账户:['.$account['id'].']'.$typename.'('.($account['remark']?$account['remark']:$account['name']).')
关联SSL证书:['.$row['oid'].']'.implode('、', $domainList).'
任务备注:'.($row['remark']?$row['remark']:'无'); + $mail_content = '尊敬的用户,您好:您的SSL证书已成功部署到'.$typename.'!
自动部署账户: ['.$account['id'].']'.$typename.'('.($account['remark']?$account['remark']:$account['name']).')
关联SSL证书: ['.$row['oid'].']'.implode('、', $domainList).'
任务备注: '.($row['remark']?$row['remark']:'无'); } else { - $mail_content = '尊敬的用户,您好:您的SSL证书部署失败!
失败原因:'.$row['error'].'
自动部署账户:['.$account['id'].']'.$typename.'('.($account['remark']?$account['remark']:$account['name']).')
关联SSL证书:['.$row['oid'].']'.implode('、', $domainList).'
任务备注:'.($row['remark']?$row['remark']:'无'); + $mail_content = '尊敬的用户,您好:您的SSL证书部署失败!
失败原因: '.$row['error'].'
自动部署账户: ['.$account['id'].']'.$typename.'('.($account['remark']?$account['remark']:$account['name']).')
关联SSL证书: ['.$row['oid'].']'.implode('、', $domainList).'
任务备注: '.($row['remark']?$row['remark']:'无'); } - $mail_content .= '
'.self::$sitename.'
'.date('Y-m-d H:i:s'); + $mail_content .= '
'.self::$sitename.'
'.date('Y-m-d H:i:s').''; + self::cert_send($mail_title, $mail_content, $result); + Db::name('cert_deploy')->where('id', $id)->update(['issend' => 1]); + } + + private static function cert_send($mail_title, $mail_content, $result) + { if (config_get('cert_notice_mail') == 1 || config_get('cert_notice_mail') == 2 && !$result) { $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('cert_notice_wxtpl') == 1 || config_get('cert_notice_wxtpl') == 2 && !$result) { $content = str_replace(['
', '', ''], ["\n\n", '**', '**'], $mail_content); - self::send_wechat_tplmsg($mail_title, $content); + self::send_wechat_tplmsg($mail_title, strip_tags($content)); } if (config_get('cert_notice_tgbot') == 1 || config_get('cert_notice_tgbot') == 2 && !$result) { $content = str_replace('
', "\n", $mail_content); - $content = "" . $mail_title . "\n" . $content; + $content = "".$mail_title."\n".strip_tags($content); self::send_telegram_bot($content); } - Db::name('cert_deploy')->where('id', $id)->update(['issend' => 1]); + if (config_get('cert_notice_webhook') == 1) { + $content = str_replace(['*', '
', '', ''], ['\*', "\n", '**', '**'], $mail_content); + self::send_webhook($mail_title, $content); + } } public static function send_mail($to, $sub, $msg) @@ -187,7 +188,7 @@ class MsgNotice if (isset($arr['success']) && $arr['success'] == true) { return true; } else { - return $arr['msg']; + return isset($arr['msg']) ? $arr['msg'] : '请求失败'; } } @@ -203,7 +204,48 @@ class MsgNotice if (isset($arr['ok']) && $arr['ok'] == true) { return true; } else { - return $arr['description']; + return isset($arr['description']) ? $arr['description'] : '请求失败'; + } + } + + public static function send_webhook($title, $content) + { + $url = config_get('webhook_url'); + if (!$url || !parse_url($url)) return false; + if (strpos($url, 'oapi.dingtalk.com')) { + $content = '### '.$title." \n ".str_replace("\n", " \n ", $content); + $post = [ + 'msgtype' => 'markdown', + 'markdown' => [ + 'title' => $title, + 'text' => $content, + ], + ]; + } elseif (strpos($url, 'qyapi.weixin.qq.com')) { + $content = '## '.$title."\n".$content; + $post = [ + 'msgtype' => 'markdown', + 'markdown' => [ + 'content' => $content, + ], + ]; + } elseif (strpos($url, 'open.feishu.cn')) { + $content = str_replace(['\*', '**'], ['*', ''], strip_tags($content)); + $post = [ + 'msg_type' => 'text', + 'content' => [ + 'text' => $content, + ], + ]; + } else { + return '不支持的Webhook地址'; + } + $result = get_curl($url, json_encode($post), 0, 0, 0, 0, 0, ['Content-Type: application/json; charset=UTF-8']); + $arr = json_decode($result, true); + if (isset($arr['errcode']) && $arr['errcode'] == 0 || isset($arr['code']) && $arr['code'] == 0) { + return true; + } else { + return isset($arr['errmsg']) ? $arr['errmsg'] : (isset($arr['msg']) ? $arr['msg'] : '请求失败'); } } @@ -233,6 +275,7 @@ class MsgNotice curl_setopt($ch, CURLOPT_PROXYTYPE, $proxy_type); } curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); $httpheader[] = "Accept: */*"; diff --git a/app/view/cert/certset.html b/app/view/cert/certset.html index a7fa391..26af945 100644 --- a/app/view/cert/certset.html +++ b/app/view/cert/certset.html @@ -64,6 +64,10 @@
+
+ +
+
diff --git a/app/view/dmonitor/overview.html b/app/view/dmonitor/overview.html index da86afc..f15d9a1 100644 --- a/app/view/dmonitor/overview.html +++ b/app/view/dmonitor/overview.html @@ -56,6 +56,10 @@
+
+ +
+
+
+

群机器人Webhook

+
+
+
+ +
+
+
+ +
+
+
+ +
{/block} @@ -139,7 +159,7 @@ function saveSetting(obj){ success : function(data) { layer.close(ii); if(data.code == 0){ - layer.alert('设置保存成功!
重启检测进程或容器后生效', { + layer.alert('设置保存成功!
如有使用容灾切换,重启检测进程后生效', { icon: 1, closeBtn: false }, function(){ @@ -196,5 +216,25 @@ function tgbottest(){ } }); } +function webhooktest(){ + var ii = layer.load(2, {shade:[0.1,'#fff']}); + $.ajax({ + type : 'GET', + url : '/system/webhooktest', + dataType : 'json', + success : function(data) { + layer.close(ii); + if(data.code == 0){ + layer.alert(data.msg, {icon: 1}); + }else{ + layer.alert(data.msg, {icon: 2}) + } + }, + error:function(data){ + layer.close(ii); + layer.msg('服务器错误'); + } + }); +} {/block} \ No newline at end of file diff --git a/public/static/images/mwpanel.ico b/public/static/images/mwpanel.ico new file mode 100644 index 0000000..17e918a Binary files /dev/null and b/public/static/images/mwpanel.ico differ diff --git a/route/app.php b/route/app.php index 374d87c..82eaf28 100644 --- a/route/app.php +++ b/route/app.php @@ -110,6 +110,7 @@ Route::group(function () { Route::any('/system/proxyset', 'system/proxyset'); Route::get('/system/mailtest', 'system/mailtest'); Route::get('/system/tgbottest', 'system/tgbottest'); + Route::get('/system/webhooktest', 'system/webhooktest'); Route::post('/system/proxytest', 'system/proxytest'); })->middleware(CheckLogin::class)