<?php
declare(strict_types=1);
namespace App\Services\Platform;
use App\Models\Shop;
use CURLFile;
use Exception;
use Illuminate\Support\Facades\Redis;
class KfzClient
{
private string $appKey;
private string $appSecret;
private string $method;
private Shop $shop;
public function __construct(Shop $shop)
{
$this->appKey = "448";
$this->appSecret = "82da9ff496e4244717f149d7e671fbd9bb0e0bbfc8d1041429a9244254cf2b3a";
$this->shop = $shop;
if ($shop['app_key'] && $shop['app_secret']) {
$this->appKey = $shop['app_key'];
$this->appSecret = $shop['app_secret'];
}
}
public function setAccessToken(string $accessToken): void
{
$this->shop['access_token'] = $accessToken;
}
protected function getCommonParams()
{
return [
'appId' => $this->appKey,
'format' => 'json',
'v' => '1.0',
'signMethod' => 'md5',
'simplify' => 1,
'accessToken' => $this->shop['access_token'],
'method' => $this->method,
'datetime' => date('Y-m-d H:i:s', time()),
];
}
/**
* 获取授权链接
*
* @param $state
* @return string
*/
public function getAuthorizedUrl($state): string
{
$redirectUri = urlencode('https://token.shumaibao.com/index/kfz');
return "https://open.kongfz.com/v1/oauth2/authorize?appId=$this->appKey&responseType=code&state=$state&redirectUri=$redirectUri";
}
public function decrApiNumOptimized($adminKey, $adminId, $num)
{
$lua = <<<LUA
local adminKey = KEYS[1]
local adminField = KEYS[2]
local num = tonumber(KEYS[3])
local total = tonumber(redis.call('HGET', adminKey, adminField) or "0")
if total < -num then
return -1
end
redis.call('HINCRBY', adminKey, adminField, num)
return 1
LUA;
$result = Redis::eval(
$lua,
3,
[$adminKey, $adminId, $num]
);
return $result == 1;
}
/**
* 请求接口
* @param string $method
* @param array $paramMap
* @return array
* @throws Exception
*/
public function request(string $method, array $paramMap = []): array
{
$this->method = $method;
$url = 'https://open.kongfz.com/router/rest';
$data = array_merge($this->getCommonParams(), $paramMap);
$data['sign'] = $this->getSign($data);
try {
$response = $this->sendRequest($url, $data);
if ($method === 'kongfz.image.upload') {
$num = -4;
if (!$this->decrApiNumOptimized('admin_api_num', $this->shop['admin_id'], $num)) {
throw new Exception('接口请求次数不足');
}
Redis::zadd('api_log',time(), json_encode([
'admin_id' => $this->shop['admin_id'],
'title'=> '孔夫子-'.$this->shop['name'].'上传图片',
'num' => $num,
'createtime'=> time()
]));
}
} catch (Exception $e) {
if ($e->getMessage() === "接口请求次数不足") {
Shop::where('id', $this->shop['id'])->update([
'status' => "0",
'warning_msg' => '接口请求次数不足'
]);
throw new Exception('接口请求次数不足');
} else {
return [
'errorResponse' => [
'code' => 500,
'subMsg' => '接口请求失败' . $e->getMessage()
],
'successResponse' => null
];
}
}
return $this->returnResult($response);
}
/**
* 发送请求
*
* @param string $url
* @param array $params
* @param bool $hasFile
*
* @return array
* @throws Exception
*/
protected function sendRequest(string $url, array $params, bool $hasFile = false): array
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 禁用SSL证书验证
if ($hasFile) {
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: multipart/form-data']);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
} else {
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
}
//超时
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$ret = curl_exec($ch);
if (false === $ret) {
$err = curl_error($ch);
$errno = curl_errno($ch);
$info = curl_getinfo($ch);
curl_close($ch);
throw new Exception("请求失败,错误码:{$errno},错误信息:{$err},curl信息:{$info['url']}");
}
$err = curl_error($ch);
if (!empty($err)) {
$errno = curl_errno($ch);
$info = curl_getinfo($ch);
curl_close($ch);
return [
'ret' => false,
'errno' => $errno,
'msg' => $err,
'info' => $info,
];
}
curl_close($ch);
return [
'ret' => true,
'msg' => $ret,
];
}
/**
* 上传图片
*
* @param string $imagePath
*
* @return array
* @throws Exception
*/
public function upload(string $imagePath): array
{
$this->method = 'kongfz.image.upload';
$postData = $this->getCommonParams();
$postData['bucket'] = 'book';
$postData['sign'] = $this->getSign($postData);
$postData['image'] = new CURLFile($imagePath);
$uri = 'https://open.kongfz.com/router/image/upload';
$response = $this->sendRequest($uri, $postData, true);
return $this->returnResult($response);
}
/**
* 返回结果
*
* @param $response
* @return array
*/
protected function returnResult($response): array
{
if ($response['ret']) {
$body = json_decode($response['msg'], true);
if (empty($body)) {
return [
'errorResponse' => [
'code' => 500,
'subMsg' => '接口请求失败:' . $response['msg']
],
'successResponse' => null
];
}
if (!empty($body['errorResponse']['code']) && in_array($body['errorResponse']['code'], [2000, 2001])) {
//授权过期
// 2000 => '无效的Access Token',
// 2001 => 'Access Token已经过期',
Shop::where('id', $this->shop['id'])
->update([
'status' => 0,
'warning_msg' => '授权过期或失效'
]);
}
return $body;
} else {
return [
'errorResponse' => [
'code' => 500,
'subMsg' => '接口请求失败:' . $response['msg']
],
'successResponse' => null
];
}
}
/**
* 刷新token
* @param $refreshToken
* @return array
* @throws \Exception
*/
public function refreshToken($refreshToken): array
{
$api = 'https://open.kongfz.com/v1/oauth2/refresh';
$data = [
'grantType' => 'refresh_token',
'appId' => $this->appKey,
'appSecret' => $this->appSecret,
'refreshToken' => $refreshToken
];
return $this->returnResult($this->sendRequest($api, $data));
}
/**
* 获取access_token
* @param $code
* @return array[]
* @throws \Exception
*/
public function getAccessToken($code): array
{
$data = [
'code' => $code,
'grantType' => 'authorization_code',
'appId' => $this->appKey,
'appSecret' => $this->appSecret,
'redirectUri' => 'https://token.shumaibao.com/index/kfz'
];
$api = 'https://open.kongfz.com/v1/oauth2/token';
return $this->returnResult($this->sendRequest($api, $data));
}
/**
* 获取店铺分类
* @return array
* @throws Exception
*/
public function getShopCategory()
{
$shopCategoryResponse = $this->request('kongfz.shop.category.name.list');
$shopCategoryList = [];
if ($shopCategoryResponse['successResponse']) {
foreach ($shopCategoryResponse['successResponse'] as $value) {
$shopCategoryList[$value['value']] = $value['name'];
}
}
return $shopCategoryList;
}
/**
* 获取店铺运费模板
* @return array
* @throws Exception
*/
public function getDeliveryTemplate()
{
$shopDeliveryResponse = $this->request('kongfz.delivery.template.name.list');
$shopDeliveryList = [];
if ($shopDeliveryResponse['successResponse']) {
foreach ($shopDeliveryResponse['successResponse'] as $value) {
$shopDeliveryList[$value['mouldId']] = $value['mouldName'];
}
}
return $shopDeliveryList;
}
// /**
// * 获取错误信息
// * @param $code
// * @param $msg
// * @return string
// */
/*private function getErrorMessage($code, $msg)
{
$error = [
1000 => '授权码错误或已经过期',
1001 => '授权码不存在',
// 1002 => '系统错误,建议清理浏览器缓存,稍后重试',
// 1003 => '参数错误,xx参数必须',
1004 => '无效的appId',
1005 => 'appId对应的应用目前不可用',
1006 => '权限错误',
1007 => '应用回调地址错误,请检查回调地址是否为空,或含有非法字符',
1008 => '用户没有同意授权',
1009 => '调用次数受限',
1010 => '用户没有登录',
1011 => '无效的App Secret',
1012 => '无效的scope',
1013 => '无效的Refresh Token',
2000 => '无效的Access Token',
2001 => 'Access Token已经过期',
// 2002 => '参数错误,缺少xx参数或xx参数错误',
2003 => '无效的签名',
2004 => '编码错误',
2005 => '用户权限不足',
2006 => '不存在的方法',
2007 => '服务不可用',
// 3000 => '参数错误,缺少xx参数或xx参数错误',
3001 => '权限不足,非法访问',
3002 => '查询无结果',
3003 => '系统错误,业务逻辑错误',
500 => '接口请求失败'
];
return $error[$code] ?? $msg;
}*/
public function getSign($paramMap): string
{
//按字母顺序排序索引
ksort($paramMap);
$signString = '';
foreach ($paramMap as $key => $value) {
if ($key != 'sign' && !is_array($value)) {
$signString .= "$key$value";
}
}
switch ($paramMap['signMethod']) {
case 'hmac':
return strtoupper(hash_hmac('md5', $signString, $this->appSecret));
case 'md5':
default:
return strtoupper(hash('md5', "$this->appSecret$signString{$this->appSecret}"));
}
}
}
改为node实现
最新发布