权限添加

This commit is contained in:
焦钰锟 2025-04-01 16:23:28 +08:00
parent cb05c2d487
commit 6ef8948123
10 changed files with 725 additions and 9 deletions

View File

@ -3,6 +3,7 @@
namespace app\admin\controller\auth\api; namespace app\admin\controller\auth\api;
use app\admin\model\api\AuthRule; use app\admin\model\api\AuthRule;
use app\admin\validate\ApiAuthRule;
use app\common\controller\Backend; use app\common\controller\Backend;
use fast\Tree; use fast\Tree;
use think\Cache; use think\Cache;
@ -116,11 +117,13 @@ class Rule extends Backend
} }
} }
//这里需要针对name做唯一验证 //这里需要针对name做唯一验证
$ruleValidate = \think\Loader::validate('AuthRule'); // $ruleValidate = \think\Loader::validate(ApiAuthRule::class);
$ruleValidate->rule([ // $ruleValidate->rule([
'name' => 'require|unique:AuthRule,name,' . $row->id, // 'name' => 'require|unique:ApiAuthRule,name,' . $row->id,
]); // ]);
$result = $row->validate()->save($params); // $result = $row->validate($ruleValidate)->save($params);
$result = $row->save($params);
if ($result === false) { if ($result === false) {
$this->error($row->getError()); $this->error($row->getError());
} }

View File

@ -4,6 +4,7 @@ namespace app\adminapi\controller;
use app\adminapi\model\Admin as AdminModel; use app\adminapi\model\Admin as AdminModel;
use app\adminapi\model\AuthRule;
use app\common\controller\AdminApi; use app\common\controller\AdminApi;
use think\Cookie; use think\Cookie;
use think\Hook; use think\Hook;
@ -96,5 +97,53 @@ class Admin extends AdminApi
} }
/**
* 管理员菜单
*
* @ApiMethod (GET)
* @ApiParams (name="is_tree", type="string", required=true, description="是否是树形结构")
*/
public function menu()
{
$admin_id = $this->auth->id;
$is_tree = $this->request->get('is_tree');
$menulist = (new AuthRule)->getMenulist($admin_id,["ismenu"=>1],$is_tree);
$this->success('查询成功', $menulist);
}
/**
* 权限校验(接口校验版-用于前端自行显示隐藏)
* 返回null为无权限
* @ApiMethod (GET)
* @ApiParams (name="auth_name", type="string", required=true, description="请求路径或权限标识")
*/
public function check_auth()
{
$admin_id = $this->auth->id;
$auth_name = $this->request->get('auth_name',"") ?: "***";
$check = (new AuthRule)->authCheck($admin_id,$auth_name);
$this->success('权限校验结果返回', $check);
}
/**
* 权限校验(直接返回拥有的所有权限,前端自行比对判断)
*
* @ApiMethod (GET)
*/
public function have_auth()
{
$admin_id = $this->auth->id;
$check = (new AuthRule)->getAllRules($admin_id);
$this->success('权限列表返回', $check);
}
} }

View File

@ -0,0 +1,80 @@
<?php
namespace app\adminapi\controller;
use app\adminapi\model\AuthRule;
use app\common\controller\AdminApi;
use think\Cookie;
use think\Hook;
/**
* api权限菜单管理
*
*
*/
class Rule extends AdminApi
{
protected $model = null;
/**
* 初始化操作
* @access protected
*/
public function _initialize()
{
$this->model = new AuthRule;
parent::_initialize();
}
/**
* 所有权限列表
*
* @ApiMethod (GET)
* @ApiParams (name="is_tree", type="string", required=true, description="是否是树形结构")
*/
public function rulelist()
{
$admin_id = $this->auth->id;
$is_tree = $this->request->get('is_tree');
$menulist = $this->model->getMenulist($admin_id,[],$is_tree,true);
$this->success('查询成功', $menulist);
}
/**
* 添加权限节点(接口或菜单)
*
* @ApiMethod (POST)
* @ApiParams (name="pid", type="int", required=true, description="父ID")
* @ApiParams (name="ismenu", type="int", required=true, description="是否为菜单 0接口 1菜单")
* @ApiParams (name="name", type="string", required=true, description="节点URL节点URL和外链选填其一")
* @ApiParams (name="url", type="string", required=true, description="外链节点URL和外链选填其一")
* @ApiParams (name="rule_name", type="string", required=true, description="权限标识(菜单才需要)")
* @ApiParams (name="title", type="string", required=true, description="节点中文名")
* @ApiParams (name="icon", type="string", required=true, description="图标(菜单才需要)")
* @ApiParams (name="weigh", type="int", required=true, description="权重")
* @ApiParams (name="menutype", type="string", required=true, description="菜单类型:'addtabs','blank','dialog','ajax'")
* @ApiParams (name="extend", type="string", required=true, description="额外扩展属性(比如加类名做特特殊回调逻辑)")
* @ApiParams (name="status", type="string", required=true, description="状态normal=正常hidden=隐藏)")
*
*/
public function add()
{
$admin_id = $this->auth->id;
$params = $this->request->post();
$menulist = $this->model->add($params,true);
$this->success('查询成功', $menulist);
}
}

