三新专号服务程序应用程序接口文档

构建时间:2015-11-09 06:35:21

內容目录

三新专号服务程序应用程序接口文档

readthedocs 构建状态:

https://readthedocs.org/projects/sanxin-zhuanhao-service-apidoc/badge/?version=master https://readthedocs.org/projects/sanxin-zhuanhao-service-apidoc/badge/?version=develop

什么是三新专号

todo: 什么是三新专号 ... ...

关于该文档

该文档规定了三新专号系统中,服务程序与客户端程序(包括且不限于App,WebApp)之间的程序接口。 服务器程序与客户端开发者需要遵照该文档的规定进行程序开发工作。

其设计的根据是三新专号 App 的界面原型 https://modao.cc/app/POxerAK2EbEA7gmDx2GT

原则

  1. 基于 HTTP 1.1HTTP 2.0 就算了吧 -_-)的 WebAPI
  2. 客户端 -> 服务器 单方向访问
  3. Restfule API 形态
  4. 使用 UTF8 编码的 JSON 记录结构化数据
  5. 使用 HTTPS

HTTP Content

POST 或者 PUT 请求采用 JSON 格式的数据, 此时 Content-Type 头域的值应是 application/json

回复的 Content 也必须是 JSON 格式, 其 Content-Type 头域的值应是 application/json

如果不需要返回数据,为了保证 Content 可以被 JSON 解码,应使用 null 作为回复的内容,例如:

HTTP/1.1 200 OK
Content-Type: application/json

null

HTTP Status Code

200 执行成功

如果API调用成功,服务器应返回状态码 200 OK

回复中,头域 Content-Type 一定application/json ,内容 一定JSON 格式。

500 执行失败

如果服务器在响应API调用期间出现错误,应返回 500 Internal Server Error

如果服务器可以提供具体的错误编码以及错误信息,应采用 JSON 格式在内容中记录这些数据: code 属性记录错误编码, text 属性记录错误文本信息。

如:

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{"code": 10013, "text": "calee not allowed"}

注意

服务器无法在所有情况在都提供 JSON 格式错误信息,客户端应注意判断。

401 授权错误

登录失败, API 请求签名验证错误,均返回该 Status Code

其它

其它 Status Code 均遵照 RFC 2616 的定义

安全

User API 安全

当APP向发起HTTP请求时, 必须 附带若干URL参数,作为验证依据。

后台服务在收到请求时,会进行验证,并拒未通过绝验证的请求。

