李炎恢 Laravel API 接口学习笔记(基于 Laravel Sail 环境)

0. 学前准备:#

1. 前言#

这是一节短小精悍的 Laravel API 开发入门课,很基础,但对我来说也很有用。
这个课程在时间顺序上是在《李炎恢 / Laravel / 核心篇 / PHP 框架 / 阶段一》课程之后的,所以如果你跟我一样只看这门课程的话,可能会遇到没有数据库数据的问题,但是这个问题容易解决,用 Laravel 自带的数据填充功能填充一下就搞定。
另外,与视频教程相比,我增加了两点内容:

  • 教程里面没有编写 update 方法,我按照自己的理解补充了这部分内容,如果大家有更好的写法,不妨在评论区 show you code
  • 我按照视频里面的编码,发现储存到数据库里面的用户密码是明文储存,没有加密,所以我改动了这部分内容
    建议大家看一看原视频哦。
    还有,想知道如何使用 Laravel Sail 环境请参考:历时三天,成功搭建 Laravel Sail 环境

2. 创建项目#

curl -s https://laravel.build/yanhui | bash

如上所示,我创建一个名为 yanhui 的 Laravel 项目。
在 Terminal 中启动项目:

./vendor/bin/sail up

如图所示:

此时,我用浏览器打开 http://localhost/ ,就可以看到以下画面,说明项目已经成功创建并且启动(这个画面我们不需要更改):

3. 创建用于 API 操作的资源控制器和对应路由#

先在 Terminal 终端配置一下 Sail 命令,让它变得简短一点:

alias sail='bash vendor/bin/sail'

然后,使用以下命令创建 API 控制器

sail artisan make:controller UserController --api

--api 参数的意义是,从控制器中排除 createedit 方法。
此处出现在我终端里面的 爆红 提示,是因为我在之前学习的时候已经创建过同名控制器,所以我再次演示就会这个提示,大家无视就好。
最后,我输入 code .,使用 VS code 打开项目文件夹。
整个流程如下图所示:

至此,控制器已经创建好,接下来是对应路由,在创建路由之前,我们我们可以使用 sail artisan route:list 命令来查看一下,当前项目的路由列表是怎样的(如果你是新建的项目,应该会看到如下所示的画面):

接下来,我们打开 routes\api.php 文件,用一下代码,创建资源路由(文件原本的代码我们先不改动,注意顶部引入控制器):

<?php
use App\Http\Controllers\UserController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::middleware('auth:api')->get('/user', function (Request $request) {
    return $request->user();
});
Route::apiResource('user',UserController::class);

可以看到,与原文件相比,我只添加了两句代码:

use App\Http\Controllers\UserController;
Route::apiResource('user',UserController::class);

此时,我们再次在终端中输入 sail artisan route:list 命令,可以看到如下画面:
然后,我们来测试一下:

app\Http\Controllers\UserControllers.php 中,我们修改 index 方法:

public function index()
    {
        return '你好,Laravel~';
    }

然后我们用浏览器打开 http://localhost/api/user
可以看到如下画面:

最后,我们用专业的接口调试软件 Postman 打开 http://localhost/api/user
可以看到以下画面:

至此,控制器和路由创建并测试成功。

4. 创建一个专门用于生成 API 的控制类,然后继承并调用它#

在终端中使用命令创建控制器:

sail artisan make:controller ApiController

在生成的控制器中,我们编写一个 create 方法:(文件路径:app\Http\Controllers\ApiController.php)

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ApiController extends Controller
{
    protected function create($msg = '', $code = 200, $data = [])
    {
        $result = [
            // 返回状态码
            'code'=>$code,
            // 返回提示信息
            'msg'=>$msg,
            // 返回数据
            'data'=>$data,
        ];
        return response($result);
    }
}

然后,我们修改 UserController.php 文件(文件路径:app\Http\Controllers\UserController.php)
使其继承 ApiController 而不是原本的 Controller,这样我们就可以很自然地调用 create 方法,来生成标准的 API 返回信息。
详情如下:

