路由参数 #
一、路径参数 #
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