情况是这样的,原项目底层使用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测试结果如下: