在 Windwos 10、PHP 7.1.12 下 基于 Yii 2 Starter Kit,复制后台应用为接口应用,实现 RESTful Web 服务的流程

1、在 Windows PowerShll 中,进入 E:/wwwroot/cmcp-api 目录,如图1

在 Windows PowerShll 中,进入 E:/wwwroot/cmcp-api 目录

图1

2、新建应用api,复制backend目录为api,如图2

新建应用api,复制backend目录为api

图2

3、编辑 /.env.dist,新增 api 相关的配置,此为提交至Git版本控制的配置文件,如图3

API_HOST_INFO         = http://api.yii2-starter-kit.dev
#API_HOST_INFO         = http://yii2-starter-kit.dev
#API_BASE_URL          = /api/web
API_COOKIE_VALIDATION_KEY = <generated_key>
编辑 /.env.dist,新增 api 相关的配置,此为提交至Git版本控制的配置文件

图3

4、编辑 /.env,新增 api 相关的配置,此为实际生效的配置文件,如图4

API_HOST_INFO         = http://www.cmcp-api.localhost
#API_HOST_INFO         = http://yii2-starter-kit.dev
#API_BASE_URL          = /api/web
API_COOKIE_VALIDATION_KEY = <generated_key>
编辑 /.env,新增 api 相关的配置,此为实际生效的配置文件

图4

5、编辑 /common/config/bootstrap.php,配置接口应用的别名,如图5

Yii::setAlias('@api', realpath(__DIR__.'/../../api'));
Yii::setAlias('@apiUrl', env('API_HOST_INFO') . env('API_BASE_URL') );
编辑 /common/config/bootstrap.php,配置接口应用的别名

图5

6、编辑 /common/config/base.php,配置接口应用的语言包文件,如图6

'api'=>'api.php',
编辑 /common/config/base.php,配置接口应用的语言包文件

图6

7、新建接口应用的中文语言包文件,复制 /common/messages/zh/backend.php 为 /common/messages/zh/api.php(如果需要支持其他的语言,可以在其他的语言目录下新建),如图7

新建接口应用的中文语言包文件,复制 /common/messages/zh/backend.php 为 /common/messages/zh/api.php(如果需要支持其他的语言,可以在其他的语言目录下新建)

图7

8、编辑 /common/config/base.php,配置接口应用的URL的解析与生成,如图8

        'urlManagerApi' => /yii/helpers/ArrayHelper::merge(
            [
                'hostInfo' => env('API_HOST_INFO'),
                'baseUrl' => env('API_BASE_URL'),
            ],
            require(Yii::getAlias('@api/config/_urlManager.php'))
        ),
编辑 /common/config/base.php,配置接口应用的URL的解析与生成

图8

9、编辑 /console/controllers/AppController.php,搜索此文件有6处backend,则相应复制3份为api,如图9

    public $writablePaths = [
        '@common/runtime',
        '@frontend/runtime',
        '@frontend/web/assets',
        '@backend/runtime',
        '@backend/web/assets',
        '@api/runtime',
        '@api/web/assets',
        '@storage/cache',
        '@storage/web/source'
    ];

    public $executablePaths = [
        '@backend/yii',
        '@api/yii',
        '@frontend/yii',
        '@console/yii',
    ];
编辑 /console/controllers/AppController.php,搜索此文件有6处backend,则相应复制3份为api

图9

10、编辑 /autocompletion.php,新增接口应用相关的配置,如图10

 * @property yii/web/UrlManager $urlManagerApi UrlManager for api application.
编辑 /autocompletion.php,新增接口应用相关的配置

图10

11、在目录api中搜索backend/、backend/、=> ‘backend’、BACKEND,严格匹配大小写,将其分别替换为api/、api/、=> ‘api’、API,如图11

backend/ 替换为: api/
backend/ 替换为: api/
=> 'backend' 替换为: => 'api'
BACKEND 替换为: => API
在目录api中搜索backend/、backend/、=> 'backend'、BACKEND,严格匹配大小写,将其分别替换为api/、api/、=> 'api'、API

图11

12、在 /tests 目录中,还有相应的与新增接口应用相关的配置,此部分暂缓一下,到需要进行自动化测试的时候,再来进行配置了。查看 /api/web/index-test.php,如图12

在 /tests 目录中,还有相应的与新增接口应用相关的配置,此部分暂缓一下,到需要进行自动化测试的时候,再来进行配置了。查看 /api/web/index-test.php

图12

13、在 Windows PowerShll 中,进入 E:/wwwroot/cmcp-api 目录,再次运行命令:php console/yii app/setup,设置可写、可执行权限,如图13

在 Windows PowerShll 中,进入 E:/wwwroot/cmcp-api 目录,再次运行命令:php console/yii app/setup,设置可写、可执行权限

图13

14、在 Nginx 配置文件中新增虚拟主机,重启Nginx,如图14

## API ##
server {
    listen 80; ## 监听 ipv4 上的 80 端口
    # listen [::]:80 default_server ipv6only=on; ## 监听 ipv6 上的 80 端口

    root E:/wwwroot/cmcp-api/api/web;
    index index.php index.html;

    server_name www.cmcp-api.localhost;

    charset utf-8;

	access_log logs/www.cmcp-api.localhost.access.log;
	error_log logs/www.cmcp-api.localhost.error.log;

    client_max_body_size 128M;

    # There is a VirtualBox bug related to sendfile that can lead to
    # corrupted files, if not turned-off on Vagrant based setup
    # sendfile off;

    location / {
		# 如果找不到真实存在的文件,把请求分发至 index.php
        try_files $uri $uri/ /index.php?$args;
    }

    # location ~* ^.+/.(jpg|jpeg|gif|png|ico|css|pdf|ppt|txt|bmp|rtf|js)$ {
    #     access_log off;
    #     expires max;
    # }

    location ~ /.php$ {
        fastcgi_split_path_info ^(.+/.php)(/.+)$;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass php-fpm;
        fastcgi_index index.php;
        include fastcgi_params;
    }

	location ~ //.(ht|svn|git) {
        deny all;
    }

}
在 Nginx 配置文件中新增虚拟主机,重启Nginx

图14

15、在 hosts 文件中新增配置,如图15

# 内容管控平台接口
127.0.0.1 cmcp-api.localhost www.cmcp-api.localhost frontend.cmcp-api.localhost backend.cmcp-api.localhost storage.cmcp-api.localhost
在 hosts 文件中新增配置

图15

16、打开 http://www.cmcp-api.localhost, 如果出现样式文件加载响应404的情况,可以删除 /api/web/assets 下的所有子目录,如图16

打开 http://www.cmcp-api.localhost, 如果出现样式文件加载响应404的情况,可以删除 /api/web/assets 下的所有子目录

图16

17、准备将创建页面的功能基于接口实现,如图17

准备将创建页面的功能基于接口实现

图17

18、调整 Page 模型的相关结构,实现模型分层,参考网址:http://www.shuijingwanwq.com/2017/08/15/1713/ ,如图18

调整 Page 模型的相关结构,实现模型分层,参考网址:http://www.shuijingwanwq.com/2017/08/15/1713/

图18

19、打开网址:http://frontend.cmcp-api.localhost/gii ,如图19

