名称: phoenix-routing 用户可调用: false 描述: 在Phoenix应用程序中定义路由和URL助手,包括资源、作用域、管道和验证路由 允许工具: [Bash, Read]
Phoenix路由
Phoenix路由将传入的HTTP请求映射到控制器动作。路由器是所有Web请求的入口点,决定哪个控制器动作应处理每个请求。Phoenix提供了强大的路由宏,用于RESTful资源、作用域、管道和验证路由。
基本路由声明
单一路由
使用HTTP动词宏定义单个路由:
get "/", PageController, :home
Phoenix支持所有标准HTTP动词:
get "/users", UserController, :index
post "/users", UserController, :create
patch "/users/:id", UserController, :update
put "/users/:id", UserController, :update
delete "/users/:id", UserController, :delete
带有动态段的路由
使用:param_name语法捕获URL参数:
get "/hello/:messenger", HelloController, :show
:messenger段在控制器的参数字典中变为可用。
资源路由
基本资源声明
使用resources宏生成所有标准RESTful路由:
resources "/users", UserController
这会生成八条路由:
GET /users UserController :index
GET /users/:id/edit UserController :edit
GET /users/new UserController :new
GET /users/:id UserController :show
POST /users UserController :create
PATCH /users/:id UserController :update
PUT /users/:id UserController :update
DELETE /users/:id UserController :delete
限制资源路由
使用:only生成特定路由:
resources "/users", UserController, only: [:show]
resources "/posts", PostController, only: [:index, :show]
使用:except排除特定路由:
resources "/users", UserController, except: [:create, :delete]
别名资源
使用:as自定义路由路径助手名称:
resources "/users", UserController, as: :person
这会生成路径助手如~p"/person",而不是~p"/users"。
嵌套资源
创建分层资源关系:
resources "/users", UserController do
resources "/posts", PostController
end
生成的路由包括父资源ID:
GET /users/:user_id/posts PostController :index
GET /users/:user_id/posts/:id/edit PostController :edit
GET /users/:user_id/posts/new PostController :new
GET /users/:user_id/posts/:id PostController :show
POST /users/:user_id/posts PostController :create
PATCH /users/:user_id/posts/:id PostController :update
PUT /users/:user_id/posts/:id PostController :update
DELETE /users/:user_id/posts/:id PostController :delete
验证路由
使用~p符咒
Phoenix提供编译时验证路由,使用~p符咒:
# 静态路径
~p"/users"
~p"/posts/new"
# 带有变量的动态段
~p"/users/#{user_id}"
~p"/users/#{user_id}/posts/#{post_id}"
验证路由与结构体
直接传递结构体以生成路径:
~p"/users/#{@user}"
# 生成:"/users/42"
~p"/users/#{user}/posts/#{post}"
# 生成:"/users/42/posts/17"
Phoenix自动使用Phoenix.Param协议提取ID。
验证路由的好处
- 编译时验证 - 在编译时捕获路由错误
- 重构安全性 - 路由更改立即被捕获
- 类型安全 - 确保正确的参数类型
- URL段支持 - 轻松过渡到基于段的路由
作用域
基本作用域
在公共路径前缀下分组路由:
scope "/admin", HelloWeb.Admin do
pipe_through :browser
resources "/users", UserController
end
生成的路径包括作用域前缀:
~p"/admin/users"
带有别名的作用域
通过别名控制器模块减少重复:
scope "/", HelloWeb do
pipe_through :browser
get "/", PageController, :home
resources "/posts", PostController
end
嵌套作用域
创建分层路由组织:
scope "/api", HelloWeb.Api, as: :api do
pipe_through :api
scope "/v1", V1, as: :v1 do
resources "/users", UserController
end
scope "/v2", V2, as: :v2 do
resources "/users", UserController
end
end
生成的路径助手反映嵌套:
~p"/api/v1/users"
~p"/api/v2/users"
管道
定义管道
管道分组为特定路由运行的插头:
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {HelloWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
将管道应用于作用域
使用pipe_through应用管道:
scope "/", HelloWeb do
pipe_through :browser
get "/", PageController, :home
resources "/users", UserController
end
scope "/api", HelloWeb.Api do
pipe_through :api
resources "/users", UserController
end
管道中的自定义插头
添加应用特定插头:
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {HelloWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug HelloWeb.Plugs.Locale, "en"
end
嵌套管道
为复杂身份验证流组合管道:
pipeline :auth do
plug :browser
plug :ensure_authenticated_user
plug :ensure_user_owns_review
end
scope "/reviews", HelloWeb do
pipe_through :auth
resources "/", ReviewController
end
这首先应用:browser管道,然后是身份验证插头。
高级管道模式
会话管理管道
为基于会话的功能创建管道:
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {HelloWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :fetch_current_scope_for_user
end
defp fetch_current_scope_for_user(conn, _opts) do
if id = get_session(conn, :scope_id) do
assign(conn, :current_scope, MyApp.Scope.for_id(id))
else
id = System.unique_integer()
conn
|> put_session(:scope_id, id)
|> assign(:current_scope, MyApp.Scope.for_id(id))
end
end
多租户路由
从URL参数分配组织上下文:
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {HelloWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :fetch_current_scope_for_user
plug :assign_org_to_scope
end
defp assign_org_to_scope(conn, _opts) do
case conn.params["org"] do
nil -> conn
org_slug ->
scope = conn.assigns.current_scope
org = MyApp.Organizations.get_by_slug!(org_slug)
assign(conn, :current_scope, Map.put(scope, :organization, org))
end
end
购物车管道
为当前会话获取或创建购物车:
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {HelloWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :fetch_current_scope_for_user
plug :fetch_current_cart
end
alias MyApp.ShoppingCart
defp fetch_current_cart(%{assigns: %{current_scope: scope}} = conn, _opts)
when not is_nil(scope) do
if cart = ShoppingCart.get_cart(scope) do
assign(conn, :cart, cart)
else
{:ok, new_cart} = ShoppingCart.create_cart(scope, %{})
assign(conn, :cart, new_cart)
end
end
defp fetch_current_cart(conn, _opts), do: conn
转发
转发到插头
将路径前缀委托给另一个插头或应用:
defmodule HelloWeb.Router do
use HelloWeb, :router
scope "/", HelloWeb do
pipe_through :browser
get "/", PageController, :home
end
forward "/jobs", BackgroundJob.Plug
end
所有到/jobs/*的请求由BackgroundJob.Plug处理。
常见转发用例
# 管理界面
forward "/admin", HelloWeb.AdminRouter
# API文档
forward "/api/docs", PhoenixSwagger.Plug.SwaggerUI
# 后台作业仪表板
forward "/jobs", Oban.Web.Router
检查路由
使用mix phx.routes
查看应用中所有定义的路由:
mix phx.routes
输出显示HTTP动词、路径、控制器和动作:
GET / HelloWeb.PageController :home
GET /users HelloWeb.UserController :index
GET /users/:id/edit HelloWeb.UserController :edit
GET /users/new HelloWeb.UserController :new
GET /users/:id HelloWeb.UserController :show
POST /users HelloWeb.UserController :create
PATCH /users/:id HelloWeb.UserController :update
PUT /users/:id HelloWeb.UserController :update
DELETE /users/:id HelloWeb.UserController :delete
过滤路由
Grep特定路由:
mix phx.routes | grep users
mix phx.routes | grep POST
以编程方式构建路径
静态路径
轻松构建静态路径:
~p"/users"
# 返回:"/users"
~p"/posts/new"
# 返回:"/posts/new"
带有整数ID的路径
直接插值ID:
user_id = 42
post_id = 17
~p"/users/#{user_id}/posts/#{post_id}"
# 返回:"/users/42/posts/17"
带有结构体的路径
让Phoenix从结构体提取ID:
~p"/users/#{user}/posts/#{post}"
# 返回:"/users/42/posts/17"
这使用Phoenix.Param协议提取ID。
自定义参数实现
为结构体实现自定义URL生成:
defimpl Phoenix.Param, for: MyApp.Blog.Post do
def to_param(%{slug: slug}), do: slug
end
# 现在生成基于段的路由:
~p"/posts/#{post}"
# 返回:"/posts/my-great-post"
路由器配置示例
完整路由器设置
典型的Phoenix路由器包括多个管道和作用域:
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {HelloWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :api do
plug :accepts, ["json"]
end
scope "/", HelloWeb do
pipe_through :browser
get "/", PageController, :home
get "/hello", HelloController, :index
get "/hello/:messenger", HelloController, :show
end
scope "/api/v1", HelloWeb.Api.V1, as: :api_v1 do
pipe_through :api
resources "/users", UserController, only: [:index, :show]
end
# 管理界面
scope "/admin", HelloWeb.Admin, as: :admin do
pipe_through [:browser, :admin_auth]
resources "/users", UserController
resources "/posts", PostController
end
# 在开发中启用LiveDashboard
if Mix.env() in [:dev, :test] do
import Phoenix.LiveDashboard.Router
scope "/" do
pipe_through :browser
live_dashboard "/dashboard", metrics: HelloWeb.Telemetry
end
end
end
何时使用此技能
在以下情况下使用此技能:
- 为控制器和动作定义新路由
- 为CRUD操作创建RESTful资源路由
- 使用作用域和命名空间组织路由
- 构建嵌套资源关系
- 配置请求处理管道
- 在控制器和模板中生成验证路由路径
- 使用作用域路由实现API版本控制
- 调试路由问题和检查可用路由
- 将请求转发到外部插头或应用
- 实现自定义URL段生成
- 设置身份验证和授权管道
- 创建多租户路由架构
- 构建带有单独作用域的管理界面
- 配置不同响应格式(HTML、JSON等)
最佳实践
- 使用验证路由 - 始终使用
~p符咒以确保编译时安全性 - 分组相关路由 - 使用作用域逻辑地组织路由
- 限制资源动作 - 只生成实际需要的路由
- 明确命名作用域 - 使用描述性作用域前缀和别名
- 保持管道专注 - 每个管道应具有单一职责
- 仔细排序路由 - 更具体的路由应放在通用路由之前
- 使用资源 - 优先使用
resources而非单个路由声明 - 记录自定义路由 - 为非标准路由模式添加注释
- 避免深度嵌套 - 限制嵌套资源到2-3层
- 版本化API - 使用作用域进行API版本控制
- 保护敏感路由 - 适当应用身份验证管道
- 测试路由解析 - 验证路由解析到正确控制器
- 明智使用转发 - 转发到良好定义的插头接口
- 定期检查 - 在开发中使用
mix phx.routes - 遵循约定 - 为资源坚持RESTful约定
常见陷阱
- 硬编码路径 - 使用字符串而非验证路由
- 过度嵌套资源 - 创建深度嵌套的资源层次
- 缺少管道 - 忘记通过所需管道路由
- 错误的路由顺序 - 通用路由捕获特定路由请求
- 暴露所有动作 - 生成不必要的CRUD路由
- 不使用作用域 - 重复控制器模块前缀
- 命名不一致 - 混合路由命名约定
- 跳过CSRF保护 - 移除安全插头而不了解影响
- 缺少身份验证 - 不保护敏感路由
- 重复路由 - 在多个地方定义相同路由
- 错误的HTTP动词 - 为动作使用错误的动词(例如用GET进行破坏性操作)
- 不测试路由 - 未验证路由配置
- 暴露内部路由 - 在生产中使调试/管理路由可用
- 复杂路由逻辑 - 在路由定义中放入业务逻辑
- 忽略路由冲突 - 不检查重叠路由模式