* * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ declare(strict_types=1); namespace App\Service; use Hyperf\Config\Annotation\Value; use Hyperf\Di\Annotation\Inject; use Hyperf\Guzzle\ClientFactory; use function Hyperf\Coroutine\co; /** * Author: ykxiao * Date: 2025/6/3 * Time: 下午7:18 * Description: 阿里云日志服务. * * (c) ykxiao * * This source file is subject to the MIT license that is bundled * with this source code in the file LICENSE. */ class AliLogsSignService { protected const string LOG_SIGNATURE_METHOD = 'hmac-sha1'; protected const string LOG_API_VERSION = '0.6.0'; #[Value('alibaba.accessKeyId')] protected string $accessKeyId; #[Value('alibaba.accessKeySecret')] protected string $accessKeySecret; #[Value('alibaba.logs')] protected array $logs; #[Inject] protected ClientFactory $clientFactory; /** * 签名及请求头拼接. * @param mixed $method * @param mixed $uri * @param mixed $params * @param mixed $body * @param mixed $logProject * @param mixed $logEndpoint */ public function buildHeaders( string $method, string $uri, array $params, string $body, string|object $logProject, string $logEndpoint ): array { $headers = [ 'x-log-signaturemethod' => self::LOG_SIGNATURE_METHOD, 'x-log-apiversion' => self::LOG_API_VERSION, 'Host' => sprintf('%s.%s', $logProject, $logEndpoint), 'Content-Type' => 'application/json', ]; $contentLength = 0; $contentMd5 = ''; if (!empty($body) && strlen($body) > 0) { $contentLength = strlen($body); $contentMd5 = strtoupper(md5($body)); $headers['Content-MD5'] = $contentMd5; } // date setlocale(LC_TIME, 'en_US'); $date = gmdate('D, d M Y H:i:s \G\M\T', time()); $headers['Date'] = $date; $headers['Content-Length'] = (string)$contentLength; $contentType = $headers['Content-Type']; $message = $method . "\n" . $contentMd5 . "\n" . $contentType . "\n" . $date . "\n"; // header $filterHeaders = []; foreach ($headers as $key => $val) { if (str_starts_with($key, 'x-log-') || str_starts_with($key, 'x-acs-')) { $filterHeaders[$key] = $val; } } ksort($filterHeaders); foreach ($filterHeaders as $key => $val) { $message .= $key . ':' . $val . "\n"; } // uri and params $message .= $uri; if (sizeof($params) > 0) { $message .= '?'; } ksort($params); $sep = ''; foreach ($params as $key => $val) { $message .= $sep . $key . '=' . $val; $sep = '&'; } // signature & authorization $signature = $this->generateSignature($message); $auth = 'LOG ' . $this->accessKeyId . ':' . $signature; $headers['Authorization'] = $auth; return $headers; } /** * 实现调用PutWebTracking接口将多条日志合并进行采集. */ public function putWebTracking(array $record): void { $logEndpoint = $this->logs['log_endpoint']; $logProject = $this->logs['log_project']; $logStores = $this->logs['log_store']; $params = []; $body = [ '__topic__' => 'mes api logs', '__source__' => 'admin api', '__logs__' => [ ['Logs' => $record['formatted']], ], '__tags__' => [ 'mes' => $record['channel'], ], ]; $body = json_encode($body); $sign_url = sprintf('/logstores/%s/track', $logStores); $headers = $this->buildHeaders('POST', $sign_url, $params, $body, $logProject, $logEndpoint); $options = [ 'headers' => $headers, 'body' => $body, 'query' => $params, ]; $url = sprintf('https://%s.%s/logstores/%s/track', $logProject, $logEndpoint, $logStores); $client = $this->clientFactory->create(); co(function () use ($client, $url, $options) { $client->request('POST', $url, $options); }); } protected function generateSignature(string $message): string { return base64_encode(hash_hmac('sha1', $message, $this->accessKeySecret, true)); } }