You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Laravel多语言文章SEO友好URL(分类slug+文章slug)实现求助

没问题,我来帮你搞定这个SEO友好URL的实现,咱们一步步拆解:

1. 先把模型关联梳理清楚

首先得给ArticleArticleTranslationArticleCategory建立正确的关联,这样后续查询会顺畅很多:

// app/Models/Article.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    // 一篇文章对应多语言翻译
    public function translations()
    {
        return $this->hasMany(ArticleTranslation::class);
    }

    // 快捷获取当前语言的翻译(避免重复写查询条件)
    public function currentTranslation()
    {
        return $this->hasOne(ArticleTranslation::class)->where('locale', app()->getLocale());
    }

    // 关联文章分类
    public function category()
    {
        return $this->belongsTo(ArticleCategory::class, 'category_id');
    }
}

如果你的分类也有翻译需求(比如分类slug存在于单独的翻译表),可以参照上面的写法给ArticleCategory加关联;如果分类slug直接存在于article_categories表,那只需要加和文章的关联即可:

// app/Models/ArticleCategory.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ArticleCategory extends Model
{
    public function articles()
    {
        return $this->hasMany(Article::class);
    }
}
2. 配置匹配URL的路由

routes/web.php里定义对应结构的路由,参数顺序要和你期望的URL一致:

Route::get('articles/{articleCategory}/{slug}', [ArticleController::class, 'show'])->name('articles.show');
3. 完善Controller的show方法

现在来完成你写了一半的方法,核心是要匹配当前语言、分类、文章slug,同时确保文章属于该分类,避免非法访问:

方式一:用关联查询(更符合Eloquent风格)

// app/Http/Controllers/ArticleController.php
namespace App\Http\Controllers;

use App\Models\Article;
use App\Models\ArticleCategory;
use Illuminate\Http\Response;

public function show(ArticleCategory $articleCategory, $slug)
{
    $locale = app()->getLocale();

    // 找到符合当前语言、文章slug,且属于该分类的文章
    $article = Article::whereHas('translations', function ($query) use ($slug, $locale) {
        $query->where('slug', $slug)
              ->where('locale', $locale);
    })
    ->where('category_id', $articleCategory->id)
    ->with(['currentTranslation', 'category']) // 预加载关联,避免N+1查询
    ->firstOrFail(); // 找不到直接返回404

    return view('articles.show', compact('article', 'articleCategory'));
}

方式二:用JOIN(你最初尝试的思路)

如果你更习惯用原生JOIN写法,也可以这样实现:

public function show(ArticleCategory $articleCategory, $slug)
{
    $locale = app()->getLocale();

    $article = Article::join('article_translations', 'articles.id', '=', 'article_translations.article_id')
        ->where('article_translations.slug', $slug)
        ->where('article_translations.locale', $locale)
        ->where('articles.category_id', $articleCategory->id)
        ->select(
            'articles.*',
            'article_translations.title',
            'article_translations.subtitle',
            'article_translations.description'
        ) // 按需选择需要的翻译字段
        ->firstOrFail();

    return view('articles.show', compact('article', 'articleCategory'));
}
4. 可选优化:自定义路由模型绑定(更优雅)

如果想让路由自动帮你解析出对应的文章,不用手动写查询,可以在RouteServiceProvider里自定义绑定:

// app/Providers/RouteServiceProvider.php
public function boot()
{
    parent::boot();

    // 自定义文章的路由绑定逻辑
    Route::bind('slug', function ($value) {
        $category = request()->route('articleCategory');
        return Article::whereHas('translations', function ($query) use ($value) {
            $query->where('slug', $value)
                  ->where('locale', app()->getLocale());
        })
        ->where('category_id', $category->id)
        ->firstOrFail();
    });
}

然后路由和控制器方法可以简化成:

// 路由调整(参数名可以更清晰)
Route::get('articles/{articleCategory}/{article}', [ArticleController::class, 'show'])->name('articles.show');

// Controller方法
public function show(ArticleCategory $articleCategory, Article $article)
{
    // 直接拿到匹配好的文章实例,无需手动查询
    return view('articles.show', compact('article', 'articleCategory'));
}
5. 几个注意事项
  • 确保article_translations表有article_id外键关联到articles表的id字段,不然关联查询会报错。
  • article_translations表的sluglocale字段加联合索引,能大幅提升查询性能。
  • 如果分类的slug也存在于翻译表,那需要参照文章的逻辑,先根据当前语言的slug找到对应分类。

内容的提问来源于stack exchange,提问作者user6780526

火山引擎 最新活动