((int)$code === 200), 'code' => $code, 'msg' => $msg, ], $extra); exit(json_encode($result, JSON_UNESCAPED_UNICODE)); } function api_lifecycle_log($type, $content, $status = '记录') { global $DB, $user, $bh; $logUser = $user ?: '外部API'; mnbt_log($logUser, $type, 'API-'.$bh.' '.$content, $status, $DB); } if($conf['apiqk']=='false')api_json_exit(100, '错误!APi已关闭!开启方法:系统后台的系统设置->api密钥处开启即可'); $gn=$_GET['gn'] ?? ''; $bh=$_POST['mn_bh'] ?? ''; $key=$_POST['mn_key'] ?? ''; $keye=$_POST['mn_keye'] ?? ''; $mn_vser=$_POST['mn_vs'] ?? 0; $user=$_POST['username'] ?? ''; if($mn_vser<15)api_json_exit(300, '您的版插件本不支持当前MNBT版本!当前MNBT版本:'.intval($WEBQB/100).',插件所支持的版本'.$mn_vser); if(empty($gn) || empty($bh) || empty($key) || empty($keye) || empty($user))api_json_exit(100, '错误!表单填写不完整!'); if($key!=$conf['api']){ mnbt_log('外部API','API鉴权','API-'.$bh.' '.$user.' 系统密钥错误','鉴权失败',$DB); api_json_exit(100, '错误!您的密钥与网站APi密钥不匹配!请您仔细核对后再试!'); } $cert=$DB->get_row_prepare("SELECT * FROM MN_bt WHERE btdh=? limit 1", [$bh]); $et_zj=$DB->get_row_prepare("SELECT * FROM MN_zj WHERE user=? limit 1", [$user]); if($cert=='' || $cert['qk']=='false')api_json_exit(100, '错误!该宝塔不存在或该宝塔已经被关闭'); $adyjm=$cert['ktmy'].$cert['qmk'];$mdjm=md5($adyjm); if($keye!=$mdjm){ if(mnbt_api_compat_mode()){ // 1.81 兼容:老对接模块按 msg 文案分支,沿用原文案与不带 key 前缀的日志 mnbt_log('外部API','API鉴权','API-'.$bh.' '.$user.' 宝塔调用密钥错误','鉴权失败',$DB); api_json_exit(100, '错误!您所传输的宝塔调用密钥与该宝塔的调用密钥不匹配!'); } // 不回显正确密钥,避免密钥泄露 mnbt_log('外部API','API鉴权','API-'.$bh.' '.$user.' 宝塔调用密钥错误(发送:'.substr($keye,0,8).',btdh='.$bh.')','鉴权失败',$DB); api_json_exit(100, '调用密钥不匹配!'); } $btipe=($cert['ptl']=='true'?'https':'http').'://'.$cert['btip'].':'.$cert['btdk']; $btkeye=$cert['btmy']; // 归属校验:除 kt(新建主机)和 cfif(连接验证)外,所有动作必须校验主机属于该节点,防止跨节点越权操作 if($gn!='kt' && $gn!='cfif'){ if(empty($et_zj))api_json_exit(100, '不存在主机用户名'); // 1.81 兼容模式仅放宽跨节点校验(老对接模块传的 username 可能与节点代号对不上);空主机检查两种模式都保留 // ssbt/btdh 为 varchar 节点代号,双端强转字符串严格比较,避免 '01' 与 '1' 被宽松比较误判 if(!mnbt_api_compat_mode() && (string)$et_zj['ssbt'] !== (string)$bh)api_json_exit(100, '主机不属于该节点'); } if($gn=='cfif'){ api_json_exit(200, '连接验证成功!'); }elseif($gn=='kt'){ $pass=daddslashes($_POST['password'] ?? ''); $flowratemax=json_encode(array('max'=>daddslashes($_POST['sizemax'] ?? 0),'dq'=>0,'statistics'=>false)); $datae=($_POST['dqtime'] ?? '0')=='0' ? '0000-00-00' : daddslashes($_POST['dqtime']); $webdx=json_encode(array('max'=>daddslashes($_POST['webdx'] ?? 0),'dq'=>0)); $sqldx=json_encode(array('max'=>daddslashes($_POST['sqldx'] ?? 0),'dq'=>0)); $ymbds=daddslashes($_POST['ymbds'] ?? 0); // 直接真值判断:已存在则报错。1.81 原表达式 ($et_zj!='' || $et_zj!=false) 恒真是 bug 而非兼容需求,兼容模式刻意不还原 if($et_zj){ api_lifecycle_log('API开通主机','开通'.$user.'失败:主机已存在','开通失败'); api_json_exit(100, '错误!该主机已经存在,请重新开通!'); } $api = new bt_api($btipe,$btkeye); // 获取节点默认 PHP 版本(优先节点设置,否则自动检测最新版本) $phpVersion = $cert['mrbts_php'] ?? ''; if ($phpVersion === '' || $phpVersion === '00') { $phpList = $api->btapi_listphp(); if (is_array($phpList)) { $versions = []; foreach ($phpList as $v) { if (($v['status'] ?? false) && ($v['version'] ?? '') !== '00') { $versions[] = $v['version']; } } if (!empty($versions)) { usort($versions, function($a, $b) { return strcmp($b, $a); }); $phpVersion = $versions[0]; $DB->query_prepare("UPDATE MN_bt SET mrbts_php=? WHERE btdh=? LIMIT 1", [$phpVersion, $bh]); } } } if ($phpVersion === '' || $phpVersion === '00') { api_lifecycle_log('API开通主机','开通'.$user.'失败:无法获取节点PHP版本','开通失败'); api_json_exit(100, '错误!无法获取该节点的 PHP 版本,请先在宝塔面板安装 PHP 或联系管理员设置节点默认 PHP 版本'); } if(mb_strlen($user)<6 || mb_strlen($pass)<6){ api_lifecycle_log('API开通主机','开通'.$user.'失败:账号或密码过短','开通失败'); api_json_exit(100, '错误!账号和密码位数均不能小于6位!'); } if($DB->get_row_prepare("SELECT id FROM MN_zj WHERE user=? limit 1", [$user])){ api_lifecycle_log('API开通主机','开通'.$user.'失败:账号重复','开通失败'); api_json_exit(100, '错误!该账号已存在!请更换账号!'); } $hskr=mt_rand(4,10); $rqsj=md5($date.$user); $wjler=substr($rqsj, $hskr , 3); if(mnbt_api_compat_mode()){ // 1.81 兼容:站点名按 max(id)+1 序列生成,老对接模块据此猜测站点名 $rowe=$DB->get_row_prepare("SELECT id FROM MN_zj WHERE 1 order by id desc limit 1"); $id=($rowe['id'] ?? 0)+1; $btserw='mnbt.'.$id.mt_rand(1,999).$wjler; }else{ // 站点名不含 id,配合下方取号重试,避免并发下 max(id)+1 重复 $btserw='mnbt.'.mt_rand(1,999).$wjler; } $mrwww=$cert['btos']=='1' ? $conf['hxi'].'/'.$btserw : $conf['hxo'].'/'.$btserw; $r_data = $api->webkt($user,$pass,$btserw,'主机','true','true',$phpVersion,$mrwww); $cjqk=$r_data['siteStatus'] ?? false; $zdide=$r_data['siteId'] ?? 0; if($cjqk=='1' || $cjqk=='true' || $cjqk===true){ if(($_POST['dqtime'] ?? '0')!='0'){ $r_datan = $api->setdqsj($zdide,$datae); $de=$r_datan['status'] ?? false; if(!($de=='1' || $de=='true' || $de===true)){ api_lifecycle_log('API开通主机','开通'.$user.'失败:到期时间设置失败','开通失败'); api_json_exit(100, '错误!网站创建成功但到期时间设置失败!'); } } // 按账号精确获取 FTP/数据库 ID(原为全表拉取,主机数多时会显著变慢) $aedfs = $api->sjid('ftps', $user); $sqlfs = $api->sjid('databases', $user); // 写入主机记录:max(id)+1 在并发下可能重复导致插入失败,失败时重新取号重试几次 $insert_sql = "INSERT INTO `MN_zj` (`id`, `ssbt`, `user`, `pass`, `sqluser`, `sqlpass`, `data`, `datae`, `qk`, `btid`, `sqldz`, `ftpid`, `ymbds`, `hxa`, `hxb`, `hxc`, `hxd`, `llmax`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; $insert_args = [$bh, $user, $pass, $user, $pass, $date, $datae, 'true', $zdide, $btserw, $aedfs, $ymbds, $webdx, $sqldx, '2', $sqlfs, $flowratemax]; $insert_ok = false; if(mnbt_api_compat_mode()){ // 1.81 兼容:沿用建站前取的号单次插入,保证站点名与 id 序列一致 $insert_ok = (bool)$DB->query_prepare($insert_sql, array_merge([$id], $insert_args)); }else{ for($tryi = 0; $tryi < 3; $tryi++){ $rowe=$DB->get_row_prepare("SELECT id FROM MN_zj WHERE 1 order by id desc limit 1"); $id=($rowe['id'] ?? 0)+1; if($DB->query_prepare($insert_sql, array_merge([$id], $insert_args))){ $insert_ok = true; break; } } } if($insert_ok){ api_lifecycle_log('API开通主机','开通'.$user.'成功,站点'.$btserw,'开通成功'); $host_row = $DB->get_row_prepare("SELECT * FROM MN_zj WHERE id=? limit 1", [$id]); if (function_exists('mnbt_do_action')) { mnbt_do_action('host.created', $host_row ?: ['id'=>$id,'user'=>$user,'ssbt'=>$bh,'btid'=>$zdide,'sqldz'=>$btserw], ['source'=>'api']); } api_json_exit(200, '主机开通成功!'); } else { $api->delsite($zdide,$btserw); api_lifecycle_log('API开通主机','开通'.$user.'失败:数据库写入失败','开通失败'); api_json_exit(100, '错误!网站添加失败(数据写入数据库失败)!请尝试重试或加官Q群询问!'); } }else{ api_lifecycle_log('API开通主机','开通'.$user.'失败:'.($r_data['msg'] ?? '宝塔创建失败'),'开通失败'); api_json_exit(100, '错误!网站创建失败!宝塔返回信息:'.($r_data['msg'] ?? '未知错误')); } }elseif($gn=='zt'){ if(empty($et_zj)) api_json_exit(100, '不存在主机用户名'); $api = new bt_api($btipe,$btkeye); $api->siteqt($et_zj['btid'],$et_zj['sqldz'],false); $api->setftpzt($et_zj['ftpid'],$et_zj['user'],'0'); $DB->query_prepare("update `MN_zj` set `qk` =? where `user`=?", ['false', $user]); if (function_exists('mnbt_do_action')) { mnbt_do_action('host.paused', $et_zj, ['source'=>'api']); } api_json_exit(200, '主机暂停成功!'); }elseif($gn=='xf'){ if(empty($et_zj)) api_json_exit(100, '不存在主机用户名'); $x_dq_date=($_POST['setdate'] ?? '0')=='0' ? '0000-00-00' : $_POST['setdate']; $old_date=$et_zj['datae'] ?? ''; $api = new bt_api($btipe,$btkeye); $r_data = $api->setdqsj($et_zj['btid'],$x_dq_date); // qk 为字符串 'true'/'false',需显式比较;续费后到期日在未来且主机处于暂停状态(qk=='false',含到期暂停)时自动解停 // 1.81 兼容:老版对 $et_zj['qk'] 做真值判断('false' 字符串亦为真,故总会解停),按老版原样还原 $qk_pf = mnbt_api_compat_mode() ? $et_zj['qk'] : $et_zj['qk']=='false'; if(strtotime($date)-strtotime($x_dq_date)<0 && $x_dq_date!='0000-00-00' && $qk_pf){ $api->siteqt($et_zj['btid'],$et_zj['sqldz'],true); $api->setftpzt($et_zj['ftpid'],$et_zj['user'],'1'); } if($DB->query_prepare("update `MN_zj` set `datae` =? where `user`=?", [$x_dq_date, $user])){ api_lifecycle_log('API续费主机','续费'.$user.' '.$old_date.'=>'.$x_dq_date,'续费成功'); $host_row = $DB->get_row_prepare("SELECT * FROM MN_zj WHERE user=? limit 1", [$user]); if (function_exists('mnbt_do_action')) { mnbt_do_action('host.renewed', $host_row ?: array_merge($et_zj, ['datae'=>$x_dq_date]), ['source'=>'api','old_date'=>$old_date,'new_date'=>$x_dq_date]); } api_json_exit(200, '主机续费成功!'); } api_lifecycle_log('API续费主机','续费'.$user.'数据库写入失败','续费失败'); api_json_exit(100, '主机续费失败,数据库写入失败!'); }elseif($gn=='jc'){ if(empty($et_zj)) api_json_exit(100, '不存在主机用户名'); $api = new bt_api($btipe,$btkeye); $api->siteqt($et_zj['btid'],$et_zj['sqldz'],true); $api->setftpzt($et_zj['ftpid'],$et_zj['user'],'1'); if($DB->query_prepare("update `MN_zj` set `qk` =? where `user`=?", ['true', $user])){ if (function_exists('mnbt_do_action')) { mnbt_do_action('host.unpaused', $et_zj, ['source'=>'api']); } api_json_exit(200, '主机暂停解除成功!'); } else api_json_exit(100, '主机暂停解除成功!但是写入数据库时出现错误!请站长排查!'); }elseif($gn=='tz'){ if(empty($et_zj)) api_json_exit(100, '不存在主机用户名'); $api = new bt_api($btipe,$btkeye); $r_data = $api->delsite($et_zj['btid'],$et_zj['sqldz']); if($r_data['status']){ if($DB->query_prepare("DELETE FROM MN_zj WHERE user=? limit 1", [$user])){ api_lifecycle_log('API删除主机','删除'.$user.'成功','删除成功'); // 级联删除 hosting_shop 插件的资产记录(host_id 关联 MN_zj.id,插件未安装时表不存在、prepare 失败静默返回 false,无影响) $DB->query_prepare("DELETE FROM MN_plugin_hosting_asset WHERE host_id=?", [$et_zj['id']]); if (function_exists('mnbt_do_action')) { mnbt_do_action('host.deleted', $et_zj, ['source'=>'api']); } api_json_exit(200, '主机删除成功!'); } api_lifecycle_log('API删除主机','删除'.$user.'数据库写入失败','删除失败'); api_json_exit(100, '主机删除成功,但是写入数据库失败,请站长排查!'); }else{ api_lifecycle_log('API删除主机','删除'.$user.'失败:'.($r_data['msg'] ?? '未知错误'),'删除失败'); api_json_exit(100, '主机删除失败!因为'.($r_data['msg'] ?? '未知错误')); } }elseif($gn=='czmm'){ if(empty($et_zj)) api_json_exit(100, '不存在主机用户名'); if(!isset($_POST['password']) || $_POST['password']==='')api_json_exit(100, '错误!缺少新密码参数 password!'); $x_up_pass=daddslashes($_POST['password']); $api = new bt_api($btipe,$btkeye); $api->setftppass($et_zj['ftpid'],$user,$x_up_pass); if($DB->query_prepare("update `MN_zj` set `pass` =? where `user`=?", [$x_up_pass, $user]))api_json_exit(200, '主机FTP及控制面板登陆密码重置成功!'); else api_json_exit(100, '主机FTP密码重置成功!但是控制面板登陆密码重置失败,因为数据写入数据库失败!请站长排查'); }elseif($gn == 'zjmode'){ $zjdata=$DB->get_row_prepare("SELECT * FROM MN_zj WHERE user=?", [$user]); if($zjdata == null) api_json_exit(100, '不存在主机用户名'); // zjmode 使用单独查询,同样需要校验主机归属节点;1.81 兼容模式仅放宽跨节点校验 if(!mnbt_api_compat_mode() && (string)$zjdata['ssbt'] !== (string)$bh)api_json_exit(100, '主机不属于该节点'); $hxa_array = json_decode($zjdata['hxa'],true); $hxb_array = json_decode($zjdata['hxb'],true); $llmax_array = json_decode($zjdata['llmax'],true); if(mnbt_api_compat_mode()){ // 1.81 兼容:无条件覆盖三项配额,老对接模块依赖缺参即清零的语义 $hxa_array['max'] = $_POST['websize'] ?? null; $hxb_array['max'] = $_POST['sqlsize'] ?? null; $llmax_array['max'] = $_POST['ll'] ?? null; }else{ // 有传参才覆盖对应配额,三项相互独立,避免缺参导致配额被清零 if(isset($_POST['websize']) && $_POST['websize']!=='') $hxa_array['max'] = $_POST['websize']; if(isset($_POST['sqlsize']) && $_POST['sqlsize']!=='') $hxb_array['max'] = $_POST['sqlsize']; if(isset($_POST['ll']) && $_POST['ll']!=='') $llmax_array['max'] = $_POST['ll']; } if($DB->query_prepare("UPDATE `MN_zj` SET `hxa` = ?, `hxb` = ?, `llmax` = ? WHERE `user` = ?", [json_encode($hxa_array), json_encode($hxb_array), json_encode($llmax_array), $user])) api_json_exit(200, '主机修改成功'); api_json_exit(100, '主机修改失败我也不知道什么问题,请联系开发者'); }elseif($gn == 'start'){ if(empty($et_zj)) api_json_exit(100, '不存在主机用户名'); if($et_zj['qk'] == 'false') api_json_exit(100, '该主机已暂停,请先解除暂停'); $api = new bt_api($btipe, $btkeye); $r_data = $api->siteqt($et_zj['btid'], $et_zj['sqldz'], true); if(($r_data['status'] ?? false) == 1 || ($r_data['status'] ?? false) == '1' || ($r_data['status'] ?? false) === true){ api_lifecycle_log('API站点启动', '启动站点 '.$et_zj['sqldz'].' 成功', '操作成功'); api_json_exit(200, '站点启动成功!'); } api_lifecycle_log('API站点启动', '启动站点 '.$et_zj['sqldz'].' 失败:'.($r_data['msg'] ?? '未知错误'), '操作失败'); api_json_exit(100, '站点启动失败!'.($r_data['msg'] ?? '未知错误')); }elseif($gn == 'stop'){ if(empty($et_zj)) api_json_exit(100, '不存在主机用户名'); $api = new bt_api($btipe, $btkeye); $r_data = $api->siteqt($et_zj['btid'], $et_zj['sqldz'], false); if(($r_data['status'] ?? false) == 1 || ($r_data['status'] ?? false) == '1' || ($r_data['status'] ?? false) === true){ api_lifecycle_log('API站点停止', '停止站点 '.$et_zj['sqldz'].' 成功', '操作成功'); api_json_exit(200, '站点停止成功!'); } api_lifecycle_log('API站点停止', '停止站点 '.$et_zj['sqldz'].' 失败:'.($r_data['msg'] ?? '未知错误'), '操作失败'); api_json_exit(100, '站点停止失败!'.($r_data['msg'] ?? '未知错误')); }elseif($gn == 'ztcx'){ if(empty($et_zj)) api_json_exit(100, '不存在主机用户名'); // 解析配额 JSON(hxa 网站空间 / hxb 数据库空间 / llmax 流量) $hxa_arr = json_decode($et_zj['hxa'], true) ?: []; $hxb_arr = json_decode($et_zj['hxb'], true) ?: []; $ll_arr = json_decode($et_zj['llmax'], true) ?: []; api_json_exit(200, 'ok', [ 'data' => [ 'user' => [ 'username' => $et_zj['user'], 'domain' => $et_zj['sqldz'], 'qk' => $et_zj['qk'], 'datae' => $et_zj['datae'], 'sqluser' => $et_zj['sqluser'], 'btid' => $et_zj['btid'], 'created_at' => $et_zj['data'], ], 'quota' => [ 'web_size_max' => (int)($hxa_arr['max'] ?? 0), 'web_size_used' => (int)($hxa_arr['dq'] ?? 0), 'sql_size_max' => (int)($hxb_arr['max'] ?? 0), 'sql_size_used' => (int)($hxb_arr['dq'] ?? 0), 'flow_max' => (int)($ll_arr['max'] ?? 0), 'flow_used' => (int)($ll_arr['dq'] ?? 0), 'unit' => 'MB', ], 'node' => ['btip' => $cert['btip'], 'ptl' => $cert['ptl']], ], ]); }else{ api_json_exit(100, '此功能不存在!请仔细核对开发文档!'); } ?>