2014-02-11 84 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