搭建API服务

在实战项目中,为了和别的项目进行一些必要的对接,你的web应用可能 需要提供一些必要的api,而为了统一管理api,你可能需要搭建一个api服务专门用来提供便捷的远程api调用。

Herosphp为你提供了2种API服务的架构方案,供你参考。

1. 建立一个API的模块

你可以把所有的对外的API服务都都在一个模块中(比如API模块),这样可能方便管理。 那么,你的API模块的结构可能会是这样的:

|-- api
|---- action 
|---- service

你可以所有的服务都写在 service 目录,当然,你也可以把你的api分散到各个模块,不过这适合第二种方案。 由于你的api是要对外提供服务的,那么常规的做法是要为每个服务建立一个控制器,然后把对应的服务通过控制器暴露出去。 放心,这种方法是绝对是可行的,但是太过麻烦,更好的做法是,写一个 前端路由分发控制器,由路由控制器把调用请求分发到各个API服务中去。

我们在框架的demo模块有这个 RouterAction 的实现。这里贴出核心代码

$request->parseURL(); //重新解析,获取原来的url
$service = $request->getAction();
$method = $request->getMethod();

$instance = Beans::get("api.{$service}.service");
if (is_object($instance)) {
    $reflectMethods = new \ReflectionMethod($instance, $method);
    $params = $request->getParameters(); //获取外部POST过来的参数
    $dependParams = array(); //依赖参数
    //build the depend params
    foreach ($reflectMethods->getParameters() as $value) {
        if (isset($params[$value->getName()])) {
            $dependParams[] = $params[$value->getName()];
        } else if ($value->isDefaultValueAvailable()) {
            $dependParams[] = $value->getDefaultValue();
        } else {
            $dependParams[] = null;
        }
    }
    try {
        $result = call_user_func_array(array($instance, $method), $dependParams);
    } catch (\Exception $e) {
        Log::error($e);
    }
    die($result->output());
}
JsonResult::jsonResult(404, "调用服务失败,找不到对应的服务.");

有一点你需要注意的是,你需要为每个服务配置bean configs, 否则你通过 Beans::get() 方法就获取不到对应的服务的。你可以在 configs/beans 中新建一个 beans.api.config.php, 配置类似代码:

$beans = array(
    'api.user.service' => array(
        '@type' => Beans::BEAN_OBJECT,
        '@class' => 'api\service\UserService',
    ),
    'api.shop.service' => array(
        '@type' => Beans::BEAN_OBJECT,
        '@class' => 'api\service\ShopService',
    ),

);
return $beans;

关于bean配置的语法,请参考 Beans配置

除此之外,为了把所有的api请求转发到 RouterAction, 你需要在系统默认生命周期监听器 DefaultWebappListener 中拦截请求,然后转发到 RouterAction 很简单,就重载方法 beforeActionInvoke 就好了:

/**
 * action 方法调用之前
 * @return mixed
 */
public function beforeActionInvoke(HttpRequest $request)
{
    $module = $request->getModule();
    if ( $module == 'api' ) {
        //请求转发到api路由入口
        $request->setAction('router');
        $request->setMethod('index');
    }
}

如此这般之后,API服务就算是搭建完成了,接下来你只需要在 service 文件夹中写服务就好了。

注意:
1.api的路径规则,/api/{service}/{method}, {service}指的是服务的名称, 比如你有个UserService,那此时 {service} 就是 user的复数形式 users,{method} 对应的 UserService中的方法要调用的方法名称。

2.请求传入的参数的名称,需要跟调用的 {method} 的参数名称一致。

举个栗子:

class UserService {

    public function login($username, $password) {
        return new JsonResult(200, "登陆成功.");
    }
}

//客户端代码
$params = array(
    'username' => 'xxxxxxx', //参数名称要跟login()方法保持一致。
    'password' => '1111111'
);
$result = HttpClient::post('/api/users/login', $params);

需要说明的是,参数的顺序可以是随意的,并不要求跟方法的参数的顺序一致,系统在方法调用的时候会自动匹配的。

搭建一个API服务应用

除了建一个单独的模块,通过单独应用的入口来实现API的远程调用. 这样做有2个好处:

1.API 的访问地址会更简洁一些,加入你为api应用绑定的域名是api.herosphp.com, url 格式会变成 http://api.herosphp.com/{service}/{method}, 少了 api 这一级路径。

