情況是這樣的,原項目底層使用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的基本安裝,官網上寫的非常詳細,就不在這裏贅述了。
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測試結果如下: