Vapor 3中SQLiteModel对象的Slug生成与Leaf使用问题
在Vapor 3中为SQLiteModel生成Slug并在Leaf模板中使用
咱们一步步来搞定这个搜索引擎友好链接的问题,从slug生成到Leaf模板使用全给你捋清楚:
1. 给模型添加Slug属性与自动生成逻辑
首先得在你的SQLiteModel(比如Tag)里新增slug字段,还要写个能生成唯一slug的方法——毕竟不能出现两个相同的slug对吧?
import Vapor import FluentSQLite final class Tag: SQLiteModel { var id: Int? var name: String var slug: String // 新增的slug字段 init(id: Int? = nil, name: String, slug: String) { self.id = id self.name = name self.slug = slug } } // 扩展模型,实现slug生成逻辑 extension Tag { // 把名称转成基础slug:小写、替换特殊字符为横杠 private static func baseSlug(from name: String) -> String { return name.lowercased() .replacingOccurrences(of: "[^a-z0-9]+", with: "-", options: .regularExpression) .trimmingCharacters(in: .punctuationCharacters) } // 生成唯一slug:如果基础slug已存在,就加数字后缀 static func generateUniqueSlug(for name: String, on conn: DatabaseConnectable) throws -> Future<String> { let baseSlug = self.baseSlug(from: name) return Tag.query(on: conn) .filter(\.slug == baseSlug) .count() .map { count in count == 0 ? baseSlug : "\(baseSlug)-\(count)" } } } // 迁移时给slug加唯一约束 extension Tag: Migration { static func prepare(on conn: SQLiteConnection) -> Future<Void> { return SQLiteDatabase.create(Tag.self, on: conn) { builder in builder.field(for: \.id, isIdentifier: true) builder.field(for: \.name) builder.field(for: \.slug) builder.unique(on: \.slug) // 确保slug唯一 } } }
创建Tag的时候别手动传slug,在控制器里调用生成方法:
// 接收创建请求的结构体 struct TagCreateData: Content { var name: String } func createTag(_ req: Request) throws -> Future<Tag> { return try req.content.decode(TagCreateData.self).flatMap { data in // 先生成唯一slug,再创建Tag并保存 return Tag.generateUniqueSlug(for: data.name, on: req) .map { Tag(name: data.name, slug: $0) } .flatMap { $0.save(on: req) } } }
2. 实现Parameter协议,让Vapor通过Slug查找模型
接下来要让Vapor支持用slug代替id来查询模型,给Tag扩展Parameter协议:
extension Tag: Parameter { // 指定用slug作为参数匹配的键 static var parameterKey: String { return "slug" } // 实现通过slug查找模型的逻辑 static func parameter(for value: String, on conn: DatabaseConnectable) throws -> Future<Tag> { return Tag.query(on: conn) .filter(\.slug == value) .first() .unwrap(or: Abort(.notFound, reason: "找不到slug为\(value)的标签")) } }
然后更新路由,不用改路径写法,Vapor会自动用slug匹配:
router.get("tag", Tag.parameter, use: tagDetailHandler) // 详情页控制器方法 func tagDetailHandler(_ req: Request) throws -> Future<View> { let tag = try req.parameters.next(Tag.self) return try req.view().render("tag/detail", tag) }
3. 修正Leaf模板中的链接
你之前的模板用的是tag.id,现在直接改成tag.slug就行,确保变量名和模型属性完全一致(大小写敏感哦):
#for(tag in tags) { <a class="tag" href="/tag/#(tag.slug)">#(tag.name)</a> }
关于你说的#(model.uniqueSlug)无效的问题
你提到加了uniqueSlug属性但Leaf里用不了,大概率是这几个原因:
- 如果
uniqueSlug是计算属性,要确保它是var且有正确的getter,比如:extension Tag { var uniqueSlug: String { return slug // 或者你的自定义逻辑 } } - 检查渲染模板时有没有把完整的模型实例传进去,别只传了部分属性
- 确认模板里的变量名和模型属性名完全匹配(比如别把
uniqueSlug写成uniqueslug)
4. 测试验证
现在创建标签时,系统会自动生成唯一slug:比如第一个"Swift"标签的slug是swift,再创建同名标签就会生成swift-1,访问/tag/swift就能直接跳转到对应详情页,完全符合搜索引擎友好的要求啦。
内容的提问来源于stack exchange,提问作者Austin
相关产品推荐
相关产品推荐

