Laravel多语言文章SEO友好URL(分类slug+文章slug)实现求助
没问题,我来帮你搞定这个SEO友好URL的实现,咱们一步步拆解:
1. 先把模型关联梳理清楚
首先得给Article和ArticleTranslation、ArticleCategory建立正确的关联,这样后续查询会顺畅很多:
// 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表的slug和locale字段加联合索引,能大幅提升查询性能。 - 如果分类的slug也存在于翻译表,那需要参照文章的逻辑,先根据当前语言的slug找到对应分类。
内容的提问来源于stack exchange,提问作者user6780526




