作者 karlet

feat:合并tanli的修改

1 <?php 1 <?php
2 -  
3 -namespace jiaoyin; 2 +namespace Jiaoyin;
4 3
5 class Auth 4 class Auth
6 { 5 {
@@ -82,4 +81,5 @@ class Auth @@ -82,4 +81,5 @@ class Auth
82 return $ustr; 81 return $ustr;
83 } 82 }
84 } 83 }
  84 +
85 } 85 }
1 <?php 1 <?php
2 -  
3 -namespace jiaoyin; 2 +namespace Jiaoyin;
4 3
5 /* 4 /*
6 binance合约接口 5 binance合约接口
7 */ 6 */
8 -  
9 class BinanceFutures 7 class BinanceFutures
10 { 8 {
11 static private string $host = 'https://fapi.binance.com'; 9 static private string $host = 'https://fapi.binance.com';
12 private string $apikey = ''; 10 private string $apikey = '';
13 private string $secret = ''; 11 private string $secret = '';
14 12
15 - public function __construct($apikey = '', $secret = '', $host = '') 13 + public function __construct($apikey='', $secret='',$host='')
16 { 14 {
17 $this->apikey = $apikey; 15 $this->apikey = $apikey;
18 $this->secret = $secret; 16 $this->secret = $secret;
19 - if (!empty($host)) { 17 + if(!empty($host)){
20 self::$host = $host; 18 self::$host = $host;
21 } 19 }
22 } 20 }
@@ -44,9 +42,9 @@ class BinanceFutures @@ -44,9 +42,9 @@ class BinanceFutures
44 42
45 public function requestAccount($method, $path, $param) 43 public function requestAccount($method, $path, $param)
46 { 44 {
47 - if (empty($this->apikey) || empty($this->secret)) { 45 + if(empty($this->apikey) || empty($this->secret)){
48 output('api 或 secret 为空'); 46 output('api 或 secret 为空');
49 - return ['code' => -1, 'msg' => 'apikey or secret is empty']; 47 + return ['code'=>-1,'msg'=>'apikey or secret is empty'];
50 } 48 }
51 $url = $this->createUrl($path); 49 $url = $this->createUrl($path);
52 $param['timestamp'] = getMicrotime(); 50 $param['timestamp'] = getMicrotime();
@@ -248,3 +246,5 @@ class BinanceFutures @@ -248,3 +246,5 @@ class BinanceFutures
248 return self::requestMarket($method, $path, $param); 246 return self::requestMarket($method, $path, $param);
249 } 247 }
250 } 248 }
  249 +
  250 +?>
