ARMCLOUD 开发者文档中心
  • 简体中文
  • English
  • 简体中文
  • English
  • 产品介绍
  • 产品计费
  • 服务端 OpenAPI
    • 使用指南
    • 接口签名v1.0
    • 接口文档
    • OpenAPI 3.1规范(AI工具专用)
    • LLMs.txt (AI快速参考)
    • 错误码说明
    • 回调业务类型码
    • 实例列表
    • 安卓改机属性列表
    • 更新日志
  • Android端 SDK
    • 示例搭建
    • 接口说明
    • 回调函数
    • 错误码
    • 更新日志
  • Web H5端 SDK
    • 示例搭建
    • 接口说明
    • 回调函数
    • 错误码
    • 更新日志
  • Windows PC端 SDK
    • 示例搭建
    • 接口说明
    • 回调函数
    • 更新日志
  • 端侧与云机通信开发
    • AIDL接入方式
    • 系统服务API(aidl)
  • 场景解决方案
    • 类XP、LSP Hook框架
    • 传感器数据动态仿真
  • 常见问题
    • 平台基础问题
    • 存储相关问题
    • 网络连接问题
    • 应用管理问题
    • 开发集成问题
    • 性能优化问题
    • 计费相关问题
    • 技术支持
  • 相关协议
    • 云手机SDK隐私政策

概述

ArmCloud的服务端OpenAPI为开发者提供了一套RESTful API,用于管理和操作云手机服务资源。通过这些API,您可以实现云手机实例的创建、管理、监控等功能,从而更好地集成和扩展您的应用。

为了确保API的安全性,ArmCloud提供了两个版本的签名验证方案:

  • V2.0版本 ⭐:简化的签名方案,推荐使用
  • V1.0版本:传统的完整签名方案,详见接口签名v1.0

版本推荐

推荐使用V2.0版本,它提供了更简单的请求头设置和签名计算流程,同时保持高安全性。

获取账号(AK/SK)

在开始使用API前,需要获取Access Key ID和Secret Access Key,用于API请求鉴权。

操作步骤:

  1. 登录ARMCloud平台
  2. 进入 用户管理 -> 账户信息 菜单
  3. 其中appKey为AK,appSecret为SK

V2.0签名验证(推荐) ⭐

API域名

重要提示

请根据您的服务区域选择正确的API域名

区域域名适用范围
国内中国大陆地区用户
海外海外及港澳台地区用户

请求头设置

V2.0版本需要在HTTP请求中添加以下四个自定义请求头:

请求头名称说明示例
鉴权版本,固定值"2.0"2.0
访问密钥ID,由ArmCloud平台分配LTAI4FzK8888888888888
请求发起的时间戳(毫秒)1618900299000
请求的签名结果5d9f846a5254865894069d9a864a96696115f0edcac66de5f3a3515f58c44200

签名计算流程

1. 构建待签名字符串

核心公式

待签名字符串 = timestamp + path + body

其中body的内容根据请求方法不同:

  • GET请求:body = 查询参数字符串(如果有查询参数)
  • POST请求:body = JSON请求体字符串
详细说明

参数组成:

参数说明示例
timestamp13位毫秒时间戳,与x-timestamp请求头相同1618900299000
path完整的API路径,必须以/开头/openapi/open/device/list
body根据请求类型的不同内容(见下方说明)见具体示例

body内容规则:

  1. GET请求:

    • 如果有查询参数:body = 查询参数字符串
    • 如果无查询参数:body = ""(空字符串)
  2. POST请求:

    • body = JSON请求体的紧凑字符串
    • JSON必须是紧凑格式,无空格和换行
构建示例

GET请求示例:

请求URL: https://api.xiaosuanyun.com/openapi/open/user/info?id=12345&type=basic
timestamp: 1618900299000
path: /openapi/open/user/info
body: id=12345&type=basic

待签名字符串: 1618900299000/openapi/open/user/infoid=12345&type=basic

POST请求示例:

请求URL: https://api.xiaosuanyun.com/openapi/open/device/list
请求体: {"page": 1, "rows": 10}
timestamp: 1618900400000
path: /openapi/open/device/list
body: {"page":1,"rows":10}

待签名字符串: 1618900400000/openapi/open/device/list{"page":1,"rows":10}

2. 计算签名

使用HMAC-SHA256算法,以SecretKey作为密钥,对第1步构建的待签名字符串进行签名计算:

重要

计算得到的签名值需以形式表示,长度为64个字符。

计算示例
待签名字符串: 1618900400000/openapi/open/device/list{"page":1,"rows":10}
SecretKey: your_secret_key

signature = hmac_sha256(待签名字符串, SecretKey).toLowerCase()
结果: 3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1

错误响应

当签名验证失败时,API网关会返回以下错误:

