SEO友好URL
(Search Engine Optimization):汉译为搜索引擎优化。
一个SEO友好的URL格式可以大大提升网站的爬取率以及索引率。此外,在URL这个地方部署关键词可以提升网页的相关性。
1.创建观察者类 确保 在app目录下创建Observers 文件夹
然后我们在Observers目录中创建 TopicObserver.php类文件
知识点:
在 Laravel 的世界中,你对 Eloquent 大多数操作都会或多或少的触发一些模型事件,Laravel 事先已经定义好了 10 个模型事件以供我们使用,它们分别是:
creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored。
creating 是在数据创建之前触发,created 是数据创建之后触发
updating 是在数据更新之前触发,updated 是在数据更新之后触发
deleting 是在删除数据之前触发,deleted是在删除数据之后触发
saving 是在保存数据之前触发 ,saved 是在数据创建之后触发
需要注意的是 当模型不存在,需要新增的时候,依次触发的顺序则是
saving -> creating -> created -> saved
当模型已存在,不是新建的时候,依次触发的顺序是:
saving -> updating -> updated -> saved
saving 和 saved 则会在 Eloquent 实例的 original 数组真值更改前后触发
2.去百度翻译 API:注册个百度翻译 获取api 的key
接口作用:用于把我们标题或关键字翻译成英文。
这里逻辑是:调用接口翻译成英文,要是调用或翻译失败我们就将标题翻译成拼音,然后生成slug字段内容。
所以这里需要两个扩展包:
3.安装Guzzle扩展包:发送 http 请求的(我这里直接下载最新的,没指定版本) ,这里用于对接百度翻译 API接口
composer require "guzzlehttp/guzzle"
4.安装依赖 PinYin
$ composer require "overtrue/pinyin"
5.修改配置
config/services.php
'baidu_translate' => [
'appid' => env('BAIDU_TRANSLATE_APPID'),
'key' => env('BAIDU_TRANSLATE_KEY'),
],
.env
BAIDU_TRANSLATE_APPID=201703xxxxxxxxxxxxx
BAIDU_TRANSLATE_KEY=q0s6axxxxxxxxxxxxxxxxx
6.封装一个翻译类
app/Handlers/SlugTranslateHandler
<?php
namespace App\Handlers;
use GuzzleHttp\Client;
use Overtrue\Pinyin\Pinyin;
use Illuminate\Support\Str;
class SlugTranslateHandler
{
public function translate($text)
{
//实例化 HTTP客户端
$http = new Client;
//初始化配置信息
$api = 'https://fanyi-api.baidu.com/api/trans/vip/translate';
$appid = config('services.baidu_translate.appid');
$key = config('services.baidu_translate.key');
$salt = time();
// 如果没有配置百度翻译,自动使用兼容的拼音方案
if (empty($appid) || empty($key)) {
return $this->pinyin($text);
}
// 根据文档,生成 sign
// http://api.fanyi.baidu.com/api/trans/product/apidoc
// appid+q+salt+密钥 的MD5值
$sign = md5($appid. $text . $salt . $key);
//请求参数
$query = http_build_query([
"q" => $text,
"from" => "zh",
"to" => "en",
"appid" => $appid,
"salt" => $salt,
"sign" => $sign,
]);
//发送 HTTP Get 请求
$response = $http->get($api.$query);
$result = json_decode($response->getBody(), true);
// 获取结果,如果请求成功,dd($result) 结果如下:
// array:3 [▼
// "from" => "zh"
// "to" => "en"
// "trans_result" => array:1 [▼
// 0 => array:2 [▼
// "src" => "XSS 安全漏洞"
// "dst" => "XSS security vulnerability"
// ]
// ]
// ]
//获取翻译结果
if (isset($result['trans_result'][0]['dst'])) {
return Str::slug($result['trans_result'][0]['dst']);
} else {
// 如果百度翻译没有结果,使用拼音作为后备计划。
return $this->pinyin($text);
}
}
public function pinyin($text)
{
return Str::slug(app(Pinyin::class)->permalink($text));
}
}
7.用Redis队列实现异步处理任务 (这样就不影响用户创建话题帖子了!)
7.1.Composer 安装Redis依赖:
composer require "predis/predis"
修改环境变量QUEUE_DRIVER 的值为 redis:
.env
# SESSION_DRIVER=file
SESSION_DRIVER=redis
7.2.处理队列中任务失败的情况
使用 queue:failed-table 命令来创建 failed_jobs 表的迁移文件:
$ php artisan queue:failed-table
会新建 database/migrations/{timestamp}_create_failed_jobs_table.php 文件
最新版本laravel10默认已有这个文件了,使用 以下 命令生成 failed_jobs 表就好了:
$ php artisan migrate
3.生成任务类
使用以下 Artisan 命令来生成一个新的队列任务:
$ php artisan make:job TranslateSlug
该命令会在 app/Jobs 目录下生成一个新的类:
app/Jobs/TranslateSlug.php
编辑任务内容(调用翻译接口并异步更新slug字段):
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\Topic;
use App\Handlers\SlugTranslateHandler;
use Illuminate\Support\Facades\DB;
class TranslateSlug implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $topic;
/**
* Create a new job instance.
*/
public function __construct(Topic $topic)
{
// 队列任务构造器中接收了 Eloquent 模型,将会只序列化模型的 ID
$this->topic = $topic;
}
/**
* Execute the job.
*/
public function handle(): void
{
$slug = app(SlugTranslateHandler::class)->translate($this->topic->title);
DB::table('topics')->where('id', $this->topic->id)->update(['slug' => $slug]);
}
}
8.在我们创建的观察类(即Topic 模型监控器)app/Observers/TopicObserver.php
中用 队列执行的方式 调用 Slug 翻译
namespace App\Observers;
use App\Jobs\TranslateSlug;
...
/**
* 保存进数据库以后(因为创建帖子的时候,要创建完成后才有id)
*/
public function saved(Topic $topic)
{
//使用队列 实现异步修改slug 从而不影响用户创建话题
if (!$topic->slug) {
// 推送任务到队列
dispatch(new TranslateSlug($topic));
}
}
9.修改一下路由
Route::get('topics/{topic}/{slug?}','TopicsController@show')->name('topics.show')
在模型 Topic 模型中创建link() 方法
<?php
namespace App\Models;
class Topic extends Model{
.
.
.
public function link($params = [])
{
return route('topics.show', array_merge([$this->id, $this->slug], $params));
}}
参数 $params 允许附加 URL 参数的设
强制跳转
当文章有 Slug 的时候,我们希望用户一直使用正确的、带着 Slug 的链接来访问。我们可以在控制器中对 Slug 进行判断,当条件允许的时候,我们将发送 301 永久重定向指令给浏览器,跳转到带 Slug 的链接:
app/Http/Controllers/TopicsController.php
后面的slug 我们可以看到虽然是生成了slug 但是用户在浏览器中输入的如果不是我们生成的slug会导致输入任意slug都可以访问到我们的文章
这是我们不愿意看到的,我们要确保已经生成的slug url 必须是和url中的是一致匹配的,如果用户恶意输入错误的slug 我们将修正这个url的slug
我们在app/Http/Controllers/TopicsController.php 文件中 路由定义的show() 方法 添加如下限制
public function show(Request $request, Topic $topic)
{
// 防止用户恶意输入
if ( ! empty($topic->slug) && $topic->slug != $request->slug) {
return redirect($topic->link(), 301);
}
return view('topics.show', compact('topic'));
}
ok了