Elixir Phoenix 1.6-试图将令牌传递到浏览器,函数nil.id/0未定义或私有



我正在开发Phoenix 1.6应用程序。我使用Ueberauth通过GitHub进行身份验证,这是有效的。我建立了一个发布主题和评论的通道,经过身份验证的用户可以这样做。接下来,我尝试添加一个用户令牌,以便在通道中使用。我遵循了使用mix phx.new.socket User创建的样板代码中的说明。在user_socket.js文件中,有一些在模板中创建令牌的说明,这些说明很有效。令牌在connect函数的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。我认为问题是,在设置Ueberuth时,我创建了另一个插件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的值,应用程序就会崩溃。

我认为如果你能简化这个问题,这个问题会更容易回答——我怀疑一旦你简化了这个问题,你就会清楚地知道电线在哪里交叉。不过,让我尝试澄清一下。

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

相关,使用if检查";真实性";的值。这不仅仅是一种灵丹妙药,这种行为在任何语言中都是潜在的陷阱。例如,我似乎还记得PHP,它在一个版本中将空对象求值为false,但在另一个版本中求值为true(!!(。在Elixir中,0或空对象都是"0";truthy";,但不是真的。。。所以说更明确是值得的

例如,考虑重构此代码:

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?(作为轴心点更直接,因为对一个值进行一系列检查,即MAYBE是映射/结构或MAYBE为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更明确的内容。

我还想花点时间重新评估一下这里的流程——如果你需要为每个请求访问数据库,那么应用程序的性能就不会很好。您可能应该只在成功登录后才将所需的用户数据写入会话。

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

相关内容

  • 没有找到相关文章

最新更新