路由参数 #

一、路径参数 #

1.1 基本路径参数 #

elixir
get "/users/:id", UserController, :show

控制器接收参数:

elixir
defmodule HelloWeb.UserController do
  use HelloWeb, :controller

  def show(conn, %{"id" => id}) do
    user = Hello.Accounts.get_user!(id)
    render(conn, :show, user: user)
  end
end

1.2 多个路径参数 #

elixir
get "/users/:user_id/posts/:id", UserPostController, :show
elixir
def show(conn, %{"user_id" => user_id, "id" => post_id}) do
  post = Hello.Blog.get_user_post!(user_id, post_id)
  render(conn, :show, post: post)
end

1.3 通配符参数 #

elixir
get "/files/*path", FileController, :show
elixir
def show(conn, %{"path" => path_list}) do
  path = Enum.join(path_list, "/")
  render(conn, :show, path: path)
end

访问 /files/docs/images/logo.png 时:

elixir
%{"path" => ["docs", "images", "logo.png"]}

二、查询参数 #

2.1 获取查询参数 #

elixir
get "/users", UserController, :index
elixir
def index(conn, params) do
  page = Map.get(params, "page", "1")
  per_page = Map.get(params, "per_page", "10")

  users = Hello.Accounts.list_users(page: page, per_page: per_page)
  render(conn, :index, users: users)
end

访问 /users?page=2&per_page=20 时:

elixir
%{"page" => "2", "per_page" => "20"}

2.2 查询参数处理 #

elixir
def index(conn, params) do
  filter = %{
    page: Map.get(params, "page", "1") |> String.to_integer(),
    per_page: Map.get(params, "per_page", "10") |> String.to_integer(),
    search: Map.get(params, "search", ""),
    status: Map.get(params, "status")
  }

  users = Hello.Accounts.list_users(filter)
  render(conn, :index, users: users)
end

三、参数验证 #

3.1 使用模式匹配 #

elixir
def show(conn, %{"id" => id}) when is_binary(id) do
  case Integer.parse(id) do
    {int_id, ""} ->
      user = Hello.Accounts.get_user!(int_id)
      render(conn, :show, user: user)

    _ ->
      conn
      |> put_flash(:error, "Invalid ID")
      |> redirect(to: ~p"/users")
  end
end

3.2 使用Ecto Changeset验证 #

elixir
defmodule HelloWeb.UserParams do
  use Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    field :email, :string
    field :name, :string
    field :age, :integer
  end

  def changeset(params) do
    %__MODULE__{}
    |> cast(params, [:email, :name, :age])
    |> validate_required([:email, :name])
    |> validate_format(:email, ~r/@/)
    |> validate_number(:age, greater_than: 0)
  end
end
elixir
def create(conn, params) do
  changeset = UserParams.changeset(params)

  if changeset.valid? do
    user_params = Ecto.Changeset.apply_changes(changeset)
    {:ok, user} = Hello.Accounts.create_user(user_params)
    redirect(conn, to: ~p"/users/#{user.id}")
  else
    conn
    |> put_flash(:error, "Invalid parameters")
    |> render(:new, changeset: changeset)
  end
end

四、参数转换 #

4.1 字符串转整数 #

elixir
def show(conn, %{"id" => id}) do
  case Integer.parse(id) do
    {int_id, ""} ->
      user = Hello.Accounts.get_user!(int_id)
      render(conn, :show, user: user)

    _ ->
      conn
      |> put_status(:bad_request)
      |> json(%{error: "Invalid ID format"})
  end
end

4.2 日期参数 #

elixir
def index(conn, %{"date" => date_string}) do
  case Date.from_iso8601(date_string) do
    {:ok, date} ->
      posts = Hello.Blog.list_posts_by_date(date)
      render(conn, :index, posts: posts)

    {:error, _} ->
      conn
      |> put_status(:bad_request)
      |> json(%{error: "Invalid date format"})
  end
end

五、嵌套参数 #

5.1 处理嵌套参数 #

elixir
def create(conn, %{"user" => user_params}) do
  case Hello.Accounts.create_user(user_params) do
    {:ok, user} ->
      conn
      |> put_flash(:info, "User created successfully")
      |> redirect(to: ~p"/users/#{user.id}")

    {:error, changeset} ->
      render(conn, :new, changeset: changeset)
  end
end

5.2 深层嵌套 #

elixir
def create(conn, %{"post" => post_params}) do
  comment_params = Map.get(post_params, "comment", %{})
  tag_ids = Map.get(post_params, "tag_ids", [])

  case Hello.Blog.create_post_with_comment(post_params, comment_params, tag_ids) do
    {:ok, post} ->
      redirect(conn, to: ~p"/posts/#{post.id}")

    {:error, changeset} ->
      render(conn, :new, changeset: changeset)
  end
end

