企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 快速入门(九):API开发 使用`ThinkPHP5.0`可以更简单的进行`API`开发,并且最大程度的满足`API`对性能的要求,下面就`API`开发中的几个主要问题描述下。 - - [API版本](http://ihavenolimitations.xyz/thinkphp/thinkphp5_quickstart/160681#api-) - [异常处理](http://ihavenolimitations.xyz/thinkphp/thinkphp5_quickstart/160681#u5F02u5E38u5904u7406) - [RESTFul](http://ihavenolimitations.xyz/thinkphp/thinkphp5_quickstart/160681#restful) - [REST请求测试](http://ihavenolimitations.xyz/thinkphp/thinkphp5_quickstart/160681#-code-rest-code-) - [Postman](http://ihavenolimitations.xyz/thinkphp/thinkphp5_quickstart/160681#postman) - [REST请求伪装](http://ihavenolimitations.xyz/thinkphp/thinkphp5_quickstart/160681#-code-rest-code--1) - [API调试](http://ihavenolimitations.xyz/thinkphp/thinkphp5_quickstart/160681#api--1) - [环境安装](http://ihavenolimitations.xyz/thinkphp/thinkphp5_quickstart/160681#u73AFu5883u5B89u88C5) - [浏览器设置](http://ihavenolimitations.xyz/thinkphp/thinkphp5_quickstart/160681#u6D4Fu89C8u5668u8BBEu7F6E) - [应用配置](http://ihavenolimitations.xyz/thinkphp/thinkphp5_quickstart/160681#u5E94u7528u914Du7F6E) - [远程调试](http://ihavenolimitations.xyz/thinkphp/thinkphp5_quickstart/160681#u8FDCu7A0Bu8C03u8BD5) - [安全建议](http://ihavenolimitations.xyz/thinkphp/thinkphp5_quickstart/160681#u5B89u5168u5EFAu8BAE) `5.0`对`API`开发的支持包括架构、功能和性能方面的良好支持。 ## API版本 我们以一个用户信息读取的接口为例,包含两个版本`V1`和`V2`,`v2`版本的接口包括用户的档案信息,统一使用`json`格式数据输出到客户端。 在`application`目录下面创建`api`模块目录,并创建`controller`和`model`子目录,因为`api`接口无需视图,所以不需要创建`view`目录。 `api`版本号的传入方式有很多,包括设置头信息、请求参数传入以及路由方式,这里我们采请求参数传入的方式,设置路由如下: ``` <pre class="calibre18"> ``` <span class="hljs-operator"><span class="hljs-title1">Route</span>:<span class="hljs-string">:<span class="hljs-function">rule</span>(<span class="hljs-operator">':version/user/:id'</span>,<span class="hljs-operator">'api/:version.User/read'</span>)</span></span>; ``` ``` 不同版本的URL访问地址为: ``` <pre class="calibre18"> ``` <span class="hljs-string">http:</span> <span class="hljs-comment">//tp5.com/v1/user/10</span> <span class="hljs-string">http:</span> <span class="hljs-comment">//tp5.com/v2/user/10</span> ``` ``` > 版本号中不能包含`.`符号。 `v1`版本控制器(类文件位置为`application/api/controller/v1/User.php`)代码如下: ``` <pre class="calibre18"> ``` <span class="hljs-keyword">namespace</span> <span class="hljs-title">app</span>\<span class="hljs-title">api</span>\<span class="hljs-title">controller</span>\<span class="hljs-title">v1</span>; <span class="hljs-keyword">use</span> <span class="hljs-title">app</span>\<span class="hljs-title">api</span>\<span class="hljs-title">model</span>\<span class="hljs-title">User</span> <span class="hljs-title">as</span> <span class="hljs-title">UserModel</span>; <span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span></span>{ <span class="hljs-comment">// 获取用户信息</span><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">read</span><span class="hljs-number">(<span class="hljs-regexp">$id</span> = <span class="hljs-operator">0</span>)</span></span>{ <span class="hljs-regexp">$user</span> = UserModel::get(<span class="hljs-regexp">$id</span>); <span class="hljs-keyword">if</span> (<span class="hljs-regexp">$user</span>) { <span class="hljs-keyword">return</span> json(<span class="hljs-regexp">$user</span>); } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> json([<span class="hljs-string">'error'</span> => <span class="hljs-string">'用户不存在'</span>], <span class="hljs-number">404</span>); } } } ``` ``` `v2`版本的控制器(类文件位置为`application/api/controller/v2/User.php`)代码如下: ``` <pre class="calibre18"> ``` <span class="hljs-keyword">namespace</span> <span class="hljs-title">app</span>\<span class="hljs-title">api</span>\<span class="hljs-title">controller</span>\<span class="hljs-title">v2</span>; <span class="hljs-keyword">use</span> <span class="hljs-title">app</span>\<span class="hljs-title">api</span>\<span class="hljs-title">model</span>\<span class="hljs-title">User</span> <span class="hljs-title">as</span> <span class="hljs-title">UserModel</span>; <span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span></span>{ <span class="hljs-comment">// 获取用户信息</span><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">read</span><span class="hljs-number">(<span class="hljs-regexp">$id</span> = <span class="hljs-operator">0</span>)</span></span>{ <span class="hljs-regexp">$user</span> = UserModel::get(<span class="hljs-regexp">$id</span>, <span class="hljs-string">'profile'</span>); <span class="hljs-keyword">if</span> (<span class="hljs-regexp">$user</span>) { <span class="hljs-keyword">return</span> json(<span class="hljs-regexp">$user</span>); } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> json([<span class="hljs-string">'error'</span> => <span class="hljs-string">'用户不存在'</span>], <span class="hljs-number">404</span>); } } } ``` ``` v2版本和v1版本的接口区别在于v2的接口用户信息包含了用户的关联档案信息。 > 除了使用`json`格式返回客户端之外,系统还支持`xml`、`jsonp`格式,只需要把上面的`json`函数更改为`xml`和`jsonp`即可。 `User`模型代码如下: ``` <pre class="calibre18"> ``` namespace app\api\model; use think\<span class="hljs-operator">Model</span>; <span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword"><span class="hljs-operator">extends</span></span> <span class="hljs-title">Model</span></span>{ <span class="hljs-comment">// 定义一对一关联</span> public function profile() { <span class="hljs-keyword">return</span> $<span class="hljs-keyword">this</span>->hasOne(<span class="hljs-operator">'Profil</span>e'); } } ``` ``` `Profile`模型代码: ``` <pre class="calibre18"> ``` namespace app\api\model; use think\<span class="hljs-operator">Model</span>; <span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">Profile</span> <span class="hljs-keyword"><span class="hljs-operator">extends</span></span> <span class="hljs-title">Model</span></span>{ <span class="hljs-keyword">protected</span> $<span class="hljs-operator"><span class="hljs-keyword">type</span> =</span> [ <span class="hljs-operator">'birthda</span>y' => <span class="hljs-operator">'timestamp</span>:<span class="hljs-operator">Y</span>-m-d', ]; } ``` ``` 访问 `http://tp5.com/v1/user/10` 返回的数据是: ![](https://img.kancloud.cn/7b/d9/7bd9206ace494d194cfe7e5310e41925_1109x208.png) 访问 `http://tp5.com/v2/user/10` 返回的数据是: ![](https://img.kancloud.cn/c7/6e/c76e8fa2bf855bbf556733191d3bff62_1261x334.png) ## 异常处理 当发生异常的时候,通常我们返回不同的`HTTP`状态码来标识,控制器的`read`方法中当请求的用户不存在的时候,系统发送了`404`状态码来表示用户数据不存在,代码为: ``` <pre class="calibre18"> ``` <span class="hljs-keyword">return</span> json([<span class="hljs-string">'error'</span> => <span class="hljs-string">'用户不存在'</span>], <span class="hljs-number">404</span>); ``` ``` 并返回了错误信息如下: ![](https://img.kancloud.cn/bc/20/bc2098bd98334fe48d5a3c7b85f03e7c_326x79.png) 客户端通常可以直接通过HTTP状态码来判断接口请求的成功与否而自行进行自定义错误提示,一般`400`以上的状态码都表示请求失败,接口的代码还可以简化成: ``` <pre class="calibre18"> ``` <span class="hljs-comment">// 获取用户信息</span><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">read</span><span class="hljs-number">(<span class="hljs-regexp">$id</span> = <span class="hljs-operator">0</span>)</span></span>{ <span class="hljs-regexp">$user</span> = UserModel::get(<span class="hljs-regexp">$id</span>, <span class="hljs-string">'profile'</span>); <span class="hljs-keyword">if</span> (<span class="hljs-regexp">$user</span>) { <span class="hljs-keyword">return</span> json(<span class="hljs-regexp">$user</span>); } <span class="hljs-keyword">else</span> { <span class="hljs-comment">// 抛出HTTP异常 并发送404状态码</span> abort(<span class="hljs-number">404</span>); } } ``` ``` 如果你希望由API后台来处理异常,并且直接接管系统的所有异常信息输出json错误信息,可以自定义一个异常处理类位于(`application/api/exception/Http.php`): ``` <pre class="calibre18"> ``` <span class="hljs-keyword">namespace</span> <span class="hljs-title">app</span>\<span class="hljs-title">api</span>\<span class="hljs-title">exception</span>; <span class="hljs-keyword">use</span> <span class="hljs-title">think</span>\<span class="hljs-title">exception</span>\<span class="hljs-title">Handle</span>; <span class="hljs-keyword">use</span> <span class="hljs-title">think</span>\<span class="hljs-title">exception</span>\<span class="hljs-title">HttpException</span>; <span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">Http</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Handle</span></span>{ <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">render</span><span class="hljs-number">(\Exception <span class="hljs-regexp">$e</span>)</span></span>{ <span class="hljs-keyword">if</span> (<span class="hljs-regexp">$e</span> <span class="hljs-keyword">instanceof</span> HttpException) { <span class="hljs-regexp">$statusCode</span> = <span class="hljs-regexp">$e</span>->getStatusCode(); } <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">isset</span>(<span class="hljs-regexp">$statusCode</span>)) { <span class="hljs-regexp">$statusCode</span> = <span class="hljs-number">500</span>; } <span class="hljs-regexp">$result</span> = [ <span class="hljs-string">'code'</span> => <span class="hljs-regexp">$statusCode</span>, <span class="hljs-string">'msg'</span> => <span class="hljs-regexp">$e</span>->getMessage(), <span class="hljs-string">'time'</span> => <span class="hljs-regexp">$_SERVER</span>[<span class="hljs-string">'REQUEST_TIME'</span>], ]; <span class="hljs-keyword">return</span> json(<span class="hljs-regexp">$result</span>, <span class="hljs-regexp">$statusCode</span>); } } ``` ``` 然后,在应用配置文件中修改异常处理handle参数为自定义的异常类: ``` <pre class="calibre18"> ``` <span class="hljs-string">'exception_handle'</span> => <span class="hljs-string">'\app\api\exception\Http'</span>, ``` ``` 接管`HTTP`异常处理后,我们可以直接在方法中抛出任何的`HTTP`异常,系统自动处理为`json`格式输出到客户端: ``` <pre class="calibre18"> ``` <span class="hljs-comment">// 获取用户信息</span><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">read</span><span class="hljs-number">(<span class="hljs-regexp">$id</span> = <span class="hljs-operator">0</span>)</span></span>{ <span class="hljs-regexp">$user</span> = UserModel::get(<span class="hljs-regexp">$id</span>, <span class="hljs-string">'profile'</span>); <span class="hljs-keyword">if</span> (<span class="hljs-regexp">$user</span>) { <span class="hljs-keyword">return</span> json(<span class="hljs-regexp">$user</span>); } <span class="hljs-keyword">else</span> { <span class="hljs-comment">// 抛出HTTP异常 并发送404状态码</span> abort(<span class="hljs-number">404</span>,<span class="hljs-string">'用户不存在'</span>); } } ``` ``` 当我们请求一个不存在的用户时候 ``` <pre class="calibre18"> ``` <span class="hljs-string">http:</span> <span class="hljs-comment">//tp5.com/v2/user/100</span> ``` ``` 会看到如下的输出: ![](https://img.kancloud.cn/48/db/48db83f6c69d63d6948aee8ac63b9528_534x106.png) 如果希望捕获系统的任何异常并转发,可以使用`try catch`如下: ``` <pre class="calibre18"> ``` <span class="hljs-comment">// 获取用户信息</span><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">read</span><span class="hljs-number">(<span class="hljs-regexp">$id</span> = <span class="hljs-operator">0</span>)</span></span>{ <span class="hljs-keyword">try</span> { <span class="hljs-comment">// 制造一个方法不存在的异常</span><span class="hljs-regexp">$user</span> = UserModel::geet(<span class="hljs-regexp">$id</span>, <span class="hljs-string">'profile'</span>); <span class="hljs-keyword">if</span> (<span class="hljs-regexp">$user</span>) { <span class="hljs-keyword">return</span> json(<span class="hljs-regexp">$user</span>); } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> abort(<span class="hljs-number">404</span>, <span class="hljs-string">'用户不存在'</span>); } } <span class="hljs-keyword">catch</span> (\<span class="hljs-keyword">Exception</span> <span class="hljs-regexp">$e</span>) { <span class="hljs-comment">// 捕获异常并转发为HTTP异常</span><span class="hljs-keyword">return</span> abort(<span class="hljs-number">404</span>, <span class="hljs-regexp">$e</span>->getMessage()); } } ``` ``` 执行后会看到系统输出了 ![](https://img.kancloud.cn/44/a1/44a10223ba9ac0d2216757f47838670e_781x115.png) ## RESTFul `REST`(Representational State Transfer表述性状态转移)是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。`REST`提出了一些设计概念和准则: 1、网络上的所有事物都被抽象为资源(resource); 2、每个资源对应一个唯一的资源标识(resource identifier); 3、通过通用的连接器接口(generic connector interface)对资源进行操作; 4、对资源的各种操作不会改变资源标识; 5、所有的操作都是无状态的(stateless)。 `REST`通常基于使用`HTTP`,`URI`,和`JSON`以及`HTML`这些现有的广泛流行的协议和标准。 传统的请求模式和`REST`模式的请求模式区别: 作用 传统模式 REST模式 列举出所有的用户 GET /users/list GET /users 列出ID为1的用户信息 GET /users/show/id/1 GET /users/1 插入一个新的用户 POST /users/add POST /users 更新ID为1的用户信息 POST /users/update/id/1 PUT /users/1 删除ID为1的用户 POST /users/delete/id/1 DELETE /users/1关于更多的REST信息,可以参考:<http://zh.wikipedia.org/wiki/REST> 下面以一个博客的`REST`接口开发为例,先创建`think_blog`数据表如下: ``` <pre class="calibre18"> ``` <span class="hljs-operator"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> <span class="hljs-string">`think_blog`</span> ( <span class="hljs-string">`id`</span> <span class="hljs-number">int</span>(<span class="hljs-number">10</span>) <span class="hljs-keyword">UNSIGNED</span> <span class="hljs-keyword">NOT</span> <span class="hljs-number">NULL</span> AUTO_INCREMENT <span class="hljs-keyword">COMMENT</span> <span class="hljs-string">'ID'</span>, <span class="hljs-string">`name`</span> <span class="hljs-number">char</span>(<span class="hljs-number">40</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-number">NULL</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-string">''</span> <span class="hljs-keyword">COMMENT</span> <span class="hljs-string">'标识'</span>, <span class="hljs-string">`title`</span> <span class="hljs-number">char</span>(<span class="hljs-number">80</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-number">NULL</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-string">''</span> <span class="hljs-keyword">COMMENT</span> <span class="hljs-string">'标题'</span>, <span class="hljs-string">`content`</span> <span class="hljs-number">text</span> <span class="hljs-keyword">COMMENT</span> <span class="hljs-string">'内容'</span>, <span class="hljs-string">`create_time`</span> <span class="hljs-number">int</span>(<span class="hljs-number">10</span>) <span class="hljs-keyword">UNSIGNED</span> <span class="hljs-keyword">NOT</span> <span class="hljs-number">NULL</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-string">'0'</span> <span class="hljs-keyword">COMMENT</span> <span class="hljs-string">'创建时间'</span>, <span class="hljs-string">`update_time`</span> <span class="hljs-number">int</span>(<span class="hljs-number">10</span>) <span class="hljs-keyword">UNSIGNED</span> <span class="hljs-keyword">NOT</span> <span class="hljs-number">NULL</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-string">'0'</span> <span class="hljs-keyword">COMMENT</span> <span class="hljs-string">'更新时间'</span>, <span class="hljs-string">`status`</span> tinyint(<span class="hljs-number">1</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-number">NULL</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-string">'0'</span> <span class="hljs-keyword">COMMENT</span> <span class="hljs-string">'数据状态'</span>, PRIMARY <span class="hljs-keyword">KEY</span> (<span class="hljs-string">`id`</span>) ) <span class="hljs-keyword">ENGINE</span>=MyISAM <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">CHARSET</span>=utf8 <span class="hljs-keyword">COMMENT</span>=<span class="hljs-string">'博客表'</span>;</span> ``` ``` > 为了演示需要,该数据表做了一定程度的简化,并不一定符合实际的博客设计,因此仅供测试学习。 为了支持`RESTFul`请求的路由规则,只需要在路由配置文件中添加下面的代码: ``` <pre class="calibre18"> ``` <span class="hljs-operator"><span class="hljs-title1">Route</span>:<span class="hljs-string">:<span class="hljs-function">resource</span>(<span class="hljs-operator">'blogs'</span>,<span class="hljs-operator">'index/blog'</span>)</span></span>; ``` ``` 该方法注册了一个名为`blogs`的资源路由,其实内部会自动注册`7`个路由规则,包括: 标识 请求类型 生成路由规则 对应操作方法(默认) 描述 index GET `blogs` index 显示博客列表 create GET `blogs/create` create 新增博客页面 save POST `blogs` save 保存博客内容 read GET `blogs/:id` read 查看博客内容 edit GET `blogs/:id/edit` edit 编辑博客页面 update PUT `blogs/:id` update 更新博客内容 delete DELETE `blogs/:id` delete 删除博客> 上面的7个路由规则,在本示例中仅使用了5个(其中create和edit页面由于本API接口测试暂时不需要使用)。 创建Blog模型如下: ``` <pre class="calibre18"> ``` <span class="hljs-operator"><span class="hljs-number"><?php</span><span class="hljs-keyword">namespace</span> <span class="hljs-title">app</span>\<span class="hljs-title">index</span>\<span class="hljs-title">model</span>; <span class="hljs-keyword">use</span> <span class="hljs-title">think</span>\<span class="hljs-title">Model</span>; <span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">Blog</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span></span>{ <span class="hljs-keyword">protected</span> <span class="hljs-regexp">$autoWriteTimestamp</span> = <span class="hljs-keyword">true</span>; <span class="hljs-keyword">protected</span> <span class="hljs-regexp">$insert</span> = [ <span class="hljs-string">'status'</span> => <span class="hljs-number">1</span>, ]; <span class="hljs-keyword">protected</span> <span class="hljs-regexp">$field</span> = [ <span class="hljs-string">'id'</span> => <span class="hljs-string">'int'</span>, <span class="hljs-string">'create_time'</span> => <span class="hljs-string">'int'</span>, <span class="hljs-string">'update_time'</span> => <span class="hljs-string">'int'</span>, <span class="hljs-string">'name'</span>, <span class="hljs-string">'title'</span>, <span class="hljs-string">'content'</span>, ]; }</span> ``` ``` 为了快速生成控制器类,我们进入命令行,切换到应用根目录下面,执行下面的指令: ``` <pre class="calibre18"> ``` php think make:controller index/B<span class="hljs-number">log</span> ``` ``` 会自动生成一个`Blog`资源控制器类,并且会自动生成如前所述的资源路由对应的7个(空白)方法,我们给每个方法添加一些简单的代码,并去掉了`create`和`edit`两个方法(对于`api`开发而言可以不需要),最终控制器代码如下: ``` <pre class="calibre18"> ``` <span class="hljs-operator"><span class="hljs-number"><?php</span><span class="hljs-keyword">namespace</span> <span class="hljs-title">app</span>\<span class="hljs-title">index</span>\<span class="hljs-title">controller</span>; <span class="hljs-keyword">use</span> <span class="hljs-title">app</span>\<span class="hljs-title">index</span>\<span class="hljs-title">model</span>\<span class="hljs-title">Blog</span> <span class="hljs-title">as</span> <span class="hljs-title">Blogs</span>; <span class="hljs-keyword">use</span> <span class="hljs-title">think</span>\<span class="hljs-title">Controller</span>; <span class="hljs-keyword">use</span> <span class="hljs-title">think</span>\<span class="hljs-title">Request</span>; <span class="hljs-operator"><span class="hljs-keyword">class</span> <span class="hljs-title">Blog</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Controller</span></span>{ <span class="hljs-comment">/** * 显示资源列表 * * <span class="hljs-operator">@return</span> \think\Response */</span><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">index</span><span class="hljs-number">()</span></span>{ <span class="hljs-regexp">$list</span> = Blogs::all(); <span class="hljs-keyword">return</span> json(<span class="hljs-regexp">$list</span>); } <span class="hljs-comment">/** * 保存新建的资源 * * <span class="hljs-operator">@param</span> \think\Request $request * <span class="hljs-operator">@return</span> \think\Response */</span><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">save</span><span class="hljs-number">(Request <span class="hljs-regexp">$request</span>)</span></span>{ <span class="hljs-regexp">$data</span> = <span class="hljs-regexp">$request</span>->param(); <span class="hljs-regexp">$result</span> = Blogs::create(<span class="hljs-regexp">$data</span>); <span class="hljs-keyword">return</span> json(<span class="hljs-regexp">$result</span>); } <span class="hljs-comment">/** * 显示指定的资源 * * <span class="hljs-operator">@param</span> int $id * <span class="hljs-operator">@return</span> \think\Response */</span><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">read</span><span class="hljs-number">(<span class="hljs-regexp">$id</span>)</span></span>{ <span class="hljs-regexp">$data</span> = Blogs::get(<span class="hljs-regexp">$id</span>); <span class="hljs-keyword">return</span> json(<span class="hljs-regexp">$data</span>); } <span class="hljs-comment">/** * 保存更新的资源 * * <span class="hljs-operator">@param</span> \think\Request $request * <span class="hljs-operator">@param</span> int $id * <span class="hljs-operator">@return</span> \think\Response */</span><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span><span class="hljs-number">(Request <span class="hljs-regexp">$request</span>, <span class="hljs-regexp">$id</span>)</span></span>{ <span class="hljs-regexp">$data</span> = <span class="hljs-regexp">$request</span>->param(); <span class="hljs-regexp">$result</span> = Blogs::update(<span class="hljs-regexp">$data</span>, [<span class="hljs-string">'id'</span> => <span class="hljs-regexp">$id</span>]); <span class="hljs-keyword">return</span> json(<span class="hljs-regexp">$result</span>); } <span class="hljs-comment">/** * 删除指定资源 * * <span class="hljs-operator">@param</span> int $id * <span class="hljs-operator">@return</span> \think\Response */</span><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">delete</span><span class="hljs-number">(<span class="hljs-regexp">$id</span>)</span></span>{ <span class="hljs-regexp">$result</span> = Blogs::destroy(<span class="hljs-regexp">$id</span>); <span class="hljs-keyword">return</span> json(<span class="hljs-regexp">$result</span>); } }</span> ``` ``` > 作为学习用途,Blog接口没有添加数据验证,大家可以结合之前学习的模型验证功能自己添加。 由于`API`开发没有实际的页面显示,加上`REST`请求类型复杂,我们需要通过特殊的方式才能在开发过程中进行`REST`请求的测试。 ## `REST`请求测试 `REST`请求的测试方式有很多,下面介绍两种常用的方式: ### Postman 最方便的方法就是通过Postman来测试接口,给Chrome浏览器安装一个`postman`扩展,访问下面地址获取官方扩展:<https://www.getpostman.com/> ![](https://img.kancloud.cn/c2/02/c2027e464c794e6bf39a94223470cf4f_718x489.png) 或者直接中chrome的应用商店搜索`postman`(由于众所周知的原因,可能无法正常访问)。 ![](https://img.kancloud.cn/86/96/86962f47543eb18cdcc3346712837454_1193x692.png) 安装完成后会打开 ![](https://img.kancloud.cn/fa/9a/fa9a78b50c286ce8847624214d3f01b5_644x376.png) 点击`Postman`应用图标就可以打开应用。 如果是首次使用的话,会首先要求注册用户,完成后会进入主界面: ![](https://img.kancloud.cn/4f/2d/4f2dd98026038529641efe6e85f6b40a_1258x651.png) 下面我们就来测试下前面的`REST`应用的接口,测试之前选择对应的请求类型,并输入我们的接口地址,如果需要传入参数,则点击`Send`按钮之前的`Params`,依次输入`key`(参数名称)和`value`(参数值),然后点击`Send`按钮即可,下面依次测试博客的接口。 > #### 博客添加接口:post <http://tp5.com/blogs> ![](https://img.kancloud.cn/ef/57/ef576f89fb011b0ccdbfa25338f25c03_1258x699.png) 通过添加接口我们写入了两条数据。 > #### 博客读取接口:get <http://tp5.com/blogs/1> ![](https://img.kancloud.cn/bc/9b/bc9b803a441181ba7316a438d970a32c_1258x557.png) > #### 博客更新接口:put <http://tp5.com/blogs/1> ![](https://img.kancloud.cn/07/3f/073f9f52c5afe4bc75bd47f61ef1e812_1261x564.png) > #### 博客列表接口:get <http://tp5.com/blogs> ![](https://img.kancloud.cn/60/9a/609a77edf9cef0c484c4133b9fe8507c_1261x702.png) > #### 删除博客接口:delete <http://tp5.com/blogs/1> ![](https://img.kancloud.cn/14/34/1434933c67e2ca129acce2bdbc9d589a_1255x475.png) 如果某个接口出现错误,可以点击`Preview`查看错误页面。 ### `REST`请求伪装 除了使用`Postman`之外,可以通过一个`post`表单来伪装`REST`的请求类型。首先创建一个普通的`post`表单,把请求参数都作为表单的项目,并且在表单最后添加一个隐藏域`_method`,下面的表单模拟了删除博客的请求接口。 ``` <pre class="calibre18"> ``` <form method=<span class="hljs-string">"post"</span> <span class="hljs-operator"><span class="hljs-keyword">class</span>=</span><span class="hljs-string">"form"</span> action=<span class="hljs-string">"/blogs/1"</span>> <input <span class="hljs-operator"><span class="hljs-keyword">type</span>=</span><span class="hljs-string">"submit"</span> <span class="hljs-operator"><span class="hljs-keyword">class</span>=</span><span class="hljs-string">"btn"</span> value=<span class="hljs-string">" 删除 "</span>> <input <span class="hljs-operator"><span class="hljs-keyword">type</span>=</span><span class="hljs-string">"hidden"</span> name=<span class="hljs-string">"_method"</span> value=<span class="hljs-string">"DELETE"</span> /> </form> ``` ``` ## API调试 可以使用`ThinkPHP5.0`的`Trace`调试中的`Socket`调试功能来解决`API`开发的调试问题。 ### 环境安装 如果你首次使用,参考下面的安装办法进行`SocketLog`的安装。 > #### SocketLog安装方法 > > 首先,请在chrome浏览器上安装好插件。 > SocketLog首先需要安装chrome插件,Chrome插件[安装页面](https://chrome.google.com/webstore/detail/socketlog/apkmbfpihjhongonfcgdagliaglghcod) (需翻墙) > > 安装服务端(如果没有nodejs和npm 请首先安装,[安装参考](http://www.infoq.com/cn/articles/nodejs-npm-install-config)),运行下面指令: > > ``` > <pre class="calibre25"> > ``` > npm <span class="hljs-operator"><span class="hljs-keyword">install</span> -<span class="hljs-keyword">g</span> socketlog-<span class="hljs-keyword">server</span></span> > ``` > > ``` > > 安装完成后, 运行命令 > > ``` > <pre class="calibre25"> > ``` > socketlog-<span class="hljs-number">server</span> > ``` > > ``` > > 即可启动服务。 将会在本地起一个websocket服务 ,监听端口是1229 。 > > 如果想服务后台运行,使用: > > ``` > <pre class="calibre21"> > ``` > socketlog-<span class="hljs-number">server</span> > /dev/<span class="hljs-number">null</span> & > ``` > > ``` ### 浏览器设置 首次使用的时候,需要点击Chrome扩展进行如下设置: ![](https://img.kancloud.cn/aa/7d/aa7db2472ddeca57d914df97f90590f8_293x351.png) Client\_ID用于标识当前用户,注意不要冲突。 ### 应用配置 接下来,修改应用配置文件,修改如下参数: ``` <pre class="calibre18"> ``` <span class="hljs-string">'log'</span> => [ <span class="hljs-string">'type'</span> => <span class="hljs-string">'socket'</span>, <span class="hljs-string">'host'</span> => <span class="hljs-string">'localhost'</span>, <span class="hljs-string">'show_included_files'</span> => <span class="hljs-keyword">true</span>, <span class="hljs-string">'force_client_ids'</span> => [<span class="hljs-string">'slog_b6d7ef'</span>], <span class="hljs-string">'allow_client_ids'</span> => [<span class="hljs-string">'slog_b6d7ef'</span>], ], ``` ``` 该配置参数把日志类型设置为socket,所有的日志都会写到socket服务器。 如果你的`socket-server`服务器不是`localhost`,配置使用ip地址或者域名即可。 ### 远程调试 下面就可以进行远程调试了,你可以使用任何浏览器访问: ``` <pre class="calibre18"> ``` <span class="hljs-string">http:</span> <span class="hljs-comment">//tp5.com/v2/user/10</span> ``` ``` 然后在`Chrome`浏览器中打开`Console`,就可以随时查看该`API`应用的远程调试信息了。 ![](https://img.kancloud.cn/99/c2/99c2644e9937372e2c57141d37721c8b_967x231.png) 一旦有请求产生,就会自动刷新`Console`信息显示。 还可以支持异常和错误信息的记录,例如: ![](https://img.kancloud.cn/32/36/3236b520d879c3ec3faa471b61ecd8b9_979x151.png) 如果需要指定多个`client_id`进行调试,只需要配置`force_client_ids`和`allow_client_ids`为多个参数即可,例如: ``` <pre class="calibre18"> ``` <span class="hljs-string">'log'</span> => [ <span class="hljs-string">'type'</span> => <span class="hljs-string">'socket'</span>, <span class="hljs-string">'host'</span> => <span class="hljs-string">'localhost'</span>, <span class="hljs-string">'show_included_files'</span> => <span class="hljs-keyword">true</span>, <span class="hljs-string">'force_client_ids'</span> => [<span class="hljs-string">'slog_b6d7ef'</span>,<span class="hljs-string">'slog_abd89d'</span>], <span class="hljs-string">'allow_client_ids'</span> => [<span class="hljs-string">'slog_b6d7ef'</span>,<span class="hljs-string">'slog_abd89d'</span>], ], ``` ``` ## 安全建议 - 尽量采用HTTPS协议进行接口请求; - 重要的功能加密传输; - 做好接口的身份认证; - 对URL中的参数做好安全过滤; - 对接口请求做好请求速率限制; - 重要ID不透明处理; - 使用JSON格式返回数据; 详细可以参考 [《REST API 安全设计指南》](http://ihavenolimitations.xyz/kancloud/rest-api-design-safety)