소스 검색

pc权限,管理员,管理组

lizhen_gitee 9 달 전
부모
커밋
6b0e22a066

+ 297 - 0
application/company/controller/auth/Admin.php

@@ -0,0 +1,297 @@
+<?php
+
+namespace app\admin\controller\auth;
+
+use app\admin\model\AuthGroup;
+use app\admin\model\AuthGroupAccess;
+use app\common\controller\Backend;
+use fast\Random;
+use fast\Tree;
+use think\Db;
+use think\Validate;
+
+/**
+ * 管理员管理
+ *
+ * @icon   fa fa-users
+ * @remark 一个管理员可以有多个角色组,左侧的菜单根据管理员所拥有的权限进行生成
+ */
+class Admin extends Backend
+{
+
+    /**
+     * @var \app\admin\model\Admin
+     */
+    protected $model = null;
+    protected $selectpageFields = 'id,username,nickname,avatar';
+    protected $searchFields = 'id,username,nickname';
+    protected $childrenGroupIds = [];
+    protected $childrenAdminIds = [];
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('Admin');
+
+        $this->childrenAdminIds = $this->auth->getChildrenAdminIds($this->auth->isSuperAdmin());
+        $this->childrenGroupIds = $this->auth->getChildrenGroupIds($this->auth->isSuperAdmin());
+
+        $groupList = collection(AuthGroup::where('id', 'in', $this->childrenGroupIds)->select())->toArray();
+
+        Tree::instance()->init($groupList);
+        $groupdata = [];
+        if ($this->auth->isSuperAdmin()) {
+            $result = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0));
+            foreach ($result as $k => $v) {
+                $groupdata[$v['id']] = $v['name'];
+            }
+        } else {
+            $result = [];
+            $groups = $this->auth->getGroups();
+            foreach ($groups as $m => $n) {
+                $childlist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray($n['id']));
+                $temp = [];
+                foreach ($childlist as $k => $v) {
+                    $temp[$v['id']] = $v['name'];
+                }
+                $result[__($n['name'])] = $temp;
+            }
+            $groupdata = $result;
+        }
+
+        $this->view->assign('groupdata', $groupdata);
+        $this->assignconfig("admin", ['id' => $this->auth->id]);
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        //设置过滤方法
+        $this->request->filter(['strip_tags', 'trim']);
+        if ($this->request->isAjax()) {
+            //如果发送的来源是Selectpage,则转发到Selectpage
+            if ($this->request->request('keyField')) {
+                return $this->selectpage();
+            }
+            $childrenGroupIds = $this->childrenGroupIds;
+            $groupName = AuthGroup::where('id', 'in', $childrenGroupIds)
+                ->column('id,name');
+            $authGroupList = AuthGroupAccess::where('group_id', 'in', $childrenGroupIds)
+                ->field('uid,group_id')
+                ->select();
+
+            $adminGroupName = [];
+            foreach ($authGroupList as $k => $v) {
+                if (isset($groupName[$v['group_id']])) {
+                    $adminGroupName[$v['uid']][$v['group_id']] = $groupName[$v['group_id']];
+                }
+            }
+            $groups = $this->auth->getGroups();
+            foreach ($groups as $m => $n) {
+                $adminGroupName[$this->auth->id][$n['id']] = $n['name'];
+            }
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+
+            $list = $this->model
+                ->where($where)
+                ->where('id', 'in', $this->childrenAdminIds)
+                ->field(['password', 'salt', 'token'], true)
+                ->order($sort, $order)
+                ->paginate($limit);
+
+            foreach ($list as $k => &$v) {
+                $groups = isset($adminGroupName[$v['id']]) ? $adminGroupName[$v['id']] : [];
+                $v['groups'] = implode(',', array_keys($groups));
+                $v['groups_text'] = implode(',', array_values($groups));
+            }
+            unset($v);
+            $result = array("total" => $list->total(), "rows" => $list->items());
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 添加
+     */
+    public function add()
+    {
+        if ($this->request->isPost()) {
+            $this->token();
+            $params = $this->request->post("row/a");
+            if ($params) {
+                Db::startTrans();
+                try {
+                    if (!Validate::is($params['password'], '\S{6,30}')) {
+                        exception(__("Please input correct password"));
+                    }
+                    $params['salt'] = Random::alnum();
+                    $params['password'] = $this->auth->getEncryptPassword($params['password'], $params['salt']);
+                    $params['avatar'] = '/assets/img/avatar.png'; //设置新管理员默认头像。
+                    $result = $this->model->validate('Admin.add')->save($params);
+                    if ($result === false) {
+                        exception($this->model->getError());
+                    }
+                    $group = $this->request->post("group/a");
+
+                    //过滤不允许的组别,避免越权
+                    $group = array_intersect($this->childrenGroupIds, $group);
+                    if (!$group) {
+                        exception(__('The parent group exceeds permission limit'));
+                    }
+
+                    $dataset = [];
+                    foreach ($group as $value) {
+                        $dataset[] = ['uid' => $this->model->id, 'group_id' => $value];
+                    }
+                    model('AuthGroupAccess')->saveAll($dataset);
+                    Db::commit();
+                } catch (\Exception $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                }
+                $this->success();
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 编辑
+     */
+    public function edit($ids = null)
+    {
+        $row = $this->model->get(['id' => $ids]);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        if (!in_array($row->id, $this->childrenAdminIds)) {
+            $this->error(__('You have no permission'));
+        }
+        if ($this->request->isPost()) {
+            $this->token();
+            $params = $this->request->post("row/a");
+            if ($params) {
+                Db::startTrans();
+                try {
+                    if ($params['password']) {
+                        if (!Validate::is($params['password'], '\S{6,30}')) {
+                            exception(__("Please input correct password"));
+                        }
+                        $params['salt'] = Random::alnum();
+                        $params['password'] = $this->auth->getEncryptPassword($params['password'], $params['salt']);
+                    } else {
+                        unset($params['password'], $params['salt']);
+                    }
+                    //这里需要针对username和email做唯一验证
+                    $adminValidate = \think\Loader::validate('Admin');
+                    $adminValidate->rule([
+                        'username' => 'require|regex:\w{3,30}|unique:admin,username,' . $row->id,
+                        'email'    => 'require|email|unique:admin,email,' . $row->id,
+                        'mobile'   => 'regex:1[3-9]\d{9}|unique:admin,mobile,' . $row->id,
+                        'password' => 'regex:\S{32}',
+                    ]);
+                    $result = $row->validate('Admin.edit')->save($params);
+                    if ($result === false) {
+                        exception($row->getError());
+                    }
+
+                    // 先移除所有权限
+                    model('AuthGroupAccess')->where('uid', $row->id)->delete();
+
+                    $group = $this->request->post("group/a");
+
+                    // 过滤不允许的组别,避免越权
+                    $group = array_intersect($this->childrenGroupIds, $group);
+                    if (!$group) {
+                        exception(__('The parent group exceeds permission limit'));
+                    }
+
+                    $dataset = [];
+                    foreach ($group as $value) {
+                        $dataset[] = ['uid' => $row->id, 'group_id' => $value];
+                    }
+                    model('AuthGroupAccess')->saveAll($dataset);
+                    Db::commit();
+                } catch (\Exception $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                }
+                $this->success();
+            }
+            $this->error(__('Parameter %s can not be empty', ''));
+        }
+        $grouplist = $this->auth->getGroups($row['id']);
+        $groupids = [];
+        foreach ($grouplist as $k => $v) {
+            $groupids[] = $v['id'];
+        }
+        $this->view->assign("row", $row);
+        $this->view->assign("groupids", $groupids);
+        return $this->view->fetch();
+    }
+
+    /**
+     * 删除
+     */
+    public function del($ids = "")
+    {
+        if (!$this->request->isPost()) {
+            $this->error(__("Invalid parameters"));
+        }
+        $ids = $ids ? $ids : $this->request->post("ids");
+        if ($ids) {
+            $ids = array_intersect($this->childrenAdminIds, array_filter(explode(',', $ids)));
+            // 避免越权删除管理员
+            $childrenGroupIds = $this->childrenGroupIds;
+            $adminList = $this->model->where('id', 'in', $ids)->where('id', 'in', function ($query) use ($childrenGroupIds) {
+                $query->name('auth_group_access')->where('group_id', 'in', $childrenGroupIds)->field('uid');
+            })->select();
+            if ($adminList) {
+                $deleteIds = [];
+                foreach ($adminList as $k => $v) {
+                    $deleteIds[] = $v->id;
+                }
+                $deleteIds = array_values(array_diff($deleteIds, [$this->auth->id]));
+                if ($deleteIds) {
+                    Db::startTrans();
+                    try {
+                        $this->model->destroy($deleteIds);
+                        model('AuthGroupAccess')->where('uid', 'in', $deleteIds)->delete();
+                        Db::commit();
+                    } catch (\Exception $e) {
+                        Db::rollback();
+                        $this->error($e->getMessage());
+                    }
+                    $this->success();
+                }
+                $this->error(__('No rows were deleted'));
+            }
+        }
+        $this->error(__('You have no permission'));
+    }
+
+    /**
+     * 批量更新
+     * @internal
+     */
+    public function multi($ids = "")
+    {
+        // 管理员禁止批量操作
+        $this->error();
+    }
+
+    /**
+     * 下拉搜索
+     */
+    public function selectpage()
+    {
+        $this->dataLimit = 'auth';
+        $this->dataLimitField = 'id';
+        return parent::selectpage();
+    }
+}

+ 146 - 0
application/company/controller/auth/Adminlog.php

@@ -0,0 +1,146 @@
+<?php
+
+namespace app\admin\controller\auth;
+
+use app\admin\model\AuthGroup;
+use app\common\controller\Backend;
+
+/**
+ * 管理员日志
+ *
+ * @icon   fa fa-users
+ * @remark 管理员可以查看自己所拥有的权限的管理员日志
+ */
+class Adminlog extends Backend
+{
+
+    /**
+     * @var \app\admin\model\AdminLog
+     */
+    protected $model = null;
+    protected $childrenGroupIds = [];
+    protected $childrenAdminIds = [];
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('AdminLog');
+
+        $this->childrenAdminIds = $this->auth->getChildrenAdminIds(true);
+        $this->childrenGroupIds = $this->auth->getChildrenGroupIds(true);
+
+        $groupName = AuthGroup::where('id', 'in', $this->childrenGroupIds)
+            ->column('id,name');
+
+        $this->view->assign('groupdata', $groupName);
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        //设置过滤方法
+        $this->request->filter(['strip_tags', 'trim']);
+        if ($this->request->isAjax()) {
+            list($where, $sort, $order, $offset, $limit) = $this->buildparams();
+            $isSuperAdmin = $this->auth->isSuperAdmin();
+            $childrenAdminIds = $this->childrenAdminIds;
+            $list = $this->model
+                ->where($where)
+                ->where(function ($query) use ($isSuperAdmin, $childrenAdminIds) {
+                    if (!$isSuperAdmin) {
+                        $query->where('admin_id', 'in', $childrenAdminIds);
+                    }
+                })
+                ->field('content,useragent', true)
+                ->order($sort, $order)
+                ->paginate($limit);
+
+            $result = array("total" => $list->total(), "rows" => $list->items());
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 详情
+     */
+    public function detail($ids)
+    {
+        $row = $this->model->get(['id' => $ids]);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        if (!$this->auth->isSuperAdmin()) {
+            if (!$row['admin_id'] || !in_array($row['admin_id'], $this->childrenAdminIds)) {
+                $this->error(__('You have no permission'));
+            }
+        }
+        $this->view->assign("row", $row->toArray());
+        return $this->view->fetch();
+    }
+
+    /**
+     * 添加
+     * @internal
+     */
+    public function add()
+    {
+        $this->error();
+    }
+
+    /**
+     * 编辑
+     * @internal
+     */
+    public function edit($ids = null)
+    {
+        $this->error();
+    }
+
+    /**
+     * 删除
+     */
+    public function del($ids = "")
+    {
+        if (!$this->request->isPost()) {
+            $this->error(__("Invalid parameters"));
+        }
+        $ids = $ids ? $ids : $this->request->post("ids");
+        if ($ids) {
+            $isSuperAdmin = $this->auth->isSuperAdmin();
+            $childrenAdminIds = $this->childrenAdminIds;
+            $adminList = $this->model->where('id', 'in', $ids)
+                ->where(function ($query) use ($isSuperAdmin, $childrenAdminIds) {
+                    if (!$isSuperAdmin) {
+                        $query->where('admin_id', 'in', $childrenAdminIds);
+                    }
+                })
+                ->select();
+            if ($adminList) {
+                $deleteIds = [];
+                foreach ($adminList as $k => $v) {
+                    $deleteIds[] = $v->id;
+                }
+                if ($deleteIds) {
+                    $this->model->destroy($deleteIds);
+                    $this->success();
+                }
+            }
+        }
+        $this->error();
+    }
+
+    /**
+     * 批量更新
+     * @internal
+     */
+    public function multi($ids = "")
+    {
+        // 管理员禁止批量操作
+        $this->error();
+    }
+
+}

+ 317 - 0
application/company/controller/auth/Group.php

@@ -0,0 +1,317 @@
+<?php
+
+namespace app\admin\controller\auth;
+
+use app\admin\model\AuthGroup;
+use app\common\controller\Backend;
+use fast\Tree;
+use think\Db;
+use think\Exception;
+
+/**
+ * 角色组
+ *
+ * @icon   fa fa-group
+ * @remark 角色组可以有多个,角色有上下级层级关系,如果子角色有角色组和管理员的权限则可以派生属于自己组别下级的角色组或管理员
+ */
+class Group extends Backend
+{
+
+    /**
+     * @var \app\admin\model\AuthGroup
+     */
+    protected $model = null;
+    //当前登录管理员所有子组别
+    protected $childrenGroupIds = [];
+    //当前组别列表数据
+    protected $grouplist = [];
+    protected $groupdata = [];
+    //无需要权限判断的方法
+    protected $noNeedRight = ['roletree'];
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        $this->model = model('AuthGroup');
+
+        $this->childrenGroupIds = $this->auth->getChildrenGroupIds(true);
+
+        $groupList = collection(AuthGroup::where('id', 'in', $this->childrenGroupIds)->select())->toArray();
+
+        Tree::instance()->init($groupList);
+        $groupList = [];
+        if ($this->auth->isSuperAdmin()) {
+            $groupList = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0));
+        } else {
+            $groups = $this->auth->getGroups();
+            $groupIds = [];
+            foreach ($groups as $m => $n) {
+                if (in_array($n['id'], $groupIds) || in_array($n['pid'], $groupIds)) {
+                    continue;
+                }
+                $groupList = array_merge($groupList, Tree::instance()->getTreeList(Tree::instance()->getTreeArray($n['pid'])));
+                foreach ($groupList as $index => $item) {
+                    $groupIds[] = $item['id'];
+                }
+            }
+        }
+        $groupName = [];
+        foreach ($groupList as $k => $v) {
+            $groupName[$v['id']] = $v['name'];
+        }
+
+        $this->grouplist = $groupList;
+        $this->groupdata = $groupName;
+        $this->assignconfig("admin", ['id' => $this->auth->id, 'group_ids' => $this->auth->getGroupIds()]);
+
+        $this->view->assign('groupdata', $this->groupdata);
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            $list = $this->grouplist;
+            $total = count($list);
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 添加
+     */
+    public function add()
+    {
+        if ($this->request->isPost()) {
+            $this->token();
+            $params = $this->request->post("row/a", [], 'strip_tags');
+            $params['rules'] = explode(',', $params['rules']);
+            if (!in_array($params['pid'], $this->childrenGroupIds)) {
+                $this->error(__('The parent group exceeds permission limit'));
+            }
+            $parentmodel = model("AuthGroup")->get($params['pid']);
+            if (!$parentmodel) {
+                $this->error(__('The parent group can not found'));
+            }
+            // 父级别的规则节点
+            $parentrules = explode(',', $parentmodel->rules);
+            // 当前组别的规则节点
+            $currentrules = $this->auth->getRuleIds();
+            $rules = $params['rules'];
+            // 如果父组不是超级管理员则需要过滤规则节点,不能超过父组别的权限
+            $rules = in_array('*', $parentrules) ? $rules : array_intersect($parentrules, $rules);
+            // 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
+            $rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules);
+            $params['rules'] = implode(',', $rules);
+            if ($params) {
+                $this->model->create($params);
+                $this->success();
+            }
+            $this->error();
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 编辑
+     */
+    public function edit($ids = null)
+    {
+        if (!in_array($ids, $this->childrenGroupIds)) {
+            $this->error(__('You have no permission'));
+        }
+        $row = $this->model->get(['id' => $ids]);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        if ($this->request->isPost()) {
+            $this->token();
+            $params = $this->request->post("row/a", [], 'strip_tags');
+            //父节点不能是非权限内节点
+            if (!in_array($params['pid'], $this->childrenGroupIds)) {
+                $this->error(__('The parent group exceeds permission limit'));
+            }
+            // 父节点不能是它自身的子节点或自己本身
+            if (in_array($params['pid'], Tree::instance()->getChildrenIds($row->id, true))) {
+                $this->error(__('The parent group can not be its own child or itself'));
+            }
+            $params['rules'] = explode(',', $params['rules']);
+
+            $parentmodel = model("AuthGroup")->get($params['pid']);
+            if (!$parentmodel) {
+                $this->error(__('The parent group can not found'));
+            }
+            // 父级别的规则节点
+            $parentrules = explode(',', $parentmodel->rules);
+            // 当前组别的规则节点
+            $currentrules = $this->auth->getRuleIds();
+            $rules = $params['rules'];
+            // 如果父组不是超级管理员则需要过滤规则节点,不能超过父组别的权限
+            $rules = in_array('*', $parentrules) ? $rules : array_intersect($parentrules, $rules);
+            // 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
+            $rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules);
+            $params['rules'] = implode(',', $rules);
+            if ($params) {
+                Db::startTrans();
+                try {
+                    $row->save($params);
+                    $children_auth_groups = model("AuthGroup")->all(['id' => ['in', implode(',', (Tree::instance()->getChildrenIds($row->id)))]]);
+                    $childparams = [];
+                    foreach ($children_auth_groups as $key => $children_auth_group) {
+                        $childparams[$key]['id'] = $children_auth_group->id;
+                        $childparams[$key]['rules'] = implode(',', array_intersect(explode(',', $children_auth_group->rules), $rules));
+                    }
+                    model("AuthGroup")->saveAll($childparams);
+                    Db::commit();
+                    $this->success();
+                } catch (Exception $e) {
+                    Db::rollback();
+                    $this->error($e->getMessage());
+                }
+            }
+            $this->error();
+            return;
+        }
+        $this->view->assign("row", $row);
+        return $this->view->fetch();
+    }
+
+    /**
+     * 删除
+     */
+    public function del($ids = "")
+    {
+        if (!$this->request->isPost()) {
+            $this->error(__("Invalid parameters"));
+        }
+        $ids = $ids ? $ids : $this->request->post("ids");
+        if ($ids) {
+            $ids = explode(',', $ids);
+            $grouplist = $this->auth->getGroups();
+            $group_ids = array_map(function ($group) {
+                return $group['id'];
+            }, $grouplist);
+            // 移除掉当前管理员所在组别
+            $ids = array_diff($ids, $group_ids);
+
+            // 循环判断每一个组别是否可删除
+            $grouplist = $this->model->where('id', 'in', $ids)->select();
+            $groupaccessmodel = model('AuthGroupAccess');
+            foreach ($grouplist as $k => $v) {
+                // 当前组别下有管理员
+                $groupone = $groupaccessmodel->get(['group_id' => $v['id']]);
+                if ($groupone) {
+                    $ids = array_diff($ids, [$v['id']]);
+                    continue;
+                }
+                // 当前组别下有子组别
+                $groupone = $this->model->get(['pid' => $v['id']]);
+                if ($groupone) {
+                    $ids = array_diff($ids, [$v['id']]);
+                    continue;
+                }
+            }
+            if (!$ids) {
+                $this->error(__('You can not delete group that contain child group and administrators'));
+            }
+            $count = $this->model->where('id', 'in', $ids)->delete();
+            if ($count) {
+                $this->success();
+            }
+        }
+        $this->error();
+    }
+
+    /**
+     * 批量更新
+     * @internal
+     */
+    public function multi($ids = "")
+    {
+        // 组别禁止批量操作
+        $this->error();
+    }
+
+    /**
+     * 读取角色权限树
+     *
+     * @internal
+     */
+    public function roletree()
+    {
+        $this->loadlang('auth/group');
+
+        $model = model('AuthGroup');
+        $id = $this->request->post("id");
+        $pid = $this->request->post("pid");
+        $parentGroupModel = $model->get($pid);
+        $currentGroupModel = null;
+        if ($id) {
+            $currentGroupModel = $model->get($id);
+        }
+        if (($pid || $parentGroupModel) && (!$id || $currentGroupModel)) {
+            $id = $id ? $id : null;
+            $ruleList = collection(model('AuthRule')->order('weigh', 'desc')->order('id', 'asc')->select())->toArray();
+            //读取父类角色所有节点列表
+            $parentRuleList = [];
+            if (in_array('*', explode(',', $parentGroupModel->rules))) {
+                $parentRuleList = $ruleList;
+            } else {
+                $parentRuleIds = explode(',', $parentGroupModel->rules);
+                foreach ($ruleList as $k => $v) {
+                    if (in_array($v['id'], $parentRuleIds)) {
+                        $parentRuleList[] = $v;
+                    }
+                }
+            }
+
+            $ruleTree = new Tree();
+            $groupTree = new Tree();
+            //当前所有正常规则列表
+            $ruleTree->init($parentRuleList);
+            //角色组列表
+            $groupTree->init(collection(model('AuthGroup')->where('id', 'in', $this->childrenGroupIds)->select())->toArray());
+
+            //读取当前角色下规则ID集合
+            $adminRuleIds = $this->auth->getRuleIds();
+            //是否是超级管理员
+            $superadmin = $this->auth->isSuperAdmin();
+            //当前拥有的规则ID集合
+            $currentRuleIds = $id ? explode(',', $currentGroupModel->rules) : [];
+
+            if (!$id || !in_array($pid, $this->childrenGroupIds) || !in_array($pid, $groupTree->getChildrenIds($id, true))) {
+                $parentRuleList = $ruleTree->getTreeList($ruleTree->getTreeArray(0), 'name');
+                $hasChildrens = [];
+                foreach ($parentRuleList as $k => $v) {
+                    if ($v['haschild']) {
+                        $hasChildrens[] = $v['id'];
+                    }
+                }
+                $parentRuleIds = array_map(function ($item) {
+                    return $item['id'];
+                }, $parentRuleList);
+                $nodeList = [];
+                foreach ($parentRuleList as $k => $v) {
+                    if (!$superadmin && !in_array($v['id'], $adminRuleIds)) {
+                        continue;
+                    }
+                    if ($v['pid'] && !in_array($v['pid'], $parentRuleIds)) {
+                        continue;
+                    }
+                    $state = array('selected' => in_array($v['id'], $currentRuleIds) && !in_array($v['id'], $hasChildrens));
+                    $nodeList[] = array('id' => $v['id'], 'parent' => $v['pid'] ? $v['pid'] : '#', 'text' => __($v['title']), 'type' => 'menu', 'state' => $state);
+                }
+                $this->success('', null, $nodeList);
+            } else {
+                $this->error(__('Can not change the parent to child'));
+            }
+        } else {
+            $this->error(__('Group not found'));
+        }
+    }
+}

+ 159 - 0
application/company/controller/auth/Rule.php

@@ -0,0 +1,159 @@
+<?php
+
+namespace app\admin\controller\auth;
+
+use app\admin\model\AuthRule;
+use app\common\controller\Backend;
+use fast\Tree;
+use think\Cache;
+
+/**
+ * 规则管理
+ *
+ * @icon   fa fa-list
+ * @remark 规则通常对应一个控制器的方法,同时左侧的菜单栏数据也从规则中体现,通常建议通过控制台进行生成规则节点
+ */
+class Rule extends Backend
+{
+
+    /**
+     * @var \app\admin\model\AuthRule
+     */
+    protected $model = null;
+    protected $rulelist = [];
+    protected $multiFields = 'ismenu,status';
+
+    public function _initialize()
+    {
+        parent::_initialize();
+        if (!$this->auth->isSuperAdmin()) {
+            $this->error(__('Access is allowed only to the super management group'));
+        }
+        $this->model = model('AuthRule');
+        // 必须将结果集转换为数组
+        $ruleList = \think\Db::name("auth_rule")->field('type,condition,remark,createtime,updatetime', true)->order('weigh DESC,id ASC')->select();
+        foreach ($ruleList as $k => &$v) {
+            $v['title'] = __($v['title']);
+        }
+        unset($v);
+        Tree::instance()->init($ruleList)->icon = ['&nbsp;&nbsp;&nbsp;&nbsp;', '&nbsp;&nbsp;&nbsp;&nbsp;', '&nbsp;&nbsp;&nbsp;&nbsp;'];
+        $this->rulelist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'title');
+        $ruledata = [0 => __('None')];
+        foreach ($this->rulelist as $k => &$v) {
+            if (!$v['ismenu']) {
+                continue;
+            }
+            $ruledata[$v['id']] = $v['title'];
+            unset($v['spacer']);
+        }
+        unset($v);
+        $this->view->assign('ruledata', $ruledata);
+        $this->view->assign("menutypeList", $this->model->getMenutypeList());
+    }
+
+    /**
+     * 查看
+     */
+    public function index()
+    {
+        if ($this->request->isAjax()) {
+            $list = $this->rulelist;
+            $total = count($this->rulelist);
+            $result = array("total" => $total, "rows" => $list);
+
+            return json($result);
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 添加
+     */
+    public function add()
+    {
+        if ($this->request->isPost()) {
+            $this->token();
+            $params = $this->request->post("row/a", [], 'strip_tags');
+            if ($params) {
+                if (!$params['ismenu'] && !$params['pid']) {
+                    $this->error(__('The non-menu rule must have parent'));
+                }
+                $result = $this->model->validate()->save($params);
+                if ($result === false) {
+                    $this->error($this->model->getError());
+                }
+                Cache::rm('__menu__');
+                $this->success();
+            }
+            $this->error();
+        }
+        return $this->view->fetch();
+    }
+
+    /**
+     * 编辑
+     */
+    public function edit($ids = null)
+    {
+        $row = $this->model->get(['id' => $ids]);
+        if (!$row) {
+            $this->error(__('No Results were found'));
+        }
+        if ($this->request->isPost()) {
+            $this->token();
+            $params = $this->request->post("row/a", [], 'strip_tags');
+            if ($params) {
+                if (!$params['ismenu'] && !$params['pid']) {
+                    $this->error(__('The non-menu rule must have parent'));
+                }
+                if ($params['pid'] == $row['id']) {
+                    $this->error(__('Can not change the parent to self'));
+                }
+                if ($params['pid'] != $row['pid']) {
+                    $childrenIds = Tree::instance()->init(collection(AuthRule::select())->toArray())->getChildrenIds($row['id']);
+                    if (in_array($params['pid'], $childrenIds)) {
+                        $this->error(__('Can not change the parent to child'));
+                    }
+                }
+                //这里需要针对name做唯一验证
+                $ruleValidate = \think\Loader::validate('AuthRule');
+                $ruleValidate->rule([
+                    'name' => 'require|unique:AuthRule,name,' . $row->id,
+                ]);
+                $result = $row->validate()->save($params);
+                if ($result === false) {
+                    $this->error($row->getError());
+                }
+                Cache::rm('__menu__');
+                $this->success();
+            }
+            $this->error();
+        }
+        $this->view->assign("row", $row);
+        return $this->view->fetch();
+    }
+
+    /**
+     * 删除
+     */
+    public function del($ids = "")
+    {
+        if (!$this->request->isPost()) {
+            $this->error(__("Invalid parameters"));
+        }
+        $ids = $ids ? $ids : $this->request->post("ids");
+        if ($ids) {
+            $delIds = [];
+            foreach (explode(',', $ids) as $k => $v) {
+                $delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, true));
+            }
+            $delIds = array_unique($delIds);
+            $count = $this->model->where('id', 'in', $delIds)->delete();
+            if ($count) {
+                Cache::rm('__menu__');
+                $this->success();
+            }
+        }
+        $this->error();
+    }
+}

+ 14 - 0
application/company/lang/zh-cn/auth/admin.php

@@ -0,0 +1,14 @@
+<?php
+
+return [
+    'Email'                                     => '电子邮箱',
+    'Mobile'                                    => '手机号',
+    'Group'                                     => '所属组别',
+    'Loginfailure'                              => '登录失败次数',
+    'Login time'                                => '最后登录',
+    'The parent group exceeds permission limit' => '父组别超出权限范围',
+    'Please input correct username'             => '用户名只能由3-30位数字、字母、下划线组合',
+    'Username must be 3 to 30 characters'       => '用户名只能由3-30位数字、字母、下划线组合',
+    'Please input correct password'             => '密码长度必须在6-30位之间,不能包含空格',
+    'Password must be 6 to 30 characters'       => '密码长度必须在6-30位之间,不能包含空格',
+];

+ 12 - 0
application/company/lang/zh-cn/auth/group.php

@@ -0,0 +1,12 @@
+<?php
+
+return [
+    'The parent group can not be its own child'                            => '父组别不能是自身的子组别',
+    'The parent group can not found'                                       => '父组别未找到',
+    'Group not found'                                                      => '组别未找到',
+    'Can not change the parent to child'                                   => '父组别不能是它的子组别',
+    'Can not change the parent to self'                                    => '父组别不能是它自己',
+    'You can not delete group that contain child group and administrators' => '你不能删除含有子组和管理员的组',
+    'The parent group exceeds permission limit'                            => '父组别超出权限范围',
+    'The parent group can not be its own child or itself'                  => '父组别不能是它的子组别及本身',
+];

+ 28 - 0
application/company/lang/zh-cn/auth/rule.php

@@ -0,0 +1,28 @@
+<?php
+
+return [
+    'Toggle all'                                                => '显示全部',
+    'Condition'                                                 => '规则条件',
+    'Remark'                                                    => '备注',
+    'Icon'                                                      => '图标',
+    'Alert'                                                     => '警告',
+    'Name'                                                      => '规则',
+    'Controller/Action'                                         => '控制器名/方法名',
+    'Ismenu'                                                    => '菜单',
+    'Menutype'                                                  => '菜单类型',
+    'Addtabs'                                                   => '选项卡(默认)',
+    'Dialog'                                                    => '弹窗',
+    'Ajax'                                                      => 'Ajax请求',
+    'Blank'                                                     => '链接',
+    'Extend'                                                    => '扩展属性',
+    'Search icon'                                               => '搜索图标',
+    'Toggle menu visible'                                       => '点击切换菜单显示',
+    'Toggle sub menu'                                           => '点击切换子菜单',
+    'Menu tips'                                                 => '父级菜单无需匹配控制器和方法,子级菜单请使用控制器名',
+    'Node tips'                                                 => '控制器/方法名,如果有目录请使用 目录名/控制器名/方法名',
+    'Url tips'                                                  => '一般情况下留空即可,如果是外部链接或相对链接请输入',
+    'The non-menu rule must have parent'                        => '非菜单规则节点必须有父级',
+    'Can not change the parent to child'                        => '父级不能是它的子级',
+    'Can not change the parent to self'                         => '父级不能是它自己',
+    'Name only supports letters, numbers, underscore and slash' => 'URL规则只能是小写字母、数字、下划线和/组成',
+];

+ 32 - 0
application/company/model/Admin.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace app\admin\model;
+
+use think\Model;
+use think\Session;
+
+class Admin extends Model
+{
+
+    // 开启自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = 'updatetime';
+    protected $hidden = [
+        'password',
+        'salt'
+    ];
+
+    public static function init()
+    {
+        self::beforeWrite(function ($row) {
+            $changed = $row->getChangedData();
+            //如果修改了用户或或密码则需要重新登录
+            if (isset($changed['username']) || isset($changed['password']) || isset($changed['salt'])) {
+                $row->token = '';
+            }
+        });
+    }
+
+}

+ 117 - 0
application/company/model/AdminLog.php

@@ -0,0 +1,117 @@
+<?php
+
+namespace app\admin\model;
+
+use app\admin\library\Auth;
+use think\Model;
+use think\Loader;
+
+class AdminLog extends Model
+{
+
+    // 开启自动写入时间戳字段
+    protected $autoWriteTimestamp = 'int';
+    // 定义时间戳字段名
+    protected $createTime = 'createtime';
+    protected $updateTime = '';
+    //自定义日志标题
+    protected static $title = '';
+    //自定义日志内容
+    protected static $content = '';
+    //忽略的链接正则列表
+    protected static $ignoreRegex = [
+        '/^(.*)\/(selectpage|index)$/i',
+    ];
+
+    public static function setTitle($title)
+    {
+        self::$title = $title;
+    }
+
+    public static function setContent($content)
+    {
+        self::$content = $content;
+    }
+
+    public static function setIgnoreRegex($regex = [])
+    {
+        $regex = is_array($regex) ? $regex : [$regex];
+        self::$ignoreRegex = array_merge(self::$ignoreRegex, $regex);
+    }
+
+    /**
+     * 记录日志
+     * @param string $title   日志标题
+     * @param string $content 日志内容
+     */
+    public static function record($title = '', $content = '')
+    {
+        $auth = Auth::instance();
+        $admin_id = $auth->isLogin() ? $auth->id : 0;
+        $username = $auth->isLogin() ? $auth->username : __('Unknown');
+
+        // 设置过滤函数
+        request()->filter('trim,strip_tags,htmlspecialchars');
+
+        $controllername = Loader::parseName(request()->controller());
+        $actionname = strtolower(request()->action());
+        $path = str_replace('.', '/', $controllername) . '/' . $actionname;
+        if (self::$ignoreRegex) {
+            foreach (self::$ignoreRegex as $index => $item) {
+                if (preg_match($item, $path)) {
+                    return;
+                }
+            }
+        }
+        $content = $content ?: self::$content;
+        if (!$content) {
+            $content = request()->param('') ?: file_get_contents("php://input");
+            $content = self::getPureContent($content);
+        }
+        $title = $title ?: self::$title;
+        if (!$title) {
+            $title = [];
+            $breadcrumb = Auth::instance()->getBreadcrumb($path);
+            foreach ($breadcrumb as $k => $v) {
+                $title[] = $v['title'];
+            }
+            $title = implode(' / ', $title);
+        }
+        self::create([
+            'title'     => $title,
+            'content'   => !is_scalar($content) ? json_encode($content, JSON_UNESCAPED_UNICODE) : $content,
+            'url'       => substr(xss_clean(strip_tags(request()->url())), 0, 1500),
+            'admin_id'  => $admin_id,
+            'username'  => $username,
+            'useragent' => substr(request()->server('HTTP_USER_AGENT'), 0, 255),
+            'ip'        => xss_clean(strip_tags(request()->ip()))
+        ]);
+    }
+
+    /**
+     * 获取已屏蔽关键信息的数据
+     * @param $content
+     * @return array
+     */
+    protected static function getPureContent($content)
+    {
+        if (!is_array($content)) {
+            return $content;
+        }
+        foreach ($content as $index => &$item) {
+            if (preg_match("/(password|salt|token)/i", $index)) {
+                $item = "***";
+            } else {
+                if (is_array($item)) {
+                    $item = self::getPureContent($item);
+                }
+            }
+        }
+        return $content;
+    }
+
+    public function admin()
+    {
+        return $this->belongsTo('Admin', 'admin_id')->setEagerlyType(0);
+    }
+}

+ 21 - 0
application/company/model/AuthGroup.php

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

+ 10 - 0
application/company/model/AuthGroupAccess.php

@@ -0,0 +1,10 @@
+<?php
+
+namespace app\admin\model;
+
+use think\Model;
+
+class AuthGroupAccess extends Model
+{
+    //
+}

+ 62 - 0
application/company/model/AuthRule.php

@@ -0,0 +1,62 @@
+<?php
+
+namespace app\admin\model;
+
+use think\Cache;
+use think\Model;
+
+class AuthRule extends Model
+{
+
+    // 开启自动写入时间戳字段
+    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 '';
+    }
+}

