api机制

概述

API概述

应用程序接口(API:application programming interface)是一组定义、程序及协议的集合,通过 API 接口实现网络多节点间的相互通信.

本API文档对两种机制做描述:

  • Ecstore对外提供的资源API接口(以下简称API)
  • Ecstore资源变更时主动发起的请求(以下简称主动发起)

API:

  • Matrix对接API(api)
  • 直联API
    • 不通过matrix直接联通的API机制,包括校验机制,调用方需要预先获取token
  • 开放API(open api)
    • 提供给与外系统直接进行互联的途径,没有校验机制,需自己添加

主动发起:

  • 订单变更
  • 支付单变更
  • ……

联通角色

  • Ecstore
    资源提供方,提供接口供接入方调用资源
    
  • ShopEx开放平台
    ShopEx开放平台(ShopEx Open Platform,简称SOP)ShopEx服务的开放平台,基于基础服务、数据和流程。提供互连互通服务。通过平台,连接一切。
    
          ShopEx是面向电子商务企业的数据和服务的综合性平台,为互联网电子商务企业应用提供应用接入、应用分销、整合方案、服务接入等一整套服务的开放性平台,通过接入ShopEx开放平台,电子商务企业可轻松与第三方开放应用数据整合、成本控制、资源共享等完整的解决方案。
    
          其主要内容包括:以OpenAPI形式开放的ShopEx电子商务基础服务、ShopEx自有的开放式应用平台、对第三方应用平台的开放式基础支持。
    
    
  • 接入方
    接入方为资源使用方
    

如何联通?

接入方发起(以下三种联通方式):

  • 联通ShopEx开放平台,通过ShopEx开放平台联通Ecstore
  • 通过Ecstore提供的直联API,直接调用(需要获取token,使用固定传输规则)
  • 通过Ecstore提供的开放API,直接调用

Ecstore发起:

  • 接入方接入ShopEx开放平台,Ecstore资源发生变更时主动发起请求通过ShopEx开放平台通知调用方
  • Ecstore资源发生变更时直接发送请求通知调用方

Matrix对接API

前提:需要在ecstore后台绑定接入方,同时要再 app/${app_id}(默认b2c)/apiv_mapper.xml中绑定双方API版本映射关系

版本机制介绍

在Ecstore2.0中针对API设计了版本机制。目前最新的API版本是2.0,之前的API版本设为1.0。接入方接入Ecstore时需要指定API版本号,若不指定,默认使用最新版本。

API版本机制新增相关文件

  app/${app_id}/apiv_mapper.xml 版本关系映射表(接入方的API版本和Ecstore API版本关系对应表)
  app/b2c/lib/apiv/             API机制主目录
        exchange/               路由器目录
        extends/                基类目录
        interface/              类接口目录
        apis/                   新增API实现目录

版本机制判断流程:

  • 来源请求中包含 from_node_id(调用方编号) 和 from_api_v(调用方API版本号)
    • 判断是否绑定
    • 查看版本映射表中是否有调用方API版本(apiv_mapper.xml),计算对应的本地版本号
  • 来源请求中不包含上列字段
    • 设定默认请求本地最高版本API(目前是2.0)
  • 从得到的本地版本开始查找,如果没有对应的接口,则向下递减(例如1.0)

如何调用

matrix对接API需要接入方通过ShopEx开放平台联通

API系统级请求参数

  • 数据格式 utf-8
  • HTTP请求content-type:application/x-www-form-urlencoded
  • 数据格式为datetime(如:)
  • 数据返回格式(json)
参数 类型 是否必须 描述
method String Y API接口名称
v String Y 矩阵API协议版本,可选值:1.0
timestamp String Y 时间戳,格式为yyyy-MM-dd hh:mm:ss,例如:2008-01-25 20:23:30。ShopEx API服务端允许客户端请求时间误差为10分钟
format String Y 可选,指定响应格式。默认JSON ,目前支持格式为JSON
sign String Y 对调用API时所有输入参数(包括应用级参数)进行签名结果
certi_id Number N 分配给应用的证书ID
from_node_id String N API调用的来源节点ID,对于第三方开发者来说,此id即为申请应用颁发的APP KEY
from_api_v String N 请求方版本号
to_node_id String N API调用的目的节点ID(除了基础服务类api,其他接口必传此参数)
to_api_v String N 接收方版本号
callback_url String N 响应回调地址,带http路径的标准URL。(调用异步接口此参数必填)

响应结果

