本篇主要讲权限管理(RBAC)
RBAC是什么?
RBAC 是基于角色的访问控制(Role-Based Access Control )
翻译过来就是让不同角色拥有不同管理权限
RBAC怎么实现?
后台需要登录
要有用户表
id | username | realname | password | salt | create_time | update_time | status |
---|---|---|---|---|---|---|---|
主键 | 用户名 | 真实姓名 | 密码 | 密码盐 | 创建时间 | 更新时间 | 用户状态 |
有了用户登录了以后肯定要见到需要管理的东西,我这里管理的东西是其他后台,为了不混淆概念给其他后台起个统一别名,改名为应用
于是有了应用表
id | keyword | name | create_time | update_time | status | app_url |
---|---|---|---|---|---|---|
主键 | 应用的关键词 | 应用名称 | 创建时间 | 更新时间 | 应用状态 | 应用后台的地址 |
现在当前登录的用户有了可以管理的东西了
有了应用以后就需要考虑角色问题了,一个应用中有诸多角色,比如运营,产品,测试,超级管理员等
角色表
id | app_id | name | auth | level | create_time | update_time | status |
---|---|---|---|---|---|---|---|
主键 | 角色对应用的id | 角色名称 | 角色所拥有的权限 | 角色的等级 | 创建时间 | 更新时间 | 角色状态 |
这里讲解一下,角色表是和应用表关联起来的,应用为参照物,应用=>角色
应该是一对多的关系。
角色=>应用
应该是一对一的关系。
现在有了用户表、应用表、角色表,要知道最终要实现的是对资源(路由)的管理控制,所以还需要另一个管理资源的表
资源表
id | app_id | fid | name | url | is_show | create_time | update_time |
---|---|---|---|---|---|---|---|
主键 | 资源所属哪个应用 | 资源的父级id(无限极分类使用) | 资源(路由)名称 | 是否显示该路由 | 创建时间 | 更新时间 | 资源状态(是否启用) |
现在一共创建了4张表分别是
1.用户表 (管理用户)
2.应用表 (管理应用)
3.角色表 (管理角色)
4.资源表(管理控制器方法资源)
现在需要想一个问题?
我想去给应用A下的角色运营A分配一个Banner管理权限该如何把这4张表联系起来?
即如何表述 用户 角色 和 资源(权限)之间的关系。
可以这样实现
用一张表记录上用户id、角色id、应用id
所以第五张表诞生了
关系表
id | app_id | role_id | user_id |
---|---|---|---|
主键 | 应用表的主键id | 角色表的主键id | 用户表的主键id |
为什么没有资源表的主键id呢?因为资源表中已经有了与应用表
作为关联的外键(join查询 或者 in 都可以),所以关联了应用id就相当于关联了资源表。
关系表最终实现的是一种关系记录,例如:张三拥有官网banner的banner的curd操作
部分代码:
前端Layui+后端Yii2+缓存Redis
1.资源表遍历无限极分类(为前端美化所提供的方法)
public static function getTree($data, $id, $fid = '', $spac = 0)
{
if (empty($data)) {
return '';
}
//子类每次后面追加2个空格
$spac = $spac + 2;
static $str;
foreach ($data as $k => $v) {
if ($v['fid'] == $id) {
if ($id == 0) {
//这里可以改成根据自己需要的样式
$str .= "<option value='" . $v['id'] . "'";
if ($fid == $v['id']) {
$str .= ' selected';
}
$str .= ">" . $v['name'] . "</option>";
} else {
$str .= "<option value='" . $v['id'] . "'";
if ($fid == $v['id']) {
$str .= ' selected';
}
$str .= ">" . str_repeat(" ", $spac) . "|--" . $v['name'] . "</option>";
}
self::getTree($data, $v['id'], $fid, $spac);
}
}
return $str;
}
2.资源新增
public function saveData($params)
{
$data = [];
//这里如果是修改的话 可以改成 $model = self::findOne(主键ID);
$model = new self();
$data['app_id'] = intval($params['app_id']);
$data['fid'] = intval($params['fid']);
$data['name'] = trim($params['name']);
$data['url'] = trim($params['url']);
$data['is_show'] = intval($params['is_show']);
$data['create_time'] = time();
$model->setAttributes($data);
try {
if ($model->validate() && $model->save()) {
//更新缓存
$app_id = $model->app_id;
$functionData = self::find()->where(['app_id' => $app_id])->all();
$functionArr = Functions::getArrTree($functionData, 0);
$appKeyword = App::find()->select('keyword')->where(['id' => $app_id])->scalar();
$cache = new BaseCache();
//设置好redis的 key
$cacheKey = CacheKey::BASE_BACKEND_RESOURCES_KEY.$appKeyword;
//redis set
$cache->set($cacheKey, json_encode($functionArr, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES));
return ['status' => 1, 'msg' => '添加成功'];
} else {
$errors = array_values($model->getFirstErrors());
throw new Exception($errors[0]);
}
} catch (Exception $e) {
return ['status' => 0, 'msg' => $e->getMessage()];
}
}
public static function getArrTree($data, $fid)
{
if (empty($data)) {
return '';
}
$tree = [];
foreach ($data as $k => $v) {
if ($v['fid'] == $fid) {
$rdata['id'] = $v['id'];
$rdata['fid'] = $v['fid'];
$rdata['name'] = $v['name'];
$rdata['url'] = $v['url'];
$rdata['is_show'] = $v['is_show'];
$rdata['child'] = self::getArrTree($data, $rdata['id']);
$tree[] = $rdata;
}
}
return $tree;
}
//为了存进缓存的无限极分类
public static function getArrTree($data, $fid)
{
if (empty($data)) {
return '';
}
$tree = [];
foreach ($data as $k => $v) {
if ($v['fid'] == $fid) {
$rdata['id'] = $v['id'];
$rdata['fid'] = $v['fid'];
$rdata['name'] = $v['name'];
$rdata['url'] = $v['url'];
$rdata['is_show'] = $v['is_show'];
$rdata['child'] = self::getArrTree($data, $rdata['id']);
$tree[] = $rdata;
}
}
return $tree;
}
后端整体实现思路:
每次更新权限时,数据库先更新,从数据库查询出更新后的数据存入缓存。