平台对接集成指南

3D模型平台 API 集成文档

概述

平台提供RESTful API接口,支持商户系统通过HMAC签名认证方式调用。所有API请求都需要进行签名验证,确保请求的安全性和完整性。

核心特性

  • HMAC签名认证:使用SHA256算法生成请求签名
  • 时间戳防重放:请求包含时间戳,有效期5分钟
  • IP白名单:支持配置IP白名单限制访问来源
  • 重试机制:内置请求重试,提高稳定性
  • SSE通知:支持服务器推送事件,实时接收平台通知

认证机制

HMAC签名算法

平台使用HMAC-SHA256算法进行请求签名验证。

签名生成步骤

  1. 获取时间戳
  2. const timestamp = Math.floor(Date.now() / 1000).toString();
  3. 构建待签名字符串
  4. 将请求参数(query参数 + body参数)按key排序,格式:key1=value1&key2=value2×tamp=1234567890

    function buildSignString(params, timestamp) {
      const sortedKeys = Object.keys(params).sort();
      const signParts = sortedKeys.map((key) => `${key}=${params[key]}`);
      signParts.push(`timestamp=${timestamp}`);
      return signParts.join('&');
    }
  5. 生成签名
  6. const signString = buildSignString(params, timestamp);
    const signature = crypto.createHmac('sha256', apiSecret)
      .update(signString)
      .digest('hex');

请求头设置

所有API请求必须包含以下HTTP头:

X-Merchant-Id: {商户ID}
X-API-Key: {API密钥}
X-Timestamp: {时间戳(秒)}
X-Signature: {HMAC签名}
Content-Type: application/json
时间戳验证:时间戳必须是Unix时间戳(秒),请求时间戳与服务器时间差不能超过5分钟(300秒),超过有效期将返回 TIMESTAMP_EXPIRED 错误。

环境配置

必需的环境变量

.env 文件中配置以下变量:

# Platform API配置
PLATFORM_API_URL=http://localhost:3000  # 平台API地址
MERCHANT_API_KEY=your_api_key           # API密钥
MERCHANT_API_SECRET=your_api_secret     # API密钥
MERCHANT_ID=your_merchant_id            # 商户ID

配置示例(Node.js)

// config/index.js
require('dotenv').config();

module.exports = {
  platform: {
    url: process.env.PLATFORM_API_URL || 'http://localhost:3000',
    apiKey: process.env.MERCHANT_API_KEY,
    apiSecret: process.env.MERCHANT_API_SECRET,
    merchantId: process.env.MERCHANT_ID,
  },
};

API客户端实现

基础客户端类

const axios = require('axios');
const crypto = require('crypto');
const config = require('./config');

class PlatformApiClient {
  constructor() {
    this.baseURL = config.platform.url;
    this.apiKey = config.platform.apiKey;
    this.apiSecret = config.platform.apiSecret;
    this.merchantId = config.platform.merchantId;
  }

  /**
   * 生成HMAC签名
   */
  generateSignature(data, timestamp) {
    const signString = this.buildSignString(data, timestamp);
    return crypto.createHmac('sha256', this.apiSecret)
      .update(signString)
      .digest('hex');
  }

  /**
   * 构建待签名字符串
   */
  buildSignString(params, timestamp) {
    const sortedKeys = Object.keys(params).sort();
    const signParts = sortedKeys.map((key) => `${key}=${params[key]}`);
    signParts.push(`timestamp=${timestamp}`);
    return signParts.join('&');
  }

  /**
   * 发送请求
   */
  async request(method, path, data = null, options = {}) {
    const timestamp = Math.floor(Date.now() / 1000).toString();
    const url = `${this.baseURL}${path}`;

    // 构建请求参数(query + body)
    const params = {
      ...(options.params || {}),
      ...(data || {}),
    };

    // 生成签名
    const signature = this.generateSignature(params, timestamp);

    // 构建请求头
    const headers = {
      'X-Merchant-Id': this.merchantId,
      'X-API-Key': this.apiKey,
      'X-Timestamp': timestamp,
      'X-Signature': signature,
      'Content-Type': 'application/json',
      ...options.headers,
    };

    try {
      let response;
      if (method === 'GET') {
        response = await axios.get(url, { params: data, headers });
      } else if (method === 'POST') {
        response = await axios.post(url, data, { headers });
      } else if (method === 'PUT') {
        response = await axios.put(url, data, { headers });
      } else if (method === 'DELETE') {
        response = await axios.delete(url, { params: data, headers });
      }

      return response.data;
    } catch (error) {
      throw this.handleError(error);
    }
  }

