18 Commits
2.0.0 ... 2.3.0

Author SHA1 Message Date
net909
d1eb6267a2 支持火山引擎更多产品部署 2025-01-24 14:36:29 +08:00
net909
2c81b36249 增加导入已有证书,支持天翼云CDN部署 2025-01-10 14:54:45 +08:00
net909
8d5a9bc083 version 2025-01-05 10:57:43 +08:00
net909
31300d8a7b 优化SSL订单等待耗时 2025-01-05 10:56:02 +08:00
net909
300f2a9b92 修复部分接口请求异常 2024-12-31 15:30:59 +08:00
net909
865275c065 新增西部数码虚拟主机部署 2024-12-30 18:00:47 +08:00
net909
cae9887e05 SSL订单提示优化 2024-12-28 17:17:14 +08:00
net909
6de361171e readme 2024-12-28 17:02:59 +08:00
net909
e59ff8997e 支持批量添加域名 2024-12-28 16:59:48 +08:00
net909
6ce2e006b5 修复泛域名CNAME代理 2024-12-27 22:27:38 +08:00
net909
197d816bbb 修复Goedge账号添加 2024-12-27 13:40:20 +08:00
net909
dfded1122b 云服务商接口支持代理请求 2024-12-26 14:34:55 +08:00
net909
8201318bd7 支持MW面板部署,增加群机器人webhook通知 2024-12-25 20:08:59 +08:00
net909
b31aee7166 fix 2024-12-25 11:37:43 +08:00
net909
06b43fa33f 修复华为云添加TXT解析 2024-12-24 20:41:46 +08:00
net909
fa9235562a 修复华为云部署 2024-12-23 22:31:26 +08:00
net909
92ba34833b 修复1panel 2024-12-22 16:54:34 +08:00
net909
83c1afc186 fix 2024-12-22 11:09:54 +08:00
73 changed files with 2076 additions and 331 deletions

View File

@@ -1,19 +1,12 @@
## 聚合DNS管理系统
聚合DNS管理系统可以实现在一个网站内管理多个平台的域名解析目前已支持的域名平台有
- 阿里云
- 腾讯云
- 华为云
- 西部数码
- DNSLA
- CloudFlare
聚合DNS管理系统可以实现在一个网站内管理多个平台的域名解析目前已支持的域名平台有阿里云、腾讯云、华为云、百度云、西部数码、火山引擎、DNSLA、CloudFlare、Namesilo
### 功能特性
- 多用户管理,可为每个用户可分配不同的域名解析权限
- 提供API接口可获取域名单独的登录链接方便各种IDC系统对接
- 容灾切换功能支持ping、tcp、http(s)检测协议并自动暂停/修改域名解析,并支持邮件、微信公众号通知
- 容灾切换功能支持ping、tcp、http(s)检测协议并自动暂停/修改域名解析,并支持邮件、微信公众号、TG群机器人通知
- CF优选IP功能支持获取最新的Cloudflare优选IP并自动更新到解析记录
- SSL证书申请与自动部署功能支持从Let's Encrypt等渠道申请SSL证书并自动部署到各种面板、云服务商、服务器等

View File

