From f20b132d97e64d82b1ee19bdcb3839e998ab8f69 Mon Sep 17 00:00:00 2001 From: net909 Date: Wed, 1 May 2024 10:51:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9ECF=E4=BC=98=E9=80=89IP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 +- app/command/Opiptask.php | 28 ++++ app/controller/Dmonitor.php | 10 +- app/controller/Domain.php | 18 ++- app/controller/Index.php | 3 + app/controller/Optimizeip.php | 174 +++++++++++++++++++++++++ app/lib/OptimizeService.php | 204 ++++++++++++++++++++++++++++++ app/lib/TaskRunner.php | 62 +++++---- app/lib/dns/dnspod.php | 2 + app/lib/dns/huawei.php | 1 + app/sql/install.sql | 28 +++- app/sql/update.sql | 23 +++- app/view/common/layout.html | 13 ++ app/view/dmonitor/task.html | 16 ++- app/view/dmonitor/taskform.html | 21 ++- app/view/index/index.html | 4 + app/view/optimizeip/opipform.html | 181 ++++++++++++++++++++++++++ app/view/optimizeip/opiplist.html | 184 +++++++++++++++++++++++++++ app/view/optimizeip/opipset.html | 105 +++++++++++++++ config/app.php | 4 +- config/console.php | 1 + route/app.php | 8 ++ 22 files changed, 1048 insertions(+), 53 deletions(-) create mode 100644 app/command/Opiptask.php create mode 100644 app/controller/Optimizeip.php create mode 100644 app/lib/OptimizeService.php create mode 100644 app/view/optimizeip/opipform.html create mode 100644 app/view/optimizeip/opiplist.html create mode 100644 app/view/optimizeip/opipset.html diff --git a/README.md b/README.md index b64aaa7..6cc088c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,12 @@ - DNSLA - CloudFlare -本系统支持多用户,每个用户可分配不同的域名解析权限;支持API接口,支持获取域名独立DNS控制面板登录链接,方便各种IDC系统对接。 +### 功能特性 + +- 多用户管理,可为每个用户可分配不同的域名解析权限 +- 提供API接口,可获取域名单独的登录链接,方便各种IDC系统对接 +- 容灾切换功能,支持ping、tcp、http(s)检测协议并自动暂停/修改域名解析,并支持邮件、微信公众号通知 +- CF优选IP功能,支持获取最新的Cloudflare优选IP,并自动更新到解析记录 ### 演示截图 @@ -29,6 +34,10 @@ ![](https://p0.meituan.net/csc/d1bd90bedca9b6cbc5da40286bdb5cd5228438.png) +CF优选IP功能,添加优选IP任务 + +![](https://p1.meituan.net/csc/da70c76753aee4bce044d16fadd56e5f217660.png) + ### 部署方法 * 从[Release](https://github.com/netcccyun/dnsmgr/releases)页面下载安装包 diff --git a/app/command/Opiptask.php b/app/command/Opiptask.php new file mode 100644 index 0000000..1a40b3c --- /dev/null +++ b/app/command/Opiptask.php @@ -0,0 +1,28 @@ +setName('opiptask') + ->setDescription('CF优选IP任务'); + } + + protected function execute(Input $input, Output $output) + { + (new OptimizeService())->execute(); + } +} diff --git a/app/controller/Dmonitor.php b/app/controller/Dmonitor.php index b287dfb..9430a43 100644 --- a/app/controller/Dmonitor.php +++ b/app/controller/Dmonitor.php @@ -157,7 +157,7 @@ class Dmonitor extends BaseController if($action == 'edit'){ $id = input('get.id/d'); $task = Db::name('dmtask')->where('id', $id)->find(); - if(empty($task)) return $this->alert('error', '任务不存在'); + if(empty($task)) return $this->alert('error', '切换策略不存在'); } $domains = []; @@ -169,7 +169,7 @@ class Dmonitor extends BaseController View::assign('info', $task); View::assign('action', $action); View::assign('support_ping', function_exists('exec')?'1':'0'); - return View::fetch('taskform'); + return View::fetch(); } public function taskinfo() @@ -177,14 +177,16 @@ class Dmonitor extends BaseController if(!checkPermission(2)) return $this->alert('error', '无权限'); $id = input('param.id/d'); $task = Db::name('dmtask')->where('id', $id)->find(); - if(empty($task)) return $this->alert('error', '任务不存在'); + if(empty($task)) return $this->alert('error', '切换策略不存在'); $switch_count = Db::name('dmlog')->where('taskid', $id)->where('date', '>=', date("Y-m-d H:i:s",strtotime("-1 days")))->count(); $fail_count = Db::name('dmlog')->where('taskid', $id)->where('date', '>=', date("Y-m-d H:i:s",strtotime("-1 days")))->where('action', 1)->count(); $task['switch_count'] = $switch_count; $task['fail_count'] = $fail_count; - if($task['type'] == 2){ + if($task['type'] == 3){ + $task['action_name'] = ['未知', '开启解析', '暂停解析']; + }elseif($task['type'] == 2){ $task['action_name'] = ['未知', '切换备用解析记录', '恢复主解析记录']; }else{ $task['action_name'] = ['未知', '暂停解析', '启用解析']; diff --git a/app/controller/Domain.php b/app/controller/Domain.php index c5f5dc4..01976f5 100644 --- a/app/controller/Domain.php +++ b/app/controller/Domain.php @@ -220,6 +220,7 @@ class Domain extends BaseController $id = input('post.id/d'); Db::name('domain')->where('id', $id)->delete(); Db::name('dmtask')->where('did', $id)->delete(); + Db::name('optimizeip')->where('did', $id)->delete(); return json(['code'=>0]); } return json(['code'=>-3]); @@ -232,9 +233,16 @@ class Domain extends BaseController $page = input('?post.page') ? input('post.page/d') : 1; $pagesize = input('?post.pagesize') ? input('post.pagesize/d') : 10; $dns = DnsHelper::getModel($aid); - $list = $dns->getDomainList($kw, $page, $pagesize); - if(!$list) return json(['code'=>-1, 'msg'=>'获取域名列表失败,'.$dns->getError()]); - return json(['code'=>0, 'data'=>$list]); + $result = $dns->getDomainList($kw, $page, $pagesize); + if(!$result) return json(['code'=>-1, 'msg'=>'获取域名列表失败,'.$dns->getError()]); + + $newlist = []; + foreach($result['list'] as $row){ + if(!Db::name('domain')->where('aid', $aid)->where('name', $row['Domain'])->find()){ + $newlist[] = $row; + } + } + return json(['code'=>0, 'data'=>['total'=>$result['total'], 'list'=>$newlist]]); } //获取解析线路和最小TTL @@ -244,10 +252,10 @@ class Domain extends BaseController if(empty($recordLine)){ $dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']); if(!$dns) return $this->alert('error', 'DNS模块不存在'); - $recordLine = $dns->getRecordLine($drow['name']); + $recordLine = $dns->getRecordLine(); if(!$recordLine) return $this->alert('error', '获取解析线路列表失败,'.$dns->getError()); cache('record_line_'.$drow['id'], $recordLine, 604800); - $minTTL = $dns->getMinTTL($drow['name']); + $minTTL = $dns->getMinTTL(); if($minTTL){ cache('min_ttl_'.$drow['id'], $minTTL, 604800); } diff --git a/app/controller/Index.php b/app/controller/Index.php index a904d8f..5c8a4ee 100644 --- a/app/controller/Index.php +++ b/app/controller/Index.php @@ -112,4 +112,7 @@ class Index extends BaseController return view(); } + public function test(){ + + } } diff --git a/app/controller/Optimizeip.php b/app/controller/Optimizeip.php new file mode 100644 index 0000000..8336829 --- /dev/null +++ b/app/controller/Optimizeip.php @@ -0,0 +1,174 @@ +alert('error', '无权限'); + if(request()->isPost()){ + $params = input('post.'); + foreach ($params as $key=>$value){ + if (empty($key)) { + continue; + } + config_set($key, $value); + Cache::delete('configs'); + } + return json(['code'=>0, 'msg'=>'succ']); + } + return View::fetch(); + } + + public function opiplist() + { + if(!checkPermission(2)) return $this->alert('error', '无权限'); + return View::fetch(); + } + + public function opiplist_data(){ + if(!checkPermission(2)) return json(['total'=>0, 'rows'=>[]]); + $type = input('post.type/d', 1); + $kw = input('post.kw', null, 'trim'); + $offset = input('post.offset/d'); + $limit = input('post.limit/d'); + + $select = Db::name('optimizeip')->alias('A')->join('domain B','A.did = B.id'); + if(!empty($kw)){ + if($type == 1){ + $select->whereLike('rr|B.name', '%'.$kw.'%'); + }elseif($type == 2){ + $select->whereLike('remark', '%'.$kw.'%'); + } + } + $total = $select->count(); + $list = $select->order('A.id','desc')->limit($offset, $limit)->field('A.*,B.name domain')->select(); + + return json(['total'=>$total, 'rows'=>$list]); + } + + public function opipform() + { + if(!checkPermission(2)) return $this->alert('error', '无权限'); + $action = input('param.action'); + if(request()->isPost()){ + if($action == 'add'){ + $task = [ + 'did' => input('post.did/d'), + 'rr' => input('post.rr', null, 'trim'), + 'type' => input('post.type/d'), + 'ip_type' => input('post.ip_type', null, 'trim'), + 'cdn_type' => input('post.cdn_type/d'), + 'recordnum' => input('post.recordnum/d'), + 'ttl' => input('post.ttl/d'), + 'remark' => input('post.remark', null, 'trim'), + 'addtime' => time(), + 'active' => 1 + ]; + + if(empty($task['did']) || empty($task['rr']) || empty($task['ip_type']) || empty($task['recordnum']) || empty($task['ttl'])){ + return json(['code'=>-1, 'msg'=>'必填项不能为空']); + } + if($task['recordnum'] > 5){ + return json(['code'=>-1, 'msg'=>'解析数量不能超过5个']); + } + if(Db::name('optimizeip')->where('did', $task['did'])->where('rr', $task['rr'])->find()){ + return json(['code'=>-1, 'msg'=>'当前域名的优选IP任务已存在']); + } + Db::name('optimizeip')->insert($task); + return json(['code'=>0, 'msg'=>'添加成功']); + }elseif($action == 'edit'){ + $id = input('post.id/d'); + $task = [ + 'did' => input('post.did/d'), + 'rr' => input('post.rr', null, 'trim'), + 'type' => input('post.type/d'), + 'ip_type' => input('post.ip_type', null, 'trim'), + 'cdn_type' => input('post.cdn_type/d'), + 'recordnum' => input('post.recordnum/d'), + 'ttl' => input('post.ttl/d'), + 'remark' => input('post.remark', null, 'trim'), + ]; + + if(empty($task['did']) || empty($task['rr']) || empty($task['ip_type']) || empty($task['recordnum']) || empty($task['ttl'])){ + return json(['code'=>-1, 'msg'=>'必填项不能为空']); + } + if($task['recordnum'] > 5){ + return json(['code'=>-1, 'msg'=>'解析数量不能超过5个']); + } + if(Db::name('optimizeip')->where('did', $task['did'])->where('rr', $task['rr'])->where('id', '<>', $id)->find()){ + return json(['code'=>-1, 'msg'=>'当前域名的优选IP任务已存在']); + } + Db::name('optimizeip')->where('id', $id)->update($task); + return json(['code'=>0, 'msg'=>'修改成功']); + }elseif($action == 'setactive'){ + $id = input('post.id/d'); + $active = input('post.active/d'); + Db::name('optimizeip')->where('id', $id)->update(['active'=>$active]); + return json(['code'=>0, 'msg'=>'设置成功']); + }elseif($action == 'del'){ + $id = input('post.id/d'); + Db::name('optimizeip')->where('id', $id)->delete(); + return json(['code'=>0, 'msg'=>'删除成功']); + }elseif($action == 'run'){ + $id = input('post.id/d'); + $task = Db::name('optimizeip')->where('id', $id)->find(); + if(empty($task)) return json(['code'=>-1, 'msg'=>'任务不存在']); + try{ + $result = (new OptimizeService())->execute_one($task); + Db::name('optimizeip')->where('id', $id)->update(['status' => 1, 'errmsg' => null, 'updatetime' => date('Y-m-d H:i:s')]); + return json(['code'=>0, 'msg'=>'优选任务执行成功:'.$result]); + }catch(Exception $e){ + Db::name('optimizeip')->where('id', $id)->update(['status' => 2, 'errmsg' => $e->getMessage(), 'updatetime' => date('Y-m-d H:i:s')]); + return json(['code'=>-1, 'msg'=>'优选任务执行失败:'.$e->getMessage(), 'stack'=>$e->__toString()]); + } + }else{ + return json(['code'=>-1, 'msg'=>'参数错误']); + } + } + $task = null; + if($action == 'edit'){ + $id = input('get.id/d'); + $task = Db::name('optimizeip')->where('id', $id)->find(); + if(empty($task)) return $this->alert('error', '任务不存在'); + } + + $domains = []; + foreach(Db::name('domain')->alias('A')->join('account B','A.aid = B.id')->field('A.*')->where('B.type', '<>', 'cloudflare')->select() as $row){ + $domains[$row['id']] = $row['name']; + } + View::assign('domains', $domains); + + View::assign('info', $task); + View::assign('action', $action); + return View::fetch(); + } + + public function queryapi() + { + if(!checkPermission(2)) return $this->alert('error', '无权限'); + $optimize_ip_api = input('post.optimize_ip_api/d'); + $optimize_ip_key = input('post.optimize_ip_key', null, 'trim'); + if(empty($optimize_ip_key)) return json(['code'=>-1, 'msg'=>'参数不能为空']); + try{ + $result = (new OptimizeService())->get_license($optimize_ip_api, $optimize_ip_key); + return json(['code'=>0, 'msg'=>'当前积分余额:'.$result]); + }catch(Exception $e){ + return json(['code'=>-1, 'msg'=>$e->getMessage()]); + } + } + + public function status() + { + $run_time = Db::name('optimizeip')->where('active', 1)->order('updatetime', 'desc')->value('updatetime'); + $run_state = $run_time ? (time()-strtotime($run_time) > 3600 ? 0 : 1) : 0; + return $run_state == 1 ? 'ok' : 'error'; + } +} \ No newline at end of file diff --git a/app/lib/OptimizeService.php b/app/lib/OptimizeService.php new file mode 100644 index 0000000..1f125b9 --- /dev/null +++ b/app/lib/OptimizeService.php @@ -0,0 +1,204 @@ + ['DEF'=>'default', 'CT'=>'telecom', 'CU'=>'unicom', 'CM'=>'mobile', 'AB'=>'oversea'], + 'dnspod' => ['DEF'=>'0', 'CT'=>'10=0', 'CU'=>'10=1', 'CM'=>'10=3', 'AB'=>'3=0'], + 'huawei' => ['DEF'=>'default_view', 'CT'=>'Dianxin', 'CU'=>'Liantong', 'CM'=>'Yidong', 'AB'=>'Abroad'], + 'west' => ['DEF'=>'', 'CT'=>'LTEL', 'CU'=>'LCNC', 'CM'=>'LMOB', 'AB'=>'LFOR'], + 'dnsla' => ['DEF'=>'', 'CT'=>'84613316902921216', 'CU'=>'84613316923892736', 'CM'=>'84613316953252864', 'AB'=>''], + ]; + + private $ip_address = []; + private $add_num = 0; + private $change_num = 0; + private $del_num = 0; + + public static function get_license($api, $key){ + if($api == 1){ + $url = 'https://api.hostmonit.com/get_license?license='.$key; + }else{ + $url = 'https://monitor.gacjie.cn/api/client/get_account_integral?license='.$key; + } + $response = get_curl($url); + $arr = json_decode($response, true); + if(isset($arr['code']) && $arr['code'] == 200 && isset($arr['count'])){ + return $arr['count']; + }elseif(isset($arr['info'])){ + throw new Exception('获取剩余请求次数失败,'.$arr['info']); + }else{ + throw new Exception('获取剩余请求次数失败'); + } + } + + public function get_ip_address($cdn_type = 1, $ip_type = 'v4'){ + $api = config_get('optimize_ip_api', 0); + if($api == 1){ + $url = 'https://api.hostmonit.com/get_optimization_ip'; + }else{ + $url = 'https://monitor.gacjie.cn/api/client/get_ip_address'; + } + $params = [ + 'key' => config_get('optimize_ip_key', 'o1zrmHAF'), + 'type' => $ip_type, + ]; + if($api == 0){ + $params['cdn_server'] = $cdn_type; + } + $response = get_curl($url, json_encode($params), 0, 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']; + }elseif(isset($arr['info'])){ + throw new Exception('获取优选IP数据失败,'.$arr['info']); + }elseif(isset($arr['msg'])){ + throw new Exception('获取优选IP数据失败,'.$arr['msg']); + }else{ + throw new Exception('获取优选IP数据失败,原因未知'); + } + } + + public function get_ip_address2($cdn_type = 1, $ip_type = 'v4'){ + $key = $cdn_type.'_'.$ip_type; + if(!isset($this->ip_address[$key])){ + $info = $this->get_ip_address($cdn_type, $ip_type); + $res = []; + if(isset($info['DEF'])) $res['DEF'] = $info['DEF']; + if(isset($info['CT'])) $res['CT'] = $info['CT']; + if(isset($info['CU'])) $res['CU'] = $info['CU']; + if(isset($info['CM'])) $res['CM'] = $info['CM']; + $this->ip_address[$key] = $res; + } + return $this->ip_address[$key]; + } + + //批量执行优选任务 + public function execute(){ + $list = Db::name('optimizeip')->where('active', 1)->select(); + echo '开始执行IP优选任务,共获取到'.count($list).'个待执行任务'."\n"; + foreach($list as $row){ + try{ + $result = $this->execute_one($row); + Db::name('optimizeip')->where('id', $row['id'])->update(['status' => 1, 'errmsg' => null, 'updatetime' => date('Y-m-d H:i:s')]); + echo '优选任务'.$row['id'].'执行成功:'.$result."\n"; + }catch(Exception $e){ + Db::name('optimizeip')->where('id', $row['id'])->update(['status' => 2, 'errmsg' => $e->getMessage(), 'updatetime' => date('Y-m-d H:i:s')]); + echo '优选任务'.$row['id'].'执行失败:'.$e->getMessage()."\n"; + } + } + } + + //执行单个优选任务 + public function execute_one($row){ + $this->add_num = 0; + $this->change_num = 0; + $this->del_num = 0; + $ip_types = explode(',', $row['ip_type']); + foreach($ip_types as $ip_type){ + if(empty($ip_type)) continue; + + $drow = Db::name('domain')->alias('A')->join('account B','A.aid = B.id')->where('A.id', $row['did'])->field('A.*,B.type,B.ak,B.sk,B.ext')->find(); + if(!$drow){ + throw new Exception('域名不存在(ID:'.$row['did'].')'); + } + if(!isset(self::$line_name[$drow['type']])){ + throw new Exception('不支持的DNS服务商'); + } + + $info = $this->get_ip_address2($row['cdn_type'], $ip_type); + + $dns = DnsHelper::getModel($drow['aid'], $drow['name'], $drow['thirdid']); + $domainRecords = $dns->getSubDomainRecords($row['rr'], 1, 100); + if(!$domainRecords){ + throw new Exception('获取记录列表失败,'.$dns->getError()); + } + + if($row['type'] == 1 && isset($info['DEF']) && !empty($info['DEF'])) $row['type'] = 0; + + foreach($info as $line=>$iplist){ + if(empty($iplist)) continue; + $get_ips = array_column($iplist, 'ip'); + if($drow['type']=='huawei') {sort($get_ips); $get_ips = [implode(',',$get_ips)]; $row['recordnum'] = 1;} + if($row['type'] == 1 && $line == 'CT') $line = 'DEF'; + $line_name = self::$line_name[$drow['type']][$line]; + $this->process_dns_line($dns, $row, $domainRecords['list'], $get_ips, $line_name, $ip_type); + } + } + + return '成功添加'.$this->add_num.'条记录,修改'.$this->change_num.'条记录,删除'.$this->del_num.'条记录'; + } + + //处理单个线路的解析记录 + private function process_dns_line($dns, $row, $record_list, $get_ips, $line_name, $ip_type){ + $record_num = $row['recordnum']; + $records = array_filter($record_list, function($v) use($line_name){ + return $v['Line'] == $line_name; + }); + + //删除CNAME记录 + $cname_records = array_filter($records, function($v){ + return $v['Type'] == 'CNAME'; + }); + if(!empty($cname_records)){ + foreach($cname_records as $record){ + $dns->deleteDomainRecord($record['RecordId']); + } + } + + //处理A/AAAA记录 + $ip_records = array_filter($records, function($v) use ($ip_type){ + return $v['Type'] == ($ip_type == 'v6' ? 'AAAA' : 'A'); + }); + + if(!empty($ip_records) && is_array($ip_records[array_key_first($ip_records)]['Value'])){ //处理华为云记录 + foreach($ip_records as &$ip_record){ + sort($ip_record['Value']); + $ip_record['Value'] = implode(',', $ip_record['Value']); + } + } + + $exist_ips = array_column($ip_records, 'Value'); + $add_ips = array_diff($get_ips, $exist_ips); + $del_ips = array_diff($exist_ips, $get_ips); + $correct_ips = array_diff($exist_ips, $del_ips); + $correct_count = count($correct_ips); + if(!empty($del_ips)){ + foreach($ip_records as $record){ + if(in_array($record['Value'], $del_ips)){ + $add_ip = array_pop($add_ips); + if($add_ip){ + $res = $dns->updateDomainRecord($record['RecordId'], $row['rr'], $ip_type == 'v6' ? 'AAAA' : 'A', $add_ip, $line_name, $row['ttl']); + if(!$res){ + throw new Exception('修改解析失败,'.$dns->getError()); + } + $this->change_num++; + $correct_count++; + }else{ + $res = $dns->deleteDomainRecord($record['RecordId']); + if(!$res){ + throw new Exception('删除解析失败,'.$dns->getError()); + } + $this->del_num++; + } + } + } + } + if($correct_count < $record_num && !empty($add_ips)){ + foreach($add_ips as $add_ip){ + $res = $dns->addDomainRecord($row['rr'], $ip_type == 'v6' ? 'AAAA' : 'A', $add_ip, $line_name, $row['ttl']); + if(!$res){ + throw new Exception('添加解析失败,'.$dns->getError()); + } + $this->add_num++; + $correct_count++; + if($correct_count >= $record_num) break; + } + } + } +} \ No newline at end of file diff --git a/app/lib/TaskRunner.php b/app/lib/TaskRunner.php index f6e4cbd..7925bc8 100644 --- a/app/lib/TaskRunner.php +++ b/app/lib/TaskRunner.php @@ -27,31 +27,43 @@ class TaskRunner public function execute($row) { - if($row['checktype'] == 2){ - $result = CheckUtils::curl($row['checkurl'], $row['timeout'], $row['main_value']); - }else if($row['checktype'] == 1){ - $result = CheckUtils::tcp($row['main_value'], $row['tcpport'], $row['timeout']); - }else{ - $result = CheckUtils::ping($row['main_value']); - } - - $action = 0; - if($result['status'] && $row['status']==1){ - if($row['cycle'] <= 1 || $row['errcount'] >= $row['cycle']){ - $this->db()->name('dmtask')->where('id', $row['id'])->update(['status'=>0, 'errcount'=>0, 'switchtime'=>time()]); + if($row['type'] == 3){ //条件开启解析 + $action = 0; + $remain = $this->db()->name('dmtask')->where(['did'=>$row['did'], 'rr'=>$row['rr'], 'type'=>1, 'status'=>0])->count(); + if($remain<=$row['cycle'] && $row['status']==0){ $action = 2; - }else{ - $this->db()->name('dmtask')->where('id', $row['id'])->inc('errcount')->update(); - } - }elseif(!$result['status'] && $row['status']==0){ - if($row['cycle'] <= 1 || $row['errcount'] >= $row['cycle']){ $this->db()->name('dmtask')->where('id', $row['id'])->update(['status'=>1, 'errcount'=>0, 'switchtime'=>time()]); + }elseif($remain>$row['cycle'] && $row['status']==1){ $action = 1; + $this->db()->name('dmtask')->where('id', $row['id'])->update(['status'=>0, 'errcount'=>0, 'switchtime'=>time()]); + } + }else{ + if($row['checktype'] == 2){ + $result = CheckUtils::curl($row['checkurl'], $row['timeout'], $row['main_value']); + }else if($row['checktype'] == 1){ + $result = CheckUtils::tcp($row['main_value'], $row['tcpport'], $row['timeout']); }else{ - $this->db()->name('dmtask')->where('id', $row['id'])->inc('errcount')->update(); + $result = CheckUtils::ping($row['main_value']); + } + + $action = 0; + if($result['status'] && $row['status']==1){ + if($row['cycle'] <= 1 || $row['errcount'] >= $row['cycle']){ + $this->db()->name('dmtask')->where('id', $row['id'])->update(['status'=>0, 'errcount'=>0, 'switchtime'=>time()]); + $action = 2; + }else{ + $this->db()->name('dmtask')->where('id', $row['id'])->inc('errcount')->update(); + } + }elseif(!$result['status'] && $row['status']==0){ + if($row['cycle'] <= 1 || $row['errcount'] >= $row['cycle']){ + $this->db()->name('dmtask')->where('id', $row['id'])->update(['status'=>1, 'errcount'=>0, 'switchtime'=>time()]); + $action = 1; + }else{ + $this->db()->name('dmtask')->where('id', $row['id'])->inc('errcount')->update(); + } + }elseif($row['errcount'] > 0){ + $this->db()->name('dmtask')->where('id', $row['id'])->update(['errcount'=>0]); } - }elseif($row['errcount'] > 0){ - $this->db()->name('dmtask')->where('id', $row['id'])->update(['errcount'=>0]); } if($action > 0){ @@ -71,7 +83,7 @@ class TaskRunner if(!$res){ $this->db()->name('log')->insert(['uid' => 0, 'domain' => $drow['name'], 'action' => '修改解析失败', 'data' => $dns->getError(), 'addtime' => date("Y-m-d H:i:s")]); } - }elseif($row['type'] == 1){ + }elseif($row['type'] == 1 || $row['type'] == 3){ $dns = DnsHelper::getModel2($drow); $res = $dns->setDomainRecordStatus($row['recordid'], '0'); if(!$res){ @@ -86,7 +98,7 @@ class TaskRunner if(!$res){ $this->db()->name('log')->insert(['uid' => 0, 'domain' => $drow['name'], 'action' => '修改解析失败', 'data' => $dns->getError(), 'addtime' => date("Y-m-d H:i:s")]); } - }elseif($row['type'] == 1){ + }elseif($row['type'] == 1 || $row['type'] == 3){ $dns = DnsHelper::getModel2($drow); $res = $dns->setDomainRecordStatus($row['recordid'], '1'); if(!$res){ @@ -101,11 +113,13 @@ class TaskRunner $this->db()->name('dmlog')->insert([ 'taskid' => $row['id'], 'action' => $action, - 'errmsg' => $result['status'] ? null : $result['errmsg'], + 'errmsg' => isset($result) ? ($result['status'] ? null : $result['errmsg']) : null, 'date' => date('Y-m-d H:i:s') ]); $this->closeDb(); - MsgNotice::send($action, $row, $result); + if($row['type'] != 3){ + MsgNotice::send($action, $row, $result); + } } } \ No newline at end of file diff --git a/app/lib/dns/dnspod.php b/app/lib/dns/dnspod.php index 0049b3e..945ef35 100644 --- a/app/lib/dns/dnspod.php +++ b/app/lib/dns/dnspod.php @@ -84,6 +84,8 @@ class dnspod implements DnsInterface { ]; } return ['total' => $data['RecordCountInfo']['TotalCount'], 'list' => $list]; + }elseif($this->error == '记录列表为空。'){ + return ['total' => 0, 'list' => []]; } return false; } diff --git a/app/lib/dns/huawei.php b/app/lib/dns/huawei.php index 781c428..148dd57 100644 --- a/app/lib/dns/huawei.php +++ b/app/lib/dns/huawei.php @@ -91,6 +91,7 @@ class huawei implements DnsInterface { public function getDomainRecordInfo($RecordId){ $data = $this->send_reuqest('GET', '/v2.1/zones/'.$this->domainid.'/recordsets/'.$RecordId); if($data){ + if($data['name'] == $data['zone_name']) $data['name'] = '@'; return [ 'RecordId' => $data['id'], 'Domain' => rtrim($data['zone_name'], '.'), diff --git a/app/sql/install.sql b/app/sql/install.sql index bc0a520..b1291d8 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', '1003'); +INSERT INTO `dnsmgr_config` VALUES ('version', '1005'); 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'); @@ -64,7 +64,7 @@ CREATE TABLE `dnsmgr_permission` ( DROP TABLE IF EXISTS `dnsmgr_log`; CREATE TABLE `dnsmgr_log` ( - `id` int(11) NOT NULL AUTO_INCREMENT, + `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 '', @@ -77,7 +77,7 @@ CREATE TABLE `dnsmgr_log` ( DROP TABLE IF EXISTS `dnsmgr_dmtask`; CREATE TABLE `dnsmgr_dmtask` ( - `id` int(11) NOT NULL AUTO_INCREMENT, + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `did` int(11) unsigned NOT NULL, `rr` varchar(128) NOT NULL, `recordid` varchar(60) NOT NULL, @@ -105,7 +105,7 @@ CREATE TABLE `dnsmgr_dmtask` ( DROP TABLE IF EXISTS `dnsmgr_dmlog`; CREATE TABLE `dnsmgr_dmlog` ( - `id` int(11) NOT NULL AUTO_INCREMENT, + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `taskid` int(11) unsigned NOT NULL, `action` tinyint(4) NOT NULL DEFAULT 0, `errmsg` varchar(100) DEFAULT NULL, @@ -113,4 +113,24 @@ CREATE TABLE `dnsmgr_dmlog` ( PRIMARY KEY (`id`), KEY `taskid` (`taskid`), KEY `date` (`date`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +DROP TABLE IF EXISTS `dnsmgr_optimizeip`; +CREATE TABLE `dnsmgr_optimizeip` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `did` int(11) unsigned NOT NULL, + `rr` varchar(128) NOT NULL, + `type` tinyint(1) NOT NULL DEFAULT 0, + `ip_type` varchar(10) NOT NULL, + `cdn_type` tinyint(5) NOT NULL DEFAULT 1, + `recordnum` tinyint(5) NOT NULL DEFAULT 2, + `ttl` int(5) NOT NULL DEFAULT 600, + `remark` varchar(100) DEFAULT NULL, + `addtime` datetime NOT NULL, + `updatetime` datetime DEFAULT NULL, + `status` tinyint(1) NOT NULL DEFAULT 0, + `active` tinyint(1) NOT NULL DEFAULT 0, + `errmsg` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `did` (`did`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/app/sql/update.sql b/app/sql/update.sql index 65aae20..1ab9e5d 100644 --- a/app/sql/update.sql +++ b/app/sql/update.sql @@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS `dnsmgr_config` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `dnsmgr_dmtask` ( - `id` int(11) NOT NULL AUTO_INCREMENT, + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `did` int(11) unsigned NOT NULL, `rr` varchar(128) NOT NULL, `recordid` varchar(60) NOT NULL, @@ -32,7 +32,7 @@ CREATE TABLE IF NOT EXISTS `dnsmgr_dmtask` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `dnsmgr_dmlog` ( - `id` int(11) NOT NULL AUTO_INCREMENT, + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `taskid` int(11) unsigned NOT NULL, `action` tinyint(4) NOT NULL DEFAULT 0, `errmsg` varchar(100) DEFAULT NULL, @@ -40,4 +40,23 @@ CREATE TABLE IF NOT EXISTS `dnsmgr_dmlog` ( PRIMARY KEY (`id`), KEY `taskid` (`taskid`), KEY `date` (`date`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `dnsmgr_optimizeip` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `did` int(11) unsigned NOT NULL, + `rr` varchar(128) NOT NULL, + `type` tinyint(1) NOT NULL DEFAULT 0, + `ip_type` varchar(10) NOT NULL, + `cdn_type` tinyint(5) NOT NULL DEFAULT 1, + `recordnum` tinyint(5) NOT NULL DEFAULT 2, + `ttl` int(5) NOT NULL DEFAULT 600, + `remark` varchar(100) DEFAULT NULL, + `addtime` datetime NOT NULL, + `updatetime` datetime DEFAULT NULL, + `status` tinyint(1) NOT NULL DEFAULT 0, + `active` tinyint(1) NOT NULL DEFAULT 0, + `errmsg` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `did` (`did`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/app/view/common/layout.html b/app/view/common/layout.html index 1f9162e..f371b62 100644 --- a/app/view/common/layout.html +++ b/app/view/common/layout.html @@ -121,6 +121,19 @@
  • 通知设置
  • +
  • + + + CF优选IP + + + + + +
  • 域名账户
  • diff --git a/app/view/dmonitor/task.html b/app/view/dmonitor/task.html index 311b637..aae129e 100644 --- a/app/view/dmonitor/task.html +++ b/app/view/dmonitor/task.html @@ -75,6 +75,8 @@ $(document).ready(function(){ return '暂停解析'; } else if(value == 2) { return '切换备用'; + } else if(value == 3) { + return '条件开启'; } else { return '无操作'; } @@ -84,11 +86,15 @@ $(document).ready(function(){ field: 'frequency', title: '检测间隔', formatter: function(value, row, index) { - var checktype = 'PING'; - if(row.checktype == 2){ - checktype = row.checkurl; - }else if(row.checktype == 1){ - checktype = 'TCP('+row.tcpport+'端口)'; + if(row.type <= 2){ + var checktype = 'PING'; + if(row.checktype == 2){ + checktype = row.checkurl; + }else if(row.checktype == 1){ + checktype = 'TCP('+row.tcpport+'端口)'; + } + }else{ + var checktype = ''; } return '' + value + '秒'; } diff --git a/app/view/dmonitor/taskform.html b/app/view/dmonitor/taskform.html index 2093dc7..dd6541c 100644 --- a/app/view/dmonitor/taskform.html +++ b/app/view/dmonitor/taskform.html @@ -7,6 +7,7 @@ position: absolute; left: 0; } + .tips{color: #f6a838; padding-left: 5px;}
    @@ -49,7 +50,7 @@
    -
    +
    -
    +
    -
    +
    -
    +
    @@ -78,6 +79,12 @@
    +
    + +
    + +
    +
    @@ -87,7 +94,7 @@
    -
    +
    @@ -128,7 +135,7 @@ new Vue({ main_value: '', type: 1, backup_value: '', - checktype: 2, + checktype: 1, tcpport: 80, checkurl: '', frequency: 5, @@ -140,6 +147,7 @@ new Vue({ {value:0, label:'无操作'}, {value:1, label:'暂停解析'}, {value:2, label:'切换备用解析'}, + {value:3, label:'条件开启解析'}, ], checktypeList: [ {value:0, label:'PING', disabled: support_ping=='0'}, @@ -169,6 +177,7 @@ new Vue({ $("#taskform").bootstrapValidator({ live: 'submitted', }); + $('[data-toggle="tooltip"]').tooltip(); }, methods: { getRecordList(){ diff --git a/app/view/index/index.html b/app/view/index/index.html index d4074f2..473cbe7 100644 --- a/app/view/index/index.html +++ b/app/view/index/index.html @@ -169,6 +169,10 @@ function speedModeNotice(){ var html = "
    当前浏览器是兼容模式,为确保后台功能正常使用,请切换到极速模式
    操作方法:点击浏览器地址栏右侧的IE符号→选择“极速模式
    "; $("#browser-notice").html(html) } + else if(window.location.protocol.indexOf("https")==-1){ + var html = "
    当前正在使用HTTP访问,可能存在被窃取敏感信息风险,请使用HTTPS访问!
    "; + $("#browser-notice").html(html) + } } speedModeNotice(); diff --git a/app/view/optimizeip/opipform.html b/app/view/optimizeip/opipform.html new file mode 100644 index 0000000..fc297f7 --- /dev/null +++ b/app/view/optimizeip/opipform.html @@ -0,0 +1,181 @@ +{extend name="common/layout" /} +{block name="title"}优选IP任务{/block} +{block name="main"} + +
    +
    +
    +

    返回{if $action=='edit'}编辑{else}添加{/if}优选IP任务

    +
    +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + + +
    +
    +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + + 提示:所选域名需保留一个默认线路的解析记录,指向{{cdntypeList[set.cdn_type]}}提供的CNAME地址,其他线路的解析记录需要删除,添加任务后将自动为当前域名添加电信/联通/移动线路的解析记录。 +
    +
    +
    +
    +
    +
    + + 提示:所选域名需删除全部解析记录,添加任务后将自动为当前域名添加解析记录。 +
    +
    +
    +
    +
    +
    +
    +
    +
    +{/block} +{block name="script"} + + + + +{/block} \ No newline at end of file diff --git a/app/view/optimizeip/opiplist.html b/app/view/optimizeip/opiplist.html new file mode 100644 index 0000000..e595604 --- /dev/null +++ b/app/view/optimizeip/opiplist.html @@ -0,0 +1,184 @@ +{extend name="common/layout" /} +{block name="title"}CF优选IP任务管理{/block} +{block name="main"} + +
    +
    +
    +
    + +
    +
    + +
    + +
    +
    +
    + +
    + + 刷新 + 添加 +
    + + +
    +
    +
    +
    +
    +{/block} +{block name="script"} + + + + + +{/block} \ No newline at end of file diff --git a/app/view/optimizeip/opipset.html b/app/view/optimizeip/opipset.html new file mode 100644 index 0000000..136e05f --- /dev/null +++ b/app/view/optimizeip/opipset.html @@ -0,0 +1,105 @@ +{extend name="common/layout" /} +{block name="title"}CF优选IP设置{/block} +{block name="main"} +
    +
    +
    +

    功能简介

    +
    +

    由于CloudFlare官方IP是泛播路由,同一个IP在不同地区不同运营商所链接的机房是不同的,速度或延迟也会有区别。目前网上也有很多CF优选CNAME服务,然而公共的CNAME可能无法满足稳定性和安全性的需要。

    +

    本功能可以获取CloudFlare最新的优选IP地址(分为电信/联通/移动线路),并自动更新到域名解析记录。

    +
    +
    +
    +

    使用说明

    +
    +

  • 数据接口:GacJieMonitor 数据接口支持CloudFlare、CloudFront、Gcore;HostMonit 只支持CloudFlare。
  • +

  • 接口密钥:默认o1zrmHAF为免费KEY可永久免费使用。
  • +

  • 计划任务:将以下命令添加到计划任务,周期设置为15分钟以上
  • +

    cd {:app()->getRootPath()} && php think opiptask

    +
    +
    +
    +
    +
    +

    数据接口设置

    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    +
    + + 查询积分 +
    +
    +
    +
    +
    +
    + +
    +{/block} +{block name="script"} + + +{/block} \ No newline at end of file diff --git a/config/app.php b/config/app.php index c7d9e11..d37eb2d 100644 --- a/config/app.php +++ b/config/app.php @@ -31,7 +31,7 @@ return [ 'show_error_msg' => true, 'exception_tmpl' => \think\facade\App::getAppPath() . 'view/exception.tpl', - 'version' => '1004', + 'version' => '1005', - 'dbversion' => '1003' + 'dbversion' => '1005' ]; diff --git a/config/console.php b/config/console.php index f260599..ebb1915 100644 --- a/config/console.php +++ b/config/console.php @@ -6,5 +6,6 @@ return [ // 指令定义 'commands' => [ 'dmtask' => 'app\command\Dmtask', + 'opiptask' => 'app\command\Opiptask', ], ]; diff --git a/route/app.php b/route/app.php index 2b32084..47b94ce 100644 --- a/route/app.php +++ b/route/app.php @@ -24,12 +24,14 @@ Route::any('/login', 'auth/login')->middleware(\think\middleware\SessionInit::cl Route::get('/logout', 'auth/logout'); Route::any('/quicklogin', 'auth/quicklogin'); Route::any('/dmtask/status', 'dmonitor/status'); +Route::any('/optimizeip/status', 'optimizeip/status'); Route::group(function () { Route::any('/', 'index/index'); Route::post('/changeskin', 'index/changeskin'); Route::get('/cleancache', 'index/cleancache'); Route::any('/setpwd', 'index/setpwd'); + Route::get('/test', 'index/test'); Route::post('/user/data', 'user/user_data'); Route::post('/user/op', 'user/user_op'); @@ -68,6 +70,12 @@ Route::group(function () { Route::get('/dmonitor/mailtest', 'dmonitor/mailtest'); Route::post('/dmonitor/clean', 'dmonitor/clean'); + Route::any('/optimizeip/opipset', 'optimizeip/opipset'); + Route::post('/optimizeip/queryapi', 'optimizeip/queryapi'); + Route::post('/optimizeip/opiplist/data', 'optimizeip/opiplist_data'); + Route::get('/optimizeip/opiplist', 'optimizeip/opiplist'); + Route::any('/optimizeip/opipform/:action', 'optimizeip/opipform'); + })->middleware(\app\middleware\CheckLogin::class) ->middleware(\app\middleware\ViewOutput::class);