Yii2-RESTfulApi实践

46 阅读12分钟

1、了解REST

REST -- Resource REpresentational State Transfer 通俗讲:资源在网络中以某种表现形式进行状态转移。

(1)、Resource:资源,即数据。 (2)、Representational:某种表现形式,比如JSON,XML等。 (3)、State Transfer:状态变化。通过HTTP动词实现。 REST就是选择通过http协议和uri,利用client/server model对资源进行CRUD(Create/Read/Update/Delete)增删改查操作。 REST风格六个限制

(1)、客户-服务器(Client-Server)客户端服务器分离

优点: 提高用户界面的便携性(操作简单) 通过简化服务器提高可伸缩性(高性能,低成本) 允许组件分别优化(可以让服务端和客户端分别进行改进和优化) (2)、无状态(Stateless)

从客户端的每个请求要包含服务器所需要的所有信息 优点: 提高可见性(可以单独考虑每个请求) 提高了可靠性(更容易从局部故障中修复) 提高可扩展性(降低了服务器资源使用) (3)、缓存(Cachable) 服务器返回信息必须被标记是否可以缓存,如果缓存,客户端可能会重用之前的信息发送请求 优点: 减少交互次数 减少交互平均延迟 (4)、分层系统(Layered System) 系统组件不需要知道与他交流组件之外的事情。封装服务,引入中间层。 优点: 限制了系统的复杂性 提高了可扩展性 (5)、统一接口(Uniform Interface) 优点: 提高交互的可见性 鼓励单独改善组件 (6)、支持按需代码(Code-On-Demand可选) 优点: 提高可扩展性 2、了解RESTful

RESTful(采用REST架构规范的)架构风格规定,数据的元操作,即CRUD(create, read, update和delete,即数据的增删查改)操作,分别对应于HTTP方法:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源,这样就统一了数据操作的接口,仅通过HTTP方法,就可以完成对数据的所有增删查改工作。 HTTP动词

GET(SELECT):从服务器取出资源(一项或多项)。 POST(CREATE):在服务器新建一个资源。 PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。 PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。 DELETE(DELETE):从服务器删除资源。 HEAD:获取资源的元数据。 OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。 例如: GET /zoos:列出所有动物园 POST /zoos:新建一个动物园 GET /zoos/ID:获取某个指定动物园的信息 PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息) PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息) DELETE /zoos/ID:删除某个动物园 GET /zoos/ID/animals:列出某个指定动物园的所有动物 DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物 状态码

服务器向用户返回的状态码和提示信息 200: OK。一切正常。 201: 响应 POST 请求时成功创建一个资源。Location header 包含的URL指向新创建的资源。 204: 该请求被成功处理,响应不包含正文内容 (类似 DELETE 请求)。 304: 资源没有被修改。可以使用缓存的版本。 400: 错误的请求。可能通过用户方面的多种原因引起的,例如在请求体内有无效的JSON 数据,无效的操作参数,等等。 401: 验证失败。 403: 已经经过身份验证的用户不允许访问指定的 API 末端。 404: 所请求的资源不存在。 405: 不被允许的方法。 请检查 Allow header 允许的HTTP方法。 415: 不支持的媒体类型。 所请求的内容类型或版本号是无效的。 422: 数据验证失败 (例如,响应一个 POST 请求)。 请检查响应体内详细的错误消息。 429: 请求过多。 由于限速请求被拒绝。 500: 内部服务器错误。 这可能是由于内部程序错误引起的。 3、Yii2-RESTful Api

Yii 提供了一整套用来简化实现 RESTful 风格的 Web Service 服务的 API。 特别是,Yii 支持以下关于 RESTful 风格的 API 支持 Active Record 类的通用API的快速原型 涉及的响应格式(在默认情况下支持 JSON 和 XML) 支持可选输出字段的定制对象序列化 适当的格式的数据采集和验证错误 支持 HATEOAS 有适当HTTP动词检查的高效的路由 内置OPTIONS和HEAD动词的支持 认证和授权 数据缓存和HTTP缓存 速率限制 Yii2 中实践

(1)、框架结构(采用的yii2-app-basic基础版) ├─components │ └─ApiController.php │ ├─models │ ProjectTeam.php │ User.php │ LoginForm.php │ ├─modules │ └─api │ │ Module.php │ │ │ ├─controllers │ ProjectTeamController.php │ UserController.php │


(2)、建立api模块 修改配置文件(config/web.php)

