起因
前些日子听到运营反馈:从管理后台生成的内容因没有妥善保存,造成丢失,事后查找起来麻烦。所以这里需要制作表单功能,提交生成的内容以及相关信息。并在前台以列表形式展示出来,类似于网站留言板功能。
先前按照视频学习过 Laravel ,这次将通过实战,重新复习部分内容,并记录下来。相关开发环境和新项目手脚架,可以在 初尝 Laravel 5.8 文中找到,这里不再累述。
创建数据库迁移文件
应充分考虑数据表结构,简单来说要保存有用字段。从前端界面来说,有:
- 链接名称;
- 链接实体,包含原链接、短链接、重定向链接三种;
- 链接的类型,依用途区分不同类型;
- 链接打开后的页面,这里简单地用 id 表示;
用表格表示所有数据库字段:
字段名 | 字段类型 | 描述 |
---|---|---|
id | int | 自增 ID |
title | string | 链接名称 |
type | string | 链接类型 |
short_url | string | 经过短链接处理后的链接 |
page_id | string | 打开后的页面 id |
long_url | text | URL 可能会很长,使用 text 字段类型 |
redirect_url | text | 同 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 助手已为我们补全了 id
和 timestamps
基础字段:
// 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 应用开发的过程中,前后端技术紧密关联。
制定开发方案前,需要充分了解用户需求,在有限的时间内选择最优的解决方案。