0

我正在开发 Phoenix 1.6 应用程序。我通过 GitHub 使用 Ueberauth 进行身份验证,它正在工作。我设置了一个发布主题和评论的频道,经过身份验证的用户可以这样做。接下来,我尝试添加一个用户令牌以在频道中使用。我按照使用mix phx.new.socket User创建的样板代码中的说明进行操作。在user_socket.js文件中,有在模板中创建令牌的说明,它有效。令牌在连接函数的user_socket.ex中进行验证。我在router.ex中创建了一个新插件put_user_token以将令牌添加到conn,这也有效。但是,我对插件中的逻辑有疑问。这是我的代码:

  defp put_user_token(conn, _) do
    if conn.assigns.user do
      token = Phoenix.Token.sign(conn, "user socket", conn.assigns.user.id)
      assign(conn, :user_token, token)
    else
      conn
    end

只要我登录就可以使用。问题是当我注销并尝试重新登录时。插件中 if 语句的样板代码是:

if current_user = conn.assigns[:current_user] do

我的代码不同,因为当前用户被定义为user,其值等于数据库中用户记录的 id。我认为问题在于,在设置 Ueberauth 时,我创建了另一个插件SetUser,它位于put_user_token插件之前。这是 SetUser 的调用函数:

 def call(conn, _opts) do
        user_id = get_session(conn, :user_id)

        cond do
            user = user_id && Repo.get(User, user_id) ->
                assign(conn, :user, user)
            true ->
                assign(conn, :user, nil)
        end
    end

似乎正在发生的事情是,在我注销后,应用程序重定向到主页,这导致插件执行,并且conn.assigns.user的值设置为nil。然后发生错误,我无法重新登录。

我需要想出一种方法让put_user_token插件中的if语句能够处理nil值。我尝试了 is_integer(conn.assigns.user)和其他一些比较,但如果存在nil值,则应用程序崩溃。

4

1 回答 1

0

我认为如果你可以简化这个问题会更容易回答——我怀疑一旦你简化了问题,你就会清楚电线在哪里交叉。但是,让我尝试做一些澄清。

首先, Elixir 中的语句有些单调——您会发现,大多数情况下,您的执行流程可以在没有它们的情况下定义,并且当不依赖语句if时,代码通常更易于阅读。if

if相关,在使用检查值的“真实性”时要非常小心。这不仅仅是 Elixir 的事情,这种行为是任何语言的潜在问题。例如,我似乎记得 PHP,它在一个版本中将空对象评估为 false,但在另一个版本中评估为 true (!!)。在 Elixir0或空对象中,两者都是“真实的”,但不是真实的......所以所有这一切都说它是值得更明确的。

例如,考虑重构此代码:

defp put_user_token(conn, _) do
  if conn.assigns.user do
    token = Phoenix.Token.sign(conn, "user socket", conn.assigns.user.id)
    assign(conn, :user_token, token)
  else
    conn
  end
end

更明确地说,也许是:

defp put_user_token(conn, _) do
  case conn.assigns.user do
    nil -> conn
    user -> 
      token = Phoenix.Token.sign(conn, "user socket", conn.assigns.user.id)
      assign(conn, :user_token, token)
  end
end

或者考虑将模式匹配一​​直推到函数签名中,就像这样(我不确定 的确切形状conn,但希望你明白):

defp put_user_token(%{assigns: %{user: nil}} = conn, _), do: conn
defp put_user_token(%{assigns: %{user: user}} = conn, _) do 
  token = Phoenix.Token.sign(conn, "user socket", conn.assigns.user.id)
  assign(conn, :user_token, token)
end

您可能会发现在代码中分配一个简单的布尔值(例如:is_logged_in?作为枢轴点)更直接,因为对可能是映射/结构或可能是 nil 的值进行大量检查可能会令人困惑并且更难阅读.

最后,仔细检查您获取用户数据的代码的另一端:

def call(conn, _opts) do
  case get_session(conn, :user_id) do
    nil -> conn
    user_id -> user = Repo.get(User, user_id)
      assign(conn, :user, user)
  end
end

或者稍微严格一点并处理会话中的用户 ID 在数据库中不存在的可能性,您可以将其重构为如下with语句:

def call(conn, _opts) do
  with user_id when !is_nil(user_id) <- get_session(conn, :user_id) 
   user when !is_nil(user) <- Repo.get(User, user_id)
      assign(conn, :user, user)
  else
    _ -> conn
  end
end

我认为如果您将组件步骤移动到它们自己命名的私有函数中,它会更容易阅读,它返回的内容比nil.

我还要花点时间重新评估这里的流程——如果您需要为每个请求访问数据库,该应用程序将无法正常运行。只有在成功登录后,您才应该将所需的用户数据写入会话。

所有代码示例都未经测试。

于 2022-02-28T13:31:07.013 回答