View File

@ -4,6 +4,8 @@ namespace app\adminapi\library;
use app\admin\model\Admin; use app\admin\model\Admin;
use app\admin\model\api\AuthRule; use app\admin\model\api\AuthRule;
use app\adminapi\model\AuthGroup;
use app\adminapi\model\AuthGroupAccess;
use app\common\library\Token; use app\common\library\Token;
use app\common\model\User; use app\common\model\User;
use fast\Random; use fast\Random;
@ -90,6 +92,113 @@ class Auth
} }
public function isSuperAdmin()
{
return in_array('*', $this->getRuleIds()) ? true : false;
}
public function getRuleIds($uid=null)
{
$uid = is_null($uid) ? $this->id : $uid;
//读取用户所属用户组
$groups = $this->getGroups($uid);
$ids = []; //保存用户所属用户组设置的所有权限规则id
foreach ($groups as $g) {
$ids = array_merge($ids, explode(',', trim($g['rules'], ',')));
}
$ids = array_unique($ids);
return $ids;
}
public function getGroups($uid=null)
{
$uid = is_null($uid) ? $this->id : $uid;
//读取用户所属用户组
$group_ids = AuthGroupAccess::where("uid",$uid)->column("group_id");
$groups = [];
if($group_ids) $groups = AuthGroup::where('id', 'in', $group_ids)->select();
return $groups;
}
/**
* 取出当前管理员所拥有权限的分组
* @param boolean $withself 是否包含当前所在的分组
* @return array
*/
public function getChildrenGroupIds($withself = false)
{
//取出当前管理员所有的分组
$groups = $this->getGroups();
$groupIds = [];
foreach ($groups as $k => $v) {
$groupIds[] = $v['id'];
}
$originGroupIds = $groupIds;
foreach ($groups as $k => $v) {
if (in_array($v['pid'], $originGroupIds)) {
$groupIds = array_diff($groupIds, [$v['id']]);
unset($groups[$k]);
}
}
// 取出所有分组
$groupList = \app\adminapi\model\AuthGroup::where($this->isSuperAdmin() ? '1=1' : ['status' => 'normal'])->select();
$objList = [];
foreach ($groups as $k => $v) {
if ($v['rules'] === '*') {
$objList = $groupList;
break;
}
// 取出包含自己的所有子节点
$childrenList = Tree::instance()->init($groupList, 'pid')->getChildren($v['id'], true);
$obj = Tree::instance()->init($childrenList, 'pid')->getTreeArray($v['pid']);
$objList = array_merge($objList, Tree::instance()->getTreeList($obj));
}
$childrenGroupIds = [];
foreach ($objList as $k => $v) {
$childrenGroupIds[] = $v['id'];
}
if (!$withself) {
$childrenGroupIds = array_diff($childrenGroupIds, $groupIds);
}
return $childrenGroupIds;
}
/**
* 取出当前管理员所拥有权限的管理员
* @param boolean $withself 是否包含自身
* @return array
*/
public function getChildrenAdminIds($withself = false)
{
$childrenAdminIds = [];
if (!$this->isSuperAdmin()) {
$groupIds = $this->getChildrenGroupIds(false);
$authGroupList = \app\adminapi\model\AuthGroupAccess::field('uid,group_id')
->where('group_id', 'in', $groupIds)
->select();
foreach ($authGroupList as $k => $v) {
$childrenAdminIds[] = $v['uid'];
}
} else {
//超级管理员拥有所有人的权限
$childrenAdminIds = Admin::column('id');
}
if ($withself) {
if (!in_array($this->id, $childrenAdminIds)) {
$childrenAdminIds[] = $this->id;
}
} else {
$childrenAdminIds = array_diff($childrenAdminIds, [$this->id]);
}
return $childrenAdminIds;
}
/** /**
* 根据Token初始化 * 根据Token初始化
* *
@ -344,8 +453,12 @@ class Auth
foreach ($groups as $group){ foreach ($groups as $group){
$rules = array_merge($rules, explode(',', $group->rules)); $rules = array_merge($rules, explode(',', $group->rules));
} }
if(in_array('*',$rules)){
$this->rules = AuthRule::where('status', 'normal')->field('id,pid,rule_name,name,title,ismenu')->select();
}else{
$this->rules = AuthRule::where('status', 'normal')->where('id', 'in', $rules)->field('id,pid,rule_name,name,title,ismenu')->select();
}
$this->rules = AuthRule::where('status', 'normal')->where('id', 'in', $rules)->field('id,pid,rule_name,name,title,ismenu')->select();
return $this->rules; return $this->rules;
} }
@ -359,16 +472,21 @@ class Auth
*/ */
public function check($path = null, $module = null) public function check($path = null, $module = null)
{ {
if (!$this->_logined) { if (!$this->_logined) {
return false; return false;
} }
$ruleList = $this->getRuleList(); $ruleList = $this->getRuleList();
$rules = []; $rules = [];
foreach ($ruleList as $k => $v) { foreach ($ruleList as $k => $v) {
$rules[] = $v['name']; $rules[] = $v['name'];
} }
$url = ($module ? $module : request()->module()) . '/' . (is_null($path) ? $this->getRequestUri() : $path); // var_dump($rules);
// $url = ($module ? $module : request()->module()) . '/' . (is_null($path) ? $this->getRequestUri() : $path);
$url = (is_null($path) ? $this->getRequestUri() : $path);
$url = strtolower(str_replace('.', '/', $url)); $url = strtolower(str_replace('.', '/', $url));
return in_array($url, $rules); return in_array($url, $rules);
} }

