diff --git a/app/controller/Domain.php b/app/controller/Domain.php index ac45999..5da1d84 100644 --- a/app/controller/Domain.php +++ b/app/controller/Domain.php @@ -8,6 +8,7 @@ use think\facade\View; use think\facade\Cache; use app\lib\DnsHelper; use app\service\ExpireNoticeService; +use app\utils\DnsQueryUtils; use Exception; class Domain extends BaseController @@ -157,8 +158,10 @@ class Domain extends BaseController } $accounts[] = ['id' => $row['id'], 'name' => $name, 'type' => DnsHelper::$dns_config[$row['type']]['name'], 'add' => DnsHelper::$dns_config[$row['type']]['add']]; } + $categorys = Db::name('domain_category')->order('sort', 'asc')->order('id', 'desc')->select(); View::assign('accounts', $accounts); View::assign('types', $types); + View::assign('categorys', $categorys); return view(); } @@ -188,6 +191,7 @@ class Domain extends BaseController $kw = input('post.kw', null, 'trim'); $type = input('post.type', null, 'trim'); $status = input('post.status', null, 'trim'); + $cid = input('post.cid', null, 'trim'); $order = input('post.order', null, 'trim'); $offset = input('post.offset/d', 0); $limit = input('post.limit/d', 10); @@ -206,6 +210,9 @@ class Domain extends BaseController if (!empty($type)) { $select->whereLike('B.type', $type); } + if (!isNullOrEmpty($cid)) { + $select->where('A.cid', $cid); + } if (request()->user['level'] == 1) { $select->where('is_hide', 0)->where('A.name', 'in', request()->user['permission']); } @@ -235,10 +242,12 @@ class Domain extends BaseController } $rows = $select->fieldRaw('A.*,B.type,B.remark aremark')->limit($offset, $limit)->select(); + $categorys = Db::name('domain_category')->column('name', 'id'); $list = []; foreach ($rows as $row) { $row['typename'] = DnsHelper::$dns_config[$row['type']]['name']; $row['icon'] = DnsHelper::$dns_config[$row['type']]['icon']; + $row['category_name'] = isset($categorys[$row['cid']]) ? $categorys[$row['cid']] : ''; $list[] = $row; } @@ -290,6 +299,7 @@ class Domain extends BaseController $is_hide = input('post.is_hide/d'); $is_sso = input('post.is_sso/d'); $is_notice = input('post.is_notice/d'); + $cid = input('post.cid/d', 0); $expiretime = input('post.expiretime', null, 'trim'); $remark = input('post.remark', null, 'trim'); if (empty($remark)) $remark = null; @@ -297,6 +307,7 @@ class Domain extends BaseController 'is_hide' => $is_hide, 'is_sso' => $is_sso, 'is_notice' => $is_notice, + 'cid' => $cid, 'expiretime' => $expiretime ? $expiretime : null, 'remark' => $remark, ]); @@ -1280,4 +1291,234 @@ class Domain extends BaseController $result = (new ExpireNoticeService())->updateDomainDate($id, $drow['name']); return json($result); } + + public function record_check() + { + $id = input('param.id/d'); + $drow = Db::name('domain')->where('id', $id)->find(); + if (!$drow) { + return json(['code' => -1, 'msg' => '域名不存在']); + } + if (!checkPermission(0, $drow['name'])) return json(['code' => -1, 'msg' => '无权限']); + + $recordid = input('post.recordid', null, 'trim'); + $name = input('post.name', null, 'trim'); + $type = input('post.type', null, 'trim'); + $value = input('post.value', null, 'trim'); + + if (empty($recordid) || empty($name) || empty($type)) { + return json(['code' => -1, 'msg' => '参数不能为空']); + } + + $domain = $name === '@' ? $drow['name'] : $name . '.' . $drow['name']; + $domain = strtolower($domain); + + $supported_types = ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SOA', 'SRV', 'CAA', 'PTR', 'LOC', 'LUA']; + if (!in_array($type, $supported_types)) { + return json(['code' => -1, 'msg' => '该记录类型暂不支持检测']); + } + + $dns_records = DnsQueryUtils::get_dns_records($domain, $type); + if ($dns_records === false || empty($dns_records)) { + $dns_records = DnsQueryUtils::query_dns_doh($domain, $type); + } + + if ($dns_records === false || empty($dns_records)) { + return json(['code' => 0, 'data' => ['status' => 'not_found', 'message' => '未查询到该解析记录', 'actual' => []]]); + } + + $dns_records = array_map('strtolower', $dns_records); + $expected_value = strtolower(trim($value)); + + if (in_array($expected_value, $dns_records)) { + return json(['code' => 0, 'data' => ['status' => 'active', 'message' => '解析已生效', 'actual' => $dns_records]]); + } else { + return json(['code' => 0, 'data' => ['status' => 'mismatch', 'message' => '解析值不匹配', 'expected' => $expected_value, 'actual' => $dns_records]]); + } + } + + public function dnscheck() + { + if (!checkPermission(1)) return $this->alert('error', '无权限'); + + if (request()->isAjax()) { + $domain = input('post.domain', null, 'trim'); + $type = input('post.type', null, 'trim'); + $dns_server = input('post.dns_server', 'default', 'trim'); + + if (empty($domain) || empty($type)) { + return json(['code' => -1, 'msg' => '域名和记录类型不能为空']); + } + + $domain = strtolower($domain); + + $supported_types = ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SOA', 'SRV', 'CAA', 'PTR']; + if (!in_array($type, $supported_types)) { + return json(['code' => -1, 'msg' => '该记录类型暂不支持检测']); + } + + $result = $this->query_dns_with_server($domain, $type, $dns_server); + + return json($result); + } + + return view(); + } + + private function query_dns_with_server($domain, $type, $dns_server) + { + $dns_servers = [ + 'alidns' => ['223.5.5.5', '223.6.6.6'], + 'dnspod' => ['119.29.29.29', '182.254.116.116'], + 'google' => ['8.8.8.8', '8.8.4.4'], + 'cloudflare' => ['1.1.1.1', '1.0.0.1'], + ]; + + $server_names = [ + 'default' => '系统默认', + 'alidns' => '阿里DNS', + 'dnspod' => 'DNSPod', + 'google' => 'Google DNS', + 'cloudflare' => 'Cloudflare', + ]; + + if ($dns_server != 'default' && isset($dns_servers[$dns_server])) { + $records = $this->query_dns_custom_server($domain, $type, $dns_servers[$dns_server][0]); + } else { + $records = DnsQueryUtils::get_dns_records($domain, $type); + if ($records === false || empty($records)) { + $records = DnsQueryUtils::query_dns_doh($domain, $type); + } + } + + if ($records === false) { + return ['code' => 0, 'data' => ['status' => 'error', 'message' => '查询失败', 'dns_server' => $server_names[$dns_server] ?? $dns_server]]; + } + + if (empty($records)) { + return ['code' => 0, 'data' => ['status' => 'not_found', 'message' => '未找到该类型的DNS记录', 'dns_server' => $server_names[$dns_server] ?? $dns_server]]; + } + + return ['code' => 0, 'data' => ['status' => 'success', 'records' => $records, 'dns_server' => $server_names[$dns_server] ?? $dns_server]]; + } + + private function query_dns_custom_server($domain, $type, $server) + { + $dns_type = ['A' => DNS_A, 'AAAA' => DNS_AAAA, 'CNAME' => DNS_CNAME, 'MX' => DNS_MX, 'TXT' => DNS_TXT, 'NS' => DNS_NS, 'SOA' => DNS_SOA, 'PTR' => DNS_PTR, 'SRV' => DNS_SRV, 'CAA' => DNS_CAA]; + + if (!array_key_exists($type, $dns_type)) { + return false; + } + + try { + $cmd = 'dig +short +time=5 @' . escapeshellarg($server) . ' ' . escapeshellarg($domain) . ' ' . escapeshellarg($type) . ' 2>/dev/null'; + $output = shell_exec($cmd); + + if (empty($output)) { + return false; + } + + $lines = explode("\n", trim($output)); + $result = []; + foreach ($lines as $line) { + $line = trim($line); + if (!empty($line)) { + $result[] = $line; + } + } + + return empty($result) ? false : $result; + } catch (Exception $e) { + return false; + } + } + + public function category() + { + if (!checkPermission(2)) return $this->alert('error', '无权限'); + return view(); + } + + public function category_data() + { + if (!checkPermission(2)) return json(['total' => 0, 'rows' => []]); + $offset = input('post.offset/d', 0); + $limit = input('post.limit/d', 10); + + $select = Db::name('domain_category'); + $total = $select->count(); + $rows = $select->order('sort', 'asc')->order('id', 'desc')->limit($offset, $limit)->select()->toArray(); + + foreach ($rows as &$row) { + $row['domain_count'] = Db::name('domain')->where('cid', $row['id'])->count(); + } + + return json(['total' => $total, 'rows' => $rows]); + } + + public function category_op() + { + if (!checkPermission(2)) return json(['code' => -1, 'msg' => '无权限']); + $action = input('param.action'); + if ($action == 'add') { + $name = input('post.name', null, 'trim'); + $remark = input('post.remark', null, 'trim'); + $sort = input('post.sort/d', 0); + if (empty($name)) return json(['code' => -1, 'msg' => '分类名称不能为空']); + if (Db::name('domain_category')->where('name', $name)->find()) { + return json(['code' => -1, 'msg' => '分类名称已存在']); + } + Db::name('domain_category')->insert([ + 'name' => $name, + 'remark' => $remark, + 'sort' => $sort, + 'addtime' => date('Y-m-d H:i:s'), + ]); + return json(['code' => 0, 'msg' => '添加分类成功!']); + } elseif ($action == 'edit') { + $id = input('post.id/d'); + $row = Db::name('domain_category')->where('id', $id)->find(); + if (!$row) return json(['code' => -1, 'msg' => '分类不存在']); + $name = input('post.name', null, 'trim'); + $remark = input('post.remark', null, 'trim'); + $sort = input('post.sort/d', 0); + if (empty($name)) return json(['code' => -1, 'msg' => '分类名称不能为空']); + if (Db::name('domain_category')->where('name', $name)->where('id', '<>', $id)->find()) { + return json(['code' => -1, 'msg' => '分类名称已存在']); + } + Db::name('domain_category')->where('id', $id)->update([ + 'name' => $name, + 'remark' => $remark, + 'sort' => $sort, + ]); + return json(['code' => 0, 'msg' => '修改分类成功!']); + } elseif ($action == 'del') { + $id = input('post.id/d'); + $count = Db::name('domain')->where('cid', $id)->count(); + if ($count > 0) return json(['code' => -1, 'msg' => '该分类下存在域名,无法删除']); + Db::name('domain_category')->where('id', $id)->delete(); + return json(['code' => 0, 'msg' => '删除分类成功!']); + } + return json(['code' => -3]); + } + + public function category_list() + { + if (!checkPermission(2)) return json(['code' => -1, 'msg' => '无权限']); + $list = Db::name('domain_category')->order('sort', 'asc')->order('id', 'desc')->select(); + foreach ($list as &$row) { + $row['domain_count'] = Db::name('domain')->where('cid', $row['id'])->count(); + } + return json(['code' => 0, 'data' => $list]); + } + + public function domain_set_category() + { + if (!checkPermission(2)) return json(['code' => -1, 'msg' => '无权限']); + $ids = input('post.ids'); + $cid = input('post.cid/d', 0); + if (empty($ids)) return json(['code' => -1, 'msg' => '请选择要操作的域名']); + $count = Db::name('domain')->where('id', 'in', $ids)->update(['cid' => $cid]); + return json(['code' => 0, 'msg' => '成功设置' . $count . '个域名的分类!']); + } } diff --git a/app/sql/install.sql b/app/sql/install.sql index e40b902..f431c16 100644 --- a/app/sql/install.sql +++ b/app/sql/install.sql @@ -5,7 +5,7 @@ CREATE TABLE `dnsmgr_config` ( PRIMARY KEY (`key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -INSERT INTO `dnsmgr_config` VALUES ('version', '1048'); +INSERT INTO `dnsmgr_config` VALUES ('version', '1049'); INSERT INTO `dnsmgr_config` VALUES ('notice_mail', '0'); INSERT INTO `dnsmgr_config` VALUES ('notice_wxtpl', '0'); INSERT INTO `dnsmgr_config` VALUES ('mail_smtp', 'smtp.qq.com'); @@ -26,6 +26,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, + `cid` int(11) unsigned NOT NULL DEFAULT '0', `name` varchar(255) NOT NULL, `thirdid` varchar(60) DEFAULT NULL, `addtime` datetime DEFAULT NULL, @@ -40,7 +41,8 @@ CREATE TABLE `dnsmgr_domain` ( `noticetime` datetime DEFAULT NULL, `checkstatus` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), - KEY `name` (`name`) + KEY `name` (`name`), + KEY `cid` (`cid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; DROP TABLE IF EXISTS `dnsmgr_user`; @@ -261,4 +263,15 @@ CREATE TABLE `dnsmgr_domain_alias` ( PRIMARY KEY (`id`), KEY `did` (`did`), KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +DROP TABLE IF EXISTS `dnsmgr_domain_category`; +CREATE TABLE `dnsmgr_domain_category` ( + `id` int(11) unsigned NOT NULL auto_increment, + `name` varchar(50) NOT NULL, + `remark` varchar(100) DEFAULT NULL, + `sort` int(11) NOT NULL DEFAULT '0', + `addtime` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `sort` (`sort`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/app/sql/update.sql b/app/sql/update.sql index 69ce7f3..fa8f9b2 100644 --- a/app/sql/update.sql +++ b/app/sql/update.sql @@ -198,4 +198,18 @@ CREATE TABLE IF NOT EXISTS `dnsmgr_domain_alias` ( PRIMARY KEY (`id`), KEY `did` (`did`), KEY `name` (`name`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `dnsmgr_domain_category` ( + `id` int(11) unsigned NOT NULL auto_increment, + `name` varchar(50) NOT NULL, + `remark` varchar(100) DEFAULT NULL, + `sort` int(11) NOT NULL DEFAULT '0', + `addtime` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `sort` (`sort`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +ALTER TABLE `dnsmgr_domain` +ADD COLUMN `cid` int(11) unsigned NOT NULL DEFAULT '0', +ADD KEY `cid` (`cid`); \ No newline at end of file diff --git a/app/view/common/layout.html b/app/view/common/layout.html index fa3ae6c..4b8434e 100644 --- a/app/view/common/layout.html +++ b/app/view/common/layout.html @@ -109,6 +109,9 @@
提示:可以选择不同的DNS服务器来检测解析是否在全球生效。
+主机记录:' + row.Name + '
'; + content += '记录类型:' + row.Type + '
'; + content += '记录值:' + htmlEscape(row.Value) + '
'; + content += '检测结果:' + result.message + '
'; + if(result.actual && result.actual.length > 0){ + content += '实际解析值:
'; + content += '期望解析值:' + htmlEscape(result.expected) + '
'; + } + content += '