我有一个看起来像这样的对象:
const ROUTES = {
ACCOUNT: {
TO: '/account',
RESTRICTIONS: {
shouldBeLoggedIn: true,
},
ROUTES: {
PROFILE: {
TO: '/account/profile',
RESTRICTIONS: {
shouldBeLoggedIn: true,
},
ROUTES: {
INFORMATION: {
TO: '/account/profile/information',
RESTRICTIONS: {
shouldBeLoggedIn: true,
permissions: ['EMAIL'],
},
},
PASSWORD: {
TO: '/account/profile/password',
RESTRICTIONS: {
shouldBeLoggedIn: true,
permissions: ['EMAIL', 'ADMIN'],
},
},
},
},
COLLECTIONS: {
TO: '/account/collections',
RESTRICTIONS: {
shouldBeLoggedIn: true,
permissions: ['ADMIN'],
},
},
LIKES: {
TO: '/account/likes',
RESTRICTIONS: {
shouldBeLoggedIn: true,
},
},
},
},
};
我想创建一个函数(getRoutes
),该函数根据传入的RESTRICTIONS
来过滤/减少该对象,所有permissions
必须匹配。
function getRoutes(routes, restrictions){
//...
}
const USER_RESTRICTIONS = {
shouldBeLoggedIn: true,
permissions: ['EMAIL'],
}
const allowedRoutes = getRoutes(ROUTES, USER_RESTRICTIONS)
allowedRoutes === {
ACCOUNT: {
TO: '/account',
RESTRICTIONS: {
shouldBeLoggedIn: true,
},
ROUTES: {
PROFILE: {
TO: '/account/profile',
RESTRICTIONS: {
shouldBeLoggedIn: true,
},
ROUTES: {
INFORMATION: {
TO: '/account/profile/information',
RESTRICTIONS: {
shouldBeLoggedIn: true,
permissions: ['EMAIL'],
},
},
},
},
LIKES: {
TO: '/account/likes',
RESTRICTIONS: {
shouldBeLoggedIn: true,
},
},
},
},
} ? 'YAY' : 'NAY'
答案 0 :(得分:2)
首先,在不考虑递归内容的情况下,请确保已正确定义规则逻辑。
我尝试使用您所需的API编写验证功能,但认为它不易读。您可能需要稍后对其进行重构。 (提示:编写一些单元测试!)
下面的示例从您的树中获取一个规则配置对象和一个节点。它返回一个布尔值,指示节点是否符合要求。
const includedIn = xs => x => xs.includes(x);
// RuleSet -> Path -> bool
const isAllowed = ({ shouldBeLoggedIn = false, permissions = [] }) =>
({ RESTRICTIONS }) => (
(shouldBeLoggedIn ? RESTRICTIONS.shouldBeLoggedIn : true) &&
RESTRICTIONS.permissions.every(includedIn(permissions))
);
console.log(
[
{ RESTRICTIONS: { shouldBeLoggedIn: true, permissions: [ ] } },
{ RESTRICTIONS: { shouldBeLoggedIn: true, permissions: [ 'EMAIL' ] } },
{ RESTRICTIONS: { shouldBeLoggedIn: true, permissions: [ 'EMAIL', 'ADMIN' ] } }
].map(
isAllowed({ shouldBeLoggedIn: true, permissions: [ 'EMAIL'] })
)
)
使用这段代码后,您可以开始考虑如何遍历树。您基本上要定义的是如何遍历每条路径以及何时返回。
如果我们只想登录,则是(1)检查ROUTES
,以及(2)循环访问v.ROUTES
对象内部的条目的问题。
const traverse = obj => {
Object
.entries(obj)
.forEach(
([k, v]) => {
console.log(v.TO);
if (v.ROUTES) traverse(v.ROUTES)
}
)
};
traverse(getRoutes());
function getRoutes() {
return {
ACCOUNT: {
TO: '/account',
RESTRICTIONS: {
shouldBeLoggedIn: true,
},
ROUTES: {
PROFILE: {
TO: '/account/profile',
RESTRICTIONS: {
shouldBeLoggedIn: true,
},
ROUTES: {
INFORMATION: {
TO: '/account/profile/information',
RESTRICTIONS: {
shouldBeLoggedIn: true,
permissions: ['EMAIL'],
},
},
PASSWORD: {
TO: '/account/profile/password',
RESTRICTIONS: {
shouldBeLoggedIn: true,
permissions: ['EMAIL', 'ADMIN'],
},
},
},
},
COLLECTIONS: {
TO: '/account/collections',
RESTRICTIONS: {
shouldBeLoggedIn: true,
permissions: ['ADMIN'],
},
},
LIKES: {
TO: '/account/likes',
RESTRICTIONS: {
shouldBeLoggedIn: true,
},
},
},
},
};
};
这是最困难的部分:创建新的树结构。
我选择采取两个步骤:
filter
列出未通过验证的值,如果存在子路由,我们将创建一个具有过滤的ROUTES值的新路径对象。
const traverse = (obj, pred) => Object
.fromEntries(
Object
.entries(obj)
.filter(
([k, v]) => pred(v) // Get rid of the paths that don't match restrictions
)
.map(
([k, v]) => [
k, v.ROUTES
// If there are child paths, filter those as well (i.e. recurse)
? Object.assign({}, v, { ROUTES: traverse(v.ROUTES, pred) })
: v
]
)
);
const includedIn = xs => x => xs.includes(x);
const isAllowed = ({ shouldBeLoggedIn = false, permissions = [] }) =>
({ RESTRICTIONS }) => (
(shouldBeLoggedIn ? RESTRICTIONS.shouldBeLoggedIn : true) &&
(RESTRICTIONS.permissions || []).every(includedIn(permissions))
);
console.log(
traverse(
getRoutes(),
isAllowed({ shouldBeLoggedIn: true, permissions: [ 'EMAIL'] })
)
)
function getRoutes() {
return {
ACCOUNT: {
TO: '/account',
RESTRICTIONS: {
shouldBeLoggedIn: true,
},
ROUTES: {
PROFILE: {
TO: '/account/profile',
RESTRICTIONS: {
shouldBeLoggedIn: true,
},
ROUTES: {
INFORMATION: {
TO: '/account/profile/information',
RESTRICTIONS: {
shouldBeLoggedIn: true,
permissions: ['EMAIL'],
},
},
PASSWORD: {
TO: '/account/profile/password',
RESTRICTIONS: {
shouldBeLoggedIn: true,
permissions: ['EMAIL', 'ADMIN'],
},
},
},
},
COLLECTIONS: {
TO: '/account/collections',
RESTRICTIONS: {
shouldBeLoggedIn: true,
permissions: ['ADMIN'],
},
},
LIKES: {
TO: '/account/likes',
RESTRICTIONS: {
shouldBeLoggedIn: true,
},
},
},
},
};
};
我希望这个例子可以帮助您入门,并让您能够编写自己的/抛光版本。让我知道是否错过任何要求。
答案 1 :(得分:0)
我像这样“解决”它:
export const checkLoggedIn = (shouldBeLoggedIn, isAuthenticated) => {
if (!shouldBeLoggedIn) {
return true;
}
return isAuthenticated;
};
function isRouteAllowed(route, restrictions) {
const routeShouldBeLoggedIn = route.RESTRICTIONS.shouldBeLoggedIn;
const passedLoggedInCheck = checkLoggedIn(
routeShouldBeLoggedIn,
restrictions.get('shouldBeLoggedIn')
);
if (!passedLoggedInCheck) {
return false;
} else {
const routePermissions = route.RESTRICTIONS.permissions;
if (!routePermissions) {
return true;
} else {
const passedPermissions = routePermissions.every((permission) => {
const restrictPermissions = restrictions.get('permissions');
return (
restrictPermissions &&
restrictPermissions.find &&
restrictPermissions.find(
(userPermission) => userPermission === permission
)
);
});
return passedLoggedInCheck && passedPermissions;
}
}
}
function forEachRoute(
routes,
restrictions,
routesToDelete = [],
parentPath = []
) {
const routeSize = Object.keys(routes).length - 1;
Object.entries(routes).forEach(([key, route], index) => {
const childRoutes = route.ROUTES;
if (childRoutes) {
parentPath.push(key);
parentPath.push('ROUTES');
forEachRoute(childRoutes, restrictions, routesToDelete, parentPath);
} else {
const allowed = isRouteAllowed(route, restrictions);
if (!allowed) {
const toAdd = [...parentPath, key];
routesToDelete.push(toAdd);
}
}
if (routeSize === index) {
// new parent
parentPath.pop();
parentPath.pop();
}
});
}
const deletePropertyByPath = (object, path) => {
let currentObject = object;
let parts = path.split('.');
const last = parts.pop();
for (const part of parts) {
currentObject = currentObject[part];
if (!currentObject) {
return;
}
}
delete currentObject[last];
};
export function removeRestrictedRoutes(routes, restrictions) {
let routesToDelete = [];
forEachRoute(routes, restrictions, routesToDelete);
let allowedRoutes = routes;
routesToDelete.forEach((path) => {
deletePropertyByPath(allowedRoutes, path.join('.'));
});
return allowedRoutes;
}
使用方式如下:
const USER_RESTRICTIONS = {
shouldBeLoggedIn: true,
permissions: ['EMAIL'],
}
const allowedRoutes = getRoutes(ROUTES, USER_RESTRICTIONS)
不是性能最高的解决方案,但它确实有效。 @ user3297291解决方案似乎更好,因此可以对其进行重构,只需使其更具可读性即可。我认为用.reduce()
解决方案将是最好的解决方案,但也许不可能。
答案 2 :(得分:0)
我的版本在算法上与user3297291的版本没有区别。但是代码设计有些不同。
我尝试在对象遍历和匹配测试中都更加通用。我希望两者都是可重用的功能。遍历采用一个谓词和一个子代的属性名称(在您的情况下,'ROUTES'
)供子代递归,并返回一个过滤提供给它的对象的函数。
对于谓词,我将调用matchesRestrictions
的结果与您的USER_RESTRICTIONS
对象类似。认为可能还会有其他限制。我假设如果值是布尔值,那么该对象的键值必须具有相同的布尔值。如果是数组,则其中的每个项目都必须在该键处出现在数组中。添加其他类型很容易。但是,这可能太通用了。我真的不知道在USER_PERMMISSIONS
或RESTRICTIONS
部分中还会出现什么。
这是我想出的代码:
const filterObj = (pred, children) => (obj) =>
Object .fromEntries (
Object .entries (obj)
.filter ( ([k, v]) => pred (v))
.map ( ([k, v]) => [
k,
v [children]
? {
...v,
[children]: filterObj (pred, children) (v [children])
}
: v
]
)
)
const matchesRestrictions = (config) => ({RESTRICTIONS = {}}) =>
Object .entries (RESTRICTIONS) .every (([key, val]) =>
typeof val == 'boolean'
? config [key] === val
: Array.isArray (val)
? val .every (v => (config [key] || []) .includes (v))
: true // What else do you want to handle?
)
const ROUTES = {ACCOUNT: {TO: "/account", RESTRICTIONS: {shouldBeLoggedIn: true}, ROUTES: {PROFILE: {TO: "/account/profile", RESTRICTIONS: {shouldBeLoggedIn: true}, ROUTES: {INFORMATION: {TO: "/account/profile/information", RESTRICTIONS: {shouldBeLoggedIn: true, permissions: ["EMAIL"]}}, PASSWORD: {TO: "/account/profile/password", RESTRICTIONS: {shouldBeLoggedIn: true, permissions: ["EMAIL", "ADMIN"]}}}}, COLLECTIONS: {TO: "/account/collections", RESTRICTIONS: {shouldBeLoggedIn: true, permissions: ["ADMIN"]}}, LIKES: {TO: "/account/likes", RESTRICTIONS: {shouldBeLoggedIn: true}}}}};
const USER_RESTRICTIONS = {shouldBeLoggedIn: true, permissions: ['EMAIL']}
console .log (
filterObj (matchesRestrictions (USER_RESTRICTIONS), 'ROUTES') (ROUTES)
)
我不知道通用filterObj
最终是如何。但是我确实用另一个对象和通往孩子的不同路径对其进行了测试:
const obj = {x: {foo: 1, val: 20, kids: {a: {foo: 2, val: 15, kids: {b: {foo: 3, val: 8}, c: {foo: 4, val: 17}, d: {foo: 5, val: 12}}}, e: {foo: 6, val: 5, kids: {f: {foo: 7, val: 23}, g: {foo: 8, val: 17}}}, h: {foo: 9, val: 11, kids: {i: {foo: 10, val: 3}, j: {foo: 11, val: 7}}}}}, y: {foo: 12, val: 8}, z: {foo: 13, val: 25, kids: {k: {foo: 14, val: 18, kids: {l: {foo: 5, val: 3}, m: {foo: 11, val: 7}}}}}}
const pred = ({val}) => val > 10
filterObj ( pred, 'kids') (obj)
获得此结果:
{x: {foo: 1, kids: {a: {foo: 2, kids: {c: {foo: 4, val: 17}, d: {foo: 5, val: 12}}, val: 15}, h: {foo: 9, kids: {}, val: 11}}, val: 20}, z: {foo: 13, kids: {k: {foo: 14, kids: {}, val: 18}}, val: 25}}
因此至少可以重用。