{
  "res":"",
  "rsp":"succ",
  "data":
  {
    "tid":"000001"
  }
}
参数名称 描述
Rsp 请求是否正确 , succ 为成功 , fail 为失败
Res 返回的消息字符串.请求正确时为空,失败时为错误消息
Data 返回请求的数据结果集

签名算法(sign的生成方式)

<?php
function get_sign($params,$token){
    return 
strtoupper(md5(strtoupper(md5(assemble($params))).$token));
}
function 
assemble($params)
{
    if(!
is_array($params))  return null;
    
ksort($params,SORT_STRING);
    
$sign '';
    foreach(
$params AS $key=>$val){
        
$sign .= $key . (is_array($val) ? assemble($val) : $val);
    }
    return 
$sign;
}
?>


token的来源:

token是在域名绑定shopex_id时由商派中心生成的一串验签码,此验签码存放在config/certi.php文件中,和证书一起。


调用方式: 

base_certificate::token();

如何开发

    1. 注册对应的API service(规则:api-response_版本号_接口名称),例如:
          <service id="api-response_1.0_b2c.order">
            <class>b2c_api_ocs_1_0_order</class>
          </service>
          <service id="api-response_2.0_b2c.order">
            <class>b2c_apiv_apis_20_order</class>
          </service>
      
    2. 实现对应的接口方法

最佳实践(订单搜索)

  1. 注册订单模块API2.0接口:
        <service id="api-response_2.0_b2c.order">
          <class>b2c_apiv_apis_response20_order</class>
        </service>
    
  1. 创建 app/b2c/lib/apiv/apis/response20/order.php

    第一个参数为接入方请求的用户级参数,第二个参数是API控制对象

    <?php
        
    public function search$params, &$service )
        {
            
    //校验参数
            
    if( !( $start_time $params['start_time'] ) )
                
    $service->send_user_error('7001''开始时间不能为空!');
            if( (
    $start_time strtotime(trim($start_time))) === false || $start_time == -)
                
    $service->send_user_error('7002''开始时间不合法!');

            if( !( 
    $end_time $params['end_time'] ) )
                
    $service->send_user_error('7003''结束时间不能为空!');
            if( (
    $end_time strtotime(trim($end_time))) === false || $end_time == -)
                
    $service->send_user_error('7004''结束时间不合法!');

            
    $obj_orders = &app::get('b2c')->model('orders');

            
    ……
        
    }

直联API

如何调用

直联API同Matrix机制相同,唯一不同在于跳过了ShopEx开放平台。接入方直接发送请求到Ecstore。

API系统级请求参数

  • 接入地址:http://网店地址/index.php/api
  • 数据格式:utf-8
  • HTTP请求:支持GET、POST方式,支持GZIP压缩
  • 数据格式,例:direct=true&method=b2c.payment.create&sign=6F30EF7D2005A3DAF6D14DBEFEB59A7A
  • 数据返回格式(json)
参数 类型 是否必须 描述
direct string Y 设置为true
method String Y 指定调用api的service和mehtod. 例如:method设为b2c.payment.create 那么service:api.b2c.payment, method:create
sign String Y 签名,参看签名算法
date String Y 时间戳,格式为yyyy-MM-dd hh:mm:ss,例如:2008-01-25 20:23:30
format String N 可选,指定响应格式。默认json

响应结果

{
  "res":"",
  "rsp":"succ",
  "data":
  {
    "tid":"000001"
  }
}
参数名称 描述
Rsp 请求是否正确 , succ 为成功 , fail 为失败
Res 返回的消息字符串.请求正确时为空,失败时为错误消息
Data 返回请求的数据结果集

签名算法

同matrix对接API

如何开发

同matrix对接API

Ecstore主动发起

Ecstore资源发生变更时会对外主动发起请求。当前已有的发起点包括:

发起到ShopEx开放平台

前提:需要在ecstore后台绑定接入方,同时要再 app/${app_id}(默认b2c)/apiv_mapper.xml中绑定双方API版本映射关系

如何开发

    1. 注册对应service,规则是 api-request_本地api版本号_目标平台_动作,例如(订单新增):
          <service id="api-request_2.0_ecos.ome_ordercreate">
            <class>b2c_apiv_apis_request20_ome_order</class>
          </service>
      
    2. 实现对应方法,需要 extends b2c_apiv_extends_request,重载对应方法和变量。

最佳实践(订单创建时)

  1. 注册Service
        <service id="api-request_2.0_ecos.ome_ordercreate">
          <class>b2c_apiv_apis_request20_ome_order</class>
        </service>
    
  2. 实现
    <?php
    class b2c_apiv_apis_20_ome_order extends b2c_apiv_extends_request
    {
      var 
    $method 'store.trade.add';
      var 
    $callback = array();
      var 
    $title '订单新增';
      var 
    $timeout 1;
      var 
    $async true;

      public function 
    get_params($sdf)
      {
        
    $order_id $sdf['order_id'];
        
    $order_detail kernel::single('b2c_order_full')->get($order_id);
        return 
    $order_detail;
      }
    }

