我正在实现用于设置用户角色的GUI,其中应用程序的管理员可以定义嵌套角色。此特定情况下的角色由简单的键值对定义,键标识角色,值是当前角色扩展的“子角色”,或者是应用程序某些部分的路径。以下是可能结构的一些任意示例:
user => path::/articles/read
user => path::/settings/edit/my
moderator => extend::user
moderator => path::/articles/edit
siteadmin => extend::moderator
siteadmin => extend::some-other-role
siteadmin => path::/user/ban
如您所见,角色可以嵌套,单个角色可以扩展多个角色。
我的目标是
你会如何解决这个问题?我在php中实现这个,但任何解决方案(伪代码,java等)都很受欢迎(假设算法正确)。
答案 0 :(得分:1)
下面的代码应该可以解决问题。您需要提供一个简单的解析器来解析'=>'左侧和右侧的值签名并将其提供给下面的Flatten.update()
方法。
代码分两个阶段进行:
1)为每个角色构建一个映射到用“path ::”声明的路径列表和用“extend ::”声明的父角色列表
2)通过遍历映射同时保留所有访问过的父节点的映射来平展每个角色的所有路径,以避免循环依赖。
class ChildValue {
Set<String> paths = new HashSet<String>();
Set<String> extend = new HashSet<String>();
}
/*
* Logic to flatten interpret and flatten out
* roles data
*/
class Flatten {
// logical mapping store
Map<String, ChildValue> mapping = new HashMap<String, ChildValue>();
// Stage 1. Build logical mappin
//
// Call this method for every "role" => "path::/path" or "extend::role" pair
void update(String roleName, String value) {
ChildValue child = mapping.get(roleName);
if (null == child) {
// create new child node
child = new ChildValue();
mapping.put(roleName, child);
}
if (value.startsWith("path::")) {
String path = value.substring(6);
child.paths.add(path);
} else {// assume "extend::"
String extend = value.substring(8);
child.extend.add(extend);
}
}
// Stage 2.
// After the mapping is build, call this method to
// find all flattened paths for the role you need
Set<String> getPathsFor(String roleName) {
Set<String> visited = new HashSet<String>();
return getPathsFor(roleName, visited);
}
// this method is called recursively
private Set<String> getPathsFor(String roleName, Set<String> visited) {
Set<String> paths = new HashSet<String>();
ChildValue child = mapping.get(roleName);
if (child != null) {
// first add all paths directly declared with "path::"
paths.addAll(child.paths);
// then add all transitive paths
for (String extendedRole : child.extend) {
// check if parent node has not yet been visited
// to avoid circular dependencies
if (!visited.contains(extendedRole)) {
// not yet visited here, add all extended paths
visited.add(extendedRole);
paths.addAll(getPathsFor(extendedRole, visited));
}else{
// node already visited, seems like we
// we have a circular dependency
throw new RuntimeException("Detected circular dep");
}
}
}
return paths;
}
}
以下是上述代码的简单测试
public class Roles {
public static void main(String[] args) {
Flatten ft = new Flatten();
ft.update("user", "path::/path1");
ft.update("user", "path::/path2");
ft.update("user", "extend::parent");
ft.update("parent", "path::/parent/path1");
ft.update("parent", "path::/parent/path2");
ft.update("parent", "extend::user");// circular dep
System.out.println("paths for user => " + ft.getPathsFor("user"));
System.out.println("paths for parent => " + ft.getPathsFor("parent"));
System.out.println("paths for unknown => " + ft.getPathsFor("unknown"));
//will print
// paths for user => [/path2, /path1, /parent/path2, /parent/path1]
// paths for parent => [/path2, /path1, /parent/path2, /parent/path1]
// paths for unknown => []
}
}
希望这能抓住一般的想法。