52 Commits
2.7.1 ... 2.8.2

Author SHA1 Message Date
net909
0863d02cc9 fix 2025-07-28 20:37:10 +08:00
net909
9032ea0405 整合计划任务,新增访问URL方式的计划任务 2025-07-28 20:30:09 +08:00
net909
e3749ecb6c Merge branch 'main' of ssh://ssh.github.com:443/netcccyun/dnsmgr 2025-07-18 14:47:30 +08:00
net909
e1e90c3c71 修复阿里云ESA部署失败 2025-07-18 14:47:12 +08:00
dependabot[bot]
a171a5b9b0 Bump topthink/think-trace from 1.6 to 2.0 (#276)
---
updated-dependencies:
- dependency-name: topthink/think-trace
  dependency-version: '2.0'
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-16 17:08:08 +08:00
dependabot[bot]
f608b2fceb Bump topthink/framework from 8.1.2 to 8.1.3 (#277)
---
updated-dependencies:
- dependency-name: topthink/framework
  dependency-version: 8.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-16 17:08:01 +08:00
Hanada
0837ac9be1 Merge pull request #274 from HanadaLee/multipartpostfix
重构POST内容判断逻辑,去除X-Content-Type标头
2025-07-12 09:44:14 +08:00
net909
c31e0eaf41 修复部署失败 2025-07-11 09:56:08 +08:00
net909
987deda95d 修复报错 2025-07-10 16:41:11 +08:00
消失的彩虹海
654151ce5b Merge pull request #270 from HanadaLee/ssldeployfix
修复部署证书到阿里云和群晖失败的问题
2025-07-08 23:25:03 +08:00
Hanada
2fedee1e93 修复使用guzzle库后部署证书到群晖失败的问题 2025-07-08 23:12:40 +08:00
Hanada
ba97ac3685 修复部署阿里云时证书序列号可能存在前置0的问题 2025-07-08 21:42:40 +08:00
net909
f2f1a0d01e 修复upyun部署 2025-07-07 21:47:15 +08:00
net909
933f98a909 2.8.1 2025-07-06 12:21:04 +08:00
net909
3beb0ec637 修复阿里云WAF部署 2025-07-06 12:19:02 +08:00
net909
7a92ad7094 Merge branch 'main' of ssh://ssh.github.com:443/netcccyun/dnsmgr 2025-07-06 11:17:06 +08:00
net909
5dc4dbfabd 增加Edgeone IP优选,修复EO海外版部署失败 2025-07-06 11:16:44 +08:00
DearTanker
291f715e91 优化记录值复制功能 (#266)
1、修复无法复制包含双引号的 TXT 记录值
2、调整 MX 记录值的复制按钮到右侧,因为复制不需要复制优先级。
2025-07-04 09:43:55 +08:00
net909
4f15d4d7a2 调整快速打开按钮位置 2025-07-03 19:48:34 +08:00
DearTanker
11e83860b7 添加 自定义样式功能、主机记录增加快速打开链接 (#263)
* 添加支持自定义样式功能。

* 在主机记录列中增加一个新窗口打开链接的小按钮,方便快速访问。

* 记录值增加快速复制按钮,并用 padding 来调整了图标的间距避免空格
2025-07-03 19:37:34 +08:00
net909
479af4fe5f 修复批量操作证书和部署任务 2025-06-30 12:44:31 +08:00
net909
3c00e426cc 修改字段长度 2025-06-25 14:35:28 +08:00
net909
814781c1d1 version 2025-06-25 14:24:31 +08:00
net909
af2117faf0 支持雨云SSL自动部署 2025-06-25 13:37:37 +08:00
net909
9275256e36 新增lucky部署,支持直接对接DNS服务商添加域名 2025-06-25 12:57:09 +08:00
net909
d368a0190a 修复API接口操作记录报错 2025-06-23 17:45:47 +08:00
dependabot[bot]
684a4e59ce Bump topthink/think-orm from 4.0.41 to 4.0.44 (#253) 2025-06-20 10:25:54 +08:00
Hanada
1bab48ad93 修复登录设置页面侧边栏没有自动展开的问题 (#252) 2025-06-20 10:21:55 +08:00
耗子
02718e58a9 更新PHPMailer并添加依赖机器人 (#251) 2025-06-20 10:21:40 +08:00
Hanada
8e563a89ff 阿里云ESA支持部署证书到非中国内地 (#249) 2025-06-19 12:51:02 +08:00
Hanada
7f370a095d 修复网宿API验证失败的问题 (#250)
* 变更网宿check方法调用的API接口以避免产品未开通导致验证失败

* 兼容网宿CDN接口使用result作为失败返回值的场景
2025-06-19 12:50:46 +08:00
Hanada
5aab54c79e 将登录验证码开关移到系统设置 (#246) 2025-06-16 19:25:28 +08:00
耗子
ce9ae51aeb 优化错误信息 (#239) 2025-06-10 11:22:52 +08:00
消失的彩虹海
8b06cc5400 Merge pull request #238 from devhaozi/main
恢复自定义解析特性
2025-06-10 10:40:02 +08:00
耗子
a71fd35f6f 恢复自定义解析特性 2025-06-10 10:37:04 +08:00
消失的彩虹海
0d20d8ad46 Merge pull request #236 from devhaozi/main
更新ThinkPHP框架到8.1及curl请求重构
2025-06-10 09:44:52 +08:00
耗子
ff676b7be1 撤销首页提示更改 2025-06-09 14:23:17 +08:00
耗子
8c7c568e5c 撤销首页提示更改 2025-06-09 14:21:42 +08:00
耗子
a4698d67c6 优化函数签名 2025-06-09 05:40:42 +08:00
耗子
5f0e973770 优化函数签名 2025-06-09 05:38:52 +08:00
耗子
a99d0320df 优化函数签名 2025-06-09 05:36:57 +08:00
耗子
bb35ee3378 优化默认Content-Type值及函数名 2025-06-09 05:17:15 +08:00
耗子
8193ade02d 修复get_curl方法header错误 2025-06-09 05:12:06 +08:00
耗子
904c3ceb5b 回滚swoole更改 2025-06-09 05:08:47 +08:00
耗子
5add791ef1 重构使用guzzlehttp发送请求 2025-06-09 05:04:06 +08:00
耗子
3587db2b53 优化代码格式 2025-06-09 01:53:04 +08:00
耗子
4a4cdd059c 优化代码格式 2025-06-09 01:47:53 +08:00
耗子
1021deb60d 优化部分代码 2025-06-09 01:43:58 +08:00
耗子
848610ffe0 优化部分代码 2025-06-09 00:51:50 +08:00
耗子
767aec5ebc 添加框架版本显示 2025-06-09 00:09:48 +08:00
耗子
30912fdf75 更新框架到8.1 2025-06-09 00:01:33 +08:00
net909
5050af2f73 修复阿里云设置权重 2025-06-06 09:49:45 +08:00
93 changed files with 3360 additions and 531 deletions

15
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
# Dependabot configuration.
#
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/working-with-dependabot/dependabot-options-reference
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "daily"

1
.gitignore vendored
View File

@@ -3,4 +3,3 @@
/vendor
*.log
.env
/composer.lock

View File

@@ -29,7 +29,7 @@
* 从[Release](https://github.com/netcccyun/dnsmgr/releases)页面下载安装包
* 运行环境要求PHP7.4+MySQL5.6+
* 运行环境要求PHP8.0+MySQL5.6+
* 设置网站运行目录为`public`
@@ -52,10 +52,13 @@
* Nginx
```
location ~* (runtime|application)/ {
return 403;
}
location / {
if (!-e $request_filename){
rewrite ^(.*)$ /index.php?s=$1 last; break;
}
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=$1 last; break;
}
}
```

View File

@@ -12,7 +12,9 @@ use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
use think\facade\Config;
use app\service\OptimizeService;
use app\service\CertTaskService;
use app\service\ExpireNoticeService;
class Certtask extends Command
{
@@ -20,7 +22,7 @@ class Certtask extends Command
{
// 指令配置
$this->setName('certtask')
->setDescription('证书申请与部署任务');
->setDescription('SSL证书续签与部署、域名到期提醒、CF优选IP更新');
}
protected function execute(Input $input, Output $output)
@@ -28,6 +30,11 @@ class Certtask extends Command
$res = Db::name('config')->cache('configs', 0)->column('value', 'key');
Config::set($res, 'sys');
(new CertTaskService())->execute();
$res = (new OptimizeService())->execute();
if (!$res) {
(new CertTaskService())->execute();
(new ExpireNoticeService())->task();
}
echo 'done'.PHP_EOL;
}
}

View File

@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace app\command;
use Exception;
use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
use think\facade\Config;
use app\service\OptimizeService;
class Opiptask extends Command
{
protected function configure()
{
// 指令配置
$this->setName('opiptask')
->setDescription('CF优选IP任务');
}
protected function execute(Input $input, Output $output)
{
$res = Db::name('config')->cache('configs', 0)->column('value', 'key');
Config::set($res, 'sys');
(new OptimizeService())->execute();
}
}

View File

@@ -1,50 +1,54 @@
<?php
// 应用公共文件
use think\facade\Db;
use think\facade\Config;
use think\facade\Request;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
function get_curl($url, $post = 0, $referer = 0, $cookie = 0, $header = 0, $ua = 0, $nobody = 0, $addheader = 0)
function get_curl($url, $post = 0, $referer = 0, $cookie = 0, $ua = 0, $nobody = 0, $addheader = [])
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$httpheader[] = "Accept: */*";
$httpheader[] = "Accept-Encoding: gzip,deflate,sdch";
$httpheader[] = "Accept-Language: zh-CN,zh;q=0.8";
$httpheader[] = "Connection: close";
if ($addheader) {
$httpheader = array_merge($httpheader, $addheader);
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
if ($post) {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
}
if ($header) {
curl_setopt($ch, CURLOPT_HEADER, true);
$options = [
'timeout' => 10,
'verify' => false,
'headers' => [
'User-Agent' => $ua ?: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
],
'http_errors' => false // 不抛出异常
];
$options['headers'] = array_merge($options['headers'], $addheader);
if ($referer) {
$options['headers']['Referer'] = $referer;
}
if ($cookie) {
curl_setopt($ch, CURLOPT_COOKIE, $cookie);
$options['headers']['Cookie'] = $cookie;
}
if ($referer) {
curl_setopt($ch, CURLOPT_REFERER, $referer);
$method = 'GET';
if ($post) {
$method = 'POST';
if (!isset($options['headers']['Content-Type'])) {
$options['headers']['Content-Type'] = 'application/x-www-form-urlencoded';
}
if (is_array($post)) {
$options['form_params'] = $post;
} else {
$options['body'] = $post;
}
}
if ($ua) {
curl_setopt($ch, CURLOPT_USERAGENT, $ua);
} else {
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Linux; U; Android 4.0.4; es-mx; HTC_One_X Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0");
try {
$client = new Client();
$response = $client->request($method, $url, $options);
if ($nobody) {
return '';
}
return $response->getBody()->getContents();
} catch (GuzzleException $e) {
return '';
}
if ($nobody) {
curl_setopt($ch, CURLOPT_NOBODY, 1);
}
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$ret = curl_exec($ch);
curl_close($ch);
return $ret;
}
function real_ip($type = 0)
@@ -319,47 +323,41 @@ function getMainDomain($host)
function check_proxy($url, $proxy_server, $proxy_port, $type, $proxy_user, $proxy_pwd)
{
$ch = curl_init($url);
if ($type == 'https') {
$proxy_type = CURLPROXY_HTTPS;
} elseif ($type == 'sock4') {
$proxy_type = CURLPROXY_SOCKS4;
} elseif ($type == 'sock5') {
$proxy_type = CURLPROXY_SOCKS5;
} elseif ($type == 'sock5h') {
$proxy_type = CURLPROXY_SOCKS5_HOSTNAME;
} else {
$proxy_type = CURLPROXY_HTTP;
}
curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_PROXY, $proxy_server);
curl_setopt($ch, CURLOPT_PROXYPORT, intval($proxy_port));
match ($type) {
'https' => $proxy_string = 'https://',
'sock4' => $proxy_string = 'socks4://',
'sock5' => $proxy_string = 'socks5://',
'sock5h' => $proxy_string = 'socks5h://',
default => $proxy_string = 'http://',
};
if (!empty($proxy_user) && !empty($proxy_pwd)) {
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy_user . ':' . $proxy_pwd);
$proxy_string .= $proxy_user . ':' . $proxy_pwd . '@';
}
curl_setopt($ch, CURLOPT_PROXYTYPE, $proxy_type);
$httpheader[] = "Accept: */*";
$httpheader[] = "Accept-Language: zh-CN,zh;q=0.8";
$httpheader[] = "Connection: close";
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36');
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
curl_exec($ch);
$errno = curl_errno($ch);
if ($errno) {
$errmsg = curl_error($ch);
curl_close($ch);
throw new Exception($errmsg);
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 200 && $httpCode < 400) {
return true;
} else {
throw new Exception('HTTP状态码异常' . $httpCode);
$proxy_string .= $proxy_server . ':' . intval($proxy_port);
$options = [
'proxy' => $proxy_string,
'timeout' => 3,
'connect_timeout' => 3,
'verify' => false,
'headers' => [
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
]
];
try {
$client = new Client();
$response = $client->request('GET', $url, $options);
$httpCode = $response->getStatusCode();
if ($httpCode >= 200 && $httpCode < 400) {
return true;
} else {
throw new Exception('HTTP状态码异常' . $httpCode);
}
} catch (GuzzleException $e) {
throw new Exception(guzzle_error($e));
}
}
@@ -394,59 +392,155 @@ function clearDirectory($dir): bool
return true;
}
function curl_client($url, $data = null, $referer = null, $cookie = null, $headers = null, $proxy = false, $method = null, $timeout = 5, $default_headers = true)
/**
* 发送 HTTP 请求
*
* @param string $url 请求URL
* @param mixed $data 请求数据,可以是字符串或数组,数组将自动根据请求方法及传入的 Content-Type 头序列化
* @param string|null $referer 请求的 Referer 头
* @param array|null $cookie 请求的 Cookie 头
* @param array|null $headers 其他自定义请求头
* @param bool $proxy 是否使用代理
* @param string|null $method 请求方法,默认为 GET 或 POST如果存在请求数据
* @param int $timeout 请求超时时间,默认为 10 秒
* @return array 包含 HTTP 状态码、重定向 URL、响应头和响应体的数组
* @throws Exception
*/
function http_request($url, $data = null, $referer = null, $cookie = null, $headers = null, $proxy = false, $method = null, $timeout = 10): array
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
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;
$options = [
'timeout' => $timeout,
'connect_timeout' => $timeout,
'allow_redirects' => false,
'verify' => false,
'headers' => [
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36',
],
'http_errors' => false // 不抛出异常
];
// 默认请求方法
if (!$method) {
$method = $data ? 'POST' : 'GET';
}
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");
curl_setopt($ch, CURLOPT_HEADER, true);
if ($data) {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
// 处理头部
if (is_array($headers)) {
$options['headers'] = array_merge($options['headers'], $headers);
}
// 处理Cookie
if ($cookie) {
curl_setopt($ch, CURLOPT_COOKIE, $cookie);
$options['headers']['Cookie'] = $cookie;
}
// 处理Referer
if ($referer) {
curl_setopt($ch, CURLOPT_REFERER, $referer);
$options['headers']['Referer'] = $referer;
}
if ($method) {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
// 处理数据
if ($data) {
if ($method !== 'GET') {
if (is_string($data)) {
$options['body'] = $data;
if (!isset($options['headers']['Content-Type'])) {
if (json_validate($data)) {
// json
$options['headers']['Content-Type'] = 'application/json';
} elseif (str_contains($data, '=') || str_contains($data, '&')) {
// 表单
$options['headers']['Content-Type'] = 'application/x-www-form-urlencoded';
}
}
} else if (is_array($data) || is_object($data)) {
if (!isset($options['headers']['Content-Type'])) {
// 默认为表单
$options['headers']['Content-Type'] = 'application/x-www-form-urlencoded';
}
if ($options['headers']['Content-Type'] == 'application/x-www-form-urlencoded') {
// 表单
$options['form_params'] = $data;
} else if ($options['headers']['Content-Type'] == 'multipart/form-data') {
// 表单文件
$options['multipart'] = $data;
unset($options['headers']['Content-Type']); // 由GuzzleHttp重新生成Content-Type头部
} else if ($options['headers']['Content-Type'] == 'application/json') {
// json
$options['json'] = $data;
} else {
// 其他
$options['body'] = http_build_query($data);
}
} else {
$options['body'] = $data;
}
} else {
// 兼容已经存在查询字符串的情况
if (!str_contains($url, '?')) {
$options['query'] = $data;
}
}
}
// 处理代理
if ($proxy) {
curl_set_proxy($ch);
$proxy_server = config_get('proxy_server');
$proxy_port = intval(config_get('proxy_port'));
$proxy_userpwd = config_get('proxy_user').':'.config_get('proxy_pwd');
$proxy_type = config_get('proxy_type');
if (empty($proxy_server) || empty($proxy_port)) {
throw new Exception('代理服务器或端口未配置');
}
match ($proxy_type) {
'https' => $proxy_string = 'https://',
'sock4' => $proxy_string = 'socks4://',
'sock5' => $proxy_string = 'socks5://',
'sock5h' => $proxy_string = 'socks5h://',
default => $proxy_string = 'http://',
};
if ($proxy_userpwd != ':') {
$proxy_string .= $proxy_userpwd . '@';
}
$proxy_string .= $proxy_server . ':' . $proxy_port;
$options['proxy'] = $proxy_string;
}
$ret = curl_exec($ch);
$errno = curl_errno($ch);
if ($errno) {
$errmsg = curl_error($ch);
curl_close($ch);
throw new Exception('Curl error: ' . $errmsg);
try {
$client = new Client();
$response = $client->request($method, $url, $options);
$code = $response->getStatusCode();
// 取重定向URL
$redirect_url = '';
if ($code >= 300 && $code < 400) {
$redirect_url = $response->getHeaderLine('Location');
}
return [
'code' => $code,
'redirect_url' => $redirect_url,
'headers' => $response->getHeaders(),
'body' => $response->getBody()->getContents()
];
} catch (GuzzleException $e) {
throw new Exception('请求失败: ' . guzzle_error($e));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$redirect_url = curl_getinfo($ch, CURLINFO_REDIRECT_URL);
curl_close($ch);
$header = substr($ret, 0, $headerSize);
$body = substr($ret, $headerSize);
return ['code' => $httpCode, 'redirect_url' => $redirect_url, 'header' => $header, 'body' => $body];
}
function guzzle_error($e)
{
$errmsg = $e->getMessage();
if (preg_match('/^cURL error \d+: /', $errmsg)) {
$errmsg = preg_replace('/^cURL error \d+: /', '', $errmsg);
}
$pos = strpos($errmsg, ' (see https://curl.haxx.se/libcurl/c/libcurl-errors.html)');
if ($pos !== false) {
$errmsg = substr($errmsg, 0, $pos);
}
if (strlen($errmsg) > 100) {
$errmsg = substr($errmsg, 0, 97) . '...';
}
return $errmsg;
}
function curl_set_proxy(&$ch)

View File

@@ -458,7 +458,7 @@ class Cert extends BaseController
$ids = input('post.ids');
$success = 0;
foreach ($ids as $id) {
if (input('post.action') == 'delete') {
if (input('post.act') == 'delete') {
$dcount = DB::name('cert_deploy')->where('oid', $id)->count();
if ($dcount > 0) continue;
try {
@@ -468,7 +468,7 @@ class Cert extends BaseController
Db::name('cert_order')->where('id', $id)->delete();
Db::name('cert_domain')->where('oid', $id)->delete();
$success++;
} elseif (input('post.action') == 'reset') {
} elseif (input('post.act') == 'reset') {
try {
$service = new CertOrderService($id);
$service->cancel();
@@ -476,8 +476,8 @@ class Cert extends BaseController
$success++;
} catch (Exception $e) {
}
} elseif (input('post.action') == 'open' || input('post.action') == 'close') {
$isauto = input('post.action') == 'open' ? 1 : 0;
} elseif (input('post.act') == 'open' || input('post.act') == 'close') {
$isauto = input('post.act') == 'open' ? 1 : 0;
Db::name('cert_order')->where('id', $id)->update(['isauto' => $isauto]);
$success++;
}
@@ -754,21 +754,21 @@ class Cert extends BaseController
if (!$cert) return json(['code' => -1, 'msg' => '证书订单不存在']);
}
foreach ($ids as $id) {
if (input('post.action') == 'delete') {
if (input('post.act') == 'delete') {
Db::name('cert_deploy')->where('id', $id)->delete();
$success++;
} elseif (input('post.action') == 'reset') {
} elseif (input('post.act') == '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;
} elseif (input('post.act') == 'open' || input('post.act') == 'close') {
$active = input('post.act') == 'open' ? 1 : 0;
Db::name('cert_deploy')->where('id', $id)->update(['active' => $active]);
$success++;
} elseif (input('post.action') == 'cert') {
} elseif (input('post.act') == 'cert') {
Db::name('cert_deploy')->where('id', $id)->update(['oid' => $certid]);
$success++;
}

View File

@@ -142,13 +142,14 @@ class Domain extends BaseController
$accounts = [];
$types = [];
foreach ($list as $row) {
$accounts[$row['id']] = $row['id'] . '_' . DnsHelper::$dns_config[$row['type']]['name'];
$name = $row['id'] . '_' . DnsHelper::$dns_config[$row['type']]['name'];
if (!array_key_exists($row['type'], $types)) {
$types[$row['type']] = DnsHelper::$dns_config[$row['type']]['name'];
}
if (!empty($row['remark'])) {
$accounts[$row['id']] .= '' . $row['remark'] . '';
$name .= '' . $row['remark'] . '';
}
$accounts[] = ['id' => $row['id'], 'name' => $name, 'type' => DnsHelper::$dns_config[$row['type']]['name'], 'add' => DnsHelper::$dns_config[$row['type']]['add']];
}
View::assign('accounts', $accounts);
View::assign('types', $types);
@@ -225,13 +226,21 @@ class Domain extends BaseController
} elseif ($act == 'add') {
if (!checkPermission(2)) return $this->alert('error', '无权限');
$aid = input('post.aid/d');
$method = input('post.method/d', 0);
$name = input('post.name', null, 'trim');
$thirdid = input('post.thirdid', null, 'trim');
$recordcount = input('post.recordcount/d', 0);
if (empty($name) || empty($thirdid)) return json(['code' => -1, 'msg' => '参数不能为空']);
if ($method == 1 && empty($name) || $method == 0 && (empty($name) || empty($thirdid))) return json(['code' => -1, 'msg' => '参数不能为空']);
if (Db::name('domain')->where('aid', $aid)->where('name', $name)->find()) {
return json(['code' => -1, 'msg' => '域名已存在']);
}
if ($method == 1) {
$dns = DnsHelper::getModel($aid);
$result = $dns->addDomain($name);
if (!$result) return json(['code' => -1, 'msg' => '添加域名失败,' . $dns->getError()]);
$name = $result['name'];
$thirdid = $result['id'];
}
Db::name('domain')->insert([
'aid' => $aid,
'name' => $name,
@@ -536,7 +545,6 @@ class Domain extends BaseController
$remark = input('post.remark', null, 'trim');
$recordinfo = input('post.recordinfo', null, 'trim');
$recordinfo = json_decode($recordinfo, true);
if (empty($recordid) || empty($name) || empty($type) || empty($value)) {
return json(['code' => -1, 'msg' => '参数不能为空']);
@@ -546,6 +554,7 @@ class Domain extends BaseController
$recordid = $dns->updateDomainRecord($recordid, $name, $type, $value, $line, $ttl, $mx, $weight, $remark);
if ($recordid) {
if ($recordinfo) {
$recordinfo = json_decode($recordinfo, true);
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.')');
@@ -572,7 +581,6 @@ class Domain extends BaseController
$recordid = input('post.recordid', null, 'trim');
$recordinfo = input('post.recordinfo', null, 'trim');
$recordinfo = json_decode($recordinfo, true);
if (empty($recordid)) {
return json(['code' => -1, 'msg' => '参数不能为空']);
@@ -581,6 +589,7 @@ class Domain extends BaseController
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
if ($dns->deleteDomainRecord($recordid)) {
if ($recordinfo) {
$recordinfo = json_decode($recordinfo, true);
if (is_array($recordinfo['Value'])) $recordinfo['Value'] = implode(',', $recordinfo['Value']);
$this->add_log($drow['name'], '删除解析', $recordinfo['Name'].' ['.$recordinfo['Type'].'] '.$recordinfo['Value'].' (线路:'.$recordinfo['Line'].' TTL:'.$recordinfo['TTL'].')');
} else {
@@ -604,7 +613,6 @@ class Domain extends BaseController
$recordid = input('post.recordid', null, 'trim');
$status = input('post.status', null, 'trim');
$recordinfo = input('post.recordinfo', null, 'trim');
$recordinfo = json_decode($recordinfo, true);
if (empty($recordid)) {
return json(['code' => -1, 'msg' => '参数不能为空']);
@@ -614,6 +622,7 @@ class Domain extends BaseController
if ($dns->setDomainRecordStatus($recordid, $status)) {
$action = $status == '1' ? '启用解析' : '暂停解析';
if ($recordinfo) {
$recordinfo = json_decode($recordinfo, true);
if (is_array($recordinfo['Value'])) $recordinfo['Value'] = implode(',', $recordinfo['Value']);
$this->add_log($drow['name'], $action, $recordinfo['Name'].' ['.$recordinfo['Type'].'] '.$recordinfo['Value'].' (线路:'.$recordinfo['Line'].' TTL:'.$recordinfo['TTL'].')');
} else {
@@ -990,7 +999,7 @@ class Domain extends BaseController
return json(['code' => -1, 'msg' => '参数不能为空']);
}
$dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']);
if ($dns->setWeightStatus($subdomain, $status, $type, $line)) {
if ($type == 'CNAME' || $dns->setWeightStatus($subdomain, $status, $type, $line)) {
if ($status == '1') {
$success = 0;
foreach($weight as $recordid => $weight) {

View File

@@ -7,8 +7,6 @@ use Exception;
use think\facade\Db;
use think\facade\View;
use think\facade\Cache;
use app\lib\DnsHelper;
use app\utils\MsgNotice;
class Index extends BaseController
{
@@ -28,7 +26,7 @@ class Index extends BaseController
$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;
@@ -44,7 +42,7 @@ class Index extends BaseController
$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();
@@ -63,7 +61,7 @@ class Index extends BaseController
$tmp = 'version()';
$mysqlVersion = Db::query("select version()")[0][$tmp];
$info = [
'framework_version' => app()::VERSION,
'framework_version' => app()->version(),
'php_version' => PHP_VERSION,
'mysql_version' => $mysqlVersion,
'software' => $_SERVER['SERVER_SOFTWARE'],

View File

@@ -20,6 +20,9 @@ class Optimizeip extends BaseController
if (empty($key)) {
continue;
}
if ($key == 'optimize_ip_min' && intval($value) < 10) {
return json(['code' => -1, 'msg' => '自动更新时间间隔不能小于10分钟']);
}
config_set($key, $value);
Cache::delete('configs');
}

View File

@@ -7,6 +7,9 @@ use Exception;
use think\facade\Db;
use think\facade\View;
use think\facade\Cache;
use app\service\OptimizeService;
use app\service\CertTaskService;
use app\service\ExpireNoticeService;
class System extends BaseController
{
@@ -28,6 +31,12 @@ class System extends BaseController
return json(['code' => 0, 'msg' => 'succ']);
}
public function loginset()
{
if (!checkPermission(2)) return $this->alert('error', '无权限');
return View::fetch();
}
public function noticeset()
{
if (!checkPermission(2)) return $this->alert('error', '无权限');
@@ -101,4 +110,38 @@ class System extends BaseController
}
return json(['code' => 0]);
}
public function cronset()
{
if (!checkPermission(2)) return $this->alert('error', '无权限');
if (config_get('cron_key') === null) {
config_set('cron_key', random(10));
Cache::delete('configs');
}
View::assign('is_user_www', isset($_SERVER['USER']) && $_SERVER['USER'] == 'www');
View::assign('siteurl', request()->root(true));
return View::fetch();
}
public function cron()
{
if (function_exists("set_time_limit")) {
@set_time_limit(0);
}
if (function_exists("ignore_user_abort")) {
@ignore_user_abort(true);
}
if (isset($_SERVER['HTTP_USER_AGENT']) && str_contains($_SERVER['HTTP_USER_AGENT'], 'Baiduspider')) exit;
$key = input('get.key', '');
$cron_key = config_get('cron_key');
if (config_get('cron_type', '0') != '1' || empty($cron_key)) exit('未开启当前方式');
if ($key != $cron_key) exit('访问密钥错误');
$res = (new OptimizeService())->execute();
if (!$res) {
(new CertTaskService())->execute();
(new ExpireNoticeService())->task();
}
echo 'success!';
}
}

View File

@@ -641,6 +641,44 @@ class DeployHelper
],
],
],
'lucky' => [
'name' => 'Lucky',
'class' => 1,
'icon' => 'lucky.png',
'desc' => '更新Lucky证书',
'note' => '在“设置->开发者设置”打开OpenToken开关',
'tasknote' => '系统会根据关联SSL证书的域名自动更新对应证书',
'inputs' => [
'url' => [
'name' => '面板地址',
'type' => 'input',
'placeholder' => 'Lucky 面板地址',
'note' => '填写规则如https://192.168.1.100:16601 ,不要带其他后缀',
'required' => true,
],
'path' => [
'name' => '安全入口',
'type' => 'input',
'note' => '未设置请留空参考Lucky设置中的安全入口设置'
],
'opentoken' => [
'name' => 'OpenToken',
'type' => 'input',
'placeholder' => '',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [],
],
'proxmox' => [
'name' => 'Proxmox VE',
'class' => 1,
@@ -770,7 +808,7 @@ class DeployHelper
['value'=>'ap-southeast-1', 'label'=>'非中国内地'],
],
'value' => 'cn-hangzhou',
'show' => 'product==\'waf\'||product==\'waf2\'||product==\'ddoscoo\'',
'show' => 'product==\'waf\'||product==\'waf2\'||product==\'ddoscoo\'||product==\'esa\'',
'required' => true,
],
'regionid' => [
@@ -960,6 +998,24 @@ class DeployHelper
'show' => 'product==\'lighthouse\'||product==\'ddos\'',
'required' => true,
],
'site_type' => [
'name' => '站点类型',
'type' => 'select',
'options' => [
['value'=>'cn', 'label'=>'国内站'],
['value'=>'intl', 'label'=>'国际站'],
],
'value' => 'cn',
'show' => 'product==\'teo\'',
'required' => true,
],
'site_id' => [
'name' => '站点ID',
'type' => 'input',
'placeholder' => '类似于zone-xxxx在站点列表或概览页面查看',
'show' => 'product==\'teo\'',
'required' => true,
],
'domain' => [
'name' => '绑定的域名',
'type' => 'input',
@@ -1132,7 +1188,7 @@ class DeployHelper
'doge' => [
'name' => '多吉云',
'class' => 2,
'icon' => 'cloud.png',
'icon' => 'doge.png',
'desc' => '支持部署到多吉云融合CDN',
'note' => '支持部署到多吉云融合CDN',
'inputs' => [
@@ -1540,6 +1596,46 @@ class DeployHelper
],
],
],
'rainyun' => [
'name' => '雨云',
'class' => 2,
'icon' => 'waf.png',
'desc' => '替换雨云证书管理内的证书',
'note' => null,
'inputs' => [
'account' => [
'name' => '账号',
'type' => 'input',
'placeholder' => '仅用作标记',
'required' => true,
],
'apikey' => [
'name' => 'ApiKey',
'type' => 'input',
'placeholder' => '',
'note' => '在 账户设置->API密钥 页面查看',
'required' => true,
],
'proxy' => [
'name' => '使用代理服务器',
'type' => 'radio',
'options' => [
'0' => '否',
'1' => '是',
],
'value' => '0'
],
],
'taskinputs' => [
'id' => [
'name' => '证书ID',
'type' => 'input',
'placeholder' => '',
'note' => '在SSL证书->我的证书页面查看,注意域名是否与证书匹配',
'required' => true,
],
],
],
'aws' => [
'name' => 'AWS',
'class' => 2,
@@ -1788,6 +1884,12 @@ class DeployHelper
'show' => 'format==\'pfx\'',
'required' => true,
],
'cmd_pre' => [
'name' => '上传前执行命令',
'type' => 'textarea',
'show' => 'format==\'pem\'||uptype==0',
'placeholder' => '可留空,上传前执行脚本命令',
],
'cmd' => [
'name' => '上传完执行命令',
'type' => 'textarea',

View File

@@ -19,6 +19,7 @@ class DnsHelper
'log' => true, //是否支持查看日志
'weight' => false, //是否支持权重
'page' => false, //是否客户端分页
'add' => true, //是否支持添加域名
],
'dnspod' => [
'name' => '腾讯云',
@@ -32,6 +33,7 @@ class DnsHelper
'log' => true,
'weight' => true,
'page' => false,
'add' => true,
],
'huawei' => [
'name' => '华为云',
@@ -45,6 +47,7 @@ class DnsHelper
'log' => false,
'weight' => true,
'page' => false,
'add' => true,
],
'baidu' => [
'name' => '百度云',
@@ -58,6 +61,7 @@ class DnsHelper
'log' => false,
'weight' => false,
'page' => true,
'add' => true,
],
'west' => [
'name' => '西部数码',
@@ -71,6 +75,7 @@ class DnsHelper
'log' => false,
'weight' => false,
'page' => false,
'add' => false,
],
'huoshan' => [
'name' => '火山引擎',
@@ -84,6 +89,7 @@ class DnsHelper
'log' => false,
'weight' => true,
'page' => false,
'add' => true,
],
'jdcloud' => [
'name' => '京东云',
@@ -97,6 +103,7 @@ class DnsHelper
'log' => false,
'weight' => true,
'page' => false,
'add' => true,
],
'dnsla' => [
'name' => 'DNSLA',
@@ -110,6 +117,7 @@ class DnsHelper
'log' => false,
'weight' => true,
'page' => false,
'add' => true,
],
'cloudflare' => [
'name' => 'Cloudflare',
@@ -123,6 +131,7 @@ class DnsHelper
'log' => false,
'weight' => false,
'page' => false,
'add' => true,
],
'namesilo' => [
'name' => 'NameSilo',
@@ -136,6 +145,7 @@ class DnsHelper
'log' => false,
'weight' => false,
'page' => true,
'add' => false,
],
'powerdns' => [
'name' => 'PowerDNS',
@@ -150,6 +160,7 @@ class DnsHelper
'log' => false,
'weight' => false,
'page' => true,
'add' => true,
],
];

View File

@@ -31,4 +31,6 @@ interface DnsInterface
function getRecordLine();
function getMinTTL();
function addDomain($Domain);
}

View File

@@ -10,11 +10,11 @@ class NewDbManager extends \think\Db
/**
* 创建数据库连接实例
* @access protected
* @param string|null $name 连接标识
* @param string|array|null $name 连接标识
* @param bool $force 强制重新连接
* @return ConnectionInterface
*/
protected function instance(string $name = null, bool $force = false): ConnectionInterface
protected function instance(string|array $name = null, bool $force = false): ConnectionInterface
{
if (empty($name)) {
$name = $this->getConfig('default', 'mysql');

View File

@@ -128,7 +128,7 @@ class google implements CertInterface
private function getEAB()
{
$api = "https://gts.rat.dev/eab";
$response = curl_client($api, null, null, null, null, $this->config['proxy'] == 1, 'GET', 10);
$response = http_request($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']);

View File

@@ -110,7 +110,7 @@ class tencent implements CertInterface
'ServiceType' => 'nginx',
];
$data = $this->request('DescribeDownloadCertificateUrl', $param);
$file_data = curl_client($data['DownloadCertificateUrl'], null, null, null, null, $this->proxy);
$file_data = http_request($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'];

View File

@@ -120,7 +120,7 @@ class zerossl implements CertInterface
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);
$response = http_request($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']);

View File

@@ -27,7 +27,7 @@ class Ucloud
$param = array_merge($param, $params);
$param['Signature'] = $this->ucloudSignature($param);
$ua = sprintf("PHP/%s PHP-SDK/%s", phpversion(), self::VERSION);
$response = get_curl($this->ApiUrl, json_encode($param), 0, 0, 0, $ua, 0, ['Content-Type: application/json']);
$response = get_curl($this->ApiUrl, json_encode($param), 0, 0, $ua, 0, ['Content-Type' => 'application/json']);
$result = json_decode($response, true);
if (isset($result['RetCode']) && $result['RetCode'] == 0) {
return $result;

View File

@@ -42,14 +42,14 @@ class aliyun implements DeployInterface
} elseif ($config['product'] == 'fc2') {
$this->deploy_fc2($fullchain, $privatekey, $config);
} else {
[$cert_id, $cert_name] = $this->get_cert_id($fullchain, $privatekey);
[$cert_id, $cert_name] = $this->get_cert_id($fullchain, $privatekey, $config);
if (!$cert_id) throw new Exception('证书ID获取失败');
if ($config['product'] == 'cdn') {
$this->deploy_cdn($cert_id, $cert_name, $config);
} elseif ($config['product'] == 'dcdn') {
$this->deploy_dcdn($cert_id, $cert_name, $config);
} elseif ($config['product'] == 'esa') {
$this->deploy_esa($cert_id, $config);
$this->deploy_esa($cert_id, $cert_name, $config);
} elseif ($config['product'] == 'oss') {
$this->deploy_oss($cert_id, $config);
} elseif ($config['product'] == 'waf') {
@@ -74,14 +74,20 @@ class aliyun implements DeployInterface
}
}
private function get_cert_id($fullchain, $privatekey)
private function get_cert_id($fullchain, $privatekey, $config)
{
$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']);
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'cas.aliyuncs.com', '2020-04-07', $this->proxy);
if ($config['region'] == 'ap-southeast-1') {
$endpoint = 'cas.ap-southeast-1.aliyuncs.com';
} else {
$endpoint = 'cas.aliyuncs.com';
}
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2020-04-07', $this->proxy);
$param = [
'Action' => 'ListUserCertificateOrder',
'Keyword' => $certInfo['subject']['CN'],
@@ -95,7 +101,7 @@ class aliyun implements DeployInterface
$cert_id = null;
if ($data['TotalCount'] > 0 && !empty($data['CertificateOrderList'])) {
foreach ($data['CertificateOrderList'] as $cert) {
if (strtolower($cert['SerialNo']) == $serial_no) {
if (strtolower($cert['SerialNo']) == $serial_no || strpos(strtolower($cert['SerialNo']), $serial_no) !== false) {
$cert_id = $cert['CertificateId'];
$cert_name = $cert['Name'];
break;
@@ -157,12 +163,18 @@ class aliyun implements DeployInterface
$this->log('DCDN域名 ' . $domain . ' 部署证书成功!');
}
private function deploy_esa($cas_id, $config)
private function deploy_esa($cas_id, $cert_name, $config)
{
$sitename = $config['esa_sitename'];
if (empty($sitename)) throw new Exception('ESA站点名称不能为空');
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, 'esa.cn-hangzhou.aliyuncs.com', '2024-09-10');
if ($config['region'] == 'ap-southeast-1') {
$endpoint = 'esa.ap-southeast-1.aliyuncs.com';
} else {
$endpoint = 'esa.cn-hangzhou.aliyuncs.com';
}
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2024-09-10');
$param = [
'Action' => 'ListSites',
'SiteName' => $sitename,
@@ -188,24 +200,26 @@ class aliyun implements DeployInterface
}
$this->log('ESA站点 ' . $sitename . ' 查询到' . $data['TotalCount'] . '个SSL证书');
$cert_id = null;
$cert_name = null;
$casid = null;
foreach ($data['Result'] as $cert) {
$domains = explode(',', $cert['SAN']);
$flag = true;
foreach ($domains as $domain) {
if (!in_array($domain, $config['domainList'])) {
$flag = false;
$exist_cert_id = null;
$exist_cert_name = null;
$exist_cert_casid = null;
if ($data['TotalCount'] > 0) {
foreach ($data['Result'] as $cert) {
$domains = explode(',', $cert['SAN']);
$flag = true;
foreach ($domains as $domain) {
if (!in_array($domain, $config['domainList'])) {
$flag = false;
break;
}
}
if ($flag) {
$exist_cert_id = $cert['Id'];
$exist_cert_name = $cert['Name'];
$exist_cert_casid = isset($cert['CasId']) ? $cert['CasId'] : null;
break;
}
}
if ($flag) {
$cert_id = $cert['Id'];
$cert_name = $cert['CommonName'];
$casid = $cert['CasId'];
break;
}
}
$param = [
@@ -213,20 +227,25 @@ class aliyun implements DeployInterface
'SiteId' => $site_id,
'Type' => 'cas',
'CasId' => $cas_id,
'Name' => $cert_name,
'Region' => $config['region'],
];
if ($cert_id) {
$param['Update'] = 'true';
$param['Id'] = $cert_id;
if ($casid == $cas_id) {
if ($exist_cert_id) {
$param['Id'] = $exist_cert_id;
if ($exist_cert_casid == $cas_id) {
$this->log('ESA站点 ' . $sitename . ' 证书已配置,无需重复操作');
return;
}
}
$client->request($param);
if ($cert_id) {
$this->log('ESA站点 ' . $sitename . ' 域名 ' . $cert_name . ' 更新证书成功!');
if ($exist_cert_name) {
$this->log('ESA站点 ' . $sitename . ' 证书 ' . $exist_cert_name . ' 更新成功');
} else {
$this->log('ESA站点 ' . $sitename . ' 添加证书成功!');
$this->log('ESA站点 ' . $sitename . ' 证书添加成功!');
}
}
@@ -245,6 +264,12 @@ class aliyun implements DeployInterface
$domain = $config['domain'];
if (empty($domain)) throw new Exception('WAF绑定域名不能为空');
if ($config['region'] == 'ap-southeast-1') {
$cert_id .= '-ap-southeast-1';
} else {
$cert_id .= '-cn-hangzhou';
}
$endpoint = 'wafopenapi.' . $config['region'] . '.aliyuncs.com';
$client = new AliyunClient($this->AccessKeyId, $this->AccessKeySecret, $endpoint, '2021-10-01', $this->proxy);
@@ -273,17 +298,19 @@ class aliyun implements DeployInterface
} catch (Exception $e) {
throw new Exception('查询CNAME接入详情失败' . $e->getMessage());
}
if (!isset($data['Listen'])) {
throw new Exception('没有找到' . $domain . '监听器');
}
if (isset($data['Listen']['CertId'])) {
$old_cert_id = $data['Listen']['CertId'];
if (strpos($old_cert_id, '-')) $old_cert_id = substr($old_cert_id, 0, strpos($old_cert_id, '-'));
if (!empty($old_cert_id) && $old_cert_id == $cert_id) {
$this->log('WAF域名 ' . $domain . ' 证书已配置,无需重复操作');
return;
}
}
$data['Listen']['CertId'] = $cert_id . '-cn-hangzhou';
$data['Listen']['CertId'] = $cert_id;
if (empty($data['Listen']['HttpsPorts'])) $data['Listen']['HttpsPorts'] = [443];
$data['Redirect']['Backends'] = $data['Redirect']['AllBackends'];
$param = [

View File

@@ -56,10 +56,10 @@ class baishan implements DeployInterface
$headers = [];
$body = null;
if ($params) {
$headers[] = 'Content-Type: application/json';
$headers['Content-Type'] = 'application/json';
$body = json_encode($params);
}
$response = curl_client($url, $body, null, null, $headers, $this->proxy);
$response = http_request($url, $body, null, null, $headers, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['code']) && $result['code'] == 0) {
return $result;

View File

@@ -179,7 +179,7 @@ class btpanel implements DeployInterface
'request_time' => $now_time
];
$post_data = array_merge($post_data, $params);
$response = curl_client($url, $post_data, null, null, null, $this->proxy);
$response = http_request($url, $post_data, null, null, null, $this->proxy);
return $response['body'];
}
}

View File

@@ -123,12 +123,12 @@ class btwaf implements DeployInterface
$now_time = time();
$headers = [
'waf_request_time: ' . $now_time,
'waf_request_token: ' . md5($now_time . md5($this->key)),
'Content-Type: application/json',
'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');
$response = http_request($url, $post, null, null, $headers, $this->proxy, 'POST');
return $response['body'];
}
}

View File

@@ -37,13 +37,13 @@ class cachefly implements DeployInterface
private function request($path, $params = null, $method = null)
{
$url = $this->url . $path;
$headers = ['x-cf-authorization: Bearer ' . $this->apikey];
$headers = ['x-cf-authorization' => 'Bearer ' . $this->apikey];
$body = null;
if ($params) {
$headers[] = 'Content-Type: application/json';
$headers['Content-Type'] = 'application/json';
$body = json_encode($params);
}
$response = curl_client($url, $body, null, null, $headers, $this->proxy, $method);
$response = http_request($url, $body, null, null, $headers, $this->proxy, $method);
$result = json_decode($response['body'], true);
if ($response['code'] >= 200 && $response['code'] < 300) {
return $result;

View File

@@ -44,13 +44,13 @@ class cdnfly implements DeployInterface
private function request($path, $params = null, $method = null)
{
$url = $this->url . $path;
$headers = ['api-key: ' . $this->api_key, 'api-secret: ' . $this->api_secret];
$headers = ['api-key' => $this->api_key, 'api-secret' => $this->api_secret];
$body = null;
if ($params) {
$headers[] = 'Content-Type: application/json';
$headers['Content-Type'] = 'application/json';
$body = json_encode($params);
}
$response = curl_client($url, $body, null, null, $headers, $this->proxy, $method);
$response = http_request($url, $body, null, null, $headers, $this->proxy, $method);
$result = json_decode($response['body'], true);
if (isset($result['code']) && $result['code'] == 0) {
return isset($result['data']) ? $result['data'] : null;

View File

@@ -95,13 +95,13 @@ class doge implements DeployInterface
$signStr = $path . "\n" . $body;
$sign = hash_hmac('sha1', $signStr, $this->SecretKey);
$authorization = "TOKEN " . $this->AccessKey . ":" . $sign;
$headers = ['Authorization: ' . $authorization];
if($body && $json) $headers[] = 'Content-Type: application/json';
$headers = ['Authorization' => $authorization];
if($body && $json) $headers['Content-Type'] = 'application/json';
$url = 'https://api.dogecloud.com'.$path;
$response = curl_client($url, $body, null, null, $headers, $this->proxy);
$response = http_request($url, $body, null, null, $headers, $this->proxy);
$result = json_decode($response['body'], true);
if(isset($result['code']) && $result['code'] == 200){
return isset($result['data']) ? $result['data'] : true;
return $result['data'] ?? true;
}elseif(isset($result['msg'])){
throw new Exception($result['msg']);
}else{

View File

@@ -42,13 +42,13 @@ class gcore implements DeployInterface
private function request($path, $params = null, $method = null)
{
$url = $this->url . $path;
$headers = ['Authorization: APIKey ' . $this->apikey];
$headers = ['Authorization' => 'APIKey ' . $this->apikey];
$body = null;
if ($params) {
$headers[] = 'Content-Type: application/json';
$headers['Content-Type'] = 'application/json';
$body = json_encode($params);
}
$response = curl_client($url, $body, null, null, $headers, $this->proxy, $method);
$response = http_request($url, $body, null, null, $headers, $this->proxy, $method);
$result = json_decode($response['body'], true);
if ($response['code'] >= 200 && $response['code'] < 300) {
return $result;

View File

@@ -119,19 +119,19 @@ class goedge implements DeployInterface
$body = null;
if ($this->accessToken) {
if ($this->systype == '1') {
$headers[] = 'X-Cloud-Access-Token: ' . $this->accessToken;
$headers['X-Cloud-Access-Token'] = $this->accessToken;
} else {
$headers[] = 'X-Edge-Access-Token: ' . $this->accessToken;
$headers['X-Edge-Access-Token'] = $this->accessToken;
}
}
if ($params) {
$headers[] = 'Content-Type: application/json';
$headers['Content-Type'] = 'application/json';
$body = json_encode($params);
}
$response = curl_client($url, $body, null, null, $headers, $this->proxy);
$response = http_request($url, $body, null, null, $headers, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['code']) && $result['code'] == 200) {
return isset($result['data']) ? $result['data'] : null;
return $result['data'] ?? null;
} elseif (isset($result['message'])) {
throw new Exception($result['message']);
} else {

View File

@@ -71,7 +71,7 @@ class kangle implements DeployInterface
'certificate' => $fullchain,
'certificate_key' => $privatekey,
];
$response = curl_client($this->url . $path, http_build_query($post), null, $this->cookie, null, $this->proxy);
$response = http_request($this->url . $path, http_build_query($post), null, $this->cookie, null, $this->proxy);
if (strpos($response['body'], '成功')) {
return true;
} elseif (preg_match('/alert\(\'(.*?)\'\)/i', $response['body'], $match)) {
@@ -90,7 +90,7 @@ class kangle implements DeployInterface
'certificate' => $fullchain,
'certificate_key' => $privatekey,
];
$response = curl_client($this->url . $path, http_build_query($post), null, $this->cookie, null, $this->proxy);
$response = http_request($this->url . $path, http_build_query($post), null, $this->cookie, null, $this->proxy);
if (strpos($response['body'], '成功')) {
return true;
} elseif (preg_match('/alert\(\'(.*?)\'\)/i', $response['body'], $match)) {
@@ -114,11 +114,11 @@ class kangle implements DeployInterface
private function loginBySkey()
{
$url = $this->url . '/vhost/index.php?c=sso&a=hello&url=' . urlencode($this->url . '/index.php?');
$response = curl_client($url, null, null, null, null, $this->proxy);
$response = http_request($url, null, null, null, null, $this->proxy);
if ($response['code'] == 302 && !empty($response['redirect_url'])) {
$cookie = '';
if (preg_match_all('/Set-Cookie: (.*);/iU', $response['header'], $matchs)) {
foreach ($matchs[1] as $val) {
if (isset($response['headers']['Set-Cookie'])) {
foreach ($response['headers']['Set-Cookie'] as $val) {
$arr = explode('=', $val);
if ($arr[1] == '' || $arr[1] == 'deleted') continue;
$cookie .= $val . '; ';
@@ -147,7 +147,7 @@ class kangle implements DeployInterface
{
$s = md5($sess_key . $this->username . $sess_key . $this->skey);
$url = $this->url . '/vhost/index.php?c=sso&a=login&name=' . $this->username . '&r=' . $sess_key . '&s=' . $s;
$response = curl_client($url, null, null, $cookie, null, $this->proxy);
$response = http_request($url, null, null, $cookie, null, $this->proxy);
if ($response['code'] == 302) {
return true;
} elseif (strlen($response['body']) > 3 && strlen($response['body']) < 50) {
@@ -165,11 +165,11 @@ class kangle implements DeployInterface
'username' => $this->username,
'passwd' => $this->password,
];
$response = curl_client($url, http_build_query($post), $referer, null, null, $this->proxy);
$response = http_request($url, http_build_query($post), $referer, null, null, $this->proxy);
if ($response['code'] == 302) {
$cookie = '';
if (preg_match_all('/Set-Cookie: (.*);/iU', $response['header'], $matchs)) {
foreach ($matchs[1] as $val) {
if (isset($response['headers']['Set-Cookie'])) {
foreach ($response['headers']['Set-Cookie'] as $val) {
$arr = explode('=', $val);
if ($arr[1] == '' || $arr[1] == 'deleted') continue;
$cookie .= $val . '; ';
@@ -191,7 +191,7 @@ class kangle implements DeployInterface
private function getMain()
{
$path = '/vhost/';
curl_client($this->url . $path, null, null, $this->cookie, null, $this->proxy);
http_request($this->url . $path, null, null, $this->cookie, null, $this->proxy);
}
public function setLogger($func)

View File

@@ -71,7 +71,7 @@ class kangleadmin implements DeployInterface
'certificate' => $fullchain,
'certificate_key' => $privatekey,
];
$response = curl_client($this->url . $path, http_build_query($post), null, $this->cookie, null, $this->proxy);
$response = http_request($this->url . $path, http_build_query($post), null, $this->cookie, null, $this->proxy);
if (strpos($response['body'], '成功')) {
return true;
} elseif (preg_match('/alert\(\'(.*?)\'\)/i', $response['body'], $match)) {
@@ -90,7 +90,7 @@ class kangleadmin implements DeployInterface
'certificate' => $fullchain,
'certificate_key' => $privatekey,
];
$response = curl_client($this->url . $path, http_build_query($post), null, $this->cookie, null, $this->proxy);
$response = http_request($this->url . $path, http_build_query($post), null, $this->cookie, null, $this->proxy);
if (strpos($response['body'], '成功')) {
return true;
} elseif (preg_match('/alert\(\'(.*?)\'\)/i', $response['body'], $match)) {
@@ -105,11 +105,11 @@ class kangleadmin implements DeployInterface
private function login()
{
$url = $this->url . $this->path . '/index.php?c=sso&a=hello&url=' . urlencode($this->url . $this->path . '/index.php?');
$response = curl_client($url, null, null, null, null, $this->proxy);
$response = http_request($url, null, null, null, null, $this->proxy);
if ($response['code'] == 302 && !empty($response['redirect_url'])) {
$cookie = '';
if (preg_match_all('/Set-Cookie: (.*);/iU', $response['header'], $matchs)) {
foreach ($matchs[1] as $val) {
if (isset($response['headers']['Set-Cookie'])) {
foreach ($response['headers']['Set-Cookie'] as $val) {
$arr = explode('=', $val);
if ($arr[1] == '' || $arr[1] == 'deleted') continue;
$cookie .= $val . '; ';
@@ -138,7 +138,7 @@ class kangleadmin implements DeployInterface
{
$s = md5($sess_key . $this->username . $sess_key . $this->skey);
$url = $this->url . $this->path . '/index.php?c=sso&a=login&name=' . $this->username . '&r=' . $sess_key . '&s=' . $s;
$response = curl_client($url, null, null, $cookie, null, $this->proxy);
$response = http_request($url, null, null, $cookie, null, $this->proxy);
if ($response['code'] == 302) {
return true;
} elseif (strlen($response['body']) > 3 && strlen($response['body']) < 50) {
@@ -151,9 +151,9 @@ class kangleadmin implements DeployInterface
private function loginVhost($name)
{
$url = $this->url . $this->path . '/index.php?c=vhost&a=impLogin&name=' . $name;
$response = curl_client($url, null, null, $this->cookie, null, $this->proxy);
$response = http_request($url, null, null, $this->cookie, null, $this->proxy);
if ($response['code'] == 302) {
curl_client($this->url . '/vhost/', null, null, $this->cookie, null, $this->proxy);
http_request($this->url . '/vhost/', null, null, $this->cookie, null, $this->proxy);
} else {
throw new Exception('用户面板登录失败 (httpCode=' . $response['code'] . ')');
}

View File

@@ -73,8 +73,8 @@ class kuocai implements DeployInterface
$url = 'https://kuocai.cn' . $path;
$body = $json ? json_encode($params) : $params;
$headers = [];
if ($json) $headers[] = 'Content-Type: application/json';
$response = curl_client(
if ($json) $headers['Content-Type'] = 'application/json';
$response = http_request(
$url,
$body,
null,

View File

@@ -76,16 +76,16 @@ class lecdn implements DeployInterface
$headers = [];
$body = null;
if ($this->accessToken) {
$headers[] = 'Authorization: Bearer ' . $this->accessToken;
$headers['Authorization'] = 'Bearer ' . $this->accessToken;
}
if ($params) {
$headers[] = 'Content-Type: application/json;charset=UTF-8';
$headers['Content-Type'] = 'application/json;charset=UTF-8';
$body = json_encode($params);
}
$response = curl_client($url, $body, null, null, $headers, $this->proxy, $method);
$response = http_request($url, $body, null, null, $headers, $this->proxy, $method);
$result = json_decode($response['body'], true);
if (isset($result['code']) && $result['code'] == 200) {
return isset($result['data']) ? $result['data'] : null;
return $result['data'] ?? null;
} elseif (isset($result['message'])) {
throw new Exception($result['message']);
} else {

114
app/lib/deploy/lucky.php Normal file
View File

@@ -0,0 +1,114 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class lucky implements DeployInterface
{
private $logger;
private $url;
private $opentoken;
private $proxy;
public function __construct($config)
{
$this->url = rtrim($config['url'], '/') . (!empty($config['path']) ? $config['path'] : '');
$this->opentoken = $config['opentoken'];
$this->proxy = $config['proxy'] == 1;
}
public function check()
{
if (empty($this->url) || empty($this->opentoken)) throw new Exception('请填写面板地址和OpenToken');
$this->request("/api/modules/list");
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
$domains = $config['domainList'];
if (empty($domains)) throw new Exception('没有设置要部署的域名');
try {
$data = $this->request("/api/ssl");
$this->log('获取证书列表成功');
} catch (Exception $e) {
throw new Exception('获取证书列表失败:' . $e->getMessage());
}
$success = 0;
$errmsg = null;
if (!empty($data['list'])) {
foreach ($data['list'] as $row) {
if (empty($row['CertsInfo']['Domains'])) continue;
$cert_domains = $row['CertsInfo']['Domains'];
$flag = false;
foreach ($cert_domains as $domain) {
if (in_array($domain, $domains)) {
$flag = true;
break;
}
}
if ($flag) {
$params = [
'Key' => $row['Key'],
'CertBase64' => base64_encode($fullchain),
'KeyBase64' => base64_encode($privatekey),
'AddFrom' => 'file',
'Enable' => true,
'MappingToPath' => false,
'Remark' => $row['Remark'] ?: '',
'AllSyncClient' => false,
];
try {
$this->request('/api/ssl', $params, 'PUT');
$this->log("证书ID:{$row['Key']}更新成功!");
$success++;
} catch (Exception $e) {
$errmsg = $e->getMessage();
$this->log("证书ID:{$row['Key']}更新失败:" . $errmsg);
}
}
}
}
if ($success == 0) {
throw new Exception($errmsg ? $errmsg : '没有要更新的证书');
}
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
private function request($path, $params = null, $method = null)
{
$url = $this->url . $path;
$headers = [
'openToken' => $this->opentoken,
];
$body = null;
if ($params) {
$body = json_encode($params);
$headers['Content-Type'] = 'application/json';
}
$response = http_request($url, $body, null, null, $headers, $this->proxy, $method);
$result = json_decode($response['body'], true);
if (isset($result['ret']) && $result['ret'] == 0) {
return $result;
} elseif (isset($result['msg'])) {
throw new Exception($result['msg']);
} else {
throw new Exception('请求失败(httpCode=' . $response['code'] . ')');
}
}
}

View File

@@ -118,10 +118,10 @@ class mwpanel implements DeployInterface
$url = $this->url . $path;
$headers = [
'app-id: '.$this->appid,
'app-secret: '.$this->appsecret,
'app-id' => $this->appid,
'app-secret' => $this->appsecret,
];
$response = curl_client($url, $params ? http_build_query($params) : null, null, null, $headers, $this->proxy);
$response = http_request($url, $params ? http_build_query($params) : null, null, null, $headers, $this->proxy);
return $response['body'];
}
}

View File

@@ -96,15 +96,15 @@ class opanel implements DeployInterface
$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';
$response = curl_client($url, $body, null, null, $headers, $this->proxy);
if ($body) $headers['Content-Type'] = 'application/json';
$response = http_request($url, $body, null, null, $headers, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['code']) && $result['code'] == 200) {
return isset($result['data']) ? $result['data'] : null;
return $result['data'] ?? null;
} elseif (isset($result['message'])) {
throw new Exception($result['message']);
} else {

View File

@@ -59,9 +59,9 @@ class proxmox implements DeployInterface
private function send_request($path, $params = null)
{
$url = $this->url . $path;
$headers = ['Authorization: PVEAPIToken=' . $this->api_user . '=' . $this->api_key];
$headers = ['Authorization' => 'PVEAPIToken=' . $this->api_user . '=' . $this->api_key];
$post = $params ? http_build_query($params) : null;
$response = curl_client($url, $post, null, null, $headers, $this->proxy);
$response = http_request($url, $post, null, null, $headers, $this->proxy);
if ($response['code'] == 200) {
$result = json_decode($response['body'], true);
if (isset($result['data'])) {
@@ -75,12 +75,7 @@ class proxmox implements DeployInterface
throw new Exception('返回数据解析失败');
}
} else {
$header = getSubstr($response['header'], ' ', "\r\n");
if ($header) {
throw new Exception($header);
} else {
throw new Exception('请求失败(httpCode=' . $response['code'] . ')');
}
throw new Exception('请求失败(httpCode=' . $response['code'] . ', body=' . $response['body'] . ')');
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace app\lib\deploy;
use app\lib\DeployInterface;
use Exception;
class rainyun implements DeployInterface
{
private $logger;
private $url = 'https://api.v2.rainyun.com';
private $apikey;
private $proxy;
public function __construct($config)
{
$this->apikey = $config['apikey'];
$this->proxy = $config['proxy'] == 1;
}
public function check()
{
if (empty($this->apikey)) throw new Exception('ApiKey不能为空');
$this->request('/product/');
}
public function deploy($fullchain, $privatekey, $config, &$info)
{
if (empty($config['id'])) throw new Exception('证书ID不能为空');
$params = [
'cert' => $fullchain,
'key' => $privatekey,
];
try {
$this->request('/product/sslcenter/' . $config['id'], $params, 'PUT');
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
$this->log('证书ID:' . $config['id'] . '更新成功!');
}
private function request($path, $params = null, $method = null)
{
$url = $this->url . $path;
$headers = [
'x-api-key' => $this->apikey,
];
$body = null;
if ($params) {
$headers['Content-Type'] = 'application/json';
$body = json_encode($params);
}
$response = http_request($url, $body, null, null, $headers, $this->proxy, $method);
$result = json_decode($response['body'], true);
if (isset($result['code']) && $result['code'] == 200) {
return $result;
} elseif (isset($result['message'])) {
throw new Exception($result['message']);
} else {
if (!empty($response['body'])) $this->log('Response:' . $response['body']);
throw new Exception('请求失败(httpCode=' . $response['code'] . ')');
}
}
public function setLogger($func)
{
$this->logger = $func;
}
private function log($txt)
{
if ($this->logger) {
call_user_func($this->logger, $txt);
}
}
}

View File

@@ -113,10 +113,10 @@ class ratpanel implements DeployInterface
$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']
$response = http_request($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'];
}
@@ -130,7 +130,7 @@ class ratpanel implements DeployInterface
// 规范化路径
$canonicalPath = $path;
if (strpos($path, '/api') !== 0) {
if (!str_starts_with($path, '/api')) {
$apiPos = strpos($path, '/api');
if ($apiPos !== false) {
$canonicalPath = substr($path, $apiPos);

View File

@@ -83,16 +83,16 @@ class safeline implements DeployInterface
private function request($path, $params = null)
{
$url = $this->url . $path;
$headers = ['X-SLCE-API-TOKEN: ' . $this->token];
$headers = ['X-SLCE-API-TOKEN' => $this->token];
$body = null;
if ($params) {
$heders[] = 'Content-Type: application/json';
$headers['Content-Type'] = 'application/json';
$body = json_encode($params);
}
$response = curl_client($url, $body, null, null, $headers, $this->proxy);
$response = http_request($url, $body, null, null, $headers, $this->proxy);
$result = json_decode($response['body'], true);
if ($response['code'] == 200 && $result) {
return isset($result['data']) ? $result['data'] : null;
return $result['data'] ?? null;
} else {
throw new Exception(!empty($result['msg']) ? $result['msg'] : '请求失败(httpCode=' . $response['code'] . ')');
}

View File

@@ -23,6 +23,14 @@ class ssh implements DeployInterface
public function deploy($fullchain, $privatekey, $config, &$info)
{
$connection = $this->connect();
if (isset($config['cmd_pre']) && !empty($config['cmd_pre'])) {
$cmds = explode("\n", $config['cmd_pre']);
foreach ($cmds as $cmd) {
$cmd = trim($cmd);
if (empty($cmd)) continue;
$this->exec($connection, $cmd);
}
}
$sftp = ssh2_sftp($connection);
if ($config['format'] == 'pem') {
$stream = fopen("ssh2.sftp://$sftp{$config['pem_cert_file']}", 'w');

View File

@@ -43,7 +43,7 @@ class synology implements DeployInterface
'format' => 'sid',
'enable_syno_token' => 'yes',
];
$response = curl_client($url, http_build_query($params), null, null, null, $this->proxy);
$response = http_request($url, http_build_query($params), null, null, null, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['success']) && $result['success']) {
$this->token = $result['data'];
@@ -69,7 +69,7 @@ class synology implements DeployInterface
'_sid' => $this->token['sid'],
'SynoToken' => $this->token['synotoken'],
];
$response = curl_client($url . '?' . http_build_query($params), null, null, $this->proxy);
$response = http_request($url . '?' . http_build_query($params), null, null, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['success']) && $result['success']) {
$this->log('获取证书列表成功');
@@ -109,19 +109,30 @@ class synology implements DeployInterface
'_sid' => $this->token['sid'],
'SynoToken' => $this->token['synotoken'],
];
$privatekey_file = tempnam(sys_get_temp_dir(), 'privatekey');
file_put_contents($privatekey_file, $privatekey);
$fullchain_file = tempnam(sys_get_temp_dir(), 'fullchain');
file_put_contents($fullchain_file, $fullchain);
$post = [
'key' => new \CURLFile($privatekey_file),
'cert' => new \CURLFile($fullchain_file),
'id' => $id,
'desc' => $config['desc'],
$headers = [
'Content-Type' => 'multipart/form-data'
];
$response = curl_client($url . '?' . http_build_query($params), $post, null, null, null, $this->proxy, null, 15);
unlink($privatekey_file);
unlink($fullchain_file);
$post = [
[
'name' => 'key',
'filename' => 'key.pem',
'contents' => $privatekey
],
[
'name' => 'cert',
'filename' => 'cert.pem',
'contents' => $fullchain
],
[
'name' => 'id',
'contents' => $id
],
[
'name' => 'desc',
'contents' => $config['desc']
]
];
$response = http_request($url . '?' . http_build_query($params), $post, null, null, $headers, $this->proxy, null, 15);
$result = json_decode($response['body'], true);
if ($id) {
if (isset($result['success']) && $result['success']) {

View File

@@ -60,6 +60,8 @@ class tencent implements DeployInterface
return $this->deploy_clb($cert_id, $config);
} elseif ($config['product'] == 'scf') {
return $this->deploy_scf($cert_id, $config);
} elseif ($config['product'] == 'teo' && isset($config['site_id'])) {
return $this->deploy_teo($cert_id, $config);
} else {
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
if ($config['product'] == 'waf') {
@@ -122,11 +124,17 @@ class tencent implements DeployInterface
} else {
$instance_ids = [$instance_id];
}
if ($product == 'cdn') {
$instance_ids = array_map(function ($id) {
return $id . '|on';
}, $instance_ids);
}
$param = [
'CertificateId' => $cert_id,
'InstanceIdList' => $instance_ids,
'ResourceType' => $product,
];
if ($product == 'live') $param['Status'] = 1;
$data = $this->client->request('DeployCertificateInstance', $param);
$this->log(json_encode($data));
$this->log(strtoupper($product) . '实例 ' . $instance_id . ' 部署证书成功!');
@@ -253,6 +261,26 @@ class tencent implements DeployInterface
$this->log('云函数自定义域名 ' . $config['domain'] . ' 部署证书成功!');
}
private function deploy_teo($cert_id, $config)
{
if (empty($config['site_id'])) throw new Exception('站点ID不能为空');
if (empty($config['domain'])) throw new Exception('绑定的域名不能为空');
$endpoint = isset($config['site_type']) && $config['site_type'] == 'intl' ? 'teo.intl.tencentcloudapi.com' : 'teo.tencentcloudapi.com';
$client = new TencentCloud($this->SecretId, $this->SecretKey, $endpoint, 'teo', '2022-09-01', null, $this->proxy);
$hosts = explode(',', $config['domain']);
$param = [
'ZoneId' => $config['site_id'],
'Hosts' => $hosts,
'Mode' => 'sslcert',
'ServerCertInfo' => [[
'CertId' => $cert_id
]]
];
$data = $client->request('ModifyHostsCertificate', $param);
$this->log('边缘安全加速域名 ' . $config['domain'] . ' 部署证书成功!');
}
public function setLogger($func)
{
$this->logger = $func;

View File

@@ -35,7 +35,7 @@ class upyun implements DeployInterface
'certificate' => $fullchain,
'private_key' => $privatekey,
];
$response = curl_client($url, http_build_query($params), null, $this->cookie, null, $this->proxy);
$response = http_request($url, http_build_query($params), null, $this->cookie, null, $this->proxy);
$result = json_decode($response['body'], true);
if ($result['data']['status'] === 0) {
$common_name = $result['data']['result']['commonName'];
@@ -52,7 +52,7 @@ class upyun implements DeployInterface
'limit' => 100,
'domain' => $common_name,
];
$response = curl_client($url . '?' . http_build_query($params), null, null, $this->cookie, null, $this->proxy);
$response = http_request($url . '?' . http_build_query($params), null, null, $this->cookie, null, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['data']['result']) && is_array($result['data']['result'])) {
$cert_list = $result['data']['result'];
@@ -73,7 +73,7 @@ class upyun implements DeployInterface
'new_crt_id' => $certificate_id,
'old_crt_id' => $crt_id,
];
$response = curl_client($url, http_build_query($params), null, $this->cookie, null, $this->proxy);
$response = http_request($url, http_build_query($params), null, $this->cookie, null, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['data']['result']) && $result['data']['result'] == true) {
$i++;
@@ -97,12 +97,12 @@ class upyun implements DeployInterface
'username' => $this->username,
'password' => $this->password,
];
$response = curl_client($url, http_build_query($params), null, null, null, $this->proxy);
$response = http_request($url, http_build_query($params), null, null, null, $this->proxy);
$result = json_decode($response['body'], true);
if (isset($result['data']['result']) && $result['data']['result'] == true) {
$cookie = '';
if (preg_match_all('/Set-Cookie: (.*);/iU', $response['header'], $matchs)) {
foreach ($matchs[1] as $val) {
if (isset($response['headers']['set-cookie'])) {
foreach ($response['headers']['set-cookie'] as $val) {
$arr = explode('=', $val);
if ($arr[1] == '' || $arr[1] == 'deleted') continue;
$cookie .= $val . '; ';

View File

@@ -24,7 +24,7 @@ class wangsu implements DeployInterface
public function check()
{
if (empty($this->username) || empty($this->apiKey)) throw new Exception('必填参数不能为空');
$this->request('/cdn/certificates');
$this->request('/api/ssl/certificate');
return true;
}
@@ -273,7 +273,7 @@ class wangsu implements DeployInterface
} 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');
$this->request('/api/certificate/' . $cert['certificate-id'], ['name' => $cert_name . '-bak'], true, null, 'PUT');
} catch (Exception $e) {
throw new Exception('证书更名失败:' . $e->getMessage());
}
@@ -416,29 +416,29 @@ class wangsu implements DeployInterface
if (empty($headers)) {
$headers = [
'Authorization: ' . $authorization,
'Date: ' . $date,
'Accept: application/json',
'Connection: close',
'Authorization' => $authorization,
'Date' => $date,
'Accept' => 'application/json',
'Connection' => 'close',
];
} else {
$headers[] = 'Authorization: ' . $authorization;
$headers[] = 'Date: ' . $date;
$headers[] = 'Accept: application/json';
$headers[] = 'Connection: close';
$headers['Authorization'] = $authorization;
$headers['Date'] = $date;
$headers['Accept'] = 'application/json';
$headers['Connection'] = 'close';
}
if ($body && $json) {
$headers[] = 'Content-Type: application/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);
$response = http_request($url, $body, null, null, $headers, $this->proxy, $method, 30);
$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 (isset($response['headers']['Location'])) {
$location = trim(array_shift($response['headers']['Location'])); // 提取 Location 头部的值并去除多余空格
if (!empty($location)) {
return $location;
}
@@ -452,6 +452,9 @@ class wangsu implements DeployInterface
} elseif (isset($result['message'])) {
throw new Exception($result['message']);
} elseif (isset($result['result'])) {
throw new Exception($result['result']);
} else {
throw new Exception('请求失败');
}

View File

@@ -103,7 +103,7 @@ class west implements DeployInterface
$params['username'] = $this->username;
$params['time'] = getMillisecond();
$params['token'] = md5($this->username . $this->api_password . $params['time']);
$response = curl_client($this->baseUrl . $path, str_replace('+', '%20', http_build_query($params)), null, null, null, $this->proxy);
$response = http_request($this->baseUrl . $path, str_replace('+', '%20', http_build_query($params)), null, null, null, $this->proxy);
$response = mb_convert_encoding($response['body'], 'UTF-8', 'GBK');
$arr = json_decode($response, true);
if ($arr) {

View File

@@ -293,6 +293,16 @@ class aliyun implements DnsInterface
return $this->request($param);
}
public function addDomain($Domain)
{
$param = ['Action' => 'AddDomain', 'DomainName' => $Domain];
$result = $this->request($param, true);
if ($result) {
return ['id' => $result['DomainId'], 'name' => $result['DomainName']];
}
return false;
}
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'];

View File

@@ -222,6 +222,19 @@ class baidu implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$query = ['clientToken' => getSid(), 'name' => $Domain];
$res = $this->send_reuqest('POST', '/v1/dns/zone', null, $query);
if ($res) {
$data = $this->getDomainInfo($Domain);
if ($data) {
return ['id' => $data['DomainId'], 'name' => $data['Domain']];
}
}
return false;
}
private function convertType($type)
{
return $type;

View File

@@ -205,6 +205,16 @@ class cloudflare implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$param = ['name' => $Domain];
$data = $this->send_reuqest('POST', '/zones', $param);
if ($data) {
return ['id' => $data['result']['id'], 'name' => $data['result']['name']];
}
return false;
}
private function convertType($type)
{
$convert_dict = ['REDIRECT_URL' => 'URI', 'FORWARD_URL' => 'URI'];

View File

@@ -210,6 +210,16 @@ class dnsla implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$param = ['domain' => $Domain];
$data = $this->execute('POST', '/api/domain', $param);
if ($data) {
return ['id' => $data['id'], 'name' => $Domain];
}
return false;
}
private function convertType($type)
{
$typeList = array_flip($this->typeList);

View File

@@ -314,6 +314,19 @@ class dnspod implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$action = 'CreateDomain';
$param = [
'Domain' => $Domain,
];
$data = $this->send_request($action, $param);
if ($data) {
return ['id' => $data['DomainInfo']['Id'], 'name' => $data['DomainInfo']['Domain']];
}
return false;
}
private function convertLineCode($line)
{
$convert_dict = ['default' => '0', 'unicom' => '10=1', 'telecom' => '10=0', 'mobile' => '10=3', 'edu' => '10=2', 'oversea' => '3=0', 'btvn' => '10=22', 'search' => '80=0', 'internal' => '7=0'];

View File

@@ -229,6 +229,18 @@ class huawei implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$params = [
'name' => $Domain,
];
$data = $this->send_request('POST', '/v2/zones', null, $params);
if ($data) {
return ['id' => $data['id'], 'name' => rtrim($data['name'], '.')];
}
return false;
}
private function convertType($type)
{
return $type;

View File

@@ -237,6 +237,16 @@ class huoshan implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$params = ['ZoneName' => $Domain];
$data = $this->send_request('POST', 'CreateZone', $params);
if ($data) {
return ['id' => $data['ZID'], 'name' => $data['ZoneName']];
}
return false;
}
private function convertType($type)
{
return $type;

View File

@@ -225,6 +225,16 @@ class jdcloud implements DnsInterface
return false;
}
public function addDomain($Domain)
{
$params = ['packId' => 0, 'domainName' => $Domain];
$data = $this->send_request('POST', '/domain', $params);
if ($data) {
return ['id' => $data['data']['id'], 'name' => $data['data']['domainName']];
}
return false;
}
private function convertType($type)
{
$convert_dict = ['REDIRECT_URL' => 'EXPLICIT_URL', 'FORWARD_URL' => 'IMPLICIT_URL'];

View File

@@ -181,6 +181,11 @@ class namesilo implements DnsInterface
return false;
}
public function addDomain($Domain)
{
return false;
}
private function send_reuqest($operation, $param = null)
{
$url = $this->baseUrl . $operation;
@@ -197,7 +202,7 @@ class namesilo implements DnsInterface
$url .= '?' . http_build_query($params);
try{
$response = curl_client($url, null, null, null, null, $this->proxy);
$response = http_request($url, null, null, null, null, $this->proxy);
}catch(Exception $e){
$this->setError($e->getMessage());
return false;

View File

@@ -327,6 +327,23 @@ class powerdns implements DnsInterface
return false;
}
public function addDomain($Domain)
{
if (substr($Domain, -1) != '.') {
$Domain .= '.';
}
$param = [
'name' => $Domain,
'kind' => 'Native',
'soa_edit_api' => 'INCREASE',
];
$result = $this->send_reuqest('POST', '/servers/' . $this->server_id . '/zones', $param);
if ($result) {
return ['id' => $result['id'], 'name' => rtrim($result['name'], '.')];
}
return false;
}
private function rrset_replace($host, $type, $ttl, $records, $remark = null)
{
$name = $host == '@' ? $this->domainid : $host . '.' . $this->domainid;
@@ -369,7 +386,7 @@ class powerdns implements DnsInterface
private function send_reuqest($method, $path, $params = null)
{
$url = $this->url . $path;
$headers[] = 'X-API-Key: ' . $this->apikey;
$headers['X-API-Key'] = $this->apikey;
$body = null;
if ($method == 'GET' || $method == 'DELETE') {
if ($params) {
@@ -377,10 +394,10 @@ class powerdns implements DnsInterface
}
} else {
$body = json_encode($params);
$headers[] = 'Content-Type: application/json';
$headers['Content-Type'] = 'application/json';
}
try {
$response = curl_client($url, $body, null, null, $headers, $this->proxy, $method);
$response = http_request($url, $body, null, null, $headers, $this->proxy, $method);
} catch (Exception $e) {
$this->setError($e->getMessage());
return false;
@@ -404,6 +421,5 @@ class powerdns implements DnsInterface
private function setError($message)
{
$this->error = $message;
//file_put_contents('logs.txt',date('H:i:s').' '.$message."\r\n", FILE_APPEND);
}
}

View File

@@ -171,6 +171,11 @@ class west implements DnsInterface
return false;
}
public function addDomain($Domain)
{
return false;
}
private function convertType($type)
{
return $type;
@@ -182,7 +187,7 @@ class west implements DnsInterface
$params['time'] = getMillisecond();
$params['token'] = md5($this->username.$this->api_password.$params['time']);
try{
$response = curl_client($this->baseUrl . $path, http_build_query($params), null, null, null, $this->proxy);
$response = http_request($this->baseUrl . $path, http_build_query($params), null, null, null, $this->proxy);
}catch(\Exception $e){
$this->setError($e->getMessage());
return false;

View File

@@ -1,2 +0,0 @@
<?php
namespace app\lib\mail\PHPMailer; class Exception extends \Exception { public function errorMessage() { return '<strong>' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "</strong><br />\n"; } }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -89,7 +89,7 @@ class CertDeployService
private function saveResult($status, $error = null, $retrytime = null)
{
$this->task['status'] = $status;
if (mb_strlen($error) > 300) {
if (!empty($error) && strlen($error) > 300) {
$error = mb_strcut($error, 0, 300);
}
$update = ['status' => $status, 'error' => $error, 'retrytime' => $retrytime];

View File

@@ -178,7 +178,7 @@ class CertOrderService
private function saveResult($status, $error = null, $retrytime = null)
{
$this->order['status'] = $status;
if (mb_strlen($error) > 300) {
if (!empty($error) && strlen($error) > 300) {
$error = mb_strcut($error, 0, 300);
}
$update = ['status' => $status, 'error' => $error, 'updatetime' => date('Y-m-d H:i:s'), 'retrytime' => $retrytime];

View File

@@ -11,15 +11,17 @@ class CertTaskService
public function execute()
{
$this->execute_deploy();
$this->execute_order();
(new ExpireNoticeService())->task();
config_set('certtask_time', date("Y-m-d H:i:s"));
echo 'done'.PHP_EOL;
if ($this->execute_deploy()) {
config_set('certdeploy_time', date("Y-m-d H:i:s"));
}
if ($this->execute_order()) {
config_set('certtask_time', date("Y-m-d H:i:s"));
}
}
private function execute_order()
{
echo '开始执行SSL证书签发任务...'.PHP_EOL;
$days = config_get('cert_renewdays', 7);
$list = Db::name('cert_order')->field('id,aid,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;
@@ -55,6 +57,7 @@ class CertTaskService
if ($failcount >= 3) break;
sleep(1);
}
return true;
}
private function execute_deploy()
@@ -64,14 +67,15 @@ class CertTaskService
$hour = date('H');
if($start <= $end){
if($hour < $start || $hour > $end){
echo '不在部署任务运行时间范围内'.PHP_EOL; return;
echo '不在部署任务运行时间范围内'.PHP_EOL; return false;
}
}else{
if($hour < $start && $hour > $end){
echo '不在部署任务运行时间范围内'.PHP_EOL; return;
echo '不在部署任务运行时间范围内'.PHP_EOL; return false;
}
}
echo '开始执行SSL证书部署任务...'.PHP_EOL;
$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;
@@ -95,5 +99,6 @@ class CertTaskService
if ($count >= 3) break;
sleep(1);
}
return true;
}
}

View File

@@ -26,6 +26,8 @@ class ExpireNoticeService
public function task()
{
echo '开始执行域名到期提醒任务...' . PHP_EOL;
config_set('domain_expire_time', date("Y-m-d H:i:s"));
$count = $this->refreshDomainList();
if ($count > 0) return;

View File

@@ -39,9 +39,7 @@ class OptimizeService
public function get_ip_address($cdn_type = 1, $ip_type = 'v4')
{
$api = config_get('optimize_ip_api', 0);
if ($api == 2) {
$url = 'https://api.345673.xyz/get_data';
} elseif ($api == 1) {
if ($api == 1) {
$url = 'https://api.hostmonit.com/get_optimization_ip';
} else {
$url = 'https://www.wetest.vip/api/cf2dns/';
@@ -51,13 +49,15 @@ class OptimizeService
$url .= 'get_cloudfront_ip';
} elseif ($cdn_type == 3) {
$url .= 'get_gcore_ip';
} elseif ($cdn_type == 4) {
$url .= 'get_edgeone_ip';
}
}
$params = [
'key' => config_get('optimize_ip_key', 'o1zrmHAF'),
'type' => $ip_type,
];
$response = get_curl($url, json_encode($params), 0, 0, 0, 0, 0, ['Content-Type: application/json; charset=UTF-8']);
$response = get_curl($url, json_encode($params), 0, 0, 0, 0, ['Content-Type' => 'application/json; charset=UTF-8']);
$arr = json_decode($response, true);
if (isset($arr['code']) && $arr['code'] == 200) {
return $arr['info'];
@@ -96,7 +96,15 @@ class OptimizeService
//批量执行优选任务
public function execute()
{
$minute = config_get('optimize_ip_min', '30');
$last = config_get('optimize_ip_time', null, true);
if ($last && strtotime($last) > time() - $minute * 60) {
return false;
}
$list = Db::name('optimizeip')->where('active', 1)->select();
if (count($list) == 0) {
return false;
}
echo '开始执行IP优选任务共获取到'.count($list).'个待执行任务'."\n";
foreach ($list as $row) {
try {
@@ -108,6 +116,8 @@ class OptimizeService
echo '优选任务'.$row['id'].'执行失败:'.$e->getMessage()."\n";
}
}
config_set('optimize_ip_time', date("Y-m-d H:i:s"));
return true;
}
//执行单个优选任务

View File

@@ -28,7 +28,7 @@ DROP TABLE IF EXISTS `dnsmgr_domain`;
CREATE TABLE `dnsmgr_domain` (
`id` int(11) unsigned NOT NULL auto_increment,
`aid` int(11) unsigned NOT NULL,
`name` varchar(128) NOT NULL,
`name` varchar(255) NOT NULL,
`thirdid` varchar(60) DEFAULT NULL,
`addtime` datetime DEFAULT NULL,
`is_hide` tinyint(1) NOT NULL DEFAULT '0',
@@ -66,7 +66,7 @@ DROP TABLE IF EXISTS `dnsmgr_permission`;
CREATE TABLE `dnsmgr_permission` (
`id` int(11) unsigned NOT NULL auto_increment,
`uid` int(11) unsigned NOT NULL,
`domain` varchar(128) NOT NULL,
`domain` varchar(255) NOT NULL,
`sub` varchar(80) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `uid` (`uid`)
@@ -77,7 +77,7 @@ CREATE TABLE `dnsmgr_log` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`uid` int(11) unsigned NOT NULL,
`action` varchar(40) NOT NULL,
`domain` varchar(128) NOT NULL DEFAULT '',
`domain` varchar(255) NOT NULL DEFAULT '',
`data` varchar(500) DEFAULT NULL,
`addtime` datetime NOT NULL,
PRIMARY KEY (`id`),

View File

@@ -2,17 +2,22 @@
namespace app\utils;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
class CheckUtils
{
public static function curl($url, $timeout, $ip = null, $proxy = false)
{
$status = true;
$errmsg = null;
$start = microtime(true);
$urlarr = parse_url($url);
if (!$urlarr) {
return ['status' => false, 'errmsg' => 'Invalid URL', 'usetime' => 0];
}
if (substr($urlarr['host'], 0, 1) == '[' && substr($urlarr['host'], -1) == ']') {
if (str_starts_with($urlarr['host'], '[') && str_ends_with($urlarr['host'], ']')) {
$urlarr['host'] = substr($urlarr['host'], 1, -1);
}
if (!empty($ip) && !filter_var($urlarr['host'], FILTER_VALIDATE_IP)) {
@@ -20,75 +25,79 @@ class CheckUtils
$ip = gethostbyname($ip);
}
if (!empty($ip) && filter_var($ip, FILTER_VALIDATE_IP)) {
$port = isset($urlarr['port']) ? $urlarr['port'] : ($urlarr['scheme'] == 'https' ? 443 : 80);
$port = $urlarr['port'] ?? ($urlarr['scheme'] == 'https' ? 443 : 80);
$resolve = $urlarr['host'] . ':' . $port . ':' . $ip;
}
}
$ch = curl_init();
$options = [
'timeout' => $timeout,
'connect_timeout' => $timeout,
'verify' => false,
'headers' => [
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
],
'http_errors' => false // 不抛出异常
];
// 处理解析
if (!empty($resolve)) {
$options['curl'] = [
CURLOPT_DNS_USE_GLOBAL_CACHE => false,
CURLOPT_RESOLVE => [$resolve]
];
}
// 处理代理
if ($proxy) {
$proxy_server = config_get('proxy_server');
$proxy_port = intval(config_get('proxy_port'));
$proxy_userpwd = config_get('proxy_user').':'.config_get('proxy_pwd');
$proxy_type = config_get('proxy_type');
if ($proxy_type == 'https') {
$proxy_type = CURLPROXY_HTTPS;
} elseif ($proxy_type == 'sock4') {
$proxy_type = CURLPROXY_SOCKS4;
} elseif ($proxy_type == 'sock5') {
$proxy_type = CURLPROXY_SOCKS5;
} elseif ($proxy_type == 'sock5h') {
$proxy_type = CURLPROXY_SOCKS5_HOSTNAME;
} else {
$proxy_type = CURLPROXY_HTTP;
if (!empty($proxy_server) && !empty($proxy_port)) {
match ($proxy_type) {
'https' => $proxy_string = 'https://',
'sock4' => $proxy_string = 'socks4://',
'sock5' => $proxy_string = 'socks5://',
'sock5h' => $proxy_string = 'socks5h://',
default => $proxy_string = 'http://',
};
if ($proxy_userpwd != ':') {
$proxy_string .= $proxy_userpwd . '@';
}
$proxy_string .= $proxy_server . ':' . $proxy_port;
$options['proxy'] = $proxy_string;
}
curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_PROXY, $proxy_server);
curl_setopt($ch, CURLOPT_PROXYPORT, $proxy_port);
if ($proxy_userpwd != ':') {
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy_userpwd);
}
try {
$client = new Client();
$response = $client->request('GET', $url, $options);
$httpcode = $response->getStatusCode();
if ($httpcode < 200 || $httpcode >= 400) {
$status = false;
$errmsg = 'http_code=' . $httpcode;
}
curl_setopt($ch, CURLOPT_PROXYTYPE, $proxy_type);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$httpheader[] = "Accept: */*";
$httpheader[] = "Accept-Language: zh-CN,zh;q=0.8";
$httpheader[] = "Connection: close";
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
if (!empty($resolve)) {
curl_setopt($ch, CURLOPT_DNS_USE_GLOBAL_CACHE, false);
curl_setopt($ch, CURLOPT_RESOLVE, [$resolve]);
}
curl_exec($ch);
$errno = curl_errno($ch);
if ($errno) {
} catch (GuzzleException $e) {
$status = false;
$errmsg = curl_error($ch);
$errmsg = guzzle_error($e);
}
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status && ($httpcode < 200 || $httpcode >= 400)) {
$status = false;
$errmsg = 'http_code='.$httpcode;
}
$usetime = round(curl_getinfo($ch, CURLINFO_TOTAL_TIME) * 1000);
curl_close($ch);
$usetime = round((microtime(true) - $start) * 1000);
return ['status' => $status, 'errmsg' => $errmsg, 'usetime' => $usetime];
}
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 (str_ends_with($target, '.')) $target = substr($target, 0, -1);
if (!filter_var($target, FILTER_VALIDATE_IP) && checkDomain($target)) {
$target = gethostbyname($target);
if (!$target) return ['status' => false, 'errmsg' => 'DNS resolve failed', 'usetime' => 0];
}
if (filter_var($target, FILTER_VALIDATE_IP) && strpos($target, ':') !== false) {
if (filter_var($target, FILTER_VALIDATE_IP) && str_contains($target, ':')) {
$target = '['.$target.']';
}
$starttime = getMillisecond();
@@ -108,7 +117,7 @@ class CheckUtils
{
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 (str_ends_with($target, '.')) $target = substr($target, 0, -1);
if (!filter_var($target, FILTER_VALIDATE_IP) && checkDomain($target)) {
$target = gethostbyname($target);
if (!$target) return ['status' => false, 'errmsg' => 'DNS resolve failed', 'usetime' => 0];
@@ -117,8 +126,19 @@ class CheckUtils
return ['status' => false, 'errmsg' => 'Invalid IP address', 'usetime' => 0];
}
$timeout = 1;
exec('ping -c 1 -w '.$timeout.' '.$target.'', $output, $return_var);
$usetime = !empty($output[1]) ? round(getSubstr($output[1], 'time=', ' ms')) : 0;
if (str_contains($target, ':')) {
exec('ping -6 -c 1 -w '.$timeout.' '.$target, $output, $return_var);
} else {
exec('ping -c 1 -w '.$timeout.' '.$target, $output, $return_var);
}
if (!empty($output[1])) {
if (strpos($output[1], '毫秒') !== false) {
$usetime = getSubstr($output[1], '时间=', ' 毫秒');
} else {
$usetime = getSubstr($output[1], 'time=', ' ms');
}
}
$usetime = !empty($usetime) ? round(trim($usetime)) : 0;
$errmsg = null;
if ($return_var !== 0) {
$usetime = $usetime == 0 ? $timeout * 1000 : $usetime;

View File

@@ -44,7 +44,7 @@ class DnsQueryUtils
$data = get_curl($url);
$arr = json_decode($data, true);
if (!$arr) {
unset($doh_servers[$id]);
unset(self::$doh_servers[$id]);
$id = array_rand(self::$doh_servers);
$url = self::$doh_servers[$id].'?name='.urlencode($domain).'&type='.$dns_type[$type];
$data = get_curl($url);

View File

@@ -1,6 +1,7 @@
<?php
namespace app\utils;
use PHPMailer\PHPMailer\PHPMailer;
use think\facade\Db;
use app\lib\CertHelper;
use app\lib\DeployHelper;
@@ -185,7 +186,8 @@ class MsgNotice
$mail_smtp = config_get('mail_smtp');
$mail_pwd = config_get('mail_pwd');
if (!$mail_name || !$mail_port || !$mail_smtp || !$mail_pwd) return false;
$mail = new \app\lib\mail\PHPMailer\PHPMailer(true);
$mail = new PHPMailer(true);
$mail->setLanguage('zh_cn');
try {
$mail->SMTPDebug = 0;
$mail->CharSet = 'UTF-8';
@@ -220,12 +222,12 @@ class MsgNotice
if (!$wechat_apptoken || !$wechat_appuid) return false;
$url = 'https://wxpusher.zjiecode.com/api/send/message';
$post = ['appToken' => $wechat_apptoken, 'content' => $content, 'summary' => $title, 'contentType' => 3, 'uids' => [$wechat_appuid]];
$result = get_curl($url, json_encode($post), 0, 0, 0, 0, 0, ['Content-Type: application/json; charset=UTF-8']);
$result = get_curl($url, json_encode($post), 0, 0, 0, 0, ['Content-Type' => 'application/json; charset=UTF-8']);
$arr = json_decode($result, true);
if (isset($arr['success']) && $arr['success'] == true) {
return true;
} else {
return isset($arr['msg']) ? $arr['msg'] : '请求失败';
return $arr['msg'] ?? '请求失败';
}
}
@@ -248,7 +250,7 @@ class MsgNotice
if (isset($arr['ok']) && $arr['ok'] == true) {
return true;
} else {
return isset($arr['description']) ? $arr['description'] : '请求失败';
return $arr['description'] ?? '请求失败';
}
}
@@ -284,12 +286,12 @@ class MsgNotice
} else {
return '不支持的Webhook地址';
}
$result = get_curl($url, json_encode($post), 0, 0, 0, 0, 0, ['Content-Type: application/json; charset=UTF-8']);
$result = get_curl($url, json_encode($post), 0, 0, 0, 0, ['Content-Type' => 'application/json; charset=UTF-8']);
$arr = json_decode($result, true);
if (isset($arr['errcode']) && $arr['errcode'] == 0 || isset($arr['code']) && $arr['code'] == 0) {
return true;
} else {
return isset($arr['errmsg']) ? $arr['errmsg'] : (isset($arr['msg']) ? $arr['msg'] : '请求失败');
return $arr['errmsg'] ?? (isset($arr['msg']) ? $arr['msg'] : '请求失败');
}
}

View File

@@ -454,7 +454,7 @@ function operation(action){
$.ajax({
type : 'POST',
url : '/cert/order/operation',
data : {action: action, ids: ids},
data : {act: action, ids: ids},
dataType : 'json',
success : function(data) {
layer.close(ii);

View File

@@ -3,14 +3,6 @@
{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-warning">
<div class="panel-heading"><h3 class="panel-title">计划任务说明</h3></div>
<div class="panel-body">
<p><li>计划任务将以下命令添加到计划任务1分钟1次</li></p>
<p><code>cd {:app()->getRootPath()} && php think certtask</code></p>
<p><li>上次运行时间:<font color="green">{:config_get('certtask_time', '未运行', true)}</font></li></p>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">自动续签设置</h3></div>
@@ -28,7 +20,7 @@
</form>
</div>
<div class="panel-footer">
<li>提示:只有已开启自动续签的证书,才会自动续签。</li>
<li>提示:只有已开启自动续签的证书,并添加<a href="/system/cronset">计划任务</a>才会自动续签。</li>
</div>
</div>

View File

@@ -317,7 +317,7 @@ function operation(action){
$.ajax({
type : 'POST',
url : '/cert/deploy/operation',
data : {action: action, ids: ids},
data : {act: action, ids: ids},
dataType : 'json',
success : function(data) {
layer.close(ii);
@@ -337,7 +337,7 @@ function batch_set_cert(ids){
$.ajax({
type : 'POST',
url : '/cert/deploy/operation',
data : {action: 'cert', ids: ids, certid: text},
data : {act: 'cert', ids: ids, certid: text},
dataType : 'json',
success : function(data) {
layer.close(ii);

View File

@@ -12,6 +12,9 @@
<link href="/static/css/app.min.css" rel="stylesheet">
<link href="/static/css/skins/{$skin}.css" rel="stylesheet">
<link href="/static/css/bootstrap-table.css?v=2" rel="stylesheet"/>
{if file_exists(public_path() . 'static/css/custom.css')}
<link href="/static/css/custom.css" rel="stylesheet">
{/if}
<script src="{$cdnpublic}jquery/3.6.4/jquery.min.js"></script>
<!--[if lt IE 9]>
<script src="{$cdnpublic}html5shiv/3.7.3/html5shiv.min.js"></script>
@@ -150,10 +153,10 @@
<li class="{:checkIfActive('deployaccount')}"><a href="/cert/deployaccount"><i class="fa fa-circle-o"></i> 自动部署账户</a></li>
<li class="{:checkIfActive('deploytask,deploy_form')}"><a href="/cert/deploytask"><i class="fa fa-circle-o"></i> 自动部署任务</a></li>
<li class="{:checkIfActive('cname')}"><a href="/cert/cname"><i class="fa fa-circle-o"></i> CNAME代理</a></li>
<li class="{:checkIfActive('certset')}"><a href="/cert/certset"><i class="fa fa-circle-o"></i> 计划任务设置</a></li>
<li class="{:checkIfActive('certset')}"><a href="/cert/certset"><i class="fa fa-circle-o"></i> 自动续签设置</a></li>
</ul>
</li>
<li class="treeview {:checkIfActive('noticeset,proxyset')}">
<li class="treeview {:checkIfActive('cronset,loginset,noticeset,proxyset')}">
<a href="javascript:;">
<i class="fa fa-cogs fa-fw"></i>
<span>系统设置</span>
@@ -162,6 +165,8 @@
</span>
</a>
<ul class="treeview-menu">
<li class="{:checkIfActive('cronset')}"><a href="/system/cronset"><i class="fa fa-circle-o"></i> 计划任务</a></li>
<li class="{:checkIfActive('loginset')}"><a href="/system/loginset"><i class="fa fa-circle-o"></i> 登录设置</a></li>
<li class="{:checkIfActive('noticeset')}"><a href="/system/noticeset"><i class="fa fa-circle-o"></i> 通知设置</a></li>
<li class="{:checkIfActive('proxyset')}"><a href="/system/proxyset"><i class="fa fa-circle-o"></i> 代理设置</a></li>
<li><a href="https://www.showdoc.com.cn/dnsmgr/11058996709621562" target="_blank" rel="noreferrer"><i class="fa fa-circle-o"></i> <span>接口文档</span></a></li>

View File

@@ -160,7 +160,7 @@
<p>1、php需要安装swoole组件</p>
<p>2、在命令行执行以下命令启动进程</p>
<p><code>cd {:app()->getRootPath()} && php think dmtask</code></p>
<p>3、也可以使用进程守护管理器添加守护进程,运行目录:{:app()->getRootPath()},启动命令:php think dmtask</p>
<p>3、也可以使用进程守护管理器添加守护进程<br/>运行目录:<code>{:app()->getRootPath()}</code><br/>启动命令:<code>php think dmtask</code></p>
</div>
</div>
</div>

View File

@@ -17,18 +17,31 @@
<label class="col-sm-3 control-label">域名账户</label>
<div class="col-sm-9">
<select name="aid" class="form-control">
{foreach $accounts as $k=>$v}
<option value="{$k}">{$v}</option>
{foreach $accounts as $item}
<option value="{$item.id}" data-type="{$item.type}" data-add="{$item.add}">{$item.name}</option>
{/foreach}
</select>
</div>
</div>
<div class="form-group">
<div class="form-group" id="methodSelect" style="display: none;">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-9">
<label class="radio-inline"><input type="radio" name="method" value="0" checked> 已有域名</label>
<label class="radio-inline"><input type="radio" name="method" value="1"> 新域名</label>
</div>
</div>
<div class="form-group" id="domainSelect">
<label class="col-sm-3 control-label">选择域名</label>
<div class="col-sm-9">
<select name="domain" id="domainList" class="form-control"></select>
</div>
</div>
<div class="form-group" id="domainInput" style="display: none;">
<label class="col-sm-3 control-label">输入域名</label>
<div class="col-sm-9">
<input type="text" class="form-control" name="adddomain" placeholder="输入要新增的域名" value="">
</div>
</div>
</form>
</div>
@@ -157,7 +170,7 @@
var userLevel = "{:request()->user['level']}";
$(document).ready(function(){
updateToolbar();
const defaultPageSize = 15;
const defaultPageSize = getCookie('domain_pagesize') ? getCookie('domain_pagesize') : 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;
@@ -285,11 +298,40 @@ $(document).ready(function(){
],
onLoadSuccess: function(data) {
$('[data-toggle="tooltip"]').tooltip()
}
},
onPageChange: function(number, size){
if(size != defaultPageSize){
defaultPageSize = size;
setCookie('domain_pagesize', size);
}
},
})
$("#form-store select[name=aid]").change(function(){
getDomainList()
if($(this).val() != ''){
$("#form-store input[name=method][value=0]").prop('checked', true);
$("#domainSelect").show();
$("#domainInput").hide();
var add = $(this).find('option:selected').data('add');
if(add == '1'){
$("#methodSelect").show();
}else{
$("#methodSelect").hide();
}
getDomainList();
}
})
$("#form-store input[name=method]").change(function(){
var value = $("#form-store input[name=method]:checked").val();
if(value == '0'){
$("#domainSelect").show();
$("#domainInput").hide();
getDomainList();
}else{
$("#domainSelect").hide();
$("#domainInput").show();
$('#domainList').empty();
}
})
$('[data-toggle="popover"]').popover()
@@ -298,26 +340,37 @@ function addframe(){
$("#modal-store").modal('show');
var aid = $("#form-store select[name=aid]").val();
if(aid != ''){
getDomainList()
$("#form-store select[name=aid]").change();
}
}
function saveAdd(){
var aid = $("#form-store select[name=aid]").val();
var select = $('#domainList').select2('data');
if(select.length == 0){
layer.alert('请选择域名!');return false;
if(aid == ''){
layer.alert('请选择域名账户!');return false;
}
var name = select[0].text;
var thirdid = select[0].id;
var recordcount = select[0].recordcount;
if(aid=='' || thirdid==''){
layer.alert('请确保各项不能为空!');return false;
var method = $("#form-store input[name=method]:checked").val();
if(method == '1'){
var name = $("#form-store input[name=adddomain]").val();
if(name == ''){
layer.alert('域名不能为空!');return false;
}
}else{
var select = $('#domainList').select2('data');
if(select.length == 0){
layer.alert('请选择域名!');return false;
}
var name = select[0].text;
var thirdid = select[0].id;
var recordcount = select[0].recordcount;
if(aid=='' || thirdid==''){
layer.alert('请确保各项不能为空!');return false;
}
}
var ii = layer.load(2);
$.ajax({
type : 'POST',
url : '/domain/op/act/add',
data : {aid: aid, thirdid: thirdid, name: name, recordcount: recordcount},
data : {aid: aid, method: method, thirdid: thirdid, name: name, recordcount: recordcount},
dataType : 'json',
success : function(data) {
layer.close(ii);

View File

@@ -35,12 +35,8 @@
</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 class="panel-footer">
<p>需添加<a href="/system/cronset">计划任务</a>,支持域名到期提醒+域名列表到期时间自动刷新。</p>
</div>
</div>

View File

@@ -38,6 +38,9 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
<option value="CAA">CAA</option>
{if $dnsconfig.redirect}<option value="REDIRECT_URL">显性URL</option>
<option value="FORWARD_URL">隐性URL</option>{/if}
{if $dnsconfig.type=='powerdns'}<option value="LOC">LOC</option>
<option value="PTR">PTR</option>
<option value="LUA">LUA</option>{/if}
</select>
</div>
</div>
@@ -112,6 +115,9 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
<option value="CAA">CAA</option>
{if $dnsconfig.redirect}<option value="REDIRECT_URL">显性URL</option>
<option value="FORWARD_URL">隐性URL</option>{/if}
{if $dnsconfig.type=='powerdns'}<option value="LOC">LOC</option>
<option value="PTR">PTR</option>
<option value="LUA">LUA</option>{/if}
</select>
</div>
</div>
@@ -169,7 +175,7 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
<div id="searchbox1">
<div class="form-group">
<label>搜索</label>
<input type="text" class="form-control" name="keyword" placeholder="输入关键字">
<input type="text" class="form-control" name="keyword" placeholder="输入关键字">
</div>
<div class="form-group">
<select name="status" class="form-control"><option value="">所有状态</option><option value="1">启用</option><option value="0">暂停</option></select>
@@ -201,16 +207,19 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
<option value="CAA">CAA</option>
{if $dnsconfig.redirect}<option value="REDIRECT_URL">显性URL</option>
<option value="FORWARD_URL">隐性URL</option>{/if}
{if $dnsconfig.type=='powerdns'}<option value="LOC">LOC</option>
<option value="PTR">PTR</option>
<option value="LUA">LUA</option>{/if}
</select>
</div>
<div class="form-group">
<input type="text" class="form-control" name="subdomain" placeholder="输入主机记录">
<input type="text" class="form-control" name="subdomain" placeholder="输入主机记录">
</div>
<div class="form-group">
<select name="line" class="form-control"><option value="">全部线路</option></select>
</div>
<div class="form-group">
<input type="text" class="form-control" name="value" placeholder="输入记录值">
<input type="text" class="form-control" name="value" 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>
@@ -218,9 +227,9 @@ td{overflow: hidden;text-overflow: ellipsis;white-space: nowrap;max-width:360px;
</div>
</form>
<table id="listTable">
<table id="listTable">
</table>
</div>
</div>
</div>
</div>
</div>
@@ -260,10 +269,10 @@ $(document).ready(function(){
visible: false,
title: '记录ID'
},
{
field: 'Name',
title: '主机记录'
},
{
field: 'Name',
title: '主机记录'
},
{
field: 'Type',
title: '记录类型',
@@ -273,18 +282,26 @@ $(document).ready(function(){
return value;
}
},
{
{
field: 'LineName',
title: '线路类型'
},
{
field: 'Value',
title: '记录值',
formatter: function(value, row, index) {
if(row.Type == 'MX') return value + ' | '+row.MX;
return value;
}
},
field: 'Value',
title: '记录值',
formatter: function(value, row, index) {
var copyId = 'copy-value-' + row.RecordId;
if(row.Type == 'MX') {
// 只复制 mx.yandex.net按钮在其右侧优先级单独显示
return '<span id="'+copyId+'" data-value="'+htmlEscape(value)+'">'+value+'</span>'
+ '<a href="javascript:void(0);" title="复制记录值" onclick="copyToClipboard(null, \'#'+copyId+'\')" style="padding-left:6px;"><i class=\"fa fa-copy\"></i></a>'
+ '<span class="mx-priority"> | '+row.MX+'</span>';
} else {
return '<span id="'+copyId+'" data-value="'+htmlEscape(value)+'">'+value+'</span>'
+ '<a href="javascript:void(0);" title="复制记录值" onclick="copyToClipboard(null, \'#'+copyId+'\')" style="padding-left:6px;"><i class=\"fa fa-copy\"></i></a>';
}
}
},
{
field: 'TTL',
title: 'TTL'
@@ -330,8 +347,12 @@ $(document).ready(function(){
}
html += '<a href="javascript:delItem(\''+row.RecordId+'\')" class="btn btn-danger btn-xs">删除</a>&nbsp;&nbsp;';
if(dnsconfig.remark == 1){
html += '<a href="javascript:setRemark(\''+row.RecordId+'\')" class="btn btn-info btn-xs">备注</a>';
html += '<a href="javascript:setRemark(\''+row.RecordId+'\')" class="btn btn-info btn-xs">备注</a>&nbsp;&nbsp;';
}
if(row.Name === "@") var domain = "{$domainName}";
else var domain = row.Name + ".{$domainName}";
domain = domain.replace(/\*/g, 'www');
html += '<a href="http://' + domain + '" target="_blank" title="访问域名" class="btn btn-default btn-xs"><i class="fa fa-external-link"></i></a>';
return html;
}
},
@@ -710,5 +731,39 @@ function advanceSearch(){
$("#searchbox1").slideDown();
}
}
function copyToClipboard(text, selector) {
if (!text && selector) {
var el = document.querySelector(selector);
if (el) {
text = el.getAttribute('data-value');
}
}
var tempInput = document.createElement('input');
tempInput.style.position = 'absolute';
tempInput.style.left = '-9999px';
tempInput.value = text;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand('copy');
document.body.removeChild(tempInput);
if(selector){
var icon = document.querySelector(selector + ' + a i');
if(icon){
var oldClass = icon.className;
icon.className = 'fa fa-check';
setTimeout(function(){ icon.className = oldClass; }, 1000);
}
}
layer.msg('已复制到剪贴板', {icon: 1, time: 600});
}
// 工具函数HTML转义防止XSS
function htmlEscape(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
</script>
{/block}

View File

@@ -156,6 +156,8 @@ function editframe(id){
$("#form-store input[name=id]").val(id);
$("#form-store input[name=subdomain]").val(row.SubDomain);
$("#form-store input[name=type]").val(row.Type);
if(row.Type == 'CNAME') $("#weight-switch").prop("disabled", true);
else $("#weight-switch").prop("disabled", false);
lineList = [];
$.each(recordLine, function(i, item){

View File

@@ -71,9 +71,9 @@
</div>
<!-- /.row -->
<div class="row">
<div class="col-md-12">
<div id="browser-notice"></div>
</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">
@@ -129,14 +129,22 @@
</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>
</tr>
<tr>
<td class="query-title">MySQL版本</td>
<td class="query-title">数据库版本</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.date}</td>
@@ -218,15 +226,14 @@ function cleancache(){
</script>
<script>
function speedModeNotice(){
var ua = window.navigator.userAgent;
if(ua.indexOf('Windows NT')>-1 && ua.indexOf('Trident/')>-1){
var html = "<div class=\"panel panel-default\"><div class=\"panel-body\">当前浏览器是兼容模式,为确保后台功能正常使用,请切换到<b style='color:#51b72f'>极速模式</b><br>操作方法点击浏览器地址栏右侧的IE符号<b style='color:#51b72f;'><i class='fa fa-internet-explorer fa-fw'></i></b>→选择“<b style='color:#51b72f;'><i class='fa fa-flash fa-fw'></i></b><b style='color:#51b72f;'>极速模式</b>”</div></div>";
$("#browser-notice").html(html)
}
else if(window.location.protocol.indexOf("https")==-1){
var html = "<div class=\"panel panel-default\"><div class=\"panel-body\"><b style='color:#CC3022;'><i class='fa fa-info-circle fa-fw'></i></b>当前正在使用HTTP访问可能存在被窃取敏感信息风险请使用HTTPS访问</div></div>";
$("#browser-notice").html(html)
}
var ua = window.navigator.userAgent;
if(ua.indexOf('Windows NT')>-1 && ua.indexOf('Trident/')>-1){
var html = "<div class=\"panel panel-default\"><div class=\"panel-body\">当前浏览器是兼容模式,为确保后台功能正常使用,请切换到<b style='color:#51b72f'>极速模式</b><br>操作方法点击浏览器地址栏右侧的IE符号<b style='color:#51b72f;'><i class='fa fa-internet-explorer fa-fw'></i></b>→选择“<b style='color:#51b72f;'><i class='fa fa-flash fa-fw'></i></b><b style='color:#51b72f;'>极速模式</b>”</div></div>";
$("#browser-notice").html(html)
}else if(window.location.protocol.indexOf("https")==-1){
var html = "<div class=\"panel panel-default\"><div class=\"panel-body\"><b style='color:#CC3022;'><i class='fa fa-info-circle fa-fw'></i></b>当前正在使用HTTP访问可能存在被窃取敏感信息风险请使用HTTPS访问</div></div>";
$("#browser-notice").html(html)
}
}
speedModeNotice();
</script>

View File

@@ -70,19 +70,6 @@
</div>
</div>
</div>
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">其他登录设置</h3></div>
<div class="panel-body">
<form onsubmit="return saveAccount(this)" method="post" class="form-horizontal" role="form">
<div class="form-group">
<div class="form-group">
<label class="col-sm-3 control-label">开启图形验证码</label>
<div class="col-sm-9" style="margin-top:6.5px"><div class="material-switch"><input id="vocde_switch" type="checkbox" {if config_get('vcode', '1')=='1'}checked{/if} onchange="setvcode()"><label for="vocde_switch" class="label-primary"></label></div></div>
</div>
</div>
</form>
</div>
</div>
{/block}
{block name="script"}
<script src="{$cdnpublic}layer/3.1.1/layer.js"></script>
@@ -181,17 +168,6 @@ function close_totp(){
});
});
}
function setvcode(){
var status = $("#vocde_switch").is(':checked') ? '1' : '2';
$.post('/system/set', {vcode: status}, function(res){
if(res.code == 0){
layer.msg(status == '1' ? '图形验证码已开启' : '图形验证码已关闭', {icon: 1, time: 1000});
}else{
layer.alert(res.msg, {icon: 2});
$("#vocde_switch").prop('checked', !status);
}
});
}
$(document).ready(function(){
var clipboard = new Clipboard('#copy-btn');
clipboard.on('success', function (e) {

View File

@@ -129,7 +129,7 @@ new Vue({
cdntypeList: {
1:'CloudFlare',
2:"CloudFront",
3:'Gcore'
4:'EdgeOne'
},
},
watch: {

View File

@@ -75,6 +75,8 @@ $(document).ready(function(){
return 'CloudFront';
}else if(value == 3){
return 'Gcore';
}else if(value == 4){
return 'EdgeOne';
}else{
return '未知';
}

View File

@@ -14,10 +14,9 @@
<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>数据接口:<a href="https://www.wetest.vip/" target="_blank" rel="noreferrer">wetest.vip</a> 数据接口支持CloudFlare、CloudFront、EdgeOne<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>
<p><code>cd {:app()->getRootPath()} && php think opiptask</code></p>
<p><li>自动更新:可查看<a href="/system/cronset">计划任务设置</a></p>
</div>
</div>
</div>
@@ -43,6 +42,22 @@
</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"><input type="text" name="optimize_ip_min" value="{:config_get('optimize_ip_min', '30')}" 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"/>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,126 @@
{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">
{if config_get('cron_type', '0') == '1'}
<p><li>需定时访问以下URL频率1分钟1次</li></p>
<p><code>{$siteurl}/cron?key={:config_get('cron_key')}</code></p>
{else}
<p><li>将以下Shell命令添加到计划任务频率1分钟1次</li></p>
<p><code>cd {:app()->getRootPath()} && php think certtask</code></p>
{if $is_user_www}<p><li><b>计划任务执行用户必须选择www用户</b></li></p>{/if}
<p><li>采用Docker镜像部署的会自动添加计划任务无需手动添加。</li></p>
{/if}
</div>
</div>
<div class="panel panel-intro">
<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="cron_type" default="{:config_get('cron_type', '0')}"><option value="0">Shell命令推荐</option><option value="1">访问URL</option></select></div>
</div>
<div class="form-group" id="cron_url" {:config_get('cron_type', '0') == 0 ? 'style="display: none"' : ''}>
<label class="col-sm-3 control-label">访问密钥</label>
<div class="col-sm-9"><input type="text" name="cron_key" value="{:config_get('cron_key')}" class="form-control" requ/></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">
<p>优先推荐使用Shell命令方式执行计划任务访问URL方式可能会请求超时导致执行失败。</p><p>如果是虚拟主机环境无法执行命令则可以使用访问URL方式。</p>
</div>
</div>
<div class="panel panel-success mt-3">
<div class="panel-heading"><h3 class="panel-title">计划任务运行状态</h3></div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>任务名称</th>
<th>上次运行时间</th>
</tr>
</thead>
<tbody>
<tr>
<td>SSL证书续签</td>
<td><font color="green">{:config_get('certtask_time', '未运行', true)}</font></td>
</tr>
<tr>
<td>SSL证书部署</td>
<td><font color="green">{:config_get('certdeploy_time', '未运行', true)}</font></td>
</tr>
<tr>
<td>域名到期提醒</td>
<td><font color="green">{:config_get('domain_expire_time', '未运行', true)}</font></td>
</tr>
<tr>
<td>CF优选IP更新</td>
<td><font color="green">{:config_get('optimize_ip_time', '未运行', true)}</font></td>
</tr>
</tbody>
</table>
</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 cron_type = $("select[name='cron_type']").val();
var cron_key = $("input[name='cron_key']").val();
if(cron_type == 1 && cron_key == ''){
layer.alert('访问密钥不能为空!', {icon: 2});
return false;
}
var ii = layer.load(2, {shade:[0.1,'#fff']});
$.ajax({
type : 'POST',
url : '/system/set',
data : {cron_type:cron_type, cron_key:cron_key},
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;
}
$("select[name='cron_type']").change(function(){
if($(this).val() == 0){
$("#cron_url").hide();
}else{
$("#cron_url").show();
}
});
</script>
{/block}

View File

@@ -0,0 +1,39 @@
{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">
<div class="form-group">
<div class="form-group">
<label class="col-sm-3 control-label">开启图形验证码</label>
<div class="col-sm-9"><div class="material-switch"><input id="vocde_switch" type="checkbox" {if config_get('vcode', '1')=='1'}checked{/if} onchange="setvcode()"><label for="vocde_switch" class="label-primary"></label></div></div>
</div>
</div>
</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 setvcode(){
var status = $("#vocde_switch").is(':checked') ? '1' : '2';
$.post('/system/set', {vcode: status}, function(res){
if(res.code == 0){
layer.msg(status == '1' ? '图形验证码已开启' : '图形验证码已关闭', {icon: 1, time: 1000});
}else{
layer.alert(res.msg, {icon: 2});
$("#vocde_switch").prop('checked', !status);
}
});
}
</script>
{/block}

View File

@@ -1,4 +1,5 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "netcccyun/dnsmgr",
"description": "聚合DNS管理系统",
"type": "project",
@@ -31,40 +32,50 @@
"email": "admin@kuxi.tech",
"homepage": "https://www.kuxi.tech",
"role": "Project Developer"
},
{
"name": "耗子",
"email": "haozi@loli.email",
"homepage": "https://hzbk.net",
"role": "Project Developer"
}
],
"require": {
"php": ">=7.4.0",
"ext-pdo": "*",
"ext-gd": "*",
"php": ">=8.2.0",
"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",
"ext-gd": "*",
"ext-mbstring": "*",
"ext-openssl": "*",
"ext-pdo": "*",
"ext-sockets": "*",
"ext-ssh2": "*",
"cccyun/php-whois": "^1.0",
"cccyun/think-captcha": "^3.0",
"symfony/polyfill-intl-idn": "^1.31",
"symfony/polyfill-php80": "^1.31",
"cccyun/php-whois": "^1.0"
"guzzlehttp/guzzle": "^7.0",
"phpmailer/phpmailer": "^6.10",
"symfony/polyfill-intl-idn": "^1.32",
"symfony/polyfill-mbstring": "^1.32",
"symfony/polyfill-php81": "^1.32",
"symfony/polyfill-php82": "^1.32",
"topthink/framework": "^8.1.0",
"topthink/think-orm": "^4.0",
"topthink/think-view": "^2.0"
},
"require-dev": {
"symfony/var-dumper": "^4.2",
"topthink/think-trace":"^1.0",
"swoole/ide-helper": "^5.1"
"symfony/var-dumper": "^7.3",
"topthink/think-trace":"^2.0",
"swoole/ide-helper": "^6.0"
},
"autoload": {
"psr-4": {
"app\\": "app"
},
"psr-0": {
"": "extend/"
}
},
"config": {
"optimize-autoloader": true,
"sort-packages": true,
"platform-check": false,
"preferred-install": "dist"
},
"scripts": {
@@ -72,5 +83,7 @@
"@php think service:discover",
"@php think vendor:publish"
]
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

1882
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -6,7 +6,6 @@ return [
// 指令定义
'commands' => [
'dmtask' => 'app\command\Dmtask',
'opiptask' => 'app\command\Opiptask',
'certtask' => 'app\command\Certtask',
'reset' => 'app\command\Reset',
],

View File

@@ -12,8 +12,8 @@
// [ 应用入口文件 ]
namespace think;
if (version_compare(PHP_VERSION, '7.4.0', '<')) {
die('require PHP >= 7.4 !');
if (version_compare(PHP_VERSION, '8.0.0', '<')) {
die('require PHP >= 8.0 !');
}
require __DIR__ . '/../vendor/autoload.php';

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -29,6 +29,7 @@ Route::get('/logout', 'auth/logout');
Route::any('/quicklogin', 'auth/quicklogin');
Route::any('/dmtask/status', 'dmonitor/status');
Route::any('/optimizeip/status', 'optimizeip/status');
Route::get('/cron', 'system/cron');
Route::group(function () {
Route::any('/', 'index/index');
@@ -112,6 +113,7 @@ Route::group(function () {
Route::get('/cert/certset', 'cert/certset');
Route::get('/system/loginset', 'system/loginset');
Route::get('/system/noticeset', 'system/noticeset');
Route::get('/system/proxyset', 'system/proxyset');
Route::post('/system/set', 'system/set');
@@ -119,6 +121,7 @@ Route::group(function () {
Route::get('/system/tgbottest', 'system/tgbottest');
Route::get('/system/webhooktest', 'system/webhooktest');
Route::post('/system/proxytest', 'system/proxytest');
Route::get('/system/cronset', 'system/cronset');
})->middleware(CheckLogin::class)
->middleware(ViewOutput::class);