将Phoenix请求路径与数据库中定义的Route匹配

时间:2018-12-05 05:12:54

标签: elixir phoenix-framework

当我们发送请求时,我们在conn结构中具有当前路径信息。假设是补丁请求。

["v1", "users", "2"] or "v1/users/2"

我正在根据数据库中的路径信息编写用于用户授权的插件。路径在数据库中的存储方式如下:

"v1/users/:id"

这是我们从运行mix phx.routes获得的路径。是否有可能我得到"v1/users/:id"而不是"v1/users/2"的当前路径?这样我就可以将它与数据库中存储的路径进行匹配。

有什么解决方法吗?

2 个答案:

答案 0 :(得分:1)

一种非常hackey的方法是手动替换关联密钥路径中的值。

conn结构中,我们有path_info: ["v1", "users", "2"]path_params: %{"id" => "2"},所以我们可以这样做:

Enum.reduce(conn.path_params, conn.path_info, fn {key, value}, acc ->
  index = Enum.find_index(acc, fn x -> x == value end)
  List.replace_at(acc, index, ":#{key}")
end)
|> Enum.join("/")

输出将为v1/users/:id

请注意,如果您的参数值与路由的一部分相同(这似乎不太可能发生),则此操作将失败。另外,如果您有多个可以取相同值的参数,那么我们将依赖于path_params中参数的顺序。

答案 1 :(得分:0)

鉴于您已在数据库中存储了一系列用户定义的路由,因此可以手动检查传入请求的路径是否与其中之一匹配。 正则表达式可以帮助您优雅地解决此问题(尽管它不如已编译的路由有效):

defmodule RouteMatcher do    
  def find(routes, path) do
    Enum.find_value(routes, nil, &match(&1, path))
  end

  defp match(route, path) do
    pattern = String.replace(route, ~r/:(\w+)/, ~S"(?<\g{1}>[\w-]+)")
    regex = ~r/^#{pattern}$/

    case Regex.named_captures(regex, path) do
      nil -> nil
      map -> {route, map}
    end
  end   
end

现在假设这是数据库中所有已定义路由的列表:

routes = [
  "/v1/users",
  "/v1/users/:user_id",
  "/v1/users/:user_id/posts",
  "/v1/users/:user_id/posts/:post_id",
  "/v1/users/:user_id/posts/:post_id/:comment_id",
]

然后RouteMatcher.find/2函数将返回与给定路径匹配的第一条路线以及匹配的参数(如果没有路线匹配,它将仅返回nil):

RouteMatcher.find(routes, "/v1/users")
#=> {"/v1/users", %{}}

RouteMatcher.find(routes, "/v1/users/psy")
#=> {"/v1/users/:user_id", %{"user_id" => "psy"}}

RouteMatcher.find(routes, "/v1/users/psy/posts")
#=> {"/v1/users/:user_id/posts", %{"user_id" => "psy"}}

RouteMatcher.find(routes, "/v1/users/psy/posts/hello-world")
#=> {"/v1/users/:user_id/posts/:post_id", %{"post_id" => "hello-world", "user_id" => "psy"}}

RouteMatcher.find(routes, "/v1/users/psy/posts/hello-world/45")
#=> {"/v1/users/:user_id/posts/:post_id/:comment_id", %{"comment_id" => "45", "post_id" => "hello-world", "user_id" => "psy"}}

RouteMatcher.find(routes, "/unknown/route")                    
#=> nil