起因

前些日子听到运营反馈:从管理后台生成的内容因没有妥善保存,造成丢失,事后查找起来麻烦。所以这里需要制作表单功能,提交生成的内容以及相关信息。并在前台以列表形式展示出来,类似于网站留言板功能。

先前按照视频学习过 Laravel ,这次将通过实战,重新复习部分内容,并记录下来。相关开发环境和新项目手脚架,可以在 初尝 Laravel 5.8 文中找到,这里不再累述。

创建数据库迁移文件

应充分考虑数据表结构,简单来说要保存有用字段。从前端界面来说,有:

  • 链接名称;
  • 链接实体,包含原链接、短链接、重定向链接三种;
  • 链接的类型,依用途区分不同类型;
  • 链接打开后的页面,这里简单地用 id 表示;

用表格表示所有数据库字段:

字段名字段类型描述
idint自增 ID
titlestring链接名称
typestring链接类型
short_urlstring经过短链接处理后的链接
page_idstring打开后的页面 id
long_urltextURL 可能会很长,使用 text 字段类型
redirect_urltext同 long_url

定义该数据表名称为 links ,并创建对应的 Model 和 数据库迁移文件

php artisan make:model Link -m

// created file in
// app/Link.php
// database/migrations/2020_04_12_131409_create_links_table.php

接下来将上面填写好的表格,以配置的形式写入迁移文件中,Artisan 助手已为我们补全了 idtimestamps 基础字段:

// database/migrations/2020_04_12_131409_create_links_table.php

class CreateLinksTable extends Migration
{
    public function up()
    {
        Schema::create('links', function (Blueprint $table) {
            $table->bigIncrements('id');
            // add table field start
            $table->string('title');
            $table->string('type');
            $table->string('short_url');
            $table->string('page_id');
            $table->TEXT('long_url');
            $table->TEXT('redirect_url');
            // add table field end
            $table->timestamps();
        });
    }

   public function down()
    {
        Schema::dropIfExists('links');
    }
}

定义完数据表结构和字段类型后,无需在数据库中手动创建这张表,将工作交给 artisan 助手来实现。

使用 php artisan migrate 即可将迁移文件应用到数据库中,它会在数据库中创建 links 数据表。

Migrating: 2020_04_12_131409_create_links_table
Migrated:  2020_04_12_131409_create_links_table (0.02 seconds)

数据库交互模型

经典 MVC 模型中使用 Model 层处理数据库读写,Laravel 提供了名叫 Eloquent ORM 的功能,使得我们在代码中可以简洁高效地操作数据库。

在先前创建迁移文件时,Links Model 已经存在,中指定空的 guarded 属性,表示无黑名单,任何数据经过这里都会被保留下来,进入数据库操作:

// app/Link.php

class Link extends Model
{
    protected $guarded = [];
}

就算使用无黑名单的配置,我们还是要在接口请求时做表单验证,确保数据准确性。

定义 Web API 接口

Web API 接口用于对接前端,也可简单地将所有字段映射为接口内容,当参数和关联字段过多时,会显得十分臃肿。

Laraval 框架路由默认在 routes 文件夹中, routes/api.php 定义非模板渲染的页面请求,在此定义一个 POST 请求,将它指向 LinkController 控制器里的 store 方法;

同时定义请求路径上的必填参数 linkType,用于区分数据中的链接类型

Route::post('link/{linkType}', 'LinkController@store');

最后框架将此路由解析为路径 //my.domain.name/api/link/deeplink

表单验证

继续创建路由中定义的 LinkController ,它会处理请求中的内容:

php artisan make:controller LinkController

Requests 类可以非常方便地助我们验证请求中的表单字段,所以创建名为 StoreLink 的类:

php artisan make:request StoreLink

接下来可以真正地开始编写逻辑代码,在 LinkController 中定义 store 函数:

use App\Http\Requests\StoreLink;
use App\Link;
use Illuminate\Http\Request;

class LinkController extends Controller
{

    public function store(StoreLink $request)
    {
        $data = $this->validate(request(), $request -> rules());
        $link = Link::create($data);
    }

}
注意 store 函数的参数 StoreLink $request ,并不需要给 store 传递任何参数,只需要知名方法参数类型,框架会自动查找相关依赖自动注入。

别忘了我们还有 StoreLink ,只需要指定字段类型和期望值,它可以帮我们验证表单字段:

class StoreLink extends FormRequest
{
    public function rules()
    {
        return [
            'page_id'      => 'required',
            'title'        => 'required',
            'type'         => 'required',
            'long_url'     => 'required',
            'short_url'    => 'string|nullable',
            'redirect_url' => 'string|nullable',
        ];
    }
}

还记得 Web API 定义的 link/{linkType} 请求路径吗?这里的链接类型是附属在路径名称中的,这样做的好处是减少一些请求字段。

由于 type 字段是依附在请求路径中的,表单验证只会获取请求实体的内容,由于缺少type 字段,则会出现校检错误的提示:

{
    "message": "The given data was invalid.",
    "errors": {
        "type": [
            "The type field is required."
        ]
    }
}

必须先把请求路径中的 linkType 取出并赋值到 $request 实体中的 type 字段,才能通过 FormRequest 的验证:

class StoreLink extends FormRequest
{
    public function prepareForValidation()
    {
        $this -> merge(['type' => $this -> linkType]);
    }
}

测试接口

使用 Postman 发出模拟 POST 请求,什么都不发送,提示表单验证不通过

{
    "message": "The given data was invalid.",
    "errors": {
        "page_id": [
            "The page id field is required."
        ],
        "title": [
            "The title field is required."
        ],
        "long_url": [
            "The long url field is required."
        ]
    }
}

但是某些值虽然通过校检,但是因为数据库设计错误,导致报错

"short_url": "",
'short_url'    => 'string|nullable',
SQLSTATE[23000]: Integrity constraint violation: 

1048 Column 'short_url' cannot be null 

(SQL: insert into `links` (`page_id`, `title`, `type`, `long_url`, `short_url`, `redirect_url`, `updated_at`, `created_at`) values (1, 测试, openpage, 1, ?, 重定向链接, 2020-05-16 20:03:06, 2020-05-16 20:03:06))

改进数据库

因为初期设计数据表时,没有考虑到 short_url 可能为空值,导致插入数据失败。

那么现在来修改数据表,Laravel 不建议我们直接操作数据库,像创建数据表一样,创建迁移文件

artisan make:migration modify_links_table

在里头重新声明我们要修改的数据表字段类型和附加值

Schema::table('links', function (Blueprint $table) {

    $table->TEXT('long_url')->nullable()->change();
    $table->string('short_url')->nullable()->change();
    $table->TEXT('redirect_url')->nullable()->change();
    
});

使用 php artisan migrate 应用到数据库中,现在 short_url 字段为空时,也能保存到数据表。

+----+-------+----------+----------+-----------+--------------+---------+---------------------+---------------------+
| id | title | type     | long_url | short_url | redirect_url | page_id | created_at          | updated_at          |
+----+-------+----------+----------+-----------+--------------+---------+---------------------+---------------------+
|  1 | 测试  | openpage | 1        | NULL      | 重定向链接   | 1       | 2020-05-16 20:33:47 | 2020-05-16 20:33:47 |
+----+-------+----------+----------+-----------+--------------+---------+---------------------+---------------------+
1 row in set (0.02 sec)

获取所有数据

除了把数据记录起来,还得有个展示的功能。

提供路由

Route::get('link/{linkType}', 'LinkController@index');

在控制器中可查询数据表内容,并按照时间倒序排列

public function index($linkType = '')
{
    $lists = DB::table('links')
        ->where('type', '=', $linkType)
        ->orderBy('created_at', 'desc')
        ->paginate(100);

    return $lists;
}

以 JSON 的形式输出

{
    "current_page": 1,
    "data": [
        {
            "id": 1,
            "title": "测试",
            "type": "openpage",
            "long_url": "1",
            "short_url": null,
            "redirect_url": "重定向链接",
            "page_id": "1",
            "created_at": "2020-05-16 20:33:47",
            "updated_at": "2020-05-16 20:33:47"
        }
    ],
    "first_page_url": "http://api.misaka.im/api/link/openpage?page=1",
    "from": 1,
    "last_page": 1,
    "last_page_url": "http://api.misaka.im/api/link/openpage?page=1",
    "next_page_url": null,
    "path": "http://api.misaka.im/api/link/openpage",
    "per_page": 100,
    "prev_page_url": null,
    "to": 1,
    "total": 1
}

总结

在岗位上只做自身技术范围的事情,显得太被动。在 Web 应用开发的过程中,前后端技术紧密关联。

制定开发方案前,需要充分了解用户需求,在有限的时间内选择最优的解决方案。

标签: none

添加新评论