[ 'api' => [ 'class' => 'api\modules\api\Module', ], ], ...... api模块文件(modules/api/Module.php) user->enableSession = false; //关闭登录失败跳转 \Yii::$app->user->loginUrl = null; } } (3)、配置控制器 Api基类控制器 # components/ApiController.php [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'enableStrictParsing' =>true, 'rules' => [ [ 'class' => 'yii\rest\UrlRule', 'controller' => ['api/project-team'], 'pluralize' => false //不在url链接中的project-team后加s 复数 ], ] ] ...... (5)、配置请求跟响应(config/web.php) [ 'request' => [ 'cookieValidationKey' => 'test', 'class' => '\yii\web\Request', 'enableCookieValidation' => false, 'parsers' => [ 'application/json' => 'yii\web\JsonParser', ] ], 'response' => [ 'class' => 'yii\web\Response', 'on beforeSend' => function ($event) { //restful api $response = $event->sender; $code = $response->getStatusCode(); $msg = $response->statusText; if ($code == 404) { !empty($response->data['message']) && $msg = $response->data['message']; } //设置固定返回数据参数 $data = [ 'code' => $code, 'msg' => $msg, 'data' => $response->data ]; $code == 200 && $data['data'] = $response->data; $response->data = $data; $response->format = yii\web\Response::FORMAT_JSON; }, ], ...... ] (6)、模拟请求 GET获取列表 GET /project-team: 逐页列出所有项目组 HEAD /project-team: 显示项目组列表的概要信息 POST /project-team: 创建一个新项目组 GET /project-team/2: 返回项目组 2 的详细信息 PATCH /project-team/2 and PUT /project-team/2: 更新项目组2 DELETE /project-team/2: 删除项目组2 (7)、授权认证 和Web应用不同,RESTful APIs 通常是无状态的, 也就意味着不应使用sessions 或 cookies, 因此每个请求应附带某种授权凭证,因为用户授权状态可能没通过sessions 或 cookies维护, 常用的做法是每个请求都发送一个秘密的access token来认证用户, 由于access token可以唯一识别和认证用户, API 请求应通过HTTPS来防止man-in-the-middle (MitM) 中间人攻击. 以下几种方式来发送access token 1、(HttpBasicAuth)HTTP 基本认证: access token 当作用户名发送,应用在access token可安全存在API使用端的场景, 例如,API使用端是运行在一台服务器上的程序。 2、(QueryParamAuth)请求参数: access token 当作API URL请求参数发送,例如 https://example.com/users?access-token=xxxxxxxx , 由于大多数服务器都会保存请求参数到日志, 这种方式应主要用于JSONP 请求,因为它不能使用HTTP头来发送access token。 3、(HttpBearerAuth)OAuth 2: 使用者从认证服务器上获取基于 OAuth2协议的access token,然后通过 HTTP Bearer Tokens 发送到API 服务器。 (8)、认证类选择【QueryParamAuth| HttpBearerAuth】 二者的区别:前者通过get方式传递token,后者通过header头传递。get传递token的方式有一个风险,以nginx为例,实际请求的地址假设是 /controller/action?token=123,那么nginx的access.log就会把这个访问地址记录下来,对于有访问这个日志文件的管理人员而言,显而易见我们就把用户的token暴露了,这是非常不好的一件事,所以我们推荐使用header头进行传递。即我们选择 yii\filters\auth\HttpBearerAuth 作为接收token并校验。 建议选择HttpBearerAuth。 (9)、改造User模型 # user 表结构: CREATE TABLE `user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `username` varchar(30) NOT NULL DEFAULT '' COMMENT '用户名', `password_hash ` varchar(150) NOT NULL DEFAULT '' COMMENT '密码', `createtime` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', `updatetime` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', `api_token` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; # 用户模型 # models/User.php TimestampBehavior::className(), 'createdAtAttribute' => 'createtime', 'updatedAtAttribute' => 'updatetime', 'attributes' => [ ActiveRecord::EVENT_BEFORE_INSERT => ['createtime', 'updatetime'], ActiveRecord::EVENT_BEFORE_UPDATE => ['updatetime'], ] ] ]; } //表名 public static function tableName() { return "{{%user}}"; } //规则 public function rules() { return [ ['username', 'required', 'message' => '用户名不能为空'], ['api_token', 'required', 'message' => 'api_token不能为空'] ]; } /** * 生成 "remember me" 认证key */ public function generateAuthKey() { $this->auth_key = Yii::$app->security->generateRandomString(); } /** * 生成 api_token */ public function generateApiToken() { $this->api_token = Yii::$app->security->generateRandomString() . '_' . time(); } /** * 校验api_token是否有效 */ public static function apiTokenIsValid($token) { if (empty($token)) { return false; } $timestamp = (int) substr($token, strrpos($token, '_') + 1); $expire = Yii::$app->params['user.apiTokenExpire']; return $timestamp + $expire >= time(); } /** * 根据api token 获取用户 * @param $token * @return array|null|ActiveRecord */ public static function findByApiToken($token) { return static::find()->where('api_token = :api_token', [':api_token' => $token])->one(); } /** * 根据用户名查询用户 * @param $username * @return array|null|ActiveRecord */ public static function findByUsername($username) { return static::find()->where('username = :username', [':username' => $username])->one(); } /** * @inheritdoc */ public static function findIdentity($id) { return static::findOne(['id' => $id]); } /** * @inheritdoc */ public static function findIdentityByAccessToken($token, $type = null) { // 如果token无效的话 if(!static::apiTokenIsValid($token)) { throw new \yii\web\UnauthorizedHttpException("token is invalid."); } return static::findOne(['api_token' => $token]); } /** * @inheritdoc */ public function getId() { return $this->id; } /** * @inheritdoc */ public function getAuthKey() { return $this->authKey; } /** * @inheritdoc */ public function validateAuthKey($authKey) { return $this->authKey === $authKey; } /** * 为model的password_hash字段生成密码的hash值 * * @param string $password */ public function setPassword($password) { $this->password_hash = Yii::$app->security->generatePasswordHash($password); } /** * Validates password * * @param string $password password to validate * @return bool if password provided is valid for current user */ public function validatePassword($password, $password_hash) { return Yii::$app->security->validatePassword($password, $password_hash); } } (10)、配置登录认证 # 配置基类控制器 # components/ApiController.php HttpBearerAuth::className(), 'optional' => [ 'login', //认证排除登录接口 'reg' //认证排除测试注册用户 ] ]; return $behaviors; } public function actions() { $actions = parent::actions(); return $actions; } } # 配置登录表单 # models/LoginForm.php on(self::GET_API_TOKEN, [$this, 'onGenerateApiToken']); } /** * @inheritdoc * 对客户端表单数据进行验证的rule */ public function rules() { return [ [['username', 'password'], 'required'], ['password', 'validatePassword'], ]; } /** * 自定义的密码认证方法 */ public function validatePassword($attribute, $params) { if (!$this->hasErrors()) { $this->_user = $this->getUser(); if (!$this->_user || !$this->_user->validatePassword($this->password)) { $this->addError($attribute, '用户名或密码错误.'); } } } /** * @inheritdoc */ public function attributeLabels() { return [ 'username' => '用户名', 'password' => '密码', ]; } /** * Logs in a user using the provided username and password. * * @return boolean whether the user is logged in successfully */ public function login() { if ($this->validate()) { $this->trigger(self::GET_API_TOKEN); return $this->_user; } else { return null; } } /** * 根据用户名获取用户的认证信息 * * @return User|null */ protected function getUser() { if ($this->_user === null) { $this->_user = User::findByUsername($this->username); } return $this->_user; } /** * 登录校验成功后,为用户生成新的token * 如果token失效,则重新生成token */ public function onGenerateApiToken () { if (!User::apiTokenIsValid($this->_user->api_token)) { $this->_user->generateApiToken(); $this->_user->save(false); } } } # 配置用户控制器 # modules/api/controllers/UserController.php generateAuthKey(); $user->setPassword('123456'); $user->username = 'test'; $user->save(false); return [ 'code' => 0 ]; } //登录 public function actionLogin () { $model = new LoginForm; $model->setAttributes(Yii::$app->request->post()); if ($user = $model->login()) { return $user->api_token; } else { return $model->errors; } } } # 配置url路由 # config/web.php [ ...... 'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'enableStrictParsing' =>true, 'rules' => [ [ 'class' => 'yii\rest\UrlRule', 'controller' => ['api/project-team'], 'pluralize' => false //不在url链接中的project-team后加s 复数 ], [ 'controller' => ['api/user'], 'class' => 'yii\rest\UrlRule', 'pluralize' => false, 'yii\rest\UrlRule' => [ 'POST login' => 'login', 'GET reg' => 'reg' ], ], ], ] ...... ], 'params' => [ // token 有效期默认1天,可以按照自己的项目需求配置 'user.apiTokenExpire' => 1*24*3600, ] ...... (11)、登录获取token POST /api/user/login { "code": 200, "msg": "OK", "data": "6GA0kFMIJt7Wm4zae-6BRo5bDcCgsRhl_1503122875" } 将获取到的token保存起来,用于请求其他接口的认证 yii\filters\auth\HttpBearerAuth 类是从header头的Authorization这个key中进行获取的,其格式如下 Authorization: Bearer your-token (12)、获取用户信息 # modules/api/controllers/UserController.php //获取用户信息 public function actionInfo() { $user = $this->authenticate(Yii::$app->user, Yii::$app->request, Yii::$app->response); return $user; } # 配置用户信息路由 # config/web.php ...... [ 'controller' => ['api/user'], 'extraPatterns' => [ 'POST login' => 'login', 'GET reg' => 'reg', //获取用户信息 'GET info' => 'info', ], ] ...... 请求 获取用户信息 (13)、接口速率限制 # 用户表添加字段 allowance:剩余的允许的请求数量 allowance_updated_at:相应的UNIX时间戳数 alter table `user` add `allowance` int(11) DEFAULT '0'; alter table `user` add `allowance_updated_at` int(11) DEFAULT '0'; # 修改User模型 TimestampBehavior::className(), 'createdAtAttribute' => 'createtime', 'updatedAtAttribute' => 'updatetime', 'attributes' => [ ActiveRecord::EVENT_BEFORE_INSERT => ['createtime', 'updatetime'], ActiveRecord::EVENT_BEFORE_UPDATE => ['updatetime'], ] ] ]; } # 速度控制 2秒内访问3次,注意,数组的第一个不要设置1,设置1会出问题,一定要 #大于2,譬如下面 2秒内只能访问三次 # 文档标注:返回允许的请求的最大数目及时间,例如,[100, 600] 表示在600秒内最多100次的API调用。 public function getRateLimit($request, $action){ return [3, 2]; } # 文档标注: 返回剩余的允许的请求和相应的UNIX时间戳数 当最后一次速率限制检查时。 public function loadAllowance($request, $action){ //return [1,strtotime(date("Y-m-d H:i:s"))]; //echo $this->allowance;exit; return [$this->allowance, $this->allowance_updated_at]; } # allowance 对应user 表的allowance字段 int类型 # allowance_updated_at 对应user allowance_updated_at int类型 # 文档标注:保存允许剩余的请求数和当前的UNIX时间戳。 public function saveAllowance($request, $action, $allowance, $timestamp){ $this->allowance = $allowance; $this->allowance_updated_at = $timestamp; $this->save(); } //表名 public static function tableName() { return "{{%user}}"; } //规则 public function rules() { return [ ['username', 'required', 'message' => '用户名不能为空'], ['display_name', 'required', 'message' => '显示名不能为空'], ['dn', 'required', 'message' => '显示名不能为空'], ['api_token', 'required', 'message' => 'api_token不能为空'] ]; } /** * 生成 "remember me" 认证key */ public function generateAuthKey() { $this->auth_key = Yii::$app->security->generateRandomString(); } /** * 生成 api_token */ public function generateApiToken() { $this->api_token = Yii::$app->security->generateRandomString() . '_' . time(); } /** * 校验api_token是否有效 */ public static function apiTokenIsValid($token) { if (empty($token)) { return false; } $timestamp = (int) substr($token, strrpos($token, '_') + 1); $expire = Yii::$app->params['user.apiTokenExpire']; return $timestamp + $expire >= time(); } /** * 根据api token 获取用户 * @param $token * @return array|null|ActiveRecord */ public static function findByApiToken($token) { return static::find()->where('api_token = :api_token', [':api_token' => $token])->one(); } /** * 根据用户名查询用户 * @param $username * @return array|null|ActiveRecord */ public static function findByUsername($username) { return static::find()->where('username = :username', [':username' => $username])->one(); } /** * @inheritdoc */ public static function findIdentity($id) { return static::findOne(['id' => $id]); } /** * @inheritdoc */ public static function findIdentityByAccessToken($token, $type = null) { // 如果token无效的话 if(!static::apiTokenIsValid($token)) { throw new \yii\web\UnauthorizedHttpException("token is invalid."); } return static::findOne(['api_token' => $token]); } /** * @inheritdoc */ public function getId() { return $this->id; } /** * @inheritdoc */ public function getAuthKey() { return $this->authKey; } /** * @inheritdoc */ public function validateAuthKey($authKey) { return $this->authKey === $authKey; } /** * 为model的password_hash字段生成密码的hash值 * * @param string $password */ public function setPassword($password) { $this->password_hash = Yii::$app->security->generatePasswordHash($password); } /** * Validates password * * @param string $password password to validate * @return bool if password provided is valid for current user */ public function validatePassword($password, $password_hash) { return Yii::$app->security->validatePassword($password, $password_hash); } } # 修改Api基类 # components/ApiController.php use yii\filters\RateLimiter; use yii\filters\auth\HttpBearerAuth; public function behaviors() { $behaviors = parent::behaviors(); $behaviors['authenticator'] = [ 'class' => HttpBearerAuth::className(), 'optional' => [ 'login', 'reg' ], ]; # rate limit部分,速度的设置是在 # app\models\User::getRateLimit($request, $action) /* 官方文档: 当速率限制被激活,默认情况下每个响应将包含以下HTTP头发送 目前的速率限制信息: X-Rate-Limit-Limit: 同一个时间段所允许的请求的最大数目; X-Rate-Limit-Remaining: 在当前时间段内剩余的请求的数量; X-Rate-Limit-Reset: 为了得到最大请求数所等待的秒数。 你可以禁用这些头信息通过配置 yii\filters\RateLimiter::enableRateLimitHeaders 为false, 就像在上面的代码示例所示。 */ $behaviors['rateLimiter'] = [ 'class' => RateLimiter::className(), 'enableRateLimitHeaders' => true, ]; return $behaviors; } # 请求过多响应如下: { "name": "Too Many Requests", "message": "Rate limit exceeded.", "code": 0, "status": 429, "type": "yii\\web\\TooManyRequestsHttpException" } QA: (1)、跨域请求 # 修改Api基类控制器 use yii\filters\RateLimiter; use yii\filters\auth\HttpBearerAuth; use yii\filters\Cors; public function behaviors() { $behaviors = parent::behaviors(); unset($behaviors['authenticator']); /* 取消默认authenticator认证,以确保 cors 被首先处理。然后,我们在实施自己的认证程序之前,强制 cors 允许凭据。 */ //设置跨域 $behaviors['corsFilter'] = [ 'class' => Cors::className(), 'cors' => [ 'Origin' => ['*'], 'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'], 'Access-Control-Request-Headers' => ['*'], 'Access-Control-Allow-Credentials' => true, ], ]; $behaviors['authenticator'] = [ 'class' => HttpBearerAuth::className(), 'optional' => [ 'login', 'reg' ], ]; $behaviors['rateLimiter'] = [ 'class' => RateLimiter::className(), 'enableRateLimitHeaders' => true, ]; return $behaviors; } (2)、ajax跨域请求提示404 浏览器自动在跨域的 GET 请求发送之前发送一个 OPTIONS 请求,以判断服务端是否允许这一域访问。 # 一般规则是当您要求您的浏览器执行一个HTTP的动词,例如PUT,DELETE或POST时,您可以在浏览器的网络标签中检查该列表。到任何网址,它可能首先向该相同的网址发送OPTIONS请求。 # 例如获取用户信息接口 修改为: [ 'controller' => ['api/user'], 'class' => 'yii\rest\UrlRule', 'pluralize' => false, 'extraPatterns' => [ 'POST login' => 'login', 'GET reg' => 'reg', //支持OPTIONS请求 'GET,OPTIONS info' => 'info', ], ], (3)、ajax请求接口时出现 401授权验证问题 # 修改Api基类控制器 use yii\filters\RateLimiter; use yii\filters\auth\HttpBearerAuth; use yii\filters\Cors; public function behaviors() { $behaviors = parent::behaviors(); unset($behaviors['authenticator']); $behaviors['corsFilter'] = [ 'class' => Cors::className(), 'cors' => [ 'Origin' => ['*'], 'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'], 'Access-Control-Request-Headers' => ['*'], 'Access-Control-Allow-Credentials' => true, ], ]; $behaviors['authenticator'] = [ 'class' => HttpBearerAuth::className(), 'optional' => [ 'login', 'reg' ], 'except'=> ['options'] //认证排除OPTIONS请求 ]; $behaviors['rateLimiter'] = [ 'class' => RateLimiter::className(), 'enableRateLimitHeaders' => true, ]; return $behaviors; } public function actions() { $actions = parent::actions(); //设置固定options控制器 $actions['options'] = [ 'class' => 'yii\rest\OptionsAction', // optional: 'collectionOptions' => ['GET', 'POST', 'HEAD', 'OPTIONS'], 'resourceOptions' => ['GET', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'], ]; return $actions; } 优化的路由配置文件、Api基类 # config/url-rules.php ['api/user'], 'extraPatterns' => [ 'POST login' => 'login', 'GET reg' => 'reg', //获取 'GET info' => 'info', ], ], //例如项目组 [ 'controller' => ['api/project-team'] ], ]; /** * 基础的api url规则配置 */ $apiUrls = array_map(function($unit) { $urlRule = $unit; //防止默认options控制器被屏蔽 if(isset($unit['only'])&&!empty($unit['only'])&&!in_array('options', $unit['only'])){ $urlRule['only'][] = 'options'; } if(isset($unit['except'])&&!empty($unit['except'])&&in_array('options', $unit['except'])){ $urlRule['except'] = array_merge(array_diff($unit['except'], ['options'])); } //由于ajax设置请求头后,会有一次options请求,默认为所有路由添加支持options请求 if(isset($unit['extraPatterns'])&&!empty($unit['extraPatterns'])){ foreach ($unit['extraPatterns'] as $key => $val) { if(!is_numeric(strpos($key, 'OPTIONS'))){ //判断是否有空格符 if(is_numeric(strpos($key, ' '))){ //存在 $tmp = explode(' ', $key); $k = str_replace($tmp[0], 'OPTIONS', $key); $urlRule['extraPatterns'][$k] = 'options'; } else { //不存在 $urlRule['extraPatterns']['OPTIONS'] = 'options'; } } } } if(isset($unit['patterns'])&&!empty($unit['patterns'])) { foreach ($unit['patterns'] as $key => $val) { if (!is_numeric(strpos($key, 'OPTIONS'))) { //判断是否有空格符 if (is_numeric(strpos($key, ' '))) { //存在 $tmp = explode(' ', $key); $k = str_replace($tmp[0], 'OPTIONS', $key); $urlRule['patterns'][$k] = 'options'; } else { //不存在 $urlRule['patterns']['OPTIONS'] = 'options'; } } } } $config = [ 'class' => 'yii\rest\UrlRule', 'pluralize' => false ]; return array_merge($config, $urlRule); }, $apiRuleConfigs); //合并整个项目路由 return array_merge($baseRuleConfigs, $apiUrls); //路由规则配置 'rules' => require(__DIR__ . '/url-rules.php') Cors::className(), 'cors' => [ 'Origin' => ['*'], 'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'], 'Access-Control-Request-Headers' => ['*'], 'Access-Control-Allow-Credentials' => true, ], ]; $behaviors['authenticator'] = [ 'class' => HttpBearerAuth::className(), 'optional' => $this->optional, 'except'=> ['options'] //认证排除OPTIONS请求 ]; # rate limit部分,速度的设置是在 # app\models\User::getRateLimit($request, $action) /* 官方文档: 当速率限制被激活,默认情况下每个响应将包含以下HTTP头发送 目前的速率限制信息: X-Rate-Limit-Limit: 同一个时间段所允许的请求的最大数目; X-Rate-Limit-Remaining: 在当前时间段内剩余的请求的数量; X-Rate-Limit-Reset: 为了得到最大请求数所等待的秒数。 你可以禁用这些头信息通过配置 yii\filters\RateLimiter::enableRateLimitHeaders 为false, 就像在上面的代码示例所示。 */ $behaviors['rateLimiter'] = [ 'class' => RateLimiter::className(), 'enableRateLimitHeaders' => true, ]; return $behaviors; } public function actions() { $actions = parent::actions(); //判断是否需要重写的控制器 if(!empty($this->rewriteActions)){ foreach ($this->rewriteActions as $actionKey) { if(isset($actions[$actionKey])&&$actionKey!='options') unset($actions[$actionKey]); } } //设置固定options控制器 $actions['options'] = [ 'class' => 'yii\rest\OptionsAction', // optional: 'collectionOptions' => ['GET', 'POST', 'HEAD', 'OPTIONS'], 'resourceOptions' => ['GET', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'], ]; return $actions; } } 参考: 1、Yii2文档 2、restful_api 3、CORS跨域 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/qq_24700495/article/details/144395619