API 调用
与以往版本不同的是,SDK 不再内置具体 API 的逻辑,所有的 API 均交由开发者自行调用,以更新用户备注为例:
$api = $app->getClient();
$response = $api->post('/cgi-bin/user/info/updateremark', [
'json' => [
"openid" => "oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
"remark" => "pangzi"
]
]);
// or
// 如果参数中存在query之类的关键字建议使用上面的方法,不建议以下调用方式
$response = $api->postJson('/cgi-bin/user/info/updateremark', [
"openid" => "oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
"remark" => "pangzi"
]);
语法说明
get(string $uri, array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
post(string $uri, array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
patch(string $uri, array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
put(string $uri, array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
delete(string $uri, array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
同时还内置了一些便捷方法:
postJson(string $url, array $data = [], array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
patchJson(string $url, array $data = [], array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
postXml(string $url, array $data = [], array $options = []): Symfony\Contracts\HttpClient\ResponseInterface
$options
为请求参数,可以指定 query
/body
/json
/xml
/headers
等等,具体请参考:HttpClientInterface::OPTIONS_DEFAULTS
请求参数
GET
$response = $api->get('/cgi-bin/user/list', [
'next_openid' => 'OPENID1',
]);
POST
$response = $api->post('/cgi-bin/user/info/updateremark', [
'body' => \json_encode([
"openid" => "oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
"remark" => "pangzi"
])
]);
或者可以简写为:
$response = $api->postJson('/cgi-bin/user/info/updateremark', [
"openid" => "oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
"remark" => "pangzi"
]);
或者指定 xml 格式:
$response = $api->postXml('/mmpaymkttransfers/promotion/transfers', [
'mch_appid' => $app->getConfig()['app_id'],
'mchid' => $app->getConfig()['mch_id'],
'partner_trade_no' => '202203081646729819743',
'openid' => 'ogn1H45HCRxVRiEMLbLLuABbxxxx',
'check_name' => 'FORCE_CHECK',
're_user_name'=> 'overtrue',
'amount' => 100,
'desc' => '理赔',
]);
请求证书
你可以在请求支付时指定证书,以微信支付 V2 为例:
$response = $api->post('/mmpaymkttransfers/promotion/transfers', [
'xml' => [
'mch_appid' => $app->getConfig()['app_id'],
'mchid' => $app->getConfig()['mch_id'],
'partner_trade_no' => '202203081646729819743',
'openid' => 'ogn1H45HCRxVRiEMLbLLuABbxxxx',
'check_name' => 'FORCE_CHECK',
're_user_name'=> 'overtrue',
'amount' => 100,
'desc' => '理赔',
],
'local_cert' => $app->getConfig()['cert_path'],
'local_pk' => $app->getConfig()['key_path'],
]);
文件上传
你有两种上传文件的方式可以选择:
从指定路径上传
use EasyWeChat\Kernel\Form\File;
use EasyWeChat\Kernel\Form\Form;
$options = Form::create(
[
'media' => File::fromPath('/path/to/image.jpg'),
]
)->toArray();
$response = $api->post('cgi-bin/media/upload?type=image', $options);
从二进制内容上传
use EasyWeChat\Kernel\Form\File;
use EasyWeChat\Kernel\Form\Form;
$options = Form::create(
[
'media' => File::withContents($contents, 'image.jpg'), // 注意:请指定文件名
]
)->toArray();
$response = $api->post('cgi-bin/media/upload?type=image', $options);
简化写法 6.4.0+
上面的两种传法都可以简写为下面的方式:
// withFile(string $localPath, string $formName = 'file', string $filename = null)
$media = $client->withFile($path, 'media')->post('cgi-bin/media/upload?type=image');
// withFileContents(string $contents, string $formName = 'file', string $filename = null)
$media = $client->withFileContents($contents, 'media', 'filename.png')->post('cgi-bin/media/upload?type=image');
自定义 access_token
自定义 Access Token 需要实现接口 EasyWeChat\Kernel\Contracts\AccessToken
:
class MyAccessToken implements EasyWeChat\Kernel\Contracts\AccessToken
{
public function getToken(): string
{
// 你的逻辑
return 'your token';
}
public function toQuery(): array
{
return ['access_token' => $this->getToken()];
}
}
$client->withAccessToken(new MyAccessToken());
$client->get('xxxx');
$client->post('xxxx');
//...
预置参数的传递 6.4.0+
在调用 API 的时候难免有的需要传递账号的一些信息,尤其是支付相关的 API,例如查询订单:
$client->get('v3/pay/transactions/id/1217752501201407033233368018', [
'mchid' => $app->getAccount()->getMchid(),
]);
不得不把商户号这种基础信息再读取传递一遍,比较麻烦,设计了如下的简化方案:
$client->withMchId()->get('endpoint');
// 结果:endpoint?mchid=xxxxx
注意:withMchId
默认产生的结果为:mch_id
,因为微信支付官方的各种不统一, 可能有的场景你需要的不是 mch_id
而是 mchid
, 可以通过 withMchIdAs('mchid')
来指定,比如:
$client->withMchIdAs('mchid')->get('v3/pay/transactions/id/1217752501201407033233368018');
// 结果:v3/pay/transactions/id/1217752501201407033233368018?mchid=xxxxx
原理就是 with
+ 配置 key
:
注意: 如果配置key含有下划线的,如
app_id
应该转换为大写withAppId
$client->withAppId()->post('/path/to/resources', [...]);
$client->withAppId()->withMchid()->post('/path/to/resources', [...]);
也可以自定义值:
$client->withAppId('12345678')->post('/path/to/resources', [...]);
// or
$client->with('appid', '123456')->post('/path/to/resources', [...]);
还可以设置别名:把 appid
作为参数 mch_appid
值使用:
$client->withAppIdAs('mch_appid')->post('/path/to/resources', [...]);
其它通用方法:
$client->with('appid')->post(...)
$client->with(['appid', 'mchid'])->post(...)
$client->with(['appid' => '1234565', 'mchid'])->post(...)
处理响应
API Client 基于 symfony/http-client 实现,你可以通过以下方式对响应值进行访问:
异常处理 6.3.0+
当请求失败,例如状态码不为 200 时,默认访问响应内容都会抛出异常:
$response->getContent(); // 这里会抛出异常
如果你不希望默认抛出异常,而希望自己处理,可以在配置文件指定 http.throw
参数为 false
:
$config = [
//...
'http' => [
'throw' => false,
//...
],
];
这样,你就可以在调用 API 时,自己处理异常:
$options = [
'query' => [
'openid' => 'oDF3iY9ffA-hqb2vVvbr7qxf6A0Q',
]
];
$response = $api->get('/cgi-bin/user/get', $options);
if ($response->isFailed()) {
// 出错了,处理异常
}
return $response;
或者不改变默认配置的情况下,在调用请求时单独设置throw(false)
,若该请求失败,也可以自己处理异常。
// $options 同上文,这里省略
$response = $api->get('/cgi-bin/user/get', $options)->throw(false);
if ($response->isFailed()) {
// 出错了,处理异常
}
return $response;
数组式访问
EasyWeChat 增强了 API 响应对象,比如增加了数组式访问,你可以不用每次 toArray
后再取值,更加便捷美观:
$response = $api->get('/foo/bar');
$response['foo']; // "bar"
isset($response['foo']); // true
获取状态码
$response->getStatusCode();
// 200
判断业务是否成功/失败 6.3.0+
比如状态码是 200,但是公众号接口返回 40029 code 错误:
$response->isSuccessful(); // false
$response->isFailed(); // true
获取响应头
$response->getHeaders();
// ['content-type' => ['application/json;encoding=utf-8'], '...']
$response->getHeader('content-type');
// ['application/json;encoding=utf-8']
$response->getHeaderLine('content-type');
// 'application/json;encoding=utf-8'
获取响应内容
$response->getContent();
$response->getContent(false); // 失败不抛出异常
// {"foo":"bar"}
// 获取 json 转换后的数组格式
$response->toArray();
$response->toArray(false); // 失败不抛出异常
// ["foo" => "bar"]
// 获取 json
$response->toJson();
$response->toJson(false);
// {"foo":"bar"}
// 将内容转换成流返回
$response->toStream();
$response->toStream(false); // 失败不抛出异常
转换为 PSR-7 Response 6.6.0+
如果你希望直接将 API 响应转换成 PSR-7 规范 Response,可以使用 toPsrResponse
方法:
$psrResponse = $response->toPsrResponse();
比如在 Laravel 中就可以这样使用:
return $response->toPsrResponse();
保存到文件 6.3.0+
你可以方便的将内容直接存储到文件:
$path = $response->saveAs('/path/to/file.jpg');
// /path/to/file.jpg
转换为 Data URLs 6.3.0+
你可以将内容转换为Data URLs
$dataUrl = $response->toDataUrl();
// data:image/png,%89PNG%0D%0A...
获取其他上下文信息
如:"response_headers", "redirect_count", "start_time", "redirect_url" 等:
$httpInfo = $response->getInfo();
// 获取指定信息
$startTime = $response->getInfo('start_time');
// 获取请求日志
$httpLogs = $response->getInfo('debug');
📖 更多使用请参考: HTTP client: Processing Responses
异步请求
所有的请求都是异步的,当你第一次访问 $response
时才会真正的请求,比如:
// 这段代码会立即执行,并不会发起网络请求
$response = $api->postJson('/cgi-bin/user/info/updateremark', [
"openid" => "oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
"remark" => "pangzi"
]);
// 当你尝试访问 $response 的信息时,才会发起请求并等待返回
$contentType = $response->getHeaders()['content-type'][0];
// 尝试获取响应内容将阻塞执行,直到接收到完整的响应内容
$content = $response->getContent();
并行请求
由于请求天然是异步的,那么你可以很简单实现并行请求:
$responses = [
$api->get('/cgi-bin/user/get'),
$api->post('/cgi-bin/user/info/updateremark', ['body' => ...]),
$api->post('/cgi-bin/user/message/custom/send', ['body' => ...]),
];
// 访问任意一个 $response 时将执行并发请求:
foreach ($responses as $response) {
$content = $response->getContent();
// ...
}
当然你也可以给每个请求分配名字独立访问:
$responses = [
'users' => $api->get('/cgi-bin/user/get'),
'remark' => $api->post('/cgi-bin/user/info/updateremark', ['body' => ...]),
'message' => $api->post('/cgi-bin/user/message/custom/send', ['body' => ...]),
];
// 访问任意一个 $response 时将执行并发请求:
$responses['users']->toArray();
失败重试 6.1.0+
默认在公众号、小程序开启了重试机制,你可以通过全局配置或者手动开启重试特性。
🚨 不建议在支付模块使用重试功能,因为一旦重试导致支付数据异常,可能造成无法挽回的损失。
方式一:全局配置
在支持重试的模块里增加如下配置可以完成重试机制的全局启用:
'http' => [
//...
'retry' => true, // 使用默认配置
// 'retry' => [
// // 仅以下状态码重试
// 'status_codes' => [429, 500]
// 'max_retries' => 3
// // 请求间隔 (毫秒)
// 'delay' => 1000,
// // 如果设置,每次重试的等待时间都会增加这个系数
// // (例如. 首次:1000ms; 第二次: 3 * 1000ms; etc.)
// 'multiplier' => 0.1
// ],
],
方式二:手动开启
如果你不想使用基于配置的全局重试机制,你可以使用 HttpClient::retry()
方法来开启失败重试能力:
$app->getClient()->retry()->get('/foo/bar');
当然,你可以在 retry
配置中自定义重试的配置,如下所示:
$app->getClient()->retry([
'max_retries' => 2,
//...
])->get('/foo/bar');
自定义重试策略
如果觉得参数不能满足需求,你还可以自己实现 Symfony\Component\HttpClient\RetryStrategyInterface
接口来自定义重试策略,然后调用 retryUsing
方法来使用它。
💡 建议继承基类来拓展,以实现默认重试类的基础功能。
class MyRetryStrategy extends \Symfony\Component\HttpClient\Retry\GenericRetryStrategy
{
public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool
{
// 你的自定义逻辑
// if (...) {
// return false;
// }
return parent::shouldRetry($context, $responseContent, $exception);
}
}
使用自定义重试策略:
$app->getClient()->retryUsing(new MyRetryStrategy())->get('/foo/bar');
更多使用方法
📖 更多使用请参考:symfony/http-client