Elixir递归实现组权限关联模块可见性过滤技术问询
Got it, let's break down how to implement this recursive check properly. The goal is to verify if the user is in the module's group or any of its parent groups (while ignoring deleted groups), and handle the case where the module has no group at all.
Step 1: Implement the Main Function
First, the main get_visible_module function will branch based on whether the module has an associated group. For ungrouped modules, you can decide visibility (I've assumed they're public here, but adjust as needed).
Step 2: Recursive Helper Function
We'll use a private recursive helper to traverse the group hierarchy. It checks if the user is in the current group, and if not, moves up to the parent group until there are no more parents or we find a match.
Full Code Implementation
def get_visible_module(module, user_id) do case module.group do # Handle modules with no associated group: adjust this return value based on your requirements nil -> true group -> user_has_access_to_group_hierarchy?(group, user_id) end end # Recursive helper to check group and all parent groups defp user_has_access_to_group_hierarchy?(nil, _user_id), do: false # Skip deleted groups entirely defp user_has_access_to_group_hierarchy?(%{deleted: true}, _user_id), do: false defp user_has_access_to_group_hierarchy?(group, user_id) do # First, run your existing basic check: is the user directly in this group? if user_in_group?(group.id, user_id) do true else # Fetch parent group if not preloaded (or use preloaded parent if available) parent_group = if is_nil(group.parent), do: Repo.get(Group, group.parent_id), else: group.parent # Recurse on the parent group user_has_access_to_group_hierarchy?(parent_group, user_id) end end # Your existing basic check (adjust if your implementation differs) defp user_in_group?(group_id, user_id) do Repo.exists?(from ug in UserGroup, where: ug.group_id == ^group_id and ug.user_id == ^user_id) end
Key Considerations
- Handling Deleted Groups: The helper skips any group marked as
deleted: true—this ensures deleted groups don't grant access accidentally. - Preloading vs. On-Demand Fetching: The code above fetches parent groups on-demand if they aren't preloaded. To avoid N+1 database queries (which can be slow for deep hierarchies), preload the entire group hierarchy when fetching the module. Here's how you could preload recursively:
def preload_module_with_group_hierarchy(module_id) do module = Repo.get(Module, module_id) |> Repo.preload(:group) preload_parent_groups(module) end defp preload_parent_groups(%{group: nil} = module), do: module defp preload_parent_groups(%{group: group} = module) do updated_group = Repo.preload(group, :parent) preload_parent_groups(%{module | group: updated_group}) end - Ungrouped Modules: The
nilcase returnstrue—change this tofalseif ungrouped modules should be hidden by default.
Optimization Note
For very deep group hierarchies, a recursive Ecto query using Common Table Expressions (CTE) is more efficient than recursive function calls with individual DB queries. Here's a quick example of that approach:
def user_has_access_to_group_hierarchy?(group_id, user_id) do cte = """ WITH RECURSIVE group_hierarchy AS ( SELECT id, parent_id, deleted FROM groups WHERE id = ^group_id UNION ALL SELECT g.id, g.parent_id, g.deleted FROM groups g JOIN group_hierarchy gh ON g.id = gh.parent_id ) SELECT EXISTS( SELECT 1 FROM user_groups ug JOIN group_hierarchy gh ON ug.group_id = gh.id WHERE ug.user_id = ^user_id AND gh.deleted = false ) """ Repo.query!(cte, [group_id, user_id]).rows |> hd() |> hd() end
This query fetches all ancestor groups in one go and checks if the user is in any of them.
内容的提问来源于stack exchange,提问作者Sardoan

