Просмотр исходного кода

新增即梦AI接口签名方法

lh 8 месяцев назад
Родитель
Сommit
58d3156862
1 измененных файлов с 181 добавлено и 0 удалено
  1. 181 0
      app/Services/VolcEngineService.php

+ 181 - 0
app/Services/VolcEngineService.php

@@ -0,0 +1,181 @@
+<?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,
+        ];
+    }
+}