六、文件上传参数 #

6.1 处理文件上传 #

elixir
def upload(conn, %{"file" => file}) do
  %Plug.Upload{
    filename: filename,
    content_type: content_type,
    path: temp_path
  } = file

  case Hello.Files.save_file(temp_path, filename) do
    {:ok, file_path} ->
      json(conn, %{path: file_path})

    {:error, reason} ->
      conn
      |> put_status(:unprocessable_entity)
      |> json(%{error: reason})
  end
end

6.2 文件验证 #

elixir
def upload(conn, %{"file" => file}) do
  with :ok <- validate_file_size(file),
       :ok <- validate_file_type(file) do
    case Hello.Files.save_file(file) do
      {:ok, file_path} ->
        json(conn, %{path: file_path})

      {:error, reason} ->
        conn
        |> put_status(:unprocessable_entity)
        |> json(%{error: reason})
    end
  else
    {:error, reason} ->
      conn
      |> put_status(:unprocessable_entity)
      |> json(%{error: reason})
  end
end

defp validate_file_size(%Plug.Upload{path: path}) do
  case File.stat(path) do
    {:ok, %{size: size}} when size <= 10_000_000 -> :ok
    _ -> {:error, "File too large"}
  end
end

defp validate_file_type(%Plug.Upload{content_type: type}) do
  allowed = ["image/jpeg", "image/png", "image/gif"]
  if type in allowed, do: :ok, else: {:error, "Invalid file type"}
end

七、JSON参数 #

7.1 接收JSON参数 #

elixir
pipeline :api do
  plug :accepts, ["json"]
end

scope "/api", HelloWeb.Api do
  pipe_through :api

  post "/users", UserController, :create
end
elixir
def create(conn, params) do
  case Hello.Accounts.create_user(params) do
    {:ok, user} ->
      conn
      |> put_status(:created)
      |> json(%{data: user})

    {:error, changeset} ->
      conn
      |> put_status(:unprocessable_entity)
      |> json(%{errors: format_errors(changeset)})
  end
end

八、参数安全 #

8.1 参数白名单 #

elixir
def create(conn, params) do
  allowed_params = Map.take(params, ["email", "name", "age"])

  case Hello.Accounts.create_user(allowed_params) do
    {:ok, user} ->
      redirect(conn, to: ~p"/users/#{user.id}")

    {:error, changeset} ->
      render(conn, :new, changeset: changeset)
  end
end

8.2 使用Ecto cast #

elixir
defmodule Hello.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :email, :string
    field :name, :string
    field :role, :string
    field :is_admin, :boolean, default: false
  end

  def registration_changeset(user, attrs) do
    user
    |> cast(attrs, [:email, :name])
    |> validate_required([:email, :name])
  end

  def update_changeset(user, attrs) do
    user
    |> cast(attrs, [:name])
    |> validate_required([:name])
  end
end

九、参数默认值 #

9.1 使用Map.get设置默认值 #

elixir
def index(conn, params) do
  page = Map.get(params, "page", "1") |> String.to_integer()
  per_page = Map.get(params, "per_page", "10") |> String.to_integer()

  users = Hello.Accounts.list_users(page: page, per_page: per_page)
  render(conn, :index, users: users)
end

9.2 使用模式匹配默认值 #

elixir
def index(conn, %{"page" => page, "per_page" => per_page} = params) do
  users = Hello.Accounts.list_users(page: page, per_page: per_page)
  render(conn, :index, users: users)
end

def index(conn, _params) do
  users = Hello.Accounts.list_users(page: 1, per_page: 10)
  render(conn, :index, users: users)
end

十、调试参数 #

10.1 日志记录 #

elixir
def create(conn, params) do
  require Logger
  Logger.info("Received params: #{inspect(params)}")

  case Hello.Accounts.create_user(params) do
    {:ok, user} ->
      redirect(conn, to: ~p"/users/#{user.id}")

    {:error, changeset} ->
      Logger.error("Failed to create user: #{inspect(changeset.errors)}")
      render(conn, :new, changeset: changeset)
  end
end

10.2 IEx调试 #

elixir
def create(conn, params) do
  require IEx
  IEx.pry()

  case Hello.Accounts.create_user(params) do
    {:ok, user} -> redirect(conn, to: ~p"/users/#{user.id}")
    {:error, changeset} -> render(conn, :new, changeset: changeset)
  end
end

十一、总结 #

11.1 参数类型 #

类型 示例 获取方式
路径参数 /users/:id %
查询参数 ?page=1 Map.get(params, “page”)
表单参数 form data %
JSON参数 JSON body 直接使用params
文件参数 file upload %{“file” => %Plug.Upload

11.2 下一步 #

现在你已经了解了路由参数,接下来让我们学习 路由管道,深入了解请求处理管道!

最后更新:2026-03-28