slug 与异步获取 slug (队列任务) 的笔记分享
总结
-
今天学习的composer包
- Guzzle:就是个发送 http 请求的
composer require "guzzlehttp/guzzle:~6.3"
请求接口的,具体请求方式就是拼好接口地址,然后实例化 GuzzleHttp\Client; 类
$http = new Client;
,然后用这个类请求数据$response = $http->get('接口地址');
此时 $response 是响应结果,可以用$response->getBody()
读取响应体。
http_build_query(给个键值对数组)
=> 可以将这个数组转成键=值&键=值
这样的格式,多用于请求接口时配置参数- PinYin:国内大神写的汉字转拼音
composer require "overtrue/pinyin:~3.0"
调用方法
return str_slug(app(Pinyin::class)->permalink(要翻译的文字));
比如 “汉字” 返回的就是 “han-zi”
str_slug()
(把 'han zi' 转为 'han-zi') 是 laravel 提供的辅助函数,包括之前用过的str_random(n)
(生成随机 n 位的字符串)app(XxxClass::class)
可以理解为直接实例化了一个 XxxClass 。这是 laravel 的 “服务容器” 功能。(现在还太懂,记用法)- Horizon:监控队列任务的
composer require "laravel/horizon:~1.0"
安装好后必须生成配置文件和模板以及样式文件
php artisan vendor:publish --provider="Laravel\Horizon\HorizonServiceProvider"
开启监控
php artisan horizon
(此时命令行里面就可以看得到在后台处理的队列任务的进展清空),访问项目域名/horizon
可以看得到图形化界面的队伍任务管理器。 - Guzzle:就是个发送 http 请求的
-
slug
主要目的就是为了让 TopicsController@show 帖子详情的地址变成
项目网站/topics/帖子主键/slug
,有助于 SEO 优化。- 生成 slug 主要是用 百度翻译API,需要注册,得到 AppID 和 key。
- 百度翻译的接口地址是
http://api.fanyi.baidu.com/api/trans/vip/translate?q=【你要翻译的文字】&from=【要被翻译的文字的语言】&to=【要翻译成什么语言】&appid=【你注册的appid】&slat=【一个加盐代码建议用当前时间戳time()】&sign=【用md5加密(你的appid+要翻译的文字+加盐代码+你注册的key)】
- 是用 Handler 实现的翻译 title 然后生成 slug 的功能,详情参考 app/Handlers/SlugTranslateHandler.php。(大致历程就是配置好接口,先看不能访问接口,能访问就请求接口,不能就用 PinYin 生成 slug,然后看请求结果,有结果就用翻译结果,没有结果就继续用拼音,然后 translate() 方法最后返回的文字的格式就是 'fan-yi-cheng-zhe-yang')
其实 Laravel 是没有 Handler 这一说的,不过教程让我们创建了这样一个文件夹 Handlers ,里面就放处理这些杂七杂八的东西的类文件,可以借鉴。
- 上面的 Handler 的结果是返回一个字符串,现在需要在模型监视器 TopicObserver 中实现逻辑:在发帖或者编辑帖子的过程中,翻译 title 为英文,存于数据表的 slug 字段。不过这一部分可以忽略了,因为后面需要用队列去完成这个功能。(此时插入 slug 字段是同步的,非常不好)
- 上面的逻辑已经生成了 slug 字段的值,现在需要在浏览器的地址栏中体现出来以便提升 SEO 效率,那么首先需要的就是配置路由:在之前的开发过程中系统帮我们设置了一个资源路由
Route::resource('topics', ...
它里面带关于 TopicsController@show 的路由,所有我们从它后面的only => [...]
里面把 show 去掉,自己定义 show 路由Route::get('topics/{topic}/{slug?}', 'TopicsController@show')->name('topics.show');
- 路由定义好了,但是此时很多地方需要修改路由地址,加上 $slag ,非常麻烦,所以在 Topic 模型里面定义了一个生成链接的函数 Topic@link
public function link($params = []) { return route('topics.show', array_merge([$this->id, $this->slug], $params)); }
这里确实就是返回一个路由地址,
route('路由名称', [参数1=>值1, 参数2=>值2])
,然后我们把参数地址用array_merge()
的方法将当前对象的 id (对应{topic}) 和 slug (对应{slug?}) 组成了路由参数。
array_merge
就是将多个元素组成一个数组- 最后在控制器层和视图层中,不论是
return redirect()...
还是<a href="...">
这里都直接调用当前对象 $topic 的方法 link()$topic->link()
即可生成路由地址在控制器层中,用
return redirect()->to(路由链接)
可以跳转
- 还需要完善一下 TopicsController@show 方法:在方法中增加一条判断,强制将 url 地址合理化
因为在网页访问的时候,其实只依赖参数 {topic} ,说白了就是 {slug?} 用户可以在浏览器地址里面随便改,这样并不严谨,所以在方法中判断一次,当出现用户修改 slug (和数据库里面的 slug 不一样) 的时候,强制跳转把浏览器地址改成一样的
// 当有 slug 并且当数据库中存的这篇帖子的 slug 不等于 请求的slug if(isset($topic->slug) && ($topic->slug != $request->slug)) { return redirect($topic->link(), 301); //301 强制跳转,为了防止用户改浏览器地址最后面翻译出来的 slug }
(Request $request)
在参数列表中实例化请求类之后,$request->参数名
一定可以调出参数值。 -
队列任务
这是一个新的知识重点。今天用队列任务实现了一个异步请求接口获得帖子的 slug 的功能。
- 安装和配置:
- 装 predis (php redis)
composer require "predis/predis:~1.0"
- 配置 .env
QUEUE_DRIVER=redis
,该配置项默认为sync
同步执行。 - 创建迁移文件
php artisan queue:failed-table
- 执行迁移
php artisan migrate
=> 创建 failed_jobs 表,用于记录失败的队列任务
- 装 predis (php redis)
- 理解任务队列的调用过程:是在 TopicObserver 这个模型监视器里面监听模型的 saved() 方法,当模型执行保存数据到数据库成功后这一时间点,就在 saved 方法里面用
dispatch()
来实例化任务类(它会自动执行构造函数和 handle() 方法),此时就是推送了一个任务给后台,然后在后台(任务类@handle)方法执行请求接口,更新数据库的功能。说白了就是异步更新数据库 slug 字段:让用户发帖不用提交表单之后等程序请求百度翻译接口,然后更新数据库,最后才跳转到 TopicsController@show 。
- 创建任务队列的命令是
php artisan make:job 任务名首字母大写驼峰命名法
,创建的任务文件都位于 app/Jobs/ 目录下。我们是这样编辑 app/Jobs/TranslateSlug.php// 不要改里面的引用内容,在下面引用自己需要的东西 use App\Models\Topic; //模型 use App\Handlers\SlugTranslateHandler; //翻译助手(写好的提供请求接口翻译内容的类文件)
...
protected $topic; //配置等下需要用的参数 public function __construct(Topic $topic) //构造函数 通常用来给成员属性赋值 (配置参数) { $this->topic = $topic; } public function handle() { $slug = app(SlugTranslateHandler::class)->translate($this->topic->title); //调用 Handler 请求接口翻译 slug // 这里必须用 \DB::table() 来读取表数据然后修改,而不能实例化模型 \DB::table('topics')->where('id', $this->topic->id)->update(['slug' => $slug]); }
> 定义成员属性 `protected $topic;` 用于存等下要用的 $topic 对象 > 在构造函数 `__construct()` 中,参数列表里面实例化 Topic 模型,然后给上面的成员属性 $topic 存起来。 > 在 `handle()` 方法中(handle 是关键字,在我们实例化这个类的时候它就自己调用 handle()),我们先通过用于翻译的 Handler 请求接口获得 slug。 > 然后一定要用 `\DB::table()` 来直接读取数据表并进行某条数据某个字段的更新。(因为我们是在模型监视器 saved 方法里面调用这个任务队列类,如果在任务队列类里再实例化模型更新数据,又会去调模型监视器里的 saved()) ------------------------------------------------------------------------------------------------ * 最后由于逻辑的问题,任务队列类 app/Jobs/TranslateSlug.php 是需要在实例化的时候就能得到一个由准确的 主键id 查找数据库获得的模型对象 $topic。所以我们要确保调用这个任务类的时候,传递的参数 $topic 是有 id 属性的对象。写在 `saving()` 就不合适了(因为 `saving()` 代指`updating()`(更新前,有主键) 和 `creating()`(创建前,还没入库没有主键)),updating 没事 但是 creating 就有问题了,因为此时没有主键。 TopicObserver@saved 的内容如下
use App\Jobs\TranslateSlug;
...
public function saved(Topic $topic)
{
// 如 slug 字段无内容,即使用翻译器对 title 进行翻译
if ( ! $topic->slug) {
// 推送任务到队列
dispatch(new TranslateSlug($topic));
}
}> `dispatch(new 任务类))` => 实例化任务类,推送到队列任务中。此时这个对象会在后台默默尝试执行几次,成功会更新数据表 topics,几次失败之后会把失败信息写进 failed_jobs 数据表,然后释放自己。
- 安装和配置:
推荐文章: