2024-11-25 10:11:30 +08:00
|
|
|
|
<?php
|
|
|
|
|
namespace app\server;
|
|
|
|
|
|
|
|
|
|
use app\model\Orders;
|
|
|
|
|
use app\model\ThirdMobileLogs;
|
|
|
|
|
use http\Client;
|
|
|
|
|
use support\Log;
|
|
|
|
|
use support\Redis;
|
|
|
|
|
use think\Exception;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 抖音接口
|
|
|
|
|
*/
|
|
|
|
|
class DyApiService {
|
|
|
|
|
// 生成 client_token
|
|
|
|
|
CONST CLIENT_TOKEN = '/oauth/client_token/';
|
|
|
|
|
// 确认接单
|
|
|
|
|
CONST ORDER_CONFIRM = '/goodlife/v1/trip/trade/travelagency/order/confirm/';
|
|
|
|
|
// 订单POI信息查询接口
|
|
|
|
|
CONST POI_QUERY = '/goodlife/v1/trip/trade/travelagency/order/poi/query/';
|
|
|
|
|
|
|
|
|
|
// redis key
|
|
|
|
|
CONST DY_TOKEN_KEY = 'dy_client_key';
|
|
|
|
|
// 请求地址
|
|
|
|
|
private $host;
|
|
|
|
|
private $app_id;
|
|
|
|
|
// 加密密钥
|
|
|
|
|
private $app_secret;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构造函数.
|
|
|
|
|
*/
|
|
|
|
|
public function __construct() {
|
|
|
|
|
$this->host = env('DY_API_URL');
|
|
|
|
|
$this->app_id = env('DY_APPID');
|
|
|
|
|
$this->app_secret = env('DY_APPSECRET');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param $url
|
|
|
|
|
* @param array $data
|
|
|
|
|
* @param array $extra
|
|
|
|
|
* @return array
|
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
|
|
|
*/
|
|
|
|
|
public function send($url, array $data = [], $extra = []): array {
|
|
|
|
|
$signData = [
|
|
|
|
|
'Api-App-Key' => $this->app_id,
|
|
|
|
|
'Api-Time-Stamp' => time() * 1000,
|
|
|
|
|
];
|
|
|
|
|
// 组装请求数据
|
|
|
|
|
$reqData = $data;
|
|
|
|
|
|
|
|
|
|
// 请求头加上签名
|
|
|
|
|
// $header['Api-Sign'] = $this->getSign($signData, $reqData);
|
|
|
|
|
$header['access-token'] = $this->getAccessToken();
|
|
|
|
|
if ($this->app_id == 'sandbox289') {
|
|
|
|
|
$header['x-sandbox-token'] = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->request($url, $reqData, $header, $extra);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取token
|
|
|
|
|
* @return bool|mixed|string
|
|
|
|
|
* @throws Exception
|
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
|
|
|
*/
|
|
|
|
|
private function getAccessToken() {
|
|
|
|
|
// 测试token
|
|
|
|
|
if ($this->app_id == 'sandbox289') {
|
|
|
|
|
return 'ka.F9tiX0EN5QB5plK43W22p3cDgji9O9ZTAigryyd1A7AlbDwTmDxEN0PwjfI9';
|
|
|
|
|
}
|
|
|
|
|
$key = self::DY_TOKEN_KEY;
|
|
|
|
|
if ($token = Redis::get($key)) {
|
|
|
|
|
return $token;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$reqData = [
|
|
|
|
|
'client_key' => $this->app_id,
|
|
|
|
|
'client_secret' => $this->app_secret,
|
|
|
|
|
'grant_type' => 'client_credential',
|
|
|
|
|
];
|
|
|
|
|
$res = $this->request(self::CLIENT_TOKEN, $reqData);
|
|
|
|
|
if (!$res['flag'] || !isset($res['data']['access_token'])) {
|
|
|
|
|
throw new Exception('获取token失败');
|
|
|
|
|
}
|
|
|
|
|
Redis::setEx($key, $res['data']['expires_in'], $res['data']['access_token']);
|
|
|
|
|
return $res['data']['access_token'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取签名
|
|
|
|
|
*/
|
|
|
|
|
public function getSign($signData, $postData): string {
|
|
|
|
|
// $signData = array_merge($signData, $postData);
|
|
|
|
|
$buff = "";
|
|
|
|
|
ksort($signData);
|
|
|
|
|
// 将key和value按照顺序直接拼接到一起
|
|
|
|
|
foreach ($signData as $k => $v){
|
|
|
|
|
if (!empty($v)) {
|
|
|
|
|
if (is_array($v)) {
|
|
|
|
|
$v = json_encode(ksort($v), JSON_UNESCAPED_UNICODE);
|
|
|
|
|
}
|
|
|
|
|
$buff .= $k . $v;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 签名步骤三:在上一步的结果后直接拼secretKey
|
|
|
|
|
$str = $buff . $this->app_secret;
|
|
|
|
|
// 在上一步结果后直接拼接,经过去除空白字符(\s)的HTTP Body原始字符串
|
|
|
|
|
$str .= preg_replace('/\s+/', '', json_encode($postData));
|
|
|
|
|
// 对上一步结果进行sha1加密,得到16进制字符串
|
|
|
|
|
$str = sha1($str);
|
|
|
|
|
// 对上一步结果进行md5加密,得到16进制字符串
|
|
|
|
|
// 将上一步结果转为大写
|
|
|
|
|
return strtoupper(md5($str));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param string $url
|
|
|
|
|
* @param array $data
|
|
|
|
|
* @param array $header
|
|
|
|
|
* @param array $extra
|
|
|
|
|
* @return array
|
|
|
|
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
|
|
|
*/
|
|
|
|
|
private function request(string $url, array $data = [], $header = []): array {
|
|
|
|
|
$client = new \GuzzleHttp\Client();
|
|
|
|
|
try {
|
|
|
|
|
$reqUrl = $this->host . $url;
|
|
|
|
|
$header['Content-Type'] = 'application/json';
|
|
|
|
|
$header['charset'] = 'UTF-8';
|
|
|
|
|
$options['headers'] = $header;
|
|
|
|
|
$options['json'] = $data;
|
|
|
|
|
$method = 'POST';
|
|
|
|
|
|
|
|
|
|
$response = $client->request($method, $reqUrl, $options);
|
|
|
|
|
Log::info(sprintf('dy url:%s, req:%s, response:%s', $reqUrl, json_encode($data), $response->getBody())) ;
|
|
|
|
|
$result = ['code' => $response->getStatusCode(), 'body' => json_decode($response->getBody(), true, 512, JSON_BIGINT_AS_STRING)];
|
|
|
|
|
if (isset($result['code']) && $result['code'] == 200) {
|
|
|
|
|
if ($result['body']['data']['error_code'] == '0') {
|
|
|
|
|
return ['flag' => true, 'data' => $result['body']['data'] ?? [], 'message' => $result['body']['data']['description '] ?? ''];
|
|
|
|
|
} else {
|
|
|
|
|
return ['flag' => false, 'data' => $result['body']['data'] ?? [], 'message' => $result['body']['data']['description '] ?? ''];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ['flag' => false, 'message' => $result['body']['data']['description'] ?? '', 'data' => []];
|
|
|
|
|
} catch (\Exception $exception) {
|
|
|
|
|
Log::info(sprintf('dy req fail:%s, req:%s,res:%s', $exception->getMessage(), $exception->getFile(), $exception->getLine())) ;
|
|
|
|
|
return ['flag' => false, 'message' => $exception->getMessage(), 'data' => []];
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-11-25 17:18:00 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
1.根据ClientKev找到ClientSecret,将ClientSecret向左右使用字符补齐32位/裁剪至32位,补齐:补位字符:#,先补左侧再补右侧再补左侧………直到补满32位。
|
|
|
|
|
裁剪:先裁剪左侧再裁右侧再裁左侧………直到剩余32位。(正常不需要补齐,secret默认为32位,此举是为了
|
|
|
|
|
以防万一)
|
|
|
|
|
2.将ClientSecret作为Key,右侧16位为向量IV
|
|
|
|
|
3.将密文进行base64解码。
|
|
|
|
|
4.使用AES-256-CBC模式解密解码后的密文,对齐使用PKCS5Padding方式
|
|
|
|
|
* @param $cipherText
|
|
|
|
|
* @param $clientSecret
|
|
|
|
|
* @return false|string
|
|
|
|
|
*/
|
|
|
|
|
public function decrypt($cipherText, $clientSecret) {
|
|
|
|
|
Log::info('$cipherText:' . $cipherText);
|
|
|
|
|
// 补齐或裁剪 ClientSecret 为 32 位
|
|
|
|
|
$clientSecret = $this->padOrTruncate($clientSecret);
|
|
|
|
|
Log::info('$cipherText step 2:' . $clientSecret);
|
|
|
|
|
|
|
|
|
|
// 使用 ClientSecret 作为密钥
|
|
|
|
|
$key = $clientSecret;
|
|
|
|
|
|
|
|
|
|
// 右侧16位为初始化向量IV
|
|
|
|
|
$iv = substr($key, 16, 16);
|
|
|
|
|
|
|
|
|
|
// 将密文 base64 解码
|
|
|
|
|
$cipherTextDecoded = base64_decode($cipherText);
|
|
|
|
|
|
|
|
|
|
// 使用 AES-256-CBC 解密
|
|
|
|
|
return openssl_decrypt($cipherTextDecoded, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function encrypt($cipherText, $clientSecret) {
|
|
|
|
|
Log::info('$cipherText:' . $cipherText);
|
|
|
|
|
// 补齐或裁剪 ClientSecret 为 32 位
|
|
|
|
|
$clientSecret = $this->padOrTruncate($clientSecret);
|
|
|
|
|
Log::info('$cipherText step 2:' . $clientSecret);
|
|
|
|
|
|
|
|
|
|
// 使用 ClientSecret 作为密钥
|
|
|
|
|
$key = $clientSecret;
|
|
|
|
|
|
|
|
|
|
// 右侧16位为初始化向量IV
|
|
|
|
|
$iv = substr($key, 16, 16);
|
|
|
|
|
|
|
|
|
|
// 使用 AES-256-CBC 解密
|
|
|
|
|
return base64_encode(openssl_encrypt($cipherText, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param $clientSecret
|
|
|
|
|
* @return false|string
|
|
|
|
|
*/
|
|
|
|
|
private function padOrTruncate($clientSecret) {
|
|
|
|
|
// 1. 如果 clientSecret 长度为32,直接返回
|
|
|
|
|
if (strlen($clientSecret) == 32) {
|
|
|
|
|
return $clientSecret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 如果 length < 32,补齐至32位
|
|
|
|
|
if (strlen($clientSecret) < 32) {
|
|
|
|
|
// 补齐过程:先补左侧,再补右侧,再补左侧,直到长度为32
|
|
|
|
|
$paddingChar = '#';
|
|
|
|
|
$padded = $clientSecret;
|
|
|
|
|
|
|
|
|
|
// 左侧补齐
|
|
|
|
|
$left = true;
|
|
|
|
|
while (strlen($padded) < 32) {
|
|
|
|
|
if ($left) {
|
|
|
|
|
$padded = $paddingChar . $padded;
|
|
|
|
|
$left = false;
|
|
|
|
|
} else {
|
|
|
|
|
$padded = $padded . $paddingChar;
|
|
|
|
|
$left = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return substr($padded, 0, 32); // 确保返回32位
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 如果 length > 32,裁剪至32位
|
|
|
|
|
if (strlen($clientSecret) > 32) {
|
|
|
|
|
// 裁剪过程:先裁左侧,再裁右侧,再裁左侧,直到长度为32
|
|
|
|
|
$cropped = $clientSecret;
|
|
|
|
|
|
|
|
|
|
// 左侧裁剪
|
|
|
|
|
while (strlen($cropped) > 32) {
|
|
|
|
|
$cropped = substr($cropped, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 右侧裁剪
|
|
|
|
|
while (strlen($cropped) > 32) {
|
|
|
|
|
$cropped = substr($cropped, 0, -1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 左侧裁剪(再一次)
|
|
|
|
|
while (strlen($cropped) > 32) {
|
|
|
|
|
$cropped = substr($cropped, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return substr($cropped, 0, 32); // 确保返回32位
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $clientSecret;
|
|
|
|
|
}
|
2024-11-25 10:11:30 +08:00
|
|
|
|
}
|