class UserController extends ApiController
{
    public function index()
    {
        return $this->create('请求成功',200,'你好,Laravel~');
    }

此时,我们用 Postman 再次发送请求,可以看到如下画面,说明控制类创建成功。

5. 数据列表和分页#

填充用户数据#

在本小节,我们将真正编写第一个控制器方法的具体逻辑,但是,对于没有看前置课程的同学来说,我们也将面临一个问题:我们的数据库里面,没有任何数据
好在,这个问题我们可以用 Laravel 的数据填充功能来解决,至于填充的原理,我就不再赘述,只讲怎么做,想进一步了解原理的同学可以阅读 Laravel 文档,或者参考本社区 《L01 Laravel 教程》的第 8 章第 4 小节。
数据填充第一步:创建 seeder 类

sail artisan make:seeder UserSeeder

生成的 UserSeeder 文件位于:database\seeders\UserSeeder.php
让我们编辑其中的 run 方法,如下所示:

<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class UserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        User::factory(50)->create();
    }
}

User::factory(50)->create(); 这句代码会调用用户工厂方法,为我们生成 50 条用户数据。
用户工厂,Laravel 默认自带,文件位于 database\factories\UserFactory.php,保持默认即可,暂时不需要做修改。
然后我们修改 DatabaseSeeder.php 文件中的 run 方法,让其调用我们创建的 UserSeeder 类:
文件位置:database\seeders\DatabaseSeeder.php

<?php
namespace Database\Seeders;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        Model::unguard();
        $this->call(UserSeeder::class);
        Model::reguard();
    }
}

现在,让我们回到终端中,使用以下命令,生成 Laravel 默认的用户数据表:

sail artisan migrate

接着用以下命令,填充数据

sail artisan db:seed

终端画面如下所示:

至此,数据填充完成,如果你用 Navicat 等工具连接并打开数据库中的 users 表,就可以看到 Laravel 为我们生成的 50 条用户数据,这里就不演示了。

编写 UserController 控制器的 index 方法#

数据库有了数据,我们接下来编写的控制器方法才有意义,首先我们来看第一个控制器方法:
文件位置:app\Http\Controllers\UserController.php

public function index()
    {
        // 数据获取
        // 返回数据

        $data = User::select('id', 'name', 'email')->simplePaginate(10); // 简单分页
        return $this->create('数据获取成功', 200, $data);
    }

可以看到,我通过查询构造器,从 users 表的每条记录中选出了三个字段,并把所有的记录进行简单分页,每页展示 10 条数据。
用 Postman 进行调试,可以看到如下画面:

可以看到,之前填充的数据,被我们从数据库调出来。
index 方法还有一些其他的写法,这里仅做记录,有兴趣的同学可以自己调试以下写法:

// 获取全部可显示字段
$data = User::get();
// 获取指定字段 
$data = User::select('id','name','email')->get();
// 获取指定字段并分页
$data = User::select('id','name','email')->paginate(10);

至此,查询所有数据并且分页的方法,编写完成。

6. 配置一个 404 的 HTTP 异常,并且用 JSON 格式返回(覆盖系统自带的 404 提示)#

Larave 框架自带 404 提示,为什么我们还有重新写一个呢?
因为自带的 404 提示不符合 API 格式,这你让前端很难做事的,所以,我们要重写一个符合 API 格式的 404 提示,覆盖原有的提示。
操作起来也简单,两步走:

  1. 在 resources/views 目录下创建 errors 文件夹
  2. 在 resources/views/errors 目录下创建 404.blade.php 文件,并编写返回的内容

你可以手动创建相关目录和文件,也可以像我一样,在终端中使用命令来创建:

mkdir resources/views/errors && touch resources/views/errors/404.blade.php

同样,因为我在之前已经创建过,所以,此时演示的时候,提示文件夹已存在。

接下来我们开始编写 resources/views/errors 目录下的 404.blade.php 文件,很简单,以下就是文件的全部内容:

<?php
$result = [
    // 返回状态码
    'code' => 404,
    // 返回提示信息
    'msg' => '资源不存在',
    // 返回数据
    'data' => [],
];
echo json_encode($result);

接下来我们在 Postman 里面调试一下,填入一个不存在的路径,看看是不是我们自己编写的 404 提示(如果各位的信息显示有点乱的话,可以注意我标记红框的位置):

7. 展示单个数据 API#

