李炎恢 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 协议》,转载必须注明作者和本文链接
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《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

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

2年前 评论
chowjiawei (作者) 2年前
Alcohol (楼主) 2年前
lun1bz 2年前
lun1bz 2年前
chowjiawei (作者) 2年前
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

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

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

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

2年前 评论

这是李炎恢的原版代码吗

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

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!