feat(api): 支持 1.81 与 1.83+ 外部 API 行为切换
8 个文件变更
+130
-20
13172193298@163.com
| •MPHX/function.php | +7 -0 |
| •admin/api/setting.php | +8 -0 |
| •api/api.php | +46 -17 |
| •docs/api/external.md | +23 -0 |
| •install/install.api.php | +9 -0 |
| •install/install.sql | +3 -2 |
| •templates/default/admin/set.php | +29 -1 |
| •update/update_v185_api_compat.sql | +5 -0 |
变更内容
diff --git a/MPHX/function.php b/MPHX/function.php
index 46f0a44..c7bd1e2 100644
--- a/MPHX/function.php
+++ b/MPHX/function.php
@@ -286,6 +286,13 @@ function mnbt_log($user = '系统', $type = '系统日志', $content = '内容',
return logjl($user, $type, $content, $status, $db);
}
+function mnbt_api_compat_mode()
+{
+ global $conf;
+ // api_compat='1' 时对外主机 API 还原 1.81 老协议行为,供未升级对接模块的客户过渡使用
+ return isset($conf['api_compat']) && (string)$conf['api_compat'] === '1';
+}
+
function send_post($url, $post_data) {
if(!is_array($post_data)) $post_data=[];
$postdata = http_build_query($post_data);
diff --git a/admin/api/setting.php b/admin/api/setting.php
index 88c3188..269e6f6 100644
--- a/admin/api/setting.php
+++ b/admin/api/setting.php
@@ -19,6 +19,14 @@ if($egn=='setapi') {
if($DB->query_prepare($sql,[$apikey,$apiqk,$lin,$win,$siteid]))json_exit('修改成功'); else json_exit('修改失败'.$DB->error());
return;
}
+if($egn=='setapicompat') {
+ // 对外 API 协议模式开关:0=新版协议(1.83+ 严格),1=兼容 1.81 对接模块
+ $apicompat = (daddslashes($_POST['api_compat'] ?? '0') === '1') ? '1' : '0';
+ logjl($user,'API设置','对外API协议模式切换为'.($apicompat==='1'?'兼容1.81':'新版1.83+'),'修改成功',$DB);
+ $sql="update `MN_config` set `api_compat` =? where `id`=?";
+ if($DB->query_prepare($sql,[$apicompat,$siteid]))json_exit('修改成功'); else json_exit('修改失败'.$DB->error());
+ return;
+}
if($egn=='setkzmb') {
$name=daddslashes($_POST['name']);
$ftp=daddslashes($_POST['ftp']);
diff --git a/api/api.php b/api/api.php
index 1722018..12548c4 100644
--- a/api/api.php
+++ b/api/api.php
@@ -37,6 +37,11 @@ $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, '调用密钥不匹配!');
@@ -47,8 +52,9 @@ $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((string)$et_zj['ssbt'] !== (string)$bh)api_json_exit(100, '主机不属于该节点');
+ if(!mnbt_api_compat_mode() && (string)$et_zj['ssbt'] !== (string)$bh)api_json_exit(100, '主机不属于该节点');
}
if($gn=='cfif'){
@@ -60,7 +66,7 @@ if($gn=='cfif'){
$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, '错误!该主机已经存在,请重新开通!');
@@ -96,11 +102,18 @@ if($gn=='cfif'){
api_lifecycle_log('API开通主机','开通'.$user.'失败:账号重复','开通失败');
api_json_exit(100, '错误!该账号已存在!请更换账号!');
}
- // max(id)+1 并发下可能重复导致插入失败,失败时重新取号重试几次
$hskr=mt_rand(4,10);
$rqsj=md5($date.$user);
$wjler=substr($rqsj, $hskr , 3);
- $btserw='mnbt.'.mt_rand(1,999).$wjler;
+ 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;
@@ -120,13 +133,20 @@ if($gn=='cfif'){
foreach(($r_datn['data'] ?? []) as $val){ if($val['name']===$user){ $aedfs=$val['id']; break; } }
foreach(($r_datp['data'] ?? []) as $val){ if($val['name']===$user){ $sqlfs=$val['id']; break; } }
// 写入主机记录: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;
- 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 INTO `MN_zj` (`id`, `ssbt`, `user`, `pass`, `sqluser`, `sqlpass`, `data`, `datae`, `qk`, `btid`, `sqldz`, `ftpid`, `ymbds`, `hxa`, `hxb`, `hxc`, `hxd`, `llmax`) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", [$id, $bh, $user, $pass, $user, $pass, $date, $datae, 'true', $zdide, $btserw, $aedfs, $ymbds, $webdx, $sqldx, '2', $sqlfs, $flowratemax])){
- $insert_ok = true;
- break;
+ 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){
@@ -162,7 +182,9 @@ if($gn=='cfif'){
$api = new bt_api($btipe,$btkeye);
$r_data = $api->setdqsj($et_zj['btid'],$x_dq_date);
// qk 为字符串 'true'/'false',需显式比较;续费后到期日在未来且主机处于暂停状态(qk=='false',含到期暂停)时自动解停
- if(strtotime($date)-strtotime($x_dq_date)<0 && $x_dq_date!='0000-00-00' && $et_zj['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');
}
@@ -219,15 +241,22 @@ if($gn=='cfif'){
}elseif($gn == 'zjmode'){
$zjdata=$DB->get_row_prepare("SELECT * FROM MN_zj WHERE user=?", [$user]);
if($zjdata == null) api_json_exit(100, '不存在主机用户名');
- // zjmode 使用单独查询,同样需要校验主机归属节点
- if((string)$zjdata['ssbt'] !== (string)$bh)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(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(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'){
diff --git a/docs/api/external.md b/docs/api/external.md
index df28356..a9e32f4 100644
--- a/docs/api/external.md
+++ b/docs/api/external.md
@@ -69,6 +69,29 @@ mn_bh=1&mn_key=YOUR_API_KEY&mn_keye=MD5_OF_BT_KEY&mn_vs=15&username=testuser&pas
- 自动检测节点 PHP 版本(优先使用 `MN_bt.mrbts_php`,否则取最新已安装版本)
- 自动触发 `host.created` 钩子(插件可监听)
+### 协议兼容开关(`MN_config.api_compat`)
+
+V1.83+ 在不变的传输包络(POST 表单 + `{success,code,msg}` 响应)之上加入了若干**行为约束**,仍在使用 1.81 版对接模块(`wp_seller_plugin`、魔方财务 `mnbthost` server module)的客户升级 MNBT 后可能被拒。为此提供开关:
+
+- 取值:`0` = 新版协议(1.83+ 严格,默认);`1` = 兼容 1.81 对接模块
+- 后台切换:系统设置 → API 设置 → 「对外 API 协议模式」
+- 代码判断:`api/api.php` 内统一走 `mnbt_api_compat_mode()`(`MPHX/function.php`)读取该配置
+
+两种模式的行为差异(仅 `api/api.php`):
+
+| 差异点 | 新版协议(api_compat=0) | 兼容 1.81(api_compat=1) |
+|--------|--------------------------|---------------------------|
+| 跨节点归属校验(除 `kt`/`cfif` 外,含 `zjmode`) | `MN_zj.ssbt` 与 `mn_bh` 严格比较,不一致返回 `主机不属于该节点` | 跳过该校验(老模块传的 `username` 可能与节点代号对不上) |
+| 空主机检查 | `不存在主机用户名` | 保留不变(1.81 靠弱类型访问空行是 bug,不还原) |
+| `kt` 站点名 | `mnbt.<随机>.<后缀>`,写库失败重取号重试 | `mnbt.<max(id)+1><随机>.<后缀>`,单次取号插入(老模块按 id 序列猜站点名) |
+| `zjmode` 配额 | 仅在传入 `websize`/`sqlsize`/`ll` 非空时覆盖 | 无条件覆盖,缺参即清零(老模块依赖该语义) |
+| `xf` 续费自动解停 | 仅当 `qk=='false'`(真暂停)才解停 | 对 `qk` 做真值判断(1.81 原行为:总会解停) |
+| 宝塔调用密钥校验失败 | `调用密钥不匹配!`,日志带 key 前缀 | `错误!您所传输的宝塔调用密钥与该宝塔的调用密钥不匹配!`,日志不带前缀(老模块按 msg 文案分支) |
+| `kt` 主机存在判断 | `if($et_zj)` | 同左,刻意不还原 1.81 的恒真表达式(那是 bug 不是兼容需求) |
+| `tz` 删除主机 | 级联删除 `MN_plugin_hosting_asset` | 同左,保留不动 |
+
+> **提示:** 兼容模式只是过渡期方案,会放宽跨节点归属等安全约束。建议尽快将对接模块升级到适配 1.83+ 协议的版本,然后切回「新版协议」。
+
---
## 4.2 MNBT 节点 API(api/node.php)
diff --git a/install/install.api.php b/install/install.api.php
index 053cc94..23903db 100644
--- a/install/install.api.php
+++ b/install/install.api.php
@@ -108,6 +108,8 @@ function mnbt_upgrade_columns($actual_config, $actual_zj, $actual_bt, $actual_dp
'home_footer' => "ALTER TABLE `{$actual_config}` ADD `home_footer` text NOT NULL",
'home_show_notice' => "ALTER TABLE `{$actual_config}` ADD `home_show_notice` varchar(10) NOT NULL DEFAULT 'true'",
'home_show_plans' => "ALTER TABLE `{$actual_config}` ADD `home_show_plans` varchar(10) NOT NULL DEFAULT 'true'",
+ // V1.85 对外 API 协议模式开关(0=1.83+ 严格,1=1.81 兼容)
+ 'api_compat' => "ALTER TABLE `{$actual_config}` ADD `api_compat` varchar(10) NOT NULL DEFAULT '0'",
),
$actual_zj => array(
'backup' => "ALTER TABLE `{$actual_zj}` ADD `backup` VARCHAR(50) NOT NULL DEFAULT '{\"max\":\"3\",\"dq\":0}'",
@@ -313,6 +315,13 @@ switch ($action) {
$result['need_upgrade'] = true;
$result['missing_columns'][] = 'MN_config.home_enable';
}
+
+ // 检查 V1.85 新增字段(对外 API 协议模式开关)
+ $col_api_compat = DB::get_row("SHOW COLUMNS FROM `MN_config` LIKE 'api_compat'");
+ if (!$col_api_compat) {
+ $result['need_upgrade'] = true;
+ $result['missing_columns'][] = 'MN_config.api_compat';
+ }
}
echo Res(1, '升级检测完成', $result);
diff --git a/install/install.sql b/install/install.sql
index 3538e6c..6a6f33f 100644
--- a/install/install.sql
+++ b/install/install.sql
@@ -38,10 +38,11 @@ CREATE TABLE `MN_config` (
`home_footer` text NOT NULL, -- V1.84: 主页底部版权(空则回退系统版权)
`home_show_notice` varchar(10) NOT NULL DEFAULT 'true', -- V1.84: 显示公告区
`home_show_plans` varchar(10) NOT NULL DEFAULT 'true', -- V1.84: 显示套餐区
+ `api_compat` varchar(10) NOT NULL DEFAULT '0', -- V1.85: 对外 API 协议模式(0=1.83+ 严格,1=1.81 兼容)
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-INSERT INTO `MN_config`(`id`, `user`, `pwd`, `gg`, `name`, `yzm`,`yzme`,`wzqk`,`auther`,`kzmbqk`, `apiqk`,`api`,`qqh`,`date`,`hxw`, `hxe`, `hxr`, `hxt`, `hxy`, `hxu`,`hxi`,`hxo`,`hxp`,`hxa`, `hxs`,`hxd`, `pay_methods`, `home_enable`, `home_theme`, `home_theme_settings`, `home_title`, `home_hero`, `home_primary`, `home_logo`, `home_favicon`, `home_footer`, `home_show_notice`, `home_show_plans`) VALUES
-('1', 'admin', '123456', '', '', 'true', 'false', '', '', 'true', '', '', '', '', 'mnftp', '', '', '', '', '56', '/www/wwwroot', 'D:/wwwroot', "<a href='./'>Copyright ©梦奈云 2023</a>", '', '', '', '', 'true', '', '', '', '', '#4f46e5', '', '', '', 'true', 'true');
+INSERT INTO `MN_config`(`id`, `user`, `pwd`, `gg`, `name`, `yzm`,`yzme`,`wzqk`,`auther`,`kzmbqk`, `apiqk`,`api`,`qqh`,`date`,`hxw`, `hxe`, `hxr`, `hxt`, `hxy`, `hxu`,`hxi`,`hxo`,`hxp`,`hxa`, `hxs`,`hxd`, `pay_methods`, `home_enable`, `home_theme`, `home_theme_settings`, `home_title`, `home_hero`, `home_primary`, `home_logo`, `home_favicon`, `home_footer`, `home_show_notice`, `home_show_plans`, `api_compat`) VALUES
+('1', 'admin', '123456', '', '', 'true', 'false', '', '', 'true', '', '', '', '', 'mnftp', '', '', '', '', '56', '/www/wwwroot', 'D:/wwwroot', "<a href='./'>Copyright ©梦奈云 2023</a>", '', '', '', '', 'true', '', '', '', '', '#4f46e5', '', '', '', 'true', 'true', '0');
diff --git a/templates/default/admin/set.php b/templates/default/admin/set.php
index 6456f18..95dc3c7 100644
--- a/templates/default/admin/set.php
+++ b/templates/default/admin/set.php
@@ -93,14 +93,42 @@ if ($set == 'wz') {
</div>
</div>
</div>
+ <div class="mn-set-field">
+ <label for="apicompat">对外 API 协议模式</label>
+ <select class="form-control" id="apicompat" name="apicompat" size="1">
+ <option value="0" <?php if ((string)($conf['api_compat'] ?? '0') !== '1') echo 'selected'; ?>>新版协议(1.83+,默认)</option>
+ <option value="1" <?php if ((string)($conf['api_compat'] ?? '0') === '1') echo 'selected'; ?>>兼容 1.81 对接模块</option>
+ </select>
+ <small>兼容模式仅还原 1.81 老协议的行为约束(跨节点校验、站点名规则等),供未升级对接模块的老客户过渡使用。</small>
+ </div>
<div class="mn-set-actions">
<button class="btn btn-primary btn-block" type="button" onclick="setapi()"><i class="mdi mdi-content-save-outline"></i> 保存修改</button>
+ <button class="btn btn-outline-secondary btn-block" type="button" onclick="setapicompat()"><i class="mdi mdi-swap-vertical"></i> 保存协议模式</button>
</div>
<div class="mn-set-note">
- <b>注意:</b>建站目录请勿随意修改,已开通主机可能受影响。API 密钥修改后,监控 URL 与外部对接均需同步更新。默认 PHP 版本需在宝塔软件商店中已安装。
+ <b>注意:</b>建站目录请勿随意修改,已开通主机可能受影响。API 密钥修改后,监控 URL 与外部对接均需同步更新。默认 PHP 版本需在宝塔软件商店中已安装。兼容模式只是过渡期方案,建议尽快升级对接模块(WordPress 销售插件 / 魔方财务 server module)后切回新版协议。
</div>
</div>
</div>
+<script>
+// 协议模式独立提交:xtset.js 的 setapi() 不感知该字段,为避免公共 JS 扩散改动单独走 setapicompat
+function setapicompat() {
+ msloading('正在修改中!请稍后...','text-info','text-info');
+ let data = {};
+ data['gn'] = 'setapicompat';
+ data['api_compat'] = document.getElementById('apicompat').value;
+ $.post('./ajax.php', data, function (date) {
+ var jsoe = JSON.parse(date);
+ var qk = jsoe.code;
+ if (qk == '修改成功') {
+ msalert(1, '协议模式已保存!', 2000);
+ } else {
+ msalert(4, qk, 2000);
+ }
+ msloadingde();
+ });
+}
+</script>
<?php } elseif ($set == 'kzmb') { ?>
<div class="mn-set-card">
diff --git a/update/update_v185_api_compat.sql b/update/update_v185_api_compat.sql
new file mode 100644
index 0000000..116e646
--- /dev/null
+++ b/update/update_v185_api_compat.sql
@@ -0,0 +1,5 @@
+-- MNBT V1.85 对外 API 协议兼容开关增量 SQL
+-- 为 MN_config 追加 api_compat 字段(0=1.83+ 严格协议,1=兼容 1.81 对接模块)
+-- 注意:在线更新的执行器按分号逐句跑且忽略单句报错,所以这里用裸 ALTER,
+-- 不要写成 DELIMITER/存储过程(那样会被拆碎),列已存在时报错会被安全跳过。
+ALTER TABLE `MN_config` ADD `api_compat` varchar(10) NOT NULL DEFAULT '0';