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測試結果如下:


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