@@ -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: */*";
@@ -165,6 +166,11 @@ function getSubstr($str, $leftStr, $rightStr)
}
}
function arrays_are_equal($array1, $array2)
{
return empty(array_diff($array1, $array2)) && empty(array_diff($array2, $array1));
}
function checkRefererHost()
{
if (!Request::header('referer')) {
@@ -407,26 +413,7 @@ function curl_client($url, $data = null, $referer = null, $cookie = null, $heade
}
if ($proxy) {
$proxy_server = config_get('proxy_server');
$proxy_port = intval(config_get('proxy_port'));
$proxy_userpwd = config_get('proxy_user') . ':' . config_get('proxy_pwd');
$proxy_type = config_get('proxy_type');
if ($proxy_type == 'https') {
$proxy_type = CURLPROXY_HTTPS;
} elseif ($proxy_type == 'sock4') {
$proxy_type = CURLPROXY_SOCKS4;
} elseif ($proxy_type == 'sock5') {
$proxy_type = CURLPROXY_SOCKS5;
} else {
$proxy_type = CURLPROXY_HTTP;
}
curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_PROXY, $proxy_server);
curl_setopt($ch, CURLOPT_PROXYPORT, $proxy_port);
if ($proxy_userpwd != ':') {
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy_userpwd);
}
curl_setopt($ch, CURLOPT_PROXYTYPE, $proxy_type);
curl_set_proxy($ch);
}
$ret = curl_exec($ch);
@@ -442,4 +429,31 @@ function curl_client($url, $data = null, $referer = null, $cookie = null, $heade
$header = substr($ret, 0, $headerSize);
$body = substr($ret, $headerSize);
return ['code' => $httpCode, 'redirect_url' => $redirect_url, 'header' => $header, 'body' => $body];
}
function curl_set_proxy(&$ch)
{
$proxy_server = config_get('proxy_server');
$proxy_port = intval(config_get('proxy_port'));
$proxy_userpwd = config_get('proxy_user') . ':' . config_get('proxy_pwd');
$proxy_type = config_get('proxy_type');
if (empty($proxy_server) || empty($proxy_port)) {
return;
}
if ($proxy_type == 'https') {
$proxy_type = CURLPROXY_HTTPS;
} elseif ($proxy_type == 'sock4') {
$proxy_type = CURLPROXY_SOCKS4;
} elseif ($proxy_type == 'sock5') {
$proxy_type = CURLPROXY_SOCKS5;
} else {
$proxy_type = CURLPROXY_HTTP;
}
curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_PROXY, $proxy_server);
curl_setopt($ch, CURLOPT_PROXYPORT, $proxy_port);
if ($proxy_userpwd != ':') {
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy_userpwd);
}
curl_setopt($ch, CURLOPT_PROXYTYPE, $proxy_type);
}

View File

@@ -38,7 +38,7 @@ class Auth extends BaseController
$user = Db::name('user')->where('username', $username)->find();
if ($user && password_verify($password, $user['password'])) {
if ($user['status'] == 0) return json(['code' => -1, 'msg' => '此用户已被封禁', 'vcode' => 1]);
if ($user['totp_open'] == 1 && !empty($user['totp_secret'])) {
if (isset($user['totp_open']) && $user['totp_open'] == 1 && !empty($user['totp_secret'])) {
session('pre_login_user', $user['id']);
if (file_exists($login_limit_file)) {
unlink($login_limit_file);
@@ -53,7 +53,9 @@ class Auth extends BaseController
} else {
if ($user) {
Db::name('log')->insert(['uid' => $user['id'], 'action' => '登录失败', 'data' => 'IP:' . $this->clientip, 'addtime' => date("Y-m-d H:i:s")]);
if ($user['totp_open'] == 1 && !empty($user['totp_secret'])) $login_limit_count = 10;
if (isset($user['totp_open']) && $user['totp_open'] == 1 && !empty($user['totp_secret'])) {
return json(['code' => -1, 'msg' => '用户名或密码错误', 'vcode' => 1]);
}
}
if (!file_exists($login_limit_file)) {
$login_limit = ['count' => 0, 'time' => 0];

View File

@@ -317,6 +317,72 @@ class Cert extends BaseController
Db::name('cert_domain')->insertAll($domainList);
Db::commit();
return json(['code' => 0, 'msg' => '修改证书订单成功!']);
} elseif ($action == 'import') {
$fullchain = input('post.fullchain', null, 'trim');
$privatekey = input('post.privatekey', null, 'trim');
if (!openssl_x509_read($fullchain)) return json(['code' => -1, 'msg' => '证书内容填写错误']);
if (!openssl_get_privatekey($privatekey)) return json(['code' => -1, 'msg' => '私钥内容填写错误']);
if (!openssl_x509_check_private_key($fullchain, $privatekey)) return json(['code' => -1, 'msg' => 'SSL证书与私钥不匹配']);
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo || !isset($certInfo['extensions']['subjectAltName'])) return json(['code' => -1, 'msg' => '证书内容解析失败']);
$domains = [];
$subjectAltName = explode(',', $certInfo['extensions']['subjectAltName']);
foreach ($subjectAltName as $domain) {
$domain = trim($domain);
if (strpos($domain, 'DNS:') === 0) $domain = substr($domain, 4);
if (!empty($domain)) {
$domains[] = $domain;
}
}
$domains = array_unique($domains);
if (empty($domains)) return json(['code' => -1, 'msg' => '证书绑定域名不能为空']);
$issuetime = date('Y-m-d H:i:s', $certInfo['validFrom_time_t']);
$expiretime = date('Y-m-d H:i:s', $certInfo['validTo_time_t']);
$issuer = $certInfo['issuer']['CN'];
$order_ids = Db::name('cert_order')->where('issuetime', $issuetime)->column('id');
if (!empty($order_ids)) {
foreach ($order_ids as $order_id) {
$domains2 = Db::name('cert_domain')->where('oid', $order_id)->column('domain');
if (arrays_are_equal($domains2, $domains)) {
return json(['code' => -1, 'msg' => '该证书已存在,无需重复添加']);
}
}
}
$order = [
'aid' => input('post.aid/d'),
'keytype' => input('post.keytype'),
'keysize' => input('post.keysize'),
'addtime' => date('Y-m-d H:i:s'),
'updatetime' => date('Y-m-d H:i:s'),
'issuetime' => $issuetime,
'expiretime' => $expiretime,
'issuer' => $issuer,
'status' => 3,
'fullchain' => $fullchain,
'privatekey' => $privatekey,
];
if (empty($order['aid']) || empty($order['keytype']) || empty($order['keysize'])) return json(['code' => -1, 'msg' => '必填参数不能为空']);
$res = $this->check_order($order, $domains);
if (is_array($res)) return json($res);
Db::startTrans();
$id = Db::name('cert_order')->insertGetId($order);
$domainList = [];
$i = 1;
foreach ($domains as $domain) {
$domainList[] = [
'oid' => $id,
'domain' => $domain,
'sort' => $i++,
];
}
Db::name('cert_domain')->insertAll($domainList);
Db::commit();
return json(['code' => 0, 'msg' => '导入证书成功!']);
} elseif ($action == 'del') {
$id = input('post.id/d');
$dcount = DB::name('cert_deploy')->where('oid', $id)->count();
@@ -368,13 +434,18 @@ class Cert extends BaseController
$max_domains = CertHelper::$cert_config[$account['type']]['max_domains'];
$wildcard = CertHelper::$cert_config[$account['type']]['wildcard'];
$cname = CertHelper::$cert_config[$account['type']]['cname'];
if (count($domains) > $max_domains) return ['code' => -1, 'msg' => '域名数量不能超过'.$max_domains.'个'];
if (count($domains) > $max_domains) {
if (!(count($domains) == 2 && $max_domains == 1 && ltrim($domains[0], 'www.') == ltrim($domains[1], 'www.'))) {
return ['code' => -1, 'msg' => '域名数量不能超过'.$max_domains.'个'];
}
}
foreach($domains as $domain){
if(!$wildcard && strpos($domain, '*') !== false) return ['code' => -1, 'msg' => '该证书账户类型不支持泛域名'];
$mainDomain = getMainDomain($domain);
$drow = Db::name('domain')->where('name', $mainDomain)->find();
if (!$drow) {
if (substr($domain, 0, 2) == '*.') $domain = substr($domain, 2);
if (!$cname || !Db::name('cert_cname')->where('domain', $domain)->where('status', 1)->find()) {
return ['code' => -1, 'msg' => '域名'.$domain.'未在本系统添加'];
}
@@ -437,6 +508,20 @@ class Cert extends BaseController
return View::fetch();
}
public function order_import()
{
if (!checkPermission(2)) return $this->alert('error', '无权限');
$accounts = [];
foreach (Db::name('cert_account')->where('deploy', 0)->select() as $row) {
$accounts[$row['id']] = ['name'=>$row['id'].'_'.CertHelper::$cert_config[$row['type']]['name'], 'type'=>$row['type']];
if (!empty($row['remark'])) {
$accounts[$row['id']]['name'] .= '' . $row['remark'] . '';
}
}
View::assign('accounts', $accounts);
return View::fetch();
}
public function deploytask()
{

View File

@@ -57,6 +57,7 @@ class Domain extends BaseController
$sk = input('post.sk', null, 'trim');
$ext = input('post.ext', null, 'trim');
$remark = input('post.remark', null, 'trim');
$proxy = input('post.proxy/d', 0);
if (empty($ak) || empty($sk)) return json(['code' => -1, 'msg' => 'AccessKey和SecretKey不能为空']);
if (Db::name('account')->where('type', $type)->where('ak', $ak)->find()) {
return json(['code' => -1, 'msg' => '域名账户已存在']);
@@ -67,6 +68,7 @@ class Domain extends BaseController
'ak' => $ak,
'sk' => $sk,
'ext' => $ext,
'proxy' => $proxy,
'remark' => $remark,
'addtime' => date('Y-m-d H:i:s'),
]);
@@ -92,6 +94,7 @@ class Domain extends BaseController
$sk = input('post.sk', null, 'trim');
$ext = input('post.ext', null, 'trim');
$remark = input('post.remark', null, 'trim');
$proxy = input('post.proxy/d', 0);
if (empty($ak) || empty($sk)) return json(['code' => -1, 'msg' => 'AccessKey和SecretKey不能为空']);
if (Db::name('account')->where('type', $type)->where('ak', $ak)->where('id', '<>', $id)->find()) {
return json(['code' => -1, 'msg' => '域名账户已存在']);
@@ -102,6 +105,7 @@ class Domain extends BaseController
'ak' => $ak,
'sk' => $sk,
'ext' => $ext,
'proxy' => $proxy,
'remark' => $remark,
]);
$dns = DnsHelper::getModel($id);
@@ -150,6 +154,26 @@ class Domain extends BaseController
return view();
}
public function domain_add()
{
if (!checkPermission(2)) return $this->alert('error', '无权限');
$list = Db::name('account')->select();
$accounts = [];
$types = [];
foreach ($list as $row) {
$accounts[$row['id']] = $row['id'] . '_' . DnsHelper::$dns_config[$row['type']]['name'];
if (!array_key_exists($row['type'], $types)) {
$types[$row['type']] = DnsHelper::$dns_config[$row['type']]['name'];
}
if (!empty($row['remark'])) {
$accounts[$row['id']] .= '' . $row['remark'] . '';
}
}
View::assign('accounts', $accounts);
View::assign('types', $types);
return view();
}
public function domain_data()
{
if (!checkPermission(1)) return json(['total' => 0, 'rows' => []]);
@@ -230,6 +254,25 @@ class Domain extends BaseController
Db::name('dmtask')->where('did', $id)->delete();
Db::name('optimizeip')->where('did', $id)->delete();
return json(['code' => 0]);
} elseif ($act == 'batchadd') {
if (!checkPermission(2)) return $this->alert('error', '无权限');
$aid = input('post.aid/d');
$domains = input('post.domains');
if (empty($domains)) return json(['code' => -1, 'msg' => '参数不能为空']);
$data = [];
foreach ($domains as $row) {
$data[] = [
'aid' => $aid,
'name' => $row['name'],
'thirdid' => $row['id'],
'addtime' => date('Y-m-d H:i:s'),
'is_hide' => 0,
'is_sso' => 1,
'recordcount' => $row['recordcount'],
];
}
Db::name('domain')->insertAll($data);
return json(['code' => 0, 'msg' => '成功添加' . count($data) . '个域名!']);
}
return json(['code' => -3]);
}
@@ -245,13 +288,10 @@ class Domain extends BaseController
$result = $dns->getDomainList($kw, $page, $pagesize);
if (!$result) return json(['code' => -1, 'msg' => '获取域名列表失败,' . $dns->getError()]);
$newlist = [];
foreach ($result['list'] as $row) {
if (!Db::name('domain')->where('aid', $aid)->where('name', $row['Domain'])->find()) {
$newlist[] = $row;
}
foreach ($result['list'] as &$row) {
$row['disabled'] = Db::name('domain')->where('aid', $aid)->where('name', $row['Domain'])->find() != null;
}
return json(['code' => 0, 'data' => ['total' => $result['total'], 'list' => $newlist]]);
return json(['code' => 0, 'data' => ['total' => $result['total'], 'list' => $result['list']]]);
}
//获取解析线路和最小TTL

View File

@@ -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', '无权限');

View File

@@ -1922,4 +1922,7 @@ com.za
com.zw
mil.cn
edu.kg
edu.cn
edu.cn
eu.org
us.kg
ggff.net

View File

@@ -154,6 +154,15 @@ class CertHelper
'placeholder' => '申请证书时填写的邮箱',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
'aliyun' => [
@@ -195,6 +204,15 @@ class CertHelper
'placeholder' => '申请联系人的邮箱地址',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
]
],
'ucloud' => [

View File

@@ -259,16 +259,6 @@ class DeployHelper
'note' => '需要先<a href="https://goedge.cloud/docs/API/Settings.md" target="_blank" rel="noreferrer">开启HTTP API端口</a>',
'tasknote' => '系统会根据关联SSL证书的域名自动更新对应证书',
'inputs' => [
'systype' => [
'name' => '系统类型',
'type' => 'radio',
'options' => [
'0' => 'GoEdge',
'1' => 'FlexCDN',
],
'value' => '0',
'required' => true,
],
'url' => [
'name' => 'HTTP API地址',
'type' => 'input',
@@ -298,6 +288,16 @@ class DeployHelper
'value' => 'user',
'required' => true,
],
'systype' => [
'name' => '系统类型',
'type' => 'radio',
'options' => [
'0' => 'GoEdge',
'1' => 'FlexCDN',
],
'value' => '0',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
@@ -342,6 +342,63 @@ class DeployHelper
],
'taskinputs' => [],
],
'mwpanel' => [
'name' => 'MW面板',
'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,
@@ -361,6 +418,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
@@ -499,6 +565,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
@@ -592,7 +667,7 @@ class DeployHelper
'name' => '实例ID',
'type' => 'input',
'placeholder' => '',
'show' => 'product==\'lighthouse\'',
'show' => 'product==\'lighthouse\'||product==\'ddos\'',
'required' => true,
],
'domain' => [
@@ -600,6 +675,7 @@ class DeployHelper
'type' => 'input',
'placeholder' => '',
'show' => 'product!=\'clb\'&&product!=\'tke\'',
'note' => 'CDN、EO、WAF多个域名可用,隔开其他只能填写1个域名',
'required' => true,
],
],
@@ -622,6 +698,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
@@ -714,6 +799,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
@@ -722,10 +816,18 @@ class DeployHelper
'options' => [
['value'=>'cdn', 'label'=>'CDN'],
['value'=>'oss', 'label'=>'OSS'],
['value'=>'pili', 'label'=>'视频直播'],
],
'value' => 'cdn',
'required' => true,
],
'pili_hub' => [
'name' => '直播空间名称',
'type' => 'input',
'placeholder' => '',
'show' => 'product==\'pili\'',
'required' => true,
],
'domain' => [
'name' => '绑定的域名',
'type' => 'input',
@@ -752,6 +854,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'domain' => [
@@ -780,6 +891,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'domain' => [
@@ -808,12 +928,87 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
'name' => '要部署的产品',
'type' => 'select',
'options' => [
['value'=>'cdn', 'label'=>'内容分发网络CDN'],
['value'=>'dcdn', 'label'=>'全站加速DCDN'],
['value'=>'clb', 'label'=>'负载均衡CLB'],
['value'=>'tos', 'label'=>'对象存储TOS'],
['value'=>'live', 'label'=>'视频直播'],
['value'=>'imagex', 'label'=>'veImageX'],
],
'value' => 'cdn',
'required' => true,
],
'bucket_domain' => [
'name' => 'Bucket域名',
'type' => 'input',
'placeholder' => '',
'show' => 'product==\'tos\'',
'required' => true,
],
'domain' => [
'name' => '绑定的域名',
'type' => 'input',
'placeholder' => '多个域名可使用英文逗号分隔',
'placeholder' => '多个域名可使用,分隔',
'show' => 'product!=\'clb\'',
'required' => true,
],
'listener_id' => [
'name' => '监听器ID',
'type' => 'input',
'placeholder' => '',
'show' => 'product==\'clb\'',
'required' => true,
],
],
],
'west' => [
'name' => '西部数码',
'class' => 2,
'icon' => 'west.ico',
'note' => '支持部署到西部数码虚拟主机',
'inputs' => [
'username' => [
'name' => '用户名',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'api_password' => [
'name' => 'API密码',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'sitename' => [
'name' => 'FTP账号',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
],
@@ -857,6 +1052,48 @@ class DeployHelper
],
],
],
'ctyun' => [
'name' => '天翼云',
'class' => 2,
'icon' => 'ctyun.ico',
'note' => '支持部署到天翼云CDN',
'inputs' => [
'AccessKeyId' => [
'name' => 'AccessKeyId',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'SecretAccessKey' => [
'name' => 'SecretAccessKey',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
'name' => '产品',
'type' => 'hidden',
'value' => 'cdn',
],
'domain' => [
'name' => 'CDN域名',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
],
],
'allwaf' => [
'name' => 'AllWAF',
'class' => 2,
@@ -906,6 +1143,15 @@ class DeployHelper
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'product' => [
@@ -1266,7 +1512,7 @@ class DeployHelper
];
public static $class_config = [
1 => '自建面板',
1 => '自建系统',
2 => '云服务商',
3 => '服务器',
];

View File

@@ -227,7 +227,7 @@ class ACMECert extends ACMEv2
public function authOrder($order)
{
if ($order['status'] != 'ready' && empty($order['challenges'])) {
if ($order['status'] != 'pending' && $order['status'] != 'ready' && empty($order['challenges'])) {
throw new Exception('No challenges available');
}

View File

@@ -308,26 +308,7 @@ class ACMEv2
));
if ($this->proxy) {
$proxy_server = config_get('proxy_server');
$proxy_port = intval(config_get('proxy_port'));
$proxy_userpwd = config_get('proxy_user').':'.config_get('proxy_pwd');
$proxy_type = config_get('proxy_type');
if ($proxy_type == 'https') {
$proxy_type = CURLPROXY_HTTPS;
} elseif ($proxy_type == 'sock4') {
$proxy_type = CURLPROXY_SOCKS4;
} elseif ($proxy_type == 'sock5') {
$proxy_type = CURLPROXY_SOCKS5;
} else {
$proxy_type = CURLPROXY_HTTP;
}
curl_setopt($this->ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
curl_setopt($this->ch, CURLOPT_PROXY, $proxy_server);
curl_setopt($this->ch, CURLOPT_PROXYPORT, $proxy_port);
if ($proxy_userpwd != ':') {
curl_setopt($this->ch, CURLOPT_PROXYUSERPWD, $proxy_userpwd);
}
curl_setopt($this->ch, CURLOPT_PROXYTYPE, $proxy_type);
curl_set_proxy($this->ch);
}
$took = microtime(true);

View File

@@ -20,7 +20,8 @@ class aliyun implements CertInterface
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->AccessKeySecret = $config['AccessKeySecret'];
$this->client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $this->Endpoint, $this->Version);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $this->Endpoint, $this->Version, $proxy);
$this->config = $config;
}

View File

@@ -21,7 +21,8 @@ class huoshan implements CertInterface
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->SecretAccessKey = $config['SecretAccessKey'];
$this->client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint, $this->service, $this->version, $this->region);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint, $this->service, $this->version, $this->region, $proxy);
}
public function register()

View File

@@ -21,7 +21,8 @@ class tencent implements CertInterface
{
$this->SecretId = $config['SecretId'];
$this->SecretKey = $config['SecretKey'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, $this->endpoint, $this->service, $this->version);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, $this->endpoint, $this->service, $this->version, null, $proxy);
$this->email = $config['email'];
}

View File

@@ -16,8 +16,9 @@ class AWS
private $version;
private $region;
private $etag;
private $proxy = false;
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $service, $version, $region)
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $service, $version, $region, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->SecretAccessKey = $SecretAccessKey;
@@ -25,6 +26,7 @@ class AWS
$this->service = $service;
$this->version = $version;
$this->region = $region;
$this->proxy = $proxy;
}
/**
@@ -142,6 +144,7 @@ class AWS
}
$path = '/' . $this->version . $path;
$body = '';
if ($method == 'GET' || $method == 'DELETE') {
$query = $params;
} else {
@@ -179,7 +182,7 @@ class AWS
// step 1: build canonical request string
$httpRequestMethod = $method;
$canonicalUri = $path;
$canonicalUri = $this->getCanonicalURI($path);
$canonicalQueryString = $this->getCanonicalQueryString($query);
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
$hashedRequestPayload = hash("sha256", $body);
@@ -219,6 +222,17 @@ class AWS
$replace = ['%20', '%2A', '~'];
return str_replace($search, $replace, urlencode($str));
}
private function getCanonicalURI($path)
{
if (empty($path)) return '/';
$pattens = explode('/', $path);
$pattens = array_map(function ($item) {
return $this->escape($item);
}, $pattens);
$canonicalURI = implode('/', $pattens);
return $canonicalURI;
}
private function getCanonicalQueryString($parameters)
{
@@ -252,6 +266,9 @@ class AWS
private function curl($method, $url, $body, $header, $xml = false, $etag = false)
{
$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);

View File

@@ -13,13 +13,15 @@ class Aliyun
private $AccessKeySecret;
private $Endpoint;
private $Version;
private $proxy = false;
public function __construct($AccessKeyId, $AccessKeySecret, $Endpoint, $Version)
public function __construct($AccessKeyId, $AccessKeySecret, $Endpoint, $Version, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->AccessKeySecret = $AccessKeySecret;
$this->Endpoint = $Endpoint;
$this->Version = $Version;
$this->proxy = $proxy;
}
/**
@@ -45,6 +47,9 @@ class Aliyun
$url .= '?' . http_build_query($data);
}
$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_RETURNTRANSFER, true);

View File

@@ -13,13 +13,15 @@ class AliyunNew
private $AccessKeySecret;
private $Endpoint;
private $Version;
private $proxy = false;
public function __construct($AccessKeyId, $AccessKeySecret, $Endpoint, $Version)
public function __construct($AccessKeyId, $AccessKeySecret, $Endpoint, $Version, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->AccessKeySecret = $AccessKeySecret;
$this->Endpoint = $Endpoint;
$this->Version = $Version;
$this->proxy = $proxy;
}
/**
@@ -74,7 +76,7 @@ class AliyunNew
// step 1: build canonical request string
$httpRequestMethod = $method;
$canonicalUri = $path;
$canonicalUri = $this->getCanonicalURI($path);
$canonicalQueryString = $this->getCanonicalQueryString($query);
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
$hashedRequestPayload = hash("sha256", $body);
@@ -106,6 +108,17 @@ class AliyunNew
return str_replace($search, $replace, urlencode($str));
}
private function getCanonicalURI($path)
{
if (empty($path)) return '/';
$pattens = explode('/', $path);
$pattens = array_map(function ($item) {
return $this->escape($item);
}, $pattens);
$canonicalURI = implode('/', $pattens);
return $canonicalURI;
}
private function getCanonicalQueryString($parameters)
{
if (empty($parameters)) return '';
@@ -138,6 +151,9 @@ class AliyunNew
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);

View File

@@ -9,12 +9,14 @@ class AliyunOSS
private $AccessKeyId;
private $AccessKeySecret;
private $Endpoint;
private $proxy = false;
public function __construct($AccessKeyId, $AccessKeySecret, $Endpoint)
public function __construct($AccessKeyId, $AccessKeySecret, $Endpoint, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->AccessKeySecret = $AccessKeySecret;
$this->Endpoint = $Endpoint;
$this->proxy = $proxy;
}
public function addBucketCnameCert($bucket, $domain, $cert_id)
@@ -101,6 +103,9 @@ class AliyunOSS
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);

View File

@@ -12,12 +12,14 @@ class BaiduCloud
private $AccessKeyId;
private $SecretAccessKey;
private $endpoint;
private $proxy = false;
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint)
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->SecretAccessKey = $SecretAccessKey;
$this->endpoint = $endpoint;
$this->proxy = $proxy;
}
/**
@@ -140,6 +142,9 @@ class BaiduCloud
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);

163
app/lib/client/Ctyun.php Normal file
View File

@@ -0,0 +1,163 @@
<?php
namespace app\lib\client;
use Exception;
/**
* 天翼云
*/
class Ctyun
{
private $AccessKeyId;
private $SecretAccessKey;
private $endpoint;
private $proxy = false;
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->SecretAccessKey = $SecretAccessKey;
$this->endpoint = $endpoint;
$this->proxy = $proxy;
}
/**
* @param string $method 请求方法
* @param string $path 请求路径
* @param array|null $query 请求参数
* @param array|null $params 请求体
* @return array
* @throws Exception
*/
public function request($method, $path, $query = null, $params = null)
{
if (!empty($query)) {
$query = array_filter($query, function ($a) { return $a !== null;});
}
if (!empty($params)) {
$params = array_filter($params, function ($a) { return $a !== null;});
}
$time = time();
$date = date("Ymd\THis\Z", $time);
$body = !empty($params) ? json_encode($params) : '';
$headers = [
'Host' => $this->endpoint,
'Eop-date' => $date,
'ctyun-eop-request-id' => getSid(),
];
if ($body) {
$headers['Content-Type'] = 'application/json';
}
$authorization = $this->generateSign($query, $headers, $body, $date);
$headers['Eop-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($query, $headers, $body, $date)
{
// step 1: build canonical request string
$canonicalQueryString = $this->getCanonicalQueryString($query);
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
$hashedRequestPayload = hash("sha256", $body);
// step 2: build string to sign
$stringToSign = $canonicalHeaders . "\n"
. $canonicalQueryString . "\n"
. $hashedRequestPayload;
// step 3: sign string
$ktime = hash_hmac("sha256", $date, $this->SecretAccessKey, true);
$kAk = hash_hmac("sha256", $this->AccessKeyId, $ktime, true);
$kdate = hash_hmac("sha256", substr($date, 0, 8), $kAk, true);
$signature = hash_hmac("sha256", $stringToSign, $kdate, true);
$signature = base64_encode($signature);
// step 4: build authorization
$authorization = $this->AccessKeyId . " Headers=" . $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));
}
curl_close($ch);
$arr = json_decode($response, true);
if (isset($arr['statusCode']) && $arr['statusCode'] == 100000) {
return isset($arr['returnObj']) ? $arr['returnObj'] : true;
} elseif (isset($arr['errorMessage'])) {
throw new Exception($arr['errorMessage']);
} elseif (isset($arr['message'])) {
throw new Exception($arr['message']);
} else {
throw new Exception('返回数据解析失败');
}
}
}