打开网址:http://frontend.cmcp-api.localhost/gii

图19

20、基于数据库表 ca_page 建立相应模型,如图20

基于数据库表 ca_page 建立相应模型

图20

21、在common目录中新建logics目录,用于MySQL模型的逻辑层所在目录,如图21

在common目录中新建logics目录,用于MySQL模型的逻辑层所在目录

图21

22、复制 /common/models/Page.php 至 /common/logics/Page.php,如图22

<?php namespace common/models; use Yii; use yii/behaviors/SluggableBehavior; use yii/behaviors/TimestampBehavior; use yii/db/ActiveRecord; /** * This is the model class for table "page". * * @property integer $id * @property string $slug * @property string $title * @property string $body * @property string $view * @property integer $status * @property integer $created_at * @property integer $updated_at */ class Page extends ActiveRecord { const STATUS_DRAFT = 0; const STATUS_PUBLISHED = 1; /** * @inheritdoc */ public static function tableName() { return '{{%page}}'; } /** * @inheritdoc */ public function behaviors() { return [ TimestampBehavior::className(), 'slug' => [
                'class' => SluggableBehavior::className(),
                'attribute' => 'title',
                'ensureUnique' => true,
                'immutable' => true
            ]
        ];
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['title', 'body'], 'required'],
            [['body'], 'string'],
            [['status'], 'integer'],
            [['slug'], 'unique'],
            [['slug'], 'string', 'max' => 2048],
            [['title'], 'string', 'max' => 512],
            [['view'], 'string', 'max' => 255]
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => Yii::t('common', 'ID'),
            'slug' => Yii::t('common', 'Slug'),
            'title' => Yii::t('common', 'Title'),
            'body' => Yii::t('common', 'Body'),
            'view' => Yii::t('common', 'Page View'),
            'status' => Yii::t('common', 'Active'),
            'created_at' => Yii::t('common', 'Created At'),
            'updated_at' => Yii::t('common', 'Updated At'),
        ];
    }
}

复制 /common/models/Page.php 至 /common/logics/Page.php

图22

23、基于 diff ,编辑 /common/logics/Page.php,如图23

基于 diff ,编辑 /common/logics/Page.php

图23

24、在common/logics目录中的MySQL模型文件为业务逻辑相关,继承至 /common/models/Page 数据层,如图24

<?php namespace common/logics; use Yii; use yii/behaviors/SluggableBehavior; use yii/behaviors/TimestampBehavior; class Page extends /common/models/Page { const STATUS_DRAFT = 0; const STATUS_PUBLISHED = 1; /** * @inheritdoc */ public function behaviors() { return [ TimestampBehavior::className(), 'slug' => [
                'class' => SluggableBehavior::className(),
                'attribute' => 'title',
                'ensureUnique' => true,
                'immutable' => true
            ]
        ];
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['title', 'body'], 'required'],
            [['body'], 'string'],
            [['status'], 'integer'],
            [['slug'], 'unique'],
            [['slug'], 'string', 'max' => 2048],
            [['title'], 'string', 'max' => 512],
            [['view'], 'string', 'max' => 255],
        ];
    }

}

在common/logics目录中的MySQL模型文件为业务逻辑相关,继承至 /common/models/Page 数据层

图24

25、点击Model Generator下的Start按钮,生成模型Page,命名空间为common/models,此时需支持国际化,覆盖/common/models/Page.php,如图25

点击Model Generator下的Start按钮,生成模型Page,命名空间为common/models,此时需支持国际化,覆盖/common/models/Page.php

图25

26、编辑 /common/config/base.php,当源语言和目标语言相同时,是否强制进行消息翻译,默认为假,设置为真,如图26

                '*'=> [
                    'class' => 'yii/i18n/PhpMessageSource',
                    'forceTranslation' => true,
                    'basePath'=>'@common/messages',
                    'fileMap'=>[
                        'common'=>'common.php',
                        'backend'=>'backend.php',
                        'api'=>'api.php',
                        'frontend'=>'frontend.php',
                    ],
                    'on missingTranslation' => ['/backend/modules/i18n/Module', 'missingTranslation']
                ],
编辑 /common/config/base.php,当源语言和目标语言相同时,是否强制进行消息翻译,默认为假,设置为真

图26

27、新建 /common/messages/en/model/page.php,支持目标语言为英语美国时的消息翻译,如图27

return [
    'ID' => 'ID',
    'Slug' => 'Slug',
    'Title' => 'Title',
    'Body' => 'Body',
    'View' => 'Page View',
    'Status' => 'Active',
    'Created At' => 'Created At',
    'Updated At' => 'Updated At',
];
新建 /common/messages/en/model/page.php,支持目标语言为英语美国时的消息翻译

图27

28、新建 /common/messages/zh/model/page.php,支持目标语言为简体中文时的消息翻译,如图28

return [
    'ID' => 'ID',
    'Slug' => '别名',
    'Title' => '标题',
    'Body' => '内容',
    'View' => '页面浏览',
    'Status' => '活动',
    'Created At' => '创建时间',
    'Updated At' => '更新时间',
];
新建 /common/messages/zh/model/page.php,支持目标语言为简体中文时的消息翻译

图28

29、新建 /api/models/Page.php,在api/models目录中的MySQL模型文件为业务逻辑相关(仅与api相关),继承至 /common/logics/Page 逻辑层,如图29

新建 /api/models/Page.php,在api/models目录中的MySQL模型文件为业务逻辑相关(仅与api相关),继承至 /common/logics/Page 逻辑层

图29

30、复制/api/models/Page.php 至 /frontend/models/Page.php、/backend/models/Page.php,调整为各自的命名空间,如图30

复制/api/models/Page.php 至 /frontend/models/Page.php、/backend/models/Page.php,调整为各自的命名空间

图30

31、在 api 应用中搜索 use common/models/Page;,替换为:use api/models/Page;,在前台、后台应用中同样类似处理,如图31

在 api 应用中搜索 use common/models/Page;,替换为:use api/models/Page;,在前台、后台应用中同样类似处理

图31

32、当本地设置为简体中文时,打开 http://backend.cmcp-api.localhost/page/create ,翻译功能可用,如图32

当本地设置为简体中文时,打开 http://backend.cmcp-api.localhost/page/create ,翻译功能可用

图32

33、编辑个人信息,设置本地为英语美国,如图33

编辑个人信息,设置本地为英语美国

图33

34、当本地设置为英语美国时,再次打开 http://backend.cmcp-api.localhost/page/create ,翻译功能可用,如图34

当本地设置为英语美国时,再次打开 http://backend.cmcp-api.localhost/page/create ,翻译功能可用

图34

35、开始实现 RESTful Web 服务,参考网址:http://www.shuijingwanwq.com/2016/11/28/1457/ ,如图35

开始实现 RESTful Web 服务,参考网址:http://www.shuijingwanwq.com/2016/11/28/1457/

图35

36、打开网址:http://backend.cmcp-api.localhost/article/create ,创建文章,如图36

打开网址:http://backend.cmcp-api.localhost/article/create ,创建文章

图36

37、在 Postman 中,GET http://frontend.cmcp-api.localhost/api/v1/articles ,此为自带的RESTful Web 服务,响应结果,如图37