{
  "code": 100005,
  "msg": "验证签名失败",
  "data": null
}

常见错误情况:

  • AccessKey不存在或已禁用
  • 请求时间戳与服务器时间相差超过5分钟
  • 签名计算错误
  • 请求头格式不正确

接口限流说明

为了确保平台服务的稳定性和可靠性,ArmCloud对所有OpenAPI接口实施了访问频率限制。

限流机制

限流目的

接口限流的主要目的是让客户使用的平台更加稳定、可靠,确保所有用户能够公平地享受服务资源。

限流策略:

限流计算方式

限流采用固定时间窗口计算,每个Access Key独立计算限制。同时满足QPS和每分钟两个维度的限制。

用户类型QPS限制(每秒)RPM限制(每分)说明
测试用户200次/秒5,000次/分钟免费用户默认限制
付费用户2,000次/秒30,000次/分钟付费升级后限制

限流维度说明:

  1. QPS限制:在任意1秒钟时间窗口内的最大请求数
  2. 每分钟限制:在任意1分钟时间窗口内的最大请求数
  3. Access Key级别:每个Access Key独立计算,不同Key之间不共享额度
  4. 所有接口共享:单个Access Key下所有接口调用共享同一个限流额度

限流响应

当触发限流时,API会返回HTTP状态码429 Too Many Requests,响应内容如下:

{
  "msg": "Too many requests. Please try again later..",
  "code": 429,
  "data": null
}

响应头说明:

响应头说明示例
X-RateLimit-Limit当前限流周期的请求上限,根据限流类型而定200
X-RateLimit-Remaining当前限流周期剩余可用请求数150
X-RateLimit-Reset当前限流周期重置时间戳(Unix时间戳)1618900361
X-RateLimit-Type触发的限流类型QPS/RPM

最佳实践

建议

  1. 合理控制请求频率:建议在应用中实现请求频率控制,避免短时间内发送大量请求
  2. 实现重试机制:当收到429错误时,应等待适当时间后重试,建议使用指数退避算法
  3. 监控响应头:关注X-RateLimit-*响应头,主动控制请求节奏
  4. 批量操作优化:尽量使用支持批量操作的接口,减少单次请求数量

重试策略示例:

import time
import random

def api_request_with_retry(api_func, max_retries=3):
    """带重试的API请求示例"""
    for attempt in range(max_retries + 1):
        try:
            response = api_func()
            if response.status_code == 429:
                if attempt < max_retries:
                    # 指数退避 + 随机抖动
                    delay = (2 ** attempt) + random.uniform(0, 1)
                    time.sleep(delay)
                    continue
                else:
                    raise Exception("请求频率限制,已达到最大重试次数")
            return response
        except Exception as e:
            if attempt == max_retries:
                raise e
            time.sleep(1)

注意事项

重要提醒

  1. 每个请求都必须重新计算签名,不可重复使用
  2. 签名计算需包含完整的请求路径,包括最前面的斜杠
  3. 所有字符串都必须使用UTF-8编码

客户端Demo(Java/Python/NodeJs)

重要说明

API域名选择

根据您的服务区域选择对应的域名:

  • 国内用户:
  • 海外用户:

接口路径说明

以下代码示例中使用的接口路径(如 /openapi/open/device/list、/openapi/open/pad/net/storage/res/create)仅为演示用途。

Java实现示例

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Arrays;
import com.fasterxml.jackson.databind.ObjectMapper;

public class ArmCloudSignatureV2 {
    private static final String ALGORITHM = "HmacSHA256";
    private final String accessKeyId;
    private final String secretKey;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public ArmCloudSignatureV2(String accessKeyId, String secretKey) {
        this.accessKeyId = accessKeyId;
        this.secretKey = secretKey;
    }

    // GET请求签名
    public Map<String, String> signGetRequest(String path, String queryString) throws Exception {
        String timestamp = String.valueOf(Instant.now().toEpochMilli());
        String body = queryString != null ? queryString : "";
        String signature = calculateSignature(timestamp, path, body);

        Map<String, String> headers = new HashMap<>();
        headers.put("authver", "2.0");
        headers.put("x-ak", accessKeyId);
        headers.put("x-timestamp", timestamp);
        headers.put("x-sign", signature);
        return headers;
    }

    // POST请求签名
    public Map<String, String> signPostRequest(String path, Object requestData) throws Exception {
        String timestamp = String.valueOf(Instant.now().toEpochMilli());
        String requestBody = objectMapper.writeValueAsString(requestData);
        String signature = calculateSignature(timestamp, path, requestBody);

        Map<String, String> headers = new HashMap<>();
        headers.put("authver", "2.0");
        headers.put("x-ak", accessKeyId);
        headers.put("x-timestamp", timestamp);
        headers.put("x-sign", signature);
        headers.put("Content-Type", "application/json");
        return headers;
    }

