Yii2 实现伪Rest风格WebAPI

情况是这样的,原项目底层使用C#做的WebService,本意是使用Restful API的整套规范,但是开发的过程中走了样,变成了一个Restful+Json的混合体,大致风格如下:

某接口访问地址:api/LinkManage 

请求方式 参数形式
get Url Query String
post content-type:application/json 的json文本
put content-type:application/json 的json文本
delete Url Query String
现在的需基于Yii2进行底层的重写,研究了一天,初步实现了此风格的Web API,怕自己以后忘记,写下来做个记录,也为有此需求的朋友提供一个参考。来看看具体施工步骤:

Yii2的基本安装,官网上写的非常详细,就不在这里赘述了。

1 首先改造Components中的request组件,让请求组件可以将Json直接反序列化为我们想要的PHP数组。

'request' => [
            'class' => 'yii\web\Request',
            'csrfParam' => '_csrf-api',
//            'enableCookieValidation' => false,//关闭cookie验证
            'enableCsrfValidation' => false,//关闭表单验证
            'parsers' => [
                'application/json' => 'yii\web\JsonParser',
            ],
        ],
2 对Response组件进行配置,接输出美化过后的Json。

'response' => [
            // ...
            'formatters' => [
                \yii\web\Response::FORMAT_JSON => [
                    'class' => 'yii\web\JsonResponseFormatter',
                    'prettyPrint' => YII_DEBUG, // use "pretty" output in debug mode
                    'encodeOptions' => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
                    // ...
                ],
            ],
        ],
3 增加CommonController,这是所有控制器需要继承的自定义的基控制器。代码如下:

<?php
/**
 * Created by PhpStorm.
 * User: DH
 * Date: 2017/12/30
 * Time: 11:34
 */

namespace api\controllers;

use yii\base\Exception;

use yii\filters\auth\HttpBearerAuth;
use yii\helpers\ArrayHelper;
use yii\helpers\Json;
use yii\rest\Controller;
use yii\web\HttpException;
use yii\web\Response;

/**
 * Class CommonController
 * @package api\controllers
 */
class CommonController extends Controller
{
    /**
     * RunAction要调用的方法
     * @var string
     */
    protected $callbackFunc;
    /**
     * 允许的请求方式
     * @var array
     */
    protected static $permissionMethods = ['get', 'post', 'put', 'delete', 'options'];
    /**
     * 需要验签和验参的请求方式
     * @var array
     */
    protected static $needCheckMValueMethods = ['post', 'put', 'delete'];

    /**
     * M     * @var string
     */
    public $m;
    /**
     * 请求的参数列表
     * @var array
     */
    public $requestValues;

    public $stage;

    /**
     * 1 更改默认的AccessToken -> token
     * 2 修改响应文本格式为JSON
     * @return array
     */
    public function behaviors()
    {
        return ArrayHelper::merge(parent::behaviors(), [
            'authenticator' => [
                'class' => HttpBearerAuth::className(),
                'optional' => ArrayHelper::merge(['index','options'], $this->module->params['noAuth'])
            ],
            [
                'class' => 'yii\filters\ContentNegotiator',
                'formats' => [
                    'application/json' => Response::FORMAT_JSON
                ]
            ]
        ]);
    }

    /**
     * 检查请求方式是否再允许列表中
     * @param \yii\base\Action $action
     * @return bool
     * @throws HttpException
     */
    public function beforeAction($action)
    {
        $this->callbackFunc = strtolower(\Yii::$app->request->method);

        switch ($this->callbackFunc) {
            case 'post':
            case 'put':
                $this->m = \Yii::$app->request->post('m') ? \Yii::$app->request->post('m') : '';
                $this->requestValues = \Yii::$app->request->post('value') ? \Yii::$app->request->post('value') : [];
                break;
            case 'delete':

                $this->m = \Yii::$app->request->get('m') ? \Yii::$app->request->get('m') : '';
                $this->requestValues = \Yii::$app->request->queryParams;
                unset($this->requestValues['m']);
                break;
            default:
                $this->m = '';
                $this->requestValues = [];
                break;
        }

        if (!in_array($this->callbackFunc, self::$permissionMethods)) {
            throw new HttpException('不允许的请求方式');
        }
        $this->stageGet();
        in_array($this->callbackFunc, self::$needCheckMValueMethods) && $this->validateRequestParams();
        return parent::beforeAction($action);
    }

    /**
     * 请求总是先进入默认控制器,由默认控制器进行Run的分发
     * @return bool|mixed
     */
    public function actionIndex()
    {
        return in_array($this->callbackFunc, self::$needCheckMValueMethods) ? $this->validateKey() : $this->runAction($this->callbackFunc);
    }

    /**
     * 验证POST密文是否正确
     * @return bool|mixed
     */
    public function validateKey()
    {

        if ($this->requestValues && $this->m) {
            $local_m = strtoupper(md5(Json::encode($this->requestValues) . \Yii::$app->params['appKey']));
            return $this->m === $local_m ? $this->runAction($this->callbackFunc) : $this->error('验签失败');
        } else {
            return $this->error('请求参数缺失');
        }
    }

    /**
     * 验证请求的参数是否在允许的参数列表中
     * @throws Exception
     * @throws HttpException
     */
    protected function validateRequestParams()
    {
        $params = $this->module->params['accessRequestParams'];
        if (is_array($params)) {
            $requestParams = array_keys($this->requestValues);
            if ($requestParams == $params[strtolower(\Yii::$app->request->method)]) {
                return true;
            } else {
                throw new HttpException('您请求的参数数量不对');
            }
        } else {
            throw new Exception('参数验证内部错误,参数列表不是数组');
        }

    }

    public function actionOptions()
    {
        return self::$permissionMethods;
    }

    /**
     * 不中断程序返回错误信息
     * ```
     *  请与throw区别开,如不需要异常中断,则使用此方法返回错误提示,
     *  如需中断程序,则根据需要,选择合适的Exception类抛出异常
     * ```
     * @param $message
     * @param $code
     * @return array
     */
    public function error($message, $code = 400)
    {
        return [
            'StatusCode' => intval($code),
            'Message' => trim($message)
        ];
    }

    /**
     * 区分Get场景,参数顺序必须一致
     * @return int|string
     */
    public function stageGet()
    {
        $search = $this->module->params['getStages'];//获取当前模块的get场景
        $getParams = array_keys(\Yii::$app->request->queryParams);
        foreach ($search as $stage => $way) {
            if ($getParams == $way) {
                $this->stage = $stage;
                return $this->stage;
            }
        }
        return '';
    }
}


此基控制器实现了请求方式->方法的自动加载,对应顺序为:

post -> actionPost、get -> actionGet、put -> actionPut、 delete -> actionDelete

这样,在每个模块的DefaultController中,就只需要四种固定的写法即可。如下图:

4 一个Get处理多种情况,在C#中,一个方法名是可以重复的,参数可变。但是在PHP中是不被允许的。所以要根据请求的参数进行舞台区分,于是乎就得道在Module中的配置是这样的。


我们看getStages参数,模块初始化时将此参数放入配置中,在控制器里就可以根据设置好的舞台(参数列表)进行分配。如:当使用Get方式请求,且参数为 id、type时,对应的舞台是queryAd。在CommonController中已经做了自动处理。这样一来,我们只需要在模块控制器中根据舞台,来处理业务逻辑。实现一个Get处理多种逻辑。如下图所示:


5 当然,最后要配置好路由,才能如愿以偿:

到此,基本已经实现了上面所说的风格。用PostMan测试结果如下:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章