控制器基础 #
一、控制器概述 #
1.1 什么是控制器 #
控制器是Phoenix MVC架构中的C,负责处理HTTP请求并返回响应。每个控制器是一个Elixir模块,包含多个动作函数。
text
Request → Router → Controller → View → Response
1.2 控制器结构 #
elixir
defmodule HelloWeb.UserController do
use HelloWeb, :controller
def index(conn, _params) do
users = Hello.Accounts.list_users()
render(conn, :index, users: users)
end
def show(conn, %{"id" => id}) do
user = Hello.Accounts.get_user!(id)
render(conn, :show, user: user)
end
end
二、创建控制器 #
2.1 手动创建 #
elixir
defmodule HelloWeb.PostController do
use HelloWeb, :controller
def index(conn, _params) do
posts = Hello.Blog.list_posts()
render(conn, :index, posts: posts)
end
def show(conn, %{"id" => id}) do
post = Hello.Blog.get_post!(id)
render(conn, :show, post: post)
end
def new(conn, _params) do
changeset = Hello.Blog.change_post(%Hello.Blog.Post{})
render(conn, :new, changeset: changeset)
end
def create(conn, %{"post" => post_params}) do
case Hello.Blog.create_post(post_params) do
{:ok, post} ->
conn
|> put_flash(:info, "Post created successfully")
|> redirect(to: ~p"/posts/#{post.id}")
{:error, changeset} ->
render(conn, :new, changeset: changeset)
end
end
def edit(conn, %{"id" => id}) do
post = Hello.Blog.get_post!(id)
changeset = Hello.Blog.change_post(post)
render(conn, :edit, post: post, changeset: changeset)
end
def update(conn, %{"id" => id, "post" => post_params}) do
post = Hello.Blog.get_post!(id)
case Hello.Blog.update_post(post, post_params) do
{:ok, post} ->
conn
|> put_flash(:info, "Post updated successfully")
|> redirect(to: ~p"/posts/#{post.id}")
{:error, changeset} ->
render(conn, :edit, post: post, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
post = Hello.Blog.get_post!(id)
{:ok, _post} = Hello.Blog.delete_post(post)
conn
|> put_flash(:info, "Post deleted successfully")
|> redirect(to: ~p"/posts")
end
end
2.2 使用生成器 #
bash
mix phx.gen.html Blog Post posts title:string body:text
mix phx.gen.json Blog Post posts title:string body:text
mix phx.gen.live Blog Post posts title:string body:text
三、Conn结构 #
3.1 Conn概述 #
conn是Plug.Conn结构体,包含请求的所有信息:
elixir
%Plug.Conn{
host: "localhost",
method: "GET",
path_info: ["users", "1"],
request_path: "/users/1",
params: %{"id" => "1"},
req_headers: [{"content-type", "text/html"}],
resp_headers: [],
status: 200,
assigns: %{},
cookies: %{},
session: %{}
}
3.2 常用Conn操作 #
elixir
def show(conn, params) do
conn
|> assign(:user, user)
|> put_session(:user_id, user.id)
|> put_flash(:info, "Welcome!")
|> put_resp_header("x-custom", "value")
|> put_status(:created)
|> render(:show, user: user)
end
四、渲染响应 #
4.1 渲染模板 #
elixir
def index(conn, _params) do
users = Hello.Accounts.list_users()
render(conn, :index, users: users)
end
4.2 渲染JSON #
elixir
def show(conn, %{"id" => id}) do
user = Hello.Accounts.get_user!(id)
json(conn, %{id: user.id, name: user.name, email: user.email})
end
4.3 渲染文本 #
elixir
def health(conn, _params) do
text(conn, "OK")
end
4.4 发送文件 #
elixir
def download(conn, %{"id" => id}) do
file = Hello.Files.get_file!(id)
send_download(conn, {:file, file.path}, filename: file.name)
end
4.5 自定义响应 #
elixir
def custom(conn, _params) do
conn
|> put_resp_content_type("text/plain")
|> send_resp(200, "Custom response")
end
五、重定向 #
5.1 基本重定向 #
elixir
def create(conn, params) do
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
5.2 外部重定向 #
elixir
def external(conn, _params) do
redirect(conn, external: "https://example.com")
end
5.3 返回上一页 #
elixir
def delete(conn, %{"id" => id}) do
{:ok, _} = Hello.Accounts.delete_user(id)
conn
|> put_flash(:info, "User deleted")
|> redirect(to: conn.assigns[:return_to] || ~p"/users")
end
六、Flash消息 #
6.1 设置Flash #
elixir
def create(conn, params) do
case Hello.Accounts.create_user(params) do
{:ok, user} ->
conn
|> put_flash(:info, "User created successfully")
|> redirect(to: ~p"/users/#{user.id}")
{:error, changeset} ->
conn
|> put_flash(:error, "Failed to create user")
|> render(:new, changeset: changeset)
end
end
6.2 Flash类型 #
elixir
put_flash(conn, :info, "Information message")
put_flash(conn, :error, "Error message")
put_flash(conn, :warning, "Warning message")
put_flash(conn, :success, "Success message")
七、状态码 #
7.1 设置状态码 #
elixir
def create(conn, params) do
case Hello.Accounts.create_user(params) do
{:ok, user} ->
conn
|> put_status(:created)
|> render(:show, user: user)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render(:new, changeset: changeset)
end
end
7.2 常用状态码 #
| 原子 | 代码 | 说明 |
|---|---|---|
| :ok | 200 | 成功 |
| :created | 201 | 已创建 |
| :no_content | 204 | 无内容 |
| :bad_request | 400 | 错误请求 |
| :unauthorized | 401 | 未授权 |
| :forbidden | 403 | 禁止访问 |
| :not_found | 404 | 未找到 |
| :unprocessable_entity | 422 | 无法处理 |
| :internal_server_error | 500 | 服务器错误 |
八、响应头 #
8.1 设置响应头 #
elixir
def show(conn, %{"id" => id}) do
user = Hello.Accounts.get_user!(id)
conn
|> put_resp_header("x-request-id", generate_request_id())
|> put_resp_header("cache-control", "max-age=3600")
|> render(:show, user: user)
end
8.2 内容类型 #
elixir
def export(conn, %{"format" => "csv"}) do
csv_data = generate_csv()
conn
|> put_resp_content_type("text/csv")
|> send_resp(200, csv_data)
end
九、Assigns #
9.1 设置Assigns #
elixir
def index(conn, _params) do
conn
|> assign(:users, Hello.Accounts.list_users())
|> assign(:page_title, "Users List")
|> render(:index)
end
9.2 合并Assigns #
elixir
def show(conn, %{"id" => id}) do
user = Hello.Accounts.get_user!(id)
posts = Hello.Blog.list_user_posts(user.id)
conn
|> assign(:user, user)
|> assign(:posts, posts)
|> render(:show)
end
9.3 在模板中使用 #
heex
defmodule HelloWeb.Layouts do
use HelloWeb, :html
embed_templates "layouts/*"
end
十、错误处理 #
10.1 404处理 #
elixir
def show(conn, %{"id" => id}) do
case Hello.Accounts.get_user(id) do
nil ->
conn
|> put_status(:not_found)
|> put_view(HelloWeb.ErrorHTML)
|> render("404.html")
user ->
render(conn, :show, user: user)
end
end
10.2 使用get_user! #
elixir
def show(conn, %{"id" => id}) do
user = Hello.Accounts.get_user!(id)
render(conn, :show, user: user)
end
get_user!会在找不到时抛出Ecto.NotFoundError,Phoenix会自动返回404。
10.3 自定义错误 #
elixir
def create(conn, params) do
case Hello.Accounts.create_user(params) do
{:ok, user} ->
redirect(conn, to: ~p"/users/#{user.id}")
{:error, %Ecto.Changeset{} = changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render(:new, changeset: changeset)
{:error, :email_taken} ->
conn
|> put_status(:conflict)
|> json(%{error: "Email already taken"})
end
end
十一、API控制器 #
11.1 JSON响应 #
elixir
defmodule HelloWeb.Api.UserController do
use HelloWeb, :controller
def index(conn, _params) do
users = Hello.Accounts.list_users()
json(conn, %{data: users})
end
def show(conn, %{"id" => id}) do
user = Hello.Accounts.get_user!(id)
json(conn, %{data: user})
end
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
defp format_errors(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
Regex.replace(~r"%{(\w+)}", msg, fn _, key ->
opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
end)
end)
end
end
11.2 分页响应 #
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()
%{entries: users, total_pages: total_pages, page_number: page_number} =
Hello.Accounts.list_users_paginated(page: page, per_page: per_page)
json(conn, %{
data: users,
meta: %{
current_page: page_number,
total_pages: total_pages
}
})
end
十二、总结 #
12.1 核心操作 #
| 操作 | 函数 |
|---|---|
| 渲染模板 | render/3 |
| 返回JSON | json/2 |
| 返回文本 | text/2 |
| 重定向 | redirect/2 |
| Flash消息 | put_flash/3 |
| 状态码 | put_status/2 |
| 响应头 | put_resp_header/3 |
| Assigns | assign/3 |
12.2 下一步 #
现在你已经了解了控制器基础,接下来让我们学习 请求与响应,深入了解请求处理和响应生成!
最后更新:2026-03-28