{
    "items": [
        {
            "id": 1,
            "slug": "biao-ti20180104-0",
            "category_id": 1,
            "title": "标题20180104-0",
            "body": "

内容

",
            "published_at": 1515031309,
            "_links": {
                "self": {
                    "href": "http://frontend.cmcp-api.localhost/api/v1/articles/1"
                }
            }
        }
    ],
    "_links": {
        "self": {
            "href": "http://frontend.cmcp-api.localhost/api/v1/articles?page=1"
        }
    },
    "_meta": {
        "totalCount": 1,
        "pageCount": 1,
        "currentPage": 1,
        "perPage": 20
    }
}
在 Postman 中,GET http://frontend.cmcp-api.localhost/api/v1/articles ,此为自带的RESTful Web 服务,响应结果

图37

38、RESTful Web 服务,建议基于一个单独的接口应用来实现,而不是基于前台应用中的一个api模块来实现,这样可以更为方便地维护你的WEB应用程序,如图38

RESTful Web 服务,建议基于一个单独的接口应用来实现,而不是基于前台应用中的一个api模块来实现,这样可以更为方便地维护你的WEB应用程序

图38

39、新建目录:/api/rests,此目录将做为 RESTful Web 服务的操作方法类目录,如图39

新建目录:/api/rests,此目录将做为 RESTful Web 服务的操作方法类目录

图39

40、编辑控制器类 /api/controllers/PageController.php ,控制器类扩展自 [[yii/rest/ActiveController]]。 通过指定 [[yii/rest/ActiveController::modelClass|modelClass]] 作为 api/models/Page, 控制器就能知道使用哪个模型去获取和处理数据。如图40

namespace api/controllers;

use yii/rest/ActiveController;

class PageController extends ActiveController
{
    public $modelClass = 'api/models/Page';
}

编辑控制器类 /api/controllers/PageController.php ,控制器类扩展自 [[yii/rest/ActiveController]]。 通过指定 [[yii/rest/ActiveController::modelClass|modelClass]] 作为 api/models/Page, 控制器就能知道使用哪个模型去获取和处理数据。

图40

41、配置URL规则,修改有关在应用程序配置的urlManager组件的配置,编辑:/api/config/_urlManager.php,如图41

return [
    'class' => yii/web/UrlManager::class,
    'enablePrettyUrl' => true,
    'showScriptName' => false,
    'rules' => [
        [
            'class' => 'yii/rest/UrlRule',
            'controller' => ['page'],
        ],
    ],
];
配置URL规则,修改有关在应用程序配置的urlManager组件的配置,编辑:/api/config/_urlManager.php

图41

42、在 Postman 中,GET http://www.cmcp-api.localhost/pages ,403响应,如图42

{
    "name": "Forbidden",
    "message": "Login Required",
    "code": 0,
    "status": 403,
    "type": "yii//web//ForbiddenHttpException"
}
在 Postman 中,GET http://www.cmcp-api.localhost/pages ,403响应

图42

43、编辑 /api/config/web.php,注释附加的行为globalAccess,有待后期调整(验证、授权),如图43

    /*
    'as globalAccess' => [
        'class' => common/behaviors/GlobalAccessBehavior::class,
        'rules' => [
            [
                'controllers' => ['sign-in'],
                'allow' => true,
                'roles' => ['?'],
                'actions' => ['login']
            ],
            [
                'controllers' => ['sign-in'],
                'allow' => true,
                'roles' => ['@'],
                'actions' => ['logout']
            ],
            [
                'controllers' => ['site'],
                'allow' => true,
                'roles' => ['?', '@'],
                'actions' => ['error']
            ],
            [
                'controllers' => ['debug/default'],
                'allow' => true,
                'roles' => ['?'],
            ],
            [
                'controllers' => ['user'],
                'allow' => true,
                'roles' => ['administrator'],
            ],
            [
                'controllers' => ['user'],
                'allow' => false,
            ],
            [
                'allow' => true,
                'roles' => ['manager'],
            ]
        ]
    ]
    */
编辑 /api/config/web.php,注释附加的行为globalAccess,有待后期调整(验证、授权)

图43

44、在 Postman 中,GET http://www.cmcp-api.localhost/pages ,200响应,如图44

[
    {
        "id": 1,
        "slug": "about",
        "title": "About",
        "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
        "view": null,
        "status": 1,
        "created_at": 1514860785,
        "updated_at": 1514860785
    }
]
在 Postman 中,GET http://www.cmcp-api.localhost/pages ,200响应

图44

45、调用API服务后返回数据采用统一格式,返回的HTTP状态码为20x,代表调用成功;返回4xx或5xx的HTTP状态码代表调用失败。调整200响应的返回数据格式,与403响应一致,至少包含:”message”,”code”,新建语言包文件:/api/messages/zh/app.php(响应成功)、/api/messages/zh/error.php(响应失败),如图45

return [
    10000 => 'success',
    10001 => '获取页面列表成功',
];
调用API服务后返回数据采用统一格式,返回的HTTP状态码为20x,代表调用成功;返回4xx或5xx的HTTP状态码代表调用失败。调整200响应的返回数据格式,与403响应一致,至少包含:"message","code",新建语言包文件:/api/messages/zh/app.php(响应成功)、/api/messages/zh/error.php(响应失败)

图45

46、版本化的实现,参考网址:https://github.com/yiisoft/yii2/blob/master/docs/guide-zh-CN/rest-versioning.md ,编辑 /api/controllers/PageController.php,删除 modelClass,如图46

<?php

namespace api/controllers;

use yii/rest/ActiveController;

class PageController extends ActiveController
{
}

版本化的实现,参考网址:https://github.com/yiisoft/yii2/blob/master/docs/guide-zh-CN/rest-versioning.md ,编辑 /api/controllers/PageController.php,删除 modelClass

图46

47、把每个主要版本的 API 实现在一个单独的模块 ID 的主版本号,基于 Gii 生成模块 v1,打开网址:http://frontend.cmcp-api.localhost/gii/module ,如图47

把每个主要版本的 API 实现在一个单独的模块 ID 的主版本号,基于 Gii 生成模块 v1,打开网址:http://frontend.cmcp-api.localhost/gii/module

图47

48、新建 /api/modules/v1/models/Page.php,继承至 /api/models/Page.php,如图48
注:/api/modules/v1/models/Page(仅用于 v1 模块) > /api/models/Page(仅用于 api 应用) > /common/logics/Page.php(可用于 api、frontend 等多个应用) > /common/models/Page.php(仅限于 Gii 生成) > /yii/db/ActiveRecord

<?php

namespace api/modules/v1/models;

class Page extends /api/models/Page
{

}

新建 /api/modules/v1/models/Page.php,继承至 /api/models/Page.php

图48

49、/api/modules/v1/controllers/DefaultController.php 重命名为 /api/modules/v1/controllers/PageController.php,编辑代码,如图49
注:/api/modules/v1/controllers/PageController.php(仅用于 v1 模块) > /api/controllers/PageController.php(仅用于 api 应用) > /yii/rest/ActiveController

<?php

namespace api/modules/v1/controllers;

/**
 * Page controller for the `v1` module
 */