View File

@ -0,0 +1,22 @@
<?php
namespace app\adminapi\model;
use think\Model;
class AuthGroup extends Model
{
protected $name = 'api_auth_group';
// 开启自动写入时间戳字段
protected $autoWriteTimestamp = 'int';
// 定义时间戳字段名
protected $createTime = 'createtime';
protected $updateTime = 'updatetime';
public function getNameAttr($value, $data)
{
return __($value);
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace app\adminapi\model;
use think\Model;
class AuthGroupAccess extends Model
{
//
protected $name = 'api_auth_group_access';
}

View File

@ -0,0 +1,210 @@
<?php
namespace app\adminapi\model;
use app\common\model\BaseModel;
use think\Cache;
use think\Model;
class AuthRule extends BaseModel
{
protected $name = 'api_auth_rule';
// 开启自动写入时间戳字段
protected $autoWriteTimestamp = 'int';
// 定义时间戳字段名
protected $createTime = 'createtime';
protected $updateTime = 'updatetime';
// 数据自动完成字段
protected $insert = ['py', 'pinyin'];
protected $update = ['py', 'pinyin'];
// 拼音对象
protected static $pinyin = null;
protected static function init()
{
self::$pinyin = new \Overtrue\Pinyin\Pinyin('Overtrue\Pinyin\MemoryFileDictLoader');
self::beforeWrite(function ($row) {
if (isset($_POST['row']) && is_array($_POST['row']) && isset($_POST['row']['condition'])) {
$originRow = $_POST['row'];
$row['condition'] = $originRow['condition'] ?? '';
}
});
self::afterWrite(function ($row) {
Cache::rm('__menu__');
});
}
public function getTitleAttr($value, $data)
{
return __($value);
}
public function getMenutypeList()
{
return ['addtabs' => __('Addtabs'), 'dialog' => __('Dialog'), 'ajax' => __('Ajax'), 'blank' => __('Blank')];
}
public function setPyAttr($value, $data)
{
if (isset($data['title']) && $data['title']) {
return self::$pinyin->abbr(__($data['title']));
}
return '';
}
public function setPinyinAttr($value, $data)
{
if (isset($data['title']) && $data['title']) {
return self::$pinyin->permalink(__($data['title']), '');
}
return '';
}
/**
* 获取会员组别规则列表
* @return array|bool|\PDOStatement|string|\think\Collection
*/
public function getRuleList($admin_id,$where=[],$full = false)
{
$group_ids = AuthGroupAccess::where("uid",$admin_id)->column("group_id");
if(!$group_ids && !$full) return [];
$groups = AuthGroup::where('id', 'in', $group_ids)->select();
if (!$groups && !$full ) {
return [];
}
$rules = [];
foreach ($groups as $group){
$rules = array_merge($rules, explode(',', $group->rules));
}
//包含*全查,否则按值查
if ($full || in_array('*', $rules)) {
return AuthRule::where($where)->where('status', 'normal')->order('weigh desc,id desc')->select();
}
return AuthRule::where($where)->where('status', 'normal')->where('id', 'in', $rules)->order('weigh desc,id desc')->select();
}
//得到菜单列表
public function getMenulist($admin_id,$where= ["ismenu"=>1],$is_tree=false,$full = false,$id_name='id',$pid_name='pid',$child_name='children')
{
$menu = $this->getRuleList($admin_id,$where,$full);
if(!$is_tree){
return $menu;
}
// 生成菜单的树状结构 id pid ,pid=0为顶级菜单
$menulist = [];
foreach ($menu as $k => $v) {
$v = $v->toArray();
$v[$id_name] = $v['id'];
$v[$pid_name] = $v['pid'];
$menulist[$v[$id_name]] = $v;
}
$tree = [];
foreach ($menulist as $k => $v) {
if (isset($menulist[$v[$pid_name]])) {
$menulist[$v[$pid_name]][$child_name][] = &$menulist[$v[$id_name]];
} else {
$tree[] = &$menulist[$v[$id_name]];
}
}
return $tree;
}
public function authCheck($admin_id,$auth_name)
{
$group_ids = AuthGroupAccess::where("uid",$admin_id)->column("group_id");
if(!$group_ids) return null;
$groups = AuthGroup::where('id', 'in', $group_ids)->select();
if (!$groups) {
return null;
}
$rules = [];
foreach ($groups as $group){
$rules = array_merge($rules, explode(',', $group->rules));
}
//包含*全查,否则按值查
if (in_array('*', $rules)) {
return AuthRule::where('status', 'normal')->where( 'name|rule_name',$auth_name)->find() ;
}
return AuthRule::where('status', 'normal')->where( 'name|rule_name',$auth_name)->where('id', 'in', $rules)->find();
}
public function getAllRules($admin_id)
{
$group_ids = AuthGroupAccess::where("uid",$admin_id)->column("group_id");
if(!$group_ids) return null;
$groups = AuthGroup::where('id', 'in', $group_ids)->select();
if (!$groups) {
return null;
}
$rules = [];
foreach ($groups as $group){
$rules = array_merge($rules, explode(',', $group->rules));
}
//包含*全查,否则按值查
if (in_array('*', $rules)) {
return AuthRule::where('status', 'normal')->field("name,rule_name,ismenu")->select() ;
}
return AuthRule::where('status', 'normal')->where('id', 'in', $rules)->field("name,rule_name,ismenu")->select();
}
/** 通用新增(后台api版本)
* @param $params
* @param $trans
* @return $this
* @throws \Exception
*/
public function add($params,$trans=false){
if (empty($params)) {
throw new \Exception(__('Parameter %s can not be empty', ''));
}
if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
$params[$this->dataLimitField] = $this->auth->id;
}
//判断逻辑
if($trans){
self::beginTrans();
}
$res = true;
try{
//是否采用模型验证
if ($this->modelValidate) {
$name = str_replace("\\model\\", "\\validate\\", get_class($this));
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate;
$this->validateFailException()->validate($validate);
}
$result = $this->allowField(true)->save($params);
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage().$e->getFile().$e->getLine());
}
return $this;
}
}

