CaiWuXiTong/app/Services/Esign/EsignRequestSigner.php

76 lines
2.4 KiB
PHP

<?php
namespace App\Services\Esign;
use InvalidArgumentException;
final class EsignRequestSigner
{
public const ACCEPT = '*/*';
public const CONTENT_TYPE = 'application/json; charset=UTF-8';
public function __construct(
private readonly string $appId,
private readonly string $appSecret,
) {
if ($this->appId === '' || $this->appSecret === '') {
throw new InvalidArgumentException('E 签宝 App ID 和 AppSecret 不能为空。');
}
}
public function headers(string $method, string $pathAndParameters, string $body = ''): array
{
$method = strtoupper($method);
$pathAndParameters = $this->canonicalPath($pathAndParameters);
$contentMd5 = $body === '' ? '' : base64_encode(md5($body, true));
$stringToSign = implode("\n", [
$method,
self::ACCEPT,
$contentMd5,
self::CONTENT_TYPE,
'',
]) . "\n" . $pathAndParameters;
return [
'X-Tsign-Open-App-Id' => $this->appId,
'X-Tsign-Open-Auth-Mode' => 'Signature',
'X-Tsign-Open-Ca-Timestamp' => (string) floor(microtime(true) * 1000),
'X-Tsign-Open-Ca-Signature' => base64_encode(hash_hmac('sha256', $stringToSign, $this->appSecret, true)),
'Accept' => self::ACCEPT,
'Content-Type' => self::CONTENT_TYPE,
'Content-MD5' => $contentMd5,
];
}
public function canonicalPath(string $pathAndParameters): string
{
$parts = parse_url($pathAndParameters);
$path = (string) ($parts['path'] ?? '');
if ($path === '' || ! str_starts_with($path, '/')) {
throw new InvalidArgumentException('E 签宝 API 路径必须以 / 开头。');
}
if (! isset($parts['query']) || $parts['query'] === '') {
return $path;
}
$parameters = [];
foreach (explode('&', $parts['query']) as $pair) {
[$key, $value] = array_pad(explode('=', $pair, 2), 2, null);
if (! array_key_exists($key, $parameters)) {
$parameters[$key] = $value;
}
}
ksort($parameters, SORT_STRING);
$query = collect($parameters)->map(function (?string $value, string $key): string {
return $value === null || $value === '' ? $key : $key . '=' . $value;
})->implode('&');
return $path . '?' . $query;
}
}