View File

@@ -12,12 +12,14 @@ class HuaweiCloud
private $AccessKeyId;
private $SecretAccessKey;
private $endpoint;
private $proxy = false;
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint)
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->SecretAccessKey = $SecretAccessKey;
$this->endpoint = $endpoint;
$this->proxy = $proxy;
}
/**
@@ -68,8 +70,7 @@ class HuaweiCloud
// step 1: build canonical request string
$httpRequestMethod = $method;
$canonicalUri = $path;
if (substr($canonicalUri, -1) != "/") $canonicalUri .= "/";
$canonicalUri = $this->getCanonicalURI($path);
$canonicalQueryString = $this->getCanonicalQueryString($query);
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
$hashedRequestPayload = hash("sha256", $body);
@@ -103,6 +104,18 @@ class HuaweiCloud
return str_replace($search, $replace, urlencode($str));
}
private function getCanonicalURI($path)
{
if (empty($path)) return '/';
$pattens = explode('/', $path);
$pattens = array_map(function ($item) {
return $this->escape($item);
}, $pattens);
$canonicalURI = implode('/', $pattens);
if (substr($canonicalURI, -1) != '/') $canonicalURI .= '/';
return $canonicalURI;
}
private function getCanonicalQueryString($parameters)
{
if (empty($parameters)) return '';
@@ -135,6 +148,9 @@ class HuaweiCloud
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);
@@ -150,6 +166,7 @@ class HuaweiCloud
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);
@@ -164,7 +181,6 @@ class HuaweiCloud
return $arr;
}
} else {
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode >= 200 && $httpCode < 300) {
return null;
} else {

View File

@@ -12,11 +12,13 @@ class Qiniu
private $ApiUrl = 'https://api.qiniu.com';
private $AccessKey;
private $SecretKey;
private $proxy = false;
public function __construct($AccessKey, $SecretKey)
public function __construct($AccessKey, $SecretKey, $proxy = false)
{
$this->AccessKey = $AccessKey;
$this->SecretKey = $SecretKey;
$this->proxy = $proxy;
}
/**
@@ -59,6 +61,39 @@ class Qiniu
return $this->curl($method, $url, $body, $header);
}
public function pili_request($method, $path, $query = null, $params = null)
{
$this->ApiUrl = 'https://pili.qiniuapi.com';
$url = $this->ApiUrl . $path;
$query_str = null;
$body = null;
if (!empty($query)) {
$query = array_filter($query, function ($a) {
return $a !== null;
});
$query_str = http_build_query($query);
$url .= '?' . $query_str;
}
if (!empty($params)) {
$params = array_filter($params, function ($a) {
return $a !== null;
});
$body = json_encode($params);
}
$sign_str = $method . ' ' . $path . ($query_str ? '?' . $query_str : '') . "\nHost: pili.qiniuapi.com" . ($body ? "\nContent-Type: application/json" : '') . "\n\n" . $body;
$hmac = hash_hmac('sha1', $sign_str, $this->SecretKey, true);
$sign = $this->AccessKey . ':' . $this->base64_urlSafeEncode($hmac);
$header = [
'Authorization: Qiniu ' . $sign,
];
if ($body) {
$header[] = 'Content-Type: application/json';
}
return $this->curl($method, $url, $body, $header);
}
private function base64_urlSafeEncode($data)
{
$find = array('+', '/');
@@ -69,6 +104,9 @@ class Qiniu
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);
@@ -90,7 +128,7 @@ class Qiniu
if ($httpCode == 200) {
$arr = json_decode($response, true);
if($arr) return $arr;
if ($arr) return $arr;
return true;
} else {
$arr = json_decode($response, true);

View File

@@ -15,8 +15,9 @@ class TencentCloud
private $service;
private $version;
private $region;
private $proxy = false;
public function __construct($SecretId, $SecretKey, $endpoint, $service, $version, $region = null)
public function __construct($SecretId, $SecretKey, $endpoint, $service, $version, $region = null, $proxy = false)
{
$this->SecretId = $SecretId;
$this->SecretKey = $SecretKey;
@@ -24,6 +25,7 @@ class TencentCloud
$this->service = $service;
$this->version = $version;
$this->region = $region;
$this->proxy = $proxy;
}
/**
@@ -98,6 +100,9 @@ class TencentCloud
{
$url = 'https://'.$this->endpoint.'/';
$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);

View File

@@ -15,8 +15,9 @@ class Volcengine
private $service;
private $version;
private $region;
private $proxy = false;
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $service, $version, $region)
public function __construct($AccessKeyId, $SecretAccessKey, $endpoint, $service, $version, $region, $proxy = false)
{
$this->AccessKeyId = $AccessKeyId;
$this->SecretAccessKey = $SecretAccessKey;
@@ -24,6 +25,7 @@ class Volcengine
$this->service = $service;
$this->version = $version;
$this->region = $region;
$this->proxy = $proxy;
}
/**
@@ -36,7 +38,9 @@ class Volcengine
public function request($method, $action, $params = [], $querys = [])
{
if (!empty($params)) {
$params = array_filter($params, function ($a) { return $a !== null;});
$params = array_filter($params, function ($a) {
return $a !== null;
});
}
$query = [
@@ -76,9 +80,51 @@ class Volcengine
return $this->curl($method, $url, $body, $header);
}
/**
* @param string $method 请求方法
* @param string $action 方法名称
* @param array $params 请求参数
* @return array
* @throws Exception
*/
public function tos_request($method, $params = [], $query = [])
{
if (!empty($params)) {
$params = array_filter($params, function ($a) {
return $a !== null;
});
}
$body = '';
if ($method != 'GET') {
$body = !empty($params) ? json_encode($params) : '';
}
$time = time();
$headers = [
'Host' => $this->endpoint,
'X-Tos-Date' => gmdate("Ymd\THis\Z", $time),
'X-Tos-Content-Sha256' => hash("sha256", $body),
];
if ($body) {
$headers['Content-Type'] = 'application/json';
}
$path = '/';
$authorization = $this->generateSign($method, $path, $query, $headers, $body, $time);
$headers['Authorization'] = $authorization;
$url = 'https://' . $this->endpoint . $path . '?' . 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, $time)
{
$algorithm = "HMAC-SHA256";
$algorithm = $this->service == 'tos' ? "TOS4-HMAC-SHA256" : "HMAC-SHA256";
// step 1: build canonical request string
$httpRequestMethod = $method;
@@ -157,6 +203,9 @@ class Volcengine
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);
@@ -172,21 +221,27 @@ class Volcengine
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 ($arr) {
if ($httpCode == 200) {
if (isset($arr['Result'])) {
return $arr['Result'];
}
return true;
} else {
if (isset($arr['ResponseMetadata']['Error']['MessageCN'])) {
throw new Exception($arr['ResponseMetadata']['Error']['MessageCN']);
} elseif (isset($arr['ResponseMetadata']['Error']['Message'])) {
throw new Exception($arr['ResponseMetadata']['Error']['Message']);
} elseif (isset($arr['Result'])) {
return $arr['Result'];
} elseif (isset($arr['Message'])) {
throw new Exception($arr['Message']);
} elseif (isset($arr['message'])) {
throw new Exception($arr['message']);
} else {
return true;
throw new Exception('返回数据解析失败(http_code=' . $httpCode . ')');
}
} else {
throw new Exception('返回数据解析失败');
}
}
}

View File