ANY /api/user/(string: telnum)/*

该系统的服务器需要为每一个接入程序(以下简称 APP )分配相应的 ACCESS-IDACCESS-KEY

APP 在调用服务器接口时,需要使用 ACCESS-IDACCESS-KEY 进行签名。 服务器会验证签名的正确性,并拒绝验证不通过的请求。

使用URL参数承载签名数据。

Query Parameters:
 
  • accessid (string) – 客户端访问ID,由系统管理员分配
  • timestamp (string) –

    Unix 时间戳,形如: 1445851008

    注意

    服务器将验证时间戳,如果与服务器时间差超过正负48小时,服务器将认为验证失败

  • signature (string) – 签名,由 url path, telnum, password , token (见 POST /api/user/(string: telnum)/login) , timestampaccessidaccesskey 7个参数计算得出

APP应将 signature 参数填写为 SHA1 哈希值的16进制字符串表达式,其算法是:

  1. url path (裁剪结尾的 / ), telnum, password (大写MD5散列HEX表达式) , token, timestamp, accessid, accesskey (大写MD5散列HEX表达式) 这7个参数按照字符串顺序进行从小到大的排序。
  2. 将排序完毕的各个参数字符串头尾相连,连接成新的字符串。
  3. 计算上一个步骤中,连接好的字符串的 SHA-1 散列值字符串,这个字符串是经过大写字母转换(upper case)的十六进制表达式。

注意

调用登录接口时(POST /api/user/(string: telnum)/login), token 参数应使用空字符串

签名算法例子代码

在下面的例子中:

手机号码为 13887654321 的用户已经登录;

其登录密码是 This_Is#My&p@ssw0rd

该APP的访问ID与KEY分别是 developer-001xm90uojWSd34E8y3

token=4C609E5D5D234A406D446EA42898EFAD50E4541C

当前时间戳是 1407812629434

要访问的URL路径是 /api/user/13887654321/path/of/the/api

那么,最终计算得出的签名将是 DCE009D2AF85050E249A6511D1C0F0F180EDFA64

加上验证和签名参数的完整url是:

http://api/user/13887654321/path/of/the/api?accessid=developer-001&timestamp=1407812629434&signature=DCE009D2AF85050E249A6511D1C0F0F180EDFA64

下面是几种语言的算法实现片段:

Java
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;

public class Signature {

      public static String byteArrayToHex(byte[] byteArray) {
              char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                              'A', 'B', 'C', 'D', 'E', 'F' };
              char[] resultCharArray = new char[byteArray.length * 2];
              int index = 0;
              for (byte b : byteArray) {
                      resultCharArray[index++] = hexDigits[b >>> 4 & 0xf];
                      resultCharArray[index++] = hexDigits[b & 0xf];
              }
              return new String(resultCharArray);
      }

      public static String hashStr(String input, String digest)
                      throws NoSuchAlgorithmException {
              MessageDigest messageDigest = MessageDigest.getInstance(digest);
              byte[] inputByteArray = input.getBytes();
              messageDigest.update(inputByteArray);
              byte[] resultByteArray = messageDigest.digest();
              return byteArrayToHex(resultByteArray);
      }

      public static String calcSignature() throws NoSuchAlgorithmException {
              String accessid = "developer-001";
              String accesskey = hashStr("xm90uojWSd34E8y3", "MD5");
              String urlpath = "/api/user/13887654321/path/of/the/api";
              String telnum = "13887654321";
              String token = "4C609E5D5D234A406D446EA42898EFAD50E4541C";
              String password = hashStr("This_Is#My&p@ssw0rd", "MD5");
              String timestamp = "1407812629434";

              ArrayList<String> tmpList = new ArrayList<String>();
              tmpList.add(accessid);
              tmpList.add(accesskey);
              tmpList.add(urlpath);
              tmpList.add(telnum);
              tmpList.add(token);
              tmpList.add(password);
              tmpList.add(timestamp);
              Collections.sort(tmpList);

              String result = hashStr(String.join("", tmpList), "SHA1");
              return result;
      }

      public static void main(String[] args)  {
              try {
                      String sigstr = calcSignature();
                      System.out.format("Signature = %s", sigstr);
              } catch (NoSuchAlgorithmException e) {
                      e.printStackTrace();
              }
      }

}
NodeJs
var crypto = require('crypto');

(function (){
  let hashStr = function (input, algorithm) {
    let hasher = crypto.createHash(algorithm);
    hasher.update(input);
    return hasher.digest('hex').toUpperCase();
  }

  let accessid = "developer-001";
  let accesskey = hashStr("xm90uojWSd34E8y3", "md5");
  let urlpath = "/api/user/13887654321/path/of/the/api";
  let telnum = "13887654321";
  let token = "4C609E5D5D234A406D446EA42898EFAD50E4541C";
  let password = hashStr("This_Is#My&p@ssw0rd", "md5");
  let timestamp = "1407812629434";

  let tmpList = [accessid,accesskey, urlpath, telnum, token, password, timestamp];
  tmpList.sort();
  let signature = hashStr(tmpList.join(''), 'sha1');

  console.log("signature = " + signature);
})();
Php
<?php
$accessid = 'developer-001';
$accesskey = strtoupper(md5('xm90uojWSd34E8y3'));
$url_path = '/api/user/13887654321/path/of/the/api';
$telnum = '13887654321';
$token = '4C609E5D5D234A406D446EA42898EFAD50E4541C';
$password = strtoupper(md5('This_Is#My&p@ssw0rd'));
$timestamp = '1407812629434';
$tmp_arr = array($accessid, $accesskey, $url_path, $telnum, $token, $password, $timestamp);
sort($tmp_arr, SORT_STRING);
$signature = strtoupper(sha1(implode($tmp_arr)));
echo(signature);
Python

仅适用于 Python 3.0+

from hashlib import sha1, md5
accessid = b'developer-001'
accesskey = bytes(md5(b'xm90uojWSd34E8y3').hexdigest().upper(), 'ascii')
url_path = b'/api/user/13887654321/path/of/the/api'
telnum = b'13887654321'
token = b'4C609E5D5D234A406D446EA42898EFAD50E4541C'
password = bytes(md5(b'This_Is#My&p@ssw0rd').hexdigest().upper(), 'ascii')
timestamp = b'1407812629434'
signature = sha1(b''.join(sorted([accessid, accesskey, url_path, telnum, token, password, timestamp]))).hexdigest().upper()
print(signature)
获取 Unix 时间戳的例子代码

以下是几种常见语言获取 Unix 时间戳的方法:

C
#include <time.h>       /* time_t, struct tm, time ... */