+ 57 - 0
application/company/validate/Admin.php

@@ -0,0 +1,57 @@
+<?php
+
+namespace app\admin\validate;
+
+use think\Validate;
+
+class Admin extends Validate
+{
+
+    /**
+     * 验证规则
+     */
+    protected $rule = [
+        'username' => 'require|regex:\w{3,30}|unique:admin',
+        'nickname' => 'require',
+        'password' => 'require|regex:\S{32}',
+        'email'    => 'require|email|unique:admin,email',
+        'mobile'   => 'regex:1[3-9]\d{9}|unique:admin,mobile',
+    ];
+
+    /**
+     * 提示消息
+     */
+    protected $message = [
+    ];
+
+    /**
+     * 字段描述
+     */
+    protected $field = [
+    ];
+
+    /**
+     * 验证场景
+     */
+    protected $scene = [
+        'add'  => ['username', 'email', 'nickname', 'password', 'mobile'],
+        'edit' => ['username', 'email', 'nickname', 'password', 'mobile'],
+    ];
+
+    public function __construct(array $rules = [], $message = [], $field = [])
+    {
+        $this->field = [
+            'username' => __('Username'),
+            'nickname' => __('Nickname'),
+            'password' => __('Password'),
+            'email'    => __('Email'),
+            'mobile'   => __('Mobile'),
+        ];
+        $this->message = array_merge($this->message, [
+            'username.regex' => __('Please input correct username'),
+            'password.regex' => __('Please input correct password')
+        ]);
+        parent::__construct($rules, $message, $field);
+    }
+
+}

+ 52 - 0
application/company/validate/AuthRule.php

@@ -0,0 +1,52 @@
+<?php
+
+namespace app\admin\validate;
+
+use think\Validate;
+
+class AuthRule extends Validate
+{
+
+    /**
+     * 正则
+     */
+    protected $regex = ['format' => '[a-z0-9_\/]+'];
+
+    /**
+     * 验证规则
+     */
+    protected $rule = [
+        'name'  => 'require|unique:AuthRule',
+        'title' => 'require',
+    ];
+
+    /**
+     * 提示消息
+     */
+    protected $message = [
+        'name.format' => 'URL规则只能是小写字母、数字、下划线和/组成'
+    ];
+
+    /**
+     * 字段描述
+     */
+    protected $field = [
+    ];
+
+    /**
+     * 验证场景
+     */
+    protected $scene = [
+    ];
+
+    public function __construct(array $rules = [], $message = [], $field = [])
+    {
+        $this->field = [
+            'name'  => __('Name'),
+            'title' => __('Title'),
+        ];
+        $this->message['name.format'] = __('Name only supports letters, numbers, underscore and slash');
+        parent::__construct($rules, $message, $field);
+    }
+
+}