class PageController extends /api/controllers/PageController
{
    public $modelClass = 'api/modules/v1/models/Page';
}

/api/modules/v1/controllers/DefaultController.php 重命名为 /api/modules/v1/controllers/PageController.php,编辑代码

图49

50、要在应用中使用模块,只需要将模块加入到应用主体配置的[[yii/base/Application::modules|modules]]属性的列表中, 如下代码的应用主体配置 使用 v1 模块,编辑 /api/config/web.php,如图50

    'modules' => [
        'v1' => [
            'class' => api/modules/v1/Module::class,
        ],
        'i18n' => [
            'class' => api/modules/i18n/Module::class,
            'defaultRoute' => 'i18n-message/index'
        ]
    ],

要在应用中使用模块,只需要将模块加入到应用主体配置的[[yii/base/Application::modules|modules]]属性的列表中, 如下代码的应用主体配置 使用 v1 模块,编辑 /api/config/web.php

图50

51、配置URL规则,修改有关在应用程序配置的urlManager组件的配置,以支持 v1 模块,编辑:/api/config/_urlManager.php,如图51
注:通过配置 only 选项来明确列出哪些行为支持,v1/page 仅支持:’index’, ‘update’, ‘delete’, ‘options’

<?php return [ 'class' => yii/web/UrlManager::class,
    'enablePrettyUrl' => true,
    'enableStrictParsing' => true,
    'showScriptName' => false,
    'rules' => [
        [
            'class' => 'yii/rest/UrlRule',
            'controller' => ['v1/page'],
            'only' => ['index', 'update', 'delete', 'options'],
        ],
    ],
];

配置URL规则,修改有关在应用程序配置的urlManager组件的配置,以支持 v1 模块,编辑:/api/config/_urlManager.php

图51

52、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,如图52

[
    {
        "id": 1,
        "slug": "about",
        "title": "About",
        "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
        "view": null,
        "status": 1,
        "created_at": 1514860785,
        "updated_at": 1514860785
    }
]
在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应

图52

53、测试 only 选项,在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/1 ,405响应(不被允许的方法。因为 ‘view’ 行为不被支持),测试通过,如图53

测试 only 选项,在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/1 ,405响应(不被允许的方法。因为 'view' 行为不被支持),测试通过

图53

54、配置URL规则,修改有关在应用程序配置的urlManager组件的配置,以支持 v1 模块,编辑:/api/config/_urlManager.php,如图54
注:取消配置 only 选项,以后续支持所有行为

<?php return [ 'class' => yii/web/UrlManager::class,
    'enablePrettyUrl' => true,
    'enableStrictParsing' => true,
    'showScriptName' => false,
    'rules' => [
        [
            'class' => 'yii/rest/UrlRule',
            'controller' => ['v1/page'],
        ],
    ],
];

配置URL规则,修改有关在应用程序配置的urlManager组件的配置,以支持 v1 模块,编辑:/api/config/_urlManager.php

图54

55、在 Postman 中,GET http://www.cmcp-api.localhost/v1 ,404响应,格式为HMTL,如图55

<!DOCTYPE html>
<html lang="en-US">
    <head>
        <meta charset="UTF-8">
        <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'>
        <meta name="csrf-param" content="_csrf">
        <meta name="csrf-token" content="MtUm8pVxEHfmqhYMruxdUyScsktaMyFDc350jW_XR7dzmGqaoDRyT7bPc13M2W0AVNuGISJSbQUYOFn9LKNzgQ==">
        <title>Not Found (#404)</title>
        <link href="/assets/95b60493/themes/smoothness/jquery-ui.css?v=1514958071" rel="stylesheet">
        <link href="/assets/7635f0fe/css/bootstrap.css?v=1512021376" rel="stylesheet">
        <link href="/assets/1ee3df8/css/font-awesome.min.css?v=1512021376" rel="stylesheet">
        <link href="/assets/541f775b/css/AdminLTE.min.css?v=1512021373" rel="stylesheet">
        <link href="/assets/541f775b/css/skins/_all-skins.min.css?v=1512021373" rel="stylesheet">
        <link href="/css/style.css?v=1512021342" rel="stylesheet">

    </head>
    <body class=" skin-blue ">
    </body>
</html>
在 Postman 中,GET http://www.cmcp-api.localhost/v1 ,404响应,格式为HMTL

图55

56、对于404响应格式为HTML的解决,编辑 /api/config/web.php,设置默认的响应格式为JSON,如图56

        'response' => [
            'format' => yii/web/Response::FORMAT_JSON,
        ],
对于404响应格式为HTML的解决,编辑 /api/config/web.php,设置默认的响应格式为JSON

图56

57、在 Postman 中,GET http://www.cmcp-api.localhost/v1 ,404响应,格式为JSON,如图57

{
    "name": "Not Found",
    "message": "Page not found.",
    "code": 0,
    "status": 404,
    "type": "yii//web//NotFoundHttpException"
}
在 Postman 中,GET http://www.cmcp-api.localhost/v1 ,404响应,格式为JSON

图57

58、数据序列化的实现,在响应主体内包含分页信息来简化客户端的开发工作,编辑 /api/controllers/PageController.php,如图58

<?php namespace api/controllers; use yii/rest/ActiveController; class PageController extends ActiveController { public $serializer = [ 'class' => 'yii/rest/Serializer',
        'collectionEnvelope' => 'items',
    ];
}

数据序列化的实现,在响应主体内包含分页信息来简化客户端的开发工作,编辑 /api/controllers/PageController.php

图58

59、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,在响应主体内包含分页信息,如图59

{
    "items": [
        {
            "id": 1,
            "slug": "about",
            "title": "About",
            "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
            "view": null,
            "status": 1,
            "created_at": 1514860785,
            "updated_at": 1514860785
        }
    ],
    "_links": {
        "self": {
            "href": "http://www.cmcp-api.localhost/v1/pages?page=1"
        }
    },
    "_meta": {
        "totalCount": 1,
        "pageCount": 1,
        "currentPage": 1,
        "perPage": 20
    }
}
在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,在响应主体内包含分页信息

图59

60、RESTful APIs 通常是无状态的,因此,配置 user 应用组件,编辑 /api/config/web.php,如图60
注:配置user 应用组件:
设置 [[yii/web/User::enableSession|enableSession]] 属性为 false.
设置 [[yii/web/User::loginUrl|loginUrl]] 属性为null 显示一个HTTP 403 错误而不是跳转到登录界面.

        'user' => [
            'class' => yii/web/User::class,
            'identityClass' => common/models/User::class,
            'enableSession' => false,
            'loginUrl' => null,
            'enableAutoLogin' => false,
            'as afterLogin' => common/behaviors/LoginTimestampBehavior::class
        ],
RESTful APIs 通常是无状态的,因此,配置 user 应用组件,编辑 /api/config/web.php

图60

61、复制目录 /vendor/yiisoft/yii2/rest 下的 Action.php、IndexAction.php、ViewAction.php、CreateAction.php、UpdateAction.php、DeleteAction.php、Serializer.php 至目录 /api/rests/page,如图61

复制目录 /vendor/yiisoft/yii2/rest 下的 Action.php、IndexAction.php、ViewAction.php、CreateAction.php、UpdateAction.php、DeleteAction.php、Serializer.php 至目录 /api/rests/page

图61

62、如果为多个单词组合的目录,建议目录使用小写+下划线,参考网址:https://github.com/hfcorriez/fig-standards/blob/master/accepted/zh_CN/PSR-0.md ,编辑 /api/controllers/PageController.php,如图62

<?php namespace api/controllers; use yii/rest/ActiveController; class PageController extends ActiveController { public $serializer = [ 'class' => 'api/rests/page/Serializer',
        'collectionEnvelope' => 'items',
    ];

    /**
     * @inheritdoc
     */
    public function actions()
    {
        return [
            'index' => [
                'class' => 'api/rests/page/IndexAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
            ],
            'view' => [
                'class' => 'api/rests/page/ViewAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
            ],
            'create' => [
                'class' => 'api/rests/page/CreateAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
                'scenario' => $this->createScenario,
            ],
            'update' => [
                'class' => 'api/rests/page/UpdateAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
                'scenario' => $this->updateScenario,
            ],
            'delete' => [
                'class' => 'api/rests/page/DeleteAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
            ],
            'options' => [
                'class' => 'yii/rest/OptionsAction',
            ],
        ];
    }
}

注:如果仅支持较少的行为,可以选择下面的方案,例

    public function actions()
    {
        $actions = parent::actions();
        
        $actions['view']['class'] = 'api/rests/page/ViewAction';

        return $actions;
    }
如果为多个单词组合的目录,建议目录使用小写+下划线,参考网址:https://github.com/hfcorriez/fig-standards/blob/master/accepted/zh_CN/PSR-0.md ,编辑 /api/controllers/PageController.php

图62

63、编辑 /api/rests/page/IndexAction.php,调整命名空间、继承关系、查询条件等,如图63

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace api/rests/page;

use Yii;
use yii/data/ActiveDataProvider;

/**
 * IndexAction implements the API endpoint for listing multiple models.
 *
 * For more details and usage information on IndexAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class IndexAction extends /yii/rest/IndexAction
{
    const STATUS_INACTIVE = 0; //状态:不活跃
    const STATUS_ACTIVE = 1; //状态:活跃

    /**
     * Prepares the data provider that should return the requested collection of the models.
     * @return ActiveDataProvider
     */
    protected function prepareDataProvider()
    {
        $requestParams = Yii::$app->getRequest()->getBodyParams();
        if (empty($requestParams)) {
            $requestParams = Yii::$app->getRequest()->getQueryParams();
        }

        $filter = null;
        if ($this->dataFilter !== null) {
            $this->dataFilter = Yii::createObject($this->dataFilter);
            if ($this->dataFilter->load($requestParams)) {
                $filter = $this->dataFilter->build();
                if ($filter === false) {
                    return $this->dataFilter;
                }
            }
        }

        if ($this->prepareDataProvider !== null) {
            return call_user_func($this->prepareDataProvider, $this, $filter);
        }

        /* @var $modelClass /yii/db/BaseActiveRecord */
        $modelClass = $this->modelClass;

        $query = $modelClass::find()->where(['status' => self::STATUS_ACTIVE]);
        if (!empty($filter)) {
            $query->andWhere($filter);
        }

        return Yii::createObject([
            'class' => ActiveDataProvider::className(),
            'query' => $query,
            'pagination' => [
                'params' => $requestParams,
            ],
            'sort' => [
                'params' => $requestParams,
            ],
        ]);
    }
}

