mirror of
https://github.com/netcccyun/dnsmgr.git
synced 2026-05-10 15:36:26 +02:00
Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55272fd51b | ||
|
|
a1e4476603 | ||
|
|
3734e98048 | ||
|
|
372018c03a | ||
|
|
9b037834ad | ||
|
|
bf05d51d08 | ||
|
|
ec89fd685b | ||
|
|
300686aa0a | ||
|
|
8a158ea0a5 | ||
|
|
8a41c1642a | ||
|
|
6e3350afbd | ||
|
|
b5f74368d2 | ||
|
|
49047db438 | ||
|
|
7c54d8af44 | ||
|
|
9b7a7c2d60 | ||
|
|
8e7adead48 | ||
|
|
1e747a8e9e | ||
|
|
a13fb38e66 | ||
|
|
3c6944a701 | ||
|
|
c141089c69 | ||
|
|
994bdc7fa3 | ||
|
|
842b2aa2d9 | ||
|
|
4850250f3c | ||
|
|
f8add88e3d | ||
|
|
dcc440c1f9 | ||
|
|
a4d3cdd612 | ||
|
|
a483476e6b | ||
|
|
be55d4b67d | ||
|
|
fa47ffb080 | ||
|
|
333aacaab9 | ||
|
|
0752f07f7d | ||
|
|
4310ccb770 | ||
|
|
fb8fe3526b | ||
|
|
cf36b4bd51 | ||
|
|
079a142b40 | ||
|
|
76e9adb405 | ||
|
|
1723761b94 | ||
|
|
9fb3764878 | ||
|
|
651132967f | ||
|
|
81a85fce45 | ||
|
|
4c62084bc6 | ||
|
|
33ba9a6ebd | ||
|
|
64d0585788 |
111
README.md
111
README.md
@@ -1,50 +1,31 @@
|
||||
## 聚合DNS管理系统
|
||||
# 彩虹聚合DNS管理系统
|
||||
|
||||
聚合DNS管理系统可以实现在一个网站内管理多个平台的域名解析,目前已支持的域名平台有:阿里云、腾讯云、华为云、百度云、西部数码、火山引擎、DNSLA、CloudFlare、Namesilo
|
||||
<div align="center">
|
||||
|
||||
### 功能特性
|
||||
[](https://github.com/netcccyun/dnsmgr/stargazers)
|
||||
[](https://github.com/netcccyun/dnsmgr/forks)
|
||||
[](https://hub.docker.com/r/netcccyun/dnsmgr)
|
||||
[](https://github.com/netcccyun/dnsmgr/releases)
|
||||
[](https://github.com/netcccyun/dnsmgr/commits/main)
|
||||
|
||||
- 多用户管理,可为每个用户可分配不同的域名解析权限
|
||||
- 提供API接口,可获取域名单独的登录链接,方便各种IDC系统对接
|
||||
- 容灾切换功能,支持ping、tcp、http(s)检测协议并自动暂停/修改域名解析,并支持邮件、微信公众号、TG群机器人通知
|
||||
- CF优选IP功能,支持获取最新的Cloudflare优选IP,并自动更新到解析记录
|
||||
- SSL证书申请与自动部署功能,支持从Let's Encrypt等渠道申请SSL证书,并自动部署到各种面板、云服务商、服务器等
|
||||
</div>
|
||||
|
||||
### 演示截图
|
||||
彩虹聚合DNS管理系统 是一款基于ThinkPHP开发的网站程序,可实现在单一网站内管理多个平台的域名解析,目前已支持的域名解析平台有:阿里云、腾讯云、华为云、百度云、西部数码、火山引擎、DNSLA、CloudFlare、Namesilo、PowerDNS
|
||||
|
||||
添加域名账户
|
||||
## 功能特性
|
||||
|
||||

|
||||
- 多用户管理,可为每个用户可分配不同的域名解析权限;
|
||||
- 提供API接口,可获取域名单独的登录链接,方便各种IDC系统对接;
|
||||
- 容灾切换功能,支持ping、tcp、http(s)检测协议并自动暂停/修改域名解析,并支持发送通知;
|
||||
- CF优选IP功能,支持获取最新的Cloudflare优选IP,并自动更新到解析记录;
|
||||
- SSL证书申请与自动部署功能,支持从Let's Encrypt等渠道申请SSL证书,并自动部署到各种面板、云服务商、服务器等;
|
||||
- 支持邮件、微信公众号、Telegram、钉钉、飞书、企业微信等多种通知渠道。
|
||||
|
||||
域名管理列表
|
||||
## 部署方式
|
||||
|
||||

|
||||
### 自部署
|
||||
|
||||
域名DNS解析管理,支持解析批量操作
|
||||
|
||||

|
||||
|
||||
用户管理添加用户,支持为用户开启API接口
|
||||
|
||||

|
||||
|
||||
CF优选IP功能,添加优选IP任务
|
||||
|
||||

|
||||
|
||||
SSL证书申请功能
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
SSL证书自动部署功能
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 部署方法
|
||||
可以使用宝塔、Kangle等任意支持PHP-MySQL的环境部署
|
||||
|
||||
* 从[Release](https://github.com/netcccyun/dnsmgr/releases)页面下载安装包
|
||||
|
||||
@@ -64,6 +45,8 @@ SSL证书自动部署功能
|
||||
|
||||
* 访问首页登录控制面板
|
||||
|
||||
* 后续更新方式:重新下载安装包上传覆盖即可
|
||||
|
||||
##### 伪静态规则
|
||||
|
||||
* Nginx
|
||||
@@ -89,7 +72,7 @@ location / {
|
||||
</IfModule>
|
||||
```
|
||||
|
||||
### Docker部署方法
|
||||
### Docker 部署
|
||||
|
||||
首先需要安装Docker,然后执行以下命令拉取镜像并启动(启动后监听8081端口):
|
||||
|
||||
@@ -103,7 +86,13 @@ docker run --name dnsmgr -dit -p 8081:80 -v /var/dnsmgr:/app/www netcccyun/dnsmg
|
||||
docker restart dnsmgr
|
||||
```
|
||||
|
||||
### docker-compose部署方法
|
||||
从国内镜像地址拉取:
|
||||
|
||||
```
|
||||
docker pull swr.cn-east-3.myhuaweicloud.com/netcccyun/dnsmgr:latest
|
||||
```
|
||||
|
||||
### docker-compose 部署
|
||||
|
||||
```
|
||||
version: '3'
|
||||
@@ -144,6 +133,7 @@ networks:
|
||||
```
|
||||
|
||||
在运行之前请创建好目录
|
||||
|
||||
```
|
||||
mkdir -p ./web
|
||||
mkdir -p ./mysql/conf
|
||||
@@ -156,6 +146,7 @@ sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_
|
||||
```
|
||||
|
||||
登陆mysql容器创建数据库
|
||||
|
||||
```
|
||||
docker exec -it dnsmgr-mysql /bin/bash
|
||||
mysql -uroot -p123456
|
||||
@@ -164,9 +155,45 @@ create database dnsmgr;
|
||||
|
||||
在install界面链接IP填写dnsmgr-mysql
|
||||
|
||||
### 作者信息
|
||||
## 演示截图
|
||||
|
||||
消失的彩虹海(https://blog.cccyun.cn)
|
||||
添加域名账户
|
||||
|
||||

|
||||
|
||||
域名管理列表
|
||||
|
||||

|
||||
|
||||
域名DNS解析管理,支持解析批量操作
|
||||
|
||||

|
||||
|
||||
用户管理添加用户,支持为用户开启API接口
|
||||
|
||||

|
||||
|
||||
CF优选IP功能,添加优选IP任务
|
||||
|
||||

|
||||
|
||||
SSL证书申请功能
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
SSL证书自动部署功能
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 支持与反馈
|
||||
|
||||
🌐 作者信息:消失的彩虹海(https://blog.cccyun.cn)
|
||||
|
||||
⭐ 如果您觉得本项目对您有帮助,欢迎给项目点个 Star
|
||||
|
||||
### 其他推荐
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
// 应用公共文件
|
||||
use think\facade\Db;
|
||||
use think\facade\Config;
|
||||
use think\facade\Request;
|
||||
|
||||
function get_curl($url, $post = 0, $referer = 0, $cookie = 0, $header = 0, $ua = 0, $nobody = 0, $addheader = 0)
|
||||
@@ -294,6 +295,16 @@ function convert_second($s)
|
||||
function getMainDomain($host)
|
||||
{
|
||||
if (filter_var($host, FILTER_VALIDATE_IP)) return $host;
|
||||
$domains = config('temp.domains');
|
||||
if (!$domains) {
|
||||
$domains = Db::name('domain')->column('name');
|
||||
config(['domains'=>$domains], 'temp');
|
||||
}
|
||||
foreach ($domains as $domain) {
|
||||
if (str_ends_with($host, $domain)) {
|
||||
return $domain;
|
||||
}
|
||||
}
|
||||
$domain_root = file_get_contents(app()->getBasePath() . 'data' . DIRECTORY_SEPARATOR . 'domain_root.txt');
|
||||
$domain_root = explode("\r\n", $domain_root);
|
||||
$data = explode('.', $host);
|
||||
@@ -381,7 +392,7 @@ function clearDirectory($dir): bool
|
||||
return true;
|
||||
}
|
||||
|
||||
function curl_client($url, $data = null, $referer = null, $cookie = null, $headers = null, $proxy = false, $method = null, $timeout = 5)
|
||||
function curl_client($url, $data = null, $referer = null, $cookie = null, $headers = null, $proxy = false, $method = null, $timeout = 5, $default_headers = true)
|
||||
{
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
@@ -389,11 +400,15 @@ function curl_client($url, $data = null, $referer = null, $cookie = null, $heade
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
$httpheader[] = "Accept: */*";
|
||||
$httpheader[] = "Accept-Language: zh-CN,zh;q=0.8";
|
||||
$httpheader[] = "Connection: close";
|
||||
if ($headers) {
|
||||
$httpheader = array_merge($httpheader, $headers);
|
||||
if ($default_headers === true) {
|
||||
$httpheader[] = "Accept: */*";
|
||||
$httpheader[] = "Accept-Language: zh-CN,zh;q=0.8";
|
||||
$httpheader[] = "Connection: close";
|
||||
if ($headers) {
|
||||
$httpheader = array_merge($headers, $httpheader);
|
||||
}
|
||||
} else {
|
||||
$httpheader = $headers;
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36");
|
||||
@@ -419,8 +434,9 @@ function curl_client($url, $data = null, $referer = null, $cookie = null, $heade
|
||||
$ret = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
if ($errno) {
|
||||
$errmsg = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception('Curl error: ' . curl_error($ch));
|
||||
throw new Exception('Curl error: ' . $errmsg);
|
||||
}
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
@@ -471,4 +487,23 @@ function convertDomainToUtf8($domain) {
|
||||
} else {
|
||||
return $domain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDomainDate($domain)
|
||||
{
|
||||
try {
|
||||
$whois = \Iodev\Whois\Factory::get()->createWhois();
|
||||
$info = $whois->loadDomainInfo($domain);
|
||||
if ($info) {
|
||||
if ($info->expirationDate > 0) {
|
||||
return [date('Y-m-d H:i:s', $info->creationDate), date('Y-m-d H:i:s', $info->expirationDate)];
|
||||
} else {
|
||||
throw new Exception('域名到期时间未知');
|
||||
}
|
||||
} else {
|
||||
throw new Exception('域名信息未找到');
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('查询域名whois失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,6 +244,17 @@ class Cert extends BaseController
|
||||
return json(['total' => $total, 'rows' => $list]);
|
||||
}
|
||||
|
||||
public function order_info()
|
||||
{
|
||||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||||
$id = input('post.id/d');
|
||||
$row = Db::name('cert_order')->where('id', $id)->find();
|
||||
if (!$row) return json(['code' => -1, 'msg' => '证书订单不存在']);
|
||||
$pfx = CertHelper::getPfx($row['fullchain'], $row['privatekey']);
|
||||
$row['pfx'] = base64_encode($pfx);
|
||||
return json(['code' => 0, 'data' => ['id' => $row['id'], 'crt' => $row['fullchain'], 'key' => $row['privatekey'], 'pfx' => $row['pfx'], 'issuetime' => $row['issuetime'], 'expiretime' => $row['expiretime'], 'domains' => Db::name('cert_domain')->where('oid', $row['id'])->order('sort','ASC')->column('domain')]]);
|
||||
}
|
||||
|
||||
public function order_op()
|
||||
{
|
||||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||||
@@ -265,6 +276,7 @@ class Cert extends BaseController
|
||||
'addtime' => date('Y-m-d H:i:s'),
|
||||
'issuer' => '',
|
||||
'status' => 0,
|
||||
'isauto' => 1,
|
||||
];
|
||||
$domains = array_map('trim', $domains);
|
||||
$domains = array_filter($domains, function ($v) {
|
||||
@@ -435,6 +447,35 @@ class Cert extends BaseController
|
||||
$file = app()->getRuntimePath().'log/'.$processid.'.log';
|
||||
if(!file_exists($file)) return json(['code' => -1, 'msg' => '日志文件不存在']);
|
||||
return json(['code' => 0, 'data' => file_get_contents($file), 'time'=>filemtime($file)]);
|
||||
} elseif ($action == 'operation') {
|
||||
$ids = input('post.ids');
|
||||
$success = 0;
|
||||
foreach ($ids as $id) {
|
||||
if (input('post.action') == 'delete') {
|
||||
$dcount = DB::name('cert_deploy')->where('oid', $id)->count();
|
||||
if ($dcount > 0) continue;
|
||||
try {
|
||||
(new CertOrderService($id))->cancel();
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
Db::name('cert_order')->where('id', $id)->delete();
|
||||
Db::name('cert_domain')->where('oid', $id)->delete();
|
||||
$success++;
|
||||
} elseif (input('post.action') == 'reset') {
|
||||
try {
|
||||
$service = new CertOrderService($id);
|
||||
$service->cancel();
|
||||
$service->reset();
|
||||
$success++;
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
} elseif (input('post.action') == 'open' || input('post.action') == 'close') {
|
||||
$isauto = input('post.action') == 'open' ? 1 : 0;
|
||||
Db::name('cert_order')->where('id', $id)->update(['isauto' => $isauto]);
|
||||
$success++;
|
||||
}
|
||||
}
|
||||
return json(['code' => 0, 'msg' => '成功操作' . $success . '个证书订单']);
|
||||
}
|
||||
return json(['code' => -3]);
|
||||
}
|
||||
@@ -454,7 +495,6 @@ class Cert extends BaseController
|
||||
|
||||
foreach($domains as $domain){
|
||||
if (!$wildcard && strpos($domain, '*') !== false) return ['code' => -1, 'msg' => '该证书账户类型不支持泛域名'];
|
||||
if (preg_match('/[\x{4e00}-\x{9fa5}]/u', $domain) && !function_exists('idn_to_ascii')) return ['code' => -1, 'msg' => '域名包含中文,请开启intl扩展'];
|
||||
$mainDomain = getMainDomain($domain);
|
||||
$drow = Db::name('domain')->where('name', $mainDomain)->find();
|
||||
if (!$drow) {
|
||||
@@ -645,6 +685,27 @@ class Cert extends BaseController
|
||||
$file = app()->getRuntimePath().'log/'.$processid.'.log';
|
||||
if(!file_exists($file)) return json(['code' => -1, 'msg' => '日志文件不存在']);
|
||||
return json(['code' => 0, 'data' => file_get_contents($file), 'time'=>filemtime($file)]);
|
||||
} elseif ($action == 'operation') {
|
||||
$ids = input('post.ids');
|
||||
$success = 0;
|
||||
foreach ($ids as $id) {
|
||||
if (input('post.action') == 'delete') {
|
||||
Db::name('cert_deploy')->where('id', $id)->delete();
|
||||
$success++;
|
||||
} elseif (input('post.action') == 'reset') {
|
||||
try {
|
||||
$service = new CertDeployService($id);
|
||||
$service->reset();
|
||||
$success++;
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
} elseif (input('post.action') == 'open' || input('post.action') == 'close') {
|
||||
$active = input('post.action') == 'open' ? 1 : 0;
|
||||
Db::name('cert_deploy')->where('id', $id)->update(['active' => $active]);
|
||||
$success++;
|
||||
}
|
||||
}
|
||||
return json(['code' => 0, 'msg' => '成功操作' . $success . '个任务']);
|
||||
}
|
||||
return json(['code' => -3]);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ class Dmonitor extends BaseController
|
||||
{
|
||||
if (!checkPermission(2)) return json(['total' => 0, 'rows' => []]);
|
||||
$type = input('post.type/d', 1);
|
||||
$status = input('post.status', null);
|
||||
$kw = input('post.kw', null, 'trim');
|
||||
$offset = input('post.offset/d');
|
||||
$limit = input('post.limit/d');
|
||||
@@ -57,6 +58,9 @@ class Dmonitor extends BaseController
|
||||
$select->whereLike('remark', '%' . $kw . '%');
|
||||
}
|
||||
}
|
||||
if (!isNullOrEmpty($status)) {
|
||||
$select->where('status', intval($status));
|
||||
}
|
||||
$total = $select->count();
|
||||
$list = $select->order('A.id', 'desc')->limit($offset, $limit)->field('A.*,B.name domain')->select()->toArray();
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ namespace app\controller;
|
||||
use app\BaseController;
|
||||
use think\facade\Db;
|
||||
use think\facade\View;
|
||||
use think\facade\Request;
|
||||
use think\facade\Cache;
|
||||
use app\lib\DnsHelper;
|
||||
use app\service\ExpireNoticeService;
|
||||
use Exception;
|
||||
|
||||
class Domain extends BaseController
|
||||
@@ -179,6 +180,7 @@ class Domain extends BaseController
|
||||
if (!checkPermission(1)) return json(['total' => 0, 'rows' => []]);
|
||||
$kw = input('post.kw', null, 'trim');
|
||||
$type = input('post.type', null, 'trim');
|
||||
$status = input('post.status', null, 'trim');
|
||||
$offset = input('post.offset/d', 0);
|
||||
$limit = input('post.limit/d', 10);
|
||||
|
||||
@@ -192,6 +194,13 @@ class Domain extends BaseController
|
||||
if (request()->user['level'] == 1) {
|
||||
$select->where('is_hide', 0)->where('A.name', 'in', request()->user['permission']);
|
||||
}
|
||||
if (!isNullOrEmpty($status)) {
|
||||
if ($status == '2') {
|
||||
$select->where('A.expiretime', '<=', date('Y-m-d H:i:s'));
|
||||
} elseif ($status == '1') {
|
||||
$select->where('A.expiretime', '<=', date('Y-m-d H:i:s', time() + 86400 * 30))->where('A.expiretime', '>', date('Y-m-d H:i:s'));
|
||||
}
|
||||
}
|
||||
$total = $select->count();
|
||||
$rows = $select->fieldRaw('A.*,B.type,B.remark aremark')->order('A.id', 'desc')->limit($offset, $limit)->select();
|
||||
|
||||
@@ -240,11 +249,15 @@ class Domain extends BaseController
|
||||
if (!$row) return json(['code' => -1, 'msg' => '域名不存在']);
|
||||
$is_hide = input('post.is_hide/d');
|
||||
$is_sso = input('post.is_sso/d');
|
||||
$is_notice = input('post.is_notice/d');
|
||||
$expiretime = input('post.expiretime', null, 'trim');
|
||||
$remark = input('post.remark', null, 'trim');
|
||||
if (empty($remark)) $remark = null;
|
||||
Db::name('domain')->where('id', $id)->update([
|
||||
'is_hide' => $is_hide,
|
||||
'is_sso' => $is_sso,
|
||||
'is_notice' => $is_notice,
|
||||
'expiretime' => $expiretime ? $expiretime : null,
|
||||
'remark' => $remark,
|
||||
]);
|
||||
return json(['code' => 0, 'msg' => '修改域名配置成功!']);
|
||||
@@ -280,8 +293,15 @@ class Domain extends BaseController
|
||||
if (empty($ids)) return json(['code' => -1, 'msg' => '参数不能为空']);
|
||||
$remark = input('post.remark', null, 'trim');
|
||||
if (empty($remark)) $remark = null;
|
||||
Db::name('domain')->where('id', 'in', $ids)->update(['remark' => $remark]);
|
||||
return json(['code' => 0, 'msg' => '成功修改' . count($ids) . '个域名!']);
|
||||
$count = Db::name('domain')->where('id', 'in', $ids)->update(['remark' => $remark]);
|
||||
return json(['code' => 0, 'msg' => '成功修改' . $count . '个域名!']);
|
||||
} elseif ($act == 'batchsetnotice') {
|
||||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||||
$ids = input('post.ids');
|
||||
$is_notice = input('post.is_notice/d', 0);
|
||||
if (empty($ids)) return json(['code' => -1, 'msg' => '参数不能为空']);
|
||||
$count = Db::name('domain')->where('id', 'in', $ids)->update(['is_notice' => $is_notice]);
|
||||
return json(['code' => 0, 'msg' => '成功修改' . $count . '个域名!']);
|
||||
} elseif ($act == 'batchdel') {
|
||||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||||
$ids = input('post.ids');
|
||||
@@ -401,8 +421,8 @@ class Domain extends BaseController
|
||||
$type = input('post.type', null, 'trim');
|
||||
$line = input('post.line', null, 'trim');
|
||||
$status = input('post.status', null, 'trim');
|
||||
$offset = input('post.offset/d');
|
||||
$limit = input('post.limit/d');
|
||||
$offset = input('post.offset/d', 0);
|
||||
$limit = input('post.limit/d', 10);
|
||||
if ($limit == 0) {
|
||||
$page = 1;
|
||||
} else {
|
||||
@@ -439,7 +459,6 @@ class Domain extends BaseController
|
||||
|
||||
public function record_list()
|
||||
{
|
||||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||||
$id = input('post.id/d');
|
||||
$rr = input('post.rr', null, 'trim');
|
||||
|
||||
@@ -455,11 +474,14 @@ class Domain extends BaseController
|
||||
|
||||
list($recordLine, $minTTL) = $this->get_line_and_ttl($drow);
|
||||
|
||||
$list = [];
|
||||
foreach ($domainRecords['list'] as &$row) {
|
||||
if ($rr == '@' && ($row['Type'] == 'NS' || $row['Type'] == 'SOA')) continue;
|
||||
$row['LineName'] = isset($recordLine[$row['Line']]) ? $recordLine[$row['Line']]['name'] : $row['Line'];
|
||||
$list[] = $row;
|
||||
}
|
||||
|
||||
return json(['code' => 0, 'data' => $domainRecords['list']]);
|
||||
return json(['code' => 0, 'data' => $list]);
|
||||
}
|
||||
|
||||
public function record_add()
|
||||
@@ -523,10 +545,14 @@ class Domain extends BaseController
|
||||
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
|
||||
$recordid = $dns->updateDomainRecord($recordid, $name, $type, $value, $line, $ttl, $mx, $weight, $remark);
|
||||
if ($recordid) {
|
||||
if (is_array($recordinfo['Value'])) $recordinfo['Value'] = implode(',', $recordinfo['Value']);
|
||||
if ($recordinfo['Name'] != $name || $recordinfo['Type'] != $type || $recordinfo['Value'] != $value) {
|
||||
$this->add_log($drow['name'], '修改解析', $recordinfo['Name'].' ['.$recordinfo['Type'].'] '.$recordinfo['Value'].' → '.$name.' ['.$type.'] '.$value.' (线路:'.$line.' TTL:'.$ttl.')');
|
||||
} elseif($recordinfo['Line'] != $line || $recordinfo['TTL'] != $ttl) {
|
||||
if ($recordinfo) {
|
||||
if (is_array($recordinfo['Value'])) $recordinfo['Value'] = implode(',', $recordinfo['Value']);
|
||||
if ($recordinfo['Name'] != $name || $recordinfo['Type'] != $type || $recordinfo['Value'] != $value) {
|
||||
$this->add_log($drow['name'], '修改解析', $recordinfo['Name'].' ['.$recordinfo['Type'].'] '.$recordinfo['Value'].' → '.$name.' ['.$type.'] '.$value.' (线路:'.$line.' TTL:'.$ttl.')');
|
||||
} elseif($recordinfo['Line'] != $line || $recordinfo['TTL'] != $ttl) {
|
||||
$this->add_log($drow['name'], '修改解析', $name.' ['.$type.'] '.$value.' (线路:'.$line.' TTL:'.$ttl.')');
|
||||
}
|
||||
} else {
|
||||
$this->add_log($drow['name'], '修改解析', $name.' ['.$type.'] '.$value.' (线路:'.$line.' TTL:'.$ttl.')');
|
||||
}
|
||||
return json(['code' => 0, 'msg' => '修改解析记录成功!']);
|
||||
@@ -768,6 +794,12 @@ class Domain extends BaseController
|
||||
if (empty($record) || empty($recordlist)) {
|
||||
return json(['code' => -1, 'msg' => '参数不能为空']);
|
||||
}
|
||||
if (is_null($line)) {
|
||||
$line = DnsHelper::$line_name[$dnstype]['DEF'];
|
||||
if ($dnstype == 'cloudflare' && input('post.proxy/d', 0) == 1) {
|
||||
$line = '1';
|
||||
}
|
||||
}
|
||||
|
||||
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
|
||||
|
||||
@@ -786,7 +818,13 @@ class Domain extends BaseController
|
||||
$fail++;
|
||||
}
|
||||
}
|
||||
return json(['code' => 0, 'msg' => '批量添加解析,成功' . $success . '条,失败' . $fail . '条']);
|
||||
if ($success > 0) {
|
||||
return json(['code' => 0, 'msg' => '批量添加解析,成功' . $success . '条,失败' . $fail . '条']);
|
||||
} elseif($fail > 0) {
|
||||
return json(['code' => -1, 'msg' => '批量添加解析失败,' . $dns->getError()]);
|
||||
} else {
|
||||
return json(['code' => -1, 'msg' => '批量添加解析失败,没有可添加的记录']);
|
||||
}
|
||||
}
|
||||
|
||||
list($recordLine, $minTTL) = $this->get_line_and_ttl($drow);
|
||||
@@ -807,6 +845,89 @@ class Domain extends BaseController
|
||||
return view('batchadd');
|
||||
}
|
||||
|
||||
public function record_batch_add2()
|
||||
{
|
||||
return view('batchadd2');
|
||||
}
|
||||
|
||||
public function record_batch_edit2()
|
||||
{
|
||||
if (request()->isAjax()) {
|
||||
$id = input('post.id/d');
|
||||
$drow = Db::name('domain')->where('id', $id)->find();
|
||||
if (!$drow) {
|
||||
return json(['code' => -1, 'msg' => '域名不存在']);
|
||||
}
|
||||
$dnstype = Db::name('account')->where('id', $drow['aid'])->value('type');
|
||||
if (!checkPermission(0, $drow['name'])) return json(['code' => -1, 'msg' => '无权限']);
|
||||
|
||||
$name = input('post.name', null, 'trim');
|
||||
$type = input('post.type', null, 'trim');
|
||||
$value = input('post.value', null, 'trim');
|
||||
$ttl = input('post.ttl/d', 0);
|
||||
$mx = input('post.mx/d', 0);
|
||||
|
||||
if (empty($name) || empty($type) || empty($value)) {
|
||||
return json(['code' => -1, 'msg' => '必填参数不能为空']);
|
||||
}
|
||||
$line = DnsHelper::$line_name[$dnstype]['DEF'];
|
||||
|
||||
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
|
||||
$domainRecords = $dns->getSubDomainRecords($name, 1, 100);
|
||||
if (!$domainRecords) return json(['code' => -1, 'msg' => '获取记录列表失败,' . $dns->getError()]);
|
||||
if (empty($domainRecords['list'])) return json(['code' => -1, 'msg' => '没有可修改的记录']);
|
||||
|
||||
if ($type == 'A' || $type == 'AAAA' || $type == 'CNAME') {
|
||||
$list2 = array_filter($domainRecords['list'], function ($item) use ($type) {
|
||||
return $item['Type'] == $type;
|
||||
});
|
||||
if (!empty($list2)) {
|
||||
$list = $list2;
|
||||
} else {
|
||||
$list = array_filter($domainRecords['list'], function ($item) {
|
||||
return $item['Type'] == 'A' || $item['Type'] == 'AAAA' || $item['Type'] == 'CNAME';
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$list = array_filter($domainRecords['list'], function ($item) use ($type) {
|
||||
return $item['Type'] == $type;
|
||||
});
|
||||
}
|
||||
if (empty($list)) return json(['code' => -1, 'msg' => '没有可修改的'.$type.'记录']);
|
||||
|
||||
$list2 = array_filter($domainRecords['list'], function ($item) use ($line) {
|
||||
return $item['Line'] == $line;
|
||||
});
|
||||
if (!empty($list2)) $list = $list2;
|
||||
|
||||
$success = 0;
|
||||
$fail = 0;
|
||||
foreach ($list as $record) {
|
||||
if ($name == '@' && ($record['Type'] == 'NS' || $record['Type'] == 'SOA')) continue;
|
||||
|
||||
if ($ttl > 0) $record['TTL'] = $ttl;
|
||||
if ($mx > 0) $record['MX'] = $mx;
|
||||
$recordid = $dns->updateDomainRecord($record['RecordId'], $record['Name'], $type, $value, $record['Line'], $record['TTL'], $record['MX'], $record['Weight'], $record['Remark']);
|
||||
if ($recordid) {
|
||||
if (is_array($record['Value'])) $record['Value'] = implode(',', $record['Value']);
|
||||
$this->add_log($drow['name'], '修改解析', $record['Name'].' ['.$record['Type'].'] '.$record['Value'].' → '.$record['Name'].' ['.$type.'] '.$value.' (线路:'.$record['Line'].' TTL:'.$record['TTL'].')');
|
||||
$success++;
|
||||
} else {
|
||||
$fail++;
|
||||
}
|
||||
}
|
||||
if ($success > 0) {
|
||||
return json(['code' => 0, 'msg' => '成功修改' . $success . '条解析记录']);
|
||||
} elseif($fail > 0) {
|
||||
return json(['code' => -1, 'msg' => $dns->getError()]);
|
||||
} else {
|
||||
return json(['code' => -1, 'msg' => '没有可修改的记录']);
|
||||
}
|
||||
}
|
||||
|
||||
return view('batchedit');
|
||||
}
|
||||
|
||||
public function record_log()
|
||||
{
|
||||
$id = input('param.id/d');
|
||||
@@ -836,4 +957,132 @@ class Domain extends BaseController
|
||||
if (strlen($data) > 500) $data = substr($data, 0, 500);
|
||||
Db::name('log')->insert(['uid' => request()->user['id'], 'domain' => $domain, 'action' => $action, 'data' => $data, 'addtime' => date("Y-m-d H:i:s")]);
|
||||
}
|
||||
|
||||
|
||||
public function weight()
|
||||
{
|
||||
$id = input('param.id/d');
|
||||
$drow = Db::name('domain')->where('id', $id)->find();
|
||||
if (!$drow) {
|
||||
return $this->alert('error', '域名不存在');
|
||||
}
|
||||
if (!checkPermission(0, $drow['name'])) return $this->alert('error', '无权限');
|
||||
if (request()->isAjax()) {
|
||||
$act = input('param.act');
|
||||
if ($act == 'status') {
|
||||
$subdomain = input('post.subdomain', null, 'trim');
|
||||
$status = input('post.status', null, 'trim');
|
||||
$type = input('post.type', null, 'trim');
|
||||
$line = input('post.line', null, 'trim');
|
||||
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
|
||||
if ($dns->setWeightStatus($subdomain, $status, $type, $line)) {
|
||||
return json(['code' => 0, 'msg' => '操作成功']);
|
||||
} else {
|
||||
return json(['code' => -1, 'msg' => '操作失败,' . $dns->getError()]);
|
||||
}
|
||||
} elseif ($act == 'update') {
|
||||
$subdomain = input('post.subdomain', null, 'trim');
|
||||
$status = input('post.status', '0', 'trim');
|
||||
$type = input('post.type', null, 'trim');
|
||||
$line = input('post.line', null, 'trim');
|
||||
$weight = input('post.weight');
|
||||
if (empty($subdomain) || empty($type) || empty($line) || $status == '1' && empty($weight)) {
|
||||
return json(['code' => -1, 'msg' => '参数不能为空']);
|
||||
}
|
||||
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
|
||||
if ($dns->setWeightStatus($subdomain, $status, $type, $line)) {
|
||||
if ($status == '1') {
|
||||
$success = 0;
|
||||
foreach($weight as $recordid => $weight) {
|
||||
if ($dns->updateRecordWeight($recordid, $weight)) {
|
||||
$success++;
|
||||
}
|
||||
}
|
||||
if ($success > 0) {
|
||||
return json(['code' => 0, 'msg' => '成功修改' . $success . '条解析记录权重']);
|
||||
} else {
|
||||
return json(['code' => -1, 'msg' => '修改权重失败,' . $dns->getError()]);
|
||||
}
|
||||
}
|
||||
return json(['code' => 0, 'msg' => '修改成功']);
|
||||
} else {
|
||||
return json(['code' => -1, 'msg' => '修改失败,' . $dns->getError()]);
|
||||
}
|
||||
} else {
|
||||
return json(['code' => -1, 'msg' => '参数错误']);
|
||||
}
|
||||
}
|
||||
|
||||
$dnstype = Db::name('account')->where('id', $drow['aid'])->value('type');
|
||||
if ($dnstype != 'aliyun') {
|
||||
return $this->alert('error', '仅支持阿里云解析的域名');
|
||||
}
|
||||
list($recordLine, $minTTL) = $this->get_line_and_ttl($drow);
|
||||
|
||||
$recordLineArr = [];
|
||||
foreach ($recordLine as $key => $item) {
|
||||
$recordLineArr[] = ['id' => strval($key), 'name' => $item['name'], 'parent' => $item['parent']];
|
||||
}
|
||||
|
||||
$dnsconfig = DnsHelper::$dns_config[$dnstype];
|
||||
$dnsconfig['type'] = $dnstype;
|
||||
|
||||
View::assign('domainId', $id);
|
||||
View::assign('domainName', $drow['name']);
|
||||
View::assign('recordLine', $recordLineArr);
|
||||
View::assign('dnsconfig', $dnsconfig);
|
||||
return view();
|
||||
}
|
||||
|
||||
public function weight_data()
|
||||
{
|
||||
$id = input('param.id/d');
|
||||
$keyword = input('post.keyword', null, 'trim');
|
||||
$offset = input('post.offset/d');
|
||||
$limit = input('post.limit/d');
|
||||
if ($limit == 0) {
|
||||
$page = 1;
|
||||
} else {
|
||||
$page = $offset / $limit + 1;
|
||||
}
|
||||
|
||||
$drow = Db::name('domain')->where('id', $id)->find();
|
||||
if (!$drow) {
|
||||
return json(['total' => 0, 'rows' => []]);
|
||||
}
|
||||
if (!checkPermission(0, $drow['name'])) return json(['total' => 0, 'rows' => []]);
|
||||
|
||||
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
|
||||
$domainRecords = $dns->getWeightSubDomains($page, $limit, $keyword);
|
||||
return json(['total' => $domainRecords['total'], 'rows' => $domainRecords['list']]);
|
||||
}
|
||||
|
||||
public function expire_notice()
|
||||
{
|
||||
if (!checkPermission(2)) return $this->alert('error', '无权限');
|
||||
if ($this->request->isPost()) {
|
||||
$params = input('post.');
|
||||
foreach ($params as $key => $value) {
|
||||
if (empty($key)) {
|
||||
continue;
|
||||
}
|
||||
config_set($key, $value);
|
||||
Cache::delete('configs');
|
||||
}
|
||||
return json(['code' => 0, 'msg' => 'succ']);
|
||||
}
|
||||
return View::fetch();
|
||||
}
|
||||
|
||||
public function update_date()
|
||||
{
|
||||
$id = input('param.id/d');
|
||||
$drow = Db::name('domain')->where('id', $id)->find();
|
||||
if (!$drow) {
|
||||
return json(['code' => -1, 'msg' => '域名不存在']);
|
||||
}
|
||||
if (!checkPermission(0, $drow['name'])) return json(['code' => -1, 'msg' => '无权限']);
|
||||
$result = (new ExpireNoticeService())->updateDomainDate($id, $drow['name']);
|
||||
return json($result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,18 +19,36 @@ class Index extends BaseController
|
||||
}
|
||||
if ($this->request->isAjax()) {
|
||||
if (input('post.do') == 'stat') {
|
||||
$stat = ['domains' => 0, 'tasks' => 0, 'certs' => 0, 'deploys' => 0];
|
||||
$stat = [];
|
||||
if ($this->request->user['level'] == 2) {
|
||||
$stat['domains'] = Db::name('domain')->count();
|
||||
$stat['tasks'] = Db::name('dmtask')->count();
|
||||
$stat['certs'] = Db::name('cert_order')->count();
|
||||
$stat['deploys'] = Db::name('cert_deploy')->count();
|
||||
} else {
|
||||
$stat['domains'] = Db::name('domain')->where('name', 'in', $this->request->user['permission'])->count();
|
||||
$stat['tasks'] = Db::name('dmtask')->count();
|
||||
$stat['certs'] = Db::name('cert_order')->count();
|
||||
$stat['deploys'] = Db::name('cert_deploy')->count();
|
||||
}
|
||||
$stat['tasks'] = Db::name('dmtask')->count();
|
||||
$stat['certs'] = Db::name('cert_order')->count();
|
||||
$stat['deploys'] = Db::name('cert_deploy')->count();
|
||||
|
||||
$run_time = config_get('run_time', null, true);
|
||||
$run_state = $run_time ? (time() - strtotime($run_time) > 10 ? 0 : 1) : 0;
|
||||
$stat['dmonitor_state'] = $run_state;
|
||||
$stat['dmonitor_active'] = Db::name('dmtask')->where('active', 1)->count();
|
||||
$stat['dmonitor_status_0'] = Db::name('dmtask')->where('status', 0)->count();
|
||||
$stat['dmonitor_status_1'] = Db::name('dmtask')->where('status', 1)->count();
|
||||
|
||||
$stat['optimizeip_active'] = Db::name('optimizeip')->where('active', 1)->count();
|
||||
$stat['optimizeip_status_1'] = Db::name('optimizeip')->where('status', 1)->count();
|
||||
$stat['optimizeip_status_2'] = Db::name('optimizeip')->where('status', 2)->count();
|
||||
|
||||
$stat['certorder_status_3'] = Db::name('cert_order')->where('status', 3)->count();
|
||||
$stat['certorder_status_5'] = Db::name('cert_order')->where('status', '<', 0)->count();
|
||||
$stat['certorder_status_6'] = Db::name('cert_order')->where('expiretime', '<', date('Y-m-d H:i:s', time() + 86400 * 7))->where('expiretime', '>=', date('Y-m-d H:i:s'))->count();
|
||||
$stat['certorder_status_7'] = Db::name('cert_order')->where('expiretime', '<', date('Y-m-d H:i:s'))->count();
|
||||
|
||||
$stat['certdeploy_status_0'] = Db::name('cert_deploy')->where('status', 0)->count();
|
||||
$stat['certdeploy_status_1'] = Db::name('cert_deploy')->where('status', 1)->count();
|
||||
$stat['certdeploy_status_2'] = Db::name('cert_deploy')->where('status', -1)->count();
|
||||
|
||||
return json($stat);
|
||||
}
|
||||
return json(['code' => -3]);
|
||||
|
||||
@@ -39,6 +39,7 @@ class Optimizeip extends BaseController
|
||||
if (!checkPermission(2)) return json(['total' => 0, 'rows' => []]);
|
||||
$type = input('post.type/d', 1);
|
||||
$kw = input('post.kw', null, 'trim');
|
||||
$status = input('post.status', null);
|
||||
$offset = input('post.offset/d');
|
||||
$limit = input('post.limit/d');
|
||||
|
||||
@@ -50,6 +51,9 @@ class Optimizeip extends BaseController
|
||||
$select->whereLike('remark', '%' . $kw . '%');
|
||||
}
|
||||
}
|
||||
if (!isNullOrEmpty($status)) {
|
||||
$select->where('status', intval($status));
|
||||
}
|
||||
$total = $select->count();
|
||||
$list = $select->order('A.id', 'desc')->limit($offset, $limit)->field('A.*,B.name domain')->select();
|
||||
|
||||
|
||||
@@ -288,6 +288,7 @@ uk.com
|
||||
us.com
|
||||
uy.com
|
||||
za.com
|
||||
it.com
|
||||
co.cr
|
||||
ed.cr
|
||||
fi.cr
|
||||
@@ -1341,6 +1342,7 @@ zagan.pl
|
||||
zarow.pl
|
||||
zgora.pl
|
||||
zgorzelec.pl
|
||||
co.pl
|
||||
co.pn
|
||||
net.pn
|
||||
org.pn
|
||||
@@ -1925,4 +1927,18 @@ edu.kg
|
||||
edu.cn
|
||||
eu.org
|
||||
us.kg
|
||||
ggff.net
|
||||
xx.kg
|
||||
qzz.io
|
||||
dpdns.org
|
||||
ggff.net
|
||||
ac.ru
|
||||
edu.ru
|
||||
com.ru
|
||||
msk.ru
|
||||
net.ru
|
||||
nov.ru
|
||||
org.ru
|
||||
pp.ru
|
||||
spb.ru
|
||||
uk.co
|
||||
gov.scot
|
||||
@@ -1,367 +1,415 @@
|
||||
<?php
|
||||
|
||||
namespace app\lib;
|
||||
|
||||
use think\facade\Db;
|
||||
|
||||
class CertHelper
|
||||
{
|
||||
public static $cert_config = [
|
||||
'letsencrypt' => [
|
||||
'name' => 'Let\'s Encrypt',
|
||||
'class' => 1,
|
||||
'icon' => 'letsencrypt.ico',
|
||||
'wildcard' => true,
|
||||
'max_domains' => 100,
|
||||
'cname' => true,
|
||||
'note' => null,
|
||||
'inputs' => [
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => '用于注册Let\'s Encrypt账号',
|
||||
'required' => true,
|
||||
],
|
||||
'mode' => [
|
||||
'name' => '环境选择',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'live' => '正式环境',
|
||||
'staging' => '测试环境',
|
||||
],
|
||||
'value' => 'live'
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '否',
|
||||
'1' => '是',
|
||||
],
|
||||
'value' => '0'
|
||||
],
|
||||
]
|
||||
],
|
||||
'zerossl' => [
|
||||
'name' => 'ZeroSSL',
|
||||
'class' => 1,
|
||||
'icon' => 'zerossl.ico',
|
||||
'wildcard' => true,
|
||||
'max_domains' => 100,
|
||||
'cname' => true,
|
||||
'note' => '<a href="https://app.zerossl.com/developer" target="_blank" rel="noreferrer">ZeroSSL密钥生成地址</a>',
|
||||
'inputs' => [
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => 'EAB申请邮箱',
|
||||
'required' => true,
|
||||
],
|
||||
'kid' => [
|
||||
'name' => 'EAB KID',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'key' => [
|
||||
'name' => 'EAB HMAC Key',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '否',
|
||||
'1' => '是',
|
||||
],
|
||||
'value' => '0'
|
||||
],
|
||||
]
|
||||
],
|
||||
'google' => [
|
||||
'name' => 'Google SSL',
|
||||
'class' => 1,
|
||||
'icon' => 'google.ico',
|
||||
'wildcard' => true,
|
||||
'max_domains' => 100,
|
||||
'cname' => true,
|
||||
'note' => '<a href="https://cloud.google.com/certificate-manager/docs/public-ca-tutorial" target="_blank" rel="noreferrer">查看Google SSL账户配置说明</a>',
|
||||
'inputs' => [
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => 'EAB申请邮箱',
|
||||
'required' => true,
|
||||
],
|
||||
'kid' => [
|
||||
'name' => 'keyId',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'key' => [
|
||||
'name' => 'b64MacKey',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'mode' => [
|
||||
'name' => '环境选择',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'live' => '正式环境',
|
||||
'staging' => '测试环境',
|
||||
],
|
||||
'value' => 'live'
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '否',
|
||||
'1' => '是',
|
||||
],
|
||||
'value' => '0'
|
||||
],
|
||||
]
|
||||
],
|
||||
'tencent' => [
|
||||
'name' => '腾讯云免费SSL',
|
||||
'class' => 2,
|
||||
'icon' => 'tencent.ico',
|
||||
'wildcard' => false,
|
||||
'max_domains' => 1,
|
||||
'cname' => false,
|
||||
'note' => '一个账号有50张免费证书额度,证书到期或吊销可释放额度。<a href="https://cloud.tencent.com/document/product/400/89868" target="_blank" rel="noreferrer">腾讯云免费SSL简介与额度说明</a>',
|
||||
'inputs' => [
|
||||
'SecretId' => [
|
||||
'name' => 'SecretId',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'SecretKey' => [
|
||||
'name' => 'SecretKey',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请证书时填写的邮箱',
|
||||
'required' => true,
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '否',
|
||||
'1' => '是',
|
||||
],
|
||||
'value' => '0'
|
||||
],
|
||||
]
|
||||
],
|
||||
'aliyun' => [
|
||||
'name' => '阿里云免费SSL',
|
||||
'class' => 2,
|
||||
'icon' => 'aliyun.ico',
|
||||
'wildcard' => false,
|
||||
'max_domains' => 1,
|
||||
'cname' => false,
|
||||
'note' => '每个自然年有20张免费证书额度,证书到期或吊销不释放额度。需要先进入阿里云控制台-<a href="https://yundun.console.aliyun.com/?p=cas#/certExtend/free/cn-hangzhou" target="_blank" rel="noreferrer">数字证书管理服务</a>,购买个人测试证书资源包。',
|
||||
'inputs' => [
|
||||
'AccessKeyId' => [
|
||||
'name' => 'AccessKeyId',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'AccessKeySecret' => [
|
||||
'name' => 'AccessKeySecret',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'username' => [
|
||||
'name' => '姓名',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请联系人的姓名',
|
||||
'required' => true,
|
||||
],
|
||||
'phone' => [
|
||||
'name' => '手机号码',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请联系人的手机号码',
|
||||
'required' => true,
|
||||
],
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请联系人的邮箱地址',
|
||||
'required' => true,
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '否',
|
||||
'1' => '是',
|
||||
],
|
||||
'value' => '0'
|
||||
],
|
||||
]
|
||||
],
|
||||
'ucloud' => [
|
||||
'name' => 'UCloud免费SSL',
|
||||
'class' => 2,
|
||||
'icon' => 'ucloud.ico',
|
||||
'wildcard' => false,
|
||||
'max_domains' => 1,
|
||||
'cname' => false,
|
||||
'note' => '一个账号有40张免费证书额度,证书到期或吊销可释放额度。',
|
||||
'inputs' => [
|
||||
'PublicKey' => [
|
||||
'name' => '公钥',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'PrivateKey' => [
|
||||
'name' => '私钥',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'username' => [
|
||||
'name' => '姓名',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请联系人的姓名',
|
||||
'required' => true,
|
||||
],
|
||||
'phone' => [
|
||||
'name' => '手机号码',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请联系人的手机号码',
|
||||
'required' => true,
|
||||
],
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请联系人的邮箱地址',
|
||||
'required' => true,
|
||||
],
|
||||
]
|
||||
],
|
||||
'customacme' => [
|
||||
'name' => '自定义ACME',
|
||||
'class' => 1,
|
||||
'icon' => 'ssl.ico',
|
||||
'wildcard' => true,
|
||||
'max_domains' => 100,
|
||||
'cname' => true,
|
||||
'note' => null,
|
||||
'inputs' => [
|
||||
'directory' => [
|
||||
'name' => 'ACME地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => 'ACME Directory 地址',
|
||||
'required' => true,
|
||||
],
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => '证书申请邮箱',
|
||||
'required' => true,
|
||||
],
|
||||
'kid' => [
|
||||
'name' => 'EAB KID',
|
||||
'type' => 'input',
|
||||
'placeholder' => '留空则不使用EAB认证',
|
||||
],
|
||||
'key' => [
|
||||
'name' => 'EAB HMAC Key',
|
||||
'type' => 'input',
|
||||
'placeholder' => '留空则不使用EAB认证',
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '否',
|
||||
'1' => '是',
|
||||
],
|
||||
'value' => '0'
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
public static $class_config = [
|
||||
1 => '基于ACME的SSL证书',
|
||||
2 => '云服务商的SSL证书',
|
||||
];
|
||||
|
||||
public static function getList()
|
||||
{
|
||||
return self::$cert_config;
|
||||
}
|
||||
|
||||
private static function getConfig($aid)
|
||||
{
|
||||
$account = Db::name('cert_account')->where('id', $aid)->find();
|
||||
if (!$account) return false;
|
||||
return $account;
|
||||
}
|
||||
|
||||
public static function getInputs($type, $config = null)
|
||||
{
|
||||
$config = $config ? json_decode($config, true) : [];
|
||||
$inputs = self::$cert_config[$type]['inputs'];
|
||||
foreach ($inputs as &$input) {
|
||||
if (isset($config[$input['name']])) {
|
||||
$input['value'] = $config[$input['name']];
|
||||
}
|
||||
}
|
||||
return $inputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CertInterface|bool
|
||||
*/
|
||||
public static function getModel($aid)
|
||||
{
|
||||
$account = self::getConfig($aid);
|
||||
if (!$account) return false;
|
||||
$type = $account['type'];
|
||||
$class = "\\app\\lib\\cert\\{$type}";
|
||||
if (class_exists($class)) {
|
||||
$config = json_decode($account['config'], true);
|
||||
$ext = $account['ext'] ? json_decode($account['ext'], true) : null;
|
||||
$model = new $class($config, $ext);
|
||||
return $model;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CertInterface|bool
|
||||
*/
|
||||
public static function getModel2($type, $config, $ext = null)
|
||||
{
|
||||
$class = "\\app\\lib\\cert\\{$type}";
|
||||
if (class_exists($class)) {
|
||||
$model = new $class($config, $ext);
|
||||
return $model;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getPfx($fullchain, $privatekey, $pwd = '123456'){
|
||||
openssl_pkcs12_export($fullchain, $pfx, $privatekey, $pwd);
|
||||
return $pfx;
|
||||
}
|
||||
}
|
||||
<?php
|
||||
|
||||
namespace app\lib;
|
||||
|
||||
use think\facade\Db;
|
||||
|
||||
class CertHelper
|
||||
{
|
||||
public static $cert_config = [
|
||||
'letsencrypt' => [
|
||||
'name' => 'Let\'s Encrypt',
|
||||
'class' => 1,
|
||||
'icon' => 'letsencrypt.ico',
|
||||
'wildcard' => true,
|
||||
'max_domains' => 100,
|
||||
'cname' => true,
|
||||
'note' => null,
|
||||
'inputs' => [
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => '用于注册Let\'s Encrypt账号',
|
||||
'required' => true,
|
||||
],
|
||||
'mode' => [
|
||||
'name' => '环境选择',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'live' => '正式环境',
|
||||
'staging' => '测试环境',
|
||||
],
|
||||
'value' => 'live'
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '否',
|
||||
'1' => '是',
|
||||
],
|
||||
'value' => '0'
|
||||
],
|
||||
]
|
||||
],
|
||||
'zerossl' => [
|
||||
'name' => 'ZeroSSL',
|
||||
'class' => 1,
|
||||
'icon' => 'zerossl.ico',
|
||||
'wildcard' => true,
|
||||
'max_domains' => 100,
|
||||
'cname' => true,
|
||||
'note' => '<a href="https://app.zerossl.com/developer" target="_blank" rel="noreferrer">ZeroSSL密钥手动获取</a>',
|
||||
'inputs' => [
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => 'EAB申请邮箱',
|
||||
'required' => true,
|
||||
],
|
||||
'eabMode' => [
|
||||
'name' => 'EAB获取方式',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'auto' => '自动获取',
|
||||
'manual' => '手动输入',
|
||||
],
|
||||
'value' => 'manual'
|
||||
],
|
||||
'kid' => [
|
||||
'name' => 'EAB KID',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
'show' => 'eabMode==\'manual\'',
|
||||
],
|
||||
'key' => [
|
||||
'name' => 'EAB HMAC Key',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
'show' => 'eabMode==\'manual\'',
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '否',
|
||||
'1' => '是',
|
||||
],
|
||||
'value' => '0'
|
||||
],
|
||||
]
|
||||
],
|
||||
'google' => [
|
||||
'name' => 'Google SSL',
|
||||
'class' => 1,
|
||||
'icon' => 'google.ico',
|
||||
'wildcard' => true,
|
||||
'max_domains' => 100,
|
||||
'cname' => true,
|
||||
'note' => 'EAB支持通过第三方接口<a href="https://panel.haozi.net" target="_blank" rel="noreferrer">(耗子面板提供)</a>自动获取(不支持测试环境)或手动输入,<a href="https://cloud.google.com/certificate-manager/docs/public-ca-tutorial" target="_blank" rel="noreferrer">查看Google SSL账户手动配置说明</a>',
|
||||
'inputs' => [
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => 'EAB申请邮箱',
|
||||
'required' => true,
|
||||
],
|
||||
'eabMode' => [
|
||||
'name' => 'EAB获取方式',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'auto' => '自动获取',
|
||||
'manual' => '手动输入',
|
||||
],
|
||||
'value' => 'manual'
|
||||
],
|
||||
'kid' => [
|
||||
'name' => 'keyId',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
'show' => 'eabMode==\'manual\'',
|
||||
],
|
||||
'key' => [
|
||||
'name' => 'b64MacKey',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
'show' => 'eabMode==\'manual\'',
|
||||
],
|
||||
'mode' => [
|
||||
'name' => '环境选择',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'live' => '正式环境',
|
||||
'staging' => '测试环境',
|
||||
],
|
||||
'value' => 'live'
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '否',
|
||||
'1' => '是',
|
||||
'2' => '是(反向代理)'
|
||||
],
|
||||
'value' => '0'
|
||||
],
|
||||
'proxy_url' => [
|
||||
'name' => '反向代理地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => 'https://gts.rat.dev',
|
||||
'required' => true,
|
||||
'show' => 'proxy==2',
|
||||
'note' => '反向代理配置参考:
|
||||
<pre>resolver 8.8.8.8 ipv6=off valid=300s;
|
||||
resolver_timeout 10s;
|
||||
|
||||
location / {
|
||||
set $empty "";
|
||||
proxy_pass https://dv.acme-v02.api.pki.goog$empty;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
proxy_ssl_session_reuse off;
|
||||
proxy_ssl_server_name on;
|
||||
proxy_ssl_protocols TLSv1.2 TLSv1.3;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
sub_filter_once off;
|
||||
sub_filter_types *;
|
||||
sub_filter \'dv.acme-v02.api.pki.goog\' \'gts.rat.dev\'; # 替换自己的域名
|
||||
}</pre>',
|
||||
],
|
||||
]
|
||||
],
|
||||
'tencent' => [
|
||||
'name' => '腾讯云免费SSL',
|
||||
'class' => 2,
|
||||
'icon' => 'tencent.ico',
|
||||
'wildcard' => false,
|
||||
'max_domains' => 1,
|
||||
'cname' => false,
|
||||
'note' => '一个账号有50张免费证书额度,证书到期或吊销可释放额度。<a href="https://cloud.tencent.com/document/product/400/89868" target="_blank" rel="noreferrer">腾讯云免费SSL简介与额度说明</a>',
|
||||
'inputs' => [
|
||||
'SecretId' => [
|
||||
'name' => 'SecretId',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'SecretKey' => [
|
||||
'name' => 'SecretKey',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请证书时填写的邮箱',
|
||||
'required' => true,
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '否',
|
||||
'1' => '是',
|
||||
],
|
||||
'value' => '0'
|
||||
],
|
||||
]
|
||||
],
|
||||
'aliyun' => [
|
||||
'name' => '阿里云免费SSL',
|
||||
'class' => 2,
|
||||
'icon' => 'aliyun.ico',
|
||||
'wildcard' => false,
|
||||
'max_domains' => 1,
|
||||
'cname' => false,
|
||||
'note' => '每个自然年有20张免费证书额度,证书到期或吊销不释放额度。需要先进入阿里云控制台-<a href="https://yundun.console.aliyun.com/?p=cas#/certExtend/free/cn-hangzhou" target="_blank" rel="noreferrer">数字证书管理服务</a>,购买个人测试证书资源包。',
|
||||
'inputs' => [
|
||||
'AccessKeyId' => [
|
||||
'name' => 'AccessKeyId',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'AccessKeySecret' => [
|
||||
'name' => 'AccessKeySecret',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'username' => [
|
||||
'name' => '姓名',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请联系人的姓名',
|
||||
'required' => true,
|
||||
],
|
||||
'phone' => [
|
||||
'name' => '手机号码',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请联系人的手机号码',
|
||||
'required' => true,
|
||||
],
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请联系人的邮箱地址',
|
||||
'required' => true,
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '否',
|
||||
'1' => '是',
|
||||
],
|
||||
'value' => '0'
|
||||
],
|
||||
]
|
||||
],
|
||||
'ucloud' => [
|
||||
'name' => 'UCloud免费SSL',
|
||||
'class' => 2,
|
||||
'icon' => 'ucloud.ico',
|
||||
'wildcard' => false,
|
||||
'max_domains' => 1,
|
||||
'cname' => false,
|
||||
'note' => '一个账号有40张免费证书额度,证书到期或吊销可释放额度。',
|
||||
'inputs' => [
|
||||
'PublicKey' => [
|
||||
'name' => '公钥',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'PrivateKey' => [
|
||||
'name' => '私钥',
|
||||
'type' => 'input',
|
||||
'placeholder' => '',
|
||||
'required' => true,
|
||||
],
|
||||
'username' => [
|
||||
'name' => '姓名',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请联系人的姓名',
|
||||
'required' => true,
|
||||
],
|
||||
'phone' => [
|
||||
'name' => '手机号码',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请联系人的手机号码',
|
||||
'required' => true,
|
||||
],
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => '申请联系人的邮箱地址',
|
||||
'required' => true,
|
||||
],
|
||||
]
|
||||
],
|
||||
'customacme' => [
|
||||
'name' => '自定义ACME',
|
||||
'class' => 1,
|
||||
'icon' => 'ssl.ico',
|
||||
'wildcard' => true,
|
||||
'max_domains' => 100,
|
||||
'cname' => true,
|
||||
'note' => null,
|
||||
'inputs' => [
|
||||
'directory' => [
|
||||
'name' => 'ACME地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => 'ACME Directory 地址',
|
||||
'required' => true,
|
||||
],
|
||||
'email' => [
|
||||
'name' => '邮箱地址',
|
||||
'type' => 'input',
|
||||
'placeholder' => '证书申请邮箱',
|
||||
'required' => true,
|
||||
],
|
||||
'kid' => [
|
||||
'name' => 'EAB KID',
|
||||
'type' => 'input',
|
||||
'placeholder' => '留空则不使用EAB认证',
|
||||
],
|
||||
'key' => [
|
||||
'name' => 'EAB HMAC Key',
|
||||
'type' => 'input',
|
||||
'placeholder' => '留空则不使用EAB认证',
|
||||
],
|
||||
'proxy' => [
|
||||
'name' => '使用代理服务器',
|
||||
'type' => 'radio',
|
||||
'options' => [
|
||||
'0' => '否',
|
||||
'1' => '是',
|
||||
],
|
||||
'value' => '0'
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
public static $class_config = [
|
||||
1 => '基于ACME的SSL证书',
|
||||
2 => '云服务商的SSL证书',
|
||||
];
|
||||
|
||||
public static function getList()
|
||||
{
|
||||
return self::$cert_config;
|
||||
}
|
||||
|
||||
private static function getConfig($aid)
|
||||
{
|
||||
$account = Db::name('cert_account')->where('id', $aid)->find();
|
||||
if (!$account) return false;
|
||||
return $account;
|
||||
}
|
||||
|
||||
public static function getInputs($type, $config = null)
|
||||
{
|
||||
$config = $config ? json_decode($config, true) : [];
|
||||
$inputs = self::$cert_config[$type]['inputs'];
|
||||
foreach ($inputs as &$input) {
|
||||
if (isset($config[$input['name']])) {
|
||||
$input['value'] = $config[$input['name']];
|
||||
}
|
||||
}
|
||||
return $inputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CertInterface|bool
|
||||
*/
|
||||
public static function getModel($aid)
|
||||
{
|
||||
$account = self::getConfig($aid);
|
||||
if (!$account) return false;
|
||||
$type = $account['type'];
|
||||
$class = "\\app\\lib\\cert\\{$type}";
|
||||
if (class_exists($class)) {
|
||||
$config = json_decode($account['config'], true);
|
||||
$ext = $account['ext'] ? json_decode($account['ext'], true) : null;
|
||||
$model = new $class($config, $ext);
|
||||
return $model;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CertInterface|bool
|
||||
*/
|
||||
public static function getModel2($type, $config, $ext = null)
|
||||
{
|
||||
$class = "\\app\\lib\\cert\\{$type}";
|
||||
if (class_exists($class)) {
|
||||
$model = new $class($config, $ext);
|
||||
return $model;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function getPfx($fullchain, $privatekey, $pwd = '123456')
|
||||
{
|
||||
openssl_pkcs12_export($fullchain, $pfx, $privatekey, $pwd);
|
||||
return $pfx;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -85,6 +85,19 @@ class DnsHelper
|
||||
'weight' => true,
|
||||
'page' => false,
|
||||
],
|
||||
'jdcloud' => [
|
||||
'name' => '京东云',
|
||||
'config' => [
|
||||
'ak' => 'AccessKeyId',
|
||||
'sk' => 'AccessKeySecret',
|
||||
],
|
||||
'remark' => 0,
|
||||
'status' => true,
|
||||
'redirect' => true,
|
||||
'log' => false,
|
||||
'weight' => true,
|
||||
'page' => false,
|
||||
],
|
||||
'dnsla' => [
|
||||
'name' => 'DNSLA',
|
||||
'config' => [
|
||||
@@ -105,7 +118,7 @@ class DnsHelper
|
||||
'sk' => 'API密钥/令牌',
|
||||
],
|
||||
'remark' => 2,
|
||||
'status' => false,
|
||||
'status' => true,
|
||||
'redirect' => false,
|
||||
'log' => false,
|
||||
'weight' => false,
|
||||
@@ -148,8 +161,10 @@ class DnsHelper
|
||||
'dnsla' => ['DEF' => '', 'CT' => '84613316902921216', 'CU' => '84613316923892736', 'CM' => '84613316953252864', 'AB' => ''],
|
||||
'huoshan' => ['DEF' => 'default', 'CT' => 'telecom', 'CU' => 'unicom', 'CM' => 'mobile', 'AB' => 'oversea'],
|
||||
'baidu' => ['DEF' => 'default', 'CT' => 'ct', 'CU' => 'cnc', 'CM' => 'cmnet', 'AB' => ''],
|
||||
'jdcloud' => ['DEF' => '-1', 'CT' => '1', 'CU' => '2', 'CM' => '3', 'AB' => '4'],
|
||||
'cloudflare' => ['DEF' => '0'],
|
||||
'namesilo' => ['DEF' => '0'],
|
||||
'namesilo' => ['DEF' => 'default'],
|
||||
'powerdns' => ['DEF' => 'default'],
|
||||
];
|
||||
|
||||
public static function getList()
|
||||
|
||||
@@ -25,7 +25,7 @@ class ACMECert extends ACMEv2
|
||||
$protected = array(
|
||||
'alg' => 'HS256',
|
||||
'kid' => $eab_kid,
|
||||
'url' => $this->resources['newAccount']
|
||||
'url' => $this->unproxiedURL($this->resources['newAccount'])
|
||||
);
|
||||
$payload = $this->jwk_header['jwk'];
|
||||
|
||||
|
||||
@@ -8,13 +8,22 @@ class ACMEv2
|
||||
{ // Communication with Let's Encrypt via ACME v2 protocol
|
||||
|
||||
protected
|
||||
$ch = null, $logger = true, $bits, $sha_bits, $directory, $resources, $jwk_header, $kid_header, $account_key, $thumbprint, $nonce = null, $proxy;
|
||||
$ch = null, $logger = true, $bits, $sha_bits, $directory, $resources, $jwk_header, $kid_header, $account_key, $thumbprint, $nonce = null, $proxy, $proxy_config = null;
|
||||
private $delay_until = null;
|
||||
|
||||
public function __construct($directory, $proxy = false)
|
||||
{
|
||||
/**
|
||||
* @param $directory string ACME directory URL
|
||||
* @param $proxy int 代理模式,0为不使用代理,1为使用系统代理,2为使用反向代理
|
||||
* @param null $proxy_config array 反向代理配置,proxy参数为2时必填
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($directory, $proxy = 0, $proxy_config = null)
|
||||
{
|
||||
$this->directory = $directory;
|
||||
$this->proxy = $proxy;
|
||||
if ($proxy == 2) {
|
||||
$this->proxy_config = $proxy_config;
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
@@ -190,7 +199,8 @@ class ACMEv2
|
||||
}
|
||||
|
||||
if (!$this->kid_header['kid'] && $type === 'newAccount') {
|
||||
$this->kid_header['kid'] = $ret['headers']['location'];
|
||||
// 反向替换反向代理配置,防止破坏签名
|
||||
$this->kid_header['kid'] = $this->unproxiedURL($ret['headers']['location']);
|
||||
$this->log('AccountID: ' . $this->kid_header['kid']);
|
||||
}
|
||||
|
||||
@@ -218,7 +228,8 @@ class ACMEv2
|
||||
throw new Exception('Resource "' . $type . '" not available.');
|
||||
}
|
||||
|
||||
$protected['url'] = $this->resources[$type];
|
||||
// 反向替换反向代理配置,防止破坏签名
|
||||
$protected['url'] = $this->unproxiedURL($this->resources[$type]);
|
||||
|
||||
$protected64 = $this->base64url(json_encode($protected, JSON_UNESCAPED_SLASHES));
|
||||
$payload64 = $this->base64url(is_string($payload) ? $payload : json_encode($payload, JSON_UNESCAPED_SLASHES));
|
||||
@@ -285,6 +296,9 @@ class ACMEv2
|
||||
$this->delay_until = null;
|
||||
}
|
||||
|
||||
// 替换反向代理配置
|
||||
$url = $this->proxiedURL($url);
|
||||
|
||||
$method = $data === false ? 'HEAD' : ($data === null ? 'GET' : 'POST');
|
||||
$user_agent = 'ACMECert v3.4.0 (+https://github.com/skoerfgen/ACMECert)';
|
||||
$header = ($data === null || $data === false) ? array() : array('Content-Type: application/jose+json');
|
||||
@@ -406,4 +420,30 @@ class ACMEv2
|
||||
}, isset($error['subproblems']) ? $error['subproblems'] : array())
|
||||
);
|
||||
}
|
||||
|
||||
// 替换反向代理配置
|
||||
protected function proxiedURL($url)
|
||||
{
|
||||
if ($this->proxy == 2) {
|
||||
return str_replace(
|
||||
$this->proxy_config['origin'],
|
||||
$this->proxy_config['proxy'],
|
||||
$url
|
||||
);
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
// 反向替换反向代理配置
|
||||
protected function unproxiedURL($url)
|
||||
{
|
||||
if ($this->proxy == 2) {
|
||||
return str_replace(
|
||||
$this->proxy_config['proxy'],
|
||||
$this->proxy_config['origin'],
|
||||
$url
|
||||
);
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class customacme implements CertInterface
|
||||
public function __construct($config, $ext = null)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->ac = new ACMECert($config['directory'], $config['proxy'] == 1);
|
||||
$this->ac = new ACMECert($config['directory'], (int)$config['proxy']);
|
||||
if ($ext) {
|
||||
$this->ext = $ext;
|
||||
$this->ac->loadAccountKey($ext['key']);
|
||||
|
||||
@@ -1,118 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace app\lib\cert;
|
||||
|
||||
use app\lib\CertInterface;
|
||||
use app\lib\acme\ACMECert;
|
||||
use Exception;
|
||||
|
||||
class google implements CertInterface
|
||||
{
|
||||
private $directories = array(
|
||||
'live' => 'https://dv.acme-v02.api.pki.goog/directory',
|
||||
'staging' => 'https://dv.acme-v02.test-api.pki.goog/directory'
|
||||
);
|
||||
private $ac;
|
||||
private $config;
|
||||
private $ext;
|
||||
|
||||
public function __construct($config, $ext = null)
|
||||
{
|
||||
$this->config = $config;
|
||||
if (empty($config['mode'])) $config['mode'] = 'live';
|
||||
$this->ac = new ACMECert($this->directories[$config['mode']], $config['proxy']==1);
|
||||
if ($ext) {
|
||||
$this->ext = $ext;
|
||||
$this->ac->loadAccountKey($ext['key']);
|
||||
$this->ac->setAccount($ext['kid']);
|
||||
}
|
||||
}
|
||||
|
||||
public function register()
|
||||
{
|
||||
if (empty($this->config['email'])) throw new Exception('邮件地址不能为空');
|
||||
if (empty($this->config['kid']) || empty($this->config['key'])) throw new Exception('必填参数不能为空');
|
||||
|
||||
if (!empty($this->ext['key'])) {
|
||||
$kid = $this->ac->registerEAB(true, $this->config['kid'], $this->config['key'], $this->config['email']);
|
||||
return ['kid' => $kid, 'key' => $this->ext['key']];
|
||||
}
|
||||
|
||||
$key = $this->ac->generateRSAKey(2048);
|
||||
$this->ac->loadAccountKey($key);
|
||||
$kid = $this->ac->registerEAB(true, $this->config['kid'], $this->config['key'], $this->config['email']);
|
||||
return ['kid' => $kid, 'key' => $key];
|
||||
}
|
||||
|
||||
public function buyCert($domainList, &$order)
|
||||
{
|
||||
}
|
||||
|
||||
public function createOrder($domainList, &$order, $keytype, $keysize)
|
||||
{
|
||||
$domain_config = [];
|
||||
foreach ($domainList as $domain) {
|
||||
if (empty($domain)) continue;
|
||||
$domain_config[$domain] = ['challenge' => 'dns-01'];
|
||||
}
|
||||
if (empty($domain_config)) throw new Exception('域名列表不能为空');
|
||||
|
||||
$order = $this->ac->createOrder($domain_config);
|
||||
|
||||
$dnsList = [];
|
||||
if (!empty($order['challenges'])) {
|
||||
foreach ($order['challenges'] as $opts) {
|
||||
$mainDomain = getMainDomain($opts['domain']);
|
||||
$name = str_replace('.' . $mainDomain, '', $opts['key']);
|
||||
/*if (!array_key_exists($mainDomain, $dnsList)) {
|
||||
$dnsList[$mainDomain][] = ['name' => '@', 'type' => 'CAA', 'value' => '0 issue "pki.goog"'];
|
||||
}*/
|
||||
$dnsList[$mainDomain][] = ['name' => $name, 'type' => 'TXT', 'value' => $opts['value']];
|
||||
}
|
||||
}
|
||||
|
||||
return $dnsList;
|
||||
}
|
||||
|
||||
public function authOrder($domainList, $order)
|
||||
{
|
||||
$this->ac->authOrder($order);
|
||||
}
|
||||
|
||||
public function getAuthStatus($domainList, $order)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function finalizeOrder($domainList, $order, $keytype, $keysize)
|
||||
{
|
||||
if (empty($domainList)) throw new Exception('域名列表不能为空');
|
||||
|
||||
if ($keytype == 'ECC') {
|
||||
if (empty($keysize)) $keysize = '384';
|
||||
$private_key = $this->ac->generateECKey($keysize);
|
||||
} else {
|
||||
if (empty($keysize)) $keysize = '2048';
|
||||
$private_key = $this->ac->generateRSAKey($keysize);
|
||||
}
|
||||
$fullchain = $this->ac->finalizeOrder($domainList, $order, $private_key);
|
||||
|
||||
$certInfo = openssl_x509_parse($fullchain, true);
|
||||
if (!$certInfo) throw new Exception('证书解析失败');
|
||||
return ['private_key' => $private_key, 'fullchain' => $fullchain, 'issuer' => $certInfo['issuer']['CN'], 'subject' => $certInfo['subject']['CN'], 'validFrom' => $certInfo['validFrom_time_t'], 'validTo' => $certInfo['validTo_time_t']];
|
||||
}
|
||||
|
||||
public function revoke($order, $pem)
|
||||
{
|
||||
$this->ac->revoke($pem);
|
||||
}
|
||||
|
||||
public function cancel($order)
|
||||
{
|
||||
}
|
||||
|
||||
public function setLogger($func)
|
||||
{
|
||||
$this->ac->setLogger($func);
|
||||
}
|
||||
}
|
||||
<?php
|
||||
|
||||
namespace app\lib\cert;
|
||||
|
||||
use app\lib\CertInterface;
|
||||
use app\lib\acme\ACMECert;
|
||||
use Exception;
|
||||
|
||||
class google implements CertInterface
|
||||
{
|
||||
private $directories = array(
|
||||
'live' => 'https://dv.acme-v02.api.pki.goog',
|
||||
'staging' => 'https://dv.acme-v02.test-api.pki.goog'
|
||||
);
|
||||
private $ac;
|
||||
private $config;
|
||||
private $ext;
|
||||
|
||||
public function __construct($config, $ext = null)
|
||||
{
|
||||
$this->config = $config;
|
||||
if (empty($config['mode'])) $config['mode'] = 'live';
|
||||
if (empty($config['proxy_url'])) $config['proxy_url'] = '';
|
||||
$this->ac = new ACMECert($this->directories[$config['mode']] . '/directory', (int)$config['proxy'], [
|
||||
'origin' => $this->directories[$config['mode']],
|
||||
'proxy' => rtrim($config['proxy_url'], '/'),
|
||||
]);
|
||||
if ($ext) {
|
||||
$this->ext = $ext;
|
||||
$this->ac->loadAccountKey($ext['key']);
|
||||
$this->ac->setAccount($ext['kid']);
|
||||
}
|
||||
}
|
||||
|
||||
public function register()
|
||||
{
|
||||
if (empty($this->config['email'])) throw new Exception('邮件地址不能为空');
|
||||
|
||||
if (isset($this->config['eabMode']) && $this->config['eabMode'] == 'auto') {
|
||||
$eab = $this->getEAB();
|
||||
} else {
|
||||
$eab = ['kid' => $this->config['kid'], 'key' => $this->config['key']];
|
||||
}
|
||||
|
||||
if (!empty($this->ext['key'])) {
|
||||
$kid = $this->ac->registerEAB(true, $eab['kid'], $eab['key'], $this->config['email']);
|
||||
return ['kid' => $kid, 'key' => $this->ext['key']];
|
||||
}
|
||||
|
||||
$key = $this->ac->generateRSAKey(2048);
|
||||
$this->ac->loadAccountKey($key);
|
||||
$kid = $this->ac->registerEAB(true, $eab['kid'], $eab['key'], $this->config['email']);
|
||||
return ['kid' => $kid, 'key' => $key];
|
||||
}
|
||||
|
||||
public function buyCert($domainList, &$order)
|
||||
{
|
||||
}
|
||||
|
||||
public function createOrder($domainList, &$order, $keytype, $keysize)
|
||||
{
|
||||
$domain_config = [];
|
||||
foreach ($domainList as $domain) {
|
||||
if (empty($domain)) continue;
|
||||
$domain_config[$domain] = ['challenge' => 'dns-01'];
|
||||
}
|
||||
if (empty($domain_config)) throw new Exception('域名列表不能为空');
|
||||
|
||||
$order = $this->ac->createOrder($domain_config);
|
||||
|
||||
$dnsList = [];
|
||||
if (!empty($order['challenges'])) {
|
||||
foreach ($order['challenges'] as $opts) {
|
||||
$mainDomain = getMainDomain($opts['domain']);
|
||||
$name = str_replace('.' . $mainDomain, '', $opts['key']);
|
||||
/*if (!array_key_exists($mainDomain, $dnsList)) {
|
||||
$dnsList[$mainDomain][] = ['name' => '@', 'type' => 'CAA', 'value' => '0 issue "pki.goog"'];
|
||||
}*/
|
||||
$dnsList[$mainDomain][] = ['name' => $name, 'type' => 'TXT', 'value' => $opts['value']];
|
||||
}
|
||||
}
|
||||
|
||||
return $dnsList;
|
||||
}
|
||||
|
||||
public function authOrder($domainList, $order)
|
||||
{
|
||||
$this->ac->authOrder($order);
|
||||
}
|
||||
|
||||
public function getAuthStatus($domainList, $order)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function finalizeOrder($domainList, $order, $keytype, $keysize)
|
||||
{
|
||||
if (empty($domainList)) throw new Exception('域名列表不能为空');
|
||||
|
||||
if ($keytype == 'ECC') {
|
||||
if (empty($keysize)) $keysize = '384';
|
||||
$private_key = $this->ac->generateECKey($keysize);
|
||||
} else {
|
||||
if (empty($keysize)) $keysize = '2048';
|
||||
$private_key = $this->ac->generateRSAKey($keysize);
|
||||
}
|
||||
$fullchain = $this->ac->finalizeOrder($domainList, $order, $private_key);
|
||||
|
||||
$certInfo = openssl_x509_parse($fullchain, true);
|
||||
if (!$certInfo) throw new Exception('证书解析失败');
|
||||
return ['private_key' => $private_key, 'fullchain' => $fullchain, 'issuer' => $certInfo['issuer']['CN'], 'subject' => $certInfo['subject']['CN'], 'validFrom' => $certInfo['validFrom_time_t'], 'validTo' => $certInfo['validTo_time_t']];
|
||||
}
|
||||
|
||||
public function revoke($order, $pem)
|
||||
{
|
||||
$this->ac->revoke($pem);
|
||||
}
|
||||
|
||||
public function cancel($order)
|
||||
{
|
||||
}
|
||||
|
||||
public function setLogger($func)
|
||||
{
|
||||
$this->ac->setLogger($func);
|
||||
}
|
||||
|
||||
private function getEAB()
|
||||
{
|
||||
$api = "https://gts.rat.dev/eab";
|
||||
$response = curl_client($api, null, null, null, null, $this->config['proxy'] == 1, 'GET', 10);
|
||||
$result = json_decode($response['body'], true);
|
||||
if (!isset($result['msg'])) {
|
||||
throw new Exception('解析返回数据失败:' . $response['body']);
|
||||
} elseif ($result['msg'] != 'success') {
|
||||
throw new Exception('获取EAB失败:' . $result['msg']);
|
||||
} elseif (empty($result['data']['key_id']) || empty($result['data']['mac_key'])) {
|
||||
throw new Exception('获取EAB失败:返回数据不完整');
|
||||
}
|
||||
return ['kid' => $result['data']['key_id'], 'key' => $result['data']['mac_key']];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ class letsencrypt implements CertInterface
|
||||
{
|
||||
$this->config = $config;
|
||||
if (empty($config['mode'])) $config['mode'] = 'live';
|
||||
$this->ac = new ACMECert($this->directories[$config['mode']], $config['proxy'] == 1);
|
||||
$this->ac = new ACMECert($this->directories[$config['mode']], (int)$config['proxy']);
|
||||
if ($ext) {
|
||||
$this->ext = $ext;
|
||||
$this->ac->loadAccountKey($ext['key']);
|
||||
|
||||
@@ -15,14 +15,15 @@ class tencent implements CertInterface
|
||||
private $service = "ssl";
|
||||
private $version = "2019-12-05";
|
||||
private $logger;
|
||||
private $proxy;
|
||||
private TencentCloud $client;
|
||||
|
||||
public function __construct($config, $ext = null)
|
||||
{
|
||||
$this->SecretId = $config['SecretId'];
|
||||
$this->SecretKey = $config['SecretKey'];
|
||||
$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->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
|
||||
$this->client = new TencentCloud($this->SecretId, $this->SecretKey, $this->endpoint, $this->service, $this->version, null, $this->proxy);
|
||||
$this->email = $config['email'];
|
||||
}
|
||||
|
||||
@@ -102,7 +103,9 @@ class tencent implements CertInterface
|
||||
'ServiceType' => 'nginx',
|
||||
];
|
||||
$data = $this->request('DescribeDownloadCertificateUrl', $param);
|
||||
$file_data = get_curl($data['DownloadCertificateUrl']);
|
||||
$file_data = curl_client($data['DownloadCertificateUrl'], null, null, null, null, $this->proxy);
|
||||
$file_data = $file_data['body'] ?? null;
|
||||
if (empty($file_data)) throw new Exception('下载证书失败');
|
||||
$file_path = app()->getRuntimePath() . 'cert/' . $data['DownloadFilename'];
|
||||
$file_name = substr($data['DownloadFilename'], 0, -4);
|
||||
file_put_contents($file_path, $file_data);
|
||||
|
||||
@@ -16,7 +16,7 @@ class zerossl implements CertInterface
|
||||
public function __construct($config, $ext = null)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->ac = new ACMECert($this->directory, $config['proxy'] == 1);
|
||||
$this->ac = new ACMECert($this->directory, (int)$config['proxy']);
|
||||
if ($ext) {
|
||||
$this->ext = $ext;
|
||||
$this->ac->loadAccountKey($ext['key']);
|
||||
@@ -27,20 +27,27 @@ class zerossl implements CertInterface
|
||||
public function register()
|
||||
{
|
||||
if (empty($this->config['email'])) throw new Exception('邮件地址不能为空');
|
||||
if (empty($this->config['kid']) || empty($this->config['key'])) throw new Exception('必填参数不能为空');
|
||||
|
||||
if (isset($this->config['eabMode']) && $this->config['eabMode'] == 'auto') {
|
||||
$eab = $this->getEAB($this->config['email']);
|
||||
} else {
|
||||
$eab = ['kid' => $this->config['kid'], 'key' => $this->config['key']];
|
||||
}
|
||||
|
||||
if (!empty($this->ext['key'])) {
|
||||
$kid = $this->ac->registerEAB(true, $this->config['kid'], $this->config['key'], $this->config['email']);
|
||||
$kid = $this->ac->registerEAB(true, $eab['kid'], $eab['key'], $this->config['email']);
|
||||
return ['kid' => $kid, 'key' => $this->ext['key']];
|
||||
}
|
||||
|
||||
$key = $this->ac->generateRSAKey(2048);
|
||||
$this->ac->loadAccountKey($key);
|
||||
$kid = $this->ac->registerEAB(true, $this->config['kid'], $this->config['key'], $this->config['email']);
|
||||
$kid = $this->ac->registerEAB(true, $eab['kid'], $eab['key'], $this->config['email']);
|
||||
return ['kid' => $kid, 'key' => $key];
|
||||
}
|
||||
|
||||
public function buyCert($domainList, &$order) {}
|
||||
public function buyCert($domainList, &$order)
|
||||
{
|
||||
}
|
||||
|
||||
public function createOrder($domainList, &$order, $keytype, $keysize)
|
||||
{
|
||||
@@ -101,10 +108,27 @@ class zerossl implements CertInterface
|
||||
$this->ac->revoke($pem);
|
||||
}
|
||||
|
||||
public function cancel($order) {}
|
||||
public function cancel($order)
|
||||
{
|
||||
}
|
||||
|
||||
public function setLogger($func)
|
||||
{
|
||||
$this->ac->setLogger($func);
|
||||
}
|
||||
|
||||
private function getEAB($email)
|
||||
{
|
||||
$api = "https://api.zerossl.com/acme/eab-credentials-email";
|
||||
$response = curl_client($api, http_build_query(['email' => $email]), null, null, null, $this->config['proxy'] == 1);
|
||||
$result = json_decode($response['body'], true);
|
||||
if (!isset($result['success'])) {
|
||||
throw new Exception('获取EAB失败:' . $response['body']);
|
||||
} elseif (!$result['success'] && isset($result['error'])) {
|
||||
throw new Exception('获取EAB失败:' . $result['error']['code'] . ' - ' . $result['error']['type']);
|
||||
} elseif (!isset($result['eab_kid']) || !isset($result['eab_hmac_key'])) {
|
||||
throw new Exception('获取EAB失败:返回数据不完整');
|
||||
}
|
||||
return ['kid' => $result['eab_kid'], 'key' => $result['eab_hmac_key']];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ class AWS
|
||||
|
||||
$path = '/' . $this->version . $path;
|
||||
$body = '';
|
||||
$query = [];
|
||||
if ($method == 'GET' || $method == 'DELETE') {
|
||||
$query = $params;
|
||||
} else {
|
||||
@@ -284,8 +285,9 @@ class AWS
|
||||
$response = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
if ($errno) {
|
||||
$errmsg = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception('Curl error: ' . curl_error($ch));
|
||||
throw new Exception('Curl error: ' . $errmsg);
|
||||
}
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if ($etag) {
|
||||
@@ -327,16 +329,31 @@ class AWS
|
||||
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA), JSON_UNESCAPED_UNICODE), true);
|
||||
}
|
||||
|
||||
private function array2xml($array, $xml = null)
|
||||
private function array2xml($array, $xml = null, $parentTagName = 'root')
|
||||
{
|
||||
if ($xml === null) {
|
||||
$xml = new \SimpleXMLElement('<root/>');
|
||||
}
|
||||
|
||||
foreach ($array as $key => $value) {
|
||||
// 确定当前标签名:如果是数字键名,使用父级标签名,否则使用当前键名
|
||||
$tagName = is_numeric($key) ? $parentTagName : $key;
|
||||
|
||||
if (is_array($value)) {
|
||||
$subNode = $xml->addChild($key);
|
||||
$this->array2xml($value, $subNode);
|
||||
// 检查数组的第一个子节点的键是否为0
|
||||
$firstKey = array_key_first($value);
|
||||
$isFirstKeyZero = ($firstKey === 0 || $firstKey === '0');
|
||||
|
||||
if ($isFirstKeyZero) {
|
||||
// 如果第一个子节点的键是0,则不生成当前节点标签,直接递归子节点
|
||||
$this->array2xml($value, $xml, $tagName);
|
||||
|
||||
} else {
|
||||
// 否则生成当前节点标签,并递归子节点
|
||||
$subNode = $xml->addChild($tagName);
|
||||
$this->array2xml($value, $subNode, $tagName);
|
||||
}
|
||||
|
||||
} else {
|
||||
$xml->addChild($key, $value);
|
||||
}
|
||||
|
||||
@@ -62,8 +62,9 @@ class Aliyun
|
||||
$errno = curl_errno($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if ($errno) {
|
||||
$errmsg = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception('Curl error: ' . curl_error($ch));
|
||||
throw new Exception('Curl error: ' . $errmsg);
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
|
||||
@@ -34,13 +34,15 @@ class AliyunNew
|
||||
public function request($method, $action, $path = '/', $params = null)
|
||||
{
|
||||
if (!empty($params)) {
|
||||
$params = array_filter($params, function ($a) { return $a !== null;});
|
||||
$params = array_filter($params, function ($a) {
|
||||
return $a !== null;
|
||||
});
|
||||
}
|
||||
|
||||
if($method == 'GET' || $method == 'DELETE'){
|
||||
if ($method == 'GET' || $method == 'DELETE') {
|
||||
$query = $params;
|
||||
$body = '';
|
||||
}else{
|
||||
} else {
|
||||
$query = [];
|
||||
$body = !empty($params) ? json_encode($params) : '';
|
||||
}
|
||||
@@ -59,13 +61,13 @@ class AliyunNew
|
||||
$authorization = $this->generateSign($method, $path, $query, $headers, $body);
|
||||
$headers['Authorization'] = $authorization;
|
||||
|
||||
$url = 'https://'.$this->Endpoint.$path;
|
||||
$url = 'https://' . $this->Endpoint . $path;
|
||||
if (!empty($query)) {
|
||||
$url .= '?'.http_build_query($query);
|
||||
$url .= '?' . http_build_query($query);
|
||||
}
|
||||
$header = [];
|
||||
foreach ($headers as $key => $value) {
|
||||
$header[] = $key.': '.$value;
|
||||
$header[] = $key . ': ' . $value;
|
||||
}
|
||||
return $this->curl($method, $url, $body, $header);
|
||||
}
|
||||
@@ -80,17 +82,17 @@ class AliyunNew
|
||||
$canonicalQueryString = $this->getCanonicalQueryString($query);
|
||||
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
|
||||
$hashedRequestPayload = hash("sha256", $body);
|
||||
$canonicalRequest = $httpRequestMethod."\n"
|
||||
.$canonicalUri."\n"
|
||||
.$canonicalQueryString."\n"
|
||||
.$canonicalHeaders."\n"
|
||||
.$signedHeaders."\n"
|
||||
.$hashedRequestPayload;
|
||||
$canonicalRequest = $httpRequestMethod . "\n"
|
||||
. $canonicalUri . "\n"
|
||||
. $canonicalQueryString . "\n"
|
||||
. $canonicalHeaders . "\n"
|
||||
. $signedHeaders . "\n"
|
||||
. $hashedRequestPayload;
|
||||
|
||||
// step 2: build string to sign
|
||||
$hashedCanonicalRequest = hash("sha256", $canonicalRequest);
|
||||
$stringToSign = $algorithm."\n"
|
||||
.$hashedCanonicalRequest;
|
||||
$stringToSign = $algorithm . "\n"
|
||||
. $hashedCanonicalRequest;
|
||||
|
||||
// step 3: sign string
|
||||
$signature = hash_hmac("sha256", $stringToSign, $this->AccessKeySecret);
|
||||
@@ -125,7 +127,7 @@ class AliyunNew
|
||||
ksort($parameters);
|
||||
$canonicalQueryString = '';
|
||||
foreach ($parameters as $key => $value) {
|
||||
$canonicalQueryString .= '&' . $this->escape($key). '=' . $this->escape($value);
|
||||
$canonicalQueryString .= '&' . $this->escape($key) . '=' . $this->escape($value);
|
||||
}
|
||||
return substr($canonicalQueryString, 1);
|
||||
}
|
||||
@@ -166,8 +168,9 @@ class AliyunNew
|
||||
$response = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
if ($errno) {
|
||||
$errmsg = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception('Curl error: ' . curl_error($ch));
|
||||
throw new Exception('Curl error: ' . $errmsg);
|
||||
}
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
@@ -176,10 +179,10 @@ class AliyunNew
|
||||
if ($httpCode == 200) {
|
||||
return $arr;
|
||||
} elseif ($arr) {
|
||||
if(strpos($arr['Message'], '.') > 0) $arr['Message'] = substr($arr['Message'], 0, strpos($arr['Message'], '.')+1);
|
||||
if (strpos($arr['Message'], '.') > 0) $arr['Message'] = substr($arr['Message'], 0, strpos($arr['Message'], '.') + 1);
|
||||
throw new Exception($arr['Message']);
|
||||
} else {
|
||||
throw new Exception('返回数据解析失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +119,9 @@ class AliyunOSS
|
||||
$errno = curl_errno($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if ($errno) {
|
||||
$errmsg = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception('Curl error: ' . curl_error($ch));
|
||||
throw new Exception('Curl error: ' . $errmsg);
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
|
||||
@@ -158,8 +158,9 @@ class BaiduCloud
|
||||
$errno = curl_errno($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if ($errno) {
|
||||
$errmsg = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception('Curl error: ' . curl_error($ch));
|
||||
throw new Exception('Curl error: ' . $errmsg);
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
|
||||
@@ -144,8 +144,9 @@ class Ctyun
|
||||
$response = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
if ($errno) {
|
||||
$errmsg = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception('Curl error: ' . curl_error($ch));
|
||||
throw new Exception('Curl error: ' . $errmsg);
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
|
||||
@@ -163,8 +163,9 @@ class HuaweiCloud
|
||||
$response = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
if ($errno) {
|
||||
$errmsg = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception('Curl error: ' . curl_error($ch));
|
||||
throw new Exception('Curl error: ' . $errmsg);
|
||||
}
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
191
app/lib/client/Jdcloud.php
Normal file
191
app/lib/client/Jdcloud.php
Normal file
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace app\lib\client;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* 京东云
|
||||
*/
|
||||
class Jdcloud
|
||||
{
|
||||
private static $algorithm = 'JDCLOUD2-HMAC-SHA256';
|
||||
private $AccessKeyId;
|
||||
private $AccessKeySecret;
|
||||
private $endpoint;
|
||||
private $service;
|
||||
private $region;
|
||||
private $proxy = false;
|
||||
|
||||
public function __construct($AccessKeyId, $AccessKeySecret, $endpoint, $service, $region, $proxy = false)
|
||||
{
|
||||
$this->AccessKeyId = $AccessKeyId;
|
||||
$this->AccessKeySecret = $AccessKeySecret;
|
||||
$this->endpoint = $endpoint;
|
||||
$this->service = $service;
|
||||
$this->region = $region;
|
||||
$this->proxy = $proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method 请求方法
|
||||
* @param string $path 请求路径
|
||||
* @param array $params 请求参数
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function request($method, $path, $params = [])
|
||||
{
|
||||
if (!empty($params)) {
|
||||
$params = array_filter($params, function ($a) {
|
||||
return $a !== null;
|
||||
});
|
||||
}
|
||||
|
||||
if ($method == 'GET' || $method == 'DELETE') {
|
||||
$query = $params;
|
||||
$body = '';
|
||||
} else {
|
||||
$query = [];
|
||||
$body = !empty($params) ? json_encode($params) : '';
|
||||
}
|
||||
|
||||
$date = gmdate("Ymd\THis\Z");
|
||||
$headers = [
|
||||
'Host' => $this->endpoint,
|
||||
'x-jdcloud-algorithm' => self::$algorithm,
|
||||
'x-jdcloud-date' => $date,
|
||||
'x-jdcloud-nonce' => uniqid('php', true),
|
||||
];
|
||||
if ($body) {
|
||||
$headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
$authorization = $this->generateSign($method, $path, $query, $headers, $body, $date);
|
||||
$headers['authorization'] = $authorization;
|
||||
|
||||
$url = 'https://' . $this->endpoint . $path;
|
||||
if (!empty($query)) {
|
||||
$url .= '?' . http_build_query($query);
|
||||
}
|
||||
$header = [];
|
||||
foreach ($headers as $key => $value) {
|
||||
$header[] = $key . ': ' . $value;
|
||||
}
|
||||
return $this->curl($method, $url, $body, $header);
|
||||
}
|
||||
|
||||
private function generateSign($method, $path, $query, $headers, $body, $date)
|
||||
{
|
||||
// step 1: build canonical request string
|
||||
$httpRequestMethod = $method;
|
||||
$canonicalUri = $path;
|
||||
$canonicalQueryString = $this->getCanonicalQueryString($query);
|
||||
[$canonicalHeaders, $signedHeaders] = $this->getCanonicalHeaders($headers);
|
||||
$hashedRequestPayload = hash("sha256", $body);
|
||||
$canonicalRequest = $httpRequestMethod . "\n"
|
||||
. $canonicalUri . "\n"
|
||||
. $canonicalQueryString . "\n"
|
||||
. $canonicalHeaders . "\n"
|
||||
. $signedHeaders . "\n"
|
||||
. $hashedRequestPayload;
|
||||
|
||||
// step 2: build string to sign
|
||||
$shortDate = substr($date, 0, 8);
|
||||
$credentialScope = $shortDate . '/' . $this->region . '/' . $this->service . '/jdcloud2_request';
|
||||
$hashedCanonicalRequest = hash("sha256", $canonicalRequest);
|
||||
$stringToSign = self::$algorithm . "\n"
|
||||
. $date . "\n"
|
||||
. $credentialScope . "\n"
|
||||
. $hashedCanonicalRequest;
|
||||
|
||||
// step 3: sign string
|
||||
$kDate = hash_hmac("sha256", $shortDate, 'JDCLOUD2' . $this->AccessKeySecret, true);
|
||||
$kRegion = hash_hmac("sha256", $this->region, $kDate, true);
|
||||
$kService = hash_hmac("sha256", $this->service, $kRegion, true);
|
||||
$kSigning = hash_hmac("sha256", "jdcloud2_request", $kService, true);
|
||||
$signature = hash_hmac("sha256", $stringToSign, $kSigning);
|
||||
|
||||
// step 4: build authorization
|
||||
$credential = $this->AccessKeyId . '/' . $credentialScope;
|
||||
$authorization = self::$algorithm . ' Credential=' . $credential . ", SignedHeaders=" . $signedHeaders . ", Signature=" . $signature;
|
||||
|
||||
return $authorization;
|
||||
}
|
||||
|
||||
private function escape($str)
|
||||
{
|
||||
$search = ['+', '*', '%7E'];
|
||||
$replace = ['%20', '%2A', '~'];
|
||||
return str_replace($search, $replace, urlencode($str));
|
||||
}
|
||||
|
||||
private function getCanonicalQueryString($parameters)
|
||||
{
|
||||
if (empty($parameters)) return '';
|
||||
ksort($parameters);
|
||||
$canonicalQueryString = '';
|
||||
foreach ($parameters as $key => $value) {
|
||||
$canonicalQueryString .= '&' . $this->escape($key) . '=' . $this->escape($value);
|
||||
}
|
||||
return substr($canonicalQueryString, 1);
|
||||
}
|
||||
|
||||
private function getCanonicalHeaders($oldheaders)
|
||||
{
|
||||
$headers = array();
|
||||
foreach ($oldheaders as $key => $value) {
|
||||
$headers[strtolower($key)] = trim($value);
|
||||
}
|
||||
ksort($headers);
|
||||
|
||||
$canonicalHeaders = '';
|
||||
$signedHeaders = '';
|
||||
foreach ($headers as $key => $value) {
|
||||
$canonicalHeaders .= $key . ':' . $value . "\n";
|
||||
$signedHeaders .= $key . ';';
|
||||
}
|
||||
$signedHeaders = substr($signedHeaders, 0, -1);
|
||||
return [$canonicalHeaders, $signedHeaders];
|
||||
}
|
||||
|
||||
private function curl($method, $url, $body, $header)
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
if ($this->proxy) {
|
||||
curl_set_proxy($ch);
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
if (!empty($body)) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
}
|
||||
$response = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
if ($errno) {
|
||||
$errmsg = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception('Curl error: ' . $errmsg);
|
||||
}
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
$arr = json_decode($response, true);
|
||||
if ($httpCode == 200) {
|
||||
if (isset($arr['result'])) {
|
||||
return $arr['result'];
|
||||
}
|
||||
return $arr;
|
||||
} else {
|
||||
if (isset($arr['error']['message'])) {
|
||||
throw new Exception($arr['error']['message']);
|
||||
} else {
|
||||
throw new Exception('返回数据解析失败(http_code=' . $httpCode . ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,8 +121,9 @@ class Qiniu
|
||||
$errno = curl_errno($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if ($errno) {
|
||||
$errmsg = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception('Curl error: ' . curl_error($ch));
|
||||
throw new Exception('Curl error: ' . $errmsg);
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
|
||||
@@ -113,8 +113,9 @@ class TencentCloud
|
||||
$response = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
if ($errno) {
|
||||
$errmsg = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception('Curl error: ' . curl_error($ch));
|
||||
throw new Exception('Curl error: ' . $errmsg);
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
|
||||
@@ -218,15 +218,20 @@ class Volcengine
|
||||
$response = curl_exec($ch);
|
||||
$errno = curl_errno($ch);
|
||||
if ($errno) {
|
||||
$errmsg = curl_error($ch);
|
||||
curl_close($ch);
|
||||
throw new Exception('Curl error: ' . curl_error($ch));
|
||||
throw new Exception('Curl error: ' . $errmsg);
|
||||
}
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
$arr = json_decode($response, true);
|
||||
if ($httpCode == 200) {
|
||||
if (isset($arr['Result'])) {
|
||||
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'];
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -85,7 +85,7 @@ class aliyun implements DeployInterface
|
||||
$param = [
|
||||
'Action' => 'ListUserCertificateOrder',
|
||||
'Keyword' => $certInfo['subject']['CN'],
|
||||
'OrderType' => 'UPLOAD',
|
||||
'OrderType' => 'CERT',
|
||||
];
|
||||
try {
|
||||
$data = $client->request($param);
|
||||
@@ -190,6 +190,7 @@ class aliyun implements DeployInterface
|
||||
|
||||
$cert_id = null;
|
||||
$cert_name = null;
|
||||
$casid = null;
|
||||
foreach ($data['Result'] as $cert) {
|
||||
$domains = explode(',', $cert['SAN']);
|
||||
$flag = true;
|
||||
@@ -202,6 +203,7 @@ class aliyun implements DeployInterface
|
||||
if ($flag) {
|
||||
$cert_id = $cert['Id'];
|
||||
$cert_name = $cert['CommonName'];
|
||||
$casid = $cert['CasId'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -215,6 +217,10 @@ class aliyun implements DeployInterface
|
||||
if ($cert_id) {
|
||||
$param['Update'] = 'true';
|
||||
$param['Id'] = $cert_id;
|
||||
if ($casid == $cas_id) {
|
||||
$this->log('ESA站点 ' . $sitename . ' 证书已配置,无需重复操作');
|
||||
return;
|
||||
}
|
||||
}
|
||||
$client->request($param);
|
||||
if ($cert_id) {
|
||||
@@ -230,7 +236,7 @@ class aliyun implements DeployInterface
|
||||
if (empty($config['oss_endpoint'])) throw new Exception('OSS Endpoint不能为空');
|
||||
if (empty($config['oss_bucket'])) throw new Exception('OSS Bucket不能为空');
|
||||
$client = new AliyunOSS($this->AccessKeyId, $this->AccessKeySecret, $config['oss_endpoint']);
|
||||
$client->addBucketCnameCert($config['oss_bucket'], $config['domain'], $cert_id);
|
||||
$client->addBucketCnameCert($config['oss_bucket'], $config['domain'], $cert_id . '-cn-hangzhou');
|
||||
$this->log('OSS域名 ' . $config['domain'] . ' 部署证书成功!');
|
||||
}
|
||||
|
||||
|
||||
@@ -29,22 +29,24 @@ class aws implements DeployInterface
|
||||
}
|
||||
|
||||
public function deploy($fullchain, $privatekey, $config, &$info)
|
||||
{
|
||||
if ($config['product'] == 'acm') {
|
||||
if (empty($config['acm_arn'])) throw new Exception('ACM ARN不能为空');
|
||||
$this->get_cert_id($fullchain, $privatekey, $config['acm_arn'], true);
|
||||
} else {
|
||||
$this->deploy_cloudfront($fullchain, $privatekey, $config, $info);
|
||||
}
|
||||
}
|
||||
|
||||
private function deploy_cloudfront($fullchain, $privatekey, $config, &$info)
|
||||
{
|
||||
if (empty($config['distribution_id'])) throw new Exception('分配ID不能为空');
|
||||
$certInfo = openssl_x509_parse($fullchain, true);
|
||||
if (!$certInfo) throw new Exception('证书解析失败');
|
||||
$config['cert_name'] = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
|
||||
|
||||
if (isset($info['cert_id']) && isset($info['cert_name']) && $info['cert_name'] == $config['cert_name']) {
|
||||
$cert_id = $info['cert_id'];
|
||||
$this->log('证书已上传:' . $cert_id);
|
||||
} else {
|
||||
$cert_id = $this->get_cert_id($fullchain, $privatekey);
|
||||
$this->log('证书上传成功:' . $cert_id);
|
||||
$info['cert_id'] = $cert_id;
|
||||
$info['cert_name'] = $config['cert_name'];
|
||||
usleep(500000);
|
||||
}
|
||||
$cert_id = isset($info['cert_id']) ? $info['cert_id'] : null;
|
||||
$cert_id = $this->get_cert_id($fullchain, $privatekey, $cert_id);
|
||||
usleep(500000);
|
||||
|
||||
$client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'cloudfront.amazonaws.com', 'cloudfront', '2020-05-31', 'us-east-1', $this->proxy);
|
||||
try {
|
||||
@@ -54,20 +56,71 @@ class aws implements DeployInterface
|
||||
}
|
||||
|
||||
$data['ViewerCertificate']['ACMCertificateArn'] = $cert_id;
|
||||
$data['ViewerCertificate']['CloudFrontDefaultCertificate'] = false;
|
||||
$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><DistributionConfig></DistributionConfig>');
|
||||
$data['ViewerCertificate']['CloudFrontDefaultCertificate'] = 'false';
|
||||
unset($data['ViewerCertificate']['Certificate']);
|
||||
unset($data['ViewerCertificate']['CertificateSource']);
|
||||
|
||||
$xml = new \SimpleXMLElement('<DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/"></DistributionConfig>');
|
||||
$client->requestXmlN('PUT', '/distribution/' . $config['distribution_id'] . '/config', $data, $xml);
|
||||
$this->log('分配ID: ' . $config['distribution_id'] . ' 证书部署成功!');
|
||||
}
|
||||
|
||||
private function get_cert_id($fullchain, $privatekey)
|
||||
private function get_cert_id($fullchain, $privatekey, $cert_id = null, $acm = false)
|
||||
{
|
||||
$cert = explode('-----END CERTIFICATE-----', $fullchain)[0] . '-----END CERTIFICATE-----';
|
||||
if ($acm === true && $cert_id == null) {
|
||||
throw new Exception('ACM ARN不能为空');
|
||||
}
|
||||
|
||||
$certificates = explode('-----END CERTIFICATE-----', $fullchain);
|
||||
$cert = $certificates[0] . '-----END CERTIFICATE-----';
|
||||
|
||||
$client = new AWSClient($this->AccessKeyId, $this->SecretAccessKey, 'acm.us-east-1.amazonaws.com', 'acm', '', 'us-east-1', $this->proxy);
|
||||
|
||||
if (!empty($cert_id)) {
|
||||
try {
|
||||
$data = $client->request('POST', 'CertificateManager.GetCertificate', [
|
||||
'CertificateArn' => $cert_id
|
||||
]);
|
||||
// 如果成功获取证书信息,说明证书存在,直接返回cert_id
|
||||
if (isset($data['Certificate']) && trim($data['Certificate']) == trim($cert)) {
|
||||
$this->log('证书已是最新,ACM ARN:' . $cert_id);
|
||||
return $cert_id;
|
||||
} else {
|
||||
$this->log('证书已过期或被删除,准备更新或者重新上传');
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($acm === true) {
|
||||
throw new Exception('获取证书信息失败,请检查ACM ARN是否正确:' . $e->getMessage());
|
||||
}
|
||||
$this->log('证书已被删除:' . $cert_id. ',准备重新上传');
|
||||
}
|
||||
}
|
||||
|
||||
$certificateChain = '';
|
||||
if (count($certificates) > 1) {
|
||||
// 从第二个证书开始,重新拼接中间证书链
|
||||
for ($i = 1; $i < count($certificates); $i++) {
|
||||
if (trim($certificates[$i]) !== '') { // 忽略空字符串(可能由末尾分割产生)
|
||||
$certificateChain .= $certificates[$i] . '-----END CERTIFICATE-----';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$param = [
|
||||
'Certificate' => base64_encode($cert),
|
||||
'PrivateKey' => base64_encode($privatekey),
|
||||
];
|
||||
|
||||
// 如果有中间证书链,则添加到参数中
|
||||
if (!empty($certificateChain)) {
|
||||
$param['CertificateChain'] = base64_encode($certificateChain);
|
||||
}
|
||||
|
||||
// 如果是ACM,则添加ARN参数,用于更新证书
|
||||
if ($acm === true) {
|
||||
$param['CertificateArn'] = $cert_id;
|
||||
}
|
||||
|
||||
$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);
|
||||
@@ -75,6 +128,11 @@ class aws implements DeployInterface
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('上传证书失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->log('证书上传成功:' . $cert_id);
|
||||
|
||||
$info['cert_id'] = $cert_id;
|
||||
|
||||
return $cert_id;
|
||||
}
|
||||
|
||||
|
||||
134
app/lib/deploy/btwaf.php
Normal file
134
app/lib/deploy/btwaf.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace app\lib\deploy;
|
||||
|
||||
use app\lib\DeployInterface;
|
||||
use Exception;
|
||||
|
||||
class btwaf implements DeployInterface
|
||||
{
|
||||
private $logger;
|
||||
private $url;
|
||||
private $key;
|
||||
private $proxy;
|
||||
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->url = rtrim($config['url'], '/');
|
||||
$this->key = $config['key'];
|
||||
$this->proxy = $config['proxy'] == 1;
|
||||
}
|
||||
|
||||
public function check()
|
||||
{
|
||||
if (empty($this->url) || empty($this->key)) throw new Exception('请填写面板地址和接口密钥');
|
||||
|
||||
$path = '/api/user/latest_version';
|
||||
$response = $this->request($path, []);
|
||||
$result = json_decode($response, true);
|
||||
if (isset($result['code']) && $result['code'] == 0) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Exception(isset($result['res']) ? $result['res'] : '面板地址无法连接');
|
||||
}
|
||||
}
|
||||
|
||||
public function deploy($fullchain, $privatekey, $config, &$info)
|
||||
{
|
||||
$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 deploySite($siteName, $fullchain, $privatekey)
|
||||
{
|
||||
$site_id = null;
|
||||
$listen_ssl_port = ['443'];
|
||||
$path = '/api/wafmastersite/get_site_list';
|
||||
$data = ['p' => 1, 'p_size' => 10, 'site_name' => $siteName];
|
||||
$response = $this->request($path, $data);
|
||||
$result = json_decode($response, true);
|
||||
if (isset($result['code']) && $result['code'] == 0) {
|
||||
foreach ($result['res']['list'] as $site) {
|
||||
if ($site['site_name'] == $siteName) {
|
||||
$site_id = $site['site_id'];
|
||||
if (isset($site['server']['listen_ssl_port']) && !empty($site['server']['listen_ssl_port'])) {
|
||||
$listen_ssl_port = $site['server']['listen_ssl_port'];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$site_id) {
|
||||
throw new Exception("网站名称不存在");
|
||||
}
|
||||
} elseif (isset($result['res'])) {
|
||||
throw new Exception($result['res']);
|
||||
} else {
|
||||
throw new Exception($response ? $response : '返回数据解析失败');
|
||||
}
|
||||
$path = '/api/wafmastersite/modify_site';
|
||||
$data = [
|
||||
'types' => 'openCert',
|
||||
'site_id' => $site_id,
|
||||
'server' => [
|
||||
'listen_ssl_port' => $listen_ssl_port,
|
||||
'ssl' => [
|
||||
'is_ssl' => 1,
|
||||
'private_key' => $privatekey,
|
||||
'full_chain' => $fullchain,
|
||||
],
|
||||
]
|
||||
];
|
||||
$response = $this->request($path, $data);
|
||||
$result = json_decode($response, true);
|
||||
if (isset($result['code']) && $result['code'] == 0) {
|
||||
return true;
|
||||
} elseif (isset($result['res'])) {
|
||||
throw new Exception($result['res']);
|
||||
} 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)
|
||||
{
|
||||
$url = $this->url . $path;
|
||||
|
||||
$now_time = time();
|
||||
$headers = [
|
||||
'waf_request_time: ' . $now_time,
|
||||
'waf_request_token: ' . md5($now_time . md5($this->key)),
|
||||
'Content-Type: application/json',
|
||||
];
|
||||
$post = $params ? json_encode($params) : null;
|
||||
$response = curl_client($url, $post, null, null, $headers, $this->proxy, 'POST');
|
||||
return $response['body'];
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,8 @@ class doge implements DeployInterface
|
||||
|
||||
public function deploy($fullchain, $privatekey, $config, &$info)
|
||||
{
|
||||
$domain = $config['domain'];
|
||||
if (empty($domain)) throw new Exception('绑定的域名不能为空');
|
||||
$domains = $config['domain'];
|
||||
if (empty($domains)) throw new Exception('绑定的域名不能为空');
|
||||
|
||||
$certInfo = openssl_x509_parse($fullchain, true);
|
||||
if (!$certInfo) throw new Exception('证书解析失败');
|
||||
@@ -37,13 +37,14 @@ class doge implements DeployInterface
|
||||
|
||||
$cert_id = $this->get_cert_id($fullchain, $privatekey, $cert_name);
|
||||
|
||||
$param = [
|
||||
'id' => $cert_id,
|
||||
'domain' => $domain,
|
||||
];
|
||||
$this->request('/cdn/cert/bind.json', $param);
|
||||
|
||||
$this->log('CDN域名 ' . $domain . ' 绑定证书成功!');
|
||||
foreach (explode(',', $domains) as $domain) {
|
||||
$param = [
|
||||
'id' => $cert_id,
|
||||
'domain' => $domain,
|
||||
];
|
||||
$this->request('/cdn/cert/bind.json', $param);
|
||||
$this->log('CDN域名 ' . $domain . ' 绑定证书成功!');
|
||||
}
|
||||
$info['cert_id'] = $cert_id;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,8 +58,10 @@ class huawei implements DeployInterface
|
||||
],
|
||||
],
|
||||
];
|
||||
$client->request('PUT', '/v1.1/cdn/configuration/domains/' . $config['domain'] . '/configs', null, $param);
|
||||
$this->log('CDN域名 ' . $config['domain'] . ' 部署证书成功!');
|
||||
foreach (explode(',', $config['domain']) as $domain) {
|
||||
$client->request('PUT', '/v1.1/cdn/configuration/domains/' . $domain . '/configs', null, $param);
|
||||
$this->log('CDN域名 ' . $domain . ' 部署证书成功!');
|
||||
}
|
||||
}
|
||||
|
||||
private function deploy_elb($fullchain, $privatekey, $config)
|
||||
|
||||
@@ -46,6 +46,8 @@ class huoshan implements DeployInterface
|
||||
$this->deploy_imagex($cert_id, $config);
|
||||
} elseif ($config['product'] == 'clb') {
|
||||
$this->deploy_clb($cert_id, $config);
|
||||
} elseif ($config['product'] == 'alb') {
|
||||
$this->deploy_alb($cert_id, $config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,7 +135,6 @@ class huoshan implements DeployInterface
|
||||
|
||||
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) {
|
||||
@@ -168,6 +169,19 @@ class huoshan implements DeployInterface
|
||||
$this->log('CLB监听器 ' . $config['listener_id'] . ' 部署证书成功!');
|
||||
}
|
||||
|
||||
private function deploy_alb($cert_id, $config)
|
||||
{
|
||||
if (empty($config['listener_id'])) throw new Exception('监听器ID不能为空');
|
||||
$client = new Volcengine($this->AccessKeyId, $this->SecretAccessKey, 'open.volcengineapi.com', 'alb', '2020-04-01', 'cn-beijing', $this->proxy);
|
||||
$param = [
|
||||
'ListenerId' => $config['listener_id'],
|
||||
'CertificateSource' => 'cert_center',
|
||||
'CertCenterCertificateId' => $cert_id,
|
||||
];
|
||||
$client->request('GET', 'ModifyListenerAttributes', $param);
|
||||
$this->log('ALB监听器 ' . $config['listener_id'] . ' 部署证书成功!');
|
||||
}
|
||||
|
||||
private function get_cert_id($fullchain, $privatekey)
|
||||
{
|
||||
$certInfo = openssl_x509_parse($fullchain, true);
|
||||
|
||||
@@ -14,7 +14,7 @@ class opanel implements DeployInterface
|
||||
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->url = rtrim($config['url'], '/');
|
||||
$this->url = rtrim($config['url'], '/') . '/api/' . (isset($config['version']) ? $config['version'] : 'v1');
|
||||
$this->key = $config['key'];
|
||||
$this->proxy = $config['proxy'] == 1;
|
||||
}
|
||||
@@ -22,7 +22,7 @@ class opanel implements DeployInterface
|
||||
public function check()
|
||||
{
|
||||
if (empty($this->url) || empty($this->key)) throw new Exception('请填写面板地址和接口密钥');
|
||||
$this->request('/api/v1/settings/search');
|
||||
$this->request("/settings/search");
|
||||
}
|
||||
|
||||
public function deploy($fullchain, $privatekey, $config, &$info)
|
||||
@@ -30,9 +30,9 @@ class opanel implements DeployInterface
|
||||
$domains = $config['domainList'];
|
||||
if (empty($domains)) throw new Exception('没有设置要部署的域名');
|
||||
|
||||
$params = ['page'=>1, 'pageSize'=>500];
|
||||
$params = ['page' => 1, 'pageSize' => 500];
|
||||
try {
|
||||
$data = $this->request('/api/v1/websites/ssl/search', $params);
|
||||
$data = $this->request("/websites/ssl/search", $params);
|
||||
$this->log('获取证书列表成功(total=' . $data['total'] . ')');
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('获取证书列表失败:' . $e->getMessage());
|
||||
@@ -45,7 +45,7 @@ class opanel implements DeployInterface
|
||||
if (empty($row['primaryDomain'])) continue;
|
||||
$cert_domains = [];
|
||||
$cert_domains[] = $row['primaryDomain'];
|
||||
if(!empty($row['domains'])) $cert_domains += explode(',', $row['domains']);
|
||||
if (!empty($row['domains'])) $cert_domains += explode(',', $row['domains']);
|
||||
$flag = false;
|
||||
foreach ($cert_domains as $domain) {
|
||||
if (in_array($domain, $domains)) {
|
||||
@@ -62,7 +62,7 @@ class opanel implements DeployInterface
|
||||
'description' => '',
|
||||
];
|
||||
try {
|
||||
$this->request('/api/v1/websites/ssl/upload', $params);
|
||||
$this->request('/websites/ssl/upload', $params);
|
||||
$this->log("证书ID:{$row['id']}更新成功!");
|
||||
$success++;
|
||||
} catch (Exception $e) {
|
||||
@@ -93,21 +93,21 @@ class opanel implements DeployInterface
|
||||
{
|
||||
$url = $this->url . $path;
|
||||
|
||||
$timestamp = time().'';
|
||||
$timestamp = time() . '';
|
||||
$token = md5('1panel' . $this->key . $timestamp);
|
||||
$headers = [
|
||||
'1Panel-Token: '.$token,
|
||||
'1Panel-Timestamp: '.$timestamp
|
||||
'1Panel-Token: ' . $token,
|
||||
'1Panel-Timestamp: ' . $timestamp
|
||||
];
|
||||
$body = $params ? json_encode($params) : '{}';
|
||||
if($body) $headers[] = 'Content-Type: application/json';
|
||||
if ($body) $headers[] = 'Content-Type: application/json';
|
||||
$response = curl_client($url, $body, null, null, $headers, $this->proxy);
|
||||
$result = json_decode($response['body'], true);
|
||||
if(isset($result['code']) && $result['code'] == 200){
|
||||
if (isset($result['code']) && $result['code'] == 200) {
|
||||
return isset($result['data']) ? $result['data'] : null;
|
||||
}elseif(isset($result['message'])){
|
||||
} elseif (isset($result['message'])) {
|
||||
throw new Exception($result['message']);
|
||||
}else{
|
||||
} else {
|
||||
throw new Exception('请求失败(httpCode=' . $response['code'] . ')');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ class qiniu implements DeployInterface
|
||||
|
||||
public function deploy($fullchain, $privatekey, $config, &$info)
|
||||
{
|
||||
$domain = $config['domain'];
|
||||
if (empty($domain)) throw new Exception('绑定的域名不能为空');
|
||||
$domains = $config['domain'];
|
||||
if (empty($domains)) throw new Exception('绑定的域名不能为空');
|
||||
|
||||
$certInfo = openssl_x509_parse($fullchain, true);
|
||||
if (!$certInfo) throw new Exception('证书解析失败');
|
||||
@@ -38,14 +38,16 @@ class qiniu implements DeployInterface
|
||||
|
||||
$cert_id = $this->get_cert_id($fullchain, $privatekey, $certInfo['subject']['CN'], $cert_name);
|
||||
|
||||
if ($config['product'] == 'cdn') {
|
||||
$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('未知的产品类型');
|
||||
foreach (explode(',', $domains) as $domain) {
|
||||
if ($config['product'] == 'cdn') {
|
||||
$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('未知的产品类型');
|
||||
}
|
||||
}
|
||||
$info['cert_id'] = $cert_id;
|
||||
$info['cert_name'] = $cert_name;
|
||||
|
||||
163
app/lib/deploy/ratpanel.php
Normal file
163
app/lib/deploy/ratpanel.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace app\lib\deploy;
|
||||
|
||||
use app\lib\DeployInterface;
|
||||
use Exception;
|
||||
|
||||
class ratpanel implements DeployInterface
|
||||
{
|
||||
private $logger;
|
||||
private $url;
|
||||
private $id;
|
||||
private $token;
|
||||
private $proxy;
|
||||
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->url = rtrim($config['url'], '/');
|
||||
$this->id = $config['id'];
|
||||
$this->token = $config['token'];
|
||||
$this->proxy = $config['proxy'] == 1;
|
||||
}
|
||||
|
||||
public function check()
|
||||
{
|
||||
if (empty($this->url) || empty($this->id) || empty($this->token)) throw new Exception('请填写完整面板地址和访问令牌');
|
||||
|
||||
$response = $this->request('/user/info', null, 'GET');
|
||||
$result = json_decode($response, true);
|
||||
if (isset($result['msg']) && $result['msg'] == "success") {
|
||||
return true;
|
||||
} else {
|
||||
throw new Exception($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) {
|
||||
$site = trim($site);
|
||||
if (empty($site)) continue;
|
||||
try {
|
||||
$this->deploySite($site, $fullchain, $privatekey);
|
||||
$this->log("网站 {$site} 证书部署成功");
|
||||
$success++;
|
||||
} catch (Exception $e) {
|
||||
$errmsg = $e->getMessage();
|
||||
$this->log("网站 {$site} 证书部署失败:" . $errmsg);
|
||||
}
|
||||
}
|
||||
if ($success == 0) {
|
||||
throw new Exception($errmsg ?: '要部署的网站不存在');
|
||||
}
|
||||
}
|
||||
|
||||
private function deployPanel($fullchain, $privatekey)
|
||||
{
|
||||
$data = [
|
||||
'cert' => $fullchain,
|
||||
'key' => $privatekey,
|
||||
];
|
||||
$response = $this->request('/setting/cert', $data);
|
||||
$result = json_decode($response, true);
|
||||
if (isset($result['msg']) && $result['msg'] == "success") {
|
||||
return true;
|
||||
} elseif (isset($result['msg'])) {
|
||||
throw new Exception($result['msg']);
|
||||
} else {
|
||||
throw new Exception($response ?: '返回数据解析失败');
|
||||
}
|
||||
}
|
||||
|
||||
private function deploySite($name, $fullchain, $privatekey)
|
||||
{
|
||||
$data = [
|
||||
'name' => $name,
|
||||
'cert' => $fullchain,
|
||||
'key' => $privatekey,
|
||||
];
|
||||
$response = $this->request('/website/cert', $data);
|
||||
$result = json_decode($response, true);
|
||||
if (isset($result['msg']) && $result['msg'] == "success") {
|
||||
return true;
|
||||
} elseif (isset($result['msg'])) {
|
||||
throw new Exception($result['msg']);
|
||||
} else {
|
||||
throw new Exception($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, $method = 'POST')
|
||||
{
|
||||
$url = $this->url . '/api' . $path;
|
||||
$body = $method == 'GET' ? null : json_encode($params);
|
||||
$sign = $this->signRequest($method, $url, $body, $this->id, $this->token);
|
||||
$response = curl_client($url, $body, null, null, [
|
||||
'Content-Type: application/json',
|
||||
'X-Timestamp: ' . $sign['timestamp'],
|
||||
'Authorization: HMAC-SHA256 Credential=' . $sign['id'] . ', Signature=' . $sign['signature']
|
||||
], $this->proxy, $method);
|
||||
return $response['body'];
|
||||
}
|
||||
|
||||
private function signRequest($method, $url, $body, $id, $token)
|
||||
{
|
||||
// 解析URL并获取路径
|
||||
$parsedUrl = parse_url($url);
|
||||
$path = $parsedUrl['path'];
|
||||
$query = $parsedUrl['query'] ?? '';
|
||||
|
||||
// 规范化路径
|
||||
$canonicalPath = $path;
|
||||
if (strpos($path, '/api') !== 0) {
|
||||
$apiPos = strpos($path, '/api');
|
||||
if ($apiPos !== false) {
|
||||
$canonicalPath = substr($path, $apiPos);
|
||||
}
|
||||
}
|
||||
|
||||
// 构造规范化请求
|
||||
$canonicalRequest = implode("\n", [
|
||||
$method,
|
||||
$canonicalPath,
|
||||
$query,
|
||||
hash('sha256', $body ?: '')
|
||||
]);
|
||||
|
||||
// 计算签名
|
||||
$timestamp = time();
|
||||
$stringToSign = implode("\n", [
|
||||
'HMAC-SHA256',
|
||||
$timestamp,
|
||||
hash('sha256', $canonicalRequest)
|
||||
]);
|
||||
$signature = hash_hmac('sha256', $stringToSign, $token);
|
||||
|
||||
return [
|
||||
'timestamp' => $timestamp,
|
||||
'signature' => $signature,
|
||||
'id' => $id
|
||||
];
|
||||
}
|
||||
}
|
||||
471
app/lib/deploy/wangsu.php
Normal file
471
app/lib/deploy/wangsu.php
Normal file
@@ -0,0 +1,471 @@
|
||||
<?php
|
||||
|
||||
namespace app\lib\deploy;
|
||||
|
||||
use app\lib\DeployInterface;
|
||||
use Exception;
|
||||
|
||||
class wangsu implements DeployInterface
|
||||
{
|
||||
private $logger;
|
||||
private $username;
|
||||
private $apiKey;
|
||||
private $spKey;
|
||||
private $proxy;
|
||||
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->username = $config['username'];
|
||||
$this->apiKey = $config['apiKey'];
|
||||
$this->spKey = $config['spKey'];
|
||||
$this->proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
|
||||
}
|
||||
|
||||
public function check()
|
||||
{
|
||||
if (empty($this->username) || empty($this->apiKey)) throw new Exception('必填参数不能为空');
|
||||
$this->request('/cdn/certificates');
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deploy($fullchain, $privatekey, $config, &$info)
|
||||
{
|
||||
if ($config['product'] == 'cdnpro') {
|
||||
$this->deploy_cdnpro($fullchain, $privatekey, $config, $info);
|
||||
|
||||
} elseif ($config['product'] == 'cdn') {
|
||||
$this->deploy_cdn($fullchain, $privatekey, $config, $info);
|
||||
|
||||
} elseif ($config['product'] == 'certificate') {
|
||||
$certInfo = openssl_x509_parse($fullchain, true);
|
||||
if (!$certInfo) {
|
||||
throw new Exception('证书解析失败');
|
||||
}
|
||||
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
|
||||
$serial_no = strtolower($certInfo['serialNumberHex']);
|
||||
$this->get_cert_id($fullchain, $privatekey, $cert_name, $config['cert_id'], $serial_no, true);
|
||||
} else {
|
||||
throw new Exception('未知的产品类型');
|
||||
}
|
||||
}
|
||||
|
||||
public function deploy_cdn($fullchain, $privatekey, $config, &$info)
|
||||
{
|
||||
if (empty($config['domains'])) {
|
||||
throw new Exception('绑定的域名不能为空');
|
||||
}
|
||||
$domains = explode(',', $config['domains']);
|
||||
|
||||
$certInfo = openssl_x509_parse($fullchain, true);
|
||||
if (!$certInfo) {
|
||||
throw new Exception('证书解析失败');
|
||||
}
|
||||
|
||||
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
|
||||
$serial_no = strtolower($certInfo['serialNumberHex']);
|
||||
$this->log('证书序列号:' . $serial_no);
|
||||
$cert_id = isset($info['cert_id']) ? $info['cert_id'] : null;
|
||||
$cert_id = $this->get_cert_id($fullchain, $privatekey, $cert_name, $cert_id, $serial_no, false);
|
||||
|
||||
$param = [
|
||||
'certificateId' => $cert_id,
|
||||
'domainNames' => $domains
|
||||
];
|
||||
|
||||
try {
|
||||
$data = $this->request('/api/config/certificate/batch', $param, true, null, 'PUT');
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('绑定域名失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->log('绑定证书成功,证书ID:' . $cert_id);
|
||||
$info['cert_id'] = $cert_id;
|
||||
}
|
||||
|
||||
public function deploy_cdnpro($fullchain, $privatekey, $config, &$info)
|
||||
{
|
||||
if (empty($config['domain'])) {
|
||||
throw new Exception('绑定的域名不能为空');
|
||||
}
|
||||
$domain = $config['domain'];
|
||||
|
||||
$certInfo = openssl_x509_parse($fullchain, true);
|
||||
if (!$certInfo) {
|
||||
throw new Exception('证书解析失败');
|
||||
}
|
||||
|
||||
$cert_name = str_replace('*.', '', $certInfo['subject']['CN']) . '-' . $certInfo['validFrom_time_t'];
|
||||
$cert_id = $this->get_cert_id_cdnpro($fullchain, $privatekey, $cert_name);
|
||||
|
||||
try {
|
||||
$hostnameInfo = $this->request('/cdn/hostnames/' . $domain);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('获取域名信息失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (empty($hostnameInfo["propertyInProduction"])) {
|
||||
throw new Exception('域名 ' . $domain . ' 不存在或未部署到生产环境');
|
||||
} else {
|
||||
$this->log('CDN域名 ' . $domain . ' 对应的加速项目ID:' . $hostnameInfo["propertyInProduction"]["propertyId"]);
|
||||
$this->log('CDN域名 ' . $domain . ' 对应的加速项目生产版本:' . $hostnameInfo["propertyInProduction"]["version"]);
|
||||
}
|
||||
|
||||
if ($hostnameInfo["propertyInProduction"]["certificateId"] == $cert_id) {
|
||||
$this->log('CDN域名 ' . $domain . ' 已绑定证书:' . $cert_name);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$properity = $this->request('/cdn/properties/' . $hostnameInfo["propertyInProduction"]["propertyId"] . '/versions/' . $hostnameInfo["propertyInProduction"]["version"]);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('获取加速项目版本信息失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$properityConfig = $properity["configs"];
|
||||
$properityConfig["tlsCertificateId"] = $cert_id;
|
||||
|
||||
try {
|
||||
$data = $this->request('/cdn/properties/' . $hostnameInfo["propertyInProduction"]["propertyId"] . '/versions', $properityConfig, true);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('新增加速项目版本失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$url_parts = parse_url($data);
|
||||
$path_parts = explode('/', $url_parts['path']);
|
||||
$newVersion = end($path_parts);
|
||||
|
||||
$param = [
|
||||
'propertyId' => $hostnameInfo["propertyInProduction"]["propertyId"],
|
||||
'version' => intval($newVersion),
|
||||
];
|
||||
|
||||
try {
|
||||
$data = $this->request('/cdn/validations', $param, true);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('发起加速项目验证失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$url_parts = parse_url($data);
|
||||
$path_parts = explode('/', $url_parts['path']);
|
||||
$validationTaskId = end($path_parts);
|
||||
$this->log('验证任务ID:' . $validationTaskId);
|
||||
|
||||
$attempts = 0;
|
||||
$maxAttempts = 12;
|
||||
$status = null;
|
||||
|
||||
do {
|
||||
sleep(5);
|
||||
|
||||
try {
|
||||
$data = $this->request('/cdn/validations/' . $validationTaskId);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('获取验证任务状态失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$status = $data['status'];
|
||||
|
||||
if ($status === 'failed') {
|
||||
throw new Exception('证书绑定失败,加速项目验证失败');
|
||||
}
|
||||
|
||||
if ($status === 'succeeded') {
|
||||
break; // 验证成功立即退出循环
|
||||
}
|
||||
|
||||
$attempts++;
|
||||
} while ($attempts < $maxAttempts);
|
||||
|
||||
if ($status !== 'succeeded') {
|
||||
throw new Exception('证书绑定超时,加速项目验证时间过长');
|
||||
}
|
||||
|
||||
$this->log('加速项目验证成功,开始部署...');
|
||||
|
||||
$deploymentTasks = [
|
||||
'target' => 'production',
|
||||
'actions' => [
|
||||
[
|
||||
'action' => 'deploy_cert',
|
||||
'certificateId' => $cert_id,
|
||||
'version' => 1,
|
||||
],
|
||||
[
|
||||
'action' => 'deploy_property',
|
||||
'propertyId' => $hostnameInfo["propertyInProduction"]["propertyId"],
|
||||
'version' => intval($newVersion),
|
||||
]
|
||||
],
|
||||
'name' => 'Deploy certificate and property for ' . $hostnameInfo["propertyInProduction"]["propertyId"],
|
||||
];
|
||||
|
||||
try {
|
||||
$data = $this->request('/cdn/deploymentTasks', $deploymentTasks, true, null, 'POST', false, ['Check-Certificate' => 'no', 'Check-Usage' => 'no']);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('下发证书部署任务失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$url_parts = parse_url($data);
|
||||
$path_parts = explode('/', $url_parts['path']);
|
||||
$deploymentTaskId = end($path_parts);
|
||||
|
||||
$this->log('CDN域名 ' . $domain . ' 绑定证书部署任务下发成功,部署任务ID:' . $deploymentTaskId);
|
||||
$info['cert_id'] = $cert_id;
|
||||
}
|
||||
|
||||
private function get_cert_id($fullchain, $privatekey, $cert_name, $cert_id = null, $serial_no = null, $overwrite = false)
|
||||
{
|
||||
if ($cert_id) {
|
||||
try {
|
||||
$data = $this->request('/api/certificate/' . $cert_id);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('获取证书详情失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (isset($data['message']) && $data['message'] == 'success' && $data['data']['name'] == $cert_name && $data['data']['serial'] == $serial_no) {
|
||||
$this->log('证书已是最新,证书ID:' . $cert_id);
|
||||
return $cert_id;
|
||||
}
|
||||
|
||||
$this->log('证书已过期或被删除,准备重新上传');
|
||||
|
||||
} elseif ($overwrite === true) {
|
||||
throw new Exception('证书ID不能为空');
|
||||
}
|
||||
|
||||
if ($overwrite === true) {
|
||||
$param = [
|
||||
'name' => $cert_name,
|
||||
'certificate' => $fullchain,
|
||||
'privateKey' => $privatekey,
|
||||
];
|
||||
|
||||
try {
|
||||
$data = $this->request('/api/certificate/' . $cert_id, $param, true, null, 'PUT');
|
||||
$this->log('更新证书成功,证书ID:' . $cert_id);
|
||||
return $cert_id;
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('更新证书失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $this->request('/api/ssl/certificate');
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('获取证书列表失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$certificates = $data['ssl-certificate'];
|
||||
|
||||
if (!empty($certificates)) {
|
||||
foreach ($certificates as $cert) {
|
||||
if ($serial_no == $cert['certificate-serial']) {
|
||||
$cert_id = $cert['certificate-id'];
|
||||
$this->log('证书' . $cert_name . '已存在,新证书ID:' . $cert_id);
|
||||
try {
|
||||
$this->request('/api/certificate/' . $cert_id, ['name' => $cert_name], true, null, 'PUT');
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('证书更名失败:' . $e->getMessage());
|
||||
}
|
||||
$this->log('将证书ID为' . $cert_id . '的证书更名为:' . $cert_name);
|
||||
return $cert_id;
|
||||
|
||||
} elseif ($cert_name == $cert['name']) {
|
||||
$this->log('证书' . $cert_name . '已存在,但序列号(' . $cert['certificate-id'] . ')不匹配,准备重新上传');
|
||||
try {
|
||||
$this->request('/api/certificate/' . $cert['certificate-id'], [['name'] => $cert_name . '-bak'], true, null, 'PUT');
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('证书更名失败:' . $e->getMessage());
|
||||
}
|
||||
$this->log('将证书ID为' . $cert['certificate-id'] . '的证书更名为:' . $cert_name . '-bak');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$param = [
|
||||
'name' => $cert_name,
|
||||
'certificate' => $fullchain,
|
||||
'privateKey' => $privatekey,
|
||||
];
|
||||
|
||||
try {
|
||||
$data = $this->request('/api/certificate', $param, true, null, 'POST', true);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('上传证书失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$url_parts = parse_url($data);
|
||||
$path_parts = explode('/', $url_parts['path']);
|
||||
$cert_id = end($path_parts);
|
||||
$this->log('上传证书成功,证书ID:' . $cert_id);
|
||||
|
||||
return $cert_id;
|
||||
}
|
||||
|
||||
private function get_cert_id_cdnpro($fullchain, $privatekey, $cert_name)
|
||||
{
|
||||
$cert_id = null;
|
||||
|
||||
try {
|
||||
$data = $this->request('/cdn/certificates?search=' . urlencode($cert_name));
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('获取证书列表失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
if ($data['count'] > 0) {
|
||||
foreach ($data['certificates'] as $cert) {
|
||||
if ($cert_name == $cert['name']) {
|
||||
$cert_id = $cert['certificateId'];
|
||||
$this->log('证书' . $cert_name . '已存在,证书ID:' . $cert_id);
|
||||
return $cert_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$date = gmdate("D, d M Y H:i:s T");
|
||||
$encryptedKey = $this->encryptPrivateKey($privatekey, $date);
|
||||
$param = [
|
||||
'name' => $cert_name,
|
||||
'autoRenew' => 'Off',
|
||||
'newVersion' => [
|
||||
'privateKey' => $encryptedKey,
|
||||
'certificate' => $fullchain,
|
||||
]
|
||||
];
|
||||
|
||||
try {
|
||||
$data = $this->request('/cdn/certificates', $param, true, $date);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception('上传证书失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
$url_parts = parse_url($data);
|
||||
$path_parts = explode('/', $url_parts['path']);
|
||||
$cert_id = end($path_parts);
|
||||
$this->log('上传证书成功,证书ID:' . $cert_id);
|
||||
|
||||
usleep(500000);
|
||||
|
||||
return $cert_id;
|
||||
}
|
||||
|
||||
private function encryptPrivateKey($privateKey, $date = null)
|
||||
{
|
||||
// 获取当前 GMT 时间(DATE)
|
||||
if (empty($date)) {
|
||||
$date = gmdate("D, d M Y H:i:s T");
|
||||
}
|
||||
|
||||
// 生成 HMAC-SHA256 密钥材料
|
||||
if (!empty($this->spKey)) {
|
||||
$apiKey = $this->spKey;
|
||||
} else {
|
||||
$apiKey = $this->apiKey;
|
||||
}
|
||||
$hmac = hash_hmac('sha256', $date, $apiKey, true);
|
||||
$aesIvKeyHex = bin2hex($hmac);
|
||||
|
||||
if (strlen($aesIvKeyHex) != 64) {
|
||||
throw new Exception("Invalid HMAC length: " . strlen($aesIvKeyHex));
|
||||
}
|
||||
|
||||
// 提取 IV 和 Key
|
||||
$ivHex = substr($aesIvKeyHex, 0, 32);
|
||||
$keyHex = substr($aesIvKeyHex, 32, 64);
|
||||
|
||||
$iv = hex2bin($ivHex);
|
||||
$key = hex2bin($keyHex);
|
||||
|
||||
$blockSize = 16; // AES 块大小为 16 字节
|
||||
$plainLen = strlen($privateKey);
|
||||
$padLen = $blockSize - ($plainLen % $blockSize);
|
||||
$padding = str_repeat(chr($padLen), $padLen);
|
||||
$plainText = $privateKey . $padding;
|
||||
|
||||
// AES-128-CBC 加密
|
||||
$encrypted = openssl_encrypt(
|
||||
$plainText,
|
||||
'AES-128-CBC',
|
||||
$key,
|
||||
OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,
|
||||
$iv
|
||||
);
|
||||
|
||||
if ($encrypted === false) {
|
||||
throw new Exception("Encryption failed: " . openssl_error_string());
|
||||
}
|
||||
|
||||
// 返回 Base64 编码结果
|
||||
return base64_encode($encrypted);
|
||||
}
|
||||
|
||||
private function request($path, $data = null, $json = false, $date = null, $method = null, $getLocation = false, $headers = [])
|
||||
{
|
||||
$body = null;
|
||||
if ($data) {
|
||||
$body = $json ? json_encode($data) : http_build_query($data);
|
||||
}
|
||||
|
||||
if (empty($date)) {
|
||||
$date = gmdate("D, d M Y H:i:s T");
|
||||
}
|
||||
|
||||
$hmac = hash_hmac('sha1', $date, $this->apiKey, true);
|
||||
$signature = base64_encode($hmac);
|
||||
$authorization = 'Basic ' . base64_encode($this->username . ':' . $signature);
|
||||
|
||||
if (empty($headers)) {
|
||||
$headers = [
|
||||
'Authorization: ' . $authorization,
|
||||
'Date: ' . $date,
|
||||
'Accept: application/json',
|
||||
'Connection: close',
|
||||
];
|
||||
} else {
|
||||
$headers[] = 'Authorization: ' . $authorization;
|
||||
$headers[] = 'Date: ' . $date;
|
||||
$headers[] = 'Accept: application/json';
|
||||
$headers[] = 'Connection: close';
|
||||
}
|
||||
|
||||
if ($body && $json) {
|
||||
$headers[] = 'Content-Type: application/json';
|
||||
}
|
||||
|
||||
$url = 'https://open.chinanetcenter.com' . $path;
|
||||
$response = curl_client($url, $body, null, null, $headers, $this->proxy, $method, 30, false);
|
||||
$result = json_decode($response['body'], true);
|
||||
|
||||
if ((isset($response['code']) && $response['code'] == 201) || (isset($response['code']) && $response['code'] == 200 && $getLocation === true)) {
|
||||
if (preg_match('/Location:\s*(.*)/i', $response['header'], $matches)) {
|
||||
$location = trim($matches[1]); // 提取 Location 头部的值并去除多余空格
|
||||
if (!empty($location)) {
|
||||
return $location;
|
||||
}
|
||||
}
|
||||
// 如果没有找到 Location 头部,返回默认值 true
|
||||
return true;
|
||||
|
||||
} elseif (isset($response['code']) && $response['code'] >= 200 && $response['code'] <= 299) {
|
||||
return isset($result) ? $result : true;
|
||||
|
||||
} elseif (isset($result['message'])) {
|
||||
throw new Exception($result['message']);
|
||||
|
||||
} else {
|
||||
throw new Exception('请求失败');
|
||||
}
|
||||
}
|
||||
|
||||
public function setLogger($func)
|
||||
{
|
||||
$this->logger = $func;
|
||||
}
|
||||
|
||||
private function log($txt)
|
||||
{
|
||||
if ($this->logger) {
|
||||
call_user_func($this->logger, $txt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,6 +253,46 @@ class aliyun implements DnsInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
//获取权重配置子域名列表
|
||||
public function getWeightSubDomains($PageNumber = 1, $PageSize = 20, $SubDomain = null)
|
||||
{
|
||||
$param = ['Action' => 'DescribeDNSSLBSubDomains', 'DomainName' => $this->domain, 'PageNumber' => $PageNumber, 'PageSize' => $PageSize];
|
||||
if (!empty($SubDomain)) {
|
||||
$param += ['Rr' => $SubDomain];
|
||||
}
|
||||
$data = $this->request($param, true);
|
||||
if ($data) {
|
||||
$list = $data['SlbSubDomains']['SlbSubDomain'];
|
||||
$i = 1;
|
||||
foreach ($list as &$v) {
|
||||
$v['id'] = $i++;
|
||||
$v['rr'] = substr($v['SubDomain'], 0, -strlen($this->domain) - 1);
|
||||
}
|
||||
return ['total' => $data['TotalCount'], 'list' => $list];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//开启关闭权重配置
|
||||
public function setWeightStatus($SubDomain, $Open, $Type = null, $Line = null)
|
||||
{
|
||||
$param = ['Action' => 'SetDNSSLBStatus', 'DomainName' => $this->domain, 'SubDomain' => $SubDomain, 'Open' => $Open == '1' ? 'true' : 'false'];
|
||||
if (!empty($Type)) {
|
||||
$param += ['Type' => $Type];
|
||||
}
|
||||
if (!empty($Line)) {
|
||||
$param += ['Line' => $Line];
|
||||
}
|
||||
return $this->request($param);
|
||||
}
|
||||
|
||||
//修改权重
|
||||
public function updateRecordWeight($RecordId, $Weight)
|
||||
{
|
||||
$param = ['Action' => 'UpdateDNSSLBWeight', 'RecordId' => $RecordId, 'Weight' => $Weight];
|
||||
return $this->request($param);
|
||||
}
|
||||
|
||||
private function convertLineCode($line)
|
||||
{
|
||||
$convert_dict = ['0' => 'default', '10=1' => 'unicom', '10=0' => 'telecom', '10=3' => 'mobile', '10=2' => 'edu', '3=0' => 'oversea', '10=22' => 'btvn', '80=0' => 'search', '7=0' => 'internal'];
|
||||
@@ -265,13 +305,13 @@ class aliyun implements DnsInterface
|
||||
private function request($param, $returnData = false)
|
||||
{
|
||||
if (empty($this->AccessKeyId) || empty($this->AccessKeySecret)) return false;
|
||||
try{
|
||||
try {
|
||||
$result = $this->client->request($param);
|
||||
}catch(Exception $e){
|
||||
try{
|
||||
} catch (Exception $e) {
|
||||
try {
|
||||
usleep(50000);
|
||||
$result = $this->client->request($param);
|
||||
}catch(Exception $e){
|
||||
} catch (Exception $e) {
|
||||
$this->setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -61,8 +61,9 @@ class baidu implements DnsInterface
|
||||
//获取解析记录列表
|
||||
public function getDomainRecords($PageNumber = 1, $PageSize = 20, $KeyWord = null, $SubDomain = null, $Value = null, $Type = null, $Line = null, $Status = null)
|
||||
{
|
||||
$query = ['rr' => $KeyWord];
|
||||
$query = [];
|
||||
if (!isNullOrEmpty($SubDomain)) {
|
||||
$SubDomain = strtolower($SubDomain);
|
||||
$query['rr'] = $SubDomain;
|
||||
}
|
||||
$data = $this->send_reuqest('GET', '/v1/dns/zone/'.$this->domain.'/record', $query);
|
||||
@@ -84,6 +85,32 @@ class baidu implements DnsInterface
|
||||
'UpdateTime' => null,
|
||||
];
|
||||
}
|
||||
if (!isNullOrEmpty($SubDomain)) {
|
||||
$list = array_values(array_filter($list, function ($v) use ($SubDomain) {
|
||||
return $v['Name'] == $SubDomain;
|
||||
}));
|
||||
} else {
|
||||
if (!isNullOrEmpty($KeyWord)) {
|
||||
$list = array_values(array_filter($list, function ($v) use ($KeyWord) {
|
||||
return strpos($v['Name'], $KeyWord) !== false || strpos($v['Value'], $KeyWord) !== false;
|
||||
}));
|
||||
}
|
||||
if (!isNullOrEmpty($Value)) {
|
||||
$list = array_values(array_filter($list, function ($v) use ($Value) {
|
||||
return $v['Value'] == $Value;
|
||||
}));
|
||||
}
|
||||
if (!isNullOrEmpty($Type)) {
|
||||
$list = array_values(array_filter($list, function ($v) use ($Type) {
|
||||
return $v['Type'] == $Type;
|
||||
}));
|
||||
}
|
||||
if (!isNullOrEmpty($Status)) {
|
||||
$list = array_values(array_filter($list, function ($v) use ($Status) {
|
||||
return $v['Status'] == $Status;
|
||||
}));
|
||||
}
|
||||
}
|
||||
return ['total' => count($list), 'list' => $list];
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -76,6 +76,8 @@ class cloudflare implements DnsInterface
|
||||
$list = [];
|
||||
foreach ($data['result'] as $row) {
|
||||
$name = $this->domain == $row['name'] ? '@' : str_replace('.'.$this->domain, '', $row['name']);
|
||||
$status = str_ends_with($name, '_pause') ? '0' : '1';
|
||||
$name = $status == '0' ? substr($name, 0, -6) : $name;
|
||||
$list[] = [
|
||||
'RecordId' => $row['id'],
|
||||
'Domain' => $this->domain,
|
||||
@@ -85,7 +87,7 @@ class cloudflare implements DnsInterface
|
||||
'Line' => $row['proxied'] ? '1' : '0',
|
||||
'TTL' => $row['ttl'],
|
||||
'MX' => isset($row['priority']) ? $row['priority'] : null,
|
||||
'Status' => '1',
|
||||
'Status' => $status,
|
||||
'Weight' => null,
|
||||
'Remark' => $row['comment'],
|
||||
'UpdateTime' => $row['modified_on'],
|
||||
@@ -108,6 +110,8 @@ class cloudflare implements DnsInterface
|
||||
$data = $this->send_reuqest('GET', '/zones/'.$this->domainid.'/dns_records/'.$RecordId);
|
||||
if ($data) {
|
||||
$name = $this->domain == $data['result']['name'] ? '@' : str_replace('.' . $this->domain, '', $data['result']['name']);
|
||||
$status = str_ends_with($name, '_pause') ? '0' : '1';
|
||||
$name = $status == '0' ? substr($name, 0, -6) : $name;
|
||||
return [
|
||||
'RecordId' => $data['result']['id'],
|
||||
'Domain' => $this->domain,
|
||||
@@ -117,7 +121,7 @@ class cloudflare implements DnsInterface
|
||||
'Line' => $data['result']['proxied'] ? '1' : '0',
|
||||
'TTL' => $data['result']['ttl'],
|
||||
'MX' => isset($data['result']['priority']) ? $data['result']['priority'] : null,
|
||||
'Status' => '1',
|
||||
'Status' => $status,
|
||||
'Weight' => null,
|
||||
'Remark' => $data['result']['comment'],
|
||||
'UpdateTime' => $data['result']['modified_on'],
|
||||
@@ -168,7 +172,9 @@ class cloudflare implements DnsInterface
|
||||
//设置解析记录状态
|
||||
public function setDomainRecordStatus($RecordId, $Status)
|
||||
{
|
||||
return false;
|
||||
$info = $this->getDomainRecordInfo($RecordId);
|
||||
$Name = $Status == '1' ? str_replace('_pause', '', $info['Name']) : $info['Name'] . '_pause';
|
||||
return $this->updateDomainRecord($RecordId, $Name, $info['Type'], $info['Value'], $info['Line'], $info['TTL'], $info['MX'], $info['Weight'], $info['Remark']);
|
||||
}
|
||||
|
||||
//获取解析记录操作日志
|
||||
|
||||
@@ -77,7 +77,7 @@ class huoshan implements DnsInterface
|
||||
{
|
||||
$query = ['ZID' => intval($this->domainid), 'PageNumber' => $PageNumber, 'PageSize' => $PageSize, 'SearchOrder' => 'desc'];
|
||||
if (!empty($SubDomain) || !empty($Type) || !empty($Line) || !empty($Value)) {
|
||||
$query += ['Host' => $SubDomain, 'Value' => $Value, 'Type' => $Type, 'Line' => $Line];
|
||||
$query += ['Host' => $SubDomain, 'Value' => $Value, 'Type' => $Type, 'Line' => $Line, 'SearchMode' => 'exact'];
|
||||
} elseif (!empty($KeyWord)) {
|
||||
$query += ['Host' => $KeyWord];
|
||||
}
|
||||
|
||||
253
app/lib/dns/jdcloud.php
Normal file
253
app/lib/dns/jdcloud.php
Normal file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
namespace app\lib\dns;
|
||||
|
||||
use app\lib\DnsInterface;
|
||||
use app\lib\client\Jdcloud as JdcloudClient;
|
||||
use Exception;
|
||||
|
||||
class jdcloud implements DnsInterface
|
||||
{
|
||||
private $AccessKeyId;
|
||||
private $AccessKeySecret;
|
||||
private $endpoint = "domainservice.jdcloud-api.com";
|
||||
private $service = "domainservice";
|
||||
private $version = "v2";
|
||||
private $region = "cn-north-1";
|
||||
private $error;
|
||||
private $domain;
|
||||
private $domainid;
|
||||
private $domainInfo;
|
||||
private JdcloudClient $client;
|
||||
|
||||
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->AccessKeyId = $config['ak'];
|
||||
$this->AccessKeySecret = $config['sk'];
|
||||
$proxy = isset($config['proxy']) ? $config['proxy'] == 1 : false;
|
||||
$this->client = new JdcloudClient($this->AccessKeyId, $this->AccessKeySecret, $this->endpoint, $this->service, $this->region, $proxy);
|
||||
$this->domain = $config['domain'];
|
||||
$this->domainid = $config['domainid'];
|
||||
}
|
||||
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function check()
|
||||
{
|
||||
if ($this->getDomainList() != false) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//获取域名列表
|
||||
public function getDomainList($KeyWord = null, $PageNumber = 1, $PageSize = 20)
|
||||
{
|
||||
$query = ['pageNumber' => $PageNumber, 'pageSize' => $PageSize, 'domainName' => $KeyWord];
|
||||
$data = $this->send_request('GET', '/domain', $query);
|
||||
if ($data) {
|
||||
$list = [];
|
||||
if (!empty($data['dataList'])) {
|
||||
foreach ($data['dataList'] as $row) {
|
||||
$list[] = [
|
||||
'DomainId' => $row['id'],
|
||||
'Domain' => $row['domainName'],
|
||||
'RecordCount' => 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
return ['total' => $data['totalCount'], 'list' => $list];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//获取解析记录列表
|
||||
public function getDomainRecords($PageNumber = 1, $PageSize = 20, $KeyWord = null, $SubDomain = null, $Value = null, $Type = null, $Line = null, $Status = null)
|
||||
{
|
||||
if ($PageSize > 99) $PageSize = 99;
|
||||
$query = ['pageNumber' => $PageNumber, 'pageSize' => $PageSize];
|
||||
if (!isNullOrEmpty($SubDomain)) {
|
||||
$SubDomain = strtolower($SubDomain);
|
||||
$query += ['search' => $SubDomain];
|
||||
} elseif (!isNullOrEmpty($KeyWord)) {
|
||||
$query += ['search' => $KeyWord];
|
||||
}
|
||||
$data = $this->send_request('GET', '/domain/'.$this->domainid.'/ResourceRecord', $query);
|
||||
if ($data) {
|
||||
$list = [];
|
||||
foreach ($data['dataList'] as $row) {
|
||||
if ($row['type'] == 'SRV') {
|
||||
$row['hostValue'] = $row['mxPriority'].' '.$row['weight'].' '.$row['port'].' '.$row['hostValue'];
|
||||
}
|
||||
$list[] = [
|
||||
'RecordId' => $row['id'],
|
||||
'Domain' => $this->domain,
|
||||
'Name' => $row['hostRecord'],
|
||||
'Type' => $row['type'],
|
||||
'Value' => $row['hostValue'],
|
||||
'Line' => array_pop($row['viewValue']),
|
||||
'TTL' => $row['ttl'],
|
||||
'MX' => isset($row['mxPriority']) ? $row['mxPriority'] : null,
|
||||
'Status' => $row['resolvingStatus'] == '2' ? '1' : '0',
|
||||
'Weight' => $row['weight'],
|
||||
'Remark' => null,
|
||||
'UpdateTime' => date('Y-m-d H:i:s', $row['updateTime']),
|
||||
];
|
||||
}
|
||||
if (!isNullOrEmpty($SubDomain) && !empty($list)) {
|
||||
$list = array_values(array_filter($list, function ($v) use ($SubDomain) {
|
||||
return $v['Name'] == $SubDomain;
|
||||
}));
|
||||
}
|
||||
return ['total' => $data['totalCount'], 'list' => $list];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//获取子域名解析记录列表
|
||||
public function getSubDomainRecords($SubDomain, $PageNumber = 1, $PageSize = 20, $Type = null, $Line = null)
|
||||
{
|
||||
return $this->getDomainRecords($PageNumber, $PageSize, null, $SubDomain, null, $Type, $Line);
|
||||
}
|
||||
|
||||
//获取解析记录详细信息
|
||||
public function getDomainRecordInfo($RecordId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//添加解析记录
|
||||
public function addDomainRecord($Name, $Type, $Value, $Line = '0', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
|
||||
{
|
||||
$params = ['hostRecord' => $Name, 'type' => $this->convertType($Type), 'hostValue' => $Value, 'viewValue' => intval($Line), 'ttl' => intval($TTL)];
|
||||
if ($Type == 'MX') $params['mxPriority'] = intval($MX);
|
||||
if (!isNullOrEmpty($Weight)) $params['weight'] = intval($Weight);
|
||||
if ($Type == 'SRV') {
|
||||
$values = explode(' ', $Value);
|
||||
$params['mxPriority'] = intval($values[0]);
|
||||
$params['weight'] = intval($values[1]);
|
||||
$params['port'] = intval($values[2]);
|
||||
$params['hostValue'] = $values[3];
|
||||
}
|
||||
$data = $this->send_request('POST', '/domain/'.$this->domainid.'/ResourceRecord', ['req'=>$params]);
|
||||
return is_array($data) ? $data['dataList']['id'] : false;
|
||||
}
|
||||
|
||||
//修改解析记录
|
||||
public function updateDomainRecord($RecordId, $Name, $Type, $Value, $Line = '0', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
|
||||
{
|
||||
$params = ['domainName'=>$this->domain, 'hostRecord' => $Name, 'type' => $this->convertType($Type), 'hostValue' => $Value, 'viewValue' => intval($Line), 'ttl' => intval($TTL)];
|
||||
if ($Type == 'MX') $params['mxPriority'] = intval($MX);
|
||||
if (!isNullOrEmpty($Weight)) $params['weight'] = intval($Weight);
|
||||
if ($Type == 'SRV') {
|
||||
$values = explode(' ', $Value);
|
||||
$params['mxPriority'] = intval($values[0]);
|
||||
$params['weight'] = intval($values[1]);
|
||||
$params['port'] = intval($values[2]);
|
||||
$params['hostValue'] = $values[3];
|
||||
}
|
||||
return $this->send_request('PUT', '/domain/'.$this->domainid.'/ResourceRecord/'.$RecordId, ['req'=>$params]);
|
||||
}
|
||||
|
||||
//修改解析记录备注
|
||||
public function updateDomainRecordRemark($RecordId, $Remark)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//删除解析记录
|
||||
public function deleteDomainRecord($RecordId)
|
||||
{
|
||||
return $this->send_request('DELETE', '/domain/'.$this->domainid.'/ResourceRecord/'.$RecordId);
|
||||
}
|
||||
|
||||
//设置解析记录状态
|
||||
public function setDomainRecordStatus($RecordId, $Status)
|
||||
{
|
||||
$params = ['action' => $Status == '1' ? 'enable' : 'disable'];
|
||||
$data = $this->send_request('PUT', '/domain/'.$this->domainid.'/ResourceRecord/'.$RecordId.'/status', $params);
|
||||
return is_array($data);
|
||||
}
|
||||
|
||||
//获取解析记录操作日志
|
||||
public function getDomainRecordLog($PageNumber = 1, $PageSize = 20, $KeyWord = null, $StartDate = null, $endDate = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//获取解析线路列表
|
||||
public function getRecordLine()
|
||||
{
|
||||
$domainInfo = $this->getDomainInfo();
|
||||
if (!$domainInfo) return false;
|
||||
$packId = $domainInfo['packId'];
|
||||
$data = $this->send_request('GET', '/domain/'.$this->domainid.'/viewTree', ['packId'=>$packId, 'viewId'=>'0']);
|
||||
if ($data) {
|
||||
$list = [];
|
||||
$this->processLineList($list, $data['data'], null);
|
||||
return $list;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function processLineList(&$list, $line_list, $parent)
|
||||
{
|
||||
foreach ($line_list as $row) {
|
||||
if ($row['disabled']) continue;
|
||||
if (!isset($list[$row['value']])) {
|
||||
$list[$row['value']] = ['name' => $row['label'], 'parent' => $parent];
|
||||
if (!$row['leaf'] && $row['children']) {
|
||||
$this->processLineList($list, $row['children'], $row['value']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//获取域名概览信息
|
||||
public function getDomainInfo()
|
||||
{
|
||||
if (!empty($this->domainInfo)) return $this->domainInfo;
|
||||
$query = ['domainId' => intval($this->domainid)];
|
||||
$data = $this->send_request('GET', '/domain', $query);
|
||||
if ($data && $data['dataList']) {
|
||||
return $data['dataList'][0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//获取域名最低TTL
|
||||
public function getMinTTL()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private function convertType($type)
|
||||
{
|
||||
$convert_dict = ['REDIRECT_URL' => 'EXPLICIT_URL', 'FORWARD_URL' => 'IMPLICIT_URL'];
|
||||
if (array_key_exists($type, $convert_dict)) {
|
||||
return $convert_dict[$type];
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
private function send_request($method, $action, $params = [])
|
||||
{
|
||||
$path = '/'.$this->version.'/regions/'.$this->region.$action;
|
||||
try{
|
||||
return $this->client->request($method, $path, $params);
|
||||
}catch(Exception $e){
|
||||
$this->setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function setError($message)
|
||||
{
|
||||
$this->error = $message;
|
||||
//file_put_contents('logs.txt',date('H:i:s').' '.$message."\r\n", FILE_APPEND);
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ class namesilo implements DnsInterface
|
||||
}
|
||||
if(!isNullOrEmpty($SubDomain)){
|
||||
$list = array_values(array_filter($list, function($v) use ($SubDomain){
|
||||
return $v['Name'] == $SubDomain;
|
||||
return strcasecmp($v['Name'], $SubDomain) === 0;
|
||||
}));
|
||||
}else{
|
||||
if(!isNullOrEmpty($KeyWord)){
|
||||
@@ -120,6 +120,7 @@ class namesilo implements DnsInterface
|
||||
//添加解析记录
|
||||
public function addDomainRecord($Name, $Type, $Value, $Line = 'default', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
|
||||
{
|
||||
if ($Name == '@') $Name = '';
|
||||
$param = ['domain' => $this->domain, 'rrtype' => $Type, 'rrhost' => $Name, 'rrvalue' => $Value, 'rrttl' => $TTL];
|
||||
if ($Type == 'MX') $param['rrdistance'] = intval($MX);
|
||||
$data = $this->send_reuqest('dnsAddRecord', $param);
|
||||
@@ -129,6 +130,7 @@ class namesilo implements DnsInterface
|
||||
//修改解析记录
|
||||
public function updateDomainRecord($RecordId, $Name, $Type, $Value, $Line = 'default', $TTL = 600, $MX = 1, $Weight = null, $Remark = null)
|
||||
{
|
||||
if ($Name == '@') $Name = '';
|
||||
$param = ['domain' => $this->domain, 'rrid' => $RecordId, 'rrtype' => $Type, 'rrhost' => $Name, 'rrvalue' => $Value, 'rrttl' => $TTL];
|
||||
if ($Type == 'MX') $param['rrdistance'] = intval($MX);
|
||||
$data = $this->send_reuqest('dnsUpdateRecord', $param);
|
||||
|
||||
@@ -93,7 +93,7 @@ class powerdns implements DnsInterface
|
||||
cache('powerdns_' . $this->domainid, $data['rrsets'], 86400);
|
||||
if (!isNullOrEmpty($SubDomain)) {
|
||||
$list = array_values(array_filter($list, function ($v) use ($SubDomain) {
|
||||
return $v['Name'] == $SubDomain;
|
||||
return strcasecmp($v['Name'], $SubDomain) === 0;
|
||||
}));
|
||||
} else {
|
||||
if (!isNullOrEmpty($KeyWord)) {
|
||||
|
||||
@@ -13,6 +13,7 @@ class CertTaskService
|
||||
{
|
||||
$this->execute_deploy();
|
||||
$this->execute_order();
|
||||
(new ExpireNoticeService())->task();
|
||||
config_set('certtask_time', date("Y-m-d H:i:s"));
|
||||
echo 'done'.PHP_EOL;
|
||||
}
|
||||
@@ -20,7 +21,7 @@ class CertTaskService
|
||||
private function execute_order()
|
||||
{
|
||||
$days = config_get('cert_renewdays', 7);
|
||||
$list = Db::name('cert_order')->field('id,status,issend')->whereRaw('status NOT IN (3,4) AND (retrytime IS NULL OR retrytime<NOW()) OR status=3 AND expiretime<:expiretime', ['expiretime' => date('Y-m-d H:i:s', time() + $days * 86400)])->select();
|
||||
$list = Db::name('cert_order')->field('id,status,issend')->whereRaw('status NOT IN (3,4) AND (retrytime IS NULL OR retrytime<NOW()) OR status=3 AND isauto=1 AND expiretime<:expiretime', ['expiretime' => date('Y-m-d H:i:s', time() + $days * 86400)])->select();
|
||||
//print_r($list);exit;
|
||||
$failcount = 0;
|
||||
foreach ($list as $row) {
|
||||
@@ -67,7 +68,7 @@ class CertTaskService
|
||||
}
|
||||
}
|
||||
|
||||
$list = Db::name('cert_deploy')->field('id,status,issend')->whereRaw('status IN (0,-1) AND (retrytime IS NULL OR retrytime<NOW())')->select();
|
||||
$list = Db::name('cert_deploy')->field('id,status,issend')->whereRaw('active=1 AND status IN (0,-1) AND (retrytime IS NULL OR retrytime<NOW())')->select();
|
||||
//print_r($list);exit;
|
||||
$count = 0;
|
||||
foreach ($list as $row) {
|
||||
|
||||
102
app/service/ExpireNoticeService.php
Normal file
102
app/service/ExpireNoticeService.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace app\service;
|
||||
|
||||
use Exception;
|
||||
use think\facade\Db;
|
||||
use app\utils\MsgNotice;
|
||||
|
||||
/**
|
||||
* 域名到期提醒
|
||||
*/
|
||||
class ExpireNoticeService
|
||||
{
|
||||
|
||||
public function updateDomainDate($id, $domain)
|
||||
{
|
||||
try {
|
||||
[$regTime, $expireTime] = getDomainDate($domain);
|
||||
Db::name('domain')->where('id', $id)->update(['regtime' => $regTime, 'expiretime' => $expireTime, 'checktime' => date('Y-m-d H:i:s'), 'checkstatus' => 1]);
|
||||
return ['code' => 0, 'regTime' => $regTime, 'expireTime' => $expireTime, 'msg' => 'Success'];
|
||||
} catch (Exception $e) {
|
||||
Db::name('domain')->where('id', $id)->update(['checktime' => date('Y-m-d H:i:s'), 'checkstatus' => 2]);
|
||||
return ['code' => -1, 'msg' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
public function task()
|
||||
{
|
||||
$count = $this->refreshDomainList();
|
||||
if ($count > 0) return;
|
||||
|
||||
$days = config_get('expire_noticedays');
|
||||
$max_day = 30;
|
||||
if (!empty($days)) {
|
||||
$days = explode(',', $days);
|
||||
$days = array_map('intval', $days);
|
||||
$max_day = max($days) + 1;
|
||||
}
|
||||
$count = $this->refreshExpiringDomainList($max_day);
|
||||
if ($count > 0) return;
|
||||
|
||||
if (!empty($days) && (config_get('expire_notice_mail') == '1' || config_get('expire_notice_wxtpl') == '1' || config_get('expire_notice_tgbot') == '1' || config_get('expire_notice_webhook') == '1') && date('H') >= 9) {
|
||||
$this->noticeExpiringDomainList($max_day, $days);
|
||||
}
|
||||
}
|
||||
|
||||
private function refreshDomainList()
|
||||
{
|
||||
$domainList = Db::name('domain')->field('id,name')->where('expiretime', null)->where('checkstatus', 0)->select();
|
||||
$count = 0;
|
||||
foreach ($domainList as $domain) {
|
||||
$res = $this->updateDomainDate($domain['id'], $domain['name']);
|
||||
if ($res['code'] == 0) {
|
||||
echo '域名: ' . $domain['name'] . ' 注册时间: ' . $res['regTime'] . ' 到期时间: ' . $res['expireTime'] . PHP_EOL;
|
||||
} else {
|
||||
echo '域名: ' . $domain['name'] . ' 更新失败,' . $res['msg'] . PHP_EOL;
|
||||
}
|
||||
$count++;
|
||||
if ($count >= 5) break;
|
||||
sleep(1);
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function refreshExpiringDomainList($max_day)
|
||||
{
|
||||
$domainList = Db::name('domain')->field('id,name')->whereRaw('expiretime>=(NOW() - INTERVAL 5 DAY) AND expiretime<=(NOW() + INTERVAL ' . $max_day . ' DAY) AND checktime<=(NOW() - INTERVAL 1 DAY)')->select();
|
||||
$count = 0;
|
||||
foreach ($domainList as $domain) {
|
||||
$res = $this->updateDomainDate($domain['id'], $domain['name']);
|
||||
if ($res['code'] == 0) {
|
||||
echo '域名: ' . $domain['name'] . ' 注册时间: ' . $res['regTime'] . ' 到期时间: ' . $res['expireTime'] . PHP_EOL;
|
||||
} else {
|
||||
echo '域名: ' . $domain['name'] . ' 更新失败,' . $res['msg'] . PHP_EOL;
|
||||
}
|
||||
$count++;
|
||||
if ($count >= 5) break;
|
||||
sleep(1);
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function noticeExpiringDomainList($max_day, $days)
|
||||
{
|
||||
$domainList = Db::name('domain')->field('id,name,expiretime')->whereRaw('expiretime>=NOW() AND expiretime<=(NOW() + INTERVAL ' . $max_day . ' DAY) AND is_notice=1 AND (noticetime IS NULL OR noticetime<=(NOW() - INTERVAL 20 HOUR))')->order('expiretime', 'asc')->select();
|
||||
$noticeList = [];
|
||||
foreach ($domainList as $domain) {
|
||||
$expireDay = intval((strtotime($domain['expiretime']) - time()) / 86400);
|
||||
if (in_array($expireDay, $days)) {
|
||||
$noticeList[$expireDay][] = ['id' => $domain['id'], 'name' => $domain['name'], 'expiretime' => $domain['expiretime']];
|
||||
}
|
||||
}
|
||||
if (!empty($noticeList)) {
|
||||
foreach ($noticeList as $day => $list) {
|
||||
$ids = array_column($list, 'id');
|
||||
Db::name('domain')->whereIn('id', $ids)->update(['noticetime' => date('Y-m-d H:i:s')]);
|
||||
MsgNotice::expire_notice_send($day, $list);
|
||||
echo '域名到期提醒: ' . $day . '天内到期的' . count($ids) . '个域名已发送' . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,6 +157,9 @@ class OptimizeService
|
||||
if ($row['type'] == 1 && $line == 'CT') {
|
||||
$line = 'DEF';
|
||||
}
|
||||
if (!isset(DnsHelper::$line_name[$drow['type']][$line])) {
|
||||
continue;
|
||||
}
|
||||
$line_name = DnsHelper::$line_name[$drow['type']][$line];
|
||||
$this->process_dns_line($dns, $row, $domainRecords['list'], $record_num, $get_ips, $line_name, $ip_type);
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ class TaskRunner
|
||||
if ($row['checktype'] == 2) {
|
||||
$result = CheckUtils::curl($row['checkurl'], $row['timeout'], $row['main_value'], $row['proxy'] == 1);
|
||||
} elseif ($row['checktype'] == 1) {
|
||||
$result = CheckUtils::tcp($row['main_value'], $row['tcpport'], $row['timeout']);
|
||||
$result = CheckUtils::tcp($row['main_value'], $row['checkurl'], $row['tcpport'], $row['timeout']);
|
||||
} else {
|
||||
$result = CheckUtils::ping($row['main_value']);
|
||||
$result = CheckUtils::ping($row['main_value'], $row['checkurl']);
|
||||
}
|
||||
|
||||
$action = 0;
|
||||
|
||||
@@ -5,7 +5,7 @@ CREATE TABLE `dnsmgr_config` (
|
||||
PRIMARY KEY (`key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
INSERT INTO `dnsmgr_config` VALUES ('version', '1028');
|
||||
INSERT INTO `dnsmgr_config` VALUES ('version', '1033');
|
||||
INSERT INTO `dnsmgr_config` VALUES ('notice_mail', '0');
|
||||
INSERT INTO `dnsmgr_config` VALUES ('notice_wxtpl', '0');
|
||||
INSERT INTO `dnsmgr_config` VALUES ('mail_smtp', 'smtp.qq.com');
|
||||
@@ -35,6 +35,12 @@ CREATE TABLE `dnsmgr_domain` (
|
||||
`is_sso` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`recordcount` int(1) NOT NULL DEFAULT '0',
|
||||
`remark` varchar(100) DEFAULT NULL,
|
||||
`is_notice` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`regtime` datetime DEFAULT NULL,
|
||||
`expiretime` datetime DEFAULT NULL,
|
||||
`checktime` datetime DEFAULT NULL,
|
||||
`noticetime` datetime DEFAULT NULL,
|
||||
`checkstatus` tinyint(1) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `name` (`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
@@ -155,4 +155,12 @@ ALTER TABLE `dnsmgr_account`
|
||||
ADD COLUMN `proxy` tinyint(1) NOT NULL DEFAULT '0';
|
||||
|
||||
ALTER TABLE `dnsmgr_dmtask`
|
||||
ADD COLUMN `cdn` tinyint(1) NOT NULL DEFAULT 0;
|
||||
ADD COLUMN `cdn` tinyint(1) NOT NULL DEFAULT 0;
|
||||
|
||||
ALTER TABLE `dnsmgr_domain`
|
||||
ADD COLUMN `is_notice` tinyint(1) NOT NULL DEFAULT '0',
|
||||
ADD COLUMN `regtime` datetime DEFAULT NULL,
|
||||
ADD COLUMN `expiretime` datetime DEFAULT NULL,
|
||||
ADD COLUMN `checktime` datetime DEFAULT NULL,
|
||||
ADD COLUMN `noticetime` datetime DEFAULT NULL,
|
||||
ADD COLUMN `checkstatus` tinyint(1) NOT NULL DEFAULT '0';
|
||||
@@ -72,8 +72,9 @@ class CheckUtils
|
||||
return ['status' => $status, 'errmsg' => $errmsg, 'usetime' => $usetime];
|
||||
}
|
||||
|
||||
public static function tcp($target, $port, $timeout)
|
||||
public static function tcp($target, $ip, $port, $timeout)
|
||||
{
|
||||
if (!empty($ip) && filter_var($ip, FILTER_VALIDATE_IP)) $target = $ip;
|
||||
if (substr($target, -1) == '.') $target = substr($target, 0, -1);
|
||||
if (!filter_var($target, FILTER_VALIDATE_IP) && checkDomain($target)) {
|
||||
$target = gethostbyname($target);
|
||||
@@ -95,9 +96,10 @@ class CheckUtils
|
||||
return ['status' => $status, 'errmsg' => $errStr, 'usetime' => $usetime];
|
||||
}
|
||||
|
||||
public static function ping($target)
|
||||
public static function ping($target, $ip)
|
||||
{
|
||||
if (!function_exists('exec')) return ['status' => false, 'errmsg' => 'exec函数不可用', 'usetime' => 0];
|
||||
if (!empty($ip) && filter_var($ip, FILTER_VALIDATE_IP)) $target = $ip;
|
||||
if (substr($target, -1) == '.') $target = substr($target, 0, -1);
|
||||
if (!filter_var($target, FILTER_VALIDATE_IP) && checkDomain($target)) {
|
||||
$target = gethostbyname($target);
|
||||
|
||||
@@ -133,6 +133,34 @@ class MsgNotice
|
||||
}
|
||||
}
|
||||
|
||||
public static function expire_notice_send($day, $list)
|
||||
{
|
||||
$mail_title = '您有'.count($list).'个域名即将在'.$day.'天后到期';
|
||||
$mail_content = '尊敬的用户,您好:您有'.count($list).'个域名即将在'.$day.'天后到期!<br/><b>域名&到期时间:</b><br/>';
|
||||
foreach ($list as $domain) {
|
||||
$mail_content .= '<b>'.$domain['name'].'</b> - '.$domain['expiretime'].'<br/>';
|
||||
}
|
||||
$mail_content .= '<br/><font color="grey">'.self::$sitename.'</font><br/><font color="grey">'.date('Y-m-d H:i:s').'</font>';
|
||||
|
||||
if (config_get('expire_notice_mail') == 1 || config_get('expire_notice_mail') == 2) {
|
||||
$mail_name = config_get('mail_recv') ? config_get('mail_recv') : config_get('mail_name');
|
||||
self::send_mail($mail_name, $mail_title, $mail_content);
|
||||
}
|
||||
if (config_get('expire_notice_wxtpl') == 1 || config_get('expire_notice_wxtpl') == 2) {
|
||||
$content = str_replace(['<br/>', '<b>', '</b>'], ["\n\n", '**', '**'], $mail_content);
|
||||
self::send_wechat_tplmsg($mail_title, strip_tags($content));
|
||||
}
|
||||
if (config_get('expire_notice_tgbot') == 1 || config_get('expire_notice_tgbot') == 2) {
|
||||
$content = str_replace('<br/>', "\n", $mail_content);
|
||||
$content = "<strong>".$mail_title."</strong>\n".strip_tags($content);
|
||||
self::send_telegram_bot($content);
|
||||
}
|
||||
if (config_get('expire_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)
|
||||
{
|
||||
$mail_type = config_get('mail_type');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{block name="title"}SSL证书订单列表{/block}
|
||||
{block name="main"}
|
||||
<style>
|
||||
tbody tr>td:nth-child(4){overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:180px;}
|
||||
tbody tr>td:nth-child(5){overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:180px;}
|
||||
.tips{cursor:pointer;}
|
||||
textarea.form-control{margin-bottom: 3px;}
|
||||
hr{margin-top: 10px;margin-bottom: 15px;border-top: 1px solid #eee;}
|
||||
@@ -41,6 +41,10 @@ pre.pre-log{height: 330px;overflow-y: auto;width: 100%;background-color: rgba(51
|
||||
<li><a href="/cert/order/import">导入已有证书</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<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="javascript:operation('delete')">删除</a></li><li><a href="javascript:operation('reset')">重置订单</a></li><li><a href="javascript:operation('open')">开启续签</a></li><li><a href="javascript:operation('close')">关闭续签</a></li></ul>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
@@ -71,6 +75,10 @@ $(document).ready(function(){
|
||||
classes: 'table table-striped table-hover table-bordered',
|
||||
uniqueId: 'id',
|
||||
columns: [
|
||||
{
|
||||
field: '',
|
||||
checkbox: true
|
||||
},
|
||||
{
|
||||
field: 'id',
|
||||
title: 'ID'
|
||||
@@ -423,5 +431,39 @@ function showLog(processid){
|
||||
}
|
||||
}, 'json');
|
||||
}
|
||||
function operation(action){
|
||||
var rows = $("#listTable").bootstrapTable('getSelections');
|
||||
if(rows.length == 0){
|
||||
layer.msg('请选择要操作的订单');
|
||||
return;
|
||||
}
|
||||
var ids = [];
|
||||
for(var i in rows){
|
||||
ids.push(rows[i].id);
|
||||
}
|
||||
if(action == 'delete'){
|
||||
if(!confirm('确定要删除所选证书吗?删除后将无法再次下载')) return;
|
||||
}else if(action == 'reset'){
|
||||
if(!confirm('重置订单后,订单将变成待提交状态,是否确定重置?')) return;
|
||||
}
|
||||
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/cert/order/operation',
|
||||
data : {action: action, ids: ids},
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
layer.closeAll();
|
||||
layer.alert(data.msg, {icon: 1});
|
||||
searchRefresh();
|
||||
}else{
|
||||
layer.alert(data.msg, {icon: 2});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{/block}
|
||||
@@ -2,7 +2,7 @@
|
||||
{block name="title"}SSL证书自动部署任务{/block}
|
||||
{block name="main"}
|
||||
<style>
|
||||
tbody tr>td:nth-child(3){max-width:180px;}
|
||||
tbody tr>td:nth-child(4){max-width:180px;}
|
||||
.tips{cursor:pointer;}
|
||||
pre.pre-log{height: 330px;overflow-y: auto;width: 100%;background-color: rgba(51, 51, 51, 1);white-space: pre-line;color: rgba(236, 236, 236, 1)}
|
||||
</style>
|
||||
@@ -35,6 +35,10 @@ pre.pre-log{height: 330px;overflow-y: auto;width: 100%;background-color: rgba(51
|
||||
<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/deploy/add" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>
|
||||
<div class="btn-group" role="group">
|
||||
<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="javascript:operation('delete')">删除</a></li><li><a href="javascript:operation('reset')">重置任务</a></li><li><a href="javascript:operation('open')">开启任务</a></li><li><a href="javascript:operation('close')">停止任务</a></li></ul>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<table id="listTable">
|
||||
@@ -64,6 +68,10 @@ $(document).ready(function(){
|
||||
classes: 'table table-striped table-hover table-bordered',
|
||||
uniqueId: 'id',
|
||||
columns: [
|
||||
{
|
||||
field: '',
|
||||
checkbox: true
|
||||
},
|
||||
{
|
||||
field: 'id',
|
||||
title: 'ID'
|
||||
@@ -285,5 +293,39 @@ function showLog(processid){
|
||||
}
|
||||
}, 'json');
|
||||
}
|
||||
function operation(action){
|
||||
var rows = $("#listTable").bootstrapTable('getSelections');
|
||||
if(rows.length == 0){
|
||||
layer.msg('请选择要操作的任务');
|
||||
return;
|
||||
}
|
||||
var ids = [];
|
||||
for(var i in rows){
|
||||
ids.push(rows[i].id);
|
||||
}
|
||||
if(action == 'delete'){
|
||||
if(!confirm('确定要删除所选自动部署任务吗?')) return;
|
||||
}else if(action == 'reset'){
|
||||
if(!confirm('重置任务后,任务将变成待处理状态,是否确定重置?')) return;
|
||||
}
|
||||
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/cert/deploy/operation',
|
||||
data : {action: action, ids: ids},
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
layer.closeAll();
|
||||
layer.alert(data.msg, {icon: 1});
|
||||
searchRefresh();
|
||||
}else{
|
||||
layer.alert(data.msg, {icon: 2});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{/block}
|
||||
@@ -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,domain_add')}">
|
||||
<li class="{:checkIfActive('domain,record,record_log,record_batch_add,domain_add,weight,record_batch_add2,record_batch_edit2,expire_notice')}">
|
||||
<a href="/domain"><i class="fa fa-list-ul fa-fw"></i> <span>域名管理</span></a>
|
||||
</li>
|
||||
{if request()->user['level'] eq 2}
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
{extend name="common/layout" /}
|
||||
{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;">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title">容灾切换通知设置</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">邮件通知</label>
|
||||
<div class="col-sm-9"><select class="form-control" name="notice_mail" default="{:config_get('notice_mail')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">微信公众号通知</label>
|
||||
<div class="col-sm-9"><select class="form-control" name="notice_wxtpl" default="{:config_get('notice_wxtpl')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Telegram机器人通知</label>
|
||||
<div class="col-sm-9"><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">
|
||||
<div class="col-sm-offset-3 col-sm-9"><input type="submit" name="submit" value="保存" class="btn btn-primary btn-block"/></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title">发信邮箱设置</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">发信模式</label>
|
||||
<div class="col-sm-9"><select class="form-control" name="mail_type" default="{:config_get('mail_type')}"><option value="0">SMTP发信</option><option value="1">搜狐Sendcloud</option><option value="2">阿里云邮件推送</option></select></div>
|
||||
</div>
|
||||
<div id="frame_set1">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">SMTP服务器</label>
|
||||
<div class="col-sm-9"><input type="text" name="mail_smtp" value="{:config_get('mail_smtp')}" class="form-control"/></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">SMTP端口</label>
|
||||
<div class="col-sm-9"><input type="text" name="mail_port" value="{:config_get('mail_port')}" class="form-control"/></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">邮箱账号</label>
|
||||
<div class="col-sm-9"><input type="text" name="mail_name" value="{:config_get('mail_name')}" class="form-control"/></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">邮箱密码</label>
|
||||
<div class="col-sm-9"><input type="text" name="mail_pwd" value="{:config_get('mail_pwd')}" class="form-control"/></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="frame_set2">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">API_USER</label>
|
||||
<div class="col-sm-9"><input type="text" name="mail_apiuser" value="{:config_get('mail_apiuser')}" class="form-control"/></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">API_KEY</label>
|
||||
<div class="col-sm-9"><input type="text" name="mail_apikey" value="{:config_get('mail_apikey')}" class="form-control"/></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">发信邮箱</label>
|
||||
<div class="col-sm-9"><input type="text" name="mail_name2" value="{:config_get('mail_name')}" class="form-control"/></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">收信邮箱</label>
|
||||
<div class="col-sm-9"><input type="text" name="mail_recv" value="{:config_get('mail_recv')}" class="form-control" placeholder="不填默认为发信邮箱"/></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:mailtest()" class="btn btn-default btn-block">发送测试邮件</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<span class="glyphicon glyphicon-info-sign"></span>
|
||||
使用普通模式发信时,建议使用QQ邮箱,SMTP服务器smtp.qq.com,端口465或587,密码是QQ邮箱设置界面生成的<a href="https://service.mail.qq.com/detail/0/75" target="_blank" rel="noreferrer">授权码</a>。<br/>阿里云邮件推送:<a href="https://www.aliyun.com/product/directmail" target="_blank" rel="noreferrer">点此进入</a>|<a href="https://usercenter.console.aliyun.com/#/manage/ak" target="_blank" rel="noreferrer">获取AK/SK</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title">微信公众号消息接口设置</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">appToken</label>
|
||||
<div class="col-sm-9"><input type="text" name="wechat_apptoken" value="{:config_get('wechat_apptoken')}" class="form-control"/></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">用户UID</label>
|
||||
<div class="col-sm-9"><input type="text" name="wechat_appuid" value="{:config_get('wechat_appuid')}" 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"/></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<b>WxPusher:</b><a href="https://wxpusher.zjiecode.com/admin/" target="_blank" rel="noopener noreferrer">点此进入</a> ,注册并且创建应用 -> 将appToken填写到上方输入框 -> 扫码关注应用 -> 在用户列表查看自己的UID填写到上方输入框<br/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title">Telegram机器人接口设置</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">Token</label>
|
||||
<div class="col-sm-9"><input type="text" name="tgbot_token" value="{:config_get('tgbot_token')}" class="form-control"/></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Chat Id</label>
|
||||
<div class="col-sm-9"><input type="text" name="tgbot_chatid" value="{:config_get('tgbot_chatid')}" class="form-control"/></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">使用代理服务器</label>
|
||||
<div class="col-sm-9"><select class="form-control" name="tgbot_proxy" default="{:config_get('tgbot_proxy')}"><option value="0">否</option><option value="1">是</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"/>
|
||||
<a href="javascript:tgbottest()" class="btn btn-default btn-block">发送测试消息</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
与<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>
|
||||
</div>
|
||||
{/block}
|
||||
{block name="script"}
|
||||
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
|
||||
<script>
|
||||
var items = $("select[default]");
|
||||
for (i = 0; i < items.length; i++) {
|
||||
$(items[i]).val($(items[i]).attr("default")||0);
|
||||
}
|
||||
$("select[name='mail_type']").change(function(){
|
||||
if($(this).val() == 0){
|
||||
$("#frame_set1").show();
|
||||
$("#frame_set2").hide();
|
||||
}else{
|
||||
$("#frame_set1").hide();
|
||||
$("#frame_set2").show();
|
||||
}
|
||||
});
|
||||
$("select[name='mail_type']").change();
|
||||
function saveSetting(obj){
|
||||
var ii = layer.load(2, {shade:[0.1,'#fff']});
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '',
|
||||
data : $(obj).serialize(),
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
layer.alert('设置保存成功!<br/>重启检测进程或容器后生效', {
|
||||
icon: 1,
|
||||
closeBtn: false
|
||||
}, function(){
|
||||
window.location.reload()
|
||||
});
|
||||
}else{
|
||||
layer.alert(data.msg, {icon: 2})
|
||||
}
|
||||
},
|
||||
error:function(data){
|
||||
layer.close(ii);
|
||||
layer.msg('服务器错误');
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
function mailtest(){
|
||||
var ii = layer.load(2, {shade:[0.1,'#fff']});
|
||||
$.ajax({
|
||||
type : 'GET',
|
||||
url : '/dmonitor/mailtest',
|
||||
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('服务器错误');
|
||||
}
|
||||
});
|
||||
}
|
||||
function tgbottest(){
|
||||
var ii = layer.load(2, {shade:[0.1,'#fff']});
|
||||
$.ajax({
|
||||
type : 'GET',
|
||||
url : '/dmonitor/tgbottest',
|
||||
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}
|
||||
@@ -1,110 +0,0 @@
|
||||
{extend name="common/layout" /}
|
||||
{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;">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title">代理服务器设置</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">代理IP</label>
|
||||
<div class="col-sm-9"><input type="text" name="proxy_server" value="{:config_get('proxy_server')}" class="form-control"/></div>
|
||||
</div><br/>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">代理端口</label>
|
||||
<div class="col-sm-9"><input type="text" name="proxy_port" value="{:config_get('proxy_port')}" class="form-control"/></div>
|
||||
</div><br/>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">代理账号</label>
|
||||
<div class="col-sm-9"><input type="text" name="proxy_user" value="{:config_get('proxy_user')}" class="form-control" placeholder="没有请留空"/></div>
|
||||
</div><br/>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">代理密码</label>
|
||||
<div class="col-sm-9"><input type="text" name="proxy_pwd" value="{:config_get('proxy_pwd')}" class="form-control" placeholder="没有请留空"/></div>
|
||||
</div><br/>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">代理协议</label>
|
||||
<div class="col-sm-9"><select class="form-control" name="proxy_type" default="{:config_get('proxy_type')}">
|
||||
<option value="http">HTTP</option>
|
||||
<option value="https">HTTPS</option>
|
||||
<option value="sock4">SOCK4</option>
|
||||
<option value="sock5">SOCK5</option>
|
||||
</select></div>
|
||||
</div><br/>
|
||||
<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"/><br/>
|
||||
<a href="javascript:proxytest()" class="btn btn-default btn-block">测试连通性</a></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
{block name="script"}
|
||||
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
|
||||
<script>
|
||||
var items = $("select[default]");
|
||||
for (i = 0; i < items.length; i++) {
|
||||
$(items[i]).val($(items[i]).attr("default")||0);
|
||||
}
|
||||
function saveSetting(obj){
|
||||
var ii = layer.load(2, {shade:[0.1,'#fff']});
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '',
|
||||
data : $(obj).serialize(),
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
layer.alert('设置保存成功!', {
|
||||
icon: 1,
|
||||
closeBtn: false
|
||||
}, function(){
|
||||
window.location.reload()
|
||||
});
|
||||
}else{
|
||||
layer.alert(data.msg, {icon: 2})
|
||||
}
|
||||
},
|
||||
error:function(data){
|
||||
layer.close(ii);
|
||||
layer.msg('服务器错误');
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
function proxytest(){
|
||||
var proxy_server = $("input[name='proxy_server']").val();
|
||||
var proxy_port = $("input[name='proxy_port']").val();
|
||||
var proxy_user = $("input[name='proxy_user']").val();
|
||||
var proxy_pwd = $("input[name='proxy_pwd']").val();
|
||||
var proxy_type = $("select[name='proxy_type']").val();
|
||||
if(proxy_server=='' || proxy_port==''){
|
||||
layer.alert('代理服务器和端口不能为空!');
|
||||
return false;
|
||||
}
|
||||
var ii = layer.load(2, {shade:[0.1,'#fff']});
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/dmonitor/proxytest',
|
||||
data : {proxy_server:proxy_server, proxy_port:proxy_port, proxy_user:proxy_user, proxy_pwd:proxy_pwd, proxy_type:proxy_type},
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
layer.alert('连通性测试成功!', {icon: 1})
|
||||
}else{
|
||||
layer.alert('连通性测试失败:'+data.msg, {icon: 2})
|
||||
}
|
||||
},
|
||||
error:function(data){
|
||||
layer.close(ii);
|
||||
layer.msg('服务器错误');
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{/block}
|
||||
@@ -14,11 +14,16 @@ tbody tr>td:nth-child(2){overflow: hidden;text-overflow: ellipsis;white-space: n
|
||||
<label>搜索</label>
|
||||
<div class="form-group">
|
||||
<select name="type" class="form-control"><option value="1">域名</option><option value="3">解析记录</option><option value="4">备用解析记录</option><option value="2">解析记录ID</option><option value="5">备注</option></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="kw" placeholder="">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<select name="status" class="form-control"><option value="">健康状况</option><option value="0">正常</option><option value="1">异常</option></select>
|
||||
</div>
|
||||
</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="/dmonitor/task/add" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
.control-label[is-required]:before {
|
||||
content: "*";
|
||||
color: #f56c6c;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.tips{color: #f6a838; padding-left: 5px;}
|
||||
</style>
|
||||
<div class="row" id="app">
|
||||
@@ -16,15 +21,20 @@
|
||||
<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 col-xs-12 control-label no-padding-right">域名选择</label>
|
||||
<div class="col-sm-3 col-xs-5"><input type="text" name="rr" v-model="set.rr" placeholder="主机记录" class="form-control" required></div>
|
||||
<div class="col-sm-3 col-xs-7 dselect"><select name="did" v-model="set.did" class="form-control" required>
|
||||
<option value="">--主域名--</option>
|
||||
<option v-for="option in domainList" :value="option.id">{{option.name}}</option>
|
||||
</select></div>
|
||||
<label class="col-sm-3 col-xs-12 control-label no-padding-right" is-required>域名选择</label>
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group">
|
||||
<input type="text" name="rr" v-model="set.rr" placeholder="主机记录" class="form-control" required>
|
||||
<span class="input-group-addon">.</span>
|
||||
<select name="did" v-model="set.did" class="form-control" required>
|
||||
<option value="">--主域名--</option>
|
||||
<option v-for="option in domainList" :value="option.id">{{option.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label no-padding-right">解析记录</label>
|
||||
<label class="col-sm-3 control-label no-padding-right" is-required>解析记录</label>
|
||||
<div class="col-sm-6"><div class="input-group">
|
||||
<select name="recordid" v-model="set.recordid" id="recordid" class="form-control" required>
|
||||
<option v-for="option in recordList" :value="option.RecordId">{{option.Value}} (线路:{{option.LineName}})</option>
|
||||
@@ -35,7 +45,7 @@
|
||||
</div></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label no-padding-right">切换设置</label>
|
||||
<label class="col-sm-3 control-label no-padding-right" is-required>切换设置</label>
|
||||
<div class="col-sm-6">
|
||||
<label class="radio-inline" v-for="option in typeList">
|
||||
<input type="radio" name="type" :value="option.value" v-model="set.type" :disabled="option.disabled"> {{option.label}}
|
||||
@@ -43,7 +53,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-show="set.type==2">
|
||||
<label class="col-sm-3 control-label no-padding-right">备用解析记录</label>
|
||||
<label class="col-sm-3 control-label no-padding-right" is-required>备用解析记录</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="backup_value" v-model="set.backup_value" placeholder="支持填写IPv4或CNAME地址" class="form-control" required>
|
||||
</div>
|
||||
@@ -58,21 +68,27 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-show="set.type<=2">
|
||||
<label class="col-sm-3 control-label no-padding-right">检测协议</label>
|
||||
<label class="col-sm-3 control-label no-padding-right" is-required>检测协议</label>
|
||||
<div class="col-sm-6">
|
||||
<label class="radio-inline" v-for="option in checktypeList">
|
||||
<input type="radio" name="checktype" :value="option.value" v-model="set.checktype" :disabled="option.disabled"> {{option.label}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-show="set.type<=2&&set.checktype<2">
|
||||
<label class="col-sm-3 control-label no-padding-right">指定检测IP</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="checkurl" v-model="set.checkurl" placeholder="留空默认为解析记录值IP" class="form-control" data-bv-uri="true" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-show="set.type<=2&&set.checktype==1">
|
||||
<label class="col-sm-3 control-label no-padding-right">TCP检测端口</label>
|
||||
<label class="col-sm-3 control-label no-padding-right" is-required>TCP检测端口</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="tcpport" v-model="set.tcpport" placeholder="填写TCP端口号" class="form-control" data-bv-integer="true" min="1" max="65535" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-show="set.type<=2&&set.checktype==2">
|
||||
<label class="col-sm-3 control-label no-padding-right">检测URL地址</label>
|
||||
<label class="col-sm-3 control-label no-padding-right" is-required>检测URL地址</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="checkurl" v-model="set.checkurl" placeholder="填写以http(s)://开头的完整地址,http状态码须为2xx/3xx" class="form-control" data-bv-uri="true" required>
|
||||
</div>
|
||||
@@ -89,7 +105,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-show="set.type<=2&&set.checktype>0">
|
||||
<label class="col-sm-3 control-label no-padding-right">最大超时时间</label>
|
||||
<label class="col-sm-3 control-label no-padding-right" is-required>最大超时时间</label>
|
||||
<div class="col-sm-3">
|
||||
<div class="input-group">
|
||||
<input type="text" name="timeout" v-model="set.timeout" placeholder="填写请求最大超时时间" class="form-control" data-bv-integer="true" min="1" required>
|
||||
@@ -98,13 +114,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-show="set.type==3">
|
||||
<label class="col-sm-3 control-label no-padding-right">同域名正常数量<span class="tips" title="" data-toggle="tooltip" data-placement="bottom" data-original-title="与暂停解析配合使用,当同域名正常记录数量<=几条时开启解析"><i class="fa fa-question-circle"></i></span></label>
|
||||
<label class="col-sm-3 control-label no-padding-right" is-required>同域名正常数量<span class="tips" title="" data-toggle="tooltip" data-placement="bottom" data-original-title="与暂停解析配合使用,当同域名正常记录数量<=几条时开启解析"><i class="fa fa-question-circle"></i></span></label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" name="cycle" v-model="set.cycle" placeholder="同域名正常记录数量<=几条时开启解析" class="form-control" data-bv-integer="true" min="0" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label no-padding-right">检测间隔</label>
|
||||
<label class="col-sm-3 control-label no-padding-right" is-required>检测间隔</label>
|
||||
<div class="col-sm-3">
|
||||
<div class="input-group">
|
||||
<input type="text" name="frequency" v-model="set.frequency" placeholder="每次检测的间隔时间" class="form-control" data-bv-integer="true" min="1" required>
|
||||
@@ -113,7 +129,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-show="set.type<=2">
|
||||
<label class="col-sm-3 control-label no-padding-right">确认次数</label>
|
||||
<label class="col-sm-3 control-label no-padding-right" is-required>确认次数</label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" name="cycle" v-model="set.cycle" placeholder="连续失败几次后进行切换" class="form-control" data-bv-integer="true" min="1" required>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-3 col-sm-6"><button type="button" class="btn btn-primary" onclick="save()">保存</button></div>
|
||||
<div class="col-sm-offset-3 col-sm-6"><button type="button" class="btn btn-primary" onclick="save()">添加</button></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -110,7 +110,6 @@ function save(){
|
||||
if(!$("#form-store").data("bootstrapValidator").isValid()){
|
||||
return;
|
||||
}
|
||||
var act = $("#form-store input[name=action]").val();
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
|
||||
176
app/view/domain/batchadd2.html
Normal file
176
app/view/domain/batchadd2.html
Normal file
@@ -0,0 +1,176 @@
|
||||
{extend name="common/layout" /}
|
||||
{block name="title"}批量添加解析{/block}
|
||||
{block name="main"}
|
||||
<style>
|
||||
tbody tr>td:nth-child(3){min-width:300px;word-break:break-all;}
|
||||
</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="/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 col-xs-12 control-label no-padding-right">主机记录&记录值</label>
|
||||
<div class="col-sm-6">
|
||||
<textarea name="record" v-model="set.record" placeholder="主机记录和记录值用空格隔开,一行一个" class="form-control" rows="8" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label no-padding-right">记录类型</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="type" class="form-control" v-model="set.type">
|
||||
<option value="">A / CNAME / AAAA 自动识别</option>
|
||||
<option value="A">A</option>
|
||||
<option value="CNAME">CNAME</option>
|
||||
<option value="AAAA">AAAA</option>
|
||||
<option value="NS">NS</option>
|
||||
<option value="MX">MX</option>
|
||||
<option value="SRV">SRV</option>
|
||||
<option value="TXT">TXT</option>
|
||||
<option value="CAA">CAA</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label no-padding-right">线路类型</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="line" class="form-control" disabled><option value="default">默认</option></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-if="existCF">
|
||||
<label class="col-sm-3 control-label no-padding-right">开启反代</label>
|
||||
<div class="col-sm-6">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="proxy" value="0" v-model="set.proxy"> 否
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="proxy" value="1" v-model="set.proxy"> 是(仅Cloudflare域名)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="display:none" id="mx_type">
|
||||
<label class="col-sm-3 control-label no-padding-right">MX优先级</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" class="form-control" name="mx" v-model="set.mx">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label no-padding-right">TTL</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" class="form-control" name="ttl" v-model="set.ttl" placeholder="指解析结果在DNS服务器中的缓存时间" required>
|
||||
</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>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><h3 class="panel-title">解析记录添加结果</h3></div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-striped table-hover table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>域名</th>
|
||||
<th>添加结果</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in domainList">
|
||||
<td>{{item.id}}</td>
|
||||
<td><img :src="'/static/images/'+item.type+'.ico'" class="type-logo"></img><a :href="'/record/'+item.id" target="_blank">{{item.name}}</a></td>
|
||||
<td v-html="item.result"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</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: {
|
||||
domainList: [],
|
||||
set: {
|
||||
record: '',
|
||||
type: '',
|
||||
mx: 10,
|
||||
ttl: 600,
|
||||
proxy: 0,
|
||||
},
|
||||
existCF: false,
|
||||
},
|
||||
watch: {
|
||||
'set.type': function(val){
|
||||
if(val == 'MX'){
|
||||
$("#mx_type").show();
|
||||
}else{
|
||||
$("#mx_type").hide();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.domainList = JSON.parse(sessionStorage.getItem('domains')) || [];
|
||||
if(this.domainList.length == 0){
|
||||
layer.alert('请先选中要添加解析的域名', {icon: 2}, function(){
|
||||
window.location.href = '/domain';
|
||||
});
|
||||
}
|
||||
for(var i=0; i<this.domainList.length; i++){
|
||||
this.$set(this.domainList[i], 'result', '<span class="text-muted">待添加</span>');
|
||||
}
|
||||
this.existCF = this.domainList.some(item => item.type === 'cloudflare');
|
||||
},
|
||||
methods: {
|
||||
async save(id){
|
||||
var that = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: '/record/batchadd/'+id,
|
||||
data: that.set,
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
resolve(data);
|
||||
},
|
||||
error: function() {
|
||||
reject('服务器错误');
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
async submit(){
|
||||
if(this.set.record == ''){
|
||||
layer.alert('请填写主机记录和记录值', {icon: 2});
|
||||
return;
|
||||
}
|
||||
if(this.set.ttl == ''){
|
||||
layer.alert('请填写TTL', {icon: 2});
|
||||
return;
|
||||
}
|
||||
if(this.set.type == 'MX' && this.set.mx == ''){
|
||||
layer.alert('请填写MX优先级', {icon: 2});
|
||||
return;
|
||||
}
|
||||
var ii = layer.load(2);
|
||||
for(var i=0; i<this.domainList.length; i++){
|
||||
this.domainList[i].result = '<span class="text-yellow"><i class="fa fa-refresh fa-spin fa-fw"></i> 正在添加</span>';
|
||||
var res = await this.save(this.domainList[i].id);
|
||||
if(res.code == 0){
|
||||
this.domainList[i].result = '<span class="text-green">'+res.msg+'</span>';
|
||||
}else{
|
||||
this.domainList[i].result = '<span class="text-red">'+res.msg+'</span>';
|
||||
}
|
||||
}
|
||||
layer.close(ii);
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
{/block}
|
||||
157
app/view/domain/batchedit.html
Normal file
157
app/view/domain/batchedit.html
Normal file
@@ -0,0 +1,157 @@
|
||||
{extend name="common/layout" /}
|
||||
{block name="title"}批量修改解析{/block}
|
||||
{block name="main"}
|
||||
<style>
|
||||
tbody tr>td:nth-child(3){min-width:300px;word-break:break-all;}
|
||||
</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="/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 no-padding-right">主机记录</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" class="form-control" name="name" v-model="set.name" placeholder="填写已有的主机记录" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label no-padding-right">记录类型</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="type" class="form-control" v-model="set.type">
|
||||
<option value="A">A</option>
|
||||
<option value="CNAME">CNAME</option>
|
||||
<option value="AAAA">AAAA</option>
|
||||
<option value="NS">NS</option>
|
||||
<option value="MX">MX</option>
|
||||
<option value="SRV">SRV</option>
|
||||
<option value="TXT">TXT</option>
|
||||
<option value="CAA">CAA</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label no-padding-right">线路类型</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="line" class="form-control" disabled><option value="default">默认</option></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label no-padding-right">记录值</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" class="form-control" name="value" v-model="set.value" placeholder="输入新的记录值" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="display:none" id="mx_type">
|
||||
<label class="col-sm-3 control-label no-padding-right">MX优先级</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" class="form-control" name="mx" v-model="set.mx" placeholder="留空则不修改">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label no-padding-right">TTL</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" class="form-control" name="ttl" v-model="set.ttl" placeholder="留空则不修改">
|
||||
</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>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><h3 class="panel-title">解析记录修改结果</h3></div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-striped table-hover table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>域名</th>
|
||||
<th>修改结果</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in domainList">
|
||||
<td>{{item.id}}</td>
|
||||
<td><img :src="'/static/images/'+item.type+'.ico'" class="type-logo"></img><a :href="'/record/'+item.id" target="_blank">{{item.name}}</a></td>
|
||||
<td v-html="item.result"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</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: {
|
||||
domainList: [],
|
||||
set: {
|
||||
id: '',
|
||||
name: '',
|
||||
type: 'A',
|
||||
value: '',
|
||||
mx: '',
|
||||
ttl: '',
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.domainList = JSON.parse(sessionStorage.getItem('domains')) || [];
|
||||
if(this.domainList.length == 0){
|
||||
layer.alert('请先选中要修改解析的域名', {icon: 2}, function(){
|
||||
window.location.href = '/domain';
|
||||
});
|
||||
}
|
||||
for(var i=0; i<this.domainList.length; i++){
|
||||
this.$set(this.domainList[i], 'result', '<span class="text-muted">待修改</span>');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async save(id){
|
||||
var that = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
that.set.id = id;
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: '/record/batchedit',
|
||||
data: that.set,
|
||||
dataType: 'json',
|
||||
success: function(data) {
|
||||
resolve(data);
|
||||
},
|
||||
error: function() {
|
||||
reject('服务器错误');
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
async submit(){
|
||||
if(this.set.name == ''){
|
||||
layer.alert('请填写主机记录', {icon: 2});
|
||||
return;
|
||||
}
|
||||
if(this.set.value == ''){
|
||||
layer.alert('请填写记录值', {icon: 2});
|
||||
return;
|
||||
}
|
||||
var ii = layer.load(2);
|
||||
for(var i=0; i<this.domainList.length; i++){
|
||||
this.domainList[i].result = '<span class="text-yellow"><i class="fa fa-refresh fa-spin fa-fw"></i> 正在修改</span>';
|
||||
var res = await this.save(this.domainList[i].id);
|
||||
if(res.code == 0){
|
||||
this.domainList[i].result = '<span class="text-green">'+res.msg+'</span>';
|
||||
}else{
|
||||
this.domainList[i].result = '<span class="text-red">'+res.msg+'</span>';
|
||||
}
|
||||
}
|
||||
layer.close(ii);
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
{/block}
|
||||
@@ -1,6 +1,7 @@
|
||||
{extend name="common/layout" /}
|
||||
{block name="title"}域名管理{/block}
|
||||
{block name="main"}
|
||||
<link href="{$cdnpublic}bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
|
||||
<div class="modal" id="modal-store" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content animated flipInX">
|
||||
@@ -50,6 +51,24 @@
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal" id="form-store2">
|
||||
<input type="hidden" name="id"/>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">到期时间</label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" class="form-control" name="expiretime" placeholder="" value="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">到期提醒</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select name="is_notice" class="form-control">
|
||||
<option value="0">否</option>
|
||||
<option value="1">是</option>
|
||||
</select>
|
||||
<a tabindex="0" class="input-group-addon" role="button" data-toggle="popover" data-trigger="focus" title="" data-placement="bottom" data-content="域名到期提醒,其他设置在“到期提醒设置”里面" data-original-title="说明"><span class="glyphicon glyphicon-info-sign"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">是否隐藏</label>
|
||||
<div class="col-sm-9">
|
||||
@@ -104,13 +123,17 @@
|
||||
<option value="{$k}">{$v}</option>
|
||||
{/foreach}</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<select name="status" class="form-control"><option value="">所有状态</option><option value="1">即将到期</option><option value="2">已到期</option></select>
|
||||
</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>
|
||||
{if request()->user['level'] eq 2}<a href="javascript:addframe()" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>
|
||||
<div class="btn-group" role="group">
|
||||
<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="/domain/add">添加</a></li><li><a href="javascript:operation('editremark')">修改备注</a></li><li><a href="javascript:operation('delete')">删除</a></li></ul>
|
||||
</div>{/if}
|
||||
<ul class="dropdown-menu"><li><a href="/domain/add">添加域名</a></li><li><a href="javascript:operation('editremark')">修改域名备注</a></li><li><a href="javascript:operation('opennotice')">开启到期提醒</a></li><li><a href="javascript:operation('closenotice')">关闭到期提醒</a></li><li><a href="javascript:operation('delete')">删除域名</a></li><li role="separator" class="divider"></li><li><a href="javascript:operation('addrecord')">添加解析</a></li><li><a href="javascript:operation('editrecord')">修改解析</a></li></ul>
|
||||
</div>
|
||||
<a href="/domain/expirenotice" class="btn btn-default">到期提醒设置</a>{/if}
|
||||
</form>
|
||||
|
||||
<table id="listTable">
|
||||
@@ -121,6 +144,9 @@
|
||||
</div>
|
||||
{/block}
|
||||
{block name="script"}
|
||||
<script src="{$cdnpublic}moment.js/2.29.4/moment.min.js"></script>
|
||||
<script src="{$cdnpublic}moment.js/2.29.4/locale/zh-cn.js"></script>
|
||||
<script src="{$cdnpublic}bootstrap-datetimepicker/4.17.47/js/bootstrap-datetimepicker.min.js"></script>
|
||||
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
|
||||
<script src="{$cdnpublic}bootstrap-table/1.21.4/bootstrap-table.min.js"></script>
|
||||
<script src="{$cdnpublic}bootstrap-table/1.21.4/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
|
||||
@@ -173,12 +199,61 @@ $(document).ready(function(){
|
||||
title: '添加时间'
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注'
|
||||
field: 'regtime',
|
||||
title: '注册时间',
|
||||
visible: false,
|
||||
formatter: function(value, row, index) {
|
||||
var html = '';
|
||||
if(value == null) {
|
||||
if (row.checkstatus == 0) {
|
||||
html = '<font color="#bdbdbd">待查询</font>';
|
||||
} else if (row.checkstatus == 2) {
|
||||
html = '<font color="#bdbdbd">查询失败</font>';
|
||||
}
|
||||
} else {
|
||||
html = value.slice(0,10);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'expiretime',
|
||||
title: '到期时间',
|
||||
formatter: function(value, row, index) {
|
||||
var html = '';
|
||||
if(value == null) {
|
||||
if (row.checkstatus == 0) {
|
||||
html = '<font color="#bdbdbd">待查询</font>';
|
||||
} else if (row.checkstatus == 2) {
|
||||
html = '<font color="#bdbdbd">查询失败</font>';
|
||||
}
|
||||
} else {
|
||||
var now = new Date().getTime();
|
||||
var expiretime = new Date(value).getTime();
|
||||
var days = parseInt((expiretime - now) / 1000 / 24 / 60 / 60);
|
||||
if (days > 30) {
|
||||
html += '<span title="还有'+days+'天到期" data-toggle="tooltip" data-placement="bottom">'+value.slice(0,10)+'</span>';
|
||||
} else if (days > 0) {
|
||||
html += '<b><span class="text-yellow" title="还有'+days+'天到期" data-toggle="tooltip" data-placement="bottom">'+value.slice(0,10)+'</span></b>';
|
||||
} else {
|
||||
html += '<b><span class="text-red" title="已到期" data-toggle="tooltip" data-placement="bottom">'+value.slice(0,10)+'</span></b>';
|
||||
}
|
||||
}
|
||||
html += ' <a href="javascript:updateDate('+row.id+')" title="刷新到期时间" class="text-green"><i class="fa fa-refresh"></i></a>';
|
||||
return html;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'is_notice',
|
||||
title: '到期提醒',
|
||||
formatter: function(value, row, index) {
|
||||
return value==1?'<font color="green">是</font>':'<font color="blue">否</font>';
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'is_hide',
|
||||
title: '是否隐藏',
|
||||
visible: false,
|
||||
formatter: function(value, row, index) {
|
||||
return value==1?'<font color="grey">是</font>':'<font color="blue">否</font>';
|
||||
}
|
||||
@@ -186,10 +261,15 @@ $(document).ready(function(){
|
||||
{
|
||||
field: 'is_sso',
|
||||
title: '对接开关',
|
||||
visible: false,
|
||||
formatter: function(value, row, index) {
|
||||
return value==1?'<font color="green">是</font>':'<font color="red">否</font>';
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注'
|
||||
},
|
||||
{
|
||||
field: '',
|
||||
title: '操作',
|
||||
@@ -202,7 +282,10 @@ $(document).ready(function(){
|
||||
return html;
|
||||
}
|
||||
},
|
||||
]
|
||||
],
|
||||
onLoadSuccess: function(data) {
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
}
|
||||
})
|
||||
|
||||
$("#form-store select[name=aid]").change(function(){
|
||||
@@ -259,7 +342,14 @@ function editframe(id){
|
||||
$("#form-store2 input[name=id]").val(row.id);
|
||||
$("#form-store2 select[name=is_hide]").val(row.is_hide);
|
||||
$("#form-store2 select[name=is_sso]").val(row.is_sso);
|
||||
$("#form-store2 select[name=is_notice]").val(row.is_notice);
|
||||
$("#form-store2 input[name=remark]").val(row.remark);
|
||||
|
||||
$("#form-store2 input[name=expiretime]").datetimepicker({
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
locale: 'zh-cn',
|
||||
defaultDate: row.expiretime,
|
||||
}).val(row.expiretime);
|
||||
}
|
||||
function saveEdit(){
|
||||
var ii = layer.load(2);
|
||||
@@ -351,7 +441,7 @@ function getDomainList(){
|
||||
function operation(action){
|
||||
var rows = $("#listTable").bootstrapTable('getSelections');
|
||||
if(rows.length == 0){
|
||||
layer.msg('请选择要操作的记录');
|
||||
layer.msg('请选择要操作的域名');
|
||||
return;
|
||||
}
|
||||
var ids = [];
|
||||
@@ -361,30 +451,58 @@ function operation(action){
|
||||
if(action == 'editremark'){
|
||||
batch_edit_remark(ids)
|
||||
return;
|
||||
}
|
||||
var confirmobj = layer.confirm('确定要删除所选记录吗?', {
|
||||
btn: ['确定','取消']
|
||||
}, function(){
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/domain/op/act/batchdel',
|
||||
data : {ids: ids},
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
layer.closeAll();
|
||||
layer.alert(data.msg, {icon: 1});
|
||||
searchRefresh();
|
||||
}else{
|
||||
layer.alert(data.msg, {icon: 2});
|
||||
}else if(action == 'addrecord'){
|
||||
sessionStorage.setItem('domains', JSON.stringify(rows));
|
||||
window.location.href = '/record/batchadd';
|
||||
return;
|
||||
}else if(action == 'editrecord'){
|
||||
sessionStorage.setItem('domains', JSON.stringify(rows));
|
||||
window.location.href = '/record/batchedit';
|
||||
return;
|
||||
}else if(action == 'delete'){
|
||||
var confirmobj = layer.confirm('确定要删除所选域名吗?', {
|
||||
btn: ['确定','取消']
|
||||
}, function(){
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/domain/op/act/batchdel',
|
||||
data : {ids: ids},
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
layer.closeAll();
|
||||
layer.alert(data.msg, {icon: 1});
|
||||
searchRefresh();
|
||||
}else{
|
||||
layer.alert(data.msg, {icon: 2});
|
||||
}
|
||||
}
|
||||
});
|
||||
}, function(){
|
||||
layer.close(confirmobj);
|
||||
});
|
||||
}else{
|
||||
var is_notice = action == 'opennotice' ? 1 : 0;
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/domain/op/act/batchsetnotice',
|
||||
data : {ids: ids, is_notice: is_notice},
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
layer.closeAll();
|
||||
layer.alert(data.msg, {icon: 1});
|
||||
searchRefresh();
|
||||
}else{
|
||||
layer.alert(data.msg, {icon: 2});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, function(){
|
||||
layer.close(confirmobj);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
function batch_edit_remark(ids) {
|
||||
layer.open({
|
||||
@@ -420,6 +538,24 @@ function batch_edit_remark(ids) {
|
||||
}
|
||||
});
|
||||
}
|
||||
function updateDate(id){
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/domain/updatedate',
|
||||
data : {id: id},
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
layer.msg('刷新成功', {icon: 1, time: 600});
|
||||
searchRefresh();
|
||||
}else{
|
||||
layer.alert(data.msg, {icon: 2});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function loading(){
|
||||
layer.load(2);
|
||||
}
|
||||
@@ -429,4 +565,4 @@ document.addEventListener("visibilitychange", function() {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{/block}
|
||||
{/block}
|
||||
|
||||
85
app/view/domain/expire_notice.html
Normal file
85
app/view/domain/expire_notice.html
Normal file
@@ -0,0 +1,85 @@
|
||||
{extend name="common/layout" /}
|
||||
{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;">
|
||||
|
||||
<div class="panel panel-info">
|
||||
<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 saveSetting(this)" method="post" class="form-horizontal" role="form">
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">到期提醒天数</label>
|
||||
<div class="col-sm-9"><input type="text" name="expire_noticedays" value="{:config_get('expire_noticedays')}" class="form-control" placeholder="留空则不开启到期提醒"/><font color="green">域名到期前多少天发送通知,可填写多个天数,用英文逗号隔开。例如填写7,14则在域名到期前7天与14天分别发送通知。</font></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">邮件通知</label>
|
||||
<div class="col-sm-9"><select class="form-control" name="expire_notice_mail" default="{:config_get('expire_notice_mail')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">微信公众号通知</label>
|
||||
<div class="col-sm-9"><select class="form-control" name="expire_notice_wxtpl" default="{:config_get('expire_notice_wxtpl')}"><option value="0">关闭</option><option value="1">开启</option></select></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label">Telegram机器人通知</label>
|
||||
<div class="col-sm-9"><select class="form-control" name="expire_notice_tgbot" default="{:config_get('expire_notice_tgbot')}"><option value="0">关闭</option><option value="1">开启</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="expire_notice_webhook" default="{:config_get('expire_notice_webhook')}"><option value="0">关闭</option><option value="1">开启</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>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading"><h3 class="panel-title">计划任务说明</h3></div>
|
||||
<div class="panel-body">
|
||||
<p>支持域名到期提醒+域名列表到期时间自动刷新。与SSL证书共用计划任务,不需要单独添加计划任务。</p><p><a href="/cert/certset">查看计划任务说明</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
{block name="script"}
|
||||
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
|
||||
<script>
|
||||
var items = $("select[default]");
|
||||
for (i = 0; i < items.length; i++) {
|
||||
$(items[i]).val($(items[i]).attr("default")||0);
|
||||
}
|
||||
function saveSetting(obj){
|
||||
var ii = layer.load(2, {shade:[0.1,'#fff']});
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '',
|
||||
data : $(obj).serialize(),
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
layer.alert('设置保存成功!', {
|
||||
icon: 1,
|
||||
closeBtn: false
|
||||
}, function(){
|
||||
window.location.reload()
|
||||
});
|
||||
}else{
|
||||
layer.alert(data.msg, {icon: 2})
|
||||
}
|
||||
},
|
||||
error:function(data){
|
||||
layer.close(ii);
|
||||
layer.msg('服务器错误');
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
{/block}
|
||||
@@ -159,7 +159,7 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 center-block" style="float: none;">
|
||||
<div class="panel panel-default panel-default">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{if request()->user['type'] eq 'user'}<a href="/domain" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回</a>{/if}{$domainName}</h3>
|
||||
</div>
|
||||
@@ -177,6 +177,7 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
|
||||
<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="javascript:addframe()" class="btn btn-success"><i class="fa fa-plus"></i> 添加记录</a>
|
||||
{if $dnsconfig.type=='aliyun'}<a href="/record/weight/{$domainId}" class="btn btn-default">权重配置</a>{/if}
|
||||
<div class="btn-group" role="group">
|
||||
<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="/record/batchadd/{$domainId}">添加</a></li><li><a href="javascript:operation('open')">启用</a></li><li><a href="javascript:operation('pause')">暂停</a></li><li><a href="javascript:operation('edit')">修改记录</a></li><li><a href="javascript:operation('editline')">修改线路</a></li>{if $dnsconfig.remark == 1}<li><a href="javascript:operation('editremark')">修改备注</a></li>{/if}<li><a href="javascript:operation('delete')">删除</a></li></ul>
|
||||
|
||||
250
app/view/domain/weight.html
Normal file
250
app/view/domain/weight.html
Normal file
@@ -0,0 +1,250 @@
|
||||
{extend name="common/layout" /}
|
||||
{block name="title"}权重配置 - {$domainName}{/block}
|
||||
{block name="main"}
|
||||
<style>
|
||||
.table-bordered>tbody>tr>td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:200px;vertical-align:middle;}
|
||||
</style>
|
||||
<div class="modal" id="modal-store" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="static">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content animated flipInX">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"><span
|
||||
aria-hidden="true">×</span><span
|
||||
class="sr-only">Close</span></button>
|
||||
<h4 class="modal-title" id="modal-title">设置权重</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="form-horizontal" id="form-store" onsubmit="return save(this)">
|
||||
<input type="hidden" name="id"/>
|
||||
<input type="hidden" name="subdomain"/>
|
||||
<input type="hidden" name="type"/>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label no-padding-right">选择线路</label>
|
||||
<div class="col-sm-9">
|
||||
<select name="line" id="line" class="form-control" onchange="changeLine()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label no-padding-right">线路权重开关</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="material-switch" style="padding-top:7px"><input id="weight-switch" name="status" type="checkbox" value="1" checked onchange="changeOpen()"/><label for="weight-switch" class="label-primary"></label></div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-striped table-hover table-bordered">
|
||||
<thead>
|
||||
<tr><th>主机记录</th><th>记录类型</th><th>记录值</th><th>权重</th></tr>
|
||||
</thead>
|
||||
<tbody id="weight-list">
|
||||
<tr><td colspan="4" class="text-center">正在加载...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<span class="text-muted">提示:权重范围为数字0-100</span>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12 text-right">
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<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="/record/{$domainId}" class="btn btn-sm btn-default pull-right" style="margin-top:-6px"><i class="fa fa-reply fa-fw"></i> 返回</a>权重配置 - {$domainName}</h3></div>
|
||||
<div class="panel-body">
|
||||
|
||||
<form onsubmit="return searchSubmit()" method="GET" class="form-inline" id="searchToolbar">
|
||||
<div class="form-group">
|
||||
<label>搜索</label>
|
||||
<input type="text" class="form-control" name="keyword" placeholder="子域名">
|
||||
</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>
|
||||
</form>
|
||||
|
||||
<table id="listTable">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/block}
|
||||
{block name="script"}
|
||||
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
|
||||
<script src="{$cdnpublic}bootstrap-table/1.21.4/bootstrap-table.min.js"></script>
|
||||
<script src="{$cdnpublic}bootstrap-table/1.21.4/extensions/page-jump-to/bootstrap-table-page-jump-to.min.js"></script>
|
||||
<script src="/static/js/custom.js"></script>
|
||||
<script>
|
||||
var dnsconfig = {$dnsconfig|json_encode|raw};
|
||||
var recordLine = {$recordLine|json_encode|raw};
|
||||
var domainId = {$domainId};
|
||||
var weightList = [];
|
||||
var lineList = [];
|
||||
$(document).ready(function(){
|
||||
updateToolbar();
|
||||
const defaultPageSize = 15;
|
||||
const pageNumber = typeof window.$_GET['pageNumber'] != 'undefined' ? parseInt(window.$_GET['pageNumber']) : 1;
|
||||
const pageSize = typeof window.$_GET['pageSize'] != 'undefined' ? parseInt(window.$_GET['pageSize']) : defaultPageSize;
|
||||
|
||||
$("#listTable").bootstrapTable({
|
||||
url: '/record/weight/data/{$domainId}',
|
||||
pageNumber: pageNumber,
|
||||
pageSize: pageSize,
|
||||
classes: 'table table-striped table-hover table-bordered',
|
||||
uniqueId: 'id',
|
||||
columns: [
|
||||
{
|
||||
field: 'SubDomain',
|
||||
title: '子域名'
|
||||
},
|
||||
{
|
||||
field: 'Type',
|
||||
title: '记录类型'
|
||||
},
|
||||
{
|
||||
field: 'RecordCount',
|
||||
title: '记录数量'
|
||||
},
|
||||
{
|
||||
field: 'Open',
|
||||
title: '权重配置状态',
|
||||
formatter: function(value, row, index) {
|
||||
if(value == 1){
|
||||
return '<font color="green"><i class="fa fa-check-circle"></i>已开启</font>';
|
||||
}else{
|
||||
return '<font color="#b5bbc8"><i class="fa fa-pause-circle"></i>已关闭</font>';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: '',
|
||||
title: '操作',
|
||||
formatter: function(value, row, index) {
|
||||
var html = '';
|
||||
if(row.Open == 1){
|
||||
if(row.Type == 'CNAME'){
|
||||
html += '<a class="btn btn-warning btn-xs" title="CNAME类型解析默认必须开权重模式,每次解析应答只返回一条解析结果记录值" disabled>关闭权重</a> ';
|
||||
}else{
|
||||
html += '<a href="javascript:setWeightStatus(\''+row.SubDomain+'\', \'0\')" class="btn btn-warning btn-xs">关闭权重</a> ';
|
||||
}
|
||||
}else{
|
||||
html += '<a href="javascript:setWeightStatus(\''+row.SubDomain+'\', \'1\')" class="btn btn-success btn-xs">开启权重</a> ';
|
||||
}
|
||||
html += '<a href="javascript:editframe('+row.id+')" class="btn btn-primary btn-xs">设置权重</a>';
|
||||
return html;
|
||||
}
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
function editframe(id){
|
||||
var row = $("#listTable").bootstrapTable('getRowByUniqueId', id);
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/record/list',
|
||||
data : {id: domainId, rr: row.rr},
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
$("#modal-store").modal('show');
|
||||
$("#modal-title").html("设置权重 - "+row.SubDomain);
|
||||
$("#form-store input[name=id]").val(id);
|
||||
$("#form-store input[name=subdomain]").val(row.SubDomain);
|
||||
$("#form-store input[name=type]").val(row.Type);
|
||||
|
||||
lineList = [];
|
||||
$.each(recordLine, function(i, item){
|
||||
if(data.data.find(x => x.Line == item.id)){
|
||||
item.open = row.Open;
|
||||
if(row.LineAlgorithms && row.LineAlgorithms.LineAlgorithm.length > 0){
|
||||
var tmpLine = row.LineAlgorithms.LineAlgorithm.find(x => x.Line == item.id);
|
||||
if(tmpLine) item.open = tmpLine.Open;
|
||||
}
|
||||
lineList.push(item);
|
||||
}
|
||||
});
|
||||
$("#line").empty();
|
||||
$.each(lineList, function(i, item){
|
||||
$("#line").append('<option value="'+item.id+'">'+item.name+'</option>');
|
||||
});
|
||||
|
||||
weightList = data.data;
|
||||
changeLine();
|
||||
}else{
|
||||
layer.alert(data.msg, {icon: 2})
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function changeLine(){
|
||||
var line = $("#line").val();
|
||||
$("#weight-switch").prop("checked", lineList.find(x => x.id == line).open);
|
||||
$("#weight-list").empty();
|
||||
$.each(weightList, function(i, item){
|
||||
if(item.Line == line){
|
||||
$("#weight-list").append('<tr><td>'+item.Name+'</td><td>'+item.Type+'</td><td>'+item.Value+'</td><td><input type="number" class="form-control" name="weight['+item.RecordId+']" value="'+item.Weight+'" style="width:80px;" min="0" max="100"/></td></tr>');
|
||||
}
|
||||
});
|
||||
changeOpen();
|
||||
}
|
||||
function changeOpen(){
|
||||
if($("#weight-switch").is(':checked')){
|
||||
$("#weight-list input[name^='weight']").prop("disabled", false);
|
||||
}else{
|
||||
$("#weight-list input[name^='weight']").prop("disabled", true);
|
||||
}
|
||||
}
|
||||
function save(){
|
||||
if($("#form-store input[name=username]").val()==''){
|
||||
layer.alert('请确保各项不能为空!');return false;
|
||||
}
|
||||
var act = $("#form-store input[name=action]").val();
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/record/weight/{$domainId}/act/update',
|
||||
data : $("#form-store").serialize(),
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
layer.alert(data.msg,{
|
||||
icon: 1,
|
||||
closeBtn: false
|
||||
}, function(){
|
||||
layer.closeAll();
|
||||
$("#modal-store").modal('hide');
|
||||
searchRefresh();
|
||||
});
|
||||
}else{
|
||||
layer.alert(data.msg, {icon: 2})
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
function setWeightStatus(subdomain, status){
|
||||
var ii = layer.load(2);
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : '/record/weight/{$domainId}/act/status',
|
||||
data : {subdomain: subdomain, status: status},
|
||||
dataType : 'json',
|
||||
success : function(data) {
|
||||
layer.close(ii);
|
||||
if(data.code == 0){
|
||||
layer.closeAll();
|
||||
searchRefresh();
|
||||
}else{
|
||||
layer.alert(data.msg, {icon: 2});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{/block}
|
||||
@@ -71,19 +71,64 @@
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
<div class="row">
|
||||
<div class="col-md-7 col-sm-12">
|
||||
<div id="browser-notice"></div>
|
||||
<div class="col-md-12">
|
||||
<div id="browser-notice"></div>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<i class="fa fa-heartbeat fa-fw"></i>
|
||||
<h3 class="box-title">容灾切换概览</h3>
|
||||
</div>
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li><a href="/dmonitor/overview">运行状态<b><span class="pull-right" id="dmonitor_state"></span></b></a></li>
|
||||
<li><a href="/dmonitor/task">切换策略(已开启)<span class="pull-right badge bg-blue" id="dmonitor_active">0</span></a>
|
||||
</li>
|
||||
<li><a href="/dmonitor/task?status=0">健康状况正常<span class="pull-right badge bg-green" id="dmonitor_status_0">0</span></a></li>
|
||||
<li><a href="/dmonitor/task?status=1">健康状况异常<span class="pull-right badge bg-red" id="dmonitor_status_1">0</span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="box box-info">
|
||||
<div class="box-header with-border">
|
||||
<i class="fa fa-globe fa-fw"></i>
|
||||
<h3 class="box-title">CF优选IP概览</h3>
|
||||
</div>
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li><a href="/optimizeip/opiplist">任务数量(已开启)<span class="pull-right badge bg-blue" id="optimizeip_active">0</span></a>
|
||||
</li>
|
||||
<li><a href="/optimizeip/opiplist?status=1">更新成功<span class="pull-right badge bg-green" id="optimizeip_status_1">0</span></a></li>
|
||||
<li><a href="/optimizeip/opiplist?status=2">更新失败<span class="pull-right badge bg-red" id="optimizeip_status_2">0</span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<div class="box box-success">
|
||||
<div class="box-header with-border">
|
||||
<i class="fa fa-globe fa-fw"></i>
|
||||
<h3 class="box-title">SSL证书概览</h3>
|
||||
</div>
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li class="text-center"><a href="/cert/certorder"><b>SSL证书订单</b></a></li>
|
||||
<li><a href="/cert/certorder?status=3">已签发<span class="pull-right badge bg-green" id="certorder_status_3">0</span></a>
|
||||
</li>
|
||||
<li><a href="/cert/certorder?status=5">签发失败<span class="pull-right badge bg-red" id="certorder_status_5">0</span></a></li>
|
||||
<li><a href="/cert/certorder?status=6">即将过期<span class="pull-right badge bg-orange" id="certorder_status_6">0</span></a></li>
|
||||
<li><a href="/cert/certorder?status=7">已过期<span class="pull-right badge bg-gray" id="certorder_status_7">0</span></a></li>
|
||||
<li class="text-center"><a href="/cert/deploytask"><b>自动部署任务</b></a></li>
|
||||
<li><a href="/cert/deploytask?status=0">待处理<span class="pull-right badge bg-aqua" id="certdeploy_status_0">0</span></a></li>
|
||||
<li><a href="/cert/deploytask?status=1">部署成功<span class="pull-right badge bg-green" id="certdeploy_status_1">0</span></a></li>
|
||||
<li><a href="/cert/deploytask?status=-1">部署失败<span class="pull-right badge bg-red" id="certdeploy_status_2">0</span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12">
|
||||
<div class="box box-primary">
|
||||
<div class="box-header with-border">
|
||||
<i class="fa fa-cloud"></i>
|
||||
<h3 class="box-title">服务器信息</h3>
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="query-title">框架版本</td>
|
||||
<td class="query-result">{$info.framework_version}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="query-title">PHP版本</td>
|
||||
<td class="query-result">{$info.php_version}</td>
|
||||
@@ -92,14 +137,6 @@
|
||||
<td class="query-title">MySQL版本</td>
|
||||
<td class="query-result">{$info.mysql_version}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="query-title">WEB软件</td>
|
||||
<td class="query-result">{$info.software}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="query-title">操作系统</td>
|
||||
<td class="query-result">{$info.os}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="query-title">服务器时间</td>
|
||||
<td class="query-result">{$info.date}</td>
|
||||
@@ -107,8 +144,6 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5 col-sm-12">
|
||||
<div class="box box-default">
|
||||
<div class="box-header with-border">
|
||||
<i class="fa fa-volume-up"></i>
|
||||
@@ -134,6 +169,25 @@ $(document).ready(function(){
|
||||
$('#count2').html(data.tasks);
|
||||
$('#count3').html(data.certs);
|
||||
$('#count4').html(data.deploys);
|
||||
$('#dmonitor_state').html(data.dmonitor_state==1?'<font color="green">正在运行</font>':'<font color="red">已停止</font>');
|
||||
$('#dmonitor_active').html(data.dmonitor_active);
|
||||
$('#dmonitor_status_0').html(data.dmonitor_status_0);
|
||||
$('#dmonitor_status_1').html(data.dmonitor_status_1);
|
||||
$('#optimizeip_active').html(data.optimizeip_active);
|
||||
$('#optimizeip_status_1').html(data.optimizeip_status_1);
|
||||
$('#optimizeip_status_2').html(data.optimizeip_status_2);
|
||||
$('#certorder_status_3').html(data.certorder_status_3);
|
||||
$('#certorder_status_5').html(data.certorder_status_5);
|
||||
$('#certorder_status_6').html(data.certorder_status_6);
|
||||
$('#certorder_status_7').html(data.certorder_status_7);
|
||||
$('#certdeploy_status_0').html(data.certdeploy_status_0);
|
||||
$('#certdeploy_status_1').html(data.certdeploy_status_1);
|
||||
$('#certdeploy_status_2').html(data.certdeploy_status_2);
|
||||
$('.badge').each(function() {
|
||||
if ($(this).text().trim() === '0') {
|
||||
$(this).css('opacity', '0.4');
|
||||
}
|
||||
});
|
||||
$.ajax({
|
||||
url: '{$checkupdate}',
|
||||
type: 'get',
|
||||
|
||||
@@ -16,14 +16,20 @@
|
||||
<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 col-xs-12 control-label no-padding-right">域名选择</label>
|
||||
<div class="col-sm-3 col-xs-5"><input type="text" name="rr" v-model="set.rr" placeholder="主机记录" class="form-control" required></div>
|
||||
<div class="col-sm-3 col-xs-7 dselect"><select name="did" v-model="set.did" class="form-control" required>
|
||||
<option value="">--主域名--</option>
|
||||
{foreach $domains as $k=>$v}
|
||||
<option value="{$k}">{$v}</option>
|
||||
{/foreach}
|
||||
</select></div>
|
||||
<label class="col-sm-3 control-label no-padding-right">域名选择</label>
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group">
|
||||
<input type="text" name="rr" v-model="set.rr" placeholder="主机记录" class="form-control" required>
|
||||
<span class="input-group-addon">.</span>
|
||||
<select name="did" v-model="set.did" class="form-control" required>
|
||||
<option value="">--主域名--</option>
|
||||
{foreach $domains as $k=>$v}
|
||||
<option value="{$k}">{$v}</option>
|
||||
{/foreach}
|
||||
</select>
|
||||
<a tabindex="0" class="input-group-addon" role="button" data-toggle="popover" data-trigger="focus" title="" data-placement="bottom" data-content="不支持对CloudFlare里的域名添加优选,必须使用其他DNS服务商。" data-original-title="说明"><span class="glyphicon glyphicon-info-sign"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="col-sm-3 control-label no-padding-right">CDN服务商</label>
|
||||
@@ -142,6 +148,7 @@ new Vue({
|
||||
live: 'submitted',
|
||||
});
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
$('[data-toggle="popover"]').popover()
|
||||
},
|
||||
methods: {
|
||||
submit(){
|
||||
|
||||
@@ -14,11 +14,16 @@ tbody tr>td:nth-child(2){overflow: hidden;text-overflow: ellipsis;white-space: n
|
||||
<label>搜索</label>
|
||||
<div class="form-group">
|
||||
<select name="type" class="form-control"><option value="1">域名</option><option value="2">备注</option></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="kw" placeholder="">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group">
|
||||
<select name="status" class="form-control"><option value="">更新结果</option><option value="1">成功</option><option value="2">失败</option></select>
|
||||
</div>
|
||||
</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="/optimizeip/opipform/add" class="btn btn-success"><i class="fa fa-plus"></i> 添加</a>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<div class="panel panel-warning">
|
||||
<div class="panel-heading"><h3 class="panel-title">使用说明</h3></div>
|
||||
<div class="panel-body">
|
||||
<p><li>不支持对CloudFlare里的域名添加优选,必须使用其他DNS服务商。需开通Cloudflare for SaaS,且域名使用CNAME的方式解析到CloudFlare。</li></p>
|
||||
<p><li>数据接口:<a href="https://www.wetest.vip/" target="_blank" rel="noreferrer">wetest.vip</a> 数据接口支持CloudFlare、CloudFront、Gcore;<a href="https://stock.hostmonit.com/" target="_blank" rel="noreferrer">HostMonit</a> 只支持CloudFlare。</li></p>
|
||||
<p><li>接口密钥:默认o1zrmHAF为免费KEY可永久免费使用。</li></p>
|
||||
<p><li>计划任务:将以下命令添加到计划任务,周期设置为15分钟以上</li></p>
|
||||
|
||||
@@ -38,10 +38,18 @@
|
||||
"ext-pdo": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-sockets": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-ssh2": "*",
|
||||
"ext-ftp": "*",
|
||||
"topthink/framework": "^6.0.0",
|
||||
"topthink/think-orm": "^2.0",
|
||||
"topthink/think-view": "^1.0",
|
||||
"cccyun/think-captcha": "^3.0"
|
||||
"cccyun/think-captcha": "^3.0",
|
||||
"symfony/polyfill-intl-idn": "^1.31",
|
||||
"symfony/polyfill-php80": "^1.31",
|
||||
"cccyun/php-whois": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/var-dumper": "^4.2",
|
||||
|
||||
@@ -31,7 +31,7 @@ return [
|
||||
'show_error_msg' => true,
|
||||
'exception_tmpl' => \think\facade\App::getAppPath() . 'view/exception.tpl',
|
||||
|
||||
'version' => '1030',
|
||||
'version' => '1035',
|
||||
|
||||
'dbversion' => '1028'
|
||||
'dbversion' => '1033'
|
||||
];
|
||||
|
||||
BIN
public/static/images/jdcloud.ico
Normal file
BIN
public/static/images/jdcloud.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
public/static/images/ratpanel.ico
Executable file
BIN
public/static/images/ratpanel.ico
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
public/static/images/wangsu.ico
Normal file
BIN
public/static/images/wangsu.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -49,6 +49,8 @@ Route::group(function () {
|
||||
Route::post('/account/op', 'domain/account_op');
|
||||
Route::get('/account', 'domain/account');
|
||||
|
||||
Route::any('/domain/expirenotice', 'domain/expire_notice');
|
||||
Route::post('/domain/updatedate', 'domain/update_date');
|
||||
Route::post('/domain/data', 'domain/domain_data');
|
||||
Route::post('/domain/op', 'domain/domain_op');
|
||||
Route::post('/domain/list', 'domain/domain_list');
|
||||
@@ -64,8 +66,12 @@ Route::group(function () {
|
||||
Route::post('/record/batch/:id', 'domain/record_batch');
|
||||
Route::post('/record/batchedit/:id', 'domain/record_batch_edit');
|
||||
Route::any('/record/batchadd/:id', 'domain/record_batch_add');
|
||||
Route::get('/record/batchadd', 'domain/record_batch_add2');
|
||||
Route::any('/record/batchedit', 'domain/record_batch_edit2');
|
||||
Route::any('/record/log/:id', 'domain/record_log');
|
||||
Route::post('/record/list', 'domain/record_list');
|
||||
Route::post('/record/weight/data/:id', 'domain/weight_data');
|
||||
Route::any('/record/weight/:id', 'domain/weight');
|
||||
Route::get('/record/:id', 'domain/record');
|
||||
|
||||
Route::get('/dmonitor/overview', 'dmonitor/overview');
|
||||
@@ -130,6 +136,8 @@ Route::group('api', function () {
|
||||
Route::post('/record/remark/:id', 'domain/record_remark');
|
||||
Route::post('/record/batch/:id', 'domain/record_batch');
|
||||
|
||||
Route::post('/cert/order', 'cert/order_info');
|
||||
|
||||
})->middleware(AuthApi::class);
|
||||
|
||||
Route::miss(function() {
|
||||
|
||||
Reference in New Issue
Block a user