swift - Vapor 3 - 如何在保存对象之前检查类似的电子邮件
问题描述
我想创建一条路线让用户更新他们的数据(例如更改他们的电子邮件或用户名)。为了确保用户不能使用与另一个用户相同的用户名,我想检查数据库中是否已经存在具有相同用户名的用户。
我已经使用户名在迁移中唯一。
我有一个看起来像这样的用户模型:
struct User: Content, SQLiteModel, Migration {
var id: Int?
var username: String
var name: String
var email: String
var password: String
var creationDate: Date?
// Permissions
var staff: Bool = false
var superuser: Bool = false
init(username: String, name: String, email: String, password: String) {
self.username = username
self.name = name
self.email = email
self.password = password
self.creationDate = Date()
}
}
这是我要使用它的一段代码:
func create(_ req: Request) throws -> EventLoopFuture<User> {
return try req.content.decode(UserCreationRequest.self).flatMap { userRequest in
// Check if `userRequest.email` already exists
// If if does -> throw Abort(.badRequest, reason: "Email already in use")
// Else -> Go on with creation
let digest = try req.make(BCryptDigest.self)
let hashedPassword = try digest.hash(userRequest.password)
let persistedUser = User(name: userRequest.name, email: userRequest.email, password: hashedPassword)
return persistedUser.save(on: req)
}
}
我可以这样做(请参阅下一个片段),但这似乎是一个奇怪的选择,因为当必须执行更多检查(例如,在更新用户的情况下)时,它需要大量嵌套。
func create(_ req: Request) throws -> EventLoopFuture<User> {
return try req.content.decode(UserCreationRequest.self).flatMap { userRequest in
let userID = userRequest.email
return User.query(on: req).filter(\.userID == userID).first().flatMap { existingUser in
guard existingUser == nil else {
throw Abort(.badRequest, reason: "A user with this email already exists")
}
let digest = try req.make(BCryptDigest.self)
let hashedPassword = try digest.hash(userRequest.password)
let persistedUser = User(name: userRequest.name, email: userRequest.email, password: hashedPassword)
return persistedUser.save(on: req)
}
}
}
正如其中一个答案所建议的那样,我尝试添加错误中间件(请参阅下一个片段),但这并没有正确捕获错误(也许我在代码中做错了 - 刚从 Vapor 开始)。
import Vapor
import FluentSQLite
enum InternalError: Error {
case emailDuplicate
}
struct EmailDuplicateErrorMiddleware: Middleware {
func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture<Response> {
let response: Future<Response>
do {
response = try next.respond(to: request)
} catch is SQLiteError {
response = request.eventLoop.newFailedFuture(error: InternalError.emailDuplicate)
}
return response.catchFlatMap { error in
if let response = error as? ResponseEncodable {
do {
return try response.encode(for: request)
} catch {
return request.eventLoop.newFailedFuture(error: InternalError.emailDuplicate)
}
} else {
return request.eventLoop.newFailedFuture(error: error)
}
}
}
}
解决方案
快速的方法是User.query(on: req).filter(\.email == email).count()
在尝试保存之前执行类似的操作并检查是否等于 0。
然而,虽然这对几乎每个人都适用,但您仍然会冒两个用户尝试在完全相同的时间使用相同用户名注册的极端情况 - 处理此问题的唯一方法是捕获保存失败,检查是否是因为电子邮件的唯一约束并将错误返回给用户。但是,即使对于大型应用程序,您实际命中的机会也很少。
推荐阅读
- c# - 为什么我无法通过官方 Binance REST API 使用 c# 进行 Binance 订单?
- javascript - trigger.click - 多次点击
- javascript - 如何在 html 代码中显示 JavaScript 计算?
- office-js - 带有图形 api 的 Outlook 插件
- c++ - C++ - CreateJVM 之后的所有 QProcess 调用都卡在 waitForFinished()
- javascript - 在Javascript中将数组数组更改为其他数据类型的数组
- compare - 有没有办法将谷歌地球地名复制为文本?
- javascript - 在 html a 标签中获取当前 url 字符串的最佳方法是什么?
- python - 在 Python/Pandas 中显示不同的值并取最大值
- google-apps-script - 当单元格值更改时,使用多个工作表选项卡发送电子邮件通知