编辑 /api/rests/page/IndexAction.php,调整命名空间、继承关系、查询条件等

图63

64、编辑 /api/rests/page/Serializer.php,调整命名空间、继承关系、响应结构(响应成功:”code”: 10000,”message”,”data”;响应失败:”code”: 不等于10000的其他数字,”message”)等,如图64

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace api/rests/page;

use Yii;
use yii/data/DataProviderInterface;

/**
 * Serializer converts resource objects and collections into array representation.
 *
 * Serializer is mainly used by REST controllers to convert different objects into array representation
 * so that they can be further turned into different formats, such as JSON, XML, by response formatters.
 *
 * The default implementation handles resources as [[Model]] objects and collections as objects
 * implementing [[DataProviderInterface]]. You may override [[serialize()]] to handle more types.
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class Serializer extends /yii/rest/Serializer
{
    /**
     * Serializes a data provider.
     * @param DataProviderInterface $dataProvider
     * @return array the array representation of the data provider.
     */
    protected function serializeDataProvider($dataProvider)
    {
        if ($this->preserveKeys) {
            $models = $dataProvider->getModels();
        } else {
            $models = array_values($dataProvider->getModels());
        }
        $models = $this->serializeModels($models);

        if (($pagination = $dataProvider->getPagination()) !== false) {
            $this->addPaginationHeaders($pagination);
        }

        if ($this->request->getIsHead()) {
            return null;
        } elseif ($this->collectionEnvelope === null) {
            return $models;
        }

        $result = [
            $this->collectionEnvelope => $models,
        ];

        if (empty($result['items'])) {
            return ['code' => 20001, 'message' => Yii::t('error', '20001')];
        }

        if ($pagination !== false) {
            return ['code' => 10000, 'message' => Yii::t('app', '10001'), 'data' => array_merge($result, $this->serializePagination($pagination))];
        }

        return ['code' => 10000, 'message' => Yii::t('app', '10001'), 'data' => $result];
    }
}

编辑 /api/rests/page/Serializer.php,调整命名空间、继承关系、响应结构(响应成功:"code": 10000,"message","data";响应失败:"code": 不等于10000的其他数字,"message")等

图64

65、编辑 /api/config/base.php,配置接口应用的 i18n 应用组件 ,如图65

        'i18n' => [
            'translations' => [
                'model/*'=> [
                    'class' => 'yii/i18n/PhpMessageSource',
                    'forceTranslation' => true,
                    'basePath'=>'@common/messages',
                    'fileMap'=>[
                    ],
                ],
                'app'=> [
                    'class' => 'yii/i18n/PhpMessageSource',
                    'forceTranslation' => true,
                    'basePath'=>'@api/messages',
                    'fileMap'=>[
                    ],
                ],
                '*'=> [
                    'class' => 'yii/i18n/PhpMessageSource',
                    'forceTranslation' => true,
                    'basePath'=>'@api/messages',
                    'fileMap'=>[
                    ],
                ],
            ],
        ],
编辑 /api/config/base.php,配置接口应用的 i18n 应用组件

图65

66、新建语言包文件:/api/messages/zh/error.php(简体中文、响应失败),如图66

return [
    20000 => 'error',
    20001 => '页面列表为空',
];
新建语言包文件:/api/messages/zh/error.php(简体中文、响应失败)

图66

67、新建语言包文件:/api/messages/en/app.php(英语美国、响应成功),如图67

return [
    10000 => 'success',
    10001 => 'Get page list is successful',
];
新建语言包文件:/api/messages/en/app.php(英语美国、响应成功)

图67

68、新建语言包文件:/api/messages/en/error.php(英语美国、响应失败),如图68

return [
    20000 => 'error',
    20001 => 'Page list is empty',
];
新建语言包文件:/api/messages/en/error.php(英语美国、响应失败)

图68

