slug 与异步获取 slug (队列任务) 的笔记分享

总结

  1. 今天学习的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 可以看得到图形化界面的队伍任务管理器。

  2. 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->参数名 一定可以调出参数值。

  3. 队列任务

    这是一个新的知识重点。今天用队列任务实现了一个异步请求接口获得帖子的 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 表,用于记录失败的队列任务

    • 理解任务队列的调用过程:是在 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 数据表,然后释放自己。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

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