View File

@ -32,6 +32,62 @@ class BaseModel extends Model
/**
* 是否开启Validate验证
*/
protected $modelValidate = false;
/**
* 是否开启模型场景验证
*/
protected $modelSceneValidate = false;
/**
* 是否开启数据限制
* 支持auth/personal
* 表示按权限判断/仅限个人
* 默认为禁用,若启用请务必保证表中存在admin_id字段
*/
protected $dataLimit = false;
/**
* 数据限制字段
*/
protected $dataLimitField = 'admin_id';
/**
* 数据限制开启时自动填充限制字段值
*/
protected $dataLimitFieldAutoFill = true;
/**
* 获取数据限制的管理员ID(后台api版本)
* 禁用数据限制时返回的是null
* @return mixed
*/
protected function getDataLimitAdminIds()
{
if (!$this->dataLimit) {
return null;
}
$auth = \app\adminapi\library\Auth::instance();
if ($auth->isSuperAdmin()) {
return null;
}
$adminIds = [];
if (in_array($this->dataLimit, ['auth', 'personal'])) {
$adminIds = $this->dataLimit == 'auth' ? $auth->getChildrenAdminIds(true) : [$auth->id];
}
return $adminIds;
}
public function checkAssemblyParameters($get=[],$exclude = []){ public function checkAssemblyParameters($get=[],$exclude = []){
//得到所有get参数 //得到所有get参数
@ -288,4 +344,171 @@ class BaseModel extends Model
} }
/** 通用新增(后台api版本)
* @param $params
* @param $trans
* @return $this
* @throws \Exception
*/
public function add($params,$trans=false){
if (empty($params)) {
throw new \Exception(__('Parameter %s can not be empty', ''));
}
if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
$params[$this->dataLimitField] = $this->auth->id;
}
//判断逻辑
if($trans){
self::beginTrans();
}
$res = true;
try{
//是否采用模型验证
if ($this->modelValidate) {
$name = str_replace("\\model\\", "\\validate\\", get_class($this));
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate;
$this->validateFailException()->validate($validate);
}
$result = $this->allowField(true)->save($params);
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage().$e->getFile().$e->getLine());
}
return $this;
}
/** 通用编辑(后台api版本)
* @param $params
* @param $trans
* @return $this
* @throws \Exception
*/
public function edit($id,$params,$trans=false){
$row = $this->get($id);
if (!$row) {
throw new \Exception(__('No Results were found'));
}
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds) && !in_array($row[$this->dataLimitField], $adminIds)) {
throw new \Exception(__('You have no permission'));
}
if (empty($params)) {
throw new \Exception(__('Parameter %s can not be empty', ''));
}
//判断逻辑
if($trans){
self::beginTrans();
}
$res = true;
try{
//是否采用模型验证
if ($this->modelValidate) {
$name = str_replace("\\model\\", "\\validate\\", get_class($this));
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
$row->validateFailException()->validate($validate);
}
$result = $row->allowField(true)->save($params);
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage().$e->getFile().$e->getLine());
}
return $row;
}
/** 通用详情(后台api版本)
* @param $params
* @param $trans
* @return $this
* @throws \Exception
*/
public function detail($id,$show_field=[],$except_field=[]){
$row = $this->get($id);
if (!$row) {
throw new \Exception(__('No Results were found'));
}
if($show_field){
$row->visible($show_field);
}
if($except_field){
$row->hidden($except_field);
}
return $row;
}
// public function index($page,$limit,$where=[])
// {
// $adminIds = $this->getDataLimitAdminIds();
// $aliasName = "" ;
// if (is_array($adminIds)) {
// $where[] = [$aliasName . $this->dataLimitField, 'in', $adminIds];
// }
//
// }
/** 通用删除(后台api版本)
* @param $params
* @param $trans
* @return $this
* @throws \Exception
*/
public function del($ids = null,$trans=false){
if (empty($ids)) {
throw new \Exception(__('Parameter %s can not be empty', 'ids'));
}
//判断逻辑
$pk = $this->getPk();
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
$this->where($this->dataLimitField, 'in', $adminIds);
}
$list = $this->where($pk, 'in', $ids)->select();
$count = 0;
if($trans){
self::beginTrans();
}
$res = true;
try{
foreach ($list as $item) {
$count += $item->delete();
}
if($trans){
self::commitTrans();
}
}catch (\Exception $e){
if($trans){
self::rollbackTrans();
}
throw new \Exception($e->getMessage().$e->getFile().$e->getLine());
}
return $count;
}
} }