    private String calculateSignature(String timestamp, String path, String body) throws Exception {
        String stringToSign = timestamp + path + (body != null ? body : "");
        Mac hmacSha256 = Mac.getInstance(ALGORITHM);
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), ALGORITHM);
        hmacSha256.init(secretKeySpec);
        byte[] hash = hmacSha256.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(hash);
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        return hexString.toString();
    }

    // 使用示例
    public static void main(String[] args) throws Exception {
        ArmCloudSignatureV2 signer = new ArmCloudSignatureV2("LTAI4FzK8888888888888", "your_secret_key");

        // POST请求示例 - 板卡列表查询(接口路径请参考OpenAPI文档)
        Map<String, Object> deviceListData = new HashMap<>();
        deviceListData.put("page", 1);
        deviceListData.put("rows", 10);
        deviceListData.put("padCodes", Arrays.asList("AC21020010391"));
        deviceListData.put("vmStatus", "1");
        deviceListData.put("deviceStatus", "0");

        // 注意:此处路径仅为示例,实际路径请查看OpenAPI文档
        Map<String, String> deviceListHeaders = signer.signPostRequest("/openapi/open/device/list", deviceListData);
        System.out.println("板卡列表查询请求头: " + deviceListHeaders);

        // POST请求示例 - 创建云实例(接口路径和参数请参考OpenAPI文档)
        Map<String, Object> createInstanceData = new HashMap<>();
        createInstanceData.put("clusterCode", "001");
        createInstanceData.put("specificationCode", "m2-3");
        createInstanceData.put("imageId", "img-25021828327");
        createInstanceData.put("screenLayoutCode", "realdevice_1440x3120x560");
        createInstanceData.put("number", 2);
        createInstanceData.put("dns", "8.8.8.8");
        createInstanceData.put("storageSize", 16);
        createInstanceData.put("realPhoneTemplateId", 36);

        // 注意:此处路径仅为示例,实际路径请查看OpenAPI文档
        Map<String, String> createHeaders = signer.signPostRequest("/openapi/open/pad/net/storage/res/create", createInstanceData);
        System.out.println("创建云实例请求头: " + createHeaders);
    }
}

Python实现示例

import hmac
import hashlib
import time
import json
import requests
from typing import Dict, Any, Optional