/// ... ...
time_t val = time(NULL);
int ts = (int) val;

/// ... ...
C#
int tx = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
Java
long ts = System.currentTimeMillis()/1000L;
Javascript
var ts = Math.floor(Date.now()/1000);
Php
<?php
$ts = time();
Python
import time
ts = int(time.time())

CTI API 安全

ANY /api/cti/*

CTI服务器与Web服务器之间的通信与APP的不同,它们之间的API调用是内部的、固定的、无身份的。

Web服务器 同时 使用下面两种方法保障CTI调用的安全:

  1. SSL 客户端证书:

    Web服务器要为CTI服务器颁客户端SSL证书,只有通过SSL验证的HTTP请求才被允许。 建议使用 nginx 进行SSL证书验证,见 http://nginx.org/en/docs/http/ngx_http_ssl_module.html

  2. HTTP 基本认证

    Web服务器要针对CTI的HTTP亲求进行HTTP基本认证

验证与授权

登录

POST /api/user/(string: telnum)/login

手机号码为 telnum 的用户登录

Request JSON Object:
 
  • password (string) – 密码的MD5散列,用十六进制字符串(其中的小写字母转要换成大写字母)表示
Response JSON Object:
 

注意

token 具有时效性。服务程序在一段时间之后,会抛弃原来的令牌。 此时,客户端需要调用登录接口,以获取新的令牌。 一点调用该接口,原来的令牌就会失效。

注销

POST /api/user/(string: telnum)/logout

注销手机号码为 telnum 的用户

用户管理

用户管理API用于管理用户的账户信息和基本资料。

注意

在本系统中,用户使用手机号码登录,且专号与手机号码绑定,因此,手机号码被视作用户ID。

获取用户信息

GET /api/user/(string: telnum)

返回手机号码为 telnum 的用户的基本信息

Response JSON Object:
 
  • telnum (string) – 用户的手机号码
  • name (string) – 用户的名称
  • createtime (string) – 用户建立的时间(ISO格式)
  • avatar (string) – 用户的头像(BASE64格式)

注册新用户

POST /api/user
Request JSON Object:
 
  • telnum (string) – 用户的手机号码 (必填)
  • name (string) – 用户的名称 (必填)
  • avatar (string) – 用户的头像(BASE64格式) (非必填)

修改用户信息

PUT /api/user/(string: telnum)

修改手机号码为 telnum 的用户的基本信息

Request JSON Object:
 
  • name (string) – 用户的名称
  • avatar (string) – 用户的头像(BASE64格式)

注解

JSON 中的属性都不是必填项,属性值为 null 的或者没有提供属性的,不会被修改。

删除用户

DELETE /api/user/(string: telnum)

删除手机号码为 telnum 的用户

专号管理

专号管理API用于管理用户所属的专号

获取专号列表

GET /api/user/(string: telnum)/vtelnum

返回手机号码为 telnum 的用户所拥有的专号列表

Query Parameters:
 
  • page (int) – 要返回的页码。默认为1(1开始)。
  • perPage (int) – 每页长度。如果不指定,服务器采用其默认设置。
Response Headers:
 
  • X-Pagination-Current-Page – 每页长度
  • X-Pagination-Per-Page – 当前返回结果的页码(1开始)
  • X-Pagination-Totle-Pages – 总页数
  • X-Pagination-Totle-Entries – 总条目数
Response JSON Object:
 
  • vtelnum (string) – 专号
例子

此例子中,返回手机号码 (telnum) 为 123 的用户,所拥有的5个专号中的第3~4个

Request

GET /api/user/123/vtelnum?page=2&perPage=2 HTTP/1.1
Host: example.com

Response

HTTP/1.1 200 OK
Content-Type: application/json
X-Pagination-Current-Page: 2
X-Pagination-Per-Page: 2
X-Pagination-Totle-Pages: 3
X-Pagination-Totle-Entries: 5

[{"vtelnum": "10003"}, {"vtelnum": "10004"}]

获取可选专号列表

GET /api/user/(string: telnum)/availablevtelnum

返回手机号码为 telnum 的用户可以选取的可用专号的列表

Query Parameters:
 
  • page (int) – 要返回的页码。默认为1(1开始)。
  • perPage (int) – 每页长度。如果不指定,服务器采用其默认设置。
Response Headers:
 
  • X-Pagination-Current-Page – 每页长度
  • X-Pagination-Per-Page – 当前返回结果的页码(1开始)
  • X-Pagination-Totle-Pages – 总页数
  • X-Pagination-Totle-Entries – 总条目数
Response JSON Object:
 
  • vtelnum (string) – 专号

绑定新专号

POST /api/user/(string: telnum)/vtelnum

手机号码为 telnum 的用户为该手机号码绑定一个新的专号

Request JSON Object:
 
  • vtelnum (string) – 要绑定的新专号

取消专号绑定

DELETE /api/user/(string: telnum)/vtelnum/(string: vtelnum)

手机号码为 telnum 的用户取消该手机号码绑定的的一个专号

更换专号

POST /api/user/(string: telnum)/vtelnum/(string: vtelnum)/replace
手机号码为 telnum 的用户取消该手机号码绑定的专号替换成另外一个
Request JSON Object:
 
  • vtelnum (string) – 要更换的新专号

电话控制

发起呼叫

POST /api/user/(string: telnum)/makecall

手机号码为 telnum 的用户通知服务器,将要发起呼叫

Request JSON Object:
 
  • caller (string) – 作为主叫号码专号
  • callee (string) – 被叫号码
Response JSON Object:
 
  • callid (string) – 呼叫ID

注解

调用用该API之后,服务器将记录 telnum 的呼叫请求。

2分钟 之内,服务器如果收到 telnum 向其专号 caller 的呼叫, 就会自动接通,然后以 caller 为主叫号码,向 callee 发起呼叫,并桥接两者之间的通话。

注意

对于同一个手机号码,服务器只保存其最近一次呼叫请求。 如果同一个手机号码连续调用该接口,服务器将以最后一次请求为准。

注意

如果没有调用该接口,或者调用后又取消(POST /api/user/(string: telnum)/cancelcall), 服务器不会接受 telnumcaller 的电话呼叫。

例子

此例中,电话号码为 1001 的用户,专号是 2001 ,他使用该专号呼叫目标号码 3001

Request

POST /api/user/1001/makecall HTTP/1.1
Host: example.com
Content-Type: application/json

{"caller": "2001", "callee": "3001"}

Response

HTTP/1.1 200 OK
Content-Type: application/json

{"callid": "12870498123"}
顺序图

下面使用顺序图描述一个呼叫场景。 该场景中:电话号码为 1001 的用户,专号是 2001 ,他使用该专号呼叫目标号码 3001

blockdiag UserPhone WebServer CtiServer TargetPhone POST /api/user/1001/makeca ll {"caller":"2001","callee": "3001"} 200 OK {"callid":"27"} CALL: 3001 POST /api/cti/callin {"from":"1001","to":"3001" } 200 OK {"action":"bridge","caller ":"2001","callee":"3001"} CALL: from=2001,to=3001 RINGING RINGING ANSWER ANSWER

取消呼叫

POST /api/user/(string: telnum)/cancelcall

取消上次的呼叫请求

电话服务

电话网服务器(CTI)通过调用Web服务器的电话服务接口,控制其电话线路的接续、转接等行为。

注意

这一系列的接口需要进行 HTTP 基本认证

呼入事件

POST /api/cti/callin

电话网服务器(CTI)在接到电话呼入后,调用这个API。 Web服务器应在API的返回中,告知CTI服务器后续动作。

Request JSON Object:
 
  • from (string) – 主叫号码
  • to (string) – 被叫号码
Response JSON Object:
 
  • action (string) –

    CTI后续动作,有:

    • bridge : 桥接
    • refuse : 拒接
  • caller (string) –

    进行桥接时的主叫号码

    注解

    仅当 action“bridge” 时有效

  • callee (string) –

    进行桥接时的被叫号码

    注解

    仅当 action“bridge” 时有效

例子

此例中,电话号码为 1001 的用户,专号是 2001 ,他使用该专号呼叫目标号码 3001

当APP的呼叫发起请求 (POST /api/user/(string: telnum)/makecall) 提交成功后, APP控制手机呼叫 2001 。 此时,CTI服务器询问Web服务器后续动作,而Web服务器告知CTI应以 2001 的名义呼叫 3001 , 并桥接 10013001

Request

POST /api/cti/callin HTTP/1.1
Host: example.com
Content-Type: application/json

{"from": "1001", "to": "2001"}

Response

HTTP/1.1 200 OK
Content-Type: application/json

{"action": "bridge", "caller": "2001", "callee": "3001"}