69、打开网址:http://backend.cmcp-api.localhost/page/update?id=1 ,编辑文章,活跃复选框取消选中,如图69

打开网址:http://backend.cmcp-api.localhost/page/update?id=1 ,编辑文章,活跃复选框取消选中

图69

70、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,状态为活跃的页面列表为空,如图70
注:
Accept application/json; version=0.0

{
    "code": 20001,
    "message": "Page list is empty"
}
在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,状态为活跃的页面列表为空

图70

71、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,状态为活跃的页面列表为空,如图71
注:
Accept application/json; version=0.0
Accept-Language zh-CN

{
    "code": 20001,
    "message": "页面列表为空"
}
在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,状态为活跃的页面列表为空

图71

72、打开网址:http://backend.cmcp-api.localhost/page/update?id=1 ,编辑文章,活跃复选框勾选,如图72

打开网址:http://backend.cmcp-api.localhost/page/update?id=1 ,编辑文章,活跃复选框勾选

图72

73、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,状态为活跃的页面列表不为空,如图73
注:
Accept application/json; version=0.0
Accept-Language en-US

{
    "code": 10000,
    "message": "Get page list is successful",
    "data": {
        "items": [
            {
                "id": 1,
                "slug": "about",
                "title": "About",
                "body": "

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

",
                "view": "",
                "status": 1,
                "created_at": 1514860785,
                "updated_at": 1515482758
            }
        ],
        "_links": {
            "self": {
                "href": "http://www.cmcp-api.localhost/v1/pages?page=1"
            }
        },
        "_meta": {
            "totalCount": 1,
            "pageCount": 1,
            "currentPage": 1,
            "perPage": 20
        }
    }
}
在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,状态为活跃的页面列表不为空

图73

74、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,状态为活跃的页面列表不为空,如图74
注:
Accept application/json; version=0.0
Accept-Language zh-CN

{
    "code": 10000,
    "message": "获取页面列表成功",
    "data": {
        "items": [
            {
                "id": 1,
                "slug": "about",
                "title": "About",
                "body": "

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

",
                "view": "",
                "status": 1,
                "created_at": 1514860785,
                "updated_at": 1515482758
            }
        ],
        "_links": {
            "self": {
                "href": "http://www.cmcp-api.localhost/v1/pages?page=1"
            }
        },
        "_meta": {
            "totalCount": 1,
            "pageCount": 1,
            "currentPage": 1,
            "perPage": 20
        }
    }
}
在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,状态为活跃的页面列表不为空

图74

75、GET /pages/1: 返回页面 1 的详细信息,编辑 /api/rests/page/Action.php,调整命名空间、继承关系、响应结构等,如图75

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace api/rests/page;

use Yii;
use yii/db/ActiveRecordInterface;
use yii/web/NotFoundHttpException;

/**
 * Action is the base class for action classes that implement RESTful API.
 *
 * For more details and usage information on Action, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class Action extends /yii/rest/Action
{
    /**
     * Returns the data model based on the primary key given.
     * If the data model is not found, a 404 HTTP exception will be raised.
     * @param string $id the ID of the model to be loaded. If the model has a composite primary key,
     * the ID must be a string of the primary key values separated by commas.
     * The order of the primary key values should follow that returned by the `primaryKey()` method
     * of the model.
     * @return ActiveRecordInterface the model found
     * @throws NotFoundHttpException if the model cannot be found
     */
    public function findModel($id)
    {
        if ($this->findModel !== null) {
            return call_user_func($this->findModel, $id, $this);
        }

        /* @var $modelClass ActiveRecordInterface */
        $modelClass = $this->modelClass;
        $keys = $modelClass::primaryKey();
        if (count($keys) > 1) {
            $values = explode(',', $id);
            if (count($keys) === count($values)) {
                $model = $modelClass::findOne(array_combine($keys, $values));
            }
        } elseif ($id !== null) {
            $model = $modelClass::findOne($id);
        }

        if (isset($model)) {
            return $model;
        }

        throw new NotFoundHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '20002'), ['id' => $id])), 20002);
    }
}

GET /pages/1: 返回页面 1 的详细信息,编辑 /api/rests/page/Action.php,调整命名空间、继承关系、响应结构等

图75

76、编辑 /api/rests/page/ViewAction.php,调整命名空间、继承关系、响应结构等,如图76

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace api/rests/page;

use Yii;

/**
 * ViewAction implements the API endpoint for returning the detailed information about a model.
 *
 * For more details and usage information on ViewAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class ViewAction extends Action
{
    const STATUS_INACTIVE = 0; //状态:不活跃
    const STATUS_ACTIVE = 1; //状态:活跃

    /**
     * Displays a model.
     * @param string $id the primary key of the model.
     * @return /yii/db/ActiveRecordInterface the model being displayed
     */
    public function run($id)
    {
        $model = $this->findModel($id);
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id, $model);
        }

        /* 判断状态,如果为不活跃,则返回失败 */
        if ($model->status === self::STATUS_INACTIVE) {
            return ['code' => 20003, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20003'), ['id' => $id]))];
        }

        return ['code' => 10000, 'message' => Yii::t('app', '10002'), 'data' => $model];
    }
}

编辑 /api/rests/page/ViewAction.php,调整命名空间、继承关系、响应结构等

图76

77、编辑语言包文件:/api/messages/zh/app.php(简体中文、响应成功)、/api/messages/zh/error.php(简体中文、响应失败)、/api/messages/en/app.php(英语美国、响应成功)、/api/messages/en/error.php(英语美国、响应失败),如图77
/api/messages/zh/app.php

return [
    10000 => 'success',
    10001 => '获取页面列表成功',
    10002 => '获取页面详情成功',
];

/api/messages/zh/error.php

return [
    20000 => 'error',
    20001 => '页面列表为空',
    20002 => '页面ID:{id},不存在',
    20003 => '页面ID:{id},的状态不活跃',
];

/api/messages/en/app.php

return [
    10000 => 'success',
    10001 => 'Get page list is successful',
    10002 => 'Get page details succeeded',
];

/api/messages/en/error.php

return [
    20000 => 'error',
    20001 => 'Page list is empty',
    20002 => 'Page ID: {id}, does not exist',
    20003 => 'Page ID: {id}, the status is not active',
];
编辑语言包文件:/api/messages/zh/app.php(简体中文、响应成功)、/api/messages/zh/error.php(简体中文、响应失败)、/api/messages/en/app.php(英语美国、响应成功)、/api/messages/en/error.php(英语美国、响应失败)

图77

78、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/1 ,200响应,其状态为活跃,如图78
注:
Accept application/json; version=0.0
Accept-Language zh-CN

{
    "code": 10000,
    "message": "获取页面详情成功",
    "data": {
        "id": 2,
        "slug": "contact",
        "title": "Contact",
        "body": "

Contact

",
        "view": "",
        "status": 1,
        "created_at": 1515488912,
        "updated_at": 1515488912
    }
}
在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/1 ,200响应,其状态为活跃

图78

79、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/1 ,200响应,其状态为活跃,如图79
注:
Accept application/json; version=0.0
Accept-Language en-US

