api机制
概述
API概述
应用程序接口(API:application programming interface)是一组定义、程序及协议的集合,通过 API 接口实现网络多节点间的相互通信.
本API文档对两种机制做描述:
- Ecstore对外提供的资源API接口(以下简称API)
- Ecstore资源变更时主动发起的请求(以下简称主动发起)
API:
- Matrix对接API(api)
- 系统自带的标准API, 是和ShopEx开放平台(ShopExOpenPlatform) 进行互联互通的专用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版本映射关系
版本机制介绍
在ECStore中针对API设计了版本机制。目前最新的API版本是2.0,之前的API版本设为1.0。接入方接入Ecstore时需要指定API版本号,若不指定,默认使用最新版本。
API版本机制新增相关文件
- 新增api.xml
app/{app_id}/api.xml 所有ECSTORE系统提供的API都需要在api.xml中进行注册,才可以被外部系统调用 api.xml中只进行API接口注册,不提供openapi接口注册,openapi还是和旧版本一样在services.xml中进行注册 修改api.xml后 cmd update 更新api.xml中注册的API接口
- 新增apiv_mapper.xml
app/${app_id}/apiv_mapper.xml 版本关系映射表(接入方的API版本和Ecstore API版本关系对应表)
- 新版API目录
▾ b2c/ ▾ lib/ ▸ api/ 留下旧API版本的回调地址和系统基本请求外部API ▾ apiv/ API机制主目录 ▾ apis/ 新增API实现目录 ▸ request/ ECStore调用请求外部系统API ▸ response/ ECStore提供API实现类 ▸ exchanges/ 路由器目录 ▸ extends/ 基类目录 ▸ interface/ 类接口目录
- 查看ECStore提供的API列表
1、查看API列表 ➜ base git:(hotfix) ./cmd base:api list 提供API的APP:b2c API:b2c.coupon.get_coupon_list 获取优惠劵列表 API:b2c.coupon.get_coupon_number 获取优惠券,只有B类优惠券需要调用 API:b2c.coupon.get_coupon_use_log 获取优惠券使用记录 API:b2c.coupon.create_coupon_to_member 对指定会员发放指定的B类优惠券 API:b2c.order.basic.create 添加单笔交易 API:b2c.order.leave_message 添加订单留言 API:b2c.order.detail 获取单笔交易的详细信息 API:b2c.order.remark 修改一笔交易备注 API:b2c.order.status_update 订单状态更新 ...... 2、调试API - 在app/b2c/testcase/api/basic.php中 $api_params定义需要测试API的应用级参数 - ➜ base git:(hotfix) ./cmd dev:test do b2c api.php b2c.coupon.get_coupon_list
版本机制判断流程:
- 来源请求中包含 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();
如何开发
- 注册对应的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>
- 实现对应的接口方法
最佳实践(订单搜索)
- 注册订单模块API2.0接口:
<service id="api-response_2.0_b2c.order"> <class>b2c_apiv_apis_response20_order</class> </service>
- 创建 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 == -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
如何调用
直联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 | 返回请求的数据结果集 |
签名算法
如何开发
Ecstore主动发起
Ecstore资源发生变更时会对外主动发起请求。当前已有的发起点包括:
发起到ShopEx开放平台
前提:需要在ecstore后台绑定接入方,同时要再 app/${app_id}(默认b2c)/apiv_mapper.xml中绑定双方API版本映射关系
如何开发
- 注册对应service,规则是 api-request_本地api版本号_目标平台_动作,例如(订单新增):
<service id="api-request_2.0_ecos.ome_ordercreate"> <class>b2c_apiv_apis_request20_ome_order</class> </service>
- 实现对应方法,需要 extends b2c_apiv_extends_request,重载对应方法和变量。
最佳实践(订单创建时)
- 注册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(单个触发点可多次注册),规则是 api-request_out_动作,例如(订单新增):
<service id="api-request_out_ordercreate"> <class>b2c_apiv_apis_out_order</class> </service>
- 实现对应方法,需要 implements b2c_apiv_interface_requestout,在方法内发起请求到对应接入方API接口。
最佳实践(订单创建时)
- 注册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 = 'http://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'); } } }
- 安全性, 可靠性和可维护性都是公有化需求, 开发一个 myapp_openapi基类. 所有业务的api继承之, 下边给一个不算完整的例子以作参考