直接发起到接入方

如何开发

  1. 注册对应service(单个触发点可多次注册),规则是 api-request_out_动作,例如(订单新增):
        <service id="api-request_out_ordercreate">
          <class>b2c_apiv_apis_out_order</class>
        </service>
    
  2. 实现对应方法,需要 implements b2c_apiv_interface_requestout,在方法内发起请求到对应接入方API接口。

最佳实践(订单创建时)

  1. 注册Service
        <service id="api-request_out_ordercreate">
          <class>b2c_apiv_apis_out_order</class>
        </service>
    
  2. 实现
    <?php
    class b2c_apiv_apis_out_order implements b2c_apiv_interface_requestout
    {
      public function 
    init($sdf)
      {
          
    $url 'https://www.baidu.com';
          
    $core_http kernel::single('base_httpclient');
          
    $response $core_http->set_timeout(10)->post($url,$sdf,array(
                                                            
    'Content-Encoding' => 'gzip',
                                                            ));

          if(
    $response===HTTP_TIME_OUT){
              
    $headers $core_http->responseHeader;
              
    kernel::log('Request timeout, process-id is '.$headers['process-id']);
              return 
    false;
          }else{
              
          }
      }
    }

开放API(openapi)

开放API, 是很轻量级的API. 系统不支持签名验证, 也没有做异常处理. 因此可以按照实际业务需要定制开发签名验证和异常处理.

如何调用Ecos开放api

请求地址

http://{$mydomain}/index.php/openapi/{$openapi_key}/{$openapi_method}/{$key_1}/{$value_1}/{$key_2}/{$value_2}

如果服务器设置过rewrite

http://{$mydomain}/openapi/{$openapi_key}/{$openapi_method}/{$key_1}/{$value_1}/{$key_2}/{$value_2}

$myadmin:	域名
$openapi_key: 	open api的唯一标识
$openapi_method: 	调用方法
$key_1:		参数1
$value_1:	参数1的值
$key_2:		参数2
$value_2:	参数2的值

请求方法

通过POST/GET进行请求

小技巧:

1. 在系统中可以直接用工具类base_httpclient 来实现.
2. openapi的调用api可以用kernel::openapi_url()生成.

例如:
$http = new base_httpclient;
$url =  kernel::openapi_url('openapi.queue','worker',array('task_id'=>$task_id));
$http->post($url,$_POST);

请求参数

系统参数

用户参数

用户传参主要有以下两种方式

    • url传参(参见上文)
    • $_POST传参

如何开发openapi

应用场景

  • 系统内置回调api, 例如:队列, pam登陆
  • 多系统联通, 例如:Ecstore和erp进行联通. 但需要自定义传输格式(json/xml/serilize/..), 错误处理及签名认证.

如何注册新的openapi

openapi是通过service机制进行注册的, 默认的service box以"openapi."作为前缀, 来看一下我们系统目前所提供的所有openapi.

bryant@forsky %> ./cmd dev:show services | grep -i "^openapi"
openapi.rpc_callback                                    base_rpc_service
openapi.check                                           base_rpc_check
openapi.queue                                           base_service_queue
openapi.pam_callback                                    pam_callback
openapi.ectools_payment                                 ectools_payment_api
openapi.b2c.callback.shoprelation                       b2c_api_callback_shoprelation
左侧:	$openapi_key		open api的唯一标识
右侧: 	$openapi_class_name     注册在$openapi_key上的相应openapi处理类
补充知识:

如果需要使用 ./cmd dev:show services, 需要预装dev app

openapi类的写法

api类的存放位置: app/{$app_id}/lib/openapi/{$openapi_class_name}.php

系统实例化api类的时候会将对应的app对象({$app_id})作为参数传进来, 也可以通过调用app:get($app_id)来获取需要的app对象:

<?php

