服务端 SDK

Open API 是标准 HTTP + JSON,签名规则固定(SHA-1),不绑定特定语言的 SDK。仓库已提供 Node.js App Server SDK 实物(@ni-paas-im/server-sdk,含 Open API + Admin API、重试与错误映射);Java / Go / PHP / Python 建议由 OpenAPI 3.1 导出生成并补签名拦截器。下面给出与 IDL 一致的最小封装:签名 helper、带错误处理的请求函数与四语言示例;完整的 SDK 交付状态见 Node.js App Server SDK。

签名 helper​

签名 = hex_lower(sha1(AppSecret + Nonce + Timestamp));Nonce 建议使用 16 字节随机数的 hex(32 字符),Timestamp 为 epoch 毫秒。

import { createHash, randomBytes } from 'node:crypto';

const APP_KEY = 'demo';
const APP_SECRET = 'YOUR_APP_SECRET';
const BASE_URL = 'https://im.example.com';

/** 调用 Open API:自动签名并校验 code。 */
export async function callOpenApi(path, body = {}) {
  const nonce = randomBytes(16).toString('hex');
  const timestamp = String(Date.now());
  const signature = createHash('sha1')
    .update(APP_SECRET + nonce + timestamp)
    .digest('hex');

  const res = await fetch(BASE_URL + path, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'App-Key': APP_KEY,
      Nonce: nonce,
      Timestamp: timestamp,
      Signature: signature,
    },
    body: JSON.stringify(body),
  });
  const result = await res.json();
  if (result.code !== 0) throw new Error(result.code + ': ' + result.msg);
  return result.data;
}

// 用法
const user = await callOpenApi('/im/v2/user/register', { userId: 'user1', name: 'Alice' });
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.HexFormat;

public final class OpenApiClient {
    private static final HttpClient HTTP = HttpClient.newHttpClient();
    private final String appKey;
    private final String appSecret;
    private final String baseUrl;

    public OpenApiClient(String appKey, String appSecret, String baseUrl) {
        this.appKey = appKey;
        this.appSecret = appSecret;
        this.baseUrl = baseUrl;
    }

    public String call(String path, String jsonBody) throws Exception {
        byte[] nonceBytes = new byte[16];
        new SecureRandom().nextBytes(nonceBytes);
        String nonce = HexFormat.of().formatHex(nonceBytes);
        String timestamp = String.valueOf(System.currentTimeMillis());
        String signature = HexFormat.of().formatHex(
            MessageDigest.getInstance("SHA-1")
                .digest((appSecret + nonce + timestamp).getBytes(StandardCharsets.UTF_8)));

        HttpRequest request = HttpRequest.newBuilder(URI.create(baseUrl + path))
            .header("Content-Type", "application/json")
            .header("App-Key", appKey)
            .header("Nonce", nonce)
            .header("Timestamp", timestamp)
            .header("Signature", signature)
            .POST(HttpRequest.BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8))
            .build();
        return HTTP.send(request, HttpResponse.BodyHandlers.ofString()).body();
    }
}
import hashlib
import json
import secrets
import time
import urllib.request

APP_KEY = "demo"
APP_SECRET = "YOUR_APP_SECRET"
BASE_URL = "https://im.example.com"

def call_open_api(path: str, body: dict) -> dict:
    nonce = secrets.token_hex(16)
    timestamp = str(int(time.time() * 1000))
    signature = hashlib.sha1(
        (APP_SECRET + nonce + timestamp).encode("utf-8")
    ).hexdigest()
    data = json.dumps(body).encode("utf-8")
    request = urllib.request.Request(
        BASE_URL + path,
        data=data,
        method="POST",
        headers={
            "Content-Type": "application/json",
            "App-Key": APP_KEY,
            "Nonce": nonce,
            "Timestamp": timestamp,
            "Signature": signature,
        },
    )
    with urllib.request.urlopen(request) as response:
        result = json.loads(response.read().decode("utf-8"))
    if result["code"] != 0:
        raise RuntimeError(f'{result["code"]}: {result.get("msg")}')
    return result["data"]

cURL​

Shell
APP_KEY="demo"
APP_SECRET="YOUR_APP_SECRET"
BASE_URL="https://im.example.com"
NONCE="$(openssl rand -hex 16)"
TIMESTAMP="$(date +%s%3N)"
SIGNATURE="$(printf '%s' "$APP_SECRET$NONCE$TIMESTAMP" | openssl sha1 | awk '{print $2}')"

curl -sS -X POST "$BASE_URL/im/v2/user/register" \
  -H "Content-Type: application/json" \
  -H "App-Key: $APP_KEY" -H "Nonce: $NONCE" \
  -H "Timestamp: $TIMESTAMP" -H "Signature: $SIGNATURE" \
  -d '{"userId":"user1","name":"Alice"}'

封装建议​

  • 统一处理 code !== 0:对 1008、1025 与 2xxx 按指数退避重试,其余错误直接抛出。
  • 所有请求携带 X-Request-Id 并记录到日志,便于按 requestId 在服务端排查。
  • 写操作使用业务幂等键(消息的 clientMsgId、幂等接口的业务唯一键),避免网络重试造成重复写入。
  • App-Secret 只从环境变量或密钥管理服务读取,不要提交到代码仓库或下发客户端。
  • 对 data 中的 int64/uint64 字段(seq、时间戳)注意语言侧的数值精度。

相关文档​