| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- <?php
- namespace App\Services;
- use GuzzleHttp\Client;
- use GuzzleHttp\Exception\GuzzleException;
- class VolcEngineService
- {
- /** @var Client */
- protected $client;
- public function __construct()
- {
- $this->client = new Client([
- 'timeout' => 300,
- ]);
- }
- /**
- * 通用请求方法:内部完成火山引擎签名并发送 HTTP 请求
- *
- * @param string $method HTTP 方法:GET / POST / PUT ...
- * @param string $host 目标 Host,例如:iam.volcengineapi.com
- * @param string $path 请求路径,例如:"/" 或 "/"
- * @param array $query 查询参数(不含 Action/Version)
- * @param string $body 请求 Body 字符串(GET 一般为空字符串)
- * @param string $ak AccessKeyId
- * @param string $sk SecretAccessKey
- * @param string $service 服务名,例如:iam / vod / sms 等
- * @param string $region 区域,例如:cn-north-1
- * @param string $action 公共参数 Action
- * @param string $version 公共参数 Version
- * @param string $contentType Content-Type,默认 application/json
- *
- * @return array [
- * 'status' => int,
- * 'headers' => array,
- * 'body' => string,
- * ]
- * @throws GuzzleException
- */
- public function request(
- string $method,
- string $host,
- string $path,
- array $query,
- string $body,
- string $ak,
- string $sk,
- string $service,
- string $region,
- string $action,
- string $version,
- string $contentType = 'application/json'
- ): array {
- $signed = $this->sign(
- $method,
- $host,
- $path,
- $query,
- $body,
- $ak,
- $sk,
- $service,
- $region,
- $action,
- $version,
- $contentType
- );
- $response = $this->client->request($method, 'https://' . $host . $path, [
- 'headers' => $signed['headers'],
- 'query' => $signed['query'],
- 'body' => $body,
- ]);
- return [
- 'status' => $response->getStatusCode(),
- 'headers' => $response->getHeaders(),
- 'body' => (string) $response->getBody(),
- ];
- }
- /**
- * 生成火山引擎签名头部和 query
- *
- * @return array [
- * 'headers' => [...], // 需要加到 HTTP Header 的字段
- * 'query' => [...], // 已合并 Action/Version 并排序后的 query
- * ]
- */
- public function sign(
- string $method,
- string $host,
- string $path,
- array $query,
- string $body,
- string $ak,
- string $sk,
- string $service,
- string $region,
- string $action,
- string $version,
- string $contentType = 'application/json'
- ): array {
- // 1. 合并公共参数,并按 key 排序
- $query = array_merge($query, [
- 'Action' => $action,
- 'Version' => $version,
- ]);
- ksort($query);
- // 2. 时间与 Body 哈希
- $xDate = gmdate('Ymd\THis\Z'); // 例如 20250105T081030Z
- $shortDate = substr($xDate, 0, 8); // 例如 20250105
- $xContentSha256 = hash('sha256', $body);
- // 3. CanonicalRequest
- $signedHeaders = 'content-type;host;x-content-sha256;x-date';
- $canonicalHeaders = implode("\n", [
- 'content-type:' . $contentType,
- 'host:' . $host,
- 'x-content-sha256:' . $xContentSha256,
- 'x-date:' . $xDate,
- ]);
- $canonicalQueryString = http_build_query($query);
- $canonicalRequest = implode("\n", [
- strtoupper($method),
- $path,
- $canonicalQueryString,
- $canonicalHeaders,
- '',
- $signedHeaders,
- $xContentSha256,
- ]);
- $hashedCanonicalRequest = hash('sha256', $canonicalRequest);
- // 4. StringToSign
- $credentialScope = implode('/', [$shortDate, $region, $service, 'request']);
- $stringToSign = implode("\n", [
- 'HMAC-SHA256',
- $xDate,
- $credentialScope,
- $hashedCanonicalRequest,
- ]);
- // 5. 派生签名 key
- $kDate = hash_hmac('sha256', $shortDate, $sk, true);
- $kRegion = hash_hmac('sha256', $region, $kDate, true);
- $kService = hash_hmac('sha256', $service, $kRegion, true);
- $kSigning = hash_hmac('sha256', 'request', $kService, true);
- // 6. 计算最终签名
- $signature = hash_hmac('sha256', $stringToSign, $kSigning);
- $authorization = sprintf(
- 'HMAC-SHA256 Credential=%s, SignedHeaders=%s, Signature=%s',
- $ak . '/' . $credentialScope,
- $signedHeaders,
- $signature
- );
- $headers = [
- 'Host' => $host,
- 'Content-Type' => $contentType,
- 'X-Content-Sha256' => $xContentSha256,
- 'X-Date' => $xDate,
- 'Authorization' => $authorization,
- ];
- return [
- 'headers' => $headers,
- 'query' => $query,
- ];
- }
- }
|