2014-02-11 34 views
6

简单的代码,应该通过检查用户,用户处于活动状态,并在那之后更新上次登录日期时间。斯卡拉异步/回调代码重写

def authenticate() = Action.async { implicit request => 
    loginForm.bindFromRequest.fold(
     errors => Future.successful(BadRequest(views.html.logon(errors))), 
     usersData =>{ 
      val cursor = this.collection.find(BSONDocument("name" -> usersData._1)).one[Account].map(_.filter(p=>p.password == hashedPass(usersData._2, usersData._1))) 
      cursor.flatMap(p => p match { 
       case None => Future.successful(BadRequest(views.html.logon(loginForm.withGlobalError("user/pass incorect!!!")))) 
       case Some(user) => { 
       if(!user.active) 
        Future.successful(BadRequest(views.html.logon(loginForm.withGlobalError("inactive!!!")))) 
       else collection.update(BSONDocument("_id" -> user.id), 
          BSONDocument("$set" -> 
          BSONDocument("lastLogin" -> BSONDateTime(new org.joda.time.DateTime().getMillis())))) 
          .flatMap(x => gotoLoginSucceeded(user.id.stringify)) 

       } 
       }) 
      }) 
    } 

如何将它重写为flat flatmap/map spaghetti?

另一种解决方案

def authenticate() = AsyncStack { implicit request => 
loginForm.bindFromRequest.fold(
    errors => Future.successful(BadRequest(views.html.logon(errors))), 
    usersData =>{ 
     for{ 
     user <- this.collection.find(BSONDocument("name" -> usersData._1)).one[Account].map(_.filter(p=>p.password == hashedPass(usersData._2, usersData._1))) 
     update <- { 
     lazy val update = collection.update(BSONDocument("_id" -> user.get.id), 
     BSONDocument("$set" -> 
     BSONDocument("lastLogin" -> BSONDateTime(new org.joda.time.DateTime().getMillis())))) 
     update 
     } 
     result <- { 
     lazy val result = gotoLoginSucceeded(user.get.id.stringify) 
     result 
     } 
     } yield 
     if(user.isEmpty) BadRequest(views.html.logon(loginForm.withGlobalError("login\pass mismatch"))) 
     else if(!user.get.active) BadRequest(views.html.logon(loginForm.withGlobalError("inactive"))) 
     else if(update.err.isEmpty) result 
     else InternalServerError(views.html.logon(loginForm.withGlobalError("server error"))) 
     }) 

}

+3

如何将它分解为几个较小的功能呢? – vptheron

+0

这看起来对我来说是非常好的代码。如EECOLOR所做的那样,它可能会从将某些块重构为方法中受益,但除此之外,我看不出有什么问题。这是什么让你烦恼 –

回答

5

我可能会重构代码弄成这个样子:

def authenticate() = Action.async { implicit request => 
    loginForm.bindFromRequest.fold(
    hasErrors = displayFormWithErrors, 
    success = loginUser) 
} 

private def displayFormWithErrors[T](errors:Form[T]) = 
    Future.successful(BadRequest(views.html.logon(errors))) 

private def loginUser(userData:(String, String)) = { 
    val (username, password) = userData 

    findUser(username, password) 
    .flatMap { 
     case None => 
     showLoginFormWithError("user/pass incorect!!!") 
     case Some(user) if (!user.active) => 
     showLoginFormWithError("inactive!!!") 
     case Some(user) => 
     updateUserAndRedirect(user) 
    } 
} 

private def findUser(username:String, password:String) = 
    this.collection 
    .find(BSONDocument("name" -> username)) 
    .one[Account] 
    .map(_.filter(_.password == hashedPass(password, username))) 

private def showLoginFormWithError(error:String) = 
    Future.successful(BadRequest(
    views.html.logon(loginForm.withGlobalError(error)))) 

private def updateUserAndRedirect(user:Account) = 
    updateLastLogin(user) 
    .flatMap(_ => gotoLoginSucceeded(user.id.stringify)) 

private def updateLastLogin(user:Account) = 
    collection 
    .update(BSONDocument("_id" -> user.id), 
       BSONDocument("$set" -> 
       BSONDocument("lastLogin" -> 
       BSONDateTime(new JodaDateTime().getMillis())))) 
+0

看起来像我的第一个解决方案。 – sh1ng

0

我喜欢做密码的形式确认子句&用户验证 - 会是这样的(未经测试,但你的想法):

private val loginForm = Form(
    mapping(
    "name" -> nonEmptyText, 
    "password" -> nonEmptyText 
){ 
    (name, password) => (
     this.collection.find(BSONDocument("name" -> name)).one[Account], 
     password) 
    }{ 
    data => Some((data._1.name, data._2)) 
    }.verifying(new Constraint(None, Seq())({ 
    data: (Option[Account], String) => data match { 
     case (Some(account: Account), _) if !account.active => Invalid(ValidationError("inactive")) 
     case (Some(account: Account), password) if account.password==hashedPass(account.name, password) => Valid 
     case _ => Invalid(ValidationError("login/pass mismatch")) 
    } 
    })) 
) 

然后控制器变得简单多了:

def authenticate() = Action.async { implicit request => 
    loginForm.bindFromRequest.fold(
    errors => Future.successful(BadRequest(views.html.logon(errors))), 
    usersData =>{ 
     collection.update(BSONDocument("_id" -> usersData._1.id), 
         BSONDocument("$set" -> 
         BSONDocument("lastLogin" -> BSONDateTime(new org.joda.time.DateTime().getMillis())))) 
       .flatMap(x => gotoLoginSucceeded(user.id.stringify)) 

    } 
) 
} 
+0

编译错误。 this.collection.find(BSONDocument(“name” - > name))。one [Account]:Future [Option [Account]] – sh1ng

+0

我不认为数据库交互作为表单验证的一部分非常适合而且,正如sh1ng所说,只有在使用同步/阻塞数据库客户端的情况下才能使用。 – johanandren