{
    "code": 10000,
    "message": "Get page details succeeded",
    "data": {
        "id": 2,
        "slug": "contact",
        "title": "Contact",
        "body": "

Contact

",
        "view": "",
        "status": 1,
        "created_at": 1515488912,
        "updated_at": 1515488912
    }
}
在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/1 ,200响应,其状态为活跃

图79

80、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/2 ,200响应,其状态为不活跃,如图80
注:
Accept application/json; version=0.0
Accept-Language en-US

{
    "code": 20003,
    "message": "Page ID: 2, the status is not active"
}
在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/2 ,200响应,其状态为不活跃

图80

81、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/3 ,404响应,页面不存在,如图81
注:
Accept application/json; version=0.0
Accept-Language zh-CN

{
    "name": "Not Found",
    "message": "页面ID:3,不存在",
    "code": 20002,
    "status": 404,
    "type": "yii//web//NotFoundHttpException"
}
在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/3 ,404响应,页面不存在

图81

82、实现[[yii/web/Linkable]] 接口来支持HATEOAS,返回与本资源对象的相关链接,编辑资源类 /api/models/Page.php,如图82

<?php namespace api/models; use yii/helpers/Url; use yii/web/Linkable; use yii/web/Link; class Page extends /common/logics/Page implements Linkable { /** * Returns a list of links. * * @return array the links */ public function getLinks() { return [ Link::REL_SELF => Url::to(['page/view', 'id' => $this->id], true),
            'index' => Url::to(['page/index'], true),
            'view' => Url::to(['page/view', 'id' => $this->id], true),
            'create' => Url::to(['page/index'], true),
            'update' => Url::to(['page/view', 'id' => $this->id], true),
            'delete' => Url::to(['page/view', 'id' => $this->id], true),
            'options' => Url::to(['page/index'], true),

        ];
    }
}

实现[[yii/web/Linkable]] 接口来支持HATEOAS,返回与本资源对象的相关链接,编辑资源类 /api/models/Page.php

图82

83、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/1 ,200响应,支持HATEOAS,如图83

注:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br

{
    "code": 10000,
    "message": "获取页面详情成功",
    "data": {
        "id": 1,
        "slug": "about",
        "title": "About",
        "body": "

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

",
        "view": "",
        "status": 1,
        "created_at": 1514860785,
        "updated_at": 1515548927,
        "_links": {
            "self": {
                "href": "http://www.cmcp-api.localhost/v1/pages/1"
            },
            "index": {
                "href": "http://www.cmcp-api.localhost/v1/pages"
            },
            "view": {
                "href": "http://www.cmcp-api.localhost/v1/pages/1"
            },
            "create": {
                "href": "http://www.cmcp-api.localhost/v1/pages"
            },
            "update": {
                "href": "http://www.cmcp-api.localhost/v1/pages/1"
            },
            "delete": {
                "href": "http://www.cmcp-api.localhost/v1/pages/1"
            },
            "options": {
                "href": "http://www.cmcp-api.localhost/v1/pages"
            }
        }
    }
}
在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages/1 ,200响应,支持HATEOAS

图83

84、POST /pages: 创建一个新页面,编辑 /api/rests/page/CreateAction.php,如图84

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace api/rests/page;

use Yii;
use yii/base/Model;
use yii/helpers/Url;
use yii/web/ServerErrorHttpException;

/**
 * CreateAction implements the API endpoint for creating a new model from the given data.
 *
 * For more details and usage information on CreateAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class CreateAction extends Action
{
    /**
     * @var string the scenario to be assigned to the new model before it is validated and saved.
     */
    public $scenario = Model::SCENARIO_DEFAULT;
    /**
     * @var string the name of the view action. This property is need to create the URL when the model is successfully created.
     */
    public $viewAction = 'view';


    /**
     * Creates a new model.
     * @return /yii/db/ActiveRecordInterface the model newly created
     * @throws ServerErrorHttpException if there is any error when creating the model
     */
    public function run()
    {
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }

        /* @var $model /yii/db/ActiveRecord */
        $model = new $this->modelClass([
            'scenario' => $this->scenario,
        ]);

        $model->load(Yii::$app->getRequest()->getBodyParams(), '');
        if ($model->save()) {
            $response = Yii::$app->getResponse();
            $response->setStatusCode(201);
            $id = implode(',', array_values($model->getPrimaryKey(true)));
            $response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true));
        } elseif ($model->hasErrors()) {
            $response = Yii::$app->getResponse();
            $response->setStatusCode(422, 'Data Validation Failed.');
            foreach ($model->getFirstErrors() as $message) {
                $firstErrors = $message;
            }
            return ['code' => 20004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20004'), ['firstErrors' => $firstErrors]))];
        } elseif (!$model->hasErrors()) {
            throw new ServerErrorHttpException('Failed to create the object for unknown reason.');
        }

        return ['code' => 10000, 'message' => Yii::t('app', '10003'), 'data' => $model];
    }
}

POST /pages: 创建一个新页面,编辑 /api/rests/page/CreateAction.php

图84

85、在 Postman 中,POST http://www.cmcp-api.localhost/v1/pages ,201响应,如图85
注:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded

{
    "code": 10000,
    "message": "创建页面成功",
    "data": {
        "slug": "slug-20180110-4",
        "title": "title-20180110-4",
        "body": "body-20180110-4",
        "view": "view-20180110-4",
        "status": "0",
        "created_at": 1515566824,
        "updated_at": 1515566824,
        "id": 7,
        "_links": {
            "self": {
                "href": "http://www.cmcp-api.localhost/v1/pages/7"
            },
            "index": {
                "href": "http://www.cmcp-api.localhost/v1/pages"
            },
            "view": {
                "href": "http://www.cmcp-api.localhost/v1/pages/7"
            },
            "create": {
                "href": "http://www.cmcp-api.localhost/v1/pages"
            },
            "update": {
                "href": "http://www.cmcp-api.localhost/v1/pages/7"
            },
            "delete": {
                "href": "http://www.cmcp-api.localhost/v1/pages/7"
            },
            "options": {
                "href": "http://www.cmcp-api.localhost/v1/pages"
            }
        }
    }
}
在 Postman 中,POST http://www.cmcp-api.localhost/v1/pages ,201响应

图85

86、在 Postman 中,POST http://www.cmcp-api.localhost/v1/pages ,参数保持原样,422响应,如图86
注:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded

{
    "code": 20004,
    "message": "数据验证失败:Slug的值/"slug-20180110-4/"已经被占用了。"
}
在 Postman 中,POST http://www.cmcp-api.localhost/v1/pages ,参数保持原样,422响应

图86

87、在 Postman 中,POST http://www.cmcp-api.localhost/v1/pages ,422响应(数据验证失败 (例如,响应一个 POST 请求)。 请检查响应体内详细的错误消息。),如图87
注:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded

Body
缺少 title 参数

{
    "code": 20004,
    "message": "数据验证失败:Title不能为空。"
}
在 Postman 中,POST http://www.cmcp-api.localhost/v1/pages ,422响应(数据验证失败 (例如,响应一个 POST 请求)。 请检查响应体内详细的错误消息。)

图87

88、在 Postman 中,POST http://www.cmcp-api.localhost/v1/pages ,422响应,如图88
注:
Accept application/json; version=0.0
Accept-Language en-US
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded

