You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Phoenix框架基于用户角色的模板部分渲染权限控制技术问询

基于角色控制Phoenix模板内容渲染的实现方案

嘿,这个需求在Phoenix项目里太常见了,我给你梳理一套清晰的实现步骤,从模板渲染到控制器权限保护都覆盖到,确保既满足前端展示需求,又保证后端安全:

1. 确认用户模型的角色字段

首先确保你的User模型已经包含:role字段(字符串类型,值为"user"或"admin")。如果之前用phx.gen.html生成时没加,需要跑个数据库迁移补上,但你说项目已经有用户和角色,这里默认已经配置好,比如在lib/your_app/users/user.ex里有类似定义:

field :role, :string, default: "user"

2. 在视图中添加权限判断辅助函数

Phoenix的视图是存放模板辅助逻辑的最佳位置,打开lib/your_app_web/views/user_view.ex,添加两个权限判断函数:

# 判断是否有权限编辑用户
def can_edit?(current_user, target_user) do
  current_user.role == "admin" || current_user.id == target_user.id
end

# 判断是否有权限删除用户(这里可以根据需求调整,比如普通用户不能删自己)
def can_delete?(current_user, target_user) do
  current_user.role == "admin"
end

3. 修改模板实现条件渲染

打开用户列表模板lib/your_app_web/templates/user/index.html.heex,找到表格里的操作按钮部分,把原来固定渲染的按钮改成条件渲染:

<td>
  <!-- Show按钮所有用户都能看,保持不变 -->
  <span><%= link "Show", to: Routes.user_path(@conn, :show, user) %></span>
  
  <!-- 只有满足编辑权限时才渲染Edit按钮 -->
  <%= if can_edit?(@current_user, user) do %>
    <span><%= link "Edit", to: Routes.user_path(@conn, :edit, user) %></span>
  <% end %>
  
  <!-- 只有满足删除权限时才渲染Delete按钮 -->
  <%= if can_delete?(@current_user, user) do %>
    <span><%= link "Delete", to: Routes.user_path(@conn, :delete, user), method: :delete, data: [confirm: "Are you sure?"] %></span>
  <% end %>
</td>

注意:要确保控制器已经把当前登录用户current_user传给模板,比如在UsersController的index动作里:

def index(conn, _params) do
  users = Users.list_users()
  current_user = conn.assigns.current_user # 假设已通过认证中间件赋值
  render(conn, "index.html", users: users, current_user: current_user)
end

4. 控制器层面的权限保护(重中之重!)

模板隐藏按钮只是前端视觉控制,必须在控制器里做后端权限校验,不然用户可以直接构造URL访问编辑/删除接口。打开lib/your_app_web/controllers/users_controller.ex,修改edit、update、delete动作:

先在控制器里定义和视图一致的权限判断函数(或者直接调用视图的函数,不过控制器单独定义更清晰):

defp can_edit?(current_user, target_user) do
  current_user.role == "admin" || current_user.id == target_user.id
end

defp can_delete?(current_user, target_user) do
  current_user.role == "admin"
end

然后修改对应动作:

def edit(conn, %{"id" => id}) do
  user = Users.get_user!(id)
  current_user = conn.assigns.current_user

  if can_edit?(current_user, user) do
    changeset = Users.change_user(user)
    render(conn, "edit.html", user: user, changeset: changeset)
  else
    conn
    |> put_flash(:error, "你没有权限编辑该用户")
    |> redirect(to: Routes.user_path(conn, :index))
  end
end

def update(conn, %{"id" => id, "user" => user_params}) do
  user = Users.get_user!(id)
  current_user = conn.assigns.current_user

  if can_edit?(current_user, user) do
    case Users.update_user(user, user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "用户更新成功")
        |> redirect(to: Routes.user_path(conn, :show, user))

      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "edit.html", user: user, changeset: changeset)
    end
  else
    conn
    |> put_flash(:error, "你没有权限更新该用户")
    |> redirect(to: Routes.user_path(conn, :index))
  end
end

def delete(conn, %{"id" => id}) do
  user = Users.get_user!(id)
  current_user = conn.assigns.current_user

  if can_delete?(current_user, user) do
    {:ok, _user} = Users.delete_user(user)

    conn
    |> put_flash(:info, "用户删除成功")
    |> redirect(to: Routes.user_path(conn, :index))
  else
    conn
    |> put_flash(:error, "你没有权限删除该用户")
    |> redirect(to: Routes.user_path(conn, :index))
  end
end

5. 可选:用权限库简化复杂逻辑

如果以后你的权限规则变复杂(比如新增角色、更细的权限粒度),可以考虑用bodyguard这类Elixir权限库,它能帮你更系统地管理权限,避免重复写判断逻辑。不过对于当前的简单场景,上面的手动判断已经足够灵活好用了。

内容的提问来源于stack exchange,提问作者Sorebrez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 11:12:56