API调用详解 更新时间:2016-05-23 

QMOP的API是基于HTTP协议来调用的,开发者(ISV)可以直接使用QMOP提供的官方SDK(支持JAVA,PHP,.NET语言,包含了请求的封装,签名加密,响应解释,性能优化等)来调用,也可以根据千米开放平台的协议来封装HTTP请求进行调用,以下主要是针对自行封装HTTP请求进行API调用的原理进行详细解说。

一、调用流程

调用介绍

根据千米开放平台API调用协议:封装参数 > 生成签名 > 拼装HTTP请求 > 封装参数 > 发起HTTP请求 > 获取HTTP响应 > 解释json报文,以下是大致的调用示意图:

二、调用入口

调用API的服务的URL地址,目前QMOP提供了2个环境给ISV使用:沙箱测试环境,正式测试环境,正式环境.

沙箱测试环境:目前仅提供给新零售接入应用使用,由于E生活便民类目的特殊性,沙箱测试环境无法模拟真实的充值缴费环节,所以不对E生活应用提供沙箱环境测试,沙箱环境的数据与正式环境完全隔离,可放开使用.

正式测试环境:ISV应用在开发中(正式上线之前)的正式模拟环境,环境入口和正式环境一样,应用创建后即可进行调用,限制的API调用次数:5000次/天.

正式环境:ISV应用在开发测试完毕,需要正式发布上线的环境,上线以后,应用流量包会打开,具体的流量限制根据应用类型和用户规模有关,例如商家后台应用初始:10W次/天,在线订购应用100W次/天.根据用户规模后续可以申请更高的流量包.

调用环境 入口地址(HTTP) 入口地址(HTTPS)
沙箱测试环境 http://gw.api.demo.qianmi.com/api
正式测试环境 http://gw.api.qianmi.com/api https://api.qianmi.com/api
正式环境 http://gw.api.qianmi.com/api https://api.qianmi.com/api

三、公共参数

API参数由系统参数和应用参数组成,系统级参数是调用任意一个API都必须传入的参数,目前需要传入的系统级参数有:

系统级参数说明
名称 类型 是否必须 描述
appKey string 必须 千米开放平台颁发给应用的appKey,在应用证书中查看
method string 必须 API接口方法名称
v string 必须 接口版本号,可选值:1.1
format string 必须 响应类型,目前只支持json格式返回
sign string 必须 API输入参数的数字签名,签名算法参照下面的介绍
access_token string 可选 用户登录授权成功以后,千米开放平台会颁发给应用的授权信息access_token(接口访问令牌),API是需要需要传入此参数,请参考各API的用户授权类型.
timestamp number 必须 请求时间戳,时区为GMT+8(北京时间),格式:"yyyy-MM-dd HH:mm:ss",
例如"2012-12-20 10:20:35",千米API服务器允许的时间误差在10分钟。

四、业务参数

业务级参数是每个API本身业务所需要的参数,每个API的业务级参数请参考API文档说明,下面以qianmi.elife.recharge.mobile.getItemInfo(话费充值-查询可用商品信息)为例说明。

应用级参数说明
名称 类型 是否必须 描述
mobileNo number 必须 手机号码
rechargeAmount number 必须 充值金额

五、数字签名算法

为了防止API调用过程中被黑客恶意篡改,调用任何一个API都需要携带数字签名,千米开放平台服务端会对签名进行校验,签名不合法的请求将会被拒绝,千米开放平台目前支持的签名算法为SHA1,签名的大致过程如下:

  • 对所有API请求参数(包括公共参数和业务级参数,但除去sign参数本身和image(图片数据)的参数),根据参数名称的ASCII码表的顺序排序。如:bad=2,bac=1,cba=3,排序后的顺序是bac=1,bad=2,cba=3。
  • 将排序好的参数排在一起,根据上面的示例得到的排序后的结果:bac1bad2cba3。
  • 把拼装好的字符串用uft-8编码,需要在拼装后的字符串前后都加上应用的appSecret(应用证书中查看)在进行SHA1摘要,例如应用的appSecret为QianMi,进行签名计算:SHA1(QianMibac1bad2cba3QianMi)
  • 将摘要得到的字节流转化成16进制,例如将上面的签名摘要转换后的结果为:5F7DEFBFD29BDB0CEF0FBD200AB780084CE86ADC

说明:SHA1为安全哈希算法,主要用于数字签名标准(DSS)中的签名算法(DSA),SHA1会产生一个160位的消息摘要,用16进制表示,一个16进制的字符能表示4个位,所以签名后的长度固定为40个16进制字符.

