ThirdPartyTrait.php
19.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
<?php
/**
* +-----------------------------------------------------------------------------------------------------------------------
* trait :第三方相关接口 trait
* +-----------------------------------------------------------------------------------------------------------------------
*
* PHP version 7
*
* @category App\Models\Traits
* @package App\Models\Traits
* @author Richer <yangzi1028@163.com>
* @date 2022年9月29日15:44:28
* @copyright 2020-2021 Richer (http://www.Richer.com/)
* @license http://www.Richer.com/ License
* @link http://www.Richer.com/
*/
namespace App\Models\Traits;
use App\Models\Bus;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Http\Response;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
/**
* Trait ThirdPartyTrait
*
* @category App\Models\Traits
* @package App\Models\Traits
* @author Richer <yangzi1028@163.com>
* @date 2022年6月30日09:45:22
* @copyright 2020-2021 Richer (http://www.Richer.com/)
* @license http://www.Richer.com/ License
* @link http://www.Richer.com/
*/
trait ThirdPartyTrait
{
// 接口地址
protected $service_url = 'http://open.aichezaixian.com/route/rest';
// 几米分给客户的APP_KEY
protected $app_key = 'de';
// 用户ID
protected $user_id = 'dw';
// 用户ID密码
protected $user_pwd = '123';
// 操作提示
protected $message = '操作成功';
// 日志频道
protected $channel = 'third-party';
/**
* 获取日志 channel
*
* @return string
*/
public function getLogChannel(): string
{
return $this->channel;
}
/**
* 获取请求url
*
* @return string
*/
public function getServiceUrl(): string
{
// 生产环境
if (config('app.env') === 'production') {
// $this->service_url = 'http://xxlapi.mengbashi.cn/api/riceTransferZsh';
return 'https://zsh.mengbashi.cn';
}
return 'http://zhangshanghui.dev.zhangxinkeji.com';
}
/**
* 获取 accesstoken
* 接口说明
* 获取后续访问接口的令牌,1分钟内只能访请求1次,在有效期内访问token不变。
*
* 请求URL
* 本接口通用参数里面method的值为jimi.oauth.token.get
*
* @return false|Response
*/
public function getAccessToken()
{
record_log($this->channel, '获取 accesstoken', 'begin');
$access_token = Cache::get('access_token');
if (!$access_token) {
// 1、组合 API输入参数
$params = [
'user_id' => $this->user_id,
'user_pwd_md5' => md5($this->user_pwd), // MD5结果是小写的
'expires_in' => 7200, // access token的有效期,以秒为单位 60-7200
];
record_log($this->channel, 'API输入参数:' . json_encode($params));
// 2、获取API输入参数签名结果
$sign = $this->generateSign($params);
record_log($this->channel, '签名 sign:' . $sign);
// 3、获取通用参数
$method = 'jimi.oauth.token.get';
$common_params = $this->getGeneralParams($method, $sign);
record_log($this->channel, '通用参数:' . json_encode($common_params));
// 4、组合参数
$params = array_merge($params, $common_params);
record_log($this->channel, '完整请求参数:' . json_encode($params));
$header = ['Content-Type' => 'application/x-www-form-urlencoded'];
$url = $this->service_url;
$response = $this->clientRequestForm($url, $header, $params, $this->channel);
// $response = $this->clientRequest($url, 'POST', $header, $params, $this->channel);
$result = $this->dealResponse($response);
record_log($this->channel, '获取 accesstoken', 'end');
if ($result === false) {
return false;
}
// 获取 access_token 和 refresh_token
$access_token = Arr::get($result, 'accessToken');// 后续接口访问的访问令牌,对应到公司下的帐号
$refresh_token = Arr::get($result, 'refreshToken'); // 刷新令牌,用于更新accessToken
$expires_in = Arr::get($result, 'expiresIn'); // access_token访问过期时间【单位是秒】,还有多少秒后过期
Cache::put('access_token', $access_token, $expires_in); //
Cache::forever('refresh_token', $refresh_token);
}
return $access_token;
}
/**
* 刷新 accesstoken
* 接口说明
* 获取后续访问接口的令牌,1分钟内只能访请求1次,在有效期内访问token不变。
*
* 请求URL
* 本接口通用参数里面method的值为jimi.oauth.token.get
*
* @return false|Response
*/
public function refreshAccessToken()
{
$method = 'jimi.oauth.token.refresh';
record_log($this->channel, '刷新 accesstoken', 'begin');
// 1、组合 API输入参数
$params = [
'user_id' => $this->user_id,
'user_pwd_md5' => md5($this->user_pwd), // MD5结果是小写的
'expires_in' => 7200, // access token的有效期,以秒为单位 60-7200
];
record_log($this->channel, 'API输入参数:' . json_encode($params));
// 2、获取API输入参数签名结果
$sign = $this->generateSign($params);
record_log($this->channel, '签名 sign:' . $sign);
// 3、获取通用参数
$common_params = $this->getGeneralParams($method, $sign);
record_log($this->channel, '通用参数:' . json_encode($common_params));
// 4、组合参数
$params = array_merge($params, $common_params);
record_log($this->channel, '完整请求参数:' . json_encode($params));
$header = ['Content-Type'=> 'application/json'];
$url = $this->service_url;
$result = $this->clientRequest($url, 'POST', $header, $params, $this->channel);
record_log($this->channel, '刷新 accesstoken', 'end');
if ($result === false) {
return false;
}
$code = Arr::get($result, 'code');
if ($code === 200) {
return true;
} else {
$this->message = Arr::get($result, 'msg');
return false;
}
}
/**
* 根据IMEI获取最新定位数据
* 接口说明
* 获取单个或多个设备最新的位置信息。
*
* 请求URL
* 本接口通用参数里面method的值为jimi.device.location.get
*
* @param array $imeis
* @return false|Response
*/
public function getLocation($imeis = [])
{
record_log($this->channel, '获取 accesstoken', 'begin');
$method = 'jimi.device.location.get';
if (!$imeis) {
$this->message = "设备IMEI为空。";
return false;
}
// 1、组合 API输入参数
$params = [
'access_token' => $this->getAccessToken(), // 访问令牌,表明其是一个合法第三方
'imeis' => implode(',', $imeis), // 设备imei号,多个中间用英文逗号隔开。 如果设备过多,建议采用POST方式(一次最多100个IMEI)
'map_type' => 'BAIDU', // 如果要显示在百度地图上,map_type=BAIDU此时返回的经纬度将经过baidu校准方式校准 如果要显示在google地图上,map_type=GOOGLE,此时返回的经纬度将经过google校准方式校准 map_type如果不填,则返回原始经纬度
];
record_log($this->channel, 'API输入参数:' . json_encode($params));
// 2、获取API输入参数签名结果
$sign = $this->generateSign($params);
record_log($this->channel, '签名 sign:' . $sign);
// 3、获取通用参数
$common_params = $this->getGeneralParams($method, $sign);
record_log($this->channel, '通用参数:' . json_encode($common_params));
// 4、组合参数
$params = array_merge($params, $common_params);
record_log($this->channel, '完整请求参数:' . json_encode($params));
$header = ['Content-Type' => 'application/json'];
$url = $this->service_url;
$response = $this->clientRequest($url, 'POST', $header, $params, $this->channel);
$result = $this->dealResponse($response);
record_log($this->channel, '获取 accesstoken', 'end');
if ($result === false) {
return false;
}
return $result;
$imeis = data_get($result, '*.imei');
$records = [];
// 去查询全部的设备
Bus::whereIn('imei', $imeis)->get()->each(function ($bus) use (&$records, $result) {
// 获取全部的车辆信息
foreach ($result as $item) {
//
$imei = Arr::get($item, 'imei');
if ($imei === $bus->imei) {
$records[] = [
'bus_id' => $bus->id,
'latitude' => Arr::get($item, 'lat'),
'longitude' => Arr::get($item, 'lng'),
'speed' => Arr::get($item, 'speed'),
'status' => Arr::get($item, 'status'),
];
}
// 更加imei
}
});
}
/**
* 进行 http 请求
*
* @param $url
* @param $method
* @param $header
* @param array $data
* @param string $channel
* @return array|false
*/
protected function clientRequest($url, $method, $header = [], $data = [], $channel = 'third-party')
{
try {
record_log($channel, '请求 url: ' . $url);
record_log($channel, '请求 参数: ' . json_encode($data));
dump($data);
$client = new Client();
$response = $client->request($method, $url, [
'headers' => $header,
'json' => $data,
'form_params'=> $data
]);
$status = $response->getStatusCode(); // 200
record_log($channel, '响应 status: ' . $status);
$reason = $response->getReasonPhrase(); // OK
record_log($channel, '响应 reason: ' . $reason);
if ($status == 200) {
$content = json_decode($response->getBody()->getContents(), true);
dd($content);
record_log($channel, '响应 content: ' . json_encode($content));
return $content;
} else {
$this->message = $reason;
return false;
}
} catch (GuzzleException $e) {
record_log($channel, '错误信息: ' . $e->getMessage());
$this->message = $e->getMessage();
return false;
}
}
/**
* 进行 http 请求
*
* @param $url
* @param $method
* @param array $data
* @param string $channel
* @return array|false
*/
protected function clientRequestForm($url, $data = [], $channel = 'third-party', $method = 'POST')
{
try {
record_log($channel, '请求 url: ' . $url);
record_log($channel, '请求 参数: ' . json_encode($data));
$client = new Client();
$header = ['Content-Type'=> 'application/x-www-form-urlencoded;charset=UTF-8'];
$response = $client->request($method, $url, [
'headers' => $header,
'form_params'=> $data
]);
$status = $response->getStatusCode(); // 200
record_log($channel, '响应 status: ' . $status);
$reason = $response->getReasonPhrase(); // OK
record_log($channel, '响应 reason: ' . $reason);
if ($status == 200) {
$content = json_decode($response->getBody()->getContents(), true);
record_log($channel, '响应 content: ' . json_encode($content));
return $content;
} else {
$this->message = $reason;
return false;
}
} catch (GuzzleException $e) {
record_log($channel, '错误信息: ' . $e->getMessage());
$this->message = $e->getMessage();
return false;
}
}
/**
* 生成 sign
*
* 以执行店铺查询https://openapi.jushuitan.com/open/shops/query 为例,
* 假如开发者的app_secret是e9c5ca33fecb404b8e6cdbd0ef4a6d25,请求的参数如下:
* app_key:5b53060f23d84ddf9703056e84fa5a2d
* access_token:d7b01bf0842a4742a9450e21ffd95f60
* timestamp:1639128407
* version:2
* charset:utf-8
* sign:395f5a78b446be465ac03a02491296c7
* biz:{"page_index":"1","page_size":"100","nicks":["老板"]}
* 步骤:
* 将请求参数中除 sign 外的多个键值对,根据键按照字典序排序,并按照 "key1value1key2value2..." 的格式拼成一个字符串。请求参数拼接后的结果为:access_tokend7b01bf0842a4742a9450e21ffd95f60app_key5b53060f23d84ddf9703056e84fa5a2dbiz{"page_index":"1","page_size":"100","nicks":["老板"]}charsetutf-8timestamp1639128407version2
* 将 app_secret 拼接在 1 中排序后的字符串前面得到待签名字符串:e9c5ca33fecb404b8e6cdbd0ef4a6d25access_tokend7b01bf0842a4742a9450e21ffd95f60app_key5b53060f23d84ddf9703056e84fa5a2dbiz{"page_index":"1","page_size":"100","nicks":["老板"]}charsetutf-8timestamp1639128407version2
* 使用 MD5 算法加密待加密字符串并转为32位小写即为 sign: 395f5a78b446be465ac03a02491296c7
* 将 sign 添加到请求参数中
* 注意:
*
* 对于biz这类复杂参数,不论value内部是否包含多个字段,均把value看作一个完整字符串来处理,不需要对内部字段进行拆分和排序。
* 请求参数中有中文时,中文需要经过 url 编码,但计算签名时不需要。
* 计算 MD5 签名时,需要以 utf-8 的编码转换 byte 流,否则可能导致含中文参数的签名计算不正确
*
* @param $body
* @return string|null
*/
public function generateSign($body): ?string
{
record_log($this->channel, '获取 sign');
if ($body == null) {
return null;
}
// 1、根据键按照字典序排序
ksort($body);
// 2、并按照 "key1value1key2value2..." 的格式拼成一个字符串
$str = "";
foreach ($body as $key => $vo) {
if ($key != null && $key != "" && $key != "sign") {
$str .= $key.$vo;
}
}
record_log($this->channel, '拼接字符串'. $str);
// dump($str);
// dump($str);
record_log($this->channel, '拼接字符串'. $str);
// 4.使用 MD5 算法加密待加密字符串并转为32位小写即为
// $sign = strtolower(md5($str));
// dump($sign);
$sign = bin2hex(md5($str, true));
// dump($sign);
record_log($this->channel, 'sign:'. $sign);
return $sign;
}
/**
* 获取 sign
*
* @param $params
* @return string
*/
protected function getSign($params): string
{
// 1、 获取需要进行验签的参数:去掉不需要验证参数
// foreach ($params as $key => $vo) {
// if (!in_array($key, self::SING_PARAM_OPTIONS)) {
// unset($params[$key]);
// }
// }
// 2、去掉参数值为空的参数,并组合成字符串
$str = $this->getSignContent($params)."&key=".$this->getPrivateMD5Key();
record_log($this->channel, "签名串 : $str");
$sign = strtoupper(md5($str)); // key对应商户私钥通过安卓APK工具解析出来的MD5KEY
record_log($this->channel, "加密签名串 : $sign");
return $sign;
// 2、 对数组的值按照 key 进行升序
// ksort($params);
// dump($sign);
// // 3、生成 url 形式:参数=参数值
// $params = http_build_query($params);
// dump($params);
// // 4、生成签名,并转换成大写
//// $sign = strtoupper(md5($params));
// dump($sign);
}
/**
* 获取验签的内容
*
* @param $params
* @return string
*/
protected function getSignContent($params)
{
ksort($params);
$stringToBeSigned = "";
$i = 0;
foreach ($params as $k => $v) {
if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
if ($i == 0) {
$stringToBeSigned .= "$k" . "=" . "$v";
} else {
$stringToBeSigned .= "&" . "$k" . "=" . "$v";
}
$i++;
}
}
unset($k, $v);
return $stringToBeSigned;
}
/**
* 验证参数是否为空
*
* @param $value
* @return bool
*/
protected function checkEmpty($value)
{
if (!isset($value)) {
return true;
}
if ($value === null) {
return true;
}
if (trim($value) === "") {
return true;
}
return false;
}
/**
* 通用参数
*
* method String 是 API接口名称
* timestamp String 是 时间戳,格式为yyyy-MM-dd HH:mm:ss允许与系统时间误差正负10分钟,例如:2012-03-25 20:00:00
* app_key String 是 几米分给客户的APP_KEY
* sign String 是 API输入参数签名结果
* sign_method String 是 可选,指定签名方式。系统默认md5,支持方式有:md5 md5 md5
* v String 是 可选,指定API版本。系统默认1.0,支持版本有:0.9、1.0 0.9:不进行签名校验 1.0:会进行签名校验
* format String 是 可选,指定响应格式。系统默认json json
*
* @param string $method
* @param string $sign
* @return array
*/
protected function getGeneralParams($method = '', $sign = ''): array
{
return [
'method' => $method, // 接口名称
'timestamp' => now()->toDateTimeString(), // 时间戳
'app_key' => $this->app_key, // 几米分给客户的APP_KEY
'sign' => $sign, // API输入参数签名结果
'sign_method' => 'md5', // 可选,指定签名方式。系统默认md5,支持方式有:md5
'v' => '1.0', // 可选,指定API版本。系统默认1.0,支持版本有:0.9、1.0 0.9:不进行签名校验 1.0:会进行签名校验
'format' => 'json', // 可选,指定响应格式。系统默认json
];
}
/**
* 处理请求响应结果
* @param $response
* @return bool|array
*/
protected function dealResponse($response)
{
$code = Arr::get($response, 'code');
if ($code !== 0) {
$this->message = Arr::get($response, 'message');
return false;
}
return Arr::get($response, 'result');
}
}