1 <?php 1 <?php
2 -  
3 -namespace jiaoyin; 2 +namespace Jiaoyin;
4 3
5 /* 4 /*
6 binance现货接口 5 binance现货接口
7 */ 6 */
8 -  
9 class BinanceSpot 7 class BinanceSpot
10 { 8 {
11 static private string $host = 'https://api.binance.com'; 9 static private string $host = 'https://api.binance.com';
12 private string $apikey = ''; 10 private string $apikey = '';
13 private string $secret = ''; 11 private string $secret = '';
14 12
15 - public function __construct($apikey = '', $secret = '', $host = '') 13 + public function __construct($apikey='', $secret='',$host='')
16 { 14 {
17 $this->apikey = $apikey; 15 $this->apikey = $apikey;
18 $this->secret = $secret; 16 $this->secret = $secret;
19 - if (!empty($host)) { 17 + if(!empty($host)){
20 self::$host = $host; 18 self::$host = $host;
21 } 19 }
22 } 20 }
@@ -35,7 +33,7 @@ class BinanceSpot @@ -35,7 +33,7 @@ class BinanceSpot
35 { 33 {
36 $len = count($param); 34 $len = count($param);
37 if ($len == 0) { 35 if ($len == 0) {
38 - // output('param 为空,签名失败'); 36 +// output('param 为空,签名失败');
39 return $param; 37 return $param;
40 } 38 }
41 $paramStr = http_build_query($param); 39 $paramStr = http_build_query($param);
@@ -67,7 +65,7 @@ class BinanceSpot @@ -67,7 +65,7 @@ class BinanceSpot
67 private function requestListenKey($method, $path, $param) 65 private function requestListenKey($method, $path, $param)
68 { 66 {
69 $url = $this->createUrl($path); 67 $url = $this->createUrl($path);
70 - // $param['timestamp'] = getMicrotime(); 68 +// $param['timestamp'] = getMicrotime();
71 $param['signature'] = $this->createSign($this->secret, $param); 69 $param['signature'] = $this->createSign($this->secret, $param);
72 $header = $this->createHeader($this->apikey); 70 $header = $this->createHeader($this->apikey);
73 if (!in_array(strtoupper($method), ['GET', 'POST', 'DELETE'])) { 71 if (!in_array(strtoupper($method), ['GET', 'POST', 'DELETE'])) {
@@ -114,14 +112,14 @@ class BinanceSpot @@ -114,14 +112,14 @@ class BinanceSpot
114 * @param int $type (=1:行情等接口,不需要签名;=2:账户等接口需要签名) 112 * @param int $type (=1:行情等接口,不需要签名;=2:账户等接口需要签名)
115 * @return array|int|mixed 113 * @return array|int|mixed
116 */ 114 */
117 - public function request(string $path, string $method, array $param, int $type = 1) 115 + public function request(string $path, string $method, array $param, int $type=1)
118 { 116 {
119 - if ($type == 2) { 117 + if($type==2){
120 return self::requestAccount($method, $path, $param); 118 return self::requestAccount($method, $path, $param);
121 - } else if ($type == 1) { 119 + }else if($type==1){
122 return self::requestMarket($method, $path, $param); 120 return self::requestMarket($method, $path, $param);
123 - } else {  
124 - return ['code' => 1000, 'msg' => 'type参数错误']; 121 + }else{
  122 + return ['code'=>1000,'msg'=>'type参数错误'];
125 } 123 }
126 } 124 }
127 125
@@ -209,20 +207,25 @@ class BinanceSpot @@ -209,20 +207,25 @@ class BinanceSpot
209 return $this->requestAccount($method, $path, $param); 207 return $this->requestAccount($method, $path, $param);
210 } 208 }
211 209
212 - public function cancelOrderById($param)  
213 - { 210 + public function cancelOrderById($param){
214 $path = '/api/v3/order'; 211 $path = '/api/v3/order';
215 $method = 'DELETE'; 212 $method = 'DELETE';
216 return $this->requestAccount($method, $path, $param); 213 return $this->requestAccount($method, $path, $param);
217 } 214 }
218 215
219 - public function openOrders($param)  
220 - { 216 + public function openOrders($param){
221 $path = '/api/v3/openOrders'; 217 $path = '/api/v3/openOrders';
222 $method = 'GET'; 218 $method = 'GET';
223 return $this->requestAccount($method, $path, $param); 219 return $this->requestAccount($method, $path, $param);
224 } 220 }
225 221
  222 + //账户信息
  223 + public function account($param=[]){
  224 + $path = '/api/v3/account';
  225 + $method = 'GET';
  226 + return $this->requestAccount($method, $path, $param);
  227 + }
  228 +
226 /* 229 /*
227 * ================================== 230 * ==================================
228 * 以下杠杆账户接口 231 * 以下杠杆账户接口
@@ -236,29 +239,25 @@ class BinanceSpot @@ -236,29 +239,25 @@ class BinanceSpot
236 return $this->requestListenKey($method, $path, $param); 239 return $this->requestListenKey($method, $path, $param);
237 } 240 }
238 //杠杆账户下单 241 //杠杆账户下单
239 - public function orderMargin($param)  
240 - { 242 + public function orderMargin($param){
241 $path = '/sapi/v1/margin/order'; 243 $path = '/sapi/v1/margin/order';
242 $method = 'POST'; 244 $method = 'POST';
243 return $this->requestAccount($method, $path, $param); 245 return $this->requestAccount($method, $path, $param);
244 } 246 }
245 //杠杆账户撤销订单 (TRADE) 247 //杠杆账户撤销订单 (TRADE)
246 - public function cancelOrderMargin($param)  
247 - { 248 + public function cancelOrderMargin($param){
248 $path = '/sapi/v1/margin/order'; 249 $path = '/sapi/v1/margin/order';
249 $method = 'DELETE'; 250 $method = 'DELETE';
250 return $this->requestAccount($method, $path, $param); 251 return $this->requestAccount($method, $path, $param);
251 } 252 }
252 //杠杆账户订单列表 253 //杠杆账户订单列表
253 - public function openOrdersMargin($param)  
254 - { 254 + public function openOrdersMargin($param){
255 $path = '/sapi/v1/margin/openOrders'; 255 $path = '/sapi/v1/margin/openOrders';
256 $method = 'GET'; 256 $method = 'GET';
257 return $this->requestAccount($method, $path, $param); 257 return $this->requestAccount($method, $path, $param);
258 } 258 }
259 //调整全仓最大杠杆 (USER_DATA) 259 //调整全仓最大杠杆 (USER_DATA)
260 - public function maxLeverageMargin($param)  
261 - { 260 + public function maxLeverageMargin($param){
262 $path = '/sapi/v1/margin/max-leverage'; 261 $path = '/sapi/v1/margin/max-leverage';
263 $method = 'POST'; 262 $method = 'POST';
264 return $this->requestAccount($method, $path, $param); 263 return $this->requestAccount($method, $path, $param);
@@ -269,12 +268,20 @@ class BinanceSpot @@ -269,12 +268,20 @@ class BinanceSpot
269 * 以下公共接口,无限apikey 268 * 以下公共接口,无限apikey
270 * ================================== 269 * ==================================
271 */ 270 */
  271 + //市场最新价格
  272 + public function tickerPrice($param=[]){
  273 + $path ='/api/v3/ticker/price';
  274 + $method = 'GET';
  275 + return self::requestMarket($method,$path,$param);
  276 + }
272 //24小时成交数据 277 //24小时成交数据
273 - // static public function ticker24hr($param = [])  
274 - // {  
275 - // $path = '/fapi/v1/ticker/24hr';  
276 - // $method = 'GET';  
277 - // return self::requestMarket($method, $path, $param);  
278 - // } 278 +// static public function ticker24hr($param = [])
  279 +// {
  280 +// $path = '/fapi/v1/ticker/24hr';
  281 +// $method = 'GET';
  282 +// return self::requestMarket($method, $path, $param);
  283 +// }
279 284
280 } 285 }
  286 +
  287 +?>
  1 +<?php
  2 +namespace Jiaoyin;
  3 +
  4 +/*
  5 + binance现货接口
  6 +*/
  7 +class BinanceUnified
  8 +{
  9 + static private string $host = 'https://papi.binance.com';
  10 + private string $apikey = '';
  11 + private string $secret = '';
  12 +
  13 + public function __construct($apikey='', $secret='',$host='')
  14 + {
  15 + $this->apikey = $apikey;
  16 + $this->secret = $secret;
  17 + if(!empty($host)){
  18 + self::$host = $host;
  19 + }
  20 + }
  21 +
  22 + static private function createUrl($path): string
  23 + {
  24 + return self::$host . $path;
  25 + }
  26 +
  27 + static private function createHeader($apikey): array
  28 + {
  29 + return ["X-MBX-APIKEY:" . $apikey];
  30 + }
  31 +
  32 + static private function createSign($secret, $param)
  33 + {
  34 + $len = count($param);
  35 + if ($len == 0) {
  36 +// output('param 为空,签名失败');
  37 + return $param;
  38 + }
  39 + $paramStr = http_build_query($param);
  40 + return hash_hmac('sha256', $paramStr, $secret);
  41 + }
  42 +
  43 + private function requestAccount($method, $path, $param=[])
  44 + {
  45 + $url = $this->createUrl($path);
  46 + $param['timestamp'] = getMicrotime();
  47 + $param['signature'] = $this->createSign($this->secret, $param);
  48 + $header = $this->createHeader($this->apikey);
  49 + if (!in_array(strtoupper($method), ['GET', 'POST', 'DELETE'])) {
  50 + output('请求方法错误', $method, $path, $param);
  51 + return 0;
  52 + }
  53 + $data = json_encode([]);
  54 + if (strtoupper($method) == 'POST') {
  55 + $data = Curl::httpPost($url, $param, $header);
  56 + }
  57 + if (strtoupper($method) == 'GET') {
  58 + $data = Curl::httpGet($url, $param, $header);
  59 + }
  60 + if (strtoupper($method) == 'DELETE') {
  61 + $data = Curl::httpDelete($url, $param, $header);
  62 + }
  63 + return json_decode($data, true);
  64 + }
  65 + private function requestListenKey($method, $path, $param)
  66 + {
  67 + $url = $this->createUrl($path);
  68 +// $param['timestamp'] = getMicrotime();
  69 + $param['signature'] = $this->createSign($this->secret, $param);
  70 + $header = $this->createHeader($this->apikey);
  71 + if (!in_array(strtoupper($method), ['GET', 'POST', 'DELETE'])) {
  72 + output('请求方法错误', $method, $path, $param);
  73 + return 0;
  74 + }
  75 + $data = json_encode([]);
  76 + if (strtoupper($method) == 'POST') {
  77 + $data = Curl::httpPost($url, $param, $header);
  78 + }
  79 + if (strtoupper($method) == 'GET') {
  80 + $data = Curl::httpGet($url, $param, $header);
  81 + }
  82 + if (strtoupper($method) == 'DELETE') {
  83 + $data = Curl::httpDelete($url, $param, $header);
  84 + }
  85 + return json_decode($data, true);
  86 + }
  87 +
  88 + static private function requestMarket($method, $path, $param)
  89 + {
  90 + $url = self::createUrl($path);
  91 + if (!in_array(strtoupper($method), ['GET', 'POST', 'DELETE'])) {
  92 + output('请求方法错误', $method, $path, $param);
  93 + return 0;
  94 + }
  95 + $data = json_encode([]);
  96 + if (strtoupper($method) == 'POST') {
  97 + $data = Curl::httpPost($url, $param);
  98 + }
  99 + if (strtoupper($method) == 'GET') {
  100 + $data = Curl::httpGet($url, $param);
  101 + }
  102 + if (strtoupper($method) == 'DELETE') {
  103 + $data = Curl::httpDelete($url, $param);
  104 + }
  105 + return json_decode($data, true);
  106 + }
  107 +
  108 + /**
  109 + * @param string $path (binance api path)
  110 + * @param string $method (GET POST DELETE)
  111 + * @param array $param (binance api param)
  112 + * @param int $type (=1:行情等接口,不需要签名;=2:账户等接口需要签名)
  113 + * @return array|int|mixed
  114 + */
  115 + public function request(string $path, string $method, array $param, int $type=1)
  116 + {
  117 + if($type==2){
  118 + return self::requestAccount($method, $path, $param);
  119 + }else if($type==1){
  120 + return self::requestMarket($method, $path, $param);
  121 + }else{
  122 + return ['code'=>1000,'msg'=>'type参数错误'];
  123 + }
  124 + }
  125 +
  126 + //获取账户余额
  127 + public function getUserAsset($param=[])
  128 + {
  129 + $path = '/papi/v1/balance';
  130 + $method = 'GET';
  131 + return $this->requestAccount($method, $path, $param);
  132 + }
  133 + //获取Listen Key (USER_STREAM)
  134 + public function listenKey($param=[])
  135 + {
  136 + $path = '/papi/v1/listenKey';
  137 + $method = 'POST';
  138 + return $this->requestListenKey($method, $path, $param);
  139 + }
  140 + //延长 Listen Key 有效期 (USER_STREAM)
  141 + public function listenKeyDelay($param=[])
  142 + {
  143 + $path = '/papi/v1/listenKey';
  144 + $method = 'PUT';
  145 + return $this->requestListenKey($method, $path, $param);
  146 + }
  147 +
  148 + //现有UM账户资产和仓位信息【期货合约(永续合约、交割合约)】
  149 + public function accountUm($param=[])
  150 + {
  151 + $path = '/papi/v1/um/account';
  152 + $method = 'GET';
  153 + return $this->requestAccount($method, $path, $param);
  154 + }
  155 + //获取现有CM账户资产和仓位信息【现货市场(包括杠杆借贷现货交易)】
  156 + public function accountCm($param=[])
  157 + {
  158 + $path = '/papi/v1/cm/account';
  159 + $method = 'GET';
  160 + return $this->requestAccount($method, $path, $param);
  161 + }
  162 +
  163 +// public function order($param)
  164 +// {
  165 +// $path = '/api/v3/order';
  166 +// $method = 'POST';
  167 +// return $this->requestAccount($method, $path, $param);
  168 +// }
  169 +//
  170 +// public function cancelOrderById($param){
  171 +// $path = '/api/v3/order';
  172 +// $method = 'DELETE';
  173 +// return $this->requestAccount($method, $path, $param);
  174 +// }
  175 +//
  176 +// public function openOrders($param){
  177 +// $path = '/api/v3/openOrders';
  178 +// $method = 'GET';
  179 +// return $this->requestAccount($method, $path, $param);
  180 +// }
  181 +//
  182 + //统一账户账户生成 Listen Key (USER_STREAM)
  183 +
  184 +
  185 +}
  186 +
  187 +?>
1 <?php 1 <?php
2 2
3 -namespace jiaoyin; 3 +namespace Jiaoyin;
4 4
5 class Curl 5 class Curl
6 { 6 {
@@ -19,13 +19,15 @@ class Curl @@ -19,13 +19,15 @@ class Curl
19 $output = curl_exec($ch); 19 $output = curl_exec($ch);
20 if ($output === false) { 20 if ($output === false) {
21 echo 'Curl error: ' . curl_error($ch); 21 echo 'Curl error: ' . curl_error($ch);
  22 + curl_close($ch);
  23 + return json_encode(['code' => '-1', 'msg' => 'curl error: ' . curl_error($ch)]);
22 } 24 }
23 curl_close($ch); 25 curl_close($ch);
24 return $output; 26 return $output;
25 } 27 }
26 28
27 //post 获取数据 29 //post 获取数据
28 - static public function httpPost($url, $param = [], $header = [], $asBody = false) 30 + static public function httpPost($url, $param = [], $header = [])
29 { 31 {
30 if (empty($url)) { 32 if (empty($url)) {
31 return false; 33 return false;
@@ -35,42 +37,21 @@ class Curl @@ -35,42 +37,21 @@ class Curl
35 $opts = []; 37 $opts = [];
36 $opts[CURLOPT_POST] = 1; 38 $opts[CURLOPT_POST] = 1;
37 $opts[CURLOPT_USERAGENT] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.98 Safari/537.36"; 39 $opts[CURLOPT_USERAGENT] = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.98 Safari/537.36";
38 -  
39 - // 修改判断逻辑  
40 - $isJson = false;  
41 - foreach ($header as $h) {  
42 - if (stripos($h, 'Content-Type: application/json') !== false) {  
43 - $isJson = true;  
44 - break;  
45 - }  
46 - }  
47 -  
48 - if ($asBody) {  
49 - // form-data格式提交  
50 - $boundary = '----WebKitFormBoundary' . uniqid();  
51 - $body = '';  
52 - foreach ($param as $key => $value) {  
53 - $body .= "--$boundary\r\n";  
54 - $body .= "Content-Disposition: form-data; name=\"$key\"\r\n\r\n";  
55 - $body .= "$value\r\n";  
56 - }  
57 - $body .= "--$boundary--\r\n";  
58 - $opts[CURLOPT_POSTFIELDS] = $body;  
59 - $header[] = "Content-Type: multipart/form-data; boundary=$boundary";  
60 - curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 40 + if (!empty($header) && in_array('Content-Type:application/json', $header)) {
  41 + $opts[CURLOPT_POSTFIELDS] = json_encode($param);
61 } else { 42 } else {
62 - $opts[CURLOPT_POSTFIELDS] = $isJson ? json_encode($param) : http_build_query($param); 43 + $opts[CURLOPT_POSTFIELDS] = http_build_query($param);
63 } 44 }
64 -  
65 curl_setopt_array($ch, $opts); 45 curl_setopt_array($ch, $opts);
66 $output = curl_exec($ch); 46 $output = curl_exec($ch);
67 if ($output === false) { 47 if ($output === false) {
68 echo 'Curl error: ' . curl_error($ch); 48 echo 'Curl error: ' . curl_error($ch);
  49 + curl_close($ch);
  50 + return json_encode(['code' => '-1', 'msg' => 'curl error: ' . curl_error($ch)]);
69 } 51 }
70 curl_close($ch); 52 curl_close($ch);
71 return $output; 53 return $output;
72 } 54 }
73 -  
74 //delete 请求 55 //delete 请求
75 static public function httpDelete($url, $param = [], $header = []) 56 static public function httpDelete($url, $param = [], $header = [])
76 { 57 {
@@ -86,6 +67,9 @@ class Curl @@ -86,6 +67,9 @@ class Curl
86 $output = curl_exec($ch); 67 $output = curl_exec($ch);
87 if ($output === false) { 68 if ($output === false) {
88 echo 'Curl error: ' . curl_error($ch); 69 echo 'Curl error: ' . curl_error($ch);
  70 + echo 'Curl error: ' . curl_error($ch);
  71 + curl_close($ch);
  72 + return json_encode(['code' => '-1', 'msg' => 'curl error: ' . curl_error($ch)]);
89 } 73 }
90 curl_close($ch); 74 curl_close($ch);
91 return $output; 75 return $output;
1 <?php 1 <?php
  2 +namespace Jiaoyin;
2 3
3 -namespace jiaoyin;  
4 -  
5 -class Feishu  
6 -{ 4 +class Feishu{
7 const user_liquid = 'gd1d6b34'; 5 const user_liquid = 'gd1d6b34';
8 const user_jiaoyin = 'efdgdecb'; 6 const user_jiaoyin = 'efdgdecb';
9 const user_small = 'db9d9g4d'; 7 const user_small = 'db9d9g4d';
@@ -11,13 +9,13 @@ class Feishu @@ -11,13 +9,13 @@ class Feishu
11 const user_tanli = 'f16c2bd7'; 9 const user_tanli = 'f16c2bd7';
12 const user_007 = '9de6cgf3'; 10 const user_007 = '9de6cgf3';
13 //风控处置群 11 //风控处置群
14 - const url_risk = 'https://open.feishu.cn/open-apis/bot/v2/hook/7509c731-a1ab-4e2f-b781-9082a26fd11d'; 12 + const url_risk ='https://open.feishu.cn/open-apis/bot/v2/hook/7509c731-a1ab-4e2f-b781-9082a26fd11d';
15 //零容忍 13 //零容忍
16 const url_error = 'https://open.feishu.cn/open-apis/bot/v2/hook/a344e313-4d5d-4aa4-912b-dda37b2e3ee8'; 14 const url_error = 'https://open.feishu.cn/open-apis/bot/v2/hook/a344e313-4d5d-4aa4-912b-dda37b2e3ee8';
17 //零忽略 15 //零忽略
18 const url_warning = 'https://open.feishu.cn/open-apis/bot/v2/hook/01c53b94-378a-45a3-abed-415f84f57f4a'; 16 const url_warning = 'https://open.feishu.cn/open-apis/bot/v2/hook/01c53b94-378a-45a3-abed-415f84f57f4a';
19 //软件异常报警 17 //软件异常报警
20 - const url_warning_soft = 'https://open.feishu.cn/open-apis/bot/v2/hook/d48c2e4a-0ef9-4684-88d4-e62d878fdaca'; 18 + const url_warning_soft ='https://open.feishu.cn/open-apis/bot/v2/hook/d48c2e4a-0ef9-4684-88d4-e62d878fdaca';
21 //测试频道 19 //测试频道
22 const url_test = 'https://open.feishu.cn/open-apis/bot/v2/hook/34a9f127-3838-43fc-bc35-6f5e4e96bf6d'; 20 const url_test = 'https://open.feishu.cn/open-apis/bot/v2/hook/34a9f127-3838-43fc-bc35-6f5e4e96bf6d';
23 //交易与跟单业务沟通 21 //交易与跟单业务沟通
@@ -26,36 +24,31 @@ class Feishu @@ -26,36 +24,31 @@ class Feishu
26 const url_strategy = 'https://open.feishu.cn/open-apis/bot/v2/hook/53444f37-cfec-420c-a4e6-2f415b908dee'; 24 const url_strategy = 'https://open.feishu.cn/open-apis/bot/v2/hook/53444f37-cfec-420c-a4e6-2f415b908dee';
27 //网格策略运营群 25 //网格策略运营群
28 const url_grid = 'https://open.feishu.cn/open-apis/bot/v2/hook/19471aa1-f926-46d7-a1d5-a367d1449092'; 26 const url_grid = 'https://open.feishu.cn/open-apis/bot/v2/hook/19471aa1-f926-46d7-a1d5-a367d1449092';
29 - //量化机会通知群  
30 - const url_quant_chance = 'https://open.feishu.cn/open-apis/bot/v2/hook/b590bbd4-6a0a-4328-bf28-9fc15677643e';  
31 - static public function notice(string $content, string $channel_url, array $users = []) 27 + static public function notice($content,$channel_url,$users=[])
32 { 28 {
33 $userXml = ''; 29 $userXml = '';
34 foreach ($users as $user) { 30 foreach ($users as $user) {
35 $userXml .= "<at user_id='" . $user . "'></at>"; 31 $userXml .= "<at user_id='" . $user . "'></at>";
36 } 32 }
37 $content = [ 33 $content = [
38 - 'msg_type' => "text",  
39 - 'content' => [  
40 - 'text' => $content . $userXml 34 + 'msg_type'=>"text",
  35 + 'content'=>[
  36 + 'text'=>$content.$userXml
41 ] 37 ]
42 ]; 38 ];
43 $con = json_encode($content); 39 $con = json_encode($content);
44 - return self::send_post_json($channel_url, $con); 40 + return self::send_post_json($channel_url,$con);
45 } 41 }
46 static public function send_post_json($url, $jsonStr) 42 static public function send_post_json($url, $jsonStr)
47 { 43 {
48 $ch = curl_init(); 44 $ch = curl_init();
49 curl_setopt($ch, CURLOPT_POST, 1); 45 curl_setopt($ch, CURLOPT_POST, 1);
50 - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);  
51 - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 46 + curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
  47 + curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
52 curl_setopt($ch, CURLOPT_URL, $url); 48 curl_setopt($ch, CURLOPT_URL, $url);
53 curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonStr); 49 curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonStr);
54 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 50 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
55 - curl_setopt(  
56 - $ch,  
57 - CURLOPT_HTTPHEADER,  
58 - array( 51 + curl_setopt($ch, CURLOPT_HTTPHEADER, array(
59 'Content-Type: application/json; charset=utf-8', 52 'Content-Type: application/json; charset=utf-8',
60 'Content-Length: ' . strlen($jsonStr) 53 'Content-Length: ' . strlen($jsonStr)
61 ) 54 )
1 <?php 1 <?php
2 2
3 -namespace jiaoyin; 3 +namespace Jiaoyin;
4 4
5 class KlineAssist 5 class KlineAssist
6 { 6 {
@@ -27,4 +27,5 @@ class KlineAssist @@ -27,4 +27,5 @@ class KlineAssist
27 } 27 }
28 return -1; 28 return -1;
29 } 29 }
  30 +
30 } 31 }
1 <?php 1 <?php
2 2
3 -namespace jiaoyin;  
4 -  
5 -require_once __DIR__ . '/func.php';  
6 -namespace jiaoyin; 3 +namespace Jiaoyin;
  4 +require_once __DIR__ . '/functions.php';
7 class Logger 5 class Logger
8 { 6 {
9 private string $logPath; 7 private string $logPath;
10 private bool $dateSlice; 8 private bool $dateSlice;
11 private array $types; 9 private array $types;
12 10
13 - public function __construct($logPath, $types = ['info', 'warning', 'error'], $dateSlice = false) 11 + public function __construct($logPath, $types = ['info','warning','error'], $dateSlice = false)
14 { 12 {
15 $this->logPath = $logPath; 13 $this->logPath = $logPath;
16 $this->dateSlice = $dateSlice; 14 $this->dateSlice = $dateSlice;
17 $this->types = $types; 15 $this->types = $types;
18 } 16 }
19 - public function debug($msg)  
20 - {  
21 - $this->log($msg, 'debug'); 17 + public function debug($msg){
  18 + $this->log($msg,'debug');
22 } 19 }
23 - public function info($msg)  
24 - {  
25 - $this->log($msg, 'info'); 20 + public function info($msg){
  21 + $this->log($msg,'info');
26 } 22 }
27 - public function warning($msg)  
28 - {  
29 - $this->log($msg, 'warning'); 23 + public function warning($msg){
  24 + $this->log($msg,'warning');
30 } 25 }
31 - public function error($msg)  
32 - {  
33 - $this->log($msg, 'error'); 26 + public function error($msg){
  27 + $this->log($msg,'error');
34 } 28 }
35 - private function log($msg, $type)  
36 - {  
37 - $msg = '[' . timeFormat('ms') . '][' . $type . ']:' . $msg . PHP_EOL; 29 + private function log($msg, $type){
  30 + $msg = '['.timeFormat('ms').']['.$type.']:'.$msg.PHP_EOL;
38 echo $msg; 31 echo $msg;
39 - if (!in_array($type, $this->types)) { 32 + if (!in_array($type,$this->types)){
40 return; 33 return;
41 } 34 }
42 if ($this->dateSlice) { 35 if ($this->dateSlice) {
43 - $path = $this->logPath . '/' . date('Y-m-d');  
44 - } else { 36 + $path = $this->logPath.'/'.date('Y-m-d');
  37 + }else{
45 $path = $this->logPath; 38 $path = $this->logPath;
46 } 39 }
47 - $file = $path . '/' . $type . '.log';  
48 - $this->save($file, $msg, $type); 40 + $file = $path.'/'.$type.'.log';
  41 + $this->save($file,$msg,$type);
49 } 42 }
50 - private function save($file, $msg, $type = 'info')  
51 - {  
52 - \Swoole\Coroutine::create(function () use ($file, $msg, $type) { 43 + private function save($file,$msg, $type='info'){
  44 + \Swoole\Coroutine::create(function () use ($file,$msg,$type) {
53 $this->checkFileDir($file); 45 $this->checkFileDir($file);
54 - file_put_contents($file, $msg, FILE_APPEND); 46 + file_put_contents($file,$msg,FILE_APPEND);
55 }); 47 });
56 } 48 }
57 - private function checkFileDir($file)  
58 - { 49 + private function checkFileDir($file){
59 // 获取目录路径 50 // 获取目录路径
60 $directoryPath = dirname($file); 51 $directoryPath = dirname($file);
61 if (!is_dir($directoryPath)) { 52 if (!is_dir($directoryPath)) {
1 <?php 1 <?php
2 -  
3 -namespace jiaoyin; 2 +namespace Jiaoyin;
4 3
5 class MongoCli 4 class MongoCli
6 { 5 {
7 private $pool; 6 private $pool;
8 private $database; 7 private $database;
9 - public function __construct($host, $port, $username, $password, $database, $num = 20) 8 + public function __construct($host,$port,$username,$password,$database, $num = 20)
10 { 9 {
11 $this->database = $database; 10 $this->database = $database;
12 - $userInfo = $username ? "{$username}:{$password}@" : ""; 11 + $userInfo = $username?"{$username}:{$password}@":"";
13 $dsn = "mongodb://{$userInfo}{$host}:{$port}/{$database}"; 12 $dsn = "mongodb://{$userInfo}{$host}:{$port}/{$database}";
14 - $this->pool = new MongoPool($dsn, $num); 13 + $this->pool = new MongoPool($dsn,$num);
15 } 14 }
16 15
17 /* 16 /*
18 * 创建集合 17 * 创建集合
19 */ 18 */
20 - public function createCollection($table, $createIndex = [])  
21 - { 19 + public function createCollection($table,$createIndex=[]){
22 try { 20 try {
23 $mongodb = $this->pool->get(); 21 $mongodb = $this->pool->get();
24 $database = $mongodb->selectDatabase($this->database); 22 $database = $mongodb->selectDatabase($this->database);
@@ -61,7 +59,7 @@ class MongoCli @@ -61,7 +59,7 @@ class MongoCli
61 * 写入数据 59 * 写入数据
62 * $collection->insertOne(['name' => 'John Doe']); 60 * $collection->insertOne(['name' => 'John Doe']);
63 * */ 61 * */
64 - public function insertOne($table, $data = []) 62 + public function insertOne($table,$data = [])
65 { 63 {
66 try { 64 try {
67 $mongodb = $this->pool->get(); 65 $mongodb = $this->pool->get();
@@ -80,7 +78,7 @@ class MongoCli @@ -80,7 +78,7 @@ class MongoCli
80 * 批量写入数据 78 * 批量写入数据
81 * $collection->insertMany([['name' => 'John Doe'],['name' => 'John Doe']]); 79 * $collection->insertMany([['name' => 'John Doe'],['name' => 'John Doe']]);
82 * */ 80 * */
83 - public function insertAll($table, $data = []) 81 + public function insertAll($table,$data = [])
84 { 82 {
85 try { 83 try {
86 $mongodb = $this->pool->get(); 84 $mongodb = $this->pool->get();
@@ -99,7 +97,7 @@ class MongoCli @@ -99,7 +97,7 @@ class MongoCli
99 * 查询单条数据 97 * 查询单条数据
100 * $collection->findOne(['name' => 'John Doe']); 98 * $collection->findOne(['name' => 'John Doe']);
101 * */ 99 * */
102 - public function findOne($table, $where = []) 100 + public function findOne($table,$where = [])
103 { 101 {
104 try { 102 try {
105 $mongodb = $this->pool->get(); 103 $mongodb = $this->pool->get();
@@ -126,17 +124,17 @@ class MongoCli @@ -126,17 +124,17 @@ class MongoCli
126 * 'limit' => 100, 124 * 'limit' => 100,
127 * ]; 125 * ];
128 * */ 126 * */
129 - public function findAll($table, $where = [], $options = []) 127 + public function findAll($table,$where = [], $options=[])
130 { 128 {
131 try { 129 try {
132 $mongodb = $this->pool->get(); 130 $mongodb = $this->pool->get();
133 $database = $mongodb->selectDatabase($this->database); 131 $database = $mongodb->selectDatabase($this->database);
134 $collection = $database->selectCollection($table); 132 $collection = $database->selectCollection($table);
135 - $result = $collection->find($where, $options); 133 + $result = $collection->find($where,$options);
136 $this->pool->push($mongodb); 134 $this->pool->push($mongodb);
137 // 遍历结果集 135 // 遍历结果集
138 $data = []; 136 $data = [];
139 - if ($result) { 137 + if ($result){
140 $arr = $result->toArray(); 138 $arr = $result->toArray();
141 foreach ($arr as $item) { 139 foreach ($arr as $item) {
142 $data[] = $item->getArrayCopy(); 140 $data[] = $item->getArrayCopy();
@@ -154,13 +152,13 @@ class MongoCli @@ -154,13 +152,13 @@ class MongoCli
154 * 更新数据 152 * 更新数据
155 * $collection->updateOne(['name' => 'John Doe'], ['$set' => ['age' => 31]]); 153 * $collection->updateOne(['name' => 'John Doe'], ['$set' => ['age' => 31]]);
156 * */ 154 * */
157 - public function updateOne($table, $where = [], $upData = []) 155 + public function updateOne($table,$where = [],$upData = [])
158 { 156 {
159 try { 157 try {
160 $mongodb = $this->pool->get(); 158 $mongodb = $this->pool->get();
161 $database = $mongodb->selectDatabase($this->database); 159 $database = $mongodb->selectDatabase($this->database);
162 $collection = $database->selectCollection($table); 160 $collection = $database->selectCollection($table);
163 - $result = $collection->updateOne($where, $upData); 161 + $result = $collection->updateOne($where,$upData);
164 $this->pool->push($mongodb); 162 $this->pool->push($mongodb);
165 return $result; 163 return $result;
166 } catch (\Exception $e) { 164 } catch (\Exception $e) {
@@ -173,7 +171,7 @@ class MongoCli @@ -173,7 +171,7 @@ class MongoCli
173 * 删除数据 171 * 删除数据
174 * $collection->deleteOne(['name' => 'John Doe']); 172 * $collection->deleteOne(['name' => 'John Doe']);
175 * */ 173 * */
176 - public function deleteOne($table, $where = []) 174 + public function deleteOne($table,$where = [])
177 { 175 {
178 try { 176 try {
179 $mongodb = $this->pool->get(); 177 $mongodb = $this->pool->get();
@@ -192,7 +190,7 @@ class MongoCli @@ -192,7 +190,7 @@ class MongoCli
192 * 批量删除数据 190 * 批量删除数据
193 * $collection->deleteMany(['name' => 'John Doe']); 191 * $collection->deleteMany(['name' => 'John Doe']);
194 * */ 192 * */
195 - public function deleteBatch($table, $where = []) 193 + public function deleteBatch($table,$where = [])
196 { 194 {
197 try { 195 try {
198 $mongodb = $this->pool->get(); 196 $mongodb = $this->pool->get();
@@ -206,4 +204,5 @@ class MongoCli @@ -206,4 +204,5 @@ class MongoCli
206 return []; 204 return [];
207 } 205 }
208 } 206 }
  207 +
209 } 208 }
1 <?php 1 <?php
2 -  
3 -namespace jiaoyin; 2 +namespace Jiaoyin;
4 3
5 use MongoDB\Client; 4 use MongoDB\Client;
6 use Swoole\Coroutine; 5 use Swoole\Coroutine;
@@ -14,7 +13,7 @@ class MongoPool @@ -14,7 +13,7 @@ class MongoPool
14 private $mongoUri; 13 private $mongoUri;
15 private $options; 14 private $options;
16 15
17 - public function __construct($mongoUri, $maxSize = 20, $options = []) 16 + public function __construct($mongoUri, $maxSize=20, $options = [])
18 { 17 {
19 $this->mongoUri = $mongoUri; 18 $this->mongoUri = $mongoUri;
20 $this->maxSize = $maxSize; 19 $this->maxSize = $maxSize;
@@ -36,8 +35,8 @@ class MongoPool @@ -36,8 +35,8 @@ class MongoPool
36 35
37 public function get() 36 public function get()
38 { 37 {
39 - while ($this->pool->isEmpty()) {  
40 - // output('Mongodb连接池为空,等待释放连接'); 38 + while ($this->pool->isEmpty()){
  39 +// output('Mongodb连接池为空,等待释放连接');
41 Coroutine::sleep(0.1); 40 Coroutine::sleep(0.1);
42 } 41 }
43 $connection = $this->pool->pop(); 42 $connection = $this->pool->pop();
@@ -59,7 +58,7 @@ class MongoPool @@ -59,7 +58,7 @@ class MongoPool
59 // 检查连接是否有效,这里仅做示例,实际应用中可能需要更复杂的逻辑 58 // 检查连接是否有效,这里仅做示例,实际应用中可能需要更复杂的逻辑
60 if (is_object($connection) && get_class($connection) === Client::class) { 59 if (is_object($connection) && get_class($connection) === Client::class) {
61 $this->pool->push($connection); 60 $this->pool->push($connection);
62 - } else { 61 + }else{
63 $this->currentSize -= 1; 62 $this->currentSize -= 1;
64 } 63 }
65 } 64 }
1 <?php 1 <?php
2 2
3 -namespace jiaoyin; 3 +namespace Jiaoyin;
4 4
5 use Swoole\Database\MysqliConfig; 5 use Swoole\Database\MysqliConfig;
6 use Swoole\Database\MysqliPool; 6 use Swoole\Database\MysqliPool;
  7 +use Swoole\Coroutine;
  8 +use Swoole\Coroutine\Channel;
7 9
8 class MysqlCli 10 class MysqlCli
9 { 11 {
10 12
11 private $pool = null; 13 private $pool = null;
12 - private $prefix = ''; // 前缀 14 + private $prefix = '';// 前缀
13 15
14 private int $reconnectCount = 0; //period时间内重连次数 16 private int $reconnectCount = 0; //period时间内重连次数
15 private int $period = 300; 17 private int $period = 300;
@@ -45,57 +47,57 @@ class MysqlCli @@ -45,57 +47,57 @@ class MysqlCli
45 ->withDbName($this->connectConfig['database']) 47 ->withDbName($this->connectConfig['database'])
46 ->withCharset($this->connectConfig['charset']) 48 ->withCharset($this->connectConfig['charset'])
47 ->withUsername($this->connectConfig['username']) 49 ->withUsername($this->connectConfig['username'])
48 - ->withPassword($this->connectConfig['password']),  
49 - $this->connectConfig['connectCount'] 50 + ->withPassword($this->connectConfig['password'])
  51 + , $this->connectConfig['connectCount']
50 ); 52 );
51 $this->prefix = $this->connectConfig['prefix']; 53 $this->prefix = $this->connectConfig['prefix'];
52 } 54 }
53 55
54 //执行sql 56 //执行sql
55 - public function execute($action, $sql) 57 + public function execute($action, $sql, float $timeout = 5.0, int $retry = 1)
56 { 58 {
57 $ret = false; 59 $ret = false;
58 $db = $this->pool->get(); 60 $db = $this->pool->get();
59 try { 61 try {
60 - $stmt = $db->query($sql);  
61 - if ($stmt == false) {  
62 - output($db->errno, $db->error);  
63 - } else {  
64 - if ($action == 'SELECT') {  
65 - $ret = $stmt->fetch_all();  
66 - } else {  
67 - $ret = $stmt; 62 + $chan = new Channel(1);
  63 + Coroutine::create(function () use ($chan, $db, $sql) {
  64 + try {
  65 + $result = $db->query($sql);
  66 + $chan->push($result);
  67 + } catch (\Throwable $e) {
  68 + $chan->push($e);
68 } 69 }
  70 + });
  71 +
  72 + $result = $chan->pop($timeout); // 超时控制
  73 +
  74 + if ($result === false) {
  75 + // 超时
  76 + output("SQL超时: {$sql}, 超时时间 {$timeout}s");
  77 + if ($retry > 0) {
  78 + output("超时后自动重试一次: {$sql}");
  79 + $this->pool->put($db);
  80 + return $this->execute($action, $sql, $timeout, $retry - 1);
69 } 81 }
70 - } catch (\Exception $e) {  
71 - $errorMsg = $e->getMessage();  
72 - output($sql);  
73 - output('sql错误', $errorMsg);  
74 - if ($errorMsg == 'MySQL server has gone away') {  
75 - if ($this->reconnectCount >= 3 && time() - $this->lastReconnectTime < $this->period) {  
76 - //period内重连超过3次,不继续重连但电话通知异常  
77 - output('数据库连接失效,并重连3次失败'); 82 + } elseif ($result instanceof \Throwable) {
  83 + output("执行SQL异常: {$sql}");
  84 + output("异常信息: " . $result->getMessage());
78 } else { 85 } else {
79 - //重连  
80 - output('重新创建mysql连接池', $this->reconnectCount);  
81 - $this->pool->put($db);  
82 - $this->poolConnect();  
83 - if (time() - $this->lastReconnectTime < $this->period) {  
84 - $this->reconnectCount += 1; 86 + if ($action === 'SELECT') {
  87 + $ret = $result->fetch_all();
85 } else { 88 } else {
86 - $this->reconnectCount = 0; 89 + $ret = $result;
87 } 90 }
88 - $this->lastReconnectTime = time();  
89 - return $this->execute($action, $sql);  
90 } 91 }
91 - } else {  
92 - output('数据库操作异常'); 92 +
  93 + } finally {
  94 + if ($db) {
  95 + $this->pool->put($db);
93 } 96 }
94 } 97 }
95 - $this->pool->put($db); 98 +
96 return $ret; 99 return $ret;
97 } 100 }
98 -  
99 //插入数据 101 //插入数据
100 public function insert($table, $data) 102 public function insert($table, $data)
101 { 103 {
@@ -109,13 +111,36 @@ class MysqlCli @@ -109,13 +111,36 @@ class MysqlCli
109 //select数据 111 //select数据
110 public function select($table, $where = [], $col = [], $orderBy = '', $limit = '') 112 public function select($table, $where = [], $col = [], $orderBy = '', $limit = '')
111 { 113 {
112 -  
113 $sql = $this->parseSelect($table, $where, $col, $orderBy, $limit); 114 $sql = $this->parseSelect($table, $where, $col, $orderBy, $limit);
114 if (!$sql) { 115 if (!$sql) {
115 return false; 116 return false;
116 } 117 }
117 $data = $this->execute('SELECT', $sql); 118 $data = $this->execute('SELECT', $sql);
  119 +
  120 + // 如果没有数据
  121 + if (!$data) return [];
  122 +
118 $newData = []; 123 $newData = [];
  124 +
  125 + // ✅ 判断是否包含聚合函数
  126 + $isAggregate = false;
  127 + foreach ($col as $c) {
  128 + if (stripos($c, 'sum(') !== false || stripos($c, 'count(') !== false || stripos($c, 'avg(') !== false || stripos($c, 'max(') !== false || stripos($c, 'min(') !== false) {
  129 + $isAggregate = true;
  130 + break;
  131 + }
  132 + }
  133 +
  134 + if ($isAggregate) {
  135 + // ✅ 聚合查询直接使用 fetch_assoc 风格
  136 + $db = $this->pool->get();
  137 + $stmt = $db->query($sql);
  138 + $result = $stmt->fetch_assoc();
  139 + $this->pool->put($db);
  140 + return [$result];
  141 + }
  142 +
  143 + // ✅ 普通查询才查字段结构
119 if (empty($col)) { 144 if (empty($col)) {
120 $newsql = 'SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "' . $this->database . '" AND TABLE_NAME="' . $this->parseTable($table) . '" ORDER BY `ORDINAL_POSITION` ASC'; 145 $newsql = 'SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "' . $this->database . '" AND TABLE_NAME="' . $this->parseTable($table) . '" ORDER BY `ORDINAL_POSITION` ASC';
121 $coldata = $this->execute('SELECT', $newsql); 146 $coldata = $this->execute('SELECT', $newsql);
@@ -127,6 +152,7 @@ class MysqlCli @@ -127,6 +152,7 @@ class MysqlCli
127 } 152 }
128 } 153 }
129 } 154 }
  155 +
130 foreach ($data as $key => $value) { 156 foreach ($data as $key => $value) {
131 $tmp = []; 157 $tmp = [];
132 foreach ($col as $k => $v) { 158 foreach ($col as $k => $v) {
@@ -208,12 +234,16 @@ class MysqlCli @@ -208,12 +234,16 @@ class MysqlCli
208 var_dump($where); 234 var_dump($where);
209 return false; 235 return false;
210 } 236 }
211 - $value[1] = $this->transferValue($value[1]);  
212 - if ($value[1] === 'NULL') { 237 + $symbol = $value[2] ?? '=';
  238 + // in + 数组专门处理
  239 + if (strtolower($symbol) === 'in' && is_array($value[1])) {
  240 + $val = "('" . implode("','", array_map('addslashes', $value[1])) . "')";
  241 + $tmp .= $value[0] . ' in ' . $val;
  242 + } elseif ($value[1] === 'NULL') {
213 $tmp .= $value[0] . ' is NULL'; 243 $tmp .= $value[0] . ' is NULL';
214 } else { 244 } else {
215 - $symbol = $value[2] ?? '=';  
216 - $tmp .= $value[0] . ' ' . $symbol . ' ' . $value[1]; 245 + $val = $this->transferValue($value[1]);
  246 + $tmp .= $value[0] . ' ' . $symbol . ' ' . $val;
217 } 247 }
218 $tmp .= $key < count($where) - 1 ? ' and ' : ''; 248 $tmp .= $key < count($where) - 1 ? ' and ' : '';
219 } 249 }
@@ -273,4 +303,5 @@ class MysqlCli @@ -273,4 +303,5 @@ class MysqlCli
273 } 303 }
274 return $valueNew; 304 return $valueNew;
275 } 305 }
  306 +
276 } 307 }
1 <?php 1 <?php
2 -  
3 -namespace jiaoyin; 2 +namespace Jiaoyin;
4 3
5 /* 4 /*
6 所有接口 5 所有接口
7 */ 6 */
8 -  
9 class OkApi 7 class OkApi
10 { 8 {
11 protected string $host = 'https://www.okx.com'; 9 protected string $host = 'https://www.okx.com';
@@ -80,9 +78,9 @@ class OkApi @@ -80,9 +78,9 @@ class OkApi
80 ]); 78 ]);
81 } 79 }
82 80
83 - $headers_array = [];  
84 - foreach ($headers as $key => $value) {  
85 - $headers_array[] = $key . ':' . $value; 81 + $headers_array=[];
  82 + foreach ($headers as $key => $value){
  83 + $headers_array[]=$key.':'.$value;
86 } 84 }
87 $this->headers = $headers_array; 85 $this->headers = $headers_array;
88 return $headers; 86 return $headers;
@@ -92,14 +90,14 @@ class OkApi @@ -92,14 +90,14 @@ class OkApi
92 * 公共请求接口 90 * 公共请求接口
93 * $type 1: 需鉴权 2: 不需鉴权 91 * $type 1: 需鉴权 2: 不需鉴权
94 * */ 92 * */
95 - public function request($method, $path, $param, $type = 1) 93 + public function request($method, $path, $param,$type=1)
96 { 94 {
97 $this->method = strtoupper($method); 95 $this->method = strtoupper($method);
98 $this->path = $path; 96 $this->path = $path;
99 $this->param = $param; 97 $this->param = $param;
100 - if ($type == 2) { 98 + if($type==2){
101 $this->createHeaders(); 99 $this->createHeaders();
102 - } else { 100 + }else{
103 if (empty($this->apikey) || empty($this->secret) || empty($this->apipwd)) { 101 if (empty($this->apikey) || empty($this->secret) || empty($this->apipwd)) {
104 return ['code' => -1, 'msg' => 'apikey, secret or apipwd is empty']; 102 return ['code' => -1, 'msg' => 'apikey, secret or apipwd is empty'];
105 } 103 }
@@ -127,7 +125,7 @@ class OkApi @@ -127,7 +125,7 @@ class OkApi
127 /** 125 /**
128 * 合约账户余额查询 126 * 合约账户余额查询
129 */ 127 */
130 - public function getBalance($param = []) 128 + public function getBalance($param=[])
131 { 129 {
132 $path = '/api/v5/account/balance'; 130 $path = '/api/v5/account/balance';
133 return $this->request('GET', $path, $param); 131 return $this->request('GET', $path, $param);
@@ -135,7 +133,7 @@ class OkApi @@ -135,7 +133,7 @@ class OkApi
135 /** 133 /**
136 * 资金账户余额查询 134 * 资金账户余额查询
137 */ 135 */
138 - public function getAssetBalance($param = []) 136 + public function getAssetBalance($param=[])
139 { 137 {
140 $path = '/api/v5/asset/balances'; 138 $path = '/api/v5/asset/balances';
141 return $this->request('GET', $path, $param); 139 return $this->request('GET', $path, $param);
@@ -160,9 +158,10 @@ class OkApi @@ -160,9 +158,10 @@ class OkApi
160 /* 158 /*
161 * 公共请求接口 159 * 公共请求接口
162 * */ 160 * */
163 - public function getTransferHistory($path, $param) 161 + public function getTransferHistory($path,$param)
164 { 162 {
165 $path = '/api/v5/asset/bills'; 163 $path = '/api/v5/asset/bills';
166 return $this->request('GET', $path, $param); 164 return $this->request('GET', $path, $param);
167 } 165 }
  166 +
168 } 167 }
1 <?php 1 <?php
2 2
3 -namespace jiaoyin; 3 +namespace Jiaoyin;
4 4
5 use Swoole\Database\RedisConfig; 5 use Swoole\Database\RedisConfig;
6 use Swoole\Database\RedisPool; 6 use Swoole\Database\RedisPool;
  7 +use function Jiaoyin\output;
7 8
8 class RedisCli 9 class RedisCli
9 { 10 {
10 private $pool; 11 private $pool;
11 public $subRedis = null; 12 public $subRedis = null;
12 - public function __construct($host, $port, $password, $dbIndex, $num = 20) 13 +
  14 + private $host;
  15 + private $port;
  16 + private $dbIndex;
  17 +
  18 + public function __construct($host, $port, $password, $dbIndex, $num = 40)
13 { 19 {
14 - $this->pool = new RedisPool((new RedisConfig) 20 + $this->host = $host;
  21 + $this->port = $port;
  22 + $this->dbIndex = $dbIndex;
  23 +
  24 + $this->pool = new RedisPool(
  25 + (new RedisConfig)
15 ->withHost($host) 26 ->withHost($host)
16 ->withPort($port) 27 ->withPort($port)
17 ->withAuth($password) 28 ->withAuth($password)
18 ->withDbIndex($dbIndex), 29 ->withDbIndex($dbIndex),
19 - $num //默认64个连接池 30 + $num
20 ); 31 );
21 } 32 }
22 - //集合存数据  
23 - public function sAdd($key, $member) 33 +
  34 + private function exec(callable $fn, string $method)
24 { 35 {
25 $redis = $this->pool->get(); 36 $redis = $this->pool->get();
26 try { 37 try {
27 - $res = $redis->sAdd($key, $member); 38 + return $fn($redis);
28 } catch (\RedisException $e) { 39 } catch (\RedisException $e) {
29 - return false; 40 + $this->logError($method, $e);
  41 + try {
  42 + $redis->close(); // 销毁坏连接
  43 + } catch (\Throwable $t) {
30 } 44 }
  45 + return false;
  46 + } finally {
  47 + if ($redis && $redis->isConnected()) {
31 $this->pool->put($redis); 48 $this->pool->put($redis);
32 - return $res; 49 + }
  50 + }
33 } 51 }
34 52
35 - //获取集合  
36 - public function sMembers($key) 53 + // ------------------ 集合 ------------------
  54 + public function sAdd($key, $member)
37 { 55 {
38 - $redis = $this->pool->get();  
39 - try {  
40 - $res = $redis->sMembers($key);  
41 - } catch (\RedisException $e) {  
42 - return false; 56 + return $this->exec(fn($r) => $r->sAdd($key, $member), "sAdd");
43 } 57 }
44 - $this->pool->put($redis);  
45 - return $res; 58 +
  59 + public function sMembers($key)
  60 + {
  61 + return $this->exec(fn($r) => $r->sMembers($key), "sMembers");
46 } 62 }
47 63
48 - //移除集合成员  
49 public function sRem($key, $member) 64 public function sRem($key, $member)
50 { 65 {
51 - $redis = $this->pool->get();  
52 - try {  
53 - $res = $redis->sRem($key, $member);  
54 - } catch (\RedisException $e) {  
55 - return false; 66 + return $this->exec(fn($r) => $r->sRem($key, $member), "sRem");
56 } 67 }
57 68
58 - $this->pool->put($redis);  
59 - return $res; 69 + // ------------------ 哈希 ------------------
  70 + public function hSet($key, $field, $value)
  71 + {
  72 + return $this->exec(fn($r) => $r->hSet($key, $field, $value), "hSet");
60 } 73 }
61 74
62 - //哈希 键 字段 值 设置  
63 - public function hSet($key, $field, $value) 75 + public function hMSet($key, $field)
64 { 76 {
65 - $redis = $this->pool->get();  
66 - try {  
67 - $res = $redis->hSet($key, $field, $value);  
68 - } catch (\RedisException $e) {  
69 - return false; 77 + return $this->exec(fn($r) => $r->hMSet($key, $field), "hMSet");
70 } 78 }
71 - $this->pool->put($redis);  
72 - return $res; 79 +
  80 + public function hMGet($key, $field)
  81 + {
  82 + return $this->exec(fn($r) => $r->hMGet($key, $field), "hMGet");
73 } 83 }
74 - //获取 键对应字段的值 84 +
75 public function hGet($key, $field) 85 public function hGet($key, $field)
76 { 86 {
77 - $redis = $this->pool->get();  
78 - try {  
79 - $res = $redis->hGet($key, $field);  
80 - } catch (\RedisException $e) {  
81 - return false; 87 + return $this->exec(fn($r) => $r->hGet($key, $field), "hGet");
82 } 88 }
83 - $this->pool->put($redis);  
84 - return $res;  
85 - }  
86 - //获取键所有字段和值 89 +
87 public function hGetAll($key) 90 public function hGetAll($key)
88 { 91 {
89 - $redis = $this->pool->get();  
90 - try {  
91 - $res = $redis->hGetAll($key);  
92 - } catch (\RedisException $e) {  
93 - return false; 92 + return $this->exec(fn($r) => $r->hGetAll($key), "hGetAll");
94 } 93 }
95 - $this->pool->put($redis);  
96 - return $res;  
97 - }  
98 - //删除一个值 94 +
99 public function hDel($key, $field) 95 public function hDel($key, $field)
100 { 96 {
101 - $redis = $this->pool->get();  
102 - try {  
103 - $res = $redis->hDel($key, $field);  
104 - } catch (\RedisException $e) {  
105 - return false;  
106 - }  
107 - $this->pool->put($redis);  
108 - return $res; 97 + return $this->exec(fn($r) => $r->hDel($key, $field), "hDel");
109 } 98 }
110 99
111 - /*  
112 - * 发布订阅等  
113 - */  
114 - //将信息发送到指定的频道 100 + // ------------------ 发布 ------------------
115 public function publish($channel, $message) 101 public function publish($channel, $message)
116 { 102 {
117 - $redis = $this->pool->get();  
118 - try {  
119 - $res = $redis->publish($channel, $message);  
120 - } catch (\RedisException $e) {  
121 - return false;  
122 - }  
123 - $this->pool->put($redis);  
124 - return $res; 103 + return $this->exec(fn($r) => $r->publish($channel, $message), "publish");
125 } 104 }
126 105
  106 + // ------------------ 订阅模式(独立连接) ------------------
127 public function subscribe($channels, callable $onMessage) 107 public function subscribe($channels, callable $onMessage)
128 { 108 {
129 $redis = $this->pool->get(); 109 $redis = $this->pool->get();
130 $this->subRedis = $redis; 110 $this->subRedis = $redis;
131 try { 111 try {
132 - $res = $redis->subscribe($channels, function ($redis, $channel, $message) use ($onMessage) {  
133 - call_user_func($onMessage, $redis, $channel, $message); 112 + return $redis->subscribe($channels, function ($redis, $channel, $message) use ($onMessage) {
  113 + $onMessage($redis, $channel, $message);
134 }); 114 });
135 } catch (\RedisException $e) { 115 } catch (\RedisException $e) {
  116 + $this->logError("subscribe", $e);
136 return false; 117 return false;
137 - } 118 + } finally {
138 $this->subRedis = null; 119 $this->subRedis = null;
139 - $this->pool->put($redis);  
140 - return $res; 120 + // 不放回池子,订阅是阻塞连接
  121 + }
141 } 122 }
142 123
143 public function psubscribe($channels, callable $onMessage) 124 public function psubscribe($channels, callable $onMessage)
@@ -145,14 +126,44 @@ class RedisCli @@ -145,14 +126,44 @@ class RedisCli
145 $redis = $this->pool->get(); 126 $redis = $this->pool->get();
146 $this->subRedis = $redis; 127 $this->subRedis = $redis;
147 try { 128 try {
148 - $res = $redis->psubscribe($channels, function ($redis, $pattern, $channel, $message) use ($onMessage) {  
149 - call_user_func($onMessage, $pattern, $redis, $channel, $message); 129 + return $redis->psubscribe($channels, function ($redis, $pattern, $channel, $message) use ($onMessage) {
  130 + $onMessage($pattern, $redis, $channel, $message);
150 }); 131 });
151 } catch (\RedisException $e) { 132 } catch (\RedisException $e) {
  133 + $this->logError("psubscribe", $e);
152 return false; 134 return false;
153 - } 135 + } finally {
154 $this->subRedis = null; 136 $this->subRedis = null;
155 - $this->pool->put($redis);  
156 - return $res; 137 + // 不放回池子
  138 + }
  139 + }
  140 +
  141 + // ------------------ 健康检查 ------------------
  142 + public function ping()
  143 + {
  144 + return $this->exec(fn($r) => $r->ping(), "ping");
  145 + }
  146 +
  147 + // ------------------ 错误日志 ------------------
  148 + private function logError($method, \RedisException $e)
  149 + {
  150 + if (function_exists('swoole_get_worker_id')) {
  151 + $workerId = swoole_get_worker_id();
  152 + $processInfo = "WorkerID:{$workerId}";
  153 + } else {
  154 + $pid = getmypid();
  155 + $uid = function_exists('posix_getuid') ? posix_getuid() : 'N/A';
  156 + $processInfo = "PID:{$pid}, UID:{$uid}";
  157 + }
  158 +
  159 + output(sprintf(
  160 + "[RedisCli][%s][%s:%d][db:%d] %s failed: %s",
  161 + $processInfo,
  162 + $this->host,
  163 + $this->port,
  164 + $this->dbIndex,
  165 + $method,
  166 + $e->getMessage()
  167 + ));
157 } 168 }
158 } 169 }
1 <?php 1 <?php
2 2
3 -namespace jiaoyin; 3 +namespace Jiaoyin;
4 4
5 class SimpleRequest 5 class SimpleRequest
6 { 6 {
@@ -11,7 +11,6 @@ class SimpleRequest @@ -11,7 +11,6 @@ class SimpleRequest
11 public string $method; 11 public string $method;
12 public array $header; 12 public array $header;
13 public array $cookie; 13 public array $cookie;
14 - public string $rawContent;  
15 14
16 public function __construct($data) 15 public function __construct($data)
17 { 16 {
@@ -22,14 +21,5 @@ class SimpleRequest @@ -22,14 +21,5 @@ class SimpleRequest
22 $this->method = $data['method']; 21 $this->method = $data['method'];
23 $this->header = $data['header']; 22 $this->header = $data['header'];
24 $this->cookie = $data['cookie']; 23 $this->cookie = $data['cookie'];
25 - $this->rawContent = isset($data['rawContent']) ? $data['rawContent'] : '';  
26 - if ($this->method == 'POST') {  
27 - if (count($this->post) == 0) {  
28 - $data = json_decode($this->rawContent, true);  
29 - if ($data) {  
30 - $this->post = $data;  
31 - }  
32 - }  
33 - }  
34 } 24 }
35 } 25 }
1 <?php 1 <?php
2 -  
3 -namespace jiaoyin;  
4 -  
5 -require_once __DIR__ . '/func.php';  
6 - 2 +namespace Jiaoyin;
7 use Swoole\Http\Request; 3 use Swoole\Http\Request;
8 use Swoole\Http\Response; 4 use Swoole\Http\Response;
9 use Swoole\Http\Server; 5 use Swoole\Http\Server;
10 -use function jiaoyin\output;  
11 6
12 class SimpleServer 7 class SimpleServer
13 { 8 {
@@ -16,9 +11,8 @@ class SimpleServer @@ -16,9 +11,8 @@ class SimpleServer
16 { 11 {
17 $this->httpServer = new Server($host, $port); 12 $this->httpServer = new Server($host, $port);
18 } 13 }
19 - public function router($url, $callback)  
20 - {  
21 - $this->httpServer->on('Request', function (Request $request, Response $response) use ($url, $callback) { 14 + public function router($url, $callback){
  15 + $this->httpServer->on('Request', function (Request $request, Response $response) use ($url, $callback){
22 $requestInfo = [ 16 $requestInfo = [
23 'path' => $request->server['path_info'], 17 'path' => $request->server['path_info'],
24 'uri' => $request->server['request_uri'], 18 'uri' => $request->server['request_uri'],
@@ -28,17 +22,16 @@ class SimpleServer @@ -28,17 +22,16 @@ class SimpleServer
28 'header' => $request->header ?: [], 22 'header' => $request->header ?: [],
29 'cookie' => $request->cookie ?: [], 23 'cookie' => $request->cookie ?: [],
30 ]; 24 ];
31 - output($requestInfo);  
32 $simpleRequest = new SimpleRequest($requestInfo); 25 $simpleRequest = new SimpleRequest($requestInfo);
33 if ($simpleRequest->path == '/favicon.ico' || $simpleRequest->uri == '/favicon.ico') { 26 if ($simpleRequest->path == '/favicon.ico' || $simpleRequest->uri == '/favicon.ico') {
34 $response->end(); 27 $response->end();
35 return; 28 return;
36 } 29 }
37 - if ($url == $simpleRequest->uri) { 30 + if($url == $simpleRequest->uri){
38 $res = call_user_func($callback, $simpleRequest); 31 $res = call_user_func($callback, $simpleRequest);
39 - if (!$res) { 32 + if(!$res){
40 $response->end(); 33 $response->end();
41 - } else { 34 + }else{
42 $response->end($res); 35 $response->end($res);
43 } 36 }
44 return; 37 return;
@@ -47,8 +40,8 @@ class SimpleServer @@ -47,8 +40,8 @@ class SimpleServer
47 }); 40 });
48 } 41 }
49 42
50 - public function start()  
51 - { 43 + public function start(){
52 $this->httpServer->start(); 44 $this->httpServer->start();
53 } 45 }
54 } 46 }
  47 +
1 <?php 1 <?php
2 -  
3 -namespace jiaoyin;  
4 -  
5 -require_once __DIR__ . '/func.php';  
6 - 2 +namespace Jiaoyin;
7 use Swoole\Http\Request; 3 use Swoole\Http\Request;
8 use Swoole\Http\Response; 4 use Swoole\Http\Response;
9 use Swoole\Coroutine\Http\Server; 5 use Swoole\Coroutine\Http\Server;
10 -use function jiaoyin\output;  
11 6
12 class SimpleServerCoroutine 7 class SimpleServerCoroutine
13 { 8 {
@@ -16,52 +11,35 @@ class SimpleServerCoroutine @@ -16,52 +11,35 @@ class SimpleServerCoroutine
16 { 11 {
17 $this->httpServer = new Server($host, $port, $ssl); 12 $this->httpServer = new Server($host, $port, $ssl);
18 } 13 }
19 - public function router(array $method, $path, $callback)  
20 - {  
21 - $this->httpServer->handle($path, function (Request $request, Response $response) use ($method, $callback) { 14 + public function router($method, $path, $callback){
  15 + $this->httpServer->handle($path, function (Request $request, Response $response) use ($method,$callback) {
22 $m = $request->getMethod(); 16 $m = $request->getMethod();
23 - foreach ($method as $k => $v) {  
24 - $method[$k] = strtolower($v);  
25 - }  
26 - $m = strtolower($m);  
27 - if (!in_array($m, $method)) { 17 + if(!in_array($m,$method)){
28 $response->status(405); 18 $response->status(405);
29 - $data = [  
30 - 'code' => 405,  
31 - 'msg' => 'Method Not Allowed',  
32 - 'currentMethod' => $m,  
33 - 'allowMethod' => $method,  
34 - ];  
35 - $response->end(json_encode($data)); 19 + $response->end();
36 return; 20 return;
37 } 21 }
38 $requestInfo = [ 22 $requestInfo = [
39 - 'method' => $request->getMethod(),  
40 'path' => $request->server['path_info'], 23 'path' => $request->server['path_info'],
41 'uri' => $request->server['request_uri'], 24 'uri' => $request->server['request_uri'],
42 'get' => $request->get ?: [], 25 'get' => $request->get ?: [],
43 'post' => $request->post ?: [], 26 'post' => $request->post ?: [],
  27 + 'method' => $request->getMethod(),
44 'header' => $request->header ?: [], 28 'header' => $request->header ?: [],
45 'cookie' => $request->cookie ?: [], 29 'cookie' => $request->cookie ?: [],
46 - 'rawContent' => $request->rawContent() ?: ''  
47 ]; 30 ];
48 - output($requestInfo['method'], $requestInfo['path'], "GET:" . json_encode($requestInfo['get']), "POST:" . json_encode($requestInfo['post']), "rawContent:" . $requestInfo['rawContent']);  
49 $simpleRequest = new SimpleRequest($requestInfo); 31 $simpleRequest = new SimpleRequest($requestInfo);
50 $res = call_user_func($callback, $simpleRequest); 32 $res = call_user_func($callback, $simpleRequest);
51 - if (empty($res)) {  
52 - $response->end(json_encode(['code' => -1, 'msg' => 'nothing return']));  
53 - } else { 33 + if(empty($res)){
  34 + $response->end(json_encode(['code'=>-1,'msg'=>'nothing return']));
  35 + }else{
54 $response->end($res); 36 $response->end($res);
55 } 37 }
56 }); 38 });
57 } 39 }
58 40
59 - public function start()  
60 - { 41 + public function start(){
61 $this->httpServer->start(); 42 $this->httpServer->start();
62 } 43 }
63 - public function stop()  
64 - {  
65 - $this->httpServer->shutdown();  
66 - }  
67 } 44 }
  45 +
1 <?php 1 <?php
2 2
3 -namespace jiaoyin; 3 +namespace Jiaoyin;
4 4
5 use Swoole\Coroutine; 5 use Swoole\Coroutine;
6 use Swoole\Coroutine\Http\Client; 6 use Swoole\Coroutine\Http\Client;
@@ -26,7 +26,7 @@ class Websocket @@ -26,7 +26,7 @@ class Websocket
26 private int $timerPing = 0; 26 private int $timerPing = 0;
27 private string $desc = 'websocket未命名'; 27 private string $desc = 'websocket未命名';
28 private int $pingCount = 0; 28 private int $pingCount = 0;
29 - private int $pingDelay = 10000; //ping tick时间 10s 29 + private int $pingDelay = 10000;//ping tick时间 10s
30 30
31 // 例如:wss://fstream.binance.com/stream、ws://175.178.36.217:9528/spot/stream 31 // 例如:wss://fstream.binance.com/stream、ws://175.178.36.217:9528/spot/stream
32 public function __construct($url, $desc = null) 32 public function __construct($url, $desc = null)
@@ -64,13 +64,9 @@ class Websocket @@ -64,13 +64,9 @@ class Websocket
64 $this->client?->close(); 64 $this->client?->close();
65 } 65 }
66 66
67 - public function connect(  
68 - callable $onOpen = null,  
69 - callable $onMessage = null,  
70 - callable $onClose = null,  
71 - $onPing = null,  
72 - $onPong = null  
73 - ): void { 67 + public function connect(callable $onOpen = null, callable $onMessage = null,
  68 + callable $onClose = null, $onPing = null, $onPong = null): void
  69 + {
74 $this->close(); 70 $this->close();
75 $this->pingCount = 0; 71 $this->pingCount = 0;
76 $this->onOpen = $onOpen; 72 $this->onOpen = $onOpen;
@@ -84,8 +80,8 @@ class Websocket @@ -84,8 +80,8 @@ class Websocket
84 $ret = $client->upgrade($this->path); 80 $ret = $client->upgrade($this->path);
85 if ($ret) { 81 if ($ret) {
86 output($this->desc, "连接成功"); 82 output($this->desc, "连接成功");
87 - $this->client = &$client;  
88 - swoole_timer_after(50, function () use ($onOpen, $client) { 83 + $this->client=&$client;
  84 + swoole_timer_after(50, function () use ($onOpen,$client) {
89 if ($onOpen) { 85 if ($onOpen) {
90 call_user_func($onOpen, $client); 86 call_user_func($onOpen, $client);
91 } 87 }
@@ -94,7 +90,7 @@ class Websocket @@ -94,7 +90,7 @@ class Websocket
94 while ($client) { 90 while ($client) {
95 $frame = $client->recv($this->recvTimeout); 91 $frame = $client->recv($this->recvTimeout);
96 if (!$frame && $client->errCode != 60) { 92 if (!$frame && $client->errCode != 60) {
97 - output($this->desc, '错误码:' . $client->errCode . ",错误数据:", $frame); 93 + output($this->desc,'错误码:' . $client->errCode.",错误数据:", $frame);
98 break; 94 break;
99 } 95 }
100 $this->lastRecvTime = time(); 96 $this->lastRecvTime = time();
  1 +<?php
  2 +
  3 +namespace Jiaoyin;
  4 +
  5 +use Swoole\Coroutine;
  6 +use Swoole\Coroutine\Http\Client;
  7 +use Swoole\WebSocket\Frame;
  8 +
  9 +class WebsocketOk
  10 +{
  11 + private string $host;
  12 + private string $path;
  13 + private int $port;
  14 + private bool $ssl;
  15 + private int $recvTimeout = 60;
  16 + public ?Client $client = null;
  17 + public string $url = '';
  18 + private int $lastRecvTime = 0;
  19 + private $onOpen = null;
  20 + private $onMessage = null;
  21 + private $onClose = null;
  22 + private $onPing = null;
  23 + private $onPong = null;
  24 + private bool $pingState = true;
  25 + private bool $reconnecting = false; //防频繁重连
  26 + private ?int $timerCheck = 0;
  27 + private string $desc = 'websocket未命名';
  28 + private int $pingCount = 0;
  29 + private int $pingDelay = 25000; // ping tick时间
  30 +
  31 + public function __construct($url, $desc = null)
  32 + {
  33 + $this->url = $url;
  34 + $pattern = '/(ws{1,2}):\/\/([\w\.-]+):*(\d*)([\/\-\w\.\?&=]*)/';
  35 + preg_match($pattern, $url, $result);
  36 + $this->ssl = $result[1] === 'wss';
  37 + $this->host = $result[2];
  38 + $this->port = empty($result[3]) ? ($this->ssl ? 443 : 80) : (int)$result[3];
  39 + $this->path = empty($result[4]) ? '/' : $result[4];
  40 + if ($desc) {
  41 + $this->desc = 'websocket ' . $desc;
  42 + }
  43 + output($this->host, $this->port, $this->ssl, $this->path);
  44 + }
  45 +
  46 + public function push($data): void
  47 + {
  48 + if ($this->client && $this->client->connected) {
  49 + $this->client->push($data);
  50 + } else {
  51 + output('push error, client not connected');
  52 + }
  53 + }
  54 +
  55 + public function close(): void
  56 + {
  57 + swoole_timer_clear($this->timerCheck);
  58 + if ($this->client) {
  59 + $this->client->close();
  60 + }
  61 + $this->client = null;
  62 + }
  63 +
  64 + public function connect(
  65 + callable $onOpen = null,
  66 + callable $onMessage = null,
  67 + callable $onClose = null,
  68 + $onPing = null,
  69 + $onPong = null
  70 + ): void {
  71 + $this->close();
  72 + $this->pingCount = 0;
  73 + $this->onOpen = $onOpen;
  74 + $this->onMessage = $onMessage;
  75 + $this->onClose = $onClose;
  76 + $this->onPing = $onPing;
  77 + $this->onPong = $onPong;
  78 +
  79 + Coroutine::create(function () use ($onOpen, $onMessage, $onClose, $onPing, $onPong) {
  80 + $client = new Client($this->host, $this->port, $this->ssl);
  81 + $client->set(['timeout' => 5]);
  82 + $ret = $client->upgrade($this->path);
  83 +
  84 + if ($ret) {
  85 + output($this->desc, "连接成功");
  86 + $this->reconnecting = false; //解除重连状态
  87 + $this->client = &$client;
  88 + swoole_timer_after(50, function () use ($onOpen, $client) {
  89 + if ($onOpen) {
  90 + call_user_func($onOpen, $client);
  91 + }
  92 + $this->sendPing();
  93 + });
  94 +
  95 + while ($client) {
  96 + $frame = $client->recv($this->recvTimeout);
  97 + if (!$frame && $client->errCode !== 60) {
  98 + output($this->desc, '错误码:' . $client->errCode . ',状态码:' . $client->statusCode . ',body:' . $client->body . ";错误数据:" . $frame);
  99 + break;
  100 + }
  101 +
  102 + $this->lastRecvTime = time();
  103 +
  104 + if (is_object($frame) && get_class($frame) === Frame::class) {
  105 + if ($frame->opcode === WEBSOCKET_OPCODE_TEXT) {
  106 + $data = json_decode($frame->data, true);
  107 + if (isset($data['event']) && $data['event'] === 'pong') {
  108 + $this->recvPongData($data['ts'] ?? null);
  109 + if ($onPong) {
  110 + call_user_func($onPong, $data);
  111 + }
  112 + } else {
  113 + $this->pingState = true; // 收到任何消息都算活跃
  114 + call_user_func($onMessage, $frame->data);
  115 + }
  116 + }
  117 +
  118 + if ($frame->opcode === WEBSOCKET_OPCODE_CLOSE) {
  119 + output($this->desc, "服务器主动关闭连接,连接关闭");
  120 + break;
  121 + }
  122 + }
  123 + }
  124 +
  125 + Coroutine::defer(function () use ($onClose) {
  126 + output($this->desc, "协程退出");
  127 + if ($this->client) {
  128 + $this->client->close();
  129 + }
  130 + $this->client = null;
  131 + if ($onClose) {
  132 + call_user_func($onClose);
  133 + }
  134 + });
  135 + } else {
  136 + if ($onClose) {
  137 + call_user_func($onClose);
  138 + }
  139 + output($this->desc, "升级websocket连接失败,1s后重连");
  140 + swoole_timer_after(3000, function () {
  141 + $this->connect($this->onOpen, $this->onMessage, $this->onClose, $this->onPing, $this->onPong);
  142 + });
  143 + }
  144 + });
  145 + }
  146 +
  147 + private function sendPing(): void
  148 + {
  149 + $pingData = 'ping';
  150 + $this->push($pingData);
  151 + $this->pingState = false;
  152 +
  153 + $this->timerCheck = swoole_timer_after(15000, function () {
  154 + if (!$this->pingState && $this->lastRecvTime < time() - 10 && !$this->reconnecting) {
  155 + $this->reconnecting = true;
  156 + output($this->desc, 'ping pong 超时且未收到数据,重新连接');
  157 + $this->connect($this->onOpen, $this->onMessage, $this->onClose, $this->onPing, $this->onPong);
  158 + } else {
  159 + if (!$this->client || !$this->client->connected) {
  160 + output($this->desc, '客户端已断开,停止Ping');
  161 + return;
  162 + } else {
  163 + $this->sendPing();
  164 + }
  165 + }
  166 + });
  167 + }
  168 + private function recvPongData($ts = null): void
  169 + {
  170 + $this->pingState = true;
  171 + if ($ts) {
  172 + $now = round(microtime(true) * 1000);
  173 + $delay = $now - $ts;
  174 + output($this->desc, "收到pong,延迟:{$delay}ms");
  175 + }
  176 + }
  177 +}
  1 +<?php
  2 +namespace Jiaoyin;
  3 +//格式化输出
  4 +use Swoole\Process;
  5 +use Swoole\Event;
  6 +
  7 +function output(): void
  8 +{
  9 + $args = func_get_args();
  10 + $outStr = '['.timeFormat('ms').']:';
  11 + foreach($args as $key => $value){
  12 + $value = is_array($value) ? json_encode($value) : $value;
  13 + if(is_bool($value)){
  14 + $value = $value ? 'bool:true':'bool:false';
  15 + }
  16 + $outStr .= count($args) - $key > 1 ? $value.' ' : $value;
  17 + }
  18 + echo $outStr.PHP_EOL;
  19 +}
  20 +//格式化时间
  21 +function timeFormat($type='s',$format='Y-m-d H:i:s'): string
  22 +{
  23 + date_default_timezone_set('Asia/Shanghai');
  24 + $microTime = microtime();
  25 + list($msTime,$sTime) = explode(' ',$microTime);
  26 + $timeStr = date($format,$sTime);
  27 + if($type == 'ms'){
  28 + $timeStr .= '.'.sprintf("%03d",floor($msTime*1000));
  29 + }
  30 + return $timeStr;
  31 +}
  32 +
  33 +// 获取当前时间的微秒数
  34 +function getMicrotime(): float|int
  35 +{
  36 + list($uSec, $sec) = explode(' ', microtime());
  37 + return $sec*1000+round($uSec*1000);
  38 +}
  39 +
  40 +//获取精度
  41 +function getPrecision($number): int
  42 +{
  43 + $count = 0;
  44 + while($number < 1){
  45 + $number *= 10;
  46 + $count += 1;
  47 + }
  48 + return $count;
  49 +}
  50 +
  51 +function getIntervalUnit($interval,$type='s'): float|int
  52 +{
  53 + $unitTime = 0;
  54 + if($interval == '1m'){
  55 + $unitTime = 60;
  56 + }
  57 + if($interval == '5m'){
  58 + $unitTime = 60*5;
  59 + }
  60 + if($interval == '15m'){
  61 + $unitTime = 60*15;
  62 + }
  63 + if($interval == '1h'){
  64 + $unitTime = 60*60;
  65 + }
  66 + if($interval == '4h'){
  67 + $unitTime = 60*60*4;
  68 + }
  69 + if($interval == '1d'){
  70 + $unitTime = 60*60*24;
  71 + }
  72 + return $type == 's' ? $unitTime : $unitTime*1000;
  73 +}
  74 +
  75 +//以守护进程运行
  76 +function runAsDaemon($callback,$isDaemon=true): void
  77 +{
  78 + if($isDaemon){
  79 + Process::daemon();
  80 + $process = new Process(function ()use ($callback){
  81 + call_user_func($callback);
  82 + Event::wait();
  83 + });
  84 + $process->start();
  85 + }else{
  86 + call_user_func($callback);
  87 + Event::wait();
  88 + }
  89 +}