JAVA签名示例代码
package com.qianmi.util;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.*;

/**
 * Created by QianMi
 */
public class SignUtil {
    public static String sha1(String str) throws IOException {
        return byte2hex(getSHA1Digest(str));
    }

    private static byte[] getSHA1Digest(String data) throws IOException {
        byte[] bytes;
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            bytes = md.digest(data.getBytes("utf-8"));
        } catch (GeneralSecurityException gse) {
            throw new IOException(gse);
        }
        return bytes;
    }

    private static String byte2hex(byte[] bytes) {
        StringBuilder sign = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                sign.append("0");
            }
            sign.append(hex.toUpperCase());
        }
        return sign.toString();
    }

    public static String sign(Map<String, String> param, String secret) throws IOException {
        if (param == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        List<String> paramNames = new ArrayList<>(param.size());
        paramNames.addAll(param.keySet());
        Collections.sort(paramNames);
        sb.append(secret);
        for (String paramName : paramNames) {
            sb.append(paramName).append(param.get(paramName));
        }
        sb.append(secret);
        return sha1(sb.toString());
    }

    public static void main(String[] args) throws IOException {
        Map<String, String> param = new HashMap<>();
        param.put("access_token", "7466bdfc5f79a7fe1defd9a5880a4b84");
        param.put("appKey", "10000");
        param.put("format", "json");
        param.put("v", "1.1");
        param.put("method", "recharge.mobile.getItemInfo");
        param.put("timestamp", "1428488009985");
        param.put("mobileNo", "13888888888");
        param.put("rechargeAmount", "100");
        System.out.println(sign(param, "test"));
    }
}

其他语言的签名示例请参考千米开放平台官方SDK源码

六、调用示例

下面以qianmi.elife.recharge.mobile.getItemInfo(话费充值-查询可用商品信息)为例说明,具体步骤如下:

1.准备参数项
公共参数:
  • appKey="10000"
  • method="qianmi.elife.recharge.mobile.getItemInfo"
  • v="1.1"
  • format="json"
  • access_token="7466bdfc5f79a7fe1defd9a5880a4b84"
  • timestamp="2016-01-01 12:00:00"

业务参数:
  • mobileNo="13888888888"
  • rechargeAmount="100"
2.按ASCII顺序排序
  • access_token="7466bdfc5f79a7fe1defd9a5880a4b84"
  • appKey="10000"
  • format="json"
  • method="qianmi.elife.recharge.mobile.getItemInfo"
  • mobileNo="13888888888"
  • rechargeAmount="100"
  • timestamp="2016-01-01 12:00:00"
  • v="1.1"
3.拼接参数名和参数值

连接参数名与参数值,并在首尾加上appSecret,示例如下(假设appSecret值为"test"):

testaccess_token7466bdfc5f79a7fe1defd9a5880a4b84appKey10000formatjsonmethodqianmi.elife.recharge.mobile.getItemInfo mobileNo13888888888rechargeAmount100timestamp2016-01-01 12:00:00v1.1test

4.生成签名摘要

将上面的拼接好的字符串进行SHA1签名摘要,并转换成16进制字符,最终签名后的sign值为:444F4A793F22D7483C240FC489D8DB8710D1F45A

5.拼装HTTP请求

将所有参数名和参数值采用utf-8进行URL编码(参数顺序可随意,但是必须要包括签名参数sign),然后通过GET或者POST发送HTTP请求,如:

http://gw.api.qianmi.com/api?appKey=10000&method=qianmi.elife.recharge.mobile.getItemInfo&v=1.1&format=json &access_token=7466bdfc5f79a7fe1defd9a5880a4b84&timestamp=2016-01-01+12%3A00%3A00&mobileNo=13888888888&rechargeAmount=100&sign=444F4A793F22D7483C240FC489D8DB8710D1F45A

6.返回结果示例
{
  status: 1,
  message: null,
  data: {
    itemId: "1414504",
    inPrice: "110.000",
    numberChoice: "1-10",
    province: "江苏",
    city: "南京",
    operator: "移动"
  }
}

七、注意事项

  • 所有请求和响应数据编码皆为utf-8
  • 没有特别说明的情况下,API接口超时时间为15秒
  • 部分E生活接口是否成功失败如果返回值有status,则按status值为参考,1成功 0失败
  • 查询类API建议使用GET请求,增删改类请求请使用POST请求
  • 正式环境和沙箱环境的appKey、appSecret不通用,请注意切换
  • 生成签名仅对未使用千米官方SDK进行API调用时需要自己生成,如果使用了官方SDK,该步骤会自动完成