有句话说得好,最可怕的事情不是别人比你优秀,而是优秀的人竟然还比你更努力。 —《考拉小巫的留学成长日记》

此篇章主要是讲述接口统一请求的方式,以及提供一个PHP实现的简单客户端。

1.13.1 指定接口服务:?service=XXX.XXX

我们统一固定用service参数来表示需要请求获得的服务,并通过GET方式传递,即请求的URI格式为:

  1. 接口域名 + 入口路径 + ?service=XXX.XXX
  2. 如:
  3. http://dev.phalapi.com + /demo/ + ?service=User.GetBaseInfo

当我们在浏览器以GET方式请求时,可以在nignx看到这样的日记:

  1. 127.0.0.1 - - [07/Feb/2015:22:46:46 -0800] "GET /demo/?service=User.GetBaseInfo&sign=&user_id=1 HTTP/1.1" 200 107 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:11.0) Gecko/20100101 Firefox/11.0"

如果通过接口用POST方式请求,则会看到:

  1. 127.0.0.1 - - [07/Feb/2015:19:32:05 -0800] "POST /demo/?service=User.GetBaseInfo&sign= HTTP/1.1" 200 135 "-" "-"

这里service的名称,开头不区分大小写,建议统一以大写开头,以显得专业。对应的接口是:

  1. class Api_User extends PhalApi_Api {
  2. public function getBaseInfo() {
  3. }
  4. }

其他传递接口服务名称的方式

虽然在这里,我们统一使用?service=XXX.XXX的格式来传递接口服务名称,但如果项目有需要,也可以采用其他方式来传递,如使用熟悉的斜杠分割:?service=XXX/XXX,或者使用省略方法名的方式而在服务端自动补全默认方法,又或者使用其他参数名称,如使用?s=XXX.XXX

如果需要采用其他传递接口服务名称的方式,则可以重载PhalApi_Request::getService()方法。以下是改用斜杠分割、并换用r参数名字的实现示例:

  1. <?php
  2. class Common_Request extends PhalApi_Request {
  3. public function getService() {
  4. // 优先返回自定义格式的接口服务名称
  5. $servcie = $this->get('r');
  6. if (!empty($servcie)) {
  7. return str_replace('/', '.', $servcie);
  8. }
  9. return parent::getService();
  10. }
  11. }

实现好自定义的请求类,需要在入口文件./Public/项目/index.php进行注册:

  1. //装载你的接口
  2. DI()->loader->addDirs('Demo');
  3. DI()->request = new Common_Request();

这样,请求接口服务的方式便可以变成这样了。

原来的方式 现在的方式
?service=Default.Index ?r=Default/Index
?servcie=User.GetBaseInfo ?r=User/GetBaseInfo

这里有几个注意事项:

  • 1、需要转换为原始的接口服务格式,即:XXX.XXX
  • 2、为保持兼容性,子类需兼容父类的实现。即在取不到自定义的接口服务名称参数时,应该返回原来的。
温馨提示:以上定制功能,需要PhalApi 1.4.0及以上版本,方可支持。

1.13.2 统一参数用GET

在一个项目中,会有很多公共的接口参数,如客户端、版本号、密钥等。这些同样可以纳入GET参数里面,或者也可以放到POST里面。

温馨提示:这样要求是有目的的,因为这样的话可以在nginx的access日记里面查看来自客户端的快照信息,以便统计或者定位问题。

1.13.3 接口参数用POST

特别地,接口参数我们建议统一使用POST方式传递,理由很简单:

  • 1、相对保护上传的数据,一如密码;
  • 2、避免特殊字符或者过大数据包在GET下的限制;

1.13.4 获取Header头信息

每个请求都有自己的Header头,获取Header头信息可以判断请求来源或者是把一些敏感的内容存放到Header头信息中(如:加密后的参数),框架也提供了方便快捷获取Header的方法:

  1. DI()->request->getHeader("Host");

1.13.5 测试下的模拟参数

默认地,PhalApi框架会将$_REQUEST作为接口参数的来源:

  1. DI()->request = 'PhalApi_Request';