View File

@ -37,7 +37,7 @@ define(['jquery', 'bootstrap', 'backend', 'csmtable', 'form'], function ($, unde
{checkbox: true}, {checkbox: true},
{field: 'id', title: __('Id')}, {field: 'id', title: __('Id') ,sortable:true},
{field: 'title', title: __('Title'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content}, {field: 'title', title: __('Title'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
{field: 'headimage', title: __('Headimage'), operate: false, events: Table.api.events.image, formatter: Table.api.formatter.image}, {field: 'headimage', title: __('Headimage'), operate: false, events: Table.api.events.image, formatter: Table.api.formatter.image},
{field: 'has_expire', title: __('Has_expire'), searchList: {"1":__('Has_expire 1'),"2":__('Has_expire 2')}, formatter: Table.api.formatter.normal}, {field: 'has_expire', title: __('Has_expire'), searchList: {"1":__('Has_expire 1'),"2":__('Has_expire 2')}, formatter: Table.api.formatter.normal},

View File

@ -36,7 +36,7 @@ define(['jquery', 'bootstrap', 'backend', 'csmtable', 'form'], function ($, unde
[ [
{checkbox: true}, {checkbox: true},
{field: 'id', title: __('Id')}, {field: 'id', title: __('Id') ,sortable:true},
{field: 'title', title: __('Title'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content}, {field: 'title', title: __('Title'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
{field: 'headimage', title: __('Headimage'), operate: false, events: Table.api.events.image, formatter: Table.api.formatter.image}, {field: 'headimage', title: __('Headimage'), operate: false, events: Table.api.events.image, formatter: Table.api.formatter.image},