  /**
   * 错误处理
   */
  handleError(error) {
    if (error.response) {
      const { status, data } = error.response;
      const errorMessage = data?.error?.message || error.message;
      const errorCode = data?.error?.code || 'UNKNOWN_ERROR';
      return new Error(`${errorCode}: ${errorMessage}`);
    } else if (error.request) {
      return new Error('Network error: Unable to connect to platform API');
    } else {
      return error;
    }
  }

  // API方法
  async getModels(params = {}) {
    return this.request('GET', '/api/platform/models', params);
  }

  async getModelById(modelId) {
    return this.request('GET', `/api/platform/models/${modelId}`);
  }

  async getModelStreamUrl(modelId) {
    return this.request('GET', `/api/platform/models/${modelId}/stream`);
  }

  async getCreditBalance() {
    return this.request('GET', '/api/platform/credits/balance');
  }

  async getCreditTransactions(params = {}) {
    return this.request('GET', '/api/platform/credits/transactions', params);
  }

  async checkCreditBalance(amount) {
    return this.request('POST', '/api/platform/credits/check', { amount });
  }

  async consumeCredit(amount, description, orderId, modelId = null) {
    const data = { amount, description, orderId };
    if (modelId) data.modelId = modelId;
    return this.request('POST', `/api/platform/merchants/${this.merchantId}/credit/consume`, data);
  }

  async createRechargeOrder(amount, activityId = null) {
    const data = {
      type: 'recharge',
      amount,
      description: `积分充值:${amount}元`,
    };
    if (activityId) data.activityId = activityId;
    return this.request('POST', '/api/platform/payments/orders', data);
  }

  async getPaymentOrder(orderId) {
    return this.request('GET', `/api/platform/payments/orders/${orderId}`);
  }

  async getMerchantStatistics(startDate = null, endDate = null) {
    const params = {};
    if (startDate) params.startDate = startDate;
    if (endDate) params.endDate = endDate;
    return this.request('GET', '/api/platform/statistics/merchant/me', params);
  }
}

module.exports = new PlatformApiClient();

重试机制

async function retry(fn, options = {}) {
  const { maxRetries = 3, delay = 1000 } = options;
  let lastError;

  for (let i = 0; i <= maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      
      // 如果是认证错误,不重试
      if (error.response?.status === 401 || error.response?.status === 403) {
        throw error;
      }

      // 最后一次尝试失败,抛出错误
      if (i === maxRetries) {
        break;
      }

      // 等待后重试
      await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
    }
  }

  throw lastError;
}

API接口列表

1. 模型管理

获取模型列表

GET /api/platform/models

请求参数:

  • page (number, 可选): 页码,默认1
  • pageSize (number, 可选): 每页数量,默认20
  • search (string, 可选): 搜索关键词
  • auditStatus (string, 可选): 审核状态(pending, approved, rejected)
  • status (string, 可选): 状态(active, inactive)
// 使用示例
const result = await platformApiClient.getModels({
  page: 1,
  pageSize: 20,
  search: '模型名称',
  auditStatus: 'approved'
});

获取模型详情

GET /api/platform/models/:id
const result = await platformApiClient.getModelById('model-id');

获取模型流URL

GET /api/platform/models/:id/stream
const result = await platformApiClient.getModelStreamUrl('model-id');
const streamUrl = result.data.url; // 临时访问URL

2. 积分管理

查询积分余额

GET /api/platform/credits/balance
const result = await platformApiClient.getCreditBalance();
console.log('余额:', result.data.balance);

查询积分交易记录

GET /api/platform/credits/transactions
const result = await platformApiClient.getCreditTransactions({
  page: 1,
  pageSize: 20,
  type: 'recharge',
  startDate: '2025-01-01',
  endDate: '2025-12-31'
});

检查积分是否充足

POST /api/platform/credits/check
const result = await platformApiClient.checkCreditBalance(100.00);
if (result.data.sufficient) {
  console.log('积分充足');
} else {
  console.log('积分不足');
}

消费积分

POST /api/platform/merchants/:merchantId/credit/consume
const result = await platformApiClient.consumeCredit(
  100.00,
  '使用模型',
  'order-id',
  'model-id'
);
console.log('消费成功,剩余余额:', result.data.balance);

3. 支付管理

创建充值订单

POST /api/platform/payments/orders
const result = await platformApiClient.createRechargeOrder(100.00, 'activity-id');
console.log('订单ID:', result.data.orderId);
console.log('支付URL:', result.data.paymentUrl);

查询支付订单状态

GET /api/platform/payments/orders/:orderId
const result = await platformApiClient.getPaymentOrder('order-id');
console.log('订单状态:', result.data.status);

4. 统计信息

获取商户统计信息