@@ -13,17 +13,19 @@ class aliyun implements DeployInterface
private $logger;
private $AccessKeyId;
private $AccessKeySecret;
private $proxy;
public function __construct($config)
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->AccessKeySecret = $config['AccessKeySecret'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function check()
{
if (empty($this->AccessKeyId) || empty($this->AccessKeySecret)) throw new Exception('必填参数不能为空');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cas.aliyuncs.com', '2020-04-07');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cas.aliyuncs.com', '2020-04-07', $this->proxy);
$param = ['Action' => 'ListUserCertificateOrder'];
$client->request($param);
return true;
@@ -79,7 +81,7 @@ class aliyun implements DeployInterface
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$serial_no = strtolower($certInfo['serialNumberHex']);
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cas.aliyuncs.com', '2020-04-07');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cas.aliyuncs.com', '2020-04-07', $this->proxy);
$param = [
'Action' => 'ListUserCertificateOrder',
'Keyword' => $certInfo['subject']['CN'],
@@ -125,7 +127,7 @@ class aliyun implements DeployInterface
{
$domain = $config['domain'];
if (empty($domain)) throw new Exception('CDN绑定域名不能为空');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cdn.aliyuncs.com', '2018-05-10');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cdn.aliyuncs.com', '2018-05-10', $this->proxy);
$param = [
'Action' => 'SetCdnDomainSSLCertificate',
'DomainName' => $domain,
@@ -142,7 +144,7 @@ class aliyun implements DeployInterface
{
$domain = $config['domain'];
if (empty($domain)) throw new Exception('DCDN绑定域名不能为空');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'dcdn.aliyuncs.com', '2018-01-15');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'dcdn.aliyuncs.com', '2018-01-15', $this->proxy);
$param = [
'Action' => 'SetDcdnDomainSSLCertificate',
'DomainName' => $domain,
@@ -239,7 +241,7 @@ class aliyun implements DeployInterface
$endpoint = 'wafopenapi.' . $config['region'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2021-10-01');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2021-10-01', $this->proxy);
$param = [
'Action' => 'DescribeInstance',
@@ -298,7 +300,7 @@ class aliyun implements DeployInterface
$endpoint = 'wafopenapi.' . $config['region'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2019-09-10');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2019-09-10', $this->proxy);
$param = [
'Action' => 'DescribeInstanceInfo',
@@ -337,7 +339,7 @@ class aliyun implements DeployInterface
$endpoint = 'apigateway.' . $config['regionid'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2016-07-14');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2016-07-14', $this->proxy);
$param = [
'Action' => 'SetDomainCertificate',
@@ -359,7 +361,7 @@ class aliyun implements DeployInterface
$endpoint = 'ddoscoo.' . $config['region'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2020-01-01');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2020-01-01', $this->proxy);
$param = [
'Action' => 'AssociateWebCert',
@@ -375,7 +377,7 @@ class aliyun implements DeployInterface
{
$domain = $config['domain'];
if (empty($domain)) throw new Exception('视频直播绑定域名不能为空');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'live.aliyuncs.com', '2016-11-01');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'live.aliyuncs.com', '2016-11-01', $this->proxy);
$param = [
'Action' => 'SetLiveDomainCertificate',
'DomainName' => $domain,
@@ -392,7 +394,7 @@ class aliyun implements DeployInterface
{
$domain = $config['domain'];
if (empty($domain)) throw new Exception('视频点播绑定域名不能为空');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'vod.cn-shanghai.aliyuncs.com', '2017-03-21');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'vod.cn-shanghai.aliyuncs.com', '2017-03-21', $this->proxy);
$param = [
'Action' => 'SetVodDomainCertificate',
'DomainName' => $domain,
@@ -415,7 +417,7 @@ class aliyun implements DeployInterface
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new AliyunNewClient($this->AccessKeyId, $this->AccessKeySecret, $fc_cname, '2023-03-30');
$client = new AliyunNewClient($this->AccessKeyId, $this->AccessKeySecret, $fc_cname, '2023-03-30', $this->proxy);
try {
$data = $client->request('GET', 'GetCustomDomain', '/2023-03-30/custom-domains/' . $domain);
@@ -458,7 +460,7 @@ class aliyun implements DeployInterface
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new AliyunNewClient($this->AccessKeyId, $this->AccessKeySecret, $fc_cname, '2021-04-06');
$client = new AliyunNewClient($this->AccessKeyId, $this->AccessKeySecret, $fc_cname, '2021-04-06', $this->proxy);
try {
$data = $client->request('GET', 'GetCustomDomain', '/2021-04-06/custom-domains/' . $domain);
@@ -495,7 +497,7 @@ class aliyun implements DeployInterface
if (empty($config['clb_port'])) throw new Exception('HTTPS监听端口不能为空');
$endpoint = 'slb.' . $config['regionid'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2014-05-15');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2014-05-15', $this->proxy);
$param = [
'Action' => 'DescribeServerCertificates',
@@ -570,7 +572,7 @@ class aliyun implements DeployInterface
if (empty($config['alb_listener_id'])) throw new Exception('负载均衡监听ID不能为空');
$endpoint = 'alb.' . $config['regionid'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2020-06-16');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2020-06-16', $this->proxy);
$param = [
'Action' => 'ListListenerCertificates',
@@ -605,7 +607,7 @@ class aliyun implements DeployInterface
if (empty($config['nlb_listener_id'])) throw new Exception('负载均衡监听ID不能为空');
$endpoint = 'nlb.' . $config['regionid'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2022-04-30');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2022-04-30', $this->proxy);
$param = [
'Action' => 'ListListenerCertificates',

View File

@@ -52,14 +52,33 @@ class allwaf implements DeployInterface
$this->log('获取证书列表成功(total=' . count($list) . ')');
$certInfo = openssl_x509_parse($fullchain, true);
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
foreach ($list as $row) {
if (!empty($list)) {
foreach ($list as $row) {
$params = [
'sslCertId' => $row['id'],
'isOn' => true,
'name' => $row['name'],
'description' => $row['description'],
'serverName' => $row['serverName'],
'isCA' => false,
'certData' => base64_encode($fullchain),
'keyData' => base64_encode($privatekey),
'timeBeginAt' => $certInfo['validFrom_time_t'],
'timeEndAt' => $certInfo['validTo_time_t'],
'dnsNames' => $domains,
'commonNames' => [$certInfo['issuer']['CN']],
];
$this->request('/SSLCertService/updateSSLCert', $params);
$this->log('证书ID:' . $row['id'] . '更新成功!');
}
} else {
$params = [
'sslCertId' => $row['id'],
'isOn' => true,
'name' => $row['name'],
'description' => $row['description'],
'serverName' => $row['serverName'],
'name' => $cert_name,
'description' => $cert_name,
'serverName' => $certInfo['subject']['CN'],
'isCA' => false,
'certData' => base64_encode($fullchain),
'keyData' => base64_encode($privatekey),
@@ -68,8 +87,8 @@ class allwaf implements DeployInterface
'dnsNames' => $domains,
'commonNames' => [$certInfo['issuer']['CN']],
];
$this->request('/SSLCertService/updateSSLCert', $params);
$this->log('证书ID:' . $row['id'] . '更新成功!');
$result = $this->request('/SSLCertService/createSSLCert', $params);
$this->log('证书ID:' . $result['sslCertId'] . '添加成功!');
}
}

View File

@@ -11,17 +11,19 @@ class aws implements DeployInterface
private $logger;
private $AccessKeyId;
private $SecretAccessKey;
private $proxy;
public function __construct($config)
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->SecretAccessKey = $config['SecretAccessKey'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function check()
{
if (empty($this->AccessKeyId) || empty($this->SecretAccessKey)) throw new Exception('必填参数不能为空');
$client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'iam.amazonaws.com', 'iam', '2010-05-08', 'us-east-1');
$client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'iam.amazonaws.com', 'iam', '2010-05-08', 'us-east-1', $this->proxy);
$client->requestXml('GET', 'GetUser');
return true;
}
@@ -44,7 +46,7 @@ class aws implements DeployInterface
usleep(500000);
}
$client = new \app\lib\client\AWS($this->AccessKeyId, $this->SecretAccessKey, 'cloudfront.amazonaws.com', 'cloudfront', '2020-05-31', 'us-east-1');
$client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'cloudfront.amazonaws.com', 'cloudfront', '2020-05-31', 'us-east-1', $this->proxy);
try {
$data = $client->requestXmlN('GET', '/distribution/' . $config['distribution_id'] . '/config', [], null, true);
} catch (Exception $e) {
@@ -66,7 +68,7 @@ class aws implements DeployInterface
'PrivateKey' => base64_encode($privatekey),
];
$client = new \app\lib\client\AWS($this->AccessKeyId, $this->SecretAccessKey, 'acm.us-east-1.amazonaws.com', 'acm', '', 'us-east-1');
$client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'acm.us-east-1.amazonaws.com', 'acm', '', 'us-east-1', $this->proxy);
try {
$data = $client->request('POST', 'CertificateManager.ImportCertificate', $param);
$cert_id = $data['CertificateArn'];

View File

@@ -11,17 +11,19 @@ class baidu implements DeployInterface
private $logger;
private $AccessKeyId;
private $SecretAccessKey;
private $proxy;
public function __construct($config)
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->SecretAccessKey = $config['SecretAccessKey'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function check()
{
if (empty($this->AccessKeyId) || empty($this->SecretAccessKey)) throw new Exception('必填参数不能为空');
$client = new BaiduCloud($this->AccessKeyId, $this->SecretAccessKey, 'cdn.baidubce.com');
$client = new BaiduCloud($this->AccessKeyId, $this->SecretAccessKey, 'cdn.baidubce.com', $this->proxy);
$client->request('GET', '/v2/domain');
return true;
}
@@ -33,7 +35,7 @@ class baidu implements DeployInterface
if (!$certInfo) throw new Exception('证书解析失败');
$config['cert_name'] = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new BaiduCloud($this->AccessKeyId, $this->SecretAccessKey, 'cdn.baidubce.com');
$client = new BaiduCloud($this->AccessKeyId, $this->SecretAccessKey, 'cdn.baidubce.com', $this->proxy);
try {
$data = $client->request('GET', '/v2/' . $config['domain'] . '/certificates');
if (isset($data['certName']) && $data['certName'] == $config['cert_name']) {

81
app/lib/deploy/ctyun.php Normal file
View File

@@ -0,0 +1,81 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use app\lib\client\Ctyun as CtyunClient;
use Exception;
class ctyun implements DeployInterface
{
private $logger;
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');
return true;
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$param = [
'name' => $cert_name,
'key' => $privatekey,
'certs' => $fullchain,
];
try {
$this->client->request('POST', '/v1/cert/creat-cert', null, $param);
} catch (Exception $e) {
if (strpos($e->getMessage(), '已存在重名的证书') !== false) {
$this->log('已存在重名的证书 cert_name=' . $cert_name);
} else {
throw new Exception('上传证书失败:' . $e->getMessage());
}
}
$this->log('上传证书成功 cert_name=' . $cert_name);
$param = [
'domain' => $config['domain'],
'https_status' => 'on',
'cert_name' => $cert_name,
];
try {
$this->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'] . ' 部署证书成功!');
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
}

View File

@@ -10,11 +10,13 @@ class doge implements DeployInterface
private $logger;
private $AccessKey;
private $SecretKey;
private $proxy;
public function __construct($config)
{
$this->AccessKey = $config['AccessKey'];
$this->SecretKey = $config['SecretKey'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function check()
@@ -95,7 +97,7 @@ class doge implements DeployInterface
$headers = ['Authorization: ' . $authorization];
if($body && $json) $headers[] = 'Content-Type: application/json';
$url = 'https://api.dogecloud.com'.$path;
$response = curl_client($url, $body, null, null, $headers);
$response = curl_client($url, $body, null, null, $headers, $this->proxy);
$result = json_decode($response['body'], true);
if(isset($result['code']) && $result['code'] == 200){
return isset($result['data']) ? $result['data'] : true;

View File

@@ -50,20 +50,39 @@ class goedge implements DeployInterface
throw new Exception('获取证书列表失败:' . $e->getMessage());
}
$list = json_decode(base64_decode($data['sslCertsJSON']), true);
if (!$list || empty($list)) {
if ($list === false) {
throw new Exception('证书列表为空');
}
$this->log('获取证书列表成功(total=' . count($list) . ')');
$certInfo = openssl_x509_parse($fullchain, true);
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
foreach ($list as $row) {
if (!empty($list)) {
foreach ($list as $row) {
$params = [
'sslCertId' => $row['id'],
'isOn' => true,
'name' => $row['name'],
'description' => $row['description'],
'serverName' => $row['serverName'],
'isCA' => false,
'certData' => base64_encode($fullchain),
'keyData' => base64_encode($privatekey),
'timeBeginAt' => $certInfo['validFrom_time_t'],
'timeEndAt' => $certInfo['validTo_time_t'],
'dnsNames' => $domains,
'commonNames' => [$certInfo['issuer']['CN']],
];
$this->request('/SSLCertService/updateSSLCert', $params);
$this->log('证书ID:' . $row['id'] . '更新成功!');
}
} else {
$params = [
'sslCertId' => $row['id'],
'isOn' => true,
'name' => $row['name'],
'description' => $row['description'],
'serverName' => $row['serverName'],
'name' => $cert_name,
'description' => $cert_name,
'serverName' => $certInfo['subject']['CN'],
'isCA' => false,
'certData' => base64_encode($fullchain),
'keyData' => base64_encode($privatekey),
@@ -72,8 +91,8 @@ class goedge implements DeployInterface
'dnsNames' => $domains,
'commonNames' => [$certInfo['issuer']['CN']],
];
$this->request('/SSLCertService/updateSSLCert', $params);
$this->log('证书ID:' . $row['id'] . '更新成功!');
$result = $this->request('/SSLCertService/createSSLCert', $params);
$this->log('证书ID:' . $result['sslCertId'] . '添加成功!');
}
}

View File

@@ -11,17 +11,19 @@ class huawei implements DeployInterface
private $logger;
private $AccessKeyId;
private $SecretAccessKey;
private $proxy;
public function __construct($config)
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->SecretAccessKey = $config['SecretAccessKey'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function check()
{
if (empty($this->AccessKeyId) || empty($this->SecretAccessKey)) throw new Exception('必填参数不能为空');
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, 'scm.cn-north-4.myhuaweicloud.com');
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, 'scm.cn-north-4.myhuaweicloud.com', $this->proxy);
$client->request('GET', '/v3/scm/certificates');
return true;
}
@@ -43,7 +45,7 @@ class huawei implements DeployInterface
private function deploy_cdn($fullchain, $privatekey, $config)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, 'cdn.myhuaweicloud.com');
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, 'cdn.myhuaweicloud.com', $this->proxy);
$param = [
'configs' => [
'https' => [
@@ -66,7 +68,7 @@ class huawei implements DeployInterface
if (empty($config['region_id'])) throw new Exception('区域ID不能为空');
if (empty($config['cert_id'])) throw new Exception('证书ID不能为空');
$endpoint = 'elb.' . $config['region_id'] . '.myhuaweicloud.com';
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, $endpoint);
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, $endpoint, $this->proxy);
try {
$data = $client->request('GET', '/v3/' . $config['project_id'] . '/elb/certificates/' . $config['cert_id']);
} catch (Exception $e) {
@@ -93,7 +95,7 @@ class huawei implements DeployInterface
if (empty($config['region_id'])) throw new Exception('区域ID不能为空');
if (empty($config['cert_id'])) throw new Exception('证书ID不能为空');
$endpoint = 'waf.' . $config['region_id'] . '.myhuaweicloud.com';
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, $endpoint);
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, $endpoint, $this->proxy);
try {
$data = $client->request('GET', '/v1/' . $config['project_id'] . '/waf/certificates/' . $config['cert_id']);
} catch (Exception $e) {
@@ -118,7 +120,7 @@ class huawei implements DeployInterface
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, 'scm.cn-north-4.myhuaweicloud.com');
$client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, 'scm.cn-north-4.myhuaweicloud.com', $this->proxy);
$param = [
'name' => $cert_name,
'certificate' => $fullchain,

View File

@@ -11,34 +11,49 @@ class huoshan implements DeployInterface
private $logger;
private $AccessKeyId;
private $SecretAccessKey;
private $proxy;
public function __construct($config)
{
$this->AccessKeyId = $config['AccessKeyId'];
$this->SecretAccessKey = $config['SecretAccessKey'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function check()
{
if (empty($this->AccessKeyId) || empty($this->SecretAccessKey)) throw new Exception('必填参数不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'cdn.volcengineapi.com', 'cdn', '2021-03-01', 'cn-north-1');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'cdn', '2021-03-01', 'cn-north-1', $this->proxy);
$client->request('POST', 'ListCertInfo', ['Source' => 'volc_cert_center']);
return true;
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$cert_id = $this->get_cert_id($fullchain, $privatekey);
if (!$cert_id) throw new Exception('获取证书ID失败');
$info['cert_id'] = $cert_id;
$this->deploy_cdn($cert_id, $config);
if ($config['product'] == 'live') {
$this->deploy_live($fullchain, $privatekey, $config);
} else {
$cert_id = $this->get_cert_id($fullchain, $privatekey);
if (!$cert_id) throw new Exception('获取证书ID失败');
$info['cert_id'] = $cert_id;
if (!isset($config['product']) || $config['product'] == 'cdn') {
$this->deploy_cdn($cert_id, $config);
} elseif ($config['product'] == 'dcdn') {
$this->deploy_dcdn($cert_id, $config);
} elseif ($config['product'] == 'tos') {
$this->deploy_tos($cert_id, $config);
} elseif ($config['product'] == 'imagex') {
$this->deploy_imagex($cert_id, $config);
} elseif ($config['product'] == 'clb') {
$this->deploy_clb($cert_id, $config);
}
}
}
private function deploy_cdn($cert_id, $config)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'cdn.volcengineapi.com', 'cdn', '2021-03-01', 'cn-north-1');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'cdn.volcengineapi.com', 'cdn', '2021-03-01', 'cn-north-1', $this->proxy);
$param = [
'CertId' => $cert_id,
'Domain' => $config['domain'],
@@ -49,37 +64,137 @@ class huoshan implements DeployInterface
if ($row['Status'] == 'success') {
$this->log('CDN域名 ' . $row['Domain'] . ' 部署证书成功!');
} else {
$this->log('CDN域名 ' . $row['Domain'] . ' 部署证书失败:' . isset($row['ErrorMsg']) ? $row['ErrorMsg'] : '');
$this->log('CDN域名 ' . $row['Domain'] . ' 部署证书失败:' . (isset($row['ErrorMsg']) ? $row['ErrorMsg'] : ''));
}
}
}
private function deploy_dcdn($cert_id, $config)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'dcdn', '2021-04-01', 'cn-north-1', $this->proxy);
$param = [
'CertId' => $cert_id,
'DomainNames' => explode(',', $config['domain']),
];
$client->request('POST', 'CreateCertBind', $param);
$this->log('DCDN域名 ' . $config['domain'] . ' 部署证书成功!');
}
private function deploy_tos($cert_id, $config)
{
if (empty($config['bucket_domain'])) throw new Exception('Bucket域名不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, $config['bucket_domain'], 'tos', '2021-04-01', 'cn-beijing', $this->proxy);
foreach (explode(',', $config['domain']) as $domain) {
$param = [
'CustomDomainRule' => [
'Domain' => $domain,
'CertId' => $cert_id,
]
];
$query = ['customdomain' => ''];
$client->tos_request('PUT', $param, $query);
$this->log('对象存储域名 ' . $config['domain'] . ' 部署证书成功!');
}
}
private function deploy_live($fullchain, $privatekey, $config)
{
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'live.volcengineapi.com', 'live', '2023-01-01', 'cn-north-1', $this->proxy);
$param = [
'CertName' => $cert_name,
'Rsa' => [
'Pubkey' => $fullchain,
'Prikey' => $privatekey,
],
'UseWay' => 'https',
];
$result = $client->request('POST', 'CreateCert', $param);
$this->log('上传证书成功 ChainID=' . $result['ChainID']);
foreach (explode(',', $config['domain']) as $domain) {
$param = [
'ChainID' => $result['ChainID'],
'Domain' => $domain,
'HTTPS' => true,
'HTTP2' => true,
];
$client->request('POST', 'BindCert', $param);
$this->log('视频直播域名 ' . $domain . ' 部署证书成功!');
}
}
private function deploy_imagex($cert_id, $config)
{
if (empty($config['bucket_domain'])) throw new Exception('Bucket域名不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'imagex.volcengineapi.com', 'imagex', '2018-08-01', 'cn-north-1', $this->proxy);
foreach (explode(',', $config['domain']) as $domain) {
$param = [
[
'domain' => $domain,
'cert_id' => $cert_id,
]
];
$result = $client->request('POST', 'UpdateImageBatchDomainCert', $param);
if (isset($result['SuccessDomains']) && count($result['SuccessDomains']) > 0) {
$this->log('veImageX域名 ' . $domain . ' 部署证书成功!');
} elseif (isset($result['FailedDomains']) && count($result['FailedDomains']) > 0) {
$errmsg = $result['FailedDomains'][0]['ErrMsg'];
$this->log('veImageX域名 ' . $domain . ' 部署证书失败:' . $errmsg);
} else {
$this->log('veImageX域名 ' . $domain . ' 部署证书失败');
}
}
}
private function deploy_clb($cert_id, $config)
{
if (empty($config['listener_id'])) throw new Exception('监听器ID不能为空');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'clb', '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('CLB监听器 ' . $config['listener_id'] . ' 部署证书成功!');
}
private function get_cert_id($fullchain, $privatekey)
{
$certInfo = openssl_x509_parse($fullchain, true);
if (!$certInfo) throw new Exception('证书解析失败');
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'cdn.volcengineapi.com', 'cdn', '2021-03-01', 'cn-north-1');
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'certificate_service', '2024-10-01', 'cn-beijing', $this->proxy);
$param = [
'Source' => 'volc_cert_center',
'Certificate' => $fullchain,
'PrivateKey' => $privatekey,
'Desc' => $cert_name,
'Tag' => $cert_name,
'Repeatable' => false,
'CertificateInfo' => [
'CertificateChain' => $fullchain,
'PrivateKey' => $privatekey,
],
];
try {
$data = $client->request('POST', 'AddCertificate', $param);
$data = $client->request('POST', 'ImportCertificate', $param);
} catch (Exception $e) {
if (strpos($e->getMessage(), '证书已存在ID为') !== false) {
$cert_id = trim(getSubstr($e->getMessage(), '证书已存在ID为', '。'));
$this->log('证书已存在 CertId=' . $cert_id);
return $cert_id;
}
throw new Exception('上传证书失败:' . $e->getMessage());
}
$this->log('上传证书成功 CertId=' . $data['CertId']);
return $data['CertId'];
if (!empty($data['InstanceId'])) {
$cert_id = $data['InstanceId'];
} else {
$cert_id = $data['RepeatId'];
}
$this->log('上传证书成功 CertId=' . $cert_id);
return $cert_id;
}
public function setLogger($func)

View File

@@ -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']);

127
app/lib/deploy/mwpanel.php Normal file
View File

@@ -0,0 +1,127 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class mwpanel implements DeployInterface
{
private $logger;
private $url;
private $appid;
private $appsecret;
private $proxy;
public function __construct($config)
{
$this->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'];
}
}

View File

@@ -40,32 +40,35 @@ class opanel implements DeployInterface
$success = 0;
$errmsg = null;
foreach ($data['items'] as $row) {
if (empty($row['primaryDomain'])) continue;
$cert_domains[] = $row['primaryDomain'];
if(!empty($row['domains'])) $cert_domains += explode(',', $row['domains']);
$flag = false;
foreach ($cert_domains as $domain) {
if (in_array($domain, $domains)) {
$flag = true;
break;
if (!empty($data['items'])) {
foreach ($data['items'] as $row) {
if (empty($row['primaryDomain'])) continue;
$cert_domains = [];
$cert_domains[] = $row['primaryDomain'];
if(!empty($row['domains'])) $cert_domains += explode(',', $row['domains']);
$flag = false;
foreach ($cert_domains as $domain) {
if (in_array($domain, $domains)) {
$flag = true;
break;
}
}
}
if ($flag) {
$params = [
'sslID' => $row['id'],
'type' => 'paste',
'certificate' => $fullchain,
'privateKey' => $privatekey,
'description' => '',
];
try {
$this->request('/api/v1/websites/ssl/upload', $params);
$this->log("证书ID:{$row['id']}更新成功!");
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$this->log("证书ID:{$row['id']}更新失败:" . $errmsg);
if ($flag) {
$params = [
'sslID' => $row['id'],
'type' => 'paste',
'certificate' => $fullchain,
'privateKey' => $privatekey,
'description' => '',
];
try {
$this->request('/api/v1/websites/ssl/upload', $params);
$this->log("证书ID:{$row['id']}更新成功!");
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$this->log("证书ID:{$row['id']}更新失败:" . $errmsg);
}
}
}
}
@@ -96,7 +99,7 @@ class opanel implements DeployInterface
'1Panel-Token: '.$token,
'1Panel-Timestamp: '.$timestamp
];
$body = $params ? json_encode($params) : null;
$body = $params ? json_encode($params) : '{}';
if($body) $headers[] = 'Content-Type: application/json';
$response = curl_client($url, $body, null, null, $headers, $this->proxy);
$result = json_decode($response['body'], true);

View File

@@ -17,7 +17,7 @@ class qiniu implements DeployInterface
{
$this->AccessKey = $config['AccessKey'];
$this->SecretKey = $config['SecretKey'];
$this->client = new QiniuClient($this->AccessKey, $this->SecretKey);
$this->client = new QiniuClient($this->AccessKey, $this->SecretKey, isset($config['proxy']) ? $config['proxy'] == 1 : false);
}
public function check()
@@ -42,6 +42,8 @@ class qiniu implements DeployInterface
$this->deploy_cdn($domain, $cert_id);
} elseif ($config['product'] == 'oss') {
$this->deploy_oss($domain, $cert_id);
} elseif ($config['product'] == 'pili') {
$this->deploy_pili($config['pili_hub'], $domain, $cert_name);
} else {
throw new Exception('未知的产品类型');
}
@@ -87,6 +89,15 @@ class qiniu implements DeployInterface
$this->log('OSS域名 ' . $domain . ' 证书部署成功!');
}
private function deploy_pili($hub, $domain, $cert_name)
{
$param = [
'CertName' => $cert_name,
];
$this->client->pili_request('POST', '/v2/hubs/'.$hub.'/domains/'.$domain.'/cert', null, $param);
$this->log('视频直播域名 ' . $domain . ' 证书部署成功!');
}
private function get_cert_id($fullchain, $privatekey, $common_name, $cert_name)
{
$cert_id = null;

View File

@@ -68,7 +68,15 @@ class safeline implements DeployInterface
}
}
if ($success == 0) {
throw new Exception($errmsg ? $errmsg : '没有要更新的证书');
$params = [
'manual' => [
'crt' => $fullchain,
'key' => $privatekey,
],
'type' => 2,
];
$this->request('/api/open/cert', $params);
$this->log("证书上传成功!");
}
}

View File

@@ -12,12 +12,14 @@ class tencent implements DeployInterface
private $SecretId;
private $SecretKey;
private TencentCloud $client;
private $proxy;
public function __construct($config)
{
$this->SecretId = $config['SecretId'];
$this->SecretKey = $config['SecretKey'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05');
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', null, $this->proxy);
}
public function check()
@@ -35,21 +37,25 @@ class tencent implements DeployInterface
if (empty($config['regionid'])) throw new Exception('所属地域ID不能为空');
if (empty($config['cos_bucket'])) throw new Exception('存储桶名称不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$instance_id = $config['regionid'] . '#' . $config['cos_bucket'] . '#' . $config['domain'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid']);
$instance_id = $config['regionid'] . '|' . $config['cos_bucket'] . '|' . $config['domain'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid'], $this->proxy);
} elseif ($config['product'] == 'tke') {
if (empty($config['regionid'])) throw new Exception('所属地域ID不能为空');
if (empty($config['tke_cluster_id'])) throw new Exception('集群ID不能为空');
if (empty($config['tke_namespace'])) throw new Exception('命名空间不能为空');
if (empty($config['tke_secret'])) throw new Exception('secret名称不能为空');
$instance_id = $config['tke_cluster_id'] . '|' . $config['tke_namespace'] . '|' . $config['tke_secret'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid']);
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid'], $this->proxy);
} elseif ($config['product'] == 'lighthouse') {
if (empty($config['regionid'])) throw new Exception('所属地域ID不能为空');
if (empty($config['lighthouse_id'])) throw new Exception('实例ID不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$instance_id = $config['regionid'] . '|' . $config['lighthouse_id'] . '|' . $config['domain'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid']);
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid'], $this->proxy);
} elseif ($config['product'] == 'ddos') {
if (empty($config['lighthouse_id'])) throw new Exception('实例ID不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$instance_id = $config['lighthouse_id'] . '|' . $config['domain'] . '|443';
} elseif ($config['product'] == 'clb') {
return $this->deploy_clb($cert_id, $config);
} elseif ($config['product'] == 'scf') {
@@ -57,10 +63,10 @@ class tencent implements DeployInterface
} else {
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
if ($config['product'] == 'waf') {
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['region']);
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['region'], $this->proxy);
} elseif (in_array($config['product'], ['tse', 'scf'])) {
if (empty($config['regionid'])) throw new Exception('所属地域ID不能为空');
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid']);
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, 'ssl.tencentcloudapi.com', 'ssl', '2019-12-05', $config['regionid'], $this->proxy);
}
$instance_id = $config['domain'];
}
@@ -104,9 +110,14 @@ class tencent implements DeployInterface
private function deploy_common($product, $cert_id, $instance_id)
{
if (in_array($product, ['cdn', 'waf', 'teo', 'ddos', 'live', 'vod']) && strpos($instance_id, ',') !== false) {
$instance_ids = explode(',', $instance_id);
} else {
$instance_ids = [$instance_id];
}
$param = [
'CertificateId' => $cert_id,
'InstanceIdList' => [$instance_id],
'InstanceIdList' => $instance_ids,
'ResourceType' => $product,
];
$data = $this->client->request('DeployCertificateInstance', $param);
@@ -144,7 +155,7 @@ class tencent implements DeployInterface
if (empty($config['clb_id'])) throw new Exception('负载均衡ID不能为空');
$sni_switch = !empty($config['clb_domain']) ? 1 : 0;
$client = new TencentCloud($this->SecretId, $this->SecretKey, 'clb.tencentcloudapi.com', 'clb', '2018-03-17', $config['regionid']);
$client = new TencentCloud($this->SecretId, $this->SecretKey, 'clb.tencentcloudapi.com', 'clb', '2018-03-17', $config['regionid'], $this->proxy);
$param = [
'LoadBalancerId' => $config['clb_id'],
'Protocol' => 'HTTPS',
@@ -209,7 +220,7 @@ class tencent implements DeployInterface
if (empty($config['regionid'])) throw new Exception('所属地域ID不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$client = new TencentCloud($this->SecretId, $this->SecretKey, 'scf.tencentcloudapi.com', 'scf', '2018-04-16', $config['regionid']);
$client = new TencentCloud($this->SecretId, $this->SecretKey, 'scf.tencentcloudapi.com', 'scf', '2018-04-16', $config['regionid'], $this->proxy);
$param = [
'Domain' => $config['domain'],
];

131
app/lib/deploy/west.php Normal file
View File

@@ -0,0 +1,131 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class west implements DeployInterface
{
private $logger;
private $username;
private $api_password;
private $baseUrl = 'https://api.west.cn/api/v2';
private $proxy;
public function __construct($config)
{
$this->username = $config['username'];
$this->api_password = $config['api_password'];
$this->proxy = $config['proxy'] == 1;
}
public function check()
{
if (empty($this->username) || empty($this->api_password)) throw new Exception('用户名或API密码不能为空');
$this->execute('/vhost/', ['act' => 'products']);
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
if (empty($config['sitename'])) throw new Exception('FTP账号不能为空');
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'info'
];
try {
$data = $this->execute('/vhost/', $params);
} catch (Exception $e) {
throw new Exception('获取虚拟主机SSL配置失败:' . $e->getMessage());
}
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'import',
'keycontent' => $privatekey,
'certcontent' => $fullchain,
];
try {
$this->execute('/vhost/', $params);
} catch (Exception $e) {
throw new Exception('上传SSL证书失败:' . $e->getMessage());
}
$this->log('SSL证书上传成功');
if (!isset($data['SSLEnabled']) || $data['SSLEnabled'] == 0) {
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'openssl',
];
try {
$this->execute('/vhost/', $params);
} catch (Exception $e) {
throw new Exception('虚拟主机部署SSL失败:' . $e->getMessage());
}
} else {
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'info'
];
try {
$data = $this->execute('/vhost/', $params);
} catch (Exception $e) {
throw new Exception('获取虚拟主机SSL配置失败:' . $e->getMessage());
}
if (!empty($data['sslcert']['ssl'])) {
foreach ($data['sslcert']['ssl'] as $domain => $row) {
if (!in_array($domain, $config['domainList'])) continue;
$params = [
'act' => 'vhostssl',
'sitename' => $config['sitename'],
'cmd' => 'clearsslcache',
'sslid' => $row['sysid'],
'dm' => $domain,
];
try {
$this->execute('/vhost/', $params);
$this->log('更新' . $domain . '证书缓存成功');
} catch (Exception $e) {
$this->log('更新' . $domain . '证书缓存失败:' . $e->getMessage());
}
}
}
}
$this->log('虚拟主机' . $config['sitename'] . '部署SSL成功');
}
private function execute($path, $params)
{
$params['username'] = $this->username;
$params['time'] = getMillisecond();
$params['token'] = md5($this->username . $this->api_password . $params['time']);
$response = curl_client($this->baseUrl . $path, str_replace('+', '%20', http_build_query($params)), null, null, null, $this->proxy);
$response = mb_convert_encoding($response['body'], 'UTF-8', 'GBK');
$arr = json_decode($response, true);
if ($arr) {
if ($arr['result'] == 200) {
return isset($arr['data']) ? $arr['data'] : [];
} else {
throw new Exception($arr['msg']);
}
} else {
throw new Exception('请求失败(httpCode=' . $response['code'] . ')');
}
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
}

View File

@@ -22,7 +22,8 @@ class aliyun implements DnsInterface
{
$this->AccessKeyId = $config['ak'];
$this->AccessKeySecret = $config['sk'];
$this->client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $this->Endpoint, $this->Version);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $this->Endpoint, $this->Version, $proxy);
$this->domain = $config['domain'];
}

View File

@@ -20,7 +20,8 @@ class baidu implements DnsInterface
{
$this->AccessKeyId = $config['ak'];
$this->SecretAccessKey = $config['sk'];
$this->client = new BaiduCloud($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new BaiduCloud($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint, $proxy);
$this->domain = $config['domain'];
$this->domainid = $config['domainid'];
}

View File

@@ -12,6 +12,7 @@ class cloudflare implements DnsInterface
private $error;
private $domain;
private $domainid;
private $proxy;
function __construct($config)
{
@@ -19,6 +20,7 @@ class cloudflare implements DnsInterface
$this->ApiKey = $config['sk'];
$this->domain = $config['domain'];
$this->domainid = $config['domainid'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function getError()
@@ -261,6 +263,9 @@ class cloudflare implements DnsInterface
}
$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, $headers);

View File

@@ -13,6 +13,7 @@ class dnsla implements DnsInterface
private $error;
private $domain;
private $domainid;
private $proxy;
public function __construct($config)
{
@@ -20,6 +21,7 @@ class dnsla implements DnsInterface
$this->apisecret = $config['sk'];
$this->domain = $config['domain'];
$this->domainid = $config['domainid'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function getError()
@@ -249,10 +251,13 @@ class dnsla implements DnsInterface
}
}
private function curl($method, $path, $header, $body = null, $isPut = false)
private function curl($method, $path, $header, $body = null)
{
$url = $this->baseUrl . $path;
$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);

View File

@@ -23,7 +23,8 @@ class dnspod implements DnsInterface
{
$this->SecretId = $config['ak'];
$this->SecretKey = $config['sk'];
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, $this->endpoint, $this->service, $this->version);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, $this->endpoint, $this->service, $this->version, null, $proxy);
$this->domain = $config['domain'];
}

View File

@@ -20,7 +20,8 @@ class huawei implements DnsInterface
{
$this->AccessKeyId = $config['ak'];
$this->SecretAccessKey = $config['sk'];
$this->client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new HuaweiCloud($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint, $proxy);
$this->domain = $config['domain'];
$this->domainid = $config['domainid'];
}
@@ -134,7 +135,7 @@ class huawei implements DnsInterface
{
$Name = $this->getHost($Name);
if ($Type == 'TXT' && substr($Value, 0, 1) != '"') $Value = '"' . $Value . '"';
$records = explode(',', $Value);
$records = array_reverse(explode(',', $Value));
$params = ['name' => $Name, 'type' => $this->convertType($Type), 'records' => $records, 'line' => $Line, 'ttl' => intval($TTL), 'description' => $Remark];
if ($Type == 'MX') $params['records'][0] = intval($MX) . ' ' . $Value;
if ($Weight > 0) $params['weight'] = intval($Weight);
@@ -147,7 +148,7 @@ class huawei implements DnsInterface
{
$Name = $this->getHost($Name);
if ($Type == 'TXT' && substr($Value, 0, 1) != '"') $Value = '"' . $Value . '"';
$records = explode(',', $Value);
$records = array_reverse(explode(',', $Value));
$params = ['name' => $Name, 'type' => $this->convertType($Type), 'records' => $records, 'line' => $Line, 'ttl' => intval($TTL), 'description' => $Remark];
if ($Type == 'MX') $params['records'][0] = intval($MX) . ' ' . $Value;
if ($Weight > 0) $params['weight'] = intval($Weight);

View File

@@ -32,7 +32,8 @@ class huoshan implements DnsInterface
{
$this->AccessKeyId = $config['ak'];
$this->SecretAccessKey = $config['sk'];
$this->client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint, $this->service, $this->version, $this->region);
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
$this->client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, $this->endpoint, $this->service, $this->version, $this->region, $proxy);
$this->domain = $config['domain'];
$this->domainid = $config['domainid'];
}

View File

@@ -12,11 +12,13 @@ class namesilo implements DnsInterface
private $version = '1';
private $error;
private $domain;
private $proxy;
function __construct($config)
{
$this->apikey = $config['sk'];
$this->domain = $config['domain'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function getError()
@@ -193,7 +195,7 @@ class namesilo implements DnsInterface
$url .= '?' . http_build_query($params);
try{
$response = curl_client($url);
$response = curl_client($url, null, null, null, null, $this->proxy);
}catch(Exception $e){
$this->setError($e->getMessage());
return false;

View File

@@ -15,12 +15,14 @@ class west implements DnsInterface
private $error;
private $domain;
private $domainid;
private $proxy;
public function __construct($config)
{
$this->username = $config['ak'];
$this->api_password = $config['sk'];
$this->domain = $config['domain'];
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
}
public function getError()
@@ -177,10 +179,15 @@ class west implements DnsInterface
private function execute($path, $params)
{
$params['username'] = $this->username;
$params['time'] = $this->getMillisecond();
$params['time'] = getMillisecond();
$params['token'] = md5($this->username.$this->api_password.$params['time']);
$response = $this->curl($path, $params);
$response = mb_convert_encoding($response, 'UTF-8', 'GBK');
try{
$response = curl_client($this->baseUrl . $path, http_build_query($params), null, null, null, $this->proxy);
}catch(\Exception $e){
$this->setError($e->getMessage());
return false;
}
$response = mb_convert_encoding($response['body'], 'UTF-8', 'GBK');
$arr = json_decode($response, true);
if ($arr) {
if ($arr['result'] == 200) {
@@ -195,34 +202,6 @@ class west implements DnsInterface
}
}
private function curl($path, $params = null)
{
$url = $this->baseUrl . $path;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
if ($params) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
}
$response = curl_exec($ch);
$errno = curl_errno($ch);
if ($errno) {
$this->setError('Curl error: ' . curl_error($ch));
}
curl_close($ch);
if ($errno) return false;
return $response;
}
private function getMillisecond()
{
list($s1, $s2) = explode(' ', microtime());
return (float)sprintf('%.0f', (floatval($s1) + floatval($s2)) * 1000);
}
private function setError($message)
{
$this->error = $message;

View File

@@ -131,8 +131,13 @@ class CertDeployService
private function saveLog($txt)
{
if (empty($this->task['processid'])) return;
if (!is_dir(app()->getRuntimePath() . 'log')) mkdir(app()->getRuntimePath() . 'log');
$file_name = app()->getRuntimePath().'log/'.$this->task['processid'].'.log';
$file_exists = file_exists($file_name);
file_put_contents($file_name, $txt . PHP_EOL, FILE_APPEND);
if (!$file_exists) {
@chmod($file_name, 0777);
}
if(php_sapi_name() == 'cli'){
echo $txt . PHP_EOL;
}

View File

@@ -69,6 +69,7 @@ class CertOrderService
foreach($this->domainList as $domain){
$mainDomain = getMainDomain($domain);
if (!Db::name('domain')->where('name', $mainDomain)->find()) {
if (substr($domain, 0, 2) == '*.') $domain = substr($domain, 2);
$cname_row = Db::name('cert_cname')->where('domain', $domain)->where('status', 1)->find();
if (!$cname || !$cname_row) {
$errmsg = '域名'.$domain.'未在本系统添加';
@@ -116,7 +117,9 @@ class CertOrderService
$this->saveLog(date('Y-m-d H:i:s').' - 开始添加DNS记录');
$this->addDns();
$this->saveLog('添加DNS记录成功请等待生效后进行验证...');
Db::name('cert_order')->where('id', $this->order['id'])->update(['retrytime' => date('Y-m-d H:i:s', time() + 300)]);
if (CertHelper::$cert_config[$this->atype]['cname']) {
Db::name('cert_order')->where('id', $this->order['id'])->update(['retrytime' => date('Y-m-d H:i:s', time() + 180)]);
}
return 1;
}
// step4: 查询DNS
@@ -417,8 +420,13 @@ class CertOrderService
private function saveLog($txt)
{
if (empty($this->order['processid'])) return;
if (!is_dir(app()->getRuntimePath() . 'log')) mkdir(app()->getRuntimePath() . 'log');
$file_name = app()->getRuntimePath().'log/'.$this->order['processid'].'.log';
$file_exists = file_exists($file_name);
file_put_contents($file_name, $txt . PHP_EOL, FILE_APPEND);
if (!$file_exists) {
@chmod($file_name, 0777);
}
if(php_sapi_name() == 'cli'){
echo $txt . PHP_EOL;
}

View File

@@ -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++;
}

View File

@@ -18,6 +18,7 @@ CREATE TABLE `dnsmgr_account` (
`ak` varchar(256) DEFAULT NULL,
`sk` varchar(256) DEFAULT NULL,
`ext` varchar(256) DEFAULT NULL,
`proxy` tinyint(1) NOT NULL DEFAULT '0',
`remark` varchar(100) DEFAULT NULL,
`addtime` datetime DEFAULT NULL,
PRIMARY KEY (`id`)

View File

@@ -149,4 +149,7 @@ CREATE TABLE IF NOT EXISTS `dnsmgr_cert_cname` (
`addtime` datetime DEFAULT NULL,
`status` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `dnsmgr_account`
ADD COLUMN `proxy` tinyint(1) NOT NULL DEFAULT '0';

View File

@@ -39,6 +39,9 @@ class CertDnsUtils
usort($list, function ($a, $b) {
return strcmp($a['name'], $b['name']);
});
if ($drow['type'] == 'huawei') {
$list = self::getHuaweiDnsRecords($list);
}
$records = [];
foreach ($list as $row) {
$domain = $row['name'] . '.' . $mainDomain;
@@ -46,7 +49,8 @@ class CertDnsUtils
if (!$records[$row['name']]) throw new Exception('获取'.$domain.'记录列表失败,'.$dns->getError());
$filter_records = array_filter($records[$row['name']]['list'], function ($v) use ($row) {
return $v['Type'] == $row['type'] && $v['Value'] == $row['value'];
if (is_array($v['Value'])) $v['Value'] = implode(',', $v['Value']);
return $v['Type'] == $row['type'] && ($v['Value'] == $row['value'] || rtrim($v['Value'], '.') == $row['value']);
});
if (!empty($filter_records)) {
foreach ($filter_records as $recordid => $record) {
@@ -76,6 +80,22 @@ class CertDnsUtils
}
}
private static function getHuaweiDnsRecords($list)
{
//将name相同的TXT记录合并
$txt_records = [];
foreach ($list as $key => $row) {
if ($row['type'] == 'TXT') {
$txt_records[$row['name']][] = $row['value'];
unset($list[$key]);
}
}
foreach ($txt_records as $name => $rows) {
$list[] = ['name' => $name, 'type' => 'TXT', 'value' => '"' . implode('","', $rows) . '"'];
}
return $list;
}
public static function delDns($dnsList, callable $log, $cname = false)
{
$cnameDomainList = [];
@@ -107,6 +127,9 @@ class CertDnsUtils
usort($list, function ($a, $b) {
return strcmp($a['name'], $b['name']);
});
if ($drow['type'] == 'huawei') {
$list = self::getHuaweiDnsRecords($list);
}
$records = [];
foreach ($list as $row) {
//if ($row['type'] == 'CAA') continue;
@@ -115,6 +138,7 @@ class CertDnsUtils
if (!$records[$row['name']]) throw new Exception('获取'.$domain.'记录列表失败,'.$dns->getError());
$filter_records = array_filter($records[$row['name']]['list'], function ($v) use ($row) {
if (is_array($v['Value'])) $v['Value'] = implode(',', $v['Value']);
return $v['Type'] == $row['type'] && ($v['Value'] == $row['value'] || rtrim($v['Value'], '.') == $row['value']);
});
if (empty($filter_records)) continue;

View File

@@ -74,9 +74,13 @@ class CheckUtils
public static function tcp($target, $port, $timeout)
{
if (substr($target, -1) == '.') $target = substr($target, 0, -1);
if (!filter_var($target, FILTER_VALIDATE_IP) && checkDomain($target)) {
$target = gethostbyname($target);
if (!$target) return ['status' => false, 'error' => 'DNS resolve failed', 'usetime' => 0];
if (!$target) return ['status' => false, 'errmsg' => 'DNS resolve failed', 'usetime' => 0];
}
if (filter_var($target, FILTER_VALIDATE_IP) && strpos($target, ':') !== false) {
$target = '['.$target.']';
}
$starttime = getMillisecond();
$fp = @fsockopen($target, $port, $errCode, $errStr, $timeout);
@@ -93,13 +97,14 @@ class CheckUtils
public static function ping($target)
{
if (!function_exists('exec')) return ['status' => false, 'error' => 'exec函数不可用', 'usetime' => 0];
if (!function_exists('exec')) return ['status' => false, 'errmsg' => 'exec函数不可用', 'usetime' => 0];
if (substr($target, -1) == '.') $target = substr($target, 0, -1);
if (!filter_var($target, FILTER_VALIDATE_IP) && checkDomain($target)) {
$target = gethostbyname($target);
if (!$target) return ['status' => false, 'error' => 'DNS resolve failed', 'usetime' => 0];
if (!$target) return ['status' => false, 'errmsg' => 'DNS resolve failed', 'usetime' => 0];
}
if (!filter_var($target, FILTER_VALIDATE_IP)) {
return ['status' => false, 'error' => 'Invalid IP address', 'usetime' => 0];
return ['status' => false, 'errmsg' => 'Invalid IP address', 'usetime' => 0];
}
$timeout = 1;
exec('ping -c 1 -w '.$timeout.' '.$target.'', $output, $return_var);

View File

@@ -13,7 +13,7 @@ class MsgNotice
{
if ($action == 1) {
$mail_title = 'DNS容灾切换-发生告警通知';
$mail_content = '尊敬的系统管理员,您好:<br/>您的域名 <b>'.$task['domain'].'</b> 的 <b>'.$task['main_value'].'</b> 记录发生了异常';
$mail_content = '尊敬的用户,您好:<br/>您的域名 <b>'.$task['domain'].'</b> 的 <b>'.$task['main_value'].'</b> 记录发生了异常';
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 .= '。<br/>异常信息:'.$result['errmsg'];
$mail_content .= '。<br/>异常信息:<font color="warning">'.$result['errmsg'].'</font>';
}
} else {
$mail_title = 'DNS容灾切换-恢复正常通知';
$mail_content = '尊敬的系统管理员,您好:<br/>您的域名 <b>'.$task['domain'].'</b> 的 <b>'.$task['main_value'].'</b> 记录已恢复正常';
$mail_content = '尊敬的用户,您好:<br/>您的域名 <b>'.$task['domain'].'</b> 的 <b>'.$task['main_value'].'</b> 记录已恢复正常';
if ($task['type'] == 2) {
$mail_content .= ',已自动切换回当前解析记录';
} elseif ($task['type'] == 1) {
@@ -41,7 +41,7 @@ class MsgNotice
if (!empty($task['remark'])) {
$mail_content .= '<br/>备注:'.$task['remark'];
}
$mail_content .= '<br/>'.self::$sitename.'<br/>'.date('Y-m-d H:i:s');
$mail_content .= '<br/><font color="grey">'.self::$sitename.'</font><br/><font color="grey">'.date('Y-m-d H:i:s').'</font>';
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(['<br/>', '<b>', '</b>'], ["\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('<br/>', "\n", $mail_content);
$content = "<strong>".$mail_title."</strong>\n".$content;
$content = "<strong>".$mail_title."</strong>\n".strip_tags($content);
self::send_telegram_bot($content);
}
if (config_get('notice_webhook') == 1) {
$content = str_replace(['<br/>', '<b>', '</b>'], ["\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证书已签发成功<br/>证书账户:'.CertHelper::$cert_config[$type]['name'].'('.$row['aid'].')<br/>证书域名:'.implode('、', $domainList).'<br/>签发时间:'.$row['issuetime'].'<br/>到期时间:'.$row['expiretime'].'<br/>颁发机构:'.$row['issuer'];
$mail_content = '尊敬的用户您好您的SSL证书已签发成功<br/><b>证书账户:</b> '.CertHelper::$cert_config[$type]['name'].'('.$row['aid'].')<br/><b>证书域名:</b> '.implode('、', $domainList).'<br/><b>签发时间:</b> '.$row['issuetime'].'<br/><b>到期时间:</b> '.$row['expiretime'].'<br/><b>颁发机构:</b> '.$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']].'<br/>证书账户:'.CertHelper::$cert_config[$type]['name'].'('.$row['aid'].')<br/>证书域名:'.implode('、', $domainList).'<br/>失败时间:'.date('Y-m-d H:i:s').'<br/>失败原因:'.$row['error'];
$mail_content = '尊敬的用户您好您的SSL证书'.$status_arr[$row['status']].'<br/><b>证书账户:</b> '.CertHelper::$cert_config[$type]['name'].'('.$row['aid'].')<br/><b>证书域名:</b> '.implode('、', $domainList).'<br/><b>失败时间:</b> '.date('Y-m-d H:i:s').'<br/><b>失败原因:</b> <font color="warning">'.$row['error'].'</font>';
}
$mail_content .= '<br/>'.self::$sitename.'<br/>'.date('Y-m-d H:i:s');
$mail_content .= '<br/><font color="grey">'.self::$sitename.'</font><br/><font color="grey">'.date('Y-m-d H:i:s').'</font>';
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(['<br/>', '<b>', '</b>'], ["\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('<br/>', "\n", $mail_content);
$content = "<strong>" . $mail_title . "</strong>\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.'<br/>自动部署账户:['.$account['id'].']'.$typename.'('.($account['remark']?$account['remark']:$account['name']).')<br/>关联SSL证书['.$row['oid'].']'.implode('、', $domainList).'<br/>任务备注:'.($row['remark']?$row['remark']:'无');
$mail_content = '尊敬的用户您好您的SSL证书已成功部署到'.$typename.'<br/><b>自动部署账户:</b> ['.$account['id'].']'.$typename.'('.($account['remark']?$account['remark']:$account['name']).')<br/><b>关联SSL证书</b> ['.$row['oid'].']'.implode('、', $domainList).'<br/><b>任务备注:</b> '.($row['remark']?$row['remark']:'无');
} else {
$mail_content = '尊敬的用户您好您的SSL证书部署失败<br/>失败原因:'.$row['error'].'<br/>自动部署账户:['.$account['id'].']'.$typename.'('.($account['remark']?$account['remark']:$account['name']).')<br/>关联SSL证书['.$row['oid'].']'.implode('、', $domainList).'<br/>任务备注:'.($row['remark']?$row['remark']:'无');
$mail_content = '尊敬的用户您好您的SSL证书部署失败<br/><b>失败原因:</b> <font color="warning">'.$row['error'].'</font><br/><b>自动部署账户:</b> ['.$account['id'].']'.$typename.'('.($account['remark']?$account['remark']:$account['name']).')<br/><b>关联SSL证书</b> ['.$row['oid'].']'.implode('、', $domainList).'<br/><b>任务备注:</b> '.($row['remark']?$row['remark']:'无');
}
$mail_content .= '<br/>'.self::$sitename.'<br/>'.date('Y-m-d H:i:s');
$mail_content .= '<br/><font color="grey">'.self::$sitename.'</font><br/><font color="grey">'.date('Y-m-d H:i:s').'</font>';
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(['<br/>', '<b>', '</b>'], ["\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('<br/>', "\n", $mail_content);
$content = "<strong>" . $mail_title . "</strong>\n" . $content;
$content = "<strong>".$mail_title."</strong>\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(['*', '<br/>', '<b>', '</b>'], ['\*', "\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') || strpos($url, 'open.larksuite.com')) {
$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'] : '请求失败');
}
}
@@ -211,28 +253,10 @@ class MsgNotice
{
$ch = curl_init();
if (config_get('tgbot_proxy') == 1) {
$proxy_server = config_get('proxy_server');
$proxy_port = intval(config_get('proxy_port'));
$proxy_userpwd = config_get('proxy_user').':'.config_get('proxy_pwd');
$proxy_type = config_get('proxy_type');
if ($proxy_type == 'https') {
$proxy_type = CURLPROXY_HTTPS;
} elseif ($proxy_type == 'sock4') {
$proxy_type = CURLPROXY_SOCKS4;
} elseif ($proxy_type == 'sock5') {
$proxy_type = CURLPROXY_SOCKS5;
} else {
$proxy_type = CURLPROXY_HTTP;
}
curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_PROXY, $proxy_server);
curl_setopt($ch, CURLOPT_PROXYPORT, $proxy_port);
if ($proxy_userpwd != ':') {
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy_userpwd);
}
curl_setopt($ch, CURLOPT_PROXYTYPE, $proxy_type);
curl_set_proxy($ch);
}
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: */*";

View File

@@ -150,7 +150,6 @@ new Vue({
})
return {label: classList[key], children: tempList}
})
console.log(this.typeOption);
if(this.action == 'edit'){
Object.keys(info).forEach((key) => {
this.set[key] = info[key]
@@ -159,7 +158,9 @@ new Vue({
this.inputs = typeList[this.set.type].inputs;
this.note = typeList[this.set.type].note;
$.each(this.inputs, (name, item) => {
item.value = config[name];
if(typeof config[name] != 'undefined'){
item.value = config[name];
}
if(typeof item.value == 'undefined'){
if(item.type == 'checkbox'){
item.value = false;

View File

@@ -29,7 +29,16 @@ pre.pre-log{height: 330px;overflow-y: auto;width: 100%;background-color: rgba(51
</div>
<button type="submit" class="btn btn-primary"><i class="fa fa-search"></i> 搜索</button>
<a href="javascript:searchClear()" class="btn btn-default" title="刷新订单列表"><i class="fa fa-refresh"></i> 刷新</a>
<a href="/cert/order/add" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>
<div class="btn-group">
<a href="/cert/order/add" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="/cert/order/import">导入已有证书</a></li>
</ul>
</div>
</form>
<table id="listTable">
@@ -130,6 +139,16 @@ $(document).ready(function(){
} else if(value == 2) {
return '<span class="label" style="background-color: #3e76fb;">正在验证</span>';
} else if(value == 1) {
if(row.retrytime != null){
var now = new Date().getTime();
var retry = new Date(row.retrytime).getTime();
var diff = retry - now;
if(diff > 0){
var min = Math.floor(diff / 60000);
var sec = Math.floor((diff - min * 60000) / 1000);
return '<span title="'+min+'分'+sec+'秒后自动验证" data-toggle="tooltip" data-placement="top" class="label" style="background-color: #3e76fb;">待验证</span>';
}
}
return '<span class="label" style="background-color: #3e76fb;">待验证</span>';
} else if(value == 0) {
return '<span class="label label-info">待提交</span>';
@@ -142,6 +161,16 @@ $(document).ready(function(){
else if(value == -5) title = '验证订单失败';
else if(value == -6) title = '订单验证未通过';
else if(value == -7) title = '签发证书失败';
if(row.retrytime != null){
var now = new Date().getTime();
var retry = new Date(row.retrytime).getTime();
var diff = retry - now;
if(diff > 0){
var min = Math.floor(diff / 60000);
var sec = Math.floor((diff - min * 60000) / 1000);
return '<span title="'+min+'分'+sec+'秒后自动重试" data-toggle="tooltip" data-placement="top" class="label label-danger">'+title+'</span>'+(row.error?' <span onclick="showmsg(\''+row.error+'\')" class="tips" title="失败原因"><i class="fa fa-info-circle"></i></span>':'');
}
}
return '<span class="label label-danger">'+title+'</span>'+(row.error?' <span onclick="showmsg(\''+row.error+'\')" class="tips" title="失败原因"><i class="fa fa-info-circle"></i></span>':'');
}
}

View File

@@ -64,6 +64,10 @@
<label class="col-sm-3 control-label">Telegram机器人通知</label>
<div class="col-sm-9"><select class="form-control" name="cert_notice_tgbot" default="{:config_get('cert_notice_tgbot')}"><option value="0">关闭</option><option value="1">开启</option><option value="2">开启(仅失败时)</option></select></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">群机器人Webhook</label>
<div class="col-sm-9"><select class="form-control" name="cert_notice_webhook" default="{:config_get('cert_notice_webhook')}"><option value="0">关闭</option><option value="1">开启</option><option value="2">开启(仅失败时)</option></select></div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9"><input type="submit" name="submit" value="保存" class="btn btn-primary btn-block"/></div>
</div>

View File

@@ -4,6 +4,7 @@
<style>
.copy-btn{color:#52c41a;cursor:pointer;margin-right: 5px;}
.copy-btn:hover{color:#85ef79;}
.btn-refresh{margin-left:5px;font-size:10px;background-color:#6896cf}
tbody tr>td:nth-child(3){word-break:break-all;max-width:180px;}
tbody tr>td:nth-child(4){word-break:break-all;max-width:260px;}
</style>
@@ -120,7 +121,7 @@ $(document).ready(function(){
} else {
html += '<span class="label label-warning">未验证</span>';
}
html += '&nbsp;<a href="javascript:checkItem('+row.id+')" title="立即验证" class="btn btn-primary btn-xs"><i class="fa fa-refresh"></i></a>';
html += '<a href="javascript:checkItem('+row.id+')" title="立即验证" class="btn btn-primary btn-xs btn-refresh"><i class="fa fa-refresh"></i></a>';
return html;
}
},

View File

@@ -160,7 +160,9 @@ new Vue({
this.inputs = typeList[this.set.type].taskinputs;
this.note = typeList[this.set.type].tasknote;
$.each(this.inputs, (name, item) => {
item.value = config[name];
if(typeof config[name] != 'undefined'){
item.value = config[name];
}
if(typeof item.value == 'undefined'){
if(item.type == 'checkbox'){
item.value = false;
@@ -183,7 +185,7 @@ new Vue({
if(document.referrer.indexOf('&oid=') > 0){
var oid = document.referrer.split('&oid=')[1].split('&')[0];
if(oid){
$('select[name=oid]').val(20).trigger('change');
$('select[name=oid]').val(oid).trigger('change');
that.set.oid = oid;
}
}

View File

@@ -0,0 +1,167 @@
{extend name="common/layout" /}
{block name="title"}导入已有证书{/block}
{block name="main"}
<style>
.tips{color: #f6a838; padding-left: 5px;}
.control-label[is-required]:before {
content: "*";
color: #f56c6c;
margin-right: 4px;
}
.input-group-addon{padding: 6px 6px;}
</style>
<div class="row" id="app">
<div class="col-xs-12 center-block" style="float: none;">
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title"><a href="/cert/certorder" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回</a>导入已有证书</h3></div>
<div class="panel-body">
<form onsubmit="return false" method="post" class="form-horizontal" role="form" id="taskform">
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" is-required>证书内容</label>
<div class="col-sm-6">
<div class="input-group">
<textarea name="fullchain" v-model="set.fullchain" class="form-control" rows="5" placeholder="输入PEM格式证书链" required></textarea>
<a class="btn btn-default input-group-addon" @click="upload('fullchain')" title="上传证书文件"><i class="fa fa-upload"></i></a>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" is-required>私钥内容</label>
<div class="col-sm-6">
<div class="input-group">
<textarea name="privatekey" v-model="set.privatekey" class="form-control" rows="5" placeholder="输入PEM格式私钥" required></textarea>
<a class="btn btn-default input-group-addon" @click="upload('privatekey')" title="上传私钥文件"><i class="fa fa-upload"></i></a>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 col-xs-12 control-label no-padding-right" is-required>证书续期账户</label>
<div class="col-sm-6"><select name="aid" v-model="set.aid" class="form-control" required>
<option value="">--选择证书账户--</option>
{foreach $accounts as $k=>$v}
<option value="{$k}" data-type="{$v.type}">{$v.name}</option>
{/foreach}
</select></div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" is-required>签名算法</label>
<div class="col-sm-6">
<label class="radio-inline" v-for="item in keytypeList">
<input type="radio" name="keytype" :value="item" v-model="set.keytype"> {{item}}
</label>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" is-required>密钥长度</label>
<div class="col-sm-6">
<label class="radio-inline" v-for="item in keysizeList">
<input type="radio" name="keysize" :value="item.value" v-model="set.keysize"> {{item.label}}
</label>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-6"><button type="button" class="btn btn-primary" @click="submit">提交</button></div>
</div>
</form>
</div>
</div>
{/block}
{block name="script"}
<script src="{$cdnpublic}vue/2.6.14/vue.min.js"></script>
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
<script src="/static/js/bootstrapValidator.min.js"></script>
<script>
new Vue({
el: '#app',
data: {
type: '',
set: {
fullchain: '',
privatekey: '',
aid: '',
keytype: '',
keysize: '',
},
keytypeList: [
'RSA',
'ECC'
],
keysizeMap: [
{label:'2048 bit',value:'2048',type:'RSA'},
{label:'3072 bit',value:'3072',type:'RSA'},
{label:'P-256',value:'256',type:'ECC'},
{label:'P-384',value:'384',type:'ECC'},
],
keysizeList: [],
},
watch: {
'set.aid': function(val){
this.type = $('option:selected', 'select[name=aid]').data('type');
},
'set.keytype': function(val){
this.keysizeList = this.keysizeMap.filter((item) => {
return item.type == val;
})
if(!this.keysizeList.filter((item) => {return item.value == this.set.keysize}).length)
this.set.keysize = this.keysizeList[0].value;
},
},
mounted() {
this.set.keytype = 'RSA';
$("#taskform").bootstrapValidator({
live: 'submitted',
});
$('[data-toggle="tooltip"]').tooltip();
},
methods: {
submit(){
var that=this;
$("#taskform").data("bootstrapValidator").validate();
if(!$("#taskform").data("bootstrapValidator").isValid()){
return false;
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type: "POST",
url: "",
data: this.set,
dataType: 'json',
success: function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert(data.msg, {icon: 1}, function(){
if(document.referrer.indexOf('/cert/certorder?') > 0)
window.location.href = document.referrer;
else
window.location.href = '/cert/certorder';
});
}else{
layer.alert(data.msg, {icon: 2});
}
},
error: function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
},
upload(name){
//读取上传文件并填充到表单
var file = document.createElement('input');
file.type = 'file';
file.accept = '.pem,.crt,.key';
file.style.display = 'none';
file.onchange = function(){
var reader = new FileReader();
reader.onload = function(e){
this.set[name] = e.target.result;
}.bind(this);
reader.readAsText(file.files[0]);
}.bind(this);
document.body.appendChild(file);
file.click();
}
},
});
</script>
{/block}

View File

@@ -103,7 +103,7 @@
{if request()->user['type'] eq 'user'}<li class="{:checkIfActive('index')}">
<a href="/"><i class="fa fa-home fa-fw"></i> <span>后台首页</span></a>
</li>{/if}
<li class="{:checkIfActive('domain,record,record_log,record_batch_add')}">
<li class="{:checkIfActive('domain,record,record_log,record_batch_add,domain_add')}">
<a href="/domain"><i class="fa fa-list-ul fa-fw"></i> <span>域名管理</span></a>
</li>
{if request()->user['level'] eq 2}
@@ -136,7 +136,7 @@
<li><a href="/optimizeip/opiplist"><i class="fa fa-circle-o"></i> 任务管理</a></li>
</ul>
</li>
<li class="treeview {:checkIfActive('certaccount,account_form,certorder,order_form,deployaccount,deploytask,deploy_form,certset,cname')}">
<li class="treeview {:checkIfActive('certaccount,account_form,certorder,order_form,order_import,deployaccount,deploytask,deploy_form,certset,cname')}">
<a href="javascript:;">
<i class="fa fa-expeditedssl fa-fw"></i>
<span>SSL证书</span>

View File

@@ -56,6 +56,10 @@
<label class="col-sm-4 control-label">Telegram机器人通知</label>
<div class="col-sm-8"><select class="form-control" name="notice_tgbot" default="{:config_get('notice_tgbot')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">群机器人Webhook</label>
<div class="col-sm-8"><select class="form-control" name="notice_webhook" default="{:config_get('notice_webhook')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
</div>
</form>
</div>
<div class="modal-footer">

View File

@@ -36,12 +36,20 @@
<input type="text" class="form-control" name="sk" required>
</div>
</div>
<div class="form-group">
<div class="form-group" id="ext_name_div" style="display:none;">
<label class="col-sm-3 control-label no-padding-right" id="ext_name">扩展字段</label>
<div class="col-sm-9">
<input type="text" class="form-control" name="ext" placeholder="没有请勿填写">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right" id="ext_name">使用代理</label>
<div class="col-sm-9">
<label class="radio-inline"><input type="radio" name="proxy" value="0">
</label><label class="radio-inline"><input type="radio" name="proxy" value="1">
</label>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">备注</label>
<div class="col-sm-9">
@@ -137,8 +145,12 @@ $(document).ready(function(){
if(dnsconfig[type] == undefined) return;
$("#ak_name").html(dnsconfig[type].config.ak);
$("#sk_name").html(dnsconfig[type].config.sk);
if(dnsconfig[type].config.ext == undefined) dnsconfig[type].config.ext = '扩展字段';
else $("#ext_name").html(dnsconfig[type].config.ext);
if(dnsconfig[type].config.ext == undefined){
$("#ext_name_div").hide();
}else{
$("#ext_name_div").show();
$("#ext_name").html(dnsconfig[type].config.ext);
}
});
})
function addframe(){
@@ -149,6 +161,7 @@ function addframe(){
$("#form-store input[name=ak]").val('');
$("#form-store input[name=sk]").val('');
$("#form-store input[name=ext]").val('');
$("#form-store input[name=proxy]").eq(0).prop('checked',true);
$("#form-store input[name=remark]").val('');
$("select[name=type]").change();
}
@@ -170,6 +183,7 @@ function editframe(id){
$("#form-store input[name=ak]").val(data.data.ak);
$("#form-store input[name=sk]").val(data.data.sk);
$("#form-store input[name=ext]").val(data.data.ext);
$("#form-store input[name=proxy]").eq(data.data.proxy).prop('checked',true);
$("#form-store input[name=remark]").val(data.data.remark);
$("select[name=type]").change();
}else{

View File

@@ -12,7 +12,6 @@
</div>
<div class="modal-body">
<form class="form-horizontal" id="form-store">
<input type="hidden" name="recordcount"/>
<div class="form-group">
<label class="col-sm-3 control-label">域名账户</label>
<div class="col-sm-9">
@@ -27,8 +26,10 @@
<label class="col-sm-3 control-label">选择域名</label>
<div class="col-sm-9">
<select name="domain" id="domainList" class="form-control"></select>
<span class="pull-right"><a href="/domain/add">批量添加</a></span>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@@ -200,11 +201,6 @@ $(document).ready(function(){
getDomainList()
})
$('#domainList').on('select2:select', function (e) {
var data = e.params.data;
$("#form-store input[name=recordcount]").val(data.recordcount);
});
$('[data-toggle="popover"]').popover()
})
function addframe(){
@@ -222,7 +218,7 @@ function saveAdd(){
}
var name = select[0].text;
var thirdid = select[0].id;
var recordcount = $("#form-store input[name=recordcount]").val();
var recordcount = select[0].recordcount;
if(aid=='' || thirdid==''){
layer.alert('请确保各项不能为空!');return false;
}
@@ -342,9 +338,9 @@ function getDomainList(){
params.page = params.page || 1
if(data.code == 0){
var resultData = [];
for (var i = 0; i < data.data.list.length; i++) {
resultData.push({'id': data.data.list[i].DomainId, 'text': data.data.list[i].Domain, 'recordcount': data.data.list[i].RecordCount});
}
$.each(data.data.list, function(index, item){
resultData.push({'id': item.DomainId, 'text': item.Domain + (item.disabled ? ' (已添加)' : ''), 'recordcount': item.RecordCount, disabled:item.disabled});
})
return {
results: resultData,
pagination: {

View File

@@ -0,0 +1,158 @@
{extend name="common/layout" /}
{block name="title"}批量添加域名{/block}
{block name="main"}
<div class="row" id="app">
<div class="col-xs-12 col-sm-10 col-md-8 col-lg-6 center-block" style="float: none;">
<div class="panel panel-default">
<div class="panel-heading"><h3 class="panel-title"><a href="/domain" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回</a>批量添加域名</h3></div>
<div class="panel-body">
<form onsubmit="return false" method="post" class="form-horizontal" role="form" id="form-store">
<div class="form-group">
<label class="col-sm-3 control-label">域名账户</label>
<div class="col-sm-9">
<div class="input-group">
<select name="aid" class="form-control" v-model="aid">
{foreach $accounts as $k=>$v}
<option value="{$k}">{$v}</option>
{/foreach}
</select>
<div class="input-group-btn">
<button type="button" @click="getDomainList" class="btn btn-info">获取域名</button>
</div>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">选择域名</label>
<div class="col-sm-9">
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th><input type="checkbox" v-model="checkall"></th>
<th>域名</th>
</tr>
</thead>
<tbody>
<tr v-for="item in domainList">
<td><input type="checkbox" name="domain[]" :value="item.DomainId" v-model="item.checked" :disabled="item.disabled"></td>
<td><span :title="item.DomainId">{{item.Domain}}</span><font color="#888" v-if="item.disabled"> (已添加)</font></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9"><button type="button" class="btn btn-primary" @click="submit">确定添加</button></div>
</div>
</form>
</div>
</div>
{/block}
{block name="script"}
<script src="{$cdnpublic}vue/2.6.14/vue.min.js"></script>
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
<script>
new Vue({
el: '#app',
data: {
aid: '',
domainList: [],
page: 1,
pagesize: 10,
checkall: false,
},
watch: {
aid: function(val){
this.domainList = [];
},
checkall: function(val){
this.domainList.forEach(function(item){
item.checked = val&&!item.disabled;
});
}
},
mounted() {
this.aid = '{$accounts|@key}';
},
methods: {
async getDomainListPaged(){
var that = this;
return new Promise((resolve, reject) => {
$.ajax({
type: "POST",
url: "/domain/list",
data: {aid: that.aid, page: that.page, pagesize: that.pagesize},
dataType: 'json',
success: function(data) {
if(data.code == 0){
resolve(data.data);
}else{
reject(data.msg);
}
}
});
});
},
async getDomainList(){
this.domainList = [];
while(true){
try{
layer.msg('正在获取第'+this.page+'页域名', {icon: 16, shade: 0.01});
var data = await this.getDomainListPaged();
if(data.total == 0 || data.list.length == 0){
layer.closeAll();
break;
}
this.domainList = this.domainList.concat(data.list);
if(this.domainList.length >= data.total){
layer.closeAll();
break;
}
this.page++;
}catch(e){
layer.alert(e, {icon: 2});
break;
}
}
},
submit(){
var domains = [];
this.domainList.forEach(function(item){
if(item.checked && !item.disabled){
domains.push({name: item.Domain, id: item.DomainId, recordcount:item.RecordCount});
}
});
if(this.aid == ''){
layer.alert('请选择域名账户');
return;
}
if(domains.length == 0){
layer.alert('请选择要添加的域名');
return;
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type: "POST",
url: "/domain/op/act/batchadd",
data: {aid: this.aid, domains: domains},
dataType: 'json',
success: function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert(data.msg, {icon: 1}, function(){
window.location.href = '/domain';
});
}else{
layer.alert(data.msg, {icon: 2});
}
},
error: function(data){
layer.close(ii);
layer.msg('服务器错误');
}
});
}
},
});
</script>
{/block}

View File

@@ -109,6 +109,26 @@
<a href="https://t.me/BotFather" target="_blank" rel="noopener noreferrer">@BotFather</a>对话,使用/newbot命令创建一个新的机器人根据提示输入机器人的名称和用户名可得到Token或使用/mybots命令查看已创建的机器人<a href="https://t.me/getmyid_bot" target="_blank" rel="noopener noreferrer">@getmyid_bot</a>对话可得到Chat Id<br/>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">群机器人Webhook</h3></div>
<div class="panel-body">
<form onsubmit="return saveSetting(this)" method="post" class="form-horizontal" role="form">
<div class="form-group">
<label class="col-sm-3 control-label">Webhook地址</label>
<div class="col-sm-9"><input type="text" name="webhook_url" value="{:config_get('webhook_url')}" class="form-control"/></div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<input type="submit" name="submit" value="保存" class="btn btn-primary btn-block"/>
<a href="javascript:webhooktest()" class="btn btn-default btn-block">发送测试消息</a>
</div>
</div>
</form>
</div>
<div class="panel-footer">
仅支持填写企业微信、钉钉、飞书群机器人的Webhook地址
</div>
</div>
</div>
</div>
{/block}
@@ -139,7 +159,7 @@ function saveSetting(obj){
success : function(data) {
layer.close(ii);
if(data.code == 0){
layer.alert('设置保存成功!<br/>重启检测进程或容器后生效', {
layer.alert('设置保存成功!<br/>如有使用容灾切换,重启检测进程后生效', {
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('服务器错误');
}
});
}
</script>
{/block}

View File

@@ -1,5 +1,5 @@
{extend name="common/layout" /}
{block name="title"}容灾切换代理设置{/block}
{block name="title"}代理设置{/block}
{block name="main"}
<div class="row">
<div class="col-xs-12 col-sm-8 col-lg-6 center-block" style="float: none;">

View File

@@ -31,7 +31,7 @@ return [
'show_error_msg' => true,
'exception_tmpl' => \think\facade\App::getAppPath() . 'view/exception.tpl',
'version' => '1021',
'version' => '1027',
'dbversion' => '1021'
'dbversion' => '1023'
];

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -52,6 +52,7 @@ Route::group(function () {
Route::post('/domain/data', 'domain/domain_data');
Route::post('/domain/op', 'domain/domain_op');
Route::post('/domain/list', 'domain/domain_list');
Route::get('/domain/add', 'domain/domain_add');
Route::get('/domain', 'domain/domain');
Route::post('/record/data/:id', 'domain/record_data');
@@ -73,6 +74,7 @@ Route::group(function () {
Route::get('/dmonitor/task/info/:id', 'dmonitor/taskinfo');
Route::any('/dmonitor/task/:action', 'dmonitor/taskform');
Route::get('/dmonitor/task', 'dmonitor/task');
Route::post('/dmonitor/noticeset', 'dmonitor/noticeset');
Route::post('/dmonitor/clean', 'dmonitor/clean');
Route::any('/optimizeip/opipset', 'optimizeip/opipset');
@@ -91,6 +93,7 @@ Route::group(function () {
Route::post('/cert/order/data', 'cert/order_data');
Route::post('/cert/order/process', 'cert/order_process');
Route::post('/cert/order/:action', 'cert/order_op');
Route::get('/cert/order/import', 'cert/order_import');
Route::get('/cert/order/:action', 'cert/order_form');
Route::get('/cert/deploytask', 'cert/deploytask');
@@ -109,6 +112,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)