展示单个数据,对应的是控制器的 show 方法,通过传入的 $id 来返回对应的用户信息。
但是根据 web 开发工程师第一准则:永远不要相信用户输入的数据
所以我们要考虑更多的东西,比如:

  • 我们要求用户传入的参数是数字,但是用户扔给我们一个单词,我们该怎么办?
  • 现在我们数据库里面数值的范围在 1 - 50 之内,用户给我们抛个 100 过来,超出了我们的数据范围,我们又该返回什么样的信息呢?

所以,show 方法编写如下,具体的思路请看注释部分文字:
文件位置: app\Http\Controllers\UserControllers.php

public function show($id)
    {
        // 获取数据
        // 数据存在 返回成功信息
        // 数据不存在
        // 参数超出范围导致数据不存在
        // 参数类型错误导致数据不存在

        $data = User::select('id', 'name', 'email')->find($id);

        if (isset($data)) {
            return $this->create('数据获取成功', 200, $data);
        } else {
            if (is_numeric($id)) {
                return $this->create('用户不存在', 204, []);
            } else {
                return $this->create('id 参数错误', 400, []);
            }
        }
    }

Postman 调试如下(正确参数):

非数字参数:

超范围参数:

8. 新增数据 API#

新增数据对应的控制器方法,是 store 方法,这部分代码我写的与原视频里面有些不同,主要区别就是在讲数据存入数据库的时候,我将密码用 md5() 函数进行加密,虽然这种方式也比较朴素,但是,起码数据库里面的数据不再是赤裸裸的 123456 了,这是什么?这就是进步啊~
文件位置: app\Http\Controllers\UserControllers.php
编码如下:

public function store(Request $request)
    {
        // 接收数据
        $data = $request->all();
        // 数据验证
        $validator = Validator::make($data, [
            'name' => 'required|min:2|max:50',
            'email' => 'required|min:2|max:50|email',
            'password' => 'required|min:6|max:50',
        ]);
        if ($validator->fails()) {
            return $this->create($validator->errors(), 400);
        } else {
            $newUser = User::create([
                'name' => $request->name,
                'email' => $request->email,
                'password' => md5($request->password),
            ]);
            $newUser = User::select('id', 'name', 'email')->find($newUser['id']);
            return $this->create('用户创建成功', 200, $newUser);
        }
    }

关于数据验证代码的解析请看原视频,或者参考本社区 《L01 Laravel 教程》的第 8 章第 4 小节。或者参考本社区 《L01 Laravel 教程》的第 6 章,此处点到为止。
以下是 Postman 调试环节,请注意我标红框的部分,都是细节哦:

9. 删除数据 API#

有增加就有删除,就连后端人员的自嘲 —— 增删改查工程师 的头两个字,也是
删除数据对应的控制器方法,是 destroy 方法,其逻辑与增加数据很相似。
文件位置: app\Http\Controllers\UserControllers.php
编码如下:

public function destroy($id)
    {
        // 获取数据
        // 数据存在,删除
        // 数据不存在
        // 参数超出范围导致数据不存在
        // 参数类型错误导致数据不存在
        $data = User::find($id);
        if (isset($data)) {
            if ($data->delete()) {
                return $this->create('用户删除成功', 200);
            }
        } else {
            if (is_numeric($id)) {
                return $this->create('用户不存在,无法删除', 204, []);
            } else {
                return $this->create('id 参数错误', 400, []);
            }
        }
    }

但是,要注意一点,在实际的生产环境中,无论我们多么想删除数据库里面的数据,事实上我们都不会删,因为我们受过专业的训练(毒打)。
那么,如何让存在数据里面的数据,看起来像是 “被删除” 的样子呢?
答案是增加一个 状态字段,如果一条数据的状态字段的值是 1,代表这是一条正常的记录;如果是 -1 或者其它值,代表这是一条已经被删除的记录,没有权限的人是查不到这条记录的。
由此,引申出 web 工程师第二准则:永远不要删除生产环境的数据。(除非你想搞破坏)
接下来是 Postman 调试(就删除我们刚刚创建的第 51 号用户好了):

此时如果我们打开数据库,发现第 51 条记录已经没有了,再次提示,这只是在本地开发环境的学习过程,在生产环境中,任何与删除有关的动作都是很危险的,一定要慎之又慎。

10. 修改数据 API#

