isValidConfig($config)) { throw new BusinessException("缺少{$label}配置"); } $this->config = $config; $this->app_id = $config['app_id']; $this->api_url = rtrim($config['api_url'], '/'); $this->secret = $config['secret']; } protected function isValidConfig(mixed $config): bool { if (!is_array($config)) { return false; } return !empty($config['app_id']) && !empty($config['api_url']) && !empty($config['secret']); } protected function buildSignPayload(array $data): string { $signData = []; if (isset($data['form_params'])) { $signData = $data['form_params']; } if (isset($data['multipart'])) { foreach ($data['multipart'] as $item) { if (($item['name'] ?? '') === 'pdfFile') { continue; } $signData[$item['name']] = $item['contents']; } } if (empty($signData)) { return ''; } ksort($signData); return implode('&', $signData); } protected function getAuthSignature(array $data): string { return hash('sha256', $this->buildSignPayload($data)); } protected function shouldAttachAuthSignature(string $path): bool { $enabled = filter_var((string) ($this->config['enable_auth_signature'] ?? false), FILTER_VALIDATE_BOOLEAN); if (!$enabled) { return false; } return str_contains($path, '/api/cloudCert/open/v2/cert/') || str_contains($path, '/api/cloudCert/open/v3/cert/') || str_contains($path, '/api/certgw/certapi/') || str_contains($path, '/v5/api/certgw/certapi/'); } protected function buildCertSignPayload(array $data): string { $payload = json_encode($data, JSON_UNESCAPED_UNICODE); if ($payload === false) { throw new BusinessException('CA V2签名原文序列化失败'); } return $payload; } protected function buildCertSignTransportPayload(array $data): string { return base64_encode($this->buildCertSignPayload($data)); } protected function resolveCertSerialnumber(string $entityId, array $response): string { return (string) ($response['certSerialnumber'] ?? $response['certSn'] ?? ''); } protected function normalizeCertSignResponse(string $entityId, array $response): array { if (empty($response['signP7']) && !empty($response['requestId'])) { $result = $this->getCertSignResult((string) $response['requestId']); if (is_array($result) && !empty($result['signP7'])) { $response = array_merge($response, $result); } } if (empty($response['signP7'])) { if (!empty($response['signUrl'])) { throw new BusinessException('当前证书未开启免密签署,返回的是页面签署链接,暂不支持服务端自动完成'); } throw new BusinessException('CA V2签名响应缺少signP7'); } if (empty($response['certSerialnumber'])) { $response['certSerialnumber'] = $this->resolveCertSerialnumber($entityId, $response); } return $response; } protected function getOptionalConfig(string $key): string { return trim((string) ($this->config[$key] ?? '')); } public function getCertSign(string $user_id, string $pin, array $data): mixed { $generator = $this->container->get(IdGeneratorInterface::class); $payload = $this->buildCertSignTransportPayload($data); $option = [ 'form_params' => [ 'entityId' => $user_id, 'requestId' => (string) $generator->generate(), 'businessType' => 'certSign', 'toSign' => $payload, 'extParam' => json_encode(['toSignEncoding' => 'base64'], JSON_UNESCAPED_UNICODE), ], ]; $notifyUrl = $this->getOptionalConfig('notify_url'); if ($notifyUrl !== '') { $option['form_params']['notifyUrl'] = $notifyUrl; } $jumpUrl = $this->getOptionalConfig('jump_url'); if ($jumpUrl !== '') { $option['form_params']['jumpUrl'] = $jumpUrl; } try { $response = $this->httpRequest($this->api_url . '/open/api/data/certSign', $option); if (empty($response)) { throw new BusinessException(HttpEnumCode::getMessage(HttpEnumCode::SERVER_ERROR)); } return $this->normalizeCertSignResponse($user_id, $response); } catch (GuzzleException $e) { throw new BusinessException($e->getMessage()); } } public function getCertSignResult(string $requestId): mixed { $option = [ 'form_params' => [ 'requestId' => $requestId, ], ]; try { return $this->httpRequest($this->api_url . '/open/api/data/getCertSignResult', $option); } catch (GuzzleException $e) { throw new BusinessException($e->getMessage()); } } public function verifyPkcs7(string $sign_p7, array $data) { $generator = $this->container->get(IdGeneratorInterface::class); $option = [ 'form_params' => [ 'opType' => '签名验证', 'requestId' => $generator->generate(), 'signedData' => $sign_p7, 'toSign' => $this->buildCertSignTransportPayload($data), ], ]; try { $response = $this->httpRequest( $this->api_url . '/signgw-service/api/signature/verifyPkcs7', $option ); if (empty($response)) { throw new BusinessException(HttpEnumCode::getMessage(HttpEnumCode::SERVER_ERROR)); } return $response; } catch (GuzzleException $e) { throw new BusinessException($e->getMessage()); } } public function httpRequest(string $path, array $arg = []): mixed { $headers = [ 'app_id' => $this->app_id, 'signature' => $this->getRequestSign($arg), ]; if ($this->shouldAttachAuthSignature($path)) { $headers['authSignature'] = $this->getAuthSignature($arg); } $arg['headers'] = array_merge($arg['headers'] ?? [], $headers); $response = $this->client->post($path, $arg); if ($response->getStatusCode() != '200') { throw new BusinessException($response->getBody()->getContents()); } $body = json_decode($response->getBody(), true); if (empty($body)) { throw new BusinessException(HttpEnumCode::getMessage(HttpEnumCode::SERVER_ERROR)); } if ($body['result_code'] != 0) { if (!empty($body['result_msg'])) { throw new BusinessException($body['result_msg']); } throw new BusinessException(HttpEnumCode::getMessage(HttpEnumCode::SERVER_ERROR)); } return $body['body']; } public function getRequestSign(array $data): string { return hash_hmac('sha1', $this->buildSignPayload($data), $this->secret); } }