2.可以很方便搭建RESTFUl API,herosphp 内置实现了一个简单的 restful api 的 框架。包括对资源的 POST, GET, PUT, PATCH, DELETE 操作。

下面简单说下搭建步骤:

1. 新建nginx配置文档

server {
    listen   80;
    server_name  api.herosphp.my;
    root /php/herosphp;
    autoindex on;
    index index.html index.php index.htm;
    #设定本虚拟主机的访问日志
    access_log  /dev/null;
    error_log /dev/null;

    # Make site accessible from http://localhost/

    location ~ .*\.(php|php5)?$
    {
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    #add surpport pathinfo visitd mode    
    if (!-f $request_filename) {
        rewrite ^/.*$ /www/app/api.php last;
        break;
    }

    location ~ /\.ht {
        deny all;
    }
}

这个文件其实你也可以去项目根目录的 /res/design 下去拷贝。

2. 修改api.php

api.php 这个文件中其实只有三行代码

require_once __DIR__."/server.php";
define('RESTFUL_API', false); //是否开启使用 restful api
Herosphp::api(); //启动初始化应用程序

默认是没有开启restful api的,如果你不开启restful api, 那么你的api服务不需要做任何更改,跟上面是完全一样 的,创建服务,创建 Beans 配置文档,不过有一点不一样的是,如果你使用这种方式去搭建 API 服务,那么你就 不用去创建 RouterAction 了,因为系统有专门的 API 网关了(GeneralApi 和 RestfulApi)。

3. 创建服务

如果你使用的普通的API风格,那么你的服务创建格式就跟前面讲的第一种方法一样,没有什么特殊的要求, 但是如果你使用的是 restful API 风格的话,那么你的服务必须实现 IRestfulApiService 接口。一个完整的栗子:

<?php
namespace api\service;
use herosphp\api\interfaces\IRestfulApiService;
use herosphp\utils\JsonResult;

/**
 * restful api test
 * @author yangjian
 * @email yangjian102621@gmail.com
 * @date 2017-03-26
 */
class ShopService implements IRestfulApiService {

    public function add($data)
    {
        return new JsonResult(201, "添加数据成功", $data);
    }

    public function get($ID)
    {
        return new JsonResult(200, "查询成功", ['username' => 'xxxxxx', 'password' => '111111']);
    }

    public function gets($filter, $page, $pagesize, $sort)
    {
        return new JsonResult(200, "查询成功", [
                ['username' => 'xxxxxx', 'password' => '111111'],
                ['username' => 'yyyyyy', 'password' => '222222']
                ]);
    }

    public function update($ID, $data)
    {
        return new JsonResult(200, "更新数据成功", $data);
    }

    public function delete($ID)
    {
        return new JsonResult(200, "删除成功", "ID => {$ID}");
    }
}

4. 配置拦截器

如果你需要在客户端调用你的 API 服务的时候进行一些授权认证之类的话,我们为你提供了请求拦截器的供你使用, 你只需在拦截器的方法中做相应的处理就好了。在调用服务之前会调用拦截器的方法,如果返回的是false,那么这次 服务调用就会被拦截,返回相应的错误信息给客户端。

拦截器在 common\listener\ApiListener

<?php

namespace common\listener;

use herosphp\api\interfaces\IApiListener;

/**
 * api 服务拦截器
 * @package common\listener
 * @author yangjian102621@gmail.com
 */
class ApiListener implements IApiListener {

    /**
     * @param $params
     * @return bool
     */
    public function authorize($params)
    {
        //这里写授权认证的代码
        return true;
    }
}

RESTFUl API 的设计规则:

目前只支持单层资源的访问,如 /zoos, /zoos/ID,暂时不支持多层资源访问,如 /zoos/ID/animals/ID

API 的返回值类型都必须是 JsonResult

如果在 API 服务中抛出异常,则必须是 APIException 类型或者其子类。

附录:JsonResult 中的错误代码类型

200 => 'OK.',
201 => 'Created.',
204 => 'No Contents.',
400 => 'Bad Request.',
401 => 'Not Authorized.',
503 => 'Access Forbidden.',
404 => 'Not Found.',
405 => 'Method Not Allow.',
500 => 'Server internal Error.',

关于JsonResult 的更多用法,请移步 JsonResult

Copyright © HerosPHP 2016 all right reserved,powered by Gitbook最后更新时间: 2017-03-28 22:20:25

results matching ""

    No results matching ""