应用程序接口(API:application programming interface)是一组定义、程序及协议的集合,通过 API 接口实现网络多节点间的相互通信.
本API文档对两种机制做描述:
API:
主动发起:
资源提供方,提供接口供接入方调用资源
ShopEx开放平台(ShopEx Open Platform,简称SOP)ShopEx服务的开放平台,基于基础服务、数据和流程。提供互连互通服务。通过平台,连接一切。
      ShopEx是面向电子商务企业的数据和服务的综合性平台,为互联网电子商务企业应用提供应用接入、应用分销、整合方案、服务接入等一整套服务的开放性平台,通过接入ShopEx开放平台,电子商务企业可轻松与第三方开放应用数据整合、成本控制、资源共享等完整的解决方案。
      其主要内容包括:以OpenAPI形式开放的ShopEx电子商务基础服务、ShopEx自有的开放式应用平台、对第三方应用平台的开放式基础支持。
接入方为资源使用方
接入方发起(以下三种联通方式):
Ecstore发起:
前提:需要在ecstore后台绑定接入方,同时要再 app/${app_id}(默认b2c)/apiv_mapper.xml中绑定双方API版本映射关系
在Ecstore2.0中针对API设计了版本机制。目前最新的API版本是2.0,之前的API版本设为1.0。接入方接入Ecstore时需要指定API版本号,若不指定,默认使用最新版本。
  app/${app_id}/apiv_mapper.xml 版本关系映射表(接入方的API版本和Ecstore API版本关系对应表)
  app/b2c/lib/apiv/             API机制主目录
        exchange/               路由器目录
        extends/                基类目录
        interface/              类接口目录
        apis/                   新增API实现目录
matrix对接API需要接入方通过ShopEx开放平台联通
| 参数 | 类型 | 是否必须 | 描述 | 
|---|---|---|---|
| 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 | 返回请求的数据结果集 | 
<?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;
}
?>
    <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>
    <service id="api-response_2.0_b2c.order">
      <class>b2c_apiv_apis_response20_order</class>
    </service>
第一个参数为接入方请求的用户级参数,第二个参数是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 == -1 )
            $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 == -1 )
            $service->send_user_error('7004', '结束时间不合法!');
        $obj_orders = &app::get('b2c')->model('orders');
        ……
    }
直联API同Matrix机制相同,唯一不同在于跳过了ShopEx开放平台。接入方直接发送请求到Ecstore。
| 参数 | 类型 | 是否必须 | 描述 | 
|---|---|---|---|
| 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 | 返回请求的数据结果集 | 
Ecstore资源发生变更时会对外主动发起请求。当前已有的发起点包括:
前提:需要在ecstore后台绑定接入方,同时要再 app/${app_id}(默认b2c)/apiv_mapper.xml中绑定双方API版本映射关系
    <service id="api-request_2.0_ecos.ome_ordercreate">
      <class>b2c_apiv_apis_request20_ome_order</class>
    </service>
    <service id="api-request_2.0_ecos.ome_ordercreate">
      <class>b2c_apiv_apis_request20_ome_order</class>
    </service>
<?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;
  }
}
    <service id="api-request_out_ordercreate">
      <class>b2c_apiv_apis_out_order</class>
    </service>
    <service id="api-request_out_ordercreate">
      <class>b2c_apiv_apis_out_order</class>
    </service>
<?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, 是很轻量级的API. 系统不支持签名验证, 也没有做异常处理. 因此可以按照实际业务需要定制开发签名验证和异常处理.
https://{$mydomain}/index.php/openapi/{$openapi_key}/{$openapi_method}/{$key_1}/{$value_1}/{$key_2}/{$value_2}
如果服务器设置过rewrite
https://{$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);
无
用户传参主要有以下两种方式
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
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';
    }
}
签名验证主要目的是为了保证多节点通信的多方之间的信任关系
建议方法:
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;
}
?>
  例如:json
  失败:{"rsp":"fail","res":"4003","data":"sign error"}
  成功:{"rsp":"succ","res":"","data":"....."}
  请求失败的时候 res 会返回相关错误信息
  请求成功的时候 res 一般为空 data项返回相关数据信息(详情要看相关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');
	   }
	}
    }