如何使用 Yesod 检查 intField 中的随机数

How to check for a random number in intField with Yesod

我想在 Yesod 中创建自定义验证码,您必须在其中输入基于 IO() 操作的结果来解决随机数学问题。

在 POST 处理程序中评估表单时,正在创建一个新的随机数,而之前的输入是错误的。

我如何设法检查 IO 生成的输入与用户的输入是否正确?

{-# LANGUAGE GADTs #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TemplateHaskell, QuasiQuotes, FlexibleContexts #-}

module Main where

import Yesod
import Data.Text
import Yesod.Form
import System.Random

main :: IO ()
main = warp 3000 Captcha


data Captcha = Captcha

instance Yesod Captcha

instance RenderMessage Captcha FormMessage where
    renderMessage _ _ = defaultFormMessage

mkYesod "Captcha" [parseRoutes|
/          HomeR GET POST
|]


randomMForm :: Html -> MForm Handler (FormResult Int, Widget)
randomMForm token = do
      rand <- liftIO $ randomRIO (0 :: Int ,10 :: Int)
      (iResult, iView) <- mreq (checkIntInput rand) "" Nothing
      let widget = [whamlet|
           ^{token}
                 ^{fvInput iView}
             <input type=submit value="Submit">
             <p> Input this: #{show rand}
          <br>
        |]
      return (iResult, widget)

checkIntInput :: ((RenderMessage (HandlerSite m) FormMessage), (Monad m)) => Int -> Field m Int
checkIntInput n = checkBool (\x -> x == n) nmsg intField
  where msg = "Doesn't match the random number " :: Text
        x = pack $ show n
        nmsg = msg `append` x

getHomeR :: Handler Html
getHomeR = do
       (widget, enctype) <-generateFormPost $ randomMForm
       defaultLayout [whamlet|
              <form method=post enctype=#{enctype}>
                  ^{widget}
                     |]

postHomeR :: Handler Html
postHomeR = do
       ((res,widget), enctype) <- runFormPost $ randomMForm
       case res of
          (FormSuccess i) ->  defaultLayout [whamlet|
                                   <p> You entered the right int: #{show i}
                                   <a href=@{HomeR}> Get back!
                                          |]
          (FormFailure (err:_)) ->  defaultLayout [whamlet|
                                   <p> Error: #{err}
                                   <form method=post enctype=#{enctype}>
                                       ^{widget}
                                   <a href=@{HomeR}> Get back!
                                          |]
          (_) ->  defaultLayout [whamlet|
                                   <p> Total error!
                                   <form method=post enctype=#{enctype}>
                                       ^{widget}
                                   <a href=@{HomeR}> Get back!
                                          |]

这是一个供您自己测试的最小示例。

我还做了一个 webm 来展示如果你想提交一个数字的样子。

您可能需要:

  • 生成随机数
  • 将其存储在用户的会话中
  • 验证表单时,检查会话并比较用户输入

它就像一个魅力!

这是我最初想要创建的代码 - 也许有人想要实现这种数学验证码。 WebM

{-# LANGUAGE GADTs #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE TemplateHaskell, QuasiQuotes, FlexibleContexts #-}

module Main where

import Prelude
import Yesod
import Data.Text
import Yesod.Form
import System.Random
import Data.Maybe

main :: IO ()
main = warp 3000 Captcha


data Captcha = Captcha

instance Yesod Captcha

instance RenderMessage Captcha FormMessage where
    renderMessage _ _ = defaultFormMessage

mkYesod "Captcha" [parseRoutes|
/          HomeR GET POST
|]


data MathEquation = Math {x :: Int, y :: Int, result :: Int, function ::  Char}

maybeRead :: Read a => String -> Maybe a
maybeRead (reads -> [(x,"")]) = Just x
maybeRead _                   = Nothing

maybeInt :: String -> Maybe Int
maybeInt = maybeRead

createMathEq :: IO (MathEquation)
createMathEq = do
    a  <- randomRIO (0 :: Int, 100 :: Int)
    b  <- randomRIO (0 :: Int, 100 :: Int)
    f' <- randomRIO (0 :: Int, 2 :: Int)
    let (f, fs) = case f' of
                      0 -> ((+),'+')
                      1 -> ((-),'-')
                      _ -> ((*),'*')
        r =  f a b
    return $ Math a b r fs

randomMForm :: MathEquation -> Html -> MForm Handler (FormResult Int, Widget)
randomMForm (Math x y res fs) token = do
    (iResult, iView) <- mreq intField "" Nothing
    let widget = [whamlet|
         ^{token}
               #{show x} #{fs} #{show y} = ^{fvInput iView}
               Should be: #{show res}
           <input type=submit value="Submit">
        <br>
      |]
    return (iResult, widget)

getHomeR :: Handler Html
getHomeR = do
       equation <- liftIO $ createMathEq
       setSession "captcha" (pack $ show $ result equation)
       (widget, enctype) <-generateFormPost $ randomMForm equation
       defaultLayout [whamlet|
              <form method=post enctype=#{enctype}>
                  ^{widget}
                     |]

postHomeR :: Handler Html
postHomeR = do
       equation <- liftIO $ createMathEq
       mText <- lookupSession "captcha"
       ((res,widget), enctype) <- runFormPost $ randomMForm equation
       case (res, mText) of
          (FormSuccess i, (Just captcha)) ->  case ((Just i) == (maybeInt (unpack captcha))) of
                                              True -> do
                                                  setSession "captcha" (pack $ show $ result equation)
                                                  defaultLayout [whamlet|
                                                            <p> You entered the right int: #{show i}
                                                            <a href=@{HomeR}> Get back!
                                                                   |]
                                              False -> do
                                                  setSession "captcha" (pack $ show $ result equation)
                                                  defaultLayout [whamlet|
                                                          <p> Error: Sorry, the input doesn't match
                                                          <form method=post enctype=#{enctype}>
                                                              ^{widget}
                                                          <a href=@{HomeR}> Get back!
                                                                  |]
      (FormFailure (err:_), _) ->  do
          setSession "captcha" (pack $ show $ result equation)
          defaultLayout [whamlet|
                                   <p> Error: #{err}
                                   <form method=post enctype=#{enctype}>
                                       ^{widget}
                                   <a href=@{HomeR}> Get back!
                                          |]
      (_, _) -> do
          setSession "captcha" (pack $ show $ result equation)
          defaultLayout [whamlet|
                                   <p> Total error!
                                   <form method=post enctype=#{enctype}>
                                       ^{widget}
                                   <a href=@{HomeR}> Get back!
                                          |]