如何在PHP中实现NIMLAB加密货币交易所API的鉴权?
如何在PHP中实现NIMLAB加密货币交易所API的鉴权?
嘿,我看了你给出的官方Python示例和你写的PHP代码,问题大概率出在JSON序列化的格式差异上——这可是跨语言实现API签名时最容易踩的坑!
先帮你梳理下官方Python代码里签名生成的关键细节,再对比你的PHP代码找出问题:
官方Python签名的核心步骤
- 将GET参数里的所有值(包括数组元素)转为字符串
- 合并GET和POST参数为一个字典
- 用无空格的JSON格式序列化:
json.dumps(..., separators=(',', ':'), ensure_ascii=False)——这里的separators=(',', ':')会让JSON输出没有多余空格,比如{"key":"value"}而不是{"key": "value"} - 对这个JSON字符串做SHA256哈希得到
checksum_params - 把
checksum_params和API密钥拼接后再做一次SHA256哈希,得到最终签名
你的PHP代码里的问题
你用了json_encode($params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),但PHP默认会保留空格,生成的JSON和Python的完全不一样,直接导致签名不匹配!另外还有几个小细节需要对齐。
现在给你修正后的完整PHP代码,我已经把所有和Python不一致的地方对齐了:
<?php class NIMLABAPI { private $apiUrl; private $apiKey; private $apiSecret; public function __construct($apiUrl, $apiKey, $apiSecret) { if (empty($apiUrl)) { throw new InvalidArgumentException('apiUrl is required'); } if (empty($apiKey)) { throw new InvalidArgumentException('apiKey is required'); } if (empty($apiSecret)) { throw new InvalidArgumentException('apiSecret is required'); } $this->apiUrl = $apiUrl; $this->apiKey = $apiKey; $this->apiSecret = $apiSecret; } private function generateHash($getParams, $postParams) { // 对齐Python:把GET参数的所有值转成字符串(数组元素也要转) foreach ($getParams as $key => &$value) { if (is_array($value)) { $value = array_map('strval', $value); } else { $value = strval($value); } } unset($value); // 释放引用,避免后续变量污染 // 合并GET和POST参数 $params = array_merge($getParams, $postParams); // 关键:对齐Python的JSON序列化格式——无空格,不转义ASCII $jsonParams = json_encode($params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); // 去掉JSON里所有空格,模拟Python的separators=(',', ':')效果 $jsonParams = preg_replace('/\s+/', '', $jsonParams); // 生成checksum和最终哈希 $checksumParams = hash('sha256', $jsonParams); $hash = hash('sha256', $checksumParams . $this->apiSecret); return $hash; } public function call($method, $params = [], $rewriteConfig = []) { $get = $params['get'] ?? []; $post = $params['post'] ?? []; if (!is_array($get)) { return ["errorCode" => 20, "message" => 'Error: "get" params must be an array']; } if (!is_array($post)) { return ["errorCode" => 21, "message" => 'Error: "post" params must be an array']; } $configService = array_merge([ 'apiUrl' => $this->apiUrl, 'apikey' => $this->apiKey, 'secret' => $this->apiSecret ], $rewriteConfig); $methodParts = explode(':', $method); $typeMethod = count($methodParts) === 2 ? strtoupper($methodParts[0]) : 'POST'; $method = count($methodParts) === 2 ? $methodParts[1] : $methodParts[0]; // 对齐Python:GET请求添加毫秒级时间戳 if ($typeMethod === 'GET') { $get['time'] = intval(microtime(true) * 1000); } // 处理GET参数的数组格式,对齐Python的key[]=item编码 $urlParams = http_build_query($get, '', '&', PHP_QUERY_RFC3986); // 优化URL拼接,避免重复斜杠问题 $url = rtrim($configService['apiUrl'], '/') . '/' . ltrim($method, '/') . ($urlParams ? '?' . $urlParams : ''); $hash = $this->generateHash($get, $post); $headers = [ 'cache-control: no-cache', 'apikey: ' . $configService['apikey'], 'hash: ' . $hash, 'Content-Type: application/json' ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $typeMethod); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 处理可能的跳转 if ($typeMethod === 'POST') { // POST的JSON也要对齐格式:无空格 $postJson = json_encode($post, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $postJson = preg_replace('/\s+/', '', $postJson); curl_setopt($ch, CURLOPT_POSTFIELDS, $postJson); } $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE); // 捕获Curl本身的错误 if (curl_errno($ch)) { $error = 'Curl Error: ' . curl_error($ch); curl_close($ch); throw new Exception($error); } if ($httpCode !== 200) { $error = "Error: HTTP $httpCode"; if (strpos($contentType, 'application/json') !== false) { $responseData = json_decode($response, true); $error = $responseData['error'] ?? $error; } curl_close($ch); throw new Exception($error); } curl_close($ch); $responseData = json_decode($response, true); if (empty($responseData['success']) || empty($responseData['data'])) { throw new Exception('API Error: ' . ($responseData['error'] ?? 'Unknown error')); } return $responseData['data']; } } ?>
主要修正点说明
- JSON序列化格式对齐:用
preg_replace('/\s+/', '', $jsonParams)去掉所有空格,和Python的separators=(',', ':')效果完全一致 - GET参数数组处理:用
http_build_query的PHP_QUERY_RFC3986参数,确保编码格式和Python的urlencode一致 - URL拼接优化:添加了
rtrim和ltrim处理斜杠,避免出现//这种无效路径 - POST请求的JSON格式:同样去掉空格,和Python的POST数据格式保持一致
- 添加CURL错误处理:捕获Curl本身的错误,方便排查网络层面的问题
你可以测试下这个代码,应该能解决签名不匹配的问题。如果还是有问题,可以先对比Python和PHP生成的JSON字符串、checksum值,看看哪一步不一致,这样就能快速定位问题啦!
备注:内容来源于stack exchange,提问作者Alex Frox
相关产品推荐
相关产品推荐

