Swift+Vapor+Fluent 如何查询用户ID后插入关联数据
Vapor+Fluent 按用户名关联插入Acronym实现方案
原有代码的核心错误是混用async/await同步写法和EventLoopFuture回调风格:在声明返回EventLoopFuture<Acronym>的非async函数里,无法直接用await取异步结果,二选一改写即可。另外原辅助函数里的强制解包会在用户不存在时直接触发服务崩溃,必须做判空处理。
方案1:Fluent标准ORM实现(推荐)
该写法和Fluent模型逻辑统一,自动兼容PostgreSQL/MySQL/SQLite等多数据库,维护成本低。
async/await 写法(Vapor 4.50+ 推荐,代码最简洁)
import Vapor import Fluent struct AcronymsController: RouteCollection{ func boot boot(routes: RoutesBuilder) throws { let acronymsRoutes = routes.grouped("api", "acronyms") acronymsRoutes.post(use: createHandler) } func createHandler(_ req: Request) async throws -> Acronym { let formData = try req.content.decode(FormData.self) // 查询对应用户,不存在直接返回404 guard let user = try await User.query(on: req.db) .filter(\.$username == formData.username) .first() else { throw Abort(.notFound, reason: "指定用户名不存在") } // 生成Acronym实例并保存 let acronym = Acronym( id: formData.id, short: formData.short, long: formData.long, userID: user.id! ) try await acronym.save(on: req.db) return acronym } } struct FormData: Content{ let id: UUID let short: String let long: String let username: String }
EventLoopFuture 链式写法(兼容旧版本,无需async/await)
如果项目环境不支持async/await,用flatMap把异步操作串成链式调用即可,不要在同步逻辑里强行取异步值:
func createHandler(_ req: Request) throws -> EventLoopFuture<Acronym> { let formData = try req.content.decode(FormData.self) return User.query(on: req.db) .filter(\.$username == formData.username) .first() .flatMap { user in guard let user = user else { return req.eventLoop.future(error: Abort(.notFound, reason: "指定用户名不存在")) } let acronym = Acronym( id: formData.id, short: formData.short, long: formData.long, userID: user.id! ) return acronym.save(on: req.db).map { acronym } } }
方案2:直接执行原生PostgreSQL语句(性能更优)
你给出的带子查询的INSERT语句完全可以直接执行,相比两次Fluent查询少一次数据库网络往返,性能更高,适合对接口延迟敏感的场景。实现时必须用参数绑定传值,禁止直接拼接用户输入到SQL中,避免SQL注入风险:
import PostgresNIO func createHandler(_ req: Request) async throws -> Acronym { let formData = try req.content.decode(FormData.self) guard let pgDB = req.db as? PostgresDatabase else { throw Abort(.internalServerError, reason: "数据库类型不匹配") } // 注意驼峰字段userID如果没有配置蛇形命名转换,需要加双引号 let insertSQL = """ INSERT INTO acronyms (id, short, long, "userID") VALUES ($1, $2, $3, (SELECT id FROM users WHERE username = $4 LIMIT 1)) RETURNING *; """ return try await pgDB.raw(SQLQueryString(insertSQL)) .bind(formData.id) .bind(formData.short) .bind(formData.long) .bind(formData.username) .first(decoding: Acronym.self) }
子查询里加
LIMIT 1是因为业务上username唯一,避免极端情况返回多行导致插入失败。
方案选型参考
- 常规业务优先选方案1:代码可读性高、跨数据库兼容、和Fluent模型的生命周期钩子(如
willSave/didSave)逻辑兼容 - 高并发性能敏感场景选方案2:减少一次数据库往返,延迟更低,但代码和PostgreSQL绑定,后续换数据库需要重写逻辑
内容的提问来源于stack exchange,提问作者michaD
相关产品推荐
相关产品推荐