class {$openapi_class_name}
{
    
/**
     * app object
     */
    
public $app;

    
/**
     * 构造方法
     * @param object app
     */
    
public function __construct($app)
    {
        
$this->app app::get('ectools');
        
$this->app_b2c $app;
    }

api类方法的写法:

第一个参数: 将url传过来的参数转化成数组.array({$key1} => {$value1},{$key2} => {$vaule2),...)

返回值: 可通过echo/print_r等直接输出. 也可以和通信方约定传输格式及错误处理

<?php
class {$openapi_class_name}
{
    
/**
     * app object
     */
    
public $app;

    
/**
     * 构造方法
     * @param object app
     */
    
public function __construct($app)
    {
        
$this->app app::get('ectools');
        
$this->app_b2c $app;
    }
    public function 
create($params)
    {
    ...
    echo 
'succ';
    }
}

为openapi增加签名验证机制和错误处理

增加签名验证

签名验证主要目的是为了保证多节点通信的多方之间的信任关系

建议方法:

  • 唯一标识验证
    • 联通两端分别存放统一的token(一段字符串)
    • 在调用端将token作为调用参数. 
    • 在被调用端, 验证传输过来的token是否与本地存放token一致.
  • 签名认证.
    • 联通两端分别存放一致的token
    • 调用端用token对传输的所有参数(params)通过AC算法生成签名(sign), 然后将sign作为调用参数
    • 在被调用端, 取出sign参数, 然后用本地token对所有参数(除了sign参数)通过AC算法生成本地的sign, 然后跟远端传过来的sign进行比对看是否一致

AC算法:

<?php
function get_sign($params,$token){
    return strtoupper(md5(strtoupper(md5(assemble($params))).$token));
}
function assemble($params)
{
    if(!is_array($params))  return null;
    ksort($params,SORT_STRING);
    $sign = '';
    foreach($params AS $key=>$val){
        $sign .= $key . (is_array($val) ? assemble($val) : $val);
    }
    return $sign;
}
?>

增加错误处理机制
  • 通过set_error_handler函数定义错误处理函数(php4)或通过try catch进行捕获错误
  • 对返回值进行包装. 加入错误处理, 及处理相应错误的错误ID号
      例如:json
    
      失败:{"rsp":"fail","res":"4003","data":"sign error"}
      成功:{"rsp":"succ","res":"","data":"....."}
    
      请求失败的时候 res 会返回相关错误信息
    
      请求成功的时候 res 一般为空 data项返回相关数据信息(详情要看相关api接口文档)
    

    最佳实践

  • 场景: 开发一个订单汇总系统, 需要各个销售前端主动将数据推送过来, 设计符合实际业务的api
    • 安全性, 出于数据私密性的考虑, 对安全性有要求, 不希望接口被随意访问
    • 可靠性, 希望不丢单
    • 可维护性,当数据出了问题时, 能快速定位并解决
  • 方案:
    • 安全性: 增加签名验证, 已保证接口被可信任的系统访问
    • 可靠性: 当接口发生错误时, 系统可以捕获错误. 并通知订单推送端数据重打, 当重试次数大于3次时记错误日志, 并发邮件给管理员
    • 可维护性: 记录每一个打过来的业务api
  • 解决:
    • 安全性, 可靠性和可维护性都是公有化需求, 开发一个 myapp_openapi基类. 所有业务的api继承之, 下边给一个不算完整的例子以作参考

      openapi基类: myapp_openapi

          <?php
          class myapp_openapi {
      
              public function __construct() {
      	    set_error_handler(array('myapp_openapi', 'error_handler'));
      	}
      
              static function error_handler($errno, $errstr, $errfile, $errline ){
                  switch ($errno) {
                      case E_ERROR:
                      case E_USER_ERROR:
                          throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
                      break;
      
                      case E_STRICT:
                      case E_USER_WARNING:
                      case E_USER_NOTICE:
                      default:
                          //do nothing
                      break;
      	    }
      	}
      
      	function log($message) {
      	    error_log($message, 0);
      	}
      
      	function verify($params) {
      	    //验证是否合法
      	}
      
      	//返回成功
      	function send_user_succ($result){
                  $result_json = array(
                      'rsp'=>'succ',
                      'data'=>$result,
                  }
                 echo json_encode($result_json);
      	   exit;
      	}
      
      	//返回失败
      	function send_user_error($code, $data){
                  $res = array(
                      'rsp'   =>  'fail',
                      'res'   =>  $code,
                      'data'  =>  $data,
                  );
                  echo json_encode($res);
      	    exit;
      	}
          }
      
      业务api: myapp_openapi_login
          <?php
          class myapp_openapi_login extends myapp_openapi {
              public function __construct($app){
                  $this->app = $app;
      	    parent::__construct();
              }
      
      	/登陆
      	function login($params) {
      	   if (parent::verify($parames)) {
      	       //如果成功则记录日志
      	       $this->log("join user");
      	   }else{
      	       //如果失败则返回错误信息
      	       $this->send_user_error('4003', 'user login fail');
      	   }
      	}
          }