首页 > 解决方案 > 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)
            }
        }
    }
}

标签: swiftrestvaporvapor-fluent

解决方案


快速的方法是User.query(on: req).filter(\.email == email).count()在尝试保存之前执行类似的操作并检查是否等于 0。

然而,虽然这对几乎每个人都适用,但您仍然会冒两个用户尝试在完全相同的时间使用相同用户名注册的极端情况 - 处理此问题的唯一方法是捕获保存失败,检查是否是因为电子邮件的唯一约束并将错误返回给用户。但是,即使对于大型应用程序,您实际命中的机会也很少。


推荐阅读