在原视频中,是没有这部分内容的,但是,出来混,要讲信用,说了增删改查工程师,就要增删改查,一个都不能少。
以下是我通过自己的理解写出来的,还是那就说话,如果有更好的写法,请在评论区 show you code
文件位置: app\Http\Controllers\UserControllers.php
编码如下:

public function update(Request $request, $id)
    {
        // 接收数据
        // 验证数据
        // 查找用户
        // 如果用户不存在
        // 更新并返回信息
        $data = $request->all();
        $validator = Validator::make($data, [
            'name' => 'required|min:2|max:50',
            'password' => 'required|min:6|max:50',
        ]);
        if ($validator->fails()) {
            return $this->create($validator->errors(), 400);
        }
        $user = User::find($id);
        if (isset($user)) {
            if ($user->update([
                'name' => $request->name,
                'password' => md5($request->password),
            ])) {
                return $this->create('用户更新成功', 200);
            }
        } else {
            if (is_numeric($id)) {
                return $this->create('用户不存在,无法更新', 204, []);
            } else {
                return $this->create('id 参数错误', 400, []);
            }
        }
    }

代码思路请看注释文字,以下是 Postman 调试时间:
查找第一个用户的信息如下:

然后调用 api,修改 1 号用户的 name 和 password 信息:

最后,再次查看,可以看到,已经修改成功:

至此,整个课程笔记已经结束。
因为之前的代码是分块编写并解释,观感并不是很好,所以在附录部分,贴上 app\Http\Controllers\UserControllers.php 的完整代码,供大家参考,以免出错。

附录#

完整的 app\Http\Controllers\UserControllers.php 如下:

<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Auth\Events\Validated;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class UserController extends ApiController
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        // 数据获取
        // 返回数据

        $data = User::select('id', 'name', 'email')->simplePaginate(10); // 简单分页
        return $this->create('数据获取成功', 200, $data);

        // 数据获取的其它写法举例
        // 获取全部可显示字段
        // $data = User::get();
        // 获取指定字段 
        // $data = User::select('id','name','email')->get();
        // 获取指定字段并分页
        // $data = User::select('id','name','email')->paginate(10);
    }
    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        // 接收数据
        $data = $request->all();
        // 数据验证
        $validator = Validator::make($data, [
            'name' => 'required|min:2|max:50',
            'email' => 'required|min:2|max:50|email',
            'password' => 'required|min:6|max:50',
        ]);
        if ($validator->fails()) {
            return $this->create($validator->errors(), 400);
        } else {
            $newUser = User::create([
                'name' => $request->name,
                'email' => $request->email,
                'password' => md5($request->password),
            ]);
            $newUser = User::select('id', 'name', 'email')->find($newUser['id']);
            return $this->create('用户创建成功', 200, $newUser);
        }
    }
    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        // 获取数据
        // 数据存在 返回成功信息
        // 数据不存在
        // 参数超出范围导致数据不存在
        // 参数类型错误导致数据不存在

        $data = User::select('id', 'name', 'email')->find($id);
        if (isset($data)) {
            return $this->create('数据获取成功', 200, $data);
        } else {
            if (is_numeric($id)) {
                return $this->create('用户不存在', 204, []);
            } else {
                return $this->create('id 参数错误', 400, []);
            }
        }
    }
    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        // 接收数据
        // 验证数据
        // 查找用户
        // 如果用户不存在
        // 更新并返回信息
        $data = $request->all();
        $validator = Validator::make($data, [
            'name' => 'required|min:2|max:50',
            'password' => 'required|min:6|max:50',
        ]);
        if ($validator->fails()) {
            return $this->create($validator->errors(), 400);
        }
        $user = User::find($id);
        if (isset($user)) {
            if ($user->update([
                'name' => $request->name,
                'password' => md5($request->password),
            ])) {
                return $this->create('用户更新成功', 200);
            }
        } else {
            if (is_numeric($id)) {
                return $this->create('用户不存在,无法更新', 204, []);
            } else {
                return $this->create('id 参数错误', 400, []);
            }
        }
    }
    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        // 获取数据
        // 数据存在,删除
        // 数据不存在
        // 参数超出范围导致数据不存在
        // 参数类型错误导致数据不存在
        $data = User::find($id);
        if (isset($data)) {
            if ($data->delete()) {
                return $this->create('用户删除成功', 200);
            }
        } else {
            if (is_numeric($id)) {
                return $this->create('用户不存在,无法删除', 204, []);
            } else {
                return $this->create('id 参数错误', 400, []);
            }
        }
    }
}