当我们需要统一强制用$_GET,可以在init.php文件中这样简单定制:

  1. DI()->request = new PhalApi_Request($_GET);

同样,也可以在init.php文件中强制用$_POST:

  1. DI()->request = new PhalApi_Request($_POST);

在测试环境下,为了模拟接口请求,我们需要人工提供接口参数,因此可以这样轻松模拟:

  1. $str = 'service=User.GetBaseInfo&user_id=1';
  2. parse_str($str, $params);
  3. DI()->request = new PhalApi_Request($params);

1.13.6 PHP接口客户端示例

先看下调用和使用的代码示例:

  1. <?php
  2. require_once './PhalApiClient.php';
  3. $config = array(
  4. 'host' => 'http://dev.phalapi.com/demo',
  5. 'secrect' => '******'
  6. );
  7. $client = new PhalApiClient($config);
  8. $rs = $client->request('User.GetBaseInfo', array('userId' => 1));
  9. if ($client->getRet() == PhalApiClient::RET_OK) {
  10. var_dump($rs);
  11. } else {
  12. var_dump($client->getMsg());
  13. var_dump($client->getUrl());
  14. }

附调用接口的客户端源代码:

  1. //$ vim ./PhalApiClient.php
  2. <?php
  3. class PhalApiClient
  4. {
  5. protected $host;
  6. protected $secrect = '';
  7. protected $params = array();
  8. protected $moreParams = array();
  9. protected $url;
  10. protected $ret;
  11. protected $msg;
  12. protected $data = array();
  13. const RET_OK = 'OK';
  14. const RET_WRONG = 'WRONG';
  15. const RET_ERROR = 'ERROR';
  16. public function __construct($config)
  17. {
  18. $this->host = rtrim($config['host'], '/') . '/';
  19. $this->secrect = $config['secrect'];
  20. }
  21. public function request($service, $params = array(), $timeoutMs = 3000)
  22. {
  23. if (!empty($service)) {
  24. $this->params['service'] = $service;
  25. }
  26. $this->params['sign'] = $this->encryptAppKey($params, $this->secrect);
  27. $this->url = $this->host . '?' . http_build_query($this->params);
  28. $this->moreParams = $params;
  29. $rs = $this->doRequest($this->url, $params, $timeoutMs);
  30. if ($rs === false) {
  31. $this->ret = self::RET_ERROR;
  32. $this->msg = '后台接口请求超时';
  33. return $this->getData();
  34. }
  35. $rs = json_decode($rs, true);
  36. if (isset($rs['data']['code']) && $rs['data']['code'] != 0) {
  37. $this->ret = self::RET_WRONG;
  38. $this->msg = '接口调用失败[code =' . $rs['data']['code'] . ']' . ', 错误>信息:' . isset($rs['data']['msg']) ? $rs['data']['msg'] : '无';
  39. return $this->getData();
  40. }
  41. $this->ret = intval($rs['ret']) == 200 ? self::RET_OK : self::RET_WRONG;
  42. $this->data = $rs['data'];
  43. $this->msg = $rs['msg'];
  44. return $this->getData();
  45. }
  46. public function getRet()
  47. {
  48. return $this->ret;
  49. }
  50. public function getData()
  51. {
  52. return $this->data;
  53. }
  54. public function getMsg()
  55. {
  56. return $this->msg;
  57. }
  58. public function getUrl()
  59. {
  60. return $this->url . '&' . http_build_query($this->moreParams);
  61. }
  62. protected function encryptAppKey($params, $secrect)
  63. {
  64. return '';
  65. }
  66. protected function doRequest($url, $data, $timeoutMs = 3000)
  67. {
  68. $ch = curl_init();
  69. curl_setopt($ch, CURLOPT_URL, $url);
  70. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  71. curl_setopt($ch, CURLOPT_HEADER, 0);
  72. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $timeoutMs);
  73. if (!empty($data)) {
  74. curl_setopt($ch, CURLOPT_POST, 1);
  75. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  76. }
  77. $rs = curl_exec($ch);
  78. curl_close($ch);
  79. return $rs;
  80. }
  81. }

原文: https://www.phalapi.net/wikis/1-13.html