You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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'];
    }
}

?>

主要修正点说明

  1. JSON序列化格式对齐:用preg_replace('/\s+/', '', $jsonParams)去掉所有空格,和Python的separators=(',', ':')效果完全一致
  2. GET参数数组处理:用http_build_query的PHP_QUERY_RFC3986参数,确保编码格式和Python的urlencode一致
  3. URL拼接优化:添加了rtrim和ltrim处理斜杠,避免出现//这种无效路径
  4. POST请求的JSON格式:同样去掉空格,和Python的POST数据格式保持一致
  5. 添加CURL错误处理:捕获Curl本身的错误,方便排查网络层面的问题

你可以测试下这个代码,应该能解决签名不匹配的问题。如果还是有问题,可以先对比Python和PHP生成的JSON字符串、checksum值,看看哪一步不一致,这样就能快速定位问题啦!

备注:内容来源于stack exchange,提问作者Alex Frox

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 17:53:05