class ArmCloudSignatureV2:
    def __init__(self, access_key_id: str, secret_key: str, base_url: str = "https://api.xiaosuanyun.com"):
        self.access_key_id = access_key_id
        self.secret_key = secret_key
        self.base_url = base_url  # 国内: https://api.xiaosuanyun.com 海外: https://openapi-hk.armcloud.net

    def _calculate_signature(self, timestamp: str, path: str, body: str = "") -> str:
        """计算HMAC-SHA256签名"""
        string_to_sign = timestamp + path + (body if body else "")
        signature = hmac.new(
            self.secret_key.encode('utf-8'),
            string_to_sign.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        return signature

    def sign_get_request(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, str]:
        """为GET请求生成签名头"""
        timestamp = str(int(time.time() * 1000))
        query_string = ""

        if params:
            query_string = "&".join([f"{k}={v}" for k, v in params.items()])
            body = query_string
        else:
            body = ""

        signature = self._calculate_signature(timestamp, path, body)

        return {
            "authver": "2.0",
            "x-ak": self.access_key_id,
            "x-timestamp": timestamp,
            "x-sign": signature
        }

    def sign_post_request(self, path: str, data: Dict[str, Any]) -> Dict[str, str]:
        """为POST请求生成签名头"""
        timestamp = str(int(time.time() * 1000))
        request_body = json.dumps(data, ensure_ascii=False, separators=(',', ':'))
        signature = self._calculate_signature(timestamp, path, request_body)

        return {
            "authver": "2.0",
            "x-ak": self.access_key_id,
            "x-timestamp": timestamp,
            "x-sign": signature,
            "Content-Type": "application/json"
        }

    def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> requests.Response:
        """发起GET请求"""
        headers = self.sign_get_request(path, params)
        url = self.base_url + path
        return requests.get(url, params=params, headers=headers)

    def post(self, path: str, data: Dict[str, Any]) -> requests.Response:
        """发起POST请求"""
        headers = self.sign_post_request(path, data)
        url = self.base_url + path
        request_body = json.dumps(data, ensure_ascii=False, separators=(',', ':'))
        return requests.post(url, data=request_body, headers=headers)

# 使用示例
if __name__ == "__main__":
    # 初始化客户端 - 根据地区选择域名
    # 国内用户
    client = ArmCloudSignatureV2("LTAI4FzK8888888888888", "your_secret_key", "https://api.xiaosuanyun.com")
    # 海外用户
    # client = ArmCloudSignatureV2("LTAI4FzK8888888888888", "your_secret_key", "https://openapi-hk.armcloud.net")

    # POST请求示例 - 板卡列表查询(接口路径请参考OpenAPI文档)
    device_list_data = {
        "page": 1,
        "rows": 10,
        "padCodes": ["AC21020010391"],
        "vmStatus": "1",
        "deviceStatus": "0"
    }

    try:
        # 注意:此处路径仅为示例,实际路径请查看OpenAPI文档
        response = client.post("/openapi/open/device/list", device_list_data)
        print(f"板卡列表查询 Response: {response.status_code}")
        print(f"Response Body: {response.text}")
    except Exception as e:
        print(f"板卡列表查询 Error: {e}")

    # POST请求示例 - 创建云实例(接口路径和参数请参考OpenAPI文档)
    create_instance_data = {
        "clusterCode": "001",
        "specificationCode": "m2-3",
        "imageId": "img-25021828327",
        "screenLayoutCode": "realdevice_1440x3120x560",
        "number": 2,
        "dns": "8.8.8.8",
        "storageSize": 16,
        "realPhoneTemplateId": 36
    }

    try:
        # 注意:此处路径仅为示例,实际路径请查看OpenAPI文档
        response = client.post("/openapi/open/pad/net/storage/res/create", create_instance_data)
        print(f"创建云实例 Response: {response.status_code}")
        print(f"Response Body: {response.text}")
    except Exception as e:
        print(f"创建云实例 Error: {e}")

Node.js实现示例

const crypto = require('crypto');
const axios = require('axios');

class ArmCloudSignatureV2 {
    constructor(accessKeyId, secretKey, baseUrl = 'https://api.xiaosuanyun.com') {
        this.accessKeyId = accessKeyId;
        this.secretKey = secretKey;
        this.baseUrl = baseUrl; // 国内: https://api.xiaosuanyun.com 海外: https://openapi-hk.armcloud.net
    }

    calculateSignature(timestamp, path, body = '') {
        const stringToSign = timestamp + path + body;
        return crypto
            .createHmac('sha256', this.secretKey)
            .update(stringToSign, 'utf8')
            .digest('hex');
    }

    signGetRequest(path, params = {}) {
        const timestamp = Date.now().toString();
        const queryString = new URLSearchParams(params).toString();
        const body = queryString ? queryString : '';
        const signature = this.calculateSignature(timestamp, path, body);

        return {
            'authver': '2.0',
            'x-ak': this.accessKeyId,
            'x-timestamp': timestamp,
            'x-sign': signature
        };
    }

    signPostRequest(path, data) {
        const timestamp = Date.now().toString();
        const requestBody = JSON.stringify(data);
        const signature = this.calculateSignature(timestamp, path, requestBody);

        return {
            'authver': '2.0',
            'x-ak': this.accessKeyId,
            'x-timestamp': timestamp,
            'x-sign': signature,
            'Content-Type': 'application/json'
        };
    }

    async get(path, params = {}) {
        const headers = this.signGetRequest(path, params);
        return await axios.get(this.baseUrl + path, { headers, params });
    }

    async post(path, data) {
        const headers = this.signPostRequest(path, data);
        return await axios.post(this.baseUrl + path, data, { headers });
    }
}

// 使用示例 - 根据地区选择域名
// 国内用户
const client = new ArmCloudSignatureV2('LTAI4FzK8888888888888', 'your_secret_key', 'https://api.xiaosuanyun.com');
// 海外用户
// const client = new ArmCloudSignatureV2('LTAI4FzK8888888888888', 'your_secret_key', 'https://openapi-hk.armcloud.net');

// POST请求 - 板卡列表查询(接口路径请参考OpenAPI文档)
const deviceListData = {
    page: 1,
    rows: 10,
    padCodes: ['AC21020010391'],
    vmStatus: '1',
    deviceStatus: '0'
};

// 注意:此处路径仅为示例,实际路径请查看OpenAPI文档
client.post('/openapi/open/device/list', deviceListData)
    .then(response => console.log('板卡列表查询 Response:', response.data))
    .catch(error => console.error('板卡列表查询 Error:', error.message));

// POST请求 - 创建云实例(接口路径和参数请参考OpenAPI文档)
const createInstanceData = {
    clusterCode: '001',
    specificationCode: 'm2-3',
    imageId: 'img-25021828327',
    screenLayoutCode: 'realdevice_1440x3120x560',
    number: 2,
    dns: '8.8.8.8',
    storageSize: 16,
    realPhoneTemplateId: 36
};

// 注意:此处路径仅为示例,实际路径请查看OpenAPI文档
client.post('/openapi/open/pad/net/storage/res/create', createInstanceData)
    .then(response => console.log('创建云实例 Response:', response.data))
    .catch(error => console.error('创建云实例 Error:', error.message));
Prev
产品计费
Next
Android端 SDK