GET /api/platform/statistics/merchant/me
const result = await platformApiClient.getMerchantStatistics(
  '2025-01-01',
  '2025-12-31'
);
console.log('积分余额:', result.data.creditBalance);
console.log('充值总额:', result.data.rechargeTotal);
console.log('消费总额:', result.data.consumeTotal);

错误处理

错误响应格式

{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "错误描述"
  }
}

常见错误码

错误码 HTTP状态码 说明
MISSING_AUTH_HEADERS 401 缺少认证头信息
INVALID_API_KEY 401 API密钥无效
INVALID_SIGNATURE 401 签名验证失败
TIMESTAMP_EXPIRED 401 时间戳过期
MERCHANT_INACTIVE 403 商户账户未激活
IP_NOT_ALLOWED 401 IP不在白名单中
INSUFFICIENT_CREDIT 400 积分不足

错误处理示例

try {
  const result = await platformApiClient.getCreditBalance();
  console.log('Balance:', result.data.balance);
} catch (error) {
  if (error.response) {
    const { status, data } = error.response;
    
    switch (data?.error?.code) {
      case 'INVALID_SIGNATURE':
        console.error('签名验证失败,请检查API密钥');
        break;
      case 'TIMESTAMP_EXPIRED':
        console.error('请求时间戳过期,请检查系统时间');
        break;
      case 'INSUFFICIENT_CREDIT':
        console.error('积分不足');
        break;
      default:
        console.error('API错误:', data?.error?.message);
    }
  } else {
    console.error('网络错误:', error.message);
  }
}

最佳实践

1. 时间同步

确保系统时间与平台服务器时间同步,避免时间戳验证失败。

2. 请求重试

实现指数退避重试机制,提高稳定性。

3. 请求日志

记录所有API请求和响应,便于调试和审计。

4. 连接池

使用HTTP连接池提高性能。

5. 缓存策略

对不经常变化的数据进行缓存。

安全建议:
  • 不要将API密钥提交到代码仓库
  • 使用环境变量或密钥管理服务
  • 定期轮换API密钥
  • 生产环境必须使用HTTPS
  • 在平台管理后台配置IP白名单

示例代码

完整示例:积分充值流程

const platformApiClient = require('./platformApiClient');

async function rechargeCredit(amount, activityId = null) {
  try {
    // 1. 检查当前余额
    const balanceResult = await platformApiClient.getCreditBalance();
    console.log('当前余额:', balanceResult.data.balance);

    // 2. 创建充值订单
    const orderResult = await platformApiClient.createRechargeOrder(amount, activityId);
    const orderId = orderResult.data.orderId;
    console.log('订单创建成功:', orderId);

    // 3. 轮询订单状态(或使用SSE接收通知)
    const checkOrderStatus = async () => {
      const orderResult = await platformApiClient.getPaymentOrder(orderId);
      return orderResult.data.status;
    };

    // 等待支付完成
    let status = 'pending';
    while (status === 'pending') {
      await new Promise(resolve => setTimeout(resolve, 2000));
      status = await checkOrderStatus();
    }

    if (status === 'paid') {
      // 4. 支付成功,查询最新余额
      const newBalanceResult = await platformApiClient.getCreditBalance();
      console.log('充值成功,新余额:', newBalanceResult.data.balance);
      return { success: true, orderId, balance: newBalanceResult.data.balance };
    } else {
      throw new Error('支付失败');
    }
  } catch (error) {
    console.error('充值失败:', error.message);
    throw error;
  }
}

// 使用示例
rechargeCredit(100.00, 'activity-id')
  .then(result => console.log('充值完成:', result))
  .catch(error => console.error('充值失败:', error));

完整示例:使用模型

async function useModel(modelId, orderId) {
  try {
    // 1. 获取模型详情
    const modelResult = await platformApiClient.getModelById(modelId);
    const model = modelResult.data;
    console.log('模型信息:', model.name, '价格:', model.price);

    // 2. 检查积分是否充足
    const checkResult = await platformApiClient.checkCreditBalance(model.price);
    if (!checkResult.data.sufficient) {
      throw new Error('积分不足');
    }

    // 3. 获取模型流URL
    const streamResult = await platformApiClient.getModelStreamUrl(modelId);
    const streamUrl = streamResult.data.url;
    console.log('模型流URL:', streamUrl);

    // 4. 消费积分
    const consumeResult = await platformApiClient.consumeCredit(
      model.price,
      `使用模型: ${model.name}`,
      orderId,
      modelId
    );
    console.log('积分消费成功,剩余余额:', consumeResult.data.balance);

    return {
      success: true,
      streamUrl,
      balance: consumeResult.data.balance,
    };
  } catch (error) {
    console.error('使用模型失败:', error.message);
    throw error;
  }
}