完结,撒花,感谢阅读。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 3
chowjiawei

写的挺好,但是你的结果返回,都是用自定义的,所以个人看起来代码会繁杂,
代码命名也不够清晰,204 也是用错了,建议你的接口返回使用 psr 规范,删除 create 方法,而且怎么能把返回消息的封装叫做 create?
如果是我 以删除为例会这样子处理:

public function destroy($userId)
    {
        $user = User::findOrFail($userId);
        $user->delete();
        return response('', 204); 
    }

findOrFail 找不到会帮你抛出异常,删除无返回内容,返回前端 204 即可
如果前端觉得难,必须要有固定内容的返回给他,那么说明你的前端需要技术培训,不要为了前端的技术薄弱迁就你的代码


protected function validateForm(Request $request)
{
  $request->validate([
            'name' => 'required|min:2|max:50',
            'email' => 'required|min:2|max:50|email',
            'password' => 'required|min:6|max:50',
        ]);
}

public function store(Request $request)
    {
       $this->validateForm($request);
       $newUser = User::create([
           'name' => $request->name,
           'email' => $request->email,
           'password' => md5($request->password),
       ]);

       return $newUser;

    }

创建用户验证剥离开,你都创建了可以直接返回,又去查询一次浪费性能,如果你要实现只给前端返回某些东西,model hidden 住即可,不用各个方法要返回,都要重复代码

还有的问题是
public function update(Request $request, $id)

这样子不好,请这样子
public function update( $id,Request $request)

<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Auth\Events\Validated;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class UserController extends ApiController

改为:

<?php
namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Auth\Events\Validated;
use Illuminate\Support\Facades\Validator;

class UserController extends ApiController

即使是练习,也要保持住规范,让规范成为一种习惯

3年前 评论
chowjiawei (作者) 3年前
Alcohol (楼主) 3年前
lun1bz 3年前
lun1bz 3年前
chowjiawei (作者) 3年前
chowjiawei

写的挺好,但是你的结果返回,都是用自定义的,所以个人看起来代码会繁杂,
代码命名也不够清晰,204 也是用错了,建议你的接口返回使用 psr 规范,删除 create 方法,而且怎么能把返回消息的封装叫做 create?
如果是我 以删除为例会这样子处理:

public function destroy($userId)
    {
        $user = User::findOrFail($userId);
        $user->delete();
        return response('', 204); 
    }

findOrFail 找不到会帮你抛出异常,删除无返回内容,返回前端 204 即可
如果前端觉得难,必须要有固定内容的返回给他,那么说明你的前端需要技术培训,不要为了前端的技术薄弱迁就你的代码


protected function validateForm(Request $request)
{
  $request->validate([
            'name' => 'required|min:2|max:50',
            'email' => 'required|min:2|max:50|email',
            'password' => 'required|min:6|max:50',
        ]);
}

public function store(Request $request)
    {
       $this->validateForm($request);
       $newUser = User::create([
           'name' => $request->name,
           'email' => $request->email,
           'password' => md5($request->password),
       ]);

       return $newUser;

    }

创建用户验证剥离开,你都创建了可以直接返回,又去查询一次浪费性能,如果你要实现只给前端返回某些东西,model hidden 住即可,不用各个方法要返回,都要重复代码

还有的问题是
public function update(Request $request, $id)

这样子不好,请这样子
public function update( $id,Request $request)

<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Auth\Events\Validated;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class UserController extends ApiController

改为:

<?php
namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Auth\Events\Validated;
use Illuminate\Support\Facades\Validator;

class UserController extends ApiController

即使是练习,也要保持住规范,让规范成为一种习惯

3年前 评论
chowjiawei (作者) 3年前
Alcohol (楼主) 3年前
lun1bz 3年前
lun1bz 3年前
chowjiawei (作者) 3年前

关于前端,其实我们算是菜鸡互啄

3年前 评论

这是李炎恢的原版代码吗

3年前 评论
Alcohol (楼主) 3年前