Body
缺少 title 参数

{
    "code": 20004,
    "message": "Data validation failed: Title cannot be blank."
}
在 Postman 中,POST http://www.cmcp-api.localhost/v1/pages ,422响应

图88

89、PUT /pages/4: 更新一个页面,编辑 /api/rests/page/UpdateAction.php,如图89

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace api/rests/page;

use Yii;
use yii/base/Model;
use yii/db/ActiveRecord;
use yii/web/ServerErrorHttpException;

/**
 * UpdateAction implements the API endpoint for updating a model.
 *
 * For more details and usage information on UpdateAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class UpdateAction extends Action
{
    /**
     * @var string the scenario to be assigned to the model before it is validated and updated.
     */
    public $scenario = Model::SCENARIO_DEFAULT;


    /**
     * Updates an existing model.
     * @param string $id the primary key of the model.
     * @return /yii/db/ActiveRecordInterface the model being updated
     * @throws ServerErrorHttpException if there is any error when updating the model
     */
    public function run($id)
    {
        /* @var $model ActiveRecord */
        $model = $this->findModel($id);

        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id, $model);
        }

        $model->scenario = $this->scenario;
        $model->load(Yii::$app->getRequest()->getBodyParams(), '');
        if ($model->save() === false) {
            if ($model->hasErrors()) {
                $response = Yii::$app->getResponse();
                $response->setStatusCode(422, 'Data Validation Failed.');
                foreach ($model->getFirstErrors() as $message) {
                    $firstErrors = $message;
                }
                return ['code' => 20004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20004'), ['firstErrors' => $firstErrors]))];
            } elseif (!$model->hasErrors()) {
                throw new ServerErrorHttpException('Failed to update the object for unknown reason.');
            }
        }

        return ['code' => 10000, 'message' => Yii::t('app', '10004'), 'data' => $model];
    }
}

PUT /pages/4: 更新一个页面,编辑 /api/rests/page/UpdateAction.php

图89

90、在 Postman 中,PUT http://www.cmcp-api.localhost/v1/pages/4 ,200响应,如图90

{
    "code": 10000,
    "message": "更新页面成功",
    "data": {
        "id": 4,
        "slug": "slug-20180110-44",
        "title": "title-20180110-44",
        "body": "body-20180110-44",
        "view": "view-20180110-44",
        "status": "1",
        "created_at": 1515554512,
        "updated_at": 1515569633,
        "_links": {
            "self": {
                "href": "http://www.cmcp-api.localhost/v1/pages/4"
            },
            "index": {
                "href": "http://www.cmcp-api.localhost/v1/pages"
            },
            "view": {
                "href": "http://www.cmcp-api.localhost/v1/pages/4"
            },
            "create": {
                "href": "http://www.cmcp-api.localhost/v1/pages"
            },
            "update": {
                "href": "http://www.cmcp-api.localhost/v1/pages/4"
            },
            "delete": {
                "href": "http://www.cmcp-api.localhost/v1/pages/4"
            },
            "options": {
                "href": "http://www.cmcp-api.localhost/v1/pages"
            }
        }
    }
}
在 Postman 中,PUT http://www.cmcp-api.localhost/v1/pages/4 ,200响应

图90

91、在 Postman 中,PUT http://www.cmcp-api.localhost/v1/pages/4 ,422响应,如图91
注:
Accept application/json; version=0.0
Accept-Language en-US
Accept-Encoding gzip, deflate, br
Content-Type application/x-www-form-urlencoded

Body
slug 的值已经被另一页面占用

{
    "code": 20004,
    "message": "Data validation failed: Slug /"slug-20180110-5/" has already been taken."
}
在 Postman 中,PUT http://www.cmcp-api.localhost/v1/pages/4 ,422响应

图91

92、DELETE /pages/4: 删除页面4,编辑 /api/rests/page/DeleteAction.php,如图92

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace api/rests/page;

use Yii;
use yii/web/ServerErrorHttpException;

/**
 * DeleteAction implements the API endpoint for deleting a model.
 *
 * For more details and usage information on DeleteAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class DeleteAction extends Action
{
    /**
     * Deletes a model.
     * @param mixed $id id of the model to be deleted.
     * @throws ServerErrorHttpException on failure.
     */
    public function run($id)
    {
        $model = $this->findModel($id);

        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id, $model);
        }

        if ($model->delete() === false) {
            throw new ServerErrorHttpException('Failed to delete the object for unknown reason.');
        }

        return ['code' => 10000, 'message' => Yii::t('app', '10005')];
    }
}

DELETE /pages/4: 删除页面4,编辑 /api/rests/page/DeleteAction.php

图92

93、在 Postman 中,DELETE http://www.cmcp-api.localhost/v1/pages/7 ,200响应,如图93
注:
Accept application/json; version=0.0
Accept-Language zh-CN
Accept-Encoding gzip, deflate, br

{
    "code": 10000,
    "message": "删除页面成功"
}
在 Postman 中,DELETE http://www.cmcp-api.localhost/v1/pages/7 ,200响应

图93

94、展示一下语言包文件的最后内容,如图94
/api/messages/zh/app.php

return [
    10000 => 'success',
    10001 => '获取页面列表成功',
    10002 => '获取页面详情成功',
    10003 => '创建页面成功',
    10004 => '更新页面成功',
    10005 => '删除页面成功',
];

/api/messages/zh/error.php

return [
    20000 => 'error',
    20001 => '页面列表为空',
    20002 => '页面ID:{id},不存在',
    20003 => '页面ID:{id},的状态不活跃',
    20004 => '数据验证失败:{firstErrors}',
];

/api/messages/en/app.php

return [
    10000 => 'success',
    10001 => 'Get page list is successful',
    10002 => 'Get page details succeeded',
    10003 => 'Create a page success',
    10004 => 'Update page success',
    10005 => 'Delete page success',
];

/api/messages/en/error.php

return [
    20000 => 'error',
    20001 => 'Page list is empty',
    20002 => 'Page ID: {id}, does not exist',
    20003 => 'Page ID: {id}, the status is not active',
    20004 => 'Data validation failed: {firstErrors}',
];
展示一下语言包文件的最后内容

图94

95、OPTIONS /pages: 显示关于末端 /pages 支持的动词,在 Postman 中,OPTIONS http://www.cmcp-api.localhost/v1/pages ,200响应,如图95

OPTIONS /pages: 显示关于末端 /pages 支持的动词,在 Postman 中,OPTIONS http://www.cmcp-api.localhost/v1/pages ,200响应

图95

96、OPTIONS /pages/1: 显示关于末端 /pages/1 支持的动词,在 Postman 中,OPTIONS http://www.cmcp-api.localhost/v1/pages/1 ,200响应,如图96

OPTIONS /pages/1: 显示关于末端 /pages/1 支持的动词,在 Postman 中,OPTIONS http://www.cmcp-api.localhost/v1/pages/1 ,200响应

图96

97、总结:现在支持的行为:index、view、create、update、delete、options,除 options 之外,基本上是继承之后,再次覆写实现具体的需求了的。

 

原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/180869.html

(0)
上一篇 2021年10月31日
下一篇 2021年10月31日

相关推荐

发表回复

登录后才能评论