better-staridc-MNBT
1<?php
2/**
3 * MNBT API 客户端(适配器实现)。
4 *
5 * 封装 MNBT `api/api.php` 的鉴权、请求、重试与错误归一化。
6 * 鉴权参数:mn_bh(节点代号)/ mn_key(系统密钥)/ mn_keye(调用密钥 md5(ktmy.qmk))/ mn_vs(版本号)。
7 *
8 * @package MnbtWp
9 */
10
11namespace MnbtWp\Mnbt;
12
13use MnbtWp\Logger;
14
15if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17}
18
19/**
20 * MNBT API 客户端。
21 */
22class Client implements HostProviderInterface {
23
24 /**
25 * 节点配置。
26 *
27 * @var array
28 */
29 private $config = array();
30
31 /**
32 * 构造。
33 *
34 * @param array $config 节点配置(api_url/btdh/mn_key/mn_keye/mn_vs/provider_id)。
35 */
36 public function __construct( array $config ) {
37 $this->config = wp_parse_args(
38 $config,
39 array(
40 'api_url' => '',
41 'btdh' => '',
42 'mn_key' => '',
43 'mn_keye' => '',
44 'mn_vs' => 15,
45 'provider_id' => 0,
46 )
47 );
48 }
49
50 /**
51 * 拼接 API 地址。
52 *
53 * @return string
54 */
55 private function url() {
56 return untrailingslashit( $this->config['api_url'] ) . '/api/api.php';
57 }
58
59 /**
60 * 连接测试。
61 *
62 * @return array
63 */
64 public function testConnection() {
65 return $this->request( 'cfif', array( 'username' => 'mnbtwp_test' ) );
66 }
67
68 /**
69 * 开通主机。
70 *
71 * @param array $params 开通参数。
72 * @return array
73 */
74 public function createHost( array $params ) {
75 $params = wp_parse_args(
76 $params,
77 array(
78 'password' => '',
79 'sizemax' => 0,
80 'dqtime' => 0,
81 'webdx' => 0,
82 'sqldx' => 0,
83 'ymbds' => 0,
84 )
85 );
86 return $this->request( 'kt', $params );
87 }
88
89 /**
90 * 暂停主机。
91 *
92 * @param string $username 主机用户名。
93 * @return array
94 */
95 public function suspendHost( $username ) {
96 return $this->request( 'zt', array( 'username' => $username ) );
97 }
98
99 /**
100 * 恢复主机。
101 *
102 * @param string $username 主机用户名。
103 * @return array
104 */
105 public function resumeHost( $username ) {
106 return $this->request( 'jc', array( 'username' => $username ) );
107 }
108
109 /**
110 * 删除主机。
111 *
112 * @param string $username 主机用户名。
113 * @return array
114 */
115 public function deleteHost( $username ) {
116 return $this->request( 'tz', array( 'username' => $username ) );
117 }
118
119 /**
120 * 续费。
121 *
122 * @param string $username 主机用户名。
123 * @param string $expire_date 到期日期 Y-m-d。
124 * @return array
125 */
126 public function renewHost( $username, $expire_date ) {
127 return $this->request(
128 'xf',
129 array(
130 'username' => $username,
131 'setdate' => $expire_date,
132 )
133 );
134 }
135
136 /**
137 * 重置密码。
138 *
139 * @param string $username 主机用户名。
140 * @param string $password 新密码。
141 * @return array
142 */
143 public function changePassword( $username, $password ) {
144 return $this->request(
145 'czmm',
146 array(
147 'username' => $username,
148 'password' => $password,
149 )
150 );
151 }
152
153 /**
154 * 升降级。
155 *
156 * @param string $username 主机用户名。
157 * @param array $quota 配额(websize/sqlsize/ll,MB)。
158 * @return array
159 */
160 public function changePackage( $username, array $quota ) {
161 $quota = wp_parse_args(
162 $quota,
163 array(
164 'websize' => 0,
165 'sqlsize' => 0,
166 'll' => 0,
167 )
168 );
169 return $this->request(
170 'zjmode',
171 array(
172 'username' => $username,
173 'websize' => $quota['websize'],
174 'sqlsize' => $quota['sqlsize'],
175 'll' => $quota['ll'],
176 )
177 );
178 }
179
180 /**
181 * 启动站点。
182 *
183 * @param string $username 主机用户名。
184 * @return array
185 */
186 public function startSite( $username ) {
187 return $this->request( 'start', array( 'username' => $username ) );
188 }
189
190 /**
191 * 停止站点。
192 *
193 * @param string $username 主机用户名。
194 * @return array
195 */
196 public function stopSite( $username ) {
197 return $this->request( 'stop', array( 'username' => $username ) );
198 }
199
200 /**
201 * 状态与配额查询。
202 *
203 * @param string $username 主机用户名。
204 * @return array
205 */
206 public function getHostStatus( $username ) {
207 return $this->request( 'ztcx', array( 'username' => $username ) );
208 }
209
210 /**
211 * 发起 MNBT API 请求。
212 *
213 * 网络层失败(超时/连接失败)自动重试 2 次(1s/2s 退避);
214 * 业务失败(code!=200)不重试,抛出异常。
215 *
216 * @param string $gn API 动作。
217 * @param array $params 业务参数。
218 * @return array ['ok'=>true, 'msg'=>string, 'data'=>array]
219 *
220 * @throws Exception 请求失败时抛出。
221 */
222 private function request( $gn, array $params ) {
223 $body = array_merge(
224 array(
225 'mn_bh' => $this->config['btdh'],
226 'mn_key' => $this->config['mn_key'],
227 'mn_keye' => $this->config['mn_keye'],
228 'mn_vs' => (int) $this->config['mn_vs'],
229 ),
230 $params
231 );
232 if ( empty( $body['username'] ) ) {
233 $body['username'] = 'mnbtwp_test';
234 }
235
236 $start = microtime( true );
237 $response = null;
238 $last_error = '';
239
240 // 网络层失败重试(最多 3 次尝试)。
241 for ( $attempt = 0; $attempt < 3; $attempt++ ) {
242 if ( $attempt > 0 ) {
243 usleep( $attempt * 1000000 ); // 1s / 2s 退避。
244 }
245 $response = wp_remote_post(
246 $this->url(),
247 array(
248 'timeout' => 15,
249 'body' => $body,
250 'sslverify' => true,
251 )
252 );
253 if ( ! is_wp_error( $response ) ) {
254 break;
255 }
256 $last_error = $response->get_error_message();
257 }
258
259 $duration = microtime( true ) - $start;
260
261 if ( is_wp_error( $response ) ) {
262 Logger::api( (int) $this->config['provider_id'], $gn, $body, 0, '网络错误:' . $last_error, $duration );
263 throw ( new Exception( '网络错误:' . $last_error ) )->set_context(
264 array( 'gn' => $gn, 'provider_id' => $this->config['provider_id'] )
265 );
266 }
267
268 $code = wp_remote_retrieve_response_code( $response );
269 if ( 200 !== $code ) {
270 Logger::api( (int) $this->config['provider_id'], $gn, $body, 0, 'HTTP ' . $code, $duration );
271 throw ( new Exception( 'MNBT 接口返回 HTTP ' . $code ) )->set_context(
272 array( 'gn' => $gn, 'provider_id' => $this->config['provider_id'], 'http_code' => $code )
273 );
274 }
275
276 $raw = wp_remote_retrieve_body( $response );
277 $data = json_decode( $raw, true );
278 if ( ! is_array( $data ) ) {
279 Logger::api( (int) $this->config['provider_id'], $gn, $body, 0, '响应解析失败', $duration );
280 throw ( new Exception( 'MNBT 响应解析失败' ) )->set_context(
281 array( 'gn' => $gn, 'provider_id' => $this->config['provider_id'] )
282 );
283 }
284
285 $ok = isset( $data['code'] ) && 200 === (int) $data['code'];
286 $msg = isset( $data['msg'] ) ? (string) $data['msg'] : 'ok';
287
288 Logger::api( (int) $this->config['provider_id'], $gn, $body, $ok ? 200 : 100, $msg, $duration );
289
290 if ( ! $ok ) {
291 throw ( new Exception( $msg ) )->set_context(
292 array( 'gn' => $gn, 'provider_id' => $this->config['provider_id'], 'code' => isset( $data['code'] ) ? $data['code'] : null )
293 );
294 }
295
296 return array(
297 'ok' => true,
298 'msg' => $msg,
299 'data' => isset( $data['data'] ) && is_array( $data['data'] ) ? $data['data'] : array(),
300 );
301 }
302}