assertConfigured(); $bodyJson = json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($bodyJson === false) { throw new RuntimeException('聚水潭请求体序列化失败。'); } $baseParams = $this->baseParams($method); $baseParams['sign'] = $this->sign($baseParams, $bodyJson); $sync = $this->config['sync'] ?? []; $timeout = (int) ($sync['request_timeout'] ?? 30); $retries = max((int) ($sync['retry_times'] ?? 5), 1); $retrySleep = (int) ($sync['retry_sleep_ms'] ?? 1000); $response = Http::withOptions(['query' => $baseParams]) ->withHeaders(['Content-Type' => 'application/json']) ->timeout($timeout) ->retry($retries, $retrySleep) ->withBody($bodyJson, 'application/json') ->post($this->gatewayUrl()); if ($response->failed()) { Log::warning('聚水潭奇门接口 HTTP 失败。', ['method' => $method, 'status' => $response->status()]); throw new RuntimeException("聚水潭接口 {$method} 请求失败:HTTP {$response->status()}"); } $data = $response->json(); if (! is_array($data)) { throw new RuntimeException("聚水潭接口 {$method} 返回非 JSON。"); } // 奇门通常以 code=0 / issuccess 标识成功,具体字段以实际返回为准,此处仅做基础校验。 return $data; } /** @return array */ private function baseParams(string $method): array { return [ 'app_key' => (string) ($this->config['app_key'] ?? ''), 'customerId' => (string) ($this->config['customer_id'] ?? ''), 'format' => (string) ($this->config['format'] ?? 'json'), 'method' => $method, 'simplify' => (string) ($this->config['simplify'] ?? 'true'), 'timestamp' => now()->format('Y-m-d H:i:s'), 'v' => (string) ($this->config['version'] ?? '2'), ]; } /** * 奇门 MD5 签名。等价于工作平台 ErpApiHelper.SignTopRequest: * raw = secret + Σ(key+value,按固定顺序,跳过 sign) + body + secret */ private function sign(array $baseParams, string $bodyJson): string { $secret = (string) ($this->config['app_secret'] ?? ''); $raw = $secret; foreach (self::SIGN_ORDER as $key) { $value = $baseParams[$key] ?? null; if ($value !== null && $value !== '') { $raw .= $key . $value; } } $raw .= $bodyJson . $secret; return strtoupper(md5($raw)); } private function gatewayUrl(): string { $url = trim((string) ($this->config['gateway_url'] ?? '')); if ($url === '') { throw new RuntimeException('未配置聚水潭奇门网关地址(JST_QIMEN_URL)。'); } return $url; } private function assertConfigured(): void { foreach (['app_key' => 'JST_APP_KEY', 'app_secret' => 'JST_APP_SECRET', 'customer_id' => 'JST_CUSTOMER_ID'] as $key => $env) { if (trim((string) ($this->config[$key] ?? '')) === '') { throw new RuntimeException("未配置聚水潭凭证 {$env},已拒绝请求(不会同步任何数据)。"); } } } }