仰望星辰工作室

better-staridc-MNBT

better-staridc-MNBT/ api/api.php 18.4 KB · 313 行 原始文件
Z zfhsh first commit 1 天前
1<?php
2@header('Content-Type: application/json; charset=UTF-8');
3include("../MPHX/common.php");
4include("../MPHX/BL.php");
5include("./api.class.php");
6
7function api_json_exit($code, $msg, $extra = []) {
8 $result = array_merge([
9 'success' => ((int)$code === 200),
10 'code' => $code,
11 'msg' => $msg,
12 ], $extra);
13 exit(json_encode($result, JSON_UNESCAPED_UNICODE));
14}
15
16function api_lifecycle_log($type, $content, $status = '记录') {
17 global $DB, $user, $bh;
18 $logUser = $user ?: '外部API';
19 mnbt_log($logUser, $type, 'API-'.$bh.' '.$content, $status, $DB);
20}
21
22if($conf['apiqk']=='false')api_json_exit(100, '错误!APi已关闭!开启方法:系统后台的系统设置->api密钥处开启即可');
23$gn=$_GET['gn'] ?? '';
24$bh=$_POST['mn_bh'] ?? '';
25$key=$_POST['mn_key'] ?? '';
26$keye=$_POST['mn_keye'] ?? '';
27$mn_vser=$_POST['mn_vs'] ?? 0;
28$user=$_POST['username'] ?? '';
29if($mn_vser<15)api_json_exit(300, '您的版插件本不支持当前MNBT版本!当前MNBT版本:'.intval($WEBQB/100).',插件所支持的版本'.$mn_vser);
30if(empty($gn) || empty($bh) || empty($key) || empty($keye) || empty($user))api_json_exit(100, '错误!表单填写不完整!');
31if($key!=$conf['api']){
32 mnbt_log('外部API','API鉴权','API-'.$bh.' '.$user.' 系统密钥错误','鉴权失败',$DB);
33 api_json_exit(100, '错误!您的密钥与网站APi密钥不匹配!请您仔细核对后再试!');
34}
35$cert=$DB->get_row_prepare("SELECT * FROM MN_bt WHERE btdh=? limit 1", [$bh]);
36$et_zj=$DB->get_row_prepare("SELECT * FROM MN_zj WHERE user=? limit 1", [$user]);
37if($cert=='' || $cert['qk']=='false')api_json_exit(100, '错误!该宝塔不存在或该宝塔已经被关闭');
38$adyjm=$cert['ktmy'].$cert['qmk'];$mdjm=md5($adyjm);
39if($keye!=$mdjm){
40 if(mnbt_api_compat_mode()){
41 // 1.81 兼容:老对接模块按 msg 文案分支,沿用原文案与不带 key 前缀的日志
42 mnbt_log('外部API','API鉴权','API-'.$bh.' '.$user.' 宝塔调用密钥错误','鉴权失败',$DB);
43 api_json_exit(100, '错误!您所传输的宝塔调用密钥与该宝塔的调用密钥不匹配!');
44 }
45 // 不回显正确密钥,避免密钥泄露
46 mnbt_log('外部API','API鉴权','API-'.$bh.' '.$user.' 宝塔调用密钥错误(发送:'.substr($keye,0,8).',btdh='.$bh.')','鉴权失败',$DB);
47 api_json_exit(100, '调用密钥不匹配!');
48}
49$btipe=($cert['ptl']=='true'?'https':'http').'://'.$cert['btip'].':'.$cert['btdk'];
50$btkeye=$cert['btmy'];
51
52// 归属校验:除 kt(新建主机)和 cfif(连接验证)外,所有动作必须校验主机属于该节点,防止跨节点越权操作
53if($gn!='kt' && $gn!='cfif'){
54 if(empty($et_zj))api_json_exit(100, '不存在主机用户名');
55 // 1.81 兼容模式仅放宽跨节点校验(老对接模块传的 username 可能与节点代号对不上);空主机检查两种模式都保留
56 // ssbt/btdh 为 varchar 节点代号,双端强转字符串严格比较,避免 '01' 与 '1' 被宽松比较误判
57 if(!mnbt_api_compat_mode() && (string)$et_zj['ssbt'] !== (string)$bh)api_json_exit(100, '主机不属于该节点');
58}
59
60if($gn=='cfif'){
61 api_json_exit(200, '连接验证成功!');
62}elseif($gn=='kt'){
63 $pass=daddslashes($_POST['password'] ?? '');
64 $flowratemax=json_encode(array('max'=>daddslashes($_POST['sizemax'] ?? 0),'dq'=>0,'statistics'=>false));
65 $datae=($_POST['dqtime'] ?? '0')=='0' ? '0000-00-00' : daddslashes($_POST['dqtime']);
66 $webdx=json_encode(array('max'=>daddslashes($_POST['webdx'] ?? 0),'dq'=>0));
67 $sqldx=json_encode(array('max'=>daddslashes($_POST['sqldx'] ?? 0),'dq'=>0));
68 $ymbds=daddslashes($_POST['ymbds'] ?? 0);
69 // 直接真值判断:已存在则报错。1.81 原表达式 ($et_zj!='' || $et_zj!=false) 恒真是 bug 而非兼容需求,兼容模式刻意不还原
70 if($et_zj){
71 api_lifecycle_log('API开通主机','开通'.$user.'失败:主机已存在','开通失败');
72 api_json_exit(100, '错误!该主机已经存在,请重新开通!');
73 }
74 $api = new bt_api($btipe,$btkeye);
75 // 获取节点默认 PHP 版本(优先节点设置,否则自动检测最新版本)
76 $phpVersion = $cert['mrbts_php'] ?? '';
77 if ($phpVersion === '' || $phpVersion === '00') {
78 $phpList = $api->btapi_listphp();
79 if (is_array($phpList)) {
80 $versions = [];
81 foreach ($phpList as $v) {
82 if (($v['status'] ?? false) && ($v['version'] ?? '') !== '00') {
83 $versions[] = $v['version'];
84 }
85 }
86 if (!empty($versions)) {
87 usort($versions, function($a, $b) { return strcmp($b, $a); });
88 $phpVersion = $versions[0];
89 $DB->query_prepare("UPDATE MN_bt SET mrbts_php=? WHERE btdh=? LIMIT 1", [$phpVersion, $bh]);
90 }
91 }
92 }
93 if ($phpVersion === '' || $phpVersion === '00') {
94 api_lifecycle_log('API开通主机','开通'.$user.'失败:无法获取节点PHP版本','开通失败');
95 api_json_exit(100, '错误!无法获取该节点的 PHP 版本,请先在宝塔面板安装 PHP 或联系管理员设置节点默认 PHP 版本');
96 }
97 if(mb_strlen($user)<6 || mb_strlen($pass)<6){
98 api_lifecycle_log('API开通主机','开通'.$user.'失败:账号或密码过短','开通失败');
99 api_json_exit(100, '错误!账号和密码位数均不能小于6位!');
100 }
101 if($DB->get_row_prepare("SELECT id FROM MN_zj WHERE user=? limit 1", [$user])){
102 api_lifecycle_log('API开通主机','开通'.$user.'失败:账号重复','开通失败');
103 api_json_exit(100, '错误!该账号已存在!请更换账号!');
104 }
105 $hskr=mt_rand(4,10);
106 $rqsj=md5($date.$user);
107 $wjler=substr($rqsj, $hskr , 3);
108 if(mnbt_api_compat_mode()){
109 // 1.81 兼容:站点名按 max(id)+1 序列生成,老对接模块据此猜测站点名
110 $rowe=$DB->get_row_prepare("SELECT id FROM MN_zj WHERE 1 order by id desc limit 1");
111 $id=($rowe['id'] ?? 0)+1;
112 $btserw='mnbt.'.$id.mt_rand(1,999).$wjler;
113 }else{
114 // 站点名不含 id,配合下方取号重试,避免并发下 max(id)+1 重复
115 $btserw='mnbt.'.mt_rand(1,999).$wjler;
116 }
117 $mrwww=$cert['btos']=='1' ? $conf['hxi'].'/'.$btserw : $conf['hxo'].'/'.$btserw;
118 $r_data = $api->webkt($user,$pass,$btserw,'主机','true','true',$phpVersion,$mrwww);
119 $cjqk=$r_data['siteStatus'] ?? false;
120 $zdide=$r_data['siteId'] ?? 0;
121 if($cjqk=='1' || $cjqk=='true' || $cjqk===true){
122 if(($_POST['dqtime'] ?? '0')!='0'){
123 $r_datan = $api->setdqsj($zdide,$datae);
124 $de=$r_datan['status'] ?? false;
125 if(!($de=='1' || $de=='true' || $de===true)){
126 api_lifecycle_log('API开通主机','开通'.$user.'失败:到期时间设置失败','开通失败');
127 api_json_exit(100, '错误!网站创建成功但到期时间设置失败!');
128 }
129 }
130 // 按账号精确获取 FTP/数据库 ID(原为全表拉取,主机数多时会显著变慢)
131 $aedfs = $api->sjid('ftps', $user);
132 $sqlfs = $api->sjid('databases', $user);
133 // 写入主机记录:max(id)+1 在并发下可能重复导致插入失败,失败时重新取号重试几次
134 $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 (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
135 $insert_args = [$bh, $user, $pass, $user, $pass, $date, $datae, 'true', $zdide, $btserw, $aedfs, $ymbds, $webdx, $sqldx, '2', $sqlfs, $flowratemax];
136 $insert_ok = false;
137 if(mnbt_api_compat_mode()){
138 // 1.81 兼容:沿用建站前取的号单次插入,保证站点名与 id 序列一致
139 $insert_ok = (bool)$DB->query_prepare($insert_sql, array_merge([$id], $insert_args));
140 }else{
141 for($tryi = 0; $tryi < 3; $tryi++){
142 $rowe=$DB->get_row_prepare("SELECT id FROM MN_zj WHERE 1 order by id desc limit 1");
143 $id=($rowe['id'] ?? 0)+1;
144 if($DB->query_prepare($insert_sql, array_merge([$id], $insert_args))){
145 $insert_ok = true;
146 break;
147 }
148 }
149 }
150 if($insert_ok){
151 api_lifecycle_log('API开通主机','开通'.$user.'成功,站点'.$btserw,'开通成功');
152 $host_row = $DB->get_row_prepare("SELECT * FROM MN_zj WHERE id=? limit 1", [$id]);
153 if (function_exists('mnbt_do_action')) {
154 mnbt_do_action('host.created', $host_row ?: ['id'=>$id,'user'=>$user,'ssbt'=>$bh,'btid'=>$zdide,'sqldz'=>$btserw], ['source'=>'api']);
155 }
156 api_json_exit(200, '主机开通成功!');
157 } else {
158 $api->delsite($zdide,$btserw);
159 api_lifecycle_log('API开通主机','开通'.$user.'失败:数据库写入失败','开通失败');
160 api_json_exit(100, '错误!网站添加失败(数据写入数据库失败)!请尝试重试或加官Q群询问!');
161 }
162 }else{
163 api_lifecycle_log('API开通主机','开通'.$user.'失败:'.($r_data['msg'] ?? '宝塔创建失败'),'开通失败');
164 api_json_exit(100, '错误!网站创建失败!宝塔返回信息:'.($r_data['msg'] ?? '未知错误'));
165 }
166}elseif($gn=='zt'){
167 if(empty($et_zj)) api_json_exit(100, '不存在主机用户名');
168 $api = new bt_api($btipe,$btkeye);
169 $api->siteqt($et_zj['btid'],$et_zj['sqldz'],false);
170 $api->setftpzt($et_zj['ftpid'],$et_zj['user'],'0');
171 $DB->query_prepare("update `MN_zj` set `qk` =? where `user`=?", ['false', $user]);
172 if (function_exists('mnbt_do_action')) {
173 mnbt_do_action('host.paused', $et_zj, ['source'=>'api']);
174 }
175 api_json_exit(200, '主机暂停成功!');
176}elseif($gn=='xf'){
177 if(empty($et_zj)) api_json_exit(100, '不存在主机用户名');
178 $x_dq_date=($_POST['setdate'] ?? '0')=='0' ? '0000-00-00' : $_POST['setdate'];
179 $old_date=$et_zj['datae'] ?? '';
180 $api = new bt_api($btipe,$btkeye);
181 $r_data = $api->setdqsj($et_zj['btid'],$x_dq_date);
182 // qk 为字符串 'true'/'false',需显式比较;续费后到期日在未来且主机处于暂停状态(qk=='false',含到期暂停)时自动解停
183 // 1.81 兼容:老版对 $et_zj['qk'] 做真值判断('false' 字符串亦为真,故总会解停),按老版原样还原
184 $qk_pf = mnbt_api_compat_mode() ? $et_zj['qk'] : $et_zj['qk']=='false';
185 if(strtotime($date)-strtotime($x_dq_date)<0 && $x_dq_date!='0000-00-00' && $qk_pf){
186 $api->siteqt($et_zj['btid'],$et_zj['sqldz'],true);
187 $api->setftpzt($et_zj['ftpid'],$et_zj['user'],'1');
188 }
189 if($DB->query_prepare("update `MN_zj` set `datae` =? where `user`=?", [$x_dq_date, $user])){
190 api_lifecycle_log('API续费主机','续费'.$user.' '.$old_date.'=>'.$x_dq_date,'续费成功');
191 $host_row = $DB->get_row_prepare("SELECT * FROM MN_zj WHERE user=? limit 1", [$user]);
192 if (function_exists('mnbt_do_action')) {
193 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]);
194 }
195 api_json_exit(200, '主机续费成功!');
196 }
197 api_lifecycle_log('API续费主机','续费'.$user.'数据库写入失败','续费失败');
198 api_json_exit(100, '主机续费失败,数据库写入失败!');
199}elseif($gn=='jc'){
200 if(empty($et_zj)) api_json_exit(100, '不存在主机用户名');
201 $api = new bt_api($btipe,$btkeye);
202 $api->siteqt($et_zj['btid'],$et_zj['sqldz'],true);
203 $api->setftpzt($et_zj['ftpid'],$et_zj['user'],'1');
204 if($DB->query_prepare("update `MN_zj` set `qk` =? where `user`=?", ['true', $user])){
205 if (function_exists('mnbt_do_action')) {
206 mnbt_do_action('host.unpaused', $et_zj, ['source'=>'api']);
207 }
208 api_json_exit(200, '主机暂停解除成功!');
209 }
210 else api_json_exit(100, '主机暂停解除成功!但是写入数据库时出现错误!请站长排查!');
211}elseif($gn=='tz'){
212 if(empty($et_zj)) api_json_exit(100, '不存在主机用户名');
213 $api = new bt_api($btipe,$btkeye);
214 $r_data = $api->delsite($et_zj['btid'],$et_zj['sqldz']);
215 if($r_data['status']){
216 if($DB->query_prepare("DELETE FROM MN_zj WHERE user=? limit 1", [$user])){
217 api_lifecycle_log('API删除主机','删除'.$user.'成功','删除成功');
218 // 级联删除 hosting_shop 插件的资产记录(host_id 关联 MN_zj.id,插件未安装时表不存在、prepare 失败静默返回 false,无影响)
219 $DB->query_prepare("DELETE FROM MN_plugin_hosting_asset WHERE host_id=?", [$et_zj['id']]);
220 if (function_exists('mnbt_do_action')) {
221 mnbt_do_action('host.deleted', $et_zj, ['source'=>'api']);
222 }
223 api_json_exit(200, '主机删除成功!');
224 }
225 api_lifecycle_log('API删除主机','删除'.$user.'数据库写入失败','删除失败');
226 api_json_exit(100, '主机删除成功,但是写入数据库失败,请站长排查!');
227 }else{
228 api_lifecycle_log('API删除主机','删除'.$user.'失败:'.($r_data['msg'] ?? '未知错误'),'删除失败');
229 api_json_exit(100, '主机删除失败!因为'.($r_data['msg'] ?? '未知错误'));
230 }
231}elseif($gn=='czmm'){
232 if(empty($et_zj)) api_json_exit(100, '不存在主机用户名');
233 if(!isset($_POST['password']) || $_POST['password']==='')api_json_exit(100, '错误!缺少新密码参数 password!');
234 $x_up_pass=daddslashes($_POST['password']);
235 $api = new bt_api($btipe,$btkeye);
236 $api->setftppass($et_zj['ftpid'],$user,$x_up_pass);
237 if($DB->query_prepare("update `MN_zj` set `pass` =? where `user`=?", [$x_up_pass, $user]))api_json_exit(200, '主机FTP及控制面板登陆密码重置成功!');
238 else api_json_exit(100, '主机FTP密码重置成功!但是控制面板登陆密码重置失败,因为数据写入数据库失败!请站长排查');
239}elseif($gn == 'zjmode'){
240 $zjdata=$DB->get_row_prepare("SELECT * FROM MN_zj WHERE user=?", [$user]);
241 if($zjdata == null) api_json_exit(100, '不存在主机用户名');
242 // zjmode 使用单独查询,同样需要校验主机归属节点;1.81 兼容模式仅放宽跨节点校验
243 if(!mnbt_api_compat_mode() && (string)$zjdata['ssbt'] !== (string)$bh)api_json_exit(100, '主机不属于该节点');
244 $hxa_array = json_decode($zjdata['hxa'],true);
245 $hxb_array = json_decode($zjdata['hxb'],true);
246 $llmax_array = json_decode($zjdata['llmax'],true);
247 if(mnbt_api_compat_mode()){
248 // 1.81 兼容:无条件覆盖三项配额,老对接模块依赖缺参即清零的语义
249 $hxa_array['max'] = $_POST['websize'] ?? null;
250 $hxb_array['max'] = $_POST['sqlsize'] ?? null;
251 $llmax_array['max'] = $_POST['ll'] ?? null;
252 }else{
253 // 有传参才覆盖对应配额,三项相互独立,避免缺参导致配额被清零
254 if(isset($_POST['websize']) && $_POST['websize']!=='') $hxa_array['max'] = $_POST['websize'];
255 if(isset($_POST['sqlsize']) && $_POST['sqlsize']!=='') $hxb_array['max'] = $_POST['sqlsize'];
256 if(isset($_POST['ll']) && $_POST['ll']!=='') $llmax_array['max'] = $_POST['ll'];
257 }
258 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, '主机修改成功');
259 api_json_exit(100, '主机修改失败我也不知道什么问题,请联系开发者');
260}elseif($gn == 'start'){
261 if(empty($et_zj)) api_json_exit(100, '不存在主机用户名');
262 if($et_zj['qk'] == 'false') api_json_exit(100, '该主机已暂停,请先解除暂停');
263 $api = new bt_api($btipe, $btkeye);
264 $r_data = $api->siteqt($et_zj['btid'], $et_zj['sqldz'], true);
265 if(($r_data['status'] ?? false) == 1 || ($r_data['status'] ?? false) == '1' || ($r_data['status'] ?? false) === true){
266 api_lifecycle_log('API站点启动', '启动站点 '.$et_zj['sqldz'].' 成功', '操作成功');
267 api_json_exit(200, '站点启动成功!');
268 }
269 api_lifecycle_log('API站点启动', '启动站点 '.$et_zj['sqldz'].' 失败:'.($r_data['msg'] ?? '未知错误'), '操作失败');
270 api_json_exit(100, '站点启动失败!'.($r_data['msg'] ?? '未知错误'));
271}elseif($gn == 'stop'){
272 if(empty($et_zj)) api_json_exit(100, '不存在主机用户名');
273 $api = new bt_api($btipe, $btkeye);
274 $r_data = $api->siteqt($et_zj['btid'], $et_zj['sqldz'], false);
275 if(($r_data['status'] ?? false) == 1 || ($r_data['status'] ?? false) == '1' || ($r_data['status'] ?? false) === true){
276 api_lifecycle_log('API站点停止', '停止站点 '.$et_zj['sqldz'].' 成功', '操作成功');
277 api_json_exit(200, '站点停止成功!');
278 }
279 api_lifecycle_log('API站点停止', '停止站点 '.$et_zj['sqldz'].' 失败:'.($r_data['msg'] ?? '未知错误'), '操作失败');
280 api_json_exit(100, '站点停止失败!'.($r_data['msg'] ?? '未知错误'));
281}elseif($gn == 'ztcx'){
282 if(empty($et_zj)) api_json_exit(100, '不存在主机用户名');
283 // 解析配额 JSON(hxa 网站空间 / hxb 数据库空间 / llmax 流量)
284 $hxa_arr = json_decode($et_zj['hxa'], true) ?: [];
285 $hxb_arr = json_decode($et_zj['hxb'], true) ?: [];
286 $ll_arr = json_decode($et_zj['llmax'], true) ?: [];
287 api_json_exit(200, 'ok', [
288 'data' => [
289 'user' => [
290 'username' => $et_zj['user'],
291 'domain' => $et_zj['sqldz'],
292 'qk' => $et_zj['qk'],
293 'datae' => $et_zj['datae'],
294 'sqluser' => $et_zj['sqluser'],
295 'btid' => $et_zj['btid'],
296 'created_at' => $et_zj['data'],
297 ],
298 'quota' => [
299 'web_size_max' => (int)($hxa_arr['max'] ?? 0),
300 'web_size_used' => (int)($hxa_arr['dq'] ?? 0),
301 'sql_size_max' => (int)($hxb_arr['max'] ?? 0),
302 'sql_size_used' => (int)($hxb_arr['dq'] ?? 0),
303 'flow_max' => (int)($ll_arr['max'] ?? 0),
304 'flow_used' => (int)($ll_arr['dq'] ?? 0),
305 'unit' => 'MB',
306 ],
307 'node' => ['btip' => $cert['btip'], 'ptl' => $cert['ptl']],
308 ],
309 ]);
310}else{
311 api_json_exit(100, '此功能不存在!请仔细核对开发文档!');
312}
313?>